Flutter TUIKit V2.1.0

This commit is contained in:
anonymous 2023-05-30 15:50:10 +08:00
parent d93b15a35f
commit 599ff9b9c4
76 changed files with 2384 additions and 1918 deletions

View File

@ -1,3 +1,55 @@
## 2.1.0
### Breaking Changes
* Migrated to Flutter 3.10.0 and Dart 3.0.0, no longer supporting projects with Flutter < 3.10.0 and Dart < 3.0.0.
* Updated the minimum requirement for Android AGP to 7.0, projects with AGP < 7.0 are no longer supported.
We highly recommend updating to these new versions for a better experience.
### New Features
* Added several methods to `TIMUIKitChatController`, including `hideAllBottomPanelOnMobile`, `mentionOtherMemberInGroup`, `setInputTextField`, and `getGroupMemberList`. Please refer to the corresponding annotations for usage.
* Added more parameter fields to the `TIMUIKitChatController`'s `sendMessage` method. For details, please refer to the corresponding annotations.
* Added `onSecondaryTapAvatar` to `TIMUIKitChat`, serving as callback trigger for secondary avatar clicks in the message list.
* Introduced `isUseMessageHoverBarOnDesktop` and `desktopMessageInputFieldLines` to `TIMUIKitChatConfig`. For usage details, please refer to the corresponding annotations.
### Improvements
* Enhanced performance and user experience when switching conversations on Desktop, including features like text field auto-focus and draft text.
* Enabled displaying correct new lines in markdown mode.
* Changed the order of members in the mentioned member selection panel: Group Owner => Group Administrator => Member, sorted based on the code units' first differing position in the member show names.
* Implemented auto-focus after clicking a member in the mentioned member selection panel.
* Added text field auto-focus when replying to a message.
* Updated other members' display names in at-tag messages to use `namecard`, followed by `nickname` and `userId`.
* Widened Desktop message input area's control bar.
* Replaced the default icon in Desktop's message input area from `png` to `svg` for better performance and clarity. `DesktopControlBarConfig` now supports defining `svgPath` for each item as well.
* Improved Web platform detection.
* Mentioning "all" or "at all" can now only be used by group owners and administrators.
* Supported returning null for each message item builder in `MessageItemBuilder` to use the default message widget.
* Enhanced group members filtering in the group member mentioned selection panel with case-insensitive fuzzy matching, leading to increased filtering accuracy.
* For security purposes, downloading files by `fetch` and `blob` in the Web now replaces previewing files in a new browser tab, whereas previewing images and videos is displayed in a new tab on the Web.
* Changed the default order in the message tooltip menu.
* Previewing images and videos is set to open in a new tab on the Web.
* Improved the ratio for sending video messages.
### Bug Fixes
* Fixed issues when enabling the section function in markdown mode with `inEnableTextSelection` set to `true`.
* Addressed an issue where the replied message was removed when selecting all text in the message and clicking backspace.
* Fixed an issue where Chinese characters could not be entered while replying to a message.
* Resolved some console errors during debugging.
* Fixed an issue with links not opening in markdown mode.
* Fixed an issue that caused two `Scrollbar`s to appear in the message input field on Desktop.
* Solved an issue that might cause incorrect layout when the app is launched.
* Addressed an issue where messages were directly sent when the Enter key was pressed while entering Chinese text.
* Fixed related issues with the mentioned member selection panel on Desktop.
* Resolved an issue where images couldn't be pasted directly into the message input area for sending on the Web.
* Fixed an issue where files couldn't be sent on the Web.
* Remedied an issue where media and files couldn't be opened when local downloaded resources were deleted; now, resources will automatically re-download.
* Fixed an issue that caused the `iconImageAsset` of the `MessageToolTipItem` config to head internally to this chat UIKit.
* Improved the downloading process of media and files by avoiding frequent calls to `setState`, thus preventing the entire project from re-rendering.
## 2.0.0 ## 2.0.0
If you are upgrading from version 1.7.0, please refer to the changelog of all 2.0.0-preview versions, ranging from preview.1 to preview.7. If you are upgrading from version 1.7.0, please refer to the changelog of all 2.0.0-preview versions, ranging from preview.1 to preview.7.

View File

@ -74,19 +74,6 @@ Official Documentation</button></a>
<br> <br>
## Preview Version Release Notes
This version is a major update with version number 2.0.0-preview series, which is not backward
compatible. Tencent Cloud Chat UIKit has been extended from mobile-only (iOS/Android/mobile web) to
support all platforms, including iOS/Android/Web/Windows/macOS, resulting in significant changes to
the codebase.
Therefore, users should evaluate the compatibility complexity of their business logic before
upgrading, while new users can use this version without any impact.
The documentation for the new version is still being improved, and users can refer to the sample app
source code at at https://github.com/TencentCloud/chat-demo-flutter.
## Check Out Our Sample Apps ## Check Out Our Sample Apps
Experience our Chat and Voice/Video Call modules by trying out our sample apps. Experience our Chat and Voice/Video Call modules by trying out our sample apps.

View File

@ -1,73 +0,0 @@
// ignore_for_file: file_names
import 'dart:convert';
import 'package:crypto/crypto.dart';
import 'package:archive/archive.dart';
import 'package:archive/archive_io.dart';
/// userSig
/// Generate userSig for Tencent Cloud instant messaging test
///
class GenerateTestUserSig {
GenerateTestUserSig({required this.sdkappid, required this.key});
int sdkappid;
String key;
/// UserSig
/// Generate UserSig
///
String genSig({
required String identifier,
required int expire,
}) {
int currTime = _getCurrentTime();
String sig = '';
Map<String, dynamic> sigDoc = <String, dynamic>{};
sigDoc.addAll({
"TLS.ver": "2.0",
"TLS.identifier": identifier,
// ignore: unnecessary_this
"TLS.sdkappid": this.sdkappid,
"TLS.expire": expire,
"TLS.time": currTime,
});
sig = _hmacsha256(
identifier: identifier,
currTime: currTime,
expire: expire,
);
sigDoc['TLS.sig'] = sig;
String jsonStr = json.encode(sigDoc);
List<int>? compress = const ZLibEncoder().encode(utf8.encode(jsonStr));
return _escape(content: base64.encode(compress));
}
int _getCurrentTime() {
return (DateTime.now().millisecondsSinceEpoch / 1000).floor();
}
String _hmacsha256({
required String identifier,
required int currTime,
int expire = 30 * 24 * 60 * 60,
}) {
int sdkappid = this.sdkappid;
String contentToBeSigned =
"TLS.identifier:$identifier\nTLS.sdkappid:$sdkappid\nTLS.time:$currTime\nTLS.expire:$expire\n";
Hmac hmacSha256 = Hmac(sha256, utf8.encode(key));
Digest hmacSha256Digest =
hmacSha256.convert(utf8.encode(contentToBeSigned));
return base64.encode(hmacSha256Digest.bytes);
}
String _escape({
required String content,
}) {
return content
.replaceAll('+', '*')
.replaceAll('/', '-')
.replaceAll('=', '_');
}
}

View File

@ -1,9 +1,5 @@
// ignore_for_file: avoid_print // ignore_for_file: avoid_print
import 'package:example/GenerateUserSig.dart';
import 'package:example/TIMUIKitChatExample.dart';
import 'package:example/TIMUIKitConversationExample.dart';
import 'package:example/TIMUIKitProfileExample.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart'; import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
import 'TIMUIKitAddFriendExample.dart'; import 'TIMUIKitAddFriendExample.dart';
@ -74,12 +70,15 @@ class _MyHomePageState extends State<MyHomePage> {
return const String.fromEnvironment('SECRET', defaultValue: ""); return const String.fromEnvironment('SECRET', defaultValue: "");
} }
String getUsersig() {
return const String.fromEnvironment('USERSIG', defaultValue: "");
}
initTIMUIKIT() async { initTIMUIKIT() async {
int sdkappid = getSDKAPPID(); int sdkappid = getSDKAPPID();
String userid = getUserID(); String userid = getUserID();
String secret = getSecret(); String secret = getSecret();
String usersig = GenerateTestUserSig(sdkappid: sdkappid, key: secret) String usersig = getUsersig();
.genSig(identifier: userid, expire: 24 * 7 * 60 * 60 * 1000);
if (sdkappid == 0 || userid == '' || secret == '' || usersig == '') { if (sdkappid == 0 || userid == '' || secret == '' || usersig == '') {
print("The running parameters are abnormal, please check"); print("The running parameters are abnormal, please check");
return; return;

View File

@ -7,16 +7,15 @@ import Foundation
import audioplayers_darwin import audioplayers_darwin
import desktop_drop import desktop_drop
import device_info_plus_macos import device_info_plus
import fc_native_video_thumbnail_for_us import fc_native_video_thumbnail_for_us
import package_info_plus_macos import package_info_plus
import pasteboard import pasteboard
import path_provider_foundation import path_provider_foundation
import photo_manager import photo_manager
import shared_preferences_foundation import shared_preferences_foundation
import sqflite import sqflite
import url_launcher_macos import url_launcher_macos
import wakelock_macos
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin")) AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin"))
@ -30,5 +29,4 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
WakelockMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockMacosPlugin"))
} }

View File

@ -45,10 +45,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: async name: async
sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0 sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.10.0" version: "2.11.0"
audioplayers: audioplayers:
dependency: transitive dependency: transitive
description: description:
@ -197,10 +197,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: characters name: characters
sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.1" version: "1.3.0"
charcode: charcode:
dependency: transitive dependency: transitive
description: description:
@ -209,14 +209,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.1" version: "1.3.1"
chewie: chewie_for_us:
dependency: transitive dependency: transitive
description: description:
name: chewie name: chewie_for_us
sha256: e9da4898ee4859825404f507969f57113c04ca0060e152b95c9afd73934126ad sha256: "0307723e811508d361fffa6f8bbd9040b1bfea5536544e4d655e10c27de002ec"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.4.0" version: "1.5.0"
clock: clock:
dependency: transitive dependency: transitive
description: description:
@ -229,10 +229,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: collection name: collection
sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.17.0" version: "1.17.1"
convert: convert:
dependency: transitive dependency: transitive
description: description:
@ -293,50 +293,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: device_info_plus name: device_info_plus
sha256: b809c4ed5f7fcdb325ccc70b80ad934677dc4e2aa414bf46859a42bfdfafcbb6 sha256: "2c35b6d1682b028e42d07b3aee4b98fa62996c10bc12cb651ec856a80d6a761b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.1.3" version: "9.0.2"
device_info_plus_linux:
dependency: transitive
description:
name: device_info_plus_linux
sha256: "77a8b3c4af06bc46507f89304d9f49dfc64b4ae004b994532ed23b34adeae4b3"
url: "https://pub.dev"
source: hosted
version: "3.0.0"
device_info_plus_macos:
dependency: transitive
description:
name: device_info_plus_macos
sha256: "37961762fbd46d3620c7b69ca606671014db55fc1b7a11e696fd90ed2e8fe03d"
url: "https://pub.dev"
source: hosted
version: "3.0.0"
device_info_plus_platform_interface: device_info_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: device_info_plus_platform_interface name: device_info_plus_platform_interface
sha256: "83fdba24fcf6846d3b10f10dfdc8b6c6d7ada5f8ed21d62ea2909c2dfa043773" sha256: d3b01d5868b50ae571cd1dc6e502fc94d956b665756180f7b16ead09e836fd64
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.0" version: "7.0.0"
device_info_plus_web:
dependency: transitive
description:
name: device_info_plus_web
sha256: "5890f6094df108181c7a29720bc23d0fd6159f17d82787fac093d1fefcaf6325"
url: "https://pub.dev"
source: hosted
version: "3.0.0"
device_info_plus_windows:
dependency: transitive
description:
name: device_info_plus_windows
sha256: "23a2874af0e23ee6e3a2a0ebcecec3a9da13241f2cb93a93a44c8764df123dd7"
url: "https://pub.dev"
source: hosted
version: "4.1.0"
diff_match_patch: diff_match_patch:
dependency: transitive dependency: transitive
description: description:
@ -365,10 +333,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: extended_image name: extended_image
sha256: "5854d0d05ee0c687d1852af9db05f15cfe058520fa56f417075705c5bce965d4" sha256: e77d18f956649ba6e5ecebd0cb68542120886336a75ee673788145bd4c3f0767
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.4.0" version: "8.0.2"
extended_image_library: extended_image_library:
dependency: transitive dependency: transitive
description: description:
@ -377,6 +345,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.4.1" version: "3.4.1"
extended_text:
dependency: transitive
description:
name: extended_text
sha256: "75ddf28ce7d5be33a050ff2179b6567b4b98e6225ad3e61e4c3748f7448c25f7"
url: "https://pub.dev"
source: hosted
version: "11.0.0"
extended_text_field:
dependency: transitive
description:
name: extended_text_field
sha256: "6cf8c090de4dc1e309cf3b24cb9448d7463c6c17926b628cf0954631bf4e56db"
url: "https://pub.dev"
source: hosted
version: "12.0.0"
extended_text_library:
dependency: transitive
description:
name: extended_text_library
sha256: "308b50cfcc8e3accf46a09cb692715fbd1097333817c15b0f7527de1766bc1ff"
url: "https://pub.dev"
source: hosted
version: "11.0.1"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:
@ -421,10 +413,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: file_picker name: file_picker
sha256: b85eb92b175767fdaa0c543bf3b0d1f610fe966412ea72845fe5ba7801e763ff sha256: "9d6e95ec73abbd31ec54d0e0df8a961017e165aba1395e462e5b31ea0c165daf"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.2.10" version: "5.3.1"
file_utils: file_utils:
dependency: transitive dependency: transitive
description: description:
@ -490,10 +482,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: flutter_plugin_android_lifecycle name: flutter_plugin_android_lifecycle
sha256: "60fc7b78455b94e6de2333d2f95196d32cf5c22f4b0b0520a628804cb463503b" sha256: "950e77c2bbe1692bc0874fc7fb491b96a4dc340457f4ea1641443d0a6c1ea360"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.7" version: "2.0.15"
flutter_plugin_record_plus: flutter_plugin_record_plus:
dependency: transitive dependency: transitive
description: description:
@ -636,18 +628,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: intl name: intl
sha256: "910f85bce16fb5c6f614e117efa303e85a1731bb0081edf3604a2ae6e9a3cc91" sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.17.0" version: "0.18.1"
js: js:
dependency: transitive dependency: transitive
description: description:
name: js name: js
sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.6.5" version: "0.6.7"
json_annotation: json_annotation:
dependency: transitive dependency: transitive
description: description:
@ -708,10 +700,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.12.13" version: "0.12.15"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
@ -724,10 +716,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.8.0" version: "1.9.1"
mime_type: mime_type:
dependency: transitive dependency: transitive
description: description:
@ -764,50 +756,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: package_info_plus name: package_info_plus
sha256: f62d7253edc197fe3c88d7c2ddab82d68f555e778d55390ccc3537eca8e8d637 sha256: ceb027f6bc6a60674a233b4a90a7658af1aebdea833da0b5b53c1e9821a78c7b
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.4.3+1" version: "4.0.2"
package_info_plus_linux:
dependency: transitive
description:
name: package_info_plus_linux
sha256: "04b575f44233d30edbb80a94e57cad9107aada334fc02aabb42b6becd13c43fc"
url: "https://pub.dev"
source: hosted
version: "1.0.5"
package_info_plus_macos:
dependency: transitive
description:
name: package_info_plus_macos
sha256: a2ad8b4acf4cd479d4a0afa5a74ea3f5b1c7563b77e52cc32b3ee6956d5482a6
url: "https://pub.dev"
source: hosted
version: "1.3.0"
package_info_plus_platform_interface: package_info_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: package_info_plus_platform_interface name: package_info_plus_platform_interface
sha256: f7a0c8f1e7e981bc65f8b64137a53fd3c195b18d429fba960babc59a5a1c7ae8 sha256: "9bc8ba46813a4cc42c66ab781470711781940780fd8beddd0c3da62506d3a6c6"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.2" version: "2.0.1"
package_info_plus_web:
dependency: transitive
description:
name: package_info_plus_web
sha256: f0829327eb534789e0a16ccac8936a80beed4e2401c4d3a74f3f39094a822d3b
url: "https://pub.dev"
source: hosted
version: "1.0.6"
package_info_plus_windows:
dependency: transitive
description:
name: package_info_plus_windows
sha256: "79524f11c42dd9078b96d797b3cf79c0a2883a50c4920dc43da8562c115089bc"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
pasteboard: pasteboard:
dependency: transitive dependency: transitive
description: description:
@ -820,10 +780,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: path name: path
sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.8.2" version: "1.8.3"
path_drawing: path_drawing:
dependency: transitive dependency: transitive
description: description:
@ -948,10 +908,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: photo_manager name: photo_manager
sha256: "55d50ad1b8f984c57fa7c4bd4980f4760e80d3d9355263cf72624a6ff1bf2b5b" sha256: bdc4ab1fa9fb064d8ccfea6ab44119f55b220293d7ce2e19eb5a5f998db86c88
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.5.2" version: "2.6.0"
platform: platform:
dependency: transitive dependency: transitive
description: description:
@ -964,10 +924,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: plugin_platform_interface name: plugin_platform_interface
sha256: dbf0f707c78beedc9200146ad3cb0ab4d5da13c246336987be6940f026500d3a sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.3" version: "2.1.4"
pointycastle: pointycastle:
dependency: transitive dependency: transitive
description: description:
@ -1177,73 +1137,49 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: tencent_cloud_chat_sdk name: tencent_cloud_chat_sdk
sha256: "765a93262a41080e155ce5b8a6ca20147a81c7d306f7f87444077c5eaae87e08" sha256: f98bdb55164051e2b196cac6e2e79e60248ed8351dc5a91d25568712ccb15839
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.1.5" version: "5.1.7"
tencent_cloud_chat_uikit: tencent_cloud_chat_uikit:
dependency: "direct main" dependency: "direct main"
description: description:
path: ".." path: ".."
relative: true relative: true
source: path source: path
version: "2.0.0+1" version: "2.1.0"
tencent_cloud_uikit_core: tencent_cloud_uikit_core:
dependency: transitive dependency: transitive
description: description:
name: tencent_cloud_uikit_core name: tencent_cloud_uikit_core
sha256: "829dfde0c4fbdae019ba233f7f2c299e7cbd18c3ae20ecfe3ab4a43084a33064" sha256: "517d760b0d497ea291d70fe6a021508e5b66f0754c72679f19ee16c0bb354e91"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.2" version: "1.0.3"
tencent_extended_text:
dependency: transitive
description:
name: tencent_extended_text
sha256: "27a2f7ee58ada480e295102471f1733a7402178a239d0c80a7aa33a134c641ef"
url: "https://pub.dev"
source: hosted
version: "1.0.2"
tencent_extended_text_field:
dependency: transitive
description:
name: tencent_extended_text_field
sha256: d311c240983dbf78e31b58f91e425920a40d6564942813e692a3419bf5c9deb0
url: "https://pub.dev"
source: hosted
version: "1.0.1"
tencent_extended_text_library:
dependency: transitive
description:
name: tencent_extended_text_library
sha256: d6dad4e4e426e6319db809267f160082c44a334716e9f8593fac56d65ae75545
url: "https://pub.dev"
source: hosted
version: "1.0.0"
tencent_im_base: tencent_im_base:
dependency: transitive dependency: transitive
description: description:
name: tencent_im_base name: tencent_im_base
sha256: "516356a80f43b94a6c0719b54e4c641cb1f164830b2b3e887d175ae862ebab3f" sha256: "9b8e712bf27ffae9b686ec532ee8417b8263eba8bab04f105e28a95de1807322"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.51" version: "1.0.57"
tencent_im_sdk_plugin_desktop: tencent_im_sdk_plugin_desktop:
dependency: "direct main" dependency: "direct main"
description: description:
name: tencent_im_sdk_plugin_desktop name: tencent_im_sdk_plugin_desktop
sha256: "5fe5ab0765183185fe4a1f94ce1fdc6ab0a450b8522011806549678edb52130d" sha256: "8d986f2f6aedeac8d771286e31b7bbb9bbee12192461fc879c857be903a41a7f"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.1.13" version: "0.1.19"
tencent_im_sdk_plugin_platform_interface: tencent_im_sdk_plugin_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: tencent_im_sdk_plugin_platform_interface name: tencent_im_sdk_plugin_platform_interface
sha256: "04043582f1af698b4abe12d53cd0f043466228fae712677688988d8ff7bfc1f1" sha256: "53263e4acd7179871aad2a67ec4964bc8fae861f54384fba2c60bd2c16d2867c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.3.19" version: "0.3.20"
tencent_im_sdk_plugin_web: tencent_im_sdk_plugin_web:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1276,14 +1212,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.0.1" version: "0.0.1"
tencent_wechat_camera_picker:
dependency: transitive
description:
name: tencent_wechat_camera_picker
sha256: "8f95b435c7a12a9221f00fe4354fb9c0f9313d79cc09ddb5902b7b418185092d"
url: "https://pub.dev"
source: hosted
version: "3.6.5+1"
term_glyph: term_glyph:
dependency: transitive dependency: transitive
description: description:
@ -1296,10 +1224,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.4.16" version: "0.5.1"
tim_ui_kit_sticker_plugin: tim_ui_kit_sticker_plugin:
dependency: transitive dependency: transitive
description: description:
@ -1432,10 +1360,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: video_player name: video_player
sha256: "59f7f31c919c59cbedd37c617317045f5f650dc0eeb568b0b0de9a36472bdb28" sha256: "868a139229acb5018d22aded3eb9cb4767ff43a8216573c086b6c535a4957481"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.5.1" version: "2.6.0"
video_player_android: video_player_android:
dependency: transitive dependency: transitive
description: description:
@ -1468,22 +1396,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.13" version: "2.0.13"
wakelock: wakelock_for_us:
dependency: transitive dependency: transitive
description: description:
name: wakelock name: wakelock_for_us
sha256: "769ecf42eb2d07128407b50cb93d7c10bd2ee48f0276ef0119db1d25cc2f87db" sha256: b73bfa90e5e764f41155063ae92fbd1e3a04ee6372e65ff7d288d2c3057f9498
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.6.2" version: "0.6.3"
wakelock_macos:
dependency: transitive
description:
name: wakelock_macos
sha256: "047c6be2f88cb6b76d02553bca5a3a3b95323b15d30867eca53a19a0a319d4cd"
url: "https://pub.dev"
source: hosted
version: "0.4.0"
wakelock_platform_interface: wakelock_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -1492,22 +1412,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.3.0" version: "0.3.0"
wakelock_web:
dependency: transitive
description:
name: wakelock_web
sha256: "1b256b811ee3f0834888efddfe03da8d18d0819317f20f6193e2922b41a501b5"
url: "https://pub.dev"
source: hosted
version: "0.4.0"
wakelock_windows:
dependency: transitive
description:
name: wakelock_windows
sha256: "857f77b3fe6ae82dd045455baa626bc4b93cb9bb6c86bf3f27c182167c3a5567"
url: "https://pub.dev"
source: hosted
version: "0.2.1"
watcher: watcher:
dependency: transitive dependency: transitive
description: description:
@ -1520,18 +1424,34 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: wechat_assets_picker name: wechat_assets_picker
sha256: "49184fbc83f855bade59961566a6323a2015634ece1f889de5af6fa133a10706" sha256: "5aeac81c6a28e1142a2c9ba9ee802b909c2dad9186d9a58dbe4eb74493af4743"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.3.4" version: "8.5.0"
wechat_camera_picker:
dependency: transitive
description:
name: wechat_camera_picker
sha256: d8108ea33b1ed25933770199d08d64c210a2854bc6a326fd058a2f34dca8bf46
url: "https://pub.dev"
source: hosted
version: "3.8.0"
win32: win32:
dependency: transitive dependency: transitive
description: description:
name: win32 name: win32
sha256: a6f0236dbda0f63aa9a25ad1ff9a9d8a4eaaa5012da0dc59d21afdb1dc361ca4 sha256: "5a751eddf9db89b3e5f9d50c20ab8612296e4e8db69009788d6c8b060a84191c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.4" version: "4.1.4"
win32_registry:
dependency: transitive
description:
name: win32_registry
sha256: "1c52f994bdccb77103a6231ad4ea331a244dbcef5d1f37d8462f713143b0bfae"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:
@ -1557,5 +1477,5 @@ packages:
source: hosted source: hosted
version: "3.1.1" version: "3.1.1"
sdks: sdks:
dart: ">=2.19.0 <3.0.0" dart: ">=3.0.0 <4.0.0"
flutter: ">=3.7.0" flutter: ">=3.10.0"

View File

@ -0,0 +1 @@
<svg t="1684915863093" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="13769" width="200" height="200"><path d="M872.802928 755.99406 872.864326 755.99406 872.864326 755.624646Z" fill="#232832" p-id="13770"></path><path d="M641.612475 478.465233 378.562407 478.465233c-18.447141 0-33.352619 13.225209-33.352619 29.515221 0 16.305361 14.905478 29.544897 33.352619 29.544897l263.050068 0c18.417465 0 33.383318-13.239536 33.383318-29.544897C674.995793 491.690442 660.088268 478.465233 641.612475 478.465233" fill="#232832" p-id="13771"></path><path d="M512.006651 94.661963c-228.163513 0-413.794327 158.926616-413.794327 354.272699 0 99.213629 49.568952 192.906521 136.968511 260.892939l101.667517 78.787414c-3.004427 16.778128-15.917528 56.111972-30.527271 92.115977-4.582365 11.159154-1.845021 23.979134 6.78452 32.340569 5.623068 5.445012 13.06148 8.30106 20.588921 8.30106 4.046153 0 8.123005-0.831948 12.019755-2.527566 33.6811-14.997576 115.680673-55.042617 173.285648-115.920126 224.933959-3.243881 406.785703-160.637584 406.785703-353.989243C925.785629 253.588579 740.185514 94.661963 512.006651 94.661963M510.431783 744.875838c-1.847068 0-1.847068 0.029676-3.571339 0.326435-12.734022 1.457188-28.682249 18.655895-28.682249 18.655895s-52.634778 52.692083-96.758718 77.745688c16.096607-53.705156 13.269211-73.488735 8.123005-81.138972-1.279133-1.992377-2.707668-3.508917-4.432962-4.670369l0.118704-0.058328c0 0-0.416486-0.208754-1.130753-0.595564-0.38681-0.237407-0.773619-0.416486-1.190105-0.655939-13.449313-7.198959-83.338057-46.055942-140.16839-100.506065-55.101969-57.871035-85.421508-135.467321-85.421508-205.04498 0-162.780387 159.121044-295.226908 354.689185-295.226908 195.567117 0 354.688161 132.447545 354.688161 295.226908C866.694813 611.728352 705.998901 744.875838 510.431783 744.875838" fill="#232832" p-id="13772"></path><path d="M645.450896 360.374674l-266.88849 0c-18.447141 0-33.383318 13.224186-33.383318 29.515221 0 16.304338 14.936177 29.529547 33.383318 29.529547l266.88849 0c18.47784 0 33.352619-13.225209 33.352619-29.529547C678.803515 373.59886 663.928736 360.374674 645.450896 360.374674" fill="#232832" p-id="13773"></path></svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

7
images/svg/send_code.svg Normal file
View File

@ -0,0 +1,7 @@
<svg width="16" height="14" viewBox="0 0 16 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="&#231;&#188;&#150;&#231;&#187;&#132; 9&#229;&#164;&#135;&#228;&#187;&#189; 3" opacity="0.795169">
<rect id="&#231;&#159;&#169;&#229;&#189;&#162;" x="0.5" y="0.5" width="15" height="13" rx="1.75" stroke="#232832"/>
<rect id="&#231;&#159;&#169;&#229;&#189;&#162;_2" x="6.25" y="10.25" width="3.5" height="0.5" rx="0.25" stroke="#232832" stroke-width="0.5"/>
<path id="&#232;&#183;&#175;&#229;&#190;&#132; 8" d="M10.5 3.5L3.59271 10.5222" stroke="#232832" stroke-linecap="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 593 B

5
images/svg/send_face.svg Normal file
View File

@ -0,0 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="8" cy="8" r="7.5" stroke="#232832"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M5 5C5.55228 5 6 5.44772 6 6C6 6.55228 5.55228 7 5 7C4.44772 7 4 6.55228 4 6C4 5.44772 4.44772 5 5 5Z" fill="#232832"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M11 5C11.5523 5 12 5.44772 12 6C12 6.55228 11.5523 7 11 7C10.4477 7 10 6.55228 10 6C10 5.44772 10.4477 5 11 5Z" fill="#232832"/>
</svg>

After

Width:  |  Height:  |  Size: 499 B

6
images/svg/send_file.svg Normal file
View File

@ -0,0 +1,6 @@
<svg width="16" height="14" viewBox="0 0 16 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="&#231;&#188;&#150;&#231;&#187;&#132; 9&#229;&#164;&#135;&#228;&#187;&#189; 2" opacity="0.795169">
<path id="&#231;&#159;&#169;&#229;&#189;&#162;" d="M0.5 2.25C0.5 1.2835 1.2835 0.5 2.25 0.5H6.54211C6.62301 0.5 6.70271 0.519631 6.77435 0.557208L8.63621 1.53374C8.85115 1.64647 9.09023 1.70536 9.33293 1.70536H13.75C14.7165 1.70536 15.5 2.48886 15.5 3.45536V11.75C15.5 12.7165 14.7165 13.5 13.75 13.5H2.25C1.2835 13.5 0.5 12.7165 0.5 11.75V2.25Z" stroke="#232832"/>
<rect id="&#231;&#159;&#169;&#229;&#189;&#162;_2" x="0.5" y="3.5" width="15" height="10" rx="1.75" fill="white" stroke="#232832"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 710 B

View File

@ -0,0 +1,7 @@
<svg width="16" height="14" viewBox="0 0 16 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="&#231;&#188;&#150;&#231;&#187;&#132; 9" opacity="0.795169">
<circle id="&#230;&#164;&#173;&#229;&#156;&#134;&#229;&#189;&#162;" cx="5.5" cy="4.5" r="1" stroke="#232832"/>
<rect id="&#231;&#159;&#169;&#229;&#189;&#162;" x="0.5" y="0.5" width="15" height="13" rx="1.75" stroke="#232832"/>
<path id="&#232;&#183;&#175;&#229;&#190;&#132; 2" d="M3 11L5.42403 8.9433C5.50934 8.87092 5.63487 8.87221 5.71867 8.94633L7.88124 10.8592C7.97058 10.9382 8.10606 10.9338 8.19006 10.8491L13 6" stroke="#232832" stroke-linecap="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 636 B

View File

@ -0,0 +1,4 @@
<svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="200" height="200">]
<path d="M779.7 588.6c-23.2 0-45.3 4.4-65.7 12.5L282.9 108.3c-10.2-11.6-27.9-12.8-39.5-2.6-11.6 10.2-12.8 27.9-2.6 39.5l424.4 485.1c-39.3 32.8-64.3 82.2-64.3 137.2 0 98.6 80.2 178.8 178.8 178.8 98.6 0 178.8-80.2 178.8-178.8s-80.2-178.9-178.8-178.9z m0 301.7c-67.7 0-122.8-55.1-122.8-122.8 0-67.7 55.1-122.8 122.8-122.8 67.7 0 122.8 55.1 122.8 122.8 0 67.7-55.1 122.8-122.8 122.8z" fill="#232832" p-id="6499"></path>
<path d="M779.7 105.6c-11.6-10.2-29.3-9-39.5 2.6L309 601.1c-20.3-8.1-42.5-12.5-65.7-12.5-98.6 0-178.8 80.2-178.8 178.8s80.2 178.8 178.8 178.8c98.6 0 178.8-80.2 178.8-178.8 0-55.1-25-104.4-64.3-137.2l424.4-485.1c10.3-11.6 9.1-29.3-2.5-39.5zM243.4 890.3c-67.7 0-122.8-55.1-122.8-122.8 0-67.7 55.1-122.8 122.8-122.8 67.7 0 122.8 55.1 122.8 122.8 0 67.7-55.1 122.8-122.8 122.8z" fill="#232832" p-id="6500"></path>
</svg>

After

Width:  |  Height:  |  Size: 950 B

View File

@ -0,0 +1,6 @@
<svg width="16" height="14" viewBox="0 0 16 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="&#231;&#188;&#150;&#231;&#187;&#132; 9&#229;&#164;&#135;&#228;&#187;&#189;" opacity="0.795169">
<rect id="&#231;&#159;&#169;&#229;&#189;&#162;" x="0.5" y="0.5" width="15" height="13" rx="1.75" stroke="#232832"/>
<path id="&#228;&#184;&#137;&#232;&#167;&#146;&#229;&#189;&#162;" d="M10.0282 7L6.5 9.1169L6.5 4.8831L10.0282 7Z" stroke="#232832"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 460 B

