init
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
// ignore_for_file: camel_case_types
|
||||
|
||||
import 'package:flutter/cupertino.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_statelesswidget.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_conversation_view_model.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/ui/widgets/unread_message.dart';
|
||||
|
||||
typedef unreadCountBuilder = Widget Function(int unreadCount);
|
||||
|
||||
class TIMUIKitConversationTotalUnread extends TIMUIKitStatelessWidget {
|
||||
final TUIConversationViewModel model =
|
||||
serviceLocator<TUIConversationViewModel>();
|
||||
final int? unreadCount;
|
||||
final unreadCountBuilder? builder;
|
||||
final double? width;
|
||||
final double? height;
|
||||
|
||||
TIMUIKitConversationTotalUnread(
|
||||
{this.width = 22.0,
|
||||
this.height = 22.0,
|
||||
this.unreadCount,
|
||||
this.builder,
|
||||
Key? key})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider.value(value: model),
|
||||
],
|
||||
child: Consumer<TUIConversationViewModel>(
|
||||
builder: (context, value, child) {
|
||||
if (value.totalUnReadCount == 0) {
|
||||
return Container();
|
||||
}
|
||||
|
||||
if (builder != null) {
|
||||
return builder!(value.totalUnReadCount);
|
||||
}
|
||||
return UnreadMessage(
|
||||
unreadCount: unreadCount ?? value.totalUnReadCount,
|
||||
width: width,
|
||||
height: height);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,452 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_easyrefresh/easy_refresh.dart';
|
||||
import 'package:flutter_slidable_plus_plus/flutter_slidable_plus_plus.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:scroll_to_index/scroll_to_index.dart';
|
||||
import 'package:tencent_chat_i18n_tool/tencent_chat_i18n_tool.dart';
|
||||
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart'
|
||||
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_conversation.dart';
|
||||
import 'package:tencent_cloud_chat_sdk/models/v2_tim_user_status.dart'
|
||||
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_user_status.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_statelesswidget.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/business_logic/life_cycle/conversation_life_cycle.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_conversation_view_model.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_friendship_view_model.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/business_logic/listener_model/tui_group_listener_model.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/data_services/core/tim_uikit_wide_modal_operation_key.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/ui/controller/tim_uikit_conversation_controller.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/ui/utils/platform.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/ui/utils/screen_utils.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitConversation/tim_uikit_conversation_item.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/ui/widgets/customize_ball_pulse_header.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/ui/widgets/wide_popup.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_callback.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/theme/color.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/theme/tui_theme_view_model.dart';
|
||||
|
||||
typedef ConversationItemBuilder = Widget Function(V2TimConversation conversationItem, [V2TimUserStatus? onlineStatus]);
|
||||
|
||||
typedef ConversationItemSlideBuilder = List<ConversationItemSlidePanel> Function(V2TimConversation conversationItem);
|
||||
|
||||
typedef ConversationItemSecondaryMenuBuilder = Widget Function(
|
||||
V2TimConversation conversationItem, VoidCallback onClose);
|
||||
|
||||
class TIMUIKitConversation extends StatefulWidget {
|
||||
/// the callback after clicking conversation item
|
||||
final ValueChanged<V2TimConversation>? onTapItem;
|
||||
|
||||
/// conversation controller
|
||||
final TIMUIKitConversationController? controller;
|
||||
|
||||
/// the builder for conversation item
|
||||
final ConversationItemBuilder? itemBuilder;
|
||||
|
||||
/// the builder for Slidable item for each conversation item, shows on narrow screens.
|
||||
final ConversationItemSlideBuilder? itemSlideBuilder;
|
||||
|
||||
/// the widget of secondary tap menu for each conversation item, shows on wide screens.
|
||||
final ConversationItemSecondaryMenuBuilder? itemSecondaryMenuBuilder;
|
||||
|
||||
/// the widget shows when no conversation exists
|
||||
final Widget Function()? emptyBuilder;
|
||||
|
||||
/// the filter for conversation
|
||||
final bool Function(V2TimConversation? conversation)? conversationCollector;
|
||||
|
||||
/// the builder for the second line in each conservation item,
|
||||
/// usually shows the summary of the last message
|
||||
final LastMessageBuilder? lastMessageBuilder;
|
||||
|
||||
/// The life cycle hooks for `TIMUIKitConversation`
|
||||
final ConversationLifeCycle? lifeCycle;
|
||||
|
||||
/// Control if shows the online status for each user on its avatar.
|
||||
final bool isShowOnlineStatus;
|
||||
|
||||
/// Control if shows the identifier that the conversation has a draft text, inputted in previous.
|
||||
final bool isShowDraft;
|
||||
|
||||
const TIMUIKitConversation(
|
||||
{Key? key,
|
||||
this.lifeCycle,
|
||||
this.onTapItem,
|
||||
this.controller,
|
||||
this.itemSecondaryMenuBuilder,
|
||||
this.itemBuilder,
|
||||
this.isShowDraft = true,
|
||||
this.itemSlideBuilder,
|
||||
this.conversationCollector,
|
||||
this.emptyBuilder,
|
||||
this.lastMessageBuilder,
|
||||
this.isShowOnlineStatus = true})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return _TIMUIKitConversationState();
|
||||
}
|
||||
}
|
||||
|
||||
class ConversationItemSlidePanel extends TIMUIKitStatelessWidget {
|
||||
ConversationItemSlidePanel({
|
||||
Key? key,
|
||||
this.flex = 1,
|
||||
this.backgroundColor = Colors.white,
|
||||
this.foregroundColor,
|
||||
this.autoClose = true,
|
||||
required this.onPressed,
|
||||
this.icon,
|
||||
this.spacing = 4,
|
||||
this.label,
|
||||
}) : assert(flex > 0),
|
||||
assert(icon != null || label != null),
|
||||
super(key: key);
|
||||
|
||||
/// {@macro slidable.actions.flex}
|
||||
final int flex;
|
||||
|
||||
/// {@macro slidable.actions.backgroundColor}
|
||||
final Color backgroundColor;
|
||||
|
||||
/// {@macro slidable.actions.foregroundColor}
|
||||
final Color? foregroundColor;
|
||||
|
||||
/// {@macro slidable.actions.autoClose}
|
||||
final bool autoClose;
|
||||
|
||||
/// {@macro slidable.actions.onPressed}
|
||||
final SlidableActionCallback? onPressed;
|
||||
|
||||
/// An icon to display above the [label].
|
||||
final IconData? icon;
|
||||
|
||||
/// The space between [icon] and [label] if both set.
|
||||
///
|
||||
/// Defaults to 4.
|
||||
final double spacing;
|
||||
|
||||
/// A label to display below the [icon].
|
||||
final String? label;
|
||||
|
||||
@override
|
||||
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
|
||||
return SlidableAction(
|
||||
onPressed: onPressed,
|
||||
flex: flex,
|
||||
backgroundColor: backgroundColor,
|
||||
foregroundColor: foregroundColor,
|
||||
autoClose: autoClose,
|
||||
label: label,
|
||||
spacing: spacing,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _TIMUIKitConversationState extends TIMUIKitState<TIMUIKitConversation> {
|
||||
final TUIConversationViewModel model = serviceLocator<TUIConversationViewModel>();
|
||||
late TIMUIKitConversationController _timuiKitConversationController;
|
||||
final TUIThemeViewModel themeViewModel = serviceLocator<TUIThemeViewModel>();
|
||||
final TUIFriendShipViewModel friendShipViewModel = serviceLocator<TUIFriendShipViewModel>();
|
||||
final TUIGroupListenerModel groupListenerModel = serviceLocator<TUIGroupListenerModel>();
|
||||
late AutoScrollController _autoScrollController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final controller = getController();
|
||||
_timuiKitConversationController = controller;
|
||||
_timuiKitConversationController.model = model;
|
||||
_autoScrollController = AutoScrollController();
|
||||
}
|
||||
|
||||
TIMUIKitConversationController getController() {
|
||||
return widget.controller ?? TIMUIKitConversationController();
|
||||
}
|
||||
|
||||
void onTapConvItem(V2TimConversation conversation) {
|
||||
if (widget.onTapItem != null) {
|
||||
widget.onTapItem!(conversation);
|
||||
}
|
||||
model.setSelectedConversation(conversation);
|
||||
}
|
||||
|
||||
_clearHistory(V2TimConversation conversationItem) {
|
||||
_timuiKitConversationController.clearHistoryMessage(conversation: conversationItem);
|
||||
}
|
||||
|
||||
_pinConversation(V2TimConversation conversation) {
|
||||
_timuiKitConversationController.pinConversation(
|
||||
conversationID: conversation.conversationID, isPinned: !conversation.isPinned!);
|
||||
}
|
||||
|
||||
_deleteConversation(V2TimConversation conversation) {
|
||||
_timuiKitConversationController.deleteConversation(conversationID: conversation.conversationID);
|
||||
}
|
||||
|
||||
List<V2TimConversation?> getFilteredConversation() {
|
||||
List<V2TimConversation?> filteredConversationList =
|
||||
model.conversationList.where((element) => (element?.groupID != null || element?.userID != null)).toList();
|
||||
if (widget.conversationCollector != null) {
|
||||
filteredConversationList = filteredConversationList.where(widget.conversationCollector!).toList();
|
||||
}
|
||||
return filteredConversationList;
|
||||
}
|
||||
|
||||
_onScrollToConversation(String conversationID) {
|
||||
final msgList = getFilteredConversation();
|
||||
bool isFound = false;
|
||||
int targetIndex = 1;
|
||||
for (int i = msgList.length - 1; i >= 0; i--) {
|
||||
final currentConversation = msgList[i];
|
||||
if (currentConversation?.conversationID == conversationID) {
|
||||
isFound = true;
|
||||
targetIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isFound) {
|
||||
_autoScrollController.scrollToIndex(
|
||||
targetIndex,
|
||||
preferPosition: AutoScrollPosition.begin,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _defaultSecondaryMenu(V2TimConversation conversationItem, VoidCallback onClose) {
|
||||
return TUIKitColumnMenu(data: [
|
||||
if (!PlatformUtils().isWeb)
|
||||
ColumnMenuItem(
|
||||
label: TIM_t("清除消息"),
|
||||
icon: const Icon(Icons.clear_all, size: 16),
|
||||
onClick: () {
|
||||
onClose();
|
||||
_clearHistory(conversationItem);
|
||||
}),
|
||||
ColumnMenuItem(
|
||||
label: conversationItem.isPinned! ? TIM_t("取消置顶") : TIM_t("置顶"),
|
||||
icon: Icon(conversationItem.isPinned! ? Icons.vertical_align_bottom : Icons.vertical_align_top, size: 16),
|
||||
onClick: () {
|
||||
onClose();
|
||||
_pinConversation(conversationItem);
|
||||
}),
|
||||
ColumnMenuItem(
|
||||
label: TIM_t("删除会话"),
|
||||
icon: const Icon(Icons.delete_outline, size: 16),
|
||||
onClick: () {
|
||||
onClose();
|
||||
_deleteConversation(conversationItem);
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
List<ConversationItemSlidePanel> _defaultSlideBuilder(
|
||||
V2TimConversation conversationItem,
|
||||
) {
|
||||
final theme = themeViewModel.theme;
|
||||
return [
|
||||
if (!PlatformUtils().isWeb)
|
||||
ConversationItemSlidePanel(
|
||||
onPressed: (context) {
|
||||
_clearHistory(conversationItem);
|
||||
},
|
||||
backgroundColor: theme.conversationItemSliderClearBgColor ?? CommonColor.primaryColor,
|
||||
foregroundColor: theme.conversationItemSliderTextColor,
|
||||
label: TIM_t("清除"),
|
||||
spacing: 0,
|
||||
autoClose: true,
|
||||
),
|
||||
ConversationItemSlidePanel(
|
||||
onPressed: (context) {
|
||||
_pinConversation(conversationItem);
|
||||
},
|
||||
backgroundColor: theme.conversationItemSliderPinBgColor ?? CommonColor.infoColor,
|
||||
foregroundColor: theme.conversationItemSliderTextColor,
|
||||
label: conversationItem.isPinned! ? TIM_t("取消置顶") : TIM_t("置顶"),
|
||||
),
|
||||
ConversationItemSlidePanel(
|
||||
onPressed: (context) {
|
||||
_deleteConversation(conversationItem);
|
||||
},
|
||||
backgroundColor: theme.conversationItemSliderDeleteBgColor ?? Colors.red,
|
||||
foregroundColor: theme.conversationItemSliderTextColor,
|
||||
label: TIM_t("删除"),
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
Widget _getSecondaryMenu(V2TimConversation conversation, VoidCallback onClose) {
|
||||
if (widget.itemSecondaryMenuBuilder != null) {
|
||||
return widget.itemSecondaryMenuBuilder!(conversation, onClose);
|
||||
}
|
||||
return _defaultSecondaryMenu(conversation, onClose);
|
||||
}
|
||||
|
||||
ConversationItemSlideBuilder _getSlideBuilder() {
|
||||
return widget.itemSlideBuilder ?? _defaultSlideBuilder;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
|
||||
final theme = value.theme;
|
||||
final isDesktopScreen = TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop;
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider.value(value: model),
|
||||
ChangeNotifierProvider.value(value: friendShipViewModel),
|
||||
ChangeNotifierProvider.value(value: groupListenerModel)
|
||||
],
|
||||
builder: (BuildContext context, Widget? w) {
|
||||
final _model = Provider.of<TUIConversationViewModel>(context);
|
||||
bool haveMoreData = _model.haveMoreData;
|
||||
final _friendShipViewModel = Provider.of<TUIFriendShipViewModel>(context);
|
||||
_model.lifeCycle = widget.lifeCycle;
|
||||
|
||||
final TUIGroupListenerModel groupListenerModel = Provider.of<TUIGroupListenerModel>(context, listen: true);
|
||||
final NeedUpdate? needUpdate = groupListenerModel.needUpdate;
|
||||
if (needUpdate != null) {
|
||||
groupListenerModel.needUpdate = null;
|
||||
if (needUpdate.updateType == UpdateType.groupDismissed) {
|
||||
onTIMCallback(TIMCallback(
|
||||
type: TIMCallbackType.INFO,
|
||||
infoRecommendText: "${needUpdate!.extraData}${TIM_t("已解散")}",
|
||||
infoCode: 6661402));
|
||||
} else if (needUpdate.updateType == UpdateType.kickedFromGroup) {
|
||||
onTIMCallback(TIMCallback(
|
||||
type: TIMCallbackType.INFO,
|
||||
infoRecommendText: "${TIM_t("您已被踢出")}${needUpdate!.extraData}",
|
||||
infoCode: 6661402));
|
||||
}
|
||||
}
|
||||
|
||||
List<V2TimConversation?> filteredConversationList = getFilteredConversation();
|
||||
|
||||
if (TencentUtils.checkString(_model.scrollToConversation) != null) {
|
||||
_onScrollToConversation(_model.scrollToConversation!);
|
||||
_model.clearScrollToConversation();
|
||||
}
|
||||
|
||||
Widget conversationList() {
|
||||
return filteredConversationList.isNotEmpty
|
||||
? ListView.builder(
|
||||
controller: _autoScrollController,
|
||||
shrinkWrap: true,
|
||||
itemCount: filteredConversationList.length,
|
||||
itemBuilder: (context, index) {
|
||||
if (index == filteredConversationList.length - 1) {
|
||||
if (haveMoreData) {
|
||||
_timuiKitConversationController.loadData();
|
||||
}
|
||||
}
|
||||
|
||||
final conversationItem = filteredConversationList[index];
|
||||
|
||||
final V2TimUserStatus? onlineStatus = _friendShipViewModel.userStatusList.firstWhere(
|
||||
(item) => item.userID == conversationItem?.userID,
|
||||
orElse: () => V2TimUserStatus(statusType: 0));
|
||||
|
||||
if (widget.itemBuilder != null) {
|
||||
return widget.itemBuilder!(conversationItem!, onlineStatus);
|
||||
}
|
||||
|
||||
final slideChildren = _getSlideBuilder()(conversationItem!);
|
||||
|
||||
final isCurrent = conversationItem.conversationID == model.selectedConversation?.conversationID;
|
||||
|
||||
final isPined = conversationItem.isPinned ?? false;
|
||||
|
||||
Widget conversationLineItem() {
|
||||
return Material(
|
||||
color: (isCurrent && isDesktopScreen)
|
||||
? theme.conversationItemChooseBgColor
|
||||
: isPined
|
||||
? theme.conversationItemPinedBgColor
|
||||
: theme.conversationItemBgColor,
|
||||
child: GestureDetector(
|
||||
child: TIMUIKitConversationItem(
|
||||
isCurrent: isCurrent,
|
||||
lastMessageBuilder: widget.lastMessageBuilder,
|
||||
faceUrl: conversationItem.faceUrl ?? "",
|
||||
nickName: conversationItem.showName ?? "",
|
||||
isDisturb:
|
||||
(conversationItem.groupType == "Meeting" ? false : conversationItem.recvOpt != 0),
|
||||
lastMsg: conversationItem.lastMessage,
|
||||
isPined: isPined,
|
||||
groupAtInfoList: conversationItem.groupAtInfoList ?? [],
|
||||
unreadCount: conversationItem.unreadCount ?? 0,
|
||||
draftText: conversationItem.draftText,
|
||||
onlineStatus: (widget.isShowOnlineStatus &&
|
||||
conversationItem.userID != null &&
|
||||
conversationItem.userID!.isNotEmpty)
|
||||
? onlineStatus
|
||||
: null,
|
||||
draftTimestamp: conversationItem.draftTimestamp,
|
||||
convType: conversationItem.type),
|
||||
onTap: () => onTapConvItem(conversationItem),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return TUIKitScreenUtils.getDeviceWidget(
|
||||
context: context,
|
||||
desktopWidget: AutoScrollTag(
|
||||
key: ValueKey(conversationItem.conversationID),
|
||||
controller: _autoScrollController,
|
||||
index: index,
|
||||
child: InkWell(
|
||||
onSecondaryTapDown: (details) {
|
||||
TUIKitWidePopup.showPopupWindow(
|
||||
operationKey: TUIKitWideModalOperationKey.conversationSecondaryMenu,
|
||||
isDarkBackground: false,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(4)),
|
||||
context: context,
|
||||
offset: Offset(
|
||||
min(details.globalPosition.dx, MediaQuery.of(context).size.width - 80),
|
||||
min(details.globalPosition.dy, MediaQuery.of(context).size.height - 130)),
|
||||
child: (onClose) => _getSecondaryMenu(conversationItem, onClose));
|
||||
},
|
||||
child: conversationLineItem(),
|
||||
),
|
||||
),
|
||||
defaultWidget: AutoScrollTag(
|
||||
key: ValueKey(conversationItem.conversationID),
|
||||
controller: _autoScrollController,
|
||||
index: index,
|
||||
child: Slidable(
|
||||
groupTag: 'conversation-list',
|
||||
child: conversationLineItem(),
|
||||
endActionPane: ActionPane(
|
||||
extentRatio: slideChildren.length > 2 ? 0.77 : 0.5,
|
||||
motion: const DrawerMotion(),
|
||||
children: slideChildren)),
|
||||
));
|
||||
})
|
||||
: (widget.emptyBuilder != null ? widget.emptyBuilder!() : Container());
|
||||
}
|
||||
|
||||
return TUIKitScreenUtils.getDeviceWidget(
|
||||
context: context,
|
||||
defaultWidget: SlidableAutoCloseBehavior(
|
||||
child: EasyRefresh(
|
||||
header: CustomizeBallPulseHeader(color: theme.primaryColor),
|
||||
onRefresh: () async {
|
||||
model.refresh();
|
||||
},
|
||||
child: conversationList(),
|
||||
),
|
||||
),
|
||||
desktopWidget: Scrollbar(controller: _autoScrollController, child: conversationList()));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:tencent_chat_i18n_tool/tencent_chat_i18n_tool.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_statelesswidget.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/theme/tui_theme.dart';
|
||||
|
||||
class TIMUIKitDraftText extends TIMUIKitStatelessWidget {
|
||||
final BuildContext context;
|
||||
final String draftText;
|
||||
final double fontSize;
|
||||
|
||||
TIMUIKitDraftText({
|
||||
Key? key,
|
||||
this.fontSize = 14.0,
|
||||
required this.context,
|
||||
required this.draftText,
|
||||
}) : super(key: key);
|
||||
|
||||
String _getDraftShowText() {
|
||||
final draftShowText = TIM_t("草稿");
|
||||
|
||||
return '[$draftShowText] ';
|
||||
}
|
||||
|
||||
@override
|
||||
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
|
||||
final TUITheme theme = value.theme;
|
||||
return Row(children: [
|
||||
Text(_getDraftShowText(),
|
||||
style: TextStyle(
|
||||
color: theme.conversationItemDraftTextColor,
|
||||
)),
|
||||
Expanded(
|
||||
child: Text(
|
||||
draftText,
|
||||
softWrap: true,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
height: 1.5,
|
||||
color: theme.conversationItemLastMessageTextColor,
|
||||
fontSize: fontSize),
|
||||
)),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
// ignore_for_file: empty_catches
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_at_info.dart'
|
||||
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_group_at_info.dart';
|
||||
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message.dart'
|
||||
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_message.dart';
|
||||
import 'package:tencent_cloud_chat_sdk/models/v2_tim_user_status.dart'
|
||||
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_user_status.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/ui/utils/screen_utils.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_statelesswidget.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/ui/utils/time_ago.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitConversation/tim_uikit_conversation_last_msg.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/ui/widgets/avatar.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/ui/widgets/unread_message.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/theme/color.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/theme/tui_theme.dart';
|
||||
|
||||
typedef LastMessageBuilder = Widget? Function(V2TimMessage? lastMsg, List<V2TimGroupAtInfo?> groupAtInfoList);
|
||||
|
||||
class TIMUIKitConversationItem extends TIMUIKitStatelessWidget {
|
||||
final String faceUrl;
|
||||
final String nickName;
|
||||
final V2TimMessage? lastMsg;
|
||||
final int unreadCount;
|
||||
final bool isPined;
|
||||
final List<V2TimGroupAtInfo?> groupAtInfoList;
|
||||
final String? draftText;
|
||||
final int? draftTimestamp;
|
||||
final bool isDisturb;
|
||||
final LastMessageBuilder? lastMessageBuilder;
|
||||
final V2TimUserStatus? onlineStatus;
|
||||
final int? convType;
|
||||
final bool isCurrent;
|
||||
|
||||
TIMUIKitConversationItem({
|
||||
Key? key,
|
||||
required this.faceUrl,
|
||||
required this.nickName,
|
||||
required this.lastMsg,
|
||||
this.onlineStatus,
|
||||
required this.isPined,
|
||||
this.isCurrent = false,
|
||||
required this.unreadCount,
|
||||
required this.groupAtInfoList,
|
||||
required this.isDisturb,
|
||||
this.draftText,
|
||||
this.draftTimestamp,
|
||||
this.lastMessageBuilder,
|
||||
this.convType,
|
||||
}) : super(key: key);
|
||||
|
||||
Widget _getShowMsgWidget(BuildContext context) {
|
||||
final isDesktopScreen = TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop;
|
||||
if (lastMsg != null && lastMessageBuilder != null && lastMessageBuilder!(lastMsg, groupAtInfoList) != null) {
|
||||
return lastMessageBuilder!(lastMsg, groupAtInfoList)!;
|
||||
}
|
||||
|
||||
if (lastMsg != null || (draftText != null && draftText != "")) {
|
||||
return TIMUIKitLastMsg(
|
||||
fontSize: isDesktopScreen ? 12 : 14,
|
||||
groupAtInfoList: groupAtInfoList,
|
||||
lastMsg: lastMsg,
|
||||
isDisturb: isDisturb,
|
||||
unreadCount: unreadCount,
|
||||
context: context,
|
||||
draftText: draftText ?? "",
|
||||
);
|
||||
}
|
||||
|
||||
return Container(
|
||||
height: 0,
|
||||
);
|
||||
}
|
||||
|
||||
bool isHaveSecondLine() {
|
||||
return (draftText != null && draftText != "") || (lastMsg != null);
|
||||
}
|
||||
|
||||
Widget _getTimeStringForChatWidget(BuildContext context, TUITheme theme) {
|
||||
try {
|
||||
if (draftTimestamp != null && draftTimestamp != 0) {
|
||||
return Text(TimeAgo().getTimeStringForChat(draftTimestamp as int) ?? "",
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: theme.conversationItemTitmeTextColor,
|
||||
));
|
||||
} else if (lastMsg != null) {
|
||||
return Text(TimeAgo().getTimeStringForChat(lastMsg!.timestamp as int) ?? "",
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: theme.conversationItemTitmeTextColor,
|
||||
));
|
||||
}
|
||||
} catch (err) {}
|
||||
|
||||
return Container();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
|
||||
final TUITheme theme = value.theme;
|
||||
final isDesktopScreen = TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop;
|
||||
return Container(
|
||||
padding: const EdgeInsets.only(top: 6, bottom: 6, left: 16, right: 16),
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: theme.conversationItemBorderColor ?? CommonColor.weakDividerColor,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.only(top: 0, bottom: 2, right: 0),
|
||||
child: SizedBox(
|
||||
width: isDesktopScreen ? 40 : 44,
|
||||
height: isDesktopScreen ? 40 : 44,
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
Avatar(onlineStatus: onlineStatus, faceUrl: faceUrl, showName: nickName, type: convType),
|
||||
if (unreadCount != 0)
|
||||
Positioned(
|
||||
top: isDisturb ? -2.5 : -4.5,
|
||||
right: isDisturb ? -2.5 : -4.5,
|
||||
child: UnconstrainedBox(
|
||||
child: UnreadMessage(
|
||||
width: isDisturb ? 10 : 18,
|
||||
height: isDisturb ? 10 : 18,
|
||||
unreadCount: isDisturb ? 0 : unreadCount),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Container(
|
||||
height: 60,
|
||||
margin: EdgeInsets.only(left: isDesktopScreen ? 10 : 12),
|
||||
padding: const EdgeInsets.only(top: 0, bottom: 0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
nickName,
|
||||
softWrap: true,
|
||||
textAlign: TextAlign.left,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
color: theme.conversationItemTitleTextColor,
|
||||
fontSize: isDesktopScreen ? 14 : 18,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
)),
|
||||
_getTimeStringForChatWidget(context, theme),
|
||||
],
|
||||
),
|
||||
if (isHaveSecondLine())
|
||||
const SizedBox(
|
||||
height: 6,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(child: _getShowMsgWidget(context)),
|
||||
if (isDisturb)
|
||||
SizedBox(
|
||||
width: 18,
|
||||
height: 18,
|
||||
child: Icon(
|
||||
Icons.notifications_off,
|
||||
color: theme.conversationItemNoNotificationIconColor,
|
||||
size: isDesktopScreen ? 14 : 16.0,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
))
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
// ignore_for_file: unrelated_type_equality_checks
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:extended_text/extended_text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tencent_chat_i18n_tool/tencent_chat_i18n_tool.dart';
|
||||
import 'package:tencent_cloud_chat_sdk/enum/message_elem_type.dart';
|
||||
import 'package:tencent_cloud_chat_sdk/enum/message_status.dart';
|
||||
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_at_info.dart'
|
||||
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_group_at_info.dart';
|
||||
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message.dart'
|
||||
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_message.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/ui/utils/common_utils.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/ui/utils/message.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/ui/utils/screen_utils.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitTextField/special_text/DefaultSpecialTextSpanBuilder.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/theme/tui_theme.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/theme/tui_theme_view_model.dart';
|
||||
|
||||
class TIMUIKitLastMsg extends StatefulWidget {
|
||||
final V2TimMessage? lastMsg;
|
||||
final List<V2TimGroupAtInfo?> groupAtInfoList;
|
||||
final BuildContext context;
|
||||
final double fontSize;
|
||||
bool isDisturb;
|
||||
int unreadCount;
|
||||
String draftText;
|
||||
|
||||
TIMUIKitLastMsg(
|
||||
{Key? key,
|
||||
this.lastMsg,
|
||||
required this.groupAtInfoList,
|
||||
this.isDisturb = false,
|
||||
this.unreadCount = 0,
|
||||
required this.draftText,
|
||||
required this.context,
|
||||
this.fontSize = 14.0})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
State<TIMUIKitLastMsg> createState() => _TIMUIKitLastMsgState();
|
||||
}
|
||||
|
||||
class _TIMUIKitLastMsgState extends TIMUIKitState<TIMUIKitLastMsg> {
|
||||
String groupTipsAbstractText = "";
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_getMsgElem();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant TIMUIKitLastMsg oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if ((oldWidget.lastMsg?.msgID != widget.lastMsg?.msgID) ||
|
||||
(oldWidget.lastMsg?.id != widget.lastMsg?.id) ||
|
||||
(oldWidget.lastMsg?.status != widget.lastMsg?.status) ||
|
||||
(oldWidget.unreadCount != widget.unreadCount)) {
|
||||
_getMsgElem();
|
||||
}
|
||||
}
|
||||
|
||||
(bool isRevoke, bool isRevokeByAdmin) isRevokeMessage(V2TimMessage? message) {
|
||||
if (message == null) {
|
||||
return (false, false);
|
||||
}
|
||||
if (message.status == 6) {
|
||||
return (true, false);
|
||||
} else {
|
||||
try {
|
||||
final customData = jsonDecode(message.cloudCustomData ?? "{}");
|
||||
final isRevoke = customData["isRevoke"] ?? false;
|
||||
final revokeByAdmin = customData["revokeByAdmin"] ?? false;
|
||||
return (isRevoke, revokeByAdmin);
|
||||
} catch (e) {
|
||||
return (false, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _getMsgElem() async {
|
||||
final revokeStatus = isRevokeMessage(widget.lastMsg);
|
||||
final isRevokedMessage = revokeStatus.$1;
|
||||
final isAdminRevoke = revokeStatus.$2;
|
||||
if (isRevokedMessage) {
|
||||
final isSelf = widget.lastMsg!.isSelf ?? true;
|
||||
final option1 =
|
||||
isAdminRevoke ? TIM_t("管理员") : (isSelf ? TIM_t("您") : widget.lastMsg!.nickName ?? widget.lastMsg?.sender);
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
groupTipsAbstractText = TIM_t_para("{{option1}}撤回了一条消息", "$option1撤回了一条消息")(option1: option1);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
String msgShowText = await _getLastMsgShowText(widget.lastMsg, widget.context) ?? "";
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
groupTipsAbstractText = msgShowText;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String _getDisturbUnreadCountInfo() {
|
||||
if (widget.isDisturb && widget.unreadCount > 0) {
|
||||
final option1 = widget.unreadCount.toString();
|
||||
String unreadCountText = TIM_t_para("[{{option1}} 条]", "[$option1 条]")(option1: option1);
|
||||
return unreadCountText;
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
Future<String?> _getLastMsgShowText(V2TimMessage? message, BuildContext context) async {
|
||||
final msgType = message!.elemType;
|
||||
switch (msgType) {
|
||||
case MessageElemType.V2TIM_ELEM_TYPE_CUSTOM:
|
||||
return TIM_t("[自定义]");
|
||||
case MessageElemType.V2TIM_ELEM_TYPE_SOUND:
|
||||
return TIM_t("[语音]");
|
||||
case MessageElemType.V2TIM_ELEM_TYPE_TEXT:
|
||||
return (widget.lastMsg?.textElem?.text)?.trim() ?? "";
|
||||
case MessageElemType.V2TIM_ELEM_TYPE_FACE:
|
||||
return TIM_t("[表情]");
|
||||
case MessageElemType.V2TIM_ELEM_TYPE_FILE:
|
||||
final option1 = widget.lastMsg!.fileElem!.fileName;
|
||||
return TIM_t_para("[文件] {{option1}}", "[文件] $option1")(option1: option1);
|
||||
case MessageElemType.V2TIM_ELEM_TYPE_GROUP_TIPS:
|
||||
return await MessageUtils.groupTipsMessageAbstract(widget.lastMsg!.groupTipsElem!, []);
|
||||
case MessageElemType.V2TIM_ELEM_TYPE_IMAGE:
|
||||
return TIM_t("[图片]");
|
||||
case MessageElemType.V2TIM_ELEM_TYPE_VIDEO:
|
||||
return TIM_t("[视频]");
|
||||
case MessageElemType.V2TIM_ELEM_TYPE_LOCATION:
|
||||
return TIM_t("[位置]");
|
||||
case MessageElemType.V2TIM_ELEM_TYPE_MERGER:
|
||||
return TIM_t("[聊天记录]");
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Icon? _getIconByMsgStatus(BuildContext context) {
|
||||
final msgStatus = widget.lastMsg!.status;
|
||||
final theme = Provider.of<TUIThemeViewModel>(context).theme;
|
||||
if (msgStatus == MessageStatus.V2TIM_MSG_STATUS_SEND_FAIL) {
|
||||
return Icon(Icons.error, color: theme.cautionColor, size: 16);
|
||||
}
|
||||
if (msgStatus == MessageStatus.V2TIM_MSG_STATUS_SENDING) {
|
||||
return Icon(Icons.arrow_back, color: theme.weakTextColor, size: 16);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String _getAtMessage() {
|
||||
bool atMe = false;
|
||||
bool atAll = false;
|
||||
String msg = "";
|
||||
for (var item in widget.groupAtInfoList) {
|
||||
if (item!.atType == 1) {
|
||||
atMe = true;
|
||||
continue;
|
||||
} else if (item!.atType == 2) {
|
||||
atAll = true;
|
||||
continue;
|
||||
} else if (item!.atType == 3) {
|
||||
atMe = true;
|
||||
atAll = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (atAll && atMe) {
|
||||
msg = TIM_t("[@所有人][有人@我]");
|
||||
} else if (atAll) {
|
||||
msg = TIM_t("[@所有人]");
|
||||
} else if (atMe) {
|
||||
msg = TIM_t("[有人@我]");
|
||||
}
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
String _getDraftShowText() {
|
||||
final draftShowText = TIM_t("草稿");
|
||||
return '[$draftShowText]';
|
||||
}
|
||||
|
||||
@override
|
||||
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
|
||||
final isDesktopScreen = TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop;
|
||||
final TUITheme theme = value.theme;
|
||||
final icon = _getIconByMsgStatus(context);
|
||||
String disturbUnreadCountInfo = _getDisturbUnreadCountInfo();
|
||||
return Row(children: [
|
||||
if (icon != null)
|
||||
Container(
|
||||
margin: const EdgeInsets.only(right: 2),
|
||||
child: icon,
|
||||
),
|
||||
if (widget.groupAtInfoList.isNotEmpty)
|
||||
Text(_getAtMessage(), style: TextStyle(color: theme.cautionColor, fontSize: widget.fontSize)),
|
||||
if (widget.draftText != null && widget.draftText != "")
|
||||
Text(_getDraftShowText(),
|
||||
style: TextStyle(color: theme.conversationItemDraftTextColor, fontSize: widget.fontSize)),
|
||||
if (disturbUnreadCountInfo != "")
|
||||
Text(disturbUnreadCountInfo, style: TextStyle(color: theme.weakTextColor, fontSize: widget.fontSize)),
|
||||
if (widget.draftText != null && widget.draftText != "")
|
||||
Expanded(
|
||||
child: ExtendedText(groupTipsAbstractText,
|
||||
softWrap: true,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(height: 1, color: theme.weakTextColor, fontSize: widget.fontSize),
|
||||
specialTextSpanBuilder: DefaultSpecialTextSpanBuilder(
|
||||
isUseQQPackage: true,
|
||||
isUseTencentCloudChatPackage: true,
|
||||
showAtBackground: true,
|
||||
)),
|
||||
),
|
||||
if (widget.draftText == null || widget.draftText == "" && TencentUtils.checkString(groupTipsAbstractText) != null)
|
||||
Expanded(
|
||||
child: ExtendedText(groupTipsAbstractText,
|
||||
softWrap: true,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(height: 1, color: theme.weakTextColor, fontSize: widget.fontSize),
|
||||
specialTextSpanBuilder: DefaultSpecialTextSpanBuilder(
|
||||
isUseQQPackage: true,
|
||||
isUseTencentCloudChatPackage: true,
|
||||
showAtBackground: true,
|
||||
)),
|
||||
)
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user