This commit is contained in:
abu 2025-09-03 11:25:31 +08:00
parent f4dcf8a52d
commit 839d6ef5ad
33 changed files with 2652 additions and 885 deletions

View File

@ -13,12 +13,123 @@ class ChatController extends GetxController {
final chatList = <ConversationViewModel>[].obs;
void initChatData() {
// chatList.value = <ConversationViewModel>[];
chatList.clear();
nextSeq.value = '0';
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 {
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');
if (hasNoFriend) {
//
final ConversationViewModel matchItem = chatList.firstWhere(
(item) => item.conversation.conversationGroupList?.contains(myConversationType.ConversationType.noFriend.name) ?? false,
);
//
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(
// nofriend分组是否还有数据
final noFriendData = 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.conversation.showName = '陌生人消息';
conviewModel.conversation.unreadCount = unread.data;
// 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;
}
if (noFriendData.success && (noFriendData.data?.conversationList?.isNotEmpty == true)) {
//
final ConversationViewModel matchItem = chatList.firstWhere(
(item) => item.conversation.conversationGroupList?.contains(myConversationType.ConversationType.noFriend.name) ?? false,
orElse: () {
//viewmodel会话上的分组被移除
logger.w("没有找到符合条件的元素,执行移除逻辑");
return ConversationViewModel(conversation: V2TimConversation(conversationID: ''));
},
);
//
final unreadTotal = await ImService.instance.getUnreadMessageCountByFilter(
filter: V2TimConversationFilter(
conversationGroup: myConversationType.ConversationType.noFriend.name,
hasUnreadCount: true,
),
);
// nofriend分组
if (matchItem.conversation.conversationID.isEmpty) {
// viewmodel
await ConversationViewModel.createConversationViewModel(convList: noFriendData.data!.conversationList!);
} else {
matchItem.conversation.lastMessage = csion!.lastMessage;
matchItem.conversation.unreadCount = unreadTotal.data;
}
} else {
//
chatList.removeWhere((chatItem) => chatItem.isCustomAdmin == myConversationType.ConversationType.noFriend.name);
}
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()}');
}
}

View File

@ -13,6 +13,7 @@ class ChatDetailController extends GetxController {
final RxList<V2TimMessage> chatList = <V2TimMessage>[].obs;
final RxBool isFriend = true.obs;
final RxInt followType = 0.obs;
void updateChatListWithTimeLabels(List<V2TimMessage> originMessages) async {
final idRes = await ImService.instance.selfUserId();
@ -55,7 +56,7 @@ class ChatDetailController extends GetxController {
// label后插入
displayMessages.add(current);
if (needInsertLabel) {
final labelTime = Utils().formatChatTime(currentTimestamp);
final labelTime = Utils.formatChatTime(currentTimestamp);
final timeLabel = await IMMessage().insertTimeLabel(labelTime, selfUserId);
displayMessages.add(timeLabel.data);
}

View File

@ -131,6 +131,4 @@ class ImUserInfoController extends GetxController {
if (gender.value < 0) return;
await ImService.instance.setSelfInfo(userFullInfo: V2TimUserFullInfo(gender: gender.value));
}
/// updateAvatarupdateSignature
}

View File

@ -51,12 +51,15 @@ class GlobalBadge extends GetxController {
final chatItem = ctl.chatList[i];
logger.w('需要更新的ID:${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(
(c) => c.conversationID == chatItem.conversation.conversationID,
orElse: () => V2TimConversation(conversationID: ''),
);
//
ctl.getNoFriendData(csion: chatItem.conversation);
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.unreadCount = unread.data; //
} else {
update();
} else if (updatedConv.conversationID != '') {
//
logger.w('不需要分组的会话,正常更新');
logger.w('非陌生人消息的会话,正常更新');
chatItem.conversation = updatedConv;
update();
}
} else {
logger.e('会话列表中不包含的会话:$updatedIds');
logger.e('本地会话列表中不包含的会话:$updatedIds'); // 1nofriend分组会话2:
//
for (var cvID in updatedIds) {
//
final isReal = await ImService.instance.getConversation(conversationID: cvID);
final V2TimConversation realConv = isReal.data;
if (isReal.success) {
if (realConv.conversationID.isNotEmpty) {
final V2TimConversation realConv = isReal.data;
if (realConv.lastMessage != null) {
//
if (Get.isRegistered<NotifyNoFriendController>()) {
logger.w('在陌生人会话列表');
final notifyCtl = Get.find<NotifyNoFriendController>();
notifyCtl.updateLastMsg(conversation: realConv);
} else {
//
if (realConv.conversationGroupList?.isEmpty ?? false) {
willInsert.add(realConv);
//
if (realConv.conversationGroupList?.contains(ConversationType.noFriend.name) ?? false) {
notifyCtl.updateLastMsg(conversation: realConv);
} else {
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) {
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);
}
//
@ -116,12 +144,23 @@ class GlobalBadge extends GetxController {
final btime = b.conversation.lastMessage?.timestamp ?? 0;
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();
},
}, //changeEnd;
);
// final ctl = Get.find<ChatController>();
// ctl.getConversationList();
_initUnreadCount();
initUnreadCount();
_addListener();
}
@ -171,7 +210,7 @@ class GlobalBadge extends GetxController {
}
///
void _initUnreadCount() async {
void initUnreadCount() async {
final res = await TencentImSDKPlugin.v2TIMManager.getConversationManager().getTotalUnreadMessageCount();
if (res.code == 0) {
totalUnread.value = res.data ?? 0;
@ -187,7 +226,7 @@ class GlobalBadge extends GetxController {
///
handAndroid() {
_initUnreadCount();
initUnreadCount();
final ctl = Get.find<ChatController>();
ctl.initChatData();
ctl.getConversationList();
@ -201,7 +240,7 @@ class GlobalBadge extends GetxController {
/// total
Future<void> refreshUnreadCount() async {
_initUnreadCount();
initUnreadCount();
}
///

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:get/get.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/controller/video_module_controller.dart';
import 'package:loopin/utils/common.dart';
@ -51,6 +52,8 @@ class ImCore {
},
onUserSigExpired: () => logger.w("UserSig 过期"),
onSelfInfoUpdated: (V2TimUserFullInfo info) {
final ImUserInfoController imUserInfoController = Get.find<ImUserInfoController>();
imUserInfoController.refreshUserInfo();
logger.i("用户信息更新: ${info.toJson()}");
},
),

View File

@ -1,5 +1,4 @@
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/models/v2_tim_friend_application.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_friend_info.dart';
@ -62,7 +61,7 @@ class ImFriendListeners {
for (var item in userInfoList) {
logger.i('新增粉丝:${item.toJson()}');
}
NotificationBanner.foucs(userInfoList.last);
// NotificationBanner.foucs(userInfoList.last);
} else {
//
for (var item in userInfoList) {

View File

@ -3,6 +3,7 @@ import 'dart:async';
import 'package:get/get.dart';
import 'package:logger/logger.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_service.dart';
import 'package:loopin/utils/index.dart';
@ -49,12 +50,12 @@ class ImMessageListenerService extends GetxService {
DateTime.now().millisecondsSinceEpoch,
)) {
// 3
final showLabel = Utils().formatChatTime(DateTime.now().millisecondsSinceEpoch ~/ 1000);
final showLabel = Utils.formatChatTime(DateTime.now().millisecondsSinceEpoch ~/ 1000);
final resMsg = await IMMessage().insertTimeLabel(showLabel, selfUserId);
messagesToInsert.add(resMsg.data);
} else {
//
final showLabel = Utils().formatChatTime(DateTime.now().millisecondsSinceEpoch ~/ 1000);
final showLabel = Utils.formatChatTime(DateTime.now().millisecondsSinceEpoch ~/ 1000);
final resMsg = await IMMessage().insertTimeLabel(showLabel, selfUserId);
messagesToInsert.add(resMsg.data);
}
@ -177,11 +178,15 @@ class ImMessageListenerService extends GetxService {
}
},
onRecvC2CReadReceipt: (List<V2TimMessageReceipt> receiptList) {
final gtl = Get.find<GlobalBadge>();
gtl.initUnreadCount();
for (var receipt in receiptList) {
logger.i("C2C已读: msgID=${receipt.msgID}, userID=${receipt.userID}");
}
},
onRecvMessageReadReceipts: (List<V2TimMessageReceipt> receiptList) {
final gtl = Get.find<GlobalBadge>();
gtl.initUnreadCount();
for (var receipt in receiptList) {
logger.i("群已读: groupID=${receipt.groupID}, msgID=${receipt.msgID}, read=${receipt.readCount}, unread=${receipt.unreadCount}");
}

View File

@ -710,4 +710,15 @@ class ImService {
);
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);
}
}

View File

@ -8,6 +8,16 @@ enum ConversationType {
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 {
String get name {
switch (this) {

View File

@ -19,7 +19,6 @@ class ConversationViewModel {
// userID groupID
for (var conv in convList) {
logger.e('未过滤前到会话数据:${conv.toLogString()}');
if (conv.userID != null) {
userIDList.add(conv.userID!);
} else if (conv.groupID != null) {

View File

@ -530,7 +530,7 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
final url = obj['url'];
final title = obj['title'];
final price = obj['price'];
final sell = Utils().graceNumber(int.tryParse(obj['sell'])!);
final sell = Utils.graceNumber(int.tryParse(obj['sell'])!);
msgtpl.add(RenderChatItem(
data: item,
child: GestureDetector(
@ -1039,12 +1039,12 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
DateTime.now().millisecondsSinceEpoch,
)) {
// 3
final showLabel = Utils().formatChatTime(DateTime.now().millisecondsSinceEpoch ~/ 1000);
final showLabel = Utils.formatChatTime(DateTime.now().millisecondsSinceEpoch ~/ 1000);
final resMsg = await IMMessage().insertTimeLabel(showLabel, selfUserId);
messagesToInsert.add(resMsg.data);
} else {
//
final showLabel = Utils().formatChatTime(DateTime.now().millisecondsSinceEpoch ~/ 1000);
final showLabel = Utils.formatChatTime(DateTime.now().millisecondsSinceEpoch ~/ 1000);
final resMsg = await IMMessage().insertTimeLabel(showLabel, selfUserId);
messagesToInsert.add(resMsg.data);
@ -2155,18 +2155,32 @@ class RenderChatItem extends StatelessWidget {
@override
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(
margin: const EdgeInsets.only(bottom: 10.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
!(data.isSelf ?? false)
? SizedBox(
height: 35.0,
width: 35.0,
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(20.0)),
child: NetworkOrAssetImage(imageUrl: data.faceUrl),
? GestureDetector(
onTap: () {
//
logger.e("点击了头像");
Get.toNamed('/vloger', arguments: {'memberId': data.sender});
},
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(),
@ -2176,13 +2190,13 @@ class RenderChatItem extends StatelessWidget {
child: Column(
crossAxisAlignment: !(data.isSelf ?? false) ? CrossAxisAlignment.start : CrossAxisAlignment.end,
children: [
Text(
data.friendRemark ?? data.nameCard ?? data.nickName ?? '未知昵称',
style: const TextStyle(color: Colors.grey, fontSize: 12.0),
),
const SizedBox(
height: 3.0,
),
// Text(
// displayName ?? '未知昵称',
// style: const TextStyle(color: Colors.grey, fontSize: 12.0),
// ),
// const SizedBox(
// height: 3.0,
// ),
Stack(
children: [
//

View File

@ -792,12 +792,12 @@ class _ChatState extends State<ChatGroup> with SingleTickerProviderStateMixin {
DateTime.now().millisecondsSinceEpoch,
)) {
// 3
final showLabel = Utils().formatChatTime(DateTime.now().millisecondsSinceEpoch ~/ 1000);
final showLabel = Utils.formatChatTime(DateTime.now().millisecondsSinceEpoch ~/ 1000);
final resMsg = await IMMessage().insertTimeLabel(showLabel, selfUserId);
messagesToInsert.add(resMsg.data);
} else {
//
final showLabel = Utils().formatChatTime(DateTime.now().millisecondsSinceEpoch ~/ 1000);
final showLabel = Utils.formatChatTime(DateTime.now().millisecondsSinceEpoch ~/ 1000);
final resMsg = await IMMessage().insertTimeLabel(showLabel, selfUserId);
messagesToInsert.add(resMsg.data);

View File

@ -3,16 +3,17 @@ library;
import 'package:flutter/material.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/im_message.dart';
import 'package:loopin/IM/im_result.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/models/conversation_type.dart';
import 'package:loopin/pages/chat/notify_controller/notify_no_friend_controller.dart';
import 'package:loopin/utils/snapshot.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_message.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 String _tips = '回复或关注对方之前,对方只能发送一条消息';
bool isLoading = false; //
bool hasMore = true; //
final RxBool _throttleFlag = false.obs; //
@ -152,13 +155,27 @@ class _ChatNoFriendState extends State<ChatNoFriend> with SingleTickerProviderSt
void isMyFriend() async {
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) {
controller.isFriend.value = isfd.data;
print(isfd.data);
} else {
controller.isFriend.value = false;
print(isfd.desc);
controller.followType.value = isfd.data?.first.followType ?? 0;
if ([3].contains(controller.followType.value)) {
controller.isFriend.value = true;
} else {
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),
child: RichTextUtil.getRichText(item.textElem?.text ?? '', color: !(item.isSelf ?? false) ? Colors.black : Colors.white), // emoj//
),
onLongPress: () {
contextMenuDialog();
},
// onLongPress: () {
// contextMenuDialog();
// },
),
),
),
@ -883,12 +900,12 @@ class _ChatNoFriendState extends State<ChatNoFriend> with SingleTickerProviderSt
DateTime.now().millisecondsSinceEpoch,
)) {
// 3
final showLabel = Utils().formatChatTime(DateTime.now().millisecondsSinceEpoch ~/ 1000);
final showLabel = Utils.formatChatTime(DateTime.now().millisecondsSinceEpoch ~/ 1000);
final resMsg = await IMMessage().insertTimeLabel(showLabel, selfUserId);
messagesToInsert.add(resMsg.data);
} else {
//
final showLabel = Utils().formatChatTime(DateTime.now().millisecondsSinceEpoch ~/ 1000);
final showLabel = Utils.formatChatTime(DateTime.now().millisecondsSinceEpoch ~/ 1000);
final resMsg = await IMMessage().insertTimeLabel(showLabel, selfUserId);
messagesToInsert.add(resMsg.data);
@ -1203,6 +1220,7 @@ class _ChatNoFriendState extends State<ChatNoFriend> with SingleTickerProviderSt
//
void contextMenuDialog() {
return;
showDialog(
context: context,
builder: (context) {
@ -1295,115 +1313,66 @@ class _ChatNoFriendState extends State<ChatNoFriend> with SingleTickerProviderSt
)),
),
actions: [
IconButton(
icon: const Icon(
Icons.more_horiz,
color: Colors.white,
),
onPressed: () async {
final paddingTop = MediaQuery.of(Get.context!).padding.top;
// IconButton(
// icon: const Icon(
// Icons.more_horiz,
// color: Colors.white,
// ),
// onPressed: () async {
// final paddingTop = MediaQuery.of(Get.context!).padding.top;
final selected = await showMenu(
context: Get.context!,
position: RelativeRect.fromLTRB(
double.infinity,
kToolbarHeight + paddingTop - 12,
8,
double.infinity,
),
color: FStyle.primaryColor,
elevation: 8,
items: [
PopupMenuItem<String>(
value: 'remark',
child: Row(
children: [
Icon(Icons.edit, color: Colors.white, size: 18),
SizedBox(width: 8),
Text(
'设置备注',
style: TextStyle(color: Colors.white),
),
],
),
),
PopupMenuItem<String>(
value: 'not',
child: Row(
children: [
Icon(Icons.do_not_disturb_on, color: Colors.white, size: 18),
SizedBox(width: 8),
Text(
'设为免打扰',
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),
),
],
),
),
],
);
// final selected = await showMenu(
// context: Get.context!,
// position: RelativeRect.fromLTRB(
// double.infinity,
// kToolbarHeight + paddingTop - 12,
// 8,
// double.infinity,
// ),
// color: FStyle.primaryColor,
// elevation: 8,
// items: [
// 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),
// ),
// ],
// ),
// ),
// ],
// );
if (selected != null) {
switch (selected) {
case 'remark':
print('点击了备注');
setRemark();
break;
case 'not':
print('点击了免打扰');
break;
case 'report':
print('点击了举报');
break;
case 'block':
print('点击了拉黑');
break;
case 'foucs':
print('点击了取关');
break;
}
}
},
),
// if (selected != null) {
// switch (selected) {
// case 'report':
// print('点击了举报');
// break;
// case 'block':
// print('点击了拉黑');
// break;
// }
// }
// },
// ),
],
),
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(
child: GestureDetector(
@ -1438,22 +1440,92 @@ class _ChatNoFriendState extends State<ChatNoFriend> with SingleTickerProviderSt
child: LayoutBuilder(
builder: (context, constraints) {
return Obx(() {
final showTipWidget = controller.followType.value;
const double tipHeight = 50; //
final msgWidgets = renderChatList().reversed.toList();
return ListView(
controller: chatController,
reverse: true,
padding: const EdgeInsets.all(10.0),
return Stack(
children: [
ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight - 20,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: msgWidgets,
ListView(
controller: chatController,
reverse: true,
padding: EdgeInsets.only(
top: [1, 2].contains(showTipWidget) ? tipHeight + 10 : 0, //
left: 10,
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: [
InkWell(
child: Icon(
voiceBtnEnable ? Icons.keyboard_outlined : Icons.contactless_outlined,
// voiceBtnEnable ? Icons.keyboard_outlined : Icons.contactless_outlined,
Icons.keyboard_outlined,
color: const Color(0xFF3B3B3B),
size: 30.0,
),
onTap: () {
setState(() {
toolbarEnable = false;
if (voiceBtnEnable) {
voiceBtnEnable = false;
editorFocusNode.requestFocus();
} else {
voiceBtnEnable = true;
if (editorFocusNode.hasFocus) {
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(
width: 10.0,
),
InkWell(
child: const Icon(
Icons.add_reaction_rounded,
color: Color(0xFF3B3B3B),
size: 30.0,
),
onTap: () {
handleEmojChooseState(0);
},
),
const SizedBox(
width: 8.0,
),
InkWell(
child: const Icon(
Icons.add,
color: Color(0xFF3B3B3B),
size: 30.0,
),
onTap: () {
handleEmojChooseState(1);
},
),
// InkWell(
// child: const Icon(
// Icons.add_reaction_rounded,
// color: Color(0xFF3B3B3B),
// size: 30.0,
// ),
// onTap: () {
// handleEmojChooseState(0);
// },
// ),
// const SizedBox(
// width: 8.0,
// ),
// InkWell(
// child: const Icon(
// Icons.add,
// color: Color(0xFF3B3B3B),
// size: 30.0,
// ),
// onTap: () {
// handleEmojChooseState(1);
// },
// ),
const SizedBox(
width: 8.0,
),
@ -1942,27 +2025,33 @@ class RenderChatItem extends StatelessWidget {
@override
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(
margin: const EdgeInsets.only(bottom: 10.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
!(data.isSelf ?? false)
? SizedBox(
height: 35.0,
width: 35.0,
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(20.0)),
child: Image.network(
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,
);
},
? GestureDetector(
onTap: () {
//
logger.e("点击了头像");
Get.toNamed('/vloger', arguments: {'memberId': data.sender});
//
},
child: SizedBox(
height: 35.0,
width: 35.0,
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(20.0)),
child: NetworkOrAssetImage(imageUrl: data.faceUrl),
),
),
)
@ -1973,13 +2062,13 @@ class RenderChatItem extends StatelessWidget {
child: Column(
crossAxisAlignment: !(data.isSelf ?? false) ? CrossAxisAlignment.start : CrossAxisAlignment.end,
children: [
Text(
data.friendRemark ?? data.nameCard ?? data.nickName ?? '未知昵称',
style: const TextStyle(color: Colors.grey, fontSize: 12.0),
),
const SizedBox(
height: 3.0,
),
// Text(
// displayName ?? '未知昵称',
// style: const TextStyle(color: Colors.grey, fontSize: 12.0),
// ),
// const SizedBox(
// height: 3.0,
// ),
Stack(
children: [
//
@ -2011,17 +2100,7 @@ class RenderChatItem extends StatelessWidget {
width: 35.0,
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(20.0)),
child: Image.network(
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,
);
},
),
child: NetworkOrAssetImage(imageUrl: data.faceUrl),
),
)
: const SizedBox.shrink(),

View File

@ -232,17 +232,18 @@ class ChatPageState extends State<ChatPage> {
if (selected != null) {
switch (selected) {
case 'group':
print('点击了发起群聊');
logger.w('点击了发起群聊');
Get.toNamed('/group');
break;
case 'friend':
print('点击了添加朋友');
logger.w('点击了添加朋友');
break;
case 'scan':
print('点击了扫一扫');
logger.w('点击了扫一扫');
ScanUtil.openScanner(onResult: (code) {
print('扫码结果:$code');
logger.w('扫码结果:$code');
Get.snackbar('扫码成功', code);
//
//
});
break;
}
@ -279,159 +280,174 @@ class ChatPageState extends State<ChatPage> {
Text('群聊'),
],
),
Column(
children: [
SvgPicture.asset(
'assets/images/svg/kefu.svg',
height: 36.0,
width: 36.0,
),
Text('互关'),
],
GestureDetector(
onTap: () {
//
Get.toNamed('/eachFlow');
},
child: Column(
children: [
SvgPicture.asset(
'assets/images/svg/kefu.svg',
height: 36.0,
width: 36.0,
),
Text('互关'),
],
),
),
Column(
children: [
SvgPicture.asset(
'assets/images/svg/comment.svg',
height: 36.0,
width: 36.0,
),
Text('粉丝'),
],
GestureDetector(
onTap: () {
//
Get.toNamed('/fans');
},
child: Column(
children: [
SvgPicture.asset(
'assets/images/svg/comment.svg',
height: 36.0,
width: 36.0,
),
Text('粉丝'),
],
),
),
Column(
children: [
SvgPicture.asset(
'assets/images/svg/comment.svg',
height: 36.0,
width: 36.0,
),
Text('关注'),
],
GestureDetector(
onTap: () {
//
Get.toNamed('/flow');
},
child: Column(
children: [
SvgPicture.asset(
'assets/images/svg/comment.svg',
height: 36.0,
width: 36.0,
),
Text('关注'),
],
),
),
],
),
),
Expanded(
child: RefreshIndicator(
backgroundColor: Colors.white,
color: Color(0xFFFF5000),
displacement: 10.0,
onRefresh: handleRefresh,
child: Obx(() {
final chatList = controller.chatList;
child: Obx(
() {
final chatList = controller.chatList;
return ListView.builder(
shrinkWrap: true,
physics: BouncingScrollPhysics(),
itemCount: chatList.length,
itemBuilder: (context, index) {
final isNoFriend = chatList[index].conversation.conversationGroupList?.contains(ConversationType.noFriend.name) ?? false;
final isAdmin = chatList[index].isCustomAdmin != null && chatList[index].isCustomAdmin != '0';
return ListView.builder(
shrinkWrap: true,
physics: BouncingScrollPhysics(),
itemCount: chatList.length,
itemBuilder: (context, index) {
// logger.w(chatList[index].conversation.conversationGroupList);
// 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);
return Ink(
// color: chatList[index]['topMost'] == null ? Colors.white : Colors.grey[100], //
child: InkWell(
splashColor: Colors.grey[200],
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 10.0),
child: Row(
spacing: 10.0,
children: <Widget>[
//
ClipOval(
child: NetworkOrAssetImage(
imageUrl: chatList[index].faceUrl,
width: 50,
height: 50,
),
// logger.e(chatList[index].isCustomAdmin);
return Ink(
// color: chatList[index]['topMost'] == null ? Colors.white : Colors.grey[100], //
child: InkWell(
splashColor: Colors.grey[200],
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 10.0),
child: Row(
spacing: 10.0,
children: <Widget>[
//
ClipOval(
child: NetworkOrAssetImage(
imageUrl: chatList[index].faceUrl,
width: 50,
height: 50,
),
),
//
Expanded(
child: Column(
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,
//
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
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),
),
Text(
chatList[index].conversation.showName ?? '未知',
style: TextStyle(
fontSize: (isAdmin || isNoFriend) ? 20 : 16,
fontWeight: (isAdmin || isNoFriend) ? FontWeight.bold : FontWeight.normal),
),
const SizedBox(height: 5.0),
//
Visibility(
visible: (chatList[index].conversation.unreadCount ?? 0) > 0,
child: FStyle.badge(chatList[index].conversation.unreadCount ?? 0),
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,
),
],
),
Visibility(
visible: (isAdmin || isNoFriend),
child: const Icon(
Icons.arrow_forward_ios,
color: Colors.blueGrey,
size: 14.0,
),
//
Column(
crossAxisAlignment: CrossAxisAlignment.end,
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]);
},
),
);
},
);
},
),
),
],
),

View 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 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),
),
),
],
),
),
);
},
);
},
),
),
],
),
),
);
}
}

View File

@ -111,7 +111,7 @@ class NofriendState extends State<Nofriend> with SingleTickerProviderStateMixin
),
title: Text(
'陌生人消息',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
actions: [],
),
@ -137,6 +137,8 @@ class NofriendState extends State<Nofriend> with SingleTickerProviderStateMixin
final isNoFriend = conv.conversationGroupList?.contains(ConversationType.noFriend.name) ?? false;
return Ink(
key: ValueKey(conv.conversationID),
// color: conv['topMost'] == null ? Colors.white : Colors.grey[100], //
child: InkWell(
splashColor: Colors.grey[200],

View File

@ -20,11 +20,19 @@ class NotifyNoFriendController extends GetxController {
if (index != -1) {
convList[index] = conversation;
convList.refresh();
// convList.value = List.from(convList)..[index] = conversation;
} else {
// id查询会话检测是否存在
logger.e('会话不存在,更新会话失败');
// insert进去 convlist做去重处理
logger.e('当前会话数据未拉取到,执行插入逻辑');
// 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();
}
}

View File

@ -123,7 +123,7 @@ class _GoodsState extends State<Goods> {
"price": shopObj['price'],
"title": shopObj['describe'],
"url": shopObj['pic'],
"sell": Utils().graceNumber(int.parse(shopObj['sales'] ?? '0')),
"sell": Utils.graceNumber(int.parse(shopObj['sales'] ?? '0')),
});
final res = await IMMessage().createCustomMessage(
data: makeJson,
@ -389,7 +389,7 @@ class _GoodsState extends State<Goods> {
),
Text(
// '已售${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),
),
],

View 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),
),
),
),
),
);
}
}

View File

@ -88,7 +88,7 @@ class _IndexPageState extends State<IndexPage> with SingleTickerProviderStateMix
]),
),
Text(
'已售${Utils().graceNumber(int.parse(item['sales'] ?? '0'))}',
'已售${Utils.graceNumber(int.parse(item['sales'] ?? '0'))}',
style: TextStyle(color: Colors.grey, fontSize: 10.0),
),
],
@ -254,8 +254,9 @@ class _IndexPageState extends State<IndexPage> with SingleTickerProviderStateMix
header: ClassicHeader(
dragText: '下拉刷新',
armedText: '释放刷新',
readyText: '刷新中...',
processingText: '刷新完成',
readyText: '加载中...',
processingText: '加载中...',
processedText: '加载完成',
messageText: '最后更新于 %T',
),
child: CustomScrollView(

304
lib/pages/my/fans.dart Normal file
View 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
View 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),
),
),
],
),
),
);
},
);
},
),
),
],
),
),
);
}
}

View File

@ -369,7 +369,10 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
padding: const EdgeInsets.all(10.0),
child: Column(
children: [
const SizedBox(height: 10),
Obx(() => _buildStatsCard()),
const SizedBox(height: 10),
Obx(() => _buildInfoDesc(context)),
const SizedBox(height: 10.0),
_buildOrderCard(context),
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) {
final listToShow = tabIndex == 0 ? items : favoriteItems;
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() {
return Container(
decoration: BoxDecoration(
@ -836,21 +772,46 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Column(children: [Text('9999', style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold)), SizedBox(height: 3.0), Text('获赞')]),
Column(children: [
Text('${followInfo.value?.mutualFollowersCount ?? 0}', style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold)),
SizedBox(height: 3.0),
Text('互关')
]),
Column(children: [
Text('${followInfo.value?.followingCount ?? 0}', style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold)),
SizedBox(height: 3.0),
Text('关注')
]),
Column(children: [
Text('${followInfo.value?.followersCount ?? 0}', style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold)),
SizedBox(height: 3.0),
Text('粉丝')
]),
GestureDetector(
onTap: () async {
//
await Get.toNamed('/eachFlow');
refreshData();
},
child: Column(children: [
Text('${followInfo.value?.mutualFollowersCount ?? 0}', style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold)),
SizedBox(height: 3.0),
Text('互关')
]),
),
GestureDetector(
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('粉丝'),
],
),
)
],
)),
);

View 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),
),
),
],
),
),
);
},
);
},
),
),
],
),
),
);
}
}

View File

@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:loopin/IM/controller/im_user_info_controller.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/styles/index.dart';
import 'package:loopin/utils/index.dart';
@ -321,9 +322,13 @@ class _UserInfoState extends State<UserInfo> {
final imageUrl = userInfoController.customInfo['coverBg'];
return GestureDetector(
onTap: () => pickCover(context),
child: Image(
image: (imageUrl != null && imageUrl.isNotEmpty) ? NetworkImage(imageUrl) : const AssetImage('assets/images/pic2.jpg') as ImageProvider,
fit: BoxFit.cover,
// child: Image(
// image: (imageUrl != null && imageUrl.isNotEmpty) ? NetworkImage(imageUrl) : const AssetImage('assets/images/pic2.jpg') as ImageProvider,
// fit: BoxFit.cover,
// ),
child: NetworkOrAssetImage(
imageUrl: imageUrl,
placeholderAsset: 'assets/images/bk.jpg',
),
);
}),
@ -423,7 +428,7 @@ class _UserInfoState extends State<UserInfo> {
maxLines: 1,
);
} else {
String wxText = val['unionId'] == null || val['unionId'] == '' ? '未授权' : '已授权';
String wxText = val['openId'] == null || val['openId'] == '' ? '未授权' : '已授权';
return Row(
children: [
Spacer(),
@ -473,9 +478,13 @@ class _UserInfoState extends State<UserInfo> {
child: CircleAvatar(
radius: 60,
backgroundColor: Colors.white,
child: CircleAvatar(
radius: 57,
backgroundImage: avatar.isNotEmpty ? NetworkImage(avatar) : const AssetImage('assets/images/avatar/img11.jpg') as ImageProvider,
child: ClipOval(
child: NetworkOrAssetImage(
imageUrl: avatar,
width: double.infinity,
height: double.infinity,
fit: BoxFit.cover,
),
),
),
),

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.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/components/custom_sticky_header.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');
}
},
child:Scaffold(
backgroundColor: const Color(0xFFFAF6F9),
body: Obx(() {
return NestedScrollViewPlus(
controller: scrollController,
physics: shouldFixHeader.value ? const OnlyDownScrollPhysics(parent: AlwaysScrollableScrollPhysics()) : const AlwaysScrollableScrollPhysics(),
overscrollBehavior: OverscrollBehavior.outer,
headerSliverBuilder: (context, innerBoxIsScrolled) {
return [
SliverAppBar(
backgroundColor: Colors.transparent,
surfaceTintColor: Colors.transparent,
expandedHeight: 180.0,
collapsedHeight: 120.0,
pinned: true,
stretch: true,
child: Scaffold(
backgroundColor: const Color(0xFFFAF6F9),
body: Obx(() {
return NestedScrollViewPlus(
controller: scrollController,
physics: shouldFixHeader.value ? const OnlyDownScrollPhysics(parent: AlwaysScrollableScrollPhysics()) : const AlwaysScrollableScrollPhysics(),
overscrollBehavior: OverscrollBehavior.outer,
headerSliverBuilder: (context, innerBoxIsScrolled) {
return [
SliverAppBar(
backgroundColor: Colors.transparent,
surfaceTintColor: Colors.transparent,
expandedHeight: 180.0,
collapsedHeight: 120.0,
pinned: true,
stretch: true,
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () {
Get.back(result: {
icon: const Icon(Icons.arrow_back),
onPressed: () {
Get.back(result: {
'returnTo': '/',
'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 {
logger.i('触发 stretch 拉伸');
//
},
flexibleSpace: Obx(() {
userInfo.value;
return _buildFlexibleSpace();
}),
),
SliverToBoxAdapter(
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)),
],
SliverToBoxAdapter(
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(
pinned: true,
delegate: CustomStickyHeader(
child: PreferredSize(
preferredSize: const Size.fromHeight(48.0),
child: Container(
color: Colors.white,
child: TabBar(
controller: tabController,
tabs: tabList.map((item) {
return Tab(
child: Badge.count(
backgroundColor: Colors.red,
count: item['badge'] ?? 0,
isLabelVisible: item['badge'] != null,
alignment: Alignment.topRight,
offset: const Offset(14, -6),
child: Text(item['name'], style: const TextStyle(fontWeight: FontWeight.bold)),
),
);
}).toList(),
isScrollable: true, //
tabAlignment: TabAlignment.start,
overlayColor: WidgetStateProperty.all(Colors.transparent),
unselectedLabelColor: Colors.black87,
labelColor: Colors.black,
indicator: const UnderlineTabIndicator(borderSide: BorderSide(color: Colors.transparent, width: 2.0)),
indicatorSize: TabBarIndicatorSize.label,
unselectedLabelStyle: const TextStyle(fontSize: 16.0, fontFamily: 'Microsoft YaHei'),
labelStyle: const TextStyle(fontSize: 18.0, fontFamily: 'Microsoft YaHei', fontWeight: FontWeight.bold),
dividerHeight: 0,
padding: const EdgeInsets.symmetric(horizontal: 10.0),
labelPadding: const EdgeInsets.symmetric(horizontal: 15.0),
SliverPersistentHeader(
pinned: true,
delegate: CustomStickyHeader(
child: PreferredSize(
preferredSize: const Size.fromHeight(48.0),
child: Container(
color: Colors.white,
child: TabBar(
controller: tabController,
tabs: tabList.map((item) {
return Tab(
child: Badge.count(
backgroundColor: Colors.red,
count: item['badge'] ?? 0,
isLabelVisible: item['badge'] != null,
alignment: Alignment.topRight,
offset: const Offset(14, -6),
child: Text(item['name'], style: const TextStyle(fontWeight: FontWeight.bold)),
),
);
}).toList(),
isScrollable: true, //
tabAlignment: TabAlignment.start,
overlayColor: WidgetStateProperty.all(Colors.transparent),
unselectedLabelColor: Colors.black87,
labelColor: Colors.black,
indicator: const UnderlineTabIndicator(borderSide: BorderSide(color: Colors.transparent, width: 2.0)),
indicatorSize: TabBarIndicatorSize.label,
unselectedLabelStyle: const TextStyle(fontSize: 16.0, fontFamily: 'Microsoft YaHei'),
labelStyle: const TextStyle(fontSize: 18.0, fontFamily: 'Microsoft YaHei', fontWeight: FontWeight.bold),
dividerHeight: 0,
padding: const EdgeInsets.symmetric(horizontal: 10.0),
labelPadding: const EdgeInsets.symmetric(horizontal: 15.0),
),
),
),
),
),
),
];
},
body: TabBarView(
controller: tabController,
children: [
// Tab 1:
Obx(() => _buildGridTab(0)),
],
),
);
}),
),
];
},
body: TabBarView(
controller: tabController,
children: [
// Tab 1:
Obx(() => _buildGridTab(0)),
],
),
);
}),
),
);
}
@ -387,7 +388,6 @@ class MyPageState extends State<Vloger> with SingleTickerProviderStateMixin {
final double currentHeight = constraints.maxHeight;
double ratio = (currentHeight - minHeight) / (maxHeight - minHeight);
ratio = ratio.clamp(0.0, 1.0);
String coverBg = userInfo.value.customInfo?['coverBg'] ?? '';
return Stack(
fit: StackFit.expand,
children: [
@ -395,8 +395,9 @@ class MyPageState extends State<Vloger> with SingleTickerProviderStateMixin {
child: Opacity(
opacity: 1.0,
child: NetworkOrAssetImage(
imageUrl: coverBg,
imageUrl: userInfo.value.customInfo?['coverBg'],
width: double.infinity,
placeholderAsset: 'assets/images/bk.jpg',
),
),
),
@ -410,7 +411,11 @@ class MyPageState extends State<Vloger> with SingleTickerProviderStateMixin {
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
ClipOval(
child: NetworkOrAssetImage(imageUrl: userInfo.value.faceUrl),
child: NetworkOrAssetImage(
imageUrl: userInfo.value.faceUrl,
width: 80,
height: 80,
),
),
const SizedBox(width: 15.0),
Expanded(
@ -540,19 +545,42 @@ class MyPageState extends State<Vloger> with SingleTickerProviderStateMixin {
child: child,
);
},
child: [1, 3].contains(followed.value)
child: [1, 2, 3].contains(followed.value)
? Row(
key: const ValueKey('followed'),
children: [
Expanded(
child: ElevatedButton(
onPressed: () async {
print('点击已关注');
final res = await ImService.instance.unfollowUser(userIDList: [vlogerId]);
if (res.success) {
// 1032
followed.value = followed.value == 1 ? 0 : 2;
// group
logger.w('点击已关注/已回关:${followed.value}');
final ctl = Get.find<ChatController>();
if (followed.value == 2) {
//
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) {
// 1032
followed.value = followed.value == 1 ? 0 : 2;
ctl.mergeNoFriend(conversationID: 'c2c_${userInfo.value.userID}');
}
}
},
style: ElevatedButton.styleFrom(
@ -565,16 +593,18 @@ class MyPageState extends State<Vloger> with SingleTickerProviderStateMixin {
),
child: Text(followed.value == 1
? '已关注'
: followed.value == 3
? '互关'
: '未知状态'),
: followed.value == 2
? '回关'
: followed.value == 3
? '已互关'
: '未知状态'),
),
),
const SizedBox(width: 12),
Expanded(
child: ElevatedButton(
onPressed: () async {
print('私信');
logger.w('私信');
//
final res = await ImService.instance.getConversation(conversationID: 'c2c_$vlogerId');
V2TimConversation conversation = res.data;
@ -614,12 +644,12 @@ class MyPageState extends State<Vloger> with SingleTickerProviderStateMixin {
child: ElevatedButton.icon(
onPressed: () async {
// 02
print('点击关注');
logger.w('点击关注');
final res = await ImService.instance.followUser(userIDList: [vlogerId]);
if (res.success) {
followed.value = followed.value == 0 ? 1 : 3;
if (followed.value == 3) {
// 3noFriend会话组
// 3noFriend会话组
final res = await ImService.instance.getConversation(conversationID: 'c2c_$vlogerId');
if (res.success) {
V2TimConversation conversation = res.data;
@ -629,14 +659,13 @@ class MyPageState extends State<Vloger> with SingleTickerProviderStateMixin {
groupName: conversation.conversationGroupList!.first!,
conversationIDList: [conversation.conversationID],
);
//
}
}
}
}
},
icon: const Icon(Icons.add),
label: const Text('关注'),
label: Text('关注'),
style: ElevatedButton.styleFrom(
backgroundColor: FStyle.primaryColor,
foregroundColor: Colors.white,

View File

@ -1,13 +1,11 @@
import 'package:flutter/material.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/service/http.dart';
import 'package:loopin/api/common_api.dart';
import 'package:loopin/utils/index.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';
class SearchResultPage extends StatefulWidget {
@ -198,7 +196,7 @@ class _SearchResultPageState extends State<SearchResultPage> with SingleTickerPr
final data = {
'title': _searchQuery,
'size': _pageSize,
'type': tabIndex+1, // 1-2-3-
'type': tabIndex + 1, // 1-2-3-
'current': currentPage,
};
final res = await Http.post(CommonApi.aggregationSearchApi, data: data);
@ -270,75 +268,77 @@ class _SearchResultPageState extends State<SearchResultPage> with SingleTickerPr
}
//
onFocusBtnClick (user,index) async {
final vlogerId = user['id'];
final doIFollowVloger = user['doIFollowVloger'];
print('是否关注此用户------------->${doIFollowVloger}');
if (doIFollowVloger == null || doIFollowVloger == false) {
final res = await ImService.instance.followUser(userIDList: [vlogerId]);
print('关注结果------------->${res.success}');
if (res.success) {
setState(() {
_userResults[index]['doIFollowVloger'] = true;
});
}
}else{
final res = await ImService.instance.unfollowUser(userIDList: [vlogerId]);
print('取消关注结果------------->${res.success}');
if (res.success) {
setState(() {
_userResults[index]['doIFollowVloger'] = false;
});
}
}
onFocusBtnClick(user, index) async {
final vlogerId = user['id'];
final doIFollowVloger = user['doIFollowVloger'];
print('是否关注此用户------------->$doIFollowVloger');
print('此用户UserId------------->$vlogerId');
if (doIFollowVloger == false) {
final res = await ImService.instance.followUser(userIDList: [vlogerId]);
print('关注结果------------->${res.success}');
if (res.success) {
setState(() {
_userResults[index]['doIFollowVloger'] = !_userResults[index]['doIFollowVloger'];
});
}
} else {
final res = await ImService.instance.followUser(userIDList: [vlogerId]);
print('取消关注结果------------->${res.success}');
if (res.success) {
setState(() {
_userResults[index]['doIFollowVloger'] = !_userResults[index]['doIFollowVloger'];
});
}
}
}
//
Widget _buildLoadMoreWidget(int tabIndex) {
if (_isLoadingMore) {
return const Center(
child: Padding(
padding: EdgeInsets.symmetric(vertical: 8.0), //
child: SizedBox(
width: 20,
height: 20,
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,
if (_isLoadingMore) {
return const Center(
child: Padding(
padding: EdgeInsets.symmetric(vertical: 8.0), //
child: SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
),
),
),
);
}
);
}
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的数据
List<dynamic> _getCurrentResults(int tabIndex) {
@ -476,9 +476,7 @@ class _SearchResultPageState extends State<SearchResultPage> with SingleTickerPr
return NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification scrollInfo) {
if (scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent &&
!_isLoadingMore &&
_videoHasMore) {
if (scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent && !_isLoadingMore && _videoHasMore) {
_loadMoreData(0);
}
return false;
@ -580,14 +578,14 @@ class _SearchResultPageState extends State<SearchResultPage> with SingleTickerPr
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.end, //
children: [
Text(
Text(
video['title']?.toString() ?? '无标题',
style: const TextStyle(
fontSize: 11,
fontWeight: FontWeight.w500,
),
maxLines: 1, // 1
overflow: TextOverflow.ellipsis, //
maxLines: 1, // 1
overflow: TextOverflow.ellipsis, //
),
const SizedBox(height: 4),
Row(
@ -605,9 +603,7 @@ class _SearchResultPageState extends State<SearchResultPage> with SingleTickerPr
)
: null,
),
child: video['avatar'] == null || video['avatar'].toString().isEmpty
? const Icon(Icons.person, size: 10, color: Colors.grey)
: null,
child: video['avatar'] == null || video['avatar'].toString().isEmpty ? const Icon(Icons.person, size: 10, color: Colors.grey) : null,
),
const SizedBox(width: 4),
Expanded(
@ -627,7 +623,7 @@ class _SearchResultPageState extends State<SearchResultPage> with SingleTickerPr
const Icon(Icons.favorite_border, size: 10, color: Colors.grey),
const SizedBox(width: 2),
Text(
Utils().formatLikeCount(video['likeCounts'] ?? 0),
Utils.graceNumber(video['likeCounts'] ?? 0),
style: const TextStyle(
fontSize: 9,
color: Colors.grey,
@ -688,9 +684,7 @@ class _SearchResultPageState extends State<SearchResultPage> with SingleTickerPr
return NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification scrollInfo) {
if (scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent &&
!_isLoadingMore &&
_productHasMore) {
if (scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent && !_isLoadingMore && _productHasMore) {
_loadMoreData(1);
}
return false;
@ -755,97 +749,97 @@ class _SearchResultPageState extends State<SearchResultPage> with SingleTickerPr
return Container();
}
Widget _buildProductItem(Map<String, dynamic> product) {
return GestureDetector(
onTap: () {
//
print('点击了商品: ${product['id']}');
Get.toNamed('/goods', arguments: {'goodsId': product['id']});
},
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(_itemCornerRadius),
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
blurRadius: 4,
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),
Widget _buildProductItem(Map<String, dynamic> product) {
return GestureDetector(
onTap: () {
//
print('点击了商品: ${product['id']}');
Get.toNamed('/goods', arguments: {'goodsId': product['id']});
},
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(_itemCornerRadius),
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
blurRadius: 4,
offset: const Offset(0, 2),
),
child: Container(
width: double.infinity,
color: Colors.grey[200],
child: product['pic'] != null
? Image.network(
product['pic'].toString(),
fit: BoxFit.cover,
)
: Container(
height: 110,
child: const Center(
child: Icon(Icons.shopping_bag, color: Colors.grey, size: 32),
),
),
),
),
// -
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,
],
),
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: product['pic'] != null
? Image.network(
product['pic'].toString(),
fit: BoxFit.cover,
)
: SizedBox(
height: 110,
child: const Center(
child: Icon(Icons.shopping_bag, color: Colors.grey, size: 32),
),
),
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
Widget _buildUserTab() {
@ -855,9 +849,7 @@ class _SearchResultPageState extends State<SearchResultPage> with SingleTickerPr
return NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification scrollInfo) {
if (scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent &&
!_isLoadingMore &&
_userHasMore) {
if (scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent && !_isLoadingMore && _userHasMore) {
_loadMoreData(2);
}
return false;
@ -872,7 +864,7 @@ class _SearchResultPageState extends State<SearchResultPage> with SingleTickerPr
return _buildLoadMoreWidget(2);
}
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) {
//
bool isFollowing = user['doIFollowVloger'] ?? false;
print('111111111111111111111111${isFollowing}');
print('111111111111111111111111$isFollowing');
return Container(
margin: const EdgeInsets.only(bottom: 8),
padding: const EdgeInsets.all(10),
@ -912,9 +904,7 @@ class _SearchResultPageState extends State<SearchResultPage> with SingleTickerPr
)
: null,
),
child: user['avatar'] == null
? const Icon(Icons.person, color: Colors.grey, size: 20)
: null,
child: user['avatar'] == null ? const Icon(Icons.person, color: Colors.grey, size: 20) : null,
),
const SizedBox(width: 10),
Expanded(
@ -951,9 +941,7 @@ class _SearchResultPageState extends State<SearchResultPage> with SingleTickerPr
borderRadius: BorderRadius.circular(16),
),
//
side: isFollowing
? BorderSide(color: Colors.grey[400]!, width: 0.5)
: BorderSide.none,
side: isFollowing ? BorderSide(color: Colors.grey[400]!, width: 0.5) : BorderSide.none,
),
child: Text(
isFollowing ? '已关注' : '关注',

View File

@ -675,7 +675,7 @@ class _RecommendModuleState extends State<RecommendModule> {
'size': pageSize,
});
final data = res['data'];
logger.d('关注用户的视频列表:$data');
// logger.d('关注用户的视频列表:$data');
if (data == null || (data is List && data.isEmpty)) {
return;
}
@ -698,7 +698,7 @@ class _RecommendModuleState extends State<RecommendModule> {
if (videos.isNotEmpty) {
page++;
}
logger.i('视频数据列表------------------->$videoList');
// logger.i('视频数据列表------------------->$videoList');
//
player.open(
@ -1247,17 +1247,14 @@ class _RecommendModuleState extends State<RecommendModule> {
),
],
),
onTap: ()async {
player.pause();
//
final result = await Get.toNamed(
'/report',
arguments: videoList[videoModuleController
.videoPlayIndex.value]);
if (result != null) {
player.play();
};
},
onTap: () async {
player.pause();
//
final result = await Get.toNamed('/report', arguments: videoList[videoModuleController.videoPlayIndex.value]);
if (result != null) {
player.play();
}
},
),
],
),

View File

@ -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/noFriend.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/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/setting.dart';
import 'package:loopin/pages/my/user_info.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/search-result.dart';
import 'package:loopin/pages/video/commonVideo.dart';
import 'package:loopin/pages/video/report.dart';
import '../layouts/index.dart';
/* 引入路由页面 */
@ -42,8 +46,8 @@ final Map<String, Widget> routes = {
'/vloger': const Vloger(),
'/report': const ReportPage(),
'/videoDetail': const VideoDetailPage(),
'/search': const SearchPage(),
'/search-result': const SearchResultPage(),
'/search': const SearchPage(),
'/search-result': const SearchResultPage(),
//settins
'/setting': const Setting(),
'/userInfo': const UserInfo(),
@ -56,6 +60,12 @@ final Map<String, Widget> routes = {
'/noFriend': const Nofriend(),
'/system': const System(),
'/interaction': const Interaction(),
//
'/fans': const Fans(),
'/flow': const Flowing(),
'/eachFlow': const MutualFollowers(),
//
'/group': const StartGroupChatPage(),
};
final List<GetPage> routeList = routes.entries

View File

@ -10,6 +10,7 @@ class HttpConfig {
// baseUrl: 'http://cjh.wuzhongjie.com.cn',
// baseUrl: 'http://82.156.121.2:8880',
baseUrl: 'http://192.168.1.65:8880',
// baseUrl: 'http://192.168.1.22:8080',
// connectTimeout: Duration(seconds: 30),
// receiveTimeout: Duration(seconds: 30),

View File

@ -5,26 +5,34 @@ import 'package:flutter/material.dart';
class FStyle {
// Badge
static badge(int count, {
Color color = Colors.red,
bool isdot = false,
double height = 16.0,
double width = 16.0
}) {
static badge(int count, {Color color = Colors.red, bool isdot = false, double height = 16.0, double width = 16.0}) {
final num = count > 99 ? '99+' : count;
return Container(
alignment: Alignment.center,
height: isdot ? height / 2 : height,
width: isdot ? width / 2 : width,
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 white = Colors.white;
static const c999 = Color(0xFF999999);

View File

@ -113,7 +113,7 @@ class Utils {
}
//
String graceNumber(int number) {
static String graceNumber(int number) {
if (number == 0) return "0";
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$'), '');
}
//
String formatChatTime(int timestamp) {
static String formatChatTime(int timestamp) {
// timestamp DateTime
DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000);
@ -189,31 +189,29 @@ class Utils {
'${messageTime.minute.toString().padLeft(2, '0')}';
}
String _formatHourMinute(DateTime dt) {
static String _formatHourMinute(DateTime dt) {
final hour = dt.hour.toString().padLeft(2, '0');
final minute = dt.minute.toString().padLeft(2, '0');
return "$hour:$minute";
}
String _weekdayLabel(int weekday) {
static String _weekdayLabel(int weekday) {
const weekdays = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"];
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 '未知状态';
}
}
}

View File

@ -1,6 +1,5 @@
import 'dart:convert';
import 'package:loopin/IM/push_service.dart';
import 'package:loopin/models/notify_message.type.dart';
import 'package:loopin/models/summary_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]';
final sum = msg.cloudCustomData;
final elment = msg.customElem; //
logger.w('解析自定义消息:${msg.toJson()}');
logger.w('解析element${elment?.desc ?? 'summary_error'}');
// logger.w('解析自定义消息:${msg.toJson()}');
// logger.w('解析element${elment?.desc ?? 'summary_error'}');
try {
switch (sum) {
case SummaryType.hongbao: