From ef7dba1cb15021d8ce4e980839befe644552fe6f Mon Sep 17 00:00:00 2001 From: houchengdong Date: Thu, 25 Jan 2024 16:07:56 +0800 Subject: [PATCH] =?UTF-8?q?feat:9.7.0=20=E5=8F=91=E5=B8=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/launch.json | 128 ----- im_demo/CHANGELOG.md | 6 + .../app/src/main/res/values/strings.xml | 2 +- im_demo/ios/Podfile | 8 + im_demo/ios/Runner/Info.plist | 2 +- im_demo/lib/src/home/home_page.dart | 23 +- im_demo/lib/src/mine/about.dart | 2 +- im_demo/pubspec.yaml | 19 +- nim_chatkit_location/CHANGELOG.md | 4 + nim_chatkit_location/README.md | 16 +- nim_chatkit_location/pubspec.lock | 44 +- nim_chatkit_location/pubspec.yaml | 16 +- nim_chatkit_ui/CHANGELOG.md | 10 + .../images/ic_chat_delete_round.svg | 14 + .../images/ic_chat_delete_round_disable.svg | 15 + .../images/ic_chat_input_expand.svg | 9 + .../images/ic_chat_item_forward.svg | 10 + .../images/ic_chat_item_forward_disable.svg | 11 + nim_chatkit_ui/images/ic_chat_lessen.svg | 10 + .../images/ic_chat_merge_forward.svg | 13 + .../images/ic_chat_merge_forward_disable.svg | 7 + nim_chatkit_ui/images/ic_chat_send.svg | 9 + nim_chatkit_ui/lib/chat_kit_client.dart | 76 ++- .../helper/chat_message_helper.dart | 239 +++++++- .../helper/chat_message_user_helper.dart | 10 +- .../lib/helper/merge_message_helper.dart | 128 +++++ .../chat_kit_client_localizations.dart | 126 ++++ .../chat_kit_client_localizations_en.dart | 79 +++ .../chat_kit_client_localizations_zh.dart | 73 +++ nim_chatkit_ui/lib/l10n/intl_en.arb | 58 +- nim_chatkit_ui/lib/l10n/intl_zh.arb | 58 +- nim_chatkit_ui/lib/media/audio_player.dart | 18 +- nim_chatkit_ui/lib/view/ait/ait_manager.dart | 41 +- .../chat_kit_message_list.dart | 46 +- .../item/chat_kit_message_audio_item.dart | 7 +- .../item/chat_kit_message_file_item.dart | 55 +- .../item/chat_kit_message_image_item.dart | 20 +- .../item/chat_kit_message_item.dart | 101 +++- .../item/chat_kit_message_merged_item.dart | 146 +++++ ...chat_kit_message_multi_line_text_item.dart | 119 ++++ .../item/chat_kit_message_notify_item.dart | 8 +- .../item/chat_kit_message_text_item.dart | 129 +---- .../item/chat_kit_message_tips_item.dart | 7 +- .../item/chat_kit_message_video_item.dart | 29 +- .../chat_kit_merged_message_item.dart | 270 +++++++++ .../pinMessage/chat_kit_pin_message_item.dart | 71 ++- .../pop_menu/chat_kit_message_pop_menu.dart | 34 +- .../widgets/chat_forward_dialog.dart | 140 +++-- .../widgets/chat_thumb_view.dart | 21 +- nim_chatkit_ui/lib/view/input/actions.dart | 5 +- .../lib/view/input/bottom_input_field.dart | 404 ++++++++++--- nim_chatkit_ui/lib/view/page/chat_page.dart | 539 ++++++++++++++---- .../lib/view/page/chat_search_page.dart | 47 +- .../lib/view/page/merged_message_page.dart | 106 ++++ .../lib/view_model/chat_pin_view_model.dart | 13 +- .../lib/view_model/chat_view_model.dart | 383 ++++++++++--- nim_chatkit_ui/pubspec.yaml | 17 +- nim_contactkit_ui/.flutter-plugins | 20 +- .../.flutter-plugins-dependencies | 2 +- nim_contactkit_ui/CHANGELOG.md | 6 + nim_contactkit_ui/pubspec.yaml | 12 +- nim_conversationkit_ui/CHANGELOG.md | 6 + ...conversation_kit_client_localizations.dart | 12 + ...versation_kit_client_localizations_en.dart | 6 + ...versation_kit_client_localizations_zh.dart | 6 + nim_conversationkit_ui/lib/l10n/intl_en.arb | 4 +- nim_conversationkit_ui/lib/l10n/intl_zh.arb | 4 +- .../view_model/conversation_view_model.dart | 39 +- .../lib/widgets/conversation_item.dart | 45 +- nim_conversationkit_ui/pubspec.yaml | 14 +- nim_searchkit_ui/.flutter-plugins | 8 +- .../.flutter-plugins-dependencies | 2 +- nim_searchkit_ui/CHANGELOG.md | 6 + nim_searchkit_ui/lib/l10n/intl_en.arb | 4 +- nim_searchkit_ui/lib/l10n/intl_zh.arb | 4 +- .../search_kit_client_localizations.dart | 12 + .../search_kit_client_localizations_en.dart | 6 + .../search_kit_client_localizations_zh.dart | 6 + .../lib/page/search_kit_search_page.dart | 18 +- nim_searchkit_ui/pubspec.yaml | 10 +- nim_teamkit_ui/.flutter-plugins | 20 +- nim_teamkit_ui/.flutter-plugins-dependencies | 2 +- nim_teamkit_ui/CHANGELOG.md | 6 + nim_teamkit_ui/images/ic_member_empty.svg | 60 ++ nim_teamkit_ui/lib/l10n/intl_en.arb | 32 +- nim_teamkit_ui/lib/l10n/intl_zh.arb | 36 +- .../team_kit_client_localizations.dart | 138 +++++ .../team_kit_client_localizations_en.dart | 74 +++ .../team_kit_client_localizations_zh.dart | 75 ++- nim_teamkit_ui/lib/team_kit_client.dart | 12 + .../pages/team_kit_avatar_editor_page.dart | 33 +- .../lib/view/pages/team_kit_manage_page.dart | 314 ++++++++++ .../pages/team_kit_manager_list_page.dart | 240 ++++++++ .../view/pages/team_kit_member_list_page.dart | 268 ++++++++- .../lib/view/pages/team_kit_setting_page.dart | 339 +++++------ .../view/pages/team_kit_team_info_page.dart | 42 +- .../view_model/team_setting_view_model.dart | 46 +- nim_teamkit_ui/pubspec.yaml | 12 +- 98 files changed, 4834 insertions(+), 1168 deletions(-) delete mode 100644 .vscode/launch.json create mode 100644 nim_chatkit_ui/images/ic_chat_delete_round.svg create mode 100644 nim_chatkit_ui/images/ic_chat_delete_round_disable.svg create mode 100644 nim_chatkit_ui/images/ic_chat_input_expand.svg create mode 100644 nim_chatkit_ui/images/ic_chat_item_forward.svg create mode 100644 nim_chatkit_ui/images/ic_chat_item_forward_disable.svg create mode 100644 nim_chatkit_ui/images/ic_chat_lessen.svg create mode 100644 nim_chatkit_ui/images/ic_chat_merge_forward.svg create mode 100644 nim_chatkit_ui/images/ic_chat_merge_forward_disable.svg create mode 100644 nim_chatkit_ui/images/ic_chat_send.svg rename nim_chatkit_ui/lib/{view/chat_kit_message_list => }/helper/chat_message_helper.dart (64%) rename nim_chatkit_ui/lib/{view/chat_kit_message_list => }/helper/chat_message_user_helper.dart (85%) create mode 100644 nim_chatkit_ui/lib/helper/merge_message_helper.dart create mode 100644 nim_chatkit_ui/lib/view/chat_kit_message_list/item/chat_kit_message_merged_item.dart create mode 100644 nim_chatkit_ui/lib/view/chat_kit_message_list/item/chat_kit_message_multi_line_text_item.dart create mode 100644 nim_chatkit_ui/lib/view/chat_kit_message_list/item/mergedMessage/chat_kit_merged_message_item.dart create mode 100644 nim_chatkit_ui/lib/view/page/merged_message_page.dart create mode 100644 nim_teamkit_ui/images/ic_member_empty.svg create mode 100644 nim_teamkit_ui/lib/view/pages/team_kit_manage_page.dart create mode 100644 nim_teamkit_ui/lib/view/pages/team_kit_manager_list_page.dart diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index fe66b26..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,128 +0,0 @@ -{ - // 使用 IntelliSense 了解相关属性。 - // 悬停以查看现有属性的描述。 - // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "name": "im_demo", - "cwd": "im_demo", - "request": "launch", - "type": "dart" - }, - { - "name": "im_demo (profile mode)", - "cwd": "im_demo", - "request": "launch", - "type": "dart", - "flutterMode": "profile" - }, - { - "name": "im_demo (release mode)", - "cwd": "im_demo", - "request": "launch", - "type": "dart", - "flutterMode": "release" - }, - { - "name": "nim_chatkit_ui", - "cwd": "nim_chatkit_ui", - "request": "launch", - "type": "dart" - }, - { - "name": "nim_chatkit_ui (profile mode)", - "cwd": "nim_chatkit_ui", - "request": "launch", - "type": "dart", - "flutterMode": "profile" - }, - { - "name": "nim_chatkit_ui (release mode)", - "cwd": "nim_chatkit_ui", - "request": "launch", - "type": "dart", - "flutterMode": "release" - }, - { - "name": "nim_contactkit_ui", - "cwd": "nim_contactkit_ui", - "request": "launch", - "type": "dart" - }, - { - "name": "nim_contactkit_ui (profile mode)", - "cwd": "nim_contactkit_ui", - "request": "launch", - "type": "dart", - "flutterMode": "profile" - }, - { - "name": "nim_contactkit_ui (release mode)", - "cwd": "nim_contactkit_ui", - "request": "launch", - "type": "dart", - "flutterMode": "release" - }, - { - "name": "nim_conversationkit_ui", - "cwd": "nim_conversationkit_ui", - "request": "launch", - "type": "dart" - }, - { - "name": "nim_conversationkit_ui (profile mode)", - "cwd": "nim_conversationkit_ui", - "request": "launch", - "type": "dart", - "flutterMode": "profile" - }, - { - "name": "nim_conversationkit_ui (release mode)", - "cwd": "nim_conversationkit_ui", - "request": "launch", - "type": "dart", - "flutterMode": "release" - }, - { - "name": "nim_searchkit_ui", - "cwd": "nim_searchkit_ui", - "request": "launch", - "type": "dart" - }, - { - "name": "nim_searchkit_ui (profile mode)", - "cwd": "nim_searchkit_ui", - "request": "launch", - "type": "dart", - "flutterMode": "profile" - }, - { - "name": "nim_searchkit_ui (release mode)", - "cwd": "nim_searchkit_ui", - "request": "launch", - "type": "dart", - "flutterMode": "release" - }, - { - "name": "nim_teamkit_ui", - "cwd": "nim_teamkit_ui", - "request": "launch", - "type": "dart" - }, - { - "name": "nim_teamkit_ui (profile mode)", - "cwd": "nim_teamkit_ui", - "request": "launch", - "type": "dart", - "flutterMode": "profile" - }, - { - "name": "nim_teamkit_ui (release mode)", - "cwd": "nim_teamkit_ui", - "request": "launch", - "type": "dart", - "flutterMode": "release" - } - ] -} \ No newline at end of file diff --git a/im_demo/CHANGELOG.md b/im_demo/CHANGELOG.md index d61449a..88627c8 100644 --- a/im_demo/CHANGELOG.md +++ b/im_demo/CHANGELOG.md @@ -1,5 +1,11 @@ # IMKit(Flutter) ChangeLog +## 9.7.0(Jan 25, 2024) +### New Features +* 新增合并转发和多选功能 +* 新增群管理 +* 新增换行消息 + ## 1.2.0(Nov 8, 2023) ### New Features * 新增@功能 diff --git a/im_demo/android/app/src/main/res/values/strings.xml b/im_demo/android/app/src/main/res/values/strings.xml index d5b055d..e639e98 100644 --- a/im_demo/android/app/src/main/res/values/strings.xml +++ b/im_demo/android/app/src/main/res/values/strings.xml @@ -3,5 +3,5 @@ - 云信IM + 云信IM(flutter) \ No newline at end of file diff --git a/im_demo/ios/Podfile b/im_demo/ios/Podfile index 696c7b0..e0b70b3 100644 --- a/im_demo/ios/Podfile +++ b/im_demo/ios/Podfile @@ -85,6 +85,14 @@ post_install do |installer| ## dart: PermissionGroup.bluetooth # 'PERMISSION_BLUETOOTH=0' ] + #https://github.com/fluttercommunity/plus_plugins/issues/2154#issuecomment-1730330223 + config.build_settings.delete 'IPHONEOS_DEPLOYMENT_TARGET' + + #https://stackoverflow.com/a/77134628/5734049 + xcconfig_path = config.base_configuration_reference.real_path + xcconfig = File.read(xcconfig_path) + xcconfig_mod = xcconfig.gsub(/DT_TOOLCHAIN_DIR/, "TOOLCHAIN_DIR") + File.open(xcconfig_path, "w") { |file| file << xcconfig_mod } end end diff --git a/im_demo/ios/Runner/Info.plist b/im_demo/ios/Runner/Info.plist index c229757..7df1c06 100644 --- a/im_demo/ios/Runner/Info.plist +++ b/im_demo/ios/Runner/Info.plist @@ -15,7 +15,7 @@ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName - 云信IM + 云信IM(flutter) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier diff --git a/im_demo/lib/src/home/home_page.dart b/im_demo/lib/src/home/home_page.dart index 597fae8..ab254c1 100644 --- a/im_demo/lib/src/home/home_page.dart +++ b/im_demo/lib/src/home/home_page.dart @@ -4,7 +4,13 @@ import 'dart:convert'; +import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:im_demo/l10n/S.dart'; +import 'package:im_demo/src/mine/mine_page.dart'; +import 'package:netease_common_ui/utils/color_utils.dart'; import 'package:netease_corekit_im/router/imkit_router_factory.dart'; import 'package:netease_corekit_im/service_locator.dart'; import 'package:netease_corekit_im/services/login/login_service.dart'; @@ -13,17 +19,11 @@ import 'package:nim_chatkit_ui/chat_kit_client.dart'; import 'package:nim_chatkit_ui/view/chat_kit_message_list/item/chat_kit_message_item.dart'; import 'package:nim_chatkit_ui/view/input/actions.dart'; import 'package:nim_chatkit_ui/view_model/chat_view_model.dart'; -import 'package:netease_common_ui/utils/color_utils.dart'; import 'package:nim_contactkit/repo/contact_repo.dart'; import 'package:nim_contactkit_ui/page/contact_page.dart'; import 'package:nim_conversationkit/repo/conversation_repo.dart'; import 'package:nim_conversationkit_ui/conversation_kit_client.dart'; import 'package:nim_conversationkit_ui/page/conversation_page.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:fluttertoast/fluttertoast.dart'; -import 'package:im_demo/l10n/S.dart'; -import 'package:im_demo/src/mine/mine_page.dart'; import 'package:nim_core/nim_core.dart'; import 'package:provider/provider.dart'; import 'package:yunxin_alog/yunxin_alog.dart'; @@ -164,11 +164,12 @@ class _HomePageState extends State { ConversationKitClient.instance.conversationUIConfig = ConversationUIConfig( itemConfig: ConversationItemConfig( lastMessageContentBuilder: (context, conversationInfo) { - if (conversationInfo.session.lastMessageType == NIMMessageType.custom) { - return S.of(context).customMessage; - } - return null; - })); + if (conversationInfo.session.lastMessageType == NIMMessageType.custom && + conversationInfo.session.lastMessageAttachment == null) { + return S.of(context).customMessage; + } + return null; + })); } @override diff --git a/im_demo/lib/src/mine/about.dart b/im_demo/lib/src/mine/about.dart index 69a8edb..74f3620 100644 --- a/im_demo/lib/src/mine/about.dart +++ b/im_demo/lib/src/mine/about.dart @@ -51,7 +51,7 @@ class AboutPage extends StatelessWidget { ), // 如果引入package_info 则不需要手动修改此处,但是如果只为了版本号引入不值得 trailing: Text( - 'V1.2.0', + 'V9.7.0', style: _style, ), ), diff --git a/im_demo/pubspec.yaml b/im_demo/pubspec.yaml index 0f64958..492264c 100644 --- a/im_demo/pubspec.yaml +++ b/im_demo/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.2.0 +version: 9.7.0 environment: sdk: ">=2.16.1 <4.0.0" @@ -32,16 +32,6 @@ dependencies: flutter_localizations: sdk: flutter - - netease_corekit_im: - ">=1.2.0 <1.3.0" -# path: ../../netease_corekit/netease_corekit_im - netease_common_ui: - ">=1.2.0 <1.3.0" -# path: ../../common/netease_common_ui - nim_conversationkit: - ">=1.2.0 <1.3.0" -# path: ../nim_conversationkit nim_conversationkit_ui: path: ../nim_conversationkit_ui nim_chatkit_ui: @@ -55,18 +45,15 @@ dependencies: # 地理位置消息支持 nim_chatkit_location: path: ../nim_chatkit_location - nim_core: - ^1.7.3 -# path: ../../nim_core/nim_core yunxin_alog: ^2.0.0 - fluttertoast: ^8.1.2 + fluttertoast: ^8.2.4 # 高德定位 amap_flutter_location: ^3.0.0 # 高德地图 amap_flutter_map: ^3.0.0 permission_handler: ^11.0.1 # 网图加载 - cached_network_image: ^3.2.3 + cached_network_image: ^3.3.1 # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. diff --git a/nim_chatkit_location/CHANGELOG.md b/nim_chatkit_location/CHANGELOG.md index db02fdc..fb59014 100644 --- a/nim_chatkit_location/CHANGELOG.md +++ b/nim_chatkit_location/CHANGELOG.md @@ -1,5 +1,9 @@ # NimChatKitLocation ChangeLog +## 9.7.0(Jan 25, 2024) +### Dependency Updates +* 升级FlutterToast 到^8.2.4 + ## 1.2.0(Nov 8, 2023) ### New Features * 位置消息功能独立模块 diff --git a/nim_chatkit_location/README.md b/nim_chatkit_location/README.md index f635a07..87ddaf1 100644 --- a/nim_chatkit_location/README.md +++ b/nim_chatkit_location/README.md @@ -1,13 +1,11 @@ -# kit.nim_chatkit_location +# nim_chatkit_location -提供位置消息功能实现(包含UI) +A new Flutter project. -### Declaring dependencies -如需添加 ChatKitUI 的依赖项,您必须将 pub 库添加到项目中。 +## Getting Started -在应用或模块的 `pubspec.yaml` 文件中添加所需工件的依赖项: +For help getting started with Flutter development, view the online +[documentation](https://flutter.dev/). -``` -dependencies: - nim_chatkit_location: ^1.0.0 -``` +For instructions integrating Flutter modules to your existing applications, +see the [add-to-app documentation](https://flutter.dev/docs/development/add-to-app). diff --git a/nim_chatkit_location/pubspec.lock b/nim_chatkit_location/pubspec.lock index 23d4338..26965c9 100644 --- a/nim_chatkit_location/pubspec.lock +++ b/nim_chatkit_location/pubspec.lock @@ -77,26 +77,26 @@ packages: dependency: "direct main" description: name: cached_network_image - sha256: f98972704692ba679db144261172a8e20feb145636c617af0eb4022132a6797f + sha256: "28ea9690a8207179c319965c13cd8df184d5ee721ae2ce60f398ced1219cea1f" url: "https://pub.dev" source: hosted - version: "3.3.0" + version: "3.3.1" cached_network_image_platform_interface: dependency: transitive description: name: cached_network_image_platform_interface - sha256: "56aa42a7a01e3c9db8456d9f3f999931f1e05535b5a424271e9a38cabf066613" + sha256: "9e90e78ae72caa874a323d78fa6301b3fb8fa7ea76a8f96dc5b5bf79f283bf2f" url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "4.0.0" cached_network_image_web: dependency: transitive description: name: cached_network_image_web - sha256: "759b9a9f8f6ccbb66c185df805fac107f05730b1dab9c64626d1008cca532257" + sha256: "42a835caa27c220d1294311ac409a43361088625a4f23c820b006dd9bffb3316" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" characters: dependency: transitive description: @@ -329,10 +329,10 @@ packages: dependency: "direct main" description: name: fluttertoast - sha256: "474f7d506230897a3cd28c965ec21c5328ae5605fc9c400cd330e9e9d6ac175c" + sha256: dfdde255317af381bfc1c486ed968d5a43a2ded9c931e87cbecd88767d6a71c1 url: "https://pub.dev" source: hosted - version: "8.2.2" + version: "8.2.4" get_it: dependency: transitive description: @@ -501,14 +501,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + netease_common: + dependency: transitive + description: + name: netease_common + sha256: "28dd98105b2cca6d79f2243a3d93dd6873a82ea637d82c4751a0045f429d3467" + url: "https://pub.dev" + source: hosted + version: "1.0.4" netease_common_ui: dependency: "direct main" description: name: netease_common_ui - sha256: f493e82b64bf2c2152f01bcf9fe7a4cbb654b2bdc733a65f58b918c30d129e38 + sha256: "3aec237bef2c9ac8a682c3c6b3148e36e88246a5a1337e1bbc9dcc40496e1e7b" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "9.7.0" netease_corekit: dependency: "direct main" description: @@ -521,26 +529,26 @@ packages: dependency: "direct main" description: name: netease_corekit_im - sha256: "3111031037b001422b3db828847cbcb1bfa674c8f2e5e459682dd5b49a41a30f" + sha256: ea0dd75686eea8e3692872c380d11358917ed49044f3f0691b18890db5b9934e url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "9.7.0" nim_chatkit: dependency: "direct main" description: name: nim_chatkit - sha256: "7947205a57b5ace20b120a40f4c55bebbdee46f1ba7d8a67e5b615374ba1dfba" + sha256: "13c8ea01c88cd36094d585a2a93e2c2fa62903b53c2433c61c9bc81be3ceebed" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "9.7.0" nim_core: dependency: "direct main" description: name: nim_core - sha256: "1258c6b4a36df39816f534a77d0af5b7707c687a8057883d4a80f3425d4c11d2" + sha256: "88a9416cde5e815b0de34d253b2aac3adbf9c7450fbafed1b8039067157e4044" url: "https://pub.dev" source: hosted - version: "1.7.3" + version: "1.7.4" nim_core_macos: dependency: transitive description: @@ -553,10 +561,10 @@ packages: dependency: transitive description: name: nim_core_platform_interface - sha256: d010cad5ef9d8ed208f780217e50382d02e20375229c99f97dd2aceef17dcd53 + sha256: "159d70a4692153b333d950855193337f140f6b4800224474ab5a915d62feabcd" url: "https://pub.dev" source: hosted - version: "1.7.0" + version: "1.7.4" nim_core_web: dependency: transitive description: diff --git a/nim_chatkit_location/pubspec.yaml b/nim_chatkit_location/pubspec.yaml index 04d3cb1..ca28f06 100644 --- a/nim_chatkit_location/pubspec.yaml +++ b/nim_chatkit_location/pubspec.yaml @@ -1,6 +1,6 @@ name: nim_chatkit_location description: Chat UI base on ChatKit. -version: 1.2.0 +version: 9.7.0 homepage: https://github.com/netease-kit/nim-uikit-flutter environment: @@ -17,23 +17,23 @@ dependencies: sdk: flutter netease_common_ui: - ">=1.2.0 <1.3.0" + ">=9.7.0 <10.0.0" # path: ../../common/netease_common_ui netease_corekit_im: - ">=1.2.0 <1.3.0" + ">=9.7.0 <10.0.0" # path: ../../netease_corekit/netease_corekit_im netease_corekit: ">=1.2.0 <1.3.0" # path: ../../netease_corekit/netease_corekit nim_chatkit: - ">=1.2.0 <1.3.0" + ">=9.7.0 <10.0.0" # path: ../nim_chatkit - cached_network_image: ^3.2.0 - fluttertoast: ^8.0.9 - nim_core: ^1.7.3 - # path: ../../nim_core/nim_core + cached_network_image: ^3.3.1 + fluttertoast: ^8.2.4 + nim_core: ^1.7.4 +# path: ../../nim_core/nim_core intl: ^0.18.0 flutter_svg: ^2.0.7 yunxin_alog: ^2.0.0 diff --git a/nim_chatkit_ui/CHANGELOG.md b/nim_chatkit_ui/CHANGELOG.md index 69fb302..d96bcbe 100644 --- a/nim_chatkit_ui/CHANGELOG.md +++ b/nim_chatkit_ui/CHANGELOG.md @@ -1,5 +1,15 @@ # NimChatKitUI ChangeLog +## 9.7.0(Jan 25, 2024) +### New Features +* 新增合并转发和多选功能 +* 新增群管理 +* 新增换行消息 + +## 1.2.0+1(Nov 13, 2023) +### Bug Fixes +* 解决鸿蒙4.0 切换扬声器和听筒的问题 + ## 1.2.0(Nov 8, 2023) ### New Features * 新增@功能 diff --git a/nim_chatkit_ui/images/ic_chat_delete_round.svg b/nim_chatkit_ui/images/ic_chat_delete_round.svg new file mode 100644 index 0000000..4021a67 --- /dev/null +++ b/nim_chatkit_ui/images/ic_chat_delete_round.svg @@ -0,0 +1,14 @@ + + + + + + + + + + diff --git a/nim_chatkit_ui/images/ic_chat_delete_round_disable.svg b/nim_chatkit_ui/images/ic_chat_delete_round_disable.svg new file mode 100644 index 0000000..ede1c95 --- /dev/null +++ b/nim_chatkit_ui/images/ic_chat_delete_round_disable.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + diff --git a/nim_chatkit_ui/images/ic_chat_input_expand.svg b/nim_chatkit_ui/images/ic_chat_input_expand.svg new file mode 100644 index 0000000..32a0282 --- /dev/null +++ b/nim_chatkit_ui/images/ic_chat_input_expand.svg @@ -0,0 +1,9 @@ + + + + + diff --git a/nim_chatkit_ui/images/ic_chat_item_forward.svg b/nim_chatkit_ui/images/ic_chat_item_forward.svg new file mode 100644 index 0000000..e4bd140 --- /dev/null +++ b/nim_chatkit_ui/images/ic_chat_item_forward.svg @@ -0,0 +1,10 @@ + + + + + + diff --git a/nim_chatkit_ui/images/ic_chat_item_forward_disable.svg b/nim_chatkit_ui/images/ic_chat_item_forward_disable.svg new file mode 100644 index 0000000..5d5f070 --- /dev/null +++ b/nim_chatkit_ui/images/ic_chat_item_forward_disable.svg @@ -0,0 +1,11 @@ + + + + + + + diff --git a/nim_chatkit_ui/images/ic_chat_lessen.svg b/nim_chatkit_ui/images/ic_chat_lessen.svg new file mode 100644 index 0000000..10015ec --- /dev/null +++ b/nim_chatkit_ui/images/ic_chat_lessen.svg @@ -0,0 +1,10 @@ + + + + + + diff --git a/nim_chatkit_ui/images/ic_chat_merge_forward.svg b/nim_chatkit_ui/images/ic_chat_merge_forward.svg new file mode 100644 index 0000000..bedb6b3 --- /dev/null +++ b/nim_chatkit_ui/images/ic_chat_merge_forward.svg @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/nim_chatkit_ui/images/ic_chat_merge_forward_disable.svg b/nim_chatkit_ui/images/ic_chat_merge_forward_disable.svg new file mode 100644 index 0000000..3a39db1 --- /dev/null +++ b/nim_chatkit_ui/images/ic_chat_merge_forward_disable.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/nim_chatkit_ui/images/ic_chat_send.svg b/nim_chatkit_ui/images/ic_chat_send.svg new file mode 100644 index 0000000..487268e --- /dev/null +++ b/nim_chatkit_ui/images/ic_chat_send.svg @@ -0,0 +1,9 @@ + + + + + diff --git a/nim_chatkit_ui/lib/chat_kit_client.dart b/nim_chatkit_ui/lib/chat_kit_client.dart index acafd44..6aff422 100644 --- a/nim_chatkit_ui/lib/chat_kit_client.dart +++ b/nim_chatkit_ui/lib/chat_kit_client.dart @@ -15,11 +15,11 @@ import 'package:nim_chatkit_ui/view/chat_kit_message_list/pop_menu/chat_kit_pop_ import 'package:nim_chatkit_ui/view/page/chat_pin_page.dart'; import 'package:nim_core/nim_core.dart'; -import 'view/page/chat_page.dart'; -import 'view/page/chat_search_page.dart'; import 'l10n/S.dart'; import 'view/chat_kit_message_list/item/chat_kit_message_item.dart'; import 'view/input/actions.dart'; +import 'view/page/chat_page.dart'; +import 'view/page/chat_search_page.dart'; typedef NIMMessageAction = Future Function(NIMMessage message); @@ -96,6 +96,9 @@ class ChatUIConfig { ///[width] 图片宽度 Widget Function(double aspectRatio, {double? width})? imagePlaceHolder; + ///是否展示头像 + bool? Function(NIMMessage message)? isShowAvatar; + ///视频消息最大size 单位M,不设置默认200 int? maxVideoSize; @@ -110,34 +113,44 @@ class ChatUIConfig { ///[text] @的文本 Function(String account, String text)? onTapAitLink; - ChatUIConfig({ - this.showTeamMessageStatus, - this.receiveMessageBg, - this.selfMessageBg, - this.showP2pMessageStatus, - this.signalBgColor, - this.timeTextColor, - this.timeTextSize, - this.messageTextSize, - this.messageTextColor, - this.userNickTextSize, - this.userNickColor, - this.avatarCornerRadius, - this.enableMessageLongPress = true, - this.popMenuConfig, - this.keepDefaultMoreAction = true, - this.moreActions, - this.messageBuilder, - this.messageClickListener, - this.getPushPayload, - this.imagePlaceHolder, - this.maxVideoSize, - this.locationProvider, - this.onTapAitLink, - this.maxFileSize, - this.keepDefaultInputAction = true, - this.inputActions, - }); + ///消息简要展示,将会显示在被回复的消息,合并转发之后的消息,以及推送内容 + ///[message] 消息 + String? Function(NIMMessage message)? getMessageBrief; + + ///展示时间的消息间隔,单位ms,默认5分钟 + int showTimeInterval; + + ChatUIConfig( + {this.showTeamMessageStatus, + this.receiveMessageBg, + this.selfMessageBg, + this.showP2pMessageStatus, + this.signalBgColor, + this.timeTextColor, + this.timeTextSize, + this.messageTextSize, + this.messageTextColor, + this.userNickTextSize, + this.userNickColor, + this.avatarCornerRadius, + this.enableMessageLongPress = true, + this.popMenuConfig, + this.keepDefaultMoreAction = true, + this.moreActions, + this.messageBuilder, + this.messageClickListener, + this.getPushPayload, + this.imagePlaceHolder, + this.maxVideoSize, + this.locationProvider, + this.onTapAitLink, + this.maxFileSize, + this.keepDefaultInputAction = true, + this.inputActions, + this.getMessageBrief, + this.showTimeInterval = 5 * 60 * 1000, + this.isShowAvatar, + this.messageLinkColor}); } ///消息点击回调 @@ -150,10 +163,13 @@ class MessageClickListener { bool Function(String? userID, {bool isSelf})? onTapAvatar; + bool Function(String? userID, {bool isSelf})? onLongPressAvatar; + MessageClickListener( {this.onMessageItemLongClick, this.onMessageItemClick, this.customPopActions, + this.onLongPressAvatar, this.onTapAvatar}); } diff --git a/nim_chatkit_ui/lib/view/chat_kit_message_list/helper/chat_message_helper.dart b/nim_chatkit_ui/lib/helper/chat_message_helper.dart similarity index 64% rename from nim_chatkit_ui/lib/view/chat_kit_message_list/helper/chat_message_helper.dart rename to nim_chatkit_ui/lib/helper/chat_message_helper.dart index 9ce62da..7f003fa 100644 --- a/nim_chatkit_ui/lib/view/chat_kit_message_list/helper/chat_message_helper.dart +++ b/nim_chatkit_ui/lib/helper/chat_message_helper.dart @@ -2,25 +2,43 @@ // Use of this source code is governed by a MIT license that can be // found in the LICENSE file. +import 'dart:math'; + +import 'package:collection/collection.dart'; import 'package:flutter/cupertino.dart'; +import 'package:flutter/gestures.dart'; +import 'package:netease_common/netease_common.dart'; import 'package:netease_common_ui/ui/dialog.dart'; import 'package:netease_common_ui/utils/color_utils.dart'; import 'package:netease_corekit_im/im_kit_client.dart'; +import 'package:netease_corekit_im/model/ait/ait_contacts_model.dart'; +import 'package:netease_corekit_im/model/ait/ait_msg.dart'; import 'package:netease_corekit_im/model/contact_info.dart'; +import 'package:netease_corekit_im/model/custom_type_constant.dart'; import 'package:netease_corekit_im/router/imkit_router_factory.dart'; +import 'package:netease_corekit_im/service_locator.dart'; import 'package:netease_corekit_im/services/contact/contact_provider.dart'; +import 'package:netease_corekit_im/services/message/chat_message.dart'; import 'package:netease_corekit_im/services/team/team_provider.dart'; +import 'package:nim_chatkit/message/message_helper.dart'; +import 'package:nim_chatkit_ui/chat_kit_client.dart'; import 'package:nim_chatkit_ui/l10n/S.dart'; -import 'package:nim_chatkit_ui/view/chat_kit_message_list/helper/chat_message_user_helper.dart'; -import 'package:netease_corekit_im/service_locator.dart'; - import 'package:nim_core/nim_core.dart'; -import '../widgets/chat_forward_dialog.dart'; +import '../view/chat_kit_message_list/item/chat_kit_message_multi_line_text_item.dart'; +import '../view/chat_kit_message_list/widgets/chat_forward_dialog.dart'; +import '../view/input/emoji.dart'; +import 'chat_message_user_helper.dart'; +import 'merge_message_helper.dart'; ///定义转发方法 +///[isLastUser] 是否是最后一个用户,用于转发给多个用户的case,主要用于合并转发和逐条转发 +///[postScript] 转发附言 +///[sessionId] 会话id +///[sessionType] 会话类型 typedef ForwardMessageFunction = Function( - String sessionId, NIMSessionType sessionType); + String sessionId, NIMSessionType sessionType, + {String? postScript, bool isLastUser}); class NotifyHelper { static Future getNotificationText(NIMMessage message) async { @@ -262,13 +280,13 @@ class NotifyHelper { static String getTeamInvitePermissionName(NIMTeamInviteModeEnum mode) { return mode == NIMTeamInviteModeEnum.all ? S.of().chatTeamPermissionInviteAll - : S.of().chatTeamPermissionInviteOnlyOwner; + : S.of().chatTeamPermissionInviteOnlyOwnerAndManagers; } static String getTeamUpdatePermissionName(NIMTeamUpdateModeEnum mode) { return mode == NIMTeamUpdateModeEnum.all ? S.of().chatTeamPermissionUpdateAll - : S.of().chatTeamPermissionUpdateOnlyOwner; + : S.of().chatTeamPermissionUpdateOnlyOwnerAndManagers; } } @@ -292,7 +310,7 @@ class ChatMessageHelper { : await getUserNickInTeam( nimMessage.sessionId!, nimMessage.fromAccount!, showAlias: false); - String content = getReplayBrief(nimMessage); + String content = getMessageBrief(nimMessage); return '$nick : $content'; } else { return S.of(context).chatMessageHaveBeenRevokedOrDelete; @@ -302,8 +320,14 @@ class ChatMessageHelper { } } - static String getReplayBrief(NIMMessage message) { + static String getMessageBrief(NIMMessage message) { String brief = 'unknown'; + var customBrief = + ChatKitClient.instance.chatUIConfig.getMessageBrief?.call(message); + if (customBrief?.isNotEmpty == true) { + brief = customBrief!; + return brief; + } switch (message.messageType) { case NIMMessageType.text: brief = message.content!; @@ -325,10 +349,24 @@ class ChatMessageHelper { break; case NIMMessageType.avchat: //todo avChat - brief = ''; + brief = S.of().chatMessageNonsupport; + break; + case NIMMessageType.custom: + var mergedMessage = MergeMessageHelper.parseMergeMessage(message); + if (mergedMessage != null) { + brief = S.of().chatMessageBriefChatHistory; + } else { + var multiLineMap = MessageHelper.parseMultiLineMessage(message); + if (multiLineMap != null && + multiLineMap[ChatMessage.keyMultiLineTitle] != null) { + brief = multiLineMap[ChatMessage.keyMultiLineTitle]!; + } else { + brief = S.of().chatMessageBriefCustom; + } + } break; default: - brief = S.of().chatMessageBriefCustom; + brief = S.of().chatMessageNonsupport; break; } return brief; @@ -337,7 +375,9 @@ class ChatMessageHelper { ///显示转发选择框 static void showForwardMessageDialog( BuildContext context, ForwardMessageFunction forwardMessage, - {List? filterUser, required String sessionName}) { + {List? filterUser, + required String sessionName, + ForwardType type = ForwardType.normal}) { // 转发 var style = const TextStyle(fontSize: 16, color: CommonColors.color_333333); showBottomChoose(context: context, actions: [ @@ -362,28 +402,36 @@ class ChatMessageHelper { ]).then((value) { if (value == 1) { _goContactSelector(context, forwardMessage, - filterUser: filterUser, sessionName: sessionName); + filterUser: filterUser, sessionName: sessionName, type: type); } else if (value == 2) { - _goTeamSelector(context, forwardMessage, sessionName: sessionName); + _goTeamSelector(context, forwardMessage, + sessionName: sessionName, type: type); } }); } //转发到群 static void _goTeamSelector( - BuildContext context, - ForwardMessageFunction forwardMessage, { - required String sessionName, - }) { - String forwardStr = S.of(context).messageForwardMessageTips(sessionName); + BuildContext context, ForwardMessageFunction forwardMessage, + {required String sessionName, ForwardType type = ForwardType.normal}) { + String forwardStr; + if (type == ForwardType.normal) { + forwardStr = S.of(context).messageForwardMessageTips(sessionName); + } else if (type == ForwardType.merge) { + forwardStr = S.of(context).messageForwardMessageMergedTips(sessionName); + } else { + forwardStr = S.of(context).messageForwardMessageOneByOneTips(sessionName); + } goTeamListPage(context, selectorModel: true).then((result) { if (result is NIMTeam) { showChatForwardDialog( context: context, contentStr: forwardStr, team: result) .then((forward) { - if (forward == true) { - forwardMessage(result.id!, NIMSessionType.team); + if (forward != null && forward.result == true) { + forwardMessage(result.id!, NIMSessionType.team, + postScript: forward.postScript, isLastUser: true); } + hideKeyboard(); }); } }); @@ -392,9 +440,19 @@ class ChatMessageHelper { //转发到个人 static void _goContactSelector( BuildContext context, ForwardMessageFunction forwardMessage, - {required String sessionName, List? filterUser}) { - String forwardStr = S.of(context).messageForwardMessageTips(sessionName); - goToContactSelector(context, filter: filterUser, returnContact: true) + {required String sessionName, + List? filterUser, + ForwardType type = ForwardType.normal}) { + String forwardStr; + if (type == ForwardType.normal) { + forwardStr = S.of(context).messageForwardMessageTips(sessionName); + } else if (type == ForwardType.merge) { + forwardStr = S.of(context).messageForwardMessageMergedTips(sessionName); + } else { + forwardStr = S.of(context).messageForwardMessageOneByOneTips(sessionName); + } + goToContactSelector(context, + filter: filterUser, returnContact: true, mostCount: 6) .then((selectedUsers) { if (selectedUsers is List) { showChatForwardDialog( @@ -402,13 +460,140 @@ class ChatMessageHelper { contentStr: forwardStr, contacts: selectedUsers) .then((result) { - if (result == true) { - for (var user in selectedUsers) { - forwardMessage(user.user.userId!, NIMSessionType.p2p); + if (result != null && result.result == true) { + for (int i = 0; i < selectedUsers.length; i++) { + var user = selectedUsers[i]; + forwardMessage(user.user.userId!, NIMSessionType.p2p, + postScript: result.postScript, + isLastUser: i == selectedUsers.length - 1); } } }); } }); } + + static Map? getMultiLineMessageMap( + {String? title, String? content}) { + if (title?.isNotEmpty == true) { + return { + CustomMessageKey.type: CustomMessageType.customMultiLineMessageType, + CustomMessageKey.data: { + ChatMessage.keyMultiLineTitle: title, + ChatMessage.keyMultiLineBody: content + } + }; + } + return null; + } + + ///解析Text消息,将@消息和普通文本分开 + static List textSpan(BuildContext context, String text, int start, + {int? end, + ChatUIConfig? chatUIConfig, + Map? remoteExtension}) { + //定义文本字体大小和颜色 + final textSize = chatUIConfig?.messageTextSize ?? 16; + final textColor = + chatUIConfig?.messageTextColor ?? CommonColors.color_333333; + final textAitColor = + chatUIConfig?.messageLinkColor ?? CommonColors.color_007aff; + + //需要返回的spans + final List spans = []; + //如果有@消息,则需要将@消息的文本和普通文本分开 + if (remoteExtension?[ChatMessage.keyAitMsg] != null) { + //获取@消息的文本list + List aitSegments = []; + //将所有@的文本和位置提取出来 + try { + var aitMap = remoteExtension![ChatMessage.keyAitMsg] as Map; + final AitContactsModel aitContactsModel = + AitContactsModel.fromMap(Map.from(aitMap)); + aitContactsModel.aitBlocks.forEach((key, value) { + var aitMsg = value as AitMsg; + aitMsg.segments.forEach((segment) { + aitSegments.add(AitItemModel(key, aitMsg.text, segment)); + }); + }); + } catch (e) { + Alog.e( + tag: 'ChatKitMessageTextItem', + content: 'aitContactsModel.fromMap error: $e'); + } + //如果没有解析到@消息,则直接返回 + if (aitSegments.isEmpty) { + spans.add(TextSpan( + text: text, + style: TextStyle(fontSize: textSize, color: textColor))); + return spans; + } + + //根据@消息的位置,将文本分成多个部分 + aitSegments.sort((a, b) => a.segment.start.compareTo(b.segment.start)); + int preIndex = start; + for (var aitItem in aitSegments) { + //@之前的部分 + if (aitItem.segment.start > preIndex) { + spans.add(TextSpan( + text: text.substring( + preIndex, min(aitItem.segment.start, text.length)), + style: TextStyle(fontSize: textSize, color: textColor))); + } + //@部分 + spans.add(TextSpan( + text: aitItem.text, + style: TextStyle(fontSize: textSize, color: textAitColor), + recognizer: TapGestureRecognizer() + ..onTap = () { + //点击@消息,如果有自定义回调,则回调,否则跳转到用户详情页 + if (chatUIConfig?.onTapAitLink != null) { + chatUIConfig?.onTapAitLink + ?.call(aitItem.account, aitItem.text); + } else if (aitItem.account != AitContactsModel.accountAll) { + if (IMKitClient.account() != aitItem.account) { + goToContactDetail(context, aitItem.account); + } else { + gotoMineInfoPage(context); + } + } + })); + preIndex = end == null + ? aitItem.segment.endIndex + : end - aitItem.segment.endIndex; + } + //最后一个@之后的部分 + if (preIndex < text.length - 1) { + spans.add(TextSpan( + text: text.substring(preIndex, text.length), + style: TextStyle(fontSize: textSize, color: textColor))); + } + } else { + //没有@消息,直接返回 + spans.add(TextSpan( + text: text, style: TextStyle(fontSize: textSize, color: textColor))); + } + return spans; + } + + ///处理文本消息中的表情 + static WidgetSpan? imageSpan(String? tag) { + var item = emojiData.firstWhereOrNull((element) => element['tag'] == tag); + if (item == null) return null; + String name = item['name'] as String; + return WidgetSpan( + child: Image.asset( + name, + package: kPackage, + height: 24, + width: 24, + ), + ); + } +} + +enum ForwardType { + normal, + oneByOne, + merge, } diff --git a/nim_chatkit_ui/lib/view/chat_kit_message_list/helper/chat_message_user_helper.dart b/nim_chatkit_ui/lib/helper/chat_message_user_helper.dart similarity index 85% rename from nim_chatkit_ui/lib/view/chat_kit_message_list/helper/chat_message_user_helper.dart rename to nim_chatkit_ui/lib/helper/chat_message_user_helper.dart index 83dbc41..e6713fd 100644 --- a/nim_chatkit_ui/lib/view/chat_kit_message_list/helper/chat_message_user_helper.dart +++ b/nim_chatkit_ui/lib/helper/chat_message_user_helper.dart @@ -19,7 +19,9 @@ extension MessageUserHelper on String { } Future getAvatar() async { - return getIt().getContact(this).then((value) { + return getIt() + .getContact(this, needFriend: false) + .then((value) { if (value != null) { return value.user.avatar; } else { @@ -53,10 +55,12 @@ Future getUserNickInTeam(String tId, String accId, } else { var teamMember = await NimCore.instance.teamService.queryTeamMember(tId, accId); - if (teamMember.data?.teamNick?.isNotEmpty == true) { + var userInfo = await getIt().getContact(accId); + if (showAlias && userInfo?.friend?.alias?.isNotEmpty == true) { + return userInfo!.friend!.alias!; + } else if (teamMember.data?.teamNick?.isNotEmpty == true) { return teamMember.data!.teamNick!; } else { - var userInfo = await getIt().getContact(accId); return userInfo?.user.nick?.isNotEmpty == true ? userInfo!.user.nick! : accId; diff --git a/nim_chatkit_ui/lib/helper/merge_message_helper.dart b/nim_chatkit_ui/lib/helper/merge_message_helper.dart new file mode 100644 index 0000000..3d519f4 --- /dev/null +++ b/nim_chatkit_ui/lib/helper/merge_message_helper.dart @@ -0,0 +1,128 @@ +// Copyright (c) 2022 NetEase, Inc. All rights reserved. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +import 'package:netease_corekit_im/model/custom_type_constant.dart'; +import 'package:netease_corekit_im/service_locator.dart'; +import 'package:netease_corekit_im/services/contact/contact_provider.dart'; +import 'package:netease_corekit_im/services/message/nim_chat_cache.dart'; +import 'package:nim_chatkit/message/merge_message.dart'; +import 'package:nim_chatkit/repo/chat_message_repo.dart'; +import 'package:nim_chatkit_ui/helper/chat_message_user_helper.dart'; +import 'package:nim_core/nim_core.dart'; + +import 'chat_message_helper.dart'; + +class MergeMessageHelper { + ///解析合并消息 + static MergedMessage? parseMergeMessage(NIMMessage message) { + if (message.messageType == NIMMessageType.custom && + message.messageAttachment is NIMCustomMessageAttachment) { + var data = (message.messageAttachment as NIMCustomMessageAttachment).data; + if (data?[CustomMessageKey.type] == + CustomMessageType.customMergeMessageType && + data?[CustomMessageKey.data] is Map) { + return MergedMessage.fromMap( + (data![CustomMessageKey.data] as Map).cast()); + } + } + return null; + } + + ///创建合并消息 + static Future> createMergedMessage( + List messages, + String sessionId, + NIMSessionType sessionType) async { + if (messages.isEmpty) { + return NIMResult.failure(message: 'message list is empty'); + } + final mergedMessage = await mergeMessage(messages); + if (mergedMessage.isSuccess && mergedMessage.data != null) { + final customMsgBuilder = await MessageBuilder.createCustomMessage( + sessionId: sessionId, + sessionType: sessionType, + attachment: NIMCustomMessageAttachment(data: mergedMessage.data!)); + if (customMsgBuilder.isSuccess && customMsgBuilder.data != null) { + customMsgBuilder.data!.pushContent = + ChatMessageHelper.getMessageBrief(customMsgBuilder.data!); + return NIMResult.success(data: customMsgBuilder.data!); + } else { + return NIMResult.failure(message: customMsgBuilder.errorDetails); + } + } else { + return NIMResult.failure(message: mergedMessage.errorDetails); + } + } + + static int getMergedMessageDepth(NIMMessage message) { + var mergeMsg = parseMergeMessage(message); + if (mergeMsg != null) { + return mergeMsg.depth ?? 0; + } + return 0; + } + + ///合并消息,返回Map + static Future>> mergeMessage( + List messageList) async { + if (messageList.isEmpty) { + return NIMResult.failure(message: 'merge message list is empty'); + } + final messages = messageList; + + if (messages.isEmpty) { + return NIMResult.failure(message: 'filtrated messages is empty'); + } + final messageUpload = + await ChatMessageRepo.uploadMergedMessageFile(messages); + if (messageUpload.isSuccess && messageUpload.data != null) { + final Map result = {}; + result[CustomMessageKey.type] = CustomMessageType.customMergeMessageType; + String url = messageUpload.data!.url; + String md5 = messageUpload.data!.md5; + var depth = 0; + String sessionId; + String sessionName; + sessionId = messages.first.sessionId!; + if (messages.first.sessionType == NIMSessionType.p2p) { + sessionName = await sessionId.getUserName(needAlias: false); + } else if (messages.first.sessionType == NIMSessionType.team) { + sessionName = NIMChatCache.instance.teamInfo?.name ?? sessionId; + } else { + sessionName = sessionId; + } + final List abstracts = List.empty(growable: true); + for (int i = 0; i < messages.length; i++) { + var message = messages[i]; + if (i < 3) { + String userAccId = message.fromAccount!; + String senderNick = (await getIt() + .getContact(userAccId, needFriend: false)) + ?.getName(needAlias: false) ?? + userAccId; + String content = ChatMessageHelper.getMessageBrief(message); + abstracts.add(MergeMessageAbstract( + senderNick: senderNick, content: content, userAccId: userAccId)); + } + var mergeMsg = parseMergeMessage(message); + if (mergeMsg != null && + mergeMsg.depth != null && + mergeMsg.depth! > depth) { + depth = mergeMsg.depth!; + } + } + depth++; + result[CustomMessageKey.data] = MergedMessage( + sessionId: sessionId, + sessionName: sessionName, + url: url, + md5: md5, + depth: depth, + abstracts: abstracts) + .toMap(); + return NIMResult.success(data: result); + } + return NIMResult.failure(message: messageUpload.errorDetails); + } +} diff --git a/nim_chatkit_ui/lib/l10n/chat_localization/chat_kit_client_localizations.dart b/nim_chatkit_ui/lib/l10n/chat_localization/chat_kit_client_localizations.dart index 8d11960..05a4d9d 100644 --- a/nim_chatkit_ui/lib/l10n/chat_localization/chat_kit_client_localizations.dart +++ b/nim_chatkit_ui/lib/l10n/chat_localization/chat_kit_client_localizations.dart @@ -383,6 +383,12 @@ abstract class ChatKitClientLocalizations { /// **'[Custom Message]'** String get chatMessageBriefCustom; + /// No description provided for @chatMessageBriefChatHistory. + /// + /// In en, this message translates to: + /// **'[Chat History]'** + String get chatMessageBriefChatHistory; + /// No description provided for @chatSetting. /// /// In en, this message translates to: @@ -778,6 +784,126 @@ abstract class ChatKitClientLocalizations { /// In en, this message translates to: /// **'No pinned message'** String get chatHaveNoPinMessage; + + /// No description provided for @chatMessageMergeForward. + /// + /// In en, this message translates to: + /// **'Merge Forward'** + String get chatMessageMergeForward; + + /// No description provided for @chatMessageItemsForward. + /// + /// In en, this message translates to: + /// **'Items Forward'** + String get chatMessageItemsForward; + + /// No description provided for @chatMessageMergedTitle. + /// + /// In en, this message translates to: + /// **'{user}History Message'** + String chatMessageMergedTitle(String user); + + /// No description provided for @chatMessageChatHistory. + /// + /// In en, this message translates to: + /// **'Chat History'** + String get chatMessageChatHistory; + + /// No description provided for @chatMessagePostScript. + /// + /// In en, this message translates to: + /// **'Post Script'** + String get chatMessagePostScript; + + /// No description provided for @chatMessageMergeMessageError. + /// + /// In en, this message translates to: + /// **'System Error,Forward Error'** + String get chatMessageMergeMessageError; + + /// No description provided for @chatMessageInputTitle. + /// + /// In en, this message translates to: + /// **'Input Title'** + String get chatMessageInputTitle; + + /// No description provided for @chatMessageNotSupportEmptyMessage. + /// + /// In en, this message translates to: + /// **'Not support empty message'** + String get chatMessageNotSupportEmptyMessage; + + /// No description provided for @chatMessageMergedForwardLimitOut. + /// + /// In en, this message translates to: + /// **'Merge message limit{number}'** + String chatMessageMergedForwardLimitOut(String number); + + /// No description provided for @chatMessageForwardOneByOneLimitOut. + /// + /// In en, this message translates to: + /// **'Forward message one by one limit{number}'** + String chatMessageForwardOneByOneLimitOut(String number); + + /// No description provided for @messageForwardMessageOneByOneTips. + /// + /// In en, this message translates to: + /// **'[Forward One by One]{user}Chat History'** + String messageForwardMessageOneByOneTips(String user); + + /// No description provided for @messageForwardMessageMergedTips. + /// + /// In en, this message translates to: + /// **'[Merged Forward]{user}Chat History'** + String messageForwardMessageMergedTips(String user); + + /// No description provided for @chatMessageHaveMessageCantForward. + /// + /// In en, this message translates to: + /// **'There are messages that cannot be forwarded'** + String get chatMessageHaveMessageCantForward; + + /// No description provided for @chatMessageInfoError. + /// + /// In en, this message translates to: + /// **'Message info error'** + String get chatMessageInfoError; + + /// No description provided for @chatMessageMergeDepthOut. + /// + /// In en, this message translates to: + /// **'The message exceeds the merging limit and cannot be forwarded as is. Should it be sent without merging?'** + String get chatMessageMergeDepthOut; + + /// No description provided for @chatMessageExitMessageCannotForward. + /// + /// In en, this message translates to: + /// **'Exit message cannot be forwarded'** + String get chatMessageExitMessageCannotForward; + + /// No description provided for @chatTeamPermissionInviteOnlyOwnerAndManagers. + /// + /// In en, this message translates to: + /// **'Owner and managers'** + String get chatTeamPermissionInviteOnlyOwnerAndManagers; + + /// No description provided for @chatTeamPermissionUpdateOnlyOwnerAndManagers. + /// + /// In en, this message translates to: + /// **'Owner and managers'** + String get chatTeamPermissionUpdateOnlyOwnerAndManagers; + + /// No description provided for @chatTeamHaveBeenKick. + /// + /// In en, this message translates to: + /// **'You have been kicked'** + String get chatTeamHaveBeenKick; + + /// No description provided for @chatMessageHaveCannotForwardMessages. + /// + /// In en, this message translates to: + /// **'There are messages that cannot be forwarded'** + String get chatMessageHaveCannotForwardMessages; } class _ChatKitClientLocalizationsDelegate diff --git a/nim_chatkit_ui/lib/l10n/chat_localization/chat_kit_client_localizations_en.dart b/nim_chatkit_ui/lib/l10n/chat_localization/chat_kit_client_localizations_en.dart index 19c0526..ccf97ec 100644 --- a/nim_chatkit_ui/lib/l10n/chat_localization/chat_kit_client_localizations_en.dart +++ b/nim_chatkit_ui/lib/l10n/chat_localization/chat_kit_client_localizations_en.dart @@ -159,6 +159,9 @@ class ChatKitClientLocalizationsEn extends ChatKitClientLocalizations { @override String get chatMessageBriefCustom => '[Custom Message]'; + @override + String get chatMessageBriefChatHistory => '[Chat History]'; + @override String get chatSetting => 'Chat setting'; @@ -417,4 +420,80 @@ class ChatKitClientLocalizationsEn extends ChatKitClientLocalizations { @override String get chatHaveNoPinMessage => 'No pinned message'; + + @override + String get chatMessageMergeForward => 'Merge Forward'; + + @override + String get chatMessageItemsForward => 'Items Forward'; + + @override + String chatMessageMergedTitle(String user) { + return '${user}History Message'; + } + + @override + String get chatMessageChatHistory => 'Chat History'; + + @override + String get chatMessagePostScript => 'Post Script'; + + @override + String get chatMessageMergeMessageError => 'System Error,Forward Error'; + + @override + String get chatMessageInputTitle => 'Input Title'; + + @override + String get chatMessageNotSupportEmptyMessage => 'Not support empty message'; + + @override + String chatMessageMergedForwardLimitOut(String number) { + return 'Merge message limit$number'; + } + + @override + String chatMessageForwardOneByOneLimitOut(String number) { + return 'Forward message one by one limit$number'; + } + + @override + String messageForwardMessageOneByOneTips(String user) { + return '[Forward One by One]${user}Chat History'; + } + + @override + String messageForwardMessageMergedTips(String user) { + return '[Merged Forward]${user}Chat History'; + } + + @override + String get chatMessageHaveMessageCantForward => + 'There are messages that cannot be forwarded'; + + @override + String get chatMessageInfoError => 'Message info error'; + + @override + String get chatMessageMergeDepthOut => + 'The message exceeds the merging limit and cannot be forwarded as is. Should it be sent without merging?'; + + @override + String get chatMessageExitMessageCannotForward => + 'Exit message cannot be forwarded'; + + @override + String get chatTeamPermissionInviteOnlyOwnerAndManagers => + 'Owner and managers'; + + @override + String get chatTeamPermissionUpdateOnlyOwnerAndManagers => + 'Owner and managers'; + + @override + String get chatTeamHaveBeenKick => 'You have been kicked'; + + @override + String get chatMessageHaveCannotForwardMessages => + 'There are messages that cannot be forwarded'; } diff --git a/nim_chatkit_ui/lib/l10n/chat_localization/chat_kit_client_localizations_zh.dart b/nim_chatkit_ui/lib/l10n/chat_localization/chat_kit_client_localizations_zh.dart index c9a9d59..dd10268 100644 --- a/nim_chatkit_ui/lib/l10n/chat_localization/chat_kit_client_localizations_zh.dart +++ b/nim_chatkit_ui/lib/l10n/chat_localization/chat_kit_client_localizations_zh.dart @@ -157,6 +157,9 @@ class ChatKitClientLocalizationsZh extends ChatKitClientLocalizations { @override String get chatMessageBriefCustom => '[自定义消息]'; + @override + String get chatMessageBriefChatHistory => '[聊天记录]'; + @override String get chatSetting => '聊天设置'; @@ -413,4 +416,74 @@ class ChatKitClientLocalizationsZh extends ChatKitClientLocalizations { @override String get chatHaveNoPinMessage => '暂无标记消息'; + + @override + String get chatMessageMergeForward => '合并转发'; + + @override + String get chatMessageItemsForward => '逐条转发'; + + @override + String chatMessageMergedTitle(String user) { + return '$user的消息'; + } + + @override + String get chatMessageChatHistory => '聊天记录'; + + @override + String get chatMessagePostScript => '留言'; + + @override + String get chatMessageMergeMessageError => '系统异常,转发失败'; + + @override + String get chatMessageInputTitle => '请输入标题'; + + @override + String get chatMessageNotSupportEmptyMessage => '不支持发送空消息'; + + @override + String chatMessageMergedForwardLimitOut(String number) { + return '合并转发限制$number条消息'; + } + + @override + String chatMessageForwardOneByOneLimitOut(String number) { + return '逐条转发限制$number条消息'; + } + + @override + String messageForwardMessageOneByOneTips(String user) { + return '[逐条转发]$user的会话记录'; + } + + @override + String messageForwardMessageMergedTips(String user) { + return '[合并转发]$user的会话记录'; + } + + @override + String get chatMessageHaveMessageCantForward => '存在不可转发的消息体'; + + @override + String get chatMessageInfoError => '信息获取失败'; + + @override + String get chatMessageMergeDepthOut => '存在超出合并限制的消息,无法合并转发,是否去除后发送?'; + + @override + String get chatMessageExitMessageCannotForward => '存在不可转发的消息体'; + + @override + String get chatTeamPermissionInviteOnlyOwnerAndManagers => '群主和管理员'; + + @override + String get chatTeamPermissionUpdateOnlyOwnerAndManagers => '群主和管理员'; + + @override + String get chatTeamHaveBeenKick => '您已被移除群聊'; + + @override + String get chatMessageHaveCannotForwardMessages => '存在不可转发的消息体,是否去除后发送?'; } diff --git a/nim_chatkit_ui/lib/l10n/intl_en.arb b/nim_chatkit_ui/lib/l10n/intl_en.arb index bb60bbd..34ad4ee 100644 --- a/nim_chatkit_ui/lib/l10n/intl_en.arb +++ b/nim_chatkit_ui/lib/l10n/intl_en.arb @@ -75,6 +75,7 @@ "chatMessageBriefLocation": "[Location]", "chatMessageBriefFile": "[File]", "chatMessageBriefCustom": "[Custom Message]", + "chatMessageBriefChatHistory": "[Chat History]", "chatSetting": "Chat setting", "chatMessageSignal": "Message mark", "chatMessageOpenMessageNotice": "Open message notice", @@ -367,5 +368,60 @@ "chatTeamBeRemovedTitle": "Tip", "chatTeamBeRemovedContent": "This group is disbanded", "chatMessageSendFailedByBlackList": "Send failed, you are in the blacklist", - "chatHaveNoPinMessage": "No pinned message" + "chatHaveNoPinMessage": "No pinned message", + "chatMessageMergeForward": "Merge Forward", + "chatMessageItemsForward": "Items Forward", + "chatMessageMergedTitle": "{user}History Message", + "@chatMessageMergedTitle": { + "placeholders": { + "user": { + "type": "String" + } + } + }, + "chatMessageChatHistory": "Chat History", + "chatMessagePostScript": "Post Script", + "chatMessageMergeMessageError": "System Error,Forward Error", + "chatMessageInputTitle": "Input Title", + "chatMessageNotSupportEmptyMessage": "Not support empty message", + "chatMessageMergedForwardLimitOut": "Merge message limit{number}", + "@chatMessageMergedForwardLimitOut": { + "placeholders": { + "number": { + "type": "String" + } + } + }, + "chatMessageForwardOneByOneLimitOut": "Forward message one by one limit{number}", + "@chatMessageForwardOneByOneLimitOut": { + "placeholders": { + "number": { + "type": "String" + } + } + }, + "messageForwardMessageOneByOneTips": "[Forward One by One]{user}Chat History", + "@messageForwardMessageOneByOneTips": { + "placeholders": { + "user": { + "type": "String" + } + } + }, + "messageForwardMessageMergedTips": "[Merged Forward]{user}Chat History", + "@messageForwardMessageMergedTips": { + "placeholders": { + "user": { + "type": "String" + } + } + }, + "chatMessageHaveMessageCantForward": "There are messages that cannot be forwarded", + "chatMessageInfoError": "Message info error", + "chatMessageMergeDepthOut": "The message exceeds the merging limit and cannot be forwarded as is. Should it be sent without merging?", + "chatMessageExitMessageCannotForward": "Exit message cannot be forwarded", + "chatTeamPermissionInviteOnlyOwnerAndManagers": "Owner and managers", + "chatTeamPermissionUpdateOnlyOwnerAndManagers": "Owner and managers", + "chatTeamHaveBeenKick": "You have been kicked", + "chatMessageHaveCannotForwardMessages": "There are messages that cannot be forwarded" } \ No newline at end of file diff --git a/nim_chatkit_ui/lib/l10n/intl_zh.arb b/nim_chatkit_ui/lib/l10n/intl_zh.arb index 3e8350c..fd93c7a 100644 --- a/nim_chatkit_ui/lib/l10n/intl_zh.arb +++ b/nim_chatkit_ui/lib/l10n/intl_zh.arb @@ -75,6 +75,7 @@ "chatMessageBriefLocation": "[地理位置]", "chatMessageBriefFile": "[文件]", "chatMessageBriefCustom": "[自定义消息]", + "chatMessageBriefChatHistory": "[聊天记录]", "chatSetting": "聊天设置", "chatMessageSignal": "标记", "chatMessageOpenMessageNotice": "开启消息提醒", @@ -367,5 +368,60 @@ "chatTeamBeRemovedTitle": "提醒", "chatTeamBeRemovedContent": "该群聊已被解散", "chatMessageSendFailedByBlackList":"对方已将您拉黑,消息发送失败", - "chatHaveNoPinMessage": "暂无标记消息" + "chatHaveNoPinMessage": "暂无标记消息", + "chatMessageMergeForward": "合并转发", + "chatMessageItemsForward": "逐条转发", + "chatMessageMergedTitle": "{user}的消息", + "@chatMessageMergedTitle": { + "placeholders": { + "user": { + "type": "String" + } + } + }, + "chatMessageChatHistory": "聊天记录", + "chatMessagePostScript": "留言", + "chatMessageMergeMessageError": "系统异常,转发失败", + "chatMessageInputTitle": "请输入标题", + "chatMessageNotSupportEmptyMessage": "不支持发送空消息", + "chatMessageMergedForwardLimitOut": "合并转发限制{number}条消息", + "@chatMessageMergedForwardLimitOut": { + "placeholders": { + "number": { + "type": "String" + } + } + }, + "chatMessageForwardOneByOneLimitOut": "逐条转发限制{number}条消息", + "@chatMessageForwardOneByOneLimitOut": { + "placeholders": { + "number": { + "type": "String" + } + } + }, + "messageForwardMessageOneByOneTips": "[逐条转发]{user}的会话记录", + "@messageForwardMessageOneByOneTips": { + "placeholders": { + "user": { + "type": "String" + } + } + }, + "messageForwardMessageMergedTips": "[合并转发]{user}的会话记录", + "@messageForwardMessageMergedTips": { + "placeholders": { + "user": { + "type": "String" + } + } + }, + "chatMessageHaveMessageCantForward": "存在不可转发的消息体", + "chatMessageInfoError": "信息获取失败", + "chatMessageMergeDepthOut": "存在超出合并限制的消息,无法合并转发,是否去除后发送?", + "chatMessageExitMessageCannotForward": "存在不可转发的消息体", + "chatTeamPermissionInviteOnlyOwnerAndManagers": "群主和管理员", + "chatTeamPermissionUpdateOnlyOwnerAndManagers": "群主和管理员", + "chatTeamHaveBeenKick": "您已被移除群聊", + "chatMessageHaveCannotForwardMessages": "存在不可转发的消息体,是否去除后发送?" } \ No newline at end of file diff --git a/nim_chatkit_ui/lib/media/audio_player.dart b/nim_chatkit_ui/lib/media/audio_player.dart index 3103e03..776c1b8 100644 --- a/nim_chatkit_ui/lib/media/audio_player.dart +++ b/nim_chatkit_ui/lib/media/audio_player.dart @@ -58,15 +58,15 @@ class ChatAudioPlayer { } Future play( - String id, - Source source, { - required StopAction stopAction, - double? volume, - double? balance, - AudioContext? ctx, - Duration? position, - PlayerMode? mode, - }) async { + String id, + Source source, { + required StopAction stopAction, + double? volume, + double? balance, + AudioContext? ctx, + Duration? position, + PlayerMode? mode, + }) async { _setupSpeaker(); //回掉之前的停止操作 _stopAction?.call(); diff --git a/nim_chatkit_ui/lib/view/ait/ait_manager.dart b/nim_chatkit_ui/lib/view/ait/ait_manager.dart index 2153d52..c2d4ba4 100644 --- a/nim_chatkit_ui/lib/view/ait/ait_manager.dart +++ b/nim_chatkit_ui/lib/view/ait/ait_manager.dart @@ -57,8 +57,8 @@ class AitManager { bool aitEnd(String text) { int len = text.length; for (var element in _aitContactsModel.aitBlocks.values) { - for (var segment in element.segments) { - if (segment.start < len && segment.end >= len) { + for (AitSegment segment in element.segments) { + if (segment.start < len && segment.endIndex >= len) { return true; } } @@ -112,10 +112,10 @@ class AitManager { ///光标移动到@后自动到后面 int resetAitCursor(int baseIndex) { - for (var element in _aitContactsModel.aitBlocks.values) { - for (var segment in element.segments) { - if (segment.start < baseIndex && segment.end > baseIndex) { - return segment.end; + for (AitMsg element in _aitContactsModel.aitBlocks.values) { + for (AitSegment segment in element.segments) { + if (segment.start < baseIndex && segment.endIndex + 1 > baseIndex) { + return segment.endIndex + 1; } } } @@ -164,18 +164,19 @@ class AitManager { ], ), ), - ListTile( - leading: SvgPicture.asset( - 'images/ic_team_all.svg', - package: kPackage, - height: 42, - width: 42, + if (NIMChatCache.instance.haveAitAllPrivilege()) + ListTile( + leading: SvgPicture.asset( + 'images/ic_team_all.svg', + package: kPackage, + height: 42, + width: 42, + ), + title: Text(S.of(context).chatTeamAitAll), + onTap: () { + Navigator.pop(context, AitContactsModel.accountAll); + }, ), - title: Text(S.of(context).chatTeamAitAll), - onTap: () { - Navigator.pop(context, AitContactsModel.accountAll); - }, - ), if (_teamMembers?.isNotEmpty == true) Expanded( child: ListView.builder( @@ -190,7 +191,11 @@ class AitManager { height: 42, width: 42, ), - title: Text(user.getName()), + title: Text( + user.getName(), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), onTap: () { Navigator.pop(context, user); }, diff --git a/nim_chatkit_ui/lib/view/chat_kit_message_list/chat_kit_message_list.dart b/nim_chatkit_ui/lib/view/chat_kit_message_list/chat_kit_message_list.dart index 7748d18..0df93e6 100644 --- a/nim_chatkit_ui/lib/view/chat_kit_message_list/chat_kit_message_list.dart +++ b/nim_chatkit_ui/lib/view/chat_kit_message_list/chat_kit_message_list.dart @@ -2,23 +2,24 @@ // Use of this source code is governed by a MIT license that can be // found in the LICENSE file. -import 'package:netease_common_ui/widgets/neListView/size_cache_widget.dart'; -import 'package:netease_corekit_im/router/imkit_router.dart'; -import 'package:nim_chatkit_ui/l10n/S.dart'; -import 'package:nim_chatkit_ui/view/chat_kit_message_list/helper/chat_message_helper.dart'; -import 'package:nim_chatkit_ui/view/chat_kit_message_list/pop_menu/chat_kit_pop_actions.dart'; import 'package:collection/collection.dart'; -import 'package:netease_common_ui/ui/dialog.dart'; -import 'package:netease_corekit_im/services/message/chat_message.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:fluttertoast/fluttertoast.dart'; +import 'package:netease_common/netease_common.dart'; +import 'package:netease_common_ui/ui/dialog.dart'; +import 'package:netease_common_ui/widgets/neListView/size_cache_widget.dart'; +import 'package:netease_corekit_im/router/imkit_router.dart'; +import 'package:netease_corekit_im/services/message/chat_message.dart'; +import 'package:nim_chatkit/message/message_helper.dart'; +import 'package:nim_chatkit_ui/l10n/S.dart'; +import 'package:nim_chatkit_ui/view/chat_kit_message_list/pop_menu/chat_kit_pop_actions.dart'; import 'package:nim_core/nim_core.dart'; import 'package:provider/provider.dart'; import 'package:scroll_to_index/scroll_to_index.dart'; -import 'package:yunxin_alog/yunxin_alog.dart'; import '../../chat_kit_client.dart'; +import '../../helper/chat_message_helper.dart'; import '../../view_model/chat_view_model.dart'; import 'item/chat_kit_message_item.dart'; @@ -78,15 +79,24 @@ class ChatKitMessageListState extends State customActions!.onMessageCopy!(message)) { return true; } - if (message.nimMessage.content?.isNotEmpty == true) { + if (message.nimMessage.messageType == NIMMessageType.text && + message.nimMessage.content?.isNotEmpty == true) { Clipboard.setData(ClipboardData(text: message.nimMessage.content!)); Fluttertoast.showToast(msg: S.of().chatMessageCopySuccess); return true; } + var multiLineMap = MessageHelper.parseMultiLineMessage(message.nimMessage); + if (multiLineMap != null) { + var title = multiLineMap[ChatMessage.keyMultiLineTitle] as String; + var content = multiLineMap[ChatMessage.keyMultiLineBody]; + Clipboard.setData(ClipboardData(text: content ?? title)); + Fluttertoast.showToast(msg: S.of().chatMessageCopySuccess); + return true; + } return false; } - _scrollToIndex(String uuid) { + _scrollToMessageByUUID(String uuid) { var index = context .read() .messageList @@ -166,11 +176,11 @@ class ChatKitMessageListState extends State context.read().sessionType == NIMSessionType.p2p ? [context.read().sessionId] : null; - ChatMessageHelper.showForwardMessageDialog(context, - (sessionId, sessionType) { - context - .read() - .forwardMessage(message.nimMessage, sessionId, sessionType); + ChatMessageHelper.showForwardMessageDialog(context, (sessionId, sessionType, + {String? postScript, bool? isLastUser}) { + context.read().forwardMessage( + message.nimMessage, sessionId, sessionType, + postScript: postScript); }, filterUser: filterUser, sessionName: sessionName); return true; } @@ -190,7 +200,9 @@ class ChatKitMessageListState extends State } bool _onMessageMultiSelect(ChatMessage message) { - ///todo implement + context.read().isMultiSelected = true; + context.read().addSelectedMessage(message.nimMessage); + hideKeyboard(); return true; } @@ -381,7 +393,7 @@ class ChatKitMessageListState extends State lastMessage: lastMessage, popMenuAction: getDefaultPopMenuActions(widget.popMenuAction), - scrollToIndex: _scrollToIndex, + scrollToIndex: _scrollToMessageByUUID, onTapFailedMessage: _resendMessage, onTapAvatar: widget.onTapAvatar, onAvatarLongPress: widget.onAvatarLongPress, diff --git a/nim_chatkit_ui/lib/view/chat_kit_message_list/item/chat_kit_message_audio_item.dart b/nim_chatkit_ui/lib/view/chat_kit_message_list/item/chat_kit_message_audio_item.dart index 77f0d8b..63f6c14 100644 --- a/nim_chatkit_ui/lib/view/chat_kit_message_list/item/chat_kit_message_audio_item.dart +++ b/nim_chatkit_ui/lib/view/chat_kit_message_list/item/chat_kit_message_audio_item.dart @@ -7,13 +7,13 @@ import 'dart:io'; import 'dart:math'; import 'package:audioplayers/audioplayers.dart'; -import 'package:netease_common_ui/utils/color_utils.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:netease_common_ui/utils/color_utils.dart'; import 'package:netease_corekit_im/router/imkit_router.dart'; import 'package:nim_core/nim_core.dart'; -import 'package:phone_state/phone_state.dart'; import 'package:permission_handler/permission_handler.dart'; +import 'package:phone_state/phone_state.dart'; import '../../../chat_kit_client.dart'; import '../../../media/audio_player.dart'; @@ -55,7 +55,7 @@ class ChatKitMessageAudioState extends State double _getWidth(NIMMessage message) { int dur = _getAudioLen(message); - double baseLen = 77.0; + double baseLen = 78.0; double maxLen = 265.0; if (dur <= 2) { return baseLen; @@ -225,6 +225,7 @@ class ChatKitMessageAudioState extends State if (value != null) { var attachment = widget.message.messageAttachment as NIMAudioAttachment; var durLast = attachment.duration! - value.inMilliseconds; + isPlaying = true; _startPlayAni(durLast); } }); diff --git a/nim_chatkit_ui/lib/view/chat_kit_message_list/item/chat_kit_message_file_item.dart b/nim_chatkit_ui/lib/view/chat_kit_message_list/item/chat_kit_message_file_item.dart index c84adf9..70f6e58 100644 --- a/nim_chatkit_ui/lib/view/chat_kit_message_list/item/chat_kit_message_file_item.dart +++ b/nim_chatkit_ui/lib/view/chat_kit_message_list/item/chat_kit_message_file_item.dart @@ -5,14 +5,16 @@ import 'dart:async'; import 'dart:io'; +import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; -import 'package:netease_common_ui/utils/color_utils.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:netease_common_ui/utils/color_utils.dart'; import 'package:nim_chatkit/repo/chat_message_repo.dart'; import 'package:nim_chatkit/repo/chat_service_observer_repo.dart'; import 'package:nim_chatkit_ui/media/audio_player.dart'; import 'package:nim_core/nim_core.dart'; import 'package:open_filex/open_filex.dart'; +import 'package:path_provider/path_provider.dart'; import 'package:yunxin_alog/yunxin_alog.dart'; import '../../../chat_kit_client.dart'; @@ -138,7 +140,11 @@ const support_type_map_android = { class ChatKitMessageFileItem extends StatefulWidget { final NIMMessage message; - const ChatKitMessageFileItem({Key? key, required this.message}) + ///独立的文件,比如合并转发后的文件 + final bool independentFile; + + const ChatKitMessageFileItem( + {Key? key, required this.message, this.independentFile = false}) : super(key: key); @override @@ -161,6 +167,7 @@ class ChatKitMessageFileState extends State { @override void initState() { super.initState(); + processStreamSub = ChatServiceObserverRepo.observeAttachmentProgress().listen((event) { if (event.id == widget.message.uuid) { @@ -262,26 +269,48 @@ class ChatKitMessageFileState extends State { @override Widget build(BuildContext context) { return GestureDetector( - onTap: () { - if (attachment.path == null || !File(attachment.path!).existsSync()) { + onTap: () async { + String filePath; + if (widget.independentFile) { + var directory; + if (Platform.isIOS) { + directory = await getApplicationDocumentsDirectory(); + } else { + directory = await getExternalStorageDirectory(); + } + filePath = + '${directory?.path}/${widget.message.uuid}/${attachment.displayName}'; + } else { + filePath = attachment.path ?? ''; + } + if (!File(filePath).existsSync()) { processValue = 0.0; - ChatMessageRepo.downloadAttachment( - message: widget.message, thumb: false) - .then((value) => { - Alog.d( - tag: 'ChatKitMessageFileItem', - content: 'downloadAttachment result is $value') - }); + if (widget.independentFile) { + Dio().download(attachment.url!, filePath, + onReceiveProgress: (count, total) { + processValue = count / total; + processVisible = (processValue ?? 0) < 1.0; + setState(() {}); + }); + } else { + ChatMessageRepo.downloadAttachment( + message: widget.message, thumb: false) + .then((value) => { + Alog.d( + tag: 'ChatKitMessageFileItem', + content: 'downloadAttachment result is $value') + }); + } } else { if (_needAudioFocus()) { ChatAudioPlayer.instance.stopAll(); } if (Platform.isAndroid) { - OpenFilex.open(attachment.path, + OpenFilex.open(filePath, type: support_type_map_android[ attachment.extension?.toLowerCase()]); } else { - OpenFilex.open(attachment.path); + OpenFilex.open(filePath); } } }, diff --git a/nim_chatkit_ui/lib/view/chat_kit_message_list/item/chat_kit_message_image_item.dart b/nim_chatkit_ui/lib/view/chat_kit_message_list/item/chat_kit_message_image_item.dart index 050e5e8..e39d235 100644 --- a/nim_chatkit_ui/lib/view/chat_kit_message_list/item/chat_kit_message_image_item.dart +++ b/nim_chatkit_ui/lib/view/chat_kit_message_list/item/chat_kit_message_image_item.dart @@ -2,9 +2,9 @@ // Use of this source code is governed by a MIT license that can be // found in the LICENSE file. +import 'package:flutter/material.dart'; import 'package:nim_chatkit_ui/media/picture.dart'; import 'package:nim_chatkit_ui/view/chat_kit_message_list/widgets/chat_thumb_view.dart'; -import 'package:flutter/material.dart'; import 'package:nim_core/nim_core.dart'; import 'package:provider/provider.dart'; @@ -13,10 +13,16 @@ import '../../../view_model/chat_view_model.dart'; class ChatKitMessageImageItem extends StatefulWidget { final NIMMessage message; - final bool isPin; + final bool showOneImage; + + //是否显示方向 + final bool showDirection; const ChatKitMessageImageItem( - {Key? key, required this.message, this.isPin = false}) + {Key? key, + required this.message, + this.showOneImage = false, + this.showDirection = true}) : super(key: key); @override @@ -38,13 +44,15 @@ class ChatKitMessageImageState extends State { return ChatThumbView( message: widget.message, radius: BorderRadius.only( - topLeft: Radius.circular(_isReceive ? 0 : 12), - topRight: Radius.circular(_isReceive ? 12 : 0), + topLeft: + Radius.circular((!widget.showDirection || _isReceive) ? 0 : 12), + topRight: + Radius.circular((!widget.showDirection || _isReceive) ? 12 : 0), bottomLeft: const Radius.circular(12), bottomRight: const Radius.circular(12)), onTap: () { List messagesList; - if (!widget.isPin) { + if (!widget.showOneImage) { messagesList = context .read() .messageList diff --git a/nim_chatkit_ui/lib/view/chat_kit_message_list/item/chat_kit_message_item.dart b/nim_chatkit_ui/lib/view/chat_kit_message_list/item/chat_kit_message_item.dart index 04d2ae8..723e9b0 100644 --- a/nim_chatkit_ui/lib/view/chat_kit_message_list/item/chat_kit_message_item.dart +++ b/nim_chatkit_ui/lib/view/chat_kit_message_list/item/chat_kit_message_item.dart @@ -11,6 +11,7 @@ import 'package:netease_common_ui/ui/avatar.dart'; import 'package:netease_common_ui/ui/progress_ring.dart'; import 'package:netease_common_ui/utils/color_utils.dart'; import 'package:netease_common_ui/utils/string_utils.dart'; +import 'package:netease_common_ui/widgets/radio_button.dart'; import 'package:netease_corekit_im/model/team_models.dart'; import 'package:netease_corekit_im/service_locator.dart'; import 'package:netease_corekit_im/services/contact/contact_provider.dart'; @@ -18,15 +19,18 @@ import 'package:netease_corekit_im/services/login/login_service.dart'; import 'package:netease_corekit_im/services/message/chat_message.dart'; import 'package:netease_corekit_im/services/message/nim_chat_cache.dart'; import 'package:netease_corekit_im/services/team/team_provider.dart'; +import 'package:nim_chatkit/message/message_helper.dart'; import 'package:nim_chatkit/message/message_reply_info.dart'; import 'package:nim_chatkit/message/message_revoke_info.dart'; import 'package:nim_chatkit/repo/chat_message_repo.dart'; +import 'package:nim_chatkit_ui/chat_kit_client.dart'; +import 'package:nim_chatkit_ui/helper/chat_message_helper.dart'; +import 'package:nim_chatkit_ui/helper/chat_message_user_helper.dart'; import 'package:nim_chatkit_ui/l10n/S.dart'; -import 'package:nim_chatkit_ui/view/chat_kit_message_list/helper/chat_message_helper.dart'; -import 'package:nim_chatkit_ui/view/chat_kit_message_list/helper/chat_message_user_helper.dart'; import 'package:nim_chatkit_ui/view/chat_kit_message_list/item/chat_kit_message_audio_item.dart'; import 'package:nim_chatkit_ui/view/chat_kit_message_list/item/chat_kit_message_file_item.dart'; import 'package:nim_chatkit_ui/view/chat_kit_message_list/item/chat_kit_message_image_item.dart'; +import 'package:nim_chatkit_ui/view/chat_kit_message_list/item/chat_kit_message_merged_item.dart'; import 'package:nim_chatkit_ui/view/chat_kit_message_list/item/chat_kit_message_nonsupport_item.dart'; import 'package:nim_chatkit_ui/view/chat_kit_message_list/item/chat_kit_message_notify_item.dart'; import 'package:nim_chatkit_ui/view/chat_kit_message_list/item/chat_kit_message_tips_item.dart'; @@ -34,13 +38,14 @@ import 'package:nim_chatkit_ui/view/chat_kit_message_list/item/chat_kit_message_ import 'package:nim_chatkit_ui/view/chat_kit_message_list/pop_menu/chat_kit_message_pop_menu.dart'; import 'package:nim_chatkit_ui/view/chat_kit_message_list/pop_menu/chat_kit_pop_actions.dart'; import 'package:nim_chatkit_ui/view/page/chat_message_ack_page.dart'; +import 'package:nim_chatkit_ui/view_model/chat_view_model.dart'; import 'package:nim_core/nim_core.dart'; import 'package:provider/provider.dart'; import 'package:visibility_detector/visibility_detector.dart'; import 'package:yunxin_alog/yunxin_alog.dart'; -import '../../../chat_kit_client.dart'; -import '../../../view_model/chat_view_model.dart'; +import '../../../helper/merge_message_helper.dart'; +import 'chat_kit_message_multi_line_text_item.dart'; import 'chat_kit_message_text_item.dart'; typedef ChatMessageItemBuilder = Widget Function(NIMMessage message); @@ -54,6 +59,7 @@ class ChatKitMessageBuilder { ChatMessageItemBuilder? tipsMessageBuilder; ChatMessageItemBuilder? fileMessageBuilder; ChatMessageItemBuilder? locationMessageBuilder; + ChatMessageItemBuilder? mergedMessageBuilder; Map? extendBuilder; } @@ -106,7 +112,7 @@ class ChatKitMessageItem extends StatefulWidget { } class ChatKitMessageItemState extends State { - static const showTimeInterval = 5 * 60 * 1000; + int showTimeInterval = ChatKitClient.instance.chatUIConfig.showTimeInterval; static const maxReceiptNum = 100; @@ -205,7 +211,7 @@ class ChatKitMessageItemState extends State { double getMaxWidth(isSelect) { final size = MediaQuery.of(context).size; final width = size.width; - return width - (isSelect ? 130 : 110); + return width - (isSelect ? 135 : 110); } bool showNickname() { @@ -224,7 +230,6 @@ class ChatKitMessageItemState extends State { bool _showReeditText(RevokedMessageInfo? revokedMessageInfo) { var message = widget.chatMessage; return isSelf() && - message.nimMessage.messageType == NIMMessageType.text && revokedMessageInfo != null && DateTime.now().millisecondsSinceEpoch - message.nimMessage.timestamp < reeditTime; @@ -359,6 +364,31 @@ class ChatKitMessageItemState extends State { } return ChatKitMessageNonsupportItem(); default: + if (message.nimMessage.messageType == NIMMessageType.custom) { + var mergedMessage = + MergeMessageHelper.parseMergeMessage(message.nimMessage); + var multiLineMap = + MessageHelper.parseMultiLineMessage(message.nimMessage); + var multiLineTitle = multiLineMap?[ChatMessage.keyMultiLineTitle]; + var multiLineBody = multiLineMap?[ChatMessage.keyMultiLineBody]; + if (mergedMessage != null) { + if (messageItemBuilder?.mergedMessageBuilder != null) { + return messageItemBuilder!.mergedMessageBuilder! + .call(message.nimMessage); + } + return ChatKitMessageMergedItem( + message: message.nimMessage, + mergedMessage: mergedMessage, + chatUIConfig: widget.chatUIConfig); + } else if (multiLineTitle != null) { + return ChatKitMessageMultiLineItem( + message: message.nimMessage, + chatUIConfig: widget.chatUIConfig, + title: multiLineTitle, + body: multiLineBody, + ); + } + } if (messageItemBuilder?.extendBuilder != null) { if (messageItemBuilder ?.extendBuilder![message.nimMessage.messageType] != @@ -429,6 +459,10 @@ class ChatKitMessageItemState extends State { } else if (_showMsgAck(message)) { return InkWell( onTap: () { + ///多选模式下不可点击 + if (context.read().isMultiSelected) { + return; + } _log('click $_teamUnAck'); if (message.nimMessage.sessionType == NIMSessionType.team && _teamUnAck != 0) { @@ -500,6 +534,11 @@ class ChatKitMessageItemState extends State { } bool _hideAvatarMessage(ChatMessage message) { + var configShowAvatar = + widget.chatUIConfig?.isShowAvatar?.call(message.nimMessage); + if (configShowAvatar != null) { + return configShowAvatar; + } return message.nimMessage.messageType == NIMMessageType.notification || message.nimMessage.messageType == NIMMessageType.tip; } @@ -589,6 +628,7 @@ class ChatKitMessageItemState extends State { Widget _getSingleMiddleEllipsisText(String? data, {TextStyle? style, String? userName}) { String info = data ?? ""; + bool isMultiSelect = context.watch().isMultiSelected; final TextPainter textPainter = TextPainter( text: TextSpan(text: info, style: style), maxLines: 1, @@ -596,7 +636,7 @@ class ChatKitMessageItemState extends State { ..layout(minWidth: 0, maxWidth: double.infinity); //超出的宽度,多计算上头像占位 final exceedWidth = - (textPainter.size.width - (getMaxWidth(false) - 50)).toInt(); + (textPainter.size.width - (getMaxWidth(isMultiSelect) - 50)).toInt(); if (exceedWidth > 0 && userName?.isNotEmpty == true) { //每一个字符的宽度 final pre = textPainter.width / info.length; @@ -632,6 +672,32 @@ class ChatKitMessageItemState extends State { ); } + Widget _getSelectWidget(bool isSelectModel, ChatViewModel chatViewModel) { + if (isSelectModel) { + if (!widget.chatMessage.isRevoke) { + return Container( + width: 18, + margin: const EdgeInsets.only(right: 8, top: 10), + child: CheckBoxButton( + isChecked: + chatViewModel.isSelectedMessage(widget.chatMessage.nimMessage), + onChanged: (value) { + if (value) { + chatViewModel.addSelectedMessage(widget.chatMessage.nimMessage); + } else { + chatViewModel + .removeSelectedMessage(widget.chatMessage.nimMessage); + } + }, + ), + ); + } else { + return Container(width: 25); + } + } + return Container(); + } + @override void dispose() { _popMenu?.clean(); @@ -648,6 +714,8 @@ class ChatKitMessageItemState extends State { var screenWidth = MediaQuery.of(context).size.width; var pinTextStyle = TextStyle(color: '#3EAF96'.toColor(), fontSize: 11); + + var chatViewModel = context.watch(); return VisibilityDetector( key: widget.key!, child: Column( @@ -670,8 +738,10 @@ class ChatKitMessageItemState extends State { color: _getBgColor(), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, children: [ + _getSelectWidget( + chatViewModel.isMultiSelected, chatViewModel), FutureBuilder( future: _getUserInfo( widget.chatMessage.nimMessage.fromAccount!), @@ -757,13 +827,15 @@ class ChatKitMessageItemState extends State { left: isSelf() ? 8 : 0), decoration: _getMessageDecoration(), constraints: BoxConstraints( - maxWidth: getMaxWidth(false)), + maxWidth: getMaxWidth( + chatViewModel + .isMultiSelected)), child: Builder( builder: (context) { return GestureDetector( child: IgnorePointer( - ///todo ignoring for multiSelect - ignoring: false, + ignoring: chatViewModel + .isMultiSelected, child: widget .chatMessage.isRevoke ? _buildRevokedMessage( @@ -823,8 +895,9 @@ class ChatKitMessageItemState extends State { builder: (context, snapshot) { return Container( constraints: BoxConstraints( - maxWidth: - getMaxWidth(false)), + maxWidth: getMaxWidth( + chatViewModel + .isMultiSelected)), child: Row( crossAxisAlignment: CrossAxisAlignment.center, diff --git a/nim_chatkit_ui/lib/view/chat_kit_message_list/item/chat_kit_message_merged_item.dart b/nim_chatkit_ui/lib/view/chat_kit_message_list/item/chat_kit_message_merged_item.dart new file mode 100644 index 0000000..37f62a1 --- /dev/null +++ b/nim_chatkit_ui/lib/view/chat_kit_message_list/item/chat_kit_message_merged_item.dart @@ -0,0 +1,146 @@ +// Copyright (c) 2022 NetEase, Inc. All rights reserved. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:netease_common_ui/utils/color_utils.dart'; +import 'package:netease_common_ui/utils/string_utils.dart'; +import 'package:netease_common_ui/widgets/text_untils.dart'; +import 'package:nim_chatkit/message/merge_message.dart'; +import 'package:nim_chatkit_ui/chat_kit_client.dart'; +import 'package:nim_core/nim_core.dart'; + +import '../../../l10n/S.dart'; +import '../../page/merged_message_page.dart'; + +///在消息体里展示合并消息 +class ChatKitMessageMergedItem extends StatefulWidget { + final NIMMessage message; + + final MergedMessage mergedMessage; + + final ChatUIConfig? chatUIConfig; + + ///是否展示margin + final bool showMargin; + + ///是否区分不同方向的消息 + final bool diffDirection; + + const ChatKitMessageMergedItem( + {Key? key, + required this.message, + this.chatUIConfig, + required this.mergedMessage, + this.showMargin = true, + this.diffDirection = true}) + : super(key: key); + + @override + State createState() { + return _ChatKitMessageMergedItemState(); + } +} + +class _ChatKitMessageMergedItemState extends State { + late MergedMessage _mergedMessage; + + ///摘要中昵称的最大长度 + static const int _maxLengthOfNick = 5; + + String getAbstract() { + StringBuffer abstract = StringBuffer(); + for (int i = 0; i < _mergedMessage.abstracts.length; i++) { + var abs = _mergedMessage.abstracts[i]; + abstract.write( + '${abs.senderNick.subStringWithMaxLength(_maxLengthOfNick)}: ${abs.content}'); + if (i != _mergedMessage.abstracts.length - 1) { + abstract.write('\n'); + } + } + return abstract.toString(); + } + + @override + void initState() { + _mergedMessage = widget.mergedMessage; + super.initState(); + } + + bool isSelf() { + return widget.message.messageDirection == NIMMessageDirection.outgoing; + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () { + Navigator.push(context, MaterialPageRoute(builder: (context) { + return MergedMessagePage( + mergedMessage: _mergedMessage, message: widget.message); + })); + }, + child: Container( + margin: widget.showMargin ? EdgeInsets.all(8) : null, + padding: EdgeInsets.symmetric(horizontal: 12, vertical: 8), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: widget.diffDirection + ? (isSelf() + ? const BorderRadius.only( + topLeft: Radius.circular(8), + bottomLeft: Radius.circular(8), + bottomRight: Radius.circular(8)) + : const BorderRadius.only( + topRight: Radius.circular(8), + bottomLeft: Radius.circular(8), + bottomRight: Radius.circular(8))) + : BorderRadius.circular(8), + border: widget.showMargin + ? null + : Border.all(color: '#E4E9F2'.toColor(), width: 1), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + getSingleMiddleEllipsisText( + S + .of(context) + .chatMessageMergedTitle(_mergedMessage.sessionName), + endLen: 4, + lessLen: isSelf() ? 10 : 0, + style: TextStyle( + fontSize: 14, + color: Color(0xFF333333), + fontWeight: FontWeight.w500, + )), + SizedBox( + height: 4, + ), + Text( + getAbstract(), + maxLines: 3, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 14, + color: '#999999'.toColor(), + ), + ), + Container( + margin: EdgeInsets.symmetric(vertical: 8), + height: 0.5, + color: '#999999'.toColor(), + ), + Text( + S.of(context).chatMessageChatHistory, + style: TextStyle( + fontSize: 12, + color: '#999999'.toColor(), + ), + ), + ], + ), + ), + ); + } +} diff --git a/nim_chatkit_ui/lib/view/chat_kit_message_list/item/chat_kit_message_multi_line_text_item.dart b/nim_chatkit_ui/lib/view/chat_kit_message_list/item/chat_kit_message_multi_line_text_item.dart new file mode 100644 index 0000000..e12a8af --- /dev/null +++ b/nim_chatkit_ui/lib/view/chat_kit_message_list/item/chat_kit_message_multi_line_text_item.dart @@ -0,0 +1,119 @@ +// Copyright (c) 2022 NetEase, Inc. All rights reserved. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +import 'package:flutter/widgets.dart'; +import 'package:netease_common_ui/utils/color_utils.dart'; +import 'package:netease_corekit_im/model/ait/ait_msg.dart'; +import 'package:nim_chatkit_ui/chat_kit_client.dart'; +import 'package:nim_chatkit_ui/helper/chat_message_helper.dart'; +import 'package:nim_core/nim_core.dart'; + +class ChatKitMessageMultiLineItem extends StatefulWidget { + final NIMMessage message; + + final ChatUIConfig? chatUIConfig; + + final bool needPadding; + + final String title; + + final String? body; + + final int? titleMaxLines; + + final int? bodyMaxLines; + + const ChatKitMessageMultiLineItem( + {Key? key, + required this.message, + this.chatUIConfig, + this.body, + this.needPadding = true, + this.titleMaxLines, + this.bodyMaxLines, + required this.title}) + : super(key: key); + + @override + State createState() => ChatKitMessageMultiLineState(); +} + +class ChatKitMessageMultiLineState extends State { + @override + Widget build(BuildContext context) { + ///处理title + final String title = widget.title; + + ///处理body + final String text = widget.body ?? ''; + var matches = RegExp("\\[[^\\[]{1,10}\\]").allMatches(text); + List spans = []; + int preIndex = 0; + if (matches.isNotEmpty) { + for (final match in matches) { + if (match.start > preIndex) { + spans.addAll(ChatMessageHelper.textSpan( + context, text.substring(preIndex, match.start), preIndex, + end: match.start, + chatUIConfig: widget.chatUIConfig, + remoteExtension: widget.message.remoteExtension)); + } + var span = ChatMessageHelper.imageSpan(match.group(0)); + if (span != null) { + spans.add(span); + } else if (match.group(0)?.isNotEmpty == true) { + spans.addAll(ChatMessageHelper.textSpan(context, match.group(0)!, 0, + chatUIConfig: widget.chatUIConfig, + remoteExtension: widget.message.remoteExtension)); + } + preIndex = match.end; + } + if (preIndex < text.length) { + spans.addAll(ChatMessageHelper.textSpan( + context, text.substring(preIndex, text.length), preIndex, + chatUIConfig: widget.chatUIConfig, + remoteExtension: widget.message.remoteExtension)); + } + } else { + spans.addAll(ChatMessageHelper.textSpan(context, text, 0, + chatUIConfig: widget.chatUIConfig, + remoteExtension: widget.message.remoteExtension)); + } + return Container( + //放到里面 + padding: widget.needPadding + ? const EdgeInsets.only(left: 16, top: 12, right: 16, bottom: 12) + : null, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(title, + maxLines: widget.titleMaxLines, + overflow: + (widget.titleMaxLines != null) ? TextOverflow.ellipsis : null, + style: TextStyle( + fontSize: widget.chatUIConfig?.messageTextSize ?? 16, + color: widget.chatUIConfig?.messageTextColor ?? + CommonColors.color_333333, + fontWeight: FontWeight.w600)), + if (text.isNotEmpty) + Text.rich( + TextSpan(children: spans), + overflow: + (widget.bodyMaxLines != null) ? TextOverflow.ellipsis : null, + maxLines: widget.bodyMaxLines, + ), + ], + ), + ); + } +} + +class AitItemModel { + String account; + String text; + AitSegment segment; + + AitItemModel(this.account, this.text, this.segment); +} diff --git a/nim_chatkit_ui/lib/view/chat_kit_message_list/item/chat_kit_message_notify_item.dart b/nim_chatkit_ui/lib/view/chat_kit_message_list/item/chat_kit_message_notify_item.dart index d1a0d4b..c97a35d 100644 --- a/nim_chatkit_ui/lib/view/chat_kit_message_list/item/chat_kit_message_notify_item.dart +++ b/nim_chatkit_ui/lib/view/chat_kit_message_list/item/chat_kit_message_notify_item.dart @@ -2,11 +2,12 @@ // Use of this source code is governed by a MIT license that can be // found in the LICENSE file. -import 'package:nim_chatkit_ui/view/chat_kit_message_list/helper/chat_message_helper.dart'; -import 'package:netease_common_ui/utils/color_utils.dart'; import 'package:flutter/widgets.dart'; +import 'package:netease_common_ui/utils/color_utils.dart'; import 'package:nim_core/nim_core.dart'; +import '../../../helper/chat_message_helper.dart'; + class ChatKitMessageNotificationItem extends StatefulWidget { final NIMMessage message; @@ -28,8 +29,7 @@ class ChatKitMessageNotificationState builder: (context, snap) { return Text( snap.data ?? '', - maxLines: 1, - overflow: TextOverflow.ellipsis, + maxLines: null, style: TextStyle(fontSize: 12, color: '#999999'.toColor()), ); }, diff --git a/nim_chatkit_ui/lib/view/chat_kit_message_list/item/chat_kit_message_text_item.dart b/nim_chatkit_ui/lib/view/chat_kit_message_list/item/chat_kit_message_text_item.dart index 098ba6c..8c3f81e 100644 --- a/nim_chatkit_ui/lib/view/chat_kit_message_list/item/chat_kit_message_text_item.dart +++ b/nim_chatkit_ui/lib/view/chat_kit_message_list/item/chat_kit_message_text_item.dart @@ -2,20 +2,12 @@ // Use of this source code is governed by a MIT license that can be // found in the LICENSE file. -import 'package:flutter/gestures.dart'; -import 'package:netease_corekit_im/model/ait/ait_contacts_model.dart'; +import 'package:flutter/widgets.dart'; import 'package:netease_corekit_im/model/ait/ait_msg.dart'; -import 'package:netease_corekit_im/router/imkit_router_factory.dart'; -import 'package:netease_corekit_im/service_locator.dart'; -import 'package:netease_corekit_im/services/login/login_service.dart'; -import 'package:netease_corekit_im/services/message/chat_message.dart'; import 'package:nim_chatkit_ui/chat_kit_client.dart'; -import 'package:nim_chatkit_ui/view/input/emoji.dart'; -import 'package:collection/collection.dart'; -import 'package:netease_common_ui/utils/color_utils.dart'; -import 'package:flutter/widgets.dart'; import 'package:nim_core/nim_core.dart'; -import 'package:yunxin_alog/yunxin_alog.dart'; + +import '../../../helper/chat_message_helper.dart'; class ChatKitMessageTextItem extends StatefulWidget { final NIMMessage message; @@ -39,98 +31,6 @@ class ChatKitMessageTextItem extends StatefulWidget { } class ChatKitMessageTextState extends State { - List _textSpan(String text, int start, [int? end]) { - //定义文本字体大小和颜色 - final textSize = widget.chatUIConfig?.messageTextSize ?? 16; - final textColor = - widget.chatUIConfig?.messageTextColor ?? CommonColors.color_333333; - final textAitColor = - widget.chatUIConfig?.messageLinkColor ?? CommonColors.color_007aff; - - //需要返回的spans - final List spans = []; - //如果有@消息,则需要将@消息的文本和普通文本分开 - if (widget.message.remoteExtension?[ChatMessage.keyAitMsg] != null) { - //获取@消息的文本list - List aitSegments = []; - //将所有@的文本和位置提取出来 - try { - var aitMap = - widget.message.remoteExtension![ChatMessage.keyAitMsg] as Map; - final AitContactsModel aitContactsModel = - AitContactsModel.fromMap(Map.from(aitMap)); - aitContactsModel.aitBlocks.forEach((key, value) { - var aitMsg = value as AitMsg; - aitMsg.segments.forEach((segment) { - aitSegments.add(AitItemModel(key, aitMsg.text, segment)); - }); - }); - } catch (e) { - Alog.e( - tag: 'ChatKitMessageTextItem', - content: 'aitContactsModel.fromMap error: $e'); - } - - //根据@消息的位置,将文本分成多个部分 - aitSegments.sort((a, b) => a.segment.start.compareTo(b.segment.start)); - int preIndex = start; - for (var aitItem in aitSegments) { - //@之前的部分 - if (aitItem.segment.start > preIndex) { - spans.add(TextSpan( - text: text.substring(preIndex, aitItem.segment.start), - style: TextStyle(fontSize: textSize, color: textColor))); - } - //@部分 - spans.add(TextSpan( - text: aitItem.text, - style: TextStyle(fontSize: textSize, color: textAitColor), - recognizer: TapGestureRecognizer() - ..onTap = () { - //点击@消息,如果有自定义回调,则回调,否则跳转到用户详情页 - if (widget.chatUIConfig?.onTapAitLink != null) { - widget.chatUIConfig?.onTapAitLink - ?.call(aitItem.account, aitItem.text); - } else if (aitItem.account != AitContactsModel.accountAll) { - if (getIt().userInfo?.userId != - aitItem.account) { - goToContactDetail(context, aitItem.account); - } else { - gotoMineInfoPage(context); - } - } - })); - preIndex = - end == null ? aitItem.segment.end : end - aitItem.segment.end; - } - //最后一个@之后的部分 - if (preIndex < text.length) { - spans.add(TextSpan( - text: text.substring(preIndex, text.length), - style: TextStyle(fontSize: textSize, color: textColor))); - } - } else { - //没有@消息,直接返回 - spans.add(TextSpan( - text: text, style: TextStyle(fontSize: textSize, color: textColor))); - } - return spans; - } - - WidgetSpan? _imageSpan(String? tag) { - var item = emojiData.firstWhereOrNull((element) => element['tag'] == tag); - if (item == null) return null; - String name = item['name'] as String; - return WidgetSpan( - child: Image.asset( - name, - package: kPackage, - height: 24, - width: 24, - ), - ); - } - @override Widget build(BuildContext context) { final String text = widget.message.content!; @@ -140,21 +40,32 @@ class ChatKitMessageTextState extends State { if (matches.isNotEmpty) { for (final match in matches) { if (match.start > preIndex) { - spans.addAll(_textSpan( - text.substring(preIndex, match.start), preIndex, match.start)); + spans.addAll(ChatMessageHelper.textSpan( + context, text.substring(preIndex, match.start), preIndex, + end: match.start, + chatUIConfig: widget.chatUIConfig, + remoteExtension: widget.message.remoteExtension)); } - var span = _imageSpan(match.group(0)); + var span = ChatMessageHelper.imageSpan(match.group(0)); if (span != null) { spans.add(span); + } else if (match.group(0)?.isNotEmpty == true) { + spans.addAll(ChatMessageHelper.textSpan(context, match.group(0)!, 0, + chatUIConfig: widget.chatUIConfig, + remoteExtension: widget.message.remoteExtension)); } preIndex = match.end; } if (preIndex < text.length) { - spans - .addAll(_textSpan(text.substring(preIndex, text.length), preIndex)); + spans.addAll(ChatMessageHelper.textSpan( + context, text.substring(preIndex, text.length), preIndex, + chatUIConfig: widget.chatUIConfig, + remoteExtension: widget.message.remoteExtension)); } } else { - spans.addAll(_textSpan(text, 0)); + spans.addAll(ChatMessageHelper.textSpan(context, text, 0, + chatUIConfig: widget.chatUIConfig, + remoteExtension: widget.message.remoteExtension)); } return Container( //放到里面 diff --git a/nim_chatkit_ui/lib/view/chat_kit_message_list/item/chat_kit_message_tips_item.dart b/nim_chatkit_ui/lib/view/chat_kit_message_list/item/chat_kit_message_tips_item.dart index 61ad5ec..a93483e 100644 --- a/nim_chatkit_ui/lib/view/chat_kit_message_list/item/chat_kit_message_tips_item.dart +++ b/nim_chatkit_ui/lib/view/chat_kit_message_list/item/chat_kit_message_tips_item.dart @@ -2,9 +2,9 @@ // Use of this source code is governed by a MIT license that can be // found in the LICENSE file. -import 'package:netease_corekit_im/router/imkit_router_constants.dart'; -import 'package:netease_common_ui/utils/color_utils.dart'; import 'package:flutter/widgets.dart'; +import 'package:netease_common_ui/utils/color_utils.dart'; +import 'package:netease_corekit_im/router/imkit_router_constants.dart'; import 'package:nim_core/nim_core.dart'; class ChatKitMessageTipsItem extends StatefulWidget { @@ -33,8 +33,7 @@ class ChatKitMessageTipsState extends State { padding: const EdgeInsets.only(left: 16, top: 12, right: 16, bottom: 8), child: Text( _getTips(widget.message), - maxLines: 1, - overflow: TextOverflow.ellipsis, + maxLines: null, style: TextStyle(fontSize: 12, color: '#999999'.toColor()), ), ); diff --git a/nim_chatkit_ui/lib/view/chat_kit_message_list/item/chat_kit_message_video_item.dart b/nim_chatkit_ui/lib/view/chat_kit_message_list/item/chat_kit_message_video_item.dart index 8c1e480..8dc4765 100644 --- a/nim_chatkit_ui/lib/view/chat_kit_message_list/item/chat_kit_message_video_item.dart +++ b/nim_chatkit_ui/lib/view/chat_kit_message_list/item/chat_kit_message_video_item.dart @@ -5,16 +5,16 @@ import 'dart:async'; import 'dart:io'; +import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:netease_common_ui/extension.dart'; import 'package:netease_common_ui/widgets/neListView/frame_separate_widget.dart'; import 'package:nim_chatkit/extension.dart'; import 'package:nim_chatkit/repo/chat_service_observer_repo.dart'; import 'package:nim_chatkit_ui/media/audio_player.dart'; import 'package:nim_chatkit_ui/media/video.dart'; import 'package:nim_chatkit_ui/view/chat_kit_message_list/widgets/chat_thumb_view.dart'; -import 'package:netease_common_ui/extension.dart'; -import 'package:dio/dio.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_svg/svg.dart'; import 'package:nim_core/nim_core.dart'; import 'package:path_provider/path_provider.dart'; import 'package:yunxin_alog/yunxin_alog.dart'; @@ -24,7 +24,11 @@ import '../../../chat_kit_client.dart'; class ChatKitMessageVideoItem extends StatefulWidget { final NIMMessage message; - const ChatKitMessageVideoItem({Key? key, required this.message}) + ///独立的文件,比如合并转发后的文件 + final bool independentFile; + + const ChatKitMessageVideoItem( + {Key? key, required this.message, this.independentFile = false}) : super(key: key); @override @@ -32,7 +36,7 @@ class ChatKitMessageVideoItem extends StatefulWidget { } class _ChatKitMessageVideoState extends State { - late StreamSubscription _subscription; + StreamSubscription? _subscriptionMsgDownload; late StreamController _progress; NIMVideoAttachment get attachment => @@ -121,7 +125,7 @@ class _ChatKitMessageVideoState extends State { } void _videoOnTap() async { - if (Platform.isIOS) { + if (Platform.isIOS || widget.independentFile) { // SDK不提供下载功能,需要手动下载 var appDocDir = await getTemporaryDirectory(); String savePath = "${appDocDir.path}/${attachment.md5}.mp4"; @@ -148,7 +152,8 @@ class _ChatKitMessageVideoState extends State { void initState() { super.initState(); _progress = StreamController.broadcast(); - _subscription = + + _subscriptionMsgDownload = ChatServiceObserverRepo.observeAttachmentProgress().listen((event) { if (event.id == widget.message.uuid) { _log('onAttachmentProgress -->> ${event.id} : ${event.progress}'); @@ -162,7 +167,7 @@ class _ChatKitMessageVideoState extends State { @override void dispose() { _progress.close(); - _subscription.cancel(); + _subscriptionMsgDownload?.cancel(); super.dispose(); } @@ -191,8 +196,10 @@ class _ChatKitMessageVideoState extends State { child: Stack( children: [ ChatThumbView( - message: widget.message, - radius: const BorderRadius.all(Radius.circular(12))), + message: widget.message, + radius: const BorderRadius.all(Radius.circular(12)), + thumbFromRemote: widget.independentFile, + ), Positioned.fill( child: Visibility( visible: path.isNotEmpty, diff --git a/nim_chatkit_ui/lib/view/chat_kit_message_list/item/mergedMessage/chat_kit_merged_message_item.dart b/nim_chatkit_ui/lib/view/chat_kit_message_list/item/mergedMessage/chat_kit_merged_message_item.dart new file mode 100644 index 0000000..f7b3879 --- /dev/null +++ b/nim_chatkit_ui/lib/view/chat_kit_message_list/item/mergedMessage/chat_kit_merged_message_item.dart @@ -0,0 +1,270 @@ +// Copyright (c) 2022 NetEase, Inc. All rights reserved. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart' as Intl; +import 'package:netease_common_ui/ui/avatar.dart'; +import 'package:netease_common_ui/utils/color_utils.dart'; +import 'package:netease_corekit_im/services/message/chat_message.dart'; +import 'package:nim_chatkit/message/merge_message.dart'; +import 'package:nim_chatkit/message/message_helper.dart'; +import 'package:nim_chatkit_ui/chat_kit_client.dart'; +import 'package:nim_chatkit_ui/view/chat_kit_message_list/item/chat_kit_message_file_item.dart'; +import 'package:nim_chatkit_ui/view/chat_kit_message_list/item/chat_kit_message_image_item.dart'; +import 'package:nim_chatkit_ui/view/chat_kit_message_list/item/chat_kit_message_nonsupport_item.dart'; +import 'package:nim_chatkit_ui/view/chat_kit_message_list/item/chat_kit_message_video_item.dart'; +import 'package:nim_core/nim_core.dart'; + +import '../../../../helper/merge_message_helper.dart'; +import '../../../../l10n/S.dart'; +import '../chat_kit_message_item.dart'; +import '../chat_kit_message_merged_item.dart'; +import '../chat_kit_message_multi_line_text_item.dart'; +import '../chat_kit_message_text_item.dart'; + +///在合并消息详情页展示消息列表 +class ChatKitMergedMessageItem extends StatefulWidget { + final NIMMessage message; + + final ChatKitMessageBuilder? messageBuilder; + + final ChatUIConfig? chatUIConfig; + + final String chatTitle; + + final int? lastMessageTime; + + ChatKitMergedMessageItem( + {Key? key, + required this.message, + required this.chatTitle, + this.lastMessageTime, + this.messageBuilder, + this.chatUIConfig}) + : super(key: key); + + @override + State createState() => _ChatKitMergedMessageItemState(); +} + +class _ChatKitMergedMessageItemState extends State { + int showTimeInterval = ChatKitClient.instance.chatUIConfig.showTimeInterval; + + //item 复用MessageItem + Widget _buildMessage(NIMMessage message) { + var messageItemBuilder = widget.messageBuilder; + switch (message.messageType) { + case NIMMessageType.text: + if (messageItemBuilder?.textMessageBuilder != null) { + return messageItemBuilder!.textMessageBuilder!(message); + } + return ChatKitMessageTextItem( + message: message, chatUIConfig: widget.chatUIConfig); + case NIMMessageType.audio: + return Container( + decoration: BoxDecoration( + border: Border.all(color: '#F0F0F0'.toColor()), + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(12), + topRight: Radius.circular(12), + bottomLeft: Radius.circular(12), + bottomRight: Radius.circular(12))), + child: Container( + padding: + const EdgeInsets.only(left: 16, top: 12, right: 16, bottom: 12), + child: Text(S.of(context).chatMessageBriefAudio, + style: TextStyle(fontSize: 16, color: '#333333'.toColor())), + ), + ); + case NIMMessageType.image: + if (messageItemBuilder?.imageMessageBuilder != null) { + return messageItemBuilder!.imageMessageBuilder!(message); + } + return ChatKitMessageImageItem( + message: message, + showOneImage: true, + showDirection: false, + ); + case NIMMessageType.video: + if (messageItemBuilder?.videoMessageBuilder != null) { + return messageItemBuilder!.videoMessageBuilder!(message); + } + return ChatKitMessageVideoItem( + message: message, + independentFile: true, + ); + case NIMMessageType.file: + if (messageItemBuilder?.fileMessageBuilder != null) { + return messageItemBuilder!.fileMessageBuilder!(message); + } + return ChatKitMessageFileItem( + message: message, + independentFile: true, + ); + + case NIMMessageType.location: + if (messageItemBuilder?.locationMessageBuilder != null) { + return messageItemBuilder!.locationMessageBuilder!.call(message); + } + if (widget.chatUIConfig?.locationProvider != null) { + return widget.chatUIConfig!.locationProvider! + .buildLocationItem(widget.message); + } + return ChatKitMessageNonsupportItem(); + default: + if (message.messageType == NIMMessageType.custom) { + var mergedMessage = MergeMessageHelper.parseMergeMessage(message); + if (mergedMessage != null) { + if (messageItemBuilder?.mergedMessageBuilder != null) { + return messageItemBuilder!.mergedMessageBuilder!.call(message); + } + return ChatKitMessageMergedItem( + message: message, + mergedMessage: mergedMessage, + chatUIConfig: widget.chatUIConfig, + showMargin: false, + diffDirection: false, + ); + } + var multiLineMap = MessageHelper.parseMultiLineMessage(message); + var multiLineTitle = multiLineMap?[ChatMessage.keyMultiLineTitle]; + var multiLineBody = multiLineMap?[ChatMessage.keyMultiLineBody]; + if (multiLineTitle != null) { + return ChatKitMessageMultiLineItem( + message: message, + chatUIConfig: widget.chatUIConfig, + title: multiLineTitle, + body: multiLineBody, + ); + } + } + if (messageItemBuilder?.extendBuilder != null) { + if (messageItemBuilder?.extendBuilder![message.messageType] != null) { + return messageItemBuilder! + .extendBuilder![message.messageType]!(message); + } + } + return ChatKitMessageNonsupportItem(); + } + } + + //时间格式化 + String _timeFormat(int milliSecond) { + var nowTime = DateTime.now(); + var messageTime = DateTime.fromMillisecondsSinceEpoch(milliSecond); + if (nowTime.year != messageTime.year) { + return Intl.DateFormat('yyyy-MM-dd HH:mm').format(messageTime); + } else if (nowTime.month != messageTime.month || + nowTime.day != messageTime.day) { + return Intl.DateFormat('MM-dd HH:mm').format(messageTime); + } else { + return Intl.DateFormat('HH:mm').format(messageTime); + } + } + + bool _showTime() { + if (widget.lastMessageTime == null) { + return true; + } + var currentTime = widget.message.timestamp; + var lastMessageTime = widget.lastMessageTime!; + return currentTime - lastMessageTime > showTimeInterval; + } + + bool _hideMessageBg(NIMMessage message) { + if (message.messageType == NIMMessageType.image || + message.messageType == NIMMessageType.video || + message.messageType == NIMMessageType.location) { + return true; + } + //合并消息不显示背景 + if (message.messageType == NIMMessageType.custom) { + var mergedMessage = MergeMessageHelper.parseMergeMessage(message); + if (mergedMessage != null) { + return true; + } + } + return false; + } + + BoxDecoration _getMessageDecoration() { + if (widget.chatUIConfig?.receiveMessageBg != null) { + return widget.chatUIConfig!.receiveMessageBg!; + } else { + return BoxDecoration( + color: _hideMessageBg(widget.message) + ? Colors.transparent + : '#E8EAED'.toColor(), + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(12), + topRight: Radius.circular(12), + bottomLeft: Radius.circular(12), + bottomRight: Radius.circular(12)), + ); + } + } + + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final sendNick = widget.message.remoteExtension?[mergedMessageNickKey] ?? + widget.message.fromAccount; + final sendAvatar = widget.message.remoteExtension?[mergedMessageAvatarKey]; + return Container( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 5), + margin: const EdgeInsets.only(bottom: 10), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + if (_showTime()) + Padding( + padding: EdgeInsets.symmetric(vertical: 8), + child: Text( + _timeFormat(widget.message.timestamp), + style: TextStyle( + fontSize: widget.chatUIConfig?.timeTextSize ?? 12, + color: widget.chatUIConfig?.timeTextColor ?? + '#B3B7BC'.toColor()), + ), + ), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Avatar(avatar: sendAvatar, name: sendNick, width: 32, height: 32), + Container( + margin: const EdgeInsets.only(left: 8), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(sendNick, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 12, color: '#888888'.toColor())), + Container( + margin: const EdgeInsets.only(top: 3), + decoration: _getMessageDecoration(), + constraints: BoxConstraints( + maxWidth: MediaQuery.of(context).size.width - 110), + child: _buildMessage(widget.message), + ) + ], + ), + ) + ], + ), + ], + ), + ); + } +} diff --git a/nim_chatkit_ui/lib/view/chat_kit_message_list/item/pinMessage/chat_kit_pin_message_item.dart b/nim_chatkit_ui/lib/view/chat_kit_message_list/item/pinMessage/chat_kit_pin_message_item.dart index cfe6b72..6780dca 100644 --- a/nim_chatkit_ui/lib/view/chat_kit_message_list/item/pinMessage/chat_kit_pin_message_item.dart +++ b/nim_chatkit_ui/lib/view/chat_kit_message_list/item/pinMessage/chat_kit_pin_message_item.dart @@ -13,9 +13,12 @@ import 'package:netease_common_ui/ui/dialog.dart'; import 'package:netease_common_ui/utils/color_utils.dart'; import 'package:netease_common_ui/utils/connectivity_checker.dart'; import 'package:netease_corekit_im/services/message/chat_message.dart'; +import 'package:nim_chatkit/message/message_helper.dart'; import 'package:nim_chatkit/repo/chat_message_repo.dart'; -import 'package:nim_chatkit_ui/view/chat_kit_message_list/helper/chat_message_helper.dart'; -import 'package:nim_chatkit_ui/view/chat_kit_message_list/helper/chat_message_user_helper.dart'; +import 'package:nim_chatkit_ui/chat_kit_client.dart'; +import 'package:nim_chatkit_ui/helper/chat_message_helper.dart'; +import 'package:nim_chatkit_ui/helper/chat_message_user_helper.dart'; +import 'package:nim_chatkit_ui/l10n/S.dart'; import 'package:nim_chatkit_ui/view/chat_kit_message_list/item/chat_kit_message_audio_item.dart'; import 'package:nim_chatkit_ui/view/chat_kit_message_list/item/chat_kit_message_file_item.dart'; import 'package:nim_chatkit_ui/view/chat_kit_message_list/item/chat_kit_message_image_item.dart'; @@ -25,9 +28,10 @@ import 'package:nim_chatkit_ui/view_model/chat_pin_view_model.dart'; import 'package:nim_core/nim_core.dart'; import 'package:provider/provider.dart'; -import '../../../../chat_kit_client.dart'; -import '../../../../l10n/S.dart'; +import '../../../../helper/merge_message_helper.dart'; import '../chat_kit_message_item.dart'; +import '../chat_kit_message_merged_item.dart'; +import '../chat_kit_message_multi_line_text_item.dart'; import '../chat_kit_message_text_item.dart'; class ChatKitPinMessageItem extends StatefulWidget { @@ -104,7 +108,8 @@ class _ChatKitPinMessageItemState extends State { } return ChatKitMessageImageItem( message: message.nimMessage, - isPin: true, + showOneImage: true, + showDirection: false, ); case NIMMessageType.video: if (messageItemBuilder?.videoMessageBuilder != null) { @@ -128,6 +133,36 @@ class _ChatKitPinMessageItemState extends State { } return ChatKitMessageNonsupportItem(); default: + if (message.nimMessage.messageType == NIMMessageType.custom) { + var mergedMessage = + MergeMessageHelper.parseMergeMessage(message.nimMessage); + var multiLineMap = + MessageHelper.parseMultiLineMessage(message.nimMessage); + var multiLineTitle = multiLineMap?[ChatMessage.keyMultiLineTitle]; + var multiLineBody = multiLineMap?[ChatMessage.keyMultiLineBody]; + if (mergedMessage != null) { + if (messageItemBuilder?.mergedMessageBuilder != null) { + return messageItemBuilder!.mergedMessageBuilder! + .call(message.nimMessage); + } + return ChatKitMessageMergedItem( + message: message.nimMessage, + mergedMessage: mergedMessage, + chatUIConfig: widget.chatUIConfig, + showMargin: false, + diffDirection: false, + ); + } else if (multiLineTitle != null) { + return ChatKitMessageMultiLineItem( + message: message.nimMessage, + chatUIConfig: widget.chatUIConfig, + title: multiLineTitle, + body: multiLineBody, + titleMaxLines: 1, + bodyMaxLines: 2, + ); + } + } if (messageItemBuilder?.extendBuilder != null) { if (messageItemBuilder ?.extendBuilder![message.nimMessage.messageType] != @@ -160,7 +195,8 @@ class _ChatKitPinMessageItemState extends State { } //操作弹框 - void _showOptionDialog(BuildContext context) { + void _showOptionDialog(BuildContext context, ChatMessage optionMsg) { + final message = optionMsg; var style = const TextStyle(fontSize: 16, color: CommonColors.color_333333); //将弹框的context 回调出来,解决弹框显示后Item remove的问题 BuildContext? buildContext; @@ -192,7 +228,7 @@ class _ChatKitPinMessageItemState extends State { S.of(context).chatMessageActionCopy, style: style, )), - if (_showForward(widget.chatUIConfig, widget.chatMessage)) + if (_showForward(widget.chatUIConfig, message)) CupertinoActionSheetAction( onPressed: () { if (mounted) { @@ -210,15 +246,14 @@ class _ChatKitPinMessageItemState extends State { buildContext = context; }).then((value) { if (value == 1) { - context.read().removePinMessage(widget.chatMessage); + context.read().removePinMessage(message); } else if (value == 2) { if (mounted) { - Clipboard.setData( - ClipboardData(text: widget.chatMessage.nimMessage.content!)); + Clipboard.setData(ClipboardData(text: message.nimMessage.content!)); Fluttertoast.showToast(msg: S.of().chatMessageCopySuccess); } } else if (value == 3) { - _showForwardMessageDialog(); + _showForwardMessageDialog(message); } }); } @@ -232,10 +267,10 @@ class _ChatKitPinMessageItemState extends State { return false; } - void _showForwardMessageDialog() { - final NIMMessage msg = widget.chatMessage.nimMessage; - ChatMessageHelper.showForwardMessageDialog(context, - (sessionId, sessionType) { + void _showForwardMessageDialog(ChatMessage message) { + final NIMMessage msg = message.nimMessage; + ChatMessageHelper.showForwardMessageDialog(context, (sessionId, sessionType, + {String? postScript, bool? isLastUser}) { if (mounted) { context .read() @@ -253,6 +288,10 @@ class _ChatKitPinMessageItemState extends State { } }); } + if (postScript?.isNotEmpty == true) { + ChatMessageRepo.sendTextMessageWithMessageAck( + sessionId: sessionId, sessionType: sessionType, text: postScript!); + } }, filterUser: widget.chatMessage.nimMessage.sessionType == NIMSessionType.p2p @@ -326,7 +365,7 @@ class _ChatKitPinMessageItemState extends State { package: kPackage, ), onTap: () { - _showOptionDialog(context); + _showOptionDialog(context, widget.chatMessage); }, )) ], diff --git a/nim_chatkit_ui/lib/view/chat_kit_message_list/pop_menu/chat_kit_message_pop_menu.dart b/nim_chatkit_ui/lib/view/chat_kit_message_list/pop_menu/chat_kit_message_pop_menu.dart index ba9407b..5a8b98d 100644 --- a/nim_chatkit_ui/lib/view/chat_kit_message_list/pop_menu/chat_kit_message_pop_menu.dart +++ b/nim_chatkit_ui/lib/view/chat_kit_message_list/pop_menu/chat_kit_message_pop_menu.dart @@ -2,12 +2,13 @@ // Use of this source code is governed by a MIT license that can be // found in the LICENSE file. -import 'package:nim_chatkit_ui/l10n/S.dart'; -import 'package:nim_chatkit_ui/view/chat_kit_message_list/pop_menu/chat_kit_pop_actions.dart'; -import 'package:netease_common_ui/utils/color_utils.dart'; -import 'package:netease_corekit_im/services/message/chat_message.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:netease_common_ui/utils/color_utils.dart'; +import 'package:netease_corekit_im/services/message/chat_message.dart'; +import 'package:nim_chatkit/message/message_helper.dart'; +import 'package:nim_chatkit_ui/l10n/S.dart'; +import 'package:nim_chatkit_ui/view/chat_kit_message_list/pop_menu/chat_kit_pop_actions.dart'; import 'package:nim_core/nim_core.dart'; import 'package:super_tooltip/super_tooltip.dart'; @@ -98,9 +99,15 @@ class ChatKitMessagePopMenu { } bool _showCopy(ChatUIConfig? config, ChatMessage message) { - if (config?.popMenuConfig?.enableCopy != false && - message.nimMessage.messageType == NIMMessageType.text) { - return true; + if (config?.popMenuConfig?.enableCopy != false) { + if (message.nimMessage.messageType == NIMMessageType.text) { + return true; + } + var multiLineMap = + MessageHelper.parseMultiLineMessage(message.nimMessage); + if (multiLineMap != null) { + return true; + } } return false; } @@ -151,13 +158,6 @@ class ChatKitMessagePopMenu { "id": _messageHavePined(message) ? cancelPinMessageId : pinMessageId, "icon": "images/ic_chat_pin.svg" }, - // if (config?.popMenuConfig?.enableMultiSelect != false && - // _enableStatus(message)) - // { - // "label": S.of(context).chatMessageActionMultiSelect, - // "id": multiSelectId, - // "icon": "images/ic_chat_select.svg" - // }, // if (config?.popMenuConfig?.enableCollect != false && // _enableStatus(message)) // { @@ -171,6 +171,12 @@ class ChatKitMessagePopMenu { "id": deleteMessageId, "icon": "images/ic_chat_delete.svg" }, + if (config?.popMenuConfig?.enableMultiSelect != false) + { + "label": S.of(context).chatMessageActionMultiSelect, + "id": multiSelectId, + "icon": "images/ic_chat_select.svg" + }, if (shouldShowRevokeAction && config?.popMenuConfig?.enableRevoke != false && _enableStatus(message)) diff --git a/nim_chatkit_ui/lib/view/chat_kit_message_list/widgets/chat_forward_dialog.dart b/nim_chatkit_ui/lib/view/chat_kit_message_list/widgets/chat_forward_dialog.dart index 35cec29..09af4b7 100644 --- a/nim_chatkit_ui/lib/view/chat_kit_message_list/widgets/chat_forward_dialog.dart +++ b/nim_chatkit_ui/lib/view/chat_kit_message_list/widgets/chat_forward_dialog.dart @@ -2,19 +2,28 @@ // Use of this source code is governed by a MIT license that can be // found in the LICENSE file. +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; import 'package:netease_common_ui/ui/avatar.dart'; import 'package:netease_common_ui/utils/color_utils.dart'; +import 'package:netease_common_ui/widgets/text_untils.dart'; import 'package:netease_corekit_im/model/contact_info.dart'; -import 'package:flutter/material.dart'; import 'package:nim_core/nim_core.dart'; import '../../../l10n/S.dart'; -Future showChatForwardDialog( +///弹出转发消息的对话框 +///[context] 上下文 +///[contentStr] 转发的消息内容 +///[contacts] 转发的联系人 +///[team] 转发的群组 +Future showChatForwardDialog( {required BuildContext context, required String contentStr, List? contacts, NIMTeam? team}) async { + TextEditingController _inputControl = TextEditingController(); + Widget _getTargetUser() { if (team != null) { return Row(children: [ @@ -88,7 +97,7 @@ Future showChatForwardDialog( return Container(); } - Widget _getContent() { + Widget _getContent(double width) { return Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, @@ -103,19 +112,30 @@ Future showChatForwardDialog( Container( padding: EdgeInsets.only(left: 12, right: 12, top: 7, bottom: 9), color: '#F2F4F5'.toColor(), - child: Row( - children: [ - Expanded( - child: Text( - contentStr, - style: TextStyle(fontSize: 14, color: '#333333'.toColor()), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ) - ], - ), - ) + height: 38, + width: width, + child: getSingleMiddleEllipsisText(contentStr, + endLen: 5, + style: TextStyle(fontSize: 14, color: '#333333'.toColor())), + ), + Container( + margin: EdgeInsets.only(top: 12), + child: CupertinoTextField( + controller: _inputControl, + placeholder: S.of(context).chatMessagePostScript, + placeholderStyle: TextStyle( + color: '#A6ADB6'.toColor(), + fontSize: 16, + fontWeight: FontWeight.w400), + style: TextStyle( + color: '#333333'.toColor(), + fontSize: 16, + fontWeight: FontWeight.w400), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: '#A6ADB6'.toColor())), + )) ], ); } @@ -123,35 +143,63 @@ Future showChatForwardDialog( return showDialog( context: context, builder: (context) { - return AlertDialog( - backgroundColor: Colors.white, - content: _getContent(), - actions: [ - Row( - children: [ - Expanded( - child: TextButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: Text( - S.of(context).messageCancel, - style: const TextStyle( - fontSize: 17, color: CommonColors.color_666666), - ))), - Expanded( - child: TextButton( - onPressed: () { - Navigator.of(context).pop(true); - }, - child: Text( - S.of(context).chatMessageSend, - style: const TextStyle( - fontSize: 17, color: CommonColors.color_007aff), - ))), - ], - ) - ], - ); + double dialogWidth = MediaQuery.of(context).size.width; + return SimpleDialog( + backgroundColor: Colors.white, + contentPadding: EdgeInsets.zero, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10.0), + ), + children: [ + Padding( + padding: EdgeInsets.all(14), + child: _getContent(dialogWidth - 28), + ), + Container(height: 1, color: '#E1E6E8'.toColor()), + SizedBox( + height: 50, + child: Row( + children: [ + Expanded( + child: TextButton( + onPressed: () { + Navigator.of(context).pop( + ForwardResult(result: false)); + }, + child: Text( + S.of(context).messageCancel, + style: const TextStyle( + fontSize: 17, + color: CommonColors.color_666666), + ))), + Container( + width: 1, + height: 50, + color: '#E1E6E8'.toColor(), + ), + Expanded( + child: TextButton( + onPressed: () { + Navigator.of(context).pop( + ForwardResult( + result: true, + postScript: _inputControl.text)); + }, + child: Text( + S.of(context).chatMessageSend, + style: const TextStyle( + fontSize: 17, + color: CommonColors.color_007aff), + ))), + ], + ), + ), + ]); }); } + +class ForwardResult { + bool result; //是否转发 + String? postScript; //附言 + ForwardResult({required this.result, this.postScript}); +} diff --git a/nim_chatkit_ui/lib/view/chat_kit_message_list/widgets/chat_thumb_view.dart b/nim_chatkit_ui/lib/view/chat_kit_message_list/widgets/chat_thumb_view.dart index 20c04a0..2ec4ad1 100644 --- a/nim_chatkit_ui/lib/view/chat_kit_message_list/widgets/chat_thumb_view.dart +++ b/nim_chatkit_ui/lib/view/chat_kit_message_list/widgets/chat_thumb_view.dart @@ -12,13 +12,19 @@ import 'package:nim_core/nim_core.dart'; class ChatThumbView extends StatefulWidget { const ChatThumbView( - {Key? key, required this.message, required this.radius, this.onTap}) + {Key? key, + required this.message, + required this.radius, + this.onTap, + this.thumbFromRemote = false}) : super(key: key); final NIMMessage message; final BorderRadius radius; final Function()? onTap; + final bool thumbFromRemote; + @override State createState() => _ChatThumbViewState(); } @@ -123,6 +129,9 @@ class _ChatThumbViewState extends State { if (_fileExistCheck(path)) { return _localImage(path); } + if (widget.thumbFromRemote && _getUrlForVideo().isNotEmpty) { + return _networkImage(_getUrlForVideo()); + } return _placeHolder(_getImageRatio()); } @@ -147,6 +156,7 @@ class _ChatThumbViewState extends State { return _localImage(path); } if (_isGif() || + widget.thumbFromRemote || (widget.message.attachmentStatus != NIMMessageAttachmentStatus.transferred || widget.message.attachmentStatus != @@ -186,6 +196,15 @@ class _ChatThumbViewState extends State { return ""; } + String _getUrlForVideo() { + if (widget.message.messageAttachment is NIMVideoAttachment) { + NIMVideoAttachment attachment = + widget.message.messageAttachment as NIMVideoAttachment; + return attachment.thumbUrl ?? ""; + } + return ""; + } + bool _isGif() { //如果remoteExtension中有ImageType字段,且值为gif,则为gif图片 if (widget.message.remoteExtension?[ChatMessage.keyImageType] == gifType) { diff --git a/nim_chatkit_ui/lib/view/input/actions.dart b/nim_chatkit_ui/lib/view/input/actions.dart index b72eda9..ff78274 100644 --- a/nim_chatkit_ui/lib/view/input/actions.dart +++ b/nim_chatkit_ui/lib/view/input/actions.dart @@ -2,8 +2,8 @@ // Use of this source code is governed by a MIT license that can be // found in the LICENSE file. -import 'package:netease_common_ui/widgets/permission_request.dart'; import 'package:flutter/material.dart'; +import 'package:netease_common_ui/widgets/permission_request.dart'; import 'package:permission_handler/permission_handler.dart'; class ActionItem { @@ -15,12 +15,15 @@ class ActionItem { //权限拒绝后的提示 String? deniedTip; + bool enable; + ActionItem( {required this.type, required this.icon, this.title, this.onTap, this.permissions, + this.enable = true, this.deniedTip}); } diff --git a/nim_chatkit_ui/lib/view/input/bottom_input_field.dart b/nim_chatkit_ui/lib/view/input/bottom_input_field.dart index dc45208..a39b060 100644 --- a/nim_chatkit_ui/lib/view/input/bottom_input_field.dart +++ b/nim_chatkit_ui/lib/view/input/bottom_input_field.dart @@ -10,6 +10,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_svg/svg.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:image_picker/image_picker.dart'; +import 'package:netease_common/netease_common.dart'; import 'package:netease_common_ui/ui/dialog.dart'; import 'package:netease_common_ui/utils/color_utils.dart'; import 'package:netease_common_ui/widgets/permission_request.dart'; @@ -19,16 +20,16 @@ import 'package:netease_corekit_im/model/team_models.dart'; import 'package:netease_corekit_im/service_locator.dart'; import 'package:netease_corekit_im/services/login/login_service.dart'; import 'package:netease_corekit_im/services/message/chat_message.dart'; +import 'package:netease_corekit_im/services/message/nim_chat_cache.dart'; +import 'package:nim_chatkit_ui/helper/chat_message_helper.dart'; +import 'package:nim_chatkit_ui/helper/chat_message_user_helper.dart'; import 'package:nim_chatkit_ui/view/ait/ait_manager.dart'; -import 'package:nim_chatkit_ui/view/chat_kit_message_list/helper/chat_message_helper.dart'; import 'package:nim_chatkit_ui/view/input/emoji_panel.dart'; import 'package:nim_core/nim_core.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:provider/provider.dart'; import 'package:scroll_to_index/scroll_to_index.dart'; import 'package:video_player/video_player.dart'; -import 'package:yunxin_alog/yunxin_alog.dart'; -import 'package:nim_chatkit_ui/view/chat_kit_message_list/helper/chat_message_user_helper.dart'; import '../../chat_kit_client.dart'; import '../../l10n/S.dart'; @@ -60,8 +61,10 @@ class _BottomInputFieldState extends State static const String blank = ' '; late TextEditingController inputController; + late TextEditingController titleController; late ScrollController _scrollController; late FocusNode _focusNode; + late FocusNode _titleFocusNode; late ChatViewModel _viewModel; final ImagePicker _picker = ImagePicker(); @@ -77,10 +80,16 @@ class _BottomInputFieldState extends State AitManager? _aitManager; + bool _isExpanded = false; + + ChatMessage? _replyMessageTemp; + hideAllPanel() { _focusNode.unfocus(); + _titleFocusNode.unfocus(); setState(() { _currentType = ActionConstants.none; + _isExpanded = false; }); } @@ -101,6 +110,9 @@ class _BottomInputFieldState extends State } _onEmojiActionTap(BuildContext context) { + if (_titleFocusNode.hasFocus) { + return; + } if (_currentType == ActionConstants.emoji) { _currentType = ActionConstants.none; } else { @@ -349,10 +361,12 @@ class _BottomInputFieldState extends State inputText = text; } - void _handleReplyAit() { - ChatMessage? replyMsg = _viewModel.replyMessage; + void _handleReplyAit(ChatMessage replyMsg) { + if (_replyMessageTemp?.nimMessage.uuid == replyMsg.nimMessage.uuid) { + return; + } + _replyMessageTemp = replyMsg; if (widget.sessionType == NIMSessionType.team && - replyMsg != null && replyMsg.fromUser?.userId != null && replyMsg.fromUser?.userId != getIt().userInfo?.userId) { String account = replyMsg.fromUser!.userId!; @@ -378,10 +392,10 @@ class _BottomInputFieldState extends State if (deletedAit != null) { //删除前判断长度,解决奔溃问题, //复现路径:发送消息@信息在最后,然后撤回,重新编辑,在删除 - if (deletedAit.segments[0].end - deleteLen < value.length) { - inputController.text = - value.substring(0, deletedAit.segments[0].start) + - value.substring(deletedAit.segments[0].end - deleteLen); + if (deletedAit.segments[0].endIndex - deleteLen < value.length) { + inputController.text = value.substring( + 0, deletedAit.segments[0].start) + + value.substring(deletedAit.segments[0].endIndex + 1 - deleteLen); } else { inputController.text = value.substring(0, deletedAit.segments[0].start); @@ -432,8 +446,12 @@ class _BottomInputFieldState extends State } _sendTextMessage() { - final text = inputController.text.trim(); - if (text.isNotEmpty) { + final title = titleController.text.trim(); + var text = inputController.text.trim(); + if (_aitManager?.aitEnd(text) == true) { + text += blank; + } + if (title.isNotEmpty || text.isNotEmpty) { List? pushList; if (widget.sessionType == NIMSessionType.team) { if (_aitManager?.aitContactsModel != null) { @@ -445,12 +463,26 @@ class _BottomInputFieldState extends State replyMsg: _viewModel.replyMessage?.nimMessage, pushList: pushList, aitContactsModel: _aitManager?.aitContactsModel, + title: title, ); _viewModel.replyMessage = null; + _replyMessageTemp = null; // aitMemberMap.clear(); inputController.clear(); + titleController.clear(); inputText = ''; _aitManager?.cleanAit(); + setState(() { + _isExpanded = false; + }); + //100ms 后重新Request focus,以此来弹出键盘 + Future.delayed(Duration(milliseconds: 100)).then((value) { + _titleFocusNode.unfocus(); + _focusNode.requestFocus(); + }); + } else { + Fluttertoast.showToast( + msg: S.of(context).chatMessageNotSupportEmptyMessage); } _scrollToBottom(); } @@ -469,28 +501,53 @@ class _BottomInputFieldState extends State } onViewModelChange() { - if (_viewModel.reeditMessage != null && - _viewModel.reeditMessage!.reeditMessage?.isNotEmpty == true) { - _focusNode.requestFocus(); - //由于发送消息的时候回吧Text中的空格trim - //判断如果是@信息在最后则补充空格 - var needBlank = false; - if (_viewModel.reeditMessage?.aitContactsModel != null) { - _aitManager?.forkAit(_viewModel.reeditMessage!.aitContactsModel!); - if (_aitManager?.aitEnd(_viewModel.reeditMessage!.reeditMessage!) == - true) { - needBlank = true; + if (_viewModel.reeditMessage != null) { + var reeditMessageContent = _viewModel.reeditMessage!.reeditMessage; + var multiLineMap = _viewModel.reeditMessage!.multiLineMessage; + String? titleText; + if (multiLineMap?.isNotEmpty == true) { + reeditMessageContent = multiLineMap![ChatMessage.keyMultiLineBody]; + titleText = multiLineMap[ChatMessage.keyMultiLineTitle]; + } + + //处理文本body + if (reeditMessageContent?.isNotEmpty == true) { + //由于发送消息的时候会把Text中的空格trim + //判断如果是@信息在最后则补充空格 + var needBlank = false; + if (_viewModel.reeditMessage?.aitContactsModel != null) { + _aitManager?.forkAit(_viewModel.reeditMessage!.aitContactsModel!); + if (_aitManager?.aitEnd(reeditMessageContent!) == true) { + needBlank = true; + } } + inputController.text = reeditMessageContent! + (needBlank ? blank : ''); + inputController.selection = TextSelection.fromPosition( + TextPosition(offset: reeditMessageContent.length)); + inputText = inputController.text; + } + //处理title + if (titleText?.isNotEmpty == true) { + titleController.text = titleText!; + titleController.selection = + TextSelection.fromPosition(TextPosition(offset: titleText.length)); + setState(() { + _isExpanded = true; + }); + if (!_viewModel.isMultiSelected) _titleFocusNode.requestFocus(); + } else { + if (!_viewModel.isMultiSelected) _focusNode.requestFocus(); } - inputController.text = - _viewModel.reeditMessage!.reeditMessage! + (needBlank ? blank : ''); - inputController.selection = TextSelection.fromPosition(TextPosition( - offset: _viewModel.reeditMessage!.reeditMessage!.length)); - inputText = inputController.text; + _viewModel.reeditMessage = null; } if (_viewModel.replyMessage != null) { - _focusNode.requestFocus(); + _handleReplyAit(_viewModel.replyMessage!); + if (!_viewModel.isMultiSelected) { + _focusNode.requestFocus(); + } + } else { + _replyMessageTemp = null; } } @@ -499,6 +556,7 @@ class _BottomInputFieldState extends State super.initState(); WidgetsBinding.instance.addObserver(this); inputController = TextEditingController(); + titleController = TextEditingController(); _viewModel = context.read(); inputController.addListener(() { if (_viewModel.sessionType == NIMSessionType.p2p) { @@ -509,6 +567,11 @@ class _BottomInputFieldState extends State var index = inputController.selection.baseOffset; var indexMoved = _aitManager?.resetAitCursor(index); if (indexMoved != null && indexMoved != index) { + Alog.d( + tag: 'ChatKit', + moduleName: 'bottom input', + content: + 'inputController.selection.baseOffset:$index, indexMoved:$indexMoved'); if (indexMoved > inputController.text.length) { indexMoved = inputController.text.length; } @@ -517,8 +580,27 @@ class _BottomInputFieldState extends State } } }); + titleController.addListener(() { + if (_viewModel.sessionType == NIMSessionType.p2p) { + _viewModel.sendInputNotification(titleController.text.isNotEmpty); + } + if (titleController.text.isEmpty) { + setState(() {}); + } + }); _scrollController = ScrollController(); _focusNode = FocusNode(); + _titleFocusNode = FocusNode(); + _focusNode.addListener(() { + if (_focusNode.hasFocus) { + _titleFocusNode.unfocus(); + } + }); + _titleFocusNode.addListener(() { + if (_titleFocusNode.hasFocus) { + _focusNode.unfocus(); + } + }); _viewModel.addListener(onViewModelChange); if (widget.sessionType == NIMSessionType.team) { _aitManager = AitManager(_viewModel.sessionId); @@ -528,8 +610,8 @@ class _BottomInputFieldState extends State @override void didChangeAppLifecycleState(AppLifecycleState state) { if (state == AppLifecycleState.resumed) { - if (_focusNode.hasFocus) { - SystemChannels.textInput.invokeMethod('TextInput.show'); + if (_focusNode.hasFocus || _titleFocusNode.hasFocus) { + showKeyboard(); } } super.didChangeAppLifecycleState(state); @@ -539,6 +621,7 @@ class _BottomInputFieldState extends State void dispose() { WidgetsBinding.instance.removeObserver(this); _focusNode.dispose(); + _titleFocusNode.dispose(); _viewModel.removeListener(onViewModelChange); _aitManager?.dispose(); super.dispose(); @@ -559,21 +642,44 @@ class _BottomInputFieldState extends State } } + /// 是否有焦点 + bool haveFocus() { + return _titleFocusNode.hasFocus || _focusNode.hasFocus; + } + + bool _isShowTitle() { + return !mute && (_isExpanded || titleController.text.trim().isNotEmpty); + } + @override Widget build(BuildContext context) { var team = context.watch().teamInfo; if (team != null && team.creator != getIt().userInfo?.userId) { - mute = team.isAllMute ?? false; + mute = team.isAllMute == true && + NIMChatCache.instance.myTeamRole() == TeamMemberType.normal; + } + if (mute) { + _isExpanded = false; + titleController.clear(); + inputController.clear(); + } + bool showTitle = _isShowTitle(); + if (_isExpanded) { + _currentType = ActionConstants.input; } String? hint = mute ? S.of(context).chatTeamAllMute : widget.hint; + if (context.read().isMultiSelected) { + _focusNode.unfocus(); + _titleFocusNode.unfocus(); + } return Container( width: MediaQuery.of(context).size.width, color: const Color(0xffeff1f3), child: SafeArea( child: Column( children: [ - _viewModel.replyMessage != null + _viewModel.replyMessage != null && !_isExpanded ? Container( height: 36, padding: const EdgeInsets.only(left: 11, right: 7), @@ -582,6 +688,7 @@ class _BottomInputFieldState extends State InkWell( onTap: () { context.read().replyMessage = null; + _replyMessageTemp = null; }, child: const Icon( Icons.close_rounded, @@ -603,10 +710,6 @@ class _BottomInputFieldState extends State _viewModel.sessionId, _viewModel.sessionType), builder: (context, snapshot) { - if (snapshot.connectionState == - ConnectionState.done) { - _handleReplyAit(); - } return Text( S.of(context).chatMessageReplySomeone( snapshot.data ?? ''), @@ -628,61 +731,182 @@ class _BottomInputFieldState extends State ) : Padding( padding: const EdgeInsets.all(7.0), - child: SizedBox( - height: 40, - child: TextField( - controller: inputController, - scrollController: _scrollController, - focusNode: _focusNode, - decoration: InputDecoration( - contentPadding: const EdgeInsets.symmetric( - vertical: 9, horizontal: 12), - fillColor: mute ? Color(0xffe3e4e4) : Colors.white, - filled: true, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - borderSide: BorderSide.none), - isDense: true, - hintText: hint, - hintStyle: const TextStyle( - color: Color(0xffb3b7bc), fontSize: 16), - enabled: !mute), - maxLines: 1, - style: const TextStyle( - color: CommonColors.color_333333, fontSize: 16), - textInputAction: TextInputAction.send, - onChanged: (value) { - _handleAitText(); - }, - onEditingComplete: _sendTextMessage, - enabled: !mute, - ), - ), - ), - _recording - ? SizedBox( - height: 47, - child: Text( - S.of(context).chatMessageVoiceIn, - style: const TextStyle( - fontSize: 12, color: CommonColors.color_999999), - ), - ) - : Row( - children: _getInputActions() - .map((action) => Expanded( - child: InputTextAction( - action: action, - enable: !mute, - onTap: () { - _scrollToBottom(); - if (action.onTap != null) { - action.onTap!(context); - } + child: Column( + children: [ + if (showTitle && !mute) + TextField( + controller: titleController, + focusNode: _titleFocusNode, + decoration: InputDecoration( + contentPadding: const EdgeInsets.symmetric( + vertical: 9, horizontal: 12), + fillColor: + mute ? Color(0xffe3e4e4) : Colors.white, + filled: true, + border: OutlineInputBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(8), + topRight: Radius.circular(8)), + borderSide: BorderSide.none), + isDense: true, + hintText: S.of(context).chatMessageInputTitle, + hintStyle: const TextStyle( + color: Color(0xff333333), + fontSize: 18, + ), + enabled: !mute, + suffixIcon: IconButton( + onPressed: () { + if (!mute) { + setState(() { + _isExpanded = !_isExpanded; + _titleFocusNode.unfocus(); + _focusNode.unfocus(); + hideKeyboard(); + }); + } + }, + icon: SvgPicture.asset( + _isExpanded + ? 'images/ic_chat_lessen.svg' + : 'images/ic_chat_input_expand.svg', + package: kPackage, + width: 24, + height: 24, + ), + )), + style: const TextStyle( + fontWeight: FontWeight.w500, + color: CommonColors.color_333333, + fontSize: 18), + textInputAction: TextInputAction.send, + onChanged: (value) { + _handleAitText(); + }, + maxLines: 1, + enabled: !mute, + inputFormatters: [ + LengthLimitingTextInputFormatter(20), + ], + onEditingComplete: _sendTextMessage, + maxLengthEnforcement: MaxLengthEnforcement.none, + ), + SingleChildScrollView( + child: SizedBox( + height: showTitle ? null : 40, + child: TextField( + controller: inputController, + scrollController: _scrollController, + focusNode: _focusNode, + decoration: InputDecoration( + contentPadding: const EdgeInsets.symmetric( + vertical: 9, horizontal: 12), + fillColor: + mute ? Color(0xffe3e4e4) : Colors.white, + filled: true, + border: OutlineInputBorder( + borderRadius: _isExpanded + ? BorderRadius.zero + : (showTitle + ? BorderRadius.only( + bottomLeft: + Radius.circular(8), + bottomRight: + Radius.circular(8)) + : BorderRadius.circular(8)), + borderSide: BorderSide.none), + isDense: true, + hintText: hint, + hintStyle: const TextStyle( + color: Color(0xffb3b7bc), fontSize: 16), + enabled: !mute, + suffixIcon: showTitle + ? null + : IconButton( + onPressed: () { + if (!mute) { + setState(() { + _isExpanded = !_isExpanded; + _titleFocusNode.unfocus(); + _focusNode.unfocus(); + hideKeyboard(); + }); + } + }, + icon: SvgPicture.asset( + 'images/ic_chat_input_expand.svg', + package: kPackage, + width: 24, + height: 24, + ), + )), + style: const TextStyle( + color: CommonColors.color_333333, + fontSize: 16), + textInputAction: _isExpanded + ? TextInputAction.newline + : TextInputAction.send, + onChanged: (value) { + _handleAitText(); }, - ))) - .toList(), + maxLines: _isExpanded ? 8 : (showTitle ? 2 : 1), + onEditingComplete: _sendTextMessage, + enabled: !mute, + ), + ), + ), + if (_isExpanded) ...[ + Container( + height: 1, + color: '#ECECEC'.toColor(), + ), + Container( + color: Colors.white, + child: Align( + alignment: Alignment.centerRight, + child: IconButton( + onPressed: () { + if (!mute) { + _sendTextMessage(); + } + }, + icon: SvgPicture.asset( + 'images/ic_chat_send.svg', + package: kPackage, + width: 32, + height: 32, + ), + )), + ), + ] + ], + ), ), + if (_recording) + SizedBox( + height: 47, + child: Text( + S.of(context).chatMessageVoiceIn, + style: const TextStyle( + fontSize: 12, color: CommonColors.color_999999), + ), + ), + if (!_isExpanded && !_recording) + Row( + children: _getInputActions() + .map((action) => Expanded( + child: InputTextAction( + action: action, + enable: !mute, + onTap: () { + _scrollToBottom(); + if (action.enable && action.onTap != null) { + action.onTap!(context); + } + }, + ))) + .toList(), + ), if (!mute) AnimatedContainer( duration: const Duration(milliseconds: 300), diff --git a/nim_chatkit_ui/lib/view/page/chat_page.dart b/nim_chatkit_ui/lib/view/page/chat_page.dart index b17842e..61a6bc3 100644 --- a/nim_chatkit_ui/lib/view/page/chat_page.dart +++ b/nim_chatkit_ui/lib/view/page/chat_page.dart @@ -4,30 +4,35 @@ import 'dart:async'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:fluttertoast/fluttertoast.dart'; import 'package:netease_common_ui/base/base_state.dart'; import 'package:netease_common_ui/ui/dialog.dart'; +import 'package:netease_common_ui/utils/color_utils.dart'; +import 'package:netease_common_ui/widgets/no_network_tip.dart'; +import 'package:netease_corekit_im/model/contact_info.dart'; import 'package:netease_corekit_im/router/imkit_router.dart'; +import 'package:netease_corekit_im/router/imkit_router_constants.dart'; +import 'package:netease_corekit_im/router/imkit_router_factory.dart'; import 'package:netease_corekit_im/service_locator.dart'; import 'package:netease_corekit_im/services/login/login_service.dart'; +import 'package:netease_corekit_im/services/message/chat_message.dart'; import 'package:netease_corekit_im/services/message/nim_chat_cache.dart'; +import 'package:nim_chatkit/message/merge_message.dart'; import 'package:nim_chatkit/repo/chat_message_repo.dart'; -import 'package:nim_chatkit_ui/view/page/chat_setting_page.dart'; +import 'package:nim_chatkit_ui/helper/merge_message_helper.dart'; import 'package:nim_chatkit_ui/view/chat_kit_message_list/chat_kit_message_list.dart'; import 'package:nim_chatkit_ui/view/chat_kit_message_list/item/chat_kit_message_item.dart'; import 'package:nim_chatkit_ui/view/chat_kit_message_list/pop_menu/chat_kit_pop_actions.dart'; +import 'package:nim_chatkit_ui/view/page/chat_setting_page.dart'; import 'package:nim_chatkit_ui/view_model/chat_view_model.dart'; -import 'package:netease_corekit_im/services/message/chat_message.dart'; -import 'package:netease_corekit_im/router/imkit_router_constants.dart'; -import 'package:netease_corekit_im/router/imkit_router_factory.dart'; -import 'package:netease_common_ui/widgets/no_network_tip.dart'; -import 'package:netease_corekit_im/model/contact_info.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_svg/svg.dart'; import 'package:nim_core/nim_core.dart'; import 'package:provider/provider.dart'; import 'package:scroll_to_index/scroll_to_index.dart'; import '../../chat_kit_client.dart'; +import '../../helper/chat_message_helper.dart'; import '../../l10n/S.dart'; import '../../media/audio_player.dart'; import '../input/bottom_input_field.dart'; @@ -72,6 +77,12 @@ class ChatPageState extends BaseState with RouteAware { late AutoScrollController autoController; final GlobalKey _inputField = GlobalKey(); + //合并转发限制的消息数 + static const int mergedMessageLimit = 100; + + //逐条转发限制的消息数 + static const int forwardMessageLimit = 10; + Timer? _typingTimer; int _remainTime = 5; @@ -140,6 +151,9 @@ class ChatPageState extends BaseState with RouteAware { if (_isTeamDisMessageNotify(msg)) { _showTeamDismissDialog(); break; + } else if (_isTeamKickedMessageNotify(msg)) { + _showTeamKickedDialog(); + break; } } }); @@ -164,6 +178,25 @@ class ChatPageState extends BaseState with RouteAware { return false; } + ///是否是被踢出群的通知 + bool _isTeamKickedMessageNotify(NIMMessage msg) { + if (msg.sessionId == widget.sessionId && + msg.messageType == NIMMessageType.notification) { + NIMTeamNotificationAttachment attachment = + msg.messageAttachment as NIMTeamNotificationAttachment; + if (attachment.type == NIMTeamNotificationTypes.kickMember) { + NIMMemberChangeAttachment memberChangeAttachment = + attachment as NIMMemberChangeAttachment; + if (memberChangeAttachment.targets + ?.contains(getIt().userInfo?.userId) == + true) { + return true; + } + } + } + return false; + } + void _showTeamDismissDialog() { showCommonDialog( context: GlobalKey().currentContext ?? context, @@ -177,6 +210,20 @@ class ChatPageState extends BaseState with RouteAware { }); } + ///显示被踢的确认弹框 + void _showTeamKickedDialog() { + showCommonDialog( + context: GlobalKey().currentContext ?? context, + title: S.of().chatTeamBeRemovedTitle, + content: S.of().chatTeamHaveBeenKick, + showNavigate: false) + .then((value) { + if (value == true) { + Navigator.popUntil(context, ModalRoute.withName('/')); + } + }); + } + @override void onAppLifecycleState(AppLifecycleState state) { if (state == AppLifecycleState.resumed) { @@ -206,6 +253,124 @@ class ChatPageState extends BaseState with RouteAware { super.didPopNext(); } + void _mergedForward(BuildContext context) { + //判断网络 + if (!checkNetwork()) { + return; + } + var selectedMessages = context.read().selectedMessages; + //判断选择消息的数量 + if (selectedMessages.length > mergedMessageLimit) { + Fluttertoast.showToast( + msg: S + .of(context) + .chatMessageMergedForwardLimitOut(mergedMessageLimit.toString())); + return; + } + //判断不能合并转发的 + // 1. 消息深度超过最大深度 + // 2. 消息发送失败 + // 3. 消息正在发送 + var cannotMergeMessage = selectedMessages.where((e) { + if (MergeMessageHelper.getMergedMessageDepth(e) >= + MergedMessage.defaultMaxDepth) { + return true; + } + return e.status == NIMMessageStatus.fail || + e.messageType == NIMMessageType.avchat || + e.status == NIMMessageStatus.sending; + }).toList(); + if (cannotMergeMessage.isNotEmpty) { + showCommonDialog( + context: context, + content: S.of(context).chatMessageHaveCannotForwardMessages) + .then((value) { + if (value == true) { + context + .read() + .removeSelectedMessages(cannotMergeMessage); + } + }); + return; + } + if (context.read().selectedMessages.isEmpty) { + return; + } + + // 处理合并转发 + var sessionName = context.read().chatTitle; + ChatMessageHelper.showForwardMessageDialog(context, (sessionId, sessionType, + {String? postScript, bool? isLastUser}) { + context.read().mergedMessageForward(sessionId, sessionType, + postScript: postScript, + errorToast: S.of(context).chatMessageMergeMessageError, + exitMultiMode: isLastUser == true); + }, sessionName: sessionName, type: ForwardType.merge); + } + + void _forwardOneByOne(BuildContext context) { + //判断网络 + if (!checkNetwork()) { + return; + } + var selectedMessages = context.read().selectedMessages; + if (selectedMessages.length > forwardMessageLimit) { + Fluttertoast.showToast( + msg: S.of(context).chatMessageForwardOneByOneLimitOut( + forwardMessageLimit.toString())); + return; + } + + //判断有不能转发的消息 + // 1. 消息发送失败 + // 2. 消息正在发送 + // 3. 消息是语音消息 + var cannotMergeMessage = selectedMessages.where((e) { + return e.status == NIMMessageStatus.fail || + e.status == NIMMessageStatus.sending || + e.messageType == NIMMessageType.avchat || + e.messageType == NIMMessageType.audio; + }).toList(); + if (cannotMergeMessage.isNotEmpty) { + showCommonDialog( + context: context, + content: S.of(context).chatMessageHaveCannotForwardMessages) + .then((value) { + if (value == true) { + context + .read() + .removeSelectedMessages(cannotMergeMessage); + } + }); + return; + } + if (context.read().selectedMessages.isEmpty) { + return; + } + var sessionName = context.read().chatTitle; + ChatMessageHelper.showForwardMessageDialog(context, (sessionId, sessionType, + {String? postScript, bool? isLastUser}) { + context.read().forwardMessageOneByOne( + sessionId, sessionType, + postScript: postScript, exitMultiMode: isLastUser == true); + }, sessionName: sessionName, type: ForwardType.oneByOne); + } + + void _deleteMessageOneByOne(BuildContext context) { + //提前判断网络 + if (!checkNetwork()) { + return; + } + showCommonDialog( + context: context, + title: S.of().chatMessageActionDelete, + content: S.of().chatMessageDeleteConfirm) + .then((value) => { + if (value ?? false) + context.read().deleteMessageOneByOne() + }); + } + @override Widget build(BuildContext context) { if (NIMChatCache.instance.currentChatSession?.sessionId != @@ -226,117 +391,267 @@ class ChatPageState extends BaseState with RouteAware { } else { title = inputHint; } - return Scaffold( - backgroundColor: Colors.white, - appBar: AppBar( - leading: IconButton( - icon: const Icon( - Icons.arrow_back_ios_rounded, - size: 26, - ), - onPressed: () { - Navigator.pop(context); - }, - ), - centerTitle: true, - title: Text( - title, - style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), - ), - elevation: 0.5, - actions: [ - IconButton( + bool haveSelectedMessage = + context.watch().selectedMessages.isNotEmpty; + return WillPopScope( + child: Scaffold( + backgroundColor: Colors.white, + appBar: AppBar( + leading: IconButton( + icon: const Icon( + Icons.arrow_back_ios_rounded, + size: 26, + ), onPressed: () { - if (widget.sessionType == NIMSessionType.p2p) { - ContactInfo? info = - context.read().contactInfo; - if (info != null) { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => - ChatSettingPage(info))); - } - } else if (widget.sessionType == NIMSessionType.team) { - Navigator.pushNamed( - context, RouterConstants.PATH_TEAM_SETTING_PAGE, - arguments: { - 'teamId': widget.sessionId - }).then((value) { - if (value == true) { - Navigator.pop(context); - } - }); - } + Navigator.pop(context); }, - icon: SvgPicture.asset( - 'images/ic_setting.svg', - width: 26, - height: 26, - package: kPackage, - )) - ], - ), - body: Stack( - alignment: Alignment.topCenter, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, + ), + centerTitle: true, + title: Text( + title, + style: + TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), + elevation: 0.5, + actions: [ + context.watch().isMultiSelected + ? TextButton( + onPressed: () { + context.read().isMultiSelected = + false; + }, + child: Text(S.of(context).messageCancel, + maxLines: 1, + style: TextStyle( + fontSize: 16, + color: '#333333'.toColor()))) + : IconButton( + onPressed: () { + if (widget.sessionType == NIMSessionType.p2p) { + ContactInfo? info = + context.read().contactInfo; + if (info != null) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + ChatSettingPage(info))); + } + } else if (widget.sessionType == + NIMSessionType.team) { + Navigator.pushNamed(context, + RouterConstants.PATH_TEAM_SETTING_PAGE, + arguments: { + 'teamId': widget.sessionId + }).then((value) { + if (value == true) { + Navigator.pop(context); + } + }); + } + }, + icon: SvgPicture.asset( + 'images/ic_setting.svg', + width: 26, + height: 26, + package: kPackage, + )) + ], + ), + body: Stack( + alignment: Alignment.topCenter, children: [ - if (!hasNetWork) NoNetWorkTip(), - Expanded( - child: GestureDetector( - onTap: () { - _inputField.currentState.hideAllPanel(); - }, - child: ChatKitMessageList( - scrollController: autoController, - popMenuAction: widget.customPopActions ?? - chatUIConfig - ?.messageClickListener?.customPopActions, - anchor: widget.anchor, - messageBuilder: widget.messageBuilder ?? - chatUIConfig?.messageBuilder, - onTapAvatar: (String? userId, - {bool isSelf = false}) { - if (widget.onTapAvatar != null && - widget.onTapAvatar!(userId, isSelf: isSelf)) { - return true; - } - if (chatUIConfig - ?.messageClickListener?.onTapAvatar != - null && - chatUIConfig!.messageClickListener! - .onTapAvatar!(userId, isSelf: isSelf)) { - return true; - } - _defaultAvatarTap(userId, isSelf: isSelf); - return true; - }, - onAvatarLongPress: _defaultAvatarLongPress, - chatUIConfig: chatUIConfig, - teamInfo: context.watch().teamInfo, - onMessageItemClick: widget.onMessageItemClick ?? - chatUIConfig - ?.messageClickListener?.onMessageItemClick, - onMessageItemLongClick: - widget.onMessageItemLongClick ?? + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (!hasNetWork) NoNetWorkTip(), + Expanded( + child: GestureDetector( + onTap: () { + if (!context + .read() + .isMultiSelected) { + _inputField.currentState.hideAllPanel(); + } + }, + child: ChatKitMessageList( + scrollController: autoController, + popMenuAction: widget.customPopActions ?? chatUIConfig?.messageClickListener - ?.onMessageItemLongClick, + ?.customPopActions, + anchor: widget.anchor, + messageBuilder: widget.messageBuilder ?? + chatUIConfig?.messageBuilder, + onTapAvatar: (String? userId, + {bool isSelf = false}) { + if (context + .read() + .isMultiSelected) { + return true; + } + if (widget.onTapAvatar != null && + widget.onTapAvatar!(userId, + isSelf: isSelf)) { + return true; + } + if (chatUIConfig?.messageClickListener + ?.onTapAvatar != + null && + chatUIConfig!.messageClickListener! + .onTapAvatar!(userId, + isSelf: isSelf)) { + return true; + } + _defaultAvatarTap(userId, isSelf: isSelf); + return true; + }, + onAvatarLongPress: (userId, {isSelf = false}) { + if (context + .read() + .isMultiSelected) { + return true; + } + if (chatUIConfig?.messageClickListener + ?.onLongPressAvatar != + null && + chatUIConfig!.messageClickListener! + .onLongPressAvatar!(userId, + isSelf: isSelf)) { + return true; + } + return _defaultAvatarLongPress(userId, + isSelf: isSelf); + }, + chatUIConfig: chatUIConfig, + teamInfo: + context.watch().teamInfo, + onMessageItemClick: widget.onMessageItemClick ?? + chatUIConfig?.messageClickListener + ?.onMessageItemClick, + onMessageItemLongClick: + widget.onMessageItemLongClick ?? + chatUIConfig?.messageClickListener + ?.onMessageItemLongClick, + ), + ), ), - ), + if (context.watch().isMultiSelected) + Container( + color: '#EFF1F3'.toColor(), + padding: const EdgeInsets.symmetric(vertical: 10), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceEvenly, + children: [ + BottomOption( + icon: haveSelectedMessage + ? 'images/ic_chat_merge_forward.svg' + : 'images/ic_chat_merge_forward_disable.svg', + label: + S.of(context).chatMessageMergeForward, + onTap: () { + _mergedForward(context); + }, + enable: haveSelectedMessage, + ), + BottomOption( + icon: haveSelectedMessage + ? 'images/ic_chat_item_forward.svg' + : 'images/ic_chat_item_forward_disable.svg', + label: + S.of(context).chatMessageItemsForward, + onTap: () { + _forwardOneByOne(context); + }, + enable: haveSelectedMessage, + ), + BottomOption( + icon: haveSelectedMessage + ? 'images/ic_chat_delete_round.svg' + : 'images/ic_chat_delete_round_disable.svg', + label: + S.of(context).chatMessageActionDelete, + onTap: () { + _deleteMessageOneByOne(context); + }, + enable: haveSelectedMessage, + ), + ], + ), + ), + Visibility( + visible: !context + .watch() + .isMultiSelected, + maintainState: true, + child: BottomInputField( + scrollController: autoController, + sessionType: widget.sessionType, + hint: S + .of(context) + .chatMessageSendHint(inputHint), + chatUIConfig: chatUIConfig, + key: _inputField, + )), + ], ), - BottomInputField( - scrollController: autoController, - sessionType: widget.sessionType, - hint: S.of(context).chatMessageSendHint(inputHint), - chatUIConfig: chatUIConfig, - key: _inputField, - ) ], - ), - ], - )); + )), + onWillPop: () async { + if (context.read().isMultiSelected) { + context.read().isMultiSelected = false; + return false; + } + return true; + }); }); } } + +class BottomOption extends StatelessWidget { + final String icon; + + final String label; + + final Function()? onTap; + + final bool enable; + + const BottomOption( + {Key? key, + required this.icon, + required this.label, + this.onTap, + this.enable = true}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: () { + if (enable) { + onTap?.call(); + } + }, + child: Column( + children: [ + SvgPicture.asset( + icon, + package: kPackage, + width: 48, + height: 48, + ), + const SizedBox( + height: 8, + ), + Text( + label, + style: TextStyle( + decoration: TextDecoration.none, + fontSize: 14, + color: '#666666'.toColor()), + ) + ], + )); + } +} diff --git a/nim_chatkit_ui/lib/view/page/chat_search_page.dart b/nim_chatkit_ui/lib/view/page/chat_search_page.dart index 1a927d2..cb85de8 100644 --- a/nim_chatkit_ui/lib/view/page/chat_search_page.dart +++ b/nim_chatkit_ui/lib/view/page/chat_search_page.dart @@ -2,17 +2,17 @@ // Use of this source code is governed by a MIT license that can be // found in the LICENSE file. -import 'package:netease_common_ui/utils/text_search.dart'; -import 'package:nim_chatkit/repo/chat_message_repo.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; import 'package:netease_common_ui/extension.dart'; -import 'package:netease_corekit_im/router/imkit_router_constants.dart'; import 'package:netease_common_ui/ui/avatar.dart'; import 'package:netease_common_ui/utils/color_utils.dart'; +import 'package:netease_common_ui/utils/text_search.dart'; import 'package:netease_common_ui/widgets/search_page.dart'; +import 'package:netease_corekit_im/router/imkit_router_constants.dart'; import 'package:netease_corekit_im/services/message/chat_message.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:nim_chatkit_ui/view/chat_kit_message_list/helper/chat_message_user_helper.dart'; +import 'package:nim_chatkit/repo/chat_message_repo.dart'; +import 'package:nim_chatkit_ui/helper/chat_message_user_helper.dart'; import 'package:nim_core/nim_core.dart'; import '../../chat_kit_client.dart'; @@ -27,11 +27,22 @@ class ChatSearchPage extends StatefulWidget { State createState() => _ChatSearchPageState(); } -class _ChatSearchPageState extends State { - TextEditingController inputController = TextEditingController(); +class ChatSearchResult extends StatelessWidget { + final List? searchResult; + final String keyword; + + final String teamId; + + ChatSearchResult( + {this.searchResult, + required this.keyword, + Key? key, + required this.teamId}) + : super(key: key); - Widget _searchResultWidget(List? searchResult, String keyword) { - return searchResult == null || searchResult.isEmpty + @override + Widget build(BuildContext context) { + return searchResult == null || searchResult?.isEmpty == true ? Column( children: [ const SizedBox( @@ -51,15 +62,15 @@ class _ChatSearchPageState extends State { ], ) : ListView.builder( - itemCount: searchResult.length, + itemCount: searchResult!.length, itemBuilder: (context, index) { - ChatMessage item = searchResult[index]; + ChatMessage item = searchResult![index]; return InkWell( onTap: () { Navigator.pushNamedAndRemoveUntil(context, RouterConstants.PATH_CHAT_PAGE, ModalRoute.withName('/'), arguments: { - 'sessionId': widget.teamId, + 'sessionId': teamId, 'sessionType': NIMSessionType.team, 'anchor': item.nimMessage }); @@ -68,6 +79,10 @@ class _ChatSearchPageState extends State { ); }); } +} + +class _ChatSearchPageState extends State { + TextEditingController inputController = TextEditingController(); @override Widget build(BuildContext context) { @@ -83,7 +98,11 @@ class _ChatSearchPageState extends State { keyword, widget.teamId, NIMSessionType.team), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.done) { - return _searchResultWidget(snapshot.data, keyword); + return ChatSearchResult( + keyword: keyword, + teamId: widget.teamId, + searchResult: snapshot.data, + ); } return Center( child: CircularProgressIndicator(), diff --git a/nim_chatkit_ui/lib/view/page/merged_message_page.dart b/nim_chatkit_ui/lib/view/page/merged_message_page.dart new file mode 100644 index 0000000..92b205d --- /dev/null +++ b/nim_chatkit_ui/lib/view/page/merged_message_page.dart @@ -0,0 +1,106 @@ +// Copyright (c) 2022 NetEase, Inc. All rights reserved. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:netease_common/netease_common.dart'; +import 'package:nim_chatkit/message/merge_message.dart'; +import 'package:nim_chatkit/repo/chat_message_repo.dart'; +import 'package:nim_chatkit_ui/view/chat_kit_message_list/item/mergedMessage/chat_kit_merged_message_item.dart'; +import 'package:nim_core/nim_core.dart'; + +import '../../chat_kit_client.dart'; +import '../../l10n/S.dart'; +import '../chat_kit_message_list/item/chat_kit_message_item.dart'; + +class MergedMessagePage extends StatefulWidget { + final MergedMessage mergedMessage; + + final NIMMessage message; + + final ChatUIConfig? chatUIConfig; + + final ChatKitMessageBuilder? messageBuilder; + + const MergedMessagePage( + {Key? key, + required this.mergedMessage, + required this.message, + this.chatUIConfig, + this.messageBuilder}) + : super(key: key); + + @override + _MergedMessagePageState createState() => _MergedMessagePageState(); +} + +class _MergedMessagePageState extends State { + List messages = List.empty(growable: true); + + ChatUIConfig? chatUIConfig; + + @override + void initState() { + chatUIConfig = widget.chatUIConfig ?? ChatKitClient.instance.chatUIConfig; + var mergedMsg = widget.mergedMessage; + mergedMsg.messageId = widget.message.uuid; + ChatMessageRepo.getMessagesFromMergedMessage(mergedMsg).then((value) { + if (value.isSuccess && value.data != null) { + setState(() { + messages.addAll(value.data!); + }); + } else { + Alog.e( + tag: 'MergedMessagePage', + content: + 'getMessagesFromMergedMessage error: ${value.errorDetails}'); + Fluttertoast.showToast(msg: S.of(context).chatMessageInfoError); + Navigator.pop(context); + } + }); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + leading: IconButton( + icon: const Icon( + Icons.arrow_back_ios_rounded, + size: 26, + ), + onPressed: () { + Navigator.pop(context); + }, + ), + centerTitle: true, + title: Text( + S.of(context).chatMessageChatHistory, + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), + elevation: 0.5, + ), + body: Container( + color: Colors.white, + constraints: BoxConstraints.expand(), + child: ListView.builder( + itemCount: messages.length, + shrinkWrap: true, + itemBuilder: (context, index) { + var msg = messages[index]; + var lastTime = index > 0 ? messages[index - 1].timestamp : null; + return ChatKitMergedMessageItem( + message: msg, + chatTitle: widget.mergedMessage.sessionName, + lastMessageTime: lastTime, + chatUIConfig: chatUIConfig, + messageBuilder: + widget.messageBuilder ?? chatUIConfig?.messageBuilder, + ); + }, + ), + )); + } +} diff --git a/nim_chatkit_ui/lib/view_model/chat_pin_view_model.dart b/nim_chatkit_ui/lib/view_model/chat_pin_view_model.dart index db9bcf6..5d2562c 100644 --- a/nim_chatkit_ui/lib/view_model/chat_pin_view_model.dart +++ b/nim_chatkit_ui/lib/view_model/chat_pin_view_model.dart @@ -95,7 +95,18 @@ class ChatPinViewModel extends ChangeNotifier { break; } } - }) + }), + ChatServiceObserverRepo.observeMessageDelete().listen((event) { + if (event.isNotEmpty) { + for (var msg in event) { + if (msg.sessionId == sessionId && msg.sessionType == sessionType) { + _pinnedMessages.remove(ChatMessage(msg)); + isEmpty = _pinnedMessages.isEmpty; + } + } + notifyListeners(); + } + }), ]); } diff --git a/nim_chatkit_ui/lib/view_model/chat_view_model.dart b/nim_chatkit_ui/lib/view_model/chat_view_model.dart index e4e1198..0099e20 100644 --- a/nim_chatkit_ui/lib/view_model/chat_view_model.dart +++ b/nim_chatkit_ui/lib/view_model/chat_view_model.dart @@ -5,23 +5,29 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'dart:math'; +import 'package:collection/collection.dart'; +import 'package:flutter/widgets.dart'; +import 'package:fluttertoast/fluttertoast.dart'; import 'package:netease_common_ui/utils/connectivity_checker.dart'; import 'package:netease_corekit_im/im_kit_client.dart'; import 'package:netease_corekit_im/model/ait/ait_contacts_model.dart'; -import 'package:netease_corekit_im/services/message/nim_chat_cache.dart'; -import 'package:nim_chatkit/location.dart'; -import 'package:nim_chatkit/message/message_reply_info.dart'; -import 'package:nim_chatkit/message/message_revoke_info.dart'; -import 'package:nim_chatkit/repo/chat_message_repo.dart'; -import 'package:nim_chatkit/repo/chat_service_observer_repo.dart'; import 'package:netease_corekit_im/model/contact_info.dart'; import 'package:netease_corekit_im/repo/config_repo.dart'; import 'package:netease_corekit_im/service_locator.dart'; import 'package:netease_corekit_im/services/contact/contact_provider.dart'; import 'package:netease_corekit_im/services/login/login_service.dart'; import 'package:netease_corekit_im/services/message/chat_message.dart'; -import 'package:flutter/widgets.dart'; +import 'package:netease_corekit_im/services/message/nim_chat_cache.dart'; +import 'package:nim_chatkit/chatkit_client_repo.dart'; +import 'package:nim_chatkit/location.dart'; +import 'package:nim_chatkit/message/message_reply_info.dart'; +import 'package:nim_chatkit/message/message_revoke_info.dart'; +import 'package:nim_chatkit/repo/chat_message_repo.dart'; +import 'package:nim_chatkit/repo/chat_service_observer_repo.dart'; +import 'package:nim_chatkit_ui/helper/chat_message_helper.dart'; +import 'package:nim_chatkit_ui/helper/merge_message_helper.dart'; import 'package:nim_core/nim_core.dart'; import 'package:yunxin_alog/yunxin_alog.dart'; @@ -44,6 +50,10 @@ class ChatViewModel extends ChangeNotifier { ///only for p2p bool isTyping = false; + ///当消息列表中的数据少于这个值的时候自动拉取更多消息 + ///用于批量删除和删除回调 + final int _autoFetchMessageSize = 15; + set receiptTime(int value) { _receiptTime = value; notifyListeners(); @@ -96,6 +106,24 @@ class ChatViewModel extends ChangeNotifier { bool initListener = false; static const int messageLimit = 100; + //是否是多选状态 + bool _isMultiSelected = false; + + bool get isMultiSelected => _isMultiSelected; + + set isMultiSelected(bool value) { + _isMultiSelected = value; + if (!value) { + _selectedMessages.clear(); + } + notifyListeners(); + } + + //多选状态下选中的消息 + List _selectedMessages = []; + + List get selectedMessages => _selectedMessages.toList(); + ChatViewModel(this.sessionId, this.sessionType, {this.showReadAck = true}) { _setNIMMessageListener(); if (sessionType == NIMSessionType.p2p) { @@ -141,13 +169,19 @@ class ChatViewModel extends ChangeNotifier { final subscriptions = []; bool _isFilterMessage(NIMMessage message) { - // 过滤被邀请人相关通知消息 - return message.messageType == NIMMessageType.notification && - message.messageAttachment is NIMUpdateTeamAttachment && - (message.messageAttachment as NIMUpdateTeamAttachment) - .updatedFields - .updatedBeInviteMode != - null; + if (message.messageType == NIMMessageType.notification && + message.messageAttachment is NIMUpdateTeamAttachment) { + var attachment = message.messageAttachment as NIMUpdateTeamAttachment; + // 过滤被邀请人相关通知消息 + if (attachment.updatedFields.updatedBeInviteMode != null) { + return true; + } + // 过滤群信息扩展参数变更通知消息 + if (attachment.updatedFields.updatedExtension != null) { + return true; + } + } + return false; } void _setNIMMessageListener() { @@ -189,6 +223,23 @@ class ChatViewModel extends ChangeNotifier { } })); + subscriptions + .add(ChatServiceObserverRepo.observeMessageDelete().listen((event) { + if (event.isNotEmpty) { + for (var msg in event) { + if (msg.sessionId == sessionId && msg.sessionType == sessionType) { + _messageList.remove(ChatMessage(msg)); + _selectedMessages.removeWhere((e) => e.uuid == msg.uuid); + } + } + if (_messageList.length < _autoFetchMessageSize && + hasMoreForwardMessages) { + fetchMoreMessage(QueryDirection.QUERY_OLD); + } + notifyListeners(); + } + })); + //昵称更新 subscriptions.add(NIMChatCache.instance.contactInfoNotifier.listen((event) { if (event != null && @@ -437,8 +488,7 @@ class ChatViewModel extends ChangeNotifier { _onListFetchSuccess(List? list, QueryDirection direction) { if (direction == QueryDirection.QUERY_OLD) { //先判断是否有更多,在过滤 - hasMoreForwardMessages = - (list != null && list.isNotEmpty && list.length >= messageLimit); + hasMoreForwardMessages = (list != null && list.isNotEmpty); _logD( 'older forward has more:$hasMoreForwardMessages because list length = ${list?.length}'); list = _successMessageFilter(list); @@ -503,6 +553,36 @@ class ChatViewModel extends ChangeNotifier { } } + ///是否是被选中的消息 + bool isSelectedMessage(NIMMessage message) { + return _selectedMessages + .firstWhereOrNull((element) => element.uuid == message.uuid) != + null; + } + + ///添加选中的消息 + void addSelectedMessage(NIMMessage message) { + if (isSelectedMessage(message)) { + return; + } + _selectedMessages.add(message); + notifyListeners(); + } + + ///移除选中的消息 + void removeSelectedMessage(NIMMessage message) { + _selectedMessages.removeWhere((element) => element.uuid == message.uuid); + notifyListeners(); + } + + ///移除选中的消息 + void removeSelectedMessages(List messages) { + for (var msg in messages) { + _selectedMessages.removeWhere((element) => element.uuid == msg.uuid); + } + notifyListeners(); + } + void _updateMessagePin(NIMMessagePin messagePin, {bool delete = false}) { for (int i = 0; i < _messageList.length; i++) { if (_isSameMessage(_messageList[i].nimMessage, messagePin)) { @@ -516,29 +596,44 @@ class ChatViewModel extends ChangeNotifier { void sendTextMessage(String text, {NIMMessage? replyMsg, List? pushList, - AitContactsModel? aitContactsModel}) { - var aitMap = aitContactsModel?.toMap(); - - MessageBuilder.createTextMessage( - sessionId: sessionId, - sessionType: sessionType, - text: text, - ).then((value) { - if (value.isSuccess && value.data != null) { - if (sessionType == NIMSessionType.team && - pushList != null && - pushList.isNotEmpty) { - value.data!.memberPushOption = NIMMemberPushOption( - forcePushContent: value.data!.content, forcePushList: pushList); - } - if (aitMap != null) { - value.data!.remoteExtension = { - ChatMessage.keyAitMsg: aitMap, - }; - } - sendMessage(value.data!, replyMsg: replyMsg); + AitContactsModel? aitContactsModel, + String? title}) async { + var aitMap; + + if (aitContactsModel?.aitBlocks.isNotEmpty == true) { + aitMap = aitContactsModel?.toMap(); + } + + var customData = + ChatMessageHelper.getMultiLineMessageMap(title: title, content: text); + + var msgBuildResult = (title?.isNotEmpty == true) + ? (await MessageBuilder.createCustomMessage( + sessionId: sessionId, + sessionType: sessionType, + attachment: NIMCustomMessageAttachment(data: customData))) + : (await MessageBuilder.createTextMessage( + sessionId: sessionId, + sessionType: sessionType, + text: text, + )); + if (msgBuildResult.isSuccess && msgBuildResult.data != null) { + if (sessionType == NIMSessionType.team && + pushList != null && + pushList.isNotEmpty) { + msgBuildResult.data!.memberPushOption = NIMMemberPushOption( + forcePushContent: title ?? text, forcePushList: pushList); } - }); + if (title?.isNotEmpty == true) { + msgBuildResult.data!.pushContent = title; + } + if (aitMap != null) { + msgBuildResult.data!.remoteExtension = { + ChatMessage.keyAitMsg: aitMap, + }; + } + sendMessage(msgBuildResult.data!, replyMsg: replyMsg); + } } void sendAudioMessage(String filePath, int fileSize, int duration, @@ -725,15 +820,173 @@ class ChatViewModel extends ChangeNotifier { return false; } + ///合并转发 + ///[exitMultiMode] 是否退出多选模式 + ///[postScript] 转发后的附言 + ///[sessionId] 转发的目标会话id + ///[sessionType] 转发的目标会话类型 + ///[errorToast] 转发失败的提示 + void mergedMessageForward(String sessionId, NIMSessionType sessionType, + {String? postScript, + String? errorToast, + bool exitMultiMode = true}) async { + if (await haveConnectivity()) { + _selectedMessages.removeWhere((element) => + element.status == NIMMessageStatus.fail || + element.status == NIMMessageStatus.sending); + _selectedMessages.sort((a, b) => a.timestamp - b.timestamp); + MergeMessageHelper.createMergedMessage( + selectedMessages, sessionId, sessionType) + .then((value) async { + if (value.isSuccess && value.data != null) { + value.data!.messageAck = await ConfigRepo.getShowReadStatus(); + ChatMessageRepo.sendMessage(message: value.data!).then((value) { + if (value.code == ChatMessageRepo.errorInBlackList) { + ChatMessageRepo.saveTipsMessage(sessionId, sessionType, + S.of().chatMessageSendFailedByBlackList); + } + if (postScript?.isNotEmpty == true) { + ChatMessageRepo.sendTextMessageWithMessageAck( + sessionId: sessionId, + sessionType: sessionType, + text: postScript!); + } + }); + } else { + _logI( + 'createMergedMessage failed, code = ${value.code}, error = ${value.errorDetails}'); + if (errorToast?.isNotEmpty == true) { + Fluttertoast.showToast(msg: errorToast!); + } + } + if (exitMultiMode) { + isMultiSelected = false; + } + notifyListeners(); + }); + } + } + + bool filterForwardMessage(bool Function(NIMMessage) filter) { + var oldLength = _selectedMessages.length; + _selectedMessages.removeWhere((element) => filter(element)); + notifyListeners(); + return oldLength > _selectedMessages.length; + } + + ///逐条转发 + ///[exitMultiMode] 是否退出多选模式 + ///[postScript] 转发后的附言 + ///[sessionId] 转发的目标会话id + ///[sessionType] 转发的目标会话类型 + void forwardMessageOneByOne(String sessionId, NIMSessionType sessionType, + {String? postScript, bool exitMultiMode = true}) async { + if (!await haveConnectivity()) { + return; + } + _selectedMessages.removeWhere((element) => + element.status == NIMMessageStatus.fail || + element.status == NIMMessageStatus.sending); + for (var element in _selectedMessages) { + forwardMessage(element, sessionId, sessionType); + } + if (postScript?.isNotEmpty == true) { + ChatMessageRepo.sendTextMessageWithMessageAck( + sessionId: sessionId, sessionType: sessionType, text: postScript!) + .then((msgSend) { + if (msgSend.code == ChatMessageRepo.errorInBlackList) { + ChatMessageRepo.saveTipsMessage( + sessionId, sessionType, S.of().chatMessageSendFailedByBlackList); + } + }); + } + if (exitMultiMode) { + isMultiSelected = false; + } + notifyListeners(); + } + + ///逐条删除 + void deleteMessageOneByOne() async { + if (!await haveConnectivity()) { + return; + } + + if (_selectedMessages.length < 100) { + _deleteMsgList(_selectedMessages); + } else { + //远端删除消息,每次最多删除99条 + int i = 0; + int j = 99; + final deleteMessage = List.of(_selectedMessages); + while (i < deleteMessage.length && j <= deleteMessage.length) { + //异步操作防止触发频控 + await _deleteMsgList( + deleteMessage.sublist(i, min(j, deleteMessage.length))); + i = j; + j = min(j + 99, deleteMessage.length); + } + } + } + + //批量删除消息 + //如果是本地消息,则直接删除本地消息 + //如果是远程消息,则删除远程消息 + Future _deleteMsgList(List deleteMsgList) async { + var localMessage = deleteMsgList + .where((element) => + element.status == NIMMessageStatus.fail || + (element.serverId ?? 0) <= 0) + .toList(); + if (localMessage.isNotEmpty) { + await ChatMessageRepo.deleteLocalMessageList(localMessage); + var uuidList = localMessage.map((e) => e.uuid).toList(); + _messageList.removeWhere((e) => uuidList.contains(e.nimMessage.uuid)); + } + + var remoteMessage = deleteMsgList + .where((element) => + element.status == NIMMessageStatus.success || + (element.serverId ?? 0) > 0) + .toList(); + if (remoteMessage.isNotEmpty) { + var remoteResult = await ChatMessageRepo.deleteMessageList(remoteMessage); + if (remoteResult.isSuccess) { + var uuidList = remoteMessage.map((e) => e.uuid).toList(); + _messageList.removeWhere((e) => uuidList.contains(e.nimMessage.uuid)); + } + } + isMultiSelected = false; + notifyListeners(); + if (_messageList.length < _autoFetchMessageSize && hasMoreForwardMessages) { + fetchMoreMessage(QueryDirection.QUERY_OLD); + } + } + void forwardMessage( - NIMMessage message, String sessionId, NIMSessionType sessionType) async { + NIMMessage message, String sessionId, NIMSessionType sessionType, + {String? postScript}) async { if (await haveConnectivity()) { + message.messageAck = await ConfigRepo.getShowReadStatus(); ChatMessageRepo.forwardMessage(message, sessionId, sessionType) .then((value) { if (value.code == ChatMessageRepo.errorInBlackList) { ChatMessageRepo.saveTipsMessage( sessionId, sessionType, S.of().chatMessageSendFailedByBlackList); } + if (postScript?.isNotEmpty == true) { + ChatMessageRepo.sendTextMessageWithMessageAck( + sessionId: sessionId, + sessionType: sessionType, + text: postScript!) + .then((msgSend) { + if (msgSend.code == ChatMessageRepo.errorInBlackList) { + ChatMessageRepo.saveTipsMessage(sessionId, sessionType, + S.of().chatMessageSendFailedByBlackList); + } + }); + } + notifyListeners(); }); } } @@ -809,49 +1062,17 @@ class ChatViewModel extends ChangeNotifier { } void _onMessageRevoked(ChatMessage revokedMsg) async { - RevokedMessageInfo? revokedMessageInfo = - RevokedMessageInfo.getRevokedMessage(revokedMsg.nimMessage); - //创建一条特殊的占位消息 - MessageBuilder.createTextMessage( - sessionId: revokedMsg.nimMessage.sessionId!, - sessionType: revokedMsg.nimMessage.sessionType!, - text: S.of().chatMessageHaveBeenRevoked) - .then((msgResult) { - if (msgResult.isSuccess && msgResult.data != null) { - //设置撤回标识 - if (msgResult.data!.localExtension != null) { - msgResult.data!.localExtension![ChatMessage.keyRevokeMsg] = true; - msgResult.data!.localExtension![ChatMessage.keyRevokeMsgContent] = - revokedMessageInfo?.toMap(); - } else { - msgResult.data!.localExtension = { - ChatMessage.keyRevokeMsg: true, - ChatMessage.keyRevokeMsgContent: revokedMessageInfo?.toMap() - }; - } - msgResult.data!.fromAccount = revokedMsg.nimMessage.fromAccount; - msgResult.data!.messageDirection = - revokedMsg.nimMessage.fromAccount == IMKitClient.account() - ? NIMMessageDirection.outgoing - : NIMMessageDirection.received; - msgResult.data!.config = - NIMCustomMessageConfig(enableUnreadCount: false); - //将占位消息插入到本地 - NimCore.instance.messageService - .saveMessageToLocalEx( - message: msgResult.data!, time: revokedMsg.nimMessage.timestamp) - .then((savedMsg) { - //找到位置,跟新 - if (savedMsg.isSuccess && savedMsg.data != null) { - int pos = _messageList.indexOf(revokedMsg); - if (pos >= 0) { - _messageList[pos] = ChatMessage(savedMsg.data!); - notifyListeners(); - } - } - }); + final localMessage = await ChatKitClientRepo.instance + .onMessageRevoked(revokedMsg, S.of().chatMessageHaveBeenRevoked); + if (localMessage.isSuccess && localMessage.data != null) { + int pos = _messageList.indexOf(revokedMsg); + if (pos >= 0) { + _messageList[pos] = ChatMessage(localMessage.data!); + _selectedMessages.removeWhere( + (element) => element.uuid == revokedMsg.nimMessage.uuid); + notifyListeners(); } - }); + } } void sendMessageP2PReceipt(NIMMessage message) { diff --git a/nim_chatkit_ui/pubspec.yaml b/nim_chatkit_ui/pubspec.yaml index 3a8295a..dc305f0 100644 --- a/nim_chatkit_ui/pubspec.yaml +++ b/nim_chatkit_ui/pubspec.yaml @@ -1,6 +1,6 @@ name: nim_chatkit_ui description: Chat UI base on ChatKit. -version: 1.2.0 +version: 9.7.0 homepage: https://github.com/netease-kit/nim-uikit-flutter environment: @@ -17,31 +17,34 @@ dependencies: sdk: flutter netease_common_ui: - ">=1.2.0 <1.3.0" + ">=9.7.0 <10.0.0" # path: ../../common/netease_common_ui netease_corekit_im: - ">=1.2.0 <1.3.0" + ">=9.7.0 <10.0.0" # path: ../../netease_corekit/netease_corekit_im netease_corekit: ">=1.2.0 <1.3.0" # path: ../../netease_corekit/netease_corekit nim_chatkit: - ">=1.2.0 <1.3.0" + ">=9.7.0 <10.0.0" # path: ../nim_chatkit + netease_common: ^1.0.4 +# path: ../../common/common + scroll_to_index: ^3.0.1 image_picker: ^1.0.1 - cached_network_image: ^3.2.0 + cached_network_image: ^3.3.1 video_player: ^2.4.2 photo_view: ^0.14.0 audioplayers: ^5.0.0 visibility_detector: ^0.4.0+2 image_gallery_saver: ^2.0.3 - fluttertoast: ^8.0.9 + fluttertoast: ^8.2.4 dio: ^5.3.0 super_tooltip: ^1.0.1 - nim_core: ^1.7.3 + nim_core: ^1.7.4 # path: ../../nim_core/nim_core intl: ^0.18.0 flutter_svg: ^2.0.7 diff --git a/nim_contactkit_ui/.flutter-plugins b/nim_contactkit_ui/.flutter-plugins index 9e1cb82..2b200a1 100644 --- a/nim_contactkit_ui/.flutter-plugins +++ b/nim_contactkit_ui/.flutter-plugins @@ -1,11 +1,13 @@ # This is a generated file; do not edit or check into version control. -connectivity_plus=/Users/houchengdong/.pub-cache/hosted/pub.dev/connectivity_plus-4.0.2/ +alog_macos=/Users/houchengdong/.pub-cache/hosted/pub.dev/alog_macos-1.0.12/ +alog_windows=/Users/houchengdong/.pub-cache/hosted/pub.dev/alog_windows-1.0.12/ +connectivity_plus=/Users/houchengdong/.pub-cache/hosted/pub.dev/connectivity_plus-5.0.1/ device_info_plus=/Users/houchengdong/.pub-cache/hosted/pub.dev/device_info_plus-9.0.2/ file_selector_linux=/Users/houchengdong/.pub-cache/hosted/pub.dev/file_selector_linux-0.9.2/ file_selector_macos=/Users/houchengdong/.pub-cache/hosted/pub.dev/file_selector_macos-0.9.3+1/ file_selector_windows=/Users/houchengdong/.pub-cache/hosted/pub.dev/file_selector_windows-0.9.3/ flutter_plugin_android_lifecycle=/Users/houchengdong/.pub-cache/hosted/pub.dev/flutter_plugin_android_lifecycle-2.0.8/ -fluttertoast=/Users/houchengdong/.pub-cache/hosted/pub.dev/fluttertoast-8.2.1/ +fluttertoast=/Users/houchengdong/.pub-cache/hosted/pub.dev/fluttertoast-8.2.4/ image_picker=/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker-1.0.2/ image_picker_android=/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker_android-0.8.7+4/ image_picker_for_web=/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker_for_web-3.0.0/ @@ -13,7 +15,7 @@ image_picker_ios=/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker_ios- image_picker_linux=/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker_linux-0.2.1/ image_picker_macos=/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker_macos-0.2.1/ image_picker_windows=/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker_windows-0.2.1/ -nim_core=/Users/houchengdong/.pub-cache/hosted/pub.dev/nim_core-1.7.0/ +nim_core=/Users/houchengdong/.pub-cache/hosted/pub.dev/nim_core-1.7.4/ nim_core_macos=/Users/houchengdong/.pub-cache/hosted/pub.dev/nim_core_macos-1.0.8/ nim_core_web=/Users/houchengdong/.pub-cache/hosted/pub.dev/nim_core_web-1.0.2/ nim_core_windows=/Users/houchengdong/.pub-cache/hosted/pub.dev/nim_core_windows-1.0.8/ @@ -22,18 +24,18 @@ path_provider_android=/Users/houchengdong/.pub-cache/hosted/pub.dev/path_provide path_provider_foundation=/Users/houchengdong/.pub-cache/hosted/pub.dev/path_provider_foundation-2.1.2/ path_provider_linux=/Users/houchengdong/.pub-cache/hosted/pub.dev/path_provider_linux-2.1.9/ path_provider_windows=/Users/houchengdong/.pub-cache/hosted/pub.dev/path_provider_windows-2.1.7/ -permission_handler=/Users/houchengdong/.pub-cache/hosted/pub.dev/permission_handler-10.3.0/ -permission_handler_android=/Users/houchengdong/.pub-cache/hosted/pub.dev/permission_handler_android-10.2.0/ -permission_handler_apple=/Users/houchengdong/.pub-cache/hosted/pub.dev/permission_handler_apple-9.1.0/ -permission_handler_windows=/Users/houchengdong/.pub-cache/hosted/pub.dev/permission_handler_windows-0.1.2/ +permission_handler=/Users/houchengdong/.pub-cache/hosted/pub.dev/permission_handler-11.0.1/ +permission_handler_android=/Users/houchengdong/.pub-cache/hosted/pub.dev/permission_handler_android-11.1.0/ +permission_handler_apple=/Users/houchengdong/.pub-cache/hosted/pub.dev/permission_handler_apple-9.1.4/ +permission_handler_windows=/Users/houchengdong/.pub-cache/hosted/pub.dev/permission_handler_windows-0.1.3/ shared_preferences=/Users/houchengdong/.pub-cache/hosted/pub.dev/shared_preferences-2.0.18/ shared_preferences_android=/Users/houchengdong/.pub-cache/hosted/pub.dev/shared_preferences_android-2.0.16/ shared_preferences_foundation=/Users/houchengdong/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.1.4/ shared_preferences_linux=/Users/houchengdong/.pub-cache/hosted/pub.dev/shared_preferences_linux-2.1.4/ shared_preferences_web=/Users/houchengdong/.pub-cache/hosted/pub.dev/shared_preferences_web-2.0.5/ shared_preferences_windows=/Users/houchengdong/.pub-cache/hosted/pub.dev/shared_preferences_windows-2.1.4/ -sqflite=/Users/houchengdong/.pub-cache/hosted/pub.dev/sqflite-2.0.3+1/ +sqflite=/Users/houchengdong/.pub-cache/hosted/pub.dev/sqflite-2.3.0/ webview_flutter=/Users/houchengdong/.pub-cache/hosted/pub.dev/webview_flutter-3.0.4/ webview_flutter_android=/Users/houchengdong/.pub-cache/hosted/pub.dev/webview_flutter_android-2.10.4/ webview_flutter_wkwebview=/Users/houchengdong/.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-2.9.5/ -yunxin_alog=/Users/houchengdong/.pub-cache/hosted/pub.dev/yunxin_alog-1.0.11/ +yunxin_alog=/Users/houchengdong/.pub-cache/hosted/pub.dev/yunxin_alog-2.0.0/ diff --git a/nim_contactkit_ui/.flutter-plugins-dependencies b/nim_contactkit_ui/.flutter-plugins-dependencies index 05139ad..e11808f 100644 --- a/nim_contactkit_ui/.flutter-plugins-dependencies +++ b/nim_contactkit_ui/.flutter-plugins-dependencies @@ -1 +1 @@ -{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"connectivity_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/connectivity_plus-4.0.2/","native_build":true,"dependencies":[]},{"name":"device_info_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/device_info_plus-9.0.2/","native_build":true,"dependencies":[]},{"name":"fluttertoast","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/fluttertoast-8.2.1/","native_build":true,"dependencies":[]},{"name":"image_picker_ios","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker_ios-0.8.8/","native_build":true,"dependencies":[]},{"name":"nim_core","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/nim_core-1.7.0/","native_build":true,"dependencies":["yunxin_alog"]},{"name":"path_provider_foundation","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/path_provider_foundation-2.1.2/","shared_darwin_source":true,"native_build":true,"dependencies":[]},{"name":"permission_handler_apple","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/permission_handler_apple-9.1.0/","native_build":true,"dependencies":[]},{"name":"shared_preferences_foundation","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.1.4/","shared_darwin_source":true,"native_build":true,"dependencies":[]},{"name":"sqflite","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/sqflite-2.0.3+1/","native_build":true,"dependencies":[]},{"name":"webview_flutter_wkwebview","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-2.9.5/","native_build":true,"dependencies":[]},{"name":"yunxin_alog","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/yunxin_alog-1.0.11/","native_build":true,"dependencies":[]}],"android":[{"name":"connectivity_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/connectivity_plus-4.0.2/","native_build":true,"dependencies":[]},{"name":"device_info_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/device_info_plus-9.0.2/","native_build":true,"dependencies":[]},{"name":"flutter_plugin_android_lifecycle","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/flutter_plugin_android_lifecycle-2.0.8/","native_build":true,"dependencies":[]},{"name":"fluttertoast","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/fluttertoast-8.2.1/","native_build":true,"dependencies":[]},{"name":"image_picker_android","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker_android-0.8.7+4/","native_build":true,"dependencies":["flutter_plugin_android_lifecycle"]},{"name":"nim_core","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/nim_core-1.7.0/","native_build":true,"dependencies":["yunxin_alog"]},{"name":"path_provider_android","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/path_provider_android-2.0.23/","native_build":true,"dependencies":[]},{"name":"permission_handler_android","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/permission_handler_android-10.2.0/","native_build":true,"dependencies":[]},{"name":"shared_preferences_android","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/shared_preferences_android-2.0.16/","native_build":true,"dependencies":[]},{"name":"sqflite","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/sqflite-2.0.3+1/","native_build":true,"dependencies":[]},{"name":"webview_flutter_android","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/webview_flutter_android-2.10.4/","native_build":true,"dependencies":[]},{"name":"yunxin_alog","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/yunxin_alog-1.0.11/","native_build":true,"dependencies":[]}],"macos":[{"name":"connectivity_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/connectivity_plus-4.0.2/","native_build":true,"dependencies":[]},{"name":"device_info_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/device_info_plus-9.0.2/","native_build":true,"dependencies":[]},{"name":"file_selector_macos","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/file_selector_macos-0.9.3+1/","native_build":true,"dependencies":[]},{"name":"image_picker_macos","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker_macos-0.2.1/","native_build":false,"dependencies":["file_selector_macos"]},{"name":"nim_core_macos","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/nim_core_macos-1.0.8/","native_build":true,"dependencies":[]},{"name":"path_provider_foundation","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/path_provider_foundation-2.1.2/","shared_darwin_source":true,"native_build":true,"dependencies":[]},{"name":"shared_preferences_foundation","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.1.4/","shared_darwin_source":true,"native_build":true,"dependencies":[]},{"name":"sqflite","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/sqflite-2.0.3+1/","native_build":true,"dependencies":[]}],"linux":[{"name":"connectivity_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/connectivity_plus-4.0.2/","native_build":false,"dependencies":[]},{"name":"device_info_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/device_info_plus-9.0.2/","native_build":false,"dependencies":[]},{"name":"file_selector_linux","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/file_selector_linux-0.9.2/","native_build":true,"dependencies":[]},{"name":"image_picker_linux","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker_linux-0.2.1/","native_build":false,"dependencies":["file_selector_linux"]},{"name":"path_provider_linux","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/path_provider_linux-2.1.9/","native_build":false,"dependencies":[]},{"name":"shared_preferences_linux","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/shared_preferences_linux-2.1.4/","native_build":false,"dependencies":["path_provider_linux"]}],"windows":[{"name":"connectivity_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/connectivity_plus-4.0.2/","native_build":true,"dependencies":[]},{"name":"device_info_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/device_info_plus-9.0.2/","native_build":false,"dependencies":[]},{"name":"file_selector_windows","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/file_selector_windows-0.9.3/","native_build":true,"dependencies":[]},{"name":"image_picker_windows","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker_windows-0.2.1/","native_build":false,"dependencies":["file_selector_windows"]},{"name":"nim_core_windows","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/nim_core_windows-1.0.8/","native_build":true,"dependencies":[]},{"name":"path_provider_windows","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/path_provider_windows-2.1.7/","native_build":false,"dependencies":[]},{"name":"permission_handler_windows","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/permission_handler_windows-0.1.2/","native_build":true,"dependencies":[]},{"name":"shared_preferences_windows","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/shared_preferences_windows-2.1.4/","native_build":false,"dependencies":["path_provider_windows"]}],"web":[{"name":"connectivity_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/connectivity_plus-4.0.2/","dependencies":[]},{"name":"device_info_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/device_info_plus-9.0.2/","dependencies":[]},{"name":"fluttertoast","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/fluttertoast-8.2.1/","dependencies":[]},{"name":"image_picker_for_web","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker_for_web-3.0.0/","dependencies":[]},{"name":"nim_core_web","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/nim_core_web-1.0.2/","dependencies":[]},{"name":"shared_preferences_web","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/shared_preferences_web-2.0.5/","dependencies":[]}]},"dependencyGraph":[{"name":"connectivity_plus","dependencies":[]},{"name":"device_info_plus","dependencies":[]},{"name":"file_selector_linux","dependencies":[]},{"name":"file_selector_macos","dependencies":[]},{"name":"file_selector_windows","dependencies":[]},{"name":"flutter_plugin_android_lifecycle","dependencies":[]},{"name":"fluttertoast","dependencies":[]},{"name":"image_picker","dependencies":["image_picker_android","image_picker_for_web","image_picker_ios","image_picker_linux","image_picker_macos","image_picker_windows"]},{"name":"image_picker_android","dependencies":["flutter_plugin_android_lifecycle"]},{"name":"image_picker_for_web","dependencies":[]},{"name":"image_picker_ios","dependencies":[]},{"name":"image_picker_linux","dependencies":["file_selector_linux"]},{"name":"image_picker_macos","dependencies":["file_selector_macos"]},{"name":"image_picker_windows","dependencies":["file_selector_windows"]},{"name":"nim_core","dependencies":["nim_core_windows","nim_core_macos","nim_core_web","path_provider","yunxin_alog"]},{"name":"nim_core_macos","dependencies":[]},{"name":"nim_core_web","dependencies":[]},{"name":"nim_core_windows","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"permission_handler","dependencies":["permission_handler_android","permission_handler_apple","permission_handler_windows"]},{"name":"permission_handler_android","dependencies":[]},{"name":"permission_handler_apple","dependencies":[]},{"name":"permission_handler_windows","dependencies":[]},{"name":"shared_preferences","dependencies":["shared_preferences_android","shared_preferences_foundation","shared_preferences_linux","shared_preferences_web","shared_preferences_windows"]},{"name":"shared_preferences_android","dependencies":[]},{"name":"shared_preferences_foundation","dependencies":[]},{"name":"shared_preferences_linux","dependencies":["path_provider_linux"]},{"name":"shared_preferences_web","dependencies":[]},{"name":"shared_preferences_windows","dependencies":["path_provider_windows"]},{"name":"sqflite","dependencies":[]},{"name":"webview_flutter","dependencies":["webview_flutter_android","webview_flutter_wkwebview"]},{"name":"webview_flutter_android","dependencies":[]},{"name":"webview_flutter_wkwebview","dependencies":[]},{"name":"yunxin_alog","dependencies":[]}],"date_created":"2023-08-21 19:56:52.122260","version":"3.10.3"} \ No newline at end of file +{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"connectivity_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/connectivity_plus-5.0.1/","native_build":true,"dependencies":[]},{"name":"device_info_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/device_info_plus-9.0.2/","native_build":true,"dependencies":[]},{"name":"fluttertoast","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/fluttertoast-8.2.4/","native_build":true,"dependencies":[]},{"name":"image_picker_ios","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker_ios-0.8.8/","native_build":true,"dependencies":[]},{"name":"nim_core","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/nim_core-1.7.4/","native_build":true,"dependencies":["yunxin_alog"]},{"name":"path_provider_foundation","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/path_provider_foundation-2.1.2/","shared_darwin_source":true,"native_build":true,"dependencies":[]},{"name":"permission_handler_apple","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/permission_handler_apple-9.1.4/","native_build":true,"dependencies":[]},{"name":"shared_preferences_foundation","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.1.4/","shared_darwin_source":true,"native_build":true,"dependencies":[]},{"name":"sqflite","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/sqflite-2.3.0/","native_build":true,"dependencies":[]},{"name":"webview_flutter_wkwebview","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-2.9.5/","native_build":true,"dependencies":[]},{"name":"yunxin_alog","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/yunxin_alog-2.0.0/","native_build":true,"dependencies":[]}],"android":[{"name":"connectivity_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/connectivity_plus-5.0.1/","native_build":true,"dependencies":[]},{"name":"device_info_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/device_info_plus-9.0.2/","native_build":true,"dependencies":[]},{"name":"flutter_plugin_android_lifecycle","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/flutter_plugin_android_lifecycle-2.0.8/","native_build":true,"dependencies":[]},{"name":"fluttertoast","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/fluttertoast-8.2.4/","native_build":true,"dependencies":[]},{"name":"image_picker_android","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker_android-0.8.7+4/","native_build":true,"dependencies":["flutter_plugin_android_lifecycle"]},{"name":"nim_core","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/nim_core-1.7.4/","native_build":true,"dependencies":["yunxin_alog"]},{"name":"path_provider_android","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/path_provider_android-2.0.23/","native_build":true,"dependencies":[]},{"name":"permission_handler_android","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/permission_handler_android-11.1.0/","native_build":true,"dependencies":[]},{"name":"shared_preferences_android","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/shared_preferences_android-2.0.16/","native_build":true,"dependencies":[]},{"name":"sqflite","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/sqflite-2.3.0/","native_build":true,"dependencies":[]},{"name":"webview_flutter_android","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/webview_flutter_android-2.10.4/","native_build":true,"dependencies":[]},{"name":"yunxin_alog","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/yunxin_alog-2.0.0/","native_build":true,"dependencies":[]}],"macos":[{"name":"alog_macos","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/alog_macos-1.0.12/","native_build":true,"dependencies":[]},{"name":"connectivity_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/connectivity_plus-5.0.1/","native_build":true,"dependencies":[]},{"name":"device_info_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/device_info_plus-9.0.2/","native_build":true,"dependencies":[]},{"name":"file_selector_macos","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/file_selector_macos-0.9.3+1/","native_build":true,"dependencies":[]},{"name":"image_picker_macos","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker_macos-0.2.1/","native_build":false,"dependencies":["file_selector_macos"]},{"name":"nim_core_macos","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/nim_core_macos-1.0.8/","native_build":true,"dependencies":[]},{"name":"path_provider_foundation","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/path_provider_foundation-2.1.2/","shared_darwin_source":true,"native_build":true,"dependencies":[]},{"name":"shared_preferences_foundation","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.1.4/","shared_darwin_source":true,"native_build":true,"dependencies":[]},{"name":"sqflite","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/sqflite-2.3.0/","native_build":true,"dependencies":[]}],"linux":[{"name":"connectivity_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/connectivity_plus-5.0.1/","native_build":false,"dependencies":[]},{"name":"device_info_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/device_info_plus-9.0.2/","native_build":false,"dependencies":[]},{"name":"file_selector_linux","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/file_selector_linux-0.9.2/","native_build":true,"dependencies":[]},{"name":"image_picker_linux","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker_linux-0.2.1/","native_build":false,"dependencies":["file_selector_linux"]},{"name":"path_provider_linux","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/path_provider_linux-2.1.9/","native_build":false,"dependencies":[]},{"name":"shared_preferences_linux","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/shared_preferences_linux-2.1.4/","native_build":false,"dependencies":["path_provider_linux"]}],"windows":[{"name":"alog_windows","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/alog_windows-1.0.12/","native_build":true,"dependencies":[]},{"name":"connectivity_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/connectivity_plus-5.0.1/","native_build":true,"dependencies":[]},{"name":"device_info_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/device_info_plus-9.0.2/","native_build":false,"dependencies":[]},{"name":"file_selector_windows","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/file_selector_windows-0.9.3/","native_build":true,"dependencies":[]},{"name":"image_picker_windows","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker_windows-0.2.1/","native_build":false,"dependencies":["file_selector_windows"]},{"name":"nim_core_windows","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/nim_core_windows-1.0.8/","native_build":true,"dependencies":[]},{"name":"path_provider_windows","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/path_provider_windows-2.1.7/","native_build":false,"dependencies":[]},{"name":"permission_handler_windows","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/permission_handler_windows-0.1.3/","native_build":true,"dependencies":[]},{"name":"shared_preferences_windows","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/shared_preferences_windows-2.1.4/","native_build":false,"dependencies":["path_provider_windows"]}],"web":[{"name":"connectivity_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/connectivity_plus-5.0.1/","dependencies":[]},{"name":"device_info_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/device_info_plus-9.0.2/","dependencies":[]},{"name":"fluttertoast","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/fluttertoast-8.2.4/","dependencies":[]},{"name":"image_picker_for_web","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker_for_web-3.0.0/","dependencies":[]},{"name":"nim_core_web","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/nim_core_web-1.0.2/","dependencies":[]},{"name":"shared_preferences_web","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/shared_preferences_web-2.0.5/","dependencies":[]}]},"dependencyGraph":[{"name":"alog_macos","dependencies":[]},{"name":"alog_windows","dependencies":[]},{"name":"connectivity_plus","dependencies":[]},{"name":"device_info_plus","dependencies":[]},{"name":"file_selector_linux","dependencies":[]},{"name":"file_selector_macos","dependencies":[]},{"name":"file_selector_windows","dependencies":[]},{"name":"flutter_plugin_android_lifecycle","dependencies":[]},{"name":"fluttertoast","dependencies":[]},{"name":"image_picker","dependencies":["image_picker_android","image_picker_for_web","image_picker_ios","image_picker_linux","image_picker_macos","image_picker_windows"]},{"name":"image_picker_android","dependencies":["flutter_plugin_android_lifecycle"]},{"name":"image_picker_for_web","dependencies":[]},{"name":"image_picker_ios","dependencies":[]},{"name":"image_picker_linux","dependencies":["file_selector_linux"]},{"name":"image_picker_macos","dependencies":["file_selector_macos"]},{"name":"image_picker_windows","dependencies":["file_selector_windows"]},{"name":"nim_core","dependencies":["nim_core_windows","nim_core_macos","nim_core_web","path_provider","yunxin_alog"]},{"name":"nim_core_macos","dependencies":[]},{"name":"nim_core_web","dependencies":[]},{"name":"nim_core_windows","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"permission_handler","dependencies":["permission_handler_android","permission_handler_apple","permission_handler_windows"]},{"name":"permission_handler_android","dependencies":[]},{"name":"permission_handler_apple","dependencies":[]},{"name":"permission_handler_windows","dependencies":[]},{"name":"shared_preferences","dependencies":["shared_preferences_android","shared_preferences_foundation","shared_preferences_linux","shared_preferences_web","shared_preferences_windows"]},{"name":"shared_preferences_android","dependencies":[]},{"name":"shared_preferences_foundation","dependencies":[]},{"name":"shared_preferences_linux","dependencies":["path_provider_linux"]},{"name":"shared_preferences_web","dependencies":[]},{"name":"shared_preferences_windows","dependencies":["path_provider_windows"]},{"name":"sqflite","dependencies":[]},{"name":"webview_flutter","dependencies":["webview_flutter_android","webview_flutter_wkwebview"]},{"name":"webview_flutter_android","dependencies":[]},{"name":"webview_flutter_wkwebview","dependencies":[]},{"name":"yunxin_alog","dependencies":["alog_macos","alog_windows"]}],"date_created":"2024-01-25 14:03:03.355243","version":"3.10.6"} \ No newline at end of file diff --git a/nim_contactkit_ui/CHANGELOG.md b/nim_contactkit_ui/CHANGELOG.md index 147cebb..b0b5f2e 100644 --- a/nim_contactkit_ui/CHANGELOG.md +++ b/nim_contactkit_ui/CHANGELOG.md @@ -1,5 +1,11 @@ # NimContactKitUI ChangeLog +## 9.7.0(Jan 25, 2024) +### New Features +* 新增合并转发和多选功能 +* 新增群管理 +* 新增换行消息 + ## 1.2.0(Nov 8, 2023) ### Bug Fixes * 优化缓存,提高性能 diff --git a/nim_contactkit_ui/pubspec.yaml b/nim_contactkit_ui/pubspec.yaml index c64ef78..cc997e7 100644 --- a/nim_contactkit_ui/pubspec.yaml +++ b/nim_contactkit_ui/pubspec.yaml @@ -1,6 +1,6 @@ name: nim_contactkit_ui description: Contact UI base on ContactKit. -version: 1.2.0 +version: 9.7.0 homepage: https://github.com/netease-kit/nim-uikit-flutter # Remove this line if you wish to publish to pub.dev #publish_to: none @@ -16,27 +16,27 @@ dependencies: sdk: flutter nim_contactkit: - ">=1.2.0 <1.3.0" + ">=9.7.0 <10.0.0" # path: ../nim_contactkit netease_corekit_im: - ">=1.2.0 <1.3.0" + ">=9.7.0 <10.0.0" # path: ../../netease_corekit/netease_corekit_im netease_corekit: ">=1.2.0 <1.3.0" # path: ../../netease_corekit/netease_corekit netease_common_ui: - ">=1.2.0 <1.3.0" + ">=9.7.0 <10.0.0" # path: ../../common/netease_common_ui azlistview: ^2.0.0 provider: ^6.0.0 lpinyin: ^2.0.3 pull_to_refresh: ^2.0.0 - nim_core: ^1.7.3 + nim_core: ^1.7.4 # path: ../../nim_core/nim_core intl: ^0.18.0 yunxin_alog: ^2.0.0 connectivity_plus: ^5.0.0 - fluttertoast: ^8.0.9 + fluttertoast: ^8.2.4 flutter_svg: ^2.0.7 diff --git a/nim_conversationkit_ui/CHANGELOG.md b/nim_conversationkit_ui/CHANGELOG.md index 00c8e7c..2da6d1e 100644 --- a/nim_conversationkit_ui/CHANGELOG.md +++ b/nim_conversationkit_ui/CHANGELOG.md @@ -1,5 +1,11 @@ # NimConversationKitUI ChangeLog +## 9.7.0(Jan 25, 2024) +### New Features +* 新增合并转发和多选功能 +* 新增群管理 +* 新增换行消息 + ## 1.2.0(Nov 8, 2023) ### Bug Fixes * 优化List请求,提高性能 diff --git a/nim_conversationkit_ui/lib/l10n/conversation_localization/conversation_kit_client_localizations.dart b/nim_conversationkit_ui/lib/l10n/conversation_localization/conversation_kit_client_localizations.dart index 62703a1..688c65e 100644 --- a/nim_conversationkit_ui/lib/l10n/conversation_localization/conversation_kit_client_localizations.dart +++ b/nim_conversationkit_ui/lib/l10n/conversation_localization/conversation_kit_client_localizations.dart @@ -244,6 +244,18 @@ abstract class ConversationKitClientLocalizations { /// In en, this message translates to: /// **'[Notification]'** String get notificationMessageType; + + /// No description provided for @tipMessageType. + /// + /// In en, this message translates to: + /// **'[Tip]'** + String get tipMessageType; + + /// No description provided for @chatHistoryBrief. + /// + /// In en, this message translates to: + /// **'[Chat history]'** + String get chatHistoryBrief; } class _ConversationKitClientLocalizationsDelegate diff --git a/nim_conversationkit_ui/lib/l10n/conversation_localization/conversation_kit_client_localizations_en.dart b/nim_conversationkit_ui/lib/l10n/conversation_localization/conversation_kit_client_localizations_en.dart index ccc2af4..2fa7ffd 100644 --- a/nim_conversationkit_ui/lib/l10n/conversation_localization/conversation_kit_client_localizations_en.dart +++ b/nim_conversationkit_ui/lib/l10n/conversation_localization/conversation_kit_client_localizations_en.dart @@ -83,4 +83,10 @@ class ConversationKitClientLocalizationsEn @override String get notificationMessageType => '[Notification]'; + + @override + String get tipMessageType => '[Tip]'; + + @override + String get chatHistoryBrief => '[Chat history]'; } diff --git a/nim_conversationkit_ui/lib/l10n/conversation_localization/conversation_kit_client_localizations_zh.dart b/nim_conversationkit_ui/lib/l10n/conversation_localization/conversation_kit_client_localizations_zh.dart index 34ece8e..63d972c 100644 --- a/nim_conversationkit_ui/lib/l10n/conversation_localization/conversation_kit_client_localizations_zh.dart +++ b/nim_conversationkit_ui/lib/l10n/conversation_localization/conversation_kit_client_localizations_zh.dart @@ -82,4 +82,10 @@ class ConversationKitClientLocalizationsZh @override String get notificationMessageType => '[通知消息]'; + + @override + String get tipMessageType => '[提醒消息]'; + + @override + String get chatHistoryBrief => '[聊天记录]'; } diff --git a/nim_conversationkit_ui/lib/l10n/intl_en.arb b/nim_conversationkit_ui/lib/l10n/intl_en.arb index 879c481..6a347d5 100644 --- a/nim_conversationkit_ui/lib/l10n/intl_en.arb +++ b/nim_conversationkit_ui/lib/l10n/intl_en.arb @@ -30,5 +30,7 @@ "videoMessageType": "[Video]", "locationMessageType": "[Location]", "fileMessageType": "[File]", - "notificationMessageType": "[Notification]" + "notificationMessageType": "[Notification]", + "tipMessageType": "[Tip]", + "chatHistoryBrief": "[Chat history]" } \ No newline at end of file diff --git a/nim_conversationkit_ui/lib/l10n/intl_zh.arb b/nim_conversationkit_ui/lib/l10n/intl_zh.arb index 67e49a2..4e8413e 100644 --- a/nim_conversationkit_ui/lib/l10n/intl_zh.arb +++ b/nim_conversationkit_ui/lib/l10n/intl_zh.arb @@ -30,5 +30,7 @@ "videoMessageType": "[视频]", "locationMessageType": "[位置]", "fileMessageType": "[文件]", - "notificationMessageType": "[通知消息]" + "notificationMessageType": "[通知消息]", + "tipMessageType": "[提醒消息]", + "chatHistoryBrief": "[聊天记录]" } \ No newline at end of file diff --git a/nim_conversationkit_ui/lib/view_model/conversation_view_model.dart b/nim_conversationkit_ui/lib/view_model/conversation_view_model.dart index 6f07013..e591f4e 100644 --- a/nim_conversationkit_ui/lib/view_model/conversation_view_model.dart +++ b/nim_conversationkit_ui/lib/view_model/conversation_view_model.dart @@ -4,19 +4,19 @@ import 'dart:async'; +import 'package:flutter/material.dart'; import 'package:netease_common_ui/utils/connectivity_checker.dart'; import 'package:netease_corekit_im/im_kit_client.dart'; +import 'package:netease_corekit_im/service_locator.dart'; +import 'package:netease_corekit_im/services/login/login_service.dart'; import 'package:netease_corekit_im/services/message/message_provider.dart'; import 'package:nim_conversationkit/extention.dart'; import 'package:nim_conversationkit/model/conversation_info.dart'; import 'package:nim_conversationkit/repo/conversation_repo.dart'; -import 'package:netease_corekit_im/service_locator.dart'; -import 'package:netease_corekit_im/services/login/login_service.dart'; -import 'package:flutter/material.dart'; import 'package:nim_conversationkit_ui/conversation_kit_client.dart'; +import 'package:nim_conversationkit_ui/service/ait/ait_server.dart'; import 'package:nim_core/nim_core.dart'; import 'package:yunxin_alog/yunxin_alog.dart'; -import 'package:nim_conversationkit_ui/service/ait/ait_server.dart'; class ConversationViewModel extends ChangeNotifier { final String modelName = 'ConversationViewModel'; @@ -181,6 +181,16 @@ class ConversationViewModel extends ChangeNotifier { } })); + // all read observer for ios + subscriptions.add( + NimCore.instance.messageService.allMessagesReadForIOS.listen((event) { + _logI('allMessagesReadForIOS'); + _conversationList.forEach((element) { + element.session.unreadCount = 0; + }); + notifyListeners(); + })); + //异步加载userInfo 回调 subscriptions .add(ConversationRepo.instance.onUserInfoUpdated.listen((event) { @@ -284,13 +294,26 @@ class ConversationViewModel extends ChangeNotifier { _logI( 'insertIndex:$insertIndex unread:${conversationInfo.session.unreadCount} haveBeenAit:${conversationInfo.haveBeenAit}'); _conversationList.insert(insertIndex, conversationInfo); - } else { + } else if (_isMySession(conversationInfo)) { int insertIndex = _searchComparatorIndex(conversationInfo); _conversationList.insert(insertIndex, conversationInfo); } notifyListeners(); } + /// 是否是我的会话,如果不是则不展示 + /// p2p 一定是我的会话 + /// team 有可能不是我的会话 + bool _isMySession(ConversationInfo conversationInfo) { + if (conversationInfo.session.sessionType == NIMSessionType.p2p) { + return true; + } + if (conversationInfo.session.sessionType == NIMSessionType.team) { + return conversationInfo.team?.isMyTeam == true; + } + return true; + } + _deleteItem(String sessionId, NIMSessionType sessionType) { int index = _conversationList.indexWhere((element) => element.session.sessionId == sessionId && @@ -350,7 +373,11 @@ class ConversationViewModel extends ChangeNotifier { session.lastMessageAttachment as NIMTeamNotificationAttachment; var accId = getIt().userInfo?.userId; return notify.type == NIMTeamNotificationTypes.dismissTeam || - notify.type == NIMTeamNotificationTypes.kickMember || + (notify.type == NIMTeamNotificationTypes.kickMember && + (notify as NIMMemberChangeAttachment?) + ?.targets + ?.contains(accId) == + true) || (notify.type == NIMTeamNotificationTypes.leaveTeam && session.senderAccount == accId); } diff --git a/nim_conversationkit_ui/lib/widgets/conversation_item.dart b/nim_conversationkit_ui/lib/widgets/conversation_item.dart index 8f6ec7f..018134c 100644 --- a/nim_conversationkit_ui/lib/widgets/conversation_item.dart +++ b/nim_conversationkit_ui/lib/widgets/conversation_item.dart @@ -2,15 +2,17 @@ // Use of this source code is governed by a MIT license that can be // found in the LICENSE file. +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; import 'package:netease_common_ui/extension.dart'; import 'package:netease_common_ui/ui/avatar.dart'; import 'package:netease_common_ui/widgets/unread_message.dart'; +import 'package:netease_corekit_im/model/custom_type_constant.dart'; +import 'package:netease_corekit_im/services/message/chat_message.dart'; import 'package:nim_conversationkit/model/conversation_info.dart'; import 'package:nim_conversationkit_ui/conversation_kit_client.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:nim_core/nim_core.dart'; import 'package:nim_conversationkit_ui/l10n/S.dart'; +import 'package:nim_core/nim_core.dart'; bool isSupportMessageType(NIMMessageType? type) { return type == NIMMessageType.text || @@ -39,10 +41,16 @@ class ConversationItem extends StatelessWidget { final int index; String _getLastMessageContent(BuildContext context) { + var configMessageContent = + config.lastMessageContentBuilder?.call(context, conversationInfo); + if (configMessageContent?.isNotEmpty == true) { + return configMessageContent!; + } switch (conversationInfo.session.lastMessageType) { case NIMMessageType.text: - case NIMMessageType.tip: return conversationInfo.session.lastMessageContent ?? ''; + case NIMMessageType.tip: + return S.of(context).tipMessageType; case NIMMessageType.audio: return S.of(context).audioMessageType; case NIMMessageType.image: @@ -57,9 +65,9 @@ class ConversationItem extends StatelessWidget { return S.of(context).locationMessageType; case NIMMessageType.custom: var customLastMessageContent = - config.lastMessageContentBuilder?.call(context, conversationInfo); - if (customLastMessageContent != null) { - return customLastMessageContent; + _getCustomLastMessageBrief(context, conversationInfo); + if (customLastMessageContent?.isNotEmpty == true) { + return customLastMessageContent!; } return S.of(context).chatMessageNonsupportType; default: @@ -67,6 +75,29 @@ class ConversationItem extends StatelessWidget { } } + String? _getCustomLastMessageBrief( + BuildContext context, ConversationInfo conversationInfo) { + if (conversationInfo.session.lastMessageAttachment + is NIMCustomMessageAttachment) { + var data = (conversationInfo.session.lastMessageAttachment + as NIMCustomMessageAttachment) + .data; + if (data?[CustomMessageKey.type] == + CustomMessageType.customMergeMessageType) { + return S.of(context).chatHistoryBrief; + } + if (data?[CustomMessageKey.type] == + CustomMessageType.customMultiLineMessageType) { + var dataMap = data?[CustomMessageKey.data] as Map?; + var title = dataMap?[ChatMessage.keyMultiLineTitle] as String?; + if (title != null) { + return title; + } + } + } + return null; + } + @override Widget build(BuildContext context) { String? avatar; diff --git a/nim_conversationkit_ui/pubspec.yaml b/nim_conversationkit_ui/pubspec.yaml index f86574a..a81d095 100644 --- a/nim_conversationkit_ui/pubspec.yaml +++ b/nim_conversationkit_ui/pubspec.yaml @@ -1,6 +1,6 @@ name: nim_conversationkit_ui description: Conversation UI base on ConversationKit. -version: 1.2.0 +version: 9.7.0 homepage: https://github.com/netease-kit/nim-uikit-flutter environment: @@ -17,27 +17,27 @@ dependencies: sdk: flutter netease_common_ui: - ">=1.2.0 <1.3.0" + ">=9.7.0 <10.0.0" # path: ../../common/netease_common_ui nim_conversationkit: - ">=1.2.0 <1.3.0" + ">=9.7.0 <10.0.0" # path: ../nim_conversationkit netease_corekit_im: - ">=1.2.0 <1.3.0" + ">=9.7.0 <10.0.0" # path: ../../netease_corekit/netease_corekit_im netease_corekit: ">=1.2.0 <1.3.0" # path: ../../netease_corekit/netease_corekit - nim_core: ^1.7.3 + nim_core: ^1.7.4 # path: ../../nim_core/nim_core + path_provider: ^2.0.11 + sqflite: ^2.3.0 intl: ^0.18.0 flutter_svg: ^2.0.7 provider: ^6.0.0 yunxin_alog: ^2.0.0 connectivity_plus: ^5.0.0 flutter_slidable: ^3.0.0 - path_provider: ^2.0.11 - sqflite: ^2.3.0 dev_dependencies: flutter_test: diff --git a/nim_searchkit_ui/.flutter-plugins b/nim_searchkit_ui/.flutter-plugins index 097e8ed..e39fb54 100644 --- a/nim_searchkit_ui/.flutter-plugins +++ b/nim_searchkit_ui/.flutter-plugins @@ -7,7 +7,7 @@ file_selector_linux=/Users/houchengdong/.pub-cache/hosted/pub.dev/file_selector_ file_selector_macos=/Users/houchengdong/.pub-cache/hosted/pub.dev/file_selector_macos-0.9.3+1/ file_selector_windows=/Users/houchengdong/.pub-cache/hosted/pub.dev/file_selector_windows-0.9.3/ flutter_plugin_android_lifecycle=/Users/houchengdong/.pub-cache/hosted/pub.dev/flutter_plugin_android_lifecycle-2.0.7/ -fluttertoast=/Users/houchengdong/.pub-cache/hosted/pub.dev/fluttertoast-8.1.3/ +fluttertoast=/Users/houchengdong/.pub-cache/hosted/pub.dev/fluttertoast-8.2.4/ image_picker=/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker-1.0.2/ image_picker_android=/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker_android-0.8.7+4/ image_picker_for_web=/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker_for_web-3.0.0/ @@ -16,9 +16,9 @@ image_picker_linux=/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker_li image_picker_macos=/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker_macos-0.2.1/ image_picker_windows=/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker_windows-0.2.1/ nim_core=/Users/houchengdong/flutterProject/xkit-flutter/nim_core/nim_core/ -nim_core_macos=/Users/houchengdong/.pub-cache/hosted/pub.dev/nim_core_macos-1.0.8/ -nim_core_web=/Users/houchengdong/.pub-cache/hosted/pub.dev/nim_core_web-1.0.2/ -nim_core_windows=/Users/houchengdong/.pub-cache/hosted/pub.dev/nim_core_windows-1.0.8/ +nim_core_macos=/Users/houchengdong/flutterProject/xkit-flutter/nim_core/nim_core_macos/ +nim_core_web=/Users/houchengdong/flutterProject/xkit-flutter/nim_core/nim_core_web/ +nim_core_windows=/Users/houchengdong/flutterProject/xkit-flutter/nim_core/nim_core_windows/ path_provider=/Users/houchengdong/.pub-cache/hosted/pub.dev/path_provider-2.0.12/ path_provider_android=/Users/houchengdong/.pub-cache/hosted/pub.dev/path_provider_android-2.0.22/ path_provider_foundation=/Users/houchengdong/.pub-cache/hosted/pub.dev/path_provider_foundation-2.1.1/ diff --git a/nim_searchkit_ui/.flutter-plugins-dependencies b/nim_searchkit_ui/.flutter-plugins-dependencies index c6d701c..9e2affc 100644 --- a/nim_searchkit_ui/.flutter-plugins-dependencies +++ b/nim_searchkit_ui/.flutter-plugins-dependencies @@ -1 +1 @@ -{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"connectivity_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/connectivity_plus-5.0.1/","native_build":true,"dependencies":[]},{"name":"device_info_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/device_info_plus-9.0.2/","native_build":true,"dependencies":[]},{"name":"fluttertoast","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/fluttertoast-8.1.3/","native_build":true,"dependencies":[]},{"name":"image_picker_ios","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker_ios-0.8.8/","native_build":true,"dependencies":[]},{"name":"nim_core","path":"/Users/houchengdong/flutterProject/xkit-flutter/nim_core/nim_core/","native_build":true,"dependencies":["yunxin_alog"]},{"name":"path_provider_foundation","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/path_provider_foundation-2.1.1/","shared_darwin_source":true,"native_build":true,"dependencies":[]},{"name":"permission_handler_apple","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/permission_handler_apple-9.1.4/","native_build":true,"dependencies":[]},{"name":"shared_preferences_foundation","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.1.3/","shared_darwin_source":true,"native_build":true,"dependencies":[]},{"name":"sqflite","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/sqflite-2.3.0/","native_build":true,"dependencies":[]},{"name":"webview_flutter_wkwebview","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-2.9.5/","native_build":true,"dependencies":[]},{"name":"yunxin_alog","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/yunxin_alog-2.0.0/","native_build":true,"dependencies":[]}],"android":[{"name":"connectivity_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/connectivity_plus-5.0.1/","native_build":true,"dependencies":[]},{"name":"device_info_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/device_info_plus-9.0.2/","native_build":true,"dependencies":[]},{"name":"flutter_plugin_android_lifecycle","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/flutter_plugin_android_lifecycle-2.0.7/","native_build":true,"dependencies":[]},{"name":"fluttertoast","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/fluttertoast-8.1.3/","native_build":true,"dependencies":[]},{"name":"image_picker_android","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker_android-0.8.7+4/","native_build":true,"dependencies":["flutter_plugin_android_lifecycle"]},{"name":"nim_core","path":"/Users/houchengdong/flutterProject/xkit-flutter/nim_core/nim_core/","native_build":true,"dependencies":["yunxin_alog"]},{"name":"path_provider_android","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/path_provider_android-2.0.22/","native_build":true,"dependencies":[]},{"name":"permission_handler_android","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/permission_handler_android-11.1.0/","native_build":true,"dependencies":[]},{"name":"shared_preferences_android","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/shared_preferences_android-2.0.15/","native_build":true,"dependencies":[]},{"name":"sqflite","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/sqflite-2.3.0/","native_build":true,"dependencies":[]},{"name":"webview_flutter_android","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/webview_flutter_android-2.10.4/","native_build":true,"dependencies":[]},{"name":"yunxin_alog","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/yunxin_alog-2.0.0/","native_build":true,"dependencies":[]}],"macos":[{"name":"alog_macos","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/alog_macos-1.0.12/","native_build":true,"dependencies":[]},{"name":"connectivity_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/connectivity_plus-5.0.1/","native_build":true,"dependencies":[]},{"name":"device_info_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/device_info_plus-9.0.2/","native_build":true,"dependencies":[]},{"name":"file_selector_macos","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/file_selector_macos-0.9.3+1/","native_build":true,"dependencies":[]},{"name":"image_picker_macos","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker_macos-0.2.1/","native_build":false,"dependencies":["file_selector_macos"]},{"name":"nim_core_macos","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/nim_core_macos-1.0.8/","native_build":true,"dependencies":[]},{"name":"path_provider_foundation","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/path_provider_foundation-2.1.1/","shared_darwin_source":true,"native_build":true,"dependencies":[]},{"name":"shared_preferences_foundation","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.1.3/","shared_darwin_source":true,"native_build":true,"dependencies":[]},{"name":"sqflite","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/sqflite-2.3.0/","native_build":true,"dependencies":[]}],"linux":[{"name":"connectivity_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/connectivity_plus-5.0.1/","native_build":false,"dependencies":[]},{"name":"device_info_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/device_info_plus-9.0.2/","native_build":false,"dependencies":[]},{"name":"file_selector_linux","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/file_selector_linux-0.9.2/","native_build":true,"dependencies":[]},{"name":"image_picker_linux","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker_linux-0.2.1/","native_build":false,"dependencies":["file_selector_linux"]},{"name":"path_provider_linux","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/path_provider_linux-2.1.8/","native_build":false,"dependencies":[]},{"name":"shared_preferences_linux","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/shared_preferences_linux-2.1.3/","native_build":false,"dependencies":["path_provider_linux"]}],"windows":[{"name":"alog_windows","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/alog_windows-1.0.12/","native_build":true,"dependencies":[]},{"name":"connectivity_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/connectivity_plus-5.0.1/","native_build":true,"dependencies":[]},{"name":"device_info_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/device_info_plus-9.0.2/","native_build":false,"dependencies":[]},{"name":"file_selector_windows","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/file_selector_windows-0.9.3/","native_build":true,"dependencies":[]},{"name":"image_picker_windows","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker_windows-0.2.1/","native_build":false,"dependencies":["file_selector_windows"]},{"name":"nim_core_windows","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/nim_core_windows-1.0.8/","native_build":true,"dependencies":[]},{"name":"path_provider_windows","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/path_provider_windows-2.1.7/","native_build":false,"dependencies":[]},{"name":"permission_handler_windows","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/permission_handler_windows-0.1.3/","native_build":true,"dependencies":[]},{"name":"shared_preferences_windows","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/shared_preferences_windows-2.1.3/","native_build":false,"dependencies":["path_provider_windows"]}],"web":[{"name":"connectivity_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/connectivity_plus-5.0.1/","dependencies":[]},{"name":"device_info_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/device_info_plus-9.0.2/","dependencies":[]},{"name":"fluttertoast","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/fluttertoast-8.1.3/","dependencies":[]},{"name":"image_picker_for_web","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker_for_web-3.0.0/","dependencies":[]},{"name":"nim_core_web","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/nim_core_web-1.0.2/","dependencies":[]},{"name":"shared_preferences_web","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/shared_preferences_web-2.0.4/","dependencies":[]}]},"dependencyGraph":[{"name":"alog_macos","dependencies":[]},{"name":"alog_windows","dependencies":[]},{"name":"connectivity_plus","dependencies":[]},{"name":"device_info_plus","dependencies":[]},{"name":"file_selector_linux","dependencies":[]},{"name":"file_selector_macos","dependencies":[]},{"name":"file_selector_windows","dependencies":[]},{"name":"flutter_plugin_android_lifecycle","dependencies":[]},{"name":"fluttertoast","dependencies":[]},{"name":"image_picker","dependencies":["image_picker_android","image_picker_for_web","image_picker_ios","image_picker_linux","image_picker_macos","image_picker_windows"]},{"name":"image_picker_android","dependencies":["flutter_plugin_android_lifecycle"]},{"name":"image_picker_for_web","dependencies":[]},{"name":"image_picker_ios","dependencies":[]},{"name":"image_picker_linux","dependencies":["file_selector_linux"]},{"name":"image_picker_macos","dependencies":["file_selector_macos"]},{"name":"image_picker_windows","dependencies":["file_selector_windows"]},{"name":"nim_core","dependencies":["nim_core_windows","nim_core_macos","nim_core_web","path_provider","yunxin_alog"]},{"name":"nim_core_macos","dependencies":[]},{"name":"nim_core_web","dependencies":[]},{"name":"nim_core_windows","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"permission_handler","dependencies":["permission_handler_android","permission_handler_apple","permission_handler_windows"]},{"name":"permission_handler_android","dependencies":[]},{"name":"permission_handler_apple","dependencies":[]},{"name":"permission_handler_windows","dependencies":[]},{"name":"shared_preferences","dependencies":["shared_preferences_android","shared_preferences_foundation","shared_preferences_linux","shared_preferences_web","shared_preferences_windows"]},{"name":"shared_preferences_android","dependencies":[]},{"name":"shared_preferences_foundation","dependencies":[]},{"name":"shared_preferences_linux","dependencies":["path_provider_linux"]},{"name":"shared_preferences_web","dependencies":[]},{"name":"shared_preferences_windows","dependencies":["path_provider_windows"]},{"name":"sqflite","dependencies":[]},{"name":"webview_flutter","dependencies":["webview_flutter_android","webview_flutter_wkwebview"]},{"name":"webview_flutter_android","dependencies":[]},{"name":"webview_flutter_wkwebview","dependencies":[]},{"name":"yunxin_alog","dependencies":["alog_macos","alog_windows"]}],"date_created":"2023-11-06 16:27:18.307604","version":"3.10.3"} \ No newline at end of file +{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"connectivity_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/connectivity_plus-5.0.1/","native_build":true,"dependencies":[]},{"name":"device_info_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/device_info_plus-9.0.2/","native_build":true,"dependencies":[]},{"name":"fluttertoast","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/fluttertoast-8.2.4/","native_build":true,"dependencies":[]},{"name":"image_picker_ios","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker_ios-0.8.8/","native_build":true,"dependencies":[]},{"name":"nim_core","path":"/Users/houchengdong/flutterProject/xkit-flutter/nim_core/nim_core/","native_build":true,"dependencies":["yunxin_alog"]},{"name":"path_provider_foundation","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/path_provider_foundation-2.1.1/","shared_darwin_source":true,"native_build":true,"dependencies":[]},{"name":"permission_handler_apple","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/permission_handler_apple-9.1.4/","native_build":true,"dependencies":[]},{"name":"shared_preferences_foundation","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.1.3/","shared_darwin_source":true,"native_build":true,"dependencies":[]},{"name":"sqflite","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/sqflite-2.3.0/","native_build":true,"dependencies":[]},{"name":"webview_flutter_wkwebview","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-2.9.5/","native_build":true,"dependencies":[]},{"name":"yunxin_alog","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/yunxin_alog-2.0.0/","native_build":true,"dependencies":[]}],"android":[{"name":"connectivity_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/connectivity_plus-5.0.1/","native_build":true,"dependencies":[]},{"name":"device_info_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/device_info_plus-9.0.2/","native_build":true,"dependencies":[]},{"name":"flutter_plugin_android_lifecycle","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/flutter_plugin_android_lifecycle-2.0.7/","native_build":true,"dependencies":[]},{"name":"fluttertoast","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/fluttertoast-8.2.4/","native_build":true,"dependencies":[]},{"name":"image_picker_android","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker_android-0.8.7+4/","native_build":true,"dependencies":["flutter_plugin_android_lifecycle"]},{"name":"nim_core","path":"/Users/houchengdong/flutterProject/xkit-flutter/nim_core/nim_core/","native_build":true,"dependencies":["yunxin_alog"]},{"name":"path_provider_android","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/path_provider_android-2.0.22/","native_build":true,"dependencies":[]},{"name":"permission_handler_android","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/permission_handler_android-11.1.0/","native_build":true,"dependencies":[]},{"name":"shared_preferences_android","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/shared_preferences_android-2.0.15/","native_build":true,"dependencies":[]},{"name":"sqflite","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/sqflite-2.3.0/","native_build":true,"dependencies":[]},{"name":"webview_flutter_android","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/webview_flutter_android-2.10.4/","native_build":true,"dependencies":[]},{"name":"yunxin_alog","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/yunxin_alog-2.0.0/","native_build":true,"dependencies":[]}],"macos":[{"name":"alog_macos","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/alog_macos-1.0.12/","native_build":true,"dependencies":[]},{"name":"connectivity_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/connectivity_plus-5.0.1/","native_build":true,"dependencies":[]},{"name":"device_info_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/device_info_plus-9.0.2/","native_build":true,"dependencies":[]},{"name":"file_selector_macos","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/file_selector_macos-0.9.3+1/","native_build":true,"dependencies":[]},{"name":"image_picker_macos","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker_macos-0.2.1/","native_build":false,"dependencies":["file_selector_macos"]},{"name":"nim_core_macos","path":"/Users/houchengdong/flutterProject/xkit-flutter/nim_core/nim_core_macos/","native_build":true,"dependencies":[]},{"name":"path_provider_foundation","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/path_provider_foundation-2.1.1/","shared_darwin_source":true,"native_build":true,"dependencies":[]},{"name":"shared_preferences_foundation","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.1.3/","shared_darwin_source":true,"native_build":true,"dependencies":[]},{"name":"sqflite","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/sqflite-2.3.0/","native_build":true,"dependencies":[]}],"linux":[{"name":"connectivity_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/connectivity_plus-5.0.1/","native_build":false,"dependencies":[]},{"name":"device_info_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/device_info_plus-9.0.2/","native_build":false,"dependencies":[]},{"name":"file_selector_linux","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/file_selector_linux-0.9.2/","native_build":true,"dependencies":[]},{"name":"image_picker_linux","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker_linux-0.2.1/","native_build":false,"dependencies":["file_selector_linux"]},{"name":"path_provider_linux","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/path_provider_linux-2.1.8/","native_build":false,"dependencies":[]},{"name":"shared_preferences_linux","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/shared_preferences_linux-2.1.3/","native_build":false,"dependencies":["path_provider_linux"]}],"windows":[{"name":"alog_windows","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/alog_windows-1.0.12/","native_build":true,"dependencies":[]},{"name":"connectivity_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/connectivity_plus-5.0.1/","native_build":true,"dependencies":[]},{"name":"device_info_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/device_info_plus-9.0.2/","native_build":false,"dependencies":[]},{"name":"file_selector_windows","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/file_selector_windows-0.9.3/","native_build":true,"dependencies":[]},{"name":"image_picker_windows","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker_windows-0.2.1/","native_build":false,"dependencies":["file_selector_windows"]},{"name":"nim_core_windows","path":"/Users/houchengdong/flutterProject/xkit-flutter/nim_core/nim_core_windows/","native_build":true,"dependencies":[]},{"name":"path_provider_windows","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/path_provider_windows-2.1.7/","native_build":false,"dependencies":[]},{"name":"permission_handler_windows","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/permission_handler_windows-0.1.3/","native_build":true,"dependencies":[]},{"name":"shared_preferences_windows","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/shared_preferences_windows-2.1.3/","native_build":false,"dependencies":["path_provider_windows"]}],"web":[{"name":"connectivity_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/connectivity_plus-5.0.1/","dependencies":[]},{"name":"device_info_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/device_info_plus-9.0.2/","dependencies":[]},{"name":"fluttertoast","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/fluttertoast-8.2.4/","dependencies":[]},{"name":"image_picker_for_web","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker_for_web-3.0.0/","dependencies":[]},{"name":"nim_core_web","path":"/Users/houchengdong/flutterProject/xkit-flutter/nim_core/nim_core_web/","dependencies":[]},{"name":"shared_preferences_web","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/shared_preferences_web-2.0.4/","dependencies":[]}]},"dependencyGraph":[{"name":"alog_macos","dependencies":[]},{"name":"alog_windows","dependencies":[]},{"name":"connectivity_plus","dependencies":[]},{"name":"device_info_plus","dependencies":[]},{"name":"file_selector_linux","dependencies":[]},{"name":"file_selector_macos","dependencies":[]},{"name":"file_selector_windows","dependencies":[]},{"name":"flutter_plugin_android_lifecycle","dependencies":[]},{"name":"fluttertoast","dependencies":[]},{"name":"image_picker","dependencies":["image_picker_android","image_picker_for_web","image_picker_ios","image_picker_linux","image_picker_macos","image_picker_windows"]},{"name":"image_picker_android","dependencies":["flutter_plugin_android_lifecycle"]},{"name":"image_picker_for_web","dependencies":[]},{"name":"image_picker_ios","dependencies":[]},{"name":"image_picker_linux","dependencies":["file_selector_linux"]},{"name":"image_picker_macos","dependencies":["file_selector_macos"]},{"name":"image_picker_windows","dependencies":["file_selector_windows"]},{"name":"nim_core","dependencies":["nim_core_windows","nim_core_macos","nim_core_web","path_provider","yunxin_alog"]},{"name":"nim_core_macos","dependencies":[]},{"name":"nim_core_web","dependencies":[]},{"name":"nim_core_windows","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"permission_handler","dependencies":["permission_handler_android","permission_handler_apple","permission_handler_windows"]},{"name":"permission_handler_android","dependencies":[]},{"name":"permission_handler_apple","dependencies":[]},{"name":"permission_handler_windows","dependencies":[]},{"name":"shared_preferences","dependencies":["shared_preferences_android","shared_preferences_foundation","shared_preferences_linux","shared_preferences_web","shared_preferences_windows"]},{"name":"shared_preferences_android","dependencies":[]},{"name":"shared_preferences_foundation","dependencies":[]},{"name":"shared_preferences_linux","dependencies":["path_provider_linux"]},{"name":"shared_preferences_web","dependencies":[]},{"name":"shared_preferences_windows","dependencies":["path_provider_windows"]},{"name":"sqflite","dependencies":[]},{"name":"webview_flutter","dependencies":["webview_flutter_android","webview_flutter_wkwebview"]},{"name":"webview_flutter_android","dependencies":[]},{"name":"webview_flutter_wkwebview","dependencies":[]},{"name":"yunxin_alog","dependencies":["alog_macos","alog_windows"]}],"date_created":"2024-01-18 18:38:11.320921","version":"3.13.8"} \ No newline at end of file diff --git a/nim_searchkit_ui/CHANGELOG.md b/nim_searchkit_ui/CHANGELOG.md index 0adad07..f450636 100644 --- a/nim_searchkit_ui/CHANGELOG.md +++ b/nim_searchkit_ui/CHANGELOG.md @@ -1,5 +1,11 @@ # NimSearchKitUI ChangeLog +## 9.7.0(Jan 25, 2024) +### New Features +* 新增合并转发和多选功能 +* 新增群管理 +* 新增换行消息 + ## 1.2.0(Nov 8, 2023) ### Bug Fixes * 优化List请求,提高性能 diff --git a/nim_searchkit_ui/lib/l10n/intl_en.arb b/nim_searchkit_ui/lib/l10n/intl_en.arb index 23cd38d..47a0ed5 100644 --- a/nim_searchkit_ui/lib/l10n/intl_en.arb +++ b/nim_searchkit_ui/lib/l10n/intl_en.arb @@ -5,5 +5,7 @@ "searchSearchFriend": "Friend", "searchSearchNormalTeam": "Normal team", "searchSearchAdvanceTeam": "Advance Team", - "searchEmptyTips": "This user not exist" + "searchEmptyTips": "This user not exist", + "searchTeamLeave": "Leave Team", + "searchTeamDismissOrLeave": "Dismiss or leave team" } \ No newline at end of file diff --git a/nim_searchkit_ui/lib/l10n/intl_zh.arb b/nim_searchkit_ui/lib/l10n/intl_zh.arb index c6f7e8e..3bc0ad6 100644 --- a/nim_searchkit_ui/lib/l10n/intl_zh.arb +++ b/nim_searchkit_ui/lib/l10n/intl_zh.arb @@ -5,5 +5,7 @@ "searchSearchFriend": "好友", "searchSearchNormalTeam": "讨论组", "searchSearchAdvanceTeam": "高级群", - "searchEmptyTips": "该用户不存在" + "searchEmptyTips": "该用户不存在", + "searchTeamLeave": "离开群聊", + "searchTeamDismissOrLeave": "您已被移除群聊或群聊已解散" } \ No newline at end of file diff --git a/nim_searchkit_ui/lib/l10n/search_localization/search_kit_client_localizations.dart b/nim_searchkit_ui/lib/l10n/search_localization/search_kit_client_localizations.dart index a50350a..4e224a0 100644 --- a/nim_searchkit_ui/lib/l10n/search_localization/search_kit_client_localizations.dart +++ b/nim_searchkit_ui/lib/l10n/search_localization/search_kit_client_localizations.dart @@ -136,6 +136,18 @@ abstract class SearchKitClientLocalizations { /// In en, this message translates to: /// **'This user not exist'** String get searchEmptyTips; + + /// No description provided for @searchTeamLeave. + /// + /// In en, this message translates to: + /// **'Leave Team'** + String get searchTeamLeave; + + /// No description provided for @searchTeamDismissOrLeave. + /// + /// In en, this message translates to: + /// **'Dismiss or leave team'** + String get searchTeamDismissOrLeave; } class _SearchKitClientLocalizationsDelegate diff --git a/nim_searchkit_ui/lib/l10n/search_localization/search_kit_client_localizations_en.dart b/nim_searchkit_ui/lib/l10n/search_localization/search_kit_client_localizations_en.dart index b36ef54..f2cbbb9 100644 --- a/nim_searchkit_ui/lib/l10n/search_localization/search_kit_client_localizations_en.dart +++ b/nim_searchkit_ui/lib/l10n/search_localization/search_kit_client_localizations_en.dart @@ -25,4 +25,10 @@ class SearchKitClientLocalizationsEn extends SearchKitClientLocalizations { @override String get searchEmptyTips => 'This user not exist'; + + @override + String get searchTeamLeave => 'Leave Team'; + + @override + String get searchTeamDismissOrLeave => 'Dismiss or leave team'; } diff --git a/nim_searchkit_ui/lib/l10n/search_localization/search_kit_client_localizations_zh.dart b/nim_searchkit_ui/lib/l10n/search_localization/search_kit_client_localizations_zh.dart index d290849..178868e 100644 --- a/nim_searchkit_ui/lib/l10n/search_localization/search_kit_client_localizations_zh.dart +++ b/nim_searchkit_ui/lib/l10n/search_localization/search_kit_client_localizations_zh.dart @@ -25,4 +25,10 @@ class SearchKitClientLocalizationsZh extends SearchKitClientLocalizations { @override String get searchEmptyTips => '该用户不存在'; + + @override + String get searchTeamLeave => '离开群聊'; + + @override + String get searchTeamDismissOrLeave => '您已被移除群聊或群聊已解散'; } diff --git a/nim_searchkit_ui/lib/page/search_kit_search_page.dart b/nim_searchkit_ui/lib/page/search_kit_search_page.dart index c5870e3..a31f37e 100644 --- a/nim_searchkit_ui/lib/page/search_kit_search_page.dart +++ b/nim_searchkit_ui/lib/page/search_kit_search_page.dart @@ -2,12 +2,13 @@ // Use of this source code is governed by a MIT license that can be // found in the LICENSE file. +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; import 'package:netease_common_ui/ui/avatar.dart'; +import 'package:netease_common_ui/ui/dialog.dart'; import 'package:netease_common_ui/utils/color_utils.dart'; import 'package:netease_common_ui/widgets/search_page.dart'; import 'package:netease_corekit_im/model/contact_info.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_svg/svg.dart'; import 'package:netease_corekit_im/router/imkit_router_factory.dart'; import 'package:nim_core/nim_core.dart'; import 'package:nim_searchkit/model/friend_search_info.dart'; @@ -148,7 +149,18 @@ class _SearchKitGlobalState extends State { NIMTeam team = (currentItem as TeamSearchInfo).team; return InkWell( onTap: () { - goToTeamChat(context, team.id!); + NimCore.instance.teamService.queryTeam(team.id!).then((value) { + if (value.isSuccess && + value.data != null && + value.data!.isMyTeam == true) { + goToTeamChat(context, team.id!); + } else { + showCommonDialog( + context: context, + title: S.of(context).searchTeamLeave, + content: S.of(context).searchTeamDismissOrLeave); + } + }); }, child: Row( crossAxisAlignment: CrossAxisAlignment.center, diff --git a/nim_searchkit_ui/pubspec.yaml b/nim_searchkit_ui/pubspec.yaml index ce6d620..adf83c2 100644 --- a/nim_searchkit_ui/pubspec.yaml +++ b/nim_searchkit_ui/pubspec.yaml @@ -1,6 +1,6 @@ name: nim_searchkit_ui description: Search UI base on SearchKit. -version: 1.2.0 +version: 9.7.0 homepage: https://github.com/netease-kit/nim-uikit-flutter # Remove this line if you wish to publish to pub.dev #publish_to: none @@ -16,18 +16,18 @@ dependencies: sdk: flutter netease_common_ui: - ">=1.2.0 <1.3.0" + ">=9.7.0 <10.0.0" # path: ../../common/netease_common_ui nim_searchkit: - ">=1.2.0 <1.3.0" + ">=9.7.0 <10.0.0" # path: ../nim_searchkit netease_corekit_im: - ">=1.2.0 <1.3.0" + ">=9.7.0 <10.0.0" # path: ../../netease_corekit/netease_corekit_im netease_corekit: ">=1.2.0 <1.3.0" # path: ../../netease_corekit/netease_corekit - nim_core: ^1.7.3 + nim_core: ^1.7.4 # path: ../../nim_core/nim_core intl: ^0.18.0 flutter_svg: ^2.0.7 diff --git a/nim_teamkit_ui/.flutter-plugins b/nim_teamkit_ui/.flutter-plugins index ecd8678..59e3dec 100644 --- a/nim_teamkit_ui/.flutter-plugins +++ b/nim_teamkit_ui/.flutter-plugins @@ -1,11 +1,13 @@ # This is a generated file; do not edit or check into version control. -connectivity_plus=/Users/houchengdong/.pub-cache/hosted/pub.dev/connectivity_plus-4.0.2/ +alog_macos=/Users/houchengdong/.pub-cache/hosted/pub.dev/alog_macos-1.0.12/ +alog_windows=/Users/houchengdong/.pub-cache/hosted/pub.dev/alog_windows-1.0.12/ +connectivity_plus=/Users/houchengdong/.pub-cache/hosted/pub.dev/connectivity_plus-5.0.1/ device_info_plus=/Users/houchengdong/.pub-cache/hosted/pub.dev/device_info_plus-9.0.2/ file_selector_linux=/Users/houchengdong/.pub-cache/hosted/pub.dev/file_selector_linux-0.9.2/ file_selector_macos=/Users/houchengdong/.pub-cache/hosted/pub.dev/file_selector_macos-0.9.3+1/ file_selector_windows=/Users/houchengdong/.pub-cache/hosted/pub.dev/file_selector_windows-0.9.3/ flutter_plugin_android_lifecycle=/Users/houchengdong/.pub-cache/hosted/pub.dev/flutter_plugin_android_lifecycle-2.0.7/ -fluttertoast=/Users/houchengdong/.pub-cache/hosted/pub.dev/fluttertoast-8.1.3/ +fluttertoast=/Users/houchengdong/.pub-cache/hosted/pub.dev/fluttertoast-8.2.4/ image_picker=/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker-1.0.2/ image_picker_android=/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker_android-0.8.7+4/ image_picker_for_web=/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker_for_web-3.0.0/ @@ -13,7 +15,7 @@ image_picker_ios=/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker_ios- image_picker_linux=/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker_linux-0.2.1/ image_picker_macos=/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker_macos-0.2.1/ image_picker_windows=/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker_windows-0.2.1/ -nim_core=/Users/houchengdong/.pub-cache/hosted/pub.dev/nim_core-1.7.0/ +nim_core=/Users/houchengdong/.pub-cache/hosted/pub.dev/nim_core-1.7.4/ nim_core_macos=/Users/houchengdong/.pub-cache/hosted/pub.dev/nim_core_macos-1.0.8/ nim_core_web=/Users/houchengdong/.pub-cache/hosted/pub.dev/nim_core_web-1.0.2/ nim_core_windows=/Users/houchengdong/.pub-cache/hosted/pub.dev/nim_core_windows-1.0.8/ @@ -22,18 +24,18 @@ path_provider_android=/Users/houchengdong/.pub-cache/hosted/pub.dev/path_provide path_provider_foundation=/Users/houchengdong/.pub-cache/hosted/pub.dev/path_provider_foundation-2.1.1/ path_provider_linux=/Users/houchengdong/.pub-cache/hosted/pub.dev/path_provider_linux-2.1.8/ path_provider_windows=/Users/houchengdong/.pub-cache/hosted/pub.dev/path_provider_windows-2.1.7/ -permission_handler=/Users/houchengdong/.pub-cache/hosted/pub.dev/permission_handler-10.3.0/ -permission_handler_android=/Users/houchengdong/.pub-cache/hosted/pub.dev/permission_handler_android-10.2.0/ -permission_handler_apple=/Users/houchengdong/.pub-cache/hosted/pub.dev/permission_handler_apple-9.1.0/ -permission_handler_windows=/Users/houchengdong/.pub-cache/hosted/pub.dev/permission_handler_windows-0.1.2/ +permission_handler=/Users/houchengdong/.pub-cache/hosted/pub.dev/permission_handler-11.0.1/ +permission_handler_android=/Users/houchengdong/.pub-cache/hosted/pub.dev/permission_handler_android-11.1.0/ +permission_handler_apple=/Users/houchengdong/.pub-cache/hosted/pub.dev/permission_handler_apple-9.1.4/ +permission_handler_windows=/Users/houchengdong/.pub-cache/hosted/pub.dev/permission_handler_windows-0.1.3/ shared_preferences=/Users/houchengdong/.pub-cache/hosted/pub.dev/shared_preferences-2.0.17/ shared_preferences_android=/Users/houchengdong/.pub-cache/hosted/pub.dev/shared_preferences_android-2.0.15/ shared_preferences_foundation=/Users/houchengdong/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.1.3/ shared_preferences_linux=/Users/houchengdong/.pub-cache/hosted/pub.dev/shared_preferences_linux-2.1.3/ shared_preferences_web=/Users/houchengdong/.pub-cache/hosted/pub.dev/shared_preferences_web-2.0.4/ shared_preferences_windows=/Users/houchengdong/.pub-cache/hosted/pub.dev/shared_preferences_windows-2.1.3/ -sqflite=/Users/houchengdong/.pub-cache/hosted/pub.dev/sqflite-2.0.3+1/ +sqflite=/Users/houchengdong/.pub-cache/hosted/pub.dev/sqflite-2.3.0/ webview_flutter=/Users/houchengdong/.pub-cache/hosted/pub.dev/webview_flutter-3.0.4/ webview_flutter_android=/Users/houchengdong/.pub-cache/hosted/pub.dev/webview_flutter_android-2.10.4/ webview_flutter_wkwebview=/Users/houchengdong/.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-2.9.5/ -yunxin_alog=/Users/houchengdong/.pub-cache/hosted/pub.dev/yunxin_alog-1.0.11/ +yunxin_alog=/Users/houchengdong/.pub-cache/hosted/pub.dev/yunxin_alog-2.0.0/ diff --git a/nim_teamkit_ui/.flutter-plugins-dependencies b/nim_teamkit_ui/.flutter-plugins-dependencies index fe3a890..3a445af 100644 --- a/nim_teamkit_ui/.flutter-plugins-dependencies +++ b/nim_teamkit_ui/.flutter-plugins-dependencies @@ -1 +1 @@ -{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"connectivity_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/connectivity_plus-4.0.2/","native_build":true,"dependencies":[]},{"name":"device_info_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/device_info_plus-9.0.2/","native_build":true,"dependencies":[]},{"name":"fluttertoast","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/fluttertoast-8.1.3/","native_build":true,"dependencies":[]},{"name":"image_picker_ios","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker_ios-0.8.8/","native_build":true,"dependencies":[]},{"name":"nim_core","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/nim_core-1.7.0/","native_build":true,"dependencies":["yunxin_alog"]},{"name":"path_provider_foundation","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/path_provider_foundation-2.1.1/","shared_darwin_source":true,"native_build":true,"dependencies":[]},{"name":"permission_handler_apple","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/permission_handler_apple-9.1.0/","native_build":true,"dependencies":[]},{"name":"shared_preferences_foundation","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.1.3/","shared_darwin_source":true,"native_build":true,"dependencies":[]},{"name":"sqflite","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/sqflite-2.0.3+1/","native_build":true,"dependencies":[]},{"name":"webview_flutter_wkwebview","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-2.9.5/","native_build":true,"dependencies":[]},{"name":"yunxin_alog","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/yunxin_alog-1.0.11/","native_build":true,"dependencies":[]}],"android":[{"name":"connectivity_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/connectivity_plus-4.0.2/","native_build":true,"dependencies":[]},{"name":"device_info_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/device_info_plus-9.0.2/","native_build":true,"dependencies":[]},{"name":"flutter_plugin_android_lifecycle","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/flutter_plugin_android_lifecycle-2.0.7/","native_build":true,"dependencies":[]},{"name":"fluttertoast","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/fluttertoast-8.1.3/","native_build":true,"dependencies":[]},{"name":"image_picker_android","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker_android-0.8.7+4/","native_build":true,"dependencies":["flutter_plugin_android_lifecycle"]},{"name":"nim_core","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/nim_core-1.7.0/","native_build":true,"dependencies":["yunxin_alog"]},{"name":"path_provider_android","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/path_provider_android-2.0.22/","native_build":true,"dependencies":[]},{"name":"permission_handler_android","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/permission_handler_android-10.2.0/","native_build":true,"dependencies":[]},{"name":"shared_preferences_android","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/shared_preferences_android-2.0.15/","native_build":true,"dependencies":[]},{"name":"sqflite","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/sqflite-2.0.3+1/","native_build":true,"dependencies":[]},{"name":"webview_flutter_android","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/webview_flutter_android-2.10.4/","native_build":true,"dependencies":[]},{"name":"yunxin_alog","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/yunxin_alog-1.0.11/","native_build":true,"dependencies":[]}],"macos":[{"name":"connectivity_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/connectivity_plus-4.0.2/","native_build":true,"dependencies":[]},{"name":"device_info_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/device_info_plus-9.0.2/","native_build":true,"dependencies":[]},{"name":"file_selector_macos","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/file_selector_macos-0.9.3+1/","native_build":true,"dependencies":[]},{"name":"image_picker_macos","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker_macos-0.2.1/","native_build":false,"dependencies":["file_selector_macos"]},{"name":"nim_core_macos","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/nim_core_macos-1.0.8/","native_build":true,"dependencies":[]},{"name":"path_provider_foundation","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/path_provider_foundation-2.1.1/","shared_darwin_source":true,"native_build":true,"dependencies":[]},{"name":"shared_preferences_foundation","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.1.3/","shared_darwin_source":true,"native_build":true,"dependencies":[]},{"name":"sqflite","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/sqflite-2.0.3+1/","native_build":true,"dependencies":[]}],"linux":[{"name":"connectivity_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/connectivity_plus-4.0.2/","native_build":false,"dependencies":[]},{"name":"device_info_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/device_info_plus-9.0.2/","native_build":false,"dependencies":[]},{"name":"file_selector_linux","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/file_selector_linux-0.9.2/","native_build":true,"dependencies":[]},{"name":"image_picker_linux","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker_linux-0.2.1/","native_build":false,"dependencies":["file_selector_linux"]},{"name":"path_provider_linux","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/path_provider_linux-2.1.8/","native_build":false,"dependencies":[]},{"name":"shared_preferences_linux","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/shared_preferences_linux-2.1.3/","native_build":false,"dependencies":["path_provider_linux"]}],"windows":[{"name":"connectivity_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/connectivity_plus-4.0.2/","native_build":true,"dependencies":[]},{"name":"device_info_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/device_info_plus-9.0.2/","native_build":false,"dependencies":[]},{"name":"file_selector_windows","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/file_selector_windows-0.9.3/","native_build":true,"dependencies":[]},{"name":"image_picker_windows","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker_windows-0.2.1/","native_build":false,"dependencies":["file_selector_windows"]},{"name":"nim_core_windows","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/nim_core_windows-1.0.8/","native_build":true,"dependencies":[]},{"name":"path_provider_windows","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/path_provider_windows-2.1.7/","native_build":false,"dependencies":[]},{"name":"permission_handler_windows","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/permission_handler_windows-0.1.2/","native_build":true,"dependencies":[]},{"name":"shared_preferences_windows","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/shared_preferences_windows-2.1.3/","native_build":false,"dependencies":["path_provider_windows"]}],"web":[{"name":"connectivity_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/connectivity_plus-4.0.2/","dependencies":[]},{"name":"device_info_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/device_info_plus-9.0.2/","dependencies":[]},{"name":"fluttertoast","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/fluttertoast-8.1.3/","dependencies":[]},{"name":"image_picker_for_web","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker_for_web-3.0.0/","dependencies":[]},{"name":"nim_core_web","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/nim_core_web-1.0.2/","dependencies":[]},{"name":"shared_preferences_web","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/shared_preferences_web-2.0.4/","dependencies":[]}]},"dependencyGraph":[{"name":"connectivity_plus","dependencies":[]},{"name":"device_info_plus","dependencies":[]},{"name":"file_selector_linux","dependencies":[]},{"name":"file_selector_macos","dependencies":[]},{"name":"file_selector_windows","dependencies":[]},{"name":"flutter_plugin_android_lifecycle","dependencies":[]},{"name":"fluttertoast","dependencies":[]},{"name":"image_picker","dependencies":["image_picker_android","image_picker_for_web","image_picker_ios","image_picker_linux","image_picker_macos","image_picker_windows"]},{"name":"image_picker_android","dependencies":["flutter_plugin_android_lifecycle"]},{"name":"image_picker_for_web","dependencies":[]},{"name":"image_picker_ios","dependencies":[]},{"name":"image_picker_linux","dependencies":["file_selector_linux"]},{"name":"image_picker_macos","dependencies":["file_selector_macos"]},{"name":"image_picker_windows","dependencies":["file_selector_windows"]},{"name":"nim_core","dependencies":["nim_core_windows","nim_core_macos","nim_core_web","path_provider","yunxin_alog"]},{"name":"nim_core_macos","dependencies":[]},{"name":"nim_core_web","dependencies":[]},{"name":"nim_core_windows","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"permission_handler","dependencies":["permission_handler_android","permission_handler_apple","permission_handler_windows"]},{"name":"permission_handler_android","dependencies":[]},{"name":"permission_handler_apple","dependencies":[]},{"name":"permission_handler_windows","dependencies":[]},{"name":"shared_preferences","dependencies":["shared_preferences_android","shared_preferences_foundation","shared_preferences_linux","shared_preferences_web","shared_preferences_windows"]},{"name":"shared_preferences_android","dependencies":[]},{"name":"shared_preferences_foundation","dependencies":[]},{"name":"shared_preferences_linux","dependencies":["path_provider_linux"]},{"name":"shared_preferences_web","dependencies":[]},{"name":"shared_preferences_windows","dependencies":["path_provider_windows"]},{"name":"sqflite","dependencies":[]},{"name":"webview_flutter","dependencies":["webview_flutter_android","webview_flutter_wkwebview"]},{"name":"webview_flutter_android","dependencies":[]},{"name":"webview_flutter_wkwebview","dependencies":[]},{"name":"yunxin_alog","dependencies":[]}],"date_created":"2023-09-20 17:01:02.216068","version":"3.10.3"} \ No newline at end of file +{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"connectivity_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/connectivity_plus-5.0.1/","native_build":true,"dependencies":[]},{"name":"device_info_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/device_info_plus-9.0.2/","native_build":true,"dependencies":[]},{"name":"fluttertoast","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/fluttertoast-8.2.4/","native_build":true,"dependencies":[]},{"name":"image_picker_ios","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker_ios-0.8.8/","native_build":true,"dependencies":[]},{"name":"nim_core","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/nim_core-1.7.4/","native_build":true,"dependencies":["yunxin_alog"]},{"name":"path_provider_foundation","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/path_provider_foundation-2.1.1/","shared_darwin_source":true,"native_build":true,"dependencies":[]},{"name":"permission_handler_apple","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/permission_handler_apple-9.1.4/","native_build":true,"dependencies":[]},{"name":"shared_preferences_foundation","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.1.3/","shared_darwin_source":true,"native_build":true,"dependencies":[]},{"name":"sqflite","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/sqflite-2.3.0/","native_build":true,"dependencies":[]},{"name":"webview_flutter_wkwebview","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-2.9.5/","native_build":true,"dependencies":[]},{"name":"yunxin_alog","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/yunxin_alog-2.0.0/","native_build":true,"dependencies":[]}],"android":[{"name":"connectivity_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/connectivity_plus-5.0.1/","native_build":true,"dependencies":[]},{"name":"device_info_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/device_info_plus-9.0.2/","native_build":true,"dependencies":[]},{"name":"flutter_plugin_android_lifecycle","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/flutter_plugin_android_lifecycle-2.0.7/","native_build":true,"dependencies":[]},{"name":"fluttertoast","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/fluttertoast-8.2.4/","native_build":true,"dependencies":[]},{"name":"image_picker_android","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker_android-0.8.7+4/","native_build":true,"dependencies":["flutter_plugin_android_lifecycle"]},{"name":"nim_core","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/nim_core-1.7.4/","native_build":true,"dependencies":["yunxin_alog"]},{"name":"path_provider_android","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/path_provider_android-2.0.22/","native_build":true,"dependencies":[]},{"name":"permission_handler_android","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/permission_handler_android-11.1.0/","native_build":true,"dependencies":[]},{"name":"shared_preferences_android","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/shared_preferences_android-2.0.15/","native_build":true,"dependencies":[]},{"name":"sqflite","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/sqflite-2.3.0/","native_build":true,"dependencies":[]},{"name":"webview_flutter_android","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/webview_flutter_android-2.10.4/","native_build":true,"dependencies":[]},{"name":"yunxin_alog","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/yunxin_alog-2.0.0/","native_build":true,"dependencies":[]}],"macos":[{"name":"alog_macos","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/alog_macos-1.0.12/","native_build":true,"dependencies":[]},{"name":"connectivity_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/connectivity_plus-5.0.1/","native_build":true,"dependencies":[]},{"name":"device_info_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/device_info_plus-9.0.2/","native_build":true,"dependencies":[]},{"name":"file_selector_macos","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/file_selector_macos-0.9.3+1/","native_build":true,"dependencies":[]},{"name":"image_picker_macos","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker_macos-0.2.1/","native_build":false,"dependencies":["file_selector_macos"]},{"name":"nim_core_macos","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/nim_core_macos-1.0.8/","native_build":true,"dependencies":[]},{"name":"path_provider_foundation","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/path_provider_foundation-2.1.1/","shared_darwin_source":true,"native_build":true,"dependencies":[]},{"name":"shared_preferences_foundation","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.1.3/","shared_darwin_source":true,"native_build":true,"dependencies":[]},{"name":"sqflite","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/sqflite-2.3.0/","native_build":true,"dependencies":[]}],"linux":[{"name":"connectivity_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/connectivity_plus-5.0.1/","native_build":false,"dependencies":[]},{"name":"device_info_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/device_info_plus-9.0.2/","native_build":false,"dependencies":[]},{"name":"file_selector_linux","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/file_selector_linux-0.9.2/","native_build":true,"dependencies":[]},{"name":"image_picker_linux","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker_linux-0.2.1/","native_build":false,"dependencies":["file_selector_linux"]},{"name":"path_provider_linux","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/path_provider_linux-2.1.8/","native_build":false,"dependencies":[]},{"name":"shared_preferences_linux","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/shared_preferences_linux-2.1.3/","native_build":false,"dependencies":["path_provider_linux"]}],"windows":[{"name":"alog_windows","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/alog_windows-1.0.12/","native_build":true,"dependencies":[]},{"name":"connectivity_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/connectivity_plus-5.0.1/","native_build":true,"dependencies":[]},{"name":"device_info_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/device_info_plus-9.0.2/","native_build":false,"dependencies":[]},{"name":"file_selector_windows","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/file_selector_windows-0.9.3/","native_build":true,"dependencies":[]},{"name":"image_picker_windows","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker_windows-0.2.1/","native_build":false,"dependencies":["file_selector_windows"]},{"name":"nim_core_windows","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/nim_core_windows-1.0.8/","native_build":true,"dependencies":[]},{"name":"path_provider_windows","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/path_provider_windows-2.1.7/","native_build":false,"dependencies":[]},{"name":"permission_handler_windows","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/permission_handler_windows-0.1.3/","native_build":true,"dependencies":[]},{"name":"shared_preferences_windows","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/shared_preferences_windows-2.1.3/","native_build":false,"dependencies":["path_provider_windows"]}],"web":[{"name":"connectivity_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/connectivity_plus-5.0.1/","dependencies":[]},{"name":"device_info_plus","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/device_info_plus-9.0.2/","dependencies":[]},{"name":"fluttertoast","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/fluttertoast-8.2.4/","dependencies":[]},{"name":"image_picker_for_web","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/image_picker_for_web-3.0.0/","dependencies":[]},{"name":"nim_core_web","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/nim_core_web-1.0.2/","dependencies":[]},{"name":"shared_preferences_web","path":"/Users/houchengdong/.pub-cache/hosted/pub.dev/shared_preferences_web-2.0.4/","dependencies":[]}]},"dependencyGraph":[{"name":"alog_macos","dependencies":[]},{"name":"alog_windows","dependencies":[]},{"name":"connectivity_plus","dependencies":[]},{"name":"device_info_plus","dependencies":[]},{"name":"file_selector_linux","dependencies":[]},{"name":"file_selector_macos","dependencies":[]},{"name":"file_selector_windows","dependencies":[]},{"name":"flutter_plugin_android_lifecycle","dependencies":[]},{"name":"fluttertoast","dependencies":[]},{"name":"image_picker","dependencies":["image_picker_android","image_picker_for_web","image_picker_ios","image_picker_linux","image_picker_macos","image_picker_windows"]},{"name":"image_picker_android","dependencies":["flutter_plugin_android_lifecycle"]},{"name":"image_picker_for_web","dependencies":[]},{"name":"image_picker_ios","dependencies":[]},{"name":"image_picker_linux","dependencies":["file_selector_linux"]},{"name":"image_picker_macos","dependencies":["file_selector_macos"]},{"name":"image_picker_windows","dependencies":["file_selector_windows"]},{"name":"nim_core","dependencies":["nim_core_windows","nim_core_macos","nim_core_web","path_provider","yunxin_alog"]},{"name":"nim_core_macos","dependencies":[]},{"name":"nim_core_web","dependencies":[]},{"name":"nim_core_windows","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"permission_handler","dependencies":["permission_handler_android","permission_handler_apple","permission_handler_windows"]},{"name":"permission_handler_android","dependencies":[]},{"name":"permission_handler_apple","dependencies":[]},{"name":"permission_handler_windows","dependencies":[]},{"name":"shared_preferences","dependencies":["shared_preferences_android","shared_preferences_foundation","shared_preferences_linux","shared_preferences_web","shared_preferences_windows"]},{"name":"shared_preferences_android","dependencies":[]},{"name":"shared_preferences_foundation","dependencies":[]},{"name":"shared_preferences_linux","dependencies":["path_provider_linux"]},{"name":"shared_preferences_web","dependencies":[]},{"name":"shared_preferences_windows","dependencies":["path_provider_windows"]},{"name":"sqflite","dependencies":[]},{"name":"webview_flutter","dependencies":["webview_flutter_android","webview_flutter_wkwebview"]},{"name":"webview_flutter_android","dependencies":[]},{"name":"webview_flutter_wkwebview","dependencies":[]},{"name":"yunxin_alog","dependencies":["alog_macos","alog_windows"]}],"date_created":"2024-01-25 14:20:09.809428","version":"3.10.6"} \ No newline at end of file diff --git a/nim_teamkit_ui/CHANGELOG.md b/nim_teamkit_ui/CHANGELOG.md index 677fbd9..462fe9c 100644 --- a/nim_teamkit_ui/CHANGELOG.md +++ b/nim_teamkit_ui/CHANGELOG.md @@ -1,5 +1,11 @@ # NimTeamKitUI ChangeLog +## 9.7.0(Jan 25, 2024) +### New Features +* 新增合并转发和多选功能 +* 新增群管理 +* 新增换行消息 + ## 1.2.0(Nov 8, 2023) ### Bug Fixes * 新增群成员列表排序 diff --git a/nim_teamkit_ui/images/ic_member_empty.svg b/nim_teamkit_ui/images/ic_member_empty.svg new file mode 100644 index 0000000..fce2036 --- /dev/null +++ b/nim_teamkit_ui/images/ic_member_empty.svg @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/nim_teamkit_ui/lib/l10n/intl_en.arb b/nim_teamkit_ui/lib/l10n/intl_en.arb index 3264ab8..b85366b 100644 --- a/nim_teamkit_ui/lib/l10n/intl_en.arb +++ b/nim_teamkit_ui/lib/l10n/intl_en.arb @@ -34,5 +34,35 @@ "teamSave": "Save", "teamSearchMember": "Search Member", "teamNoPermission": "No Permission", - "teamNameMustNotEmpty": "Team name must not empty" + "teamNameMustNotEmpty": "Team name must not empty", + "teamManage": "Team Manage", + "teamAitPermission": "Who can @all", + "teamManager": "Team Manager", + "teamAddManagers": "Add Managers", + "teamOwnerManager": "Owner Add Manager", + "teamMemberRemove": "Remove", + "teamRemoveConfirm": "Sure to remove", + "teamRemoveConfirmContent": "This Member will lost Manager Promise after remove", + "teamSettingFailed": "Setting failed", + "teamMsgAitAllPrivilegeIsAll": "@All privilege update to all", + "teamMsgAitAllPrivilegeIsOwner": "@All privilege update to owner and manager", + "teamManagers": "Managers", + "teamSelectMembers": "Select members", + "teamManagerLimit": "The number of managers cannot exceed {number}", + "@teamManagerLimit": { + "placeholders": { + "number": { + "type": "String" + } + } + }, + "teamNoOperatePermission": "No operate permission", + "teamManagerEmpty": "No manager", + "teamMemberRemoveContent": "This member will leave this team after remove", + "teamMemberRemoveFailed": "Remove failed", + "teamManagerManagers": "Manager managers", + "teamMemberSelect": "Select member", + "teamPermissionDeny": "you have no permission", + "teamManagerRemoveFailed": "Remove failed", + "teamMemberEmpty": "No member" } \ No newline at end of file diff --git a/nim_teamkit_ui/lib/l10n/intl_zh.arb b/nim_teamkit_ui/lib/l10n/intl_zh.arb index dcf1de5..7df28b3 100644 --- a/nim_teamkit_ui/lib/l10n/intl_zh.arb +++ b/nim_teamkit_ui/lib/l10n/intl_zh.arb @@ -16,8 +16,8 @@ "teamMessageTip": "开启消息提醒", "teamSessionPin": "聊天置顶", "teamMute": "群禁言", - "teamInviteOtherPermission": "邀请他人权限", - "teamUpdateInfoPermission": "群资料修改权限", + "teamInviteOtherPermission": "谁可以添加群成员", + "teamUpdateInfoPermission": "谁可以编辑群信息", "teamNeedAgreedWhenBeInvitedPermission": "是否需要被邀请者同意", "teamAdvancedDismiss": "解散群聊", "teamAdvancedQuit": "退出群聊", @@ -34,5 +34,35 @@ "teamSave": "保存", "teamSearchMember": "搜索成员", "teamNoPermission": "没有修改权限", - "teamNameMustNotEmpty": "群名称不可为空" + "teamNameMustNotEmpty": "群名称不可为空", + "teamManage": "群管理", + "teamAitPermission": "谁可以@所有人", + "teamManager": "管理员", + "teamAddManagers": "添加管理员", + "teamOwnerManager": "群主和管理员", + "teamMemberRemove": "移除", + "teamRemoveConfirm": "是否移除", + "teamRemoveConfirmContent": "移除后该成员将无管理权限", + "teamSettingFailed": "设置失败", + "teamMsgAitAllPrivilegeIsAll": "@所有人权限更新为所有人", + "teamMsgAitAllPrivilegeIsOwner": "@所有人权限更新为群主和管理员", + "teamManagers": "群管理员", + "teamSelectMembers": "请选择成员", + "teamManagerLimit": "最多只能设置{number}个管理员", + "@teamManagerLimit": { + "placeholders": { + "number": { + "type": "String" + } + } + }, + "teamNoOperatePermission": "您暂无操作权限", + "teamManagerEmpty": "暂无群管理人员", + "teamMemberRemoveContent": "移除后该成员将离开当前群聊", + "teamMemberRemoveFailed": "移除失败", + "teamManagerManagers": "管理管理员", + "teamMemberSelect": "人员选择", + "teamPermissionDeny": "您暂无权限操作", + "teamManagerRemoveFailed": "管理员移除失败", + "teamMemberEmpty": "暂无成员" } \ No newline at end of file diff --git a/nim_teamkit_ui/lib/l10n/team_localization/team_kit_client_localizations.dart b/nim_teamkit_ui/lib/l10n/team_localization/team_kit_client_localizations.dart index 8e8c439..db5639b 100644 --- a/nim_teamkit_ui/lib/l10n/team_localization/team_kit_client_localizations.dart +++ b/nim_teamkit_ui/lib/l10n/team_localization/team_kit_client_localizations.dart @@ -310,6 +310,144 @@ abstract class TeamKitClientLocalizations { /// In en, this message translates to: /// **'Team name must not empty'** String get teamNameMustNotEmpty; + + /// No description provided for @teamManage. + /// + /// In en, this message translates to: + /// **'Team Manage'** + String get teamManage; + + /// No description provided for @teamAitPermission. + /// + /// In en, this message translates to: + /// **'Who can @all'** + String get teamAitPermission; + + /// No description provided for @teamManager. + /// + /// In en, this message translates to: + /// **'Team Manager'** + String get teamManager; + + /// No description provided for @teamAddManagers. + /// + /// In en, this message translates to: + /// **'Add Managers'** + String get teamAddManagers; + + /// No description provided for @teamOwnerManager. + /// + /// In en, this message translates to: + /// **'Owner Add Manager'** + String get teamOwnerManager; + + /// No description provided for @teamMemberRemove. + /// + /// In en, this message translates to: + /// **'Remove'** + String get teamMemberRemove; + + /// No description provided for @teamRemoveConfirm. + /// + /// In en, this message translates to: + /// **'Sure to remove'** + String get teamRemoveConfirm; + + /// No description provided for @teamRemoveConfirmContent. + /// + /// In en, this message translates to: + /// **'This Member will lost Manager Promise after remove'** + String get teamRemoveConfirmContent; + + /// No description provided for @teamSettingFailed. + /// + /// In en, this message translates to: + /// **'Setting failed'** + String get teamSettingFailed; + + /// No description provided for @teamMsgAitAllPrivilegeIsAll. + /// + /// In en, this message translates to: + /// **'@All privilege update to all'** + String get teamMsgAitAllPrivilegeIsAll; + + /// No description provided for @teamMsgAitAllPrivilegeIsOwner. + /// + /// In en, this message translates to: + /// **'@All privilege update to owner and manager'** + String get teamMsgAitAllPrivilegeIsOwner; + + /// No description provided for @teamManagers. + /// + /// In en, this message translates to: + /// **'Managers'** + String get teamManagers; + + /// No description provided for @teamSelectMembers. + /// + /// In en, this message translates to: + /// **'Select members'** + String get teamSelectMembers; + + /// No description provided for @teamManagerLimit. + /// + /// In en, this message translates to: + /// **'The number of managers cannot exceed {number}'** + String teamManagerLimit(String number); + + /// No description provided for @teamNoOperatePermission. + /// + /// In en, this message translates to: + /// **'No operate permission'** + String get teamNoOperatePermission; + + /// No description provided for @teamManagerEmpty. + /// + /// In en, this message translates to: + /// **'No manager'** + String get teamManagerEmpty; + + /// No description provided for @teamMemberRemoveContent. + /// + /// In en, this message translates to: + /// **'This member will leave this team after remove'** + String get teamMemberRemoveContent; + + /// No description provided for @teamMemberRemoveFailed. + /// + /// In en, this message translates to: + /// **'Remove failed'** + String get teamMemberRemoveFailed; + + /// No description provided for @teamManagerManagers. + /// + /// In en, this message translates to: + /// **'Manager managers'** + String get teamManagerManagers; + + /// No description provided for @teamMemberSelect. + /// + /// In en, this message translates to: + /// **'Select member'** + String get teamMemberSelect; + + /// No description provided for @teamPermissionDeny. + /// + /// In en, this message translates to: + /// **'you have no permission'** + String get teamPermissionDeny; + + /// No description provided for @teamManagerRemoveFailed. + /// + /// In en, this message translates to: + /// **'Remove failed'** + String get teamManagerRemoveFailed; + + /// No description provided for @teamMemberEmpty. + /// + /// In en, this message translates to: + /// **'No member'** + String get teamMemberEmpty; } class _TeamKitClientLocalizationsDelegate diff --git a/nim_teamkit_ui/lib/l10n/team_localization/team_kit_client_localizations_en.dart b/nim_teamkit_ui/lib/l10n/team_localization/team_kit_client_localizations_en.dart index 76b906b..da23ed4 100644 --- a/nim_teamkit_ui/lib/l10n/team_localization/team_kit_client_localizations_en.dart +++ b/nim_teamkit_ui/lib/l10n/team_localization/team_kit_client_localizations_en.dart @@ -114,4 +114,78 @@ class TeamKitClientLocalizationsEn extends TeamKitClientLocalizations { @override String get teamNameMustNotEmpty => 'Team name must not empty'; + + @override + String get teamManage => 'Team Manage'; + + @override + String get teamAitPermission => 'Who can @all'; + + @override + String get teamManager => 'Team Manager'; + + @override + String get teamAddManagers => 'Add Managers'; + + @override + String get teamOwnerManager => 'Owner Add Manager'; + + @override + String get teamMemberRemove => 'Remove'; + + @override + String get teamRemoveConfirm => 'Sure to remove'; + + @override + String get teamRemoveConfirmContent => + 'This Member will lost Manager Promise after remove'; + + @override + String get teamSettingFailed => 'Setting failed'; + + @override + String get teamMsgAitAllPrivilegeIsAll => '@All privilege update to all'; + + @override + String get teamMsgAitAllPrivilegeIsOwner => + '@All privilege update to owner and manager'; + + @override + String get teamManagers => 'Managers'; + + @override + String get teamSelectMembers => 'Select members'; + + @override + String teamManagerLimit(String number) { + return 'The number of managers cannot exceed $number'; + } + + @override + String get teamNoOperatePermission => 'No operate permission'; + + @override + String get teamManagerEmpty => 'No manager'; + + @override + String get teamMemberRemoveContent => + 'This member will leave this team after remove'; + + @override + String get teamMemberRemoveFailed => 'Remove failed'; + + @override + String get teamManagerManagers => 'Manager managers'; + + @override + String get teamMemberSelect => 'Select member'; + + @override + String get teamPermissionDeny => 'you have no permission'; + + @override + String get teamManagerRemoveFailed => 'Remove failed'; + + @override + String get teamMemberEmpty => 'No member'; } diff --git a/nim_teamkit_ui/lib/l10n/team_localization/team_kit_client_localizations_zh.dart b/nim_teamkit_ui/lib/l10n/team_localization/team_kit_client_localizations_zh.dart index 21a6a51..10fca6f 100644 --- a/nim_teamkit_ui/lib/l10n/team_localization/team_kit_client_localizations_zh.dart +++ b/nim_teamkit_ui/lib/l10n/team_localization/team_kit_client_localizations_zh.dart @@ -57,10 +57,10 @@ class TeamKitClientLocalizationsZh extends TeamKitClientLocalizations { String get teamMute => '群禁言'; @override - String get teamInviteOtherPermission => '邀请他人权限'; + String get teamInviteOtherPermission => '谁可以添加群成员'; @override - String get teamUpdateInfoPermission => '群资料修改权限'; + String get teamUpdateInfoPermission => '谁可以编辑群信息'; @override String get teamNeedAgreedWhenBeInvitedPermission => '是否需要被邀请者同意'; @@ -112,4 +112,75 @@ class TeamKitClientLocalizationsZh extends TeamKitClientLocalizations { @override String get teamNameMustNotEmpty => '群名称不可为空'; + + @override + String get teamManage => '群管理'; + + @override + String get teamAitPermission => '谁可以@所有人'; + + @override + String get teamManager => '管理员'; + + @override + String get teamAddManagers => '添加管理员'; + + @override + String get teamOwnerManager => '群主和管理员'; + + @override + String get teamMemberRemove => '移除'; + + @override + String get teamRemoveConfirm => '是否移除'; + + @override + String get teamRemoveConfirmContent => '移除后该成员将无管理权限'; + + @override + String get teamSettingFailed => '设置失败'; + + @override + String get teamMsgAitAllPrivilegeIsAll => '@所有人权限更新为所有人'; + + @override + String get teamMsgAitAllPrivilegeIsOwner => '@所有人权限更新为群主和管理员'; + + @override + String get teamManagers => '群管理员'; + + @override + String get teamSelectMembers => '请选择成员'; + + @override + String teamManagerLimit(String number) { + return '最多只能设置$number个管理员'; + } + + @override + String get teamNoOperatePermission => '您暂无操作权限'; + + @override + String get teamManagerEmpty => '暂无群管理人员'; + + @override + String get teamMemberRemoveContent => '移除后该成员将离开当前群聊'; + + @override + String get teamMemberRemoveFailed => '移除失败'; + + @override + String get teamManagerManagers => '管理管理员'; + + @override + String get teamMemberSelect => '人员选择'; + + @override + String get teamPermissionDeny => '您暂无权限操作'; + + @override + String get teamManagerRemoveFailed => '管理员移除失败'; + + @override + String get teamMemberEmpty => '暂无成员'; } diff --git a/nim_teamkit_ui/lib/team_kit_client.dart b/nim_teamkit_ui/lib/team_kit_client.dart index 44e42a5..5c008d6 100644 --- a/nim_teamkit_ui/lib/team_kit_client.dart +++ b/nim_teamkit_ui/lib/team_kit_client.dart @@ -13,6 +13,9 @@ import 'view/pages/team_kit_setting_page.dart'; const String kPackage = 'nim_teamkit_ui'; class TeamKitClient { + /// 群管理员数量限制 + int? teamManagerLimit; + static get delegate { return S.delegate; } @@ -26,4 +29,13 @@ class TeamKitClient { XKitReporter().register(moduleName: 'TeamUIKit', moduleVersion: '1.1.0'); } + + TeamKitClient._(); + + static TeamKitClient? _instance; + + static TeamKitClient get instance { + _instance ??= TeamKitClient._(); + return _instance!; + } } diff --git a/nim_teamkit_ui/lib/view/pages/team_kit_avatar_editor_page.dart b/nim_teamkit_ui/lib/view/pages/team_kit_avatar_editor_page.dart index 50b74ff..5387da0 100644 --- a/nim_teamkit_ui/lib/view/pages/team_kit_avatar_editor_page.dart +++ b/nim_teamkit_ui/lib/view/pages/team_kit_avatar_editor_page.dart @@ -2,15 +2,17 @@ // Use of this source code is governed by a MIT license that can be // found in the LICENSE file. +import 'package:connectivity_plus/connectivity_plus.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:fluttertoast/fluttertoast.dart'; import 'package:netease_common_ui/extension.dart'; import 'package:netease_common_ui/ui/avatar.dart'; import 'package:netease_common_ui/ui/background.dart'; import 'package:netease_common_ui/ui/photo.dart'; import 'package:netease_common_ui/utils/color_utils.dart'; import 'package:netease_common_ui/widgets/transparent_scaffold.dart'; -import 'package:connectivity_plus/connectivity_plus.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_svg/svg.dart'; +import 'package:netease_corekit_im/services/message/nim_chat_cache.dart'; import 'package:nim_core/nim_core.dart'; import 'package:nim_teamkit/model/team_default_icon.dart'; import 'package:nim_teamkit/repo/team_repo.dart'; @@ -21,7 +23,9 @@ import '../../team_kit_client.dart'; class TeamKitAvatarEditorPage extends StatefulWidget { final NIMTeam team; - const TeamKitAvatarEditorPage({Key? key, required this.team}) + final String? avatar; + + const TeamKitAvatarEditorPage({Key? key, required this.team, this.avatar}) : super(key: key); @override @@ -56,14 +60,13 @@ class TeamKitAvatarEditorState extends State { @override Widget build(BuildContext context) { return TransparentScaffold( - leading: TextButton( + leading: IconButton( onPressed: () { Navigator.pop(context); }, - child: Text( - S.of(context).teamCancel, - style: TextStyle(fontSize: 16, color: '#666666'.toColor()), - ), + icon: Text(S.of(context).teamCancel, + style: TextStyle(fontSize: 16, color: '#666666'.toColor()), + maxLines: 1), ), title: S.of(context).teamUpdateIcon, centerTitle: true, @@ -76,6 +79,15 @@ class TeamKitAvatarEditorState extends State { if (photoAvatar != null) { TeamRepo.updateTeamIcon(widget.team.id!, photoAvatar!) .then((value) { + if (!value) { + if (!NIMChatCache.instance.hasPrivilegeToModify()) { + Fluttertoast.showToast( + msg: S.of(context).teamPermissionDeny); + } else { + Fluttertoast.showToast( + msg: S.of(context).teamSettingFailed); + } + } Navigator.pop(context, photoAvatar!); }); } @@ -102,7 +114,8 @@ class TeamKitAvatarEditorState extends State { Avatar( width: 80, height: 80, - avatar: photoAvatar ?? widget.team.icon, + avatar: photoAvatar ?? + (widget.avatar ?? widget.team.icon), name: widget.team.name, ), Align( diff --git a/nim_teamkit_ui/lib/view/pages/team_kit_manage_page.dart b/nim_teamkit_ui/lib/view/pages/team_kit_manage_page.dart new file mode 100644 index 0000000..97aeaf9 --- /dev/null +++ b/nim_teamkit_ui/lib/view/pages/team_kit_manage_page.dart @@ -0,0 +1,314 @@ +// Copyright (c) 2022 NetEase, Inc. All rights reserved. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +import 'dart:async'; +import 'dart:convert'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:netease_common_ui/ui/background.dart'; +import 'package:netease_common_ui/ui/dialog.dart'; +import 'package:netease_common_ui/utils/color_utils.dart'; +import 'package:netease_common_ui/widgets/transparent_scaffold.dart'; +import 'package:netease_corekit_im/model/team_models.dart'; +import 'package:netease_corekit_im/services/message/nim_chat_cache.dart'; +import 'package:nim_core/nim_core.dart'; +import 'package:nim_teamkit/repo/team_repo.dart'; +import 'package:nim_teamkit_ui/view/pages/team_kit_manager_list_page.dart'; + +import '../../l10n/S.dart'; + +class TeamKitManagerPage extends StatefulWidget { + final NIMTeam team; + + const TeamKitManagerPage({Key? key, required this.team}) : super(key: key); + + @override + _TeamKitManagerPageState createState() => _TeamKitManagerPageState(); +} + +class _TeamKitManagerPageState extends State { + late NIMTeamInviteModeEnum invitePrivilege; + + late NIMTeamUpdateModeEnum updatePrivilege; + + String aitPrivilege = aitPrivilegeAll; + + List _teamSubs = []; + + int _managerCount = 0; + + TextStyle style = + const TextStyle(color: CommonColors.color_333333, fontSize: 16); + + @override + void initState() { + invitePrivilege = + (NIMChatCache.instance.teamInfo as NIMTeam).teamInviteMode; + updatePrivilege = + (NIMChatCache.instance.teamInfo as NIMTeam).teamUpdateMode; + _parseExtension((NIMChatCache.instance.teamInfo as NIMTeam).extension); + _updateManagerCount(NIMChatCache.instance.teamMembers); + _initListener(); + super.initState(); + } + + @override + void dispose() { + _teamSubs.forEach((element) { + element.cancel(); + }); + super.dispose(); + } + + void _initListener() { + _teamSubs.addAll([ + NIMChatCache.instance.teamInfoNotifier.listen((event) { + if (event is NIMTeam) { + invitePrivilege = event.teamInviteMode; + updatePrivilege = event.teamUpdateMode; + _parseExtension(event.extension); + if (mounted) { + setState(() {}); + } + } + }), + NIMChatCache.instance.teamMembersNotifier.listen((event) { + if (event is List) { + _updateManagerCount(event); + if (mounted) { + setState(() {}); + } + } + }), + ]); + } + + void _parseExtension(String? extension) { + if (extension?.isNotEmpty == true) { + var extMap = json.decode(extension!) as Map?; + if (extMap != null) { + aitPrivilege = extMap[aitPrivilegeKey] as String? ?? aitPrivilegeAll; + } + } + } + + void _updateManagerCount(List? memberList) { + _managerCount = memberList + ?.where( + (element) => element.teamInfo.type == TeamMemberType.manager) + .length ?? + 0; + } + + Widget _setting(BuildContext context, NIMTeam team) { + return Column( + children: ListTile.divideTiles(context: context, tiles: [ + ListTile( + title: Text( + S.of(context).teamUpdateInfoPermission, + style: style, + ), + subtitle: Text( + updatePrivilege == NIMTeamUpdateModeEnum.all + ? S.of(context).teamAllMember + : S.of(context).teamOwnerManager, + style: + const TextStyle(fontSize: 14, color: CommonColors.color_999999), + ), + trailing: const Icon(Icons.keyboard_arrow_right_outlined), + onTap: () { + if (NIMChatCache.instance.myTeamRole() == TeamMemberType.normal) { + Fluttertoast.showToast( + msg: S.of(context).teamNoOperatePermission); + return; + } + _showTeamIdentifyDialog((value) { + if (value != null) { + var updateMode = value == 1 + ? NIMTeamUpdateModeEnum.all + : NIMTeamUpdateModeEnum.manager; + TeamRepo.updateTeamInfoPrivilege(team.id!, updateMode) + .then((value) { + if (value) { + updatePrivilege = updateMode; + setState(() {}); + } + }); + } + }); + }, + ), + ListTile( + title: Text( + S.of(context).teamInviteOtherPermission, + style: style, + ), + subtitle: Text( + invitePrivilege == NIMTeamInviteModeEnum.all + ? S.of(context).teamAllMember + : S.of(context).teamOwnerManager, + style: + const TextStyle(fontSize: 14, color: CommonColors.color_999999), + ), + trailing: const Icon(Icons.keyboard_arrow_right_outlined), + onTap: () { + if (NIMChatCache.instance.myTeamRole() == TeamMemberType.normal) { + Fluttertoast.showToast( + msg: S.of(context).teamNoOperatePermission); + return; + } + _showTeamIdentifyDialog((value) { + if (value != null) { + var modeEnum = value == 1 + ? NIMTeamInviteModeEnum.all + : NIMTeamInviteModeEnum.manager; + TeamRepo.updateInviteMode(team.id!, modeEnum).then((value) { + if (value) {} + }); + } + }); + }, + ), + ListTile( + title: Text( + S.of(context).teamAitPermission, + style: style, + ), + subtitle: Text( + aitPrivilege == aitPrivilegeAll + ? S.of(context).teamAllMember + : S.of(context).teamOwnerManager, + style: + const TextStyle(fontSize: 14, color: CommonColors.color_999999), + ), + trailing: const Icon(Icons.keyboard_arrow_right_outlined), + onTap: () { + if (NIMChatCache.instance.myTeamRole() == TeamMemberType.normal) { + Fluttertoast.showToast( + msg: S.of(context).teamNoOperatePermission); + return; + } + _showTeamIdentifyDialog((value) { + if (value != null) { + var aitModel = + value == 1 ? aitPrivilegeAll : aitPrivilegeManager; + TeamRepo.updateTeamExtension(team.id!, + _updateTeamExtensionByAitPrivilegeAll(aitModel)) + .then((value) { + if (value == false) { + Fluttertoast.showToast( + msg: S.of(context).teamSettingFailed); + } else { + var tip = aitModel == aitPrivilegeAll + ? S.of(context).teamMsgAitAllPrivilegeIsAll + : S.of(context).teamMsgAitAllPrivilegeIsOwner; + MessageBuilder.createTipMessage( + sessionId: team.id!, + sessionType: NIMSessionType.team, + content: tip) + .then((value) { + if (value.isSuccess && value.data != null) { + value.data!.config = NIMCustomMessageConfig( + enableUnreadCount: false, enablePush: false); + NimCore.instance.messageService + .sendMessage(message: value.data!); + } + }); + } + }); + } + }); + }, + ), + ]).toList(), + ); + } + + String _updateTeamExtensionByAitPrivilegeAll(String aitModel) { + var extension = widget.team.extension; + if (extension?.isNotEmpty == true) { + var extMap = (json.decode(extension!) as Map?)?.cast(); + if (extMap != null) { + extMap[aitPrivilegeKey] = aitModel; + return json.encode(extMap); + } + } + return json.encode({aitPrivilegeKey: aitModel}); + } + + void _showTeamIdentifyDialog(ValueChanged onChoose) { + var style = const TextStyle(fontSize: 16, color: CommonColors.color_333333); + showBottomChoose( + context: context, + actions: [ + CupertinoActionSheetAction( + onPressed: () { + Navigator.pop(context, 1); + }, + child: Text( + S.of(context).teamAllMember, + style: style, + ), + ), + CupertinoActionSheetAction( + onPressed: () { + Navigator.pop(context, 0); + }, + child: Text( + S.of(context).teamOwnerManager, + style: style, + ), + ), + ], + showCancel: true) + .then((value) => onChoose(value)); + } + + @override + Widget build(BuildContext context) { + return TransparentScaffold( + title: S.of(context).teamManage, + body: SingleChildScrollView( + scrollDirection: Axis.vertical, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Column( + children: [ + if (NIMChatCache.instance.myTeamRole() == TeamMemberType.owner) + CardBackground( + child: ListTile( + title: Text( + S.of(context).teamManagerManagers, + style: style, + ), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + _managerCount.toString(), + style: TextStyle( + fontSize: 16, color: CommonColors.color_999999), + ), + const Icon(Icons.keyboard_arrow_right_outlined) + ], + ), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => TeamKitManagerListPage( + tId: widget.team.id!, + ))); + }, + ), + ), + CardBackground(child: _setting(context, widget.team)), + ], + )), + ), + ); + } +} diff --git a/nim_teamkit_ui/lib/view/pages/team_kit_manager_list_page.dart b/nim_teamkit_ui/lib/view/pages/team_kit_manager_list_page.dart new file mode 100644 index 0000000..9652db8 --- /dev/null +++ b/nim_teamkit_ui/lib/view/pages/team_kit_manager_list_page.dart @@ -0,0 +1,240 @@ +// Copyright (c) 2022 NetEase, Inc. All rights reserved. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:netease_common_ui/base/base_state.dart'; +import 'package:netease_common_ui/ui/avatar.dart'; +import 'package:netease_common_ui/ui/dialog.dart'; +import 'package:netease_common_ui/utils/color_utils.dart'; +import 'package:netease_corekit_im/model/team_models.dart'; +import 'package:netease_corekit_im/router/imkit_router_factory.dart'; +import 'package:netease_corekit_im/service_locator.dart'; +import 'package:netease_corekit_im/services/login/login_service.dart'; +import 'package:netease_corekit_im/services/message/nim_chat_cache.dart'; +import 'package:nim_core/nim_core.dart'; +import 'package:nim_teamkit_ui/view/pages/team_kit_member_list_page.dart'; +import 'package:provider/provider.dart'; + +import '../../l10n/S.dart'; +import '../../team_kit_client.dart'; +import '../../view_model/team_setting_view_model.dart'; + +class TeamKitManagerListPage extends StatefulWidget { + final String tId; + + const TeamKitManagerListPage({Key? key, required this.tId}) : super(key: key); + + @override + State createState() => TeamKitManagerListPageState(); +} + +class TeamKitManagerListPageState extends State { + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return ChangeNotifierProvider( + create: (context) { + var viewModel = TeamSettingViewModel(); + viewModel.requestTeamMembers(widget.tId); + viewModel.addTeamSubscribe(); + return viewModel; + }, + builder: (context, child) { + var memberList = context + .watch() + .userInfoData + ?.where((e) => e.teamInfo.type == TeamMemberType.manager) + .toList(); + memberList?.sort( + (a, b) => a.teamInfo.joinTime.compareTo(b.teamInfo.joinTime)); + return Scaffold( + appBar: AppBar( + leading: IconButton( + icon: const Icon(Icons.arrow_back_ios_rounded), + onPressed: () { + Navigator.pop(context); + }, + ), + title: Text(S.of(context).teamManagers, + style: TextStyle(color: '#333333'.toColor(), fontSize: 16)), + backgroundColor: Colors.white, + iconTheme: Theme.of(context) + .primaryIconTheme + .copyWith(color: Colors.grey), + elevation: 0, + centerTitle: true), + body: Container( + padding: const EdgeInsets.symmetric(horizontal: 20), + color: Colors.white, + child: Column( + children: [ + ListTile( + title: Text( + S.of(context).teamAddManagers, + style: TextStyle(fontSize: 16, color: '#333333'.toColor()), + ), + trailing: const Icon(Icons.keyboard_arrow_right_outlined), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => TeamKitMemberListPage( + tId: widget.tId, + showOwnerAndManager: false, + isSelectModel: true, + ))).then((value) { + if (value is List) { + context + .read() + .addTeamManager(widget.tId, value); + } + }); + }, + ), + (memberList?.length ?? 0) > 0 + ? Expanded( + child: ListView.builder( + itemCount: memberList?.length ?? 0, + itemBuilder: (context, index) { + var user = memberList?[index]; + return TeamMemberListItem(teamMember: user!); + }), + ) + : Column( + children: [ + SizedBox( + height: 170, + ), + SvgPicture.asset( + 'images/ic_member_empty.svg', + package: kPackage, + ), + Padding( + padding: EdgeInsets.only(top: 18), + child: Text( + S.of(context).teamManagerEmpty, + style: TextStyle( + color: CommonColors.color_b3b7bc, + fontSize: 14), + ), + ), + ], + ) + ], + ), + ), + ); + }, + ); + } +} + +class TeamMemberListItem extends StatefulWidget { + final UserInfoWithTeam teamMember; + + const TeamMemberListItem({Key? key, required this.teamMember}) + : super(key: key); + + @override + State createState() => TeamMemberListItemState(); +} + +class TeamMemberListItemState extends BaseState { + void _showRemoveConfirmDialog( + BuildContext context, String tid, String account) { + showCommonDialog( + context: context, + title: S.of(context).teamRemoveConfirm, + content: S.of(context).teamRemoveConfirmContent, + positiveContent: S.of(context).teamMemberRemove, + navigateContent: S.of(context).teamCancel, + ).then((value) { + if (value == true) { + if (checkNetwork()) { + context + .read() + .removeTeamManager(tid, account) + .then((value) { + if (!value.isSuccess) { + Fluttertoast.showToast( + msg: S.of(context).teamManagerRemoveFailed); + } + }); + } + } + }); + } + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: () { + if (getIt().userInfo?.userId == + widget.teamMember.userInfo?.userId) { + gotoMineInfoPage(context); + } else { + goToContactDetail(context, widget.teamMember.userInfo!.userId!); + } + }, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 10), + child: Row( + children: [ + Avatar( + width: 42, + height: 42, + avatar: widget.teamMember.getAvatar(), + name: widget.teamMember + .getName(needAlias: false, needTeamNick: false), + bgCode: AvatarColor.avatarColor( + content: widget.teamMember.teamInfo.account), + ), + const Padding(padding: EdgeInsets.symmetric(horizontal: 7)), + Expanded( + child: Text( + widget.teamMember.getName(), + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle(fontSize: 16, color: '#333333'.toColor()), + ), + ), + if (NIMChatCache.instance.myTeamRole() == TeamMemberType.owner) + TextButton( + onPressed: () { + _showRemoveConfirmDialog( + context, + widget.teamMember.teamInfo.id!, + widget.teamMember.userInfo!.userId!); + }, + child: Container( + width: 50, + padding: + const EdgeInsets.symmetric(horizontal: 10, vertical: 2), + decoration: BoxDecoration( + border: Border.all(color: '#E6605C'.toColor(), width: 1), + borderRadius: BorderRadius.circular(10)), + child: Text( + S.of(context).teamMemberRemove, + maxLines: 1, + style: TextStyle(fontSize: 12, color: '#E6605C'.toColor()), + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/nim_teamkit_ui/lib/view/pages/team_kit_member_list_page.dart b/nim_teamkit_ui/lib/view/pages/team_kit_member_list_page.dart index 33c60a8..1ac5c31 100644 --- a/nim_teamkit_ui/lib/view/pages/team_kit_member_list_page.dart +++ b/nim_teamkit_ui/lib/view/pages/team_kit_member_list_page.dart @@ -2,29 +2,49 @@ // Use of this source code is governed by a MIT license that can be // found in the LICENSE file. -import 'package:netease_corekit_im/router/imkit_router_factory.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:netease_common_ui/base/base_state.dart'; import 'package:netease_common_ui/ui/avatar.dart'; +import 'package:netease_common_ui/ui/dialog.dart'; import 'package:netease_common_ui/utils/color_utils.dart'; +import 'package:netease_common_ui/widgets/radio_button.dart'; import 'package:netease_corekit_im/model/team_models.dart'; +import 'package:netease_corekit_im/router/imkit_router_factory.dart'; import 'package:netease_corekit_im/service_locator.dart'; import 'package:netease_corekit_im/services/login/login_service.dart'; -import 'package:flutter/material.dart'; +import 'package:netease_corekit_im/services/message/nim_chat_cache.dart'; import 'package:nim_core/nim_core.dart'; +import 'package:nim_teamkit_ui/team_kit_client.dart'; import 'package:provider/provider.dart'; import '../../l10n/S.dart'; import '../../view_model/team_setting_view_model.dart'; +/// 默认群管理数量限制 +const int teamManagersLimitDefault = 10; + class TeamKitMemberListPage extends StatefulWidget { final String tId; - const TeamKitMemberListPage({Key? key, required this.tId}) : super(key: key); + /// 是否显示群主和管理员 + final bool showOwnerAndManager; + + final bool isSelectModel; + + const TeamKitMemberListPage( + {Key? key, + required this.tId, + this.showOwnerAndManager = true, + this.isSelectModel = false}) + : super(key: key); @override State createState() => TeamKitMemberListPageState(); } -class TeamKitMemberListPageState extends State { +class TeamKitMemberListPageState extends BaseState { String? filterStr; void _onFilterChange(String text, BuildContext context) { @@ -59,22 +79,85 @@ class TeamKitMemberListPageState extends State { }, builder: (context, child) { var memberList = context.watch().filterList; + if (!widget.showOwnerAndManager) { + memberList = memberList + ?.where((e) => + e.teamInfo.type != TeamMemberType.owner && + e.teamInfo.type != TeamMemberType.manager) + .toList(); + } return Scaffold( appBar: AppBar( - leading: IconButton( - icon: const Icon(Icons.arrow_back_ios_rounded), - onPressed: () { - Navigator.pop(context); - }, - ), - title: Text(S.of(context).teamMemberTitle, - style: TextStyle(color: '#333333'.toColor(), fontSize: 16)), - backgroundColor: Colors.white, - iconTheme: Theme.of(context) - .primaryIconTheme - .copyWith(color: Colors.grey), - elevation: 0, - centerTitle: true), + leading: IconButton( + icon: widget.isSelectModel + ? Text( + S.of(context).teamCancel, + style: + TextStyle(color: '#666666'.toColor(), fontSize: 16), + ) + : const Icon(Icons.arrow_back_ios_rounded), + onPressed: () { + Navigator.pop(context); + }, + ), + title: Text( + widget.isSelectModel + ? S.of(context).teamMemberSelect + : S.of(context).teamMemberTitle, + style: TextStyle(color: '#333333'.toColor(), fontSize: 16)), + backgroundColor: Colors.white, + iconTheme: + Theme.of(context).primaryIconTheme.copyWith(color: Colors.grey), + elevation: 0, + centerTitle: true, + actions: [ + if (widget.isSelectModel) + TextButton( + onPressed: () { + if (!checkNetwork()) { + return; + } + if (context + .read() + .selectedList + .isNotEmpty) { + int managerLimit = + TeamKitClient.instance.teamManagerLimit ?? + teamManagersLimitDefault; + int teamManagersCount = + NIMChatCache.instance.getTeamManagers()?.length ?? + 0; + if (teamManagersCount + + context + .read() + .selectedList + .length > + managerLimit) { + Fluttertoast.showToast( + msg: S + .of(context) + .teamManagerLimit(managerLimit.toString())); + return; + } + Navigator.pop( + context, + context + .read() + .selectedList + .map((e) => e.teamInfo.account!) + .toList()); + } else { + Fluttertoast.showToast( + msg: S.of(context).teamSelectMembers); + } + }, + child: Text( + '${S.of(context).teamConfirm}(${context.read().selectedList.length})', + style: + TextStyle(color: '#337EFF'.toColor(), fontSize: 16), + )) + ], + ), body: Container( padding: const EdgeInsets.all(20), color: Colors.white, @@ -99,13 +182,36 @@ class TeamKitMemberListPageState extends State { TextStyle(fontSize: 14, color: '#A6ADB6'.toColor()), prefixIcon: const Icon(Icons.search)), ), - Expanded( - child: ListView.builder( - itemCount: memberList?.length ?? 0, - itemBuilder: (context, index) { - var user = memberList?[index]; - return TeamMemberListItem(teamMember: user!); - })) + memberList?.isNotEmpty == true + ? Expanded( + child: ListView.builder( + itemCount: memberList?.length ?? 0, + itemBuilder: (context, index) { + var user = memberList?[index]; + return TeamMemberListItem( + teamMember: user!, + isSelectModel: widget.isSelectModel); + })) + : Column( + children: [ + SizedBox( + height: 170, + ), + SvgPicture.asset( + 'images/ic_member_empty.svg', + package: kPackage, + ), + Padding( + padding: EdgeInsets.only(top: 18), + child: Text( + S.of(context).teamMemberEmpty, + style: TextStyle( + color: CommonColors.color_b3b7bc, + fontSize: 14), + ), + ), + ], + ) ], ), ), @@ -118,18 +224,71 @@ class TeamKitMemberListPageState extends State { class TeamMemberListItem extends StatefulWidget { final UserInfoWithTeam teamMember; - const TeamMemberListItem({Key? key, required this.teamMember}) + final bool isSelectModel; + + const TeamMemberListItem( + {Key? key, required this.teamMember, this.isSelectModel = false}) : super(key: key); @override State createState() => TeamMemberListItemState(); } -class TeamMemberListItemState extends State { +class TeamMemberListItemState extends BaseState { + bool _showRemoveButton(UserInfoWithTeam teamMember) { + if (teamMember.teamInfo.type == TeamMemberType.owner) { + return false; + } + if (NIMChatCache.instance.myTeamRole() == TeamMemberType.owner) { + return true; + } else if (NIMChatCache.instance.myTeamRole() == TeamMemberType.manager) { + if (teamMember.teamInfo.type == TeamMemberType.normal) { + return true; + } + } + return false; + } + + void _showRemoveConfirmDialog( + BuildContext context, String tid, String account) { + showCommonDialog( + context: context, + title: S.of(context).teamRemoveConfirm, + content: S.of(context).teamMemberRemoveContent, + positiveContent: S.of(context).teamMemberRemove, + navigateContent: S.of(context).teamCancel, + ).then((value) { + if (value == true) { + if (checkNetwork()) { + context + .read() + .removeTeamMember(tid, account) + .then((value) { + if (value.isSuccess == false) { + Fluttertoast.showToast(msg: S.of(context).teamMemberRemoveFailed); + } + }); + } + } + }); + } + @override Widget build(BuildContext context) { return InkWell( onTap: () { + if (widget.isSelectModel) { + if (context + .read() + .isSelected(widget.teamMember)) { + context + .read() + .removeSelected(widget.teamMember); + } else { + context.read().addSelected(widget.teamMember); + } + return; + } if (getIt().userInfo?.userId == widget.teamMember.userInfo?.userId) { gotoMineInfoPage(context); @@ -141,6 +300,17 @@ class TeamMemberListItemState extends State { padding: const EdgeInsets.symmetric(vertical: 10), child: Row( children: [ + if (widget.isSelectModel) + Container( + margin: const EdgeInsets.only(right: 10), + // 选择框 + child: CheckBoxButton( + isChecked: context + .watch() + .isSelected(widget.teamMember), + clickable: false, + ), + ), Avatar( width: 42, height: 42, @@ -164,12 +334,52 @@ class TeamMemberListItemState extends State { padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 2), decoration: BoxDecoration( - color: '#e0ecff'.toColor(), + color: '#F7F7F7'.toColor(), + border: Border.all(color: '#D6D8DB'.toColor(), width: 1), borderRadius: BorderRadius.circular(10)), child: Text( S.of(context).teamOwner, - style: TextStyle(fontSize: 12, color: '#337eff'.toColor()), + style: TextStyle(fontSize: 12, color: '#656A72'.toColor()), ), + ), + if (widget.teamMember.teamInfo.type == TeamMemberType.manager) + Container( + padding: + const EdgeInsets.symmetric(horizontal: 10, vertical: 2), + decoration: BoxDecoration( + color: '#F7F7F7'.toColor(), + border: Border.all(color: '#D6D8DB'.toColor(), width: 1), + borderRadius: BorderRadius.circular(10)), + child: Text( + S.of(context).teamManager, + style: TextStyle(fontSize: 12, color: '#656A72'.toColor()), + ), + ), + if (!widget.isSelectModel && _showRemoveButton(widget.teamMember)) + TextButton( + onPressed: () { + _showRemoveConfirmDialog( + context, + widget.teamMember.teamInfo.id!, + widget.teamMember.userInfo!.userId!); + }, + child: Container( + width: 50, + padding: + const EdgeInsets.symmetric(horizontal: 10, vertical: 2), + decoration: BoxDecoration( + border: Border.all(color: '#E6605C'.toColor(), width: 1), + borderRadius: BorderRadius.circular(10)), + child: Text( + S.of(context).teamMemberRemove, + maxLines: 1, + style: TextStyle(fontSize: 12, color: '#E6605C'.toColor()), + ), + ), + ), + if (!widget.isSelectModel && !_showRemoveButton(widget.teamMember)) + Container( + width: 70, ) ], ), diff --git a/nim_teamkit_ui/lib/view/pages/team_kit_setting_page.dart b/nim_teamkit_ui/lib/view/pages/team_kit_setting_page.dart index bbe6a89..e901b47 100644 --- a/nim_teamkit_ui/lib/view/pages/team_kit_setting_page.dart +++ b/nim_teamkit_ui/lib/view/pages/team_kit_setting_page.dart @@ -2,9 +2,12 @@ // Use of this source code is governed by a MIT license that can be // found in the LICENSE file. +import 'dart:io'; + import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:fluttertoast/fluttertoast.dart'; import 'package:netease_common_ui/ui/avatar.dart'; import 'package:netease_common_ui/ui/background.dart'; import 'package:netease_common_ui/ui/dialog.dart'; @@ -17,9 +20,11 @@ import 'package:netease_corekit_im/router/imkit_router_constants.dart'; import 'package:netease_corekit_im/router/imkit_router_factory.dart'; import 'package:netease_corekit_im/service_locator.dart'; import 'package:netease_corekit_im/services/login/login_service.dart'; +import 'package:netease_corekit_im/services/message/nim_chat_cache.dart'; import 'package:netease_corekit_im/services/team/team_provider.dart'; import 'package:nim_core/nim_core.dart'; import 'package:nim_teamkit_ui/l10n/S.dart'; +import 'package:nim_teamkit_ui/view/pages/team_kit_manage_page.dart'; import 'package:nim_teamkit_ui/view/pages/team_kit_member_list_page.dart'; import 'package:nim_teamkit_ui/view/pages/team_kit_team_info_page.dart'; import 'package:provider/provider.dart'; @@ -40,17 +45,6 @@ class _TeamSettingPageState extends State { TextStyle style = const TextStyle(color: CommonColors.color_333333, fontSize: 16); - //是否有权限邀请其他人 - bool _hasPrivilegeToInvite(TeamWithMember teamWithMember) { - var team = teamWithMember.team; - var teamMember = teamWithMember.teamMember; - return (team.teamInviteMode == NIMTeamInviteModeEnum.all) || - (!getIt().isGroupTeam(team) && - (teamMember?.type == TeamMemberType.owner || - teamMember?.type == TeamMemberType.manager)) || - getIt().isGroupTeam(team); - } - //是否有权限修改群信息 bool _hasPrivilegeToModify(TeamWithMember teamWithMember) { var team = teamWithMember.team; @@ -66,7 +60,7 @@ class _TeamSettingPageState extends State { List? list) { var team = teamWithMember.team; - bool hasPrivilegeToInvite = _hasPrivilegeToInvite(teamWithMember); + bool hasPrivilegeToInvite = NIMChatCache.instance.hasPrivilegeToInvite(); int _getListCount() { var count = list?.length ?? 0; @@ -215,9 +209,24 @@ class _TeamSettingPageState extends State { .then((contacts) { if (contacts is List && contacts.isNotEmpty) { - context.read().addMembers( - team.id!, - contacts.map((e) => e.user.userId!).toList()); + if (NIMChatCache.instance.hasPrivilegeToInvite()) { + context + .read() + .addMembers( + team.id!, + contacts + .map((e) => e.user.userId!) + .toList()) + .then((value) { + if (value.isSuccess != true) { + Fluttertoast.showToast( + msg: S.of(context).teamSettingFailed); + } + }); + } else { + Fluttertoast.showToast( + msg: S.of(context).teamNoOperatePermission); + } } }); }, @@ -236,7 +245,7 @@ class _TeamSettingPageState extends State { ); } - Widget _setting(BuildContext context, NIMTeam team) { + Widget _setting(BuildContext context, TeamWithMember teamMember) { return Column( children: ListTile.divideTiles(context: context, tiles: [ ListTile( @@ -250,12 +259,7 @@ class _TeamSettingPageState extends State { arguments: { 'sessionId': widget.teamId, 'sessionType': NIMSessionType.team, - 'chatTitle': context - .read() - .teamWithMember - ?.team - .name ?? - '', + 'chatTitle': teamMember.team.name ?? '', }); }, ), @@ -270,70 +274,116 @@ class _TeamSettingPageState extends State { arguments: {'teamId': widget.teamId}); }, ), - ListTile( - title: Text( - S.of(context).teamMessageTip, - style: style, + if (getIt().isGroupTeam(teamMember.team)) ...[ + ListTile( + title: Text( + S.of(context).teamMessageTip, + style: style, + ), + trailing: CupertinoSwitch( + activeColor: CommonColors.color_337eff, + onChanged: (bool value) { + context + .read() + .muteTeam(teamMember.team.id!, !value); + }, + value: context.read().messageTip, + ), ), - trailing: CupertinoSwitch( - activeColor: CommonColors.color_337eff, - onChanged: (bool value) { - context.read().muteTeam(team.id!, !value); - }, - value: context.read().messageTip, + ListTile( + title: Text( + S.of(context).teamSessionPin, + style: style, + ), + trailing: CupertinoSwitch( + activeColor: CommonColors.color_337eff, + onChanged: (bool value) { + context + .read() + .configStick(teamMember.team.id!, value); + }, + value: context.read().isStick, + ), + ) + ], + if (!getIt().isGroupTeam(teamMember.team)) ...[ + ListTile( + title: Text( + S.of(context).teamMessageTip, + style: style, + ), + trailing: CupertinoSwitch( + activeColor: CommonColors.color_337eff, + onChanged: (bool value) { + context + .read() + .muteTeam(teamMember.team.id!, !value); + }, + value: context.read().messageTip, + ), ), - ), - ListTile( - title: Text( - S.of(context).teamSessionPin, - style: style, + ListTile( + title: Text( + S.of(context).teamSessionPin, + style: style, + ), + trailing: CupertinoSwitch( + activeColor: CommonColors.color_337eff, + onChanged: (bool value) { + context + .read() + .configStick(teamMember.team.id!, value); + }, + value: context.read().isStick, + ), ), - trailing: CupertinoSwitch( - activeColor: CommonColors.color_337eff, - onChanged: (bool value) { - context.read().configStick(team.id!, value); + ListTile( + title: Text( + S.of(context).teamMyNicknameTitle, + style: style, + ), + trailing: const Icon(Icons.keyboard_arrow_right_outlined), + onTap: () { + var teamNick = + context.read().myTeamNickName; + Future _updateNick(nickname) async { + var result = await context + .read() + .updateNickname( + teamMember.team.id!, (nickname as String).trim()); + if (!result) { + Fluttertoast.showToast(msg: S.of(context).teamSettingFailed); + } + return result; + } + + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => UpdateTextInfoPage( + title: S.of(context).teamMyNicknameTitle, + content: teamNick, + maxLength: 30, + privilege: true, + onSave: _updateNick, + leading: Text( + S.of(context).teamCancel, + style: const TextStyle( + fontSize: 16, + color: CommonColors.color_666666), + ), + sureStr: S.of(context).teamSave, + ))); }, - value: context.read().isStick, ), - ), + ] ]).toList(), ); } - Widget _teamMute(BuildContext context, TeamWithMember teamWithMember) { + Widget _teamManage(BuildContext context, TeamWithMember teamWithMember) { return Column( children: ListTile.divideTiles(context: context, tiles: [ - ListTile( - title: Text( - S.of(context).teamMyNicknameTitle, - style: style, - ), - trailing: const Icon(Icons.keyboard_arrow_right_outlined), - onTap: () { - var teamNick = context.read().myTeamNickName; - Future _updateNick(nickname) { - return context.read().updateNickname( - teamWithMember.team.id!, (nickname as String).trim()); - } - - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => UpdateTextInfoPage( - title: S.of(context).teamMyNicknameTitle, - content: teamNick, - maxLength: 30, - privilege: true, - onSave: _updateNick, - leading: Text( - S.of(context).teamCancel, - style: const TextStyle( - fontSize: 16, color: CommonColors.color_666666), - ), - sureStr: S.of(context).teamSave, - ))); - }, - ), Visibility( visible: teamWithMember.teamMember?.type == TeamMemberType.owner, child: ListTile( @@ -352,108 +402,23 @@ class _TeamSettingPageState extends State { ), ), ), - ]).toList(), - ); - } - - void _showTeamIdentifyDialog(ValueChanged onChoose) { - var style = const TextStyle(fontSize: 16, color: CommonColors.color_333333); - showBottomChoose( - context: context, - actions: [ - CupertinoActionSheetAction( - onPressed: () { - Navigator.pop(context, 1); - }, - child: Text( - S.of(context).teamAllMember, - style: style, - ), - ), - CupertinoActionSheetAction( - onPressed: () { - Navigator.pop(context, 0); - }, - child: Text( - S.of(context).teamOwner, - style: style, - ), - ), - ], - showCancel: true) - .then((value) => onChoose(value)); - } - - Widget _invitation(BuildContext context, NIMTeam team) { - return Column( - children: ListTile.divideTiles(context: context, tiles: [ - ListTile( - title: Text( - S.of(context).teamInviteOtherPermission, - style: style, - ), - subtitle: Text( - context.read().invitePrivilege == - NIMTeamInviteModeEnum.all - ? S.of(context).teamAllMember - : S.of(context).teamOwner, - style: - const TextStyle(fontSize: 14, color: CommonColors.color_999999), - ), - trailing: const Icon(Icons.keyboard_arrow_right_outlined), - onTap: () { - _showTeamIdentifyDialog((value) { - if (value != null) { - context.read().updateInvitePrivilege( - team.id!, - value == 1 - ? NIMTeamInviteModeEnum.all - : NIMTeamInviteModeEnum.manager); - } - }); - }, - ), - ListTile( - title: Text( - S.of(context).teamUpdateInfoPermission, - style: style, - ), - subtitle: Text( - context.read().infoPrivilege == - NIMTeamUpdateModeEnum.all - ? S.of(context).teamAllMember - : S.of(context).teamOwner, - style: - const TextStyle(fontSize: 14, color: CommonColors.color_999999), - ), - trailing: const Icon(Icons.keyboard_arrow_right_outlined), - onTap: () { - _showTeamIdentifyDialog((value) { - if (value != null) { - context.read().updateInfoPrivilege( - team.id!, - value == 1 - ? NIMTeamUpdateModeEnum.all - : NIMTeamUpdateModeEnum.manager); - } - }); - }, - ), - ListTile( - title: Text( - S.of(context).teamNeedAgreedWhenBeInvitedPermission, - style: style, - ), - trailing: CupertinoSwitch( - activeColor: CommonColors.color_337eff, - onChanged: (bool value) { - context - .read() - .updateBeInviteMode(team.id!, value); + if (!getIt().isGroupTeam(teamWithMember.team) && + (NIMChatCache.instance.myTeamRole() == TeamMemberType.owner || + NIMChatCache.instance.myTeamRole() == TeamMemberType.manager)) + ListTile( + title: Text( + S.of(context).teamManage, + style: style, + ), + trailing: const Icon(Icons.keyboard_arrow_right_outlined), + onTap: () { + Navigator.push(context, MaterialPageRoute(builder: (context) { + return TeamKitManagerPage( + team: teamWithMember.team, + ); + })); }, - value: context.read().beInvitedNeedAgreed, ), - ), ]).toList(), ); } @@ -530,6 +495,15 @@ class _TeamSettingPageState extends State { ); } + @override + void initState() { + //ios 端需要重新获取群成员 + if (Platform.isIOS) { + NIMChatCache.instance.fetchTeamMember(widget.teamId); + } + super.initState(); + } + @override Widget build(BuildContext context) { return TransparentScaffold( @@ -563,25 +537,14 @@ class _TeamSettingPageState extends State { height: 16, ), CardBackground( - child: _setting(context, teamWithMember.team)), + child: _setting(context, teamWithMember)), Visibility( visible: !getIt() .isGroupTeam(teamWithMember.team), child: Padding( padding: const EdgeInsets.only(top: 16), child: CardBackground( - child: _teamMute(context, teamWithMember)), - )), - Visibility( - visible: !getIt() - .isGroupTeam(teamWithMember.team) && - teamWithMember.teamMember?.type == - TeamMemberType.owner, - child: Padding( - padding: const EdgeInsets.only(top: 16), - child: CardBackground( - child: _invitation( - context, teamWithMember.team)), + child: _teamManage(context, teamWithMember)), )), const SizedBox( height: 16, diff --git a/nim_teamkit_ui/lib/view/pages/team_kit_team_info_page.dart b/nim_teamkit_ui/lib/view/pages/team_kit_team_info_page.dart index dfd1c38..2fbea20 100644 --- a/nim_teamkit_ui/lib/view/pages/team_kit_team_info_page.dart +++ b/nim_teamkit_ui/lib/view/pages/team_kit_team_info_page.dart @@ -2,14 +2,15 @@ // Use of this source code is governed by a MIT license that can be // found in the LICENSE file. +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; import 'package:netease_common_ui/ui/avatar.dart'; import 'package:netease_common_ui/ui/background.dart'; import 'package:netease_common_ui/utils/color_utils.dart'; import 'package:netease_common_ui/widgets/transparent_scaffold.dart'; import 'package:netease_common_ui/widgets/update_text_info_page.dart'; -import 'package:flutter/material.dart'; -import 'package:fluttertoast/fluttertoast.dart'; import 'package:netease_corekit_im/service_locator.dart'; +import 'package:netease_corekit_im/services/message/nim_chat_cache.dart'; import 'package:netease_corekit_im/services/team/team_provider.dart'; import 'package:nim_core/nim_core.dart'; import 'package:nim_teamkit/repo/team_repo.dart'; @@ -44,6 +45,15 @@ class _TeamKitTeamInfoState extends State { } return TeamRepo.updateTeamName(widget.team.id!, name).then((value) { _updatedName = name; + if (!value) { + if (!NIMChatCache.instance.hasPrivilegeToModify()) { + Fluttertoast.showToast( + msg: S.of(context).teamPermissionDeny, gravity: ToastGravity.TOP); + } else { + Fluttertoast.showToast( + msg: S.of(context).teamSettingFailed, gravity: ToastGravity.TOP); + } + } return value; }); } @@ -52,6 +62,13 @@ class _TeamKitTeamInfoState extends State { return TeamRepo.updateTeamIntroduce(widget.team.id!, introduce) .then((result) { _updatedIntroduce = introduce; + if (!result) { + if (!NIMChatCache.instance.hasPrivilegeToModify()) { + Fluttertoast.showToast(msg: S.of(context).teamPermissionDeny); + } else { + Fluttertoast.showToast(msg: S.of(context).teamSettingFailed); + } + } return result; }); } @@ -75,13 +92,14 @@ class _TeamKitTeamInfoState extends State { children: [ InkWell( onTap: () { - if (!widget.hasPrivilegeToUpdateInfo) { + if (!NIMChatCache.instance.hasPrivilegeToModify()) { Fluttertoast.showToast(msg: S.of(context).teamNoPermission); return; } Navigator.push(context, MaterialPageRoute(builder: (context) { - return TeamKitAvatarEditorPage(team: widget.team); + return TeamKitAvatarEditorPage( + team: widget.team, avatar: avatar); })).then((value) { if (value?.isNotEmpty == true) { setState(() { @@ -132,10 +150,6 @@ class _TeamKitTeamInfoState extends State { ), InkWell( onTap: () { - if (!widget.hasPrivilegeToUpdateInfo) { - Fluttertoast.showToast(msg: S.of(context).teamNoPermission); - return; - } Navigator.push( context, MaterialPageRoute( @@ -143,7 +157,8 @@ class _TeamKitTeamInfoState extends State { title: S.of(context).teamNameTitle, content: _updatedName ?? widget.team.name, maxLength: 30, - privilege: true, + privilege: NIMChatCache.instance + .hasPrivilegeToModify(), onSave: _updateName, leading: Text( S.of(context).teamCancel, @@ -181,12 +196,6 @@ class _TeamKitTeamInfoState extends State { if (!getIt().isGroupTeam(widget.team)) InkWell( onTap: () { - if (!widget.hasPrivilegeToUpdateInfo) { - Fluttertoast.showToast( - msg: S.of(context).teamNoPermission); - return; - } - Navigator.push( context, MaterialPageRoute( @@ -195,7 +204,8 @@ class _TeamKitTeamInfoState extends State { content: _updatedIntroduce ?? widget.team.introduce, maxLength: 100, - privilege: true, + privilege: NIMChatCache.instance + .hasPrivilegeToModify(), onSave: _updateIntroduce, leading: Text( S.of(context).teamCancel, diff --git a/nim_teamkit_ui/lib/view_model/team_setting_view_model.dart b/nim_teamkit_ui/lib/view_model/team_setting_view_model.dart index 53bc251..20dec16 100644 --- a/nim_teamkit_ui/lib/view_model/team_setting_view_model.dart +++ b/nim_teamkit_ui/lib/view_model/team_setting_view_model.dart @@ -4,21 +4,24 @@ import 'dart:async'; +import 'package:flutter/material.dart'; import 'package:netease_common_ui/utils/connectivity_checker.dart'; import 'package:netease_corekit_im/model/team_models.dart'; import 'package:netease_corekit_im/service_locator.dart'; import 'package:netease_corekit_im/services/login/login_service.dart'; -import 'package:flutter/material.dart'; import 'package:netease_corekit_im/services/message/nim_chat_cache.dart'; import 'package:nim_core/nim_core.dart'; import 'package:nim_teamkit/repo/team_repo.dart'; class TeamSettingViewModel extends ChangeNotifier { + //当前用户在群里的身份 TeamWithMember? teamWithMember; List? userInfoData; List? filterList; + List selectedList = List.empty(growable: true); + bool messageTip = true; bool isStick = false; bool muteAllMember = false; @@ -70,15 +73,49 @@ class TeamSettingViewModel extends ChangeNotifier { _teamSub.addAll([ NIMChatCache.instance.teamMembersNotifier.listen((event) { userInfoData = event; - notifyListeners(); //更新完毕后重新排序,可能有新成员加入 if (_searchKey?.isNotEmpty == true) { filterByText(_searchKey); } + //移除选择列表中不存在的成员 + if (selectedList.isNotEmpty) { + var allMembers = + userInfoData?.map((e) => e.teamInfo.account).toList(); + selectedList.removeWhere( + (element) => !allMembers!.contains(element.teamInfo.account)); + } + notifyListeners(); }), ]); } + void addSelected(UserInfoWithTeam userInfoWithTeam) { + selectedList.add(userInfoWithTeam); + notifyListeners(); + } + + void removeSelected(UserInfoWithTeam userInfoWithTeam) { + selectedList.remove(userInfoWithTeam); + notifyListeners(); + } + + bool isSelected(UserInfoWithTeam userInfoWithTeam) { + return selectedList.contains(userInfoWithTeam); + } + + void addTeamManager(String tid, List accounts) { + TeamRepo.addTeamManager(tid, accounts).then((value) {}); + } + + Future>> removeTeamManager( + String tid, String accId) { + return TeamRepo.removeTeamManager(tid, [accId]); + } + + Future> removeTeamMember(String tid, String accId) { + return TeamRepo.removeTeamMembers(tid, [accId]); + } + void filterByText(String? filterStr) { _searchKey = filterStr; if (filterStr == null || filterStr.isEmpty) { @@ -200,8 +237,9 @@ class TeamSettingViewModel extends ChangeNotifier { }); } - void addMembers(String teamId, List members) { - TeamRepo.inviteUser(teamId, members).then((value) {}); + Future>> addMembers( + String teamId, List members) { + return TeamRepo.inviteUser(teamId, members); } @override diff --git a/nim_teamkit_ui/pubspec.yaml b/nim_teamkit_ui/pubspec.yaml index 437d690..9517099 100644 --- a/nim_teamkit_ui/pubspec.yaml +++ b/nim_teamkit_ui/pubspec.yaml @@ -1,6 +1,6 @@ name: nim_teamkit_ui description: Team UI base on TeamKit. -version: 1.2.0 +version: 9.7.0 homepage: https://github.com/netease-kit/nim-uikit-flutter # Remove this line if you wish to publish to pub.dev #publish_to: none @@ -16,21 +16,21 @@ dependencies: sdk: flutter nim_teamkit: - ">=1.2.0 <1.3.0" + ">=9.7.0 <10.0.0" # path: ../nim_teamkit netease_common_ui: - ">=1.2.0 <1.3.0" + ">=9.7.0 <10.0.0" # path: ../../common/netease_common_ui netease_corekit_im: - ">=1.2.0 <1.3.0" + ">=9.7.0 <10.0.0" # path: ../../netease_corekit/netease_corekit_im netease_corekit: ">=1.2.0 <1.3.0" # path: ../../netease_corekit/netease_corekit - nim_core: ^1.7.3 + nim_core: ^1.7.4 # path: ../../nim_core/nim_core provider: ^6.0.0 - fluttertoast: ^8.0.9 + fluttertoast: ^8.2.4 intl: ^0.18.0 flutter_svg: ^2.0.7 connectivity_plus: ^5.0.0