This commit is contained in:
2025-06-30 16:24:20 +08:00
commit 68de5eb048
454 changed files with 56921 additions and 0 deletions
@@ -0,0 +1,179 @@
import 'package:flutter/cupertino.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_friend_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_friend_info.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_user_full_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_user_full_info.dart';
enum ProfileWidgetEnum {
/// THe card shows the user info of a specific user.
userInfoCard,
/// The switch of if add another user to block list.
/// This will not shows when friendship relation not exist as default.
addToBlockListBar,
/// The switch of if pin the one-to-one conversation to the top of the conversation list.
/// This will not shows when friendship relation not exist as default.
pinConversationBar,
/// The switch of if mute the message notification from a specific user.
/// This will not shows when friendship relation not exist as default.
messageMute,
/// The entrance to search page, please set the `onTap` callback yourself, to the search page with conversation ID.
/// You can develop it with `TIMUIKitProfileWidget.searchBar`.
searchBar,
/// The bar shows the portrait.
portraitBar,
/// The bar shows the nickname of a specific user.
nicknameBar,
/// The bar shows the user account of a specific user.
userAccountBar,
/// The bar shows the signature of a specific user.
signatureBar,
/// The bar shows the gender of a specific user.
/// 1 represent male, 2 represent female.
genderBar,
/// The bar shows the birthday of a specific user.
/// Int like "19981112", means November 12, 1998.
birthdayBar,
/// The area shows the buttons,
/// contains "Send message", "Voice/Video Call", "Delete friend" when has the friend relationship,
/// while contains "Add friend" when no relationship exists, as default.
addAndDeleteArea,
/// The divider between sets of profile widget
operationDivider,
/// The setting of remark for a specific user.
/// This will not shows when friendship relation not exist as default.
remarkBar,
/// Custom area, you may define send message, make calling, search or anything you want here.
customBuilderOne,
/// Custom area, you may define send message, make calling, search or anything you want here.
customBuilderTwo,
/// Custom area, you may define send message, make calling, search or anything you want here.
customBuilderThree,
/// Custom area, you may define send message, make calling, search or anything you want here.
customBuilderFour,
/// Custom area, you may define send message, make calling, search or anything you want here.
customBuilderFive
}
typedef ProfileWidgetItemContent = Widget? Function(
bool isShowJump,
VoidCallback clearJump,
);
class ProfileWidgetBuilder {
/// The divider between sets of profile widget
Widget Function()? operationDivider;
/// The setting of remark for a specific user
Widget Function(String remark, Function()? handleTap)? remarkBar;
/// The switch of if add another user to block list.
/// This will not shows when friendship relation not exist as default.
Widget Function(bool isAsBlocked, Function(bool value)? onChange)? addToBlockListBar;
/// The switch of if pin the one-to-one conversation to the top of the conversation list.
/// This will not shows when friendship relation not exist as default.
Widget Function(bool isPinned, Function(bool value)? onChange)? pinConversationBar;
/// The switch of if mute the message notification from a specific user.
/// This will not shows when friendship relation not exist as default.
Widget Function(bool isMute, Function(bool value)? onChange)? messageMute;
/// Override the default operation item style for un-customized profile widget.
Widget Function({
required String operationName,
required String type,
bool? operationValue,
String? operationText,
void Function(bool newValue)? onSwitchChange,
})? operationItem;
/// The entrance to search page, please set the `onTap` callback yourself, to the search page with conversation ID.
/// You can develop it with `TIMUIKitProfileWidget.searchBar`.
Widget Function(V2TimConversation conversation)? searchBar;
/// The bar shows the portrait.
Widget Function(V2TimUserFullInfo? userInfo)? portraitBar;
/// The bar shows the nickname of a specific user.
Widget Function(String nickName)? nicknameBar;
/// The bar shows the user account of a specific user.
Widget Function(String userAccount)? userAccountBar;
/// The bar shows the signature of a specific user.
Widget Function(String signature)? signatureBar;
/// The bar shows the gender of a specific user.
/// 1 represent male, 2 represent female.
Widget Function(int gender)? genderBar;
/// The bar shows the birthday of a specific user.
/// Int like "19981111", means November 11, 1998.
Widget Function(int? birthday)? birthdayBar;
/// THe card shows the user info of a specific user.
Widget Function(V2TimUserFullInfo? userInfo)? userInfoCard;
/// The area shows the buttons,
/// contains "Send message", "Voice/Video Call", "Delete friend" when has the friend relationship,
/// while contains "Add friend" when no relationship exists, as default.
Widget Function(V2TimFriendInfo friendInfo, V2TimConversation conversation, int friendType, bool isDisturb)?
addAndDeleteArea;
/// Custom area, you may define send message, make calling, search or anything you want here.
Widget Function(bool isFriend, V2TimFriendInfo friendInfo, V2TimConversation conversation)? customBuilderOne;
/// Custom area, you may define send message, make calling, search or anything you want here.
Widget Function(bool isFriend, V2TimFriendInfo friendInfo, V2TimConversation conversation)? customBuilderTwo;
/// Custom area, you may define send message, make calling, search or anything you want here.
Widget Function(bool isFriend, V2TimFriendInfo friendInfo, V2TimConversation conversation)? customBuilderThree;
/// Custom area, you may define send message, make calling, search or anything you want here.
Widget Function(bool isFriend, V2TimFriendInfo friendInfo, V2TimConversation conversation)? customBuilderFour;
/// Custom area, you may define send message, make calling, search or anything you want here.
Widget Function(bool isFriend, V2TimFriendInfo friendInfo, V2TimConversation conversation)? customBuilderFive;
ProfileWidgetBuilder(
{this.operationDivider,
this.remarkBar,
this.addToBlockListBar,
this.pinConversationBar,
this.messageMute,
this.operationItem,
this.searchBar,
this.portraitBar,
this.nicknameBar,
this.userAccountBar,
this.signatureBar,
this.genderBar,
this.birthdayBar,
this.userInfoCard,
this.addAndDeleteArea,
this.customBuilderOne,
this.customBuilderTwo,
this.customBuilderThree,
this.customBuilderFive,
this.customBuilderFour});
}
@@ -0,0 +1,403 @@
import 'package:flutter/material.dart';
import 'package:loading_animation_widget/loading_animation_widget.dart';
import 'package:provider/provider.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_friend_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_friend_info.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_user_full_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_user_full_info.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/life_cycle/profile_life_cycle.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/separate_models/tui_profile_view_model.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_self_info_view_model.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/base_widgets/tim_ui_kit_base.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/screen_utils.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitProfile/profile_widget.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitProfile/widget/tim_uikit_profile_widget.dart';
typedef OnSelfAvatarTap = void Function();
typedef ProfileBuilder = Widget Function(
BuildContext context, V2TimFriendInfo userInfo, V2TimConversation conversation, int friendType, bool isMute);
class TIMUIKitProfile extends StatefulWidget {
/// user ID
final String userID;
/// [Deprecated:] the builder for custom operation list.
/// [operationListBuilder] and [bottomOperationBuilder] merged into [builder], please use it instead.
final Widget Function(BuildContext context, V2TimFriendInfo friendInfo, V2TimConversation conversation,
int friendType, bool isMute)? operationListBuilder;
/// [Deprecated:] The builder for custom bottom operation area.
/// [operationListBuilder] and [bottomOperationBuilder] merged into [builder], please use it instead.
final Widget Function(
BuildContext context, V2TimFriendInfo? friendInfo, V2TimConversation? conversation, int friendType)?
bottomOperationBuilder;
/// [Deprecated:] Callback when clicking profile detail card.
/// This widget will no longer shows the personal info card and can not jump to personal info page automatically,
/// please navigate to your custom personal info page manually and directly, you may refer to our demo.
final void Function(BuildContext context, V2TimUserFullInfo? userFullInfo)? handleProfileDetailCardTap;
/// Profile Controller
final TIMUIKitProfileController? controller;
/// [Deprecated:] If allows jump to personal profiler page.
/// This widget will no longer shows the personal info card and can not jump to personal info page automatically,
/// please navigate to your custom personal info page manually and directly, you may refer to our demo.
final bool canJumpToPersonalProfile;
/// [Deprecated:] The callback when clicking self avatar.
/// This widget will no longer shows the personal info card and will not support to change self avatar,
/// please navigate to your custom personal info page manually and directly, you may refer to our demo.
final OnSelfAvatarTap? onSelfAvatarTap;
/// [If you tend to customize the profile page, use [profileWidgetsBuilder] with [profileWidgetsOrder] as priority.]
/// The builder for each widgets in profile page,
/// you can customize some of it by pass your own widget into here.
/// Or, you can add your custom widget to the three custom widgets.
final ProfileWidgetBuilder? profileWidgetBuilder;
/// [If you tend to customize the profile page, use [profileWidgetsBuilder] with [profileWidgetsOrder] as priority.]
/// If the default widget order can not meet you needs,
/// you may change the order by this array with widget enum.
final List<ProfileWidgetEnum>? profileWidgetsOrder;
/// The builder for the whole profile page, you can use this to customize all the element here.
/// Mentioned: If you use this builder, [profileWidgetBuilder] and [profileWidgetsOrder] will no longer works.
final ProfileBuilder? builder;
/// The life cycle hooks for user profile business logic
final ProfileLifeCycle? lifeCycle;
/// Whether the specify user is current logged in user.
/// Default: [false].
final bool isSelf;
/// Whether use the small card mode on Desktop. Usually shows on the Chat page.
final bool smallCardMode;
const TIMUIKitProfile(
{Key? key,
required this.userID,
@Deprecated("[operationListBuilder] and [bottomOperationBuilder] merged into [builder], please use it instead")
this.operationListBuilder,
@Deprecated("[operationListBuilder] and [bottomOperationBuilder] merged into [builder], please use it instead")
this.bottomOperationBuilder,
@Deprecated(
"This widget will no longer shows the personal info card and can not jump to personal info page automatically, please navigate to your custom personal info page manually and directly, you may refer to our demo")
this.handleProfileDetailCardTap,
@Deprecated(
"This widget will no longer shows the personal info card and can not jump to personal info page automatically, please navigate to your custom personal info page manually and directly, you may refer to our demo")
this.canJumpToPersonalProfile = false,
@Deprecated(
"This widget will no longer shows the personal info card and will not support to change self avatar, please navigate to your custom personal info page manually and directly, you may refer to our demo")
this.onSelfAvatarTap,
this.controller,
this.profileWidgetBuilder,
this.profileWidgetsOrder,
this.builder,
this.isSelf = false,
this.lifeCycle,
this.smallCardMode = false})
: super(key: key);
@override
State<StatefulWidget> createState() => _TIMUIKitProfileState();
}
class _TIMUIKitProfileState extends TIMUIKitState<TIMUIKitProfile> {
final TUIProfileViewModel _model = TUIProfileViewModel();
late TIMUIKitProfileController _controller;
@override
void initState() {
_controller = widget.controller ?? TIMUIKitProfileController();
_model.lifeCycle = widget.lifeCycle;
_model.loadData(userID: widget.userID, isNeedConversation: !widget.isSelf);
super.initState();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
void didUpdateWidget(covariant TIMUIKitProfile oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.userID != widget.userID || _model.userProfile?.friendInfo?.userID != widget.userID) {
_model.userProfile = null;
_model.loadData(userID: widget.userID, isNeedConversation: !widget.isSelf);
}
}
final List<ProfileWidgetEnum> _defaultWidgetOrder = [
ProfileWidgetEnum.userInfoCard,
ProfileWidgetEnum.operationDivider,
ProfileWidgetEnum.remarkBar,
ProfileWidgetEnum.operationDivider,
ProfileWidgetEnum.addToBlockListBar,
ProfileWidgetEnum.pinConversationBar,
ProfileWidgetEnum.messageMute,
ProfileWidgetEnum.operationDivider,
ProfileWidgetEnum.addAndDeleteArea
];
@override
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
final theme = value.theme;
final isDesktopScreen = TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop;
return ChangeNotifierProvider.value(
value: _model,
child: Consumer<TUIProfileViewModel>(
builder: (context, value, child) {
final TUIProfileViewModel model = Provider.of<TUIProfileViewModel>(context);
_controller.model = model;
final V2TimFriendInfo? userInfo = model.userProfile?.friendInfo;
if (userInfo == null) {
return Center(
child: LoadingAnimationWidget.staggeredDotsWave(
color: theme.weakTextColor ?? Colors.grey,
size: 48,
),
);
}
final conversation = model.userProfile?.conversation ??
V2TimConversation(
conversationID: "c2c_${widget.userID}",
type: 1,
userID: widget.userID,
faceUrl: model.userProfile?.friendInfo?.userProfile?.faceUrl,
showName: TencentUtils.checkString(model.userProfile?.friendInfo?.friendRemark) ??
TencentUtils.checkString(model.userProfile?.friendInfo?.userProfile?.nickName) ??
widget.userID);
final TUISelfInfoViewModel _selfInfoViewModel = serviceLocator<TUISelfInfoViewModel>();
final isFriend = model.friendType != 0;
final isSelf = (model.userProfile?.friendInfo?.userID == _selfInfoViewModel.loginInfo?.userID);
final isMute = model.isDisturb ?? false;
Widget profilePage({required Widget child}) {
return Container(
color: isDesktopScreen ? theme.wideBackgroundColor : null,
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()),
child: Container(
child: child,
),
),
);
}
void handleAddToBlockList(bool value) async {
model.addToBlackList(value, userInfo.userID);
}
void handlePinConversation(bool value) async {
model.pinedConversation(value, conversation.conversationID);
}
void handleMuteMessage(bool value) async {
model.setMessageDisturb(userInfo.userID, value);
}
void handleTapRemarkBar({Offset? offset, String? initText}) {
_controller.showTextInputBottomSheet(
initOffset: offset,
initText: initText,
context: context,
title: TIM_t("修改备注名"),
tips: TIM_t("仅限汉字、英文、数字和下划线"),
onSubmitted: (String remark) async {
final res = await _controller.updateRemarks(widget.userID, remark);
if (res.code == 0) {
widget.lifeCycle?.didRemarkUpdated(remark);
}
},
theme: theme);
}
void handleAddFriend() async {
model.addFriend(userInfo.userID);
}
Future<void> handleDeleteFriend() async {
var result = await model.deleteFriend(userInfo.userID, needUpdateData: false);
if (result != null) {
Navigator.pop(context);
}
}
List<Widget> _renderWidgetsWithOrder(List<ProfileWidgetEnum> order) {
final ProfileWidgetBuilder? customBuilder = widget.profileWidgetBuilder;
return order.map((element) {
switch (element) {
case ProfileWidgetEnum.userInfoCard:
return (customBuilder?.userInfoCard != null
? customBuilder?.userInfoCard!(userInfo.userProfile)
: TIMUIKitProfileUserInfoCard(userInfo: userInfo.userProfile))!;
case ProfileWidgetEnum.addToBlockListBar:
if (isSelf) {
return Container();
}
return (customBuilder?.addToBlockListBar != null
? customBuilder?.addToBlockListBar!(model.isAddToBlackList ?? false, handleAddToBlockList)
: TIMUIKitProfileWidget.addToBlackListBar(
model.isAddToBlackList ?? false, context, handleAddToBlockList, widget.smallCardMode))!;
case ProfileWidgetEnum.pinConversationBar:
// if (!isFriend) {
// return Container();
// }
return (customBuilder?.pinConversationBar != null
? customBuilder?.pinConversationBar!(conversation.isPinned ?? false, handlePinConversation)
: TIMUIKitProfileWidget.pinConversationBar(
conversation.isPinned ?? false, context, handlePinConversation, widget.smallCardMode))!;
case ProfileWidgetEnum.messageMute:
// if (!isFriend) {
// return Container();
// }
return (customBuilder?.messageMute != null
? customBuilder?.messageMute!(isMute, handleMuteMessage)
: TIMUIKitProfileWidget.messageDisturb(
context, isMute, handleMuteMessage, widget.smallCardMode))!;
case ProfileWidgetEnum.searchBar:
return (customBuilder?.searchBar != null
? customBuilder?.searchBar!(conversation)
// Please define the search bar with navigating in `profileWidgetBuilder` before using it here.
: Text(TIM_t("你必须自定义search bar,并处理点击跳转")))!;
case ProfileWidgetEnum.portraitBar:
return (customBuilder?.portraitBar != null
? customBuilder?.portraitBar!(userInfo.userProfile)
: TIMUIKitProfileWidget.portraitBar(
TIMUIKitProfileWidget.defaultPortraitWidget(userInfo.userProfile, widget.smallCardMode),
widget.smallCardMode))!;
case ProfileWidgetEnum.nicknameBar:
return (customBuilder?.nicknameBar != null
? customBuilder?.nicknameBar!(userInfo.userProfile?.nickName ?? "")
: TIMUIKitProfileWidget.nicknameBar(userInfo.userProfile?.nickName ?? "", widget.smallCardMode))!;
case ProfileWidgetEnum.userAccountBar:
return (customBuilder?.userAccountBar != null
? customBuilder?.userAccountBar!(userInfo.userProfile?.userID ?? "")
: TIMUIKitProfileWidget.userAccountBar(
userInfo.userProfile?.userID ?? "", widget.smallCardMode))!;
case ProfileWidgetEnum.signatureBar:
return (customBuilder?.signatureBar != null
? customBuilder?.signatureBar!(userInfo.userProfile?.selfSignature ?? "")
: TIMUIKitProfileWidget.signatureBar(
userInfo.userProfile?.selfSignature ?? "", widget.smallCardMode))!;
case ProfileWidgetEnum.genderBar:
return (customBuilder?.genderBar != null
? customBuilder?.genderBar!(userInfo.userProfile?.gender ?? 0)
: TIMUIKitProfileWidget.genderBar(userInfo.userProfile?.gender ?? 0, widget.smallCardMode))!;
case ProfileWidgetEnum.birthdayBar:
return (customBuilder?.birthdayBar != null
? customBuilder?.birthdayBar!(userInfo.userProfile?.birthday)
: TIMUIKitProfileWidget.birthdayBar(userInfo.userProfile?.birthday, widget.smallCardMode))!;
case ProfileWidgetEnum.addAndDeleteArea:
if (isSelf) {
return Container();
}
return (customBuilder?.addAndDeleteArea != null
? customBuilder?.addAndDeleteArea!(
userInfo,
conversation,
value.friendType,
isMute,
)
: isDesktopScreen
? TIMUIKitProfileWidget.addAndDeleteAreaWide(
userInfo,
conversation,
value.friendType,
isMute,
model.isAddToBlackList ?? false,
theme,
handleAddFriend,
handleDeleteFriend,
widget.smallCardMode)
: TIMUIKitProfileWidget.addAndDeleteArea(
userInfo,
conversation,
value.friendType,
isMute,
model.isAddToBlackList ?? false,
theme,
handleAddFriend,
handleDeleteFriend,
widget.smallCardMode))!;
case ProfileWidgetEnum.operationDivider:
return (customBuilder?.operationDivider != null
? customBuilder?.operationDivider!()
: TIMUIKitProfileWidget.operationDivider(
color: theme.weakDividerColor,
height: isDesktopScreen ? 1 : 10,
margin:
isDesktopScreen ? EdgeInsets.symmetric(vertical: widget.smallCardMode ? 4 : 20) : null))!;
case ProfileWidgetEnum.remarkBar:
if (!isFriend) {
return Container();
}
return (customBuilder?.remarkBar != null
? customBuilder?.remarkBar!(userInfo.friendRemark ?? "", handleTapRemarkBar)
: TIMUIKitProfileWidget.remarkBar(
context, userInfo.friendRemark ?? "", handleTapRemarkBar, widget.smallCardMode))!;
case ProfileWidgetEnum.customBuilderOne:
return (customBuilder?.customBuilderOne != null
? customBuilder?.customBuilderOne!(isFriend, userInfo, conversation)
// Please define the corresponding custom widget in `profileWidgetBuilder` before using it here.
: Text(TIM_t("如使用自定义区域,请在profileWidgetBuilder传入对应组件")))!;
case ProfileWidgetEnum.customBuilderTwo:
return (customBuilder?.customBuilderTwo != null
? customBuilder?.customBuilderTwo!(isFriend, userInfo, conversation)
// Please define the corresponding custom widget in `profileWidgetBuilder` before using it here.
: Text(TIM_t("如使用自定义区域,请在profileWidgetBuilder传入对应组件")))!;
case ProfileWidgetEnum.customBuilderThree:
return (customBuilder?.customBuilderThree != null
? customBuilder?.customBuilderThree!(isFriend, userInfo, conversation)
// Please define the corresponding custom widget in `profileWidgetBuilder` before using it here.
: Text(TIM_t("如使用自定义区域,请在profileWidgetBuilder传入对应组件")))!;
case ProfileWidgetEnum.customBuilderFour:
return (customBuilder?.customBuilderFour != null
? customBuilder?.customBuilderFour!(isFriend, userInfo, conversation)
// Please define the corresponding custom widget in `profileWidgetBuilder` before using it here.
: Text(TIM_t("如使用自定义区域,请在profileWidgetBuilder传入对应组件")))!;
case ProfileWidgetEnum.customBuilderFive:
return (customBuilder?.customBuilderFive != null
? customBuilder?.customBuilderFive!(isFriend, userInfo, conversation)
// Please define the corresponding custom widget in `profileWidgetBuilder` before using it here.
: Text(TIM_t("如使用自定义区域,请在profileWidgetBuilder传入对应组件")))!;
default:
return Container();
}
}).toList();
}
if (widget.builder != null) {
return widget.builder!(context, userInfo, conversation, value.friendType, isMute);
} else if (widget.profileWidgetsOrder != null) {
return profilePage(
child: Column(
children: [..._renderWidgetsWithOrder(widget.profileWidgetsOrder!)],
),
);
} else {
return profilePage(
child: Column(
children: [..._renderWidgetsWithOrder(_defaultWidgetOrder)],
));
}
},
),
);
}
}
@@ -0,0 +1,207 @@
import 'package:flutter/cupertino.dart';
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/ui/utils/screen_utils.dart';
import 'package:tencent_cloud_chat_uikit/theme/color.dart';
import 'package:tencent_cloud_chat_uikit/theme/tui_theme.dart';
class TIMUIKitOperationItem extends TIMUIKitStatelessWidget {
final String operationName;
/// shows on the second line
final String? operationDescription;
final bool? operationValue;
/// Is show allow edit status, while a right icon shows on mobile, and `showAllowEditStatus` shows on desktop.
final bool showAllowEditStatus;
/// Used on wide screen.
final String? wideEditText;
/// Used on wide screen.
final bool isEmpty;
/// the operationText widget for replacement, for developers to define what to do
final Widget? operationRightWidget;
final String type;
final void Function(bool newValue)? onSwitchChange;
final Key? itemBoxKey;
final bool isUseCheckedBoxOnWide;
/// Is use the small card mode on Desktop. Usually shows on the Chat page.
final bool smallCardMode;
TIMUIKitOperationItem(
{Key? key,
this.wideEditText,
this.itemBoxKey,
this.operationDescription,
required this.isEmpty,
required this.operationName,
this.smallCardMode = false,
this.operationValue,
this.type = "arrow",
this.isUseCheckedBoxOnWide = false,
this.onSwitchChange,
this.operationRightWidget,
this.showAllowEditStatus = true})
: super(key: key);
@override
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
final TUITheme theme = value.theme;
final isDesktopScreen =
TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop;
return Container(
padding: isDesktopScreen
? EdgeInsets.symmetric(
horizontal: isUseCheckedBoxOnWide ? 6 : 16,
vertical: smallCardMode ? 0 : 4)
: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
margin: isDesktopScreen ? null : const EdgeInsets.only(bottom: 1),
color: Colors.white,
child: (isDesktopScreen && isUseCheckedBoxOnWide)
? Row(
children: [
Transform.scale(
scale: 0.8,
child: Checkbox(
fillColor: MaterialStateProperty.all(theme.primaryColor),
value: operationValue ?? false,
onChanged: (val) {
if (onSwitchChange != null) {
onSwitchChange!(val ?? false);
}
}),
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
operationName,
style: const TextStyle(fontSize: 14),
),
if (operationDescription != null)
Text(
operationDescription!,
style:
TextStyle(color: theme.weakTextColor, fontSize: 12),
)
],
)),
],
)
: Row(
mainAxisAlignment: isDesktopScreen
? MainAxisAlignment.start
: MainAxisAlignment.spaceBetween,
children: [
if (isDesktopScreen)
SizedBox(
width: 130,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
operationName,
style: TextStyle(
color:
isDesktopScreen ? hexToColor("7f7f7f") : null),
),
if (operationDescription != null)
Text(
operationDescription!,
style: TextStyle(
color: theme.weakTextColor, fontSize: 12),
)
],
),
),
if (!isDesktopScreen)
Expanded(
child: SizedBox(
width: 130,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
operationName,
style: TextStyle(
color:
isDesktopScreen ? hexToColor("7f7f7f") : null),
),
if (operationDescription != null)
Text(
operationDescription!,
style: TextStyle(
color: theme.weakTextColor, fontSize: 12),
)
],
),
)),
if (type == "switch")
Transform.scale(
key: itemBoxKey,
scale: 0.8,
child: CupertinoSwitch(
value: operationValue ?? false,
onChanged: onSwitchChange,
activeColor: theme.primaryColor,
),
),
if (type != "switch" && !isDesktopScreen)
Transform.scale(
scale: 0,
child: CupertinoSwitch(
value: false,
onChanged: onSwitchChange,
),
),
(type != "switch" &&
isDesktopScreen &&
showAllowEditStatus &&
isEmpty)
? MouseRegion(
key: itemBoxKey,
child: Text(
wideEditText ?? TIM_t("编辑"),
style: TextStyle(color: theme.weakTextColor),
),
)
: Container(
width: 0,
),
if (type != "switch")
Expanded(
child: Row(
mainAxisAlignment: isDesktopScreen
? MainAxisAlignment.start
: MainAxisAlignment.end,
children: [Expanded(child: operationRightWidget ?? const Text(""))],
)),
(type != "switch" && !isDesktopScreen && showAllowEditStatus)
? const Icon(Icons.keyboard_arrow_right)
: Container(
width: 0,
),
if (type != "switch" && isDesktopScreen)
SizedBox(
width: 0,
child: Transform.scale(
scale: 0,
child: CupertinoSwitch(
value: false,
onChanged: onSwitchChange,
),
),
),
],
),
);
}
}
@@ -0,0 +1,54 @@
// ignore_for_file: deprecated_member_use_from_same_package
import 'package:flutter/cupertino.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_user_full_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_user_full_info.dart';
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/screen_utils.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitProfile/widget/tim_uikit_profile_userinfo_card/tim_uikit_profile_userinfo_card_narrow.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitProfile/widget/tim_uikit_profile_userinfo_card/tim_uikit_profile_userinfo_card_wide.dart';
class TIMUIKitProfileUserInfoCard extends StatelessWidget {
/// User info
final V2TimUserFullInfo? userInfo;
final bool isJumpToPersonalProfile;
final VoidCallback? onClickAvatar;
/// If shows the arrow icon on the right
final bool showArrowRightIcon;
const TIMUIKitProfileUserInfoCard(
{Key? key,
this.userInfo,
@Deprecated(
"This info card can no longer navigate to default personal profile page automatically, please deal with it manually.")
this.isJumpToPersonalProfile = false,
this.showArrowRightIcon = false,
this.onClickAvatar})
: super(key: key);
@override
Widget build(BuildContext context) {
return TUIKitScreenUtils.getDeviceWidget(
context: context,
defaultWidget: TIMUIKitProfileUserInfoCardNarrow(
userInfo: userInfo,
isJumpToPersonalProfile: isJumpToPersonalProfile,
showArrowRightIcon: showArrowRightIcon,
onClickAvatar: onClickAvatar,
),
desktopWidget: TIMUIKitProfileUserInfoCardWide(
userInfo: userInfo,
onClickAvatar: onClickAvatar,
isJumpToPersonalProfile: isJumpToPersonalProfile,
showArrowRightIcon: showArrowRightIcon,
),
mobileWidget: TIMUIKitProfileUserInfoCardNarrow(
userInfo: userInfo,
onClickAvatar: onClickAvatar,
isJumpToPersonalProfile: isJumpToPersonalProfile,
showArrowRightIcon: showArrowRightIcon,
),
);
}
}
@@ -0,0 +1,98 @@
import 'package:flutter/material.dart';
import 'package:tencent_chat_i18n_tool/tencent_chat_i18n_tool.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_user_full_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_user_full_info.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/ui/widgets/avatar.dart';
import 'package:tencent_cloud_chat_uikit/theme/tui_theme.dart';
class TIMUIKitProfileUserInfoCardNarrow extends TIMUIKitStatelessWidget {
/// User info
final V2TimUserFullInfo? userInfo;
final bool isJumpToPersonalProfile;
final VoidCallback? onClickAvatar;
/// If shows the arrow icon on the right
final bool showArrowRightIcon;
TIMUIKitProfileUserInfoCardNarrow(
{Key? key,
this.onClickAvatar,
this.userInfo,
@Deprecated(
"This info card can no longer navigate to default personal profile page automatically, please deal with it manually.")
this.isJumpToPersonalProfile = false,
this.showArrowRightIcon = false})
: super(key: key);
@override
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
final TUITheme theme = value.theme;
final faceUrl = userInfo?.faceUrl ?? "";
final nickName = userInfo?.nickName ?? "";
final signature = userInfo?.selfSignature;
final showName = nickName != "" ? nickName : userInfo?.userID;
final option1 = signature;
final signatureText =
option1 != null ? TIM_t_para("个性签名: {{option1}}", "个性签名: $option1")(option1: option1) : TIM_t("暂无个性签名");
return Container(
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
color: Colors.white,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(
width: 48,
height: 48,
child: GestureDetector(
onTap: onClickAvatar,
child: Avatar(
faceUrl: faceUrl,
isShowBigWhenClick: onClickAvatar == null,
showName: showName ?? "",
type: 1,
),
),
),
const SizedBox(
width: 12,
),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
child: SelectableText(
showName ?? "",
style: const TextStyle(fontSize: 18, color: Colors.black),
),
),
Container(
margin: const EdgeInsets.symmetric(vertical: 4),
child: Row(
children: [
Text(
"ID: ",
style: TextStyle(fontSize: 13, color: theme.weakTextColor),
),
SelectableText(
userInfo?.userID ?? "",
style: TextStyle(fontSize: 13, color: theme.weakTextColor),
),
],
),
),
SelectableText(signatureText, style: TextStyle(fontSize: 13, color: theme.weakTextColor))
],
),
),
showArrowRightIcon ? const Icon(Icons.keyboard_arrow_right) : Container()
],
),
);
}
}
@@ -0,0 +1,102 @@
import 'package:flutter/material.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_user_full_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_user_full_info.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/ui/widgets/avatar.dart';
import 'package:tencent_cloud_chat_uikit/theme/tui_theme.dart';
import 'package:tencent_cloud_chat_uikit/theme/color.dart';
class TIMUIKitProfileUserInfoCardWide extends TIMUIKitStatelessWidget {
/// User info
final V2TimUserFullInfo? userInfo;
final bool isJumpToPersonalProfile;
final VoidCallback? onClickAvatar;
/// If shows the arrow icon on the right
final bool showArrowRightIcon;
TIMUIKitProfileUserInfoCardWide(
{Key? key,
this.userInfo,
this.onClickAvatar,
@Deprecated(
"This info card can no longer navigate to default personal profile page automatically, please deal with it manually.")
this.isJumpToPersonalProfile = false,
this.showArrowRightIcon = false})
: super(key: key);
@override
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
final TUITheme theme = value.theme;
final faceUrl = userInfo?.faceUrl ?? "";
final nickName = userInfo?.nickName ?? "";
final signature = userInfo?.selfSignature;
final showName = nickName != "" ? nickName : userInfo?.userID;
return Container(
padding: const EdgeInsets.only(bottom: 10, left: 16, right: 16),
color: Colors.white,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
child: SelectableText(
showName ?? "",
style: const TextStyle(fontSize: 20, color: Colors.black),
),
margin: const EdgeInsets.only(right: 10),
),
Row(
children: [
Text(
"ID: ",
style: TextStyle(fontSize: 12, color: theme.weakTextColor),
),
Expanded(
child: SelectableText(
userInfo?.userID ?? "",
style: TextStyle(fontSize: 12, color: theme.weakTextColor),
)),
],
),
if (signature != null)
Container(
margin: const EdgeInsets.only(top: 18),
child: SelectableText(signature, style: TextStyle(fontSize: 14, color: hexToColor("7f7f7f"))),
)
],
),
),
Row(
children: [
const SizedBox(
width: 40,
),
SizedBox(
width: 80,
height: 80,
child: InkWell(
onTap: onClickAvatar,
child: Avatar(
faceUrl: faceUrl,
isShowBigWhenClick: onClickAvatar == null,
showName: showName ?? "",
type: 1,
),
),
),
showArrowRightIcon ? const Icon(Icons.keyboard_arrow_right) : Container()
],
)
],
),
);
}
}
@@ -0,0 +1,395 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:intl/intl.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_friend_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_friend_info.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_user_full_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_user_full_info.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_class.dart';
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/screen_utils.dart';
import 'package:tencent_cloud_chat_uikit/ui/widgets/avatar.dart';
import 'package:tencent_cloud_chat_uikit/theme/color.dart';
import 'package:tencent_cloud_chat_uikit/theme/tui_theme.dart';
class TIMUIKitProfileWidget extends TIMUIKitClass {
static final bool isDesktopScreen = TUIKitScreenUtils.getFormFactor() == DeviceType.Desktop;
static Widget operationDivider({Color? color, double? height, EdgeInsetsGeometry? margin}) {
return Container(
color: color,
margin: margin,
height: height ?? 10,
);
}
/// Remarks
static Widget remarkBar(BuildContext context, String remark, Function({Offset? offset, String? initText})? handleTap,
bool smallCardMode) {
final GlobalKey key = GlobalKey();
return InkWell(
onTapDown: (details) {
if (handleTap != null) {
handleTap(
offset: Offset(min(details.globalPosition.dx, MediaQuery.of(context).size.width - 400),
min(details.globalPosition.dy, MediaQuery.of(context).size.height - 100)),
initText: remark);
}
},
child: TIMUIKitOperationItem(
smallCardMode: smallCardMode,
itemBoxKey: key,
isEmpty: remark.isEmpty,
wideEditText: TIM_t("设置备注名"),
operationName: TIM_t("备注名"),
operationRightWidget: Text(remark, textAlign: isDesktopScreen ? null : TextAlign.end),
),
);
}
/// add to block list
static Widget addToBlackListBar(
bool value, BuildContext context, Function(bool value)? onChanged, bool smallCardMode) {
return TIMUIKitOperationItem(
smallCardMode: smallCardMode,
isEmpty: false,
operationName: TIM_t("加入黑名单"),
type: "switch",
operationValue: value,
onSwitchChange: (value) {
if (onChanged != null) {
onChanged(value);
}
},
);
}
/// pin the conversation to the top
static Widget pinConversationBar(
bool value, BuildContext context, Function(bool value)? onChanged, bool smallCardMode) {
return TIMUIKitOperationItem(
smallCardMode: smallCardMode,
isEmpty: false,
operationName: TIM_t("置顶聊天"),
type: "switch",
operationValue: value,
onSwitchChange: (value) {
if (onChanged != null) {
onChanged(value);
}
},
);
}
/// message disturb
static Widget messageDisturb(
BuildContext context, bool isDisturb, Function(bool value)? onChanged, bool smallCardMode) {
return TIMUIKitOperationItem(
smallCardMode: smallCardMode,
isEmpty: false,
operationName: TIM_t("消息免打扰"),
type: "switch",
operationValue: isDisturb,
onSwitchChange: (value) {
if (onChanged != null) {
onChanged(value);
}
},
);
}
static Widget operationItem(
{required String operationName,
required String type,
bool? operationValue,
String? operationText,
required bool isEmpty,
void Function(bool newValue)? onSwitchChange,
required bool smallCardMode}) {
return TIMUIKitOperationItem(
smallCardMode: smallCardMode,
isEmpty: isEmpty,
operationName: operationName,
type: type,
operationRightWidget: Text(operationText ?? "", textAlign: isDesktopScreen ? null : TextAlign.end),
operationValue: operationValue,
onSwitchChange: onSwitchChange,
);
}
/// find history message
static Widget searchBar(
BuildContext context,
V2TimConversation conversation,
bool smallCardMode, {
Function()? handleTap,
}) {
return InkWell(
onTap: () {
if (handleTap != null) {
handleTap();
}
},
child: TIMUIKitOperationItem(
isEmpty: true,
wideEditText: TIM_t("立即搜索"),
operationName: TIM_t("查找聊天内容"),
),
);
}
/// portrait
static Widget portraitBar(Widget portraitWidget, bool smallCardMode) {
return SizedBox(
child: TIMUIKitOperationItem(
smallCardMode: smallCardMode,
isEmpty: false,
operationName: TIM_t("头像"),
operationRightWidget: portraitWidget,
showAllowEditStatus: false,
),
);
}
/// defaultPortraitWidget
static Widget defaultPortraitWidget(V2TimUserFullInfo? userInfo, bool smallCardMode) {
return SizedBox(
width: 48,
height: 48,
child: userInfo != null
? Avatar(
faceUrl: userInfo.faceUrl ?? "",
showName: userInfo.nickName ?? "",
type: 1,
)
: Container(),
);
}
/// nickname
static Widget nicknameBar(
String nickName,
bool smallCardMode,
) {
return SizedBox(
child: TIMUIKitOperationItem(
smallCardMode: smallCardMode,
isEmpty: nickName.isEmpty,
showAllowEditStatus: false,
operationName: TIM_t("昵称"),
operationRightWidget: Text(nickName, textAlign: isDesktopScreen ? null : TextAlign.end),
),
);
}
/// user account
static Widget userAccountBar(String userNum, bool smallCardMode) {
return SizedBox(
child: TIMUIKitOperationItem(
smallCardMode: smallCardMode,
isEmpty: false,
showAllowEditStatus: false,
operationName: TIM_t("账号"),
operationRightWidget: SelectableText(userNum, textAlign: isDesktopScreen ? null : TextAlign.end),
),
);
}
/// signature
static Widget signatureBar(String signature, bool smallCardMode) {
return SizedBox(
child: TIMUIKitOperationItem(
smallCardMode: smallCardMode,
isEmpty: false,
showAllowEditStatus: false,
operationName: TIM_t("个性签名"),
operationRightWidget: Text(signature, textAlign: isDesktopScreen ? null : TextAlign.end),
),
);
}
/// gender
static Widget genderBar(int gender, bool smallCardMode) {
Map genderMap = {
0: TIM_t("未填写"),
1: TIM_t("男"),
2: TIM_t("女"),
};
return SizedBox(
child: TIMUIKitOperationItem(
smallCardMode: smallCardMode,
isEmpty: false,
showAllowEditStatus: false,
operationName: TIM_t("性别"),
operationRightWidget: Text(genderMap[gender], textAlign: isDesktopScreen ? null : TextAlign.end),
),
);
}
/// gender
static Widget genderBarWithArrow(int gender, bool smallCardMode) {
Map genderMap = {
0: TIM_t("未填写"),
1: TIM_t("男"),
2: TIM_t("女"),
};
return SizedBox(
child: TIMUIKitOperationItem(
smallCardMode: smallCardMode,
isEmpty: false,
operationName: TIM_t("性别"),
operationRightWidget: Text(genderMap[gender], textAlign: isDesktopScreen ? null : TextAlign.end),
),
);
}
/// birthday
static Widget birthdayBar(int? birthday, bool smallCardMode) {
try {
final date = DateTime.parse(birthday.toString());
DateFormat formatter = DateFormat('yyyy-MM-dd');
return TIMUIKitOperationItem(
smallCardMode: smallCardMode,
isEmpty: false,
operationName: TIM_t("生日"),
operationRightWidget: Text(formatter.format(date), textAlign: isDesktopScreen ? null : TextAlign.end),
);
} catch (e) {
return TIMUIKitOperationItem(
smallCardMode: smallCardMode,
isEmpty: false,
operationName: TIM_t("生日"),
operationRightWidget: Text(TIM_t("未填写"), textAlign: isDesktopScreen ? null : TextAlign.end),
);
}
}
/// default button area
static Widget addAndDeleteArea(
V2TimFriendInfo friendInfo,
V2TimConversation conversation,
int friendType,
bool isDisturb,
bool isBlocked,
TUITheme theme,
VoidCallback handleAddFriend,
VoidCallback handleDeleteFriend,
bool smallCardMode) {
_buildDeleteFriend(V2TimConversation conversation, theme) {
return InkWell(
onTap: () {
handleDeleteFriend();
},
child: Container(
alignment: Alignment.center,
padding: const EdgeInsets.symmetric(vertical: 15),
decoration:
BoxDecoration(color: Colors.white, border: Border(bottom: BorderSide(color: theme.weakDividerColor))),
child: Text(
TIM_t("删除好友"),
style: TextStyle(color: theme.cautionColor, fontSize: 17),
),
),
);
}
_buildAddOperation() {
return Container(
alignment: Alignment.center,
// padding: const EdgeInsets.symmetric(vertical: 15),
decoration: BoxDecoration(
color: Colors.white,
border: Border(bottom: BorderSide(color: theme.weakDividerColor ?? CommonColor.weakDividerColor))),
child: Row(children: [
Expanded(
child: TextButton(
child: Text(TIM_t("加为好友"), style: TextStyle(color: theme.primaryColor, fontSize: 17)),
onPressed: () {
handleAddFriend();
}),
)
]),
);
}
return Column(
children: [
if (friendType != 0) _buildDeleteFriend(conversation, theme),
if (friendType == 0 && !isBlocked) _buildAddOperation()
],
);
}
static Widget wideButton({
required VoidCallback onPressed,
required String text,
required Color color,
required bool smallCardMode,
EdgeInsets? margin,
}) {
return Container(
margin: margin ?? const EdgeInsets.symmetric(vertical: 10),
child: smallCardMode
? OutlinedButton(
onPressed: onPressed,
child: Text(
text,
style: TextStyle(color: color),
),
style: ButtonStyle(
minimumSize: MaterialStateProperty.all<Size>(const Size(160, 40)),
))
: ElevatedButton(
onPressed: onPressed,
child: Text(text),
style: ButtonStyle(
minimumSize: MaterialStateProperty.all<Size>(const Size(180, 46)),
backgroundColor: MaterialStateProperty.all<Color>(color)),
),
);
}
/// default button area
static Widget addAndDeleteAreaWide(
V2TimFriendInfo friendInfo,
V2TimConversation conversation,
int friendType,
bool isDisturb,
bool isBlocked,
TUITheme theme,
VoidCallback handleAddFriend,
VoidCallback handleDeleteFriend,
bool smallCardMode,
) {
_buildDeleteFriend(V2TimConversation conversation, theme) {
return wideButton(
smallCardMode: smallCardMode,
onPressed: () {
handleDeleteFriend();
},
color: theme.cautionColor ?? Colors.red,
text: TIM_t("删除好友"),
);
}
_buildAddOperation() {
return wideButton(
smallCardMode: smallCardMode,
onPressed: handleAddFriend,
color: theme.primaryColor ?? hexToColor("3e4b67"),
text: TIM_t("加为好友"),
);
}
return Column(
children: [
if (friendType != 0) _buildDeleteFriend(conversation, theme),
if (friendType == 0 && !isBlocked) _buildAddOperation()
],
);
}
}