Browse Source

Merge branch 'v1.1.0' into v1.1.0-iOS

“HeShaoZe” 4 months ago
parent
commit
677db69fcb

+ 1 - 0
assets/string/base/string.xml

@@ -375,4 +375,5 @@
         1.可能是自己/TA手机解锁权限尚未开启,请在个人中心设置中开启权限\n
         2.可能是TA手机尚未开启本软件
     </string>
+    <string name="track_no_stay_data">暂无停留数据</string>
 </resources>

+ 5 - 0
lib/data/api/atmob_api.dart

@@ -27,6 +27,7 @@ import 'package:location/data/api/response/configs_response.dart';
 import 'package:location/data/api/response/contact_list_response.dart';
 import 'package:location/data/api/response/contact_may_day_all_response.dart';
 import 'package:location/data/api/response/daily_keyword_response.dart';
+import 'package:location/data/api/response/electric_query_response.dart';
 import 'package:location/data/api/response/friends_list_response.dart';
 import 'package:location/data/api/response/item_list_response.dart';
 import 'package:location/data/api/response/location_track_days_response.dart';
@@ -255,4 +256,8 @@ abstract class AtmobApi {
   @POST("/s/v1/location/track/daily/interpret")
   Future<BaseResponse<TrackDailyInterpretResponse>> trackDailyInterpret(
       @Body() QueryTrackRequest request);
+
+  @POST("/s/v1/user/electric/query")
+  Future<BaseResponse<ElectricQueryResponse>> userElectricQuery(
+      @Body() FriendsOperationRequest request);
 }

File diff suppressed because it is too large
+ 846 - 539
lib/data/api/atmob_api.g.dart


+ 14 - 0
lib/data/api/response/electric_query_response.dart

@@ -0,0 +1,14 @@
+import 'package:json_annotation/json_annotation.dart';
+
+part 'electric_query_response.g.dart';
+
+@JsonSerializable()
+class ElectricQueryResponse {
+  @JsonKey(name: "electric")
+  int? electric;
+
+  ElectricQueryResponse({this.electric});
+
+  factory ElectricQueryResponse.fromJson(Map<String, dynamic> json) =>
+      _$ElectricQueryResponseFromJson(json);
+}

+ 19 - 0
lib/data/api/response/electric_query_response.g.dart

@@ -0,0 +1,19 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'electric_query_response.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+ElectricQueryResponse _$ElectricQueryResponseFromJson(
+        Map<String, dynamic> json) =>
+    ElectricQueryResponse(
+      electric: (json['electric'] as num?)?.toInt(),
+    );
+
+Map<String, dynamic> _$ElectricQueryResponseToJson(
+        ElectricQueryResponse instance) =>
+    <String, dynamic>{
+      'electric': instance.electric,
+    };

+ 8 - 0
lib/data/repositories/friends_repository.dart

@@ -11,6 +11,7 @@ import '../../socket/atmob_location_client.dart';
 import '../../utils/atmob_log.dart';
 import '../api/request/add_friend_request.dart';
 import '../api/request/friends_operation_request.dart';
+import '../api/response/electric_query_response.dart';
 import '../api/response/friends_list_response.dart';
 import '../bean/user_info.dart';
 
@@ -31,6 +32,7 @@ class FriendsRepository {
     AtmobLocationClient.addLocationListener((location) {
       updateFriendsLocation(location);
     });
+    refreshFriends();
   }
 
   void updateFriendsLocation(List<LocationInfo> location) {
@@ -134,4 +136,10 @@ class FriendsRepository {
         .getUserInfoFromId(FriendsOperationRequest(friendId: friendId))
         .then(HttpHandler.handle(true));
   }
+
+  Future<ElectricQueryResponse> userElectricQuery(String friendId) {
+    return atmobApi
+        .userElectricQuery(FriendsOperationRequest(friendId: friendId))
+        .then(HttpHandler.handle(true));
+  }
 }

+ 10 - 11
lib/di/get_it.config.dart

@@ -1,4 +1,3 @@
-// dart format width=80
 // GENERATED CODE - DO NOT MODIFY BY HAND
 
 // **************************************************************************
@@ -63,14 +62,14 @@ extension GetItInjectableX on _i174.GetIt {
       environmentFilter,
     );
     final networkModule = _$NetworkModule();
-    gh.factory<_i973.SplashController>(() => _i973.SplashController());
-    gh.factory<_i756.TrackDetailController>(
-        () => _i756.TrackDetailController());
     gh.factory<_i256.AboutController>(() => _i256.AboutController());
-    gh.factory<_i769.FeedBackController>(() => _i769.FeedBackController());
     gh.factory<_i923.BrowserController>(() => _i923.BrowserController());
+    gh.factory<_i769.FeedBackController>(() => _i769.FeedBackController());
     gh.factory<_i108.PermissionSettingController>(
         () => _i108.PermissionSettingController());
+    gh.factory<_i973.SplashController>(() => _i973.SplashController());
+    gh.factory<_i756.TrackDetailController>(
+        () => _i756.TrackDetailController());
     gh.factory<_i779.NewsReportController>(() => _i779.NewsReportController());
     gh.singleton<_i361.Dio>(() => networkModule.createDefaultDio());
     gh.lazySingleton<_i772.InternetConnectionHelper>(
@@ -85,14 +84,14 @@ extension GetItInjectableX on _i174.GetIt {
         .provideAtmobStreamApi(gh<_i361.Dio>(instanceName: 'stream')));
     gh.lazySingleton<_i20.AccountRepository>(
         () => _i20.AccountRepository(gh<_i243.AtmobApi>()));
-    gh.lazySingleton<_i274.PhoneEventRepository>(
-        () => _i274.PhoneEventRepository(gh<_i243.AtmobApi>()));
-    gh.lazySingleton<_i1053.FriendsRepository>(
-        () => _i1053.FriendsRepository(gh<_i243.AtmobApi>()));
     gh.lazySingleton<_i850.ContactRepository>(
         () => _i850.ContactRepository(gh<_i243.AtmobApi>()));
+    gh.lazySingleton<_i1053.FriendsRepository>(
+        () => _i1053.FriendsRepository(gh<_i243.AtmobApi>()));
     gh.lazySingleton<_i791.MessageRepository>(
         () => _i791.MessageRepository(gh<_i243.AtmobApi>()));
+    gh.lazySingleton<_i274.PhoneEventRepository>(
+        () => _i274.PhoneEventRepository(gh<_i243.AtmobApi>()));
     gh.lazySingleton<_i983.UrgentContactRepository>(
         () => _i983.UrgentContactRepository(gh<_i243.AtmobApi>()));
     gh.lazySingleton<_i220.AtmobLocationClient>(
@@ -128,10 +127,10 @@ extension GetItInjectableX on _i174.GetIt {
               gh<_i983.UrgentContactRepository>(),
               gh<_i20.AccountRepository>(),
             ));
-    gh.factory<_i492.FriendSettingController>(
-        () => _i492.FriendSettingController(gh<_i1053.FriendsRepository>()));
     gh.factory<_i897.AddFriendDialogController>(
         () => _i897.AddFriendDialogController(gh<_i1053.FriendsRepository>()));
+    gh.factory<_i492.FriendSettingController>(
+        () => _i492.FriendSettingController(gh<_i1053.FriendsRepository>()));
     gh.lazySingleton<_i814.MemberRepository>(() => _i814.MemberRepository(
           gh<_i243.AtmobApi>(),
           gh<_i20.AccountRepository>(),

+ 1 - 1
lib/module/analyse/location_analyse_page.dart

@@ -48,7 +48,7 @@ class LocationAnalysePage extends BasePage<LocationAnalyseController> {
         '#D8D8FE'.color,
       ], begin: Alignment.topCenter, end: Alignment.bottomCenter)),
       child: Stack(
-        children: [buildBackBtnView(), buildAnalyseView()],
+        children: [buildAnalyseView(), buildBackBtnView()],
       ),
     );
   }

+ 94 - 31
lib/module/main/main_controller.dart

@@ -1,7 +1,5 @@
 import 'dart:async';
-import 'dart:ffi';
 import 'dart:io';
-
 import 'package:flutter/services.dart';
 import 'package:flutter_map/flutter_map.dart';
 import 'package:flutter_tool_android/flutter_tool_android.dart';
@@ -26,7 +24,6 @@ import 'package:location/sdk/map/map_helper.dart';
 import 'package:location/utils/atmob_log.dart';
 import 'package:location/utils/mmkv_util.dart';
 import 'package:location/utils/toast_util.dart';
-
 import '../../data/bean/member_status_info.dart';
 import '../../data/repositories/config_repository.dart';
 import '../../data/repositories/track_repository.dart';
@@ -93,6 +90,10 @@ class MainController extends BaseController {
   String? lastCheckFriendId;
   bool isExecuteAutoSelect = false;
 
+  Timer? electricTimer;
+
+  final Map<String, int> electricMap = {};
+
   RxMap<String, TodayTrackReportBean> get todayTrackReportMap =>
       todayTrackHelper.todayTrackReportMap;
 
@@ -127,42 +128,66 @@ class MainController extends BaseController {
         _selectedFriend.value = mineUserInfo;
       }
     });
-    friendsListSubscription = _friendsList.listen((list) {
-      integrateList.clear();
-      integrateList.add(mineUserInfo);
-      integrateList.addAll(list);
-      mapController.replaceAllMarkers(
-          Location2MarkerUtil.userInfoList2MarkerList(integrateList,
-              accountRepository.memberIsExpired(), selectedFriend));
-      if (selectedFriend != null) {
-        UserInfo? userInfo = integrateList
-            .firstWhereOrNull((element) => element.id == selectedFriend?.id);
-        _selectedFriend.value = userInfo;
-      }
-      _autoSelectFriend();
-    });
+    //刷新好友列表
+    if (_friendsList.isNotEmpty) {
+      _updateFriendList(_friendsList);
+    }
+    friendsListSubscription =
+        _friendsList.listen((list) => _updateFriendList(list));
+
+    //刷新我的marker
     updateMineInfo(accountRepository.mineUserInfo.value);
     mineUserInfoSubscription =
         accountRepository.mineUserInfo.listen((mineInfo) {
       updateMineInfo(mineInfo);
     });
+    //刷新我的定位
+    final location = accountRepository.mineUserInfo.value.lastLocation.value;
+    if (location != null) {
+      _updateMineLocation(location);
+    }
     mineLocationSubscription =
         accountRepository.mineUserInfo.value.lastLocation.listen((location) {
-      final mineInfo = accountRepository.mineUserInfo.value;
-      mapController.updateOrAddMarker(Location2MarkerUtil.userInfo2Marker(
-          mineInfo, selectedFriend?.id == mineInfo.id));
-      if (isFirstShowMineLocation && location != null) {
-        isFirstShowMineLocation = false;
-        animateCameraToUser(mineInfo);
-      }
+      _updateMineLocation(location);
+    });
+    //根据会员状态刷新好友列表
+    memberStatusInfoSubscription =
+        accountRepository.memberStatusInfo.listen((memberStatus) {
+      _updateFriendList(_friendsList);
     });
-
-    friendsRepository.refreshFriends();
 
     ///刷新检查有没有广告
     _refreshTrackDailyDialogs();
   }
 
