修复安卓端口会话,用户基本信息,异常问题

This commit is contained in:
abu 2025-08-26 17:38:59 +08:00
parent 53f368938a
commit d89bbf3086
36 changed files with 2275 additions and 768 deletions

View File

@ -13,24 +13,31 @@ class ChatController extends GetxController {
final chatList = <ConversationViewModel>[].obs;
void initChatData() {
chatList.value = <ConversationViewModel>[];
// chatList.value = <ConversationViewModel>[];
chatList.clear();
nextSeq.value = '0';
isFinished.value = false;
}
//
void getConversationList() async {
logger.e('开始获取会话列表数据');
if (isFinished.value) {
//
logger.e('会话触底无数据');
return;
}
try {
final res = await ImService.instance.getConversationList(nextSeq.value, count.value);
if (!res.success || res.data == null) return;
if (!res.success || res.data == null) {
logger.e('获取会话失败::${res.desc}');
return;
}
final List<ConversationViewModel> convList = res.data;
for (var conv in convList) {
logger.i('基本会话: ${conv.conversation.toJson()}, 会话ID: ${conv.conversation.conversationID}');
logger.w('基本会话: ${conv.conversation.toJson()}, 会话ID: ${conv.conversation.conversationID}');
}
chatList.addAll(convList);
@ -39,6 +46,9 @@ class ChatController extends GetxController {
if (!hasNoFriend) {
getNoFriendData();
}
} catch (e) {
logger.e('获取会话异常::$e');
}
}
///
@ -102,7 +112,7 @@ class ChatController extends GetxController {
}
/// getConversationListByFilter
// void getConversationList() async {
// void filterConversationList() async {
// final res = await ImService.instance.getConversationListByFilter(
// filter: V2TimConversationFilter(conversationGroup: null),
// nextSeq: nextSeq.value,

View File

@ -59,7 +59,7 @@ class ImUserInfoController extends GetxController {
init(updatedUserInfo.data);
}
} catch (e) {
print('刷新用户信息失败: $e');
logger.e('刷新用户信息失败: $e');
}
}

View File

@ -16,6 +16,10 @@ class GlobalBadge extends GetxController {
/// add/remove
late final V2TimConversationListener _listener;
void rest() {
totalUnread.value = 0;
}
@override
void onInit() {
super.onInit();
@ -34,12 +38,16 @@ class GlobalBadge extends GetxController {
onConversationChanged: (List<V2TimConversation> conversationList) async {
logger.w('会话变更:会话分组:${conversationList.first.conversationGroupList},会话内容${conversationList.first.toLogString()}');
final ctl = Get.find<ChatController>();
logger.w('当前会话列表内容:${ctl.chatList.toJson()}');
final updatedIds = conversationList.map((e) => e.conversationID).toSet();
logger.w('要变更的会话id$updatedIds');
for (int i = 0; i < ctl.chatList.length; i++) {
final chatItem = ctl.chatList[i];
logger.w('需要更新的ID:${chatItem.conversation.conversationID}');
if (updatedIds.contains(chatItem.conversation.conversationID)) {
logger.w('找到的chatList的ID:${chatItem.conversation.conversationID}');
final updatedConv = conversationList.firstWhere(
(c) => c.conversationID == chatItem.conversation.conversationID,
orElse: () => V2TimConversation(conversationID: ''),
@ -57,10 +65,20 @@ class GlobalBadge extends GetxController {
chatItem.conversation.unreadCount = unread.data; //
} else {
//
logger.w('不需要分组的会话,正常更新');
chatItem.conversation = updatedConv;
update();
}
}
}
//
if (ctl.chatList.isEmpty) {
//
logger.w('重新获取会话');
ctl.initChatData();
ctl.getConversationList();
}
//
ctl.chatList.sort((a, b) {
final atime = a.conversation.lastMessage?.timestamp ?? 0;
@ -117,7 +135,7 @@ class GlobalBadge extends GetxController {
}
}
} else {
logger.w('不需要进行分组');
logger.w('新会话不需分组');
}
}
@ -129,9 +147,21 @@ class GlobalBadge extends GetxController {
Get.find<TabBarController>().setBadge(TabType.chat, totalUnread.value);
final to = res.data;
logger.i('初始化未读消息数$to');
} else {
//
logger.e('获取初始化未读数失败:${res.desc},重新补偿获取');
Future.delayed(Duration(seconds: 1), handAndroid);
}
}
///
handAndroid() {
_initUnreadCount();
final ctl = Get.find<ChatController>();
ctl.initChatData();
ctl.getConversationList();
}
///
void _addListener() {
TencentImSDKPlugin.v2TIMManager.getConversationManager().addConversationListener(listener: _listener);
@ -146,6 +176,8 @@ class GlobalBadge extends GetxController {
///
@override
void onClose() {
logger.i(_listener);
logger.i('移除global未读监听器');
TencentImSDKPlugin.v2TIMManager.getConversationManager().removeConversationListener(listener: _listener);
super.onClose();
}

View File

@ -1,4 +1,9 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:logger/logger.dart';
import 'package:loopin/IM/im_service.dart';
import 'package:loopin/controller/video_module_controller.dart';
import 'package:loopin/utils/common.dart';
import 'package:tencent_cloud_chat_sdk/enum/V2TimSDKListener.dart';
import 'package:tencent_cloud_chat_sdk/enum/log_level_enum.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_user_full_info.dart';
@ -9,6 +14,18 @@ final logger = Logger();
class ImCore {
static bool _isInitialized = false;
static Future<void> handleLogout() async {
final loginRes = await ImService.instance.logout();
if (loginRes.success) {
//
Common.logout();
//
final videoController = Get.find<VideoModuleController>();
videoController.init();
Get.toNamed('/login');
}
}
static Future<bool> init({required int sdkAppId}) async {
if (_isInitialized) return true;
@ -20,7 +37,18 @@ class ImCore {
logger.i("IM连接成功");
},
onConnectFailed: (code, error) => logger.e("IM连接失败: $code $error"),
onKickedOffline: () => logger.w("IM被踢下线"),
onKickedOffline: () {
logger.w("IM被踢下线");
Get.snackbar(
'提示',
'您的帐号已在其他处登录',
duration: Duration(seconds: 10),
backgroundColor: Colors.red.withAlpha(230),
colorText: Colors.white,
icon: const Icon(Icons.error_outline, color: Colors.white),
);
handleLogout();
},
onUserSigExpired: () => logger.w("UserSig 过期"),
onSelfInfoUpdated: (V2TimUserFullInfo info) {
logger.i("用户信息更新: ${info.toJson()}");

View File

@ -29,6 +29,8 @@ import 'package:tencent_cloud_chat_sdk/models/v2_tim_friend_info_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_friend_operation_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message_change_info.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message_search_param.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message_search_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_user_full_info.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_user_info_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_value_callback.dart';
@ -70,7 +72,12 @@ class ImService {
await Wxsdk.init();
// (+)
if (!Get.isRegistered<ImUserInfoController>()) {
Get.put(ImUserInfoController(), permanent: true);
} else {
Get.find<ImUserInfoController>().refreshUserInfo();
}
//
final messageService = ImMessageListenerService();
Get.put<ImMessageListenerService>(messageService, permanent: true);
@ -101,16 +108,12 @@ class ImService {
Future<ImResult> logout() async {
final res = await TencentImSDKPlugin.v2TIMManager.logout();
if (res.code == 0) {
///
Get.delete<ImUserInfoController>(force: true);
///
Get.find<ImMessageListenerService>().onClose();
Get.delete<ImMessageListenerService>(force: true);
await Get.delete<ImMessageListenerService>(force: true);
///
Get.find<ImFriendListeners>().unregister();
Get.delete<ImFriendListeners>(force: true);
await Get.delete<ImFriendListeners>(force: true);
/// tabbar
Get.find<TabBarController>().badgeMap.clear();
@ -120,13 +123,13 @@ class ImService {
///
Get.find<GlobalBadge>().onClose();
Get.delete<GlobalBadge>(force: true);
await Get.delete<GlobalBadge>(force: true);
///
PushService.unInitPush();
await PushService.unInitPush();
///
ImCore.unInit();
await ImCore.unInit();
}
return ImResult.wrapNoData(res);
}
@ -231,26 +234,26 @@ class ImService {
}
final convList = res.data?.conversationList ?? [];
logger.w('未过滤前到会话数据:$convList');
final userIDList = <String>[];
final groupIDList = <String>[];
// userID groupID
for (var conv in convList) {
if ((conv.faceUrl == null || conv.faceUrl!.isEmpty)) {
if (conv.userID != null) {
userIDList.add(conv.userID!);
} else if (conv.groupID != null) {
groupIDList.add(conv.groupID!);
}
}
}
Map<String, String?> userFaceUrlMap = {};
Map<String, String?> groupFaceUrlMap = {};
String isCustomAdmin = '0';
Map<String, String> isCustomAdmin = {};
if (userIDList.isNotEmpty) {
final userRes = await TencentImSDKPlugin.v2TIMManager.getUsersInfo(userIDList: userIDList);
if (userRes.code == 0) {
for (var user in userRes.data!) {
final userId = user.userID ?? '';
@ -258,8 +261,9 @@ class ImService {
//
final customInfo = user.customInfo;
if (customInfo != null) {
isCustomAdmin = customInfo['admin'] ?? '0';
isCustomAdmin[userId] = customInfo['admin'] ?? '0';
}
}
}
@ -280,7 +284,6 @@ class ImService {
final viewList = convList.map((conv) {
String? faceUrl = conv.faceUrl;
// faceUrl map
if (faceUrl == null || faceUrl.isEmpty) {
if (conv.userID != null) {
faceUrl = userFaceUrlMap[conv.userID!];
@ -289,7 +292,11 @@ class ImService {
}
}
return ConversationViewModel(conversation: conv, faceUrl: faceUrl, isCustomAdmin: isCustomAdmin);
return ConversationViewModel(
conversation: conv,
faceUrl: faceUrl,
isCustomAdmin: isCustomAdmin[conv.userID],
);
}).toList();
// ,
@ -337,9 +344,6 @@ class ImService {
///
Future<ImResult<V2TimConversationResult>> getConvData(String nextSeq, int count) async {
final res = await TencentImSDKPlugin.v2TIMManager.getConversationManager().getConversationList(nextSeq: nextSeq, count: count);
// for (var element in res.data!.conversationList) {
// logger.e('所有的会话数据:${element.toJson()}');
// }
return ImResult.wrap(res);
}
@ -357,6 +361,33 @@ class ImService {
);
}
///
Future<ImResult<V2TimMessageSearchResult>> searchLocalMessages({
required String page,
required String conversationID,
/// or=0and=1
int type = 1,
/// ['你好','周末']
required List<String> keywordList,
///
List<int> messageTypeList = const [1, 2],
}) async {
final searchParam = V2TimMessageSearchParam(
type: type,
conversationID: conversationID,
keywordList: keywordList,
messageTypeList: messageTypeList,
pageSize: 100,
// pageIndex: page,
searchCursor: page,
);
final V2TimValueCallback<V2TimMessageSearchResult> res = await TIMMessageManager.instance.searchLocalMessages(searchParam: searchParam);
return ImResult.wrap(res);
}
///
Future<ImResult<List<V2TimMessage>>> findMessages({
required List<String> messageIDList,

View File

@ -135,7 +135,7 @@ class PushService {
logger.w(router);
if (router == null || router != '') {
//
if (data['userID'] != null) {
if (data['userID'] != null || data['userID'] != '') {
logger.w('有userID');
//
final covRes = await ImService.instance.getConversation(conversationID: 'c2c_${data['userID']}');

View File

@ -6,8 +6,8 @@ class ShopApi {
/// [nameLike]
static const String shopList = '/app/product/page'; //
/// [showStatus]1= [nameLike]
static const String shopCategory = '/app/productCategory/page'; //
/// [showStatus]1= [nameLike] [level]1
static const String shopCategory = '/app/product/category/tree'; //
/// []
static const String shopSwiperList = '/app/article/carousel'; //

View File

@ -26,6 +26,18 @@ class NetworkOrAssetImage extends StatelessWidget {
width: width,
height: height,
fit: fit,
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) {
return child;
}
//
return Image.asset(
placeholderAsset,
width: width,
height: height,
fit: fit,
);
},
errorBuilder: (context, error, stackTrace) {
return Image.asset(
placeholderAsset,

View File

@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:loopin/IM/im_service.dart';
import 'package:loopin/api/shop_api.dart';
import 'package:loopin/service/http.dart';
@ -39,7 +38,7 @@ class ShopIndexController extends GetxController with GetSingleTickerProviderSta
RxInt currentTabIndex = 0.obs;
/// Tab
void initTabs() async {
void initTabs({required TickerProvider vsync}) async {
// ScrollController
tabs.forEach((_, state) => state.scrollController.dispose());
tabs.clear();
@ -50,10 +49,10 @@ class ShopIndexController extends GetxController with GetSingleTickerProviderSta
// tab
final res = await Http.post(ShopApi.shopCategory, data: {
'showStatus': 1,
'level': 1,
});
final data = res['data']['records'] as List<dynamic>;
logger.w(data);
final data = res['data'] as List<dynamic>;
// logger.w(data);
tabList.addAll(data);
// tab
@ -70,8 +69,9 @@ class ShopIndexController extends GetxController with GetSingleTickerProviderSta
}
// TabController
tabController = TabController(length: tabList.length, vsync: this);
tabController!.addListener(_tabListener);
tabController = TabController(length: tabList.length, vsync: vsync);
tabController?.addListener(_tabListener);
// tab
if (tabList.isNotEmpty) {
loadSwiperData();
@ -118,7 +118,7 @@ class ShopIndexController extends GetxController with GetSingleTickerProviderSta
final data = res['data']['records'];
tab.dataList.addAll(data);
logger.w(res);
// logger.w(res);
tab.currentPage.value += 1;
tab.isLoading.value = false;
@ -131,14 +131,13 @@ class ShopIndexController extends GetxController with GetSingleTickerProviderSta
'type': 1,
});
final data = res['data'];
logger.w(res);
// logger.w(res);
swiperData.assignAll(data);
}
@override
void onClose() {
tabController?.removeListener(_tabListener);
tabController?.dispose();
tabs.forEach((_, state) => state.scrollController.dispose());
super.onClose();
}

View File

@ -3,7 +3,6 @@ library;
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:loopin/IM/controller/chat_controller.dart';
import 'package:loopin/IM/controller/tab_bar_controller.dart';
import 'package:loopin/models/tab_type.dart';
import 'package:loopin/pages/video/module/recommend.dart';
@ -222,10 +221,11 @@ class _LayoutState extends State<Layout> {
}
if (index == 3) {
//
final ctl = Get.find<ChatController>();
if (ctl.chatList.isEmpty) {
Get.find<ChatController>().getConversationList();
}
// final ctl = Get.find<ChatController>();
// logger.e(ctl.chatList.toJson());
// if (ctl.chatList.isEmpty) {
// Get.find<ChatController>().getConversationList();
// }
}
if (index == 4) {
myPageKey.currentState?.refreshData();

View File

@ -10,6 +10,7 @@ import 'package:loopin/IM/controller/chat_controller.dart';
import 'package:loopin/IM/controller/tab_bar_controller.dart';
import 'package:loopin/IM/im_core.dart' as im_core;
import 'package:loopin/IM/im_service.dart';
import 'package:loopin/controller/shop_index_controller.dart';
import 'package:loopin/service/http_config.dart';
import 'package:loopin/utils/common.dart';
import 'package:loopin/utils/lifecycle_handler.dart';
@ -21,13 +22,14 @@ import 'package:shirne_dialog/shirne_dialog.dart';
import 'layouts/index.dart';
//
import 'router/index.dart';
// import 'utils/common.dart';
void main() async {
// tabbar
Get.put(TabBarController());
Get.put(TabBarController(), permanent: true);
//
Get.put(ChatController());
Get.put(ChatController(), permanent: true);
//
Get.put(ShopIndexController(), permanent: true);
// app前后台状态
WidgetsFlutterBinding.ensureInitialized();

View File

@ -1,4 +1,4 @@
///
///
enum ConversationType {
noFriend, //
system, //

View File

@ -4,7 +4,7 @@ enum NotifyMessageType {
systemNotify, // ->
systemReport, // ->
systemCheck, // ->
systemPush, //->广
systemPush, //->广
interactionComment, //->
interactionAt, //->@
interactionLike, //->
@ -18,6 +18,26 @@ enum NotifyMessageType {
groupNotifyLeaveUp, // ->
}
///
class NotifyMessageTypeConstants {
static const String newFoucs = 'newFoucs';
static const String systemNotify = 'systemNotify';
static const String systemReport = 'systemReport';
static const String systemCheck = 'systemCheck';
static const String systemPush = 'systemPush';
static const String interactionComment = 'interactionComment';
static const String interactionAt = 'interactionAt';
static const String interactionLike = 'interactionLike';
static const String interactionReply = 'interactionReply';
static const String orderRecharge = 'orderRecharge';
static const String orderPay = 'orderPay';
static const String orderRefund = 'orderRefund';
static const String groupNotifyCheck = 'groupNotifyCheck';
static const String groupNotifyAccpet = 'groupNotifyAccpet';
static const String groupNotifyFail = 'groupNotifyFail';
static const String groupNotifyLeaveUp = 'groupNotifyLeaveUp';
}
extension NotifyMessageTypeExtension on NotifyMessageType {
String get name {
switch (this) {

View File

@ -0,0 +1,13 @@
///
enum ShareType { video, shop }
extension ShareTypeExtension on ShareType {
String get name {
switch (this) {
case ShareType.video:
return 'https://wuzhongjie.com.cn/video';
case ShareType.shop:
return 'https://wuzhongjie.com.cn/shop';
}
}
}

View File

@ -72,8 +72,8 @@ class _LoginState extends State<Login> {
String userId = obj['userId'];
String userSig = obj['userSig'];
// im_sdk
await im_core.ImCore.init(sdkAppId: 1600080789);
final initRes = await im_core.ImCore.init(sdkAppId: 1600080789);
if (initRes) {
// String userId = '1940667704585248769'; //13212279365
// String userId = '1943510443312078850'; //18832510385
// String userSig =
@ -83,7 +83,6 @@ class _LoginState extends State<Login> {
try {
final loginRes = await ImService.instance.login(userID: userId, userSig: userSig);
logger.i(loginRes);
if (loginRes.success) {
//
Storage.write('hasLogged', true);
@ -91,8 +90,8 @@ class _LoginState extends State<Login> {
Storage.write('userId', userId);
Storage.write('token', obj['access_token']);
//
final accountRes = await Http.get('${CommonApi.accountInfo}');
logger.i(accountRes);
// final accountRes = await Http.get(CommonApi.accountInfo);
// logger.e(accountRes);
//
final videoController = Get.find<VideoModuleController>();
videoController.markNeedRefresh();
@ -103,6 +102,9 @@ class _LoginState extends State<Login> {
} catch (e) {
logger.i(e);
}
} else {
logger.e('登陆异常im初始化失败');
}
}
}

View File

@ -7,7 +7,9 @@ import 'package:get/get.dart';
import 'package:loopin/IM/controller/chat_controller.dart';
import 'package:loopin/IM/global_badge.dart';
import 'package:loopin/IM/im_service.dart';
import 'package:loopin/components/network_or_asset_image.dart';
import 'package:loopin/components/scan_util.dart';
import 'package:loopin/models/conversation_type.dart';
import 'package:loopin/models/conversation_view_model.dart';
import 'package:loopin/utils/parse_message_summary.dart';
import 'package:shirne_dialog/shirne_dialog.dart';
@ -334,35 +336,10 @@ class ChatPageState extends State<ChatPage> {
children: <Widget>[
//
ClipOval(
child: Builder(
builder: (context) {
final faceUrl = chatList[index].faceUrl;
final isNetwork =
faceUrl != null && faceUrl.isNotEmpty && (faceUrl.startsWith('http://') || faceUrl.startsWith('https://'));
if (isNetwork) {
return Image.network(
faceUrl,
height: 50.0,
width: 50.0,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return Image.asset(
'assets/images/pic1.jpg',
height: 50.0,
width: 50.0,
fit: BoxFit.cover,
);
},
);
} else {
return Image.asset(
(faceUrl != null && faceUrl.isNotEmpty) ? faceUrl : 'assets/images/pic1.jpg',
height: 50.0,
width: 50.0,
fit: BoxFit.cover,
);
}
},
child: NetworkOrAssetImage(
imageUrl: chatList[index].faceUrl,
width: 50,
height: 50,
),
),
@ -371,17 +348,17 @@ class ChatPageState extends State<ChatPage> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// Text(
// chatList[index].conversation.showName ?? '',
// style: const TextStyle(fontSize: 16.0),
// ),
Text(
chatList[index].conversation.showName ?? '',
chatList[index].conversation.showName ?? '未知',
style: TextStyle(
fontSize: (chatList[index].conversation.conversationGroupList?.isNotEmpty ?? false) ? 20 : 16,
fontWeight:
(chatList[index].conversation.conversationGroupList?.isNotEmpty ?? false) ? FontWeight.bold : FontWeight.normal,
),
fontSize: (chatList[index].conversation.conversationGroupList!.contains(ConversationType.noFriend.name)) ||
chatList[index].isCustomAdmin != '0'
? 20
: 16,
fontWeight: (chatList[index].conversation.conversationGroupList!.contains(ConversationType.noFriend.name)) ||
chatList[index].isCustomAdmin != '0'
? FontWeight.bold
: FontWeight.normal),
),
const SizedBox(height: 2.0),
Text(
@ -400,7 +377,8 @@ class ChatPageState extends State<ChatPage> {
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
Visibility(
visible: chatList[index].conversation.lastMessage?.timestamp != null,
visible: !(chatList[index].isCustomAdmin != '0' ||
chatList[index].conversation.conversationGroupList!.contains(ConversationType.noFriend.name)),
child: Text(
//
DateTime.fromMillisecondsSinceEpoch(
@ -418,19 +396,26 @@ class ChatPageState extends State<ChatPage> {
],
),
Visibility(
visible: chatList[index].isCustomAdmin != '0',
visible: chatList[index].isCustomAdmin != '0' ||
chatList[index].conversation.conversationGroupList!.contains(ConversationType.noFriend.name),
child: const Icon(
Icons.arrow_forward_ios,
color: Colors.grey,
size: 12.0,
color: Colors.blueGrey,
size: 14.0,
),
),
],
),
),
onTap: () {
if (chatList[index].isCustomAdmin != '0') {
//
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);

View File

View File

@ -0,0 +1,368 @@
///
library;
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:get/get.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/models/conversation_type.dart';
import 'package:loopin/models/notify_message.type.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_message.dart';
// interactionComment, //->
// interactionAt, //->@
// interactionLike, //->
// interactionReply, //->
class Interaction extends StatefulWidget {
const Interaction({super.key});
@override
State<Interaction> createState() => InteractionState();
}
class InteractionState extends State<Interaction> with SingleTickerProviderStateMixin {
bool isLoading = false; //
bool hasMore = true; //
final RxBool _throttleFlag = false.obs; //
final ScrollController chatController = ScrollController();
String page = '';
///-------------------
V2TimConversation? conv;
RxList<V2TimMessage> msgList = <V2TimMessage>[].obs;
final RxString selectedMessageType = '全部'.obs;
final List<Map<String, dynamic>> messageFilters = [
{'value': 'all', 'label': '全部', 'icon': Icons.all_inclusive},
{'value': NotifyMessageTypeConstants.interactionLike, 'label': '点赞', 'icon': Icons.favorite_border},
{'value': NotifyMessageTypeConstants.interactionComment, 'label': '评论', 'icon': Icons.comment},
{'value': NotifyMessageTypeConstants.interactionAt, 'label': '@我', 'icon': Icons.alternate_email},
{'value': NotifyMessageTypeConstants.interactionReply, 'label': '回复', 'icon': Icons.reply},
];
RxList demoList = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15].obs;
@override
void initState() {
super.initState();
if (Get.arguments != null && Get.arguments is V2TimConversation) {
//
conv = Get.arguments as V2TimConversation;
final lastmsg = conv?.lastMessage;
if (lastmsg != null) {
msgList.add(lastmsg);
}
}
chatController.addListener(() {
if (_throttleFlag.value) return;
if (chatController.position.pixels >= chatController.position.maxScrollExtent - 50) {
_throttleFlag.value = true;
getMsgData().then((_) {
//
Future.delayed(Duration(milliseconds: 1000), () {
_throttleFlag.value = false;
});
});
}
});
}
@override
void dispose() {
super.dispose();
chatController.dispose();
}
//
Future<void> getMsgData() async {
//
V2TimMessage? lastRealMsg;
lastRealMsg = msgList.last;
final res = await ImService.instance.getHistoryMessageList(
userID: ConversationType.interaction.name, // userID为固定的interaction
lastMsg: lastRealMsg,
);
if (res.success && res.data != null) {
msgList.addAll(res.data!);
if (res.data!.isEmpty) {
hasMore = false;
}
logger.i('聊天数据加载成功');
} else {
logger.e('聊天数据加载失败:${res.desc}');
}
}
//
Future<void> handleRefresh() async {
await Future.delayed(Duration(seconds: 5));
setState(() {});
}
//
void _filterMessages(String filterType) async {
logger.e(filterType);
final res = await ImService.instance.searchLocalMessages(
page: page,
conversationID: 'c2c_${ConversationType.interaction.name}',
keywordList: ['action', filterType],
);
logger.e(res.data!.toLogString());
if (res.success && res.data != null) {
final resultList = res.data?.messageSearchResultItems ?? [];
if (resultList.isNotEmpty) {
for (var item in resultList) {
logger.e(item.toJson());
msgList.addAll(item.messageList ?? []);
}
} else {
logger.e('数据为空${res.desc}');
}
}
}
//
void _showFilterMenu(BuildContext context) {
final double screenWidth = MediaQuery.of(context).size.width;
final double statusBarHeight = MediaQuery.of(context).padding.top;
showMenu(
context: context,
position: RelativeRect.fromLTRB(
0,
kToolbarHeight + statusBarHeight + 1,
0,
0,
),
elevation: 8,
color: Colors.white,
constraints: BoxConstraints(
minWidth: screenWidth,
maxWidth: screenWidth,
),
useRootNavigator: true, //
items: messageFilters.map((filter) {
return PopupMenuItem<String>(
value: filter['value'],
padding: EdgeInsets.zero, //
child: Container(
width: screenWidth,
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 16),
decoration: BoxDecoration(
border: Border(
bottom: filter['value'] != messageFilters.last['value'] ? BorderSide(color: Colors.grey[200]!) : BorderSide.none,
),
),
child: Row(
children: [
Icon(filter['icon'], size: 22, color: Colors.grey[700]),
SizedBox(width: 16),
Expanded(
child: Text(
filter['label'],
style: TextStyle(
fontSize: 16,
color: selectedMessageType.value == filter['label'] ? Colors.orange : Colors.black,
),
),
),
if (selectedMessageType.value == filter['label']) Icon(Icons.check, size: 20, color: Colors.orange),
],
),
),
);
}).toList(),
).then((value) {
if (value != null) {
final selectedFilter = messageFilters.firstWhere((f) => f['value'] == value);
selectedMessageType.value = selectedFilter['label'];
_filterMessages(value);
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[50],
appBar: AppBar(
centerTitle: true,
forceMaterialTransparency: true,
bottom: PreferredSize(
preferredSize: Size.fromHeight(1.0),
child: Container(
color: Colors.grey[300],
height: 1.0,
),
),
title: Obx(
() => GestureDetector(
onTap: () => _showFilterMenu(context),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(width: 4),
Text(
selectedMessageType.value,
style: TextStyle(
color: Colors.black,
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
Icon(Icons.arrow_drop_down, color: Colors.black, size: 20),
],
),
),
),
),
actions: [],
),
body: ScrollConfiguration(
behavior: CustomScrollBehavior().copyWith(scrollbars: false),
child: Column(
children: [
Expanded(
child: RefreshIndicator(
backgroundColor: Colors.white,
color: Color(0xFFFF5000),
displacement: 10.0,
onRefresh: handleRefresh,
child: Obx(() {
return ListView.builder(
controller: chatController,
shrinkWrap: true,
physics: BouncingScrollPhysics(),
// itemCount: msgList.length,
itemCount: demoList.length,
itemBuilder: (context, index) {
//cloudCustomData
// interactionComment, //->
// interactionAt, //->@
// interactionLike, //->
// interactionReply, //->
//----
// final element =msgList[index].customElem!;
// final cloudCustomData = msgList[index].cloudCustomData;
// final desc = msgList[index].customElem!.desc!;
// final jsonData = msgList[index].customElem!.data;
// ----
final jsonData = '{"faceUrl":"","nickName":"测试昵称","userID":"213213"}';
final item = jsonDecode(jsonData); //
final desc = '测试desc';
final cloudCustomData = 'interactionLike';
V2TimMessage element = V2TimMessage(elemType: 2, isRead: index > 2 ? true : false);
return Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 10.0),
child: Row(
spacing: 10.0,
children: <Widget>[
//
InkWell(
onTap: () async {
//
//
// cloudCustomData是interactionCommentinteractionAtinteractionReply,id
//
// final res = await Http.get('${VideoApi.detail}/${item['vlogID']}');
// Get.toNamed('/vloger', arguments: res['data']);
Get.toNamed('/vloger');
},
child: ClipOval(
child: NetworkOrAssetImage(
imageUrl: item['faceUrl'],
width: 50,
height: 50,
),
),
),
//
Expanded(
child: InkWell(
onTap: () {
//
//
// cloudCustomData是interactionCommentinteractionAtinteractionReply,id
//
// final res = await Http.get('${VideoApi.detail}/${item['vlogID']}');
// Get.toNamed('/vloger', arguments: res['data']);
Get.toNamed('/vloger');
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
//
Text(
item['nickName'] ?? '未知',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.normal,
),
),
const SizedBox(height: 2.0),
//
Text(
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(color: Colors.grey, fontSize: 12.0),
// desc,
'很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容',
),
Text(
Utils.formatTime(element.timestamp ?? DateTime.now().millisecondsSinceEpoch ~/ 1000),
style: TextStyle(
color: Colors.grey[600],
fontSize: 12,
),
),
],
),
),
),
//
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Visibility(
visible: true,
//
child: NetworkOrAssetImage(
imageUrl: item['firstFrameImg'],
placeholderAsset: 'assets/images/bk.jpg',
width: 40,
height: 60,
),
),
const SizedBox(width: 5.0),
//
Visibility(
visible: !(element.isRead ?? true),
child: FStyle.badge(0, isdot: true),
),
],
),
],
),
);
},
);
})),
),
],
),
),
);
}
}

View File

View File

@ -0,0 +1,235 @@
///
library;
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:get/get.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/models/conversation_type.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_conversation_filter.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message.dart';
// noFriend, //
class Nofriend extends StatefulWidget {
const Nofriend({super.key});
@override
State<Nofriend> createState() => NofriendState();
}
class NofriendState extends State<Nofriend> with SingleTickerProviderStateMixin {
bool isLoading = false; //
bool hasMore = true; //
final RxBool _throttleFlag = false.obs; //
final ScrollController chatController = ScrollController();
int page = 1;
///-------------------
RxList<V2TimConversation> convList = <V2TimConversation>[].obs;
RxList demoList = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15].obs;
@override
void initState() {
super.initState();
getMsgData();
chatController.addListener(() {
if (_throttleFlag.value) return;
if (chatController.position.pixels >= chatController.position.maxScrollExtent - 50) {
_throttleFlag.value = true;
getMsgData().then((_) {
//
Future.delayed(Duration(milliseconds: 1000), () {
_throttleFlag.value = false;
});
});
}
});
}
@override
void dispose() {
super.dispose();
chatController.dispose();
}
//
Future<void> getMsgData() async {
final res = await ImService.instance.getConversationListByFilter(
filter: V2TimConversationFilter(conversationGroup: ConversationType.noFriend.name),
nextSeq: page,
);
logger.i('获取会话数据成功:${res.data!.toJson()}');
if (res.success && res.data != null) {
final newList = res.data!.conversationList ?? [];
final isFinished = res.data!.isFinished ?? true;
convList.addAll(newList);
if (isFinished) {
hasMore = false;
//
page = int.parse(res.data!.nextSeq ?? '0');
} else {
page++;
}
logger.i('获取会话数据成功:$newList');
} else {
logger.e('获取会话数据失败:${res.data!.isFinished}');
}
}
//
Future<void> handleRefresh() async {
await Future.delayed(Duration(seconds: 5));
setState(() {});
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[50],
appBar: AppBar(
centerTitle: true,
forceMaterialTransparency: true,
bottom: PreferredSize(
preferredSize: Size.fromHeight(1.0),
child: Container(
color: Colors.grey[300],
height: 1.0,
),
),
title: Text(
'陌生人消息',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
),
actions: [],
),
body: ScrollConfiguration(
behavior: CustomScrollBehavior().copyWith(scrollbars: false),
child: Column(
children: [
Expanded(
child: RefreshIndicator(
backgroundColor: Colors.white,
color: Color(0xFFFF5000),
displacement: 10.0,
onRefresh: handleRefresh,
child: Obx(() {
return ListView.builder(
controller: chatController,
shrinkWrap: true,
physics: BouncingScrollPhysics(),
// itemCount: convList.length,
itemCount: demoList.length,
itemBuilder: (context, index) {
// ----
final jsonData = '{"faceUrl":"","nickName":"测试昵称","userID":"213213"}';
final item = jsonDecode(jsonData); //
final desc = '测试desc';
final cloudCustomData = 'interactionLike';
V2TimMessage element = V2TimMessage(elemType: 2, isRead: index > 2 ? true : false);
return Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 10.0),
child: Row(
spacing: 10.0,
children: <Widget>[
//
InkWell(
onTap: () async {
//
//
// cloudCustomData是interactionCommentinteractionAtinteractionReply,id
//
// final res = await Http.get('${VideoApi.detail}/${item['vlogID']}');
// Get.toNamed('/vloger', arguments: res['data']);
Get.toNamed('/vloger');
},
child: ClipOval(
child: NetworkOrAssetImage(
imageUrl: item['faceUrl'],
width: 50,
height: 50,
),
),
),
//
Expanded(
child: InkWell(
onTap: () {
//
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
//
Text(
item['nickName'] ?? '未知',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.normal,
),
),
const SizedBox(height: 2.0),
//
Text(
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(color: Colors.grey, fontSize: 12.0),
// desc,
'很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容',
),
Text(
Utils.formatTime(element.timestamp ?? DateTime.now().millisecondsSinceEpoch ~/ 1000),
style: TextStyle(
color: Colors.grey[600],
fontSize: 12,
),
),
],
),
),
),
//
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Visibility(
visible: true,
//
child: NetworkOrAssetImage(
imageUrl: item['firstFrameImg'],
placeholderAsset: 'assets/images/bk.jpg',
width: 40,
height: 60,
),
),
const SizedBox(width: 5.0),
//
Visibility(
visible: !(element.isRead ?? true),
child: FStyle.badge(0, isdot: true),
),
],
),
],
),
);
},
);
})),
),
],
),
),
);
}
}

View File

View File

@ -0,0 +1,444 @@
///
library;
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart';
import 'package:loopin/IM/controller/chat_controller.dart';
import 'package:loopin/IM/global_badge.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/components/scan_util.dart';
import 'package:loopin/models/conversation_type.dart';
import 'package:loopin/models/conversation_view_model.dart';
import 'package:loopin/styles/index.dart';
import 'package:loopin/utils/parse_message_summary.dart';
import 'package:shirne_dialog/shirne_dialog.dart';
// systemNotify, // ->
// systemReport, // ->
// systemCheck, // ->
// systemPush, //->广
class System extends StatefulWidget {
const System({super.key});
@override
State<System> createState() => SystemState();
}
class SystemState extends State<System> {
late final ChatController controller;
@override
void initState() {
super.initState();
controller = Get.find<ChatController>();
}
void deletConv(context, ConversationViewModel item) async {
final res = await ImService.instance.deleteConversation(conversationID: item.conversation.conversationID);
if (res.success) {
Navigator.of(context).pop();
controller.chatList.remove(item);
}
}
//
double posDX = 0.0;
double posDY = 0.0;
//
Future<void> handleRefresh() async {
await Future.delayed(Duration(seconds: 1));
setState(() {});
}
//
void showContextMenu(BuildContext context, ConversationViewModel item) {
bool isLeft = posDX > MediaQuery.of(context).size.width / 2 ? false : true;
bool isTop = posDY > MediaQuery.of(context).size.height / 2 ? false : true;
showDialog(
context: context,
barrierColor: Colors.black12, //
builder: (context) {
return Stack(
children: [
Positioned(
top: isTop ? posDY : posDY - 135,
left: isLeft ? posDX : posDX - 135,
width: 135,
child: Material(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15.0)),
color: Colors.white,
elevation: 2.0,
clipBehavior: Clip.hardEdge,
child: Column(
children: [
ListTile(
title: const Text(
'设为免打扰',
style: TextStyle(color: Colors.black87, fontSize: 14.0),
),
dense: true,
onTap: () {},
),
ListTile(
title: const Text(
'置顶消息',
style: TextStyle(color: Colors.black87, fontSize: 14.0),
),
dense: true,
onTap: () {},
),
ListTile(
title: const Text(
'不显示该消息',
style: TextStyle(color: Colors.black87, fontSize: 14.0),
),
dense: true,
onTap: () {},
),
ListTile(
title: const Text(
'删除',
style: TextStyle(color: Colors.black87, fontSize: 14.0),
),
dense: true,
onTap: () {
deletConv(context, item);
},
)
],
),
),
)
],
);
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[50],
appBar: AppBar(
forceMaterialTransparency: true,
title: Row(
spacing: 8.0,
children: [
Text('消息'),
Container(
padding: EdgeInsets.symmetric(horizontal: 6.0, vertical: 4.0),
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(20.0), boxShadow: [
BoxShadow(
color: Colors.black.withAlpha(20),
offset: Offset(0.0, 1.0),
blurRadius: 2.0,
spreadRadius: 0.0,
),
]),
child: InkWell(
onTap: () async {
if (Get.find<GlobalBadge>().totalUnread > 0) {
final res = await ImService.instance.clearConversationUnreadCount(conversationID: '');
if (res.success) {
MyDialog.toast('操作成功', icon: const Icon(Icons.check_circle), style: ToastStyle(backgroundColor: Colors.green.withAlpha(200)));
} else {
MyDialog.toast(res.desc, icon: Icon(Icons.warning), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200)));
}
}
},
child: Row(
spacing: 3.0,
children: [
Icon(
Icons.cleaning_services_sharp,
size: 14.0,
),
Text(
'清除未读',
style: TextStyle(fontSize: 12.0),
)
],
),
)),
],
),
actions: [
///
// IconButton(
// icon: const Icon(Icons.search),
// onPressed: () {},
// ),
IconButton(
icon: const Icon(Icons.add_circle_outline),
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: 'group',
child: Row(
children: [
Icon(Icons.group, color: Colors.white, size: 18),
SizedBox(width: 8),
Text(
'发起群聊',
style: TextStyle(color: Colors.white),
),
],
),
),
PopupMenuItem<String>(
value: 'friend',
child: Row(
children: [
Icon(Icons.person_add, color: Colors.white, size: 18),
SizedBox(width: 8),
Text(
'添加朋友',
style: TextStyle(color: Colors.white),
),
],
),
),
PopupMenuItem<String>(
value: 'scan',
child: Row(
children: [
Icon(Icons.qr_code_scanner, color: Colors.white, size: 18),
SizedBox(width: 8),
Text(
'扫一扫',
style: TextStyle(color: Colors.white),
),
],
),
),
],
);
if (selected != null) {
switch (selected) {
case 'group':
print('点击了发起群聊');
break;
case 'friend':
print('点击了添加朋友');
break;
case 'scan':
print('点击了扫一扫');
ScanUtil.openScanner(onResult: (code) {
print('扫码结果:$code');
Get.snackbar('扫码成功', code);
//
});
break;
}
}
},
),
],
),
body: ScrollConfiguration(
behavior: CustomScrollBehavior().copyWith(scrollbars: false),
child: Column(
children: [
Container(
margin: EdgeInsets.symmetric(horizontal: 15.0, vertical: 10.0),
padding: EdgeInsets.all(10.0),
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(15.0), boxShadow: [
BoxShadow(
color: Colors.black.withAlpha(10),
offset: Offset(0.0, 1.0),
blurRadius: 2.0,
spreadRadius: 0.0,
),
]),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Column(
children: [
SvgPicture.asset(
'assets/images/svg/order.svg',
height: 36.0,
width: 36.0,
),
Text('群聊'),
],
),
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('粉丝'),
],
),
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;
return ListView.builder(
shrinkWrap: true,
physics: BouncingScrollPhysics(),
itemCount: chatList.length,
itemBuilder: (context, index) {
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: (chatList[index].conversation.conversationGroupList!.contains(ConversationType.noFriend.name)) ||
chatList[index].isCustomAdmin != '0'
? 20
: 16,
fontWeight: (chatList[index].conversation.conversationGroupList!.contains(ConversationType.noFriend.name)) ||
chatList[index].isCustomAdmin != '0'
? FontWeight.bold
: FontWeight.normal),
),
const SizedBox(height: 2.0),
Text(
chatList[index].conversation.lastMessage != null
? parseMessageSummary(chatList[index].conversation.lastMessage!)
: '',
style: const TextStyle(color: Colors.grey, fontSize: 13.0),
overflow: TextOverflow.ellipsis,
),
],
),
),
//
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
Visibility(
visible: !(chatList[index].isCustomAdmin != '0' ||
chatList[index].conversation.conversationGroupList!.contains(ConversationType.noFriend.name)),
child: Text(
//
DateTime.fromMillisecondsSinceEpoch(
(chatList[index].conversation.lastMessage!.timestamp ?? 0) * 1000,
).toLocal().toString().substring(0, 16), //
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: chatList[index].isCustomAdmin != '0' ||
chatList[index].conversation.conversationGroupList!.contains(ConversationType.noFriend.name),
child: const Icon(
Icons.arrow_forward_ios,
color: Colors.blueGrey,
size: 14.0,
),
),
],
),
),
onTap: () {
if (conversationTypeFromString(chatList[index].isCustomAdmin)) {
//
logger.e(chatList[index].isCustomAdmin);
} else if (chatList[index].conversation.conversationGroupList!.contains(ConversationType.noFriend.name)) {
//
logger.e(chatList[index].conversation.conversationGroupList);
} 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

@ -13,6 +13,7 @@ import 'package:loopin/IM/im_service.dart';
import 'package:loopin/api/shop_api.dart';
import 'package:loopin/components/my_toast.dart';
import 'package:loopin/components/network_or_asset_image.dart';
import 'package:loopin/models/share_type.dart';
import 'package:loopin/models/summary_type.dart';
import 'package:loopin/service/http.dart';
import 'package:loopin/utils/index.dart';
@ -80,10 +81,10 @@ class _GoodsState extends State<Goods> {
final description = shopObj['describe']; //
if (index == 1) {
//
Wxsdk.shareToFriend(title: '快看看我分享的商品', description: description, webpageUrl: 'https://baidu.com');
Wxsdk.shareToFriend(title: '快看看我分享的商品', description: description, webpageUrl: '${ShareType.shop.name}?id=${shopObj['id']}');
} else if (index == 2) {
//
Wxsdk.shareToTimeline(title: '快看看我分享的商品', webpageUrl: 'https://baidu.com');
Wxsdk.shareToTimeline(title: '快看看我分享的商品', webpageUrl: '${ShareType.shop.name}?id=${shopObj['id']}');
}
}
@ -551,9 +552,9 @@ class _GoodsState extends State<Goods> {
// ),
GestureDetector(
onTap: () async {
//
//
logger.i('联系客服');
final res = await ImService.instance.getConversation(conversationID: 'c2c_${shopObj['shoperId']}');
final res = await ImService.instance.getConversation(conversationID: 'c2c_${shopObj['tenantId']}');
V2TimConversation conversation = res.data;
logger.i(conversation.toLogString());
if (res.success) {
@ -619,11 +620,18 @@ class _GoodsState extends State<Goods> {
alignment: Alignment.center,
padding: const EdgeInsets.symmetric(horizontal: 20.0),
color: Color(0xFFFF5000),
child: GestureDetector(
onTap: () {
// orderId
String orderId = '1958380183857659904'; //
Get.toNamed('/order/detail', arguments: orderId);
},
child: Text(
'立即购买',
style: TextStyle(color: Colors.white, fontSize: 14.0),
),
),
),
],
),
),

View File

@ -8,6 +8,7 @@ import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import 'package:get/get.dart';
import 'package:loopin/components/backtop.dart';
import 'package:loopin/components/loading.dart';
import 'package:loopin/components/network_or_asset_image.dart';
import 'package:loopin/controller/shop_index_controller.dart';
import 'package:loopin/utils/index.dart';
@ -38,7 +39,7 @@ class _IndexPageState extends State<IndexPage> with SingleTickerProviderStateMix
// }
// ];
final ScrollController pageScrollController = ScrollController();
final ShopIndexController controller = Get.put(ShopIndexController());
late ShopIndexController controller;
//
Widget cardList(item) {
@ -55,7 +56,12 @@ class _IndexPageState extends State<IndexPage> with SingleTickerProviderStateMix
]),
child: Column(
children: [
Image.network('${item['pic']}'),
// Image.network(),
NetworkOrAssetImage(
imageUrl: '${item['pic']}',
width: double.infinity,
placeholderAsset: 'assets/images/bk.jpg',
),
Container(
padding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 5.0),
child: Column(
@ -106,77 +112,67 @@ class _IndexPageState extends State<IndexPage> with SingleTickerProviderStateMix
@override
void initState() {
super.initState();
controller.initTabs();
controller = Get.find<ShopIndexController>();
controller.initTabs(vsync: this);
}
@override
Widget build(BuildContext context) {
return Obx(() {
final tabIndex = controller.currentTabIndex.value;
final currentTab = controller.tabs[tabIndex];
return Scaffold(
backgroundColor: Colors.grey[50],
body: Column(
children: [
// + TabBar
_buildTopSection(),
//
Expanded(
child: TabBarView(
child: controller.tabController == null
? const Center(child: CircularProgressIndicator())
: TabBarView(
controller: controller.tabController,
children: controller.tabList.asMap().entries.map((entry) {
final index = entry.key;
return _buildTabContent(index);
}).toList(),
children: List.generate(
controller.tabList.length,
(index) => _buildTabContent(index),
),
),
),
],
),
floatingActionButton: currentTab != null
? Backtop(
floatingActionButton: Obx(() {
final tabIndex = controller.currentTabIndex.value;
final currentTab = controller.tabs[tabIndex];
if (currentTab == null) return const SizedBox.shrink();
return Backtop(
controller: currentTab.scrollController,
offset: currentTab.scrollOffset.value,
)
: null,
);
});
}),
);
}
//
Widget _buildTopSection() {
double screenWidth = MediaQuery.of(context).size.width;
int tabCount = controller.tabList.length;
// Tab
double minTabWidth = 80;
//
bool isScrollable = tabCount * minTabWidth > screenWidth;
if (controller.tabController == null) {
return SizedBox();
}
return Column(
children: [
//
Container(
Obx(() {
if (controller.swiperData.isEmpty) return const SizedBox.shrink();
if (controller.swiperData.length == 1) {
final imageUrl = controller.swiperData.first['images'] ?? '';
return Image.network(imageUrl, fit: BoxFit.fill, width: double.infinity, height: 240);
}
return SizedBox(
width: double.infinity,
height: 240,
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [Color(0xFFFF5000), Color(0xFFfcaec4)],
),
),
child: controller.swiperData.length <= 1
? (controller.swiperData.isNotEmpty
? Image.network(
controller.swiperData.first['images'] ?? '',
fit: BoxFit.fill,
)
: const SizedBox.shrink())
: Swiper(
child: Swiper(
itemCount: controller.swiperData.length,
autoplay: true,
loop: true,
pagination: SwiperPagination(
pagination: const SwiperPagination(
builder: DotSwiperPaginationBuilder(
color: Colors.white70,
activeColor: Colors.white,
@ -187,28 +183,50 @@ class _IndexPageState extends State<IndexPage> with SingleTickerProviderStateMix
return imageUrl.isNotEmpty ? Image.network(imageUrl, fit: BoxFit.fill) : const SizedBox.shrink();
},
),
),
);
}),
// TabBar
Container(
Obx(() {
int tabCount = controller.tabList.length;
double screenWidth = MediaQuery.of(context).size.width;
double minTabWidth = 60;
bool isScrollable = tabCount * minTabWidth > screenWidth;
return Container(
color: Colors.white,
child: TabBar(
controller: controller.tabController,
tabs: controller.tabList.map((item) {
return Tab(
child: Text(item['name'], style: const TextStyle(fontWeight: FontWeight.bold)),
child: Text(
item['name'] ?? '',
style: const TextStyle(fontWeight: FontWeight.bold),
overflow: TextOverflow.ellipsis,
softWrap: false,
),
);
}).toList(),
isScrollable: isScrollable,
overlayColor: WidgetStateProperty.all(Colors.transparent),
unselectedLabelColor: Colors.black87,
labelColor: Color.fromARGB(255, 236, 108, 49),
indicator: const UnderlineTabIndicator(borderSide: BorderSide(color: Color.fromARGB(255, 236, 108, 49), width: 2.0)),
unselectedLabelStyle: const TextStyle(fontSize: 16.0, fontFamily: 'Microsoft YaHei'),
labelStyle: const TextStyle(fontSize: 18.0, fontFamily: 'Microsoft YaHei', fontWeight: FontWeight.bold),
labelColor: const Color.fromARGB(255, 236, 108, 49),
indicator: const UnderlineTabIndicator(
borderSide: BorderSide(color: Color.fromARGB(255, 236, 108, 49), width: 2.0),
),
unselectedLabelStyle: const TextStyle(
fontSize: 14.0,
fontFamily: 'Microsoft YaHei',
),
labelStyle: const TextStyle(
fontSize: 16.0,
fontFamily: 'Microsoft YaHei',
fontWeight: FontWeight.bold,
),
dividerHeight: 0,
),
),
);
}),
],
);
}

View File

@ -1,8 +1,6 @@
///
library;
import 'dart:ui';
import 'package:card_swiper/card_swiper.dart';
import 'package:flutter/material.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
@ -118,7 +116,7 @@ class _IndexPageState extends State<IndexPage> with SingleTickerProviderStateMix
@override
void initState() {
super.initState();
controller.initTabs();
controller.initTabs(vsync: this);
}
@override

View File

@ -21,7 +21,7 @@ class PageParams {
int page;
int pageSize;
bool isLoading;
bool hasMore;
RxBool hasMore;
int total;
bool isInitLoading;
@ -29,16 +29,16 @@ class PageParams {
this.page = 1,
this.pageSize = 10,
this.isLoading = false,
this.hasMore = true,
bool hasMore = true,
this.total = 0,
this.isInitLoading = true,
});
}) : hasMore = hasMore.obs;
void init() {
page = 1;
pageSize = 10;
isLoading = false;
hasMore = true;
hasMore.value = true;
total = 0;
isInitLoading = true;
}
@ -86,7 +86,6 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
@override
void initState() {
super.initState();
initControllers();
scrollListener = () {
@ -95,9 +94,9 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
if (!isNearBottom) return;
if (currentTabIndex.value == 0 && !itemsParams.isLoading && itemsParams.hasMore) {
if (currentTabIndex.value == 0 && !itemsParams.isLoading && itemsParams.hasMore.value) {
loadData(0);
} else if (currentTabIndex.value == 1 && !favoriteParams.isLoading && favoriteParams.hasMore) {
} else if (currentTabIndex.value == 1 && !favoriteParams.isLoading && favoriteParams.hasMore.value) {
loadData(1);
}
};
@ -145,12 +144,11 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
Future<void> loadData([int? tabIndex]) async {
final index = tabIndex ?? currentTabIndex.value;
if (index == 0) {
if (itemsParams.isLoading || !itemsParams.hasMore) return;
if (itemsParams.isLoading || !itemsParams.hasMore.value) return;
itemsParams.isLoading = true;
// itemsParams.isInitLoading = true;
try {
final res = await Http.post(VideoApi.myPublicList, data: {
"userId": imUserInfoController?.userID.value,
"yesOrNo": 0,
@ -159,50 +157,47 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
});
final obj = res['data'];
final total = obj['total'];
final row = obj['rows'];
final row = obj['records'] ?? [];
logger.i(res['data']);
//
if (items.length >= total) {
itemsParams.hasMore = false;
}
logger.e(items.length);
//
items.addAll(row);
logger.e(obj);
if (items.length >= total) {
itemsParams.hasMore.value = false;
}
//
itemsParams.page++;
} finally {
//
itemsParams.isLoading = false;
itemsParams.isInitLoading = false;
}
} else if (index == 1) {
if (favoriteParams.isLoading || !favoriteParams.hasMore) return;
if (favoriteParams.isLoading || !favoriteParams.hasMore.value) return;
favoriteParams.isLoading = true;
// favoriteParams.isInitLoading = true;
try {
final res = await Http.post(VideoApi.myPublicList, data: {
final res = await Http.post(VideoApi.myLikedList, data: {
"userId": imUserInfoController?.userID.value,
"yesOrNo": 0,
"current": itemsParams.page,
"size": itemsParams.pageSize,
"current": favoriteParams.page,
"size": favoriteParams.pageSize,
});
final obj = res['data'];
final total = obj['total'];
final row = obj['rows'];
final row = obj['records'] ?? [];
favoriteItems.addAll(row);
if (favoriteItems.length >= total) {
itemsParams.hasMore = false;
favoriteParams.hasMore.value = false;
}
favoriteItems.addAll(row);
favoriteParams.page++;
} finally {
favoriteParams.isLoading = false;
favoriteParams.isInitLoading = false;
}
}
}
void initControllers() {
tabController = TabController(initialIndex: 0, length: tabList.length, vsync: this);
@ -237,7 +232,11 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
//
void selfInfo() async {
// imUserInfoController = Get.find<ImUserInfoController>();
final res = await ImService.instance.getUserFollowInfo(userIDList: [imUserInfoController!.userID.value]);
if (!Get.isRegistered<ImUserInfoController>()) {
logger.e('用户信息controller未注册');
return;
}
final res = await ImService.instance.getUserFollowInfo(userIDList: [imUserInfoController?.userID.value ?? '']);
if (res.success) {
//
// followersCount粉丝,mutualFollowersCount互关,followingCount我关注了多少人
@ -330,8 +329,7 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
} else {
return Scaffold(
backgroundColor: const Color(0xFFFAF6F9),
body: Obx(() {
return NestedScrollViewPlus(
body: NestedScrollViewPlus(
controller: scrollController,
physics: shouldFixHeader.value
? OnlyDownScrollPhysics(parent: AlwaysScrollableScrollPhysics())
@ -430,8 +428,7 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
_buildGridTab(1)
],
),
);
}),
),
);
}
});
@ -510,7 +507,7 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 20.0),
child: Center(
child: params.hasMore ? const CircularProgressIndicator() : const Text('没有更多数据了'),
child: params.hasMore.value ? CircularProgressIndicator() : Text('没有更多数据了'),
),
),
),
@ -612,36 +609,38 @@ 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);
builder: (context, constraints) {
final double maxHeight = 180;
final double minHeight = 120;
final currentHeight = constraints.maxHeight;
double ratio = ((currentHeight - minHeight) / (maxHeight - minHeight)).clamp(0.0, 1.0);
return Stack(
fit: StackFit.expand,
children: [
Positioned.fill(
child: Opacity(
opacity: 1.0,
child: NetworkOrAssetImage(imageUrl: imUserInfoController?.customInfo['coverBg'], placeholderAsset: 'assets/images/bk.jpg'),
),
),
// Obx
Obx(() {
final bgUrl = imUserInfoController?.customInfo['coverBg'] ?? '';
return NetworkOrAssetImage(
imageUrl: bgUrl,
placeholderAsset: 'assets/images/bk.jpg',
fit: BoxFit.cover,
);
}),
Positioned(
left: 15.0,
left: 15,
bottom: 0,
right: 15.0,
right: 15,
child: Container(
padding: const EdgeInsets.symmetric(vertical: 10.0),
padding: const EdgeInsets.symmetric(vertical: 10),
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
ClipOval(
child: Obx(() {
final faceUrl = imUserInfoController?.faceUrl.value;
return ClipRRect(
borderRadius: BorderRadius.circular(30),
children: [
// Obx
Obx(() {
final faceUrl = imUserInfoController?.faceUrl.value ?? '';
return ClipOval(
child: NetworkOrAssetImage(
imageUrl: faceUrl,
width: 80,
@ -649,49 +648,69 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
),
);
}),
),
const SizedBox(width: 15.0),
const SizedBox(width: 15),
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 == true ? imUserInfoController!.nickname.value : '昵称',
style: TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold, fontFamily: 'Arial', color: Colors.white),
// Obx
Obx(() {
final nickname = imUserInfoController?.nickname.value ?? '';
return Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
decoration: BoxDecoration(
color: Colors.black.withAlpha(76),
borderRadius: BorderRadius.circular(20),
),
)),
const SizedBox(width: 8.0),
child: Text(
nickname.isNotEmpty ? nickname : '昵称',
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
);
}),
const SizedBox(width: 8),
InkWell(
onTap: () {
qrcodeAlertDialog(context);
},
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),
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 5),
decoration: BoxDecoration(
color: Colors.black.withAlpha(76),
borderRadius: BorderRadius.circular(20),
),
child: const Icon(Icons.qr_code_outlined, size: 18, 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)),
const SizedBox(height: 8),
// ID Obx
Obx(() {
final userId = imUserInfoController?.userID.value ?? '';
return Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
decoration: BoxDecoration(
color: Colors.black.withAlpha(76),
borderRadius: BorderRadius.circular(20),
),
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)));
Clipboard.setData(ClipboardData(text: userId));
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)),
),
child: Text('ID:$userId', style: const TextStyle(fontSize: 12, color: Colors.white)),
),
);
}),
],
),
),
@ -705,6 +724,104 @@ 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(
@ -783,7 +900,9 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
_buildOrderIcon('assets/images/ico_sh.png', '提现vloger', () {
Get.toNamed('/vloger');
}),
_buildOrderIcon('assets/images/ico_tgm.png', '推广码', () {}),
_buildOrderIcon('assets/images/ico_tgm.png', '推广码', () {
logger.e('推广码');
}),
],
),
),

View File

@ -388,18 +388,18 @@ class MyPageState extends State<Vloger> with SingleTickerProviderStateMixin {
double ratio = (currentHeight - minHeight) / (maxHeight - minHeight);
ratio = ratio.clamp(0.0, 1.0);
String coverBg = userInfo.value.customInfo?['coverBg'] ?? '';
coverBg = coverBg.isEmpty ? 'assets/images/pic2.jpg' : coverBg;
logger.w(coverBg);
return Stack(
fit: StackFit.expand,
children: [
Positioned.fill(
child: Opacity(
opacity: 1.0,
child: Image.asset(
coverBg,
fit: BoxFit.cover,
))),
child: NetworkOrAssetImage(
imageUrl: coverBg,
width: double.infinity,
),
),
),
Positioned(
left: 15.0,
bottom: 0,
@ -411,20 +411,6 @@ class MyPageState extends State<Vloger> with SingleTickerProviderStateMixin {
children: <Widget>[
ClipOval(
child: NetworkOrAssetImage(imageUrl: userInfo.value.faceUrl),
// child: Image.asset(
// userInfo.value.faceUrl ?? 'assets/images/pic1.jpg',
// height: 60.0,
// width: 60.0,
// fit: BoxFit.cover,
// errorBuilder: (context, error, stackTrace) {
// return Image.asset(
// 'assets/images/pic1.jpg',
// height: 60.0,
// width: 60.0,
// fit: BoxFit.cover,
// );
// },
// ),
),
const SizedBox(width: 15.0),
Expanded(

View File

@ -1,7 +1,9 @@
library;
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:shirne_dialog/shirne_dialog.dart';
import 'package:timer_count_down/timer_count_down.dart';
import '../../behavior/custom_scroll_behavior.dart';
@ -13,6 +15,30 @@ class OrderDetail extends StatefulWidget {
}
class _OrderDetailState extends State<OrderDetail> with SingleTickerProviderStateMixin {
late String _orderId;
@override
void initState() {
super.initState();
_orderId = Get.arguments;
getOrderDetail(orderId: _orderId);
}
//
void getOrderDetail({required String orderId}) async {
//
}
//
int handleTime() {
String dbTime = '2025-08-21 15:07:31.293';
DateTime orderTime = DateTime.parse(dbTime);
// = + 15
DateTime expireTime = orderTime.add(const Duration(minutes: 15));
//
int remainSeconds = expireTime.difference(DateTime.now()).inSeconds;
if (remainSeconds < 0) remainSeconds = 0;
return remainSeconds;
}
Widget emptyTip() {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
@ -39,12 +65,12 @@ class _OrderDetailState extends State<OrderDetail> with SingleTickerProviderStat
foregroundColor: Colors.white,
title: Text('订单详情'),
titleSpacing: 1.0,
actions: [
IconButton(
icon: Icon(Icons.help, size: 18.0),
onPressed: () {},
),
],
// actions: [
// IconButton(
// icon: Icon(Icons.help, size: 18.0),
// onPressed: () {},
// ),
// ],
),
body: ScrollConfiguration(
behavior: CustomScrollBehavior().copyWith(scrollbars: false),
@ -64,9 +90,31 @@ class _OrderDetailState extends State<OrderDetail> with SingleTickerProviderStat
)),
TextSpan(text: ' 待支付, '),
TextSpan(text: ' 剩余 '),
TextSpan(
text: '00 : 29 : 55',
style: TextStyle(color: Colors.red),
// TextSpan(
// text: '00 : 29 : 55',
// style: TextStyle(color: Colors.red),
// ),
WidgetSpan(
alignment: PlaceholderAlignment.middle,
child: Countdown(
// createtime
// seconds: 15 * 60, // 15
seconds: handleTime(),
build: (_, double time) {
int m = ((time % 3600) ~/ 60).toInt();
int s = (time % 60).toInt();
String formatted = "${m.toString().padLeft(2, '0')} : ${s.toString().padLeft(2, '0')}";
return Text(
formatted,
style: const TextStyle(color: Colors.red),
);
},
interval: const Duration(seconds: 1),
onFinished: () {
print("倒计时结束");
},
),
),
]),
),

View File

@ -9,12 +9,12 @@ import '../../behavior/custom_scroll_behavior.dart';
import '../../components/keepalive_wrapper.dart';
import '../../controller/video_module_controller.dart';
import './module/attention.dart';
import './module/recommend.dart';
// import './module/browse.dart';
// import './module/buying.dart';
// import './module/drama.dart';
// import './module/live.dart';
import './module/friend.dart';
import './module/recommend.dart';
// tab内容模块
// import './module/subscribe.dart';

View File

@ -1,10 +1,11 @@
///
library;
import 'dart:async';
import 'dart:convert';
import 'package:flutter/services.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart';
import 'package:loopin/IM/controller/chat_controller.dart';
@ -16,9 +17,9 @@ import 'package:loopin/components/my_toast.dart';
import 'package:loopin/components/network_or_asset_image.dart';
import 'package:loopin/models/summary_type.dart';
import 'package:loopin/service/http.dart';
import 'package:loopin/utils/wxsdk.dart';
import 'package:loopin/utils/permissions.dart';
import 'package:loopin/utils/download_video.dart';
import 'package:loopin/utils/permissions.dart';
import 'package:loopin/utils/wxsdk.dart';
import 'package:media_kit/media_kit.dart';
import 'package:media_kit_video/media_kit_video.dart';
import 'package:media_kit_video/media_kit_video_controls/src/controls/extensions/duration.dart';
@ -48,6 +49,7 @@ class RecommendModule extends StatefulWidget {
@override
State<RecommendModule> createState() => _RecommendModuleState();
}
class CommentBottomSheet extends StatefulWidget {
final String videoId;
final Function(int) onCommentCountChanged; //
@ -109,8 +111,7 @@ class _CommentBottomSheetState extends State<CommentBottomSheet> {
if (res['code'] == 200 && res['data'] != null) {
final data = res['data'];
final List<Map<String, dynamic>> newComments =
List<Map<String, dynamic>>.from(data['records'] ?? []);
final List<Map<String, dynamic>> newComments = List<Map<String, dynamic>>.from(data['records'] ?? []);
final int total = data['total'] ?? 0;
setState(() {
@ -134,7 +135,7 @@ class _CommentBottomSheetState extends State<CommentBottomSheet> {
}
}
Future<void> postComment(String content, {String parentCommentId = ''}) async {
Future<void> postComment(String content, {String parentCommentId = ''}) async {
try {
final res = await Http.post(VideoApi.doVideoComment, data: {
'vlogId': widget.videoId,
@ -148,11 +149,8 @@ Future<void> postComment(String content, {String parentCommentId = ''}) async {
fetchReplies(parentCommentId, false);
//
setState(() {
final comment = commentList.firstWhere(
(c) => c['id'] == parentCommentId,
orElse: () => <String, dynamic>{}
);
if (comment != null && comment.isNotEmpty) {
final comment = commentList.firstWhere((c) => c['id'] == parentCommentId, orElse: () => <String, dynamic>{});
if (comment.isNotEmpty) {
comment['childCount'] = (comment['childCount'] ?? 0) + 1;
}
});
@ -175,7 +173,7 @@ Future<void> postComment(String content, {String parentCommentId = ''}) async {
type: 'error',
);
}
}
}
//
Future<void> fetchReplies(String commentId, bool loadMore) async {
@ -199,8 +197,7 @@ Future<void> postComment(String content, {String parentCommentId = ''}) async {
if (res['code'] == 200 && res['data'] != null) {
final data = res['data'];
final List<Map<String, dynamic>> newReplies =
List<Map<String, dynamic>>.from(data['records'] ?? []);
final List<Map<String, dynamic>> newReplies = List<Map<String, dynamic>>.from(data['records'] ?? []);
setState(() {
if (loadMore) {
@ -218,7 +215,7 @@ Future<void> postComment(String content, {String parentCommentId = ''}) async {
isLoadingReplies[commentId] = false;
});
}
}
}
@override
Widget build(BuildContext context) {
@ -270,9 +267,7 @@ Future<void> postComment(String content, {String parentCommentId = ''}) async {
Expanded(
child: NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification scrollInfo) {
if (scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent &&
!isLoadingMoreComments &&
hasMoreComments) {
if (scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent && !isLoadingMoreComments && hasMoreComments) {
fetchComments(true);
}
return false;
@ -292,9 +287,7 @@ Future<void> postComment(String content, {String parentCommentId = ''}) async {
return Center(
child: Padding(
padding: EdgeInsets.all(16.0),
child: isLoadingMoreComments
? CircularProgressIndicator()
: Text('没有更多评论了'),
child: isLoadingMoreComments ? CircularProgressIndicator() : Text('没有更多评论了'),
),
);
}
@ -388,7 +381,7 @@ Future<void> postComment(String content, {String parentCommentId = ''}) async {
),
),
Text(
'${comment['createTime']?.toString().substring(0, 10) ?? ''}',
comment['createTime']?.toString().substring(0, 10) ?? '',
style: TextStyle(color: Colors.grey, fontSize: 12.0),
),
],
@ -448,21 +441,19 @@ Future<void> postComment(String content, {String parentCommentId = ''}) async {
),
),
Text(
'${reply['createTime']?.toString().substring(0, 10) ?? ''}',
reply['createTime']?.toString().substring(0, 10) ?? '',
style: TextStyle(color: Colors.grey, fontSize: 11.0),
),
],
),
)).toList(),
)),
//
if (replies.length < comment['childCount'])
Center(
child: TextButton(
onPressed: () => fetchReplies(comment['id'], true),
child: isLoadingReplies[comment['id']] == true
? CircularProgressIndicator()
: Text('加载更多回复'),
child: isLoadingReplies[comment['id']] == true ? CircularProgressIndicator() : Text('加载更多回复'),
),
),
],
@ -527,7 +518,10 @@ Future<void> postComment(String content, {String parentCommentId = ''}) async {
),
),
onTap: () {
Navigator.push(context, FadeRoute(child: PopupReply(
Navigator.push(
context,
FadeRoute(
child: PopupReply(
hintText: replyingCommentId.isNotEmpty ? '回复 @$replyingCommentUser' : '说点什么...',
onChanged: (value) {
debugPrint('评论内容: $value');
@ -549,7 +543,8 @@ Future<void> postComment(String content, {String parentCommentId = ''}) async {
),
);
}
}
}
class _RecommendModuleState extends State<RecommendModule> {
VideoModuleController videoModuleController = Get.put(VideoModuleController());
final ChatController chatController = Get.find<ChatController>();
@ -731,9 +726,7 @@ class _RecommendModuleState extends State<RecommendModule> {
Future<void> doUnLikeVideo(item) async {
logger.d('点击了点赞按钮$item');
try {
final res = await Http.post(VideoApi.unlike, data:{
'vlogId': item['id']
});
final res = await Http.post(VideoApi.unlike, data: {'vlogId': item['id']});
final resCode = res['code'];
if (resCode == 200) {
item['doILikeThisVlog'] = !item['doILikeThisVlog'];
@ -746,9 +739,7 @@ class _RecommendModuleState extends State<RecommendModule> {
//
Future<void> doLikeVideo(item) async {
try {
final res = await Http.post(VideoApi.like, data:{
'vlogId': item['id']
});
final res = await Http.post(VideoApi.like, data: {'vlogId': item['id']});
final resCode = res['code'];
if (resCode == 200) {
item['doILikeThisVlog'] = !item['doILikeThisVlog'];
@ -760,7 +751,7 @@ class _RecommendModuleState extends State<RecommendModule> {
}
//
void handleComment(index) {
void handleComment(index) {
final videoId = videoList[index]['id'];
logger.i('点击了评论按钮$videoId');
@ -784,11 +775,10 @@ void handleComment(index) {
videoList[index]['commentsCounts'] = newCount;
}
});
}
);
});
},
);
}
}
//
void handleShare(index) {
@ -843,19 +833,20 @@ void handleComment(index) {
),
//
if (chatController.chatList.where((item) => item.conversation.conversationGroupList?.isEmpty == true).isNotEmpty)
SizedBox(
height: 110,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: chatController.chatList.where((item) => item.conversation.conversationGroupList?.isEmpty == true).length,
padding: EdgeInsets.symmetric(horizontal: 0, vertical: 20.0),
padding: const EdgeInsets.symmetric(horizontal: 0, vertical: 20.0),
itemBuilder: (context, index) {
final filteredList = chatController.chatList.where((item) => item.conversation.conversationGroupList?.isEmpty == true).toList();
return GestureDetector(
onTap: () => handlCoverClick(filteredList[index].conversation),
child: Container(
width: 64,
margin: EdgeInsets.symmetric(horizontal: 8.0),
margin: const EdgeInsets.symmetric(horizontal: 8.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
@ -864,10 +855,10 @@ void handleComment(index) {
width: 48.0,
height: 48.0,
),
SizedBox(height: 5),
const SizedBox(height: 5),
Text(
'${filteredList[index].conversation.showName}',
style: TextStyle(fontSize: 12.0),
filteredList[index].conversation.showName ?? '',
style: const TextStyle(fontSize: 12.0),
overflow: TextOverflow.ellipsis,
),
],
@ -909,21 +900,22 @@ void handleComment(index) {
final videoUrl = videoList[videoModuleController.videoPlayIndex.value]['url'];
final description = videoList[videoModuleController.videoPlayIndex.value]['title'] ?? '快来看看这个视频';
var httpPrefix = 'http://43.143.227.203/adv';
logger.i('分享链接地址----------------: ${httpPrefix}/goods-detail?id=${videoId}');
logger.i('分享链接地址----------------: $httpPrefix/goods-detail?id=$videoId');
if (index == 0) {
//
Wxsdk.shareToFriend(title: '快来看看这个视频', description: description, webpageUrl: '${httpPrefix}/video-detail?id=${videoId}');
Wxsdk.shareToFriend(title: '快来看看这个视频', description: description, webpageUrl: '$httpPrefix/video-detail?id=$videoId');
} else if (index == 1) {
//
Wxsdk.shareToTimeline(title: '快来看看这个视频', webpageUrl: '${httpPrefix}/goods-detail?id=${videoId}');
Wxsdk.shareToTimeline(title: '快来看看这个视频', webpageUrl: '$httpPrefix/goods-detail?id=$videoId');
} else if (index == 2) {
//
copyToClipboard(videoUrl);
} else if (index == 3) {
//
_downloadVideoWithDio(videoUrl,description);
_downloadVideoWithDio(videoUrl, description);
}
}
//
void copyToClipboard(String text) async {
try {
@ -1080,7 +1072,6 @@ void handleComment(index) {
height: double.infinity,
),
),
StreamBuilder(
stream: player.stream.playing,
builder: (context, playing) {
@ -1123,20 +1114,16 @@ void handleComment(index) {
height: 55.0,
width: 48.0,
child: GestureDetector(
onTap: ()async {
onTap: () async {
player.pause();
// Vloger
final result = await Get.toNamed(
'/vloger',
arguments: videoList[videoModuleController
.videoPlayIndex.value]);
final result = await Get.toNamed('/vloger', arguments: videoList[videoModuleController.videoPlayIndex.value]);
if (result != null) {
//
print('返回的数据: ${result['followStatus']}');
player.play();
videoList[index]['doIFollowVloger'] = result['followStatus'];
};
}
},
child: UnconstrainedBox(
alignment: Alignment.topCenter,
@ -1177,7 +1164,7 @@ void handleComment(index) {
final vlogerId = videoList[index]['memberId'];
final doIFollowVloger = videoList[index]['doIFollowVloger'];
//
if(doIFollowVloger == false){
if (doIFollowVloger == false) {
final res = await ImService.instance.followUser(userIDList: [vlogerId]);
if (res.success) {
setState(() {
@ -1207,10 +1194,10 @@ void handleComment(index) {
),
onTap: () {
logger.d('点击了点赞按钮${videoList[index]['doILikeThisVlog']}');
if(videoList[index]['doILikeThisVlog'] == true){
if (videoList[index]['doILikeThisVlog'] == true) {
logger.d('点击了点赞按钮${videoList[index]['doILikeThisVlog']}');
doUnLikeVideo(videoList[index]);
}else{
} else {
doLikeVideo(videoList[index]);
}
},

View File

@ -7,6 +7,9 @@ import 'package:loopin/bings/chat_binding.dart';
import 'package:loopin/pages/chat/chat.dart';
import 'package:loopin/pages/chat/chat_group.dart';
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/my/des.dart';
import 'package:loopin/pages/my/nick_name.dart';
import 'package:loopin/pages/my/setting.dart';
@ -28,7 +31,6 @@ import '../utils/common.dart';
final Map<String, Widget> routes = {
'/': const Layout(),
'/goods': const Goods(),
// '/chat': const Chat(),
// '/chatNoFriend': const ChatNoFriend(),
// '/chatGroup': const ChatGroup(),
'/order': const Order(),
@ -42,6 +44,10 @@ final Map<String, Widget> routes = {
'/about': const Setting(),
'/des': const Des(),
'/nickName': const NickName(),
//
'/noFriend': const Nofriend(),
'/system': const System(),
'/interaction': const Interaction(),
};
final List<GetPage> routeList = routes.entries

View File

@ -8,7 +8,9 @@ class HttpConfig {
// baseUrl: 'http://43.143.227.203:8099',
// baseUrl: 'http://111.62.22.190:8080',
// baseUrl: 'http://cjh.wuzhongjie.com.cn',
baseUrl: 'http://82.156.121.2:8880',
// baseUrl: 'http://82.156.121.2:8880',
baseUrl: 'http://192.168.1.65:8880',
// connectTimeout: Duration(seconds: 30),
// receiveTimeout: Duration(seconds: 30),
));

View File

@ -134,6 +134,7 @@ class Utils {
return number.toStringAsFixed(1).replaceAll(RegExp(r'\.0$'), '');
}
//
String formatChatTime(int timestamp) {
// timestamp DateTime
DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000);
@ -157,6 +158,23 @@ class Utils {
}
}
// IM消息时间显示
static String formatTime(int timestamp) {
//
final messageTime = DateTime.fromMillisecondsSinceEpoch(
timestamp * 1000,
).toLocal();
//
final now = DateTime.now();
if (messageTime.year == now.year) {
// -
return '${messageTime.month.toString().padLeft(2, '0')}-${messageTime.day.toString().padLeft(2, '0')}';
} else {
// -- :
return '${messageTime.year}-${messageTime.month.toString().padLeft(2, '0')}-${messageTime.day.toString().padLeft(2, '0')}';
}
}
String _formatHourMinute(DateTime dt) {
final hour = dt.hour.toString().padLeft(2, '0');
final minute = dt.minute.toString().padLeft(2, '0');

View File

@ -1,5 +1,6 @@
import 'dart:convert';
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';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message.dart';
@ -31,9 +32,26 @@ String parseMessageSummary(V2TimMessage msg) {
}
}
// newFoucs, //
// systemNotify, // ->
// systemReport, // ->
// systemCheck, // ->
// systemPush, //->广
// interactionComment, //->
// interactionAt, //->@
// interactionLike, //->
// interactionReply, //->
// orderRecharge, //-> online
// orderPay, //-> online
// orderRefund, //->退
// groupNotifyCheck, //-> online
// groupNotifyAccpet, // -> online
// groupNotifyFail, // -> online
// groupNotifyLeaveUp, // ->
String _parseCustomMessage(V2TimMessage? msg) {
if (msg == null) return '[null]';
final sum = msg.cloudCustomData;
final elment = msg.customElem; //
try {
switch (sum) {
case SummaryType.hongbao:
@ -43,6 +61,115 @@ String _parseCustomMessage(V2TimMessage? msg) {
return '[分享商品]';
case SummaryType.shareVideo:
return '[分享视频]';
//
/// [des]
/// [data] json数据
/// firstFrameImgcoverfirstFrameImg
case NotifyMessageTypeConstants.newFoucs:
//
///[userID],
///[nickName],
///[faceUrl],
///------
///
return elment!.desc!;
case NotifyMessageTypeConstants.systemNotify:
///
return elment!.desc!;
case NotifyMessageTypeConstants.systemReport:
//
///
///[vlogID],
///[title],
///[firstFrameImg]
return elment!.desc!;
case NotifyMessageTypeConstants.systemCheck:
//
///
///[vlogID]
///[title],
///[firstFrameImg]
return elment!.desc!;
case NotifyMessageTypeConstants.systemPush:
///广
return elment!.desc!;
case NotifyMessageTypeConstants.interactionComment:
//
/// [userID]
/// [faceUrl]
/// [nickName]
/// [comment]
/// id[commentID]
/// [vlogID]
/// [firstFrameImg]
return elment!.desc!;
case NotifyMessageTypeConstants.interactionAt:
// @
/// @[userID]
/// @[nickName]
/// @[faceUrl]
/// [firstFrameImg]
/// [vlogID]
/// id[commentID]
return elment!.desc!;
case NotifyMessageTypeConstants.interactionLike:
//
/// [userID]
/// [faceUrl]
/// [nickName]
/// [vlogID]
/// [firstFrameImg]
return elment!.desc!;
case NotifyMessageTypeConstants.interactionReply:
//
/// [userID]
/// [faceUrl]
/// [nickName]
/// [vlogID]
/// [firstFrameImg]
/// [commentID]
/// [comment]
return elment!.desc!;
case NotifyMessageTypeConstants.orderRecharge:
//
/// [orderID]
/// [amount]
/// [totalAmount]
return elment!.desc!;
case NotifyMessageTypeConstants.orderPay:
//
/// [orderID]
/// [amount]
/// [name]
/// [describe]
/// [price]
/// [pic]
return elment!.desc!;
case NotifyMessageTypeConstants.orderRefund:
// 退
/// [orderID]
/// [amount]
/// [name]
/// [describe]
/// [price]
/// [pic]
return elment!.desc!;
case NotifyMessageTypeConstants.groupNotifyCheck:
return elment!.desc!;
case NotifyMessageTypeConstants.groupNotifyAccpet:
return elment!.desc!;
case NotifyMessageTypeConstants.groupNotifyFail:
return elment!.desc!;
case NotifyMessageTypeConstants.groupNotifyLeaveUp:
return elment!.desc!;
default:
return '[未知消息类型2]';
}

View File

@ -1274,6 +1274,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.7.4"
timer_count_down:
dependency: "direct main"
description:
name: timer_count_down
sha256: d025d408c2654e497ca0bd4bde014bd7509d4c6397af4ed23a0f9b692bbcf337
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.2.2"
typed_data:
dependency: transitive
description:

View File

@ -87,6 +87,7 @@ dependencies:
record: ^6.0.0 #音频
audioplayers: ^6.5.0 #音频播放
flutter_html: ^3.0.0
timer_count_down: ^2.2.2 #倒计时
dev_dependencies:
flutter_launcher_icons: ^0.13.1 # 使用最新版本