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:
@@ -0,0 +1,113 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:openim_common/openim_common.dart';
|
||||
|
||||
import 'core/controller/im_controller.dart';
|
||||
import 'routes/app_pages.dart';
|
||||
import 'widgets/app_view.dart';
|
||||
|
||||
class ChatApp extends StatelessWidget {
|
||||
const ChatApp({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AppView(
|
||||
builder: (locale, builder) => GetMaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
enableLog: true,
|
||||
builder: builder,
|
||||
logWriterCallback: Logger.print,
|
||||
translations: TranslationService(),
|
||||
localizationsDelegates: const [
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
fallbackLocale: TranslationService.fallbackLocale,
|
||||
locale: locale,
|
||||
localeResolutionCallback: (locale, list) {
|
||||
Get.locale ??= locale;
|
||||
return locale;
|
||||
},
|
||||
supportedLocales: const [Locale('zh', 'CN'), Locale('en', 'US')],
|
||||
getPages: AppPages.routes,
|
||||
initialBinding: InitBinding(),
|
||||
initialRoute: AppRoutes.splash,
|
||||
theme: _themeData,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
ThemeData get _themeData => ThemeData.light().copyWith(
|
||||
scaffoldBackgroundColor: Colors.grey.shade50,
|
||||
canvasColor: Colors.white,
|
||||
appBarTheme: const AppBarTheme(color: Colors.white),
|
||||
textSelectionTheme: const TextSelectionThemeData().copyWith(cursorColor: Colors.blue),
|
||||
checkboxTheme: const CheckboxThemeData().copyWith(
|
||||
checkColor: WidgetStateProperty.all(Colors.white),
|
||||
fillColor: WidgetStateProperty.resolveWith((states) {
|
||||
if (states.contains(WidgetState.disabled)) {
|
||||
return Colors.grey;
|
||||
}
|
||||
if (states.contains(WidgetState.selected)) {
|
||||
return Colors.blue;
|
||||
}
|
||||
return Colors.white;
|
||||
}),
|
||||
side: BorderSide(color: Colors.grey.shade500, width: 1),
|
||||
),
|
||||
dialogTheme: const DialogTheme().copyWith(
|
||||
backgroundColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
),
|
||||
),
|
||||
textButtonTheme: TextButtonThemeData(
|
||||
style: ButtonStyle(
|
||||
shape: WidgetStatePropertyAll(
|
||||
RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(4.0),
|
||||
),
|
||||
),
|
||||
textStyle: WidgetStatePropertyAll(
|
||||
TextStyle(
|
||||
fontSize: 16.sp,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
foregroundColor: const WidgetStatePropertyAll(Colors.black),
|
||||
),
|
||||
),
|
||||
progressIndicatorTheme: const ProgressIndicatorThemeData()
|
||||
.copyWith(color: Colors.white, linearTrackColor: Colors.grey[300], circularTrackColor: Colors.grey[300]),
|
||||
cupertinoOverrideTheme: CupertinoThemeData(
|
||||
brightness: Brightness.light,
|
||||
primaryColor: CupertinoColors.systemBlue,
|
||||
barBackgroundColor: Colors.white,
|
||||
applyThemeToAll: true,
|
||||
textTheme: const CupertinoTextThemeData().copyWith(
|
||||
navActionTextStyle: TextStyle(color: CupertinoColors.label, fontSize: 17.sp),
|
||||
actionTextStyle: TextStyle(color: CupertinoColors.systemBlue, fontSize: 17.sp),
|
||||
textStyle: TextStyle(color: CupertinoColors.label, fontSize: 17.sp),
|
||||
navLargeTitleTextStyle: TextStyle(color: CupertinoColors.label, fontSize: 20.sp),
|
||||
navTitleTextStyle: TextStyle(color: CupertinoColors.label, fontSize: 17.sp),
|
||||
pickerTextStyle: TextStyle(color: CupertinoColors.label, fontSize: 17.sp),
|
||||
tabLabelTextStyle: TextStyle(color: CupertinoColors.label, fontSize: 17.sp),
|
||||
dateTimePickerTextStyle: TextStyle(color: CupertinoColors.label, fontSize: 17.sp),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class InitBinding extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
Get.put<IMController>(IMController());
|
||||
Get.put<PushController>(PushController());
|
||||
Get.put<CacheController>(CacheController());
|
||||
Get.put<DownloadController>(DownloadController());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:audio_session/audio_session.dart';
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_new_badger/flutter_new_badger.dart';
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:flutter_openim_sdk/flutter_openim_sdk.dart' as im;
|
||||
import 'package:flutter_openim_sdk/flutter_openim_sdk.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:just_audio/just_audio.dart';
|
||||
import 'package:openim/core/im_callback.dart';
|
||||
import 'package:openim_common/openim_common.dart';
|
||||
import 'package:sound_mode/sound_mode.dart';
|
||||
import 'package:sound_mode/utils/ringer_mode_statuses.dart';
|
||||
import 'package:vibration/vibration.dart';
|
||||
|
||||
import '../../utils/upgrade_manager.dart';
|
||||
import 'im_controller.dart';
|
||||
|
||||
class AppController extends GetxController with UpgradeManger {
|
||||
var isRunningBackground = false;
|
||||
|
||||
final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
|
||||
|
||||
final initializationSettingsAndroid = const AndroidInitializationSettings('@mipmap/ic_launcher');
|
||||
|
||||
final DarwinInitializationSettings initializationSettingsDarwin = const DarwinInitializationSettings(
|
||||
requestAlertPermission: false,
|
||||
requestBadgePermission: false,
|
||||
requestSoundPermission: false,
|
||||
);
|
||||
|
||||
RTCBridge? get rtcBridge => PackageBridge.rtcBridge;
|
||||
|
||||
bool get shouldMuted =>
|
||||
rtcBridge?.hasConnection == true ||
|
||||
Get.find<IMController>().imSdkStatusSubject.values.last.status != IMSdkStatus.syncEnded;
|
||||
|
||||
final _ring = 'assets/audio/message_ring.wav';
|
||||
final _audioPlayer = AudioPlayer();
|
||||
final configuration = const AudioSessionConfiguration(
|
||||
avAudioSessionCategory: AVAudioSessionCategory.ambient,
|
||||
avAudioSessionCategoryOptions: AVAudioSessionCategoryOptions.mixWithOthers,
|
||||
androidAudioFocusGainType: AndroidAudioFocusGainType.gainTransientMayDuck,
|
||||
androidAudioAttributes: AndroidAudioAttributes(
|
||||
contentType: AndroidAudioContentType.sonification,
|
||||
usage: AndroidAudioUsage.notification,
|
||||
),
|
||||
);
|
||||
late AudioSession session;
|
||||
|
||||
late BaseDeviceInfo deviceInfo;
|
||||
|
||||
final clientConfigMap = <String, dynamic>{}.obs;
|
||||
|
||||
Future<void> runningBackground(bool run) async {
|
||||
Logger.print('-----App running background : $run-------------');
|
||||
|
||||
if (isRunningBackground && !run) {}
|
||||
isRunningBackground = run;
|
||||
if (!run) {
|
||||
_cancelAllNotifications();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onInit() async {
|
||||
_initPlayer();
|
||||
final initializationSettings = InitializationSettings(
|
||||
android: initializationSettingsAndroid,
|
||||
iOS: initializationSettingsDarwin,
|
||||
);
|
||||
await flutterLocalNotificationsPlugin.initialize(
|
||||
initializationSettings,
|
||||
onDidReceiveNotificationResponse: (notificationResponse) {},
|
||||
);
|
||||
|
||||
autoCheckVersionUpgrade();
|
||||
super.onInit();
|
||||
}
|
||||
|
||||
void _requestPermissions() {
|
||||
flutterLocalNotificationsPlugin
|
||||
.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()
|
||||
?.requestNotificationsPermission();
|
||||
flutterLocalNotificationsPlugin
|
||||
.resolvePlatformSpecificImplementation<IOSFlutterLocalNotificationsPlugin>()
|
||||
?.requestPermissions(
|
||||
alert: true,
|
||||
badge: true,
|
||||
sound: true,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> showNotification(im.Message message, {bool showNotification = true}) async {
|
||||
if (_isGlobalNotDisturb() ||
|
||||
message.attachedInfoElem?.notSenderNotificationPush == true ||
|
||||
message.contentType == im.MessageType.typing ||
|
||||
message.sendID == OpenIM.iMManager.userID ||
|
||||
(message.contentType! >= 1000 && message.contentType != 1400)) return;
|
||||
|
||||
var sourceID = message.sessionType == ConversationType.single ? message.sendID : message.groupID;
|
||||
if (sourceID != null && message.sessionType != null) {
|
||||
var i = await OpenIM.iMManager.conversationManager.getOneConversation(
|
||||
sourceID: sourceID,
|
||||
sessionType: message.sessionType!,
|
||||
);
|
||||
if (i.recvMsgOpt != 0) return;
|
||||
}
|
||||
|
||||
if (showNotification) {
|
||||
promptSoundOrNotification(message.seq!);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> promptSoundOrNotification(int seq) async {
|
||||
if (Get.find<IMController>().imSdkStatusSubject.values.lastOrNull?.status != IMSdkStatus.syncEnded) {
|
||||
return;
|
||||
}
|
||||
if (!isRunningBackground) {
|
||||
_playMessageSound();
|
||||
} else {
|
||||
if (Platform.isAndroid) {
|
||||
final id = seq;
|
||||
|
||||
const androidPlatformChannelSpecifics = AndroidNotificationDetails('chat', 'OpenIM聊天消息',
|
||||
channelDescription: '来自OpenIM的信息', importance: Importance.max, priority: Priority.high, ticker: 'ticker');
|
||||
const NotificationDetails platformChannelSpecifics =
|
||||
NotificationDetails(android: androidPlatformChannelSpecifics);
|
||||
await flutterLocalNotificationsPlugin.show(id, '您收到了一条新消息', '消息内容:.....', platformChannelSpecifics,
|
||||
payload: '');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _cancelAllNotifications() async {
|
||||
await flutterLocalNotificationsPlugin.cancelAll();
|
||||
}
|
||||
|
||||
Future<void> _startForegroundService() async {
|
||||
await getAppInfo();
|
||||
const androidPlatformChannelSpecifics = AndroidNotificationDetails('pro', 'OpenIM后台进程',
|
||||
channelDescription: '保证app能收到信息', importance: Importance.max, priority: Priority.high, ticker: 'ticker');
|
||||
|
||||
await flutterLocalNotificationsPlugin
|
||||
.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()
|
||||
?.startForegroundService(1, packageInfo!.appName, '正在运行...',
|
||||
notificationDetails: androidPlatformChannelSpecifics, payload: '');
|
||||
}
|
||||
|
||||
Future<void> _stopForegroundService() async {
|
||||
await flutterLocalNotificationsPlugin
|
||||
.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()
|
||||
?.stopForegroundService();
|
||||
}
|
||||
|
||||
void showBadge(count) {
|
||||
OpenIM.iMManager.messageManager.setAppBadge(count);
|
||||
|
||||
if (count == 0) {
|
||||
removeBadge();
|
||||
PushController.resetBadge();
|
||||
} else {
|
||||
FlutterNewBadger.setBadge(count);
|
||||
PushController.setBadge(count);
|
||||
}
|
||||
}
|
||||
|
||||
void removeBadge() {
|
||||
FlutterNewBadger.removeBadge();
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
closeSubject();
|
||||
_audioPlayer.dispose();
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
Locale? getLocale() {
|
||||
var local = Get.locale;
|
||||
var index = DataSp.getLanguage() ?? 0;
|
||||
switch (index) {
|
||||
case 1:
|
||||
local = const Locale('zh', 'CN');
|
||||
break;
|
||||
case 2:
|
||||
local = const Locale('en', 'US');
|
||||
break;
|
||||
}
|
||||
return local;
|
||||
}
|
||||
|
||||
@override
|
||||
void onReady() {
|
||||
queryClientConfig();
|
||||
_getDeviceInfo();
|
||||
_cancelAllNotifications();
|
||||
super.onReady();
|
||||
}
|
||||
|
||||
bool _isGlobalNotDisturb() {
|
||||
bool isRegistered = Get.isRegistered<IMController>();
|
||||
if (isRegistered) {
|
||||
var logic = Get.find<IMController>();
|
||||
return logic.userInfo.value.globalRecvMsgOpt == 2;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void _initPlayer() async {
|
||||
session = await AudioSession.instance;
|
||||
await session.configure(configuration);
|
||||
|
||||
_audioPlayer.setAsset(_ring, package: 'openim_common');
|
||||
_audioPlayer.playerStateStream.listen((state) {
|
||||
switch (state.processingState) {
|
||||
case ProcessingState.idle:
|
||||
case ProcessingState.loading:
|
||||
case ProcessingState.buffering:
|
||||
case ProcessingState.ready:
|
||||
break;
|
||||
case ProcessingState.completed:
|
||||
_stopMessageSound();
|
||||
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _playMessageSound() async {
|
||||
if (shouldMuted) {
|
||||
return;
|
||||
}
|
||||
bool isRegistered = Get.isRegistered<IMController>();
|
||||
bool isAllowVibration = true;
|
||||
bool isAllowBeep = true;
|
||||
if (isRegistered) {
|
||||
var logic = Get.find<IMController>();
|
||||
isAllowVibration = logic.userInfo.value.allowVibration == 1;
|
||||
isAllowBeep = logic.userInfo.value.allowBeep == 1;
|
||||
}
|
||||
|
||||
RingerModeStatus ringerStatus = await SoundMode.ringerModeStatus;
|
||||
|
||||
Logger.print('System ringer status: $ringerStatus, user is allow beep: $isAllowBeep',
|
||||
fileName: 'app_controller.dart');
|
||||
|
||||
if (!_audioPlayer.playerState.playing &&
|
||||
isAllowBeep &&
|
||||
(ringerStatus == RingerModeStatus.normal || ringerStatus == RingerModeStatus.unknown)) {
|
||||
await session.setActive(true);
|
||||
_audioPlayer.setAsset(_ring, package: 'openim_common');
|
||||
_audioPlayer.setLoopMode(LoopMode.off);
|
||||
_audioPlayer.setVolume(1.0);
|
||||
_audioPlayer.play();
|
||||
}
|
||||
|
||||
if (isAllowVibration &&
|
||||
(ringerStatus == RingerModeStatus.normal ||
|
||||
ringerStatus == RingerModeStatus.vibrate ||
|
||||
ringerStatus == RingerModeStatus.unknown)) {
|
||||
if (await Vibration.hasVibrator() == true) {
|
||||
Vibration.vibrate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _stopMessageSound() async {
|
||||
if (_audioPlayer.playerState.playing) {
|
||||
_audioPlayer.stop();
|
||||
}
|
||||
await session.setActive(false);
|
||||
}
|
||||
|
||||
void _getDeviceInfo() async {
|
||||
final deviceInfoPlugin = DeviceInfoPlugin();
|
||||
deviceInfo = await deviceInfoPlugin.deviceInfo;
|
||||
}
|
||||
|
||||
Future queryClientConfig() async {
|
||||
final map = await Apis.getClientConfig();
|
||||
clientConfigMap.assignAll(map);
|
||||
|
||||
return clientConfigMap;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.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 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:openim_live/openim_live.dart';
|
||||
|
||||
import '../im_callback.dart';
|
||||
|
||||
class IMController extends GetxController with IMCallback, OpenIMLive {
|
||||
late Rx<UserFullInfo> userInfo;
|
||||
late String atAllTag;
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
super.close();
|
||||
onCloseLive();
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
@override
|
||||
void onInit() async {
|
||||
super.onInit();
|
||||
onInitLive();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => initOpenIM());
|
||||
}
|
||||
|
||||
void initOpenIM() async {
|
||||
final initialized = await OpenIM.iMManager.initSDK(
|
||||
platformID: IMUtils.getPlatform(),
|
||||
apiAddr: Config.imApiUrl,
|
||||
wsAddr: Config.imWsUrl,
|
||||
dataDir: Config.cachePath,
|
||||
logLevel: Config.logLevel,
|
||||
logFilePath: Config.cachePath,
|
||||
listener: OnConnectListener(
|
||||
onConnecting: () {
|
||||
imSdkStatus(IMSdkStatus.connecting);
|
||||
},
|
||||
onConnectFailed: (code, error) {
|
||||
imSdkStatus(IMSdkStatus.connectionFailed);
|
||||
},
|
||||
onConnectSuccess: () {
|
||||
imSdkStatus(IMSdkStatus.connectionSucceeded);
|
||||
},
|
||||
onKickedOffline: kickedOffline,
|
||||
onUserTokenExpired: kickedOffline,
|
||||
onUserTokenInvalid: userTokenInvalid,
|
||||
),
|
||||
);
|
||||
|
||||
OpenIM.iMManager
|
||||
..setUploadLogsListener(OnUploadLogsListener(onUploadProgress: uploadLogsProgress))
|
||||
..userManager.setUserListener(OnUserListener(
|
||||
onSelfInfoUpdated: (u) {
|
||||
selfInfoUpdated(u);
|
||||
|
||||
userInfo.update((val) {
|
||||
val?.nickname = u.nickname;
|
||||
val?.faceURL = u.faceURL;
|
||||
|
||||
val?.remark = u.remark;
|
||||
val?.ex = u.ex;
|
||||
val?.globalRecvMsgOpt = u.globalRecvMsgOpt;
|
||||
});
|
||||
},
|
||||
onUserStatusChanged: userStausChanged))
|
||||
..messageManager.setAdvancedMsgListener(OnAdvancedMsgListener(
|
||||
onRecvC2CReadReceipt: recvC2CMessageReadReceipt,
|
||||
onRecvNewMessage: recvNewMessage,
|
||||
onNewRecvMessageRevoked: recvMessageRevoked,
|
||||
onRecvOfflineNewMessage: recvOfflineMessage,
|
||||
onRecvOnlineOnlyMessage: (msg) {
|
||||
if (msg.isCustomType) {
|
||||
final data = msg.customElem!.data;
|
||||
final map = jsonDecode(data!);
|
||||
final customType = map['customType'];
|
||||
if (customType == CustomMessageType.callingInvite ||
|
||||
customType == CustomMessageType.callingAccept ||
|
||||
customType == CustomMessageType.callingReject ||
|
||||
customType == CustomMessageType.callingCancel ||
|
||||
customType == CustomMessageType.callingHungup) {
|
||||
final signaling = SignalingInfo(invitation: InvitationInfo.fromJson(map['data']));
|
||||
signaling.userID = signaling.invitation?.inviterUserID;
|
||||
|
||||
switch (customType) {
|
||||
case CustomMessageType.callingInvite:
|
||||
receiveNewInvitation(signaling);
|
||||
break;
|
||||
case CustomMessageType.callingAccept:
|
||||
inviteeAccepted(signaling);
|
||||
break;
|
||||
case CustomMessageType.callingReject:
|
||||
inviteeRejected(signaling);
|
||||
break;
|
||||
case CustomMessageType.callingCancel:
|
||||
invitationCancelled(signaling);
|
||||
break;
|
||||
case CustomMessageType.callingHungup:
|
||||
beHangup(signaling);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
))
|
||||
..messageManager.setMsgSendProgressListener(OnMsgSendProgressListener(
|
||||
onProgress: progressCallback,
|
||||
))
|
||||
..messageManager.setCustomBusinessListener(OnCustomBusinessListener(
|
||||
onRecvCustomBusinessMessage: recvCustomBusinessMessage,
|
||||
))
|
||||
..friendshipManager.setFriendshipListener(OnFriendshipListener(
|
||||
onBlackAdded: blacklistAdded,
|
||||
onBlackDeleted: blacklistDeleted,
|
||||
onFriendApplicationAccepted: friendApplicationAccepted,
|
||||
onFriendApplicationAdded: friendApplicationAdded,
|
||||
onFriendApplicationDeleted: friendApplicationDeleted,
|
||||
onFriendApplicationRejected: friendApplicationRejected,
|
||||
onFriendInfoChanged: friendInfoChanged,
|
||||
onFriendAdded: friendAdded,
|
||||
onFriendDeleted: friendDeleted,
|
||||
))
|
||||
..conversationManager.setConversationListener(OnConversationListener(
|
||||
onConversationChanged: conversationChanged,
|
||||
onNewConversation: newConversation,
|
||||
onTotalUnreadMessageCountChanged: totalUnreadMsgCountChanged,
|
||||
onInputStatusChanged: inputStateChanged,
|
||||
onSyncServerFailed: (reInstall) {
|
||||
imSdkStatus(IMSdkStatus.syncFailed, reInstall: reInstall ?? false);
|
||||
},
|
||||
onSyncServerFinish: (reInstall) {
|
||||
imSdkStatus(IMSdkStatus.syncEnded, reInstall: reInstall ?? false);
|
||||
if (Platform.isAndroid) {
|
||||
Permissions.request([Permission.systemAlertWindow]);
|
||||
}
|
||||
},
|
||||
onSyncServerStart: (reInstall) {
|
||||
imSdkStatus(IMSdkStatus.syncStart, reInstall: reInstall ?? false);
|
||||
},
|
||||
onSyncServerProgress: (progress) {
|
||||
imSdkStatus(IMSdkStatus.syncProgress, progress: progress);
|
||||
}))
|
||||
..groupManager.setGroupListener(OnGroupListener(
|
||||
onGroupApplicationAccepted: groupApplicationAccepted,
|
||||
onGroupApplicationAdded: groupApplicationAdded,
|
||||
onGroupApplicationDeleted: groupApplicationDeleted,
|
||||
onGroupApplicationRejected: groupApplicationRejected,
|
||||
onGroupInfoChanged: groupInfoChanged,
|
||||
onGroupMemberAdded: groupMemberAdded,
|
||||
onGroupMemberDeleted: groupMemberDeleted,
|
||||
onGroupMemberInfoChanged: groupMemberInfoChanged,
|
||||
onJoinedGroupAdded: joinedGroupAdded,
|
||||
onJoinedGroupDeleted: joinedGroupDeleted,
|
||||
));
|
||||
|
||||
initializedSubject.sink.add(initialized);
|
||||
}
|
||||
|
||||
Future login(String userID, String token) async {
|
||||
try {
|
||||
var user = await OpenIM.iMManager.login(
|
||||
userID: userID,
|
||||
token: token,
|
||||
defaultValue: () async => UserInfo(userID: userID),
|
||||
);
|
||||
ApiService().setToken(token);
|
||||
userInfo = UserFullInfo.fromJson(user.toJson()).obs;
|
||||
_queryMyFullInfo();
|
||||
_queryAtAllTag();
|
||||
} catch (e, s) {
|
||||
Logger.print('e: $e s:$s');
|
||||
await _handleLoginRepeatError(e);
|
||||
|
||||
return Future.error(e, s);
|
||||
}
|
||||
}
|
||||
|
||||
Future logout() {
|
||||
return OpenIM.iMManager.logout();
|
||||
}
|
||||
|
||||
void _queryAtAllTag() async {
|
||||
atAllTag = OpenIM.iMManager.conversationManager.atAllTag;
|
||||
}
|
||||
|
||||
void _queryMyFullInfo() async {
|
||||
final data = await Apis.queryMyFullInfo();
|
||||
if (data is UserFullInfo) {
|
||||
userInfo.update((val) {
|
||||
val?.allowAddFriend = data.allowAddFriend;
|
||||
val?.allowBeep = data.allowBeep;
|
||||
val?.allowVibration = data.allowVibration;
|
||||
val?.nickname = data.nickname;
|
||||
val?.faceURL = data.faceURL;
|
||||
val?.phoneNumber = data.phoneNumber;
|
||||
val?.email = data.email;
|
||||
val?.birth = data.birth;
|
||||
val?.gender = data.gender;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_handleLoginRepeatError(e) async {
|
||||
if (e is PlatformException && (e.code == "13002" || e.code == '1507')) {
|
||||
await logout();
|
||||
await DataSp.removeLoginCertificate();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter_openim_sdk/flutter_openim_sdk.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:openim_common/openim_common.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
|
||||
import 'controller/app_controller.dart';
|
||||
|
||||
enum IMSdkStatus {
|
||||
connectionFailed,
|
||||
connecting,
|
||||
connectionSucceeded,
|
||||
syncStart,
|
||||
synchronizing,
|
||||
syncEnded,
|
||||
syncFailed,
|
||||
syncProgress,
|
||||
}
|
||||
|
||||
enum KickoffType {
|
||||
kickedOffline,
|
||||
userTokenInvalid,
|
||||
userTokenExpired,
|
||||
}
|
||||
|
||||
mixin IMCallback {
|
||||
final initLogic = Get.find<AppController>();
|
||||
|
||||
Function(RevokedInfo info)? onRecvMessageRevoked;
|
||||
|
||||
Function(List<ReadReceiptInfo> list)? onRecvC2CReadReceipt;
|
||||
|
||||
Function(Message msg)? onRecvNewMessage;
|
||||
|
||||
Function(Message msg)? onRecvOfflineMessage;
|
||||
|
||||
Function(String msgId, int progress)? onMsgSendProgress;
|
||||
|
||||
Function(BlacklistInfo u)? onBlacklistAdd;
|
||||
|
||||
Function(BlacklistInfo u)? onBlacklistDeleted;
|
||||
|
||||
Function(int current, int size)? onUploadProgress;
|
||||
|
||||
final conversationAddedSubject = BehaviorSubject<List<ConversationInfo>>();
|
||||
|
||||
final conversationChangedSubject = BehaviorSubject<List<ConversationInfo>>();
|
||||
|
||||
final unreadMsgCountEventSubject = PublishSubject<int>();
|
||||
|
||||
final friendApplicationChangedSubject = BehaviorSubject<FriendApplicationInfo>();
|
||||
|
||||
final friendAddSubject = BehaviorSubject<FriendInfo>();
|
||||
|
||||
final friendDelSubject = BehaviorSubject<FriendInfo>();
|
||||
|
||||
final friendInfoChangedSubject = PublishSubject<FriendInfo>();
|
||||
|
||||
final selfInfoUpdatedSubject = BehaviorSubject<UserInfo>();
|
||||
|
||||
final userStatusChangedSubject = BehaviorSubject<UserStatusInfo>();
|
||||
|
||||
final groupInfoUpdatedSubject = BehaviorSubject<GroupInfo>();
|
||||
|
||||
final groupApplicationChangedSubject = BehaviorSubject<GroupApplicationInfo>();
|
||||
|
||||
final initializedSubject = PublishSubject<bool>();
|
||||
|
||||
final memberAddedSubject = BehaviorSubject<GroupMembersInfo>();
|
||||
|
||||
final memberDeletedSubject = BehaviorSubject<GroupMembersInfo>();
|
||||
|
||||
final memberInfoChangedSubject = PublishSubject<GroupMembersInfo>();
|
||||
|
||||
final joinedGroupDeletedSubject = BehaviorSubject<GroupInfo>();
|
||||
|
||||
final joinedGroupAddedSubject = BehaviorSubject<GroupInfo>();
|
||||
|
||||
final onKickedOfflineSubject = PublishSubject<KickoffType>();
|
||||
|
||||
final imSdkStatusSubject = ReplaySubject<({IMSdkStatus status, bool reInstall, int? progress})>();
|
||||
|
||||
final imSdkStatusPublishSubject = PublishSubject<({IMSdkStatus status, bool reInstall, int? progress})>();
|
||||
|
||||
final inputStateChangedSubject = PublishSubject<InputStatusChangedData>();
|
||||
|
||||
void imSdkStatus(IMSdkStatus status, {bool reInstall = false, int? progress}) {
|
||||
imSdkStatusSubject.add((status: status, reInstall: reInstall, progress: progress));
|
||||
imSdkStatusPublishSubject.add((status: status, reInstall: reInstall, progress: progress));
|
||||
}
|
||||
|
||||
void kickedOffline() {
|
||||
onKickedOfflineSubject.add(KickoffType.kickedOffline);
|
||||
}
|
||||
|
||||
void userTokenInvalid() {
|
||||
onKickedOfflineSubject.add(KickoffType.userTokenInvalid);
|
||||
}
|
||||
|
||||
void selfInfoUpdated(UserInfo u) {
|
||||
selfInfoUpdatedSubject.addSafely(u);
|
||||
}
|
||||
|
||||
void userStausChanged(UserStatusInfo u) {
|
||||
userStatusChangedSubject.addSafely(u);
|
||||
}
|
||||
|
||||
void uploadLogsProgress(int current, int size) {
|
||||
onUploadProgress?.call(current, size);
|
||||
}
|
||||
|
||||
void recvMessageRevoked(RevokedInfo info) {
|
||||
onRecvMessageRevoked?.call(info);
|
||||
}
|
||||
|
||||
void recvC2CMessageReadReceipt(List<ReadReceiptInfo> list) {
|
||||
onRecvC2CReadReceipt?.call(list);
|
||||
}
|
||||
|
||||
void recvNewMessage(Message msg) {
|
||||
initLogic.showNotification(msg);
|
||||
onRecvNewMessage?.call(msg);
|
||||
}
|
||||
|
||||
void recvOfflineMessage(Message msg) {
|
||||
initLogic.showNotification(msg);
|
||||
onRecvOfflineMessage?.call(msg);
|
||||
}
|
||||
|
||||
void recvCustomBusinessMessage(String s) {}
|
||||
|
||||
void progressCallback(String msgId, int progress) {
|
||||
onMsgSendProgress?.call(msgId, progress);
|
||||
}
|
||||
|
||||
void blacklistAdded(BlacklistInfo u) {
|
||||
onBlacklistAdd?.call(u);
|
||||
}
|
||||
|
||||
void blacklistDeleted(BlacklistInfo u) {
|
||||
onBlacklistDeleted?.call(u);
|
||||
}
|
||||
|
||||
void friendApplicationAccepted(FriendApplicationInfo u) {
|
||||
friendApplicationChangedSubject.addSafely(u);
|
||||
}
|
||||
|
||||
void friendApplicationAdded(FriendApplicationInfo u) {
|
||||
friendApplicationChangedSubject.addSafely(u);
|
||||
}
|
||||
|
||||
void friendApplicationDeleted(FriendApplicationInfo u) {
|
||||
friendApplicationChangedSubject.addSafely(u);
|
||||
}
|
||||
|
||||
void friendApplicationRejected(FriendApplicationInfo u) {
|
||||
friendApplicationChangedSubject.addSafely(u);
|
||||
}
|
||||
|
||||
void friendInfoChanged(FriendInfo u) {
|
||||
friendInfoChangedSubject.addSafely(u);
|
||||
}
|
||||
|
||||
void friendAdded(FriendInfo u) {
|
||||
friendAddSubject.addSafely(u);
|
||||
}
|
||||
|
||||
void friendDeleted(FriendInfo u) {
|
||||
friendDelSubject.addSafely(u);
|
||||
}
|
||||
|
||||
void conversationChanged(List<ConversationInfo> list) {
|
||||
conversationChangedSubject.addSafely(list);
|
||||
}
|
||||
|
||||
void newConversation(List<ConversationInfo> list) {
|
||||
conversationAddedSubject.addSafely(list);
|
||||
}
|
||||
|
||||
void groupApplicationAccepted(GroupApplicationInfo info) {
|
||||
groupApplicationChangedSubject.add(info);
|
||||
}
|
||||
|
||||
void groupApplicationAdded(GroupApplicationInfo info) {
|
||||
groupApplicationChangedSubject.add(info);
|
||||
}
|
||||
|
||||
void groupApplicationDeleted(GroupApplicationInfo info) {
|
||||
groupApplicationChangedSubject.add(info);
|
||||
}
|
||||
|
||||
void groupApplicationRejected(GroupApplicationInfo info) {
|
||||
groupApplicationChangedSubject.add(info);
|
||||
}
|
||||
|
||||
void groupInfoChanged(GroupInfo info) {
|
||||
groupInfoUpdatedSubject.addSafely(info);
|
||||
}
|
||||
|
||||
void groupMemberAdded(GroupMembersInfo info) {
|
||||
memberAddedSubject.add(info);
|
||||
}
|
||||
|
||||
void groupMemberDeleted(GroupMembersInfo info) {
|
||||
memberDeletedSubject.add(info);
|
||||
}
|
||||
|
||||
void groupMemberInfoChanged(GroupMembersInfo info) {
|
||||
memberInfoChangedSubject.add(info);
|
||||
}
|
||||
|
||||
void joinedGroupAdded(GroupInfo info) {
|
||||
joinedGroupAddedSubject.add(info);
|
||||
}
|
||||
|
||||
void joinedGroupDeleted(GroupInfo info) {
|
||||
joinedGroupDeletedSubject.add(info);
|
||||
}
|
||||
|
||||
void totalUnreadMsgCountChanged(int count) {
|
||||
initLogic.showBadge(count);
|
||||
unreadMsgCountEventSubject.addSafely(count);
|
||||
}
|
||||
|
||||
void inputStateChanged(InputStatusChangedData status) {
|
||||
inputStateChangedSubject.addSafely(status);
|
||||
}
|
||||
|
||||
void close() {
|
||||
initializedSubject.close();
|
||||
friendApplicationChangedSubject.close();
|
||||
friendAddSubject.close();
|
||||
friendDelSubject.close();
|
||||
friendInfoChangedSubject.close();
|
||||
selfInfoUpdatedSubject.close();
|
||||
groupInfoUpdatedSubject.close();
|
||||
conversationAddedSubject.close();
|
||||
conversationChangedSubject.close();
|
||||
memberAddedSubject.close();
|
||||
memberDeletedSubject.close();
|
||||
memberInfoChangedSubject.close();
|
||||
onKickedOfflineSubject.close();
|
||||
groupApplicationChangedSubject.close();
|
||||
imSdkStatusSubject.close();
|
||||
imSdkStatusPublishSubject.close();
|
||||
joinedGroupDeletedSubject.close();
|
||||
joinedGroupAddedSubject.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:openim_common/openim_common.dart';
|
||||
|
||||
import 'app.dart';
|
||||
|
||||
void main() {
|
||||
runZonedGuarded(() {
|
||||
FlutterError.onError = (FlutterErrorDetails details) {
|
||||
FlutterError.presentError(details);
|
||||
Logger.print('FlutterError: ${details.exception.toString()}, ${details.stack.toString()}');
|
||||
};
|
||||
|
||||
Config.init(() => runApp(const ChatApp()));
|
||||
}, (error, stackTrace) {
|
||||
Logger.print('FlutterError: ${error.toString()}, ${stackTrace.toString()}');
|
||||
});
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -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,
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
+13
@@ -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());
|
||||
}
|
||||
}
|
||||
+212
@@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
+10
@@ -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());
|
||||
}
|
||||
}
|
||||
+132
@@ -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,
|
||||
);
|
||||
}
|
||||
+92
@@ -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,
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+10
@@ -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());
|
||||
}
|
||||
}
|
||||
+43
@@ -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 _;
|
||||
}
|
||||
}
|
||||
+100
@@ -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,
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+10
@@ -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());
|
||||
}
|
||||
}
|
||||
+71
@@ -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;
|
||||
}
|
||||
}
|
||||
+127
@@ -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();
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import 'search_friend_logic.dart';
|
||||
|
||||
class SelectContactsFromSearchFriendsBinding extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
Get.lazyPut(() => SelectContactsFromSearchFriendsLogic());
|
||||
}
|
||||
}
|
||||
+46
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+84
@@ -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();
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import 'search_group_logic.dart';
|
||||
|
||||
class SelectContactsFromSearchGroupBinding extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
Get.lazyPut(() => SelectContactsFromSearchGroupLogic());
|
||||
}
|
||||
}
|
||||
+49
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+99
@@ -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,
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import 'search_contacts_logic.dart';
|
||||
|
||||
class SelectContactsFromSearchBinding extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
Get.lazyPut(() => SelectContactsFromSearchLogic());
|
||||
}
|
||||
}
|
||||
+45
@@ -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);
|
||||
}
|
||||
}
|
||||
+94
@@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+10
@@ -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());
|
||||
}
|
||||
}
|
||||
+69
@@ -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));
|
||||
}
|
||||
}
|
||||
+57
@@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import 'friend_setup_logic.dart';
|
||||
|
||||
class FriendSetupBinding extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
Get.lazyPut(() => FriendSetupLogic());
|
||||
}
|
||||
}
|
||||
+128
@@ -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();
|
||||
}
|
||||
+105
@@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import 'personal_info_logic.dart';
|
||||
|
||||
class PersonalInfoBinding extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
Get.lazyPut(() => PersonalInfoLogic());
|
||||
}
|
||||
}
|
||||
+106
@@ -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 (_) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user