Flutter TUIKit V2.1.0

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

View File

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

View File

@ -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.
@ -512,4 +499,4 @@ tend to learn more about the use cases.
- WhatsApp Group: <https://chat.whatsapp.com/Gfbxk7rQBqc8Rz4pzzP27A>
- QQ Group: 788910197, chat in Chinese
Our Website: <https://www.tencentcloud.com/products/im?from=pub>
Our Website: <https://www.tencentcloud.com/products/im?from=pub>

View File

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

View File

@ -1,9 +1,5 @@
// ignore_for_file: avoid_print
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;

View File

@ -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"))
}

View File

@ -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"

View File

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

After

Width:  |  Height:  |  Size: 2.2 KiB

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

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

After

Width:  |  Height:  |  Size: 593 B

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

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

After

Width:  |  Height:  |  Size: 499 B

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

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

After

Width:  |  Height:  |  Size: 710 B

View File

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

After

Width:  |  Height:  |  Size: 636 B

View File

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

After

Width:  |  Height:  |  Size: 950 B

View File

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

After

Width:  |  Height:  |  Size: 460 B

View File

@ -7,6 +7,7 @@ import 'package:flutter/cupertino.dart';
// ignore: unnecessary_import
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,27 +599,32 @@ 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 &&
convType == ConvType.group &&
((chatConfig.groupReadReceiptPermissionList != null &&
chatConfig.groupReadReceiptPermissionList!
.contains(_groupType)) ||
(chatConfig.groupReadReceiptPermisionList != null &&
chatConfig.groupReadReceiptPermisionList!
.contains(oldGroupType))),
needReadReceipt: needReadReceipt ??
chatConfig.isShowGroupReadingStatus &&
convType == ConvType.group &&
((chatConfig.groupReadReceiptPermissionList != null &&
chatConfig.groupReadReceiptPermissionList!
.contains(_groupType)) ||
(chatConfig.groupReadReceiptPermisionList != null &&
chatConfig.groupReadReceiptPermisionList!
.contains(oldGroupType))),
groupID: groupID,
offlinePushInfo: offlinePushInfo,
onlineUserOnly: onlineUserOnly ?? false,
cloudCustomData: showC2cMessageEditStatus == true
? json.encode({
"messageFeature": {
"needTyping": 1,
"version": 1,
}
})
: "",
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,

View File

@ -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,23 +885,28 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
final sendMsgRes = await _messageService.sendMessage(
id: id,
receiver: receiver,
needReadReceipt: chatConfig.isShowGroupReadingStatus &&
convType == ConvType.group &&
((chatConfig.groupReadReceiptPermissionList != null &&
chatConfig.groupReadReceiptPermissionList!
.contains(groupType)) ||
(chatConfig.groupReadReceiptPermisionList != null &&
chatConfig.groupReadReceiptPermisionList!
.contains(oldGroupType))),
needReadReceipt: needReadReceipt ??
chatConfig.isShowGroupReadingStatus &&
convType == ConvType.group &&
((chatConfig.groupReadReceiptPermissionList != null &&
chatConfig.groupReadReceiptPermissionList!
.contains(groupType)) ||
(chatConfig.groupReadReceiptPermisionList != null &&
chatConfig.groupReadReceiptPermisionList!
.contains(oldGroupType))),
groupID: groupID,
priority: priority,
localCustomData: localCustomData,
isExcludedFromUnreadCount: isExcludedFromUnreadCount ?? false,
offlinePushInfo: offlinePushInfo,
onlineUserOnly: onlineUserOnly ?? false,
cloudCustomData: json.encode({
"messageFeature": {
"needTyping": 1,
"version": 1,
}
}));
cloudCustomData: cloudCustomData ??
json.encode({
"messageFeature": {
"needTyping": 1,
"version": 1,
}
}));
if (isEditStatusMessage == false) {
updateMessage(sendMsgRes, convID, id, convType, groupType, setInputField);
}
@ -904,7 +918,7 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
{bool needResetNewMessageCount = true}) {
_messageListMap[conversationID] = messageList;
if (needResetNewMessageCount) {
_recivedNewMessageCount = 0;
_receivedNewMessageCount = 0;
}
notifyListeners();
}
@ -961,18 +975,21 @@ 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();
notifyListeners();
if (convID == currentSelectedConv) {
notifyListeners();
}
}
List<V2TimMessage>? getMessageList(String conversationID) {

View File

@ -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;

View File

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

View File

@ -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] ?? "";
}
}

View File

@ -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 = "",

File diff suppressed because one or more lines are too long

View File

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

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,7 @@
// ignore_for_file: avoid_print
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.
/// IDChat页面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();
}
}

View File

@ -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,

View File

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

View File

