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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,20 +2,22 @@
library; library;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:loopin/IM/controller/chat_controller.dart'; import 'package:loopin/IM/controller/chat_controller.dart';
import 'package:loopin/IM/global_badge.dart'; import 'package:loopin/IM/global_badge.dart';
import 'package:loopin/IM/im_service.dart'; import 'package:loopin/IM/im_service.dart';
import 'package:loopin/api/shop_api.dart';
import 'package:loopin/components/network_or_asset_image.dart'; import 'package:loopin/components/network_or_asset_image.dart';
import 'package:loopin/components/scan_util.dart'; import 'package:loopin/components/scan_util.dart';
import 'package:loopin/models/conversation_type.dart'; import 'package:loopin/models/conversation_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/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/index.dart';
import 'package:loopin/utils/scan_code_type.dart'; //
import 'package:loopin/utils/parse_message_summary.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:shirne_dialog/shirne_dialog.dart';
import 'package:tencent_cloud_chat_sdk/enum/receive_message_opt.dart'; import 'package:tencent_cloud_chat_sdk/enum/receive_message_opt.dart';
@ -55,7 +57,8 @@ class ChatPageState extends State<ChatPage> {
await Future.delayed(Duration(seconds: 1)); await Future.delayed(Duration(seconds: 1));
setState(() {}); setState(() {});
} }
//
//
void handleScanResult(String code) { void handleScanResult(String code) {
print('扫码结果11111111111111111111111$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'); print('处理核销码: $value');
// //
Get.toNamed('/sellerOrder/detail', arguments: {'writeOffCodeId':value}); Get.toNamed('/sellerOrder/detail', arguments: {'writeOffCodeId': value});
} }
// //
void _handleFriendCode(String value) { void _handleFriendCode(String value) {
print('处理好友码: $value'); print('处理好友码: $value');
// 仿 // 仿
Get.toNamed('/vloger', arguments: {'memberId':value}); Get.toNamed('/vloger', arguments: {'memberId': value});
} }
// 广 // 广
void _handlePromotionCode(String value)async { void _handlePromotionCode(String value) async {
try { try {
print('处理推广码111: $value'); print('处理推广码111: $value');
final res = await Http.post('${ShopApi.bindSpreadCodeId}', data: { final res = await Http.post(ShopApi.bindSpreadCodeId, data: {"socialCode": value});
"socialCode": value if (res != null && res['code'] == 200) {
}); MyDialog.toast('推广码绑定失败', icon: const Icon(Icons.check_circle), style: ToastStyle(backgroundColor: Colors.green.withAlpha(200)));
if(res != null && res['code'] == 200){ Get.toNamed('/vloger', arguments: {'memberId': value});
MyDialog.toast('推广码绑定失败', icon: const Icon(Icons.check_circle), style: ToastStyle(backgroundColor: Colors.green.withAlpha(200))); }
Get.toNamed('/vloger', arguments: {'memberId':value});
}
} catch (e) { } catch (e) {
MyDialog.toast('推广码绑定失败'); MyDialog.toast('推广码绑定失败');
} }
} }
// //
void showContextMenu(BuildContext context, ConversationViewModel item) { void showContextMenu(BuildContext context, ConversationViewModel item) {
return;
bool isLeft = posDX > MediaQuery.of(context).size.width / 2 ? false : true; bool isLeft = posDX > MediaQuery.of(context).size.width / 2 ? false : true;
bool isTop = posDY > MediaQuery.of(context).size.height / 2 ? false : true; bool isTop = posDY > MediaQuery.of(context).size.height / 2 ? false : true;
@ -183,7 +186,7 @@ class ChatPageState extends State<ChatPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
backgroundColor: Colors.grey[50], // backgroundColor: Colors.grey[50],
appBar: AppBar( appBar: AppBar(
forceMaterialTransparency: true, forceMaterialTransparency: true,
title: Row( title: Row(
@ -299,6 +302,7 @@ class ChatPageState extends State<ChatPage> {
break; break;
case 'friend': case 'friend':
logger.w('点击了添加朋友'); logger.w('点击了添加朋友');
Get.to(() => AddFriend());
break; break;
case 'scan': case 'scan':
logger.w('点击了扫一扫'); logger.w('点击了扫一扫');
@ -408,6 +412,7 @@ class ChatPageState extends State<ChatPage> {
// logger.w(chatList[index].conversation.conversationGroupList); // logger.w(chatList[index].conversation.conversationGroupList);
// logger.w(chatList[index].isCustomAdmin); // logger.w(chatList[index].isCustomAdmin);
// logger.w(chatList[index].conversation.recvOpt); // logger.w(chatList[index].conversation.recvOpt);
final item = chatList[index];
final bool quiet = [ReceiveMsgOptType.kTIMRecvMsgOpt_Not_Notify_Except_At].contains(chatList[index].conversation.recvOpt ?? 0) final bool quiet = [ReceiveMsgOptType.kTIMRecvMsgOpt_Not_Notify_Except_At].contains(chatList[index].conversation.recvOpt ?? 0)
? true ? true
: false; // : false; //
@ -416,125 +421,155 @@ class ChatPageState extends State<ChatPage> {
chatList[index].isCustomAdmin != null && (chatList[index].isCustomAdmin?.isNotEmpty ?? false) && chatList[index].isCustomAdmin != '0'; 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'; final placeholderAsset = chatList[index].conversation.type == 2 ? 'assets/images/group.png' : 'assets/images/avatar/default.png';
// logger.e(chatList[index].isCustomAdmin); // logger.e(chatList[index].isCustomAdmin);
return Ink( return Slidable(
// color: chatList[index]['topMost'] == null ? Colors.white : Colors.grey[100], // key: ValueKey(chatList[index].conversation.conversationID),
child: InkWell( endActionPane: ActionPane(
key: ValueKey(chatList[index].conversation.conversationID), motion: const DrawerMotion(), // StretchMotion ScrollMotion DrawerMotion
splashColor: Colors.grey[200], children: [
child: Container( SlidableAction(
padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 10.0), onPressed: (_) {
child: Row( //
spacing: 10.0, },
children: <Widget>[ backgroundColor: Colors.blue,
// foregroundColor: Colors.white,
ClipOval( icon: Icons.delete,
child: NetworkOrAssetImage( label: '已读',
imageUrl: chatList[index].faceUrl, ),
width: 50, if (!(isAdmin || isNoFriend))
height: 50, SlidableAction(
placeholderAsset: placeholderAsset, 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( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, 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>[ children: <Widget>[
// Visibility(
Text( visible: !(isAdmin || isNoFriend),
chatList[index].conversation.showName ?? '未知', child: Text(
maxLines: 1, //
softWrap: false, // DateTime.fromMillisecondsSinceEpoch(
overflow: TextOverflow.ellipsis, // (chatList[index].conversation.lastMessage!.timestamp ?? 0) * 1000,
style: TextStyle( // ).toLocal().toString().substring(0, 16), //
fontSize: (isAdmin || isNoFriend) ? 20 : 16, Utils.formatTime(
fontWeight: (isAdmin || isNoFriend) ? FontWeight.bold : FontWeight.normal), chatList[index].conversation.lastMessage!.timestamp ?? DateTime.now().millisecondsSinceEpoch ~/ 1000),
style: const TextStyle(color: Colors.grey, fontSize: 12.0),
),
), ),
const SizedBox(height: 2.0), const SizedBox(height: 5.0),
// //
Text( // class ReceiveMsgOptType {
chatList[index].conversation.lastMessage != null ? parseMessageSummary(chatList[index].conversation.lastMessage!) : '', // 线线 APNs
style: const TextStyle(color: Colors.grey, fontSize: 13.0), // static const int kTIMRecvMsgOpt_Receive = 0;
overflow: TextOverflow.ellipsis, // 线
maxLines: 1, // static const int kTIMRecvMsgOpt_Not_Receive = 1;
softWrap: false, // 线线
// 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;
// }
// 03
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(
Column( Icons.arrow_forward_ios,
crossAxisAlignment: CrossAxisAlignment.end, color: Colors.blueGrey,
children: <Widget>[ size: 14.0,
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: 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;
// }
// 03
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=01=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=01=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]);
},
), ),
); );
}, },

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,13 +1,17 @@
import 'dart:io';
import 'package:bottom_picker/bottom_picker.dart'; import 'package:bottom_picker/bottom_picker.dart';
import 'package:city_pickers/city_pickers.dart'; import 'package:city_pickers/city_pickers.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.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/IM/controller/im_user_info_controller.dart';
import 'package:loopin/api/common_api.dart'; import 'package:loopin/api/common_api.dart';
import 'package:loopin/components/network_or_asset_image.dart'; import 'package:loopin/components/network_or_asset_image.dart';
import 'package:loopin/service/http.dart'; import 'package:loopin/service/http.dart';
import 'package:loopin/styles/index.dart'; import 'package:loopin/styles/index.dart';
import 'package:loopin/utils/image_utils.dart';
import 'package:loopin/utils/index.dart'; import 'package:loopin/utils/index.dart';
import 'package:loopin/utils/permissions.dart'; import 'package:loopin/utils/permissions.dart';
import 'package:loopin/utils/wxsdk.dart'; import 'package:loopin/utils/wxsdk.dart';
@ -208,7 +212,7 @@ class _UserInfoState extends State<UserInfo> {
void pickCover(BuildContext context) async { void pickCover(BuildContext context) async {
final hasPer = await Permissions.requestPhotoPermission(); final hasPer = await Permissions.requestPhotoPermission();
if (!hasPer) { if (!hasPer) {
Permissions.showPermissionDialog(); Permissions.showPermissionDialog('相册');
return; return;
} }
final pickedAssets = await AssetPicker.pickAssets( final pickedAssets = await AssetPicker.pickAssets(
@ -252,7 +256,7 @@ class _UserInfoState extends State<UserInfo> {
void pickFaceUrl(BuildContext context) async { void pickFaceUrl(BuildContext context) async {
final hasPer = await Permissions.requestPhotoPermission(); final hasPer = await Permissions.requestPhotoPermission();
if (!hasPer) { if (!hasPer) {
Permissions.showPermissionDialog(); Permissions.showPermissionDialog('相册');
return; return;
} }
final pickedAssets = await AssetPicker.pickAssets( final pickedAssets = await AssetPicker.pickAssets(
@ -281,12 +285,46 @@ class _UserInfoState extends State<UserInfo> {
} else { } else {
print("图片合法,大小:$sizeInMB MB"); print("图片合法,大小:$sizeInMB MB");
//upload(file)url地址 //upload(file)url地址
final istance = MyDialog.loading('上传中');
final res = await Http.upload(CommonApi.uploadFile, filePath: file.path); final croppedFile = await ImageCropper().cropImage(
userInfoController.faceUrl.value = res['data']['url']; sourcePath: file.path,
userInfoController.updateFaceUrl(); maxWidth: 1024,
userInfoController.customInfo.refresh(); maxHeight: null,
istance.close(); 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();
}
} }
} }
} }

View File

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

View File

@ -516,7 +516,15 @@ class _VideoDetailPageState extends State<VideoDetailPage> {
data: makeJson, data: makeJson,
); );
if (res.success) { 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) { if (sendRes.success) {
MyToast().tip( MyToast().tip(
title: '分享成功', title: '分享成功',

View File

@ -15,12 +15,12 @@ import 'package:loopin/IM/im_service.dart' hide logger;
import 'package:loopin/api/video_api.dart'; import 'package:loopin/api/video_api.dart';
import 'package:loopin/components/my_toast.dart'; import 'package:loopin/components/my_toast.dart';
import 'package:loopin/components/network_or_asset_image.dart'; import 'package:loopin/components/network_or_asset_image.dart';
import 'package:loopin/models/share_type.dart';
import 'package:loopin/models/summary_type.dart'; import 'package:loopin/models/summary_type.dart';
import 'package:loopin/service/http.dart'; import 'package:loopin/service/http.dart';
import 'package:loopin/utils/download_video.dart'; import 'package:loopin/utils/download_video.dart';
import 'package:loopin/utils/permissions.dart'; import 'package:loopin/utils/permissions.dart';
import 'package:loopin/utils/wxsdk.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/media_kit.dart';
import 'package:media_kit_video/media_kit_video.dart'; import 'package:media_kit_video/media_kit_video.dart';
import 'package:media_kit_video/media_kit_video_controls/src/controls/extensions/duration.dart'; import 'package:media_kit_video/media_kit_video_controls/src/controls/extensions/duration.dart';
@ -997,7 +997,15 @@ class _AttentionModuleState extends State<AttentionModule> {
data: makeJson, data: makeJson,
); );
if (res.success) { 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) { if (sendRes.success) {
MyToast().tip( MyToast().tip(
title: '分享成功', title: '分享成功',
@ -1019,398 +1027,399 @@ class _AttentionModuleState extends State<AttentionModule> {
color: Colors.black, color: Colors.black,
child: Column( child: Column(
children: [ children: [
// //
if (videoList.isEmpty && !isLoadingMore) if (videoList.isEmpty && !isLoadingMore)
Expanded( Expanded(
child: Center( child: Center(
child: Text( child: Text(
'暂无数据', '暂无数据',
style: TextStyle( style: TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 16.0, fontSize: 16.0,
),
), ),
), ),
), )
)
else else
Expanded( Expanded(
child: Stack( child: Stack(
children: [ children: [
/// ///
PageView.builder( PageView.builder(
scrollBehavior: CustomScrollBehavior().copyWith(scrollbars: false), scrollBehavior: CustomScrollBehavior().copyWith(scrollbars: false),
scrollDirection: Axis.vertical, scrollDirection: Axis.vertical,
controller: pageController, controller: pageController,
onPageChanged: (index) async { onPageChanged: (index) async {
videoModuleController.updateVideoPlayIndex(index); videoModuleController.updateVideoPlayIndex(index);
setState(() { setState(() {
sliderValue = 0.0; sliderValue = 0.0;
sliderDraging = false; sliderDraging = false;
position = Duration.zero; position = Duration.zero;
duration = Duration.zero; duration = Duration.zero;
}); });
player.stop(); player.stop();
await player.open(Media(videoList[index]['url'])); await player.open(Media(videoList[index]['url']));
if (index == videoList.length - 2 && !isLoadingMore) { if (index == videoList.length - 2 && !isLoadingMore) {
await fetchVideoList(); await fetchVideoList();
} }
}, },
itemCount: videoList.length, itemCount: videoList.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final videoWidth = videoList[index]['width'] ?? 1; final videoWidth = videoList[index]['width'] ?? 1;
final videoHeight = videoList[index]['height'] ?? 1; final videoHeight = videoList[index]['height'] ?? 1;
final isHorizontal = videoWidth > videoHeight; final isHorizontal = videoWidth > videoHeight;
final filteredList = chatController.chatList.where((item) => item.conversation.conversationGroupList?.isEmpty == true).toList(); final filteredList = chatController.chatList.where((item) => item.conversation.conversationGroupList?.isEmpty == true).toList();
return Stack( return Stack(
children: [ children: [
// //
Positioned( Positioned(
top: 0, top: 0,
left: 0, left: 0,
right: 0, right: 0,
bottom: 0, bottom: 0,
child: GestureDetector( child: GestureDetector(
child: Stack( 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(
children: [ children: [
SizedBox( Visibility(
height: 55.0, visible: videoModuleController.videoPlayIndex.value == index && position > Duration.zero,
width: 48.0, child: Video(
child: GestureDetector( controller: videoController,
onTap: () async { fit: isHorizontal ? BoxFit.contain : BoxFit.cover,
player.pause(); controls: NoVideoControls,
// 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( AnimatedOpacity(
bottom: 0, opacity: videoModuleController.videoPlayIndex.value == index && position > Duration(milliseconds: 100) ? 0.0 : 1.0,
left: 15.0, duration: Duration(milliseconds: 50),
child: InkWell( child: Image.network(
child: Container( videoList[index]['firstFrameImg'] ?? 'https://wuzhongjie.com.cn/download/logo.png',
height: 18.0, fit: isHorizontal ? BoxFit.contain : BoxFit.cover,
width: 18.0, width: double.infinity,
decoration: BoxDecoration( height: double.infinity,
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'];
});
}
}
},
), ),
), ),
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( onTap: () {
child: Column( player.playOrPause();
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( Positioned(
bottom: 100.0, bottom: 100.0,
left: 10.0, right: 6.0,
right: 10.0, child: Column(
child: Visibility( spacing: 15.0,
visible: sliderDraging, children: [
child: DefaultTextStyle( Stack(
style: TextStyle(color: Colors.white54, fontSize: 18.0, fontFamily: 'Arial'),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 8.0,
children: [ children: [
Text(position.label(reference: duration), style: TextStyle(color: Colors.white)), SizedBox(
Text('/', style: TextStyle(fontSize: 14.0)), height: 55.0,
Text(duration.label(reference: duration)), 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)),
],
),
)),
),
],
);
},
),
],
),
), ),
),
], ],
), ),
); );

View File

@ -1002,7 +1002,15 @@ class _FriendModuleState extends State<FriendModule> {
data: makeJson, data: makeJson,
); );
if (res.success) { 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) { if (sendRes.success) {
MyToast().tip( MyToast().tip(
title: '分享成功', title: '分享成功',
@ -1141,8 +1149,8 @@ class _FriendModuleState extends State<FriendModule> {
onTap: () async { onTap: () async {
player.pause(); player.pause();
// Vloger // Vloger
final vloggerId = videoList[videoModuleController.videoPlayIndex.value]['memberId']; 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) { if (result != null) {
// //
print('返回的数据: ${result['followStatus']}'); print('返回的数据: ${result['followStatus']}');

View File

@ -981,7 +981,6 @@ class _RecommendModuleState extends State<RecommendModule> {
void handlCoverClick(V2TimConversation conv) async { void handlCoverClick(V2TimConversation conv) async {
// VideoMsg, // VideoMsg,
final userId = conv.userID;
final currentVideo = videoList[videoModuleController.videoPlayIndex.value]; final currentVideo = videoList[videoModuleController.videoPlayIndex.value];
logger.w(currentVideo); logger.w(currentVideo);
final img = (currentVideo['cover'] != null && currentVideo['cover'].toString().isNotEmpty) ? currentVideo['cover'] : currentVideo['firstFrameImg']; final img = (currentVideo['cover'] != null && currentVideo['cover'].toString().isNotEmpty) ? currentVideo['cover'] : currentVideo['firstFrameImg'];
@ -1001,7 +1000,16 @@ class _RecommendModuleState extends State<RecommendModule> {
data: makeJson, data: makeJson,
); );
if (res.success) { 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) { if (sendRes.success) {
MyToast().tip( MyToast().tip(
title: '分享成功', title: '分享成功',
@ -1127,7 +1135,7 @@ class _RecommendModuleState extends State<RecommendModule> {
player.pause(); player.pause();
// Vloger // Vloger
final vloggerId = videoList[videoModuleController.videoPlayIndex.value]['memberId']; 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) { if (result != null) {
// //
print('返回的数据: ${result['followStatus']}'); print('返回的数据: ${result['followStatus']}');

View File

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

View File

@ -32,11 +32,14 @@ class FStyle {
// //
// static const backgroundColor = Color(0xFFEEEEEE); // static const backgroundColor = Color(0xFFEEEEEE);
static const backgroundColor = Colors.white; static const backgroundColor = Color(0xFFFDF6F0);
static const primaryColor = Color(0xFFFF5000); static const primaryColor = Color(0xFFFF5000);
static const white = Colors.white; static const white = Colors.white;
static const c999 = Color(0xFF999999); 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 mt(double v) => EdgeInsets.only(top: v);
static mb(double v) => EdgeInsets.only(bottom: v); static mb(double v) => EdgeInsets.only(bottom: v);

View File

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

View File

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

View File

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

View File

@ -25,19 +25,14 @@ class Permissions {
} }
} else if (Platform.isIOS) { } else if (Platform.isIOS) {
final status = await Permission.photos.request(); 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 { static Future<bool> requestPhotoPermission() async {
// return await _checkAndRequest(Permission.photos);
if (Platform.isAndroid) { if (Platform.isAndroid) {
final deviceInfoPlugin = DeviceInfoPlugin(); final deviceInfoPlugin = DeviceInfoPlugin();
final androidInfo = await deviceInfoPlugin.androidInfo; final androidInfo = await deviceInfoPlugin.androidInfo;
@ -46,17 +41,27 @@ class Permissions {
if (sdkInt >= 33) { if (sdkInt >= 33) {
// Android 13 // Android 13
final status = await Permission.photos.request(); final status = await Permission.photos.request();
return status.isGranted; // return status.isGranted;
return handleStatus(status, isAndroid: true);
} else { } else {
// Android 12 // Android 12
final status = await Permission.storage.request(); final status = await Permission.storage.request();
return status.isGranted; // return status.isGranted;
return handleStatus(status, isAndroid: true);
} }
} else if (Platform.isIOS) { } else if (Platform.isIOS) {
final status = await Permission.photos.request(); 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) { if (result.isPermanentlyDenied) {
// //
showPermissionDialog(); return false;
} else { } else {
// //
Get.snackbar('权限请求失败', '无法访问,请授权对应权限后重试'); Get.snackbar('权限请求失败', '无法访问,请授权对应权限后重试');
@ -90,10 +95,45 @@ class Permissions {
return false; 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( final confirmed = await ConfirmDialog.show(
title: '需要权限', title: '需要$name权限',
content: '请前往系统设置中手动开启权限', content: '请前往系统设置中手动开启权限',
confirmText: '去设置', confirmText: '去设置',
); );

View File

@ -1,8 +1,14 @@
// //
class QrTypeCode {
static const String hxm = 'hxm-';
static const String hym = 'hym-';
static const String tgm = 'tgm-';
}
enum ScanCodeType { enum ScanCodeType {
verification('hxm'), // verification('hxm'), //
friend('hym'), // friend('hym'), //
promotion('tgm'); // 广 promotion('tgm'); // 广
final String prefix; final String prefix;
const ScanCodeType(this.prefix); const ScanCodeType(this.prefix);
@ -26,4 +32,4 @@ enum ScanCodeType {
} }
return null; return null;
} }
} }

View File

@ -113,6 +113,30 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "3.2.1" 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: card_swiper:
dependency: "direct main" dependency: "direct main"
description: description:
@ -350,6 +374,14 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" 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: flutter_form_builder:
dependency: "direct main" dependency: "direct main"
description: description:
@ -451,6 +483,14 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "2.0.28" 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: flutter_staggered_grid_view:
dependency: "direct main" dependency: "direct main"
description: description:
@ -605,6 +645,38 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "4.5.4" 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: image_picker:
dependency: "direct main" dependency: "direct main"
description: description:
@ -893,6 +965,14 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "3.0.0" 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: package_info_plus:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1085,6 +1165,14 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "6.0.2" 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: provider:
dependency: transitive dependency: transitive
description: description:
@ -1173,6 +1261,14 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "1.0.6" 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: safe_local_storage:
dependency: transitive dependency: transitive
description: description:
@ -1234,6 +1330,46 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "7.0.0" 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: stack_trace:
dependency: transitive dependency: transitive
description: description:

View File

@ -67,7 +67,8 @@ dependencies:
flutter_upgrader: ^1.1.20 #更新 flutter_upgrader: ^1.1.20 #更新
path_provider: ^2.1.2 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 install_plugin: ^2.1.0 # Android 安装 APK
flutter_native_splash: ^2.4.6 # 启动图 flutter_native_splash: ^2.4.6 # 启动图
@ -94,6 +95,10 @@ dependencies:
image_picker: ^1.2.0 #相机 image_picker: ^1.2.0 #相机
video_player: ^2.10.0 #视频处理 video_player: ^2.10.0 #视频处理
mime: ^2.0.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: dev_dependencies:
flutter_launcher_icons: ^0.13.1 # 使用最新版本 flutter_launcher_icons: ^0.13.1 # 使用最新版本