admin
2021-11-25 70a344485bd0c9b68ac91f72ed23ec5bfa998b09
功能完善
1个文件已删除
23个文件已修改
28个文件已添加
3866 ■■■■ 已修改文件
android/app/build.gradle 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
android/app/src/main/java/com/yeshi/location/MainActivity.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
android/app/src/main/java/com/yeshi/location/plugins/FlutterAliyunPhoneNumberAuthPlugins.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
android/app/src/main/java/com/yeshi/location/plugins/POISearchFlutterPlugins.java 110 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
android/build.gradle 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
assets/images/map/icon_travel_end.png 补丁 | 查看 | 原始文档 | blame | 历史
assets/images/map/icon_travel_end_1.png 补丁 | 查看 | 原始文档 | blame | 历史
assets/images/user/icon_user_add.png 补丁 | 查看 | 原始文档 | blame | 历史
assets/images/user/icon_user_location.png 补丁 | 查看 | 原始文档 | blame | 历史
assets/images/user/icon_user_type_daughter.png 补丁 | 查看 | 原始文档 | blame | 历史
assets/images/user/icon_user_type_daughter_son.png 补丁 | 查看 | 原始文档 | blame | 历史
assets/images/user/icon_user_type_father.png 补丁 | 查看 | 原始文档 | blame | 历史
assets/images/user/icon_user_type_father_mother.png 补丁 | 查看 | 原始文档 | blame | 历史
assets/images/user/icon_user_type_husband.png 补丁 | 查看 | 原始文档 | blame | 历史
assets/images/user/icon_user_type_husband_wife.png 补丁 | 查看 | 原始文档 | blame | 历史
assets/images/user/icon_user_type_mother.png 补丁 | 查看 | 原始文档 | blame | 历史
assets/images/user/icon_user_type_other.png 补丁 | 查看 | 原始文档 | blame | 历史
assets/images/user/icon_user_type_son.png 补丁 | 查看 | 原始文档 | blame | 历史
assets/images/user/icon_user_type_wife.png 补丁 | 查看 | 原始文档 | blame | 历史
lib/api/http.dart 355 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
lib/main.dart 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
lib/model/common/http_model.dart 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
lib/model/map/location_model.dart 183 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
lib/model/map/location_user_model.dart 106 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
lib/model/map/map_model.dart 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
lib/model/user/user_info.dart 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
lib/ui/main/location.dart 787 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
lib/ui/main/mine.dart 118 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
lib/ui/main/travel_main.dart 150 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
lib/ui/map/location_search.dart 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
lib/ui/map/travel.dart 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
lib/ui/mine/add_location_person.dart 759 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
lib/ui/mine/login.dart 100 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
lib/ui/mine/settings.dart 215 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
lib/ui/widget/base_ui.dart 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
lib/ui/widget/capture.dart 29 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
lib/ui/widget/custom.dart 88 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
lib/ui/widget/dialog.dart 130 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
lib/ui/widget/map_marker.dart 149 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
lib/utils/app_util.dart 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
lib/utils/encrypt_util.dart 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
lib/utils/event_bus_util.dart 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
lib/utils/global.dart 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
lib/utils/location_util.dart 63 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
lib/utils/map_util.dart 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
lib/utils/push_util.dart 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
lib/utils/search_util.dart 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
lib/utils/string_util.dart 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
lib/utils/ui_utils.dart 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
lib/utils/user_util.dart 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pubspec.lock 56 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pubspec.yaml 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
android/app/build.gradle
@@ -49,6 +49,11 @@
            //兼容64位与32位系统
            abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
        }
        manifestPlaceholders = [
                JPUSH_PKGNAME: applicationId,
                JPUSH_APPKEY : "987d3d50f209994fa522d001", //JPush 上注册的包名对应的 Appkey.
                JPUSH_CHANNEL: "developer-default"] //暂时填写默认值即可.
    }
    signingConfigs {
        release {
@@ -57,7 +62,7 @@
            storeFile file(keystoreProperties['storeFile'])
            storePassword keystoreProperties['storePassword']
        }
        debug{
        debug {
            keyAlias keystoreProperties['keyAlias']
            keyPassword keystoreProperties['keyPassword']
            storeFile file(keystoreProperties['storeFile'])
@@ -85,6 +90,7 @@
    api files('lib/umeng-apm-armeabi-v1.5.2.aar')
    api files('lib/umeng-common-9.4.4.jar')
    api files('lib/umeng-asms-armeabi-v1.4.1.aar')
    //阿里云手机号认证
    implementation files('lib/crashshield-2.1.2-release.aar')
    implementation files('lib/logger-2.1.2-release.aar')
android/app/src/main/java/com/yeshi/location/MainActivity.java
@@ -5,6 +5,7 @@
import com.yeshi.location.plugins.FlutterAliyunPhoneNumberAuthPlugins;
import com.yeshi.location.plugins.InitAppFlutterPlugins;
import com.yeshi.location.plugins.LoginFlutterPlugins;
import com.yeshi.location.plugins.POISearchFlutterPlugins;
import com.yeshi.location.plugins.ShareFlutterPlugins;
import androidx.annotation.NonNull;
@@ -33,5 +34,7 @@
        InitAppFlutterPlugins.registerWith(flutterEngine.getDartExecutor().getBinaryMessenger());
        //登录插件
        LoginFlutterPlugins.registerWith(MainActivity.this,flutterEngine.getDartExecutor().getBinaryMessenger());
        //POI搜索
        POISearchFlutterPlugins.registerWith(MainActivity.this,flutterEngine.getDartExecutor().getBinaryMessenger());
    }
}
android/app/src/main/java/com/yeshi/location/plugins/FlutterAliyunPhoneNumberAuthPlugins.java
@@ -68,6 +68,8 @@
                break;
            case "startLogin":
                mPhoneNumberAuthHelper.getLoginToken(activity, 5000);
            case "closeLogin":
                mPhoneNumberAuthHelper.quitLoginPage();
                break;
        }
