merge
This commit is contained in:
parent
cbe0dca250
commit
3b01d77393
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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(
|
||||
|
129
lib/components/my_qrcode.dart
Normal file
129
lib/components/my_qrcode.dart
Normal 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),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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(
|
||||
placeholderAsset.isEmpty ? 'assets/images/avatar/default.png' : placeholderAsset,
|
||||
width: width,
|
||||
height: height,
|
||||
fit: fit,
|
||||
);
|
||||
},
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
return Image.asset(
|
||||
placeholderAsset,
|
||||
width: width,
|
||||
height: height,
|
||||
fit: fit,
|
||||
);
|
||||
},
|
||||
placeholder: (context, url) => Image.asset(
|
||||
placeholderAsset.isEmpty ? 'assets/images/avatar/default.png' : placeholderAsset,
|
||||
width: width,
|
||||
height: height,
|
||||
fit: fit,
|
||||
),
|
||||
errorWidget: (context, url, error) => Image.asset(
|
||||
placeholderAsset,
|
||||
width: width,
|
||||
height: height,
|
||||
fit: fit,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Image.asset(
|
||||
|
@ -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) {
|
||||
|
@ -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(),
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -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) {
|
||||
|
@ -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,7 +57,8 @@ class ChatPageState extends State<ChatPage> {
|
||||
await Future.delayed(Duration(seconds: 1));
|
||||
setState(() {});
|
||||
}
|
||||
// 处理扫码结果
|
||||
|
||||
// 处理扫码结果
|
||||
void handleScanResult(String code) {
|
||||
print('扫码结果11111111111111111111111:$code');
|
||||
|
||||
@ -85,37 +88,37 @@ 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 {
|
||||
try {
|
||||
print('处理推广码111: $value');
|
||||
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});
|
||||
}
|
||||
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) {
|
||||
MyDialog.toast('推广码绑定失败', icon: const Icon(Icons.check_circle), style: ToastStyle(backgroundColor: Colors.green.withAlpha(200)));
|
||||
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,125 +421,155 @@ 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(
|
||||
// color: chatList[index]['topMost'] == null ? Colors.white : Colors.grey[100], //置顶颜色
|
||||
child: InkWell(
|
||||
key: ValueKey(chatList[index].conversation.conversationID),
|
||||
splashColor: Colors.grey[200],
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 10.0),
|
||||
child: Row(
|
||||
spacing: 10.0,
|
||||
children: <Widget>[
|
||||
// 头图
|
||||
ClipOval(
|
||||
child: NetworkOrAssetImage(
|
||||
imageUrl: chatList[index].faceUrl,
|
||||
width: 50,
|
||||
height: 50,
|
||||
placeholderAsset: placeholderAsset,
|
||||
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),
|
||||
splashColor: Colors.grey[200],
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 10.0),
|
||||
child: Row(
|
||||
spacing: 10.0,
|
||||
children: <Widget>[
|
||||
// 头图
|
||||
ClipOval(
|
||||
child: NetworkOrAssetImage(
|
||||
imageUrl: chatList[index].faceUrl,
|
||||
width: 50,
|
||||
height: 50,
|
||||
placeholderAsset: placeholderAsset,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// 消息
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
// 消息
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
// 昵称
|
||||
Text(
|
||||
chatList[index].conversation.showName ?? '未知',
|
||||
maxLines: 1,
|
||||
softWrap: false,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: (isAdmin || isNoFriend) ? 20 : 16,
|
||||
fontWeight: (isAdmin || isNoFriend) ? FontWeight.bold : FontWeight.normal),
|
||||
),
|
||||
const SizedBox(height: 2.0),
|
||||
// 消息内容
|
||||
Text(
|
||||
chatList[index].conversation.lastMessage != null
|
||||
? parseMessageSummary(chatList[index].conversation.lastMessage!)
|
||||
: '',
|
||||
style: const TextStyle(color: Colors.grey, fontSize: 13.0),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
softWrap: false,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// 右侧
|
||||
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: <Widget>[
|
||||
// 昵称
|
||||
Text(
|
||||
chatList[index].conversation.showName ?? '未知',
|
||||
maxLines: 1,
|
||||
softWrap: false,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: (isAdmin || isNoFriend) ? 20 : 16,
|
||||
fontWeight: (isAdmin || isNoFriend) ? FontWeight.bold : FontWeight.normal),
|
||||
Visibility(
|
||||
visible: !(isAdmin || isNoFriend),
|
||||
child: Text(
|
||||
// 转成日期字符串显示
|
||||
// DateTime.fromMillisecondsSinceEpoch(
|
||||
// (chatList[index].conversation.lastMessage!.timestamp ?? 0) * 1000,
|
||||
// ).toLocal().toString().substring(0, 16), // 简单截取年月日时分
|
||||
Utils.formatTime(
|
||||
chatList[index].conversation.lastMessage!.timestamp ?? DateTime.now().millisecondsSinceEpoch ~/ 1000),
|
||||
style: const TextStyle(color: Colors.grey, fontSize: 12.0),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2.0),
|
||||
// 消息内容
|
||||
Text(
|
||||
chatList[index].conversation.lastMessage != null ? parseMessageSummary(chatList[index].conversation.lastMessage!) : '',
|
||||
style: const TextStyle(color: Colors.grey, fontSize: 13.0),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
softWrap: false,
|
||||
const SizedBox(height: 5.0),
|
||||
// 数字角标
|
||||
// class ReceiveMsgOptType {
|
||||
// 在线正常接收消息,离线时会进行 APNs 推送
|
||||
// static const int kTIMRecvMsgOpt_Receive = 0;
|
||||
// 不会接收到消息,离线不会有推送通知
|
||||
// static const int kTIMRecvMsgOpt_Not_Receive = 1;
|
||||
// 在线正常接收消息,离线不会有推送通知
|
||||
// static const int kTIMRecvMsgOpt_Not_Notify = 2;
|
||||
// 在线接收消息,离线只接收 at 消息的推送
|
||||
// static const int kTIMRecvMsgOpt_Not_Notify_Except_At = 3;
|
||||
// 在线和离线都只接收@消息
|
||||
// static const int kTIMRecvMsgOpt_Not_Receive_Except_At = 4;
|
||||
// }
|
||||
// 现阶段只允许设置为0和3
|
||||
Visibility(
|
||||
visible: (chatList[index].conversation.unreadCount ?? 0) > 0,
|
||||
child: FStyle.badge(chatList[index].conversation.unreadCount ?? 0, color: quiet ? Colors.grey : Colors.red),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// 右侧
|
||||
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: <Widget>[
|
||||
Visibility(
|
||||
visible: !(isAdmin || isNoFriend),
|
||||
child: Text(
|
||||
// 转成日期字符串显示
|
||||
// DateTime.fromMillisecondsSinceEpoch(
|
||||
// (chatList[index].conversation.lastMessage!.timestamp ?? 0) * 1000,
|
||||
// ).toLocal().toString().substring(0, 16), // 简单截取年月日时分
|
||||
Utils.formatTime(chatList[index].conversation.lastMessage!.timestamp ?? DateTime.now().millisecondsSinceEpoch ~/ 1000),
|
||||
style: const TextStyle(color: Colors.grey, fontSize: 12.0),
|
||||
),
|
||||
Visibility(
|
||||
visible: (isAdmin || isNoFriend),
|
||||
child: const Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
color: Colors.blueGrey,
|
||||
size: 14.0,
|
||||
),
|
||||
const SizedBox(height: 5.0),
|
||||
// 数字角标
|
||||
// class ReceiveMsgOptType {
|
||||
// 在线正常接收消息,离线时会进行 APNs 推送
|
||||
// static const int kTIMRecvMsgOpt_Receive = 0;
|
||||
// 不会接收到消息,离线不会有推送通知
|
||||
// static const int kTIMRecvMsgOpt_Not_Receive = 1;
|
||||
// 在线正常接收消息,离线不会有推送通知
|
||||
// static const int kTIMRecvMsgOpt_Not_Notify = 2;
|
||||
// 在线接收消息,离线只接收 at 消息的推送
|
||||
// static const int kTIMRecvMsgOpt_Not_Notify_Except_At = 3;
|
||||
// 在线和离线都只接收@消息
|
||||
// static const int kTIMRecvMsgOpt_Not_Receive_Except_At = 4;
|
||||
// }
|
||||
// 现阶段只允许设置为0和3
|
||||
Visibility(
|
||||
visible: (chatList[index].conversation.unreadCount ?? 0) > 0,
|
||||
child: FStyle.badge(chatList[index].conversation.unreadCount ?? 0, color: quiet ? Colors.grey : Colors.red),
|
||||
),
|
||||
],
|
||||
),
|
||||
Visibility(
|
||||
visible: (isAdmin || isNoFriend),
|
||||
child: const Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
color: Colors.blueGrey,
|
||||
size: 14.0,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
if (conversationTypeFromString(chatList[index].isCustomAdmin) != null) {
|
||||
// 跳转对应的通知消息页
|
||||
logger.e(chatList[index].isCustomAdmin);
|
||||
Get.toNamed('/${chatList[index].isCustomAdmin}', arguments: chatList[index].conversation);
|
||||
} else if (chatList[index].conversation.conversationGroupList!.contains(ConversationType.noFriend.name)) {
|
||||
// 跳转陌生人消息页面
|
||||
logger.e(chatList[index].conversation.conversationGroupList);
|
||||
Get.toNamed('/noFriend');
|
||||
} else if (chatList[index].conversation.type == 2) {
|
||||
// 跳转群聊 type=0非法,1=单聊,2=群聊
|
||||
Get.toNamed('/chatGroup', arguments: chatList[index].conversation);
|
||||
} else {
|
||||
// 会话id查询会话详情
|
||||
Get.toNamed('/chat', arguments: chatList[index].conversation);
|
||||
}
|
||||
},
|
||||
onTapDown: (TapDownDetails details) {
|
||||
posDX = details.globalPosition.dx;
|
||||
posDY = details.globalPosition.dy;
|
||||
},
|
||||
onLongPress: () {
|
||||
showContextMenu(context, chatList[index]);
|
||||
},
|
||||
),
|
||||
onTap: () {
|
||||
if (conversationTypeFromString(chatList[index].isCustomAdmin) != null) {
|
||||
// 跳转对应的通知消息页
|
||||
logger.e(chatList[index].isCustomAdmin);
|
||||
Get.toNamed('/${chatList[index].isCustomAdmin}', arguments: chatList[index].conversation.lastMessage);
|
||||
} else if (chatList[index].conversation.conversationGroupList!.contains(ConversationType.noFriend.name)) {
|
||||
// 跳转陌生人消息页面
|
||||
logger.e(chatList[index].conversation.conversationGroupList);
|
||||
Get.toNamed('/noFriend');
|
||||
} else if (chatList[index].conversation.type == 2) {
|
||||
// 跳转群聊 type=0非法,1=单聊,2=群聊
|
||||
Get.toNamed('/chatGroup', arguments: chatList[index].conversation);
|
||||
} else {
|
||||
// 会话id查询会话详情
|
||||
Get.toNamed('/chat', arguments: chatList[index].conversation);
|
||||
}
|
||||
},
|
||||
onTapDown: (TapDownDetails details) {
|
||||
posDX = details.globalPosition.dx;
|
||||
posDY = details.globalPosition.dy;
|
||||
},
|
||||
onLongPress: () {
|
||||
showContextMenu(context, chatList[index]);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
|
177
lib/pages/chat/menu/add_friend.dart
Normal file
177
lib/pages/chat/menu/add_friend.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
/// 聊天首页模板
|
||||
/// 互动通知
|
||||
library;
|
||||
|
||||
import 'dart:convert';
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
msgList.addAll(res.data!);
|
||||
logger.e(msgList);
|
||||
if (res.data!.isEmpty) {
|
||||
hasMore = false;
|
||||
}
|
||||
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);
|
||||
});
|
||||
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) {
|
||||
return ListView.builder(
|
||||
physics: physics,
|
||||
itemCount: dataList.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = dataList[index];
|
||||
return Ink(
|
||||
key: ValueKey(item.userInfo.userID),
|
||||
child: Container(
|
||||
child: RefreshIndicator(
|
||||
backgroundColor: Colors.white,
|
||||
color: Color(0xFFFF5000),
|
||||
displacement: 10.0,
|
||||
onRefresh: handleRefresh,
|
||||
child: Obx(() {
|
||||
return ListView.builder(
|
||||
controller: chatController,
|
||||
shrinkWrap: true,
|
||||
physics: BouncingScrollPhysics(),
|
||||
itemCount: msgList.length,
|
||||
itemBuilder: (context, index) {
|
||||
//检测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>[
|
||||
// 左侧部分(头像 + 昵称 + 描述)
|
||||
// 头像
|
||||
InkWell(
|
||||
onTap: () async {
|
||||
// 点击头像转到对方主页
|
||||
// 先获取视频详情
|
||||
// 如果cloudCustomData是interactionComment,interactionAt,interactionReply,传参时带上评论id,
|
||||
//
|
||||
final res = await Http.get('${VideoApi.detail}/${item['vlogID']}');
|
||||
Get.toNamed('/vloger', arguments: res['data']);
|
||||
// Get.toNamed('/vloger');
|
||||
},
|
||||
child: ClipOval(
|
||||
child: NetworkOrAssetImage(
|
||||
imageUrl: item['faceUrl'],
|
||||
width: 50,
|
||||
height: 50,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// 消息
|
||||
Expanded(
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
Get.toNamed(
|
||||
'/vloger',
|
||||
arguments: item.userInfo.userID,
|
||||
);
|
||||
onTap: () async {
|
||||
// 点击头像转到对方主页
|
||||
// 先获取视频详情
|
||||
// 如果cloudCustomData是interactionComment,interactionAt,interactionReply,传参时带上评论id,
|
||||
//
|
||||
final res = await Http.get('${VideoApi.detail}/${item['vlogID']}');
|
||||
Get.toNamed('/vloger', arguments: res['data']);
|
||||
// Get.toNamed('/vloger');
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
ClipOval(
|
||||
child: NetworkOrAssetImage(
|
||||
imageUrl: item.userInfo.faceUrl,
|
||||
width: 50,
|
||||
height: 50,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
// 昵称
|
||||
Text(
|
||||
item['nickName'] ?? '未知',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
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,
|
||||
),
|
||||
],
|
||||
],
|
||||
const SizedBox(height: 2.0),
|
||||
// 描述内容
|
||||
Text(
|
||||
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),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
})),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -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),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
@ -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,12 +285,46 @@ class _UserInfoState extends State<UserInfo> {
|
||||
} else {
|
||||
print("图片合法,大小:$sizeInMB MB");
|
||||
//走upload(file)上传图片拿到url地址
|
||||
final istance = MyDialog.loading('上传中');
|
||||
final res = await Http.upload(CommonApi.uploadFile, filePath: file.path);
|
||||
userInfoController.faceUrl.value = res['data']['url'];
|
||||
userInfoController.updateFaceUrl();
|
||||
userInfoController.customInfo.refresh();
|
||||
istance.close();
|
||||
|
||||
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 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();
|
||||
istance.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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: '分享成功',
|
||||
|
@ -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: '分享成功',
|
||||
@ -1019,398 +1027,399 @@ class _AttentionModuleState extends State<AttentionModule> {
|
||||
color: Colors.black,
|
||||
child: Column(
|
||||
children: [
|
||||
// 添加暂无数据提示
|
||||
if (videoList.isEmpty && !isLoadingMore)
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: Text(
|
||||
'暂无数据',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 16.0,
|
||||
// 添加暂无数据提示
|
||||
if (videoList.isEmpty && !isLoadingMore)
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: Text(
|
||||
'暂无数据',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 16.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
else
|
||||
Expanded(
|
||||
child: Stack(
|
||||
children: [
|
||||
/// 垂直滚动模块
|
||||
PageView.builder(
|
||||
scrollBehavior: CustomScrollBehavior().copyWith(scrollbars: false),
|
||||
scrollDirection: Axis.vertical,
|
||||
controller: pageController,
|
||||
onPageChanged: (index) async {
|
||||
videoModuleController.updateVideoPlayIndex(index);
|
||||
setState(() {
|
||||
sliderValue = 0.0;
|
||||
sliderDraging = false;
|
||||
position = Duration.zero;
|
||||
duration = Duration.zero;
|
||||
});
|
||||
Expanded(
|
||||
child: Stack(
|
||||
children: [
|
||||
/// 垂直滚动模块
|
||||
PageView.builder(
|
||||
scrollBehavior: CustomScrollBehavior().copyWith(scrollbars: false),
|
||||
scrollDirection: Axis.vertical,
|
||||
controller: pageController,
|
||||
onPageChanged: (index) async {
|
||||
videoModuleController.updateVideoPlayIndex(index);
|
||||
setState(() {
|
||||
sliderValue = 0.0;
|
||||
sliderDraging = false;
|
||||
position = Duration.zero;
|
||||
duration = Duration.zero;
|
||||
});
|
||||
|
||||
player.stop();
|
||||
await player.open(Media(videoList[index]['url']));
|
||||
if (index == videoList.length - 2 && !isLoadingMore) {
|
||||
await fetchVideoList();
|
||||
}
|
||||
},
|
||||
itemCount: videoList.length,
|
||||
itemBuilder: (context, index) {
|
||||
final videoWidth = videoList[index]['width'] ?? 1;
|
||||
final videoHeight = videoList[index]['height'] ?? 1;
|
||||
final isHorizontal = videoWidth > videoHeight;
|
||||
final filteredList = chatController.chatList.where((item) => item.conversation.conversationGroupList?.isEmpty == true).toList();
|
||||
return Stack(
|
||||
children: [
|
||||
// 视频区域
|
||||
Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: GestureDetector(
|
||||
child: Stack(
|
||||
children: [
|
||||
Visibility(
|
||||
visible: videoModuleController.videoPlayIndex.value == index && position > Duration.zero,
|
||||
child: Video(
|
||||
controller: videoController,
|
||||
fit: isHorizontal ? BoxFit.contain : BoxFit.cover,
|
||||
controls: NoVideoControls,
|
||||
),
|
||||
),
|
||||
AnimatedOpacity(
|
||||
opacity: videoModuleController.videoPlayIndex.value == index && position > Duration(milliseconds: 100) ? 0.0 : 1.0,
|
||||
duration: Duration(milliseconds: 50),
|
||||
child: Image.network(
|
||||
videoList[index]['firstFrameImg'] ?? 'https://wuzhongjie.com.cn/download/logo.png',
|
||||
fit: isHorizontal ? BoxFit.contain : BoxFit.cover,
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
),
|
||||
),
|
||||
StreamBuilder(
|
||||
stream: player.stream.playing,
|
||||
builder: (context, playing) {
|
||||
return Visibility(
|
||||
visible: playing.data == false,
|
||||
child: Center(
|
||||
child: IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
onPressed: () {
|
||||
player.playOrPause();
|
||||
},
|
||||
icon: Icon(
|
||||
playing.data == true ? Icons.pause : Icons.play_arrow_rounded,
|
||||
color: Colors.white60,
|
||||
size: 80,
|
||||
),
|
||||
style: ButtonStyle(backgroundColor: WidgetStateProperty.all(Colors.black.withAlpha(15))),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
player.playOrPause();
|
||||
},
|
||||
),
|
||||
),
|
||||
// 右侧操作栏
|
||||
Positioned(
|
||||
bottom: 100.0,
|
||||
right: 6.0,
|
||||
child: Column(
|
||||
spacing: 15.0,
|
||||
children: [
|
||||
Stack(
|
||||
player.stop();
|
||||
await player.open(Media(videoList[index]['url']));
|
||||
if (index == videoList.length - 2 && !isLoadingMore) {
|
||||
await fetchVideoList();
|
||||
}
|
||||
},
|
||||
itemCount: videoList.length,
|
||||
itemBuilder: (context, index) {
|
||||
final videoWidth = videoList[index]['width'] ?? 1;
|
||||
final videoHeight = videoList[index]['height'] ?? 1;
|
||||
final isHorizontal = videoWidth > videoHeight;
|
||||
final filteredList = chatController.chatList.where((item) => item.conversation.conversationGroupList?.isEmpty == true).toList();
|
||||
return Stack(
|
||||
children: [
|
||||
// 视频区域
|
||||
Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: GestureDetector(
|
||||
child: Stack(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 55.0,
|
||||
width: 48.0,
|
||||
child: GestureDetector(
|
||||
onTap: () async {
|
||||
player.pause();
|
||||
// 跳转到 Vloger 页面并等待返回结果
|
||||
final vloggerId = videoList[videoModuleController.videoPlayIndex.value]['memberId'];
|
||||
final result = await Get.toNamed('/vloger', arguments: {'memberId':vloggerId});
|
||||
if (result != null) {
|
||||
// 处理返回的参数
|
||||
print('返回的数据: ${result['followStatus']}');
|
||||
player.play();
|
||||
videoList[index]['doIFollowVloger'] = result['followStatus'];
|
||||
}
|
||||
},
|
||||
child: UnconstrainedBox(
|
||||
alignment: Alignment.topCenter,
|
||||
child: Container(
|
||||
height: 48.0,
|
||||
width: 48.0,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.white, width: 2.0),
|
||||
borderRadius: BorderRadius.circular(100.0),
|
||||
),
|
||||
child: ClipOval(
|
||||
child: NetworkOrAssetImage(
|
||||
imageUrl: videoList[index]['commentUserFace'],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Visibility(
|
||||
visible: videoModuleController.videoPlayIndex.value == index && position > Duration.zero,
|
||||
child: Video(
|
||||
controller: videoController,
|
||||
fit: isHorizontal ? BoxFit.contain : BoxFit.cover,
|
||||
controls: NoVideoControls,
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 15.0,
|
||||
child: InkWell(
|
||||
child: Container(
|
||||
height: 18.0,
|
||||
width: 18.0,
|
||||
decoration: BoxDecoration(
|
||||
color: videoList[index]['doIFollowVloger'] ? Colors.white : Color(0xFFFF5000),
|
||||
borderRadius: BorderRadius.circular(100.0),
|
||||
),
|
||||
child: Icon(
|
||||
videoList[index]['doIFollowVloger'] ? Icons.check : Icons.add,
|
||||
color: videoList[index]['doIFollowVloger'] ? Color(0xFFFF5000) : Colors.white,
|
||||
size: 14.0,
|
||||
),
|
||||
),
|
||||
onTap: () async {
|
||||
final vlogerId = videoList[index]['memberId'];
|
||||
final doIFollowVloger = videoList[index]['doIFollowVloger'];
|
||||
// 未关注点击才去关注
|
||||
if (doIFollowVloger == false) {
|
||||
final res = await ImService.instance.followUser(userIDList: [vlogerId]);
|
||||
if (res.success) {
|
||||
setState(() {
|
||||
videoList[index]['doIFollowVloger'] = !videoList[index]['doIFollowVloger'];
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
AnimatedOpacity(
|
||||
opacity: videoModuleController.videoPlayIndex.value == index && position > Duration(milliseconds: 100) ? 0.0 : 1.0,
|
||||
duration: Duration(milliseconds: 50),
|
||||
child: Image.network(
|
||||
videoList[index]['firstFrameImg'] ?? 'https://wuzhongjie.com.cn/download/logo.png',
|
||||
fit: isHorizontal ? BoxFit.contain : BoxFit.cover,
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
),
|
||||
),
|
||||
StreamBuilder(
|
||||
stream: player.stream.playing,
|
||||
builder: (context, playing) {
|
||||
return Visibility(
|
||||
visible: playing.data == false,
|
||||
child: Center(
|
||||
child: IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
onPressed: () {
|
||||
player.playOrPause();
|
||||
},
|
||||
icon: Icon(
|
||||
playing.data == true ? Icons.pause : Icons.play_arrow_rounded,
|
||||
color: Colors.white60,
|
||||
size: 80,
|
||||
),
|
||||
style: ButtonStyle(backgroundColor: WidgetStateProperty.all(Colors.black.withAlpha(15))),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
GestureDetector(
|
||||
child: Column(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
'assets/images/svg/heart.svg',
|
||||
colorFilter: ColorFilter.mode(videoList[index]['doILikeThisVlog'] ? Color(0xFFFF5000) : Colors.white, BlendMode.srcIn),
|
||||
height: 40.0,
|
||||
width: 40.0,
|
||||
),
|
||||
Text(
|
||||
'${videoList[index]['likeCounts'] + (videoList[index]['doILikeThisVlog'] ? 1 : 0)}',
|
||||
style: TextStyle(color: Colors.white, fontSize: 12.0),
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
logger.d('点击了点赞按钮${videoList[index]['doILikeThisVlog']}');
|
||||
if (videoList[index]['doILikeThisVlog'] == true) {
|
||||
logger.d('点击了点赞按钮${videoList[index]['doILikeThisVlog']}');
|
||||
doUnLikeVideo(videoList[index]);
|
||||
} else {
|
||||
doLikeVideo(videoList[index]);
|
||||
}
|
||||
},
|
||||
),
|
||||
GestureDetector(
|
||||
child: Column(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
'assets/images/svg/reply.svg',
|
||||
colorFilter: ColorFilter.mode(Colors.white, BlendMode.srcIn),
|
||||
height: 40.0,
|
||||
width: 40.0,
|
||||
),
|
||||
Text(
|
||||
'${videoList[index]['commentsCounts']}',
|
||||
style: TextStyle(color: Colors.white, fontSize: 12.0),
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
handleComment(index);
|
||||
},
|
||||
),
|
||||
GestureDetector(
|
||||
child: Column(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
'assets/images/svg/share.svg',
|
||||
colorFilter: ColorFilter.mode(Colors.white, BlendMode.srcIn),
|
||||
height: 40.0,
|
||||
width: 40.0,
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
handleShare(index);
|
||||
},
|
||||
),
|
||||
GestureDetector(
|
||||
child: Column(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
'assets/images/svg/report.svg',
|
||||
colorFilter: ColorFilter.mode(Colors.white, BlendMode.srcIn),
|
||||
height: 40.0,
|
||||
width: 40.0,
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () async {
|
||||
player.pause();
|
||||
// 跳转到举报页面并等待返回结果
|
||||
final result = await Get.toNamed('/report', arguments: videoList[videoModuleController.videoPlayIndex.value]);
|
||||
if (result != null) {
|
||||
player.play();
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 15.0,
|
||||
left: 10.0,
|
||||
right: 80.0,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'@${videoList[videoModuleController.videoPlayIndex.value]['commentUserNickname'] ?? '未知'}',
|
||||
style: const TextStyle(color: Colors.white, fontSize: 16.0),
|
||||
),
|
||||
LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final text = videoList[videoModuleController.videoPlayIndex.value]['title'] ?? '未知';
|
||||
final span = TextSpan(
|
||||
text: text,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 14.0),
|
||||
);
|
||||
final tp = TextPainter(
|
||||
text: span,
|
||||
maxLines: 3,
|
||||
textDirection: TextDirection.ltr,
|
||||
);
|
||||
tp.layout(maxWidth: constraints.maxWidth);
|
||||
final isOverflow = tp.didExceedMaxLines;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
text,
|
||||
maxLines: videoList[videoModuleController.videoPlayIndex.value]['expanded'] ? null : 3,
|
||||
overflow:
|
||||
videoList[videoModuleController.videoPlayIndex.value]['expanded'] ? TextOverflow.visible : TextOverflow.ellipsis,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 14.0),
|
||||
),
|
||||
if (isOverflow)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 6.0),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
videoList[videoModuleController.videoPlayIndex.value]['expanded'] =
|
||||
!videoList[videoModuleController.videoPlayIndex.value]['expanded'];
|
||||
});
|
||||
},
|
||||
child: Text(
|
||||
videoList[videoModuleController.videoPlayIndex.value]['expanded'] ? '收起' : '展开更多',
|
||||
textAlign: TextAlign.right,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
)),
|
||||
Positioned(
|
||||
bottom: 0.0,
|
||||
left: 6.0,
|
||||
right: 6.0,
|
||||
child: Visibility(
|
||||
visible: videoModuleController.videoPlayIndex.value == index && position > Duration.zero,
|
||||
child: Listener(
|
||||
child: SliderTheme(
|
||||
data: SliderThemeData(
|
||||
trackHeight: sliderDraging ? 6.0 : 2.0,
|
||||
thumbShape: RoundSliderThumbShape(enabledThumbRadius: 4.0),
|
||||
overlayShape: RoundSliderOverlayShape(overlayRadius: 0),
|
||||
inactiveTrackColor: Colors.white24,
|
||||
activeTrackColor: Colors.white,
|
||||
thumbColor: Colors.white,
|
||||
overlayColor: Colors.transparent,
|
||||
),
|
||||
child: Slider(
|
||||
value: sliderValue,
|
||||
onChanged: (value) async {
|
||||
setState(() {
|
||||
sliderValue = value;
|
||||
});
|
||||
await player.seek(duration * value.clamp(0.0, 1.0));
|
||||
},
|
||||
onChangeEnd: (value) async {
|
||||
setState(() {
|
||||
sliderDraging = false;
|
||||
});
|
||||
if (!player.state.playing) {
|
||||
await player.play();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
onPointerMove: (e) {
|
||||
setState(() {
|
||||
sliderDraging = true;
|
||||
});
|
||||
onTap: () {
|
||||
player.playOrPause();
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 100.0,
|
||||
left: 10.0,
|
||||
right: 10.0,
|
||||
child: Visibility(
|
||||
visible: sliderDraging,
|
||||
child: DefaultTextStyle(
|
||||
style: TextStyle(color: Colors.white54, fontSize: 18.0, fontFamily: 'Arial'),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
spacing: 8.0,
|
||||
// 右侧操作栏
|
||||
Positioned(
|
||||
bottom: 100.0,
|
||||
right: 6.0,
|
||||
child: Column(
|
||||
spacing: 15.0,
|
||||
children: [
|
||||
Stack(
|
||||
children: [
|
||||
Text(position.label(reference: duration), style: TextStyle(color: Colors.white)),
|
||||
Text('/', style: TextStyle(fontSize: 14.0)),
|
||||
Text(duration.label(reference: duration)),
|
||||
SizedBox(
|
||||
height: 55.0,
|
||||
width: 48.0,
|
||||
child: GestureDetector(
|
||||
onTap: () async {
|
||||
player.pause();
|
||||
// 跳转到 Vloger 页面并等待返回结果
|
||||
final vloggerId = videoList[videoModuleController.videoPlayIndex.value]['memberId'];
|
||||
final result = await Get.toNamed('/vloger', arguments: {'memberId': vloggerId});
|
||||
if (result != null) {
|
||||
// 处理返回的参数
|
||||
print('返回的数据: ${result['followStatus']}');
|
||||
player.play();
|
||||
videoList[index]['doIFollowVloger'] = result['followStatus'];
|
||||
}
|
||||
},
|
||||
child: UnconstrainedBox(
|
||||
alignment: Alignment.topCenter,
|
||||
child: Container(
|
||||
height: 48.0,
|
||||
width: 48.0,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.white, width: 2.0),
|
||||
borderRadius: BorderRadius.circular(100.0),
|
||||
),
|
||||
child: ClipOval(
|
||||
child: NetworkOrAssetImage(
|
||||
imageUrl: videoList[index]['commentUserFace'],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 15.0,
|
||||
child: InkWell(
|
||||
child: Container(
|
||||
height: 18.0,
|
||||
width: 18.0,
|
||||
decoration: BoxDecoration(
|
||||
color: videoList[index]['doIFollowVloger'] ? Colors.white : Color(0xFFFF5000),
|
||||
borderRadius: BorderRadius.circular(100.0),
|
||||
),
|
||||
child: Icon(
|
||||
videoList[index]['doIFollowVloger'] ? Icons.check : Icons.add,
|
||||
color: videoList[index]['doIFollowVloger'] ? Color(0xFFFF5000) : Colors.white,
|
||||
size: 14.0,
|
||||
),
|
||||
),
|
||||
onTap: () async {
|
||||
final vlogerId = videoList[index]['memberId'];
|
||||
final doIFollowVloger = videoList[index]['doIFollowVloger'];
|
||||
// 未关注点击才去关注
|
||||
if (doIFollowVloger == false) {
|
||||
final res = await ImService.instance.followUser(userIDList: [vlogerId]);
|
||||
if (res.success) {
|
||||
setState(() {
|
||||
videoList[index]['doIFollowVloger'] = !videoList[index]['doIFollowVloger'];
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
GestureDetector(
|
||||
child: Column(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
'assets/images/svg/heart.svg',
|
||||
colorFilter: ColorFilter.mode(videoList[index]['doILikeThisVlog'] ? Color(0xFFFF5000) : Colors.white, BlendMode.srcIn),
|
||||
height: 40.0,
|
||||
width: 40.0,
|
||||
),
|
||||
Text(
|
||||
'${videoList[index]['likeCounts'] + (videoList[index]['doILikeThisVlog'] ? 1 : 0)}',
|
||||
style: TextStyle(color: Colors.white, fontSize: 12.0),
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
logger.d('点击了点赞按钮${videoList[index]['doILikeThisVlog']}');
|
||||
if (videoList[index]['doILikeThisVlog'] == true) {
|
||||
logger.d('点击了点赞按钮${videoList[index]['doILikeThisVlog']}');
|
||||
doUnLikeVideo(videoList[index]);
|
||||
} else {
|
||||
doLikeVideo(videoList[index]);
|
||||
}
|
||||
},
|
||||
),
|
||||
GestureDetector(
|
||||
child: Column(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
'assets/images/svg/reply.svg',
|
||||
colorFilter: ColorFilter.mode(Colors.white, BlendMode.srcIn),
|
||||
height: 40.0,
|
||||
width: 40.0,
|
||||
),
|
||||
Text(
|
||||
'${videoList[index]['commentsCounts']}',
|
||||
style: TextStyle(color: Colors.white, fontSize: 12.0),
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
handleComment(index);
|
||||
},
|
||||
),
|
||||
GestureDetector(
|
||||
child: Column(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
'assets/images/svg/share.svg',
|
||||
colorFilter: ColorFilter.mode(Colors.white, BlendMode.srcIn),
|
||||
height: 40.0,
|
||||
width: 40.0,
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
handleShare(index);
|
||||
},
|
||||
),
|
||||
GestureDetector(
|
||||
child: Column(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
'assets/images/svg/report.svg',
|
||||
colorFilter: ColorFilter.mode(Colors.white, BlendMode.srcIn),
|
||||
height: 40.0,
|
||||
width: 40.0,
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () async {
|
||||
player.pause();
|
||||
// 跳转到举报页面并等待返回结果
|
||||
final result = await Get.toNamed('/report', arguments: videoList[videoModuleController.videoPlayIndex.value]);
|
||||
if (result != null) {
|
||||
player.play();
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 15.0,
|
||||
left: 10.0,
|
||||
right: 80.0,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'@${videoList[videoModuleController.videoPlayIndex.value]['commentUserNickname'] ?? '未知'}',
|
||||
style: const TextStyle(color: Colors.white, fontSize: 16.0),
|
||||
),
|
||||
LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final text = videoList[videoModuleController.videoPlayIndex.value]['title'] ?? '未知';
|
||||
final span = TextSpan(
|
||||
text: text,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 14.0),
|
||||
);
|
||||
final tp = TextPainter(
|
||||
text: span,
|
||||
maxLines: 3,
|
||||
textDirection: TextDirection.ltr,
|
||||
);
|
||||
tp.layout(maxWidth: constraints.maxWidth);
|
||||
final isOverflow = tp.didExceedMaxLines;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
text,
|
||||
maxLines: videoList[videoModuleController.videoPlayIndex.value]['expanded'] ? null : 3,
|
||||
overflow: videoList[videoModuleController.videoPlayIndex.value]['expanded']
|
||||
? TextOverflow.visible
|
||||
: TextOverflow.ellipsis,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 14.0),
|
||||
),
|
||||
if (isOverflow)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 6.0),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
videoList[videoModuleController.videoPlayIndex.value]['expanded'] =
|
||||
!videoList[videoModuleController.videoPlayIndex.value]['expanded'];
|
||||
});
|
||||
},
|
||||
child: Text(
|
||||
videoList[videoModuleController.videoPlayIndex.value]['expanded'] ? '收起' : '展开更多',
|
||||
textAlign: TextAlign.right,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
)),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
Positioned(
|
||||
bottom: 0.0,
|
||||
left: 6.0,
|
||||
right: 6.0,
|
||||
child: Visibility(
|
||||
visible: videoModuleController.videoPlayIndex.value == index && position > Duration.zero,
|
||||
child: Listener(
|
||||
child: SliderTheme(
|
||||
data: SliderThemeData(
|
||||
trackHeight: sliderDraging ? 6.0 : 2.0,
|
||||
thumbShape: RoundSliderThumbShape(enabledThumbRadius: 4.0),
|
||||
overlayShape: RoundSliderOverlayShape(overlayRadius: 0),
|
||||
inactiveTrackColor: Colors.white24,
|
||||
activeTrackColor: Colors.white,
|
||||
thumbColor: Colors.white,
|
||||
overlayColor: Colors.transparent,
|
||||
),
|
||||
child: Slider(
|
||||
value: sliderValue,
|
||||
onChanged: (value) async {
|
||||
setState(() {
|
||||
sliderValue = value;
|
||||
});
|
||||
await player.seek(duration * value.clamp(0.0, 1.0));
|
||||
},
|
||||
onChangeEnd: (value) async {
|
||||
setState(() {
|
||||
sliderDraging = false;
|
||||
});
|
||||
if (!player.state.playing) {
|
||||
await player.play();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
onPointerMove: (e) {
|
||||
setState(() {
|
||||
sliderDraging = true;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 100.0,
|
||||
left: 10.0,
|
||||
right: 10.0,
|
||||
child: Visibility(
|
||||
visible: sliderDraging,
|
||||
child: DefaultTextStyle(
|
||||
style: TextStyle(color: Colors.white54, fontSize: 18.0, fontFamily: 'Arial'),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
spacing: 8.0,
|
||||
children: [
|
||||
Text(position.label(reference: duration), style: TextStyle(color: Colors.white)),
|
||||
Text('/', style: TextStyle(fontSize: 14.0)),
|
||||
Text(duration.label(reference: duration)),
|
||||
],
|
||||
),
|
||||
)),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
@ -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: '分享成功',
|
||||
@ -1141,8 +1149,8 @@ class _FriendModuleState extends State<FriendModule> {
|
||||
onTap: () async {
|
||||
player.pause();
|
||||
// 跳转到 Vloger 页面并等待返回结果
|
||||
final vloggerId = videoList[videoModuleController.videoPlayIndex.value]['memberId'];
|
||||
final result = await Get.toNamed('/vloger', arguments: {'memberId':vloggerId});
|
||||
final vloggerId = videoList[videoModuleController.videoPlayIndex.value]['memberId'];
|
||||
final result = await Get.toNamed('/vloger', arguments: {'memberId': vloggerId});
|
||||
if (result != null) {
|
||||
// 处理返回的参数
|
||||
print('返回的数据: ${result['followStatus']}');
|
||||
|
@ -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']}');
|
||||
|
@ -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(),
|
||||
//关系链
|
||||
|
@ -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);
|
||||
|
@ -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']) {
|
||||
|
72
lib/utils/image_utils.dart
Normal file
72
lib/utils/image_utils.dart
Normal 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;
|
||||
}
|
||||
}
|
@ -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(
|
||||
child: NetworkOrAssetImage(
|
||||
imageUrl: avatar,
|
||||
placeholderAsset: isGroup ? 'assets/images/group.png' : 'assets/images/avatar/default.png',
|
||||
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)));
|
||||
// }
|
||||
// },
|
||||
// );
|
||||
}
|
||||
|
||||
/// 被关注通知
|
||||
|
@ -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;
|
||||
}
|
||||
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,17 +41,27 @@ 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;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 请求相机权限
|
||||
static Future<bool> requestCameraPermission() async {
|
||||
return await _checkAndRequest(Permission.camera);
|
||||
}
|
||||
|
||||
// 请求麦克风权限
|
||||
@ -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: '去设置',
|
||||
);
|
||||
|
@ -1,8 +1,14 @@
|
||||
// 扫码 类型
|
||||
class QrTypeCode {
|
||||
static const String hxm = 'hxm-';
|
||||
static const String hym = 'hym-';
|
||||
static const String tgm = 'tgm-';
|
||||
}
|
||||
|
||||
enum ScanCodeType {
|
||||
verification('hxm'), // 核销码
|
||||
friend('hym'), // 好友码
|
||||
promotion('tgm'); // 推广码
|
||||
friend('hym'), // 好友码
|
||||
promotion('tgm'); // 推广码
|
||||
|
||||
final String prefix;
|
||||
const ScanCodeType(this.prefix);
|
||||
@ -26,4 +32,4 @@ enum ScanCodeType {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
136
pubspec.lock
136
pubspec.lock
@ -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:
|
||||
|
@ -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 # 使用最新版本
|
||||
|
Loading…
x
Reference in New Issue
Block a user