feat(mobile-next): 导入官方 openim-flutter-demo 固定基线 3.8.3-patch.3 并完成双构建

- 上游: github.com/OpenIMSDK/openim-flutter-demo tag 3.8.3-patch.3
  commit b3dfdb1e8aaeaaf6f0793e10cadd20d5c184a31f
- 并行目录 mobile-next/ 原样导入(含 LICENSE),不覆盖现网 mobile/
- 锁定 Flutter 3.24.5 / Dart 3.5.4 / JDK 17 / Gradle 7.6.3 / AGP 7.3.1
- 实测 Android debug 与 release 构建均成功(见 docs/mobile-next-baseline-import.md)
- 本提交可单独回退:git revert 1db1229(重写前)

Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
编码工程师
2026-08-19 15:24:33 +08:00
co-authored by multica-agent
parent fa8584a73f
commit 8046ec7483
767 changed files with 57658 additions and 0 deletions
@@ -0,0 +1,13 @@
import 'package:get/get.dart';
import 'package:openim_common/openim_common.dart';
import 'chat_logic.dart';
import 'group_setup/group_setup_logic.dart';
class ChatBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => ChatLogic(), tag: GetTags.chat);
Get.lazyPut(() => GroupSetupLogic());
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,10 @@
import 'package:get/get.dart';
import 'chat_setup_logic.dart';
class ChatSetupBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => ChatSetupLogic());
}
}
@@ -0,0 +1,111 @@
import 'dart:async';
import 'package:flutter_openim_sdk/flutter_openim_sdk.dart';
import 'package:get/get.dart';
import 'package:openim_common/openim_common.dart';
import '../../../core/controller/app_controller.dart';
import '../../../core/controller/im_controller.dart';
import '../../../routes/app_navigator.dart';
import '../chat_logic.dart';
class ChatSetupLogic extends GetxController {
final chatLogic = Get.find<ChatLogic>(tag: GetTags.chat);
final appLogic = Get.find<AppController>();
final imLogic = Get.find<IMController>();
late Rx<ConversationInfo> conversationInfo;
late StreamSubscription ccSub;
late StreamSubscription fcSub;
String get conversationID => conversationInfo.value.conversationID;
bool get isPinned => conversationInfo.value.isPinned == true;
@override
void onClose() {
ccSub.cancel();
fcSub.cancel();
super.onClose();
}
@override
void onInit() {
conversationInfo = Rx(Get.arguments['conversationInfo']);
final sourceID = conversationInfo.value.conversationType == ConversationType.single
? conversationInfo.value.userID
: conversationInfo.value.groupID;
OpenIM.iMManager.conversationManager
.getOneConversation(sourceID: sourceID!, sessionType: conversationInfo.value.conversationType!)
.then((value) {
conversationInfo.value = value;
});
ccSub = imLogic.conversationChangedSubject.listen((newList) {
for (var newValue in newList) {
if (newValue.conversationID == conversationID) {
conversationInfo.update((val) {
val?.burnDuration = newValue.burnDuration ?? 30;
val?.isPrivateChat = newValue.isPrivateChat;
val?.isPinned = newValue.isPinned;
val?.recvMsgOpt = newValue.recvMsgOpt;
val?.isMsgDestruct = newValue.isMsgDestruct;
val?.msgDestructTime = newValue.msgDestructTime;
val?.showName = newValue.showName;
});
break;
}
}
});
fcSub = imLogic.friendInfoChangedSubject.listen((value) {
if (conversationInfo.value.userID == value.userID) {
conversationInfo.update((val) {
val?.showName = value.getShowName();
val?.faceURL = value.faceURL;
});
}
});
super.onInit();
}
void toggleTopContacts() async {
await LoadingView.singleton.wrap(
asyncFunction: () => OpenIM.iMManager.conversationManager.pinConversation(
conversationID: conversationID,
isPinned: !isPinned,
),
);
}
void clearChatHistory() async {
var confirm = await Get.dialog(CustomDialog(
title: StrRes.confirmClearChatHistory,
rightText: StrRes.clearAll,
));
if (confirm == true) {
await LoadingView.singleton.wrap(
asyncFunction: () => OpenIM.iMManager.conversationManager.clearConversationAndDeleteAllMsg(
conversationID: conversationID,
),
);
chatLogic.clearAllMessage();
IMViews.showToast(StrRes.clearSuccessfully);
}
}
void createGroup() => AppNavigator.startCreateGroup(defaultCheckedList: [
UserInfo(
userID: conversationInfo.value.userID,
faceURL: conversationInfo.value.faceURL,
nickname: conversationInfo.value.showName,
),
OpenIM.iMManager.userInfo,
]);
void viewUserInfo() => AppNavigator.startUserProfilePane(
userID: conversationInfo.value.userID!,
nickname: conversationInfo.value.showName,
faceURL: conversationInfo.value.faceURL,
);
}
@@ -0,0 +1,154 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:openim_common/openim_common.dart';
import 'chat_setup_logic.dart';
class ChatSetupPage extends StatelessWidget {
final logic = Get.find<ChatSetupLogic>();
ChatSetupPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: TitleBar.back(),
backgroundColor: Styles.c_F8F9FA,
body: SingleChildScrollView(
child: Obx(() => Column(
children: [
_buildBaseInfoView(),
17.verticalSpace,
_buildItemView(
text: StrRes.topContacts,
switchOn: logic.isPinned,
onChanged: (_) => logic.toggleTopContacts(),
showSwitchButton: true,
isTopRadius: true,
),
10.verticalSpace,
_buildItemView(
text: StrRes.clearChatHistory,
textStyle: Styles.ts_FF381F_17sp,
onTap: logic.clearChatHistory,
showRightArrow: true,
isTopRadius: true,
isBottomRadius: true,
),
],
)),
),
);
}
Widget _buildBaseInfoView() => Container(
margin: EdgeInsets.symmetric(horizontal: 10.w, vertical: 10.h),
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 10.h),
decoration: BoxDecoration(
color: Styles.c_FFFFFF,
borderRadius: BorderRadius.circular(6.r),
),
child: Row(
children: [
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: logic.viewUserInfo,
child: SizedBox(
width: 60.w,
child: Column(
children: [
AvatarView(
width: 44.w,
height: 44.h,
text: logic.conversationInfo.value.showName,
url: logic.conversationInfo.value.faceURL,
),
8.verticalSpace,
(logic.conversationInfo.value.showName ?? '').toText
..style = Styles.ts_8E9AB0_14sp
..maxLines = 1
..overflow = TextOverflow.ellipsis,
],
),
),
),
SizedBox(
width: 60.w,
child: Column(
children: [
ImageRes.addFriendTobeGroup.toImage
..width = 44.w
..height = 44.h
..onTap = logic.createGroup,
8.verticalSpace,
''.toText
..style = Styles.ts_8E9AB0_14sp
..maxLines = 1
..overflow = TextOverflow.ellipsis,
],
),
),
],
),
);
Widget _buildItemView({
required String text,
String? hintText,
TextStyle? textStyle,
String? value,
bool switchOn = false,
bool isTopRadius = false,
bool isBottomRadius = false,
bool showRightArrow = false,
bool showSwitchButton = false,
ValueChanged<bool>? onChanged,
Function()? onTap,
}) =>
GestureDetector(
onTap: onTap,
behavior: HitTestBehavior.translucent,
child: Container(
height: hintText == null ? 46.h : 68.h,
margin: EdgeInsets.symmetric(horizontal: 10.w),
padding: EdgeInsets.symmetric(horizontal: 16.w),
decoration: BoxDecoration(
color: Styles.c_FFFFFF,
borderRadius: BorderRadius.only(
topRight: Radius.circular(isTopRadius ? 6.r : 0),
topLeft: Radius.circular(isTopRadius ? 6.r : 0),
bottomLeft: Radius.circular(isBottomRadius ? 6.r : 0),
bottomRight: Radius.circular(isBottomRadius ? 6.r : 0),
),
),
child: Row(
children: [
null != hintText
? Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
text.toText..style = textStyle ?? Styles.ts_0C1C33_17sp,
hintText.toText..style = Styles.ts_8E9AB0_14sp,
],
)
: (text.toText..style = textStyle ?? Styles.ts_0C1C33_17sp),
const Spacer(),
if (null != value) value.toText..style = Styles.ts_8E9AB0_14sp,
if (showSwitchButton)
CupertinoSwitch(
value: switchOn,
activeColor: Styles.c_0089FF,
onChanged: onChanged,
),
if (showRightArrow)
ImageRes.rightArrow.toImage
..width = 24.w
..height = 24.h,
],
),
),
);
}
@@ -0,0 +1,10 @@
import 'package:get/get.dart';
import 'favorite_manage_logic.dart';
class FavoriteManageBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => FavoriteManageLogic());
}
}
@@ -0,0 +1,53 @@
import 'package:get/get.dart';
import 'package:openim_common/openim_common.dart';
import 'package:wechat_assets_picker/wechat_assets_picker.dart';
class FavoriteManageLogic extends GetxController {
var cacheLogic = Get.find<CacheController>();
var isMultiModel = false.obs;
var selectedList = <String>[].obs;
void addFavorite() async {
final List<AssetEntity>? assets = await AssetPicker.pickAssets(
Get.context!,
pickerConfig: const AssetPickerConfig(requestType: RequestType.image),
);
if (null != assets) {
for (var asset in assets) {
var path = (await asset.file)!.path;
var width = asset.width;
var height = asset.height;
switch (asset.type) {
case AssetType.image:
cacheLogic.addFavoriteFromPath(path, width, height);
IMViews.showToast(StrRes.addSuccessfully);
break;
default:
break;
}
}
}
}
void updateSelectedStatus(String url) {
if (selectedList.contains(url)) {
selectedList.remove(url);
} else {
selectedList.add(url);
}
}
void manage() {
isMultiModel.value = !isMultiModel.value;
selectedList.clear();
}
void delete() {
if (selectedList.isNotEmpty) {
cacheLogic.delFavoriteList(selectedList);
selectedList.clear();
}
}
bool isChecked(String url) => selectedList.contains(url);
}
@@ -0,0 +1,98 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:openim_common/openim_common.dart';
import 'package:sprintf/sprintf.dart';
import 'favorite_manage_logic.dart';
class FavoriteManagePage extends StatelessWidget {
final logic = Get.find<FavoriteManageLogic>();
FavoriteManagePage({super.key});
@override
Widget build(BuildContext context) {
return Obx(() => Scaffold(
appBar: TitleBar.back(
title: StrRes.favoriteFace,
right: StrRes.favoriteManage.toText
..onTap = logic.manage
..style = Styles.ts_0C1C33_17sp,
),
body: Column(
children: [
Expanded(
child: GridView.builder(
padding: EdgeInsets.symmetric(
horizontal: 22.w,
vertical: 10.h,
),
itemCount: logic.cacheLogic.urlList.length + 1,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4,
childAspectRatio: 1,
mainAxisSpacing: 22.h,
crossAxisSpacing: 22.w,
),
itemBuilder: (BuildContext context, int index) {
if (index == 0) {
return GestureDetector(
onTap: logic.addFavorite,
child: ImageRes.addFavorite.toImage
..width = 66.w
..height = 66.h,
);
}
var url = logic.cacheLogic.urlList.elementAt(index - 1);
return GestureDetector(
onTap: logic.isMultiModel.value ? () => logic.updateSelectedStatus(url) : null,
child: Stack(
children: [
ImageUtil.networkImage(
url: url,
width: 66.w,
height: 66.h,
cacheWidth: 66.w.toInt(),
fit: BoxFit.cover,
),
if (logic.isMultiModel.value)
Positioned(
right: 4.w,
bottom: 4.h,
child: ChatRadio(checked: logic.isChecked(url)),
),
],
),
);
},
),
),
_buildBottomBar(),
],
),
));
}
Widget _buildBottomBar() => Container(
height: MediaQuery.of(Get.context!).viewPadding.bottom + 48.h,
padding: EdgeInsets.symmetric(horizontal: 16.w),
decoration: BoxDecoration(
border: BorderDirectional(
top: BorderSide(color: Styles.c_E8EAEF, width: 1),
),
),
child: Row(
children: [
sprintf(StrRes.favoriteCount, [logic.cacheLogic.favoriteList.length]).toText..style = Styles.ts_8E9AB0_16sp,
const Spacer(),
if (logic.isMultiModel.value)
GestureDetector(
onTap: logic.delete,
behavior: HitTestBehavior.translucent,
child: sprintf(StrRes.favoriteDel, [logic.selectedList.length]).toText..style = Styles.ts_0089FF_16sp,
),
],
),
);
}
+273
View File
@@ -0,0 +1,273 @@
import 'package:flutter/material.dart';
import 'package:flutter_openim_sdk/flutter_openim_sdk.dart';
import 'package:get/get.dart';
import 'package:openim_common/openim_common.dart';
import '../../widgets/file_download_progress.dart';
import 'chat_logic.dart';
class ChatPage extends StatelessWidget {
final logic = Get.find<ChatLogic>(tag: GetTags.chat);
ChatPage({super.key});
Widget _buildItemView(Message message) => ChatItemView(
key: logic.itemKey(message),
message: message,
textScaleFactor: logic.scaleFactor.value,
allAtMap: logic.getAtMapping(message),
timelineStr: logic.getShowTime(message),
sendStatusSubject: logic.sendStatusSub,
closePopMenuSubject: logic.forceCloseMenuSub,
enabledReadStatus: logic.enabledReadStatus(message),
readingDuration: logic.readTime(message),
isPlayingSound: logic.isPlaySound(message),
showLongPressMenu: !logic.isInvalidGroup,
leftNickname: logic.getNewestNickname(message),
leftFaceUrl: logic.getNewestFaceURL(message),
rightNickname: logic.senderName,
rightFaceUrl: OpenIM.iMManager.userInfo.faceURL,
showLeftNickname: !logic.isSingleChat,
showRightNickname: !logic.isSingleChat,
enabledCopyMenu: logic.showCopyMenu(message),
enabledRevokeMenu: logic.showRevokeMenu(message),
enabledReplyMenu: logic.showReplyMenu(message),
enabledForwardMenu: logic.showForwardMenu(message),
enabledDelMenu: logic.showDelMenu(message),
enabledAddEmojiMenu: logic.showAddEmojiMenu(message),
onFailedToResend: () => logic.failedResend(message),
onPopMenuShowChanged: logic.onPopMenuShowChanged,
onClickItemView: () => logic.parseClickEvent(message),
onTapCopyMenu: () => logic.copy(message),
onTapDelMenu: () => logic.deleteMsg(message),
onTapForwardMenu: () => logic.forward(message),
onTapRevokeMenu: () {
logic.markRevokedMessage(message);
logic.revokeMsgV2(message);
},
onTapAddEmojiMenu: () => logic.addEmoji(message),
visibilityChange: (msg, visible) {
logic.markMessageAsRead(message, visible);
},
onLongPressLeftAvatar: () {
logic.onLongPressLeftAvatar(message);
},
onLongPressRightAvatar: () {},
onTapLeftAvatar: () {
logic.onTapLeftAvatar(message);
},
onVisibleTrulyText: (text) {
logic.copyTextMap[message.clientMsgID] = text;
},
customTypeBuilder: _buildCustomTypeItemView,
fileDownloadProgressView: FileDownloadProgressView(message),
patterns: <MatchPattern>[
MatchPattern(
type: PatternType.email,
onTap: logic.clickLinkText,
),
MatchPattern(
type: PatternType.url,
onTap: logic.clickLinkText,
),
MatchPattern(
type: PatternType.mobile,
onTap: logic.clickLinkText,
),
MatchPattern(
type: PatternType.tel,
onTap: logic.clickLinkText,
),
],
mediaItemBuilder: (context, message) {
return _buildMediaItem(context, message);
},
onTapUserProfile: handleUserProfileTap,
);
void handleUserProfileTap(({String userID, String name, String? faceURL, String? groupID}) userProfile) {
final userInfo = UserInfo(userID: userProfile.userID, nickname: userProfile.name, faceURL: userProfile.faceURL);
logic.viewUserInfo(userInfo);
}
Widget? _buildMediaItem(BuildContext context, Message message) {
if (message.contentType != MessageType.picture && message.contentType != MessageType.video) {
return null;
}
return GestureDetector(
onTap: () async {
try {
logic.stopVoice();
IMUtils.previewMediaFile(
context: context,
message: message,
onAutoPlay: (index) {
return !logic.playOnce;
},
muted: logic.rtcIsBusy,
onPageChanged: (index) {
logic.playOnce = true;
},
onOperate: (type) {
if (type == OperateType.forward) {
logic.forward(message);
}
}).then((value) {
logic.playOnce = false;
});
} catch (e) {
IMViews.showToast(e.toString());
}
},
child: Hero(
tag: message.clientMsgID!,
child: _buildMediaContent(message),
placeholderBuilder: (BuildContext context, Size heroSize, Widget child) => child,
),
);
}
Widget _buildMediaContent(Message message) {
final isOutgoing = message.sendID == OpenIM.iMManager.userID;
if (message.isVideoType) {
return ChatVideoView(
isISend: isOutgoing,
message: message,
);
} else {
return ChatPictureView(
isISend: isOutgoing,
message: message,
);
}
}
CustomTypeInfo? _buildCustomTypeItemView(_, Message message) {
final data = IMUtils.parseCustomMessage(message);
if (null != data) {
final viewType = data['viewType'];
if (viewType == CustomMessageType.call) {
final type = data['type'];
final content = data['content'];
final view = ChatCallItemView(type: type, content: content);
return CustomTypeInfo(view);
} else if (viewType == CustomMessageType.deletedByFriend || viewType == CustomMessageType.blockedByFriend) {
final view = ChatFriendRelationshipAbnormalHintView(
name: logic.nickname.value,
onTap: logic.sendFriendVerification,
blockedByFriend: viewType == CustomMessageType.blockedByFriend,
deletedByFriend: viewType == CustomMessageType.deletedByFriend,
);
return CustomTypeInfo(view, false, false);
} else if (viewType == CustomMessageType.removedFromGroup) {
return CustomTypeInfo(
StrRes.removedFromGroupHint.toText..style = Styles.ts_8E9AB0_12sp,
false,
false,
);
} else if (viewType == CustomMessageType.groupDisbanded) {
return CustomTypeInfo(
StrRes.groupDisbanded.toText..style = Styles.ts_8E9AB0_12sp,
false,
false,
);
} else if (viewType == CustomMessageType.tag) {
final isISend = message.sendID == OpenIM.iMManager.userID;
if (null != data['textElem']) {
final textElem = TextElem.fromJson(data['textElem']);
return CustomTypeInfo(
ChatText(
text: textElem.content ?? '',
textScaleFactor: logic.scaleFactor.value,
model: TextModel.normal,
),
);
} else if (null != data['soundElem']) {
final soundElem = SoundElem.fromJson(data['soundElem']);
return CustomTypeInfo(
ChatVoiceView(
isISend: isISend,
soundPath: soundElem.soundPath,
soundUrl: soundElem.sourceUrl,
duration: soundElem.duration,
isPlaying: logic.isPlaySound(message),
),
);
}
}
}
return null;
}
Widget? get _groupCallHintView => null;
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: logic.willPop(),
child: ChatVoiceRecordLayout(
onCompleted: logic.sendVoice,
builder: (bar) => Obx(() {
return Scaffold(
backgroundColor: Styles.c_F0F2F6,
appBar: TitleBar.chat(
title: logic.nickname.value,
member: logic.memberStr,
subTitle: logic.subTile,
showOnlineStatus: logic.showOnlineStatus(),
isOnline: logic.onlineStatus.value,
onCloseMultiModel: logic.exit,
onClickMoreBtn: logic.chatSetup,
onClickCallBtn: logic.call,
),
body: SafeArea(
child: WaterMarkBgView(
text: '',
path: logic.background.value,
backgroundColor: Styles.c_FFFFFF,
floatView: _groupCallHintView,
bottomView: ChatInputBox(
forceCloseToolboxSub: logic.forceCloseToolbox,
controller: logic.inputCtrl,
focusNode: logic.focusNode,
isNotInGroup: logic.isInvalidGroup,
directionalText: logic.directionalText(),
onCloseDirectional: logic.onClearDirectional,
onSend: (v) => logic.sendTextMsg(),
toolbox: ChatToolBox(
onTapAlbum: logic.onTapAlbum,
onTapCamera: logic.onTapCamera,
onTapCall: logic.call,
onTapCard: logic.onTapCarte,
onTapFile: logic.onTapFile,
onTapLocation: logic.onTapLocation,
),
voiceRecordBar: bar,
emojiView: ChatEmojiView(
textEditingController: logic.inputCtrl,
favoriteList: logic.cacheLogic.urlList,
onAddFavorite: logic.favoriteManage,
onSelectedFavorite: logic.sendFavoritePic,
),
),
child: ChatListView(
onTouch: () => logic.closeToolbox(),
itemCount: logic.messageList.length,
controller: logic.scrollController,
onScrollToBottomLoad: logic.onScrollToBottomLoad,
onScrollToTop: logic.onScrollToTop,
itemBuilder: (_, index) {
final message = logic.indexOfMessage(index);
return Obx(() => _buildItemView(message));
},
),
),
));
}),
),
);
}
}
@@ -0,0 +1,10 @@
import 'package:get/get.dart';
import 'edit_name_logic.dart';
class EditGroupNameBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => EditGroupNameLogic());
}
}
@@ -0,0 +1,55 @@
import 'package:flutter/material.dart';
import 'package:flutter_openim_sdk/flutter_openim_sdk.dart';
import 'package:get/get.dart';
import 'package:openim/pages/chat/group_setup/group_setup_logic.dart';
import 'package:openim_common/openim_common.dart';
enum EditNameType {
myGroupMemberNickname,
groupNickname,
}
class EditGroupNameLogic extends GetxController {
final groupSetupLogic = Get.find<GroupSetupLogic>();
late TextEditingController inputCtrl;
late EditNameType type;
String? faceUrl;
@override
void onInit() {
type = Get.arguments['type'];
faceUrl = Get.arguments['faceUrl'];
inputCtrl = TextEditingController(
text: type == EditNameType.groupNickname ? groupSetupLogic.groupInfo.value.groupName : groupSetupLogic.myGroupMembersInfo.value.nickname,
);
super.onInit();
}
@override
void onClose() {
inputCtrl.dispose();
super.onClose();
}
String? get title => type == EditNameType.myGroupMemberNickname ? StrRes.myGroupMemberNickname : StrRes.groupName;
void save() async {
if (inputCtrl.text.trim().length > 16) {
return IMViews.showToast(StrRes.createGroupTips);
}
await LoadingView.singleton.wrap(asyncFunction: () async {
if (type == EditNameType.groupNickname) {
await OpenIM.iMManager.groupManager
.setGroupInfo(GroupInfo(groupID: groupSetupLogic.groupInfo.value.groupID, groupName: inputCtrl.text.trim()));
} else if (type == EditNameType.myGroupMemberNickname) {
await OpenIM.iMManager.groupManager.setGroupMemberNickname(
groupID: groupSetupLogic.groupInfo.value.groupID,
userID: OpenIM.iMManager.userID,
groupNickname: inputCtrl.text.trim(),
);
}
});
IMViews.showToast(StrRes.setSuccessfully);
Get.back();
}
}
@@ -0,0 +1,100 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:openim_common/openim_common.dart';
import 'edit_name_logic.dart';
class EditGroupNamePage extends StatelessWidget {
final logic = Get.find<EditGroupNameLogic>();
EditGroupNamePage({super.key});
@override
Widget build(BuildContext context) {
if (logic.type == EditNameType.groupNickname) {
return Scaffold(
backgroundColor: Colors.white,
appBar: TitleBar.back(),
body: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
67.verticalSpace,
StrRes.editGroupName.toText..style = Styles.ts_0C1C33_20sp,
10.verticalSpace,
StrRes.editGroupTips.toText..style = Styles.ts_8E9AB0_15sp,
37.verticalSpace,
Row(
children: [
50.horizontalSpace,
AvatarView(
url: logic.faceUrl,
isGroup: true,
),
Expanded(
child: TextField(
controller: logic.inputCtrl,
style: Styles.ts_0C1C33_17sp,
autofocus: true,
inputFormatters: [LengthLimitingTextInputFormatter(16)],
decoration: InputDecoration(
border: InputBorder.none,
isDense: true,
contentPadding: EdgeInsets.symmetric(
vertical: 10.h,
horizontal: 12.w,
),
),
),
),
40.horizontalSpace,
],
),
const Expanded(child: SizedBox.shrink()),
Button(
margin: EdgeInsets.symmetric(horizontal: 100.w),
text: StrRes.save,
onTap: logic.save,
),
const Expanded(child: SizedBox.shrink()),
],
));
}
return Scaffold(
appBar: TitleBar.back(
title: logic.title,
right: StrRes.save.toText
..style = Styles.ts_0C1C33_17sp
..onTap = logic.save,
),
backgroundColor: Styles.c_FFFFFF,
body: Column(
children: [
22.verticalSpace,
Container(
margin: EdgeInsets.symmetric(horizontal: 10.w),
decoration: BoxDecoration(
color: Styles.c_E8EAEF,
borderRadius: BorderRadius.circular(4.r),
),
child: TextField(
controller: logic.inputCtrl,
style: Styles.ts_0C1C33_17sp,
autofocus: true,
inputFormatters: [LengthLimitingTextInputFormatter(16)],
decoration: InputDecoration(
border: InputBorder.none,
isDense: true,
contentPadding: EdgeInsets.symmetric(
vertical: 10.h,
horizontal: 12.w,
),
),
),
),
],
),
);
}
}
@@ -0,0 +1,10 @@
import 'package:get/get.dart';
import 'group_manage_logic.dart';
class GroupManageBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => GroupManageLogic());
}
}
@@ -0,0 +1,32 @@
import 'package:flutter_openim_sdk/flutter_openim_sdk.dart';
import 'package:get/get.dart';
import 'package:openim/pages/chat/group_setup/group_setup_logic.dart';
import 'package:openim_common/openim_common.dart';
import '../../../../routes/app_navigator.dart';
import '../group_member_list/group_member_list_logic.dart';
class GroupManageLogic extends GetxController {
final groupSetupLogic = Get.find<GroupSetupLogic>();
Rx<GroupInfo> get groupInfo => groupSetupLogic.groupInfo;
void transferGroupOwnerRight() async {
var result = await AppNavigator.startGroupMemberList(
groupInfo: groupInfo.value,
opType: GroupMemberOpType.transferRight,
);
if (result is GroupMembersInfo) {
await LoadingView.singleton.wrap(
asyncFunction: () => OpenIM.iMManager.groupManager.transferGroupOwner(
groupID: groupInfo.value.groupID,
userID: result.userID!,
),
);
groupInfo.update((val) {
val?.ownerUserID = result.userID;
});
Get.back();
}
}
}
@@ -0,0 +1,90 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:openim_common/openim_common.dart';
import 'group_manage_logic.dart';
class GroupManagePage extends StatelessWidget {
final logic = Get.find<GroupManageLogic>();
GroupManagePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: TitleBar.back(
title: StrRes.groupManage,
),
backgroundColor: Styles.c_F8F9FA,
body: Column(
children: [
_buildItemView(
text: StrRes.transferGroupOwnerRight,
onTap: logic.transferGroupOwnerRight,
showRightArrow: true,
isTopRadius: true,
isBottomRadius: true,
),
],
),
);
}
Widget _buildItemView({
required String text,
TextStyle? textStyle,
String? value,
bool switchOn = false,
bool isTopRadius = false,
bool isBottomRadius = false,
bool showRightArrow = false,
bool showSwitchButton = false,
ValueChanged<bool>? onChanged,
Function()? onTap,
}) =>
GestureDetector(
onTap: onTap,
behavior: HitTestBehavior.translucent,
child: Container(
height: 46.h,
margin: EdgeInsets.symmetric(horizontal: 10.w),
padding: EdgeInsets.symmetric(horizontal: 16.w),
decoration: BoxDecoration(
color: Styles.c_FFFFFF,
borderRadius: BorderRadius.only(
topRight: Radius.circular(isTopRadius ? 6.r : 0),
topLeft: Radius.circular(isTopRadius ? 6.r : 0),
bottomLeft: Radius.circular(isBottomRadius ? 6.r : 0),
bottomRight: Radius.circular(isBottomRadius ? 6.r : 0),
),
),
child: Row(
children: [
Expanded(
child: text.toText..style = textStyle ?? Styles.ts_0C1C33_17sp,
),
if (null != value)
ConstrainedBox(
constraints: BoxConstraints(maxWidth: 150.w),
child: value.toText
..style = Styles.ts_8E9AB0_14sp
..maxLines = 1
..overflow = TextOverflow.ellipsis,
),
if (showSwitchButton)
CupertinoSwitch(
value: switchOn,
activeColor: Styles.c_0089FF,
onChanged: onChanged,
),
if (showRightArrow)
ImageRes.rightArrow.toImage
..width = 24.w
..height = 24.h,
],
),
),
);
}
@@ -0,0 +1,13 @@
import 'package:get/get.dart';
import '../group_setup_logic.dart';
import 'group_member_list_logic.dart';
class GroupMemberListBinding extends Bindings {
@override
void dependencies() {
GroupMemberOpType opType = Get.arguments['opType'];
Get.lazyPut(() => GroupMemberListLogic(), tag: opType.name);
Get.lazyPut(() => GroupSetupLogic());
}
}
@@ -0,0 +1,212 @@
import 'dart:async';
import 'dart:math';
import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_openim_sdk/flutter_openim_sdk.dart';
import 'package:get/get.dart';
import 'package:openim/routes/app_navigator.dart';
import 'package:openim_common/openim_common.dart';
import 'package:pull_to_refresh_new/pull_to_refresh.dart';
import 'package:sprintf/sprintf.dart';
import '../../../../core/controller/im_controller.dart';
import '../group_setup_logic.dart';
enum GroupMemberOpType {
view,
transferRight,
call,
at,
del,
}
class GroupMemberListLogic extends GetxController {
final imLogic = Get.find<IMController>();
final groupSetupLogic = Get.find<GroupSetupLogic>();
final controller = RefreshController();
final memberList = <GroupMembersInfo>[].obs;
final checkedList = <GroupMembersInfo>[].obs;
final poController = CustomPopupMenuController();
int count = 500;
final myGroupMemberLevel = 1.obs;
late GroupInfo groupInfo;
late GroupMemberOpType opType;
late StreamSubscription mISub;
bool get isMultiSelMode =>
opType == GroupMemberOpType.call || opType == GroupMemberOpType.at || opType == GroupMemberOpType.del;
bool get excludeSelfFromList =>
opType == GroupMemberOpType.call || opType == GroupMemberOpType.at || opType == GroupMemberOpType.transferRight;
bool get isDelMember => opType == GroupMemberOpType.del;
bool get isAdmin => myGroupMemberLevel.value == GroupRoleLevel.admin;
bool get isOwner => myGroupMemberLevel.value == GroupRoleLevel.owner;
bool get isOwnerOrAdmin => isAdmin || isOwner;
int get maxLength => min(groupInfo.memberCount!, 10);
@override
void onClose() {
mISub.cancel();
super.onClose();
}
@override
void onInit() {
groupInfo = Get.arguments['groupInfo'];
opType = Get.arguments['opType'];
mISub = imLogic.memberInfoChangedSubject.listen(_updateMemberLevel);
super.onInit();
}
@override
void onReady() {
_queryMyGroupMemberLevel();
super.onReady();
}
void _updateMemberLevel(GroupMembersInfo e) {
if (e.groupID == groupInfo.groupID) {
equal(GroupMembersInfo el) => el.userID == e.userID;
final member = memberList.firstWhereOrNull(equal);
if (null != member && e.roleLevel != member.roleLevel) {
member.roleLevel = e.roleLevel;
}
memberList.sort((a, b) {
if (b.roleLevel != a.roleLevel) {
return b.roleLevel!.compareTo(a.roleLevel!);
} else {
return b.joinTime!.compareTo(a.joinTime!);
}
});
}
}
void _queryMyGroupMemberLevel() async {
LoadingView.singleton.wrap(asyncFunction: () async {
final list = await OpenIM.iMManager.groupManager.getGroupMembersInfo(
groupID: groupInfo.groupID,
userIDList: [OpenIM.iMManager.userID],
);
final myInfo = list.firstOrNull;
if (null != myInfo) {
myGroupMemberLevel.value = myInfo.roleLevel ?? 1;
}
await onLoad();
});
}
Future<List<GroupMembersInfo>> _getGroupMembers() {
final result = OpenIM.iMManager.groupManager.getGroupMemberList(
groupID: groupInfo.groupID,
count: count,
offset: memberList.length,
filter: isDelMember ? (isOwner ? 4 : (isAdmin ? 3 : 0)) : 0,
);
count = 100;
return result;
}
onLoad() async {
final list = await _getGroupMembers();
memberList.addAll(list);
if (list.length < count) {
controller.loadNoData();
} else {
controller.loadComplete();
}
}
bool isChecked(GroupMembersInfo membersInfo) => checkedList.contains(membersInfo);
clickMember(GroupMembersInfo membersInfo) async {
if (opType == GroupMemberOpType.transferRight) {
_transferGroupRight(membersInfo);
return;
}
if (isMultiSelMode) {
if (isChecked(membersInfo)) {
checkedList.remove(membersInfo);
} else if (checkedList.length < maxLength) {
checkedList.add(membersInfo);
}
} else {
viewMemberInfo(membersInfo);
}
}
static _transferGroupRight(GroupMembersInfo membersInfo) async {
var confirm = await Get.dialog(CustomDialog(
title: sprintf(StrRes.confirmTransferGroupToUser, [membersInfo.nickname]),
));
if (confirm == true) {
Get.back(result: membersInfo);
}
}
void removeSelectedMember(GroupMembersInfo membersInfo) {
checkedList.remove(membersInfo);
}
viewMemberInfo(GroupMembersInfo membersInfo) => AppNavigator.startUserProfilePane(
userID: membersInfo.userID!,
groupID: membersInfo.groupID,
nickname: membersInfo.nickname,
faceURL: membersInfo.faceURL,
);
void addMember() async {
poController.hideMenu();
await groupSetupLogic.addMember();
refreshData();
}
void refreshData() {
LoadingView.singleton.wrap(asyncFunction: () async {
memberList.clear();
await onLoad();
});
}
void delMember() async {
poController.hideMenu();
await groupSetupLogic.removeMember();
refreshData();
}
void search() async {
final memberInfo = await AppNavigator.startSearchGroupMember(
groupInfo: groupInfo,
opType: opType,
);
if (opType == GroupMemberOpType.transferRight) {
Get.back(result: memberInfo);
} else if (isMultiSelMode) {
clickMember(memberInfo);
}
}
static _buildEveryoneMemberInfo() => GroupMembersInfo(
userID: OpenIM.iMManager.conversationManager.atAllTag,
nickname: StrRes.everyone,
);
void selectEveryone() {
Get.back(result: <GroupMembersInfo>[_buildEveryoneMemberInfo()]);
}
void confirmSelectedMember() {
Get.back(result: checkedList.value);
}
bool hiddenMember(GroupMembersInfo membersInfo) =>
excludeSelfFromList && membersInfo.userID == OpenIM.iMManager.userID;
}
@@ -0,0 +1,275 @@
import 'package:flutter/material.dart';
import 'package:flutter_openim_sdk/flutter_openim_sdk.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:openim_common/openim_common.dart';
import 'package:pull_to_refresh_new/pull_to_refresh.dart';
import 'package:sprintf/sprintf.dart';
import 'group_member_list_logic.dart';
class GroupMemberListPage extends StatelessWidget {
final logic = Get.find<GroupMemberListLogic>(tag: (Get.arguments['opType'] as GroupMemberOpType).name);
GroupMemberListPage({super.key});
@override
Widget build(BuildContext context) {
return Obx(() => Scaffold(
appBar: TitleBar.back(
title: logic.opType == GroupMemberOpType.del ? StrRes.removeGroupMember : StrRes.groupMember,
right: logic.opType == GroupMemberOpType.view
? PopButton(
popCtrl: logic.poController,
horizontalMargin: 1.w,
menus: [
PopMenuInfo(text: StrRes.addMember, onTap: logic.addMember),
if (logic.isOwnerOrAdmin) PopMenuInfo(text: StrRes.delMember, onTap: logic.delMember),
],
child: ImageRes.moreBlack.toImage
..width = 28.w
..height = 28.h)
: null,
),
backgroundColor: Styles.c_F8F9FA,
body: Column(
children: [
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: logic.search,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 10.h),
color: Styles.c_FFFFFF,
child: const SearchBox(),
),
),
if (logic.opType == GroupMemberOpType.at && logic.isOwnerOrAdmin)
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: logic.selectEveryone,
child: Container(
height: 64.h,
color: Styles.c_FFFFFF,
margin: EdgeInsets.symmetric(vertical: 10.h),
padding: EdgeInsets.symmetric(horizontal: 16.w),
alignment: Alignment.centerLeft,
child: Row(
children: [
AvatarView(
width: 44.w,
height: 44.h,
text: '@',
textStyle: Styles.ts_FFFFFF_21sp,
),
10.horizontalSpace,
StrRes.everyone.toText..style = Styles.ts_0C1C33_17sp,
],
),
),
),
Flexible(
child: SmartRefresher(
controller: logic.controller,
onLoading: logic.onLoad,
enablePullDown: false,
enablePullUp: true,
header: IMViews.buildHeader(),
footer: IMViews.buildFooter(),
child: ListView.builder(
itemCount: logic.memberList.length,
itemBuilder: (_, index) => Obx(() => _buildItemView(logic.memberList[index])),
),
),
),
if (logic.isMultiSelMode) _buildCheckedConfirmView(),
],
),
));
}
Widget _buildItemView(GroupMembersInfo membersInfo) => logic.hiddenMember(membersInfo)
? const SizedBox()
: GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () => logic.clickMember(membersInfo),
child: Container(
height: 64.h,
padding: EdgeInsets.symmetric(horizontal: 16.w),
color: Styles.c_FFFFFF,
child: Row(
children: [
if (logic.isMultiSelMode)
Padding(
padding: EdgeInsets.only(right: 15.w),
child: ChatRadio(checked: logic.isChecked(membersInfo)),
),
AvatarView(
url: membersInfo.faceURL,
text: membersInfo.nickname,
),
10.horizontalSpace,
Expanded(
child: (membersInfo.nickname ?? '').toText
..style = Styles.ts_0C1C33_17sp
..maxLines = 1
..overflow = TextOverflow.ellipsis,
),
if (membersInfo.roleLevel == GroupRoleLevel.owner)
StrRes.groupOwner.toText..style = Styles.ts_8E9AB0_17sp,
if (membersInfo.roleLevel == GroupRoleLevel.admin)
StrRes.groupAdmin.toText..style = Styles.ts_8E9AB0_17sp,
],
),
),
);
Widget _buildCheckedConfirmView() => Container(
height: 66.h,
decoration: BoxDecoration(
color: Styles.c_FFFFFF,
boxShadow: [
BoxShadow(
offset: Offset(0, -1.h),
blurRadius: 4.r,
spreadRadius: 1.r,
color: Styles.c_000000_opacity4,
),
],
),
padding: EdgeInsets.symmetric(horizontal: 16.w),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () => Get.bottomSheet(
SelectedMemberListView(),
isScrollControlled: true,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
children: [
sprintf(StrRes.selectedPeopleCount, [logic.checkedList.length]).toText
..style = Styles.ts_0089FF_14sp,
ImageRes.expandUpArrow.toImage
..width = 24.w
..height = 24.h,
],
),
if (logic.checkedList.isNotEmpty) 4.verticalSpace,
logic.checkedList.map((e) => e.nickname ?? '').join('').toText
..style = Styles.ts_8E9AB0_14sp
..maxLines = 1
..overflow = TextOverflow.ellipsis,
],
),
),
),
Button(
height: 40.h,
padding: EdgeInsets.symmetric(horizontal: 14.w),
text: sprintf(StrRes.confirmSelectedPeople, [
logic.checkedList.length,
logic.maxLength,
]),
textStyle: Styles.ts_FFFFFF_14sp,
onTap: logic.confirmSelectedMember,
),
],
),
);
}
class SelectedMemberListView extends StatelessWidget {
SelectedMemberListView({Key? key}) : super(key: key);
final logic = Get.find<GroupMemberListLogic>(tag: (Get.arguments['opType'] as GroupMemberOpType).name);
@override
Widget build(BuildContext context) {
return Container(
constraints: BoxConstraints(maxHeight: 548.h),
decoration: BoxDecoration(
color: Styles.c_FFFFFF,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(6.r),
topRight: Radius.circular(6.r),
),
),
child: Obx(() => Column(
children: [
Container(
padding: EdgeInsets.symmetric(horizontal: 16.w),
decoration: BoxDecoration(
border: BorderDirectional(
bottom: BorderSide(color: Styles.c_E8EAEF, width: 1),
),
),
child: Row(
children: [
sprintf(StrRes.selectedPeopleCount, [logic.checkedList.length]).toText
..style = Styles.ts_0C1C33_17sp_medium,
const Spacer(),
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () => Get.back(),
child: Container(
height: 52.h,
alignment: Alignment.center,
child: StrRes.confirm.toText..style = Styles.ts_0089FF_17sp,
),
),
],
),
),
Expanded(
child: ListView.builder(
itemCount: logic.checkedList.length,
shrinkWrap: true,
itemBuilder: (_, index) => _buildItemView(logic.checkedList[index]),
),
),
],
)),
);
}
Widget _buildItemView(GroupMembersInfo membersInfo) => Container(
height: 64.h,
padding: EdgeInsets.symmetric(horizontal: 16.w),
color: Styles.c_FFFFFF,
child: Row(
children: [
AvatarView(
url: membersInfo.faceURL,
text: membersInfo.nickname,
),
10.horizontalSpace,
Expanded(
child: (membersInfo.nickname ?? '').toText
..style = Styles.ts_0C1C33_17sp
..maxLines = 1
..overflow = TextOverflow.ellipsis,
),
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () => logic.removeSelectedMember(membersInfo),
child: Container(
padding: EdgeInsets.symmetric(horizontal: 13.w, vertical: 4.h),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(2.r),
border: Border.all(
color: Styles.c_E8EAEF,
width: 1,
),
),
child: StrRes.remove.toText..style = Styles.ts_0089FF_17sp,
),
),
],
),
);
}
@@ -0,0 +1,10 @@
import 'package:get/get.dart';
import 'search_group_member_logic.dart';
class SearchGroupMemberBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => SearchGroupMemberLogic());
}
}
@@ -0,0 +1,132 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_openim_sdk/flutter_openim_sdk.dart';
import 'package:get/get.dart';
import 'package:openim/pages/chat/group_setup/group_setup_logic.dart';
import 'package:openim_common/openim_common.dart';
import 'package:pull_to_refresh_new/pull_to_refresh.dart';
import 'package:sprintf/sprintf.dart';
import '../../../../../core/controller/im_controller.dart';
import '../../../../../routes/app_navigator.dart';
import '../group_member_list_logic.dart';
class SearchGroupMemberLogic extends GetxController {
final imLogic = Get.find<IMController>();
final controller = RefreshController();
final focusNode = FocusNode();
final searchCtrl = TextEditingController();
final memberList = <GroupMembersInfo>[].obs;
final count = 100;
late GroupInfo groupInfo;
late GroupMemberOpType opType;
late StreamSubscription mISub;
@override
void onInit() {
groupInfo = Get.arguments['groupInfo'];
opType = Get.arguments['opType'];
searchCtrl.addListener(_clearInput);
mISub = imLogic.memberInfoChangedSubject.listen((e) {
if (e.groupID == groupInfo.groupID) {
final member = memberList.firstWhereOrNull((el) => el.userID == e.userID);
if (null != member && e.roleLevel != member.roleLevel) {
member.roleLevel = e.roleLevel;
memberList.refresh();
}
}
});
super.onInit();
}
@override
void onClose() {
focusNode.dispose();
searchCtrl.dispose();
mISub.cancel();
super.onClose();
}
bool get isSearchNotResult => searchCtrl.text.trim().isNotEmpty && memberList.isEmpty;
_clearInput() {
final key = searchCtrl.text.trim();
if (key.isEmpty) {
memberList.clear();
}
}
Future<List<GroupMembersInfo>> _request(int offset) => LoadingView.singleton.wrap(
asyncFunction: () => OpenIM.iMManager.groupManager.searchGroupMembers(
groupID: groupInfo.groupID,
isSearchMemberNickname: true,
isSearchUserID: true,
keywordList: [searchCtrl.text.trim()],
offset: offset,
count: count,
),
);
search() async {
final key = searchCtrl.text.trim();
if (key.isNotEmpty) {
final list = await _request(0);
memberList.assignAll(list);
if (list.length < count) {
controller.loadNoData();
} else {
controller.loadComplete();
}
}
}
load() async {
final key = searchCtrl.text.trim();
if (key.isNotEmpty) {
final list = await _request(memberList.length);
memberList.addAll(list);
if (list.length < count) {
controller.loadNoData();
} else {
controller.loadComplete();
}
}
}
bool hiddenMembers(GroupMembersInfo info) {
if (opType == GroupMemberOpType.transferRight || opType == GroupMemberOpType.at || opType == GroupMemberOpType.call) {
return info.userID == OpenIM.iMManager.userID;
} else if (opType == GroupMemberOpType.del) {
final logic = Get.find<GroupSetupLogic>();
return logic.isAdmin && info.roleLevel != GroupRoleLevel.member || logic.isOwner && info.roleLevel == GroupRoleLevel.owner;
}
return false;
}
clickMember(GroupMembersInfo membersInfo) {
if (opType == GroupMemberOpType.transferRight) {
_transferGroupRight(membersInfo);
} else if (opType == GroupMemberOpType.at || opType == GroupMemberOpType.call || opType == GroupMemberOpType.del) {
Get.back(result: membersInfo);
} else {
viewMemberInfo(membersInfo);
}
}
static _transferGroupRight(GroupMembersInfo membersInfo) async {
var confirm = await Get.dialog(CustomDialog(
title: sprintf(StrRes.confirmTransferGroupToUser, [membersInfo.nickname]),
));
if (confirm == true) {
Get.back(result: membersInfo);
}
}
viewMemberInfo(GroupMembersInfo membersInfo) => AppNavigator.startUserProfilePane(
userID: membersInfo.userID!,
groupID: membersInfo.groupID,
nickname: membersInfo.nickname,
faceURL: membersInfo.faceURL,
);
}
@@ -0,0 +1,92 @@
import 'package:flutter/material.dart';
import 'package:flutter_openim_sdk/flutter_openim_sdk.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:openim_common/openim_common.dart';
import 'package:pull_to_refresh_new/pull_to_refresh.dart';
import 'package:search_keyword_text/search_keyword_text.dart';
import 'search_group_member_logic.dart';
class SearchGroupMemberPage extends StatelessWidget {
final logic = Get.find<SearchGroupMemberLogic>();
SearchGroupMemberPage({super.key});
@override
Widget build(BuildContext context) {
return TouchCloseSoftKeyboard(
child: Scaffold(
appBar: TitleBar.search(
focusNode: logic.focusNode,
controller: logic.searchCtrl,
onSubmitted: (_) => logic.search(),
onCleared: () => logic.focusNode.requestFocus(),
),
backgroundColor: Styles.c_F8F9FA,
body: Obx(() => logic.isSearchNotResult
? _emptyListView
: SmartRefresher(
controller: logic.controller,
enablePullUp: true,
enablePullDown: false,
footer: IMViews.buildFooter(),
onLoading: logic.load,
child: ListView.builder(
itemCount: logic.memberList.length,
itemBuilder: (_, index) {
final info = logic.memberList.elementAt(index);
if (logic.hiddenMembers(info)) {
return const SizedBox();
} else {
return _buildItemView(info);
}
},
),
)),
),
);
}
Widget _buildItemView(GroupMembersInfo membersInfo) => GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () => logic.clickMember(membersInfo),
child: Container(
height: 64.h,
padding: EdgeInsets.symmetric(horizontal: 16.w),
color: Styles.c_FFFFFF,
child: Row(
children: [
AvatarView(
url: membersInfo.faceURL,
text: membersInfo.nickname,
),
10.horizontalSpace,
Expanded(
child: SearchKeywordText(
text: membersInfo.nickname ?? '',
keyText: logic.searchCtrl.text.trim(),
style: Styles.ts_0C1C33_17sp,
keyStyle: Styles.ts_0089FF_17sp,
),
),
if (membersInfo.roleLevel == GroupRoleLevel.owner)
StrRes.groupOwner.toText..style = Styles.ts_8E9AB0_17sp,
if (membersInfo.roleLevel == GroupRoleLevel.admin)
StrRes.groupAdmin.toText..style = Styles.ts_8E9AB0_17sp,
],
),
),
);
Widget get _emptyListView => SizedBox(
width: 1.sw,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
44.verticalSpace,
StrRes.searchNotFound.toText..style = Styles.ts_8E9AB0_17sp,
],
),
);
}
@@ -0,0 +1,10 @@
import 'package:get/get.dart';
import 'group_qrcode_logic.dart';
class GroupQrcodeBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => GroupQrcodeLogic());
}
}
@@ -0,0 +1,11 @@
import 'package:get/get.dart';
import 'package:openim/pages/chat/group_setup/group_setup_logic.dart';
import 'package:openim_common/openim_common.dart';
class GroupQrcodeLogic extends GetxController {
final groupSetupLogic = Get.find<GroupSetupLogic>();
String buildQRContent() {
return '${Config.groupScheme}${groupSetupLogic.groupInfo.value.groupID}';
}
}
@@ -0,0 +1,98 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:openim_common/openim_common.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'group_qrcode_logic.dart';
class GroupQrcodePage extends StatelessWidget {
final logic = Get.find<GroupQrcodeLogic>();
GroupQrcodePage({super.key});
@override
Widget build(BuildContext context) {
return Obx(
() => Scaffold(
appBar: TitleBar.back(title: StrRes.groupQrcode),
backgroundColor: Styles.c_F8F9FA,
body: Container(
alignment: Alignment.topCenter,
child: Container(
margin: EdgeInsets.only(top: 22.h),
padding: EdgeInsets.symmetric(horizontal: 30.w),
width: 331.w,
height: 460.h,
decoration: BoxDecoration(
color: Styles.c_FFFFFF,
borderRadius: BorderRadius.circular(10.r),
boxShadow: [
BoxShadow(
blurRadius: 7.r,
spreadRadius: 1.r,
color: Styles.c_000000.withOpacity(.08),
),
],
),
child: Stack(
children: [
Positioned(
top: 30.h,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
AvatarView(
width: 48.w,
height: 48.h,
url: logic.groupSetupLogic.groupInfo.value.faceURL,
text: logic.groupSetupLogic.groupInfo.value.groupName,
textStyle: Styles.ts_FFFFFF_14sp,
),
12.horizontalSpace,
ConstrainedBox(
constraints: BoxConstraints(maxWidth: 180.w),
child: (logic.groupSetupLogic.groupInfo.value.groupName ?? '').toText
..style = Styles.ts_0C1C33_20sp
..maxLines = 1
..overflow = TextOverflow.ellipsis,
),
],
),
),
Positioned(
top: 140.h,
width: 272.w,
child: StrRes.groupQrcodeHint.toText
..style = Styles.ts_8E9AB0_15sp
..textAlign = TextAlign.center,
),
Positioned(
top: 183.h,
width: 272.w,
child: Container(
alignment: Alignment.center,
child: Container(
width: 180.w,
height: 180.w,
alignment: Alignment.center,
decoration: BoxDecoration(
color: Styles.c_FFFFFF,
border: Border.all(color: Styles.c_E8EAEF, width: 4.w),
),
child: QrImageView(
data: logic.buildQRContent(),
size: 140.w,
backgroundColor: Styles.c_FFFFFF,
),
),
),
)
],
),
),
),
),
);
}
}
@@ -0,0 +1,10 @@
import 'package:get/get.dart';
import 'group_setup_logic.dart';
class GroupSetupBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => GroupSetupLogic());
}
}
@@ -0,0 +1,438 @@
import 'dart:async';
import 'dart:io';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter_openim_sdk/flutter_openim_sdk.dart';
import 'package:get/get.dart';
import 'package:openim_common/openim_common.dart';
import 'package:synchronized/synchronized.dart';
import 'package:wechat_assets_picker/wechat_assets_picker.dart';
import '../../../core/controller/app_controller.dart';
import '../../../core/controller/im_controller.dart';
import '../../../routes/app_navigator.dart';
import '../../contacts/select_contacts/select_contacts_logic.dart';
import '../../conversation/conversation_logic.dart';
import '../chat_logic.dart';
import 'edit_name/edit_name_logic.dart';
import 'group_member_list/group_member_list_logic.dart';
class GroupSetupLogic extends GetxController {
final imLogic = Get.find<IMController>();
final chatLogic = Get.find<ChatLogic>(tag: GetTags.chat);
final appLogic = Get.find<AppController>();
final conversationLogic = Get.find<ConversationLogic>();
final memberList = <GroupMembersInfo>[].obs;
late Rx<ConversationInfo> conversationInfo;
late Rx<GroupInfo> groupInfo;
late Rx<GroupMembersInfo> myGroupMembersInfo;
late StreamSubscription _guSub;
late StreamSubscription _mASub;
late StreamSubscription _mISub;
late StreamSubscription _mDSub;
late StreamSubscription _ccSub;
late StreamSubscription _jasSub;
late StreamSubscription _jdsSub;
final lock = Lock();
final isJoinedGroup = false.obs;
final avatar = Rx<File?>(null);
@override
void onInit() async {
if (Get.arguments['conversationInfo'] != null) {
conversationInfo = Rx(Get.arguments['conversationInfo']);
} else {
final temp = await OpenIM.iMManager.conversationManager.getOneConversation(
sourceID: chatLogic.conversationInfo.isGroupChat
? chatLogic.conversationInfo.groupID!
: chatLogic.conversationInfo.userID!,
sessionType: chatLogic.conversationInfo.conversationType!);
conversationInfo = Rx(temp);
}
groupInfo = Rx(_defaultGroupInfo);
myGroupMembersInfo = Rx(_defaultMemberInfo);
_ccSub = imLogic.conversationChangedSubject.listen((newList) {
final newValue =
newList.firstWhereOrNull((element) => element.conversationID == conversationInfo.value.conversationID);
if (newValue != null) {
conversationInfo.update((val) {
val?.isPinned = newValue.isPinned;
val?.recvMsgOpt = newValue.recvMsgOpt;
val?.isMsgDestruct = newValue.isMsgDestruct;
val?.msgDestructTime = newValue.msgDestructTime;
});
}
});
_guSub = imLogic.groupInfoUpdatedSubject.listen((value) {
if (value.groupID == groupInfo.value.groupID) {
_updateGroupInfo(value);
}
});
_jasSub = imLogic.joinedGroupAddedSubject.listen((value) {
if (value.groupID == groupInfo.value.groupID) {
isJoinedGroup.value = true;
_queryAllInfo();
}
});
_jdsSub = imLogic.joinedGroupDeletedSubject.listen((value) {
if (value.groupID == groupInfo.value.groupID) {
isJoinedGroup.value = false;
}
});
_mISub = imLogic.memberInfoChangedSubject.listen((e) {
if (e.groupID == groupInfo.value.groupID && e.userID == myGroupMembersInfo.value.userID) {
myGroupMembersInfo.update((val) {
val?.nickname = e.nickname;
val?.roleLevel = e.roleLevel;
});
}
if (e.groupID == groupInfo.value.groupID && e.userID == groupInfo.value.ownerUserID) {
var index = memberList.indexWhere((element) => element.userID == groupInfo.value.ownerUserID);
if (index == -1) {
memberList.insert(0, e);
} else if (index != 0) {
memberList.insert(0, memberList.removeAt(index));
}
}
memberList.sort((a, b) {
if (b.roleLevel != a.roleLevel) {
return b.roleLevel!.compareTo(a.roleLevel!);
} else {
return b.joinTime!.compareTo(a.joinTime!);
}
});
});
_mASub = imLogic.memberAddedSubject.listen((e) async {
if (e.groupID == groupInfo.value.groupID) {
if (e.userID == OpenIM.iMManager.userID) {
isJoinedGroup.value = true;
_queryAllInfo();
} else {
memberList.add(e);
}
}
});
_mDSub = imLogic.memberDeletedSubject.listen((e) {
if (e.groupID == groupInfo.value.groupID) {
if (e.userID == OpenIM.iMManager.userID) {
isJoinedGroup.value = false;
} else {
memberList.removeWhere((element) => element.userID == e.userID);
}
}
});
super.onInit();
}
@override
void onReady() {
_checkIsJoinedGroup();
super.onReady();
}
@override
void onClose() {
_guSub.cancel();
_mASub.cancel();
_mDSub.cancel();
_ccSub.cancel();
_mISub.cancel();
_jdsSub.cancel();
_jasSub.cancel();
super.onClose();
}
get _defaultGroupInfo => GroupInfo(
groupID: conversationInfo.value.groupID!,
groupName: conversationInfo.value.showName,
faceURL: conversationInfo.value.faceURL,
memberCount: 0,
);
get _defaultMemberInfo => GroupMembersInfo(
userID: OpenIM.iMManager.userID,
nickname: OpenIM.iMManager.userInfo.nickname,
);
bool get isOwnerOrAdmin => isOwner || isAdmin;
bool get isAdmin => myGroupMembersInfo.value.roleLevel == GroupRoleLevel.admin;
bool get isNotDisturb => conversationInfo.value.recvMsgOpt != 0;
bool get isOwner => groupInfo.value.ownerUserID == OpenIM.iMManager.userID;
String get conversationID => conversationInfo.value.conversationID;
void _checkIsJoinedGroup() async {
isJoinedGroup.value = await OpenIM.iMManager.groupManager.isJoinedGroup(
groupID: groupInfo.value.groupID,
);
_queryAllInfo();
}
void _queryAllInfo() {
if (isJoinedGroup.value) {
getGroupInfo();
getGroupMembers();
getMyGroupMemberInfo();
}
}
getGroupMembers() async {
var list = await OpenIM.iMManager.groupManager.getGroupMemberList(
groupID: groupInfo.value.groupID,
count: 10,
);
memberList.assignAll(list);
}
getGroupInfo() async {
var list = await OpenIM.iMManager.groupManager.getGroupsInfo(
groupIDList: [groupInfo.value.groupID],
);
var value = list.firstOrNull;
if (null != value) {
_updateGroupInfo(value);
}
}
getMyGroupMemberInfo() async {
final list = await OpenIM.iMManager.groupManager.getGroupMembersInfo(
groupID: groupInfo.value.groupID,
userIDList: [OpenIM.iMManager.userID],
);
final info = list.firstOrNull;
if (null != info) {
myGroupMembersInfo.update((val) {
val?.nickname = info.nickname;
val?.roleLevel = info.roleLevel;
});
}
}
void _updateGroupInfo(GroupInfo value) {
groupInfo.update((val) {
val?.groupName = value.groupName;
val?.faceURL = value.faceURL;
val?.notification = value.notification;
val?.introduction = value.introduction;
val?.memberCount = value.memberCount;
val?.ownerUserID = value.ownerUserID;
val?.status = value.status;
val?.needVerification = value.needVerification;
val?.groupType = value.groupType;
val?.lookMemberInfo = value.lookMemberInfo;
val?.applyMemberFriend = value.applyMemberFriend;
val?.notificationUserID = value.notificationUserID;
val?.notificationUpdateTime = value.notificationUpdateTime;
val?.ex = value.ex;
});
}
void modifyGroupAvatar() async {
final List<AssetEntity>? assets = await AssetPicker.pickAssets(
Get.context!,
pickerConfig: const AssetPickerConfig(maxAssets: 1, requestType: RequestType.image),
);
if (assets != null) {
final file = await assets.first.file;
final result = await IMViews.uCropPic(file!.path);
final path = result['path'];
final url = result['url'];
if (url != null) {
avatar.value = File(path);
await _modifyGroupInfo(faceUrl: url);
groupInfo.update((val) {
val?.faceURL = url;
});
}
}
}
void modifyGroupName(String? faceUrl) => AppNavigator.startEditGroupName(
type: EditNameType.groupNickname,
faceUrl: faceUrl,
);
_modifyGroupInfo({
String? groupName,
String? notification,
String? introduction,
String? faceUrl,
}) =>
OpenIM.iMManager.groupManager.setGroupInfo(GroupInfo(
groupID: groupInfo.value.groupID,
groupName: groupName,
notification: notification,
introduction: introduction,
faceURL: faceUrl,
));
void viewGroupQrcode() => AppNavigator.startGroupQrcode();
void viewGroupMembers() => AppNavigator.startGroupMemberList(
groupInfo: groupInfo.value,
);
void groupManage() => AppNavigator.startGroupManage(
groupInfo: groupInfo.value,
);
void _removeConversation() async {
await OpenIM.iMManager.conversationManager.deleteConversationAndDeleteAllMsg(
conversationID: conversationInfo.value.conversationID,
);
conversationLogic.removeConversation(conversationInfo.value.conversationID);
}
void quitGroup() async {
if (isJoinedGroup.value) {
if (isOwner) {
var confirm = await Get.dialog(CustomDialog(
title: StrRes.dismissGroupHint,
));
if (confirm == true) {
await OpenIM.iMManager.groupManager.dismissGroup(
groupID: groupInfo.value.groupID,
);
} else {
return;
}
} else {
var confirm = await Get.dialog(CustomDialog(
title: StrRes.quitGroupHint,
));
if (confirm == true) {
await OpenIM.iMManager.groupManager.quitGroup(
groupID: groupInfo.value.groupID,
);
} else {
return;
}
}
} else {
_removeConversation();
}
AppNavigator.startBackMain();
}
void copyGroupID() {
IMUtils.copy(text: groupInfo.value.groupID);
}
int length() {
int buttons = isOwnerOrAdmin ? 2 : 1;
return (memberList.length + buttons) > 10 ? 10 : (memberList.length + buttons);
}
Widget itemBuilder({
required int index,
required Widget Function(GroupMembersInfo info) builder,
required Widget Function() addButton,
required Widget Function() delButton,
}) {
var length = isOwnerOrAdmin ? 8 : 9;
if (memberList.length > length) {
if (index < length) {
var info = memberList.elementAt(index);
return builder(info);
} else if (index == length) {
return addButton();
} else {
return delButton();
}
} else {
if (index < memberList.length) {
var info = memberList.elementAt(index);
return builder(info);
} else if (index == memberList.length) {
return addButton();
} else {
return delButton();
}
}
}
void toggleNotDisturb() {
LoadingView.singleton.wrap(
asyncFunction: () => OpenIM.iMManager.conversationManager.setConversationRecvMessageOpt(
conversationID: conversationID,
status: !isNotDisturb ? 2 : 0,
));
}
void clearChatHistory() async {
var confirm = await Get.dialog(CustomDialog(
title: StrRes.confirmClearChatHistory,
rightText: StrRes.clearAll,
));
if (confirm == true) {
await OpenIM.iMManager.conversationManager.clearConversationAndDeleteAllMsg(
conversationID: conversationID,
);
chatLogic.clearAllMessage();
IMViews.showToast(StrRes.clearSuccessfully);
}
}
addMember() async {
final result = await AppNavigator.startSelectContacts(
action: SelAction.addMember,
groupID: groupInfo.value.groupID,
);
final list = IMUtils.convertSelectContactsResultToUserID(result);
if (list is List<String>) {
try {
await LoadingView.singleton.wrap(
asyncFunction: () => OpenIM.iMManager.groupManager.inviteUserToGroup(
groupID: groupInfo.value.groupID,
userIDList: list,
reason: 'Come on baby',
),
);
} catch (_) {}
getGroupMembers();
}
}
removeMember() async {
final list = await AppNavigator.startGroupMemberList(
groupInfo: groupInfo.value,
opType: GroupMemberOpType.del,
);
if (list is List<GroupMembersInfo>) {
var removeUidList = list.map((e) => e.userID!).toList();
try {
await LoadingView.singleton.wrap(
asyncFunction: () => OpenIM.iMManager.groupManager.kickGroupMember(
groupID: groupInfo.value.groupID,
userIDList: removeUidList,
reason: 'Get out baby',
),
);
} catch (_) {}
getGroupMembers();
}
}
void viewMemberInfo(GroupMembersInfo membersInfo) => AppNavigator.startUserProfilePane(
userID: membersInfo.userID!,
nickname: membersInfo.nickname,
faceURL: membersInfo.faceURL,
groupID: membersInfo.groupID,
);
}
@@ -0,0 +1,311 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:openim_common/openim_common.dart';
import 'package:sprintf/sprintf.dart';
import 'group_setup_logic.dart';
class GroupSetupPage extends StatelessWidget {
final logic = Get.find<GroupSetupLogic>();
GroupSetupPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: TitleBar.back(title: StrRes.groupChatSetup),
backgroundColor: Styles.c_F8F9FA,
body: Obx(() => SingleChildScrollView(
child: Column(
children: [
if (logic.isJoinedGroup.value) _buildBaseInfoView(),
if (logic.isJoinedGroup.value) _buildMemberView(),
if (logic.isOwner)
_buildItemView(
text: StrRes.groupManage,
showRightArrow: true,
isBottomRadius: true,
onTap: logic.groupManage,
),
10.verticalSpace,
_buildItemView(
text: StrRes.messageNotDisturb,
switchOn: logic.isNotDisturb,
showSwitchButton: true,
isBottomRadius: true,
onChanged: (_) => logic.toggleNotDisturb(),
),
10.verticalSpace,
_buildItemView(
text: StrRes.clearChatHistory,
textStyle: Styles.ts_FF381F_17sp,
isTopRadius: true,
showRightArrow: true,
onTap: logic.clearChatHistory,
),
if (!logic.isOwner)
_buildItemView(
text: logic.isJoinedGroup.value ? StrRes.exitGroup : StrRes.delete,
textStyle: Styles.ts_FF381F_17sp,
showRightArrow: true,
onTap: logic.quitGroup,
),
if (logic.isOwner)
_buildItemView(
text: StrRes.dismissGroup,
textStyle: Styles.ts_FF381F_17sp,
isBottomRadius: true,
showRightArrow: true,
onTap: logic.quitGroup,
),
40.verticalSpace,
],
),
)),
);
}
Widget _buildBaseInfoView() => Container(
height: 80.h,
padding: EdgeInsets.symmetric(horizontal: 16.w),
margin: EdgeInsets.symmetric(horizontal: 10.w, vertical: 10.h),
decoration: BoxDecoration(
color: Styles.c_FFFFFF,
borderRadius: BorderRadius.circular(6.r),
),
child: Row(
children: [
SizedBox(
width: 50.h,
height: 50.h,
child: Stack(
children: [
AvatarView(
width: 48.w,
height: 48.h,
url: logic.groupInfo.value.faceURL,
file: logic.avatar.value,
text: logic.groupInfo.value.groupName,
textStyle: Styles.ts_FFFFFF_14sp,
isGroup: true,
onTap: logic.isOwnerOrAdmin ? logic.modifyGroupAvatar : null,
),
if (logic.isOwnerOrAdmin)
Align(
alignment: Alignment.bottomRight,
child: ImageRes.editAvatar.toImage
..width = 14.w
..height = 14.h)
],
),
),
10.horizontalSpace,
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: logic.isOwnerOrAdmin ? () => logic.modifyGroupName(logic.conversationInfo.value.faceURL) : null,
child: Row(
children: [
ConstrainedBox(
constraints: BoxConstraints(maxWidth: 200.w),
child: (logic.groupInfo.value.groupName ?? '').toText..style = Styles.ts_0C1C33_17sp),
'(${logic.groupInfo.value.memberCount ?? 0})'.toText..style = Styles.ts_0C1C33_17sp,
6.horizontalSpace,
if (logic.isOwnerOrAdmin)
ImageRes.editName.toImage
..width = 12.w
..height = 12.h,
],
),
),
4.verticalSpace,
logic.groupInfo.value.groupID.toText
..style = Styles.ts_8E9AB0_14sp
..onTap = logic.copyGroupID,
],
),
),
ImageRes.mineQr.toImage
..width = 18.w
..height = 18.h
..onTap = logic.viewGroupQrcode,
],
),
);
Widget _buildMemberView() => Container(
decoration: BoxDecoration(
color: Styles.c_FFFFFF,
borderRadius: BorderRadius.circular(6.r),
),
margin: EdgeInsets.symmetric(horizontal: 10.w),
child: Column(
children: [
GridView.builder(
physics: const NeverScrollableScrollPhysics(),
itemCount: logic.length(),
shrinkWrap: true,
padding: EdgeInsets.symmetric(horizontal: 2.w, vertical: 8.h),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 5,
crossAxisSpacing: 3.w,
mainAxisSpacing: 2.h,
childAspectRatio: 68.w / 78.h,
),
itemBuilder: (BuildContext context, int index) {
return logic.itemBuilder(
index: index,
builder: (info) => Column(
children: [
SizedBox(
width: 58.w,
child: Stack(
alignment: Alignment.center,
children: [
AvatarView(
width: 48.w,
height: 48.h,
url: info.faceURL,
text: info.nickname,
textStyle: Styles.ts_FFFFFF_14sp,
onTap: () => logic.viewMemberInfo(info),
),
if (logic.groupInfo.value.ownerUserID == info.userID)
Positioned(
bottom: 0.h,
child: Container(
width: 52.h,
alignment: Alignment.center,
decoration: BoxDecoration(
color: Styles.c_E8EAEF,
borderRadius: BorderRadius.circular(6.r),
),
child: StrRes.groupOwner.toText
..style = Styles.ts_8E9AB0_10sp
..maxLines = 1
..overflow = TextOverflow.ellipsis,
),
)
],
),
),
2.verticalSpace,
(info.nickname ?? '').toText
..style = Styles.ts_8E9AB0_10sp
..maxLines = 1
..overflow = TextOverflow.ellipsis,
],
),
addButton: () => GestureDetector(
onTap: logic.addMember,
child: Column(
children: [
ImageRes.addMember.toImage
..width = 48.w
..height = 48.h,
StrRes.addMember.toText..style = Styles.ts_8E9AB0_10sp,
],
),
),
delButton: () => GestureDetector(
onTap: logic.removeMember,
child: Column(
children: [
ImageRes.delMember.toImage
..width = 48.w
..height = 48.h,
StrRes.delMember.toText..style = Styles.ts_8E9AB0_10sp,
],
),
),
);
},
),
Container(
color: Styles.c_E8EAEF,
height: 1,
margin: EdgeInsets.symmetric(horizontal: 10.w),
),
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: logic.viewGroupMembers,
child: Container(
padding: EdgeInsets.only(left: 12.w, right: 16.w),
height: 46.h,
child: Row(
children: [
sprintf(StrRes.viewAllGroupMembers, [logic.groupInfo.value.memberCount]).toText
..style = Styles.ts_0C1C33_17sp,
const Spacer(),
ImageRes.rightArrow.toImage
..width = 24.w
..height = 24.h,
],
),
),
),
],
),
);
Widget _buildItemView({
required String text,
TextStyle? textStyle,
String? value,
bool switchOn = false,
bool isTopRadius = false,
bool isBottomRadius = false,
bool showRightArrow = false,
bool showSwitchButton = false,
ValueChanged<bool>? onChanged,
Function()? onTap,
}) =>
GestureDetector(
onTap: onTap,
behavior: HitTestBehavior.translucent,
child: Container(
height: 46.h,
margin: EdgeInsets.symmetric(horizontal: 10.w),
padding: EdgeInsets.symmetric(horizontal: 16.w),
decoration: BoxDecoration(
color: Styles.c_FFFFFF,
borderRadius: BorderRadius.only(
topRight: Radius.circular(isTopRadius ? 6.r : 0),
topLeft: Radius.circular(isTopRadius ? 6.r : 0),
bottomLeft: Radius.circular(isBottomRadius ? 6.r : 0),
bottomRight: Radius.circular(isBottomRadius ? 6.r : 0),
),
),
child: Row(
children: [
Expanded(
child: text.toText
..style = textStyle ?? Styles.ts_0C1C33_17sp
..maxLines = 1),
if (null != value)
value.toText
..style = Styles.ts_8E9AB0_14sp
..maxLines = 1
..overflow = TextOverflow.ellipsis,
if (showSwitchButton)
CupertinoSwitch(
value: switchOn,
activeColor: Styles.c_0089FF,
onChanged: onChanged,
),
if (showRightArrow)
ImageRes.rightArrow.toImage
..width = 24.w
..height = 24.h,
],
),
),
);
}
@@ -0,0 +1,10 @@
import 'package:get/get.dart';
import 'oa_notification_logic.dart';
class OANotificationBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => OANotificationLogic());
}
}
@@ -0,0 +1,74 @@
import 'dart:convert';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter_openim_sdk/flutter_openim_sdk.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:pull_to_refresh_new/pull_to_refresh.dart';
import '../../../core/controller/im_controller.dart';
class OANotificationLogic extends GetxController {
late ConversationInfo info;
var messageList = <Message>[].obs;
final pageSize = 40;
final refreshController = RefreshController(initialRefresh: false);
final imLogic = Get.find<IMController>();
int? lastMinSeq;
bool _isFirstLoad = false;
@override
void onInit() {
info = Get.arguments;
imLogic.onRecvNewMessage = (Message message) {
if (message.contentType == MessageType.oaNotification) {
if (!messageList.contains(message)) messageList.add(message);
}
};
super.onInit();
}
@override
void onReady() {
loadNotification();
super.onReady();
}
void loadNotification() async {
final result = await OpenIM.iMManager.messageManager.getAdvancedHistoryMessageList(
conversationID: info.conversationID,
count: 200,
startMsg: _isFirstLoad ? null : messageList.firstOrNull,
);
if (result.messageList == null || result.messageList!.isEmpty) {
return refreshController.loadNoData();
}
final list = result.messageList!;
lastMinSeq = result.lastMinSeq;
if (_isFirstLoad) {
_isFirstLoad = false;
messageList.assignAll(list);
} else {
messageList.insertAll(0, list);
}
if (result.isEnd == true) {
refreshController.loadNoData();
} else {
refreshController.loadComplete();
}
}
OANotification parse(Message message) => OANotification.fromJson(json.decode(message.notificationElem!.detail!));
Size calSize(OANotification oa, double w, double h) {
final width = 50.w;
final height = width * h / w;
print('----${oa.videoElem?.snapshotWidth}---width:$width');
print('----${oa.videoElem?.snapshotHeight}---height:$height');
return Size(width, height);
}
}
@@ -0,0 +1,166 @@
import 'package:flutter/material.dart';
import 'package:flutter_openim_sdk/flutter_openim_sdk.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:get/get.dart';
import 'package:openim_common/openim_common.dart';
import 'package:pull_to_refresh_new/pull_to_refresh.dart';
import 'package:url_launcher/url_launcher_string.dart';
import 'oa_notification_logic.dart';
class OANotificationPage extends StatelessWidget {
final logic = Get.find<OANotificationLogic>();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: TitleBar.back(
title: logic.info.showName,
),
backgroundColor: Styles.c_F8F9FA,
body: Obx(() => SmartRefresher(
controller: logic.refreshController,
header: IMViews.buildHeader(),
footer: IMViews.buildFooter(),
enablePullDown: false,
enablePullUp: true,
onLoading: () => logic.loadNotification(),
child: ListView.builder(
padding: EdgeInsets.symmetric(horizontal: 22.w),
itemCount: logic.messageList.length,
shrinkWrap: true,
itemBuilder: (_, index) {
final message = logic.messageList.reversed.elementAt(index);
return _buildItemView(index, message, logic.parse(message));
},
),
)),
);
}
Widget _buildItemView(int index, Message message, OANotification oa) => Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
height: 15.h,
),
Text(
IMUtils.getChatTimeline(message.sendTime!),
style: Styles.ts_8E9AB0_10sp,
),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AvatarView(
url: oa.notificationFaceURL,
builder: oa.notificationFaceURL == null ? () => _buildCustomAvatar() : null,
),
SizedBox(width: 12.w),
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(oa.notificationName!, style: Styles.ts_0C1C33_14sp),
GestureDetector(
onTap: () {},
behavior: HitTestBehavior.translucent,
child: Container(
margin: EdgeInsets.only(top: 8.h),
decoration: BoxDecoration(
color: Styles.c_FFFFFF,
borderRadius: BorderRadius.circular(2),
),
padding: EdgeInsets.symmetric(
horizontal: 16.w,
vertical: 8.h,
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
oa.notificationName!,
style: Styles.ts_8E9AB0_14sp,
),
Text(
oa.text!,
style: Styles.ts_8E9AB0_12sp,
),
if (oa.mixType == 1 || oa.mixType == 2 || oa.mixType == 3)
Container(
margin: EdgeInsets.only(top: 12.h),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (oa.mixType == 1) _buildPictureView(message, oa, index),
if (oa.mixType == 2) _buildVideoView(message, oa, index),
if (oa.mixType == 3) _buildFileView(message, oa, index),
],
),
),
],
),
),
),
],
),
)
],
),
],
);
Widget _buildPictureView(Message message, OANotification oa, int index) => GestureDetector(
onTap: () async {
final url = oa.externalUrl;
final canLunch = await canLaunchUrlString(url!);
if (url.isNotEmpty == true && canLunch) {
launchUrlString(url);
}
},
child: ChatPictureView(
message: message..pictureElem = oa.pictureElem,
isISend: false,
),
);
Widget _buildVideoView(Message message, OANotification oa, int index) => GestureDetector(
onTap: () {
IMUtils.previewMediaFile(
context: Get.context!,
message: message,
onAutoPlay: (p0) => true,
onlySave: true,
);
},
child: ChatVideoView(
message: message..videoElem = oa.videoElem,
isISend: false,
),
);
Widget _buildFileView(Message message, OANotification oa, int index) => GestureDetector(
onTap: () {
IMUtils.previewFile(message);
},
child: ChatFileView(
message: message..fileElem = oa.fileElem,
isISend: false,
),
);
Widget? _buildCustomAvatar() => Container(
color: Styles.c_0089FF,
height: 48.h,
width: 48.h,
alignment: Alignment.center,
child: FaIcon(
FontAwesomeIcons.solidBell,
color: Styles.c_FFFFFF,
),
);
}
@@ -0,0 +1,10 @@
import 'package:get/get.dart';
import 'add_by_search_logic.dart';
class AddContactsBySearchBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => AddContactsBySearchLogic());
}
}
@@ -0,0 +1,153 @@
import 'package:flutter/material.dart';
import 'package:flutter_openim_sdk/flutter_openim_sdk.dart';
import 'package:get/get.dart';
import 'package:openim/pages/contacts/group_profile_panel/group_profile_panel_logic.dart';
import 'package:openim/routes/app_navigator.dart';
import 'package:openim_common/openim_common.dart';
import 'package:pull_to_refresh_new/pull_to_refresh.dart';
import 'package:sprintf/sprintf.dart';
enum SearchType {
user,
group,
}
class AddContactsBySearchLogic extends GetxController {
final refreshCtrl = RefreshController();
final searchCtrl = TextEditingController();
final focusNode = FocusNode();
final userInfoList = <UserFullInfo>[].obs;
final groupInfoList = <GroupInfo>[].obs;
late SearchType searchType;
int pageNo = 0;
@override
void onClose() {
searchCtrl.dispose();
focusNode.dispose();
super.onClose();
}
@override
void onInit() {
searchType = Get.arguments['searchType'] ?? SearchType.user;
searchCtrl.addListener(() {
if (searchKey.isEmpty) {
focusNode.requestFocus();
userInfoList.clear();
groupInfoList.clear();
}
});
super.onInit();
}
bool get isSearchUser => searchType == SearchType.user;
String get searchKey => searchCtrl.text.trim();
bool get isNotFoundUser => userInfoList.isEmpty && searchKey.isNotEmpty;
bool get isNotFoundGroup => groupInfoList.isEmpty && searchKey.isNotEmpty;
void search() {
if (searchKey.isEmpty) return;
if (isSearchUser) {
searchUser();
} else {
searchGroup();
}
}
void searchUser() async {
var list = await LoadingView.singleton.wrap(
asyncFunction: () => Apis.searchUserFullInfo(
content: searchKey,
pageNumber: pageNo = 1,
showNumber: 20,
),
);
userInfoList.assignAll(list ?? []);
refreshCtrl.refreshCompleted();
if (null == list || list.isEmpty || list.length < 20) {
refreshCtrl.loadNoData();
} else {
refreshCtrl.loadComplete();
}
}
void loadMoreUser() async {
var list = await LoadingView.singleton.wrap(
asyncFunction: () => Apis.searchUserFullInfo(
content: searchKey,
pageNumber: ++pageNo,
showNumber: 20,
),
);
userInfoList.addAll(list ?? []);
refreshCtrl.refreshCompleted();
if (null == list || list.isEmpty || list.length < 20) {
refreshCtrl.loadNoData();
} else {
refreshCtrl.loadComplete();
}
}
void searchGroup() async {
var list = await OpenIM.iMManager.groupManager.getGroupsInfo(
groupIDList: [searchKey],
);
groupInfoList.assignAll(list);
}
String getMatchContent(UserFullInfo userInfo) {
final keyword = searchCtrl.text;
return sprintf(StrRes.searchNicknameIs, [userInfo.nickname]);
}
String? getShowName(dynamic info) {
if (info is UserFullInfo) {
return info.nickname;
} else if (info is GroupInfo) {
return info.groupName;
}
return null;
}
void viewInfo(dynamic info) {
if (info is UserFullInfo) {
AppNavigator.startUserProfilePane(
userID: info.userID!,
nickname: info.nickname,
faceURL: info.faceURL,
);
} else if (info is GroupInfo) {
AppNavigator.startGroupProfilePanel(
groupID: info.groupID,
joinGroupMethod: JoinGroupMethod.search,
);
}
}
String getShowTitle(info) {
if (!isSearchUser) {
return sprintf(StrRes.searchGroupNicknameIs, [getShowName(info)]);
}
UserFullInfo userFullInfo = info;
String? tips, content;
if (int.tryParse(searchKey) != null) {
if (searchKey.length == 11) {
tips = StrRes.phoneNumber;
content = userFullInfo.phoneNumber ?? searchKey;
} else {
tips = StrRes.userID;
content = userFullInfo.userID;
}
} else {
tips = StrRes.searchNicknameIs;
content = getShowName(info);
}
return "$tips:$content";
}
}
@@ -0,0 +1,119 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:openim/routes/app_navigator.dart';
import 'package:openim_common/openim_common.dart';
import 'package:pull_to_refresh_new/pull_to_refresh.dart';
import 'package:sprintf/sprintf.dart';
import 'add_by_search_logic.dart';
class AddContactsBySearchPage extends StatelessWidget {
final logic = Get.find<AddContactsBySearchLogic>();
AddContactsBySearchPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: TitleBar.back(
title: logic.isSearchUser ? StrRes.addFriend : StrRes.addGroup,
),
backgroundColor: Styles.c_FFFFFF,
body: Column(
children: [
SearchBox(
focusNode: logic.focusNode,
controller: logic.searchCtrl,
hintText: logic.isSearchUser ? StrRes.searchByPhoneAndUid : StrRes.searchIDAddGroup,
enabled: true,
autofocus: true,
margin: EdgeInsets.symmetric(horizontal: 17.w, vertical: 10.h),
onSubmitted: (_) => logic.search(),
),
Divider(color: Styles.c_E8EAEF, height: 1.h),
if (logic.isSearchUser)
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: AppNavigator.startScan,
child: Container(
margin: EdgeInsets.only(top: 22.h),
padding: EdgeInsets.symmetric(horizontal: 16.w),
child: Row(
children: [
ImageRes.scanBlue.toImage
..width = 40.w
..height = 40.h,
8.horizontalSpace,
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
StrRes.scan.toText..style = Styles.ts_0C1C33_17sp_medium,
8.verticalSpace,
StrRes.scanHint.toText..style = Styles.ts_8E9AB0_12sp,
],
),
],
),
),
),
Obx(() => Expanded(
child: logic.isSearchUser
? (logic.isNotFoundUser ? _buildNotFoundView() : _buildUserListView())
: (logic.isNotFoundGroup
? _buildNotFoundView()
: (Column(
children: logic.groupInfoList.map((e) => _buildItemView(e)).toList(),
))),
))
],
),
);
}
Widget _buildUserListView() => SmartRefresher(
controller: logic.refreshCtrl,
enablePullDown: false,
enablePullUp: true,
footer: IMViews.buildFooter(),
onLoading: logic.loadMoreUser,
child: ListView.builder(
itemCount: logic.userInfoList.length,
itemBuilder: (_, index) {
final userInfo = logic.userInfoList.elementAt(index);
return _buildItemView(userInfo);
},
),
);
Widget _buildItemView(dynamic info) => InkWell(
onTap: () => logic.viewInfo(info),
child: Container(
padding: EdgeInsets.symmetric(horizontal: 16.w),
decoration: BoxDecoration(
border: BorderDirectional(
bottom: BorderSide(color: Styles.c_E8EAEF, width: 1.h),
),
),
height: 49.h,
child: Row(
children: [
(logic.isSearchUser ? ImageRes.searchPersonIcon : ImageRes.searchGroupIcon).toImage
..width = 24.w
..height = 24.h,
12.horizontalSpace,
logic.getShowTitle(info).toText
..style = Styles.ts_0089FF_17sp
..maxLines = 1
..overflow = TextOverflow.ellipsis,
],
),
),
);
Widget _buildNotFoundView() => Container(
padding: EdgeInsets.symmetric(vertical: 12.h),
child: (logic.isSearchUser ? StrRes.noFoundUser : StrRes.noFoundGroup).toText..style = Styles.ts_8E9AB0_17sp,
);
}
@@ -0,0 +1,10 @@
import 'package:get/get.dart';
import 'add_method_logic.dart';
class AddContactsMethodBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => AddContactsMethodLogic());
}
}
@@ -0,0 +1,17 @@
import 'package:flutter_openim_sdk/flutter_openim_sdk.dart';
import 'package:get/get.dart';
import 'package:openim/pages/contacts/add_by_search/add_by_search_logic.dart';
import 'package:openim/routes/app_navigator.dart';
class AddContactsMethodLogic extends GetxController {
scan() => AppNavigator.startScan();
addFriend() =>
AppNavigator.startAddContactsBySearch(searchType: SearchType.user);
createGroup() => AppNavigator.startCreateGroup(
defaultCheckedList: [OpenIM.iMManager.userInfo]);
addGroup() =>
AppNavigator.startAddContactsBySearch(searchType: SearchType.group);
}
@@ -0,0 +1,108 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:openim_common/openim_common.dart';
import 'add_method_logic.dart';
class AddContactsMethodPage extends StatelessWidget {
final logic = Get.find<AddContactsMethodLogic>();
AddContactsMethodPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: TitleBar.back(title: StrRes.add),
body: Column(
children: [
10.verticalSpace,
_buildItemView(
icon: ImageRes.scanBlue,
text: StrRes.scan,
hintText: StrRes.scanHint,
onTap: logic.scan,
),
_buildItemView(
icon: ImageRes.addFriendBlue,
text: StrRes.addFriend,
hintText: StrRes.addFriendHint,
onTap: logic.addFriend,
),
_buildItemView(
icon: ImageRes.createGroupBlue,
text: StrRes.createGroup,
hintText: StrRes.createGroupHint,
onTap: logic.createGroup,
),
_buildItemView(
icon: ImageRes.addGroupBLue,
text: StrRes.addGroup,
hintText: StrRes.addGroupHint,
onTap: logic.addGroup,
underline: false,
),
],
),
);
}
Widget _buildItemView({
required String icon,
required String text,
required String hintText,
bool underline = true,
Function()? onTap,
}) =>
Ink(
color: Styles.c_FFFFFF,
child: InkWell(
onTap: onTap,
child: SizedBox(
height: 74.h,
child: Row(
children: [
22.horizontalSpace,
icon.toImage
..width = 28.w
..height = 28.h,
16.horizontalSpace,
Expanded(
child: Container(
decoration: underline
? BoxDecoration(
border: BorderDirectional(
bottom: BorderSide(
color: Styles.c_E8EAEF,
width: .5,
),
),
)
: null,
child: Row(
children: [
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
text.toText..style = Styles.ts_0C1C33_17sp,
4.verticalSpace,
hintText.toText..style = Styles.ts_8E9AB0_12sp,
],
),
),
ImageRes.rightArrow.toImage
..width = 24.w
..height = 24.h,
16.horizontalSpace,
],
),
),
)
],
),
),
),
);
}
@@ -0,0 +1,10 @@
import 'package:get/get.dart';
import 'contacts_logic.dart';
class ContactsBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => ContactsLogic());
}
}
@@ -0,0 +1,89 @@
import 'package:flutter_openim_sdk/flutter_openim_sdk.dart';
import 'package:get/get.dart';
import 'package:openim/pages/contacts/group_profile_panel/group_profile_panel_logic.dart';
import 'package:openim/routes/app_navigator.dart';
import 'package:openim_common/openim_common.dart';
import '../../core/controller/im_controller.dart';
import '../home/home_logic.dart';
import 'select_contacts/select_contacts_logic.dart';
class ContactsLogic extends GetxController
implements ViewUserProfileBridge, SelectContactsBridge, ScanBridge {
final imLogic = Get.find<IMController>();
final homeLogic = Get.find<HomeLogic>();
final friendApplicationList = <UserInfo>[];
int get friendApplicationCount => homeLogic.unhandledFriendApplicationCount.value;
int get groupApplicationCount => homeLogic.unhandledGroupApplicationCount.value;
@override
void onInit() {
PackageBridge.selectContactsBridge = this;
PackageBridge.viewUserProfileBridge = this;
PackageBridge.scanBridge = this;
super.onInit();
}
@override
void onClose() {
PackageBridge.selectContactsBridge = null;
PackageBridge.viewUserProfileBridge = null;
PackageBridge.scanBridge = null;
super.onClose();
}
void newFriend() => AppNavigator.startFriendRequests();
void newGroup() => AppNavigator.startGroupRequests();
void myFriend() => AppNavigator.startFriendList();
void myGroup() => AppNavigator.startGroupList();
void searchContacts() => AppNavigator.startGlobalSearch();
void addContacts() => AppNavigator.startAddContactsMethod();
@override
Future<T?>? selectContacts<T>(
int type, {
List<String>? defaultCheckedIDList,
List? checkedList,
List<String>? excludeIDList,
bool openSelectedSheet = false,
String? groupID,
String? ex,
}) =>
AppNavigator.startSelectContacts(
action: SelAction.values[type],
defaultCheckedIDList: defaultCheckedIDList,
checkedList: checkedList,
excludeIDList: excludeIDList,
openSelectedSheet: openSelectedSheet,
groupID: groupID,
ex: ex,
);
@override
viewUserProfile(String userID, String? nickname, String? faceURL, [String? groupID]) =>
AppNavigator.startUserProfilePane(
userID: userID,
nickname: nickname,
faceURL: faceURL,
groupID: groupID,
);
@override
scanOutGroupID(String groupID) => AppNavigator.startGroupProfilePanel(
groupID: groupID,
joinGroupMethod: JoinGroupMethod.qrcode,
offAndToNamed: true,
);
@override
scanOutUserID(String userID) => AppNavigator.startUserProfilePane(userID: userID, offAndToNamed: true);
}
@@ -0,0 +1,91 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:openim_common/openim_common.dart';
import 'contacts_logic.dart';
class ContactsPage extends StatelessWidget {
final logic = Get.find<ContactsLogic>();
ContactsPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: TitleBar.contacts(
onClickAddContacts: logic.addContacts,
),
backgroundColor: Styles.c_F8F9FA,
body: Obx(
() => SingleChildScrollView(
child: Column(
children: [
_buildItemView(
assetsName: ImageRes.newFriend,
label: StrRes.newFriend,
count: logic.friendApplicationCount,
onTap: logic.newFriend,
),
_buildItemView(
assetsName: ImageRes.newGroup,
label: StrRes.newGroupRequest,
count: logic.groupApplicationCount,
onTap: logic.newGroup,
),
10.verticalSpace,
_buildItemView(
assetsName: ImageRes.myFriend,
label: StrRes.myFriend,
onTap: logic.myFriend,
),
_buildItemView(
assetsName: ImageRes.myGroup,
label: StrRes.myGroup,
onTap: logic.myGroup,
),
],
),
),
),
);
}
Widget _buildItemView({
String? assetsName,
required String label,
Widget? icon,
int count = 0,
bool showRightArrow = true,
double? height,
Function()? onTap,
}) =>
Ink(
color: Styles.c_FFFFFF,
child: InkWell(
onTap: onTap,
child: Container(
height: height ?? 60.h,
padding: EdgeInsets.symmetric(horizontal: 16.w),
child: Row(
children: [
if (null != assetsName)
assetsName.toImage
..width = 42.w
..height = 42.h,
if (null != icon) icon,
12.horizontalSpace,
label.toText..style = Styles.ts_0C1C33_17sp,
const Spacer(),
if (count > 0) UnreadCountView(count: count),
4.horizontalSpace,
if (showRightArrow)
ImageRes.rightArrow.toImage
..width = 24.w
..height = 24.h,
],
),
),
),
);
}
@@ -0,0 +1,10 @@
import 'package:get/get.dart';
import 'create_group_logic.dart';
class CreateGroupBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => CreateGroupLogic());
}
}
@@ -0,0 +1,130 @@
import 'dart:math';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter_openim_sdk/flutter_openim_sdk.dart';
import 'package:get/get.dart';
import 'package:openim_common/openim_common.dart';
import '../../../routes/app_navigator.dart';
import '../../conversation/conversation_logic.dart';
import '../select_contacts/select_contacts_logic.dart';
class CreateGroupLogic extends GetxController {
final conversationLogic = Get.find<ConversationLogic>();
final nameCtrl = TextEditingController();
final checkedList = <UserInfo>[];
final defaultCheckedList = <UserInfo>[];
final allList = <UserInfo>[].obs;
final faceURL = ''.obs;
@override
void onInit() {
defaultCheckedList.addAll(Get.arguments['defaultCheckedList']);
checkedList.addAll(Get.arguments['checkedList']);
allList.addAll(defaultCheckedList);
allList.addAll(checkedList);
super.onInit();
}
String get groupName {
String name = nameCtrl.text.trim();
if (name.isEmpty) {
int limit = min(allList.length, 3);
name = allList.sublist(0, limit).map((e) => e.nickname).join('');
}
return name;
}
completeCreation() async {
if (allList.length > 1) {
var info = await LoadingView.singleton.wrap(
asyncFunction: () => OpenIM.iMManager.groupManager.createGroup(
groupInfo: GroupInfo(
groupID: '',
groupName: groupName,
faceURL: faceURL.value,
groupType: GroupType.work,
),
memberUserIDs: allList.where((e) => e.userID != OpenIM.iMManager.userID).map((e) => e.userID!).toList(),
),
);
conversationLogic.toChat(
offUntilHome: true,
groupID: info.groupID,
nickname: groupName,
faceURL: faceURL.value,
sessionType: info.sessionType,
);
} else {
conversationLogic.toChat(
offUntilHome: true,
userID: checkedList.firstOrNull?.userID,
nickname: checkedList.firstOrNull?.nickname,
faceURL: checkedList.firstOrNull?.faceURL,
sessionType: ConversationType.single,
);
}
}
void selectAvatar() {
IMViews.openPhotoSheet(onData: (path, url) {
if (url != null) faceURL.value = url;
});
}
int length() {
return (allList.length + 2) > 10 ? 10 : (allList.length + 2);
}
Widget itemBuilder({
required int index,
required Widget Function(UserInfo info) builder,
required Widget Function() addButton,
required Widget Function() delButton,
}) {
if (allList.length > 8) {
if (index < 8) {
var info = allList.elementAt(index);
return builder(info);
} else if (index == 8) {
return addButton();
} else {
return delButton();
}
} else {
if (index < allList.length) {
var info = allList.elementAt(index);
return builder(info);
} else if (index == allList.length) {
return addButton();
} else {
return delButton();
}
}
}
@override
void onClose() {
nameCtrl.dispose();
super.onClose();
}
void opMember({bool isDel = false}) async {
final result = await AppNavigator.startSelectContacts(
action: SelAction.addMember,
checkedList: checkedList,
defaultCheckedIDList: defaultCheckedList.map((e) => e.userID!).toList(),
openSelectedSheet: isDel,
);
final list = IMUtils.convertSelectContactsResultToUserInfo(result);
if (list is List<UserInfo>) {
checkedList
..clear()
..addAll(list);
allList
..assignAll(defaultCheckedList)
..addAll(list);
}
}
}
@@ -0,0 +1,142 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:openim_common/openim_common.dart';
import 'package:sprintf/sprintf.dart';
import 'create_group_logic.dart';
class CreateGroupPage extends StatelessWidget {
final logic = Get.find<CreateGroupLogic>();
CreateGroupPage({super.key});
@override
Widget build(BuildContext context) {
return TouchCloseSoftKeyboard(
child: Scaffold(
appBar: TitleBar.back(title: StrRes.createGroup),
backgroundColor: Styles.c_F8F9FA,
body: SingleChildScrollView(
child: SizedBox(
height: 1.sh - 44.h - 10.h - 34.h,
child: Column(
children: [
_buildGroupBaseInfoView(),
_buildGroupMemberView(),
const Spacer(),
Container(
color: Styles.c_FFFFFF,
padding: EdgeInsets.symmetric(
horizontal: 16.w,
vertical: 12.h,
),
child: Button(
text: StrRes.completeCreation,
onTap: logic.completeCreation,
),
),
],
),
),
),
),
);
}
Widget _buildGroupBaseInfoView() => Container(
margin: EdgeInsets.fromLTRB(12.w, 8.h, 12.w, 12.h),
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 12.h),
decoration: BoxDecoration(
color: Styles.c_FFFFFF,
borderRadius: BorderRadius.circular(6.r),
),
child: Obx(() => Row(
children: [
if (logic.faceURL.isNotEmpty)
AvatarView(
width: 48.w,
height: 48.h,
url: logic.faceURL.value,
onTap: logic.selectAvatar,
)
else
ImageRes.cameraGray.toImage
..width = 48.w
..height = 48.h
..onTap = logic.selectAvatar,
12.horizontalSpace,
Flexible(
child: TextField(
style: Styles.ts_0C1C33_17sp,
autofocus: true,
controller: logic.nameCtrl,
inputFormatters: [LengthLimitingTextInputFormatter(16)],
decoration: InputDecoration(
hintStyle: Styles.ts_8E9AB0_17sp,
hintText: StrRes.plsEnterGroupNameHint,
border: InputBorder.none,
),
),
),
],
)),
);
Widget _buildGroupMemberView() => Obx(() => Container(
decoration: BoxDecoration(
color: Styles.c_FFFFFF,
borderRadius: BorderRadius.circular(6.r),
),
margin: EdgeInsets.symmetric(horizontal: 10.w),
child: Column(
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 12.h),
child: Row(
children: [
StrRes.groupMember.toText..style = Styles.ts_8E9AB0_17sp,
const Spacer(),
sprintf(StrRes.nPerson, [logic.allList.length]).toText..style = Styles.ts_8E9AB0_17sp,
],
),
),
GridView.builder(
physics: const NeverScrollableScrollPhysics(),
itemCount: logic.length(),
shrinkWrap: true,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 5,
crossAxisSpacing: 3.w,
mainAxisSpacing: 2.h,
childAspectRatio: 68.w / 78.h,
),
itemBuilder: (BuildContext context, int index) {
return logic.itemBuilder(
index: index,
builder: (info) => Column(
children: [
AvatarView(
width: 48.w,
height: 48.h,
url: info.faceURL,
text: info.nickname,
textStyle: Styles.ts_FFFFFF_14sp,
),
2.verticalSpace,
(info.nickname ?? '').toText
..style = Styles.ts_8E9AB0_10sp
..maxLines = 1
..overflow = TextOverflow.ellipsis,
],
),
addButton: () => const SizedBox.shrink(),
delButton: () => const SizedBox.shrink(),
);
},
),
],
),
));
}
@@ -0,0 +1,10 @@
import 'package:get/get.dart';
import 'friend_list_logic.dart';
class FriendListBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => FriendListLogic());
}
}
@@ -0,0 +1,109 @@
import 'dart:async';
import 'package:azlistview/azlistview.dart';
import 'package:flutter_openim_sdk/flutter_openim_sdk.dart';
import 'package:get/get.dart';
import 'package:openim/routes/app_navigator.dart';
import 'package:openim_common/openim_common.dart';
import '../../../core/controller/im_controller.dart';
class FriendListLogic extends GetxController {
final imLoic = Get.find<IMController>();
final friendList = <ISUserInfo>[].obs;
final userIDList = <String>[];
late StreamSubscription delSub;
late StreamSubscription addSub;
late StreamSubscription infoChangedSub;
int _count = 10000;
@override
void onInit() {
delSub = imLoic.friendDelSubject.listen(_delFriend);
addSub = imLoic.friendAddSubject.listen(_addFriend);
infoChangedSub = imLoic.friendInfoChangedSubject.listen(_friendInfoChanged);
imLoic.onBlacklistAdd = _delFriend;
imLoic.onBlacklistDeleted = _addFriend;
super.onInit();
}
@override
void onReady() {
_getFriendList();
super.onReady();
}
@override
void onClose() {
delSub.cancel();
addSub.cancel();
infoChangedSub.cancel();
super.onClose();
}
_getFriendList() async {
List<FriendInfo> list = [];
for (int i = 0;; i++) {
final temp = await OpenIM.iMManager.friendshipManager.getFriendListPage(
offset: list.length,
count: _count,
filterBlack: true,
);
list.addAll(temp);
if (temp.length < _count) {
break;
}
_count = 1000;
}
final result = list.map((e) {
userIDList.add(e.userID!);
return ISUserInfo.fromJson(e.toJson());
}).toList();
final convertResult = IMUtils.convertToAZList(result);
onUserIDList(userIDList);
friendList.assignAll(convertResult.cast<ISUserInfo>());
}
void onUserIDList(List<String> userIDList) {}
_addFriend(dynamic user) {
if (user is FriendInfo || user is BlacklistInfo) {
_addUser(user.toJson());
}
}
_delFriend(dynamic user) {
if (user is FriendInfo || user is BlacklistInfo) {
friendList.removeWhere((e) => e.userID == user.userID);
}
}
_friendInfoChanged(FriendInfo user) {
friendList.removeWhere((e) => e.userID == user.userID);
_addUser(user.toJson());
}
void _addUser(Map<String, dynamic> json) {
final info = ISUserInfo.fromJson(json);
friendList.add(IMUtils.setAzPinyinAndTag(info) as ISUserInfo);
SuspensionUtil.sortListBySuspensionTag(friendList);
SuspensionUtil.setShowSuspensionStatus(friendList);
}
void viewFriendInfo(ISUserInfo info) => AppNavigator.startUserProfilePane(
userID: info.userID!,
nickname: info.nickname,
faceURL: info.faceURL,
);
void searchFriend() => AppNavigator.startSearchFriend();
}
@@ -0,0 +1,63 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:openim_common/openim_common.dart';
import 'friend_list_logic.dart';
class FriendListPage extends StatelessWidget {
final logic = Get.find<FriendListLogic>();
FriendListPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: TitleBar.back(title: StrRes.myFriend),
backgroundColor: Styles.c_F8F9FA,
body: Column(
children: [
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: logic.searchFriend,
child: Container(
color: Styles.c_FFFFFF,
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 10.h),
child: const SearchBox(),
),
),
Flexible(
child: Obx(
() => WrapAzListView<ISUserInfo>(
data: logic.friendList,
itemCount: logic.friendList.length,
itemBuilder: (_, data, index) => _buildItemView(data),
),
),
),
],
),
);
}
Widget _buildItemView(ISUserInfo info) => Ink(
height: 64.h,
color: Styles.c_FFFFFF,
child: InkWell(
onTap: () => logic.viewFriendInfo(info),
child: Container(
padding: EdgeInsets.symmetric(horizontal: 16.w),
child: Row(
children: [
AvatarView(
url: info.faceURL,
text: info.showName,
),
10.horizontalSpace,
info.showName.toText..style = Styles.ts_0C1C33_17sp,
],
),
),
),
);
}
@@ -0,0 +1,10 @@
import 'package:get/get.dart';
import 'search_friend_logic.dart';
class SearchFriendBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => SearchFriendLogic());
}
}
@@ -0,0 +1,53 @@
import 'package:flutter/material.dart';
import 'package:flutter_openim_sdk/flutter_openim_sdk.dart';
import 'package:get/get.dart';
import 'package:openim_common/openim_common.dart';
import '../../../../routes/app_navigator.dart';
import '../friend_list_logic.dart';
class SearchFriendLogic extends GetxController {
final logic = Get.find<FriendListLogic>();
final focusNode = FocusNode();
final searchCtrl = TextEditingController();
final resultList = <ISUserInfo>[].obs;
late bool isMultiModel;
@override
void onInit() {
searchCtrl.addListener(_clearInput);
super.onInit();
}
@override
void onClose() {
focusNode.dispose();
searchCtrl.dispose();
super.onClose();
}
bool get isSearchNotResult => searchCtrl.text.trim().isNotEmpty && resultList.isEmpty;
_clearInput() {
final key = searchCtrl.text.trim();
if (key.isEmpty) {
resultList.clear();
}
}
search() async {
var key = searchCtrl.text.trim();
resultList.clear();
if (key.isNotEmpty) {
final result = await OpenIM.iMManager.friendshipManager
.searchFriends(keywordList: [key], isSearchNickname: true, isSearchRemark: true, isSearchUserID: true);
final users = result.map((e) => ISUserInfo.fromJson(e.toJson())).toList();
resultList.addAll(users);
}
}
viewFriendInfo(ISUserInfo info) => AppNavigator.startUserProfilePane(
userID: info.userID!,
offAndToNamed: true,
);
}
@@ -0,0 +1,71 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:openim_common/openim_common.dart';
import 'package:search_keyword_text/search_keyword_text.dart';
import 'search_friend_logic.dart';
class SearchFriendPage extends StatelessWidget {
final logic = Get.find<SearchFriendLogic>();
SearchFriendPage({super.key});
@override
Widget build(BuildContext context) {
return TouchCloseSoftKeyboard(
child: Scaffold(
appBar: TitleBar.search(
focusNode: logic.focusNode,
controller: logic.searchCtrl,
onSubmitted: (_) => logic.search(),
onCleared: () => logic.focusNode.requestFocus(),
),
backgroundColor: Styles.c_F8F9FA,
body: Obx(() => logic.isSearchNotResult
? _emptyListView
: ListView.builder(
itemCount: logic.resultList.length,
itemBuilder: (_, index) => _buildItemView(logic.resultList[index]),
)),
),
);
}
Widget _buildItemView(ISUserInfo info) => Ink(
height: 64.h,
color: Styles.c_FFFFFF,
child: InkWell(
onTap: () => logic.viewFriendInfo(info),
child: Container(
padding: EdgeInsets.symmetric(horizontal: 16.w),
child: Row(
children: [
AvatarView(
url: info.faceURL,
text: info.showName,
),
10.horizontalSpace,
SearchKeywordText(
text: info.showName,
keyText: logic.searchCtrl.text.trim(),
style: Styles.ts_0C1C33_17sp,
keyStyle: Styles.ts_0089FF_17sp,
),
],
),
),
),
);
Widget get _emptyListView => SizedBox(
width: 1.sw,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
44.verticalSpace,
StrRes.searchNotFound.toText..style = Styles.ts_8E9AB0_17sp,
],
),
);
}
@@ -0,0 +1,10 @@
import 'package:get/get.dart';
import 'friend_requests_logic.dart';
class FriendRequestsBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => FriendRequestsLogic());
}
}
@@ -0,0 +1,77 @@
import 'dart:async';
import 'package:flutter_openim_sdk/flutter_openim_sdk.dart';
import 'package:get/get.dart';
import 'package:openim/routes/app_navigator.dart';
import 'package:openim_common/openim_common.dart';
import '../../../core/controller/im_controller.dart';
import '../../home/home_logic.dart';
class FriendRequestsLogic extends GetxController {
final imLogic = Get.find<IMController>();
final homeLogic = Get.find<HomeLogic>();
final applicationList = <FriendApplicationInfo>[].obs;
late StreamSubscription faSub;
@override
void onInit() {
faSub = imLogic.friendApplicationChangedSubject.listen((value) {
_getFriendRequestsList();
});
super.onInit();
}
@override
void onReady() {
_getFriendRequestsList();
super.onReady();
}
@override
void onClose() {
faSub.cancel();
homeLogic.getUnhandledFriendApplicationCount();
super.onClose();
}
void _getFriendRequestsList() async {
final list = await Future.wait([
OpenIM.iMManager.friendshipManager.getFriendApplicationListAsRecipient(),
OpenIM.iMManager.friendshipManager.getFriendApplicationListAsApplicant(),
]);
final allList = <FriendApplicationInfo>[];
allList
..addAll(list[0])
..addAll(list[1]);
allList.sort((a, b) {
if (a.createTime! > b.createTime!) {
return -1;
} else if (a.createTime! < b.createTime!) {
return 1;
}
return 0;
});
var haveReadList = DataSp.getHaveReadUnHandleFriendApplication();
haveReadList ??= <String>[];
for (var e in list[0]) {
var id = IMUtils.buildFriendApplicationID(e);
if (!haveReadList.contains(id)) {
haveReadList.add(id);
}
}
DataSp.putHaveReadUnHandleFriendApplication(haveReadList);
applicationList.assignAll(allList);
}
bool isISendRequest(FriendApplicationInfo info) => info.fromUserID == OpenIM.iMManager.userID;
void acceptFriendApplication(FriendApplicationInfo info) => AppNavigator.startProcessFriendRequests(
applicationInfo: info,
);
void refuseFriendApplication(FriendApplicationInfo info) async {}
}
@@ -0,0 +1,90 @@
import 'package:flutter/material.dart';
import 'package:flutter_openim_sdk/flutter_openim_sdk.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:openim_common/openim_common.dart';
import 'friend_requests_logic.dart';
class FriendRequestsPage extends StatelessWidget {
final logic = Get.find<FriendRequestsLogic>();
FriendRequestsPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: TitleBar.back(title: StrRes.newFriend),
backgroundColor: Styles.c_F8F9FA,
body: Obx(() => ListView.builder(
padding: EdgeInsets.only(top: 10.h),
itemCount: logic.applicationList.length,
itemBuilder: (_, index) =>
_buildItemView(logic.applicationList[index]),
)),
);
}
Widget _buildItemView(FriendApplicationInfo info) {
final isISendRequest = info.fromUserID == OpenIM.iMManager.userID;
String? name = isISendRequest ? info.toNickname : info.fromNickname;
String? faceURL = isISendRequest ? info.toFaceURL : info.fromFaceURL;
String? reason = info.reqMsg;
return Container(
height: 68.h,
padding: EdgeInsets.symmetric(horizontal: 16.w),
decoration: BoxDecoration(
color: Styles.c_FFFFFF,
border: BorderDirectional(
bottom: BorderSide(
color: Styles.c_F8F9FA,
width: 1,
),
),
),
child: Row(
children: [
AvatarView(url: faceURL, text: name),
10.horizontalSpace,
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
(name ?? '').toText
..style = Styles.ts_0C1C33_17sp
..maxLines = 1
..overflow = TextOverflow.ellipsis,
4.verticalSpace,
if (IMUtils.isNotNullEmptyStr(reason))
(reason ?? '').toText
..style = Styles.ts_8E9AB0_14sp
..maxLines = 1
..overflow = TextOverflow.ellipsis,
],
),
),
if (/*info.isWaitingHandle && */ isISendRequest)
ImageRes.sendRequests.toImage
..width = 20.w
..height = 20.h,
if (info.isWaitingHandle && !isISendRequest)
Button(
text: StrRes.lookOver,
textStyle: Styles.ts_FFFFFF_14sp,
onTap: () => logic.acceptFriendApplication(info),
height: 28.h,
padding: EdgeInsets.symmetric(horizontal: 13.w),
),
if (info.isWaitingHandle && isISendRequest)
StrRes.waitingForVerification.toText..style = Styles.ts_8E9AB0_14sp,
if (info.isRejected)
StrRes.rejected.toText..style = Styles.ts_8E9AB0_14sp,
if (info.isAgreed)
StrRes.approved.toText..style = Styles.ts_8E9AB0_14sp,
],
),
);
}
}
@@ -0,0 +1,10 @@
import 'package:get/get.dart';
import 'process_friend_requests_logic.dart';
class ProcessFriendRequestsBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => ProcessFriendRequestsLogic());
}
}
@@ -0,0 +1,43 @@
import 'package:flutter_openim_sdk/flutter_openim_sdk.dart';
import 'package:get/get.dart';
import 'package:openim_common/openim_common.dart';
class ProcessFriendRequestsLogic extends GetxController {
late FriendApplicationInfo applicationInfo;
@override
void onInit() {
applicationInfo = Get.arguments['applicationInfo'];
super.onInit();
}
void acceptFriendApplication() async {
LoadingView.singleton
.wrap(
asyncFunction: () =>
OpenIM.iMManager.friendshipManager.acceptFriendApplication(userID: applicationInfo.fromUserID!))
.then(_addSuccessfully)
.catchError((_) => IMViews.showToast(StrRes.addFailed));
}
void refuseFriendApplication() async {
LoadingView.singleton
.wrap(
asyncFunction: () =>
OpenIM.iMManager.friendshipManager.refuseFriendApplication(userID: applicationInfo.fromUserID!))
.then(_rejectSuccessfully)
.catchError((_) => IMViews.showToast(StrRes.rejectFailed));
}
_addSuccessfully(_) {
IMViews.showToast(StrRes.addSuccessfully);
Get.back(result: 1);
return _;
}
_rejectSuccessfully(_) {
IMViews.showToast(StrRes.rejectSuccessfully);
Get.back(result: -1);
return _;
}
}
@@ -0,0 +1,100 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:openim_common/openim_common.dart';
import 'process_friend_requests_logic.dart';
class ProcessFriendRequestsPage extends StatelessWidget {
final logic = Get.find<ProcessFriendRequestsLogic>();
ProcessFriendRequestsPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: TitleBar.back(title: StrRes.newFriend),
backgroundColor: Styles.c_F8F9FA,
body: Container(
color: Styles.c_FFFFFF,
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
AvatarView(
width: 48.w,
height: 48.h,
url: logic.applicationInfo.fromFaceURL,
text: logic.applicationInfo.fromNickname,
),
10.horizontalSpace,
(logic.applicationInfo.fromNickname ?? '').toText
..style = Styles.ts_0C1C33_17sp,
],
),
12.verticalSpace,
if (IMUtils.isNotNullEmptyStr(logic.applicationInfo.reqMsg))
Container(
height: 80.h,
width: 343.w,
margin: EdgeInsets.only(bottom: 12.h),
decoration: BoxDecoration(
color: Styles.c_E8EAEF_opacity50,
borderRadius: BorderRadius.circular(6.r),
),
child: SingleChildScrollView(
padding: EdgeInsets.symmetric(
horizontal: 16.w,
vertical: 16.h,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
(logic.applicationInfo.reqMsg ?? '').toText
..style = Styles.ts_0C1C33_17sp,
],
),
),
),
Row(
children: [
Flexible(child: _buildRejectButton()),
12.horizontalSpace,
Flexible(
child: Button(
text: StrRes.accept,
textStyle: Styles.ts_FFFFFF_17sp,
onTap: logic.acceptFriendApplication,
),
),
],
)
],
),
),
);
}
Widget _buildRejectButton() => Material(
child: Ink(
height: 44.h,
decoration: BoxDecoration(
color: Styles.c_FFFFFF,
border: Border.all(
color: Styles.c_E8EAEF,
width: 1,
),
borderRadius: BorderRadius.circular(6.r),
),
child: InkWell(
onTap: logic.refuseFriendApplication,
child: Container(
alignment: Alignment.center,
child: StrRes.reject.toText..style = Styles.ts_0C1C33_17sp,
),
),
),
);
}
@@ -0,0 +1,10 @@
import 'package:get/get.dart';
import 'group_list_logic.dart';
class GroupListBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => GroupListLogic());
}
}
@@ -0,0 +1,147 @@
import 'package:flutter/material.dart';
import 'package:flutter_openim_sdk/flutter_openim_sdk.dart';
import 'package:get/get.dart';
import 'package:openim/routes/app_navigator.dart';
import 'package:pull_to_refresh_new/pull_to_refresh.dart';
import '../../../core/controller/im_controller.dart';
import '../../../core/im_callback.dart';
import '../../conversation/conversation_logic.dart';
class GroupListLogic extends GetxController {
final imLoic = Get.find<IMController>();
final iCreateRefreshController = RefreshController(initialRefresh: true);
final iJoinRefreshController = RefreshController(initialRefresh: true);
final iCreateGlobalKey = GlobalKey();
final iJoinGlobalKey = GlobalKey();
final conversationLogic = Get.find<ConversationLogic>();
final index = 0.obs;
final iCreatedList = <GroupInfo>[].obs;
final iJoinedList = <GroupInfo>[].obs;
int iCreatedOffset = 0;
int iJoinedOffset = 0;
int count = 1000;
@override
void onInit() {
imLoic.imSdkStatusPublishSubject.last.then((con) {
if (con.status == IMSdkStatus.syncEnded) {
iCreatedInitial();
iJoinedInitial();
}
});
iCreatedInitial();
iJoinedInitial();
super.onInit();
}
void switchTab(i) {
index.value = i;
}
void iCreatedInitial() async {
iCreatedOffset = 0;
iCreatedList.clear();
final length = await initial(iCreate: true, offset: iCreatedOffset);
iCreateRefreshController.refreshCompleted();
if (length >= count) {
iCreatedOffset += length;
} else {
iCreateRefreshController.loadNoData();
}
}
void iCreatedLoadMore() async {
final length = await loadMore(iCreate: true, offset: iCreatedOffset);
if (length < count) {
iCreateRefreshController.loadNoData();
} else {
iCreateRefreshController.loadComplete();
iCreatedOffset += length;
}
}
void iJoinedInitial() async {
iJoinedOffset = 0;
iJoinedList.clear();
final length = await initial(iCreate: false, offset: iJoinedOffset);
iJoinRefreshController.refreshCompleted();
if (length >= count) {
iJoinedOffset += length;
} else {
iJoinRefreshController.loadNoData();
}
}
void iJoinedLoadMore() async {
final length = await loadMore(iCreate: false, offset: iJoinedOffset);
if (length < count) {
iJoinRefreshController.loadNoData();
} else {
iJoinRefreshController.loadComplete();
iJoinedOffset += length;
}
}
Future<int> initial({bool iCreate = true, int offset = 0}) async {
final list = await OpenIM.iMManager.groupManager.getJoinedGroupListPage(offset: offset, count: count);
int iCreatedCount = 0;
int iJoinedCount = 0;
if (iCreate) {
final result = list.where((e) => e.ownerUserID == OpenIM.iMManager.userID);
iCreatedList.addAll(result);
iCreatedCount = result.length;
} else {
final result = list.where((e) => e.ownerUserID != OpenIM.iMManager.userID);
iJoinedList.addAll(result);
iJoinedCount = result.length;
}
return iCreate ? iCreatedCount : iJoinedCount;
}
Future<int> loadMore({bool iCreate = true, int offset = 0}) async {
final list = await OpenIM.iMManager.groupManager.getJoinedGroupListPage(offset: offset, count: count);
int iCreatedCount = 0;
int iJoinedCount = 0;
if (iCreate) {
final result = list.where((e) => e.ownerUserID == OpenIM.iMManager.userID);
iCreatedList.addAll(result);
iCreatedCount = result.length;
} else {
final result = list.where((e) => e.ownerUserID != OpenIM.iMManager.userID);
iJoinedList.addAll(result);
iJoinedCount = result.length;
}
return iCreate ? iCreatedCount : iJoinedCount;
}
void toGroupChat(GroupInfo info) {
conversationLogic.toChat(
offUntilHome: false,
groupID: info.groupID,
nickname: info.groupName,
faceURL: info.faceURL,
sessionType: info.sessionType,
);
}
void searchGroup() => AppNavigator.startSearchGroup();
}
@@ -0,0 +1,110 @@
import 'package:flutter/material.dart';
import 'package:flutter_openim_sdk/flutter_openim_sdk.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:openim_common/openim_common.dart';
import 'package:pull_to_refresh_new/pull_to_refresh.dart';
import 'package:sprintf/sprintf.dart';
import 'group_list_logic.dart';
class GroupListPage extends StatelessWidget {
final logic = Get.find<GroupListLogic>();
GroupListPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: TitleBar.back(title: StrRes.myGroup),
backgroundColor: Styles.c_F8F9FA,
body: Column(
children: [
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: logic.searchGroup,
child: Container(
color: Styles.c_FFFFFF,
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 10.h),
child: const SearchBox(),
),
),
Obx(
() => CustomTabBar(
labels: [StrRes.iCreatedGroup, StrRes.iJoinedGroup],
index: logic.index.value,
onTabChanged: (i) => logic.switchTab(i),
showUnderline: true,
),
),
Expanded(
child: Obx(
() => logic.index.value == 0 ? _buildICreatedListView() : _buildIJoinedListView(),
),
),
],
),
);
}
Widget _buildICreatedListView() => SmartRefresher(
key: logic.iCreateGlobalKey,
controller: logic.iCreateRefreshController,
header: IMViews.buildHeader(30),
footer: IMViews.buildFooter(),
enablePullUp: true,
enablePullDown: true,
onRefresh: logic.iCreatedInitial,
onLoading: logic.iCreatedLoadMore,
child: ListView.builder(
physics: const BouncingScrollPhysics(),
itemCount: logic.iCreatedList.length,
itemBuilder: (_, index) => _buildItemView(logic.iCreatedList[index]),
),
);
Widget _buildIJoinedListView() => SmartRefresher(
key: logic.iJoinGlobalKey,
controller: logic.iJoinRefreshController,
header: IMViews.buildHeader(30),
footer: IMViews.buildFooter(),
enablePullUp: true,
enablePullDown: true,
onRefresh: logic.iJoinedInitial,
onLoading: logic.iJoinedLoadMore,
child: ListView.builder(
physics: const BouncingScrollPhysics(),
itemCount: logic.iJoinedList.length,
itemBuilder: (_, index) => _buildItemView(logic.iJoinedList[index]),
),
);
Widget _buildItemView(GroupInfo info) => Ink(
height: 64.h,
color: Styles.c_FFFFFF,
child: InkWell(
onTap: () => logic.toGroupChat(info),
child: Container(
padding: EdgeInsets.symmetric(horizontal: 16.w),
child: Row(
children: [
AvatarView(
url: info.faceURL,
text: info.groupName,
isGroup: true,
),
10.horizontalSpace,
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
(info.groupName ?? '').toText..style = Styles.ts_0C1C33_17sp,
sprintf(StrRes.nPerson, [info.memberCount]).toText..style = Styles.ts_8E9AB0_14sp,
],
),
],
),
),
),
);
}
@@ -0,0 +1,10 @@
import 'package:get/get.dart';
import 'search_group_logic.dart';
class SearchGroupBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => SearchGroupLogic());
}
}
@@ -0,0 +1,54 @@
import 'package:flutter/material.dart';
import 'package:flutter_openim_sdk/flutter_openim_sdk.dart';
import 'package:get/get.dart';
import '../../../conversation/conversation_logic.dart';
import '../group_list_logic.dart';
class SearchGroupLogic extends GetxController {
final conversationLogic = Get.find<ConversationLogic>();
final logic = Get.find<GroupListLogic>();
final focusNode = FocusNode();
final searchCtrl = TextEditingController();
final resultList = <GroupInfo>[].obs;
@override
void onInit() {
searchCtrl.addListener(_clearInput);
super.onInit();
}
@override
void onClose() {
focusNode.dispose();
searchCtrl.dispose();
super.onClose();
}
bool get isSearchNotResult => searchCtrl.text.trim().isNotEmpty && resultList.isEmpty;
_clearInput() {
final key = searchCtrl.text.trim();
if (key.isEmpty) {
resultList.clear();
}
}
search() async {
var key = searchCtrl.text.trim();
final result = await OpenIM.iMManager.groupManager.searchGroups(keywordList: [key], isSearchGroupName: true);
resultList.clear();
resultList.addAll(result);
}
void toGroupChat(GroupInfo info) {
conversationLogic.toChat(
offUntilHome: false,
groupID: info.groupID,
nickname: info.groupName,
faceURL: info.faceURL,
sessionType: info.sessionType,
);
}
}
@@ -0,0 +1,81 @@
import 'package:flutter/material.dart';
import 'package:flutter_openim_sdk/flutter_openim_sdk.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:openim_common/openim_common.dart';
import 'package:search_keyword_text/search_keyword_text.dart';
import 'package:sprintf/sprintf.dart';
import 'search_group_logic.dart';
class SearchGroupPage extends StatelessWidget {
final logic = Get.find<SearchGroupLogic>();
SearchGroupPage({super.key});
@override
Widget build(BuildContext context) {
return TouchCloseSoftKeyboard(
child: Scaffold(
appBar: TitleBar.search(
focusNode: logic.focusNode,
controller: logic.searchCtrl,
onSubmitted: (_) => logic.search(),
onCleared: () => logic.focusNode.requestFocus(),
),
backgroundColor: Styles.c_F8F9FA,
body: Obx(() => logic.isSearchNotResult
? _emptyListView
: ListView.builder(
itemCount: logic.resultList.length,
itemBuilder: (_, index) => _buildItemView(logic.resultList[index]),
)),
),
);
}
Widget _buildItemView(GroupInfo info) => Ink(
height: 64.h,
color: Styles.c_FFFFFF,
child: InkWell(
onTap: () => logic.toGroupChat(info),
child: Container(
padding: EdgeInsets.symmetric(horizontal: 16.w),
child: Row(
children: [
AvatarView(
url: info.faceURL,
text: info.groupName,
isGroup: true,
),
10.horizontalSpace,
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
SearchKeywordText(
text: info.groupName ?? '',
keyText: logic.searchCtrl.text.trim(),
style: Styles.ts_0C1C33_17sp,
keyStyle: Styles.ts_0089FF_17sp,
),
sprintf(StrRes.nPerson, [info.memberCount]).toText..style = Styles.ts_8E9AB0_14sp,
],
),
],
),
),
),
);
Widget get _emptyListView => SizedBox(
width: 1.sw,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
44.verticalSpace,
StrRes.searchNotFound.toText..style = Styles.ts_8E9AB0_17sp,
],
),
);
}
@@ -0,0 +1,10 @@
import 'package:get/get.dart';
import 'group_profile_panel_logic.dart';
class GroupProfilePanelBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => GroupProfilePanelLogic());
}
}
@@ -0,0 +1,119 @@
import 'dart:async';
import 'package:collection/collection.dart';
import 'package:flutter_openim_sdk/flutter_openim_sdk.dart';
import 'package:get/get.dart';
import '../../../core/controller/im_controller.dart';
import '../../../routes/app_navigator.dart';
import '../../conversation/conversation_logic.dart';
enum JoinGroupMethod { search, qrcode, invite }
class GroupProfilePanelLogic extends GetxController {
final conversationLogic = Get.find<ConversationLogic>();
final imLogic = Get.find<IMController>();
final isJoined = false.obs;
final members = <GroupMembersInfo>[].obs;
late Rx<GroupInfo> groupInfo;
late JoinGroupMethod joinGroupMethod;
late StreamSubscription sub;
late StreamSubscription joinedGroupAddedSub;
late StreamSubscription memberAddedSub;
@override
void onInit() {
groupInfo = Rx(GroupInfo(groupID: Get.arguments['groupID']));
joinGroupMethod = Get.arguments['joinGroupMethod'];
sub = imLogic.groupApplicationChangedSubject.listen(_onChanged);
joinedGroupAddedSub = imLogic.joinedGroupAddedSubject.listen(_onChanged);
memberAddedSub = imLogic.memberAddedSubject.listen(_onChanged);
_checkGroup();
_getGroupInfo();
_getMembers();
super.onInit();
}
_onChanged(dynamic value) {
if (value is GroupApplicationInfo) {
if (value.groupID == groupInfo.value.groupID && value.handleResult == 1) {
if (!isJoined.value) {
isJoined.value = true;
_getGroupInfo();
_getMembers();
}
}
} else if (value is GroupInfo) {
if (value.groupID == groupInfo.value.groupID) {
if (!isJoined.value) {
isJoined.value = true;
_getGroupInfo();
_getMembers();
}
}
} else if (value is GroupMembersInfo) {
if (value.groupID == groupInfo.value.groupID && value.userID == OpenIM.iMManager.userID) {
if (!isJoined.value) {
isJoined.value = true;
_getGroupInfo();
_getMembers();
}
}
}
}
_getGroupInfo() async {
var list = await OpenIM.iMManager.groupManager.getGroupsInfo(
groupIDList: [groupInfo.value.groupID],
);
var info = list.firstOrNull;
if (null != info) {
groupInfo.update((val) {
val?.groupName = info.groupName;
val?.faceURL = info.faceURL;
val?.memberCount = info.memberCount;
val?.groupType = info.groupType;
val?.createTime = info.createTime;
});
}
}
_checkGroup() async {
isJoined.value = await OpenIM.iMManager.groupManager.isJoinedGroup(
groupID: groupInfo.value.groupID,
);
}
_getMembers() async {
var list = await OpenIM.iMManager.groupManager.getGroupMemberList(
groupID: groupInfo.value.groupID,
count: 10,
);
members.assignAll(list);
}
enterGroup() async {
if (isJoined.value) {
conversationLogic.toChat(
groupID: groupInfo.value.groupID,
nickname: groupInfo.value.groupName,
faceURL: groupInfo.value.faceURL,
sessionType: groupInfo.value.sessionType,
);
} else {
AppNavigator.startSendVerificationApplication(
groupID: groupInfo.value.groupID,
joinGroupMethod: joinGroupMethod,
);
}
}
@override
void onClose() {
sub.cancel();
joinedGroupAddedSub.cancel();
memberAddedSub.cancel();
super.onClose();
}
}
@@ -0,0 +1,147 @@
import 'dart:math';
import 'package:common_utils/common_utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:openim_common/openim_common.dart';
import 'package:sprintf/sprintf.dart';
import 'group_profile_panel_logic.dart';
class GroupProfilePanelPage extends StatelessWidget {
final logic = Get.find<GroupProfilePanelLogic>();
GroupProfilePanelPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: TitleBar.back(),
backgroundColor: Styles.c_F8F9FA,
body: Obx(() => Column(
children: [
_buildBaseInfo(),
if (logic.members.isNotEmpty) _buildGroupMemberList(),
Container(
height: 56.h,
color: Styles.c_FFFFFF,
padding: EdgeInsets.symmetric(horizontal: 16.w),
child: Row(
children: [
StrRes.groupID.toText..style = Styles.ts_0C1C33_17sp,
12.horizontalSpace,
logic.groupInfo.value.groupID.toText
..style = Styles.ts_8E9AB0_17sp,
],
),
),
const Spacer(),
Container(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h),
color: Styles.c_FFFFFF,
child: Button(
text: logic.isJoined.value
? StrRes.enterGroup
: StrRes.applyJoin,
onTap: logic.enterGroup,
),
),
],
)),
);
}
Widget _buildBaseInfo() => Container(
height: 80.h,
color: Styles.c_FFFFFF,
padding: EdgeInsets.symmetric(horizontal: 16.w),
margin: EdgeInsets.only(bottom: 10.h),
child: Row(
children: [
AvatarView(
width: 48.w,
height: 48.h,
url: logic.groupInfo.value.faceURL,
text: logic.groupInfo.value.groupName,
isGroup: true,
),
12.horizontalSpace,
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
(logic.groupInfo.value.groupName ?? '').toText
..style = Styles.ts_0C1C33_17sp_medium,
4.verticalSpace,
Row(
children: [
ImageRes.createGroupTime.toImage
..width = 12.w
..height = 12.h,
6.horizontalSpace,
DateUtil.formatDateMs(
(logic.groupInfo.value.createTime ?? 0),
format: IMUtils.getTimeFormat1(),
).toText
..style = Styles.ts_8E9AB0_14sp,
],
),
],
),
),
],
),
);
Widget _buildGroupMemberList() => Container(
color: Styles.c_FFFFFF,
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h),
margin: EdgeInsets.only(bottom: 10.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
RichText(
text: TextSpan(
text: StrRes.groupMember,
style: Styles.ts_0C1C33_17sp,
children: [
WidgetSpan(child: 12.horizontalSpace),
TextSpan(
text: sprintf(
StrRes.nPerson, [logic.groupInfo.value.memberCount]),
style: Styles.ts_8E9AB0_17sp,
),
],
),
),
10.verticalSpace,
GridView.builder(
physics: const NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 7,
crossAxisSpacing: 6.w,
childAspectRatio: 1,
),
itemCount: min(logic.members.length, 7),
shrinkWrap: true,
itemBuilder: (_, index) {
final member = logic.members.elementAt(index);
if (index == 6 && logic.members.length != 7) {
return ImageRes.moreMembers.toImage
..width = 44.w
..height = 44.h;
}
return AvatarView(
width: 44.w,
height: 44.h,
text: member.nickname,
url: member.faceURL,
);
},
),
],
),
);
}
@@ -0,0 +1,10 @@
import 'package:get/get.dart';
import 'group_requests_logic.dart';
class GroupRequestsBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => GroupRequestsLogic());
}
}
@@ -0,0 +1,140 @@
import 'package:flutter_openim_sdk/flutter_openim_sdk.dart';
import 'package:get/get.dart';
import 'package:openim/routes/app_navigator.dart';
import 'package:openim_common/openim_common.dart';
import '../../../core/controller/im_controller.dart';
import '../../home/home_logic.dart';
class GroupRequestsLogic extends GetxController {
final imLogic = Get.find<IMController>();
final homeLogic = Get.find<HomeLogic>();
final list = <GroupApplicationInfo>[].obs;
final groupList = <String, GroupInfo>{}.obs;
final memberList = <GroupMembersInfo>[].obs;
final userInfoList = <UserInfo>[].obs;
@override
void onReady() {
getApplicationList();
getJoinedGroup();
super.onReady();
}
@override
void onInit() {
imLogic.groupApplicationChangedSubject.listen((info) {
getApplicationList();
});
super.onInit();
}
@override
void onClose() {
homeLogic.getUnhandledGroupApplicationCount();
super.onClose();
}
bool isInvite(GroupApplicationInfo info) {
if (info.joinSource == 2) {
return info.inviterUserID != null && info.inviterUserID!.isNotEmpty;
}
return false;
}
getApplicationList() async {
final list = await LoadingView.singleton.wrap(asyncFunction: () async {
final list = await Future.wait([
OpenIM.iMManager.groupManager.getGroupApplicationListAsRecipient(),
OpenIM.iMManager.groupManager.getGroupApplicationListAsApplicant(),
]);
final allList = <GroupApplicationInfo>[];
allList
..addAll(list[0])
..addAll(list[1]);
allList.sort((a, b) {
if (a.reqTime! > b.reqTime!) {
return -1;
} else if (a.reqTime! < b.reqTime!) {
return 1;
}
return 0;
});
var map = <String, List<String>>{};
var inviterList = <String>[];
var haveReadList = DataSp.getHaveReadUnHandleGroupApplication();
haveReadList ??= <String>[];
for (var a in list[0]) {
var id = IMUtils.buildGroupApplicationID(a);
if (!haveReadList.contains(id)) {
haveReadList.add(id);
}
}
DataSp.putHaveReadUnHandleGroupApplication(haveReadList);
var groupIDList = <String>[];
for (var a in allList) {
if (isInvite(a)) {
if (!map.containsKey(a.groupID)) {
map[a.groupID!] = [a.inviterUserID!];
} else {
if (!map[a.groupID!]!.contains(a.inviterUserID!)) {
map[a.groupID!]!.add(a.inviterUserID!);
}
}
if (!inviterList.contains(a.inviterUserID!)) {
inviterList.add(a.inviterUserID!);
}
}
}
if (map.isNotEmpty) {
await Future.wait(map.entries.map((e) => OpenIM.iMManager.groupManager
.getGroupMembersInfo(groupID: e.key, userIDList: e.value)
.then((list) => memberList.assignAll(list))));
}
if (inviterList.isNotEmpty) {
await OpenIM.iMManager.userManager
.getUsersInfo(userIDList: inviterList)
.then((list) => userInfoList.assignAll(list.map((e) => e.simpleUserInfo).toList()));
}
return allList;
});
this.list.assignAll(list);
}
void getJoinedGroup() {
OpenIM.iMManager.groupManager.getJoinedGroupList().then((list) {
var map = <String, GroupInfo>{};
for (var e in list) {
map[e.groupID] = e;
}
groupList.addAll(map);
});
}
String getGroupName(GroupApplicationInfo info) => info.groupName ?? groupList[info.groupID]?.groupName ?? '';
String getInviterNickname(GroupApplicationInfo info) =>
(getMemberInfo(info.inviterUserID!)?.nickname) ?? (getUserInfo(info.inviterUserID!)?.nickname) ?? '-';
GroupMembersInfo? getMemberInfo(inviterUserID) => memberList.firstWhereOrNull((e) => e.userID == inviterUserID);
UserInfo? getUserInfo(inviterUserID) => userInfoList.firstWhereOrNull((e) => e.userID == inviterUserID);
void handle(GroupApplicationInfo info) async {
var result = await AppNavigator.startProcessGroupRequests(applicationInfo: info);
if (result is int) {
info.handleResult = result;
list.refresh();
}
}
}
@@ -0,0 +1,136 @@
import 'package:flutter/material.dart';
import 'package:flutter_openim_sdk/flutter_openim_sdk.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:openim_common/openim_common.dart';
import 'package:sprintf/sprintf.dart';
import 'group_requests_logic.dart';
class GroupRequestsPage extends StatelessWidget {
final logic = Get.find<GroupRequestsLogic>();
GroupRequestsPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: TitleBar.back(title: StrRes.newGroupRequest),
backgroundColor: Styles.c_F8F9FA,
body: Obx(() => ListView.builder(
padding: EdgeInsets.only(top: 10.h),
itemCount: logic.list.length,
itemBuilder: (_, index) => _buildItemView(logic.list[index]),
)),
);
}
Widget _buildItemView(GroupApplicationInfo info) {
final isISendRequest = info.userID == OpenIM.iMManager.userID;
return Container(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 10.h),
decoration: BoxDecoration(
color: Styles.c_FFFFFF,
border: BorderDirectional(
bottom: BorderSide(color: Styles.c_F8F9FA, width: 1.h),
),
),
child: Row(
children: [
Expanded(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AvatarView(
url: info.userFaceURL,
text: info.nickname,
),
10.horizontalSpace,
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
(info.nickname ?? '').toText
..style = Styles.ts_0C1C33_17sp
..maxLines = 1
..overflow = TextOverflow.ellipsis,
4.verticalSpace,
if (!logic.isInvite(info))
RichText(
text: TextSpan(
text: StrRes.applyJoin,
style: Styles.ts_8E9AB0_14sp,
children: [
WidgetSpan(child: 2.horizontalSpace),
TextSpan(
text: logic.getGroupName(info),
style: Styles.ts_0089FF_14sp,
),
],
),
)
else
RichText(
text: TextSpan(
text: logic.getInviterNickname(info),
style: Styles.ts_0089FF_14sp,
children: [
WidgetSpan(child: 2.horizontalSpace),
TextSpan(
text: StrRes.invite,
style: Styles.ts_8E9AB0_14sp,
),
WidgetSpan(child: 2.horizontalSpace),
TextSpan(
text: info.nickname,
style: Styles.ts_0089FF_14sp,
),
WidgetSpan(child: 2.horizontalSpace),
TextSpan(
text: StrRes.joinIn,
style: Styles.ts_8E9AB0_14sp,
),
WidgetSpan(child: 2.horizontalSpace),
TextSpan(
text: logic.getGroupName(info),
style: Styles.ts_0089FF_14sp,
),
],
),
),
if (null != IMUtils.emptyStrToNull(info.reqMsg))
Padding(
padding: EdgeInsets.only(top: 4.h),
child: sprintf(StrRes.applyReason, [info.reqMsg!]).toText
..style = Styles.ts_8E9AB0_14sp
..maxLines = 1
..overflow = TextOverflow.ellipsis,
),
],
),
)
],
),
),
if (/*info.handleResult == 0 && */ isISendRequest)
ImageRes.sendRequests.toImage
..width = 20.w
..height = 20.h,
if (info.handleResult == 0 && !isISendRequest)
Button(
text: StrRes.lookOver,
textStyle: Styles.ts_FFFFFF_14sp,
height: 28.h,
padding: EdgeInsets.symmetric(horizontal: 13.w),
onTap: () => logic.handle(info),
),
if (info.handleResult == 0 && isISendRequest)
StrRes.waitingForVerification.toText..style = Styles.ts_8E9AB0_14sp,
if (info.handleResult == -1) StrRes.rejected.toText..style = Styles.ts_8E9AB0_14sp,
if (info.handleResult == 1) StrRes.approved.toText..style = Styles.ts_8E9AB0_14sp,
],
),
);
}
}
@@ -0,0 +1,10 @@
import 'package:get/get.dart';
import 'process_group_requests_logic.dart';
class ProcessGroupRequestsBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => ProcessGroupRequestsLogic());
}
}
@@ -0,0 +1,71 @@
import 'package:flutter/services.dart';
import 'package:flutter_openim_sdk/flutter_openim_sdk.dart';
import 'package:get/get.dart';
import 'package:openim_common/openim_common.dart';
import '../group_requests_logic.dart';
class ProcessGroupRequestsLogic extends GetxController {
final groupRequestsLogic = Get.find<GroupRequestsLogic>();
late GroupApplicationInfo applicationInfo;
@override
void onInit() {
applicationInfo = Get.arguments['applicationInfo'];
super.onInit();
}
bool get isInvite => groupRequestsLogic.isInvite(applicationInfo);
String get groupName => groupRequestsLogic.getGroupName(applicationInfo);
String get inviterNickname => groupRequestsLogic.getInviterNickname(applicationInfo);
GroupMembersInfo? getMemberInfo(inviterUserID) => groupRequestsLogic.getMemberInfo(inviterUserID);
UserInfo? getUserInfo(inviterUserID) => groupRequestsLogic.getUserInfo(inviterUserID);
String get sourceFrom {
if (applicationInfo.joinSource == 2) {
return '$inviterNickname${StrRes.byMemberInvite}';
} else if (applicationInfo.joinSource == 4) {
return StrRes.byScanQrcode;
}
return StrRes.bySearch;
}
void approve() {
LoadingView.singleton
.wrap(
asyncFunction: () => OpenIM.iMManager.groupManager.acceptGroupApplication(
groupID: applicationInfo.groupID!,
userID: applicationInfo.userID!,
handleMsg: "reason",
))
.then((value) => Get.back(result: 1))
.catchError(_parse);
}
void reject() {
LoadingView.singleton
.wrap(
asyncFunction: () => OpenIM.iMManager.groupManager.refuseGroupApplication(
groupID: applicationInfo.groupID!,
userID: applicationInfo.userID!,
handleMsg: "reason",
))
.then((value) => Get.back(result: -1))
.catchError(_parse)
.catchError((_) => IMViews.showToast(StrRes.rejectFailed));
}
_parse(e) {
if (e is PlatformException) {
if (e.code == '${SDKErrorCode.groupApplicationHasBeenProcessed}') {
IMViews.showToast(StrRes.groupRequestHandled);
return;
}
}
throw e;
}
}
@@ -0,0 +1,127 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:openim_common/openim_common.dart';
import 'package:sprintf/sprintf.dart';
import 'process_group_requests_logic.dart';
class ProcessGroupRequestsPage extends StatelessWidget {
final logic = Get.find<ProcessGroupRequestsLogic>();
ProcessGroupRequestsPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: TitleBar.back(title: StrRes.newGroup),
backgroundColor: Styles.c_F8F9FA,
body: Container(
color: Styles.c_FFFFFF,
padding: EdgeInsets.symmetric(
horizontal: 16.w,
vertical: 16.h,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
AvatarView(
width: 48.w,
height: 48.h,
url: logic.applicationInfo.userFaceURL,
text: logic.applicationInfo.nickname,
),
10.horizontalSpace,
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
(logic.applicationInfo.nickname ?? '').toText..style = Styles.ts_0C1C33_17sp,
RichText(
text: TextSpan(
text: StrRes.applyJoin,
style: Styles.ts_8E9AB0_14sp,
children: [
WidgetSpan(child: 2.horizontalSpace),
TextSpan(
text: logic.groupName,
style: Styles.ts_0089FF_14sp,
),
],
),
)
],
),
],
),
12.verticalSpace,
if (IMUtils.isNotNullEmptyStr(logic.applicationInfo.reqMsg))
Container(
height: 80.h,
width: 343.w,
margin: EdgeInsets.only(bottom: 12.h),
decoration: BoxDecoration(
color: Styles.c_E8EAEF_opacity50,
borderRadius: BorderRadius.circular(6.r),
),
child: SingleChildScrollView(
padding: EdgeInsets.symmetric(
horizontal: 16.w,
vertical: 8.h,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
(logic.applicationInfo.reqMsg ?? '').toText..style = Styles.ts_0C1C33_17sp,
],
),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
sprintf(StrRes.sourceFrom, [logic.sourceFrom]).toText..style = Styles.ts_8E9AB0_14sp
],
),
12.verticalSpace,
Row(
children: [
Flexible(child: _buildRejectButton()),
12.horizontalSpace,
Flexible(
child: Button(
onTap: logic.approve,
text: StrRes.accept,
textStyle: Styles.ts_FFFFFF_17sp,
),
),
],
)
],
),
),
);
}
Widget _buildRejectButton() => Material(
child: Ink(
height: 44.h,
decoration: BoxDecoration(
color: Styles.c_FFFFFF,
border: Border.all(
color: Styles.c_E8EAEF,
width: 1,
),
borderRadius: BorderRadius.circular(6.r),
),
child: InkWell(
onTap: logic.reject,
child: Container(
alignment: Alignment.center,
child: StrRes.reject.toText..style = Styles.ts_0C1C33_17sp,
),
),
),
);
}
@@ -0,0 +1,10 @@
import 'package:get/get.dart';
import 'friend_list_logic.dart';
class SelectContactsFromFriendsBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => SelectContactsFromFriendsLogic());
}
}
@@ -0,0 +1,54 @@
import 'package:get/get.dart';
import 'package:openim/pages/contacts/friend_list/friend_list_logic.dart';
import 'package:openim/routes/app_navigator.dart';
import 'package:openim_common/openim_common.dart';
import '../select_contacts_logic.dart';
class SelectContactsFromFriendsLogic extends FriendListLogic {
final selectContactsLogic = Get.find<SelectContactsLogic>();
@override
searchFriend() async {
final result = await AppNavigator.startSelectContactsFromSearchFriends();
if (null != result) {
Get.back(result: result);
}
}
@override
void onUserIDList(List<String> userIDList) {
selectContactsLogic.updateDefaultCheckedList(userIDList);
super.onUserIDList(userIDList);
}
bool get isSelectAll {
if (selectContactsLogic.checkedList.isEmpty) {
return false;
} else if (operableList
.every((item) => selectContactsLogic.isChecked(item))) {
return true;
} else {
return false;
}
}
Iterable<ISUserInfo> get operableList => friendList.where(_remove);
bool _remove(ISUserInfo info) => !selectContactsLogic.isDefaultChecked(info);
selectAll() {
if (isSelectAll) {
for (var info in operableList) {
selectContactsLogic.removeItem(info);
}
} else {
for (var info in operableList) {
final isChecked = selectContactsLogic.isChecked(info);
if (!isChecked) {
selectContactsLogic.toggleChecked(info);
}
}
}
}
}
@@ -0,0 +1,101 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:openim_common/openim_common.dart';
import '../select_contacts_logic.dart';
import 'friend_list_logic.dart';
class SelectContactsFromFriendsPage extends StatelessWidget {
final logic = Get.find<SelectContactsFromFriendsLogic>();
final selectContactsLogic = Get.find<SelectContactsLogic>();
SelectContactsFromFriendsPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: TitleBar.back(title: StrRes.myFriend),
backgroundColor: Styles.c_F8F9FA,
body: Column(
children: [
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: logic.searchFriend,
child: Container(
color: Styles.c_FFFFFF,
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 10.h),
child: const SearchBox(),
),
),
if (selectContactsLogic.isMultiModel)
Padding(
padding: EdgeInsets.symmetric(vertical: 10.h),
child: Ink(
height: 64.h,
color: Styles.c_FFFFFF,
child: InkWell(
onTap: logic.selectAll,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 16.w),
child: Row(
children: [
Obx(() => Padding(
padding: EdgeInsets.only(right: 10.w),
child: ChatRadio(checked: logic.isSelectAll),
)),
10.horizontalSpace,
StrRes.selectAll.toText..style = Styles.ts_0C1C33_17sp,
],
),
),
),
),
),
Flexible(
child: Obx(
() => WrapAzListView<ISUserInfo>(
data: logic.friendList,
itemCount: logic.friendList.length,
itemBuilder: (_, data, index) => _buildItemView(data),
),
),
),
selectContactsLogic.checkedConfirmView,
],
),
);
}
Widget _buildItemView(ISUserInfo info) {
Widget buildChild() => Ink(
height: 64.h,
color: Styles.c_FFFFFF,
child: InkWell(
onTap: selectContactsLogic.onTap(info),
child: Container(
padding: EdgeInsets.symmetric(horizontal: 16.w),
child: Row(
children: [
if (selectContactsLogic.isMultiModel)
Padding(
padding: EdgeInsets.only(right: 10.w),
child: ChatRadio(
checked: selectContactsLogic.isChecked(info),
enabled: !selectContactsLogic.isDefaultChecked(info),
),
),
AvatarView(
url: info.faceURL,
text: info.showName,
),
10.horizontalSpace,
info.showName.toText..style = Styles.ts_0C1C33_17sp,
],
),
),
),
);
return selectContactsLogic.isMultiModel ? Obx(buildChild) : buildChild();
}
}
@@ -0,0 +1,10 @@
import 'package:get/get.dart';
import 'search_friend_logic.dart';
class SelectContactsFromSearchFriendsBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => SelectContactsFromSearchFriendsLogic());
}
}
@@ -0,0 +1,46 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:openim_common/openim_common.dart';
import '../friend_list_logic.dart';
class SelectContactsFromSearchFriendsLogic extends GetxController {
final logic = Get.find<SelectContactsFromFriendsLogic>();
final focusNode = FocusNode();
final searchCtrl = TextEditingController();
final resultList = <ISUserInfo>[].obs;
@override
void onInit() {
searchCtrl.addListener(_clearInput);
super.onInit();
}
@override
void onClose() {
focusNode.dispose();
searchCtrl.dispose();
super.onClose();
}
bool get isSearchNotResult => searchCtrl.text.trim().isNotEmpty && resultList.isEmpty;
_clearInput() {
final key = searchCtrl.text.trim();
if (key.isEmpty) {
resultList.clear();
}
}
search() {
var key = searchCtrl.text.trim();
resultList.clear();
if (key.isNotEmpty) {
for (var element in logic.friendList) {
if (element.showName.toUpperCase().contains(key.toUpperCase())) {
resultList.add(element);
}
}
}
}
}
@@ -0,0 +1,84 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:openim_common/openim_common.dart';
import 'package:search_keyword_text/search_keyword_text.dart';
import '../../select_contacts_logic.dart';
import 'search_friend_logic.dart';
class SelectContactsFromSearchFriendsPage extends StatelessWidget {
final logic = Get.find<SelectContactsFromSearchFriendsLogic>();
final selectContactsLogic = Get.find<SelectContactsLogic>();
SelectContactsFromSearchFriendsPage({super.key});
@override
Widget build(BuildContext context) {
return TouchCloseSoftKeyboard(
child: Scaffold(
appBar: TitleBar.search(
focusNode: logic.focusNode,
controller: logic.searchCtrl,
onSubmitted: (_) => logic.search(),
onCleared: () => logic.focusNode.requestFocus(),
),
backgroundColor: Styles.c_F8F9FA,
body: Obx(() => logic.isSearchNotResult
? _emptyListView
: ListView.builder(
itemCount: logic.resultList.length,
itemBuilder: (_, index) => _buildItemView(logic.resultList[index]),
)),
),
);
}
Widget _buildItemView(ISUserInfo info) {
Widget buildChild() => Ink(
height: 64.h,
color: Styles.c_FFFFFF,
child: InkWell(
onTap: selectContactsLogic.onTap(info),
child: Container(
padding: EdgeInsets.symmetric(horizontal: 16.w),
child: Row(
children: [
if (selectContactsLogic.isMultiModel)
Padding(
padding: EdgeInsets.only(right: 10.w),
child: ChatRadio(
checked: selectContactsLogic.isChecked(info),
enabled: !selectContactsLogic.isDefaultChecked(info),
),
),
AvatarView(
url: info.faceURL,
text: info.showName,
),
10.horizontalSpace,
SearchKeywordText(
text: info.showName,
keyText: logic.searchCtrl.text.trim(),
style: Styles.ts_0C1C33_17sp,
keyStyle: Styles.ts_0089FF_17sp,
),
],
),
),
),
);
return selectContactsLogic.isMultiModel ? Obx(buildChild) : buildChild();
}
Widget get _emptyListView => SizedBox(
width: 1.sw,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
44.verticalSpace,
StrRes.searchNotFound.toText..style = Styles.ts_8E9AB0_17sp,
],
),
);
}
@@ -0,0 +1,10 @@
import 'package:get/get.dart';
import 'group_list_logic.dart';
class SelectContactsFromGroupBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => SelectContactsFromGroupLogic());
}
}
@@ -0,0 +1,58 @@
import 'package:flutter_openim_sdk/flutter_openim_sdk.dart';
import 'package:get/get.dart';
import '../../../../routes/app_navigator.dart';
import '../select_contacts_logic.dart';
class SelectContactsFromGroupLogic extends GetxController {
final selectContactsLogic = Get.find<SelectContactsLogic>();
final allList = <GroupInfo>[].obs;
@override
void onReady() {
_getGroupRelatedToMe();
super.onReady();
}
void _getGroupRelatedToMe() async {
final list = await OpenIM.iMManager.groupManager.getJoinedGroupList();
allList.addAll(list);
}
Iterable<GroupInfo> get operableList => allList.where(_remove);
bool _remove(GroupInfo info) => !selectContactsLogic.isDefaultChecked(info);
bool get isSelectAll {
if (selectContactsLogic.checkedList.isEmpty) {
return false;
} else if (operableList
.every((item) => selectContactsLogic.isChecked(item))) {
return true;
} else {
return false;
}
}
selectAll() {
if (isSelectAll) {
for (var info in operableList) {
selectContactsLogic.removeItem(info);
}
} else {
for (var info in operableList) {
final isChecked = selectContactsLogic.isChecked(info);
if (!isChecked) {
selectContactsLogic.toggleChecked(info);
}
}
}
}
void searchGroup() async {
final result = await AppNavigator.startSelectContactsFromSearchGroup();
if (null != result) {
Get.back(result: result);
}
}
}
@@ -0,0 +1,114 @@
import 'package:flutter/material.dart';
import 'package:flutter_openim_sdk/flutter_openim_sdk.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:openim_common/openim_common.dart';
import 'package:sprintf/sprintf.dart';
import '../select_contacts_logic.dart';
import 'group_list_logic.dart';
class SelectContactsFromGroupPage extends StatelessWidget {
final logic = Get.find<SelectContactsFromGroupLogic>();
final selectContactsLogic = Get.find<SelectContactsLogic>();
SelectContactsFromGroupPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: TitleBar.back(title: StrRes.myGroup),
backgroundColor: Styles.c_F8F9FA,
body: Column(
children: [
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: logic.searchGroup,
child: Container(
color: Styles.c_FFFFFF,
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 10.h),
child: const SearchBox(),
),
),
if (selectContactsLogic.isMultiModel)
Padding(
padding: EdgeInsets.symmetric(vertical: 10.h),
child: Ink(
height: 64.h,
color: Styles.c_FFFFFF,
child: InkWell(
onTap: logic.selectAll,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 16.w),
child: Row(
children: [
Obx(() => Padding(
padding: EdgeInsets.only(right: 10.w),
child: ChatRadio(checked: logic.isSelectAll),
)),
10.horizontalSpace,
StrRes.selectAll.toText..style = Styles.ts_0C1C33_17sp,
],
),
),
),
),
),
Expanded(
child: Obx(() => ListView.builder(
itemCount: logic.allList.length,
itemBuilder: (_, index) =>
_buildItemView(logic.allList[index]),
))),
selectContactsLogic.checkedConfirmView,
],
),
);
}
Widget _buildItemView(GroupInfo info) {
Widget buildChild() => Ink(
height: 64.h,
color: Styles.c_FFFFFF,
child: InkWell(
onTap: selectContactsLogic.onTap(info),
child: Container(
padding: EdgeInsets.symmetric(horizontal: 16.w),
child: Row(
children: [
if (selectContactsLogic.isMultiModel)
Padding(
padding: EdgeInsets.only(right: 10.w),
child: ChatRadio(
checked: selectContactsLogic.isChecked(info),
enabled: !selectContactsLogic.isDefaultChecked(info),
),
),
AvatarView(
url: info.faceURL,
text: info.groupName,
isGroup: true,
),
10.horizontalSpace,
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
(info.groupName ?? '').toText
..style = Styles.ts_0C1C33_17sp
..maxLines = 1
..overflow = TextOverflow.ellipsis,
sprintf(StrRes.nPerson, [info.memberCount]).toText
..style = Styles.ts_8E9AB0_14sp,
],
),
),
],
),
),
),
);
return selectContactsLogic.isMultiModel ? Obx(buildChild) : buildChild();
}
}
@@ -0,0 +1,10 @@
import 'package:get/get.dart';
import 'search_group_logic.dart';
class SelectContactsFromSearchGroupBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => SelectContactsFromSearchGroupLogic());
}
}
@@ -0,0 +1,49 @@
import 'package:flutter/material.dart';
import 'package:flutter_openim_sdk/flutter_openim_sdk.dart';
import 'package:get/get.dart';
import '../group_list_logic.dart';
class SelectContactsFromSearchGroupLogic extends GetxController {
final logic = Get.find<SelectContactsFromGroupLogic>();
final focusNode = FocusNode();
final searchCtrl = TextEditingController();
final resultList = <GroupInfo>[].obs;
@override
void onInit() {
searchCtrl.addListener(_clearInput);
super.onInit();
}
@override
void onClose() {
focusNode.dispose();
searchCtrl.dispose();
super.onClose();
}
bool get isSearchNotResult =>
searchCtrl.text.trim().isNotEmpty && resultList.isEmpty;
_clearInput() {
final key = searchCtrl.text.trim();
if (key.isEmpty) {
resultList.clear();
}
}
search() {
var key = searchCtrl.text.trim();
resultList.clear();
if (key.isNotEmpty) {
for (var element in logic.allList) {
if ((element.groupName ?? '')
.toUpperCase()
.contains(key.toUpperCase())) {
resultList.add(element);
}
}
}
}
}
@@ -0,0 +1,99 @@
import 'package:flutter/material.dart';
import 'package:flutter_openim_sdk/flutter_openim_sdk.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:openim_common/openim_common.dart';
import 'package:search_keyword_text/search_keyword_text.dart';
import 'package:sprintf/sprintf.dart';
import '../../select_contacts_logic.dart';
import 'search_group_logic.dart';
class SelectContactsFromSearchGroupPage extends StatelessWidget {
final logic = Get.find<SelectContactsFromSearchGroupLogic>();
final selectContactsLogic = Get.find<SelectContactsLogic>();
SelectContactsFromSearchGroupPage({super.key});
@override
Widget build(BuildContext context) {
return TouchCloseSoftKeyboard(
child: Scaffold(
appBar: TitleBar.search(
focusNode: logic.focusNode,
controller: logic.searchCtrl,
onSubmitted: (_) => logic.search(),
onCleared: () => logic.focusNode.requestFocus(),
),
backgroundColor: Styles.c_F8F9FA,
body: Obx(() => logic.isSearchNotResult
? _emptyListView
: ListView.builder(
itemCount: logic.resultList.length,
itemBuilder: (_, index) => _buildItemView(logic.resultList[index]),
)),
),
);
}
Widget _buildItemView(GroupInfo info) {
Widget buildChild() => Ink(
height: 64.h,
color: Styles.c_FFFFFF,
child: InkWell(
onTap: () {
selectContactsLogic.toggleChecked(info);
},
child: Container(
padding: EdgeInsets.symmetric(horizontal: 16.w),
child: Row(
children: [
if (selectContactsLogic.isMultiModel)
Padding(
padding: EdgeInsets.only(right: 10.w),
child: ChatRadio(
checked: selectContactsLogic.isChecked(info),
),
),
AvatarView(
url: info.faceURL,
text: info.groupName,
isGroup: true,
),
10.horizontalSpace,
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
SearchKeywordText(
text: info.groupName ?? '',
keyText: logic.searchCtrl.text.trim(),
style: Styles.ts_0C1C33_17sp,
keyStyle: Styles.ts_0089FF_17sp,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
sprintf(StrRes.nPerson, [info.memberCount]).toText..style = Styles.ts_8E9AB0_14sp,
],
),
),
],
),
),
),
);
return selectContactsLogic.isMultiModel ? Obx(buildChild) : buildChild();
}
Widget get _emptyListView => SizedBox(
width: 1.sw,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
44.verticalSpace,
StrRes.searchNotFound.toText..style = Styles.ts_8E9AB0_17sp,
],
),
);
}
@@ -0,0 +1,10 @@
import 'package:get/get.dart';
import 'search_contacts_logic.dart';
class SelectContactsFromSearchBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => SelectContactsFromSearchLogic());
}
}
@@ -0,0 +1,45 @@
import 'package:flutter_openim_sdk/flutter_openim_sdk.dart';
import 'package:get/get.dart';
import 'package:openim_common/openim_common.dart';
import '../../../global_search/global_search_logic.dart';
import '../select_contacts_logic.dart';
class SelectContactsFromSearchLogic extends CommonSearchLogic {
final selectContactsLogic = Get.find<SelectContactsLogic>();
final resultList = <dynamic>{}.obs;
bool get isSearchNotResult => searchCtrl.text.trim().isNotEmpty && resultList.isEmpty;
@override
void clearList() {
resultList.clear();
}
void search() async {
final result = await LoadingView.singleton.wrap(
asyncFunction: () => Future.wait([
searchFriend(),
if (!selectContactsLogic.hiddenGroup) searchGroup(),
]));
final friendList = result[0] as List<FriendInfo>;
clearList();
resultList
..addAll(friendList);
if (selectContactsLogic.action == SelAction.addMember) {
var memberInfoList = await getMemberInfo(friendList.map((e) => e.userID!).toList());
for (var element in memberInfoList) {
selectContactsLogic.defaultCheckedIDList.add(element.userID!);
}
}
if (result.length == 3) {
final groupList = result[2] as List<GroupInfo>;
resultList.addAll(groupList);
}
}
Future<List<GroupMembersInfo>> getMemberInfo(List<String> uidList) async {
return await OpenIM.iMManager.groupManager
.getGroupMembersInfo(groupID: selectContactsLogic.groupID!, userIDList: uidList);
}
}
@@ -0,0 +1,94 @@
import 'package:flutter/material.dart';
import 'package:flutter_openim_sdk/flutter_openim_sdk.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:openim_common/openim_common.dart';
import 'package:search_keyword_text/search_keyword_text.dart';
import '../select_contacts_logic.dart';
import 'search_contacts_logic.dart';
class SelectContactsFromSearchPage extends StatelessWidget {
final logic = Get.find<SelectContactsFromSearchLogic>();
final selectContactsLogic = Get.find<SelectContactsLogic>();
SelectContactsFromSearchPage({super.key});
@override
Widget build(BuildContext context) {
return TouchCloseSoftKeyboard(
child: Scaffold(
appBar: TitleBar.search(
focusNode: logic.focusNode,
controller: logic.searchCtrl,
onSubmitted: (_) => logic.search(),
onCleared: () => logic.focusNode.requestFocus(),
),
backgroundColor: Styles.c_F8F9FA,
body: Obx(() => logic.isSearchNotResult
? _emptyListView
: ListView.builder(
itemCount: logic.resultList.length,
itemBuilder: (_, index) => _buildItemView(logic.resultList.elementAt(index)),
)),
),
);
}
Widget _buildItemView(dynamic info) {
Widget buildChild() => Ink(
height: 64.h,
color: Styles.c_FFFFFF,
child: InkWell(
onTap: selectContactsLogic.onTap(info),
child: Container(
padding: EdgeInsets.symmetric(horizontal: 16.w),
child: Row(
children: [
if (selectContactsLogic.isMultiModel)
Padding(
padding: EdgeInsets.only(right: 10.w),
child: ChatRadio(
checked: selectContactsLogic.isChecked(info),
enabled: !selectContactsLogic.isDefaultChecked(info),
),
),
AvatarView(
url: logic.parseFaceURL(info),
text: logic.parseNickname(info),
isGroup: info is GroupInfo,
),
10.horizontalSpace,
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
SearchKeywordText(
text: logic.parseNickname(info) ?? '',
keyText: logic.searchCtrl.text.trim(),
style: Styles.ts_0C1C33_17sp,
keyStyle: Styles.ts_0089FF_17sp,
),
],
),
),
],
),
),
),
);
return selectContactsLogic.isMultiModel ? Obx(buildChild) : buildChild();
}
Widget get _emptyListView => SizedBox(
width: 1.sw,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
44.verticalSpace,
StrRes.searchNotFound.toText..style = Styles.ts_8E9AB0_17sp,
],
),
);
}
@@ -0,0 +1,10 @@
import 'package:get/get.dart';
import 'select_contacts_logic.dart';
class SelectContactsBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => SelectContactsLogic());
}
}
@@ -0,0 +1,226 @@
import 'package:flutter/material.dart';
import 'package:flutter_openim_sdk/flutter_openim_sdk.dart';
import 'package:get/get.dart';
import 'package:openim/pages/conversation/conversation_logic.dart';
import 'package:openim/routes/app_navigator.dart';
import 'package:openim_common/openim_common.dart';
import 'select_contacts_view.dart';
enum SelAction {
forward,
carte,
crateGroup,
addMember,
recommend,
}
class SelectContactsLogic extends GetxController implements OrganizationMultiSelBridge {
final checkedList = <String, dynamic>{}.obs; // 已经选中的
final defaultCheckedIDList = <String>{}.obs; // 默认选中,且不能修改
List<String>? excludeIDList; // 剔除某些数据
late SelAction action;
late bool openSelectedSheet;
String? groupID;
final conversationList = <ConversationInfo>[].obs;
String? ex;
final inputCtrl = TextEditingController();
@override
void onInit() {
action = Get.arguments['action'];
groupID = Get.arguments['groupID'];
excludeIDList = Get.arguments['excludeIDList'];
defaultCheckedIDList.addAll(Get.arguments['defaultCheckedIDList'] ?? []);
checkedList.addAll(Get.arguments['checkedList'] ?? {});
openSelectedSheet = Get.arguments['openSelectedSheet'];
ex = Get.arguments['ex'];
super.onInit();
}
@override
void onClose() {
inputCtrl.dispose();
super.onClose();
}
@override
void onReady() {
_queryConversationList();
if (openSelectedSheet) viewSelectedContactsList();
super.onReady();
}
@override
bool get isMultiModel => action != SelAction.carte;
bool get hiddenGroup => action == SelAction.carte || action == SelAction.crateGroup || action == SelAction.addMember;
bool get hiddenConversations =>
action == SelAction.carte || action == SelAction.crateGroup || action == SelAction.addMember;
_queryConversationList() async {
if (!hiddenConversations) {
final cons = Get.find<ConversationLogic>().list;
final futures = cons.map((con) async {
if (con.isGroupChat) {
final result = await OpenIM.iMManager.groupManager.isJoinedGroup(groupID: con.groupID!);
return result ? con : null;
}
return con.conversationType == ConversationType.notification ? null : con;
}).toList();
final results = await Future.wait(futures);
final filteredCons = results.where((con) => con != null).cast<ConversationInfo>().toList();
conversationList.addAll(filteredCons);
}
}
static String? parseID(e) {
if (e is ConversationInfo) {
return e.isSingleChat ? e.userID : e.groupID;
} else if (e is GroupInfo) {
return e.groupID;
} else if (e is UserInfo || e is FriendInfo || e is UserFullInfo) {
return e.userID;
} else {
return null;
}
}
static String? parseName(e) {
if (e is ConversationInfo) {
return e.showName;
} else if (e is GroupInfo) {
return e.groupName;
} else if (e is UserInfo || e is FriendInfo || e is UserFullInfo) {
return e.nickname;
} else {
return null;
}
}
static String? parseFaceURL(e) {
if (e is ConversationInfo) {
return e.faceURL;
} else if (e is GroupInfo) {
return e.faceURL;
} else if (e is UserInfo || e is FriendInfo || e is UserFullInfo) {
return e.faceURL;
} else {
return null;
}
}
@override
bool isChecked(info) => checkedList.containsKey(parseID(info));
@override
bool isDefaultChecked(info) => defaultCheckedIDList.contains(parseID(info));
@override
Function()? onTap(dynamic info) => isDefaultChecked(info) ? null : () => toggleChecked(info);
@override
removeItem(dynamic info) {
checkedList.remove(parseID(info));
}
@override
toggleChecked(dynamic info) {
if (isMultiModel) {
final key = parseID(info);
if (checkedList.containsKey(key)) {
checkedList.remove(key);
} else {
checkedList.putIfAbsent(key ?? '', () => info);
}
} else {
confirmSelectedItem(info);
}
}
@override
updateDefaultCheckedList(List<String> userIDList) async {
if (groupID != null) {
var list = await OpenIM.iMManager.groupManager.getGroupMembersInfo(
groupID: groupID!,
userIDList: userIDList,
);
defaultCheckedIDList.addAll(list.map((e) => e.userID!));
}
}
String get checkedStrTips => checkedList.values.map(parseName).join('');
viewSelectedContactsList() => Get.bottomSheet(
SelectedContactsListView(),
isScrollControlled: true,
);
selectFromMyFriend() async {
final result = await AppNavigator.startSelectContactsFromFriends();
if (null != result) {
Get.back(result: result);
}
}
selectFromMyGroup() async {
final result = await AppNavigator.startSelectContactsFromGroup();
if (null != result) {
Get.back(result: result);
}
}
void selectFromSearch() async {
final result = await AppNavigator.startSelectContactsFromSearch();
if (null != result) {
Get.back(result: result);
}
}
selectTagGroup() async {
final result = await await AppNavigator.startSelectContactsFromTag();
if (null != result) {
Get.back(result: result);
}
}
confirmSelectedList() async {
if (action == SelAction.forward || action == SelAction.recommend) {
final sure = await Get.dialog(ForwardHintDialog(
title: ex ?? '',
checkedList: checkedList.values.toList(),
));
if (sure == true) {
Get.back(result: {
"checkedList": checkedList.values,
});
}
} else {
Get.back(result: checkedList.value);
}
}
confirmSelectedItem(dynamic info) async {
if (action == SelAction.carte) {
final sure = await Get.dialog(CustomDialog(
title: StrRes.sendCarteConfirmHint,
));
if (sure == true) {
Get.back(result: UserInfo.fromJson(info.toJson()));
}
}
}
bool get enabledConfirmButton => checkedList.isNotEmpty;
@override
Widget get checkedConfirmView => isMultiModel ? CheckedConfirmView() : const SizedBox();
}
@@ -0,0 +1,309 @@
import 'package:flutter/material.dart';
import 'package:flutter_openim_sdk/flutter_openim_sdk.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:openim_common/openim_common.dart';
import 'package:sprintf/sprintf.dart';
import 'select_contacts_logic.dart';
class SelectContactsPage extends StatelessWidget {
final logic = Get.find<SelectContactsLogic>();
SelectContactsPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: TitleBar.back(),
backgroundColor: Styles.c_F8F9FA,
body: Column(
children: [
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: logic.selectFromSearch,
child: Container(
color: Styles.c_FFFFFF,
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 10.h),
child: const SearchBox(),
),
),
10.verticalSpace,
Flexible(
child: Obx(() => CustomScrollView(
slivers: [
SliverFixedExtentList(
delegate: SliverChildListDelegate(
[
_buildCategoryItemView(
label: StrRes.myFriend,
onTap: logic.selectFromMyFriend,
),
if (!logic.hiddenGroup)
_buildCategoryItemView(
label: StrRes.myGroup,
onTap: logic.selectFromMyGroup,
),
],
),
itemExtent: 56.h,
),
if (logic.conversationList.isNotEmpty)
SliverToBoxAdapter(
child: Container(
height: 29.h,
alignment: Alignment.centerLeft,
margin: EdgeInsets.symmetric(horizontal: 16.w),
child: StrRes.recentConversations.toText..style = Styles.ts_8E9AB0_12sp,
),
),
SliverFixedExtentList(
delegate: SliverChildBuilderDelegate(
childCount: logic.conversationList.length,
(_, index) => _buildRecentConversationsItemView(
logic.conversationList.elementAt(index),
),
),
itemExtent: 64.h,
),
],
)),
),
logic.checkedConfirmView,
],
),
);
}
Widget _buildCategoryItemView({
required String label,
Function()? onTap,
}) =>
Ink(
height: 56.h,
color: Styles.c_FFFFFF,
child: InkWell(
onTap: onTap,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 16.w),
child: Row(
children: [
label.toText..style = Styles.ts_0C1C33_17sp,
const Spacer(),
ImageRes.rightArrow.toImage
..width = 24.w
..height = 24.h,
],
),
),
),
);
Widget _buildRecentConversationsItemView(ConversationInfo info) {
Widget buildChild() => Ink(
height: 56.h,
color: Styles.c_FFFFFF,
child: InkWell(
onTap: logic.onTap(info),
child: Container(
padding: EdgeInsets.symmetric(horizontal: 16.w),
child: Row(
children: [
if (logic.isMultiModel)
Padding(
padding: EdgeInsets.only(right: 10.w),
child: ChatRadio(
checked: logic.isChecked(info),
),
),
AvatarView(
url: info.faceURL,
text: info.showName,
isGroup: !info.isSingleChat,
),
10.horizontalSpace,
Flexible(
child: (info.showName ?? '').toText
..style = Styles.ts_0C1C33_17sp
..maxLines = 1
..overflow = TextOverflow.ellipsis,
),
],
),
),
),
);
return logic.isMultiModel ? Obx(buildChild) : buildChild();
}
}
class CheckedConfirmView extends StatelessWidget {
CheckedConfirmView({Key? key}) : super(key: key);
final logic = Get.find<SelectContactsLogic>();
@override
Widget build(BuildContext context) {
return Container(
height: 66.h,
decoration: BoxDecoration(
color: Styles.c_FFFFFF,
boxShadow: [
BoxShadow(
offset: Offset(0, -1.h),
blurRadius: 4.r,
spreadRadius: 1.r,
color: Styles.c_000000_opacity4,
),
],
),
padding: EdgeInsets.symmetric(horizontal: 16.w),
child: Obx(() => Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: logic.viewSelectedContactsList,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
children: [
sprintf(StrRes.selectedPeopleCount, [logic.checkedList.length]).toText
..style = Styles.ts_0089FF_14sp,
ImageRes.expandUpArrow.toImage
..width = 24.w
..height = 24.h,
],
),
if (logic.checkedList.isNotEmpty) 4.verticalSpace,
logic.checkedStrTips.toText
..style = Styles.ts_8E9AB0_14sp
..maxLines = 1
..overflow = TextOverflow.ellipsis,
],
),
),
),
Button(
height: 40.h,
enabled: logic.enabledConfirmButton,
padding: EdgeInsets.symmetric(horizontal: 14.w),
text: sprintf(StrRes.confirmSelectedPeople, [
logic.checkedList.length,
'999',
]),
textStyle: Styles.ts_FFFFFF_14sp,
onTap: logic.confirmSelectedList,
),
],
)),
);
}
}
class SelectedContactsListView extends StatelessWidget {
SelectedContactsListView({Key? key}) : super(key: key);
final logic = Get.find<SelectContactsLogic>();
@override
Widget build(BuildContext context) {
return Container(
constraints: BoxConstraints(maxHeight: 548.h),
decoration: BoxDecoration(
color: Styles.c_FFFFFF,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(6.r),
topRight: Radius.circular(6.r),
),
),
child: Obx(() => Column(
children: [
Container(
padding: EdgeInsets.symmetric(horizontal: 16.w),
decoration: BoxDecoration(
border: BorderDirectional(
bottom: BorderSide(color: Styles.c_E8EAEF, width: 1),
),
),
child: Row(
children: [
sprintf(StrRes.selectedPeopleCount, [logic.checkedList.length]).toText
..style = Styles.ts_0C1C33_17sp_medium,
const Spacer(),
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () => Get.back(),
child: Container(
height: 52.h,
alignment: Alignment.center,
child: StrRes.confirm.toText..style = Styles.ts_0089FF_17sp,
),
),
],
),
),
Expanded(
child: ListView.builder(
itemCount: logic.checkedList.length,
shrinkWrap: true,
itemBuilder: (_, index) => _buildItemView(index),
),
),
],
)),
);
}
Widget _buildItemView(int index) {
final info = logic.checkedList.values.elementAt(index);
String? name;
String? faceURL;
bool isGroup = false;
name = SelectContactsLogic.parseName(info);
faceURL = SelectContactsLogic.parseFaceURL(info);
if (info is ConversationInfo) {
isGroup = !info.isSingleChat;
} else if (info is GroupInfo) {
isGroup = true;
name = info.groupName;
faceURL = info.faceURL;
} else if (info is UserInfo) {
name = info.nickname;
faceURL = info.faceURL;
}
return Container(
height: 64.h,
padding: EdgeInsets.symmetric(horizontal: 16.w),
color: Styles.c_FFFFFF,
child: Row(
children: [
AvatarView(url: faceURL, text: name, isGroup: isGroup),
10.horizontalSpace,
Expanded(
child: (name ?? '').toText
..style = Styles.ts_0C1C33_17sp
..maxLines = 1
..overflow = TextOverflow.ellipsis,
),
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () => logic.removeItem(info),
child: Container(
padding: EdgeInsets.symmetric(horizontal: 13.w, vertical: 4.h),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(2.r),
border: Border.all(
color: Styles.c_E8EAEF,
width: 1,
),
),
child: StrRes.remove.toText..style = Styles.ts_0089FF_17sp,
),
),
],
),
);
}
}
@@ -0,0 +1,10 @@
import 'package:get/get.dart';
import 'send_verification_application_logic.dart';
class SendVerificationApplicationBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => SendVerificationApplicationLogic());
}
}
@@ -0,0 +1,69 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
import 'package:flutter_openim_sdk/flutter_openim_sdk.dart';
import 'package:get/get.dart';
import 'package:openim_common/openim_common.dart';
import '../group_profile_panel/group_profile_panel_logic.dart';
class SendVerificationApplicationLogic extends GetxController {
final inputCtrl = TextEditingController();
String? userID;
String? groupID;
JoinGroupMethod? joinGroupMethod;
bool get isEnterGroup => groupID != null;
bool get isAddFriend => userID != null;
@override
void onInit() {
userID = Get.arguments['userID'];
groupID = Get.arguments['groupID'];
joinGroupMethod = Get.arguments['joinGroupMethod'];
super.onInit();
}
void send() async {
if (isAddFriend) {
_applyAddFriend();
} else if (isEnterGroup) {
_applyEnterGroup();
}
}
_applyAddFriend() async {
try {
await LoadingView.singleton.wrap(
asyncFunction: () => OpenIM.iMManager.friendshipManager.addFriend(
userID: userID!,
reason: inputCtrl.text.trim(),
),
);
Get.back();
IMViews.showToast(StrRes.sendSuccessfully);
} catch (_) {
if (_ is PlatformException) {
if (_.code == '${SDKErrorCode.refuseToAddFriends}') {
IMViews.showToast(StrRes.canNotAddFriends);
return;
}
}
IMViews.showToast(StrRes.sendFailed);
}
}
_applyEnterGroup() {
LoadingView.singleton
.wrap(
asyncFunction: () => OpenIM.iMManager.groupManager.joinGroup(
groupID: groupID!,
reason: inputCtrl.text.trim(),
joinSource: joinGroupMethod == JoinGroupMethod.qrcode ? 4 : 3,
),
)
.then((value) => IMViews.showToast(StrRes.sendSuccessfully))
.then((value) => Get.back())
.catchError((e) => IMViews.showToast(StrRes.sendFailed));
}
}
@@ -0,0 +1,57 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:openim_common/openim_common.dart';
import 'send_verification_application_logic.dart';
class SendVerificationApplicationPage extends StatelessWidget {
final logic = Get.find<SendVerificationApplicationLogic>();
SendVerificationApplicationPage({super.key});
@override
Widget build(BuildContext context) {
return TouchCloseSoftKeyboard(
child: Scaffold(
appBar: TitleBar.back(
title: logic.isEnterGroup ? StrRes.groupVerification : StrRes.friendVerification,
right: StrRes.send.toText
..style = Styles.ts_0C1C33_17sp
..onTap = logic.send,
),
backgroundColor: Styles.c_F8F9FA,
body: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.symmetric(vertical: 6.h, horizontal: 16.w),
child: (logic.isEnterGroup ? StrRes.sendEnterGroupApplication : StrRes.sendToBeFriendApplication).toText
..style = Styles.ts_8E9AB0_14sp,
),
Container(
height: 122.h,
color: Styles.c_FFFFFF,
padding: EdgeInsets.symmetric(vertical: 12.h),
child: TextField(
controller: logic.inputCtrl,
autofocus: true,
maxLines: 10,
maxLength: 20,
decoration: InputDecoration(
isDense: true,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(
horizontal: 16.w,
),
),
),
),
],
),
),
),
);
}
}
@@ -0,0 +1,10 @@
import 'package:get/get.dart';
import 'friend_setup_logic.dart';
class FriendSetupBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => FriendSetupLogic());
}
}
@@ -0,0 +1,128 @@
import 'package:flutter_openim_sdk/flutter_openim_sdk.dart';
import 'package:get/get.dart';
import 'package:openim_common/openim_common.dart';
import '../../../../routes/app_navigator.dart';
import '../../../chat/chat_logic.dart';
import '../../../conversation/conversation_logic.dart';
import '../../select_contacts/select_contacts_logic.dart';
import '../user_profile _panel_logic.dart';
class FriendSetupLogic extends GetxController {
final conversationLogic = Get.find<ConversationLogic>();
final userProfilesLogic = Get.find<UserProfilePanelLogic>(tag: GetTags.userProfile);
late String userID;
@override
void onInit() {
userID = Get.arguments['userID'];
super.onInit();
}
void toggleBlacklist() {
if (userProfilesLogic.userInfo.value.isBlacklist == true) {
removeBlacklist();
} else {
addBlacklist();
}
}
void addBlacklist() async {
var confirm = await Get.dialog(CustomDialog(title: StrRes.areYouSureAddBlacklist));
if (confirm == true) {
await OpenIM.iMManager.friendshipManager.addBlacklist(
userID: userProfilesLogic.userInfo.value.userID!,
);
userProfilesLogic.userInfo.update((val) {
val?.isBlacklist = true;
});
}
}
void removeBlacklist() async {
await OpenIM.iMManager.friendshipManager.removeBlacklist(
userID: userProfilesLogic.userInfo.value.userID!,
);
userProfilesLogic.userInfo.update((val) {
val?.isBlacklist = false;
});
}
void deleteFromFriendList() async {
var confirm = await Get.dialog(CustomDialog(
title: StrRes.areYouSureDelFriend,
rightText: StrRes.delete,
));
if (confirm) {
await LoadingView.singleton.wrap(asyncFunction: () async {
await OpenIM.iMManager.friendshipManager.deleteFriend(
userID: userProfilesLogic.userInfo.value.userID!,
);
userProfilesLogic.userInfo.update((val) {
val?.isFriendship = false;
});
final userIDList = [
userProfilesLogic.userInfo.value.userID,
OpenIM.iMManager.userID,
];
userIDList.sort();
final conversationID = 'si_${userIDList.join('_')}';
await OpenIM.iMManager.conversationManager.deleteConversationAndDeleteAllMsg(conversationID: conversationID);
conversationLogic.list.removeWhere((e) => e.conversationID == conversationID);
});
if (userProfilesLogic.offAllWhenDelFriend == true) {
AppNavigator.startBackMain();
} else {
Get.back();
}
}
}
recommendToFriend() async {
final isRegistered = Get.isRegistered<ChatLogic>(tag: GetTags.chat);
if (isRegistered) {
final logic = Get.find<ChatLogic>(tag: GetTags.chat);
logic.recommendFriendCarte(UserInfo.fromJson(userProfilesLogic.userInfo.value.toJson()));
return;
}
final result = await AppNavigator.startSelectContacts(
action: SelAction.recommend,
ex: '[${StrRes.carte}]${userProfilesLogic.userInfo.value.nickname}',
);
if (null != result) {
final customEx = result['customEx'];
final checkedList = result['checkedList'];
for (var info in checkedList) {
final userID = IMUtils.convertCheckedToUserID(info);
final groupID = IMUtils.convertCheckedToGroupID(info);
if (customEx is String && customEx.isNotEmpty) {
OpenIM.iMManager.messageManager.sendMessage(
message: await OpenIM.iMManager.messageManager.createTextMessage(
text: customEx,
),
userID: userID,
groupID: groupID,
offlinePushInfo: Config.offlinePushInfo,
);
}
OpenIM.iMManager.messageManager.sendMessage(
message: await OpenIM.iMManager.messageManager.createCardMessage(
userID: userProfilesLogic.userInfo.value.userID!,
nickname: userProfilesLogic.userInfo.value.showName,
faceURL: userProfilesLogic.userInfo.value.faceURL,
),
userID: userID,
groupID: groupID,
offlinePushInfo: Config.offlinePushInfo,
);
}
}
}
void setFriendRemark() => AppNavigator.startSetFriendRemark();
}
@@ -0,0 +1,105 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:openim_common/openim_common.dart';
import 'friend_setup_logic.dart';
class FriendSetupPage extends StatelessWidget {
final logic = Get.find<FriendSetupLogic>();
FriendSetupPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: TitleBar.back(
title: StrRes.friendSetup,
),
backgroundColor: Styles.c_F8F9FA,
body: Column(
children: [
10.verticalSpace,
_buildItemView(
label: StrRes.setupRemark,
borderRadius: BorderRadius.only(
topRight: Radius.circular(6.r),
topLeft: Radius.circular(6.r),
),
showRightArrow: true,
onTap: logic.setFriendRemark,
),
_buildItemView(
label: StrRes.recommendToFriend,
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(6.r),
bottomRight: Radius.circular(6.r),
),
showRightArrow: true,
onTap: logic.recommendToFriend,
),
10.verticalSpace,
Obx(() => _buildItemView(
label: StrRes.addToBlacklist,
showSwitchButton: true,
switchOn:
logic.userProfilesLogic.userInfo.value.isBlacklist == true,
onChanged: (_) => logic.toggleBlacklist(),
)),
10.verticalSpace,
_buildItemView(
isDelFriendButton: true,
onTap: logic.deleteFromFriendList,
)
],
),
);
}
Widget _buildItemView({
String? label,
bool showRightArrow = false,
bool showSwitchButton = false,
BorderRadius? borderRadius,
bool switchOn = false,
bool isDelFriendButton = false,
ValueChanged<bool>? onChanged,
Function()? onTap,
}) =>
Container(
margin: EdgeInsets.symmetric(horizontal: 10.w),
child: Ink(
height: 46.h,
decoration: BoxDecoration(
color: Styles.c_FFFFFF,
borderRadius: borderRadius ?? BorderRadius.circular(6.r),
),
child: InkWell(
onTap: onTap,
child: Container(
alignment: isDelFriendButton ? Alignment.center : null,
padding: EdgeInsets.symmetric(horizontal: 16.w),
child: isDelFriendButton
? (StrRes.unfriend.toText..style = Styles.ts_FF381F_17sp)
: Row(
children: [
(label ?? '').toText..style = Styles.ts_0C1C33_17sp,
const Spacer(),
if (showRightArrow)
ImageRes.rightArrow.toImage
..width = 24.w
..height = 24.h,
if (showSwitchButton)
CupertinoSwitch(
value: switchOn,
onChanged: onChanged,
activeColor: Styles.c_0089FF,
),
],
),
),
),
),
);
}
@@ -0,0 +1,10 @@
import 'package:get/get.dart';
import 'personal_info_logic.dart';
class PersonalInfoBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => PersonalInfoLogic());
}
}
@@ -0,0 +1,106 @@
import 'package:collection/collection.dart';
import 'package:common_utils/common_utils.dart';
import 'package:get/get.dart';
import 'package:openim_common/openim_common.dart';
import 'package:url_launcher/url_launcher.dart';
import '../user_profile _panel_logic.dart';
class PersonalInfoLogic extends GetxController {
final userProfilesLogic = Get.find<UserProfilePanelLogic>(tag: GetTags.userProfile);
late String userID;
final userFullInfo = UserFullInfo().obs;
@override
void onInit() {
userID = Get.arguments['userID'];
super.onInit();
}
@override
void onReady() {
_queryUserFullInfo();
super.onReady();
}
void _queryUserFullInfo() async {
final existUser = UserCacheManager().getUserInfo(userID);
if (existUser != null) {
userFullInfo.update((val) {
val?.nickname = existUser.nickname;
val?.faceURL = existUser.faceURL;
val?.status = existUser.status;
val?.level = existUser.level;
val?.phoneNumber = existUser.phoneNumber;
val?.areaCode = existUser.areaCode;
val?.birth = existUser.birth;
val?.email = existUser.email;
val?.gender = existUser.gender;
val?.mobile = existUser.mobile;
});
}
final list = await LoadingView.singleton.wrap(
asyncFunction: () => Apis.getUserFullInfo(userIDList: [userID]),
);
final info = list?.firstOrNull;
if (null != info) {
UserCacheManager().addOrUpdateUserInfo(userID, info);
userFullInfo.update((val) {
val?.nickname = info.nickname;
val?.faceURL = info.faceURL;
val?.gender = info.gender;
val?.englishName = info.englishName;
val?.birth = info.birth;
val?.telephone = info.telephone;
val?.phoneNumber = info.phoneNumber;
val?.email = info.email;
});
}
}
String? get nickname => IMUtils.emptyStrToNull(userProfilesLogic.userInfo.value.nickname) ?? IMUtils.emptyStrToNull(userFullInfo.value.nickname);
String? get faceURL => IMUtils.emptyStrToNull(userProfilesLogic.userInfo.value.faceURL) ?? IMUtils.emptyStrToNull(userFullInfo.value.faceURL);
bool get isMale => (userProfilesLogic.userInfo.value.gender ?? userFullInfo.value.gender) == 1;
String? get englishName => IMUtils.emptyStrToNull(userFullInfo.value.englishName) ?? '-';
int? get _birth => userProfilesLogic.userInfo.value.birth ?? userFullInfo.value.birth;
String? get birth => _birth == null ? '-' : DateUtil.formatDateMs(_birth!, format: IMUtils.getTimeFormat1());
String? get telephone => IMUtils.emptyStrToNull(userFullInfo.value.telephone) ?? '-';
String? get phoneNumber => IMUtils.emptyStrToNull(userFullInfo.value.phoneNumber) ?? '-';
String? get email => IMUtils.emptyStrToNull(userFullInfo.value.email) ?? '-';
clickPhoneNumber() => _callSystemPhone(userFullInfo.value.phoneNumber);
clickTel() => _callSystemPhone(userFullInfo.value.telephone);
clickEmail() => _callSystemEmail(userFullInfo.value.email);
_callSystemPhone(String? phone) async {
final value = IMUtils.emptyStrToNull(phone);
if (null != value) {
final uri = Uri.parse('tel:$value');
try {
launchUrl(uri);
} catch (_) {}
}
}
_callSystemEmail(String? email) {
final value = IMUtils.emptyStrToNull(email);
if (null != value) {
final uri = Uri.parse('mailto:$value');
try {
launchUrl(uri);
} catch (_) {}
}
}
}
@@ -0,0 +1,116 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:openim_common/openim_common.dart';
import 'personal_info_logic.dart';
class PersonalInfoPage extends StatelessWidget {
final logic = Get.find<PersonalInfoLogic>();
PersonalInfoPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: TitleBar.back(
title: StrRes.personalInfo,
),
backgroundColor: Styles.c_F8F9FA,
body: SingleChildScrollView(
child: Obx(
() => Column(
children: [
10.verticalSpace,
_buildCornerBgView(
children: [
_buildItemView(
label: StrRes.avatar,
isAvatar: true,
value: logic.nickname,
url: logic.faceURL,
),
_buildItemView(
label: StrRes.name,
value: logic.nickname,
),
_buildItemView(
label: StrRes.gender,
value: logic.isMale ? StrRes.man : StrRes.woman,
),
_buildItemView(
label: StrRes.englishName,
value: logic.englishName,
),
_buildItemView(
label: StrRes.birthDay,
value: logic.birth,
),
],
),
10.verticalSpace,
_buildCornerBgView(
children: [
_buildItemView(
label: StrRes.mobile,
value: logic.phoneNumber,
onTap: logic.clickPhoneNumber,
),
_buildItemView(
label: StrRes.email,
value: logic.email,
onTap: logic.clickEmail,
),
],
),
],
),
)),
);
}
Widget _buildCornerBgView({required List<Widget> children}) => Container(
padding: EdgeInsets.symmetric(horizontal: 16.w),
margin: EdgeInsets.symmetric(horizontal: 10.w),
decoration: BoxDecoration(
color: Styles.c_FFFFFF,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(6.r),
topRight: Radius.circular(6.r),
bottomLeft: Radius.circular(6.r),
bottomRight: Radius.circular(6.r),
),
),
child: Column(children: children),
);
Widget _buildItemView({
required String label,
String? value,
String? url,
bool isAvatar = false,
Function()? onTap,
}) =>
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: onTap,
child: SizedBox(
height: 46.h,
child: Row(
children: [
label.toText..style = Styles.ts_0C1C33_17sp,
const Spacer(),
if (null != value && !isAvatar) value.toText..style = Styles.ts_0C1C33_17sp,
if (isAvatar)
AvatarView(
width: 32.w,
height: 32.h,
url: url,
text: value,
textStyle: Styles.ts_FFFFFF_10sp,
),
],
),
),
);
}
@@ -0,0 +1,10 @@
import 'package:get/get.dart';
import 'set_remark_logic.dart';
class SetFriendRemarkBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => SetFriendRemarkLogic());
}
}
@@ -0,0 +1,40 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter_openim_sdk/flutter_openim_sdk.dart';
import 'package:get/get.dart';
import 'package:openim_common/openim_common.dart';
import '../user_profile _panel_logic.dart';
class SetFriendRemarkLogic extends GetxController {
final userProfilesLogic =
Get.find<UserProfilePanelLogic>(tag: GetTags.userProfile);
late TextEditingController inputCtrl;
void save() async {
try {
await LoadingView.singleton.wrap(
asyncFunction: () => OpenIM.iMManager.friendshipManager.setFriendRemark(
userID: userProfilesLogic.userInfo.value.userID!,
remark: inputCtrl.text.trim(),
),
);
IMViews.showToast(StrRes.saveSuccessfully);
Get.back(result: inputCtrl.text.trim());
} catch (_) {
IMViews.showToast(StrRes.saveFailed);
}
}
@override
void onInit() {
inputCtrl =
TextEditingController(text: userProfilesLogic.userInfo.value.remark);
super.onInit();
}
@override
void onClose() {
inputCtrl.dispose();
super.onClose();
}
}
@@ -0,0 +1,52 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:openim_common/openim_common.dart';
import 'set_remark_logic.dart';
class SetFriendRemarkPage extends StatelessWidget {
final logic = Get.find<SetFriendRemarkLogic>();
SetFriendRemarkPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: TitleBar.back(
title: StrRes.remark,
right: StrRes.save.toText
..style = Styles.ts_0C1C33_17sp
..onTap = logic.save,
),
backgroundColor: Styles.c_FFFFFF,
body: Column(
children: [
22.verticalSpace,
Container(
margin: EdgeInsets.symmetric(horizontal: 10.w),
decoration: BoxDecoration(
color: Styles.c_E8EAEF,
borderRadius: BorderRadius.circular(4.r),
),
child: TextField(
controller: logic.inputCtrl,
style: Styles.ts_0C1C33_17sp,
autofocus: true,
inputFormatters: [LengthLimitingTextInputFormatter(16)],
decoration: InputDecoration(
border: InputBorder.none,
isDense: true,
contentPadding: EdgeInsets.symmetric(
vertical: 10.h,
horizontal: 12.w,
),
),
),
),
],
),
);
}
}
@@ -0,0 +1,11 @@
import 'package:get/get.dart';
import 'package:openim_common/openim_common.dart';
import 'user_profile _panel_logic.dart';
class UserProfilePanelBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => UserProfilePanelLogic(), tag: GetTags.userProfile);
}
}

Some files were not shown because too many files have changed in this diff Show More