@ -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,29 +15,48 @@ 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(context != null){
double deviceWidth = MediaQuery.of(context).size.width;
double deviceHeight = MediaQuery.of(context).size.height;
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;
if (deviceWidth > FormFactor.desktop || deviceWidth > deviceHeight * 1.1) {
deviceType = DeviceType.Desktop;
} else if (deviceWidth > FormFactor.handset) {
deviceType = DeviceType.Mobile;
}
final diagonalInInches =
sqrt(pow(screenWidth, 2) + pow(screenHeight, 2)) / 96.0;
deviceType = diagonalInInches < 8.0 ? DeviceType.Mobile : DeviceType.Desktop;
return deviceType ?? DeviceType.Mobile;
}else{
return DeviceType.Mobile;
if(context != null){
double deviceWidth = MediaQuery.of(context).size.width;
double deviceHeight = MediaQuery.of(context).size.height;
if (deviceWidth > FormFactor.desktop || deviceWidth > deviceHeight * 1.1) {
deviceType = DeviceType.Desktop;
} else if (deviceWidth > FormFactor.handset) {
deviceType = DeviceType.Mobile;
}
return deviceType ?? DeviceType.Mobile;
}else{
return DeviceType.Mobile;
}
}
}
static Widget getDeviceWidget({
/// 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;
}

View File

@ -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(

View File

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

View File

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

View File

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

View File

@ -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,8 +186,9 @@ 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.")
this.additionalItemBuilder});
@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});
}
class TIMUIKitHistoryMessageListItem extends StatefulWidget {
@ -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,8 +284,9 @@ 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")
this.showNickName = false,
@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,
this.onTapForOthersPortrait,
@ -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,192 +410,206 @@ class _TIMUIKItHistoryMessageListItemState
switch (msgType) {
case MessageElemType.V2TIM_ELEM_TYPE_CUSTOM:
if (messageItemBuilder?.customMessageItemBuilder != null) {
return messageItemBuilder!.customMessageItemBuilder!(
messageItem,
isShowJump,
() => model.jumpMsgID = "",
)!;
}
return TIMUIKitCustomElem(
message: messageItem,
customElem: messageItem.customElem,
isFromSelf: isFromSelf,
messageBackgroundColor: widget.themeData?.messageBackgroundColor,
messageBorderRadius: widget.themeData?.messageBorderRadius,
messageFontStyle: widget.themeData?.messageTextStyle,
textPadding: widget.textPadding,
isShowMessageReaction: widget.isUseMessageReaction,
);
final customWidget =
messageItemBuilder?.customMessageItemBuilder != null
? messageItemBuilder!.customMessageItemBuilder!(
messageItem,
isShowJump,
() => model.jumpMsgID = "",
)
: null;
return customWidget ??
TIMUIKitCustomElem(
message: messageItem,
customElem: messageItem.customElem,
isFromSelf: isFromSelf,
messageBackgroundColor: widget.themeData?.messageBackgroundColor,
messageBorderRadius: widget.themeData?.messageBorderRadius,
messageFontStyle: widget.themeData?.messageTextStyle,
textPadding: widget.textPadding,
isShowMessageReaction: widget.isUseMessageReaction,
);
case MessageElemType.V2TIM_ELEM_TYPE_SOUND:
if (messageItemBuilder?.soundMessageItemBuilder != null) {
return messageItemBuilder!.soundMessageItemBuilder!(
messageItem,
isShowJump,
clearJump,
)!;
}
return TIMUIKitSoundElem(
chatModel: model,
message: messageItem,
soundElem: messageItem.soundElem!,
msgID: messageItem.msgID ?? "",
isFromSelf: messageItem.isSelf ?? true,
clearJump: clearJump,
isShowJump: isShowJump,
localCustomInt: messageItem.localCustomInt,
borderRadius: widget.themeData?.messageBorderRadius,
fontStyle: widget.themeData?.messageTextStyle,
backgroundColor: widget.themeData?.messageBackgroundColor,
textPadding: widget.textPadding,
isShowMessageReaction: widget.isUseMessageReaction,
);
final customWidget = messageItemBuilder?.soundMessageItemBuilder != null
? messageItemBuilder!.soundMessageItemBuilder!(
messageItem,
isShowJump,
() => model.jumpMsgID = "",
)
: null;
return customWidget ??
TIMUIKitSoundElem(
chatModel: model,
message: messageItem,
soundElem: messageItem.soundElem!,
msgID: messageItem.msgID ?? "",
isFromSelf: messageItem.isSelf ?? true,
clearJump: clearJump,
isShowJump: isShowJump,
localCustomInt: messageItem.localCustomInt,
borderRadius: widget.themeData?.messageBorderRadius,
fontStyle: widget.themeData?.messageTextStyle,
backgroundColor: widget.themeData?.messageBackgroundColor,
textPadding: widget.textPadding,
isShowMessageReaction: widget.isUseMessageReaction,
);
case MessageElemType.V2TIM_ELEM_TYPE_TEXT:
if (isReplyMessage(messageItem)) {
if (messageItemBuilder?.textReplyMessageItemBuilder != null) {
return messageItemBuilder!.textReplyMessageItemBuilder!(
messageItem,
isShowJump,
clearJump,
)!;
}
return TIMUIKitReplyElem(
message: messageItem,
clearJump: clearJump,
isShowJump: isShowJump,
scrollToIndex: widget.onScrollToIndex ?? () {},
borderRadius: widget.themeData?.messageBorderRadius,
fontStyle: widget.themeData?.messageTextStyle,
backgroundColor: widget.themeData?.messageBackgroundColor,
textPadding: widget.textPadding,
isUseDefaultEmoji: widget.isUseDefaultEmoji,
customEmojiStickerList: widget.customEmojiStickerList,
chatModel: model,
isShowMessageReaction: widget.isUseMessageReaction,
);
final customWidget =
messageItemBuilder?.textReplyMessageItemBuilder != null
? messageItemBuilder!.textReplyMessageItemBuilder!(
messageItem,
isShowJump,
() => model.jumpMsgID = "",
)
: null;
return customWidget ??
TIMUIKitReplyElem(
message: messageItem,
clearJump: clearJump,
isShowJump: isShowJump,
scrollToIndex: widget.onScrollToIndex ?? () {},
borderRadius: widget.themeData?.messageBorderRadius,
fontStyle: widget.themeData?.messageTextStyle,
backgroundColor: widget.themeData?.messageBackgroundColor,
textPadding: widget.textPadding,
isUseDefaultEmoji: widget.isUseDefaultEmoji,
customEmojiStickerList: widget.customEmojiStickerList,
chatModel: model,
isShowMessageReaction: widget.isUseMessageReaction,
);
}
if (messageItemBuilder?.textMessageItemBuilder != null) {
return messageItemBuilder!.textMessageItemBuilder!(
messageItem,
isShowJump,
clearJump,
)!;
}
return TIMUIKitTextElem(
chatModel: model,
message: messageItem,
isFromSelf: messageItem.isSelf ?? true,
clearJump: clearJump,
isShowJump: isShowJump,
borderRadius: widget.themeData?.messageBorderRadius,
fontStyle: widget.themeData?.messageTextStyle,
backgroundColor: widget.themeData?.messageBackgroundColor,
textPadding: widget.textPadding,
isShowMessageReaction: widget.isUseMessageReaction,
isUseDefaultEmoji: widget.isUseDefaultEmoji,
customEmojiStickerList: widget.customEmojiStickerList,
);
final customWidget = messageItemBuilder?.textMessageItemBuilder != null
? messageItemBuilder!.textMessageItemBuilder!(
messageItem,
isShowJump,
() => model.jumpMsgID = "",
)
: null;
return customWidget ??
TIMUIKitTextElem(
chatModel: model,
message: messageItem,
isFromSelf: messageItem.isSelf ?? true,
clearJump: clearJump,
isShowJump: isShowJump,
borderRadius: widget.themeData?.messageBorderRadius,
fontStyle: widget.themeData?.messageTextStyle,
backgroundColor: widget.themeData?.messageBackgroundColor,
textPadding: widget.textPadding,
isShowMessageReaction: widget.isUseMessageReaction,
isUseDefaultEmoji: widget.isUseDefaultEmoji,
customEmojiStickerList: widget.customEmojiStickerList,
);
case MessageElemType.V2TIM_ELEM_TYPE_FACE:
if (messageItemBuilder?.faceMessageItemBuilder != null) {
return messageItemBuilder!.faceMessageItemBuilder!(
messageItem,
isShowJump,
clearJump,
)!;
}
return TIMUIKitFaceElem(
model: model,
path: messageItem.faceElem!.data ?? "",
clearJump: clearJump,
isShowJump: isShowJump,
message: messageItem,
isShowMessageReaction: widget.isUseMessageReaction,
);
final customWidget = messageItemBuilder?.faceMessageItemBuilder != null
? messageItemBuilder!.faceMessageItemBuilder!(
messageItem,
isShowJump,
() => model.jumpMsgID = "",
)
: null;
return customWidget ??
TIMUIKitFaceElem(
model: model,
path: messageItem.faceElem!.data ?? "",
clearJump: clearJump,
isShowJump: isShowJump,
message: messageItem,
isShowMessageReaction: widget.isUseMessageReaction,
);
case MessageElemType.V2TIM_ELEM_TYPE_FILE:
if (messageItemBuilder?.fileMessageItemBuilder != null) {
return messageItemBuilder!.fileMessageItemBuilder!(
messageItem,
isShowJump,
clearJump,
)!;
}
return TIMUIKitFileElem(
chatModel: model,
message: messageItem,
messageID: messageItem.msgID,
fileElem: messageItem.fileElem,
isSelf: messageItem.isSelf ?? true,
clearJump: clearJump,
isShowJump: isShowJump,
isShowMessageReaction: widget.isUseMessageReaction,
);
final customWidget = messageItemBuilder?.fileMessageItemBuilder != null
? messageItemBuilder!.fileMessageItemBuilder!(
messageItem,
isShowJump,
() => model.jumpMsgID = "",
)
: null;
return customWidget ??
TIMUIKitFileElem(
chatModel: model,
message: messageItem,
messageID: messageItem.msgID,
fileElem: messageItem.fileElem,
isSelf: messageItem.isSelf ?? true,
clearJump: clearJump,
isShowJump: isShowJump,
isShowMessageReaction: widget.isUseMessageReaction,
);
case MessageElemType.V2TIM_ELEM_TYPE_GROUP_TIPS:
if (messageItemBuilder?.groupTipsMessageItemBuilder != null) {
return messageItemBuilder!.groupTipsMessageItemBuilder!(
messageItem,
isShowJump,
clearJump,
)!;
}
return Text(TIM_t("[群系统消息]"));
final customWidget =
messageItemBuilder?.groupTipsMessageItemBuilder != null
? messageItemBuilder!.groupTipsMessageItemBuilder!(
messageItem,
isShowJump,
() => model.jumpMsgID = "",
)
: null;
return customWidget ?? Text(TIM_t("[群系统消息]"));
case MessageElemType.V2TIM_ELEM_TYPE_IMAGE:
if (messageItemBuilder?.imageMessageItemBuilder != null) {
return messageItemBuilder!.imageMessageItemBuilder!(
messageItem,
isShowJump,
clearJump,
)!;
}
return TIMUIKitImageElem(
clearJump: clearJump,
isShowJump: isShowJump,
chatModel: model,
message: messageItem,
isShowMessageReaction: widget.isUseMessageReaction,
key: Key("${messageItem.seq}_${messageItem.timestamp}"),
);
final customWidget = messageItemBuilder?.imageMessageItemBuilder != null
? messageItemBuilder!.imageMessageItemBuilder!(
messageItem,
isShowJump,
() => model.jumpMsgID = "",
)
: null;
return customWidget ??
TIMUIKitImageElem(
clearJump: clearJump,
isShowJump: isShowJump,
chatModel: model,
message: messageItem,
isShowMessageReaction: widget.isUseMessageReaction,
key: Key("${messageItem.seq}_${messageItem.timestamp}"),
);
case MessageElemType.V2TIM_ELEM_TYPE_VIDEO:
if (messageItemBuilder?.videoMessageItemBuilder != null) {
return messageItemBuilder!.videoMessageItemBuilder!(
messageItem,
isShowJump,
clearJump,
)!;
}
return TIMUIKitVideoElem(
messageItem,
isShowJump: isShowJump,
chatModel: model,
clearJump: clearJump,
isShowMessageReaction: widget.isUseMessageReaction,
);
final customWidget = messageItemBuilder?.videoMessageItemBuilder != null
? messageItemBuilder!.videoMessageItemBuilder!(
messageItem,
isShowJump,
() => model.jumpMsgID = "",
)
: null;
return customWidget ??
TIMUIKitVideoElem(
messageItem,
isShowJump: isShowJump,
chatModel: model,
clearJump: clearJump,
isShowMessageReaction: widget.isUseMessageReaction,
);
case MessageElemType.V2TIM_ELEM_TYPE_LOCATION:
if (messageItemBuilder?.locationMessageItemBuilder != null) {
return messageItemBuilder!.locationMessageItemBuilder!(
messageItem,
isShowJump,
clearJump,
)!;
}
return Text(TIM_t("[位置]"));
final customWidget =
messageItemBuilder?.locationMessageItemBuilder != null
? messageItemBuilder!.locationMessageItemBuilder!(
messageItem,
isShowJump,
() => model.jumpMsgID = "",
)
: null;
return customWidget ?? Text(TIM_t("[位置]"));
case MessageElemType.V2TIM_ELEM_TYPE_MERGER:
if (messageItemBuilder?.mergerMessageItemBuilder != null) {
return messageItemBuilder!.mergerMessageItemBuilder!(
messageItem,
isShowJump,
clearJump,
)!;
}
return TIMUIKitMergerElem(
messageItemBuilder: messageItemBuilder,
model: model,
isShowJump: isShowJump,
clearJump: clearJump,
message: messageItem,
isShowMessageReaction: widget.isUseMessageReaction,
mergerElem: messageItem.mergerElem!,
messageID: messageItem.msgID ?? "",
isSelf: messageItem.isSelf ?? true);
final customWidget =
messageItemBuilder?.mergerMessageItemBuilder != null
? messageItemBuilder!.mergerMessageItemBuilder!(
messageItem,
isShowJump,
() => model.jumpMsgID = "",
)
: null;
return customWidget ??
TIMUIKitMergerElem(
messageItemBuilder: messageItemBuilder,
model: model,
isShowJump: isShowJump,
clearJump: clearJump,
message: messageItem,
isShowMessageReaction: widget.isUseMessageReaction,
mergerElem: messageItem.mergerElem!,
messageID: messageItem.msgID ?? "",
isSelf: messageItem.isSelf ?? true);
default:
return Text(TIM_t("[未知消息]"));
}
@ -941,12 +979,18 @@ class _TIMUIKItHistoryMessageListItemState
),
onClick: (_) {
model.repliedMessage = widget.message;
if (widget.allowAtUserWhenReply &&
widget.onLongPressForOthersHeadPortrait != null &&
!(widget.message.isSelf ?? true)) {
widget.onLongPressForOthersHeadPortrait!(
widget.message.sender, widget.message.nickName);
}
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!(
!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(

View File

@ -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,26 +97,44 @@ class TIMUIKitMessageTooltipState
return;
}
if (PlatformUtils().isDesktop) {
if (globalModal.getMessageProgress(widget.message.msgID) == 100) {
String savePath =
TencentUtils.checkString(widget.message.fileElem!.localUrl) ??
globalModal.getFileMessageLocation(widget.message.msgID);
if (widget.message.fileElem != null) {
if (globalModal.getMessageProgress(widget.message.msgID) == 100) {
String savePath =
TencentUtils.checkString(widget.message.fileElem!.localUrl) ??
globalModal.getFileMessageLocation(widget.message.msgID);
File f = File(savePath);
if (f.existsSync() && widget.message.msgID != null) {
filePath = savePath;
isShowOpenFile = true;
return;
}
isShowOpenFile = false;
return;
}
String savePath = widget.message.fileElem!.localUrl ?? '';
File f = File(savePath);
if (f.existsSync() && widget.message.msgID != null) {
filePath = savePath;
globalModal.setMessageProgress(widget.message.msgID!, 100);
isShowOpenFile = true;
return;
}
} else if (widget.message.imageElem != null) {
if (TencentUtils.checkString(
widget.message.imageElem!.imageList![0]!.localUrl) !=
null &&
File(widget.message.imageElem!.imageList![0]!.localUrl!)
.existsSync()) {
isShowOpenFile = true;
return;
}
} else if (widget.message.videoElem != null) {
if (TencentUtils.checkString(widget.message.videoElem!.localVideoUrl) !=
null &&
File(widget.message.videoElem!.localVideoUrl!).existsSync()) {
isShowOpenFile = true;
return;
}
isShowOpenFile = false;
return;
}
String savePath = widget.message.fileElem!.localUrl ?? '';
File f = File(savePath);
if (f.existsSync() && widget.message.msgID != null) {
filePath = savePath;
globalModal.setMessageProgress(widget.message.msgID!, 100);
isShowOpenFile = true;
return;
}
}
isShowOpenFile = false;
@ -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)) {
widget.onLongPressForOthersHeadPortrait!(
widget.message.sender, widget.message.nickName);
}
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!(
!isAtWhenReply ? null : widget.message.sender,
!isAtWhenReply ? null : widget.message.nickName);
break;
default:
onTIMCallback(TIMCallback(

View File

@ -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,8 +90,9 @@ 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")
this.showNickName = true,
@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,
this.toolTipsConfig,
@ -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:

View File

@ -80,9 +80,8 @@ class _TIMUIKitAppBarState extends TIMUIKitState<TIMUIKitAppBar> {
changedInfo.userProfile?.userID) ??
"";
}
// ignore: empty_catches
} catch (e) {
}
// ignore: empty_catches
} catch (e) {}
},
);
if (_friendshipListener != null) {
@ -103,9 +102,8 @@ class _TIMUIKitAppBarState extends TIMUIKitState<TIMUIKitAppBar> {
setState(() {});
}
}
// ignore: empty_catches
} catch (e) {
}
// ignore: empty_catches
} catch (e) {}
},
);
if (_groupListener != null) {
@ -161,9 +159,8 @@ class _TIMUIKitAppBarState extends TIMUIKitState<TIMUIKitAppBar> {
"";
});
}
// ignore: empty_catches
} catch (e) {
}
// ignore: empty_catches
} 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,12 +218,10 @@ class _TIMUIKitAppBarState extends TIMUIKitState<TIMUIKitAppBar> {
},
child: Text(
TIM_t('取消'),
style: setAppbar?.textTheme?.titleMedium ??
TextStyle(
color:
theme.appbarTextColor ?? hexToColor("010000"),
fontSize: 16,
),
style: TextStyle(
color: theme.appbarTextColor ?? hexToColor("010000"),
fontSize: 16,
),
),
)
: setAppbar?.leading ??
@ -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 {

View File

@ -8,18 +8,18 @@ import 'package:flutter/material.dart';
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
import 'package:tencent_cloud_chat_uikit/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) ??
model.getFileMessageLocation(widget.messageID);
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;
}
@ -108,7 +152,7 @@ class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
}
addUrlToWaitingPath() async {
if(widget.messageID !=null ){
if (widget.messageID != null) {
model.addWaitingList(widget.messageID!);
print("add path success");
}
@ -127,7 +171,7 @@ class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
}
downloadFile(TUITheme theme) async {
if(PlatformUtils().isMobile){
if (PlatformUtils().isMobile) {
if (PlatformUtils().isIOS) {
if (!await Permissions.checkPermission(
context, Permission.photosAddOnly.value, theme, false)) {
@ -136,12 +180,13 @@ 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){
if (!storage) {
return;
}
}
@ -151,7 +196,7 @@ class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
}
tryOpenFile(context, theme) async {
if(PlatformUtils().isMobile){
if (PlatformUtils().isMobile) {
if (PlatformUtils().isIOS) {
if (!await Permissions.checkPermission(
context, Permission.photosAddOnly.value, theme!, false)) {
@ -160,12 +205,13 @@ 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){
if (!storage) {
return;
}
}
@ -173,19 +219,67 @@ class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
}
try {
if(PlatformUtils().isDesktop && !PlatformUtils().isWindows){
if (PlatformUtils().isDesktop && !PlatformUtils().isWindows) {
launchUrl(Uri.file(filePath));
}else{
} else {
OpenFile.open(filePath);
}
// ignore: empty_catches
// ignore: empty_catches
} catch (e) {}
}
void downloadWebFile(String fileUrl) async {
String fileName = Uri.parse(fileUrl).pathSegments.last;
try {
http.Response response = await http.get(
Uri.parse(fileUrl),
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
);
final html.AnchorElement downloadAnchor =
html.document.createElement('a') as html.AnchorElement;
final html.Blob blob = html.Blob([response.bodyBytes]);
downloadAnchor.href = html.Url.createObjectUrlFromBlob(blob);
downloadAnchor.download = widget.message.fileElem?.fileName ?? fileName;
downloadAnchor.click();
} catch (e) {
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;
final received = downloadProgress;
final fileName = widget.fileElem!.fileName ?? "";
final fileSize = widget.fileElem!.fileSize;
final borderRadius = widget.isSelf
? const BorderRadius.only(
topLeft: Radius.circular(10),
topRight: Radius.circular(2),
bottomLeft: Radius.circular(10),
bottomRight: Radius.circular(10))
: const BorderRadius.only(
topLeft: Radius.circular(2),
topRight: Radius.circular(10),
bottomLeft: Radius.circular(10),
bottomRight: Radius.circular(10));
String? fileFormat;
if (widget.fileElem?.fileName != null &&
widget.fileElem!.fileName!.isNotEmpty) {
final String fileName = widget.fileElem!.fileName!;
fileFormat = fileName.split(".")[max(fileName.split(".").length - 1, 0)];
}
return TIMUIKitMessageReactionWrapper(
chatModel: widget.chatModel,
isShowJump: widget.isShowJump,
@ -193,135 +287,104 @@ class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
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 fileName = widget.fileElem!.fileName ?? "";
final fileSize = widget.fileElem!.fileSize;
final borderRadius = widget.isSelf
? const BorderRadius.only(
topLeft: Radius.circular(10),
topRight: Radius.circular(2),
bottomLeft: Radius.circular(10),
bottomRight: Radius.circular(10))
: const BorderRadius.only(
topLeft: Radius.circular(2),
topRight: Radius.circular(10),
bottomLeft: Radius.circular(10),
bottomRight: Radius.circular(10));
String? fileFormat;
if (widget.fileElem?.fileName != null &&
widget.fileElem!.fileName!.isNotEmpty) {
final String fileName = widget.fileElem!.fileName!;
fileFormat =
fileName.split(".")[max(fileName.split(".").length - 1, 0)];
child: GestureDetector(
onTap: () async {
if (PlatformUtils().isWeb) {
downloadWebFile(widget.fileElem?.path ?? "");
return;
}
if (await hasFile()) {
if (received == 100) {
tryOpenFile(context, theme);
} else {
//
onTIMCallback(
TIMCallback(
type: TIMCallbackType.INFO,
infoRecommendText: TIM_t("正在下载中"),
infoCode: 6660411,
),
);
}
return;
}
return InkWell(
onTap: () async {
if (PlatformUtils().isWeb) {
launchUrl(
Uri.parse(widget.fileElem?.path ?? ""),
mode: LaunchMode.externalApplication,
);
return;
}
if (await hasFile()) {
if (received == 100) {
tryOpenFile(context, theme);
} else {
//
onTIMCallback(
TIMCallback(
type: TIMCallbackType.INFO,
infoRecommendText: TIM_t("正在下载中"),
infoCode: 6660411,
),
);
}
return;
}
if (checkIsWaiting()) {
onTIMCallback(
TIMCallback(
type: TIMCallbackType.INFO,
infoRecommendText: TIM_t("已加入待下载队列,其他文件下载中"),
infoCode: 6660413),
);
return;
} else {
await addUrlToWaitingPath();
}
await downloadFile(theme);
},
child: Container(
width: 237,
decoration: BoxDecoration(
border: Border.all(
color: theme.weakDividerColor ??
CommonColor.weakDividerColor,
),
borderRadius: borderRadius),
child: Stack(children: [
ClipRRect(
//
borderRadius: borderRadius,
child: LinearProgressIndicator(
minHeight: 66,
value: (received == 100 ? 0 : received) / 100,
backgroundColor: received == 100
? theme.weakBackgroundColor
: Colors.white,
valueColor: AlwaysStoppedAnimation(
theme.lightPrimaryMaterialColor.shade50)),
),
Padding(
padding: const EdgeInsets.symmetric(
vertical: 8, horizontal: 12),
child: Row(
mainAxisAlignment: widget.isSelf
? MainAxisAlignment.end
: MainAxisAlignment.start,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
constraints:
const BoxConstraints(maxWidth: 160),
child: LayoutBuilder(
builder:
(buildContext, boxConstraints) {
return CustomText(
fileName,
width: boxConstraints.maxWidth,
style: TextStyle(
color: theme.darkTextColor,
fontSize: 16,
),
);
},
if (checkIsWaiting()) {
onTIMCallback(
TIMCallback(
type: TIMCallbackType.INFO,
infoRecommendText: TIM_t("已加入待下载队列,其他文件下载中"),
infoCode: 6660413),
);
return;
} else {
await addUrlToWaitingPath();
}
await downloadFile(theme);
},
child: Container(
width: 237,
decoration: BoxDecoration(
border: Border.all(
color:
theme.weakDividerColor ?? CommonColor.weakDividerColor,
),
borderRadius: borderRadius),
child: Stack(children: [
ClipRRect(
//
borderRadius: borderRadius,
child: LinearProgressIndicator(
minHeight: 66,
value: (received == 100 ? 0 : received) / 100,
backgroundColor: received == 100
? theme.weakBackgroundColor
: Colors.white,
valueColor: AlwaysStoppedAnimation(
theme.lightPrimaryMaterialColor.shade50)),
),
Padding(
padding:
const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
child: Row(
mainAxisAlignment: widget.isSelf
? MainAxisAlignment.end
: MainAxisAlignment.start,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
constraints:
const BoxConstraints(maxWidth: 160),
child: LayoutBuilder(
builder: (buildContext, boxConstraints) {
return CustomText(
fileName,
width: boxConstraints.maxWidth,
style: TextStyle(
color: theme.darkTextColor,
fontSize: 16,
),
),
if (fileSize != null)
Text(
showFileSize(fileSize),
// "${received > 0 ? (received / 1024).ceil() : (received / 1024).ceil()} KB",
style: TextStyle(
fontSize: 14,
color: theme.weakTextColor),
)
],
)),
TIMUIKitFileIcon(
fileFormat: fileFormat,
);
},
),
])),
]),
));
})));
),
if (fileSize != null)
Text(
showFileSize(fileSize),
// "${received > 0 ? (received / 1024).ceil() : (received / 1024).ceil()} KB",
style: TextStyle(
fontSize: 14, color: theme.weakTextColor),
)
],
)),
TIMUIKitFileIcon(
fileFormat: fileFormat,
),
])),
]),
)));
}
}

