Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cd14469d79 | |||
| b846d352a2 | |||
|
|
a24f41a8d5 | ||
|
|
65b4a3ac34 |
@@ -37,8 +37,8 @@ android {
|
|||||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||||
minSdk = flutter.minSdkVersion
|
minSdk = flutter.minSdkVersion
|
||||||
targetSdk = flutter.targetSdkVersion
|
targetSdk = flutter.targetSdkVersion
|
||||||
versionCode = 7
|
versionCode = 6
|
||||||
versionName = "1.2.4"
|
versionName = "1.2.3"
|
||||||
}
|
}
|
||||||
|
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
@@ -68,3 +68,8 @@ android {
|
|||||||
flutter {
|
flutter {
|
||||||
source = "../.."
|
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")
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,39 +1,53 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||||
android:name="android.permission.READ_EXTERNAL_STORAGE"
|
|
||||||
android:maxSdkVersion="32" />
|
android:maxSdkVersion="32" />
|
||||||
<uses-permission
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
|
||||||
android:maxSdkVersion="32" />
|
android:maxSdkVersion="32" />
|
||||||
<uses-permission
|
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
|
||||||
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
|
|
||||||
android:maxSdkVersion="32" />
|
android:maxSdkVersion="32" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||||
<uses-permission android:name="android.permission.CAMERA" />
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||||
<!--定位权限-->
|
<!--定位权限-->
|
||||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
||||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
|
<!--用于申请调用A-GPS模块-->
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"></uses-permission>
|
||||||
|
<!--如果设置了target >= 28 如果需要启动后台定位则必须声明这个权限-->
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||||
|
<!--如果您的应用需要后台定位权限,且有可能运行在Android Q设备上,并且设置了target>28,必须增加这个权限声明-->
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
|
||||||
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||||
|
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:requestLegacyExternalStorage="true"
|
android:label="小羚羚"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
android:icon="@mipmap/logo"
|
android:icon="@mipmap/logo">
|
||||||
android:label="小羚羚">
|
|
||||||
|
<!-- 高德地图Key -->
|
||||||
|
<meta-data
|
||||||
|
android:name="com.amap.api.v2.apikey"
|
||||||
|
android:value="92495660f7bc990cb475426c47c03b65" />
|
||||||
|
|
||||||
|
<!-- 高德地图定位服务 -->
|
||||||
|
<service android:name="com.amap.api.location.APSService" />
|
||||||
|
<!--高德导航-->
|
||||||
|
<activity
|
||||||
|
android:name="com.amap.api.navi.AmapRouteActivity"
|
||||||
|
android:theme="@android:style/Theme.NoTitleBar"
|
||||||
|
android:configChanges="orientation|keyboardHidden|screenSize|navigation" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:hardwareAccelerated="true"
|
|
||||||
android:launchMode="singleTop"
|
android:launchMode="singleTop"
|
||||||
android:taskAffinity=""
|
android:taskAffinity=""
|
||||||
android:theme="@style/LaunchTheme"
|
android:theme="@style/LaunchTheme"
|
||||||
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||||
|
android:hardwareAccelerated="true"
|
||||||
android:windowSoftInputMode="adjustResize">
|
android:windowSoftInputMode="adjustResize">
|
||||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||||
the Android process has started. This theme is visible to the user
|
the Android process has started. This theme is visible to the user
|
||||||
@@ -41,10 +55,11 @@
|
|||||||
to determine the Window background behind the Flutter UI. -->
|
to determine the Window background behind the Flutter UI. -->
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="io.flutter.embedding.android.NormalTheme"
|
android:name="io.flutter.embedding.android.NormalTheme"
|
||||||
android:resource="@style/NormalTheme" />
|
android:resource="@style/NormalTheme"
|
||||||
|
/>
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<!-- Don't delete the meta-data below.
|
<!-- Don't delete the meta-data below.
|
||||||
@@ -54,13 +69,32 @@
|
|||||||
android:value="2" />
|
android:value="2" />
|
||||||
|
|
||||||
<!-- 请填写你自己的- appKey -->
|
<!-- 请填写你自己的- appKey -->
|
||||||
<meta-data
|
<meta-data android:name="com.alibaba.app.appkey" android:value="335642645"/>
|
||||||
android:name="com.alibaba.app.appkey"
|
|
||||||
android:value="335642645" />
|
|
||||||
<!-- 请填写你自己的appSecret -->
|
<!-- 请填写你自己的appSecret -->
|
||||||
<meta-data
|
<meta-data android:name="com.alibaba.app.appsecret" android:value="39628204345a4240b5b645b68a5896c7"/>
|
||||||
android:name="com.alibaba.app.appsecret"
|
<!-- 华为通道的参数appid -->
|
||||||
android:value="39628204345a4240b5b645b68a5896c7" />
|
<meta-data android:name="com.huawei.hms.client.appid" android:value="" />
|
||||||
|
|
||||||
|
<!-- vivo通道的参数api_key为appkey -->
|
||||||
|
<meta-data android:name="com.vivo.push.api_key" android:value="" />
|
||||||
|
<meta-data android:name="com.vivo.push.app_id" android:value="" />
|
||||||
|
|
||||||
|
<!-- honor通道的参数-->
|
||||||
|
<meta-data android:name="com.hihonor.push.app_id" android:value="" />
|
||||||
|
|
||||||
|
<!-- oppo -->
|
||||||
|
<meta-data android:name="com.oppo.push.key" android:value="" />
|
||||||
|
<meta-data android:name="com.oppo.push.secret" android:value="" />
|
||||||
|
|
||||||
|
<!-- 小米-->
|
||||||
|
<meta-data android:name="com.xiaomi.push.id" android:value="id=2222222222222222222" />
|
||||||
|
<meta-data android:name="com.xiaomi.push.key" android:value="id=5555555555555" />
|
||||||
|
|
||||||
|
<!-- 魅族-->
|
||||||
|
<meta-data android:name="com.meizu.push.id" android:value="" />
|
||||||
|
<meta-data android:name="com.meizu.push.key" android:value="" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- 接收推送消息 -->
|
<!-- 接收推送消息 -->
|
||||||
<receiver
|
<receiver
|
||||||
@@ -83,7 +117,6 @@
|
|||||||
android:exported="true">
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
@@ -102,8 +135,8 @@
|
|||||||
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
||||||
<queries>
|
<queries>
|
||||||
<intent>
|
<intent>
|
||||||
<action android:name="android.intent.action.PROCESS_TEXT" />
|
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||||
<data android:mimeType="text/plain" />
|
<data android:mimeType="text/plain"/>
|
||||||
</intent>
|
</intent>
|
||||||
</queries>
|
</queries>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -1,6 +1,195 @@
|
|||||||
package com.lnkj.ln_jq_app;
|
package com.lnkj.ln_jq_app;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
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.android.FlutterActivity;
|
||||||
|
import io.flutter.embedding.engine.FlutterEngine;
|
||||||
|
import io.flutter.plugin.common.MethodChannel;
|
||||||
|
|
||||||
public class MainActivity extends FlutterActivity {
|
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)
|
||||||
|
);
|
||||||
|
|
||||||
|
// 注册方法通道用于地图控制
|
||||||
|
new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
|
||||||
|
.setMethodCallHandler((call, result) -> {
|
||||||
|
switch (call.method) {
|
||||||
|
case "requestPermissions":
|
||||||
|
requestPermissions();
|
||||||
|
result.success(null);
|
||||||
|
break;
|
||||||
|
case "onResume":
|
||||||
|
if (mapView != null) {
|
||||||
|
mapView.onResume();
|
||||||
|
}
|
||||||
|
result.success(null);
|
||||||
|
break;
|
||||||
|
case "onPause":
|
||||||
|
if (mapView != null) {
|
||||||
|
mapView.onPause();
|
||||||
|
}
|
||||||
|
result.success(null);
|
||||||
|
break;
|
||||||
|
case "onDestroy":
|
||||||
|
if (mapView != null) {
|
||||||
|
mapView.dispose();
|
||||||
|
mapView = null;
|
||||||
|
}
|
||||||
|
result.success(null);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
result.notImplemented();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前系统版本需要申请的权限列表
|
||||||
|
*/
|
||||||
|
private String[] getRequiredPermissions() {
|
||||||
|
List<String> permissions = new ArrayList<>();
|
||||||
|
// 定位权限是必须的
|
||||||
|
permissions.add(Manifest.permission.ACCESS_FINE_LOCATION);
|
||||||
|
permissions.add(Manifest.permission.ACCESS_COARSE_LOCATION);
|
||||||
|
|
||||||
|
// 存储权限处理:Android 13 (API 33) 以下才需要申请 legacy 存储权限
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
|
||||||
|
permissions.add(Manifest.permission.READ_EXTERNAL_STORAGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return permissions.toArray(new String[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查并申请权限
|
||||||
|
*/
|
||||||
|
private void checkAndRequestPermissions() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
String[] requiredPermissions = getRequiredPermissions();
|
||||||
|
List<String> deniedPermissions = new ArrayList<>();
|
||||||
|
|
||||||
|
for (String permission : requiredPermissions) {
|
||||||
|
if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
|
||||||
|
deniedPermissions.add(permission);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!deniedPermissions.isEmpty()) {
|
||||||
|
ActivityCompat.requestPermissions(
|
||||||
|
this,
|
||||||
|
deniedPermissions.toArray(new String[0]),
|
||||||
|
PERMISSION_REQUEST_CODE
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "所有必要权限已授予");
|
||||||
|
if (mapView != null) {
|
||||||
|
mapView.startLocation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (mapView != null) {
|
||||||
|
mapView.startLocation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void requestPermissions() {
|
||||||
|
checkAndRequestPermissions();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMapView(NativeMapView mapView) {
|
||||||
|
this.mapView = mapView;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
// 注意:高德SDK合规检查通过后再进行定位相关操作
|
||||||
|
// 这里仅保留地图生命周期调用,权限建议在Flutter端或按需触发
|
||||||
|
if (mapView != null) {
|
||||||
|
mapView.onResume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
if (mapView != null) {
|
||||||
|
mapView.onPause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
if (mapView != null) {
|
||||||
|
mapView.dispose();
|
||||||
|
mapView = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSaveInstanceState(@NonNull Bundle outState) {
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
if (mapView != null) {
|
||||||
|
mapView.onSaveInstanceState(outState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||||
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||||
|
|
||||||
|
if (requestCode == PERMISSION_REQUEST_CODE) {
|
||||||
|
boolean locationGranted = false;
|
||||||
|
for (int i = 0; i < permissions.length; i++) {
|
||||||
|
if (Manifest.permission.ACCESS_FINE_LOCATION.equals(permissions[i])
|
||||||
|
&& grantResults[i] == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
locationGranted = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (locationGranted) {
|
||||||
|
if (mapView != null) {
|
||||||
|
mapView.startLocation();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 只有在定位权限确实被拒绝时才弹出提示
|
||||||
|
Toast.makeText(this, "请授予应用定位权限以正常使用地图功能", Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,695 @@
|
|||||||
|
package com.lnkj.ln_jq_app;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.drawable.GradientDrawable;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.util.TypedValue;
|
||||||
|
import android.view.Gravity;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.inputmethod.EditorInfo;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.ImageButton;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
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.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.INaviInfoCallback;
|
||||||
|
import com.amap.api.navi.enums.PathPlanningStrategy;
|
||||||
|
import com.amap.api.navi.model.AMapCarInfo;
|
||||||
|
import com.amap.api.navi.model.AMapNaviLocation;
|
||||||
|
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.route.BusRouteResult;
|
||||||
|
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.List;
|
||||||
|
|
||||||
|
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 {
|
||||||
|
|
||||||
|
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; // 保存Activity引用用于导航
|
||||||
|
private GeocodeSearch geocoderSearch;
|
||||||
|
private RouteSearch routeSearch;
|
||||||
|
private final OkHttpClient httpClient = new OkHttpClient();
|
||||||
|
|
||||||
|
// UI组件
|
||||||
|
private EditText startInput;
|
||||||
|
private EditText endInput;
|
||||||
|
private LatLng currentLatLng;
|
||||||
|
private String startName = "我的位置";
|
||||||
|
private String endName = "目的地";
|
||||||
|
private LatLng startPoint;
|
||||||
|
private LatLng endPoint;
|
||||||
|
private boolean isFirstLocation = true;
|
||||||
|
private final List<Marker> stationMarkers = new ArrayList<>();
|
||||||
|
|
||||||
|
public NativeMapView(Context context, int id, Object args) {
|
||||||
|
this.mContext = context;
|
||||||
|
|
||||||
|
// 尝试获取Activity引用
|
||||||
|
mActivity = getActivityFromContext(context);
|
||||||
|
|
||||||
|
MapsInitializer.updatePrivacyShow(context, true, true);
|
||||||
|
MapsInitializer.updatePrivacyAgree(context, true);
|
||||||
|
|
||||||
|
container = new FrameLayout(context);
|
||||||
|
container.setClickable(true);
|
||||||
|
container.setFocusable(true);
|
||||||
|
|
||||||
|
mapView = new MapView(context);
|
||||||
|
mapView.onCreate(null);
|
||||||
|
aMap = mapView.getMap();
|
||||||
|
|
||||||
|
container.addView(mapView, new FrameLayout.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||||
|
|
||||||
|
initServices(context);
|
||||||
|
initOverlays(context);
|
||||||
|
setupMapUi();
|
||||||
|
|
||||||
|
// 通知MainActivity
|
||||||
|
if (context instanceof MainActivity) {
|
||||||
|
((MainActivity) context).setMapView(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(TAG, "NativeMapView初始化完成");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化服务
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化覆盖层UI
|
||||||
|
*/
|
||||||
|
private void initOverlays(Context context) {
|
||||||
|
LinearLayout searchBox = new LinearLayout(context);
|
||||||
|
searchBox.setOrientation(LinearLayout.VERTICAL);
|
||||||
|
searchBox.setBackground(getRoundedDrawable(Color.WHITE, 12));
|
||||||
|
searchBox.setElevation(dp2px(8));
|
||||||
|
int p = dp2px(15);
|
||||||
|
searchBox.setPadding(p, p, p, p);
|
||||||
|
searchBox.setClickable(true);
|
||||||
|
searchBox.setFocusable(true);
|
||||||
|
|
||||||
|
startInput = createInput(context, "起点: 正在定位...");
|
||||||
|
searchBox.addView(startInput);
|
||||||
|
|
||||||
|
View vSpace = new View(context);
|
||||||
|
searchBox.addView(vSpace, new LinearLayout.LayoutParams(1, dp2px(10)));
|
||||||
|
|
||||||
|
LinearLayout endRow = new LinearLayout(context);
|
||||||
|
endRow.setOrientation(LinearLayout.HORIZONTAL);
|
||||||
|
endRow.setGravity(Gravity.CENTER_VERTICAL);
|
||||||
|
|
||||||
|
endInput = createInput(context, "终点: 请输入目的地");
|
||||||
|
endRow.addView(endInput, new LinearLayout.LayoutParams(0, dp2px(42), 1f));
|
||||||
|
|
||||||
|
Button routeBtn = new Button(context);
|
||||||
|
routeBtn.setText("路径规划");
|
||||||
|
routeBtn.setTextColor(Color.WHITE);
|
||||||
|
routeBtn.setAllCaps(false);
|
||||||
|
routeBtn.setTextSize(14);
|
||||||
|
routeBtn.setBackground(getRoundedDrawable(Color.parseColor("#017143"), 6));
|
||||||
|
routeBtn.setOnClickListener(v -> startRouteSearch());
|
||||||
|
endRow.addView(routeBtn, new LinearLayout.LayoutParams(dp2px(90), dp2px(42)));
|
||||||
|
|
||||||
|
searchBox.addView(endRow);
|
||||||
|
|
||||||
|
FrameLayout.LayoutParams searchParams = new FrameLayout.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||||
|
searchParams.setMargins(dp2px(15), dp2px(50), dp2px(15), 0);
|
||||||
|
container.addView(searchBox, searchParams);
|
||||||
|
|
||||||
|
ImageButton locBtn = new ImageButton(context);
|
||||||
|
locBtn.setImageResource(android.R.drawable.ic_menu_mylocation);
|
||||||
|
locBtn.setBackground(getRoundedDrawable(Color.WHITE, 30));
|
||||||
|
locBtn.setElevation(dp2px(4));
|
||||||
|
locBtn.setOnClickListener(v -> {
|
||||||
|
if (currentLatLng != null) {
|
||||||
|
aMap.animateCamera(CameraUpdateFactory.newLatLngZoom(currentLatLng, 15f));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
FrameLayout.LayoutParams locParams = new FrameLayout.LayoutParams(dp2px(50), dp2px(50));
|
||||||
|
locParams.setMargins(0, 0, dp2px(20), dp2px(120));
|
||||||
|
locParams.gravity = Gravity.BOTTOM | Gravity.END;
|
||||||
|
container.addView(locBtn, locParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
private EditText createInput(Context context, String hint) {
|
||||||
|
EditText et = new EditText(context);
|
||||||
|
et.setHint(hint);
|
||||||
|
et.setTextSize(14f);
|
||||||
|
et.setTextColor(Color.BLACK);
|
||||||
|
et.setPadding(dp2px(12), dp2px(10), dp2px(12), dp2px(10));
|
||||||
|
et.setBackground(getRoundedDrawable(Color.parseColor("#F5F5F5"), 6));
|
||||||
|
et.setSingleLine(true);
|
||||||
|
et.setImeOptions(EditorInfo.IME_ACTION_DONE);
|
||||||
|
et.setFocusable(true);
|
||||||
|
et.setFocusableInTouchMode(true);
|
||||||
|
return et;
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupMapUi() {
|
||||||
|
aMap.setLocationSource(this);
|
||||||
|
aMap.setMyLocationEnabled(true);
|
||||||
|
aMap.setOnMarkerClickListener(this);
|
||||||
|
|
||||||
|
MyLocationStyle myLocationStyle = new MyLocationStyle();
|
||||||
|
|
||||||
|
// --- 放大定位图标 ---
|
||||||
|
try {
|
||||||
|
Bitmap carBitmap = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.car);
|
||||||
|
if (carBitmap != null) {
|
||||||
|
// 放大到 80dp
|
||||||
|
int iconSize = dp2px(25);
|
||||||
|
Bitmap scaledBitmap = Bitmap.createScaledBitmap(carBitmap, iconSize, iconSize, true);
|
||||||
|
myLocationStyle.myLocationIcon(BitmapDescriptorFactory.fromBitmap(scaledBitmap));
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "设置大图标失败", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
myLocationStyle.anchor(0.5f, 0.5f);
|
||||||
|
myLocationStyle.myLocationType(MyLocationStyle.LOCATION_TYPE_LOCATION_ROTATE_NO_CENTER);
|
||||||
|
myLocationStyle.showMyLocation(true);
|
||||||
|
myLocationStyle.strokeColor(Color.TRANSPARENT);
|
||||||
|
myLocationStyle.radiusFillColor(Color.TRANSPARENT);
|
||||||
|
aMap.setMyLocationStyle(myLocationStyle);
|
||||||
|
|
||||||
|
aMap.getUiSettings().setZoomControlsEnabled(true);
|
||||||
|
aMap.getUiSettings().setScaleControlsEnabled(true);
|
||||||
|
aMap.getUiSettings().setLogoPosition(AMapOptions.LOGO_POSITION_BOTTOM_LEFT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== LocationSource 接口实现 ====================
|
||||||
|
@Override
|
||||||
|
public void activate(OnLocationChangedListener listener) {
|
||||||
|
mListener = listener;
|
||||||
|
startLocation();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deactivate() {
|
||||||
|
mListener = null;
|
||||||
|
if (mlocationClient != null) {
|
||||||
|
mlocationClient.stopLocation();
|
||||||
|
mlocationClient.onDestroy();
|
||||||
|
}
|
||||||
|
mlocationClient = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startLocation() {
|
||||||
|
if (mlocationClient == null) {
|
||||||
|
try {
|
||||||
|
mlocationClient = new AMapLocationClient(mContext);
|
||||||
|
AMapLocationClientOption option = new AMapLocationClientOption();
|
||||||
|
mlocationClient.setLocationListener(this);
|
||||||
|
option.setLocationMode(AMapLocationClientOption.AMapLocationMode.Hight_Accuracy);
|
||||||
|
mlocationClient.setLocationOption(option);
|
||||||
|
mlocationClient.startLocation();
|
||||||
|
Log.d(TAG, "定位启动成功");
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "定位启动失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLocationChanged(AMapLocation loc) {
|
||||||
|
if (loc != null) {
|
||||||
|
if (mListener != null) {
|
||||||
|
mListener.onLocationChanged(loc);
|
||||||
|
}
|
||||||
|
currentLatLng = new LatLng(loc.getLatitude(), loc.getLongitude());
|
||||||
|
|
||||||
|
if (loc.getErrorCode() == 0) {
|
||||||
|
if (isFirstLocation) {
|
||||||
|
isFirstLocation = false;
|
||||||
|
startPoint = currentLatLng;
|
||||||
|
aMap.moveCamera(CameraUpdateFactory.newLatLngZoom(currentLatLng, 14f));
|
||||||
|
getAddressByLatlng(currentLatLng);
|
||||||
|
fetchRecommendStation(loc);
|
||||||
|
fetchNearbyStations(loc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 逆地理编码 ====================
|
||||||
|
private void getAddressByLatlng(LatLng latLng) {
|
||||||
|
LatLonPoint point = new LatLonPoint(latLng.latitude, latLng.longitude);
|
||||||
|
RegeocodeQuery query = new RegeocodeQuery(point, 200, GeocodeSearch.AMAP);
|
||||||
|
geocoderSearch.getFromLocationAsyn(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRegeocodeSearched(RegeocodeResult result, int rCode) {
|
||||||
|
if (rCode == AMapException.CODE_AMAP_SUCCESS && result != null && result.getRegeocodeAddress() != null) {
|
||||||
|
RegeocodeAddress addr = result.getRegeocodeAddress();
|
||||||
|
String fullAddr = addr.getFormatAddress();
|
||||||
|
|
||||||
|
// 优化地址显示逻辑
|
||||||
|
startName = formatAddress(fullAddr,result);
|
||||||
|
|
||||||
|
new Handler(Looper.getMainLooper()).post(() -> {
|
||||||
|
startInput.setText(startName);
|
||||||
|
startInput.setSelection(startName.length()); // 光标移到末尾
|
||||||
|
});
|
||||||
|
|
||||||
|
Log.d(TAG, "逆地理编码成功: " + startName);
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "逆地理编码失败: code=" + rCode + ", result=" + (result != null ? "null" : "has data"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化地址显示,移除重复的前缀并限制长度
|
||||||
|
*/
|
||||||
|
private String formatAddress(String fullAddress,RegeocodeResult result) {
|
||||||
|
if (fullAddress == null || fullAddress.isEmpty()) {
|
||||||
|
return "未知地点";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取各级地址信息
|
||||||
|
String province = null;
|
||||||
|
String city = null;
|
||||||
|
String district = null;
|
||||||
|
String township = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (result.getRegeocodeAddress() != null) {
|
||||||
|
RegeocodeAddress addr = result.getRegeocodeAddress();
|
||||||
|
province = addr.getProvince();
|
||||||
|
city = addr.getCity();
|
||||||
|
district = addr.getDistrict();
|
||||||
|
township = addr.getTownship();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "获取地址信息失败", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
String formattedAddr = fullAddress;
|
||||||
|
|
||||||
|
// 按优先级移除重复前缀(省、市、区、乡)
|
||||||
|
String[] prefixes = {province, city, district, township};
|
||||||
|
for (String prefix : prefixes) {
|
||||||
|
if (prefix != null && !prefix.isEmpty() && formattedAddr.startsWith(prefix)) {
|
||||||
|
formattedAddr = formattedAddr.substring(prefix.length());
|
||||||
|
Log.d(TAG, "移除前缀: " + prefix + " -> " + formattedAddr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 限制地址长度并添加省略号
|
||||||
|
if (formattedAddr.length() > 25) {
|
||||||
|
formattedAddr = formattedAddr.substring(0, 25) + "...";
|
||||||
|
Log.d(TAG, "地址长度截断: " + formattedAddr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return formattedAddr;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onGeocodeSearched(GeocodeResult result, int rCode) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== API请求 ====================
|
||||||
|
|
||||||
|
private void fetchRecommendStation(AMapLocation loc) {
|
||||||
|
try {
|
||||||
|
JSONObject json = new JSONObject();
|
||||||
|
json.put("province", loc.getProvince() != null ? loc.getProvince() : "");
|
||||||
|
json.put("city", loc.getCity() != null && !loc.getCity().isEmpty() ? loc.getCity() : "");
|
||||||
|
json.put("district", loc.getDistrict() != null ? loc.getDistrict() : "");
|
||||||
|
json.put("longitude", String.valueOf(loc.getLongitude()));
|
||||||
|
json.put("latitude", String.valueOf(loc.getLatitude()));
|
||||||
|
|
||||||
|
RequestBody body = RequestBody.create(json.toString(), MediaType.parse("application/json; charset=utf-8"));
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url("https://beta-esg.api.lnh2e.com/appointment/station/getStationInfoByArea")
|
||||||
|
.post(body)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
httpClient.newCall(request).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");
|
||||||
|
String addr = data.optString("address", "");
|
||||||
|
new Handler(Looper.getMainLooper()).post(() -> {
|
||||||
|
endInput.setText(addr);
|
||||||
|
markStation(endPoint, endName, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fetchNearbyStations(AMapLocation loc) {
|
||||||
|
try {
|
||||||
|
JSONObject json = new JSONObject();
|
||||||
|
json.put("longitude", String.valueOf(loc.getLongitude()));
|
||||||
|
json.put("latitude", String.valueOf(loc.getLatitude()));
|
||||||
|
|
||||||
|
RequestBody body = RequestBody.create(json.toString(), MediaType.parse("application/json; charset=utf-8"));
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url("https://beta-esg.api.lnh2e.com/appointment/station/getNearbyHydrogenStationsByLocation")
|
||||||
|
.post(body)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
httpClient.newCall(request).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")) {
|
||||||
|
JSONArray array = res.getJSONArray("data");
|
||||||
|
new Handler(Looper.getMainLooper()).post(() -> {
|
||||||
|
for (int i = 0; i < array.length(); i++) {
|
||||||
|
try {
|
||||||
|
JSONObject item = array.getJSONObject(i);
|
||||||
|
markStation(new LatLng(item.getDouble("latitude"), item.getDouble("longitude")),
|
||||||
|
item.getString("name"), false);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void markStation(LatLng latLng, String name, boolean isRecommend) {
|
||||||
|
MarkerOptions opt = new MarkerOptions()
|
||||||
|
.position(latLng).title(name)
|
||||||
|
.icon(BitmapDescriptorFactory.defaultMarker(isRecommend ? BitmapDescriptorFactory.HUE_RED : BitmapDescriptorFactory.HUE_GREEN));
|
||||||
|
Marker m = aMap.addMarker(opt);
|
||||||
|
m.setObject(latLng);
|
||||||
|
stationMarkers.add(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onMarkerClick(Marker marker) {
|
||||||
|
if (marker.getObject() instanceof LatLng) {
|
||||||
|
endPoint = (LatLng) marker.getObject();
|
||||||
|
endName = marker.getTitle();
|
||||||
|
endInput.setText(endName);
|
||||||
|
startRouteSearch();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 路径规划 ====================
|
||||||
|
|
||||||
|
private void startRouteSearch() {
|
||||||
|
if (startPoint == null || endPoint == null) {
|
||||||
|
Toast.makeText(mContext, "正在定位中,请稍后...", Toast.LENGTH_SHORT).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Poi start = new Poi(startName, startPoint, "");
|
||||||
|
Poi end = new Poi(endName, endPoint, "");
|
||||||
|
|
||||||
|
AmapNaviParams params = new AmapNaviParams(start, null, end, AmapNaviType.DRIVER, AmapPageType.ROUTE);
|
||||||
|
|
||||||
|
try {
|
||||||
|
AMapNavi mAMapNavi = AMapNavi.getInstance(mContext);
|
||||||
|
AMapCarInfo carInfo = new AMapCarInfo();
|
||||||
|
carInfo.setCarNumber("沪AGK2267");
|
||||||
|
carInfo.setCarType("1");
|
||||||
|
carInfo.setVehicleAxis("6");
|
||||||
|
carInfo.setVehicleHeight("3.32");
|
||||||
|
carInfo.setVehicleLength("6.9");
|
||||||
|
carInfo.setVehicleWidth("2.26");
|
||||||
|
carInfo.setVehicleSize("2");
|
||||||
|
carInfo.setVehicleLoad("4.5");
|
||||||
|
carInfo.setVehicleWeight("4.5");
|
||||||
|
carInfo.setRestriction(true);
|
||||||
|
carInfo.setVehicleLoadSwitch(true);
|
||||||
|
mAMapNavi.setCarInfo(carInfo);
|
||||||
|
} catch (com.amap.api.maps.AMapException e) {
|
||||||
|
Log.e(TAG, "设置车辆信息失败", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
params.setRouteStrategy(PathPlanningStrategy.DRIVING_MULTIPLE_ROUTES_DEFAULT);
|
||||||
|
|
||||||
|
if (mActivity != null) {
|
||||||
|
AmapNaviPage.getInstance().showRouteActivity(mActivity, params, new INaviInfoCallback() {
|
||||||
|
@Override
|
||||||
|
public void onInitNaviFailure() {
|
||||||
|
Log.e(TAG, "导航初始化失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onGetNavigationText(String s) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLocationChange(AMapNaviLocation location) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onArriveDestination(boolean b) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStartNavi(int i) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCalculateRouteSuccess(int[] ints) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCalculateRouteFailure(int i) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStopSpeaking() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReCalculateRoute(int i) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onArrivedWayPoint(int i) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onExitPage(int i) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStrategyChanged(int i) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMapTypeChanged(int i) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNaviDirectionChanged(int i) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDayAndNightModeChanged(int i) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBroadcastModeChanged(int i) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onScaleAutoChanged(boolean b) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getCustomNaviBottomView() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getCustomNaviView() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getCustomMiddleView() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDriveRouteSearched(DriveRouteResult result, int rCode) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@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) {
|
||||||
|
Context base = ((android.content.ContextWrapper) context).getBaseContext();
|
||||||
|
if (base instanceof Activity)
|
||||||
|
return (Activity) base;
|
||||||
|
}
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
ln_jq_app/android/app/src/main/res/drawable/car.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
ln_jq_app/android/app/src/main/res/drawable/ic_tag.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
@@ -21,9 +21,11 @@
|
|||||||
.amap-callamap,
|
.amap-callamap,
|
||||||
.amap-lib-driving-callBtn,
|
.amap-lib-driving-callBtn,
|
||||||
.amap-copyright,
|
.amap-copyright,
|
||||||
.amap-logo{bottom: 60px}
|
.amap-logo {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* 去除高德默认的 label 边框 and 背景 */
|
/* 去除高德默认的 label 边框和背景 */
|
||||||
.amap-marker-label {
|
.amap-marker-label {
|
||||||
border: none !important;
|
border: none !important;
|
||||||
background-color: transparent !important;
|
background-color: transparent !important;
|
||||||
@@ -107,7 +109,7 @@
|
|||||||
/* --- 导航结果面板 (底部弹出) --- */
|
/* --- 导航结果面板 (底部弹出) --- */
|
||||||
#panel {
|
#panel {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 95px;
|
bottom: 75px;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 35%;
|
height: 35%;
|
||||||
@@ -127,7 +129,7 @@
|
|||||||
#location-btn {
|
#location-btn {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
right: 10px;
|
right: 10px;
|
||||||
bottom: 105px;
|
bottom: 75px;
|
||||||
/* 默认位置 */
|
/* 默认位置 */
|
||||||
width: 44px;
|
width: 44px;
|
||||||
height: 44px;
|
height: 44px;
|
||||||
@@ -157,7 +159,7 @@
|
|||||||
/* --- 调整比例尺位置 --- */
|
/* --- 调整比例尺位置 --- */
|
||||||
.amap-scalecontrol {
|
.amap-scalecontrol {
|
||||||
/* 初始状态:避开底部的定位按钮或留出安全间距 */
|
/* 初始状态:避开底部的定位按钮或留出安全间距 */
|
||||||
bottom: 110px !important;
|
bottom: 80px !important;
|
||||||
left: 10px !important;
|
left: 10px !important;
|
||||||
transition: bottom 0.3s ease;
|
transition: bottom 0.3s ease;
|
||||||
/* 增加平滑动画 */
|
/* 增加平滑动画 */
|
||||||
@@ -193,10 +195,10 @@
|
|||||||
|
|
||||||
<div id="search-box">
|
<div id="search-box">
|
||||||
<div class="input-row">
|
<div class="input-row">
|
||||||
<input id="startInput" placeholder="起点: 请输入当前地点" onfocus="this.select()" />
|
<input id="startInput" placeholder="起点: 请输入当前地点" />
|
||||||
</div>
|
</div>
|
||||||
<div class="input-row">
|
<div class="input-row">
|
||||||
<input id="endInput" placeholder="终点: 请输入目的地" onfocus="this.select()" />
|
<input id="endInput" placeholder="终点: 请输入目的地" />
|
||||||
<button onclick="startRouteSearch()">路径规划</button>
|
<button onclick="startRouteSearch()">路径规划</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -219,7 +221,6 @@
|
|||||||
var currentLat, currentLng;
|
var currentLat, currentLng;
|
||||||
var isTruckMode = false;
|
var isTruckMode = false;
|
||||||
var isInitialLocationSet = false;
|
var isInitialLocationSet = false;
|
||||||
var stationMarkers = []; // 存储所有站点的标记
|
|
||||||
|
|
||||||
|
|
||||||
function initMap() {
|
function initMap() {
|
||||||
@@ -242,11 +243,6 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 点击地图空白处重置状态
|
|
||||||
map.on('click', function() {
|
|
||||||
resetSearchState();
|
|
||||||
});
|
|
||||||
|
|
||||||
// 添加基础控件
|
// 添加基础控件
|
||||||
map.addControl(new AMap.Scale());
|
map.addControl(new AMap.Scale());
|
||||||
map.addControl(new AMap.ToolBar({
|
map.addControl(new AMap.ToolBar({
|
||||||
@@ -288,19 +284,6 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 重置搜索状态,隐藏面板和路线
|
|
||||||
*/
|
|
||||||
function resetSearchState() {
|
|
||||||
if (document.body.classList.contains('panel-active')) {
|
|
||||||
console.log("JS->: 重置地图状态");
|
|
||||||
document.body.classList.remove('panel-active');
|
|
||||||
var panel = document.getElementById('panel');
|
|
||||||
panel.style.display = 'none';
|
|
||||||
if (driving) driving.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 核心功能 1: 接收 Flutter 传来的定位数据
|
* 核心功能 1: 接收 Flutter 传来的定位数据
|
||||||
* Flutter 端调用: webViewController.evaluateJavascript("updateMyLocation(...)")
|
* Flutter 端调用: webViewController.evaluateJavascript("updateMyLocation(...)")
|
||||||
@@ -353,8 +336,6 @@
|
|||||||
fetchStationInfo(addressComponent.province, addressComponent.city,
|
fetchStationInfo(addressComponent.province, addressComponent.city,
|
||||||
addressComponent.district, lat, lng);
|
addressComponent.district, lat, lng);
|
||||||
|
|
||||||
fetchStationInfoList(lat, lng);
|
|
||||||
|
|
||||||
// 策略1: 优先使用最近的、类型合适的POI的名称
|
// 策略1: 优先使用最近的、类型合适的POI的名称
|
||||||
if (pois && pois.length > 0) {
|
if (pois && pois.length > 0) {
|
||||||
// 查找第一个类型不是“商务住宅”或“地名地址信息”的POI,这类POI通常是具体的建筑或地点名
|
// 查找第一个类型不是“商务住宅”或“地名地址信息”的POI,这类POI通常是具体的建筑或地点名
|
||||||
@@ -416,6 +397,7 @@
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
// "asoco-token": "e28eada8-4611-4dc2-a942-0122e52f52da"
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
province: province,
|
province: province,
|
||||||
@@ -455,79 +437,6 @@
|
|||||||
.catch(err => console.error('JS->:获取站点信息失败:', err));
|
.catch(err => console.error('JS->:获取站点信息失败:', err));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取站点列表
|
|
||||||
*/
|
|
||||||
function fetchStationInfoList(lat, lng) {
|
|
||||||
fetch('https://beta-esg.api.lnh2e.com/appointment/station/getNearbyHydrogenStationsByLocation', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
longitude: lng,
|
|
||||||
latitude: lat,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.then(response => {
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('网络响应错误: ' + response.status);
|
|
||||||
}
|
|
||||||
return response.json(); // 解析 JSON
|
|
||||||
})
|
|
||||||
.then(res => {
|
|
||||||
console.log("JS->:2 接口完整返回:", JSON.stringify(res));
|
|
||||||
if (res.code === 0 && res.data && Array.isArray(res.data)) {
|
|
||||||
// 1. 清除旧的站点标记
|
|
||||||
stationMarkers.forEach(m => m.setMap(null));
|
|
||||||
stationMarkers = [];
|
|
||||||
|
|
||||||
// 2. 循环标记所有加氢站
|
|
||||||
res.data.forEach(station => {
|
|
||||||
var stationIcon = new AMap.Icon({
|
|
||||||
size: new AMap.Size(32, 32),
|
|
||||||
image: 'ic_tag.png',
|
|
||||||
imageSize: new AMap.Size(32, 32)
|
|
||||||
});
|
|
||||||
|
|
||||||
var sMarker = new AMap.Marker({
|
|
||||||
map: map,
|
|
||||||
position: [station.longitude, station.latitude],
|
|
||||||
icon: stationIcon,
|
|
||||||
offset: new AMap.Pixel(-16, -32),
|
|
||||||
title: station.name,
|
|
||||||
label: {
|
|
||||||
content: '<div class="custom-bubble">' + station.name +
|
|
||||||
'</div>',
|
|
||||||
direction: 'top'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 3. 绑定点击事件:选中即为目的地,并开始规划
|
|
||||||
sMarker.on('click', function () {
|
|
||||||
var stationName = station.name || "目的地";
|
|
||||||
document.getElementById('endInput').value = station.address ||
|
|
||||||
stationName;
|
|
||||||
|
|
||||||
// 更新当前的 destMarker
|
|
||||||
if (destMarker && destMarker !== sMarker) destMarker.setMap(null);
|
|
||||||
destMarker = sMarker;
|
|
||||||
|
|
||||||
// 直接传入坐标对象,避免关键字搜索失败
|
|
||||||
var loc = new AMap.LngLat(station.longitude, station.latitude);
|
|
||||||
startRouteSearch(loc);
|
|
||||||
});
|
|
||||||
|
|
||||||
stationMarkers.push(sMarker);
|
|
||||||
});
|
|
||||||
|
|
||||||
} else {
|
|
||||||
console.log("JS->: 业务报错或无数据:", res.message);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(err => console.error('JS->:获取站点信息失败:', err));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 地理编码并在地图标记终点
|
* 地理编码并在地图标记终点
|
||||||
*/
|
*/
|
||||||
@@ -538,6 +447,7 @@
|
|||||||
if (destMarker) destMarker.setMap(null);
|
if (destMarker) destMarker.setMap(null);
|
||||||
|
|
||||||
// 2. 创建自定义图标
|
// 2. 创建自定义图标
|
||||||
|
// 假设图标大小为 32x32,你可以根据实际图片尺寸调整 Size
|
||||||
var destIcon = new AMap.Icon({
|
var destIcon = new AMap.Icon({
|
||||||
size: new AMap.Size(32, 32), // 图标尺寸
|
size: new AMap.Size(32, 32), // 图标尺寸
|
||||||
image: 'ic_tag.png', // 本地图片路径
|
image: 'ic_tag.png', // 本地图片路径
|
||||||
@@ -549,6 +459,8 @@
|
|||||||
map: map,
|
map: map,
|
||||||
position: [longitude, latitude],
|
position: [longitude, latitude],
|
||||||
icon: destIcon, // 使用自定义图标
|
icon: destIcon, // 使用自定义图标
|
||||||
|
// 偏移量:如果图标底部中心是尖角,offset 设为宽的一半的负数,高度的负数
|
||||||
|
// 这样能确保图片的底部尖端指向地图上的精确位置
|
||||||
offset: new AMap.Pixel(-16, -32),
|
offset: new AMap.Pixel(-16, -32),
|
||||||
title: name,
|
title: name,
|
||||||
label: {
|
label: {
|
||||||
@@ -557,7 +469,17 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("JS->: 终点标记已添加", address);
|
// 4. 打印调试信息
|
||||||
|
console.log("JS->: 终点标记已添加", address, loc.toString());
|
||||||
|
|
||||||
|
// 5. 自动调整视野包含起点和终点
|
||||||
|
// if (marker) {
|
||||||
|
// // 如果起点标志已存在,缩放地图以展示两者
|
||||||
|
// map.setFitView([marker, destMarker], false, [60, 60, 60, 60]);
|
||||||
|
// } else {
|
||||||
|
// // 如果没有起点,直接跳到终点
|
||||||
|
// map.setCenter(loc);
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -576,12 +498,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 路径规划
|
* 路径规划
|
||||||
* @param {AMap.LngLat} [destLoc] 可选的终点坐标
|
|
||||||
*/
|
*/
|
||||||
function startRouteSearch(destLoc) {
|
function startRouteSearch() {
|
||||||
|
// 获取输入框的文字
|
||||||
var startKw = document.getElementById('startInput').value;
|
var startKw = document.getElementById('startInput').value;
|
||||||
var endKw = document.getElementById('endInput').value;
|
var endKw = document.getElementById('endInput').value;
|
||||||
|
|
||||||
@@ -589,59 +510,63 @@
|
|||||||
alert("请输入起点");
|
alert("请输入起点");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!endKw) {
|
if (!endKw) {
|
||||||
alert("请输入终点");
|
alert("请输入终点");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 清除旧路线
|
||||||
if (driving) driving.clear();
|
if (driving) driving.clear();
|
||||||
|
|
||||||
|
// 收起键盘
|
||||||
document.getElementById('startInput').blur();
|
document.getElementById('startInput').blur();
|
||||||
document.getElementById('endInput').blur();
|
document.getElementById('endInput').blur();
|
||||||
|
|
||||||
|
// --- 构造路径规划的点 (使用数组方式,更灵活) ---
|
||||||
var points = [];
|
var points = [];
|
||||||
|
|
||||||
// 1. 起点逻辑
|
// 1. 处理起点逻辑
|
||||||
if (!startKw || startKw === '我的位置' || startKw.includes('当前位置')) {
|
// 如果输入框是空的,或者写着 "我的位置",则使用 GPS 坐标
|
||||||
|
if (!startKw || startKw === '我的位置') {
|
||||||
if (!currentLng || !currentLat) {
|
if (!currentLng || !currentLat) {
|
||||||
if (window.flutter_inappwebview) window.flutter_inappwebview.callHandler('requestLocation');
|
// 如果还没获取到定位
|
||||||
|
if (window.flutter_inappwebview) {
|
||||||
|
window.flutter_inappwebview.callHandler('requestLocation');
|
||||||
|
}
|
||||||
alert("正在获取定位,请稍后...");
|
alert("正在获取定位,请稍后...");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// 使用精准坐标对象 (避免高德去猜 '我的位置' 关键词)
|
||||||
points.push({
|
points.push({
|
||||||
keyword: '我的位置',
|
keyword: '我的位置', // 用于显示的名字
|
||||||
location: new AMap.LngLat(currentLng, currentLat)
|
location: new AMap.LngLat(currentLng, currentLat) // 实际导航用的坐标
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
// 如果用户手动输入了地点 (例如 "北京南站")
|
||||||
|
// 直接存入关键词,让高德自己去搜
|
||||||
points.push({
|
points.push({
|
||||||
keyword: startKw
|
keyword: startKw
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 终点逻辑:如果有传入坐标,则直接使用坐标导航,成功率最高
|
// 2. 处理终点逻辑 (通常是关键词)
|
||||||
if (destLoc) {
|
|
||||||
points.push({
|
|
||||||
keyword: endKw,
|
|
||||||
location: destLoc // 关键:使用精确坐标
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
points.push({
|
points.push({
|
||||||
keyword: endKw
|
keyword: endKw
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
// 3. 发起搜索
|
// 3. 发起搜索
|
||||||
|
// points 数组里现在是一个起点对象和一个终点对象
|
||||||
driving.search(points, function (status, result) {
|
driving.search(points, function (status, result) {
|
||||||
if (status === 'complete') {
|
if (status === 'complete') {
|
||||||
console.log('JS: 规划成功');
|
console.log('JS: 规划成功');
|
||||||
var panel = document.getElementById('panel');
|
var panel = document.getElementById('panel');
|
||||||
panel.style.display = 'block';
|
panel.style.display = 'block';
|
||||||
document.body.classList.add('panel-active');
|
document.body.classList.add('panel-active');
|
||||||
|
} else {
|
||||||
|
console.log('JS: 规划失败', result);
|
||||||
|
alert("规划失败,请检查起终点名称");
|
||||||
}
|
}
|
||||||
// else {
|
|
||||||
// console.error('JS: 规划失败', result);
|
|
||||||
// // 如果坐标规划都失败了,通常是由于起终点距离过近或政策限制(如货车禁行)
|
|
||||||
// alert("路径规划未成功,请尝试微调起终点");
|
|
||||||
// }
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 892 B |
|
Before Width: | Height: | Size: 394 B |
|
Before Width: | Height: | Size: 947 B After Width: | Height: | Size: 573 B |
|
Before Width: | Height: | Size: 594 B After Width: | Height: | Size: 508 B |
|
Before Width: | Height: | Size: 636 B |
|
Before Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 896 B |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 163 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 545 KiB After Width: | Height: | Size: 112 KiB |
50
ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK.podspec
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
#
|
||||||
|
# Be sure to run `pod lib lint AMapNavIOSSDK.podspec' to ensure this is a
|
||||||
|
# valid spec before submitting.
|
||||||
|
#
|
||||||
|
# Any lines starting with a # are optional, but their use is encouraged
|
||||||
|
# To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html
|
||||||
|
#
|
||||||
|
|
||||||
|
Pod::Spec.new do |s|
|
||||||
|
s.name = 'AMapNavIOSSDK'
|
||||||
|
s.version = '0.1.0'
|
||||||
|
s.summary = 'A short description of AMapNavIOSSDK.'
|
||||||
|
|
||||||
|
# This description is used to generate tags and improve search results.
|
||||||
|
# * Think: What does it do? Why did you write it? What is the focus?
|
||||||
|
# * Try to keep it short, snappy and to the point.
|
||||||
|
# * Write the description between the DESC delimiters below.
|
||||||
|
# * Finally, don't worry about the indent, CocoaPods strips it!
|
||||||
|
|
||||||
|
s.description = <<-DESC
|
||||||
|
TODO: Add long description of the pod here.
|
||||||
|
DESC
|
||||||
|
|
||||||
|
s.homepage = 'https://github.com/xiaoshuai/AMapNavIOSSDK'
|
||||||
|
# s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2'
|
||||||
|
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||||
|
s.author = { 'xiaoshuai' => 'xiaoshuai@net.cn' }
|
||||||
|
s.source = { :git => 'https://github.com/xiaoshuai/AMapNavIOSSDK.git', :tag => s.version.to_s }
|
||||||
|
# s.social_media_url = 'https://twitter.com/<TWITTER_USERNAME>'
|
||||||
|
|
||||||
|
s.ios.deployment_target = '12.0'
|
||||||
|
|
||||||
|
s.source_files = 'AMapNavIOSSDK/Classes/**/*'
|
||||||
|
s.resource = 'AMapNavIOSSDK/**/*.bundle'
|
||||||
|
s.resource_bundles = {
|
||||||
|
'AMapNavIOSSDKPrivacyInfo' => ['AMapNavIOSSDK/**/PrivacyInfo.xcprivacy']
|
||||||
|
}
|
||||||
|
|
||||||
|
# s.public_header_files = 'Pod/Classes/**/*.h'
|
||||||
|
# s.frameworks = 'UIKit', 'MapKit'
|
||||||
|
# s.dependency 'AFNetworking', '~> 2.3'
|
||||||
|
|
||||||
|
s.dependency 'Masonry'
|
||||||
|
s.dependency 'MJExtension'
|
||||||
|
|
||||||
|
s.dependency 'AMapNavi-NO-IDFA'
|
||||||
|
s.dependency 'AMapLocation-NO-IDFA'
|
||||||
|
s.dependency 'AMapSearch-NO-IDFA'
|
||||||
|
|
||||||
|
end
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 840 B |
@@ -0,0 +1,31 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>NSPrivacyTracking</key>
|
||||||
|
<false/>
|
||||||
|
<key>NSPrivacyTrackingDomains</key>
|
||||||
|
<array/>
|
||||||
|
<key>NSPrivacyAccessedAPITypes</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>NSPrivacyAccessedAPIType</key>
|
||||||
|
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
|
||||||
|
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||||
|
<array>
|
||||||
|
<string>0A2A.1</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>NSPrivacyAccessedAPIType</key>
|
||||||
|
<string>NSPrivacyAccessedAPICategoryDiskSpace</string>
|
||||||
|
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||||
|
<array>
|
||||||
|
<string>85F4.1</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
<key>NSPrivacyCollectedDataTypes</key>
|
||||||
|
<array/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
//
|
||||||
|
// ABaseViewController.h
|
||||||
|
// ANavDemo
|
||||||
|
//
|
||||||
|
// Created by admin on 2026/2/5.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
#import <Masonry/Masonry.h>
|
||||||
|
#import "AMapNavCommonUtil.h"
|
||||||
|
|
||||||
|
#define kRoutePlanBarHeight (self.navigationController.navigationBar.frame.size.height + UIApplication.sharedApplication.statusBarFrame.size.height + 0)
|
||||||
|
|
||||||
|
#define kRoutePlanStatusBarHeight (UIApplication.sharedApplication.statusBarFrame.size.height + 0)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@interface ABaseViewController : UIViewController
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
//
|
||||||
|
// ABaseViewController.m
|
||||||
|
// ANavDemo
|
||||||
|
//
|
||||||
|
// Created by admin on 2026/2/5.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "ABaseViewController.h"
|
||||||
|
|
||||||
|
@interface ABaseViewController ()
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation ABaseViewController
|
||||||
|
|
||||||
|
- (void)viewDidLoad {
|
||||||
|
[super viewDidLoad];
|
||||||
|
// Do any additional setup after loading the view.
|
||||||
|
|
||||||
|
self.view.backgroundColor = [UIColor whiteColor];
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
#pragma mark - Navigation
|
||||||
|
|
||||||
|
// In a storyboard-based application, you will often want to do a little preparation before navigation
|
||||||
|
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
|
||||||
|
// Get the new view controller using [segue destinationViewController].
|
||||||
|
// Pass the selected object to the new view controller.
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
//
|
||||||
|
// AMapHyStationModel.h
|
||||||
|
// AMapNavIOSSDK
|
||||||
|
//
|
||||||
|
// Created by admin on 2026/2/11.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
#import <MJExtension/MJExtension.h>
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
/**
|
||||||
|
{
|
||||||
|
"name": "嘉兴经开站",
|
||||||
|
"shortName": null,
|
||||||
|
"siteNo": null,
|
||||||
|
"city": null,
|
||||||
|
"address": "嘉兴市秀洲区岗山路272号",
|
||||||
|
"contact": "龚明伟",
|
||||||
|
"phone": "18888888888",
|
||||||
|
"type": null,
|
||||||
|
"coOpMode": null,
|
||||||
|
"booking": null,
|
||||||
|
"siteStatus": 0,
|
||||||
|
"startBusiness": "06:00:00",
|
||||||
|
"endBusiness": "22:00:00",
|
||||||
|
"billingMethod": null,
|
||||||
|
"term": null,
|
||||||
|
"remark": null,
|
||||||
|
"longitude": "120.75972800",
|
||||||
|
"latitude": "30.79962800"
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
@interface AMapHyStationModel : NSObject
|
||||||
|
|
||||||
|
@property (nonatomic, copy) NSString *name;
|
||||||
|
@property (nonatomic, copy, nullable) NSString *shortName;
|
||||||
|
@property (nonatomic, copy, nullable) NSString *siteNo;
|
||||||
|
@property (nonatomic, copy, nullable) NSString *city;
|
||||||
|
@property (nonatomic, copy, nullable) NSString *address;
|
||||||
|
@property (nonatomic, copy, nullable) NSString *contact;
|
||||||
|
@property (nonatomic, copy, nullable) NSString *phone;
|
||||||
|
@property (nonatomic, copy, nullable) NSString *type;
|
||||||
|
@property (nonatomic, copy, nullable) NSString *coOpMode;
|
||||||
|
@property (nonatomic, strong, nullable) NSString * booking;
|
||||||
|
@property (nonatomic, assign) NSInteger siteStatus;
|
||||||
|
@property (nonatomic, copy, nullable) NSString *startBusiness;
|
||||||
|
@property (nonatomic, copy, nullable) NSString *endBusiness;
|
||||||
|
@property (nonatomic, copy, nullable) NSString *billingMethod;
|
||||||
|
@property (nonatomic, copy, nullable) NSString *term;
|
||||||
|
@property (nonatomic, copy, nullable) NSString *remark;
|
||||||
|
@property (nonatomic, copy, nullable) NSString *longitude;
|
||||||
|
@property (nonatomic, copy, nullable) NSString *latitude;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
/**
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"status": true,
|
||||||
|
"message": "success",
|
||||||
|
"data": [],
|
||||||
|
"time": "1770800256408",
|
||||||
|
"error": null
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
@interface AMapHyResponse : NSObject
|
||||||
|
@property (nonatomic, assign) NSInteger code;
|
||||||
|
@property (nonatomic, assign) NSInteger status;
|
||||||
|
@property (nonatomic, copy, nullable) NSString *message;
|
||||||
|
@property (nonatomic, copy, nullable) NSString *time;
|
||||||
|
@property (nonatomic, copy, nullable) NSString *error;
|
||||||
|
|
||||||
|
@property(nonatomic , strong)NSArray <AMapHyStationModel * > * data;
|
||||||
|
|
||||||
|
@end
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
//
|
||||||
|
// AMapHyStationModel.m
|
||||||
|
// AMapNavIOSSDK
|
||||||
|
//
|
||||||
|
// Created by admin on 2026/2/11.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "AMapHyStationModel.h"
|
||||||
|
|
||||||
|
@implementation AMapHyStationModel
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
|
||||||
|
@implementation AMapHyResponse
|
||||||
|
|
||||||
|
+ (NSDictionary *)mj_objectClassInArray {
|
||||||
|
return @{@"data" : AMapHyStationModel.class};
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
//
|
||||||
|
// AMapNavSDKHeader.h
|
||||||
|
// Pods
|
||||||
|
//
|
||||||
|
// Created by admin on 2026/2/10.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef AMapNavSDKHeader_h
|
||||||
|
#define AMapNavSDKHeader_h
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* AMapNavSDKHeader_h */
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
//
|
||||||
|
// AMapNavSDKManager.h
|
||||||
|
// Pods
|
||||||
|
//
|
||||||
|
// Created by admin on 2026/2/10.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
#import "ARoutePlaneController.h"
|
||||||
|
|
||||||
|
#define kAMapSDKDebugFlag
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@interface AMapNavSDKManager : NSObject
|
||||||
|
|
||||||
|
@property (strong, nonatomic) UIWindow *window;
|
||||||
|
|
||||||
|
@property (nonatomic , strong) NSString * localCity;
|
||||||
|
@property (nonatomic, copy) NSString * locationAddressDetail;
|
||||||
|
|
||||||
|
@property (nonatomic , strong , readonly) UIViewController * targetVC;
|
||||||
|
|
||||||
|
+ (instancetype)sharedManager;
|
||||||
|
- (void)configWithKey:(NSString*)key;
|
||||||
|
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
//
|
||||||
|
// AMapNavSDKManager.m
|
||||||
|
// Pods
|
||||||
|
//
|
||||||
|
// Created by admin on 2026/2/10.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "AMapNavSDKManager.h"
|
||||||
|
#import "AMapPrivacyUtility.h"
|
||||||
|
|
||||||
|
#import <AMapFoundationKit/AMapFoundationKit.h>
|
||||||
|
|
||||||
|
@interface AMapNavSDKManager ()
|
||||||
|
@property (nonatomic , strong , readwrite) UIViewController * targetVC;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation AMapNavSDKManager
|
||||||
|
|
||||||
|
+ (instancetype)sharedManager {
|
||||||
|
static AMapNavSDKManager *manager = nil;
|
||||||
|
static dispatch_once_t onceToken;
|
||||||
|
dispatch_once(&onceToken, ^{
|
||||||
|
manager = [[AMapNavSDKManager alloc] init];
|
||||||
|
});
|
||||||
|
return manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (instancetype)init {
|
||||||
|
self = [super init];
|
||||||
|
if (self) {
|
||||||
|
_targetVC = [ARoutePlaneController new];
|
||||||
|
}
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- (void)configWithKey:(NSString*)key {
|
||||||
|
if (1) {
|
||||||
|
/*
|
||||||
|
* 调用隐私合规处理方法
|
||||||
|
*/
|
||||||
|
// [AMapPrivacyUtility handlePrivacyAgreeStatusIn:_targetVC];
|
||||||
|
//
|
||||||
|
// 初始化高德导航SDK
|
||||||
|
[self configureAPIKey:key];
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (UIViewController *)targetVC {
|
||||||
|
return _targetVC;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - private
|
||||||
|
- (void)configureAPIKey:(NSString*)key {
|
||||||
|
if ([key length] == 0)
|
||||||
|
{
|
||||||
|
NSString *reason = [NSString stringWithFormat:@"apiKey为空,请检查key是否正确设置。"];
|
||||||
|
|
||||||
|
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"提示" message:reason delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil, nil];
|
||||||
|
|
||||||
|
[alert show];
|
||||||
|
}
|
||||||
|
[AMapServices sharedServices].enableHTTPS = YES;
|
||||||
|
[AMapServices sharedServices].apiKey = (NSString *)key;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
//
|
||||||
|
// ARoutePlaneController.h
|
||||||
|
// ANavDemo
|
||||||
|
//
|
||||||
|
// Created by admin on 2026/2/5.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
#import "ABaseViewController.h"
|
||||||
|
|
||||||
|
#import "SelectableOverlay.h"
|
||||||
|
#import "NaviPointAnnotation.h"
|
||||||
|
|
||||||
|
#import <AMapNaviKit/AMapNaviKit.h>
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@interface ARoutePlaneController : ABaseViewController
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
@@ -0,0 +1,930 @@
|
|||||||
|
//
|
||||||
|
// ARoutePlaneController.m
|
||||||
|
// ANavDemo
|
||||||
|
//
|
||||||
|
// Created by admin on 2026/2/5.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "ARoutePlaneController.h"
|
||||||
|
|
||||||
|
#import <AMapNaviKit/MAMapKit.h>
|
||||||
|
#import <AMapNaviKit/AMapNaviKit.h>
|
||||||
|
#import <AMapLocationKit/AMapLocationKit.h>
|
||||||
|
#import <AMapSearchKit/AMapSearchAPI.h>
|
||||||
|
|
||||||
|
#import "ASearchAddressController.h"
|
||||||
|
|
||||||
|
#import "AMapNavSDKManager.h"
|
||||||
|
|
||||||
|
#import "AMapPrivacyUtility.h"
|
||||||
|
|
||||||
|
#define kRouteIndicatorViewHeight 64.f
|
||||||
|
|
||||||
|
#import "AMapHyStationModel.h"
|
||||||
|
#import "AMapNavHttpUtil.h"
|
||||||
|
|
||||||
|
@interface ARoutePlaneController ()<MAMapViewDelegate, AMapNaviDriveManagerDelegate,AMapNaviCompositeManagerDelegate , AMapLocationManagerDelegate , UITextFieldDelegate >
|
||||||
|
@property (nonatomic, strong) UITextField *textField;
|
||||||
|
|
||||||
|
@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) 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;//开始查询当前节点;
|
||||||
|
|
||||||
|
@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 = @"https://beta-esg.api.lnh2e.com/appointment/station/getNearbyHydrogenStationsByLocation";
|
||||||
|
|
||||||
|
/**
|
||||||
|
//汽车公园有数据
|
||||||
|
"longitude": "121.30461400",
|
||||||
|
"latitude": "31.17321100"
|
||||||
|
*/
|
||||||
|
// NSDictionary * dic = @{@"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 = @"https://beta-esg.api.lnh2e.com/appointment/station/getStationInfoByArea";
|
||||||
|
|
||||||
|
[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)updateHeadAddressWithStation:(AMapHyStationModel*)model {
|
||||||
|
|
||||||
|
AMapPOI * aoi = [[AMapPOI alloc] init];
|
||||||
|
|
||||||
|
aoi.location = [AMapGeoPoint locationWithLatitude:[model.latitude doubleValue] longitude:[model.longitude doubleValue]];
|
||||||
|
|
||||||
|
aoi.name = model.name;
|
||||||
|
|
||||||
|
self.dstPoi = aoi;
|
||||||
|
|
||||||
|
///地址栏
|
||||||
|
[self updateUIWithData:aoi textField:self.dstTf];
|
||||||
|
|
||||||
|
|
||||||
|
///地图显示
|
||||||
|
[self updateMapAnnotationWithData:@[model]];
|
||||||
|
}
|
||||||
|
|
||||||
|
-(void)updateMapAnnotationWithData:(NSArray *)dataArr {
|
||||||
|
self.hyStationArr = dataArr;
|
||||||
|
if (!(dataArr && dataArr.count > 0)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
///添加标注
|
||||||
|
NSMutableArray * points = [NSMutableArray arrayWithCapacity:dataArr.count];
|
||||||
|
for (AMapHyStationModel * model in dataArr) {
|
||||||
|
MAPointAnnotation *pointAnnotation = [[MAPointAnnotation alloc] init];
|
||||||
|
if (!(model.latitude && model.longitude)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
pointAnnotation.coordinate = CLLocationCoordinate2DMake([model.latitude doubleValue], [model.longitude doubleValue]);
|
||||||
|
pointAnnotation.title = model.name;
|
||||||
|
[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 -
|
||||||
|
-(void)initSubview {
|
||||||
|
UITextField * startTf = [[UITextField alloc] init];
|
||||||
|
startTf.borderStyle = UITextBorderStyleRoundedRect;
|
||||||
|
startTf.placeholder = @"起点";
|
||||||
|
startTf.tag = 100;
|
||||||
|
startTf.delegate = self;
|
||||||
|
startTf.font = [UIFont systemFontOfSize:13];
|
||||||
|
[self.view addSubview:startTf];
|
||||||
|
|
||||||
|
[startTf mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||||
|
make.top.mas_equalTo(self.view).offset(kRoutePlanStatusBarHeight + 35);
|
||||||
|
make.left.mas_equalTo(self.view).offset(5);
|
||||||
|
make.width.mas_equalTo(@120);
|
||||||
|
make.height.mas_equalTo(@32);
|
||||||
|
}];
|
||||||
|
|
||||||
|
self.startTf = startTf;
|
||||||
|
|
||||||
|
UITextField * dstTf = [[UITextField alloc] init];
|
||||||
|
dstTf.borderStyle = UITextBorderStyleRoundedRect;
|
||||||
|
dstTf.placeholder = @"终点";
|
||||||
|
dstTf.tag = 200;
|
||||||
|
dstTf.delegate = self;
|
||||||
|
dstTf.font = [UIFont systemFontOfSize:13];
|
||||||
|
|
||||||
|
[self.view addSubview:dstTf];
|
||||||
|
|
||||||
|
[dstTf mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||||
|
make.centerY.mas_equalTo(startTf);
|
||||||
|
make.left.mas_equalTo(startTf.mas_right).offset(15);
|
||||||
|
// make.right.mas_equalTo(self.view).offset(-5);
|
||||||
|
make.width.height.mas_equalTo(startTf);
|
||||||
|
}];
|
||||||
|
self.dstTf = dstTf;
|
||||||
|
|
||||||
|
UIButton * btn = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||||
|
[btn setTitle:@"规划线路" forState:UIControlStateNormal];
|
||||||
|
btn.backgroundColor = [UIColor whiteColor];
|
||||||
|
btn.titleLabel.font = [UIFont systemFontOfSize:14];
|
||||||
|
|
||||||
|
btn.layer.borderColor = [UIColor blueColor].CGColor;
|
||||||
|
btn.layer.borderWidth = 1;
|
||||||
|
btn.layer.cornerRadius = 5;
|
||||||
|
|
||||||
|
[btn setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
|
||||||
|
[btn addTarget:self action:@selector(calRoutePath) forControlEvents:UIControlEventTouchUpInside];
|
||||||
|
[self.view addSubview:btn];
|
||||||
|
[btn mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||||
|
make.left.equalTo(dstTf.mas_right).offset(12);
|
||||||
|
make.top.mas_equalTo(startTf);
|
||||||
|
make.right.mas_equalTo(self.view).offset(-5);
|
||||||
|
make.height.mas_equalTo(@30);
|
||||||
|
}];
|
||||||
|
|
||||||
|
|
||||||
|
UIButton * navBtn = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||||
|
[navBtn setTitle:@"导航>" forState:UIControlStateNormal];
|
||||||
|
navBtn.backgroundColor = [UIColor whiteColor];
|
||||||
|
navBtn.titleLabel.font = [UIFont systemFontOfSize:14];
|
||||||
|
|
||||||
|
navBtn.layer.borderColor = [UIColor blueColor].CGColor;
|
||||||
|
navBtn.layer.borderWidth = 1;
|
||||||
|
navBtn.layer.cornerRadius = 6;
|
||||||
|
|
||||||
|
[navBtn setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
|
||||||
|
[navBtn addTarget:self action:@selector(navAction) forControlEvents:UIControlEventTouchUpInside];
|
||||||
|
[self.view addSubview:navBtn];
|
||||||
|
[navBtn mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||||
|
// make.left.equalTo(self.view).offset(12);
|
||||||
|
// make.centerX.equalTo(self.view);
|
||||||
|
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.mapView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||||
|
make.left.right.equalTo(self.view);
|
||||||
|
// make.top.equalTo(startTf.mas_bottom).offset(5);
|
||||||
|
make.top.equalTo(self.view).offset(0);
|
||||||
|
// make.bottom.equalTo(navBtn.mas_top).offset(-3);
|
||||||
|
make.bottom.equalTo(self.view).offset(0);
|
||||||
|
}];
|
||||||
|
|
||||||
|
[self.view bringSubviewToFront:navBtn];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (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;
|
||||||
|
|
||||||
|
CGFloat ze = self.mapView.zoomLevel;
|
||||||
|
self.mapView.zoomLevel = 9;
|
||||||
|
|
||||||
|
// 2. 禁用所有不必要的动画和自动调整
|
||||||
|
// self.mapView.autoresizesSubviews = NO;
|
||||||
|
// [self.mapView setShowsWorldMap:NO]; // 不显示世界地图
|
||||||
|
|
||||||
|
// 3. 固定缩放级别
|
||||||
|
// [self.mapView setMinZoomLevel:6.0];
|
||||||
|
// [self.mapView setMaxZoomLevel:20.0];
|
||||||
|
// [self.mapView setZoomLevel:10.0 animated:NO];
|
||||||
|
|
||||||
|
|
||||||
|
// 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];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///TEST
|
||||||
|
// MAPointAnnotation *pointAnnotation = [[MAPointAnnotation alloc] init];
|
||||||
|
// pointAnnotation.coordinate = CLLocationCoordinate2DMake(31.19, 121.32);
|
||||||
|
// pointAnnotation.title = @"嘉兴经开站";
|
||||||
|
// [_mapView addAnnotation:pointAnnotation];
|
||||||
|
//
|
||||||
|
// MAPointAnnotation *pointAnnotation2 = [[MAPointAnnotation alloc] init];
|
||||||
|
// pointAnnotation2.coordinate = CLLocationCoordinate2DMake(30.81669400, 120.94291800);
|
||||||
|
// pointAnnotation2.title = @"测试站点1";
|
||||||
|
// [_mapView addAnnotation:pointAnnotation2];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
UIButton * btn = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||||
|
[btn setImage:[AMapNavCommonUtil imageWithName:@"icon_local"] 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.top.equalTo(self.view).offset(150);
|
||||||
|
}];
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
-(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 = kCLLocationAccuracyBest; // 最高精度模式
|
||||||
|
_locationService.distanceFilter = 5;
|
||||||
|
_locationService.locatingWithReGeocode = YES;
|
||||||
|
}
|
||||||
|
return _locationService;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- (void)dealloc {
|
||||||
|
[self.locationService stopUpdatingLocation];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
- (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 - Action
|
||||||
|
///选择方式
|
||||||
|
-(void)navAction {
|
||||||
|
[self showSelectNavType];
|
||||||
|
}
|
||||||
|
|
||||||
|
-(void)showSelectNavType {
|
||||||
|
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"选择导航类型" message:nil preferredStyle:UIAlertControllerStyleActionSheet];
|
||||||
|
UIAlertAction *sure = [UIAlertAction actionWithTitle:@"SDK导航" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
|
||||||
|
[self navigationType_sdk];
|
||||||
|
}];
|
||||||
|
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)navigationType_sdk {
|
||||||
|
id delegate = [AMapNaviDriveManager sharedInstance].delegate;
|
||||||
|
if (!delegate) {
|
||||||
|
[AMapNaviDriveManager sharedInstance].delegate = self;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
NSDictionary * routes = [AMapNaviDriveManager sharedInstance].naviRoutes;
|
||||||
|
if (!routes) {
|
||||||
|
NSLog(@"暂无路线信息!!!!!!!!!");
|
||||||
|
self.isStartNav = YES;
|
||||||
|
[self calRoutePath];
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
AMapNaviCompositeUserConfig *config = [[AMapNaviCompositeUserConfig alloc] init];
|
||||||
|
|
||||||
|
// [config setRoutePlanPOIType:AMapNaviRoutePlanPOITypeEnd location:[AMapNaviPoint locationWithLatitude:32.21 longitude:121.34] name:@"故宫22" POIId:nil];
|
||||||
|
|
||||||
|
[config setStartNaviDirectly:YES]; //直接进入导航界面
|
||||||
|
[config setNeedCalculateRouteWhenPresent:NO];//不在算路
|
||||||
|
[config setMultipleRouteNaviMode:NO];//直接单线路径导航
|
||||||
|
// [config setNeedDestoryDriveManagerInstanceWhenDismiss:NO];
|
||||||
|
|
||||||
|
self.isStartNav = NO;
|
||||||
|
|
||||||
|
[self.compositeManager presentRoutePlanViewControllerWithOptions:config];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#pragma mark - 计算线路
|
||||||
|
///计算线路
|
||||||
|
-(void)calRoutePath {
|
||||||
|
// [self.mapView removeOverlays:self.mapView.overlays];
|
||||||
|
// self.startTf.text = @"click calpath";
|
||||||
|
// return;
|
||||||
|
|
||||||
|
[self initAnnotations];
|
||||||
|
|
||||||
|
AMapNaviPoint * startPoint = [AMapNaviPoint locationWithLatitude:self.startPoi.location.latitude longitude:self.startPoi.location.longitude];
|
||||||
|
|
||||||
|
AMapNaviPoint * endPoint = [AMapNaviPoint locationWithLatitude:self.dstPoi.location.latitude longitude:self.dstPoi.location.longitude];
|
||||||
|
|
||||||
|
AMapNaviDrivingStrategy strategy = ConvertDrivingPreferenceToDrivingStrategy(0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0);
|
||||||
|
|
||||||
|
id delegate = [AMapNaviDriveManager sharedInstance].delegate;
|
||||||
|
if (!delegate) {
|
||||||
|
[AMapNaviDriveManager sharedInstance].delegate = self;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[AMapNaviDriveManager sharedInstance] calculateDriveRouteWithStartPoints:@[startPoint]
|
||||||
|
endPoints:@[endPoint]
|
||||||
|
wayPoints:nil
|
||||||
|
drivingStrategy:strategy];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
- (void)driveManagerOnCalculateRouteSuccess:(AMapNaviDriveManager *)driveManager
|
||||||
|
{
|
||||||
|
NSLog(@"onCalculateRouteSuccess");
|
||||||
|
//算路成功后显示路径
|
||||||
|
[self showNaviRoutes];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (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];
|
||||||
|
}
|
||||||
|
|
||||||
|
MAPolyline *polyline = [MAPolyline polylineWithCoordinates:coords count:count];
|
||||||
|
|
||||||
|
SelectableOverlay *selectablePolyline = [[SelectableOverlay alloc] initWithOverlay:polyline];
|
||||||
|
[selectablePolyline setRouteID:[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 navigationType_sdk];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
- (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<MAOverlay> 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 - 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号";
|
||||||
|
#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<MAAnnotation>)annotation
|
||||||
|
{
|
||||||
|
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 isMemberOfClass:[MAPointAnnotation class]])
|
||||||
|
{
|
||||||
|
MAUserLocation *user = (MAUserLocation *)annotation;
|
||||||
|
|
||||||
|
static NSString *pointReuseIndentifier = @"pointReuseIndentifier";
|
||||||
|
MAPinAnnotationView*annotationView = (MAPinAnnotationView*)[mapView dequeueReusableAnnotationViewWithIdentifier:pointReuseIndentifier];
|
||||||
|
if (annotationView == nil)
|
||||||
|
{
|
||||||
|
annotationView = [[MAPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:pointReuseIndentifier];
|
||||||
|
}
|
||||||
|
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<MAOverlay>)overlay
|
||||||
|
{
|
||||||
|
if ([overlay isKindOfClass:[SelectableOverlay class]])
|
||||||
|
{
|
||||||
|
SelectableOverlay * selectableOverlay = (SelectableOverlay *)overlay;
|
||||||
|
id<MAOverlay> actualOverlay = selectableOverlay.overlay;
|
||||||
|
|
||||||
|
MAPolylineRenderer *polylineRenderer = [[MAPolylineRenderer alloc] initWithPolyline:actualOverlay];
|
||||||
|
|
||||||
|
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:[MAPointAnnotation 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 {
|
||||||
|
NSLog(@"didSelectAnnotationView: %s" , __func__);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 当标注被取消选中时调用
|
||||||
|
- (void)mapView:(MAMapView *)mapView didDeselectAnnotationView:(MAAnnotationView *)view {
|
||||||
|
if ([view.annotation isMemberOfClass:[MAPointAnnotation class]]) {
|
||||||
|
// 可以立即或稍作延迟后重新选中
|
||||||
|
// [mapView selectAnnotation:view.annotation animated:NO];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//选中一个点
|
||||||
|
- (void)mapView:(MAMapView *)mapView didAnnotationViewCalloutTapped:(MAAnnotationView *)view {
|
||||||
|
id pointAnnotation = view.annotation;
|
||||||
|
if ([pointAnnotation isMemberOfClass:MAPointAnnotation.class]) {
|
||||||
|
MAPointAnnotation *point = (MAPointAnnotation *)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 - UITextFieldDelegate
|
||||||
|
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
|
||||||
|
|
||||||
|
ASearchAddressController * vc = [[ASearchAddressController alloc] init];
|
||||||
|
|
||||||
|
UINavigationController * nav = [[UINavigationController alloc]initWithRootViewController:vc];
|
||||||
|
//UIModalPresentationOverFullScreen/UIModalPresentationFullScreen触发离开
|
||||||
|
nav.modalPresentationStyle = UIModalPresentationOverFullScreen;
|
||||||
|
nav.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
|
||||||
|
|
||||||
|
__weak typeof(self)weakSelf = self;
|
||||||
|
vc.selectAddressBlk = ^(AMapPOI * _Nonnull poi) {
|
||||||
|
[weakSelf updateUIWithData:poi textField:textField];
|
||||||
|
};
|
||||||
|
|
||||||
|
[self presentViewController:nav animated:YES completion:^{
|
||||||
|
|
||||||
|
}];
|
||||||
|
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
-(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
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
//
|
||||||
|
// ASearchAddressController.h
|
||||||
|
// ANavDemo
|
||||||
|
//
|
||||||
|
// Created by admin on 2026/2/6.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "ABaseViewController.h"
|
||||||
|
|
||||||
|
#import <AMapSearchKit/AMapSearchKit.h>
|
||||||
|
#import <AMapLocationKit/AMapLocationKit.h>
|
||||||
|
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@interface ASearchAddressController : ABaseViewController
|
||||||
|
|
||||||
|
@property (nonatomic , copy) void(^selectAddressBlk)(AMapPOI * poi);
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
@@ -0,0 +1,232 @@
|
|||||||
|
//
|
||||||
|
// ASearchAddressController.m
|
||||||
|
// ANavDemo
|
||||||
|
//
|
||||||
|
// Created by admin on 2026/2/6.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "ASearchAddressController.h"
|
||||||
|
#import "AMapNavSDKManager.h"
|
||||||
|
|
||||||
|
#import <AMapFoundationKit/AMapFoundationKit.h>
|
||||||
|
|
||||||
|
#import "AMapNavCommonUtil.h"
|
||||||
|
|
||||||
|
@interface ASearchAddressController ()<UITextFieldDelegate , AMapSearchDelegate,UITableViewDelegate , UITableViewDataSource>
|
||||||
|
|
||||||
|
@property (nonatomic , strong) UITableView *tableView;
|
||||||
|
@property (nonatomic , strong) UIBarButtonItem *rightItem;
|
||||||
|
@property (nonatomic ,strong)UIButton * backBtn;
|
||||||
|
|
||||||
|
@property (nonatomic , strong) 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];
|
||||||
|
|
||||||
|
[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<UITouch *> *)touches withEvent:(UIEvent *)event {
|
||||||
|
[self.inputAddressTf resignFirstResponder];
|
||||||
|
}
|
||||||
|
@end
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
//
|
||||||
|
// AMapNavCommonUtil.h
|
||||||
|
// Pods
|
||||||
|
//
|
||||||
|
// Created by admin on 2026/2/11.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@interface AMapNavCommonUtil : NSObject
|
||||||
|
|
||||||
|
+(UIImage *)imageWithName:(NSString *)name;
|
||||||
|
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
//
|
||||||
|
// AMapNavCommonUtil.m
|
||||||
|
// Pods
|
||||||
|
//
|
||||||
|
// Created by admin on 2026/2/11.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "AMapNavCommonUtil.h"
|
||||||
|
|
||||||
|
@implementation AMapNavCommonUtil
|
||||||
|
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
//
|
||||||
|
// AMapNavHttpUtil.h
|
||||||
|
// AMapNavIOSSDK
|
||||||
|
//
|
||||||
|
// Created by admin on 2026/2/11.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
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
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
//
|
||||||
|
// AMapPrivacyUtility.h
|
||||||
|
// officialDemoNavi
|
||||||
|
//
|
||||||
|
// Created by menglong on 2021/10/29.
|
||||||
|
// Copyright © 2021 AutoNavi. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
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
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
//
|
||||||
|
// AMapPrivacyUtility.m
|
||||||
|
// officialDemoNavi
|
||||||
|
//
|
||||||
|
// Created by menglong on 2021/10/29.
|
||||||
|
// Copyright © 2021 AutoNavi. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "AMapPrivacyUtility.h"
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
#import <AMapNavikit/AMapNaviManagerConfig.h>
|
||||||
|
@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
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
//
|
||||||
|
// NaviPointAnnotation.h
|
||||||
|
// AMapNaviKit
|
||||||
|
//
|
||||||
|
// Created by 刘博 on 16/3/8.
|
||||||
|
// Copyright © 2016年 AutoNavi. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <AMapNaviKit/MAMapKit.h>
|
||||||
|
|
||||||
|
typedef NS_ENUM(NSInteger, NaviPointAnnotationType)
|
||||||
|
{
|
||||||
|
NaviPointAnnotationStart,
|
||||||
|
NaviPointAnnotationWay,
|
||||||
|
NaviPointAnnotationEnd
|
||||||
|
};
|
||||||
|
|
||||||
|
@interface NaviPointAnnotation : MAPointAnnotation
|
||||||
|
|
||||||
|
@property (nonatomic, assign) NaviPointAnnotationType navPointType;
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -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
|
||||||
24
ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Tools/SelectableOverlay.h
Executable file
@@ -0,0 +1,24 @@
|
|||||||
|
//
|
||||||
|
// SelectableOverlay.h
|
||||||
|
// officialDemo2D
|
||||||
|
//
|
||||||
|
// Created by yi chen on 14-5-8.
|
||||||
|
// Copyright (c) 2014年 AutoNavi. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
#import <AMapNaviKit/MAMapKit.h>
|
||||||
|
|
||||||
|
@interface SelectableOverlay : MABaseOverlay
|
||||||
|
|
||||||
|
@property (nonatomic, assign) NSInteger routeID;
|
||||||
|
|
||||||
|
@property (nonatomic, assign, getter = isSelected) BOOL selected;
|
||||||
|
@property (nonatomic, strong) UIColor * selectedColor;
|
||||||
|
@property (nonatomic, strong) UIColor * regularColor;
|
||||||
|
|
||||||
|
@property (nonatomic, strong) id<MAOverlay> overlay;
|
||||||
|
|
||||||
|
- (id)initWithOverlay:(id<MAOverlay>) overlay;
|
||||||
|
|
||||||
|
@end
|
||||||
41
ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Tools/SelectableOverlay.m
Executable file
@@ -0,0 +1,41 @@
|
|||||||
|
//
|
||||||
|
// SelectableOverlay.m
|
||||||
|
// officialDemo2D
|
||||||
|
//
|
||||||
|
// Created by yi chen on 14-5-8.
|
||||||
|
// Copyright (c) 2014年 AutoNavi. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "SelectableOverlay.h"
|
||||||
|
|
||||||
|
@implementation SelectableOverlay
|
||||||
|
|
||||||
|
#pragma mark - MAOverlay Protocol
|
||||||
|
|
||||||
|
- (CLLocationCoordinate2D)coordinate
|
||||||
|
{
|
||||||
|
return [self.overlay coordinate];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (MAMapRect)boundingMapRect
|
||||||
|
{
|
||||||
|
return [self.overlay boundingMapRect];
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - Life Cycle
|
||||||
|
|
||||||
|
- (id)initWithOverlay:(id<MAOverlay>)overlay
|
||||||
|
{
|
||||||
|
self = [super init];
|
||||||
|
if (self)
|
||||||
|
{
|
||||||
|
self.overlay = overlay;
|
||||||
|
self.selected = NO;
|
||||||
|
self.selectedColor = [UIColor colorWithRed:0.05 green:0.39 blue:0.9 alpha:0.8];
|
||||||
|
self.regularColor = [UIColor colorWithRed:0.5 green:0.6 blue:0.9 alpha:0.8];
|
||||||
|
}
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -31,7 +31,11 @@ require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelpe
|
|||||||
flutter_ios_podfile_setup
|
flutter_ios_podfile_setup
|
||||||
|
|
||||||
target 'Runner' do
|
target 'Runner' do
|
||||||
use_frameworks!
|
# use_frameworks!
|
||||||
|
use_frameworks! :linkage => :static
|
||||||
|
|
||||||
|
pod 'AMapNavIOSSDK' , :path => './AMapNavIOSSDK'
|
||||||
|
|
||||||
|
|
||||||
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
|
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
|
||||||
target 'RunnerTests' do
|
target 'RunnerTests' do
|
||||||
|
|||||||
@@ -1,19 +1,30 @@
|
|||||||
PODS:
|
PODS:
|
||||||
- AlicloudELS (1.0.3)
|
- AlicloudELS (1.0.3)
|
||||||
- AlicloudPush (3.2.3):
|
- AlicloudPush (3.2.3):
|
||||||
- AlicloudELS (= 1.0.3)
|
- AlicloudELS (~> 1.0.3)
|
||||||
- AlicloudUTDID (~> 1.0)
|
- AlicloudUTDID (~> 1.0)
|
||||||
- AlicloudUTDID (1.6.1)
|
- AlicloudUTDID (1.6.1)
|
||||||
- aliyun_push_flutter (0.0.1):
|
- aliyun_push_flutter (0.0.1):
|
||||||
- AlicloudPush (< 4.0, >= 3.2.3)
|
- AlicloudPush (< 4.0, >= 3.2.3)
|
||||||
- Flutter
|
- 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
|
||||||
|
- MJExtension
|
||||||
|
- AMapSearch-NO-IDFA (9.7.4):
|
||||||
|
- AMapFoundation-NO-IDFA (>= 1.8.0)
|
||||||
- connectivity_plus (0.0.1):
|
- connectivity_plus (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- device_info_plus (0.0.1):
|
- device_info_plus (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- Flutter (1.0.0)
|
- Flutter (1.0.0)
|
||||||
- flutter_app_update (0.0.1):
|
|
||||||
- Flutter
|
|
||||||
- flutter_inappwebview_ios (0.0.1):
|
- flutter_inappwebview_ios (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- flutter_inappwebview_ios/Core (= 0.0.1)
|
- flutter_inappwebview_ios/Core (= 0.0.1)
|
||||||
@@ -30,19 +41,16 @@ PODS:
|
|||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- image_picker_ios (0.0.1):
|
- image_picker_ios (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- Masonry (1.1.0)
|
||||||
|
- MJExtension (3.4.2)
|
||||||
- mobile_scanner (7.0.0):
|
- mobile_scanner (7.0.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- OrderedSet (6.0.3)
|
- OrderedSet (6.0.3)
|
||||||
- package_info_plus (0.4.5):
|
- package_info_plus (0.4.5):
|
||||||
- Flutter
|
- Flutter
|
||||||
- path_provider_foundation (0.0.1):
|
|
||||||
- Flutter
|
|
||||||
- FlutterMacOS
|
|
||||||
- permission_handler_apple (9.3.0):
|
- permission_handler_apple (9.3.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
- saver_gallery (0.0.1):
|
|
||||||
- Flutter
|
|
||||||
- shared_preferences_foundation (0.0.1):
|
- shared_preferences_foundation (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
@@ -51,10 +59,10 @@ PODS:
|
|||||||
|
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
- aliyun_push_flutter (from `.symlinks/plugins/aliyun_push_flutter/ios`)
|
- aliyun_push_flutter (from `.symlinks/plugins/aliyun_push_flutter/ios`)
|
||||||
|
- AMapNavIOSSDK (from `./AMapNavIOSSDK`)
|
||||||
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
||||||
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||||
- Flutter (from `Flutter`)
|
- Flutter (from `Flutter`)
|
||||||
- flutter_app_update (from `.symlinks/plugins/flutter_app_update/ios`)
|
|
||||||
- flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`)
|
- flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`)
|
||||||
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
||||||
- flutter_pdfview (from `.symlinks/plugins/flutter_pdfview/ios`)
|
- flutter_pdfview (from `.symlinks/plugins/flutter_pdfview/ios`)
|
||||||
@@ -62,9 +70,7 @@ DEPENDENCIES:
|
|||||||
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
||||||
- mobile_scanner (from `.symlinks/plugins/mobile_scanner/darwin`)
|
- mobile_scanner (from `.symlinks/plugins/mobile_scanner/darwin`)
|
||||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
|
||||||
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
||||||
- saver_gallery (from `.symlinks/plugins/saver_gallery/ios`)
|
|
||||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||||
|
|
||||||
@@ -75,19 +81,25 @@ SPEC REPOS:
|
|||||||
- AlicloudELS
|
- AlicloudELS
|
||||||
- AlicloudPush
|
- AlicloudPush
|
||||||
trunk:
|
trunk:
|
||||||
|
- AMapFoundation-NO-IDFA
|
||||||
|
- AMapLocation-NO-IDFA
|
||||||
|
- AMapNavi-NO-IDFA
|
||||||
|
- AMapSearch-NO-IDFA
|
||||||
|
- Masonry
|
||||||
|
- MJExtension
|
||||||
- OrderedSet
|
- OrderedSet
|
||||||
|
|
||||||
EXTERNAL SOURCES:
|
EXTERNAL SOURCES:
|
||||||
aliyun_push_flutter:
|
aliyun_push_flutter:
|
||||||
:path: ".symlinks/plugins/aliyun_push_flutter/ios"
|
:path: ".symlinks/plugins/aliyun_push_flutter/ios"
|
||||||
|
AMapNavIOSSDK:
|
||||||
|
:path: "./AMapNavIOSSDK"
|
||||||
connectivity_plus:
|
connectivity_plus:
|
||||||
:path: ".symlinks/plugins/connectivity_plus/ios"
|
:path: ".symlinks/plugins/connectivity_plus/ios"
|
||||||
device_info_plus:
|
device_info_plus:
|
||||||
:path: ".symlinks/plugins/device_info_plus/ios"
|
:path: ".symlinks/plugins/device_info_plus/ios"
|
||||||
Flutter:
|
Flutter:
|
||||||
:path: Flutter
|
:path: Flutter
|
||||||
flutter_app_update:
|
|
||||||
:path: ".symlinks/plugins/flutter_app_update/ios"
|
|
||||||
flutter_inappwebview_ios:
|
flutter_inappwebview_ios:
|
||||||
:path: ".symlinks/plugins/flutter_inappwebview_ios/ios"
|
:path: ".symlinks/plugins/flutter_inappwebview_ios/ios"
|
||||||
flutter_native_splash:
|
flutter_native_splash:
|
||||||
@@ -102,12 +114,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/mobile_scanner/darwin"
|
:path: ".symlinks/plugins/mobile_scanner/darwin"
|
||||||
package_info_plus:
|
package_info_plus:
|
||||||
:path: ".symlinks/plugins/package_info_plus/ios"
|
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||||
path_provider_foundation:
|
|
||||||
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
|
||||||
permission_handler_apple:
|
permission_handler_apple:
|
||||||
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
||||||
saver_gallery:
|
|
||||||
:path: ".symlinks/plugins/saver_gallery/ios"
|
|
||||||
shared_preferences_foundation:
|
shared_preferences_foundation:
|
||||||
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
||||||
url_launcher_ios:
|
url_launcher_ios:
|
||||||
@@ -115,27 +123,31 @@ EXTERNAL SOURCES:
|
|||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
AlicloudELS: fbf821383330465a5af84a033f36f263ae46ca41
|
AlicloudELS: fbf821383330465a5af84a033f36f263ae46ca41
|
||||||
AlicloudPush: 95150880af380f64cf1741f5586047c17d36c1d9
|
AlicloudPush: 52cbf38ffc20c07f039cbc72d5738745fd986215
|
||||||
AlicloudUTDID: 5d2f22d50e11eecd38f30bc7a48c71925ea90976
|
AlicloudUTDID: 5d2f22d50e11eecd38f30bc7a48c71925ea90976
|
||||||
aliyun_push_flutter: 0fc2f048a08687ef256c0cfdd72dd7a550ef3347
|
aliyun_push_flutter: ab0bf7112ef3797f506770a7a9f47f004635a9f6
|
||||||
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
|
AMapFoundation-NO-IDFA: 6ce0ef596d4eb8d934ff498e56747b6de1247b05
|
||||||
device_info_plus: 71ffc6ab7634ade6267c7a93088ed7e4f74e5896
|
AMapLocation-NO-IDFA: 590fd42af0c8ea9eac26978348221bbc16be4ef9
|
||||||
|
AMapNavi-NO-IDFA: 22edfa7d6a81d75c91756e31b6c26b7746152233
|
||||||
|
AMapNavIOSSDK: 0cd6ec22ab6b6aba268028a5b580e18bb8066f7e
|
||||||
|
AMapSearch-NO-IDFA: 53b2193244be8f07f3be0a4d5161200236960587
|
||||||
|
connectivity_plus: 2a701ffec2c0ae28a48cf7540e279787e77c447d
|
||||||
|
device_info_plus: 97af1d7e84681a90d0693e63169a5d50e0839a0d
|
||||||
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
||||||
flutter_app_update: 816fdb2e30e4832a7c45e3f108d391c42ef040a9
|
flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4
|
||||||
flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99
|
flutter_native_splash: df59bb2e1421aa0282cb2e95618af4dcb0c56c29
|
||||||
flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
|
flutter_pdfview: 2e4d13ffb774858562ffbdfdb61b40744b191adc
|
||||||
flutter_pdfview: 32bf27bda6fd85b9dd2c09628a824df5081246cf
|
geolocator_apple: 66b711889fd333205763b83c9dcf0a57a28c7afd
|
||||||
geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e
|
image_picker_ios: 4f2f91b01abdb52842a8e277617df877e40f905b
|
||||||
image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326
|
Masonry: 678fab65091a9290e40e2832a55e7ab731aad201
|
||||||
mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93
|
MJExtension: e97d164cb411aa9795cf576093a1fa208b4a8dd8
|
||||||
|
mobile_scanner: 77265f3dc8d580810e91849d4a0811a90467ed5e
|
||||||
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
|
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
|
||||||
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
|
package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4
|
||||||
path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
|
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
|
||||||
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
|
shared_preferences_foundation: 5086985c1d43c5ba4d5e69a4e8083a389e2909e6
|
||||||
saver_gallery: af2d0c762dafda254e0ad025ef0dabd6506cd490
|
url_launcher_ios: bb13df5870e8c4234ca12609d04010a21be43dfa
|
||||||
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
|
||||||
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
|
|
||||||
|
|
||||||
PODFILE CHECKSUM: 357c01ff4e7591871e8c4fd6462220a8c7447220
|
PODFILE CHECKSUM: 97188da9dab9d4b3372eb4c16e872fbd555fdbea
|
||||||
|
|
||||||
COCOAPODS: 1.16.2
|
COCOAPODS: 1.16.2
|
||||||
|
|||||||
@@ -8,11 +8,12 @@
|
|||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
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 */; };
|
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
|
||||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
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 */; };
|
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 */; };
|
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
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; };
|
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 = "<group>"; };
|
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||||
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 = "<group>"; };
|
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 = "<group>"; };
|
||||||
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 = "<group>"; };
|
6D3F89E22F04C32900A154AD /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; };
|
||||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
||||||
|
8420D0072F3D9F7E006DB6CC /* NativeFirstPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeFirstPage.swift; sourceTree = "<group>"; };
|
||||||
|
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 = "<group>"; };
|
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 = "<group>"; };
|
||||||
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 = "<group>"; };
|
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
||||||
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
||||||
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
@@ -73,7 +75,7 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
59E555C098DB12132BCE9F6E /* Pods_Runner.framework in Frameworks */,
|
298D3D45379E4332D4A8A627 /* Pods_Runner.framework in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -81,7 +83,7 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
307490676CE2A16C8D75B103 /* Pods_RunnerTests.framework in Frameworks */,
|
3F21125D6B84D3CC58F3C574 /* Pods_RunnerTests.framework in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -152,6 +154,7 @@
|
|||||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
|
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
|
||||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
|
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
|
||||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
|
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
|
||||||
|
8420D0072F3D9F7E006DB6CC /* NativeFirstPage.swift */,
|
||||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
||||||
);
|
);
|
||||||
path = Runner;
|
path = Runner;
|
||||||
@@ -160,8 +163,8 @@
|
|||||||
E621C70ABD0685462494972D /* Frameworks */ = {
|
E621C70ABD0685462494972D /* Frameworks */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
6AF04C5CFFF0B4098EEDA799 /* Pods_Runner.framework */,
|
95135D36941D5EF2C00065B2 /* Pods_Runner.framework */,
|
||||||
937F9432963895EF63BCCD38 /* Pods_RunnerTests.framework */,
|
85810788944AB2417549F45E /* Pods_RunnerTests.framework */,
|
||||||
);
|
);
|
||||||
name = Frameworks;
|
name = Frameworks;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -199,7 +202,6 @@
|
|||||||
97C146EC1CF9000F007C117D /* Resources */,
|
97C146EC1CF9000F007C117D /* Resources */,
|
||||||
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||||
590CF992B35CC61AF9AA4341 /* [CP] Embed Pods Frameworks */,
|
|
||||||
AF570E8AAEEA12D52BD19B4E /* [CP] Copy Pods Resources */,
|
AF570E8AAEEA12D52BD19B4E /* [CP] Copy Pods Resources */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
@@ -288,23 +290,6 @@
|
|||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
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 */ = {
|
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
alwaysOutOfDate = 1;
|
alwaysOutOfDate = 1;
|
||||||
@@ -396,6 +381,7 @@
|
|||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
8420D0082F3D9F7E006DB6CC /* NativeFirstPage.swift in Sources */,
|
||||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
||||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
|
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
|
||||||
);
|
);
|
||||||
@@ -491,7 +477,8 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
|
CODE_SIGN_STYLE = Manual;
|
||||||
CURRENT_PROJECT_VERSION = 4;
|
CURRENT_PROJECT_VERSION = 4;
|
||||||
DEVELOPMENT_TEAM = 2228B9MS38;
|
DEVELOPMENT_TEAM = 2228B9MS38;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
@@ -526,8 +513,6 @@
|
|||||||
"-framework",
|
"-framework",
|
||||||
"\"package_info_plus\"",
|
"\"package_info_plus\"",
|
||||||
"-framework",
|
"-framework",
|
||||||
"\"path_provider_foundation\"",
|
|
||||||
"-framework",
|
|
||||||
"\"permission_handler_apple\"",
|
"\"permission_handler_apple\"",
|
||||||
"-framework",
|
"-framework",
|
||||||
"\"shared_preferences_foundation\"",
|
"\"shared_preferences_foundation\"",
|
||||||
@@ -565,8 +550,6 @@
|
|||||||
"-framework",
|
"-framework",
|
||||||
"\"package_info_plus\"",
|
"\"package_info_plus\"",
|
||||||
"-framework",
|
"-framework",
|
||||||
"\"path_provider_foundation\"",
|
|
||||||
"-framework",
|
|
||||||
"\"permission_handler_apple\"",
|
"\"permission_handler_apple\"",
|
||||||
"-framework",
|
"-framework",
|
||||||
"\"shared_preferences_foundation\"",
|
"\"shared_preferences_foundation\"",
|
||||||
@@ -759,7 +742,8 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
|
CODE_SIGN_STYLE = Manual;
|
||||||
CURRENT_PROJECT_VERSION = 4;
|
CURRENT_PROJECT_VERSION = 4;
|
||||||
DEVELOPMENT_TEAM = 2228B9MS38;
|
DEVELOPMENT_TEAM = 2228B9MS38;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
@@ -794,8 +778,6 @@
|
|||||||
"-framework",
|
"-framework",
|
||||||
"\"package_info_plus\"",
|
"\"package_info_plus\"",
|
||||||
"-framework",
|
"-framework",
|
||||||
"\"path_provider_foundation\"",
|
|
||||||
"-framework",
|
|
||||||
"\"permission_handler_apple\"",
|
"\"permission_handler_apple\"",
|
||||||
"-framework",
|
"-framework",
|
||||||
"\"shared_preferences_foundation\"",
|
"\"shared_preferences_foundation\"",
|
||||||
@@ -833,8 +815,6 @@
|
|||||||
"-framework",
|
"-framework",
|
||||||
"\"package_info_plus\"",
|
"\"package_info_plus\"",
|
||||||
"-framework",
|
"-framework",
|
||||||
"\"path_provider_foundation\"",
|
|
||||||
"-framework",
|
|
||||||
"\"permission_handler_apple\"",
|
"\"permission_handler_apple\"",
|
||||||
"-framework",
|
"-framework",
|
||||||
"\"shared_preferences_foundation\"",
|
"\"shared_preferences_foundation\"",
|
||||||
@@ -864,7 +844,8 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||||
|
CODE_SIGN_STYLE = Manual;
|
||||||
CURRENT_PROJECT_VERSION = 4;
|
CURRENT_PROJECT_VERSION = 4;
|
||||||
DEVELOPMENT_TEAM = 2228B9MS38;
|
DEVELOPMENT_TEAM = 2228B9MS38;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
@@ -899,8 +880,6 @@
|
|||||||
"-framework",
|
"-framework",
|
||||||
"\"package_info_plus\"",
|
"\"package_info_plus\"",
|
||||||
"-framework",
|
"-framework",
|
||||||
"\"path_provider_foundation\"",
|
|
||||||
"-framework",
|
|
||||||
"\"permission_handler_apple\"",
|
"\"permission_handler_apple\"",
|
||||||
"-framework",
|
"-framework",
|
||||||
"\"shared_preferences_foundation\"",
|
"\"shared_preferences_foundation\"",
|
||||||
|
|||||||
@@ -1,13 +1,86 @@
|
|||||||
import Flutter
|
import Flutter
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
|
///
|
||||||
|
let kAMapKey = "key";
|
||||||
|
|
||||||
|
|
||||||
@main
|
@main
|
||||||
@objc class AppDelegate: FlutterAppDelegate {
|
@objc class AppDelegate: FlutterAppDelegate {
|
||||||
override func application(
|
override func application(
|
||||||
_ application: UIApplication,
|
_ application: UIApplication,
|
||||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||||
) -> Bool {
|
) -> 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)
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key></key>
|
|
||||||
<string></string>
|
|
||||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
@@ -16,6 +14,11 @@
|
|||||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
<string>6.0</string>
|
<string>6.0</string>
|
||||||
|
<key>CFBundleLocalizations</key>
|
||||||
|
<array>
|
||||||
|
<string>zh-Hans</string>
|
||||||
|
<string>en</string>
|
||||||
|
</array>
|
||||||
<key>CFBundleName</key>
|
<key>CFBundleName</key>
|
||||||
<string>ln_jq_app</string>
|
<string>ln_jq_app</string>
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
@@ -28,6 +31,10 @@
|
|||||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||||
<key>ITSAppUsesNonExemptEncryption</key>
|
<key>ITSAppUsesNonExemptEncryption</key>
|
||||||
<false/>
|
<false/>
|
||||||
|
<key>LSApplicationQueriesSchemes</key>
|
||||||
|
<array>
|
||||||
|
<string>iosamap</string>
|
||||||
|
</array>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>NSCameraUsageDescription</key>
|
<key>NSCameraUsageDescription</key>
|
||||||
@@ -44,6 +51,12 @@
|
|||||||
<string>需要访问您的相册以选择二维码图片进行识别</string>
|
<string>需要访问您的相册以选择二维码图片进行识别</string>
|
||||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>UIBackgroundModes</key>
|
||||||
|
<array>
|
||||||
|
<string>remote-notification</string>
|
||||||
|
<string>fetch</string>
|
||||||
|
<string>location</string>
|
||||||
|
</array>
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
<string>LaunchScreen</string>
|
<string>LaunchScreen</string>
|
||||||
<key>UIMainStoryboardFile</key>
|
<key>UIMainStoryboardFile</key>
|
||||||
@@ -63,24 +76,5 @@
|
|||||||
</array>
|
</array>
|
||||||
<key>uses</key>
|
<key>uses</key>
|
||||||
<string></string>
|
<string></string>
|
||||||
|
|
||||||
<key>UIBackgroundModes</key>
|
|
||||||
<array>
|
|
||||||
<string>remote-notification</string>
|
|
||||||
<string>fetch</string>
|
|
||||||
</array>
|
|
||||||
|
|
||||||
<key>CFBundleLocalizations</key>
|
|
||||||
<array>
|
|
||||||
<string>zh-Hans</string>
|
|
||||||
<string>en</string>
|
|
||||||
</array>
|
|
||||||
|
|
||||||
<key>UIFileSharingEnabled</key>
|
|
||||||
<true/>
|
|
||||||
<!-- 允许在“文件”App中直接打开文档 -->
|
|
||||||
<key>LSSupportsOpeningDocumentsInPlace</key>
|
|
||||||
<true/>
|
|
||||||
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
66
ln_jq_app/ios/Runner/NativeFirstPage.swift
Normal file
@@ -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<UITouch>, with event: UIEvent?) {
|
||||||
|
view.backgroundColor = .orange
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1 +1,2 @@
|
|||||||
#import "GeneratedPluginRegistrant.h"
|
#import "GeneratedPluginRegistrant.h"
|
||||||
|
#import <AMapNavSDKManager.h>
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
import 'package:getx_scaffold/common/index.dart';
|
|
||||||
import 'package:ln_jq_app/pages/login/view.dart';
|
|
||||||
import 'package:ln_jq_app/storage_service.dart';
|
|
||||||
|
|
||||||
class AuthGuard {
|
|
||||||
static bool _handling401 = false;
|
|
||||||
|
|
||||||
static Future<void> handle401(String? message) async {
|
|
||||||
if (_handling401) return;
|
|
||||||
_handling401 = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await StorageService.to.clearLoginInfo();
|
|
||||||
Get.offAll(() => const LoginPage());
|
|
||||||
} finally {
|
|
||||||
// 防止意外卡死,可视情况是否延迟重置
|
|
||||||
Future.delayed(const Duration(seconds: 1), () {
|
|
||||||
_handling401 = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,9 +4,7 @@ class StationModel {
|
|||||||
final String address;
|
final String address;
|
||||||
final String price;
|
final String price;
|
||||||
final String siteStatusName; // 例如 "维修中"
|
final String siteStatusName; // 例如 "维修中"
|
||||||
final int isSelect; // 1是可用 0是不可用
|
final int isSelect; // 新增字段 1是可用 0是不可用
|
||||||
final String startBusiness; // 新增:可预约最早开始时间,如 "06:00:00"
|
|
||||||
final String endBusiness; // 新增:可预约最晚结束时间,如 "22:00:00"
|
|
||||||
|
|
||||||
StationModel({
|
StationModel({
|
||||||
required this.hydrogenId,
|
required this.hydrogenId,
|
||||||
@@ -15,10 +13,9 @@ class StationModel {
|
|||||||
required this.price,
|
required this.price,
|
||||||
required this.siteStatusName,
|
required this.siteStatusName,
|
||||||
required this.isSelect,
|
required this.isSelect,
|
||||||
required this.startBusiness,
|
|
||||||
required this.endBusiness,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 从 JSON map 创建对象的工厂构造函数
|
||||||
factory StationModel.fromJson(Map<String, dynamic> json) {
|
factory StationModel.fromJson(Map<String, dynamic> json) {
|
||||||
return StationModel(
|
return StationModel(
|
||||||
hydrogenId: json['hydrogenId'] ?? '',
|
hydrogenId: json['hydrogenId'] ?? '',
|
||||||
@@ -26,9 +23,7 @@ class StationModel {
|
|||||||
address: json['address'] ?? '地址未知',
|
address: json['address'] ?? '地址未知',
|
||||||
price: json['price']?.toString() ?? '0.00',
|
price: json['price']?.toString() ?? '0.00',
|
||||||
siteStatusName: json['siteStatusName'] ?? '',
|
siteStatusName: json['siteStatusName'] ?? '',
|
||||||
isSelect: json['isSelect'] as int? ?? 0,
|
isSelect: json['isSelect'] as int? ?? 0, // 新增字段的解析,默认为 0
|
||||||
startBusiness: json['startBusiness'] ?? '00:00:00', // 默认全天
|
|
||||||
endBusiness: json['endBusiness'] ?? '23:59:59', // 默认全天
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import 'package:flutter_localizations/flutter_localizations.dart';
|
|||||||
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
||||||
import 'package:get_storage/get_storage.dart';
|
import 'package:get_storage/get_storage.dart';
|
||||||
import 'package:getx_scaffold/getx_scaffold.dart';
|
import 'package:getx_scaffold/getx_scaffold.dart';
|
||||||
import 'package:ln_jq_app/common/AuthGuard.dart';
|
|
||||||
import 'package:ln_jq_app/common/model/base_model.dart';
|
import 'package:ln_jq_app/common/model/base_model.dart';
|
||||||
import 'package:ln_jq_app/common/token_interceptor.dart';
|
import 'package:ln_jq_app/common/token_interceptor.dart';
|
||||||
import 'package:ln_jq_app/storage_service.dart';
|
import 'package:ln_jq_app/storage_service.dart';
|
||||||
@@ -66,7 +65,6 @@ void main() async {
|
|||||||
void initHttpSet() {
|
void initHttpSet() {
|
||||||
AppTheme.test_service_url = StorageService.to.hostUrl ?? AppTheme.test_service_url;
|
AppTheme.test_service_url = StorageService.to.hostUrl ?? AppTheme.test_service_url;
|
||||||
|
|
||||||
HttpService.to.init(timeout: 15);
|
|
||||||
HttpService.to.setBaseUrl(AppTheme.test_service_url);
|
HttpService.to.setBaseUrl(AppTheme.test_service_url);
|
||||||
HttpService.to.dio.interceptors.add(TokenInterceptor(tokenKey: 'asoco-token'));
|
HttpService.to.dio.interceptors.add(TokenInterceptor(tokenKey: 'asoco-token'));
|
||||||
HttpService.to.setOnResponseHandler((response) async {
|
HttpService.to.setOnResponseHandler((response) async {
|
||||||
@@ -78,7 +76,8 @@ void initHttpSet() {
|
|||||||
if (baseModel.code == 0 || baseModel.code == 200) {
|
if (baseModel.code == 0 || baseModel.code == 200) {
|
||||||
return null;
|
return null;
|
||||||
} else if (baseModel.code == 401) {
|
} else if (baseModel.code == 401) {
|
||||||
await AuthGuard.handle401(baseModel.message);
|
await StorageService.to.clearLoginInfo();
|
||||||
|
// Get.offAll(() => const LoginPage());
|
||||||
return baseModel.message;
|
return baseModel.message;
|
||||||
} else {
|
} else {
|
||||||
return (baseModel.error.toString()).isEmpty
|
return (baseModel.error.toString()).isEmpty
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:getx_scaffold/getx_scaffold.dart';
|
import 'package:getx_scaffold/getx_scaffold.dart';
|
||||||
import 'package:ln_jq_app/common/model/base_model.dart';
|
import 'package:ln_jq_app/common/model/base_model.dart';
|
||||||
import 'package:ln_jq_app/pages/b_page/site/controller.dart'; // Reuse ReservationModel
|
import 'package:ln_jq_app/pages/b_page/site/controller.dart'; // Reuse ReservationModel
|
||||||
|
|
||||||
class HistoryController extends GetxController with BaseControllerMixin {
|
class HistoryController extends GetxController {
|
||||||
@override
|
|
||||||
String get builderId => 'history';
|
|
||||||
|
|
||||||
// --- 定义 API 需要的日期格式化器 ---
|
// --- 定义 API 需要的日期格式化器 ---
|
||||||
final DateFormat _apiDateFormat = DateFormat('yyyy-MM-dd');
|
final DateFormat _apiDateFormat = DateFormat('yyyy-MM-dd');
|
||||||
|
|
||||||
@@ -14,8 +13,8 @@ class HistoryController extends GetxController with BaseControllerMixin {
|
|||||||
final Rx<DateTime> endDate = DateTime.now().obs;
|
final Rx<DateTime> endDate = DateTime.now().obs;
|
||||||
final TextEditingController plateNumberController = TextEditingController();
|
final TextEditingController plateNumberController = TextEditingController();
|
||||||
|
|
||||||
final RxString totalHydrogen = '0'.obs;
|
final RxString totalHydrogen = '0 kg'.obs;
|
||||||
final RxString totalCompletions = '0'.obs;
|
final RxString totalCompletions = '0 次'.obs;
|
||||||
|
|
||||||
final RxList<ReservationModel> historyList = <ReservationModel>[].obs;
|
final RxList<ReservationModel> historyList = <ReservationModel>[].obs;
|
||||||
final RxBool isLoading = true.obs;
|
final RxBool isLoading = true.obs;
|
||||||
@@ -24,31 +23,14 @@ class HistoryController extends GetxController with BaseControllerMixin {
|
|||||||
String get formattedStartDate => DateFormat('yyyy/MM/dd').format(startDate.value);
|
String get formattedStartDate => DateFormat('yyyy/MM/dd').format(startDate.value);
|
||||||
|
|
||||||
String get formattedEndDate => DateFormat('yyyy/MM/dd').format(endDate.value);
|
String get formattedEndDate => DateFormat('yyyy/MM/dd').format(endDate.value);
|
||||||
|
|
||||||
String stationName = "";
|
String stationName = "";
|
||||||
|
|
||||||
final Map<String, String> statusOptions = {
|
|
||||||
'': '全部',
|
|
||||||
'100': '未预约加氢',
|
|
||||||
'0': '待加氢',
|
|
||||||
'1': '已加氢',
|
|
||||||
'2': '未加氢',
|
|
||||||
'5': '拒绝加氢',
|
|
||||||
};
|
|
||||||
|
|
||||||
final RxString selectedStatus = ''.obs;
|
|
||||||
final RxString selectedDateType = ''.obs; // week, month, three_month
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
final args = Get.arguments as Map<String, dynamic>;
|
|
||||||
stationName = args['stationName'] as String? ?? "";
|
|
||||||
refreshData();
|
|
||||||
}
|
|
||||||
|
|
||||||
void refreshData() {
|
final args = Get.arguments as Map<String, dynamic>;
|
||||||
getAllOrderCounts();
|
stationName = args['stationName'] as String;
|
||||||
fetchHistoryData();
|
fetchHistoryData();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,50 +38,51 @@ class HistoryController extends GetxController with BaseControllerMixin {
|
|||||||
var response = await HttpService.to.post(
|
var response = await HttpService.to.post(
|
||||||
"appointment/orderAddHyd/getAllOrderCounts",
|
"appointment/orderAddHyd/getAllOrderCounts",
|
||||||
data: {
|
data: {
|
||||||
/*'startTime': _apiDateFormat.format(startDate.value),
|
// --- 直接使用 DateFormat 来格式化日期 ---
|
||||||
'endTime': _apiDateFormat.format(endDate.value),*/
|
'startTime': _apiDateFormat.format(startDate.value),
|
||||||
|
'endTime': _apiDateFormat.format(endDate.value),
|
||||||
'plateNumber': plateNumberController.text,
|
'plateNumber': plateNumberController.text,
|
||||||
'stationName': stationName,
|
'stationName': stationName, // 加氢站名称
|
||||||
"status": selectedStatus.value,
|
|
||||||
"dateType": selectedDateType.value,
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (response == null || response.data == null) {
|
if (response == null || response.data == null) {
|
||||||
totalHydrogen.value = '0';
|
totalHydrogen.value = '0 kg';
|
||||||
totalCompletions.value = '0';
|
totalCompletions.value = '0 次';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
final baseModel = BaseModel<dynamic>.fromJson(response.data);
|
final baseModel = BaseModel<dynamic>.fromJson(response.data);
|
||||||
final dataMap = baseModel.data as Map<String, dynamic>;
|
final dataMap = baseModel.data as Map<String, dynamic>;
|
||||||
totalHydrogen.value = '${dataMap['totalAddAmount'] ?? 0}';
|
totalHydrogen.value = '${dataMap['totalAddAmount'] ?? 0} kg';
|
||||||
totalCompletions.value = '${dataMap['orderCompleteCount'] ?? 0}';
|
totalCompletions.value = '${dataMap['orderCompleteCount'] ?? 0} 次';
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
totalHydrogen.value = '0';
|
totalHydrogen.value = '0 kg';
|
||||||
totalCompletions.value = '0';
|
totalCompletions.value = '0 次';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> fetchHistoryData() async {
|
Future<void> fetchHistoryData() async {
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
updateUi();
|
|
||||||
|
//获取数据
|
||||||
|
getAllOrderCounts();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var response = await HttpService.to.post(
|
var response = await HttpService.to.post(
|
||||||
"appointment/orderAddHyd/sitOrderPage",
|
"appointment/orderAddHyd/sitOrderPage",
|
||||||
data: {
|
data: {
|
||||||
/*'startTime': _apiDateFormat.format(startDate.value),
|
// --- 直接使用 DateFormat 来格式化日期 ---
|
||||||
'endTime': _apiDateFormat.format(endDate.value),*/
|
'startTime': _apiDateFormat.format(startDate.value),
|
||||||
|
'endTime': _apiDateFormat.format(endDate.value),
|
||||||
'plateNumber': plateNumberController.text,
|
'plateNumber': plateNumberController.text,
|
||||||
'pageNum': 1,
|
'pageNum': 1,
|
||||||
'pageSize': 50,
|
'pageSize': 50,
|
||||||
'stationName': stationName,
|
'stationName': stationName, // 加氢站名称
|
||||||
"status": selectedStatus.value,
|
|
||||||
"dateType": selectedDateType.value,
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response == null || response.data == null) {
|
if (response == null || response.data == null) {
|
||||||
|
showToast('无法获取历史记录');
|
||||||
_resetData();
|
_resetData();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -107,6 +90,7 @@ class HistoryController extends GetxController with BaseControllerMixin {
|
|||||||
final baseModel = BaseModel<dynamic>.fromJson(response.data);
|
final baseModel = BaseModel<dynamic>.fromJson(response.data);
|
||||||
if (baseModel.code == 0 && baseModel.data != null) {
|
if (baseModel.code == 0 && baseModel.data != null) {
|
||||||
final dataMap = baseModel.data as Map<String, dynamic>;
|
final dataMap = baseModel.data as Map<String, dynamic>;
|
||||||
|
|
||||||
final List<dynamic> listFromServer = dataMap['records'] ?? [];
|
final List<dynamic> listFromServer = dataMap['records'] ?? [];
|
||||||
historyList.assignAll(
|
historyList.assignAll(
|
||||||
listFromServer
|
listFromServer
|
||||||
@@ -115,13 +99,14 @@ class HistoryController extends GetxController with BaseControllerMixin {
|
|||||||
);
|
);
|
||||||
hasData.value = historyList.isNotEmpty;
|
hasData.value = historyList.isNotEmpty;
|
||||||
} else {
|
} else {
|
||||||
|
showToast(baseModel.message);
|
||||||
_resetData();
|
_resetData();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
showToast('获取历史记录失败: $e');
|
||||||
_resetData();
|
_resetData();
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
updateUi();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,15 +115,97 @@ class HistoryController extends GetxController with BaseControllerMixin {
|
|||||||
hasData.value = false;
|
hasData.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void onStatusSelected(String status) {
|
void pickDate(BuildContext context, bool isStartDate) {
|
||||||
if (selectedStatus.value == status) return;
|
// 确定当前操作的日期和临时存储变量
|
||||||
selectedStatus.value = status;
|
final DateTime initialDate = isStartDate ? startDate.value : endDate.value;
|
||||||
refreshData();
|
DateTime tempDate = initialDate;
|
||||||
|
|
||||||
|
// 定义全局的最早可选日期
|
||||||
|
final DateTime globalMinimumDate = DateTime(2025, 12, 1);
|
||||||
|
|
||||||
|
// 动态计算当前选择器的最小/最大日期范围
|
||||||
|
DateTime minimumDate;
|
||||||
|
DateTime? maximumDate; // 声明为可空,因为两个日期都可能没有最大限制
|
||||||
|
|
||||||
|
if (isStartDate) {
|
||||||
|
// 当选择【开始日期】时 它的最小日期就是全局最小日期
|
||||||
|
minimumDate = globalMinimumDate;
|
||||||
|
// 最大日期没有限制
|
||||||
|
maximumDate = null;
|
||||||
|
} else {
|
||||||
|
// 当选择【结束日期】时 它的最小日期不能早于当前的开始日期
|
||||||
|
minimumDate = startDate.value;
|
||||||
|
// 确认结束日期没有最大限制 ---
|
||||||
|
//最大日期没有限制
|
||||||
|
maximumDate = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
void onDateTypeSelected(String type) {
|
Get.bottomSheet(
|
||||||
selectedDateType.value = type;
|
Container(
|
||||||
refreshData();
|
height: 300,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(16),
|
||||||
|
topRight: Radius.circular(16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
// 顶部的取消和确认按钮
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Get.back(),
|
||||||
|
child: const Text('取消', style: TextStyle(color: Colors.grey)),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
// 4. 确认后,更新对应的日期变量
|
||||||
|
if (isStartDate) {
|
||||||
|
startDate.value = tempDate;
|
||||||
|
// 如果新的开始日期晚于结束日期,自动将结束日期调整为同一天
|
||||||
|
if (tempDate.isAfter(endDate.value)) {
|
||||||
|
endDate.value = tempDate;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
endDate.value = tempDate;
|
||||||
|
}
|
||||||
|
Get.back();
|
||||||
|
|
||||||
|
// 选择日期后自动刷新数据
|
||||||
|
fetchHistoryData();
|
||||||
|
},
|
||||||
|
child: const Text(
|
||||||
|
'确认',
|
||||||
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Divider(height: 1),
|
||||||
|
// 日期选择器
|
||||||
|
Expanded(
|
||||||
|
child: CupertinoDatePicker(
|
||||||
|
mode: CupertinoDatePickerMode.date,
|
||||||
|
initialDateTime: initialDate,
|
||||||
|
// 应用动态计算好的最小/最大日期
|
||||||
|
minimumDate: minimumDate,
|
||||||
|
maximumDate: maximumDate,
|
||||||
|
onDateTimeChanged: (DateTime newDate) {
|
||||||
|
tempDate = newDate;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
backgroundColor: Colors.transparent, // 使底部工作表外的区域透明
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -1,173 +1,107 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:getx_scaffold/getx_scaffold.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:ln_jq_app/common/login_util.dart';
|
import 'package:ln_jq_app/common/styles/theme.dart';
|
||||||
import 'package:ln_jq_app/pages/b_page/history/controller.dart';
|
import 'package:ln_jq_app/pages/b_page/history/controller.dart';
|
||||||
import 'package:ln_jq_app/pages/b_page/site/controller.dart';
|
import 'package:ln_jq_app/pages/b_page/site/controller.dart'; // Reuse ReservationModel
|
||||||
|
|
||||||
class HistoryPage extends GetView<HistoryController> {
|
class HistoryPage extends GetView<HistoryController> {
|
||||||
const HistoryPage({super.key});
|
const HistoryPage({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return GetBuilder<HistoryController>(
|
Get.put(HistoryController());
|
||||||
init: HistoryController(),
|
|
||||||
id: 'history',
|
|
||||||
builder: (_) {
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: const Color(0xFFF7F8FA),
|
appBar: AppBar(title: const Text('历史记录'), centerTitle: true),
|
||||||
appBar: AppBar(
|
body: Padding(
|
||||||
backgroundColor: Colors.white,
|
padding: const EdgeInsets.all(12.0),
|
||||||
elevation: 0,
|
child: Column(
|
||||||
leading: IconButton(
|
|
||||||
icon: const Icon(Icons.arrow_back_ios, color: Colors.black, size: 20),
|
|
||||||
onPressed: () => Get.back(),
|
|
||||||
),
|
|
||||||
title: _buildSearchBox(),
|
|
||||||
),
|
|
||||||
body: Column(
|
|
||||||
children: [
|
children: [
|
||||||
_buildFilterBar(),
|
_buildFilterCard(context),
|
||||||
|
const SizedBox(height: 12),
|
||||||
_buildSummaryCard(),
|
_buildSummaryCard(),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
_buildListHeader(),
|
||||||
Expanded(child: _buildHistoryList()),
|
Expanded(child: _buildHistoryList()),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildSearchBox() {
|
Widget _buildFilterCard(BuildContext context) {
|
||||||
return Container(
|
return Card(
|
||||||
height: 36,
|
elevation: 2,
|
||||||
decoration: BoxDecoration(
|
child: Padding(
|
||||||
color: const Color(0xFFF2F3F5),
|
padding: const EdgeInsets.all(16.0),
|
||||||
borderRadius: BorderRadius.circular(18),
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Text('时间范围', style: TextStyle(fontSize: 14, color: Colors.grey)),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(child: _buildDateField(context, true)),
|
||||||
|
const Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
|
child: Text('至'),
|
||||||
),
|
),
|
||||||
|
Expanded(child: _buildDateField(context, false)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
const Text('车牌号', style: TextStyle(fontSize: 14, color: Colors.grey)),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
SizedBox(
|
||||||
|
height: 44,
|
||||||
child: TextField(
|
child: TextField(
|
||||||
controller: controller.plateNumberController,
|
controller: controller.plateNumberController,
|
||||||
onSubmitted: (v) => controller.refreshData(),
|
decoration: InputDecoration(
|
||||||
decoration: const InputDecoration(
|
hintText: '请输入车牌号',
|
||||||
hintText: '搜索车牌号',
|
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)),
|
||||||
hintStyle: TextStyle(color: Color(0xFFBBBBBB), fontSize: 14),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
prefixIcon: Icon(Icons.search_sharp, color: Color(0xFFBBBBBB), size: 20),
|
|
||||||
border: InputBorder.none,
|
|
||||||
contentPadding: EdgeInsets.only(bottom: 12),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildFilterBar() {
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
scrollDirection: Axis.horizontal,
|
|
||||||
child: Row(
|
|
||||||
children: controller.statusOptions.entries.map((entry) {
|
|
||||||
return Obx(() {
|
|
||||||
bool isSelected = controller.selectedStatus.value == entry.key;
|
|
||||||
return GestureDetector(
|
|
||||||
onTap: () => controller.onStatusSelected(entry.key),
|
|
||||||
child: Container(
|
|
||||||
margin: const EdgeInsets.only(right: 12),
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: isSelected ? const Color(0xFF006633) : Colors.white,
|
|
||||||
borderRadius: BorderRadius.circular(15),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
entry.value,
|
|
||||||
style: TextStyle(
|
|
||||||
color: isSelected
|
|
||||||
? Colors.white
|
|
||||||
: Color.fromRGBO(148, 163, 184, 1),
|
|
||||||
fontSize: 12.sp,
|
|
||||||
fontWeight: isSelected ? FontWeight.bold : FontWeight.w600,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
const SizedBox(height: 16),
|
||||||
});
|
ElevatedButton.icon(
|
||||||
}).toList(),
|
onPressed: () {
|
||||||
|
FocusScope.of(context).unfocus(); // Hide keyboard
|
||||||
|
controller.fetchHistoryData();
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.search, size: 20),
|
||||||
|
label: const Text('查询'),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
minimumSize: const Size(double.infinity, 44),
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
_buildTimeFilterIcon(),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildTimeFilterIcon() {
|
|
||||||
return PopupMenuButton<String>(
|
|
||||||
icon: LoginUtil.getAssImg("ic_ex_menu@2x"),
|
|
||||||
onSelected: controller.onDateTypeSelected,
|
|
||||||
itemBuilder: (context) => [
|
|
||||||
const PopupMenuItem(value: 'week', child: Text('最近一周')),
|
|
||||||
const PopupMenuItem(value: 'month', child: Text('最近一月')),
|
|
||||||
const PopupMenuItem(value: 'three_month', child: Text('最近三月')),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildSummaryCard() {
|
Widget _buildSummaryCard() {
|
||||||
return Container(
|
return Card(
|
||||||
margin: const EdgeInsets.only(left: 16, right: 16,bottom: 12),
|
elevation: 2,
|
||||||
padding: const EdgeInsets.all(20),
|
child: Padding(
|
||||||
height: 160,
|
padding: const EdgeInsets.symmetric(vertical: 20.0),
|
||||||
width: double.infinity,
|
child: Obx(
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.circular(24),
|
|
||||||
image: const DecorationImage(
|
|
||||||
image: AssetImage('assets/images/history_bg.png'),
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const Text('加氢站', style: TextStyle(color: Colors.white70, fontSize: 12)),
|
|
||||||
Text(
|
|
||||||
controller.stationName,
|
|
||||||
style: const TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontSize: 20,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Spacer(),
|
|
||||||
Obx(
|
|
||||||
() => Row(
|
() => Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
children: [
|
children: [
|
||||||
_buildSummaryItem('实际加氢量', '${controller.totalHydrogen.value} Kg'),
|
_buildSummaryItem('实际加氢总量', controller.totalHydrogen.value, Colors.blue),
|
||||||
const SizedBox(width: 40),
|
const SizedBox(width: 1, height: 40, child: VerticalDivider()),
|
||||||
_buildSummaryItem('预约完成次数', '${controller.totalCompletions.value} 次'),
|
_buildSummaryItem(
|
||||||
],
|
'预约完成次数',
|
||||||
),
|
controller.totalCompletions.value,
|
||||||
|
Colors.green,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildSummaryItem(String label, String value) {
|
|
||||||
return Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(label, style: const TextStyle(color: Colors.white60, fontSize: 12)),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text(
|
|
||||||
value,
|
|
||||||
style: const TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,138 +110,143 @@ class HistoryPage extends GetView<HistoryController> {
|
|||||||
if (controller.isLoading.value) {
|
if (controller.isLoading.value) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
}
|
}
|
||||||
if (controller.historyList.isEmpty) {
|
if (!controller.hasData.value) {
|
||||||
return const Center(
|
return const Center(child: Text('没有找到相关记录'));
|
||||||
child: Text('暂无相关记录', style: TextStyle(color: Color(0xFF999999))),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return ListView.builder(
|
return ListView.builder(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
||||||
itemCount: controller.historyList.length,
|
itemCount: controller.historyList.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return _buildHistoryItem(controller.historyList[index]);
|
final ReservationModel item = controller.historyList[index];
|
||||||
|
return Card(
|
||||||
|
margin: const EdgeInsets.only(bottom: 8),
|
||||||
|
child: ListTile(
|
||||||
|
title: Text('车牌号: ${item.plateNumber}'),
|
||||||
|
subtitle: Text.rich(
|
||||||
|
TextSpan(
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: '加氢站: ${item.stationName}\n',
|
||||||
|
style: TextStyle(fontSize: 16),
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
text: '时间: ${item.time}\n',
|
||||||
|
style: TextStyle(fontSize: 16),
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
text: '加氢量:',
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
text: '${item.amount}',
|
||||||
|
style: TextStyle(fontSize: 16, color: AppTheme.themeColor),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
,
|
||||||
|
trailing:
|
||||||
|
// 状态标签
|
||||||
|
_buildStatusChip(item.status),
|
||||||
|
),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildHistoryItem(ReservationModel item) {
|
Widget _buildStatusChip(ReservationStatus status) {
|
||||||
return Container(
|
String text;
|
||||||
margin: const EdgeInsets.only(bottom: 12),
|
Color color;
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'车牌号',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Color.fromRGBO(148, 163, 184, 1),
|
|
||||||
fontSize: 12.sp,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text(
|
|
||||||
item.plateNumber,
|
|
||||||
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
_buildStatusBadge(item.status),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
_buildInfoColumn('加氢时间:', item.time),
|
|
||||||
_buildInfoColumn('加氢量', '${item.amount} Kg', isRight: true),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildInfoColumn(String label, String value, {bool isRight = false}) {
|
|
||||||
return Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: isRight ? CrossAxisAlignment.end : CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
label,
|
|
||||||
style: TextStyle(color: Color.fromRGBO(148, 163, 184, 1), fontSize: 12.sp),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text(
|
|
||||||
value,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: isRight ? 16 : 13,
|
|
||||||
fontWeight: isRight ? FontWeight.bold : FontWeight.normal,
|
|
||||||
color: const Color(0xFF333333),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildStatusBadge(ReservationStatus status) {
|
|
||||||
String text = '未知';
|
|
||||||
Color bgColor = Colors.grey.shade100;
|
|
||||||
Color textColor = Colors.grey;
|
|
||||||
|
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case ReservationStatus.pending:
|
case ReservationStatus.pending:
|
||||||
text = '待加氢';
|
text = '待加氢';
|
||||||
bgColor = const Color(0xFFFFF7E8);
|
color = Colors.orange;
|
||||||
textColor = const Color(0xFFFF9800);
|
|
||||||
break;
|
break;
|
||||||
case ReservationStatus.completed:
|
case ReservationStatus.completed:
|
||||||
text = '已加氢';
|
text = '已加氢';
|
||||||
bgColor = const Color(0xFFE8F5E9);
|
color = Colors.greenAccent;
|
||||||
textColor = const Color(0xFF4CAF50);
|
|
||||||
break;
|
break;
|
||||||
case ReservationStatus.rejected:
|
case ReservationStatus.rejected:
|
||||||
text = '拒绝加氢';
|
text = '拒绝加氢';
|
||||||
bgColor = const Color(0xFFFFEBEE);
|
color = Colors.red;
|
||||||
textColor = const Color(0xFFF44336);
|
|
||||||
break;
|
break;
|
||||||
case ReservationStatus.unadded:
|
case ReservationStatus.unadded:
|
||||||
text = '未加氢';
|
text = '未加氢';
|
||||||
bgColor = const Color(0xFFFFEBEE);
|
color = Colors.red;
|
||||||
textColor = const Color(0xFFF44336);
|
|
||||||
break;
|
break;
|
||||||
case ReservationStatus.cancel:
|
case ReservationStatus.cancel:
|
||||||
text = '已取消';
|
text = '已取消';
|
||||||
bgColor = const Color(0xFFFFEBEE);
|
color = Colors.red;
|
||||||
textColor = const Color(0xFFF44336);
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
text = '未知状态';
|
text = '未知状态';
|
||||||
bgColor = Colors.grey;
|
color = Colors.grey;
|
||||||
textColor = Colors.grey;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: bgColor,
|
color: color.withOpacity(0.1),
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(12),
|
||||||
border: Border.all(color: textColor.withOpacity(0.3)),
|
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.circle, color: color, size: 8),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
Text(
|
||||||
text,
|
text,
|
||||||
style: TextStyle(color: textColor, fontSize: 12, fontWeight: FontWeight.bold),
|
style: TextStyle(color: color, fontSize: 12, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDateField(BuildContext context, bool isStart) {
|
||||||
|
return Obx(
|
||||||
|
() => InkWell(
|
||||||
|
onTap: () => controller.pickDate(context, isStart),
|
||||||
|
child: Container(
|
||||||
|
height: 44,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(color: Colors.grey.shade400),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(isStart ? controller.formattedStartDate : controller.formattedEndDate),
|
||||||
|
const Icon(Icons.calendar_today, size: 18, color: Colors.grey),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSummaryItem(String label, String value, Color color) {
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(label, style: const TextStyle(color: Colors.grey, fontSize: 14)),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
value,
|
||||||
|
style: TextStyle(color: color, fontSize: 22, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildListHeader() {
|
||||||
|
return const Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 14.0),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text('加氢明细', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,9 @@
|
|||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:flutter/gestures.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:getx_scaffold/getx_scaffold.dart';
|
import 'package:getx_scaffold/getx_scaffold.dart';
|
||||||
import 'package:ln_jq_app/common/login_util.dart';
|
import 'package:ln_jq_app/common/login_util.dart';
|
||||||
import 'package:ln_jq_app/pages/b_page/reservation/controller.dart';
|
import 'package:ln_jq_app/pages/b_page/reservation/controller.dart';
|
||||||
import 'package:ln_jq_app/pages/c_page/message/view.dart';
|
import 'package:ln_jq_app/pages/c_page/message/view.dart';
|
||||||
import 'package:ln_jq_app/pages/common/webview/view.dart';
|
|
||||||
|
|
||||||
class ReservationPage extends GetView<ReservationController> {
|
class ReservationPage extends GetView<ReservationController> {
|
||||||
const ReservationPage({super.key});
|
const ReservationPage({super.key});
|
||||||
@@ -40,31 +36,7 @@ class ReservationPage extends GetView<ReservationController> {
|
|||||||
_buildSystemTips(),
|
_buildSystemTips(),
|
||||||
SizedBox(height: 24),
|
SizedBox(height: 24),
|
||||||
_buildLogoutButton(),
|
_buildLogoutButton(),
|
||||||
SizedBox(height: 15.h),
|
SizedBox(height: 75.h),
|
||||||
Text.rich(
|
|
||||||
TextSpan(
|
|
||||||
style: const TextStyle(color: Colors.grey, fontSize: 13),
|
|
||||||
children: [
|
|
||||||
TextSpan(
|
|
||||||
text: '《用户协议》',
|
|
||||||
style: TextStyle(color: Colors.blue, fontSize: 13),
|
|
||||||
recognizer: TapGestureRecognizer()
|
|
||||||
..onTap = () {
|
|
||||||
openPage("用户协议", "https://lnh2e.com/user_agreement.html");
|
|
||||||
},
|
|
||||||
),
|
|
||||||
TextSpan(
|
|
||||||
text: '《隐私政策》',
|
|
||||||
style: TextStyle(color: Colors.blue, fontSize: 13),
|
|
||||||
recognizer: TapGestureRecognizer()
|
|
||||||
..onTap = () {
|
|
||||||
openPage("隐私政策", "https://lnh2e.com/privacy_agreement.html");
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(height: 95.h),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -76,14 +48,6 @@ class ReservationPage extends GetView<ReservationController> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void openPage(String title, String url) {
|
|
||||||
if (Platform.isIOS) {
|
|
||||||
openWebPage(url);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Get.to(() => const WebViewPage(), arguments: {'title': title, 'url': url});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 1. 顶部个人信息及统计栏
|
/// 1. 顶部个人信息及统计栏
|
||||||
Widget _buildTopSection(BuildContext context) {
|
Widget _buildTopSection(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
@@ -114,16 +78,12 @@ class ReservationPage extends GetView<ReservationController> {
|
|||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Flexible(
|
Text(
|
||||||
child: Text(
|
|
||||||
controller.name,
|
controller.name,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
_buildStatusTag(),
|
_buildStatusTag(),
|
||||||
@@ -138,11 +98,12 @@ class ReservationPage extends GetView<ReservationController> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () async {
|
onPressed: () async{
|
||||||
var scanResult = await Get.to(() => const MessagePage());
|
var scanResult = await Get.to(() => const MessagePage());
|
||||||
if (scanResult == null) {
|
if (scanResult == null) {
|
||||||
controller.msgNotice();
|
controller.msgNotice();
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
style: IconButton.styleFrom(
|
style: IconButton.styleFrom(
|
||||||
backgroundColor: Colors.grey[100],
|
backgroundColor: Colors.grey[100],
|
||||||
@@ -150,7 +111,9 @@ class ReservationPage extends GetView<ReservationController> {
|
|||||||
),
|
),
|
||||||
icon: Badge(
|
icon: Badge(
|
||||||
smallSize: 8,
|
smallSize: 8,
|
||||||
backgroundColor: controller.isNotice ? Colors.red : Colors.transparent,
|
backgroundColor: controller.isNotice
|
||||||
|
? Colors.red
|
||||||
|
: Colors.transparent,
|
||||||
child: const Icon(
|
child: const Icon(
|
||||||
Icons.notifications_outlined,
|
Icons.notifications_outlined,
|
||||||
color: Colors.black87,
|
color: Colors.black87,
|
||||||
@@ -269,19 +232,14 @@ class ReservationPage extends GetView<ReservationController> {
|
|||||||
label,
|
label,
|
||||||
style: TextStyle(color: Colors.grey, fontSize: 11.sp),
|
style: TextStyle(color: Colors.grey, fontSize: 11.sp),
|
||||||
),
|
),
|
||||||
Expanded(
|
Text(
|
||||||
child: Text(
|
|
||||||
value,
|
value,
|
||||||
textAlign: TextAlign.right,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
maxLines: 1,
|
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: const Color(0xFF333333),
|
color: Color(0xFF333333),
|
||||||
fontSize: 12.sp,
|
fontSize: 12.sp,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -557,15 +515,14 @@ class ReservationPage extends GetView<ReservationController> {
|
|||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: kPrimaryColor,
|
backgroundColor: kPrimaryColor,
|
||||||
minimumSize: const Size(double.infinity, 50),
|
minimumSize: const Size(double.infinity, 50),
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
child: const Text("发送广播", style: TextStyle(color: Colors.white)),
|
child: const Text("发送广播", style: TextStyle(color: Colors.white)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:getx_scaffold/getx_scaffold.dart';
|
import 'package:getx_scaffold/getx_scaffold.dart';
|
||||||
import 'package:ln_jq_app/common/login_util.dart';
|
import 'package:ln_jq_app/common/login_util.dart';
|
||||||
|
import 'package:ln_jq_app/common/styles/theme.dart';
|
||||||
import 'package:ln_jq_app/pages/b_page/history/view.dart';
|
import 'package:ln_jq_app/pages/b_page/history/view.dart';
|
||||||
import 'package:ln_jq_app/pages/c_page/message/view.dart';
|
import 'package:ln_jq_app/pages/c_page/message/view.dart';
|
||||||
import 'package:pull_to_refresh/pull_to_refresh.dart';
|
import 'package:pull_to_refresh/pull_to_refresh.dart';
|
||||||
@@ -56,33 +57,17 @@ class SitePage extends GetView<SiteController> {
|
|||||||
),
|
),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
// 手动录入
|
Get.to(
|
||||||
controller.confirmReservation("", isAdd: true);
|
() => HistoryPage(),
|
||||||
|
arguments: {'stationName': controller.name},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Text(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
'历史记录',
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
border: Border.all(color: const Color(0xFFEEEEEE)),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
const Icon(
|
|
||||||
Icons.add_circle_outline,
|
|
||||||
size: 18,
|
|
||||||
color: Color(0xFF666666),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 4),
|
|
||||||
Text(
|
|
||||||
"无预约车辆加氢",
|
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Color.fromRGBO(51, 51, 51, 1),
|
fontSize: 14.sp,
|
||||||
fontSize: 13.sp,
|
fontWeight: FontWeight.bold,
|
||||||
fontWeight: FontWeight.w400
|
color: Color.fromRGBO(156, 163, 175, 1),
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -93,12 +78,12 @@ class SitePage extends GetView<SiteController> {
|
|||||||
Column(
|
Column(
|
||||||
children: [
|
children: [
|
||||||
_buildSearchView(),
|
_buildSearchView(),
|
||||||
SizedBox(height: 15.h),
|
|
||||||
controller.hasReservationData
|
controller.hasReservationData
|
||||||
? _buildReservationListView()
|
? _buildReservationListView()
|
||||||
: _buildEmptyReservationView(),
|
: _buildEmptyReservationView(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
SizedBox(height: 35.h),
|
SizedBox(height: 35.h),
|
||||||
//第三部分
|
//第三部分
|
||||||
Container(
|
Container(
|
||||||
@@ -151,7 +136,7 @@ class SitePage extends GetView<SiteController> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 105.h),
|
SizedBox(height: 75.h),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -200,7 +185,27 @@ class SitePage extends GetView<SiteController> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
_buildDropdownMenu(),
|
IconButton(
|
||||||
|
onPressed: () async {
|
||||||
|
var scanResult = await Get.to(() => const MessagePage());
|
||||||
|
if (scanResult == null) {
|
||||||
|
controller.msgNotice();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
style: IconButton.styleFrom(
|
||||||
|
backgroundColor: Colors.grey[100],
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
),
|
||||||
|
icon: Badge(
|
||||||
|
smallSize: 8,
|
||||||
|
backgroundColor: controller.isNotice ? Colors.red : Colors.transparent,
|
||||||
|
child: const Icon(
|
||||||
|
Icons.notifications_outlined,
|
||||||
|
color: Colors.black87,
|
||||||
|
size: 30,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 25),
|
const SizedBox(height: 25),
|
||||||
@@ -228,40 +233,6 @@ class SitePage extends GetView<SiteController> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildDropdownMenu() {
|
|
||||||
return PopupMenuButton<String>(
|
|
||||||
icon: Container(child: const Icon(Icons.grid_view_rounded, size: 24)),
|
|
||||||
onSelected: (value) async {
|
|
||||||
if (value == 'message') {
|
|
||||||
var scanResult = await Get.to(() => const MessagePage());
|
|
||||||
if (scanResult == null) {
|
|
||||||
controller.msgNotice();
|
|
||||||
}
|
|
||||||
} else if (value == 'history') {
|
|
||||||
Get.to(() => const HistoryPage(), arguments: {'stationName': controller.name});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
itemBuilder: (context) => [
|
|
||||||
const PopupMenuItem(
|
|
||||||
value: 'message',
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Icon(Icons.notifications_none, size: 20),
|
|
||||||
SizedBox(width: 8),
|
|
||||||
Text('消息中心'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const PopupMenuItem(
|
|
||||||
value: 'history',
|
|
||||||
child: Row(
|
|
||||||
children: [Icon(Icons.history, size: 20), SizedBox(width: 8), Text('加氢历史')],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildStatBox(String title, String enTitle, String value, String unit) {
|
Widget _buildStatBox(String title, String enTitle, String value, String unit) {
|
||||||
return Expanded(
|
return Expanded(
|
||||||
child: Container(
|
child: Container(
|
||||||
@@ -420,9 +391,8 @@ class SitePage extends GetView<SiteController> {
|
|||||||
|
|
||||||
/// 构建“有预约数据”的列表视图
|
/// 构建“有预约数据”的列表视图
|
||||||
Widget _buildReservationListView() {
|
Widget _buildReservationListView() {
|
||||||
return ListView.builder(
|
return ListView.separated(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
// 因为外层已有滚动,这里禁用内部滚动
|
// 因为外层已有滚动,这里禁用内部滚动
|
||||||
itemCount: controller.reservationList.length,
|
itemCount: controller.reservationList.length,
|
||||||
@@ -431,6 +401,7 @@ class SitePage extends GetView<SiteController> {
|
|||||||
// 调用新的方法来构建每一项
|
// 调用新的方法来构建每一项
|
||||||
return _buildReservationItem(index, item);
|
return _buildReservationItem(index, item);
|
||||||
},
|
},
|
||||||
|
separatorBuilder: (context, index) => const SizedBox(height: 0), // 列表项之间的间距
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -495,7 +466,7 @@ class SitePage extends GetView<SiteController> {
|
|||||||
/// 右侧具体数据卡片
|
/// 右侧具体数据卡片
|
||||||
Widget _buildInfoCard(ReservationModel item) {
|
Widget _buildInfoCard(ReservationModel item) {
|
||||||
return Container(
|
return Container(
|
||||||
padding: EdgeInsets.all(16),
|
padding: EdgeInsets.only(left: 16.w, top: 8.5, bottom: 8.5, right: 16.w),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
@@ -537,42 +508,22 @@ class SitePage extends GetView<SiteController> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
// 联系信息
|
// 联系信息
|
||||||
|
Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: [
|
||||||
Text(
|
Text(
|
||||||
item.contactPerson.isEmpty || item.contactPhone.isEmpty
|
"${item.contactPerson} | ${item.contactPhone}",
|
||||||
? ""
|
|
||||||
: "${item.contactPerson} | ${item.contactPhone}",
|
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Color(0xFF999999),
|
color: Color(0xFF999999),
|
||||||
fontSize: 13.sp,
|
fontSize: 13.sp,
|
||||||
fontWeight: FontWeight.w400,
|
fontWeight: FontWeight.w400,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 6.h),
|
|
||||||
Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
if (item.hasDrivingAttachment)
|
|
||||||
controller.buildInfoTag('行驶证',item.drivingAttachments),
|
|
||||||
if (item.hasHydrogenationAttachment) ...[
|
|
||||||
SizedBox(width: 8.w),
|
|
||||||
controller.buildInfoTag('加氢证',item.hydrogenationAttachments)
|
|
||||||
],
|
],
|
||||||
Spacer(),
|
|
||||||
if (item.isEdit == "1") ...[
|
|
||||||
const SizedBox(height: 15),
|
|
||||||
const Divider(height: 1, color: Color(0xFFF5F5F5)),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
Align(
|
|
||||||
alignment: Alignment.centerRight,
|
|
||||||
child: _buildSmallButton(
|
|
||||||
"修改信息",
|
|
||||||
isOutline: true,
|
|
||||||
onTap: () {
|
|
||||||
controller.confirmReservation(item.id, isEdit: true);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
|
||||||
] else if (item.status == ReservationStatus.pending) ...[
|
//操作按钮(仅在待处理状态显示)
|
||||||
|
if (item.status == ReservationStatus.pending) ...[
|
||||||
const SizedBox(height: 15),
|
const SizedBox(height: 15),
|
||||||
const Divider(height: 1, color: Color(0xFFF5F5F5)),
|
const Divider(height: 1, color: Color(0xFFF5F5F5)),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
@@ -599,20 +550,17 @@ class SitePage extends GetView<SiteController> {
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 通用小按钮
|
||||||
|
|
||||||
Widget _buildSmallButton(
|
Widget _buildSmallButton(
|
||||||
String text, {
|
String text, {
|
||||||
required bool isOutline,
|
required bool isOutline,
|
||||||
required VoidCallback onTap,
|
required VoidCallback onTap,
|
||||||
}) {
|
}) {
|
||||||
const kPrimaryGreen = Color(0xFF006D35);
|
const kPrimaryGreen = Color(0xFF006D35);
|
||||||
var kDangerRed = text.contains('修改') ? Colors.red : Color.fromRGBO(255, 142, 98, 1);
|
const kDangerRed = Color(0xFFFF7D7D);
|
||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
@@ -686,4 +634,139 @@ class SitePage extends GetView<SiteController> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 右侧操作按钮(拒绝/确认)
|
||||||
|
Widget _buildActionButtons(ReservationModel item) {
|
||||||
|
return Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
// 拒绝按钮(空心)
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () => controller.rejectReservation(item.id),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
border: Border.all(color: const Color(0xFFFF7D7D)),
|
||||||
|
),
|
||||||
|
child: const Text(
|
||||||
|
"拒绝",
|
||||||
|
style: TextStyle(
|
||||||
|
color: Color(0xFFFF7D7D),
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
// 确认按钮(实心深绿)
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () => controller.confirmReservation(item.id),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFF006D35),
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
child: const Text(
|
||||||
|
"确认",
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 构建状态标签
|
||||||
|
Widget _buildStatusChip(ReservationStatus status) {
|
||||||
|
String text;
|
||||||
|
Color color;
|
||||||
|
switch (status) {
|
||||||
|
case ReservationStatus.pending:
|
||||||
|
text = '待加氢';
|
||||||
|
color = Colors.orange;
|
||||||
|
break;
|
||||||
|
case ReservationStatus.completed:
|
||||||
|
text = '已加氢';
|
||||||
|
color = Colors.greenAccent;
|
||||||
|
break;
|
||||||
|
case ReservationStatus.rejected:
|
||||||
|
text = '拒绝加氢';
|
||||||
|
color = Colors.red;
|
||||||
|
break;
|
||||||
|
case ReservationStatus.unadded:
|
||||||
|
text = '未加氢';
|
||||||
|
color = Colors.red;
|
||||||
|
break;
|
||||||
|
case ReservationStatus.cancel:
|
||||||
|
text = '已取消';
|
||||||
|
color = Colors.red;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
text = '未知状态';
|
||||||
|
color = Colors.grey;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: color.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.circle, color: color, size: 8),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
Text(
|
||||||
|
text,
|
||||||
|
style: TextStyle(color: color, fontSize: 12, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 构建信息详情行
|
||||||
|
Widget _buildDetailRow(
|
||||||
|
IconData icon,
|
||||||
|
String label,
|
||||||
|
String value, {
|
||||||
|
Color valueColor = Colors.black87,
|
||||||
|
}) {
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
Icon(icon, color: Colors.grey, size: 20),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text('$label: ', style: const TextStyle(fontSize: 14, color: Colors.grey)),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
value,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: valueColor,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 底部构建带图标的提示信息行
|
||||||
|
Widget _buildInfoItem(IconData icon, String text) {
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
Icon(icon, color: Colors.blue, size: 20),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(text, style: const TextStyle(fontSize: 14, color: Colors.black54)),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
82
ln_jq_app/lib/pages/c_page/base_widgets/NativePageIOS.dart
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
/// 原生地图页面
|
||||||
|
class NativePageIOS extends StatelessWidget {
|
||||||
|
const NativePageIOS({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (Platform.isIOS) {
|
||||||
|
return _buildIOSView(context);
|
||||||
|
} else if (Platform.isAndroid) {
|
||||||
|
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: <Factory<OneSequenceGestureRecognizer>>{}.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: AndroidView(
|
||||||
|
viewType: 'NativeFirstPage', // 与Android原生端注册的标识一致
|
||||||
|
gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{}.toSet(),
|
||||||
|
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
|
||||||
|
creationParamsCodec: const StandardMessageCodec(),
|
||||||
|
layoutDirection: TextDirection.ltr,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 处理点击事件(如需要)
|
||||||
|
void _handleTap(BuildContext context) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
print("NativePage被点击");
|
||||||
|
}
|
||||||
|
_showDialog(context, '提示', '点击了原生地图页面');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 显示对话框
|
||||||
|
void _showDialog(BuildContext context, String title, String content) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: Text(title),
|
||||||
|
content: Text(content),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
child: const Text('确定'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:getx_scaffold/getx_scaffold.dart';
|
import 'package:getx_scaffold/getx_scaffold.dart';
|
||||||
import 'package:ln_jq_app/common/login_util.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/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/map/view.dart';
|
||||||
import 'package:ln_jq_app/pages/c_page/mine/view.dart';
|
import 'package:ln_jq_app/pages/c_page/mine/view.dart';
|
||||||
import 'package:ln_jq_app/pages/c_page/reservation/view.dart';
|
import 'package:ln_jq_app/pages/c_page/reservation/view.dart';
|
||||||
@@ -34,14 +34,14 @@ class BaseWidgetsPage extends GetView<BaseWidgetsController> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<Widget> _buildPages() {
|
List<Widget> _buildPages() {
|
||||||
return [ReservationPage(), MapPage(), MallPage(), CarInfoPage(), MinePage()];
|
return [ReservationPage(), NativePageIOS(), CarInfoPage(), MinePage()];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 自定义导航栏 (悬浮胶囊样式)
|
// 自定义导航栏 (悬浮胶囊样式)
|
||||||
Widget _buildNavigationBar() {
|
Widget _buildNavigationBar() {
|
||||||
return SafeArea(
|
return SafeArea(
|
||||||
child: Container(
|
child: Container(
|
||||||
height: Get.height * 0.05,
|
height: 50.h,
|
||||||
margin: const EdgeInsets.fromLTRB(24, 0, 24, 10), // 悬浮边距
|
margin: const EdgeInsets.fromLTRB(24, 0, 24, 10), // 悬浮边距
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Color.fromRGBO(240, 244, 247, 1), // 浅灰色背景
|
color: Color.fromRGBO(240, 244, 247, 1), // 浅灰色背景
|
||||||
@@ -59,9 +59,8 @@ class BaseWidgetsPage extends GetView<BaseWidgetsController> {
|
|||||||
children: [
|
children: [
|
||||||
_buildNavItem(0, "ic_h2_select@2x", "ic_h2@2x"),
|
_buildNavItem(0, "ic_h2_select@2x", "ic_h2@2x"),
|
||||||
_buildNavItem(1, "ic_map_select@2x", "ic_map@2x"),
|
_buildNavItem(1, "ic_map_select@2x", "ic_map@2x"),
|
||||||
_buildNavItem(2, "ic_mall_select@2x", "ic_mall@2x"),
|
_buildNavItem(2, "ic_car_select@2x", "ic_car@2x"),
|
||||||
_buildNavItem(3, "ic_car_select@2x", "ic_car@2x"),
|
_buildNavItem(3, "ic_user_select@2x", "ic_user@2x"),
|
||||||
_buildNavItem(4, "ic_user_select@2x", "ic_user@2x"),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -84,8 +83,7 @@ class BaseWidgetsPage extends GetView<BaseWidgetsController> {
|
|||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: 24,
|
height: 24,
|
||||||
width: 24,
|
width: 24,
|
||||||
child: LoginUtil.getAssImg(isSelected ? selectedIcon : icon),
|
child: LoginUtil.getAssImg(isSelected ? selectedIcon : icon),),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:getx_scaffold/getx_scaffold.dart';
|
import 'package:getx_scaffold/getx_scaffold.dart';
|
||||||
import 'package:getx_scaffold/getx_scaffold.dart' as dio;
|
|
||||||
import 'package:image_picker/image_picker.dart';
|
|
||||||
import 'package:image_picker_platform_interface/src/types/image_source.dart';
|
|
||||||
import 'package:ln_jq_app/common/model/base_model.dart';
|
import 'package:ln_jq_app/common/model/base_model.dart';
|
||||||
import 'package:ln_jq_app/common/model/vehicle_info.dart';
|
import 'package:ln_jq_app/common/model/vehicle_info.dart';
|
||||||
import 'package:ln_jq_app/pages/c_page/car_info/attachment_viewer_page.dart';
|
import 'package:ln_jq_app/pages/c_page/car_info/attachment_viewer_page.dart';
|
||||||
@@ -18,9 +15,9 @@ class CarInfoController extends GetxController with BaseControllerMixin {
|
|||||||
|
|
||||||
// --- 车辆基本信息 ---
|
// --- 车辆基本信息 ---
|
||||||
String plateNumber = "";
|
String plateNumber = "";
|
||||||
String vin = "-";
|
String vin = "未知";
|
||||||
String modelName = "-";
|
String modelName = "未知";
|
||||||
String brandName = "-";
|
String brandName = "未知";
|
||||||
|
|
||||||
// --- 证件附件列表 ---
|
// --- 证件附件列表 ---
|
||||||
final RxList<String> drivingAttachments = <String>[].obs;
|
final RxList<String> drivingAttachments = <String>[].obs;
|
||||||
@@ -92,113 +89,6 @@ class CarInfoController extends GetxController with BaseControllerMixin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//上传证件照
|
|
||||||
void pickImage(String title, ImageSource source) async {
|
|
||||||
if (source == ImageSource.camera) {
|
|
||||||
takePhotoAndRecognize(title);
|
|
||||||
} else if (source == ImageSource.gallery) {
|
|
||||||
//相册选择逻辑
|
|
||||||
var status = await Permission.photos.request();
|
|
||||||
if (!status.isGranted) {
|
|
||||||
if (status.isPermanentlyDenied) openAppSettings();
|
|
||||||
showErrorToast("需要相册权限才能拍照上传");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final XFile? image = await ImagePicker().pickImage(
|
|
||||||
source: ImageSource.gallery,
|
|
||||||
imageQuality: 80,
|
|
||||||
);
|
|
||||||
if (image != null) {
|
|
||||||
_uploadAndSaveCertificate(title, image.path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void takePhotoAndRecognize(String title) async {
|
|
||||||
var status = await Permission.camera.request();
|
|
||||||
if (!status.isGranted) {
|
|
||||||
if (status.isPermanentlyDenied) openAppSettings();
|
|
||||||
showErrorToast("需要相机权限才能拍照上传");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final XFile? photo = await ImagePicker().pickImage(
|
|
||||||
source: ImageSource.camera,
|
|
||||||
imageQuality: 80, // 压缩图片质量以加快上传
|
|
||||||
);
|
|
||||||
if (photo == null) return;
|
|
||||||
|
|
||||||
_uploadAndSaveCertificate(title, photo.path);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 提取共用的上传与关联证件逻辑
|
|
||||||
void _uploadAndSaveCertificate(String title, String filePath) async {
|
|
||||||
// 上传文件
|
|
||||||
String? imageUrl = await uploadFile(filePath);
|
|
||||||
if (imageUrl == null) return;
|
|
||||||
|
|
||||||
// 根据标题映射业务类型
|
|
||||||
String type = "";
|
|
||||||
switch (title) {
|
|
||||||
case "行驶证":
|
|
||||||
type = "9";
|
|
||||||
break;
|
|
||||||
case "营运证":
|
|
||||||
type = "10";
|
|
||||||
break;
|
|
||||||
case "加氢证":
|
|
||||||
type = "12";
|
|
||||||
break;
|
|
||||||
case "登记证":
|
|
||||||
type = "13";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 调用后台接口关联证件
|
|
||||||
var response = await HttpService.to.post(
|
|
||||||
"appointment/truck/uploadCertificatePic",
|
|
||||||
data: {
|
|
||||||
"plateNumber": plateNumber,
|
|
||||||
"imageUrl": imageUrl,
|
|
||||||
"type": type,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response != null) {
|
|
||||||
final result = BaseModel.fromJson(response.data);
|
|
||||||
if (result.code == 0) {
|
|
||||||
showSuccessToast("上传成功");
|
|
||||||
getUserBindCarInfo(); // 重新拉取数据更新UI
|
|
||||||
} else {
|
|
||||||
showErrorToast(result.error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 上传图片
|
|
||||||
Future<String?> uploadFile(String filePath) async {
|
|
||||||
showLoading("正在上传...");
|
|
||||||
try {
|
|
||||||
dio.FormData formData = dio.FormData.fromMap({
|
|
||||||
'file': await dio.MultipartFile.fromFile(filePath, filename: 'ocr_identity.jpg'),
|
|
||||||
});
|
|
||||||
|
|
||||||
var response = await HttpService.to.post("appointment/ocr/upload", data: formData);
|
|
||||||
if (response != null) {
|
|
||||||
final result = BaseModel.fromJson(response.data);
|
|
||||||
if (result.code == 0) return result.data.toString();
|
|
||||||
showErrorToast(result.error);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
showErrorToast("图片上传失败");
|
|
||||||
} finally {
|
|
||||||
dismissLoading();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
void getUserBindCarInfo() async {
|
void getUserBindCarInfo() async {
|
||||||
if (StorageService.to.hasVehicleInfo) {
|
if (StorageService.to.hasVehicleInfo) {
|
||||||
VehicleInfo? bean = StorageService.to.vehicleInfo;
|
VehicleInfo? bean = StorageService.to.vehicleInfo;
|
||||||
@@ -212,7 +102,7 @@ class CarInfoController extends GetxController with BaseControllerMixin {
|
|||||||
|
|
||||||
// 获取证件信息
|
// 获取证件信息
|
||||||
final response = await HttpService.to.get(
|
final response = await HttpService.to.get(
|
||||||
'appointment/vehicle/getPicInfoByVin?vin=$vin&plateNumber=$plateNumber',
|
'appointment/vehicle/getPicInfoByVin?vin=$vin',
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response != null && response.data != null) {
|
if (response != null && response.data != null) {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_pdfview/flutter_pdfview.dart';
|
import 'package:flutter_pdfview/flutter_pdfview.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:getx_scaffold/getx_scaffold.dart';
|
import 'package:getx_scaffold/getx_scaffold.dart';
|
||||||
import 'package:image_picker/image_picker.dart';
|
|
||||||
import 'package:ln_jq_app/common/login_util.dart';
|
import 'package:ln_jq_app/common/login_util.dart';
|
||||||
import 'package:ln_jq_app/pages/c_page/message/view.dart';
|
import 'package:ln_jq_app/pages/c_page/message/view.dart';
|
||||||
import 'package:ln_jq_app/storage_service.dart';
|
import 'package:ln_jq_app/storage_service.dart';
|
||||||
@@ -134,7 +133,7 @@ class CarInfoPage extends GetView<CarInfoController> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () async {
|
onPressed: () async{
|
||||||
var scanResult = await Get.to(() => const MessagePage());
|
var scanResult = await Get.to(() => const MessagePage());
|
||||||
if (scanResult == null) {
|
if (scanResult == null) {
|
||||||
controller.msgNotice();
|
controller.msgNotice();
|
||||||
@@ -164,26 +163,11 @@ class CarInfoPage extends GetView<CarInfoController> {
|
|||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
_buildModernStatItem(
|
_buildModernStatItem('本月里程数', 'Accumulated', '2,852km', ''),
|
||||||
'本月里程数',
|
|
||||||
'Accumulated',
|
|
||||||
StorageService.to.hasVehicleInfo ? '2,852km' : '-',
|
|
||||||
'',
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
_buildModernStatItem(
|
_buildModernStatItem('总里程', 'Refuel Count', "2.5W km", ''),
|
||||||
'总里程',
|
|
||||||
'Refuel Count',
|
|
||||||
StorageService.to.hasVehicleInfo ? "2.5W km" : '-',
|
|
||||||
'',
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
_buildModernStatItem(
|
_buildModernStatItem('服务评分', 'Driver rating', "4.9分", ''),
|
||||||
'服务评分',
|
|
||||||
'Driver rating',
|
|
||||||
StorageService.to.hasVehicleInfo ? "4.9分" : '-',
|
|
||||||
'',
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -316,20 +300,20 @@ class CarInfoPage extends GetView<CarInfoController> {
|
|||||||
children: [
|
children: [
|
||||||
ClipRRect(
|
ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(4),
|
||||||
child: LinearProgressIndicator(
|
child: const LinearProgressIndicator(
|
||||||
value: StorageService.to.hasVehicleInfo ? 0.75 : 0,
|
value: 0.75,
|
||||||
minHeight: 8,
|
minHeight: 8,
|
||||||
backgroundColor: Color(0xFFF0F2F5),
|
backgroundColor: Color(0xFFF0F2F5),
|
||||||
valueColor: AlwaysStoppedAnimation<Color>(Color.fromRGBO(16, 185, 129, 1)),
|
valueColor: AlwaysStoppedAnimation<Color>(Color.fromRGBO(16, 185, 129, 1)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Row(
|
const Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text("H2 Level", style: TextStyle(fontSize: 11, color: Colors.grey)),
|
Text("H2 Level", style: TextStyle(fontSize: 11, color: Colors.grey)),
|
||||||
Text(
|
Text(
|
||||||
StorageService.to.hasVehicleInfo ? "75%" : "0%",
|
"75%",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
color: Color.fromRGBO(16, 185, 129, 1),
|
color: Color.fromRGBO(16, 185, 129, 1),
|
||||||
@@ -369,7 +353,7 @@ class CarInfoPage extends GetView<CarInfoController> {
|
|||||||
children: [
|
children: [
|
||||||
_buildCertificateContent('行驶证', controller.drivingAttachments),
|
_buildCertificateContent('行驶证', controller.drivingAttachments),
|
||||||
_buildCertificateContent('营运证', controller.operationAttachments),
|
_buildCertificateContent('营运证', controller.operationAttachments),
|
||||||
_buildCertificateContent('加氢证', controller.hydrogenationAttachments),
|
_buildCertificateContent('加氢资格证', controller.hydrogenationAttachments),
|
||||||
_buildCertificateContent('登记证', controller.registerAttachments),
|
_buildCertificateContent('登记证', controller.registerAttachments),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -389,7 +373,7 @@ class CarInfoPage extends GetView<CarInfoController> {
|
|||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.all(16.0),
|
padding: EdgeInsets.all(16.0),
|
||||||
child: attachments.isEmpty
|
child: attachments.isEmpty
|
||||||
? _buildEmptyCertificateState(title)
|
? const Center(child: Text('暂无相关证件信息'))
|
||||||
: Column(
|
: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
@@ -398,11 +382,7 @@ class CarInfoPage extends GetView<CarInfoController> {
|
|||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
_buildCertDetailItem(
|
_buildCertDetailItem('所属公司', controller.rentFromCompany, isFull: false),
|
||||||
'所属公司',
|
|
||||||
controller.rentFromCompany,
|
|
||||||
isFull: false,
|
|
||||||
),
|
|
||||||
_buildCertDetailItem('运营城市', controller.address),
|
_buildCertDetailItem('运营城市', controller.address),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -441,158 +421,6 @@ class CarInfoPage extends GetView<CarInfoController> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildEmptyCertificateState(String title) {
|
|
||||||
return Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Image.asset(
|
|
||||||
'assets/images/ic_attention@2x.png', // 请替换为您的实际图片路径
|
|
||||||
width: 120,
|
|
||||||
height: 120,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Text(
|
|
||||||
'您未上传“$title”',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16.sp,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
color: Color.fromRGBO(51, 51, 51, 1),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Text(
|
|
||||||
'上传后可提前通知加氢站报备\n大幅减少加氢站等待时间',
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12.sp,
|
|
||||||
color: Color.fromRGBO(156, 163, 175, 1),
|
|
||||||
height: 1.5,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 24),
|
|
||||||
SizedBox(
|
|
||||||
width: 200,
|
|
||||||
height: 44,
|
|
||||||
child: ElevatedButton.icon(
|
|
||||||
onPressed: () {
|
|
||||||
_showUploadDialog(title);
|
|
||||||
},
|
|
||||||
icon: const Text(
|
|
||||||
'立即上传',
|
|
||||||
style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
label: Image.asset(
|
|
||||||
'assets/images/ic_upload@2x.png',
|
|
||||||
height: 20.h,
|
|
||||||
width: 20.w,
|
|
||||||
),
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: const Color(0xFF017137),
|
|
||||||
foregroundColor: Colors.white,
|
|
||||||
shape: StadiumBorder(),
|
|
||||||
elevation: 0,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _showUploadDialog(String title) {
|
|
||||||
Get.dialog(
|
|
||||||
Dialog(
|
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.all(24),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'上传$title',
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Colors.black,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
Text(
|
|
||||||
'请确保拍摄证件清晰可见,关键文字无反光遮挡,这将有助于快速通过审核',
|
|
||||||
style: TextStyle(fontSize: 13, color: Colors.grey[400], height: 1.5),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 24),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: _buildUploadOption(
|
|
||||||
icon: Icons.camera_alt_outlined,
|
|
||||||
label: '拍照上传',
|
|
||||||
onTap: () {
|
|
||||||
controller.pickImage(title, ImageSource.camera);
|
|
||||||
Get.back();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 16),
|
|
||||||
Expanded(
|
|
||||||
child: _buildUploadOption(
|
|
||||||
icon: Icons.image_outlined,
|
|
||||||
label: '相册上传',
|
|
||||||
onTap: () {
|
|
||||||
controller.pickImage(title, ImageSource.gallery);
|
|
||||||
Get.back();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 构建弹窗内的选择按钮
|
|
||||||
Widget _buildUploadOption({
|
|
||||||
required IconData icon,
|
|
||||||
required String label,
|
|
||||||
required VoidCallback onTap,
|
|
||||||
}) {
|
|
||||||
return GestureDetector(
|
|
||||||
onTap: onTap,
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 24),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: const Color(0xFFF2F9F7), // 浅绿色背景
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.all(12),
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
color: Color(0xFF017137), // 深绿色圆圈
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
),
|
|
||||||
child: Icon(icon, color: Colors.white, size: 28),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
Text(
|
|
||||||
label,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
color: Color(0xFF333333),
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildCertDetailItem(
|
Widget _buildCertDetailItem(
|
||||||
String label,
|
String label,
|
||||||
String value, {
|
String value, {
|
||||||
|
|||||||
@@ -1,113 +0,0 @@
|
|||||||
import 'dart:developer';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:getx_scaffold/getx_scaffold.dart';
|
|
||||||
import 'package:ln_jq_app/common/model/base_model.dart';
|
|
||||||
import 'package:ln_jq_app/pages/c_page/mall/exchange_success/view.dart';
|
|
||||||
|
|
||||||
import '../mall_controller.dart';
|
|
||||||
|
|
||||||
class MallDetailController extends GetxController with BaseControllerMixin {
|
|
||||||
@override
|
|
||||||
String get builderId => 'mall_detail';
|
|
||||||
|
|
||||||
late final int goodsId;
|
|
||||||
final Rx<GoodsModel?> goodsDetail = Rx<GoodsModel?>(null);
|
|
||||||
|
|
||||||
final addressController = TextEditingController();
|
|
||||||
final nameController = TextEditingController();
|
|
||||||
final phoneController = TextEditingController();
|
|
||||||
|
|
||||||
final formKey = GlobalKey<FormState>();
|
|
||||||
|
|
||||||
@override
|
|
||||||
void onInit() {
|
|
||||||
super.onInit();
|
|
||||||
goodsId = Get.arguments['goodsId'] as int;
|
|
||||||
getGoodsDetail();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool get listenLifecycleEvent => true;
|
|
||||||
|
|
||||||
Future<void> getGoodsDetail() async {
|
|
||||||
try {
|
|
||||||
var response = await HttpService.to.post(
|
|
||||||
'appointment/score/getScoreGoodsDetail',
|
|
||||||
data: {'goodsId': goodsId},
|
|
||||||
);
|
|
||||||
if (response != null && response.data != null) {
|
|
||||||
var result = BaseModel<GoodsModel>.fromJson(
|
|
||||||
response.data,
|
|
||||||
dataBuilder: (dataJson) => GoodsModel.fromJson(dataJson),
|
|
||||||
);
|
|
||||||
if (result.code == 0 && result.data != null) {
|
|
||||||
goodsDetail.value = result.data;
|
|
||||||
} else {
|
|
||||||
showErrorToast('加载失败: ${result.message}');
|
|
||||||
Get.back();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
log('获取商品详情失败: $e');
|
|
||||||
showErrorToast('网络异常,请稍后重试');
|
|
||||||
Get.back();
|
|
||||||
} finally {
|
|
||||||
updateUi();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 兑换商品
|
|
||||||
void exchange() async {
|
|
||||||
if (!formKey.currentState!.validate()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
final mallController = Get.find<MallController>();
|
|
||||||
if (mallController.userScore.value < (goodsDetail.value?.score ?? 0)) {
|
|
||||||
showWarningToast('积分不足');
|
|
||||||
return;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
// 接口调用预留
|
|
||||||
showLoading('兑换中...');
|
|
||||||
|
|
||||||
final goods = goodsDetail.value;
|
|
||||||
if (goods == null) {
|
|
||||||
showErrorToast('兑换失败,请稍后重试');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
var response = await HttpService.to.post(
|
|
||||||
'appointment/score/scoreExchange',
|
|
||||||
data: {
|
|
||||||
"goodsId": goods.id,
|
|
||||||
"address": addressController.text,
|
|
||||||
"name": nameController.text,
|
|
||||||
"phone": phoneController.text,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
if (response != null && response.data != null) {
|
|
||||||
var result = BaseModel.fromJson(response.data);
|
|
||||||
if (result.code == 0) {
|
|
||||||
Get.off(() => MallExchangeSuccessPage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
log('兑换失败: $e');
|
|
||||||
showErrorToast('兑换失败,请稍后重试');
|
|
||||||
} finally {
|
|
||||||
dismissLoading();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void onClose() {
|
|
||||||
addressController.dispose();
|
|
||||||
nameController.dispose();
|
|
||||||
phoneController.dispose();
|
|
||||||
super.onClose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,281 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:getx_scaffold/getx_scaffold.dart';
|
|
||||||
import 'controller.dart';
|
|
||||||
|
|
||||||
class MallDetailPage extends GetView<MallDetailController> {
|
|
||||||
const MallDetailPage({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return GetBuilder<MallDetailController>(
|
|
||||||
init: MallDetailController(),
|
|
||||||
id: 'mall_detail',
|
|
||||||
builder: (_) {
|
|
||||||
return Scaffold(
|
|
||||||
backgroundColor: const Color(0xFFF7F8FA),
|
|
||||||
appBar: AppBar(
|
|
||||||
title: const Text('商品兑换'),
|
|
||||||
backgroundColor: Colors.white,
|
|
||||||
foregroundColor: Colors.black,
|
|
||||||
elevation: 0,
|
|
||||||
leading: IconButton(
|
|
||||||
icon: const Icon(Icons.arrow_back_ios, size: 20),
|
|
||||||
onPressed: () => Get.back(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
body: GestureDetector(
|
|
||||||
onTap: () {
|
|
||||||
hideKeyboard();
|
|
||||||
},
|
|
||||||
child: _buildBody(),
|
|
||||||
),
|
|
||||||
bottomNavigationBar: _buildBottomButton(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildBody() {
|
|
||||||
final goods = controller.goodsDetail.value;
|
|
||||||
if (goods == null) return const Center(child: Text('商品信息不存在'));
|
|
||||||
|
|
||||||
return SingleChildScrollView(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: Form(
|
|
||||||
key: controller.formKey,
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
_buildGoodsInfoCard(goods),
|
|
||||||
const SizedBox(height: 24),
|
|
||||||
_buildSectionTitle('填写收货信息'),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
_buildInputLabel('详细地址'),
|
|
||||||
_buildTextField(
|
|
||||||
controller: controller.addressController,
|
|
||||||
hint: '请输入完整的收货地址',
|
|
||||||
icon: Icons.location_on_outlined,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
_buildInputLabel('收货人姓名'),
|
|
||||||
_buildTextField(
|
|
||||||
controller: controller.nameController,
|
|
||||||
hint: '请输入收货人姓名',
|
|
||||||
icon: Icons.person_outline,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
_buildInputLabel('联系电话'),
|
|
||||||
_buildTextField(
|
|
||||||
controller: controller.phoneController,
|
|
||||||
hint: '请输入手机号码',
|
|
||||||
icon: Icons.phone_android_outlined,
|
|
||||||
keyboardType: TextInputType.phone,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 40),
|
|
||||||
Center(
|
|
||||||
child: Text(
|
|
||||||
'兑换成功后,商品会在3个工作日内邮寄\n请注意查收',
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(
|
|
||||||
color: Color(0xFF999999),
|
|
||||||
fontSize: 12.sp,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildGoodsInfoCard(goods) {
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.all(17),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: Colors.black.withOpacity(0.03),
|
|
||||||
blurRadius: 10,
|
|
||||||
offset: const Offset(0, 4),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
ClipRRect(
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
child: goods.goodsImage != null
|
|
||||||
? Image.network(
|
|
||||||
goods.goodsImage!,
|
|
||||||
width: 94.w,
|
|
||||||
height: 94.h,
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
)
|
|
||||||
: Container(
|
|
||||||
width: 80,
|
|
||||||
height: 80,
|
|
||||||
color: Colors.grey[200],
|
|
||||||
child: const Icon(Icons.image, color: Colors.grey),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
goods.goodsName,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16.sp,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
color: Color(0xFF333333),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(height: 8.h),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'${goods.score}',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 20.sp,
|
|
||||||
color: Color(0xFF4CAF50),
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 4),
|
|
||||||
Text(
|
|
||||||
'积分',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 10.sp,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
color: Color(0xFF999999),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
SizedBox(height: 10.h),
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: const Color(0xFFF2F3F5),
|
|
||||||
borderRadius: BorderRadius.circular(4),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
'数量: 1',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 10.sp,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
color: Color(0xFF666666),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildSectionTitle(String title) {
|
|
||||||
return Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
width: 6.w,
|
|
||||||
height: 16.h,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: const Color(0xFF4CAF50),
|
|
||||||
borderRadius: BorderRadius.circular(2),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Text(
|
|
||||||
title,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14.sp,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
color: Color.fromRGBO(148, 163, 184, 1),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildInputLabel(String label) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.only(bottom: 8),
|
|
||||||
child: Text(
|
|
||||||
label,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12.sp,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
color: Color.fromRGBO(100, 116, 139, 1),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildTextField({
|
|
||||||
required TextEditingController controller,
|
|
||||||
required String hint,
|
|
||||||
required IconData icon,
|
|
||||||
TextInputType? keyboardType,
|
|
||||||
}) {
|
|
||||||
return Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
child: TextFormField(
|
|
||||||
controller: controller,
|
|
||||||
keyboardType: keyboardType,
|
|
||||||
textAlign: TextAlign.start,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
hintText: hint,
|
|
||||||
hintStyle: TextStyle(
|
|
||||||
color: Color.fromRGBO(134, 144, 156, 1),
|
|
||||||
fontSize: 14.sp,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
prefixIcon: Icon(icon, color: const Color(0xFF999999), size: 20),
|
|
||||||
border: InputBorder.none,
|
|
||||||
contentPadding: const EdgeInsets.symmetric(vertical: 12),
|
|
||||||
),
|
|
||||||
validator: (value) {
|
|
||||||
if (value == null || value.isEmpty) {
|
|
||||||
return '内容不能为空';
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildBottomButton() {
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.fromLTRB(16, 10, 16, 30),
|
|
||||||
child: ElevatedButton(
|
|
||||||
onPressed: controller.exchange,
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: const Color(0xFF007A45),
|
|
||||||
minimumSize: const Size(double.infinity, 50),
|
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(25)),
|
|
||||||
elevation: 0,
|
|
||||||
),
|
|
||||||
child: const Text(
|
|
||||||
'兑换商品',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:getx_scaffold/common/index.dart';
|
|
||||||
import 'package:ln_jq_app/common/login_util.dart';
|
|
||||||
|
|
||||||
class MallExchangeSuccessPage extends StatelessWidget {
|
|
||||||
const MallExchangeSuccessPage({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
backgroundColor: Colors.white,
|
|
||||||
appBar: AppBar(
|
|
||||||
backgroundColor: Colors.white,
|
|
||||||
elevation: 0,
|
|
||||||
leading: IconButton(
|
|
||||||
icon: const Icon(Icons.arrow_back_ios, size: 20, color: Colors.black),
|
|
||||||
onPressed: () => Get.back(), // 返回首页
|
|
||||||
),
|
|
||||||
title: const Text('商品兑换', style: TextStyle(color: Colors.black, fontSize: 18)),
|
|
||||||
),
|
|
||||||
body: Center(
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
SizedBox(height: 114.h),
|
|
||||||
_buildSuccessIcon(),
|
|
||||||
const SizedBox(height: 24),
|
|
||||||
Text(
|
|
||||||
'兑换成功',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 24.sp,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
color: Color(0xFF333333),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Text(
|
|
||||||
'预计 3 日内发货\n请留意查收',
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(fontSize: 14.sp, color: Color(0xFF999999)),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 60),
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: () => Get.back(),
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: const Color(0xFF007A45),
|
|
||||||
minimumSize: const Size(140, 50),
|
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(25)),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
'返回首页',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
fontSize: 16.sp,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildSuccessIcon() {
|
|
||||||
return Container(child: LoginUtil.getAssImg("mall_pay_success@2x"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,160 +0,0 @@
|
|||||||
|
|
||||||
import 'package:getx_scaffold/getx_scaffold.dart';
|
|
||||||
import 'package:ln_jq_app/common/model/base_model.dart';
|
|
||||||
import 'package:ln_jq_app/pages/c_page/mall/detail/view.dart';
|
|
||||||
import 'package:ln_jq_app/pages/c_page/mall/orders/view.dart';
|
|
||||||
import 'package:ln_jq_app/pages/c_page/mall/rule/view.dart';
|
|
||||||
|
|
||||||
class GoodsModel {
|
|
||||||
final int id;
|
|
||||||
final String categoryId;
|
|
||||||
final String goodsName;
|
|
||||||
final String? goodsImage;
|
|
||||||
final int originalScore;
|
|
||||||
final int score;
|
|
||||||
final int stock;
|
|
||||||
final int status;
|
|
||||||
|
|
||||||
GoodsModel({
|
|
||||||
required this.id,
|
|
||||||
required this.categoryId,
|
|
||||||
required this.goodsName,
|
|
||||||
this.goodsImage,
|
|
||||||
required this.originalScore,
|
|
||||||
required this.score,
|
|
||||||
required this.stock,
|
|
||||||
required this.status,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory GoodsModel.fromJson(Map<String, dynamic> json) {
|
|
||||||
return GoodsModel(
|
|
||||||
id: json['id'] as int,
|
|
||||||
categoryId: json['categoryId']?.toString() ?? '',
|
|
||||||
goodsName: json['goodsName']?.toString() ?? '',
|
|
||||||
goodsImage: json['goodsImage'],
|
|
||||||
originalScore: json['originalScore'] as int? ?? 0,
|
|
||||||
score: json['score'] as int? ?? 0,
|
|
||||||
stock: json['stock'] as int? ?? 0,
|
|
||||||
status: json['status'] as int? ?? 1,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class UserScore {
|
|
||||||
final int score;
|
|
||||||
final int todaySign;
|
|
||||||
|
|
||||||
UserScore({required this.score, required this.todaySign});
|
|
||||||
|
|
||||||
factory UserScore.fromJson(Map<String, dynamic> json) {
|
|
||||||
return UserScore(
|
|
||||||
score: json['score'] as int? ?? 0,
|
|
||||||
todaySign: json['todaySign'] as int? ?? 1,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MallController extends GetxController with BaseControllerMixin {
|
|
||||||
@override
|
|
||||||
String get builderId => 'mall';
|
|
||||||
|
|
||||||
final RxInt userScore = 0.obs;
|
|
||||||
final RxInt todaySign = 1.obs; // 0可签到,1已签到
|
|
||||||
final RxList<GoodsModel> goodsList = <GoodsModel>[].obs;
|
|
||||||
final RxBool isLoading = true.obs;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void onInit() {
|
|
||||||
super.onInit();
|
|
||||||
refreshData();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> refreshData() async {
|
|
||||||
isLoading.value = true;
|
|
||||||
await Future.wait([getUserScore(), getGoodsList()]);
|
|
||||||
isLoading.value = false;
|
|
||||||
updateUi();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 获取用户积分和签到状态
|
|
||||||
Future<void> getUserScore() async {
|
|
||||||
try {
|
|
||||||
var response = await HttpService.to.post('appointment/score/getUserScore');
|
|
||||||
if (response != null && response.data != null) {
|
|
||||||
var result = BaseModel<UserScore>.fromJson(
|
|
||||||
response.data,
|
|
||||||
dataBuilder: (dataJson) => UserScore.fromJson(dataJson),
|
|
||||||
);
|
|
||||||
if (result.code == 0 && result.data != null) {
|
|
||||||
userScore.value = result.data!.score;
|
|
||||||
todaySign.value = result.data!.todaySign;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
log('获取积分失败: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 签到逻辑
|
|
||||||
Future<void> signAction() async {
|
|
||||||
if (todaySign.value == 1) return;
|
|
||||||
|
|
||||||
showLoading('签到中...');
|
|
||||||
try {
|
|
||||||
var response = await HttpService.to.post('appointment/score/sign');
|
|
||||||
dismissLoading();
|
|
||||||
if (response != null && response.data != null) {
|
|
||||||
var result = BaseModel.fromJson(response.data);
|
|
||||||
if (result.code == 0) {
|
|
||||||
showSuccessToast('签到成功');
|
|
||||||
getUserScore(); // 签到成功后刷新积分
|
|
||||||
} else {
|
|
||||||
showErrorToast(result.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
dismissLoading();
|
|
||||||
showErrorToast('签到异常');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 获取商品列表
|
|
||||||
Future<void> getGoodsList() async {
|
|
||||||
try {
|
|
||||||
var response = await HttpService.to.post(
|
|
||||||
'appointment/score/getScoreGoodsList',
|
|
||||||
data: {'categoryId': 0},
|
|
||||||
);
|
|
||||||
if (response != null && response.data != null) {
|
|
||||||
var result = BaseModel<List<GoodsModel>>.fromJson(
|
|
||||||
response.data,
|
|
||||||
dataBuilder: (dataJson) {
|
|
||||||
var list = dataJson as List;
|
|
||||||
return list.map((e) => GoodsModel.fromJson(e)).toList();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
if (result.code == 0 && result.data != null) {
|
|
||||||
goodsList.assignAll(result.data!);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
log('获取商品列表失败: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 兑换商品
|
|
||||||
void exchangeGoods(GoodsModel goods) {
|
|
||||||
Get.to(() => const MallDetailPage(), arguments: {'goodsId': goods.id})
|
|
||||||
?.then((_) => refreshData());
|
|
||||||
}
|
|
||||||
|
|
||||||
///规则说明
|
|
||||||
void toRuleDes() {
|
|
||||||
Get.to(() => const MallRulePage());
|
|
||||||
}
|
|
||||||
|
|
||||||
///历史订单
|
|
||||||
void toOrders() {
|
|
||||||
Get.to(() => const MallOrdersPage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,345 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:getx_scaffold/getx_scaffold.dart';
|
|
||||||
import 'package:ln_jq_app/common/login_util.dart';
|
|
||||||
|
|
||||||
import 'mall_controller.dart';
|
|
||||||
|
|
||||||
class MallPage extends GetView<MallController> {
|
|
||||||
const MallPage({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return GetX<MallController>(
|
|
||||||
init: MallController(),
|
|
||||||
builder: (_) {
|
|
||||||
return Scaffold(
|
|
||||||
backgroundColor: Color.fromRGBO(247, 249, 251, 1),
|
|
||||||
body: RefreshIndicator(
|
|
||||||
onRefresh: controller.refreshData,
|
|
||||||
child: CustomScrollView(
|
|
||||||
slivers: [
|
|
||||||
SliverToBoxAdapter(
|
|
||||||
child: Container(
|
|
||||||
padding: EdgeInsets.only(left: 20.w, right: 20.w, bottom: 20.h),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: BorderRadius.only(
|
|
||||||
bottomLeft: Radius.circular(20),
|
|
||||||
bottomRight: Radius.circular(20),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Column(children: [_buildAppBar(), _buildScoreCard()]),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
_buildSectionHeader(),
|
|
||||||
_buildGoodsGrid(),
|
|
||||||
SliverToBoxAdapter(child: SizedBox(height: 120.h)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildAppBar() {
|
|
||||||
return Container(
|
|
||||||
padding: EdgeInsets.only(top: MediaQuery.of(Get.context!).padding.top, bottom: 0),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
width: 40,
|
|
||||||
height: 40,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: const Color(0xFF4CAF50),
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
child: LoginUtil.getAssImg("mall_bar@2x"),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const Text(
|
|
||||||
'氢能商城',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Color(0xFF333333),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
const Text(
|
|
||||||
'Hydrogen Energy Mall',
|
|
||||||
style: TextStyle(fontSize: 12, color: Color(0xFF999999)),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
/*IconButton(
|
|
||||||
onPressed: () {},
|
|
||||||
icon: const Icon(Icons.notifications_none, color: Color(0xFF333333)),
|
|
||||||
),*/
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildScoreCard() {
|
|
||||||
return Container(
|
|
||||||
margin: EdgeInsets.only(top: 22.h),
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
gradient: const LinearGradient(
|
|
||||||
colors: [Color.fromRGBO(2, 27, 31, 1), Color.fromRGBO(11, 67, 67, 1)],
|
|
||||||
begin: Alignment.topLeft,
|
|
||||||
end: Alignment.bottomRight,
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'我的可用积分',
|
|
||||||
style: TextStyle(color: Colors.white70, fontSize: 14.sp),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 4),
|
|
||||||
GestureDetector(
|
|
||||||
onTap: (){
|
|
||||||
controller.toRuleDes();
|
|
||||||
},
|
|
||||||
child: const Icon(Icons.help_outline, color: Colors.white70, size: 14),
|
|
||||||
)
|
|
||||||
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
'Available points',
|
|
||||||
style: TextStyle(color: Colors.white38, fontSize: 11.sp),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
GestureDetector(
|
|
||||||
onTap: controller.signAction,
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: controller.todaySign.value == 0
|
|
||||||
? Color.fromRGBO(78, 184, 49, 1)
|
|
||||||
: Colors.grey,
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
controller.todaySign.value == 0 ? '立即签到' : '已签到',
|
|
||||||
style: const TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
controller.userScore.value.toString(),
|
|
||||||
style: TextStyle(
|
|
||||||
color: Color.fromRGBO(236, 236, 236, 1),
|
|
||||||
fontSize: 18.sp,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
controller.toOrders();
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
'历史订单',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Color.fromRGBO(148, 163, 184, 1),
|
|
||||||
fontSize: 12.sp,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildSectionHeader() {
|
|
||||||
return SliverToBoxAdapter(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
width: 4.w,
|
|
||||||
height: 16.h,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: const Color(0xFF4CAF50),
|
|
||||||
borderRadius: BorderRadius.circular(2),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Text(
|
|
||||||
'热门商品',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14.sp,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
color: Color.fromRGBO(78, 89, 105, 1),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildGoodsGrid() {
|
|
||||||
if (controller.goodsList.isEmpty) {
|
|
||||||
return const SliverToBoxAdapter(
|
|
||||||
child: Center(
|
|
||||||
child: Padding(
|
|
||||||
padding: EdgeInsets.only(top: 50),
|
|
||||||
child: Text('暂无商品', style: TextStyle(color: Color(0xFF999999))),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return SliverPadding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
||||||
sliver: SliverGrid(
|
|
||||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
|
||||||
crossAxisCount: 2,
|
|
||||||
mainAxisSpacing: 14.h,
|
|
||||||
crossAxisSpacing: 19.w,
|
|
||||||
childAspectRatio: 0.75,
|
|
||||||
),
|
|
||||||
delegate: SliverChildBuilderDelegate((context, index) {
|
|
||||||
final goods = controller.goodsList[index];
|
|
||||||
return _buildGoodsItem(goods);
|
|
||||||
}, childCount: controller.goodsList.length),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildGoodsItem(GoodsModel goods) {
|
|
||||||
return Container(
|
|
||||||
padding: EdgeInsets.all(8),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: Colors.black.withOpacity(0.05),
|
|
||||||
blurRadius: 10,
|
|
||||||
offset: const Offset(0, 4),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: ClipRRect(
|
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
|
||||||
child: goods.goodsImage != null
|
|
||||||
? Image.network(
|
|
||||||
goods.goodsImage!,
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
width: double.infinity,
|
|
||||||
)
|
|
||||||
: Container(
|
|
||||||
color: const Color(0xFFEEEEEE),
|
|
||||||
child: const Center(
|
|
||||||
child: Icon(Icons.image, color: Colors.grey, size: 40),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(10),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
goods.goodsName,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14.sp,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
color: Colors.black,
|
|
||||||
),
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'${goods.score}',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16.sp,
|
|
||||||
color: Color(0xFF4CAF50),
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 4),
|
|
||||||
Text(
|
|
||||||
'积分',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12.sp,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Color(0xFF4CAF50),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
GestureDetector(
|
|
||||||
onTap: () => controller.exchangeGoods(goods),
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: const Color(0xFFE8F5E9),
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
'兑换',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Color(0xFF4CAF50),
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 13.sp,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
|
|
||||||
import 'dart:developer';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:getx_scaffold/getx_scaffold.dart';
|
|
||||||
import 'package:ln_jq_app/common/model/base_model.dart';
|
|
||||||
|
|
||||||
class OrderModel {
|
|
||||||
final int id;
|
|
||||||
final int scoreGoodsId;
|
|
||||||
final String goodsName;
|
|
||||||
final String? goodsImage;
|
|
||||||
final String? goodsContent;
|
|
||||||
final String address;
|
|
||||||
final String createTime;
|
|
||||||
final String score;
|
|
||||||
|
|
||||||
OrderModel({
|
|
||||||
required this.id,
|
|
||||||
required this.scoreGoodsId,
|
|
||||||
required this.goodsName,
|
|
||||||
this.goodsImage,
|
|
||||||
this.goodsContent,
|
|
||||||
required this.address,
|
|
||||||
required this.createTime,
|
|
||||||
required this.score,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory OrderModel.fromJson(Map<String, dynamic> json) {
|
|
||||||
return OrderModel(
|
|
||||||
id: json['id'] as int,
|
|
||||||
scoreGoodsId: json['scoreGoodsId'] as int,
|
|
||||||
goodsName: json['goodsName']?.toString() ?? '',
|
|
||||||
goodsImage: json['goodsImage'],
|
|
||||||
goodsContent: json['goodsContent'],
|
|
||||||
address: json['address']?.toString() ?? '',
|
|
||||||
createTime: json['createTime']?.toString() ?? '',
|
|
||||||
score: json['score']?.toString() ?? '',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MallOrdersController extends GetxController with BaseControllerMixin {
|
|
||||||
@override
|
|
||||||
String get builderId => 'mall_orders';
|
|
||||||
|
|
||||||
final RxList<OrderModel> orderList = <OrderModel>[].obs;
|
|
||||||
final RxBool isLoading = true.obs;
|
|
||||||
int pageNum = 1;
|
|
||||||
final int pageSize = 50;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void onInit() {
|
|
||||||
super.onInit();
|
|
||||||
getOrders();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> getOrders({bool isRefresh = true}) async {
|
|
||||||
if (isRefresh) {
|
|
||||||
pageNum = 1;
|
|
||||||
isLoading.value = true;
|
|
||||||
updateUi();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
var response = await HttpService.to.post(
|
|
||||||
'appointment/score/getScoreExchangeList',
|
|
||||||
data: {
|
|
||||||
"status": "",
|
|
||||||
"pageNum": pageNum.toString(),
|
|
||||||
"pageSize": pageSize.toString()
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response != null && response.data != null) {
|
|
||||||
var result = BaseModel<Map<String, dynamic>>.fromJson(response.data);
|
|
||||||
if (result.code == 0 && result.data != null) {
|
|
||||||
var records = result.data!['records'] as List;
|
|
||||||
var list = records.map((e) => OrderModel.fromJson(e)).toList();
|
|
||||||
if (isRefresh) {
|
|
||||||
orderList.assignAll(list);
|
|
||||||
} else {
|
|
||||||
orderList.addAll(list);
|
|
||||||
}
|
|
||||||
pageNum++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
log('获取订单列表失败: $e');
|
|
||||||
} finally {
|
|
||||||
isLoading.value = false;
|
|
||||||
updateUi();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:getx_scaffold/getx_scaffold.dart';
|
|
||||||
import 'controller.dart';
|
|
||||||
|
|
||||||
class MallOrdersPage extends GetView<MallOrdersController> {
|
|
||||||
const MallOrdersPage({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return GetBuilder<MallOrdersController>(
|
|
||||||
init: MallOrdersController(),
|
|
||||||
id: 'mall_orders',
|
|
||||||
builder: (_) {
|
|
||||||
return Scaffold(
|
|
||||||
backgroundColor: const Color(0xFFF7F8FA),
|
|
||||||
appBar: AppBar(
|
|
||||||
title: const Text('历史订单'),
|
|
||||||
backgroundColor: Colors.white,
|
|
||||||
foregroundColor: Colors.black,
|
|
||||||
elevation: 0,
|
|
||||||
leading: IconButton(
|
|
||||||
icon: const Icon(Icons.arrow_back_ios, size: 20),
|
|
||||||
onPressed: () => Get.back(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
body: RefreshIndicator(
|
|
||||||
onRefresh: () => controller.getOrders(isRefresh: true),
|
|
||||||
child: controller.isLoading.value && controller.orderList.isEmpty
|
|
||||||
? const Center(child: CircularProgressIndicator())
|
|
||||||
: _buildOrderList(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildOrderList() {
|
|
||||||
if (controller.orderList.isEmpty) {
|
|
||||||
return const Center(
|
|
||||||
child: Text('暂无订单记录', style: TextStyle(color: Color(0xFF999999))),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ListView.builder(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
itemCount: controller.orderList.length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
final order = controller.orderList[index];
|
|
||||||
return _buildOrderItem(order);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildOrderItem(OrderModel order) {
|
|
||||||
return Container(
|
|
||||||
margin: const EdgeInsets.only(bottom: 12),
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'兑换时间:${order.createTime}',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12.sp,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
color: Color.fromRGBO(107, 114, 128, 1),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
ClipRRect(
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
child: order.goodsImage != null
|
|
||||||
? Image.network(
|
|
||||||
order.goodsImage!,
|
|
||||||
width: 80.w,
|
|
||||||
height: 80.h,
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
)
|
|
||||||
: Container(
|
|
||||||
width: 80.w,
|
|
||||||
height: 80.h,
|
|
||||||
color: Colors.grey[200],
|
|
||||||
child: const Icon(Icons.image, color: Colors.grey),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
order.goodsName,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 15,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Color(0xFF333333),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
order.score,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16.sp,
|
|
||||||
color: Color(0xFF4CAF50),
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(width: 4),
|
|
||||||
Text(
|
|
||||||
'积分',
|
|
||||||
style: TextStyle(fontSize: 11.sp, color: Color(0xFF4CAF50)),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
'x1',
|
|
||||||
style: TextStyle(color: Color(0xFFCCCCCC), fontSize: 16.sp),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,131 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:getx_scaffold/common/index.dart';
|
|
||||||
import 'package:ln_jq_app/common/login_util.dart';
|
|
||||||
|
|
||||||
class MallRulePage extends StatelessWidget {
|
|
||||||
const MallRulePage({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
backgroundColor: Color.fromRGBO(64, 199, 154, 1),
|
|
||||||
appBar: AppBar(
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
elevation: 0,
|
|
||||||
leading: IconButton(
|
|
||||||
icon: const Icon(Icons.arrow_back_ios, color: Colors.white, size: 20),
|
|
||||||
onPressed: () => Get.back(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
body: Stack(
|
|
||||||
children: [
|
|
||||||
// 顶部装饰图
|
|
||||||
Positioned(
|
|
||||||
top: 30,
|
|
||||||
right: Get.width * 0.15,
|
|
||||||
child: LoginUtil.getAssImg("rule_bg@2x"),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
margin: const EdgeInsets.fromLTRB(20, 100, 20, 20),
|
|
||||||
padding: const EdgeInsets.all(24),
|
|
||||||
width: double.infinity,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
image: DecorationImage(
|
|
||||||
image: AssetImage('assets/images/rule_bg_1@2x.png'),
|
|
||||||
fit: BoxFit.fill,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const Text(
|
|
||||||
'积分获取规则',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 22,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Color(0xFF2C3E50),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 30),
|
|
||||||
_buildRuleItem(
|
|
||||||
icon: 'tips_1@2x',
|
|
||||||
title: '每日首次签到积分规则',
|
|
||||||
content: '每日首签,立得 1 积分',
|
|
||||||
),
|
|
||||||
_buildRuleItem(
|
|
||||||
icon: 'tips_2@2x',
|
|
||||||
title: '每日预约加氢积分规则',
|
|
||||||
content: '每日前 2 次预约加氢,各得 1 积分',
|
|
||||||
),
|
|
||||||
_buildRuleItem(
|
|
||||||
icon: 'tips_3@2x',
|
|
||||||
title: '连续签到累计赠分规则',
|
|
||||||
content: '连续签到 3 天赠 2 积分,7 天赠 5 积分',
|
|
||||||
),
|
|
||||||
_buildRuleItem(
|
|
||||||
icon: 'tips_4@2x',
|
|
||||||
title: '连续签到周期及断签重置规则',
|
|
||||||
content: '7 天为一个签到周期,中途断签则重新从第 1 天计算',
|
|
||||||
),
|
|
||||||
_buildRuleItem(
|
|
||||||
icon: 'tips_5@2x',
|
|
||||||
title: '积分使用规则',
|
|
||||||
content:
|
|
||||||
'积分有效期3个月。个人账户内累计的所有有效积分,可在平台积分商城中,用于兑换商城内上架的各类商品、权益或服务,兑换时将按照商品标注的积分值扣除对应积分,积分兑换后不支持撤销、退换,商品兑换规则以积分商城内公示为准。',
|
|
||||||
),
|
|
||||||
const SizedBox(height: 40),
|
|
||||||
const Center(
|
|
||||||
child: Text(
|
|
||||||
'本活动最终解释权归官方所有,如有疑问可咨询客服。',
|
|
||||||
style: TextStyle(color: Color(0xFF999999), fontSize: 12),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildRuleItem({
|
|
||||||
required String icon,
|
|
||||||
required String title,
|
|
||||||
required String content,
|
|
||||||
}) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.only(bottom: 24),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
LoginUtil.getAssImg(icon),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Text(
|
|
||||||
title,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 15.sp,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Color(0xFF333333),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 6),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(left: 0),
|
|
||||||
child: Text(
|
|
||||||
content,
|
|
||||||
style: TextStyle(fontSize: 13.sp, color: Color(0xFF666666), height: 1.5),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -12,61 +12,38 @@ class MessagePage extends GetView<MessageController> {
|
|||||||
Get.put(MessageController());
|
Get.put(MessageController());
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: const Color(0xFFF7F9FB),
|
backgroundColor: const Color(0xFFF5F5F5),
|
||||||
appBar: AppBar(
|
appBar: AppBar(title: const Text('消息通知'), centerTitle: true),
|
||||||
title: const Text('消息通知'),
|
body: Column(
|
||||||
centerTitle: true,
|
|
||||||
backgroundColor: Colors.white,
|
|
||||||
foregroundColor: Colors.black,
|
|
||||||
elevation: 0,
|
|
||||||
leading: IconButton(
|
|
||||||
icon: const Icon(Icons.arrow_back_ios, size: 20),
|
|
||||||
onPressed: () => Get.back(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
body: Stack(
|
|
||||||
children: [
|
children: [
|
||||||
Obx(() => SmartRefresher(
|
Expanded(
|
||||||
|
child: Obx(() => SmartRefresher(
|
||||||
controller: controller.refreshController,
|
controller: controller.refreshController,
|
||||||
enablePullUp: true,
|
enablePullUp: true,
|
||||||
onRefresh: controller.onRefresh,
|
onRefresh: controller.onRefresh,
|
||||||
onLoading: controller.onLoading,
|
onLoading: controller.onLoading,
|
||||||
child: ListView.builder(
|
child: ListView.separated(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 20),
|
padding: const EdgeInsets.all(12),
|
||||||
itemCount: controller.messageList.length,
|
itemCount: controller.messageList.length,
|
||||||
|
separatorBuilder: (_, __) => const SizedBox(height: 12),
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return _buildMessageItem(context, controller.messageList[index]);
|
return _buildMessageItem(context, controller.messageList[index]);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
|
),
|
||||||
Obx(() => !controller.allRead.value
|
Obx(() => !controller.allRead.value
|
||||||
? Positioned(
|
? Container(
|
||||||
right: 20,
|
padding: const EdgeInsets.all(16),
|
||||||
bottom: 50,
|
|
||||||
child: GestureDetector(
|
|
||||||
onTap: controller.markAllRead,
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 14),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: const Color(0xFF007A45),
|
|
||||||
borderRadius: BorderRadius.circular(30),
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: Colors.black.withOpacity(0.1),
|
|
||||||
blurRadius: 10,
|
|
||||||
offset: const Offset(0, 4),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: const Text(
|
|
||||||
'全部已读',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontWeight: FontWeight.bold,
|
child: ElevatedButton(
|
||||||
fontSize: 14,
|
onPressed: controller.markAllRead,
|
||||||
),
|
style: ElevatedButton.styleFrom(
|
||||||
),
|
backgroundColor: Colors.blue,
|
||||||
|
minimumSize: const Size(double.infinity, 44),
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||||
),
|
),
|
||||||
|
child: const Text('全部标为已读', style: TextStyle(fontSize: 16, color: Colors.white)),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: const SizedBox.shrink()),
|
: const SizedBox.shrink()),
|
||||||
@@ -76,93 +53,55 @@ class MessagePage extends GetView<MessageController> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildMessageItem(BuildContext context, MessageModel item) {
|
Widget _buildMessageItem(BuildContext context, MessageModel item) {
|
||||||
return IntrinsicHeight(
|
return GestureDetector(
|
||||||
child: Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: [
|
|
||||||
// 左侧时间轴线条和圆点
|
|
||||||
SizedBox(
|
|
||||||
width: 40,
|
|
||||||
child: Stack(
|
|
||||||
alignment: Alignment.topCenter,
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
width: 1.5,
|
|
||||||
color: const Color(0xFFD8E2EE),
|
|
||||||
),
|
|
||||||
Positioned(
|
|
||||||
top: 25,
|
|
||||||
child: Container(
|
|
||||||
width: 12,
|
|
||||||
height: 12,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: item.isRead == 1
|
|
||||||
? const Color(0xFFAAB6C3)
|
|
||||||
: const Color(0xFF4CAF50),
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
border: Border.all(color: Colors.white, width: 2),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
// 右侧内容卡片
|
|
||||||
Expanded(
|
|
||||||
child: GestureDetector(
|
|
||||||
onTap: () {
|
onTap: () {
|
||||||
controller.markRead(item);
|
controller.markRead(item);
|
||||||
_showMessageDialog(context, item);
|
_showMessageDialog(context, item);
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
margin: const EdgeInsets.only(bottom: 16),
|
padding: const EdgeInsets.all(16),
|
||||||
padding: const EdgeInsets.all(20),
|
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
borderRadius: BorderRadius.circular(24),
|
borderRadius: BorderRadius.circular(8),
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: Colors.black.withOpacity(0.02),
|
|
||||||
blurRadius: 10,
|
|
||||||
offset: const Offset(0, 4),
|
|
||||||
),
|
),
|
||||||
],
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
margin: const EdgeInsets.only(top: 6, right: 12),
|
||||||
|
width: 8,
|
||||||
|
height: 8,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: item.isRead == 1 ? Colors.grey[300] : const Color(0xFFFAAD14),
|
||||||
|
shape: BoxShape.circle,
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
item.title,
|
item.title,
|
||||||
style: const TextStyle(
|
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.black87),
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Color(0xFF333333),
|
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 8),
|
||||||
const SizedBox(height: 10),
|
|
||||||
Text(
|
Text(
|
||||||
item.content,
|
item.content,
|
||||||
style: const TextStyle(
|
style: TextStyle(fontSize: 14, color: Colors.grey[600]),
|
||||||
fontSize: 14,
|
maxLines: 2,
|
||||||
color: Color(0xFF666666),
|
overflow: TextOverflow.ellipsis,
|
||||||
height: 1.4,
|
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 8),
|
||||||
const SizedBox(height: 12),
|
|
||||||
Text(
|
Text(
|
||||||
item.createTime,
|
item.createTime,
|
||||||
style: const TextStyle(
|
style: TextStyle(fontSize: 12, color: Colors.grey[400]),
|
||||||
fontSize: 12,
|
|
||||||
color: Color(0xFFCCCCCC),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,7 +111,7 @@ class MessagePage extends GetView<MessageController> {
|
|||||||
barrierDismissible: true,
|
barrierDismissible: true,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return Dialog(
|
return Dialog(
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.all(24),
|
padding: const EdgeInsets.all(24),
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -183,22 +122,22 @@ class MessagePage extends GetView<MessageController> {
|
|||||||
item.title,
|
item.title,
|
||||||
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 12),
|
||||||
Text(
|
Text(
|
||||||
item.content,
|
item.content,
|
||||||
style: const TextStyle(
|
style: const TextStyle(fontSize: 15, height: 1.5, color: Colors.black87),
|
||||||
fontSize: 15, height: 1.5, color: Color(0xFF333333)),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
Align(
|
Align(
|
||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
child: TextButton(
|
child: OutlinedButton(
|
||||||
onPressed: () => Navigator.pop(context),
|
onPressed: () => Navigator.pop(context),
|
||||||
style: TextButton.styleFrom(
|
style: OutlinedButton.styleFrom(
|
||||||
foregroundColor: const Color(0xFF007A45),
|
side: const BorderSide(color: Colors.blue),
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)),
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 8),
|
||||||
),
|
),
|
||||||
child: const Text('确认',
|
child: const Text('确认', style: TextStyle(color: Colors.blue)),
|
||||||
style: TextStyle(fontWeight: FontWeight.bold)),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,15 +1,10 @@
|
|||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:flutter/gestures.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:getx_scaffold/common/index.dart';
|
import 'package:getx_scaffold/common/index.dart';
|
||||||
import 'package:getx_scaffold/common/widgets/index.dart';
|
import 'package:getx_scaffold/common/widgets/index.dart';
|
||||||
import 'package:ln_jq_app/common/login_util.dart';
|
import 'package:ln_jq_app/common/login_util.dart';
|
||||||
import 'package:ln_jq_app/common/styles/theme.dart';
|
|
||||||
import 'package:ln_jq_app/pages/c_page/message/view.dart';
|
import 'package:ln_jq_app/pages/c_page/message/view.dart';
|
||||||
import 'package:ln_jq_app/pages/common/webview/view.dart';
|
|
||||||
import 'package:ln_jq_app/storage_service.dart';
|
import 'package:ln_jq_app/storage_service.dart';
|
||||||
import 'controller.dart';
|
import 'controller.dart';
|
||||||
|
|
||||||
@@ -43,30 +38,6 @@ class MinePage extends GetView<MineController> {
|
|||||||
_buildSafetyReminderCard(),
|
_buildSafetyReminderCard(),
|
||||||
SizedBox(height: 24.h),
|
SizedBox(height: 24.h),
|
||||||
_buildLogoutButton(),
|
_buildLogoutButton(),
|
||||||
SizedBox(height: 15.h),
|
|
||||||
Text.rich(
|
|
||||||
TextSpan(
|
|
||||||
style: const TextStyle(color: Colors.grey, fontSize: 13),
|
|
||||||
children: [
|
|
||||||
TextSpan(
|
|
||||||
text: '《用户协议》',
|
|
||||||
style: TextStyle(color: Colors.blue, fontSize: 13),
|
|
||||||
recognizer: TapGestureRecognizer()
|
|
||||||
..onTap = () {
|
|
||||||
openPage("用户协议", "https://lnh2e.com/user_agreement.html");
|
|
||||||
},
|
|
||||||
),
|
|
||||||
TextSpan(
|
|
||||||
text: '《隐私政策》',
|
|
||||||
style: TextStyle(color: Colors.blue, fontSize: 13),
|
|
||||||
recognizer: TapGestureRecognizer()
|
|
||||||
..onTap = () {
|
|
||||||
openPage("隐私政策", "https://lnh2e.com/privacy_agreement.html");
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(height: 95.h),
|
SizedBox(height: 95.h),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -80,15 +51,6 @@ class MinePage extends GetView<MineController> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void openPage(String title, String url) {
|
|
||||||
if (Platform.isIOS) {
|
|
||||||
openWebPage(url);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Get.to(() => const WebViewPage(), arguments: {'title': title, 'url': url});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// 构建顶部用户信息卡片
|
/// 构建顶部用户信息卡片
|
||||||
Widget _buildUserInfoCard() {
|
Widget _buildUserInfoCard() {
|
||||||
return Card(
|
return Card(
|
||||||
|
|||||||
@@ -69,34 +69,239 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
|||||||
String get formattedTimeSlot =>
|
String get formattedTimeSlot =>
|
||||||
'${_formatTimeOfDay(startTime.value)} - ${_formatTimeOfDay(endTime.value)}';
|
'${_formatTimeOfDay(startTime.value)} - ${_formatTimeOfDay(endTime.value)}';
|
||||||
|
|
||||||
|
void pickDate(BuildContext context) {
|
||||||
|
DateTime tempDate = selectedDate.value;
|
||||||
|
|
||||||
|
// 获取今天的日期 (不含时间)
|
||||||
|
final DateTime today = DateTime(
|
||||||
|
DateTime.now().year,
|
||||||
|
DateTime.now().month,
|
||||||
|
DateTime.now().day,
|
||||||
|
);
|
||||||
|
|
||||||
|
//计算明天的日期
|
||||||
|
final DateTime tomorrow = today.add(const Duration(days: 1));
|
||||||
|
|
||||||
|
Get.bottomSheet(
|
||||||
|
Container(
|
||||||
|
height: 300,
|
||||||
|
padding: const EdgeInsets.only(top: 6.0),
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(16),
|
||||||
|
topRight: Radius.circular(16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
CupertinoButton(
|
||||||
|
onPressed: () => Get.back(),
|
||||||
|
child: const Text(
|
||||||
|
'取消',
|
||||||
|
style: TextStyle(color: CupertinoColors.systemGrey),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
CupertinoButton(
|
||||||
|
onPressed: () {
|
||||||
|
final bool isChangingToToday =
|
||||||
|
tempDate.isAtSameMomentAs(today) &&
|
||||||
|
!selectedDate.value.isAtSameMomentAs(today);
|
||||||
|
final bool isDateChanged = !tempDate.isAtSameMomentAs(
|
||||||
|
selectedDate.value,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 更新选中的日期
|
||||||
|
selectedDate.value = tempDate;
|
||||||
|
Get.back(); // 先关闭弹窗
|
||||||
|
|
||||||
|
// 如果日期发生了变化,则重置时间
|
||||||
|
if (isDateChanged) {
|
||||||
|
resetTimeForSelectedDate();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: const Text(
|
||||||
|
'确认',
|
||||||
|
style: TextStyle(
|
||||||
|
color: AppTheme.themeColor,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Divider(height: 1, color: Color(0xFFE5E5E5)),
|
||||||
|
Expanded(
|
||||||
|
child: CupertinoDatePicker(
|
||||||
|
mode: CupertinoDatePickerMode.date,
|
||||||
|
initialDateTime: selectedDate.value,
|
||||||
|
minimumDate: today,
|
||||||
|
// 最小可选日期为今天
|
||||||
|
maximumDate: tomorrow,
|
||||||
|
// 最大可选日期为明天
|
||||||
|
// ---------------------
|
||||||
|
onDateTimeChanged: (DateTime newDate) {
|
||||||
|
tempDate = newDate;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
void resetTimeForSelectedDate() {
|
void resetTimeForSelectedDate() {
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
final today = DateTime(now.year, now.month, now.day);
|
final today = DateTime(now.year, now.month, now.day);
|
||||||
|
|
||||||
// 1. 获取当前站点
|
// 判断新选择的日期是不是今天
|
||||||
final station = stationOptions.firstWhereOrNull(
|
|
||||||
(s) => s.hydrogenId == selectedStationId.value,
|
|
||||||
);
|
|
||||||
if (station == null) return;
|
|
||||||
|
|
||||||
// 2. 解析营业开始和结束的小时
|
|
||||||
final bizStartHour = int.tryParse(station.startBusiness.split(':')[0]) ?? 0;
|
|
||||||
final bizEndHour = int.tryParse(station.endBusiness.split(':')[0]) ?? 23;
|
|
||||||
|
|
||||||
if (selectedDate.value.isAtSameMomentAs(today)) {
|
if (selectedDate.value.isAtSameMomentAs(today)) {
|
||||||
// 如果是今天:起始时间 = max(当前小时, 营业开始小时),且上限为营业结束小时
|
// 如果是今天,就将时间重置为当前时间所在的半小时区间
|
||||||
int targetHour = now.hour;
|
startTime.value = _calculateInitialStartTime(now);
|
||||||
if (targetHour < bizStartHour) targetHour = bizStartHour;
|
endTime.value = TimeOfDay.fromDateTime(
|
||||||
if (targetHour > bizEndHour) targetHour = bizEndHour;
|
_getDateTimeFromTimeOfDay(startTime.value).add(const Duration(minutes: 30)),
|
||||||
|
);
|
||||||
startTime.value = TimeOfDay(hour: targetHour, minute: 0);
|
|
||||||
} else {
|
} else {
|
||||||
// 如果是明天:起始时间直接重置为营业开始小时
|
// 如果是明天(或其他未来日期),则可以将时间重置为一天的最早可用时间,例如 00:00
|
||||||
startTime.value = TimeOfDay(hour: bizStartHour, minute: 0);
|
startTime.value = const TimeOfDay(hour: 0, minute: 0);
|
||||||
|
endTime.value = const TimeOfDay(hour: 0, minute: 30);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 结束时间默认顺延1小时
|
///60 分钟为间隔 时间选择器
|
||||||
endTime.value = TimeOfDay(hour: (startTime.value.hour + 1) % 24, minute: 0);
|
void pickTime(BuildContext context) {
|
||||||
|
final now = DateTime.now();
|
||||||
|
final isToday =
|
||||||
|
selectedDate.value.year == now.year &&
|
||||||
|
selectedDate.value.month == now.month &&
|
||||||
|
selectedDate.value.day == now.day;
|
||||||
|
|
||||||
|
final List<TimeSlot> availableSlots = [];
|
||||||
|
for (int i = 0; i < 24; i++) {
|
||||||
|
// 每次增加 60 分钟
|
||||||
|
final startMinutes = i * 60;
|
||||||
|
final endMinutes = startMinutes + 60;
|
||||||
|
|
||||||
|
final startTime = TimeOfDay(hour: startMinutes ~/ 60, minute: startMinutes % 60);
|
||||||
|
// 注意:endMinutes % 60 始终为 0,因为间隔是整小时
|
||||||
|
final endTime = TimeOfDay(hour: (endMinutes ~/ 60) % 24, minute: endMinutes % 60);
|
||||||
|
|
||||||
|
// 如果不是今天,所有时间段都有效
|
||||||
|
if (!isToday) {
|
||||||
|
availableSlots.add(TimeSlot(startTime, endTime));
|
||||||
|
} else {
|
||||||
|
// 如果是今天,需要判断该时间段是否可选
|
||||||
|
// 创建时间段的结束时间对象
|
||||||
|
final slotEndDateTime = DateTime(
|
||||||
|
selectedDate.value.year,
|
||||||
|
selectedDate.value.month,
|
||||||
|
selectedDate.value.day,
|
||||||
|
endTime.hour,
|
||||||
|
endTime.minute,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 注意:如果是跨天的 00:00 (例如 23:00 - 00:00),需要将日期加一天,否则 isAfter 判断会出错
|
||||||
|
// 但由于我们用的是 endTime.hour % 24,当变成 0 时,日期还是 selectedDate
|
||||||
|
// 这里做一个特殊处理:如果 endTime 是 00:00,意味着它实际上是明天的开始
|
||||||
|
DateTime realEndDateTime = slotEndDateTime;
|
||||||
|
if (endTime.hour == 0 && endTime.minute == 0) {
|
||||||
|
realEndDateTime = slotEndDateTime.add(const Duration(days: 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只要时间段的结束时间晚于当前时间,这个时间段就是可预约的
|
||||||
|
if (realEndDateTime.isAfter(now)) {
|
||||||
|
availableSlots.add(TimeSlot(startTime, endTime));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (availableSlots.isEmpty) {
|
||||||
|
showToast('今天已没有可预约的时间段');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找当前选中的时间对应的新列表中的索引
|
||||||
|
int initialItem = availableSlots.indexWhere(
|
||||||
|
(slot) => slot.start.hour == startTime.value.hour,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (initialItem == -1) {
|
||||||
|
initialItem = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeSlot tempSlot = availableSlots[initialItem];
|
||||||
|
|
||||||
|
final FixedExtentScrollController scrollController = FixedExtentScrollController(
|
||||||
|
initialItem: initialItem,
|
||||||
|
);
|
||||||
|
|
||||||
|
Get.bottomSheet(
|
||||||
|
Container(
|
||||||
|
height: 300,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(16),
|
||||||
|
topRight: Radius.circular(16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
CupertinoButton(
|
||||||
|
onPressed: () => Get.back(),
|
||||||
|
child: const Text(
|
||||||
|
'取消',
|
||||||
|
style: TextStyle(color: CupertinoColors.systemGrey),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
CupertinoButton(
|
||||||
|
onPressed: () {
|
||||||
|
startTime.value = tempSlot.start;
|
||||||
|
endTime.value = tempSlot.end;
|
||||||
|
Get.back();
|
||||||
|
},
|
||||||
|
child: const Text(
|
||||||
|
'确认',
|
||||||
|
style: TextStyle(
|
||||||
|
color: AppTheme.themeColor,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Divider(height: 1, color: Color(0xFFE5E5E5)),
|
||||||
|
Expanded(
|
||||||
|
child: CupertinoPicker(
|
||||||
|
scrollController: scrollController,
|
||||||
|
itemExtent: 40.0,
|
||||||
|
onSelectedItemChanged: (index) {
|
||||||
|
tempSlot = availableSlots[index];
|
||||||
|
},
|
||||||
|
children: availableSlots
|
||||||
|
.map((slot) => Center(child: Text(slot.display)))
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 用于存储上一次成功预约的信息
|
// 用于存储上一次成功预约的信息
|
||||||
@@ -221,14 +426,8 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
|||||||
stateName: '',
|
stateName: '',
|
||||||
addStatus: '',
|
addStatus: '',
|
||||||
addStatusName: '',
|
addStatusName: '',
|
||||||
hasEdit: true,
|
|
||||||
rejectReason: '',
|
rejectReason: '',
|
||||||
isTruckAttachment: 0,
|
hasEdit: true,
|
||||||
hasHydrogenationAttachment: true,
|
|
||||||
hasDrivingAttachment: true,
|
|
||||||
isEdit: '',
|
|
||||||
drivingAttachments: [],
|
|
||||||
hydrogenationAttachments: [], gunNumber: '',
|
|
||||||
);
|
);
|
||||||
|
|
||||||
//打开预约列表
|
//打开预约列表
|
||||||
@@ -259,7 +458,7 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
|||||||
if (_debounce?.isActive ?? false) {
|
if (_debounce?.isActive ?? false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_debounce = Timer(const Duration(milliseconds: 200), () {});
|
_debounce = Timer(const Duration(seconds: 1), () {});
|
||||||
|
|
||||||
showLoading("加载中");
|
showLoading("加载中");
|
||||||
|
|
||||||
@@ -337,13 +536,12 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String workEfficiency = "-";
|
String workEfficiency = "0";
|
||||||
String fillingWeight = "-";
|
String fillingWeight = "0";
|
||||||
String fillingTimes = "-";
|
String fillingTimes = "0";
|
||||||
String modeImage = "";
|
|
||||||
String plateNumber = "";
|
String plateNumber = "";
|
||||||
String vin = "";
|
String vin = "";
|
||||||
String leftHydrogen = "-";
|
String leftHydrogen = "0";
|
||||||
num maxHydrogen = 0;
|
num maxHydrogen = 0;
|
||||||
String difference = "";
|
String difference = "";
|
||||||
var progressValue = 0.0;
|
var progressValue = 0.0;
|
||||||
@@ -399,7 +597,7 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
|||||||
|
|
||||||
// 创建一个每1分钟执行一次的周期性定时器
|
// 创建一个每1分钟执行一次的周期性定时器
|
||||||
_refreshTimer = Timer.periodic(const Duration(minutes: 1), (timer) {
|
_refreshTimer = Timer.periodic(const Duration(minutes: 1), (timer) {
|
||||||
getSiteList(showloading: false);
|
getSiteList();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -452,7 +650,7 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
|||||||
try {
|
try {
|
||||||
HttpService.to.setBaseUrl(AppTheme.test_service_url);
|
HttpService.to.setBaseUrl(AppTheme.test_service_url);
|
||||||
var responseData = await HttpService.to.get(
|
var responseData = await HttpService.to.get(
|
||||||
'appointment/truck/history-filling-summary?vin=$vin&plateNumber=$plateNumber',
|
'appointment/truck/history-filling-summary?vin=$vin',
|
||||||
);
|
);
|
||||||
if (responseData == null || responseData.data == null) {
|
if (responseData == null || responseData.data == null) {
|
||||||
showToast('服务暂不可用,请稍后');
|
showToast('服务暂不可用,请稍后');
|
||||||
@@ -466,7 +664,6 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
|||||||
|
|
||||||
fillingWeight = "$formatted${result.data["fillingWeightUnit"]}";
|
fillingWeight = "$formatted${result.data["fillingWeightUnit"]}";
|
||||||
fillingTimes = "${result.data["fillingTimes"]}${result.data["fillingTimesUnit"]}";
|
fillingTimes = "${result.data["fillingTimes"]}${result.data["fillingTimesUnit"]}";
|
||||||
modeImage = result.data["modeImage"].toString();
|
|
||||||
|
|
||||||
updateUi();
|
updateUi();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -492,8 +689,8 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
|||||||
|
|
||||||
var result = BaseModel.fromJson(responseData.data);
|
var result = BaseModel.fromJson(responseData.data);
|
||||||
|
|
||||||
leftHydrogen = "${result.data["leftHydrogen"]}Kg";
|
leftHydrogen = result.data["leftHydrogen"].toString();
|
||||||
workEfficiency = "${result.data["workEfficiency"]}Kg";
|
workEfficiency = result.data["workEfficiency"].toString();
|
||||||
|
|
||||||
final leftHydrogenNum = double.tryParse(leftHydrogen) ?? 0.0;
|
final leftHydrogenNum = double.tryParse(leftHydrogen) ?? 0.0;
|
||||||
difference = (maxHydrogen - leftHydrogenNum).toStringAsFixed(2);
|
difference = (maxHydrogen - leftHydrogenNum).toStringAsFixed(2);
|
||||||
@@ -527,7 +724,7 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
|||||||
updateUi();
|
updateUi();
|
||||||
}
|
}
|
||||||
|
|
||||||
void getSiteList({showloading = true}) async {
|
void getSiteList() async {
|
||||||
if (StorageService.to.phone == "13344444444") {
|
if (StorageService.to.phone == "13344444444") {
|
||||||
//该账号给stationOptions手动添加一个数据
|
//该账号给stationOptions手动添加一个数据
|
||||||
final testStation = StationModel(
|
final testStation = StationModel(
|
||||||
@@ -538,9 +735,7 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
|||||||
// 价格
|
// 价格
|
||||||
siteStatusName: '营运中',
|
siteStatusName: '营运中',
|
||||||
// 状态
|
// 状态
|
||||||
isSelect: 1,
|
isSelect: 1, // 默认可选
|
||||||
startBusiness: '08:00:00',
|
|
||||||
endBusiness: '20:00:00', // 默认可选
|
|
||||||
);
|
);
|
||||||
// 使用 assignAll 可以确保列表只包含这个测试数据
|
// 使用 assignAll 可以确保列表只包含这个测试数据
|
||||||
stationOptions.assignAll([testStation]);
|
stationOptions.assignAll([testStation]);
|
||||||
@@ -552,9 +747,7 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (showloading) {
|
|
||||||
showLoading("加氢站数据加载中");
|
showLoading("加氢站数据加载中");
|
||||||
}
|
|
||||||
|
|
||||||
var responseData = await HttpService.to.get(
|
var responseData = await HttpService.to.get(
|
||||||
"appointment/station/queryHydrogenSiteInfo",
|
"appointment/station/queryHydrogenSiteInfo",
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:getx_scaffold/common/index.dart';
|
import 'package:getx_scaffold/common/index.dart';
|
||||||
import 'package:ln_jq_app/common/model/base_model.dart';
|
import 'package:ln_jq_app/common/model/base_model.dart';
|
||||||
import 'package:ln_jq_app/common/styles/theme.dart';
|
|
||||||
import 'package:ln_jq_app/pages/c_page/reservation/controller.dart';
|
import 'package:ln_jq_app/pages/c_page/reservation/controller.dart';
|
||||||
import 'package:ln_jq_app/pages/c_page/reservation_edit/controller.dart';
|
import 'package:ln_jq_app/pages/c_page/reservation_edit/controller.dart';
|
||||||
import 'package:ln_jq_app/pages/c_page/reservation_edit/view.dart';
|
import 'package:ln_jq_app/pages/c_page/reservation_edit/view.dart';
|
||||||
@@ -37,19 +36,19 @@ class _ReservationListBottomSheetState extends State<ReservationListBottomSheet>
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
height: Get.height * 0.6,
|
height: Get.height * 0.55,
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
color: Color.fromRGBO(247, 249, 251, 1),
|
color: Colors.white,
|
||||||
borderRadius: BorderRadius.only(
|
borderRadius: BorderRadius.only(
|
||||||
topLeft: Radius.circular(16),
|
topLeft: Radius.circular(16),
|
||||||
topRight: Radius.circular(16),
|
topRight: Radius.circular(16),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
children: [
|
||||||
// 构建标题和下拉框
|
// 构建标题和下拉框
|
||||||
_buildHeader(),
|
_buildHeader(),
|
||||||
|
const Divider(height: 1),
|
||||||
// 下拉筛选框
|
// 下拉筛选框
|
||||||
_buildChoice(),
|
_buildChoice(),
|
||||||
// 构建列表(使用 Obx 监听数据变化)
|
// 构建列表(使用 Obx 监听数据变化)
|
||||||
@@ -59,53 +58,35 @@ class _ReservationListBottomSheetState extends State<ReservationListBottomSheet>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildChoice() {
|
Container _buildChoice() {
|
||||||
return SingleChildScrollView(
|
return Container(
|
||||||
scrollDirection: Axis.horizontal,
|
padding: const EdgeInsets.fromLTRB(20, 8, 0, 0),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
alignment: AlignmentGeometry.centerLeft,
|
||||||
child: Row(
|
|
||||||
children: _statusOptions.entries.map((entry) {
|
|
||||||
bool isSelected = _selectedStatus == entry.key;
|
|
||||||
return GestureDetector(
|
|
||||||
behavior: HitTestBehavior.opaque,
|
|
||||||
onTap: () {
|
|
||||||
if (_selectedStatus == entry.key) return;
|
|
||||||
|
|
||||||
// 立即执行刷新逻辑
|
|
||||||
_controller.getReservationList(addStatus: entry.key);
|
|
||||||
|
|
||||||
// 先更新本地状态改变 UI 选中效果
|
|
||||||
setState(() {
|
|
||||||
_selectedStatus = entry.key;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: Container(
|
child: Container(
|
||||||
margin: const EdgeInsets.only(right: 12),
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 8),
|
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
// 选中色使用深绿色,未选中保持纯白
|
color: Colors.grey[100],
|
||||||
color: isSelected ? const Color(0xFF006633) : Colors.white,
|
borderRadius: BorderRadius.circular(8),
|
||||||
borderRadius: BorderRadius.circular(25),
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: Colors.black.withOpacity(0.04),
|
|
||||||
blurRadius: 6,
|
|
||||||
offset: const Offset(0, 2),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
entry.key == '' ? '全部' : entry.value,
|
|
||||||
style: TextStyle(
|
|
||||||
// 未选中文字颜色微调为图片中的灰蓝色
|
|
||||||
color: isSelected ? Colors.white : const Color(0xFFAAB6C3),
|
|
||||||
fontSize: 14.sp,
|
|
||||||
fontWeight: isSelected ? FontWeight.bold : FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
child: DropdownButton<String>(
|
||||||
|
value: _selectedStatus,
|
||||||
|
underline: const SizedBox.shrink(), // 隐藏下划线
|
||||||
|
items: _statusOptions.entries.map((entry) {
|
||||||
|
return DropdownMenuItem<String>(
|
||||||
|
value: entry.key,
|
||||||
|
child: Text(entry.value),
|
||||||
);
|
);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
|
onChanged: (newValue) {
|
||||||
|
if (newValue != null) {
|
||||||
|
setState(() {
|
||||||
|
_selectedStatus = newValue;
|
||||||
|
});
|
||||||
|
// 当选择新状态时,调用接口刷新数据
|
||||||
|
_controller.getReservationList(addStatus: _selectedStatus);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -113,10 +94,24 @@ class _ReservationListBottomSheetState extends State<ReservationListBottomSheet>
|
|||||||
/// 构建标题、关闭按钮和下拉筛选框
|
/// 构建标题、关闭按钮和下拉筛选框
|
||||||
Widget _buildHeader() {
|
Widget _buildHeader() {
|
||||||
return Container(
|
return Container(
|
||||||
margin: const EdgeInsets.fromLTRB(20, 20, 8, 8),
|
padding: const EdgeInsets.fromLTRB(20, 8, 8, 8),
|
||||||
child: const Text(
|
child: Stack(
|
||||||
'我的预约',
|
alignment: Alignment.center,
|
||||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
children: [
|
||||||
|
Center(child: const Text('我的预约', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: () => Get.back(),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
elevation: 0,
|
||||||
|
backgroundColor: Colors.grey[200],
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)),
|
||||||
|
),
|
||||||
|
child: const Text('关闭', style: TextStyle(color: Colors.black54)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -139,8 +134,8 @@ class _ReservationListBottomSheetState extends State<ReservationListBottomSheet>
|
|||||||
return Card(
|
return Card(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
margin: const EdgeInsets.only(bottom: 12.0),
|
margin: const EdgeInsets.only(bottom: 12.0),
|
||||||
elevation: 0,
|
elevation: 1,
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -156,20 +151,17 @@ class _ReservationListBottomSheetState extends State<ReservationListBottomSheet>
|
|||||||
vertical: 5,
|
vertical: 5,
|
||||||
),
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: reservation.state == "-1"
|
color: Colors.blue.shade50, // 淡蓝色背景
|
||||||
? Color.fromRGBO(241, 67, 56, 0.1)
|
|
||||||
: Color.fromRGBO(230, 249, 243, 1),
|
|
||||||
borderRadius: BorderRadius.circular(4), // 小圆角
|
borderRadius: BorderRadius.circular(4), // 小圆角
|
||||||
// 可以选择去掉边框,或者用极淡的边框
|
// 可以选择去掉边框,或者用极淡的边框
|
||||||
|
border: Border.all(color: Colors.blue.shade100),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
"${reservation.stateName}-${reservation.addStatusName}",
|
"${reservation.stateName}-${reservation.addStatusName}",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: reservation.state == "-1"
|
color: Colors.blue.shade700,
|
||||||
? Color.fromRGBO(241, 67, 56, 0.8)
|
fontSize: 12,
|
||||||
: Color.fromRGBO(49, 186, 133, 1),
|
fontWeight: FontWeight.w500,
|
||||||
fontSize: 12.sp,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -179,10 +171,12 @@ class _ReservationListBottomSheetState extends State<ReservationListBottomSheet>
|
|||||||
SizedBox(
|
SizedBox(
|
||||||
height: 28, // 限制按钮高度,显得精致
|
height: 28, // 限制按钮高度,显得精致
|
||||||
child: OutlinedButton(
|
child: OutlinedButton(
|
||||||
onPressed: () async {
|
onPressed: () async{
|
||||||
var responseData = await HttpService.to.post(
|
var responseData = await HttpService.to.post(
|
||||||
'appointment/orderAddHyd/vehicle-cancel',
|
'appointment/orderAddHyd/vehicle-cancel',
|
||||||
data: {'id': reservation.id},
|
data: {
|
||||||
|
'id': reservation.id,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (responseData == null || responseData.data == null) {
|
if (responseData == null || responseData.data == null) {
|
||||||
@@ -204,25 +198,22 @@ class _ReservationListBottomSheetState extends State<ReservationListBottomSheet>
|
|||||||
},
|
},
|
||||||
style: OutlinedButton.styleFrom(
|
style: OutlinedButton.styleFrom(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
side: BorderSide(color: Colors.grey.shade400),
|
side: BorderSide(color: Colors.grey.shade400), // 灰色边框
|
||||||
shape: const StadiumBorder(),
|
shape: const StadiumBorder(), // 胶囊形状
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
'取消预约',
|
'取消预约',
|
||||||
style: TextStyle(
|
style: TextStyle(color: Colors.grey.shade600, fontSize: 12),
|
||||||
color: Colors.grey.shade600,
|
|
||||||
fontSize: 11.sp,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
SizedBox(width: 10.w,),
|
||||||
SizedBox(width: 10.w),
|
|
||||||
// 修改按钮 (仅在 hasEdit 为 true 时显示)
|
// 修改按钮 (仅在 hasEdit 为 true 时显示)
|
||||||
if (reservation.hasEdit)
|
if (reservation.hasEdit)
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 28,
|
height: 28,
|
||||||
child: OutlinedButton(
|
child: OutlinedButton(
|
||||||
onPressed: () async {
|
onPressed: () async{
|
||||||
var result = await Get.to(
|
var result = await Get.to(
|
||||||
() => ReservationEditPage(),
|
() => ReservationEditPage(),
|
||||||
arguments: {
|
arguments: {
|
||||||
@@ -242,22 +233,21 @@ class _ReservationListBottomSheetState extends State<ReservationListBottomSheet>
|
|||||||
},
|
},
|
||||||
style: OutlinedButton.styleFrom(
|
style: OutlinedButton.styleFrom(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
side: BorderSide(color: AppTheme.themeColor),
|
side: const BorderSide(color: Colors.blue), // 蓝色边框
|
||||||
shape: const StadiumBorder(),
|
shape: const StadiumBorder(),
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: Colors.white,
|
||||||
),
|
),
|
||||||
child: Text(
|
child: const Text(
|
||||||
'修改',
|
'修改',
|
||||||
style: TextStyle(color: AppTheme.themeColor, fontSize: 11.sp),
|
style: TextStyle(color: Colors.blue, fontSize: 12),
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
),),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
_buildDetailRow('车牌号:', reservation.plateNumber),
|
_buildDetailRow('车牌号:', reservation.plateNumber),
|
||||||
_buildDetailRow('预约日期:', reservation.date),
|
_buildDetailRow('预约日期:', reservation.date),
|
||||||
_buildDetailRow('预约氢量:', "${reservation.hydAmount} KG"),
|
_buildDetailRow('预约氢量:', reservation.hydAmount),
|
||||||
_buildDetailRow('加氢站:', reservation.stationName),
|
_buildDetailRow('加氢站:', reservation.stationName),
|
||||||
_buildDetailRow('开始时间:', reservation.startTime),
|
_buildDetailRow('开始时间:', reservation.startTime),
|
||||||
_buildDetailRow('结束时间:', reservation.endTime),
|
_buildDetailRow('结束时间:', reservation.endTime),
|
||||||
@@ -281,26 +271,11 @@ class _ReservationListBottomSheetState extends State<ReservationListBottomSheet>
|
|||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(top: 8.0),
|
padding: const EdgeInsets.only(top: 8.0),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(label, style: const TextStyle(color: Colors.grey)),
|
||||||
label,
|
|
||||||
style: TextStyle(
|
|
||||||
color: Color.fromRGBO(51, 51, 51, 0.6),
|
|
||||||
fontSize: 14.sp,
|
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Text(
|
Expanded(
|
||||||
value,
|
child: Text(value, style: const TextStyle(fontWeight: FontWeight.w500)),
|
||||||
style: TextStyle(
|
|
||||||
color: label.contains("预约氢量:")
|
|
||||||
? Color.fromRGBO(27, 168, 85, 1)
|
|
||||||
: Color.fromRGBO(51, 51, 51, 1),
|
|
||||||
fontSize: 14.sp,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ class ReservationPage extends GetView<C_ReservationController> {
|
|||||||
Positioned(
|
Positioned(
|
||||||
left: 20.w,
|
left: 20.w,
|
||||||
right: 20.w,
|
right: 20.w,
|
||||||
bottom: Get.height * (Get.height < 826 ? 0.08 : 0.11),
|
bottom: 110.h,
|
||||||
child: _buildReservationItem(context),
|
child: _buildReservationItem(context),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -148,11 +148,9 @@ class ReservationPage extends GetView<C_ReservationController> {
|
|||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
controller.stopAutoRefresh();
|
|
||||||
var scanResult = await Get.to(() => const MessagePage());
|
var scanResult = await Get.to(() => const MessagePage());
|
||||||
if (scanResult == null) {
|
if (scanResult == null) {
|
||||||
controller.msgNotice();
|
controller.msgNotice();
|
||||||
controller.startAutoRefresh();
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
icon: Badge(
|
icon: Badge(
|
||||||
@@ -179,12 +177,7 @@ class ReservationPage extends GetView<C_ReservationController> {
|
|||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
_buildModernStatItem('总加氢次数', '', controller.fillingTimes, ''),
|
_buildModernStatItem('总加氢次数', '', controller.fillingTimes, ''),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
_buildModernStatItem(
|
_buildModernStatItem('今日里程', '', "7kg", ''),
|
||||||
'今日里程',
|
|
||||||
'',
|
|
||||||
StorageService.to.hasVehicleInfo ? "7kg" : "-",
|
|
||||||
'',
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -244,33 +237,17 @@ class ReservationPage extends GetView<C_ReservationController> {
|
|||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(flex: 4, child: LoginUtil.getAssImg('ic_car_bg@2x')),
|
||||||
flex: 4,
|
|
||||||
child: Image.network(
|
|
||||||
controller.modeImage,
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
loadingBuilder: (context, child, loadingProgress) {
|
|
||||||
if (loadingProgress == null) return child;
|
|
||||||
return Center(child: CircularProgressIndicator());
|
|
||||||
},
|
|
||||||
errorBuilder: (context, error, stackTrace) {
|
|
||||||
return Center(child: LoginUtil.getAssImg('ic_car_select@2x'));
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 6,
|
flex: 6,
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
_buildCarDataItem(
|
_buildCarDataItem('剩余电量', '36.8%'),
|
||||||
'剩余电量',
|
|
||||||
StorageService.to.hasVehicleInfo ? '36.8%' : '-',
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
_buildCarDataItem('剩余氢量', controller.leftHydrogen),
|
_buildCarDataItem('剩余氢量', '${controller.leftHydrogen}Kg'),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
_buildCarDataItem('百公里氢耗', controller.workEfficiency),
|
_buildCarDataItem('百公里氢耗', '${controller.workEfficiency}Kg'),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
Column(
|
Column(
|
||||||
children: [
|
children: [
|
||||||
@@ -298,7 +275,7 @@ class ReservationPage extends GetView<C_ReservationController> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
controller.leftHydrogen,
|
"${controller.leftHydrogen}Kg",
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
color: Color(0xFF006633),
|
color: Color(0xFF006633),
|
||||||
@@ -457,34 +434,8 @@ class ReservationPage extends GetView<C_ReservationController> {
|
|||||||
/// 时间 Slider 选择器
|
/// 时间 Slider 选择器
|
||||||
Widget _buildTimeSlider(BuildContext context) {
|
Widget _buildTimeSlider(BuildContext context) {
|
||||||
return Obx(() {
|
return Obx(() {
|
||||||
// 获取站点信息
|
// 这里的逻辑对应 Controller 中的 24 小时可用 Slot
|
||||||
final station = controller.stationOptions.firstWhereOrNull(
|
int currentIdx = controller.startTime.value.hour;
|
||||||
(s) => s.hydrogenId == controller.selectedStationId.value,
|
|
||||||
);
|
|
||||||
|
|
||||||
// 如果没有站点数据,默认隐藏
|
|
||||||
if (station == null) {
|
|
||||||
return const SizedBox(height: 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析营业范围
|
|
||||||
final startParts = station.startBusiness.split(':');
|
|
||||||
final endParts = station.endBusiness.split(':');
|
|
||||||
int bizStartHour = int.tryParse(startParts[0]) ?? 0;
|
|
||||||
int bizEndHour = int.tryParse(endParts[0]) ?? 23;
|
|
||||||
int bizEndMinute = (endParts.length > 1) ? (int.tryParse(endParts[1]) ?? 0) : 0;
|
|
||||||
if (bizEndMinute == 0 && bizEndHour > bizStartHour) bizEndHour--;
|
|
||||||
|
|
||||||
//确定当前滑块值
|
|
||||||
int currentHour = controller.startTime.value.hour;
|
|
||||||
double sliderValue = currentHour.toDouble().clamp(
|
|
||||||
bizStartHour.toDouble(),
|
|
||||||
bizEndHour.toDouble(),
|
|
||||||
);
|
|
||||||
|
|
||||||
double minVal = bizStartHour.toDouble();
|
|
||||||
double maxVal = bizEndHour.toDouble();
|
|
||||||
if (minVal >= maxVal) maxVal = minVal + 1;
|
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
@@ -533,20 +484,23 @@ class ReservationPage extends GetView<C_ReservationController> {
|
|||||||
overlayColor: const Color(0xFF006633).withOpacity(0.1),
|
overlayColor: const Color(0xFF006633).withOpacity(0.1),
|
||||||
),
|
),
|
||||||
child: Slider(
|
child: Slider(
|
||||||
value: sliderValue,
|
value: currentIdx.toDouble(),
|
||||||
min: minVal,
|
min: 0,
|
||||||
max: maxVal,
|
max: 23,
|
||||||
// divisions: bizEndHour - bizStartHour > 0 ? bizEndHour - bizStartHour : 1,
|
divisions: 23,
|
||||||
onChanged: (val) {
|
onChanged: (val) {
|
||||||
int hour = val.toInt();
|
int hour = val.toInt();
|
||||||
|
// 模拟 Controller 中的 pickTime 逻辑校验
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
final isToday =
|
final isToday =
|
||||||
controller.selectedDate.value.year == now.year &&
|
controller.selectedDate.value.year == now.year &&
|
||||||
controller.selectedDate.value.month == now.month &&
|
controller.selectedDate.value.month == now.month &&
|
||||||
controller.selectedDate.value.day == now.day;
|
controller.selectedDate.value.day == now.day;
|
||||||
|
|
||||||
// 如果是今天,判断不可选过去的时间点
|
if (isToday && hour < now.hour) {
|
||||||
if (isToday && hour < now.hour) return;
|
// 如果是今天且小时数小于当前,则忽略
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
controller.startTime.value = TimeOfDay(hour: hour, minute: 0);
|
controller.startTime.value = TimeOfDay(hour: hour, minute: 0);
|
||||||
controller.endTime.value = TimeOfDay(hour: (hour + 1) % 24, minute: 0);
|
controller.endTime.value = TimeOfDay(hour: (hour + 1) % 24, minute: 0);
|
||||||
@@ -673,7 +627,6 @@ class ReservationPage extends GetView<C_ReservationController> {
|
|||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
controller.selectedStationId.value = value;
|
controller.selectedStationId.value = value;
|
||||||
controller.resetTimeForSelectedDate();
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,8 @@ import 'dart:io';
|
|||||||
|
|
||||||
import 'package:aliyun_push_flutter/aliyun_push_flutter.dart';
|
import 'package:aliyun_push_flutter/aliyun_push_flutter.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_app_update/flutter_app_update.dart';
|
|
||||||
import 'package:flutter_app_update/result_model.dart';
|
|
||||||
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
||||||
import 'package:getx_scaffold/getx_scaffold.dart';
|
import 'package:getx_scaffold/getx_scaffold.dart';
|
||||||
import 'package:ln_jq_app/common/model/base_model.dart';
|
|
||||||
import 'package:ln_jq_app/common/styles/theme.dart';
|
import 'package:ln_jq_app/common/styles/theme.dart';
|
||||||
import 'package:ln_jq_app/pages/b_page/base_widgets/view.dart';
|
import 'package:ln_jq_app/pages/b_page/base_widgets/view.dart';
|
||||||
import 'package:ln_jq_app/pages/c_page/base_widgets/view.dart';
|
import 'package:ln_jq_app/pages/c_page/base_widgets/view.dart';
|
||||||
@@ -23,200 +20,108 @@ class HomeController extends GetxController with BaseControllerMixin {
|
|||||||
|
|
||||||
final _aliyunPush = AliyunPushFlutter();
|
final _aliyunPush = AliyunPushFlutter();
|
||||||
|
|
||||||
@override
|
|
||||||
bool get listenLifecycleEvent => true;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
|
|
||||||
// 检查是否同意过隐私政策,只有同意后才初始化推送
|
|
||||||
if (StorageService.to.isPrivacyAgreed) {
|
|
||||||
requestPermission();
|
|
||||||
initAliyunPush();
|
initAliyunPush();
|
||||||
addPushCallback();
|
addPushCallback();
|
||||||
}
|
|
||||||
|
|
||||||
FlutterNativeSplash.remove();
|
FlutterNativeSplash.remove();
|
||||||
log('page-init');
|
|
||||||
|
|
||||||
// 页面初始化后执行版本检查
|
|
||||||
checkVersionInfo();
|
|
||||||
}
|
|
||||||
|
|
||||||
String downloadUrl = "";
|
|
||||||
|
|
||||||
/// 检查 App 更新信息,增加版本号比对逻辑
|
|
||||||
void checkVersionInfo() async {
|
|
||||||
try {
|
|
||||||
final response = await HttpService.to.get('appointment/appConfig/get');
|
|
||||||
if (response != null) {
|
|
||||||
final result = BaseModel.fromJson(response.data);
|
|
||||||
if (result.code == 0 && result.data != null) {
|
|
||||||
final data = result.data as Map<String, dynamic>;
|
|
||||||
|
|
||||||
bool hasUpdate = data['hasUpdate']?.toString().toLowerCase() == "true";
|
|
||||||
bool isForce = data['isForce']?.toString().toLowerCase() == "true";
|
|
||||||
String versionName = data['versionName'] ?? "新版本";
|
|
||||||
String updateContent = data['updateContent'] ?? "优化系统性能,提升用户体验";
|
|
||||||
downloadUrl = data['downloadUrl'].toString();
|
|
||||||
|
|
||||||
// 获取服务器配置的目标构建号
|
|
||||||
int serverVersionCode =
|
|
||||||
int.tryParse(data['versionCode']?.toString() ?? "0") ?? 0;
|
|
||||||
int serverIosBuildId = int.tryParse(data['iosBuildId']?.toString() ?? "0") ?? 0;
|
|
||||||
|
|
||||||
// 获取本地当前的构建号
|
|
||||||
String currentBuildStr = await getBuildNumber();
|
|
||||||
int currentBuild = int.tryParse(currentBuildStr) ?? 0;
|
|
||||||
|
|
||||||
bool needUpdate = false;
|
|
||||||
if (GetPlatform.isAndroid) {
|
|
||||||
needUpdate = currentBuild < serverVersionCode;
|
|
||||||
} else if (GetPlatform.isIOS) {
|
|
||||||
needUpdate = currentBuild < serverIosBuildId;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果服务器标记有更新,且本地版本号确实较低,则弹出更新
|
|
||||||
if (hasUpdate && needUpdate) {
|
|
||||||
_showUpdateDialog("版本:$versionName\n\n更新内容:\n$updateContent", isForce);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
Logger.d("版本检查失败: $e");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 显示更新弹窗
|
|
||||||
void _showUpdateDialog(String content, bool isForce) {
|
|
||||||
DialogX.to.showConfirmDialog(
|
|
||||||
title: '升级提醒',
|
|
||||||
confirmText: '立即升级',
|
|
||||||
content: _buildDialogContent(content),
|
|
||||||
// 如果是强制更新,取消按钮显示为空,即隐藏
|
|
||||||
cancelText: isForce ? "" : '以后再说',
|
|
||||||
// 设置为 false,禁止点击背景和物理返回键关闭
|
|
||||||
barrierDismissible: false,
|
|
||||||
onConfirm: () {
|
|
||||||
jumpUpdateApp();
|
|
||||||
|
|
||||||
// ios如果是强制更新,点击后维持弹窗,防止用户进入 App
|
|
||||||
if (isForce && GetPlatform.isIOS) {
|
|
||||||
Future.delayed(const Duration(milliseconds: 500), () {
|
|
||||||
_showUpdateDialog(content, isForce);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildDialogContent(String content) {
|
|
||||||
return PopScope(
|
|
||||||
canPop: false, // 关键:禁止 pop
|
|
||||||
child: TextX.bodyMedium(content).padding(bottom: 16.h),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void jumpUpdateApp() {
|
|
||||||
if (GetPlatform.isIOS) {
|
|
||||||
// 跳转到 iOS 应用商店网页
|
|
||||||
openWebPage("https://apps.apple.com/cn/app/羚牛氢能/6756245815");
|
|
||||||
} else if (GetPlatform.isAndroid) {
|
|
||||||
// Android 执行下载安装流程
|
|
||||||
showAndroidDownloadDialog();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void showAndroidDownloadDialog() {
|
|
||||||
AzhonAppUpdate.listener((ResultModel model) {
|
|
||||||
if (model.type == ResultType.start) {
|
|
||||||
DialogX.to.showConfirmDialog(
|
|
||||||
content: PopScope(
|
|
||||||
canPop: false,
|
|
||||||
child: Center(
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
TextX.bodyMedium('升级中...').padding(bottom: 45.h),
|
|
||||||
CircularProgressIndicator(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
confirmText: '',
|
|
||||||
cancelText: "",
|
|
||||||
barrierDismissible: false,
|
|
||||||
);
|
|
||||||
} else if (model.type == ResultType.done) {
|
|
||||||
Get.back();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
UpdateModel model = UpdateModel(downloadUrl, "xll.apk", "logo", '正在下载最新版本...');
|
|
||||||
AzhonAppUpdate.update(model);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据登录状态和登录渠道返回不同的首页
|
// 根据登录状态和登录渠道返回不同的首页
|
||||||
Widget getHomePage() {
|
Widget getHomePage() {
|
||||||
|
requestPermission();
|
||||||
|
//登录状态跳转
|
||||||
if (StorageService.to.isLoggedIn) {
|
if (StorageService.to.isLoggedIn) {
|
||||||
|
// 如果已登录,再判断是哪个渠道
|
||||||
if (StorageService.to.loginChannel == LoginChannel.station) {
|
if (StorageService.to.loginChannel == LoginChannel.station) {
|
||||||
return B_BaseWidgetsPage();
|
return B_BaseWidgetsPage(); // 站点首页
|
||||||
} else if (StorageService.to.loginChannel == LoginChannel.driver) {
|
} else if (StorageService.to.loginChannel == LoginChannel.driver) {
|
||||||
return BaseWidgetsPage();
|
return BaseWidgetsPage(); // 司机首页
|
||||||
} else {
|
} else {
|
||||||
return LoginPage();
|
return LoginPage();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return LoginPage();
|
// 未登录,直接去登录页
|
||||||
|
return BaseWidgetsPage();
|
||||||
|
// return LoginPage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void requestPermission() async {
|
void requestPermission() async {
|
||||||
PermissionStatus status = await Permission.notification.status;
|
PermissionStatus status = await Permission.notification.status;
|
||||||
if (status.isGranted) return;
|
if (status.isGranted) {
|
||||||
|
Logger.d("通知权限已开启");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (status.isDenied) {
|
if (status.isDenied) {
|
||||||
|
// 建议此处增加一个应用内的 Rationale (解释说明) 弹窗
|
||||||
status = await Permission.notification.request();
|
status = await Permission.notification.request();
|
||||||
}
|
}
|
||||||
if (status.isGranted) {
|
if (status.isGranted) {
|
||||||
|
// 授权成功
|
||||||
Logger.d('通知已开启');
|
Logger.d('通知已开启');
|
||||||
} else if (status.isPermanentlyDenied) {
|
} else if (status.isPermanentlyDenied) {
|
||||||
Logger.d('通知权限已被拒绝,请到系统设置中开启');
|
Logger.d('通知权限已被拒绝,请到系统设置中开启');
|
||||||
|
} else if (status.isDenied) {
|
||||||
|
Logger.d('请授予通知权限,以便接收加氢站通知');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
///推送相关初始化 (保持原样)
|
///推送相关
|
||||||
Future<void> initAliyunPush() async {
|
Future<void> initAliyunPush() async {
|
||||||
|
// 1. 配置分离:建议将 Key 提取到外部或配置文件中
|
||||||
final String appKey = Platform.isIOS ? AppTheme.ios_key : AppTheme.android_key;
|
final String appKey = Platform.isIOS ? AppTheme.ios_key : AppTheme.android_key;
|
||||||
final String appSecret = Platform.isIOS
|
final String appSecret = Platform.isIOS
|
||||||
? AppTheme.ios_appsecret
|
? AppTheme.ios_appsecret
|
||||||
: AppTheme.android_appsecret;
|
: AppTheme.android_appsecret;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// 初始化推送
|
||||||
final result = await _aliyunPush.initPush(appKey: appKey, appSecret: appSecret);
|
final result = await _aliyunPush.initPush(appKey: appKey, appSecret: appSecret);
|
||||||
if (result['code'] != kAliyunPushSuccessCode) return;
|
|
||||||
|
if (result['code'] != kAliyunPushSuccessCode) {
|
||||||
|
Logger.d('初始化推送失败: ${result['code']} - ${result['errorMsg']}');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.d('阿里云推送初始化成功');
|
||||||
|
// 分平台配置
|
||||||
if (Platform.isIOS) {
|
if (Platform.isIOS) {
|
||||||
await _setupIOSConfig();
|
await _setupIOSConfig();
|
||||||
} else if (Platform.isAndroid) {
|
} else if (Platform.isAndroid) {
|
||||||
await _setupAndroidConfig();
|
await _setupAndroidConfig();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Logger.d('初始化异常: $e');
|
Logger.d('初始化过程中发生异常: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// iOS 专属配置
|
||||||
Future<void> _setupIOSConfig() async {
|
Future<void> _setupIOSConfig() async {
|
||||||
await _aliyunPush.showIOSNoticeWhenForeground(true);
|
final res = await _aliyunPush.showIOSNoticeWhenForeground(true);
|
||||||
|
if (res['code'] == kAliyunPushSuccessCode) {
|
||||||
|
Logger.d('iOS 前台通知展示已开启');
|
||||||
|
} else {
|
||||||
|
Logger.d('iOS 前台通知开启失败: ${res['errorMsg']}');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Android 专属配置
|
||||||
Future<void> _setupAndroidConfig() async {
|
Future<void> _setupAndroidConfig() async {
|
||||||
await _aliyunPush.setNotificationInGroup(true);
|
await _aliyunPush.setNotificationInGroup(true);
|
||||||
await _aliyunPush.createAndroidChannel(
|
final res = await _aliyunPush.createAndroidChannel(
|
||||||
"xll_push_android",
|
"xll_push_android",
|
||||||
'新消息通知',
|
'新消息通知',
|
||||||
4,
|
4,
|
||||||
'用于接收加氢站实时状态提醒',
|
'用于接收加氢站实时状态提醒',
|
||||||
);
|
);
|
||||||
|
if (res['code'] == kAliyunPushSuccessCode) {
|
||||||
|
Logger.d('Android 通知通道创建成功');
|
||||||
|
} else {
|
||||||
|
Logger.d('Android 通道创建失败: ${res['code']} - ${res['errorMsg']}');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void addPushCallback() {
|
void addPushCallback() {
|
||||||
@@ -235,23 +140,40 @@ class HomeController extends GetxController with BaseControllerMixin {
|
|||||||
|
|
||||||
Future<void> _onAndroidNotificationClickedWithNoAction(
|
Future<void> _onAndroidNotificationClickedWithNoAction(
|
||||||
Map<dynamic, dynamic> message,
|
Map<dynamic, dynamic> message,
|
||||||
) async {}
|
) async {
|
||||||
|
Logger.d('onAndroidNotificationClickedWithNoAction ====> $message');
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _onAndroidNotificationReceivedInApp(Map<dynamic, dynamic> message) async {}
|
Future<void> _onAndroidNotificationReceivedInApp(Map<dynamic, dynamic> message) async {
|
||||||
|
Logger.d('onAndroidNotificationReceivedInApp ====> $message');
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _onMessage(Map<dynamic, dynamic> message) async {}
|
Future<void> _onMessage(Map<dynamic, dynamic> message) async {
|
||||||
|
Logger.d('onMessage ====> $message');
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _onNotification(Map<dynamic, dynamic> message) async {}
|
Future<void> _onNotification(Map<dynamic, dynamic> message) async {
|
||||||
|
Logger.d('onNotification ====> $message');
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _onNotificationOpened(Map<dynamic, dynamic> message) async {
|
Future<void> _onNotificationOpened(Map<dynamic, dynamic> message) async {
|
||||||
|
Logger.d('onNotificationOpened ====> $message');
|
||||||
await Get.to(() => const MessagePage());
|
await Get.to(() => const MessagePage());
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onNotificationRemoved(Map<dynamic, dynamic> message) async {}
|
Future<void> _onNotificationRemoved(Map<dynamic, dynamic> message) async {
|
||||||
|
Logger.d('onNotificationRemoved ====> $message');
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _onIOSChannelOpened(Map<dynamic, dynamic> message) async {}
|
Future<void> _onIOSChannelOpened(Map<dynamic, dynamic> message) async {
|
||||||
|
Logger.d('onIOSChannelOpened ====> $message');
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _onIOSRegisterDeviceTokenSuccess(Map<dynamic, dynamic> message) async {}
|
Future<void> _onIOSRegisterDeviceTokenSuccess(Map<dynamic, dynamic> message) async {
|
||||||
|
Logger.d('onIOSRegisterDeviceTokenSuccess ====> $message');
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _onIOSRegisterDeviceTokenFailed(Map<dynamic, dynamic> message) async {}
|
Future<void> _onIOSRegisterDeviceTokenFailed(Map<dynamic, dynamic> message) async {
|
||||||
|
Logger.d('onIOSRegisterDeviceTokenFailed====> $message');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,13 +5,18 @@ import 'package:ln_jq_app/pages/home/controller.dart';
|
|||||||
class HomePage extends GetView<HomeController> {
|
class HomePage extends GetView<HomeController> {
|
||||||
const HomePage({super.key});
|
const HomePage({super.key});
|
||||||
|
|
||||||
|
// 主视图
|
||||||
|
Widget _buildView() {
|
||||||
|
return <Widget>[Text('主页面')].toColumn(mainAxisSize: MainAxisSize.min).center();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return GetBuilder<HomeController>(
|
return GetBuilder<HomeController>(
|
||||||
init: HomeController(),
|
init: HomeController(),
|
||||||
id: 'home',
|
id: 'home',
|
||||||
builder: (_) {
|
builder: (_) {
|
||||||
return Scaffold(body: controller.getHomePage());
|
return controller.getHomePage();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:getx_scaffold/getx_scaffold.dart';
|
import 'package:getx_scaffold/getx_scaffold.dart';
|
||||||
import 'package:permission_handler/permission_handler.dart';
|
|
||||||
import 'package:ln_jq_app/common/login_util.dart';
|
import 'package:ln_jq_app/common/login_util.dart';
|
||||||
import 'package:ln_jq_app/common/model/base_model.dart';
|
import 'package:ln_jq_app/common/model/base_model.dart';
|
||||||
import 'package:ln_jq_app/common/model/vehicle_info.dart';
|
import 'package:ln_jq_app/common/model/vehicle_info.dart';
|
||||||
@@ -18,8 +17,6 @@ import 'package:ln_jq_app/pages/login/controller.dart';
|
|||||||
import 'package:ln_jq_app/pages/url_host/view.dart';
|
import 'package:ln_jq_app/pages/url_host/view.dart';
|
||||||
import 'package:ln_jq_app/storage_service.dart';
|
import 'package:ln_jq_app/storage_service.dart';
|
||||||
|
|
||||||
import '../c_page/message/view.dart';
|
|
||||||
|
|
||||||
class LoginPage extends StatefulWidget {
|
class LoginPage extends StatefulWidget {
|
||||||
const LoginPage({super.key});
|
const LoginPage({super.key});
|
||||||
|
|
||||||
@@ -33,7 +30,6 @@ class _LoginPageState extends State<LoginPage> with SingleTickerProviderStateMix
|
|||||||
bool _obscureText = true;
|
bool _obscureText = true;
|
||||||
bool _rememberPassword = true;
|
bool _rememberPassword = true;
|
||||||
bool _credentialsLoaded = false;
|
bool _credentialsLoaded = false;
|
||||||
bool isPushInitialized = false;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -150,8 +146,7 @@ class _LoginPageState extends State<LoginPage> with SingleTickerProviderStateMix
|
|||||||
|
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
buildAgreement(),
|
buildAgreement(),
|
||||||
const SizedBox(height: 80),
|
const SizedBox(height: 40),
|
||||||
_buildFooterSlogan()
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -160,6 +155,7 @@ class _LoginPageState extends State<LoginPage> with SingleTickerProviderStateMix
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Positioned(left: 0, right: 0, bottom: 33.h, child: _buildFooterSlogan()),
|
||||||
if (AppTheme.is_show_host)
|
if (AppTheme.is_show_host)
|
||||||
Positioned(
|
Positioned(
|
||||||
top: 40.h,
|
top: 40.h,
|
||||||
@@ -208,9 +204,9 @@ class _LoginPageState extends State<LoginPage> with SingleTickerProviderStateMix
|
|||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
const Text(
|
const Text(
|
||||||
"欢迎使用小羚羚 ",
|
"欢迎使用 ",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 22,
|
fontSize: 24,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
color: Color.fromRGBO(51, 51, 51, 1),
|
color: Color.fromRGBO(51, 51, 51, 1),
|
||||||
),
|
),
|
||||||
@@ -392,28 +388,13 @@ class _LoginPageState extends State<LoginPage> with SingleTickerProviderStateMix
|
|||||||
content: _buildDialogContent(),
|
content: _buildDialogContent(),
|
||||||
confirmText: '同意',
|
confirmText: '同意',
|
||||||
cancelText: '拒绝',
|
cancelText: '拒绝',
|
||||||
onConfirm: () async {
|
onConfirm: () {
|
||||||
_isAgreed = true;
|
_isAgreed = true;
|
||||||
controller.updateUi();
|
controller.updateUi();
|
||||||
|
|
||||||
// 保存隐私政策同意状态
|
|
||||||
await StorageService.to.savePrivacyAgreed(true);
|
|
||||||
|
|
||||||
// 申请通知权限
|
|
||||||
await _requestNotificationPermission();
|
|
||||||
|
|
||||||
// 初始化阿里云推送
|
|
||||||
await _initPushService();
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果已经同意过,但推送还没初始化,则初始化
|
|
||||||
if (!isPushInitialized) {
|
|
||||||
await _initPushService();
|
|
||||||
}
|
|
||||||
|
|
||||||
_tabController.index == 0
|
_tabController.index == 0
|
||||||
? _handleDriverLogin(controller)
|
? _handleDriverLogin(controller)
|
||||||
: _handleStationLogin(controller);
|
: _handleStationLogin(controller);
|
||||||
@@ -555,62 +536,6 @@ class _LoginPageState extends State<LoginPage> with SingleTickerProviderStateMix
|
|||||||
addAlias(identifier);
|
addAlias(identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 申请通知权限
|
|
||||||
Future<void> _requestNotificationPermission() async {
|
|
||||||
final PermissionStatus status = await Permission.notification.request();
|
|
||||||
if (status.isGranted) {
|
|
||||||
Logger.d('通知权限已授予');
|
|
||||||
} else if (status.isPermanentlyDenied) {
|
|
||||||
Logger.d('通知权限被永久拒绝');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化推送服务
|
|
||||||
Future<void> _initPushService() async {
|
|
||||||
try {
|
|
||||||
final _aliyunPush = AliyunPushFlutter();
|
|
||||||
|
|
||||||
// 初始化推送
|
|
||||||
final String appKey = Platform.isIOS ? AppTheme.ios_key : AppTheme.android_key;
|
|
||||||
final String appSecret = Platform.isIOS
|
|
||||||
? AppTheme.ios_appsecret
|
|
||||||
: AppTheme.android_appsecret;
|
|
||||||
|
|
||||||
final result = await _aliyunPush.initPush(appKey: appKey, appSecret: appSecret);
|
|
||||||
if (result['code'] != kAliyunPushSuccessCode) {
|
|
||||||
Logger.d('推送初始化失败: ${result['errorMsg']}');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 配置平台特定设置
|
|
||||||
if (Platform.isIOS) {
|
|
||||||
await _aliyunPush.showIOSNoticeWhenForeground(true);
|
|
||||||
} else if (Platform.isAndroid) {
|
|
||||||
await _aliyunPush.setNotificationInGroup(true);
|
|
||||||
await _aliyunPush.createAndroidChannel(
|
|
||||||
"xll_push_android",
|
|
||||||
'新消息通知',
|
|
||||||
4,
|
|
||||||
'用于接收加氢站实时状态提醒',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加推送回调
|
|
||||||
_aliyunPush.addMessageReceiver(
|
|
||||||
onNotificationOpened: _onNotificationOpened,
|
|
||||||
);
|
|
||||||
|
|
||||||
isPushInitialized = true;
|
|
||||||
Logger.d('推送服务初始化成功');
|
|
||||||
} catch (e) {
|
|
||||||
Logger.d('推送服务初始化异常: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _onNotificationOpened(Map<dynamic, dynamic> message) async {
|
|
||||||
await Get.to(() => const MessagePage());
|
|
||||||
}
|
|
||||||
|
|
||||||
final _aliyunPush = AliyunPushFlutter();
|
final _aliyunPush = AliyunPushFlutter();
|
||||||
|
|
||||||
void addAlias(String alias) async {
|
void addAlias(String alias) async {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class WelcomePage extends GetView<WelcomeController> {
|
|||||||
right: 0,
|
right: 0,
|
||||||
child: Image.asset(
|
child: Image.asset(
|
||||||
'assets/images/welcome.png',
|
'assets/images/welcome.png',
|
||||||
fit: BoxFit.cover
|
fit: BoxFit.fill
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -25,14 +25,11 @@ class StorageService extends GetxService {
|
|||||||
static const String _stationAccountKey = 'station_account';
|
static const String _stationAccountKey = 'station_account';
|
||||||
static const String _stationPasswordKey = 'station_password';
|
static const String _stationPasswordKey = 'station_password';
|
||||||
|
|
||||||
// 新增:用于标记”绑定车辆”弹窗是否已在本会话中显示过
|
// 新增:用于标记“绑定车辆”弹窗是否已在本会话中显示过
|
||||||
static const String _bindDialogShownKey = 'bind_vehicle_dialog_shown';
|
static const String _bindDialogShownKey = 'bind_vehicle_dialog_shown';
|
||||||
|
|
||||||
static const String _hostUrlKey = 'host_url';
|
static const String _hostUrlKey = 'host_url';
|
||||||
|
|
||||||
// 隐私政策相关
|
|
||||||
static const String _privacyAgreedKey = 'privacy_agreed';
|
|
||||||
|
|
||||||
static StorageService get to => Get.find();
|
static StorageService get to => Get.find();
|
||||||
|
|
||||||
Future<StorageService> init() async {
|
Future<StorageService> init() async {
|
||||||
@@ -66,12 +63,9 @@ class StorageService extends GetxService {
|
|||||||
|
|
||||||
String? get stationPassword => _box.read<String?>(_stationPasswordKey);
|
String? get stationPassword => _box.read<String?>(_stationPasswordKey);
|
||||||
|
|
||||||
// 新增:获取”绑定车辆”弹窗是否已显示的标志
|
// 新增:获取“绑定车辆”弹窗是否已显示的标志
|
||||||
bool get hasShownBindVehicleDialog => _box.read<bool>(_bindDialogShownKey) ?? false;
|
bool get hasShownBindVehicleDialog => _box.read<bool>(_bindDialogShownKey) ?? false;
|
||||||
|
|
||||||
// 获取隐私政策是否已同意
|
|
||||||
bool get isPrivacyAgreed => _box.read<bool>(_privacyAgreedKey) ?? false;
|
|
||||||
|
|
||||||
VehicleInfo? get vehicleInfo {
|
VehicleInfo? get vehicleInfo {
|
||||||
final vehicleJson = _box.read<String?>(_vehicleInfoKey);
|
final vehicleJson = _box.read<String?>(_vehicleInfoKey);
|
||||||
if (vehicleJson != null) {
|
if (vehicleJson != null) {
|
||||||
@@ -116,11 +110,6 @@ class StorageService extends GetxService {
|
|||||||
await _box.write(_stationPasswordKey, password);
|
await _box.write(_stationPasswordKey, password);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存隐私政策同意状态
|
|
||||||
Future<void> savePrivacyAgreed(bool agreed) async {
|
|
||||||
await _box.write(_privacyAgreedKey, agreed);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 新增:标记“绑定车辆”弹窗已显示
|
// 新增:标记“绑定车辆”弹窗已显示
|
||||||
Future<void> markBindVehicleDialogAsShown() async {
|
Future<void> markBindVehicleDialogAsShown() async {
|
||||||
await _box.write(_bindDialogShownKey, true);
|
await _box.write(_bindDialogShownKey, true);
|
||||||
|
|||||||
@@ -16,12 +16,12 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
|||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
# In Windows, build-name is used as the major, minor, and patch parts
|
# In Windows, build-name is used as the major, minor, and patch parts
|
||||||
# of the product and file versions while build-number is used as the build suffix.
|
# of the product and file versions while build-number is used as the build suffix.
|
||||||
version: 1.2.4+7
|
version: 1.2.3+6
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.9.0
|
sdk: ^3.9.0
|
||||||
|
|
||||||
# Dependencies specify other packages tha。。。t your package needs in order to work.
|
# Dependencies specify other packages that your package needs in order to work.
|
||||||
# To automatically upgrade your package dependencies to the latest versions
|
# To automatically upgrade your package dependencies to the latest versions
|
||||||
# consider running `flutter pub upgrade --major-versions`. Alternatively,
|
# consider running `flutter pub upgrade --major-versions`. Alternatively,
|
||||||
# dependencies can be manually updated by changing the version numbers below to
|
# dependencies can be manually updated by changing the version numbers below to
|
||||||
@@ -52,9 +52,8 @@ dependencies:
|
|||||||
geolocator: ^14.0.2 # 获取精确定位
|
geolocator: ^14.0.2 # 获取精确定位
|
||||||
aliyun_push_flutter: ^1.3.6
|
aliyun_push_flutter: ^1.3.6
|
||||||
pull_to_refresh: ^2.0.0
|
pull_to_refresh: ^2.0.0
|
||||||
flutter_app_update: ^3.2.2
|
|
||||||
saver_gallery: ^4.0.0
|
|
||||||
path_provider: ^2.1.5
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
@@ -65,7 +64,6 @@ dev_dependencies:
|
|||||||
|
|
||||||
dependency_overrides:
|
dependency_overrides:
|
||||||
intl: 0.19.0
|
intl: 0.19.0
|
||||||
device_info_plus: ^12.3.0
|
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
|
|
||||||
|
|||||||