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

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,31 +13,41 @@ class ChatController extends GetxController {
final chatList = <ConversationViewModel>[].obs; final chatList = <ConversationViewModel>[].obs;
void initChatData() { void initChatData() {
chatList.value = <ConversationViewModel>[]; // chatList.value = <ConversationViewModel>[];
chatList.clear();
nextSeq.value = '0'; nextSeq.value = '0';
isFinished.value = false; isFinished.value = false;
} }
// //
void getConversationList() async { void getConversationList() async {
logger.e('开始获取会话列表数据');
if (isFinished.value) { if (isFinished.value) {
// //
logger.e('会话触底无数据');
return; return;
} }
final res = await ImService.instance.getConversationList(nextSeq.value, count.value); 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; final List<ConversationViewModel> convList = res.data;
for (var conv in convList) { 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); chatList.addAll(convList);
// noFriend才执行加载数据逻辑, // noFriend才执行加载数据逻辑,
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);
if (!hasNoFriend) { if (!hasNoFriend) {
getNoFriendData(); getNoFriendData();
}
} catch (e) {
logger.e('获取会话异常::$e');
} }
} }
@ -102,7 +112,7 @@ class ChatController extends GetxController {
} }
/// getConversationListByFilter /// getConversationListByFilter
// void getConversationList() async { // void filterConversationList() async {
// final res = await ImService.instance.getConversationListByFilter( // final res = await ImService.instance.getConversationListByFilter(
// filter: V2TimConversationFilter(conversationGroup: null), // filter: V2TimConversationFilter(conversationGroup: null),
// nextSeq: nextSeq.value, // nextSeq: nextSeq.value,

View File

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

View File

@ -16,6 +16,10 @@ class GlobalBadge extends GetxController {
/// add/remove /// add/remove
late final V2TimConversationListener _listener; late final V2TimConversationListener _listener;
void rest() {
totalUnread.value = 0;
}
@override @override
void onInit() { void onInit() {
super.onInit(); super.onInit();
@ -34,12 +38,16 @@ class GlobalBadge extends GetxController {
onConversationChanged: (List<V2TimConversation> conversationList) async { onConversationChanged: (List<V2TimConversation> conversationList) async {
logger.w('会话变更:会话分组:${conversationList.first.conversationGroupList},会话内容${conversationList.first.toLogString()}'); logger.w('会话变更:会话分组:${conversationList.first.conversationGroupList},会话内容${conversationList.first.toLogString()}');
final ctl = Get.find<ChatController>(); final ctl = Get.find<ChatController>();
logger.w('当前会话列表内容:${ctl.chatList.toJson()}');
final updatedIds = conversationList.map((e) => e.conversationID).toSet(); final updatedIds = conversationList.map((e) => e.conversationID).toSet();
logger.w('要变更的会话id$updatedIds'); logger.w('要变更的会话id$updatedIds');
for (int i = 0; i < ctl.chatList.length; i++) { for (int i = 0; i < ctl.chatList.length; i++) {
final chatItem = ctl.chatList[i]; final chatItem = ctl.chatList[i];
logger.w('需要更新的ID:${chatItem.conversation.conversationID}'); logger.w('需要更新的ID:${chatItem.conversation.conversationID}');
if (updatedIds.contains(chatItem.conversation.conversationID)) { if (updatedIds.contains(chatItem.conversation.conversationID)) {
logger.w('找到的chatList的ID:${chatItem.conversation.conversationID}');
final updatedConv = conversationList.firstWhere( final updatedConv = conversationList.firstWhere(
(c) => c.conversationID == chatItem.conversation.conversationID, (c) => c.conversationID == chatItem.conversation.conversationID,
orElse: () => V2TimConversation(conversationID: ''), orElse: () => V2TimConversation(conversationID: ''),
@ -57,10 +65,20 @@ class GlobalBadge extends GetxController {
chatItem.conversation.unreadCount = unread.data; // chatItem.conversation.unreadCount = unread.data; //
} else { } else {
// //
logger.w('不需要分组的会话,正常更新');
chatItem.conversation = updatedConv; chatItem.conversation = updatedConv;
update();
} }
} }
} }
//
if (ctl.chatList.isEmpty) {
//
logger.w('重新获取会话');
ctl.initChatData();
ctl.getConversationList();
}
// //
ctl.chatList.sort((a, b) { ctl.chatList.sort((a, b) {
final atime = a.conversation.lastMessage?.timestamp ?? 0; final atime = a.conversation.lastMessage?.timestamp ?? 0;
@ -117,7 +135,7 @@ class GlobalBadge extends GetxController {
} }
} }
} else { } else {
logger.w('不需要进行分组'); logger.w('新会话不需分组');
} }
} }
@ -129,9 +147,21 @@ class GlobalBadge extends GetxController {
Get.find<TabBarController>().setBadge(TabType.chat, totalUnread.value); Get.find<TabBarController>().setBadge(TabType.chat, totalUnread.value);
final to = res.data; final to = res.data;
logger.i('初始化未读消息数$to'); 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() { void _addListener() {
TencentImSDKPlugin.v2TIMManager.getConversationManager().addConversationListener(listener: _listener); TencentImSDKPlugin.v2TIMManager.getConversationManager().addConversationListener(listener: _listener);
@ -146,6 +176,8 @@ class GlobalBadge extends GetxController {
/// ///
@override @override
void onClose() { void onClose() {
logger.i(_listener);
logger.i('移除global未读监听器');
TencentImSDKPlugin.v2TIMManager.getConversationManager().removeConversationListener(listener: _listener); TencentImSDKPlugin.v2TIMManager.getConversationManager().removeConversationListener(listener: _listener);
super.onClose(); 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: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/V2TimSDKListener.dart';
import 'package:tencent_cloud_chat_sdk/enum/log_level_enum.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'; import 'package:tencent_cloud_chat_sdk/models/v2_tim_user_full_info.dart';
@ -9,6 +14,18 @@ final logger = Logger();
class ImCore { class ImCore {
static bool _isInitialized = false; 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 { static Future<bool> init({required int sdkAppId}) async {
if (_isInitialized) return true; if (_isInitialized) return true;
@ -20,7 +37,18 @@ class ImCore {
logger.i("IM连接成功"); logger.i("IM连接成功");
}, },
onConnectFailed: (code, error) => logger.e("IM连接失败: $code $error"), 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 过期"), onUserSigExpired: () => logger.w("UserSig 过期"),
onSelfInfoUpdated: (V2TimUserFullInfo info) { onSelfInfoUpdated: (V2TimUserFullInfo info) {
logger.i("用户信息更新: ${info.toJson()}"); 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_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.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message_change_info.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_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_user_info_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_value_callback.dart'; import 'package:tencent_cloud_chat_sdk/models/v2_tim_value_callback.dart';
@ -70,7 +72,12 @@ class ImService {
await Wxsdk.init(); await Wxsdk.init();
// (+) // (+)
Get.put(ImUserInfoController(), permanent: true); if (!Get.isRegistered<ImUserInfoController>()) {
Get.put(ImUserInfoController(), permanent: true);
} else {
Get.find<ImUserInfoController>().refreshUserInfo();
}
// //
final messageService = ImMessageListenerService(); final messageService = ImMessageListenerService();
Get.put<ImMessageListenerService>(messageService, permanent: true); Get.put<ImMessageListenerService>(messageService, permanent: true);
@ -101,16 +108,12 @@ class ImService {
Future<ImResult> logout() async { Future<ImResult> logout() async {
final res = await TencentImSDKPlugin.v2TIMManager.logout(); final res = await TencentImSDKPlugin.v2TIMManager.logout();
if (res.code == 0) { if (res.code == 0) {
///
Get.delete<ImUserInfoController>(force: true);
/// ///
Get.find<ImMessageListenerService>().onClose(); await Get.delete<ImMessageListenerService>(force: true);
Get.delete<ImMessageListenerService>(force: true);
/// ///
Get.find<ImFriendListeners>().unregister(); Get.find<ImFriendListeners>().unregister();
Get.delete<ImFriendListeners>(force: true); await Get.delete<ImFriendListeners>(force: true);
/// tabbar /// tabbar
Get.find<TabBarController>().badgeMap.clear(); Get.find<TabBarController>().badgeMap.clear();
@ -120,13 +123,13 @@ class ImService {
/// ///
Get.find<GlobalBadge>().onClose(); 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); return ImResult.wrapNoData(res);
} }
@ -231,26 +234,26 @@ class ImService {
} }
final convList = res.data?.conversationList ?? []; final convList = res.data?.conversationList ?? [];
logger.w('未过滤前到会话数据:$convList');
final userIDList = <String>[]; final userIDList = <String>[];
final groupIDList = <String>[]; final groupIDList = <String>[];
// userID groupID // userID groupID
for (var conv in convList) { for (var conv in convList) {
if ((conv.faceUrl == null || conv.faceUrl!.isEmpty)) { if (conv.userID != null) {
if (conv.userID != null) { userIDList.add(conv.userID!);
userIDList.add(conv.userID!); } else if (conv.groupID != null) {
} else if (conv.groupID != null) { groupIDList.add(conv.groupID!);
groupIDList.add(conv.groupID!);
}
} }
} }
Map<String, String?> userFaceUrlMap = {}; Map<String, String?> userFaceUrlMap = {};
Map<String, String?> groupFaceUrlMap = {}; Map<String, String?> groupFaceUrlMap = {};
String isCustomAdmin = '0'; Map<String, String> isCustomAdmin = {};
if (userIDList.isNotEmpty) { if (userIDList.isNotEmpty) {
final userRes = await TencentImSDKPlugin.v2TIMManager.getUsersInfo(userIDList: userIDList); final userRes = await TencentImSDKPlugin.v2TIMManager.getUsersInfo(userIDList: userIDList);
if (userRes.code == 0) { if (userRes.code == 0) {
for (var user in userRes.data!) { for (var user in userRes.data!) {
final userId = user.userID ?? ''; final userId = user.userID ?? '';
@ -258,8 +261,9 @@ class ImService {
// //
final customInfo = user.customInfo; final customInfo = user.customInfo;
if (customInfo != null) { if (customInfo != null) {
isCustomAdmin = customInfo['admin'] ?? '0'; isCustomAdmin[userId] = customInfo['admin'] ?? '0';
} }
} }
} }
@ -280,7 +284,6 @@ class ImService {
final viewList = convList.map((conv) { final viewList = convList.map((conv) {
String? faceUrl = conv.faceUrl; String? faceUrl = conv.faceUrl;
// faceUrl map
if (faceUrl == null || faceUrl.isEmpty) { if (faceUrl == null || faceUrl.isEmpty) {
if (conv.userID != null) { if (conv.userID != null) {
faceUrl = userFaceUrlMap[conv.userID!]; 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(); }).toList();
// , // ,
@ -337,9 +344,6 @@ class ImService {
/// ///
Future<ImResult<V2TimConversationResult>> getConvData(String nextSeq, int count) async { Future<ImResult<V2TimConversationResult>> getConvData(String nextSeq, int count) async {
final res = await TencentImSDKPlugin.v2TIMManager.getConversationManager().getConversationList(nextSeq: nextSeq, count: count); 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); 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({ Future<ImResult<List<V2TimMessage>>> findMessages({
required List<String> messageIDList, required List<String> messageIDList,

View File

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

View File

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

View File

@ -26,6 +26,18 @@ class NetworkOrAssetImage extends StatelessWidget {
width: width, width: width,
height: height, height: height,
fit: fit, 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) { errorBuilder: (context, error, stackTrace) {
return Image.asset( return Image.asset(
placeholderAsset, placeholderAsset,

View File

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

View File

@ -3,7 +3,6 @@ library;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:loopin/IM/controller/chat_controller.dart';
import 'package:loopin/IM/controller/tab_bar_controller.dart'; import 'package:loopin/IM/controller/tab_bar_controller.dart';
import 'package:loopin/models/tab_type.dart'; import 'package:loopin/models/tab_type.dart';
import 'package:loopin/pages/video/module/recommend.dart'; import 'package:loopin/pages/video/module/recommend.dart';
@ -222,10 +221,11 @@ class _LayoutState extends State<Layout> {
} }
if (index == 3) { if (index == 3) {
// //
final ctl = Get.find<ChatController>(); // final ctl = Get.find<ChatController>();
if (ctl.chatList.isEmpty) { // logger.e(ctl.chatList.toJson());
Get.find<ChatController>().getConversationList(); // if (ctl.chatList.isEmpty) {
} // Get.find<ChatController>().getConversationList();
// }
} }
if (index == 4) { if (index == 4) {
myPageKey.currentState?.refreshData(); 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/controller/tab_bar_controller.dart';
import 'package:loopin/IM/im_core.dart' as im_core; import 'package:loopin/IM/im_core.dart' as im_core;
import 'package:loopin/IM/im_service.dart'; 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/service/http_config.dart';
import 'package:loopin/utils/common.dart'; import 'package:loopin/utils/common.dart';
import 'package:loopin/utils/lifecycle_handler.dart'; import 'package:loopin/utils/lifecycle_handler.dart';
@ -21,13 +22,14 @@ import 'package:shirne_dialog/shirne_dialog.dart';
import 'layouts/index.dart'; import 'layouts/index.dart';
// //
import 'router/index.dart'; import 'router/index.dart';
// import 'utils/common.dart';
void main() async { void main() async {
// tabbar // tabbar
Get.put(TabBarController()); Get.put(TabBarController(), permanent: true);
// //
Get.put(ChatController()); Get.put(ChatController(), permanent: true);
//
Get.put(ShopIndexController(), permanent: true);
// app前后台状态 // app前后台状态
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();

View File

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

View File

@ -4,7 +4,7 @@ enum NotifyMessageType {
systemNotify, // -> systemNotify, // ->
systemReport, // -> systemReport, // ->
systemCheck, // -> systemCheck, // ->
systemPush, //->广 systemPush, //->广
interactionComment, //-> interactionComment, //->
interactionAt, //->@ interactionAt, //->@
interactionLike, //-> interactionLike, //->
@ -18,6 +18,26 @@ enum NotifyMessageType {
groupNotifyLeaveUp, // -> 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 { extension NotifyMessageTypeExtension on NotifyMessageType {
String get name { String get name {
switch (this) { 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,36 +72,38 @@ class _LoginState extends State<Login> {
String userId = obj['userId']; String userId = obj['userId'];
String userSig = obj['userSig']; String userSig = obj['userSig'];
// im_sdk // 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 =
// 'eJwtjcEKgkAURf9l1iFPm*e8EdoYYUWFURAtg5nk5VRiEln0703q8p57Ofcj9qtd8LS1SEQUgBh1mY29NXzmDodaQhwrBRIJI0kq1sPsYcpTVbERSRgDAIEi3Tf2VXFtPUfEyFc9bfj6ZwrH4J1Ig4UL-6LX0ihyS7U5bi-Wzd8LzrK8TFs6TJ1sZwWGxlGas71PxPcHwH4y9Q__';
// 'eJwtzLEKwjAUheF3ySwlNzXNbcHFxSIOaqTWUUgsF1FDG2tEfHdj2-F8P5wPO2x00tuWFUwknM2GTcbePV1oYEBMhQSeopxyZ65n58iwAjLOOXKF*VhscNTa6FJKEdOonm5-UxJQpZhN2lET3599Xllbv9ZBH2uHuDfvst5tG6FX0EFYVhpOpZ973z8W7PsDmYwyIw__';
// String userId = '1940667704585248769'; //13212279365 try {
// String userId = '1943510443312078850'; //18832510385 final loginRes = await ImService.instance.login(userID: userId, userSig: userSig);
// String userSig =
// 'eJwtjcEKgkAURf9l1iFPm*e8EdoYYUWFURAtg5nk5VRiEln0703q8p57Ofcj9qtd8LS1SEQUgBh1mY29NXzmDodaQhwrBRIJI0kq1sPsYcpTVbERSRgDAIEi3Tf2VXFtPUfEyFc9bfj6ZwrH4J1Ig4UL-6LX0ihyS7U5bi-Wzd8LzrK8TFs6TJ1sZwWGxlGas71PxPcHwH4y9Q__';
// 'eJwtzLEKwjAUheF3ySwlNzXNbcHFxSIOaqTWUUgsF1FDG2tEfHdj2-F8P5wPO2x00tuWFUwknM2GTcbePV1oYEBMhQSeopxyZ65n58iwAjLOOXKF*VhscNTa6FJKEdOonm5-UxJQpZhN2lET3599Xllbv9ZBH2uHuDfvst5tG6FX0EFYVhpOpZ973z8W7PsDmYwyIw__';
try { if (loginRes.success) {
final loginRes = await ImService.instance.login(userID: userId, userSig: userSig); //
Storage.write('hasLogged', true);
Storage.write('userSig', userSig);
Storage.write('userId', userId);
Storage.write('token', obj['access_token']);
//
// final accountRes = await Http.get(CommonApi.accountInfo);
// logger.e(accountRes);
//
final videoController = Get.find<VideoModuleController>();
videoController.markNeedRefresh();
dialogController.close();
logger.i(loginRes); Get.back();
if (loginRes.success) { }
// } catch (e) {
Storage.write('hasLogged', true); logger.i(e);
Storage.write('userSig', userSig);
Storage.write('userId', userId);
Storage.write('token', obj['access_token']);
//
final accountRes = await Http.get('${CommonApi.accountInfo}');
logger.i(accountRes);
//
final videoController = Get.find<VideoModuleController>();
videoController.markNeedRefresh();
dialogController.close();
Get.back();
} }
} catch (e) { } else {
logger.i(e); 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/controller/chat_controller.dart';
import 'package:loopin/IM/global_badge.dart'; import 'package:loopin/IM/global_badge.dart';
import 'package:loopin/IM/im_service.dart'; import 'package:loopin/IM/im_service.dart';
import 'package:loopin/components/network_or_asset_image.dart';
import 'package:loopin/components/scan_util.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/models/conversation_view_model.dart';
import 'package:loopin/utils/parse_message_summary.dart'; import 'package:loopin/utils/parse_message_summary.dart';
import 'package:shirne_dialog/shirne_dialog.dart'; import 'package:shirne_dialog/shirne_dialog.dart';
@ -334,35 +336,10 @@ class ChatPageState extends State<ChatPage> {
children: <Widget>[ children: <Widget>[
// //
ClipOval( ClipOval(
child: Builder( child: NetworkOrAssetImage(
builder: (context) { imageUrl: chatList[index].faceUrl,
final faceUrl = chatList[index].faceUrl; width: 50,
final isNetwork = height: 50,
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,
);
}
},
), ),
), ),
@ -371,17 +348,17 @@ class ChatPageState extends State<ChatPage> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
// Text(
// chatList[index].conversation.showName ?? '',
// style: const TextStyle(fontSize: 16.0),
// ),
Text( Text(
chatList[index].conversation.showName ?? '', chatList[index].conversation.showName ?? '未知',
style: TextStyle( style: TextStyle(
fontSize: (chatList[index].conversation.conversationGroupList?.isNotEmpty ?? false) ? 20 : 16, fontSize: (chatList[index].conversation.conversationGroupList!.contains(ConversationType.noFriend.name)) ||
fontWeight: chatList[index].isCustomAdmin != '0'
(chatList[index].conversation.conversationGroupList?.isNotEmpty ?? false) ? FontWeight.bold : FontWeight.normal, ? 20
), : 16,
fontWeight: (chatList[index].conversation.conversationGroupList!.contains(ConversationType.noFriend.name)) ||
chatList[index].isCustomAdmin != '0'
? FontWeight.bold
: FontWeight.normal),
), ),
const SizedBox(height: 2.0), const SizedBox(height: 2.0),
Text( Text(
@ -400,7 +377,8 @@ class ChatPageState extends State<ChatPage> {
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[ children: <Widget>[
Visibility( Visibility(
visible: chatList[index].conversation.lastMessage?.timestamp != null, visible: !(chatList[index].isCustomAdmin != '0' ||
chatList[index].conversation.conversationGroupList!.contains(ConversationType.noFriend.name)),
child: Text( child: Text(
// //
DateTime.fromMillisecondsSinceEpoch( DateTime.fromMillisecondsSinceEpoch(
@ -418,19 +396,26 @@ class ChatPageState extends State<ChatPage> {
], ],
), ),
Visibility( Visibility(
visible: chatList[index].isCustomAdmin != '0', visible: chatList[index].isCustomAdmin != '0' ||
chatList[index].conversation.conversationGroupList!.contains(ConversationType.noFriend.name),
child: const Icon( child: const Icon(
Icons.arrow_forward_ios, Icons.arrow_forward_ios,
color: Colors.grey, color: Colors.blueGrey,
size: 12.0, size: 14.0,
), ),
), ),
], ],
), ),
), ),
onTap: () { 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 { } else {
// id查询会话详情 // id查询会话详情
Get.toNamed('/chat', arguments: chatList[index].conversation); 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/api/shop_api.dart';
import 'package:loopin/components/my_toast.dart'; import 'package:loopin/components/my_toast.dart';
import 'package:loopin/components/network_or_asset_image.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/models/summary_type.dart';
import 'package:loopin/service/http.dart'; import 'package:loopin/service/http.dart';
import 'package:loopin/utils/index.dart'; import 'package:loopin/utils/index.dart';
@ -80,10 +81,10 @@ class _GoodsState extends State<Goods> {
final description = shopObj['describe']; // final description = shopObj['describe']; //
if (index == 1) { 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) { } 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( GestureDetector(
onTap: () async { onTap: () async {
// //
logger.i('联系客服'); 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; V2TimConversation conversation = res.data;
logger.i(conversation.toLogString()); logger.i(conversation.toLogString());
if (res.success) { if (res.success) {
@ -619,9 +620,16 @@ class _GoodsState extends State<Goods> {
alignment: Alignment.center, alignment: Alignment.center,
padding: const EdgeInsets.symmetric(horizontal: 20.0), padding: const EdgeInsets.symmetric(horizontal: 20.0),
color: Color(0xFFFF5000), color: Color(0xFFFF5000),
child: Text( child: GestureDetector(
'立即购买', onTap: () {
style: TextStyle(color: Colors.white, fontSize: 14.0), // 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:get/get.dart';
import 'package:loopin/components/backtop.dart'; import 'package:loopin/components/backtop.dart';
import 'package:loopin/components/loading.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/controller/shop_index_controller.dart';
import 'package:loopin/utils/index.dart'; import 'package:loopin/utils/index.dart';
@ -38,7 +39,7 @@ class _IndexPageState extends State<IndexPage> with SingleTickerProviderStateMix
// } // }
// ]; // ];
final ScrollController pageScrollController = ScrollController(); final ScrollController pageScrollController = ScrollController();
final ShopIndexController controller = Get.put(ShopIndexController()); late ShopIndexController controller;
// //
Widget cardList(item) { Widget cardList(item) {
@ -55,7 +56,12 @@ class _IndexPageState extends State<IndexPage> with SingleTickerProviderStateMix
]), ]),
child: Column( child: Column(
children: [ children: [
Image.network('${item['pic']}'), // Image.network(),
NetworkOrAssetImage(
imageUrl: '${item['pic']}',
width: double.infinity,
placeholderAsset: 'assets/images/bk.jpg',
),
Container( Container(
padding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 5.0), padding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 5.0),
child: Column( child: Column(
@ -106,109 +112,121 @@ class _IndexPageState extends State<IndexPage> with SingleTickerProviderStateMix
@override @override
void initState() { void initState() {
super.initState(); super.initState();
controller.initTabs(); controller = Get.find<ShopIndexController>();
controller.initTabs(vsync: this);
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Obx(() { return Scaffold(
final tabIndex = controller.currentTabIndex.value; backgroundColor: Colors.grey[50],
final currentTab = controller.tabs[tabIndex]; body: Column(
children: [
_buildTopSection(),
//
Expanded(
child: controller.tabController == null
? const Center(child: CircularProgressIndicator())
: TabBarView(
controller: controller.tabController,
children: List.generate(
controller.tabList.length,
(index) => _buildTabContent(index),
),
),
),
],
),
floatingActionButton: Obx(() {
final tabIndex = controller.currentTabIndex.value;
final currentTab = controller.tabs[tabIndex];
if (currentTab == null) return const SizedBox.shrink();
return Scaffold( return Backtop(
backgroundColor: Colors.grey[50], controller: currentTab.scrollController,
body: Column( offset: currentTab.scrollOffset.value,
children: [ );
// + TabBar }),
_buildTopSection(), );
//
Expanded(
child: TabBarView(
controller: controller.tabController,
children: controller.tabList.asMap().entries.map((entry) {
final index = entry.key;
return _buildTabContent(index);
}).toList(),
),
),
],
),
floatingActionButton: currentTab != null
? Backtop(
controller: currentTab.scrollController,
offset: currentTab.scrollOffset.value,
)
: null,
);
});
} }
// //
Widget _buildTopSection() { Widget _buildTopSection() {
double screenWidth = MediaQuery.of(context).size.width; if (controller.tabController == null) {
int tabCount = controller.tabList.length; return SizedBox();
// Tab }
double minTabWidth = 80;
//
bool isScrollable = tabCount * minTabWidth > screenWidth;
return Column( return Column(
children: [ children: [
// //
Container( Obx(() {
width: double.infinity, if (controller.swiperData.isEmpty) return const SizedBox.shrink();
height: 240, if (controller.swiperData.length == 1) {
decoration: const BoxDecoration( final imageUrl = controller.swiperData.first['images'] ?? '';
gradient: LinearGradient( return Image.network(imageUrl, fit: BoxFit.fill, width: double.infinity, height: 240);
begin: Alignment.topLeft, }
end: Alignment.bottomRight, return SizedBox(
colors: [Color(0xFFFF5000), Color(0xFFfcaec4)], width: double.infinity,
), height: 240,
), child: Swiper(
child: controller.swiperData.length <= 1 itemCount: controller.swiperData.length,
? (controller.swiperData.isNotEmpty autoplay: true,
? Image.network( loop: true,
controller.swiperData.first['images'] ?? '', pagination: const SwiperPagination(
fit: BoxFit.fill, builder: DotSwiperPaginationBuilder(
) color: Colors.white70,
: const SizedBox.shrink()) activeColor: Colors.white,
: Swiper(
itemCount: controller.swiperData.length,
autoplay: true,
loop: true,
pagination: SwiperPagination(
builder: DotSwiperPaginationBuilder(
color: Colors.white70,
activeColor: Colors.white,
),
),
itemBuilder: (context, index) {
final imageUrl = controller.swiperData[index]['images'] ?? '';
return imageUrl.isNotEmpty ? Image.network(imageUrl, fit: BoxFit.fill) : const SizedBox.shrink();
},
), ),
), ),
itemBuilder: (context, index) {
final imageUrl = controller.swiperData[index]['images'] ?? '';
return imageUrl.isNotEmpty ? Image.network(imageUrl, fit: BoxFit.fill) : const SizedBox.shrink();
},
),
);
}),
// TabBar // TabBar
Container( Obx(() {
color: Colors.white, int tabCount = controller.tabList.length;
child: TabBar( double screenWidth = MediaQuery.of(context).size.width;
controller: controller.tabController, double minTabWidth = 60;
tabs: controller.tabList.map((item) { bool isScrollable = tabCount * minTabWidth > screenWidth;
return Tab(
child: Text(item['name'], style: const TextStyle(fontWeight: FontWeight.bold)), return Container(
); color: Colors.white,
}).toList(), child: TabBar(
isScrollable: isScrollable, controller: controller.tabController,
overlayColor: WidgetStateProperty.all(Colors.transparent), tabs: controller.tabList.map((item) {
unselectedLabelColor: Colors.black87, return Tab(
labelColor: Color.fromARGB(255, 236, 108, 49), child: Text(
indicator: const UnderlineTabIndicator(borderSide: BorderSide(color: Color.fromARGB(255, 236, 108, 49), width: 2.0)), item['name'] ?? '',
unselectedLabelStyle: const TextStyle(fontSize: 16.0, fontFamily: 'Microsoft YaHei'), style: const TextStyle(fontWeight: FontWeight.bold),
labelStyle: const TextStyle(fontSize: 18.0, fontFamily: 'Microsoft YaHei', fontWeight: FontWeight.bold), overflow: TextOverflow.ellipsis,
dividerHeight: 0, softWrap: false,
), ),
), );
}).toList(),
isScrollable: isScrollable,
overlayColor: WidgetStateProperty.all(Colors.transparent),
unselectedLabelColor: Colors.black87,
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; library;
import 'dart:ui';
import 'package:card_swiper/card_swiper.dart'; import 'package:card_swiper/card_swiper.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
@ -118,7 +116,7 @@ class _IndexPageState extends State<IndexPage> with SingleTickerProviderStateMix
@override @override
void initState() { void initState() {
super.initState(); super.initState();
controller.initTabs(); controller.initTabs(vsync: this);
} }
@override @override

View File

@ -21,7 +21,7 @@ class PageParams {
int page; int page;
int pageSize; int pageSize;
bool isLoading; bool isLoading;
bool hasMore; RxBool hasMore;
int total; int total;
bool isInitLoading; bool isInitLoading;
@ -29,16 +29,16 @@ class PageParams {
this.page = 1, this.page = 1,
this.pageSize = 10, this.pageSize = 10,
this.isLoading = false, this.isLoading = false,
this.hasMore = true, bool hasMore = true,
this.total = 0, this.total = 0,
this.isInitLoading = true, this.isInitLoading = true,
}); }) : hasMore = hasMore.obs;
void init() { void init() {
page = 1; page = 1;
pageSize = 10; pageSize = 10;
isLoading = false; isLoading = false;
hasMore = true; hasMore.value = true;
total = 0; total = 0;
isInitLoading = true; isInitLoading = true;
} }
@ -86,7 +86,6 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
initControllers(); initControllers();
scrollListener = () { scrollListener = () {
@ -95,9 +94,9 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
if (!isNearBottom) return; if (!isNearBottom) return;
if (currentTabIndex.value == 0 && !itemsParams.isLoading && itemsParams.hasMore) { if (currentTabIndex.value == 0 && !itemsParams.isLoading && itemsParams.hasMore.value) {
loadData(0); loadData(0);
} else if (currentTabIndex.value == 1 && !favoriteParams.isLoading && favoriteParams.hasMore) { } else if (currentTabIndex.value == 1 && !favoriteParams.isLoading && favoriteParams.hasMore.value) {
loadData(1); loadData(1);
} }
}; };
@ -145,62 +144,58 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
Future<void> loadData([int? tabIndex]) async { Future<void> loadData([int? tabIndex]) async {
final index = tabIndex ?? currentTabIndex.value; final index = tabIndex ?? currentTabIndex.value;
if (index == 0) { if (index == 0) {
if (itemsParams.isLoading || !itemsParams.hasMore) return; if (itemsParams.isLoading || !itemsParams.hasMore.value) return;
itemsParams.isLoading = true; itemsParams.isLoading = true;
// itemsParams.isInitLoading = true; // itemsParams.isInitLoading = true;
try { final res = await Http.post(VideoApi.myPublicList, data: {
final res = await Http.post(VideoApi.myPublicList, data: { "userId": imUserInfoController?.userID.value,
"userId": imUserInfoController?.userID.value, "yesOrNo": 0,
"yesOrNo": 0, "current": itemsParams.page,
"current": itemsParams.page, "size": itemsParams.pageSize,
"size": itemsParams.pageSize, });
}); final obj = res['data'];
final obj = res['data']; final total = obj['total'];
final total = obj['total']; final row = obj['records'] ?? [];
final row = obj['rows']; logger.i(res['data']);
logger.i(res['data']); //
// logger.e(items.length);
if (items.length >= total) { //
itemsParams.hasMore = false; items.addAll(row);
} logger.e(obj);
// if (items.length >= total) {
items.addAll(row); itemsParams.hasMore.value = false;
//
itemsParams.page++;
} finally {
itemsParams.isLoading = false;
itemsParams.isInitLoading = false;
} }
//
itemsParams.page++;
//
itemsParams.isLoading = false;
itemsParams.isInitLoading = false;
} else if (index == 1) { } else if (index == 1) {
if (favoriteParams.isLoading || !favoriteParams.hasMore) return; if (favoriteParams.isLoading || !favoriteParams.hasMore.value) return;
favoriteParams.isLoading = true; favoriteParams.isLoading = true;
// favoriteParams.isInitLoading = true; // favoriteParams.isInitLoading = true;
try { final res = await Http.post(VideoApi.myLikedList, data: {
final res = await Http.post(VideoApi.myPublicList, data: { "userId": imUserInfoController?.userID.value,
"userId": imUserInfoController?.userID.value, "yesOrNo": 0,
"yesOrNo": 0, "current": favoriteParams.page,
"current": itemsParams.page, "size": favoriteParams.pageSize,
"size": itemsParams.pageSize, });
}); final obj = res['data'];
final obj = res['data']; final total = obj['total'];
final total = obj['total']; final row = obj['records'] ?? [];
final row = obj['rows']; favoriteItems.addAll(row);
if (favoriteItems.length >= total) { if (favoriteItems.length >= total) {
itemsParams.hasMore = false; favoriteParams.hasMore.value = false;
}
favoriteItems.addAll(row);
favoriteParams.page++;
} finally {
favoriteParams.isLoading = false;
favoriteParams.isInitLoading = false;
} }
favoriteParams.page++;
favoriteParams.isLoading = false;
favoriteParams.isInitLoading = false;
} }
} }
@ -237,7 +232,11 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
// //
void selfInfo() async { void selfInfo() async {
// imUserInfoController = Get.find<ImUserInfoController>(); // 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) { if (res.success) {
// //
// followersCount粉丝,mutualFollowersCount互关,followingCount我关注了多少人 // followersCount粉丝,mutualFollowersCount互关,followingCount我关注了多少人
@ -330,108 +329,106 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
} else { } else {
return Scaffold( return Scaffold(
backgroundColor: const Color(0xFFFAF6F9), backgroundColor: const Color(0xFFFAF6F9),
body: Obx(() { body: NestedScrollViewPlus(
return NestedScrollViewPlus( controller: scrollController,
controller: scrollController, physics: shouldFixHeader.value
physics: shouldFixHeader.value ? OnlyDownScrollPhysics(parent: AlwaysScrollableScrollPhysics())
? OnlyDownScrollPhysics(parent: AlwaysScrollableScrollPhysics()) : isPinned.value
: isPinned.value ? NeverScrollableScrollPhysics()
? NeverScrollableScrollPhysics() : AlwaysScrollableScrollPhysics(),
: AlwaysScrollableScrollPhysics(), // physics: shouldFixHeader.value ? OnlyDownScrollPhysics(parent: AlwaysScrollableScrollPhysics()) : AlwaysScrollableScrollPhysics(),
// physics: shouldFixHeader.value ? OnlyDownScrollPhysics(parent: AlwaysScrollableScrollPhysics()) : AlwaysScrollableScrollPhysics(), overscrollBehavior: OverscrollBehavior.outer,
overscrollBehavior: OverscrollBehavior.outer, headerSliverBuilder: (context, innerBoxIsScrolled) {
headerSliverBuilder: (context, innerBoxIsScrolled) { return [
return [ SliverAppBar(
SliverAppBar( backgroundColor: Colors.transparent,
backgroundColor: Colors.transparent, surfaceTintColor: Colors.transparent,
surfaceTintColor: Colors.transparent, expandedHeight: 180.0,
expandedHeight: 180.0, collapsedHeight: 120.0,
collapsedHeight: 120.0, pinned: true,
pinned: true, stretch: true,
stretch: true, onStretchTrigger: () async {
onStretchTrigger: () async { logger.i('触发 stretch 拉伸');
logger.i('触发 stretch 拉伸'); //
// },
}, actions: [
actions: [ // _buildIcon('assets/images/svg/service.svg', () {
// _buildIcon('assets/images/svg/service.svg', () { // logger.i('点击客服按钮');
// logger.i('点击客服按钮'); // }),
// }), const SizedBox(width: 8.0),
const SizedBox(width: 8.0), _buildIcon('assets/images/svg/setting.svg', () {
_buildIcon('assets/images/svg/setting.svg', () { logger.i('点击设置按钮');
logger.i('点击设置按钮'); Get.toNamed('/setting');
Get.toNamed('/setting'); }),
}), const SizedBox(width: 10.0),
const SizedBox(width: 10.0), ],
], flexibleSpace: _buildFlexibleSpace(),
flexibleSpace: _buildFlexibleSpace(), ),
), SliverToBoxAdapter(
SliverToBoxAdapter( child: Padding(
child: Padding( padding: const EdgeInsets.all(10.0),
padding: const EdgeInsets.all(10.0), child: Column(
child: Column( children: [
children: [ Obx(() => _buildStatsCard()),
Obx(() => _buildStatsCard()), const SizedBox(height: 10.0),
const SizedBox(height: 10.0), _buildOrderCard(context),
_buildOrderCard(context), const SizedBox(height: 10.0),
const SizedBox(height: 10.0), ],
],
),
), ),
), ),
SliverPersistentHeader( ),
pinned: true, SliverPersistentHeader(
delegate: CustomStickyHeader( pinned: true,
isPinned: isPinned, delegate: CustomStickyHeader(
positions: positions, isPinned: isPinned,
child: PreferredSize( positions: positions,
preferredSize: const Size.fromHeight(48.0), child: PreferredSize(
child: Container( preferredSize: const Size.fromHeight(48.0),
color: Colors.white, child: Container(
child: TabBar( color: Colors.white,
controller: tabController, child: TabBar(
tabs: tabList.map((item) { controller: tabController,
return Tab( tabs: tabList.map((item) {
child: Badge.count( return Tab(
backgroundColor: Colors.red, child: Badge.count(
count: item['badge'] ?? 0, backgroundColor: Colors.red,
isLabelVisible: item['badge'] != null, count: item['badge'] ?? 0,
alignment: Alignment.topRight, isLabelVisible: item['badge'] != null,
offset: const Offset(14, -6), alignment: Alignment.topRight,
child: Text(item['name'], style: const TextStyle(fontWeight: FontWeight.bold)), offset: const Offset(14, -6),
), child: Text(item['name'], style: const TextStyle(fontWeight: FontWeight.bold)),
); ),
}).toList(), );
isScrollable: false, }).toList(),
overlayColor: WidgetStateProperty.all(Colors.transparent), isScrollable: false,
unselectedLabelColor: Colors.black87, overlayColor: WidgetStateProperty.all(Colors.transparent),
labelColor: const Color(0xFFFF5000), unselectedLabelColor: Colors.black87,
indicator: const UnderlineTabIndicator(borderSide: BorderSide(color: Color(0xFFFF5000), width: 2.0)), labelColor: const Color(0xFFFF5000),
indicatorSize: TabBarIndicatorSize.tab, indicator: const UnderlineTabIndicator(borderSide: BorderSide(color: Color(0xFFFF5000), width: 2.0)),
unselectedLabelStyle: const TextStyle(fontSize: 16.0, fontFamily: 'Microsoft YaHei'), indicatorSize: TabBarIndicatorSize.tab,
labelStyle: const TextStyle(fontSize: 18.0, fontFamily: 'Microsoft YaHei', fontWeight: FontWeight.bold), unselectedLabelStyle: const TextStyle(fontSize: 16.0, fontFamily: 'Microsoft YaHei'),
dividerHeight: 0, labelStyle: const TextStyle(fontSize: 18.0, fontFamily: 'Microsoft YaHei', fontWeight: FontWeight.bold),
padding: const EdgeInsets.symmetric(horizontal: 10.0), dividerHeight: 0,
labelPadding: const EdgeInsets.symmetric(horizontal: 15.0), padding: const EdgeInsets.symmetric(horizontal: 10.0),
), labelPadding: const EdgeInsets.symmetric(horizontal: 15.0),
), ),
), ),
), ),
), ),
]; ),
}, ];
body: TabBarView( },
controller: tabController, body: TabBarView(
children: [ controller: tabController,
// Tab 1: children: [
_buildGridTab(0), // Tab 1:
_buildGridTab(0),
// Tab 2: // Tab 2:
_buildGridTab(1) _buildGridTab(1)
], ],
), ),
); ),
}),
); );
} }
}); });
@ -510,7 +507,7 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(vertical: 20.0), padding: const EdgeInsets.symmetric(vertical: 20.0),
child: Center( child: Center(
child: params.hasMore ? const CircularProgressIndicator() : const Text('没有更多数据了'), child: params.hasMore.value ? CircularProgressIndicator() : Text('没有更多数据了'),
), ),
), ),
), ),
@ -612,86 +609,108 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
Widget _buildFlexibleSpace() { Widget _buildFlexibleSpace() {
return LayoutBuilder( return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) { builder: (context, constraints) {
final double maxHeight = 180.0; final double maxHeight = 180;
final double minHeight = 120.0; final double minHeight = 120;
final double currentHeight = constraints.maxHeight; final currentHeight = constraints.maxHeight;
double ratio = (currentHeight - minHeight) / (maxHeight - minHeight); double ratio = ((currentHeight - minHeight) / (maxHeight - minHeight)).clamp(0.0, 1.0);
ratio = ratio.clamp(0.0, 1.0);
return Stack( return Stack(
fit: StackFit.expand, fit: StackFit.expand,
children: [ children: [
Positioned.fill( // Obx
child: Opacity( Obx(() {
opacity: 1.0, final bgUrl = imUserInfoController?.customInfo['coverBg'] ?? '';
child: NetworkOrAssetImage(imageUrl: imUserInfoController?.customInfo['coverBg'], placeholderAsset: 'assets/images/bk.jpg'), return NetworkOrAssetImage(
), imageUrl: bgUrl,
), placeholderAsset: 'assets/images/bk.jpg',
fit: BoxFit.cover,
);
}),
Positioned( Positioned(
left: 15.0, left: 15,
bottom: 0, bottom: 0,
right: 15.0, right: 15,
child: Container( child: Container(
padding: const EdgeInsets.symmetric(vertical: 10.0), padding: const EdgeInsets.symmetric(vertical: 10),
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[ children: [
ClipOval( // Obx
child: Obx(() { Obx(() {
final faceUrl = imUserInfoController?.faceUrl.value; final faceUrl = imUserInfoController?.faceUrl.value ?? '';
return ClipRRect( return ClipOval(
borderRadius: BorderRadius.circular(30), child: NetworkOrAssetImage(
child: NetworkOrAssetImage( imageUrl: faceUrl,
imageUrl: faceUrl, width: 80,
width: 80, height: 80,
height: 80, ),
), );
); }),
}), const SizedBox(width: 15),
),
const SizedBox(width: 15.0),
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Row( Row(
children: [ children: [
Container( // Obx
padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 5.0), Obx(() {
decoration: BoxDecoration(color: Colors.black.withAlpha((0.3 * 255).round()), borderRadius: BorderRadius.circular(20.0)), final nickname = imUserInfoController?.nickname.value ?? '';
child: Obx( return Container(
() => Text( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
imUserInfoController!.nickname.value.isNotEmpty == true ? imUserInfoController!.nickname.value : '昵称', decoration: BoxDecoration(
style: TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold, fontFamily: 'Arial', color: Colors.white), color: Colors.black.withAlpha(76),
borderRadius: BorderRadius.circular(20),
),
child: Text(
nickname.isNotEmpty ? nickname : '昵称',
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white,
), ),
)), ),
const SizedBox(width: 8.0), );
}),
const SizedBox(width: 8),
InkWell( InkWell(
onTap: () { onTap: () => qrcodeAlertDialog(context),
qrcodeAlertDialog(context);
},
child: Container( child: Container(
padding: const EdgeInsets.symmetric(horizontal: 5.0, vertical: 5.0), padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 5),
decoration: BoxDecoration(color: Colors.black.withAlpha((0.3 * 255).round()), borderRadius: BorderRadius.circular(20.0)), decoration: BoxDecoration(
child: const Icon(Icons.qr_code_outlined, size: 18.0, color: Colors.white), 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), const SizedBox(height: 8),
Container( // ID Obx
padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 5.0), Obx(() {
decoration: BoxDecoration(color: Colors.black.withAlpha((0.3 * 255).round()), borderRadius: BorderRadius.circular(20.0)), final userId = imUserInfoController?.userID.value ?? '';
child: InkWell( return Container(
onTap: () { padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
logger.i('点击id'); decoration: BoxDecoration(
Clipboard.setData(ClipboardData(text: imUserInfoController!.userID.value)); color: Colors.black.withAlpha(76),
MyDialog.toast('ID已复制', icon: const Icon(Icons.check_circle), style: ToastStyle(backgroundColor: Colors.green.withAlpha(200))); borderRadius: BorderRadius.circular(20),
}, ),
child: Text('ID:${imUserInfoController!.userID.value}', style: TextStyle(fontSize: 12.0, color: Colors.white)), child: InkWell(
), onTap: () {
), Clipboard.setData(ClipboardData(text: userId));
MyDialog.toast(
'ID已复制',
icon: const Icon(Icons.check_circle),
style: ToastStyle(backgroundColor: Colors.green.withAlpha(200)),
);
},
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() { Widget _buildStatsCard() {
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
@ -783,7 +900,9 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
_buildOrderIcon('assets/images/ico_sh.png', '提现vloger', () { _buildOrderIcon('assets/images/ico_sh.png', '提现vloger', () {
Get.toNamed('/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); double ratio = (currentHeight - minHeight) / (maxHeight - minHeight);
ratio = ratio.clamp(0.0, 1.0); ratio = ratio.clamp(0.0, 1.0);
String coverBg = userInfo.value.customInfo?['coverBg'] ?? ''; String coverBg = userInfo.value.customInfo?['coverBg'] ?? '';
coverBg = coverBg.isEmpty ? 'assets/images/pic2.jpg' : coverBg;
logger.w(coverBg);
return Stack( return Stack(
fit: StackFit.expand, fit: StackFit.expand,
children: [ children: [
Positioned.fill( Positioned.fill(
child: Opacity( child: Opacity(
opacity: 1.0, opacity: 1.0,
child: Image.asset( child: NetworkOrAssetImage(
coverBg, imageUrl: coverBg,
fit: BoxFit.cover, width: double.infinity,
))), ),
),
),
Positioned( Positioned(
left: 15.0, left: 15.0,
bottom: 0, bottom: 0,
@ -411,20 +411,6 @@ class MyPageState extends State<Vloger> with SingleTickerProviderStateMixin {
children: <Widget>[ children: <Widget>[
ClipOval( ClipOval(
child: NetworkOrAssetImage(imageUrl: userInfo.value.faceUrl), 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), const SizedBox(width: 15.0),
Expanded( Expanded(

View File

@ -1,7 +1,9 @@
library; library;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:shirne_dialog/shirne_dialog.dart'; import 'package:shirne_dialog/shirne_dialog.dart';
import 'package:timer_count_down/timer_count_down.dart';
import '../../behavior/custom_scroll_behavior.dart'; import '../../behavior/custom_scroll_behavior.dart';
@ -13,6 +15,30 @@ class OrderDetail extends StatefulWidget {
} }
class _OrderDetailState extends State<OrderDetail> with SingleTickerProviderStateMixin { 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() { Widget emptyTip() {
return Column( return Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
@ -39,12 +65,12 @@ class _OrderDetailState extends State<OrderDetail> with SingleTickerProviderStat
foregroundColor: Colors.white, foregroundColor: Colors.white,
title: Text('订单详情'), title: Text('订单详情'),
titleSpacing: 1.0, titleSpacing: 1.0,
actions: [ // actions: [
IconButton( // IconButton(
icon: Icon(Icons.help, size: 18.0), // icon: Icon(Icons.help, size: 18.0),
onPressed: () {}, // onPressed: () {},
), // ),
], // ],
), ),
body: ScrollConfiguration( body: ScrollConfiguration(
behavior: CustomScrollBehavior().copyWith(scrollbars: false), behavior: CustomScrollBehavior().copyWith(scrollbars: false),
@ -64,9 +90,31 @@ class _OrderDetailState extends State<OrderDetail> with SingleTickerProviderStat
)), )),
TextSpan(text: ' 待支付, '), TextSpan(text: ' 待支付, '),
TextSpan(text: ' 剩余 '), TextSpan(text: ' 剩余 '),
TextSpan( // TextSpan(
text: '00 : 29 : 55', // text: '00 : 29 : 55',
style: TextStyle(color: Colors.red), // 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 '../../components/keepalive_wrapper.dart';
import '../../controller/video_module_controller.dart'; import '../../controller/video_module_controller.dart';
import './module/attention.dart'; import './module/attention.dart';
import './module/recommend.dart';
// import './module/browse.dart'; // import './module/browse.dart';
// import './module/buying.dart'; // import './module/buying.dart';
// import './module/drama.dart'; // import './module/drama.dart';
// import './module/live.dart'; // import './module/live.dart';
import './module/friend.dart'; import './module/friend.dart';
import './module/recommend.dart';
// tab内容模块 // tab内容模块
// import './module/subscribe.dart'; // import './module/subscribe.dart';
@ -138,19 +138,19 @@ class _VideoPageState extends State<VideoPage> with SingleTickerProviderStateMix
child: PageView( child: PageView(
controller: pageController, controller: pageController,
onPageChanged: (index) { onPageChanged: (index) {
logger.i('$index'); logger.i('$index');
// tab // tab
if (index == 0) { if (index == 0) {
AttentionModule.playVideo(); AttentionModule.playVideo();
// FriendModule.pauseVideo(); // FriendModule.pauseVideo();
RecommendModule.pauseVideo(); RecommendModule.pauseVideo();
} else if (index == 1) { } else if (index == 1) {
AttentionModule.pauseVideo(); AttentionModule.pauseVideo();
// FriendModule.playVideo(); // FriendModule.playVideo();
RecommendModule.pauseVideo(); RecommendModule.pauseVideo();
} else if (index == 2) { } else if (index == 2) {
AttentionModule.pauseVideo(); AttentionModule.pauseVideo();
// FriendModule.pauseVideo(); // FriendModule.pauseVideo();
RecommendModule.playVideo(); RecommendModule.playVideo();
} }
videoModuleController.updateVideoTabIndex(index); videoModuleController.updateVideoTabIndex(index);

View File

@ -1,10 +1,11 @@
/// ///
library; library;
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/services.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:loopin/IM/controller/chat_controller.dart'; import 'package:loopin/IM/controller/chat_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/components/network_or_asset_image.dart';
import 'package:loopin/models/summary_type.dart'; import 'package:loopin/models/summary_type.dart';
import 'package:loopin/service/http.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/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/media_kit.dart';
import 'package:media_kit_video/media_kit_video.dart'; import 'package:media_kit_video/media_kit_video.dart';
import 'package:media_kit_video/media_kit_video_controls/src/controls/extensions/duration.dart'; import 'package:media_kit_video/media_kit_video_controls/src/controls/extensions/duration.dart';
@ -48,6 +49,7 @@ class RecommendModule extends StatefulWidget {
@override @override
State<RecommendModule> createState() => _RecommendModuleState(); State<RecommendModule> createState() => _RecommendModuleState();
} }
class CommentBottomSheet extends StatefulWidget { class CommentBottomSheet extends StatefulWidget {
final String videoId; final String videoId;
final Function(int) onCommentCountChanged; // final Function(int) onCommentCountChanged; //
@ -72,7 +74,7 @@ class _CommentBottomSheetState extends State<CommentBottomSheet> {
String replyingCommentId = ''; String replyingCommentId = '';
String replyingCommentUser = ''; String replyingCommentUser = '';
// //
Map<String, dynamic> expandedReplies = {}; // {commentId: true/false} Map<String, dynamic> expandedReplies = {}; // {commentId: true/false}
Map<String, List<Map<String, dynamic>>> replyData = {}; // {commentId: [replies]} Map<String, List<Map<String, dynamic>>> replyData = {}; // {commentId: [replies]}
Map<String, int> replyPage = {}; // Map<String, int> replyPage = {}; //
@ -97,7 +99,7 @@ class _CommentBottomSheetState extends State<CommentBottomSheet> {
hasMoreComments = true; hasMoreComments = true;
commentList.clear(); commentList.clear();
} }
logger.d('入参vlogId-------------------->: ${widget.videoId}'); logger.d('入参vlogId-------------------->: ${widget.videoId}');
try { try {
final res = await Http.post(VideoApi.videoCommentList, data: { final res = await Http.post(VideoApi.videoCommentList, data: {
'vlogId': widget.videoId, 'vlogId': widget.videoId,
@ -109,8 +111,7 @@ class _CommentBottomSheetState extends State<CommentBottomSheet> {
if (res['code'] == 200 && res['data'] != null) { if (res['code'] == 200 && res['data'] != null) {
final data = res['data']; final data = res['data'];
final List<Map<String, dynamic>> newComments = final List<Map<String, dynamic>> newComments = List<Map<String, dynamic>>.from(data['records'] ?? []);
List<Map<String, dynamic>>.from(data['records'] ?? []);
final int total = data['total'] ?? 0; final int total = data['total'] ?? 0;
setState(() { setState(() {
@ -134,91 +135,87 @@ class _CommentBottomSheetState extends State<CommentBottomSheet> {
} }
} }
Future<void> postComment(String content, {String parentCommentId = ''}) async { Future<void> postComment(String content, {String parentCommentId = ''}) async {
try { try {
final res = await Http.post(VideoApi.doVideoComment, data: { final res = await Http.post(VideoApi.doVideoComment, data: {
'vlogId': widget.videoId, 'vlogId': widget.videoId,
'content': content, 'content': content,
'fatherCommentId': parentCommentId.isNotEmpty ? parentCommentId : null, 'fatherCommentId': parentCommentId.isNotEmpty ? parentCommentId : null,
}); });
if (res['code'] == 200) { if (res['code'] == 200) {
// //
if (parentCommentId.isNotEmpty) { if (parentCommentId.isNotEmpty) {
fetchReplies(parentCommentId, false); fetchReplies(parentCommentId, false);
// //
setState(() { setState(() {
final comment = commentList.firstWhere( final comment = commentList.firstWhere((c) => c['id'] == parentCommentId, orElse: () => <String, dynamic>{});
(c) => c['id'] == parentCommentId, if (comment.isNotEmpty) {
orElse: () => <String, dynamic>{} comment['childCount'] = (comment['childCount'] ?? 0) + 1;
); }
if (comment != null && comment.isNotEmpty) { });
comment['childCount'] = (comment['childCount'] ?? 0) + 1; } else {
//
fetchComments(false);
} }
}); widget.onCommentCountChanged(commentList.length + 1);
} else { MyToast().tip(
// title: '评论成功',
fetchComments(false); position: 'center',
type: 'success',
);
} }
widget.onCommentCountChanged(commentList.length + 1); } catch (e) {
logger.i('发布评论失败: $e');
MyToast().tip( MyToast().tip(
title: '评论成功', title: '评论失败',
position: 'center', position: 'center',
type: 'success', type: 'error',
); );
} }
} catch (e) {
logger.i('发布评论失败: $e');
MyToast().tip(
title: '评论失败',
position: 'center',
type: 'error',
);
} }
}
// //
Future<void> fetchReplies(String commentId, bool loadMore) async { Future<void> fetchReplies(String commentId, bool loadMore) async {
if (isLoadingReplies[commentId] == true && !loadMore) return; if (isLoadingReplies[commentId] == true && !loadMore) return;
setState(() { setState(() {
isLoadingReplies[commentId] = true; isLoadingReplies[commentId] = true;
});
if (!loadMore) {
replyPage[commentId] = 1;
replyData[commentId] = [];
}
try {
final res = await Http.post(VideoApi.videoCommentList, data: {
'fatherCommentId': commentId,
'current': replyPage[commentId],
'size': commentPageSize,
}); });
if (res['code'] == 200 && res['data'] != null) { if (!loadMore) {
final data = res['data']; replyPage[commentId] = 1;
final List<Map<String, dynamic>> newReplies = replyData[commentId] = [];
List<Map<String, dynamic>>.from(data['records'] ?? []); }
try {
final res = await Http.post(VideoApi.videoCommentList, data: {
'fatherCommentId': commentId,
'current': replyPage[commentId],
'size': commentPageSize,
});
if (res['code'] == 200 && res['data'] != null) {
final data = res['data'];
final List<Map<String, dynamic>> newReplies = List<Map<String, dynamic>>.from(data['records'] ?? []);
setState(() {
if (loadMore) {
replyData[commentId]!.addAll(newReplies);
} else {
replyData[commentId] = newReplies;
}
replyPage[commentId] = replyPage[commentId]! + 1;
});
}
} catch (e) {
logger.e('获取子评论异常: $e');
} finally {
setState(() { setState(() {
if (loadMore) { isLoadingReplies[commentId] = false;
replyData[commentId]!.addAll(newReplies);
} else {
replyData[commentId] = newReplies;
}
replyPage[commentId] = replyPage[commentId]! + 1;
}); });
} }
} catch (e) {
logger.e('获取子评论异常: $e');
} finally {
setState(() {
isLoadingReplies[commentId] = false;
});
} }
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -234,7 +231,7 @@ Future<void> postComment(String content, {String parentCommentId = ''}) async {
children: [ children: [
Row( Row(
children: [ children: [
/* Expanded( /* Expanded(
child: Text.rich(TextSpan(children: [ child: Text.rich(TextSpan(children: [
TextSpan( TextSpan(
text: '大家都在搜: ', text: '大家都在搜: ',
@ -270,9 +267,7 @@ Future<void> postComment(String content, {String parentCommentId = ''}) async {
Expanded( Expanded(
child: NotificationListener<ScrollNotification>( child: NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification scrollInfo) { onNotification: (ScrollNotification scrollInfo) {
if (scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent && if (scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent && !isLoadingMoreComments && hasMoreComments) {
!isLoadingMoreComments &&
hasMoreComments) {
fetchComments(true); fetchComments(true);
} }
return false; return false;
@ -292,9 +287,7 @@ Future<void> postComment(String content, {String parentCommentId = ''}) async {
return Center( return Center(
child: Padding( child: Padding(
padding: EdgeInsets.all(16.0), padding: EdgeInsets.all(16.0),
child: isLoadingMoreComments child: isLoadingMoreComments ? CircularProgressIndicator() : Text('没有更多评论了'),
? CircularProgressIndicator()
: Text('没有更多评论了'),
), ),
); );
} }
@ -388,7 +381,7 @@ Future<void> postComment(String content, {String parentCommentId = ''}) async {
), ),
), ),
Text( Text(
'${comment['createTime']?.toString().substring(0, 10) ?? ''}', comment['createTime']?.toString().substring(0, 10) ?? '',
style: TextStyle(color: Colors.grey, fontSize: 12.0), style: TextStyle(color: Colors.grey, fontSize: 12.0),
), ),
], ],
@ -402,67 +395,65 @@ Future<void> postComment(String content, {String parentCommentId = ''}) async {
child: Column( child: Column(
children: [ children: [
...replies.map((reply) => ListTile( ...replies.map((reply) => ListTile(
leading: ClipRRect( leading: ClipRRect(
borderRadius: BorderRadius.circular(25.0), borderRadius: BorderRadius.circular(25.0),
child: NetworkOrAssetImage( child: NetworkOrAssetImage(
imageUrl: reply['commentUserFace'] ?? 'assets/images/avatar/default.png', imageUrl: reply['commentUserFace'] ?? 'assets/images/avatar/default.png',
width: 25.0, width: 25.0,
height: 25.0, height: 25.0,
fit: BoxFit.cover, fit: BoxFit.cover,
), ),
), ),
title: Row( title: Row(
children: [ children: [
Expanded( Expanded(
child: Text( child: Text(
reply['commentUserNickname'] ?? '未知用户', reply['commentUserNickname'] ?? '未知用户',
style: TextStyle( style: TextStyle(
color: Colors.grey, color: Colors.grey,
fontSize: 11.0, fontSize: 11.0,
),
),
), ),
), GestureDetector(
onTap: () {
setState(() {
replyingCommentId = comment['id'];
replyingCommentUser = reply['commentUserNickname'] ?? '未知用户';
});
},
child: Icon(
Icons.reply,
color: Colors.black54,
size: 14.0,
),
),
],
), ),
GestureDetector( subtitle: Column(
onTap: () { crossAxisAlignment: CrossAxisAlignment.start,
setState(() { children: [
replyingCommentId = comment['id']; Container(
replyingCommentUser = reply['commentUserNickname'] ?? '未知用户'; margin: EdgeInsets.symmetric(vertical: 3.0),
}); child: Text(
}, reply['content'] ?? '',
child: Icon( style: TextStyle(fontSize: 13.0),
Icons.reply, ),
color: Colors.black54, ),
size: 14.0, Text(
), reply['createTime']?.toString().substring(0, 10) ?? '',
style: TextStyle(color: Colors.grey, fontSize: 11.0),
),
],
), ),
], )),
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
margin: EdgeInsets.symmetric(vertical: 3.0),
child: Text(
reply['content'] ?? '',
style: TextStyle(fontSize: 13.0),
),
),
Text(
'${reply['createTime']?.toString().substring(0, 10) ?? ''}',
style: TextStyle(color: Colors.grey, fontSize: 11.0),
),
],
),
)).toList(),
// //
if (replies.length < comment['childCount']) if (replies.length < comment['childCount'])
Center( Center(
child: TextButton( child: TextButton(
onPressed: () => fetchReplies(comment['id'], true), onPressed: () => fetchReplies(comment['id'], true),
child: isLoadingReplies[comment['id']] == true child: isLoadingReplies[comment['id']] == true ? CircularProgressIndicator() : Text('加载更多回复'),
? CircularProgressIndicator()
: Text('加载更多回复'),
), ),
), ),
], ],
@ -474,8 +465,8 @@ Future<void> postComment(String content, {String parentCommentId = ''}) async {
); );
}, },
), ),
),
), ),
),
// //
if (replyingCommentId.isNotEmpty) if (replyingCommentId.isNotEmpty)
Container( Container(
@ -502,54 +493,58 @@ Future<void> postComment(String content, {String parentCommentId = ''}) async {
], ],
), ),
), ),
GestureDetector( GestureDetector(
child: Container( child: Container(
margin: EdgeInsets.all(10.0), margin: EdgeInsets.all(10.0),
height: 40.0, height: 40.0,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.grey[100], color: Colors.grey[100],
borderRadius: BorderRadius.circular(30.0), borderRadius: BorderRadius.circular(30.0),
), ),
child: Row( child: Row(
children: [ children: [
SizedBox(width: 15.0), SizedBox(width: 15.0),
Icon( Icon(
Icons.edit_note, Icons.edit_note,
color: Colors.black54, color: Colors.black54,
size: 16.0, size: 16.0,
), ),
SizedBox(width: 5.0), SizedBox(width: 5.0),
Text( Text(
replyingCommentId.isNotEmpty ? '回复 @$replyingCommentUser' : '说点什么...', replyingCommentId.isNotEmpty ? '回复 @$replyingCommentUser' : '说点什么...',
style: TextStyle(color: Colors.black54, fontSize: 14.0), style: TextStyle(color: Colors.black54, fontSize: 14.0),
), ),
], ],
),
), ),
onTap: () {
Navigator.push(context, FadeRoute(child: PopupReply(
hintText: replyingCommentId.isNotEmpty ? '回复 @$replyingCommentUser' : '说点什么...',
onChanged: (value) {
debugPrint('评论内容: $value');
},
onSubmitted: (value) {
if (value.isNotEmpty) {
postComment(value, parentCommentId: replyingCommentId);
setState(() {
replyingCommentId = '';
replyingCommentUser = '';
});
Navigator.pop(context);
}
},
)));
},
), ),
], onTap: () {
), Navigator.push(
); context,
} FadeRoute(
child: PopupReply(
hintText: replyingCommentId.isNotEmpty ? '回复 @$replyingCommentUser' : '说点什么...',
onChanged: (value) {
debugPrint('评论内容: $value');
},
onSubmitted: (value) {
if (value.isNotEmpty) {
postComment(value, parentCommentId: replyingCommentId);
setState(() {
replyingCommentId = '';
replyingCommentUser = '';
});
Navigator.pop(context);
}
},
)));
},
),
],
),
);
} }
}
class _RecommendModuleState extends State<RecommendModule> { class _RecommendModuleState extends State<RecommendModule> {
VideoModuleController videoModuleController = Get.put(VideoModuleController()); VideoModuleController videoModuleController = Get.put(VideoModuleController());
final ChatController chatController = Get.find<ChatController>(); final ChatController chatController = Get.find<ChatController>();
@ -731,12 +726,10 @@ class _RecommendModuleState extends State<RecommendModule> {
Future<void> doUnLikeVideo(item) async { Future<void> doUnLikeVideo(item) async {
logger.d('点击了点赞按钮$item'); logger.d('点击了点赞按钮$item');
try { try {
final res = await Http.post(VideoApi.unlike, data:{ final res = await Http.post(VideoApi.unlike, data: {'vlogId': item['id']});
'vlogId': item['id']
});
final resCode = res['code']; final resCode = res['code'];
if (resCode == 200) { if (resCode == 200) {
item['doILikeThisVlog'] = !item['doILikeThisVlog']; item['doILikeThisVlog'] = !item['doILikeThisVlog'];
} }
} catch (e) { } catch (e) {
logger.i('点击取消喜欢按钮报错: $e'); logger.i('点击取消喜欢按钮报错: $e');
@ -746,9 +739,7 @@ class _RecommendModuleState extends State<RecommendModule> {
// //
Future<void> doLikeVideo(item) async { Future<void> doLikeVideo(item) async {
try { try {
final res = await Http.post(VideoApi.like, data:{ final res = await Http.post(VideoApi.like, data: {'vlogId': item['id']});
'vlogId': item['id']
});
final resCode = res['code']; final resCode = res['code'];
if (resCode == 200) { if (resCode == 200) {
item['doILikeThisVlog'] = !item['doILikeThisVlog']; item['doILikeThisVlog'] = !item['doILikeThisVlog'];
@ -760,35 +751,34 @@ class _RecommendModuleState extends State<RecommendModule> {
} }
// //
void handleComment(index) { void handleComment(index) {
final videoId = videoList[index]['id']; final videoId = videoList[index]['id'];
logger.i('点击了评论按钮$videoId'); logger.i('点击了评论按钮$videoId');
showModalBottomSheet( showModalBottomSheet(
backgroundColor: Colors.white, backgroundColor: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(15.0))), shape: RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(15.0))),
showDragHandle: false, showDragHandle: false,
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
isScrollControlled: true, isScrollControlled: true,
constraints: BoxConstraints( constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height * 3 / 4, maxHeight: MediaQuery.of(context).size.height * 3 / 4,
), ),
context: context, context: context,
builder: (context) { builder: (context) {
return CommentBottomSheet( return CommentBottomSheet(
videoId: videoId, videoId: videoId,
onCommentCountChanged: (newCount) { onCommentCountChanged: (newCount) {
// //
setState(() { setState(() {
if (index < videoList.length) { if (index < videoList.length) {
videoList[index]['commentsCounts'] = newCount; videoList[index]['commentsCounts'] = newCount;
} }
}); });
} });
); },
}, );
); }
}
// //
void handleShare(index) { void handleShare(index) {
@ -843,40 +833,41 @@ void handleComment(index) {
), ),
// //
SizedBox( if (chatController.chatList.where((item) => item.conversation.conversationGroupList?.isEmpty == true).isNotEmpty)
height: 110, SizedBox(
child: ListView.builder( height: 110,
scrollDirection: Axis.horizontal, child: ListView.builder(
itemCount: chatController.chatList.where((item) => item.conversation.conversationGroupList?.isEmpty == true).length, scrollDirection: Axis.horizontal,
padding: EdgeInsets.symmetric(horizontal: 0, vertical: 20.0), itemCount: chatController.chatList.where((item) => item.conversation.conversationGroupList?.isEmpty == true).length,
itemBuilder: (context, index) { padding: const EdgeInsets.symmetric(horizontal: 0, vertical: 20.0),
final filteredList = chatController.chatList.where((item) => item.conversation.conversationGroupList?.isEmpty == true).toList(); itemBuilder: (context, index) {
return GestureDetector( final filteredList = chatController.chatList.where((item) => item.conversation.conversationGroupList?.isEmpty == true).toList();
onTap: () => handlCoverClick(filteredList[index].conversation), return GestureDetector(
child: Container( onTap: () => handlCoverClick(filteredList[index].conversation),
width: 64, child: Container(
margin: EdgeInsets.symmetric(horizontal: 8.0), width: 64,
child: Column( margin: const EdgeInsets.symmetric(horizontal: 8.0),
mainAxisSize: MainAxisSize.min, child: Column(
children: [ mainAxisSize: MainAxisSize.min,
NetworkOrAssetImage( children: [
imageUrl: filteredList[index].faceUrl, NetworkOrAssetImage(
width: 48.0, imageUrl: filteredList[index].faceUrl,
height: 48.0, width: 48.0,
), height: 48.0,
SizedBox(height: 5), ),
Text( const SizedBox(height: 5),
'${filteredList[index].conversation.showName}', Text(
style: TextStyle(fontSize: 12.0), filteredList[index].conversation.showName ?? '',
overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 12.0),
), overflow: TextOverflow.ellipsis,
], ),
],
),
), ),
), );
); },
}, ),
), ),
),
// //
SafeArea( SafeArea(
@ -909,36 +900,37 @@ void handleComment(index) {
final videoUrl = videoList[videoModuleController.videoPlayIndex.value]['url']; final videoUrl = videoList[videoModuleController.videoPlayIndex.value]['url'];
final description = videoList[videoModuleController.videoPlayIndex.value]['title'] ?? '快来看看这个视频'; final description = videoList[videoModuleController.videoPlayIndex.value]['title'] ?? '快来看看这个视频';
var httpPrefix = 'http://43.143.227.203/adv'; 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) { 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) { } 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) { } else if (index == 2) {
// //
copyToClipboard(videoUrl); copyToClipboard(videoUrl);
} else if (index == 3) { } else if (index == 3) {
// //
_downloadVideoWithDio(videoUrl,description); _downloadVideoWithDio(videoUrl, description);
} }
} }
//
void copyToClipboard(String text) async { //
void copyToClipboard(String text) async {
try { try {
await Clipboard.setData(ClipboardData(text: text)); await Clipboard.setData(ClipboardData(text: text));
MyToast().tip( MyToast().tip(
title: '链接已复制到剪贴板', title: '链接已复制到剪贴板',
position: 'center', position: 'center',
type: 'success', type: 'success',
); );
} catch (e) { } catch (e) {
MyToast().tip( MyToast().tip(
title: '复制失败', title: '复制失败',
position: 'center', position: 'center',
type: 'success', type: 'success',
); );
} }
} }
@ -946,14 +938,14 @@ void handleComment(index) {
Future<void> _downloadVideoWithDio(String videoUrl, String fileName) async { Future<void> _downloadVideoWithDio(String videoUrl, String fileName) async {
try { try {
// //
String? toastId; // toast的ID便 String? toastId; // toast的ID便
var status = await Permissions.requestStoragePermission(); var status = await Permissions.requestStoragePermission();
if (!status) { if (!status) {
MyToast().tip( MyToast().tip(
title: '需要存储权限才能下载视频', title: '需要存储权限才能下载视频',
position: 'center', position: 'center',
type: 'success', type: 'success',
); );
return; return;
} }
await DownloadManager.downloadFile( await DownloadManager.downloadFile(
@ -964,14 +956,14 @@ void handleComment(index) {
// //
}, },
onComplete: (filePath) { onComplete: (filePath) {
MyToast().tip( MyToast().tip(
title: '下载完成', title: '下载完成',
position: 'center', position: 'center',
type: 'success', type: 'success',
); );
}, },
onError: (error) { onError: (error) {
MyToast().tip( MyToast().tip(
title: '下载失败: $error', title: '下载失败: $error',
position: 'center', position: 'center',
type: 'error', type: 'error',
@ -1080,7 +1072,6 @@ void handleComment(index) {
height: double.infinity, height: double.infinity,
), ),
), ),
StreamBuilder( StreamBuilder(
stream: player.stream.playing, stream: player.stream.playing,
builder: (context, playing) { builder: (context, playing) {
@ -1123,20 +1114,16 @@ void handleComment(index) {
height: 55.0, height: 55.0,
width: 48.0, width: 48.0,
child: GestureDetector( child: GestureDetector(
onTap: ()async { onTap: () async {
player.pause(); player.pause();
// Vloger // Vloger
final result = await Get.toNamed( final result = await Get.toNamed('/vloger', arguments: videoList[videoModuleController.videoPlayIndex.value]);
'/vloger',
arguments: videoList[videoModuleController
.videoPlayIndex.value]);
if (result != null) { if (result != null) {
// //
print('返回的数据: ${result['followStatus']}'); print('返回的数据: ${result['followStatus']}');
player.play(); player.play();
videoList[index]['doIFollowVloger'] = result['followStatus']; videoList[index]['doIFollowVloger'] = result['followStatus'];
}
};
}, },
child: UnconstrainedBox( child: UnconstrainedBox(
alignment: Alignment.topCenter, alignment: Alignment.topCenter,
@ -1174,17 +1161,17 @@ void handleComment(index) {
), ),
), ),
onTap: () async { onTap: () async {
final vlogerId = videoList[index]['memberId']; final vlogerId = videoList[index]['memberId'];
final doIFollowVloger = videoList[index]['doIFollowVloger']; final doIFollowVloger = videoList[index]['doIFollowVloger'];
// //
if(doIFollowVloger == false){ if (doIFollowVloger == false) {
final res = await ImService.instance.followUser(userIDList: [vlogerId]); final res = await ImService.instance.followUser(userIDList: [vlogerId]);
if (res.success) { if (res.success) {
setState(() { setState(() {
videoList[index]['doIFollowVloger'] = !videoList[index]['doIFollowVloger']; videoList[index]['doIFollowVloger'] = !videoList[index]['doIFollowVloger'];
}); });
}
} }
}
}, },
), ),
), ),
@ -1207,10 +1194,10 @@ void handleComment(index) {
), ),
onTap: () { onTap: () {
logger.d('点击了点赞按钮${videoList[index]['doILikeThisVlog']}'); logger.d('点击了点赞按钮${videoList[index]['doILikeThisVlog']}');
if(videoList[index]['doILikeThisVlog'] == true){ if (videoList[index]['doILikeThisVlog'] == true) {
logger.d('点击了点赞按钮${videoList[index]['doILikeThisVlog']}'); logger.d('点击了点赞按钮${videoList[index]['doILikeThisVlog']}');
doUnLikeVideo(videoList[index]); doUnLikeVideo(videoList[index]);
}else{ } else {
doLikeVideo(videoList[index]); 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.dart';
import 'package:loopin/pages/chat/chat_group.dart'; import 'package:loopin/pages/chat/chat_group.dart';
import 'package:loopin/pages/chat/chat_no_friend.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/des.dart';
import 'package:loopin/pages/my/nick_name.dart'; import 'package:loopin/pages/my/nick_name.dart';
import 'package:loopin/pages/my/setting.dart'; import 'package:loopin/pages/my/setting.dart';
@ -28,7 +31,6 @@ import '../utils/common.dart';
final Map<String, Widget> routes = { final Map<String, Widget> routes = {
'/': const Layout(), '/': const Layout(),
'/goods': const Goods(), '/goods': const Goods(),
// '/chat': const Chat(),
// '/chatNoFriend': const ChatNoFriend(), // '/chatNoFriend': const ChatNoFriend(),
// '/chatGroup': const ChatGroup(), // '/chatGroup': const ChatGroup(),
'/order': const Order(), '/order': const Order(),
@ -42,6 +44,10 @@ final Map<String, Widget> routes = {
'/about': const Setting(), '/about': const Setting(),
'/des': const Des(), '/des': const Des(),
'/nickName': const NickName(), '/nickName': const NickName(),
//
'/noFriend': const Nofriend(),
'/system': const System(),
'/interaction': const Interaction(),
}; };
final List<GetPage> routeList = routes.entries final List<GetPage> routeList = routes.entries

View File

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

View File

@ -134,6 +134,7 @@ class Utils {
return number.toStringAsFixed(1).replaceAll(RegExp(r'\.0$'), ''); return number.toStringAsFixed(1).replaceAll(RegExp(r'\.0$'), '');
} }
//
String formatChatTime(int timestamp) { String formatChatTime(int timestamp) {
// timestamp DateTime // timestamp DateTime
DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000); 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) { String _formatHourMinute(DateTime dt) {
final hour = dt.hour.toString().padLeft(2, '0'); final hour = dt.hour.toString().padLeft(2, '0');
final minute = dt.minute.toString().padLeft(2, '0'); final minute = dt.minute.toString().padLeft(2, '0');

View File

@ -1,5 +1,6 @@
import 'dart:convert'; import 'dart:convert';
import 'package:loopin/models/notify_message.type.dart';
import 'package:loopin/models/summary_type.dart'; import 'package:loopin/models/summary_type.dart';
import 'package:tencent_cloud_chat_sdk/enum/message_elem_type.dart'; import 'package:tencent_cloud_chat_sdk/enum/message_elem_type.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message.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) { String _parseCustomMessage(V2TimMessage? msg) {
if (msg == null) return '[null]'; if (msg == null) return '[null]';
final sum = msg.cloudCustomData; final sum = msg.cloudCustomData;
final elment = msg.customElem; //
try { try {
switch (sum) { switch (sum) {
case SummaryType.hongbao: case SummaryType.hongbao:
@ -43,6 +61,115 @@ String _parseCustomMessage(V2TimMessage? msg) {
return '[分享商品]'; return '[分享商品]';
case SummaryType.shareVideo: case SummaryType.shareVideo:
return '[分享视频]'; 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: default:
return '[未知消息类型2]'; return '[未知消息类型2]';
} }

View File

@ -1274,6 +1274,14 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "0.7.4" 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: typed_data:
dependency: transitive dependency: transitive
description: description:

View File

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