Quellcode durchsuchen

[new]增加首页部分内容

zk vor 8 Monaten
Ursprung
Commit
2efb119ce2

+ 2 - 2
assets/color/color.xml

@@ -1,8 +1,8 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
     <color name="transparent">#00FFFFFF</color>
-    <!--主题色文字色彩-->
-    <color name="colorPrimary">#7B7DFF</color>
+    <!--主题色-->
+    <color name="colorPrimary">#2674FD</color>
     <!--主题色点缀色-->
     <color name="colorAccentPrimary">#6399FF</color>
     <!--主题色背景色-->

BIN
assets/images/icon_add_friend_collect.webp


BIN
assets/images/icon_loation_member.webp


BIN
assets/images/icon_location_friend_add.webp


BIN
assets/images/icon_location_layer.webp


BIN
assets/images/icon_location_me_avatar.webp


BIN
assets/images/icon_location_menu.webp


BIN
assets/images/icon_location_position.webp


BIN
assets/images/icon_location_refresh.webp


BIN
assets/images/icon_location_search.webp


BIN
assets/images/icon_track.webp


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

@@ -9,4 +9,9 @@
     <string name="load_no_data">No more data</string>
     <string name="load_failed">Failed to load</string>
     <string name="network_error">Network error</string>
+    <string name="friend_list">Friend List</string>
+    <string name="location_add_friend">Add Friend</string>
+    <string name="history_trace">History</string>
+    <string name="location_add_friend_btn">Add</string>
+    <string name="location_add_friend_hint">Find by contact</string>
 </resources>

+ 35 - 0
lib/data/bean/location_info.dart

@@ -0,0 +1,35 @@
+import 'package:json_annotation/json_annotation.dart';
+
+part 'location_info.g.dart';
+
+@JsonSerializable()
+class LocationInfo {
+  @JsonKey(name: 'userId')
+  final String? userId;
+
+  @JsonKey(name: 'lng')
+  final double? longitude;
+
+  @JsonKey(name: 'lat')
+  final double? latitude;
+
+  @JsonKey(name: 'addr')
+  final String? address;
+
+  @JsonKey(name: 'timestamp')
+  final int? lastUpdateTime;
+
+  LocationInfo({
+    this.userId,
+    this.longitude,
+    this.latitude,
+    this.address,
+    this.lastUpdateTime,
+  });
+
+  factory LocationInfo.fromJson(Map<String, dynamic> json) =>
+      _$LocationInfoFromJson(json);
+
+  Map<String, dynamic> toJson() => _$LocationInfoToJson(this);
+
+}

+ 24 - 0
lib/data/bean/location_info.g.dart

@@ -0,0 +1,24 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'location_info.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+LocationInfo _$LocationInfoFromJson(Map<String, dynamic> json) => LocationInfo(
+      userId: json['userId'] as String?,
+      longitude: (json['lng'] as num?)?.toDouble(),
+      latitude: (json['lat'] as num?)?.toDouble(),
+      address: json['addr'] as String?,
+      lastUpdateTime: (json['timestamp'] as num?)?.toInt(),
+    );
+
+Map<String, dynamic> _$LocationInfoToJson(LocationInfo instance) =>
+    <String, dynamic>{
+      'userId': instance.userId,
+      'lng': instance.longitude,
+      'lat': instance.latitude,
+      'addr': instance.address,
+      'timestamp': instance.lastUpdateTime,
+    };

+ 64 - 0
lib/data/bean/user_info.dart

@@ -0,0 +1,64 @@
+import 'package:get/get.dart';
+import 'package:json_annotation/json_annotation.dart';
+
+import 'location_info.dart';
+
+part 'user_info.g.dart';
+
+@JsonSerializable()
+class UserInfo {
+  @JsonKey(name: 'friendId')
+  final String id;
+
+  @JsonKey(name: 'phone')
+  final String phoneNumber;
+
+  @JsonKey(name: 'remark')
+  String? remark;
+
+  @JsonKey(name: 'timestamp')
+  final int? timestamp;
+
+  @JsonKey(name: 'blockedHim')
+  bool? blockedHim;
+
+  @JsonKey(name: 'blockedMe')
+  final bool? blockedMe;
+
+  @JsonKey(name: 'location', ignore: true)
+  final Rxn<LocationInfo> lastLocation = Rxn<LocationInfo>();
+
+  @JsonKey(name: 'virtual')
+  final bool? virtual;
+
+  final bool? isMine;
+
+  UserInfo({
+    required this.id,
+    required this.phoneNumber,
+    this.remark,
+    this.timestamp,
+    this.blockedHim,
+    this.blockedMe,
+    this.virtual,
+    this.isMine,
+  });
+
+  factory UserInfo.fromJson(Map<String, dynamic> json) {
+    final userinfo = _$UserInfoFromJson(json);
+
+    LocationInfo? locationInfo = json['location'] == null
+        ? null
+        : LocationInfo.fromJson(json['location'] as Map<String, dynamic>);
+    userinfo.lastLocation.value = locationInfo;
+    return userinfo;
+  }
+
+  String getUserNickName() {
+    return (remark?.isNotEmpty == true ? remark : phoneNumber) ?? '';
+  }
+
+  void updateLocation(LocationInfo location) {
+    lastLocation.value = location;
+  }
+}

+ 29 - 0
lib/data/bean/user_info.g.dart

@@ -0,0 +1,29 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'user_info.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+UserInfo _$UserInfoFromJson(Map<String, dynamic> json) => UserInfo(
+      id: json['friendId'] as String,
+      phoneNumber: json['phone'] as String,
+      remark: json['remark'] as String?,
+      timestamp: (json['timestamp'] as num?)?.toInt(),
+      blockedHim: json['blockedHim'] as bool?,
+      blockedMe: json['blockedMe'] as bool?,
+      virtual: json['virtual'] as bool?,
+      isMine: json['isMine'] as bool?,
+    );
+
+Map<String, dynamic> _$UserInfoToJson(UserInfo instance) => <String, dynamic>{
+      'friendId': instance.id,
+      'phone': instance.phoneNumber,
+      'remark': instance.remark,
+      'timestamp': instance.timestamp,
+      'blockedHim': instance.blockedHim,
+      'blockedMe': instance.blockedMe,
+      'virtual': instance.virtual,
+      'isMine': instance.isMine,
+    };

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

@@ -33,10 +33,10 @@ extension GetItInjectableX on _i174.GetIt {
       environmentFilter,
     );
     final networkModule = _$NetworkModule();
+    gh.factory<_i821.FriendController>(() => _i821.FriendController());
+    gh.factory<_i734.LocationController>(() => _i734.LocationController());
     gh.factory<_i731.MainController>(() => _i731.MainController());
     gh.factory<_i973.SplashController>(() => _i973.SplashController());
-    gh.factory<_i734.LocationController>(() => _i734.LocationController());
-    gh.factory<_i821.FriendController>(() => _i821.FriendController());
     gh.factory<_i369.UrgentController>(() => _i369.UrgentController());
     gh.singleton<_i361.Dio>(() => networkModule.createDefaultDio());
     gh.lazySingleton<_i20.AccountRepository>(() => _i20.AccountRepository());

+ 92 - 0
lib/module/location/friend_item.dart

@@ -0,0 +1,92 @@
+import 'package:abroad_location/data/bean/user_info.dart';
+import 'package:abroad_location/resource/assets.gen.dart';
+import 'package:abroad_location/resource/colors.gen.dart';
+import 'package:abroad_location/resource/string.gen.dart';
+import 'package:abroad_location/utils/base_expand.dart';
+import 'package:flutter/cupertino.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+
+typedef OnFriendCallback = void Function(UserInfo userInfo);
+
+Widget locationFriendItem(UserInfo userInfo,
+    {OnFriendCallback? onItemClick, OnFriendCallback? onTrackClick}) {
+  return GestureDetector(
+    onTap: (){
+      onItemClick?.call(userInfo);
+    },
+    child: Container(
+      height: 70.w,
+      margin: EdgeInsets.symmetric(horizontal: 8.w),
+      decoration: BoxDecoration(
+        color: ColorName.white,
+        borderRadius: BorderRadius.circular(20.w),
+      ),
+      child: Row(
+        children: [
+          SizedBox(width: 12.w),
+          Container(
+            //阴影,
+            decoration: BoxDecoration(
+              borderRadius: BorderRadius.circular(18.w),
+                boxShadow: [
+                  BoxShadow(
+                      color: ColorName.black20.withOpacity(0.1),
+                      blurRadius: 5.w)
+                ]),
+              child: Assets.images.iconLocationMeAvatar.image(width: 44.w, height: 44.w)),
+          SizedBox(width: 11.w),
+          Expanded(
+            child: IntrinsicHeight(
+              child: Column(
+                crossAxisAlignment: CrossAxisAlignment.start,
+                children: [
+                  Row(children: [
+                    Text(userInfo.phoneNumber ?? '',
+                        style: TextStyle(
+                            fontSize: 16.sp,
+                            color: ColorName.black80,
+                            fontWeight: FontWeight.bold)),
+                    SizedBox(width: 5.w),
+                    Expanded(
+                      child: Text('1h',
+                          style: TextStyle(fontSize: 12.sp, color: ColorName.black40)),
+                    ),
+                    SizedBox(width: 5.w),
+                  ]),
+                  Text('Hong Kong',
+                      style: TextStyle(fontSize: 12.sp, color: ColorName.black50))
+                ],
+              ),
+            ),
+          ),
+          GestureDetector(
+            onTap: (){
+              onTrackClick?.call(userInfo);
+            },
+            child: Container(
+              decoration: BoxDecoration(
+                  borderRadius: BorderRadius.circular(10.w),
+                  border: Border.all(color: '#4476FF'.color, width: 1.w)),
+              padding: EdgeInsets.only(
+                  left: 10.w, top: 5.w, bottom: 6.w, right: 10.w),
+              child: Row(
+                children: [
+                  Assets.images.iconTrack.image(width: 16.w, height: 16.w),
+                  SizedBox(width: 2.w),
+                  Text(
+                    StringName.historyTrace,
+                    style: TextStyle(
+                        fontSize: 14.sp,
+                        color: '#4476FF'.color,
+                        fontWeight: FontWeight.w500),
+                  )
+                ],
+              ),
+            ),
+          ),
+          SizedBox(width: 12.w)
+        ],
+      ),
+    ),
+  );
+}

+ 51 - 4
lib/module/location/location_controller.dart

@@ -1,10 +1,57 @@
-
-
 import 'package:abroad_location/base/base_controller.dart';
+import 'package:get/get.dart';
 import 'package:injectable/injectable.dart';
+import 'package:sliding_sheet2/sliding_sheet2.dart';
+
+import '../../data/bean/user_info.dart';
 
 @injectable
-class LocationController extends BaseController{
+class LocationController extends BaseController {
+  SheetController sheetController = SheetController();
+  final RxDouble _sheetProgress = 0.0.obs;
+
+  double get sheetProgress => _sheetProgress.value;
+  final Rxn<UserInfo> _selectUserInfo = Rxn();
+
+  UserInfo? get selectUserInfo => _selectUserInfo.value;
+  final RxList<UserInfo> locationFriendList = RxList();
+  final RxBool _isFindContact = false.obs;
+
+  bool get isFindContact => _isFindContact.value;
+
+  final findContactAnimatedDuration = const Duration(milliseconds: 250);
+
+  @override
+  void onReady() {
+    super.onReady();
+    //*******测试*******
+    for (int i = 0; i < 20; i++) {
+      locationFriendList.add(UserInfo(
+        id: 'id$i',
+        phoneNumber: 'phoneNumber$i',
+        remark: 'remark$i',
+        timestamp: 1234567890,
+        blockedHim: false,
+        blockedMe: false,
+        virtual: false,
+        isMine: false,
+      ));
+    }
+    _selectUserInfo.value = locationFriendList[0];
+    //**********
+  }
+
+  setSheetProgress(double progress) {
+    _sheetProgress.value = progress;
+  }
 
+  onSelectItemClick(UserInfo userInfo) async {
+    _selectUserInfo.value = userInfo;
+    await sheetController.scrollTo(0, duration: Duration(milliseconds: 1));
+    sheetController.snapToExtent(0, duration: Duration(milliseconds: 350));
+  }
 
-}
+  void onFindContactClick() {
+    _isFindContact.value =  !isFindContact;
+  }
+}

+ 377 - 1
lib/module/location/location_view.dart

@@ -1,7 +1,18 @@
 import 'package:abroad_location/base/base_view.dart';
+import 'package:abroad_location/resource/assets.gen.dart';
+import 'package:abroad_location/resource/colors.gen.dart';
+import 'package:abroad_location/resource/string.gen.dart';
+import 'package:abroad_location/utils/atmob_log.dart';
+import 'package:abroad_location/utils/base_expand.dart';
+import 'package:abroad_location/widget/MapWidget.dart';
 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_state_manager/src/rx_flutter/rx_obx_widget.dart';
+import 'package:sliding_sheet2/sliding_sheet2.dart';
 
+import 'friend_item.dart';
 import 'location_controller.dart';
 
 class LocationView extends BaseView<LocationController> {
@@ -9,6 +20,371 @@ class LocationView extends BaseView<LocationController> {
 
   @override
   Widget buildBody(BuildContext context) {
-    return Center(child: Text('LocationView'));
+    return SlidingSheet(
+      listener: (SheetState state) {
+        controller.setSheetProgress(state.progress);
+        AtmobLog.d('zk', 'progress: ${state.progress}');
+      },
+      color: '#F7F7F7'.color,
+      controller: controller.sheetController,
+      elevation: 10,
+      shadowColor: Colors.black.withOpacity(0.1),
+      cornerRadius: 28.w,
+      snapSpec: SnapSpec(
+        initialSnap: 158.w, // 67.w + 91.w
+        // Enable snapping. This is true by default.
+        snap: true,
+        // Set custom snapping points.
+        snappings: [158.w, 500.w],
+        // Define to what the snappings relate to. In this case,
+        // the total available space that the sheet can expand to.
+        positioning: SnapPositioning.pixelOffset,
+      ),
+      body: buildSheetBody(),
+      headerBuilder: buildLocationSheetHeader,
+      customBuilder: buildFriendList,
+    );
+  }
+
+  Widget buildSheetBody() {
+    return Stack(
+      children: [
+        MapWidget(),
+        buildLocationMember(),
+        buildAddFriendView(),
+        buildLocationMenu()
+      ],
+    );
+  }
+
+  Widget buildLocationSheetHeader(BuildContext context, SheetState state) {
+    return SizedBox(
+      width: double.infinity,
+      child: IntrinsicHeight(
+        child: Column(
+          children: [
+            SizedBox(height: 8.w),
+            Container(
+              width: 36.w,
+              height: 4.w,
+              decoration: BoxDecoration(
+                color: '#D4DCEA'.color,
+                borderRadius: BorderRadius.circular(2.w),
+              ),
+            ),
+            SizedBox(height: 19.w),
+            Container(
+              margin: EdgeInsets.only(left: 16.w, right: 16.w),
+              child: Row(
+                children: [
+                  Text(StringName.friendList,
+                      style: TextStyle(
+                          fontSize: 20.sp,
+                          color: ColorName.black80,
+                          fontWeight: FontWeight.bold)),
+                  Spacer(),
+                  Container(
+                    height: 28.w,
+                    decoration: BoxDecoration(
+                      color: '#264476FF'.color,
+                      borderRadius: BorderRadius.circular(100.w),
+                    ),
+                    padding: EdgeInsets.all(2.w),
+                    child: Row(
+                      children: [
+                        Assets.images.iconLocationFriendAdd.image(),
+                        SizedBox(width: 5.w),
+                        Text(
+                          StringName.locationAddFriend,
+                          style: TextStyle(
+                              fontSize: 12.sp,
+                              color: '#274494'.color,
+                              fontWeight: FontWeight.w500),
+                        ),
+                        SizedBox(width: 8.w),
+                      ],
+                    ),
+                  )
+                ],
+              ),
+            ),
+            SizedBox(height: 8.w),
+          ],
+        ),
+      ),
+    );
+  }
+
+  //选中的好友卡片
+  Widget buildSelectedFriendCard() {
+    return Obx(() {
+      return Visibility(
+        visible: controller.sheetProgress < 1,
+        child: Opacity(
+          opacity: 1 - controller.sheetProgress,
+          child: SingleChildScrollView(
+            child: Container(
+              color: '#F7F7F7'.color,
+              child: Column(
+                children: [
+                  SizedBox(height: 8.w),
+                  controller.selectUserInfo != null
+                      ? locationFriendItem(controller.selectUserInfo!)
+                      : SizedBox.shrink(),
+                  SizedBox(height: 13.w),
+                ],
+              ),
+            ),
+          ),
+        ),
+      );
+    });
+  }
+
+  Widget buildFriendList(
+    BuildContext context,
+    ScrollController ctl,
+    SheetState state,
+  ) {
+    return Stack(children: [
+      Obx(() {
+        return Opacity(
+          opacity: controller.sheetProgress,
+          child: ListView.builder(
+              padding: EdgeInsets.zero,
+              controller: ctl,
+              itemBuilder: buildFriendListItem,
+              itemCount: controller.locationFriendList.length),
+        );
+      }),
+      buildSelectedFriendCard(),
+    ]);
+  }
+
+  Widget buildFriendListItem(BuildContext context, int index) {
+    return Container(
+        margin: EdgeInsets.only(bottom: 10.w),
+        child: locationFriendItem(controller.locationFriendList[index],
+            onItemClick: (userInfo) {
+          controller.onSelectItemClick(userInfo);
+        }, onTrackClick: (userInfo) {}));
+  }
+
+  Widget buildLocationMenu() {
+    return Positioned(
+      right: 4.w,
+      bottom: 220.w,
+      child: IntrinsicHeight(
+        child: Column(
+          children: [
+            Assets.images.iconLocationLayer.image(width: 56.w, height: 56.w),
+            Assets.images.iconLocationPosition.image(width: 56.w, height: 56.w),
+            Assets.images.iconLocationRefresh.image(width: 56.w, height: 56.w),
+          ],
+        ),
+      ),
+    );
+  }
+
+  Widget buildLocationMember() {
+    return Positioned(
+        top: 125.w,
+        right: 4.w,
+        child:
+            Assets.images.iconLoationMember.image(width: 56.w, height: 56.w));
+  }
+
+  Widget buildAddFriendView() {
+    return Stack(
+      children: [
+        buildHeadBg(),
+        buildAddFriendBackboard(),
+        buildAddFriendInput()
+      ],
+    );
+  }
+
+  Widget buildAddFriendBackboard() {
+    return Container(
+        width: double.infinity,
+        height: 205.w,
+        decoration: BoxDecoration(
+          borderRadius: BorderRadius.only(
+              bottomLeft: Radius.circular(36.w),
+              bottomRight: Radius.circular(36.w)),
+          gradient: LinearGradient(
+            colors: [
+              ColorName.white10,
+              ColorName.white90,
+            ],
+            begin: Alignment.topCenter,
+            end: Alignment.bottomCenter,
+          ),
+          boxShadow: [
+            BoxShadow(
+                color: ColorName.black40.withOpacity(0.1),
+                blurRadius: 10.w,
+                offset: Offset(0, 2.w))
+          ],
+        ),
+      );
+
+    // return Stack(children: [
+    //
+    //   Container(
+    //     width: double.infinity,
+    //     height: 210.w,
+    //     decoration: BoxDecoration(
+    //       color: ColorName.black,
+    //       borderRadius: BorderRadius.only(
+    //           bottomLeft: Radius.circular(32.w),
+    //           bottomRight: Radius.circular(34.w)),
+    //     ),
+    //   ),
+    //
+    //   ClipRRect(
+    //     child: Container(
+    //     width: double.infinity,
+    //     height: 205.w,
+    //     decoration: BoxDecoration(
+    //       borderRadius: BorderRadius.only(
+    //           bottomLeft: Radius.circular(36.w),
+    //           bottomRight: Radius.circular(36.w)),
+    //       gradient: LinearGradient(
+    //         colors: [
+    //           Colors.transparent,
+    //           Colors.blue,
+    //           // ColorName.white10,
+    //           // ColorName.white90,
+    //         ],
+    //         begin: Alignment.topCenter,
+    //         end: Alignment.bottomCenter,
+    //       ),
+    //     ),
+    //         ),
+    //   )
+    // ],);
+
+
+
+  }
+
+
+
+
+  Container buildHeadBg() {
+    return Container(
+        decoration: BoxDecoration(
+          gradient: LinearGradient(
+            colors: [
+              '#DEE5FF'.color,
+              ColorName.transparent,
+            ],
+            begin: Alignment.topCenter,
+            end: Alignment.bottomCenter,
+          ),
+        ),
+        width: double.infinity,
+        height: 73.w,
+      );
+  }
+
+  Container buildAddFriendInput() {
+    return Container(
+        padding: EdgeInsets.only(top: 62.w),
+        child: Row(
+          children: [
+            SizedBox(width: 16.w),
+            Obx(() {
+              return AnimatedContainer(
+                duration: controller.findContactAnimatedDuration,
+                width: controller.isFindContact ? 0.w : 50.w,
+                height: controller.isFindContact ? 0.w : 50.w,
+                margin: EdgeInsets.only(right: 10.w),
+                decoration: BoxDecoration(
+                  borderRadius: BorderRadius.all(Radius.circular(18.w)),
+                  boxShadow: [
+                    BoxShadow(
+                        color: ColorName.black20.withOpacity(0.1),
+                        blurRadius: 10.w)
+                  ],
+                ),
+                child: Assets.images.iconLocationMenu
+                    .image(width: double.infinity, height: double.infinity),
+              );
+            }),
+            Expanded(
+              child: Container(
+                height: 50.w,
+                decoration: BoxDecoration(
+                  color: ColorName.white,
+                  borderRadius: BorderRadius.all(Radius.circular(100.w)),
+                  boxShadow: [
+                    BoxShadow(
+                        color: ColorName.black20.withOpacity(0.1),
+                        blurRadius: 10.w)
+                  ],
+                ),
+                child: Row(
+                  children: [
+                    SizedBox(width: 8.w),
+                    Assets.images.iconLocationSearch
+                        .image(width: 18.w, height: 18.w),
+                    SizedBox(width: 10.w),
+                    Expanded(
+                      child: GestureDetector(
+                        behavior: HitTestBehavior.opaque,
+                        onTap: () {
+                          controller.onFindContactClick();
+                        },
+                        child: SizedBox(
+                          height: double.infinity,
+                          child: Align(
+                            alignment: Alignment.centerLeft,
+                            child: Text(
+                              textAlign: TextAlign.start,
+                              StringName.locationAddFriendHint,
+                              style: TextStyle(
+                                  fontSize: 14.sp, color: ColorName.black40),
+                            ),
+                          ),
+                        ),
+                      ),
+                    ),
+                    SizedBox(width: 10.w),
+                    Obx(() {
+                      return AnimatedScale(
+                        scale: controller.isFindContact ? 0 : 1,
+                        duration: controller.findContactAnimatedDuration,
+                        child: Container(
+                          padding: EdgeInsets.symmetric(
+                              vertical: 5.5.w, horizontal: 11.w),
+                          decoration: BoxDecoration(
+                            color: '#4476FF'.color,
+                            borderRadius: BorderRadius.circular(100.w),
+                            boxShadow: [
+                              BoxShadow(
+                                  offset: Offset(0, 2.w),
+                                  color: '#4476FF'.color.withOpacity(0.4),
+                                  blurRadius: 5.w)
+                            ],
+                          ),
+                          child: Text(StringName.locationAddFriendBtn,
+                              style: TextStyle(
+                                  fontSize: 14.sp,
+                                  color: ColorName.white,
+                                  fontWeight: FontWeight.bold)),
+                        ),
+                      );
+                    }),
+                    SizedBox(width: 10.w),
+                  ],
+                ),
+              ),
+            ),
+            SizedBox(width: 16.w),
+          ],
+        ),
+      );
   }
 }

+ 51 - 1
lib/resource/assets.gen.dart

@@ -12,6 +12,42 @@ import 'package:flutter/widgets.dart';
 class $AssetsImagesGen {
   const $AssetsImagesGen();
 
+  /// File path: assets/images/icon_add_friend_collect.webp
+  AssetGenImage get iconAddFriendCollect =>
+      const AssetGenImage('assets/images/icon_add_friend_collect.webp');
+
+  /// File path: assets/images/icon_loation_member.webp
+  AssetGenImage get iconLoationMember =>
+      const AssetGenImage('assets/images/icon_loation_member.webp');
+
+  /// File path: assets/images/icon_location_friend_add.webp
+  AssetGenImage get iconLocationFriendAdd =>
+      const AssetGenImage('assets/images/icon_location_friend_add.webp');
+
+  /// File path: assets/images/icon_location_layer.webp
+  AssetGenImage get iconLocationLayer =>
+      const AssetGenImage('assets/images/icon_location_layer.webp');
+
+  /// File path: assets/images/icon_location_me_avatar.webp
+  AssetGenImage get iconLocationMeAvatar =>
+      const AssetGenImage('assets/images/icon_location_me_avatar.webp');
+
+  /// File path: assets/images/icon_location_menu.webp
+  AssetGenImage get iconLocationMenu =>
+      const AssetGenImage('assets/images/icon_location_menu.webp');
+
+  /// File path: assets/images/icon_location_position.webp
+  AssetGenImage get iconLocationPosition =>
+      const AssetGenImage('assets/images/icon_location_position.webp');
+
+  /// File path: assets/images/icon_location_refresh.webp
+  AssetGenImage get iconLocationRefresh =>
+      const AssetGenImage('assets/images/icon_location_refresh.webp');
+
+  /// File path: assets/images/icon_location_search.webp
+  AssetGenImage get iconLocationSearch =>
+      const AssetGenImage('assets/images/icon_location_search.webp');
+
   /// File path: assets/images/icon_main_tab_friend_selected.webp
   AssetGenImage get iconMainTabFriendSelected =>
       const AssetGenImage('assets/images/icon_main_tab_friend_selected.webp');
@@ -36,14 +72,28 @@ class $AssetsImagesGen {
   AssetGenImage get iconMianTabUrgentUnselect =>
       const AssetGenImage('assets/images/icon_mian_tab_urgent_unselect.webp');
 
+  /// File path: assets/images/icon_track.webp
+  AssetGenImage get iconTrack =>
+      const AssetGenImage('assets/images/icon_track.webp');
+
   /// List of all assets
   List<AssetGenImage> get values => [
+        iconAddFriendCollect,
+        iconLoationMember,
+        iconLocationFriendAdd,
+        iconLocationLayer,
+        iconLocationMeAvatar,
+        iconLocationMenu,
+        iconLocationPosition,
+        iconLocationRefresh,
+        iconLocationSearch,
         iconMainTabFriendSelected,
         iconMainTabFriendUnselect,
         iconMainTabLocationSelected,
         iconMainTabLocationUnselect,
         iconMainTabUrgentSelected,
-        iconMianTabUrgentUnselect
+        iconMianTabUrgentUnselect,
+        iconTrack
       ];
 }
 

+ 2 - 2
lib/resource/colors.gen.dart

@@ -79,8 +79,8 @@ class ColorName {
   /// Color: #6399FF
   static const Color colorAccentPrimary = Color(0xFF6399FF);
 
-  /// Color: #7B7DFF
-  static const Color colorPrimary = Color(0xFF7B7DFF);
+  /// Color: #2674FD
+  static const Color colorPrimary = Color(0xFF2674FD);
 
   /// Color: #F6FAFF
   static const Color colorPrimaryLight = Color(0xFFF6FAFF);

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

@@ -10,6 +10,11 @@ class StringName {
   static final String loadNoData = 'load_no_data'.tr; // No more data
   static final String loadFailed = 'load_failed'.tr; // Failed to load
   static final String networkError = 'network_error'.tr; // Network error
+  static final String friendList = 'friend_list'.tr; // Friend List
+  static final String locationAddFriend = 'location_add_friend'.tr; // Add Friend
+  static final String historyTrace = 'history_trace'.tr; // History
+  static final String locationAddFriendBtn = 'location_add_friend_btn'.tr; // Add
+  static final String locationAddFriendHint = 'location_add_friend_hint'.tr; // Find by contact
 }
 class StringMultiSource {
   StringMultiSource._();
@@ -23,6 +28,11 @@ class StringMultiSource {
       'load_no_data': 'No more data',
       'load_failed': 'Failed to load',
       'network_error': 'Network error',
+      'friend_list': 'Friend List',
+      'location_add_friend': 'Add Friend',
+      'history_trace': 'History',
+      'location_add_friend_btn': 'Add',
+      'location_add_friend_hint': 'Find by contact',
     },
   };
 }

+ 100 - 0
lib/utils/base_expand.dart

@@ -0,0 +1,100 @@
+import 'dart:async';
+import 'dart:ui';
+
+
+extension TrimAllExtension on String {
+  String trimAll() {
+    return replaceAll(RegExp(r'\s+'), '');
+  }
+
+  String trimAndReduceSpaces() {
+    return trim().replaceAll(RegExp(r'\s+'), ' ');
+  }
+}
+
+
+extension FutureMap<T> on Future<T> {
+  Future<R> map<R>(R Function(T) transform) {
+    return then(transform);
+  }
+}
+
+extension HexColor on String {
+  Color get color => toColor();
+
+  Color toColor() {
+    String hex = replaceAll('#', '');
+    if (hex.length == 6) {
+      hex = 'FF$hex';
+    }
+    return Color(int.parse(hex, radix: 16));
+  }
+}
+
+
+extension LetExtension<T> on T {
+  /// 类似 Kotlin 的 let 函数,允许对任意对象执行代码块
+  R let<R>(R Function(T it) block) {
+    return block(this);
+  }
+}
+
+extension ApplyExtension<T> on T {
+  /// 类似 Kotlin 的 apply 函数,允许对对象执行配置操作,并返回自身
+  T apply(void Function(T it) block) {
+    block(this);
+    return this;
+  }
+}
+
+extension AlsoExtension<T> on T {
+  T also(void Function(T it) block) {
+    block(this);
+    return this;
+  }
+}
+
+extension RunExtension<T> on T {
+  R run<R>(R Function(T it) block) => block(this);
+}
+
+extension StreamBufferTimeExtension<T> on Stream<T> {
+  /// 将流中的事件按时间窗口缓冲,每隔 [duration] 时间发送一次缓冲列表
+  Stream<List<T>> bufferTime(Duration duration) {
+    StreamController<List<T>>? controller;
+    List<T> buffer = [];
+    Timer? timer;
+
+    controller = StreamController<List<T>>(
+      onListen: () {
+        timer = Timer.periodic(duration, (_) {
+          if (buffer.isNotEmpty) {
+            controller?.add(List.from(buffer));
+            buffer.clear();
+          }
+        });
+        // 监听原始流,收集事件到缓冲区
+        listen(
+          (event) => buffer.add(event),
+          onError: (error) => controller?.addError(error),
+          onDone: () {
+            timer?.cancel();
+            // 流结束时发送剩余缓冲事件
+            if (buffer.isNotEmpty) {
+              controller?.add(List.from(buffer));
+              buffer.clear();
+            }
+            controller?.close();
+          },
+        );
+      },
+      onCancel: () {
+        timer?.cancel();
+        buffer.clear();
+      },
+    );
+
+    // 返回控制器对应的 Stream,而不是控制器本身
+    return controller.stream;
+  }
+}

+ 4 - 1
lib/widget/MapWidget.dart

@@ -1,4 +1,5 @@
 import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
 import 'package:flutter_screenutil/flutter_screenutil.dart';
 
 class MapWidget extends StatelessWidget {
@@ -6,6 +7,8 @@ class MapWidget extends StatelessWidget {
 
   @override
   Widget build(BuildContext context) {
-    return Text('地图组件', style: TextStyle(fontSize: 20.sp));
+    return Container(
+        color: Colors.white,
+        child: Center(child: Text('地图组件', style: TextStyle(fontSize: 20.sp))));
   }
 }

+ 3 - 0
pubspec.yaml

@@ -75,6 +75,9 @@ dependencies:
   #上、下拉刷新
   pull_to_refresh: 2.0.0
 
+  #抽屉
+  sliding_sheet2: 2.0.1
+
   #日志打印
   atmob_logging:
     version: ^0.0.5