android/app/src/main/java/com/yeshi/location/plugins/POISearchFlutterPlugins.java
New file
@@ -0,0 +1,110 @@
package com.yeshi.location.plugins;
import android.app.Activity;
import android.graphics.Color;
import android.util.Log;
import android.widget.ImageView;
import com.baidu.mapapi.model.LatLng;
import com.baidu.mapapi.search.poi.OnGetPoiSearchResultListener;
import com.baidu.mapapi.search.poi.PoiDetailResult;
import com.baidu.mapapi.search.poi.PoiDetailSearchResult;
import com.baidu.mapapi.search.poi.PoiIndoorResult;
import com.baidu.mapapi.search.poi.PoiNearbySearchOption;
import com.baidu.mapapi.search.poi.PoiResult;
import com.baidu.mapapi.search.poi.PoiSearch;
import com.mobile.auth.gatewayauth.AuthUIConfig;
import com.mobile.auth.gatewayauth.PhoneNumberAuthHelper;
import com.mobile.auth.gatewayauth.ResultCode;
import com.mobile.auth.gatewayauth.TokenResultListener;
import com.mobile.auth.gatewayauth.model.TokenRet;
import com.yeshi.location.R;
import com.yeshi.location.utils.DimenUtils;
import java.util.HashMap;
import java.util.Map;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import io.flutter.plugin.common.BasicMessageChannel;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.StandardMessageCodec;
public class POISearchFlutterPlugins implements BasicMessageChannel.MessageHandler<Object> {
    private static final String TAG = "AliyunPhoneNumberAuth";
    private final Activity activity;
    private final BasicMessageChannel<Object> messageChannel;
    private BasicMessageChannel.Reply<Object> reply;
    private PoiSearch mPoiSearch = PoiSearch.newInstance();
    private POISearchFlutterPlugins(Activity activity, BinaryMessenger messager) {
        this.activity = activity;
        this.messageChannel = new BasicMessageChannel<Object>(messager, "POISearch", StandardMessageCodec.INSTANCE);
        messageChannel.setMessageHandler(this);
    }
    public static POISearchFlutterPlugins registerWith(Activity activity, BinaryMessenger messager) {
        return new POISearchFlutterPlugins(activity, messager);
    }
    @Override
    public void onMessage(@Nullable Object message, @NonNull BasicMessageChannel.Reply<Object> reply) {
        this.reply = reply;
        Map<String, String> arguments = (Map<String, String>) message;
        String method = arguments.get("method");
        switch (method) {
            //初始化
            case "searchNearBy":
                mPoiSearch = PoiSearch.newInstance();
                mPoiSearch.searchNearby(new PoiNearbySearchOption()
                        .location(new LatLng(39.915446, 116.403869))
                        .radius(1000)
                        //支持多个关键字并集检索,不同关键字间以$符号分隔,最多支持10个关键字检索。如:”银行$酒店”
                        .keyword("餐厅")
                        .pageNum(0));
                mPoiSearch.setOnGetPoiSearchResultListener(new OnGetPoiSearchResultListener() {
                    @Override
                    public void onGetPoiResult(PoiResult poiResult) {
                        Map<String, Object> map = new HashMap<>();
                        map.put("code", 0);
                        map.put("data", poiResult.getAllPoi());
                        reply.reply(map);
                    }
                    @Override
                    public void onGetPoiDetailResult(PoiDetailResult poiDetailResult) {
                    }
                    @Override
                    public void onGetPoiDetailResult(PoiDetailSearchResult poiDetailSearchResult) {
                    }
                    @Override
                    public void onGetPoiIndoorResult(PoiIndoorResult poiIndoorResult) {
                    }
                });
                break;
        }
    }
    /**
     * 向Dart发送消息,并接受Dart的反馈
     *
     * @param message  要给Dart发送的消息内容
     * @param callback 来自Dart的反馈
     */
    void send(String message, BasicMessageChannel.Reply<Object> callback) {
        messageChannel.send(message, callback);
    }
}
android/build.gradle
@@ -5,6 +5,7 @@
    }
    dependencies {
        classpath 'com.google.gms:google-services:4.3.4'
        classpath 'com.android.tools.build:gradle:4.1.0'
    }
}
assets/images/map/icon_travel_end.png
assets/images/map/icon_travel_end_1.png
assets/images/user/icon_user_add.png
assets/images/user/icon_user_location.png
assets/images/user/icon_user_type_daughter.png
assets/images/user/icon_user_type_daughter_son.png
assets/images/user/icon_user_type_father.png
assets/images/user/icon_user_type_father_mother.png
assets/images/user/icon_user_type_husband.png
assets/images/user/icon_user_type_husband_wife.png
assets/images/user/icon_user_type_mother.png
assets/images/user/icon_user_type_other.png
assets/images/user/icon_user_type_son.png
assets/images/user/icon_user_type_wife.png
lib/api/http.dart
New file
@@ -0,0 +1,355 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:device_info/device_info.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:locations/model/common/http_model.dart';
import 'package:locations/model/map/location_model.dart';
import 'package:locations/model/map/location_user_model.dart';
import 'package:locations/ui/widget/dialog.dart';
import 'package:locations/utils/encrypt_util.dart';
import 'package:locations/utils/string_util.dart';
import 'package:locations/utils/user_util.dart';
typedef OnHttpRequestFinish = void Function(HttpRequestResult result);
typedef OnHttpRequestStart = void Function();
_showLoading(BuildContext context) {
  //开启加载框
  showGeneralDialog(
      context: context,
      pageBuilder: (BuildContext buildContext, Animation<double> animation,
          Animation<double> secondaryAnimation) {
        return LoadingDialog("");
      });
}
_dismissDialog(BuildContext context) {
  Navigator.pop(context);
}
class HttpUtil {
  static AndroidDeviceInfo? _androidInfo;
  static IosDeviceInfo? _iosInfo;
  static _getSign(Map<String, dynamic> params) {
    List list = [];
    //签名
    params.forEach((key, value) {
      list.add("$key=$value");
    });
    //排序
    list.sort();
    String signStr = "";
    list.forEach((element) {
      signStr += element + "&";
    });
    signStr += "@#\$^234AB&c&fg&.==888";
    return EncryptUtil.MD5(signStr);
  }
  static Future<HttpRequestResult> baseRequest(String api,
      Map<String, dynamic> params, OnHttpRequestStart? onStart) async {
    // params ??= {};
    if (Platform.isAndroid) {
      if (_androidInfo == null) {
        DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
        _androidInfo = await deviceInfo.androidInfo;
      }
    } else if (Platform.isIOS) {
      if (_iosInfo == null) {
        DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
        _iosInfo = await deviceInfo.iosInfo;
      }
    }
    print("androidId:${_androidInfo!.androidId}");
    print("device:${_androidInfo!.device}");
    print("type:${_androidInfo!.type}");
    //添加附加参数
    params["timestamp"] = DateTime.now().millisecondsSinceEpoch.toString();
    params["platform"] = Platform.isAndroid ? "android" : "ios";
    params["packages"] = "com.dw.zzql";
    if (Platform.isAndroid) {
      params["device"] = _androidInfo!.device;
      params["osVersion"] = _androidInfo!.version.release;
    } else if (Platform.isIOS) {
      params["device"] = _iosInfo!.identifierForVendor;
    }
    params["sign"] = _getSign(params);
    var httpClient = HttpClient();
    httpClient.connectionTimeout = const Duration(seconds: 10);
    var uri = Uri(
        scheme: "http",
        host: "192.168.3.122",
        path: api,
        port: 8082,
        queryParameters: params);
    print("uri:$uri");
    if (onStart != null) {
      onStart();
    }
    try {
      HttpClientRequest request = await httpClient.postUrl(uri);
      var response = await request.close();
      if (response.statusCode == HttpStatus.ok) {
        String result = await response.transform(const Utf8Decoder()).join();
        return HttpRequestResult(true, jsonDecode(result));
      } else {
        return HttpRequestResult(true, null, msg: "网络请求失败");
      }
    } on TimeoutException catch (_) {
      return HttpRequestResult(true, null, msg: "网络请求超时");
    } on SocketException catch (_) {
      Fluttertoast.showToast(msg: "网络请求出错");
      return HttpRequestResult(true, null, msg: "网络请求出错");
    }
  }
}
class UserApiUtil {
  ///验证码发送
  static Future<Map<String, dynamic>?> sendSMS(
      BuildContext context, String phone) async {
    var result =
        await HttpUtil.baseRequest("/api/v1/sms/sendSMS", {"phone": phone}, () {
      _showLoading(context);
    });
    _dismissDialog(context);
    if (result.success) {
      return result.data;
    }
    return null;
  }
  ///登录
  static Future<Map<String, dynamic>?> login(
      BuildContext context, String phone, String vcode, String token) async {
    Map<String, dynamic> params = {};
    params["phone"] = phone;
    if (!StringUtil.isNullOrEmpty(vcode)) {
      params["vcode"] = vcode;
    }
    if (!StringUtil.isNullOrEmpty(token)) {
      params["token"] = token;
    }
    var result =
        await HttpUtil.baseRequest("/api/v1/user/loginPhone", params, () {
      _showLoading(context);
    });
    _dismissDialog(context);
    if (result.success) {
      return result.data;
    }
    return null;
  }
  ///获取用户信息
  static Future<Map<String, dynamic>?> getUserInfo(
      BuildContext context, int uid) async {
    Map<String, dynamic> params = {};
    params["uid"] = uid.toString();
    var result =
        await HttpUtil.baseRequest("/api/v1/user/getUserInfo", params, () {});
    if (result.success) {
      return result.data;
    }
    return null;
  }
  ///退出登录
  static Future<Map<String, dynamic>?> logout(
      BuildContext context, int? uid) async {
    Map<String, dynamic> params = {};
    params["uid"] = uid.toString();
    var result =
        await HttpUtil.baseRequest("/api/v1/user/logout", params, () {});
    if (result.success) {
      return result.data;
    }
    return null;
  }
}
class LocationApiUtil {
  ///上传位置
  static Future<Map<String, dynamic>?> uploadLocation(
      BaiduLocation location) async {
    var uid = await UserUtil.getUid();
    var result = await HttpUtil.baseRequest(
        "/api/v1/location/uploadLocation",
        {
          "latitude": location.latitude.toString(),
          "longitude": location.longitude.toString(),
          "address": location.address,
          "locationDetail": location.locationDetail,
          "uid": uid.toString()
        },
        () {});
    if (result.success) {
      return result.data;
    }
    return null;
  }
//获取位置
  static Future<UserLocationInfo?> getLocation(String? targetUid) async {
    var uid = await UserUtil.getUid();
    var params = {"uid": uid.toString()};
    if (targetUid != null) {
      params["targetUid"] = targetUid;
    }
    var result = await HttpUtil.baseRequest(
        "/api/v1/location/getLocation", params, () {});
    if (result.success) {
      if (result.data!["code"] == 0) {
        return UserLocationInfo.fromJson(result.data!["data"]);
      } else {
        Fluttertoast.showToast(msg: result.data!["msg"]);
      }
    }
    return null;
  }
  //获取定位对象列表
  static Future<Map<String, dynamic>?> getLocationUsers(
      int uid, int page) async {
    var params = {"uid": uid.toString(), "page": page.toString()};
    var result = await HttpUtil.baseRequest(
        "/api/v1/location/getLocationUsers", params, () {});
    if (result.success) {
      return result.data;
    }
    return null;
  }
  //添加定位对象
  static Future<Map<String, dynamic>?> addLocationUser(BuildContext context,
      int uid, String? name, String phone, LocationUserType type) async {
    var params = {"uid": uid.toString()};
    if (name != null) {
      params["name"] = name;
    }
    params["phone"] = phone;
    params["type"] =
        type.toString().substring(type.toString().indexOf(".") + 1);
    var result = await HttpUtil.baseRequest(
        "/api/v1/location/addLocationUser", params, () {
      _showLoading(context);
    });
    _dismissDialog(context);
    if (result.success) {
      return result.data;
    }
    return null;
  }
  //修改定位对象
  static Future<Map<String, dynamic>?> updateLocationUser(BuildContext context,
      String id, String? phone, LocationUserType? type) async {
    var uid = await UserUtil.getUid();
    var params = {"uid": uid.toString(), "id": id};
    if (phone != null) {
      params["phone"] = phone;
    }
    if (type != null) {
      params["type"] =
          type.toString().substring(type.toString().indexOf(".") + 1);
    }
    var result = await HttpUtil.baseRequest(
        "/api/v1/location/updateLocationUser", params, () {  _showLoading(context);});
    _dismissDialog(context);
    if (result.success) {
      return result.data;
    }
    return null;
  }
  //删除定位对象
  static Future<Map<String, dynamic>?> deleteLocationUser(
      BuildContext context, int uid, String? id) async {
    var params = {"uid": uid.toString(), "id": id};
    var result = await HttpUtil.baseRequest(
        "/api/v1/location/deleteLocationUser", params, () {
      _showLoading(context);
    });
    _dismissDialog(context);
    if (result.success) {
      return result.data;
    }
    return null;
  }
  //获取需要处理的定位邀请
  static Future<Map<String, dynamic>?> getInviteLocation(int uid) async {
    var params = {"uid": uid.toString()};
    var result = await HttpUtil.baseRequest(
        "/api/v1/location/getInviteLocation", params, () {});
    if (result.success) {
      return result.data;
    }
    return null;
  }
  //同意定位邀请
  static Future<Map<String, dynamic>?> agreeInviteLocation(
      int uid, String id) async {
    var params = {"uid": uid.toString(), "id": id};
    var result = await HttpUtil.baseRequest(
        "/api/v1/location/agreeInviteLocation", params, () {});
    if (result.success) {
      return result.data;
    }
    return null;
  }
  //拒绝定位邀请
  static Future<Map<String, dynamic>?> rejectInviteLocation(
      int uid, String id) async {
    var params = {"uid": uid.toString(), "id": id};
    var result = await HttpUtil.baseRequest(
        "/api/v1/location/rejectInviteLocation", params, () {});
    if (result.success) {
      return result.data;
    }
    return null;
  }
//获取轨迹
  static Future<Map<String, dynamic>?> getTravel(
      int uid, int? targetUid, int startTime, int endTime, int page) async {
    var params = {
      "uid": uid,
      "startTime": startTime.toString(),
      "endTime": endTime.toString(),
      "page": page.toString()
    };
    if (targetUid != null) {
      params["targetUid"] = targetUid;
    }
    var result =
        await HttpUtil.baseRequest("/api/v1/location/getTravel", params, () {});
    if (result.success) {
      return result.data;
    }
    return null;
  }
}
lib/main.dart
@@ -106,7 +106,6 @@
  void init() {
    AppUtil.initApp(context).then((value) {
      //TODO 显示开屏广告
      NavigatorUtil.navigateToNextPageWithFinish(context,
          MaterialPageRoute(builder: (context) {
        return MainPage(title: "");
@@ -114,9 +113,13 @@
    });
  }
  Future requestPermission() async {
    final status = await Permission.phone.request();
    await Permission.phone.request();
    await Permission.storage.request();
    await Permission.location.request();
    return;
  }
lib/model/common/http_model.dart
New file
@@ -0,0 +1,16 @@
import 'package:locations/api/http.dart';
class HttpRequestResult {
  final Map<String, dynamic>? data;
  final bool success;
  final String? msg;
  HttpRequestResult(this.success, this.data, {this.msg});
}
class HttpRequestListener {
  OnHttpRequestFinish? onFinish;
  OnHttpRequestStart? onStart;
  HttpRequestListener({this.onStart, this.onFinish});
}
lib/model/map/location_model.dart
New file
@@ -0,0 +1,183 @@
/// locTime : "2021-11-11 15:58:00"
/// latitude : 29.674523
/// longitude : 106.517533
/// radius : 40.0
/// country : "中国"
/// province : "重庆市"
/// city : "重庆市"
/// district : "渝北区"
/// town : "礼嘉街道"
/// street : "嘉康路"
/// address : "中国重庆市渝北区礼嘉街道嘉康路"
/// locationDetail : "在天际湾附近"
class BaiduLocation {
  BaiduLocation({
    String? locTime,
    double? latitude,
    double? longitude,
    double? radius,
    String? country,
    String? province,
    String? city,
    String? district,
    String? town,
    String? street,
    String? address,
    String? locationDetail,
  }) {
    _locTime = locTime;
    _latitude = latitude;
    _longitude = longitude;
    _radius = radius;
    _country = country;
    _province = province;
    _city = city;
    _district = district;
    _town = town;
    _street = street;
    _address = address;
    _locationDetail = locationDetail;
  }
  BaiduLocation.fromJson(dynamic json) {
    _locTime = json['locTime'];
    _latitude = json['latitude'];
    _longitude = json['longitude'];
    _radius = json['radius'];
    _country = json['country'];
    _province = json['province'];
    _city = json['city'];
    _district = json['district'];
    _town = json['town'];
    _street = json['street'];
    _address = json['address'];
    _locationDetail = json['locationDetail'];
  }
  String? _locTime;
  double? _latitude;
  double? _longitude;
  double? _radius;
  String? _country;
  String? _province;
  String? _city;
  String? _district;
  String? _town;
  String? _street;
  String? _address;
  String? _locationDetail;
  String? get locTime => _locTime;
  double? get latitude => _latitude;
  double? get longitude => _longitude;
  double? get radius => _radius;
  String? get country => _country;
  String? get province => _province;
  String? get city => _city;
  String? get district => _district;
  String? get town => _town;
  String? get street => _street;
  String? get address => _address;
  String? get locationDetail => _locationDetail;
  Map<String, dynamic> toJson() {
    final map = <String, dynamic>{};
    map['locTime'] = _locTime;
    map['latitude'] = _latitude;
    map['longitude'] = _longitude;
    map['radius'] = _radius;
    map['country'] = _country;
    map['province'] = _province;
    map['city'] = _city;
    map['district'] = _district;
    map['town'] = _town;
    map['street'] = _street;
    map['address'] = _address;
    map['locationDetail'] = _locationDetail;
    return map;
  }
}
class SimpleLocation {
  SimpleLocation({
    double? latitude,
    double? longitude,
    String? address,
  }) {
    _latitude = latitude;
    _longitude = longitude;
    _address = address;
  }
  SimpleLocation.fromJson(dynamic json) {
    _latitude = json['latitude'];
    _longitude = json['longitude'];
    _address = json['address'];
  }
  double? _latitude;
  double? _longitude;
  String? _address;
  double? get latitude => _latitude;
  double? get longitude => _longitude;
  String? get address => _address;
  Map<String, dynamic> toJson() {
    final map = <String, dynamic>{};
    map['latitude'] = _latitude;
    map['longitude'] = _longitude;
    map['address'] = _address;
    return map;
  }
}
class UserLocationInfo {
  UserLocationInfo({
    int? uid,
    SimpleLocation? location,
    String? updateTime,
  }) {
    _uid = uid;
    _location = location;
    _updateTime = updateTime;
  }
  UserLocationInfo.fromJson(dynamic json) {
    _uid = json['uid'];
    _location = SimpleLocation.fromJson(json['location']);
    _updateTime = json['updateTime'];
  }
  int? _uid;
  SimpleLocation? _location;
  String? _updateTime;
  int? get uid => _uid;
  SimpleLocation? get location => _location;
  String? get updateTime => _updateTime;
  Map<String, dynamic> toJson() {
    final map = <String, dynamic>{};
    map['uid'] = _uid;
    map['location'] = _location;
    map['updateTime'] = _updateTime;
    return map;
  }
}
lib/model/map/location_user_model.dart
New file
@@ -0,0 +1,106 @@
class LocationUserModel {
  LocationUserModel({
      String? id,
      int? targetUid,
      String? targetName,
      String? targetPhone,
    LocationInviteStatus? status,
    LocationUserType? userType,}){
    _id = id;
    _targetUid = targetUid;
    _targetName = targetName;
    _targetPhone = targetPhone;
    _status = status;
    _userType = userType;
}
  LocationUserModel.fromJson(dynamic json) {
    _id = json['id'];
    _targetUid = json['targetUid'];
    _targetName = json['targetName'];
    _targetPhone = json['targetPhone'];
    switch(json['status']){
      case "sentInvite":
        _status=LocationInviteStatus.sentInvite;
        break;
      case "agree":
        _status=LocationInviteStatus.agree;
        break;
      case "reject":
        _status=LocationInviteStatus.reject;
        break;
    }
    switch(json['userType']){
      case "son":
        _userType=LocationUserType.son;
        break;
      case "daughter":
        _userType=LocationUserType.daughter;
        break;
      case "husband":
        _userType=LocationUserType.husband;
        break;
      case "wife":
        _userType=LocationUserType.wife;
        break;
      case "father":
        _userType=LocationUserType.father;
        break;
      case "mother":
        _userType=LocationUserType.mother;
        break;
      case "other":
        _userType=LocationUserType.other;
        break;
    }
  }
  String? _id;
  int? _targetUid;
  String? _targetName;
  String? _targetPhone;
  LocationInviteStatus? _status;
  LocationUserType? _userType;
  String? get id => _id;
  int? get targetUid => _targetUid;
  String? get targetName => _targetName;
  String? get targetPhone => _targetPhone;
  LocationInviteStatus? get status => _status;
  LocationUserType? get userType => _userType;
  Map<String?, dynamic> toJson() {
    final map = <String?, dynamic>{};
    map['id'] = _id;
    map['targetUid'] = _targetUid;
    map['targetName'] = _targetName;
    map['targetPhone'] = _targetPhone;
    map['status'] = _status;
    map['userType'] = _userType;
    return map;
  }
}
enum LocationUserType{
  father,
  mother,
  husband,
  wife,
  daughter,
  son,
  other
}
enum LocationInviteStatus{
  sentInvite,
  agree,
  reject
}
lib/model/map/map_model.dart
@@ -1,7 +1,9 @@
import 'package:flutter_baidu_mapapi_base/flutter_baidu_mapapi_base.dart';
import 'package:flutter_baidu_mapapi_map/flutter_baidu_mapapi_map.dart';
class MapShowInfo{
   BMFCoordinate? center;
   int? zoomLevel;
   MapShowInfo(this.center,this.zoomLevel);
}
class MapShowInfo {
  final BMFCoordinate center;
  final int zoomLevel;
  MapShowInfo(this.center, this.zoomLevel);
}
lib/model/user/user_info.dart
New file
@@ -0,0 +1,39 @@
///用户信息
class UserInfo {
  UserInfo({int? id, String? nickName, String? portrait, int? vipExpireTime}) {
    _id = id;
    _nickName = nickName;
    _portrait = portrait;
    _vipExpireTime = vipExpireTime;
  }
  UserInfo.fromJson(dynamic json) {
    _id = int.parse(json['id'].toString());
    _nickName = json['nickName'];
    _portrait = json['portrait'];
    _vipExpireTime = json['vipExpireTime'];
  }
  int? _id;
  String? _nickName;
  String? _portrait;
  int? _vipExpireTime;
  int? get id => _id;
  String? get nickName => _nickName;
  String? get portrait => _portrait;
  int? get vipExpireTime => _vipExpireTime;
  Map<String, dynamic> toJson() {
    final map = <String, dynamic>{};
    map['id'] = _id;
    map['nickName'] = _nickName;
    map['portrait'] = _portrait;
    map['vipExpireTime'] = _vipExpireTime;
    return map;
  }
}
lib/ui/main/location.dart
@@ -1,6 +1,5 @@
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'dart:ui';
import 'package:flutter/cupertino.dart';
@@ -8,46 +7,31 @@
import 'package:flutter/rendering.dart';
import 'package:flutter_baidu_mapapi_base/flutter_baidu_mapapi_base.dart';
import 'package:flutter_baidu_mapapi_map/flutter_baidu_mapapi_map.dart';
import 'package:flutter_baidu_mapapi_search/flutter_baidu_mapapi_search.dart';
import 'package:locations/api/http.dart';
import 'package:locations/model/map/location_model.dart';
import 'package:locations/model/map/location_user_model.dart';
import 'package:locations/model/user/user_info.dart';
import 'package:locations/ui/map/location_search.dart';
import 'package:locations/ui/mine/advice.dart';
import 'package:locations/ui/mine/permission.dart';
import 'package:locations/ui/mine/settings.dart';
import 'package:locations/ui/mine/share_to_friends.dart';
import 'package:locations/ui/mine/try_functions.dart';
import 'package:locations/ui/mine/add_location_person.dart';
import 'package:locations/ui/mine/login.dart';
import 'package:locations/ui/widget/button.dart';
import 'package:locations/ui/widget/capture.dart';
import 'package:locations/ui/widget/dialog.dart';
import 'package:locations/ui/widget/map_marker.dart';
import 'package:locations/utils/event_bus_util.dart';
import 'package:locations/utils/global.dart';
import 'package:locations/utils/location_util.dart';
import 'package:locations/utils/map_util.dart';
import 'package:locations/utils/pageutils.dart';
import 'package:locations/utils/string_util.dart';
import 'package:locations/utils/ui_constant.dart';
import 'package:locations/utils/ui_utils.dart';
import 'package:locations/utils/user_util.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
typedef OnPositionHiidenChange = void Function(bool hidden);
BMFMapController? _mapController;
class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
          // This is the theme of your application.
          //
          // Try running your application with "flutter run". You'll see the
          // application has a blue toolbar. Then, without quitting the app, try
          // changing the primarySwatch below to Colors.green and then invoke
          // "hot reload" (press "r" in the console where you ran "flutter run",
          // or simply save your changes to "hot reload" in a Flutter IDE).
          // Notice that the counter didn't reset back to zero; the application
          // is not restarted.
          primaryColor: Color.fromARGB(255, 150, 150, 150)),
      home: LocationPage(title: ''),
    );
  }
}
class LocationPage extends StatefulWidget {
  LocationPage({Key? key, required this.title}) : super(key: key);
@@ -70,18 +54,84 @@
class _LocationPageState extends State<LocationPage>
    with AutomaticKeepAliveClientMixin {
  GlobalKey rootWidgetKey = GlobalKey();
  CaptureController _captureController = CaptureController();
  //用户marker
  BMFMarker? userMarker;
  String? portrait;
  BaiduLocation? location;
  BMFMapOptions mapOptions = BMFMapOptions(
      showMapScaleBar: false,
      mapType: BMFMapType.Standard,
      center: BMFCoordinate(39.917215, 116.380341),
      center: Global.currentPosition != null
          ? Global.currentPosition
          : BMFCoordinate(39.917215, 116.380341),
      mapScaleBarPosition: BMFPoint(0, 200),
      zoomLevel: 12,
      mapPadding: BMFEdgeInsets(left: 30, top: 0, right: 30, bottom: 200));
  var eventBusLocation;
  @override
  void initState() {
    super.initState();
    eventBusLocation = eventBus.on<UserLocationInfoEventBus>().listen((event) {
      //设置用户头像
      setState(() {
        portrait = (event.user == null ||
                StringUtil.isNullOrEmpty(event.user!.portrait)
            ? null
            : event.user!.portrait);
      });
      location = event.location;
      drawMarker(
          BMFCoordinate(event.location!.latitude!, event.location!.longitude!));
    });
    _getLocationInvite();
  }
  void _getLocationInvite() async {
    var uid = await UserUtil.getUid();
    if (uid == null) {
      return;
    }
    Map<String, dynamic>? result =
    await LocationApiUtil.getInviteLocation(uid!);
    if (result!["code"] == 0) {
      LocationUserModel model = LocationUserModel.fromJson(result["data"]);
      showGeneralDialog(
          context: context,
          pageBuilder: (BuildContext buildContext, Animation<double> animation,
              Animation<double> secondaryAnimation) {
            return RequireLocationDialog(model.targetPhone!, () {
              LocationApiUtil.rejectInviteLocation(uid, model.id!)
                  .then((value) {
                if (value!["code"] == 0) {
                  Navigator.of(context).pop();
                } else {
                  ToastUtil.toast(value!["msg"]);
                }
              });
            }, () {
              LocationApiUtil.agreeInviteLocation(uid, model.id!).then((value) {
                if (value!["code"] == 0) {
                  Navigator.of(context).pop();
                } else {
                  ToastUtil.toast(value!["msg"]);
                }
              });
            });
          });
    }
  }
  @override
  void dispose() {
    super.dispose();
    (eventBusLocation as StreamSubscription).cancel();
  }
  @override
@@ -95,17 +145,28 @@
            PositionInfoPage(),
            Positioned(
                top: -100,
                child:
            RepaintBoundary(
                key: rootWidgetKey,
                child: Image.asset(
                  "assets/images/mine/icon_mine_default_portrait.png",
                  height: 33,
                  width: 33,
                ))),
                child: RepaintBoundary(
                    key: rootWidgetKey,
                    child: Image.asset(
                      "assets/images/mine/icon_mine_default_portrait.png",
                      height: 33,
                      width: 33,
                    ))),
            Align(
                alignment: Alignment.bottomCenter,
                child: getAddLocationObjectView()),
            //地图marker截图组件
            Positioned(
                top: -100,
                child: CaptureWidget(
                  widget: PersonLocationMarker(portrait == null
                      ? Image.asset(
                          "assets/images/mine/icon_mine_default_portrait.png",
                        )
                      : Image.network(portrait!)),
                  captureController: _captureController,
                ))
          ],
        ));
  }
@@ -127,82 +188,79 @@
      child: BMFMapWidget(
        onBMFMapCreated: (controller) {
          _mapController = controller;
          Timer(Duration(seconds: 3),(){
            _capturePng().then((value) {
              BMFMarker marker = BMFMarker(
                  position: BMFCoordinate(39.928617, 116.40329),
                  title: 'flutterMaker',
                  identifier: 'flutter_marker',
                  icon:value!.path);
              /// 添加Marker
              _mapController?.addMarker(marker);
            });
          });
          if (location != null) {
            drawMarker(
                BMFCoordinate(location!.latitude!, location!.longitude!));
          }
        },
        mapOptions: mapOptions,
      ),
    );
  }
  Future<File?> _capturePng() async {
     await Permission.storage.request();
    try {
      RenderRepaintBoundary? boundary = rootWidgetKey.currentContext!
          .findRenderObject() as RenderRepaintBoundary;
      var image = await boundary.toImage(pixelRatio: 3.0);
      ByteData? byteData = await image.toByteData(format: ImageByteFormat.png);
      Uint8List pngBytes = byteData!.buffer.asUint8List();
      Directory? tempDir = await getExternalStorageDirectory();
      String tempPath = tempDir!.path;
      String shareImgPath = "$tempPath/test.jpg";
      File(shareImgPath!).writeAsBytesSync(pngBytes!);
      int length=await File(shareImgPath!).length();
      print("截图大小为:${length}");
      return File(shareImgPath!);
    } catch (e) {
      print(e);
  //画marker
  void drawMarker(BMFCoordinate location) async {
    if (_mapController == null) {
      return;
    }
    return null;
    Directory? tempDir = await getExternalStorageDirectory();
    String tempPath = tempDir!.path;
    File? file =
        await _captureController.capturePng("$tempPath/location_person.png");
    if (userMarker == null) {
      userMarker =
          await MapUtil.addMarker(_mapController, location, file!.path);
    } else {
      userMarker!.updateIcon(file!.path);
      userMarker!.updatePosition(location);
    }
    _mapController!.setCenterCoordinate(location, true);
  }
  //添加想定位的人
  Widget getAddLocationObjectView() {
    return Container(
      alignment: Alignment.topCenter,
      height: 72,
      margin: const EdgeInsets.fromLTRB(10, 0, 10, 0),
      padding: const EdgeInsets.fromLTRB(0, 18, 0, 0),
      decoration: BoxDecoration(
          color: Colors.white,
          borderRadius: BorderRadius.only(
              topLeft: Radius.circular(10), topRight: Radius.circular(10)),
          boxShadow: getViewShadow()),
      child: Flex(
        direction: Axis.horizontal,
        mainAxisAlignment: MainAxisAlignment.center,
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          Image.asset(
            "assets/images/common/icon_person.png",
            height: 17,
    return InkWell(
        onTap: () {
          UserUtil.isLogin().then((value) {
            if (!value) {
              NavigatorUtil.navigateToNextPage(
                  context, LoginPage(title: ""), (data) {});
              return;
            }
            NavigatorUtil.navigateToNextPage(
                context, AddLocationPersonPage(title: ""), (data) {});
          });
        },
        child: Container(
          alignment: Alignment.topCenter,
          height: 72,
          margin: const EdgeInsets.fromLTRB(10, 0, 10, 0),
          padding: const EdgeInsets.fromLTRB(0, 18, 0, 0),
          decoration: BoxDecoration(
              color: Colors.white,
              borderRadius: BorderRadius.only(
                  topLeft: Radius.circular(10), topRight: Radius.circular(10)),
              boxShadow: getViewShadow()),
          child: Flex(
            direction: Axis.horizontal,
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.center,
            children: [
              Image.asset(
                "assets/images/common/icon_person.png",
                height: 17,
              ),
              Container(
                width: 11,
                height: 1,
              ),
              const Text(
                "添加想定位的对象",
                style: TextStyle(color: ColorConstant.theme, fontSize: 15),
              )
            ],
          ),
          Container(
            width: 11,
            height: 1,
          ),
          const Text(
            "添加想定位的对象",
            style: TextStyle(color: ColorConstant.theme, fontSize: 15),
          )
        ],
      ),
    );
        ));
  }
  @override
@@ -234,7 +292,101 @@
  //位置信息是否隐藏
  bool locationInfoHidden = true;
  UserInfo? user;
  BaiduLocation? _location;
  GlobalKey rootWidgetKey = GlobalKey();
  var eventBusLogin;
  List<LocationUserModel>? userList;
  @override
  void initState() {
    super.initState();
    UserUtil.getUserInfo().then((value) {
      setState(() {
        user = value;
      });
      //开始定位
      LocationUtil.startLocation(0, (state, map) {
        if (state == LocationState.success) {
          BaiduLocation location = BaiduLocation.fromJson(map);
          setState(() {
            _location = location;
          });
          eventBus.fire(UserLocationInfoEventBus(_location, user));
        }
      });
    });
    //注册eventbus
    eventBusLogin = eventBus.on<LoginEventBus>().listen((event) {
      if (event.isLogin) {
        UserUtil.getUserInfo().then((value) {
          setState(() {
            user = value;
          });
        });
      } else {
        setState(() {
          user = null;
        });
      }
    });
  }
  @override
  void dispose() {
    super.dispose();
    //取消订阅
    eventBusLogin.cancel();
  }
  void _selectLocationUser() async {
    await _getLocationUsers();
    if (userList == null || userList!.isEmpty) {
      ToastUtil.toast("请先添加定位对象");
      return;
    }
    var selectUserType = await showGeneralDialog(
        context: context,
        pageBuilder: (BuildContext buildContext, Animation<double> animation,
                Animation<double> secondaryAnimation) =>
            ListViewDialog(
              ListView.builder(
                padding: EdgeInsets.zero,
                  itemCount: userList!.length,
                  itemBuilder: (BuildContext context, int index) {
                    return _getUserItem(userList![index], index);
                  }),
              () {
                Navigator.of(context).pop();
              },
            ));
  }
  Future _getLocationUsers() async {
    var uid = await UserUtil.getUid();
    if (uid == null) return;
    Map<String, dynamic>? result =
        await LocationApiUtil.getLocationUsers(uid, 1);
    if (result!["code"] == 0) {
      List<dynamic> list = result!["data"]["list"];
      List<LocationUserModel> users = [];
      list.forEach((element) {
        users.add(LocationUserModel.fromJson(element));
      });
      userList = users;
    } else {
      ToastUtil.toast(result!["msg"]);
    }
  }
  @override
  Widget build(BuildContext context) {
@@ -248,8 +400,8 @@
              Expanded(
                  child: InkWell(
                      onTap: () {
                        NavigatorUtil.navigateToNextPage(context,  LocationSearchPage(),
                        (data) { });
                        NavigatorUtil.navigateToNextPage(
                            context, LocationSearchPage(), (data) {});
                      },
                      child: Container(
                        alignment: Alignment.center,
@@ -303,7 +455,9 @@
              child: Stack(
                children: [
                  //位置信息
                  getPositionInfoView(context),
                  _location == null
                      ? Container()
                      : getPositionInfoView(context),
                  //个人信息
                  Container(
                    height: 100,
@@ -322,31 +476,45 @@
                    child: Stack(
                      alignment: Alignment.topCenter,
                      children: [
                        Flex(
                          mainAxisAlignment: MainAxisAlignment.center,
                          direction: Axis.horizontal,
                          children: [
                            Image.asset(
                              "assets/images/main/icon_location_change_person.png",
                              width: 13,
                            ),
                            Text(
                              " 切换实时定位人",
                              style: TextStyle(
                                  color: ColorConstant.theme, fontSize: 15),
                            )
                          ],
                        ),
                        user == null
                            ? Container()
                            : InkWell(
                                onTap: () {
                                  _selectLocationUser();
                                },
                                child: Flex(
                                  mainAxisAlignment: MainAxisAlignment.center,
                                  direction: Axis.horizontal,
                                  children: [
                                    Image.asset(
                                      "assets/images/main/icon_location_change_person.png",
                                      width: 13,
                                    ),
                                    Text(
                                      " 切换实时定位人",
                                      style: TextStyle(
                                          color: ColorConstant.theme,
                                          fontSize: 15),
                                    )
                                  ],
                                )),
                        //个人信息
                        Flex(
                          direction: Axis.horizontal,
                          crossAxisAlignment: CrossAxisAlignment.center,
                          children: [
                            Image.asset(
                              "assets/images/mine/icon_mine_default_portrait.png",
                              height: 33,
                              width: 33,
                            ),
                            user == null
                                ? Image(
                                    image: AssetImage(
                                        "assets/images/mine/icon_mine_default_portrait.png"),
                                    height: 33,
                                    width: 33,
                                  )
                                : Image.network(
                                    user!.portrait!,
                                    width: 33,
                                    height: 33,
                                  ),
                            Container(
                              width: 5,
                            ),
@@ -355,23 +523,46 @@
                              direction: Axis.vertical,
                              mainAxisAlignment: MainAxisAlignment.center,
                              crossAxisAlignment: CrossAxisAlignment.start,
                              children: [
                                Text(
                                  "我自己",
                                  softWrap: false,
                                  overflow: TextOverflow.ellipsis,
                                  style: TextStyle(
                                      color: Color(0xFFA0A0A0), fontSize: 18),
                                ),
                                Container(
                                  height: 2,
                                ),
                                Text(
                                  "ID:128312",
                                  style: TextStyle(
                                      color: Color(0xFFA0A0A0), fontSize: 12),
                                )
                              ],
                              children: user == null
                                  ? [
                                      Container(
                                          width: 60,
                                          child: MyOutlineButton(
                                            "去登录",
                                            5,
                                            onClick: () {
                                              NavigatorUtil.navigateToNextPage(
                                                  context, LoginPage(title: ""),
                                                  (data) {
                                                UserUtil.getUserInfo()
                                                    .then((value) {
                                                  setState(() {
                                                    user = value;
                                                  });
                                                });
                                              });
                                            },
                                          ))
                                    ]
                                  : [
                                      Text(
                                        user!.nickName!,
                                        softWrap: false,
                                        overflow: TextOverflow.ellipsis,
                                        style: TextStyle(
                                            color: Color(0xFFA0A0A0),
                                            fontSize: 18),
                                      ),
                                      Container(
                                        height: 2,
                                      ),
                                      Text(
                                        "ID:${user!.id!}",
                                        style: TextStyle(
                                            color: Color(0xFFA0A0A0),
                                            fontSize: 12),
                                      )
                                    ],
                            )),
                            Container(
                              width: 5,
@@ -379,31 +570,33 @@
                            Flex(
                                direction: Axis.vertical,
                                mainAxisAlignment: MainAxisAlignment.center,
                                children: [
                                  MyOutlineButton(
                                    "生成轨迹",
                                    13,
                                    height: 26,
                                    width: 82,
                                    fontSize: 12,
                                    onClick: () {
                                      print("生成轨迹");
                                    },
                                  ),
                                  Container(
                                    height: 6,
                                  ),
                                  MyOutlineButton(
                                    "实时共享",
                                    13,
                                    height: 26,
                                    width: 82,
                                    fontSize: 12,
                                    onClick: () {
                                      print("实时共享");
                                    },
                                  ),
                                ])
                                children: user == null
                                    ? []
                                    : [
                                        MyOutlineButton(
                                          "生成轨迹",
                                          13,
                                          height: 26,
                                          width: 82,
                                          fontSize: 12,
                                          onClick: () {
                                            print("生成轨迹");
                                          },
                                        ),
                                        Container(
                                          height: 6,
                                        ),
                                        MyOutlineButton(
                                          "实时共享",
                                          13,
                                          height: 26,
                                          width: 82,
                                          fontSize: 12,
                                          onClick: () {
                                            print("实时共享");
                                          },
                                        ),
                                      ])
                          ],
                        )
                      ],
@@ -411,61 +604,200 @@
                  ),
                  //更多
                  Positioned(
                      left: MediaQuery.of(context).size.width / 2 - 10 - 70 / 2,
                      top: 80,
                      child: InkWell(
                          onTap: () {
                            setState(() {
                              locationInfoHidden = !locationInfoHidden;
                            });
                          },
                          child: Container(
                              width: 70,
                              height: 40,
                              child: Stack(
                                  alignment: Alignment.bottomCenter,
                                  children: [
                                    Container(
                                      height: 30,
                                      width: 60,
                                      margin: EdgeInsets.only(top: 0),
                                      alignment: Alignment.center,
                                      decoration: BoxDecoration(
                                          color: Colors.white,
                                          borderRadius:
                                              BorderRadius.circular(15),
                                          boxShadow: getViewShadow()),
                                    ),
                                    Positioned(
                                        top: 0,
                                        child: Container(
                                          height: 25,
                                          width: 70,
                                          alignment: Alignment.bottomCenter,
                  _location == null
                      ? Container()
                      : Positioned(
                          left: MediaQuery.of(context).size.width / 2 -
                              10 -
                              70 / 2,
                          top: 80,
                          child: InkWell(
                              onTap: () {
                                setState(() {
                                  locationInfoHidden = !locationInfoHidden;
                                });
                              },
                              child: Container(
                                  width: 70,
                                  height: 40,
                                  child: Stack(
                                      alignment: Alignment.bottomCenter,
                                      children: [
                                        Container(
                                          height: 30,
                                          width: 60,
                                          margin: EdgeInsets.only(top: 0),
                                          alignment: Alignment.center,
                                          decoration: BoxDecoration(
                                            color: Colors.white,
                                          ),
                                          // child: Text(
                                          //   "收起",
                                          //   style: TextStyle(
                                          //       color: ColorConstant.theme,
                                          //       fontSize: 11),
                                          // )
                                        )),
                                    Positioned(
                                        bottom: 5,
                                        child: Text(
                                          locationInfoHidden ? "展开" : "收起",
                                          style: TextStyle(
                                              color: ColorConstant.theme,
                                              fontSize: 11),
                                        )),
                                  ]))))
                                              color: Colors.white,
                                              borderRadius:
                                                  BorderRadius.circular(15),
                                              boxShadow: getViewShadow()),
                                        ),
                                        Positioned(
                                            top: 0,
                                            child: Container(
                                              height: 25,
                                              width: 70,
                                              alignment: Alignment.bottomCenter,
                                              decoration: BoxDecoration(
                                                color: Colors.white,
                                              ),
                                              // child: Text(
                                              //   "收起",
                                              //   style: TextStyle(
                                              //       color: ColorConstant.theme,
                                              //       fontSize: 11),
                                              // )
                                            )),
                                        Positioned(
                                            bottom: 5,
                                            child: Text(
                                              locationInfoHidden ? "展开" : "收起",
                                              style: TextStyle(
                                                  color: ColorConstant.theme,
                                                  fontSize: 11),
                                            )),
                                      ]))))
                ],
              )),
        ]));
  }
  Widget _getStatus(LocationInviteStatus? status, LocationUserModel user) {
    switch (status) {
      case LocationInviteStatus.sentInvite:
        return const Text(
          "等待同意",
          style: TextStyle(color: ColorConstant.theme, fontSize: 15),
        );
      case LocationInviteStatus.agree:
        return InkWell(
            onTap: () {
              Navigator.pop(context, user);
            },
            child: Container(
              padding: const EdgeInsets.only(left: 10, right: 10),
              height: 25,
              decoration: BoxDecoration(
                  color: Colors.white,
                  border: Border.all(color: ColorConstant.theme),
                  borderRadius: BorderRadius.circular(10)),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.center,
                crossAxisAlignment: CrossAxisAlignment.center,
                children: [
                  Image.asset(
                    "assets/images/user/icon_user_location.png",
                    height: 15,
                  ),
                  Container(
                    width: 5,
                  ),
                  const Text(
                    "去定位",
                    style: TextStyle(color: ColorConstant.theme, fontSize: 15),
                  )
                ],
              ),
            ));
      case LocationInviteStatus.reject:
        return const Text(
          "对方拒接",
          style: TextStyle(color: Color(0xFF9DAAB3), fontSize: 15),
        );
    }
    return Container();
  }
  Image _getPortrait(LocationUserType? type) {
    String img = "assets/images/user/";
    switch (type) {
      case LocationUserType.father:
        img += "icon_user_type_father.png";
        break;
      case LocationUserType.mother:
        img += "icon_user_type_mother.png";
        break;
      case LocationUserType.husband:
        img += "icon_user_type_husband.png";
        break;
      case LocationUserType.wife:
        img += "icon_user_type_wife.png";
        break;
      case LocationUserType.daughter:
        img += "icon_user_type_daughter.png";
        break;
      case LocationUserType.son:
        img += "icon_user_type_son.png";
        break;
      case LocationUserType.other:
        img += "icon_user_type_other.png";
    }
    return Image.asset(
      img,
      height: 31,
    );
  }
  Widget _getUserItem(LocationUserModel user, int index) {
    return Column(children: [
      Container(
          padding: const EdgeInsets.fromLTRB(27, 0, 27, 0),
          height: 80,
          decoration: BoxDecoration(
              color: Colors.white,
              borderRadius: index == 0
                  ? const BorderRadius.only(
                      topLeft: Radius.circular(10),
                      topRight: Radius.circular(10))
                  : BorderRadius.zero),
          child: Row(
            children: [
              Container(
                width: 40,
                alignment: Alignment.center,
                child: _getPortrait(user.userType),
              ),
              Container(
                width: 8,
              ),
              Expanded(
                  child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    user.targetName!,
                    style: const TextStyle(
                        fontSize: 18, color: ColorConstant.theme),
                  ),
                  Container(
                    height: 5,
                  ),
                  Text(
                    user.targetPhone!,
                    style:
                        const TextStyle(fontSize: 15, color: Color(0xFF9DAAB3)),
                  ),
                ],
              )),
              _getStatus(user.status, user)
            ],
          )),
      Container(
        height: 1,
        color: Colors.white,
        padding: const EdgeInsets.only(left: 27, right: 27),
        child: Container(
          color: const Color(0xFF9DAAB3),
        ),
      )
    ]);
  }
  Widget getPositionInfoView(BuildContext context) {
@@ -493,9 +825,10 @@
                          decoration: BoxDecoration(
                              color: const Color(0xFFCACDD3),
                              borderRadius: BorderRadius.circular(20)),
                          child: const Text(
                            "2021.08.08 12:00",
                            style: TextStyle(color: Colors.white, fontSize: 10),
                          child: Text(
                            _location!.locTime!,
                            style: const TextStyle(
                                color: Colors.white, fontSize: 10),
                          ),
                        ),
                        Container(
@@ -508,14 +841,14 @@
                              "assets/images/main/icon_location_position_name.png",
                              height: 16,
                            ),
                            Text(
                            const Text(
                              " 当前位置:",
                              style: TextStyle(
                                  color: Color(0xFF999999), fontSize: 12),
                            ),
                            Expanded(
                                child: Text(
                              "重庆市重庆市重庆市重庆市重庆市重庆市重庆市重庆庆市重庆市重庆",
                              _location!.address!,
                              softWrap: false,
                              overflow: TextOverflow.ellipsis,
                              style: TextStyle(
@@ -534,17 +867,17 @@
                              "assets/images/main/icon_location_position_location.png",
                              height: 15,
                            ),
                            Text(
                            const Text(
                              " 经  纬  度:",
                              style: TextStyle(
                                  color: Color(0xFF999999), fontSize: 12),
                            ),
                            Expanded(
                                child: Text(
                              "N 41141.1233,W 28741.389",
                              "N ${_location!.latitude!},W ${_location!.longitude!}",
                              softWrap: false,
                              overflow: TextOverflow.ellipsis,
                              style: TextStyle(
                              style: const TextStyle(
                                  color: Color(0xFFBABABA), fontSize: 12),
                            ))
                          ],
lib/ui/main/mine.dart
@@ -2,6 +2,8 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:locations/api/http.dart';
import 'package:locations/model/user/user_info.dart';
import 'package:locations/ui/common/browser.dart';
import 'package:locations/ui/mine/advice.dart';
import 'package:locations/ui/mine/login.dart';
@@ -11,6 +13,8 @@
import 'package:locations/ui/mine/try_functions.dart';
import 'package:locations/utils/pageutils.dart';
import 'package:locations/utils/ui_constant.dart';
import 'package:locations/utils/ui_utils.dart';
import 'package:locations/utils/user_util.dart';
void main() {
  runApp(MyApp());
@@ -58,6 +62,7 @@
class _MinePageState extends State<MinePage>
    with AutomaticKeepAliveClientMixin {
  UserInfo? userInfo = null;
  bool isLogin = false;
  bool isVIP = false;
  List<Widget> list = [];
@@ -104,7 +109,11 @@
      case "privacy":
        break;
      case "setting":
        Navigator.of(context).push(CustomRouteSlide(SettingPage(title: "")));
        NavigatorUtil.navigateToNextPage(context, SettingPage(title: ""),
            (data) {
          _getUserInfo();
        });
        break;
    }
  }
@@ -130,18 +139,60 @@
      ));
    });
    //获取用户信息
    _getUserInfo();
    super.initState();
  }
  void _login() {
    print("登录");
    NavigatorUtil.navigateToNextPage(context,
      LoginPage(title: ""), (data) {});
    NavigatorUtil.navigateToNextPage(context, LoginPage(title: ""), (data) {
      _getUserInfo();
    });
  }
    // setState(() {
    //   isLogin = !isLogin;
    //   isVIP = !isVIP;
    // });
  void _getUserInfo() async {
    var user = await UserUtil.getUserInfo();
    if (user == null) {
      setState(() {
        isLogin = false;
      });
      return;
    }
    setState(() {
      isLogin = true;
      userInfo = user;
    });
    Map<String, dynamic>? result =
        await UserApiUtil.getUserInfo(context, user.id!);
    var code = result!["code"];
    if (code == 0) {
      UserInfo user = UserInfo.fromJson(result["data"]);
      print(user);
      setState(() {
        userInfo = user;
        if (userInfo != null &&
            userInfo!.vipExpireTime != null &&
            userInfo!.vipExpireTime! > DateTime.now().millisecond) {
          isVIP = true;
        } else {
          isVIP = false;
        }
      });
    } else if (code == 80001) {
      //账号被封禁
      setState(() {
        isLogin = false;
      });
      ToastUtil.toast("账号已被封禁");
    } else if (code == 80002) {
      //账号被删除
      setState(() {
        isLogin = false;
      });
      ToastUtil.toast("账号已被删除");
    }
  }
  Widget getLoginContentView() {
@@ -159,14 +210,14 @@
        _login();
      },
      child: Container(
        margin: EdgeInsets.fromLTRB(10, 0, 10, 0),
        margin: const EdgeInsets.fromLTRB(10, 0, 10, 0),
        height: 38,
        alignment: Alignment.center,
        child: Text(
        child: const Text(
          "登录/注册",
          style: TextStyle(color: Colors.white, fontSize: 15),
        ),
        decoration: BoxDecoration(
        decoration: const BoxDecoration(
            color: Color(0xFF0E96FF),
            borderRadius: BorderRadius.all(Radius.elliptical(10, 10))),
      ),
@@ -177,7 +228,7 @@
    return Container(
      height: 50,
      padding: EdgeInsets.zero,
      margin: EdgeInsets.fromLTRB(10, 0, 0, 0),
      margin: const EdgeInsets.fromLTRB(10, 0, 0, 0),
      alignment: Alignment.topLeft,
      child: Flex(
        direction: Axis.vertical,
@@ -185,12 +236,12 @@
        mainAxisAlignment: MainAxisAlignment.start,
        children: [
          Text(
            "定位-981273",
            style: TextStyle(color: Color(0xFF40C7FF), fontSize: 18),
            userInfo!.nickName!,
            style: const TextStyle(color: Color(0xFF40C7FF), fontSize: 18),
          ),
          Text(
            "ID:128312",
            style: TextStyle(color: Color(0xFF40C7FF), fontSize: 12),
            "ID:" + userInfo!.id!.toString(),
            style: const TextStyle(color: Color(0xFF40C7FF), fontSize: 12),
          ),
        ],
      ),
@@ -205,8 +256,8 @@
    ));
    if (isVIP) {
      list.add(Container(
          margin: EdgeInsets.fromLTRB(6, 0, 0, 0),
          child: Text.rich(TextSpan(
          margin: const EdgeInsets.fromLTRB(6, 0, 0, 0),
          child: const Text.rich(TextSpan(
            children: <TextSpan>[
              TextSpan(text: '剩余'),
              TextSpan(
@@ -217,7 +268,7 @@
          ))));
    } else {
      list.add(Container(
        margin: EdgeInsets.fromLTRB(6, 0, 0, 0),
        margin: const EdgeInsets.fromLTRB(6, 0, 0, 0),
        child: Image.asset(
          "assets/images/mine/label_vip.png",
          height: 20,
@@ -235,7 +286,7 @@
            child: Column(
          children: [
            Container(
              decoration: BoxDecoration(
              decoration: const BoxDecoration(
                  image: DecorationImage(
                      image:
                          AssetImage("assets/images/mine/ic_mine_top_bg.png"),
@@ -265,7 +316,7 @@
                            ],
                          )),
                      height: 105,
                      decoration: BoxDecoration(
                      decoration: const BoxDecoration(
                          color: Colors.white,
                          borderRadius:
                              BorderRadius.all(Radius.elliptical(10, 10)))),
@@ -289,8 +340,8 @@
                                  children: getVIPContent(),
                                ),
                                Container(
                                  margin: EdgeInsets.fromLTRB(0, 8, 0, 0),
                                  child: Text(
                                  margin: const EdgeInsets.fromLTRB(0, 8, 0, 0),
                                  child: const Text(
                                    "定位守护亲友,黑科技保驾护航",
                                    style: TextStyle(
                                        fontSize: 15, color: Colors.white),
@@ -306,6 +357,15 @@
                                  onTap: () {
                                    //查看详情或开通
                                    print(isVIP ? "查看详情" : "开通会员");
                                    UserUtil.isLogin().then((value) {
                                      if (!value) {
                                        NavigatorUtil.navigateToNextPage(
                                            context, LoginPage(title: ""),
                                            (data) {
                                          _getUserInfo();
                                        });
                                      }
                                    });
                                  },
                                  child: Container(
                                      width: 99,
@@ -313,10 +373,10 @@
                                      alignment: Alignment.center,
                                      child: Text(
                                        isVIP ? "查看详情" : "立即开通",
                                        style:
                                            TextStyle(color: Color(0xFFD4A880)),
                                        style: const TextStyle(
                                            color: Color(0xFFD4A880)),
                                      ),
                                      decoration: BoxDecoration(
                                      decoration: const BoxDecoration(
                                        color: Color(0xFFFAEAB9),
                                        borderRadius: BorderRadius.only(
                                            topLeft: Radius.circular(13),
@@ -333,7 +393,7 @@
                                      ))))
                        ],
                      ),
                      decoration: BoxDecoration(
                      decoration: const BoxDecoration(
                          boxShadow: [
                            BoxShadow(
                                color: Color(0x4D0E96FF),
@@ -356,11 +416,11 @@
              height: 340,
              margin: const EdgeInsets.all(10),
              padding: const EdgeInsets.all(10),
              decoration: BoxDecoration(
              decoration: const BoxDecoration(
                  borderRadius: BorderRadius.all(Radius.elliptical(10, 10)),
                  color: Color(0xFFF4FFFF)),
              child: GridView(
                  gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                      crossAxisCount: 4, mainAxisSpacing: 10),
                  physics: const NeverScrollableScrollPhysics(),
                  children: list),
lib/ui/main/travel_main.dart
@@ -1,6 +1,5 @@
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'dart:ui';
import 'package:flutter/cupertino.dart';
@@ -9,22 +8,18 @@
import 'package:flutter_baidu_mapapi_base/flutter_baidu_mapapi_base.dart';
import 'package:flutter_baidu_mapapi_map/flutter_baidu_mapapi_map.dart';
import 'package:flutter_baidu_mapapi_search/flutter_baidu_mapapi_search.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:locations/ui/map/location_search.dart';
import 'package:locations/ui/map/travel.dart';
import 'package:locations/ui/mine/advice.dart';
import 'package:locations/ui/mine/permission.dart';
import 'package:locations/ui/mine/settings.dart';
import 'package:locations/ui/mine/share_to_friends.dart';
import 'package:locations/ui/mine/try_functions.dart';
import 'package:locations/ui/widget/button.dart';
import 'package:locations/ui/widget/custom.dart';
import 'package:locations/ui/widget/capture.dart';
import 'package:locations/ui/widget/map_marker.dart';
import 'package:locations/utils/global.dart';
import 'package:locations/utils/location_util.dart';
import 'package:locations/utils/map_util.dart';
import 'package:locations/utils/pageutils.dart';
import 'package:locations/utils/ui_constant.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:fluttertoast/fluttertoast.dart';
//控件阴影
List<BoxShadow> getViewShadow() {
@@ -48,7 +43,9 @@
  BMFMapOptions mapOptions = BMFMapOptions(
      showMapScaleBar: false,
      mapType: BMFMapType.Standard,
      center: BMFCoordinate(39.917215, 116.380341),
      center: Global.currentPosition != null
          ? Global.currentPosition
          : BMFCoordinate(39.917215, 116.380341),
      mapScaleBarPosition: BMFPoint(0, 200),
      zoomLevel: 12,
      mapPadding: BMFEdgeInsets(left: 30, top: 0, right: 30, bottom: 200));
@@ -58,21 +55,24 @@
  //是否正在录制轨迹
  bool travelRECIng = false;
  List<BMFMarker> startMarkers = [];
  TravelRECManager? _travelRECManager;
  //用戶目前的位置
  BMFCoordinate? currentPosition;
  TextEditingController? editingController;
  List<BMFPoiInfo>? suggestList;
  AnimationController? _animationController;
  Animation<double>? _animation;
  BMFMarker? userMarker;
  //当前用户头像
  CaptureController _captureController = CaptureController();
  @override
  void initState() {
    super.initState();
    editingController = TextEditingController();
    _animationController = new AnimationController(
        duration: const Duration(milliseconds: 300), vsync: this);
    final CurvedAnimation curve =
@@ -83,48 +83,65 @@
      });
  }
  @override
  void dispose() {
    super.dispose();
    if (null != subscription) {
      subscription!.cancel();
    }
    LocationUtil.stopLocation();
  }
  //获取地图视图
  Widget getMapView() {
    return Container(
      child: BMFMapWidget(
        onBMFMapCreated: (controller) {
          _mapController = controller;
          _startLocation();
          drawLine();
          _travelRECManager = TravelRECManager(_mapController);
          _startLocation(0);
        },
        mapOptions: mapOptions,
      ),
    );
  }
  void _startLocation() {
  StreamSubscription<Map<String, Object>?>? subscription;
  void _startLocation(int scanspan) async {
    //启动定位
    LocationUtil.init(0);
    LocationUtil.startLocation((map) {
      double lat = map["latitude"] as double;
      double lng = map["logitude"] as double;
      _mapController!.setCenterCoordinate(BMFCoordinate(lng, lat), true);
    if (subscription != null) {
      subscription!.cancel();
    }
    subscription = await LocationUtil.startLocation( scanspan, (state, map) {
      if (state == LocationState.success) {
        double lat = map!["latitude"] as double;
        double lng = map!["longitude"] as double;
        currentPosition = BMFCoordinate(lat, lng);
        _mapController!.setCenterCoordinate(BMFCoordinate(lat, lng), true);
        setCurrentPosition(BMFCoordinate(lat, lng));
      }
    });
  }
  void drawLine() {
    List<BMFCoordinate> coordinates = [];
    coordinates.add(BMFCoordinate(29.523624, 106.48916));
    coordinates.add(BMFCoordinate(29.535189, 106.502095));
    coordinates.add(BMFCoordinate(29.545747, 106.528829));
    coordinates.add(BMFCoordinate(29.556304, 106.545789));
    coordinates.add(BMFCoordinate(29.565101, 106.566486));
    coordinates.add(BMFCoordinate(29.568619, 106.588045));
    coordinates.add(BMFCoordinate(29.578922, 106.580284));
    coordinates.add(BMFCoordinate(29.587968, 106.575972));
    coordinates.add(BMFCoordinate(29.596762, 106.568211));
    coordinates.add(BMFCoordinate(29.607062, 106.567923));
    coordinates.add(BMFCoordinate(29.614096, 106.574247));
    MapUtil.drawLine(coordinates, ColorConstant.theme, _mapController);
    MapUtil.getMapShowParams(coordinates).then((value) {
      _mapController!.setCenterCoordinate(value!.center!, true);
      _mapController!.setZoomTo(value.zoomLevel!.toDouble());
    });
  void stopLocation() async {
    if (subscription != null) {
      subscription!.cancel();
    }
    LocationUtil.stopLocation();
  }
  setCurrentPosition(BMFCoordinate position) async {
    Directory? tempDir = await getExternalStorageDirectory();
    String tempPath = tempDir!.path;
    if (userMarker == null) {
      _captureController.capturePng(tempPath + "/portrait.png").then((value) {
        MapUtil.addMarker(_mapController, position, value!.path,
            offset: BMFPoint(0, 40), zIndex: 10);
      });
    } else {
      await userMarker!.updatePosition(position);
    }
  }
  @override
@@ -286,8 +303,14 @@
                      )
                    ],
                  ))),
          Positioned(top: 100, child:
          PersonLocationMarker())
          Positioned(
              top: -100,
              child: CaptureWidget(
                widget: PersonLocationMarker(Image.asset(
                  "assets/images/mine/icon_mine_default_portrait.png",
                )),
                captureController: _captureController,
              ))
        ]));
  }
@@ -359,27 +382,30 @@
                InkWell(
                  onTap: () {
                    if (!travelRECIng) {
                      currentPosition = BMFCoordinate(29.523624, 106.48916);
                      if (currentPosition == null) {
                        Fluttertoast.showToast(msg: "定位失败,请重新定位");
                        return;
                      }
                      MapUtil.addMarker(_mapController, currentPosition!,
                              "assets/images/map/icon_travel_start.png",
                              zIndex: 1)
                          .then((value) {
                        startMarkers.add(value);
                      });
                      MapUtil.addMarker(_mapController, currentPosition!,
                              "assets/images/map/icon_travel_start_1.png",
                              offset: BMFPoint(0, 38))
                          .then((value) {
                        startMarkers.add(value);
                      //开始定位
                      LocationUtil.startLocation(0, (state, map) {
                        if (state == LocationState.success) {
                          double lat = map!["latitude"] as double;
                          double lng = map!["longitude"] as double;
                          currentPosition = BMFCoordinate(lat, lng);
                          _mapController!.setCenterCoordinate(
                              BMFCoordinate(lat, lng), true);
                          setCurrentPosition(BMFCoordinate(lat, lng));
                          //开始录制
                          _travelRECManager!.startREC(currentPosition!);
                          //启动定位
                          _startLocation(1000 * 30);
                        } else {
                          Fluttertoast.showToast(msg: "定位失败,请重新定位");
                          return;
                        }
                      });
                    } else {
                      //结束录制
                      if (startMarkers.length > 0)
                        MapUtil.removeMarkers(_mapController, startMarkers);
                      _travelRECManager!.stopREC();
                      //结束定位
                      stopLocation();
                    }
                    setState(() {
@@ -439,7 +465,7 @@
                ),
                InkWell(
                  onTap: () {
                    _startLocation();
                    _startLocation(0);
                  },
                  child: Column(
                    children: [
lib/ui/map/location_search.dart
@@ -1,26 +1,16 @@
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'dart:ui';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter_baidu_mapapi_base/flutter_baidu_mapapi_base.dart';
import 'package:flutter_baidu_mapapi_map/flutter_baidu_mapapi_map.dart';
import 'package:flutter_baidu_mapapi_search/flutter_baidu_mapapi_search.dart';
import 'package:locations/ui/mine/advice.dart';
import 'package:locations/ui/mine/permission.dart';
import 'package:locations/ui/mine/settings.dart';
import 'package:locations/ui/mine/share_to_friends.dart';
import 'package:locations/ui/mine/try_functions.dart';
import 'package:locations/ui/widget/button.dart';
import 'package:locations/utils/global.dart';
import 'package:locations/utils/location_util.dart';
import 'package:locations/utils/map_util.dart';
import 'package:locations/utils/pageutils.dart';
import 'package:locations/utils/search_util.dart';
import 'package:locations/utils/ui_constant.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
//控件阴影
List<BoxShadow> getViewShadow() {
@@ -45,7 +35,9 @@
  BMFMapOptions mapOptions = BMFMapOptions(
      showMapScaleBar: false,
      mapType: BMFMapType.Standard,
      center: BMFCoordinate(39.917215, 116.380341),
      center: Global.currentPosition != null
          ? Global.currentPosition
          : BMFCoordinate(39.917215, 116.380341),
      mapScaleBarPosition: BMFPoint(0, 200),
      zoomLevel: 12,
      mapPadding: BMFEdgeInsets(left: 30, top: 0, right: 30, bottom: 200));
@@ -55,11 +47,23 @@
  TextEditingController? editingController;
  List<BMFPoiInfo>? suggestList;
  List<BMFPoiInfo>? recordList;
  BMFCoordinate? currentPosition;
  @override
  void initState() {
    super.initState();
    editingController = TextEditingController();
    loadRecord();
  }
  //加载记录页面
  void loadRecord(){
    SearchUtil.getRecordList().then((value) {
      setState(() {
        recordList = value;
      });
    });
  }
  //获取地图视图
@@ -68,12 +72,11 @@
      child: BMFMapWidget(
        onBMFMapCreated: (controller) {
          _mapController = controller;
          //启动定位
          LocationUtil.init(0);
          LocationUtil.startLocation((map) {
            double lat = map["latitude"] as double;
            double lng = map["logitude"] as double;
            _mapController!.setCenterCoordinate(BMFCoordinate(lng,lat), true);
          LocationUtil.startLocation(0, (state, map) {
            if (state == LocationState.success) {
              currentPosition = BMFCoordinate.fromMap(map!);
              _mapController!.setCenterCoordinate(currentPosition!, true);
            }
          });
        },
        mapOptions: mapOptions,
@@ -237,13 +240,14 @@
            child: Container(
          color: Colors.white,
          padding: const EdgeInsets.only(left: 20, right: 20),
          child: Column(
            children: [getHistoryItem("融恒盈嘉中心-西门", "重庆市江北区", 0)],
          ),
          child:  ListView.builder(itemBuilder: (BuildContext context, int index){
              return getHistoryItem(recordList![index].name!, recordList![index].address!, recordList![index]);
            },itemCount: recordList==null?0:recordList!.length,) ,
        ))
      ],
    );
  }
  setSearchKey(String key) {
    editingController!.text = key;
@@ -251,6 +255,16 @@
        baseOffset: key.length, extentOffset: editingController!.text.length);
    startPOISearch(key);
  }
  // static const messageChannel =
  //     const BasicMessageChannel('POISearch', StandardMessageCodec());
  //
  // void searchPOI() async {
  //   Map value = await messageChannel.send({"method": "searchNearBy"}) as Map;
  //   if (value["code"] == 0) {
  //     print("返回结果为:$value");
  //   }
  // }
  startPOISearch(String key) async {
    setState(() {
@@ -261,15 +275,18 @@
        state = 1;
    });
    print("startPOISearch-$key");
    BMFPoiNearbySearchOption poiNearbySearchOption = BMFPoiNearbySearchOption(
        keywords: <String>[key],
        location: BMFCoordinate(40.049557, 116.279295),
        isRadiusLimit: false);
        location: currentPosition != null
            ? currentPosition
            : BMFCoordinate(29.674509, 106.517571),
        radius: 10000,
        isRadiusLimit: true);
    BMFPoiNearbySearch nearbySearch = BMFPoiNearbySearch();
    nearbySearch.onGetPoiNearbySearchResult(
        callback: (BMFPoiSearchResult result, BMFSearchErrorCode errorCode) {
      print("搜索结果回调:$result");
      setState(() {
        suggestList = result.poiInfoList;
      });
@@ -314,7 +331,7 @@
                  alignment: Alignment.center, child: Text("暂无数据"));
            } else {
              return getHistoryItem(suggestList![index].name!,
                  suggestList![index].address!, index);
                  suggestList![index].address!, suggestList![index]);
            }
          },
          itemCount: suggestList != null ? suggestList!.length : 1,
@@ -323,10 +340,10 @@
  }
  //历史记录项目
  Widget getHistoryItem(String title, String content, int index) {
  Widget getHistoryItem(String title, String content, BMFPoiInfo info,{bool showDistance=false}) {
    return InkWell(
        onTap: () {
          print("点击:$index");
          SearchUtil.addSearchRecord(info);
        },
        child: Container(
          height: 67,
lib/ui/map/travel.dart
@@ -12,6 +12,7 @@
import 'package:flutter_datetime_picker/flutter_datetime_picker.dart';
import 'package:locations/ui/widget/button.dart';
import 'package:locations/ui/widget/nav.dart';
import 'package:locations/utils/global.dart';
import 'package:locations/utils/pageutils.dart';
import 'package:locations/utils/ui_constant.dart';
import 'package:path_provider/path_provider.dart';
@@ -331,7 +332,9 @@
  BMFMapOptions mapOptions = BMFMapOptions(
      showMapScaleBar: false,
      mapType: BMFMapType.Standard,
      center: BMFCoordinate(39.917215, 116.380341),
      center: Global.currentPosition != null
          ? Global.currentPosition
          : BMFCoordinate(39.917215, 116.380341),
      mapScaleBarPosition: BMFPoint(0, 200),
      zoomLevel: 12,
      zoomEnabled: false,
@@ -710,7 +713,7 @@
        File(shareImgPath!).deleteSync();
      }
      File(shareImgPath!).createSync();
      File(shareImgPath!).writeAsBytesSync(pngBytes!);
      File(shareImgPath!).writeAsBytesSync(pngBytes);
      return File(shareImgPath!);
    } catch (e) {
    }
lib/ui/mine/add_location_person.dart
@@ -2,10 +2,83 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:locations/api/http.dart';
import 'package:locations/model/map/location_user_model.dart';
import 'package:locations/ui/widget/base_ui.dart';
import 'package:locations/ui/widget/button.dart';
import 'package:locations/ui/widget/dialog.dart';
import 'package:locations/ui/widget/nav.dart';
import 'package:locations/utils/string_util.dart';
import 'package:locations/utils/ui_constant.dart';
import 'package:locations/utils/ui_utils.dart';
import 'package:locations/utils/user_util.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';
void main() {
  runApp(MyApp());
}
Image _getPortrait(LocationUserType? type) {
  String img = "assets/images/user/";
  switch (type) {
    case LocationUserType.father:
      img += "icon_user_type_father.png";
      break;
    case LocationUserType.mother:
      img += "icon_user_type_mother.png";
      break;
    case LocationUserType.husband:
      img += "icon_user_type_husband.png";
      break;
    case LocationUserType.wife:
      img += "icon_user_type_wife.png";
      break;
    case LocationUserType.daughter:
      img += "icon_user_type_daughter.png";
      break;
    case LocationUserType.son:
      img += "icon_user_type_son.png";
      break;
    case LocationUserType.other:
      img += "icon_user_type_other.png";
  }
  return Image.asset(
    img,
    height: 31,
  );
}
String _getName(LocationUserType? type) {
  String img = "assets/images/user/";
  switch (type) {
    case LocationUserType.father:
      return "父亲";
    case LocationUserType.mother:
      return "母亲";
    case LocationUserType.husband:
      return "丈夫";
    case LocationUserType.wife:
      return "妻子";
    case LocationUserType.daughter:
      return "女儿";
    case LocationUserType.son:
      return "儿子";
    case LocationUserType.other:
      return "其他";
  }
  return "";
}
//控件阴影
List<BoxShadow> getViewShadow() {
  return [
    BoxShadow(
      blurRadius: 6.5,
      spreadRadius: 1,
      color: Color(0x4D0E96FF),
    )
  ];
}
class MyApp extends StatelessWidget {
@@ -40,66 +113,694 @@
class _AddLocationPersonPageState extends State<AddLocationPersonPage>
    with SingleTickerProviderStateMixin {
  List<LocationUserModel> userList = [];
  //选中的定位对象索引
  int selectUserIndex = -1;
  LocationUserType? selectedUserType;
  @override
  void initState() {
    super.initState();
    _onRefresh();
  }
  RefreshController _refreshController =
      RefreshController(initialRefresh: false);
  void _onRefresh() async {
    int? uid = await UserUtil.getUid();
    Map<String, dynamic>? result =
        await LocationApiUtil.getLocationUsers(uid!, 1);
    print("result:$result");
    _refreshController.refreshCompleted();
    if (result!["code"] == 0) {
      List<dynamic> list = result!["data"]["list"];
      List<LocationUserModel> users = [];
      list.forEach((element) {
        users.add(LocationUserModel.fromJson(element));
      });
      setState(() {
        userList = users;
      });
    } else {
      ToastUtil.toast(result!["msg"]);
    }
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        resizeToAvoidBottomInset: false,
        backgroundColor: Color(0xFFB4E4FF),
        body: Container(
          child: Flex(
            direction: Axis.vertical,
            children: [
              TopNavBar(title: "添加定位对象"),
              Image.asset(
                  "assets/images/common/ic_add_location_person_top.png"),
              Expanded(
                child: Container(
                  margin: EdgeInsets.all(10),
                  padding: EdgeInsets.fromLTRB(27, 0, 27, 0),
                  constraints: BoxConstraints(maxHeight: 500),
                  margin: const EdgeInsets.all(10),
                  // constraints: const BoxConstraints(maxHeight: 500),
                  decoration: BoxDecoration(
                      color: Colors.white,
                      borderRadius: BorderRadius.circular(10)),
                      // color: Colors.white,
                      borderRadius: BorderRadius.circular(0)),
                  child: SmartRefresher(
                      enablePullDown: true,
                      header: refreshHeader,
                      onRefresh: _onRefresh,
                      controller: _refreshController,
                      child: ListView.builder(
                          padding: EdgeInsets.zero,
                          itemCount: userList.length + 3,
                          itemBuilder: (BuildContext context, int index) {
                            //添加按钮
                            if (index >= userList.length) {
                              return _getAddItem(index - userList.length);
                            } else {
                              //用户列表
                              return _getUserItem(userList[index], index);
                            }
                            return Container();
                          })),
                ),
              ),
              Container(
                  alignment: Alignment.center,
                  child: Container(
                      height: 77,
                      color: Colors.white,
              //添加
              selectUserIndex < 0
                  ? Container(
                      alignment: Alignment.center,
                      padding: EdgeInsets.all(10),
                      child: InkWell(
                      child: Container(
                          height: 77,
                          color: Colors.white,
                          alignment: Alignment.center,
                          padding: const EdgeInsets.all(10),
                          child: InkWell(
                            onTap: () {
                              showAddPersonDialog(-1);
                            },
                            child: Container(
                              height: 54,
                              decoration: BoxDecoration(
                                  border: Border.all(color: Color(0xFF0E96FF)),
                                  color: Colors.white,
                                  borderRadius: const BorderRadius.all(
                                      Radius.circular(10))),
                              child: Flex(
                                direction: Axis.horizontal,
                                mainAxisAlignment: MainAxisAlignment.center,
                                crossAxisAlignment: CrossAxisAlignment.center,
                                children: [
                                  Image.asset(
                                    "assets/images/common/icon_btn_watch_ad.png",
                                    height: 16,
                                  ),
                                  const Text(
                                    " 添加其他定位对象",
                                    style: TextStyle(
                                        color: ColorConstant.theme,
                                        fontSize: 15),
                                  )
                                ],
                              ),
                            ),
                          )))
                  :
                  //编辑工具栏
                  Container(
                      height: 55,
                      margin:
                          const EdgeInsets.only(top: 3, left: 10, right: 10),
                      decoration: BoxDecoration(
                          color: Colors.white,
                          borderRadius: const BorderRadius.only(
                              topLeft: Radius.circular(10),
                              topRight: Radius.circular(10)),
                          boxShadow: getViewShadow()),
                      child: Row(
                        children: [
                          Expanded(
                              child: InkWell(
                            onTap: () {
                              showUpdatePersonDialog(userList[selectUserIndex]);
                            },
                            child: Container(
                              child: Flex(
                                mainAxisAlignment: MainAxisAlignment.center,
                                crossAxisAlignment: CrossAxisAlignment.center,
                                direction: Axis.horizontal,
                                children: [
                                  Image.asset(
                                    "assets/images/common/icon_edit.png",
                                    height: 17,
                                  ),
                                  Container(
                                    width: 8,
                                  ),
                                  const Text(
                                    "编辑",
                                    style: TextStyle(
                                        color: ColorConstant.theme,
                                        fontSize: 15),
                                  )
                                ],
                              ),
                            ),
                          )),
                          Container(
                            height: 28,
                            width: 1,
                            color: ColorConstant.theme,
                          ),
                          Expanded(
                              child: InkWell(
                            onTap: () {
                              showGeneralDialog(
                                  context: context,
                                  pageBuilder: (BuildContext buildContext,
                                      Animation<double> animation,
                                      Animation<double> secondaryAnimation) {
                                    return NotifyDialog(
                                        "温馨提示", "你确认要删除该定位对象吗?", () {}, () {
                                      UserUtil.getUid().then((value) {
                                        LocationApiUtil.deleteLocationUser(
                                                context,
                                                value!,
                                                userList[selectUserIndex].id)
                                            .then((value) {
                                          if (value!["code"] == 0) {
                                            ToastUtil.toast("删除成功");
                                          } else {
                                            ToastUtil.toast(value!["msg"]);
                                          }
                                        });
                                      });
                                    });
                                  }).then((value) {
                                _onRefresh();
                              });
                            },
                            child: Container(
                              child: Flex(
                                mainAxisAlignment: MainAxisAlignment.center,
                                crossAxisAlignment: CrossAxisAlignment.center,
                                direction: Axis.horizontal,
                                children: [
                                  Image.asset(
                                    "assets/images/common/icon_delete.png",
                                    height: 17,
                                  ),
                                  Container(
                                    width: 8,
                                  ),
                                  const Text(
                                    "删除",
                                    style: TextStyle(
                                        color: ColorConstant.theme,
                                        fontSize: 15),
                                  )
                                ],
                              ),
                            ),
                          ))
                        ],
                      ),
                    )
            ],
          ),
        ));
  }
  void showAddPersonDialog(int index) async {
    if (index == 0) {
      selectedUserType = LocationUserType.father;
    } else if (index == 1) {
      selectedUserType = LocationUserType.husband;
    } else if (index == 2) {
      selectedUserType = LocationUserType.daughter;
    } else {
      selectedUserType = LocationUserType.other;
    }
    await showGeneralDialog(
        context: context,
        pageBuilder: (BuildContext buildContext, Animation<double> animation,
                Animation<double> secondaryAnimation) =>
            CustomDialog(AddDialogPage(selectedUserType)));
    _onRefresh();
  }
  void showUpdatePersonDialog(LocationUserModel user) async {
    await showGeneralDialog(
        context: context,
        pageBuilder: (BuildContext buildContext, Animation<double> animation,
                Animation<double> secondaryAnimation) =>
            CustomDialog(AddDialogPage(
              null,
              locationUserModel: user,
            )));
    _onRefresh();
  }
  Widget _getStatus(LocationInviteStatus? status) {
    switch (status) {
      case LocationInviteStatus.sentInvite:
        return const Text(
          "等待同意",
          style: TextStyle(color: ColorConstant.theme, fontSize: 15),
        );
      case LocationInviteStatus.agree:
        return InkWell(
            onTap: () {},
            child: Container(
              padding: const EdgeInsets.only(left: 10, right: 10),
              height: 25,
              decoration: BoxDecoration(
                  color: Colors.white,
                  border: Border.all(color: ColorConstant.theme),
                  borderRadius: BorderRadius.circular(10)),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.center,
                crossAxisAlignment: CrossAxisAlignment.center,
                children: [
                  Image.asset(
                    "assets/images/user/icon_user_location.png",
                    height: 15,
                  ),
                  Container(
                    width: 5,
                  ),
                  const Text(
                    "去定位",
                    style: TextStyle(color: ColorConstant.theme, fontSize: 15),
                  )
                ],
              ),
            ));
      case LocationInviteStatus.reject:
        return const Text(
          "对方拒接",
          style: TextStyle(color: Color(0xFF9DAAB3), fontSize: 15),
        );
    }
    return Container();
  }
  Widget _getUserItem(LocationUserModel user, int index) {
    return InkWell(
        onTap: () {
          if (index == selectUserIndex) {
            setState(() {
              selectUserIndex = -1;
            });
          } else {
            setState(() {
              selectUserIndex = index;
            });
          }
        },
        child: Column(children: [
          Container(
              padding: const EdgeInsets.fromLTRB(27, 0, 27, 0),
              height: 80,
              decoration: BoxDecoration(
                  color: index == selectUserIndex
                      ? const Color(0xFFF0F8FF)
                      : Colors.white,
                  borderRadius: index == 0
                      ? const BorderRadius.only(
                          topLeft: Radius.circular(10),
                          topRight: Radius.circular(10))
                      : BorderRadius.zero),
              child: Row(
                children: [
                  Container(
                    width: 40,
                    alignment: Alignment.center,
                    child: _getPortrait(user.userType),
                  ),
                  Container(
                    width: 8,
                  ),
                  Expanded(
                      child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        user.targetName!,
                        style: const TextStyle(
                            fontSize: 18, color: ColorConstant.theme),
                      ),
                      Container(
                        height: 5,
                      ),
                      Text(
                        user.targetPhone!,
                        style: const TextStyle(
                            fontSize: 15, color: Color(0xFF9DAAB3)),
                      ),
                    ],
                  )),
                  _getStatus(user.status)
                ],
              )),
          Container(
            height: 1,
            color: Colors.white,
            padding: const EdgeInsets.only(left: 27, right: 27),
            child: Container(
              color: const Color(0xFF9DAAB3),
            ),
          )
        ]));
  }
  Widget _getAddItem(int index) {
    String name = "";
    String img = "";
    if (index == 0) {
      name = "定位父母";
      img = "assets/images/user/icon_user_type_father_mother.png";
    } else if (index == 1) {
      name = "定位爱人";
      img = "assets/images/user/icon_user_type_husband_wife.png";
    } else {
      name = "定位子女";
      img = "assets/images/user/icon_user_type_daughter_son.png";
    }
    return InkWell(
        onTap: () {
          showAddPersonDialog(index);
        },
        child: Column(children: [
          Container(
              padding: const EdgeInsets.fromLTRB(27, 0, 27, 0),
              height: 80,
              decoration: BoxDecoration(
                  color: Colors.white,
                  borderRadius: index == 2
                      ? const BorderRadius.only(
                          bottomLeft: Radius.circular(10),
                          bottomRight: Radius.circular(10))
                      : (index == 0 && userList.length == 0
                          ? const BorderRadius.only(
                              topLeft: Radius.circular(10),
                              topRight: Radius.circular(10))
                          : BorderRadius.zero)),
              child: Row(
                children: [
                  Container(
                    width: 40,
                    alignment: Alignment.center,
                    child: Image.asset(
                      img,
                      height: 31,
                    ),
                  ),
                  Container(
                    width: 10,
                  ),
                  Expanded(
                      child: Text(
                    name,
                    style:
                        const TextStyle(fontSize: 15, color: Color(0xFF9DAAB3)),
                  )),
                  Image.asset(
                    "assets/images/user/icon_user_add.png",
                    width: 23,
                    height: 23,
                  )
                ],
              )),
          Container(
            height: index > 1 ? 0 : 1,
            color: Colors.white,
            padding: const EdgeInsets.only(left: 27, right: 27),
            child: Container(
              color: const Color(0xFF9DAAB3),
            ),
          )
        ]));
  }
}
class AddDialogPage extends StatefulWidget {
  LocationUserType? selectedUserType;
  LocationUserModel? locationUserModel;
  AddDialogPage(this.selectedUserType, {this.locationUserModel});
  @override
  _AddDialogPageState createState() =>
      _AddDialogPageState(selectedUserType, locationUserModel);
}
class _AddDialogPageState extends State<AddDialogPage> {
  LocationUserType? selectedUserType;
  LocationUserModel? locationUserModel;
  TextEditingController? phoneController = TextEditingController();
  _AddDialogPageState(this.selectedUserType, this.locationUserModel);
  List<LocationUserType> userTypelist = [
    LocationUserType.father,
    LocationUserType.mother,
    LocationUserType.husband,
    LocationUserType.wife,
    LocationUserType.daughter,
    LocationUserType.son,
    LocationUserType.other,
  ];
  @override
  void initState() {
    super.initState();
    if (locationUserModel != null) {
      phoneController!.text = locationUserModel!.targetPhone!;
      selectedUserType = locationUserModel!.userType;
    }
  }
  void selectUserType(int index) async {
    // switch (index) {
    //   case 0:
    //     userTypelist = [LocationUserType.father, LocationUserType.mother];
    //     break;
    //   case 1:
    //     userTypelist = [LocationUserType.husband, LocationUserType.wife];
    //     break;
    //   case 2:
    //     userTypelist = [LocationUserType.daughter, LocationUserType.son];
    //     break;
    // }
    var selectUserType = await showGeneralDialog(
        context: context,
        pageBuilder: (BuildContext buildContext, Animation<double> animation,
                Animation<double> secondaryAnimation) =>
            ListViewDialog(
              ListView.builder(
                  itemCount: userTypelist.length,
                  itemBuilder: (BuildContext context, int index) {
                    LocationUserType userType = userTypelist[index];
                    //选着
                    return InkWell(
                        onTap: () {
                          Navigator.of(context).pop(userType);
                        },
                        child: Container(
                          height: 54,
                          decoration: BoxDecoration(
                              border: Border.all(color: Color(0xFF0E96FF)),
                              color: Colors.white,
                              borderRadius:
                                  BorderRadius.all(Radius.circular(10))),
                          child: Flex(
                            direction: Axis.horizontal,
                          padding: EdgeInsets.only(top: 5, bottom: 5),
                          child: Row(
                            mainAxisAlignment: MainAxisAlignment.center,
                            crossAxisAlignment: CrossAxisAlignment.center,
                            children: [
                              Image.asset(
                                "assets/images/common/icon_btn_watch_ad.png",
                                height: 16,
                              _getPortrait(userType),
                              Container(
                                width: 20,
                              ),
                              Text(
                                " 添加其他定位对象",
                                style: TextStyle(
                                    color: ColorConstant.theme, fontSize: 15),
                                _getName(userType),
                                style: TextStyle(color: ColorConstant.theme),
                              )
                            ],
                          ),
                        ),
                      ))),
                        ));
                  }),
              () {
                Navigator.of(context).pop();
              },
              dwidth: 100,
              maxHeight: 330,
            ));
    print("返回结果:$selectUserType");
    if (selectUserType != null) {
      setState(() {
        selectedUserType = selectUserType! as LocationUserType?;
        print("赋值结果:$selectedUserType");
      });
    }
  }
  @override
  Widget build(BuildContext context) {
    double width = MediaQuery.of(context).size.width;
    double dialogWidth = width * 310 / 375;
    return ClipRRect(
        borderRadius: BorderRadius.circular(15),
        child: Container(
          color: Colors.white,
          width: dialogWidth,
          height: 290,
          child: Column(
            children: [
              Container(
                  alignment: Alignment.center,
                  height: 60,
                  child: const Text(
                    "添加定位人",
                    style: TextStyle(fontSize: 18, color: Colors.white),
                  ),
                  decoration: const BoxDecoration(
                    color: Color(0xFF1CC7FF),
                    borderRadius: BorderRadius.only(
                        topLeft: Radius.circular(15),
                        topRight: Radius.circular(15)),
                  )),
              Padding(
                padding: const EdgeInsets.all(15),
                child: Column(
                  children: [
                    Container(
                      height: 20,
                    ),
                    //选择身份
                    InkWell(
                        onTap: () {
                          selectUserType(0);
                        },
                        child: Container(
                          height: 42.5,
                          decoration: BoxDecoration(
                              color: const Color(0xFFF5F5F5),
                              borderRadius: BorderRadius.circular(10)),
                          child: Row(
                            children: [
                              Container(
                                  width: 40,
                                  alignment: Alignment.center,
                                  child: _getPortrait(selectedUserType!)),
                              Expanded(
                                  child: Text(_getName(selectedUserType!),
                                      style: const TextStyle(
                                          color: Color(0xFFA0A0A0),
                                          fontSize: 12))),
                            ],
                          ),
                        )),
                    Container(
                      height: 10,
                    ),
//电话/ID
                    Container(
                      height: 42.5,
                      decoration: BoxDecoration(
                          color: const Color(0xFFF5F5F5),
                          borderRadius: BorderRadius.circular(10)),
                      child: Row(
                        crossAxisAlignment: CrossAxisAlignment.center,
                        children: [
                          Container(
                            width: 40,
                            alignment: Alignment.center,
                            child: Image.asset(
                              "assets/images/login/icon_phone.png",
                              width: 14,
                              height: 20,
                            ),
                          ),
                          Expanded(
                              child: TextField(
                                  style: const TextStyle(
                                      color: Color(0xFF333333), fontSize: 17),
                                  onChanged: (value) {},
                                  textAlign: TextAlign.start,
                                  keyboardType: TextInputType.phone,
                                  controller: phoneController,
                                  maxLength: 11,
                                  decoration: const InputDecoration(
                                    counterText: "",
                                    hintText: "请输入手机号或ID",
                                    hintStyle: TextStyle(
                                        color: Color(0xFFA0A0A0), fontSize: 12),
                                    contentPadding: EdgeInsets.only(bottom: 8),
                                    border: InputBorder.none,
                                    focusedBorder: InputBorder.none,
                                  ))),
                        ],
                      ),
                    ),
                    Container(
                      height: 40,
                    ),
                    MyFillButton(
                      "确定",
                      10,
                      height: 44,
                      fontSize: 18,
                      onClick: () {
                        String phone = phoneController!.value.text;
                        if (StringUtil.isNullOrEmpty(phone)) {
                          ToastUtil.toast("请输入电话号码");
                          return;
                        }
                        UserUtil.getUid().then((value) {
                          if (locationUserModel != null) {
                            LocationApiUtil.updateLocationUser(context,
                                    locationUserModel!.id!,
                                    phoneController!.value.text,
                                    selectedUserType)
                                .then((value) {
                              if (value!["code"] == 0) {
                                ToastUtil.toast("修改成功");
                                Navigator.of(context).pop();
                              } else {
                                ToastUtil.toast(value!["msg"]);
                              }
                            });
                          } else {
                            LocationApiUtil.addLocationUser(
                                    context,
                                    value!,
                                    _getName(selectedUserType),
                                    phone,
                                    selectedUserType!)
                                .then((value) {
                              if (value!["code"] == 0) {
                                ToastUtil.toast("添加成功");
                                Navigator.of(context).pop();
                              } else {
                                ToastUtil.toast(value!["msg"]);
                              }
                            });
                          }
                        });
                      },
                    )
                  ],
                ),
              )
            ],
          ),
        ));
lib/ui/mine/login.dart
@@ -5,20 +5,22 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_html/flutter_html.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:fluwx_no_pay/fluwx_no_pay.dart' as fluwx;
import 'package:html/dom.dart' as dom;
import 'package:locations/api/http.dart';
import 'package:locations/model/user/user_info.dart';
import 'package:locations/ui/widget/button.dart';
import 'package:locations/ui/widget/nav.dart';
import 'package:locations/utils/pageutils.dart';
import 'package:locations/utils/event_bus_util.dart';
import 'package:locations/utils/push_util.dart';
import 'package:locations/utils/string_util.dart';
import 'package:locations/utils/ui_constant.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:locations/utils/user_util.dart';
import 'advice_submit.dart';
void main() {
  runApp(MyApp());
}
class MyApp extends StatelessWidget {
  // This widget is the root of your application.
@@ -60,7 +62,6 @@
  String code = "";
  Timer? timer;
  //重新发送验证码倒计时
  int? reSendSMSTimeLeft;
@@ -84,10 +85,10 @@
    });
  }
  //阿里云一键登录
  static const messageChannel = const BasicMessageChannel(
  static const messageChannel = BasicMessageChannel(
      'AliyunPhoneNumberAuth', StandardMessageCodec());
  void aliyunOneKeyLogin() async {
    Map value = await messageChannel.send({
      "method": "init",
@@ -100,9 +101,20 @@
      if (value["code"] == 0) {
        value = await messageChannel.send({"method": "startLogin"}) as Map;
        if (value["code"] == 0) {
          Fluttertoast.showToast(msg: "token:" + value["token"]);
          await messageChannel.send({"method": "closeLogin"});
          // Fluttertoast.showToast(msg: "token:" + value["token"]);
          Map<String, dynamic>? resultValue =
              await UserApiUtil.login(context, "", "", value["token"]);
          if (resultValue == null) return;
          if (resultValue!["code"] == 0) {
            UserInfo user = UserInfo.fromJson(resultValue["data"]);
            _loginSuccess(user);
          } else {
            Fluttertoast.showToast(msg: value!["msg"]);
          }
        } else if (value["code"] == 700000) {
          Fluttertoast.showToast(msg: "取消登录了");
          //Fluttertoast.showToast(msg: "取消登录了");
        }
      } else {
        Fluttertoast.showToast(msg: value["msg"]);
@@ -119,6 +131,16 @@
    Map value = await UserUtil.loginQQ();
  }
  void _loginSuccess(UserInfo user){
    UserUtil.setUserInfo(user).then((value) {
      print("登录成功");
      eventBus.fire(LoginEventBus(true));
      PushUtil.setAlias(user.id!.toString()).then((value) {
        Navigator.pop(context);
      });
    });
  }
  @override
  void dispose() {
    if (timer != null) {
@@ -129,7 +151,7 @@
  BoxDecoration getItemDecoration(Color bgColor, Color shadowColor) {
    return BoxDecoration(
        borderRadius: BorderRadius.all(Radius.elliptical(10, 10)),
        borderRadius: const BorderRadius.all(Radius.elliptical(10, 10)),
        color: bgColor,
        boxShadow: [
          BoxShadow(
@@ -182,10 +204,10 @@
                                  mainAxisAlignment:
                                      MainAxisAlignment.spaceAround,
                                  children: [
                                    getThirdLoginItem("微信登录",
                                        "assets/images/login/ic_login_wx.png"),
                                    getThirdLoginItem("QQ登录",
                                        "assets/images/login/ic_login_qq.png"),
                                    // getThirdLoginItem("微信登录",
                                    //     "assets/images/login/ic_login_wx.png"),
                                    // getThirdLoginItem("QQ登录",
                                    //     "assets/images/login/ic_login_qq.png"),
                                    oneKeyLogin
                                        ? getThirdLoginItem("手机号登录",
                                            "assets/images/login/ic_login_phone.png")
@@ -356,19 +378,26 @@
                            StringUtil.isMobile(phoneController!.value.text))) {
                          return;
                        }
                        setState(() {
                          reSendSMSTimeLeft = 60;
                          //倒计时
                          timer = Timer.periodic(const Duration(seconds: 1),
                              (timer) {
                            if (reSendSMSTimeLeft! > 0) {
                              setState(() {
                                reSendSMSTimeLeft = reSendSMSTimeLeft! - 1;
                        //发送验证码
                        UserApiUtil.sendSMS(
                                context, phoneController!.value.text)
                            .then((value) {
                          if (value != null && value["code"] == 0) {
                            setState(() {
                              reSendSMSTimeLeft = 60;
                              //倒计时
                              timer = Timer.periodic(const Duration(seconds: 1),
                                  (timer) {
                                if (reSendSMSTimeLeft! > 0) {
                                  setState(() {
                                    reSendSMSTimeLeft = reSendSMSTimeLeft! - 1;
                                  });
                                } else {
                                  timer.cancel();
                                }
                              });
                            } else {
                              timer.cancel();
                            }
                          });
                            });
                          }
                        });
                      },
                    ),
@@ -380,7 +409,7 @@
                  style: TextStyle(color: Color(0xFFA0A0A0), fontSize: 12)),
              Container(height: 20),
              MyFillButton(
                "获取短信验证码",
                "登录",
                10,
                height: 45,
                color: const Color(0xFFFF2B4B),
@@ -392,6 +421,23 @@
                      codeController!.value.text.length >= 4)) {
                    return;
                  }
                  if (!checked) {
                    Fluttertoast.showToast(msg: "请同意用户协议与隐私政策");
                    return;
                  }
                  UserApiUtil.login(context, phoneController!.value.text,
                          codeController!.value.text, "")
                      .then((value) {
                        print("结果: $value");
                    if (value!["code"] == 0) {
                      UserInfo user = UserInfo.fromJson(value["data"]);
                      _loginSuccess(user);
                    } else {
                      Fluttertoast.showToast(msg: value!["msg"]);
                    }
                  });
                },
              ),
            ],
lib/ui/mine/settings.dart
@@ -2,12 +2,18 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:locations/api/http.dart';
import 'package:locations/ui/widget/nav.dart';
import 'package:locations/utils/event_bus_util.dart';
import 'package:locations/utils/push_util.dart';
import 'package:locations/utils/ui_constant.dart';
import 'package:locations/utils/ui_utils.dart';
import 'package:locations/utils/user_util.dart';
void main() {
  runApp(MyApp());
}
class MyApp extends StatelessWidget {
  // This widget is the root of your application.
@@ -15,7 +21,7 @@
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '设置',
      theme: ThemeData(primaryColor: Color(0xFFF5F5F5)),
      theme: ThemeData(primaryColor: const Color(0xFFF5F5F5)),
      home: SettingPage(title: ''),
    );
  }
@@ -44,97 +50,120 @@
  bool msg = false;
  bool ad = false;
  bool login = false;
  @override
  void initState() {
    super.initState();
    UserUtil.isLogin().then((value) {
      setState(() {
        login = value;
      });
    });
  }
  Future logout() async {
    var uid = await UserUtil.getUid();
    Map<String, dynamic>? map = await UserApiUtil.logout(context, uid);
    if (map!["code"] == 0) {
      await UserUtil.logout();
      await PushUtil.removeAlias();
      eventBus.fire(LoginEventBus(false));
      setState(() {
        login = false;
      });
    } else {
      ToastUtil.toast(map["msg"]);
    }
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        backgroundColor: Color(0xFFF5F5F5),
        body: Container(
          child: Flex(
            direction: Axis.vertical,
            children: [
              TopNavBar(title: "设置"),
              getBigItemView(
                  title: "推送免打扰",
                  content: "关闭后,21:00-09:00不接受任何推送",
                  marginTop: 14,
                  marginBottom: 1,
                  checked: msg,
                  changed: (bool value) {
                    print(value);
                    setState(() {
                      msg = value;
                    });
                  }),
              getBigItemView(
                  title: "个性化广告推荐",
                  content: "关闭后,广告数量将不变,但内容相关度会降低",
                  marginTop: 0,
                  marginBottom: 16,
                  checked: ad,
                  changed: (bool value) {
                    print(value);
                    setState(() {
                      ad = value;
                    });
                  }),
              getCommonItemView(
                  title: "清理缓存",
                  content: "551.4MB",
                  onClick: () {
                    print("清理缓存");
                  }),
              getCommonItemView(
                  title: "检查更新",
                  content: "版本号2.0.0",
                  onClick: () {
                    print("检查更新");
                  }),
              getCommonItemView(
                  title: "账户注销",
                  content: "",
                  onClick: () {
                    print("账户注销");
                  }),
              getCommonItemView(
                  title: "隐私投诉",
                  content: "",
                  onClick: () {
                    print("隐私投诉");
                  }),
              getCommonItemView(
                  title: "第三方SDK列表",
                  content: "",
                  onClick: () {
                    print("第三方SDK列表");
                  }),
              Expanded(
                child: Container(),
              ),
              Container(
                  child: Stack(
                children: [
                  InkWell(
                      onTap: () {
                        print("退出登录");
                      },
                      child: Container(
                          alignment: Alignment.center,
                          height: 48,
                          color: Colors.white,
                          child: Text(
                            "退出登录",
                            style: TextStyle(
                                color: ColorConstant.theme, fontSize: 16),
                          )))
                ],
              )),
            ],
          ),
        backgroundColor: const Color(0xFFF5F5F5),
        body: Flex(
          direction: Axis.vertical,
          children: [
            TopNavBar(title: "设置"),
            getBigItemView(
                title: "推送免打扰",
                content: "关闭后,21:00-09:00不接受任何推送",
                marginTop: 14,
                marginBottom: 1,
                checked: msg,
                changed: (bool value) {
                  print(value);
                  setState(() {
                    msg = value;
                  });
                }),
            getBigItemView(
                title: "个性化广告推荐",
                content: "关闭后,广告数量将不变,但内容相关度会降低",
                marginTop: 0,
                marginBottom: 16,
                checked: ad,
                changed: (bool value) {
                  print(value);
                  setState(() {
                    ad = value;
                  });
                }),
            getCommonItemView(
                title: "清理缓存",
                content: "551.4MB",
                onClick: () {
                  print("清理缓存");
                }),
            getCommonItemView(
                title: "检查更新",
                content: "版本号2.0.0",
                onClick: () {
                  print("检查更新");
                }),
            getCommonItemView(
                title: "账户注销",
                content: "",
                onClick: () {
                  print("账户注销");
                }),
            getCommonItemView(
                title: "隐私投诉",
                content: "",
                onClick: () {
                  print("隐私投诉");
                }),
            getCommonItemView(
                title: "第三方SDK列表",
                content: "",
                onClick: () {
                  print("第三方SDK列表");
                }),
            Expanded(
              child: Container(),
            ),
            login
                ? Container(
                    child: Stack(
                    children: [
                      InkWell(
                          onTap: () {
                            print("退出登录");
                            logout();
                          },
                          child: Container(
                              alignment: Alignment.center,
                              height: 48,
                              color: Colors.white,
                              child: const Text(
                                "退出登录",
                                style: TextStyle(
                                    color: ColorConstant.theme, fontSize: 16),
                              )))
                    ],
                  ))
                : Container(),
          ],
        ));
  }
@@ -144,21 +173,22 @@
      required GestureTapCallback onClick}) {
    return Container(
      height: 53,
      margin: EdgeInsets.fromLTRB(0, 0, 0, 1),
      margin: const EdgeInsets.fromLTRB(0, 0, 0, 1),
      color: Colors.white,
      child: InkWell(
        onTap: () {
          onClick();
        },
        child: Container(
            padding: EdgeInsets.fromLTRB(20, 0, 20, 0),
            padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
            child: Flex(
              crossAxisAlignment: CrossAxisAlignment.center,
              direction: Axis.horizontal,
              children: [
                Text(
                  title,
                  style: TextStyle(fontSize: 16, color: Color(0xFF333333)),
                  style:
                      const TextStyle(fontSize: 16, color: Color(0xFF333333)),
                ),
                Expanded(
                    child: Flex(
@@ -167,9 +197,9 @@
                  children: [
                    Container(
                      child: Text(content,
                          style: TextStyle(
                          style: const TextStyle(
                              fontSize: 14, color: Color(0xFF959595))),
                      margin: EdgeInsets.fromLTRB(0, 0, 13.5, 0),
                      margin: const EdgeInsets.fromLTRB(0, 0, 13.5, 0),
                    ),
                    Image.asset(
                      "assets/images/common/icon_array_right.png",
@@ -195,7 +225,7 @@
      margin: EdgeInsets.fromLTRB(0, marginTop, 0, marginBottom),
      color: Colors.white,
      child: Container(
          padding: EdgeInsets.fromLTRB(20, 0, 20, 0),
          padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
          child: Stack(children: [
            Flex(
              crossAxisAlignment: CrossAxisAlignment.start,
@@ -208,8 +238,9 @@
                ),
                Container(
                  child: Text(content,
                      style: TextStyle(fontSize: 9, color: Color(0xFF959595))),
                  margin: EdgeInsets.fromLTRB(0, 5, 0, 0),
                      style: const TextStyle(
                          fontSize: 9, color: Color(0xFF959595))),
                  margin: const EdgeInsets.fromLTRB(0, 5, 0, 0),
                )
              ],
            ),
lib/ui/widget/base_ui.dart
New file
@@ -0,0 +1,6 @@
import 'package:flutter/cupertino.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';
WaterDropHeader refreshHeader=const  WaterDropHeader(complete:Text("刷新成功"));
lib/ui/widget/capture.dart
@@ -1,35 +1,52 @@
import 'dart:io';
import 'dart:typed_data';
import 'dart:ui';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:path_provider/path_provider.dart';
///对widget截图
class CaptureWidget extends StatelessWidget {
//截图组件
  GlobalKey rootWidgetKey = GlobalKey();
  final Widget widget;
  final CaptureController captureController;
  CaptureWidget(this.widget);
  CaptureWidget({required this.widget, required this.captureController}) {
    captureController.setGlobalKey(rootWidgetKey);
  }
  @override
  Widget build(BuildContext context) {
    return RepaintBoundary(key: rootWidgetKey, child: widget);
  }
  Future<Uint8List?> capturePng() async {
}
class CaptureController {
  GlobalKey? _globalKey;
  setGlobalKey(GlobalKey globalKey) {
    _globalKey = globalKey;
  }
  Future<File?> capturePng(String path) async {
    try {
      RenderRepaintBoundary? boundary = rootWidgetKey.currentContext!
      RenderRepaintBoundary? boundary = _globalKey!.currentContext!
          .findRenderObject() as RenderRepaintBoundary;
      var image = await boundary.toImage(pixelRatio: 3.0);
      ByteData? byteData = await image.toByteData(format: ImageByteFormat.png);
      Uint8List pngBytes = byteData!.buffer.asUint8List();
      return pngBytes;
      File(path).writeAsBytesSync(pngBytes);
      return File(path);
    } catch (e) {}
    return null;
  }
}
lib/ui/widget/custom.dart
File was deleted
lib/ui/widget/dialog.dart
@@ -2,7 +2,9 @@
import 'package:flutter/material.dart';
import 'package:flutter_html/flutter_html.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:html/dom.dart' as dom;
import 'package:locations/utils/ui_constant.dart';
//通用弹框
class NotifyDialog extends Dialog {
@@ -424,16 +426,21 @@
  final ListView listView;
  final GestureTapCallback onClose;
  final double maxHeight;
  final double? dwidth;
  ListViewDialog(this.listView, this.onClose, {this.maxHeight = 420});
  ListViewDialog(this.listView, this.onClose,
      {this.maxHeight = 420, this.dwidth});
  @override
  Widget build(BuildContext context) {
    this.context = context;
    double width = MediaQuery.of(context).size.width;
    double dialogWidth = width - 20;
    print("屏幕宽:$width");
    double swidth = MediaQuery.of(context).size.width;
    double dialogWidth = swidth - 20;
    print("屏幕宽:$swidth");
    if (dwidth != null) {
      dialogWidth = dwidth!;
    }
    //关闭弹框
    // Navigator.pop(context);
    return WillPopScope(
@@ -446,30 +453,99 @@
                alignment: Alignment.center,
                child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    mainAxisSize:MainAxisSize.min,
                    mainAxisSize: MainAxisSize.min,
                    children: [
                  ClipRRect(
                      borderRadius: BorderRadius.circular(10),
                      child: Container(
                        constraints: BoxConstraints(maxHeight: maxHeight),
                        color: Colors.white,
                        alignment: Alignment.topCenter,
                        width: dialogWidth,
                        child: listView,
                      )),
                  Container(
                    height: 20,
                  ),
                  InkWell(
                    child: Image.asset(
                      "assets/images/common/icon_dialog_close.png",
                      height: 32,
                      width: 32,
                      ClipRRect(
                          borderRadius: BorderRadius.circular(10),
                          child: Container(
                            constraints: BoxConstraints(maxHeight: maxHeight),
                            color: Colors.white,
                            alignment: Alignment.topCenter,
                            width: dialogWidth,
                            child: listView,
                          )),
                      Container(
                        height: 20,
                      ),
                      InkWell(
                        child: Image.asset(
                          "assets/images/common/icon_dialog_close.png",
                          height: 32,
                          width: 32,
                        ),
                        onTap: () {
                          onClose();
                        },
                      ),
                    ]))));
  }
}
///ListView弹框
class LoadingDialog extends Dialog {
  BuildContext? context;
  final String? text;
  LoadingDialog(this.text);
  @override
  Widget build(BuildContext context) {
    this.context = context;
    //关闭弹框
    // Navigator.pop(context);
    return WillPopScope(
        onWillPop: () async {
          return false;
        },
        child: const Material(
            type: MaterialType.transparency,
            child: Align(
                alignment: Alignment.center,
                child: SpinKitCircle(
                  color: ColorConstant.theme,
                  size: 80.0,
                ))));
  }
}
class CustomDialog extends Dialog {
  BuildContext? context;
  final Widget contentWidget;
  CustomDialog(this.contentWidget);
  @override
  Widget build(BuildContext context) {
    this.context = context;
    //关闭弹框
    // Navigator.pop(context);
    return WillPopScope(
        onWillPop: () async {
          return false;
        },
        child: Material(
            type: MaterialType.transparency,
            child: Align(
                alignment: Alignment.center,
                child: Column(
                  mainAxisSize: MainAxisSize.min,
                  crossAxisAlignment: CrossAxisAlignment.center,
                  children: [
                    contentWidget,
                    Container(
                      height: 20,
                    ),
                    onTap: () {
                      onClose();
                    },
                  ),
                ]))));
                    InkWell(
                        onTap: () {
                          Navigator.of(context).pop();
                        },
                        child: Image.asset(
                          "assets/images/common/icon_dialog_close.png",
                          height: 32,
                          width: 32,
                        ))
                  ],
                ))));
  }
}
lib/ui/widget/map_marker.dart
New file
@@ -0,0 +1,149 @@
import 'dart:ui';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:locations/utils/map_util.dart';
import 'package:locations/utils/ui_constant.dart';
import 'package:flutter_baidu_mapapi_map/flutter_baidu_mapapi_map.dart';
import 'package:flutter_baidu_mapapi_base/flutter_baidu_mapapi_base.dart';
import 'dart:math' as math;
//三角形
class TrianglePainter extends CustomPainter {
  final Color color;
  Paint? _paint; //画笔
  Path? _path; //绘制路径
  TrianglePainter(this.color) {
    _paint = Paint()
      ..strokeWidth = 1.0 //线宽
      ..color = color
      ..isAntiAlias = true;
    _path = Path();
  }
  @override
  void paint(Canvas canvas, Size size) {
    final baseX = size.width * 0.5;
    final baseY = size.height * 0.5;
    //起点
    // _path!.moveTo(baseX - 0.86 * baseX, 0.5 * baseY);
    // _path!.lineTo(baseY, 1.5 * baseY);
    // _path!.lineTo(baseX + 0.86 * baseX, 0.5 * baseY);
    _path!.moveTo(size.width / 2, 0);
    _path!.lineTo(size.width, size.height);
    _path!.lineTo(0, size.height);
    canvas.drawPath(_path!, _paint!);
  }
  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return true;
  }
}
///用户位置marker
class PersonLocationMarkerWithOrientation extends StatelessWidget {
  final double angle;
  final Image portrait;
  PersonLocationMarkerWithOrientation(this.angle, this.portrait);
  @override
  Widget build(BuildContext context) {
    return Container(
        width: 70,
        height: 70,
        child: Stack(
          alignment: Alignment.center,
          children: [
            Positioned(
                top: 0,
                child: Transform.rotate(
                  angle: angle,
                  child: Container(
                      height: 69.28,
                      width: 69.28,
                      child: Stack(alignment: Alignment.topCenter, children: [
                        CustomPaint(
                          size: Size(40, 34.64),
                          painter: TrianglePainter(ColorConstant.theme),
                        )
                      ])),
                )),
            Container(
              width: 42,
              height: 42,
              padding: EdgeInsets.all(3),
              decoration: BoxDecoration(
                  color: Colors.white, borderRadius: BorderRadius.circular(40)),
              child: Container(
                width: 42 - 3 * 2,
                height: 42 - 3 * 2,
                padding: EdgeInsets.all(2),
                decoration: BoxDecoration(
                    color: ColorConstant.theme,
                    borderRadius: BorderRadius.circular(40)),
                child: portrait,
              ),
            ),
          ],
        ));
  }
}
//用户(带头像)位置marker
class PersonLocationMarker extends StatelessWidget {
  final Image portrait;
  PersonLocationMarker(this.portrait);
  @override
  Widget build(BuildContext context) {
    return Container(
      width: 42,
      height: 42,
      padding: EdgeInsets.all(3),
      decoration: BoxDecoration(
          color: Colors.white, borderRadius: BorderRadius.circular(40)),
      child: Container(
        width: 42 - 3 * 2,
        height: 42 - 3 * 2,
        padding: EdgeInsets.all(2),
        decoration: BoxDecoration(
            color: ColorConstant.theme,
            borderRadius: BorderRadius.circular(40)),
        child: Image.asset(
          "assets/images/mine/icon_mine_default_portrait.png",
        ),
      ),
    );
  }
}
class MapMarkerUtil {
  ///添加轨迹开始点
  static Future<List<BMFMarker>> addTravelStartMarker(
      BMFMapController? _mapController, BMFCoordinate position) async {
    BMFMarker marker = await MapUtil.addMarker(
        _mapController, position, "assets/images/map/icon_travel_start.png",
        zIndex: 1);
    BMFMarker marker1 = await MapUtil.addMarker(
        _mapController, position, "assets/images/map/icon_travel_start_1.png",
        offset: BMFPoint(0, 38));
    return [marker, marker1];
  }
  //添加轨迹结束点
  static Future<List<BMFMarker>> addTravelEndMarker(
      BMFMapController? _mapController, BMFCoordinate position) async {
    BMFMarker marker = await MapUtil.addMarker(
        _mapController, position, "assets/images/map/icon_travel_end.png",
        zIndex: 1);
    BMFMarker marker1 = await MapUtil.addMarker(
        _mapController, position, "assets/images/map/icon_travel_end_1.png",
        offset: BMFPoint(0, 38));
    return [marker, marker1];
  }
}
lib/utils/app_util.dart
@@ -6,8 +6,13 @@
import 'package:fluwx_no_pay/fluwx_no_pay.dart';
import 'package:flutter_baidu_mapapi_base/src/map/bmf_map_sdk.dart';
import 'package:flutter_baidu_mapapi_base/src/map/bmf_types.dart';
import 'package:flutter/services.dart';
import 'package:jpush_flutter/jpush_flutter.dart';
import 'package:locations/utils/push_util.dart';
class AppUtil {
  static JPush _jpush = JPush();
  //初始化应用
  static Future<bool> initApp(BuildContext context) async {
    print("initApp");
@@ -24,8 +29,13 @@
// 请在主工程的Manifest文件里设置,详细配置方法请参考[https://lbs.baidu.com/ 官网][https://lbs.baidu.com/)demo
      BMFMapSDK.setCoordType(BMF_COORD_TYPE.BD09LL);
    }
    //
    //初始化本地应用
    await _initNativeApp();
    print("初始化完成");
    //初始化极光
    try {
      await PushUtil.init();
    } catch (e) {}
    //初始化应用
    return true;
  }
lib/utils/encrypt_util.dart
New file
@@ -0,0 +1,8 @@
import 'dart:convert';
import 'package:crypto/crypto.dart';
class EncryptUtil {
  static String MD5(String data) {
    return md5.convert(utf8.encode(data)).toString();
  }
}
lib/utils/event_bus_util.dart
New file
@@ -0,0 +1,16 @@
import 'package:locations/model/map/location_model.dart';
import 'package:locations/model/user/user_info.dart';
import 'package:event_bus/event_bus.dart';
EventBus eventBus = EventBus();
class UserLocationInfoEventBus {
  BaiduLocation? location;
  UserInfo? user;
  UserLocationInfoEventBus(this.location, this.user);
}
class LoginEventBus {
  final bool isLogin;
  LoginEventBus(this.isLogin);
}
lib/utils/global.dart
New file
@@ -0,0 +1,5 @@
import 'package:flutter_baidu_mapapi_base/flutter_baidu_mapapi_base.dart';
class Global {
  static BMFCoordinate? currentPosition;
}
lib/utils/location_util.dart
@@ -1,13 +1,21 @@
import 'dart:async';
import 'package:flutter_baidu_mapapi_base/flutter_baidu_mapapi_base.dart';
import 'package:flutter_bmflocation/bdmap_location_flutter_plugin.dart';
import 'package:flutter_bmflocation/flutter_baidu_location.dart';
import 'package:flutter_bmflocation/flutter_baidu_location_android_option.dart';
import 'package:flutter_bmflocation/flutter_baidu_location_ios_option.dart';
import 'package:locations/api/http.dart';
import 'package:locations/model/map/location_model.dart';
import 'package:locations/utils/user_util.dart';
import 'package:permission_handler/permission_handler.dart';
typedef OnLocationCallback = void Function(Map map);
import 'global.dart';
typedef OnLocationCallback = void Function(LocationState state, Map? map);
class LocationUtil {
  static LocationFlutterPlugin? _locationFlutterPlugin=LocationFlutterPlugin();
  static LocationFlutterPlugin? _locationFlutterPlugin =
      LocationFlutterPlugin();
  static Map _getAndroidOption(int scanspan) {
    BaiduLocationAndroidOption androidOption = new BaiduLocationAndroidOption();
@@ -47,7 +55,7 @@
    /// 可选,设置发起定位请求的间隔,int类型,单位ms
    /// 如果设置为0,则代表单次定位,即仅定位一次,默认为0
    /// 如果设置非0,需设置1000ms以上才有效
    // androidOption.setLocationPurpose(BDLocationPurpose.SignIn);
    androidOption.setLocationPurpose(BDLocationPurpose.Sport);
    /// 可选,设置场景定位参数,包括签到场景、运动场景、出行场景,可选值如下:
    /// 签到场景: BDLocationPurpose.SignIn
@@ -111,24 +119,41 @@
    return iosOption.getMap();
  }
  static void init(int scanSpan) {
    _locationFlutterPlugin!.prepareLoc(
        _getAndroidOption(scanSpan), _getIosOption());
  }
  static void startLocation(OnLocationCallback callback) async {
  static Future<StreamSubscription<Map<String, Object>?>?> startLocation(
      int scanSpan, OnLocationCallback callback) async {
    final status = await requestLocationPermission();
    if (status.isGranted) {
      print("定位权限申请通过");
    } else {
      print("定位权限申请不通过");
      callback(LocationState.permission_denied, null);
      return null;
    }
    StreamSubscription<Map<String, Object>?> subscription =
        _locationFlutterPlugin!
            .onResultCallback()
            .listen((Map<String, Object>? result) {
      print("定位结果:$result");
      Global.currentPosition = BMFCoordinate.fromMap(result!);
      UserUtil.isLogin().then((value) {
        if (value) {
          LocationApiUtil.uploadLocation(BaiduLocation.fromJson(result));
        }
      });
      callback(LocationState.success, result);
      if (scanSpan == 0) {
        stopLocation();
      }
    });
    _locationFlutterPlugin!
        .prepareLoc(_getAndroidOption(scanSpan), _getIosOption());
    _locationFlutterPlugin!.startLocation();
    Stream<Map<String, Object>?> result =
        _locationFlutterPlugin!.onResultCallback();
    await for (Map<String, Object>? s in result) {
      callback(s!);
    }
    return subscription;
  }
  ///请求定位权限
@@ -136,4 +161,12 @@
    final status = await Permission.location.request();
    return status;
  }
  static stopLocation() {
    if (_locationFlutterPlugin != null) {
      _locationFlutterPlugin!.stopLocation();
    }
  }
}
enum LocationState { success, permission_denied }
lib/utils/map_util.dart
@@ -4,7 +4,10 @@
import 'package:flutter_baidu_mapapi_utils/flutter_baidu_mapapi_utils.dart';
import 'package:flutter_baidu_mapapi_map/flutter_baidu_mapapi_map.dart';
import 'package:locations/model/map/map_model.dart';
import 'package:locations/ui/widget/map_marker.dart';
import 'package:locations/utils/ui_constant.dart';
///地图工具类
class MapUtil {
  ///画线
  ///https://lbs.baidu.com/index.php?title=flutter/loc/render-map/ployline
@@ -106,9 +109,16 @@
  static Future<BMFMarker> addMarker(
      BMFMapController? _mapController, BMFCoordinate position, String icon,
      {String identifier = "flutter_marker",int zIndex=0,BMFPoint? offset}) async {
    BMFMarker marker =
        BMFMarker(position: position, identifier: identifier, icon: icon,zIndex: zIndex,centerOffset: offset);
      {String identifier = "flutter_marker",
      int zIndex = 0,
      BMFPoint? offset}) async {
    BMFMarker marker = BMFMarker(
        position: position,
        identifier: identifier,
        icon: icon,
        zIndex: zIndex,
        centerOffset: offset);
    /// 添加Marker
    await _mapController?.addMarker(marker);
    return marker;
@@ -118,7 +128,8 @@
    _mapController!.removeMarker(marker);
  }
  static removeMarkers(BMFMapController? _mapController, List<BMFMarker> markers) {
  static removeMarkers(
      BMFMapController? _mapController, List<BMFMarker> markers) {
    _mapController!.removeMarkers(markers);
  }
@@ -126,3 +137,58 @@
    _mapController!.cleanAllMarkers();
  }
}
///路径录制管理
///
class TravelRECManager {
  BMFMapController? mapController;
  List<BMFCoordinate> positionList = [];
  List<BMFMarker> startMarkers = [];
  TravelRECManager(this.mapController);
  //画起始点
  _addStartMarker(BMFCoordinate position) {}
  //画路径
  _drawLine(BMFCoordinate start, BMFCoordinate end) {
    MapUtil.drawLine([start, end], ColorConstant.theme, mapController);
  }
  //开始录制
  startREC(BMFCoordinate currentPosition) {
    positionList.clear();
    positionList.add(currentPosition);
    //画起始点
    MapMarkerUtil.addTravelStartMarker(
        mapController, currentPosition!)
        .then((value) {
      startMarkers = value;
    });
  }
  //添加位置
  addLocation(BMFCoordinate lication) async {
    if (positionList.length < 1) {
      return;
    }
    //计算距离,如果距离在10米内就不添加到数组
    double? distance = await BMFCalculateUtils.getLocationDistance(
        positionList[positionList.length - 1], lication);
    if (distance != null && distance >= 10) {
      //上传位置
      positionList.add(lication);
      _drawLine(positionList[positionList.length - 2],
          positionList[positionList.length - 1]);
    }
  }
  //停止录制
  stopREC() {
    //清除数据
    positionList.clear();
    //删除线
    mapController!.cleanAllMarkers();
  }
}
lib/utils/push_util.dart
New file
@@ -0,0 +1,63 @@
import 'dart:io';
import 'dart:ui';
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
import 'package:fluwx_no_pay/fluwx_no_pay.dart';
import 'package:flutter_baidu_mapapi_base/src/map/bmf_map_sdk.dart';
import 'package:flutter_baidu_mapapi_base/src/map/bmf_types.dart';
import 'package:flutter/services.dart';
import 'package:jpush_flutter/jpush_flutter.dart';
import 'package:locations/model/user/user_info.dart';
import 'package:locations/utils/user_util.dart';
class PushUtil {
  static JPush _jpush = JPush();
  static init() async {
    try {
      _jpush.addEventHandler(
          onReceiveNotification: (Map<String, dynamic> message) async {
        print("flutter onReceiveNotification: $message");
      }, onOpenNotification: (Map<String, dynamic> message) async {
        print("flutter onOpenNotification: $message");
      }, onReceiveMessage: (Map<String, dynamic> message) async {
        print("flutter onReceiveMessage: ${message["message"]}");
      }, onReceiveNotificationAuthorization:
              (Map<String, dynamic> message) async {
        print("flutter onReceiveNotificationAuthorization: $message");
      });
    } on PlatformException {}
    _jpush.setup(
      appKey: "987d3d50f209994fa522d001", //你自己应用的 AppKey
      channel: "developer-default",
      production: false,
      debug: true,
    );
    _jpush.applyPushAuthority(
        NotificationSettingsIOS(sound: true, alert: true, badge: true));
    // Platform messages may fail, so we use a try/catch PlatformException.
    _jpush.getRegistrationID().then((rid) {
      print("flutter get registration id : $rid");
    });
    //如果登录了就设置用户的uid
    bool isLogin = await UserUtil.isLogin();
    if (isLogin) {
      UserInfo? user = await UserUtil.getUserInfo();
      await setAlias(user!.id!.toString());
    }
  }
  ///添加alias
  static Future setAlias(String alias) async {
    await _jpush.setAlias(alias);
  }
  //删除alias
  static Future removeAlias() async {
    await _jpush.deleteAlias();
  }
}
lib/utils/search_util.dart
New file
@@ -0,0 +1,43 @@
import 'package:shared_preferences/shared_preferences.dart';
import 'package:flutter_baidu_mapapi_base/flutter_baidu_mapapi_base.dart';
import 'package:flutter_baidu_mapapi_map/flutter_baidu_mapapi_map.dart';
import 'package:flutter_baidu_mapapi_search/flutter_baidu_mapapi_search.dart';
import 'dart:convert' as convert;
class SearchUtil {
  //添加搜索记录
  static addSearchRecord(BMFPoiInfo info) async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    List<String>? list = prefs.getStringList("searchRecord");
    list ??= [];
    for (int i = 0; i < list.length; i++) {
      BMFPoiInfo poiInfo =BMFPoiInfo.fromMap( convert.jsonDecode(list[i]));
      if (poiInfo.address == info.address) {
        list.removeAt(i);
        break;
      }
    }
    if (list.length > 10) {
      list.removeRange(10, list.length);
    }
    list.add(convert.jsonEncode(info.toMap()));
    prefs.setStringList("searchRecord", list);
  }
  ///获取记录
  static Future<List<BMFPoiInfo>> getRecordList() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    List<String>? list = prefs.getStringList("searchRecord");
    print(list);
    list ??= [];
    List<BMFPoiInfo> resultList = [];
    list.reversed.forEach((element) {
      BMFPoiInfo info =BMFPoiInfo.fromMap( convert.jsonDecode(element));
      resultList.add(info);
    });
    return resultList;
  }
}
lib/utils/string_util.dart
@@ -1,9 +1,13 @@
class StringUtil {
  //是否为电话号码
  static bool isMobile(String str) {
    return RegExp(
            '^((13[0-9])|(15[^4])|(166)|(17[0-8])|(18[0-9])|(19[8-9])|(147,145))\\d{8}\$')
        .hasMatch(str);
  }
  static bool isNullOrEmpty(String? str) {
    return str==null|| str.isEmpty || str.trim().isEmpty;
  }
}
lib/utils/ui_utils.dart
New file
@@ -0,0 +1,7 @@
import 'package:fluttertoast/fluttertoast.dart';
class ToastUtil {
  static toast(String text) {
    Fluttertoast.showToast(msg: text);
  }
}
lib/utils/user_util.dart
@@ -1,13 +1,16 @@
import 'dart:convert';
import 'dart:ui';
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
import 'package:fluwx_no_pay/fluwx_no_pay.dart' as fluwx;
import 'package:locations/model/user/user_info.dart';
import 'package:locations/utils/string_util.dart';
import 'package:shared_preferences/shared_preferences.dart';
class UserUtil {
  static const _loginMessageChannel =
      const BasicMessageChannel('ThirdLogin', StandardMessageCodec());
      BasicMessageChannel('ThirdLogin', StandardMessageCodec());
  //是否同意了用户协议
  static Future<bool> isAgreeProtocol() async {
@@ -32,8 +35,45 @@
        .then((value) {});
  }
  ///QQ登录
  static Future<Map> loginQQ() async {
    Map value = await _loginMessageChannel.send({"method": "loginQQ"}) as Map;
    return value;
  }
  //是否已经登录
  static Future<bool> isLogin() async {
    UserInfo? user = await getUserInfo();
    return user != null;
  }
  //用户信息
  static Future<UserInfo?> getUserInfo() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    String? result = await prefs.getString("user_info");
    if (StringUtil.isNullOrEmpty(result)) {
      return null;
    } else {
      return UserInfo.fromJson(jsonDecode(result!));
    }
  }
  static Future<int?> getUid() async {
    UserInfo? user = await getUserInfo();
    if (user != null) {
      return user.id;
    }
    return null;
  }
  static Future setUserInfo(UserInfo user) async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    await prefs.setString("user_info", jsonEncode(user));
  }
  //退出登录
  static logout() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    prefs.remove("user_info");
  }
}
pubspec.lock
@@ -58,7 +58,7 @@
    source: hosted
    version: "1.15.0"
  crypto:
    dependency: transitive
    dependency: "direct dev"
    description:
      name: crypto
      url: "https://pub.flutter-io.cn"
@@ -78,6 +78,20 @@
      url: "https://pub.flutter-io.cn"
    source: hosted
    version: "1.0.4"
  device_info:
    dependency: "direct dev"
    description:
      name: device_info
      url: "https://pub.flutter-io.cn"
    source: hosted
    version: "2.0.3"
  device_info_platform_interface:
    dependency: transitive
    description:
      name: device_info_platform_interface
      url: "https://pub.flutter-io.cn"
    source: hosted
    version: "2.0.1"
  dio:
    dependency: "direct dev"
    description:
@@ -85,6 +99,13 @@
      url: "https://pub.flutter-io.cn"
    source: hosted
    version: "4.0.1"
  event_bus:
    dependency: "direct dev"
    description:
      name: event_bus
      url: "https://pub.flutter-io.cn"
    source: hosted
    version: "2.0.0"
  fake_async:
    dependency: transitive
    description:
@@ -195,6 +216,13 @@
      url: "https://pub.flutter-io.cn"
    source: hosted
    version: "2.0.4"
  flutter_spinkit:
    dependency: "direct dev"
    description:
      name: flutter_spinkit
      url: "https://pub.flutter-io.cn"
    source: hosted
    version: "5.1.0"
  flutter_svg:
    dependency: transitive
    description:
@@ -261,6 +289,13 @@
      url: "https://pub.flutter-io.cn"
    source: hosted
    version: "4.0.0"
  jpush_flutter:
    dependency: "direct dev"
    description:
      name: jpush_flutter
      url: "https://pub.flutter-io.cn"
    source: hosted
    version: "2.1.7"
  js:
    dependency: transitive
    description:
@@ -344,7 +379,7 @@
      name: path_provider_linux
      url: "https://pub.flutter-io.cn"
    source: hosted
    version: "2.1.0"
    version: "2.1.1"
  path_provider_macos:
    dependency: transitive
    description:
@@ -365,7 +400,7 @@
      name: path_provider_windows
      url: "https://pub.flutter-io.cn"
    source: hosted
    version: "2.0.3"
    version: "2.0.4"
  permission_handler:
    dependency: "direct dev"
    description:
@@ -415,6 +450,13 @@
      url: "https://pub.flutter-io.cn"
    source: hosted
    version: "6.0.1"
  pull_to_refresh:
    dependency: "direct dev"
    description:
      name: pull_to_refresh
      url: "https://pub.flutter-io.cn"
    source: hosted
    version: "2.0.0"
  qr:
    dependency: transitive
    description:
@@ -491,7 +533,7 @@
      name: shared_preferences_linux
      url: "https://pub.flutter-io.cn"
    source: hosted
    version: "2.0.2"
    version: "2.0.3"
  shared_preferences_macos:
    dependency: transitive
    description:
@@ -519,7 +561,7 @@
      name: shared_preferences_windows
      url: "https://pub.flutter-io.cn"
    source: hosted
    version: "2.0.2"
    version: "2.0.3"
  sky_engine:
    dependency: transitive
    description: flutter
@@ -671,7 +713,7 @@
      name: video_player
      url: "https://pub.flutter-io.cn"
    source: hosted
    version: "2.2.6"
    version: "2.2.7"
  video_player_platform_interface:
    dependency: transitive
    description:
@@ -748,7 +790,7 @@
      name: webview_flutter_wkwebview
      url: "https://pub.flutter-io.cn"
    source: hosted
    version: "2.2.0"
    version: "2.3.0"
  win32:
    dependency: transitive
    description:
pubspec.yaml
@@ -89,6 +89,21 @@
  #微信登录
  fluwx_no_pay: ^3.6.1+5
  event_bus: ^2.0.0
  #极光推送
  jpush_flutter: ^2.1.7
  #获取设备信息
  device_info: ^2.0.3
  #加密
  crypto: ^3.0.1
  flutter_spinkit: ^5.1.0
  #下拉刷新
  pull_to_refresh: ^2.0.0
# For information on the generic Dart part of this file, see the
@@ -108,6 +123,7 @@
    - assets/images/advice/
    - assets/images/map/
    - assets/images/login/
    - assets/images/user/
    - assets/images/ic_splash.png
  # To add assets to your application, add an assets section, like this:
  # assets: