This commit is contained in:
abu 2025-09-22 14:41:47 +08:00
parent 4f0c1f6f73
commit 2ba610ca7f
40 changed files with 1615 additions and 768 deletions

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -23,7 +23,6 @@ class IMMessage {
String? groupID,
String? cloudCustomData,
String? groupName,
bool isPush = true,
bool isExcludedFromUnreadCount = false,
}) async {
// toUserID groupID
@ -67,7 +66,7 @@ class IMMessage {
isSupportMessageExtension: false, //
isExcludedFromContentModeration: false, //
needReadReceipt: false, //
offlinePushInfo: isPush ? offlinePushInfo : null,
offlinePushInfo: offlinePushInfo,
cloudCustomData: cloudCustomData,
localCustomData: "",
);
@ -96,7 +95,7 @@ class IMMessage {
isSupportMessageExtension: false,
isExcludedFromContentModeration: false,
needReadReceipt: false,
offlinePushInfo: isPush ? offlinePushInfo : null,
offlinePushInfo: offlinePushInfo,
cloudCustomData: cloudCustomData,
localCustomData: "",
);

View File

@ -13,6 +13,7 @@ import 'package:shirne_dialog/shirne_dialog.dart';
import 'package:tencent_cloud_chat_sdk/enum/V2TimAdvancedMsgListener.dart';
import 'package:tencent_cloud_chat_sdk/enum/message_elem_type.dart';
import 'package:tencent_cloud_chat_sdk/enum/receive_message_opt.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_info.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message_receipt.dart';
@ -123,6 +124,14 @@ class ImMessageListenerService extends GetxService {
// ;
return;
}
} else {
//
final cRes = await ImService.instance.getConversation(conversationID: 'c2c_${message.userID}');
final V2TimConversation c2cConv = cRes.data;
if (c2cConv.recvOpt == ReceiveMsgOptType.kTIMRecvMsgOpt_Not_Notify) {
// 0=3=线线@
return;
}
}
_debounceTimer?.cancel(); //

View File

@ -1,6 +1,9 @@
class CommonApi {
///----------get
static const String getCode = '/resource/sms/code'; // {'phonenumber'}
// /app/member/sms/code
static const String getDelCode = '/app/member/sms/code'; // {'templateId'}
static const String accountInfo = '/app/member/info'; //
///---------post
@ -11,11 +14,31 @@ class CommonApi {
///[source]=wechat_open [clientId]=428a8310cd442757ae699df5d894f051 [grantType]=social [socialState]=1
static const String wxLogin = '/app/member/bind/wechat';
//
/// [member_revoked]
static const String dictionaryApi = '/app/sys/dict/type/';
//
static const String aggregationSearchApi = '/app/common/search';
/// [money]
// {
// "orderType": "RECHARGE",
// "clientType": "APP",
// "paymentMethod": "WECHAT",
// "paymentClient": "APP",
// "money": 100
// }
static const String addBalance = '/app/payment/pay';
///
// {
// "money": "1",
// "method": "1",
// }
static const String withdraw = '/app/member/withdraw';
/// /app/member/revoked
static const String revoked = '/app/member/revoked';
///resource/oss/upload
}

View File

@ -0,0 +1,41 @@
import 'package:flutter/material.dart';
import 'package:lottie/lottie.dart';
class VoiceAnimation extends StatelessWidget {
///
final bool isPlaying;
/// Lottie
final String animationAsset;
///
final IconData idleIcon;
///
final double size;
const VoiceAnimation({
super.key,
required this.isPlaying,
this.animationAsset = 'assets/animation/voice.json',
this.idleIcon = Icons.multitrack_audio, //
this.size = 24.0,
});
@override
Widget build(BuildContext context) {
if (isPlaying) {
return Lottie.asset(
animationAsset,
width: size,
height: size,
repeat: true,
);
} else {
return Icon(
idleIcon,
size: size,
);
}
}
}

View File

@ -17,9 +17,11 @@ class MyQrcode extends StatelessWidget {
MyQrcode({
super.key,
this.prefix,
this.text,
});
final String? prefix;
final String? text;
final controller = Get.find<ImUserInfoController>();
@ -106,13 +108,16 @@ class MyQrcode extends StatelessWidget {
child: Center(
child: Container(
alignment: Alignment.topCenter,
decoration: BoxDecoration(
decoration: const BoxDecoration(
color: Colors.transparent,
),
child: RepaintBoundary(
key: _qrKey,
child: PrettyQrView.data(
errorCorrectLevel: QrErrorCorrectLevel.H, //
child: Column(
mainAxisSize: MainAxisSize.min, //
children: [
PrettyQrView.data(
errorCorrectLevel: QrErrorCorrectLevel.H,
data: data, //
decoration: PrettyQrDecoration(
background: Colors.transparent,
@ -123,13 +128,26 @@ class MyQrcode extends StatelessWidget {
),
image: PrettyQrDecorationImage(
image: face,
scale: 0.3, //
scale: 0.3,
opacity: 1,
padding: EdgeInsets.all(8.0), //
padding: const EdgeInsets.all(8.0),
),
quietZone: const PrettyQrQuietZone.modules(2),
),
),
if (text != null && text!.trim().isNotEmpty) ...[
const SizedBox(height: 4),
Text(
text!,
style: const TextStyle(
fontSize: 14,
color: Colors.grey,
),
),
const SizedBox(height: 4),
],
],
),
),
),
),

View File

@ -12,6 +12,7 @@ import 'package:loopin/IM/controller/chat_detail_controller.dart';
import 'package:loopin/IM/im_message.dart';
import 'package:loopin/IM/im_result.dart';
import 'package:loopin/IM/im_service.dart';
import 'package:loopin/components/animation.dart';
import 'package:loopin/components/image_viewer.dart';
import 'package:loopin/components/network_or_asset_image.dart';
import 'package:loopin/components/preview_video.dart';
@ -56,6 +57,8 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
bool hasMore = true; //
final RxBool _throttleFlag = false.obs; //
RxMap<String, bool> voicePlayingMap = <String, bool>{}.obs;
// json
List emoJson = emotionData;
@ -498,11 +501,15 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
maxWidth: maxWidth,
// maxWidth: (item.soundElem!.duration! / 1000) / 60 * 230,
),
child: Row(
child: Obx(
() => Row(
mainAxisAlignment: !(item.isSelf ?? false) ? MainAxisAlignment.start : MainAxisAlignment.end,
children: !(item.isSelf ?? false)
? [
const Icon(Icons.multitrack_audio),
// const Icon(Icons.multitrack_audio),
VoiceAnimation(
isPlaying: voicePlayingMap[item.id ?? '${item.timestamp ?? 0}'] ?? false,
),
const SizedBox(
width: 5.0,
),
@ -513,17 +520,28 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
const SizedBox(
width: 5.0,
),
const Icon(Icons.multitrack_audio),
// const Icon(Icons.multitrack_audio),
VoiceAnimation(
isPlaying: voicePlayingMap[item.id ?? '${item.timestamp ?? 0}'] ?? false,
),
],
),
),
onTap: () {
),
onTap: () async {
final locUrl = item.soundElem?.path ?? '';
final netUrl = item.soundElem?.url ?? '';
final msgId = item.id ?? '${item.timestamp ?? 0}';
logger.w('本地地址$locUrl');
logger.w('网络地址$netUrl');
if (locUrl.isNotEmpty) {
AudioPlayerService().playNetwork(locUrl);
voicePlayingMap[msgId] = true;
await AudioPlayerService().playLocal(locUrl);
voicePlayingMap[msgId] = false;
} else if (netUrl.isNotEmpty) {
AudioPlayerService().playLocal(netUrl);
voicePlayingMap[msgId] = true;
await AudioPlayerService().playNetwork(netUrl);
voicePlayingMap[msgId] = false;
} else {
MyDialog.toast('音频文件已过期', icon: Icon(Icons.warning), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200)));
}
@ -1907,6 +1925,7 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
),
onPanStart: (details) async {
//
logger.w('开始录音');
final res = await VoiceService().startRecording();
if (res) {
setState(() {
@ -1932,7 +1951,7 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
});
},
onPanEnd: (details) {
// print('停止录音');
logger.w('停止录音');
setState(() {
switch (voiceType) {
case 1:

View File

@ -55,6 +55,7 @@ class _ChatGroupState extends State<ChatGroup> with SingleTickerProviderStateMix
bool isLoading = false; //
bool hasMore = true; //
final RxBool _throttleFlag = false.obs; //
RxMap<String, bool> voicePlayingMap = <String, bool>{}.obs;
// json
List emoJson = emotionData;
@ -457,13 +458,20 @@ class _ChatGroupState extends State<ChatGroup> with SingleTickerProviderStateMix
],
),
),
onTap: () {
onTap: () async {
final locUrl = item.soundElem?.path ?? '';
final netUrl = item.soundElem?.url ?? '';
final msgId = item.id ?? '${item.timestamp ?? 0}';
logger.w('本地地址$locUrl');
logger.w('网络地址$netUrl');
if (locUrl.isNotEmpty) {
AudioPlayerService().playNetwork(locUrl);
voicePlayingMap[msgId] = true;
await AudioPlayerService().playLocal(locUrl);
voicePlayingMap[msgId] = false;
} else if (netUrl.isNotEmpty) {
AudioPlayerService().playLocal(netUrl);
voicePlayingMap[msgId] = true;
await AudioPlayerService().playNetwork(netUrl);
voicePlayingMap[msgId] = false;
} else {
MyDialog.toast('音频文件已过期', icon: Icon(Icons.warning), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200)));
}

View File

@ -53,6 +53,8 @@ class _ChatNoFriendState extends State<ChatNoFriend> with SingleTickerProviderSt
bool hasMore = true; //
final RxBool _throttleFlag = false.obs; //
RxMap<String, bool> voicePlayingMap = <String, bool>{}.obs;
// json
List emoJson = emotionData;
@ -519,13 +521,20 @@ class _ChatNoFriendState extends State<ChatNoFriend> with SingleTickerProviderSt
],
),
),
onTap: () {
onTap: () async {
final locUrl = item.soundElem?.path ?? '';
final netUrl = item.soundElem?.url ?? '';
final msgId = item.id ?? '${item.timestamp ?? 0}';
logger.w('本地地址$locUrl');
logger.w('网络地址$netUrl');
if (locUrl.isNotEmpty) {
AudioPlayerService().playNetwork(locUrl);
voicePlayingMap[msgId] = true;
await AudioPlayerService().playLocal(locUrl);
voicePlayingMap[msgId] = false;
} else if (netUrl.isNotEmpty) {
AudioPlayerService().playLocal(netUrl);
voicePlayingMap[msgId] = true;
await AudioPlayerService().playNetwork(netUrl);
voicePlayingMap[msgId] = false;
} else {
MyDialog.toast('音频文件已过期', icon: Icon(Icons.warning), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200)));
}

View File

@ -9,8 +9,10 @@ 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/api/shop_api.dart';
import 'package:loopin/components/empty_tip.dart';
import 'package:loopin/components/network_or_asset_image.dart';
import 'package:loopin/components/scan_util.dart';
import 'package:loopin/models/conversation_type.dart' as myConversationType;
import 'package:loopin/models/conversation_type.dart';
import 'package:loopin/models/conversation_view_model.dart';
import 'package:loopin/pages/chat/menu/add_friend.dart';
@ -20,6 +22,8 @@ import 'package:loopin/utils/parse_message_summary.dart';
import 'package:loopin/utils/scan_code_type.dart'; //
import 'package:shirne_dialog/shirne_dialog.dart';
import 'package:tencent_cloud_chat_sdk/enum/receive_message_opt.dart';
import 'package:tencent_cloud_chat_sdk/enum/receive_message_opt_enum.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation_filter.dart';
import '../../behavior/custom_scroll_behavior.dart';
import '../../styles/index.dart';
@ -118,11 +122,10 @@ class ChatPageState extends State<ChatPage> {
}
}
// 广IM互相关注逻辑
void _handlePromotionCode(String value) async {
await checkFollowType(value); //
if(followed.value == 0 || followed.value == 2){
if (followed.value == 0 || followed.value == 2) {
final res = await ImService.instance.followUser(userIDList: [value]);
if (res.success) {
followed.value = followed.value == 0 ? 1 : 3;
@ -132,7 +135,7 @@ class ChatPageState extends State<ChatPage> {
Get.toNamed('/vloger', arguments: {'memberId': value});
}
}
}else{
} else {
Get.toNamed('/vloger', arguments: {'memberId': value});
}
}
@ -312,6 +315,19 @@ class ChatPageState extends State<ChatPage> {
],
),
),
PopupMenuItem<String>(
value: 'cs',
child: Row(
children: [
Icon(Icons.qr_code_scanner, color: Colors.white, size: 18),
SizedBox(width: 8),
Text(
'测试',
style: TextStyle(color: Colors.white),
),
],
),
),
],
);
@ -329,6 +345,10 @@ class ChatPageState extends State<ChatPage> {
logger.w('点击了扫一扫');
ScanUtil.openScanner(onResult: handleScanResult);
break;
case 'cs':
logger.w('去互动');
Get.toNamed('/newFoucs');
break;
}
}
},
@ -424,7 +444,7 @@ class ChatPageState extends State<ChatPage> {
child: Obx(
() {
final chatList = controller.chatList;
if (chatList.isEmpty) return EmptyTip();
return ListView.builder(
shrinkWrap: true,
physics: BouncingScrollPhysics(),
@ -434,7 +454,8 @@ class ChatPageState extends State<ChatPage> {
// logger.w(chatList[index].isCustomAdmin);
// logger.w(chatList[index].conversation.recvOpt);
final item = chatList[index];
final bool quiet = [ReceiveMsgOptType.kTIMRecvMsgOpt_Not_Notify_Except_At].contains(chatList[index].conversation.recvOpt ?? 0)
final bool quiet = [ReceiveMsgOptType.kTIMRecvMsgOpt_Not_Notify_Except_At, ReceiveMsgOptType.kTIMRecvMsgOpt_Not_Notify]
.contains(chatList[index].conversation.recvOpt ?? 0)
? true
: false; //
final isNoFriend = chatList[index].conversation.conversationGroupList?.contains(ConversationType.noFriend.name) ?? false;
@ -448,8 +469,22 @@ class ChatPageState extends State<ChatPage> {
motion: const DrawerMotion(), // StretchMotion ScrollMotion DrawerMotion
children: [
SlidableAction(
onPressed: (_) {
//
onPressed: (_) async {
logger.w(item.conversation.toLogString());
if (isNoFriend) {
//
//
final noFriendData = await ImService.instance.getConversationListByFilter(
filter: V2TimConversationFilter(conversationGroup: myConversationType.ConversationType.noFriend.name),
nextSeq: 0,
count: 1,
);
await ImService.instance.clearConversationUnreadCount(
conversationID: 'c2c_${item.conversation.userID}', cleanTimestamp: item.conversation.lastMessage!.timestamp ?? 0);
} else {
//
await ImService.instance.clearConversationUnreadCount(conversationID: item.conversation.conversationID);
}
},
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
@ -458,8 +493,30 @@ class ChatPageState extends State<ChatPage> {
),
if (!(isAdmin || isNoFriend))
SlidableAction(
onPressed: (_) {
//
onPressed: (_) async {
//
// logger.e(controller.info.value?.recvOpt);
// = 3 =20
if (item.conversation.type == 1) {
logger.w('当前单聊recvopt=${item.conversation.recvOpt}');
//
await ImService.instance.setC2CReceiveMessageOpt(
userIDList: ['${item.conversation.userID}'],
opt: item.conversation.recvOpt == 2
? ReceiveMsgOptEnum.V2TIM_RECEIVE_MESSAGE // = 0
: ReceiveMsgOptEnum.V2TIM_RECEIVE_NOT_NOTIFY_MESSAGE); // 3=at消息,2=线
} else if (item.conversation.type == 2) {
//
logger.w('当前群聊recvopt=${item.conversation.recvOpt}');
await ImService.instance.setGroupReceiveMessageOpt(
groupID: item.conversation.groupID ?? '',
opt: item.conversation.recvOpt == 3
? ReceiveMsgOptEnum.V2TIM_RECEIVE_MESSAGE
: ReceiveMsgOptEnum.V2TIM_RECEIVE_NOT_NOTIFY_MESSAGE_EXCEPT_AT,
);
}
//
Get.find<GlobalBadge>().initUnreadCount();
},
backgroundColor: FStyle.primaryColor,
foregroundColor: Colors.white,
@ -568,7 +625,7 @@ class ChatPageState extends State<ChatPage> {
),
onTap: () {
if (conversationTypeFromString(chatList[index].isCustomAdmin) != null) {
//
// notify下的内容
logger.e(chatList[index].isCustomAdmin);
Get.toNamed('/${chatList[index].isCustomAdmin}', arguments: chatList[index].conversation);
} else if (chatList[index].conversation.conversationGroupList!.contains(ConversationType.noFriend.name)) {

View File

@ -3,10 +3,12 @@ library;
import 'dart:convert';
import 'package:easy_refresh/easy_refresh.dart';
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/empty_tip.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';
@ -29,9 +31,9 @@ class Interaction extends StatefulWidget {
class InteractionState extends State<Interaction> with SingleTickerProviderStateMixin {
bool isLoading = false; //
bool hasMore = true; //
final RxBool _throttleFlag = false.obs; //
final ScrollController chatController = ScrollController();
RxBool hasMore = true.obs; //
// final RxBool _throttleFlag = false.obs; //
// final ScrollController chatController = ScrollController();
String page = '';
///-------------------
@ -58,24 +60,18 @@ class InteractionState extends State<Interaction> with SingleTickerProviderState
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();
// 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;
// });
// });
// }
// });
}
//
@ -90,7 +86,7 @@ class InteractionState extends State<Interaction> with SingleTickerProviderState
if (res.success && res.data != null) {
msgList.addAll(res.data!);
if (res.data!.isEmpty) {
hasMore = false;
hasMore.value = false;
}
logger.i('聊天数据加载成功');
} else {
@ -230,24 +226,54 @@ class InteractionState extends State<Interaction> with SingleTickerProviderState
child: Column(
children: [
Expanded(
child: RefreshIndicator(
backgroundColor: Colors.white,
color: Color(0xFFFF5000),
displacement: 10.0,
onRefresh: handleRefresh,
child: Obx(() {
child: EasyRefresh.builder(
callLoadOverOffset: 20, //
callRefreshOverOffset: 20, //
header: ClassicHeader(
dragText: '下拉刷新',
armedText: '释放刷新',
readyText: '加载中...',
processingText: '加载中...',
processedText: '加载完成',
failedText: '加载失败,请重试',
messageText: '最后更新于 %T',
),
footer: ClassicFooter(
dragText: '加载更多',
armedText: '释放加载',
readyText: '加载中...',
processingText: '加载中...',
processedText: '加载完成',
noMoreText: '没有更多了~',
failedText: '加载失败,请重试',
messageText: '最后更新于 %T',
),
onRefresh: () async {
await handleRefresh();
},
onLoad: () async {
if (hasMore.value) {
await getMsgData();
return hasMore.value ? IndicatorResult.success : IndicatorResult.noMore;
}
},
childBuilder: (context, physics) {
return Obx(
() {
// if (msgList.isEmpty) return EmptyTip();
if (demoList.isEmpty) return EmptyTip();
return ListView.builder(
controller: chatController,
// controller: chatController,
shrinkWrap: true,
physics: BouncingScrollPhysics(),
physics: physics,
// itemCount: msgList.length,
itemCount: demoList.length,
itemBuilder: (context, index) {
//cloudCustomData
// interactionComment, //->
// interactionAt, //->@
// interactionLike, //->
// interactionReply, //->
// interactionComment, //->
// interactionAt, //->@
// interactionLike, //->
// interactionReply, //->
//----
// final element =msgList[index].customElem!;
@ -272,9 +298,10 @@ class InteractionState extends State<Interaction> with SingleTickerProviderState
onTap: () async {
//
//
// cloudCustomData是interactionCommentinteractionAtinteractionReply,id
// cloudCustomData是interactionCommentinteractionAtinteractionReply,id
//
// final res = await Http.get('${VideoApi.detail}/${item['vlogID']}');
//
// Get.toNamed('/vloger', arguments: res['data']);
Get.toNamed('/vloger');
},
@ -358,7 +385,10 @@ class InteractionState extends State<Interaction> with SingleTickerProviderState
);
},
);
})),
},
);
},
),
),
],
),

View File

@ -3,11 +3,13 @@ library;
import 'dart:convert';
import 'package:easy_refresh/easy_refresh.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:loopin/IM/im_service.dart';
import 'package:loopin/api/video_api.dart';
import 'package:loopin/behavior/custom_scroll_behavior.dart';
import 'package:loopin/components/empty_tip.dart';
import 'package:loopin/components/network_or_asset_image.dart';
import 'package:loopin/models/conversation_type.dart';
import 'package:loopin/service/http.dart';
@ -17,10 +19,7 @@ import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_custom_elem.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message.dart';
// interactionComment, //->
// interactionAt, //->@
// interactionLike, //->
// interactionReply, //->
// newFocus,
class Newfoucs extends StatefulWidget {
const Newfoucs({super.key});
@ -31,9 +30,7 @@ class Newfoucs extends StatefulWidget {
class NewfoucsState extends State<Newfoucs> with SingleTickerProviderStateMixin {
bool isLoading = false; //
bool hasMore = true; //
final RxBool _throttleFlag = false.obs; //
final ScrollController chatController = ScrollController();
RxBool hasMore = true.obs; //
String page = '';
///-------------------
@ -53,24 +50,6 @@ class NewfoucsState extends State<Newfoucs> with SingleTickerProviderStateMixin
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();
}
//
@ -86,7 +65,7 @@ class NewfoucsState extends State<Newfoucs> with SingleTickerProviderStateMixin
msgList.addAll(res.data!);
logger.e(msgList);
if (res.data!.isEmpty) {
hasMore = false;
hasMore.value = false;
}
logger.i('聊天数据加载成功');
} else {
@ -138,19 +117,51 @@ class NewfoucsState extends State<Newfoucs> with SingleTickerProviderStateMixin
child: Column(
children: [
Expanded(
child: RefreshIndicator(
backgroundColor: Colors.white,
color: Color(0xFFFF5000),
displacement: 10.0,
onRefresh: handleRefresh,
child: Obx(() {
child: EasyRefresh.builder(
callLoadOverOffset: 20, //
callRefreshOverOffset: 20, //
header: ClassicHeader(
dragText: '下拉刷新',
armedText: '释放刷新',
readyText: '加载中...',
processingText: '加载中...',
processedText: '加载完成',
failedText: '加载失败,请重试',
messageText: '最后更新于 %T',
),
footer: ClassicFooter(
dragText: '加载更多',
armedText: '释放加载',
readyText: '加载中...',
processingText: '加载中...',
processedText: '加载完成',
noMoreText: '没有更多了~',
failedText: '加载失败,请重试',
messageText: '最后更新于 %T',
),
onRefresh: () async {
await handleRefresh();
},
onLoad: () async {
if (hasMore.value) {
await getMsgData();
return hasMore.value ? IndicatorResult.success : IndicatorResult.noMore;
}
},
childBuilder: (context, physics) {
return Obx(
() {
if (msgList.isEmpty) return EmptyTip();
return ListView.builder(
controller: chatController,
shrinkWrap: true,
physics: BouncingScrollPhysics(),
physics: physics,
itemCount: msgList.length,
itemBuilder: (context, index) {
//cloudCustomData
///[userID],
///[nickName],
///[faceUrl],
//----
V2TimMessage msg = msgList[index];
@ -169,8 +180,8 @@ class NewfoucsState extends State<Newfoucs> with SingleTickerProviderStateMixin
// 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);
// final cloudCustomData = 'newFocus';
// V2TimCustomElem msg = V2TimMessage(elemType: 2, isRead: index > 2 ? true : false);
// -----------
return Container(
width: double.infinity,
@ -202,10 +213,7 @@ class NewfoucsState extends State<Newfoucs> with SingleTickerProviderStateMixin
Expanded(
child: InkWell(
onTap: () async {
//
//
// cloudCustomData是interactionCommentinteractionAtinteractionReply,id
//
// ,
final res = await Http.get('${VideoApi.detail}/${item['vlogID']}');
Get.toNamed('/vloger', arguments: res['data']);
// Get.toNamed('/vloger');
@ -247,7 +255,7 @@ class NewfoucsState extends State<Newfoucs> with SingleTickerProviderStateMixin
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Visibility(
visible: true,
visible: false, //
//
child: NetworkOrAssetImage(
imageUrl: item['firstFrameImg'],
@ -269,7 +277,10 @@ class NewfoucsState extends State<Newfoucs> with SingleTickerProviderStateMixin
);
},
);
})),
},
);
},
),
),
],
),

View File

@ -1,111 +1,81 @@
///
///
library;
import 'dart:convert';
import 'package:easy_refresh/easy_refresh.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:loopin/IM/controller/chat_controller.dart';
import 'package:loopin/IM/im_service.dart';
import 'package:loopin/api/video_api.dart';
import 'package:loopin/behavior/custom_scroll_behavior.dart';
import 'package:loopin/components/empty_tip.dart';
import 'package:loopin/components/network_or_asset_image.dart';
import 'package:loopin/models/conversation_type.dart';
import 'package:loopin/service/http.dart';
import 'package:loopin/styles/index.dart';
import 'package:loopin/utils/index.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_follow_type_check_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_user_full_info.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_custom_elem.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message.dart';
class UserWithFollow {
final V2TimUserFullInfo userInfo;
int followType;
// newFocus,
UserWithFollow({
required this.userInfo,
this.followType = 0,
});
}
class Order extends StatefulWidget {
const Order({super.key});
class Newfoucs extends StatefulWidget {
const Newfoucs({super.key});
@override
State<Order> createState() => OrderState();
State<Newfoucs> createState() => NewfoucsState();
}
class OrderState extends State<Order> with SingleTickerProviderStateMixin {
class NewfoucsState extends State<Newfoucs> with SingleTickerProviderStateMixin {
bool isLoading = false; //
bool hasMore = true; //
RxBool hasMore = true.obs; //
String page = '';
List<UserWithFollow> dataList = <UserWithFollow>[];
///-------------------
V2TimConversation? conv;
RxList<V2TimMessage> msgList = <V2TimMessage>[].obs;
@override
void initState() {
super.initState();
getData();
if (Get.arguments != null && Get.arguments is V2TimConversation) {
//
conv = Get.arguments as V2TimConversation;
logger.e('lastmsg:$conv');
final lastmsg = conv?.lastMessage;
if (lastmsg != null) {
msgList.add(lastmsg);
}
}
}
//
Future<void> getData() async {
/// 0
/// 1
/// 2
/// 3
final res = await ImService.instance.getMyFollowingList(
nextCursor: page,
//
Future<void> getMsgData() async {
//
V2TimMessage? lastRealMsg;
lastRealMsg = msgList.last;
final res = await ImService.instance.getHistoryMessageList(
userID: ConversationType.newFocus.name, // userID为固定的newFocus
lastMsg: lastRealMsg,
);
if (res.success && res.data != null) {
logger.i('获取成功:${res.data!.nextCursor}');
final userInfoList = res.data!.userFullInfoList ?? [];
//
List<UserWithFollow> wrappedList = userInfoList.map((u) {
return UserWithFollow(userInfo: u);
}).toList();
// id
final userIDList = userInfoList.map((item) => item.userID).whereType<String>().toList();
if (userIDList.isNotEmpty) {
final shiRes = await ImService.instance.checkFollowType(userIDList: userIDList);
if (shiRes.success && shiRes.data != null) {
final shipResData = shiRes.data!;
for (final uwf in wrappedList) {
final userID = uwf.userInfo.userID;
if (userID != null) {
//
final match = shipResData.firstWhere(
(e) => e.userID == userID,
orElse: () => V2TimFollowTypeCheckResult(userID: ''),
);
if (match.userID?.isNotEmpty ?? false) {
uwf.followType = match.followType ?? 0;
msgList.addAll(res.data!);
logger.e(msgList);
if (res.data!.isEmpty) {
hasMore.value = false;
}
}
}
}
}
final isFinished = res.data!.nextCursor == null || res.data!.nextCursor!.isEmpty;
if (isFinished) {
setState(() {
hasMore = false;
});
//
page = '';
logger.i('聊天数据加载成功');
} else {
page = res.data!.nextCursor ?? '';
}
logger.i('获取数据成功:$userInfoList');
setState(() {
dataList.addAll(wrappedList);
});
} else {
logger.e('获取数据失败:${res.desc}');
logger.e('聊天数据加载失败:${res.desc}');
}
}
//
Future<void> handleRefresh() async {
dataList.clear();
page = '';
getData();
await Future.delayed(Duration(seconds: 5));
setState(() {});
}
@ -117,15 +87,28 @@ class OrderState extends State<Order> with SingleTickerProviderStateMixin {
centerTitle: true,
forceMaterialTransparency: true,
bottom: PreferredSize(
preferredSize: const Size.fromHeight(1.0),
preferredSize: Size.fromHeight(1.0),
child: Container(
color: Colors.grey[300],
height: 1.0,
),
),
title: const Text(
'关注',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
title: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(width: 4),
Text(
'新的关注',
style: TextStyle(
color: Colors.black,
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
],
),
),
actions: [],
),
@ -151,7 +134,8 @@ class OrderState extends State<Order> with SingleTickerProviderStateMixin {
armedText: '释放加载',
readyText: '加载中...',
processingText: '加载中...',
processedText: hasMore ? '加载完成' : '没有更多了~',
processedText: '加载完成',
noMoreText: '没有更多了~',
failedText: '加载失败,请重试',
messageText: '最后更新于 %T',
),
@ -159,66 +143,106 @@ class OrderState extends State<Order> with SingleTickerProviderStateMixin {
await handleRefresh();
},
onLoad: () async {
if (hasMore) {
await getData();
if (hasMore.value) {
await getMsgData();
return hasMore.value ? IndicatorResult.success : IndicatorResult.noMore;
}
},
childBuilder: (context, physics) {
return Obx(
() {
if (msgList.isEmpty) return EmptyTip();
return ListView.builder(
shrinkWrap: true,
physics: physics,
itemCount: dataList.length,
itemCount: msgList.length,
itemBuilder: (context, index) {
final item = dataList[index];
return Ink(
key: ValueKey(item.userInfo.userID),
child: Container(
//cloudCustomData
///[userID],
///[nickName],
///[faceUrl],
//----
V2TimMessage msg = msgList[index];
V2TimCustomElem element = msgList[index].customElem!;
final cloudCustomData = msgList[index].cloudCustomData;
logger.w(cloudCustomData);
final desc = msgList[index].customElem!.desc!;
String? jsonData = msgList[index].customElem!.data;
jsonData = (jsonData == null || jsonData.isEmpty) ? '{"faceUrl":"","nickName":"data为空","userID":"213213"}' : jsonData;
final item = jsonDecode(jsonData ?? '{"faceUrl":"","nickName":"测试昵称","userID":"213213"}');
logger.w(element.toJson());
// ----
// final jsonData = '{"faceUrl":"","nickName":"测试昵称","userID":"213213"}';
// final item = jsonDecode(jsonData); //
// final desc = '测试desc';
// final cloudCustomData = 'newFocus';
// V2TimCustomElem msg = 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(
crossAxisAlignment: CrossAxisAlignment.center,
spacing: 10.0,
children: <Widget>[
// + +
Expanded(
child: InkWell(
onTap: () {
Get.toNamed(
'/vloger',
arguments: item.userInfo.userID,
);
//
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: Row(
children: [
ClipOval(
child: ClipOval(
child: NetworkOrAssetImage(
imageUrl: item.userInfo.faceUrl,
imageUrl: item['faceUrl'],
width: 50,
height: 50,
),
),
const SizedBox(width: 10),
),
//
Expanded(
child: InkWell(
onTap: () async {
// ,
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.userInfo.nickName?.isNotEmpty == true ? item.userInfo.nickName! : '未知昵称',
style: const TextStyle(
item['nickName'] ?? '未知',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.normal,
),
),
if (item.userInfo.selfSignature?.isNotEmpty ?? false) ...[
const SizedBox(height: 2.0),
//
Text(
item.userInfo.selfSignature!,
style: const TextStyle(
color: Colors.grey,
fontSize: 13.0,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(color: Colors.grey, fontSize: 12.0),
desc,
// '很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容',
),
],
],
Text(
Utils.formatTime(msg.timestamp ?? DateTime.now().millisecondsSinceEpoch ~/ 1000),
style: TextStyle(
color: Colors.grey[600],
fontSize: 12,
),
),
],
@ -226,70 +250,32 @@ class OrderState extends State<Order> with SingleTickerProviderStateMixin {
),
),
SizedBox(width: 10),
//
TextButton(
style: TextButton.styleFrom(
backgroundColor: item.followType == 3 ? Colors.grey : FStyle.primaryColor,
minimumSize: const Size(70, 32),
padding: const EdgeInsets.symmetric(horizontal: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4),
//
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Visibility(
visible: false, //
//
child: NetworkOrAssetImage(
imageUrl: item['firstFrameImg'],
placeholderAsset: 'assets/images/bk.jpg',
width: 40,
height: 60,
),
),
onPressed: () async {
final ctl = Get.find<ChatController>();
final checkRes = await ImService.instance.checkFollowType(userIDList: [item.userInfo.userID!]);
int realFollowType = 0;
if (checkRes.success && checkRes.data != null) {
realFollowType = checkRes.data!.first.followType ?? 0;
if ([1, 3].contains(realFollowType)) {
//
final unRes = await ImService.instance.unfollowUser(userIDList: [item.userInfo.userID!]);
if (unRes.success) {
setState(() {
item.followType = 2;
});
ctl.mergeNoFriend(conversationID: 'c2c_${item.userInfo.userID!}');
}
} else {
//
final res = await ImService.instance.followUser(userIDList: [item.userInfo.userID!]);
if (res.success) {
setState(() {
item.followType = realFollowType == 0
? 1
: realFollowType == 2
? 3
: 0;
});
final chatRes = await ImService.instance.followUser(userIDList: [item.userInfo.userID!]);
if (chatRes.success) {
final res = await ImService.instance.getConversation(conversationID: 'c2c_${item.userInfo.userID}');
if (res.success) {
V2TimConversation conversation = res.data;
if (conversation.conversationGroupList?.isNotEmpty ?? false) {
await ImService.instance.deleteConversationsFromGroup(
groupName: conversation.conversationGroupList!.first!,
conversationIDList: [conversation.conversationID],
const SizedBox(width: 5.0),
//
Visibility(
visible: !(msg.isRead ?? true),
child: FStyle.badge(0, isdot: true),
),
],
),
],
),
);
ctl.updateNoFriendMenu();
}
}
}
}
}
}
},
child: Text(
Utils.getTipText(item.followType),
style: const TextStyle(color: Colors.white, fontSize: 14),
),
),
],
),
),
);
},
);

View File

@ -163,14 +163,15 @@ class _MemberActionSheetState extends State<InviteActionSheet> {
armedText: '释放加载',
readyText: '加载中...',
processingText: '加载中...',
processedText: hasMore ? '加载完成' : '没有更多了~',
processedText: '加载完成',
noMoreText: '没有更多了~',
failedText: '加载失败,请重试',
messageText: '最后更新于 %T',
),
onLoad: () async {
//
if (hasMore) {
await getMemberData();
return hasMore ? IndicatorResult.success : IndicatorResult.noMore;
}
},
child: ListView.builder(

View File

@ -69,6 +69,7 @@ class _MemberActionSheetState extends State<MemberActionSheet> {
final mem = res.data!.memberInfoList ?? [];
setState(() {
members.addAll(mem);
hasMore = res.data!.nextSeq == '0' ? false : true;
loading = false;
});
}
@ -197,18 +198,19 @@ class _MemberActionSheetState extends State<MemberActionSheet> {
armedText: '释放加载',
readyText: '加载中...',
processingText: '加载中...',
processedText: hasMore ? '加载完成' : '没有更多了~',
processedText: '加载完成',
noMoreText: '没有更多了~',
failedText: '加载失败,请重试',
messageText: '最后更新于 %T',
),
onLoad: () async {
//
if (hasMore) {
if (_query.isNotEmpty) {
if (_query.isNotEmpty && (!isFinished)) {
await searchMember(loadMore: true);
} else {
return !isFinished ? IndicatorResult.success : IndicatorResult.noMore;
} else if (hasMore) {
await getMemberData();
}
return hasMore ? IndicatorResult.success : IndicatorResult.noMore;
}
},
child: ListView.builder(

View File

@ -6,6 +6,7 @@ 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/empty_tip.dart';
import 'package:loopin/components/network_or_asset_image.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_info.dart';
@ -103,7 +104,8 @@ class GrouplistState extends State<Grouplist> with SingleTickerProviderStateMixi
armedText: '释放加载',
readyText: '加载中...',
processingText: '加载中...',
processedText: hasMore ? '加载完成' : '没有更多了~',
processedText: '加载完成',
noMoreText: '没有更多了~',
failedText: '加载失败,请重试',
messageText: '最后更新于 %T',
),
@ -113,9 +115,12 @@ class GrouplistState extends State<Grouplist> with SingleTickerProviderStateMixi
onLoad: () async {
if (hasMore) {
await getData();
return hasMore ? IndicatorResult.success : IndicatorResult.noMore;
}
},
childBuilder: (context, physics) {
if (dataList.isEmpty) return EmptyTip();
return ListView.builder(
physics: physics,
itemCount: dataList.length,

View File

@ -135,7 +135,6 @@ class _StartGroupChatPageState extends State<StartGroupChatPage> {
msg: msgRes.data!.messageInfo!,
groupID: groupID,
isExcludedFromUnreadCount: true,
isPush: false,
groupName: groupName,
cloudCustomData: 'tips',
);
@ -304,13 +303,17 @@ class _StartGroupChatPageState extends State<StartGroupChatPage> {
armedText: '释放加载',
readyText: '加载中...',
processingText: '加载中...',
processedText: hasMore ? '加载完成' : '没有更多了~',
processedText: '加载完成',
failedText: '加载失败,请重试',
noMoreText: '没有更多了~',
messageText: '最后更新于 %T',
),
onRefresh: () async => _loadData(reset: true),
onLoad: () async {
if (hasMore) await _loadData();
if (hasMore) {
await _loadData();
return hasMore ? IndicatorResult.success : IndicatorResult.noMore;
}
},
child: filteredList.isEmpty
? _emptyTip('暂无数据')

View File

@ -4,6 +4,7 @@ import 'package:loopin/IM/controller/im_user_info_controller.dart';
import 'package:loopin/components/my_qrcode.dart';
import 'package:loopin/pages/my/merchant/balance/balance.dart';
import 'package:loopin/pages/my/merchant/balance/controller.dart';
import 'package:loopin/utils/index.dart';
import 'package:loopin/utils/scan_code_type.dart';
class AllFunctionsPage extends StatefulWidget {
@ -20,8 +21,8 @@ class _AllFunctionsPageState extends State<AllFunctionsPage> {
'title': '主页展示',
'items': [
{'id': 'home_order', 'icon': 'assets/images/ico_order.png', 'label': '订单'},
{'id': 'home_balance', 'icon': 'assets/images/ico_dhx.png', 'label': '余额logout'},
{'id': 'home_withdraw', 'icon': 'assets/images/ico_sh.png', 'label': '提现vloger'},
{'id': 'home_balance', 'icon': 'assets/images/ico_dhx.png', 'label': '余额'},
{'id': 'home_hym', 'icon': 'assets/images/ico_tgm.png', 'label': '好友码'},
{'id': 'home_promo', 'icon': 'assets/images/ico_tgm.png', 'label': '推广码'},
]
},
@ -72,7 +73,13 @@ class _AllFunctionsPageState extends State<AllFunctionsPage> {
itemCount: functionList.length,
itemBuilder: (context, sectionIndex) {
final section = functionList[sectionIndex];
final role = controller.role.value;
final isSeller = Utils.hasRole(role, 2);
if (section['title'] == '更多功能' && !isSeller) {
return SizedBox();
} else {
return _buildSection(section);
}
},
),
),
@ -80,6 +87,8 @@ class _AllFunctionsPageState extends State<AllFunctionsPage> {
}
Widget _buildSection(Map<String, dynamic> section) {
final role = controller.role.value;
final isSeller = Utils.hasRole(role, 2);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
@ -112,7 +121,8 @@ class _AllFunctionsPageState extends State<AllFunctionsPage> {
itemBuilder: (context, index) {
final item = (section['items'] as List)[index];
final role = controller.role.value;
if (item['id'] == 'home_promo' && role != 4) {
final hasRole = Utils.hasRole(role, 5);
if (item['id'] == 'home_promo' && !hasRole) {
return SizedBox();
} else {
return _buildFunctionItem(item);
@ -121,7 +131,8 @@ class _AllFunctionsPageState extends State<AllFunctionsPage> {
),
// 线 - section时显示
if (functionList.indexOf(section) != functionList.length - 1)
// if (functionList.indexOf(section) != functionList.length - 1)
if (isSeller)
Container(
height: 2,
margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 20),
@ -191,14 +202,12 @@ class _AllFunctionsPageState extends State<AllFunctionsPage> {
Get.to(() => Balance());
// showLogoutDialog(context);
break;
case 'home_withdraw':
Get.toNamed('/vloger');
case 'home_hym':
qrcodeAlertDialog(context);
break;
case 'home_promo':
// 广
Get.to(() => MyQrcode(
prefix: QrTypeCode.tgm,
));
qrcodeAlertDialog(context);
break;
case 'more_seller_order':
Get.toNamed('/sellerOrder');
@ -209,6 +218,41 @@ class _AllFunctionsPageState extends State<AllFunctionsPage> {
}
}
//
void qrcodeAlertDialog(BuildContext context) {
final role = controller.role.value;
final isLeader = Utils.hasRole(role, 5);
showDialog(
context: context,
builder: (context) {
return UnconstrainedBox(
constrainedAxis: Axis.vertical,
child: SizedBox(
width: 350.0,
child: AlertDialog(
contentPadding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 20.0),
backgroundColor: Colors.white,
surfaceTintColor: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15.0)),
content: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
MyQrcode(
prefix: QrTypeCode.tgm,
text: isLeader ? '推广码' : '',
)
],
),
),
),
),
);
},
);
}
// 退
void showLogoutDialog(BuildContext context) {
showDialog(

268
lib/pages/my/delete.dart Normal file
View File

@ -0,0 +1,268 @@
///
library;
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:loopin/IM/im_service.dart';
import 'package:loopin/api/common_api.dart';
import 'package:loopin/controller/video_module_controller.dart';
import 'package:loopin/service/http.dart';
import 'package:loopin/utils/common.dart';
import 'package:shirne_dialog/shirne_dialog.dart';
import '../../utils/index.dart';
class Delete extends StatefulWidget {
const Delete({super.key});
@override
State<Delete> createState() => _DeleteState();
}
class _DeleteState extends State<Delete> {
final Map authObj = {'phonenumber': '', 'smsCode': '', 'clientId': '428a8310cd442757ae699df5d894f051', 'grantType': 'sms'};
final fieldController = TextEditingController();
Timer? timer;
String vcodeText = '获取验证码';
bool disabled = false;
int time = 60;
@override
void initState() {
super.initState();
}
@override
void dispose() {
super.dispose();
timer?.cancel();
}
//
void handleClear() {
fieldController.clear();
setState(() {
authObj['phonenumber'] = '';
});
}
//退
void handleLogout() async {}
//
void handleSubmit() async {
FocusScope.of(context).unfocus(); //
if (authObj['phonenumber'] == '') {
MyDialog.toast('手机号不能为空', icon: Icon(Icons.warning), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200)));
} else if (!Utils.checkTel(authObj['phonenumber'])) {
MyDialog.toast('手机号格式不正确', icon: Icon(Icons.warning), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200)));
} else if (authObj['smsCode'] == '') {
MyDialog.toast('验证码不能为空', icon: Icon(Icons.warning), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200)));
} else {
final dialogController = MyDialog.loading('注销中...');
// 退
final loginRes = await ImService.instance.logout();
if (loginRes.success) {
//
Common.logout();
//
final videoController = Get.find<VideoModuleController>();
videoController.init();
//
final del = await Http.post('${CommonApi.revoked}?smsCode=${authObj['smsCode']}');
logger.w(del);
//
Get.offAllNamed('/');
}
}
}
// 60s倒计时
void handleVcode() {
logger.i(authObj);
FocusScope.of(context).unfocus(); //
if (authObj['phonenumber'] == '') {
MyDialog.toast('手机号不能为空', icon: Icon(Icons.warning), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200)));
} else if (!Utils.checkTel(authObj['phonenumber'])) {
MyDialog.toast('手机号格式不正确', icon: Icon(Icons.warning), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200)));
} else {
setState(() {
disabled = true;
});
startTimer();
getCode();
}
}
startTimer() {
timer = Timer.periodic(const Duration(seconds: 1), (timer) {
setState(() {
if (time > 0) {
vcodeText = '获取验证码(${time--})';
} else {
vcodeText = '获取验证码';
time = 60;
disabled = false;
timer.cancel();
}
});
});
}
void getCode() async {
//
final res = await Http.get('${CommonApi.dictionaryApi}sms_template_id');
final dictData = res['data'] as List;
final templeId = dictData.first['dictValue'];
logger.w(templeId);
final resCode = await Http.get(CommonApi.getDelCode, params: {'templateId': templeId});
logger.i('注销验证短信:$resCode');
}
@override
Widget build(BuildContext context) {
final phoneNum = authObj['phonenumber'] as String;
return GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () => FocusScope.of(context).unfocus(),
child: Scaffold(
appBar: AppBar(
title: Text(
'注销账号',
style: TextStyle(color: Colors.black),
),
forceMaterialTransparency: true,
),
body: Container(
alignment: Alignment.center,
child: Column(
children: [
Container(
height: 40.0,
margin: const EdgeInsets.symmetric(vertical: 5.0, horizontal: 30.0),
decoration: BoxDecoration(
color: Color(0xFFFAF8F5),
borderRadius: BorderRadius.circular(15.0),
),
child: Row(
children: [
Expanded(
child: TextField(
keyboardType: TextInputType.phone,
controller: fieldController,
inputFormatters: [
LengthLimitingTextInputFormatter(11), // 11
FilteringTextInputFormatter.digitsOnly, //
],
decoration: InputDecoration(
hintText: '输入手机号',
hintStyle: const TextStyle(color: Colors.black38),
suffixIcon: Visibility(
visible: phoneNum.isNotEmpty,
child: InkWell(
hoverColor: Colors.transparent,
highlightColor: Colors.transparent,
splashColor: Colors.transparent,
onTap: handleClear,
child: const Icon(
Icons.clear,
color: Colors.grey,
size: 16.0,
),
),
),
contentPadding: const EdgeInsets.symmetric(vertical: 0, horizontal: 12.0),
border: const OutlineInputBorder(borderSide: BorderSide.none),
),
onChanged: (value) {
setState(() {
authObj['phonenumber'] = value;
});
},
),
)
],
),
),
Container(
height: 40.0,
margin: const EdgeInsets.symmetric(vertical: 5.0, horizontal: 30.0),
decoration: BoxDecoration(color: Color(0xFFFAF8F5), borderRadius: BorderRadius.circular(15.0)),
child: Row(
children: [
Expanded(
child: TextField(
keyboardType: TextInputType.phone,
inputFormatters: [
LengthLimitingTextInputFormatter(6),
FilteringTextInputFormatter.digitsOnly, //
],
decoration: const InputDecoration(
hintText: '验证码',
hintStyle: TextStyle(color: Colors.black38),
contentPadding: EdgeInsets.symmetric(vertical: 0, horizontal: 12.0),
border: OutlineInputBorder(borderSide: BorderSide.none),
),
onChanged: (value) {
setState(() {
authObj['smsCode'] = value;
});
},
),
),
SizedBox(
height: 25.0,
child: Container(
margin: const EdgeInsets.only(right: 8.0),
child: ElevatedButton(
style: ButtonStyle(
backgroundColor: WidgetStateProperty.all(Colors.white),
shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0))),
padding: WidgetStateProperty.all(EdgeInsets.symmetric(horizontal: 15.0))),
onPressed: !disabled ? handleVcode : null,
child: Text(vcodeText, style: const TextStyle(fontSize: 13.0)),
),
),
)
],
),
),
Text(
'提示:\n'
'1. 账号注销后,平台会保留您的数据 7 天。\n'
'2. 若 7 天内未登录,平台将销毁所有保留数据。\n'
'3. 若在 7 天内重新登录,则视为放弃注销。\n'
'请谨慎操作。',
style: TextStyle(fontSize: 14, color: Colors.red[700]),
),
Container(
margin: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 30.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15.0),
),
child: SizedBox(
width: double.infinity,
height: 45.0,
child: FilledButton(
style: ButtonStyle(shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.circular(30.0)))),
onPressed: handleSubmit,
child: const Text(
'确认注销',
style: TextStyle(fontSize: 16.0),
),
),
),
),
const SizedBox(
height: 10.0,
),
],
),
),
));
}
}

View File

@ -7,6 +7,7 @@ import 'package:get/get.dart';
import 'package:loopin/IM/controller/chat_controller.dart';
import 'package:loopin/IM/im_service.dart';
import 'package:loopin/behavior/custom_scroll_behavior.dart';
import 'package:loopin/components/empty_tip.dart';
import 'package:loopin/components/network_or_asset_image.dart';
import 'package:loopin/styles/index.dart';
import 'package:loopin/utils/index.dart';
@ -151,7 +152,8 @@ class FansState extends State<Fans> with SingleTickerProviderStateMixin {
armedText: '释放加载',
readyText: '加载中...',
processingText: '加载中...',
processedText: hasMore ? '加载完成' : '没有更多了~',
processedText: '加载完成',
noMoreText: '没有更多了~',
failedText: '加载失败,请重试',
messageText: '最后更新于 %T',
),
@ -161,9 +163,12 @@ class FansState extends State<Fans> with SingleTickerProviderStateMixin {
onLoad: () async {
if (hasMore) {
await getData();
return hasMore ? IndicatorResult.success : IndicatorResult.noMore;
}
},
childBuilder: (context, physics) {
if (dataList.isEmpty) return EmptyTip();
return ListView.builder(
physics: physics,
itemCount: dataList.length,

View File

@ -7,6 +7,7 @@ import 'package:get/get.dart';
import 'package:loopin/IM/controller/chat_controller.dart';
import 'package:loopin/IM/im_service.dart';
import 'package:loopin/behavior/custom_scroll_behavior.dart';
import 'package:loopin/components/empty_tip.dart';
import 'package:loopin/components/network_or_asset_image.dart';
import 'package:loopin/styles/index.dart';
import 'package:loopin/utils/index.dart';
@ -151,7 +152,8 @@ class FlowingState extends State<Flowing> with SingleTickerProviderStateMixin {
armedText: '释放加载',
readyText: '加载中...',
processingText: '加载中...',
processedText: hasMore ? '加载完成' : '没有更多了~',
processedText: '加载完成',
noMoreText: '没有更多了~',
failedText: '加载失败,请重试',
messageText: '最后更新于 %T',
),
@ -161,9 +163,12 @@ class FlowingState extends State<Flowing> with SingleTickerProviderStateMixin {
onLoad: () async {
if (hasMore) {
await getData();
return hasMore ? IndicatorResult.success : IndicatorResult.noMore;
}
},
childBuilder: (context, physics) {
if (dataList.isEmpty) return EmptyTip();
return ListView.builder(
physics: physics,
itemCount: dataList.length,

View File

@ -13,8 +13,9 @@ import 'package:loopin/components/my_qrcode.dart';
import 'package:loopin/components/network_or_asset_image.dart';
import 'package:loopin/components/only_down_scroll_physics.dart';
import 'package:loopin/controller/video_module_controller.dart';
import 'package:loopin/pages/my/merchant/balance/balance.dart';
import 'package:loopin/pages/my/merchant/balance/controller.dart';
import 'package:loopin/service/http.dart';
import 'package:loopin/styles/index.dart';
import 'package:loopin/utils/index.dart';
import 'package:loopin/utils/scan_code_type.dart';
import 'package:nested_scroll_view_plus/nested_scroll_view_plus.dart';
@ -279,6 +280,8 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
//
void qrcodeAlertDialog(BuildContext context) {
final role = imUserInfoController?.role.value ?? 0;
final isLeader = Utils.hasRole(role, 5);
showDialog(
context: context,
builder: (context) {
@ -288,7 +291,7 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
width: 350.0,
child: AlertDialog(
contentPadding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 20.0),
backgroundColor: const Color(0xff07c160),
backgroundColor: Colors.white,
surfaceTintColor: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15.0)),
content: Padding(
@ -296,14 +299,10 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
MyQrcode(prefix: QrTypeCode.tgm)
// Image.asset('assets/images/pic1.jpg', width: 250.0, fit: BoxFit.contain),
// const SizedBox(height: 15.0),
// const Text('扫一扫,加好友',
// style: TextStyle(
// color: Colors.white38,
// fontSize: 14.0,
// )),
MyQrcode(
prefix: QrTypeCode.tgm,
text: isLeader ? '推广码' : '',
),
],
),
),
@ -748,6 +747,7 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
),
child: Text(
nickname.isNotEmpty ? nickname : '昵称',
// '啊啊啊啊啊啊啊啊',
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
@ -759,51 +759,52 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
),
//
Obx(
() {
// MERCHANT(2, "商家"),
// AGENT(3, "代理"),
// PLATFORM(4, "平台"),
// REFERENCE(5, "团长")
final role = imUserInfoController?.role.value;
if (role == 4) {
return Row(children: [
SizedBox(width: 8),
//
InkWell(
onTap: () => qrcodeAlertDialog(context),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 5),
decoration: BoxDecoration(
color: Colors.black.withAlpha(176),
borderRadius: BorderRadius.circular(20),
),
child: Row(
children: [
Icon(
Icons.qr_code_outlined,
size: 18,
color: FStyle.secondaryColor,
),
SizedBox(width: 4),
Text(
'团长邀请码',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: FStyle.secondaryColor, //
),
)
],
),
),
),
]);
} else {
return const SizedBox();
}
},
),
// Obx(
// () {
// // MERCHANT(2, "商家"),
// // AGENT(3, "代理"),
// // PLATFORM(4, "平台"),
// // REFERENCE(5, "团长")
// final role = imUserInfoController?.role.value;
// final isLeader = Utils.hasRole(role ?? 0, 5);
// if (!isLeader) {
// return Row(children: [
// SizedBox(width: 8),
// //
// InkWell(
// onTap: () => qrcodeAlertDialog(context),
// child: Container(
// padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 5),
// decoration: BoxDecoration(
// color: Colors.black.withAlpha(176),
// borderRadius: BorderRadius.circular(20),
// ),
// child: Row(
// children: [
// Icon(
// Icons.qr_code_outlined,
// size: 18,
// color: FStyle.secondaryColor,
// ),
// SizedBox(width: 4),
// Text(
// '团长邀请码',
// style: TextStyle(
// fontSize: 12,
// fontWeight: FontWeight.bold,
// color: FStyle.secondaryColor, //
// ),
// )
// ],
// ),
// ),
// ),
// ]);
// } else {
// return const SizedBox();
// }
// },
// ),
],
),
const SizedBox(height: 8),
@ -907,6 +908,8 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
}
Widget _buildOrderCard(BuildContext context) {
final role = imUserInfoController?.role.value ?? 0;
final isLeader = Utils.hasRole(role, 5);
return Container(
decoration: BoxDecoration(
color: Colors.white,
@ -946,15 +949,16 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
_buildOrderIcon('assets/images/ico_order.png', '订单', () {
Get.toNamed('/myOrder');
}),
_buildOrderIcon('assets/images/ico_dhx.png', '余额logout', () {
_buildOrderIcon('assets/images/ico_dhx.png', '余额', () {
Get.put(BalanceController());
Get.to(() => Balance());
}),
_buildOrderIcon('assets/images/ico_tgm.png', isLeader ? '推广码' : '好友码', () {
qrcodeAlertDialog(context);
}),
_buildOrderIcon('assets/images/icon_logout.png', '退出登录', () {
showLogoutDialog(context);
}),
_buildOrderIcon('assets/images/ico_sh.png', '提现vloger', () {
Get.toNamed('/vloger');
}),
_buildOrderIcon('assets/images/ico_tgm.png', '推广码', () {
logger.e('推广码');
}),
],
),
),

View File

@ -2,9 +2,11 @@
import 'package:easy_refresh/easy_refresh.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:loopin/IM/im_service.dart';
import 'package:loopin/IM/controller/im_user_info_controller.dart';
import 'package:loopin/behavior/custom_scroll_behavior.dart';
import 'package:loopin/components/empty_tip.dart';
import 'package:loopin/pages/my/merchant/balance/controller.dart';
import 'package:loopin/utils/index.dart';
class Balance extends StatefulWidget {
const Balance({super.key});
@ -14,12 +16,18 @@ class Balance extends StatefulWidget {
}
class _BalanceState extends State<Balance> {
final controller = Get.put(BalanceController());
final controller = Get.find<BalanceController>();
@override
void dispose() {
Get.delete<BalanceController>();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("我的钱包")),
appBar: AppBar(title: Text("我的余额")),
body: ScrollConfiguration(
behavior: CustomScrollBehavior().copyWith(scrollbars: false),
child: Column(
@ -30,6 +38,9 @@ class _BalanceState extends State<Balance> {
color: Colors.blueAccent,
width: double.infinity,
child: Obx(() {
final ctl = Get.find<ImUserInfoController>();
final role = ctl.role.value;
final isLeader = Utils.hasRole(role, 5);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -46,13 +57,35 @@ class _BalanceState extends State<Balance> {
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 12),
// if (isLeader) const SizedBox(height: 12),
Row(
children: [
ElevatedButton(
onPressed: () => controller.recharge(100.0),
onPressed: () {
//
controller.recharge(money: '0.1');
// http
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.orange,
),
child: const Text("充值 100 元"),
child: const Text("充值"),
),
SizedBox(width: 20),
//
ElevatedButton(
onPressed: () {
//
// controller.recharge(money: '0.1');
controller.withDraw(money: '1000');
// http
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.orange,
),
child: const Text("提现"),
),
],
),
],
);
@ -61,8 +94,7 @@ class _BalanceState extends State<Balance> {
//
Expanded(
child: Obx(() {
return EasyRefresh.builder(
child: EasyRefresh.builder(
callLoadOverOffset: 20, //
callRefreshOverOffset: 20, //
header: ClassicHeader(
@ -79,32 +111,45 @@ class _BalanceState extends State<Balance> {
armedText: '释放加载',
readyText: '加载中...',
processingText: '加载中...',
processedText: controller.hasMore.value ? '加载完成' : '没有更多了~',
processedText: '加载完成',
noMoreText: '没有更多了~',
failedText: '加载失败,请重试',
messageText: '最后更新于 %T',
succeededIcon: controller.hasMore.value ? Icon(Icons.check_circle, color: Colors.green) : Icon(Icons.warning, color: Colors.orange),
),
onRefresh: () async {
await controller.getData(reset: true);
},
onLoad: () async {
if (controller.hasMore.value) {
await controller.getData();
return controller.hasMore.value ? IndicatorResult.success : IndicatorResult.noMore;
}
},
childBuilder: (context, physics) {
return Obx(
() {
//
if (controller.isLoading.value && controller.data.isEmpty) {
return Center(
child: CircularProgressIndicator(),
);
}
//
if (controller.data.isEmpty) {
return EmptyTip();
}
return ListView.builder(
physics: physics,
itemCount: controller.data.length,
itemBuilder: (context, index) {
final tx = controller.data[index];
logger.w(tx.source);
return ListTile(
title: Text(tx.source),
subtitle: Text(tx.createTime),
trailing: Text(
"变动金额:${tx.changeType == 1 ? '+' : '-'}${tx.changeAmount}", //
"变动金额:${tx.changeType == 1 ? '+' : '-'}${tx.changeAmount}${tx.id}", //
style: TextStyle(
fontWeight: FontWeight.bold,
color: tx.changeType == 1 ? Colors.green : Colors.red,
@ -115,7 +160,8 @@ class _BalanceState extends State<Balance> {
);
},
);
}),
},
),
),
],
),

View File

@ -1,5 +1,11 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:loopin/IM/controller/im_user_info_controller.dart';
import 'package:loopin/IM/im_friend_listeners.dart';
import 'package:loopin/api/common_api.dart';
import 'package:loopin/pages/my/merchant/balance/model.dart';
import 'package:loopin/service/http.dart';
import 'package:loopin/utils/wxsdk.dart';
class BalanceController extends GetxController {
///
@ -21,36 +27,130 @@ class BalanceController extends GetxController {
}
///
void recharge(double amount) {
balance.value += amount;
//
Future<void> recharge({required String money}) async {
//
final data = {"orderType": "RECHARGE", "clientType": "APP", "paymentMethod": "WECHAT", "paymentClient": "APP", "money": money};
final res = await Http.post(CommonApi.addBalance, data: data);
logger.w(res);
final payParams = res['data'];
logger.w(payParams);
//
await Wxsdk.payWithWx(
appId: payParams['appid'],
partnerId: payParams['partnerid'],
prepayId: payParams['prepayid'],
packageValue: payParams['package'],
nonceStr: payParams['noncestr'],
timestamp: int.parse(payParams['timestamp']),
sign: payParams['sign'],
);
//
// getData(reset: true);
}
///
Future<void> withDraw({required String money}) async {
final infoCtl = Get.find<ImUserInfoController>();
final openId = infoCtl.customInfo['openId'];
if (openId == null || openId.isEmpty) {
showDialog(
context: Get.context!,
builder: (context) {
return AlertDialog(
title: Text('微信授权'),
content: const Text('余额提现至您的微信零钱内,是否前往微信授权?', style: TextStyle(fontSize: 16.0)),
backgroundColor: Colors.white,
surfaceTintColor: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15.0)),
elevation: 2.0,
actionsPadding: const EdgeInsets.all(15.0),
actions: [
TextButton(onPressed: () => {Get.back()}, child: Text('取消', style: TextStyle(color: Colors.red))),
TextButton(
onPressed: () async {
//
await Wxsdk.login();
Get.back();
},
child: Text('确认')),
],
);
},
);
return;
}
//
// final res = await Http.post(CommonApi.withdraw, data: {
// "money": money,
// "method": "1",
// });
// MyDialog.('提现成功,系统将在一个工作日内将余额提现至您绑定的微信内');
showDialog(
context: Get.context!,
builder: (context) {
return AlertDialog(
title: Text('提现成功'),
content: const Text('系统将在一个工作日内将余额提现至您绑定的微信内', style: TextStyle(fontSize: 16.0)),
backgroundColor: Colors.white,
surfaceTintColor: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15.0)),
elevation: 2.0,
actionsPadding: const EdgeInsets.all(15.0),
actions: [
TextButton(onPressed: () => {Get.back()}, child: Text('确认')),
],
);
},
);
getData(reset: true);
}
///
Future<void> getData({bool reset = false}) async {
if (isLoading.value) return;
if (isLoading.value) {
logger.w('正在加载中,跳过');
return;
}
isLoading.value = true;
logger.w('开始加载数据reset: $reset, currentPage: $currentPage');
await Future.delayed(const Duration(seconds: 2));
if (reset) {
logger.w('重置数据');
currentPage = 1;
data.clear();
hasMore.value = true;
}
await Future.delayed(const Duration(seconds: 3)); //
List<AccountBill> newData = List.generate(
10,
(index) => AccountBill(id: index),
(index) {
int id = currentPage * 10 + index + 1;
// logger.w('生成数据: id=$id');
return AccountBill(
id: id,
source: '来源 $currentPage-${index + 1}',
changeAmount: (index + 1) * 10.0,
changeType: index % 2 + 1,
createTime: DateTime.now().toString(),
);
},
);
data.addAll(newData);
currentPage++;
logger.w('添加了 ${newData.length} 条数据,总数据量: ${data.length}');
if (currentPage > 3) {
currentPage++;
logger.w('页码增加到: $currentPage');
if (currentPage > 5) {
hasMore.value = false;
logger.w('没有更多数据了');
}
isLoading.value = false;
logger.w('加载完成');
}
}

View File

@ -7,6 +7,7 @@ import 'package:get/get.dart';
import 'package:loopin/IM/controller/chat_controller.dart';
import 'package:loopin/IM/im_service.dart';
import 'package:loopin/behavior/custom_scroll_behavior.dart';
import 'package:loopin/components/empty_tip.dart';
import 'package:loopin/components/network_or_asset_image.dart';
import 'package:loopin/styles/index.dart';
import 'package:loopin/utils/index.dart';
@ -151,7 +152,8 @@ class MutualFollowersState extends State<MutualFollowers> with SingleTickerProvi
armedText: '释放加载',
readyText: '加载中...',
processingText: '加载中...',
processedText: hasMore ? '加载完成' : '没有更多了~',
processedText: '加载完成',
noMoreText: '没有更多了~',
failedText: '加载失败,请重试',
messageText: '最后更新于 %T',
),
@ -161,9 +163,12 @@ class MutualFollowersState extends State<MutualFollowers> with SingleTickerProvi
onLoad: () async {
if (hasMore) {
await getData();
return hasMore ? IndicatorResult.success : IndicatorResult.noMore;
}
},
childBuilder: (context, physics) {
if (dataList.isEmpty) return EmptyTip();
return ListView.builder(
physics: physics,
itemCount: dataList.length,

View File

@ -65,10 +65,10 @@ class _NickNameState extends State<NickName> {
border: OutlineInputBorder(),
contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 10),
),
maxLength: 20,
maxLength: 8,
validator: FormBuilderValidators.compose([
FormBuilderValidators.required(errorText: '昵称不能为空'),
FormBuilderValidators.maxLength(20, errorText: '昵称不能超过20个字符'),
FormBuilderValidators.maxLength(8, errorText: '昵称不能超过8个字符'),
]),
),
const SizedBox(height: 8),

View File

@ -14,9 +14,12 @@ class Setting extends StatelessWidget {
'onTap': () => Get.toNamed('/userInfo'),
},
{
'icon': Icons.notifications,
'title': '通知设置',
'onTap': () => Get.toNamed('/notifications'),
// 'icon': Icons.notifications,
// 'title': '通知设置',
// 'onTap': () => Get.toNamed('/notifications'),
'icon': Icons.logout,
'title': '注销账号',
'onTap': () => Get.toNamed('/delete'),
},
{
'icon': Icons.lock,

View File

@ -8,6 +8,7 @@ import 'package:get/get.dart';
import 'package:image_cropper/image_cropper.dart';
import 'package:loopin/IM/controller/im_user_info_controller.dart';
import 'package:loopin/api/common_api.dart';
import 'package:loopin/components/my_toast.dart';
import 'package:loopin/components/network_or_asset_image.dart';
import 'package:loopin/service/http.dart';
import 'package:loopin/styles/index.dart';
@ -68,7 +69,12 @@ class _UserInfoState extends State<UserInfo> {
///
Future<void> wechatLogin() async {
final isNeed = userInfoController.customInfo['openId'];
if (isNeed == null || isNeed == '') {
await Wxsdk.login();
} else {
MyToast().tip(title: '您已授权无需重复操作');
}
}
///
@ -239,7 +245,7 @@ class _UserInfoState extends State<UserInfo> {
if (sizeInMB > 200) {
MyDialog.toast('图片大小不能超过200MB', icon: const Icon(Icons.check_circle), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200)));
} else {
print("视频合法,大小:$sizeInMB MB");
print("图片合法,大小:$sizeInMB MB");
//upload(file)url地址
final istance = MyDialog.loading('上传中');
final res = await Http.upload(CommonApi.uploadFile, filePath: file.path);
@ -371,10 +377,6 @@ class _UserInfoState extends State<UserInfo> {
final imageUrl = userInfoController.customInfo['coverBg'];
return GestureDetector(
onTap: () => pickCover(context),
// child: Image(
// image: (imageUrl != null && imageUrl.isNotEmpty) ? NetworkImage(imageUrl) : const AssetImage('assets/images/pic2.jpg') as ImageProvider,
// fit: BoxFit.cover,
// ),
child: NetworkOrAssetImage(
imageUrl: imageUrl,
placeholderAsset: 'assets/images/bk.jpg',

View File

@ -4,14 +4,14 @@ import 'package:get/get.dart';
import 'package:get/get_rx/src/rx_typedefs/rx_typedefs.dart';
import 'package:loopin/IM/controller/chat_controller.dart';
import 'package:loopin/IM/im_service.dart';
import 'package:loopin/service/http.dart';
import 'package:loopin/components/custom_sticky_header.dart';
import 'package:loopin/api/common_api.dart';
import 'package:loopin/api/video_api.dart';
import 'package:loopin/utils/index.dart';
import 'package:loopin/components/custom_sticky_header.dart';
import 'package:loopin/components/network_or_asset_image.dart';
import 'package:loopin/components/only_down_scroll_physics.dart';
import 'package:loopin/service/http.dart';
import 'package:loopin/styles/index.dart';
import 'package:loopin/utils/index.dart';
import 'package:nested_scroll_view_plus/nested_scroll_view_plus.dart';
import 'package:shirne_dialog/shirne_dialog.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart';
@ -82,7 +82,7 @@ class MyPageState extends State<Vloger> with SingleTickerProviderStateMixin {
void initState() {
super.initState();
args = Get.arguments ?? {};
print('argsssssssssssssssssssssss${args}');
print('argsssssssssssssssssssssss$args');
itemsParams = PageParams();
favoriteParams = PageParams();
selfInfo();
@ -128,15 +128,14 @@ class MyPageState extends State<Vloger> with SingleTickerProviderStateMixin {
//
void getUserLikesCount() async {
try {
final resData = await Http.get(CommonApi.accountInfo+'?memberId=${args['memberId']}');
print('aaaaaaaaaaaaaaaaaaa${resData}');
if(resData != null && resData['code'] == 200){
vlogLikeCount = resData['data']['vlogLikeCount']??0;
final resData = await Http.get('${CommonApi.accountInfo}?memberId=${args['memberId']}');
print('aaaaaaaaaaaaaaaaaaa$resData');
if (resData != null && resData['code'] == 200) {
vlogLikeCount = resData['data']['vlogLikeCount'] ?? 0;
}
} catch (e) {
} catch (e) {}
}
}
void loadData([int? tabIndex]) async {
final index = tabIndex ?? currentTabIndex.value;
if (index == 0) {
@ -394,7 +393,7 @@ class MyPageState extends State<Vloger> with SingleTickerProviderStateMixin {
width: double.infinity,
height: double.infinity,
fit: BoxFit.cover,
// placeholderAsset: 'assets/images/video_placeholder.png',
placeholderAsset: 'assets/images/bk.jpg',
),
//
Positioned(
@ -421,7 +420,8 @@ class MyPageState extends State<Vloger> with SingleTickerProviderStateMixin {
bottom: 8,
child: Row(
children: [
const Icon(Icons.favorite,
const Icon(
Icons.favorite,
color: Colors.white,
size: 16,
),
@ -457,9 +457,7 @@ class MyPageState extends State<Vloger> with SingleTickerProviderStateMixin {
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 20.0),
child: Center(
child: params.hasMore
? const CircularProgressIndicator()
: const Text('没有更多数据了'),
child: params.hasMore ? const CircularProgressIndicator() : const Text('没有更多数据了'),
),
),
),
@ -566,7 +564,11 @@ class MyPageState extends State<Vloger> with SingleTickerProviderStateMixin {
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Column(children: [Text('${Utils.graceNumber(vlogLikeCount)}', style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold)), SizedBox(height: 3.0), Text('获赞')]),
Column(children: [
Text(Utils.graceNumber(vlogLikeCount), style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold)),
SizedBox(height: 3.0),
Text('获赞')
]),
Column(children: [
Text('${followInfo.value.followingCount}', style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold)),
SizedBox(height: 3.0),

View File

@ -56,7 +56,7 @@ class _UploadVideoPageState extends State<UploadVideoPage> {
Future<void> pickVideo() async {
descFocusNode.unfocus();
final hasPer = await Permissions.requestVideoPermission();
final hasPer = await Permissions.requestPhotoPermission();
if (!hasPer) {
Permissions.showPermissionDialog('相册');

View File

@ -13,16 +13,17 @@ import 'package:loopin/pages/chat/notify/noFriend.dart';
import 'package:loopin/pages/chat/notify/system.dart';
import 'package:loopin/pages/groupChat/groupList.dart';
import 'package:loopin/pages/groupChat/index.dart';
import 'package:loopin/pages/my/all_function.dart';
import 'package:loopin/pages/my/delete.dart';
import 'package:loopin/pages/my/des.dart';
import 'package:loopin/pages/my/fans.dart';
import 'package:loopin/pages/my/flowing.dart';
import 'package:loopin/pages/my/merchant/income.dart';
import 'package:loopin/pages/my/mutual_followers.dart';
import 'package:loopin/pages/my/nick_name.dart';
import 'package:loopin/pages/my/setting.dart';
import 'package:loopin/pages/my/user_info.dart';
import 'package:loopin/pages/my/vloger.dart';
import 'package:loopin/pages/my/all_function.dart';
import 'package:loopin/pages/my/merchant/income.dart';
import 'package:loopin/pages/order/my_order.dart';
import 'package:loopin/pages/order/seller_order.dart';
import 'package:loopin/pages/search/index.dart';
@ -36,9 +37,9 @@ import '../pages/auth/login.dart';
//
import '../pages/goods/detail.dart';
import '../pages/order/detail.dart';
import '../pages/order/seller_detail.dart';
//
import '../pages/order/index.dart';
import '../pages/order/seller_detail.dart';
//
import '../utils/common.dart';
@ -68,9 +69,11 @@ final Map<String, Widget> routes = {
'/about': const Setting(),
'/des': const Des(),
'/nickName': const NickName(),
'/delete': const Delete(),
//
'/noFriend': const Nofriend(),
'/newFocus': const Newfoucs(),
'/newFoucs': const Newfoucs(),
'/system': const System(),
'/interaction': const Interaction(),
//

View File

@ -2,6 +2,9 @@ import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import 'package:loopin/IM/im_service.dart';
import 'package:loopin/controller/video_module_controller.dart';
import 'package:loopin/utils/common.dart';
class HttpConfig {
static final Dio dio = Dio(BaseOptions(
@ -9,10 +12,10 @@ class HttpConfig {
// baseUrl: 'http://111.62.22.190:8080',
// baseUrl: 'http://cjh.wuzhongjie.com.cn',
// baseUrl: 'http://82.156.121.2:8880',
baseUrl: 'https://www.wuzhongjie.com.cn/prod-api',
// baseUrl: 'https://www.wuzhongjie.com.cn/prod-api',
// baseUrl: 'http://192.168.1.65:8880',
// baseUrl: 'http://192.168.1.22:8080',
baseUrl: 'http://192.168.1.22:8080',
// connectTimeout: Duration(seconds: 30),
// receiveTimeout: Duration(seconds: 30),
@ -23,6 +26,24 @@ class HttpConfig {
static final box = GetStorage();
// 退
static void handleLogout() async {
Get.offAllNamed(
'/login',
predicate: (route) {
return route.settings.name == '/';
},
);
final loginRes = await ImService.instance.logout();
if (loginRes.success) {
//
Common.logout();
//
final videoController = Get.find<VideoModuleController>();
videoController.init();
}
}
static void init() {
dio.interceptors.add(
InterceptorsWrapper(
@ -46,6 +67,26 @@ class HttpConfig {
// logger.e(response.requestOptions.data);
final data = response.data;
if (data is Map<String, dynamic>) {
// token失效退
if (data['code'] == 20006) {
handleLogout();
Get.snackbar(
'登录状态已失效',
'您当前登录状态已失效,请重新登录',
duration: Duration(seconds: 5),
backgroundColor: Colors.red.withAlpha(230),
colorText: Colors.white,
icon: const Icon(Icons.error_outline, color: Colors.white),
);
return handler.reject(
DioException(
requestOptions: response.requestOptions,
error: data['msg'],
response: response,
),
);
}
//
if (data['code'] != 200) {
Get.snackbar(
'错误码${data['code']}',

View File

@ -1,5 +1,8 @@
import 'dart:io';
import 'package:audioplayers/audioplayers.dart';
import 'package:loopin/IM/im_core.dart';
import 'package:loopin/components/my_toast.dart';
class AudioPlayerService {
static final AudioPlayerService _instance = AudioPlayerService._internal();
@ -11,10 +14,19 @@ class AudioPlayerService {
///
Future<void> playLocal(String filePath) async {
try {
final file = File(filePath);
if (!file.existsSync()) {
logger.e('本地音频文件不存在:$filePath');
MyToast().tip(title: '音频文件不存在');
return;
}
await _audioPlayer.setSourceDeviceFile(filePath);
await _audioPlayer.resume();
//
await _audioPlayer.onPlayerComplete.first;
} catch (e) {
logger.e('播放本地音频失败: $e');
MyToast().tip(title: '音频文件不存在');
}
}
@ -23,8 +35,11 @@ class AudioPlayerService {
try {
await _audioPlayer.setSourceUrl(url);
await _audioPlayer.resume();
//
await _audioPlayer.onPlayerComplete.first;
} catch (e) {
logger.e('播放网络音频失败: $e');
MyToast().tip(title: '音频文件不存在');
}
}

View File

@ -234,4 +234,13 @@ class Utils {
}
return defaultValue;
}
// MERCHANT(2, "商家"),
// AGENT(3, "代理"),
// PLATFORM(4, "平台"),
// REFERENCE(5, "团长")
// int按string去拼接
static bool hasRole(int roleValue, int targetRole) {
return roleValue.toString().contains(targetRole.toString());
}
}

View File

@ -30,7 +30,7 @@ class NotificationBanner {
Get.snackbar(
'',
'',
duration: const Duration(minutes: 1),
duration: const Duration(seconds: 5),
margin: const EdgeInsets.all(12),
backgroundColor: FStyle.primaryColor.withAlpha(220),
titleText: Row(

View File

@ -10,13 +10,15 @@ class VoiceService {
VoiceService._internal();
final AudioRecorder _recorder = AudioRecorder();
String? _voiceFilePath;
DateTime? _startTime;
///
Future<bool> startRecording() async {
if (await _recorder.hasPermission()) {
final dir = await getTemporaryDirectory(); //
// final dir = await getTemporaryDirectory(); //
final dir = await getApplicationDocumentsDirectory(); // ios不行
final filePath = '${dir.path}/${DateTime.now().millisecondsSinceEpoch}.m4a';
_voiceFilePath = filePath;
_startTime = DateTime.now();

View File

@ -5,6 +5,7 @@ import 'package:get/get.dart';
import 'package:loopin/IM/controller/im_user_info_controller.dart';
import 'package:loopin/IM/im_service.dart';
import 'package:loopin/api/common_api.dart';
import 'package:loopin/pages/my/merchant/balance/controller.dart';
import 'package:loopin/service/http.dart';
class Wxsdk {
@ -48,10 +49,28 @@ class Wxsdk {
info.customInfo.refresh();
logger.w(serverRes['data']['openId']);
}
//
} else {
logger.w('微信授权失败: ${res.errStr}-类型:${res.state}');
}
}
//
if (res is WeChatPaymentResponse) {
logger.e(res);
if (res.isSuccessful) {
logger.i("微信支付成功");
//
final ctl = Get.find<BalanceController>();
//
ctl.getData(reset: true);
} else {
if (res.errCode == -2) {
logger.w("用户取消支付");
} else {
logger.e("微信支付失败: code=${res.errCode}, msg=${res.errStr}");
}
}
}
//
if (res is WeChatShareResponse) {
logger.w(res.isSuccessful);
@ -141,4 +160,46 @@ class Wxsdk {
);
Fluwx().open(target: miniProgram);
}
///
/// [appId] appId
/// [partnerId]
/// [prepayId] ID
/// [packageValue] Sign=WXPay
/// [nonceStr]
/// [timestamp] int
/// [sign]
/// [signType] MD5
/// [extData]
static Future<void> payWithWx({
required String appId,
required String partnerId,
required String prepayId,
required String packageValue,
required String nonceStr,
required int timestamp,
required String sign,
String? signType,
String? extData,
}) async {
try {
final payParams = Payment(
appId: appId,
partnerId: partnerId,
prepayId: prepayId,
packageValue: packageValue,
nonceStr: nonceStr,
timestamp: timestamp,
sign: sign,
signType: signType,
extData: extData,
);
logger.e(payParams.arguments);
final result = await fluwx.pay(which: payParams);
logger.i("调用微信支付结果: $result");
} catch (e) {
logger.e("调用微信支付失败: $e");
}
}
}

View File

@ -829,6 +829,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.3.0"
lottie:
dependency: "direct main"
description:
name: lottie
sha256: c5fa04a80a620066c15cf19cc44773e19e9b38e989ff23ea32e5903ef1015950
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.3.1"
lpinyin:
dependency: transitive
description:

View File

@ -99,6 +99,7 @@ dependencies:
cached_network_image: ^3.4.1
image_cropper: ^9.1.0
pretty_qr_code: ^3.5.0
lottie: ^3.3.1
dev_dependencies:
flutter_launcher_icons: ^0.13.1 # 使用最新版本
@ -140,6 +141,7 @@ flutter:
- assets/images/emotion/face05/
#update
- assets/images/update/rocket.png
- assets/animation/