Forráskód Böngészése

[new]完善添加好友流程

zk 1 éve
szülő
commit
06bdd44440

+ 4 - 0
android/app/src/main/AndroidManifest.xml

@@ -1,5 +1,9 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android">
 
+
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.READ_CONTACTS" />
+
     <application
         android:allowBackup="false"
         android:label="@string/app_name"

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

@@ -130,4 +130,26 @@
     <string name="friend_update_remark_title">修改备注</string>
     <string name="friend_update_remark_hint">请输入备注</string>
     <string name="remark_update_success">备注修改成功</string>
+    <string name="friend_add_now">立即添加</string>
+
+    <string name="go_request_contacts_permission">去申请</string>
+    <string name="request_contacts_content">
+        申请“通讯录权限”为了更便利读取通信录联系人信息,无需再手动输入
+    </string>
+    <string name="permission_request_fail">权限申请失败</string>
+    <string name="friend_add_explain">1.在您的对方同意添加您为好友之后,才能查看对方的位置。\n
+        2.如果您的好友还没有下载该应用,建议您邀请他们下载安装。\n
+        3.通过同意添加该用户为您的好友,即表示您同意本软件合法地收集、存储和使用您的信息,并将位置等信息与该好友分享。
+    </string>
+    <string name="dialog_not_login">登录之后才可以发送好友申请</string>
+    <string name="dialog_go_login">去登录</string>
+    <string name="invite_content">
+        该好友未注册,将应用分享给好友需要好友下载安装并同意授权后可查看定位
+    </string>
+    <string name="invite_btn">去微信分享</string>
+    <string name="request_success">发送成功</string>
+    <string name="request_fail">请求失败</string>
+    <string name="add_friend_added">该好友已在好友列表</string>
+    <string name="send_add_friend_success">好友申请已发出,请等待对方通过</string>
+    <string name="add_friend_own">不能添加自己为好友</string>
 </resources>

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

@@ -1,6 +1,7 @@
 import 'package:dio/dio.dart';
 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/friends_list_request.dart';
 import 'package:location/data/api/request/friends_operation_request.dart';
@@ -55,4 +56,7 @@ abstract class AtmobApi {
   @POST("/s/v1/friend/remark")
   Future<BaseResponse> updateFriendRemark(
       @Body() FriendsOperationRequest request);
+
+  @POST("/s/v1/friend/request/send")
+  Future<BaseResponse> addFriendRequest(@Body() AddFriendRequest request);
 }

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

@@ -365,6 +365,44 @@ class _AtmobApi implements AtmobApi {
     return _value;
   }
 
+  @override
+  Future<BaseResponse<dynamic>> addFriendRequest(
+      AddFriendRequest 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/friend/request/send',
+          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;
+  }
+
   RequestOptions _setStreamType<T>(RequestOptions requestOptions) {
     if (T != dynamic &&
         !(requestOptions.responseType == ResponseType.bytes ||

+ 18 - 0
lib/data/api/request/add_friend_request.dart

@@ -0,0 +1,18 @@
+import 'package:json_annotation/json_annotation.dart';
+import 'package:location/base/app_base_request.dart';
+
+part 'add_friend_request.g.dart';
+
+@JsonSerializable()
+class AddFriendRequest extends AppBaseRequest {
+  @JsonKey(name: "phone")
+  String phone;
+
+  AddFriendRequest(this.phone);
+
+  factory AddFriendRequest.fromJson(Map<String, dynamic> json) =>
+      _$AddFriendRequestFromJson(json);
+
+  @override
+  Map<String, dynamic> toJson() => _$AddFriendRequestToJson(this);
+}

+ 69 - 0
lib/data/api/request/add_friend_request.g.dart

@@ -0,0 +1,69 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'add_friend_request.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+AddFriendRequest _$AddFriendRequestFromJson(Map<String, dynamic> json) =>
+    AddFriendRequest(
+      json['phone'] as String,
+    )
+      ..appPlatform = (json['appPlatform'] as num).toInt()
+      ..os = json['os'] as String
+      ..osVersion = json['osVersion'] as String
+      ..packageName = json['packageName'] as String?
+      ..appVersionName = json['appVersionName'] as String?
+      ..appVersionCode = (json['appVersionCode'] as num?)?.toInt()
+      ..channelName = json['channelName'] as String?
+      ..appId = (json['appId'] as num?)?.toInt()
+      ..tgPlatform = (json['tgPlatform'] as num?)?.toInt()
+      ..oaid = json['oaid'] as String?
+      ..aaid = json['aaid'] as String?
+      ..androidId = json['androidId'] as String?
+      ..imei = json['imei'] as String?
+      ..simImei0 = json['simImei0'] as String?
+      ..simImei1 = json['simImei1'] as String?
+      ..mac = json['mac'] as String?
+      ..idfa = json['idfa'] as String?
+      ..idfv = json['idfv'] as String?
+      ..machineId = json['machineId'] as String?
+      ..brand = json['brand'] as String?
+      ..model = json['model'] as String?
+      ..wifiName = json['wifiName'] as String?
+      ..region = json['region'] as String?
+      ..locLng = (json['locLng'] as num?)?.toDouble()
+      ..locLat = (json['locLat'] as num?)?.toDouble()
+      ..authToken = json['authToken'] as String?;
+
+Map<String, dynamic> _$AddFriendRequestToJson(AddFriendRequest instance) =>
+    <String, dynamic>{
+      'appPlatform': instance.appPlatform,
+      'os': instance.os,
+      'osVersion': instance.osVersion,
+      'packageName': instance.packageName,
+      'appVersionName': instance.appVersionName,
+      'appVersionCode': instance.appVersionCode,
+      'channelName': instance.channelName,
+      'appId': instance.appId,
+      'tgPlatform': instance.tgPlatform,
+      'oaid': instance.oaid,
+      'aaid': instance.aaid,
+      'androidId': instance.androidId,
+      'imei': instance.imei,
+      'simImei0': instance.simImei0,
+      'simImei1': instance.simImei1,
+      'mac': instance.mac,
+      'idfa': instance.idfa,
+      'idfv': instance.idfv,
+      'machineId': instance.machineId,
+      'brand': instance.brand,
+      'model': instance.model,
+      'wifiName': instance.wifiName,
+      'region': instance.region,
+      'locLng': instance.locLng,
+      'locLat': instance.locLat,
+      'authToken': instance.authToken,
+      'phone': instance.phone,
+    };

+ 10 - 10
lib/data/consts/error_code.dart

@@ -2,20 +2,20 @@ import 'package:location/resource/string.gen.dart';
 
 class ErrorCode {
   /// 登录相关错误码
-  static const int verificationCodeError = 1005;
-  static const int noLoginError = 1006;
-  static const int noMember = 1007;
+  static const int verificationCodeError = 1005; //验证码错误
+  static const int noLoginError = 1006; //未登录
+  static const int noMember = 1007; //没有会员
 
   /// 好友关系相关错误码
-  static const int friendNotRegistered = 1100;
-  static const int friendRequestSent = 1101;
-  static const int alreadyInFriendList = 1102;
-  static const int cannotAddSelf = 1103;
+  static const int friendNotRegistered = 1100; //好友未注册本应用
+  static const int friendRequestSent = 1101; //好友申请已发出,请等待对方通过
+  static const int alreadyInFriendList = 1102; //该好友已在好友列表
+  static const int cannotAddSelf = 1103; //不能添加自己为好友
 
   /// 紧急联系人相关错误码
-  static const int maxContactsReached = 1200;
-  static const int contactAlreadyAdded = 1201;
-  static const int smsSendFailed = 1202;
+  static const int maxContactsReached = 1200; // 最多添加5人,请移除后再添加
+  static const int contactAlreadyAdded = 1201; //对方已是您的紧急联系人
+  static const int smsSendFailed = 1202; //短信发送失败,请核实手机号码
 
   /// 会员服务相关错误码
   static const int getMemberFree = 1300;

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

@@ -9,6 +9,7 @@ import 'package:location/utils/async_util.dart';
 import 'package:location/utils/http_handler.dart';
 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/friends_list_response.dart';
 import '../bean/user_info.dart';
@@ -113,6 +114,12 @@ class FriendsRepository {
         .then(HttpHandler.handle(true));
   }
 
+  Future<void> addFriendRequest(String phone) {
+    return atmobApi
+        .addFriendRequest(AddFriendRequest(phone))
+        .then(HttpHandler.handle(true));
+  }
+
   Future<FriendsListResponse> _friendList(int offset, int limit) {
     return atmobApi
         .friendList(FriendsListRequest(offset: offset, limit: limit))

+ 2 - 2
lib/di/get_it.config.dart

@@ -41,8 +41,6 @@ extension GetItInjectableX on _i174.GetIt {
       environmentFilter,
     );
     final networkModule = _$NetworkModule();
-    gh.factory<_i897.AddFriendDialogController>(
-        () => _i897.AddFriendDialogController());
     gh.factory<_i923.BrowserController>(() => _i923.BrowserController());
     gh.factory<_i973.SplashController>(() => _i973.SplashController());
     gh.singleton<_i361.Dio>(() => networkModule.createDefaultDio());
@@ -76,6 +74,8 @@ extension GetItInjectableX on _i174.GetIt {
           gh<_i220.AtmobLocationClient>(),
           gh<_i825.ConfigRepository>(),
         ));
+    gh.factory<_i897.AddFriendDialogController>(
+        () => _i897.AddFriendDialogController(gh<_i1053.FriendsRepository>()));
     gh.factory<_i492.FriendSettingController>(
         () => _i492.FriendSettingController(gh<_i1053.FriendsRepository>()));
     return this;

+ 7 - 7
lib/dialog/common_alert_dialog.dart

@@ -52,13 +52,13 @@ class _CommonAlertDialog extends Dialog {
 
   @override
   Widget build(BuildContext context) {
-    return IntrinsicHeight(
-      child: Container(
-        width: 300.w,
-        decoration: BoxDecoration(
-          color: Colors.white,
-          borderRadius: BorderRadius.circular(20.w),
-        ),
+    return Container(
+      width: 300.w,
+      decoration: BoxDecoration(
+        color: Colors.white,
+        borderRadius: BorderRadius.circular(20.w),
+      ),
+      child: IntrinsicHeight(
         child: Column(
           children: [
             SizedBox(height: 27.w),

+ 104 - 0
lib/dialog/common_confirm_dialog.dart

@@ -0,0 +1,104 @@
+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/utils/common_style.dart';
+
+class CommonConfirmDialog {
+  static const String _tag = "CommonConfirmDialog";
+
+  static void show(
+      {required Widget titleWidget,
+      required Widget descWidget,
+      required String confirmText,
+      VoidCallback? cancelOnTap,
+      required VoidCallback confirmOnTap,
+      String tag = _tag}) {
+    SmartDialog.show(
+        tag: _tag,
+        builder: (_) {
+          return _CommonConfirmDialog(
+              titleWidget: titleWidget,
+              descWidget: descWidget,
+              confirmText: confirmText,
+              cancelOnTap: cancelOnTap,
+              confirmOnTap: confirmOnTap);
+        });
+  }
+
+  static void dismiss({String tag = _tag}) {
+    SmartDialog.dismiss(tag: _tag);
+  }
+}
+
+class _CommonConfirmDialog extends Dialog {
+  final Widget titleWidget;
+  final Widget descWidget;
+  final String confirmText;
+  final VoidCallback? cancelOnTap;
+  final VoidCallback confirmOnTap;
+
+  const _CommonConfirmDialog({
+    required this.titleWidget,
+    required this.descWidget,
+    required this.confirmText,
+    this.cancelOnTap,
+    required this.confirmOnTap,
+  });
+
+  @override
+  Widget build(BuildContext context) {
+    return Container(
+      width: 300.w,
+      decoration: BoxDecoration(
+        color: Colors.white,
+        borderRadius: BorderRadius.circular(20.w),
+      ),
+      child: IntrinsicHeight(
+        child: Stack(
+          children: [
+            Center(
+              child: Column(
+                children: [
+                  SizedBox(height: 27.w),
+                  titleWidget,
+                  SizedBox(height: 15.w),
+                  Container(
+                    margin: EdgeInsets.symmetric(horizontal: 28.w),
+                    child: descWidget,
+                  ),
+                  SizedBox(height: 31.w),
+                  GestureDetector(
+                    onTap: () {
+                      CommonConfirmDialog.dismiss();
+                      confirmOnTap();
+                    },
+                    child: Container(
+                        width: 229.w,
+                        height: 43.w,
+                        decoration: getPrimaryBtnDecoration(46.w),
+                        child: Center(
+                            child: Text(confirmText,
+                                style: TextStyle(
+                                    fontSize: 14.sp, color: Colors.white)))),
+                  ),
+                  SizedBox(height: 20.w),
+                ],
+              ),
+            ),
+            Positioned(
+                top: 12.w,
+                right: 12.w,
+                child: GestureDetector(
+                    onTap: () {
+                      CommonConfirmDialog.dismiss();
+                      cancelOnTap?.call();
+                    },
+                    child: Assets.images.iconDialogClose
+                        .image(width: 20.w, height: 20.w)))
+          ],
+        ),
+      ),
+    );
+  }
+}

+ 57 - 0
lib/dialog/common_confirm_dialog_impl.dart

@@ -0,0 +1,57 @@
+import 'package:flutter/cupertino.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:location/dialog/common_confirm_dialog.dart';
+import 'package:location/resource/string.gen.dart';
+import 'package:location/utils/common_expand.dart';
+
+Widget getKindlyReminder() {
+  return Text(StringName.kindlyReminder,
+      style: TextStyle(
+          fontSize: 17.sp,
+          color: '#333333'.color,
+          fontWeight: FontWeight.bold));
+}
+
+void requestContactsPermissionDialog({required VoidCallback onConfirm}) {
+  CommonConfirmDialog.show(
+      titleWidget: getKindlyReminder(),
+      descWidget: Text(
+        StringName.requestContactsContent,
+        style: TextStyle(fontSize: 14.sp, color: '#404040'.color),
+      ),
+      confirmText: StringName.goRequestContactsPermission,
+      confirmOnTap: onConfirm);
+}
+
+void showAddFriendConfirmDialog({required VoidCallback onConfirm}) {
+  CommonConfirmDialog.show(
+      titleWidget: getKindlyReminder(),
+      descWidget: Text(
+        StringName.friendAddExplain,
+        style: TextStyle(fontSize: 14.sp, color: '#404040'.color),
+      ),
+      confirmText: StringName.dialogSure,
+      confirmOnTap: onConfirm);
+}
+
+void showLoginDialog({required VoidCallback onConfirm}) {
+  CommonConfirmDialog.show(
+      titleWidget: getKindlyReminder(),
+      descWidget: Text(
+        StringName.dialogNotLogin,
+        style: TextStyle(fontSize: 14.sp, color: '#404040'.color),
+      ),
+      confirmText: StringName.dialogGoLogin,
+      confirmOnTap: onConfirm);
+}
+
+void showNotRegisteredDialog({required VoidCallback onConfirm}) {
+  CommonConfirmDialog.show(
+      titleWidget: getKindlyReminder(),
+      descWidget: Text(
+        StringName.inviteContent,
+        style: TextStyle(fontSize: 14.sp, color: '#404040'.color),
+      ),
+      confirmText: StringName.inviteBtn,
+      confirmOnTap: onConfirm);
+}

+ 1 - 1
lib/dialog/add_friend_dialog.dart

@@ -77,7 +77,7 @@ class AddFriendDialogView extends Dialog {
                                   color: '#333333'.color,
                                   fontWeight: FontWeight.bold)),
                           SizedBox(height: 8.w),
-                          Text(StringName.dialogAddFriendTitle,
+                          Text(StringName.dialogAddFriendDesc,
                               style: TextStyle(
                                   fontSize: 15.sp, color: '#404040'.color)),
                           SizedBox(height: 23.w),

+ 110 - 1
lib/module/add_friend/add_friend_dialog_controller.dart

@@ -1,10 +1,119 @@
+import 'package:flutter/cupertino.dart';
 import 'package:get/get.dart';
 import 'package:injectable/injectable.dart';
+import 'package:location/data/consts/error_code.dart';
+import 'package:location/data/repositories/friends_repository.dart';
+import 'package:location/module/login/login_page.dart';
+import 'package:location/resource/string.gen.dart';
+import 'package:location/utils/common_expand.dart';
+import 'package:location/utils/http_handler.dart';
+import 'package:location/utils/permission_util.dart';
+import 'package:location/utils/toast_util.dart';
+import 'package:permission_handler/permission_handler.dart';
 import '../../../base/base_controller.dart';
+import '../../dialog/common_confirm_dialog_impl.dart';
+import 'package:flutter_contacts/flutter_contacts.dart';
+
+import '../../utils/de_bounce.dart';
 
 @injectable
 class AddFriendDialogController extends BaseController {
+  final FriendsRepository friendsRepository;
+
+  final TextEditingController etController = TextEditingController();
+
+  AddFriendDialogController(this.friendsRepository);
+
+  final RxString _phone = RxString('');
+
+  String get phone => _phone.value;
+
+  final Debounce _saveDebounce = Debounce(debounceTime: 500);
+
+  @override
+  void onReady() {
+    super.onReady();
+    etController.addListener(() {
+      _phone.value = etController.text;
+    });
+  }
+
+  void onAddFriendClick() {
+    showAddFriendConfirmDialog(onConfirm: () {
+      _saveDebounce.onClick(() {
+        _requestAddFriend();
+      });
+    });
+  }
+
+  void _requestAddFriend() {
+    friendsRepository.addFriendRequest(phone).then((_) {
+      ToastUtil.show(StringName.requestSuccess);
+      Get.back();
+    }).catchError((error) {
+      if (error is ServerErrorException) {
+        if (error.code == ErrorCode.noLoginError) {
+          _showNoLoginDialog();
+        } else if (error.code == ErrorCode.friendNotRegistered) {
+          _showNotRegisteredDialog();
+        } else if (error.code == ErrorCode.alreadyInFriendList) {
+          ToastUtil.show(StringName.addFriendAdded);
+        } else if (error.code == ErrorCode.friendRequestSent) {
+          ToastUtil.show(StringName.sendAddFriendSuccess);
+        } else if (error.code == ErrorCode.cannotAddSelf) {
+          ToastUtil.show(StringName.addFriendOwn);
+        } else {
+          ToastUtil.show(StringName.requestFail);
+        }
+      } else {
+        ToastUtil.show(StringName.requestFail);
+      }
+    });
+  }
+
+  void _showNotRegisteredDialog() {
+    showNotRegisteredDialog(onConfirm: () {
+      _shareToWechat();
+    });
+  }
+
+  void _shareToWechat() {}
+
+  void _showNoLoginDialog() {
+    showLoginDialog(onConfirm: () {
+      LoginPage.start();
+    });
+  }
+
+  @override
+  void onClose() {
+    super.onClose();
+    etController.dispose();
+  }
 
-  final title = ''.obs;
+  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();
+      }
+    }
+  }
 }

+ 37 - 11
lib/module/add_friend/add_friend_page.dart

@@ -4,6 +4,7 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
 import 'package:get/get.dart';
 import 'package:get/get_core/src/get_main.dart';
 import 'package:location/utils/common_expand.dart';
+import 'package:location/utils/common_style.dart';
 import '../../../base/base_view.dart';
 import '../../../resource/assets.gen.dart';
 import '../../../resource/colors.gen.dart';
@@ -46,9 +47,10 @@ class AddFriendPage extends BaseView<AddFriendDialogController> {
         ),
         child: Stack(
           children: [
-            // Positioned.fill(
-            //   child: Assets.images.bgAddFriendDialog.image(width: 1.sw),
-            // ),
+            Positioned(
+              top: 0,
+              child: Assets.images.bgAddFriendDialog.image(width: 1.sw),
+            ),
             _buildClose(),
             Column(
               crossAxisAlignment: CrossAxisAlignment.start,
@@ -60,11 +62,10 @@ class AddFriendPage extends BaseView<AddFriendDialogController> {
                 SizedBox(height: 16.h),
                 _buildShareWx(),
                 SizedBox(height: 20.h),
-                Center(child: Obx(() {
-                  return Text(controller.title.value,
-                      style:
-                          TextStyle(fontSize: 12.sp, color: '#A7A7A7'.color));
-                })),
+                Center(
+                    child: Text(StringName.friendAddRule,
+                        style: TextStyle(
+                            fontSize: 12.sp, color: '#A7A7A7'.color))),
                 SizedBox(height: 30.h),
               ],
             )
@@ -131,7 +132,28 @@ class AddFriendPage extends BaseView<AddFriendDialogController> {
                   fontSize: 14.sp,
                   color: ColorName.primaryTextColor,
                 ),
-              )
+              ),
+              Spacer(),
+              Obx(() {
+                return Visibility(
+                  maintainSize: true,
+                  maintainAnimation: true,
+                  maintainState: true,
+                  visible: controller.phone.length == 11,
+                  child: GestureDetector(
+                    onTap: controller.onAddFriendClick,
+                    child: Container(
+                        decoration: getPrimaryBtnDecoration(30.w),
+                        padding: EdgeInsets.symmetric(
+                            horizontal: 15.w, vertical: 8.w),
+                        child: Text(StringName.friendAddNow,
+                            style: TextStyle(
+                                fontSize: 14.sp,
+                                color: Colors.white,
+                                height: 1))),
+                  ),
+                );
+              })
             ],
           ),
           SizedBox(height: 18.h),
@@ -155,6 +177,7 @@ class AddFriendPage extends BaseView<AddFriendDialogController> {
             padding: EdgeInsets.symmetric(horizontal: 10.w),
             child: Center(
               child: TextField(
+                controller: controller.etController,
                 style: TextStyle(
                     fontSize: 14.sp, color: ColorName.primaryTextColor),
                 maxLines: 1,
@@ -182,8 +205,11 @@ class AddFriendPage extends BaseView<AddFriendDialogController> {
         SizedBox(width: 14.w),
         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)),
+        GestureDetector(
+          onTap: controller.onSelectContactClick,
+          child: Text(StringName.friendAddAddressBook,
+              style: TextStyle(fontSize: 14.sp, color: '#202020'.color)),
+        ),
       ],
     );
   }

+ 7 - 7
lib/module/main/main_controller.dart

@@ -18,11 +18,11 @@ import 'package:location/utils/base_expand.dart';
 import 'package:location/utils/toast_util.dart';
 import '../../data/repositories/config_repository.dart';
 import '../../socket/atmob_location_client.dart';
-import '../../dialog/add_friend_dialog.dart';
+import '../../dialog/friend_dialog.dart';
 import '../../dialog/check_loation_permission_dialog.dart';
 import '../../dialog/location_permission_dialog.dart';
 import '../../utils/location_convert_marker_util.dart';
-import '../../utils/location_permission_util.dart';
+import '../../utils/permission_util.dart';
 import '../add_friend/add_friend_page.dart';
 import '../mine/mine_page.dart';
 
@@ -105,7 +105,7 @@ class MainController extends BaseController {
   }
 
   void checkLocationPermissionCallback() async {
-    bool isGranted = await LocationPermissionUtil.checkLocationPermission();
+    bool isGranted = await PermissionUtil.checkLocationPermission();
     if (!isGranted) {
       CheckLocationPermissionDialog.show(
           onRequestPermissionClick: _requestLocationPermission);
@@ -166,7 +166,7 @@ class MainController extends BaseController {
 
   void onCurrentLocationClick() async {
     //权限检查
-    bool isGranted = await LocationPermissionUtil.checkLocationPermission();
+    bool isGranted = await PermissionUtil.checkLocationPermission();
     if (!isGranted) {
       LocationPermissionDialog.show(onNextStep: _requestLocationPermission);
     } else {
@@ -195,7 +195,7 @@ class MainController extends BaseController {
   }
 
   void _requestLocationPermission() async {
-    bool isGranted = await LocationPermissionUtil.requestLocationPermission();
+    bool isGranted = await PermissionUtil.requestLocationPermission();
     _showLocationAlways();
     if (isGranted) {
       _updateCurrentLocation();
@@ -203,10 +203,10 @@ class MainController extends BaseController {
   }
 
   void _showLocationAlways() async {
-    bool isGranted = await LocationPermissionUtil.checkShowLocationAlways();
+    bool isGranted = await PermissionUtil.checkShowLocationAlways();
     if (!isGranted) {
       LocationAlwaysPermissionDialog.show(onNextStep: () async {
-        isGranted = await LocationPermissionUtil.requestShowLocationAlways();
+        isGranted = await PermissionUtil.requestShowLocationAlways();
         if (isGranted) {
           _updateCurrentLocation();
         }

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

@@ -110,6 +110,20 @@ class StringName {
   static final String friendUpdateRemarkTitle = 'friend_update_remark_title'.tr; // 修改备注
   static final String friendUpdateRemarkHint = 'friend_update_remark_hint'.tr; // 请输入备注
   static final String remarkUpdateSuccess = 'remark_update_success'.tr; // 备注修改成功
+  static final String friendAddNow = 'friend_add_now'.tr; // 立即添加
+  static final String goRequestContactsPermission = 'go_request_contacts_permission'.tr; // 去申请
+  static final String requestContactsContent = 'request_contacts_content'.tr; // 申请“通讯录权限”为了更便利读取通信录联系人信息,无需再手动输入
+  static final String permissionRequestFail = 'permission_request_fail'.tr; // 权限申请失败
+  static final String friendAddExplain = 'friend_add_explain'.tr; // 1.在您的对方同意添加您为好友之后,才能查看对方的位置。\n 2.如果您的好友还没有下载该应用,建议您邀请他们下载安装。\n 3.通过同意添加该用户为您的好友,即表示您同意本软件合法地收集、存储和使用您的信息,并将位置等信息与该好友分享。
+  static final String dialogNotLogin = 'dialog_not_login'.tr; // 登录之后才可以发送好友申请
+  static final String dialogGoLogin = 'dialog_go_login'.tr; // 去登录
+  static final String inviteContent = 'invite_content'.tr; // 该好友未注册,将应用分享给好友需要好友下载安装并同意授权后可查看定位
+  static final String inviteBtn = 'invite_btn'.tr; // 去微信分享
+  static final String requestSuccess = 'request_success'.tr; // 发送成功
+  static final String requestFail = 'request_fail'.tr; // 请求失败
+  static final String addFriendAdded = 'add_friend_added'.tr; // 该好友已在好友列表
+  static final String sendAddFriendSuccess = 'send_add_friend_success'.tr; // 好友申请已发出,请等待对方通过
+  static final String addFriendOwn = 'add_friend_own'.tr; // 不能添加自己为好友
 }
 class StringMultiSource {
   StringMultiSource._();
@@ -223,6 +237,20 @@ class StringMultiSource {
       'friend_update_remark_title': '修改备注',
       'friend_update_remark_hint': '请输入备注',
       'remark_update_success': '备注修改成功',
+      'friend_add_now': '立即添加',
+      'go_request_contacts_permission': '去申请',
+      'request_contacts_content': '申请“通讯录权限”为了更便利读取通信录联系人信息,无需再手动输入',
+      'permission_request_fail': '权限申请失败',
+      'friend_add_explain': '1.在您的对方同意添加您为好友之后,才能查看对方的位置。\n 2.如果您的好友还没有下载该应用,建议您邀请他们下载安装。\n 3.通过同意添加该用户为您的好友,即表示您同意本软件合法地收集、存储和使用您的信息,并将位置等信息与该好友分享。',
+      'dialog_not_login': '登录之后才可以发送好友申请',
+      'dialog_go_login': '去登录',
+      'invite_content': '该好友未注册,将应用分享给好友需要好友下载安装并同意授权后可查看定位',
+      'invite_btn': '去微信分享',
+      'request_success': '发送成功',
+      'request_fail': '请求失败',
+      'add_friend_added': '该好友已在好友列表',
+      'send_add_friend_success': '好友申请已发出,请等待对方通过',
+      'add_friend_own': '不能添加自己为好友',
     },
   };
 }

+ 10 - 0
lib/utils/common_expand.dart

@@ -31,3 +31,13 @@ extension FutureMap<T> on Future<T> {
     return then(transform);
   }
 }
+
+extension TrimAllExtension on String {
+  String trimAll() {
+    return replaceAll(RegExp(r'\s+'), '');
+  }
+
+  String trimAndReduceSpaces() {
+    return trim().replaceAll(RegExp(r'\s+'), ' ');
+  }
+}

+ 20 - 2
lib/utils/location_permission_util.dart

@@ -1,8 +1,8 @@
 import 'package:location/sdk/map/map_helper.dart';
 import 'package:permission_handler/permission_handler.dart';
 
-class LocationPermissionUtil {
-  LocationPermissionUtil._();
+class PermissionUtil {
+  PermissionUtil._();
 
   static Future<bool> requestLocationPermission() async {
     final status = await Permission.locationWhenInUse.request();
@@ -42,4 +42,22 @@ class LocationPermissionUtil {
       return false;
     }
   }
+
+  static Future<bool> checkPermission(Permission permission) async {
+    final status = await permission.status;
+    if (status.isGranted) {
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  static Future<bool> requestPermission(Permission permission) async {
+    final status = await permission.request();
+    if (status.isGranted) {
+      return true;
+    } else {
+      return false;
+    }
+  }
 }

+ 8 - 0
pubspec.lock

@@ -315,6 +315,14 @@ packages:
     description: flutter
     source: sdk
     version: "0.0.0"
+  flutter_contacts:
+    dependency: "direct main"
+    description:
+      name: flutter_contacts
+      sha256: "388d32cd33f16640ee169570128c933b45f3259bddbfae7a100bb49e5ffea9ae"
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.1.9+2"
   flutter_gen_core:
     dependency: transitive
     description:

+ 4 - 1
pubspec.yaml

@@ -85,7 +85,10 @@ dependencies:
   web_socket_channel: 3.0.2
 
   #switch
-  animated_toggle_switch: ^0.8.4
+  animated_toggle_switch: 0.8.4
+
+  #通讯录选择
+  flutter_contacts: ^1.1.9+2
 
   ######################地图########################
   flutter_map: