merge
This commit is contained in:
parent
f4dcf8a52d
commit
839d6ef5ad
@ -13,12 +13,123 @@ class ChatController extends GetxController {
|
|||||||
final chatList = <ConversationViewModel>[].obs;
|
final chatList = <ConversationViewModel>[].obs;
|
||||||
|
|
||||||
void initChatData() {
|
void initChatData() {
|
||||||
// chatList.value = <ConversationViewModel>[];
|
|
||||||
chatList.clear();
|
chatList.clear();
|
||||||
nextSeq.value = '0';
|
nextSeq.value = '0';
|
||||||
isFinished.value = false;
|
isFinished.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 检测分组
|
||||||
|
Future<bool> handleGroup({required String conversationID}) async {
|
||||||
|
final isReal = await ImService.instance.getConversation(conversationID: conversationID);
|
||||||
|
final V2TimConversation conv = isReal.data;
|
||||||
|
if (conv.lastMessage != null) {
|
||||||
|
// 会话存在执行后续逻辑
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
// 会话不存在,结束
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 关注后移除陌生人会话
|
||||||
|
Future<void> removeNoFriend({required String conversationID}) async {
|
||||||
|
try {
|
||||||
|
final isContinue = await handleGroup(conversationID: conversationID);
|
||||||
|
if (isContinue) {
|
||||||
|
await ImService.instance.deleteConversationsFromGroup(
|
||||||
|
groupName: myConversationType.ConversationTypeStatic.noFriend,
|
||||||
|
conversationIDList: [conversationID],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
logger.e('移除陌生人会话分组异常:$e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 取关后将会话移入陌生人会话
|
||||||
|
Future<void> mergeNoFriend({required conversationID}) async {
|
||||||
|
try {
|
||||||
|
final isContinue = await handleGroup(conversationID: conversationID);
|
||||||
|
if (isContinue) {
|
||||||
|
final typeEnum = myConversationType.ConversationTypeStatic.noFriend;
|
||||||
|
final hasGroupRes = await ImService.instance.getConversationGroupList();
|
||||||
|
if (hasGroupRes.success) {
|
||||||
|
final exists = hasGroupRes.data?.any((item) => item == typeEnum) ?? false;
|
||||||
|
if (!exists) {
|
||||||
|
// 组不存在,创建组并把会话加入group中
|
||||||
|
await ImService.instance.createConversationGroup(
|
||||||
|
groupName: typeEnum,
|
||||||
|
conversationIDList: [conversationID],
|
||||||
|
);
|
||||||
|
logger.i('取关触发添加会话分组$typeEnum成功');
|
||||||
|
} else {
|
||||||
|
// 分组存在直接添加
|
||||||
|
await ImService.instance.addConversationsToGroup(
|
||||||
|
groupName: typeEnum,
|
||||||
|
conversationIDList: [conversationID],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final res = chatList.firstWhereOrNull((item) => item.conversation.conversationID == conversationID);
|
||||||
|
|
||||||
|
if (res != null) {
|
||||||
|
chatList.remove(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
logger.w('移除会话列表时发生错误: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关注后更新nofd菜单入口
|
||||||
|
Future<void> updateNoFriendMenu() async {
|
||||||
|
final res = await ImService.instance.getConversationListByFilter(
|
||||||
|
filter: V2TimConversationFilter(conversationGroup: myConversationType.ConversationType.noFriend.name),
|
||||||
|
nextSeq: 0,
|
||||||
|
count: 1,
|
||||||
|
);
|
||||||
|
if (res.success && res.data != null) {
|
||||||
|
final convList = res.data!.conversationList ?? [];
|
||||||
|
if (convList.isNotEmpty) {
|
||||||
|
// 只要有数据,入口一定存在,找到入口数据替换
|
||||||
|
final unread = await ImService.instance.getUnreadMessageCountByFilter(
|
||||||
|
filter: V2TimConversationFilter(
|
||||||
|
conversationGroup: myConversationType.ConversationType.noFriend.name,
|
||||||
|
hasUnreadCount: true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (unread.success) {
|
||||||
|
var viewModelList = await ConversationViewModel.createConversationViewModel(convList: [convList.first]);
|
||||||
|
final conviewModel = viewModelList.first;
|
||||||
|
conviewModel.faceUrl = 'assets/images/notify/msr.png';
|
||||||
|
conviewModel.isCustomAdmin = myConversationType.ConversationTypeStatic.noFriend;
|
||||||
|
conviewModel.conversation.showName = '陌生人消息';
|
||||||
|
conviewModel.conversation.unreadCount = unread.data;
|
||||||
|
conviewModel.conversation.conversationID = myConversationType.ConversationTypeStatic.noFriend;
|
||||||
|
|
||||||
|
// final newList = List<ConversationViewModel>.from(chatList);
|
||||||
|
final index = chatList.indexWhere(
|
||||||
|
(item) => item.isCustomAdmin == myConversationType.ConversationTypeStatic.noFriend,
|
||||||
|
);
|
||||||
|
if (index != -1) {
|
||||||
|
chatList[index] = conviewModel;
|
||||||
|
// 排序
|
||||||
|
chatList.sort((a, b) {
|
||||||
|
final atime = a.conversation.lastMessage?.timestamp ?? 0;
|
||||||
|
final btime = b.conversation.lastMessage?.timestamp ?? 0;
|
||||||
|
return btime.compareTo(atime); // 降序
|
||||||
|
});
|
||||||
|
// 更新
|
||||||
|
chatList.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.e('刷新入口失败-----${res.desc}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 获取所有会话列表
|
// 获取所有会话列表
|
||||||
Future<void> getConversationList() async {
|
Future<void> getConversationList() async {
|
||||||
logger.e('开始获取会话列表数据');
|
logger.e('开始获取会话列表数据');
|
||||||
@ -57,65 +168,101 @@ class ChatController extends GetxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
///构建陌生人消息菜单入口
|
///构建陌生人消息菜单入口(更新时候传入对应的会话)
|
||||||
void getNoFriendData({V2TimConversation? csion}) async {
|
Future<void> getNoFriendData({V2TimConversation? csion}) async {
|
||||||
// 检测会话列表是否已有陌生人消息菜单
|
// 检测会话列表是否已有陌生人消息菜单
|
||||||
final hasNoFriend = chatList.any((item) => item.conversation.conversationGroupList?.contains(myConversationType.ConversationType.noFriend.name) ?? false);
|
// final hasNoFriend = chatList.any((item) => item.conversation.conversationGroupList?.contains(myConversationType.ConversationType.noFriend.name) ?? false);
|
||||||
|
final hasNoFriend = chatList.any((item) => item.isCustomAdmin!.contains(myConversationType.ConversationType.noFriend.name));
|
||||||
logger.w('检测是否存在nofriend入口:$hasNoFriend');
|
logger.w('检测是否存在nofriend入口:$hasNoFriend');
|
||||||
if (hasNoFriend) {
|
if (hasNoFriend) {
|
||||||
// 已经有了入口
|
// 已经有了入口
|
||||||
final ConversationViewModel matchItem = chatList.firstWhere(
|
// 先检测nofriend分组是否还有数据
|
||||||
(item) => item.conversation.conversationGroupList?.contains(myConversationType.ConversationType.noFriend.name) ?? false,
|
final noFriendData = await ImService.instance.getConversationListByFilter(
|
||||||
);
|
|
||||||
// 获取陌生人未读总数
|
|
||||||
final unreadTotal = await ImService.instance.getUnreadMessageCountByFilter(
|
|
||||||
filter: V2TimConversationFilter(
|
|
||||||
conversationGroup: myConversationType.ConversationType.noFriend.name,
|
|
||||||
hasUnreadCount: true,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
matchItem.conversation.lastMessage = csion!.lastMessage;
|
|
||||||
matchItem.conversation.unreadCount = unreadTotal.data;
|
|
||||||
chatList.refresh();
|
|
||||||
} else {
|
|
||||||
// 没有则执行创建逻辑
|
|
||||||
final res = await ImService.instance.getConversationListByFilter(
|
|
||||||
filter: V2TimConversationFilter(conversationGroup: myConversationType.ConversationType.noFriend.name),
|
filter: V2TimConversationFilter(conversationGroup: myConversationType.ConversationType.noFriend.name),
|
||||||
nextSeq: 0,
|
nextSeq: 0,
|
||||||
count: 1,
|
count: 1,
|
||||||
);
|
);
|
||||||
if (res.success && res.data != null) {
|
if (noFriendData.success && (noFriendData.data?.conversationList?.isNotEmpty == true)) {
|
||||||
final convList = res.data!.conversationList ?? [];
|
// 还存在陌生人会话数据
|
||||||
if (convList.isNotEmpty) {
|
final ConversationViewModel matchItem = chatList.firstWhere(
|
||||||
// logger.i(res.data!.toJson());
|
(item) => item.conversation.conversationGroupList?.contains(myConversationType.ConversationType.noFriend.name) ?? false,
|
||||||
// 有陌生人消息,1.获取未读数,2.组装converstaionviewmodel
|
orElse: () {
|
||||||
final unread = await ImService.instance.getUnreadMessageCountByFilter(
|
//赋在viewmodel会话上的分组被移除
|
||||||
filter: V2TimConversationFilter(
|
logger.w("没有找到符合条件的元素,执行移除逻辑");
|
||||||
conversationGroup: myConversationType.ConversationType.noFriend.name,
|
return ConversationViewModel(conversation: V2TimConversation(conversationID: ''));
|
||||||
hasUnreadCount: true,
|
},
|
||||||
),
|
);
|
||||||
);
|
// 获取陌生人未读总数
|
||||||
if (unread.success) {
|
final unreadTotal = await ImService.instance.getUnreadMessageCountByFilter(
|
||||||
var viewModelList = await ConversationViewModel.createConversationViewModel(convList: [convList.first]);
|
filter: V2TimConversationFilter(
|
||||||
final conviewModel = viewModelList.first;
|
conversationGroup: myConversationType.ConversationType.noFriend.name,
|
||||||
conviewModel.faceUrl = 'assets/images/notify/msr.png';
|
hasUnreadCount: true,
|
||||||
conviewModel.conversation.showName = '陌生人消息';
|
),
|
||||||
conviewModel.conversation.unreadCount = unread.data;
|
);
|
||||||
// final createItem = ConversationViewModel(
|
// 如果上面没找到 就是移除nofriend分组
|
||||||
// conversation: conv,
|
if (matchItem.conversation.conversationID.isEmpty) {
|
||||||
// faceUrl: faceUrl,
|
// 执行移除更新,用最新的一条会话数据,构建viewmodel
|
||||||
// );
|
await ConversationViewModel.createConversationViewModel(convList: noFriendData.data!.conversationList!);
|
||||||
final newList = List<ConversationViewModel>.from(chatList);
|
} else {
|
||||||
newList.add(conviewModel);
|
matchItem.conversation.lastMessage = csion!.lastMessage;
|
||||||
newList.sort((a, b) {
|
matchItem.conversation.unreadCount = unreadTotal.data;
|
||||||
final atime = a.conversation.lastMessage?.timestamp ?? 0;
|
}
|
||||||
final btime = b.conversation.lastMessage?.timestamp ?? 0;
|
} else {
|
||||||
return btime.compareTo(atime); // 降序
|
// 没数据,移除入口
|
||||||
});
|
chatList.removeWhere((chatItem) => chatItem.isCustomAdmin == myConversationType.ConversationType.noFriend.name);
|
||||||
chatList.value = newList;
|
}
|
||||||
}
|
|
||||||
|
chatList.refresh();
|
||||||
|
} else {
|
||||||
|
logger.w('构建陌生人消息入口--开始');
|
||||||
|
await createNoFriendMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建陌生人入口
|
||||||
|
Future<void> createNoFriendMenu() async {
|
||||||
|
// 没有则执行创建逻辑
|
||||||
|
final res = await ImService.instance.getConversationListByFilter(
|
||||||
|
filter: V2TimConversationFilter(conversationGroup: myConversationType.ConversationType.noFriend.name),
|
||||||
|
nextSeq: 0,
|
||||||
|
count: 1,
|
||||||
|
);
|
||||||
|
if (res.success && res.data != null) {
|
||||||
|
final convList = res.data!.conversationList ?? [];
|
||||||
|
if (convList.isNotEmpty) {
|
||||||
|
// logger.i(res.data!.toJson());
|
||||||
|
// 有陌生人消息,1.获取未读数,2.组装converstaionviewmodel
|
||||||
|
final unread = await ImService.instance.getUnreadMessageCountByFilter(
|
||||||
|
filter: V2TimConversationFilter(
|
||||||
|
conversationGroup: myConversationType.ConversationType.noFriend.name,
|
||||||
|
hasUnreadCount: true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (unread.success) {
|
||||||
|
var viewModelList = await ConversationViewModel.createConversationViewModel(convList: [convList.first]);
|
||||||
|
final conviewModel = viewModelList.first;
|
||||||
|
conviewModel.faceUrl = 'assets/images/notify/msr.png';
|
||||||
|
conviewModel.isCustomAdmin = myConversationType.ConversationTypeStatic.noFriend;
|
||||||
|
conviewModel.conversation.showName = '陌生人消息';
|
||||||
|
conviewModel.conversation.unreadCount = unread.data;
|
||||||
|
conviewModel.conversation.conversationID = myConversationType.ConversationTypeStatic.noFriend;
|
||||||
|
// final createItem = ConversationViewModel(
|
||||||
|
// conversation: conv,
|
||||||
|
// faceUrl: faceUrl,
|
||||||
|
// );
|
||||||
|
final newList = List<ConversationViewModel>.from(chatList);
|
||||||
|
newList.add(conviewModel);
|
||||||
|
newList.sort((a, b) {
|
||||||
|
final atime = a.conversation.lastMessage?.timestamp ?? 0;
|
||||||
|
final btime = b.conversation.lastMessage?.timestamp ?? 0;
|
||||||
|
return btime.compareTo(atime); // 降序
|
||||||
|
});
|
||||||
|
chatList.value = newList;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
logger.e('构建失败-----${res.desc}\n${res.data!.toJson()}');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ class ChatDetailController extends GetxController {
|
|||||||
|
|
||||||
final RxList<V2TimMessage> chatList = <V2TimMessage>[].obs;
|
final RxList<V2TimMessage> chatList = <V2TimMessage>[].obs;
|
||||||
final RxBool isFriend = true.obs;
|
final RxBool isFriend = true.obs;
|
||||||
|
final RxInt followType = 0.obs;
|
||||||
|
|
||||||
void updateChatListWithTimeLabels(List<V2TimMessage> originMessages) async {
|
void updateChatListWithTimeLabels(List<V2TimMessage> originMessages) async {
|
||||||
final idRes = await ImService.instance.selfUserId();
|
final idRes = await ImService.instance.selfUserId();
|
||||||
@ -55,7 +56,7 @@ class ChatDetailController extends GetxController {
|
|||||||
// 把当前消息先插入,label后插入
|
// 把当前消息先插入,label后插入
|
||||||
displayMessages.add(current);
|
displayMessages.add(current);
|
||||||
if (needInsertLabel) {
|
if (needInsertLabel) {
|
||||||
final labelTime = Utils().formatChatTime(currentTimestamp);
|
final labelTime = Utils.formatChatTime(currentTimestamp);
|
||||||
final timeLabel = await IMMessage().insertTimeLabel(labelTime, selfUserId);
|
final timeLabel = await IMMessage().insertTimeLabel(labelTime, selfUserId);
|
||||||
displayMessages.add(timeLabel.data);
|
displayMessages.add(timeLabel.data);
|
||||||
}
|
}
|
||||||
|
@ -131,6 +131,4 @@ class ImUserInfoController extends GetxController {
|
|||||||
if (gender.value < 0) return;
|
if (gender.value < 0) return;
|
||||||
await ImService.instance.setSelfInfo(userFullInfo: V2TimUserFullInfo(gender: gender.value));
|
await ImService.instance.setSelfInfo(userFullInfo: V2TimUserFullInfo(gender: gender.value));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// updateAvatar、updateSignature 等方法
|
|
||||||
}
|
}
|
||||||
|
@ -51,12 +51,15 @@ class GlobalBadge extends GetxController {
|
|||||||
final chatItem = ctl.chatList[i];
|
final chatItem = ctl.chatList[i];
|
||||||
logger.w('需要更新的ID:${chatItem.conversation.conversationID}');
|
logger.w('需要更新的ID:${chatItem.conversation.conversationID}');
|
||||||
if (updatedIds.contains(chatItem.conversation.conversationID)) {
|
if (updatedIds.contains(chatItem.conversation.conversationID)) {
|
||||||
logger.w('找到的chatList的ID:${chatItem.conversation.conversationID}');
|
logger.w('chatList中包含:${chatItem.conversation.conversationID}');
|
||||||
|
|
||||||
|
// 从onchange中找到原始数据
|
||||||
final updatedConv = conversationList.firstWhere(
|
final updatedConv = conversationList.firstWhere(
|
||||||
(c) => c.conversationID == chatItem.conversation.conversationID,
|
(c) => c.conversationID == chatItem.conversation.conversationID,
|
||||||
orElse: () => V2TimConversation(conversationID: ''),
|
orElse: () => V2TimConversation(conversationID: ''),
|
||||||
);
|
);
|
||||||
|
// 同步更新
|
||||||
|
ctl.getNoFriendData(csion: chatItem.conversation);
|
||||||
|
|
||||||
if (updatedConv.conversationID != '' && (updatedConv.conversationGroupList?.contains(ConversationType.noFriend.name) ?? false)) {
|
if (updatedConv.conversationID != '' && (updatedConv.conversationGroupList?.contains(ConversationType.noFriend.name) ?? false)) {
|
||||||
// 单独处理陌生人会话
|
// 单独处理陌生人会话
|
||||||
@ -68,30 +71,45 @@ class GlobalBadge extends GetxController {
|
|||||||
);
|
);
|
||||||
chatItem.conversation.lastMessage = updatedConv.lastMessage;
|
chatItem.conversation.lastMessage = updatedConv.lastMessage;
|
||||||
chatItem.conversation.unreadCount = unread.data; // 获取陌生人未读总数
|
chatItem.conversation.unreadCount = unread.data; // 获取陌生人未读总数
|
||||||
} else {
|
update();
|
||||||
|
} else if (updatedConv.conversationID != '') {
|
||||||
// 其他类型统一更新处理
|
// 其他类型统一更新处理
|
||||||
logger.w('不需要分组的会话,正常更新');
|
logger.w('非陌生人消息的会话,正常更新');
|
||||||
chatItem.conversation = updatedConv;
|
chatItem.conversation = updatedConv;
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logger.e('会话列表中不包含的会话:$updatedIds');
|
logger.e('本地会话列表中不包含的会话:$updatedIds'); // 情况1:nofriend分组会话,情况2:会话分页未拉取到或者分组变更
|
||||||
|
// 检测这条会话数据远端是否存在
|
||||||
for (var cvID in updatedIds) {
|
for (var cvID in updatedIds) {
|
||||||
// 检测这条会话数据是否存在,如果存在说明在后面的分页中,不存在则是被删除不处理
|
// 检测这条会话数据是否存在,如果存在说明在后面的分页中,不存在则是被删除不处理
|
||||||
final isReal = await ImService.instance.getConversation(conversationID: cvID);
|
final isReal = await ImService.instance.getConversation(conversationID: cvID);
|
||||||
final V2TimConversation realConv = isReal.data;
|
|
||||||
if (isReal.success) {
|
if (isReal.success) {
|
||||||
if (realConv.conversationID.isNotEmpty) {
|
final V2TimConversation realConv = isReal.data;
|
||||||
|
|
||||||
|
if (realConv.lastMessage != null) {
|
||||||
// 陌生人会话列表单独处理
|
// 陌生人会话列表单独处理
|
||||||
if (Get.isRegistered<NotifyNoFriendController>()) {
|
if (Get.isRegistered<NotifyNoFriendController>()) {
|
||||||
|
logger.w('在陌生人会话列表');
|
||||||
final notifyCtl = Get.find<NotifyNoFriendController>();
|
final notifyCtl = Get.find<NotifyNoFriendController>();
|
||||||
notifyCtl.updateLastMsg(conversation: realConv);
|
// 有分组更新,没分组删除
|
||||||
} else {
|
if (realConv.conversationGroupList?.contains(ConversationType.noFriend.name) ?? false) {
|
||||||
// 收集要插入的数据
|
notifyCtl.updateLastMsg(conversation: realConv);
|
||||||
if (realConv.conversationGroupList?.isEmpty ?? false) {
|
} else {
|
||||||
willInsert.add(realConv);
|
notifyCtl.del(conversation: realConv);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 同步更新nofriend会话菜单入口的未读数量
|
||||||
|
if (chatItem.isCustomAdmin?.contains(ConversationType.noFriend.name) ?? false) {
|
||||||
|
await ctl.updateNoFriendMenu();
|
||||||
|
}
|
||||||
|
if (realConv.conversationGroupList?.contains(ConversationType.noFriend.name) ?? false) {
|
||||||
|
// 这个方法执行的逻辑:已有则刷新菜单入口数据,没有则创建菜单入口
|
||||||
|
await ctl.getNoFriendData(csion: realConv);
|
||||||
|
} else {
|
||||||
|
// 非陌生人会话,收集要插入的数据 (情况2)这条会话数据
|
||||||
|
willInsert.add(realConv);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -99,7 +117,17 @@ class GlobalBadge extends GetxController {
|
|||||||
}
|
}
|
||||||
// 添加收集的数据,别忘了分页获取的去重
|
// 添加收集的数据,别忘了分页获取的去重
|
||||||
if (willInsert.isNotEmpty) {
|
if (willInsert.isNotEmpty) {
|
||||||
var viewModelList = await ConversationViewModel.createConversationViewModel(convList: willInsert);
|
logger.w('收集到要插入的数据为:${willInsert.first.toLogString()}');
|
||||||
|
// willInsert 去重
|
||||||
|
final deduped = {for (var c in willInsert) c.conversationID.trim(): c}.values.toList();
|
||||||
|
// 已有会话ID集合
|
||||||
|
final existingIds = ctl.chatList.map((e) => e.conversation.conversationID.trim()).where((id) => id.isNotEmpty).toSet();
|
||||||
|
// 过滤掉已存在的会话
|
||||||
|
final filtered = deduped.where((c) => !existingIds.contains(c.conversationID.trim())).toList();
|
||||||
|
logger.w('最终需要插入的会话数量: ${filtered.length}, ids: ${filtered.map((c) => '"${(c.conversationID).trim()}"').toList()}');
|
||||||
|
|
||||||
|
final viewModelList = await ConversationViewModel.createConversationViewModel(convList: filtered);
|
||||||
|
|
||||||
ctl.chatList.insertAll(0, viewModelList);
|
ctl.chatList.insertAll(0, viewModelList);
|
||||||
}
|
}
|
||||||
// 如果没当前会话列表为空
|
// 如果没当前会话列表为空
|
||||||
@ -116,12 +144,23 @@ class GlobalBadge extends GetxController {
|
|||||||
final btime = b.conversation.lastMessage?.timestamp ?? 0;
|
final btime = b.conversation.lastMessage?.timestamp ?? 0;
|
||||||
return btime.compareTo(atime); // 降序
|
return btime.compareTo(atime); // 降序
|
||||||
});
|
});
|
||||||
|
//去重
|
||||||
|
final seen = <String>{};
|
||||||
|
ctl.chatList.retainWhere((item) {
|
||||||
|
final id = item.conversation.conversationID.trim();
|
||||||
|
if (seen.contains(id)) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
seen.add(id);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
ctl.chatList.refresh();
|
ctl.chatList.refresh();
|
||||||
},
|
}, //changeEnd;
|
||||||
);
|
);
|
||||||
// final ctl = Get.find<ChatController>();
|
// final ctl = Get.find<ChatController>();
|
||||||
// ctl.getConversationList();
|
// ctl.getConversationList();
|
||||||
_initUnreadCount();
|
initUnreadCount();
|
||||||
_addListener();
|
_addListener();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,7 +210,7 @@ class GlobalBadge extends GetxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 初始化时获取一次未读总数
|
/// 初始化时获取一次未读总数
|
||||||
void _initUnreadCount() async {
|
void initUnreadCount() async {
|
||||||
final res = await TencentImSDKPlugin.v2TIMManager.getConversationManager().getTotalUnreadMessageCount();
|
final res = await TencentImSDKPlugin.v2TIMManager.getConversationManager().getTotalUnreadMessageCount();
|
||||||
if (res.code == 0) {
|
if (res.code == 0) {
|
||||||
totalUnread.value = res.data ?? 0;
|
totalUnread.value = res.data ?? 0;
|
||||||
@ -187,7 +226,7 @@ class GlobalBadge extends GetxController {
|
|||||||
|
|
||||||
/// 处理安卓端异常的问题
|
/// 处理安卓端异常的问题
|
||||||
handAndroid() {
|
handAndroid() {
|
||||||
_initUnreadCount();
|
initUnreadCount();
|
||||||
final ctl = Get.find<ChatController>();
|
final ctl = Get.find<ChatController>();
|
||||||
ctl.initChatData();
|
ctl.initChatData();
|
||||||
ctl.getConversationList();
|
ctl.getConversationList();
|
||||||
@ -201,7 +240,7 @@ class GlobalBadge extends GetxController {
|
|||||||
|
|
||||||
/// 手动更新total
|
/// 手动更新total
|
||||||
Future<void> refreshUnreadCount() async {
|
Future<void> refreshUnreadCount() async {
|
||||||
_initUnreadCount();
|
initUnreadCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 移除监听器,防止重复注册
|
/// 移除监听器,防止重复注册
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:logger/logger.dart';
|
import 'package:logger/logger.dart';
|
||||||
|
import 'package:loopin/IM/controller/im_user_info_controller.dart';
|
||||||
import 'package:loopin/IM/im_service.dart';
|
import 'package:loopin/IM/im_service.dart';
|
||||||
import 'package:loopin/controller/video_module_controller.dart';
|
import 'package:loopin/controller/video_module_controller.dart';
|
||||||
import 'package:loopin/utils/common.dart';
|
import 'package:loopin/utils/common.dart';
|
||||||
@ -51,6 +52,8 @@ class ImCore {
|
|||||||
},
|
},
|
||||||
onUserSigExpired: () => logger.w("UserSig 过期"),
|
onUserSigExpired: () => logger.w("UserSig 过期"),
|
||||||
onSelfInfoUpdated: (V2TimUserFullInfo info) {
|
onSelfInfoUpdated: (V2TimUserFullInfo info) {
|
||||||
|
final ImUserInfoController imUserInfoController = Get.find<ImUserInfoController>();
|
||||||
|
imUserInfoController.refreshUserInfo();
|
||||||
logger.i("用户信息更新: ${info.toJson()}");
|
logger.i("用户信息更新: ${info.toJson()}");
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import 'package:logger/logger.dart';
|
import 'package:logger/logger.dart';
|
||||||
import 'package:loopin/utils/notification_banner.dart';
|
|
||||||
import 'package:tencent_cloud_chat_sdk/enum/V2TimFriendshipListener.dart';
|
import 'package:tencent_cloud_chat_sdk/enum/V2TimFriendshipListener.dart';
|
||||||
import 'package:tencent_cloud_chat_sdk/models/v2_tim_friend_application.dart';
|
import 'package:tencent_cloud_chat_sdk/models/v2_tim_friend_application.dart';
|
||||||
import 'package:tencent_cloud_chat_sdk/models/v2_tim_friend_info.dart';
|
import 'package:tencent_cloud_chat_sdk/models/v2_tim_friend_info.dart';
|
||||||
@ -62,7 +61,7 @@ class ImFriendListeners {
|
|||||||
for (var item in userInfoList) {
|
for (var item in userInfoList) {
|
||||||
logger.i('新增粉丝:${item.toJson()}');
|
logger.i('新增粉丝:${item.toJson()}');
|
||||||
}
|
}
|
||||||
NotificationBanner.foucs(userInfoList.last);
|
// NotificationBanner.foucs(userInfoList.last);
|
||||||
} else {
|
} else {
|
||||||
// 粉丝列表删除用户的通知
|
// 粉丝列表删除用户的通知
|
||||||
for (var item in userInfoList) {
|
for (var item in userInfoList) {
|
||||||
|
@ -3,6 +3,7 @@ import 'dart:async';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:logger/logger.dart';
|
import 'package:logger/logger.dart';
|
||||||
import 'package:loopin/IM/controller/chat_detail_controller.dart';
|
import 'package:loopin/IM/controller/chat_detail_controller.dart';
|
||||||
|
import 'package:loopin/IM/global_badge.dart';
|
||||||
import 'package:loopin/IM/im_message.dart';
|
import 'package:loopin/IM/im_message.dart';
|
||||||
import 'package:loopin/IM/im_service.dart';
|
import 'package:loopin/IM/im_service.dart';
|
||||||
import 'package:loopin/utils/index.dart';
|
import 'package:loopin/utils/index.dart';
|
||||||
@ -49,12 +50,12 @@ class ImMessageListenerService extends GetxService {
|
|||||||
DateTime.now().millisecondsSinceEpoch,
|
DateTime.now().millisecondsSinceEpoch,
|
||||||
)) {
|
)) {
|
||||||
// 消息时间间隔超过3分钟插入伪消息
|
// 消息时间间隔超过3分钟插入伪消息
|
||||||
final showLabel = Utils().formatChatTime(DateTime.now().millisecondsSinceEpoch ~/ 1000);
|
final showLabel = Utils.formatChatTime(DateTime.now().millisecondsSinceEpoch ~/ 1000);
|
||||||
final resMsg = await IMMessage().insertTimeLabel(showLabel, selfUserId);
|
final resMsg = await IMMessage().insertTimeLabel(showLabel, selfUserId);
|
||||||
messagesToInsert.add(resMsg.data);
|
messagesToInsert.add(resMsg.data);
|
||||||
} else {
|
} else {
|
||||||
// 没数据的时候直接插入伪消息
|
// 没数据的时候直接插入伪消息
|
||||||
final showLabel = Utils().formatChatTime(DateTime.now().millisecondsSinceEpoch ~/ 1000);
|
final showLabel = Utils.formatChatTime(DateTime.now().millisecondsSinceEpoch ~/ 1000);
|
||||||
final resMsg = await IMMessage().insertTimeLabel(showLabel, selfUserId);
|
final resMsg = await IMMessage().insertTimeLabel(showLabel, selfUserId);
|
||||||
messagesToInsert.add(resMsg.data);
|
messagesToInsert.add(resMsg.data);
|
||||||
}
|
}
|
||||||
@ -177,11 +178,15 @@ class ImMessageListenerService extends GetxService {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRecvC2CReadReceipt: (List<V2TimMessageReceipt> receiptList) {
|
onRecvC2CReadReceipt: (List<V2TimMessageReceipt> receiptList) {
|
||||||
|
final gtl = Get.find<GlobalBadge>();
|
||||||
|
gtl.initUnreadCount();
|
||||||
for (var receipt in receiptList) {
|
for (var receipt in receiptList) {
|
||||||
logger.i("C2C已读: msgID=${receipt.msgID}, userID=${receipt.userID}");
|
logger.i("C2C已读: msgID=${receipt.msgID}, userID=${receipt.userID}");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRecvMessageReadReceipts: (List<V2TimMessageReceipt> receiptList) {
|
onRecvMessageReadReceipts: (List<V2TimMessageReceipt> receiptList) {
|
||||||
|
final gtl = Get.find<GlobalBadge>();
|
||||||
|
gtl.initUnreadCount();
|
||||||
for (var receipt in receiptList) {
|
for (var receipt in receiptList) {
|
||||||
logger.i("群已读: groupID=${receipt.groupID}, msgID=${receipt.msgID}, read=${receipt.readCount}, unread=${receipt.unreadCount}");
|
logger.i("群已读: groupID=${receipt.groupID}, msgID=${receipt.msgID}, read=${receipt.readCount}, unread=${receipt.unreadCount}");
|
||||||
}
|
}
|
||||||
|
@ -710,4 +710,15 @@ class ImService {
|
|||||||
);
|
);
|
||||||
return ImResult.wrap(res);
|
return ImResult.wrap(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 获取我的关注列表
|
||||||
|
/// [nextCursor] 分页游标,首次传空字符串
|
||||||
|
Future<ImResult<V2TimUserInfoResult>> getMyFollowingList({
|
||||||
|
required String nextCursor,
|
||||||
|
}) async {
|
||||||
|
final res = await TIMFriendshipManager.instance.getMyFollowingList(
|
||||||
|
nextCursor: nextCursor,
|
||||||
|
);
|
||||||
|
return ImResult.wrap(res);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,16 @@ enum ConversationType {
|
|||||||
groupNotify, //群通知
|
groupNotify, //群通知
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 常量映射
|
||||||
|
class ConversationTypeStatic {
|
||||||
|
static const String noFriend = 'noFriend';
|
||||||
|
static const String system = 'system';
|
||||||
|
static const String newFoucs = 'newFoucs';
|
||||||
|
static const String interaction = 'interaction';
|
||||||
|
static const String order = 'order';
|
||||||
|
static const String groupNotify = 'groupNotify';
|
||||||
|
}
|
||||||
|
|
||||||
extension ConversationTypeExtension on ConversationType {
|
extension ConversationTypeExtension on ConversationType {
|
||||||
String get name {
|
String get name {
|
||||||
switch (this) {
|
switch (this) {
|
||||||
|
@ -19,7 +19,6 @@ class ConversationViewModel {
|
|||||||
|
|
||||||
// 提前收集所有需要批量查询的 userID 和 groupID
|
// 提前收集所有需要批量查询的 userID 和 groupID
|
||||||
for (var conv in convList) {
|
for (var conv in convList) {
|
||||||
logger.e('未过滤前到会话数据:${conv.toLogString()}');
|
|
||||||
if (conv.userID != null) {
|
if (conv.userID != null) {
|
||||||
userIDList.add(conv.userID!);
|
userIDList.add(conv.userID!);
|
||||||
} else if (conv.groupID != null) {
|
} else if (conv.groupID != null) {
|
||||||
|
@ -530,7 +530,7 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
|
|||||||
final url = obj['url'];
|
final url = obj['url'];
|
||||||
final title = obj['title'];
|
final title = obj['title'];
|
||||||
final price = obj['price'];
|
final price = obj['price'];
|
||||||
final sell = Utils().graceNumber(int.tryParse(obj['sell'])!);
|
final sell = Utils.graceNumber(int.tryParse(obj['sell'])!);
|
||||||
msgtpl.add(RenderChatItem(
|
msgtpl.add(RenderChatItem(
|
||||||
data: item,
|
data: item,
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
@ -1039,12 +1039,12 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
|
|||||||
DateTime.now().millisecondsSinceEpoch,
|
DateTime.now().millisecondsSinceEpoch,
|
||||||
)) {
|
)) {
|
||||||
// 消息时间间隔超过3分钟插入伪消息
|
// 消息时间间隔超过3分钟插入伪消息
|
||||||
final showLabel = Utils().formatChatTime(DateTime.now().millisecondsSinceEpoch ~/ 1000);
|
final showLabel = Utils.formatChatTime(DateTime.now().millisecondsSinceEpoch ~/ 1000);
|
||||||
final resMsg = await IMMessage().insertTimeLabel(showLabel, selfUserId);
|
final resMsg = await IMMessage().insertTimeLabel(showLabel, selfUserId);
|
||||||
messagesToInsert.add(resMsg.data);
|
messagesToInsert.add(resMsg.data);
|
||||||
} else {
|
} else {
|
||||||
// 没数据的时候直接插入伪消息
|
// 没数据的时候直接插入伪消息
|
||||||
final showLabel = Utils().formatChatTime(DateTime.now().millisecondsSinceEpoch ~/ 1000);
|
final showLabel = Utils.formatChatTime(DateTime.now().millisecondsSinceEpoch ~/ 1000);
|
||||||
|
|
||||||
final resMsg = await IMMessage().insertTimeLabel(showLabel, selfUserId);
|
final resMsg = await IMMessage().insertTimeLabel(showLabel, selfUserId);
|
||||||
messagesToInsert.add(resMsg.data);
|
messagesToInsert.add(resMsg.data);
|
||||||
@ -2155,18 +2155,32 @@ class RenderChatItem extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
String? displayName = (data.friendRemark?.isNotEmpty ?? false)
|
||||||
|
? data.friendRemark
|
||||||
|
: (data.nameCard?.isNotEmpty ?? false)
|
||||||
|
? data.nameCard
|
||||||
|
: (data.nickName?.isNotEmpty ?? false)
|
||||||
|
? data.nickName
|
||||||
|
: '未知昵称';
|
||||||
return Container(
|
return Container(
|
||||||
margin: const EdgeInsets.only(bottom: 10.0),
|
margin: const EdgeInsets.only(bottom: 10.0),
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
!(data.isSelf ?? false)
|
!(data.isSelf ?? false)
|
||||||
? SizedBox(
|
? GestureDetector(
|
||||||
height: 35.0,
|
onTap: () {
|
||||||
width: 35.0,
|
// 头像点击事件
|
||||||
child: ClipRRect(
|
logger.e("点击了头像");
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(20.0)),
|
Get.toNamed('/vloger', arguments: {'memberId': data.sender});
|
||||||
child: NetworkOrAssetImage(imageUrl: data.faceUrl),
|
},
|
||||||
|
child: SizedBox(
|
||||||
|
height: 35.0,
|
||||||
|
width: 35.0,
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(20.0)),
|
||||||
|
child: NetworkOrAssetImage(imageUrl: data.faceUrl),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: const SizedBox.shrink(),
|
: const SizedBox.shrink(),
|
||||||
@ -2176,13 +2190,13 @@ class RenderChatItem extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: !(data.isSelf ?? false) ? CrossAxisAlignment.start : CrossAxisAlignment.end,
|
crossAxisAlignment: !(data.isSelf ?? false) ? CrossAxisAlignment.start : CrossAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
// Text(
|
||||||
data.friendRemark ?? data.nameCard ?? data.nickName ?? '未知昵称',
|
// displayName ?? '未知昵称',
|
||||||
style: const TextStyle(color: Colors.grey, fontSize: 12.0),
|
// style: const TextStyle(color: Colors.grey, fontSize: 12.0),
|
||||||
),
|
// ),
|
||||||
const SizedBox(
|
// const SizedBox(
|
||||||
height: 3.0,
|
// height: 3.0,
|
||||||
),
|
// ),
|
||||||
Stack(
|
Stack(
|
||||||
children: [
|
children: [
|
||||||
// 气泡箭头
|
// 气泡箭头
|
||||||
|
@ -792,12 +792,12 @@ class _ChatState extends State<ChatGroup> with SingleTickerProviderStateMixin {
|
|||||||
DateTime.now().millisecondsSinceEpoch,
|
DateTime.now().millisecondsSinceEpoch,
|
||||||
)) {
|
)) {
|
||||||
// 消息时间间隔超过3分钟插入伪消息
|
// 消息时间间隔超过3分钟插入伪消息
|
||||||
final showLabel = Utils().formatChatTime(DateTime.now().millisecondsSinceEpoch ~/ 1000);
|
final showLabel = Utils.formatChatTime(DateTime.now().millisecondsSinceEpoch ~/ 1000);
|
||||||
final resMsg = await IMMessage().insertTimeLabel(showLabel, selfUserId);
|
final resMsg = await IMMessage().insertTimeLabel(showLabel, selfUserId);
|
||||||
messagesToInsert.add(resMsg.data);
|
messagesToInsert.add(resMsg.data);
|
||||||
} else {
|
} else {
|
||||||
// 没数据的时候直接插入伪消息
|
// 没数据的时候直接插入伪消息
|
||||||
final showLabel = Utils().formatChatTime(DateTime.now().millisecondsSinceEpoch ~/ 1000);
|
final showLabel = Utils.formatChatTime(DateTime.now().millisecondsSinceEpoch ~/ 1000);
|
||||||
|
|
||||||
final resMsg = await IMMessage().insertTimeLabel(showLabel, selfUserId);
|
final resMsg = await IMMessage().insertTimeLabel(showLabel, selfUserId);
|
||||||
messagesToInsert.add(resMsg.data);
|
messagesToInsert.add(resMsg.data);
|
||||||
|
@ -3,16 +3,17 @@ library;
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:loopin/IM/controller/chat_controller.dart';
|
||||||
import 'package:loopin/IM/controller/chat_detail_controller.dart';
|
import 'package:loopin/IM/controller/chat_detail_controller.dart';
|
||||||
import 'package:loopin/IM/im_message.dart';
|
import 'package:loopin/IM/im_message.dart';
|
||||||
import 'package:loopin/IM/im_result.dart';
|
import 'package:loopin/IM/im_result.dart';
|
||||||
import 'package:loopin/IM/im_service.dart';
|
import 'package:loopin/IM/im_service.dart';
|
||||||
|
import 'package:loopin/components/network_or_asset_image.dart';
|
||||||
import 'package:loopin/components/preview_video.dart';
|
import 'package:loopin/components/preview_video.dart';
|
||||||
import 'package:loopin/models/conversation_type.dart';
|
import 'package:loopin/models/conversation_type.dart';
|
||||||
import 'package:loopin/pages/chat/notify_controller/notify_no_friend_controller.dart';
|
import 'package:loopin/pages/chat/notify_controller/notify_no_friend_controller.dart';
|
||||||
import 'package:loopin/utils/snapshot.dart';
|
import 'package:loopin/utils/snapshot.dart';
|
||||||
import 'package:shirne_dialog/shirne_dialog.dart';
|
import 'package:shirne_dialog/shirne_dialog.dart';
|
||||||
import 'package:tencent_cloud_chat_sdk/enum/friend_type_enum.dart';
|
|
||||||
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart';
|
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart';
|
||||||
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message.dart';
|
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message.dart';
|
||||||
import 'package:wechat_assets_picker/wechat_assets_picker.dart';
|
import 'package:wechat_assets_picker/wechat_assets_picker.dart';
|
||||||
@ -41,6 +42,8 @@ class _ChatNoFriendState extends State<ChatNoFriend> with SingleTickerProviderSt
|
|||||||
// 聊天消息模块
|
// 聊天消息模块
|
||||||
final bool isNeedScrollBottom = true;
|
final bool isNeedScrollBottom = true;
|
||||||
|
|
||||||
|
final String _tips = '回复或关注对方之前,对方只能发送一条消息';
|
||||||
|
|
||||||
bool isLoading = false; // 是否在加载中
|
bool isLoading = false; // 是否在加载中
|
||||||
bool hasMore = true; // 是否还有更多数据
|
bool hasMore = true; // 是否还有更多数据
|
||||||
final RxBool _throttleFlag = false.obs; // 滚动节流锁
|
final RxBool _throttleFlag = false.obs; // 滚动节流锁
|
||||||
@ -152,13 +155,27 @@ class _ChatNoFriendState extends State<ChatNoFriend> with SingleTickerProviderSt
|
|||||||
|
|
||||||
void isMyFriend() async {
|
void isMyFriend() async {
|
||||||
final isFriendId = arguments.value.userID;
|
final isFriendId = arguments.value.userID;
|
||||||
final isfd = await ImService.instance.isMyFriend(isFriendId!, FriendTypeEnum.V2TIM_FRIEND_TYPE_BOTH);
|
// final isfd = await ImService.instance.isMyFriend(isFriendId!, FriendTypeEnum.V2TIM_FRIEND_TYPE_BOTH);
|
||||||
|
// if (isfd.success) {
|
||||||
|
// controller.isFriend.value = isfd.data;
|
||||||
|
// print(isfd.data);
|
||||||
|
// } else {
|
||||||
|
// controller.isFriend.value = false;
|
||||||
|
// print(isfd.desc);
|
||||||
|
// }
|
||||||
|
/// 0:不是好友也没有关注
|
||||||
|
/// 1:你关注了对方(单向)
|
||||||
|
/// 2:对方关注了你(单向)
|
||||||
|
/// 3:互相关注(双向好友)
|
||||||
|
final isfd = await ImService.instance.checkFollowType(userIDList: [isFriendId!]);
|
||||||
if (isfd.success) {
|
if (isfd.success) {
|
||||||
controller.isFriend.value = isfd.data;
|
controller.followType.value = isfd.data?.first.followType ?? 0;
|
||||||
print(isfd.data);
|
if ([3].contains(controller.followType.value)) {
|
||||||
} else {
|
controller.isFriend.value = true;
|
||||||
controller.isFriend.value = false;
|
} else {
|
||||||
print(isfd.desc);
|
controller.isFriend.value = false;
|
||||||
|
}
|
||||||
|
logger.i('当前聊天的关系为:${controller.followType.value}');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -294,9 +311,9 @@ class _ChatNoFriendState extends State<ChatNoFriend> with SingleTickerProviderSt
|
|||||||
padding: const EdgeInsets.all(10.0),
|
padding: const EdgeInsets.all(10.0),
|
||||||
child: RichTextUtil.getRichText(item.textElem?.text ?? '', color: !(item.isSelf ?? false) ? Colors.black : Colors.white), // 可自定义解析emoj/网址/电话
|
child: RichTextUtil.getRichText(item.textElem?.text ?? '', color: !(item.isSelf ?? false) ? Colors.black : Colors.white), // 可自定义解析emoj/网址/电话
|
||||||
),
|
),
|
||||||
onLongPress: () {
|
// onLongPress: () {
|
||||||
contextMenuDialog();
|
// contextMenuDialog();
|
||||||
},
|
// },
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -883,12 +900,12 @@ class _ChatNoFriendState extends State<ChatNoFriend> with SingleTickerProviderSt
|
|||||||
DateTime.now().millisecondsSinceEpoch,
|
DateTime.now().millisecondsSinceEpoch,
|
||||||
)) {
|
)) {
|
||||||
// 消息时间间隔超过3分钟插入伪消息
|
// 消息时间间隔超过3分钟插入伪消息
|
||||||
final showLabel = Utils().formatChatTime(DateTime.now().millisecondsSinceEpoch ~/ 1000);
|
final showLabel = Utils.formatChatTime(DateTime.now().millisecondsSinceEpoch ~/ 1000);
|
||||||
final resMsg = await IMMessage().insertTimeLabel(showLabel, selfUserId);
|
final resMsg = await IMMessage().insertTimeLabel(showLabel, selfUserId);
|
||||||
messagesToInsert.add(resMsg.data);
|
messagesToInsert.add(resMsg.data);
|
||||||
} else {
|
} else {
|
||||||
// 没数据的时候直接插入伪消息
|
// 没数据的时候直接插入伪消息
|
||||||
final showLabel = Utils().formatChatTime(DateTime.now().millisecondsSinceEpoch ~/ 1000);
|
final showLabel = Utils.formatChatTime(DateTime.now().millisecondsSinceEpoch ~/ 1000);
|
||||||
|
|
||||||
final resMsg = await IMMessage().insertTimeLabel(showLabel, selfUserId);
|
final resMsg = await IMMessage().insertTimeLabel(showLabel, selfUserId);
|
||||||
messagesToInsert.add(resMsg.data);
|
messagesToInsert.add(resMsg.data);
|
||||||
@ -1203,6 +1220,7 @@ class _ChatNoFriendState extends State<ChatNoFriend> with SingleTickerProviderSt
|
|||||||
|
|
||||||
// 长按消息菜单
|
// 长按消息菜单
|
||||||
void contextMenuDialog() {
|
void contextMenuDialog() {
|
||||||
|
return;
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
@ -1295,115 +1313,66 @@ class _ChatNoFriendState extends State<ChatNoFriend> with SingleTickerProviderSt
|
|||||||
)),
|
)),
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
// IconButton(
|
||||||
icon: const Icon(
|
// icon: const Icon(
|
||||||
Icons.more_horiz,
|
// Icons.more_horiz,
|
||||||
color: Colors.white,
|
// color: Colors.white,
|
||||||
),
|
// ),
|
||||||
onPressed: () async {
|
// onPressed: () async {
|
||||||
final paddingTop = MediaQuery.of(Get.context!).padding.top;
|
// final paddingTop = MediaQuery.of(Get.context!).padding.top;
|
||||||
|
|
||||||
final selected = await showMenu(
|
// final selected = await showMenu(
|
||||||
context: Get.context!,
|
// context: Get.context!,
|
||||||
position: RelativeRect.fromLTRB(
|
// position: RelativeRect.fromLTRB(
|
||||||
double.infinity,
|
// double.infinity,
|
||||||
kToolbarHeight + paddingTop - 12,
|
// kToolbarHeight + paddingTop - 12,
|
||||||
8,
|
// 8,
|
||||||
double.infinity,
|
// double.infinity,
|
||||||
),
|
// ),
|
||||||
color: FStyle.primaryColor,
|
// color: FStyle.primaryColor,
|
||||||
elevation: 8,
|
// elevation: 8,
|
||||||
items: [
|
// items: [
|
||||||
PopupMenuItem<String>(
|
// PopupMenuItem<String>(
|
||||||
value: 'remark',
|
// value: 'report',
|
||||||
child: Row(
|
// child: Row(
|
||||||
children: [
|
// children: [
|
||||||
Icon(Icons.edit, color: Colors.white, size: 18),
|
// Icon(Icons.report, color: Colors.white, size: 18),
|
||||||
SizedBox(width: 8),
|
// SizedBox(width: 8),
|
||||||
Text(
|
// Text(
|
||||||
'设置备注',
|
// '举报',
|
||||||
style: TextStyle(color: Colors.white),
|
// style: TextStyle(color: Colors.white),
|
||||||
),
|
// ),
|
||||||
],
|
// ],
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
PopupMenuItem<String>(
|
// PopupMenuItem<String>(
|
||||||
value: 'not',
|
// value: 'block',
|
||||||
child: Row(
|
// child: Row(
|
||||||
children: [
|
// children: [
|
||||||
Icon(Icons.do_not_disturb_on, color: Colors.white, size: 18),
|
// Icon(Icons.block, color: Colors.white, size: 18),
|
||||||
SizedBox(width: 8),
|
// SizedBox(width: 8),
|
||||||
Text(
|
// Text(
|
||||||
'设为免打扰',
|
// '拉黑',
|
||||||
style: TextStyle(color: Colors.white),
|
// style: TextStyle(color: Colors.white),
|
||||||
),
|
// ),
|
||||||
],
|
// ],
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
PopupMenuItem<String>(
|
// ],
|
||||||
value: 'report',
|
// );
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Icon(Icons.report, color: Colors.white, size: 18),
|
|
||||||
SizedBox(width: 8),
|
|
||||||
Text(
|
|
||||||
'举报',
|
|
||||||
style: TextStyle(color: Colors.white),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
PopupMenuItem<String>(
|
|
||||||
value: 'block',
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Icon(Icons.block, color: Colors.white, size: 18),
|
|
||||||
SizedBox(width: 8),
|
|
||||||
Text(
|
|
||||||
'拉黑',
|
|
||||||
style: TextStyle(color: Colors.white),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
PopupMenuItem<String>(
|
|
||||||
value: 'foucs',
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Icon(Icons.person_remove_alt_1, color: Colors.white, size: 18),
|
|
||||||
SizedBox(width: 8),
|
|
||||||
Text(
|
|
||||||
'取消关注',
|
|
||||||
style: TextStyle(color: Colors.white),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
if (selected != null) {
|
// if (selected != null) {
|
||||||
switch (selected) {
|
// switch (selected) {
|
||||||
case 'remark':
|
// case 'report':
|
||||||
print('点击了备注');
|
// print('点击了举报');
|
||||||
setRemark();
|
// break;
|
||||||
break;
|
// case 'block':
|
||||||
case 'not':
|
// print('点击了拉黑');
|
||||||
print('点击了免打扰');
|
// break;
|
||||||
break;
|
// }
|
||||||
case 'report':
|
// }
|
||||||
print('点击了举报');
|
// },
|
||||||
break;
|
// ),
|
||||||
case 'block':
|
|
||||||
print('点击了拉黑');
|
|
||||||
break;
|
|
||||||
case 'foucs':
|
|
||||||
print('点击了取关');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: Flex(
|
body: Flex(
|
||||||
@ -1430,6 +1399,39 @@ class _ChatNoFriendState extends State<ChatNoFriend> with SingleTickerProviderSt
|
|||||||
// ),
|
// ),
|
||||||
// ),
|
// ),
|
||||||
|
|
||||||
|
// 聊天内容
|
||||||
|
// Expanded(
|
||||||
|
// child: GestureDetector(
|
||||||
|
// behavior: HitTestBehavior.opaque,
|
||||||
|
// onTap: handleClickChatArea,
|
||||||
|
// child: LayoutBuilder(
|
||||||
|
// builder: (context, constraints) {
|
||||||
|
// return Obx(() {
|
||||||
|
// // final tipWidget = Builder(builder: (context) => Text(tips));
|
||||||
|
|
||||||
|
// final msgWidgets = renderChatList().reversed.toList();
|
||||||
|
// // if (controller.isFriend.value) {}
|
||||||
|
// return ListView(
|
||||||
|
// controller: chatController,
|
||||||
|
// reverse: true,
|
||||||
|
// padding: const EdgeInsets.all(10.0),
|
||||||
|
// children: [
|
||||||
|
// ConstrainedBox(
|
||||||
|
// constraints: BoxConstraints(
|
||||||
|
// minHeight: constraints.maxHeight - 20,
|
||||||
|
// ),
|
||||||
|
// child: Column(
|
||||||
|
// mainAxisSize: MainAxisSize.min,
|
||||||
|
// children: msgWidgets,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
|
// );
|
||||||
|
// });
|
||||||
|
// },
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
// 聊天内容
|
// 聊天内容
|
||||||
Expanded(
|
Expanded(
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
@ -1438,22 +1440,92 @@ class _ChatNoFriendState extends State<ChatNoFriend> with SingleTickerProviderSt
|
|||||||
child: LayoutBuilder(
|
child: LayoutBuilder(
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
return Obx(() {
|
return Obx(() {
|
||||||
|
final showTipWidget = controller.followType.value;
|
||||||
|
const double tipHeight = 50; // 提示横幅固定高度
|
||||||
|
|
||||||
final msgWidgets = renderChatList().reversed.toList();
|
final msgWidgets = renderChatList().reversed.toList();
|
||||||
|
|
||||||
return ListView(
|
return Stack(
|
||||||
controller: chatController,
|
|
||||||
reverse: true,
|
|
||||||
padding: const EdgeInsets.all(10.0),
|
|
||||||
children: [
|
children: [
|
||||||
ConstrainedBox(
|
ListView(
|
||||||
constraints: BoxConstraints(
|
controller: chatController,
|
||||||
minHeight: constraints.maxHeight - 20,
|
reverse: true,
|
||||||
),
|
padding: EdgeInsets.only(
|
||||||
child: Column(
|
top: [1, 2].contains(showTipWidget) ? tipHeight + 10 : 0, // 动态预留顶部空间
|
||||||
mainAxisSize: MainAxisSize.min,
|
left: 10,
|
||||||
children: msgWidgets,
|
right: 10,
|
||||||
|
bottom: 10,
|
||||||
),
|
),
|
||||||
|
children: [
|
||||||
|
ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
minHeight: constraints.maxHeight - ([1, 2].contains(showTipWidget) ? tipHeight : 0),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: msgWidgets,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
|
if ([1, 2].contains(showTipWidget))
|
||||||
|
Positioned(
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
height: tipHeight, // 固定高度
|
||||||
|
child: Container(
|
||||||
|
color: Colors.white,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.error_outline,
|
||||||
|
color: FStyle.primaryColor,
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: 10,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
'对方未回复或互关前仅可发送一条消息',
|
||||||
|
style: const TextStyle(color: Colors.black, fontSize: 14),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Visibility(
|
||||||
|
visible: [2].contains(showTipWidget),
|
||||||
|
child: TextButton(
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
backgroundColor: FStyle.primaryColor,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
|
),
|
||||||
|
onPressed: () async {
|
||||||
|
// 回关
|
||||||
|
final res = await ImService.instance.followUser(userIDList: [arguments.value.userID!]);
|
||||||
|
if (res.success) {
|
||||||
|
controller.isFriend.value = true;
|
||||||
|
controller.followType.value = 3;
|
||||||
|
if (arguments.value.conversationGroupList?.isNotEmpty == true) {
|
||||||
|
//把会话数据从陌生人分组移除
|
||||||
|
final ctl = Get.find<ChatController>();
|
||||||
|
ctl.removeNoFriend(conversationID: arguments.value.conversationID);
|
||||||
|
ctl.updateNoFriendMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: const Text('回关', style: TextStyle(color: Colors.white)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -1481,19 +1553,28 @@ class _ChatNoFriendState extends State<ChatNoFriend> with SingleTickerProviderSt
|
|||||||
children: [
|
children: [
|
||||||
InkWell(
|
InkWell(
|
||||||
child: Icon(
|
child: Icon(
|
||||||
voiceBtnEnable ? Icons.keyboard_outlined : Icons.contactless_outlined,
|
// voiceBtnEnable ? Icons.keyboard_outlined : Icons.contactless_outlined,
|
||||||
|
Icons.keyboard_outlined,
|
||||||
color: const Color(0xFF3B3B3B),
|
color: const Color(0xFF3B3B3B),
|
||||||
size: 30.0,
|
size: 30.0,
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
toolbarEnable = false;
|
toolbarEnable = false;
|
||||||
if (voiceBtnEnable) {
|
if (editorFocusNode.hasFocus) {
|
||||||
voiceBtnEnable = false;
|
|
||||||
editorFocusNode.requestFocus();
|
|
||||||
} else {
|
|
||||||
voiceBtnEnable = true;
|
|
||||||
editorFocusNode.unfocus();
|
editorFocusNode.unfocus();
|
||||||
|
} else {
|
||||||
|
editorFocusNode.requestFocus();
|
||||||
|
}
|
||||||
|
if (toolbarEnable) {
|
||||||
|
// voiceBtnEnable = false;
|
||||||
|
// editorFocusNode.requestFocus();
|
||||||
|
// editorFocusNode.requestFocus();
|
||||||
|
} else {
|
||||||
|
// voiceBtnEnable = true;
|
||||||
|
// toolbarEnable = true;
|
||||||
|
|
||||||
|
// editorFocusNode.unfocus();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -1597,29 +1678,31 @@ class _ChatNoFriendState extends State<ChatNoFriend> with SingleTickerProviderSt
|
|||||||
const SizedBox(
|
const SizedBox(
|
||||||
width: 10.0,
|
width: 10.0,
|
||||||
),
|
),
|
||||||
InkWell(
|
|
||||||
child: const Icon(
|
// InkWell(
|
||||||
Icons.add_reaction_rounded,
|
// child: const Icon(
|
||||||
color: Color(0xFF3B3B3B),
|
// Icons.add_reaction_rounded,
|
||||||
size: 30.0,
|
// color: Color(0xFF3B3B3B),
|
||||||
),
|
// size: 30.0,
|
||||||
onTap: () {
|
// ),
|
||||||
handleEmojChooseState(0);
|
// onTap: () {
|
||||||
},
|
// handleEmojChooseState(0);
|
||||||
),
|
// },
|
||||||
const SizedBox(
|
// ),
|
||||||
width: 8.0,
|
// const SizedBox(
|
||||||
),
|
// width: 8.0,
|
||||||
InkWell(
|
// ),
|
||||||
child: const Icon(
|
|
||||||
Icons.add,
|
// InkWell(
|
||||||
color: Color(0xFF3B3B3B),
|
// child: const Icon(
|
||||||
size: 30.0,
|
// Icons.add,
|
||||||
),
|
// color: Color(0xFF3B3B3B),
|
||||||
onTap: () {
|
// size: 30.0,
|
||||||
handleEmojChooseState(1);
|
// ),
|
||||||
},
|
// onTap: () {
|
||||||
),
|
// handleEmojChooseState(1);
|
||||||
|
// },
|
||||||
|
// ),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
width: 8.0,
|
width: 8.0,
|
||||||
),
|
),
|
||||||
@ -1942,27 +2025,33 @@ class RenderChatItem extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
String? displayName = (data.friendRemark?.isNotEmpty ?? false)
|
||||||
|
? data.friendRemark
|
||||||
|
: (data.nameCard?.isNotEmpty ?? false)
|
||||||
|
? data.nameCard
|
||||||
|
: (data.nickName?.isNotEmpty ?? false)
|
||||||
|
? data.nickName
|
||||||
|
: '未知昵称';
|
||||||
return Container(
|
return Container(
|
||||||
margin: const EdgeInsets.only(bottom: 10.0),
|
margin: const EdgeInsets.only(bottom: 10.0),
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
!(data.isSelf ?? false)
|
!(data.isSelf ?? false)
|
||||||
? SizedBox(
|
? GestureDetector(
|
||||||
height: 35.0,
|
onTap: () {
|
||||||
width: 35.0,
|
// 头像点击事件
|
||||||
child: ClipRRect(
|
logger.e("点击了头像");
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(20.0)),
|
Get.toNamed('/vloger', arguments: {'memberId': data.sender});
|
||||||
child: Image.network(
|
|
||||||
data.faceUrl ?? 'https://wuzhongjie.com.cn/download/logo.png',
|
// 你可以在这里处理点击事件,例如打开用户详情页
|
||||||
errorBuilder: (context, error, stackTrace) {
|
},
|
||||||
return Image.asset(
|
child: SizedBox(
|
||||||
'assets/images/pic1.jpg',
|
height: 35.0,
|
||||||
height: 60.0,
|
width: 35.0,
|
||||||
width: 60.0,
|
child: ClipRRect(
|
||||||
fit: BoxFit.cover,
|
borderRadius: const BorderRadius.all(Radius.circular(20.0)),
|
||||||
);
|
child: NetworkOrAssetImage(imageUrl: data.faceUrl),
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -1973,13 +2062,13 @@ class RenderChatItem extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: !(data.isSelf ?? false) ? CrossAxisAlignment.start : CrossAxisAlignment.end,
|
crossAxisAlignment: !(data.isSelf ?? false) ? CrossAxisAlignment.start : CrossAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
// Text(
|
||||||
data.friendRemark ?? data.nameCard ?? data.nickName ?? '未知昵称',
|
// displayName ?? '未知昵称',
|
||||||
style: const TextStyle(color: Colors.grey, fontSize: 12.0),
|
// style: const TextStyle(color: Colors.grey, fontSize: 12.0),
|
||||||
),
|
// ),
|
||||||
const SizedBox(
|
// const SizedBox(
|
||||||
height: 3.0,
|
// height: 3.0,
|
||||||
),
|
// ),
|
||||||
Stack(
|
Stack(
|
||||||
children: [
|
children: [
|
||||||
// 气泡箭头
|
// 气泡箭头
|
||||||
@ -2011,17 +2100,7 @@ class RenderChatItem extends StatelessWidget {
|
|||||||
width: 35.0,
|
width: 35.0,
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(20.0)),
|
borderRadius: const BorderRadius.all(Radius.circular(20.0)),
|
||||||
child: Image.network(
|
child: NetworkOrAssetImage(imageUrl: data.faceUrl),
|
||||||
data.faceUrl ?? 'https://wuzhongjie.com.cn/download/logo.png',
|
|
||||||
errorBuilder: (context, error, stackTrace) {
|
|
||||||
return Image.asset(
|
|
||||||
'assets/images/pic1.jpg',
|
|
||||||
height: 60.0,
|
|
||||||
width: 60.0,
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: const SizedBox.shrink(),
|
: const SizedBox.shrink(),
|
||||||
|
@ -232,17 +232,18 @@ class ChatPageState extends State<ChatPage> {
|
|||||||
if (selected != null) {
|
if (selected != null) {
|
||||||
switch (selected) {
|
switch (selected) {
|
||||||
case 'group':
|
case 'group':
|
||||||
print('点击了发起群聊');
|
logger.w('点击了发起群聊');
|
||||||
|
Get.toNamed('/group');
|
||||||
break;
|
break;
|
||||||
case 'friend':
|
case 'friend':
|
||||||
print('点击了添加朋友');
|
logger.w('点击了添加朋友');
|
||||||
break;
|
break;
|
||||||
case 'scan':
|
case 'scan':
|
||||||
print('点击了扫一扫');
|
logger.w('点击了扫一扫');
|
||||||
ScanUtil.openScanner(onResult: (code) {
|
ScanUtil.openScanner(onResult: (code) {
|
||||||
print('扫码结果:$code');
|
logger.w('扫码结果:$code');
|
||||||
Get.snackbar('扫码成功', code);
|
Get.snackbar('扫码成功', code);
|
||||||
// 在这里继续你的业务逻辑,比如跳转页面、请求接口等
|
// 执行业务逻辑
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -279,159 +280,174 @@ class ChatPageState extends State<ChatPage> {
|
|||||||
Text('群聊'),
|
Text('群聊'),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Column(
|
GestureDetector(
|
||||||
children: [
|
onTap: () {
|
||||||
SvgPicture.asset(
|
//互关
|
||||||
'assets/images/svg/kefu.svg',
|
Get.toNamed('/eachFlow');
|
||||||
height: 36.0,
|
},
|
||||||
width: 36.0,
|
child: Column(
|
||||||
),
|
children: [
|
||||||
Text('互关'),
|
SvgPicture.asset(
|
||||||
],
|
'assets/images/svg/kefu.svg',
|
||||||
|
height: 36.0,
|
||||||
|
width: 36.0,
|
||||||
|
),
|
||||||
|
Text('互关'),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
Column(
|
GestureDetector(
|
||||||
children: [
|
onTap: () {
|
||||||
SvgPicture.asset(
|
// 粉丝
|
||||||
'assets/images/svg/comment.svg',
|
Get.toNamed('/fans');
|
||||||
height: 36.0,
|
},
|
||||||
width: 36.0,
|
child: Column(
|
||||||
),
|
children: [
|
||||||
Text('粉丝'),
|
SvgPicture.asset(
|
||||||
],
|
'assets/images/svg/comment.svg',
|
||||||
|
height: 36.0,
|
||||||
|
width: 36.0,
|
||||||
|
),
|
||||||
|
Text('粉丝'),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
Column(
|
GestureDetector(
|
||||||
children: [
|
onTap: () {
|
||||||
SvgPicture.asset(
|
// 关注
|
||||||
'assets/images/svg/comment.svg',
|
Get.toNamed('/flow');
|
||||||
height: 36.0,
|
},
|
||||||
width: 36.0,
|
child: Column(
|
||||||
),
|
children: [
|
||||||
Text('关注'),
|
SvgPicture.asset(
|
||||||
],
|
'assets/images/svg/comment.svg',
|
||||||
|
height: 36.0,
|
||||||
|
width: 36.0,
|
||||||
|
),
|
||||||
|
Text('关注'),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: RefreshIndicator(
|
child: Obx(
|
||||||
backgroundColor: Colors.white,
|
() {
|
||||||
color: Color(0xFFFF5000),
|
final chatList = controller.chatList;
|
||||||
displacement: 10.0,
|
|
||||||
onRefresh: handleRefresh,
|
|
||||||
child: Obx(() {
|
|
||||||
final chatList = controller.chatList;
|
|
||||||
|
|
||||||
return ListView.builder(
|
return ListView.builder(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
physics: BouncingScrollPhysics(),
|
physics: BouncingScrollPhysics(),
|
||||||
itemCount: chatList.length,
|
itemCount: chatList.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final isNoFriend = chatList[index].conversation.conversationGroupList?.contains(ConversationType.noFriend.name) ?? false;
|
// logger.w(chatList[index].conversation.conversationGroupList);
|
||||||
final isAdmin = chatList[index].isCustomAdmin != null && chatList[index].isCustomAdmin != '0';
|
// logger.w(chatList[index].isCustomAdmin);
|
||||||
|
final isNoFriend = chatList[index].conversation.conversationGroupList?.contains(ConversationType.noFriend.name) ?? false;
|
||||||
|
final isAdmin =
|
||||||
|
chatList[index].isCustomAdmin != null && (chatList[index].isCustomAdmin?.isNotEmpty ?? false) && chatList[index].isCustomAdmin != '0';
|
||||||
|
|
||||||
// logger.e(chatList[index].isCustomAdmin);
|
// logger.e(chatList[index].isCustomAdmin);
|
||||||
return Ink(
|
return Ink(
|
||||||
// color: chatList[index]['topMost'] == null ? Colors.white : Colors.grey[100], //置顶颜色
|
// color: chatList[index]['topMost'] == null ? Colors.white : Colors.grey[100], //置顶颜色
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
splashColor: Colors.grey[200],
|
splashColor: Colors.grey[200],
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 10.0),
|
padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 10.0),
|
||||||
child: Row(
|
child: Row(
|
||||||
spacing: 10.0,
|
spacing: 10.0,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
// 头图
|
// 头图
|
||||||
ClipOval(
|
ClipOval(
|
||||||
child: NetworkOrAssetImage(
|
child: NetworkOrAssetImage(
|
||||||
imageUrl: chatList[index].faceUrl,
|
imageUrl: chatList[index].faceUrl,
|
||||||
width: 50,
|
width: 50,
|
||||||
height: 50,
|
height: 50,
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
|
||||||
// 消息
|
// 消息
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: <Widget>[
|
|
||||||
Text(
|
|
||||||
chatList[index].conversation.showName ?? '未知',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: (isAdmin || isNoFriend) ? 20 : 16,
|
|
||||||
fontWeight: (isAdmin || isNoFriend) ? FontWeight.bold : FontWeight.normal),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 2.0),
|
|
||||||
Text(
|
|
||||||
chatList[index].conversation.lastMessage != null
|
|
||||||
? parseMessageSummary(chatList[index].conversation.lastMessage!)
|
|
||||||
: '',
|
|
||||||
style: const TextStyle(color: Colors.grey, fontSize: 13.0),
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
// 右侧
|
|
||||||
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Visibility(
|
Text(
|
||||||
visible: !(isAdmin || isNoFriend),
|
chatList[index].conversation.showName ?? '未知',
|
||||||
child: Text(
|
style: TextStyle(
|
||||||
// 转成日期字符串显示
|
fontSize: (isAdmin || isNoFriend) ? 20 : 16,
|
||||||
// DateTime.fromMillisecondsSinceEpoch(
|
fontWeight: (isAdmin || isNoFriend) ? FontWeight.bold : FontWeight.normal),
|
||||||
// (chatList[index].conversation.lastMessage!.timestamp ?? 0) * 1000,
|
|
||||||
// ).toLocal().toString().substring(0, 16), // 简单截取年月日时分
|
|
||||||
Utils.formatTime(
|
|
||||||
chatList[index].conversation.lastMessage!.timestamp ?? DateTime.now().millisecondsSinceEpoch ~/ 1000),
|
|
||||||
style: const TextStyle(color: Colors.grey, fontSize: 12.0),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 5.0),
|
const SizedBox(height: 2.0),
|
||||||
// 数字角标
|
Text(
|
||||||
Visibility(
|
chatList[index].conversation.lastMessage != null ? parseMessageSummary(chatList[index].conversation.lastMessage!) : '',
|
||||||
visible: (chatList[index].conversation.unreadCount ?? 0) > 0,
|
style: const TextStyle(color: Colors.grey, fontSize: 13.0),
|
||||||
child: FStyle.badge(chatList[index].conversation.unreadCount ?? 0),
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Visibility(
|
),
|
||||||
visible: (isAdmin || isNoFriend),
|
// 右侧
|
||||||
child: const Icon(
|
|
||||||
Icons.arrow_forward_ios,
|
Column(
|
||||||
color: Colors.blueGrey,
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
size: 14.0,
|
children: <Widget>[
|
||||||
|
Visibility(
|
||||||
|
visible: !(isAdmin || isNoFriend),
|
||||||
|
child: Text(
|
||||||
|
// 转成日期字符串显示
|
||||||
|
// DateTime.fromMillisecondsSinceEpoch(
|
||||||
|
// (chatList[index].conversation.lastMessage!.timestamp ?? 0) * 1000,
|
||||||
|
// ).toLocal().toString().substring(0, 16), // 简单截取年月日时分
|
||||||
|
Utils.formatTime(chatList[index].conversation.lastMessage!.timestamp ?? DateTime.now().millisecondsSinceEpoch ~/ 1000),
|
||||||
|
style: const TextStyle(color: Colors.grey, fontSize: 12.0),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 5.0),
|
||||||
|
// 数字角标
|
||||||
|
Visibility(
|
||||||
|
visible: (chatList[index].conversation.unreadCount ?? 0) > 0,
|
||||||
|
child: FStyle.badge(chatList[index].conversation.unreadCount ?? 0),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Visibility(
|
||||||
|
visible: (isAdmin || isNoFriend),
|
||||||
|
child: const Icon(
|
||||||
|
Icons.arrow_forward_ios,
|
||||||
|
color: Colors.blueGrey,
|
||||||
|
size: 14.0,
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
onTap: () {
|
|
||||||
if (conversationTypeFromString(chatList[index].isCustomAdmin) != null) {
|
|
||||||
// 跳转对应的通知消息页
|
|
||||||
logger.e(chatList[index].isCustomAdmin);
|
|
||||||
Get.toNamed('/${chatList[index].isCustomAdmin}', arguments: chatList[index].conversation.lastMessage);
|
|
||||||
} else if (chatList[index].conversation.conversationGroupList!.contains(ConversationType.noFriend.name)) {
|
|
||||||
// 跳转陌生人消息页面
|
|
||||||
logger.e(chatList[index].conversation.conversationGroupList);
|
|
||||||
Get.toNamed('/noFriend');
|
|
||||||
} else {
|
|
||||||
// 会话id查询会话详情
|
|
||||||
Get.toNamed('/chat', arguments: chatList[index].conversation);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onTapDown: (TapDownDetails details) {
|
|
||||||
posDX = details.globalPosition.dx;
|
|
||||||
posDY = details.globalPosition.dy;
|
|
||||||
},
|
|
||||||
onLongPress: () {
|
|
||||||
showContextMenu(context, chatList[index]);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
onTap: () {
|
||||||
},
|
if (conversationTypeFromString(chatList[index].isCustomAdmin) != null) {
|
||||||
);
|
// 跳转对应的通知消息页
|
||||||
})),
|
logger.e(chatList[index].isCustomAdmin);
|
||||||
|
Get.toNamed('/${chatList[index].isCustomAdmin}', arguments: chatList[index].conversation.lastMessage);
|
||||||
|
} else if (chatList[index].conversation.conversationGroupList!.contains(ConversationType.noFriend.name)) {
|
||||||
|
// 跳转陌生人消息页面
|
||||||
|
logger.e(chatList[index].conversation.conversationGroupList);
|
||||||
|
Get.toNamed('/noFriend');
|
||||||
|
} else {
|
||||||
|
// 会话id查询会话详情
|
||||||
|
Get.toNamed('/chat', arguments: chatList[index].conversation);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onTapDown: (TapDownDetails details) {
|
||||||
|
posDX = details.globalPosition.dx;
|
||||||
|
posDY = details.globalPosition.dy;
|
||||||
|
},
|
||||||
|
onLongPress: () {
|
||||||
|
showContextMenu(context, chatList[index]);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -0,0 +1,304 @@
|
|||||||
|
/// 关注列表
|
||||||
|
library;
|
||||||
|
|
||||||
|
import 'package:easy_refresh/easy_refresh.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:loopin/IM/controller/chat_controller.dart';
|
||||||
|
import 'package:loopin/IM/im_service.dart';
|
||||||
|
import 'package:loopin/behavior/custom_scroll_behavior.dart';
|
||||||
|
import 'package:loopin/components/network_or_asset_image.dart';
|
||||||
|
import 'package:loopin/styles/index.dart';
|
||||||
|
import 'package:loopin/utils/index.dart';
|
||||||
|
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart';
|
||||||
|
import 'package:tencent_cloud_chat_sdk/models/v2_tim_follow_type_check_result.dart';
|
||||||
|
import 'package:tencent_cloud_chat_sdk/models/v2_tim_user_full_info.dart';
|
||||||
|
|
||||||
|
class UserWithFollow {
|
||||||
|
final V2TimUserFullInfo userInfo;
|
||||||
|
int followType;
|
||||||
|
|
||||||
|
UserWithFollow({
|
||||||
|
required this.userInfo,
|
||||||
|
this.followType = 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class Newfoucs extends StatefulWidget {
|
||||||
|
const Newfoucs({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<Newfoucs> createState() => NewfoucsState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class NewfoucsState extends State<Newfoucs> with SingleTickerProviderStateMixin {
|
||||||
|
bool isLoading = false; // 是否在加载中
|
||||||
|
bool hasMore = true; // 是否还有更多数据
|
||||||
|
String page = '';
|
||||||
|
List<UserWithFollow> dataList = <UserWithFollow>[];
|
||||||
|
|
||||||
|
///-------------------
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
getData();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页获取陌关注列表数据
|
||||||
|
Future<void> getData() async {
|
||||||
|
/// 0:不是好友也没有关注
|
||||||
|
/// 1:你关注了对方(单向)
|
||||||
|
/// 2:对方关注了你(单向)
|
||||||
|
/// 3:互相关注(双向好友)
|
||||||
|
final res = await ImService.instance.getMyFollowingList(
|
||||||
|
nextCursor: page,
|
||||||
|
);
|
||||||
|
if (res.success && res.data != null) {
|
||||||
|
logger.i('获取成功:${res.data!.nextCursor}');
|
||||||
|
final userInfoList = res.data!.userFullInfoList ?? [];
|
||||||
|
// 构建数据
|
||||||
|
List<UserWithFollow> wrappedList = userInfoList.map((u) {
|
||||||
|
return UserWithFollow(userInfo: u);
|
||||||
|
}).toList();
|
||||||
|
// 获取id
|
||||||
|
final userIDList = userInfoList.map((item) => item.userID).whereType<String>().toList();
|
||||||
|
if (userIDList.isNotEmpty) {
|
||||||
|
final shiRes = await ImService.instance.checkFollowType(userIDList: userIDList);
|
||||||
|
if (shiRes.success && shiRes.data != null) {
|
||||||
|
final shipResData = shiRes.data!;
|
||||||
|
for (final uwf in wrappedList) {
|
||||||
|
final userID = uwf.userInfo.userID;
|
||||||
|
if (userID != null) {
|
||||||
|
// 查找对应关系
|
||||||
|
final match = shipResData.firstWhere(
|
||||||
|
(e) => e.userID == userID,
|
||||||
|
orElse: () => V2TimFollowTypeCheckResult(userID: ''),
|
||||||
|
);
|
||||||
|
if (match.userID?.isNotEmpty ?? false) {
|
||||||
|
uwf.followType = match.followType ?? 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final isFinished = res.data!.nextCursor == null || res.data!.nextCursor!.isEmpty;
|
||||||
|
if (isFinished) {
|
||||||
|
setState(() {
|
||||||
|
hasMore = false;
|
||||||
|
});
|
||||||
|
// 加载没数据了
|
||||||
|
page = '';
|
||||||
|
} else {
|
||||||
|
page = res.data!.nextCursor ?? '';
|
||||||
|
}
|
||||||
|
logger.i('获取数据成功:$userInfoList');
|
||||||
|
setState(() {
|
||||||
|
dataList.addAll(wrappedList);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
logger.e('获取数据失败:${res.desc}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下拉刷新
|
||||||
|
Future<void> handleRefresh() async {
|
||||||
|
dataList.clear();
|
||||||
|
page = '';
|
||||||
|
getData();
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: Colors.grey[50],
|
||||||
|
appBar: AppBar(
|
||||||
|
centerTitle: true,
|
||||||
|
forceMaterialTransparency: true,
|
||||||
|
bottom: PreferredSize(
|
||||||
|
preferredSize: const Size.fromHeight(1.0),
|
||||||
|
child: Container(
|
||||||
|
color: Colors.grey[300],
|
||||||
|
height: 1.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
title: const Text(
|
||||||
|
'关注',
|
||||||
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
actions: [],
|
||||||
|
),
|
||||||
|
body: ScrollConfiguration(
|
||||||
|
behavior: CustomScrollBehavior().copyWith(scrollbars: false),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: EasyRefresh.builder(
|
||||||
|
callLoadOverOffset: 20, //触底距离
|
||||||
|
callRefreshOverOffset: 20, // 下拉距离
|
||||||
|
header: ClassicHeader(
|
||||||
|
dragText: '下拉刷新',
|
||||||
|
armedText: '释放刷新',
|
||||||
|
readyText: '加载中...',
|
||||||
|
processingText: '加载中...',
|
||||||
|
processedText: '加载完成',
|
||||||
|
failedText: '加载失败,请重试',
|
||||||
|
messageText: '最后更新于 %T',
|
||||||
|
),
|
||||||
|
footer: ClassicFooter(
|
||||||
|
dragText: '加载更多',
|
||||||
|
armedText: '释放加载',
|
||||||
|
readyText: '加载中...',
|
||||||
|
processingText: '加载中...',
|
||||||
|
processedText: hasMore ? '加载完成' : '没有更多了~',
|
||||||
|
failedText: '加载失败,请重试',
|
||||||
|
messageText: '最后更新于 %T',
|
||||||
|
),
|
||||||
|
onRefresh: () async {
|
||||||
|
await handleRefresh();
|
||||||
|
},
|
||||||
|
onLoad: () async {
|
||||||
|
if (hasMore) {
|
||||||
|
await getData();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
childBuilder: (context, physics) {
|
||||||
|
return ListView.builder(
|
||||||
|
physics: physics,
|
||||||
|
itemCount: dataList.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final item = dataList[index];
|
||||||
|
return Ink(
|
||||||
|
key: ValueKey(item.userInfo.userID),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 10.0),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: <Widget>[
|
||||||
|
// 左侧部分(头像 + 昵称 + 描述)
|
||||||
|
Expanded(
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
Get.toNamed(
|
||||||
|
'/vloger',
|
||||||
|
arguments: item.userInfo.userID,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
ClipOval(
|
||||||
|
child: NetworkOrAssetImage(
|
||||||
|
imageUrl: item.userInfo.faceUrl,
|
||||||
|
width: 50,
|
||||||
|
height: 50,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: <Widget>[
|
||||||
|
Text(
|
||||||
|
item.userInfo.nickName?.isNotEmpty == true ? item.userInfo.nickName! : '未知昵称',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.normal,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (item.userInfo.selfSignature?.isNotEmpty ?? false) ...[
|
||||||
|
const SizedBox(height: 2.0),
|
||||||
|
Text(
|
||||||
|
item.userInfo.selfSignature!,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.grey,
|
||||||
|
fontSize: 13.0,
|
||||||
|
),
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
SizedBox(width: 10),
|
||||||
|
|
||||||
|
// 右侧按钮
|
||||||
|
TextButton(
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
backgroundColor: item.followType == 3 ? Colors.grey : FStyle.primaryColor,
|
||||||
|
minimumSize: const Size(70, 32),
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () async {
|
||||||
|
final ctl = Get.find<ChatController>();
|
||||||
|
final checkRes = await ImService.instance.checkFollowType(userIDList: [item.userInfo.userID!]);
|
||||||
|
int realFollowType = 0;
|
||||||
|
if (checkRes.success && checkRes.data != null) {
|
||||||
|
realFollowType = checkRes.data!.first.followType ?? 0;
|
||||||
|
if ([1, 3].contains(realFollowType)) {
|
||||||
|
// 取关
|
||||||
|
final unRes = await ImService.instance.unfollowUser(userIDList: [item.userInfo.userID!]);
|
||||||
|
if (unRes.success) {
|
||||||
|
setState(() {
|
||||||
|
item.followType = 2;
|
||||||
|
});
|
||||||
|
ctl.mergeNoFriend(conversationID: 'c2c_${item.userInfo.userID!}');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 关注
|
||||||
|
final res = await ImService.instance.followUser(userIDList: [item.userInfo.userID!]);
|
||||||
|
if (res.success) {
|
||||||
|
setState(() {
|
||||||
|
item.followType = realFollowType == 0
|
||||||
|
? 1
|
||||||
|
: realFollowType == 2
|
||||||
|
? 3
|
||||||
|
: 0;
|
||||||
|
});
|
||||||
|
final chatRes = await ImService.instance.followUser(userIDList: [item.userInfo.userID!]);
|
||||||
|
if (chatRes.success) {
|
||||||
|
final res = await ImService.instance.getConversation(conversationID: 'c2c_${item.userInfo.userID}');
|
||||||
|
if (res.success) {
|
||||||
|
V2TimConversation conversation = res.data;
|
||||||
|
if (conversation.conversationGroupList?.isNotEmpty ?? false) {
|
||||||
|
await ImService.instance.deleteConversationsFromGroup(
|
||||||
|
groupName: conversation.conversationGroupList!.first!,
|
||||||
|
conversationIDList: [conversation.conversationID],
|
||||||
|
);
|
||||||
|
ctl.updateNoFriendMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
Utils.getTipText(item.followType),
|
||||||
|
style: const TextStyle(color: Colors.white, fontSize: 14),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -111,7 +111,7 @@ class NofriendState extends State<Nofriend> with SingleTickerProviderStateMixin
|
|||||||
),
|
),
|
||||||
title: Text(
|
title: Text(
|
||||||
'陌生人消息',
|
'陌生人消息',
|
||||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
actions: [],
|
actions: [],
|
||||||
),
|
),
|
||||||
@ -137,6 +137,8 @@ class NofriendState extends State<Nofriend> with SingleTickerProviderStateMixin
|
|||||||
final isNoFriend = conv.conversationGroupList?.contains(ConversationType.noFriend.name) ?? false;
|
final isNoFriend = conv.conversationGroupList?.contains(ConversationType.noFriend.name) ?? false;
|
||||||
|
|
||||||
return Ink(
|
return Ink(
|
||||||
|
key: ValueKey(conv.conversationID),
|
||||||
|
|
||||||
// color: conv['topMost'] == null ? Colors.white : Colors.grey[100], //置顶颜色
|
// color: conv['topMost'] == null ? Colors.white : Colors.grey[100], //置顶颜色
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
splashColor: Colors.grey[200],
|
splashColor: Colors.grey[200],
|
||||||
|
@ -20,11 +20,19 @@ class NotifyNoFriendController extends GetxController {
|
|||||||
if (index != -1) {
|
if (index != -1) {
|
||||||
convList[index] = conversation;
|
convList[index] = conversation;
|
||||||
convList.refresh();
|
convList.refresh();
|
||||||
// convList.value = List.from(convList)..[index] = conversation;
|
|
||||||
} else {
|
} else {
|
||||||
// 根据会话id查询会话检测是否存在
|
logger.e('当前会话数据未拉取到,执行插入逻辑');
|
||||||
logger.e('会话不存在,更新会话失败');
|
// 先insert进去 convlist做去重处理
|
||||||
// 如果存在,说明在后面的分页中,先insert进去 convlist做去重处理
|
convList.insert(0, conversation);
|
||||||
|
convList.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void del({required V2TimConversation conversation}) {
|
||||||
|
final index = convList.indexWhere((c) => c.conversationID == conversation.conversationID);
|
||||||
|
if (index != -1) {
|
||||||
|
convList.removeAt(index);
|
||||||
|
convList.refresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,7 +123,7 @@ class _GoodsState extends State<Goods> {
|
|||||||
"price": shopObj['price'],
|
"price": shopObj['price'],
|
||||||
"title": shopObj['describe'],
|
"title": shopObj['describe'],
|
||||||
"url": shopObj['pic'],
|
"url": shopObj['pic'],
|
||||||
"sell": Utils().graceNumber(int.parse(shopObj['sales'] ?? '0')),
|
"sell": Utils.graceNumber(int.parse(shopObj['sales'] ?? '0')),
|
||||||
});
|
});
|
||||||
final res = await IMMessage().createCustomMessage(
|
final res = await IMMessage().createCustomMessage(
|
||||||
data: makeJson,
|
data: makeJson,
|
||||||
@ -389,7 +389,7 @@ class _GoodsState extends State<Goods> {
|
|||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
// '已售${Utils().graceNumber(shopObj['sales'] ?? 0)}',
|
// '已售${Utils().graceNumber(shopObj['sales'] ?? 0)}',
|
||||||
'已售${Utils().graceNumber(int.tryParse(shopObj['sales']?.toString() ?? '0') ?? 0)}',
|
'已售${Utils.graceNumber(int.tryParse(shopObj['sales']?.toString() ?? '0') ?? 0)}',
|
||||||
style: TextStyle(color: Colors.white, fontSize: 12.0),
|
style: TextStyle(color: Colors.white, fontSize: 12.0),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
219
lib/pages/groupChat/index.dart
Normal file
219
lib/pages/groupChat/index.dart
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
import 'package:easy_refresh/easy_refresh.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class StartGroupChatPage extends StatefulWidget {
|
||||||
|
const StartGroupChatPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<StartGroupChatPage> createState() => _StartGroupChatPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _StartGroupChatPageState extends State<StartGroupChatPage> {
|
||||||
|
final TextEditingController _searchController = TextEditingController();
|
||||||
|
|
||||||
|
List<Map<String, dynamic>> dataList = [];
|
||||||
|
List<Map<String, dynamic>> filteredList = [];
|
||||||
|
Set<String> selectedIds = {}; // 已选中的用户 id
|
||||||
|
int page = 1;
|
||||||
|
bool hasMore = true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_loadData(reset: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_searchController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _loadData({bool reset = false}) async {
|
||||||
|
if (reset) {
|
||||||
|
page = 1;
|
||||||
|
hasMore = true;
|
||||||
|
dataList.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模拟网络请求
|
||||||
|
await Future.delayed(const Duration(seconds: 1));
|
||||||
|
|
||||||
|
List<Map<String, dynamic>> newItems = List.generate(
|
||||||
|
10,
|
||||||
|
(index) => {"nickName": "用户 ${(page - 1) * 10 + index + 1}", "id": "${page}_$index"},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (newItems.isEmpty) {
|
||||||
|
hasMore = false;
|
||||||
|
} else {
|
||||||
|
dataList.addAll(newItems);
|
||||||
|
page++;
|
||||||
|
}
|
||||||
|
|
||||||
|
_applySearch(_searchController.text);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _applySearch(String query) {
|
||||||
|
setState(() {
|
||||||
|
if (query.isEmpty) {
|
||||||
|
filteredList = List.from(dataList);
|
||||||
|
} else {
|
||||||
|
filteredList = dataList.where((item) => item["nickName"].toString().contains(query.trim())).toList();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _toggleSelection(String id) {
|
||||||
|
setState(() {
|
||||||
|
if (selectedIds.contains(id)) {
|
||||||
|
selectedIds.remove(id);
|
||||||
|
} else {
|
||||||
|
selectedIds.add(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildCard({
|
||||||
|
required IconData icon,
|
||||||
|
required String title,
|
||||||
|
required VoidCallback onTap,
|
||||||
|
}) {
|
||||||
|
return Card(
|
||||||
|
color: Colors.white,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||||
|
child: ListTile(
|
||||||
|
leading: Icon(icon, color: Colors.black),
|
||||||
|
title: Text(title, style: const TextStyle(fontSize: 16)),
|
||||||
|
trailing: const Icon(Icons.arrow_forward_ios, size: 16),
|
||||||
|
onTap: onTap,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
appBar: AppBar(
|
||||||
|
// backgroundColor: Colors.white,
|
||||||
|
centerTitle: true,
|
||||||
|
forceMaterialTransparency: true,
|
||||||
|
bottom: PreferredSize(
|
||||||
|
preferredSize: const Size.fromHeight(1.0),
|
||||||
|
child: Container(
|
||||||
|
color: Colors.grey[300],
|
||||||
|
height: 1.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
title: const Text(
|
||||||
|
"创建群聊",
|
||||||
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: Column(
|
||||||
|
children: [
|
||||||
|
// 上半部分 - 两个卡片
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
_buildCard(
|
||||||
|
icon: Icons.public,
|
||||||
|
title: "创建公开群",
|
||||||
|
onTap: () {
|
||||||
|
print("跳转到 创建公开群");
|
||||||
|
},
|
||||||
|
),
|
||||||
|
_buildCard(
|
||||||
|
icon: Icons.group,
|
||||||
|
title: "创建好友群",
|
||||||
|
onTap: () {
|
||||||
|
print("跳转到 创建好友群");
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const Divider(),
|
||||||
|
|
||||||
|
// 下半部分 - 搜索框 + 列表
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
// 搜索框
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: TextField(
|
||||||
|
controller: _searchController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: "搜索用户昵称",
|
||||||
|
prefixIcon: const Icon(Icons.search),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onChanged: _applySearch,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// 列表
|
||||||
|
Expanded(
|
||||||
|
child: EasyRefresh(
|
||||||
|
onRefresh: () async => _loadData(reset: true),
|
||||||
|
onLoad: () async {
|
||||||
|
if (hasMore) await _loadData();
|
||||||
|
},
|
||||||
|
child: ListView.builder(
|
||||||
|
itemCount: filteredList.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final item = filteredList[index];
|
||||||
|
final id = item["id"] as String;
|
||||||
|
final isSelected = selectedIds.contains(id);
|
||||||
|
|
||||||
|
return ListTile(
|
||||||
|
leading: CircleAvatar(
|
||||||
|
child: Text(item["nickName"].substring(0, 1)),
|
||||||
|
),
|
||||||
|
title: Text(item["nickName"]),
|
||||||
|
trailing: Checkbox(
|
||||||
|
value: isSelected,
|
||||||
|
onChanged: (_) => _toggleSelection(id),
|
||||||
|
),
|
||||||
|
onTap: () => _toggleSelection(id),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
// 底部按钮
|
||||||
|
bottomNavigationBar: SafeArea(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(12.0),
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: selectedIds.isEmpty
|
||||||
|
? null
|
||||||
|
: () {
|
||||||
|
print("选择了用户:$selectedIds");
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
minimumSize: const Size.fromHeight(50),
|
||||||
|
backgroundColor: selectedIds.isEmpty ? Colors.grey : Colors.blue,
|
||||||
|
),
|
||||||
|
child: const Text(
|
||||||
|
"发起聊天",
|
||||||
|
style: TextStyle(fontSize: 16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -88,7 +88,7 @@ class _IndexPageState extends State<IndexPage> with SingleTickerProviderStateMix
|
|||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'已售${Utils().graceNumber(int.parse(item['sales'] ?? '0'))}件',
|
'已售${Utils.graceNumber(int.parse(item['sales'] ?? '0'))}件',
|
||||||
style: TextStyle(color: Colors.grey, fontSize: 10.0),
|
style: TextStyle(color: Colors.grey, fontSize: 10.0),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -254,8 +254,9 @@ class _IndexPageState extends State<IndexPage> with SingleTickerProviderStateMix
|
|||||||
header: ClassicHeader(
|
header: ClassicHeader(
|
||||||
dragText: '下拉刷新',
|
dragText: '下拉刷新',
|
||||||
armedText: '释放刷新',
|
armedText: '释放刷新',
|
||||||
readyText: '刷新中...',
|
readyText: '加载中...',
|
||||||
processingText: '刷新完成',
|
processingText: '加载中...',
|
||||||
|
processedText: '加载完成',
|
||||||
messageText: '最后更新于 %T',
|
messageText: '最后更新于 %T',
|
||||||
),
|
),
|
||||||
child: CustomScrollView(
|
child: CustomScrollView(
|
||||||
|
304
lib/pages/my/fans.dart
Normal file
304
lib/pages/my/fans.dart
Normal file
@ -0,0 +1,304 @@
|
|||||||
|
/// 粉丝列表
|
||||||
|
library;
|
||||||
|
|
||||||
|
import 'package:easy_refresh/easy_refresh.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:loopin/IM/controller/chat_controller.dart';
|
||||||
|
import 'package:loopin/IM/im_service.dart';
|
||||||
|
import 'package:loopin/behavior/custom_scroll_behavior.dart';
|
||||||
|
import 'package:loopin/components/network_or_asset_image.dart';
|
||||||
|
import 'package:loopin/styles/index.dart';
|
||||||
|
import 'package:loopin/utils/index.dart';
|
||||||
|
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart';
|
||||||
|
import 'package:tencent_cloud_chat_sdk/models/v2_tim_follow_type_check_result.dart';
|
||||||
|
import 'package:tencent_cloud_chat_sdk/models/v2_tim_user_full_info.dart';
|
||||||
|
|
||||||
|
class UserWithFollow {
|
||||||
|
final V2TimUserFullInfo userInfo;
|
||||||
|
int followType;
|
||||||
|
|
||||||
|
UserWithFollow({
|
||||||
|
required this.userInfo,
|
||||||
|
this.followType = 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class Fans extends StatefulWidget {
|
||||||
|
const Fans({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<Fans> createState() => FansState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class FansState extends State<Fans> with SingleTickerProviderStateMixin {
|
||||||
|
bool isLoading = false; // 是否在加载中
|
||||||
|
bool hasMore = true; // 是否还有更多数据
|
||||||
|
String page = '';
|
||||||
|
List<UserWithFollow> dataList = <UserWithFollow>[];
|
||||||
|
|
||||||
|
///-------------------
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
getData();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页获取陌粉丝数据
|
||||||
|
Future<void> getData() async {
|
||||||
|
/// 0:不是好友也没有关注
|
||||||
|
/// 1:你关注了对方(单向)
|
||||||
|
/// 2:对方关注了你(单向)
|
||||||
|
/// 3:互相关注(双向好友)
|
||||||
|
final res = await ImService.instance.getMyFollowersList(
|
||||||
|
nextCursor: page,
|
||||||
|
);
|
||||||
|
if (res.success && res.data != null) {
|
||||||
|
logger.i('获取成功:${res.data!.nextCursor}');
|
||||||
|
final userInfoList = res.data!.userFullInfoList ?? [];
|
||||||
|
// 构建数据
|
||||||
|
List<UserWithFollow> wrappedList = userInfoList.map((u) {
|
||||||
|
return UserWithFollow(userInfo: u);
|
||||||
|
}).toList();
|
||||||
|
// 获取id
|
||||||
|
final userIDList = userInfoList.map((item) => item.userID).whereType<String>().toList();
|
||||||
|
if (userIDList.isNotEmpty) {
|
||||||
|
final shiRes = await ImService.instance.checkFollowType(userIDList: userIDList);
|
||||||
|
if (shiRes.success && shiRes.data != null) {
|
||||||
|
final shipResData = shiRes.data!;
|
||||||
|
for (final uwf in wrappedList) {
|
||||||
|
final userID = uwf.userInfo.userID;
|
||||||
|
if (userID != null) {
|
||||||
|
// 查找对应关系
|
||||||
|
final match = shipResData.firstWhere(
|
||||||
|
(e) => e.userID == userID,
|
||||||
|
orElse: () => V2TimFollowTypeCheckResult(userID: ''),
|
||||||
|
);
|
||||||
|
if (match.userID?.isNotEmpty ?? false) {
|
||||||
|
uwf.followType = match.followType ?? 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final isFinished = res.data!.nextCursor == null || res.data!.nextCursor!.isEmpty;
|
||||||
|
if (isFinished) {
|
||||||
|
setState(() {
|
||||||
|
hasMore = false;
|
||||||
|
});
|
||||||
|
// 加载没数据了
|
||||||
|
page = '';
|
||||||
|
} else {
|
||||||
|
page = res.data!.nextCursor ?? '';
|
||||||
|
}
|
||||||
|
logger.i('获取数据成功:$userInfoList');
|
||||||
|
setState(() {
|
||||||
|
dataList.addAll(wrappedList);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
logger.e('获取数据失败:${res.desc}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下拉刷新
|
||||||
|
Future<void> handleRefresh() async {
|
||||||
|
dataList.clear();
|
||||||
|
page = '';
|
||||||
|
getData();
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: Colors.grey[50],
|
||||||
|
appBar: AppBar(
|
||||||
|
centerTitle: true,
|
||||||
|
forceMaterialTransparency: true,
|
||||||
|
bottom: PreferredSize(
|
||||||
|
preferredSize: const Size.fromHeight(1.0),
|
||||||
|
child: Container(
|
||||||
|
color: Colors.grey[300],
|
||||||
|
height: 1.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
title: const Text(
|
||||||
|
'粉丝',
|
||||||
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
actions: [],
|
||||||
|
),
|
||||||
|
body: ScrollConfiguration(
|
||||||
|
behavior: CustomScrollBehavior().copyWith(scrollbars: false),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: EasyRefresh.builder(
|
||||||
|
callLoadOverOffset: 20, //触底距离
|
||||||
|
callRefreshOverOffset: 20, // 下拉距离
|
||||||
|
header: ClassicHeader(
|
||||||
|
dragText: '下拉刷新',
|
||||||
|
armedText: '释放刷新',
|
||||||
|
readyText: '加载中...',
|
||||||
|
processingText: '加载中...',
|
||||||
|
processedText: '加载完成',
|
||||||
|
failedText: '加载失败,请重试',
|
||||||
|
messageText: '最后更新于 %T',
|
||||||
|
),
|
||||||
|
footer: ClassicFooter(
|
||||||
|
dragText: '加载更多',
|
||||||
|
armedText: '释放加载',
|
||||||
|
readyText: '加载中...',
|
||||||
|
processingText: '加载中...',
|
||||||
|
processedText: hasMore ? '加载完成' : '没有更多了~',
|
||||||
|
failedText: '加载失败,请重试',
|
||||||
|
messageText: '最后更新于 %T',
|
||||||
|
),
|
||||||
|
onRefresh: () async {
|
||||||
|
await handleRefresh();
|
||||||
|
},
|
||||||
|
onLoad: () async {
|
||||||
|
if (hasMore) {
|
||||||
|
await getData();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
childBuilder: (context, physics) {
|
||||||
|
return ListView.builder(
|
||||||
|
physics: physics,
|
||||||
|
itemCount: dataList.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final item = dataList[index];
|
||||||
|
return Ink(
|
||||||
|
key: ValueKey(item.userInfo.userID),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 10.0),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: <Widget>[
|
||||||
|
// 左侧部分(头像 + 昵称 + 描述)
|
||||||
|
Expanded(
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
Get.toNamed(
|
||||||
|
'/vloger',
|
||||||
|
arguments: item.userInfo.userID,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
ClipOval(
|
||||||
|
child: NetworkOrAssetImage(
|
||||||
|
imageUrl: item.userInfo.faceUrl,
|
||||||
|
width: 50,
|
||||||
|
height: 50,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: <Widget>[
|
||||||
|
Text(
|
||||||
|
item.userInfo.nickName?.isNotEmpty == true ? item.userInfo.nickName! : '未知昵称',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.normal,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (item.userInfo.selfSignature?.isNotEmpty ?? false) ...[
|
||||||
|
const SizedBox(height: 2.0),
|
||||||
|
Text(
|
||||||
|
item.userInfo.selfSignature!,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.grey,
|
||||||
|
fontSize: 13.0,
|
||||||
|
),
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
SizedBox(width: 10),
|
||||||
|
|
||||||
|
// 右侧按钮
|
||||||
|
TextButton(
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
backgroundColor: item.followType != 3 ? FStyle.primaryColor : Colors.grey,
|
||||||
|
minimumSize: const Size(70, 32),
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () async {
|
||||||
|
final ctl = Get.find<ChatController>();
|
||||||
|
final checkRes = await ImService.instance.checkFollowType(userIDList: [item.userInfo.userID!]);
|
||||||
|
int realFollowType = 0;
|
||||||
|
if (checkRes.success && checkRes.data != null) {
|
||||||
|
realFollowType = checkRes.data!.first.followType ?? 0;
|
||||||
|
if ([1, 3].contains(realFollowType)) {
|
||||||
|
// 取关
|
||||||
|
final unRes = await ImService.instance.unfollowUser(userIDList: [item.userInfo.userID!]);
|
||||||
|
if (unRes.success) {
|
||||||
|
setState(() {
|
||||||
|
item.followType = 2;
|
||||||
|
});
|
||||||
|
ctl.mergeNoFriend(conversationID: 'c2c_${item.userInfo.userID!}');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 关注
|
||||||
|
final res = await ImService.instance.followUser(userIDList: [item.userInfo.userID!]);
|
||||||
|
if (res.success) {
|
||||||
|
setState(() {
|
||||||
|
item.followType = realFollowType == 0
|
||||||
|
? 1
|
||||||
|
: realFollowType == 2
|
||||||
|
? 3
|
||||||
|
: 0;
|
||||||
|
});
|
||||||
|
final chatRes = await ImService.instance.followUser(userIDList: [item.userInfo.userID!]);
|
||||||
|
if (chatRes.success) {
|
||||||
|
final res = await ImService.instance.getConversation(conversationID: 'c2c_${item.userInfo.userID}');
|
||||||
|
if (res.success) {
|
||||||
|
V2TimConversation conversation = res.data;
|
||||||
|
if (conversation.conversationGroupList?.isNotEmpty ?? false) {
|
||||||
|
await ImService.instance.deleteConversationsFromGroup(
|
||||||
|
groupName: conversation.conversationGroupList!.first!,
|
||||||
|
conversationIDList: [conversation.conversationID],
|
||||||
|
);
|
||||||
|
ctl.updateNoFriendMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
Utils.getTipText(item.followType),
|
||||||
|
style: const TextStyle(color: Colors.white, fontSize: 14),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
304
lib/pages/my/flowing.dart
Normal file
304
lib/pages/my/flowing.dart
Normal file
@ -0,0 +1,304 @@
|
|||||||
|
/// 关注列表
|
||||||
|
library;
|
||||||
|
|
||||||
|
import 'package:easy_refresh/easy_refresh.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:loopin/IM/controller/chat_controller.dart';
|
||||||
|
import 'package:loopin/IM/im_service.dart';
|
||||||
|
import 'package:loopin/behavior/custom_scroll_behavior.dart';
|
||||||
|
import 'package:loopin/components/network_or_asset_image.dart';
|
||||||
|
import 'package:loopin/styles/index.dart';
|
||||||
|
import 'package:loopin/utils/index.dart';
|
||||||
|
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart';
|
||||||
|
import 'package:tencent_cloud_chat_sdk/models/v2_tim_follow_type_check_result.dart';
|
||||||
|
import 'package:tencent_cloud_chat_sdk/models/v2_tim_user_full_info.dart';
|
||||||
|
|
||||||
|
class UserWithFollow {
|
||||||
|
final V2TimUserFullInfo userInfo;
|
||||||
|
int followType;
|
||||||
|
|
||||||
|
UserWithFollow({
|
||||||
|
required this.userInfo,
|
||||||
|
this.followType = 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class Flowing extends StatefulWidget {
|
||||||
|
const Flowing({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<Flowing> createState() => FlowingState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class FlowingState extends State<Flowing> with SingleTickerProviderStateMixin {
|
||||||
|
bool isLoading = false; // 是否在加载中
|
||||||
|
bool hasMore = true; // 是否还有更多数据
|
||||||
|
String page = '';
|
||||||
|
List<UserWithFollow> dataList = <UserWithFollow>[];
|
||||||
|
|
||||||
|
///-------------------
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
getData();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页获取陌关注列表数据
|
||||||
|
Future<void> getData() async {
|
||||||
|
/// 0:不是好友也没有关注
|
||||||
|
/// 1:你关注了对方(单向)
|
||||||
|
/// 2:对方关注了你(单向)
|
||||||
|
/// 3:互相关注(双向好友)
|
||||||
|
final res = await ImService.instance.getMyFollowingList(
|
||||||
|
nextCursor: page,
|
||||||
|
);
|
||||||
|
if (res.success && res.data != null) {
|
||||||
|
logger.i('获取成功:${res.data!.nextCursor}');
|
||||||
|
final userInfoList = res.data!.userFullInfoList ?? [];
|
||||||
|
// 构建数据
|
||||||
|
List<UserWithFollow> wrappedList = userInfoList.map((u) {
|
||||||
|
return UserWithFollow(userInfo: u);
|
||||||
|
}).toList();
|
||||||
|
// 获取id
|
||||||
|
final userIDList = userInfoList.map((item) => item.userID).whereType<String>().toList();
|
||||||
|
if (userIDList.isNotEmpty) {
|
||||||
|
final shiRes = await ImService.instance.checkFollowType(userIDList: userIDList);
|
||||||
|
if (shiRes.success && shiRes.data != null) {
|
||||||
|
final shipResData = shiRes.data!;
|
||||||
|
for (final uwf in wrappedList) {
|
||||||
|
final userID = uwf.userInfo.userID;
|
||||||
|
if (userID != null) {
|
||||||
|
// 查找对应关系
|
||||||
|
final match = shipResData.firstWhere(
|
||||||
|
(e) => e.userID == userID,
|
||||||
|
orElse: () => V2TimFollowTypeCheckResult(userID: ''),
|
||||||
|
);
|
||||||
|
if (match.userID?.isNotEmpty ?? false) {
|
||||||
|
uwf.followType = match.followType ?? 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final isFinished = res.data!.nextCursor == null || res.data!.nextCursor!.isEmpty;
|
||||||
|
if (isFinished) {
|
||||||
|
setState(() {
|
||||||
|
hasMore = false;
|
||||||
|
});
|
||||||
|
// 加载没数据了
|
||||||
|
page = '';
|
||||||
|
} else {
|
||||||
|
page = res.data!.nextCursor ?? '';
|
||||||
|
}
|
||||||
|
logger.i('获取数据成功:$userInfoList');
|
||||||
|
setState(() {
|
||||||
|
dataList.addAll(wrappedList);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
logger.e('获取数据失败:${res.desc}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下拉刷新
|
||||||
|
Future<void> handleRefresh() async {
|
||||||
|
dataList.clear();
|
||||||
|
page = '';
|
||||||
|
getData();
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: Colors.grey[50],
|
||||||
|
appBar: AppBar(
|
||||||
|
centerTitle: true,
|
||||||
|
forceMaterialTransparency: true,
|
||||||
|
bottom: PreferredSize(
|
||||||
|
preferredSize: const Size.fromHeight(1.0),
|
||||||
|
child: Container(
|
||||||
|
color: Colors.grey[300],
|
||||||
|
height: 1.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
title: const Text(
|
||||||
|
'关注',
|
||||||
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
actions: [],
|
||||||
|
),
|
||||||
|
body: ScrollConfiguration(
|
||||||
|
behavior: CustomScrollBehavior().copyWith(scrollbars: false),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: EasyRefresh.builder(
|
||||||
|
callLoadOverOffset: 20, //触底距离
|
||||||
|
callRefreshOverOffset: 20, // 下拉距离
|
||||||
|
header: ClassicHeader(
|
||||||
|
dragText: '下拉刷新',
|
||||||
|
armedText: '释放刷新',
|
||||||
|
readyText: '加载中...',
|
||||||
|
processingText: '加载中...',
|
||||||
|
processedText: '加载完成',
|
||||||
|
failedText: '加载失败,请重试',
|
||||||
|
messageText: '最后更新于 %T',
|
||||||
|
),
|
||||||
|
footer: ClassicFooter(
|
||||||
|
dragText: '加载更多',
|
||||||
|
armedText: '释放加载',
|
||||||
|
readyText: '加载中...',
|
||||||
|
processingText: '加载中...',
|
||||||
|
processedText: hasMore ? '加载完成' : '没有更多了~',
|
||||||
|
failedText: '加载失败,请重试',
|
||||||
|
messageText: '最后更新于 %T',
|
||||||
|
),
|
||||||
|
onRefresh: () async {
|
||||||
|
await handleRefresh();
|
||||||
|
},
|
||||||
|
onLoad: () async {
|
||||||
|
if (hasMore) {
|
||||||
|
await getData();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
childBuilder: (context, physics) {
|
||||||
|
return ListView.builder(
|
||||||
|
physics: physics,
|
||||||
|
itemCount: dataList.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final item = dataList[index];
|
||||||
|
return Ink(
|
||||||
|
key: ValueKey(item.userInfo.userID),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 10.0),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: <Widget>[
|
||||||
|
// 左侧部分(头像 + 昵称 + 描述)
|
||||||
|
Expanded(
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
Get.toNamed(
|
||||||
|
'/vloger',
|
||||||
|
arguments: item.userInfo.userID,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
ClipOval(
|
||||||
|
child: NetworkOrAssetImage(
|
||||||
|
imageUrl: item.userInfo.faceUrl,
|
||||||
|
width: 50,
|
||||||
|
height: 50,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: <Widget>[
|
||||||
|
Text(
|
||||||
|
item.userInfo.nickName?.isNotEmpty == true ? item.userInfo.nickName! : '未知昵称',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.normal,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (item.userInfo.selfSignature?.isNotEmpty ?? false) ...[
|
||||||
|
const SizedBox(height: 2.0),
|
||||||
|
Text(
|
||||||
|
item.userInfo.selfSignature!,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.grey,
|
||||||
|
fontSize: 13.0,
|
||||||
|
),
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
SizedBox(width: 10),
|
||||||
|
|
||||||
|
// 右侧按钮
|
||||||
|
TextButton(
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
backgroundColor: item.followType == 3 ? Colors.grey : FStyle.primaryColor,
|
||||||
|
minimumSize: const Size(70, 32),
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () async {
|
||||||
|
final ctl = Get.find<ChatController>();
|
||||||
|
final checkRes = await ImService.instance.checkFollowType(userIDList: [item.userInfo.userID!]);
|
||||||
|
int realFollowType = 0;
|
||||||
|
if (checkRes.success && checkRes.data != null) {
|
||||||
|
realFollowType = checkRes.data!.first.followType ?? 0;
|
||||||
|
if ([1, 3].contains(realFollowType)) {
|
||||||
|
// 取关
|
||||||
|
final unRes = await ImService.instance.unfollowUser(userIDList: [item.userInfo.userID!]);
|
||||||
|
if (unRes.success) {
|
||||||
|
setState(() {
|
||||||
|
item.followType = 2;
|
||||||
|
});
|
||||||
|
ctl.mergeNoFriend(conversationID: 'c2c_${item.userInfo.userID!}');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 关注
|
||||||
|
final res = await ImService.instance.followUser(userIDList: [item.userInfo.userID!]);
|
||||||
|
if (res.success) {
|
||||||
|
setState(() {
|
||||||
|
item.followType = realFollowType == 0
|
||||||
|
? 1
|
||||||
|
: realFollowType == 2
|
||||||
|
? 3
|
||||||
|
: 0;
|
||||||
|
});
|
||||||
|
final chatRes = await ImService.instance.followUser(userIDList: [item.userInfo.userID!]);
|
||||||
|
if (chatRes.success) {
|
||||||
|
final res = await ImService.instance.getConversation(conversationID: 'c2c_${item.userInfo.userID}');
|
||||||
|
if (res.success) {
|
||||||
|
V2TimConversation conversation = res.data;
|
||||||
|
if (conversation.conversationGroupList?.isNotEmpty ?? false) {
|
||||||
|
await ImService.instance.deleteConversationsFromGroup(
|
||||||
|
groupName: conversation.conversationGroupList!.first!,
|
||||||
|
conversationIDList: [conversation.conversationID],
|
||||||
|
);
|
||||||
|
ctl.updateNoFriendMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
Utils.getTipText(item.followType),
|
||||||
|
style: const TextStyle(color: Colors.white, fontSize: 14),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -369,7 +369,10 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
|
|||||||
padding: const EdgeInsets.all(10.0),
|
padding: const EdgeInsets.all(10.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
|
const SizedBox(height: 10),
|
||||||
Obx(() => _buildStatsCard()),
|
Obx(() => _buildStatsCard()),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Obx(() => _buildInfoDesc(context)),
|
||||||
const SizedBox(height: 10.0),
|
const SizedBox(height: 10.0),
|
||||||
_buildOrderCard(context),
|
_buildOrderCard(context),
|
||||||
const SizedBox(height: 10.0),
|
const SizedBox(height: 10.0),
|
||||||
@ -463,6 +466,37 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildInfoDesc(BuildContext context) {
|
||||||
|
final tx = imUserInfoController?.signature;
|
||||||
|
if (tx == null || tx.isEmpty) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(15.0),
|
||||||
|
boxShadow: [BoxShadow(color: Colors.black.withAlpha(10), offset: const Offset(0.0, 1.0), blurRadius: 2.0, spreadRadius: 0.0)],
|
||||||
|
),
|
||||||
|
clipBehavior: Clip.antiAlias,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
width: double.infinity,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'${imUserInfoController!.signature}',
|
||||||
|
style: const TextStyle(fontSize: 16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildGridTab(int tabIndex) {
|
Widget _buildGridTab(int tabIndex) {
|
||||||
final listToShow = tabIndex == 0 ? items : favoriteItems;
|
final listToShow = tabIndex == 0 ? items : favoriteItems;
|
||||||
PageParams params = tabIndex == 0 ? itemsParams : favoriteParams;
|
PageParams params = tabIndex == 0 ? itemsParams : favoriteParams;
|
||||||
@ -724,104 +758,6 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Widget _buildFlexibleSpace() {
|
|
||||||
// return LayoutBuilder(
|
|
||||||
// builder: (BuildContext context, BoxConstraints constraints) {
|
|
||||||
// final double maxHeight = 180.0;
|
|
||||||
// final double minHeight = 120.0;
|
|
||||||
// final double currentHeight = constraints.maxHeight;
|
|
||||||
// double ratio = (currentHeight - minHeight) / (maxHeight - minHeight);
|
|
||||||
// ratio = ratio.clamp(0.0, 1.0);
|
|
||||||
// final faceUrl = imUserInfoController?.faceUrl.value ?? '';
|
|
||||||
|
|
||||||
// return Obx(
|
|
||||||
// () => Stack(
|
|
||||||
// fit: StackFit.expand,
|
|
||||||
// children: [
|
|
||||||
// Positioned.fill(
|
|
||||||
// child: Opacity(
|
|
||||||
// opacity: 1.0,
|
|
||||||
// child: NetworkOrAssetImage(imageUrl: imUserInfoController?.customInfo['coverBg'] ?? '', placeholderAsset: 'assets/images/bk.jpg'),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// Positioned(
|
|
||||||
// left: 15.0,
|
|
||||||
// bottom: 0,
|
|
||||||
// right: 15.0,
|
|
||||||
// child: Container(
|
|
||||||
// padding: const EdgeInsets.symmetric(vertical: 10.0),
|
|
||||||
// child: Row(
|
|
||||||
// crossAxisAlignment: CrossAxisAlignment.end,
|
|
||||||
// children: <Widget>[
|
|
||||||
// ClipOval(
|
|
||||||
// child: ClipRRect(
|
|
||||||
// borderRadius: BorderRadius.circular(30),
|
|
||||||
// child: NetworkOrAssetImage(
|
|
||||||
// imageUrl: faceUrl,
|
|
||||||
// width: 80,
|
|
||||||
// height: 80,
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// const SizedBox(width: 15.0),
|
|
||||||
// Expanded(
|
|
||||||
// child: Column(
|
|
||||||
// crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
// children: [
|
|
||||||
// Row(
|
|
||||||
// children: [
|
|
||||||
// Container(
|
|
||||||
// padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 5.0),
|
|
||||||
// decoration: BoxDecoration(color: Colors.black.withAlpha((0.3 * 255).round()), borderRadius: BorderRadius.circular(20.0)),
|
|
||||||
// child: Obx(
|
|
||||||
// () => Text(
|
|
||||||
// imUserInfoController?.nickname.value.isNotEmpty ?? false == true
|
|
||||||
// ? imUserInfoController?.nickname.value ?? imUserInfoController!.nickname.value
|
|
||||||
// : '昵称',
|
|
||||||
// style: TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold, fontFamily: 'Arial', color: Colors.white),
|
|
||||||
// ),
|
|
||||||
// )),
|
|
||||||
// const SizedBox(width: 8.0),
|
|
||||||
// InkWell(
|
|
||||||
// onTap: () {
|
|
||||||
// qrcodeAlertDialog(context);
|
|
||||||
// },
|
|
||||||
// child: Container(
|
|
||||||
// padding: const EdgeInsets.symmetric(horizontal: 5.0, vertical: 5.0),
|
|
||||||
// decoration: BoxDecoration(color: Colors.black.withAlpha((0.3 * 255).round()), borderRadius: BorderRadius.circular(20.0)),
|
|
||||||
// child: const Icon(Icons.qr_code_outlined, size: 18.0, color: Colors.white),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ],
|
|
||||||
// ),
|
|
||||||
// const SizedBox(height: 8.0),
|
|
||||||
// Container(
|
|
||||||
// padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 5.0),
|
|
||||||
// decoration: BoxDecoration(color: Colors.black.withAlpha((0.3 * 255).round()), borderRadius: BorderRadius.circular(20.0)),
|
|
||||||
// child: InkWell(
|
|
||||||
// onTap: () {
|
|
||||||
// logger.i('点击id');
|
|
||||||
// Clipboard.setData(ClipboardData(text: imUserInfoController?.userID.value ?? ''));
|
|
||||||
// MyDialog.toast('ID已复制',
|
|
||||||
// icon: const Icon(Icons.check_circle), style: ToastStyle(backgroundColor: Colors.green.withAlpha(200)));
|
|
||||||
// },
|
|
||||||
// child: Text('ID:${imUserInfoController?.userID.value ?? ''}', style: TextStyle(fontSize: 12.0, color: Colors.white)),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ],
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ],
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ],
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
// },
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
Widget _buildStatsCard() {
|
Widget _buildStatsCard() {
|
||||||
return Container(
|
return Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
@ -836,21 +772,46 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
|
|||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
children: [
|
children: [
|
||||||
Column(children: [Text('9999', style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold)), SizedBox(height: 3.0), Text('获赞')]),
|
Column(children: [Text('9999', style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold)), SizedBox(height: 3.0), Text('获赞')]),
|
||||||
Column(children: [
|
GestureDetector(
|
||||||
Text('${followInfo.value?.mutualFollowersCount ?? 0}', style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold)),
|
onTap: () async {
|
||||||
SizedBox(height: 3.0),
|
// 互相关注
|
||||||
Text('互关')
|
await Get.toNamed('/eachFlow');
|
||||||
]),
|
refreshData();
|
||||||
Column(children: [
|
},
|
||||||
Text('${followInfo.value?.followingCount ?? 0}', style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold)),
|
child: Column(children: [
|
||||||
SizedBox(height: 3.0),
|
Text('${followInfo.value?.mutualFollowersCount ?? 0}', style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold)),
|
||||||
Text('关注')
|
SizedBox(height: 3.0),
|
||||||
]),
|
Text('互关')
|
||||||
Column(children: [
|
]),
|
||||||
Text('${followInfo.value?.followersCount ?? 0}', style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold)),
|
),
|
||||||
SizedBox(height: 3.0),
|
GestureDetector(
|
||||||
Text('粉丝')
|
onTap: () async {
|
||||||
]),
|
// 关注
|
||||||
|
await Get.toNamed('/flow');
|
||||||
|
refreshData();
|
||||||
|
},
|
||||||
|
child: Column(children: [
|
||||||
|
Text('${followInfo.value?.followingCount ?? 0}', style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold)),
|
||||||
|
SizedBox(height: 3.0),
|
||||||
|
Text('关注')
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () async {
|
||||||
|
await Get.toNamed('/fans');
|
||||||
|
refreshData();
|
||||||
|
},
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'${followInfo.value?.followersCount ?? 0}',
|
||||||
|
style: const TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 3.0),
|
||||||
|
const Text('粉丝'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
],
|
],
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
|
304
lib/pages/my/mutual_followers.dart
Normal file
304
lib/pages/my/mutual_followers.dart
Normal file
@ -0,0 +1,304 @@
|
|||||||
|
/// 互关列表
|
||||||
|
library;
|
||||||
|
|
||||||
|
import 'package:easy_refresh/easy_refresh.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:loopin/IM/controller/chat_controller.dart';
|
||||||
|
import 'package:loopin/IM/im_service.dart';
|
||||||
|
import 'package:loopin/behavior/custom_scroll_behavior.dart';
|
||||||
|
import 'package:loopin/components/network_or_asset_image.dart';
|
||||||
|
import 'package:loopin/styles/index.dart';
|
||||||
|
import 'package:loopin/utils/index.dart';
|
||||||
|
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart';
|
||||||
|
import 'package:tencent_cloud_chat_sdk/models/v2_tim_follow_type_check_result.dart';
|
||||||
|
import 'package:tencent_cloud_chat_sdk/models/v2_tim_user_full_info.dart';
|
||||||
|
|
||||||
|
class UserWithFollow {
|
||||||
|
final V2TimUserFullInfo userInfo;
|
||||||
|
int followType;
|
||||||
|
|
||||||
|
UserWithFollow({
|
||||||
|
required this.userInfo,
|
||||||
|
this.followType = 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class MutualFollowers extends StatefulWidget {
|
||||||
|
const MutualFollowers({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MutualFollowers> createState() => MutualFollowersState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class MutualFollowersState extends State<MutualFollowers> with SingleTickerProviderStateMixin {
|
||||||
|
bool isLoading = false; // 是否在加载中
|
||||||
|
bool hasMore = true; // 是否还有更多数据
|
||||||
|
String page = '';
|
||||||
|
List<UserWithFollow> dataList = <UserWithFollow>[];
|
||||||
|
|
||||||
|
///-------------------
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
getData();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页获取陌关注列表数据
|
||||||
|
Future<void> getData() async {
|
||||||
|
/// 0:不是好友也没有关注
|
||||||
|
/// 1:你关注了对方(单向)
|
||||||
|
/// 2:对方关注了你(单向)
|
||||||
|
/// 3:互相关注(双向好友)
|
||||||
|
final res = await ImService.instance.getMutualFollowersList(
|
||||||
|
nextCursor: page,
|
||||||
|
);
|
||||||
|
if (res.success && res.data != null) {
|
||||||
|
logger.i('获取成功:${res.data!.nextCursor}');
|
||||||
|
final userInfoList = res.data!.userFullInfoList ?? [];
|
||||||
|
// 构建数据
|
||||||
|
List<UserWithFollow> wrappedList = userInfoList.map((u) {
|
||||||
|
return UserWithFollow(userInfo: u);
|
||||||
|
}).toList();
|
||||||
|
// 获取id
|
||||||
|
final userIDList = userInfoList.map((item) => item.userID).whereType<String>().toList();
|
||||||
|
if (userIDList.isNotEmpty) {
|
||||||
|
final shiRes = await ImService.instance.checkFollowType(userIDList: userIDList);
|
||||||
|
if (shiRes.success && shiRes.data != null) {
|
||||||
|
final shipResData = shiRes.data!;
|
||||||
|
for (final uwf in wrappedList) {
|
||||||
|
final userID = uwf.userInfo.userID;
|
||||||
|
if (userID != null) {
|
||||||
|
// 查找对应关系
|
||||||
|
final match = shipResData.firstWhere(
|
||||||
|
(e) => e.userID == userID,
|
||||||
|
orElse: () => V2TimFollowTypeCheckResult(userID: ''),
|
||||||
|
);
|
||||||
|
if (match.userID?.isNotEmpty ?? false) {
|
||||||
|
uwf.followType = match.followType ?? 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final isFinished = res.data!.nextCursor == null || res.data!.nextCursor!.isEmpty;
|
||||||
|
if (isFinished) {
|
||||||
|
setState(() {
|
||||||
|
hasMore = false;
|
||||||
|
});
|
||||||
|
// 加载没数据了
|
||||||
|
page = '';
|
||||||
|
} else {
|
||||||
|
page = res.data!.nextCursor ?? '';
|
||||||
|
}
|
||||||
|
logger.i('获取数据成功:$userInfoList');
|
||||||
|
setState(() {
|
||||||
|
dataList.addAll(wrappedList);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
logger.e('获取数据失败:${res.desc}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下拉刷新
|
||||||
|
Future<void> handleRefresh() async {
|
||||||
|
dataList.clear();
|
||||||
|
page = '';
|
||||||
|
getData();
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: Colors.grey[50],
|
||||||
|
appBar: AppBar(
|
||||||
|
centerTitle: true,
|
||||||
|
forceMaterialTransparency: true,
|
||||||
|
bottom: PreferredSize(
|
||||||
|
preferredSize: const Size.fromHeight(1.0),
|
||||||
|
child: Container(
|
||||||
|
color: Colors.grey[300],
|
||||||
|
height: 1.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
title: const Text(
|
||||||
|
'互关',
|
||||||
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
actions: [],
|
||||||
|
),
|
||||||
|
body: ScrollConfiguration(
|
||||||
|
behavior: CustomScrollBehavior().copyWith(scrollbars: false),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: EasyRefresh.builder(
|
||||||
|
callLoadOverOffset: 20, //触底距离
|
||||||
|
callRefreshOverOffset: 20, // 下拉距离
|
||||||
|
header: ClassicHeader(
|
||||||
|
dragText: '下拉刷新',
|
||||||
|
armedText: '释放刷新',
|
||||||
|
readyText: '加载中...',
|
||||||
|
processingText: '加载中...',
|
||||||
|
processedText: '加载完成',
|
||||||
|
failedText: '加载失败,请重试',
|
||||||
|
messageText: '最后更新于 %T',
|
||||||
|
),
|
||||||
|
footer: ClassicFooter(
|
||||||
|
dragText: '加载更多',
|
||||||
|
armedText: '释放加载',
|
||||||
|
readyText: '加载中...',
|
||||||
|
processingText: '加载中...',
|
||||||
|
processedText: hasMore ? '加载完成' : '没有更多了~',
|
||||||
|
failedText: '加载失败,请重试',
|
||||||
|
messageText: '最后更新于 %T',
|
||||||
|
),
|
||||||
|
onRefresh: () async {
|
||||||
|
await handleRefresh();
|
||||||
|
},
|
||||||
|
onLoad: () async {
|
||||||
|
if (hasMore) {
|
||||||
|
await getData();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
childBuilder: (context, physics) {
|
||||||
|
return ListView.builder(
|
||||||
|
physics: physics,
|
||||||
|
itemCount: dataList.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final item = dataList[index];
|
||||||
|
return Ink(
|
||||||
|
key: ValueKey(item.userInfo.userID),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 10.0),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: <Widget>[
|
||||||
|
// 左侧部分(头像 + 昵称 + 描述)
|
||||||
|
Expanded(
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
Get.toNamed(
|
||||||
|
'/vloger',
|
||||||
|
arguments: item.userInfo.userID,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
ClipOval(
|
||||||
|
child: NetworkOrAssetImage(
|
||||||
|
imageUrl: item.userInfo.faceUrl,
|
||||||
|
width: 50,
|
||||||
|
height: 50,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: <Widget>[
|
||||||
|
Text(
|
||||||
|
item.userInfo.nickName?.isNotEmpty == true ? item.userInfo.nickName! : '未知昵称',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.normal,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (item.userInfo.selfSignature?.isNotEmpty ?? false) ...[
|
||||||
|
const SizedBox(height: 2.0),
|
||||||
|
Text(
|
||||||
|
item.userInfo.selfSignature!,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.grey,
|
||||||
|
fontSize: 13.0,
|
||||||
|
),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
maxLines: 2,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
SizedBox(width: 10),
|
||||||
|
|
||||||
|
// 右侧按钮
|
||||||
|
TextButton(
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
backgroundColor: item.followType == 3 ? Colors.grey : FStyle.primaryColor,
|
||||||
|
minimumSize: const Size(70, 32),
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () async {
|
||||||
|
final ctl = Get.find<ChatController>();
|
||||||
|
final checkRes = await ImService.instance.checkFollowType(userIDList: [item.userInfo.userID!]);
|
||||||
|
int realFollowType = 0;
|
||||||
|
if (checkRes.success && checkRes.data != null) {
|
||||||
|
realFollowType = checkRes.data!.first.followType ?? 0;
|
||||||
|
if ([1, 3].contains(realFollowType)) {
|
||||||
|
// 取关
|
||||||
|
final unRes = await ImService.instance.unfollowUser(userIDList: [item.userInfo.userID!]);
|
||||||
|
if (unRes.success) {
|
||||||
|
setState(() {
|
||||||
|
item.followType = 2;
|
||||||
|
});
|
||||||
|
ctl.mergeNoFriend(conversationID: 'c2c_${item.userInfo.userID!}');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 关注
|
||||||
|
final res = await ImService.instance.followUser(userIDList: [item.userInfo.userID!]);
|
||||||
|
if (res.success) {
|
||||||
|
setState(() {
|
||||||
|
item.followType = realFollowType == 0
|
||||||
|
? 1
|
||||||
|
: realFollowType == 2
|
||||||
|
? 3
|
||||||
|
: 0;
|
||||||
|
});
|
||||||
|
final chatRes = await ImService.instance.followUser(userIDList: [item.userInfo.userID!]);
|
||||||
|
if (chatRes.success) {
|
||||||
|
final res = await ImService.instance.getConversation(conversationID: 'c2c_${item.userInfo.userID}');
|
||||||
|
if (res.success) {
|
||||||
|
V2TimConversation conversation = res.data;
|
||||||
|
if (conversation.conversationGroupList?.isNotEmpty ?? false) {
|
||||||
|
await ImService.instance.deleteConversationsFromGroup(
|
||||||
|
groupName: conversation.conversationGroupList!.first!,
|
||||||
|
conversationIDList: [conversation.conversationID],
|
||||||
|
);
|
||||||
|
ctl.updateNoFriendMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
Utils.getTipText(item.followType),
|
||||||
|
style: const TextStyle(color: Colors.white, fontSize: 14),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:loopin/IM/controller/im_user_info_controller.dart';
|
import 'package:loopin/IM/controller/im_user_info_controller.dart';
|
||||||
import 'package:loopin/api/common_api.dart';
|
import 'package:loopin/api/common_api.dart';
|
||||||
|
import 'package:loopin/components/network_or_asset_image.dart';
|
||||||
import 'package:loopin/service/http.dart';
|
import 'package:loopin/service/http.dart';
|
||||||
import 'package:loopin/styles/index.dart';
|
import 'package:loopin/styles/index.dart';
|
||||||
import 'package:loopin/utils/index.dart';
|
import 'package:loopin/utils/index.dart';
|
||||||
@ -321,9 +322,13 @@ class _UserInfoState extends State<UserInfo> {
|
|||||||
final imageUrl = userInfoController.customInfo['coverBg'];
|
final imageUrl = userInfoController.customInfo['coverBg'];
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () => pickCover(context),
|
onTap: () => pickCover(context),
|
||||||
child: Image(
|
// child: Image(
|
||||||
image: (imageUrl != null && imageUrl.isNotEmpty) ? NetworkImage(imageUrl) : const AssetImage('assets/images/pic2.jpg') as ImageProvider,
|
// image: (imageUrl != null && imageUrl.isNotEmpty) ? NetworkImage(imageUrl) : const AssetImage('assets/images/pic2.jpg') as ImageProvider,
|
||||||
fit: BoxFit.cover,
|
// fit: BoxFit.cover,
|
||||||
|
// ),
|
||||||
|
child: NetworkOrAssetImage(
|
||||||
|
imageUrl: imageUrl,
|
||||||
|
placeholderAsset: 'assets/images/bk.jpg',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
@ -423,7 +428,7 @@ class _UserInfoState extends State<UserInfo> {
|
|||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
String wxText = val['unionId'] == null || val['unionId'] == '' ? '未授权' : '已授权';
|
String wxText = val['openId'] == null || val['openId'] == '' ? '未授权' : '已授权';
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
Spacer(),
|
Spacer(),
|
||||||
@ -473,9 +478,13 @@ class _UserInfoState extends State<UserInfo> {
|
|||||||
child: CircleAvatar(
|
child: CircleAvatar(
|
||||||
radius: 60,
|
radius: 60,
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: Colors.white,
|
||||||
child: CircleAvatar(
|
child: ClipOval(
|
||||||
radius: 57,
|
child: NetworkOrAssetImage(
|
||||||
backgroundImage: avatar.isNotEmpty ? NetworkImage(avatar) : const AssetImage('assets/images/avatar/img11.jpg') as ImageProvider,
|
imageUrl: avatar,
|
||||||
|
width: double.infinity,
|
||||||
|
height: double.infinity,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:get/get_rx/src/rx_typedefs/rx_typedefs.dart';
|
import 'package:get/get_rx/src/rx_typedefs/rx_typedefs.dart';
|
||||||
|
import 'package:loopin/IM/controller/chat_controller.dart';
|
||||||
import 'package:loopin/IM/im_service.dart';
|
import 'package:loopin/IM/im_service.dart';
|
||||||
import 'package:loopin/components/custom_sticky_header.dart';
|
import 'package:loopin/components/custom_sticky_header.dart';
|
||||||
import 'package:loopin/components/network_or_asset_image.dart';
|
import 'package:loopin/components/network_or_asset_image.dart';
|
||||||
@ -202,105 +203,105 @@ class MyPageState extends State<Vloger> with SingleTickerProviderStateMixin {
|
|||||||
print('User navigated back');
|
print('User navigated back');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child:Scaffold(
|
child: Scaffold(
|
||||||
backgroundColor: const Color(0xFFFAF6F9),
|
backgroundColor: const Color(0xFFFAF6F9),
|
||||||
body: Obx(() {
|
body: Obx(() {
|
||||||
return NestedScrollViewPlus(
|
return NestedScrollViewPlus(
|
||||||
controller: scrollController,
|
controller: scrollController,
|
||||||
physics: shouldFixHeader.value ? const OnlyDownScrollPhysics(parent: AlwaysScrollableScrollPhysics()) : const AlwaysScrollableScrollPhysics(),
|
physics: shouldFixHeader.value ? const OnlyDownScrollPhysics(parent: AlwaysScrollableScrollPhysics()) : const AlwaysScrollableScrollPhysics(),
|
||||||
overscrollBehavior: OverscrollBehavior.outer,
|
overscrollBehavior: OverscrollBehavior.outer,
|
||||||
headerSliverBuilder: (context, innerBoxIsScrolled) {
|
headerSliverBuilder: (context, innerBoxIsScrolled) {
|
||||||
return [
|
return [
|
||||||
SliverAppBar(
|
SliverAppBar(
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
surfaceTintColor: Colors.transparent,
|
surfaceTintColor: Colors.transparent,
|
||||||
expandedHeight: 180.0,
|
expandedHeight: 180.0,
|
||||||
collapsedHeight: 120.0,
|
collapsedHeight: 120.0,
|
||||||
pinned: true,
|
pinned: true,
|
||||||
stretch: true,
|
stretch: true,
|
||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
icon: const Icon(Icons.arrow_back),
|
icon: const Icon(Icons.arrow_back),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Get.back(result: {
|
Get.back(result: {
|
||||||
'returnTo': '/',
|
'returnTo': '/',
|
||||||
'vlogerId': args['memberId'],
|
'vlogerId': args['memberId'],
|
||||||
'followStatus': (followed.value == 1 || followed.value == 3)?true:false,
|
'followStatus': (followed.value == 1 || followed.value == 3) ? true : false,
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
onStretchTrigger: () async {
|
||||||
|
logger.i('触发 stretch 拉伸');
|
||||||
|
// 加载刷新逻辑
|
||||||
},
|
},
|
||||||
|
flexibleSpace: Obx(() {
|
||||||
|
userInfo.value;
|
||||||
|
return _buildFlexibleSpace();
|
||||||
|
}),
|
||||||
),
|
),
|
||||||
onStretchTrigger: () async {
|
SliverToBoxAdapter(
|
||||||
logger.i('触发 stretch 拉伸');
|
child: Padding(
|
||||||
// 加载刷新逻辑
|
padding: const EdgeInsets.all(10.0),
|
||||||
},
|
child: Column(
|
||||||
flexibleSpace: Obx(() {
|
children: [
|
||||||
userInfo.value;
|
Obx(() => _buildStatsCard()),
|
||||||
return _buildFlexibleSpace();
|
const SizedBox(height: 10.0),
|
||||||
}),
|
Obx(() => _buildInfoDesc(context)),
|
||||||
),
|
const SizedBox(height: 10.0),
|
||||||
SliverToBoxAdapter(
|
Obx(() => _buildFoucsButton(context)),
|
||||||
child: Padding(
|
],
|
||||||
padding: const EdgeInsets.all(10.0),
|
),
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Obx(() => _buildStatsCard()),
|
|
||||||
const SizedBox(height: 10.0),
|
|
||||||
Obx(() => _buildInfoDesc(context)),
|
|
||||||
const SizedBox(height: 10.0),
|
|
||||||
Obx(() => _buildFoucsButton(context)),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
SliverPersistentHeader(
|
||||||
SliverPersistentHeader(
|
pinned: true,
|
||||||
pinned: true,
|
delegate: CustomStickyHeader(
|
||||||
delegate: CustomStickyHeader(
|
child: PreferredSize(
|
||||||
child: PreferredSize(
|
preferredSize: const Size.fromHeight(48.0),
|
||||||
preferredSize: const Size.fromHeight(48.0),
|
child: Container(
|
||||||
child: Container(
|
color: Colors.white,
|
||||||
color: Colors.white,
|
child: TabBar(
|
||||||
child: TabBar(
|
controller: tabController,
|
||||||
controller: tabController,
|
tabs: tabList.map((item) {
|
||||||
tabs: tabList.map((item) {
|
return Tab(
|
||||||
return Tab(
|
child: Badge.count(
|
||||||
child: Badge.count(
|
backgroundColor: Colors.red,
|
||||||
backgroundColor: Colors.red,
|
count: item['badge'] ?? 0,
|
||||||
count: item['badge'] ?? 0,
|
isLabelVisible: item['badge'] != null,
|
||||||
isLabelVisible: item['badge'] != null,
|
alignment: Alignment.topRight,
|
||||||
alignment: Alignment.topRight,
|
offset: const Offset(14, -6),
|
||||||
offset: const Offset(14, -6),
|
child: Text(item['name'], style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||||
child: Text(item['name'], style: const TextStyle(fontWeight: FontWeight.bold)),
|
),
|
||||||
),
|
);
|
||||||
);
|
}).toList(),
|
||||||
}).toList(),
|
isScrollable: true, //禁止左右滑动
|
||||||
isScrollable: true, //禁止左右滑动
|
tabAlignment: TabAlignment.start,
|
||||||
tabAlignment: TabAlignment.start,
|
overlayColor: WidgetStateProperty.all(Colors.transparent),
|
||||||
overlayColor: WidgetStateProperty.all(Colors.transparent),
|
unselectedLabelColor: Colors.black87,
|
||||||
unselectedLabelColor: Colors.black87,
|
labelColor: Colors.black,
|
||||||
labelColor: Colors.black,
|
indicator: const UnderlineTabIndicator(borderSide: BorderSide(color: Colors.transparent, width: 2.0)),
|
||||||
indicator: const UnderlineTabIndicator(borderSide: BorderSide(color: Colors.transparent, width: 2.0)),
|
indicatorSize: TabBarIndicatorSize.label,
|
||||||
indicatorSize: TabBarIndicatorSize.label,
|
unselectedLabelStyle: const TextStyle(fontSize: 16.0, fontFamily: 'Microsoft YaHei'),
|
||||||
unselectedLabelStyle: const TextStyle(fontSize: 16.0, fontFamily: 'Microsoft YaHei'),
|
labelStyle: const TextStyle(fontSize: 18.0, fontFamily: 'Microsoft YaHei', fontWeight: FontWeight.bold),
|
||||||
labelStyle: const TextStyle(fontSize: 18.0, fontFamily: 'Microsoft YaHei', fontWeight: FontWeight.bold),
|
dividerHeight: 0,
|
||||||
dividerHeight: 0,
|
padding: const EdgeInsets.symmetric(horizontal: 10.0),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 10.0),
|
labelPadding: const EdgeInsets.symmetric(horizontal: 15.0),
|
||||||
labelPadding: const EdgeInsets.symmetric(horizontal: 15.0),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
];
|
||||||
];
|
},
|
||||||
},
|
body: TabBarView(
|
||||||
body: TabBarView(
|
controller: tabController,
|
||||||
controller: tabController,
|
children: [
|
||||||
children: [
|
// Tab 1:
|
||||||
// Tab 1:
|
Obx(() => _buildGridTab(0)),
|
||||||
Obx(() => _buildGridTab(0)),
|
],
|
||||||
],
|
),
|
||||||
),
|
);
|
||||||
);
|
}),
|
||||||
}),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -387,7 +388,6 @@ class MyPageState extends State<Vloger> with SingleTickerProviderStateMixin {
|
|||||||
final double currentHeight = constraints.maxHeight;
|
final double currentHeight = constraints.maxHeight;
|
||||||
double ratio = (currentHeight - minHeight) / (maxHeight - minHeight);
|
double ratio = (currentHeight - minHeight) / (maxHeight - minHeight);
|
||||||
ratio = ratio.clamp(0.0, 1.0);
|
ratio = ratio.clamp(0.0, 1.0);
|
||||||
String coverBg = userInfo.value.customInfo?['coverBg'] ?? '';
|
|
||||||
return Stack(
|
return Stack(
|
||||||
fit: StackFit.expand,
|
fit: StackFit.expand,
|
||||||
children: [
|
children: [
|
||||||
@ -395,8 +395,9 @@ class MyPageState extends State<Vloger> with SingleTickerProviderStateMixin {
|
|||||||
child: Opacity(
|
child: Opacity(
|
||||||
opacity: 1.0,
|
opacity: 1.0,
|
||||||
child: NetworkOrAssetImage(
|
child: NetworkOrAssetImage(
|
||||||
imageUrl: coverBg,
|
imageUrl: userInfo.value.customInfo?['coverBg'],
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
|
placeholderAsset: 'assets/images/bk.jpg',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -410,7 +411,11 @@ class MyPageState extends State<Vloger> with SingleTickerProviderStateMixin {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
ClipOval(
|
ClipOval(
|
||||||
child: NetworkOrAssetImage(imageUrl: userInfo.value.faceUrl),
|
child: NetworkOrAssetImage(
|
||||||
|
imageUrl: userInfo.value.faceUrl,
|
||||||
|
width: 80,
|
||||||
|
height: 80,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 15.0),
|
const SizedBox(width: 15.0),
|
||||||
Expanded(
|
Expanded(
|
||||||
@ -540,19 +545,42 @@ class MyPageState extends State<Vloger> with SingleTickerProviderStateMixin {
|
|||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: [1, 3].contains(followed.value)
|
child: [1, 2, 3].contains(followed.value)
|
||||||
? Row(
|
? Row(
|
||||||
key: const ValueKey('followed'),
|
key: const ValueKey('followed'),
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
print('点击已关注');
|
logger.w('点击已关注/已回关:${followed.value}');
|
||||||
final res = await ImService.instance.unfollowUser(userIDList: [vlogerId]);
|
final ctl = Get.find<ChatController>();
|
||||||
if (res.success) {
|
|
||||||
// 如果为1那么状态置为0,为3则置为2
|
if (followed.value == 2) {
|
||||||
followed.value = followed.value == 1 ? 0 : 2;
|
//回关,走关注逻辑
|
||||||
// 取关后不需重置陌生人消息group
|
final res = await ImService.instance.followUser(userIDList: [vlogerId]);
|
||||||
|
if (res.success) {
|
||||||
|
followed.value = 3;
|
||||||
|
// 回关后,如果会话存在,将此会话移除noFriend会话组
|
||||||
|
final res = await ImService.instance.getConversation(conversationID: 'c2c_$vlogerId');
|
||||||
|
if (res.success) {
|
||||||
|
V2TimConversation conversation = res.data;
|
||||||
|
if (conversation.conversationGroupList?.isNotEmpty ?? false) {
|
||||||
|
//把会话数据从陌生人分组移除
|
||||||
|
await ImService.instance.deleteConversationsFromGroup(
|
||||||
|
groupName: conversation.conversationGroupList!.first!, // 不涉及到加入多分组的情况,直接取
|
||||||
|
conversationIDList: [conversation.conversationID],
|
||||||
|
);
|
||||||
|
ctl.updateNoFriendMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
final res = await ImService.instance.unfollowUser(userIDList: [vlogerId]);
|
||||||
|
if (res.success) {
|
||||||
|
// 如果为1那么状态置为0,为3则置为2
|
||||||
|
followed.value = followed.value == 1 ? 0 : 2;
|
||||||
|
ctl.mergeNoFriend(conversationID: 'c2c_${userInfo.value.userID}');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
@ -565,16 +593,18 @@ class MyPageState extends State<Vloger> with SingleTickerProviderStateMixin {
|
|||||||
),
|
),
|
||||||
child: Text(followed.value == 1
|
child: Text(followed.value == 1
|
||||||
? '已关注'
|
? '已关注'
|
||||||
: followed.value == 3
|
: followed.value == 2
|
||||||
? '互关'
|
? '回关'
|
||||||
: '未知状态'),
|
: followed.value == 3
|
||||||
|
? '已互关'
|
||||||
|
: '未知状态'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
print('私信');
|
logger.w('私信');
|
||||||
// 获取指定会话
|
// 获取指定会话
|
||||||
final res = await ImService.instance.getConversation(conversationID: 'c2c_$vlogerId');
|
final res = await ImService.instance.getConversation(conversationID: 'c2c_$vlogerId');
|
||||||
V2TimConversation conversation = res.data;
|
V2TimConversation conversation = res.data;
|
||||||
@ -614,12 +644,12 @@ class MyPageState extends State<Vloger> with SingleTickerProviderStateMixin {
|
|||||||
child: ElevatedButton.icon(
|
child: ElevatedButton.icon(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
// 0没关系,2对方关注了我
|
// 0没关系,2对方关注了我
|
||||||
print('点击关注');
|
logger.w('点击关注');
|
||||||
final res = await ImService.instance.followUser(userIDList: [vlogerId]);
|
final res = await ImService.instance.followUser(userIDList: [vlogerId]);
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
followed.value = followed.value == 0 ? 1 : 3;
|
followed.value = followed.value == 0 ? 1 : 3;
|
||||||
if (followed.value == 3) {
|
if (followed.value == 3) {
|
||||||
// 修改后若为3,我将此会话移除noFriend会话组
|
// 修改后若为3,将此会话移除noFriend会话组
|
||||||
final res = await ImService.instance.getConversation(conversationID: 'c2c_$vlogerId');
|
final res = await ImService.instance.getConversation(conversationID: 'c2c_$vlogerId');
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
V2TimConversation conversation = res.data;
|
V2TimConversation conversation = res.data;
|
||||||
@ -629,14 +659,13 @@ class MyPageState extends State<Vloger> with SingleTickerProviderStateMixin {
|
|||||||
groupName: conversation.conversationGroupList!.first!,
|
groupName: conversation.conversationGroupList!.first!,
|
||||||
conversationIDList: [conversation.conversationID],
|
conversationIDList: [conversation.conversationID],
|
||||||
);
|
);
|
||||||
//重新构建会话
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.add),
|
icon: const Icon(Icons.add),
|
||||||
label: const Text('关注'),
|
label: Text('关注'),
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: FStyle.primaryColor,
|
backgroundColor: FStyle.primaryColor,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:loopin/IM/controller/chat_controller.dart';
|
|
||||||
import 'package:loopin/IM/im_core.dart';
|
|
||||||
import 'package:loopin/IM/im_message.dart';
|
|
||||||
import 'package:loopin/IM/im_service.dart' hide logger;
|
import 'package:loopin/IM/im_service.dart' hide logger;
|
||||||
import 'package:loopin/service/http.dart';
|
|
||||||
import 'package:loopin/api/common_api.dart';
|
import 'package:loopin/api/common_api.dart';
|
||||||
import 'package:loopin/utils/index.dart';
|
|
||||||
import 'package:loopin/components/my_toast.dart';
|
import 'package:loopin/components/my_toast.dart';
|
||||||
|
import 'package:loopin/service/http.dart';
|
||||||
|
import 'package:loopin/utils/index.dart';
|
||||||
|
|
||||||
import '../../behavior/custom_scroll_behavior.dart';
|
import '../../behavior/custom_scroll_behavior.dart';
|
||||||
|
|
||||||
class SearchResultPage extends StatefulWidget {
|
class SearchResultPage extends StatefulWidget {
|
||||||
@ -198,7 +196,7 @@ class _SearchResultPageState extends State<SearchResultPage> with SingleTickerPr
|
|||||||
final data = {
|
final data = {
|
||||||
'title': _searchQuery,
|
'title': _searchQuery,
|
||||||
'size': _pageSize,
|
'size': _pageSize,
|
||||||
'type': tabIndex+1, // 1-视频;2-商品;3-用户
|
'type': tabIndex + 1, // 1-视频;2-商品;3-用户
|
||||||
'current': currentPage,
|
'current': currentPage,
|
||||||
};
|
};
|
||||||
final res = await Http.post(CommonApi.aggregationSearchApi, data: data);
|
final res = await Http.post(CommonApi.aggregationSearchApi, data: data);
|
||||||
@ -270,75 +268,77 @@ class _SearchResultPageState extends State<SearchResultPage> with SingleTickerPr
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 点击关注按钮
|
// 点击关注按钮
|
||||||
onFocusBtnClick (user,index) async {
|
onFocusBtnClick(user, index) async {
|
||||||
final vlogerId = user['id'];
|
final vlogerId = user['id'];
|
||||||
final doIFollowVloger = user['doIFollowVloger'];
|
final doIFollowVloger = user['doIFollowVloger'];
|
||||||
print('是否关注此用户------------->${doIFollowVloger}');
|
print('是否关注此用户------------->$doIFollowVloger');
|
||||||
if (doIFollowVloger == null || doIFollowVloger == false) {
|
print('此用户UserId------------->$vlogerId');
|
||||||
final res = await ImService.instance.followUser(userIDList: [vlogerId]);
|
if (doIFollowVloger == false) {
|
||||||
print('关注结果------------->${res.success}');
|
final res = await ImService.instance.followUser(userIDList: [vlogerId]);
|
||||||
if (res.success) {
|
print('关注结果------------->${res.success}');
|
||||||
setState(() {
|
if (res.success) {
|
||||||
_userResults[index]['doIFollowVloger'] = true;
|
setState(() {
|
||||||
});
|
_userResults[index]['doIFollowVloger'] = !_userResults[index]['doIFollowVloger'];
|
||||||
}
|
});
|
||||||
}else{
|
}
|
||||||
final res = await ImService.instance.unfollowUser(userIDList: [vlogerId]);
|
} else {
|
||||||
print('取消关注结果------------->${res.success}');
|
final res = await ImService.instance.followUser(userIDList: [vlogerId]);
|
||||||
if (res.success) {
|
print('取消关注结果------------->${res.success}');
|
||||||
setState(() {
|
if (res.success) {
|
||||||
_userResults[index]['doIFollowVloger'] = false;
|
setState(() {
|
||||||
});
|
_userResults[index]['doIFollowVloger'] = !_userResults[index]['doIFollowVloger'];
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 构建加载更多组件
|
// 构建加载更多组件
|
||||||
Widget _buildLoadMoreWidget(int tabIndex) {
|
Widget _buildLoadMoreWidget(int tabIndex) {
|
||||||
if (_isLoadingMore) {
|
if (_isLoadingMore) {
|
||||||
return const Center(
|
return const Center(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.symmetric(vertical: 8.0), // 减少垂直间距
|
padding: EdgeInsets.symmetric(vertical: 8.0), // 减少垂直间距
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: 20,
|
width: 20,
|
||||||
height: 20,
|
height: 20,
|
||||||
child: CircularProgressIndicator(strokeWidth: 2),
|
child: CircularProgressIndicator(strokeWidth: 2),
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool hasMore;
|
|
||||||
switch (tabIndex) {
|
|
||||||
case 0:
|
|
||||||
hasMore = _videoHasMore;
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
hasMore = _productHasMore;
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
hasMore = _userHasMore;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
hasMore = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasMore && _getCurrentResults(tabIndex).isNotEmpty) {
|
|
||||||
return const Center(
|
|
||||||
child: Padding(
|
|
||||||
padding: EdgeInsets.symmetric(vertical: 8.0), // 减少垂直间距
|
|
||||||
child: Text(
|
|
||||||
'没有更多数据了',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12, // 减小字体
|
|
||||||
color: Colors.grey,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return Container();
|
bool hasMore;
|
||||||
}
|
switch (tabIndex) {
|
||||||
|
case 0:
|
||||||
|
hasMore = _videoHasMore;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
hasMore = _productHasMore;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
hasMore = _userHasMore;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
hasMore = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasMore && _getCurrentResults(tabIndex).isNotEmpty) {
|
||||||
|
return const Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 8.0), // 减少垂直间距
|
||||||
|
child: Text(
|
||||||
|
'没有更多数据了',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12, // 减小字体
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Container();
|
||||||
|
}
|
||||||
|
|
||||||
// 获取当前Tab的数据
|
// 获取当前Tab的数据
|
||||||
List<dynamic> _getCurrentResults(int tabIndex) {
|
List<dynamic> _getCurrentResults(int tabIndex) {
|
||||||
@ -476,9 +476,7 @@ class _SearchResultPageState extends State<SearchResultPage> with SingleTickerPr
|
|||||||
|
|
||||||
return NotificationListener<ScrollNotification>(
|
return NotificationListener<ScrollNotification>(
|
||||||
onNotification: (ScrollNotification scrollInfo) {
|
onNotification: (ScrollNotification scrollInfo) {
|
||||||
if (scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent &&
|
if (scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent && !_isLoadingMore && _videoHasMore) {
|
||||||
!_isLoadingMore &&
|
|
||||||
_videoHasMore) {
|
|
||||||
_loadMoreData(0);
|
_loadMoreData(0);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -580,14 +578,14 @@ class _SearchResultPageState extends State<SearchResultPage> with SingleTickerPr
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisAlignment: MainAxisAlignment.end, // 内容靠底部对齐
|
mainAxisAlignment: MainAxisAlignment.end, // 内容靠底部对齐
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
video['title']?.toString() ?? '无标题',
|
video['title']?.toString() ?? '无标题',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
),
|
),
|
||||||
maxLines: 1, // 改为 1,限制为单行
|
maxLines: 1, // 改为 1,限制为单行
|
||||||
overflow: TextOverflow.ellipsis, // 超出部分显示省略号
|
overflow: TextOverflow.ellipsis, // 超出部分显示省略号
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Row(
|
Row(
|
||||||
@ -605,9 +603,7 @@ class _SearchResultPageState extends State<SearchResultPage> with SingleTickerPr
|
|||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
child: video['avatar'] == null || video['avatar'].toString().isEmpty
|
child: video['avatar'] == null || video['avatar'].toString().isEmpty ? const Icon(Icons.person, size: 10, color: Colors.grey) : null,
|
||||||
? const Icon(Icons.person, size: 10, color: Colors.grey)
|
|
||||||
: null,
|
|
||||||
),
|
),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
Expanded(
|
Expanded(
|
||||||
@ -627,7 +623,7 @@ class _SearchResultPageState extends State<SearchResultPage> with SingleTickerPr
|
|||||||
const Icon(Icons.favorite_border, size: 10, color: Colors.grey),
|
const Icon(Icons.favorite_border, size: 10, color: Colors.grey),
|
||||||
const SizedBox(width: 2),
|
const SizedBox(width: 2),
|
||||||
Text(
|
Text(
|
||||||
Utils().formatLikeCount(video['likeCounts'] ?? 0),
|
Utils.graceNumber(video['likeCounts'] ?? 0),
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 9,
|
fontSize: 9,
|
||||||
color: Colors.grey,
|
color: Colors.grey,
|
||||||
@ -688,9 +684,7 @@ class _SearchResultPageState extends State<SearchResultPage> with SingleTickerPr
|
|||||||
|
|
||||||
return NotificationListener<ScrollNotification>(
|
return NotificationListener<ScrollNotification>(
|
||||||
onNotification: (ScrollNotification scrollInfo) {
|
onNotification: (ScrollNotification scrollInfo) {
|
||||||
if (scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent &&
|
if (scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent && !_isLoadingMore && _productHasMore) {
|
||||||
!_isLoadingMore &&
|
|
||||||
_productHasMore) {
|
|
||||||
_loadMoreData(1);
|
_loadMoreData(1);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -755,97 +749,97 @@ class _SearchResultPageState extends State<SearchResultPage> with SingleTickerPr
|
|||||||
return Container();
|
return Container();
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildProductItem(Map<String, dynamic> product) {
|
Widget _buildProductItem(Map<String, dynamic> product) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
// 商品点击事件处理
|
// 商品点击事件处理
|
||||||
print('点击了商品: ${product['id']}');
|
print('点击了商品: ${product['id']}');
|
||||||
Get.toNamed('/goods', arguments: {'goodsId': product['id']});
|
Get.toNamed('/goods', arguments: {'goodsId': product['id']});
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(_itemCornerRadius),
|
borderRadius: BorderRadius.circular(_itemCornerRadius),
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Colors.grey.withOpacity(0.1),
|
color: Colors.grey.withOpacity(0.1),
|
||||||
blurRadius: 4,
|
blurRadius: 4,
|
||||||
offset: const Offset(0, 2),
|
offset: const Offset(0, 2),
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
// 商品图片 - 宽度100%,高度自适应
|
|
||||||
ClipRRect(
|
|
||||||
borderRadius: const BorderRadius.only(
|
|
||||||
topLeft: Radius.circular(_itemCornerRadius),
|
|
||||||
topRight: Radius.circular(_itemCornerRadius),
|
|
||||||
),
|
),
|
||||||
child: Container(
|
],
|
||||||
width: double.infinity,
|
),
|
||||||
color: Colors.grey[200],
|
child: Column(
|
||||||
child: product['pic'] != null
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
? Image.network(
|
children: [
|
||||||
product['pic'].toString(),
|
// 商品图片 - 宽度100%,高度自适应
|
||||||
fit: BoxFit.cover,
|
ClipRRect(
|
||||||
)
|
borderRadius: const BorderRadius.only(
|
||||||
: Container(
|
topLeft: Radius.circular(_itemCornerRadius),
|
||||||
height: 110,
|
topRight: Radius.circular(_itemCornerRadius),
|
||||||
child: const Center(
|
),
|
||||||
child: Icon(Icons.shopping_bag, color: Colors.grey, size: 32),
|
child: Container(
|
||||||
),
|
width: double.infinity,
|
||||||
),
|
color: Colors.grey[200],
|
||||||
),
|
child: product['pic'] != null
|
||||||
),
|
? Image.network(
|
||||||
// 商品信息 - 固定在容器底部
|
product['pic'].toString(),
|
||||||
Expanded(
|
fit: BoxFit.cover,
|
||||||
child: Padding(
|
)
|
||||||
padding: const EdgeInsets.all(6),
|
: SizedBox(
|
||||||
child: Column(
|
height: 110,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
child: const Center(
|
||||||
mainAxisAlignment: MainAxisAlignment.end, // 内容靠底部对齐
|
child: Icon(Icons.shopping_bag, color: Colors.grey, size: 32),
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
product['name']?.toString() ?? '未知商品',
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 11,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
maxLines: 2,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'¥${product['price']?.toString() ?? '0.00'}',
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Colors.pink,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
|
||||||
'已售 ${product['sales']?.toString() ?? '0'}',
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 9,
|
|
||||||
color: Colors.grey,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
// 商品信息 - 固定在容器底部
|
||||||
],
|
Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(6),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end, // 内容靠底部对齐
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
product['name']?.toString() ?? '未知商品',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'¥${product['price']?.toString() ?? '0.00'}',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.pink,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'已售 ${product['sales']?.toString() ?? '0'}',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 9,
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 用户Tab
|
// 用户Tab
|
||||||
Widget _buildUserTab() {
|
Widget _buildUserTab() {
|
||||||
@ -855,9 +849,7 @@ class _SearchResultPageState extends State<SearchResultPage> with SingleTickerPr
|
|||||||
|
|
||||||
return NotificationListener<ScrollNotification>(
|
return NotificationListener<ScrollNotification>(
|
||||||
onNotification: (ScrollNotification scrollInfo) {
|
onNotification: (ScrollNotification scrollInfo) {
|
||||||
if (scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent &&
|
if (scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent && !_isLoadingMore && _userHasMore) {
|
||||||
!_isLoadingMore &&
|
|
||||||
_userHasMore) {
|
|
||||||
_loadMoreData(2);
|
_loadMoreData(2);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -872,7 +864,7 @@ class _SearchResultPageState extends State<SearchResultPage> with SingleTickerPr
|
|||||||
return _buildLoadMoreWidget(2);
|
return _buildLoadMoreWidget(2);
|
||||||
}
|
}
|
||||||
final user = _userResults[index] as Map<String, dynamic>;
|
final user = _userResults[index] as Map<String, dynamic>;
|
||||||
return _buildUserItem(user,index);
|
return _buildUserItem(user, index);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -882,7 +874,7 @@ class _SearchResultPageState extends State<SearchResultPage> with SingleTickerPr
|
|||||||
Widget _buildUserItem(Map<String, dynamic> user, int index) {
|
Widget _buildUserItem(Map<String, dynamic> user, int index) {
|
||||||
// 判断当前用户是否已被关注
|
// 判断当前用户是否已被关注
|
||||||
bool isFollowing = user['doIFollowVloger'] ?? false;
|
bool isFollowing = user['doIFollowVloger'] ?? false;
|
||||||
print('111111111111111111111111${isFollowing}');
|
print('111111111111111111111111$isFollowing');
|
||||||
return Container(
|
return Container(
|
||||||
margin: const EdgeInsets.only(bottom: 8),
|
margin: const EdgeInsets.only(bottom: 8),
|
||||||
padding: const EdgeInsets.all(10),
|
padding: const EdgeInsets.all(10),
|
||||||
@ -912,9 +904,7 @@ class _SearchResultPageState extends State<SearchResultPage> with SingleTickerPr
|
|||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
child: user['avatar'] == null
|
child: user['avatar'] == null ? const Icon(Icons.person, color: Colors.grey, size: 20) : null,
|
||||||
? const Icon(Icons.person, color: Colors.grey, size: 20)
|
|
||||||
: null,
|
|
||||||
),
|
),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
Expanded(
|
Expanded(
|
||||||
@ -951,9 +941,7 @@ class _SearchResultPageState extends State<SearchResultPage> with SingleTickerPr
|
|||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
),
|
),
|
||||||
// 添加边框样式
|
// 添加边框样式
|
||||||
side: isFollowing
|
side: isFollowing ? BorderSide(color: Colors.grey[400]!, width: 0.5) : BorderSide.none,
|
||||||
? BorderSide(color: Colors.grey[400]!, width: 0.5)
|
|
||||||
: BorderSide.none,
|
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
isFollowing ? '已关注' : '关注',
|
isFollowing ? '已关注' : '关注',
|
||||||
@ -984,4 +972,4 @@ class _SearchResultPageState extends State<SearchResultPage> with SingleTickerPr
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -675,7 +675,7 @@ class _RecommendModuleState extends State<RecommendModule> {
|
|||||||
'size': pageSize,
|
'size': pageSize,
|
||||||
});
|
});
|
||||||
final data = res['data'];
|
final data = res['data'];
|
||||||
logger.d('关注用户的视频列表:$data');
|
// logger.d('关注用户的视频列表:$data');
|
||||||
if (data == null || (data is List && data.isEmpty)) {
|
if (data == null || (data is List && data.isEmpty)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -698,7 +698,7 @@ class _RecommendModuleState extends State<RecommendModule> {
|
|||||||
if (videos.isNotEmpty) {
|
if (videos.isNotEmpty) {
|
||||||
page++;
|
page++;
|
||||||
}
|
}
|
||||||
logger.i('视频数据列表------------------->$videoList');
|
// logger.i('视频数据列表------------------->$videoList');
|
||||||
|
|
||||||
// 初始化播放器
|
// 初始化播放器
|
||||||
player.open(
|
player.open(
|
||||||
@ -1247,17 +1247,14 @@ class _RecommendModuleState extends State<RecommendModule> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
onTap: ()async {
|
onTap: () async {
|
||||||
player.pause();
|
player.pause();
|
||||||
// 跳转到举报页面并等待返回结果
|
// 跳转到举报页面并等待返回结果
|
||||||
final result = await Get.toNamed(
|
final result = await Get.toNamed('/report', arguments: videoList[videoModuleController.videoPlayIndex.value]);
|
||||||
'/report',
|
if (result != null) {
|
||||||
arguments: videoList[videoModuleController
|
player.play();
|
||||||
.videoPlayIndex.value]);
|
}
|
||||||
if (result != null) {
|
},
|
||||||
player.play();
|
|
||||||
};
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -10,15 +10,19 @@ import 'package:loopin/pages/chat/chat_no_friend.dart';
|
|||||||
import 'package:loopin/pages/chat/notify/interaction.dart';
|
import 'package:loopin/pages/chat/notify/interaction.dart';
|
||||||
import 'package:loopin/pages/chat/notify/noFriend.dart';
|
import 'package:loopin/pages/chat/notify/noFriend.dart';
|
||||||
import 'package:loopin/pages/chat/notify/system.dart';
|
import 'package:loopin/pages/chat/notify/system.dart';
|
||||||
|
import 'package:loopin/pages/groupChat/index.dart';
|
||||||
import 'package:loopin/pages/my/des.dart';
|
import 'package:loopin/pages/my/des.dart';
|
||||||
|
import 'package:loopin/pages/my/fans.dart';
|
||||||
|
import 'package:loopin/pages/my/flowing.dart';
|
||||||
|
import 'package:loopin/pages/my/mutual_followers.dart';
|
||||||
import 'package:loopin/pages/my/nick_name.dart';
|
import 'package:loopin/pages/my/nick_name.dart';
|
||||||
import 'package:loopin/pages/my/setting.dart';
|
import 'package:loopin/pages/my/setting.dart';
|
||||||
import 'package:loopin/pages/my/user_info.dart';
|
import 'package:loopin/pages/my/user_info.dart';
|
||||||
import 'package:loopin/pages/my/vloger.dart';
|
import 'package:loopin/pages/my/vloger.dart';
|
||||||
import 'package:loopin/pages/video/report.dart';
|
|
||||||
import 'package:loopin/pages/search/index.dart';
|
import 'package:loopin/pages/search/index.dart';
|
||||||
import 'package:loopin/pages/search/search-result.dart';
|
import 'package:loopin/pages/search/search-result.dart';
|
||||||
import 'package:loopin/pages/video/commonVideo.dart';
|
import 'package:loopin/pages/video/commonVideo.dart';
|
||||||
|
import 'package:loopin/pages/video/report.dart';
|
||||||
|
|
||||||
import '../layouts/index.dart';
|
import '../layouts/index.dart';
|
||||||
/* 引入路由页面 */
|
/* 引入路由页面 */
|
||||||
@ -42,8 +46,8 @@ final Map<String, Widget> routes = {
|
|||||||
'/vloger': const Vloger(),
|
'/vloger': const Vloger(),
|
||||||
'/report': const ReportPage(),
|
'/report': const ReportPage(),
|
||||||
'/videoDetail': const VideoDetailPage(),
|
'/videoDetail': const VideoDetailPage(),
|
||||||
'/search': const SearchPage(),
|
'/search': const SearchPage(),
|
||||||
'/search-result': const SearchResultPage(),
|
'/search-result': const SearchResultPage(),
|
||||||
//settins
|
//settins
|
||||||
'/setting': const Setting(),
|
'/setting': const Setting(),
|
||||||
'/userInfo': const UserInfo(),
|
'/userInfo': const UserInfo(),
|
||||||
@ -56,6 +60,12 @@ final Map<String, Widget> routes = {
|
|||||||
'/noFriend': const Nofriend(),
|
'/noFriend': const Nofriend(),
|
||||||
'/system': const System(),
|
'/system': const System(),
|
||||||
'/interaction': const Interaction(),
|
'/interaction': const Interaction(),
|
||||||
|
//关系链
|
||||||
|
'/fans': const Fans(),
|
||||||
|
'/flow': const Flowing(),
|
||||||
|
'/eachFlow': const MutualFollowers(),
|
||||||
|
//群
|
||||||
|
'/group': const StartGroupChatPage(),
|
||||||
};
|
};
|
||||||
|
|
||||||
final List<GetPage> routeList = routes.entries
|
final List<GetPage> routeList = routes.entries
|
||||||
|
@ -10,6 +10,7 @@ class HttpConfig {
|
|||||||
// baseUrl: 'http://cjh.wuzhongjie.com.cn',
|
// baseUrl: 'http://cjh.wuzhongjie.com.cn',
|
||||||
// baseUrl: 'http://82.156.121.2:8880',
|
// baseUrl: 'http://82.156.121.2:8880',
|
||||||
baseUrl: 'http://192.168.1.65:8880',
|
baseUrl: 'http://192.168.1.65:8880',
|
||||||
|
// baseUrl: 'http://192.168.1.22:8080',
|
||||||
|
|
||||||
// connectTimeout: Duration(seconds: 30),
|
// connectTimeout: Duration(seconds: 30),
|
||||||
// receiveTimeout: Duration(seconds: 30),
|
// receiveTimeout: Duration(seconds: 30),
|
||||||
|
@ -5,26 +5,34 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
class FStyle {
|
class FStyle {
|
||||||
// 模拟Badge
|
// 模拟Badge
|
||||||
static badge(int count, {
|
static badge(int count, {Color color = Colors.red, bool isdot = false, double height = 16.0, double width = 16.0}) {
|
||||||
Color color = Colors.red,
|
|
||||||
bool isdot = false,
|
|
||||||
double height = 16.0,
|
|
||||||
double width = 16.0
|
|
||||||
}) {
|
|
||||||
final num = count > 99 ? '99+' : count;
|
final num = count > 99 ? '99+' : count;
|
||||||
return Container(
|
return Container(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
height: isdot ? height / 2 : height,
|
height: isdot ? height / 2 : height,
|
||||||
width: isdot ? width / 2 : width,
|
width: isdot ? width / 2 : width,
|
||||||
decoration: BoxDecoration(color: color, borderRadius: BorderRadius.circular(100.00)),
|
decoration: BoxDecoration(color: color, borderRadius: BorderRadius.circular(100.00)),
|
||||||
child: isdot ? null : Text('$num', style: const TextStyle(color: Colors.white, fontSize: 10.0,)),
|
child: isdot
|
||||||
|
? null
|
||||||
|
: Text('$num',
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 10.0,
|
||||||
|
)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 边框
|
// 边框
|
||||||
static const border = Divider(color: Color(0xFFBBBBBB), height: 1.0, thickness: .5,);
|
// static const border = Divider(color: Color(0xFFBBBBBB), height: 1.0, thickness: .5,);
|
||||||
|
static const border = Divider(
|
||||||
|
color: Colors.white,
|
||||||
|
height: 1.0,
|
||||||
|
thickness: .5,
|
||||||
|
);
|
||||||
|
|
||||||
// 颜色
|
// 颜色
|
||||||
static const backgroundColor = Color(0xFFEEEEEE);
|
// static const backgroundColor = Color(0xFFEEEEEE);
|
||||||
|
static const backgroundColor = Colors.white;
|
||||||
static const primaryColor = Color(0xFFFF5000);
|
static const primaryColor = Color(0xFFFF5000);
|
||||||
static const white = Colors.white;
|
static const white = Colors.white;
|
||||||
static const c999 = Color(0xFF999999);
|
static const c999 = Color(0xFF999999);
|
||||||
|
@ -113,7 +113,7 @@ class Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 大数字转换
|
// 大数字转换
|
||||||
String graceNumber(int number) {
|
static String graceNumber(int number) {
|
||||||
if (number == 0) return "0";
|
if (number == 0) return "0";
|
||||||
|
|
||||||
if (number < 1000) {
|
if (number < 1000) {
|
||||||
@ -130,12 +130,12 @@ class Utils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _trimTrailingZero(double number) {
|
static String _trimTrailingZero(double number) {
|
||||||
return number.toStringAsFixed(1).replaceAll(RegExp(r'\.0$'), '');
|
return number.toStringAsFixed(1).replaceAll(RegExp(r'\.0$'), '');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理聊天消息时间
|
// 处理聊天消息时间
|
||||||
String formatChatTime(int timestamp) {
|
static String formatChatTime(int timestamp) {
|
||||||
// timestamp 是秒级时间戳,转成 DateTime
|
// timestamp 是秒级时间戳,转成 DateTime
|
||||||
DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000);
|
DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000);
|
||||||
|
|
||||||
@ -189,31 +189,29 @@ class Utils {
|
|||||||
'${messageTime.minute.toString().padLeft(2, '0')}';
|
'${messageTime.minute.toString().padLeft(2, '0')}';
|
||||||
}
|
}
|
||||||
|
|
||||||
String _formatHourMinute(DateTime dt) {
|
static String _formatHourMinute(DateTime dt) {
|
||||||
final hour = dt.hour.toString().padLeft(2, '0');
|
final hour = dt.hour.toString().padLeft(2, '0');
|
||||||
final minute = dt.minute.toString().padLeft(2, '0');
|
final minute = dt.minute.toString().padLeft(2, '0');
|
||||||
return "$hour:$minute";
|
return "$hour:$minute";
|
||||||
}
|
}
|
||||||
|
|
||||||
String _weekdayLabel(int weekday) {
|
static String _weekdayLabel(int weekday) {
|
||||||
const weekdays = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"];
|
const weekdays = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"];
|
||||||
return weekdays[(weekday - 1) % 7];
|
return weekdays[(weekday - 1) % 7];
|
||||||
}
|
}
|
||||||
// 格式化点赞数量
|
|
||||||
String formatLikeCount(int count) {
|
|
||||||
if (count >= 10000) {
|
|
||||||
return '${(count / 10000).toStringAsFixed(1)}w';
|
|
||||||
} else if (count >= 1000) {
|
|
||||||
return '${(count / 1000).toStringAsFixed(1)}k';
|
|
||||||
}
|
|
||||||
return count.toString();
|
|
||||||
}
|
|
||||||
// 分转换为元
|
|
||||||
static String fenToYuanSimple(dynamic fen) {
|
|
||||||
if (fen == null) {
|
|
||||||
return '0.00';
|
|
||||||
}
|
|
||||||
|
|
||||||
return (double.parse(fen) / 100).toStringAsFixed(2);
|
static String getTipText(int realFollowType) {
|
||||||
|
switch (realFollowType) {
|
||||||
|
case 0:
|
||||||
|
return '关注';
|
||||||
|
case 1:
|
||||||
|
return '已关注';
|
||||||
|
case 2:
|
||||||
|
return '回关';
|
||||||
|
case 3:
|
||||||
|
return '已互关';
|
||||||
|
default:
|
||||||
|
return '未知状态';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:loopin/IM/push_service.dart';
|
|
||||||
import 'package:loopin/models/notify_message.type.dart';
|
import 'package:loopin/models/notify_message.type.dart';
|
||||||
import 'package:loopin/models/summary_type.dart';
|
import 'package:loopin/models/summary_type.dart';
|
||||||
import 'package:tencent_cloud_chat_sdk/enum/message_elem_type.dart';
|
import 'package:tencent_cloud_chat_sdk/enum/message_elem_type.dart';
|
||||||
@ -53,8 +52,8 @@ String _parseCustomMessage(V2TimMessage? msg) {
|
|||||||
if (msg == null) return '[null]';
|
if (msg == null) return '[null]';
|
||||||
final sum = msg.cloudCustomData;
|
final sum = msg.cloudCustomData;
|
||||||
final elment = msg.customElem; // 所有服务端发送的通知消息都走【自定义消息类型】
|
final elment = msg.customElem; // 所有服务端发送的通知消息都走【自定义消息类型】
|
||||||
logger.w('解析自定义消息:${msg.toJson()}');
|
// logger.w('解析自定义消息:${msg.toJson()}');
|
||||||
logger.w('解析element:${elment?.desc ?? 'summary_error'}');
|
// logger.w('解析element:${elment?.desc ?? 'summary_error'}');
|
||||||
try {
|
try {
|
||||||
switch (sum) {
|
switch (sum) {
|
||||||
case SummaryType.hongbao:
|
case SummaryType.hongbao:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user