View File

@ -7,6 +7,7 @@ import 'package:flutter/cupertino.dart';
// ignore: unnecessary_import // ignore: unnecessary_import
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_self_info_view_model.dart';
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart'; import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
import 'package:flutter_image_compress/flutter_image_compress.dart'; import 'package:flutter_image_compress/flutter_image_compress.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
@ -30,6 +31,7 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
final GroupServices _groupServices = serviceLocator<GroupServices>(); final GroupServices _groupServices = serviceLocator<GroupServices>();
final TUIChatGlobalModel globalModel = serviceLocator<TUIChatGlobalModel>(); final TUIChatGlobalModel globalModel = serviceLocator<TUIChatGlobalModel>();
final TUIChatModelTools tools = serviceLocator<TUIChatModelTools>(); final TUIChatModelTools tools = serviceLocator<TUIChatModelTools>();
final TUISelfInfoViewModel selfModel = serviceLocator<TUISelfInfoViewModel>();
final _uuid = const Uuid(); final _uuid = const Uuid();
ChatLifeCycle? lifeCycle; ChatLifeCycle? lifeCycle;
@ -57,6 +59,7 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
V2TimGroupInfo? _groupInfo; V2TimGroupInfo? _groupInfo;
String groupMemberListSeq = "0"; String groupMemberListSeq = "0";
List<V2TimGroupMemberFullInfo?>? groupMemberList = []; List<V2TimGroupMemberFullInfo?>? groupMemberList = [];
V2TimGroupMemberFullInfo? selfMemberInfo;
double atPositionX = 0.0; double atPositionX = 0.0;
double atPositionY = 0.0; double atPositionY = 0.0;
int _activeAtIndex = -1; int _activeAtIndex = -1;
@ -191,6 +194,7 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
isGroupExist = true; isGroupExist = true;
_groupInfo = null; _groupInfo = null;
groupMemberList?.clear(); groupMemberList?.clear();
selfMemberInfo = null;
notifyListeners(); notifyListeners();
} }
if (conversationType == ConvType.c2c) { if (conversationType == ConvType.c2c) {
@ -216,7 +220,6 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
} }
} }
} }
markMessageAsRead();
globalModel.lifeCycle = lifeCycle; globalModel.lifeCycle = lifeCycle;
globalModel.setCurrentConversation( globalModel.setCurrentConversation(
CurrentConversation(conversationID, conversationType ?? ConvType.c2c)); CurrentConversation(conversationID, conversationType ?? ConvType.c2c));
@ -225,6 +228,9 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
globalModel.setChatConfig(chatConfig); globalModel.setChatConfig(chatConfig);
globalModel.clearRecivedNewMessageCount(); globalModel.clearRecivedNewMessageCount();
_isInit = true; _isInit = true;
Future.delayed(const Duration(milliseconds: 300), (){
markMessageAsRead();
});
} }
Future<bool> loadListForSpecificMessage({ Future<bool> loadListForSpecificMessage({
@ -504,6 +510,8 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
return await loadGroupMemberList( return await loadGroupMemberList(
groupID: groupID, count: count, seq: nextSeq); groupID: groupID, count: count, seq: nextSeq);
} else { } else {
selfMemberInfo = groupMemberList
?.firstWhere((e) => e?.userID == selfModel.loginInfo?.userID);
notifyListeners(); notifyListeners();
} }
} }
@ -575,6 +583,11 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
V2TimMessage? messageInfo, V2TimMessage? messageInfo,
OfflinePushInfo? offlinePushInfo, OfflinePushInfo? offlinePushInfo,
bool? onlineUserOnly = false, bool? onlineUserOnly = false,
MessagePriorityEnum priority = MessagePriorityEnum.V2TIM_PRIORITY_NORMAL,
bool? isExcludedFromUnreadCount,
bool? needReadReceipt,
String? cloudCustomData,
String? localCustomData,
bool? isEditStatusMessage = false, bool? isEditStatusMessage = false,
}) async { }) async {
String receiver = convType == ConvType.c2c ? convID : ''; String receiver = convType == ConvType.c2c ? convID : '';
@ -586,27 +599,32 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
setLoadingMessageMap(convID, messageInfo); setLoadingMessageMap(convID, messageInfo);
} }
final sendMsgRes = await _messageService.sendMessage( final sendMsgRes = await _messageService.sendMessage(
priority: priority,
localCustomData: localCustomData,
isExcludedFromUnreadCount: isExcludedFromUnreadCount ?? false,
id: id, id: id,
receiver: receiver, receiver: receiver,
needReadReceipt: chatConfig.isShowGroupReadingStatus && needReadReceipt: needReadReceipt ??
convType == ConvType.group && chatConfig.isShowGroupReadingStatus &&
((chatConfig.groupReadReceiptPermissionList != null && convType == ConvType.group &&
chatConfig.groupReadReceiptPermissionList! ((chatConfig.groupReadReceiptPermissionList != null &&
.contains(_groupType)) || chatConfig.groupReadReceiptPermissionList!
(chatConfig.groupReadReceiptPermisionList != null && .contains(_groupType)) ||
chatConfig.groupReadReceiptPermisionList! (chatConfig.groupReadReceiptPermisionList != null &&
.contains(oldGroupType))), chatConfig.groupReadReceiptPermisionList!
.contains(oldGroupType))),
groupID: groupID, groupID: groupID,
offlinePushInfo: offlinePushInfo, offlinePushInfo: offlinePushInfo,
onlineUserOnly: onlineUserOnly ?? false, onlineUserOnly: onlineUserOnly ?? false,
cloudCustomData: showC2cMessageEditStatus == true cloudCustomData: cloudCustomData ??
? json.encode({ (showC2cMessageEditStatus == true
"messageFeature": { ? json.encode({
"needTyping": 1, "messageFeature": {
"version": 1, "needTyping": 1,
} "version": 1,
}) }
: "", })
: ""),
); );
if (isEditStatusMessage == false && if (isEditStatusMessage == false &&
globalModel.getMessageListPosition(conversationID) != globalModel.getMessageListPosition(conversationID) !=
@ -916,6 +934,7 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
Future<V2TimValueCallback<V2TimMessage>?> sendImageMessage( Future<V2TimValueCallback<V2TimMessage>?> sendImageMessage(
{String? imagePath, {String? imagePath,
String? imageName,
required String convID, required String convID,
dynamic inputElement, dynamic inputElement,
required ConvType convType}) async { required ConvType convType}) async {
@ -939,7 +958,9 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
} catch (e) {} } catch (e) {}
} }
final imageMessageInfo = await _messageService.createImageMessage( final imageMessageInfo = await _messageService.createImageMessage(
imagePath: image ?? imagePath, inputElement: inputElement); imageName: imageName,
imagePath: image ?? imagePath,
inputElement: inputElement);
List<V2TimMessage> currentHistoryMsgList = getOriginMessageList(); List<V2TimMessage> currentHistoryMsgList = getOriginMessageList();
final messageInfo = imageMessageInfo!.messageInfo; final messageInfo = imageMessageInfo!.messageInfo;
if (messageInfo != null) { if (messageInfo != null) {
@ -1334,6 +1355,12 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
/// Offline push info /// Offline push info
OfflinePushInfo? offlinePushInfo, OfflinePushInfo? offlinePushInfo,
MessagePriorityEnum priority = MessagePriorityEnum.V2TIM_PRIORITY_NORMAL,
bool? onlineUserOnly,
bool? isExcludedFromUnreadCount,
bool? needReadReceipt,
String? cloudCustomData,
String? localCustomData,
}) { }) {
List<V2TimMessage> currentHistoryMsgList = getOriginMessageList(); List<V2TimMessage> currentHistoryMsgList = getOriginMessageList();
if (messageInfo != null) { if (messageInfo != null) {
@ -1351,6 +1378,12 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
} }
return _sendMessage( return _sendMessage(
priority: priority,
onlineUserOnly: onlineUserOnly,
isExcludedFromUnreadCount: isExcludedFromUnreadCount,
needReadReceipt: needReadReceipt,
cloudCustomData: cloudCustomData,
localCustomData: localCustomData,
convID: conversationID, convID: conversationID,
id: messageInfo.id as String, id: messageInfo.id as String,
convType: conversationType ?? ConvType.c2c, convType: conversationType ?? ConvType.c2c,

View File

@ -32,7 +32,7 @@ class CurrentConversation {
CurrentConversation(this.conversationID, this.conversationType); CurrentConversation(this.conversationID, this.conversationType);
} }
class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass { class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
final MessageService _messageService = serviceLocator<MessageService>(); final MessageService _messageService = serviceLocator<MessageService>();
final GroupServices _groupServices = serviceLocator<GroupServices>(); final GroupServices _groupServices = serviceLocator<GroupServices>();
final Map<String, List<V2TimMessage>?> _messageListMap = {}; final Map<String, List<V2TimMessage>?> _messageListMap = {};
@ -56,8 +56,8 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
late V2TimAdvancedMsgListener advancedMsgListener; late V2TimAdvancedMsgListener advancedMsgListener;
int _unreadCountForConversation = 0; int _unreadCountForConversation = 0;
// use for generate a new sliver list to show recived messag list // use for generate a new sliver list to show received message list
int _recivedNewMessageCount = 0; int _receivedNewMessageCount = 0;
TIMUIKitChatConfig chatConfig = const TIMUIKitChatConfig(); TIMUIKitChatConfig chatConfig = const TIMUIKitChatConfig();
List<V2TimGroupApplication>? _groupApplicationList; List<V2TimGroupApplication>? _groupApplicationList;
String Function(V2TimMessage message)? _abstractMessageBuilder; String Function(V2TimMessage message)? _abstractMessageBuilder;
@ -65,7 +65,7 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
Map.from({}); // 0 normal 1 sending Map.from({}); // 0 normal 1 sending
final Map<String, bool> _c2cMessageFromUserActiveMap = Map.from({}); final Map<String, bool> _c2cMessageFromUserActiveMap = Map.from({});
final Map<String, Timer> _c2cMessageActiveTimer = Map.from({}); final Map<String, Timer> _c2cMessageActiveTimer = Map.from({});
bool _showC2cMessageEditStaus = true; bool _showC2cMessageEditStatus = true;
final Map<String, Timer> _c2cMessageStatusShowTimer = Map.from({}); final Map<String, Timer> _c2cMessageStatusShowTimer = Map.from({});
Map<String, List> loadingMessage = {}; Map<String, List> loadingMessage = {};
@ -144,7 +144,7 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
print("start another download"); print("start another download");
} }
int getRecevied(msgID) { int getReceived(msgID) {
return messageListProgressMap[msgID] ?? 0; return messageListProgressMap[msgID] ?? 0;
} }
@ -173,11 +173,11 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
} }
int get receivedMessageListCount { int get receivedMessageListCount {
return _recivedNewMessageCount; return _receivedNewMessageCount;
} }
set receivedNewMessageCount(int value) { set receivedNewMessageCount(int value) {
_recivedNewMessageCount = value; _receivedNewMessageCount = value;
} }
int get unreadCountForConversation => _unreadCountForConversation; int get unreadCountForConversation => _unreadCountForConversation;
@ -207,6 +207,10 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
} }
clearCurrentConversation() { clearCurrentConversation() {
// Only keep the last 20 messages when existing a chat.
_messageListMap[currentSelectedConv] =
(_messageListMap[currentSelectedConv] ?? []).sublist(
max(0, ((_messageListMap[currentSelectedConv] ?? []).length - 20)));
_currentConversationList.removeLast(); _currentConversationList.removeLast();
notifyListeners(); notifyListeners();
} }
@ -216,7 +220,7 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
} }
setShowC2cEditStatus(bool show) { setShowC2cEditStatus(bool show) {
_showC2cMessageEditStaus = show; _showC2cMessageEditStatus = show;
} }
/// set edit status from chats /// set edit status from chats
@ -373,14 +377,14 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
_totalUnreadCount = 0; _totalUnreadCount = 0;
_groupApplicationList?.clear(); _groupApplicationList?.clear();
_totalUnreadCount = 0; _totalUnreadCount = 0;
_recivedNewMessageCount = 0; _receivedNewMessageCount = 0;
_messageReadReceiptMap.clear(); _messageReadReceiptMap.clear();
_messageListProgressMap.clear(); _messageListProgressMap.clear();
notifyListeners(); notifyListeners();
} }
clearRecivedNewMessageCount() { clearRecivedNewMessageCount() {
_recivedNewMessageCount = 0; _receivedNewMessageCount = 0;
} }
_preLoadImage(List<V2TimMessage> msgList) { _preLoadImage(List<V2TimMessage> msgList) {
@ -441,7 +445,6 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
return false; return false;
}); });
} }
notifyListeners();
} }
setFileMessageLocation(String msgID, String location) { setFileMessageLocation(String msgID, String location) {
@ -526,7 +529,7 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
} }
sendEditStatusMessage(bool isEditing, String toUser) async { sendEditStatusMessage(bool isEditing, String toUser) async {
if (!_showC2cMessageEditStaus) { if (!_showC2cMessageEditStatus) {
return; return;
} }
if (!(_c2cMessageFromUserActiveMap[toUser] ?? false)) { if (!(_c2cMessageFromUserActiveMap[toUser] ?? false)) {
@ -612,7 +615,7 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
); );
}); });
} }
_recivedNewMessageCount = 0; _receivedNewMessageCount = 0;
final currentMsg = _messageListMap[convID] ?? []; final currentMsg = _messageListMap[convID] ?? [];
_messageListMap[convID] = [newMsg, ...currentMsg]; _messageListMap[convID] = [newMsg, ...currentMsg];
notifyListeners(); notifyListeners();
@ -631,7 +634,7 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
} else { } else {
if (convID == currentSelectedConv) { if (convID == currentSelectedConv) {
unreadCountForConversation++; unreadCountForConversation++;
_recivedNewMessageCount++; _receivedNewMessageCount++;
final currentMsg = _messageListMap[convID] ?? []; final currentMsg = _messageListMap[convID] ?? [];
_messageListMap[convID] = [newMsg, ...currentMsg]; _messageListMap[convID] = [newMsg, ...currentMsg];
notifyListeners(); notifyListeners();
@ -746,23 +749,12 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
} }
return; return;
} }
if (messageProgress.isError) {
TIMUIKitClass.onTIMCallback(
TIMCallback(
type: TIMCallbackType.INFO,
infoRecommendText:
TIM_t("视频保存失败") + ": ${messageProgress.errorCode.toString()}",
infoCode: 6660403),
);
return;
}
if (messageProgress.totalSize != -1) { if (messageProgress.totalSize != -1) {
int progrss = int progress =
(messageProgress.currentSize / messageProgress.totalSize * 100) (messageProgress.currentSize / messageProgress.totalSize * 100)
.ceil(); .ceil();
if (progrss > 1) { if (progress > 1) {
setMessageProgress(messageProgress.msgID, progrss); setMessageProgress(messageProgress.msgID, progress);
} }
} }
} }
@ -791,6 +783,12 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
required String convID, required String convID,
ValueChanged<String>? setInputField, ValueChanged<String>? setInputField,
OfflinePushInfo? offlinePushInfo, OfflinePushInfo? offlinePushInfo,
MessagePriorityEnum priority = MessagePriorityEnum.V2TIM_PRIORITY_NORMAL,
bool? onlineUserOnly,
bool? isExcludedFromUnreadCount,
bool? needReadReceipt,
String? cloudCustomData,
String? localCustomData,
}) { }) {
final TUIChatModelTools tools = serviceLocator<TUIChatModelTools>(); final TUIChatModelTools tools = serviceLocator<TUIChatModelTools>();
List<V2TimMessage> currentHistoryMsgList = _messageListMap[convID] ?? []; List<V2TimMessage> currentHistoryMsgList = _messageListMap[convID] ?? [];
@ -807,6 +805,12 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
loadingMessage[convID] = <V2TimMessage>[messageInfoWithSender]; loadingMessage[convID] = <V2TimMessage>[messageInfoWithSender];
} }
return _sendMessage( return _sendMessage(
priority: priority,
onlineUserOnly: onlineUserOnly,
isExcludedFromUnreadCount: isExcludedFromUnreadCount,
needReadReceipt: needReadReceipt,
cloudCustomData: cloudCustomData,
localCustomData: localCustomData,
convID: convID, convID: convID,
setInputField: setInputField, setInputField: setInputField,
id: messageInfo.id as String, id: messageInfo.id as String,
@ -868,6 +872,11 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
bool? isEditStatusMessage = false, bool? isEditStatusMessage = false,
GroupReceiptAllowType? groupType, GroupReceiptAllowType? groupType,
ValueChanged<String>? setInputField, ValueChanged<String>? setInputField,
MessagePriorityEnum priority = MessagePriorityEnum.V2TIM_PRIORITY_NORMAL,
bool? isExcludedFromUnreadCount,
bool? needReadReceipt,
String? cloudCustomData,
String? localCustomData,
}) async { }) async {
String receiver = convType == ConvType.c2c ? convID : ''; String receiver = convType == ConvType.c2c ? convID : '';
String groupID = convType == ConvType.group ? convID : ''; String groupID = convType == ConvType.group ? convID : '';
@ -876,23 +885,28 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
final sendMsgRes = await _messageService.sendMessage( final sendMsgRes = await _messageService.sendMessage(
id: id, id: id,
receiver: receiver, receiver: receiver,
needReadReceipt: chatConfig.isShowGroupReadingStatus && needReadReceipt: needReadReceipt ??
convType == ConvType.group && chatConfig.isShowGroupReadingStatus &&
((chatConfig.groupReadReceiptPermissionList != null && convType == ConvType.group &&
chatConfig.groupReadReceiptPermissionList! ((chatConfig.groupReadReceiptPermissionList != null &&
.contains(groupType)) || chatConfig.groupReadReceiptPermissionList!
(chatConfig.groupReadReceiptPermisionList != null && .contains(groupType)) ||
chatConfig.groupReadReceiptPermisionList! (chatConfig.groupReadReceiptPermisionList != null &&
.contains(oldGroupType))), chatConfig.groupReadReceiptPermisionList!
.contains(oldGroupType))),
groupID: groupID, groupID: groupID,
priority: priority,
localCustomData: localCustomData,
isExcludedFromUnreadCount: isExcludedFromUnreadCount ?? false,
offlinePushInfo: offlinePushInfo, offlinePushInfo: offlinePushInfo,
onlineUserOnly: onlineUserOnly ?? false, onlineUserOnly: onlineUserOnly ?? false,
cloudCustomData: json.encode({ cloudCustomData: cloudCustomData ??
"messageFeature": { json.encode({
"needTyping": 1, "messageFeature": {
"version": 1, "needTyping": 1,
} "version": 1,
})); }
}));
if (isEditStatusMessage == false) { if (isEditStatusMessage == false) {
updateMessage(sendMsgRes, convID, id, convType, groupType, setInputField); updateMessage(sendMsgRes, convID, id, convType, groupType, setInputField);
} }
@ -904,7 +918,7 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
{bool needResetNewMessageCount = true}) { {bool needResetNewMessageCount = true}) {
_messageListMap[conversationID] = messageList; _messageListMap[conversationID] = messageList;
if (needResetNewMessageCount) { if (needResetNewMessageCount) {
_recivedNewMessageCount = 0; _receivedNewMessageCount = 0;
} }
notifyListeners(); notifyListeners();
} }
@ -961,18 +975,21 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
String convID, String convID,
) { ) {
message.id = DateTime.now().millisecondsSinceEpoch.toString(); message.id = DateTime.now().millisecondsSinceEpoch.toString();
final activeMessageList = _messageListMap[convID]; final activeMessageList = _messageListMap[convID];
if (activeMessageList == null || activeMessageList.isEmpty) { if (activeMessageList == null || activeMessageList.isEmpty) {
return; return;
} }
final msgID = message.msgID; final msgID = message.msgID;
_messageListMap[currentSelectedConv] = activeMessageList.map((item) { _messageListMap[convID] = activeMessageList.map((item) {
if (item.msgID == msgID) { if (item.msgID == msgID) {
return message; return message;
} }
return item; return item;
}).toList(); }).toList();
notifyListeners(); if (convID == currentSelectedConv) {
notifyListeners();
}
} }
List<V2TimMessage>? getMessageList(String conversationID) { List<V2TimMessage>? getMessageList(String conversationID) {

View File

@ -31,7 +31,7 @@ class LoginInfo {
{this.sdkAppID = 0, this.userSig = "", this.userID = "", this.loginUser}); {this.sdkAppID = 0, this.userSig = "", this.userID = "", this.loginUser});
} }
class CoreServicesImpl with CoreServices { class CoreServicesImpl implements CoreServices {
V2TimUserFullInfo? _loginInfo; V2TimUserFullInfo? _loginInfo;
late int _sdkAppID; late int _sdkAppID;
late String _userID; late String _userID;

View File

@ -3,7 +3,7 @@ import 'package:tencent_cloud_chat_uikit/data_services/friendShip/friendship_ser
import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart'; import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart';
import 'package:tencent_im_base/tencent_im_base.dart'; import 'package:tencent_im_base/tencent_im_base.dart';
class FriendshipServicesImpl with FriendshipServices { class FriendshipServicesImpl implements FriendshipServices {
final CoreServicesImpl _coreService = serviceLocator<CoreServicesImpl>(); final CoreServicesImpl _coreService = serviceLocator<CoreServicesImpl>();
@override @override

View File

@ -36,26 +36,26 @@ class MessageServiceImpl extends MessageService {
lastMsgID: lastMsgID, lastMsgID: lastMsgID,
lastMsgSeq: lastMsgSeq, lastMsgSeq: lastMsgSeq,
messageTypeList: messageTypeList); messageTypeList: messageTypeList);
final List<V2TimMessage> reponseMessageList = res.data ?? []; final List<V2TimMessage> responsedMessageList = res.data ?? [];
final conversationID = userID ?? groupID; final conversationID = userID ?? groupID;
final cachedMessageList = messgaeListMap[conversationID]; final cachedMessageList = messgaeListMap[conversationID];
List<V2TimMessage> commbinedMessageList = []; List<V2TimMessage> combinedMessageList = [];
// //
if (lastMsgID != null && cachedMessageList != null) { if (lastMsgID != null && cachedMessageList != null) {
commbinedMessageList = [...cachedMessageList, ...reponseMessageList]; combinedMessageList = [...cachedMessageList, ...responsedMessageList];
// //
} else { } else {
final bool existSendingMessage = sendingMessage[conversationID] != null && final bool existSendingMessage = sendingMessage[conversationID] != null &&
sendingMessage[conversationID]!.isNotEmpty; sendingMessage[conversationID]!.isNotEmpty;
// //
if (existSendingMessage) { if (existSendingMessage) {
commbinedMessageList = [ combinedMessageList = [
...sendingMessage[conversationID]!, ...sendingMessage[conversationID]!,
...reponseMessageList ...responsedMessageList
]; ];
} else { } else {
sendingMessage.remove(conversationID); sendingMessage.remove(conversationID);
commbinedMessageList = reponseMessageList; combinedMessageList = responsedMessageList;
} }
} }
if (res.code != 0) { if (res.code != 0) {
@ -64,15 +64,16 @@ class MessageServiceImpl extends MessageService {
errorMsg: res.desc, errorMsg: res.desc,
errorCode: res.code)); errorCode: res.code));
} }
if (reponseMessageList.isEmpty || if (responsedMessageList.isEmpty ||
(!PlatformUtils().isWeb && reponseMessageList.length < count) || (!PlatformUtils().isWeb && responsedMessageList.length < count) ||
(PlatformUtils().isWeb && reponseMessageList.length < min(count, 20))) { (PlatformUtils().isWeb &&
responsedMessageList.length < min(count, 20))) {
haveMoreData = false; haveMoreData = false;
} else { } else {
haveMoreData = true; haveMoreData = true;
} }
return MessageListResponse( return MessageListResponse(
haveMoreData: haveMoreData, data: commbinedMessageList); haveMoreData: haveMoreData, data: combinedMessageList);
} }
@override @override
@ -302,11 +303,13 @@ class MessageServiceImpl extends MessageService {
@override @override
Future<V2TimMsgCreateInfoResult?> createImageMessage( Future<V2TimMsgCreateInfoResult?> createImageMessage(
{String? imagePath, dynamic inputElement}) async { {String? imageName, String? imagePath, dynamic inputElement}) async {
final res = await TencentImSDKPlugin.v2TIMManager final res = await TencentImSDKPlugin.v2TIMManager
.getMessageManager() .getMessageManager()
.createImageMessage( .createImageMessage(
imagePath: imagePath ?? "", inputElement: inputElement); imageName: imageName,
imagePath: imagePath ?? "",
inputElement: inputElement);
if (res.code == 0) { if (res.code == 0) {
return res.data; return res.data;
} }
@ -345,7 +348,7 @@ class MessageServiceImpl extends MessageService {
bool isExcludedFromUnreadCount = false, bool isExcludedFromUnreadCount = false,
bool needReadReceipt = false, bool needReadReceipt = false,
OfflinePushInfo? offlinePushInfo, OfflinePushInfo? offlinePushInfo,
String? cloudCustomData, // String? cloudCustomData,
String? localCustomData, String? localCustomData,
}) async { }) async {
final result = final result =
@ -812,5 +815,4 @@ class MessageServiceImpl extends MessageService {
} }
return result.data?[text] ?? ""; return result.data?[text] ?? "";
} }
} }

View File

@ -3,6 +3,7 @@ import 'package:tencent_im_base/tencent_im_base.dart';
class MessageListResponse { class MessageListResponse {
final bool haveMoreData; final bool haveMoreData;
final List<V2TimMessage> data; final List<V2TimMessage> data;
MessageListResponse({required this.haveMoreData, required this.data}); MessageListResponse({required this.haveMoreData, required this.data});
} }
@ -51,6 +52,7 @@ abstract class MessageService {
Future<void> removeSimpleMsgListener({V2TimSimpleMsgListener? listener}); Future<void> removeSimpleMsgListener({V2TimSimpleMsgListener? listener});
Future<V2TimMsgCreateInfoResult?> createTextMessage({required String text}); Future<V2TimMsgCreateInfoResult?> createTextMessage({required String text});
Future<V2TimMsgCreateInfoResult?> createFaceMessage( Future<V2TimMsgCreateInfoResult?> createFaceMessage(
{required int index, required String data}); {required int index, required String data});
@ -88,7 +90,7 @@ abstract class MessageService {
{required V2TimMessage message}); {required V2TimMessage message});
Future<V2TimMsgCreateInfoResult?> createImageMessage( Future<V2TimMsgCreateInfoResult?> createImageMessage(
{String? imagePath, dynamic inputElement}); {String? imageName, String? imagePath, dynamic inputElement});
Future<V2TimMsgCreateInfoResult?> createVideoMessage( Future<V2TimMsgCreateInfoResult?> createVideoMessage(
{String? videoPath = "", {String? videoPath = "",

File diff suppressed because one or more lines are too long

View File

@ -688,5 +688,6 @@
"k_1698c42": "在访达中打开", "k_1698c42": "在访达中打开",
"k_066fxsz": "查看文件夹", "k_066fxsz": "查看文件夹",
"k_0k432y2": "无法发送,包含文件夹", "k_0k432y2": "无法发送,包含文件夹",
"k_002wb4y": "会话" "k_002wb4y": "会话",
"k_0od4qyh": "视频文件异常"
} }

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,7 @@
// ignore_for_file: avoid_print // ignore_for_file: avoid_print
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:scroll_to_index/scroll_to_index.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/separate_models/tui_chat_separate_view_model.dart'; import 'package:tencent_cloud_chat_uikit/business_logic/separate_models/tui_chat_separate_view_model.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_chat_global_model.dart'; import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_chat_global_model.dart';
import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart'; import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart';
@ -8,6 +9,8 @@ import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
class TIMUIKitChatController { class TIMUIKitChatController {
late TUIChatSeparateViewModel? model; late TUIChatSeparateViewModel? model;
late TIMUIKitInputTextFieldController? textFieldController;
late AutoScrollController? scrollController;
final TUIChatGlobalModel globalChatModel = final TUIChatGlobalModel globalChatModel =
serviceLocator<TUIChatGlobalModel>(); serviceLocator<TUIChatGlobalModel>();
@ -35,13 +38,11 @@ class TIMUIKitChatController {
false; false;
} }
/// clear the current conversation; /// Clear the current conversation;
///
@Deprecated("No need to dispose after tencent_cloud_chat_uikit 0.1.4") @Deprecated("No need to dispose after tencent_cloud_chat_uikit 0.1.4")
dispose() {} dispose() {}
/// clear the history of current conversation; /// Clear the history of current conversation;
///
/// Please provide `convID`, if you use `TIMUIKitChatController` without specifying to a `TIMUIKitChat`. /// Please provide `convID`, if you use `TIMUIKitChatController` without specifying to a `TIMUIKitChat`.
clearHistory([String? convID]) { clearHistory([String? convID]) {
if (convID != null) { if (convID != null) {
@ -51,7 +52,6 @@ class TIMUIKitChatController {
} }
/// refresh the history message list manually; /// refresh the history message list manually;
///
/// Please provide `convType` and `convID`, if you use `TIMUIKitChatController` without specifying to a `TIMUIKitChat`. /// Please provide `convType` and `convID`, if you use `TIMUIKitChatController` without specifying to a `TIMUIKitChat`.
Future<bool> refreshCurrentHistoryList( Future<bool> refreshCurrentHistoryList(
[String? convID, ConvType? convType]) async { [String? convID, ConvType? convType]) async {
@ -64,8 +64,7 @@ class TIMUIKitChatController {
return false; return false;
} }
/// update single message at UI model /// Update single message at UI model
///
/// Please provide `convID`, if you use `TIMUIKitChatController` without specifying to a `TIMUIKitChat`. /// Please provide `convID`, if you use `TIMUIKitChatController` without specifying to a `TIMUIKitChat`.
Future<void> updateMessage( Future<void> updateMessage(
{ {
@ -87,39 +86,72 @@ class TIMUIKitChatController {
} }
/// Sends a message to the specified conversation, or to the current conversation specified on `TIMUIKitChat`. /// Sends a message to the specified conversation, or to the current conversation specified on `TIMUIKitChat`.
/// `TIMUIKitChat`
/// You must provide `convType` and either `userID` or `groupID`, only if you use `TIMUIKitChat` without specifying a `TIMUIKitChatController`, you must provide these parameters. /// You must provide `convType` and either `userID` or `groupID`, only if you use `TIMUIKitChat` without specifying a `TIMUIKitChatController`, you must provide these parameters.
/// `convType` `userID` `groupID`, 使 `TIMUIKitChat` `TIMUIKitChatController`
Future<V2TimValueCallback<V2TimMessage>?>? sendMessage({ Future<V2TimValueCallback<V2TimMessage>?>? sendMessage({
required V2TimMessage? messageInfo, required V2TimMessage? messageInfo,
/// The type of the target conversation: either ConvType.group or ConvType.c2c. Required if using `TIMUIKitChat` without specifying a `TIMUIKitChatController`. /// The type of the target conversation: either ConvType.group or ConvType.c2c. Required if using `TIMUIKitChat` without specifying a `TIMUIKitChatController`.
/// ConvType.group ConvType.c2c使 `TIMUIKitChat` `TIMUIKitChatController`
ConvType? convType, ConvType? convType,
/// The user ID of the target one-to-one conversation. Required if convType is ConvType.c2c. /// The user ID of the target one-to-one conversation. Required if convType is ConvType.c2c.
/// ID convType ConvType.c2c
String? userID, String? userID,
/// The target group ID. Required if convType is ConvType.group. /// The target group ID. Required if convType is ConvType.group.
/// ID convType ConvType.group
String? groupID, String? groupID,
/// A callback function to update the input field when message sending fails. /// A callback function to update the input field when message sending fails.
///
ValueChanged<String>? setInputField, ValueChanged<String>? setInputField,
/// Offline push information. /// Offline push information.
/// 线
OfflinePushInfo? offlinePushInfo, OfflinePushInfo? offlinePushInfo,
/// Whether automatically scrolling to the bottom of the message list after sending a message.
/// This field solely works when `TIMUIKitChatController` is specified for use within a `TIMUIKitChat`.
bool isNavigateToMessageListBottom = true,
/// Message priorities. This field is valid only for group chat messages.
/// You are advised to set higher priorities for important messages (such as red packet and gift messages)
/// and set lower priorities for frequent but unimportant messages (such as like messages).
MessagePriorityEnum priority = MessagePriorityEnum.V2TIM_PRIORITY_NORMAL,
/// Whether the message can be received only by online users.
/// If this field is set to true, the message cannot be pulled in recipient historical message pulling.
/// This field is often used to implement weak notification features such as "The other party is typing" or unimportant notifications in the group. This field is not supported by audio-video groups (AVChatRoom).
bool? onlineUserOnly,
/// Whether the message is excluded from the conversation unread message count.
bool? isExcludedFromUnreadCount,
/// Whether a read receipt is required.
bool? needReadReceipt,
/// Cloud custom data (saved in the cloud, will be sent to the peer end,
/// and can still be pulled after the app is uninstalled and reinstalled)
String? cloudCustomData,
/// Local custom message data (saved locally, will not be sent to the peer end,
/// and will become invalid after the app is uninstalled and reinstalled).
String? localCustomData,
}) { }) {
if (convType != null) { if (convType != null) {
/// Sends a message to the specified conversation. /// Sends a message to the specified conversation.
assert((groupID == null) != (userID == null)); assert((groupID == null) != (userID == null));
assert(groupID != null || convType != ConvType.group); assert(groupID != null || convType != ConvType.group);
assert(userID != null || convType != ConvType.c2c); assert(userID != null || convType != ConvType.c2c);
if (isNavigateToMessageListBottom) {
scrollController?.animateTo(
scrollController!.position.minScrollExtent,
duration: const Duration(milliseconds: 200),
curve: Curves.ease,
);
}
return globalChatModel.sendMessageFromController( return globalChatModel.sendMessageFromController(
priority: priority,
onlineUserOnly: onlineUserOnly,
isExcludedFromUnreadCount: isExcludedFromUnreadCount,
needReadReceipt: needReadReceipt,
cloudCustomData: cloudCustomData,
localCustomData: localCustomData,
messageInfo: messageInfo, messageInfo: messageInfo,
convType: convType, convType: convType,
convID: (convType == ConvType.group ? groupID : userID) ?? "", convID: (convType == ConvType.group ? groupID : userID) ?? "",
@ -127,15 +159,28 @@ class TIMUIKitChatController {
offlinePushInfo: offlinePushInfo); offlinePushInfo: offlinePushInfo);
} else if (model != null) { } else if (model != null) {
/// Sends a message to the current conversation specified on `TIMUIKitChat`. `TIMUIKitChat` /// Sends a message to the current conversation specified on `TIMUIKitChat`. `TIMUIKitChat`
if (isNavigateToMessageListBottom) {
scrollController?.animateTo(
scrollController!.position.minScrollExtent,
duration: const Duration(milliseconds: 200),
curve: Curves.ease,
);
}
return model!.sendMessageFromController( return model!.sendMessageFromController(
messageInfo: messageInfo, offlinePushInfo: offlinePushInfo); priority: priority,
onlineUserOnly: onlineUserOnly,
isExcludedFromUnreadCount: isExcludedFromUnreadCount,
needReadReceipt: needReadReceipt,
cloudCustomData: cloudCustomData,
localCustomData: localCustomData,
messageInfo: messageInfo,
offlinePushInfo: offlinePushInfo);
} }
return null; return null;
} }
/// Send forward message; /// Send forward message;
/// /// This function solely works when `TIMUIKitChatController` is specified for use within a `TIMUIKitChat`.
/// This method needs use with TIMUIKitChat directly or model been initialized.
sendForwardMessage({ sendForwardMessage({
required List<V2TimConversation> conversationList, required List<V2TimConversation> conversationList,
}) async { }) async {
@ -143,7 +188,6 @@ class TIMUIKitChatController {
} }
/// Send merger message; /// Send merger message;
///
/// This method needs use with TIMUIKitChat directly or model been initialized. /// This method needs use with TIMUIKitChat directly or model been initialized.
Future<V2TimValueCallback<V2TimMessage>?> sendMergerMessage({ Future<V2TimValueCallback<V2TimMessage>?> sendMergerMessage({
required List<V2TimConversation> conversationList, required List<V2TimConversation> conversationList,
@ -158,8 +202,7 @@ class TIMUIKitChatController {
context: context); context: context);
} }
/// Set local custom data; returns the bool shows if succeed /// Set local custom data; returns the bool shows if succeed.
/// String字段
/// Please provide `convID`, if you use `TIMUIKitChatController` without specifying to a `TIMUIKitChat`. /// Please provide `convID`, if you use `TIMUIKitChatController` without specifying to a `TIMUIKitChat`.
Future<bool> setLocalCustomData(String msgID, String localCustomData, Future<bool> setLocalCustomData(String msgID, String localCustomData,
[String? convID]) async { [String? convID]) async {
@ -171,8 +214,7 @@ class TIMUIKitChatController {
msgID, localCustomData, conversationID); msgID, localCustomData, conversationID);
} }
/// Set local custom int; returns the bool shows if succeed /// Set local custom int; returns the bool shows if succeed.
/// int字段
/// Please provide `convID`, if you use `TIMUIKitChatController` without specifying to a `TIMUIKitChat`. /// Please provide `convID`, if you use `TIMUIKitChatController` without specifying to a `TIMUIKitChat`.
Future<bool> setLocalCustomInt(String msgID, int localCustomInt, Future<bool> setLocalCustomInt(String msgID, int localCustomInt,
[String? convID]) async { [String? convID]) async {
@ -185,8 +227,52 @@ class TIMUIKitChatController {
} }
/// Get current conversation, returns UserID or GroupID if in the chat page, returns "" if not. /// Get current conversation, returns UserID or GroupID if in the chat page, returns "" if not.
/// IDChat页面UserID or GroupID ""
String getCurrentConversation() { String getCurrentConversation() {
return globalChatModel.currentSelectedConv; return globalChatModel.currentSelectedConv;
} }
/// Hide all bottom panels, including the sticker panel and the additional functions panel, on mobile devices.
/// This function solely works when `TIMUIKitChatController` is specified for use within a `TIMUIKitChat`.
void hideAllBottomPanelOnMobile() {
textFieldController?.hideAllPanel();
}
/// Mention or @ other members in a group manually.
/// This function solely works when `TIMUIKitChatController` is specified for use within a `TIMUIKitChat`.
void mentionOtherMemberInGroup(
{required String showNameInMessage, required String userID}) {
textFieldController?.longPressToAt(showNameInMessage, userID);
}
/// Set the content within the message input text field.
/// This function solely works when `TIMUIKitChatController` is specified for use within a `TIMUIKitChat`.
void setInputTextField(String text) {
textFieldController?.setTextField(text);
}
/// Returns the list of group members of current group chat based on the provided keyword.
///
/// This method filters the group members based on the given keyword. If the keyword is not provided,
/// it returns the entire list of group members. The filtering is performed by checking if the keyword
/// is contained within the userID, nickName, or friendRemark properties of each group member.
///
/// [keyword] (optional) - The keyword to filter the group members. If not provided, the entire list of group members is returned.
/// This function solely works when `TIMUIKitChatController` is specified for use within a `TIMUIKitChat`.
List<V2TimGroupMemberFullInfo> getGroupMemberList({String? keyword}) {
final List<V2TimGroupMemberFullInfo> memberList =
(model?.groupMemberList ?? [])
.whereType<V2TimGroupMemberFullInfo>()
.toList();
return TencentUtils.checkString(keyword) == null
? memberList
: memberList.where((e) {
final userID = e.userID;
final nickName = e.nickName ?? "";
final friendRemark = e.friendRemark ?? "";
return userID.contains(keyword!) ||
nickName.contains(keyword) ||
friendRemark.contains(keyword);
}).toList();
}
} }

View File

@ -278,7 +278,7 @@ class Permissions {
); );
}); });
_entry = entry; _entry = entry;
Overlay.of(context)?.insert(entry); Overlay.of(context).insert(entry);
} }
static Future<bool?> showPermissionConfirmDialog(BuildContext context, value, static Future<bool?> showPermissionConfirmDialog(BuildContext context, value,

View File

@ -101,4 +101,5 @@ class ScreenshotHelper {
})); }));
return completer.future; return completer.future;
} }
} }

View File

@ -1,6 +1,9 @@
// ignore_for_file: constant_identifier_names // ignore_for_file: constant_identifier_names
import 'dart:math';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/platform.dart';
enum DeviceType { Desktop, Mobile } enum DeviceType { Desktop, Mobile }
@ -12,29 +15,48 @@ class FormFactor {
class TUIKitScreenUtils { class TUIKitScreenUtils {
static DeviceType? deviceType; static DeviceType? deviceType;
/// Although specifying the `BuildContext` is optional, providing it can prevent layout issues when this widget renders immediately after the app launch.
/// If this widget needs to be used at the moment the app launches, it's recommended to provide the `BuildContext` here.
static DeviceType getFormFactor([BuildContext? context]) { static DeviceType getFormFactor([BuildContext? context]) {
if (deviceType != null) return deviceType!; if (deviceType != null) return deviceType!;
if(context != null){ if (PlatformUtils().isWeb) {
double deviceWidth = MediaQuery.of(context).size.width; final win = WidgetsBinding.instance.platformDispatcher.views.first;
double deviceHeight = MediaQuery.of(context).size.height; final size = win.physicalSize;
final screenWidth = size.width / win.devicePixelRatio;
final screenHeight = size.height / win.devicePixelRatio;
if (deviceWidth > FormFactor.desktop || deviceWidth > deviceHeight * 1.1) { final diagonalInInches =
deviceType = DeviceType.Desktop; sqrt(pow(screenWidth, 2) + pow(screenHeight, 2)) / 96.0;
} else if (deviceWidth > FormFactor.handset) {
deviceType = DeviceType.Mobile; deviceType = diagonalInInches < 8.0 ? DeviceType.Mobile : DeviceType.Desktop;
}
return deviceType ?? DeviceType.Mobile; return deviceType ?? DeviceType.Mobile;
}else{ }else{
return DeviceType.Mobile; if(context != null){
double deviceWidth = MediaQuery.of(context).size.width;
double deviceHeight = MediaQuery.of(context).size.height;
if (deviceWidth > FormFactor.desktop || deviceWidth > deviceHeight * 1.1) {
deviceType = DeviceType.Desktop;
} else if (deviceWidth > FormFactor.handset) {
deviceType = DeviceType.Mobile;
}
return deviceType ?? DeviceType.Mobile;
}else{
return DeviceType.Mobile;
}
} }
} }
static Widget getDeviceWidget({ static Widget getDeviceWidget({
/// Although specifying the `BuildContext` is optional, providing it can prevent layout issues when this widget renders immediately after the app launch.
/// If this widget needs to be used at the moment the app launches, it's recommended to provide the `BuildContext` here.
BuildContext? context,
required Widget defaultWidget, required Widget defaultWidget,
Widget? desktopWidget, Widget? desktopWidget,
Widget? mobileWidget, Widget? mobileWidget,
}) { }) {
deviceType ??= getFormFactor(context);
if (deviceType == DeviceType.Desktop) return desktopWidget ?? defaultWidget; if (deviceType == DeviceType.Desktop) return desktopWidget ?? defaultWidget;
return mobileWidget ?? defaultWidget; return mobileWidget ?? defaultWidget;
} }

View File

@ -24,6 +24,7 @@ class TIMUIKitAddFriend extends StatefulWidget {
/// The life cycle hooks for adding friends and contact business logic /// The life cycle hooks for adding friends and contact business logic
final AddFriendLifeCycle? lifeCycle; final AddFriendLifeCycle? lifeCycle;
/// The callback function to close the widget upon completion by the parent component.
final VoidCallback? closeFunc; final VoidCallback? closeFunc;
const TIMUIKitAddFriend( const TIMUIKitAddFriend(

View File

@ -55,7 +55,7 @@ class _SendApplicationState extends TIMUIKitState<SendApplication> {
""; "";
final option2 = widget.friendInfo.selfSignature ?? ""; final option2 = widget.friendInfo.selfSignature ?? "";
Widget sendApplicationBody(){ Widget sendApplicationBody() {
return SingleChildScrollView( return SingleChildScrollView(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -79,7 +79,7 @@ class _SendApplicationState extends TIMUIKitState<SendApplication> {
Text( Text(
showName, showName,
style: style:
TextStyle(color: theme.darkTextColor, fontSize: 18), TextStyle(color: theme.darkTextColor, fontSize: 18),
), ),
const SizedBox( const SizedBox(
height: 4, height: 4,
@ -87,17 +87,18 @@ class _SendApplicationState extends TIMUIKitState<SendApplication> {
Text( Text(
"ID: $userID", "ID: $userID",
style: style:
TextStyle(fontSize: 13, color: theme.weakTextColor), TextStyle(fontSize: 13, color: theme.weakTextColor),
), ),
const SizedBox( const SizedBox(
height: 4, height: 4,
), ),
if(TencentUtils.checkString(option2) != null)Text( if (TencentUtils.checkString(option2) != null)
TIM_t_para("个性签名: {{option2}}", "个性签名: $option2")( Text(
option2: option2), TIM_t_para("个性签名: {{option2}}", "个性签名: $option2")(
style: option2: option2),
TextStyle(fontSize: 13, color: theme.weakTextColor), style: TextStyle(
), fontSize: 13, color: theme.weakTextColor),
),
], ],
) )
], ],
@ -168,19 +169,19 @@ class _SendApplicationState extends TIMUIKitState<SendApplication> {
Container( Container(
color: theme.white, color: theme.white,
padding: padding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 12), const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Text(
TIM_t("分组"), TIM_t("分组"),
style: style:
TextStyle(color: theme.darkTextColor, fontSize: 16), TextStyle(color: theme.darkTextColor, fontSize: 16),
), ),
Text( Text(
TIM_t("我的好友"), TIM_t("我的好友"),
style: style:
TextStyle(color: theme.darkTextColor, fontSize: 16), TextStyle(color: theme.darkTextColor, fontSize: 16),
) )
], ],
), ),
@ -197,7 +198,7 @@ class _SendApplicationState extends TIMUIKitState<SendApplication> {
if (widget.lifeCycle?.shouldAddFriend != null && if (widget.lifeCycle?.shouldAddFriend != null &&
await widget.lifeCycle!.shouldAddFriend(userID, remark, await widget.lifeCycle!.shouldAddFriend(userID, remark,
friendGroup, addWording, context) == friendGroup, addWording, context) ==
false) { false) {
return; return;
} }
@ -234,25 +235,25 @@ class _SendApplicationState extends TIMUIKitState<SendApplication> {
} }
return TUIKitScreenUtils.getDeviceWidget( return TUIKitScreenUtils.getDeviceWidget(
desktopWidget: Container( context: context,
padding: const EdgeInsets.only(top: 10), desktopWidget: Container(
color: theme.weakBackgroundColor, padding: const EdgeInsets.only(top: 10),
child: sendApplicationBody(), color: theme.weakBackgroundColor,
), child: sendApplicationBody(),
),
defaultWidget: Scaffold( defaultWidget: Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text( title: Text(
TIM_t("添加好友"), TIM_t("添加好友"),
style: TextStyle(color: theme.appbarTextColor, fontSize: 17), style: TextStyle(color: theme.appbarTextColor, fontSize: 17),
), ),
shadowColor: theme.white, shadowColor: theme.white,
backgroundColor: theme.appbarBgColor ?? backgroundColor: theme.appbarBgColor ?? theme.primaryColor,
theme.primaryColor, iconTheme: IconThemeData(
iconTheme: IconThemeData( color: theme.appbarTextColor,
color: theme.appbarTextColor, ),
), ),
), body: sendApplicationBody(),
body: sendApplicationBody(), ));
));
} }
} }

View File

@ -13,6 +13,7 @@ import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart';
class SendJoinGroupApplication extends StatefulWidget { class SendJoinGroupApplication extends StatefulWidget {
final V2TimGroupInfo groupInfo; final V2TimGroupInfo groupInfo;
final AddGroupLifeCycle? lifeCycle; final AddGroupLifeCycle? lifeCycle;
const SendJoinGroupApplication( const SendJoinGroupApplication(
{Key? key, required this.groupInfo, this.lifeCycle}) {Key? key, required this.groupInfo, this.lifeCycle})
: super(key: key); : super(key: key);
@ -76,7 +77,7 @@ class _SendJoinGroupApplicationState
final showName = widget.groupInfo.groupName ?? groupID; final showName = widget.groupInfo.groupName ?? groupID;
final option1 = _getGroupType(widget.groupInfo.groupType); final option1 = _getGroupType(widget.groupInfo.groupType);
Widget sendGroupApplicationBody(){ Widget sendGroupApplicationBody() {
return SingleChildScrollView( return SingleChildScrollView(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -100,7 +101,7 @@ class _SendJoinGroupApplicationState
Text( Text(
showName, showName,
style: style:
TextStyle(color: theme.darkTextColor, fontSize: 18), TextStyle(color: theme.darkTextColor, fontSize: 18),
), ),
const SizedBox( const SizedBox(
height: 4, height: 4,
@ -108,7 +109,7 @@ class _SendJoinGroupApplicationState
Text( Text(
"ID: $groupID", "ID: $groupID",
style: style:
TextStyle(fontSize: 13, color: theme.weakTextColor), TextStyle(fontSize: 13, color: theme.weakTextColor),
), ),
const SizedBox( const SizedBox(
height: 4, height: 4,
@ -117,7 +118,7 @@ class _SendJoinGroupApplicationState
TIM_t_para("群类型: {{option1}}", "群类型: $option1")( TIM_t_para("群类型: {{option1}}", "群类型: $option1")(
option1: option1), option1: option1),
style: style:
TextStyle(fontSize: 12, color: theme.weakTextColor), TextStyle(fontSize: 12, color: theme.weakTextColor),
), ),
], ],
) )
@ -168,23 +169,22 @@ class _SendJoinGroupApplicationState
); );
} }
return TUIKitScreenUtils.getDeviceWidget( return TUIKitScreenUtils.getDeviceWidget(
desktopWidget: sendGroupApplicationBody(), context: context,
desktopWidget: sendGroupApplicationBody(),
defaultWidget: Scaffold( defaultWidget: Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text( title: Text(
TIM_t("进群申请"), TIM_t("进群申请"),
style: TextStyle(color: theme.appbarTextColor, fontSize: 17), style: TextStyle(color: theme.appbarTextColor, fontSize: 17),
), ),
shadowColor: theme.white, shadowColor: theme.white,
backgroundColor: theme.appbarBgColor ?? backgroundColor: theme.appbarBgColor ?? theme.primaryColor,
theme.primaryColor, iconTheme: IconThemeData(
iconTheme: IconThemeData( color: theme.appbarTextColor,
color: theme.appbarTextColor, ),
), ),
), body: sendGroupApplicationBody(),
body: sendGroupApplicationBody(), ));
));
} }
} }

View File

