diff --git a/ios/Podfile.lock b/ios/Podfile.lock index d75152d..ef021bc 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -14,6 +14,8 @@ PODS: - Flutter - path_provider_ios (0.0.1): - Flutter + - permission_handler_apple (9.0.4): + - Flutter - photo_manager (2.0.0): - Flutter - FlutterMacOS @@ -35,6 +37,8 @@ PODS: - Flutter - video_player_avfoundation (0.0.1): - Flutter + - wakelock (0.0.1): + - Flutter DEPENDENCIES: - Flutter (from `Flutter`) @@ -42,6 +46,7 @@ DEPENDENCIES: - image_cropper (from `.symlinks/plugins/image_cropper/ios`) - open_file (from `.symlinks/plugins/open_file/ios`) - path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`) + - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) - photo_manager (from `.symlinks/plugins/photo_manager/ios`) - scan (from `.symlinks/plugins/scan/ios`) - smart_auth (from `.symlinks/plugins/smart_auth/ios`) @@ -49,6 +54,7 @@ DEPENDENCIES: - tencent_im_sdk_plugin (from `.symlinks/plugins/tencent_im_sdk_plugin/ios`) - vibration (from `.symlinks/plugins/vibration/ios`) - video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/ios`) + - wakelock (from `.symlinks/plugins/wakelock/ios`) SPEC REPOS: trunk: @@ -69,6 +75,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/open_file/ios" path_provider_ios: :path: ".symlinks/plugins/path_provider_ios/ios" + permission_handler_apple: + :path: ".symlinks/plugins/permission_handler_apple/ios" photo_manager: :path: ".symlinks/plugins/photo_manager/ios" scan: @@ -83,6 +91,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/vibration/ios" video_player_avfoundation: :path: ".symlinks/plugins/video_player_avfoundation/ios" + wakelock: + :path: ".symlinks/plugins/wakelock/ios" SPEC CHECKSUMS: Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a @@ -92,6 +102,7 @@ SPEC CHECKSUMS: image_cropper: 60c2789d1f1a78c873235d4319ca0c34a69f2d98 open_file: 02eb5cb6b21264bd3a696876f5afbfb7ca4f4b7d path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02 + permission_handler_apple: 44366e37eaf29454a1e7b1b7d736c2cceaeb17ce photo_manager: 4f6810b7dfc4feb03b461ac1a70dacf91fba7604 scan: aea35bb4aa59ccc8839c576a18cd57c7d492cc86 smart_auth: 4bedbc118723912d0e45a07e8ab34039c19e04f2 @@ -102,6 +113,7 @@ SPEC CHECKSUMS: TXIMSDK_Plus_iOS: 5412f55a77f058b2b5a8575900334daccbae3b08 vibration: 7d883d141656a1c1a6d8d238616b2042a51a1241 video_player_avfoundation: e489aac24ef5cf7af82702979ed16f2a5ef84cff + wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f PODFILE CHECKSUM: a75497545d4391e2d394c3668e20cfb1c2bbd4aa diff --git a/lib/routes/contact_routes.dart b/lib/routes/contact_routes.dart index 27014c7..a17b6eb 100644 --- a/lib/routes/contact_routes.dart +++ b/lib/routes/contact_routes.dart @@ -13,6 +13,10 @@ abstract class ContactRoutes { static const String friend = '/contact/friend'; static const String friendSearch = '/contact/friend/search'; static const String friendProfile = '/contact/friend/profile'; + static const String friendProfileMore = '/contact/friend/profile'; + static const String friendRemark = '/contact/friend/profile'; + static const String friendApply = '/contact/friend/profile'; + static const String friendRecommend = '/contact/friend/profile'; static const String group = '/contact/group'; static const String groupQrCode = '/contact/group/qrCode'; diff --git a/lib/views/contact/firend/profile/index_page.dart b/lib/views/contact/firend/profile/index_page.dart new file mode 100644 index 0000000..9890a40 --- /dev/null +++ b/lib/views/contact/firend/profile/index_page.dart @@ -0,0 +1,147 @@ +import 'package:chat/configs/app_colors.dart'; +import 'package:chat/controllers/private_controller.dart'; +import 'package:chat/routes/contact_routes.dart'; +import 'package:chat/routes/conversation_routes.dart'; +import 'package:chat/routes/moments_routes.dart'; +import 'package:chat/services/tim/conversation_service.dart'; +import 'package:chat/utils/im_tools.dart'; +import 'package:chat/views/home/widgets/action_button.dart'; +import 'package:chat/views/home/widgets/action_item.dart'; +import 'package:chat/widgets/custom_avatar.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +class ImFriendProfilePage extends StatelessWidget { + const ImFriendProfilePage({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return GetX(builder: (_) { + return Scaffold( + appBar: AppBar( + backgroundColor: AppColors.white, + actions: [ + Visibility( + visible: _.currentFriend.value.isFriend, + child: IconButton( + onPressed: () { + Get.toNamed( + ContactRoutes.friendProfileMore, + ); + }, + icon: const Icon( + Icons.more_horiz, + ), + ), + ), + ], + ), + body: Column( + children: [ + Container( + padding: const EdgeInsets.all(16), + color: AppColors.white, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CustomAvatar( + _.currentFriend.value.userProfile?.faceUrl, + size: 54, + ), + const SizedBox(width: 16), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + _.currentFriend.value.conversation!.showName!, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.w400, + ), + ), + Text('昵称:${_.currentFriend.value.userProfile?.nickName}'), + // Text('区块链地址:$friendId'), + ], + ), + ], + ), + ), + const Divider(height: 0), + ActionItem( + '设置备注', + extend: _.currentFriend.value.friendRemark, + onTap: () { + Get.toNamed( + ContactRoutes.friendRemark, + ); + }, + ), + const SizedBox(height: 8), + ActionItem( + '他的动态', + onTap: () { + Get.toNamed( + MomentsRoutes.user, + arguments: { + 'userId': _.currentFriend.value.userID, + }, + ); + }, + ), + const SizedBox(height: 8), + Visibility( + visible: !_.currentFriend.value.isFriend, + child: ActionButton( + '添加到通讯录', + onTap: () { + Get.toNamed( + ContactRoutes.friendApply, + arguments: { + 'userID': _.currentFriend.value.userID, + }, + ); + }, + ), + ), + Visibility( + visible: _.currentFriend.value.isFriend, + child: ActionButton( + '发消息', + color: AppColors.primary, + onTap: () async { + var result = await TimConversationService + .to.conversationManager + .getConversation( + conversationID: 'c2c_' + _.currentFriend.value.userID, + ); + if (result.code == 0) { + Get.toNamed( + ConversationRoutes.index, + arguments: { + 'conversation': result.data, + }, + ); + } + }, + ), + ), + Visibility( + visible: _.currentFriend.value.isFriend, + child: const Divider(height: 0), + ), + Visibility( + visible: _.currentFriend.value.isFriend, + child: ActionButton( + '音视频通话', + color: AppColors.primary, + onTap: () { + ImTools.showTrtcMessage(_.currentFriend.value.userID); + }, + ), + ), + ], + ), + ); + }); + } +} diff --git a/lib/views/contact/firend/profile/more_page.dart b/lib/views/contact/firend/profile/more_page.dart new file mode 100644 index 0000000..d63d5dc --- /dev/null +++ b/lib/views/contact/firend/profile/more_page.dart @@ -0,0 +1,105 @@ +import 'package:adaptive_dialog/adaptive_dialog.dart'; +import 'package:chat/controllers/private_controller.dart'; +import 'package:chat/routes/contact_routes.dart'; +import 'package:chat/services/tim/friend_service.dart'; +import 'package:chat/utils/ui_tools.dart'; +import 'package:chat/views/home/widgets/action_button.dart'; +import 'package:chat/views/home/widgets/action_item.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +class ImFriendProfileMorePage extends StatelessWidget { + const ImFriendProfileMorePage({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return GetX(builder: (_) { + return Scaffold( + appBar: AppBar( + title: const Text('资料设置'), + ), + body: Column( + children: [ + ActionItem( + '设置备注', + extend: _.currentFriend.value.friendRemark, + onTap: () { + Get.toNamed( + ContactRoutes.friendRemark, + ); + }, + ), + const SizedBox(height: 8), + ActionItem( + '把他推荐给朋友', + onTap: () { + Get.toNamed( + ContactRoutes.friendRecommend, + ); + }, + ), + const SizedBox(height: 8), + ActionItem( + '加入黑名单', + rightWidget: SizedBox( + height: 24, + child: Switch( + value: false, + onChanged: (e) async { + OkCancelResult result = await showOkCancelAlertDialog( + style: AdaptiveStyle.iOS, + context: Get.context!, + title: '加入黑名单', + message: '加入黑名单将会解除好友关系,不再接收他的消息', + okLabel: '确定', + cancelLabel: '取消', + defaultType: OkCancelAlertDefaultType.ok, + ); + + if (result == OkCancelResult.ok) { + var res = await TimBlockService.to.add( + _.currentFriend.value.userID, + ); + if (res) { + UiTools.toast('加入黑名单成功'); + Navigator.popUntil(context, (route) => route.isFirst); + } + } + }, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + ), + ), + ), + const SizedBox(height: 8), + ActionButton( + '删除', + onTap: () async { + OkCancelResult result = await showOkCancelAlertDialog( + style: AdaptiveStyle.iOS, + context: Get.context!, + title: '删除好友', + message: '确定要删除该好友么?', + okLabel: '确定', + cancelLabel: '取消', + defaultType: OkCancelAlertDefaultType.ok, + ); + + if (result == OkCancelResult.ok) { + var res = await TimFriendService.to.delete( + _.currentFriend.value.userID, + ); + if (res) { + UiTools.toast('好友删除成功'); + + /// 关闭所有页面 + Navigator.popUntil(context, (route) => route.isFirst); + } + } + }, + ), + ], + ), + ); + }); + } +} diff --git a/lib/views/contact/firend/recommend/friends_page.dart b/lib/views/contact/firend/recommend/friends_page.dart new file mode 100644 index 0000000..1d6c0f8 --- /dev/null +++ b/lib/views/contact/firend/recommend/friends_page.dart @@ -0,0 +1,164 @@ +import 'package:adaptive_dialog/adaptive_dialog.dart'; +import 'package:azlistview/azlistview.dart'; +import 'package:chat/configs/app_colors.dart'; +import 'package:chat/controllers/private_controller.dart'; +import 'package:chat/models/im/contact_info_model.dart'; +import 'package:chat/models/im/name_card_model.dart'; +import 'package:chat/models/im/private_conversation_model.dart'; +import 'package:chat/services/tim/conversation_service.dart'; +import 'package:chat/services/tim/friend_service.dart'; +import 'package:chat/utils/im_tools.dart'; +import 'package:chat/widgets/custom_avatar.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +import 'package:tencent_im_sdk_plugin/models/v2_tim_conversation.dart'; + +class ImFriendRecommendFriendsPage extends StatelessWidget { + const ImFriendRecommendFriendsPage({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + PrivateConversationModel card = PrivateController.to.currentFriend.value; + + return Scaffold( + appBar: AppBar( + title: const Text('选择联系人'), + ), + body: Column( + children: [ + Container( + color: AppColors.page, + padding: const EdgeInsets.only( + left: 16, + right: 16, + bottom: 8, + ), + child: Container( + constraints: const BoxConstraints( + maxHeight: 32, + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(32), + child: TextField( + onChanged: (e) async {}, + decoration: const InputDecoration( + hintText: '搜索', + hintStyle: TextStyle( + fontSize: 14, + color: AppColors.unactive, + ), + border: InputBorder.none, + focusedBorder: InputBorder.none, + fillColor: AppColors.white, + filled: true, + contentPadding: EdgeInsets.only( + bottom: 14, + left: 16, + ), + ), + cursorColor: AppColors.primary, + ), + ), + ), + ), + InkWell( + onTap: () { + FocusScope.of(Get.context!).requestFocus(FocusNode()); + // Get.offNamed(ImRoutes.friendRecommendGroups); + }, + child: Container( + decoration: const BoxDecoration( + color: AppColors.white, + ), + width: double.infinity, + padding: const EdgeInsets.only( + left: 16, + top: 12, + bottom: 12, + ), + child: const Text( + '选择一个群', + style: TextStyle( + fontSize: 16, + ), + ), + ), + ), + Expanded( + child: GetX(builder: (_) { + return AzListView( + physics: const ClampingScrollPhysics(), + data: _.contacts, + itemCount: _.contacts.length, + itemBuilder: (__, index) { + ContactInfoModel info = _.contacts[index]; + return Column( + children: [ + ListTile( + onTap: () async { + FocusScope.of(Get.context!).requestFocus(FocusNode()); + + V2TimConversation conversation = + await TimConversationService.to.getById( + 'c2c_' + info.userID, + ); + + OkCancelResult result = await showOkCancelAlertDialog( + style: AdaptiveStyle.iOS, + context: context, + title: '发送名片', + message: '确定要发送【个人名片】至${conversation.showName}?', + okLabel: '确定', + cancelLabel: '取消', + defaultType: OkCancelAlertDefaultType.cancel, + ); + + if (result == OkCancelResult.ok) { + var model = NameCardModel( + avatar: card.userProfile?.faceUrl ?? '', + userID: card.userID, + userName: card.userProfile?.nickName ?? '', + ); + + TimConversationService.to.sendCustomMessage( + conversation, + model, + 'NAME_CARD', + ); + + Get.back(); + } + }, + tileColor: AppColors.white, + leading: CustomAvatar( + info.friendInfo!.userProfile!.faceUrl, + size: 40, + ), + title: Text(info.name), + ), + const Divider( + height: 0, + indent: 72, + ), + ], + ); + }, + susItemBuilder: (__, index) { + ContactInfoModel model = _.contacts[index]; + if ('↑' == model.getSuspensionTag()) { + return Container(); + } + return ImTools.susItem(context, model.getSuspensionTag()); + }, + indexBarData: + SuspensionUtil.getTagIndexList(_.contacts).toList(), + indexBarOptions: ImTools.indexBarOptions, + ); + }), + ), + ], + ), + ); + } +} diff --git a/lib/views/contact/firend/recommend/groups_page.dart b/lib/views/contact/firend/recommend/groups_page.dart new file mode 100644 index 0000000..64656b9 --- /dev/null +++ b/lib/views/contact/firend/recommend/groups_page.dart @@ -0,0 +1,126 @@ +import 'package:adaptive_dialog/adaptive_dialog.dart'; +import 'package:chat/configs/app_colors.dart'; +import 'package:chat/controllers/private_controller.dart'; +import 'package:chat/models/im/name_card_model.dart'; +import 'package:chat/models/im/private_conversation_model.dart'; +import 'package:chat/services/tim/conversation_service.dart'; +import 'package:chat/services/tim/group_service.dart'; +import 'package:chat/views/home/widgets/group_avatar.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:tencent_im_sdk_plugin/models/v2_tim_conversation.dart'; +import 'package:tencent_im_sdk_plugin/models/v2_tim_group_info.dart'; + +class ImFriendRecommendGroupsPage extends StatelessWidget { + const ImFriendRecommendGroupsPage({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + PrivateConversationModel card = PrivateController.to.currentFriend.value; + + return Scaffold( + appBar: AppBar( + title: const Text('选择一个群'), + ), + body: Column( + children: [ + Container( + color: AppColors.page, + padding: const EdgeInsets.only( + left: 16, + right: 16, + bottom: 8, + ), + child: Container( + constraints: const BoxConstraints( + maxHeight: 32, + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(32), + child: TextField( + onChanged: (e) async {}, + decoration: const InputDecoration( + hintText: '搜索', + hintStyle: TextStyle( + fontSize: 14, + color: AppColors.unactive, + ), + border: InputBorder.none, + focusedBorder: InputBorder.none, + fillColor: AppColors.white, + filled: true, + contentPadding: EdgeInsets.only( + bottom: 14, + left: 16, + ), + ), + cursorColor: AppColors.primary, + ), + ), + ), + ), + Expanded( + child: GetX( + builder: (_) { + return ListView.separated( + shrinkWrap: true, + physics: const ClampingScrollPhysics(), + itemBuilder: (context, index) { + V2TimGroupInfo group = _.groups[index]; + return ListTile( + onTap: () async { + FocusScope.of(Get.context!).requestFocus(FocusNode()); + + V2TimConversation conversation = + await TimConversationService.to.getById( + 'group_' + group.groupID, + ); + + OkCancelResult result = await showOkCancelAlertDialog( + style: AdaptiveStyle.iOS, + context: context, + title: '发送名片', + message: '确定要发送【个人名片】至${conversation.showName}?', + okLabel: '确定', + cancelLabel: '取消', + defaultType: OkCancelAlertDefaultType.cancel, + ); + + if (result == OkCancelResult.ok) { + var model = NameCardModel( + avatar: card.userProfile?.faceUrl ?? '', + userID: card.userID, + userName: card.userProfile?.nickName ?? '', + ); + + TimConversationService.to.sendCustomMessage( + conversation, + model, + 'NAME_CARD', + ); + + Get.back(); + } + }, + leading: GroupAvatar(group.groupID), + tileColor: AppColors.white, + title: Text(group.groupName!), + subtitle: Text('成员数: ${group.memberCount}'), + ); + }, + separatorBuilder: (context, index) { + return const Divider( + height: 0, + indent: 72, + ); + }, + itemCount: _.groups.length, + ); + }, + ), + ), + ], + ), + ); + } +} diff --git a/lib/views/contact/firend/recommend/index_page.dart b/lib/views/contact/firend/recommend/index_page.dart new file mode 100644 index 0000000..6759a7b --- /dev/null +++ b/lib/views/contact/firend/recommend/index_page.dart @@ -0,0 +1,179 @@ +import 'package:adaptive_dialog/adaptive_dialog.dart'; +import 'package:chat/configs/app_colors.dart'; +import 'package:chat/configs/app_size.dart'; +import 'package:chat/controllers/private_controller.dart'; +import 'package:chat/models/im/name_card_model.dart'; +import 'package:chat/models/im/private_conversation_model.dart'; +import 'package:chat/services/tim/conversation_service.dart'; +import 'package:chat/views/home/widgets/group_avatar.dart'; +import 'package:chat/widgets/custom_avatar.dart'; +import 'package:chat/widgets/custom_easy_refresh.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:tencent_im_sdk_plugin/enum/conversation_type.dart'; + +class ImFriendRecommendPage extends StatelessWidget { + const ImFriendRecommendPage({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + PrivateConversationModel info = PrivateController.to.currentFriend.value; + return GestureDetector( + onTap: () { + FocusScope.of(Get.context!).requestFocus(FocusNode()); + }, + child: Scaffold( + appBar: AppBar( + title: const Text( + '选择一个聊天', + ), + ), + body: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 16, + right: 16, + bottom: 8, + ), + child: Container( + constraints: const BoxConstraints( + maxHeight: 32, + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(32), + child: TextField( + onChanged: (e) async {}, + decoration: const InputDecoration( + hintText: '搜索', + hintStyle: TextStyle( + fontSize: 14, + color: AppColors.unactive, + ), + border: InputBorder.none, + focusedBorder: InputBorder.none, + fillColor: AppColors.white, + filled: true, + contentPadding: EdgeInsets.only( + bottom: 14, + left: 16, + ), + ), + cursorColor: AppColors.primary, + ), + ), + ), + ), + InkWell( + onTap: () { + FocusScope.of(Get.context!).requestFocus(FocusNode()); + // Get.offNamed(ImRoutes.friendRecommendFriends); + }, + child: Container( + decoration: const BoxDecoration( + color: AppColors.white, + ), + width: double.infinity, + padding: const EdgeInsets.only( + left: 16, + top: 12, + bottom: 12, + ), + child: const Text( + '更多联系人', + style: TextStyle( + fontSize: 16, + ), + ), + ), + ), + Container( + padding: const EdgeInsets.only( + left: 16, + top: 6, + bottom: 6, + ), + child: const Text( + '最近联系人', + style: TextStyle( + color: AppColors.unactive, + fontSize: AppSize.smallFontSize, + ), + ), + ), + Expanded( + child: GetX( + builder: (_) { + return _.conversationList.isEmpty + ? CustomEasyRefresh.empty() + : ListView.separated( + shrinkWrap: true, + physics: const ClampingScrollPhysics(), + itemCount: _.conversationList.length, + itemBuilder: (context, index) { + var conversation = _.conversationList[index]!; + return ListTile( + tileColor: AppColors.white, + onTap: () async { + FocusScope.of(Get.context!) + .requestFocus(FocusNode()); + + OkCancelResult result = + await showOkCancelAlertDialog( + style: AdaptiveStyle.iOS, + context: context, + title: '发送名片', + message: + '确定要发送【个人名片】至${conversation.showName}?', + okLabel: '确定', + cancelLabel: '取消', + defaultType: OkCancelAlertDefaultType.cancel, + ); + + if (result == OkCancelResult.ok) { + var model = NameCardModel( + avatar: info.userProfile?.faceUrl ?? '', + userID: info.userID, + userName: info.userProfile?.nickName ?? '', + ); + + TimConversationService.to.sendCustomMessage( + conversation, + model, + 'NAME_CARD', + ); + + Get.back(); + } + }, + leading: conversation.type == + ConversationType.V2TIM_C2C + ? CustomAvatar( + conversation.faceUrl, + ) + : GroupAvatar(conversation.groupID!), + title: Text( + conversation.showName!, + style: const TextStyle( + fontSize: 16, + ), + ), + ); + }, + separatorBuilder: (context, index) { + return const Divider( + height: 0, + indent: 72, + ); + }, + ); + }, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/views/contact/firend/remark/index_page.dart b/lib/views/contact/firend/remark/index_page.dart new file mode 100644 index 0000000..dc5ee8f --- /dev/null +++ b/lib/views/contact/firend/remark/index_page.dart @@ -0,0 +1,82 @@ +import 'package:chat/configs/app_colors.dart'; +import 'package:chat/controllers/private_controller.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +class ImFriendRemarkPage extends StatefulWidget { + const ImFriendRemarkPage({Key? key}) : super(key: key); + + @override + State createState() => _ImFriendRemarkPageState(); +} + +class _ImFriendRemarkPageState extends State { + final TextEditingController _controller = TextEditingController(); + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return GetX(builder: (_) { + _controller.text = _.currentFriend.value.friendRemark; + return GestureDetector( + onTap: () { + FocusScope.of(Get.context!).requestFocus(FocusNode()); + }, + child: Scaffold( + appBar: AppBar( + actions: [ + TextButton( + child: const Text('保存'), + onPressed: () async { + _.setRemark(_controller.text); + }, + ) + ], + ), + body: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + const Text( + '设置备注', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 32), + ClipRRect( + borderRadius: BorderRadius.circular(8), + child: TextField( + controller: _controller, + autofocus: true, + decoration: const InputDecoration( + labelText: '好友备注', + hintText: '', + hintStyle: TextStyle( + fontSize: 14, + color: AppColors.unactive, + ), + border: InputBorder.none, + focusedBorder: InputBorder.none, + fillColor: AppColors.white, + filled: true, + ), + cursorColor: AppColors.primary, + ), + ), + ], + ), + ), + ), + ), + ); + }); + } +} diff --git a/lib/views/contact/firend/request/apply_page.dart b/lib/views/contact/firend/request/apply_page.dart new file mode 100644 index 0000000..d4ad553 --- /dev/null +++ b/lib/views/contact/firend/request/apply_page.dart @@ -0,0 +1,118 @@ +import 'package:chat/configs/app_colors.dart'; +import 'package:chat/services/tim/friend_service.dart'; +import 'package:chat/utils/ui_tools.dart'; +import 'package:chat/widgets/custom_primary_button.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +class ImFriendRequestApplyPage extends StatefulWidget { + const ImFriendRequestApplyPage({Key? key}) : super(key: key); + + @override + State createState() => + _ImFriendRequestApplyPageState(); +} + +class _ImFriendRequestApplyPageState extends State { + late final String userID; + String _remark = ''; + String _wording = ''; + final TextEditingController _wordingController = TextEditingController(); + final TextEditingController _remarkController = TextEditingController(); + + @override + void initState() { + super.initState(); + userID = Get.arguments['userID']; + + _wordingController.text = '我是 '; + } + + @override + void dispose() { + _wordingController.dispose(); + _remarkController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('好友申请'), + ), + body: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + '发送添加朋友申请', + style: TextStyle( + color: AppColors.unactive, + ), + ), + const SizedBox(height: 4), + TextField( + controller: _wordingController, + style: const TextStyle( + fontSize: 14, + ), + decoration: InputDecoration( + border: InputBorder.none, + fillColor: AppColors.unactive.withOpacity(0.1), + filled: true, + ), + onChanged: (e) { + setState(() { + _wording = e; + }); + }, + ), + const SizedBox(height: 32), + const Text( + '设置备注', + style: TextStyle( + color: AppColors.unactive, + ), + ), + const SizedBox(height: 4), + TextField( + controller: _remarkController, + style: const TextStyle( + fontSize: 14, + ), + decoration: InputDecoration( + hintText: '', + border: InputBorder.none, + fillColor: AppColors.unactive.withOpacity(0.1), + filled: true, + ), + onChanged: (e) { + setState(() { + _remark = e; + }); + }, + ), + const SizedBox(height: 32), + CustomPrimaryButton( + text: '发送', + onPressed: () async { + var res = await TimFriendService.to.add( + userID, + remark: _remark, + addWording: _wording, + ); + + if (res) { + UiTools.toast('申请成功'); + Get.back(); + } + }, + ), + ], + ), + ), + ); + } +} diff --git a/lib/views/contact/firend/request/index_page.dart b/lib/views/contact/firend/request/index_page.dart new file mode 100644 index 0000000..31cf6ba --- /dev/null +++ b/lib/views/contact/firend/request/index_page.dart @@ -0,0 +1,293 @@ +import 'package:chat/configs/app_colors.dart'; +import 'package:chat/controllers/private_controller.dart'; +import 'package:chat/models/im/search_user_model.dart'; +import 'package:chat/routes/contact_routes.dart'; +import 'package:chat/services/tim/apply_service.dart'; +import 'package:chat/services/tim/friend_service.dart'; +import 'package:chat/widgets/custom_avatar.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:tencent_im_sdk_plugin/enum/friend_type.dart'; +import 'package:tencent_im_sdk_plugin/models/v2_tim_friend_application.dart'; +import 'package:tencent_im_sdk_plugin/models/v2_tim_friend_check_result.dart'; + +class ImFriendRequestPage extends StatefulWidget { + const ImFriendRequestPage({Key? key}) : super(key: key); + + @override + State createState() => _ImFriendRequestState(); +} + +class _ImFriendRequestState extends State { + List? searchList; + + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('新的朋友'), + bottom: _Search( + onChanged: (String e) async { + if (e.length < 3) { + setState(() { + searchList = null; + }); + } else { + var result = await TimFriendService.to.searchUser(e); + + setState(() { + searchList = result; + }); + } + }, + ), + ), + body: GestureDetector( + onTap: () { + FocusScope.of(context).requestFocus(FocusNode()); + }, + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ListView.separated( + shrinkWrap: true, + physics: const ClampingScrollPhysics(), + itemCount: searchList?.length ?? 0, + itemBuilder: (context, index) { + var user = searchList![index]; + return FutureBuilder( + future: TimFriendService.to.check(user.userID), + builder: (context, snapshot) { + V2TimFriendCheckResult? result = snapshot.data; + return ListTile( + leading: CustomAvatar(user.avatar), + title: Text(user.nickname), + onTap: () { + if (result?.resultType == 3) { + PrivateController.to.setCurrentFriend(user.userID); + Get.toNamed( + ContactRoutes.friendProfile, + ); + } + Get.toNamed( + ContactRoutes.friendApply, + arguments: { + 'userID': user.userID, + }, + ); + }, + trailing: _buildTrailing(result), + ); + }, + ); + }, + separatorBuilder: (context, index) { + return const Divider( + height: 0, + indent: 72, + ); + }, + ), + const Divider(height: 0), + const Padding( + padding: EdgeInsets.only( + left: 16, + top: 4, + ), + child: Text( + '好友申请', + style: TextStyle( + color: AppColors.unactive, + ), + ), + ), + GetX( + builder: (_) { + return ListView.separated( + shrinkWrap: true, + physics: const ClampingScrollPhysics(), + itemBuilder: (context, index) { + V2TimFriendApplication apply = _.applies[index]!; + return ListTile( + leading: CustomAvatar(apply.faceUrl), + title: Text(apply.nickname!), + subtitle: Text(apply.addWording ?? ''), + trailing: apply.type == + FriendApplicationType + .V2TIM_FRIEND_APPLICATION_COME_IN + ? InkWell( + onTap: () { + TimApplyService.to.accept(apply.userID); + }, + child: Container( + padding: const EdgeInsets.symmetric( + vertical: 4, + horizontal: 12, + ), + decoration: BoxDecoration( + color: AppColors.primary, + borderRadius: BorderRadius.circular(16), + ), + child: const Text( + '通过请求', + style: TextStyle( + color: AppColors.white, + fontSize: 12, + ), + ), + ), + ) + : const Text('已发送'), + onTap: () { + PrivateController.to.setCurrentFriend(apply.userID); + Get.toNamed( + ContactRoutes.friendProfile, + ); + }, + ); + }, + separatorBuilder: (context, index) { + return const Divider( + height: 0, + ); + }, + itemCount: _.applies.length, + ); + }, + ) + ], + ), + ), + ), + ); + } + + Widget _buildTrailing(V2TimFriendCheckResult? result) { + if (result == null) { + return const Text(''); + } + if (result.resultType == 0) { + return Container( + padding: const EdgeInsets.symmetric( + vertical: 4, + horizontal: 8, + ), + decoration: BoxDecoration( + color: AppColors.primary, + borderRadius: BorderRadius.circular(16), + ), + child: const Text( + '添加好友', + style: TextStyle( + color: AppColors.white, + fontSize: 12, + ), + ), + ); + } + if (result.resultType == 2) { + return const Text( + '已申请', + style: TextStyle( + color: AppColors.unactive, + ), + ); + } + if (result.resultType == 3) { + return const Text('已是好友'); + } + return Text(result.resultType.toString()); + } +} + +class _Search extends StatelessWidget implements PreferredSizeWidget { + final Function(String e) onChanged; + + const _Search({ + Key? key, + required this.onChanged, + }) : super(key: key); + + @override + Size get preferredSize => const Size.fromHeight(64); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Container( + constraints: const BoxConstraints( + maxHeight: 32, + ), + padding: const EdgeInsets.symmetric(horizontal: 16), + child: ClipRRect( + borderRadius: BorderRadius.circular(16), + child: TextField( + keyboardType: TextInputType.phone, + decoration: InputDecoration( + hintText: '请输入对方手机号', + hintStyle: const TextStyle( + fontSize: 14, + color: AppColors.unactive, + ), + border: InputBorder.none, + focusedBorder: InputBorder.none, + fillColor: AppColors.unactive.withOpacity(0.1), + filled: true, + prefixIcon: const Icon( + Icons.search, + color: AppColors.unactive, + size: 18, + ), + contentPadding: const EdgeInsets.only( + bottom: 14, + ), + ), + cursorColor: AppColors.primary, + onChanged: (e) async { + onChanged.call(e); + }, + ), + ), + ), + Container( + padding: const EdgeInsets.only( + top: 8, + bottom: 16, + left: 16, + right: 16, + ), + child: InkWell( + onTap: () { + Get.toNamed(ContactRoutes.friendProfile); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: const [ + Text( + '我的二维码: ', + style: TextStyle( + color: AppColors.unactive, + ), + ), + SizedBox(width: 8), + Icon( + Icons.qr_code, + size: 18, + color: AppColors.unactive, + ), + ], + ), + ), + ), + const Divider(height: 0), + ], + ); + } +} diff --git a/lib/views/contact/firend/search/index_page.dart b/lib/views/contact/firend/search/index_page.dart new file mode 100644 index 0000000..e3a32c6 --- /dev/null +++ b/lib/views/contact/firend/search/index_page.dart @@ -0,0 +1,57 @@ +import 'package:chat/configs/app_colors.dart'; +import 'package:flutter/material.dart'; + +class ImFriendSearchPage extends StatefulWidget { + const ImFriendSearchPage({Key? key}) : super(key: key); + + @override + State createState() => _ImFriendSearchPageState(); +} + +class _ImFriendSearchPageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.white, + appBar: AppBar( + title: Container( + constraints: const BoxConstraints( + maxHeight: 32, + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(32), + child: TextField( + onChanged: (e) async {}, + autofocus: true, + decoration: const InputDecoration( + hintText: '请输入搜索内容', + hintStyle: TextStyle( + fontSize: 14, + color: AppColors.unactive, + ), + border: InputBorder.none, + focusedBorder: InputBorder.none, + fillColor: AppColors.white, + filled: true, + contentPadding: EdgeInsets.only( + bottom: 14, + left: 16, + ), + ), + cursorColor: AppColors.primary, + ), + ), + ), + actions: [ + IconButton( + icon: const Icon( + Icons.search, + color: AppColors.black, + ), + onPressed: () {}, + ), + ], + ), + ); + } +} diff --git a/lib/views/contact/index/index_page.dart b/lib/views/contact/index/index_page.dart index 4eb3d21..84fef73 100644 --- a/lib/views/contact/index/index_page.dart +++ b/lib/views/contact/index/index_page.dart @@ -1,19 +1,87 @@ +import 'package:azlistview/azlistview.dart'; +import 'package:chat/configs/app_colors.dart'; +import 'package:chat/controllers/private_controller.dart'; +import 'package:chat/models/im/contact_info_model.dart'; +import 'package:chat/routes/contact_routes.dart'; +import 'package:chat/services/tim/friend_service.dart'; +import 'package:chat/utils/im_tools.dart'; +import 'package:chat/widgets/custom_avatar.dart'; import 'package:flutter/material.dart'; +import 'package:get/get.dart'; class ContactPage extends StatefulWidget { const ContactPage({Key? key}) : super(key: key); @override - _ContactPageState createState() => _ContactPageState(); + State createState() => _ContactPageState(); } class _ContactPageState extends State { + @override + void initState() { + super.initState(); + TimFriendService.to.fetchList(); + } + @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('通讯录'), - ), + return GetX( + builder: (_) { + return Scaffold( + appBar: AppBar( + title: Text('我的好友(${_.friends.length})'), + actions: [ + IconButton( + onPressed: () { + // Get.toNamed(ImRoutes.friendSearch); + }, + icon: const Icon(Icons.search_outlined), + ), + ], + ), + body: AzListView( + physics: const ClampingScrollPhysics(), + data: _.contacts, + itemCount: _.contacts.length, + itemBuilder: (__, index) { + ContactInfoModel info = _.contacts[index]; + return Column( + children: [ + ListTile( + onTap: () async { + await PrivateController.to.setCurrentFriend( + info.userID, + ); + Get.toNamed( + ContactRoutes.friendProfile, + ); + }, + tileColor: AppColors.white, + leading: CustomAvatar( + info.friendInfo!.userProfile!.faceUrl, + size: 40, + ), + title: Text(info.name), + ), + const Divider( + height: 0, + indent: 72, + ), + ], + ); + }, + susItemBuilder: (__, index) { + ContactInfoModel model = _.contacts[index]; + if ('↑' == model.getSuspensionTag()) { + return Container(); + } + return ImTools.susItem(context, model.getSuspensionTag()); + }, + indexBarData: SuspensionUtil.getTagIndexList(_.contacts).toList(), + indexBarOptions: ImTools.indexBarOptions, + ), + ); + }, ); } }