+  void _updateMineLocation(LocationInfo? location) {
+    final mineInfo = accountRepository.mineUserInfo.value;
+    mapController.updateOrAddMarker(Location2MarkerUtil.userInfo2Marker(
+        mineInfo, selectedFriend?.id == mineInfo.id, null));
+    if (isFirstShowMineLocation && location != null) {
+      isFirstShowMineLocation = false;
+      animateCameraToUser(mineInfo);
+    }
+  }
+
+  void _updateFriendList(List<UserInfo> list) {
+    integrateList.clear();
+    integrateList.add(mineUserInfo);
+    integrateList.addAll(list);
+    mapController.replaceAllMarkers(Location2MarkerUtil.userInfoList2MarkerList(
+        integrateList, accountRepository.memberIsExpired(), selectedFriend));
+    if (selectedFriend != null) {
+      UserInfo? userInfo = integrateList
+          .firstWhereOrNull((element) => element.id == selectedFriend?.id);
+      if (userInfo == null) {
+        _selectedFriend.value = null;
+      } else {
+        _setSelectUserInfo(userInfo);
+      }
+    }
+    _autoSelectFriend();
+  }
+
   ///刷新检查有没有广告
   void _refreshTrackDailyDialogs() {
     trackRepository.locationTrackDailyDialogs().then((trackResponse) {
@@ -190,7 +215,7 @@ class MainController extends BaseController {
       return;
     }
     mapController.updateOrAddMarker(Location2MarkerUtil.userInfo2Marker(
-        mineInfo, selectedFriend?.id == mineInfo.id));
+        mineInfo, selectedFriend?.id == mineInfo.id, null));
   }
 
   Future<void> onAddFriendClick() {
@@ -211,29 +236,67 @@ class MainController extends BaseController {
 
   void onSelectUserClick(UserInfo userInfo) {
     KVUtil.putString(Constants.keyLastSelectFriendId, userInfo.id);
+    if (userInfo == _selectedFriend.value) {
+      return;
+    }
     _setSelectUserInfo(userInfo);
   }
 
   void _setSelectUserInfo(UserInfo userInfo) {
     UserInfo? oldInfo = _selectedFriend.value;
     _selectedFriend.value = userInfo;
+    //定时查询手机电量
+    _scheduleQueryBattery();
     //修改地图选中
-    _updateMapSelected(oldInfo, userInfo);
+    int? electric = electricMap[userInfo.id];
+    _updateMapSelected(oldInfo, userInfo, electric: electric);
     if (!accountRepository.memberIsExpired() || userInfo.isMine == true) {
       //移动到选中的位置
       animateCameraToUser(userInfo);
     }
   }
 
-  void _updateMapSelected(UserInfo? oldInfo, UserInfo newInfo) {
+  void _scheduleQueryBattery() {
+    electricTimer?.cancel();
+    final friend = _selectedFriend.value;
+    if (friend == null || friend.id == Constants.mineLocationId) {
+      return;
+    }
+    queryFriendElectric(friend);
+    electricTimer = Timer.periodic(const Duration(minutes: 3), (timer) {
+      queryFriendElectric(friend);
+    });
+  }
+
+  void queryFriendElectric(UserInfo friend) async {
+    friendsRepository
+        .userElectricQuery(friend.id)
+        .then((response) => response.electric)
+        .then((electric) {
+      //更新选中点位的电量
+      if (electric == null) {
+        return;
+      }
+      electricMap[friend.id] = electric;
+      final selectFriend = _selectedFriend.value;
+      if (selectFriend == null || friend.id != selectFriend.id) {
+        return;
+      }
+      _updateMapSelected(null, selectFriend, electric: electric);
+    });
+  }
+
+  void _updateMapSelected(UserInfo? oldInfo, UserInfo newInfo,
+      {int? electric}) {
     List<UserInfo> markers = [];
-    if (oldInfo != null) {
+    if (oldInfo != null && oldInfo.id != newInfo.id) {
       markers.add(oldInfo);
     }
     markers.add(newInfo);
     mapController.updateOrAddMarkers(
         Location2MarkerUtil.userInfoList2MarkerList(
-            markers, accountRepository.memberIsExpired(), selectedFriend));
+            markers, accountRepository.memberIsExpired(), selectedFriend,
+            electric: electric));
   }
 
   void animateCameraToUser(UserInfo userInfo) {

+ 1 - 0
lib/module/track/track_day_detail/time_proportion/pie_chat_data.dart

@@ -13,6 +13,7 @@ class PieChatData {
     required this.address,
     required this.color,
     required this.duration,
+    this.proportion = 0.0,
   });
 }
 

+ 11 - 0
lib/module/track/track_day_detail/track_day_detail_controller.dart

@@ -1,5 +1,6 @@
 import 'dart:async';
 import 'dart:convert';
+import 'dart:math';
 
 import 'package:flutter/cupertino.dart';
 import 'package:flutter_map/flutter_map.dart';
@@ -281,6 +282,16 @@ class TrackDayDetailController extends BaseController {
           (data.duration / totalDuration * 100).toFormattedDouble(1);
     }
     pieChatData.addAll(addrMap.values.toList());
+    //如果当数据为空,填入一个默认文案数据
+    if (pieChatData.isEmpty) {
+      PieChatData noStayData = PieChatData(
+        address: StringName.trackNoStayData,
+        duration: 0,
+        proportion: 100,
+        color: pieChatColors[0],
+      );
+      pieChatData.add(noStayData);
+    }
   }
 
   void _dealTrackExpandData() {

+ 189 - 197
lib/resource/assets.gen.dart

@@ -35,8 +35,7 @@ class $AssetsImagesGen {
 
   /// File path: assets/images/bg_dialog_location_permission_ios.webp
   AssetGenImage get bgDialogLocationPermissionIos => const AssetGenImage(
-    'assets/images/bg_dialog_location_permission_ios.webp',
-  );
+      'assets/images/bg_dialog_location_permission_ios.webp');
 
   /// File path: assets/images/bg_dialog_track_error.webp
   AssetGenImage get bgDialogTrackError =>
@@ -272,8 +271,7 @@ class $AssetsImagesGen {
 
   /// File path: assets/images/icon_main_refresh_friend_location.webp
   AssetGenImage get iconMainRefreshFriendLocation => const AssetGenImage(
-    'assets/images/icon_main_refresh_friend_location.webp',
-  );
+      'assets/images/icon_main_refresh_friend_location.webp');
 
   /// File path: assets/images/icon_main_refresh_mine_location.webp
   AssetGenImage get iconMainRefreshMineLocation =>
@@ -289,8 +287,7 @@ class $AssetsImagesGen {
 
   /// File path: assets/images/icon_member_comment_very_satisfied.webp
   AssetGenImage get iconMemberCommentVerySatisfied => const AssetGenImage(
-    'assets/images/icon_member_comment_very_satisfied.webp',
-  );
+      'assets/images/icon_member_comment_very_satisfied.webp');
 
   /// File path: assets/images/icon_member_contact_click_help.webp
   AssetGenImage get iconMemberContactClickHelp =>
@@ -298,8 +295,7 @@ class $AssetsImagesGen {
 
   /// File path: assets/images/icon_member_contact_click_help_close.webp
   AssetGenImage get iconMemberContactClickHelpClose => const AssetGenImage(
-    'assets/images/icon_member_contact_click_help_close.webp',
-  );
+      'assets/images/icon_member_contact_click_help_close.webp');
 
   /// File path: assets/images/icon_member_fun5.webp
   AssetGenImage get iconMemberFun5 =>
@@ -327,13 +323,11 @@ class $AssetsImagesGen {
 
   /// File path: assets/images/icon_member_ordinary_product_normal.webp
   AssetGenImage get iconMemberOrdinaryProductNormal => const AssetGenImage(
-    'assets/images/icon_member_ordinary_product_normal.webp',
-  );
+      'assets/images/icon_member_ordinary_product_normal.webp');
 
   /// File path: assets/images/icon_member_ordinary_product_select.webp
   AssetGenImage get iconMemberOrdinaryProductSelect => const AssetGenImage(
-    'assets/images/icon_member_ordinary_product_select.webp',
-  );
+      'assets/images/icon_member_ordinary_product_select.webp');
 
   /// File path: assets/images/icon_member_payment_completed.webp
   AssetGenImage get iconMemberPaymentCompleted =>
@@ -353,13 +347,11 @@ class $AssetsImagesGen {
 
   /// File path: assets/images/icon_member_special_products_normal.webp
   AssetGenImage get iconMemberSpecialProductsNormal => const AssetGenImage(
-    'assets/images/icon_member_special_products_normal.webp',
-  );
+      'assets/images/icon_member_special_products_normal.webp');
 
   /// File path: assets/images/icon_member_special_products_select.webp
   AssetGenImage get iconMemberSpecialProductsSelect => const AssetGenImage(
-    'assets/images/icon_member_special_products_select.webp',
-  );
+      'assets/images/icon_member_special_products_select.webp');
 
   /// File path: assets/images/icon_member_vip_back.webp
   AssetGenImage get iconMemberVipBack =>
@@ -411,8 +403,7 @@ class $AssetsImagesGen {
 
   /// File path: assets/images/icon_mine_fun_permission_setting.webp
   AssetGenImage get iconMineFunPermissionSetting => const AssetGenImage(
-    'assets/images/icon_mine_fun_permission_setting.webp',
-  );
+      'assets/images/icon_mine_fun_permission_setting.webp');
 
   /// File path: assets/images/icon_mine_fun_share.webp
   AssetGenImage get iconMineFunShare =>
@@ -440,23 +431,19 @@ class $AssetsImagesGen {
 
   /// File path: assets/images/icon_mine_trial_expiration_header.webp
   AssetGenImage get iconMineTrialExpirationHeader => const AssetGenImage(
-    'assets/images/icon_mine_trial_expiration_header.webp',
-  );
+      'assets/images/icon_mine_trial_expiration_header.webp');
 
   /// File path: assets/images/icon_mine_trial_expiration_pause_bg.webp
   AssetGenImage get iconMineTrialExpirationPauseBg => const AssetGenImage(
-    'assets/images/icon_mine_trial_expiration_pause_bg.webp',
-  );
+      'assets/images/icon_mine_trial_expiration_pause_bg.webp');
 
   /// File path: assets/images/icon_mine_trial_expiration_question.webp
   AssetGenImage get iconMineTrialExpirationQuestion => const AssetGenImage(
-    'assets/images/icon_mine_trial_expiration_question.webp',
-  );
+      'assets/images/icon_mine_trial_expiration_question.webp');
 
   /// File path: assets/images/icon_mine_trial_expiration_recommend.webp
   AssetGenImage get iconMineTrialExpirationRecommend => const AssetGenImage(
-    'assets/images/icon_mine_trial_expiration_recommend.webp',
-  );
+      'assets/images/icon_mine_trial_expiration_recommend.webp');
 
   /// File path: assets/images/icon_mine_trial_expiration_up.webp
   AssetGenImage get iconMineTrialExpirationUp =>
@@ -528,8 +515,7 @@ class $AssetsImagesGen {
 
   /// File path: assets/images/icon_track_detail_time_base_arrow.webp
   AssetGenImage get iconTrackDetailTimeBaseArrow => const AssetGenImage(
-    'assets/images/icon_track_detail_time_base_arrow.webp',
-  );
+      'assets/images/icon_track_detail_time_base_arrow.webp');
 
   /// File path: assets/images/icon_track_error.webp
   AssetGenImage get iconTrackError =>
@@ -609,23 +595,19 @@ class $AssetsImagesGen {
 
   /// File path: assets/images/img_dialog_location_always_tip_1.webp
   AssetGenImage get imgDialogLocationAlwaysTip1 => const AssetGenImage(
-    'assets/images/img_dialog_location_always_tip_1.webp',
-  );
+      'assets/images/img_dialog_location_always_tip_1.webp');
 
   /// File path: assets/images/img_dialog_location_always_tip_2.webp
   AssetGenImage get imgDialogLocationAlwaysTip2 => const AssetGenImage(
-    'assets/images/img_dialog_location_always_tip_2.webp',
-  );
+      'assets/images/img_dialog_location_always_tip_2.webp');
 
   /// File path: assets/images/img_dialog_location_always_tip_3.webp
   AssetGenImage get imgDialogLocationAlwaysTip3 => const AssetGenImage(
-    'assets/images/img_dialog_location_always_tip_3.webp',
-  );
+      'assets/images/img_dialog_location_always_tip_3.webp');
 
   /// File path: assets/images/img_member_first_week_discount_container.webp
   AssetGenImage get imgMemberFirstWeekDiscountContainer => const AssetGenImage(
-    'assets/images/img_member_first_week_discount_container.webp',
-  );
+      'assets/images/img_member_first_week_discount_container.webp');
 
   /// File path: assets/images/img_member_header_ad_1.webp
   AssetGenImage get imgMemberHeaderAd1 =>
@@ -649,8 +631,7 @@ class $AssetsImagesGen {
 
   /// File path: assets/images/img_member_user_cancels_container.webp
   AssetGenImage get imgMemberUserCancelsContainer => const AssetGenImage(
-    'assets/images/img_member_user_cancels_container.webp',
-  );
+      'assets/images/img_member_user_cancels_container.webp');
 
   /// File path: assets/images/img_track_ai_analyse.webp
   AssetGenImage get imgTrackAiAnalyse =>
@@ -666,162 +647,162 @@ class $AssetsImagesGen {
 
   /// List of all assets
   List<AssetGenImage> get values => [
-    bgAddFriendDialog,
-    bgCheckLocationPermission,
-    bgDialogLocationPermissionIos,
-    bgDialogTrackError,
-    bgFriendItem,
-    bgLocationAnalyse,
-    bgLocationAnalyseAi,
-    bgLoginHeadContainer,
-    bgMemberHeader,
-    bgMineMemberCard,
-    bgPageBackground,
-    bgTrackLocationTie,
-    bgTrackPieChat,
-    bgUrgentContactAdd,
-    bgUrgentContactEmpty,
-    bgUrgentContactLogo,
-    bgUrgentContactPopup,
-    iconAccountReplaceLogo,
-    iconAddFriendBackGround,
-    iconAgreementClose,
-    iconAlipayPayment,
-    iconAlipayScanPayment,
-    iconAppleRecoverSubscribe,
-    iconAvatarClose,
-    iconAvatarSelected,
-    iconBlackBack,
-    iconCallPhone,
-    iconCbSelected,
-    iconCbUnSelect,
-    iconCheckboxSelected,
-    iconCheckboxUnSelect,
-    iconDefaultFriendAvatar,
-    iconDefaultMineAvatar,
-    iconDialogAddFriend,
-    iconDialogClose,
-    iconDialogClose2,
-    iconDialogNetError,
-    iconEvaluate1,
-    iconEvaluate2,
-    iconEvaluate3,
-    iconEvaluate4,
-    iconEvaluate5,
-    iconExperiment,
-    iconFriendEdit,
-    iconFriendEditArrow,
-    iconFriendNews,
-    iconFriendSettingCopy,
-    iconGuard,
-    iconLoginAddressBook,
-    iconLoginClose,
-    iconLoginGoWxArrow,
-    iconLoginPhone,
-    iconLoginWx,
-    iconLogo,
-    iconLogoMax,
-    iconMainAddFriend,
-    iconMainFriendGuard,
-    iconMainHelp,
-    iconMainMapClock,
-    iconMainMine,
-    iconMainNews,
-    iconMainRefreshFriendLocation,
-    iconMainRefreshMineLocation,
-    iconMainTrackArrow,
-    iconMemberAvatar,
-    iconMemberCommentVerySatisfied,
-    iconMemberContactClickHelp,
-    iconMemberContactClickHelpClose,
-    iconMemberFun5,
-    iconMemberFun1,
-    iconMemberFun2,
-    iconMemberFun3,
-    iconMemberFun4,
-    iconMemberFun6,
-    iconMemberOrdinaryProductNormal,
-    iconMemberOrdinaryProductSelect,
-    iconMemberPaymentCompleted,
-    iconMemberRetainClose,
-    iconMemberSettlementBg,
-    iconMemberSettlementConfirm,
-    iconMemberSpecialProductsNormal,
-    iconMemberSpecialProductsSelect,
-    iconMemberVipBack,
-    iconMemberVipMiddleBg,
-    iconMemberVipMore,
-    iconMemberVipReceiveArrow,
-    iconMemberVipSign,
-    iconMessageFriendHelp,
-    iconMineFunAbout,
-    iconMineFunAccountFeedback,
-    iconMineFunArrow,
-    iconMineFunCustomerService,
-    iconMineFunExitAccount,
-    iconMineFunLogoutAccount,
-    iconMineFunPermissionSetting,
-    iconMineFunShare,
-    iconMineLogged,
-    iconMineNoLogin,
-    iconMineSmallVip,
-    iconMineTrialExpirationBg,
-    iconMineTrialExpirationHave,
-    iconMineTrialExpirationHeader,
-    iconMineTrialExpirationPauseBg,
-    iconMineTrialExpirationQuestion,
-    iconMineTrialExpirationRecommend,
-    iconMineTrialExpirationUp,
-    iconMineTrialExpirationVip,
-    iconMineUnlockVip,
-    iconMineUrgentContact,
-    iconNetMobile,
-    iconNetWifi,
-    iconNews,
-    iconNewsItem,
-    iconNewsReport,
-    iconNewsReportAgree,
-    iconSplashTitle,
-    iconTrackAiInterpretation,
-    iconTrackAnalyseRefresh,
-    iconTrackDailyDoubt,
-    iconTrackDailyReport,
-    iconTrackDailySummary,
-    iconTrackDailySummaryArrow,
-    iconTrackDetailTimeBaseArrow,
-    iconTrackError,
-    iconTrackLocation,
-    iconTrackLocationNow,
-    iconTrackMoving,
-    iconTrackSearch,
-    iconTrackSearchClear,
-    iconTrackSelectTimeArrow,
-    iconTrackStay,
-    iconTrackUnlock,
-    iconTrackUnlockNoPermission,
-    iconUrgentAdd,
-    iconUrgentContactAdd,
-    iconUrgentContactDialPhone,
-    iconUrgentContactMore,
-    iconUrgentContactTipBg,
-    iconVip,
-    iconWechatPayment,
-    iconWechatScanPayment,
-    iconWhiteBack,
-    imgDialogLocationAlwaysTip1,
-    imgDialogLocationAlwaysTip2,
-    imgDialogLocationAlwaysTip3,
-    imgMemberFirstWeekDiscountContainer,
-    imgMemberHeaderAd1,
-    imgMemberHeaderAd2,
-    imgMemberHeaderAd3,
-    imgMemberHeaderAd4,
-    imgMemberRetainContainer,
-    imgMemberUserCancelsContainer,
-    imgTrackAiAnalyse,
-    imgTrackNoData,
-    imgTrackNoMemberTips,
-  ];
+        bgAddFriendDialog,
+        bgCheckLocationPermission,
+        bgDialogLocationPermissionIos,
+        bgDialogTrackError,
+        bgFriendItem,
+        bgLocationAnalyse,
+        bgLocationAnalyseAi,
+        bgLoginHeadContainer,
+        bgMemberHeader,
+        bgMineMemberCard,
+        bgPageBackground,
+        bgTrackLocationTie,
+        bgTrackPieChat,
+        bgUrgentContactAdd,
+        bgUrgentContactEmpty,
+        bgUrgentContactLogo,
+        bgUrgentContactPopup,
+        iconAccountReplaceLogo,
+        iconAddFriendBackGround,
+        iconAgreementClose,
+        iconAlipayPayment,
+        iconAlipayScanPayment,
+        iconAppleRecoverSubscribe,
+        iconAvatarClose,
+        iconAvatarSelected,
+        iconBlackBack,
+        iconCallPhone,
+        iconCbSelected,
+        iconCbUnSelect,
+        iconCheckboxSelected,
+        iconCheckboxUnSelect,
+        iconDefaultFriendAvatar,
+        iconDefaultMineAvatar,
+        iconDialogAddFriend,
+        iconDialogClose,
+        iconDialogClose2,
+        iconDialogNetError,
+        iconEvaluate1,
+        iconEvaluate2,
+        iconEvaluate3,
+        iconEvaluate4,
+        iconEvaluate5,
+        iconExperiment,
+        iconFriendEdit,
+        iconFriendEditArrow,
+        iconFriendNews,
+        iconFriendSettingCopy,
+        iconGuard,
+        iconLoginAddressBook,
+        iconLoginClose,
+        iconLoginGoWxArrow,
+        iconLoginPhone,
+        iconLoginWx,
+        iconLogo,
+        iconLogoMax,
+        iconMainAddFriend,
+        iconMainFriendGuard,
+        iconMainHelp,
+        iconMainMapClock,
+        iconMainMine,
+        iconMainNews,
+        iconMainRefreshFriendLocation,
+        iconMainRefreshMineLocation,
+        iconMainTrackArrow,
+        iconMemberAvatar,
+        iconMemberCommentVerySatisfied,
+        iconMemberContactClickHelp,
+        iconMemberContactClickHelpClose,
+        iconMemberFun5,
+        iconMemberFun1,
+        iconMemberFun2,
+        iconMemberFun3,
+        iconMemberFun4,
+        iconMemberFun6,
+        iconMemberOrdinaryProductNormal,
+        iconMemberOrdinaryProductSelect,
+        iconMemberPaymentCompleted,
+        iconMemberRetainClose,
+        iconMemberSettlementBg,
+        iconMemberSettlementConfirm,
+        iconMemberSpecialProductsNormal,
+        iconMemberSpecialProductsSelect,
+        iconMemberVipBack,
+        iconMemberVipMiddleBg,
+        iconMemberVipMore,
+        iconMemberVipReceiveArrow,
+        iconMemberVipSign,
+        iconMessageFriendHelp,
+        iconMineFunAbout,
+        iconMineFunAccountFeedback,
+        iconMineFunArrow,
+        iconMineFunCustomerService,
+        iconMineFunExitAccount,
+        iconMineFunLogoutAccount,
+        iconMineFunPermissionSetting,
+        iconMineFunShare,
+        iconMineLogged,
+        iconMineNoLogin,
+        iconMineSmallVip,
+        iconMineTrialExpirationBg,
+        iconMineTrialExpirationHave,
+        iconMineTrialExpirationHeader,
+        iconMineTrialExpirationPauseBg,
+        iconMineTrialExpirationQuestion,
+        iconMineTrialExpirationRecommend,
+        iconMineTrialExpirationUp,
+        iconMineTrialExpirationVip,
+        iconMineUnlockVip,
+        iconMineUrgentContact,
+        iconNetMobile,
+        iconNetWifi,
+        iconNews,
+        iconNewsItem,
+        iconNewsReport,
+        iconNewsReportAgree,
+        iconSplashTitle,
+        iconTrackAiInterpretation,
+        iconTrackAnalyseRefresh,
+        iconTrackDailyDoubt,
+        iconTrackDailyReport,
+        iconTrackDailySummary,
+        iconTrackDailySummaryArrow,
+        iconTrackDetailTimeBaseArrow,
+        iconTrackError,
+        iconTrackLocation,
+        iconTrackLocationNow,
+        iconTrackMoving,
+        iconTrackSearch,
+        iconTrackSearchClear,
+        iconTrackSelectTimeArrow,
+        iconTrackStay,
+        iconTrackUnlock,
+        iconTrackUnlockNoPermission,
+        iconUrgentAdd,
+        iconUrgentContactAdd,
+        iconUrgentContactDialPhone,
+        iconUrgentContactMore,
+        iconUrgentContactTipBg,
+        iconVip,
+        iconWechatPayment,
+        iconWechatScanPayment,
+        iconWhiteBack,
+        imgDialogLocationAlwaysTip1,
+        imgDialogLocationAlwaysTip2,
+        imgDialogLocationAlwaysTip3,
+        imgMemberFirstWeekDiscountContainer,
+        imgMemberHeaderAd1,
+        imgMemberHeaderAd2,
+        imgMemberHeaderAd3,
+        imgMemberHeaderAd4,
+        imgMemberRetainContainer,
+        imgMemberUserCancelsContainer,
+        imgTrackAiAnalyse,
+        imgTrackNoData,
+        imgTrackNoMemberTips
+      ];
 }
 
 class Assets {
@@ -832,7 +813,11 @@ class Assets {
 }
 
 class AssetGenImage {
-  const AssetGenImage(this._assetName, {this.size, this.flavors = const {}});
+  const AssetGenImage(
+    this._assetName, {
+    this.size,
+    this.flavors = const {},
+  });
 
   final String _assetName;
 
@@ -892,8 +877,15 @@ class AssetGenImage {
     );
   }
 
-  ImageProvider provider({AssetBundle? bundle, String? package}) {
-    return AssetImage(_assetName, bundle: bundle, package: package);
+  ImageProvider provider({
+    AssetBundle? bundle,
+    String? package,
+  }) {
+    return AssetImage(
+      _assetName,
+      bundle: bundle,
+      package: package,
+    );
   }
 
   String get path => _assetName;

+ 2 - 0
lib/resource/string.gen.dart

@@ -308,6 +308,7 @@ class StringName {
   static String get permissionNotificationSetting => 'permission_notification_setting'.tr; // 通知栏权限
   static String get permissionNotificationSettingSubtitle => 'permission_notification_setting_subtitle'.tr; // 开启通知栏权限,可随时查看好友“每日轨迹”
   static String get trackNoDoubtDesc => 'track_no_doubt_desc'.tr; // 未授权原因:\n 1.可能是自己/TA手机解锁权限尚未开启,请在个人中心设置中开启权限\n 2.可能是TA手机尚未开启本软件
+  static String get trackNoStayData => 'track_no_stay_data'.tr; // 暂无停留数据
 }
 class StringMultiSource {
   StringMultiSource._();
@@ -619,6 +620,7 @@ class StringMultiSource {
       'permission_notification_setting': '通知栏权限',
       'permission_notification_setting_subtitle': '开启通知栏权限,可随时查看好友“每日轨迹”',
       'track_no_doubt_desc': '未授权原因:\n 1.可能是自己/TA手机解锁权限尚未开启,请在个人中心设置中开启权限\n 2.可能是TA手机尚未开启本软件',
+      'track_no_stay_data': '暂无停留数据',
     },
   };
 }

+ 11 - 6
lib/utils/location_convert_marker_util.dart

@@ -5,8 +5,8 @@ import 'package:location/data/consts/constants.dart';
 class Location2MarkerUtil {
   Location2MarkerUtil._();
 
-  static Marker userInfo2Marker(UserInfo e, bool isSelected) {
-    return Marker(
+  static Marker userInfo2Marker(UserInfo e, bool isSelected, int? electric) {
+    final marker = Marker(
       id: e.id,
       markerName: e.getUserNickName(),
       longitude: e.lastLocation.value?.longitude,
@@ -17,19 +17,24 @@ class Location2MarkerUtil {
       isSelected: isSelected,
       customAvatarUrl: e.avatar,
     );
+    if (electric != null) {
+      marker.tags = {'electric': electric};
+    }
+    return marker;
   }
 
-  static List<Marker> userInfoList2MarkerList(
-      List<UserInfo> list, bool isMemberExpired, UserInfo? selectedFriend) {
+  static List<Marker> userInfoList2MarkerList(List<UserInfo> list,
+      bool isMemberExpired, UserInfo? selectedFriend,
+      {int? electric}) {
     List<Marker> markers = [];
     for (var e in list) {
       if (e.isMine == true) {
-        markers.add(userInfo2Marker(e, e.id == selectedFriend?.id));
+        markers.add(userInfo2Marker(e, e.id == selectedFriend?.id, electric));
       } else if (e.blockedMe != true &&
           e.lastLocation.value?.longitude != null &&
           e.lastLocation.value?.latitude != null &&
           !isMemberExpired) {
-        markers.add(userInfo2Marker(e, e.id == selectedFriend?.id));
+        markers.add(userInfo2Marker(e, e.id == selectedFriend?.id, electric));
       }
     }
     return markers;

+ 3 - 0
plugins/map/lib/src/widget/map_widget.dart

@@ -63,6 +63,9 @@ class _MapWidgetState extends State<MapWidget> {
   }
 
   void showMarker() {
+    if (widget.markers == null || widget.markers!.isEmpty) {
+      return;
+    }
     final markerPoints = widget.markers
         ?.map((marker) =>
             LatLng(latitude: marker.latitude, longitude: marker.longitude))

+ 76 - 26
plugins/map_amap_android/android/src/main/java/com/atmob/map_amap_android/overlays/marker/MarkersController.java

@@ -1,5 +1,6 @@
 package com.atmob.map_amap_android.overlays.marker;
 
+import android.annotation.SuppressLint;
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
@@ -18,8 +19,8 @@ import com.amap.api.maps.model.Marker;
 import com.amap.api.maps.model.MarkerOptions;
 import com.atmob.map_amap_android.R;
 import com.atmob.map_amap_android.bean.MakerInfo;
-import com.atmob.map_amap_android.bean.Popup;
 import com.atmob.map_amap_android.contants.Constants;
+import com.atmob.map_amap_android.databinding.ItemElectricMarkerBinding;
 import com.atmob.map_amap_android.databinding.ItemLocationMarkerBinding;
 import com.atmob.map_amap_android.databinding.ItemTrackErrorMarkerBinding;
 import com.atmob.map_amap_android.databinding.ItemTrackPassingMarkerBinding;
@@ -56,6 +57,7 @@ public class MarkersController implements MyMethodCallHandler, AMap.OnMarkerClic
     private final HashMap<String, Marker> currentMarkers = new HashMap<>(10);
 
     private ItemLocationMarkerBinding markerBinding;
+    private ItemElectricMarkerBinding electricMarkerBinding;
 
     private final Gson gson;
 
@@ -86,7 +88,7 @@ public class MarkersController implements MyMethodCallHandler, AMap.OnMarkerClic
 
 
     private void removeMarker(MethodCall call, MethodChannel.Result result) {
-        LogUtil.i(TAG, "removeMarker===>" + call.arguments());
+        LogUtil.d(TAG, "removeMarker===>" + call.arguments());
         Map<String, Object> arguments = call.arguments();
         if (arguments == null || arguments.isEmpty()) {
             result.error("-1", "removeMarker.arguments is empty", null);
@@ -97,13 +99,13 @@ public class MarkersController implements MyMethodCallHandler, AMap.OnMarkerClic
         if (marker != null) {
             marker.remove();
             currentMarkers.remove(markerId);
-            LogUtil.i(TAG, "removeMarker===>成功删除marker:" + markerId);
+            LogUtil.d(TAG, "removeMarker===>成功删除marker:" + markerId);
         }
         result.success(null);
     }
 
     private void replaceAllMarkers(MethodCall call, MethodChannel.Result result) {
-        LogUtil.i(TAG, "replaceAllMarkers===>" + call.arguments());
+        LogUtil.d(TAG, "replaceAllMarkers===>" + call.arguments());
         try {
             String arguments = call.arguments();
             List<MakerInfo> list = null;
@@ -152,7 +154,7 @@ public class MarkersController implements MyMethodCallHandler, AMap.OnMarkerClic
     }
 
     private void updateMarkers(MethodCall call, MethodChannel.Result result) {
-        LogUtil.i(TAG, "updateMarkers===>" + call.arguments());
+        LogUtil.d(TAG, "updateMarkers===>" + call.arguments());
         String arguments = call.arguments();
         if (arguments == null || TextUtils.isEmpty(arguments)) {
             result.error("-1", "updateMarkers.arguments is empty", null);
@@ -189,9 +191,10 @@ public class MarkersController implements MyMethodCallHandler, AMap.OnMarkerClic
                     boolean nameChanged = !Objects.equals(cacheInfo.getMarkerName(), makerInfo.getMarkerName());
                     boolean selectedChanged = cacheInfo.isSelected() != makerInfo.isSelected();
                     boolean avatarChanged = !Objects.equals(cacheInfo.getCustomAvatarUrl(), makerInfo.getCustomAvatarUrl());
+                    boolean tagsChanged = checkMarkerTagsChanged(cacheInfo,makerInfo);
 
-                    if (nameChanged || selectedChanged || avatarChanged) {
-                        LogUtil.i(TAG, "updateMarkers==修改头像");
+                    if (nameChanged || selectedChanged || avatarChanged || tagsChanged) {
+                        LogUtil.d(TAG, "updateMarkers==marker内容发生变化,更新样式");
                         ImageCacheLoader.loadBitmapAsync(context, makerInfo.getCustomAvatarUrl(), new BitmapCallback() {
                             @Override
                             public void onStartLoading() {
@@ -201,30 +204,40 @@ public class MarkersController implements MyMethodCallHandler, AMap.OnMarkerClic
                             public void onBitmapLoaded(Bitmap bitmap) {
                                 BitmapDescriptor markerBitmap = getMarkerBitmap(makerInfo, bitmap);
                                 marker.setIcon(markerBitmap);
-                                LogUtil.i(TAG, "updateMarkers=成功修改头像:id=" + makerInfo.getId());
+                                LogUtil.d(TAG, "updateMarkers=url头像:id=" + makerInfo.getId());
                             }
 
                             @Override
                             public void onError(String errorMessage) {
-                                LogUtil.i(TAG, "updateMarkers==修改失败:" + errorMessage);
+                                LogUtil.d(TAG, "updateMarkers==url头像失败:" + errorMessage);
+                                BitmapDescriptor markerBitmap = getMarkerBitmap(makerInfo, null);
+                                marker.setIcon(markerBitmap);
                             }
                         });
                     }
                     marker.setZIndex(makerInfo.isSelected() ? 100 : 0);
                 }
-                marker.setObject(makerInfo);
             }
+            marker.setObject(makerInfo);
         } else {
+            LogUtil.d(TAG, "updateMarkers==>marker不存在,创建新 marker:" + makerInfo);
             // 创建新 marker
             loadAvatarAndCreateMarker(makerInfo, latLng);
         }
     }
 
+    private boolean checkMarkerTagsChanged(MakerInfo cacheInfo, MakerInfo makerInfo) {
+        if(makerInfo.getMarkerType() == MakerInfo.MarkerType.FRIEND){
+            if(!Objects.equals(cacheInfo.getTags(), makerInfo.getTags())){
+                return true;
+            }
+        }
+        return false;
+    }
+
     private void loadAvatarAndCreateMarker(final MakerInfo makerInfo, final LatLng latLng) {
-        if (TextUtils.isEmpty(makerInfo.getCustomAvatarUrl())) {
-            // 无头像,使用默认图标
-            addMarkerToMap(makerInfo, latLng, null);
-        } else {
+        Marker marker = addMarkerToMap(makerInfo, latLng, null);
+        if (!TextUtils.isEmpty(makerInfo.getCustomAvatarUrl())) {
             // 加载头像后再添加 marker
             ImageCacheLoader.loadBitmapAsync(context, makerInfo.getCustomAvatarUrl(), new BitmapCallback() {
                 @Override
@@ -233,21 +246,20 @@ public class MarkersController implements MyMethodCallHandler, AMap.OnMarkerClic
 
                 @Override
                 public void onBitmapLoaded(Bitmap bitmap) {
-                    addMarkerToMap(makerInfo, latLng, bitmap);
-                    LogUtil.i(TAG, "updateMarkers=成功修改头像:id=" + makerInfo.getId());
+                    BitmapDescriptor markerBitmap = getMarkerBitmap(makerInfo, bitmap);
+                    marker.setIcon(markerBitmap);
                 }
 
                 @Override
                 public void onError(String errorMessage) {
-                    addMarkerToMap(makerInfo, latLng, null);
-                    LogUtil.i(TAG, "updateMarkers=使用默认头像:id=" + makerInfo.getId() + ",errorMessage:" + errorMessage);
+
                 }
             });
         }
     }
 
 
-    private void addMarkerToMap(MakerInfo makerInfo, LatLng latLng, Bitmap avatarBitmap) {
+    private Marker addMarkerToMap(MakerInfo makerInfo, LatLng latLng, Bitmap avatarBitmap) {
         BitmapDescriptor markerBitmap = getMarkerBitmap(makerInfo, avatarBitmap);
         MarkerOptions markerOption = new MarkerOptions()
                 .position(latLng)
@@ -264,6 +276,7 @@ public class MarkersController implements MyMethodCallHandler, AMap.OnMarkerClic
         newMarker.setObject(makerInfo);
         newMarker.setZIndex(makerInfo.isSelected() ? 999 : 0);
         currentMarkers.put(makerInfo.getId(), newMarker);
+        return newMarker;
     }
 
 
@@ -273,25 +286,62 @@ public class MarkersController implements MyMethodCallHandler, AMap.OnMarkerClic
     }
 
 
+    @SuppressLint("SetTextI18n")
     public BitmapDescriptor getMarkerBitmap(MakerInfo markerInfo, Bitmap customAvatarBitmap) {
-        if (markerInfo.getMarkerType() == MakerInfo.MarkerType.MINE || markerInfo.getMarkerType() == MakerInfo.MarkerType.FRIEND) {
+        if (markerInfo.getMarkerType() == MakerInfo.MarkerType.MINE) {
             if (markerBinding == null) {
                 markerBinding = ItemLocationMarkerBinding.inflate(LayoutInflater.from(context));
             }
             ConstraintLayout view = markerBinding.getRoot();
             markerBinding.locationMarkerTvName.setText(markerInfo.getMarkerName());
             if (customAvatarBitmap == null) {
-                markerBinding.ivMarkerAvatar.setImageResource(markerInfo.getMarkerType() == MakerInfo.MarkerType.MINE ? R.drawable.icon_default_mine_avatar : R.drawable.icon_default_friend_avatar);
+                markerBinding.ivMarkerAvatar.setImageResource(R.drawable.icon_default_mine_avatar);
             } else {
                 markerBinding.ivMarkerAvatar.setImageBitmap(customAvatarBitmap);
             }
-            if (markerInfo.getMarkerType() == MakerInfo.MarkerType.MINE) {
-                markerBinding.vBottom.setBackgroundResource(markerInfo.isSelected() ? R.drawable.icon_mine_marker_selected_bottom_circle : R.drawable.icon_mine_marker_normal_bottom_circle);
-            } else {
+            markerBinding.vBottom.setBackgroundResource(markerInfo.isSelected() ? R.drawable.icon_mine_marker_selected_bottom_circle : R.drawable.icon_mine_marker_normal_bottom_circle);
+            markerBinding.locationMarkerIvBg.setImageResource(markerInfo.isSelected() ? R.drawable.icon_location_mine_marker_selected : R.drawable.icon_location_marker_normal);
+
+            return viewGetBitmapDescriptor(view);
+        } else if (markerInfo.getMarkerType() == MakerInfo.MarkerType.FRIEND) {
+            Object tags = markerInfo.getTags();
+            Integer electric = null;
+            if (tags instanceof Map) {
+                Map<String, Object> tagsMap = (Map<String, Object>) tags;
+                electric = ParamUtil.getInt(tagsMap, "electric");
+            }
+            if (!markerInfo.isSelected() || electric == null) {
+                if (markerBinding == null) {
+                    markerBinding = ItemLocationMarkerBinding.inflate(LayoutInflater.from(context));
+                }
+                ConstraintLayout view = markerBinding.getRoot();
+                markerBinding.locationMarkerTvName.setText(markerInfo.getMarkerName());
+                if (customAvatarBitmap == null) {
+                    markerBinding.ivMarkerAvatar.setImageResource(R.drawable.icon_default_friend_avatar);
+                } else {
+                    markerBinding.ivMarkerAvatar.setImageBitmap(customAvatarBitmap);
+                }
                 markerBinding.vBottom.setBackgroundResource(markerInfo.isSelected() ? R.drawable.icon_marker_selected_bottom_circle : R.drawable.icon_marker_normal_bottom_circle);
+                markerBinding.locationMarkerIvBg.setImageResource(markerInfo.isSelected() ? R.drawable.icon_location_marker_selected : R.drawable.icon_location_marker_normal);
+
+                return viewGetBitmapDescriptor(view);
+            } else {
+                if (electricMarkerBinding == null) {
+                    electricMarkerBinding = ItemElectricMarkerBinding.inflate(LayoutInflater.from(context));
+                }
+                ConstraintLayout view = electricMarkerBinding.getRoot();
+                electricMarkerBinding.batteryView.setBatteryLevel(electric);
+                electricMarkerBinding.tvElectric.setText(electric + "%");
+                if (customAvatarBitmap == null) {
+                    electricMarkerBinding.ivMarkerAvatar.setImageResource(R.drawable.icon_default_friend_avatar);
+                } else {
+                    electricMarkerBinding.ivMarkerAvatar.setImageBitmap(customAvatarBitmap);
+                }
+                electricMarkerBinding.vBottom.setBackgroundResource(markerInfo.isSelected() ? R.drawable.icon_marker_selected_bottom_circle : R.drawable.icon_marker_normal_bottom_circle);
+                electricMarkerBinding.locationMarkerIvBg.setImageResource(markerInfo.isSelected() ? R.drawable.icon_location_marker_selected : R.drawable.icon_location_marker_normal);
+
+                return viewGetBitmapDescriptor(view);
             }
-            markerBinding.locationMarkerIvBg.setImageResource(markerInfo.isSelected() ? (markerInfo.getMarkerType() == MakerInfo.MarkerType.MINE ? R.drawable.icon_location_mine_marker_selected : R.drawable.icon_location_marker_selected) : R.drawable.icon_location_marker_normal);
-            return viewGetBitmapDescriptor(view);
         } else if (markerInfo.getMarkerType() == MakerInfo.MarkerType.TRACE_START_POINT) {
             ItemTrackStartMarkerBinding trackStartPointMarkerBinding = ItemTrackStartMarkerBinding.inflate(LayoutInflater.from(context));
             ConstraintLayout view = trackStartPointMarkerBinding.getRoot();

+ 3 - 0
plugins/map_amap_android/android/src/main/java/com/atmob/map_amap_android/util/ParamUtil.java

@@ -28,6 +28,9 @@ public class ParamUtil {
         if (value == null) {
             return null;
         }
+        if (value instanceof Double) {
+            return ((Double) value).intValue();
+        }
         return (int) value;
     }
 

+ 118 - 0
plugins/map_amap_android/android/src/main/java/com/atmob/map_amap_android/widget/BatteryView.java

@@ -0,0 +1,118 @@
+package com.atmob.map_amap_android.widget;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.view.View;
+
+public class BatteryView extends View {
+
+    private int level = 100;
+
+    private Paint borderPaint;
+    private Paint fillPaint;
+    private Paint headPaint;
+
+    private int borderColor = Color.parseColor("#4476FF");
+    private int fillColor = Color.parseColor("#4476FF");
+    private int lowColor = Color.parseColor("#4476FF");
+    private int normalColor = Color.parseColor("#4476FF");
+
+    private RectF bodyRect = new RectF();
+    private RectF headRect = new RectF();
+    private RectF fillRect = new RectF();
+
+    private float borderStrokeWidth = 6f;
+    private float headStrokeWidth = 6f;
+    private float cornerRadius = 3f;
+    private float headGap = 6f;
+
+    public BatteryView(Context context) {
+        super(context);
+        init();
+    }
+
+    public BatteryView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init();
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        super.onSizeChanged(w, h, oldw, oldh);
+
+        float headWidth = headStrokeWidth;
+        float headHeight = h / 2f;
+        float gap = headGap;
+
+        float bodyRight = w - headWidth - gap - borderStrokeWidth;
+        bodyRect.set(
+                borderStrokeWidth / 2f,
+                borderStrokeWidth / 2f,
+                bodyRight,
+                h - borderStrokeWidth / 2f
+        );
+
+        float headLeft = bodyRight + gap;
+        float headTop = (h - headHeight) / 2f;
+        headRect.set(headLeft, headTop, headLeft + headWidth, headTop + headHeight);
+    }
+
+    private void init() {
+        borderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        borderPaint.setColor(borderColor);
+        borderPaint.setStyle(Paint.Style.STROKE);
+        borderPaint.setStrokeWidth(borderStrokeWidth);
+
+        fillPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        fillPaint.setColor(fillColor);
+
+        headPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        headPaint.setColor(borderColor);
+        headPaint.setStyle(Paint.Style.FILL);
+    }
+
+    public void setBatteryLevel(int level) {
+        this.level = Math.max(0, Math.min(100, level));
+        invalidate();
+    }
+
+    public void setBorderStrokeWidth(float strokeWidth) {
+        this.borderStrokeWidth = strokeWidth;
+        borderPaint.setStrokeWidth(strokeWidth);
+        invalidate();
+    }
+
+    public void setHeadStrokeWidth(float strokeWidth) {
+        this.headStrokeWidth = strokeWidth;
+        invalidate();
+    }
+
+    public void setCornerRadius(float radius) {
+        this.cornerRadius = radius;
+        invalidate();
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        canvas.drawRoundRect(bodyRect, cornerRadius, cornerRadius, borderPaint);
+        canvas.drawRect(headRect, headPaint);
+
+        fillPaint.setColor(level <= 20 ? lowColor : normalColor);
+
+        float fillPadding = borderStrokeWidth;
+        float fillWidth = (bodyRect.width() - fillPadding * 2) * level / 100f;
+        fillRect.set(
+                bodyRect.left + fillPadding,
+                bodyRect.top + fillPadding,
+                bodyRect.left + fillPadding + fillWidth,
+                bodyRect.bottom - fillPadding
+        );
+        canvas.drawRoundRect(fillRect, cornerRadius / 1.5f, cornerRadius / 1.5f, fillPaint);
+    }
+}

+ 5 - 0
plugins/map_amap_android/android/src/main/res/drawable/bg_friend_electric.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <corners android:radius="100dp" />
+    <solid android:color="#fff" />
+</shape>

+ 82 - 0
plugins/map_amap_android/android/src/main/res/layout/item_electric_marker.xml

@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content">
+
+
+    <LinearLayout
+        android:id="@+id/ll_electric"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_margin="2dp"
+        android:background="@drawable/bg_friend_electric"
+        android:elevation="2dp"
+        android:gravity="center_vertical"
+        android:orientation="horizontal"
+        android:paddingHorizontal="9dp"
+        android:paddingVertical="3dp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent">
+
+        <com.atmob.map_amap_android.widget.BatteryView
+            android:id="@+id/battery_view"
+            android:layout_width="18dp"
+            android:layout_height="9.8dp" />
+
+        <TextView
+            android:id="@+id/tv_electric"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="2dp"
+            android:textColor="#666666"
+            android:textSize="11dp"
+            tools:text="40%" />
+
+    </LinearLayout>
+
+    <Space
+        android:id="@+id/space_bottom"
+        android:layout_width="wrap_content"
+        android:layout_height="44dp"
+        app:layout_constraintEnd_toEndOf="@+id/location_marker_iv_bg"
+        app:layout_constraintStart_toStartOf="@+id/location_marker_iv_bg"
+        app:layout_constraintTop_toTopOf="@+id/location_marker_iv_bg" />
+
+
+    <View
+        android:id="@+id/v_bottom"
+        android:layout_width="11dp"
+        android:layout_height="11dp"
+        app:layout_constraintEnd_toEndOf="@+id/location_marker_iv_bg"
+        app:layout_constraintStart_toStartOf="@+id/location_marker_iv_bg"
+        app:layout_constraintTop_toBottomOf="@+id/space_bottom"
+        tools:background="@drawable/icon_marker_selected_bottom_circle" />
+
+    <ImageView
+        android:id="@+id/location_marker_iv_bg"
+        android:layout_width="44dp"
+        android:layout_height="0dp"
+        android:layout_marginTop="6dp"
+        app:layout_constraintDimensionRatio="132:150"
+        app:layout_constraintEnd_toEndOf="@+id/ll_electric"
+        app:layout_constraintStart_toStartOf="@+id/ll_electric"
+        app:layout_constraintTop_toBottomOf="@+id/ll_electric"
+        tools:src="@drawable/icon_location_marker_selected" />
+
+
+    <ImageView
+        android:id="@+id/iv_marker_avatar"
+        android:layout_width="38dp"
+        android:layout_height="38dp"
+        app:layout_constraintBottom_toBottomOf="@+id/location_marker_iv_bg"
+        app:layout_constraintEnd_toEndOf="@+id/location_marker_iv_bg"
+        app:layout_constraintStart_toStartOf="@+id/location_marker_iv_bg"
+        app:layout_constraintTop_toTopOf="@+id/location_marker_iv_bg"
+        app:layout_constraintVertical_bias="0.25"
+        tools:src="@drawable/icon_default_friend_avatar" />
+
+
+</androidx.constraintlayout.widget.ConstraintLayout>