@ -109,6 +109,7 @@ class _TIMUIKitBlackListState extends TIMUIKitState<TIMUIKitBlackList> {
} }
return TUIKitScreenUtils.getDeviceWidget( return TUIKitScreenUtils.getDeviceWidget(
context: context,
desktopWidget: itemWidget(), desktopWidget: itemWidget(),
defaultWidget: Slidable( defaultWidget: Slidable(
endActionPane: ActionPane(motion: const DrawerMotion(), children: [ endActionPane: ActionPane(motion: const DrawerMotion(), children: [

View File

@ -76,38 +76,38 @@ class MessageHoverControlItem {
} }
class MessageItemBuilder { class MessageItemBuilder {
/// text message builder /// text message builder, returns null means using default widget.
final MessageItemContent? textMessageItemBuilder; final MessageItemContent? textMessageItemBuilder;
/// text message builder for reply message /// text message builder for reply message, returns null means using default widget.
final MessageItemContent? textReplyMessageItemBuilder; final MessageItemContent? textReplyMessageItemBuilder;
/// custom message builder /// custom message builder, returns null means using default widget.
final MessageItemContent? customMessageItemBuilder; final MessageItemContent? customMessageItemBuilder;
/// image message builder /// image message builder, returns null means using default widget.
final MessageItemContent? imageMessageItemBuilder; final MessageItemContent? imageMessageItemBuilder;
/// sound message builder /// sound message builder, returns null means using default widget.
final MessageItemContent? soundMessageItemBuilder; final MessageItemContent? soundMessageItemBuilder;
/// video message builder /// video message builder, returns null means using default widget.
final MessageItemContent? videoMessageItemBuilder; final MessageItemContent? videoMessageItemBuilder;
/// file message builder /// file message builder, returns null means using default widget.
final MessageItemContent? fileMessageItemBuilder; final MessageItemContent? fileMessageItemBuilder;
/// location message (LBS) item builder; /// location message (LBS) item builder;
/// recommend to use our LBS plug-in: https://pub.dev/packages/tim_ui_kit_lbs_plugin /// recommend to use our LBS plug-in: https://pub.dev/packages/tim_ui_kit_lbs_plugin
final MessageItemContent? locationMessageItemBuilder; final MessageItemContent? locationMessageItemBuilder;
/// face message, like emoji, message builder /// face message, like emoji, message builder, returns null means using default widget.
final MessageItemContent? faceMessageItemBuilder; final MessageItemContent? faceMessageItemBuilder;
/// group tips message builder /// group tips message builder, returns null means using default widget.
final MessageItemContent? groupTipsMessageItemBuilder; final MessageItemContent? groupTipsMessageItemBuilder;
/// merger message builder /// merger message builder, returns null means using default widget.
final MessageItemContent? mergerMessageItemBuilder; final MessageItemContent? mergerMessageItemBuilder;
/// The builder for the whole message line, expect for those message type without avatar and nickname. /// The builder for the whole message line, expect for those message type without avatar and nickname.
@ -148,15 +148,32 @@ class MessageToolTipItem {
} }
class ToolTipsConfig { class ToolTipsConfig {
/// Whether to show the reply to a message option.
final bool showReplyMessage; final bool showReplyMessage;
/// Whether to show the multiple-choice option for messages.
final bool showMultipleChoiceMessage; final bool showMultipleChoiceMessage;
/// Whether to show the option to delete a message.
final bool showDeleteMessage; final bool showDeleteMessage;
/// Whether to show the option to recall a message.
final bool showRecallMessage; final bool showRecallMessage;
/// Whether to show the option to copy a message.
final bool showCopyMessage; final bool showCopyMessage;
/// Whether to show the option to forward a message.
final bool showForwardMessage; final bool showForwardMessage;
/// Whether to show the option to translate a text message. This module is not available by default. Please contact your Tencent Cloud sales representative or customer service team to enable this feature.
final bool showTranslation; final bool showTranslation;
/// A builder for additional custom items. We recommend using `additionalMessageToolTips` instead of this field since version 2.0, as you only need to provide the data rather than the whole widget. This makes usage easier and you don't need to worry about the UI display.
final Widget? Function(V2TimMessage message, Function() closeTooltip, final Widget? Function(V2TimMessage message, Function() closeTooltip,
[Key? key, BuildContext? context])? additionalItemBuilder; [Key? key, BuildContext? context])? additionalItemBuilder;
/// A list of additional message tooltip menu items, provided with the data only. We recommend using this field instead of the previous `additionalItemBuilder`.
List<MessageToolTipItem> Function( List<MessageToolTipItem> Function(
V2TimMessage message, Function() closeTooltip)? additionalMessageToolTips; V2TimMessage message, Function() closeTooltip)? additionalMessageToolTips;
@ -169,8 +186,9 @@ class ToolTipsConfig {
this.showCopyMessage = true, this.showCopyMessage = true,
this.showForwardMessage = true, this.showForwardMessage = true,
this.additionalMessageToolTips, this.additionalMessageToolTips,
@Deprecated("Please use `additionalMessageToolTips` instead. You are now only expected to specify the data, rather than providing a whole widget. This makes usage easier, as you no longer need to worry about the UI display.") @Deprecated(
this.additionalItemBuilder}); "Please use `additionalMessageToolTips` instead. You are now only expected to specify the data, rather than providing a whole widget. This makes usage easier, as you no longer need to worry about the UI display.")
this.additionalItemBuilder});
} }
class TIMUIKitHistoryMessageListItem extends StatefulWidget { class TIMUIKitHistoryMessageListItem extends StatefulWidget {
@ -181,6 +199,10 @@ class TIMUIKitHistoryMessageListItem extends StatefulWidget {
final void Function(String userID, TapDownDetails tapDetails)? final void Function(String userID, TapDownDetails tapDetails)?
onTapForOthersPortrait; onTapForOthersPortrait;
/// secondary tap remote user avatar callback function
final void Function(String userID, TapDownDetails tapDetails)?
onSecondaryTapForOthersPortrait;
/// the function use for reply message, when click replied message can scroll to it. /// the function use for reply message, when click replied message can scroll to it.
final Function? onScrollToIndex; final Function? onScrollToIndex;
@ -262,8 +284,9 @@ class TIMUIKitHistoryMessageListItem extends StatefulWidget {
const TIMUIKitHistoryMessageListItem( const TIMUIKitHistoryMessageListItem(
{Key? key, {Key? key,
required this.message, required this.message,
@Deprecated("Nickname will not show in one-to-one chat, if you tend to control it in group chat, please use `isShowSelfNameInGroup` and `isShowOthersNameInGroup` from `config: TIMUIKitChatConfig` instead") @Deprecated(
this.showNickName = false, "Nickname will not show in one-to-one chat, if you tend to control it in group chat, please use `isShowSelfNameInGroup` and `isShowOthersNameInGroup` from `config: TIMUIKitChatConfig` instead")
this.showNickName = false,
this.onScrollToIndex, this.onScrollToIndex,
this.onScrollToIndexBegin, this.onScrollToIndexBegin,
this.onTapForOthersPortrait, this.onTapForOthersPortrait,
@ -287,7 +310,8 @@ class TIMUIKitHistoryMessageListItem extends StatefulWidget {
this.bottomRowBuilder, this.bottomRowBuilder,
this.isUseDefaultEmoji = false, this.isUseDefaultEmoji = false,
this.customEmojiStickerList = const [], this.customEmojiStickerList = const [],
this.textFieldController}) this.textFieldController,
this.onSecondaryTapForOthersPortrait})
: super(key: key); : super(key: key);
@override @override
@ -386,192 +410,206 @@ class _TIMUIKItHistoryMessageListItemState
switch (msgType) { switch (msgType) {
case MessageElemType.V2TIM_ELEM_TYPE_CUSTOM: case MessageElemType.V2TIM_ELEM_TYPE_CUSTOM:
if (messageItemBuilder?.customMessageItemBuilder != null) { final customWidget =
return messageItemBuilder!.customMessageItemBuilder!( messageItemBuilder?.customMessageItemBuilder != null
messageItem, ? messageItemBuilder!.customMessageItemBuilder!(
isShowJump, messageItem,
() => model.jumpMsgID = "", isShowJump,
)!; () => model.jumpMsgID = "",
} )
return TIMUIKitCustomElem( : null;
message: messageItem, return customWidget ??
customElem: messageItem.customElem, TIMUIKitCustomElem(
isFromSelf: isFromSelf, message: messageItem,
messageBackgroundColor: widget.themeData?.messageBackgroundColor, customElem: messageItem.customElem,
messageBorderRadius: widget.themeData?.messageBorderRadius, isFromSelf: isFromSelf,
messageFontStyle: widget.themeData?.messageTextStyle, messageBackgroundColor: widget.themeData?.messageBackgroundColor,
textPadding: widget.textPadding, messageBorderRadius: widget.themeData?.messageBorderRadius,
isShowMessageReaction: widget.isUseMessageReaction, messageFontStyle: widget.themeData?.messageTextStyle,
); textPadding: widget.textPadding,
isShowMessageReaction: widget.isUseMessageReaction,
);
case MessageElemType.V2TIM_ELEM_TYPE_SOUND: case MessageElemType.V2TIM_ELEM_TYPE_SOUND:
if (messageItemBuilder?.soundMessageItemBuilder != null) { final customWidget = messageItemBuilder?.soundMessageItemBuilder != null
return messageItemBuilder!.soundMessageItemBuilder!( ? messageItemBuilder!.soundMessageItemBuilder!(
messageItem, messageItem,
isShowJump, isShowJump,
clearJump, () => model.jumpMsgID = "",
)!; )
} : null;
return TIMUIKitSoundElem( return customWidget ??
chatModel: model, TIMUIKitSoundElem(
message: messageItem, chatModel: model,
soundElem: messageItem.soundElem!, message: messageItem,
msgID: messageItem.msgID ?? "", soundElem: messageItem.soundElem!,
isFromSelf: messageItem.isSelf ?? true, msgID: messageItem.msgID ?? "",
clearJump: clearJump, isFromSelf: messageItem.isSelf ?? true,
isShowJump: isShowJump, clearJump: clearJump,
localCustomInt: messageItem.localCustomInt, isShowJump: isShowJump,
borderRadius: widget.themeData?.messageBorderRadius, localCustomInt: messageItem.localCustomInt,
fontStyle: widget.themeData?.messageTextStyle, borderRadius: widget.themeData?.messageBorderRadius,
backgroundColor: widget.themeData?.messageBackgroundColor, fontStyle: widget.themeData?.messageTextStyle,
textPadding: widget.textPadding, backgroundColor: widget.themeData?.messageBackgroundColor,
isShowMessageReaction: widget.isUseMessageReaction, textPadding: widget.textPadding,
); isShowMessageReaction: widget.isUseMessageReaction,
);
case MessageElemType.V2TIM_ELEM_TYPE_TEXT: case MessageElemType.V2TIM_ELEM_TYPE_TEXT:
if (isReplyMessage(messageItem)) { if (isReplyMessage(messageItem)) {
if (messageItemBuilder?.textReplyMessageItemBuilder != null) { final customWidget =
return messageItemBuilder!.textReplyMessageItemBuilder!( messageItemBuilder?.textReplyMessageItemBuilder != null
messageItem, ? messageItemBuilder!.textReplyMessageItemBuilder!(
isShowJump, messageItem,
clearJump, isShowJump,
)!; () => model.jumpMsgID = "",
} )
return TIMUIKitReplyElem( : null;
message: messageItem, return customWidget ??
clearJump: clearJump, TIMUIKitReplyElem(
isShowJump: isShowJump, message: messageItem,
scrollToIndex: widget.onScrollToIndex ?? () {}, clearJump: clearJump,
borderRadius: widget.themeData?.messageBorderRadius, isShowJump: isShowJump,
fontStyle: widget.themeData?.messageTextStyle, scrollToIndex: widget.onScrollToIndex ?? () {},
backgroundColor: widget.themeData?.messageBackgroundColor, borderRadius: widget.themeData?.messageBorderRadius,
textPadding: widget.textPadding, fontStyle: widget.themeData?.messageTextStyle,
isUseDefaultEmoji: widget.isUseDefaultEmoji, backgroundColor: widget.themeData?.messageBackgroundColor,
customEmojiStickerList: widget.customEmojiStickerList, textPadding: widget.textPadding,
chatModel: model, isUseDefaultEmoji: widget.isUseDefaultEmoji,
isShowMessageReaction: widget.isUseMessageReaction, customEmojiStickerList: widget.customEmojiStickerList,
); chatModel: model,
isShowMessageReaction: widget.isUseMessageReaction,
);
} }
if (messageItemBuilder?.textMessageItemBuilder != null) { final customWidget = messageItemBuilder?.textMessageItemBuilder != null
return messageItemBuilder!.textMessageItemBuilder!( ? messageItemBuilder!.textMessageItemBuilder!(
messageItem, messageItem,
isShowJump, isShowJump,
clearJump, () => model.jumpMsgID = "",
)!; )
} : null;
return TIMUIKitTextElem( return customWidget ??
chatModel: model, TIMUIKitTextElem(
message: messageItem, chatModel: model,
isFromSelf: messageItem.isSelf ?? true, message: messageItem,
clearJump: clearJump, isFromSelf: messageItem.isSelf ?? true,
isShowJump: isShowJump, clearJump: clearJump,
borderRadius: widget.themeData?.messageBorderRadius, isShowJump: isShowJump,
fontStyle: widget.themeData?.messageTextStyle, borderRadius: widget.themeData?.messageBorderRadius,
backgroundColor: widget.themeData?.messageBackgroundColor, fontStyle: widget.themeData?.messageTextStyle,
textPadding: widget.textPadding, backgroundColor: widget.themeData?.messageBackgroundColor,
isShowMessageReaction: widget.isUseMessageReaction, textPadding: widget.textPadding,
isUseDefaultEmoji: widget.isUseDefaultEmoji, isShowMessageReaction: widget.isUseMessageReaction,
customEmojiStickerList: widget.customEmojiStickerList, isUseDefaultEmoji: widget.isUseDefaultEmoji,
); customEmojiStickerList: widget.customEmojiStickerList,
);
case MessageElemType.V2TIM_ELEM_TYPE_FACE: case MessageElemType.V2TIM_ELEM_TYPE_FACE:
if (messageItemBuilder?.faceMessageItemBuilder != null) { final customWidget = messageItemBuilder?.faceMessageItemBuilder != null
return messageItemBuilder!.faceMessageItemBuilder!( ? messageItemBuilder!.faceMessageItemBuilder!(
messageItem, messageItem,
isShowJump, isShowJump,
clearJump, () => model.jumpMsgID = "",
)!; )
} : null;
return TIMUIKitFaceElem( return customWidget ??
model: model, TIMUIKitFaceElem(
path: messageItem.faceElem!.data ?? "", model: model,
clearJump: clearJump, path: messageItem.faceElem!.data ?? "",
isShowJump: isShowJump, clearJump: clearJump,
message: messageItem, isShowJump: isShowJump,
isShowMessageReaction: widget.isUseMessageReaction, message: messageItem,
); isShowMessageReaction: widget.isUseMessageReaction,
);
case MessageElemType.V2TIM_ELEM_TYPE_FILE: case MessageElemType.V2TIM_ELEM_TYPE_FILE:
if (messageItemBuilder?.fileMessageItemBuilder != null) { final customWidget = messageItemBuilder?.fileMessageItemBuilder != null
return messageItemBuilder!.fileMessageItemBuilder!( ? messageItemBuilder!.fileMessageItemBuilder!(
messageItem, messageItem,
isShowJump, isShowJump,
clearJump, () => model.jumpMsgID = "",
)!; )
} : null;
return TIMUIKitFileElem( return customWidget ??
chatModel: model, TIMUIKitFileElem(
message: messageItem, chatModel: model,
messageID: messageItem.msgID, message: messageItem,
fileElem: messageItem.fileElem, messageID: messageItem.msgID,
isSelf: messageItem.isSelf ?? true, fileElem: messageItem.fileElem,
clearJump: clearJump, isSelf: messageItem.isSelf ?? true,
isShowJump: isShowJump, clearJump: clearJump,
isShowMessageReaction: widget.isUseMessageReaction, isShowJump: isShowJump,
); isShowMessageReaction: widget.isUseMessageReaction,
);
case MessageElemType.V2TIM_ELEM_TYPE_GROUP_TIPS: case MessageElemType.V2TIM_ELEM_TYPE_GROUP_TIPS:
if (messageItemBuilder?.groupTipsMessageItemBuilder != null) { final customWidget =
return messageItemBuilder!.groupTipsMessageItemBuilder!( messageItemBuilder?.groupTipsMessageItemBuilder != null
messageItem, ? messageItemBuilder!.groupTipsMessageItemBuilder!(
isShowJump, messageItem,
clearJump, isShowJump,
)!; () => model.jumpMsgID = "",
} )
return Text(TIM_t("[群系统消息]")); : null;
return customWidget ?? Text(TIM_t("[群系统消息]"));
case MessageElemType.V2TIM_ELEM_TYPE_IMAGE: case MessageElemType.V2TIM_ELEM_TYPE_IMAGE:
if (messageItemBuilder?.imageMessageItemBuilder != null) { final customWidget = messageItemBuilder?.imageMessageItemBuilder != null
return messageItemBuilder!.imageMessageItemBuilder!( ? messageItemBuilder!.imageMessageItemBuilder!(
messageItem, messageItem,
isShowJump, isShowJump,
clearJump, () => model.jumpMsgID = "",
)!; )
} : null;
return TIMUIKitImageElem( return customWidget ??
clearJump: clearJump, TIMUIKitImageElem(
isShowJump: isShowJump, clearJump: clearJump,
chatModel: model, isShowJump: isShowJump,
message: messageItem, chatModel: model,
isShowMessageReaction: widget.isUseMessageReaction, message: messageItem,
key: Key("${messageItem.seq}_${messageItem.timestamp}"), isShowMessageReaction: widget.isUseMessageReaction,
); key: Key("${messageItem.seq}_${messageItem.timestamp}"),
);
case MessageElemType.V2TIM_ELEM_TYPE_VIDEO: case MessageElemType.V2TIM_ELEM_TYPE_VIDEO:
if (messageItemBuilder?.videoMessageItemBuilder != null) { final customWidget = messageItemBuilder?.videoMessageItemBuilder != null
return messageItemBuilder!.videoMessageItemBuilder!( ? messageItemBuilder!.videoMessageItemBuilder!(
messageItem, messageItem,
isShowJump, isShowJump,
clearJump, () => model.jumpMsgID = "",
)!; )
} : null;
return TIMUIKitVideoElem( return customWidget ??
messageItem, TIMUIKitVideoElem(
isShowJump: isShowJump, messageItem,
chatModel: model, isShowJump: isShowJump,
clearJump: clearJump, chatModel: model,
isShowMessageReaction: widget.isUseMessageReaction, clearJump: clearJump,
); isShowMessageReaction: widget.isUseMessageReaction,
);
case MessageElemType.V2TIM_ELEM_TYPE_LOCATION: case MessageElemType.V2TIM_ELEM_TYPE_LOCATION:
if (messageItemBuilder?.locationMessageItemBuilder != null) { final customWidget =
return messageItemBuilder!.locationMessageItemBuilder!( messageItemBuilder?.locationMessageItemBuilder != null
messageItem, ? messageItemBuilder!.locationMessageItemBuilder!(
isShowJump, messageItem,
clearJump, isShowJump,
)!; () => model.jumpMsgID = "",
} )
return Text(TIM_t("[位置]")); : null;
return customWidget ?? Text(TIM_t("[位置]"));
case MessageElemType.V2TIM_ELEM_TYPE_MERGER: case MessageElemType.V2TIM_ELEM_TYPE_MERGER:
if (messageItemBuilder?.mergerMessageItemBuilder != null) { final customWidget =
return messageItemBuilder!.mergerMessageItemBuilder!( messageItemBuilder?.mergerMessageItemBuilder != null
messageItem, ? messageItemBuilder!.mergerMessageItemBuilder!(
isShowJump, messageItem,
clearJump, isShowJump,
)!; () => model.jumpMsgID = "",
} )
return TIMUIKitMergerElem( : null;
messageItemBuilder: messageItemBuilder, return customWidget ??
model: model, TIMUIKitMergerElem(
isShowJump: isShowJump, messageItemBuilder: messageItemBuilder,
clearJump: clearJump, model: model,
message: messageItem, isShowJump: isShowJump,
isShowMessageReaction: widget.isUseMessageReaction, clearJump: clearJump,
mergerElem: messageItem.mergerElem!, message: messageItem,
messageID: messageItem.msgID ?? "", isShowMessageReaction: widget.isUseMessageReaction,
isSelf: messageItem.isSelf ?? true); mergerElem: messageItem.mergerElem!,
messageID: messageItem.msgID ?? "",
isSelf: messageItem.isSelf ?? true);
default: default:
return Text(TIM_t("[未知消息]")); return Text(TIM_t("[未知消息]"));
} }
@ -941,12 +979,18 @@ class _TIMUIKItHistoryMessageListItemState
), ),
onClick: (_) { onClick: (_) {
model.repliedMessage = widget.message; model.repliedMessage = widget.message;
if (widget.allowAtUserWhenReply && final isSelf = widget.message.isSelf ?? true;
widget.onLongPressForOthersHeadPortrait != null && final isGroup =
!(widget.message.isSelf ?? true)) { TencentUtils.checkString(widget.message.groupID) != null;
widget.onLongPressForOthersHeadPortrait!( final isAtWhenReply = !isSelf &&
widget.message.sender, widget.message.nickName); isGroup &&
} widget.allowAtUserWhenReply &&
widget.onLongPressForOthersHeadPortrait != null;
/// If replying to a self message, do not add a at tag, only requestFocus.
widget.onLongPressForOthersHeadPortrait!(
!isAtWhenReply ? null : widget.message.sender,
!isAtWhenReply ? null : widget.message.nickName);
}, },
), ),
if ((widget.toolTipsConfig?.showForwardMessage ?? true) && if ((widget.toolTipsConfig?.showForwardMessage ?? true) &&
@ -1009,18 +1053,28 @@ class _TIMUIKItHistoryMessageListItemState
context); context);
} }
Widget renderHoverTipAndReadStatus(TUIChatSeparateViewModel model, Widget renderHoverTipAndReadStatus(
bool isSelf, V2TimMessage message, bool isPeerRead, TUITheme theme) { TUIChatSeparateViewModel model,
bool isSelf,
V2TimMessage message,
bool isPeerRead,
TUITheme theme,
bool isDownloadWaiting) {
final isDesktopScreen = final isDesktopScreen =
TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop; TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop;
final wideHoverTipList = getMessageHoverControlBar(model, theme); final wideHoverTipList = model.chatConfig.isUseMessageHoverBarOnDesktop
final lastItemName = wideHoverTipList.last.name; ? getMessageHoverControlBar(model, theme)
: [];
final lastItemName =
wideHoverTipList.isNotEmpty ? wideHoverTipList.last.name : "";
return Column( return Column(
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
children: [ children: [
if (isDesktopScreen && isShowWideToolTip) if (isDesktopScreen &&
isShowWideToolTip &&
!((widget.message.elemType == 6 && isDownloadWaiting)))
Container( Container(
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4), borderRadius: BorderRadius.circular(4),
@ -1188,7 +1242,8 @@ class _TIMUIKItHistoryMessageListItemState
children: [ children: [
if (model.isMultiSelect) if (model.isMultiSelect)
Container( Container(
margin: EdgeInsets.only(right: 12, top: 10, left: isSelf ? 16 : 0), margin:
EdgeInsets.only(right: 12, top: 10, left: isSelf ? 16 : 0),
child: CheckBoxButton( child: CheckBoxButton(
isChecked: model.multiSelectedMessageList.contains(message), isChecked: model.multiSelectedMessageList.contains(message),
onChanged: (value) { onChanged: (value) {
@ -1264,6 +1319,26 @@ class _TIMUIKItHistoryMessageListItemState
message.sender ?? "", TapDownDetails()); message.sender ?? "", TapDownDetails());
} }
}, },
onSecondaryTap: isDesktopScreen
? null
: () {
if (widget.onSecondaryTapForOthersPortrait !=
null &&
widget.allowAvatarTap) {
widget.onSecondaryTapForOthersPortrait!(
message.sender ?? "", TapDownDetails());
}
},
onSecondaryTapDown: isDesktopScreen
? (details) {
if (widget.onSecondaryTapForOthersPortrait !=
null &&
widget.allowAvatarTap) {
widget.onSecondaryTapForOthersPortrait!(
message.sender ?? "", details);
}
}
: null,
child: widget.userAvatarBuilder != null child: widget.userAvatarBuilder != null
? widget.userAvatarBuilder!(context, message) ? widget.userAvatarBuilder!(context, message)
: Container( : Container(
@ -1282,6 +1357,16 @@ class _TIMUIKItHistoryMessageListItemState
), ),
), ),
), ),
if (isSelf &&
widget.message.elemType == 6 &&
isDownloadWaiting)
Container(
margin: const EdgeInsets.only(top: 2),
child: LoadingAnimationWidget.threeArchedCircle(
color: theme.weakTextColor ?? Colors.grey,
size: 20,
),
),
Container( Container(
margin: widget.showAvatar margin: widget.showAvatar
? (isSelf ? (isSelf
@ -1319,8 +1404,13 @@ class _TIMUIKItHistoryMessageListItemState
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
children: [ children: [
if (isSelf) if (isSelf)
renderHoverTipAndReadStatus(model, isSelf, renderHoverTipAndReadStatus(
message, isPeerRead, theme), model,
isSelf,
message,
isPeerRead,
theme,
isDownloadWaiting),
Container( Container(
constraints: BoxConstraints( constraints: BoxConstraints(
maxWidth: constraints.maxWidth * 0.77, maxWidth: constraints.maxWidth * 0.77,
@ -1406,8 +1496,13 @@ class _TIMUIKItHistoryMessageListItemState
child: Icon(Icons.circle, child: Icon(Icons.circle,
color: theme.cautionColor, size: 10)), color: theme.cautionColor, size: 10)),
if (!isSelf) if (!isSelf)
renderHoverTipAndReadStatus(model, isSelf, renderHoverTipAndReadStatus(
message, isPeerRead, theme), model,
isSelf,
message,
isPeerRead,
theme,
isDownloadWaiting),
], ],
), ),
if (widget.bottomRowBuilder != null) if (widget.bottomRowBuilder != null)
@ -1415,7 +1510,9 @@ class _TIMUIKItHistoryMessageListItemState
], ],
), ),
), ),
if (widget.message.elemType == 6 && isDownloadWaiting) if (!isSelf &&
widget.message.elemType == 6 &&
isDownloadWaiting)
Container( Container(
margin: const EdgeInsets.only(top: 24, left: 6), margin: const EdgeInsets.only(top: 24, left: 6),
child: LoadingAnimationWidget.threeArchedCircle( child: LoadingAnimationWidget.threeArchedCircle(

View File

@ -85,7 +85,10 @@ class TIMUIKitMessageTooltipState
} }
hasFile() { hasFile() {
if (PlatformUtils().isMobile || widget.message.fileElem == null) { if (PlatformUtils().isMobile ||
(widget.message.fileElem == null &&
widget.message.imageElem == null &&
widget.message.videoElem == null)) {
isShowOpenFile = false; isShowOpenFile = false;
return; return;
} }
@ -94,26 +97,44 @@ class TIMUIKitMessageTooltipState
return; return;
} }
if (PlatformUtils().isDesktop) { if (PlatformUtils().isDesktop) {
if (globalModal.getMessageProgress(widget.message.msgID) == 100) { if (widget.message.fileElem != null) {
String savePath = if (globalModal.getMessageProgress(widget.message.msgID) == 100) {
TencentUtils.checkString(widget.message.fileElem!.localUrl) ?? String savePath =
globalModal.getFileMessageLocation(widget.message.msgID); TencentUtils.checkString(widget.message.fileElem!.localUrl) ??
globalModal.getFileMessageLocation(widget.message.msgID);
File f = File(savePath);
if (f.existsSync() && widget.message.msgID != null) {
filePath = savePath;
isShowOpenFile = true;
return;
}
isShowOpenFile = false;
return;
}
String savePath = widget.message.fileElem!.localUrl ?? '';
File f = File(savePath); File f = File(savePath);
if (f.existsSync() && widget.message.msgID != null) { if (f.existsSync() && widget.message.msgID != null) {
filePath = savePath; filePath = savePath;
globalModal.setMessageProgress(widget.message.msgID!, 100);
isShowOpenFile = true;
return;
}
} else if (widget.message.imageElem != null) {
if (TencentUtils.checkString(
widget.message.imageElem!.imageList![0]!.localUrl) !=
null &&
File(widget.message.imageElem!.imageList![0]!.localUrl!)
.existsSync()) {
isShowOpenFile = true;
return;
}
} else if (widget.message.videoElem != null) {
if (TencentUtils.checkString(widget.message.videoElem!.localVideoUrl) !=
null &&
File(widget.message.videoElem!.localVideoUrl!).existsSync()) {
isShowOpenFile = true; isShowOpenFile = true;
return; return;
} }
isShowOpenFile = false;
return;
}
String savePath = widget.message.fileElem!.localUrl ?? '';
File f = File(savePath);
if (f.existsSync() && widget.message.msgID != null) {
filePath = savePath;
globalModal.setMessageProgress(widget.message.msgID!, 100);
isShowOpenFile = true;
return;
} }
} }
isShowOpenFile = false; isShowOpenFile = false;
@ -198,11 +219,6 @@ class TIMUIKitMessageTooltipState
id: "forwardMessage", id: "forwardMessage",
iconImageAsset: "images/forward_message.png", iconImageAsset: "images/forward_message.png",
onClick: () => _onTap("forwardMessage", model)), onClick: () => _onTap("forwardMessage", model)),
MessageToolTipItem(
label: TIM_t("多选"),
id: "multiSelect",
iconImageAsset: "images/multi_message.png",
onClick: () => _onTap("multiSelect", model)),
if (shouldShowReplyAction) if (shouldShowReplyAction)
MessageToolTipItem( MessageToolTipItem(
label: TIM_t(model.chatConfig.isAtWhenReply ? "回复" : "引用"), label: TIM_t(model.chatConfig.isAtWhenReply ? "回复" : "引用"),
@ -210,15 +226,20 @@ class TIMUIKitMessageTooltipState
iconImageAsset: "images/reply_message.png", iconImageAsset: "images/reply_message.png",
onClick: () => _onTap("replyMessage", model)), onClick: () => _onTap("replyMessage", model)),
MessageToolTipItem( MessageToolTipItem(
label: TIM_t("删除"), label: TIM_t("多选"),
id: "delete", id: "multiSelect",
iconImageAsset: "images/delete_message.png", iconImageAsset: "images/multi_message.png",
onClick: () => _onTap("delete", model)), onClick: () => _onTap("multiSelect", model)),
MessageToolTipItem( MessageToolTipItem(
label: TIM_t("翻译"), label: TIM_t("翻译"),
id: "translate", id: "translate",
iconImageAsset: "images/translate.png", iconImageAsset: "images/translate.png",
onClick: () => _onTap("translate", model)), onClick: () => _onTap("translate", model)),
MessageToolTipItem(
label: TIM_t("删除"),
id: "delete",
iconImageAsset: "images/delete_message.png",
onClick: () => _onTap("delete", model)),
if (shouldShowRevokeAction) if (shouldShowRevokeAction)
MessageToolTipItem( MessageToolTipItem(
label: TIM_t("撤回"), label: TIM_t("撤回"),
@ -226,6 +247,7 @@ class TIMUIKitMessageTooltipState
iconImageAsset: "images/revoke_message.png", iconImageAsset: "images/revoke_message.png",
onClick: () => _onTap("revoke", model)), onClick: () => _onTap("revoke", model)),
]; ];
final defaultTipsIds = defaultTipsList.map((e) => e.id);
List<MessageToolTipItem> defaultFormattedTipsList = defaultTipsList; List<MessageToolTipItem> defaultFormattedTipsList = defaultTipsList;
if (tooltipsConfig != null) { if (tooltipsConfig != null) {
defaultFormattedTipsList = defaultTipsList.where((element) { defaultFormattedTipsList = defaultTipsList.where((element) {
@ -235,10 +257,14 @@ class TIMUIKitMessageTooltipState
widget.message.elemType == MessageElemType.V2TIM_ELEM_TYPE_TEXT; widget.message.elemType == MessageElemType.V2TIM_ELEM_TYPE_TEXT;
} }
if (type == "forwardMessage") { if (type == "forwardMessage") {
return tooltipsConfig.showForwardMessage && !isDesktopScreen; return tooltipsConfig.showForwardMessage &&
!(isDesktopScreen &&
widget.model.chatConfig.isUseMessageHoverBarOnDesktop);
} }
if (type == "replyMessage") { if (type == "replyMessage") {
return tooltipsConfig.showReplyMessage && !isDesktopScreen; return tooltipsConfig.showReplyMessage &&
!(isDesktopScreen &&
widget.model.chatConfig.isUseMessageHoverBarOnDesktop);
} }
if (type == "delete") { if (type == "delete") {
return (!PlatformUtils().isWeb) && tooltipsConfig.showDeleteMessage; return (!PlatformUtils().isWeb) && tooltipsConfig.showDeleteMessage;
@ -286,7 +312,9 @@ class TIMUIKitMessageTooltipState
children: [ children: [
Image.asset( Image.asset(
item.iconImageAsset, item.iconImageAsset,
package: 'tencent_cloud_chat_uikit', package: defaultTipsIds.contains(item.id)
? 'tencent_cloud_chat_uikit'
: null,
width: 20, width: 20,
height: 20, height: 20,
), ),
@ -353,37 +381,53 @@ class TIMUIKitMessageTooltipState
return widgetList; return widgetList;
} }
_onOpenDesktop(String path) {
if (PlatformUtils().isDesktop) {
OpenFile.open(path);
} else {
launchUrl(
Uri.parse(path),
mode: LaunchMode.externalApplication,
);
}
}
_onTap(String operation, TUIChatSeparateViewModel model) async { _onTap(String operation, TUIChatSeparateViewModel model) async {
final messageItem = widget.message; final messageItem = widget.message;
final msgID = messageItem.msgID as String; final msgID = messageItem.msgID as String;
switch (operation) { switch (operation) {
case "open": case "open":
if (PlatformUtils().isDesktop) { if (widget.message.fileElem != null) {
final String savePath = _onOpenDesktop(widget.message.fileElem!.localUrl ??
TencentUtils.checkString(widget.message.fileElem!.localUrl) ?? widget.message.fileElem?.path ??
globalModal.getFileMessageLocation(widget.message.msgID); "");
launchUrl(Uri.file(savePath)); } else if (widget.message.imageElem != null) {
} else { _onOpenDesktop(widget.message.imageElem!.imageList?[0]?.localUrl ??
if (PlatformUtils().isWindows) { widget.message.imageElem?.path ??
OpenFile.open(widget.message.fileElem?.path ?? ""); "");
} else { } else if (widget.message.videoElem != null) {
launchUrl( _onOpenDesktop(widget.message.videoElem!.localVideoUrl ??
Uri.parse(widget.message.fileElem?.path ?? ""), widget.message.videoElem?.videoPath ??
mode: LaunchMode.externalApplication, "");
);
}
} }
break; break;
case "finder": case "finder":
final String savePath = String savePath = "";
TencentUtils.checkString(widget.message.fileElem!.localUrl) ?? if (widget.message.fileElem != null) {
globalModal.getFileMessageLocation(widget.message.msgID); savePath = (widget.message.fileElem!.localUrl ??
final String fileDir = path.dirname(savePath); widget.message.fileElem?.path ??
if (PlatformUtils().isWindows) { "");
OpenFile.open(fileDir); } else if (widget.message.imageElem != null) {
} else { savePath = (widget.message.imageElem!.imageList?[0]?.localUrl ??
launchUrl(Uri.file(fileDir)); widget.message.imageElem?.path ??
"");
} else if (widget.message.videoElem != null) {
savePath = (widget.message.videoElem!.localVideoUrl ??
widget.message.videoElem?.videoPath ??
"");
} }
final String fileDir = path.dirname(savePath);
_onOpenDesktop(fileDir);
break; break;
case "delete": case "delete":
model.deleteMsg(msgID, webMessageInstance: messageItem.messageFromWeb); model.deleteMsg(msgID, webMessageInstance: messageItem.messageFromWeb);
@ -423,12 +467,18 @@ class TIMUIKitMessageTooltipState
break; break;
case "replyMessage": case "replyMessage":
model.repliedMessage = widget.message; model.repliedMessage = widget.message;
if (widget.allowAtUserWhenReply && final isSelf = widget.message.isSelf ?? true;
widget.onLongPressForOthersHeadPortrait != null && final isGroup =
!(widget.message.isSelf ?? true)) { TencentUtils.checkString(widget.message.groupID) != null;
widget.onLongPressForOthersHeadPortrait!( final isAtWhenReply = !isSelf &&
widget.message.sender, widget.message.nickName); isGroup &&
} widget.allowAtUserWhenReply &&
widget.onLongPressForOthersHeadPortrait != null;
/// If replying to a self message, do not add a at tag, only requestFocus.
widget.onLongPressForOthersHeadPortrait!(
!isAtWhenReply ? null : widget.message.sender,
!isAtWhenReply ? null : widget.message.nickName);
break; break;
default: default:
onTIMCallback(TIMCallback( onTIMCallback(TIMCallback(

View File

@ -51,8 +51,13 @@ class TIMUIKitHistoryMessageListContainer extends StatefulWidget {
/// conversation type /// conversation type
final ConvType conversationType; final ConvType conversationType;
/// Avatar and name in message reaction tap callback.
final void Function(String userID, TapDownDetails tapDetails)? onTapAvatar; final void Function(String userID, TapDownDetails tapDetails)? onTapAvatar;
/// Avatar and name in message reaction secondary tap callback.
final void Function(String userID, TapDownDetails tapDetails)?
onSecondaryTapAvatar;
@Deprecated( @Deprecated(
"Nickname will not show in one-to-one chat, if you tend to control it in group chat, please use `isShowSelfNameInGroup` and `isShowOthersNameInGroup` from `config: TIMUIKitChatConfig` instead") "Nickname will not show in one-to-one chat, if you tend to control it in group chat, please use `isShowSelfNameInGroup` and `isShowOthersNameInGroup` from `config: TIMUIKitChatConfig` instead")
final bool showNickName; final bool showNickName;
@ -85,8 +90,9 @@ class TIMUIKitHistoryMessageListContainer extends StatefulWidget {
this.extraTipsActionItemBuilder, this.extraTipsActionItemBuilder,
this.isAllowScroll = true, this.isAllowScroll = true,
this.onTapAvatar, this.onTapAvatar,
@Deprecated("Nickname will not show in one-to-one chat, if you tend to control it in group chat, please use `isShowSelfNameInGroup` and `isShowOthersNameInGroup` from `config: TIMUIKitChatConfig` instead") @Deprecated(
this.showNickName = true, "Nickname will not show in one-to-one chat, if you tend to control it in group chat, please use `isShowSelfNameInGroup` and `isShowOthersNameInGroup` from `config: TIMUIKitChatConfig` instead")
this.showNickName = true,
this.initFindingMsg, this.initFindingMsg,
this.mainHistoryListConfig, this.mainHistoryListConfig,
this.toolTipsConfig, this.toolTipsConfig,
@ -94,6 +100,7 @@ class TIMUIKitHistoryMessageListContainer extends StatefulWidget {
this.customEmojiStickerList = const [], this.customEmojiStickerList = const [],
this.textFieldController, this.textFieldController,
required this.conversation, required this.conversation,
this.onSecondaryTapAvatar,
}) : super(key: key); }) : super(key: key);
@override @override
@ -169,6 +176,7 @@ class _TIMUIKitHistoryMessageListContainerState
widget.extraTipsActionItemBuilder), widget.extraTipsActionItemBuilder),
message: message!, message: message!,
showAvatar: chatConfig.isShowAvatar, showAvatar: chatConfig.isShowAvatar,
onSecondaryTapForOthersPortrait: widget.onSecondaryTapAvatar,
onTapForOthersPortrait: widget.onTapAvatar, onTapForOthersPortrait: widget.onTapAvatar,
messageItemBuilder: widget.messageItemBuilder, messageItemBuilder: widget.messageItemBuilder,
onLongPressForOthersHeadPortrait: onLongPressForOthersHeadPortrait:

View File

@ -80,9 +80,8 @@ class _TIMUIKitAppBarState extends TIMUIKitState<TIMUIKitAppBar> {
changedInfo.userProfile?.userID) ?? changedInfo.userProfile?.userID) ??
""; "";
} }
// ignore: empty_catches // ignore: empty_catches
} catch (e) { } catch (e) {}
}
}, },
); );
if (_friendshipListener != null) { if (_friendshipListener != null) {
@ -103,9 +102,8 @@ class _TIMUIKitAppBarState extends TIMUIKitState<TIMUIKitAppBar> {
setState(() {}); setState(() {});
} }
} }
// ignore: empty_catches // ignore: empty_catches
} catch (e) { } catch (e) {}
}
}, },
); );
if (_groupListener != null) { if (_groupListener != null) {
@ -161,9 +159,8 @@ class _TIMUIKitAppBarState extends TIMUIKitState<TIMUIKitAppBar> {
""; "";
}); });
} }
// ignore: empty_catches // ignore: empty_catches
} catch (e) { } catch (e) {}
}
} }
@override @override
@ -193,7 +190,8 @@ class _TIMUIKitAppBarState extends TIMUIKitState<TIMUIKitAppBar> {
titleTextStyle: setAppbar?.titleTextStyle, titleTextStyle: setAppbar?.titleTextStyle,
toolbarOpacity: setAppbar?.toolbarOpacity ?? 1.0, toolbarOpacity: setAppbar?.toolbarOpacity ?? 1.0,
toolbarTextStyle: setAppbar?.toolbarTextStyle, toolbarTextStyle: setAppbar?.toolbarTextStyle,
textTheme: setAppbar?.textTheme,
// textTheme: setAppbar?.textTheme,
iconTheme: setAppbar?.iconTheme ?? iconTheme: setAppbar?.iconTheme ??
const IconThemeData( const IconThemeData(
color: Colors.white, color: Colors.white,
@ -201,10 +199,8 @@ class _TIMUIKitAppBarState extends TIMUIKitState<TIMUIKitAppBar> {
title: TIMUIKitAppBarTitle( title: TIMUIKitAppBarTitle(
title: setAppbar?.title, title: setAppbar?.title,
onClick: widget.onClickTitle, onClick: widget.onClickTitle,
textStyle: setAppbar?.textTheme?.titleMedium ?? textStyle: TextStyle(
TextStyle( color: theme.appbarTextColor ?? hexToColor("010000"), fontSize: 16),
color: theme.appbarTextColor ?? hexToColor("010000"),
fontSize: 16),
conversationShowName: _conversationShowName, conversationShowName: _conversationShowName,
showC2cMessageEditStatus: widget.showC2cMessageEditStatus, showC2cMessageEditStatus: widget.showC2cMessageEditStatus,
fromUser: widget.conversationID, fromUser: widget.conversationID,
@ -222,12 +218,10 @@ class _TIMUIKitAppBarState extends TIMUIKitState<TIMUIKitAppBar> {
}, },
child: Text( child: Text(
TIM_t('取消'), TIM_t('取消'),
style: setAppbar?.textTheme?.titleMedium ?? style: TextStyle(
TextStyle( color: theme.appbarTextColor ?? hexToColor("010000"),
color: fontSize: 16,
theme.appbarTextColor ?? hexToColor("010000"), ),
fontSize: 16,
),
), ),
) )
: setAppbar?.leading ?? : setAppbar?.leading ??
@ -240,10 +234,7 @@ class _TIMUIKitAppBarState extends TIMUIKitState<TIMUIKitAppBar> {
constraints: const BoxConstraints(), constraints: const BoxConstraints(),
icon: Icon( icon: Icon(
Icons.arrow_back_ios, Icons.arrow_back_ios,
color: setAppbar color: hexToColor("010000"),
?.textTheme?.titleMedium?.color ??
theme.appbarTextColor ??
hexToColor("010000"),
size: 17, size: 17,
), ),
onPressed: () async { onPressed: () async {

View File

@ -8,18 +8,18 @@ import 'package:flutter/material.dart';
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart'; import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/permission.dart'; import 'package:tencent_cloud_chat_uikit/ui/utils/permission.dart';
import 'package:tencent_open_file/tencent_open_file.dart'; import 'package:tencent_open_file/tencent_open_file.dart';
import 'package:provider/provider.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart'; import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart'; import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/separate_models/tui_chat_separate_view_model.dart'; import 'package:tencent_cloud_chat_uikit/business_logic/separate_models/tui_chat_separate_view_model.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_chat_global_model.dart'; import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_chat_global_model.dart';
import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart'; import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart';
import 'package:universal_html/html.dart' as html;
import 'package:tencent_cloud_chat_uikit/ui/utils/platform.dart'; import 'package:tencent_cloud_chat_uikit/ui/utils/platform.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitMessageItem/TIMUIKitMessageReaction/tim_uikit_message_reaction_wrapper.dart'; import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitMessageItem/TIMUIKitMessageReaction/tim_uikit_message_reaction_wrapper.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitMessageItem/tim_uikit_chat_file_icon.dart'; import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitMessageItem/tim_uikit_chat_file_icon.dart';
import 'package:tencent_cloud_chat_uikit/ui/widgets/textSize.dart'; import 'package:tencent_cloud_chat_uikit/ui/widgets/textSize.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import 'package:http/http.dart' as http;
class TIMUIKitFileElem extends StatefulWidget { class TIMUIKitFileElem extends StatefulWidget {
final String? messageID; final String? messageID;
@ -51,6 +51,16 @@ class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
String filePath = ""; String filePath = "";
bool isDownloading = false; bool isDownloading = false;
final TUIChatGlobalModel model = serviceLocator<TUIChatGlobalModel>(); final TUIChatGlobalModel model = serviceLocator<TUIChatGlobalModel>();
int downloadProgress = 0;
late V2TimAdvancedMsgListener advancedMsgListener;
@override
void dispose() {
TencentImSDKPlugin.v2TIMManager
.getMessageManager()
.removeAdvancedMsgListener(listener: advancedMsgListener);
super.dispose();
}
@override @override
void initState() { void initState() {
@ -60,6 +70,32 @@ class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
hasFile(); hasFile();
}); });
} }
advancedMsgListener = V2TimAdvancedMsgListener(
onMessageDownloadProgressCallback:
(V2TimMessageDownloadProgress messageProgress) async {
if (messageProgress.msgID == widget.message.msgID) {
if (messageProgress.isFinish) {
if(mounted){
setState(() {
downloadProgress = 100;
});
}
} else {
if(mounted){
setState(() {
downloadProgress = (messageProgress.currentSize /
messageProgress.totalSize *
100)
.ceil();
});
}
}
}
},
);
TencentImSDKPlugin.v2TIMManager
.getMessageManager()
.addAdvancedMsgListener(listener: advancedMsgListener);
} }
Future<String> getSavePath() async { Future<String> getSavePath() async {
@ -75,12 +111,17 @@ class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
return true; return true;
} }
if (model.getMessageProgress(widget.messageID) == 100) { if (model.getMessageProgress(widget.messageID) == 100 ||
String savePath = TencentUtils.checkString(widget.message.fileElem!.localUrl) ?? downloadProgress == 100) {
model.getFileMessageLocation(widget.messageID); String savePath =
TencentUtils.checkString(widget.message.fileElem!.localUrl) ??
model.getFileMessageLocation(widget.messageID);
File f = File(savePath); File f = File(savePath);
if (f.existsSync() && widget.messageID != null) { if (f.existsSync() && widget.messageID != null) {
filePath = savePath; filePath = savePath;
setState(() {
downloadProgress = 100;
});
return true; return true;
} }
return false; return false;
@ -89,6 +130,9 @@ class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
File f = File(savePath); File f = File(savePath);
if (f.existsSync() && widget.messageID != null) { if (f.existsSync() && widget.messageID != null) {
filePath = savePath; filePath = savePath;
setState(() {
downloadProgress = 100;
});
model.setMessageProgress(widget.messageID!, 100); model.setMessageProgress(widget.messageID!, 100);
return true; return true;
} }
@ -108,7 +152,7 @@ class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
} }
addUrlToWaitingPath() async { addUrlToWaitingPath() async {
if(widget.messageID !=null ){ if (widget.messageID != null) {
model.addWaitingList(widget.messageID!); model.addWaitingList(widget.messageID!);
print("add path success"); print("add path success");
} }
@ -127,7 +171,7 @@ class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
} }
downloadFile(TUITheme theme) async { downloadFile(TUITheme theme) async {
if(PlatformUtils().isMobile){ if (PlatformUtils().isMobile) {
if (PlatformUtils().isIOS) { if (PlatformUtils().isIOS) {
if (!await Permissions.checkPermission( if (!await Permissions.checkPermission(
context, Permission.photosAddOnly.value, theme, false)) { context, Permission.photosAddOnly.value, theme, false)) {
@ -136,12 +180,13 @@ class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
} else { } else {
final DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); final DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo; AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
if ((androidInfo.version.sdkInt ?? 0) >= 33) { if ((androidInfo.version.sdkInt) >= 33) {
} else { } else {
var storage = await Permissions.checkPermission( var storage = await Permissions.checkPermission(
context, Permission.storage.value, context,
Permission.storage.value,
); );
if(!storage){ if (!storage) {
return; return;
} }
} }
@ -151,7 +196,7 @@ class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
} }
tryOpenFile(context, theme) async { tryOpenFile(context, theme) async {
if(PlatformUtils().isMobile){ if (PlatformUtils().isMobile) {
if (PlatformUtils().isIOS) { if (PlatformUtils().isIOS) {
if (!await Permissions.checkPermission( if (!await Permissions.checkPermission(
context, Permission.photosAddOnly.value, theme!, false)) { context, Permission.photosAddOnly.value, theme!, false)) {
@ -160,12 +205,13 @@ class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
} else { } else {
final DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); final DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo; AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
if ((androidInfo.version.sdkInt ?? 0) >= 33) { if ((androidInfo.version.sdkInt) >= 33) {
} else { } else {
var storage = await Permissions.checkPermission( var storage = await Permissions.checkPermission(
context, Permission.storage.value, context,
Permission.storage.value,
); );
if(!storage){ if (!storage) {
return; return;
} }
} }
@ -173,19 +219,67 @@ class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
} }
try { try {
if(PlatformUtils().isDesktop && !PlatformUtils().isWindows){ if (PlatformUtils().isDesktop && !PlatformUtils().isWindows) {
launchUrl(Uri.file(filePath)); launchUrl(Uri.file(filePath));
}else{ } else {
OpenFile.open(filePath); OpenFile.open(filePath);
} }
// ignore: empty_catches // ignore: empty_catches
} catch (e) {}
}
void downloadWebFile(String fileUrl) async {
String fileName = Uri.parse(fileUrl).pathSegments.last;
try {
http.Response response = await http.get(
Uri.parse(fileUrl),
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
);
final html.AnchorElement downloadAnchor =
html.document.createElement('a') as html.AnchorElement;
final html.Blob blob = html.Blob([response.bodyBytes]);
downloadAnchor.href = html.Url.createObjectUrlFromBlob(blob);
downloadAnchor.download = widget.message.fileElem?.fileName ?? fileName;
downloadAnchor.click();
} catch (e) { } catch (e) {
html.AnchorElement(
href: widget.fileElem?.path ?? "",
)
..setAttribute(
"download", widget.message.fileElem?.fileName ?? fileName)
..setAttribute("target", '_blank')
..style.display = "none"
..click();
} }
} }
@override @override
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) { Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
final theme = value.theme; final theme = value.theme;
final received = downloadProgress;
final fileName = widget.fileElem!.fileName ?? "";
final fileSize = widget.fileElem!.fileSize;
final borderRadius = widget.isSelf
? const BorderRadius.only(
topLeft: Radius.circular(10),
topRight: Radius.circular(2),
bottomLeft: Radius.circular(10),
bottomRight: Radius.circular(10))
: const BorderRadius.only(
topLeft: Radius.circular(2),
topRight: Radius.circular(10),
bottomLeft: Radius.circular(10),
bottomRight: Radius.circular(10));
String? fileFormat;
if (widget.fileElem?.fileName != null &&
widget.fileElem!.fileName!.isNotEmpty) {
final String fileName = widget.fileElem!.fileName!;
fileFormat = fileName.split(".")[max(fileName.split(".").length - 1, 0)];
}
return TIMUIKitMessageReactionWrapper( return TIMUIKitMessageReactionWrapper(
chatModel: widget.chatModel, chatModel: widget.chatModel,
isShowJump: widget.isShowJump, isShowJump: widget.isShowJump,
@ -193,135 +287,104 @@ class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
isFromSelf: widget.message.isSelf ?? true, isFromSelf: widget.message.isSelf ?? true,
isShowMessageReaction: widget.isShowMessageReaction ?? true, isShowMessageReaction: widget.isShowMessageReaction ?? true,
message: widget.message, message: widget.message,
child: ChangeNotifierProvider.value( child: GestureDetector(
value: model, onTap: () async {
child: if (PlatformUtils().isWeb) {
Consumer<TUIChatGlobalModel>(builder: (context, value, child) { downloadWebFile(widget.fileElem?.path ?? "");
final received = value.getMessageProgress(widget.messageID); return;
final fileName = widget.fileElem!.fileName ?? ""; }
final fileSize = widget.fileElem!.fileSize; if (await hasFile()) {
final borderRadius = widget.isSelf if (received == 100) {
? const BorderRadius.only( tryOpenFile(context, theme);
topLeft: Radius.circular(10), } else {
topRight: Radius.circular(2), //
bottomLeft: Radius.circular(10), onTIMCallback(
bottomRight: Radius.circular(10)) TIMCallback(
: const BorderRadius.only( type: TIMCallbackType.INFO,
topLeft: Radius.circular(2), infoRecommendText: TIM_t("正在下载中"),
topRight: Radius.circular(10), infoCode: 6660411,
bottomLeft: Radius.circular(10), ),
bottomRight: Radius.circular(10)); );
String? fileFormat; }
if (widget.fileElem?.fileName != null && return;
widget.fileElem!.fileName!.isNotEmpty) {
final String fileName = widget.fileElem!.fileName!;
fileFormat =
fileName.split(".")[max(fileName.split(".").length - 1, 0)];
} }
return InkWell(
onTap: () async {
if (PlatformUtils().isWeb) {
launchUrl(
Uri.parse(widget.fileElem?.path ?? ""),
mode: LaunchMode.externalApplication,
);
return;
}
if (await hasFile()) {
if (received == 100) {
tryOpenFile(context, theme);
} else {
//
onTIMCallback(
TIMCallback(
type: TIMCallbackType.INFO,
infoRecommendText: TIM_t("正在下载中"),
infoCode: 6660411,
),
);
}
return;
}
if (checkIsWaiting()) { if (checkIsWaiting()) {
onTIMCallback( onTIMCallback(
TIMCallback( TIMCallback(
type: TIMCallbackType.INFO, type: TIMCallbackType.INFO,
infoRecommendText: TIM_t("已加入待下载队列,其他文件下载中"), infoRecommendText: TIM_t("已加入待下载队列,其他文件下载中"),
infoCode: 6660413), infoCode: 6660413),
); );
return; return;
} else { } else {
await addUrlToWaitingPath(); await addUrlToWaitingPath();
} }
await downloadFile(theme); await downloadFile(theme);
}, },
child: Container( child: Container(
width: 237, width: 237,
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all( border: Border.all(
color: theme.weakDividerColor ?? color:
CommonColor.weakDividerColor, theme.weakDividerColor ?? CommonColor.weakDividerColor,
), ),
borderRadius: borderRadius), borderRadius: borderRadius),
child: Stack(children: [ child: Stack(children: [
ClipRRect( ClipRRect(
// //
borderRadius: borderRadius, borderRadius: borderRadius,
child: LinearProgressIndicator( child: LinearProgressIndicator(
minHeight: 66, minHeight: 66,
value: (received == 100 ? 0 : received) / 100, value: (received == 100 ? 0 : received) / 100,
backgroundColor: received == 100 backgroundColor: received == 100
? theme.weakBackgroundColor ? theme.weakBackgroundColor
: Colors.white, : Colors.white,
valueColor: AlwaysStoppedAnimation( valueColor: AlwaysStoppedAnimation(
theme.lightPrimaryMaterialColor.shade50)), theme.lightPrimaryMaterialColor.shade50)),
), ),
Padding( Padding(
padding: const EdgeInsets.symmetric( padding:
vertical: 8, horizontal: 12), const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
child: Row( child: Row(
mainAxisAlignment: widget.isSelf mainAxisAlignment: widget.isSelf
? MainAxisAlignment.end ? MainAxisAlignment.end
: MainAxisAlignment.start, : MainAxisAlignment.start,
children: [ children: [
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Container( Container(
constraints: constraints:
const BoxConstraints(maxWidth: 160), const BoxConstraints(maxWidth: 160),
child: LayoutBuilder( child: LayoutBuilder(
builder: builder: (buildContext, boxConstraints) {
(buildContext, boxConstraints) { return CustomText(
return CustomText( fileName,
fileName, width: boxConstraints.maxWidth,
width: boxConstraints.maxWidth, style: TextStyle(
style: TextStyle( color: theme.darkTextColor,
color: theme.darkTextColor, fontSize: 16,
fontSize: 16,
),
);
},
), ),
), );
if (fileSize != null) },
Text(
showFileSize(fileSize),
// "${received > 0 ? (received / 1024).ceil() : (received / 1024).ceil()} KB",
style: TextStyle(
fontSize: 14,
color: theme.weakTextColor),
)
],
)),
TIMUIKitFileIcon(
fileFormat: fileFormat,
), ),
])), ),
]), if (fileSize != null)
)); Text(
}))); showFileSize(fileSize),
// "${received > 0 ? (received / 1024).ceil() : (received / 1024).ceil()} KB",
style: TextStyle(
fontSize: 14, color: theme.weakTextColor),
)
],
)),
TIMUIKitFileIcon(
fileFormat: fileFormat,
),
])),
]),
)));
} }
} }

View File

@ -56,6 +56,7 @@ class TIMUIKitImageElem extends StatefulWidget {
} }
class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> { class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
final TUIChatGlobalModel globalModel = serviceLocator<TUIChatGlobalModel>();
double? networkImagePositionRadio; // double? networkImagePositionRadio; //
final TUIChatGlobalModel model = serviceLocator<TUIChatGlobalModel>(); final TUIChatGlobalModel model = serviceLocator<TUIChatGlobalModel>();
final MessageService _messageService = serviceLocator<MessageService>(); final MessageService _messageService = serviceLocator<MessageService>();
@ -140,7 +141,7 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
final DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); final DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo; AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
if (PlatformUtils().isMobile) { if (PlatformUtils().isMobile) {
if ((androidInfo.version.sdkInt ?? 0) >= 33) { if ((androidInfo.version.sdkInt) >= 33) {
final photos = await Permissions.checkPermission( final photos = await Permissions.checkPermission(
context, context,
Permission.photos.value, Permission.photos.value,
@ -309,137 +310,6 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
child: errorDisplay(context, theme), child: errorDisplay(context, theme),
)); ));
Widget _renderLocalImage(String smallImage, dynamic heroTag,
double? positionRadio, TUITheme? theme, String? originImage) {
double? currentPositionRadio = positionRadio;
File imgF = File(smallImage);
bool isExist = imgF.existsSync();
if (!isExist) {
return errorDisplay(context, theme);
}
Image image = Image.file(imgF);
String showImage = (originImage != null && File(originImage).existsSync())
? originImage
: smallImage;
image.image
.resolve(const ImageConfiguration())
.addListener(ImageStreamListener((image, synchronousCall) {
if (image.image.width != 0 && image.image.height != 0) {
currentPositionRadio = image.image.width / image.image.height;
}
}));
final message = widget.message;
final preloadImage = model.preloadImageMap[
message.seq! + message.timestamp.toString() + (message.msgID ?? "")];
final isDesktopScreen =
TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop;
return Stack(
alignment: AlignmentDirectional.topStart,
children: [
if (!isDesktopScreen && currentPositionRadio != null)
AspectRatio(
aspectRatio: currentPositionRadio!,
child: Container(
decoration: const BoxDecoration(color: Colors.transparent),
),
),
getImage(
InkWell(
onTap: () {
if (PlatformUtils().isDesktop) {
if(PlatformUtils().isWindows){
OpenFile.open(showImage);
} else{
launchUrl(Uri.file(showImage));
}
} else {
Navigator.of(context).push(
PageRouteBuilder(
opaque: false, // set to false
pageBuilder: (_, __, ___) => ImageScreen(
imageProvider: FileImage(File(showImage)),
heroTag: heroTag,
messageID: widget.message.msgID,
downloadFn: () async {
return await _saveImg(theme!);
}),
),
);
}
},
child: Container(
constraints:
const BoxConstraints(minWidth: 20, minHeight: 20),
child: Hero(
tag: heroTag,
child: preloadImage != null
? RawImage(
image: preloadImage,
fit: BoxFit.contain,
)
: Image.file(
File(smallImage),
fit: BoxFit.contain,
),
),
)),
imageElem: null)
],
);
}
@override
void initState() {
super.initState();
if (!PlatformUtils().isWeb) {
if ((widget.message.msgID != null && widget.message.msgID != '') &&
(widget.message.imageElem!.imageList![0]!.localUrl == null ||
widget.message.imageElem!.imageList![0]!.localUrl!.isEmpty)) {
_messageService.downloadMessage(
msgID: widget.message.msgID!,
messageType: 3,
imageType: 0,
isSnapshot: false);
_messageService.downloadMessage(
msgID: widget.message.msgID!,
messageType: 3,
imageType: 1,
isSnapshot: false);
_messageService.downloadMessage(
msgID: widget.message.msgID!,
messageType: 3,
imageType: 2,
isSnapshot: false);
}
}
// feature
// setOnlineImageRatio();
}
void setOnlineImageRatio() {
if (networkImagePositionRadio == null) {
V2TimImage? smallImg = getImageFromList(V2TimImageTypesEnum.small);
V2TimImage? originalImg = getImageFromList(V2TimImageTypesEnum.original);
Image image = Image.network(smallImg?.url ?? originalImg?.url ?? "");
image.image
.resolve(const ImageConfiguration())
.addListener(ImageStreamListener((ImageInfo info, bool _) {
if (info.image.width != 0 && info.image.height != 0) {
setState(() {
networkImagePositionRadio = (info.image.width / info.image.height);
});
}
}));
}
}
Widget _renderNetworkImage( Widget _renderNetworkImage(
dynamic heroTag, double? positionRadio, TUITheme? theme, dynamic heroTag, double? positionRadio, TUITheme? theme,
{String? path, V2TimImage? originalImg, V2TimImage? smallImg}) { {String? path, V2TimImage? originalImg, V2TimImage? smallImg}) {
@ -458,12 +328,32 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
getImage( getImage(
GestureDetector( GestureDetector(
onTap: () { onTap: () {
if (PlatformUtils().isWeb) {
launchUrl(
Uri.parse(widget.message.imageElem?.path ?? ""),
mode: LaunchMode.externalApplication,
);
return;
}
if (isDesktopScreen) { if (isDesktopScreen) {
onTIMCallback(TIMCallback( if (TencentUtils.checkString(widget
infoCode: 6660414, .message.imageElem!.imageList![0]!.localUrl) !=
infoRecommendText: TIM_t("正在下载中"), null &&
type: TIMCallbackType.INFO File(widget.message.imageElem!.imageList![0]!.localUrl!)
)); .existsSync()) {
if (PlatformUtils().isWindows) {
OpenFile.open(
widget.message.imageElem!.imageList![0]!.localUrl);
} else {
launchUrl(Uri.file(widget
.message.imageElem!.imageList![0]!.localUrl!));
}
}else{
onTIMCallback(TIMCallback(
infoCode: 6660414,
infoRecommendText: TIM_t("正在下载中"),
type: TIMCallbackType.INFO));
}
} else { } else {
Navigator.of(context).push( Navigator.of(context).push(
PageRouteBuilder( PageRouteBuilder(
@ -516,6 +406,152 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
} }
} }
Widget _renderLocalImage(String smallImage, dynamic heroTag,
double? positionRadio, TUITheme? theme, String? originImage) {
double? currentPositionRadio = positionRadio;
File imgF = File(smallImage);
bool isExist = imgF.existsSync();
if (!isExist) {
return errorDisplay(context, theme);
}
Image image = Image.file(imgF);
String showImage = (originImage != null && File(originImage).existsSync())
? originImage
: smallImage;
image.image
.resolve(const ImageConfiguration())
.addListener(ImageStreamListener((image, synchronousCall) {
if (image.image.width != 0 && image.image.height != 0) {
currentPositionRadio = image.image.width / image.image.height;
}
}));
final message = widget.message;
final preloadImage = model.preloadImageMap[
message.seq! + message.timestamp.toString() + (message.msgID ?? "")];
final isDesktopScreen =
TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop;
return Stack(
alignment: AlignmentDirectional.topStart,
children: [
if (!isDesktopScreen && currentPositionRadio != null)
AspectRatio(
aspectRatio: currentPositionRadio!,
child: Container(
decoration: const BoxDecoration(color: Colors.transparent),
),
),
getImage(
GestureDetector(
onTap: () {
if (PlatformUtils().isDesktop) {
if (PlatformUtils().isWindows) {
OpenFile.open(showImage);
} else {
launchUrl(Uri.file(showImage));
}
} else {
Navigator.of(context).push(
PageRouteBuilder(
opaque: false, // set to false
pageBuilder: (_, __, ___) => ImageScreen(
imageProvider: FileImage(File(showImage)),
heroTag: heroTag,
messageID: widget.message.msgID,
downloadFn: () async {
return await _saveImg(theme!);
}),
),
);
}
},
child: Container(
constraints:
const BoxConstraints(minWidth: 20, minHeight: 20),
child: Hero(
tag: heroTag,
child: preloadImage != null
? RawImage(
image: preloadImage,
fit: BoxFit.contain,
)
: Image.file(
File(smallImage),
fit: BoxFit.contain,
),
),
)),
imageElem: null)
],
);
}
@override
void initState() {
super.initState();
if (!PlatformUtils().isWeb &&
TencentUtils.checkString(widget.message.msgID) != null) {
if (TencentUtils.checkString(
widget.message.imageElem!.imageList![0]!.localUrl) ==
null ||
!File(widget.message.imageElem!.imageList![0]!.localUrl!)
.existsSync()) {
_messageService.downloadMessage(
msgID: widget.message.msgID!,
messageType: 3,
imageType: 0,
isSnapshot: false);
}
if (TencentUtils.checkString(
widget.message.imageElem!.imageList![1]!.localUrl) ==
null ||
!File(widget.message.imageElem!.imageList![1]!.localUrl!)
.existsSync()) {
_messageService.downloadMessage(
msgID: widget.message.msgID!,
messageType: 3,
imageType: 1,
isSnapshot: false);
}
if (TencentUtils.checkString(
widget.message.imageElem!.imageList![2]!.localUrl) ==
null ||
!File(widget.message.imageElem!.imageList![2]!.localUrl!)
.existsSync()) {
_messageService.downloadMessage(
msgID: widget.message.msgID!,
messageType: 3,
imageType: 2,
isSnapshot: false);
}
}
// feature
// setOnlineImageRatio();
}
void setOnlineImageRatio() {
if (networkImagePositionRadio == null) {
V2TimImage? smallImg = getImageFromList(V2TimImageTypesEnum.small);
V2TimImage? originalImg = getImageFromList(V2TimImageTypesEnum.original);
Image image = Image.network(smallImg?.url ?? originalImg?.url ?? "");
image.image
.resolve(const ImageConfiguration())
.addListener(ImageStreamListener((ImageInfo info, bool _) {
if (info.image.width != 0 && info.image.height != 0) {
setState(() {
networkImagePositionRadio = (info.image.width / info.image.height);
});
}
}));
}
}
bool isNeedShowLocalPath() { bool isNeedShowLocalPath() {
final current = (DateTime.now().millisecondsSinceEpoch / 1000).ceil(); final current = (DateTime.now().millisecondsSinceEpoch / 1000).ceil();
final timeStamp = widget.message.timestamp ?? current; final timeStamp = widget.message.timestamp ?? current;
@ -572,8 +608,7 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
smallImg: smallImg, originalImg: originalImg); smallImg: smallImg, originalImg: originalImg);
} }
if ( if ((smallImg?.url ?? originalImg?.url) != null &&
(smallImg?.url ?? originalImg?.url) != null &&
(smallImg?.url ?? originalImg?.url)!.isNotEmpty) { (smallImg?.url ?? originalImg?.url)!.isNotEmpty) {
return _renderNetworkImage(heroTag, positionRadio, theme, return _renderNetworkImage(heroTag, positionRadio, theme,
smallImg: smallImg, originalImg: originalImg); smallImg: smallImg, originalImg: originalImg);

View File

@ -6,7 +6,7 @@ import 'package:flutter/material.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/common_utils.dart'; import 'package:tencent_cloud_chat_uikit/ui/utils/common_utils.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/screen_utils.dart'; import 'package:tencent_cloud_chat_uikit/ui/utils/screen_utils.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitTextField/special_text/DefaultSpecialTextSpanBuilder.dart'; import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitTextField/special_text/DefaultSpecialTextSpanBuilder.dart';
import 'package:tencent_extended_text/extended_text.dart'; import 'package:extended_text/extended_text.dart';
import 'package:tencent_im_base/tencent_im_base.dart'; import 'package:tencent_im_base/tencent_im_base.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart'; import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart'; import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart';
@ -302,7 +302,7 @@ class _TIMUIKitReplyElemState extends TIMUIKitState<TIMUIKitReplyElem> {
isUseDefaultEmoji: widget.isUseDefaultEmoji, isUseDefaultEmoji: widget.isUseDefaultEmoji,
customEmojiStickerList: widget.customEmojiStickerList, customEmojiStickerList: widget.customEmojiStickerList,
isEnableTextSelection: isEnableTextSelection:
widget.chatModel.chatConfig.isEnableTextSelection); widget.chatModel.chatConfig.isEnableTextSelection ?? false);
return Container( return Container(
padding: widget.textPadding ?? EdgeInsets.all(isDesktopScreen ? 12 : 10), padding: widget.textPadding ?? EdgeInsets.all(isDesktopScreen ? 12 : 10),
decoration: BoxDecoration( decoration: BoxDecoration(

View File

@ -212,7 +212,7 @@ class _TIMUIKitSoundElemState extends TIMUIKitState<TIMUIKitSoundElem> {
} }
} }
} }
return InkWell( return GestureDetector(
onTap: () => _playSound(), onTap: () => _playSound(),
child: Container( child: Container(
padding: widget.textPadding ?? const EdgeInsets.all(10), padding: widget.textPadding ?? const EdgeInsets.all(10),

View File

@ -2,7 +2,7 @@ import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'package:tencent_cloud_chat_uikit/ui/utils/screen_utils.dart'; import 'package:tencent_cloud_chat_uikit/ui/utils/screen_utils.dart';
import 'package:tencent_extended_text/extended_text.dart'; import 'package:extended_text/extended_text.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart'; import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart'; import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart';
@ -170,7 +170,7 @@ class _TIMUIKitTextElemState extends TIMUIKitState<TIMUIKitTextElem> {
isUseDefaultEmoji: widget.isUseDefaultEmoji, isUseDefaultEmoji: widget.isUseDefaultEmoji,
customEmojiStickerList: widget.customEmojiStickerList, customEmojiStickerList: widget.customEmojiStickerList,
isEnableTextSelection: isEnableTextSelection:
widget.chatModel.chatConfig.isEnableTextSelection); widget.chatModel.chatConfig.isEnableTextSelection ?? false);
final borderRadius = widget.isFromSelf final borderRadius = widget.isFromSelf
? const BorderRadius.only( ? const BorderRadius.only(
topLeft: Radius.circular(10), topLeft: Radius.circular(10),

View File

@ -2,7 +2,7 @@ import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'package:tencent_cloud_chat_uikit/ui/utils/screen_utils.dart'; import 'package:tencent_cloud_chat_uikit/ui/utils/screen_utils.dart';
import 'package:tencent_extended_text/extended_text.dart'; import 'package:extended_text/extended_text.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart'; import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart'; import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart';
@ -127,7 +127,7 @@ class _TIMUIKitTextTranslationElemState
isUseDefaultEmoji: widget.isUseDefaultEmoji, isUseDefaultEmoji: widget.isUseDefaultEmoji,
customEmojiStickerList: widget.customEmojiStickerList, customEmojiStickerList: widget.customEmojiStickerList,
isEnableTextSelection: isEnableTextSelection:
widget.chatModel.chatConfig.isEnableTextSelection); widget.chatModel.chatConfig.isEnableTextSelection ?? false);
return TencentUtils.checkString(translateText) != null return TencentUtils.checkString(translateText) != null
? Container( ? Container(

View File

@ -1,14 +1,12 @@
import 'dart:io'; import 'dart:io';
import 'dart:math'; import 'dart:math';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:loading_animation_widget/loading_animation_widget.dart'; import 'package:loading_animation_widget/loading_animation_widget.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart'; import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart'; import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart';
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart'; import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/separate_models/tui_chat_separate_view_model.dart'; import 'package:tencent_cloud_chat_uikit/business_logic/separate_models/tui_chat_separate_view_model.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_chat_global_model.dart';
import 'package:tencent_cloud_chat_uikit/data_services/message/message_services.dart'; import 'package:tencent_cloud_chat_uikit/data_services/message/message_services.dart';
import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart'; import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/message.dart'; import 'package:tencent_cloud_chat_uikit/ui/utils/message.dart';
@ -40,7 +38,6 @@ class TIMUIKitVideoElem extends StatefulWidget {
} }
class _TIMUIKitVideoElemState extends TIMUIKitState<TIMUIKitVideoElem> { class _TIMUIKitVideoElemState extends TIMUIKitState<TIMUIKitVideoElem> {
final TUIChatGlobalModel globalModel = serviceLocator<TUIChatGlobalModel>();
final MessageService _messageService = serviceLocator<MessageService>(); final MessageService _messageService = serviceLocator<MessageService>();
late V2TimVideoElem stateElement = widget.message.videoElem!; late V2TimVideoElem stateElement = widget.message.videoElem!;
@ -112,13 +109,13 @@ class _TIMUIKitVideoElemState extends TIMUIKitState<TIMUIKitVideoElem> {
), ),
); );
} }
return (!kIsWeb && stateElement.snapshotUrl == null || return (!PlatformUtils().isWeb && stateElement.snapshotUrl == null ||
widget.message.status == MessageStatus.V2TIM_MSG_STATUS_SENDING) widget.message.status == MessageStatus.V2TIM_MSG_STATUS_SENDING)
? (stateElement.snapshotPath!.isNotEmpty ? (stateElement.snapshotPath!.isNotEmpty
? Image.file(File(stateElement.snapshotPath!), fit: BoxFit.fitWidth) ? Image.file(File(stateElement.snapshotPath!), fit: BoxFit.fitWidth)
: Image.file(File(stateElement.localSnapshotUrl!), : Image.file(File(stateElement.localSnapshotUrl!),
fit: BoxFit.fitWidth)) fit: BoxFit.fitWidth))
: (kIsWeb || : (PlatformUtils().isWeb ||
stateElement.localSnapshotUrl == null || stateElement.localSnapshotUrl == null ||
stateElement.localSnapshotUrl == "") stateElement.localSnapshotUrl == "")
? Image.network(stateElement.snapshotUrl!, fit: BoxFit.fitWidth) ? Image.network(stateElement.snapshotUrl!, fit: BoxFit.fitWidth)
@ -127,9 +124,9 @@ class _TIMUIKitVideoElemState extends TIMUIKitState<TIMUIKitVideoElem> {
} }
downloadMessageDetailAndSave() async { downloadMessageDetailAndSave() async {
if (widget.message.msgID != null && widget.message.msgID != '') { if (TencentUtils.checkString(widget.message.msgID) != null) {
if (widget.message.videoElem!.videoUrl == null || if (TencentUtils.checkString(widget.message.videoElem!.videoUrl) ==
widget.message.videoElem!.videoUrl == '') { null) {
final response = await _messageService.getMessageOnlineUrl( final response = await _messageService.getMessageOnlineUrl(
msgID: widget.message.msgID!); msgID: widget.message.msgID!);
if (response.data != null) { if (response.data != null) {
@ -140,16 +137,19 @@ class _TIMUIKitVideoElemState extends TIMUIKitState<TIMUIKitVideoElem> {
} }
} }
if (!PlatformUtils().isWeb) { if (!PlatformUtils().isWeb) {
if (widget.message.videoElem!.localVideoUrl == null || if (TencentUtils.checkString(widget.message.videoElem!.localVideoUrl) ==
widget.message.videoElem!.localVideoUrl == '') { null ||
!File(widget.message.videoElem!.localVideoUrl!).existsSync()) {
_messageService.downloadMessage( _messageService.downloadMessage(
msgID: widget.message.msgID!, msgID: widget.message.msgID!,
messageType: 5, messageType: 5,
imageType: 0, imageType: 0,
isSnapshot: false); isSnapshot: false);
} }
if (widget.message.videoElem!.localSnapshotUrl == null || if (TencentUtils.checkString(
widget.message.videoElem!.localSnapshotUrl == '') { widget.message.videoElem!.localSnapshotUrl) ==
null ||
!File(widget.message.videoElem!.localSnapshotUrl!).existsSync()) {
_messageService.downloadMessage( _messageService.downloadMessage(
msgID: widget.message.msgID!, msgID: widget.message.msgID!,
messageType: 5, messageType: 5,
@ -172,33 +172,40 @@ class _TIMUIKitVideoElemState extends TIMUIKitState<TIMUIKitVideoElem> {
final heroTag = final heroTag =
"${widget.message.msgID ?? widget.message.id ?? widget.message.timestamp ?? DateTime.now().millisecondsSinceEpoch}${widget.isFrom}"; "${widget.message.msgID ?? widget.message.id ?? widget.message.timestamp ?? DateTime.now().millisecondsSinceEpoch}${widget.isFrom}";
return InkWell( return GestureDetector(
onTap: () { onTap: () {
if (PlatformUtils().isWeb) {
launchUrl(
Uri.parse(widget.message.videoElem?.videoPath ?? ""),
mode: LaunchMode.externalApplication,
);
return;
}
if (PlatformUtils().isDesktop) { if (PlatformUtils().isDesktop) {
final videoElem = widget.message.videoElem; final videoElem = widget.message.videoElem;
if (videoElem != null) { if (videoElem != null) {
final localVideoUrl = TencentUtils.checkString(videoElem.localVideoUrl); final localVideoUrl =
TencentUtils.checkString(videoElem.localVideoUrl);
final videoPath = TencentUtils.checkString(videoElem.videoPath); final videoPath = TencentUtils.checkString(videoElem.videoPath);
final videoUrl = videoElem.videoUrl; final videoUrl = videoElem.videoUrl;
if (localVideoUrl != null) { if (localVideoUrl != null) {
if(PlatformUtils().isWindows){ if (PlatformUtils().isWindows) {
OpenFile.open(localVideoUrl); OpenFile.open(localVideoUrl);
} else{ } else {
launchUrl(Uri.file(localVideoUrl)); launchUrl(Uri.file(localVideoUrl));
} }
} else if (videoPath != null) { } else if (videoPath != null) {
if(PlatformUtils().isWindows){ if (PlatformUtils().isWindows) {
OpenFile.open(videoPath); OpenFile.open(videoPath);
} else{ } else {
launchUrl(Uri.file(videoPath)); launchUrl(Uri.file(videoPath));
} }
} else if (TencentUtils.isTextNotEmpty(videoUrl)) { } else if (TencentUtils.isTextNotEmpty(videoUrl)) {
onTIMCallback(TIMCallback( onTIMCallback(TIMCallback(
infoCode: 6660414, infoCode: 6660414,
infoRecommendText: TIM_t("正在下载中"), infoRecommendText: TIM_t("正在下载中"),
type: TIMCallbackType.INFO type: TIMCallbackType.INFO));
));
} }
} }
} else { } else {
@ -227,15 +234,14 @@ class _TIMUIKitVideoElemState extends TIMUIKitState<TIMUIKitVideoElem> {
borderRadius: const BorderRadius.all(Radius.circular(5)), borderRadius: const BorderRadius.all(Radius.circular(5)),
child: LayoutBuilder(builder: child: LayoutBuilder(builder:
(BuildContext context, BoxConstraints constraints) { (BuildContext context, BoxConstraints constraints) {
double positionRadio = 0.56; double? positionRadio;
if (stateElement.snapshotWidth != null && if ((stateElement.snapshotWidth) != null &&
stateElement.snapshotHeight != null && stateElement.snapshotHeight != null &&
stateElement.snapshotWidth != 0 && stateElement.snapshotWidth != 0 &&
stateElement.snapshotHeight != 0) { stateElement.snapshotHeight != 0) {
positionRadio = (stateElement.snapshotWidth! / positionRadio = (stateElement.snapshotWidth! /
stateElement.snapshotHeight!); stateElement.snapshotHeight!);
} }
return ConstrainedBox( return ConstrainedBox(
constraints: BoxConstraints( constraints: BoxConstraints(
maxWidth: PlatformUtils().isWeb maxWidth: PlatformUtils().isWeb
@ -244,52 +250,49 @@ class _TIMUIKitVideoElemState extends TIMUIKitState<TIMUIKitVideoElem> {
maxHeight: min(constraints.maxHeight * 0.8, 300), maxHeight: min(constraints.maxHeight * 0.8, 300),
minHeight: 20, minHeight: 20,
minWidth: 20), minWidth: 20),
child: AspectRatio( child: Stack(
aspectRatio: positionRadio, children: <Widget>[
child: Stack( if (positionRadio != null &&
children: <Widget>[ (stateElement.snapshotUrl != null ||
if (stateElement.snapshotUrl != null || stateElement.snapshotUrl != null))
stateElement.snapshotUrl != null) AspectRatio(
AspectRatio( aspectRatio: positionRadio,
aspectRatio: positionRadio, child: Container(
child: Container( decoration: const BoxDecoration(
decoration: const BoxDecoration( color: Colors.transparent),
color: Colors.transparent),
),
), ),
Row(
children: [
Expanded(
child: generateSnapshot(theme,
stateElement.snapshotHeight ?? 100))
],
), ),
if (widget.message.status != Row(
MessageStatus children: [
.V2TIM_MSG_STATUS_SENDING && Expanded(
(stateElement.snapshotUrl != null || child: generateSnapshot(theme,
stateElement.snapshotPath != null) && stateElement.snapshotHeight ?? 100))
stateElement.videoPath != null || ],
stateElement.videoUrl != null) ),
Positioned.fill( if (widget.message.status !=
// alignment: Alignment.center, MessageStatus.V2TIM_MSG_STATUS_SENDING &&
child: Center( (stateElement.snapshotUrl != null ||
child: Image.asset('images/play.png', stateElement.snapshotPath != null) &&
package: 'tencent_cloud_chat_uikit', stateElement.videoPath != null ||
height: 64)), stateElement.videoUrl != null)
), Positioned.fill(
Positioned( // alignment: Alignment.center,
right: 10, child: Center(
bottom: 10, child: Image.asset('images/play.png',
child: Text( package: 'tencent_cloud_chat_uikit',
MessageUtils.formatVideoTime(widget height: 64)),
.message.videoElem?.duration ?? ),
0) Positioned(
.toString(), right: 10,
style: const TextStyle( bottom: 10,
color: Colors.white, fontSize: 12))), child: Text(
], MessageUtils.formatVideoTime(
), widget.message.videoElem?.duration ??
0)
.toString(),
style: const TextStyle(
color: Colors.white, fontSize: 12))),
],
)); ));
}), }),
))), ))),

View File

@ -100,6 +100,7 @@ class _AtMemberPanelState extends TIMUIKitState<AtMemberPanel> {
width: 24, width: 24,
child: Avatar( child: Avatar(
faceUrl: memberItem.faceUrl ?? "", faceUrl: memberItem.faceUrl ?? "",
type: 1,
showName: showName), showName: showName),
), ),
const SizedBox( const SizedBox(

View File

@ -1,5 +1,5 @@
import 'package:tencent_im_base/tencent_im_base.dart'; import 'package:tencent_im_base/tencent_im_base.dart';
import 'package:tencent_wechat_camera_picker/tencent_wechat_camera_picker.dart'; import 'package:wechat_camera_picker/wechat_camera_picker.dart';
class IntlCameraPickerTextDelegate extends CameraPickerTextDelegate { class IntlCameraPickerTextDelegate extends CameraPickerTextDelegate {
/// Confirm string for the confirm button. /// Confirm string for the confirm button.

View File

@ -1,7 +1,7 @@
// ignore_for_file: file_names // ignore_for_file: file_names
import 'package:tencent_extended_text_field/extended_text_field.dart'; import 'package:extended_text_field/extended_text_field.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitTextField/special_text/http_text.dart'; import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitTextField/special_text/http_text.dart';

View File

@ -1,5 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:tencent_extended_text/extended_text.dart'; import 'package:extended_text/extended_text.dart';
import 'package:tim_ui_kit_sticker_plugin/constant/emoji.dart'; import 'package:tim_ui_kit_sticker_plugin/constant/emoji.dart';
///emoji/image text ///emoji/image text

View File

@ -2,7 +2,7 @@
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:tencent_cloud_chat_uikit/ui/widgets/link_preview/common/utils.dart'; import 'package:tencent_cloud_chat_uikit/ui/widgets/link_preview/common/utils.dart';
import 'package:tencent_extended_text/extended_text.dart'; import 'package:extended_text/extended_text.dart';
class HttpText extends SpecialText { class HttpText extends SpecialText {
HttpText(TextStyle? textStyle, SpecialTextGestureTapCallback? onTap, HttpText(TextStyle? textStyle, SpecialTextGestureTapCallback? onTap,

View File

@ -20,6 +20,7 @@ class AtText extends StatefulWidget {
final Function( final Function(
V2TimGroupMemberFullInfo memberInfo, TapDownDetails? tapDetails)? V2TimGroupMemberFullInfo memberInfo, TapDownDetails? tapDetails)?
onChooseMember; onChooseMember;
final bool canAtAll;
// some Group type cant @all // some Group type cant @all
final String? groupType; final String? groupType;
@ -32,6 +33,7 @@ class AtText extends StatefulWidget {
this.groupMemberList, this.groupMemberList,
this.closeFunc, this.closeFunc,
this.onChooseMember, this.onChooseMember,
this.canAtAll = false,
}) : super(key: key); }) : super(key: key);
@override @override
@ -116,7 +118,7 @@ class _AtTextState extends TIMUIKitState<AtText> {
groupType: widget.groupType ?? "", groupType: widget.groupType ?? "",
memberList: searchMemberList ?? [], memberList: searchMemberList ?? [],
onTapMemberItem: _onTapMemberItem, onTapMemberItem: _onTapMemberItem,
canAtAll: true, canAtAll: widget.canAtAll,
canSlideDelete: false, canSlideDelete: false,
touchBottomCallBack: () { touchBottomCallBack: () {
// Get all by once, unnecessary to load more // Get all by once, unnecessary to load more
@ -130,6 +132,7 @@ class _AtTextState extends TIMUIKitState<AtText> {
} }
return TUIKitScreenUtils.getDeviceWidget( return TUIKitScreenUtils.getDeviceWidget(
context: context,
desktopWidget: mentionedMembersBody(), desktopWidget: mentionedMembersBody(),
defaultWidget: Scaffold( defaultWidget: Scaffold(
appBar: AppBar( appBar: AppBar(
@ -137,8 +140,7 @@ class _AtTextState extends TIMUIKitState<AtText> {
iconTheme: IconThemeData( iconTheme: IconThemeData(
color: theme.appbarTextColor, color: theme.appbarTextColor,
), ),
backgroundColor: theme.appbarBgColor ?? backgroundColor: theme.appbarBgColor ?? theme.primaryColor,
theme.primaryColor,
leading: Row( leading: Row(
children: [ children: [
IconButton( IconButton(

View File

@ -12,7 +12,7 @@ import 'package:path_provider/path_provider.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart'; import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_call_invite_list.dart'; import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_call_invite_list.dart';
import 'package:tencent_wechat_camera_picker/tencent_wechat_camera_picker.dart'; import 'package:wechat_camera_picker/wechat_camera_picker.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart'; import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/separate_models/tui_chat_separate_view_model.dart'; import 'package:tencent_cloud_chat_uikit/business_logic/separate_models/tui_chat_separate_view_model.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_chat_global_model.dart'; import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_chat_global_model.dart';
@ -360,7 +360,7 @@ class _MorePanelState extends TIMUIKitState<MorePanel> {
if (PlatformUtils().isMobile){ if (PlatformUtils().isMobile){
if(PlatformUtils().isAndroid){ if(PlatformUtils().isAndroid){
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo; AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
if ((androidInfo.version.sdkInt ?? 0) >= 33) { if ((androidInfo.version.sdkInt) >= 33) {
final videos = await Permissions.checkPermission( final videos = await Permissions.checkPermission(
context,Permission.videos.value, context,Permission.videos.value,
theme, theme,

View File

@ -129,7 +129,7 @@ class _SendSoundMessageState extends TIMUIKitState<SendSoundMessage> {
), ),
); );
}); });
Overlay.of(context)?.insert(overlayEntry!); Overlay.of(context).insert(overlayEntry!);
} }
} }

View File

@ -38,8 +38,6 @@ typedef CustomStickerPanel = Widget Function({
double? height, double? height,
}); });
GlobalKey<_InputTextFieldState> inputTextFieldState = GlobalKey();
class TIMUIKitInputTextField extends StatefulWidget { class TIMUIKitInputTextField extends StatefulWidget {
/// conversation id /// conversation id
final String conversationID; final String conversationID;
@ -59,7 +57,7 @@ class TIMUIKitInputTextField extends StatefulWidget {
/// hint text for textField widget /// hint text for textField widget
final String? hintText; final String? hintText;
/// config for more pannel /// config for more panel
final MorePanelConfig? morePanelConfig; final MorePanelConfig? morePanelConfig;
/// show send audio icon /// show send audio icon
@ -133,15 +131,13 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
int? currentCursor; int? currentCursor;
bool isAddingAtSearchWords = false; bool isAddingAtSearchWords = false;
double inputWidth = 900; double inputWidth = 900;
Map<String, V2TimGroupMemberFullInfo> mentionedMembersMap = {};
Map<String, V2TimGroupMemberFullInfo> memberInfoMap = {};
late TextEditingController textEditingController; late TextEditingController textEditingController;
final TUIConversationViewModel conversationModel = final TUIConversationViewModel conversationModel =
serviceLocator<TUIConversationViewModel>(); serviceLocator<TUIConversationViewModel>();
final TUISelfInfoViewModel selfModel = serviceLocator<TUISelfInfoViewModel>(); final TUISelfInfoViewModel selfModel = serviceLocator<TUISelfInfoViewModel>();
MuteStatus muteStatus = MuteStatus.none; MuteStatus muteStatus = MuteStatus.none;
bool _isComposingText = false;
int latestSendEditStatusTime = DateTime.now().millisecondsSinceEpoch; int latestSendEditStatusTime = DateTime.now().millisecondsSinceEpoch;
setCurrentCursor(int? value) { setCurrentCursor(int? value) {
@ -160,7 +156,9 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
} }
if (TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop) { if (TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop) {
focusNode.unfocus(); textEditingController.selection = TextSelection.fromPosition(TextPosition(
offset: currentCursor ?? textEditingController.text.length));
focusNode.requestFocus();
} }
} }
@ -168,18 +166,13 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
return text.replaceAll(RegExp(r'\ufeff'), ""); return text.replaceAll(RegExp(r'\ufeff'), "");
} }
getShowName(message) {
return TencentUtils.checkStringWithoutSpace(message?.friendRemark) ??
TencentUtils.checkStringWithoutSpace(message?.nickName) ??
TencentUtils.checkStringWithoutSpace(message?.userID);
}
handleSetDraftText([String? id, ConvType? convType]) async { handleSetDraftText([String? id, ConvType? convType]) async {
String convID = id ?? widget.conversationID; String convID = id ?? widget.conversationID;
String conversationID = String conversationID = convID.contains("@TOPIC#")
(convType ?? widget.conversationType) == ConvType.c2c ? convID
: ((convType ?? widget.conversationType) == ConvType.c2c
? "c2c_$convID" ? "c2c_$convID"
: "group_$convID"; : "group_$convID");
String text = textEditingController.text; String text = textEditingController.text;
String? draftText = _filterU200b(text); String? draftText = _filterU200b(text);
@ -258,7 +251,7 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
List<String> getUserIdFromMemberInfoMap() { List<String> getUserIdFromMemberInfoMap() {
List<String> userList = []; List<String> userList = [];
memberInfoMap.forEach((String key, V2TimGroupMemberFullInfo info) { mentionedMembersMap.forEach((String key, V2TimGroupMemberFullInfo info) {
userList.add(info.userID); userList.add(info.userID);
}); });
@ -278,7 +271,7 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
convType: convType, convType: convType,
atUserIDList: getUserIdFromMemberInfoMap()), atUserIDList: getUserIdFromMemberInfoMap()),
context); context);
} else if (memberInfoMap.isNotEmpty) { } else if (mentionedMembersMap.isNotEmpty) {
widget.model.sendTextAtMessage( widget.model.sendTextAtMessage(
text: text, text: text,
convType: widget.conversationType, convType: widget.conversationType,
@ -293,7 +286,7 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
textEditingController.clear(); textEditingController.clear();
currentCursor = null; currentCursor = null;
lastText = ""; lastText = "";
memberInfoMap = {}; mentionedMembersMap = {};
goDownBottom(); goDownBottom();
_handleSendEditStatus("", false); _handleSendEditStatus("", false);
@ -320,11 +313,6 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
} }
void onModelChanged() { void onModelChanged() {
if (widget.model.repliedMessage != null) {
narrowTextFieldKey.currentState?.showKeyboard = true;
focusNode.requestFocus();
_addDeleteTag();
} else {}
if (widget.model.editRevokedMsg != "") { if (widget.model.editRevokedMsg != "") {
narrowTextFieldKey.currentState?.showKeyboard = true; narrowTextFieldKey.currentState?.showKeyboard = true;
focusNode.requestFocus(); focusNode.requestFocus();
@ -336,13 +324,6 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
} }
} }
_addDeleteTag() {
final originalText = textEditingController.text;
textEditingController.text = zeroWidthSpace + originalText;
textEditingController.selection = TextSelection.fromPosition(
TextPosition(offset: textEditingController.text.length));
}
_onCursorChange() { _onCursorChange() {
final selection = textEditingController.selection; final selection = textEditingController.selection;
currentCursor = selection.baseOffset; currentCursor = selection.baseOffset;
@ -362,24 +343,32 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
} }
_longPressToAt(String? userID, String? nickName) { _longPressToAt(String? userID, String? nickName) {
final memberInfo = V2TimGroupMemberFullInfo( if (TencentUtils.checkString(userID) == null) {
userID: userID ?? "", focusNode.requestFocus();
nickName: nickName, } else {
); final memberInfo = widget.model.groupMemberList
final showName = _getShowName(memberInfo); ?.firstWhere((element) => element?.userID == userID) ??
memberInfoMap["@$showName"] = memberInfo; V2TimGroupMemberFullInfo(
String text = "${textEditingController.text}@$showName "; userID: userID ?? "",
//please do not delete space nickName: nickName,
focusNode.requestFocus(); );
textEditingController.text = text; final showName = _getShowName(memberInfo);
textEditingController.selection = mentionedMembersMap["@$showName"] = memberInfo;
TextSelection.fromPosition(TextPosition(offset: text.length - 1)); String text = "${textEditingController.text}@$showName ";
lastText = text; //please do not delete space
focusNode.requestFocus();
textEditingController.text = text;
textEditingController.selection =
TextSelection.fromPosition(TextPosition(offset: text.length));
lastText = text;
_isComposingText = false;
narrowTextFieldKey.currentState?.showKeyboard = true;
}
} }
bool shouldRemoveAtTag(String atTag, String deletedChar) { bool shouldRemoveAtTag(String atTag, String deletedChar) {
final atMemberArray = []; final atMemberArray = [];
memberInfoMap.forEach((key, value) { mentionedMembersMap.forEach((key, value) {
atMemberArray.add(key); atMemberArray.add(key);
}); });
for (String member in atMemberArray) { for (String member in atMemberArray) {
@ -403,7 +392,11 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
final Offset caretPosition = final Offset caretPosition =
textPainter.getOffsetForCaret(lastLineOffset, Rect.zero); textPainter.getOffsetForCaret(lastLineOffset, Rect.zero);
final dx = min(inputWidth - 180, caretPosition.dx + 16); final dx = min(inputWidth - 180, caretPosition.dx + 16);
final dy = max(24, 110 - caretPosition.dy).toDouble(); final dy = max(
24,
18 * widget.model.chatConfig.desktopMessageInputFieldLines -
caretPosition.dy)
.toDouble();
return Offset(dx, dy); return Offset(dx, dy);
} }
@ -417,11 +410,11 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
parseAtList.add(str); parseAtList.add(str);
} }
for (String? key in parseAtList) { for (String? key in parseAtList) {
if (key != null && memberInfoMap[key] != null) { if (key != null && mentionedMembersMap[key] != null) {
map[key] = memberInfoMap[key]!; map[key] = mentionedMembersMap[key]!;
} }
} }
memberInfoMap = map; mentionedMembersMap = map;
} }
_handleAtText(String text, TUIChatSeparateViewModel model) async { _handleAtText(String text, TUIChatSeparateViewModel model) async {
@ -464,16 +457,21 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
parseAtList.add(str); parseAtList.add(str);
} }
for (String? key in parseAtList) { for (String? key in parseAtList) {
if (key != null && memberInfoMap[key] != null) { if (key != null && mentionedMembersMap[key] != null) {
map[key] = memberInfoMap[key]!; map[key] = mentionedMembersMap[key]!;
} }
} }
memberInfoMap = map; mentionedMembersMap = map;
return; return;
} }
} }
} }
final int selfRole = widget.model.selfMemberInfo?.role ?? 0;
final bool canAtAll =
(selfRole == GroupMemberRoleType.V2TIM_GROUP_MEMBER_ROLE_ADMIN ||
selfRole == GroupMemberRoleType.V2TIM_GROUP_MEMBER_ROLE_OWNER);
if (isDesktopScreen) { if (isDesktopScreen) {
final atPlace = text.lastIndexOf("@"); final atPlace = text.lastIndexOf("@");
final keyword = text.substring(atPlace + 1); final keyword = text.substring(atPlace + 1);
@ -484,16 +482,54 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
model.atPositionY = atPosition.dy; model.atPositionY = atPosition.dy;
isAddingAtSearchWords = true; isAddingAtSearchWords = true;
} }
final List<V2TimGroupMemberFullInfo?> showAtMemberList = List<V2TimGroupMemberFullInfo> showAtMemberList = (model
(model.groupMemberList ?? []).where((element) { .groupMemberList ??
final friendRemark = element?.friendRemark ?? ""; [])
final nickName = element?.nickName ?? ""; .where((element) {
final showName = TencentUtils.checkString(friendRemark) ?? final showName = (TencentUtils.checkStringWithoutSpace(
TencentUtils.checkString(nickName) ?? element?.friendRemark) ??
TencentUtils.checkString(element?.userID) ?? TencentUtils.checkStringWithoutSpace(element?.nameCard) ??
""; TencentUtils.checkStringWithoutSpace(element?.nickName) ??
return showName.contains(keyword); TencentUtils.checkStringWithoutSpace(element?.userID) ??
}).toList(); "")
.toLowerCase();
return element != null &&
showName.contains(keyword.toLowerCase()) &&
TencentUtils.checkString(showName) != null &&
element.userID != widget.model.selfMemberInfo?.userID;
})
.whereType<V2TimGroupMemberFullInfo>()
.toList();
showAtMemberList.sort(
(V2TimGroupMemberFullInfo userA, V2TimGroupMemberFullInfo userB) {
final isUserAIsGroupAdmin = userA.role == 300;
final isUserAIsGroupOwner = userA.role == 400;
final isUserBIsGroupAdmin = userB.role == 300;
final isUserBIsGroupOwner = userB.role == 400;
final String userAName = _getShowName(userA);
final String userBName = _getShowName(userB);
if (isUserAIsGroupOwner != isUserBIsGroupOwner) {
return isUserAIsGroupOwner ? -1 : 1;
}
if (isUserAIsGroupAdmin != isUserBIsGroupAdmin) {
return isUserAIsGroupAdmin ? -1 : 1;
}
return userAName.compareTo(userBName);
});
if (canAtAll && showAtMemberList.isNotEmpty && keyword.isEmpty) {
showAtMemberList = [
V2TimGroupMemberFullInfo(
userID: "__kImSDK_MesssageAtALL__", nickName: TIM_t("所有人")),
...showAtMemberList
];
}
model.activeAtIndex = 0; model.activeAtIndex = 0;
model.showAtMemberList = showAtMemberList; model.showAtMemberList = showAtMemberList;
@ -514,12 +550,13 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
groupMemberList: model.groupMemberList, groupMemberList: model.groupMemberList,
groupInfo: model.groupInfo, groupInfo: model.groupInfo,
groupID: groupID, groupID: groupID,
canAtAll: canAtAll,
groupType: widget.groupType), groupType: widget.groupType),
), ),
); );
final showName = _getShowName(memberInfo); final showName = _getShowName(memberInfo);
if (memberInfo != null) { if (memberInfo != null) {
memberInfoMap["@$showName"] = memberInfo; mentionedMembersMap["@$showName"] = memberInfo;
textEditingController.text = "$text$showName "; textEditingController.text = "$text$showName ";
lastText = "$text$showName "; lastText = "$text$showName ";
} }
@ -547,103 +584,125 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
bool? isAddToCursorPosition = false}) { bool? isAddToCursorPosition = false}) {
if (memberInfo != null) { if (memberInfo != null) {
final String showName = _getShowName(memberInfo); final String showName = _getShowName(memberInfo);
memberInfoMap["@$showName"] = memberInfo; mentionedMembersMap["@$showName"] = memberInfo;
replaceAtTag(showName); replaceAtTag(showName);
widget.model.showAtMemberList = []; widget.model.showAtMemberList = [];
widget.model.activeAtIndex = -1; widget.model.activeAtIndex = -1;
focusNode.requestFocus();
} }
} }
KeyEventResult handleDesktopKeyEvent(FocusNode node, RawKeyEvent event) {
final activeIndex = widget.model.activeAtIndex;
final showMemberList = widget.model.showAtMemberList;
if (event.runtimeType == RawKeyDownEvent) {
if (event.physicalKey == PhysicalKeyboardKey.backspace) {
if (textEditingController.text.isEmpty && lastText.isEmpty) {
widget.model.repliedMessage = null;
return KeyEventResult.handled;
}
} else if ((event.isShiftPressed ||
event.isAltPressed ||
event.isControlPressed ||
event.isMetaPressed) &&
event.physicalKey == PhysicalKeyboardKey.enter) {
final offset = textEditingController.selection.baseOffset;
textEditingController.text =
'${lastText.substring(0, offset)}\n${lastText.substring(offset)}';
textEditingController.selection =
TextSelection.fromPosition(TextPosition(offset: offset + 1));
lastText = textEditingController.text;
return KeyEventResult.handled;
} else if (event.physicalKey == PhysicalKeyboardKey.enter) {
if (!_isComposingText) {
if (!isAddingAtSearchWords || widget.model.showAtMemberList.isEmpty) {
onSubmitted();
} else {
isAddingAtSearchWords = false;
final V2TimGroupMemberFullInfo? memberInfo =
showMemberList[activeIndex];
if (memberInfo != null) {
handleAtMember(
memberInfo: memberInfo, isAddToCursorPosition: true);
}
}
return KeyEventResult.handled;
}
}
if (event.isKeyPressed(LogicalKeyboardKey.arrowUp) &&
isAddingAtSearchWords &&
showMemberList.isNotEmpty) {
final newIndex = max(activeIndex - 1, 0);
widget.model.activeAtIndex = newIndex;
widget.atMemberPanelScroll?.scrollToIndex(newIndex,
preferPosition: AutoScrollPosition.middle);
return KeyEventResult.handled;
}
if (event.isKeyPressed(LogicalKeyboardKey.arrowDown) &&
isAddingAtSearchWords &&
showMemberList.isNotEmpty) {
final newIndex = min(activeIndex + 1, showMemberList.length - 1);
widget.model.activeAtIndex = newIndex;
widget.atMemberPanelScroll?.scrollToIndex(newIndex,
preferPosition: AutoScrollPosition.middle);
return KeyEventResult.handled;
}
}
return KeyEventResult.ignored;
}
@override @override
void initState() { void initState() {
super.initState(); super.initState();
if (PlatformUtils().isWeb || PlatformUtils().isDesktop) { if (PlatformUtils().isWeb || PlatformUtils().isDesktop) {
focusNode = FocusNode( focusNode = FocusNode(
onKey: (node, event) { onKey: (node, event) => handleDesktopKeyEvent(node, event),
final activeIndex = widget.model.activeAtIndex;
final showMemberList = widget.model.showAtMemberList;
if (event.runtimeType == RawKeyDownEvent) {
if (event.physicalKey == PhysicalKeyboardKey.backspace) {
if (textEditingController.text.isEmpty && lastText.isEmpty) {
widget.model.repliedMessage = null;
return KeyEventResult.handled;
}
} else if ((event.isShiftPressed ||
event.isAltPressed ||
event.isControlPressed ||
event.isMetaPressed) &&
event.physicalKey == PhysicalKeyboardKey.enter) {
final offset = textEditingController.selection.baseOffset;
textEditingController.text =
'${lastText.substring(0, offset)}\n${lastText.substring(offset)}';
textEditingController.selection =
TextSelection.fromPosition(TextPosition(offset: offset + 1));
lastText = textEditingController.text;
return KeyEventResult.handled;
} else if (event.physicalKey == PhysicalKeyboardKey.enter) {
if (!isAddingAtSearchWords ||
widget.model.showAtMemberList.isEmpty) {
onSubmitted();
} else {
isAddingAtSearchWords = false;
final V2TimGroupMemberFullInfo? memberInfo =
showMemberList[activeIndex];
if (memberInfo != null) {
handleAtMember(
memberInfo: memberInfo, isAddToCursorPosition: true);
}
}
return KeyEventResult.handled;
}
if (event.isKeyPressed(LogicalKeyboardKey.arrowUp) &&
isAddingAtSearchWords &&
showMemberList.isNotEmpty) {
final newIndex = max(activeIndex - 1, 0);
widget.model.activeAtIndex = newIndex;
widget.atMemberPanelScroll?.scrollToIndex(newIndex,
preferPosition: AutoScrollPosition.middle);
return KeyEventResult.handled;
}
if (event.isKeyPressed(LogicalKeyboardKey.arrowDown) &&
isAddingAtSearchWords &&
showMemberList.isNotEmpty) {
final newIndex = min(activeIndex + 1, showMemberList.length - 1);
widget.model.activeAtIndex = newIndex;
widget.atMemberPanelScroll?.scrollToIndex(newIndex,
preferPosition: AutoScrollPosition.middle);
return KeyEventResult.handled;
}
}
return KeyEventResult.ignored;
},
); );
} else { } else {
focusNode = FocusNode(); focusNode = FocusNode();
} }
textEditingController = textEditingController =
widget.controller?.textEditingController ?? TextEditingController(); widget.controller?.textEditingController ?? TextEditingController();
if (widget.controller != null) {
widget.controller?.addListener(() {
final actionType = widget.controller?.actionType;
if (actionType == ActionType.longPressToAt) {
_longPressToAt(
widget.controller?.atUserID, widget.controller?.atUserName);
}
});
}
widget.model.addListener(onModelChanged);
if (widget.initText != null) { if (widget.initText != null) {
textEditingController.text = widget.initText!; textEditingController.text = widget.initText!;
} }
if (widget.controller != null) {
widget.controller?.addListener(controllerHandler);
}
widget.model.addListener(onModelChanged);
final AppLocale appLocale = I18nUtils.findDeviceLocale(null); final AppLocale appLocale = I18nUtils.findDeviceLocale(null);
languageType = languageType =
(appLocale == AppLocale.zhHans || appLocale == AppLocale.zhHant) (appLocale == AppLocale.zhHans || appLocale == AppLocale.zhHant)
? 'zh' ? 'zh'
: 'en'; : 'en';
textEditingController.addListener(() {
_isComposingText = textEditingController.value.composing.start != -1;
});
}
controllerHandler() {
final actionType = widget.controller?.actionType;
if (actionType == ActionType.longPressToAt) {
_longPressToAt(
widget.controller?.atUserID, widget.controller?.atUserName);
} else if (actionType == ActionType.setTextField) {
final newText = widget.controller?.inputText ?? "";
textEditingController.text = newText;
textEditingController.selection = TextSelection.fromPosition(
TextPosition(offset: textEditingController.text.length));
lastText = textEditingController.text;
return;
} else if (actionType == ActionType.requestFocus) {
focusNode.requestFocus();
return;
} else if (actionType == ActionType.handleAtMember) {
handleAtMember(memberInfo: widget.controller?.groupMemberFullInfo);
return;
}
} }
@override @override
@ -651,18 +710,27 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
super.didUpdateWidget(oldWidget); super.didUpdateWidget(oldWidget);
if (widget.conversationID != oldWidget.conversationID) { if (widget.conversationID != oldWidget.conversationID) {
handleSetDraftText(oldWidget.conversationID, oldWidget.conversationType); handleSetDraftText(oldWidget.conversationID, oldWidget.conversationType);
mentionedMembersMap.clear();
if (oldWidget.initText != widget.initText) { if (oldWidget.initText != widget.initText) {
textEditingController.text = widget.initText ?? ""; textEditingController.text = widget.initText ?? "";
} else { } else {
textEditingController.clear(); textEditingController.clear();
} }
} }
if (widget.initText != oldWidget.initText &&
TencentUtils.checkString(widget.initText) != null) {
textEditingController.text = widget.initText!;
focusNode.requestFocus();
}
} }
@override @override
void dispose() { void dispose() {
handleSetDraftText(); handleSetDraftText();
widget.model.removeListener(onModelChanged); widget.model.removeListener(onModelChanged);
if (widget.controller != null) {
widget.controller?.removeListener(controllerHandler);
}
focusNode.dispose(); focusNode.dispose();
super.dispose(); super.dispose();
} }
@ -738,6 +806,7 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
@override @override
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) { Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
final theme = value.theme;
final TUIChatSeparateViewModel model = final TUIChatSeparateViewModel model =
Provider.of<TUIChatSeparateViewModel>(context); Provider.of<TUIChatSeparateViewModel>(context);
@ -763,6 +832,7 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
builder: (BuildContext context, BoxConstraints constraints) { builder: (BuildContext context, BoxConstraints constraints) {
inputWidth = constraints.maxWidth; inputWidth = constraints.maxWidth;
return TUIKitScreenUtils.getDeviceWidget( return TUIKitScreenUtils.getDeviceWidget(
context: context,
defaultWidget: TIMUIKitTextFieldLayoutNarrow( defaultWidget: TIMUIKitTextFieldLayoutNarrow(
onEmojiSubmitted: onEmojiSubmitted, onEmojiSubmitted: onEmojiSubmitted,
onCustomEmojiFaceSubmitted: onCustomEmojiFaceSubmitted, onCustomEmojiFaceSubmitted: onCustomEmojiFaceSubmitted,
@ -798,6 +868,7 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
showMorePanel: widget.showMorePanel, showMorePanel: widget.showMorePanel,
customEmojiStickerList: widget.customEmojiStickerList), customEmojiStickerList: widget.customEmojiStickerList),
desktopWidget: TIMUIKitTextFieldLayoutWide( desktopWidget: TIMUIKitTextFieldLayoutWide(
theme: theme,
currentConversation: widget.currentConversation, currentConversation: widget.currentConversation,
onEmojiSubmitted: onEmojiSubmitted, onEmojiSubmitted: onEmojiSubmitted,
onCustomEmojiFaceSubmitted: onCustomEmojiFaceSubmitted, onCustomEmojiFaceSubmitted: onCustomEmojiFaceSubmitted,
@ -825,7 +896,6 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
handleAtText: (text) { handleAtText: (text) {
_handleAtText(text, model); _handleAtText(text, model);
}, },
handleSoftKeyBoardDelete: _handleSoftKeyBoardDelete,
onSubmitted: onSubmitted, onSubmitted: onSubmitted,
goDownBottom: goDownBottom, goDownBottom: goDownBottom,
showSendAudio: widget.showSendAudio, showSendAudio: widget.showSendAudio,

View File

@ -1,12 +1,21 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:tencent_im_base/tencent_im_base.dart';
enum ActionType { hideAllPanel, longPressToAt } enum ActionType {
hideAllPanel,
longPressToAt,
setTextField,
requestFocus,
handleAtMember
}
class TIMUIKitInputTextFieldController extends ChangeNotifier { class TIMUIKitInputTextFieldController extends ChangeNotifier {
TextEditingController? textEditingController = TextEditingController(); TextEditingController? textEditingController = TextEditingController();
ActionType? actionType; ActionType? actionType;
String? atUserName; String? atUserName;
String? atUserID; String? atUserID;
String inputText = "";
V2TimGroupMemberFullInfo? groupMemberFullInfo;
TIMUIKitInputTextFieldController([TextEditingController? controller]) { TIMUIKitInputTextFieldController([TextEditingController? controller]) {
if (controller != null) { if (controller != null) {
@ -26,4 +35,21 @@ class TIMUIKitInputTextFieldController extends ChangeNotifier {
atUserID = userID; atUserID = userID;
notifyListeners(); notifyListeners();
} }
setTextField(String text) {
inputText = text;
actionType = ActionType.setTextField;
notifyListeners();
}
requestFocus() {
actionType = ActionType.requestFocus;
notifyListeners();
}
handleAtMember(V2TimGroupMemberFullInfo? memberInfo) {
actionType = ActionType.handleAtMember;
groupMemberFullInfo = memberInfo;
notifyListeners();
}
} }

View File

@ -17,7 +17,7 @@ import 'package:tencent_cloud_chat_uikit/ui/utils/platform.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitTextField/special_text/DefaultSpecialTextSpanBuilder.dart'; import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitTextField/special_text/DefaultSpecialTextSpanBuilder.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_emoji_panel.dart'; import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_emoji_panel.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_send_sound_message.dart'; import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_send_sound_message.dart';
import 'package:tencent_extended_text_field/extended_text_field.dart'; import 'package:extended_text_field/extended_text_field.dart';
import 'package:tencent_keyboard_visibility/tencent_keyboard_visibility.dart'; import 'package:tencent_keyboard_visibility/tencent_keyboard_visibility.dart';
GlobalKey<_TIMUIKitTextFieldLayoutNarrowState> narrowTextFieldKey = GlobalKey(); GlobalKey<_TIMUIKitTextFieldLayoutNarrowState> narrowTextFieldKey = GlobalKey();

View File

@ -3,6 +3,7 @@ import 'dart:io';
import 'dart:math'; import 'dart:math';
import 'package:fc_native_video_thumbnail_for_us/fc_native_video_thumbnail_for_us.dart'; import 'package:fc_native_video_thumbnail_for_us/fc_native_video_thumbnail_for_us.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_svg/svg.dart';
import 'package:package_info_plus/package_info_plus.dart'; import 'package:package_info_plus/package_info_plus.dart';
import 'package:pasteboard/pasteboard.dart'; import 'package:pasteboard/pasteboard.dart';
import 'package:path/path.dart' as p; import 'package:path/path.dart' as p;
@ -26,7 +27,7 @@ import 'package:tencent_cloud_chat_uikit/ui/utils/platform.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitTextField/special_text/DefaultSpecialTextSpanBuilder.dart'; import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitTextField/special_text/DefaultSpecialTextSpanBuilder.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_emoji_panel.dart'; import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_emoji_panel.dart';
import 'package:tencent_cloud_chat_uikit/ui/widgets/drag_widget.dart'; import 'package:tencent_cloud_chat_uikit/ui/widgets/drag_widget.dart';
import 'package:tencent_extended_text_field/extended_text_field.dart'; import 'package:extended_text_field/extended_text_field.dart';
import 'package:universal_html/html.dart' as html; import 'package:universal_html/html.dart' as html;
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
@ -39,6 +40,7 @@ class DesktopControlBarItem {
final String item; final String item;
final IconData? icon; final IconData? icon;
final String? imgPath; final String? imgPath;
final String? svgPath;
final Color? color; final Color? color;
final ValueChanged<Offset?> onClick; final ValueChanged<Offset?> onClick;
final String? showName; final String? showName;
@ -49,10 +51,13 @@ class DesktopControlBarItem {
this.icon, this.icon,
this.color, this.color,
this.imgPath, this.imgPath,
this.svgPath,
required this.onClick, required this.onClick,
this.showName, this.showName,
this.size}) this.size})
: assert(icon != null || imgPath != null); : assert(icon != null ||
TencentUtils.checkString(imgPath) != null ||
TencentUtils.checkString(svgPath) != null);
} }
class DesktopControlBarConfig { class DesktopControlBarConfig {
@ -81,7 +86,7 @@ class TIMUIKitTextFieldLayoutWide extends StatefulWidget {
final Function(String, bool) handleSendEditStatus; final Function(String, bool) handleSendEditStatus;
final VoidCallback backSpaceText; final VoidCallback backSpaceText;
final ValueChanged<String> addStickerToText; final ValueChanged<String> addStickerToText;
final TUITheme theme;
final ValueChanged<String> handleAtText; final ValueChanged<String> handleAtText;
/// Whether to use the default emoji /// Whether to use the default emoji
@ -125,8 +130,6 @@ class TIMUIKitTextFieldLayoutWide extends StatefulWidget {
/// show send audio icon /// show send audio icon
final bool showSendAudio; final bool showSendAudio;
final VoidCallback handleSoftKeyBoardDelete;
/// on text changed /// on text changed
final void Function(String)? onChanged; final void Function(String)? onChanged;
@ -168,7 +171,6 @@ class TIMUIKitTextFieldLayoutWide extends StatefulWidget {
this.onChanged, this.onChanged,
required this.handleSendEditStatus, required this.handleSendEditStatus,
required this.handleAtText, required this.handleAtText,
required this.handleSoftKeyBoardDelete,
this.repliedMessage, this.repliedMessage,
this.forbiddenText, this.forbiddenText,
required this.onSubmitted, required this.onSubmitted,
@ -179,7 +181,8 @@ class TIMUIKitTextFieldLayoutWide extends StatefulWidget {
this.hintText, this.hintText,
required this.customEmojiStickerList, required this.customEmojiStickerList,
this.controller, this.controller,
required this.currentConversation}) required this.currentConversation,
required this.theme})
: super(key: key); : super(key: key);
@override @override
@ -199,6 +202,7 @@ class _TIMUIKitTextFieldLayoutWideState
double? bottomPadding; double? bottomPadding;
late ScrollController _scrollController; late ScrollController _scrollController;
late FocusNode textFocusNode; late FocusNode textFocusNode;
late List<DesktopControlBarItem> defaultControlBarItems;
@override @override
void initState() { void initState() {
@ -214,6 +218,27 @@ class _TIMUIKitTextFieldLayoutWideState
textFocusNode = FocusNode(); textFocusNode = FocusNode();
widget.focusNode.requestFocus(); widget.focusNode.requestFocus();
_scrollController = ScrollController(); _scrollController = ScrollController();
try {
if (PlatformUtils().isWeb) {
html.window.addEventListener('paste', (event) {
_handlePaste(event as html.ClipboardEvent);
});
}
} catch (e) {
print(e);
}
generateDefaultControlBarItems();
}
Future<void> _handlePaste(html.ClipboardEvent event) async {
try {
if (event.clipboardData!.files!.isNotEmpty) {
html.File imageFile = event.clipboardData!.files![0];
sendFileUseJs(imageFile);
}
} catch (e) {
print("Paste image failed: ${e.toString()}");
}
} }
hideAllPanel() { hideAllPanel() {
@ -324,6 +349,8 @@ class _TIMUIKitTextFieldLayoutWideState
addText: (int unicode) { addText: (int unicode) {
final newText = String.fromCharCode(unicode); final newText = String.fromCharCode(unicode);
widget.addStickerToText(newText); widget.addStickerToText(newText);
entry?.remove();
entry = null;
}, },
addCustomEmojiText: ((String singleEmojiName) { addCustomEmojiText: ((String singleEmojiName) {
String? emojiName = singleEmojiName.split('.png')[0]; String? emojiName = singleEmojiName.split('.png')[0];
@ -335,6 +362,8 @@ class _TIMUIKitTextFieldLayoutWideState
} }
final newText = '[$emojiName]'; final newText = '[$emojiName]';
widget.addStickerToText(newText); widget.addStickerToText(newText);
entry?.remove();
entry = null;
}), }),
defaultCustomEmojiStickerList: defaultCustomEmojiStickerList:
widget.isUseDefaultEmoji ? ConstData.emojiList : []) widget.isUseDefaultEmoji ? ConstData.emojiList : [])
@ -349,7 +378,7 @@ class _TIMUIKitTextFieldLayoutWideState
), ),
)); ));
}); });
Overlay.of(context)?.insert(entry!); Overlay.of(context).insert(entry!);
} }
} }
@ -363,12 +392,12 @@ class _TIMUIKitTextFieldLayoutWideState
color: const Color(0x7F000000), color: const Color(0x7F000000),
); );
}); });
Overlay.of(context)?.insert(entry!); Overlay.of(context).insert(entry!);
} }
} }
_removeOverlay() { _removeOverlay() {
entry!.remove(); entry?.remove();
entry = null; entry = null;
} }
@ -399,20 +428,19 @@ class _TIMUIKitTextFieldLayoutWideState
convID: convID, convID: convID,
convType: convType), convType: convType),
context); context);
return; } else {
File file = File(result.files.single.path!);
final int size = file.lengthSync();
final String savePath = file.path;
MessageUtils.handleMessageError(
model.sendFileMessage(
filePath: savePath,
size: size,
convID: convID,
convType: convType),
context);
} }
File file = File(result.files.single.path!);
final int size = file.lengthSync();
final String savePath = file.path;
MessageUtils.handleMessageError(
model.sendFileMessage(
filePath: savePath,
size: size,
convID: convID,
convType: convType),
context);
} else { } else {
throw TypeError(); throw TypeError();
} }
@ -424,10 +452,11 @@ class _TIMUIKitTextFieldLayoutWideState
List<Widget> generateBarIcons( List<Widget> generateBarIcons(
List<DesktopControlBarItem> items, TUITheme theme) { List<DesktopControlBarItem> items, TUITheme theme) {
final defaultItems = defaultControlBarItems.map((e) => e.item);
return items.map((e) { return items.map((e) {
final GlobalKey key = GlobalKey(); final GlobalKey key = GlobalKey();
return Container( return Container(
margin: const EdgeInsets.only(right: 6), margin: const EdgeInsets.only(right: 10),
child: InkWell( child: InkWell(
onTap: () { onTap: () {
final alignBox = final alignBox =
@ -445,20 +474,39 @@ class _TIMUIKitTextFieldLayoutWideState
textStyle: TextStyle(fontSize: 12, color: theme.white), textStyle: TextStyle(fontSize: 12, color: theme.white),
message: e.showName, message: e.showName,
child: Container( child: Container(
decoration: decoration: BoxDecoration(borderRadius: BorderRadius.circular(2)),
BoxDecoration(borderRadius: BorderRadius.circular(2)), padding: const EdgeInsets.all(4),
padding: const EdgeInsets.all(4), child: () {
child: e.imgPath != null if (TencentUtils.checkString(e.svgPath) != null) {
? Image.asset( return SvgPicture.asset(
e.imgPath!, e.svgPath!,
key: key, package: defaultItems.contains(e.item)
width: e.size ?? 20, ? 'tencent_cloud_chat_uikit'
height: e.size ?? 20, : null,
) key: key,
: Icon(e.icon, width: e.size ?? 16,
key: key, height: e.size ?? 16,
color: e.color ?? hexToColor("646a73"), );
size: e.size ?? 20)), }
if (TencentUtils.checkString(e.imgPath) != null) {
return Image.asset(
e.imgPath!,
package: defaultItems.contains(e.item)
? 'tencent_cloud_chat_uikit'
: null,
key: key,
width: e.size ?? 16,
height: e.size ?? 16,
);
}
return Icon(
e.icon,
key: key,
color: e.color ?? hexToColor("646a73"),
size: e.size ?? 20,
);
}(),
),
), ),
), ),
); );
@ -658,10 +706,11 @@ class _TIMUIKitTextFieldLayoutWideState
} }
} }
_sendImageWithConfirmation(String file) async { _sendImageWithConfirmation(
{String? fileName, Size? fileSize, required String filePath}) async {
final option1 = widget.currentConversation.showName ?? final option1 = widget.currentConversation.showName ??
(widget.conversationType == ConvType.group ? TIM_t("群聊") : TIM_t("对方")); (widget.conversationType == ConvType.group ? TIM_t("群聊") : TIM_t("对方"));
final size = await ScreenshotHelper.getImageSize(file); final size = fileSize ?? await ScreenshotHelper.getImageSize(filePath);
TUIKitWidePopup.showPopupWindow( TUIKitWidePopup.showPopupWindow(
operationKey: TUIKitWideModalOperationKey.beforeSendScreenShot, operationKey: TUIKitWideModalOperationKey.beforeSendScreenShot,
@ -679,12 +728,19 @@ class _TIMUIKitTextFieldLayoutWideState
height: min(360, size.height / 2), height: min(360, size.height / 2),
child: InkWell( child: InkWell(
onTap: () { onTap: () {
launchUrl(Uri.file(file)); launchUrl(PlatformUtils().isWeb
? Uri.parse(filePath)
: Uri.file(filePath));
}, },
child: Image.file( child: PlatformUtils().isWeb
File(file), ? Image.network(
height: min(360, size.height / 2), filePath,
), height: min(360, size.height / 2),
)
: Image.file(
File(filePath),
height: min(360, size.height / 2),
),
), ),
), ),
Row( Row(
@ -703,7 +759,8 @@ class _TIMUIKitTextFieldLayoutWideState
onPressed: () { onPressed: () {
MessageUtils.handleMessageError( MessageUtils.handleMessageError(
widget.model.sendImageMessage( widget.model.sendImageMessage(
imagePath: file, imagePath: filePath,
imageName: fileName,
convID: widget.conversationID, convID: widget.conversationID,
convType: widget.conversationType), convType: widget.conversationType),
context); context);
@ -720,12 +777,11 @@ class _TIMUIKitTextFieldLayoutWideState
_sendScreenShot() async { _sendScreenShot() async {
final file = await ScreenshotHelper.captureScreen(); final file = await ScreenshotHelper.captureScreen();
if (file != null) { if (file != null) {
_sendImageWithConfirmation(file); _sendImageWithConfirmation(filePath: file);
} else {} } else {}
} }
List<Widget> generateControlBar( generateDefaultControlBarItems() {
TUIChatSeparateViewModel model, TUITheme theme) {
final DesktopControlBarConfig config = final DesktopControlBarConfig config =
widget.model.chatConfig.desktopControlBarConfig ?? widget.model.chatConfig.desktopControlBarConfig ??
DesktopControlBarConfig(); DesktopControlBarConfig();
@ -735,9 +791,9 @@ class _TIMUIKitTextFieldLayoutWideState
item: "face", item: "face",
showName: TIM_t("表情"), showName: TIM_t("表情"),
onClick: (offset) { onClick: (offset) {
_sendEmoji(offset, theme); _sendEmoji(offset, widget.theme);
}, },
icon: Icons.mood), svgPath: "images/svg/send_face.svg"),
if (config.showScreenshotButton && PlatformUtils().isDesktop) if (config.showScreenshotButton && PlatformUtils().isDesktop)
DesktopControlBarItem( DesktopControlBarItem(
item: "screenShot", item: "screenShot",
@ -745,15 +801,15 @@ class _TIMUIKitTextFieldLayoutWideState
onClick: (offset) { onClick: (offset) {
_sendScreenShot(); _sendScreenShot();
}, },
icon: Icons.cut_outlined), svgPath: "images/svg/send_screenshot.svg"),
if (config.showSendFileButton) if (config.showSendFileButton)
DesktopControlBarItem( DesktopControlBarItem(
item: "file", item: "file",
showName: TIM_t("文件"), showName: TIM_t("文件"),
onClick: (offset) { onClick: (offset) {
_sendFile(widget.model, theme); _sendFile(widget.model, widget.theme);
}, },
icon: Icons.file_copy_outlined), svgPath: "images/svg/send_file.svg"),
if (config.showSendImageButton) if (config.showSendImageButton)
DesktopControlBarItem( DesktopControlBarItem(
item: "photo", item: "photo",
@ -762,10 +818,10 @@ class _TIMUIKitTextFieldLayoutWideState
if (PlatformUtils().isWeb) { if (PlatformUtils().isWeb) {
_sendImageFileOnWeb(widget.model); _sendImageFileOnWeb(widget.model);
} else { } else {
_sendMediaMessage(widget.model, theme, FileType.image); _sendMediaMessage(widget.model, widget.theme, FileType.image);
} }
}, },
icon: Icons.image_outlined), svgPath: "images/svg/send_image.svg"),
if (config.showSendVideoButton) if (config.showSendVideoButton)
DesktopControlBarItem( DesktopControlBarItem(
item: "video", item: "video",
@ -774,10 +830,10 @@ class _TIMUIKitTextFieldLayoutWideState
if (PlatformUtils().isWeb) { if (PlatformUtils().isWeb) {
_sendVideoFileOnWeb(widget.model); _sendVideoFileOnWeb(widget.model);
} else { } else {
_sendMediaMessage(widget.model, theme, FileType.video); _sendMediaMessage(widget.model, widget.theme, FileType.video);
} }
}, },
icon: Icons.video_library_outlined), svgPath: "images/svg/send_video.svg"),
if (config.showMessageHistoryButton) if (config.showMessageHistoryButton)
DesktopControlBarItem( DesktopControlBarItem(
item: "history", item: "history",
@ -801,19 +857,41 @@ class _TIMUIKitTextFieldLayoutWideState
onTapConversation: (V2TimConversation conversation, onTapConversation: (V2TimConversation conversation,
V2TimMessage? message) {}, V2TimMessage? message) {},
), ),
theme: theme); theme: widget.theme);
}, },
icon: Icons.chat_outlined), svgPath: "images/svg/message_history.svg"),
];
defaultControlBarItems = itemsList;
}
List<Widget> generateControlBar(
TUIChatSeparateViewModel model, TUITheme theme) {
final List<DesktopControlBarItem> itemsList = [
...defaultControlBarItems,
...(widget.model.chatConfig.additionalDesktopControlBarItems ?? []) ...(widget.model.chatConfig.additionalDesktopControlBarItems ?? [])
]; ];
return generateBarIcons(itemsList, theme); return generateBarIcons(itemsList, theme);
} }
sendFileUseJs(html.File file) {
final mimeType = file.type.split('/');
final type = mimeType[0];
final blobUrl = html.Url.createObjectUrl(file);
if (type == 'image') {
_sendImageWithConfirmation(
filePath: blobUrl,
fileName: file.name,
fileSize: const Size(500, 500));
}
}
Future<void> _handleKeyEvent(RawKeyEvent event) async { Future<void> _handleKeyEvent(RawKeyEvent event) async {
if ((event.isKeyPressed(LogicalKeyboardKey.controlLeft) && if (PlatformUtils().isDesktop &&
event.logicalKey == LogicalKeyboardKey.keyV) || ((event.isKeyPressed(LogicalKeyboardKey.controlLeft) &&
(event.isMetaPressed && event.logicalKey == LogicalKeyboardKey.keyV)) { event.logicalKey == LogicalKeyboardKey.keyV) ||
(event.isMetaPressed &&
event.logicalKey == LogicalKeyboardKey.keyV))) {
final bytes = await Pasteboard.image; final bytes = await Pasteboard.image;
if (bytes != null) { if (bytes != null) {
String directory; String directory;
@ -838,7 +916,7 @@ class _TIMUIKitTextFieldLayoutWideState
await scDirectory.create(recursive: true); await scDirectory.create(recursive: true);
} }
await file.writeAsBytes(bytes.toList()); await file.writeAsBytes(bytes.toList());
_sendImageWithConfirmation(filePath); _sendImageWithConfirmation(filePath: filePath);
} }
} }
} }
@ -857,10 +935,6 @@ class _TIMUIKitTextFieldLayoutWideState
} }
widget.handleAtText(value); widget.handleAtText(value);
widget.handleSendEditStatus(value, true); widget.handleSendEditStatus(value, true);
final isEmpty = value.isEmpty;
if (isEmpty) {
widget.handleSoftKeyBoardDelete();
}
}, const Duration(milliseconds: 80)); }, const Duration(milliseconds: 80));
final MediaQueryData data = MediaQuery.of(context); final MediaQueryData data = MediaQuery.of(context);
@ -873,7 +947,7 @@ class _TIMUIKitTextFieldLayoutWideState
focusNode: textFocusNode, focusNode: textFocusNode,
onKey: _handleKeyEvent, onKey: _handleKeyEvent,
child: Container( child: Container(
color: widget.backgroundColor, color: widget.backgroundColor ?? theme.desktopChatMessageInputBgColor,
child: Column( child: Column(
children: [ children: [
SizedBox( SizedBox(
@ -899,7 +973,8 @@ class _TIMUIKitTextFieldLayoutWideState
Expanded( Expanded(
child: Container( child: Container(
height: 35, height: 35,
color: theme.weakBackgroundColor, color: widget.backgroundColor ??
theme.desktopChatMessageInputBgColor,
alignment: Alignment.center, alignment: Alignment.center,
child: Text( child: Text(
TIM_t(widget.forbiddenText!), TIM_t(widget.forbiddenText!),
@ -913,45 +988,43 @@ class _TIMUIKitTextFieldLayoutWideState
)), )),
if (widget.forbiddenText == null) if (widget.forbiddenText == null)
Expanded( Expanded(
child: Scrollbar( child: ExtendedTextField(
controller: _scrollController, scrollController: _scrollController,
child: ExtendedTextField( autofocus: true,
autofocus: true, maxLines: widget
maxLines: 6, .model.chatConfig.desktopMessageInputFieldLines,
minLines: 6, minLines: widget
focusNode: widget.focusNode, .model.chatConfig.desktopMessageInputFieldLines,
onChanged: debounceFunc, focusNode: widget.focusNode,
keyboardType: TextInputType.multiline, onChanged: debounceFunc,
textInputAction: PlatformUtils().isAndroid keyboardType: TextInputType.multiline,
? TextInputAction.newline onEditingComplete: () {
: TextInputAction.send, // // widget.onSubmitted();
onEditingComplete: () { },
widget.onSubmitted(); textAlignVertical: TextAlignVertical.top,
}, style: const TextStyle(fontSize: 14),
textAlignVertical: TextAlignVertical.top, decoration: InputDecoration(
style: const TextStyle(fontSize: 14), hoverColor: hexToColor("fafafa"),
decoration: InputDecoration( border: InputBorder.none,
hoverColor: hexToColor("fafafa"), hintStyle: const TextStyle(
border: InputBorder.none, color: Color(0xffAEA4A3),
hintStyle: const TextStyle(
color: Color(0xffAEA4A3),
),
fillColor: hexToColor("fafafa"),
filled: true,
isDense: true,
hintText: widget.hintText ?? '',
), ),
controller: widget.textEditingController, fillColor: widget.backgroundColor ??
specialTextSpanBuilder: PlatformUtils().isWeb theme.desktopChatMessageInputBgColor ??
? null hexToColor("fafafa"),
: DefaultSpecialTextSpanBuilder( filled: true,
isUseDefaultEmoji: isDense: true,
widget.isUseDefaultEmoji, hintText: widget.hintText ?? '',
customEmojiStickerList: ),
widget.customEmojiStickerList, controller: widget.textEditingController,
showAtBackground: true, specialTextSpanBuilder: PlatformUtils().isWeb
)), ? null
), : DefaultSpecialTextSpanBuilder(
isUseDefaultEmoji: widget.isUseDefaultEmoji,
customEmojiStickerList:
widget.customEmojiStickerList,
showAtBackground: true,
)),
), ),
], ],
), ),

View File

@ -59,6 +59,10 @@ class TIMUIKitChat extends StatefulWidget {
/// Avatar and name in message reaction tap callback. /// Avatar and name in message reaction tap callback.
final void Function(String userID, TapDownDetails tapDetails)? onTapAvatar; final void Function(String userID, TapDownDetails tapDetails)? onTapAvatar;
/// Avatar and name in message reaction secondary tap callback.
final void Function(String userID, TapDownDetails tapDetails)?
onSecondaryTapAvatar;
@Deprecated( @Deprecated(
"Nickname will not shows in one-to-one chat, if you tend to control it in group chat, please use `isShowSelfNameInGroup` and `isShowOthersNameInGroup` from `config: TIMUIKitChatConfig` instead") "Nickname will not shows in one-to-one chat, if you tend to control it in group chat, please use `isShowSelfNameInGroup` and `isShowOthersNameInGroup` from `config: TIMUIKitChatConfig` instead")
@ -145,12 +149,13 @@ class TIMUIKitChat extends StatefulWidget {
this.conversationShowName, this.conversationShowName,
this.abstractMessageBuilder, this.abstractMessageBuilder,
this.onTapAvatar, this.onTapAvatar,
@Deprecated("Nickname will not show in one-to-one chat, if you tend to control it in group chat, please use `isShowSelfNameInGroup` and `isShowOthersNameInGroup` from `config: TIMUIKitChatConfig` instead") @Deprecated(
this.showNickName = false, "Nickname will not show in one-to-one chat, if you tend to control it in group chat, please use `isShowSelfNameInGroup` and `isShowOthersNameInGroup` from `config: TIMUIKitChatConfig` instead")
this.showNickName = false,
this.showTotalUnReadCount = false, this.showTotalUnReadCount = false,
this.messageItemBuilder, this.messageItemBuilder,
@Deprecated("Please use [extraTipsActionItemBuilder] instead") @Deprecated("Please use [extraTipsActionItemBuilder] instead")
this.exteraTipsActionItemBuilder, this.exteraTipsActionItemBuilder,
this.extraTipsActionItemBuilder, this.extraTipsActionItemBuilder,
this.draftText, this.draftText,
this.textFieldHintText, this.textFieldHintText,
@ -170,7 +175,8 @@ class TIMUIKitChat extends StatefulWidget {
this.topFixWidget = const SizedBox(), this.topFixWidget = const SizedBox(),
this.textFieldBuilder, this.textFieldBuilder,
this.customEmojiStickerList = const [], this.customEmojiStickerList = const [],
this.customAppBar}) this.customAppBar,
this.onSecondaryTapAvatar})
: super(key: key) { : super(key: key) {
startTime = DateTime.now().millisecondsSinceEpoch; startTime = DateTime.now().millisecondsSinceEpoch;
} }
@ -239,7 +245,9 @@ class _TUIChatState extends TIMUIKitState<TIMUIKitChat> {
model = TUIChatSeparateViewModel(); model = TUIChatSeparateViewModel();
model.abstractMessageBuilder = widget.abstractMessageBuilder; model.abstractMessageBuilder = widget.abstractMessageBuilder;
model.onTapAvatar = widget.onTapAvatar; model.onTapAvatar = widget.onTapAvatar;
textFieldController.requestFocus();
Future.delayed(const Duration(milliseconds: 50), () { Future.delayed(const Duration(milliseconds: 50), () {
textFieldController.requestFocus();
try { try {
autoController.jumpTo( autoController.jumpTo(
autoController.position.minScrollExtent, autoController.position.minScrollExtent,
@ -317,6 +325,7 @@ class _TUIChatState extends TIMUIKitState<TIMUIKitChat> {
return TIMUIKitChatProviderScope( return TIMUIKitChatProviderScope(
model: model, model: model,
groupID: widget.groupID, groupID: widget.groupID,
scrollController: autoController,
textFieldController: textFieldController, textFieldController: textFieldController,
conversationID: _getConvID(), conversationID: _getConvID(),
conversationType: _getConvType(), conversationType: _getConvType(),
@ -331,6 +340,8 @@ class _TUIChatState extends TIMUIKitState<TIMUIKitChat> {
Provider.of<TUIChatGlobalModel>(context, listen: true); Provider.of<TUIChatGlobalModel>(context, listen: true);
widget.controller?.model = model; widget.controller?.model = model;
widget.controller?.textFieldController = textFieldController;
widget.controller?.scrollController = autoController;
List<V2TimGroupApplication> filteredApplicationList = []; List<V2TimGroupApplication> filteredApplicationList = [];
if (widget.conversationType == ConvType.group && if (widget.conversationType == ConvType.group &&
widget.onDealWithGroupApplication != null) { widget.onDealWithGroupApplication != null) {
@ -431,11 +442,8 @@ class _TUIChatState extends TIMUIKitState<TIMUIKitChat> {
tongueItemBuilder: widget.tongueItemBuilder, tongueItemBuilder: widget.tongueItemBuilder,
onLongPressForOthersHeadPortrait: onLongPressForOthersHeadPortrait:
(String? userId, String? nickName) { (String? userId, String? nickName) {
if (widget.conversationType != textFieldController.longPressToAt(
ConvType.c2c) { nickName, userId);
textFieldController.longPressToAt(
nickName, userId);
}
}, },
mainHistoryListConfig: mainHistoryListConfig:
widget.mainHistoryListConfig, widget.mainHistoryListConfig,
@ -445,6 +453,8 @@ class _TUIChatState extends TIMUIKitState<TIMUIKitChat> {
widget.exteraTipsActionItemBuilder, widget.exteraTipsActionItemBuilder,
conversationType: _getConvType(), conversationType: _getConvType(),
scrollController: autoController, scrollController: autoController,
onSecondaryTapAvatar:
widget.onSecondaryTapAvatar,
onTapAvatar: widget.onTapAvatar, onTapAvatar: widget.onTapAvatar,
// ignore: deprecated_member_use_from_same_package // ignore: deprecated_member_use_from_same_package
showNickName: widget.showNickName, showNickName: widget.showNickName,
@ -463,7 +473,6 @@ class _TUIChatState extends TIMUIKitState<TIMUIKitChat> {
: (widget.textFieldBuilder != null : (widget.textFieldBuilder != null
? widget.textFieldBuilder!(context) ? widget.textFieldBuilder!(context)
: TIMUIKitInputTextField( : TIMUIKitInputTextField(
key: inputTextFieldState,
atMemberPanelScroll: atMemberPanelScroll:
atMemberPanelScroll, atMemberPanelScroll,
groupType: groupType:
@ -483,7 +492,8 @@ class _TUIChatState extends TIMUIKitState<TIMUIKitChat> {
scrollController: autoController, scrollController: autoController,
conversationID: _getConvID(), conversationID: _getConvID(),
conversationType: _getConvType(), conversationType: _getConvType(),
initText: widget.draftText, initText: widget.draftText ??
widget.conversation.draftText,
hintText: widget.textFieldHintText, hintText: widget.textFieldHintText,
showMorePanel: widget.config showMorePanel: widget.config
?.isAllowShowMorePanel ?? ?.isAllowShowMorePanel ??
@ -508,9 +518,8 @@ class _TUIChatState extends TIMUIKitState<TIMUIKitChat> {
), ),
AtMemberPanel( AtMemberPanel(
atMemberPanelScroll: atMemberPanelScroll, atMemberPanelScroll: atMemberPanelScroll,
onSelectMember: (member) => inputTextFieldState onSelectMember: (member) =>
.currentState textFieldController.handleAtMember(member),
?.handleAtMember(memberInfo: member),
) )
], ],
), ),
@ -555,6 +564,8 @@ class TIMUIKitChatProviderScope extends StatelessWidget {
final bool? isBuild; final bool? isBuild;
final AutoScrollController? scrollController;
TIMUIKitChatProviderScope( TIMUIKitChatProviderScope(
{Key? key, {Key? key,
this.child, this.child,
@ -568,13 +579,16 @@ class TIMUIKitChatProviderScope extends StatelessWidget {
required this.conversationType, required this.conversationType,
this.controller, this.controller,
this.config, this.config,
this.lifeCycle}) this.lifeCycle,
this.scrollController})
: super(key: key) { : super(key: key) {
if (isBuild ?? false) { if (isBuild ?? false) {
return; return;
} }
model ??= TUIChatSeparateViewModel(); model ??= TUIChatSeparateViewModel();
controller?.model = model; controller?.model = model;
controller?.textFieldController = textFieldController;
controller?.scrollController = scrollController;
if (config != null) { if (config != null) {
model?.chatConfig = config!; model?.chatConfig = config!;
} }

View File

@ -147,7 +147,7 @@ class TIMUIKitChatConfig {
/// Whether to use the default emoji /// Whether to use the default emoji
final bool isUseDefaultEmoji; final bool isUseDefaultEmoji;
/// Is show avatar on history message list. /// Whether shows avatar on history message list.
/// [Default]: true. /// [Default]: true.
final bool isShowAvatar; final bool isShowAvatar;
@ -157,13 +157,13 @@ class TIMUIKitChatConfig {
final List<MessageHoverControlItem>? additionalDesktopMessageHoverBarItem; final List<MessageHoverControlItem>? additionalDesktopMessageHoverBarItem;
/// This list contains additional items that are displayed /// This list contains additional items that are displayed
/// on the control bar on desktop (macOS, Windows, and desktop version of Web). /// on the message sending area control bar on desktop (macOS, Windows, and desktop version of Web).
/// Use `desktopControlBarConfig` to configure whether or not to show the default control items. /// Use `desktopControlBarConfig` to configure whether or not to show the default control items.
final List<DesktopControlBarItem>? additionalDesktopControlBarItems; final List<DesktopControlBarItem>? additionalDesktopControlBarItems;
/// This configuration is used for the control bar /// This configuration is used for the control bar
/// on desktop (macOS, Windows, and desktop version of Web). /// on desktop (macOS, Windows, and desktop version of Web).
/// Use `desktopControlBarConfig` to add additional items to the desktop control bar, in addition to the default ones. /// Use `desktopControlBarConfig` to add additional items to the desktop message sending area control bar, in addition to the default ones.
final DesktopControlBarConfig? desktopControlBarConfig; final DesktopControlBarConfig? desktopControlBarConfig;
/// Controls whether users are allowed to mention another user in the group by long-pressing on their avatar. /// Controls whether users are allowed to mention another user in the group by long-pressing on their avatar.
@ -178,15 +178,24 @@ class TIMUIKitChatConfig {
/// [Default]: true on Desktop while false on Mobile. /// [Default]: true on Desktop while false on Mobile.
final bool? isEnableTextSelection; final bool? isEnableTextSelection;
/// Controls whether enable the control bar shows when hovering a message on Desktop.
/// [Default]: true.
final bool isUseMessageHoverBarOnDesktop;
/// Define the lines in the text message input field on Desktop.
final int desktopMessageInputFieldLines;
const TIMUIKitChatConfig( const TIMUIKitChatConfig(
{this.onTapLink, {this.onTapLink,
this.timeDividerConfig, this.timeDividerConfig,
this.isAutoReportRead = true, this.isAutoReportRead = true,
this.faceURIPrefix, this.faceURIPrefix,
this.faceURISuffix, this.faceURISuffix,
this.textHeight = 1.3, this.textHeight = 1.3,
this.desktopMessageInputFieldLines = 6,
this.isAtWhenReply = true, this.isAtWhenReply = true,
this.notificationAndroidSound = "", this.notificationAndroidSound = "",
this.isUseMessageHoverBarOnDesktop = true,
this.isSupportMarkdownForTextMessage = false, this.isSupportMarkdownForTextMessage = false,
this.notificationExt, this.notificationExt,
this.isUseMessageReaction = true, this.isUseMessageReaction = true,
@ -212,7 +221,7 @@ class TIMUIKitChatConfig {
this.desktopControlBarConfig, this.desktopControlBarConfig,
this.isAllowLongPressMessage = true, this.isAllowLongPressMessage = true,
this.isAllowClickAvatar = true, this.isAllowClickAvatar = true,
this.isEnableTextSelection, this.isEnableTextSelection,
this.additionalDesktopMessageHoverBarItem, this.additionalDesktopMessageHoverBarItem,
this.isShowGroupReadingStatus = true, this.isShowGroupReadingStatus = true,
this.isReportGroupReadingStatus = true, this.isReportGroupReadingStatus = true,

View File

@ -64,6 +64,7 @@ class MultiSelectPanel extends TIMUIKitStatelessWidget {
Provider.of<TUIChatSeparateViewModel>(context); Provider.of<TUIChatSeparateViewModel>(context);
return TUIKitScreenUtils.getDeviceWidget( return TUIKitScreenUtils.getDeviceWidget(
context: context,
desktopWidget: Container( desktopWidget: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: theme.selectPanelBgColor ?? theme.primaryColor, color: theme.selectPanelBgColor ?? theme.primaryColor,

View File

@ -64,8 +64,6 @@ class TIMUIKitConversation extends StatefulWidget {
final bool isShowOnlineStatus; final bool isShowOnlineStatus;
/// Control if shows the identifier that the conversation has a draft text, inputted in previous. /// Control if shows the identifier that the conversation has a draft text, inputted in previous.
/// Also, you have better specifying the `draftText` field for `TIMUIKitChat`, from the `draftText` in `V2TimConversation`,
/// to meet the identifier shows here.
final bool isShowDraft; final bool isShowDraft;
const TIMUIKitConversation( const TIMUIKitConversation(
@ -375,7 +373,7 @@ class _TIMUIKitConversationState extends TIMUIKitState<TIMUIKitConversation> {
: isPined : isPined
? theme.conversationItemPinedBgColor ? theme.conversationItemPinedBgColor
: theme.conversationItemBgColor, : theme.conversationItemBgColor,
child: InkWell( child: GestureDetector(
child: TIMUIKitConversationItem( child: TIMUIKitConversationItem(
isCurrent: isCurrent, isCurrent: isCurrent,
isShowDraft: widget.isShowDraft, isShowDraft: widget.isShowDraft,
@ -402,11 +400,12 @@ class _TIMUIKitConversationState extends TIMUIKitState<TIMUIKitConversation> {
} }
return TUIKitScreenUtils.getDeviceWidget( return TUIKitScreenUtils.getDeviceWidget(
context: context,
desktopWidget: AutoScrollTag( desktopWidget: AutoScrollTag(
key: ValueKey(conversationItem.conversationID), key: ValueKey(conversationItem.conversationID),
controller: _autoScrollController, controller: _autoScrollController,
index: index, index: index,
child: GestureDetector( child: InkWell(
onSecondaryTapDown: (details) { onSecondaryTapDown: (details) {
TUIKitWidePopup.showPopupWindow( TUIKitWidePopup.showPopupWindow(
operationKey: TUIKitWideModalOperationKey operationKey: TUIKitWideModalOperationKey
@ -450,6 +449,7 @@ class _TIMUIKitConversationState extends TIMUIKitState<TIMUIKitConversation> {
} }
return TUIKitScreenUtils.getDeviceWidget( return TUIKitScreenUtils.getDeviceWidget(
context: context,
defaultWidget: SlidableAutoCloseBehavior( defaultWidget: SlidableAutoCloseBehavior(
child: EasyRefresh( child: EasyRefresh(
header: CustomizeBallPulseHeader(color: theme.primaryColor), header: CustomizeBallPulseHeader(color: theme.primaryColor),

View File

@ -34,6 +34,7 @@ class _AddGroupMemberPageState extends TIMUIKitState<AddGroupMemberPage> {
final TUITheme theme = value.theme; final TUITheme theme = value.theme;
return TUIKitScreenUtils.getDeviceWidget( return TUIKitScreenUtils.getDeviceWidget(
context: context,
desktopWidget: Container( desktopWidget: Container(
padding: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.symmetric(horizontal: 16),
child: ContactList( child: ContactList(

View File

@ -82,6 +82,7 @@ class _DeleteGroupMemberPageState extends TIMUIKitState<DeleteGroupMemberPage> {
final TUITheme theme = value.theme; final TUITheme theme = value.theme;
return TUIKitScreenUtils.getDeviceWidget( return TUIKitScreenUtils.getDeviceWidget(
context: context,
desktopWidget: Container( desktopWidget: Container(
padding: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.symmetric(horizontal: 16),
child: GroupProfileMemberList( child: GroupProfileMemberList(

View File

@ -376,7 +376,7 @@ class _GroupProfileGroupManagePageState
size: 16), size: 16),
onClick: () { onClick: () {
widget.model.muteGroupMember( widget.model.muteGroupMember(
e?.userID ?? "", e.userID,
false, false,
serverTime); serverTime);
onClose(); onClose();
@ -409,6 +409,7 @@ class _GroupProfileGroupManagePageState
} }
return TUIKitScreenUtils.getDeviceWidget( return TUIKitScreenUtils.getDeviceWidget(
context: context,
desktopWidget: managePage(), desktopWidget: managePage(),
defaultWidget: Scaffold( defaultWidget: Scaffold(
appBar: AppBar( appBar: AppBar(
@ -501,6 +502,7 @@ Widget _buildListItem(BuildContext context, V2TimGroupMemberFullInfo memberInfo,
} }
return TUIKitScreenUtils.getDeviceWidget( return TUIKitScreenUtils.getDeviceWidget(
context: context,
desktopWidget: nameItem(), desktopWidget: nameItem(),
defaultWidget: SingleChildScrollView( defaultWidget: SingleChildScrollView(
child: Slidable(endActionPane: endActionPane, child: nameItem()))); child: Slidable(endActionPane: endActionPane, child: nameItem())));
@ -724,7 +726,7 @@ class _GroupProfileSetManagerPageState
Icons.remove_circle_outline, Icons.remove_circle_outline,
size: 16), size: 16),
onClick: () { onClick: () {
_removeAdmin(context, e!); _removeAdmin(context, e);
onClose(); onClose();
}), }),
])); ]));
@ -758,6 +760,7 @@ class _GroupProfileSetManagerPageState
} }
return TUIKitScreenUtils.getDeviceWidget( return TUIKitScreenUtils.getDeviceWidget(
context: context,
desktopWidget: adminPage(), desktopWidget: adminPage(),
defaultWidget: Scaffold( defaultWidget: Scaffold(
appBar: AppBar( appBar: AppBar(
@ -876,6 +879,7 @@ class _GroupProfileAddAdminState extends TIMUIKitState<GroupProfileAddAdmin> {
} }
return TUIKitScreenUtils.getDeviceWidget( return TUIKitScreenUtils.getDeviceWidget(
context: context,
desktopWidget: Container( desktopWidget: Container(
padding: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.symmetric(horizontal: 16),
child: addAdminPage(), child: addAdminPage(),

View File

@ -76,11 +76,11 @@ class TIMUIKitProfile extends StatefulWidget {
/// The life cycle hooks for user profile business logic /// The life cycle hooks for user profile business logic
final ProfileLifeCycle? lifeCycle; final ProfileLifeCycle? lifeCycle;
/// If the loading user is self. /// Whether the specify user is current logged in user.
/// Default: [false]. /// Default: [false].
final bool isSelf; final bool isSelf;
/// Is use the small card mode on Desktop. Usually shows on the Chat page. /// Whether use the small card mode on Desktop. Usually shows on the Chat page.
final bool smallCardMode; final bool smallCardMode;
const TIMUIKitProfile( const TIMUIKitProfile(

View File

@ -27,6 +27,7 @@ class TIMUIKitProfileUserInfoCard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return TUIKitScreenUtils.getDeviceWidget( return TUIKitScreenUtils.getDeviceWidget(
context: context,
defaultWidget: TIMUIKitProfileUserInfoCardNarrow( defaultWidget: TIMUIKitProfileUserInfoCardNarrow(
userInfo: userInfo, userInfo: userInfo,
isJumpToPersonalProfile: isJumpToPersonalProfile, isJumpToPersonalProfile: isJumpToPersonalProfile,

View File

@ -54,6 +54,7 @@ class TIMUIKitSearchItem extends TIMUIKitStatelessWidget {
final TUITheme theme = value.theme; final TUITheme theme = value.theme;
return TUIKitScreenUtils.getDeviceWidget( return TUIKitScreenUtils.getDeviceWidget(
context: context,
defaultWidget: GestureDetector( defaultWidget: GestureDetector(
onTap: onClick, onTap: onClick,
child: Container( child: Container(

View File

@ -10,13 +10,16 @@ class LinkPreviewEntry {
/// get the text message with hyperlinks /// get the text message with hyperlinks
static LinkPreviewText? getHyperlinksText(String messageText, bool isMarkdown, static LinkPreviewText? getHyperlinksText(String messageText, bool isMarkdown,
{Function(String)? onLinkTap, {Function(String)? onLinkTap,
bool? isEnableTextSelection, bool isEnableTextSelection = false,
bool isUseDefaultEmoji = false, bool isUseDefaultEmoji = false,
List customEmojiStickerList = const []}) { List customEmojiStickerList = const []}) {
return ({TextStyle? style}) { return ({TextStyle? style}) {
return isMarkdown return isMarkdown
? LinkTextMarkdown( ? LinkTextMarkdown(
messageText: messageText, style: style, onLinkTap: onLinkTap) isEnableTextSelection: isEnableTextSelection,
messageText: replaceSingleNewlineWithTwo(messageText),
style: style,
onLinkTap: onLinkTap)
: LinkText( : LinkText(
isEnableTextSelection: isEnableTextSelection, isEnableTextSelection: isEnableTextSelection,
messageText: messageText, messageText: messageText,
@ -27,6 +30,13 @@ class LinkPreviewEntry {
}; };
} }
static String replaceSingleNewlineWithTwo(String inputText) {
return inputText.replaceAllMapped(
RegExp(r'(?<!\n)\n(?!\n)'),
(match) => '\n\n',
);
}
/// get the [LinkPreviewContent] with preview widget and website information for the first link. /// get the [LinkPreviewContent] with preview widget and website information for the first link.
/// If you provide `onUpdateMessage(String linkInfoJson)`, it can save the link info to local custom data than call updating the message on UI automatically. /// If you provide `onUpdateMessage(String linkInfoJson)`, it can save the link info to local custom data than call updating the message on UI automatically.
static Future<LinkPreviewContent?> getFirstLinkPreviewContent( static Future<LinkPreviewContent?> getFirstLinkPreviewContent(

View File

@ -1,8 +1,7 @@
// ignore_for_file: deprecated_member_use // ignore_for_file: deprecated_member_use
import 'package:tencent_cloud_chat_uikit/ui/utils/platform.dart'; import 'package:tencent_cloud_chat_uikit/ui/utils/platform.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/screen_utils.dart'; import 'package:extended_text/extended_text.dart';
import 'package:tencent_extended_text/extended_text.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:flutter_markdown/flutter_markdown.dart';
@ -20,14 +19,21 @@ class LinkTextMarkdown extends TIMStatelessWidget {
/// text style for default words /// text style for default words
final TextStyle? style; final TextStyle? style;
final bool? isEnableTextSelection;
const LinkTextMarkdown( const LinkTextMarkdown(
{Key? key, required this.messageText, this.onLinkTap, this.style}) {Key? key,
required this.messageText,
this.isEnableTextSelection,
this.onLinkTap,
this.style})
: super(key: key); : super(key: key);
@override @override
Widget timBuild(BuildContext context) { Widget timBuild(BuildContext context) {
return MarkdownBody( return MarkdownBody(
data: messageText, data: messageText,
selectable: isEnableTextSelection ?? false,
styleSheet: MarkdownStyleSheet.fromTheme(ThemeData( styleSheet: MarkdownStyleSheet.fromTheme(ThemeData(
textTheme: TextTheme( textTheme: TextTheme(
bodyText2: style ?? const TextStyle(fontSize: 16.0)))) bodyText2: style ?? const TextStyle(fontSize: 16.0))))
@ -40,9 +46,9 @@ class LinkTextMarkdown extends TIMStatelessWidget {
String title, String title,
) { ) {
if (onLinkTap != null) { if (onLinkTap != null) {
onLinkTap!(link); onLinkTap!(href ?? "");
} else { } else {
LinkUtils.launchURL(context, link); LinkUtils.launchURL(context, href ?? "");
} }
}, },
); );
@ -130,15 +136,8 @@ class LinkText extends TIMStatelessWidget {
@override @override
Widget timBuild(BuildContext context) { Widget timBuild(BuildContext context) {
final isDesktopScreen = return ExtendedText(_getContentSpan(messageText, context), softWrap: true,
TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop; onSpecialTextTap: (dynamic parameter) {
return
// Text.rich(
// TextSpan(children: [..._getContentSpan(messageText, context)]),
// style: style ?? const TextStyle(fontSize: 16.0),
// );
ExtendedText(_getContentSpan(messageText, context), softWrap: true,
onSpecialTextTap: (dynamic parameter) {
if (parameter.toString().startsWith('\$')) { if (parameter.toString().startsWith('\$')) {
if (onLinkTap != null) { if (onLinkTap != null) {
onLinkTap!((parameter.toString()).replaceAll('\$', '')); onLinkTap!((parameter.toString()).replaceAll('\$', ''));
@ -148,14 +147,11 @@ class LinkText extends TIMStatelessWidget {
} }
} }
}, },
selectionEnabled: isEnableTextSelection != null style: style ?? const TextStyle(fontSize: 16.0),
? isEnableTextSelection! specialTextSpanBuilder: DefaultSpecialTextSpanBuilder(
: isDesktopScreen, isUseDefaultEmoji: isUseDefaultEmoji,
style: style ?? const TextStyle(fontSize: 16.0), customEmojiStickerList: customEmojiStickerList,
specialTextSpanBuilder: DefaultSpecialTextSpanBuilder( showAtBackground: true,
isUseDefaultEmoji: isUseDefaultEmoji, ));
customEmojiStickerList: customEmojiStickerList,
showAtBackground: true,
));
} }
} }

View File

@ -305,6 +305,7 @@ class MergerMessageScreenState extends TIMUIKitState<MergerMessageScreen> {
} }
return TUIKitScreenUtils.getDeviceWidget( return TUIKitScreenUtils.getDeviceWidget(
context: context,
desktopWidget: Container( desktopWidget: Container(
padding: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.symmetric(horizontal: 16),
child: messageListPage(), child: messageListPage(),

View File

@ -362,6 +362,7 @@ class _MessageReadReceiptState extends TIMUIKitState<MessageReadReceipt> {
} }
return TUIKitScreenUtils.getDeviceWidget( return TUIKitScreenUtils.getDeviceWidget(
context: context,
desktopWidget: pageBody(), desktopWidget: pageBody(),
defaultWidget: DefaultTabController( defaultWidget: DefaultTabController(
length: 2, length: 2,

View File

@ -227,7 +227,7 @@ class TextInputBottomSheet {
), ),
)); ));
}); });
Overlay.of(context)?.insert(entry!); Overlay.of(context).insert(entry!);
} else { } else {
showModalBottomSheet( showModalBottomSheet(
isScrollControlled: true, // !important isScrollControlled: true, // !important

View File

@ -116,6 +116,7 @@ class _SelectNewGroupOwner extends TIMUIKitState<SelectNewGroupOwner> {
} }
return TUIKitScreenUtils.getDeviceWidget( return TUIKitScreenUtils.getDeviceWidget(
context: context,
defaultWidget: Scaffold( defaultWidget: Scaffold(
appBar: AppBar( appBar: AppBar(
shadowColor: theme.weakBackgroundColor, shadowColor: theme.weakBackgroundColor,

View File

@ -2,10 +2,10 @@
import 'dart:async'; import 'dart:async';
import 'package:chewie/chewie.dart'; import 'package:chewie_for_us/chewie_for_us.dart';
import 'package:chewie/src/helpers/utils.dart'; import 'package:chewie_for_us/src/helpers/utils.dart';
import 'package:chewie/src/animated_play_pause.dart'; import 'package:chewie_for_us/src/animated_play_pause.dart';
import 'package:chewie/src/material/material_progress_bar.dart'; import 'package:chewie_for_us/src/material/material_progress_bar.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:loading_animation_widget/loading_animation_widget.dart'; import 'package:loading_animation_widget/loading_animation_widget.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart'; import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart';

View File

@ -12,7 +12,7 @@ import 'package:permission_handler/permission_handler.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_chat_global_model.dart'; import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_chat_global_model.dart';
import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart'; import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart';
import 'package:universal_html/html.dart' as html; import 'package:universal_html/html.dart' as html;
import 'package:chewie/chewie.dart'; import 'package:chewie_for_us/chewie_for_us.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart'; import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/permission.dart'; import 'package:tencent_cloud_chat_uikit/ui/utils/permission.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/platform.dart'; import 'package:tencent_cloud_chat_uikit/ui/utils/platform.dart';
@ -89,7 +89,7 @@ class _VideoScreenState extends TIMUIKitState<VideoScreen> {
} else { } else {
final DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); final DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo; AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
if ((androidInfo.version.sdkInt ?? 0) >= 33) { if ((androidInfo.version.sdkInt) >= 33) {
final videos = await Permissions.checkPermission( final videos = await Permissions.checkPermission(
context,Permission.videos.value, context,Permission.videos.value,
); );

View File

@ -88,142 +88,145 @@ class TUIKitWidePopup {
return; return;
} }
entry = OverlayEntry(builder: (BuildContext context) { entry = OverlayEntry(builder: (BuildContext context) {
return TUIKitDragArea( return Material(
backgroundColor: isDarkBackground ? const Color(0x7F000000) : null, color: Colors.transparent,
closeFun: () { child: TUIKitDragArea(
if (entry != null) { backgroundColor: isDarkBackground ? const Color(0x7F000000) : null,
entry?.remove(); closeFun: () {
entry = null; if (entry != null) {
} entry?.remove();
}, entry = null;
initOffset: offset ?? }
(width != null && height != null },
? Offset(MediaQuery.of(context).size.width * 0.5 - width / 2, initOffset: offset ??
MediaQuery.of(context).size.height * 0.5 - height / 2) (width != null && height != null
: null), ? Offset(MediaQuery.of(context).size.width * 0.5 - width / 2,
child: Container( MediaQuery.of(context).size.height * 0.5 - height / 2)
width: width, : null),
height: height, child: Container(
decoration: BoxDecoration( width: width,
borderRadius: height: height,
borderRadius ?? const BorderRadius.all(Radius.circular(16)), decoration: BoxDecoration(
color: theme?.wideBackgroundColor ?? const Color(0xFFffffff), borderRadius:
border: isDarkBackground borderRadius ?? const BorderRadius.all(Radius.circular(16)),
? Border.all( color: theme?.wideBackgroundColor ?? const Color(0xFFffffff),
width: 2, border: isDarkBackground
color: ? Border.all(
theme?.weakBackgroundColor ?? const Color(0xFFbebebe), width: 2,
) color:
: null, theme?.weakBackgroundColor ?? const Color(0xFFbebebe),
boxShadow: isDarkBackground )
? null : null,
: const [ boxShadow: isDarkBackground
BoxShadow( ? null
color: Color(0xFFbebebe), : const [
offset: Offset(3, 3), BoxShadow(
blurRadius: 10, color: Color(0xFFbebebe),
spreadRadius: 1, offset: Offset(3, 3),
), blurRadius: 10,
], spreadRadius: 1,
),
child: Column(
children: [
if (title != null)
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: hexToColor("f5f6f7"),
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(16),
topRight: Radius.circular(16)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.max,
children: [
Text(
title,
style: TextStyle(
fontSize: 18,
color: theme?.darkTextColor ??
const Color(0xFF444444)),
),
InkWell(
onTap: () {
if (onSubmit != null) {
onSubmit();
}
entry?.remove();
entry = null;
},
child: onSubmit != null
? (submitWidget ?? const Icon(Icons.check))
: const Icon(Icons.close),
)
],
),
), ),
if (title != null) ],
SizedBox( ),
height: 1, child: Column(
child: Container( children: [
color: theme?.weakDividerColor ?? const Color(0xFFE5E6E9), if (title != null)
), Container(
), padding: const EdgeInsets.all(16),
if (height != null && width != null) decoration: BoxDecoration(
Expanded(child: child(() { color: hexToColor("f5f6f7"),
entry?.remove(); borderRadius: const BorderRadius.only(
entry = null; topLeft: Radius.circular(16),
})), topRight: Radius.circular(16)),
if (height == null || width == null) ),
child(() { child: Row(
entry?.remove(); mainAxisAlignment: MainAxisAlignment.spaceBetween,
entry = null; mainAxisSize: MainAxisSize.max,
}), children: [
if (onCancel != null || onConfirm != null) Text(
Container( title,
padding: const EdgeInsets.only(bottom: 16), style: TextStyle(
child: Row( fontSize: 18,
mainAxisAlignment: MainAxisAlignment.end, color: theme?.darkTextColor ??
children: [ const Color(0xFF444444)),
if (onCancel != null)
Container(
margin: const EdgeInsets.only(right: 16),
child: OutlinedButton(
onPressed: () {
entry?.remove();
entry = null;
onCancel();
},
child: Text(
TIM_t("取消"),
style: TextStyle(
color:
theme?.weakTextColor ?? Colors.black),
)),
), ),
if (onConfirm != null) InkWell(
Container( onTap: () {
margin: const EdgeInsets.only(right: 16), if (onSubmit != null) {
child: ElevatedButton( onSubmit();
onPressed: () { }
entry?.remove(); entry?.remove();
entry = null; entry = null;
onConfirm(); },
}, child: onSubmit != null
child: Text( ? (submitWidget ?? const Icon(Icons.check))
TIM_t("确定"), : const Icon(Icons.close),
style: TextStyle(color: theme?.primaryColor), )
)), ],
), ),
],
), ),
) if (title != null)
], SizedBox(
), height: 1,
)); child: Container(
color: theme?.weakDividerColor ?? const Color(0xFFE5E6E9),
),
),
if (height != null && width != null)
Expanded(child: child(() {
entry?.remove();
entry = null;
})),
if (height == null || width == null)
child(() {
entry?.remove();
entry = null;
}),
if (onCancel != null || onConfirm != null)
Container(
padding: const EdgeInsets.only(bottom: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
if (onCancel != null)
Container(
margin: const EdgeInsets.only(right: 16),
child: OutlinedButton(
onPressed: () {
entry?.remove();
entry = null;
onCancel();
},
child: Text(
TIM_t("取消"),
style: TextStyle(
color:
theme?.weakTextColor ?? Colors.black),
)),
),
if (onConfirm != null)
Container(
margin: const EdgeInsets.only(right: 16),
child: ElevatedButton(
onPressed: () {
entry?.remove();
entry = null;
onConfirm();
},
child: Text(
TIM_t("确定"),
style: TextStyle(color: theme?.primaryColor),
)),
),
],
),
)
],
),
)),
);
}); });
Overlay.of(context)?.insert(entry!); Overlay.of(context).insert(entry!);
} }
} }

0
pub
View File

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
name: tencent_cloud_chat_uikit name: tencent_cloud_chat_uikit
description: A powerful chat UI component library and business logic for Tencent Cloud Chat, creating seamless in-app chat modules for delightful user experiences. description: A powerful chat UI component library and business logic for Tencent Cloud Chat, creating seamless in-app chat modules for delightful user experiences.
version: 2.0.0 version: 2.1.0
homepage: https://www.tencentcloud.com/products/im?from=pub homepage: https://www.tencentcloud.com/products/im?from=pub
repository: https://github.com/TencentCloud/tc-chat-uikit-flutter repository: https://github.com/TencentCloud/tc-chat-uikit-flutter
documentation: https://comm.qq.com/im/doc/flutter/en/TUIKit/readme.html documentation: https://comm.qq.com/im/doc/flutter/en/TUIKit/readme.html
@ -14,23 +14,23 @@ platforms:
windows: windows:
environment: environment:
sdk: ">=2.17.0 <3.0.0" sdk: '>=3.0.0 <4.0.0'
flutter: ">=3.0.0" flutter: ">=3.10.0"
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
adaptive_action_sheet: ^2.0.1 adaptive_action_sheet: ^2.0.1
provider: ^6.0.1 provider: ^6.0.1
intl: ^0.17.0 intl: ^0.18.0
get_it: ^7.2.0 get_it: ^7.2.0
dotted_border: ^2.0.0+2 dotted_border: ^2.0.0+2
flutter_svg: ^1.0.0 flutter_svg: ^1.0.0
image_picker: ^0.8.5+3 image_picker: ^0.8.5+3
file_picker: ^5.2.9 file_picker: ^5.3.0
tencent_super_tooltip: ^0.0.1 tencent_super_tooltip: ^0.0.1
video_player: ^2.4.2 video_player: ^2.4.2
chewie: ^1.3.2 chewie_for_us: ^1.5.0
flutter_slidable_for_tencent_im: ^1.4.0 flutter_slidable_for_tencent_im: ^1.4.0
flutter_plugin_record_plus: ^0.0.15 flutter_plugin_record_plus: ^0.0.15
azlistview_all_platforms: ^2.1.2 azlistview_all_platforms: ^2.1.2
@ -41,13 +41,13 @@ dependencies:
cached_network_image: ^3.2.0 cached_network_image: ^3.2.0
shared_preferences: ^2.0.13 shared_preferences: ^2.0.13
scroll_to_index: ^2.1.1 scroll_to_index: ^2.1.1
wechat_assets_picker: ^7.2.0 wechat_assets_picker: ^8.5.0
tencent_wechat_camera_picker: ^3.6.5 wechat_camera_picker: ^3.8.0
flutter_easyrefresh: ^2.2.1 flutter_easyrefresh: ^2.2.1
extended_image: ^6.0.2+1 extended_image: ^8.0.1
tencent_extended_text_field: ^1.0.0 extended_text_field: ^12.0.0
tencent_extended_text: ^1.0.0 extended_text: ^11.0.0
package_info_plus: ^1.4.0 package_info_plus: ^4.0.1
loading_animation_widget: ^1.1.0+3 loading_animation_widget: ^1.1.0+3
permission_handler: ^10.2.0 permission_handler: ^10.2.0
tuple: ^2.0.0 tuple: ^2.0.0
@ -64,14 +64,14 @@ dependencies:
tencent_open_file: ^4.0.10 tencent_open_file: ^4.0.10
tencent_keyboard_visibility: ^1.0.1 tencent_keyboard_visibility: ^1.0.1
tim_ui_kit_sticker_plugin: ^2.0.1 tim_ui_kit_sticker_plugin: ^2.0.1
tencent_im_base: ^1.0.51 tencent_im_base: ^1.0.57
fc_native_video_thumbnail_for_us: ^0.4.8+1 fc_native_video_thumbnail_for_us: ^0.4.8+1
audioplayers: ^3.0.1 audioplayers: ^3.0.1
path: ^1.8.1 path: ^1.8.1
tencent_cloud_uikit_core: ^1.0.2 tencent_cloud_uikit_core: ^1.0.2
pasteboard: ^0.2.0 pasteboard: ^0.2.0
desktop_drop: ^0.4.1 desktop_drop: ^0.4.1
device_info_plus: ^4.1.3 device_info_plus: ^9.0.1
cross_file: ^0.3.3+4 cross_file: ^0.3.3+4
diff_match_patch: ^0.4.1 diff_match_patch: ^0.4.1
@ -89,6 +89,7 @@ flutter:
uses-material-design: true uses-material-design: true
assets: assets:
- images/ - images/
- images/svg/
# - assets/custom_face_resource/4349/ # - assets/custom_face_resource/4349/
# To add assets to your package, add an assets section, like this: # To add assets to your package, add an assets section, like this:

View File

@ -0,0 +1,4 @@
void main() {
}