Flutter TUIKit V2.1.0
52
CHANGELOG.md
|
|
@ -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
|
||||
|
||||
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.
|
||||
|
|
|
|||
13
README.md
|
|
@ -74,19 +74,6 @@ Official Documentation</button></a>
|
|||
|
||||
<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
|
||||
|
||||
Experience our Chat and Voice/Video Call modules by trying out our sample apps.
|
||||
|
|
|
|||
|
|
@ -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('=', '_');
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,5 @@
|
|||
// 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:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
|
||||
import 'TIMUIKitAddFriendExample.dart';
|
||||
|
|
@ -74,12 +70,15 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||
return const String.fromEnvironment('SECRET', defaultValue: "");
|
||||
}
|
||||
|
||||
String getUsersig() {
|
||||
return const String.fromEnvironment('USERSIG', defaultValue: "");
|
||||
}
|
||||
|
||||
initTIMUIKIT() async {
|
||||
int sdkappid = getSDKAPPID();
|
||||
String userid = getUserID();
|
||||
String secret = getSecret();
|
||||
String usersig = GenerateTestUserSig(sdkappid: sdkappid, key: secret)
|
||||
.genSig(identifier: userid, expire: 24 * 7 * 60 * 60 * 1000);
|
||||
String usersig = getUsersig();
|
||||
if (sdkappid == 0 || userid == '' || secret == '' || usersig == '') {
|
||||
print("The running parameters are abnormal, please check");
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -7,16 +7,15 @@ import Foundation
|
|||
|
||||
import audioplayers_darwin
|
||||
import desktop_drop
|
||||
import device_info_plus_macos
|
||||
import device_info_plus
|
||||
import fc_native_video_thumbnail_for_us
|
||||
import package_info_plus_macos
|
||||
import package_info_plus
|
||||
import pasteboard
|
||||
import path_provider_foundation
|
||||
import photo_manager
|
||||
import shared_preferences_foundation
|
||||
import sqflite
|
||||
import url_launcher_macos
|
||||
import wakelock_macos
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin"))
|
||||
|
|
@ -30,5 +29,4 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||
WakelockMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockMacosPlugin"))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,10 +45,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: async
|
||||
sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0
|
||||
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.10.0"
|
||||
version: "2.11.0"
|
||||
audioplayers:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -197,10 +197,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: characters
|
||||
sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c
|
||||
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
version: "1.3.0"
|
||||
charcode:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -209,14 +209,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
chewie:
|
||||
chewie_for_us:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: chewie
|
||||
sha256: e9da4898ee4859825404f507969f57113c04ca0060e152b95c9afd73934126ad
|
||||
name: chewie_for_us
|
||||
sha256: "0307723e811508d361fffa6f8bbd9040b1bfea5536544e4d655e10c27de002ec"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
version: "1.5.0"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -229,10 +229,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0
|
||||
sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.17.0"
|
||||
version: "1.17.1"
|
||||
convert:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -293,50 +293,18 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: device_info_plus
|
||||
sha256: b809c4ed5f7fcdb325ccc70b80ad934677dc4e2aa414bf46859a42bfdfafcbb6
|
||||
sha256: "2c35b6d1682b028e42d07b3aee4b98fa62996c10bc12cb651ec856a80d6a761b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.3"
|
||||
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"
|
||||
version: "9.0.2"
|
||||
device_info_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: device_info_plus_platform_interface
|
||||
sha256: "83fdba24fcf6846d3b10f10dfdc8b6c6d7ada5f8ed21d62ea2909c2dfa043773"
|
||||
sha256: d3b01d5868b50ae571cd1dc6e502fc94d956b665756180f7b16ead09e836fd64
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.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"
|
||||
version: "7.0.0"
|
||||
diff_match_patch:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -365,10 +333,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: extended_image
|
||||
sha256: "5854d0d05ee0c687d1852af9db05f15cfe058520fa56f417075705c5bce965d4"
|
||||
sha256: e77d18f956649ba6e5ecebd0cb68542120886336a75ee673788145bd4c3f0767
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.4.0"
|
||||
version: "8.0.2"
|
||||
extended_image_library:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -377,6 +345,30 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -421,10 +413,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: file_picker
|
||||
sha256: b85eb92b175767fdaa0c543bf3b0d1f610fe966412ea72845fe5ba7801e763ff
|
||||
sha256: "9d6e95ec73abbd31ec54d0e0df8a961017e165aba1395e462e5b31ea0c165daf"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.2.10"
|
||||
version: "5.3.1"
|
||||
file_utils:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -490,10 +482,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: flutter_plugin_android_lifecycle
|
||||
sha256: "60fc7b78455b94e6de2333d2f95196d32cf5c22f4b0b0520a628804cb463503b"
|
||||
sha256: "950e77c2bbe1692bc0874fc7fb491b96a4dc340457f4ea1641443d0a6c1ea360"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.7"
|
||||
version: "2.0.15"
|
||||
flutter_plugin_record_plus:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -636,18 +628,18 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: intl
|
||||
sha256: "910f85bce16fb5c6f614e117efa303e85a1731bb0081edf3604a2ae6e9a3cc91"
|
||||
sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.17.0"
|
||||
version: "0.18.1"
|
||||
js:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: js
|
||||
sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7"
|
||||
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.5"
|
||||
version: "0.6.7"
|
||||
json_annotation:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -708,10 +700,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72"
|
||||
sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.13"
|
||||
version: "0.12.15"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -724,10 +716,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42"
|
||||
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.8.0"
|
||||
version: "1.9.1"
|
||||
mime_type:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -764,50 +756,18 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: package_info_plus
|
||||
sha256: f62d7253edc197fe3c88d7c2ddab82d68f555e778d55390ccc3537eca8e8d637
|
||||
sha256: ceb027f6bc6a60674a233b4a90a7658af1aebdea833da0b5b53c1e9821a78c7b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.3+1"
|
||||
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"
|
||||
version: "4.0.2"
|
||||
package_info_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_info_plus_platform_interface
|
||||
sha256: f7a0c8f1e7e981bc65f8b64137a53fd3c195b18d429fba960babc59a5a1c7ae8
|
||||
sha256: "9bc8ba46813a4cc42c66ab781470711781940780fd8beddd0c3da62506d3a6c6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
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"
|
||||
version: "2.0.1"
|
||||
pasteboard:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -820,10 +780,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: path
|
||||
sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b
|
||||
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.8.2"
|
||||
version: "1.8.3"
|
||||
path_drawing:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -948,10 +908,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: photo_manager
|
||||
sha256: "55d50ad1b8f984c57fa7c4bd4980f4760e80d3d9355263cf72624a6ff1bf2b5b"
|
||||
sha256: bdc4ab1fa9fb064d8ccfea6ab44119f55b220293d7ce2e19eb5a5f998db86c88
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.2"
|
||||
version: "2.6.0"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -964,10 +924,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: plugin_platform_interface
|
||||
sha256: dbf0f707c78beedc9200146ad3cb0ab4d5da13c246336987be6940f026500d3a
|
||||
sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
version: "2.1.4"
|
||||
pointycastle:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1177,73 +1137,49 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: tencent_cloud_chat_sdk
|
||||
sha256: "765a93262a41080e155ce5b8a6ca20147a81c7d306f7f87444077c5eaae87e08"
|
||||
sha256: f98bdb55164051e2b196cac6e2e79e60248ed8351dc5a91d25568712ccb15839
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.1.5"
|
||||
version: "5.1.7"
|
||||
tencent_cloud_chat_uikit:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: ".."
|
||||
relative: true
|
||||
source: path
|
||||
version: "2.0.0+1"
|
||||
version: "2.1.0"
|
||||
tencent_cloud_uikit_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: tencent_cloud_uikit_core
|
||||
sha256: "829dfde0c4fbdae019ba233f7f2c299e7cbd18c3ae20ecfe3ab4a43084a33064"
|
||||
sha256: "517d760b0d497ea291d70fe6a021508e5b66f0754c72679f19ee16c0bb354e91"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
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"
|
||||
version: "1.0.3"
|
||||
tencent_im_base:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: tencent_im_base
|
||||
sha256: "516356a80f43b94a6c0719b54e4c641cb1f164830b2b3e887d175ae862ebab3f"
|
||||
sha256: "9b8e712bf27ffae9b686ec532ee8417b8263eba8bab04f105e28a95de1807322"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.51"
|
||||
version: "1.0.57"
|
||||
tencent_im_sdk_plugin_desktop:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: tencent_im_sdk_plugin_desktop
|
||||
sha256: "5fe5ab0765183185fe4a1f94ce1fdc6ab0a450b8522011806549678edb52130d"
|
||||
sha256: "8d986f2f6aedeac8d771286e31b7bbb9bbee12192461fc879c857be903a41a7f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.13"
|
||||
version: "0.1.19"
|
||||
tencent_im_sdk_plugin_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: tencent_im_sdk_plugin_platform_interface
|
||||
sha256: "04043582f1af698b4abe12d53cd0f043466228fae712677688988d8ff7bfc1f1"
|
||||
sha256: "53263e4acd7179871aad2a67ec4964bc8fae861f54384fba2c60bd2c16d2867c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.19"
|
||||
version: "0.3.20"
|
||||
tencent_im_sdk_plugin_web:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -1276,14 +1212,6 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1296,10 +1224,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206
|
||||
sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.4.16"
|
||||
version: "0.5.1"
|
||||
tim_ui_kit_sticker_plugin:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1432,10 +1360,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: video_player
|
||||
sha256: "59f7f31c919c59cbedd37c617317045f5f650dc0eeb568b0b0de9a36472bdb28"
|
||||
sha256: "868a139229acb5018d22aded3eb9cb4767ff43a8216573c086b6c535a4957481"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.1"
|
||||
version: "2.6.0"
|
||||
video_player_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1468,22 +1396,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.13"
|
||||
wakelock:
|
||||
wakelock_for_us:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: wakelock
|
||||
sha256: "769ecf42eb2d07128407b50cb93d7c10bd2ee48f0276ef0119db1d25cc2f87db"
|
||||
name: wakelock_for_us
|
||||
sha256: b73bfa90e5e764f41155063ae92fbd1e3a04ee6372e65ff7d288d2c3057f9498
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.2"
|
||||
wakelock_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: wakelock_macos
|
||||
sha256: "047c6be2f88cb6b76d02553bca5a3a3b95323b15d30867eca53a19a0a319d4cd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.4.0"
|
||||
version: "0.6.3"
|
||||
wakelock_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1492,22 +1412,6 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1520,18 +1424,34 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: wechat_assets_picker
|
||||
sha256: "49184fbc83f855bade59961566a6323a2015634ece1f889de5af6fa133a10706"
|
||||
sha256: "5aeac81c6a28e1142a2c9ba9ee802b909c2dad9186d9a58dbe4eb74493af4743"
|
||||
url: "https://pub.dev"
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: win32
|
||||
sha256: a6f0236dbda0f63aa9a25ad1ff9a9d8a4eaaa5012da0dc59d21afdb1dc361ca4
|
||||
sha256: "5a751eddf9db89b3e5f9d50c20ab8612296e4e8db69009788d6c8b060a84191c"
|
||||
url: "https://pub.dev"
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1557,5 +1477,5 @@ packages:
|
|||
source: hosted
|
||||
version: "3.1.1"
|
||||
sdks:
|
||||
dart: ">=2.19.0 <3.0.0"
|
||||
flutter: ">=3.7.0"
|
||||
dart: ">=3.0.0 <4.0.0"
|
||||
flutter: ">=3.10.0"
|
||||
|
|
|
|||
|
|
@ -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 |
|
|
@ -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="编组 9备份 3" opacity="0.795169">
|
||||
<rect id="矩形" x="0.5" y="0.5" width="15" height="13" rx="1.75" stroke="#232832"/>
|
||||
<rect id="矩形_2" x="6.25" y="10.25" width="3.5" height="0.5" rx="0.25" stroke="#232832" stroke-width="0.5"/>
|
||||
<path id="路径 8" d="M10.5 3.5L3.59271 10.5222" stroke="#232832" stroke-linecap="round"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 593 B |
|
|
@ -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 |
|
|
@ -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="编组 9备份 2" opacity="0.795169">
|
||||
<path id="矩形" 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="矩形_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 |
|
|
@ -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="编组 9" opacity="0.795169">
|
||||
<circle id="椭圆形" cx="5.5" cy="4.5" r="1" stroke="#232832"/>
|
||||
<rect id="矩形" x="0.5" y="0.5" width="15" height="13" rx="1.75" stroke="#232832"/>
|
||||
<path id="路径 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 |
|
|
@ -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 |
|
|
@ -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="编组 9备份" opacity="0.795169">
|
||||
<rect id="矩形" x="0.5" y="0.5" width="15" height="13" rx="1.75" stroke="#232832"/>
|
||||
<path id="三角形" d="M10.0282 7L6.5 9.1169L6.5 4.8831L10.0282 7Z" stroke="#232832"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 460 B |
|
|
@ -7,6 +7,7 @@ import 'package:flutter/cupertino.dart';
|
|||
|
||||
// ignore: unnecessary_import
|
||||
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:flutter_image_compress/flutter_image_compress.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
|
@ -30,6 +31,7 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
|
|||
final GroupServices _groupServices = serviceLocator<GroupServices>();
|
||||
final TUIChatGlobalModel globalModel = serviceLocator<TUIChatGlobalModel>();
|
||||
final TUIChatModelTools tools = serviceLocator<TUIChatModelTools>();
|
||||
final TUISelfInfoViewModel selfModel = serviceLocator<TUISelfInfoViewModel>();
|
||||
final _uuid = const Uuid();
|
||||
|
||||
ChatLifeCycle? lifeCycle;
|
||||
|
|
@ -57,6 +59,7 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
|
|||
V2TimGroupInfo? _groupInfo;
|
||||
String groupMemberListSeq = "0";
|
||||
List<V2TimGroupMemberFullInfo?>? groupMemberList = [];
|
||||
V2TimGroupMemberFullInfo? selfMemberInfo;
|
||||
double atPositionX = 0.0;
|
||||
double atPositionY = 0.0;
|
||||
int _activeAtIndex = -1;
|
||||
|
|
@ -191,6 +194,7 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
|
|||
isGroupExist = true;
|
||||
_groupInfo = null;
|
||||
groupMemberList?.clear();
|
||||
selfMemberInfo = null;
|
||||
notifyListeners();
|
||||
}
|
||||
if (conversationType == ConvType.c2c) {
|
||||
|
|
@ -216,7 +220,6 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
|
|||
}
|
||||
}
|
||||
}
|
||||
markMessageAsRead();
|
||||
globalModel.lifeCycle = lifeCycle;
|
||||
globalModel.setCurrentConversation(
|
||||
CurrentConversation(conversationID, conversationType ?? ConvType.c2c));
|
||||
|
|
@ -225,6 +228,9 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
|
|||
globalModel.setChatConfig(chatConfig);
|
||||
globalModel.clearRecivedNewMessageCount();
|
||||
_isInit = true;
|
||||
Future.delayed(const Duration(milliseconds: 300), (){
|
||||
markMessageAsRead();
|
||||
});
|
||||
}
|
||||
|
||||
Future<bool> loadListForSpecificMessage({
|
||||
|
|
@ -504,6 +510,8 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
|
|||
return await loadGroupMemberList(
|
||||
groupID: groupID, count: count, seq: nextSeq);
|
||||
} else {
|
||||
selfMemberInfo = groupMemberList
|
||||
?.firstWhere((e) => e?.userID == selfModel.loginInfo?.userID);
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
|
@ -575,6 +583,11 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
|
|||
V2TimMessage? messageInfo,
|
||||
OfflinePushInfo? offlinePushInfo,
|
||||
bool? onlineUserOnly = false,
|
||||
MessagePriorityEnum priority = MessagePriorityEnum.V2TIM_PRIORITY_NORMAL,
|
||||
bool? isExcludedFromUnreadCount,
|
||||
bool? needReadReceipt,
|
||||
String? cloudCustomData,
|
||||
String? localCustomData,
|
||||
bool? isEditStatusMessage = false,
|
||||
}) async {
|
||||
String receiver = convType == ConvType.c2c ? convID : '';
|
||||
|
|
@ -586,9 +599,13 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
|
|||
setLoadingMessageMap(convID, messageInfo);
|
||||
}
|
||||
final sendMsgRes = await _messageService.sendMessage(
|
||||
priority: priority,
|
||||
localCustomData: localCustomData,
|
||||
isExcludedFromUnreadCount: isExcludedFromUnreadCount ?? false,
|
||||
id: id,
|
||||
receiver: receiver,
|
||||
needReadReceipt: chatConfig.isShowGroupReadingStatus &&
|
||||
needReadReceipt: needReadReceipt ??
|
||||
chatConfig.isShowGroupReadingStatus &&
|
||||
convType == ConvType.group &&
|
||||
((chatConfig.groupReadReceiptPermissionList != null &&
|
||||
chatConfig.groupReadReceiptPermissionList!
|
||||
|
|
@ -599,14 +616,15 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
|
|||
groupID: groupID,
|
||||
offlinePushInfo: offlinePushInfo,
|
||||
onlineUserOnly: onlineUserOnly ?? false,
|
||||
cloudCustomData: showC2cMessageEditStatus == true
|
||||
cloudCustomData: cloudCustomData ??
|
||||
(showC2cMessageEditStatus == true
|
||||
? json.encode({
|
||||
"messageFeature": {
|
||||
"needTyping": 1,
|
||||
"version": 1,
|
||||
}
|
||||
})
|
||||
: "",
|
||||
: ""),
|
||||
);
|
||||
if (isEditStatusMessage == false &&
|
||||
globalModel.getMessageListPosition(conversationID) !=
|
||||
|
|
@ -916,6 +934,7 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
|
|||
|
||||
Future<V2TimValueCallback<V2TimMessage>?> sendImageMessage(
|
||||
{String? imagePath,
|
||||
String? imageName,
|
||||
required String convID,
|
||||
dynamic inputElement,
|
||||
required ConvType convType}) async {
|
||||
|
|
@ -939,7 +958,9 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
|
|||
} catch (e) {}
|
||||
}
|
||||
final imageMessageInfo = await _messageService.createImageMessage(
|
||||
imagePath: image ?? imagePath, inputElement: inputElement);
|
||||
imageName: imageName,
|
||||
imagePath: image ?? imagePath,
|
||||
inputElement: inputElement);
|
||||
List<V2TimMessage> currentHistoryMsgList = getOriginMessageList();
|
||||
final messageInfo = imageMessageInfo!.messageInfo;
|
||||
if (messageInfo != null) {
|
||||
|
|
@ -1334,6 +1355,12 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
|
|||
|
||||
/// Offline push info
|
||||
OfflinePushInfo? offlinePushInfo,
|
||||
MessagePriorityEnum priority = MessagePriorityEnum.V2TIM_PRIORITY_NORMAL,
|
||||
bool? onlineUserOnly,
|
||||
bool? isExcludedFromUnreadCount,
|
||||
bool? needReadReceipt,
|
||||
String? cloudCustomData,
|
||||
String? localCustomData,
|
||||
}) {
|
||||
List<V2TimMessage> currentHistoryMsgList = getOriginMessageList();
|
||||
if (messageInfo != null) {
|
||||
|
|
@ -1351,6 +1378,12 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
|
|||
}
|
||||
|
||||
return _sendMessage(
|
||||
priority: priority,
|
||||
onlineUserOnly: onlineUserOnly,
|
||||
isExcludedFromUnreadCount: isExcludedFromUnreadCount,
|
||||
needReadReceipt: needReadReceipt,
|
||||
cloudCustomData: cloudCustomData,
|
||||
localCustomData: localCustomData,
|
||||
convID: conversationID,
|
||||
id: messageInfo.id as String,
|
||||
convType: conversationType ?? ConvType.c2c,
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ class CurrentConversation {
|
|||
CurrentConversation(this.conversationID, this.conversationType);
|
||||
}
|
||||
|
||||
class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
|
||||
class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
|
||||
final MessageService _messageService = serviceLocator<MessageService>();
|
||||
final GroupServices _groupServices = serviceLocator<GroupServices>();
|
||||
final Map<String, List<V2TimMessage>?> _messageListMap = {};
|
||||
|
|
@ -56,8 +56,8 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
|
|||
late V2TimAdvancedMsgListener advancedMsgListener;
|
||||
int _unreadCountForConversation = 0;
|
||||
|
||||
// use for generate a new sliver list to show recived messag list
|
||||
int _recivedNewMessageCount = 0;
|
||||
// use for generate a new sliver list to show received message list
|
||||
int _receivedNewMessageCount = 0;
|
||||
TIMUIKitChatConfig chatConfig = const TIMUIKitChatConfig();
|
||||
List<V2TimGroupApplication>? _groupApplicationList;
|
||||
String Function(V2TimMessage message)? _abstractMessageBuilder;
|
||||
|
|
@ -65,7 +65,7 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
|
|||
Map.from({}); // 0 normal 1 sending
|
||||
final Map<String, bool> _c2cMessageFromUserActiveMap = Map.from({});
|
||||
final Map<String, Timer> _c2cMessageActiveTimer = Map.from({});
|
||||
bool _showC2cMessageEditStaus = true;
|
||||
bool _showC2cMessageEditStatus = true;
|
||||
final Map<String, Timer> _c2cMessageStatusShowTimer = Map.from({});
|
||||
Map<String, List> loadingMessage = {};
|
||||
|
||||
|
|
@ -144,7 +144,7 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
|
|||
print("start another download");
|
||||
}
|
||||
|
||||
int getRecevied(msgID) {
|
||||
int getReceived(msgID) {
|
||||
return messageListProgressMap[msgID] ?? 0;
|
||||
}
|
||||
|
||||
|
|
@ -173,11 +173,11 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
|
|||
}
|
||||
|
||||
int get receivedMessageListCount {
|
||||
return _recivedNewMessageCount;
|
||||
return _receivedNewMessageCount;
|
||||
}
|
||||
|
||||
set receivedNewMessageCount(int value) {
|
||||
_recivedNewMessageCount = value;
|
||||
_receivedNewMessageCount = value;
|
||||
}
|
||||
|
||||
int get unreadCountForConversation => _unreadCountForConversation;
|
||||
|
|
@ -207,6 +207,10 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
|
|||
}
|
||||
|
||||
clearCurrentConversation() {
|
||||
// Only keep the last 20 messages when existing a chat.
|
||||
_messageListMap[currentSelectedConv] =
|
||||
(_messageListMap[currentSelectedConv] ?? []).sublist(
|
||||
max(0, ((_messageListMap[currentSelectedConv] ?? []).length - 20)));
|
||||
_currentConversationList.removeLast();
|
||||
notifyListeners();
|
||||
}
|
||||
|
|
@ -216,7 +220,7 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
|
|||
}
|
||||
|
||||
setShowC2cEditStatus(bool show) {
|
||||
_showC2cMessageEditStaus = show;
|
||||
_showC2cMessageEditStatus = show;
|
||||
}
|
||||
|
||||
/// set edit status from chats
|
||||
|
|
@ -373,14 +377,14 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
|
|||
_totalUnreadCount = 0;
|
||||
_groupApplicationList?.clear();
|
||||
_totalUnreadCount = 0;
|
||||
_recivedNewMessageCount = 0;
|
||||
_receivedNewMessageCount = 0;
|
||||
_messageReadReceiptMap.clear();
|
||||
_messageListProgressMap.clear();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
clearRecivedNewMessageCount() {
|
||||
_recivedNewMessageCount = 0;
|
||||
_receivedNewMessageCount = 0;
|
||||
}
|
||||
|
||||
_preLoadImage(List<V2TimMessage> msgList) {
|
||||
|
|
@ -441,7 +445,6 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
|
|||
return false;
|
||||
});
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
setFileMessageLocation(String msgID, String location) {
|
||||
|
|
@ -526,7 +529,7 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
|
|||
}
|
||||
|
||||
sendEditStatusMessage(bool isEditing, String toUser) async {
|
||||
if (!_showC2cMessageEditStaus) {
|
||||
if (!_showC2cMessageEditStatus) {
|
||||
return;
|
||||
}
|
||||
if (!(_c2cMessageFromUserActiveMap[toUser] ?? false)) {
|
||||
|
|
@ -612,7 +615,7 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
|
|||
);
|
||||
});
|
||||
}
|
||||
_recivedNewMessageCount = 0;
|
||||
_receivedNewMessageCount = 0;
|
||||
final currentMsg = _messageListMap[convID] ?? [];
|
||||
_messageListMap[convID] = [newMsg, ...currentMsg];
|
||||
notifyListeners();
|
||||
|
|
@ -631,7 +634,7 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
|
|||
} else {
|
||||
if (convID == currentSelectedConv) {
|
||||
unreadCountForConversation++;
|
||||
_recivedNewMessageCount++;
|
||||
_receivedNewMessageCount++;
|
||||
final currentMsg = _messageListMap[convID] ?? [];
|
||||
_messageListMap[convID] = [newMsg, ...currentMsg];
|
||||
notifyListeners();
|
||||
|
|
@ -746,23 +749,12 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
|
|||
}
|
||||
return;
|
||||
}
|
||||
if (messageProgress.isError) {
|
||||
TIMUIKitClass.onTIMCallback(
|
||||
TIMCallback(
|
||||
type: TIMCallbackType.INFO,
|
||||
infoRecommendText:
|
||||
TIM_t("视频保存失败") + ": ${messageProgress.errorCode.toString()}",
|
||||
infoCode: 6660403),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (messageProgress.totalSize != -1) {
|
||||
int progrss =
|
||||
int progress =
|
||||
(messageProgress.currentSize / messageProgress.totalSize * 100)
|
||||
.ceil();
|
||||
if (progrss > 1) {
|
||||
setMessageProgress(messageProgress.msgID, progrss);
|
||||
if (progress > 1) {
|
||||
setMessageProgress(messageProgress.msgID, progress);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -791,6 +783,12 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
|
|||
required String convID,
|
||||
ValueChanged<String>? setInputField,
|
||||
OfflinePushInfo? offlinePushInfo,
|
||||
MessagePriorityEnum priority = MessagePriorityEnum.V2TIM_PRIORITY_NORMAL,
|
||||
bool? onlineUserOnly,
|
||||
bool? isExcludedFromUnreadCount,
|
||||
bool? needReadReceipt,
|
||||
String? cloudCustomData,
|
||||
String? localCustomData,
|
||||
}) {
|
||||
final TUIChatModelTools tools = serviceLocator<TUIChatModelTools>();
|
||||
List<V2TimMessage> currentHistoryMsgList = _messageListMap[convID] ?? [];
|
||||
|
|
@ -807,6 +805,12 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
|
|||
loadingMessage[convID] = <V2TimMessage>[messageInfoWithSender];
|
||||
}
|
||||
return _sendMessage(
|
||||
priority: priority,
|
||||
onlineUserOnly: onlineUserOnly,
|
||||
isExcludedFromUnreadCount: isExcludedFromUnreadCount,
|
||||
needReadReceipt: needReadReceipt,
|
||||
cloudCustomData: cloudCustomData,
|
||||
localCustomData: localCustomData,
|
||||
convID: convID,
|
||||
setInputField: setInputField,
|
||||
id: messageInfo.id as String,
|
||||
|
|
@ -868,6 +872,11 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
|
|||
bool? isEditStatusMessage = false,
|
||||
GroupReceiptAllowType? groupType,
|
||||
ValueChanged<String>? setInputField,
|
||||
MessagePriorityEnum priority = MessagePriorityEnum.V2TIM_PRIORITY_NORMAL,
|
||||
bool? isExcludedFromUnreadCount,
|
||||
bool? needReadReceipt,
|
||||
String? cloudCustomData,
|
||||
String? localCustomData,
|
||||
}) async {
|
||||
String receiver = convType == ConvType.c2c ? convID : '';
|
||||
String groupID = convType == ConvType.group ? convID : '';
|
||||
|
|
@ -876,7 +885,8 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
|
|||
final sendMsgRes = await _messageService.sendMessage(
|
||||
id: id,
|
||||
receiver: receiver,
|
||||
needReadReceipt: chatConfig.isShowGroupReadingStatus &&
|
||||
needReadReceipt: needReadReceipt ??
|
||||
chatConfig.isShowGroupReadingStatus &&
|
||||
convType == ConvType.group &&
|
||||
((chatConfig.groupReadReceiptPermissionList != null &&
|
||||
chatConfig.groupReadReceiptPermissionList!
|
||||
|
|
@ -885,9 +895,13 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
|
|||
chatConfig.groupReadReceiptPermisionList!
|
||||
.contains(oldGroupType))),
|
||||
groupID: groupID,
|
||||
priority: priority,
|
||||
localCustomData: localCustomData,
|
||||
isExcludedFromUnreadCount: isExcludedFromUnreadCount ?? false,
|
||||
offlinePushInfo: offlinePushInfo,
|
||||
onlineUserOnly: onlineUserOnly ?? false,
|
||||
cloudCustomData: json.encode({
|
||||
cloudCustomData: cloudCustomData ??
|
||||
json.encode({
|
||||
"messageFeature": {
|
||||
"needTyping": 1,
|
||||
"version": 1,
|
||||
|
|
@ -904,7 +918,7 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
|
|||
{bool needResetNewMessageCount = true}) {
|
||||
_messageListMap[conversationID] = messageList;
|
||||
if (needResetNewMessageCount) {
|
||||
_recivedNewMessageCount = 0;
|
||||
_receivedNewMessageCount = 0;
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
|
@ -961,19 +975,22 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
|
|||
String convID,
|
||||
) {
|
||||
message.id = DateTime.now().millisecondsSinceEpoch.toString();
|
||||
|
||||
final activeMessageList = _messageListMap[convID];
|
||||
if (activeMessageList == null || activeMessageList.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final msgID = message.msgID;
|
||||
_messageListMap[currentSelectedConv] = activeMessageList.map((item) {
|
||||
_messageListMap[convID] = activeMessageList.map((item) {
|
||||
if (item.msgID == msgID) {
|
||||
return message;
|
||||
}
|
||||
return item;
|
||||
}).toList();
|
||||
if (convID == currentSelectedConv) {
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
List<V2TimMessage>? getMessageList(String conversationID) {
|
||||
final list = messageListMap[conversationID]?.reversed.toList() ?? [];
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ class LoginInfo {
|
|||
{this.sdkAppID = 0, this.userSig = "", this.userID = "", this.loginUser});
|
||||
}
|
||||
|
||||
class CoreServicesImpl with CoreServices {
|
||||
class CoreServicesImpl implements CoreServices {
|
||||
V2TimUserFullInfo? _loginInfo;
|
||||
late int _sdkAppID;
|
||||
late String _userID;
|
||||
|
|
|
|||
|
|
@ -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_im_base/tencent_im_base.dart';
|
||||
|
||||
class FriendshipServicesImpl with FriendshipServices {
|
||||
class FriendshipServicesImpl implements FriendshipServices {
|
||||
final CoreServicesImpl _coreService = serviceLocator<CoreServicesImpl>();
|
||||
|
||||
@override
|
||||
|
|
|
|||
|
|
@ -36,26 +36,26 @@ class MessageServiceImpl extends MessageService {
|
|||
lastMsgID: lastMsgID,
|
||||
lastMsgSeq: lastMsgSeq,
|
||||
messageTypeList: messageTypeList);
|
||||
final List<V2TimMessage> reponseMessageList = res.data ?? [];
|
||||
final List<V2TimMessage> responsedMessageList = res.data ?? [];
|
||||
final conversationID = userID ?? groupID;
|
||||
final cachedMessageList = messgaeListMap[conversationID];
|
||||
List<V2TimMessage> commbinedMessageList = [];
|
||||
List<V2TimMessage> combinedMessageList = [];
|
||||
// 加载更多
|
||||
if (lastMsgID != null && cachedMessageList != null) {
|
||||
commbinedMessageList = [...cachedMessageList, ...reponseMessageList];
|
||||
combinedMessageList = [...cachedMessageList, ...responsedMessageList];
|
||||
// 首次加载
|
||||
} else {
|
||||
final bool existSendingMessage = sendingMessage[conversationID] != null &&
|
||||
sendingMessage[conversationID]!.isNotEmpty;
|
||||
// 存在未发送完成的消息
|
||||
if (existSendingMessage) {
|
||||
commbinedMessageList = [
|
||||
combinedMessageList = [
|
||||
...sendingMessage[conversationID]!,
|
||||
...reponseMessageList
|
||||
...responsedMessageList
|
||||
];
|
||||
} else {
|
||||
sendingMessage.remove(conversationID);
|
||||
commbinedMessageList = reponseMessageList;
|
||||
combinedMessageList = responsedMessageList;
|
||||
}
|
||||
}
|
||||
if (res.code != 0) {
|
||||
|
|
@ -64,15 +64,16 @@ class MessageServiceImpl extends MessageService {
|
|||
errorMsg: res.desc,
|
||||
errorCode: res.code));
|
||||
}
|
||||
if (reponseMessageList.isEmpty ||
|
||||
(!PlatformUtils().isWeb && reponseMessageList.length < count) ||
|
||||
(PlatformUtils().isWeb && reponseMessageList.length < min(count, 20))) {
|
||||
if (responsedMessageList.isEmpty ||
|
||||
(!PlatformUtils().isWeb && responsedMessageList.length < count) ||
|
||||
(PlatformUtils().isWeb &&
|
||||
responsedMessageList.length < min(count, 20))) {
|
||||
haveMoreData = false;
|
||||
} else {
|
||||
haveMoreData = true;
|
||||
}
|
||||
return MessageListResponse(
|
||||
haveMoreData: haveMoreData, data: commbinedMessageList);
|
||||
haveMoreData: haveMoreData, data: combinedMessageList);
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -302,11 +303,13 @@ class MessageServiceImpl extends MessageService {
|
|||
|
||||
@override
|
||||
Future<V2TimMsgCreateInfoResult?> createImageMessage(
|
||||
{String? imagePath, dynamic inputElement}) async {
|
||||
{String? imageName, String? imagePath, dynamic inputElement}) async {
|
||||
final res = await TencentImSDKPlugin.v2TIMManager
|
||||
.getMessageManager()
|
||||
.createImageMessage(
|
||||
imagePath: imagePath ?? "", inputElement: inputElement);
|
||||
imageName: imageName,
|
||||
imagePath: imagePath ?? "",
|
||||
inputElement: inputElement);
|
||||
if (res.code == 0) {
|
||||
return res.data;
|
||||
}
|
||||
|
|
@ -345,7 +348,7 @@ class MessageServiceImpl extends MessageService {
|
|||
bool isExcludedFromUnreadCount = false,
|
||||
bool needReadReceipt = false,
|
||||
OfflinePushInfo? offlinePushInfo,
|
||||
String? cloudCustomData, // 云自定义消息字段,只能在消息发送前添加
|
||||
String? cloudCustomData,
|
||||
String? localCustomData,
|
||||
}) async {
|
||||
final result =
|
||||
|
|
@ -812,5 +815,4 @@ class MessageServiceImpl extends MessageService {
|
|||
}
|
||||
return result.data?[text] ?? "";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import 'package:tencent_im_base/tencent_im_base.dart';
|
|||
class MessageListResponse {
|
||||
final bool haveMoreData;
|
||||
final List<V2TimMessage> data;
|
||||
|
||||
MessageListResponse({required this.haveMoreData, required this.data});
|
||||
}
|
||||
|
||||
|
|
@ -51,6 +52,7 @@ abstract class MessageService {
|
|||
Future<void> removeSimpleMsgListener({V2TimSimpleMsgListener? listener});
|
||||
|
||||
Future<V2TimMsgCreateInfoResult?> createTextMessage({required String text});
|
||||
|
||||
Future<V2TimMsgCreateInfoResult?> createFaceMessage(
|
||||
{required int index, required String data});
|
||||
|
||||
|
|
@ -88,7 +90,7 @@ abstract class MessageService {
|
|||
{required V2TimMessage message});
|
||||
|
||||
Future<V2TimMsgCreateInfoResult?> createImageMessage(
|
||||
{String? imagePath, dynamic inputElement});
|
||||
{String? imageName, String? imagePath, dynamic inputElement});
|
||||
|
||||
Future<V2TimMsgCreateInfoResult?> createVideoMessage(
|
||||
{String? videoPath = "",
|
||||
|
|
|
|||
|
|
@ -688,5 +688,6 @@
|
|||
"k_1698c42": "在访达中打开",
|
||||
"k_066fxsz": "查看文件夹",
|
||||
"k_0k432y2": "无法发送,包含文件夹",
|
||||
"k_002wb4y": "会话"
|
||||
"k_002wb4y": "会话",
|
||||
"k_0od4qyh": "视频文件异常"
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
// ignore_for_file: avoid_print
|
||||
|
||||
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/view_models/tui_chat_global_model.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 {
|
||||
late TUIChatSeparateViewModel? model;
|
||||
late TIMUIKitInputTextFieldController? textFieldController;
|
||||
late AutoScrollController? scrollController;
|
||||
final TUIChatGlobalModel globalChatModel =
|
||||
serviceLocator<TUIChatGlobalModel>();
|
||||
|
||||
|
|
@ -35,13 +38,11 @@ class TIMUIKitChatController {
|
|||
false;
|
||||
}
|
||||
|
||||
/// clear the current conversation;
|
||||
/// 销毁
|
||||
/// Clear the current conversation;
|
||||
@Deprecated("No need to dispose after tencent_cloud_chat_uikit 0.1.4")
|
||||
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`.
|
||||
clearHistory([String? convID]) {
|
||||
if (convID != null) {
|
||||
|
|
@ -51,7 +52,6 @@ class TIMUIKitChatController {
|
|||
}
|
||||
|
||||
/// refresh the history message list manually;
|
||||
/// 手动刷新会话历史消息列表
|
||||
/// Please provide `convType` and `convID`, if you use `TIMUIKitChatController` without specifying to a `TIMUIKitChat`.
|
||||
Future<bool> refreshCurrentHistoryList(
|
||||
[String? convID, ConvType? convType]) async {
|
||||
|
|
@ -64,8 +64,7 @@ class TIMUIKitChatController {
|
|||
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`.
|
||||
Future<void> updateMessage(
|
||||
{
|
||||
|
|
@ -87,39 +86,72 @@ class TIMUIKitChatController {
|
|||
}
|
||||
|
||||
/// 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.
|
||||
/// 您需要提供 `convType` 和 `userID` 或 `groupID`, 只有在如果您使用 `TIMUIKitChat` 而没有指定 `TIMUIKitChatController`,则必须提供这些参数。
|
||||
Future<V2TimValueCallback<V2TimMessage>?>? sendMessage({
|
||||
required V2TimMessage? messageInfo,
|
||||
|
||||
/// 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,
|
||||
|
||||
/// The user ID of the target one-to-one conversation. Required if convType is ConvType.c2c.
|
||||
/// 目标一对一对话的用户 ID。如果 convType 是 ConvType.c2c,则必填。
|
||||
String? userID,
|
||||
|
||||
/// The target group ID. Required if convType is ConvType.group.
|
||||
/// 目标群组的 ID。如果 convType 是 ConvType.group,则必填。
|
||||
String? groupID,
|
||||
|
||||
/// A callback function to update the input field when message sending fails.
|
||||
/// 当消息发送失败时,用于更新输入框的回调函数。
|
||||
ValueChanged<String>? setInputField,
|
||||
|
||||
/// Offline push information.
|
||||
/// 离线推送信息。
|
||||
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) {
|
||||
/// Sends a message to the specified conversation. 发送消息到指定的对话。
|
||||
/// Sends a message to the specified conversation.
|
||||
assert((groupID == null) != (userID == null));
|
||||
assert(groupID != null || convType != ConvType.group);
|
||||
assert(userID != null || convType != ConvType.c2c);
|
||||
|
||||
if (isNavigateToMessageListBottom) {
|
||||
scrollController?.animateTo(
|
||||
scrollController!.position.minScrollExtent,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
curve: Curves.ease,
|
||||
);
|
||||
}
|
||||
return globalChatModel.sendMessageFromController(
|
||||
priority: priority,
|
||||
onlineUserOnly: onlineUserOnly,
|
||||
isExcludedFromUnreadCount: isExcludedFromUnreadCount,
|
||||
needReadReceipt: needReadReceipt,
|
||||
cloudCustomData: cloudCustomData,
|
||||
localCustomData: localCustomData,
|
||||
messageInfo: messageInfo,
|
||||
convType: convType,
|
||||
convID: (convType == ConvType.group ? groupID : userID) ?? "",
|
||||
|
|
@ -127,15 +159,28 @@ class TIMUIKitChatController {
|
|||
offlinePushInfo: offlinePushInfo);
|
||||
} else if (model != null) {
|
||||
/// 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(
|
||||
messageInfo: messageInfo, offlinePushInfo: offlinePushInfo);
|
||||
priority: priority,
|
||||
onlineUserOnly: onlineUserOnly,
|
||||
isExcludedFromUnreadCount: isExcludedFromUnreadCount,
|
||||
needReadReceipt: needReadReceipt,
|
||||
cloudCustomData: cloudCustomData,
|
||||
localCustomData: localCustomData,
|
||||
messageInfo: messageInfo,
|
||||
offlinePushInfo: offlinePushInfo);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Send forward message;
|
||||
/// 逐条转发
|
||||
/// This method needs use with TIMUIKitChat directly or model been initialized.
|
||||
/// This function solely works when `TIMUIKitChatController` is specified for use within a `TIMUIKitChat`.
|
||||
sendForwardMessage({
|
||||
required List<V2TimConversation> conversationList,
|
||||
}) async {
|
||||
|
|
@ -143,7 +188,6 @@ class TIMUIKitChatController {
|
|||
}
|
||||
|
||||
/// Send merger message;
|
||||
/// 合并转发
|
||||
/// This method needs use with TIMUIKitChat directly or model been initialized.
|
||||
Future<V2TimValueCallback<V2TimMessage>?> sendMergerMessage({
|
||||
required List<V2TimConversation> conversationList,
|
||||
|
|
@ -158,8 +202,7 @@ class TIMUIKitChatController {
|
|||
context: context);
|
||||
}
|
||||
|
||||
/// Set local custom data; returns the bool shows if succeed
|
||||
/// 为本地消息配置额外String字段
|
||||
/// Set local custom data; returns the bool shows if succeed.
|
||||
/// Please provide `convID`, if you use `TIMUIKitChatController` without specifying to a `TIMUIKitChat`.
|
||||
Future<bool> setLocalCustomData(String msgID, String localCustomData,
|
||||
[String? convID]) async {
|
||||
|
|
@ -171,8 +214,7 @@ class TIMUIKitChatController {
|
|||
msgID, localCustomData, conversationID);
|
||||
}
|
||||
|
||||
/// Set local custom int; returns the bool shows if succeed
|
||||
/// 为本地消息配置额外int字段
|
||||
/// Set local custom int; returns the bool shows if succeed.
|
||||
/// Please provide `convID`, if you use `TIMUIKitChatController` without specifying to a `TIMUIKitChat`.
|
||||
Future<bool> setLocalCustomInt(String msgID, int localCustomInt,
|
||||
[String? convID]) async {
|
||||
|
|
@ -185,8 +227,52 @@ class TIMUIKitChatController {
|
|||
}
|
||||
|
||||
/// Get current conversation, returns UserID or GroupID if in the chat page, returns "" if not.
|
||||
/// 获取当前会话ID,如果在Chat页面,返回UserID or GroupID, 反之返回""
|
||||
String getCurrentConversation() {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -278,7 +278,7 @@ class Permissions {
|
|||
);
|
||||
});
|
||||
_entry = entry;
|
||||
Overlay.of(context)?.insert(entry);
|
||||
Overlay.of(context).insert(entry);
|
||||
}
|
||||
|
||||
static Future<bool?> showPermissionConfirmDialog(BuildContext context, value,
|
||||
|
|
|
|||
|
|
@ -101,4 +101,5 @@ class ScreenshotHelper {
|
|||
}));
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/ui/utils/platform.dart';
|
||||
|
||||
enum DeviceType { Desktop, Mobile }
|
||||
|
||||
|
|
@ -12,9 +15,23 @@ class FormFactor {
|
|||
class TUIKitScreenUtils {
|
||||
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]) {
|
||||
if (deviceType != null) return deviceType!;
|
||||
|
||||
if (PlatformUtils().isWeb) {
|
||||
final win = WidgetsBinding.instance.platformDispatcher.views.first;
|
||||
final size = win.physicalSize;
|
||||
final screenWidth = size.width / win.devicePixelRatio;
|
||||
final screenHeight = size.height / win.devicePixelRatio;
|
||||
|
||||
final diagonalInInches =
|
||||
sqrt(pow(screenWidth, 2) + pow(screenHeight, 2)) / 96.0;
|
||||
|
||||
deviceType = diagonalInInches < 8.0 ? DeviceType.Mobile : DeviceType.Desktop;
|
||||
return deviceType ?? DeviceType.Mobile;
|
||||
}else{
|
||||
if(context != null){
|
||||
double deviceWidth = MediaQuery.of(context).size.width;
|
||||
double deviceHeight = MediaQuery.of(context).size.height;
|
||||
|
|
@ -29,12 +46,17 @@ class TUIKitScreenUtils {
|
|||
return DeviceType.Mobile;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
Widget? desktopWidget,
|
||||
Widget? mobileWidget,
|
||||
}) {
|
||||
deviceType ??= getFormFactor(context);
|
||||
if (deviceType == DeviceType.Desktop) return desktopWidget ?? defaultWidget;
|
||||
return mobileWidget ?? defaultWidget;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ class TIMUIKitAddFriend extends StatefulWidget {
|
|||
/// The life cycle hooks for adding friends and contact business logic
|
||||
final AddFriendLifeCycle? lifeCycle;
|
||||
|
||||
/// The callback function to close the widget upon completion by the parent component.
|
||||
final VoidCallback? closeFunc;
|
||||
|
||||
const TIMUIKitAddFriend(
|
||||
|
|
|
|||
|
|
@ -92,11 +92,12 @@ class _SendApplicationState extends TIMUIKitState<SendApplication> {
|
|||
const SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
if(TencentUtils.checkString(option2) != null)Text(
|
||||
if (TencentUtils.checkString(option2) != null)
|
||||
Text(
|
||||
TIM_t_para("个性签名: {{option2}}", "个性签名: $option2")(
|
||||
option2: option2),
|
||||
style:
|
||||
TextStyle(fontSize: 13, color: theme.weakTextColor),
|
||||
style: TextStyle(
|
||||
fontSize: 13, color: theme.weakTextColor),
|
||||
),
|
||||
],
|
||||
)
|
||||
|
|
@ -234,6 +235,7 @@ class _SendApplicationState extends TIMUIKitState<SendApplication> {
|
|||
}
|
||||
|
||||
return TUIKitScreenUtils.getDeviceWidget(
|
||||
context: context,
|
||||
desktopWidget: Container(
|
||||
padding: const EdgeInsets.only(top: 10),
|
||||
color: theme.weakBackgroundColor,
|
||||
|
|
@ -246,8 +248,7 @@ class _SendApplicationState extends TIMUIKitState<SendApplication> {
|
|||
style: TextStyle(color: theme.appbarTextColor, fontSize: 17),
|
||||
),
|
||||
shadowColor: theme.white,
|
||||
backgroundColor: theme.appbarBgColor ??
|
||||
theme.primaryColor,
|
||||
backgroundColor: theme.appbarBgColor ?? theme.primaryColor,
|
||||
iconTheme: IconThemeData(
|
||||
color: theme.appbarTextColor,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart';
|
|||
class SendJoinGroupApplication extends StatefulWidget {
|
||||
final V2TimGroupInfo groupInfo;
|
||||
final AddGroupLifeCycle? lifeCycle;
|
||||
|
||||
const SendJoinGroupApplication(
|
||||
{Key? key, required this.groupInfo, this.lifeCycle})
|
||||
: super(key: key);
|
||||
|
|
@ -168,8 +169,8 @@ class _SendJoinGroupApplicationState
|
|||
);
|
||||
}
|
||||
|
||||
|
||||
return TUIKitScreenUtils.getDeviceWidget(
|
||||
context: context,
|
||||
desktopWidget: sendGroupApplicationBody(),
|
||||
defaultWidget: Scaffold(
|
||||
appBar: AppBar(
|
||||
|
|
@ -178,8 +179,7 @@ class _SendJoinGroupApplicationState
|
|||
style: TextStyle(color: theme.appbarTextColor, fontSize: 17),
|
||||
),
|
||||
shadowColor: theme.white,
|
||||
backgroundColor: theme.appbarBgColor ??
|
||||
theme.primaryColor,
|
||||
backgroundColor: theme.appbarBgColor ?? theme.primaryColor,
|
||||
iconTheme: IconThemeData(
|
||||
color: theme.appbarTextColor,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -109,6 +109,7 @@ class _TIMUIKitBlackListState extends TIMUIKitState<TIMUIKitBlackList> {
|
|||
}
|
||||
|
||||
return TUIKitScreenUtils.getDeviceWidget(
|
||||
context: context,
|
||||
desktopWidget: itemWidget(),
|
||||
defaultWidget: Slidable(
|
||||
endActionPane: ActionPane(motion: const DrawerMotion(), children: [
|
||||
|
|
|
|||
|
|
@ -76,38 +76,38 @@ class MessageHoverControlItem {
|
|||
}
|
||||
|
||||
class MessageItemBuilder {
|
||||
/// text message builder
|
||||
/// text message builder, returns null means using default widget.
|
||||
final MessageItemContent? textMessageItemBuilder;
|
||||
|
||||
/// text message builder for reply message
|
||||
/// text message builder for reply message, returns null means using default widget.
|
||||
final MessageItemContent? textReplyMessageItemBuilder;
|
||||
|
||||
/// custom message builder
|
||||
/// custom message builder, returns null means using default widget.
|
||||
final MessageItemContent? customMessageItemBuilder;
|
||||
|
||||
/// image message builder
|
||||
/// image message builder, returns null means using default widget.
|
||||
final MessageItemContent? imageMessageItemBuilder;
|
||||
|
||||
/// sound message builder
|
||||
/// sound message builder, returns null means using default widget.
|
||||
final MessageItemContent? soundMessageItemBuilder;
|
||||
|
||||
/// video message builder
|
||||
/// video message builder, returns null means using default widget.
|
||||
final MessageItemContent? videoMessageItemBuilder;
|
||||
|
||||
/// file message builder
|
||||
/// file message builder, returns null means using default widget.
|
||||
final MessageItemContent? fileMessageItemBuilder;
|
||||
|
||||
/// location message (LBS) item builder;
|
||||
/// recommend to use our LBS plug-in: https://pub.dev/packages/tim_ui_kit_lbs_plugin
|
||||
final MessageItemContent? locationMessageItemBuilder;
|
||||
|
||||
/// face message, like emoji, message builder
|
||||
/// face message, like emoji, message builder, returns null means using default widget.
|
||||
final MessageItemContent? faceMessageItemBuilder;
|
||||
|
||||
/// group tips message builder
|
||||
/// group tips message builder, returns null means using default widget.
|
||||
final MessageItemContent? groupTipsMessageItemBuilder;
|
||||
|
||||
/// merger message builder
|
||||
/// merger message builder, returns null means using default widget.
|
||||
final MessageItemContent? mergerMessageItemBuilder;
|
||||
|
||||
/// The builder for the whole message line, expect for those message type without avatar and nickname.
|
||||
|
|
@ -148,15 +148,32 @@ class MessageToolTipItem {
|
|||
}
|
||||
|
||||
class ToolTipsConfig {
|
||||
/// Whether to show the reply to a message option.
|
||||
final bool showReplyMessage;
|
||||
|
||||
/// Whether to show the multiple-choice option for messages.
|
||||
final bool showMultipleChoiceMessage;
|
||||
|
||||
/// Whether to show the option to delete a message.
|
||||
final bool showDeleteMessage;
|
||||
|
||||
/// Whether to show the option to recall a message.
|
||||
final bool showRecallMessage;
|
||||
|
||||
/// Whether to show the option to copy a message.
|
||||
final bool showCopyMessage;
|
||||
|
||||
/// Whether to show the option to forward a message.
|
||||
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;
|
||||
|
||||
/// 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,
|
||||
[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(
|
||||
V2TimMessage message, Function() closeTooltip)? additionalMessageToolTips;
|
||||
|
||||
|
|
@ -169,7 +186,8 @@ class ToolTipsConfig {
|
|||
this.showCopyMessage = true,
|
||||
this.showForwardMessage = true,
|
||||
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(
|
||||
"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});
|
||||
}
|
||||
|
||||
|
|
@ -181,6 +199,10 @@ class TIMUIKitHistoryMessageListItem extends StatefulWidget {
|
|||
final void Function(String userID, TapDownDetails tapDetails)?
|
||||
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.
|
||||
final Function? onScrollToIndex;
|
||||
|
||||
|
|
@ -262,7 +284,8 @@ class TIMUIKitHistoryMessageListItem extends StatefulWidget {
|
|||
const TIMUIKitHistoryMessageListItem(
|
||||
{Key? key,
|
||||
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(
|
||||
"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.onScrollToIndexBegin,
|
||||
|
|
@ -287,7 +310,8 @@ class TIMUIKitHistoryMessageListItem extends StatefulWidget {
|
|||
this.bottomRowBuilder,
|
||||
this.isUseDefaultEmoji = false,
|
||||
this.customEmojiStickerList = const [],
|
||||
this.textFieldController})
|
||||
this.textFieldController,
|
||||
this.onSecondaryTapForOthersPortrait})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
|
|
@ -386,14 +410,16 @@ class _TIMUIKItHistoryMessageListItemState
|
|||
|
||||
switch (msgType) {
|
||||
case MessageElemType.V2TIM_ELEM_TYPE_CUSTOM:
|
||||
if (messageItemBuilder?.customMessageItemBuilder != null) {
|
||||
return messageItemBuilder!.customMessageItemBuilder!(
|
||||
final customWidget =
|
||||
messageItemBuilder?.customMessageItemBuilder != null
|
||||
? messageItemBuilder!.customMessageItemBuilder!(
|
||||
messageItem,
|
||||
isShowJump,
|
||||
() => model.jumpMsgID = "",
|
||||
)!;
|
||||
}
|
||||
return TIMUIKitCustomElem(
|
||||
)
|
||||
: null;
|
||||
return customWidget ??
|
||||
TIMUIKitCustomElem(
|
||||
message: messageItem,
|
||||
customElem: messageItem.customElem,
|
||||
isFromSelf: isFromSelf,
|
||||
|
|
@ -404,14 +430,15 @@ class _TIMUIKItHistoryMessageListItemState
|
|||
isShowMessageReaction: widget.isUseMessageReaction,
|
||||
);
|
||||
case MessageElemType.V2TIM_ELEM_TYPE_SOUND:
|
||||
if (messageItemBuilder?.soundMessageItemBuilder != null) {
|
||||
return messageItemBuilder!.soundMessageItemBuilder!(
|
||||
final customWidget = messageItemBuilder?.soundMessageItemBuilder != null
|
||||
? messageItemBuilder!.soundMessageItemBuilder!(
|
||||
messageItem,
|
||||
isShowJump,
|
||||
clearJump,
|
||||
)!;
|
||||
}
|
||||
return TIMUIKitSoundElem(
|
||||
() => model.jumpMsgID = "",
|
||||
)
|
||||
: null;
|
||||
return customWidget ??
|
||||
TIMUIKitSoundElem(
|
||||
chatModel: model,
|
||||
message: messageItem,
|
||||
soundElem: messageItem.soundElem!,
|
||||
|
|
@ -428,14 +455,16 @@ class _TIMUIKItHistoryMessageListItemState
|
|||
);
|
||||
case MessageElemType.V2TIM_ELEM_TYPE_TEXT:
|
||||
if (isReplyMessage(messageItem)) {
|
||||
if (messageItemBuilder?.textReplyMessageItemBuilder != null) {
|
||||
return messageItemBuilder!.textReplyMessageItemBuilder!(
|
||||
final customWidget =
|
||||
messageItemBuilder?.textReplyMessageItemBuilder != null
|
||||
? messageItemBuilder!.textReplyMessageItemBuilder!(
|
||||
messageItem,
|
||||
isShowJump,
|
||||
clearJump,
|
||||
)!;
|
||||
}
|
||||
return TIMUIKitReplyElem(
|
||||
() => model.jumpMsgID = "",
|
||||
)
|
||||
: null;
|
||||
return customWidget ??
|
||||
TIMUIKitReplyElem(
|
||||
message: messageItem,
|
||||
clearJump: clearJump,
|
||||
isShowJump: isShowJump,
|
||||
|
|
@ -450,14 +479,15 @@ class _TIMUIKItHistoryMessageListItemState
|
|||
isShowMessageReaction: widget.isUseMessageReaction,
|
||||
);
|
||||
}
|
||||
if (messageItemBuilder?.textMessageItemBuilder != null) {
|
||||
return messageItemBuilder!.textMessageItemBuilder!(
|
||||
final customWidget = messageItemBuilder?.textMessageItemBuilder != null
|
||||
? messageItemBuilder!.textMessageItemBuilder!(
|
||||
messageItem,
|
||||
isShowJump,
|
||||
clearJump,
|
||||
)!;
|
||||
}
|
||||
return TIMUIKitTextElem(
|
||||
() => model.jumpMsgID = "",
|
||||
)
|
||||
: null;
|
||||
return customWidget ??
|
||||
TIMUIKitTextElem(
|
||||
chatModel: model,
|
||||
message: messageItem,
|
||||
isFromSelf: messageItem.isSelf ?? true,
|
||||
|
|
@ -472,14 +502,15 @@ class _TIMUIKItHistoryMessageListItemState
|
|||
customEmojiStickerList: widget.customEmojiStickerList,
|
||||
);
|
||||
case MessageElemType.V2TIM_ELEM_TYPE_FACE:
|
||||
if (messageItemBuilder?.faceMessageItemBuilder != null) {
|
||||
return messageItemBuilder!.faceMessageItemBuilder!(
|
||||
final customWidget = messageItemBuilder?.faceMessageItemBuilder != null
|
||||
? messageItemBuilder!.faceMessageItemBuilder!(
|
||||
messageItem,
|
||||
isShowJump,
|
||||
clearJump,
|
||||
)!;
|
||||
}
|
||||
return TIMUIKitFaceElem(
|
||||
() => model.jumpMsgID = "",
|
||||
)
|
||||
: null;
|
||||
return customWidget ??
|
||||
TIMUIKitFaceElem(
|
||||
model: model,
|
||||
path: messageItem.faceElem!.data ?? "",
|
||||
clearJump: clearJump,
|
||||
|
|
@ -488,14 +519,15 @@ class _TIMUIKItHistoryMessageListItemState
|
|||
isShowMessageReaction: widget.isUseMessageReaction,
|
||||
);
|
||||
case MessageElemType.V2TIM_ELEM_TYPE_FILE:
|
||||
if (messageItemBuilder?.fileMessageItemBuilder != null) {
|
||||
return messageItemBuilder!.fileMessageItemBuilder!(
|
||||
final customWidget = messageItemBuilder?.fileMessageItemBuilder != null
|
||||
? messageItemBuilder!.fileMessageItemBuilder!(
|
||||
messageItem,
|
||||
isShowJump,
|
||||
clearJump,
|
||||
)!;
|
||||
}
|
||||
return TIMUIKitFileElem(
|
||||
() => model.jumpMsgID = "",
|
||||
)
|
||||
: null;
|
||||
return customWidget ??
|
||||
TIMUIKitFileElem(
|
||||
chatModel: model,
|
||||
message: messageItem,
|
||||
messageID: messageItem.msgID,
|
||||
|
|
@ -506,23 +538,25 @@ class _TIMUIKItHistoryMessageListItemState
|
|||
isShowMessageReaction: widget.isUseMessageReaction,
|
||||
);
|
||||
case MessageElemType.V2TIM_ELEM_TYPE_GROUP_TIPS:
|
||||
if (messageItemBuilder?.groupTipsMessageItemBuilder != null) {
|
||||
return messageItemBuilder!.groupTipsMessageItemBuilder!(
|
||||
final customWidget =
|
||||
messageItemBuilder?.groupTipsMessageItemBuilder != null
|
||||
? messageItemBuilder!.groupTipsMessageItemBuilder!(
|
||||
messageItem,
|
||||
isShowJump,
|
||||
clearJump,
|
||||
)!;
|
||||
}
|
||||
return Text(TIM_t("[群系统消息]"));
|
||||
() => model.jumpMsgID = "",
|
||||
)
|
||||
: null;
|
||||
return customWidget ?? Text(TIM_t("[群系统消息]"));
|
||||
case MessageElemType.V2TIM_ELEM_TYPE_IMAGE:
|
||||
if (messageItemBuilder?.imageMessageItemBuilder != null) {
|
||||
return messageItemBuilder!.imageMessageItemBuilder!(
|
||||
final customWidget = messageItemBuilder?.imageMessageItemBuilder != null
|
||||
? messageItemBuilder!.imageMessageItemBuilder!(
|
||||
messageItem,
|
||||
isShowJump,
|
||||
clearJump,
|
||||
)!;
|
||||
}
|
||||
return TIMUIKitImageElem(
|
||||
() => model.jumpMsgID = "",
|
||||
)
|
||||
: null;
|
||||
return customWidget ??
|
||||
TIMUIKitImageElem(
|
||||
clearJump: clearJump,
|
||||
isShowJump: isShowJump,
|
||||
chatModel: model,
|
||||
|
|
@ -531,14 +565,15 @@ class _TIMUIKItHistoryMessageListItemState
|
|||
key: Key("${messageItem.seq}_${messageItem.timestamp}"),
|
||||
);
|
||||
case MessageElemType.V2TIM_ELEM_TYPE_VIDEO:
|
||||
if (messageItemBuilder?.videoMessageItemBuilder != null) {
|
||||
return messageItemBuilder!.videoMessageItemBuilder!(
|
||||
final customWidget = messageItemBuilder?.videoMessageItemBuilder != null
|
||||
? messageItemBuilder!.videoMessageItemBuilder!(
|
||||
messageItem,
|
||||
isShowJump,
|
||||
clearJump,
|
||||
)!;
|
||||
}
|
||||
return TIMUIKitVideoElem(
|
||||
() => model.jumpMsgID = "",
|
||||
)
|
||||
: null;
|
||||
return customWidget ??
|
||||
TIMUIKitVideoElem(
|
||||
messageItem,
|
||||
isShowJump: isShowJump,
|
||||
chatModel: model,
|
||||
|
|
@ -546,23 +581,26 @@ class _TIMUIKItHistoryMessageListItemState
|
|||
isShowMessageReaction: widget.isUseMessageReaction,
|
||||
);
|
||||
case MessageElemType.V2TIM_ELEM_TYPE_LOCATION:
|
||||
if (messageItemBuilder?.locationMessageItemBuilder != null) {
|
||||
return messageItemBuilder!.locationMessageItemBuilder!(
|
||||
final customWidget =
|
||||
messageItemBuilder?.locationMessageItemBuilder != null
|
||||
? messageItemBuilder!.locationMessageItemBuilder!(
|
||||
messageItem,
|
||||
isShowJump,
|
||||
clearJump,
|
||||
)!;
|
||||
}
|
||||
return Text(TIM_t("[位置]"));
|
||||
() => model.jumpMsgID = "",
|
||||
)
|
||||
: null;
|
||||
return customWidget ?? Text(TIM_t("[位置]"));
|
||||
case MessageElemType.V2TIM_ELEM_TYPE_MERGER:
|
||||
if (messageItemBuilder?.mergerMessageItemBuilder != null) {
|
||||
return messageItemBuilder!.mergerMessageItemBuilder!(
|
||||
final customWidget =
|
||||
messageItemBuilder?.mergerMessageItemBuilder != null
|
||||
? messageItemBuilder!.mergerMessageItemBuilder!(
|
||||
messageItem,
|
||||
isShowJump,
|
||||
clearJump,
|
||||
)!;
|
||||
}
|
||||
return TIMUIKitMergerElem(
|
||||
() => model.jumpMsgID = "",
|
||||
)
|
||||
: null;
|
||||
return customWidget ??
|
||||
TIMUIKitMergerElem(
|
||||
messageItemBuilder: messageItemBuilder,
|
||||
model: model,
|
||||
isShowJump: isShowJump,
|
||||
|
|
@ -941,12 +979,18 @@ class _TIMUIKItHistoryMessageListItemState
|
|||
),
|
||||
onClick: (_) {
|
||||
model.repliedMessage = widget.message;
|
||||
if (widget.allowAtUserWhenReply &&
|
||||
widget.onLongPressForOthersHeadPortrait != null &&
|
||||
!(widget.message.isSelf ?? true)) {
|
||||
final isSelf = widget.message.isSelf ?? true;
|
||||
final isGroup =
|
||||
TencentUtils.checkString(widget.message.groupID) != null;
|
||||
final isAtWhenReply = !isSelf &&
|
||||
isGroup &&
|
||||
widget.allowAtUserWhenReply &&
|
||||
widget.onLongPressForOthersHeadPortrait != null;
|
||||
|
||||
/// If replying to a self message, do not add a at tag, only requestFocus.
|
||||
widget.onLongPressForOthersHeadPortrait!(
|
||||
widget.message.sender, widget.message.nickName);
|
||||
}
|
||||
!isAtWhenReply ? null : widget.message.sender,
|
||||
!isAtWhenReply ? null : widget.message.nickName);
|
||||
},
|
||||
),
|
||||
if ((widget.toolTipsConfig?.showForwardMessage ?? true) &&
|
||||
|
|
@ -1009,18 +1053,28 @@ class _TIMUIKItHistoryMessageListItemState
|
|||
context);
|
||||
}
|
||||
|
||||
Widget renderHoverTipAndReadStatus(TUIChatSeparateViewModel model,
|
||||
bool isSelf, V2TimMessage message, bool isPeerRead, TUITheme theme) {
|
||||
Widget renderHoverTipAndReadStatus(
|
||||
TUIChatSeparateViewModel model,
|
||||
bool isSelf,
|
||||
V2TimMessage message,
|
||||
bool isPeerRead,
|
||||
TUITheme theme,
|
||||
bool isDownloadWaiting) {
|
||||
final isDesktopScreen =
|
||||
TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop;
|
||||
final wideHoverTipList = getMessageHoverControlBar(model, theme);
|
||||
final lastItemName = wideHoverTipList.last.name;
|
||||
final wideHoverTipList = model.chatConfig.isUseMessageHoverBarOnDesktop
|
||||
? getMessageHoverControlBar(model, theme)
|
||||
: [];
|
||||
final lastItemName =
|
||||
wideHoverTipList.isNotEmpty ? wideHoverTipList.last.name : "";
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
if (isDesktopScreen && isShowWideToolTip)
|
||||
if (isDesktopScreen &&
|
||||
isShowWideToolTip &&
|
||||
!((widget.message.elemType == 6 && isDownloadWaiting)))
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
|
|
@ -1188,7 +1242,8 @@ class _TIMUIKItHistoryMessageListItemState
|
|||
children: [
|
||||
if (model.isMultiSelect)
|
||||
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(
|
||||
isChecked: model.multiSelectedMessageList.contains(message),
|
||||
onChanged: (value) {
|
||||
|
|
@ -1264,6 +1319,26 @@ class _TIMUIKItHistoryMessageListItemState
|
|||
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
|
||||
? widget.userAvatarBuilder!(context, message)
|
||||
: 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(
|
||||
margin: widget.showAvatar
|
||||
? (isSelf
|
||||
|
|
@ -1319,8 +1404,13 @@ class _TIMUIKItHistoryMessageListItemState
|
|||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
if (isSelf)
|
||||
renderHoverTipAndReadStatus(model, isSelf,
|
||||
message, isPeerRead, theme),
|
||||
renderHoverTipAndReadStatus(
|
||||
model,
|
||||
isSelf,
|
||||
message,
|
||||
isPeerRead,
|
||||
theme,
|
||||
isDownloadWaiting),
|
||||
Container(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: constraints.maxWidth * 0.77,
|
||||
|
|
@ -1406,8 +1496,13 @@ class _TIMUIKItHistoryMessageListItemState
|
|||
child: Icon(Icons.circle,
|
||||
color: theme.cautionColor, size: 10)),
|
||||
if (!isSelf)
|
||||
renderHoverTipAndReadStatus(model, isSelf,
|
||||
message, isPeerRead, theme),
|
||||
renderHoverTipAndReadStatus(
|
||||
model,
|
||||
isSelf,
|
||||
message,
|
||||
isPeerRead,
|
||||
theme,
|
||||
isDownloadWaiting),
|
||||
],
|
||||
),
|
||||
if (widget.bottomRowBuilder != null)
|
||||
|
|
@ -1415,7 +1510,9 @@ class _TIMUIKItHistoryMessageListItemState
|
|||
],
|
||||
),
|
||||
),
|
||||
if (widget.message.elemType == 6 && isDownloadWaiting)
|
||||
if (!isSelf &&
|
||||
widget.message.elemType == 6 &&
|
||||
isDownloadWaiting)
|
||||
Container(
|
||||
margin: const EdgeInsets.only(top: 24, left: 6),
|
||||
child: LoadingAnimationWidget.threeArchedCircle(
|
||||
|
|
|
|||
|
|
@ -85,7 +85,10 @@ class TIMUIKitMessageTooltipState
|
|||
}
|
||||
|
||||
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;
|
||||
return;
|
||||
}
|
||||
|
|
@ -94,6 +97,7 @@ class TIMUIKitMessageTooltipState
|
|||
return;
|
||||
}
|
||||
if (PlatformUtils().isDesktop) {
|
||||
if (widget.message.fileElem != null) {
|
||||
if (globalModal.getMessageProgress(widget.message.msgID) == 100) {
|
||||
String savePath =
|
||||
TencentUtils.checkString(widget.message.fileElem!.localUrl) ??
|
||||
|
|
@ -115,6 +119,23 @@ class TIMUIKitMessageTooltipState
|
|||
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;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
isShowOpenFile = false;
|
||||
}
|
||||
|
|
@ -198,11 +219,6 @@ class TIMUIKitMessageTooltipState
|
|||
id: "forwardMessage",
|
||||
iconImageAsset: "images/forward_message.png",
|
||||
onClick: () => _onTap("forwardMessage", model)),
|
||||
MessageToolTipItem(
|
||||
label: TIM_t("多选"),
|
||||
id: "multiSelect",
|
||||
iconImageAsset: "images/multi_message.png",
|
||||
onClick: () => _onTap("multiSelect", model)),
|
||||
if (shouldShowReplyAction)
|
||||
MessageToolTipItem(
|
||||
label: TIM_t(model.chatConfig.isAtWhenReply ? "回复" : "引用"),
|
||||
|
|
@ -210,15 +226,20 @@ class TIMUIKitMessageTooltipState
|
|||
iconImageAsset: "images/reply_message.png",
|
||||
onClick: () => _onTap("replyMessage", model)),
|
||||
MessageToolTipItem(
|
||||
label: TIM_t("删除"),
|
||||
id: "delete",
|
||||
iconImageAsset: "images/delete_message.png",
|
||||
onClick: () => _onTap("delete", model)),
|
||||
label: TIM_t("多选"),
|
||||
id: "multiSelect",
|
||||
iconImageAsset: "images/multi_message.png",
|
||||
onClick: () => _onTap("multiSelect", model)),
|
||||
MessageToolTipItem(
|
||||
label: TIM_t("翻译"),
|
||||
id: "translate",
|
||||
iconImageAsset: "images/translate.png",
|
||||
onClick: () => _onTap("translate", model)),
|
||||
MessageToolTipItem(
|
||||
label: TIM_t("删除"),
|
||||
id: "delete",
|
||||
iconImageAsset: "images/delete_message.png",
|
||||
onClick: () => _onTap("delete", model)),
|
||||
if (shouldShowRevokeAction)
|
||||
MessageToolTipItem(
|
||||
label: TIM_t("撤回"),
|
||||
|
|
@ -226,6 +247,7 @@ class TIMUIKitMessageTooltipState
|
|||
iconImageAsset: "images/revoke_message.png",
|
||||
onClick: () => _onTap("revoke", model)),
|
||||
];
|
||||
final defaultTipsIds = defaultTipsList.map((e) => e.id);
|
||||
List<MessageToolTipItem> defaultFormattedTipsList = defaultTipsList;
|
||||
if (tooltipsConfig != null) {
|
||||
defaultFormattedTipsList = defaultTipsList.where((element) {
|
||||
|
|
@ -235,10 +257,14 @@ class TIMUIKitMessageTooltipState
|
|||
widget.message.elemType == MessageElemType.V2TIM_ELEM_TYPE_TEXT;
|
||||
}
|
||||
if (type == "forwardMessage") {
|
||||
return tooltipsConfig.showForwardMessage && !isDesktopScreen;
|
||||
return tooltipsConfig.showForwardMessage &&
|
||||
!(isDesktopScreen &&
|
||||
widget.model.chatConfig.isUseMessageHoverBarOnDesktop);
|
||||
}
|
||||
if (type == "replyMessage") {
|
||||
return tooltipsConfig.showReplyMessage && !isDesktopScreen;
|
||||
return tooltipsConfig.showReplyMessage &&
|
||||
!(isDesktopScreen &&
|
||||
widget.model.chatConfig.isUseMessageHoverBarOnDesktop);
|
||||
}
|
||||
if (type == "delete") {
|
||||
return (!PlatformUtils().isWeb) && tooltipsConfig.showDeleteMessage;
|
||||
|
|
@ -286,7 +312,9 @@ class TIMUIKitMessageTooltipState
|
|||
children: [
|
||||
Image.asset(
|
||||
item.iconImageAsset,
|
||||
package: 'tencent_cloud_chat_uikit',
|
||||
package: defaultTipsIds.contains(item.id)
|
||||
? 'tencent_cloud_chat_uikit'
|
||||
: null,
|
||||
width: 20,
|
||||
height: 20,
|
||||
),
|
||||
|
|
@ -353,37 +381,53 @@ class TIMUIKitMessageTooltipState
|
|||
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 {
|
||||
final messageItem = widget.message;
|
||||
final msgID = messageItem.msgID as String;
|
||||
switch (operation) {
|
||||
case "open":
|
||||
if (PlatformUtils().isDesktop) {
|
||||
final String savePath =
|
||||
TencentUtils.checkString(widget.message.fileElem!.localUrl) ??
|
||||
globalModal.getFileMessageLocation(widget.message.msgID);
|
||||
launchUrl(Uri.file(savePath));
|
||||
} else {
|
||||
if (PlatformUtils().isWindows) {
|
||||
OpenFile.open(widget.message.fileElem?.path ?? "");
|
||||
} else {
|
||||
launchUrl(
|
||||
Uri.parse(widget.message.fileElem?.path ?? ""),
|
||||
mode: LaunchMode.externalApplication,
|
||||
);
|
||||
}
|
||||
if (widget.message.fileElem != null) {
|
||||
_onOpenDesktop(widget.message.fileElem!.localUrl ??
|
||||
widget.message.fileElem?.path ??
|
||||
"");
|
||||
} else if (widget.message.imageElem != null) {
|
||||
_onOpenDesktop(widget.message.imageElem!.imageList?[0]?.localUrl ??
|
||||
widget.message.imageElem?.path ??
|
||||
"");
|
||||
} else if (widget.message.videoElem != null) {
|
||||
_onOpenDesktop(widget.message.videoElem!.localVideoUrl ??
|
||||
widget.message.videoElem?.videoPath ??
|
||||
"");
|
||||
}
|
||||
break;
|
||||
case "finder":
|
||||
final String savePath =
|
||||
TencentUtils.checkString(widget.message.fileElem!.localUrl) ??
|
||||
globalModal.getFileMessageLocation(widget.message.msgID);
|
||||
final String fileDir = path.dirname(savePath);
|
||||
if (PlatformUtils().isWindows) {
|
||||
OpenFile.open(fileDir);
|
||||
} else {
|
||||
launchUrl(Uri.file(fileDir));
|
||||
String savePath = "";
|
||||
if (widget.message.fileElem != null) {
|
||||
savePath = (widget.message.fileElem!.localUrl ??
|
||||
widget.message.fileElem?.path ??
|
||||
"");
|
||||
} else if (widget.message.imageElem != null) {
|
||||
savePath = (widget.message.imageElem!.imageList?[0]?.localUrl ??
|
||||
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;
|
||||
case "delete":
|
||||
model.deleteMsg(msgID, webMessageInstance: messageItem.messageFromWeb);
|
||||
|
|
@ -423,12 +467,18 @@ class TIMUIKitMessageTooltipState
|
|||
break;
|
||||
case "replyMessage":
|
||||
model.repliedMessage = widget.message;
|
||||
if (widget.allowAtUserWhenReply &&
|
||||
widget.onLongPressForOthersHeadPortrait != null &&
|
||||
!(widget.message.isSelf ?? true)) {
|
||||
final isSelf = widget.message.isSelf ?? true;
|
||||
final isGroup =
|
||||
TencentUtils.checkString(widget.message.groupID) != null;
|
||||
final isAtWhenReply = !isSelf &&
|
||||
isGroup &&
|
||||
widget.allowAtUserWhenReply &&
|
||||
widget.onLongPressForOthersHeadPortrait != null;
|
||||
|
||||
/// If replying to a self message, do not add a at tag, only requestFocus.
|
||||
widget.onLongPressForOthersHeadPortrait!(
|
||||
widget.message.sender, widget.message.nickName);
|
||||
}
|
||||
!isAtWhenReply ? null : widget.message.sender,
|
||||
!isAtWhenReply ? null : widget.message.nickName);
|
||||
break;
|
||||
default:
|
||||
onTIMCallback(TIMCallback(
|
||||
|
|
|
|||
|
|
@ -51,8 +51,13 @@ class TIMUIKitHistoryMessageListContainer extends StatefulWidget {
|
|||
/// conversation type
|
||||
final ConvType conversationType;
|
||||
|
||||
/// Avatar and name in message reaction tap callback.
|
||||
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(
|
||||
"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;
|
||||
|
|
@ -85,7 +90,8 @@ class TIMUIKitHistoryMessageListContainer extends StatefulWidget {
|
|||
this.extraTipsActionItemBuilder,
|
||||
this.isAllowScroll = true,
|
||||
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(
|
||||
"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.mainHistoryListConfig,
|
||||
|
|
@ -94,6 +100,7 @@ class TIMUIKitHistoryMessageListContainer extends StatefulWidget {
|
|||
this.customEmojiStickerList = const [],
|
||||
this.textFieldController,
|
||||
required this.conversation,
|
||||
this.onSecondaryTapAvatar,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
|
@ -169,6 +176,7 @@ class _TIMUIKitHistoryMessageListContainerState
|
|||
widget.extraTipsActionItemBuilder),
|
||||
message: message!,
|
||||
showAvatar: chatConfig.isShowAvatar,
|
||||
onSecondaryTapForOthersPortrait: widget.onSecondaryTapAvatar,
|
||||
onTapForOthersPortrait: widget.onTapAvatar,
|
||||
messageItemBuilder: widget.messageItemBuilder,
|
||||
onLongPressForOthersHeadPortrait:
|
||||
|
|
|
|||
|
|
@ -81,8 +81,7 @@ class _TIMUIKitAppBarState extends TIMUIKitState<TIMUIKitAppBar> {
|
|||
"";
|
||||
}
|
||||
// ignore: empty_catches
|
||||
} catch (e) {
|
||||
}
|
||||
} catch (e) {}
|
||||
},
|
||||
);
|
||||
if (_friendshipListener != null) {
|
||||
|
|
@ -104,8 +103,7 @@ class _TIMUIKitAppBarState extends TIMUIKitState<TIMUIKitAppBar> {
|
|||
}
|
||||
}
|
||||
// ignore: empty_catches
|
||||
} catch (e) {
|
||||
}
|
||||
} catch (e) {}
|
||||
},
|
||||
);
|
||||
if (_groupListener != null) {
|
||||
|
|
@ -162,8 +160,7 @@ class _TIMUIKitAppBarState extends TIMUIKitState<TIMUIKitAppBar> {
|
|||
});
|
||||
}
|
||||
// ignore: empty_catches
|
||||
} catch (e) {
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -193,7 +190,8 @@ class _TIMUIKitAppBarState extends TIMUIKitState<TIMUIKitAppBar> {
|
|||
titleTextStyle: setAppbar?.titleTextStyle,
|
||||
toolbarOpacity: setAppbar?.toolbarOpacity ?? 1.0,
|
||||
toolbarTextStyle: setAppbar?.toolbarTextStyle,
|
||||
textTheme: setAppbar?.textTheme,
|
||||
|
||||
// textTheme: setAppbar?.textTheme,
|
||||
iconTheme: setAppbar?.iconTheme ??
|
||||
const IconThemeData(
|
||||
color: Colors.white,
|
||||
|
|
@ -201,10 +199,8 @@ class _TIMUIKitAppBarState extends TIMUIKitState<TIMUIKitAppBar> {
|
|||
title: TIMUIKitAppBarTitle(
|
||||
title: setAppbar?.title,
|
||||
onClick: widget.onClickTitle,
|
||||
textStyle: setAppbar?.textTheme?.titleMedium ??
|
||||
TextStyle(
|
||||
color: theme.appbarTextColor ?? hexToColor("010000"),
|
||||
fontSize: 16),
|
||||
textStyle: TextStyle(
|
||||
color: theme.appbarTextColor ?? hexToColor("010000"), fontSize: 16),
|
||||
conversationShowName: _conversationShowName,
|
||||
showC2cMessageEditStatus: widget.showC2cMessageEditStatus,
|
||||
fromUser: widget.conversationID,
|
||||
|
|
@ -222,10 +218,8 @@ class _TIMUIKitAppBarState extends TIMUIKitState<TIMUIKitAppBar> {
|
|||
},
|
||||
child: Text(
|
||||
TIM_t('取消'),
|
||||
style: setAppbar?.textTheme?.titleMedium ??
|
||||
TextStyle(
|
||||
color:
|
||||
theme.appbarTextColor ?? hexToColor("010000"),
|
||||
style: TextStyle(
|
||||
color: theme.appbarTextColor ?? hexToColor("010000"),
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
|
|
@ -240,10 +234,7 @@ class _TIMUIKitAppBarState extends TIMUIKitState<TIMUIKitAppBar> {
|
|||
constraints: const BoxConstraints(),
|
||||
icon: Icon(
|
||||
Icons.arrow_back_ios,
|
||||
color: setAppbar
|
||||
?.textTheme?.titleMedium?.color ??
|
||||
theme.appbarTextColor ??
|
||||
hexToColor("010000"),
|
||||
color: hexToColor("010000"),
|
||||
size: 17,
|
||||
),
|
||||
onPressed: () async {
|
||||
|
|
|
|||
|
|
@ -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/ui/utils/permission.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_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/view_models/tui_chat_global_model.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/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/widgets/textSize.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
class TIMUIKitFileElem extends StatefulWidget {
|
||||
final String? messageID;
|
||||
|
|
@ -51,6 +51,16 @@ class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
|
|||
String filePath = "";
|
||||
bool isDownloading = false;
|
||||
final TUIChatGlobalModel model = serviceLocator<TUIChatGlobalModel>();
|
||||
int downloadProgress = 0;
|
||||
late V2TimAdvancedMsgListener advancedMsgListener;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
TencentImSDKPlugin.v2TIMManager
|
||||
.getMessageManager()
|
||||
.removeAdvancedMsgListener(listener: advancedMsgListener);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
|
@ -60,6 +70,32 @@ class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
|
|||
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 {
|
||||
|
|
@ -75,12 +111,17 @@ class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
|
|||
return true;
|
||||
}
|
||||
|
||||
if (model.getMessageProgress(widget.messageID) == 100) {
|
||||
String savePath = TencentUtils.checkString(widget.message.fileElem!.localUrl) ??
|
||||
if (model.getMessageProgress(widget.messageID) == 100 ||
|
||||
downloadProgress == 100) {
|
||||
String savePath =
|
||||
TencentUtils.checkString(widget.message.fileElem!.localUrl) ??
|
||||
model.getFileMessageLocation(widget.messageID);
|
||||
File f = File(savePath);
|
||||
if (f.existsSync() && widget.messageID != null) {
|
||||
filePath = savePath;
|
||||
setState(() {
|
||||
downloadProgress = 100;
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
@ -89,6 +130,9 @@ class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
|
|||
File f = File(savePath);
|
||||
if (f.existsSync() && widget.messageID != null) {
|
||||
filePath = savePath;
|
||||
setState(() {
|
||||
downloadProgress = 100;
|
||||
});
|
||||
model.setMessageProgress(widget.messageID!, 100);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -136,10 +180,11 @@ class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
|
|||
} else {
|
||||
final DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
|
||||
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
|
||||
if ((androidInfo.version.sdkInt ?? 0) >= 33) {
|
||||
if ((androidInfo.version.sdkInt) >= 33) {
|
||||
} else {
|
||||
var storage = await Permissions.checkPermission(
|
||||
context, Permission.storage.value,
|
||||
context,
|
||||
Permission.storage.value,
|
||||
);
|
||||
if (!storage) {
|
||||
return;
|
||||
|
|
@ -160,10 +205,11 @@ class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
|
|||
} else {
|
||||
final DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
|
||||
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
|
||||
if ((androidInfo.version.sdkInt ?? 0) >= 33) {
|
||||
if ((androidInfo.version.sdkInt) >= 33) {
|
||||
} else {
|
||||
var storage = await Permissions.checkPermission(
|
||||
context, Permission.storage.value,
|
||||
context,
|
||||
Permission.storage.value,
|
||||
);
|
||||
if (!storage) {
|
||||
return;
|
||||
|
|
@ -179,25 +225,42 @@ class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
|
|||
OpenFile.open(filePath);
|
||||
}
|
||||
// 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) {
|
||||
html.AnchorElement(
|
||||
href: widget.fileElem?.path ?? "",
|
||||
)
|
||||
..setAttribute(
|
||||
"download", widget.message.fileElem?.fileName ?? fileName)
|
||||
..setAttribute("target", '_blank')
|
||||
..style.display = "none"
|
||||
..click();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
|
||||
final theme = value.theme;
|
||||
return TIMUIKitMessageReactionWrapper(
|
||||
chatModel: widget.chatModel,
|
||||
isShowJump: widget.isShowJump,
|
||||
clearJump: widget.clearJump,
|
||||
isFromSelf: widget.message.isSelf ?? true,
|
||||
isShowMessageReaction: widget.isShowMessageReaction ?? true,
|
||||
message: widget.message,
|
||||
child: ChangeNotifierProvider.value(
|
||||
value: model,
|
||||
child:
|
||||
Consumer<TUIChatGlobalModel>(builder: (context, value, child) {
|
||||
final received = value.getMessageProgress(widget.messageID);
|
||||
final received = downloadProgress;
|
||||
final fileName = widget.fileElem!.fileName ?? "";
|
||||
final fileSize = widget.fileElem!.fileSize;
|
||||
final borderRadius = widget.isSelf
|
||||
|
|
@ -215,16 +278,19 @@ class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
|
|||
if (widget.fileElem?.fileName != null &&
|
||||
widget.fileElem!.fileName!.isNotEmpty) {
|
||||
final String fileName = widget.fileElem!.fileName!;
|
||||
fileFormat =
|
||||
fileName.split(".")[max(fileName.split(".").length - 1, 0)];
|
||||
fileFormat = fileName.split(".")[max(fileName.split(".").length - 1, 0)];
|
||||
}
|
||||
return InkWell(
|
||||
return TIMUIKitMessageReactionWrapper(
|
||||
chatModel: widget.chatModel,
|
||||
isShowJump: widget.isShowJump,
|
||||
clearJump: widget.clearJump,
|
||||
isFromSelf: widget.message.isSelf ?? true,
|
||||
isShowMessageReaction: widget.isShowMessageReaction ?? true,
|
||||
message: widget.message,
|
||||
child: GestureDetector(
|
||||
onTap: () async {
|
||||
if (PlatformUtils().isWeb) {
|
||||
launchUrl(
|
||||
Uri.parse(widget.fileElem?.path ?? ""),
|
||||
mode: LaunchMode.externalApplication,
|
||||
);
|
||||
downloadWebFile(widget.fileElem?.path ?? "");
|
||||
return;
|
||||
}
|
||||
if (await hasFile()) {
|
||||
|
|
@ -260,8 +326,8 @@ class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
|
|||
width: 237,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: theme.weakDividerColor ??
|
||||
CommonColor.weakDividerColor,
|
||||
color:
|
||||
theme.weakDividerColor ?? CommonColor.weakDividerColor,
|
||||
),
|
||||
borderRadius: borderRadius),
|
||||
child: Stack(children: [
|
||||
|
|
@ -278,8 +344,8 @@ class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
|
|||
theme.lightPrimaryMaterialColor.shade50)),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 8, horizontal: 12),
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
|
||||
child: Row(
|
||||
mainAxisAlignment: widget.isSelf
|
||||
? MainAxisAlignment.end
|
||||
|
|
@ -293,8 +359,7 @@ class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
|
|||
constraints:
|
||||
const BoxConstraints(maxWidth: 160),
|
||||
child: LayoutBuilder(
|
||||
builder:
|
||||
(buildContext, boxConstraints) {
|
||||
builder: (buildContext, boxConstraints) {
|
||||
return CustomText(
|
||||
fileName,
|
||||
width: boxConstraints.maxWidth,
|
||||
|
|
@ -311,8 +376,7 @@ class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
|
|||
showFileSize(fileSize),
|
||||
// "${received > 0 ? (received / 1024).ceil() : (received / 1024).ceil()} KB",
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: theme.weakTextColor),
|
||||
fontSize: 14, color: theme.weakTextColor),
|
||||
)
|
||||
],
|
||||
)),
|
||||
|
|
@ -321,7 +385,6 @@ class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
|
|||
),
|
||||
])),
|
||||
]),
|
||||
));
|
||||
})));
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ class TIMUIKitImageElem extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
|
||||
final TUIChatGlobalModel globalModel = serviceLocator<TUIChatGlobalModel>();
|
||||
double? networkImagePositionRadio; // 加这个字段用于异步获取被安全打击后的兜底图的比例
|
||||
final TUIChatGlobalModel model = serviceLocator<TUIChatGlobalModel>();
|
||||
final MessageService _messageService = serviceLocator<MessageService>();
|
||||
|
|
@ -140,7 +141,7 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
|
|||
final DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
|
||||
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
|
||||
if (PlatformUtils().isMobile) {
|
||||
if ((androidInfo.version.sdkInt ?? 0) >= 33) {
|
||||
if ((androidInfo.version.sdkInt) >= 33) {
|
||||
final photos = await Permissions.checkPermission(
|
||||
context,
|
||||
Permission.photos.value,
|
||||
|
|
@ -309,137 +310,6 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
|
|||
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(
|
||||
dynamic heroTag, double? positionRadio, TUITheme? theme,
|
||||
{String? path, V2TimImage? originalImg, V2TimImage? smallImg}) {
|
||||
|
|
@ -458,12 +328,32 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
|
|||
getImage(
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
if (PlatformUtils().isWeb) {
|
||||
launchUrl(
|
||||
Uri.parse(widget.message.imageElem?.path ?? ""),
|
||||
mode: LaunchMode.externalApplication,
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (isDesktopScreen) {
|
||||
if (TencentUtils.checkString(widget
|
||||
.message.imageElem!.imageList![0]!.localUrl) !=
|
||||
null &&
|
||||
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
|
||||
));
|
||||
type: TIMCallbackType.INFO));
|
||||
}
|
||||
} else {
|
||||
Navigator.of(context).push(
|
||||
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() {
|
||||
final current = (DateTime.now().millisecondsSinceEpoch / 1000).ceil();
|
||||
final timeStamp = widget.message.timestamp ?? current;
|
||||
|
|
@ -572,8 +608,7 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
|
|||
smallImg: smallImg, originalImg: originalImg);
|
||||
}
|
||||
|
||||
if (
|
||||
(smallImg?.url ?? originalImg?.url) != null &&
|
||||
if ((smallImg?.url ?? originalImg?.url) != null &&
|
||||
(smallImg?.url ?? originalImg?.url)!.isNotEmpty) {
|
||||
return _renderNetworkImage(heroTag, positionRadio, theme,
|
||||
smallImg: smallImg, originalImg: originalImg);
|
||||
|
|
|
|||
|
|
@ -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/screen_utils.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_cloud_chat_uikit/base_widgets/tim_ui_kit_base.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,
|
||||
customEmojiStickerList: widget.customEmojiStickerList,
|
||||
isEnableTextSelection:
|
||||
widget.chatModel.chatConfig.isEnableTextSelection);
|
||||
widget.chatModel.chatConfig.isEnableTextSelection ?? false);
|
||||
return Container(
|
||||
padding: widget.textPadding ?? EdgeInsets.all(isDesktopScreen ? 12 : 10),
|
||||
decoration: BoxDecoration(
|
||||
|
|
|
|||
|
|
@ -212,7 +212,7 @@ class _TIMUIKitSoundElemState extends TIMUIKitState<TIMUIKitSoundElem> {
|
|||
}
|
||||
}
|
||||
}
|
||||
return InkWell(
|
||||
return GestureDetector(
|
||||
onTap: () => _playSound(),
|
||||
child: Container(
|
||||
padding: widget.textPadding ?? const EdgeInsets.all(10),
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import 'dart:async';
|
|||
import 'dart:convert';
|
||||
|
||||
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:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.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,
|
||||
customEmojiStickerList: widget.customEmojiStickerList,
|
||||
isEnableTextSelection:
|
||||
widget.chatModel.chatConfig.isEnableTextSelection);
|
||||
widget.chatModel.chatConfig.isEnableTextSelection ?? false);
|
||||
final borderRadius = widget.isFromSelf
|
||||
? const BorderRadius.only(
|
||||
topLeft: Radius.circular(10),
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import 'dart:async';
|
|||
import 'dart:convert';
|
||||
|
||||
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:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart';
|
||||
|
|
@ -127,7 +127,7 @@ class _TIMUIKitTextTranslationElemState
|
|||
isUseDefaultEmoji: widget.isUseDefaultEmoji,
|
||||
customEmojiStickerList: widget.customEmojiStickerList,
|
||||
isEnableTextSelection:
|
||||
widget.chatModel.chatConfig.isEnableTextSelection);
|
||||
widget.chatModel.chatConfig.isEnableTextSelection ?? false);
|
||||
|
||||
return TencentUtils.checkString(translateText) != null
|
||||
? Container(
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.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_state.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/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/services_locatar.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/ui/utils/message.dart';
|
||||
|
|
@ -40,7 +38,6 @@ class TIMUIKitVideoElem extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _TIMUIKitVideoElemState extends TIMUIKitState<TIMUIKitVideoElem> {
|
||||
final TUIChatGlobalModel globalModel = serviceLocator<TUIChatGlobalModel>();
|
||||
final MessageService _messageService = serviceLocator<MessageService>();
|
||||
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)
|
||||
? (stateElement.snapshotPath!.isNotEmpty
|
||||
? Image.file(File(stateElement.snapshotPath!), fit: BoxFit.fitWidth)
|
||||
: Image.file(File(stateElement.localSnapshotUrl!),
|
||||
fit: BoxFit.fitWidth))
|
||||
: (kIsWeb ||
|
||||
: (PlatformUtils().isWeb ||
|
||||
stateElement.localSnapshotUrl == null ||
|
||||
stateElement.localSnapshotUrl == "")
|
||||
? Image.network(stateElement.snapshotUrl!, fit: BoxFit.fitWidth)
|
||||
|
|
@ -127,9 +124,9 @@ class _TIMUIKitVideoElemState extends TIMUIKitState<TIMUIKitVideoElem> {
|
|||
}
|
||||
|
||||
downloadMessageDetailAndSave() async {
|
||||
if (widget.message.msgID != null && widget.message.msgID != '') {
|
||||
if (widget.message.videoElem!.videoUrl == null ||
|
||||
widget.message.videoElem!.videoUrl == '') {
|
||||
if (TencentUtils.checkString(widget.message.msgID) != null) {
|
||||
if (TencentUtils.checkString(widget.message.videoElem!.videoUrl) ==
|
||||
null) {
|
||||
final response = await _messageService.getMessageOnlineUrl(
|
||||
msgID: widget.message.msgID!);
|
||||
if (response.data != null) {
|
||||
|
|
@ -140,16 +137,19 @@ class _TIMUIKitVideoElemState extends TIMUIKitState<TIMUIKitVideoElem> {
|
|||
}
|
||||
}
|
||||
if (!PlatformUtils().isWeb) {
|
||||
if (widget.message.videoElem!.localVideoUrl == null ||
|
||||
widget.message.videoElem!.localVideoUrl == '') {
|
||||
if (TencentUtils.checkString(widget.message.videoElem!.localVideoUrl) ==
|
||||
null ||
|
||||
!File(widget.message.videoElem!.localVideoUrl!).existsSync()) {
|
||||
_messageService.downloadMessage(
|
||||
msgID: widget.message.msgID!,
|
||||
messageType: 5,
|
||||
imageType: 0,
|
||||
isSnapshot: false);
|
||||
}
|
||||
if (widget.message.videoElem!.localSnapshotUrl == null ||
|
||||
widget.message.videoElem!.localSnapshotUrl == '') {
|
||||
if (TencentUtils.checkString(
|
||||
widget.message.videoElem!.localSnapshotUrl) ==
|
||||
null ||
|
||||
!File(widget.message.videoElem!.localSnapshotUrl!).existsSync()) {
|
||||
_messageService.downloadMessage(
|
||||
msgID: widget.message.msgID!,
|
||||
messageType: 5,
|
||||
|
|
@ -172,12 +172,20 @@ class _TIMUIKitVideoElemState extends TIMUIKitState<TIMUIKitVideoElem> {
|
|||
final heroTag =
|
||||
"${widget.message.msgID ?? widget.message.id ?? widget.message.timestamp ?? DateTime.now().millisecondsSinceEpoch}${widget.isFrom}";
|
||||
|
||||
return InkWell(
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
if (PlatformUtils().isWeb) {
|
||||
launchUrl(
|
||||
Uri.parse(widget.message.videoElem?.videoPath ?? ""),
|
||||
mode: LaunchMode.externalApplication,
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (PlatformUtils().isDesktop) {
|
||||
final videoElem = widget.message.videoElem;
|
||||
if (videoElem != null) {
|
||||
final localVideoUrl = TencentUtils.checkString(videoElem.localVideoUrl);
|
||||
final localVideoUrl =
|
||||
TencentUtils.checkString(videoElem.localVideoUrl);
|
||||
final videoPath = TencentUtils.checkString(videoElem.videoPath);
|
||||
final videoUrl = videoElem.videoUrl;
|
||||
|
||||
|
|
@ -197,8 +205,7 @@ class _TIMUIKitVideoElemState extends TIMUIKitState<TIMUIKitVideoElem> {
|
|||
onTIMCallback(TIMCallback(
|
||||
infoCode: 6660414,
|
||||
infoRecommendText: TIM_t("正在下载中"),
|
||||
type: TIMCallbackType.INFO
|
||||
));
|
||||
type: TIMCallbackType.INFO));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
@ -227,15 +234,14 @@ class _TIMUIKitVideoElemState extends TIMUIKitState<TIMUIKitVideoElem> {
|
|||
borderRadius: const BorderRadius.all(Radius.circular(5)),
|
||||
child: LayoutBuilder(builder:
|
||||
(BuildContext context, BoxConstraints constraints) {
|
||||
double positionRadio = 0.56;
|
||||
if (stateElement.snapshotWidth != null &&
|
||||
double? positionRadio;
|
||||
if ((stateElement.snapshotWidth) != null &&
|
||||
stateElement.snapshotHeight != null &&
|
||||
stateElement.snapshotWidth != 0 &&
|
||||
stateElement.snapshotHeight != 0) {
|
||||
positionRadio = (stateElement.snapshotWidth! /
|
||||
stateElement.snapshotHeight!);
|
||||
}
|
||||
|
||||
return ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: PlatformUtils().isWeb
|
||||
|
|
@ -244,12 +250,11 @@ class _TIMUIKitVideoElemState extends TIMUIKitState<TIMUIKitVideoElem> {
|
|||
maxHeight: min(constraints.maxHeight * 0.8, 300),
|
||||
minHeight: 20,
|
||||
minWidth: 20),
|
||||
child: AspectRatio(
|
||||
aspectRatio: positionRadio,
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
if (stateElement.snapshotUrl != null ||
|
||||
stateElement.snapshotUrl != null)
|
||||
if (positionRadio != null &&
|
||||
(stateElement.snapshotUrl != null ||
|
||||
stateElement.snapshotUrl != null))
|
||||
AspectRatio(
|
||||
aspectRatio: positionRadio,
|
||||
child: Container(
|
||||
|
|
@ -265,8 +270,7 @@ class _TIMUIKitVideoElemState extends TIMUIKitState<TIMUIKitVideoElem> {
|
|||
],
|
||||
),
|
||||
if (widget.message.status !=
|
||||
MessageStatus
|
||||
.V2TIM_MSG_STATUS_SENDING &&
|
||||
MessageStatus.V2TIM_MSG_STATUS_SENDING &&
|
||||
(stateElement.snapshotUrl != null ||
|
||||
stateElement.snapshotPath != null) &&
|
||||
stateElement.videoPath != null ||
|
||||
|
|
@ -282,14 +286,13 @@ class _TIMUIKitVideoElemState extends TIMUIKitState<TIMUIKitVideoElem> {
|
|||
right: 10,
|
||||
bottom: 10,
|
||||
child: Text(
|
||||
MessageUtils.formatVideoTime(widget
|
||||
.message.videoElem?.duration ??
|
||||
MessageUtils.formatVideoTime(
|
||||
widget.message.videoElem?.duration ??
|
||||
0)
|
||||
.toString(),
|
||||
style: const TextStyle(
|
||||
color: Colors.white, fontSize: 12))),
|
||||
],
|
||||
),
|
||||
));
|
||||
}),
|
||||
))),
|
||||
|
|
|
|||
|
|
@ -100,6 +100,7 @@ class _AtMemberPanelState extends TIMUIKitState<AtMemberPanel> {
|
|||
width: 24,
|
||||
child: Avatar(
|
||||
faceUrl: memberItem.faceUrl ?? "",
|
||||
type: 1,
|
||||
showName: showName),
|
||||
),
|
||||
const SizedBox(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
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 {
|
||||
/// Confirm string for the confirm button.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
// 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:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitTextField/special_text/http_text.dart';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
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';
|
||||
|
||||
///emoji/image text
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.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 {
|
||||
HttpText(TextStyle? textStyle, SpecialTextGestureTapCallback? onTap,
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ class AtText extends StatefulWidget {
|
|||
final Function(
|
||||
V2TimGroupMemberFullInfo memberInfo, TapDownDetails? tapDetails)?
|
||||
onChooseMember;
|
||||
final bool canAtAll;
|
||||
|
||||
// some Group type cant @all
|
||||
final String? groupType;
|
||||
|
|
@ -32,6 +33,7 @@ class AtText extends StatefulWidget {
|
|||
this.groupMemberList,
|
||||
this.closeFunc,
|
||||
this.onChooseMember,
|
||||
this.canAtAll = false,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
|
@ -116,7 +118,7 @@ class _AtTextState extends TIMUIKitState<AtText> {
|
|||
groupType: widget.groupType ?? "",
|
||||
memberList: searchMemberList ?? [],
|
||||
onTapMemberItem: _onTapMemberItem,
|
||||
canAtAll: true,
|
||||
canAtAll: widget.canAtAll,
|
||||
canSlideDelete: false,
|
||||
touchBottomCallBack: () {
|
||||
// Get all by once, unnecessary to load more
|
||||
|
|
@ -130,6 +132,7 @@ class _AtTextState extends TIMUIKitState<AtText> {
|
|||
}
|
||||
|
||||
return TUIKitScreenUtils.getDeviceWidget(
|
||||
context: context,
|
||||
desktopWidget: mentionedMembersBody(),
|
||||
defaultWidget: Scaffold(
|
||||
appBar: AppBar(
|
||||
|
|
@ -137,8 +140,7 @@ class _AtTextState extends TIMUIKitState<AtText> {
|
|||
iconTheme: IconThemeData(
|
||||
color: theme.appbarTextColor,
|
||||
),
|
||||
backgroundColor: theme.appbarBgColor ??
|
||||
theme.primaryColor,
|
||||
backgroundColor: theme.appbarBgColor ?? theme.primaryColor,
|
||||
leading: Row(
|
||||
children: [
|
||||
IconButton(
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import 'package:path_provider/path_provider.dart';
|
|||
import 'package:provider/provider.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_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/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';
|
||||
|
|
@ -360,7 +360,7 @@ class _MorePanelState extends TIMUIKitState<MorePanel> {
|
|||
if (PlatformUtils().isMobile){
|
||||
if(PlatformUtils().isAndroid){
|
||||
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
|
||||
if ((androidInfo.version.sdkInt ?? 0) >= 33) {
|
||||
if ((androidInfo.version.sdkInt) >= 33) {
|
||||
final videos = await Permissions.checkPermission(
|
||||
context,Permission.videos.value,
|
||||
theme,
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ class _SendSoundMessageState extends TIMUIKitState<SendSoundMessage> {
|
|||
),
|
||||
);
|
||||
});
|
||||
Overlay.of(context)?.insert(overlayEntry!);
|
||||
Overlay.of(context).insert(overlayEntry!);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -38,8 +38,6 @@ typedef CustomStickerPanel = Widget Function({
|
|||
double? height,
|
||||
});
|
||||
|
||||
GlobalKey<_InputTextFieldState> inputTextFieldState = GlobalKey();
|
||||
|
||||
class TIMUIKitInputTextField extends StatefulWidget {
|
||||
/// conversation id
|
||||
final String conversationID;
|
||||
|
|
@ -59,7 +57,7 @@ class TIMUIKitInputTextField extends StatefulWidget {
|
|||
/// hint text for textField widget
|
||||
final String? hintText;
|
||||
|
||||
/// config for more pannel
|
||||
/// config for more panel
|
||||
final MorePanelConfig? morePanelConfig;
|
||||
|
||||
/// show send audio icon
|
||||
|
|
@ -133,15 +131,13 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
|
|||
int? currentCursor;
|
||||
bool isAddingAtSearchWords = false;
|
||||
double inputWidth = 900;
|
||||
|
||||
Map<String, V2TimGroupMemberFullInfo> memberInfoMap = {};
|
||||
|
||||
Map<String, V2TimGroupMemberFullInfo> mentionedMembersMap = {};
|
||||
late TextEditingController textEditingController;
|
||||
final TUIConversationViewModel conversationModel =
|
||||
serviceLocator<TUIConversationViewModel>();
|
||||
final TUISelfInfoViewModel selfModel = serviceLocator<TUISelfInfoViewModel>();
|
||||
MuteStatus muteStatus = MuteStatus.none;
|
||||
|
||||
bool _isComposingText = false;
|
||||
int latestSendEditStatusTime = DateTime.now().millisecondsSinceEpoch;
|
||||
|
||||
setCurrentCursor(int? value) {
|
||||
|
|
@ -160,7 +156,9 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
|
|||
}
|
||||
|
||||
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'), "");
|
||||
}
|
||||
|
||||
getShowName(message) {
|
||||
return TencentUtils.checkStringWithoutSpace(message?.friendRemark) ??
|
||||
TencentUtils.checkStringWithoutSpace(message?.nickName) ??
|
||||
TencentUtils.checkStringWithoutSpace(message?.userID);
|
||||
}
|
||||
|
||||
handleSetDraftText([String? id, ConvType? convType]) async {
|
||||
String convID = id ?? widget.conversationID;
|
||||
String conversationID =
|
||||
(convType ?? widget.conversationType) == ConvType.c2c
|
||||
String conversationID = convID.contains("@TOPIC#")
|
||||
? convID
|
||||
: ((convType ?? widget.conversationType) == ConvType.c2c
|
||||
? "c2c_$convID"
|
||||
: "group_$convID";
|
||||
: "group_$convID");
|
||||
String text = textEditingController.text;
|
||||
String? draftText = _filterU200b(text);
|
||||
|
||||
|
|
@ -258,7 +251,7 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
|
|||
|
||||
List<String> getUserIdFromMemberInfoMap() {
|
||||
List<String> userList = [];
|
||||
memberInfoMap.forEach((String key, V2TimGroupMemberFullInfo info) {
|
||||
mentionedMembersMap.forEach((String key, V2TimGroupMemberFullInfo info) {
|
||||
userList.add(info.userID);
|
||||
});
|
||||
|
||||
|
|
@ -278,7 +271,7 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
|
|||
convType: convType,
|
||||
atUserIDList: getUserIdFromMemberInfoMap()),
|
||||
context);
|
||||
} else if (memberInfoMap.isNotEmpty) {
|
||||
} else if (mentionedMembersMap.isNotEmpty) {
|
||||
widget.model.sendTextAtMessage(
|
||||
text: text,
|
||||
convType: widget.conversationType,
|
||||
|
|
@ -293,7 +286,7 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
|
|||
textEditingController.clear();
|
||||
currentCursor = null;
|
||||
lastText = "";
|
||||
memberInfoMap = {};
|
||||
mentionedMembersMap = {};
|
||||
|
||||
goDownBottom();
|
||||
_handleSendEditStatus("", false);
|
||||
|
|
@ -320,11 +313,6 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
|
|||
}
|
||||
|
||||
void onModelChanged() {
|
||||
if (widget.model.repliedMessage != null) {
|
||||
narrowTextFieldKey.currentState?.showKeyboard = true;
|
||||
focusNode.requestFocus();
|
||||
_addDeleteTag();
|
||||
} else {}
|
||||
if (widget.model.editRevokedMsg != "") {
|
||||
narrowTextFieldKey.currentState?.showKeyboard = true;
|
||||
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() {
|
||||
final selection = textEditingController.selection;
|
||||
currentCursor = selection.baseOffset;
|
||||
|
|
@ -362,24 +343,32 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
|
|||
}
|
||||
|
||||
_longPressToAt(String? userID, String? nickName) {
|
||||
final memberInfo = V2TimGroupMemberFullInfo(
|
||||
if (TencentUtils.checkString(userID) == null) {
|
||||
focusNode.requestFocus();
|
||||
} else {
|
||||
final memberInfo = widget.model.groupMemberList
|
||||
?.firstWhere((element) => element?.userID == userID) ??
|
||||
V2TimGroupMemberFullInfo(
|
||||
userID: userID ?? "",
|
||||
nickName: nickName,
|
||||
);
|
||||
final showName = _getShowName(memberInfo);
|
||||
memberInfoMap["@$showName"] = memberInfo;
|
||||
mentionedMembersMap["@$showName"] = memberInfo;
|
||||
String text = "${textEditingController.text}@$showName ";
|
||||
//please do not delete space
|
||||
focusNode.requestFocus();
|
||||
textEditingController.text = text;
|
||||
textEditingController.selection =
|
||||
TextSelection.fromPosition(TextPosition(offset: text.length - 1));
|
||||
TextSelection.fromPosition(TextPosition(offset: text.length));
|
||||
lastText = text;
|
||||
_isComposingText = false;
|
||||
narrowTextFieldKey.currentState?.showKeyboard = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool shouldRemoveAtTag(String atTag, String deletedChar) {
|
||||
final atMemberArray = [];
|
||||
memberInfoMap.forEach((key, value) {
|
||||
mentionedMembersMap.forEach((key, value) {
|
||||
atMemberArray.add(key);
|
||||
});
|
||||
for (String member in atMemberArray) {
|
||||
|
|
@ -403,7 +392,11 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
|
|||
final Offset caretPosition =
|
||||
textPainter.getOffsetForCaret(lastLineOffset, Rect.zero);
|
||||
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);
|
||||
}
|
||||
|
|
@ -417,11 +410,11 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
|
|||
parseAtList.add(str);
|
||||
}
|
||||
for (String? key in parseAtList) {
|
||||
if (key != null && memberInfoMap[key] != null) {
|
||||
map[key] = memberInfoMap[key]!;
|
||||
if (key != null && mentionedMembersMap[key] != null) {
|
||||
map[key] = mentionedMembersMap[key]!;
|
||||
}
|
||||
}
|
||||
memberInfoMap = map;
|
||||
mentionedMembersMap = map;
|
||||
}
|
||||
|
||||
_handleAtText(String text, TUIChatSeparateViewModel model) async {
|
||||
|
|
@ -464,16 +457,21 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
|
|||
parseAtList.add(str);
|
||||
}
|
||||
for (String? key in parseAtList) {
|
||||
if (key != null && memberInfoMap[key] != null) {
|
||||
map[key] = memberInfoMap[key]!;
|
||||
if (key != null && mentionedMembersMap[key] != null) {
|
||||
map[key] = mentionedMembersMap[key]!;
|
||||
}
|
||||
}
|
||||
memberInfoMap = map;
|
||||
mentionedMembersMap = map;
|
||||
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) {
|
||||
final atPlace = text.lastIndexOf("@");
|
||||
final keyword = text.substring(atPlace + 1);
|
||||
|
|
@ -484,16 +482,54 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
|
|||
model.atPositionY = atPosition.dy;
|
||||
isAddingAtSearchWords = true;
|
||||
}
|
||||
final List<V2TimGroupMemberFullInfo?> showAtMemberList =
|
||||
(model.groupMemberList ?? []).where((element) {
|
||||
final friendRemark = element?.friendRemark ?? "";
|
||||
final nickName = element?.nickName ?? "";
|
||||
final showName = TencentUtils.checkString(friendRemark) ??
|
||||
TencentUtils.checkString(nickName) ??
|
||||
TencentUtils.checkString(element?.userID) ??
|
||||
"";
|
||||
return showName.contains(keyword);
|
||||
}).toList();
|
||||
List<V2TimGroupMemberFullInfo> showAtMemberList = (model
|
||||
.groupMemberList ??
|
||||
[])
|
||||
.where((element) {
|
||||
final showName = (TencentUtils.checkStringWithoutSpace(
|
||||
element?.friendRemark) ??
|
||||
TencentUtils.checkStringWithoutSpace(element?.nameCard) ??
|
||||
TencentUtils.checkStringWithoutSpace(element?.nickName) ??
|
||||
TencentUtils.checkStringWithoutSpace(element?.userID) ??
|
||||
"")
|
||||
.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.showAtMemberList = showAtMemberList;
|
||||
|
|
@ -514,12 +550,13 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
|
|||
groupMemberList: model.groupMemberList,
|
||||
groupInfo: model.groupInfo,
|
||||
groupID: groupID,
|
||||
canAtAll: canAtAll,
|
||||
groupType: widget.groupType),
|
||||
),
|
||||
);
|
||||
final showName = _getShowName(memberInfo);
|
||||
if (memberInfo != null) {
|
||||
memberInfoMap["@$showName"] = memberInfo;
|
||||
mentionedMembersMap["@$showName"] = memberInfo;
|
||||
textEditingController.text = "$text$showName ";
|
||||
lastText = "$text$showName ";
|
||||
}
|
||||
|
|
@ -547,19 +584,15 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
|
|||
bool? isAddToCursorPosition = false}) {
|
||||
if (memberInfo != null) {
|
||||
final String showName = _getShowName(memberInfo);
|
||||
memberInfoMap["@$showName"] = memberInfo;
|
||||
mentionedMembersMap["@$showName"] = memberInfo;
|
||||
replaceAtTag(showName);
|
||||
widget.model.showAtMemberList = [];
|
||||
widget.model.activeAtIndex = -1;
|
||||
focusNode.requestFocus();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (PlatformUtils().isWeb || PlatformUtils().isDesktop) {
|
||||
focusNode = FocusNode(
|
||||
onKey: (node, event) {
|
||||
KeyEventResult handleDesktopKeyEvent(FocusNode node, RawKeyEvent event) {
|
||||
final activeIndex = widget.model.activeAtIndex;
|
||||
final showMemberList = widget.model.showAtMemberList;
|
||||
if (event.runtimeType == RawKeyDownEvent) {
|
||||
|
|
@ -582,8 +615,8 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
|
|||
|
||||
return KeyEventResult.handled;
|
||||
} else if (event.physicalKey == PhysicalKeyboardKey.enter) {
|
||||
if (!isAddingAtSearchWords ||
|
||||
widget.model.showAtMemberList.isEmpty) {
|
||||
if (!_isComposingText) {
|
||||
if (!isAddingAtSearchWords || widget.model.showAtMemberList.isEmpty) {
|
||||
onSubmitted();
|
||||
} else {
|
||||
isAddingAtSearchWords = false;
|
||||
|
|
@ -596,6 +629,7 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
|
|||
}
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
}
|
||||
|
||||
if (event.isKeyPressed(LogicalKeyboardKey.arrowUp) &&
|
||||
isAddingAtSearchWords &&
|
||||
|
|
@ -619,31 +653,56 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
|
|||
}
|
||||
|
||||
return KeyEventResult.ignored;
|
||||
},
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (PlatformUtils().isWeb || PlatformUtils().isDesktop) {
|
||||
focusNode = FocusNode(
|
||||
onKey: (node, event) => handleDesktopKeyEvent(node, event),
|
||||
);
|
||||
} else {
|
||||
focusNode = FocusNode();
|
||||
}
|
||||
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) {
|
||||
textEditingController.text = widget.initText!;
|
||||
}
|
||||
if (widget.controller != null) {
|
||||
widget.controller?.addListener(controllerHandler);
|
||||
}
|
||||
widget.model.addListener(onModelChanged);
|
||||
final AppLocale appLocale = I18nUtils.findDeviceLocale(null);
|
||||
languageType =
|
||||
(appLocale == AppLocale.zhHans || appLocale == AppLocale.zhHant)
|
||||
? 'zh'
|
||||
: '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
|
||||
|
|
@ -651,18 +710,27 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
|
|||
super.didUpdateWidget(oldWidget);
|
||||
if (widget.conversationID != oldWidget.conversationID) {
|
||||
handleSetDraftText(oldWidget.conversationID, oldWidget.conversationType);
|
||||
mentionedMembersMap.clear();
|
||||
if (oldWidget.initText != widget.initText) {
|
||||
textEditingController.text = widget.initText ?? "";
|
||||
} else {
|
||||
textEditingController.clear();
|
||||
}
|
||||
}
|
||||
if (widget.initText != oldWidget.initText &&
|
||||
TencentUtils.checkString(widget.initText) != null) {
|
||||
textEditingController.text = widget.initText!;
|
||||
focusNode.requestFocus();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
handleSetDraftText();
|
||||
widget.model.removeListener(onModelChanged);
|
||||
if (widget.controller != null) {
|
||||
widget.controller?.removeListener(controllerHandler);
|
||||
}
|
||||
focusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
|
@ -738,6 +806,7 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
|
|||
|
||||
@override
|
||||
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
|
||||
final theme = value.theme;
|
||||
final TUIChatSeparateViewModel model =
|
||||
Provider.of<TUIChatSeparateViewModel>(context);
|
||||
|
||||
|
|
@ -763,6 +832,7 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
|
|||
builder: (BuildContext context, BoxConstraints constraints) {
|
||||
inputWidth = constraints.maxWidth;
|
||||
return TUIKitScreenUtils.getDeviceWidget(
|
||||
context: context,
|
||||
defaultWidget: TIMUIKitTextFieldLayoutNarrow(
|
||||
onEmojiSubmitted: onEmojiSubmitted,
|
||||
onCustomEmojiFaceSubmitted: onCustomEmojiFaceSubmitted,
|
||||
|
|
@ -798,6 +868,7 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
|
|||
showMorePanel: widget.showMorePanel,
|
||||
customEmojiStickerList: widget.customEmojiStickerList),
|
||||
desktopWidget: TIMUIKitTextFieldLayoutWide(
|
||||
theme: theme,
|
||||
currentConversation: widget.currentConversation,
|
||||
onEmojiSubmitted: onEmojiSubmitted,
|
||||
onCustomEmojiFaceSubmitted: onCustomEmojiFaceSubmitted,
|
||||
|
|
@ -825,7 +896,6 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
|
|||
handleAtText: (text) {
|
||||
_handleAtText(text, model);
|
||||
},
|
||||
handleSoftKeyBoardDelete: _handleSoftKeyBoardDelete,
|
||||
onSubmitted: onSubmitted,
|
||||
goDownBottom: goDownBottom,
|
||||
showSendAudio: widget.showSendAudio,
|
||||
|
|
|
|||
|
|
@ -1,12 +1,21 @@
|
|||
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 {
|
||||
TextEditingController? textEditingController = TextEditingController();
|
||||
ActionType? actionType;
|
||||
String? atUserName;
|
||||
String? atUserID;
|
||||
String inputText = "";
|
||||
V2TimGroupMemberFullInfo? groupMemberFullInfo;
|
||||
|
||||
TIMUIKitInputTextFieldController([TextEditingController? controller]) {
|
||||
if (controller != null) {
|
||||
|
|
@ -26,4 +35,21 @@ class TIMUIKitInputTextFieldController extends ChangeNotifier {
|
|||
atUserID = userID;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
setTextField(String text) {
|
||||
inputText = text;
|
||||
actionType = ActionType.setTextField;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
requestFocus() {
|
||||
actionType = ActionType.requestFocus;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
handleAtMember(V2TimGroupMemberFullInfo? memberInfo) {
|
||||
actionType = ActionType.handleAtMember;
|
||||
groupMemberFullInfo = memberInfo;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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/tim_uikit_emoji_panel.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';
|
||||
|
||||
GlobalKey<_TIMUIKitTextFieldLayoutNarrowState> narrowTextFieldKey = GlobalKey();
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import 'dart:io';
|
|||
import 'dart:math';
|
||||
import 'package:fc_native_video_thumbnail_for_us/fc_native_video_thumbnail_for_us.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:pasteboard/pasteboard.dart';
|
||||
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/tim_uikit_emoji_panel.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:url_launcher/url_launcher.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
|
@ -39,6 +40,7 @@ class DesktopControlBarItem {
|
|||
final String item;
|
||||
final IconData? icon;
|
||||
final String? imgPath;
|
||||
final String? svgPath;
|
||||
final Color? color;
|
||||
final ValueChanged<Offset?> onClick;
|
||||
final String? showName;
|
||||
|
|
@ -49,10 +51,13 @@ class DesktopControlBarItem {
|
|||
this.icon,
|
||||
this.color,
|
||||
this.imgPath,
|
||||
this.svgPath,
|
||||
required this.onClick,
|
||||
this.showName,
|
||||
this.size})
|
||||
: assert(icon != null || imgPath != null);
|
||||
: assert(icon != null ||
|
||||
TencentUtils.checkString(imgPath) != null ||
|
||||
TencentUtils.checkString(svgPath) != null);
|
||||
}
|
||||
|
||||
class DesktopControlBarConfig {
|
||||
|
|
@ -81,7 +86,7 @@ class TIMUIKitTextFieldLayoutWide extends StatefulWidget {
|
|||
final Function(String, bool) handleSendEditStatus;
|
||||
final VoidCallback backSpaceText;
|
||||
final ValueChanged<String> addStickerToText;
|
||||
|
||||
final TUITheme theme;
|
||||
final ValueChanged<String> handleAtText;
|
||||
|
||||
/// Whether to use the default emoji
|
||||
|
|
@ -125,8 +130,6 @@ class TIMUIKitTextFieldLayoutWide extends StatefulWidget {
|
|||
/// show send audio icon
|
||||
final bool showSendAudio;
|
||||
|
||||
final VoidCallback handleSoftKeyBoardDelete;
|
||||
|
||||
/// on text changed
|
||||
final void Function(String)? onChanged;
|
||||
|
||||
|
|
@ -168,7 +171,6 @@ class TIMUIKitTextFieldLayoutWide extends StatefulWidget {
|
|||
this.onChanged,
|
||||
required this.handleSendEditStatus,
|
||||
required this.handleAtText,
|
||||
required this.handleSoftKeyBoardDelete,
|
||||
this.repliedMessage,
|
||||
this.forbiddenText,
|
||||
required this.onSubmitted,
|
||||
|
|
@ -179,7 +181,8 @@ class TIMUIKitTextFieldLayoutWide extends StatefulWidget {
|
|||
this.hintText,
|
||||
required this.customEmojiStickerList,
|
||||
this.controller,
|
||||
required this.currentConversation})
|
||||
required this.currentConversation,
|
||||
required this.theme})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
|
|
@ -199,6 +202,7 @@ class _TIMUIKitTextFieldLayoutWideState
|
|||
double? bottomPadding;
|
||||
late ScrollController _scrollController;
|
||||
late FocusNode textFocusNode;
|
||||
late List<DesktopControlBarItem> defaultControlBarItems;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
|
@ -214,6 +218,27 @@ class _TIMUIKitTextFieldLayoutWideState
|
|||
textFocusNode = FocusNode();
|
||||
widget.focusNode.requestFocus();
|
||||
_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() {
|
||||
|
|
@ -324,6 +349,8 @@ class _TIMUIKitTextFieldLayoutWideState
|
|||
addText: (int unicode) {
|
||||
final newText = String.fromCharCode(unicode);
|
||||
widget.addStickerToText(newText);
|
||||
entry?.remove();
|
||||
entry = null;
|
||||
},
|
||||
addCustomEmojiText: ((String singleEmojiName) {
|
||||
String? emojiName = singleEmojiName.split('.png')[0];
|
||||
|
|
@ -335,6 +362,8 @@ class _TIMUIKitTextFieldLayoutWideState
|
|||
}
|
||||
final newText = '[$emojiName]';
|
||||
widget.addStickerToText(newText);
|
||||
entry?.remove();
|
||||
entry = null;
|
||||
}),
|
||||
defaultCustomEmojiStickerList:
|
||||
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),
|
||||
);
|
||||
});
|
||||
Overlay.of(context)?.insert(entry!);
|
||||
Overlay.of(context).insert(entry!);
|
||||
}
|
||||
}
|
||||
|
||||
_removeOverlay() {
|
||||
entry!.remove();
|
||||
entry?.remove();
|
||||
entry = null;
|
||||
}
|
||||
|
||||
|
|
@ -399,9 +428,7 @@ class _TIMUIKitTextFieldLayoutWideState
|
|||
convID: convID,
|
||||
convType: convType),
|
||||
context);
|
||||
return;
|
||||
}
|
||||
|
||||
} else {
|
||||
File file = File(result.files.single.path!);
|
||||
final int size = file.lengthSync();
|
||||
final String savePath = file.path;
|
||||
|
|
@ -413,6 +440,7 @@ class _TIMUIKitTextFieldLayoutWideState
|
|||
convID: convID,
|
||||
convType: convType),
|
||||
context);
|
||||
}
|
||||
} else {
|
||||
throw TypeError();
|
||||
}
|
||||
|
|
@ -424,10 +452,11 @@ class _TIMUIKitTextFieldLayoutWideState
|
|||
|
||||
List<Widget> generateBarIcons(
|
||||
List<DesktopControlBarItem> items, TUITheme theme) {
|
||||
final defaultItems = defaultControlBarItems.map((e) => e.item);
|
||||
return items.map((e) {
|
||||
final GlobalKey key = GlobalKey();
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(right: 6),
|
||||
margin: const EdgeInsets.only(right: 10),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
final alignBox =
|
||||
|
|
@ -445,20 +474,39 @@ class _TIMUIKitTextFieldLayoutWideState
|
|||
textStyle: TextStyle(fontSize: 12, color: theme.white),
|
||||
message: e.showName,
|
||||
child: Container(
|
||||
decoration:
|
||||
BoxDecoration(borderRadius: BorderRadius.circular(2)),
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(2)),
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: e.imgPath != null
|
||||
? Image.asset(
|
||||
e.imgPath!,
|
||||
child: () {
|
||||
if (TencentUtils.checkString(e.svgPath) != null) {
|
||||
return SvgPicture.asset(
|
||||
e.svgPath!,
|
||||
package: defaultItems.contains(e.item)
|
||||
? 'tencent_cloud_chat_uikit'
|
||||
: null,
|
||||
key: key,
|
||||
width: e.size ?? 20,
|
||||
height: e.size ?? 20,
|
||||
)
|
||||
: Icon(e.icon,
|
||||
width: e.size ?? 16,
|
||||
height: e.size ?? 16,
|
||||
);
|
||||
}
|
||||
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)),
|
||||
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 ??
|
||||
(widget.conversationType == ConvType.group ? TIM_t("群聊") : TIM_t("对方"));
|
||||
final size = await ScreenshotHelper.getImageSize(file);
|
||||
final size = fileSize ?? await ScreenshotHelper.getImageSize(filePath);
|
||||
|
||||
TUIKitWidePopup.showPopupWindow(
|
||||
operationKey: TUIKitWideModalOperationKey.beforeSendScreenShot,
|
||||
|
|
@ -679,10 +728,17 @@ class _TIMUIKitTextFieldLayoutWideState
|
|||
height: min(360, size.height / 2),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
launchUrl(Uri.file(file));
|
||||
launchUrl(PlatformUtils().isWeb
|
||||
? Uri.parse(filePath)
|
||||
: Uri.file(filePath));
|
||||
},
|
||||
child: Image.file(
|
||||
File(file),
|
||||
child: PlatformUtils().isWeb
|
||||
? Image.network(
|
||||
filePath,
|
||||
height: min(360, size.height / 2),
|
||||
)
|
||||
: Image.file(
|
||||
File(filePath),
|
||||
height: min(360, size.height / 2),
|
||||
),
|
||||
),
|
||||
|
|
@ -703,7 +759,8 @@ class _TIMUIKitTextFieldLayoutWideState
|
|||
onPressed: () {
|
||||
MessageUtils.handleMessageError(
|
||||
widget.model.sendImageMessage(
|
||||
imagePath: file,
|
||||
imagePath: filePath,
|
||||
imageName: fileName,
|
||||
convID: widget.conversationID,
|
||||
convType: widget.conversationType),
|
||||
context);
|
||||
|
|
@ -720,12 +777,11 @@ class _TIMUIKitTextFieldLayoutWideState
|
|||
_sendScreenShot() async {
|
||||
final file = await ScreenshotHelper.captureScreen();
|
||||
if (file != null) {
|
||||
_sendImageWithConfirmation(file);
|
||||
_sendImageWithConfirmation(filePath: file);
|
||||
} else {}
|
||||
}
|
||||
|
||||
List<Widget> generateControlBar(
|
||||
TUIChatSeparateViewModel model, TUITheme theme) {
|
||||
generateDefaultControlBarItems() {
|
||||
final DesktopControlBarConfig config =
|
||||
widget.model.chatConfig.desktopControlBarConfig ??
|
||||
DesktopControlBarConfig();
|
||||
|
|
@ -735,9 +791,9 @@ class _TIMUIKitTextFieldLayoutWideState
|
|||
item: "face",
|
||||
showName: TIM_t("表情"),
|
||||
onClick: (offset) {
|
||||
_sendEmoji(offset, theme);
|
||||
_sendEmoji(offset, widget.theme);
|
||||
},
|
||||
icon: Icons.mood),
|
||||
svgPath: "images/svg/send_face.svg"),
|
||||
if (config.showScreenshotButton && PlatformUtils().isDesktop)
|
||||
DesktopControlBarItem(
|
||||
item: "screenShot",
|
||||
|
|
@ -745,15 +801,15 @@ class _TIMUIKitTextFieldLayoutWideState
|
|||
onClick: (offset) {
|
||||
_sendScreenShot();
|
||||
},
|
||||
icon: Icons.cut_outlined),
|
||||
svgPath: "images/svg/send_screenshot.svg"),
|
||||
if (config.showSendFileButton)
|
||||
DesktopControlBarItem(
|
||||
item: "file",
|
||||
showName: TIM_t("文件"),
|
||||
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)
|
||||
DesktopControlBarItem(
|
||||
item: "photo",
|
||||
|
|
@ -762,10 +818,10 @@ class _TIMUIKitTextFieldLayoutWideState
|
|||
if (PlatformUtils().isWeb) {
|
||||
_sendImageFileOnWeb(widget.model);
|
||||
} 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)
|
||||
DesktopControlBarItem(
|
||||
item: "video",
|
||||
|
|
@ -774,10 +830,10 @@ class _TIMUIKitTextFieldLayoutWideState
|
|||
if (PlatformUtils().isWeb) {
|
||||
_sendVideoFileOnWeb(widget.model);
|
||||
} 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)
|
||||
DesktopControlBarItem(
|
||||
item: "history",
|
||||
|
|
@ -801,19 +857,41 @@ class _TIMUIKitTextFieldLayoutWideState
|
|||
onTapConversation: (V2TimConversation conversation,
|
||||
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 ?? [])
|
||||
];
|
||||
|
||||
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 {
|
||||
if ((event.isKeyPressed(LogicalKeyboardKey.controlLeft) &&
|
||||
if (PlatformUtils().isDesktop &&
|
||||
((event.isKeyPressed(LogicalKeyboardKey.controlLeft) &&
|
||||
event.logicalKey == LogicalKeyboardKey.keyV) ||
|
||||
(event.isMetaPressed && event.logicalKey == LogicalKeyboardKey.keyV)) {
|
||||
(event.isMetaPressed &&
|
||||
event.logicalKey == LogicalKeyboardKey.keyV))) {
|
||||
final bytes = await Pasteboard.image;
|
||||
if (bytes != null) {
|
||||
String directory;
|
||||
|
|
@ -838,7 +916,7 @@ class _TIMUIKitTextFieldLayoutWideState
|
|||
await scDirectory.create(recursive: true);
|
||||
}
|
||||
await file.writeAsBytes(bytes.toList());
|
||||
_sendImageWithConfirmation(filePath);
|
||||
_sendImageWithConfirmation(filePath: filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -857,10 +935,6 @@ class _TIMUIKitTextFieldLayoutWideState
|
|||
}
|
||||
widget.handleAtText(value);
|
||||
widget.handleSendEditStatus(value, true);
|
||||
final isEmpty = value.isEmpty;
|
||||
if (isEmpty) {
|
||||
widget.handleSoftKeyBoardDelete();
|
||||
}
|
||||
}, const Duration(milliseconds: 80));
|
||||
|
||||
final MediaQueryData data = MediaQuery.of(context);
|
||||
|
|
@ -873,7 +947,7 @@ class _TIMUIKitTextFieldLayoutWideState
|
|||
focusNode: textFocusNode,
|
||||
onKey: _handleKeyEvent,
|
||||
child: Container(
|
||||
color: widget.backgroundColor,
|
||||
color: widget.backgroundColor ?? theme.desktopChatMessageInputBgColor,
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
|
|
@ -899,7 +973,8 @@ class _TIMUIKitTextFieldLayoutWideState
|
|||
Expanded(
|
||||
child: Container(
|
||||
height: 35,
|
||||
color: theme.weakBackgroundColor,
|
||||
color: widget.backgroundColor ??
|
||||
theme.desktopChatMessageInputBgColor,
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
TIM_t(widget.forbiddenText!),
|
||||
|
|
@ -913,20 +988,18 @@ class _TIMUIKitTextFieldLayoutWideState
|
|||
)),
|
||||
if (widget.forbiddenText == null)
|
||||
Expanded(
|
||||
child: Scrollbar(
|
||||
controller: _scrollController,
|
||||
child: ExtendedTextField(
|
||||
scrollController: _scrollController,
|
||||
autofocus: true,
|
||||
maxLines: 6,
|
||||
minLines: 6,
|
||||
maxLines: widget
|
||||
.model.chatConfig.desktopMessageInputFieldLines,
|
||||
minLines: widget
|
||||
.model.chatConfig.desktopMessageInputFieldLines,
|
||||
focusNode: widget.focusNode,
|
||||
onChanged: debounceFunc,
|
||||
keyboardType: TextInputType.multiline,
|
||||
textInputAction: PlatformUtils().isAndroid
|
||||
? TextInputAction.newline
|
||||
: TextInputAction.send,
|
||||
onEditingComplete: () {
|
||||
widget.onSubmitted();
|
||||
// // widget.onSubmitted();
|
||||
},
|
||||
textAlignVertical: TextAlignVertical.top,
|
||||
style: const TextStyle(fontSize: 14),
|
||||
|
|
@ -936,7 +1009,9 @@ class _TIMUIKitTextFieldLayoutWideState
|
|||
hintStyle: const TextStyle(
|
||||
color: Color(0xffAEA4A3),
|
||||
),
|
||||
fillColor: hexToColor("fafafa"),
|
||||
fillColor: widget.backgroundColor ??
|
||||
theme.desktopChatMessageInputBgColor ??
|
||||
hexToColor("fafafa"),
|
||||
filled: true,
|
||||
isDense: true,
|
||||
hintText: widget.hintText ?? '',
|
||||
|
|
@ -945,14 +1020,12 @@ class _TIMUIKitTextFieldLayoutWideState
|
|||
specialTextSpanBuilder: PlatformUtils().isWeb
|
||||
? null
|
||||
: DefaultSpecialTextSpanBuilder(
|
||||
isUseDefaultEmoji:
|
||||
widget.isUseDefaultEmoji,
|
||||
isUseDefaultEmoji: widget.isUseDefaultEmoji,
|
||||
customEmojiStickerList:
|
||||
widget.customEmojiStickerList,
|
||||
showAtBackground: true,
|
||||
)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -59,6 +59,10 @@ class TIMUIKitChat extends StatefulWidget {
|
|||
/// Avatar and name in message reaction tap callback.
|
||||
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(
|
||||
"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,7 +149,8 @@ class TIMUIKitChat extends StatefulWidget {
|
|||
this.conversationShowName,
|
||||
this.abstractMessageBuilder,
|
||||
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(
|
||||
"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.messageItemBuilder,
|
||||
|
|
@ -170,7 +175,8 @@ class TIMUIKitChat extends StatefulWidget {
|
|||
this.topFixWidget = const SizedBox(),
|
||||
this.textFieldBuilder,
|
||||
this.customEmojiStickerList = const [],
|
||||
this.customAppBar})
|
||||
this.customAppBar,
|
||||
this.onSecondaryTapAvatar})
|
||||
: super(key: key) {
|
||||
startTime = DateTime.now().millisecondsSinceEpoch;
|
||||
}
|
||||
|
|
@ -239,7 +245,9 @@ class _TUIChatState extends TIMUIKitState<TIMUIKitChat> {
|
|||
model = TUIChatSeparateViewModel();
|
||||
model.abstractMessageBuilder = widget.abstractMessageBuilder;
|
||||
model.onTapAvatar = widget.onTapAvatar;
|
||||
textFieldController.requestFocus();
|
||||
Future.delayed(const Duration(milliseconds: 50), () {
|
||||
textFieldController.requestFocus();
|
||||
try {
|
||||
autoController.jumpTo(
|
||||
autoController.position.minScrollExtent,
|
||||
|
|
@ -317,6 +325,7 @@ class _TUIChatState extends TIMUIKitState<TIMUIKitChat> {
|
|||
return TIMUIKitChatProviderScope(
|
||||
model: model,
|
||||
groupID: widget.groupID,
|
||||
scrollController: autoController,
|
||||
textFieldController: textFieldController,
|
||||
conversationID: _getConvID(),
|
||||
conversationType: _getConvType(),
|
||||
|
|
@ -331,6 +340,8 @@ class _TUIChatState extends TIMUIKitState<TIMUIKitChat> {
|
|||
Provider.of<TUIChatGlobalModel>(context, listen: true);
|
||||
|
||||
widget.controller?.model = model;
|
||||
widget.controller?.textFieldController = textFieldController;
|
||||
widget.controller?.scrollController = autoController;
|
||||
List<V2TimGroupApplication> filteredApplicationList = [];
|
||||
if (widget.conversationType == ConvType.group &&
|
||||
widget.onDealWithGroupApplication != null) {
|
||||
|
|
@ -431,11 +442,8 @@ class _TUIChatState extends TIMUIKitState<TIMUIKitChat> {
|
|||
tongueItemBuilder: widget.tongueItemBuilder,
|
||||
onLongPressForOthersHeadPortrait:
|
||||
(String? userId, String? nickName) {
|
||||
if (widget.conversationType !=
|
||||
ConvType.c2c) {
|
||||
textFieldController.longPressToAt(
|
||||
nickName, userId);
|
||||
}
|
||||
},
|
||||
mainHistoryListConfig:
|
||||
widget.mainHistoryListConfig,
|
||||
|
|
@ -445,6 +453,8 @@ class _TUIChatState extends TIMUIKitState<TIMUIKitChat> {
|
|||
widget.exteraTipsActionItemBuilder,
|
||||
conversationType: _getConvType(),
|
||||
scrollController: autoController,
|
||||
onSecondaryTapAvatar:
|
||||
widget.onSecondaryTapAvatar,
|
||||
onTapAvatar: widget.onTapAvatar,
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
showNickName: widget.showNickName,
|
||||
|
|
@ -463,7 +473,6 @@ class _TUIChatState extends TIMUIKitState<TIMUIKitChat> {
|
|||
: (widget.textFieldBuilder != null
|
||||
? widget.textFieldBuilder!(context)
|
||||
: TIMUIKitInputTextField(
|
||||
key: inputTextFieldState,
|
||||
atMemberPanelScroll:
|
||||
atMemberPanelScroll,
|
||||
groupType:
|
||||
|
|
@ -483,7 +492,8 @@ class _TUIChatState extends TIMUIKitState<TIMUIKitChat> {
|
|||
scrollController: autoController,
|
||||
conversationID: _getConvID(),
|
||||
conversationType: _getConvType(),
|
||||
initText: widget.draftText,
|
||||
initText: widget.draftText ??
|
||||
widget.conversation.draftText,
|
||||
hintText: widget.textFieldHintText,
|
||||
showMorePanel: widget.config
|
||||
?.isAllowShowMorePanel ??
|
||||
|
|
@ -508,9 +518,8 @@ class _TUIChatState extends TIMUIKitState<TIMUIKitChat> {
|
|||
),
|
||||
AtMemberPanel(
|
||||
atMemberPanelScroll: atMemberPanelScroll,
|
||||
onSelectMember: (member) => inputTextFieldState
|
||||
.currentState
|
||||
?.handleAtMember(memberInfo: member),
|
||||
onSelectMember: (member) =>
|
||||
textFieldController.handleAtMember(member),
|
||||
)
|
||||
],
|
||||
),
|
||||
|
|
@ -555,6 +564,8 @@ class TIMUIKitChatProviderScope extends StatelessWidget {
|
|||
|
||||
final bool? isBuild;
|
||||
|
||||
final AutoScrollController? scrollController;
|
||||
|
||||
TIMUIKitChatProviderScope(
|
||||
{Key? key,
|
||||
this.child,
|
||||
|
|
@ -568,13 +579,16 @@ class TIMUIKitChatProviderScope extends StatelessWidget {
|
|||
required this.conversationType,
|
||||
this.controller,
|
||||
this.config,
|
||||
this.lifeCycle})
|
||||
this.lifeCycle,
|
||||
this.scrollController})
|
||||
: super(key: key) {
|
||||
if (isBuild ?? false) {
|
||||
return;
|
||||
}
|
||||
model ??= TUIChatSeparateViewModel();
|
||||
controller?.model = model;
|
||||
controller?.textFieldController = textFieldController;
|
||||
controller?.scrollController = scrollController;
|
||||
if (config != null) {
|
||||
model?.chatConfig = config!;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ class TIMUIKitChatConfig {
|
|||
/// Whether to use the default emoji
|
||||
final bool isUseDefaultEmoji;
|
||||
|
||||
/// Is show avatar on history message list.
|
||||
/// Whether shows avatar on history message list.
|
||||
/// [Default]: true.
|
||||
final bool isShowAvatar;
|
||||
|
||||
|
|
@ -157,13 +157,13 @@ class TIMUIKitChatConfig {
|
|||
final List<MessageHoverControlItem>? additionalDesktopMessageHoverBarItem;
|
||||
|
||||
/// 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.
|
||||
final List<DesktopControlBarItem>? additionalDesktopControlBarItems;
|
||||
|
||||
/// This configuration is used for the control bar
|
||||
/// 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;
|
||||
|
||||
/// Controls whether users are allowed to mention another user in the group by long-pressing on their avatar.
|
||||
|
|
@ -178,6 +178,13 @@ class TIMUIKitChatConfig {
|
|||
/// [Default]: true on Desktop while false on Mobile.
|
||||
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(
|
||||
{this.onTapLink,
|
||||
this.timeDividerConfig,
|
||||
|
|
@ -185,8 +192,10 @@ class TIMUIKitChatConfig {
|
|||
this.faceURIPrefix,
|
||||
this.faceURISuffix,
|
||||
this.textHeight = 1.3,
|
||||
this.desktopMessageInputFieldLines = 6,
|
||||
this.isAtWhenReply = true,
|
||||
this.notificationAndroidSound = "",
|
||||
this.isUseMessageHoverBarOnDesktop = true,
|
||||
this.isSupportMarkdownForTextMessage = false,
|
||||
this.notificationExt,
|
||||
this.isUseMessageReaction = true,
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ class MultiSelectPanel extends TIMUIKitStatelessWidget {
|
|||
Provider.of<TUIChatSeparateViewModel>(context);
|
||||
|
||||
return TUIKitScreenUtils.getDeviceWidget(
|
||||
context: context,
|
||||
desktopWidget: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: theme.selectPanelBgColor ?? theme.primaryColor,
|
||||
|
|
|
|||
|
|
@ -64,8 +64,6 @@ class TIMUIKitConversation extends StatefulWidget {
|
|||
final bool isShowOnlineStatus;
|
||||
|
||||
/// 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;
|
||||
|
||||
const TIMUIKitConversation(
|
||||
|
|
@ -375,7 +373,7 @@ class _TIMUIKitConversationState extends TIMUIKitState<TIMUIKitConversation> {
|
|||
: isPined
|
||||
? theme.conversationItemPinedBgColor
|
||||
: theme.conversationItemBgColor,
|
||||
child: InkWell(
|
||||
child: GestureDetector(
|
||||
child: TIMUIKitConversationItem(
|
||||
isCurrent: isCurrent,
|
||||
isShowDraft: widget.isShowDraft,
|
||||
|
|
@ -402,11 +400,12 @@ class _TIMUIKitConversationState extends TIMUIKitState<TIMUIKitConversation> {
|
|||
}
|
||||
|
||||
return TUIKitScreenUtils.getDeviceWidget(
|
||||
context: context,
|
||||
desktopWidget: AutoScrollTag(
|
||||
key: ValueKey(conversationItem.conversationID),
|
||||
controller: _autoScrollController,
|
||||
index: index,
|
||||
child: GestureDetector(
|
||||
child: InkWell(
|
||||
onSecondaryTapDown: (details) {
|
||||
TUIKitWidePopup.showPopupWindow(
|
||||
operationKey: TUIKitWideModalOperationKey
|
||||
|
|
@ -450,6 +449,7 @@ class _TIMUIKitConversationState extends TIMUIKitState<TIMUIKitConversation> {
|
|||
}
|
||||
|
||||
return TUIKitScreenUtils.getDeviceWidget(
|
||||
context: context,
|
||||
defaultWidget: SlidableAutoCloseBehavior(
|
||||
child: EasyRefresh(
|
||||
header: CustomizeBallPulseHeader(color: theme.primaryColor),
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ class _AddGroupMemberPageState extends TIMUIKitState<AddGroupMemberPage> {
|
|||
final TUITheme theme = value.theme;
|
||||
|
||||
return TUIKitScreenUtils.getDeviceWidget(
|
||||
context: context,
|
||||
desktopWidget: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: ContactList(
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ class _DeleteGroupMemberPageState extends TIMUIKitState<DeleteGroupMemberPage> {
|
|||
final TUITheme theme = value.theme;
|
||||
|
||||
return TUIKitScreenUtils.getDeviceWidget(
|
||||
context: context,
|
||||
desktopWidget: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: GroupProfileMemberList(
|
||||
|
|
|
|||
|
|
@ -376,7 +376,7 @@ class _GroupProfileGroupManagePageState
|
|||
size: 16),
|
||||
onClick: () {
|
||||
widget.model.muteGroupMember(
|
||||
e?.userID ?? "",
|
||||
e.userID,
|
||||
false,
|
||||
serverTime);
|
||||
onClose();
|
||||
|
|
@ -409,6 +409,7 @@ class _GroupProfileGroupManagePageState
|
|||
}
|
||||
|
||||
return TUIKitScreenUtils.getDeviceWidget(
|
||||
context: context,
|
||||
desktopWidget: managePage(),
|
||||
defaultWidget: Scaffold(
|
||||
appBar: AppBar(
|
||||
|
|
@ -501,6 +502,7 @@ Widget _buildListItem(BuildContext context, V2TimGroupMemberFullInfo memberInfo,
|
|||
}
|
||||
|
||||
return TUIKitScreenUtils.getDeviceWidget(
|
||||
context: context,
|
||||
desktopWidget: nameItem(),
|
||||
defaultWidget: SingleChildScrollView(
|
||||
child: Slidable(endActionPane: endActionPane, child: nameItem())));
|
||||
|
|
@ -724,7 +726,7 @@ class _GroupProfileSetManagerPageState
|
|||
Icons.remove_circle_outline,
|
||||
size: 16),
|
||||
onClick: () {
|
||||
_removeAdmin(context, e!);
|
||||
_removeAdmin(context, e);
|
||||
onClose();
|
||||
}),
|
||||
]));
|
||||
|
|
@ -758,6 +760,7 @@ class _GroupProfileSetManagerPageState
|
|||
}
|
||||
|
||||
return TUIKitScreenUtils.getDeviceWidget(
|
||||
context: context,
|
||||
desktopWidget: adminPage(),
|
||||
defaultWidget: Scaffold(
|
||||
appBar: AppBar(
|
||||
|
|
@ -876,6 +879,7 @@ class _GroupProfileAddAdminState extends TIMUIKitState<GroupProfileAddAdmin> {
|
|||
}
|
||||
|
||||
return TUIKitScreenUtils.getDeviceWidget(
|
||||
context: context,
|
||||
desktopWidget: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: addAdminPage(),
|
||||
|
|
|
|||
|
|
@ -76,11 +76,11 @@ class TIMUIKitProfile extends StatefulWidget {
|
|||
/// The life cycle hooks for user profile business logic
|
||||
final ProfileLifeCycle? lifeCycle;
|
||||
|
||||
/// If the loading user is self.
|
||||
/// Whether the specify user is current logged in user.
|
||||
/// Default: [false].
|
||||
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;
|
||||
|
||||
const TIMUIKitProfile(
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ class TIMUIKitProfileUserInfoCard extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TUIKitScreenUtils.getDeviceWidget(
|
||||
context: context,
|
||||
defaultWidget: TIMUIKitProfileUserInfoCardNarrow(
|
||||
userInfo: userInfo,
|
||||
isJumpToPersonalProfile: isJumpToPersonalProfile,
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ class TIMUIKitSearchItem extends TIMUIKitStatelessWidget {
|
|||
final TUITheme theme = value.theme;
|
||||
|
||||
return TUIKitScreenUtils.getDeviceWidget(
|
||||
context: context,
|
||||
defaultWidget: GestureDetector(
|
||||
onTap: onClick,
|
||||
child: Container(
|
||||
|
|
|
|||
|
|
@ -10,13 +10,16 @@ class LinkPreviewEntry {
|
|||
/// get the text message with hyperlinks
|
||||
static LinkPreviewText? getHyperlinksText(String messageText, bool isMarkdown,
|
||||
{Function(String)? onLinkTap,
|
||||
bool? isEnableTextSelection,
|
||||
bool isEnableTextSelection = false,
|
||||
bool isUseDefaultEmoji = false,
|
||||
List customEmojiStickerList = const []}) {
|
||||
return ({TextStyle? style}) {
|
||||
return isMarkdown
|
||||
? LinkTextMarkdown(
|
||||
messageText: messageText, style: style, onLinkTap: onLinkTap)
|
||||
isEnableTextSelection: isEnableTextSelection,
|
||||
messageText: replaceSingleNewlineWithTwo(messageText),
|
||||
style: style,
|
||||
onLinkTap: onLinkTap)
|
||||
: LinkText(
|
||||
isEnableTextSelection: isEnableTextSelection,
|
||||
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.
|
||||
/// 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(
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// ignore_for_file: deprecated_member_use
|
||||
|
||||
import 'package:tencent_cloud_chat_uikit/ui/utils/platform.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/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||
|
|
@ -20,14 +19,21 @@ class LinkTextMarkdown extends TIMStatelessWidget {
|
|||
/// text style for default words
|
||||
final TextStyle? style;
|
||||
|
||||
final bool? isEnableTextSelection;
|
||||
|
||||
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);
|
||||
|
||||
@override
|
||||
Widget timBuild(BuildContext context) {
|
||||
return MarkdownBody(
|
||||
data: messageText,
|
||||
selectable: isEnableTextSelection ?? false,
|
||||
styleSheet: MarkdownStyleSheet.fromTheme(ThemeData(
|
||||
textTheme: TextTheme(
|
||||
bodyText2: style ?? const TextStyle(fontSize: 16.0))))
|
||||
|
|
@ -40,9 +46,9 @@ class LinkTextMarkdown extends TIMStatelessWidget {
|
|||
String title,
|
||||
) {
|
||||
if (onLinkTap != null) {
|
||||
onLinkTap!(link);
|
||||
onLinkTap!(href ?? "");
|
||||
} else {
|
||||
LinkUtils.launchURL(context, link);
|
||||
LinkUtils.launchURL(context, href ?? "");
|
||||
}
|
||||
},
|
||||
);
|
||||
|
|
@ -130,14 +136,7 @@ class LinkText extends TIMStatelessWidget {
|
|||
|
||||
@override
|
||||
Widget timBuild(BuildContext context) {
|
||||
final isDesktopScreen =
|
||||
TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop;
|
||||
return
|
||||
// Text.rich(
|
||||
// TextSpan(children: [..._getContentSpan(messageText, context)]),
|
||||
// style: style ?? const TextStyle(fontSize: 16.0),
|
||||
// );
|
||||
ExtendedText(_getContentSpan(messageText, context), softWrap: true,
|
||||
return ExtendedText(_getContentSpan(messageText, context), softWrap: true,
|
||||
onSpecialTextTap: (dynamic parameter) {
|
||||
if (parameter.toString().startsWith('\$')) {
|
||||
if (onLinkTap != null) {
|
||||
|
|
@ -148,9 +147,6 @@ class LinkText extends TIMStatelessWidget {
|
|||
}
|
||||
}
|
||||
},
|
||||
selectionEnabled: isEnableTextSelection != null
|
||||
? isEnableTextSelection!
|
||||
: isDesktopScreen,
|
||||
style: style ?? const TextStyle(fontSize: 16.0),
|
||||
specialTextSpanBuilder: DefaultSpecialTextSpanBuilder(
|
||||
isUseDefaultEmoji: isUseDefaultEmoji,
|
||||
|
|
|
|||
|
|
@ -305,6 +305,7 @@ class MergerMessageScreenState extends TIMUIKitState<MergerMessageScreen> {
|
|||
}
|
||||
|
||||
return TUIKitScreenUtils.getDeviceWidget(
|
||||
context: context,
|
||||
desktopWidget: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: messageListPage(),
|
||||
|
|
|
|||
|
|
@ -362,6 +362,7 @@ class _MessageReadReceiptState extends TIMUIKitState<MessageReadReceipt> {
|
|||
}
|
||||
|
||||
return TUIKitScreenUtils.getDeviceWidget(
|
||||
context: context,
|
||||
desktopWidget: pageBody(),
|
||||
defaultWidget: DefaultTabController(
|
||||
length: 2,
|
||||
|
|
|
|||
|
|
@ -227,7 +227,7 @@ class TextInputBottomSheet {
|
|||
),
|
||||
));
|
||||
});
|
||||
Overlay.of(context)?.insert(entry!);
|
||||
Overlay.of(context).insert(entry!);
|
||||
} else {
|
||||
showModalBottomSheet(
|
||||
isScrollControlled: true, // !important
|
||||
|
|
|
|||
|
|
@ -116,6 +116,7 @@ class _SelectNewGroupOwner extends TIMUIKitState<SelectNewGroupOwner> {
|
|||
}
|
||||
|
||||
return TUIKitScreenUtils.getDeviceWidget(
|
||||
context: context,
|
||||
defaultWidget: Scaffold(
|
||||
appBar: AppBar(
|
||||
shadowColor: theme.weakBackgroundColor,
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:chewie/chewie.dart';
|
||||
import 'package:chewie/src/helpers/utils.dart';
|
||||
import 'package:chewie/src/animated_play_pause.dart';
|
||||
import 'package:chewie/src/material/material_progress_bar.dart';
|
||||
import 'package:chewie_for_us/chewie_for_us.dart';
|
||||
import 'package:chewie_for_us/src/helpers/utils.dart';
|
||||
import 'package:chewie_for_us/src/animated_play_pause.dart';
|
||||
import 'package:chewie_for_us/src/material/material_progress_bar.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:loading_animation_widget/loading_animation_widget.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart';
|
||||
|
|
|
|||
|
|
@ -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/data_services/services_locatar.dart';
|
||||
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/ui/utils/permission.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/ui/utils/platform.dart';
|
||||
|
|
@ -89,7 +89,7 @@ class _VideoScreenState extends TIMUIKitState<VideoScreen> {
|
|||
} else {
|
||||
final DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
|
||||
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
|
||||
if ((androidInfo.version.sdkInt ?? 0) >= 33) {
|
||||
if ((androidInfo.version.sdkInt) >= 33) {
|
||||
final videos = await Permissions.checkPermission(
|
||||
context,Permission.videos.value,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -88,7 +88,9 @@ class TUIKitWidePopup {
|
|||
return;
|
||||
}
|
||||
entry = OverlayEntry(builder: (BuildContext context) {
|
||||
return TUIKitDragArea(
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: TUIKitDragArea(
|
||||
backgroundColor: isDarkBackground ? const Color(0x7F000000) : null,
|
||||
closeFun: () {
|
||||
if (entry != null) {
|
||||
|
|
@ -221,9 +223,10 @@ class TUIKitWidePopup {
|
|||
)
|
||||
],
|
||||
),
|
||||
));
|
||||
)),
|
||||
);
|
||||
});
|
||||
Overlay.of(context)?.insert(entry!);
|
||||
Overlay.of(context).insert(entry!);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
632
pubspec.lock
29
pubspec.yaml
|
|
@ -1,6 +1,6 @@
|
|||
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.
|
||||
version: 2.0.0
|
||||
version: 2.1.0
|
||||
homepage: https://www.tencentcloud.com/products/im?from=pub
|
||||
repository: https://github.com/TencentCloud/tc-chat-uikit-flutter
|
||||
documentation: https://comm.qq.com/im/doc/flutter/en/TUIKit/readme.html
|
||||
|
|
@ -14,23 +14,23 @@ platforms:
|
|||
windows:
|
||||
|
||||
environment:
|
||||
sdk: ">=2.17.0 <3.0.0"
|
||||
flutter: ">=3.0.0"
|
||||
sdk: '>=3.0.0 <4.0.0'
|
||||
flutter: ">=3.10.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
adaptive_action_sheet: ^2.0.1
|
||||
provider: ^6.0.1
|
||||
intl: ^0.17.0
|
||||
intl: ^0.18.0
|
||||
get_it: ^7.2.0
|
||||
dotted_border: ^2.0.0+2
|
||||
flutter_svg: ^1.0.0
|
||||
image_picker: ^0.8.5+3
|
||||
file_picker: ^5.2.9
|
||||
file_picker: ^5.3.0
|
||||
tencent_super_tooltip: ^0.0.1
|
||||
video_player: ^2.4.2
|
||||
chewie: ^1.3.2
|
||||
chewie_for_us: ^1.5.0
|
||||
flutter_slidable_for_tencent_im: ^1.4.0
|
||||
flutter_plugin_record_plus: ^0.0.15
|
||||
azlistview_all_platforms: ^2.1.2
|
||||
|
|
@ -41,13 +41,13 @@ dependencies:
|
|||
cached_network_image: ^3.2.0
|
||||
shared_preferences: ^2.0.13
|
||||
scroll_to_index: ^2.1.1
|
||||
wechat_assets_picker: ^7.2.0
|
||||
tencent_wechat_camera_picker: ^3.6.5
|
||||
wechat_assets_picker: ^8.5.0
|
||||
wechat_camera_picker: ^3.8.0
|
||||
flutter_easyrefresh: ^2.2.1
|
||||
extended_image: ^6.0.2+1
|
||||
tencent_extended_text_field: ^1.0.0
|
||||
tencent_extended_text: ^1.0.0
|
||||
package_info_plus: ^1.4.0
|
||||
extended_image: ^8.0.1
|
||||
extended_text_field: ^12.0.0
|
||||
extended_text: ^11.0.0
|
||||
package_info_plus: ^4.0.1
|
||||
loading_animation_widget: ^1.1.0+3
|
||||
permission_handler: ^10.2.0
|
||||
tuple: ^2.0.0
|
||||
|
|
@ -64,14 +64,14 @@ dependencies:
|
|||
tencent_open_file: ^4.0.10
|
||||
tencent_keyboard_visibility: ^1.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
|
||||
audioplayers: ^3.0.1
|
||||
path: ^1.8.1
|
||||
tencent_cloud_uikit_core: ^1.0.2
|
||||
pasteboard: ^0.2.0
|
||||
desktop_drop: ^0.4.1
|
||||
device_info_plus: ^4.1.3
|
||||
device_info_plus: ^9.0.1
|
||||
cross_file: ^0.3.3+4
|
||||
diff_match_patch: ^0.4.1
|
||||
|
||||
|
|
@ -89,6 +89,7 @@ flutter:
|
|||
uses-material-design: true
|
||||
assets:
|
||||
- images/
|
||||
- images/svg/
|
||||
# - assets/custom_face_resource/4349/
|
||||
|
||||
# To add assets to your package, add an assets section, like this:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
void main() {
|
||||
|
||||
}
|
||||