Ver Fonte

[new]完善紧急联系人求助功能

zk há 8 meses atrás
pai
commit
501cdd9b6d

BIN
assets/images/bg_urgent_contact_popup.webp


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

@@ -59,6 +59,7 @@
     <string name="member_experience_vip">免费VIP体验礼包</string>
     <string name="member_experience_vip_receive">去领取</string>
 
+    <string name="mine_urgent_contact">紧急联系人</string>
     <string name="mine_fun_share">邀请好友</string>
     <string name="mine_fun_customer_service">专属客服</string>
     <string name="mine_fun_permission_setting">权限设置</string>
@@ -207,5 +208,17 @@
     <string name="urgent_contact_subtitle">紧急联系人</string>
     <string name="urgent_contact_default_selected">默认</string>
     <string name="urgent_contact_send_help">一键发送求助</string>
-
+    <string name="urgent_contact_set_default">设为默认</string>
+    <string name="urgent_contact_cancel_default">取消默认</string>
+    <string name="urgent_contact_set_delete">删除</string>
+    <string name="urgent_contact_set_success">设置成功</string>
+    <string name="urgent_contact_cancel_success">取消成功</string>
+    <string name="urgent_contact_delete_success">删除成功</string>
+    <string name="urgent_contact_send_all_help">确认向您所有好友发送短信求助?</string>
+    <string name="urgent_contact_emergency_help">紧急求助</string>
+    <string name="contact_no_default">未设置默认紧急联系人</string>
+    <string name="urgent_contact_help_send_success">已成功发送求助信息</string>
+    <string name="urgent_contact_add_max_tip">最多添加5人,请移除后再添加</string>
+    <string name="urgent_contact_ems_send_fail">短信发送失败,请核实手机号码</string>
+    <string name="urgent_contact_ems_send_part_fail">部分号码发送失败</string>
 </resources>

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

@@ -14,6 +14,7 @@ import 'package:location/data/api/request/request_friendlist_request.dart';
 import 'package:location/data/api/request/send_code_request.dart';
 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/friends_list_response.dart';
 import 'package:location/data/api/response/login_response.dart';
 import 'package:location/data/api/response/member_status_response.dart';
@@ -111,4 +112,23 @@ abstract class AtmobApi {
 
   @POST("/s/v1/contact/create")
   Future<BaseResponse> contactCreate(@Body() ContactRequest request);
+
+  @POST("/s/v1/contact/favor")
+  Future<BaseResponse> contactFavor(@Body() ContactRequest request);
+
+  @POST("/s/v1/contact/delete")
+  Future<BaseResponse> contactDelete(@Body() ContactRequest request);
+
+  //向单个紧急联系人求救
+  @POST("/s/v1/contact/mayday")
+  Future<BaseResponse> contactMayDay(@Body() ContactRequest request);
+
+  //向默认紧急联系人求救
+  @POST("/s/v1/contact/mayday/favor")
+  Future<BaseResponse> contactMayDayFavor(@Body() AppBaseRequest request);
+
+  //向所有紧急联系人求救
+  @POST("/s/v1/contact/mayday/all")
+  Future<BaseResponse<ContactMayDayAllResponse>> contactMayDayAll(
+      @Body() AppBaseRequest request);
 }

+ 189 - 0
lib/data/api/atmob_api.g.dart

@@ -828,6 +828,195 @@ class _AtmobApi implements AtmobApi {
     return _value;
   }
 
+  @override
+  Future<BaseResponse<dynamic>> contactFavor(ContactRequest request) async {
+    final _extra = <String, dynamic>{};
+    final queryParameters = <String, dynamic>{};
+    final _headers = <String, dynamic>{};
+    final _data = <String, dynamic>{};
+    _data.addAll(request.toJson());
+    final _options = _setStreamType<BaseResponse<dynamic>>(Options(
+      method: 'POST',
+      headers: _headers,
+      extra: _extra,
+    )
+        .compose(
+          _dio.options,
+          '/s/v1/contact/favor',
+          queryParameters: queryParameters,
+          data: _data,
+        )
+        .copyWith(
+            baseUrl: _combineBaseUrls(
+          _dio.options.baseUrl,
+          baseUrl,
+        )));
+    final _result = await _dio.fetch<Map<String, dynamic>>(_options);
+    late BaseResponse<dynamic> _value;
+    try {
+      _value = BaseResponse<dynamic>.fromJson(
+        _result.data!,
+        (json) => json as dynamic,
+      );
+    } on Object catch (e, s) {
+      errorLogger?.logError(e, s, _options);
+      rethrow;
+    }
+    return _value;
+  }
+
+  @override
+  Future<BaseResponse<dynamic>> contactDelete(ContactRequest request) async {
+    final _extra = <String, dynamic>{};
+    final queryParameters = <String, dynamic>{};
+    final _headers = <String, dynamic>{};
+    final _data = <String, dynamic>{};
+    _data.addAll(request.toJson());
+    final _options = _setStreamType<BaseResponse<dynamic>>(Options(
+      method: 'POST',
+      headers: _headers,
+      extra: _extra,
+    )
+        .compose(
+          _dio.options,
+          '/s/v1/contact/delete',
+          queryParameters: queryParameters,
+          data: _data,
+        )
+        .copyWith(
+            baseUrl: _combineBaseUrls(
+          _dio.options.baseUrl,
+          baseUrl,
+        )));
+    final _result = await _dio.fetch<Map<String, dynamic>>(_options);
+    late BaseResponse<dynamic> _value;
+    try {
+      _value = BaseResponse<dynamic>.fromJson(
+        _result.data!,
+        (json) => json as dynamic,
+      );
+    } on Object catch (e, s) {
+      errorLogger?.logError(e, s, _options);
+      rethrow;
+    }
+    return _value;
+  }
+
+  @override
+  Future<BaseResponse<dynamic>> contactMayDay(ContactRequest request) async {
+    final _extra = <String, dynamic>{};
+    final queryParameters = <String, dynamic>{};
+    final _headers = <String, dynamic>{};
+    final _data = <String, dynamic>{};
+    _data.addAll(request.toJson());
+    final _options = _setStreamType<BaseResponse<dynamic>>(Options(
+      method: 'POST',
+      headers: _headers,
+      extra: _extra,
+    )
+        .compose(
+          _dio.options,
+          '/s/v1/contact/mayday',
+          queryParameters: queryParameters,
+          data: _data,
+        )
+        .copyWith(
+            baseUrl: _combineBaseUrls(
+          _dio.options.baseUrl,
+          baseUrl,
+        )));
+    final _result = await _dio.fetch<Map<String, dynamic>>(_options);
+    late BaseResponse<dynamic> _value;
+    try {
+      _value = BaseResponse<dynamic>.fromJson(
+        _result.data!,
+        (json) => json as dynamic,
+      );
+    } on Object catch (e, s) {
+      errorLogger?.logError(e, s, _options);
+      rethrow;
+    }
+    return _value;
+  }
+
+  @override
+  Future<BaseResponse<dynamic>> contactMayDayFavor(
+      AppBaseRequest request) async {
+    final _extra = <String, dynamic>{};
+    final queryParameters = <String, dynamic>{};
+    final _headers = <String, dynamic>{};
+    final _data = <String, dynamic>{};
+    _data.addAll(request.toJson());
+    final _options = _setStreamType<BaseResponse<dynamic>>(Options(
+      method: 'POST',
+      headers: _headers,
+      extra: _extra,
+    )
+        .compose(
+          _dio.options,
+          '/s/v1/contact/mayday/favor',
+          queryParameters: queryParameters,
+          data: _data,
+        )
+        .copyWith(
+            baseUrl: _combineBaseUrls(
+          _dio.options.baseUrl,
+          baseUrl,
+        )));
+    final _result = await _dio.fetch<Map<String, dynamic>>(_options);
+    late BaseResponse<dynamic> _value;
+    try {
+      _value = BaseResponse<dynamic>.fromJson(
+        _result.data!,
+        (json) => json as dynamic,
+      );
+    } on Object catch (e, s) {
+      errorLogger?.logError(e, s, _options);
+      rethrow;
+    }
+    return _value;
+  }
+
+  @override
+  Future<BaseResponse<ContactMayDayAllResponse>> contactMayDayAll(
+      AppBaseRequest request) async {
+    final _extra = <String, dynamic>{};
+    final queryParameters = <String, dynamic>{};
+    final _headers = <String, dynamic>{};
+    final _data = <String, dynamic>{};
+    _data.addAll(request.toJson());
+    final _options =
+        _setStreamType<BaseResponse<ContactMayDayAllResponse>>(Options(
+      method: 'POST',
+      headers: _headers,
+      extra: _extra,
+    )
+            .compose(
+              _dio.options,
+              '/s/v1/contact/mayday/all',
+              queryParameters: queryParameters,
+              data: _data,
+            )
+            .copyWith(
+                baseUrl: _combineBaseUrls(
+              _dio.options.baseUrl,
+              baseUrl,
+            )));
+    final _result = await _dio.fetch<Map<String, dynamic>>(_options);
+    late BaseResponse<ContactMayDayAllResponse> _value;
+    try {
+      _value = BaseResponse<ContactMayDayAllResponse>.fromJson(
+        _result.data!,
+        (json) =>
+            ContactMayDayAllResponse.fromJson(json as Map<String, dynamic>),
+      );
+    } on Object catch (e, s) {
+      errorLogger?.logError(e, s, _options);
+      rethrow;
+    }
+    return _value;
+  }
+
   RequestOptions _setStreamType<T>(RequestOptions requestOptions) {
     if (T != dynamic &&
         !(requestOptions.responseType == ResponseType.bytes ||

+ 20 - 0
lib/data/api/response/contact_may_day_all_response.dart

@@ -0,0 +1,20 @@
+import 'package:json_annotation/json_annotation.dart';
+
+part 'contact_may_day_all_response.g.dart';
+
+@JsonSerializable()
+class ContactMayDayAllResponse {
+  @JsonKey(name: 'success')
+  List<String>? success;
+
+  @JsonKey(name: 'fail')
+  List<String>? fail;
+
+  ContactMayDayAllResponse({
+    this.success,
+    this.fail,
+  });
+
+  factory ContactMayDayAllResponse.fromJson(Map<String, dynamic> json) =>
+      _$ContactMayDayAllResponseFromJson(json);
+}

+ 22 - 0
lib/data/api/response/contact_may_day_all_response.g.dart

@@ -0,0 +1,22 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'contact_may_day_all_response.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+ContactMayDayAllResponse _$ContactMayDayAllResponseFromJson(
+        Map<String, dynamic> json) =>
+    ContactMayDayAllResponse(
+      success:
+          (json['success'] as List<dynamic>?)?.map((e) => e as String).toList(),
+      fail: (json['fail'] as List<dynamic>?)?.map((e) => e as String).toList(),
+    );
+
+Map<String, dynamic> _$ContactMayDayAllResponseToJson(
+        ContactMayDayAllResponse instance) =>
+    <String, dynamic>{
+      'success': instance.success,
+      'fail': instance.fail,
+    };

+ 8 - 3
lib/data/consts/error_code.dart

@@ -24,14 +24,19 @@ class ErrorCode {
 
 /// 错误码扩展方法
 extension ErrorDescription on int {
-  String get description {
+  String? get description {
     switch (this) {
       case ErrorCode.verificationCodeError:
         return StringName.loginVerificationCodeErrorToast;
       case ErrorCode.noLoginError:
         return StringName.accountNoLogin;
-      default:
-        return 'UNKNOWN_ERROR';
+      case ErrorCode.noMember:
+        return StringName.memberExpired;
+      case ErrorCode.maxContactsReached:
+        return StringName.urgentContactAddMaxTip;
+      case ErrorCode.smsSendFailed:
+        return StringName.urgentContactEmsSendFail;
     }
+    return null;
   }
 }

+ 5 - 0
lib/data/repositories/account_repository.dart

@@ -12,6 +12,7 @@ import 'package:location/data/bean/user_info.dart';
 import 'package:location/data/consts/constants.dart';
 import 'package:location/data/consts/error_code.dart';
 import 'package:location/data/repositories/message_repository.dart';
+import 'package:location/data/repositories/urgent_contact_repository.dart';
 import 'package:location/socket/atmob_location_client.dart';
 import 'package:location/data/repositories/friends_repository.dart';
 import 'package:location/di/get_it.dart';
@@ -45,6 +46,7 @@ class AccountRepository {
 
   late final FriendsRepository friendsRepository;
   late final MessageRepository messageRepository;
+  late final UrgentContactRepository urgentContactRepository;
 
   final Rx<UserInfo> mineUserInfo = Rx<UserInfo>(UserInfo(
       id: Constants.mineLocationId,
@@ -55,6 +57,7 @@ class AccountRepository {
     AtmobLog.d(tag, '$tag....init');
     friendsRepository = FriendsRepository.getInstance();
     messageRepository = MessageRepository.getInstance();
+    urgentContactRepository = UrgentContactRepository.getInstance();
 
     isLogin.bindStream(
       loginPhoneNum.map((value) {
@@ -120,6 +123,7 @@ class AccountRepository {
     refreshMemberStatus();
     friendsRepository.refreshFriends();
     messageRepository.refreshFriendWaitingCount();
+    urgentContactRepository.requestUrgentContactList();
   }
 
   void logout() {
@@ -137,6 +141,7 @@ class AccountRepository {
 
     friendsRepository.clearFriends();
     messageRepository.clearMessage();
+    urgentContactRepository.clearContactList();
   }
 
   void refreshMemberStatus() {

+ 85 - 2
lib/data/repositories/urgent_contact_repository.dart

@@ -1,26 +1,109 @@
+import 'package:get/get_rx/src/rx_types/rx_types.dart';
 import 'package:injectable/injectable.dart';
 import 'package:location/data/api/atmob_api.dart';
+import 'package:location/data/consts/error_code.dart';
+import 'package:location/utils/async_util.dart';
 import 'package:location/utils/http_handler.dart';
 
 import '../../base/app_base_request.dart';
+import '../../di/get_it.dart';
 import '../api/request/contact_request.dart';
 import '../api/response/contact_list_response.dart';
+import '../api/response/contact_may_day_all_response.dart';
+import '../bean/contact_info.dart';
 
 @lazySingleton
 class UrgentContactRepository {
   final AtmobApi atmobApi;
+  final RxList<ContactInfo> contactList = RxList<ContactInfo>();
+  CancelableFuture? _cancelableFuture;
 
-  UrgentContactRepository(this.atmobApi);
+  UrgentContactRepository(this.atmobApi) {
+    requestUrgentContactList();
+  }
+
+  static UrgentContactRepository getInstance() {
+    return getIt.get<UrgentContactRepository>();
+  }
+
+  void requestUrgentContactList() {
+    _cancelableFuture?.cancel();
+    _cancelableFuture = AsyncUtil.retryWithExponentialBackoff(
+        () => getContactList(), 6, predicate: (error) {
+      if (error is ServerErrorException &&
+          error.code == ErrorCode.noLoginError) {
+        return false;
+      }
+      return true;
+    });
+  }
 
   Future<ContactListResponse> getContactList() {
     return atmobApi
         .getContactList(AppBaseRequest())
-        .then(HttpHandler.handle(true));
+        .then(HttpHandler.handle(true))
+        .then((value) {
+      contactList.clear();
+      if (value.list != null) {
+        contactList.addAll(value.list!);
+      }
+      return value;
+    });
   }
 
   Future<void> addContact(String contactPhone) {
     return atmobApi
         .contactCreate(ContactRequest(phone: contactPhone))
+        .then(HttpHandler.handle(true))
+        .then((_) => requestUrgentContactList());
+  }
+
+  Future<void> contactFavor(String phone, bool favor) {
+    return atmobApi
+        .contactFavor(ContactRequest(phone: phone, favor: favor))
+        .then(HttpHandler.handle(true))
+        .then((_) => requestUrgentContactList());
+  }
+
+  Future<void> contactMayDay(String phone) {
+    return atmobApi
+        .contactMayDay(ContactRequest(phone: phone))
         .then(HttpHandler.handle(true));
   }
+
+  Future<void> contactMayDayFavor() {
+    return atmobApi
+        .contactMayDayFavor(AppBaseRequest())
+        .then(HttpHandler.handle(true));
+  }
+
+  Future<ContactMayDayAllResponse> contactMayDayAll() {
+    return atmobApi
+        .contactMayDayAll(AppBaseRequest())
+        .then(HttpHandler.handle(true));
+  }
+
+  Future<void> contactDelete(String phone) {
+    return atmobApi
+        .contactDelete(ContactRequest(phone: phone))
+        .then(HttpHandler.handle(true))
+        .then((_) => requestUrgentContactList());
+  }
+
+  void clearContactList() {
+    _cancelableFuture?.cancel();
+    contactList.clear();
+  }
+
+  ContactInfo? getDefaultContact() {
+    if (contactList.isEmpty) {
+      return null;
+    }
+    for (var value in contactList) {
+      if (value.favor == true) {
+        return value;
+      }
+    }
+    return null;
+  }
 }

+ 15 - 8
lib/di/get_it.config.dart

@@ -94,21 +94,28 @@ extension GetItInjectableX on _i174.GetIt {
               gh<_i791.MessageRepository>(),
               gh<_i1053.FriendsRepository>(),
             ));
-    gh.factory<_i720.UrgentContactController>(() =>
-        _i720.UrgentContactController(gh<_i983.UrgentContactRepository>()));
-    gh.factory<_i897.AddFriendDialogController>(
-        () => _i897.AddFriendDialogController(gh<_i1053.FriendsRepository>()));
-    gh.factory<_i492.FriendSettingController>(
-        () => _i492.FriendSettingController(gh<_i1053.FriendsRepository>()));
-    gh.factory<_i955.AddUrgentContactController>(() =>
-        _i955.AddUrgentContactController(gh<_i983.UrgentContactRepository>()));
     gh.factory<_i731.MainController>(() => _i731.MainController(
           gh<_i1053.FriendsRepository>(),
           gh<_i20.AccountRepository>(),
           gh<_i791.MessageRepository>(),
           gh<_i220.AtmobLocationClient>(),
+          gh<_i983.UrgentContactRepository>(),
           gh<_i825.ConfigRepository>(),
         ));
+    gh.factory<_i955.AddUrgentContactController>(
+        () => _i955.AddUrgentContactController(
+              gh<_i983.UrgentContactRepository>(),
+              gh<_i20.AccountRepository>(),
+            ));
+    gh.factory<_i720.UrgentContactController>(
+        () => _i720.UrgentContactController(
+              gh<_i983.UrgentContactRepository>(),
+              gh<_i20.AccountRepository>(),
+            ));
+    gh.factory<_i897.AddFriendDialogController>(
+        () => _i897.AddFriendDialogController(gh<_i1053.FriendsRepository>()));
+    gh.factory<_i492.FriendSettingController>(
+        () => _i492.FriendSettingController(gh<_i1053.FriendsRepository>()));
     return this;
   }
 }

+ 114 - 0
lib/dialog/common_alert_dialog_impl.dart

@@ -82,3 +82,117 @@ void showBlockMeTipDialog({required VoidCallback confirmOnTap}) {
         CommonAlertDialog.dismiss(tag: tag);
       });
 }
+
+void showDeleteUrgentContactDialog(String phone,
+    {required VoidCallback confirmOnTap}) {
+  final tag = 'showDeleteUrgentContactDialog';
+  CommonAlertDialog.show(
+      tag: tag,
+      titleWidget: Text(
+        StringName.kindlyReminder,
+        style: TextStyle(
+            fontSize: 17.sp,
+            color: '#333333'.color,
+            fontWeight: FontWeight.bold),
+      ),
+      descWidget: Text(
+        '您确定要将 $phone 移除紧急联系人吗?',
+        style: TextStyle(fontSize: 15.sp, color: '#404040'.color),
+      ),
+      cancelText: StringName.dialogCancel,
+      confirmText: StringName.dialogSure,
+      cancelOnTap: () {
+        CommonAlertDialog.dismiss(tag: tag);
+      },
+      confirmOnTap: () {
+        confirmOnTap();
+        CommonAlertDialog.dismiss(tag: tag);
+      });
+}
+
+void sendUrgentContactDialog(String phone,
+    {required VoidCallback confirmOnTap}) {
+  final tag = 'sendUrgentContactDialog';
+  CommonAlertDialog.show(
+      tag: tag,
+      titleWidget: Text(
+        StringName.urgentContactEmergencyHelp,
+        style: TextStyle(
+            fontSize: 17.sp,
+            color: '#333333'.color,
+            fontWeight: FontWeight.bold),
+      ),
+      descWidget: Text(
+        '确认向 $phone 发送短信求助?',
+        style: TextStyle(fontSize: 15.sp, color: '#404040'.color),
+      ),
+      cancelText: StringName.dialogCancel,
+      confirmText: StringName.dialogSure,
+      cancelOnTap: () {
+        CommonAlertDialog.dismiss(tag: tag);
+      },
+      confirmOnTap: () {
+        confirmOnTap();
+        CommonAlertDialog.dismiss(tag: tag);
+      });
+}
+
+void sendAllUrgentContactDialog({required VoidCallback confirmOnTap}) {
+  final tag = 'sendAllUrgentContactDialog';
+  CommonAlertDialog.show(
+      tag: tag,
+      titleWidget: Text(
+        StringName.kindlyReminder,
+        style: TextStyle(
+            fontSize: 17.sp,
+            color: '#333333'.color,
+            fontWeight: FontWeight.bold),
+      ),
+      descWidget: Text(
+        StringName.urgentContactSendAllHelp,
+        style: TextStyle(fontSize: 15.sp, color: '#404040'.color),
+      ),
+      cancelText: StringName.dialogCancel,
+      confirmText: StringName.dialogSure,
+      cancelOnTap: () {
+        CommonAlertDialog.dismiss(tag: tag);
+      },
+      confirmOnTap: () {
+        confirmOnTap();
+        CommonAlertDialog.dismiss(tag: tag);
+      });
+}
+
+String _failPhonesConvertString(List<String> phones) {
+  if (phones.isNotEmpty) {
+    return phones.join('、');
+  }
+  return "";
+}
+
+void sendUrgentContactPartErrorDialog(List<String> phones,
+    {required VoidCallback confirmOnTap}) {
+  final tag = 'sendUrgentContactPartErrorDialog';
+  CommonAlertDialog.show(
+      tag: tag,
+      titleWidget: Text(
+        StringName.urgentContactEmsSendPartFail,
+        style: TextStyle(
+            fontSize: 17.sp,
+            color: '#333333'.color,
+            fontWeight: FontWeight.bold),
+      ),
+      descWidget: Text(
+        '联系人 ${phones.map((phone) => '「$phone」').join('、')} 发送失败,请检查号码重试!',
+        style: TextStyle(fontSize: 15.sp, color: '#404040'.color),
+      ),
+      cancelText: StringName.dialogCancel,
+      confirmText: StringName.dialogSure,
+      cancelOnTap: () {
+        CommonAlertDialog.dismiss(tag: tag);
+      },
+      confirmOnTap: () {
+        confirmOnTap();
+        CommonAlertDialog.dismiss(tag: tag);
+      });
+}

+ 26 - 1
lib/module/main/main_controller.dart

@@ -23,6 +23,7 @@ import 'package:location/utils/base_expand.dart';
 import 'package:location/utils/toast_util.dart';
 import '../../data/bean/request_friend_info.dart';
 import '../../data/repositories/config_repository.dart';
+import '../../data/repositories/urgent_contact_repository.dart';
 import '../../dialog/common_alert_dialog_impl.dart';
 import '../../dialog/common_confirm_dialog_impl.dart';
 import '../../sdk/wechat/wechat_share_util.dart';
@@ -60,6 +61,7 @@ class MainController extends BaseController {
   final FriendsRepository friendsRepository;
   final AccountRepository accountRepository;
   final MessageRepository messageRepository;
+  final UrgentContactRepository urgentContactRepository;
 
   int get waitingNewsCount => messageRepository.waitingCount.value;
 
@@ -68,6 +70,7 @@ class MainController extends BaseController {
       this.accountRepository,
       this.messageRepository,
       AtmobLocationClient atmobLocationClient,
+      this.urgentContactRepository,
       ConfigRepository configRepository);
 
   @override
@@ -281,6 +284,28 @@ class MainController extends BaseController {
       LoginPage.start();
       return;
     }
-    UrgentContactPage.start();
+    //如果没有紧急联系人,则先进入页面
+    if (urgentContactRepository.contactList.isEmpty) {
+      UrgentContactPage.start();
+      return;
+    }
+    if (accountRepository.memberIsExpired()) {
+      MemberPage.start();
+      return;
+    }
+    final defaultContact = urgentContactRepository.getDefaultContact();
+    if (defaultContact != null) {
+      sendUrgentContactDialog(defaultContact.phone, confirmOnTap: () {
+        urgentContactRepository.contactMayDayFavor().then((value) {
+          ToastUtil.show(StringName.urgentContactHelpSendSuccess);
+        }).catchError((error) {
+          ErrorHandler.toastError(error);
+        });
+      });
+      return;
+    } else {
+      ToastUtil.show(StringName.contactNoDefault);
+      UrgentContactPage.start();
+    }
   }
 }

+ 5 - 0
lib/module/mine/mine_controller.dart

@@ -4,6 +4,7 @@ import 'package:injectable/injectable.dart';
 import 'package:location/base/base_controller.dart';
 import 'package:location/data/bean/member_status_info.dart';
 import 'package:location/module/login/login_page.dart';
+import 'package:location/module/urgent_contact/urgent_contact_page.dart';
 import 'package:location/resource/string.gen.dart';
 import '../../data/repositories/account_repository.dart';
 import '../../dialog/common_alert_dialog_impl.dart';
@@ -56,4 +57,8 @@ class MineController extends BaseController {
     }
     LoginPage.start();
   }
+
+  onUrgentContactClick() {
+    UrgentContactPage.start();
+  }
 }

+ 3 - 0
lib/module/mine/mine_page.dart

@@ -318,6 +318,9 @@ class MinePage extends BasePage<MineController> {
       child: Column(
         children: [
           buildMineFunItem(Assets.images.iconMineFunShare.provider(),
+              StringName.mineUrgentContact,
+              () => controller.onUrgentContactClick()),
+          buildMineFunItem(Assets.images.iconMineFunShare.provider(),
               StringName.mineFunShare, () => controller.onShareClick()),
           buildMineFunItem(
               Assets.images.iconMineFunCustomerService.provider(),

+ 9 - 1
lib/module/urgent_contact/add_contact/add_urgent_contact_controller.dart

@@ -5,8 +5,10 @@ import 'package:get/get_core/src/get_main.dart';
 import 'package:injectable/injectable.dart';
 import 'package:location/base/base_controller.dart';
 import 'package:location/data/consts/error_code.dart';
+import 'package:location/data/repositories/account_repository.dart';
 import 'package:location/data/repositories/urgent_contact_repository.dart';
 import 'package:location/handler/error_handler.dart';
+import 'package:location/module/member/member_page.dart';
 import 'package:location/utils/common_expand.dart';
 import 'package:location/utils/http_handler.dart';
 import 'package:permission_handler/permission_handler.dart';
@@ -28,8 +30,10 @@ class AddUrgentContactController extends BaseController {
   final Debounce _debounce = Debounce(debounceTime: 500);
 
   final UrgentContactRepository urgentContactRepository;
+  final AccountRepository accountRepository;
 
-  AddUrgentContactController(this.urgentContactRepository);
+  AddUrgentContactController(
+      this.urgentContactRepository, this.accountRepository);
 
   @override
   void onReady() {
@@ -79,6 +83,10 @@ class AddUrgentContactController extends BaseController {
       return;
     }
     _debounce.onClick(() {
+      if (accountRepository.memberIsExpired()) {
+        MemberPage.start();
+        return;
+      }
       urgentContactRepository.addContact(contactPhone).then((_) {
         ToastUtil.show(StringName.urgentContactAddSuccess);
         Get.back(result: true);

+ 73 - 19
lib/module/urgent_contact/urgent_contact_controller.dart

@@ -1,50 +1,104 @@
+import 'package:flutter/cupertino.dart';
 import 'package:get/get.dart';
 import 'package:injectable/injectable.dart';
 import 'package:location/base/base_controller.dart';
+import 'package:location/data/repositories/account_repository.dart';
 import 'package:location/data/repositories/urgent_contact_repository.dart';
 import 'package:location/handler/error_handler.dart';
+import 'package:location/module/member/member_page.dart';
+import 'package:location/popup/urgent_contact_more_action_popup.dart';
+import 'package:location/resource/string.gen.dart';
+import 'package:location/utils/toast_util.dart';
 
 import '../../data/bean/contact_info.dart';
+import '../../dialog/common_alert_dialog_impl.dart';
 import 'add_contact/add_urgent_contact_view.dart';
 
 @injectable
 class UrgentContactController extends BaseController {
-  final RxBool _isLoaded = false.obs;
-
-  bool get isLoaded => _isLoaded.value;
-
   final UrgentContactRepository _urgentContactRepository;
+  final AccountRepository accountRepository;
 
-  final RxList<ContactInfo> contactList = RxList<ContactInfo>();
+  RxList<ContactInfo> get contactList => _urgentContactRepository.contactList;
 
-  UrgentContactController(this._urgentContactRepository);
+  UrgentContactController(
+      this._urgentContactRepository, this.accountRepository);
 
   @override
   void onReady() {
     super.onReady();
-    requestUrgentContactList();
   }
 
-  void requestUrgentContactList() {
-    _urgentContactRepository.getContactList().then((value) {
-      _isLoaded.value = true;
-      contactList.clear();
-      if (value.list != null) {
-        contactList.addAll(value.list!);
+  void back() {
+    Get.back();
+  }
+
+  void addContactClick() async {
+    AddUrgentContactView.show();
+  }
+
+  void moreClick(BuildContext context, ContactInfo e) {
+    UrgentContactMoreActionPopup.show(context, e.favor == true,
+        onSetDefault: () {
+      _setDefaultContact(e);
+    }, onDelete: () {
+      showDeleteUrgentContactDialog(e.phone,
+          confirmOnTap: () => _deleteContact(e));
+    });
+  }
+
+  void _setDefaultContact(ContactInfo contactInfo) {
+    _urgentContactRepository
+        .contactFavor(
+            contactInfo.phone, contactInfo.favor == true ? false : true)
+        .then((value) {
+      if (contactInfo.favor == true) {
+        ToastUtil.show(StringName.urgentContactCancelSuccess);
+      } else {
+        ToastUtil.show(StringName.urgentContactSetSuccess);
       }
     }).catchError((e) {
       ErrorHandler.toastError(e);
     });
   }
 
-  void back() {
-    Get.back();
+  void _deleteContact(ContactInfo e) {
+    _urgentContactRepository.contactDelete(e.phone).then((value) {
+      ToastUtil.show(StringName.urgentContactDeleteSuccess);
+    }).catchError((e) {
+      ErrorHandler.toastError(e);
+    });
   }
 
-  void addContactClick() async {
-    bool? isCreateSuccess = await AddUrgentContactView.show();
-    if (isCreateSuccess != null && isCreateSuccess) {
-      requestUrgentContactList();
+  sendHelpClick(String phone) {
+    if (accountRepository.memberIsExpired()) {
+      MemberPage.start();
+      return;
     }
+    sendUrgentContactDialog(phone, confirmOnTap: () {
+      _urgentContactRepository.contactMayDay(phone).then((value) {
+        ToastUtil.show(StringName.urgentContactHelpSendSuccess);
+      }).catchError((e) {
+        ErrorHandler.toastError(e);
+      });
+    });
+  }
+
+  void sendAllHelpClick() {
+    if (accountRepository.memberIsExpired()) {
+      MemberPage.start();
+      return;
+    }
+    sendAllUrgentContactDialog(confirmOnTap: () {
+      _urgentContactRepository.contactMayDayAll().then((response) {
+        if (response.fail == null || response.fail!.isEmpty) {
+          ToastUtil.show(StringName.urgentContactHelpSendSuccess);
+        } else {
+          sendUrgentContactPartErrorDialog(response.fail!, confirmOnTap: () {});
+        }
+      }).catchError((e) {
+        ErrorHandler.toastError(e);
+      });
+    });
   }
 }

+ 31 - 26
lib/module/urgent_contact/urgent_contact_page.dart

@@ -28,21 +28,14 @@ class UrgentContactPage extends BasePage<UrgentContactController> {
   @override
   Widget buildBody(BuildContext context) {
     return Obx(() {
-      if (controller.isLoaded) {
-        return urgentPageView();
+      if (controller.contactList.isEmpty) {
+        return urgentEmptyView();
+      } else {
+        return urgentListView();
       }
-      return SizedBox.shrink();
     });
   }
 
-  Widget urgentPageView() {
-    if (controller.contactList.isEmpty) {
-      return urgentEmptyView();
-    } else {
-      return urgentListView();
-    }
-  }
-
   Widget urgentListView() {
     return Container(
       decoration: BoxDecoration(
@@ -86,6 +79,9 @@ class UrgentContactPage extends BasePage<UrgentContactController> {
                   ],
                 )),
                 Container(
+                  padding: EdgeInsets.only(
+                      left: 15.w, right: 15.w, top: 10.w, bottom: 15.w),
+                  width: double.infinity,
                   decoration: BoxDecoration(
                     color: ColorName.white,
                     boxShadow: [
@@ -95,18 +91,19 @@ class UrgentContactPage extends BasePage<UrgentContactController> {
                           blurRadius: 10)
                     ],
                   ),
-                  child: Container(
-                    decoration: BoxDecoration(
-                      color: '#FF5555'.color,
-                      borderRadius: BorderRadius.circular(10.w),
-                    ),
-                    height: 46.w,
-                    width: 330.w,
-                    margin: EdgeInsets.only(top: 10.w, bottom: 15.w),
-                    child: Center(
-                      child: Text(StringName.urgentContactSendHelp,
-                          style: TextStyle(
-                              fontSize: 14.sp, color: ColorName.white)),
+                  child: GestureDetector(
+                    onTap: () => controller.sendAllHelpClick(),
+                    child: Container(
+                      decoration: BoxDecoration(
+                        color: '#FF5555'.color,
+                        borderRadius: BorderRadius.circular(10.w),
+                      ),
+                      height: 46.w,
+                      child: Center(
+                        child: Text(StringName.urgentContactSendHelp,
+                            style: TextStyle(
+                                fontSize: 14.sp, color: ColorName.white)),
+                      ),
                     ),
                   ),
                 )
@@ -153,10 +150,18 @@ class UrgentContactPage extends BasePage<UrgentContactController> {
                     style: TextStyle(fontSize: 12.sp, color: '#858585'.color))),
           ),
           Spacer(),
-          Assets.images.iconUrgentContactDialPhone
-              .image(width: 40.w, height: 40.w),
+          GestureDetector(
+            onTap: () => controller.sendHelpClick(e.phone),
+            child: Assets.images.iconUrgentContactDialPhone
+                .image(width: 40.w, height: 40.w),
+          ),
           SizedBox(width: 16.w),
-          Assets.images.iconUrgentContactMore.image(width: 24.w, height: 24.w)
+          Builder(builder: (context) {
+            return GestureDetector(
+                onTap: () => controller.moreClick(context, e),
+                child: Assets.images.iconUrgentContactMore
+                    .image(width: 24.w, height: 24.w));
+          })
         ],
       ),
     );

+ 67 - 0
lib/popup/urgent_contact_more_action_popup.dart

@@ -0,0 +1,67 @@
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
+import 'package:location/resource/assets.gen.dart';
+import 'package:location/resource/colors.gen.dart';
+import 'package:location/resource/string.gen.dart';
+
+class UrgentContactMoreActionPopup {
+  static void show(BuildContext context, bool favor,
+      {required VoidCallback onSetDefault, required VoidCallback onDelete}) {
+    SmartDialog.showAttach(
+        alignment: Alignment.bottomLeft,
+        targetBuilder: (targetOffset, targetSize) =>
+            targetOffset + Offset(-30.w, -10.w),
+        animationType: SmartAnimationType.fade,
+        maskColor: ColorName.transparent,
+        targetContext: context,
+        builder: (_) {
+          return Container(
+            decoration: BoxDecoration(
+              image: DecorationImage(
+                image: Assets.images.bgUrgentContactPopup.provider(),
+                fit: BoxFit.fill,
+              ),
+            ),
+            width: 140.w,
+            height: 128.w,
+            child: Column(
+              mainAxisAlignment: MainAxisAlignment.center,
+              children: [
+                GestureDetector(
+                  behavior: HitTestBehavior.translucent,
+                  onTap: () {
+                    SmartDialog.dismiss();
+                    onSetDefault();
+                  },
+                  child: Container(
+                    padding: EdgeInsets.symmetric(vertical: 8.w),
+                    child: Text(
+                      favor
+                          ? StringName.urgentContactCancelDefault
+                          : StringName.urgentContactSetDefault,
+                      style: TextStyle(fontSize: 14.sp, color: ColorName.black),
+                    ),
+                  ),
+                ),
+                GestureDetector(
+                  behavior: HitTestBehavior.translucent,
+                  onTap: () {
+                    SmartDialog.dismiss();
+                    onDelete();
+                  },
+                  child: Container(
+                    padding: EdgeInsets.symmetric(vertical: 8.w),
+                    child: Text(
+                      StringName.urgentContactSetDelete,
+                      style: TextStyle(fontSize: 14.sp, color: ColorName.black),
+                    ),
+                  ),
+                )
+              ],
+            ),
+          );
+        });
+  }
+}

+ 5 - 0
lib/resource/assets.gen.dart

@@ -52,6 +52,10 @@ class $AssetsImagesGen {
   AssetGenImage get bgUrgentContactLogo =>
       const AssetGenImage('assets/images/bg_urgent_contact_logo.webp');
 
+  /// File path: assets/images/bg_urgent_contact_popup.webp
+  AssetGenImage get bgUrgentContactPopup =>
+      const AssetGenImage('assets/images/bg_urgent_contact_popup.webp');
+
   /// File path: assets/images/icon_agreement_close.webp
   AssetGenImage get iconAgreementClose =>
       const AssetGenImage('assets/images/icon_agreement_close.webp');
@@ -288,6 +292,7 @@ class $AssetsImagesGen {
         bgTrackLocationTie,
         bgUrgentContactAdd,
         bgUrgentContactLogo,
+        bgUrgentContactPopup,
         iconAgreementClose,
         iconBlackBack,
         iconCheckboxSelected,

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

@@ -48,6 +48,7 @@ class StringName {
   static final String memberVipPermanent = 'member_vip_permanent'.tr; // 永久会员
   static final String memberExperienceVip = 'member_experience_vip'.tr; // 免费VIP体验礼包
   static final String memberExperienceVipReceive = 'member_experience_vip_receive'.tr; // 去领取
+  static final String mineUrgentContact = 'mine_urgent_contact'.tr; // 紧急联系人
   static final String mineFunShare = 'mine_fun_share'.tr; // 邀请好友
   static final String mineFunCustomerService = 'mine_fun_customer_service'.tr; // 专属客服
   static final String mineFunPermissionSetting = 'mine_fun_permission_setting'.tr; // 权限设置
@@ -170,6 +171,31 @@ class StringName {
   static final String urgentContactSubtitle = 'urgent_contact_subtitle'.tr; // 紧急联系人
   static final String urgentContactDefaultSelected = 'urgent_contact_default_selected'.tr; // 默认
   static final String urgentContactSendHelp = 'urgent_contact_send_help'.tr; // 一键发送求助
+  static final String urgentContactSetDefault =
+      'urgent_contact_set_default'.tr; // 设为默认
+  static final String urgentContactCancelDefault =
+      'urgent_contact_cancel_default'.tr; // 取消默认
+  static final String urgentContactSetDelete =
+      'urgent_contact_set_delete'.tr; // 删除
+  static final String urgentContactSetSuccess =
+      'urgent_contact_set_success'.tr; // 设置成功
+  static final String urgentContactCancelSuccess =
+      'urgent_contact_cancel_success'.tr; // 取消成功
+  static final String urgentContactDeleteSuccess =
+      'urgent_contact_delete_success'.tr; // 删除成功
+  static final String urgentContactSendAllHelp =
+      'urgent_contact_send_all_help'.tr; // 确认向您所有好友发送短信求助?
+  static final String urgentContactEmergencyHelp =
+      'urgent_contact_emergency_help'.tr; // 紧急求助
+  static final String contactNoDefault = 'contact_no_default'.tr; // 未设置默认紧急联系人
+  static final String urgentContactHelpSendSuccess =
+      'urgent_contact_help_send_success'.tr; // 已成功发送求助信息
+  static final String urgentContactAddMaxTip =
+      'urgent_contact_add_max_tip'.tr; // 最多添加5人,请移除后再添加
+  static final String urgentContactEmsSendFail =
+      'urgent_contact_ems_send_fail'.tr; // 短信发送失败,请核实手机号码
+  static final String urgentContactEmsSendPartFail =
+      'urgent_contact_ems_send_part_fail'.tr; // 部分号码发送失败
 }
 class StringMultiSource {
   StringMultiSource._();
@@ -221,6 +247,7 @@ class StringMultiSource {
       'member_vip_permanent': '永久会员',
       'member_experience_vip': '免费VIP体验礼包',
       'member_experience_vip_receive': '去领取',
+      'mine_urgent_contact': '紧急联系人',
       'mine_fun_share': '邀请好友',
       'mine_fun_customer_service': '专属客服',
       'mine_fun_permission_setting': '权限设置',
@@ -343,6 +370,19 @@ class StringMultiSource {
       'urgent_contact_subtitle': '紧急联系人',
       'urgent_contact_default_selected': '默认',
       'urgent_contact_send_help': '一键发送求助',
+      'urgent_contact_set_default': '设为默认',
+      'urgent_contact_cancel_default': '取消默认',
+      'urgent_contact_set_delete': '删除',
+      'urgent_contact_set_success': '设置成功',
+      'urgent_contact_cancel_success': '取消成功',
+      'urgent_contact_delete_success': '删除成功',
+      'urgent_contact_send_all_help': '确认向您所有好友发送短信求助?',
+      'urgent_contact_emergency_help': '紧急求助',
+      'contact_no_default': '未设置默认紧急联系人',
+      'urgent_contact_help_send_success': '已成功发送求助信息',
+      'urgent_contact_add_max_tip': '最多添加5人,请移除后再添加',
+      'urgent_contact_ems_send_fail': '短信发送失败,请核实手机号码',
+      'urgent_contact_ems_send_part_fail': '部分号码发送失败',
     },
   };
 }