Explorar el Código

[new]增加紧急联系人新增&列表界面

zk hace 8 meses
padre
commit
da1dd5c693

BIN
assets/images/bg_urgent_contact_add.webp


BIN
assets/images/bg_urgent_contact_logo.webp


BIN
assets/images/icon_urgent_add.webp


BIN
assets/images/icon_urgent_contact_add.webp


BIN
assets/images/icon_urgent_contact_dial_phone.webp


BIN
assets/images/icon_urgent_contact_more.webp


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

@@ -195,5 +195,17 @@
     <string name="message_friend_agree">同意</string>
     <string name="message_friend_request_rejected">好友请求已拒绝</string>
     <string name="message_friend_request_agreed">好友请求已同意</string>
+    <string name="urgent_contact_title">添加紧急联系人</string>
+    <string name="urgent_contact_btn_txt">添加紧急联系人</string>
+    <string name="urgent_contact_once_click_help">使用一键求助</string>
+    <string name="urgent_contact_once_click_help_desc">
+        需要添加正确的紧急联系人手机号码,您的联系人将会收到短信以及APP消息通知
+    </string>
+    <string name="urgent_contact_has_been_added">对方已是您的紧急联系人</string>
+    <string name="urgent_contact_add_desc">紧急情况,快速求助</string>
+    <string name="urgent_contact_add_success">添加成功</string>
+    <string name="urgent_contact_subtitle">紧急联系人</string>
+    <string name="urgent_contact_default_selected">默认</string>
+    <string name="urgent_contact_send_help">一键发送求助</string>
 
 </resources>

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

@@ -3,6 +3,7 @@ import 'package:location/base/app_base_request.dart';
 import 'package:location/base/base_response.dart';
 import 'package:location/data/api/request/add_friend_request.dart';
 import 'package:location/data/api/request/configs_request.dart';
+import 'package:location/data/api/request/contact_request.dart';
 import 'package:location/data/api/request/friends_list_request.dart';
 import 'package:location/data/api/request/friends_operation_request.dart';
 import 'package:location/data/api/request/login_request.dart';
@@ -12,6 +13,7 @@ import 'package:location/data/api/request/query_track_request.dart';
 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/friends_list_response.dart';
 import 'package:location/data/api/response/login_response.dart';
 import 'package:location/data/api/response/member_status_response.dart';
@@ -102,4 +104,11 @@ abstract class AtmobApi {
   @POST("/s/v1/friend/request/refuse")
   Future<BaseResponse> refuseFriendAccept(
       @Body() OperationFriendRequest request);
+
+  @POST("/s/v1/contact/list")
+  Future<BaseResponse<ContactListResponse>> getContactList(
+      @Body() AppBaseRequest request);
+
+  @POST("/s/v1/contact/create")
+  Future<BaseResponse> contactCreate(@Body() ContactRequest request);
 }

+ 88 - 13
lib/data/api/atmob_api.g.dart

@@ -691,13 +691,13 @@ class _AtmobApi implements AtmobApi {
       extra: _extra,
     )
         .compose(
-      _dio.options,
-      '/s/v1/friend/request/accept',
-      queryParameters: queryParameters,
-      data: _data,
-    )
+          _dio.options,
+          '/s/v1/friend/request/accept',
+          queryParameters: queryParameters,
+          data: _data,
+        )
         .copyWith(
-        baseUrl: _combineBaseUrls(
+            baseUrl: _combineBaseUrls(
           _dio.options.baseUrl,
           baseUrl,
         )));
