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,48 @@
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_statelesswidget.dart';
import 'package:tencent_cloud_chat_uikit/theme/tui_theme.dart';
import 'package:tencent_cloud_chat_uikit/theme/color.dart';
class TIMUIKitSearchFolder extends TIMUIKitStatelessWidget {
final String folderName;
final List<Widget> children;
TIMUIKitSearchFolder(
{Key? key, required this.folderName, required this.children})
: super(key: key);
@override
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
final TUITheme theme = value.theme;
return Container(
margin: const EdgeInsets.only(bottom: 16),
padding: const EdgeInsets.fromLTRB(16, 0, 16, 0),
width: MediaQuery.of(context).size.width,
decoration: const BoxDecoration(
color: Colors.white,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(
border: Border(
bottom:
BorderSide(color: hexToColor("DBDBDB"), width: 0.5))),
padding: const EdgeInsets.fromLTRB(0, 6, 0, 6),
child: Text(
folderName,
style: TextStyle(
color: theme.weakTextColor, height: 1.5, fontSize: 14),
),
),
...children
],
),
);
}
}
@@ -0,0 +1,112 @@
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_base.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_statelesswidget.dart';
import 'package:tencent_cloud_chat_uikit/theme/tui_theme.dart';
enum SearchType { contact, group, history }
class TIMUIKitSearchIndicator extends TIMUIKitStatelessWidget {
final List<SearchType> typeList;
final ValueChanged<List<SearchType>> onChange;
TIMUIKitSearchIndicator(
{required this.typeList, required this.onChange, Key? key})
: super(key: key);
final titleMap = {
SearchType.contact: "联系人",
SearchType.group: "群聊",
SearchType.history: "聊天记录"
};
Widget renderItemBox(
IconData icon, SearchType item, bool isSelect, TUITheme theme) {
return InkWell(
onTap: () {
if (isSelect) {
typeList.remove(item);
} else {
typeList.add(item);
}
onChange(typeList);
},
child: Container(
padding: const EdgeInsets.all(6),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Stack(
children: [
Padding(
padding: const EdgeInsets.all(6),
child: Icon(
icon,
color: theme.weakTextColor,
size: 30,
),
),
if (isSelect)
Positioned(
right: 0,
bottom: 0,
child: Container(
height: 16,
width: 16,
decoration: BoxDecoration(shape: BoxShape.circle, color: theme.primaryColor),
child: const Icon(
Icons.check,
size: 8,
color: Colors.white,
),
))
],
),
const SizedBox(height: 4),
Text(
TIM_t(titleMap[item]!),
style: TextStyle(color: theme.textColor, fontSize: 13),
)
],
),
),
);
}
@override
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
final theme = value.theme;
return Container(
padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 20),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Expanded(
child: Text(TIM_t("搜索指定内容"),
style: TextStyle(color: theme.weakTextColor, fontSize: 12)),
)
],
),
const SizedBox(height: 1),
Divider(thickness: 0.8, color: theme.weakDividerColor),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.max,
children: [
renderItemBox(Icons.person, SearchType.contact,
typeList.contains(SearchType.contact), theme),
renderItemBox(Icons.people, SearchType.group,
typeList.contains(SearchType.group), theme),
renderItemBox(Icons.message, SearchType.history,
typeList.contains(SearchType.history), theme),
],
)
],
),
);
}
}
@@ -0,0 +1,150 @@
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_state.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/screen_utils.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';
class TIMUIKitSearchInput extends StatefulWidget {
final ValueChanged<String> onChange;
final String? initValue;
final TextEditingController? controller;
final Widget? prefixIcon;
final Widget? prefixText;
final bool? isAutoFocus;
final FocusNode focusNode;
const TIMUIKitSearchInput({
required this.onChange,
this.initValue,
this.controller,
Key? key,
this.prefixIcon,
this.isAutoFocus = true,
this.prefixText,
required this.focusNode,
}) : super(key: key);
@override
State<StatefulWidget> createState() => TIMUIKitSearchInputState();
}
class TIMUIKitSearchInputState extends TIMUIKitState<TIMUIKitSearchInput> {
late TextEditingController textEditingController =
widget.controller ?? TextEditingController();
bool isEmptyInput = true;
@override
void initState() {
super.initState();
textEditingController.text = widget.initValue ?? "";
isEmptyInput = textEditingController.text.isEmpty;
}
hideAllPanel() {
widget.focusNode.unfocus();
}
@override
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
final TUITheme theme = value.theme;
final isDesktopScreen = TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop;
return Container(
// height: 64,
padding: EdgeInsets.fromLTRB(16, isDesktopScreen ? 16 : 8, 16, 16),
margin: isDesktopScreen ? const EdgeInsets.only(bottom: 2) : null,
decoration: BoxDecoration(
color: isDesktopScreen
? theme.wideBackgroundColor
: theme.primaryColor,
boxShadow: [
BoxShadow(
color: theme.weakBackgroundColor ?? hexToColor("E6E9EB"),
offset: const Offset(0.0, 2.0),
)
]
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: ConstrainedBox(
constraints: BoxConstraints(maxHeight: isDesktopScreen ? 30 : 36),
child: TextField(
autofocus: widget.isAutoFocus ?? true,
onChanged: (value) async {
final trimValue = value.trim();
final isEmpty = trimValue.isEmpty;
if(isEmpty != isEmptyInput){
setState(() {
isEmptyInput = isEmpty ? true : false;
});
}
widget.onChange(trimValue);
},
keyboardType: TextInputType.text,
textInputAction: TextInputAction.search,
maxLines: 4,
minLines: 1,
focusNode: widget.focusNode,
controller: textEditingController,
textAlignVertical: TextAlignVertical.center,
textAlign: TextAlign.start,
style: isDesktopScreen ? const TextStyle(
fontSize: 12
) : null,
decoration: InputDecoration(
contentPadding: const EdgeInsets.all(0),
border: const OutlineInputBorder(borderSide: BorderSide.none),
hintStyle: TextStyle(
fontSize: isDesktopScreen ? 12 : 14,
color: hexToColor("CCCCCC"),
),
fillColor: isDesktopScreen ? hexToColor("f3f3f4") : Colors.white,
filled: true,
isDense: true,
hintText: TIM_t("搜索"),
prefix: widget.prefixText != null
? Container(
margin: const EdgeInsets.only(right: 8),
child: ConstrainedBox(
constraints: BoxConstraints(
maxWidth:
MediaQuery.of(context).size.width * 0.2),
child: widget.prefixText,
),
)
: null,
prefixIcon: widget.prefixIcon,
suffixIcon: isEmptyInput
? null
: IconButton(
onPressed: () {
textEditingController.clear();
setState(() {
isEmptyInput = true;
});
widget.onChange("");
},
icon: Icon(Icons.cancel, color: hexToColor("979797")),
),
),
),
)),
if(!isDesktopScreen) Container(
margin: const EdgeInsets.fromLTRB(10, 0, 0, 0),
child: GestureDetector(
onTap: () {
Navigator.pop(context);
},
child: Text(TIM_t("取消"),
style: const TextStyle(
color: Colors.white,
)),
))
],
),
);
}
}
@@ -0,0 +1,120 @@
import 'package:flutter/material.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_statelesswidget.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/screen_utils.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitSearch/tim_uikit_search_item_wide.dart';
import 'package:tencent_cloud_chat_uikit/ui/widgets/avatar.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';
class TIMUIKitSearchItem extends TIMUIKitStatelessWidget {
final String faceUrl;
final String showName;
final String lineOne;
final String? lineOneRight;
final String? lineTwo;
final VoidCallback? onClick;
TIMUIKitSearchItem(
{Key? key,
required this.faceUrl,
required this.showName,
required this.lineOne,
this.lineTwo,
this.lineOneRight,
this.onClick})
: super(key: key);
_renderLineOneRight(String? text, TUITheme theme) {
if (text != null) {
return Text(text,
style: TextStyle(
fontSize: 12,
color: theme.weakTextColor,
));
} else {
return Container();
}
}
_renderLineTwo(String? text, TUITheme theme) {
return (text != null)
? Text(
text,
style: TextStyle(
color: theme.weakTextColor, height: 1.5, fontSize: 14),
)
: Container(
height: 0,
);
}
@override
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
final TUITheme theme = value.theme;
return TUIKitScreenUtils.getDeviceWidget(
context: context,
defaultWidget: GestureDetector(
onTap: onClick,
child: Container(
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(color: hexToColor("DBDBDB"), width: 0.5))),
padding: const EdgeInsets.fromLTRB(0, 12, 0, 12),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
width: 40,
height: 40,
child: Stack(
fit: StackFit.expand,
clipBehavior: Clip.none,
children: [Avatar(faceUrl: faceUrl, showName: showName)],
),
),
Expanded(
child: Container(
margin: const EdgeInsets.only(left: 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
// height: 24,
padding: const EdgeInsets.symmetric(vertical: 2),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
lineOne,
style: TextStyle(
color: theme.darkTextColor,
fontSize: 18.0,
fontWeight: FontWeight.w400),
),
_renderLineOneRight(lineOneRight, theme),
],
),
),
_renderLineTwo(lineTwo, theme),
],
),
))
],
),
),
),
desktopWidget: TIMUIKitSearchWideItem(
lineOneRight: lineOneRight,
key: key,
lineTwo: lineTwo,
onClick: onClick,
faceUrl: faceUrl,
showName: showName,
lineOne: lineOne),
);
}
}
@@ -0,0 +1,69 @@
// ignore_for_file: file_names
import 'package:flutter/material.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';
class TIMUIKitSearchShowALl extends TIMUIKitStatelessWidget {
final String textShow;
final VoidCallback? onClick;
final bool isNeedMoreBottom;
TIMUIKitSearchShowALl(
{Key? key,
this.onClick,
required this.textShow,
this.isNeedMoreBottom = false})
: super(key: key);
@override
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
final isDesktopScreen = TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop;
return InkWell(
onTap: onClick,
child: Container(
decoration: BoxDecoration(
color: Colors.white,
border: Border(
bottom: BorderSide(color: hexToColor("DBDBDB"), width: 0.5))),
padding: EdgeInsets.fromLTRB(0, 8, 0, (isNeedMoreBottom ? 24 : 8)),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(
Icons.search,
color: hexToColor("979797"),
),
Expanded(
child: Container(
margin: const EdgeInsets.only(left: 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
// height: 24,
padding: const EdgeInsets.symmetric(vertical: 2),
child: Text(
textShow,
style: TextStyle(
color: Colors.black,
fontSize: isDesktopScreen ? 14 : 16.0,
fontWeight: FontWeight.w400),
),
),
],
),
)),
Icon(
Icons.expand_more,
color: hexToColor("979797"),
),
],
),
),
);
}
}
@@ -0,0 +1,179 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.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_result.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_friend_info_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_group_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_message_search_result_item.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_message_search_result_item.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart';
import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_search_view_model.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/platform.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitSearch/pureUI/tim_uikit_search_indicator.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitSearch/tim_uikit_search_friend.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitSearch/pureUI/tim_uikit_search_input.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitSearch/tim_uikit_search_group.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitSearch/tim_uikit_search_msg.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/views/TIMUIKitSearch/tim_uikit_search_not_support.dart';
import 'package:tencent_cloud_chat_uikit/theme/color.dart';
class TIMUIKitSearch extends StatefulWidget {
/// the callback after clicking the conversation item
final Function(V2TimConversation, V2TimMessage?) onTapConversation;
/// [Deprecated] : You are supposed to use [TIMUIKitSearchMsgDetail],
/// if you tend to search inside a specific conversation, includes c2c and group.
final V2TimConversation? conversation;
/// [Deprecated] : You are supposed to use [onEnterSearchInConversation],
/// though the effects are the same.
final Function(V2TimConversation conversation, String initKeyword)? onEnterConversation;
/// On click each conversation from 'Chat history' and searching for historical message in it.
final Function(V2TimConversation conversation, String initKeyword)? onEnterSearchInConversation;
final VoidCallback? onBack;
final bool? isAutoFocus;
const TIMUIKitSearch(
{required this.onTapConversation,
Key? key,
@Deprecated(
"You are supposed to use [TIMUIKitSearchMsgDetail], if you tend to search inside a specific conversation, includes c2c and group")
this.conversation,
@Deprecated("You are supposed to use [onEnterSearchInConversation], though the effects are the same.")
this.onEnterConversation,
this.isAutoFocus = true,
this.onEnterSearchInConversation,
this.onBack})
: super(key: key);
@override
State<StatefulWidget> createState() => TIMUIKitSearchState();
}
class TIMUIKitSearchState extends TIMUIKitState<TIMUIKitSearch> {
late TextEditingController textEditingController = TextEditingController();
final model = serviceLocator<TUISearchViewModel>();
final FocusNode focusNode = FocusNode();
GlobalKey<dynamic> inputTextField = GlobalKey();
List<SearchType> searchTypes = [SearchType.group, SearchType.contact, SearchType.history];
@override
void initState() {
super.initState();
model.initSearch();
model.initConversationMsg();
}
@override
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
if (PlatformUtils().isWeb) {
return TIMUIKitSearchNotSupport();
}
return MultiProvider(
providers: [ChangeNotifierProvider.value(value: serviceLocator<TUISearchViewModel>())],
builder: (context, w) {
List<V2TimFriendInfoResult> friendResultList = Provider.of<TUISearchViewModel>(context).friendList ?? [];
List<V2TimMessageSearchResultItem> msgList = Provider.of<TUISearchViewModel>(context).msgList ?? [];
List<V2TimGroupInfo> groupList = Provider.of<TUISearchViewModel>(context).groupList ?? [];
int totalMsgCount = Provider.of<TUISearchViewModel>(context).totalMsgCount;
return GestureDetector(
onTap: () {
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.unfocus();
}
},
child: Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
TIMUIKitSearchInput(
focusNode: focusNode,
key: inputTextField,
isAutoFocus: widget.isAutoFocus,
onChange: (String value) {
model.searchByKey(value);
},
controller: textEditingController,
prefixIcon: Icon(
Icons.search,
size: 16,
color: hexToColor("979797"),
),
),
Expanded(
child: GestureDetector(
child: SingleChildScrollView(
child: Column(
children: [
if ((friendResultList.isEmpty || !(searchTypes.contains(SearchType.contact))) &&
(groupList.isEmpty || !(searchTypes.contains(SearchType.group))) &&
(totalMsgCount == 0 || !(searchTypes.contains(SearchType.history))))
TIMUIKitSearchIndicator(
typeList: searchTypes,
onChange: (list) {
setState(() {
searchTypes = list;
});
},
),
if (searchTypes.contains(SearchType.contact))
TIMUIKitSearchFriend(
onTapConversation: (conversation, message) {
focusNode.unfocus();
Future.delayed(const Duration(milliseconds: 100), () {
widget.onTapConversation(conversation, message);
});
},
friendResultList: friendResultList),
if (searchTypes.contains(SearchType.group))
TIMUIKitSearchGroup(
groupList: groupList,
onTapConversation: (conversation, message) {
focusNode.unfocus();
Future.delayed(const Duration(milliseconds: 100), () {
widget.onTapConversation(conversation, message);
});
},
),
if (searchTypes.contains(SearchType.history))
TIMUIKitSearchMsg(
onTapConversation: widget.onTapConversation,
keyword: textEditingController.text,
totalMsgCount: totalMsgCount,
msgList: msgList,
onEnterConversation: (V2TimConversation conversation, String keyword) {
if (widget.onEnterSearchInConversation != null) {
widget.onEnterSearchInConversation!(conversation, keyword);
} else if (widget.onEnterConversation != null) {
widget.onEnterConversation!(conversation, keyword);
}
},
),
],
),
),
onTap: () {
if (widget.onBack != null) {
widget.onBack!();
}
},
))
],
),
),
);
},
);
}
}
@@ -0,0 +1,89 @@
// ignore_for_file: must_be_immutable
import 'dart:math';
import 'package:flutter/cupertino.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_result.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_friend_info_result.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_state.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitSearch/pureUI/tim_uikit_search_item.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitSearch/pureUI/tim_uikit_search_folder.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_search_view_model.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitSearch/pureUI/tim_uikit_search_showAll.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart';
class TIMUIKitSearchFriend extends StatefulWidget {
List<V2TimFriendInfoResult> friendResultList;
final Function(V2TimConversation, V2TimMessage?) onTapConversation;
TIMUIKitSearchFriend({required this.friendResultList, Key? key, required this.onTapConversation}) : super(key: key);
@override
State<StatefulWidget> createState() => TIMUIKitSearchFriendState();
}
class TIMUIKitSearchFriendState extends TIMUIKitState<TIMUIKitSearchFriend> {
bool isShowAll = false;
int defaultShowLines = 3;
Widget _renderShowALl(int currentLines) {
return (isShowAll == false && currentLines > defaultShowLines)
? TIMUIKitSearchShowALl(
textShow: TIM_t("全部联系人"),
onClick: () => setState(() {
isShowAll = true;
}),
)
: Container();
}
@override
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
List<V2TimConversation?> _conversationList = Provider.of<TUISearchViewModel>(context).conversationList;
List<V2TimFriendInfoResult> filteredFriendResultList = widget.friendResultList.where((friend) {
int index = _conversationList.indexWhere((conv) => friend.friendInfo?.userID == conv?.userID);
return index == -1 ? false : true;
}).toList();
List<V2TimFriendInfoResult> halfFilteredFriendResultList = isShowAll
? filteredFriendResultList
: filteredFriendResultList.sublist(0, min(defaultShowLines, filteredFriendResultList.length));
if (filteredFriendResultList.isNotEmpty) {
return TIMUIKitSearchFolder(folderName: TIM_t("联系人"), children: [
...halfFilteredFriendResultList.map((conv) {
int convIndex = _conversationList.indexWhere((item) => conv.friendInfo?.userID == item?.userID);
V2TimConversation conversation = _conversationList[convIndex]!;
late String? showNickName;
if (conv.friendInfo?.friendRemark != null && conv.friendInfo?.friendRemark != "") {
showNickName = conv.friendInfo?.friendRemark;
} else if (conv.friendInfo?.userProfile?.nickName != null && conv.friendInfo?.userProfile?.nickName != "") {
showNickName = conv.friendInfo?.userProfile?.nickName;
} else {
showNickName = conv.friendInfo?.userID;
}
return TIMUIKitSearchItem(
onClick: () {
widget.onTapConversation(conversation, null);
},
faceUrl: conv.friendInfo?.userProfile?.faceUrl ?? "",
showName: "",
lineOne: conversation.userID!,
lineTwo: TIM_t("昵称") + ":" + showNickName!,
);
}).toList(),
_renderShowALl(filteredFriendResultList.length),
]);
} else {
return Container();
}
}
}
@@ -0,0 +1,79 @@
// ignore_for_file: must_be_immutable
import 'dart:math';
import 'package:flutter/cupertino.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_group_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_group_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_state.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitSearch/pureUI/tim_uikit_search_item.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitSearch/pureUI/tim_uikit_search_folder.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_search_view_model.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitSearch/pureUI/tim_uikit_search_showAll.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart';
class TIMUIKitSearchGroup extends StatefulWidget {
List<V2TimGroupInfo> groupList;
final Function(V2TimConversation, V2TimMessage?) onTapConversation;
TIMUIKitSearchGroup({required this.groupList, Key? key, required this.onTapConversation}) : super(key: key);
@override
State<StatefulWidget> createState() => TIMUIKitSearchGroupState();
}
class TIMUIKitSearchGroupState extends TIMUIKitState<TIMUIKitSearchGroup> {
bool isShowAll = false;
int defaultShowLines = 3;
Widget _renderShowALl(int currentLines) {
return (isShowAll == false && currentLines > defaultShowLines)
? TIMUIKitSearchShowALl(
textShow: TIM_t("全部群聊"),
onClick: () => setState(() {
isShowAll = true;
}),
)
: Container();
}
@override
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
List<V2TimConversation?> _conversationList = Provider.of<TUISearchViewModel>(context).conversationList;
List<V2TimGroupInfo> filteredGroupResultList = widget.groupList.where((group) {
int index = _conversationList.indexWhere((conv) => group.groupID == conv?.groupID);
return index == -1 ? false : true;
}).toList();
List<V2TimGroupInfo> halfFilteredGroupResultList = isShowAll
? filteredGroupResultList
: filteredGroupResultList.sublist(0, min(defaultShowLines, filteredGroupResultList.length));
if (filteredGroupResultList.isNotEmpty) {
return TIMUIKitSearchFolder(folderName: TIM_t("群聊"), children: [
...halfFilteredGroupResultList.map((group) {
int convIndex = _conversationList.indexWhere((item) => group.groupID == item?.groupID);
V2TimConversation conversation = _conversationList[convIndex]!;
return TIMUIKitSearchItem(
onClick: () {
widget.onTapConversation(conversation, null);
},
faceUrl: conversation.faceUrl ?? group.faceUrl ?? "",
showName: "",
lineOne: conversation.showName ?? group.groupName ?? conversation.groupID ?? "",
);
}).toList(),
_renderShowALl(filteredGroupResultList.length),
]);
} else {
return Container();
}
}
}
@@ -0,0 +1,109 @@
import 'package:flutter/material.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/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';
class TIMUIKitSearchWideItem extends TIMUIKitStatelessWidget {
final String faceUrl;
final String showName;
final String lineOne;
final String? lineOneRight;
final String? lineTwo;
final VoidCallback? onClick;
TIMUIKitSearchWideItem(
{Key? key,
required this.faceUrl,
required this.showName,
required this.lineOne,
this.lineTwo,
this.lineOneRight,
this.onClick})
: super(key: key);
_renderLineOneRight(String? text, TUITheme theme) {
if (text != null) {
return Text(text,
style: TextStyle(
fontSize: 10,
color: theme.weakTextColor,
));
} else {
return Container();
}
}
_renderLineTwo(String? text, TUITheme theme) {
return (text != null)
? Container(
margin: const EdgeInsets.only(top: 0),
child: SelectableText(
text,
style: TextStyle(
color: theme.weakTextColor, height: 1.5, fontSize: 12),
),
)
: Container(
height: 0,
);
}
@override
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
final TUITheme theme = value.theme;
return Material(
color: Colors.white,
child: InkWell(
onTap: onClick,
child: Container(
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(color: hexToColor("DBDBDB"), width: 0.5))),
padding: const EdgeInsets.fromLTRB(0, 12, 0, 12),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
width: 30,
height: 30,
child: Avatar(faceUrl: faceUrl, showName: showName, isShowBigWhenClick: false,),
),
Expanded(
child: Container(
margin: const EdgeInsets.only(left: 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
// height: 24,
padding: const EdgeInsets.symmetric(vertical: 2),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(child: Text(
lineOne,
style: TextStyle(
color: theme.darkTextColor,
fontSize: 14.0,
fontWeight: FontWeight.w400),
)),
_renderLineOneRight(lineOneRight, theme),
],
),
),
_renderLineTwo(lineTwo, theme),
],
),
))
],
),
),
),
);
}
}
@@ -0,0 +1,81 @@
// ignore_for_file: must_be_immutable, unused_import
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/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_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_message_search_result_item.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_message_search_result_item.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_statelesswidget.dart';
import 'package:tencent_cloud_chat_uikit/data_services/conversation/conversation_services.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_search_view_model.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitSearch/pureUI/tim_uikit_search_item.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitSearch/pureUI/tim_uikit_search_folder.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitSearch/tim_uikit_search_msg_detail.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitSearch/pureUI/tim_uikit_search_showAll.dart';
import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart';
class TIMUIKitSearchMsg extends TIMUIKitStatelessWidget {
List<V2TimMessageSearchResultItem?> msgList;
int totalMsgCount;
String keyword;
final Function(V2TimConversation, V2TimMessage?) onTapConversation;
final model = serviceLocator<TUISearchViewModel>();
final Function(V2TimConversation, String) onEnterConversation;
TIMUIKitSearchMsg(
{required this.msgList,
required this.keyword,
required this.totalMsgCount,
Key? key,
required this.onTapConversation,
required this.onEnterConversation})
: super(key: key);
Widget _renderShowALl(bool isShowMore) {
return (isShowMore == true)
? TIMUIKitSearchShowALl(
textShow: TIM_t("更多聊天记录"),
onClick: () => {model.searchMsgByKey(keyword, false)},
)
: Container();
}
@override
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
List<V2TimConversation?> _conversationList = Provider.of<TUISearchViewModel>(context).conversationList;
if (msgList.isNotEmpty) {
return TIMUIKitSearchFolder(folderName: TIM_t("聊天记录"), children: [
...msgList.map((conv) {
V2TimConversation? conversation;
final index = _conversationList.indexWhere((item) => item!.conversationID == conv?.conversationID);
if (index > -1) {
conversation = _conversationList[index]!;
}
if (conversation == null) {
return Container();
}
final option1 = conv?.messageCount;
return TIMUIKitSearchItem(
onClick: () async {
onEnterConversation(conversation!, keyword);
},
faceUrl: conversation.faceUrl ?? "",
showName: conversation.showName ?? "",
lineOne: conversation.showName ?? "",
lineTwo: TIM_t_para("{{option1}}条相关聊天记录", "$option1条相关聊天记录")(option1: option1),
);
}).toList(),
_renderShowALl(totalMsgCount > msgList.length)
]);
} else {
return Container();
}
}
}
@@ -0,0 +1,265 @@
import 'dart:convert';
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/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_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_state.dart';
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.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/utils/time_ago.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitSearch/pureUI/tim_uikit_search_input.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitSearch/pureUI/tim_uikit_search_item.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_search_view_model.dart';
import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitSearch/pureUI/tim_uikit_search_showAll.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitSearch/tim_uikit_search_not_support.dart';
import 'package:tencent_cloud_chat_uikit/ui/widgets/avatar.dart';
import 'package:tencent_cloud_chat_uikit/theme/color.dart';
class TIMUIKitSearchMsgDetail extends StatefulWidget {
/// Conversation need search
final V2TimConversation currentConversation;
/// initial keyword
final String keyword;
final List<V2TimMessage>? initMessageList;
/// the callback after clicking each conversation message item
final Function(V2TimConversation, V2TimMessage?) onTapConversation;
final bool? isAutoFocus;
const TIMUIKitSearchMsgDetail(
{Key? key,
this.isAutoFocus = true,
required this.currentConversation,
required this.keyword,
required this.onTapConversation,
this.initMessageList})
: super(key: key);
@override
State<StatefulWidget> createState() => TIMUIKitSearchMsgDetailState();
}
class TIMUIKitSearchMsgDetailState extends TIMUIKitState<TIMUIKitSearchMsgDetail> {
final model = serviceLocator<TUISearchViewModel>();
String keywordState = "";
int currentPage = 0;
final FocusNode focusNode = FocusNode();
final TextEditingController _controller = TextEditingController();
final ScrollController _scrollController = ScrollController();
@override
void initState() {
super.initState();
keywordState = widget.keyword;
updateMsgResult(widget.keyword, true);
}
(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);
}
}
}
String _getMsgElem(V2TimMessage message) {
final msgType = message.elemType;
final revokeStatus = isRevokeMessage(message);
final isRevokedMessage = revokeStatus.$1;
final isAdminRevoke = revokeStatus.$2;
if (isRevokedMessage) {
final isSelf = message.isSelf ?? true;
final option2 = isAdminRevoke ? TIM_t("管理员") : (isSelf ? TIM_t("您") : message.nickName ?? message.sender);
return TIM_t_para("{{option2}}撤回了一条消息", "$option2撤回了一条消息")(option2: option2);
}
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 message.textElem!.text as String;
case MessageElemType.V2TIM_ELEM_TYPE_FACE:
return TIM_t("[表情]");
case MessageElemType.V2TIM_ELEM_TYPE_FILE:
final option1 = message.fileElem!.fileName;
return TIM_t_para("[文件] {{option1}}", "[文件] $option1")(option1: option1);
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 TIM_t("未知消息");
}
}
List<Widget> _renderListMessage(List<V2TimMessage> msgList, BuildContext context, bool isDesktopScreen) {
List<Widget> listWidget = [];
listWidget = msgList.map((message) {
return Container(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 0),
child: TIMUIKitSearchItem(
faceUrl: message.faceUrl ?? "",
showName: TencentUtils.checkString(message.nickName) ??
TencentUtils.checkString(message.userID) ??
message.sender ??
"",
lineOne: TencentUtils.checkString(message.nickName) ??
TencentUtils.checkString(message.userID) ??
message.sender ??
"",
lineOneRight:
(isDesktopScreen && message.timestamp != null) ? TimeAgo().getTimeForMessage(message.timestamp!) : null,
lineTwo: _getMsgElem(message),
onClick: () {
focusNode.unfocus();
widget.onTapConversation(widget.currentConversation, message);
},
),
);
}).toList();
return listWidget;
}
updateMsgResult(String? keyword, bool isNewSearch) {
if (isNewSearch) {
setState(() {
currentPage = 0;
keywordState = keyword!;
});
}
model.getMsgForConversation(keyword ?? keywordState, widget.currentConversation.conversationID, currentPage);
setState(() {
currentPage = currentPage + 1;
});
}
Widget _renderShowALl(bool isShowMore) {
return (isShowMore == true)
? Container(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 0),
decoration: const BoxDecoration(
color: Colors.white,
),
child: TIMUIKitSearchShowALl(
textShow: TIM_t("更多聊天记录"),
onClick: () => {updateMsgResult(null, false)},
),
)
: Container();
}
@override
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
final theme = value.theme;
if (PlatformUtils().isWeb) {
return TIMUIKitSearchNotSupport();
}
return MultiProvider(
providers: [ChangeNotifierProvider.value(value: serviceLocator<TUISearchViewModel>())],
builder: (context, w) {
final isDesktopScreen = TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop;
List<V2TimMessage> currentMsgListForConversation =
Provider.of<TUISearchViewModel>(context).currentMsgListForConversation;
final currentText = _controller.text;
if (currentMsgListForConversation.isEmpty &&
widget.initMessageList != null &&
widget.initMessageList!.isNotEmpty &&
currentText.isEmpty) {
currentMsgListForConversation = widget.initMessageList!;
}
final int totalMsgInConversationCount = Provider.of<TUISearchViewModel>(context).totalMsgInConversationCount;
return GestureDetector(
onTap: () {
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.unfocus();
}
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (isDesktopScreen)
Container(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 0),
child: Row(
children: [
SizedBox(
child: Avatar(faceUrl: widget.currentConversation.faceUrl ?? "", showName: ""),
width: 30,
height: 30,
),
const SizedBox(width: 16),
Text(
widget.currentConversation.showName ?? widget.currentConversation.userID ?? "",
style: TextStyle(
fontSize: 16,
color: theme.darkTextColor,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
),
TIMUIKitSearchInput(
focusNode: focusNode,
controller: _controller,
isAutoFocus: widget.isAutoFocus,
onChange: (String value) {
updateMsgResult(value, true);
},
initValue: widget.keyword,
prefixIcon: Icon(
Icons.search,
size: 16,
color: hexToColor("979797"),
),
),
Expanded(
child: Scrollbar(
controller: _scrollController,
child: ListView(
controller: _scrollController,
children: [
..._renderListMessage(currentMsgListForConversation, context, isDesktopScreen),
_renderShowALl(
keywordState.isNotEmpty && totalMsgInConversationCount > currentMsgListForConversation.length)
],
),
)),
],
),
);
},
);
}
}
@@ -0,0 +1,62 @@
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_base.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_statelesswidget.dart';
import 'package:tencent_cloud_chat_uikit/theme/color.dart';
class TIMUIKitSearchNotSupport extends TIMUIKitStatelessWidget {
TIMUIKitSearchNotSupport({Key? key}) : super(key: key);
@override
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
final theme = value.theme;
return Scaffold(
body: Container(
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(
color: hexToColor("ecf3fe"),
),
child: Stack(
alignment: Alignment.center,
children: [
Container(
// 因为底部有波浪图, icon向上一点,感觉视觉上更协调
margin: const EdgeInsets.only(bottom: 40),
child: Column(
children: [
Text(
TIM_t("Web网页端不支持搜索"),
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 18,
color: theme.darkTextColor,
),
),
Text(
TIM_t("暂时仅限 Android/iOS 端"),
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 18,
color: theme.darkTextColor,
),
),
],
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
),
),
Positioned(
bottom: 0,
child: Image.asset(
"images/logo_bottom.png",
package: 'tencent_cloud_chat_uikit',
fit: BoxFit.fitWidth,
width: MediaQuery.of(context).size.width,
),
)
],
),
),
);
}
}