View File

@ -56,6 +56,7 @@ class TIMUIKitImageElem extends StatefulWidget {
}
class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
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) {
onTIMCallback(TIMCallback(
infoCode: 6660414,
infoRecommendText: TIM_t("正在下载中"),
type: TIMCallbackType.INFO
));
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));
}
} 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);

View File

@ -6,7 +6,7 @@ import 'package:flutter/material.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/common_utils.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/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(

View File

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

View File

@ -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),

View File

@ -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(

View File

@ -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,33 +172,40 @@ 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;
if (localVideoUrl != null) {
if(PlatformUtils().isWindows){
if (PlatformUtils().isWindows) {
OpenFile.open(localVideoUrl);
} else{
} else {
launchUrl(Uri.file(localVideoUrl));
}
} else if (videoPath != null) {
if(PlatformUtils().isWindows){
if (PlatformUtils().isWindows) {
OpenFile.open(videoPath);
} else{
} else {
launchUrl(Uri.file(videoPath));
}
} else if (TencentUtils.isTextNotEmpty(videoUrl)) {
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,52 +250,49 @@ 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)
AspectRatio(
aspectRatio: positionRadio,
child: Container(
decoration: const BoxDecoration(
color: Colors.transparent),
),
child: Stack(
children: <Widget>[
if (positionRadio != null &&
(stateElement.snapshotUrl != null ||
stateElement.snapshotUrl != null))
AspectRatio(
aspectRatio: positionRadio,
child: Container(
decoration: const BoxDecoration(
color: Colors.transparent),
),
Row(
children: [
Expanded(
child: generateSnapshot(theme,
stateElement.snapshotHeight ?? 100))
],
),
if (widget.message.status !=
MessageStatus
.V2TIM_MSG_STATUS_SENDING &&
(stateElement.snapshotUrl != null ||
stateElement.snapshotPath != null) &&
stateElement.videoPath != null ||
stateElement.videoUrl != null)
Positioned.fill(
// alignment: Alignment.center,
child: Center(
child: Image.asset('images/play.png',
package: 'tencent_cloud_chat_uikit',
height: 64)),
),
Positioned(
right: 10,
bottom: 10,
child: Text(
MessageUtils.formatVideoTime(widget
.message.videoElem?.duration ??
0)
.toString(),
style: const TextStyle(
color: Colors.white, fontSize: 12))),
],
),
Row(
children: [
Expanded(
child: generateSnapshot(theme,
stateElement.snapshotHeight ?? 100))
],
),
if (widget.message.status !=
MessageStatus.V2TIM_MSG_STATUS_SENDING &&
(stateElement.snapshotUrl != null ||
stateElement.snapshotPath != null) &&
stateElement.videoPath != null ||
stateElement.videoUrl != null)
Positioned.fill(
// alignment: Alignment.center,
child: Center(
child: Image.asset('images/play.png',
package: 'tencent_cloud_chat_uikit',
height: 64)),
),
Positioned(
right: 10,
bottom: 10,
child: Text(
MessageUtils.formatVideoTime(
widget.message.videoElem?.duration ??
0)
.toString(),
style: const TextStyle(
color: Colors.white, fontSize: 12))),
],
));
}),
))),

View File

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

View File

@ -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.

View File

@ -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';

View File

@ -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

View File

@ -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,

View File

@ -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(

View File

@ -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,

View File

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

View File

@ -38,8 +38,6 @@ typedef CustomStickerPanel = Widget Function({
double? height,
});
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(
userID: userID ?? "",
nickName: nickName,
);
final showName = _getShowName(memberInfo);
memberInfoMap["@$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));
lastText = text;
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);
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));
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,103 +584,125 @@ 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();
}
}
KeyEventResult handleDesktopKeyEvent(FocusNode node, RawKeyEvent event) {
final activeIndex = widget.model.activeAtIndex;
final showMemberList = widget.model.showAtMemberList;
if (event.runtimeType == RawKeyDownEvent) {
if (event.physicalKey == PhysicalKeyboardKey.backspace) {
if (textEditingController.text.isEmpty && lastText.isEmpty) {
widget.model.repliedMessage = null;
return KeyEventResult.handled;
}
} else if ((event.isShiftPressed ||
event.isAltPressed ||
event.isControlPressed ||
event.isMetaPressed) &&
event.physicalKey == PhysicalKeyboardKey.enter) {
final offset = textEditingController.selection.baseOffset;
textEditingController.text =
'${lastText.substring(0, offset)}\n${lastText.substring(offset)}';
textEditingController.selection =
TextSelection.fromPosition(TextPosition(offset: offset + 1));
lastText = textEditingController.text;
return KeyEventResult.handled;
} else if (event.physicalKey == PhysicalKeyboardKey.enter) {
if (!_isComposingText) {
if (!isAddingAtSearchWords || widget.model.showAtMemberList.isEmpty) {
onSubmitted();
} else {
isAddingAtSearchWords = false;
final V2TimGroupMemberFullInfo? memberInfo =
showMemberList[activeIndex];
if (memberInfo != null) {
handleAtMember(
memberInfo: memberInfo, isAddToCursorPosition: true);
}
}
return KeyEventResult.handled;
}
}
if (event.isKeyPressed(LogicalKeyboardKey.arrowUp) &&
isAddingAtSearchWords &&
showMemberList.isNotEmpty) {
final newIndex = max(activeIndex - 1, 0);
widget.model.activeAtIndex = newIndex;
widget.atMemberPanelScroll?.scrollToIndex(newIndex,
preferPosition: AutoScrollPosition.middle);
return KeyEventResult.handled;
}
if (event.isKeyPressed(LogicalKeyboardKey.arrowDown) &&
isAddingAtSearchWords &&
showMemberList.isNotEmpty) {
final newIndex = min(activeIndex + 1, showMemberList.length - 1);
widget.model.activeAtIndex = newIndex;
widget.atMemberPanelScroll?.scrollToIndex(newIndex,
preferPosition: AutoScrollPosition.middle);
return KeyEventResult.handled;
}
}
return KeyEventResult.ignored;
}
@override
void initState() {
super.initState();
if (PlatformUtils().isWeb || PlatformUtils().isDesktop) {
focusNode = FocusNode(
onKey: (node, event) {
final activeIndex = widget.model.activeAtIndex;
final showMemberList = widget.model.showAtMemberList;
if (event.runtimeType == RawKeyDownEvent) {
if (event.physicalKey == PhysicalKeyboardKey.backspace) {
if (textEditingController.text.isEmpty && lastText.isEmpty) {
widget.model.repliedMessage = null;
return KeyEventResult.handled;
}
} else if ((event.isShiftPressed ||
event.isAltPressed ||
event.isControlPressed ||
event.isMetaPressed) &&
event.physicalKey == PhysicalKeyboardKey.enter) {
final offset = textEditingController.selection.baseOffset;
textEditingController.text =
'${lastText.substring(0, offset)}\n${lastText.substring(offset)}';
textEditingController.selection =
TextSelection.fromPosition(TextPosition(offset: offset + 1));
lastText = textEditingController.text;
return KeyEventResult.handled;
} else if (event.physicalKey == PhysicalKeyboardKey.enter) {
if (!isAddingAtSearchWords ||
widget.model.showAtMemberList.isEmpty) {
onSubmitted();
} else {
isAddingAtSearchWords = false;
final V2TimGroupMemberFullInfo? memberInfo =
showMemberList[activeIndex];
if (memberInfo != null) {
handleAtMember(
memberInfo: memberInfo, isAddToCursorPosition: true);
}
}
return KeyEventResult.handled;
}
if (event.isKeyPressed(LogicalKeyboardKey.arrowUp) &&
isAddingAtSearchWords &&
showMemberList.isNotEmpty) {
final newIndex = max(activeIndex - 1, 0);
widget.model.activeAtIndex = newIndex;
widget.atMemberPanelScroll?.scrollToIndex(newIndex,
preferPosition: AutoScrollPosition.middle);
return KeyEventResult.handled;
}
if (event.isKeyPressed(LogicalKeyboardKey.arrowDown) &&
isAddingAtSearchWords &&
showMemberList.isNotEmpty) {
final newIndex = min(activeIndex + 1, showMemberList.length - 1);
widget.model.activeAtIndex = newIndex;
widget.atMemberPanelScroll?.scrollToIndex(newIndex,
preferPosition: AutoScrollPosition.middle);
return KeyEventResult.handled;
}
}
return KeyEventResult.ignored;
},
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,

View File

@ -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();
}
}

View File

@ -17,7 +17,7 @@ import 'package:tencent_cloud_chat_uikit/ui/utils/platform.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitTextField/special_text/DefaultSpecialTextSpanBuilder.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitTextField/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();

View File

@ -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,20 +428,19 @@ 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;
MessageUtils.handleMessageError(
model.sendFileMessage(
filePath: savePath,
size: size,
convID: convID,
convType: convType),
context);
}
File file = File(result.files.single.path!);
final int size = file.lengthSync();
final String savePath = file.path;
MessageUtils.handleMessageError(
model.sendFileMessage(
filePath: savePath,
size: size,
convID: convID,
convType: convType),
context);
} else {
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)),
padding: const EdgeInsets.all(4),
child: e.imgPath != null
? Image.asset(
e.imgPath!,
key: key,
width: e.size ?? 20,
height: e.size ?? 20,
)
: Icon(e.icon,
key: key,
color: e.color ?? hexToColor("646a73"),
size: e.size ?? 20)),
decoration: BoxDecoration(borderRadius: BorderRadius.circular(2)),
padding: const EdgeInsets.all(4),
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 ?? 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,
);
}(),
),
),
),
);
@ -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,12 +728,19 @@ 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),
height: min(360, size.height / 2),
),
child: PlatformUtils().isWeb
? Image.network(
filePath,
height: min(360, size.height / 2),
)
: Image.file(
File(filePath),
height: min(360, size.height / 2),
),
),
),
Row(
@ -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) &&
event.logicalKey == LogicalKeyboardKey.keyV) ||
(event.isMetaPressed && event.logicalKey == LogicalKeyboardKey.keyV)) {
if (PlatformUtils().isDesktop &&
((event.isKeyPressed(LogicalKeyboardKey.controlLeft) &&
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,45 +988,43 @@ class _TIMUIKitTextFieldLayoutWideState
)),
if (widget.forbiddenText == null)
Expanded(
child: Scrollbar(
controller: _scrollController,
child: ExtendedTextField(
autofocus: true,
maxLines: 6,
minLines: 6,
focusNode: widget.focusNode,
onChanged: debounceFunc,
keyboardType: TextInputType.multiline,
textInputAction: PlatformUtils().isAndroid
? TextInputAction.newline
: TextInputAction.send,
onEditingComplete: () {
widget.onSubmitted();
},
textAlignVertical: TextAlignVertical.top,
style: const TextStyle(fontSize: 14),
decoration: InputDecoration(
hoverColor: hexToColor("fafafa"),
border: InputBorder.none,
hintStyle: const TextStyle(
color: Color(0xffAEA4A3),
),
fillColor: hexToColor("fafafa"),
filled: true,
isDense: true,
hintText: widget.hintText ?? '',
child: ExtendedTextField(
scrollController: _scrollController,
autofocus: true,
maxLines: widget
.model.chatConfig.desktopMessageInputFieldLines,
minLines: widget
.model.chatConfig.desktopMessageInputFieldLines,
focusNode: widget.focusNode,
onChanged: debounceFunc,
keyboardType: TextInputType.multiline,
onEditingComplete: () {
// // widget.onSubmitted();
},
textAlignVertical: TextAlignVertical.top,
style: const TextStyle(fontSize: 14),
decoration: InputDecoration(
hoverColor: hexToColor("fafafa"),
border: InputBorder.none,
hintStyle: const TextStyle(
color: Color(0xffAEA4A3),
),
controller: widget.textEditingController,
specialTextSpanBuilder: PlatformUtils().isWeb
? null
: DefaultSpecialTextSpanBuilder(
isUseDefaultEmoji:
widget.isUseDefaultEmoji,
customEmojiStickerList:
widget.customEmojiStickerList,
showAtBackground: true,
)),
),
fillColor: widget.backgroundColor ??
theme.desktopChatMessageInputBgColor ??
hexToColor("fafafa"),
filled: true,
isDense: true,
hintText: widget.hintText ?? '',
),
controller: widget.textEditingController,
specialTextSpanBuilder: PlatformUtils().isWeb
? null
: DefaultSpecialTextSpanBuilder(
isUseDefaultEmoji: widget.isUseDefaultEmoji,
customEmojiStickerList:
widget.customEmojiStickerList,
showAtBackground: true,
)),
),
],
),

View File

@ -59,6 +59,10 @@ class TIMUIKitChat extends StatefulWidget {
/// Avatar and name in message reaction tap callback.
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,12 +149,13 @@ 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")
this.showNickName = false,
@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,
@Deprecated("Please use [extraTipsActionItemBuilder] instead")
this.exteraTipsActionItemBuilder,
this.exteraTipsActionItemBuilder,
this.extraTipsActionItemBuilder,
this.draftText,
this.textFieldHintText,
@ -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);
}
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!;
}