@@ -706,7 +706,7 @@ class _AtmobApi implements AtmobApi {
     try {
       _value = BaseResponse<dynamic>.fromJson(
         _result.data!,
-            (json) => json as dynamic,
+        (json) => json as dynamic,
       );
     } on Object catch (e, s) {
       errorLogger?.logError(e, s, _options);
@@ -729,13 +729,88 @@ class _AtmobApi implements AtmobApi {
       extra: _extra,
     )
         .compose(
-      _dio.options,
-      '/s/v1/friend/request/refuse',
-      queryParameters: queryParameters,
-      data: _data,
+          _dio.options,
+          '/s/v1/friend/request/refuse',
+          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<ContactListResponse>> getContactList(
+      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<ContactListResponse>>(Options(
+      method: 'POST',
+      headers: _headers,
+      extra: _extra,
     )
+        .compose(
+          _dio.options,
+          '/s/v1/contact/list',
+          queryParameters: queryParameters,
+          data: _data,
+        )
         .copyWith(
-        baseUrl: _combineBaseUrls(
+            baseUrl: _combineBaseUrls(
+          _dio.options.baseUrl,
+          baseUrl,
+        )));
+    final _result = await _dio.fetch<Map<String, dynamic>>(_options);
+    late BaseResponse<ContactListResponse> _value;
+    try {
+      _value = BaseResponse<ContactListResponse>.fromJson(
+        _result.data!,
+        (json) => ContactListResponse.fromJson(json as Map<String, dynamic>),
+      );
+    } on Object catch (e, s) {
+      errorLogger?.logError(e, s, _options);
+      rethrow;
+    }
+    return _value;
+  }
+
+  @override
+  Future<BaseResponse<dynamic>> contactCreate(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/create',
+          queryParameters: queryParameters,
+          data: _data,
+        )
+        .copyWith(
+            baseUrl: _combineBaseUrls(
           _dio.options.baseUrl,
           baseUrl,
         )));
@@ -744,7 +819,7 @@ class _AtmobApi implements AtmobApi {
     try {
       _value = BaseResponse<dynamic>.fromJson(
         _result.data!,
-            (json) => json as dynamic,
+        (json) => json as dynamic,
       );
     } on Object catch (e, s) {
       errorLogger?.logError(e, s, _options);

+ 21 - 0
lib/data/api/request/contact_request.dart

@@ -0,0 +1,21 @@
+import 'package:json_annotation/json_annotation.dart';
+import 'package:location/base/app_base_request.dart';
+
+part 'contact_request.g.dart';
+
+@JsonSerializable()
+class ContactRequest extends AppBaseRequest {
+  @JsonKey(name: 'phone')
+  String phone;
+
+  @JsonKey(name: 'favor')
+  bool? favor;
+
+  ContactRequest({required this.phone, this.favor});
+
+  factory ContactRequest.fromJson(Map<String, dynamic> json) =>
+      _$ContactRequestFromJson(json);
+
+  @override
+  Map<String, dynamic> toJson() => _$ContactRequestToJson(this);
+}

+ 2 - 2
lib/data/api/request/operation_friend_request.g.dart

@@ -7,7 +7,7 @@ part of 'operation_friend_request.dart';
 // **************************************************************************
 
 OperationFriendRequest _$OperationFriendRequestFromJson(
-    Map<String, dynamic> json) =>
+        Map<String, dynamic> json) =>
     OperationFriendRequest(
       (json['id'] as num).toInt(),
     )
@@ -39,7 +39,7 @@ OperationFriendRequest _$OperationFriendRequestFromJson(
       ..authToken = json['authToken'] as String?;
 
 Map<String, dynamic> _$OperationFriendRequestToJson(
-    OperationFriendRequest instance) =>
+        OperationFriendRequest instance) =>
     <String, dynamic>{
       'appPlatform': instance.appPlatform,
       'os': instance.os,

+ 2 - 2
lib/data/api/request/request_friendlist_request.g.dart

@@ -7,7 +7,7 @@ part of 'request_friendlist_request.dart';
 // **************************************************************************
 
 RequestFriendListRequest _$RequestFriendListRequestFromJson(
-    Map<String, dynamic> json) =>
+        Map<String, dynamic> json) =>
     RequestFriendListRequest(
       offset: (json['offset'] as num?)?.toInt(),
       limit: (json['limit'] as num?)?.toInt(),
@@ -42,7 +42,7 @@ RequestFriendListRequest _$RequestFriendListRequestFromJson(
       ..authToken = json['authToken'] as String?;
 
 Map<String, dynamic> _$RequestFriendListRequestToJson(
-    RequestFriendListRequest instance) =>
+        RequestFriendListRequest instance) =>
     <String, dynamic>{
       'appPlatform': instance.appPlatform,
       'os': instance.os,

+ 18 - 0
lib/data/api/response/contact_list_response.dart

@@ -0,0 +1,18 @@
+import 'package:json_annotation/json_annotation.dart';
+
+import '../../bean/contact_info.dart';
+
+part 'contact_list_response.g.dart';
+
+@JsonSerializable()
+class ContactListResponse {
+  @JsonKey(name: 'list')
+  List<ContactInfo>? list;
+
+  ContactListResponse(this.list);
+
+  factory ContactListResponse.fromJson(Map<String, dynamic> json) =>
+      _$ContactListResponseFromJson(json);
+
+  Map<String, dynamic> toJson() => _$ContactListResponseToJson(this);
+}

+ 28 - 0
lib/data/bean/contact_info.dart

@@ -0,0 +1,28 @@
+import 'package:json_annotation/json_annotation.dart';
+
+part 'contact_info.g.dart';
+
+@JsonSerializable()
+class ContactInfo {
+  @JsonKey(name: 'id')
+  int? id;
+
+  @JsonKey(name: 'phone')
+  String phone;
+
+  @JsonKey(name: 'remark')
+  String? remark;
+
+  @JsonKey(name: 'favor')
+  bool? favor;
+
+  @JsonKey(name: 'createTime')
+  int createTime;
+
+  ContactInfo(this.id, this.phone, this.remark, this.favor, this.createTime);
+
+  factory ContactInfo.fromJson(Map<String, dynamic> json) =>
+      _$ContactInfoFromJson(json);
+
+  Map<String, dynamic> toJson() => _$ContactInfoToJson(this);
+}

+ 26 - 0
lib/data/repositories/urgent_contact_repository.dart

@@ -0,0 +1,26 @@
+import 'package:injectable/injectable.dart';
+import 'package:location/data/api/atmob_api.dart';
+import 'package:location/utils/http_handler.dart';
+
+import '../../base/app_base_request.dart';
+import '../api/request/contact_request.dart';
+import '../api/response/contact_list_response.dart';
+
+@lazySingleton
+class UrgentContactRepository {
+  final AtmobApi atmobApi;
+
+  UrgentContactRepository(this.atmobApi);
+
+  Future<ContactListResponse> getContactList() {
+    return atmobApi
+        .getContactList(AppBaseRequest())
+        .then(HttpHandler.handle(true));
+  }
+
+  Future<void> addContact(String contactPhone) {
+    return atmobApi
+        .contactCreate(ContactRequest(phone: contactPhone))
+        .then(HttpHandler.handle(true));
+  }
+}

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

@@ -19,6 +19,7 @@ import '../data/repositories/contact_repository.dart' as _i850;
 import '../data/repositories/friends_repository.dart' as _i1053;
 import '../data/repositories/message_repository.dart' as _i791;
 import '../data/repositories/track_repository.dart' as _i240;
+import '../data/repositories/urgent_contact_repository.dart' as _i983;
 import '../module/add_friend/add_friend_dialog_controller.dart' as _i897;
 import '../module/browser/browser_controller.dart' as _i923;
 import '../module/friend/friend_controller.dart' as _i821;
@@ -31,6 +32,9 @@ import '../module/news/news_controller.dart' as _i489;
 import '../module/news/pending_list/news_pending_list_controller.dart' as _i433;
 import '../module/splash/splash_controller.dart' as _i973;
 import '../module/track/track_controller.dart' as _i518;
+import '../module/urgent_contact/add_contact/add_urgent_contact_controller.dart'
+    as _i955;
+import '../module/urgent_contact/urgent_contact_controller.dart' as _i720;
 import '../socket/atmob_location_client.dart' as _i220;
 import 'network_module.dart' as _i567;
 
@@ -64,6 +68,8 @@ extension GetItInjectableX on _i174.GetIt {
         () => _i791.MessageRepository(gh<_i243.AtmobApi>()));
     gh.lazySingleton<_i240.TrackRepository>(
         () => _i240.TrackRepository(gh<_i243.AtmobApi>()));
+    gh.lazySingleton<_i983.UrgentContactRepository>(
+        () => _i983.UrgentContactRepository(gh<_i243.AtmobApi>()));
     gh.factory<_i1008.LoginController>(
         () => _i1008.LoginController(gh<_i20.AccountRepository>()));
     gh.factory<_i732.MineController>(
@@ -88,10 +94,14 @@ 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>(),

+ 9 - 0
lib/module/main/main_controller.dart

@@ -16,6 +16,7 @@ import 'package:location/module/friend/friend_page.dart';
 import 'package:location/module/login/login_page.dart';
 import 'package:location/module/member/member_page.dart';
 import 'package:location/module/news/news_page.dart';
+import 'package:location/module/urgent_contact/urgent_contact_page.dart';
 import 'package:location/resource/string.gen.dart';
 import 'package:location/sdk/map/map_helper.dart';
 import 'package:location/utils/base_expand.dart';
@@ -274,4 +275,12 @@ class MainController extends BaseController {
     }
     NewsPage.start();
   }
+
+  onUrgentContactClick() {
+    if (!accountRepository.isLogin.value) {
+      LoginPage.start();
+      return;
+    }
+    UrgentContactPage.start();
+  }
 }

+ 2 - 1
lib/module/main/main_page.dart

@@ -171,7 +171,8 @@ class MainPage extends BasePage<MainController> {
         })),
         Expanded(
             child: buildFunItem(Assets.images.iconMainHelp.provider(),
-                StringName.mainHelpTab, () {})),
+                StringName.mainHelpTab,
+                () => controller.onUrgentContactClick())),
         Expanded(
             child: buildFunItem(Assets.images.iconMainMine.provider(),
                 StringName.mainMineTab, () => controller.onMineClick()))

+ 98 - 0
lib/module/urgent_contact/add_contact/add_urgent_contact_controller.dart

@@ -0,0 +1,98 @@
+import 'package:flutter/cupertino.dart';
+import 'package:flutter_contacts/flutter_contacts.dart';
+import 'package:get/get.dart';
+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/urgent_contact_repository.dart';
+import 'package:location/handler/error_handler.dart';
+import 'package:location/utils/common_expand.dart';
+import 'package:location/utils/http_handler.dart';
+import 'package:permission_handler/permission_handler.dart';
+
+import '../../../dialog/common_confirm_dialog_impl.dart';
+import '../../../resource/string.gen.dart';
+import '../../../utils/de_bounce.dart';
+import '../../../utils/permission_util.dart';
+import '../../../utils/toast_util.dart';
+
+@injectable
+class AddUrgentContactController extends BaseController {
+  final RxString _contactPhone = RxString("");
+
+  final TextEditingController etController = TextEditingController();
+
+  String get contactPhone => _contactPhone.value;
+
+  final Debounce _debounce = Debounce(debounceTime: 500);
+
+  final UrgentContactRepository urgentContactRepository;
+
+  AddUrgentContactController(this.urgentContactRepository);
+
+  @override
+  void onReady() {
+    etController.addListener(() {
+      _contactPhone.value = etController.text;
+    });
+  }
+
+  void back() {
+    Get.back();
+  }
+
+  void onSelectContactClick() async {
+    bool isGranted = await PermissionUtil.checkPermission(Permission.contacts);
+    if (!isGranted) {
+      requestContactsPermissionDialog(onConfirm: () async {
+        isGranted = await PermissionUtil.requestPermission(Permission.contacts);
+        if (isGranted) {
+          _goContact();
+        } else {
+          ToastUtil.show(StringName.permissionRequestFail);
+        }
+      });
+    } else {
+      _goContact();
+    }
+  }
+
+  void _goContact() async {
+    final contact = await FlutterContacts.openExternalPick();
+    if (contact != null) {
+      final phones = contact.phones;
+      if (phones.isNotEmpty) {
+        etController.text = phones.first.number.trimAll();
+      }
+    }
+  }
+
+  @override
+  void onClose() {
+    super.onClose();
+    etController.dispose();
+  }
+
+  void onAddClick() {
+    if (contactPhone.length < 11) {
+      return;
+    }
+    _debounce.onClick(() {
+      urgentContactRepository.addContact(contactPhone).then((_) {
+        ToastUtil.show(StringName.urgentContactAddSuccess);
+        Get.back(result: true);
+      }).catchError((e) {
+        if (e is ServerErrorException) {
+          if (e.code == ErrorCode.contactAlreadyAdded) {
+            ToastUtil.show(StringName.urgentContactHasBeenAdded);
+          } else {
+            ErrorHandler.toastError(e);
+          }
+        } else {
+          ErrorHandler.toastError(e);
+        }
+      });
+    });
+  }
+}

+ 164 - 0
lib/module/urgent_contact/add_contact/add_urgent_contact_view.dart

@@ -0,0 +1,164 @@
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/src/widgets/framework.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:get/get.dart';
+import 'package:get/get_core/src/get_main.dart';
+import 'package:location/base/base_view.dart';
+import 'package:location/resource/assets.gen.dart';
+import 'package:location/resource/string.gen.dart';
+import 'package:location/utils/common_expand.dart';
+import '../../../resource/colors.gen.dart';
+import 'add_urgent_contact_controller.dart';
+
+class AddUrgentContactView extends BaseView<AddUrgentContactController> {
+  const AddUrgentContactView({super.key});
+
+  static Future<T?> show<T>() {
+    return Get.bottomSheet(AddUrgentContactView(),
+        isScrollControlled: true,
+        barrierColor: ColorName.black55,
+        backgroundColor: ColorName.transparent);
+  }
+
+  @override
+  Color backgroundColor() {
+    return ColorName.transparent;
+  }
+
+  @override
+  Widget buildBody(BuildContext context) {
+    return Container(
+      decoration: BoxDecoration(
+        color: '#FCFCFC'.color,
+        borderRadius: BorderRadius.only(
+          topLeft: Radius.circular(20.w),
+          topRight: Radius.circular(20.w),
+        ),
+      ),
+      child: IntrinsicHeight(
+        child: Column(
+          children: [
+            SizedBox(height: 20.w),
+            Row(
+              children: [
+                SizedBox(width: 12.w),
+                Text(StringName.urgentContactTitle,
+                    style: TextStyle(
+                        fontSize: 14.sp,
+                        color: ColorName.black90,
+                        fontWeight: FontWeight.bold)),
+                Spacer(),
+                GestureDetector(
+                    onTap: controller.back,
+                    child: Assets.images.iconDialogClose
+                        .image(width: 20.w, height: 20.w)),
+                SizedBox(width: 12.w),
+              ],
+            ),
+            SizedBox(height: 20.w),
+            buildContactPhone(),
+            SizedBox(height: 22.w),
+            GestureDetector(
+              onTap: controller.onAddClick,
+              child: Obx(() {
+                return Container(
+                    decoration: BoxDecoration(
+                      color: ColorName.colorPrimary.withOpacity(
+                          controller.contactPhone.length < 11 ? 0.3 : 1),
+                      borderRadius: BorderRadius.circular(100.w),
+                    ),
+                    width: 244.w,
+                    height: 36.w,
+                    child: Center(
+                        child: Text(StringName.dialogSure,
+                            style: TextStyle(
+                                fontSize: 15.sp, color: ColorName.white))));
+              }),
+            ),
+            SizedBox(height: 36.w)
+          ],
+        ),
+      ),
+    );
+  }
+
+  Widget buildContactPhone() {
+    return Container(
+      decoration: BoxDecoration(
+          color: ColorName.white,
+          borderRadius: BorderRadius.circular(12.w),
+          boxShadow: [
+            BoxShadow(
+              color: ColorName.black.withOpacity(0.04),
+              offset: Offset(0, 2),
+              blurRadius: 4,
+            )
+          ]),
+      margin: EdgeInsets.symmetric(horizontal: 12.w),
+      padding: EdgeInsets.all(12.w),
+      child: Column(
+        children: [
+          Row(
+            children: [
+              Assets.images.iconLoginPhone.image(width: 18.w, height: 18.w),
+              SizedBox(width: 7.w),
+              Text(StringName.urgentContactTitle,
+                  style: TextStyle(fontSize: 14.sp, color: ColorName.black90)),
+            ],
+          ),
+          SizedBox(height: 18.w),
+          Row(
+            children: [
+              Expanded(
+                  child: Container(
+                decoration: BoxDecoration(
+                  color: '#F9F9F9'.color,
+                  borderRadius: BorderRadius.circular(10.w),
+                ),
+                child: TextField(
+                  controller: controller.etController,
+                  style: TextStyle(
+                      fontSize: 14.sp, color: ColorName.primaryTextColor),
+                  maxLines: 1,
+                  maxLength: 11,
+                  keyboardType: TextInputType.phone,
+                  textAlignVertical: TextAlignVertical.center,
+                  textInputAction: TextInputAction.next,
+                  decoration: InputDecoration(
+                    hintText: StringName.friendAddPhoneEtHint,
+                    counterText: '',
+                    hintStyle:
+                        TextStyle(fontSize: 16, color: "#AFAFAF".toColor()),
+                    labelStyle: const TextStyle(
+                      fontSize: 16,
+                      color: ColorName.primaryTextColor,
+                    ),
+                    contentPadding: EdgeInsets.symmetric(horizontal: 10.w),
+                    border:
+                        const OutlineInputBorder(borderSide: BorderSide.none),
+                    enabled: true,
+                  ),
+                ),
+              )),
+              SizedBox(width: 14.w),
+              GestureDetector(
+                onTap: controller.onSelectContactClick,
+                child: Row(
+                  children: [
+                    Assets.images.iconLoginAddressBook
+                        .image(width: 15.w, height: 15.w),
+                    SizedBox(width: 3.w),
+                    Text(StringName.friendAddAddressBook,
+                        style:
+                            TextStyle(fontSize: 14.sp, color: '#202020'.color))
+                  ],
+                ),
+              )
+            ],
+          )
+        ],
+      ),
+    );
+  }
+}

+ 50 - 0
lib/module/urgent_contact/urgent_contact_controller.dart

@@ -0,0 +1,50 @@
+import 'package:get/get.dart';
+import 'package:injectable/injectable.dart';
+import 'package:location/base/base_controller.dart';
+import 'package:location/data/repositories/urgent_contact_repository.dart';
+import 'package:location/handler/error_handler.dart';
+
+import '../../data/bean/contact_info.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 RxList<ContactInfo> contactList = RxList<ContactInfo>();
+
+  UrgentContactController(this._urgentContactRepository);
+
+  @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!);
+      }
+    }).catchError((e) {
+      ErrorHandler.toastError(e);
+    });
+  }
+
+  void back() {
+    Get.back();
+  }
+
+  void addContactClick() async {
+    bool? isCreateSuccess = await AddUrgentContactView.show();
+    if (isCreateSuccess != null && isCreateSuccess) {
+      requestUrgentContactList();
+    }
+  }
+}

+ 266 - 0
lib/module/urgent_contact/urgent_contact_page.dart

@@ -0,0 +1,266 @@
+import 'package:flutter/material.dart';
+import 'package:flutter/src/widgets/framework.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:get/get.dart';
+import 'package:get/get_core/src/get_main.dart';
+import 'package:location/base/base_page.dart';
+import 'package:location/data/bean/contact_info.dart';
+import 'package:location/module/urgent_contact/urgent_contact_controller.dart';
+import 'package:location/resource/assets.gen.dart';
+import 'package:location/utils/common_expand.dart';
+import 'package:location/widget/common_view.dart';
+import '../../resource/colors.gen.dart';
+import '../../resource/string.gen.dart';
+import '../../router/app_pages.dart';
+
+class UrgentContactPage extends BasePage<UrgentContactController> {
+  const UrgentContactPage({super.key});
+
+  static void start() {
+    Get.toNamed(RoutePath.urgentContact);
+  }
+
+  @override
+  bool immersive() {
+    return true;
+  }
+
+  @override
+  Widget buildBody(BuildContext context) {
+    return Obx(() {
+      if (controller.isLoaded) {
+        return urgentPageView();
+      }
+      return SizedBox.shrink();
+    });
+  }
+
+  Widget urgentPageView() {
+    if (controller.contactList.isEmpty) {
+      return urgentEmptyView();
+    } else {
+      return urgentListView();
+    }
+  }
+
+  Widget urgentListView() {
+    return Container(
+      decoration: BoxDecoration(
+        gradient: LinearGradient(
+            colors: [ColorName.white, '#F7F7F7'.color, '#F7F7F7'.color],
+            begin: Alignment.topCenter,
+            end: Alignment.bottomCenter),
+      ),
+      child: Stack(
+        children: [
+          Assets.images.bgPageBackground.image(width: 1.sw),
+          Positioned(
+            top: 0,
+            right: 35.w,
+            child: SafeArea(
+                child: Assets.images.bgUrgentContactLogo
+                    .image(width: 145.w, height: 145.w)),
+          ),
+          SafeArea(
+            child: Column(
+              children: [
+                CommonView.buildAppBar("", backOnTap: controller.back),
+                Expanded(
+                    child: ListView(
+                  children: [
+                    SizedBox(height: 25.h),
+                    buildAddContact(),
+                    SizedBox(height: 20.w),
+                    Padding(
+                      padding: EdgeInsets.only(left: 17.w),
+                      child: Text(
+                        StringName.urgentContactSubtitle,
+                        style: TextStyle(
+                            fontSize: 16.sp,
+                            color: ColorName.black90,
+                            fontWeight: FontWeight.bold),
+                      ),
+                    ),
+                    SizedBox(height: 14.w),
+                    ...controller.contactList.map((e) => buildContactItem(e))
+                  ],
+                )),
+                Container(
+                  decoration: BoxDecoration(
+                    color: ColorName.white,
+                    boxShadow: [
+                      BoxShadow(
+                          color: ColorName.black.withOpacity(0.1),
+                          offset: Offset(20, 20),
+                          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)),
+                    ),
+                  ),
+                )
+              ],
+            ),
+          )
+        ],
+      ),
+    );
+  }
+
+  Widget buildContactItem(ContactInfo e) {
+    return Container(
+      decoration: BoxDecoration(
+          color: ColorName.white,
+          borderRadius: BorderRadius.circular(11.w),
+          boxShadow: [
+            BoxShadow(
+                color: ColorName.black.withOpacity(0.03),
+                offset: Offset(2, 2),
+                blurRadius: 2)
+          ]),
+      margin: EdgeInsets.only(left: 12.w, right: 12.w, bottom: 10.w),
+      padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 16.w),
+      child: Row(
+        children: [
+          Assets.images.iconDefaultFriendAvatar
+              .image(width: 48.w, height: 48.w),
+          SizedBox(width: 8.w),
+          Text(e.remark ?? e.phone,
+              style: TextStyle(
+                  fontSize: 14.sp,
+                  color: ColorName.black90,
+                  fontWeight: FontWeight.bold)),
+          SizedBox(width: 8.w),
+          Visibility(
+            visible: e.favor == true,
+            child: Container(
+                decoration: BoxDecoration(
+                    color: '#1F969696'.color,
+                    borderRadius: BorderRadius.circular(4.w)),
+                padding: EdgeInsets.symmetric(horizontal: 9.w, vertical: 3.w),
+                child: Text(StringName.urgentContactDefaultSelected,
+                    style: TextStyle(fontSize: 12.sp, color: '#858585'.color))),
+          ),
+          Spacer(),
+          Assets.images.iconUrgentContactDialPhone
+              .image(width: 40.w, height: 40.w),
+          SizedBox(width: 16.w),
+          Assets.images.iconUrgentContactMore.image(width: 24.w, height: 24.w)
+        ],
+      ),
+    );
+  }
+
+  Widget urgentEmptyView() {
+    return SafeArea(
+      child: Column(
+        children: [
+          CommonView.buildAppBar(StringName.urgentContactTitle,
+              backOnTap: controller.back),
+          SizedBox(height: 40.h),
+          Assets.images.bgUrgentContactLogo.image(width: 268.w, height: 234.w),
+          SizedBox(height: 37.h),
+          Text(StringName.urgentContactOnceClickHelp,
+              style: TextStyle(
+                  fontSize: 18.sp,
+                  color: '#FF4B34'.color,
+                  fontWeight: FontWeight.bold)),
+          SizedBox(height: 21.h),
+          SizedBox(
+            width: 261.w,
+            child: Text(StringName.urgentContactOnceClickHelpDesc,
+                style: TextStyle(
+                  fontSize: 13.sp,
+                  color: ColorName.black90,
+                )),
+          ),
+          SizedBox(height: 60.h),
+          GestureDetector(
+            onTap: controller.addContactClick,
+            child: Container(
+              decoration: BoxDecoration(
+                  color: ColorName.colorPrimary,
+                  borderRadius: BorderRadius.circular(10.w)),
+              width: 320.w,
+              height: 42.w,
+              child: Row(
+                mainAxisAlignment: MainAxisAlignment.center,
+                children: [
+                  Assets.images.iconUrgentAdd.image(width: 12.w, height: 12.w),
+                  SizedBox(width: 5.w),
+                  Text(StringName.urgentContactBtnTxt,
+                      style:
+                          TextStyle(fontSize: 14.sp, color: ColorName.white)),
+                ],
+              ),
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+
+  Widget buildAddContact() {
+    return GestureDetector(
+      onTap: controller.addContactClick,
+      child: Container(
+        height: 66.w,
+        decoration: BoxDecoration(boxShadow: [
+          BoxShadow(
+              color: ColorName.black.withOpacity(0.06),
+              offset: Offset(0, 2),
+              blurRadius: 10)
+        ], color: ColorName.white, borderRadius: BorderRadius.circular(10.w)),
+        margin: EdgeInsets.symmetric(horizontal: 12.w),
+        child: Row(
+          mainAxisAlignment: MainAxisAlignment.center,
+          children: [
+            SizedBox(
+              width: 52.w,
+              height: 52.w,
+              child: Stack(
+                children: [
+                  Assets.images.bgUrgentContactAdd
+                      .image(width: double.infinity, height: double.infinity),
+                  Center(
+                    child: Assets.images.iconUrgentContactAdd
+                        .image(width: 28.w, height: 28.w),
+                  ),
+                ],
+              ),
+            ),
+            SizedBox(width: 18.w),
+            Column(
+              mainAxisAlignment: MainAxisAlignment.center,
+              crossAxisAlignment: CrossAxisAlignment.start,
+              children: [
+                Text(
+                  StringName.urgentContactTitle,
+                  style: TextStyle(
+                      fontSize: 14.sp,
+                      color: ColorName.black90,
+                      fontWeight: FontWeight.bold),
+                ),
+                SizedBox(height: 3.w),
+                Text(StringName.urgentContactAddDesc,
+                    style:
+                        TextStyle(fontSize: 12.sp, color: ColorName.black70)),
+              ],
+            )
+          ],
+        ),
+      ),
+    );
+  }
+}

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

@@ -44,6 +44,14 @@ class $AssetsImagesGen {
   AssetGenImage get bgTrackLocationTie =>
       const AssetGenImage('assets/images/bg_track_location_tie.webp');
 
+  /// File path: assets/images/bg_urgent_contact_add.webp
+  AssetGenImage get bgUrgentContactAdd =>
+      const AssetGenImage('assets/images/bg_urgent_contact_add.webp');
+
+  /// File path: assets/images/bg_urgent_contact_logo.webp
+  AssetGenImage get bgUrgentContactLogo =>
+      const AssetGenImage('assets/images/bg_urgent_contact_logo.webp');
+
   /// File path: assets/images/icon_agreement_close.webp
   AssetGenImage get iconAgreementClose =>
       const AssetGenImage('assets/images/icon_agreement_close.webp');
@@ -232,6 +240,22 @@ class $AssetsImagesGen {
   AssetGenImage get iconTrackSelectTimeArrow =>
       const AssetGenImage('assets/images/icon_track_select_time_arrow.webp');
 
+  /// File path: assets/images/icon_urgent_add.webp
+  AssetGenImage get iconUrgentAdd =>
+      const AssetGenImage('assets/images/icon_urgent_add.webp');
+
+  /// File path: assets/images/icon_urgent_contact_add.webp
+  AssetGenImage get iconUrgentContactAdd =>
+      const AssetGenImage('assets/images/icon_urgent_contact_add.webp');
+
+  /// File path: assets/images/icon_urgent_contact_dial_phone.webp
+  AssetGenImage get iconUrgentContactDialPhone =>
+      const AssetGenImage('assets/images/icon_urgent_contact_dial_phone.webp');
+
+  /// File path: assets/images/icon_urgent_contact_more.webp
+  AssetGenImage get iconUrgentContactMore =>
+      const AssetGenImage('assets/images/icon_urgent_contact_more.webp');
+
   /// File path: assets/images/icon_vip.webp
   AssetGenImage get iconVip =>
       const AssetGenImage('assets/images/icon_vip.webp');
@@ -262,6 +286,8 @@ class $AssetsImagesGen {
         bgMineMemberCard,
         bgPageBackground,
         bgTrackLocationTie,
+        bgUrgentContactAdd,
+        bgUrgentContactLogo,
         iconAgreementClose,
         iconBlackBack,
         iconCheckboxSelected,
@@ -309,6 +335,10 @@ class $AssetsImagesGen {
         iconSplashTitle,
         iconTrackLocationNow,
         iconTrackSelectTimeArrow,
+        iconUrgentAdd,
+        iconUrgentContactAdd,
+        iconUrgentContactDialPhone,
+        iconUrgentContactMore,
         iconVip,
         iconWhiteBack,
         imgDialogLocationAlwaysTip1,

+ 25 - 10
lib/resource/string.gen.dart

@@ -146,25 +146,30 @@ class StringName {
   static final String trackNoData = 'track_no_data'.tr; // 该时段内未查询到历史轨迹记录\n\n1.可能是该用户未登录本软件\n2.可能是该用户未运行我们的软件\n3.可能是对方未开启定位和网络连接等原因
   static final String newsTitle = 'news_title'.tr; // 消息中心
   static final String newsRequestTitle = 'news_request_title'.tr; // 新的好友
-  static final String newsRequestDesc =
-      'news_request_desc'.tr; // 您有新的好友申请,请及时查看
+  static final String newsRequestDesc = 'news_request_desc'.tr; // 您有新的好友申请,请及时查看
   static final String newsRequestAgree = 'news_request_agree'.tr; // 已同意
   static final String newsRequestDisagree = 'news_request_disagree'.tr; // 已拒绝
   static final String newsToContact = 'news_to_contact'.tr; // 去联系
-  static final String messageTryForHelp =
-      'message_try_for_help'.tr; // 您的好友需要紧急求助,请您尽快联系他!
+  static final String messageTryForHelp = 'message_try_for_help'.tr; // 您的好友需要紧急求助,请您尽快联系他!
   static final String messageAccepted = 'message_accepted'.tr; // 已同意您的好友申请
   static final String messageRejected = 'message_rejected'.tr; // 已拒绝您的好友申请
   static final String messageDeleteYour = 'message_delete_your'.tr; // 您的好友删除了你
   static final String messageNoData = 'message_no_data'.tr; // 暂无消息
-  static final String messageNewFriendTitle =
-      'message_new_friend_title'.tr; // 新的好友
+  static final String messageNewFriendTitle = 'message_new_friend_title'.tr; // 新的好友
   static final String messageFriendRefuse = 'message_friend_refuse'.tr; // 拒绝
   static final String messageFriendAgree = 'message_friend_agree'.tr; // 同意
-  static final String messageFriendRequestRejected =
-      'message_friend_request_rejected'.tr; // 好友请求已拒绝
-  static final String messageFriendRequestAgreed =
-      'message_friend_request_agreed'.tr; // 好友请求已同意
+  static final String messageFriendRequestRejected = 'message_friend_request_rejected'.tr; // 好友请求已拒绝
+  static final String messageFriendRequestAgreed = 'message_friend_request_agreed'.tr; // 好友请求已同意
+  static final String urgentContactTitle = 'urgent_contact_title'.tr; // 添加紧急联系人
+  static final String urgentContactBtnTxt = 'urgent_contact_btn_txt'.tr; // 添加紧急联系人
+  static final String urgentContactOnceClickHelp = 'urgent_contact_once_click_help'.tr; // 使用一键求助
+  static final String urgentContactOnceClickHelpDesc = 'urgent_contact_once_click_help_desc'.tr; // 需要添加正确的紧急联系人手机号码,您的联系人将会收到短信以及APP消息通知
+  static final String urgentContactHasBeenAdded = 'urgent_contact_has_been_added'.tr; // 对方已是您的紧急联系人
+  static final String urgentContactAddDesc = 'urgent_contact_add_desc'.tr; // 紧急情况,快速求助
+  static final String urgentContactAddSuccess = 'urgent_contact_add_success'.tr; // 添加成功
+  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; // 一键发送求助
 }
 class StringMultiSource {
   StringMultiSource._();
@@ -328,6 +333,16 @@ class StringMultiSource {
       'message_friend_agree': '同意',
       'message_friend_request_rejected': '好友请求已拒绝',
       'message_friend_request_agreed': '好友请求已同意',
+      'urgent_contact_title': '添加紧急联系人',
+      'urgent_contact_btn_txt': '添加紧急联系人',
+      'urgent_contact_once_click_help': '使用一键求助',
+      'urgent_contact_once_click_help_desc': '需要添加正确的紧急联系人手机号码,您的联系人将会收到短信以及APP消息通知',
+      'urgent_contact_has_been_added': '对方已是您的紧急联系人',
+      'urgent_contact_add_desc': '紧急情况,快速求助',
+      'urgent_contact_add_success': '添加成功',
+      'urgent_contact_subtitle': '紧急联系人',
+      'urgent_contact_default_selected': '默认',
+      'urgent_contact_send_help': '一键发送求助',
     },
   };
 }

+ 7 - 0
lib/router/app_pages.dart

@@ -13,6 +13,9 @@ import 'package:location/module/mine/mine_page.dart';
 import 'package:location/module/news/news_page.dart';
 import 'package:location/module/news/pending_list/news_pending_list_controller.dart';
 import 'package:location/module/news/pending_list/news_pending_list_page.dart';
+import 'package:location/module/urgent_contact/add_contact/add_urgent_contact_controller.dart';
+import 'package:location/module/urgent_contact/urgent_contact_controller.dart';
+import 'package:location/module/urgent_contact/urgent_contact_page.dart';
 import '../module/add_friend/add_friend_dialog_controller.dart';
 import '../module/login/login_page.dart';
 import '../module/main/main_controller.dart';
@@ -39,6 +42,7 @@ abstract class RoutePath {
   static const track = '/track';
   static const news = '/news';
   static const newsPendingList = '/newsPendingList';
+  static const urgentContact = '/urgentContact';
 }
 
 class AppBinding extends Bindings {
@@ -55,6 +59,8 @@ class AppBinding extends Bindings {
     lazyPut(() => getIt.get<TrackController>());
     lazyPut(() => getIt.get<NewsController>());
     lazyPut(() => getIt.get<NewsPendingListController>());
+    lazyPut(() => getIt.get<UrgentContactController>());
+    lazyPut(() => getIt.get<AddUrgentContactController>());
   }
 
   void lazyPut<S>(InstanceBuilderCallback<S> builder) {
@@ -73,4 +79,5 @@ final generalPages = [
   GetPage(name: RoutePath.track, page: () => TrackPage()),
   GetPage(name: RoutePath.news, page: () => NewsPage()),
   GetPage(name: RoutePath.newsPendingList, page: () => NewsPendingListPage()),
+  GetPage(name: RoutePath.urgentContact, page: () => UrgentContactPage()),
 ];

+ 35 - 0
lib/widget/common_view.dart

@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
 import 'package:flutter_screenutil/flutter_screenutil.dart';
 
 import '../resource/assets.gen.dart';
+import '../resource/colors.gen.dart';
 
 class CommonView {
   static Widget getBackBtnView() {
@@ -13,4 +14,38 @@ class CommonView {
             borderRadius: BorderRadius.all(Radius.circular(10.w))),
         child: Assets.images.iconBlackBack.image(width: 24.w, height: 24.w));
   }
+
+  static Widget buildAppBar(String title,
+      {VoidCallback? backOnTap, Widget? rightWidget}) {
+    return Container(
+      width: double.infinity,
+      padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 14.w),
+      child: Stack(
+        children: [
+          GestureDetector(onTap: backOnTap, child: CommonView.getBackBtnView()),
+          Positioned(
+            left: 0,
+            right: 0,
+            top: 0,
+            bottom: 0,
+            child: Center(
+              child: Text(title,
+                  style: TextStyle(
+                      height: 1,
+                      fontSize: 16.sp,
+                      color: ColorName.black90,
+                      fontWeight: FontWeight.bold)),
+            ),
+          ),
+          if (rightWidget != null)
+            Positioned(
+              right: 0,
+              top: 0,
+              bottom: 0,
+              child: rightWidget,
+            )
+        ],
+      ),
+    );
+  }
 }