浏览代码

[new]增加消息中心内容列表显示

zk 8 月之前
父节点
当前提交
26b12c6e1a

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

@@ -4,6 +4,13 @@
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.READ_CONTACTS" />
 
+    <queries>
+        <intent>
+            <action android:name="android.intent.action.DIAL" />
+            <data android:scheme="tel" />
+        </intent>
+    </queries>
+
     <application
         android:allowBackup="false"
         android:label="@string/app_name"

二进制
assets/images/icon_message_friend_help.webp


二进制
assets/images/icon_news.webp


二进制
assets/images/icon_news_item.webp


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

@@ -179,4 +179,15 @@
         该时段内未查询到历史轨迹记录\n\n1.可能是该用户未登录本软件\n2.可能是该用户未运行我们的软件\n3.可能是对方未开启定位和网络连接等原因
     </string>
 
+    <string name="news_title">消息中心</string>
+    <string name="news_request_title">新的好友</string>
+    <string name="news_request_desc">您有新的好友申请,请及时查看</string>
+    <string name="news_request_agree">已同意</string>
+    <string name="news_request_disagree">已拒绝</string>
+    <string name="news_to_contact">去联系</string>
+    <string name="message_try_for_help">您的好友需要紧急求助,请您尽快联系他!</string>
+    <string name="message_accepted">已同意您的好友申请</string>
+    <string name="message_rejected">已拒绝您的好友申请</string>
+    <string name="message_delete_your">您的好友删除了你</string>
+
 </resources>

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

@@ -6,13 +6,17 @@ 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';
 import 'package:location/data/api/request/login_request.dart';
+import 'package:location/data/api/request/message_request.dart';
 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/friends_list_response.dart';
 import 'package:location/data/api/response/login_response.dart';
 import 'package:location/data/api/response/member_status_response.dart';
+import 'package:location/data/api/response/message_response.dart';
 import 'package:location/data/api/response/query_track_response.dart';
+import 'package:location/data/api/response/request_friend_list_response.dart';
 import 'package:retrofit/error_logger.dart';
 import 'package:retrofit/http.dart';
 
@@ -77,4 +81,16 @@ abstract class AtmobApi {
   @POST("/s/v1/friend/virtual")
   Future<BaseResponse<UserInfo?>> getVirtualFromId(
       @Body() FriendsOperationRequest request);
+
+  @POST("/s/v1/friend/message/list")
+  Future<BaseResponse<MessageResponse>> getMessageList(
+      @Body() MessageRequest request);
+
+  @POST("/s/v1/friend/request/list")
+  Future<BaseResponse<RequestFriendListResponse>> getRequestFriendList(
+      @Body() RequestFriendListRequest request);
+
+  @POST("/s/v1/friend/request/list/waiting")
+  Future<BaseResponse<RequestFriendListResponse>> getFriendRequestCount(
+      @Body() RequestFriendListRequest request);
 }

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

@@ -559,6 +559,124 @@ class _AtmobApi implements AtmobApi {
     return _value;
   }
 
+  @override
+  Future<BaseResponse<MessageResponse>> getMessageList(
+      MessageRequest 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<MessageResponse>>(Options(
+      method: 'POST',
+      headers: _headers,
+      extra: _extra,
+    )
+        .compose(
+          _dio.options,
+          '/s/v1/friend/message/list',
+          queryParameters: queryParameters,
+          data: _data,
+        )
+        .copyWith(
+            baseUrl: _combineBaseUrls(
+          _dio.options.baseUrl,
+          baseUrl,
+        )));
+    final _result = await _dio.fetch<Map<String, dynamic>>(_options);
+    late BaseResponse<MessageResponse> _value;
+    try {
+      _value = BaseResponse<MessageResponse>.fromJson(
+        _result.data!,
+        (json) => MessageResponse.fromJson(json as Map<String, dynamic>),
+      );
+    } on Object catch (e, s) {
+      errorLogger?.logError(e, s, _options);
+      rethrow;
+    }
+    return _value;
+  }
+
+  @override
+  Future<BaseResponse<RequestFriendListResponse>> getRequestFriendList(
+      RequestFriendListRequest 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<RequestFriendListResponse>>(Options(
+      method: 'POST',
+      headers: _headers,
+      extra: _extra,
+    )
+            .compose(
+              _dio.options,
+              '/s/v1/friend/request/list',
+              queryParameters: queryParameters,
+              data: _data,
+            )
+            .copyWith(
+                baseUrl: _combineBaseUrls(
+              _dio.options.baseUrl,
+              baseUrl,
+            )));
+    final _result = await _dio.fetch<Map<String, dynamic>>(_options);
+    late BaseResponse<RequestFriendListResponse> _value;
+    try {
+      _value = BaseResponse<RequestFriendListResponse>.fromJson(
+        _result.data!,
+        (json) =>
+            RequestFriendListResponse.fromJson(json as Map<String, dynamic>),
+      );
+    } on Object catch (e, s) {
+      errorLogger?.logError(e, s, _options);
+      rethrow;
+    }
+    return _value;
+  }
+
+  @override
+  Future<BaseResponse<RequestFriendListResponse>> getFriendRequestCount(
+      RequestFriendListRequest 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<RequestFriendListResponse>>(Options(
+      method: 'POST',
+      headers: _headers,
+      extra: _extra,
+    )
+            .compose(
+              _dio.options,
+              '/s/v1/friend/request/list/waiting',
+              queryParameters: queryParameters,
+              data: _data,
+            )
+            .copyWith(
+                baseUrl: _combineBaseUrls(
+              _dio.options.baseUrl,
+              baseUrl,
+            )));
+    final _result = await _dio.fetch<Map<String, dynamic>>(_options);
+    late BaseResponse<RequestFriendListResponse> _value;
+    try {
+      _value = BaseResponse<RequestFriendListResponse>.fromJson(
+        _result.data!,
+        (json) =>
+            RequestFriendListResponse.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 ||

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

@@ -0,0 +1,21 @@
+import 'package:json_annotation/json_annotation.dart';
+import 'package:location/base/app_base_request.dart';
+
+part 'message_request.g.dart';
+
+@JsonSerializable()
+class MessageRequest extends AppBaseRequest {
+  @JsonKey(name: 'offset')
+  int? offset;
+
+  @JsonKey(name: 'limit')
+  int? limit;
+
+  MessageRequest({this.offset, this.limit});
+
+  factory MessageRequest.fromJson(Map<String, dynamic> json) =>
+      _$MessageRequestFromJson(json);
+
+  @override
+  Map<String, dynamic> toJson() => _$MessageRequestToJson(this);
+}

+ 27 - 0
lib/data/api/request/request_friendlist_request.dart

@@ -0,0 +1,27 @@
+import 'package:json_annotation/json_annotation.dart';
+import 'package:location/base/app_base_request.dart';
+
+part 'request_friendlist_request.g.dart';
+
+@JsonSerializable()
+class RequestFriendListRequest extends AppBaseRequest {
+  @JsonKey(name: 'offset')
+  int? offset;
+
+  @JsonKey(name: 'limit')
+  int? limit;
+
+  @JsonKey(name: 'after')
+  int? after;
+
+  @JsonKey(name: 'before')
+  int? before;
+
+  RequestFriendListRequest({this.offset, this.limit, this.after, this.before});
+
+  factory RequestFriendListRequest.fromJson(Map<String, dynamic> json) =>
+      _$RequestFriendListRequestFromJson(json);
+
+  @override
+  Map<String, dynamic> toJson() => _$RequestFriendListRequestToJson(this);
+}

+ 21 - 0
lib/data/api/response/message_response.dart

@@ -0,0 +1,21 @@
+import 'package:json_annotation/json_annotation.dart';
+
+import '../../bean/message_info.dart';
+
+part 'message_response.g.dart';
+
+@JsonSerializable()
+class MessageResponse {
+  @JsonKey(name: 'count')
+  int count;
+
+  @JsonKey(name: 'list')
+  List<MessageInfo>? list;
+
+  MessageResponse(this.count, this.list);
+
+  factory MessageResponse.fromJson(Map<String, dynamic> json) =>
+      _$MessageResponseFromJson(json);
+
+  Map<String, dynamic> toJson() => _$MessageResponseToJson(this);
+}

+ 21 - 0
lib/data/api/response/request_friend_list_response.dart

@@ -0,0 +1,21 @@
+import 'package:json_annotation/json_annotation.dart';
+
+import '../../bean/request_friend_info.dart';
+
+part 'request_friend_list_response.g.dart';
+
+@JsonSerializable()
+class RequestFriendListResponse {
+  @JsonKey(name: 'count')
+  int count;
+
+  @JsonKey(name: 'list')
+  List<RequestFriendInfo>? list;
+
+  RequestFriendListResponse(this.count, this.list);
+
+  factory RequestFriendListResponse.fromJson(Map<String, dynamic> json) =>
+      _$RequestFriendListResponseFromJson(json);
+
+  Map<String, dynamic> toJson() => _$RequestFriendListResponseToJson(this);
+}

+ 37 - 0
lib/data/bean/message_info.dart

@@ -0,0 +1,37 @@
+import 'package:json_annotation/json_annotation.dart';
+
+part 'message_info.g.dart';
+
+@JsonSerializable()
+class MessageInfo {
+  @JsonKey(name: 'id')
+  int id;
+
+  @JsonKey(name: 'type')
+  int type;
+
+  @JsonKey(name: 'senderId')
+  String senderId;
+
+  @JsonKey(name: 'senderPhone')
+  String senderPhone;
+
+  @JsonKey(name: 'content')
+  String? content;
+
+  @JsonKey(name: 'createTime')
+  int createTime;
+
+  MessageInfo(
+      {required this.id,
+      required this.type,
+      required this.senderId,
+      required this.senderPhone,
+      this.content,
+      required this.createTime});
+
+  factory MessageInfo.fromJson(Map<String, dynamic> json) =>
+      _$MessageInfoFromJson(json);
+
+  Map<String, dynamic> toJson() => _$MessageInfoToJson(this);
+}

+ 41 - 0
lib/data/bean/request_friend_info.dart

@@ -0,0 +1,41 @@
+import 'package:json_annotation/json_annotation.dart';
+
+part 'request_friend_info.g.dart';
+
+@JsonSerializable()
+class RequestFriendInfo {
+  @JsonKey(name: 'id')
+  int id;
+
+  @JsonKey(name: 'userId')
+  String userId;
+
+  @JsonKey(name: 'userPhone')
+  String userPhone;
+
+  @JsonKey(name: 'friendId')
+  String friendId;
+
+  @JsonKey(name: 'friendPhone')
+  String friendPhone;
+
+  @JsonKey(name: 'createTime')
+  int createTime;
+
+  @JsonKey(name: 'status')
+  int status;
+
+  RequestFriendInfo(
+      {required this.id,
+      required this.userId,
+      required this.userPhone,
+      required this.friendId,
+      required this.friendPhone,
+      required this.createTime,
+      required this.status});
+
+  factory RequestFriendInfo.fromJson(Map<String, dynamic> json) =>
+      _$RequestFriendInfoFromJson(json);
+
+  Map<String, dynamic> toJson() => _$RequestFriendInfoToJson(this);
+}

+ 6 - 1
lib/data/repositories/account_repository.dart

@@ -11,6 +11,7 @@ import 'package:location/data/bean/member_status_info.dart';
 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/socket/atmob_location_client.dart';
 import 'package:location/data/repositories/friends_repository.dart';
 import 'package:location/di/get_it.dart';
@@ -43,6 +44,7 @@ class AccountRepository {
   static String? token = KVUtil.getString(keyAccountLoginToken, null);
 
   late final FriendsRepository friendsRepository;
+  late final MessageRepository messageRepository;
 
   final Rx<UserInfo> mineUserInfo = Rx<UserInfo>(UserInfo(
       id: Constants.mineLocationId,
@@ -51,6 +53,8 @@ class AccountRepository {
 
   AccountRepository(this.atmobApi) {
     AtmobLog.d(tag, '$tag....init');
+    friendsRepository = FriendsRepository.getInstance();
+    messageRepository = MessageRepository.getInstance();
 
     isLogin.bindStream(
       loginPhoneNum.map((value) {
@@ -59,7 +63,6 @@ class AccountRepository {
     );
     loginPhoneNum.value = KVUtil.getString(keyAccountLoginPhoneNum, null);
 
-    friendsRepository = FriendsRepository.getInstance();
 
     refreshMemberStatus();
     MapHelper.addLocationListener((location) {
@@ -116,6 +119,7 @@ class AccountRepository {
 
     refreshMemberStatus();
     friendsRepository.refreshFriends();
+    messageRepository.refreshFriendRequest();
   }
 
   void logout() {
@@ -132,6 +136,7 @@ class AccountRepository {
     memberStatusInfo.value = null;
 
     friendsRepository.clearFriends();
+    messageRepository.clearMessage();
   }
 
   void refreshMemberStatus() {

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

@@ -71,8 +71,6 @@ class FriendsRepository {
     return getIt.get<FriendsRepository>();
   }
 
-  void refreshFriendRequestList() {}
-
   void refreshVirtualFriend() {
     refreshVirtualFuture?.cancel();
     refreshVirtualFuture = AsyncUtil.retry(

+ 49 - 2
lib/data/repositories/message_repository.dart

@@ -1,16 +1,63 @@
+import 'package:get/get.dart';
 import 'package:injectable/injectable.dart';
 import 'package:location/data/api/atmob_api.dart';
 import 'package:location/di/get_it.dart';
+import 'package:location/utils/http_handler.dart';
+
+import '../api/request/message_request.dart';
+import '../api/request/request_friendlist_request.dart';
+import '../api/response/message_response.dart';
+import '../api/response/request_friend_list_response.dart';
+import '../bean/request_friend_info.dart';
 
 @lazySingleton
 class MessageRepository {
   final AtmobApi atmobApi;
 
-  MessageRepository(this.atmobApi);
+  bool isRefreshing = false;
+  Rxn<RequestFriendInfo> latestFriendRequest = Rxn<RequestFriendInfo>();
+  RxInt waitingCount = RxInt(0);
 
-  void requestMessageList() {}
+  MessageRepository(this.atmobApi) {
+    refreshFriendRequest();
+  }
 
   static MessageRepository getInstance() {
     return getIt.get<MessageRepository>();
   }
+
+  void refreshFriendRequest() {
+    if (isRefreshing) {
+      return;
+    }
+    isRefreshing = true;
+    _getFriendRequestCount().whenComplete(() {
+      isRefreshing = false;
+    });
+  }
+
+  Future<RequestFriendListResponse> _getFriendRequestCount() {
+    return atmobApi
+        .getFriendRequestCount(RequestFriendListRequest())
+        .then(HttpHandler.handle(true))
+        .then((data) {
+      if (data.list == null || data.list!.isEmpty) {
+        latestFriendRequest.value = null;
+      } else {
+        latestFriendRequest.value = data.list!.first;
+      }
+      waitingCount.value = data.count;
+      return data;
+    });
+  }
+
+  void clearMessage() {
+    latestFriendRequest.value = null;
+  }
+
+  Future<MessageResponse> getMessageList() {
+    return atmobApi
+        .getMessageList(MessageRequest())
+        .then(HttpHandler.handle(true));
+  }
 }

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

@@ -27,6 +27,7 @@ import '../module/login/login_controller.dart' as _i1008;
 import '../module/main/main_controller.dart' as _i731;
 import '../module/member/member_controller.dart' as _i269;
 import '../module/mine/mine_controller.dart' as _i732;
+import '../module/news/news_controller.dart' as _i489;
 import '../module/splash/splash_controller.dart' as _i973;
 import '../module/track/track_controller.dart' as _i518;
 import '../socket/atmob_location_client.dart' as _i220;
@@ -66,6 +67,8 @@ extension GetItInjectableX on _i174.GetIt {
         () => _i1008.LoginController(gh<_i20.AccountRepository>()));
     gh.factory<_i732.MineController>(
         () => _i732.MineController(gh<_i20.AccountRepository>()));
+    gh.factory<_i489.NewsController>(
+        () => _i489.NewsController(gh<_i791.MessageRepository>()));
     gh.lazySingleton<_i825.ConfigRepository>(() => _i825.ConfigRepository(
           gh<_i243.AtmobApi>(),
           gh<_i1053.FriendsRepository>(),
@@ -79,16 +82,17 @@ extension GetItInjectableX on _i174.GetIt {
           gh<_i1053.FriendsRepository>(),
           gh<_i20.AccountRepository>(),
         ));
+    gh.factory<_i897.AddFriendDialogController>(
+        () => _i897.AddFriendDialogController(gh<_i1053.FriendsRepository>()));
+    gh.factory<_i492.FriendSettingController>(
+        () => _i492.FriendSettingController(gh<_i1053.FriendsRepository>()));
     gh.factory<_i731.MainController>(() => _i731.MainController(
           gh<_i1053.FriendsRepository>(),
           gh<_i20.AccountRepository>(),
+          gh<_i791.MessageRepository>(),
           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 - 2
lib/module/friend/friend_list_item.dart

@@ -16,8 +16,13 @@ Widget buildFriendItem(UserInfo userInfo,
   return Container(
     margin: EdgeInsets.only(left: 12.w, right: 12.w, bottom: 10.w),
     width: double.infinity,
-    decoration: BoxDecoration(
-        color: ColorName.white, borderRadius: BorderRadius.circular(14.w)),
+    decoration: BoxDecoration(boxShadow: [
+      BoxShadow(
+        color: ColorName.black.withOpacity(0.04),
+        offset: Offset(2, 2),
+        blurRadius: 8,
+      )
+    ], color: ColorName.white, borderRadius: BorderRadius.circular(14.w)),
     child: Stack(
       children: [
         Positioned(

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

@@ -10,14 +10,17 @@ import 'package:location/data/consts/constants.dart';
 import 'package:location/data/repositories/account_repository.dart';
 import 'package:location/data/repositories/friends_repository.dart';
 import 'package:flutter_map/flutter_map.dart';
+import 'package:location/data/repositories/message_repository.dart';
 import 'package:location/handler/error_handler.dart';
 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/resource/string.gen.dart';
 import 'package:location/sdk/map/map_helper.dart';
 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 '../../dialog/common_alert_dialog_impl.dart';
 import '../../dialog/common_confirm_dialog_impl.dart';
@@ -34,21 +37,12 @@ import '../track/track_page.dart';
 
 @injectable
 class MainController extends BaseController {
-  final FriendsRepository friendsRepository;
-  final AccountRepository accountRepository;
-
   RxList<UserInfo> get _friendsList => friendsRepository.friendsList;
 
   RxList<UserInfo> integrateList = RxList();
 
   UserInfo get mineUserInfo => accountRepository.mineUserInfo.value;
 
-  MainController(
-      this.friendsRepository,
-      this.accountRepository,
-      AtmobLocationClient atmobLocationClient,
-      ConfigRepository configRepository);
-
   final Rxn<UserInfo> _selectedFriend = Rxn<UserInfo>();
 
   UserInfo? get selectedFriend => _selectedFriend.value;
@@ -62,6 +56,19 @@ class MainController extends BaseController {
   bool isFirstShowMineLocation = true;
   DateTime _lastRefreshTime = DateTime.fromMillisecondsSinceEpoch(0);
 
+  final FriendsRepository friendsRepository;
+  final AccountRepository accountRepository;
+  final MessageRepository messageRepository;
+
+  int get waitingNewsCount => messageRepository.waitingCount.value;
+
+  MainController(
+      this.friendsRepository,
+      this.accountRepository,
+      this.messageRepository,
+      AtmobLocationClient atmobLocationClient,
+      ConfigRepository configRepository);
+
   @override
   void onReady() {
     super.onReady();
@@ -259,4 +266,8 @@ class MainController extends BaseController {
     }
     TrackPage.start(userInfo);
   }
+
+  void onNewsClick() {
+    NewsPage.start();
+  }
 }

+ 5 - 4
lib/module/main/main_page.dart

@@ -164,10 +164,11 @@ class MainPage extends BasePage<MainController> {
                 Assets.images.iconMainFriendGuard.provider(),
                 StringName.mainFriendListTab,
                 () => controller.onFriendClick())),
-        Expanded(
-            child: buildFunItem(Assets.images.iconMainNews.provider(),
-                StringName.mainNewsTab, () {},
-                isShowDot: true)),
+        Expanded(child: Obx(() {
+          return buildFunItem(Assets.images.iconMainNews.provider(),
+              StringName.mainNewsTab, () => controller.onNewsClick(),
+              isShowDot: controller.waitingNewsCount > 0);
+        })),
         Expanded(
             child: buildFunItem(Assets.images.iconMainHelp.provider(),
                 StringName.mainHelpTab, () {})),

+ 94 - 0
lib/module/news/news_controller.dart

@@ -0,0 +1,94 @@
+import 'package:flutter/cupertino.dart';
+import 'package:get/get.dart';
+import 'package:get/get_core/src/get_main.dart';
+import 'package:get/get_rx/src/rx_types/rx_types.dart';
+import 'package:injectable/injectable.dart';
+import 'package:location/base/base_controller.dart';
+import 'package:location/data/repositories/message_repository.dart';
+import 'package:location/handler/error_handler.dart';
+import 'package:url_launcher/url_launcher.dart';
+import '../../data/bean/message_info.dart';
+import '../../data/bean/request_friend_info.dart';
+
+@injectable
+class NewsController extends BaseController {
+  final RxDouble _opacity = 0.0.obs;
+
+  double get opacity => _opacity.value;
+
+  final ScrollController scrollController = ScrollController();
+
+  final double _scrollThreshold = 80;
+
+  final MessageRepository messageRepository;
+
+  RequestFriendInfo? get latestFriendRequest =>
+      messageRepository.latestFriendRequest.value;
+
+  int get waitingNewsCount => messageRepository.waitingCount.value;
+
+  RxList<MessageInfo> messageList = RxList();
+
+  NewsController(this.messageRepository);
+
+  @override
+  void onReady() {
+    super.onReady();
+    scrollController.addListener(_handleScroll);
+    requestMessageList();
+  }
+
+  void _handleScroll() {
+    final double offset = scrollController.offset;
+    if (offset <= _scrollThreshold) {
+      _opacity.value = 0.0;
+    } else {
+      double opacity = ((offset - _scrollThreshold) / 200).clamp(0.0, 1.0);
+      _opacity.value = opacity;
+    }
+  }
+
+  void back() {
+    Get.back();
+  }
+
+  void newsClick() {}
+
+  void requestMessageList() {
+    messageRepository.getMessageList().then((response) {
+      messageList.clear();
+      if (response.list != null) {
+        messageList.addAll(response.list!);
+      }
+    }).catchError((error) {
+      ErrorHandler.toastError(error);
+    });
+  }
+
+  @override
+  void onClose() {
+    super.onClose();
+    scrollController.dispose();
+  }
+
+  ///2:你的好友请求已经被接受
+  // 3:你的好友请求已经被拒绝
+  // 4:好友发来的求救
+  // 5:你的好友删除了你
+  void onMessageFunClick(MessageInfo info) {
+    if (info.type == 4) {
+      debugPrint('好友发来的求救');
+      //跳转到打电话界面
+      _launchDialer(info.senderPhone);
+    }
+  }
+
+  void _launchDialer(String phoneNumber) async {
+    final Uri uri = Uri.parse('tel:$phoneNumber');
+    if (await canLaunchUrl(uri)) {
+      await launchUrl(uri);
+    } else {
+      throw '无法跳转拨号界面';
+    }
+  }
+}

+ 173 - 0
lib/module/news/news_list_item.dart

@@ -0,0 +1,173 @@
+import 'package:flutter/cupertino.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:location/data/bean/message_info.dart';
+import 'package:location/utils/common_expand.dart';
+
+import '../../resource/assets.gen.dart';
+import '../../resource/colors.gen.dart';
+import '../../resource/string.gen.dart';
+import '../../utils/date_util.dart';
+
+typedef MessageFunCallback = void Function(MessageInfo info);
+
+Widget buildMessageInfoItem(MessageInfo item, MessageFunCallback callback) {
+  return buildMessageItem(Assets.images.iconDefaultFriendAvatar.provider(),
+      title: item.senderPhone ?? '',
+      content: getMessageContentTxt(item),
+      contentTextStyle: getMessageContentTextStyle(item.type),
+      createTime: item.createTime,
+      bgGradient: getMessageBgGradient(item.type),
+      statusWidget: getMessageStatusWidget(item.type, funClick: () {
+        callback(item);
+      }));
+}
+
+// 2:你的好友请求已经被接受
+// 3:你的好友请求已经被拒绝
+// 4:好友发来的求救
+// 5:你的好友删除了你
+Widget getMessageStatusWidget(int type, {VoidCallback? funClick}) {
+  if (type == 2) {
+    return Container(
+      decoration: BoxDecoration(
+          color: '#1F15CB4C'.color, borderRadius: BorderRadius.circular(4.w)),
+      padding: EdgeInsets.symmetric(horizontal: 5.w, vertical: 3.w),
+      child: Text(StringName.newsRequestAgree,
+          style: TextStyle(fontSize: 12.sp, color: '#00BB70'.color, height: 1)),
+    );
+  } else if (type == 3) {
+    return Container(
+      decoration: BoxDecoration(
+          color: '#1FCB1515'.color, borderRadius: BorderRadius.circular(4.w)),
+      padding: EdgeInsets.symmetric(horizontal: 5.w, vertical: 3.w),
+      child: Text(StringName.newsRequestDisagree,
+          style: TextStyle(fontSize: 12.sp, color: '#FF2125'.color, height: 1)),
+    );
+  } else if (type == 4) {
+    return GestureDetector(
+      onTap: funClick,
+      child: Container(
+        width: 79.w,
+        height: 28.w,
+        decoration: BoxDecoration(
+            color: '#FF5555'.color, borderRadius: BorderRadius.circular(100.w)),
+        child: Center(
+          child: Row(
+            mainAxisAlignment: MainAxisAlignment.center,
+            children: [
+              Assets.images.iconMessageFriendHelp
+                  .image(width: 12.w, height: 12.w),
+              SizedBox(width: 2.w),
+              Text(StringName.newsToContact,
+                  style: TextStyle(fontSize: 13.sp, color: ColorName.white))
+            ],
+          ),
+        ),
+      ),
+    );
+  }
+  return SizedBox.shrink();
+}
+
+// 2:你的好友请求已经被接受
+// 3:你的好友请求已经被拒绝
+// 4:好友发来的求救
+// 5:你的好友删除了你
+String getMessageContentTxt(MessageInfo info) {
+  int type = info.type;
+  if (type == 2) {
+    return StringName.messageAccepted;
+  } else if (type == 3) {
+    return StringName.messageRejected;
+  } else if (type == 4) {
+    return StringName.messageTryForHelp;
+  } else if (type == 5) {
+    return StringName.messageDeleteYour;
+  }
+  return '';
+}
+
+// 2:你的好友请求已经被接受
+// 3:你的好友请求已经被拒绝
+// 4:好友发来的求救
+// 5:你的好友删除了你
+TextStyle getMessageContentTextStyle(int type) {
+  if (type == 4) {
+    return TextStyle(fontSize: 13.sp, color: '#A7A7A7'.color);
+  }
+  return TextStyle(fontSize: 13.sp, color: ColorName.black50);
+}
+
+Gradient? getMessageBgGradient(int type) {
+  if (type == 4) {
+    return LinearGradient(colors: ['#00FF5555'.color, '#38FF5555'.color]);
+  }
+  return null;
+}
+
+Widget buildMessageItem(ImageProvider avatar,
+    {required String title,
+    required String content,
+    required TextStyle contentTextStyle,
+    required int createTime,
+    required Widget statusWidget,
+    Gradient? bgGradient}) {
+  return Container(
+    decoration: BoxDecoration(
+        color: ColorName.white,
+        borderRadius: BorderRadius.circular(14.w),
+        boxShadow: [
+          BoxShadow(
+            color: ColorName.black.withOpacity(0.08),
+            offset: Offset(2, 2),
+            blurRadius: 10,
+          )
+        ]),
+    margin: EdgeInsets.only(left: 12.w, right: 12.w, bottom: 10.w),
+    child: Container(
+      decoration: BoxDecoration(
+        borderRadius: BorderRadius.circular(14.w),
+        gradient: bgGradient,
+      ),
+      padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 10.w),
+      child: Row(
+        children: [
+          Image(image: avatar, width: 46.w, height: 46.w),
+          SizedBox(width: 10.w),
+          Expanded(
+              child: Column(
+            children: [
+              Row(
+                children: [
+                  Text(
+                    title,
+                    style: TextStyle(
+                        fontSize: 16.sp,
+                        color: '#202020'.color,
+                        fontWeight: FontWeight.bold),
+                  ),
+                  Spacer(),
+                  Text(
+                    createTime == 0
+                        ? ''
+                        : DateUtil.fromMillisecondsSinceEpoch(
+                            "yyyy-MM-dd HH:mm", createTime),
+                    style: TextStyle(fontSize: 12.sp, color: '#A7A7A7'.color),
+                  )
+                ],
+              ),
+              SizedBox(height: 7.w),
+              Row(
+                children: [
+                  Expanded(child: Text(content, style: contentTextStyle)),
+                  SizedBox(width: 16.w),
+                  statusWidget
+                ],
+              )
+            ],
+          ))
+        ],
+      ),
+    ),
+  );
+}

+ 151 - 0
lib/module/news/news_page.dart

@@ -0,0 +1,151 @@
+import 'dart:ui';
+
+import 'package:flutter/src/widgets/framework.dart';
+import 'package:flutter/widgets.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/message_info.dart';
+import 'package:location/utils/common_expand.dart';
+import '../../resource/assets.gen.dart';
+import '../../resource/colors.gen.dart';
+import '../../resource/string.gen.dart';
+import '../../router/app_pages.dart';
+import '../../widget/common_view.dart';
+import 'news_controller.dart';
+import 'news_list_item.dart';
+
+class NewsPage extends BasePage<NewsController> {
+  const NewsPage({super.key});
+
+  static void start() {
+    Get.toNamed(RoutePath.news);
+  }
+
+  @override
+  bool immersive() {
+    return true;
+  }
+
+  @override
+  Widget buildBody(BuildContext context) {
+    return Stack(
+      children: [
+        Assets.images.bgPageBackground.image(width: double.infinity),
+        Positioned(
+            top: 24.w,
+            right: 19.w,
+            child: SafeArea(child: Obx(() {
+              return Opacity(
+                  opacity: 1 - controller.opacity,
+                  child: Assets.images.iconNews.image(width: 118.w));
+            }))),
+        SafeArea(
+          child: Column(
+            children: [
+              buildHeadView(),
+              Expanded(
+                  child: CustomScrollView(
+                controller: controller.scrollController,
+                slivers: [
+                  buildSliverTitle(),
+                  buildSliverWaiting(),
+                  Obx(() {
+                    return SliverList.builder(
+                        itemBuilder: buildNewsItem,
+                        itemCount: controller.messageList.length);
+                  })
+                ],
+              ))
+            ],
+          ),
+        )
+      ],
+    );
+  }
+
+  SliverToBoxAdapter buildSliverTitle() {
+    return SliverToBoxAdapter(
+        child: Column(
+      crossAxisAlignment: CrossAxisAlignment.start,
+      children: [
+        SizedBox(height: 18.w),
+        Padding(
+          padding: EdgeInsets.only(left: 12.w),
+          child: Text(StringName.newsTitle,
+              style: TextStyle(
+                  fontSize: 16.sp,
+                  color: ColorName.black90,
+                  fontWeight: FontWeight.bold)),
+        ),
+        SizedBox(height: 10.w),
+      ],
+    ));
+  }
+
+  Widget buildHeadView() {
+    return Container(
+      margin: EdgeInsets.symmetric(horizontal: 12.w, vertical: 14.w),
+      child: Row(
+        crossAxisAlignment: CrossAxisAlignment.center,
+        mainAxisAlignment: MainAxisAlignment.spaceBetween,
+        children: [
+          GestureDetector(
+              onTap: controller.back, child: CommonView.getBackBtnView()),
+          Obx(() {
+            return Opacity(
+              opacity: controller.opacity,
+              child: Text(StringName.newsTitle,
+                  style: TextStyle(
+                      fontSize: 16.sp,
+                      color: ColorName.black90,
+                      fontWeight: FontWeight.bold)),
+            );
+          }),
+          GestureDetector(
+              onTap: controller.newsClick,
+              child:
+                  Assets.images.iconFriendNews.image(width: 24.w, height: 24.w))
+        ],
+      ),
+    );
+  }
+
+  SliverToBoxAdapter buildSliverWaiting() {
+    return SliverToBoxAdapter(child: Obx(() {
+      if (controller.waitingNewsCount == 0) {
+        return SizedBox.shrink();
+      }
+      return buildMessageItem(Assets.images.iconNewsItem.provider(),
+          title: StringName.newsRequestTitle,
+          content: StringName.newsRequestDesc,
+          contentTextStyle:
+              TextStyle(fontSize: 12.sp, color: ColorName.black60),
+          createTime: controller.latestFriendRequest?.createTime ?? 0,
+          statusWidget: Container(
+            height: 16.w,
+            constraints: BoxConstraints(
+              minWidth: 16.w,
+            ),
+            padding: EdgeInsets.symmetric(horizontal: 2.w),
+            decoration: BoxDecoration(
+              color: '#FF333D'.color,
+              borderRadius: BorderRadius.circular(100),
+            ),
+            child: Center(
+              child: Text('${controller.waitingNewsCount}',
+                  textAlign: TextAlign.center,
+                  style: TextStyle(
+                      fontSize: 10.sp, color: ColorName.white, height: 1)),
+            ),
+          ));
+    }));
+  }
+
+  Widget buildNewsItem(BuildContext context, int index) {
+    final item = controller.messageList[index];
+    return buildMessageInfoItem(
+        item, (info) => controller.onMessageFunClick(info));
+  }
+}

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

@@ -160,6 +160,10 @@ class $AssetsImagesGen {
   AssetGenImage get iconMemberVipReceiveArrow =>
       const AssetGenImage('assets/images/icon_member_vip_receive_arrow.webp');
 
+  /// File path: assets/images/icon_message_friend_help.webp
+  AssetGenImage get iconMessageFriendHelp =>
+      const AssetGenImage('assets/images/icon_message_friend_help.webp');
+
   /// File path: assets/images/icon_mine_fun_about.webp
   AssetGenImage get iconMineFunAbout =>
       const AssetGenImage('assets/images/icon_mine_fun_about.webp');
@@ -208,6 +212,14 @@ class $AssetsImagesGen {
   AssetGenImage get iconMineUnlockVip =>
       const AssetGenImage('assets/images/icon_mine_unlock_vip.webp');
 
+  /// File path: assets/images/icon_news.webp
+  AssetGenImage get iconNews =>
+      const AssetGenImage('assets/images/icon_news.webp');
+
+  /// File path: assets/images/icon_news_item.webp
+  AssetGenImage get iconNewsItem =>
+      const AssetGenImage('assets/images/icon_news_item.webp');
+
   /// File path: assets/images/icon_splash_title.webp
   AssetGenImage get iconSplashTitle =>
       const AssetGenImage('assets/images/icon_splash_title.webp');
@@ -279,6 +291,7 @@ class $AssetsImagesGen {
         iconMainRefreshFriendLocation,
         iconMainRefreshMineLocation,
         iconMemberVipReceiveArrow,
+        iconMessageFriendHelp,
         iconMineFunAbout,
         iconMineFunAccountFeedback,
         iconMineFunArrow,
@@ -291,6 +304,8 @@ class $AssetsImagesGen {
         iconMineNoLogin,
         iconMineSmallVip,
         iconMineUnlockVip,
+        iconNews,
+        iconNewsItem,
         iconSplashTitle,
         iconTrackLocationNow,
         iconTrackSelectTimeArrow,

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

@@ -144,6 +144,18 @@ class StringName {
   static final String memberExpired = 'member_expired'.tr; // 会员已过期
   static final String gotIt = 'got_it'.tr; // 知道了
   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 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 messageAccepted = 'message_accepted'.tr; // 已同意您的好友申请
+  static final String messageRejected = 'message_rejected'.tr; // 已拒绝您的好友申请
+  static final String messageDeleteYour = 'message_delete_your'.tr; // 您的好友删除了你
 }
 class StringMultiSource {
   StringMultiSource._();
@@ -291,6 +303,16 @@ class StringMultiSource {
       'member_expired': '会员已过期',
       'got_it': '知道了',
       'track_no_data': '该时段内未查询到历史轨迹记录\n\n1.可能是该用户未登录本软件\n2.可能是该用户未运行我们的软件\n3.可能是对方未开启定位和网络连接等原因',
+      'news_title': '消息中心',
+      'news_request_title': '新的好友',
+      'news_request_desc': '您有新的好友申请,请及时查看',
+      'news_request_agree': '已同意',
+      'news_request_disagree': '已拒绝',
+      'news_to_contact': '去联系',
+      'message_try_for_help': '您的好友需要紧急求助,请您尽快联系他!',
+      'message_accepted': '已同意您的好友申请',
+      'message_rejected': '已拒绝您的好友申请',
+      'message_delete_your': '您的好友删除了你',
     },
   };
 }

+ 5 - 0
lib/router/app_pages.dart

@@ -10,10 +10,12 @@ import 'package:location/module/main/main_page.dart';
 import 'package:location/module/member/member_controller.dart';
 import 'package:location/module/member/member_page.dart';
 import 'package:location/module/mine/mine_page.dart';
+import 'package:location/module/news/news_page.dart';
 import '../module/add_friend/add_friend_dialog_controller.dart';
 import '../module/login/login_page.dart';
 import '../module/main/main_controller.dart';
 import '../module/mine/mine_controller.dart';
+import '../module/news/news_controller.dart';
 import '../module/splash/splash_page.dart';
 import '../module/track/track_controller.dart';
 import '../module/track/track_page.dart';
@@ -33,6 +35,7 @@ abstract class RoutePath {
   static const friendSetting = '/friendSetting';
   static const member = '/member';
   static const track = '/track';
+  static const news = '/news';
 }
 
 class AppBinding extends Bindings {
@@ -47,6 +50,7 @@ class AppBinding extends Bindings {
     lazyPut(() => getIt.get<FriendSettingController>());
     lazyPut(() => getIt.get<MemberController>());
     lazyPut(() => getIt.get<TrackController>());
+    lazyPut(() => getIt.get<NewsController>());
   }
 
   void lazyPut<S>(InstanceBuilderCallback<S> builder) {
@@ -63,4 +67,5 @@ final generalPages = [
   GetPage(name: RoutePath.friendSetting, page: () => FriendSettingPage()),
   GetPage(name: RoutePath.member, page: () => MemberPage()),
   GetPage(name: RoutePath.track, page: () => TrackPage()),
+  GetPage(name: RoutePath.news, page: () => NewsPage()),
 ];

+ 4 - 4
lib/socket/atmob_location_client.dart

@@ -122,11 +122,11 @@ class AtmobLocationClient {
               FriendsRepository.getInstance().refreshFriends();
               break;
             case SocketConstants.refreshFriendRequest:
-              FriendsRepository.getInstance().refreshFriendRequestList();
-              break;
-            case SocketConstants.refreshFriendMessage:
-              MessageRepository.getInstance().requestMessageList();
+              MessageRepository.getInstance().refreshFriendRequest();
               break;
+            // case SocketConstants.refreshFriendMessage:
+            //   MessageRepository.getInstance().requestMessageList();
+            //   break;
             case SocketConstants.refreshContact:
               ContactRepository.getInstance().refreshContactList();
               break;

+ 64 - 0
pubspec.lock

@@ -1046,6 +1046,70 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "1.4.0"
+  url_launcher:
+    dependency: "direct main"
+    description:
+      name: url_launcher
+      sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603"
+      url: "https://pub.dev"
+    source: hosted
+    version: "6.3.1"
+  url_launcher_android:
+    dependency: transitive
+    description:
+      name: url_launcher_android
+      sha256: "6fc2f56536ee873eeb867ad176ae15f304ccccc357848b351f6f0d8d4a40d193"
+      url: "https://pub.dev"
+    source: hosted
+    version: "6.3.14"
+  url_launcher_ios:
+    dependency: transitive
+    description:
+      name: url_launcher_ios
+      sha256: "16a513b6c12bb419304e72ea0ae2ab4fed569920d1c7cb850263fe3acc824626"
+      url: "https://pub.dev"
+    source: hosted
+    version: "6.3.2"
+  url_launcher_linux:
+    dependency: transitive
+    description:
+      name: url_launcher_linux
+      sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935"
+      url: "https://pub.dev"
+    source: hosted
+    version: "3.2.1"
+  url_launcher_macos:
+    dependency: transitive
+    description:
+      name: url_launcher_macos
+      sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2"
+      url: "https://pub.dev"
+    source: hosted
+    version: "3.2.2"
+  url_launcher_platform_interface:
+    dependency: transitive
+    description:
+      name: url_launcher_platform_interface
+      sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.3.2"
+  url_launcher_web:
+    dependency: transitive
+    description:
+      name: url_launcher_web
+      sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.3.3"
+  url_launcher_windows:
+    dependency: transitive
+    description:
+      name: url_launcher_windows
+      sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77"
+      url: "https://pub.dev"
+    source: hosted
+    version: "3.1.4"
   vector_graphics_codec:
     dependency: transitive
     description:

+ 3 - 1
pubspec.yaml

@@ -101,8 +101,10 @@ dependencies:
   timelines_plus: 1.0.6
 
   #时间滚轴选择器
-  flutter_cupertino_datetime_picker: ^3.0.0
+  #  flutter_cupertino_datetime_picker: ^3.0.0
 
+  #拨号
+  url_launcher: 6.3.1
 
   ######################地图########################
   flutter_map: