This commit is contained in:
abu 2025-09-17 15:32:18 +08:00
parent cbe0dca250
commit 3b01d77393
32 changed files with 1921 additions and 855 deletions

View File

@ -7,6 +7,9 @@
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
analyzer:
errors:
non_const_call_to_literal_constructor: ignore
include: package:flutter_lints/flutter.yaml
linter:

View File

@ -24,6 +24,11 @@ PODS:
- Flutter
- FlutterMacOS
- HydraAsync (2.0.6)
- image_cropper (0.0.4):
- Flutter
- TOCropViewController (~> 2.7.4)
- image_gallery_saver_plus (0.0.1):
- Flutter
- image_picker_ios (0.0.1):
- Flutter
- install_plugin (2.0.0):
@ -68,6 +73,9 @@ PODS:
- SDWebImageWebPCoder (0.14.6):
- libwebp (~> 1.0)
- SDWebImage/Core (~> 5.17)
- sqflite_darwin (0.0.4):
- Flutter
- FlutterMacOS
- tencent_cloud_chat_push (8.6.7019):
- Flutter
- TIMPush (= 8.6.7019)
@ -78,6 +86,7 @@ PODS:
- TXIMSDK_Plus_iOS_XCFramework (~> 8.6.7019)
- TIMPush (8.6.7019):
- TXIMSDK_Plus_iOS_XCFramework (>= 8.6.7019)
- TOCropViewController (2.7.4)
- TXIMSDK_Plus_iOS_XCFramework (8.6.7019)
- url_launcher_ios (0.0.1):
- Flutter
@ -102,6 +111,8 @@ DEPENDENCIES:
- flutter_upgrader (from `.symlinks/plugins/flutter_upgrader/ios`)
- fluwx (from `.symlinks/plugins/fluwx/ios`)
- geolocator_apple (from `.symlinks/plugins/geolocator_apple/darwin`)
- image_cropper (from `.symlinks/plugins/image_cropper/ios`)
- image_gallery_saver_plus (from `.symlinks/plugins/image_gallery_saver_plus/ios`)
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
- install_plugin (from `.symlinks/plugins/install_plugin/ios`)
- media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/ios`)
@ -112,6 +123,7 @@ DEPENDENCIES:
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- photo_manager (from `.symlinks/plugins/photo_manager/ios`)
- record_ios (from `.symlinks/plugins/record_ios/ios`)
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
- tencent_cloud_chat_push (from `.symlinks/plugins/tencent_cloud_chat_push/ios`)
- tencent_cloud_chat_sdk (from `.symlinks/plugins/tencent_cloud_chat_sdk/ios`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
@ -128,6 +140,7 @@ SPEC REPOS:
- SDWebImage
- SDWebImageWebPCoder
- TIMPush
- TOCropViewController
- TXIMSDK_Plus_iOS_XCFramework
- WechatOpenSDK-XCFramework
@ -148,6 +161,10 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/fluwx/ios"
geolocator_apple:
:path: ".symlinks/plugins/geolocator_apple/darwin"
image_cropper:
:path: ".symlinks/plugins/image_cropper/ios"
image_gallery_saver_plus:
:path: ".symlinks/plugins/image_gallery_saver_plus/ios"
image_picker_ios:
:path: ".symlinks/plugins/image_picker_ios/ios"
install_plugin:
@ -168,6 +185,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/photo_manager/ios"
record_ios:
:path: ".symlinks/plugins/record_ios/ios"
sqflite_darwin:
:path: ".symlinks/plugins/sqflite_darwin/darwin"
tencent_cloud_chat_push:
:path: ".symlinks/plugins/tencent_cloud_chat_push/ios"
tencent_cloud_chat_sdk:
@ -193,6 +212,8 @@ SPEC CHECKSUMS:
fluwx: 6bf9c5a3a99ad31b0de137dd92370a0d10a60f4b
geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e
HydraAsync: 8d589bd725b0224f899afafc9a396327405f8063
image_cropper: c4326ea50132b1e1564499e5d32a84f01fb03537
image_gallery_saver_plus: e597bf65a7846979417a3eae0763b71b6dfec6c3
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
install_plugin: e17e38d6f504857748a3ec1299d8a2bbeeeea854
libwebp: 1786c9f4ff8a279e4dac1e8f385004d5fc253009
@ -207,9 +228,11 @@ SPEC CHECKSUMS:
record_ios: fee1c924aa4879b882ebca2b4bce6011bcfc3d8b
SDWebImage: 73c6079366fea25fa4bb9640d5fb58f0893facd8
SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
tencent_cloud_chat_push: f87ae58098c2062b06e81f39fc53afc528395916
tencent_cloud_chat_sdk: 0a406f1854a65aad2f853494c02a2e084a027ab2
TIMPush: d0dfe96355ee413a7cacb2576f8aaa66f6073ab2
TOCropViewController: 80b8985ad794298fb69d3341de183f33d1853654
TXIMSDK_Plus_iOS_XCFramework: cb54f7de6e30e1368c6831c6eff31c25393bbb98
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
video_player_avfoundation: 2cef49524dd1f16c5300b9cd6efd9611ce03639b

View File

@ -27,7 +27,7 @@ class IMMessage {
bool isExcludedFromUnreadCount = false,
}) async {
// toUserID groupID
if ((toUserID == null && groupID == null) || (toUserID != null && groupID != null)) {
if ((toUserID == null && groupID == null) || (toUserID == '' && groupID == '')) {
return ImResult(
success: false,
code: -1,
@ -41,7 +41,7 @@ class IMMessage {
V2TimValueCallback<V2TimMessage> sendRes;
// final controller = Get.find<ChatDetailController>();
//
if (toUserID != null) {
if (toUserID != null && toUserID.isNotEmpty) {
final myInfo = Get.find<ImUserInfoController>();
logger.w('启用默认title${myInfo.nickname.value}');
OfflinePushInfo offlinePushInfo = OfflinePushInfo(

View File

@ -0,0 +1,129 @@
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:get/get.dart';
import 'package:image_gallery_saver_plus/image_gallery_saver_plus.dart';
import 'package:loopin/IM/controller/im_user_info_controller.dart';
import 'package:loopin/IM/im_friend_listeners.dart';
import 'package:loopin/components/my_toast.dart';
import 'package:loopin/styles/index.dart';
import 'package:loopin/utils/scan_code_type.dart';
import 'package:pretty_qr_code/pretty_qr_code.dart';
class MyQrcode extends StatelessWidget {
MyQrcode({super.key});
final controller = Get.find<ImUserInfoController>();
// GlobalKey Widget
final GlobalKey _qrKey = GlobalKey();
/// Widget Uint8List
Future<Uint8List?> capturePng() async {
try {
final boundary = _qrKey.currentContext?.findRenderObject() as RenderRepaintBoundary?;
if (boundary == null) return null;
final image = await boundary.toImage(pixelRatio: ui.window.devicePixelRatio);
final byteData = await image.toByteData(format: ui.ImageByteFormat.png);
return byteData?.buffer.asUint8List();
} catch (e) {
logger.e("截图失败: $e");
}
return null;
}
///
Future<void> saveQrToGallery() async {
logger.w('长安了');
final pngBytes = await capturePng();
if (pngBytes == null) return;
final result = await ImageGallerySaverPlus.saveImage(
pngBytes,
quality: 100,
name: "my_qr_${DateTime.now().millisecondsSinceEpoch}",
);
logger.w("保存结果: $result");
MyToast().tip(
title: '图片已保存',
position: 'top',
type: 'success',
);
}
@override
Widget build(BuildContext context) {
final userID = controller.userID.value;
final faceUrl = controller.faceUrl.value;
ImageProvider face;
if (faceUrl.isEmpty) {
face = AssetImage('assets/images/logo/logo.png');
} else {
face = CachedNetworkImageProvider(faceUrl);
}
return GestureDetector(
onLongPress: () {
showModalBottomSheet(
context: Get.context!,
backgroundColor: Colors.white,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
builder: (context) {
return SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
leading: const Icon(Icons.save, color: Colors.black),
title: const Text('保存到相册', style: TextStyle(color: Colors.black)),
onTap: () async {
await saveQrToGallery();
Get.back();
},
),
],
),
);
},
);
},
child: Center(
child: Container(
alignment: Alignment.topCenter,
decoration: BoxDecoration(
color: Colors.transparent,
),
child: RepaintBoundary(
key: _qrKey,
child: PrettyQrView.data(
errorCorrectLevel: QrErrorCorrectLevel.H, //
data: '${QrTypeCode.hym}$userID',
decoration: PrettyQrDecoration(
background: Colors.transparent,
shape: const PrettyQrShape.custom(
PrettyQrSmoothSymbol(color: FStyle.primaryColor),
finderPattern: PrettyQrSmoothSymbol(color: FStyle.primaryColor),
alignmentPatterns: PrettyQrSmoothSymbol(color: FStyle.primaryColor),
),
image: PrettyQrDecorationImage(
image: face,
scale: 0.3, //
opacity: 1,
padding: EdgeInsets.all(8.0), //
),
quietZone: const PrettyQrQuietZone.modules(2),
),
),
),
),
),
);
}
}

View File

@ -1,3 +1,4 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
class NetworkOrAssetImage extends StatelessWidget {
@ -21,31 +22,23 @@ class NetworkOrAssetImage extends StatelessWidget {
final isNetwork = imageUrl != null && imageUrl!.isNotEmpty && (imageUrl!.startsWith('http://') || imageUrl!.startsWith('https://'));
if (isNetwork) {
return Image.network(
imageUrl!,
return CachedNetworkImage(
imageUrl: imageUrl!,
width: width,
height: height,
fit: fit,
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) {
return child;
}
//
return Image.asset(
placeholder: (context, url) => Image.asset(
placeholderAsset.isEmpty ? 'assets/images/avatar/default.png' : placeholderAsset,
width: width,
height: height,
fit: fit,
);
},
errorBuilder: (context, error, stackTrace) {
return Image.asset(
),
errorWidget: (context, url, error) => Image.asset(
placeholderAsset,
width: width,
height: height,
fit: fit,
);
},
),
);
} else {
return Image.asset(

View File

@ -39,14 +39,14 @@ class ShopIndexController extends GetxController with GetSingleTickerProviderSta
RxInt currentTabIndex = 0.obs;
/// Tab
void initTabs({required TickerProvider vsync}) async {
void initTabs() async {
// ScrollController
tabs.forEach((_, state) => state.scrollController.dispose());
tabs.clear();
tabList.clear();
// TabController
tabController?.removeListener(_tabListener);
tabController?.dispose();
// tabController?.removeListener(_tabListener);
// tabController?.dispose();
// tab
final res = await Http.post(ShopApi.shopCategory, data: {
@ -70,9 +70,10 @@ class ShopIndexController extends GetxController with GetSingleTickerProviderSta
}
// TabController
tabController = TabController(length: tabList.length, vsync: vsync);
// tabController = TabController(length: tabList.length, vsync: vsync);
// tabController = changeController;
tabController?.addListener(_tabListener);
// tabController?.addListener(_tabListener);
// tab
if (tabList.isNotEmpty) {
loadSwiperData();
@ -80,6 +81,21 @@ class ShopIndexController extends GetxController with GetSingleTickerProviderSta
}
}
///
void addTabListener(TabController changedTab) {
// TabController listener
_removeTabListener(changedTab);
// listener
changedTab.addListener(_tabListener);
// tabController
tabController = changedTab;
}
///
void _removeTabListener(TabController changedTab) {
changedTab.removeListener(_tabListener);
}
/// Tab
void _tabListener() {
if (!tabController!.indexIsChanging) {

View File

@ -99,7 +99,7 @@ class App extends StatelessWidget {
],
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFFFF9900)),
// colorScheme: ColorScheme.fromSeed(seedColor: FStyle.primaryColor),
useMaterial3: true,
),
home: const Layout(),

View File

@ -1688,32 +1688,34 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
color: FStyle.primaryColor,
elevation: 8,
items: [
PopupMenuItem<String>(
value: 'remark',
child: Row(
children: [
Icon(Icons.edit, color: Colors.white, size: 18),
SizedBox(width: 8),
Text(
'设置备注',
style: TextStyle(color: Colors.white),
),
],
),
),
PopupMenuItem<String>(
value: 'not',
child: Row(
children: [
Icon(Icons.do_not_disturb_on, color: Colors.white, size: 18),
SizedBox(width: 8),
Text(
'设为免打扰',
style: TextStyle(color: Colors.white),
),
],
),
),
// PopupMenuItem<String>(
// value: 'remark',
// child: Row(
// children: [
// Icon(Icons.edit, color: Colors.white, size: 18),
// SizedBox(width: 8),
// Text(
// '设置备注',
// style: TextStyle(color: Colors.white),
// ),
// ],
// ),
// ),
// PopupMenuItem<String>(
// value: 'not',
// child: Row(
// children: [
// Icon(Icons.do_not_disturb_on, color: Colors.white, size: 18),
// SizedBox(width: 8),
// Text(
// '设为免打扰',
// style: TextStyle(color: Colors.white),
// ),
// ],
// ),
// ),
PopupMenuItem<String>(
value: 'report',
child: Row(
@ -1740,40 +1742,42 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
],
),
),
PopupMenuItem<String>(
value: 'foucs',
child: Row(
children: [
Icon(Icons.person_remove_alt_1, color: Colors.white, size: 18),
SizedBox(width: 8),
Text(
'取消关注',
style: TextStyle(color: Colors.white),
),
],
),
),
// PopupMenuItem<String>(
// value: 'foucs',
// child: Row(
// children: [
// Icon(Icons.person_remove_alt_1, color: Colors.white, size: 18),
// SizedBox(width: 8),
// Text(
// '取消关注',
// style: TextStyle(color: Colors.white),
// ),
// ],
// ),
// ),
],
);
if (selected != null) {
switch (selected) {
case 'remark':
print('点击了备注');
setRemark();
break;
case 'not':
print('点击了免打扰');
break;
// case 'remark':
// print('点击了备注');
// setRemark();
// break;
// case 'not':
// print('点击了免打扰');
// break;
case 'report':
print('点击了举报');
break;
case 'block':
print('点击了拉黑');
break;
case 'foucs':
print('点击了取关');
break;
// case 'foucs':
// print('点击了取关');
// break;
}
}
},

View File

@ -1056,6 +1056,7 @@ class _ChatGroupState extends State<ChatGroup> with SingleTickerProviderStateMix
res = await IMMessage().sendMessage(
msg: message,
groupID: arguments.groupID,
groupName: arguments.showName,
);
if (res.success && res.data != null) {

View File

@ -2,20 +2,22 @@
library;
import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.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/api/shop_api.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/api/shop_api.dart';
import 'package:loopin/service/http.dart';
import 'package:loopin/models/conversation_view_model.dart';
import 'package:loopin/pages/chat/menu/add_friend.dart';
import 'package:loopin/service/http.dart';
import 'package:loopin/utils/index.dart';
import 'package:loopin/utils/scan_code_type.dart'; //
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';
@ -55,6 +57,7 @@ class ChatPageState extends State<ChatPage> {
await Future.delayed(Duration(seconds: 1));
setState(() {});
}
//
void handleScanResult(String code) {
print('扫码结果11111111111111111111111$code');
@ -86,36 +89,36 @@ class ChatPageState extends State<ChatPage> {
}
//
void _handleVerificationCode (String value) async {
void _handleVerificationCode(String value) async {
print('处理核销码: $value');
//
Get.toNamed('/sellerOrder/detail', arguments: {'writeOffCodeId':value});
Get.toNamed('/sellerOrder/detail', arguments: {'writeOffCodeId': value});
}
//
void _handleFriendCode(String value) {
print('处理好友码: $value');
// 仿
Get.toNamed('/vloger', arguments: {'memberId':value});
Get.toNamed('/vloger', arguments: {'memberId': value});
}
// 广
void _handlePromotionCode(String value)async {
void _handlePromotionCode(String value) async {
try {
print('处理推广码111: $value');
final res = await Http.post('${ShopApi.bindSpreadCodeId}', data: {
"socialCode": value
});
if(res != null && res['code'] == 200){
final res = await Http.post(ShopApi.bindSpreadCodeId, data: {"socialCode": value});
if (res != null && res['code'] == 200) {
MyDialog.toast('推广码绑定失败', icon: const Icon(Icons.check_circle), style: ToastStyle(backgroundColor: Colors.green.withAlpha(200)));
Get.toNamed('/vloger', arguments: {'memberId':value});
Get.toNamed('/vloger', arguments: {'memberId': value});
}
} catch (e) {
MyDialog.toast('推广码绑定失败');
}
}
//
void showContextMenu(BuildContext context, ConversationViewModel item) {
return;
bool isLeft = posDX > MediaQuery.of(context).size.width / 2 ? false : true;
bool isTop = posDY > MediaQuery.of(context).size.height / 2 ? false : true;
@ -183,7 +186,7 @@ class ChatPageState extends State<ChatPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[50],
// backgroundColor: Colors.grey[50],
appBar: AppBar(
forceMaterialTransparency: true,
title: Row(
@ -299,6 +302,7 @@ class ChatPageState extends State<ChatPage> {
break;
case 'friend':
logger.w('点击了添加朋友');
Get.to(() => AddFriend());
break;
case 'scan':
logger.w('点击了扫一扫');
@ -408,6 +412,7 @@ class ChatPageState extends State<ChatPage> {
// logger.w(chatList[index].conversation.conversationGroupList);
// 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)
? true
: false; //
@ -416,7 +421,33 @@ class ChatPageState extends State<ChatPage> {
chatList[index].isCustomAdmin != null && (chatList[index].isCustomAdmin?.isNotEmpty ?? false) && chatList[index].isCustomAdmin != '0';
final placeholderAsset = chatList[index].conversation.type == 2 ? 'assets/images/group.png' : 'assets/images/avatar/default.png';
// logger.e(chatList[index].isCustomAdmin);
return Ink(
return Slidable(
key: ValueKey(chatList[index].conversation.conversationID),
endActionPane: ActionPane(
motion: const DrawerMotion(), // StretchMotion ScrollMotion DrawerMotion
children: [
SlidableAction(
onPressed: (_) {
//
},
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
icon: Icons.delete,
label: '已读',
),
if (!(isAdmin || isNoFriend))
SlidableAction(
onPressed: (_) {
//
},
backgroundColor: FStyle.primaryColor,
foregroundColor: Colors.white,
icon: quiet ? Icons.notifications_off : Icons.notifications,
label: quiet ? '取消' : '免打扰',
),
],
),
child: Ink(
// color: chatList[index]['topMost'] == null ? Colors.white : Colors.grey[100], //
child: InkWell(
key: ValueKey(chatList[index].conversation.conversationID),
@ -454,7 +485,9 @@ class ChatPageState extends State<ChatPage> {
const SizedBox(height: 2.0),
//
Text(
chatList[index].conversation.lastMessage != null ? parseMessageSummary(chatList[index].conversation.lastMessage!) : '',
chatList[index].conversation.lastMessage != null
? parseMessageSummary(chatList[index].conversation.lastMessage!)
: '',
style: const TextStyle(color: Colors.grey, fontSize: 13.0),
overflow: TextOverflow.ellipsis,
maxLines: 1,
@ -475,7 +508,8 @@ class ChatPageState extends State<ChatPage> {
// DateTime.fromMillisecondsSinceEpoch(
// (chatList[index].conversation.lastMessage!.timestamp ?? 0) * 1000,
// ).toLocal().toString().substring(0, 16), //
Utils.formatTime(chatList[index].conversation.lastMessage!.timestamp ?? DateTime.now().millisecondsSinceEpoch ~/ 1000),
Utils.formatTime(
chatList[index].conversation.lastMessage!.timestamp ?? DateTime.now().millisecondsSinceEpoch ~/ 1000),
style: const TextStyle(color: Colors.grey, fontSize: 12.0),
),
),
@ -515,7 +549,7 @@ class ChatPageState extends State<ChatPage> {
if (conversationTypeFromString(chatList[index].isCustomAdmin) != null) {
//
logger.e(chatList[index].isCustomAdmin);
Get.toNamed('/${chatList[index].isCustomAdmin}', arguments: chatList[index].conversation.lastMessage);
Get.toNamed('/${chatList[index].isCustomAdmin}', arguments: chatList[index].conversation);
} else if (chatList[index].conversation.conversationGroupList!.contains(ConversationType.noFriend.name)) {
//
logger.e(chatList[index].conversation.conversationGroupList);
@ -536,6 +570,7 @@ class ChatPageState extends State<ChatPage> {
showContextMenu(context, chatList[index]);
},
),
),
);
},
);

View File

@ -0,0 +1,177 @@
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/components/my_qrcode.dart';
import 'package:loopin/components/my_toast.dart';
import 'package:loopin/components/scan_util.dart';
import 'package:loopin/utils/scan_code_type.dart';
class AddFriend extends StatefulWidget {
const AddFriend({super.key});
@override
State<AddFriend> createState() => _AddFriendState();
}
class _AddFriendState extends State<AddFriend> {
final TextEditingController txtcontroller = TextEditingController();
final FocusNode focusNode = FocusNode();
@override
void dispose() {
txtcontroller.dispose();
super.dispose();
}
void handleScanResult(String code) {
logger.w('扫码结果11111111111111111111111$code');
// 使
final scanType = ScanCodeType.fromCode(code);
if (scanType != null) {
final value = code.substring(scanType.prefix.length + 1); //
_processScanCode(scanType, value);
} else {
//
Get.snackbar('未知的二维码类型', '无法识别此二维码: $code');
}
}
//
void _processScanCode(ScanCodeType type, String value) {
switch (type) {
case ScanCodeType.verification:
_handleVerificationCode(value);
break;
case ScanCodeType.friend:
_handleFriendCode(value);
break;
case ScanCodeType.promotion:
_handlePromotionCode(value);
break;
}
}
//
void _handleVerificationCode(String value) async {
logger.w('处理核销码: $value');
//
Get.toNamed('/sellerOrder/detail', arguments: {'writeOffCodeId': value});
}
//
void _handleFriendCode(String value) {
logger.w('处理好友码: $value');
// 仿
final myID = Get.find<ImUserInfoController>().userID.value;
if (myID != value) {
Get.toNamed('/vloger', arguments: {'memberId': value});
} else {
MyToast().tip(title: '这是给别人扫的,扫自己没用', position: 'top');
}
}
// 广
void _handlePromotionCode(String value) {
logger.w('处理推广码: $value');
Get.toNamed('/vloger', arguments: {'memberId': value});
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
focusNode.unfocus();
},
child: Scaffold(
appBar: AppBar(
title: const Text('添加好友'),
titleTextStyle: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.black,
),
centerTitle: true,
),
body: ListView(
children: [
//
Padding(
padding: const EdgeInsets.all(12.0),
child: TextField(
controller: txtcontroller,
focusNode: focusNode,
decoration: InputDecoration(
hintText: '请输入昵称',
prefixIcon: const Icon(Icons.search),
suffixIcon: TextButton(
child: Text('搜索'),
onPressed: () {
//
final value = txtcontroller.text.trim();
if (value.isNotEmpty) {
//
focusNode.unfocus();
}
},
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(30),
borderSide: BorderSide.none,
),
filled: true,
fillColor: Colors.grey[200],
),
onSubmitted: (value) {
if (value.trim().isNotEmpty) {
focusNode.unfocus();
//
}
},
),
),
const Divider(),
//
_buildEntry(
icon: Icons.qr_code_scanner,
title: "扫一扫",
onTap: () {
// Get.snackbar("扫一扫", "跳转到扫码页面");
ScanUtil.openScanner(onResult: handleScanResult);
},
),
// _buildEntry(
// icon: Icons.contacts,
// title: "手机联系人",
// onTap: () {},
// ),
// _buildEntry(
// icon: Icons.people_alt_outlined,
// title: "好友推荐",
// onTap: () {},
// ),
SizedBox(height: 20),
MyQrcode(),
],
),
),
);
}
Widget _buildEntry({
required IconData icon,
required String title,
required VoidCallback onTap,
}) {
return ListTile(
leading: Icon(icon, color: Colors.blue),
title: Text(title),
trailing: const Icon(Icons.arrow_forward_ios, size: 16),
onTap: onTap,
);
}
}

View File

@ -1,4 +1,4 @@
///
///
library;
import 'dart:convert';

View File

@ -1,28 +1,26 @@
///
///
library;
import 'package:easy_refresh/easy_refresh.dart';
import 'dart:convert';
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/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;
UserWithFollow({
required this.userInfo,
this.followType = 0,
});
}
// interactionComment, //->
// interactionAt, //->@
// interactionLike, //->
// interactionReply, //->
class Newfoucs extends StatefulWidget {
const Newfoucs({super.key});
@ -34,78 +32,71 @@ 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();
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);
}
}
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;
});
});
}
});
}
//
Future<void> getData() async {
/// 0
/// 1
/// 2
/// 3
final res = await ImService.instance.getMyFollowingList(
nextCursor: page,
@override
void dispose() {
super.dispose();
chatController.dispose();
}
//
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;
}
}
}
}
}
final isFinished = res.data!.nextCursor == null || res.data!.nextCursor!.isEmpty;
if (isFinished) {
setState(() {
msgList.addAll(res.data!);
logger.e(msgList);
if (res.data!.isEmpty) {
hasMore = false;
});
//
page = '';
} else {
page = res.data!.nextCursor ?? '';
}
logger.i('获取数据成功:$userInfoList');
setState(() {
dataList.addAll(wrappedList);
});
logger.i('聊天数据加载成功');
} 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 +108,28 @@ class NewfoucsState extends State<Newfoucs> 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: [],
),
@ -134,91 +138,103 @@ class NewfoucsState extends State<Newfoucs> with SingleTickerProviderStateMixin
child: Column(
children: [
Expanded(
child: EasyRefresh.builder(
callLoadOverOffset: 20, //
callRefreshOverOffset: 20, //
header: ClassicHeader(
dragText: '下拉刷新',
armedText: '释放刷新',
readyText: '加载中...',
processingText: '加载中...',
processedText: '加载完成',
failedText: '加载失败,请重试',
messageText: '最后更新于 %T',
),
footer: ClassicFooter(
dragText: '加载更多',
armedText: '释放加载',
readyText: '加载中...',
processingText: '加载中...',
processedText: hasMore ? '加载完成' : '没有更多了~',
failedText: '加载失败,请重试',
messageText: '最后更新于 %T',
),
onRefresh: () async {
await handleRefresh();
},
onLoad: () async {
if (hasMore) {
await getData();
}
},
childBuilder: (context, physics) {
child: RefreshIndicator(
backgroundColor: Colors.white,
color: Color(0xFFFF5000),
displacement: 10.0,
onRefresh: handleRefresh,
child: Obx(() {
return ListView.builder(
physics: physics,
itemCount: dataList.length,
controller: chatController,
shrinkWrap: true,
physics: BouncingScrollPhysics(),
itemCount: msgList.length,
itemBuilder: (context, index) {
final item = dataList[index];
return Ink(
key: ValueKey(item.userInfo.userID),
child: Container(
//cloudCustomData
//----
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 = '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(
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 {
//
//
// 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.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,75 +242,34 @@ class NewfoucsState extends State<Newfoucs> 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: true,
//
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],
);
ctl.updateNoFriendMenu();
}
}
}
}
}
}
},
child: Text(
Utils.getTipText(item.followType),
style: const TextStyle(color: Colors.white, fontSize: 14),
),
const SizedBox(width: 5.0),
//
Visibility(
visible: !(msg.isRead ?? true),
child: FStyle.badge(0, isdot: true),
),
],
),
],
),
);
},
);
},
),
})),
),
],
),

View File

@ -0,0 +1,304 @@
///
library;
import 'package:easy_refresh/easy_refresh.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:loopin/IM/controller/chat_controller.dart';
import 'package:loopin/IM/im_service.dart';
import 'package:loopin/behavior/custom_scroll_behavior.dart';
import 'package:loopin/components/network_or_asset_image.dart';
import 'package:loopin/styles/index.dart';
import 'package:loopin/utils/index.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_follow_type_check_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_user_full_info.dart';
class UserWithFollow {
final V2TimUserFullInfo userInfo;
int followType;
UserWithFollow({
required this.userInfo,
this.followType = 0,
});
}
class Order extends StatefulWidget {
const Order({super.key});
@override
State<Order> createState() => OrderState();
}
class OrderState extends State<Order> with SingleTickerProviderStateMixin {
bool isLoading = false; //
bool hasMore = true; //
String page = '';
List<UserWithFollow> dataList = <UserWithFollow>[];
///-------------------
@override
void initState() {
super.initState();
getData();
}
//
Future<void> getData() async {
/// 0
/// 1
/// 2
/// 3
final res = await ImService.instance.getMyFollowingList(
nextCursor: page,
);
if (res.success && res.data != null) {
logger.i('获取成功:${res.data!.nextCursor}');
final userInfoList = res.data!.userFullInfoList ?? [];
//
List<UserWithFollow> wrappedList = userInfoList.map((u) {
return UserWithFollow(userInfo: u);
}).toList();
// id
final userIDList = userInfoList.map((item) => item.userID).whereType<String>().toList();
if (userIDList.isNotEmpty) {
final shiRes = await ImService.instance.checkFollowType(userIDList: userIDList);
if (shiRes.success && shiRes.data != null) {
final shipResData = shiRes.data!;
for (final uwf in wrappedList) {
final userID = uwf.userInfo.userID;
if (userID != null) {
//
final match = shipResData.firstWhere(
(e) => e.userID == userID,
orElse: () => V2TimFollowTypeCheckResult(userID: ''),
);
if (match.userID?.isNotEmpty ?? false) {
uwf.followType = match.followType ?? 0;
}
}
}
}
}
final isFinished = res.data!.nextCursor == null || res.data!.nextCursor!.isEmpty;
if (isFinished) {
setState(() {
hasMore = false;
});
//
page = '';
} else {
page = res.data!.nextCursor ?? '';
}
logger.i('获取数据成功:$userInfoList');
setState(() {
dataList.addAll(wrappedList);
});
} else {
logger.e('获取数据失败:${res.desc}');
}
}
//
Future<void> handleRefresh() async {
dataList.clear();
page = '';
getData();
setState(() {});
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[50],
appBar: AppBar(
centerTitle: true,
forceMaterialTransparency: true,
bottom: PreferredSize(
preferredSize: const Size.fromHeight(1.0),
child: Container(
color: Colors.grey[300],
height: 1.0,
),
),
title: const Text(
'关注',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
actions: [],
),
body: ScrollConfiguration(
behavior: CustomScrollBehavior().copyWith(scrollbars: false),
child: Column(
children: [
Expanded(
child: EasyRefresh.builder(
callLoadOverOffset: 20, //
callRefreshOverOffset: 20, //
header: ClassicHeader(
dragText: '下拉刷新',
armedText: '释放刷新',
readyText: '加载中...',
processingText: '加载中...',
processedText: '加载完成',
failedText: '加载失败,请重试',
messageText: '最后更新于 %T',
),
footer: ClassicFooter(
dragText: '加载更多',
armedText: '释放加载',
readyText: '加载中...',
processingText: '加载中...',
processedText: hasMore ? '加载完成' : '没有更多了~',
failedText: '加载失败,请重试',
messageText: '最后更新于 %T',
),
onRefresh: () async {
await handleRefresh();
},
onLoad: () async {
if (hasMore) {
await getData();
}
},
childBuilder: (context, physics) {
return ListView.builder(
physics: physics,
itemCount: dataList.length,
itemBuilder: (context, index) {
final item = dataList[index];
return Ink(
key: ValueKey(item.userInfo.userID),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 10.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
// + +
Expanded(
child: InkWell(
onTap: () {
Get.toNamed(
'/vloger',
arguments: item.userInfo.userID,
);
},
child: Row(
children: [
ClipOval(
child: NetworkOrAssetImage(
imageUrl: item.userInfo.faceUrl,
width: 50,
height: 50,
),
),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
item.userInfo.nickName?.isNotEmpty == true ? item.userInfo.nickName! : '未知昵称',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.normal,
),
),
if (item.userInfo.selfSignature?.isNotEmpty ?? false) ...[
const SizedBox(height: 2.0),
Text(
item.userInfo.selfSignature!,
style: const TextStyle(
color: Colors.grey,
fontSize: 13.0,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
],
),
),
],
),
),
),
SizedBox(width: 10),
//
TextButton(
style: TextButton.styleFrom(
backgroundColor: item.followType == 3 ? Colors.grey : FStyle.primaryColor,
minimumSize: const Size(70, 32),
padding: const EdgeInsets.symmetric(horizontal: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4),
),
),
onPressed: () async {
final ctl = Get.find<ChatController>();
final checkRes = await ImService.instance.checkFollowType(userIDList: [item.userInfo.userID!]);
int realFollowType = 0;
if (checkRes.success && checkRes.data != null) {
realFollowType = checkRes.data!.first.followType ?? 0;
if ([1, 3].contains(realFollowType)) {
//
final unRes = await ImService.instance.unfollowUser(userIDList: [item.userInfo.userID!]);
if (unRes.success) {
setState(() {
item.followType = 2;
});
ctl.mergeNoFriend(conversationID: 'c2c_${item.userInfo.userID!}');
}
} else {
//
final res = await ImService.instance.followUser(userIDList: [item.userInfo.userID!]);
if (res.success) {
setState(() {
item.followType = realFollowType == 0
? 1
: realFollowType == 2
? 3
: 0;
});
final chatRes = await ImService.instance.followUser(userIDList: [item.userInfo.userID!]);
if (chatRes.success) {
final res = await ImService.instance.getConversation(conversationID: 'c2c_${item.userInfo.userID}');
if (res.success) {
V2TimConversation conversation = res.data;
if (conversation.conversationGroupList?.isNotEmpty ?? false) {
await ImService.instance.deleteConversationsFromGroup(
groupName: conversation.conversationGroupList!.first!,
conversationIDList: [conversation.conversationID],
);
ctl.updateNoFriendMenu();
}
}
}
}
}
}
},
child: Text(
Utils.getTipText(item.followType),
style: const TextStyle(color: Colors.white, fontSize: 14),
),
),
],
),
),
);
},
);
},
),
),
],
),
),
);
}
}

View File

@ -44,7 +44,7 @@ class GroupdetailState extends State<Groupdetail> {
void pickFaceUrl(BuildContext context) async {
final hasPer = await Permissions.requestPhotoPermission();
if (!hasPer) {
Permissions.showPermissionDialog();
Permissions.showPermissionDialog('相册');
return;
}
final pickedAssets = await AssetPicker.pickAssets(

View File

@ -117,7 +117,8 @@ class _IndexPageState extends State<IndexPage> with SingleTickerProviderStateMix
void initState() {
super.initState();
controller = Get.find<ShopIndexController>();
controller.initTabs(vsync: this);
// controller.initTabs(vsync: this);
controller.initTabs();
}
@override
@ -129,7 +130,7 @@ class _IndexPageState extends State<IndexPage> with SingleTickerProviderStateMix
_buildTopSection(),
//
Expanded(
child: controller.tabController == null
child: controller.tabList.isEmpty
? Center(child: CircularProgressIndicator())
: Obx(
() {
@ -153,7 +154,9 @@ class _IndexPageState extends State<IndexPage> with SingleTickerProviderStateMix
}).toList();
return DynamicTabBarWidget(
onTabControllerUpdated: (tabController) {
controller.tabController = tabController;
// controller.tabController = tabController;
// controller.initTabs(changeController: tabController, vsync: this);
controller.addTabListener(tabController);
//
if (tabController.index != 0) {
tabController.animateTo(0);
@ -205,7 +208,7 @@ class _IndexPageState extends State<IndexPage> with SingleTickerProviderStateMix
//
Widget _buildTopSection() {
if (controller.tabController == null) {
if (controller.swiperData.isEmpty) {
return SizedBox();
}
return Column(

View File

@ -116,7 +116,7 @@ class _IndexPageState extends State<IndexPage> with SingleTickerProviderStateMix
@override
void initState() {
super.initState();
controller.initTabs(vsync: this);
// controller.initTabs(vsync: this);
}
@override

View File

@ -1,13 +1,17 @@
import 'dart:io';
import 'package:bottom_picker/bottom_picker.dart';
import 'package:city_pickers/city_pickers.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
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/network_or_asset_image.dart';
import 'package:loopin/service/http.dart';
import 'package:loopin/styles/index.dart';
import 'package:loopin/utils/image_utils.dart';
import 'package:loopin/utils/index.dart';
import 'package:loopin/utils/permissions.dart';
import 'package:loopin/utils/wxsdk.dart';
@ -208,7 +212,7 @@ class _UserInfoState extends State<UserInfo> {
void pickCover(BuildContext context) async {
final hasPer = await Permissions.requestPhotoPermission();
if (!hasPer) {
Permissions.showPermissionDialog();
Permissions.showPermissionDialog('相册');
return;
}
final pickedAssets = await AssetPicker.pickAssets(
@ -252,7 +256,7 @@ class _UserInfoState extends State<UserInfo> {
void pickFaceUrl(BuildContext context) async {
final hasPer = await Permissions.requestPhotoPermission();
if (!hasPer) {
Permissions.showPermissionDialog();
Permissions.showPermissionDialog('相册');
return;
}
final pickedAssets = await AssetPicker.pickAssets(
@ -281,8 +285,41 @@ class _UserInfoState extends State<UserInfo> {
} else {
print("图片合法,大小:$sizeInMB MB");
//upload(file)url地址
final croppedFile = await ImageCropper().cropImage(
sourcePath: file.path,
maxWidth: 1024,
maxHeight: null,
compressFormat: ImageCompressFormat.png,
compressQuality: 100, // png
uiSettings: [
AndroidUiSettings(
toolbarTitle: '裁剪',
toolbarColor: Colors.black,
toolbarWidgetColor: Colors.white,
hideBottomControls: false,
lockAspectRatio: false,
cropStyle: CropStyle.circle,
),
IOSUiSettings(
title: '裁剪',
doneButtonTitle: '确认',
cropStyle: CropStyle.circle,
aspectRatioPickerButtonHidden: true, //
resetAspectRatioEnabled: false, //
cancelButtonTitle: '返回',
aspectRatioLockEnabled: false, //
rotateButtonsHidden: false,
resetButtonHidden: false,
),
],
);
//---
if (croppedFile != null) {
final istance = MyDialog.loading('上传中');
final res = await Http.upload(CommonApi.uploadFile, filePath: file.path);
//
final uploadImg = await ImageUtils.toCircleImageFile(imageFile: File(croppedFile.path));
final res = await Http.upload(CommonApi.uploadFile, filePath: uploadImg);
userInfoController.faceUrl.value = res['data']['url'];
userInfoController.updateFaceUrl();
userInfoController.customInfo.refresh();
@ -291,6 +328,7 @@ class _UserInfoState extends State<UserInfo> {
}
}
}
}
@override
Widget build(BuildContext context) {

View File

@ -59,7 +59,7 @@ class _UploadVideoPageState extends State<UploadVideoPage> {
final hasPer = await Permissions.requestVideoPermission();
if (!hasPer) {
Permissions.showPermissionDialog();
Permissions.showPermissionDialog('相册');
return;
}
@ -108,7 +108,7 @@ class _UploadVideoPageState extends State<UploadVideoPage> {
final hasPer = await Permissions.requestPhotoPermission();
if (!hasPer) {
Permissions.showPermissionDialog();
Permissions.showPermissionDialog('相册');
return;
}

View File

@ -516,7 +516,15 @@ class _VideoDetailPageState extends State<VideoDetailPage> {
data: makeJson,
);
if (res.success) {
final sendRes = await IMMessage().sendMessage(msg: res.data!.messageInfo!, toUserID: userId, cloudCustomData: SummaryType.shareVideo);
// final sendRes = await IMMessage().sendMessage(msg: res.data!.messageInfo!, toUserID: userId, cloudCustomData: SummaryType.shareVideo);
final isGroup = conv.groupID != null && conv.groupID!.isNotEmpty;
final sendRes = await IMMessage().sendMessage(
msg: res.data!.messageInfo!,
groupID: isGroup ? conv.groupID : '',
toUserID: isGroup ? '' : conv.userID,
cloudCustomData: SummaryType.shareVideo,
groupName: isGroup ? conv.showName : '',
);
if (sendRes.success) {
MyToast().tip(
title: '分享成功',

View File

@ -15,12 +15,12 @@ import 'package:loopin/IM/im_service.dart' hide logger;
import 'package:loopin/api/video_api.dart';
import 'package:loopin/components/my_toast.dart';
import 'package:loopin/components/network_or_asset_image.dart';
import 'package:loopin/models/share_type.dart';
import 'package:loopin/models/summary_type.dart';
import 'package:loopin/service/http.dart';
import 'package:loopin/utils/download_video.dart';
import 'package:loopin/utils/permissions.dart';
import 'package:loopin/utils/wxsdk.dart';
import 'package:loopin/models/share_type.dart';
import 'package:media_kit/media_kit.dart';
import 'package:media_kit_video/media_kit_video.dart';
import 'package:media_kit_video/media_kit_video_controls/src/controls/extensions/duration.dart';
@ -997,7 +997,15 @@ class _AttentionModuleState extends State<AttentionModule> {
data: makeJson,
);
if (res.success) {
final sendRes = await IMMessage().sendMessage(msg: res.data!.messageInfo!, toUserID: userId, cloudCustomData: SummaryType.shareVideo);
// final sendRes = await IMMessage().sendMessage(msg: res.data!.messageInfo!, toUserID: userId, cloudCustomData: SummaryType.shareVideo);
final isGroup = conv.groupID != null && conv.groupID!.isNotEmpty;
final sendRes = await IMMessage().sendMessage(
msg: res.data!.messageInfo!,
groupID: isGroup ? conv.groupID : '',
toUserID: isGroup ? '' : conv.userID,
cloudCustomData: SummaryType.shareVideo,
groupName: isGroup ? conv.showName : '',
);
if (sendRes.success) {
MyToast().tip(
title: '分享成功',
@ -1137,7 +1145,7 @@ class _AttentionModuleState extends State<AttentionModule> {
player.pause();
// Vloger
final vloggerId = videoList[videoModuleController.videoPlayIndex.value]['memberId'];
final result = await Get.toNamed('/vloger', arguments: {'memberId':vloggerId});
final result = await Get.toNamed('/vloger', arguments: {'memberId': vloggerId});
if (result != null) {
//
print('返回的数据: ${result['followStatus']}');
@ -1311,8 +1319,9 @@ class _AttentionModuleState extends State<AttentionModule> {
Text(
text,
maxLines: videoList[videoModuleController.videoPlayIndex.value]['expanded'] ? null : 3,
overflow:
videoList[videoModuleController.videoPlayIndex.value]['expanded'] ? TextOverflow.visible : TextOverflow.ellipsis,
overflow: videoList[videoModuleController.videoPlayIndex.value]['expanded']
? TextOverflow.visible
: TextOverflow.ellipsis,
style: const TextStyle(color: Colors.white, fontSize: 14.0),
),
if (isOverflow)

View File

@ -1002,7 +1002,15 @@ class _FriendModuleState extends State<FriendModule> {
data: makeJson,
);
if (res.success) {
final sendRes = await IMMessage().sendMessage(msg: res.data!.messageInfo!, toUserID: userId, cloudCustomData: SummaryType.shareVideo);
// final sendRes = await IMMessage().sendMessage(msg: res.data!.messageInfo!, toUserID: userId, cloudCustomData: SummaryType.shareVideo);
final isGroup = conv.groupID != null && conv.groupID!.isNotEmpty;
final sendRes = await IMMessage().sendMessage(
msg: res.data!.messageInfo!,
groupID: isGroup ? conv.groupID : '',
toUserID: isGroup ? '' : conv.userID,
cloudCustomData: SummaryType.shareVideo,
groupName: isGroup ? conv.showName : '',
);
if (sendRes.success) {
MyToast().tip(
title: '分享成功',
@ -1142,7 +1150,7 @@ class _FriendModuleState extends State<FriendModule> {
player.pause();
// Vloger
final vloggerId = videoList[videoModuleController.videoPlayIndex.value]['memberId'];
final result = await Get.toNamed('/vloger', arguments: {'memberId':vloggerId});
final result = await Get.toNamed('/vloger', arguments: {'memberId': vloggerId});
if (result != null) {
//
print('返回的数据: ${result['followStatus']}');

View File

@ -981,7 +981,6 @@ class _RecommendModuleState extends State<RecommendModule> {
void handlCoverClick(V2TimConversation conv) async {
// VideoMsg,
final userId = conv.userID;
final currentVideo = videoList[videoModuleController.videoPlayIndex.value];
logger.w(currentVideo);
final img = (currentVideo['cover'] != null && currentVideo['cover'].toString().isNotEmpty) ? currentVideo['cover'] : currentVideo['firstFrameImg'];
@ -1001,7 +1000,16 @@ class _RecommendModuleState extends State<RecommendModule> {
data: makeJson,
);
if (res.success) {
final sendRes = await IMMessage().sendMessage(msg: res.data!.messageInfo!, toUserID: userId, cloudCustomData: SummaryType.shareVideo);
final isGroup = conv.groupID != null && conv.groupID!.isNotEmpty;
logger.w(isGroup);
logger.w(conv.toJson());
final sendRes = await IMMessage().sendMessage(
msg: res.data!.messageInfo!,
groupID: isGroup ? conv.groupID : '',
toUserID: isGroup ? '' : conv.userID,
cloudCustomData: SummaryType.shareVideo,
groupName: isGroup ? conv.showName : '',
);
if (sendRes.success) {
MyToast().tip(
title: '分享成功',
@ -1127,7 +1135,7 @@ class _RecommendModuleState extends State<RecommendModule> {
player.pause();
// Vloger
final vloggerId = videoList[videoModuleController.videoPlayIndex.value]['memberId'];
final result = await Get.toNamed('/vloger', arguments: {'memberId':vloggerId});
final result = await Get.toNamed('/vloger', arguments: {'memberId': vloggerId});
if (result != null) {
//
print('返回的数据: ${result['followStatus']}');

View File

@ -8,6 +8,7 @@ import 'package:loopin/pages/chat/chat.dart';
import 'package:loopin/pages/chat/chat_group.dart';
import 'package:loopin/pages/chat/chat_no_friend.dart';
import 'package:loopin/pages/chat/notify/interaction.dart';
import 'package:loopin/pages/chat/notify/newFoucs.dart';
import 'package:loopin/pages/chat/notify/noFriend.dart';
import 'package:loopin/pages/chat/notify/system.dart';
import 'package:loopin/pages/groupChat/groupList.dart';
@ -69,6 +70,7 @@ final Map<String, Widget> routes = {
'/nickName': const NickName(),
//
'/noFriend': const Nofriend(),
'/newFocus': const Newfoucs(),
'/system': const System(),
'/interaction': const Interaction(),
//

View File

@ -32,11 +32,14 @@ class FStyle {
//
// static const backgroundColor = Color(0xFFEEEEEE);
static const backgroundColor = Colors.white;
static const backgroundColor = Color(0xFFFDF6F0);
static const primaryColor = Color(0xFFFF5000);
static const white = Colors.white;
static const c999 = Color(0xFF999999);
static const secondaryColor = Color(0xFFFFC18E);
static const inputBackground = Color(0xFFFFF2ED);
static const textPrimary = Color(0xFF2E2E2E);
static const textSecondary = Color(0xFF6F6F6F);
//
static mt(double v) => EdgeInsets.only(top: v);
static mb(double v) => EdgeInsets.only(bottom: v);

View File

@ -22,8 +22,9 @@ class UpgradeService {
});
if (!state.mounted) return;
// logger.i(res);
logger.w(res);
final result = res['data']['records'] as List;
if (result.isEmpty) return;
final data = result.first;
final currentVersion = info.buildNumber;
if (currentVersion != data['versionCode']) {

View File

@ -0,0 +1,72 @@
import 'dart:io';
import 'dart:math' as math;
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
class ImageUtils {
/// PNG
/// [imageFile] [imageData]
static Future<String> toCircleImageFile({
Uint8List? imageData,
File? imageFile,
}) async {
if (imageData == null && imageFile == null) {
throw ArgumentError('必须提供 imageData 或 imageFile');
}
//
final bytes = imageData ?? await imageFile!.readAsBytes();
// 1.
final codec = await ui.instantiateImageCodec(bytes);
final frame = await codec.getNextFrame();
final original = frame.image;
final width = original.width;
final height = original.height;
final size = math.min(width, height);
// 2.
final recorder = ui.PictureRecorder();
final canvas = Canvas(recorder);
final paint = Paint()..isAntiAlias = true;
// 3.
final center = Offset(size / 2, size / 2);
canvas.drawCircle(center, size / 2, paint..color = Colors.white);
// 4. srcIn
paint.blendMode = BlendMode.srcIn;
// 5.
final srcRect = Rect.fromLTWH(
(width - size) / 2,
(height - size) / 2,
size.toDouble(),
size.toDouble(),
);
final dstRect = Rect.fromLTWH(0, 0, size.toDouble(), size.toDouble());
canvas.drawImageRect(original, srcRect, dstRect, paint);
// 6. Image
final picture = recorder.endRecording();
final imgFinal = await picture.toImage(size, size);
// 7. PNG
final byteData = await imgFinal.toByteData(format: ui.ImageByteFormat.png);
final pngBytes = byteData!.buffer.asUint8List();
// 8.
final tempDir = await getTemporaryDirectory();
final file = File(
'${tempDir.path}/circle_${DateTime.now().millisecondsSinceEpoch}.png',
);
await file.writeAsBytes(pngBytes);
return file.path;
}
}

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:loopin/IM/im_service.dart';
import 'package:loopin/components/network_or_asset_image.dart';
import 'package:loopin/styles/index.dart';
import 'package:loopin/utils/parse_message_summary.dart';
import 'package:shirne_dialog/shirne_dialog.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_info.dart';
@ -27,21 +28,48 @@ class NotificationBanner {
}
final text = parseMessageSummary(msg);
Get.snackbar(
name,
text,
duration: const Duration(seconds: 3),
snackPosition: SnackPosition.TOP,
'',
'',
duration: const Duration(minutes: 1),
margin: const EdgeInsets.all(12),
backgroundColor: Get.theme.cardColor,
colorText: Get.theme.textTheme.bodyLarge?.color,
icon: ClipOval(
backgroundColor: FStyle.primaryColor.withAlpha(220),
titleText: Row(
children: [
//
ClipOval(
child: NetworkOrAssetImage(
imageUrl: avatar,
placeholderAsset: isGroup ? 'assets/images/group.png' : 'assets/images/avatar/default.png',
width: 50,
height: 50,
),
),
const SizedBox(width: 8),
//
Expanded(
child: Text(
name,
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 16,
),
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
),
],
),
messageText: Text(
text,
style: const TextStyle(
color: Colors.white,
fontSize: 14,
),
overflow: TextOverflow.ellipsis,
maxLines: 2,
),
onTap: (_) async {
//
Get.closeCurrentSnackbar();
String? conversationID;
if (msg.groupID != null && msg.groupID!.isNotEmpty) {
@ -52,17 +80,56 @@ class NotificationBanner {
final cRes = await ImService.instance.getConversation(conversationID: conversationID!);
if (cRes.success) {
if (msg.userID != null) {
//
Get.toNamed('/chat', arguments: cRes.data);
} else if (msg.groupID != null) {
Get.toNamed('/chatGroup', arguments: cRes.data);
}
} else {
//
MyDialog.toast(cRes.desc, icon: Icon(Icons.warning), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200)));
MyDialog.toast(
cRes.desc,
icon: const Icon(Icons.warning),
style: ToastStyle(backgroundColor: Colors.red.withAlpha(200)),
);
}
},
);
// Get.snackbar(
// name,
// text,
// duration: Duration(minutes: 1),
// margin: const EdgeInsets.all(12),
// backgroundColor: FStyle.primaryColor,
// colorText: Colors.white,
// icon: ClipOval(
// child: NetworkOrAssetImage(
// imageUrl: avatar,
// placeholderAsset: isGroup ? 'assets/images/group.png' : 'assets/images/avatar/default.png',
// ),
// ),
// onTap: (_) async {
// //
// Get.closeCurrentSnackbar();
// String? conversationID;
// if (msg.groupID != null && msg.groupID!.isNotEmpty) {
// conversationID = 'group_${msg.groupID}';
// } else if (msg.userID != null && msg.userID!.isNotEmpty) {
// conversationID = 'c2c_${msg.userID}';
// }
// final cRes = await ImService.instance.getConversation(conversationID: conversationID!);
// if (cRes.success) {
// if (msg.userID != null) {
// //
// Get.toNamed('/chat', arguments: cRes.data);
// } else if (msg.groupID != null) {
// Get.toNamed('/chatGroup', arguments: cRes.data);
// }
// } else {
// //
// MyDialog.toast(cRes.desc, icon: Icon(Icons.warning), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200)));
// }
// },
// );
}
///

View File

@ -25,19 +25,14 @@ class Permissions {
}
} else if (Platform.isIOS) {
final status = await Permission.photos.request();
return status.isGranted;
}
return status.isGranted || status.isLimited;
} else {
return false;
}
//
static Future<bool> requestCameraPermission() async {
return await _checkAndRequest(Permission.camera);
}
//
static Future<bool> requestPhotoPermission() async {
// return await _checkAndRequest(Permission.photos);
if (Platform.isAndroid) {
final deviceInfoPlugin = DeviceInfoPlugin();
final androidInfo = await deviceInfoPlugin.androidInfo;
@ -46,18 +41,28 @@ class Permissions {
if (sdkInt >= 33) {
// Android 13
final status = await Permission.photos.request();
return status.isGranted;
// return status.isGranted;
return handleStatus(status, isAndroid: true);
} else {
// Android 12
final status = await Permission.storage.request();
return status.isGranted;
// return status.isGranted;
return handleStatus(status, isAndroid: true);
}
} else if (Platform.isIOS) {
final status = await Permission.photos.request();
return status.isGranted;
}
logger.w('iOS photos = $status');
// return status.isGranted || status.isLimited;
return handleStatus(status, isAndroid: false);
} else {
return false;
}
}
//
static Future<bool> requestCameraPermission() async {
return await _checkAndRequest(Permission.camera);
}
//
static Future<bool> requestMicrophonePermission() async {
@ -81,7 +86,7 @@ class Permissions {
if (result.isPermanentlyDenied) {
//
showPermissionDialog();
return false;
} else {
//
Get.snackbar('权限请求失败', '无法访问,请授权对应权限后重试');
@ -90,10 +95,45 @@ class Permissions {
return false;
}
///
static bool handleStatus(PermissionStatus status, {required bool isAndroid}) {
logger.w("当前权限状态 = $status");
logger.e(status.isPermanentlyDenied);
if (status.isGranted) {
return true;
}
if (!isAndroid) {
// iOS
if (status.isLimited) {
logger.w("iOS 相册权限 = 仅限部分照片 (Limited)");
return true; // Limited
}
if (status.isPermanentlyDenied) {
// debug permanentlyDenied
// 访 true
logger.w("可能已授权,直接允许访问");
return true;
}
}
if (status.isDenied) {
logger.w("相册权限被拒绝(可再次请求)");
return false;
}
if (status.isPermanentlyDenied || status.isRestricted) {
logger.w("相册权限被永久拒绝,需要跳转到设置页");
return false;
}
return false;
}
//
static void showPermissionDialog() async {
static void showPermissionDialog(String name) async {
final confirmed = await ConfirmDialog.show(
title: '需要权限',
title: '需要$name权限',
content: '请前往系统设置中手动开启权限',
confirmText: '去设置',
);

View File

@ -1,4 +1,10 @@
//
class QrTypeCode {
static const String hxm = 'hxm-';
static const String hym = 'hym-';
static const String tgm = 'tgm-';
}
enum ScanCodeType {
verification('hxm'), //
friend('hym'), //

View File

@ -113,6 +113,30 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.2.1"
cached_network_image:
dependency: "direct main"
description:
name: cached_network_image
sha256: "7c1183e361e5c8b0a0f21a28401eecdbde252441106a9816400dd4c2b2424916"
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.4.1"
cached_network_image_platform_interface:
dependency: transitive
description:
name: cached_network_image_platform_interface
sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829"
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.1.1"
cached_network_image_web:
dependency: transitive
description:
name: cached_network_image_web
sha256: "980842f4e8e2535b8dbd3d5ca0b1f0ba66bf61d14cc3a17a9b4788a3685ba062"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.3.1"
card_swiper:
dependency: "direct main"
description:
@ -350,6 +374,14 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
flutter_cache_manager:
dependency: transitive
description:
name: flutter_cache_manager
sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386"
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.4.1"
flutter_form_builder:
dependency: "direct main"
description:
@ -451,6 +483,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.28"
flutter_slidable:
dependency: "direct main"
description:
name: flutter_slidable
sha256: e6bd17290cf0d011f9ed66c74d4159b8fe3b3050afedac0f11fab1ba8687e710
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.0.1"
flutter_staggered_grid_view:
dependency: "direct main"
description:
@ -605,6 +645,38 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.5.4"
image_cropper:
dependency: "direct main"
description:
name: image_cropper
sha256: "4e9c96c029eb5a23798da1b6af39787f964da6ffc78fd8447c140542a9f7c6fc"
url: "https://pub.flutter-io.cn"
source: hosted
version: "9.1.0"
image_cropper_for_web:
dependency: transitive
description:
name: image_cropper_for_web
sha256: fd81ebe36f636576094377aab32673c4e5d1609b32dec16fad98d2b71f1250a9
url: "https://pub.flutter-io.cn"
source: hosted
version: "6.1.0"
image_cropper_platform_interface:
dependency: transitive
description:
name: image_cropper_platform_interface
sha256: "6ca6b81769abff9a4dcc3bbd3d75f5dfa9de6b870ae9613c8cd237333a4283af"
url: "https://pub.flutter-io.cn"
source: hosted
version: "7.1.0"
image_gallery_saver_plus:
dependency: "direct main"
description:
name: image_gallery_saver_plus
sha256: "199b9e24f8d85e98f11e3d35571ab68ae50626ad40e2bb85c84383f69a6950ad"
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.0.1"
image_picker:
dependency: "direct main"
description:
@ -893,6 +965,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.0.0"
octo_image:
dependency: transitive
description:
name: octo_image
sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.0"
package_info_plus:
dependency: "direct main"
description:
@ -1085,6 +1165,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "6.0.2"
pretty_qr_code:
dependency: "direct main"
description:
name: pretty_qr_code
sha256: "2291db3f68d70a3dcd46c6bd599f30991ae4c02f27f36215fbb3f4865a609259"
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.5.0"
provider:
dependency: transitive
description:
@ -1173,6 +1261,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.6"
rxdart:
dependency: transitive
description:
name: rxdart
sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.28.0"
safe_local_storage:
dependency: transitive
description:
@ -1234,6 +1330,46 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "7.0.0"
sqflite:
dependency: transitive
description:
name: sqflite
sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.4.2"
sqflite_android:
dependency: transitive
description:
name: sqflite_android
sha256: "2b3070c5fa881839f8b402ee4a39c1b4d561704d4ebbbcfb808a119bc2a1701b"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.4.1"
sqflite_common:
dependency: transitive
description:
name: sqflite_common
sha256: "84731e8bfd8303a3389903e01fb2141b6e59b5973cacbb0929021df08dddbe8b"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.5.5"
sqflite_darwin:
dependency: transitive
description:
name: sqflite_darwin
sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.4.2"
sqflite_platform_interface:
dependency: transitive
description:
name: sqflite_platform_interface
sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.4.0"
stack_trace:
dependency: transitive
description:

View File

@ -67,7 +67,8 @@ dependencies:
flutter_upgrader: ^1.1.20 #更新
path_provider: ^2.1.2
permission_handler: ^12.0.0+1
permission_handler: ^12.0.0+1 #权限
image_gallery_saver_plus: ^4.0.1 #存储
install_plugin: ^2.1.0 # Android 安装 APK
flutter_native_splash: ^2.4.6 # 启动图
@ -94,6 +95,10 @@ dependencies:
image_picker: ^1.2.0 #相机
video_player: ^2.10.0 #视频处理
mime: ^2.0.0 #文件类型推断
flutter_slidable: ^4.0.1
cached_network_image: ^3.4.1
image_cropper: ^9.1.0
pretty_qr_code: ^3.5.0
dev_dependencies:
flutter_launcher_icons: ^0.13.1 # 使用最新版本