View File

@ -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,15 +178,24 @@ 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,
this.isAutoReportRead = true,
this.isAutoReportRead = true,
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,
@ -212,7 +221,7 @@ class TIMUIKitChatConfig {
this.desktopControlBarConfig,
this.isAllowLongPressMessage = true,
this.isAllowClickAvatar = true,
this.isEnableTextSelection,
this.isEnableTextSelection,
this.additionalDesktopMessageHoverBarItem,
this.isShowGroupReadingStatus = true,
this.isReportGroupReadingStatus = true,

View File

@ -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,

View File

@ -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),

View File

@ -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(

View File

@ -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(

View File

@ -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(),

View File

@ -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(

View File

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

View File

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

View File

@ -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(

View File

@ -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,15 +136,8 @@ 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,
onSpecialTextTap: (dynamic parameter) {
return ExtendedText(_getContentSpan(messageText, context), softWrap: true,
onSpecialTextTap: (dynamic parameter) {
if (parameter.toString().startsWith('\$')) {
if (onLinkTap != null) {
onLinkTap!((parameter.toString()).replaceAll('\$', ''));
@ -148,14 +147,11 @@ class LinkText extends TIMStatelessWidget {
}
}
},
selectionEnabled: isEnableTextSelection != null
? isEnableTextSelection!
: isDesktopScreen,
style: style ?? const TextStyle(fontSize: 16.0),
specialTextSpanBuilder: DefaultSpecialTextSpanBuilder(
isUseDefaultEmoji: isUseDefaultEmoji,
customEmojiStickerList: customEmojiStickerList,
showAtBackground: true,
));
style: style ?? const TextStyle(fontSize: 16.0),
specialTextSpanBuilder: DefaultSpecialTextSpanBuilder(
isUseDefaultEmoji: isUseDefaultEmoji,
customEmojiStickerList: customEmojiStickerList,
showAtBackground: true,
));
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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';

View File

@ -12,7 +12,7 @@ import 'package:permission_handler/permission_handler.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_chat_global_model.dart';
import 'package:tencent_cloud_chat_uikit/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,
);

View File

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

0
pub
View File

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
name: tencent_cloud_chat_uikit
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:

View File

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