Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
67ed131eeb | ||
|
|
314aeddf23 | ||
|
|
9902980bf8 | ||
|
|
0fd1ed8799 | ||
|
|
fa619318b8 | ||
|
|
c104e9a627 | ||
|
|
86f5234060 | ||
|
|
f9a1f2ff9f | ||
|
|
4c15dee33a | ||
|
|
564cd38516 | ||
|
|
6291dbc55d |
BIN
android_macos.zip
Normal file
BIN
assets/images/logo/androidlogo.png
Normal file
|
After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 146 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 2.1 MiB After Width: | Height: | Size: 43 KiB |
1
assets/images/svg/more.svg
Normal file
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1759238468389" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5738" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M256 512m-74.666667 0a74.666667 74.666667 0 1 0 149.333334 0 74.666667 74.666667 0 1 0-149.333334 0Z" fill="#111111" p-id="5739"></path><path d="M512 512m-74.666667 0a74.666667 74.666667 0 1 0 149.333334 0 74.666667 74.666667 0 1 0-149.333334 0Z" fill="#111111" p-id="5740"></path><path d="M768 512m-74.666667 0a74.666667 74.666667 0 1 0 149.333334 0 74.666667 74.666667 0 1 0-149.333334 0Z" fill="#111111" p-id="5741"></path></svg>
|
||||
|
After Width: | Height: | Size: 765 B |
BIN
assets/images/wait_loading.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
@ -1,4 +1,8 @@
|
||||
# Uncomment this line to define a global platform for your project
|
||||
# source 'https://mirrors.tuna.tsinghua.edu.cn/git/CocoaPods/Specs.git'
|
||||
# source 'https://mirrors.tuna.tsinghua.edu.cn/cocoapods/simple/'
|
||||
|
||||
|
||||
platform :ios, '12.0'
|
||||
# 允许拉取http资源
|
||||
# ENV['COCOAPODS_ALLOW_INSECURE_SOURCES'] = 'true'
|
||||
@ -33,6 +37,9 @@ target 'Runner' do
|
||||
use_frameworks!
|
||||
|
||||
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
|
||||
# 集成微信官方 SDK
|
||||
# pod 'WechatOpenSDK-XCFramework', '~> 2.0.5'
|
||||
|
||||
target 'RunnerTests' do
|
||||
inherit! :search_paths
|
||||
end
|
||||
|
||||
@ -1,7 +1,11 @@
|
||||
PODS:
|
||||
- app_links (6.4.1):
|
||||
- Flutter
|
||||
- audioplayers_darwin (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- connectivity_plus (0.0.1):
|
||||
- Flutter
|
||||
- device_info_plus (0.0.1):
|
||||
- Flutter
|
||||
- Flutter (1.0.0)
|
||||
@ -19,7 +23,7 @@ PODS:
|
||||
- fluwx/pay (= 0.0.1)
|
||||
- fluwx/pay (0.0.1):
|
||||
- Flutter
|
||||
- WechatOpenSDK-XCFramework (~> 2.0.4)
|
||||
- WechatOpenSDK-XCFramework (~> 2.0.5)
|
||||
- geolocator_apple (1.2.0):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
@ -33,17 +37,17 @@ PODS:
|
||||
- Flutter
|
||||
- install_plugin (2.0.0):
|
||||
- Flutter
|
||||
- libwebp (1.3.2):
|
||||
- libwebp/demux (= 1.3.2)
|
||||
- libwebp/mux (= 1.3.2)
|
||||
- libwebp/sharpyuv (= 1.3.2)
|
||||
- libwebp/webp (= 1.3.2)
|
||||
- libwebp/demux (1.3.2):
|
||||
- libwebp (1.5.0):
|
||||
- libwebp/demux (= 1.5.0)
|
||||
- libwebp/mux (= 1.5.0)
|
||||
- libwebp/sharpyuv (= 1.5.0)
|
||||
- libwebp/webp (= 1.5.0)
|
||||
- libwebp/demux (1.5.0):
|
||||
- libwebp/webp
|
||||
- libwebp/mux (1.3.2):
|
||||
- libwebp/mux (1.5.0):
|
||||
- libwebp/demux
|
||||
- libwebp/sharpyuv (1.3.2)
|
||||
- libwebp/webp (1.3.2):
|
||||
- libwebp/sharpyuv (1.5.0)
|
||||
- libwebp/webp (1.5.0):
|
||||
- libwebp/sharpyuv
|
||||
- Mantle (2.2.0):
|
||||
- Mantle/extobjc (= 2.2.0)
|
||||
@ -67,9 +71,9 @@ PODS:
|
||||
- FlutterMacOS
|
||||
- record_ios (1.0.0):
|
||||
- Flutter
|
||||
- SDWebImage (5.20.0):
|
||||
- SDWebImage/Core (= 5.20.0)
|
||||
- SDWebImage/Core (5.20.0)
|
||||
- SDWebImage (5.21.2):
|
||||
- SDWebImage/Core (= 5.21.2)
|
||||
- SDWebImage/Core (5.21.2)
|
||||
- SDWebImageWebPCoder (0.14.6):
|
||||
- libwebp (~> 1.0)
|
||||
- SDWebImage/Core (~> 5.17)
|
||||
@ -87,7 +91,7 @@ PODS:
|
||||
- TIMPush (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.7040)
|
||||
- url_launcher_ios (0.0.1):
|
||||
- Flutter
|
||||
- video_player_avfoundation (0.0.1):
|
||||
@ -100,10 +104,15 @@ PODS:
|
||||
- Flutter
|
||||
- wakelock_plus (0.0.1):
|
||||
- Flutter
|
||||
- WechatOpenSDK-XCFramework (2.0.4)
|
||||
- webview_flutter_wkwebview (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- WechatOpenSDK-XCFramework (2.0.5)
|
||||
|
||||
DEPENDENCIES:
|
||||
- app_links (from `.symlinks/plugins/app_links/ios`)
|
||||
- audioplayers_darwin (from `.symlinks/plugins/audioplayers_darwin/darwin`)
|
||||
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
||||
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||
- Flutter (from `Flutter`)
|
||||
- flutter_image_compress_common (from `.symlinks/plugins/flutter_image_compress_common/ios`)
|
||||
@ -131,6 +140,7 @@ DEPENDENCIES:
|
||||
- video_thumbnail (from `.symlinks/plugins/video_thumbnail/ios`)
|
||||
- volume_controller (from `.symlinks/plugins/volume_controller/ios`)
|
||||
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
|
||||
- webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/darwin`)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
@ -145,8 +155,12 @@ SPEC REPOS:
|
||||
- WechatOpenSDK-XCFramework
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
app_links:
|
||||
:path: ".symlinks/plugins/app_links/ios"
|
||||
audioplayers_darwin:
|
||||
:path: ".symlinks/plugins/audioplayers_darwin/darwin"
|
||||
connectivity_plus:
|
||||
:path: ".symlinks/plugins/connectivity_plus/ios"
|
||||
device_info_plus:
|
||||
:path: ".symlinks/plugins/device_info_plus/ios"
|
||||
Flutter:
|
||||
@ -201,22 +215,26 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/volume_controller/ios"
|
||||
wakelock_plus:
|
||||
:path: ".symlinks/plugins/wakelock_plus/ios"
|
||||
webview_flutter_wkwebview:
|
||||
:path: ".symlinks/plugins/webview_flutter_wkwebview/darwin"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
app_links: 3dbc685f76b1693c66a6d9dd1e9ab6f73d97dc0a
|
||||
audioplayers_darwin: 4f9ca89d92d3d21cec7ec580e78ca888e5fb68bd
|
||||
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
|
||||
device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
|
||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||
flutter_image_compress_common: 1697a328fd72bfb335507c6bca1a65fa5ad87df1
|
||||
flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
|
||||
flutter_upgrader: 16a975eb987fc210cdf6bebffe0069a480f80523
|
||||
fluwx: 6bf9c5a3a99ad31b0de137dd92370a0d10a60f4b
|
||||
fluwx: 2ef787502fccb3f3596b380509001a8ea71cbbff
|
||||
geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e
|
||||
HydraAsync: 8d589bd725b0224f899afafc9a396327405f8063
|
||||
image_cropper: c4326ea50132b1e1564499e5d32a84f01fb03537
|
||||
image_gallery_saver_plus: e597bf65a7846979417a3eae0763b71b6dfec6c3
|
||||
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
|
||||
install_plugin: e17e38d6f504857748a3ec1299d8a2bbeeeea854
|
||||
libwebp: 1786c9f4ff8a279e4dac1e8f385004d5fc253009
|
||||
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
|
||||
Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d
|
||||
media_kit_libs_ios_video: 5a18affdb97d1f5d466dc79988b13eff6c5e2854
|
||||
media_kit_video: 1746e198cb697d1ffb734b1d05ec429d1fcd1474
|
||||
@ -226,21 +244,22 @@ SPEC CHECKSUMS:
|
||||
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
|
||||
photo_manager: 1d80ae07a89a67dfbcae95953a1e5a24af7c3e62
|
||||
record_ios: fee1c924aa4879b882ebca2b4bce6011bcfc3d8b
|
||||
SDWebImage: 73c6079366fea25fa4bb9640d5fb58f0893facd8
|
||||
SDWebImage: 9f177d83116802728e122410fb25ad88f5c7608a
|
||||
SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380
|
||||
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
||||
tencent_cloud_chat_push: f87ae58098c2062b06e81f39fc53afc528395916
|
||||
tencent_cloud_chat_sdk: 0a406f1854a65aad2f853494c02a2e084a027ab2
|
||||
TIMPush: d0dfe96355ee413a7cacb2576f8aaa66f6073ab2
|
||||
TOCropViewController: 80b8985ad794298fb69d3341de183f33d1853654
|
||||
TXIMSDK_Plus_iOS_XCFramework: cb54f7de6e30e1368c6831c6eff31c25393bbb98
|
||||
TXIMSDK_Plus_iOS_XCFramework: 36ea2cdb544315d79e520cd54d0bbb1fbb1110bc
|
||||
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
|
||||
video_player_avfoundation: 2cef49524dd1f16c5300b9cd6efd9611ce03639b
|
||||
video_thumbnail: b637e0ad5f588ca9945f6e2c927f73a69a661140
|
||||
volume_controller: 3657a1f65bedb98fa41ff7dc5793537919f31b12
|
||||
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
|
||||
WechatOpenSDK-XCFramework: 36fb2bea0754266c17184adf4963d7e6ff98b69f
|
||||
webview_flutter_wkwebview: 1821ceac936eba6f7984d89a9f3bcb4dea99ebb2
|
||||
WechatOpenSDK-XCFramework: b072030c9eeee91dfff1856a7846f70f7b9a88ed
|
||||
|
||||
PODFILE CHECKSUM: 866435f3a12ad92d8fb66fa46b52776da7e16ce5
|
||||
PODFILE CHECKSUM: f650ed3bb2b12d7ea6a8477a748641b3d65c4991
|
||||
|
||||
COCOAPODS: 1.16.2
|
||||
|
||||
@ -15,8 +15,7 @@
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||
C8092B212E34A78000D25A0B /* WechatOpenSDK-XCFramework.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = C8092B202E34A78000D25A0B /* WechatOpenSDK-XCFramework.xcframework */; };
|
||||
C8092B222E34A78000D25A0B /* WechatOpenSDK-XCFramework.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C8092B202E34A78000D25A0B /* WechatOpenSDK-XCFramework.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
C8F379732E85F00600E7B665 /* WechatOpenSDK.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = C8F379722E85F00600E7B665 /* WechatOpenSDK.xcframework */; };
|
||||
ECDFBB33253E89949730F7D8 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 898AE91CA73F2F6E910D884D /* Pods_Runner.framework */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
@ -37,7 +36,6 @@
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
C8092B222E34A78000D25A0B /* WechatOpenSDK-XCFramework.xcframework in Embed Frameworks */,
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@ -66,8 +64,8 @@
|
||||
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
A9774DDA95C7FD895F6925A4 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
BB84C2FA9C50ACAF0C376254 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
C8092B202E34A78000D25A0B /* WechatOpenSDK-XCFramework.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = "WechatOpenSDK-XCFramework.xcframework"; path = "Pods/WechatOpenSDK-XCFramework/WechatOpenSDK-XCFramework.xcframework"; sourceTree = "<group>"; };
|
||||
C891EF1D2E43F9730021EB39 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; };
|
||||
C8F379722E85F00600E7B665 /* WechatOpenSDK.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = WechatOpenSDK.xcframework; path = "Pods/WechatOpenSDK-XCFramework/WechatOpenSDK.xcframework"; sourceTree = "<group>"; };
|
||||
DCA23AF172275D04ECB63EFB /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
E3EC116A6CCDD06C6D4615E2 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
@ -77,7 +75,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
C8092B212E34A78000D25A0B /* WechatOpenSDK-XCFramework.xcframework in Frameworks */,
|
||||
C8F379732E85F00600E7B665 /* WechatOpenSDK.xcframework in Frameworks */,
|
||||
ECDFBB33253E89949730F7D8 /* Pods_Runner.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@ -165,7 +163,7 @@
|
||||
9CE4C2341F34F9A85A1D3EED /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C8092B202E34A78000D25A0B /* WechatOpenSDK-XCFramework.xcframework */,
|
||||
C8F379722E85F00600E7B665 /* WechatOpenSDK.xcframework */,
|
||||
898AE91CA73F2F6E910D884D /* Pods_Runner.framework */,
|
||||
1AE799326ED7557212A901E0 /* Pods_RunnerTests.framework */,
|
||||
);
|
||||
@ -510,8 +508,13 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = cn.net.wzj.mall;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Profile;
|
||||
@ -689,6 +692,54 @@
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = 9C9VWBX77X;
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"\"${PODS_CONFIGURATION_BUILD_DIR}/HydraAsync\"",
|
||||
"\"${PODS_CONFIGURATION_BUILD_DIR}/Mantle\"",
|
||||
"\"${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage\"",
|
||||
"\"${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageWebPCoder\"",
|
||||
"\"${PODS_CONFIGURATION_BUILD_DIR}/TOCropViewController\"",
|
||||
"\"${PODS_CONFIGURATION_BUILD_DIR}/app_links\"",
|
||||
"\"${PODS_CONFIGURATION_BUILD_DIR}/audioplayers_darwin\"",
|
||||
"\"${PODS_CONFIGURATION_BUILD_DIR}/device_info_plus\"",
|
||||
"\"${PODS_CONFIGURATION_BUILD_DIR}/flutter_image_compress_common\"",
|
||||
"\"${PODS_CONFIGURATION_BUILD_DIR}/flutter_native_splash\"",
|
||||
"\"${PODS_CONFIGURATION_BUILD_DIR}/flutter_upgrader\"",
|
||||
"\"${PODS_CONFIGURATION_BUILD_DIR}/fluwx\"",
|
||||
"\"${PODS_CONFIGURATION_BUILD_DIR}/geolocator_apple\"",
|
||||
"\"${PODS_CONFIGURATION_BUILD_DIR}/image_cropper\"",
|
||||
"\"${PODS_CONFIGURATION_BUILD_DIR}/image_gallery_saver_plus\"",
|
||||
"\"${PODS_CONFIGURATION_BUILD_DIR}/image_picker_ios\"",
|
||||
"\"${PODS_CONFIGURATION_BUILD_DIR}/install_plugin\"",
|
||||
"\"${PODS_CONFIGURATION_BUILD_DIR}/libwebp\"",
|
||||
"\"${PODS_CONFIGURATION_BUILD_DIR}/media_kit_libs_ios_video\"",
|
||||
"\"${PODS_CONFIGURATION_BUILD_DIR}/media_kit_video\"",
|
||||
"\"${PODS_CONFIGURATION_BUILD_DIR}/mobile_scanner\"",
|
||||
"\"${PODS_CONFIGURATION_BUILD_DIR}/package_info_plus\"",
|
||||
"\"${PODS_CONFIGURATION_BUILD_DIR}/path_provider_foundation\"",
|
||||
"\"${PODS_CONFIGURATION_BUILD_DIR}/permission_handler_apple\"",
|
||||
"\"${PODS_CONFIGURATION_BUILD_DIR}/photo_manager\"",
|
||||
"\"${PODS_CONFIGURATION_BUILD_DIR}/record_ios\"",
|
||||
"\"${PODS_CONFIGURATION_BUILD_DIR}/sqflite_darwin\"",
|
||||
"\"${PODS_CONFIGURATION_BUILD_DIR}/tencent_cloud_chat_push\"",
|
||||
"\"${PODS_CONFIGURATION_BUILD_DIR}/tencent_cloud_chat_sdk\"",
|
||||
"\"${PODS_CONFIGURATION_BUILD_DIR}/url_launcher_ios\"",
|
||||
"\"${PODS_CONFIGURATION_BUILD_DIR}/video_player_avfoundation\"",
|
||||
"\"${PODS_CONFIGURATION_BUILD_DIR}/video_thumbnail\"",
|
||||
"\"${PODS_CONFIGURATION_BUILD_DIR}/volume_controller\"",
|
||||
"\"${PODS_CONFIGURATION_BUILD_DIR}/wakelock_plus\"",
|
||||
"\"${PODS_CONFIGURATION_BUILD_DIR}/webview_flutter_wkwebview\"",
|
||||
"\"${PODS_ROOT}/../.symlinks/plugins/media_kit_libs_ios_video/ios/Frameworks\"",
|
||||
"\"${PODS_ROOT}/../.symlinks/plugins/tencent_cloud_chat_sdk/ios/Frameworks\"",
|
||||
"\"${PODS_ROOT}/TIMPush\"",
|
||||
"\"${PODS_ROOT}/TXIMSDK_Plus_iOS_XCFramework\"",
|
||||
"\"${PODS_ROOT}/WechatOpenSDK-XCFramework\"",
|
||||
"\"${PODS_XCFRAMEWORKS_BUILD_DIR}/TIMPush\"",
|
||||
"\"${PODS_XCFRAMEWORKS_BUILD_DIR}/TXIMSDK_Plus_iOS_XCFramework\"",
|
||||
"\"${PODS_XCFRAMEWORKS_BUILD_DIR}/WechatOpenSDK-XCFramework\"/**",
|
||||
"\"${PODS_XCFRAMEWORKS_BUILD_DIR}/media_kit_libs_ios_video\"",
|
||||
"\"${PODS_XCFRAMEWORKS_BUILD_DIR}/tencent_cloud_chat_sdk\"",
|
||||
);
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
@ -698,9 +749,14 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = cn.net.wzj.mall;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Debug;
|
||||
@ -726,8 +782,13 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = cn.net.wzj.mall;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Release;
|
||||
|
||||
@ -5,17 +5,50 @@ import Flutter
|
||||
import TIMPush
|
||||
import tencent_cloud_chat_push
|
||||
|
||||
// 官方微信 SDK
|
||||
import WechatOpenSDK
|
||||
|
||||
|
||||
// Add `, TIMPushDelegate` to the following line
|
||||
@UIApplicationMain
|
||||
@objc class AppDelegate: FlutterAppDelegate, TIMPushDelegate {
|
||||
private let CHANNEL = "wechat_business_view"
|
||||
|
||||
override func application(
|
||||
_ application: UIApplication,
|
||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||
) -> Bool {
|
||||
// 集成微信商家转账用户确认收款
|
||||
let controller: FlutterViewController = window?.rootViewController as! FlutterViewController
|
||||
let methodChannel = FlutterMethodChannel(name: CHANNEL, binaryMessenger: controller.binaryMessenger)
|
||||
|
||||
methodChannel.setMethodCallHandler { [weak self] (call, result) in
|
||||
if call.method == "openBusinessView" {
|
||||
if let args = call.arguments as? [String: Any],
|
||||
let packageInfo = args["package"] as? String {
|
||||
self?.openBusinessView(packageInfo: packageInfo)
|
||||
result(true)
|
||||
} else {
|
||||
result(FlutterError(code: "BAD_ARGS", message: "Missing package info", details: nil))
|
||||
}
|
||||
} else {
|
||||
result(FlutterMethodNotImplemented)
|
||||
}
|
||||
}
|
||||
//end
|
||||
GeneratedPluginRegistrant.register(with: self)
|
||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
}
|
||||
|
||||
private func openBusinessView(packageInfo: String) {
|
||||
let req = WXOpenBusinessViewReq()
|
||||
req.businessType = "requestMerchantTransfer"
|
||||
req.query = packageInfo
|
||||
WXApi.send(req) { success in
|
||||
print("WXOpenBusinessViewReq send result: \(success)")
|
||||
}
|
||||
}
|
||||
|
||||
// To be deprecated,please use the new field businessID below.
|
||||
@objc func offlinePushCertificateID() -> Int32 {
|
||||
return TencentCloudChatPushFlutterModal.shared.offlinePushCertificateID();
|
||||
|
||||
@ -2,16 +2,17 @@
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "background.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
"appearances" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"filename" : "darkbackground.png",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
||||
|
Before Width: | Height: | Size: 69 B After Width: | Height: | Size: 69 B |
BIN
ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png
vendored
Normal file
|
After Width: | Height: | Size: 69 B |
@ -5,15 +5,48 @@
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"filename" : "LaunchImageDark.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "LaunchImage@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"filename" : "LaunchImageDark@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "LaunchImage@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"filename" : "LaunchImageDark@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
||||
|
Before Width: | Height: | Size: 136 KiB After Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 536 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 31 KiB |
BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark.png
vendored
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 31 KiB |
@ -17,7 +17,7 @@
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" image="LaunchBackground" translatesAutoresizingMaskIntoConstraints="NO" id="tWc-Dq-wcI"/>
|
||||
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4"></imageView>
|
||||
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleAspectFill" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4"></imageView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
@ -38,7 +38,7 @@
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="LaunchImage" width="1024" height="1536"/>
|
||||
<image name="LaunchImage" width="750" height="1334"/>
|
||||
<image name="LaunchBackground" width="1" height="1"/>
|
||||
</resources>
|
||||
</document>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<dict>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
@ -34,6 +34,16 @@
|
||||
<string>wxebcdaea31881caab</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>startApp</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>wuzhongjie</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||
@ -49,7 +59,15 @@
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
<key>NSAllowsArbitraryLoadsForMedia</key>
|
||||
<true/>
|
||||
<key>NSAllowsArbitraryLoadsInWebContent</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string>App 需要访问您的位置以提供附近推荐内容和标记上传视频的位置</string>
|
||||
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
|
||||
<string>App 需要访问您的位置以提供个性化内容和附近服务。</string>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>App需要使用您的相机进行拍摄</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
@ -62,8 +80,19 @@
|
||||
<string>App需要访问您的相册用于选择图片或视频</string>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
<key>CFBundleLocalizations</key>
|
||||
<array>
|
||||
<string>en</string>
|
||||
<string>zh-Hans</string>
|
||||
</array>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>remote-notification</string>
|
||||
</array>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen.storyboard</string>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UIStatusBarHidden</key>
|
||||
@ -83,15 +112,15 @@
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>remote-notification</string>
|
||||
</array>
|
||||
|
||||
<key>com.apple.developer.associated-domains</key>
|
||||
<array>
|
||||
<string>applinks:wuzhongjie.com.cn</string>
|
||||
</array>
|
||||
</dict>
|
||||
<key>LSApplicationQueriesSchemes</key>
|
||||
<array>
|
||||
<string>weixin</string>
|
||||
<string>weixinULAPI</string>
|
||||
<string>weixinURLParamsAPI</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@ -147,9 +147,9 @@ class ChatController extends GetxController {
|
||||
}
|
||||
|
||||
final List<ConversationViewModel> convList = res.data;
|
||||
for (var conv in convList) {
|
||||
logger.w('基本会话: ${conv.conversation.toJson()}');
|
||||
}
|
||||
// for (var conv in convList) {
|
||||
// logger.w('基本会话: ${conv.conversation.toJson()}');
|
||||
// }
|
||||
// 去重
|
||||
// 已有会话ID集合
|
||||
final existingIds = chatList.map((e) => e.conversation.conversationID).where((id) => id.isNotEmpty).toSet();
|
||||
|
||||
@ -9,6 +9,13 @@ class ImUserInfoController extends GetxController {
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
logger.i('开始IM用户信息初始化');
|
||||
init(V2TimUserFullInfo(customInfo: {
|
||||
"coverBg": "",
|
||||
"area": "",
|
||||
"areaCode": "",
|
||||
"openId": "",
|
||||
"tag": "",
|
||||
}));
|
||||
}
|
||||
|
||||
V2TimUserFullInfo? rawUserInfo;
|
||||
@ -47,6 +54,19 @@ class ImUserInfoController extends GetxController {
|
||||
"openId": "",
|
||||
"tag": "",
|
||||
});
|
||||
// final safeMap = <String, String>{};
|
||||
// userInfo.customInfo?.forEach((key, value) {
|
||||
// safeMap[key] = value;
|
||||
// });
|
||||
|
||||
// customInfo.assignAll({
|
||||
// "coverBg": "",
|
||||
// "area": "",
|
||||
// "areaCode": "",
|
||||
// "openId": "",
|
||||
// "tag": "",
|
||||
// ...safeMap, // 覆盖默认值
|
||||
// });
|
||||
|
||||
role.value = userInfo.role ?? 0;
|
||||
level.value = userInfo.level ?? 0;
|
||||
|
||||
@ -23,7 +23,12 @@ class ImCore {
|
||||
// 初始化视频
|
||||
final videoController = Get.find<VideoModuleController>();
|
||||
videoController.init();
|
||||
Get.toNamed('/login');
|
||||
Get.offAllNamed(
|
||||
'/login',
|
||||
predicate: (route) {
|
||||
return route.settings.name == '/';
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,10 +1,17 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:logger/logger.dart';
|
||||
import 'package:loopin/IM/controller/chat_controller.dart';
|
||||
import 'package:loopin/IM/controller/im_user_info_controller.dart';
|
||||
import 'package:loopin/IM/im_message.dart';
|
||||
import 'package:loopin/IM/im_service.dart';
|
||||
import 'package:loopin/models/conversation_view_model.dart';
|
||||
import 'package:loopin/pages/groupChat/controller/group_detail_controller.dart';
|
||||
import 'package:tencent_cloud_chat_sdk/enum/V2TimGroupListener.dart';
|
||||
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart';
|
||||
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_change_info.dart';
|
||||
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_member_change_info.dart';
|
||||
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_member_info.dart';
|
||||
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message.dart';
|
||||
import 'package:tencent_cloud_chat_sdk/models/v2_tim_topic_info.dart';
|
||||
import 'package:tencent_cloud_chat_sdk/native_im/adapter/tim_manager.dart';
|
||||
|
||||
@ -130,19 +137,67 @@ class ImGroupListeners {
|
||||
}
|
||||
|
||||
// 成员被邀请入群
|
||||
void onMemberInvited(String groupID, List<V2TimGroupMemberInfo> memberList) {
|
||||
void onMemberInvited(String groupID, List<V2TimGroupMemberInfo> memberList) async {
|
||||
if (Get.isRegistered<GroupDetailController>()) {
|
||||
final ctl = Get.find<GroupDetailController>();
|
||||
ctl.init();
|
||||
}
|
||||
//在群标记,目的为了清除被踢后的标记
|
||||
await ImService.instance.setConversationCustomData(customData: 'int', conversationIDList: ['group_$groupID']);
|
||||
}
|
||||
|
||||
// 成员被踢出群
|
||||
void onMemberKicked(String groupID, List<V2TimGroupMemberInfo> memberList) {
|
||||
void onMemberKicked(String groupID, List<V2TimGroupMemberInfo> memberList) async {
|
||||
if (Get.isRegistered<GroupDetailController>()) {
|
||||
final ctl = Get.find<GroupDetailController>();
|
||||
ctl.init();
|
||||
}
|
||||
// 这里处理会话
|
||||
final infoCtl = Get.find<ImUserInfoController>();
|
||||
final hasSelf = memberList.any((m) => m.userID == infoCtl.userID.value);
|
||||
logger.e(hasSelf);
|
||||
if (hasSelf) {
|
||||
// 给群打上标记
|
||||
await ImService.instance.setConversationCustomData(customData: 'out', conversationIDList: ['group_$groupID']);
|
||||
|
||||
// 被踢的人中包含自己,先在本地找会话,没有在去云端找
|
||||
final chatCtl = Get.find<ChatController>();
|
||||
ConversationViewModel? conv;
|
||||
try {
|
||||
conv = chatCtl.chatList.firstWhere((m) => m.conversation.groupID == groupID);
|
||||
} catch (e) {
|
||||
conv = null;
|
||||
}
|
||||
if (conv != null) {
|
||||
logger.e('本地找到');
|
||||
// 本地有 9=群tips
|
||||
final result = await IMMessage().createTextMessage(text: '你已被移出群聊');
|
||||
if (result.success && result.data != null) {
|
||||
V2TimMessage msg = result.data!.messageInfo!;
|
||||
msg.cloudCustomData = 'tips';
|
||||
final index = chatCtl.chatList.indexOf(conv);
|
||||
if (index != -1) {
|
||||
chatCtl.chatList[index].conversation.lastMessage = msg;
|
||||
chatCtl.chatList.refresh();
|
||||
logger.e('刷新');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 本地没拉到,去云端获取,
|
||||
final cloudRes = await ImService.instance.getConversation(conversationID: 'group_$groupID');
|
||||
if (cloudRes.success && cloudRes.data != null) {
|
||||
V2TimConversation cloudConv = cloudRes.data;
|
||||
final result = await IMMessage().createTextMessage(text: '你已被移出群聊');
|
||||
if (result.success && result.data != null) {
|
||||
V2TimMessage msg = result.data!.messageInfo!;
|
||||
msg.cloudCustomData = 'tips';
|
||||
cloudConv.lastMessage = msg;
|
||||
final resModel = await ConversationViewModel.createConversationViewModel(convList: [cloudConv]);
|
||||
chatCtl.chatList.insertAll(0, resModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 成员信息变更
|
||||
|
||||
@ -47,6 +47,11 @@ class IMMessage {
|
||||
title: myInfo.nickname.value,
|
||||
desc: parseMessageSummary(msg),
|
||||
ext: jsonEncode({"userID": myInfo.userID.value, "title": myInfo.nickname.value}),
|
||||
//ios ios>18必须声明
|
||||
iOSPushType: 0, //0=普通消息 1=语音/视频
|
||||
iOSSound: 'default', // kIOSOfflinePushDefaultSound
|
||||
ignoreIOSBadge: false, // 是否忽略角标
|
||||
iOSInterruptionLevel: 'active', // 普通通知 active // time-sensitive
|
||||
);
|
||||
sendRes = await TencentImSDKPlugin.v2TIMManager.getMessageManager().sendMessage(
|
||||
message: msg,
|
||||
@ -75,7 +80,12 @@ class IMMessage {
|
||||
OfflinePushInfo offlinePushInfo = OfflinePushInfo(
|
||||
title: groupName,
|
||||
desc: parseMessageSummary(msg),
|
||||
ext: jsonEncode({"groupID": groupID, "title": groupName ?? ''}),
|
||||
ext: jsonEncode({"groupID": groupID, "title": groupName ?? '群聊'}),
|
||||
//ios ios>18必须声明
|
||||
iOSPushType: 0, //0=普通消息 1=语音/视频
|
||||
iOSSound: 'default', // kIOSOfflinePushDefaultSound
|
||||
ignoreIOSBadge: false, // 是否忽略角标
|
||||
iOSInterruptionLevel: 'active', // 普通通知
|
||||
);
|
||||
sendRes = await TencentImSDKPlugin.v2TIMManager.getMessageManager().sendMessage(
|
||||
message: msg,
|
||||
|
||||
@ -84,13 +84,6 @@ class ImService {
|
||||
if (result.success) {
|
||||
logger.i("IM 登录成功:$userID");
|
||||
|
||||
// 初始化会话数据
|
||||
final ctl = Get.find<ChatController>();
|
||||
await ctl.getConversationList();
|
||||
|
||||
/// 初始化微信 SDK
|
||||
await Wxsdk.init();
|
||||
|
||||
// 注册用户信息(基本信息+自定义信息)
|
||||
if (!Get.isRegistered<ImUserInfoController>()) {
|
||||
final imInfo = Get.put(ImUserInfoController(), permanent: true);
|
||||
@ -99,6 +92,13 @@ class ImService {
|
||||
await Get.find<ImUserInfoController>().refreshUserInfo();
|
||||
}
|
||||
|
||||
// 初始化会话数据
|
||||
final ctl = Get.find<ChatController>();
|
||||
await ctl.getConversationList();
|
||||
|
||||
/// 初始化微信 SDK
|
||||
await Wxsdk.init();
|
||||
|
||||
// 登录成功后注册高级消息监听器
|
||||
final messageService = ImMessageListenerService();
|
||||
Get.put<ImMessageListenerService>(messageService, permanent: true);
|
||||
@ -124,6 +124,15 @@ class ImService {
|
||||
);
|
||||
} else {
|
||||
logger.i("IM 登录失败:${result.code} - ${result.desc}");
|
||||
if (result.code == 70013) {
|
||||
Get.snackbar(
|
||||
'登录失败',
|
||||
'请求的 Identifier 与生成 UserSig 的 Identifier 不匹配',
|
||||
backgroundColor: Colors.red.withAlpha(230),
|
||||
colorText: Colors.white,
|
||||
icon: const Icon(Icons.error_outline, color: Colors.white),
|
||||
);
|
||||
} else {
|
||||
Get.snackbar(
|
||||
'登录失败',
|
||||
'${result.code} - ${result.desc}',
|
||||
@ -132,6 +141,7 @@ class ImService {
|
||||
icon: const Icon(Icons.error_outline, color: Colors.white),
|
||||
);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -275,7 +285,7 @@ class ImService {
|
||||
|
||||
// 提前收集所有需要批量查询的 userID 和 groupID
|
||||
for (var conv in convList) {
|
||||
logger.e('未过滤前到会话数据:${conv.toLogString()}');
|
||||
// logger.e('未过滤前到会话数据:${conv.toLogString()}');
|
||||
if (conv.userID != null) {
|
||||
userIDList.add(conv.userID!);
|
||||
} else if (conv.groupID != null) {
|
||||
@ -297,7 +307,7 @@ class ImService {
|
||||
|
||||
// 读取管理员标识
|
||||
final customInfo = user.customInfo;
|
||||
logger.w('自定义信息:${user.toJson()}');
|
||||
// logger.w('自定义信息:${user.toJson()}');
|
||||
if (customInfo != null) {
|
||||
isCustomAdmin[userId] = customInfo['admin'] ?? '0';
|
||||
}
|
||||
@ -504,6 +514,14 @@ class ImService {
|
||||
);
|
||||
}
|
||||
|
||||
/// 标记单聊消息为已读
|
||||
Future<ImResult> markC2CMessageAsReadResult({
|
||||
required String userID,
|
||||
}) async {
|
||||
final res = await TIMMessageManager.instance.markC2CMessageAsRead(userID: userID);
|
||||
return ImResult.wrapNoData(res);
|
||||
}
|
||||
|
||||
///
|
||||
/// 清理指定单聊会话的未读数
|
||||
///
|
||||
@ -646,6 +664,12 @@ class ImService {
|
||||
return ImResult.wrap(res);
|
||||
}
|
||||
|
||||
/// 拉黑列表
|
||||
Future<ImResult<List<V2TimFriendInfo>>> blackList() async {
|
||||
final res = await TIMFriendshipManager.instance.getBlackList();
|
||||
return ImResult.wrap(res);
|
||||
}
|
||||
|
||||
///获取好友列表
|
||||
Future<ImResult<List<V2TimFriendInfo>>> getFriendList() async {
|
||||
final res = await TIMFriendshipManager.instance.getFriendList();
|
||||
|
||||
@ -48,20 +48,25 @@ class PushService {
|
||||
}
|
||||
|
||||
// 注册推送(初始化)
|
||||
// if (Platform.isAndroid) {
|
||||
if (Platform.isAndroid) {
|
||||
// await compute(_registerPushInIsolate, {
|
||||
// 'sdkAppId': sdkAppId,
|
||||
// 'appKey': appKey,
|
||||
// 'apnsCertificateID': apnsCertificateID,
|
||||
// });
|
||||
// } else {
|
||||
await _registerPushInIsolate({
|
||||
'sdkAppId': sdkAppId,
|
||||
'appKey': appKey,
|
||||
'apnsCertificateID': apnsCertificateID,
|
||||
});
|
||||
} else {
|
||||
await TencentCloudChatPush().registerPush(
|
||||
onNotificationClicked: _onNotificationClicked,
|
||||
sdkAppId: sdkAppId,
|
||||
appKey: appKey,
|
||||
apnsCertificateID: apnsCertificateID,
|
||||
);
|
||||
// }
|
||||
}
|
||||
|
||||
// 关闭 App 在前台时弹出通知栏
|
||||
await TencentCloudChatPush().disablePostNotificationInForeground(disable: true);
|
||||
@ -70,8 +75,10 @@ class PushService {
|
||||
if (Platform.isAndroid) {
|
||||
await TencentImSDKPlugin.v2TIMManager.login(userID: Storage.read('userId'), userSig: Storage.read('userSig'));
|
||||
}
|
||||
|
||||
logger.i('推送服务已注册,手机:$devices,证书ID:$apnsCertificateID');
|
||||
// final token = await TencentCloudChatPush().getAndroidPushToken();
|
||||
logger.i(
|
||||
'推送服务已注册,手机:$devices,证书ID:$apnsCertificateID',
|
||||
);
|
||||
|
||||
// 添加在线时监听器
|
||||
_addPushListener();
|
||||
@ -123,19 +130,23 @@ class PushService {
|
||||
/// 统一处理跳转逻辑
|
||||
static void _handleNotificationClick(String ext, {String? userID, String? groupID}) async {
|
||||
try {
|
||||
// ext={id:对应业务ID,type:'newFocus',userID:发送人的id,groupID:群ID}
|
||||
// ext={id:对应业务ID,type:'newFocus',userID:发送人的id,groupID:群ID} // type=
|
||||
// final ext = jsonEncode({
|
||||
// "userID": "123456",
|
||||
// "groupID": "654321",
|
||||
// });
|
||||
final data = jsonDecode(ext);
|
||||
logger.i(data);
|
||||
final type = data['type'];
|
||||
final router = conversationTypeFromString(type);
|
||||
String extGroupID = data['groupID'] ?? '';
|
||||
final isGroup = extGroupID.isNotEmpty;
|
||||
|
||||
// final type = data['type'];
|
||||
|
||||
final router = conversationTypeFromString(data['userID']); //这里就是解析用于发送消息的管理员的userID
|
||||
logger.w(router);
|
||||
if (router == null || router != '') {
|
||||
if (router == null) {
|
||||
// 聊天
|
||||
if (data['userID'] != null) {
|
||||
if (!isGroup) {
|
||||
logger.w('有userID');
|
||||
// 单聊,获取会话
|
||||
final covRes = await ImService.instance.getConversation(conversationID: 'c2c_${data['userID']}');
|
||||
@ -160,7 +171,9 @@ class PushService {
|
||||
}
|
||||
} else {
|
||||
// 通知类相关(这里的ID只在特定业务场景才有,比如用户发起退款,要推送给商家,这里的id对应的就是订单id)
|
||||
Get.toNamed('/$router', arguments: data['id'] ?? '');
|
||||
final notifyRes = await ImService.instance.getConversation(conversationID: 'c2c_$router');
|
||||
final V2TimConversation notifyConversation = notifyRes.data;
|
||||
Get.toNamed('/$router', arguments: notifyConversation);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.i("[推送点击] ext 解析失败: $e");
|
||||
|
||||
@ -4,7 +4,10 @@ class CommonApi {
|
||||
// /app/member/sms/code
|
||||
static const String getDelCode = '/app/member/sms/code'; // 发送短信验证码 {'templateId'}
|
||||
|
||||
static const String accountInfo = '/app/member/info'; // 账户信息
|
||||
static const String accountInfo = '/app/member/info'; // 账户基本信息
|
||||
|
||||
/// 用户钱包,积分,商家资料信息 /app/member/account
|
||||
static const String userAccount = '/app/member/account';
|
||||
|
||||
///---------post
|
||||
static const String login = '/auth/login'; // 登录 {'phonenumber': '', 'smsCode': '', 'clientId': '428a8310cd442757ae699df5d894f051', 'grantType': 'sms'};
|
||||
@ -22,13 +25,13 @@ class CommonApi {
|
||||
|
||||
///余额充值 [money]金额
|
||||
// {
|
||||
// "orderType": "RECHARGE",
|
||||
// "orderType": "RECHARGE","ORDER"
|
||||
// "clientType": "APP",
|
||||
// "paymentMethod": "WECHAT",
|
||||
// "paymentClient": "APP",
|
||||
// "money": 100
|
||||
// }
|
||||
static const String addBalance = '/app/payment/pay';
|
||||
static const String wechatPay = '/app/payment/pay';
|
||||
|
||||
///余额提现
|
||||
// {
|
||||
@ -40,5 +43,21 @@ class CommonApi {
|
||||
///注销 /app/member/revoked
|
||||
static const String revoked = '/app/member/revoked';
|
||||
|
||||
///resource/oss/upload
|
||||
/// 获取账单信息 [source]=3
|
||||
static const String bills = '/app/member/bills';
|
||||
|
||||
/// 发送红包 /api/red-packet/send
|
||||
// "chatType": 1, 1=单 2=群
|
||||
// "senderId": 1950059660646047745, 发送人
|
||||
// "receiverId": 1949004087674580994, 接收人 单聊时传
|
||||
// "groupId": "", 群聊传
|
||||
// "totalAmount": 1,
|
||||
// "totalCount": 1,
|
||||
// "remark": "恭喜发财"
|
||||
static const String redpacket = '/app/customer/packet/send';
|
||||
|
||||
/// 抢红包 /api/red-packet/grab
|
||||
// "packetId": "1954790726899544065",
|
||||
// "memberId": "1949004087674580994"
|
||||
static const String getpacket = '/app/customer/packet/grab';
|
||||
}
|
||||
|
||||
@ -5,6 +5,8 @@ class VideoApi {
|
||||
static const String friendList = '/app/vlog/friendList'; //互关好友的视频
|
||||
static const String followList = '/app/vlog/followList'; // 我关注的博主视频
|
||||
static const String detail = '/app/vlog/detail'; // 视频详情
|
||||
static const String deleteVideo = '/app/vlog/delete'; //删除当前用户的视频
|
||||
|
||||
// post
|
||||
static const String myPublicList = '/app/vlog/myPublicList'; // 我发布的视频
|
||||
static const String myLikedList = '/app/vlog/myLikedList'; // 我点赞的视频
|
||||
@ -13,9 +15,6 @@ class VideoApi {
|
||||
static const String reportVideoApi = '/app/feedback/add'; // 投诉视频
|
||||
static const String videoDetailApi = '/app/vlog/detail/'; // 根据视频Id获取视频系详情
|
||||
|
||||
|
||||
|
||||
|
||||
static const String unlike = '/app/vlog/unlike'; //取消点赞
|
||||
static const String totalLikedCounts = '/app/vlog/totalLikedCounts'; //收到点赞总数
|
||||
static const String publish = '/app/vlog/publish'; //发布视频
|
||||
@ -24,6 +23,5 @@ class VideoApi {
|
||||
static const String changeToPublic = '/app/vlog/changeToPublic'; //将视频改为公开状态
|
||||
static const String changeToPrivate = '/app/vlog/changeToPrivate'; //将视频改为私密状态
|
||||
|
||||
static const String deleteVideo = '/app/vlog/delete'; //删除当前用户的视频
|
||||
static const String getVideoListByMemberId = '/app/vlog/page'; //查询某人的作品
|
||||
}
|
||||
|
||||
@ -50,7 +50,8 @@ class _ImageViewerState extends State<ImageViewer> {
|
||||
Widget build(BuildContext context) {
|
||||
var imgCount = widget.images?.length;
|
||||
|
||||
return Scaffold(
|
||||
return SafeArea(
|
||||
child: Scaffold(
|
||||
body: Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
@ -113,6 +114,7 @@ class _ImageViewerState extends State<ImageViewer> {
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -27,12 +27,14 @@ class NetworkOrAssetImage extends StatelessWidget {
|
||||
width: width,
|
||||
height: height,
|
||||
fit: fit,
|
||||
placeholder: (context, url) => Image.asset(
|
||||
placeholder: placeholderAsset.isNotEmpty
|
||||
? (context, url) => Image.asset(
|
||||
placeholderAsset.isEmpty ? 'assets/images/avatar/default.png' : placeholderAsset,
|
||||
width: width,
|
||||
height: height,
|
||||
fit: fit,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
errorWidget: (context, url, error) => Image.asset(
|
||||
placeholderAsset,
|
||||
width: width,
|
||||
|
||||
57
lib/components/web_page.dart
Normal file
@ -0,0 +1,57 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:webview_flutter/webview_flutter.dart';
|
||||
|
||||
class WebPage extends StatefulWidget {
|
||||
final String url;
|
||||
final String title;
|
||||
const WebPage({super.key, required this.url, required this.title});
|
||||
|
||||
@override
|
||||
State<WebPage> createState() => _WebPageState();
|
||||
}
|
||||
|
||||
class _WebPageState extends State<WebPage> {
|
||||
late final WebViewController _controller;
|
||||
double _progress = 0.0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = WebViewController()
|
||||
..setJavaScriptMode(JavaScriptMode.unrestricted)
|
||||
..setNavigationDelegate(
|
||||
NavigationDelegate(
|
||||
onProgress: (progress) {
|
||||
setState(() {
|
||||
_progress = progress / 100.0;
|
||||
});
|
||||
},
|
||||
),
|
||||
)
|
||||
..loadRequest(Uri.parse(widget.url));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(widget.title),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
onPressed: () async {
|
||||
if (await _controller.canGoBack()) {
|
||||
_controller.goBack();
|
||||
} else {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
),
|
||||
bottom: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(2.0),
|
||||
child: _progress < 1.0 ? LinearProgressIndicator(value: _progress) : const SizedBox.shrink(),
|
||||
),
|
||||
),
|
||||
body: WebViewWidget(controller: _controller),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -5,6 +5,8 @@ import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:loopin/IM/controller/tab_bar_controller.dart';
|
||||
import 'package:loopin/models/tab_type.dart';
|
||||
import 'package:loopin/pages/video/module/attention.dart';
|
||||
import 'package:loopin/pages/video/module/friend.dart';
|
||||
import 'package:loopin/pages/video/module/recommend.dart';
|
||||
import 'package:loopin/update/upgrade_service.dart';
|
||||
import 'package:loopin/utils/common.dart';
|
||||
@ -32,15 +34,15 @@ class _LayoutState extends State<Layout> {
|
||||
VideoModuleController videoModuleController = Get.put(VideoModuleController());
|
||||
|
||||
// page页面
|
||||
List pageList = [
|
||||
VideoPage(),
|
||||
IndexPage(),
|
||||
UploadVideoPage(
|
||||
visible: false,
|
||||
),
|
||||
ChatPage(),
|
||||
MyPage()
|
||||
];
|
||||
// List pageList = [
|
||||
// VideoPage(),
|
||||
// IndexPage(),
|
||||
// UploadVideoPage(
|
||||
// visible: false,
|
||||
// ),
|
||||
// ChatPage(),
|
||||
// MyPage(key: myPageKey)
|
||||
// ];
|
||||
// tabs选项
|
||||
List navItems = [
|
||||
BottomNavigationBarItem(icon: Icon(Icons.play_circle_outline), label: '视频'),
|
||||
@ -126,34 +128,62 @@ class _LayoutState extends State<Layout> {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey[50],
|
||||
// body: pageList[pageCurrent],
|
||||
body: Obx(() {
|
||||
return Stack(
|
||||
// body: Stack(
|
||||
// children: [
|
||||
// Obx(
|
||||
// () => Offstage(
|
||||
// offstage: videoModuleController.layoutPageCurrent.value != 0,
|
||||
// child: const KeepAliveWrapper(child: VideoPage()),
|
||||
// ),
|
||||
// ),
|
||||
// Obx(
|
||||
// () => Offstage(
|
||||
// offstage: videoModuleController.layoutPageCurrent.value != 1,
|
||||
// child: IndexPage(), // 商品保活
|
||||
// ),
|
||||
// ),
|
||||
// Obx(
|
||||
// () => Offstage(
|
||||
// offstage: videoModuleController.layoutPageCurrent.value != 2,
|
||||
// child: UploadVideoPage(
|
||||
// visible: videoModuleController.layoutPageCurrent.value == 2,
|
||||
// ), // 不需要保活
|
||||
// ),
|
||||
// ),
|
||||
// Obx(
|
||||
// () => Offstage(
|
||||
// offstage: videoModuleController.layoutPageCurrent.value != 3,
|
||||
// child: ChatPage(), // 不需要保活
|
||||
// ),
|
||||
// ),
|
||||
// Obx(
|
||||
// () => Offstage(
|
||||
// offstage: videoModuleController.layoutPageCurrent.value != 4,
|
||||
// child: MyPage(key: myPageKey), // 不需要保活
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
body: Obx(
|
||||
() {
|
||||
return IndexedStack(
|
||||
index: videoModuleController.layoutPageCurrent.value,
|
||||
children: [
|
||||
Offstage(
|
||||
offstage: videoModuleController.layoutPageCurrent.value != 0,
|
||||
child: const KeepAliveWrapper(child: VideoPage()),
|
||||
),
|
||||
Offstage(
|
||||
offstage: videoModuleController.layoutPageCurrent.value != 1,
|
||||
child: IndexPage(), // 不需要保活
|
||||
),
|
||||
Offstage(
|
||||
offstage: videoModuleController.layoutPageCurrent.value != 2,
|
||||
child: UploadVideoPage(
|
||||
const KeepAliveWrapper(child: VideoPage()),
|
||||
const KeepAliveWrapper(child: IndexPage()),
|
||||
UploadVideoPage(
|
||||
visible: videoModuleController.layoutPageCurrent.value == 2,
|
||||
), // 不需要保活
|
||||
),
|
||||
Offstage(
|
||||
offstage: videoModuleController.layoutPageCurrent.value != 3,
|
||||
child: ChatPage(), // 不需要保活
|
||||
),
|
||||
Offstage(
|
||||
offstage: videoModuleController.layoutPageCurrent.value != 4,
|
||||
child: MyPage(key: myPageKey), // 不需要保活
|
||||
),
|
||||
const KeepAliveWrapper(child: ChatPage()),
|
||||
KeepAliveWrapper(
|
||||
child: MyPage(
|
||||
key: myPageKey,
|
||||
)),
|
||||
],
|
||||
);
|
||||
}),
|
||||
},
|
||||
),
|
||||
|
||||
// 底部导航栏
|
||||
bottomNavigationBar: Theme(
|
||||
// Flutter去掉BottomNavigationBar底部导航栏的水波纹
|
||||
@ -222,8 +252,16 @@ class _LayoutState extends State<Layout> {
|
||||
if (videoModuleController.videoTabIndex.value == 2) {
|
||||
RecommendModule.playVideo();
|
||||
}
|
||||
if (videoModuleController.videoTabIndex.value == 1) {
|
||||
FriendModule.playVideo();
|
||||
}
|
||||
if (videoModuleController.videoTabIndex.value == 0) {
|
||||
AttentionModule.playVideo();
|
||||
}
|
||||
} else {
|
||||
RecommendModule.pauseVideo();
|
||||
FriendModule.pauseVideo();
|
||||
AttentionModule.pauseVideo();
|
||||
}
|
||||
if ([2, 3, 4].contains(index) && !Common.isLogin()) {
|
||||
Get.toNamed('/login');
|
||||
@ -241,6 +279,6 @@ class _LayoutState extends State<Layout> {
|
||||
myPageKey.currentState?.refreshData();
|
||||
}
|
||||
videoModuleController.updateLayoutPage(index);
|
||||
setState(() {});
|
||||
// setState(() {});
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,13 +7,15 @@ import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:get_storage/get_storage.dart';
|
||||
import 'package:loopin/IM/controller/chat_controller.dart';
|
||||
import 'package:loopin/IM/controller/im_user_info_controller.dart';
|
||||
import 'package:loopin/IM/controller/tab_bar_controller.dart';
|
||||
import 'package:loopin/IM/im_core.dart' as im_core;
|
||||
import 'package:loopin/IM/im_service.dart';
|
||||
import 'package:loopin/controller/shop_index_controller.dart';
|
||||
import 'package:loopin/service/http_config.dart';
|
||||
import 'package:loopin/utils/common.dart';
|
||||
import 'package:loopin/utils/deep_link_listener.dart';
|
||||
import 'package:loopin/utils/lifecycle_handler.dart';
|
||||
import 'package:loopin/utils/network_utils.dart';
|
||||
import 'package:loopin/utils/storage.dart';
|
||||
import 'package:media_kit/media_kit.dart';
|
||||
import 'package:shirne_dialog/shirne_dialog.dart';
|
||||
@ -24,18 +26,21 @@ import 'layouts/index.dart';
|
||||
import 'router/index.dart';
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
// 初始化网络监听
|
||||
NetworkUtils().initialize();
|
||||
// 注入tabbar
|
||||
Get.put(TabBarController(), permanent: true);
|
||||
// 注入会话列表
|
||||
Get.put(ChatController(), permanent: true);
|
||||
// 注入底导菜单易选的
|
||||
Get.put(ShopIndexController(), permanent: true);
|
||||
// Get.put(ShopIndexController(), permanent: true);
|
||||
// 用户基本信息模型
|
||||
Get.put(ImUserInfoController(), permanent: true);
|
||||
|
||||
// 监听app前后台状态
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
WidgetsBinding.instance.addObserver(LifecycleHandler());
|
||||
// 禁止横屏
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await SystemChrome.setPreferredOrientations([
|
||||
DeviceOrientation.portraitUp,
|
||||
DeviceOrientation.portraitDown,
|
||||
@ -86,7 +91,8 @@ class App extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GetMaterialApp(
|
||||
return DeepLinkListener(
|
||||
child: GetMaterialApp(
|
||||
title: '无终街',
|
||||
locale: const Locale('zh'),
|
||||
supportedLocales: const [
|
||||
@ -115,6 +121,7 @@ class App extends StatelessWidget {
|
||||
Get.put<RouteObserver<PageRoute>>(routeObserver);
|
||||
return child!;
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,6 +12,7 @@ enum NotifyMessageType {
|
||||
orderRecharge, //订单->充值 online
|
||||
orderPay, //订单->订单交易成功通知 online
|
||||
orderRefund, //订单->退款结果通知
|
||||
orderWithDraw, //订单->提现成功通知
|
||||
groupNotifyCheck, //群通知->进群申请 online
|
||||
groupNotifyAccpet, // 群通知->进群审核审核通过 online
|
||||
groupNotifyFail, // 群通知->进群审核审核拒绝 online
|
||||
@ -32,6 +33,7 @@ class NotifyMessageTypeConstants {
|
||||
static const String orderRecharge = 'orderRecharge';
|
||||
static const String orderPay = 'orderPay';
|
||||
static const String orderRefund = 'orderRefund';
|
||||
static const String orderWithDraw = 'orderWithDraw';
|
||||
static const String groupNotifyCheck = 'groupNotifyCheck';
|
||||
static const String groupNotifyAccpet = 'groupNotifyAccpet';
|
||||
static const String groupNotifyFail = 'groupNotifyFail';
|
||||
@ -63,6 +65,8 @@ extension NotifyMessageTypeExtension on NotifyMessageType {
|
||||
return 'orderRecharge';
|
||||
case NotifyMessageType.orderPay:
|
||||
return 'orderPay';
|
||||
case NotifyMessageType.orderWithDraw:
|
||||
return 'orderWithDraw';
|
||||
case NotifyMessageType.orderRefund:
|
||||
return 'orderRefund';
|
||||
case NotifyMessageType.groupNotifyCheck:
|
||||
@ -101,6 +105,8 @@ notifyMessageTypeFromString(String? type) {
|
||||
return NotifyMessageType.orderRecharge.name;
|
||||
case 'orderPay':
|
||||
return NotifyMessageType.orderPay.name;
|
||||
case 'orderWithDraw':
|
||||
return NotifyMessageType.orderWithDraw.name;
|
||||
case 'orderRefund':
|
||||
return NotifyMessageType.orderRefund.name;
|
||||
case 'groupNotifyCheck':
|
||||
|
||||
@ -7,7 +7,7 @@ extension ShareTypeExtension on ShareType {
|
||||
case ShareType.video:
|
||||
return 'https://wuzhongjie.com.cn/spa/video-detail';
|
||||
case ShareType.shop:
|
||||
return 'https://wuzhongjie.com.cn/spa/goods-derail';
|
||||
return 'https://wuzhongjie.com.cn/spa/goods-detail';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,8 +9,11 @@ import 'package:get/get.dart';
|
||||
import 'package:loopin/IM/im_core.dart' as im_core;
|
||||
import 'package:loopin/IM/im_service.dart';
|
||||
import 'package:loopin/api/common_api.dart';
|
||||
import 'package:loopin/components/my_toast.dart';
|
||||
import 'package:loopin/components/web_page.dart';
|
||||
import 'package:loopin/controller/video_module_controller.dart';
|
||||
import 'package:loopin/service/http.dart';
|
||||
import 'package:loopin/styles/index.dart';
|
||||
import 'package:loopin/utils/storage.dart';
|
||||
import 'package:shirne_dialog/shirne_dialog.dart';
|
||||
|
||||
@ -25,12 +28,12 @@ class Login extends StatefulWidget {
|
||||
|
||||
class _LoginState extends State<Login> {
|
||||
final Map authObj = {'phonenumber': '', 'smsCode': '', 'clientId': '428a8310cd442757ae699df5d894f051', 'grantType': 'sms'};
|
||||
|
||||
bool isChecked = false;
|
||||
final fieldController = TextEditingController();
|
||||
Timer? timer;
|
||||
String vcodeText = '获取验证码';
|
||||
bool disabled = false;
|
||||
int time = 6;
|
||||
int time = 60;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -54,6 +57,10 @@ class _LoginState extends State<Login> {
|
||||
// 提交表单
|
||||
void handleSubmit() async {
|
||||
FocusScope.of(context).unfocus(); // 收起键盘
|
||||
if (!isChecked) {
|
||||
MyToast().tip(title: '未勾选隐私协议');
|
||||
return;
|
||||
}
|
||||
if (authObj['phonenumber'] == '') {
|
||||
MyDialog.toast('手机号不能为空', icon: Icon(Icons.warning), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200)));
|
||||
} else if (!Utils.checkTel(authObj['phonenumber'])) {
|
||||
@ -112,6 +119,7 @@ class _LoginState extends State<Login> {
|
||||
void handleVcode() {
|
||||
logger.i(authObj);
|
||||
FocusScope.of(context).unfocus(); // 收起键盘
|
||||
|
||||
if (authObj['phonenumber'] == '') {
|
||||
MyDialog.toast('手机号不能为空', icon: Icon(Icons.warning), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200)));
|
||||
} else if (!Utils.checkTel(authObj['phonenumber'])) {
|
||||
@ -156,7 +164,8 @@ class _LoginState extends State<Login> {
|
||||
forceMaterialTransparency: true,
|
||||
toolbarHeight: 0,
|
||||
),
|
||||
body: Container(
|
||||
body: SingleChildScrollView(
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
margin: const EdgeInsets.only(top: 50.0),
|
||||
child: Column(
|
||||
@ -299,12 +308,66 @@ class _LoginState extends State<Login> {
|
||||
),
|
||||
),
|
||||
)),
|
||||
const SizedBox(
|
||||
height: 10.0,
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(24.0, 0.0, 24.0, 0.0),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end, // 顶部对齐
|
||||
children: [
|
||||
Transform.scale(
|
||||
scale: 0.8, // 调整 Checkbox 大小,使和文字高度接近
|
||||
child: Checkbox(
|
||||
value: isChecked,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
isChecked = value ?? false;
|
||||
});
|
||||
},
|
||||
shape: const CircleBorder(),
|
||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
activeColor: FStyle.primaryColor,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
logger.w(3555);
|
||||
FocusScope.of(context).unfocus();
|
||||
Get.to(
|
||||
() => SafeArea(
|
||||
child: WebPage(url: 'https://www.wuzhongjie.com.cn/download/yinsizhengce.html', title: '隐私协议'),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Text.rich(
|
||||
TextSpan(
|
||||
text: "未注册的手机号验证后将自动创建用户账号,登录即代表您已同意",
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey,
|
||||
height: 1.1,
|
||||
),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: "《使用条款及隐私协议》",
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: FStyle.primaryColor,
|
||||
height: 1.2,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
));
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,21 +8,31 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:loopin/IM/controller/chat_controller.dart';
|
||||
import 'package:loopin/IM/controller/chat_detail_controller.dart';
|
||||
import 'package:loopin/IM/controller/im_user_info_controller.dart';
|
||||
import 'package:loopin/IM/im_message.dart';
|
||||
import 'package:loopin/IM/im_result.dart';
|
||||
import 'package:loopin/IM/im_service.dart';
|
||||
import 'package:loopin/api/common_api.dart';
|
||||
import 'package:loopin/components/animation.dart';
|
||||
import 'package:loopin/components/image_viewer.dart';
|
||||
import 'package:loopin/components/my_toast.dart';
|
||||
import 'package:loopin/components/network_or_asset_image.dart';
|
||||
import 'package:loopin/components/preview_video.dart';
|
||||
import 'package:loopin/models/summary_type.dart';
|
||||
import 'package:loopin/pages/chat/components/reportChat.dart';
|
||||
import 'package:loopin/service/http.dart';
|
||||
import 'package:loopin/utils/audio_player_service.dart';
|
||||
import 'package:loopin/utils/permissions.dart';
|
||||
import 'package:loopin/utils/snapshot.dart';
|
||||
import 'package:loopin/utils/voice_service.dart';
|
||||
import 'package:mime/mime.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:shirne_dialog/shirne_dialog.dart';
|
||||
import 'package:tencent_cloud_chat_sdk/enum/message_status.dart';
|
||||
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart';
|
||||
import 'package:tencent_cloud_chat_sdk/models/v2_tim_friend_info.dart';
|
||||
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
import 'package:wechat_assets_picker/wechat_assets_picker.dart';
|
||||
@ -43,11 +53,14 @@ class Chat extends StatefulWidget {
|
||||
|
||||
class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
|
||||
final ImagePicker _picker = ImagePicker();
|
||||
bool hasMicPermission = false; //麦克风权限
|
||||
|
||||
late final ChatDetailController controller;
|
||||
// 接收参数
|
||||
late final Rx<V2TimConversation> arguments;
|
||||
|
||||
String blackTxt = '拉黑';
|
||||
|
||||
late String selfUserId;
|
||||
|
||||
// 聊天消息模块
|
||||
@ -81,7 +94,7 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
|
||||
List chooseOptions = [
|
||||
{'key': 'photo', 'name': '相册', 'icon': 'assets/images/icon_photo.webp'},
|
||||
{'key': 'camera', 'name': '拍摄', 'icon': 'assets/images/icon_camera.webp'},
|
||||
{'key': 'location', 'name': '位置', 'icon': 'assets/images/icon_location.webp'},
|
||||
// {'key': 'location', 'name': '位置', 'icon': 'assets/images/icon_location.webp'},
|
||||
{'key': 'redpacket', 'name': '红包', 'icon': 'assets/images/icon_hb.webp'},
|
||||
];
|
||||
|
||||
@ -157,6 +170,65 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
// 获取拉黑列表
|
||||
Future<void> getBlackList() async {
|
||||
final res = await ImService.instance.blackList();
|
||||
if (res.success && res.data != null) {
|
||||
//
|
||||
final data = res.data;
|
||||
for (V2TimFriendInfo element in data ?? []) {
|
||||
logger.w(element.toJson());
|
||||
if (element.userID == arguments.value.userID) {
|
||||
// 在拉黑列表
|
||||
logger.e('匹配');
|
||||
blackTxt = '取消拉黑';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 取消拉黑
|
||||
void _cancelBlack() async {
|
||||
logger.w('开始执行取消拉黑${arguments.value.userID}');
|
||||
final res = await ImService.instance.deleteFromBlackList(userIDList: [arguments.value.userID ?? '']);
|
||||
logger.w(res.success);
|
||||
// 成功后返回并删除会话
|
||||
if (res.success) {
|
||||
blackTxt = '拉黑';
|
||||
Get.back();
|
||||
}
|
||||
}
|
||||
|
||||
// 拉黑
|
||||
void _addBlack() async {
|
||||
logger.w('开始执行拉黑${arguments.value.userID}');
|
||||
final res = await ImService.instance.addToBlackList(userIDList: [arguments.value.userID ?? '']);
|
||||
logger.w(res.success);
|
||||
// 成功后返回并删除会话
|
||||
if (res.success) {
|
||||
blackTxt = '取消拉黑';
|
||||
//删除会话,退出聊天
|
||||
await ImService.instance.deleteConversation(conversationID: 'c2c_${arguments.value.userID}');
|
||||
final ctl = Get.find<ChatController>();
|
||||
ctl.chatList.removeWhere(
|
||||
(conv) => conv.conversation.conversationID == 'c2c_${arguments.value.userID}',
|
||||
);
|
||||
ctl.chatList.refresh();
|
||||
Get.back();
|
||||
Get.back();
|
||||
}
|
||||
}
|
||||
|
||||
//拉黑/取消
|
||||
void _handleBlack() {
|
||||
if (blackTxt == '拉黑') {
|
||||
//_addBlack
|
||||
_addBlack();
|
||||
} else {
|
||||
_cancelBlack();
|
||||
}
|
||||
}
|
||||
|
||||
// 设置好友备注
|
||||
void setRemark() async {
|
||||
String remark = '';
|
||||
@ -285,6 +357,7 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
|
||||
List<Widget> renderChatList() {
|
||||
List<Widget> msgtpl = [];
|
||||
for (var item in controller.chatList) {
|
||||
// logger.e(item.status);
|
||||
// 时间提示,公告提示
|
||||
if (item.localCustomData == 'time_label') {
|
||||
msgtpl.add(Container(
|
||||
@ -322,6 +395,18 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
|
||||
msgtpl.add(
|
||||
RenderChatItem(
|
||||
data: item,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (item.status == MessageStatus.V2TIM_MSG_STATUS_SEND_FAIL) ...[
|
||||
Icon(
|
||||
Icons.error,
|
||||
color: Colors.red,
|
||||
size: 18,
|
||||
),
|
||||
SizedBox(width: 4),
|
||||
],
|
||||
Flexible(
|
||||
child: Ink(
|
||||
decoration: BoxDecoration(
|
||||
color: !(item.isSelf ?? false) ? Color(0xFFFFFFFF) : Color(0xFF89E45B),
|
||||
@ -332,7 +417,10 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
|
||||
borderRadius: BorderRadius.circular(10.0),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: RichTextUtil.getRichText(item.textElem?.text ?? '', color: !(item.isSelf ?? false) ? Colors.black : Colors.white), // 可自定义解析emoj/网址/电话
|
||||
child: RichTextUtil.getRichText(
|
||||
item.textElem?.text ?? '',
|
||||
color: !(item.isSelf ?? false) ? Colors.black : Colors.white,
|
||||
), // 可自定义解析emoj/网址/电话
|
||||
),
|
||||
onLongPress: () {
|
||||
contextMenuDialog();
|
||||
@ -340,13 +428,27 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
// gif表情模板=8
|
||||
else if (item.elemType == 8) {
|
||||
msgtpl.add(RenderChatItem(
|
||||
data: item,
|
||||
child: Ink(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (item.status == MessageStatus.V2TIM_MSG_STATUS_SEND_FAIL) ...[
|
||||
Icon(
|
||||
Icons.error,
|
||||
color: Colors.red,
|
||||
size: 18,
|
||||
),
|
||||
SizedBox(width: 4),
|
||||
],
|
||||
Ink(
|
||||
child: InkWell(
|
||||
overlayColor: WidgetStateProperty.all(Colors.transparent),
|
||||
child: Container(
|
||||
@ -362,6 +464,8 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
));
|
||||
}
|
||||
// 图片模板=3
|
||||
@ -371,7 +475,18 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
|
||||
List<String> imagePaths = originImage != null ? [originImage.url!] : [];
|
||||
msgtpl.add(RenderChatItem(
|
||||
data: item,
|
||||
child: Ink(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (item.status == MessageStatus.V2TIM_MSG_STATUS_SEND_FAIL) ...[
|
||||
Icon(
|
||||
Icons.error,
|
||||
color: Colors.red,
|
||||
size: 18,
|
||||
),
|
||||
SizedBox(width: 4),
|
||||
],
|
||||
Ink(
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
// 预览图片
|
||||
@ -402,7 +517,8 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
|
||||
color: Colors.grey[300],
|
||||
alignment: Alignment.center,
|
||||
child: CircularProgressIndicator(
|
||||
value: loadingProgress.expectedTotalBytes != null ? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes! : null,
|
||||
value:
|
||||
loadingProgress.expectedTotalBytes != null ? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes! : null,
|
||||
),
|
||||
);
|
||||
},
|
||||
@ -420,6 +536,8 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
));
|
||||
}
|
||||
// 视频模板=5
|
||||
@ -427,7 +545,18 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
|
||||
// print(item.videoElem!.toLogString());
|
||||
msgtpl.add(RenderChatItem(
|
||||
data: item,
|
||||
child: Ink(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (item.status == MessageStatus.V2TIM_MSG_STATUS_SEND_FAIL) ...[
|
||||
Icon(
|
||||
Icons.error,
|
||||
color: Colors.red,
|
||||
size: 18,
|
||||
),
|
||||
SizedBox(width: 4),
|
||||
],
|
||||
Ink(
|
||||
child: InkWell(
|
||||
overlayColor: WidgetStateProperty.all(Colors.transparent),
|
||||
child: SizedBox(
|
||||
@ -440,6 +569,7 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
|
||||
child: NetworkOrAssetImage(
|
||||
imageUrl: item.videoElem?.snapshotUrl ?? '',
|
||||
width: 120,
|
||||
placeholderAsset: 'assets/images/bk.jpg',
|
||||
),
|
||||
),
|
||||
const Align(
|
||||
@ -478,6 +608,8 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
));
|
||||
}
|
||||
// 语音模板=4
|
||||
@ -487,6 +619,17 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
|
||||
final maxWidth = (durationSeconds / 60 * 230).clamp(80.0, 230.0);
|
||||
|
||||
List<Widget> audiobody = [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (item.status == MessageStatus.V2TIM_MSG_STATUS_SEND_FAIL) ...[
|
||||
Icon(
|
||||
Icons.error,
|
||||
color: Colors.red,
|
||||
size: 18,
|
||||
),
|
||||
SizedBox(width: 4),
|
||||
],
|
||||
Ink(
|
||||
decoration: BoxDecoration(
|
||||
color: !(item.isSelf ?? false) ? const Color(0xFFFFFFFF) : const Color(0xFF89E45B),
|
||||
@ -551,6 +694,8 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
width: 5.0,
|
||||
),
|
||||
@ -597,7 +742,18 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
|
||||
// 这里带上分享人的ID
|
||||
Get.toNamed('/goods', arguments: {'goodsId': goodsId, 'userID': userID});
|
||||
},
|
||||
child: Container(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (item.status == MessageStatus.V2TIM_MSG_STATUS_SEND_FAIL) ...[
|
||||
Icon(
|
||||
Icons.error,
|
||||
color: Colors.red,
|
||||
size: 18,
|
||||
),
|
||||
SizedBox(width: 4),
|
||||
],
|
||||
Container(
|
||||
width: 160,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(15.0), boxShadow: [
|
||||
@ -613,6 +769,7 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
|
||||
NetworkOrAssetImage(
|
||||
imageUrl: url,
|
||||
width: 160.0,
|
||||
placeholderAsset: 'assets/images/bk.jpg',
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 5.0),
|
||||
@ -658,6 +815,8 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
));
|
||||
}
|
||||
@ -672,9 +831,21 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
|
||||
final width = obj['width'] as num;
|
||||
final height = obj['height'] as num;
|
||||
final isHorizontal = width > height;
|
||||
msgtpl.add(RenderChatItem(
|
||||
msgtpl.add(
|
||||
RenderChatItem(
|
||||
data: item,
|
||||
child: Ink(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (item.status == MessageStatus.V2TIM_MSG_STATUS_SEND_FAIL) ...[
|
||||
Icon(
|
||||
Icons.error,
|
||||
color: Colors.red,
|
||||
size: 18,
|
||||
),
|
||||
SizedBox(width: 4),
|
||||
],
|
||||
Ink(
|
||||
child: InkWell(
|
||||
overlayColor: WidgetStateProperty.all(Colors.transparent),
|
||||
child: SizedBox(
|
||||
@ -735,7 +906,10 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
|
||||
// },
|
||||
),
|
||||
),
|
||||
));
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
// 红包模板=自定义=2;
|
||||
else if (item.elemType == 2 && item.cloudCustomData == SummaryType.hongbao) {
|
||||
@ -745,7 +919,18 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
|
||||
// final maxNum = obj['maxNum'];
|
||||
msgtpl.add(RenderChatItem(
|
||||
data: item,
|
||||
child: Ink(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (item.status == MessageStatus.V2TIM_MSG_STATUS_SEND_FAIL) ...[
|
||||
Icon(
|
||||
Icons.error,
|
||||
color: Colors.red,
|
||||
size: 18,
|
||||
),
|
||||
SizedBox(width: 4),
|
||||
],
|
||||
Ink(
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFFF7F43),
|
||||
borderRadius: BorderRadius.circular(10.0),
|
||||
@ -805,6 +990,8 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
));
|
||||
}
|
||||
// 位置模板=7
|
||||
@ -1088,7 +1275,7 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
|
||||
}
|
||||
|
||||
// 发送消息队列
|
||||
Future<void> sendMessage(message) async {
|
||||
Future<void> sendMessage(message, {bool? isHb}) async {
|
||||
// 待插入的消息
|
||||
List<V2TimMessage> messagesToInsert = [];
|
||||
V2TimMessage? lastRealMsg;
|
||||
@ -1134,9 +1321,15 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
|
||||
// controller.chatList.addAll(messagesToInsert);
|
||||
|
||||
controller.scrollToBottom();
|
||||
print('发送成功');
|
||||
logger.w('发送成功');
|
||||
if (isHb == true) {
|
||||
createHb(res.data);
|
||||
}
|
||||
} else {
|
||||
print('消息发送失败: ${res.code} - ${res.desc}');
|
||||
logger.e('消息发送失败: ${res.code} - ${res.desc}');
|
||||
if (res.code == 20007) {
|
||||
MyToast().tip(title: '您已被对方拉黑,无法发送消息', position: 'top');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1210,26 +1403,69 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
|
||||
|
||||
// 发红包消息
|
||||
void sendHongbao(date) async {
|
||||
final amount = date['amount']; //用户输入的金额
|
||||
final totalAmount = date['amount']; //用户输入的金额
|
||||
final remark = date['remark']; //用户输入的留言
|
||||
final maxNum = date['maxNum']; //红包数量
|
||||
final totalCount = date['maxNum']; //红包数量
|
||||
|
||||
// 检测钱包余额
|
||||
final resB = await Http.get(CommonApi.userAccount);
|
||||
final resData = resB['data']['wallet']; // 余额
|
||||
if (resData == null) {
|
||||
// balance.value = double.parse(resData);
|
||||
MyToast().tip(title: '余额不足');
|
||||
return;
|
||||
}
|
||||
if (double.parse(totalAmount) > double.parse(resData)) {
|
||||
MyToast().tip(title: '余额不足');
|
||||
return;
|
||||
}
|
||||
|
||||
// 先检测可用余额
|
||||
final makeJson = jsonEncode({
|
||||
"amount": amount,
|
||||
// "packetId": packRes['data']['packetId'],
|
||||
"packetId": '',
|
||||
"totalAmount": totalAmount,
|
||||
"remark": remark,
|
||||
"maxNum": maxNum,
|
||||
"totalCount": totalCount, // 数量
|
||||
"open": false,
|
||||
});
|
||||
final res = await IMMessage().createCustomMessage(data: makeJson);
|
||||
if (res.success && (res.data != null)) {
|
||||
final custMsg = res.data!.messageInfo;
|
||||
custMsg!.cloudCustomData = SummaryType.hongbao;
|
||||
sendMessage(res.data!.messageInfo);
|
||||
Get.back();
|
||||
sendMessage(res.data!.messageInfo, isHb: true);
|
||||
Navigator.pop(context);
|
||||
}
|
||||
}
|
||||
|
||||
//生成红包订单并写入红包单号
|
||||
void createHb(V2TimMessage msg) async {
|
||||
// final makeJson = jsonEncode({
|
||||
// "packetId": packRes['data']['packetId'],
|
||||
// "packetId": '',
|
||||
// "totalAmount": totalAmount,
|
||||
// "remark": remark,
|
||||
// "totalCount": totalCount, // 数量
|
||||
// "open": false,
|
||||
// });
|
||||
final data = jsonDecode(msg.customElem!.data!);
|
||||
|
||||
//生成红包订单
|
||||
final makeData = {
|
||||
"chatType": "1",
|
||||
"totalAmount": data['totalAmount'],
|
||||
"totalCount": data['totalCount'],
|
||||
"receiverId": arguments.value.userID,
|
||||
"remark": data['remark'],
|
||||
};
|
||||
logger.w(makeData);
|
||||
final packRes = await Http.post(CommonApi.redpacket, data: makeData);
|
||||
data['packetId'] = packRes['data']['packetId'];
|
||||
msg.customElem!.data = jsonEncode(data);
|
||||
// 修改消息体
|
||||
await ImService.instance.modifyMessage(message: msg);
|
||||
logger.e(packRes);
|
||||
}
|
||||
|
||||
// 发送图片消息=3
|
||||
void sendImage(imgPath) async {
|
||||
final resImg = await IMMessage().createImageMessage(imagePath: imgPath);
|
||||
@ -1341,6 +1577,11 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
|
||||
|
||||
/// 拍照片
|
||||
Future<void> _pickPhoto() async {
|
||||
final hasPer = await Permissions.requestCameraPermission(title: '相机权限使用说明', content: '聊天时发送拍摄的照片');
|
||||
if (!hasPer) {
|
||||
Permissions.showPermissionDialog('相机');
|
||||
return;
|
||||
}
|
||||
final XFile? photo = await _picker.pickImage(
|
||||
source: ImageSource.camera,
|
||||
maxWidth: 1920,
|
||||
@ -1355,6 +1596,11 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
|
||||
|
||||
/// 拍视频
|
||||
Future<void> _pickVideo() async {
|
||||
final hasPer = await Permissions.requestCameraPermission(title: '相机权限使用说明', content: '聊天时发送拍摄的视频');
|
||||
if (!hasPer) {
|
||||
Permissions.showPermissionDialog('相机');
|
||||
return;
|
||||
}
|
||||
final XFile? video = await _picker.pickVideo(
|
||||
source: ImageSource.camera,
|
||||
maxDuration: const Duration(minutes: 1),
|
||||
@ -1394,6 +1640,12 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
|
||||
|
||||
///从相册选取图片/视频
|
||||
void pickFile(BuildContext context) async {
|
||||
//
|
||||
final hasPer = await Permissions.requestVideoPermission();
|
||||
if (!hasPer) {
|
||||
Permissions.showPermissionDialog('相册');
|
||||
return;
|
||||
}
|
||||
final pickedAssets = await AssetPicker.pickAssets(
|
||||
context,
|
||||
pickerConfig: AssetPickerConfig(
|
||||
@ -1439,10 +1691,10 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
|
||||
if (file != null) {
|
||||
var fileSizeInBytes = await file.length();
|
||||
var sizeInMB = fileSizeInBytes / (1024 * 1024);
|
||||
if (sizeInMB > 28) {
|
||||
MyDialog.toast('图片大小不能超过28MB', icon: const Icon(Icons.check_circle), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200)));
|
||||
if (sizeInMB > 100) {
|
||||
MyDialog.toast('文件大小不能超过100MB', icon: const Icon(Icons.check_circle), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200)));
|
||||
} else {
|
||||
print("图片合法,大小:$sizeInMB MB");
|
||||
print("视频合法,大小:$sizeInMB MB");
|
||||
// 执行发送逻辑
|
||||
var snapshot = await generateVideoThumbnail(file.path);
|
||||
String? mimeType = await asset.mimeTypeAsync;
|
||||
@ -1479,7 +1731,8 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
|
||||
context: context,
|
||||
builder: (context) {
|
||||
final obj = jsonDecode(data.customElem!.data!);
|
||||
final amount = obj['amount'];
|
||||
final totalAmount = obj['totalAmount'];
|
||||
final packetId = obj['packetId'];
|
||||
final remark = obj['remark'];
|
||||
final open = obj['open'] ?? false;
|
||||
|
||||
@ -1513,7 +1766,7 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
Text(
|
||||
amount,
|
||||
totalAmount,
|
||||
style: const TextStyle(color: Color(0xFFFFF9C7), fontWeight: FontWeight.w500, fontSize: 20.0),
|
||||
),
|
||||
SizedBox(height: 20.0),
|
||||
@ -1524,7 +1777,7 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
|
||||
SizedBox(
|
||||
height: 100.0,
|
||||
),
|
||||
if (open == false && data.isSelf == false)
|
||||
if (open == false)
|
||||
AnimatedBuilder(
|
||||
animation: animTurns,
|
||||
builder: (context, child) {
|
||||
@ -1550,6 +1803,9 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
|
||||
// 执行消费红包动作
|
||||
//--------
|
||||
// 成功后修改消息体
|
||||
final ctl = Get.find<ImUserInfoController>();
|
||||
try {
|
||||
await Http.post(CommonApi.getpacket, data: {"packetId": packetId, "memberId": ctl.userID.value});
|
||||
obj['open'] = true; //成功标记为true
|
||||
data.customElem!.data = jsonEncode(obj);
|
||||
ImService.instance.modifyMessage(message: data);
|
||||
@ -1559,6 +1815,9 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
|
||||
animController.reset();
|
||||
Get.back();
|
||||
});
|
||||
} catch (e) {
|
||||
logger.e(e);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
@ -1624,6 +1883,25 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
|
||||
// );
|
||||
}
|
||||
|
||||
// 拉黑
|
||||
// void _handleBlack() async {
|
||||
// logger.w('开始执行拉黑${arguments.value.userID}');
|
||||
// final res = await ImService.instance.addToBlackList(userIDList: [arguments.value.userID ?? '']);
|
||||
// logger.w(res.success);
|
||||
// // 成功后返回并删除会话
|
||||
// if (res.success) {
|
||||
// //删除会话,退出聊天
|
||||
// await ImService.instance.deleteConversation(conversationID: arguments.value.conversationID);
|
||||
// final ctl = Get.find<ChatController>();
|
||||
// ctl.chatList.removeWhere(
|
||||
// (conv) => conv.conversation.conversationID == arguments.value.conversationID,
|
||||
// );
|
||||
// ctl.chatList.refresh();
|
||||
// Get.back();
|
||||
// Get.back();
|
||||
// }
|
||||
// }
|
||||
|
||||
// 发群红包弹窗
|
||||
void sendRedPacketDialog() {
|
||||
showModalBottomSheet(
|
||||
@ -1639,7 +1917,15 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
|
||||
builder: (context) {
|
||||
return RedPacket(
|
||||
flag: false,
|
||||
onSend: (date) {
|
||||
onSend: (date) async {
|
||||
// 检测是否被拉黑
|
||||
// final res = await ImService.instance.blackList();
|
||||
// if (res.success && res.data != null) {
|
||||
// //检查列表
|
||||
// final blackList = res.data;
|
||||
// blackList
|
||||
// }
|
||||
// ImService.instance.isMyFriend(arguments.value.userID!, FriendTypeEnum.V2TIM_FRIEND_TYPE_SINGLE);
|
||||
sendHongbao(date);
|
||||
});
|
||||
},
|
||||
@ -1674,7 +1960,7 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
|
||||
title: Obx(() {
|
||||
return Text(
|
||||
// '${arguments['title']}',
|
||||
'${arguments.value.showName}',
|
||||
arguments.value.showName ?? '神秘人',
|
||||
style: const TextStyle(fontSize: 18.0, fontFamily: 'Arial'),
|
||||
);
|
||||
}),
|
||||
@ -1693,6 +1979,7 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
|
||||
color: Colors.white,
|
||||
),
|
||||
onPressed: () async {
|
||||
await getBlackList();
|
||||
final paddingTop = MediaQuery.of(Get.context!).padding.top;
|
||||
|
||||
final selected = await showMenu(
|
||||
@ -1754,7 +2041,7 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
|
||||
Icon(Icons.block, color: Colors.white, size: 18),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
'拉黑',
|
||||
blackTxt,
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
],
|
||||
@ -1788,10 +2075,34 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
|
||||
// break;
|
||||
|
||||
case 'report':
|
||||
print('点击了举报');
|
||||
// print('点击了举报');
|
||||
Get.to(() => ReportChat(convsationID: arguments.value.conversationID));
|
||||
break;
|
||||
case 'block':
|
||||
print('点击了拉黑');
|
||||
// print('点击了拉黑');
|
||||
String showTxt = blackTxt == '拉黑' ? '拉黑后将不保留任何聊天记录' : '';
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
content: Text('确认要$blackTxt对方吗?$showTxt', style: TextStyle(fontSize: 16.0)),
|
||||
backgroundColor: Colors.white,
|
||||
surfaceTintColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15.0)),
|
||||
elevation: 2.0,
|
||||
actionsPadding: const EdgeInsets.all(15.0),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
},
|
||||
child: const Text('取消', style: TextStyle(color: Colors.black54)),
|
||||
),
|
||||
TextButton(onPressed: _handleBlack, child: const Text('确认', style: TextStyle(color: Colors.red))),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
break;
|
||||
// case 'foucs':
|
||||
// print('点击了取关');
|
||||
@ -1925,7 +2236,15 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
|
||||
),
|
||||
onPanStart: (details) async {
|
||||
// 开始录音
|
||||
logger.w('开始录音');
|
||||
|
||||
final hasPer = await Permission.microphone.status;
|
||||
logger.w('开始录音:${hasPer.toString()}');
|
||||
|
||||
if (hasPer.isGranted) {
|
||||
// 有权限直接开始
|
||||
setState(() {
|
||||
hasMicPermission = true;
|
||||
});
|
||||
final res = await VoiceService().startRecording();
|
||||
if (res) {
|
||||
setState(() {
|
||||
@ -1935,8 +2254,21 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
|
||||
} else {
|
||||
MyDialog.toast('未获得麦克风权限');
|
||||
}
|
||||
} else {
|
||||
// 去获取权限
|
||||
hasMicPermission = false;
|
||||
final granted = await Permissions.requestMicrophonePermission(
|
||||
title: '麦克风权限使用说明',
|
||||
content: '聊天时发送语音消息',
|
||||
);
|
||||
if (!granted) {
|
||||
Permissions.showPermissionDialog('麦克风');
|
||||
}
|
||||
return;
|
||||
}
|
||||
},
|
||||
onPanUpdate: (details) {
|
||||
if (!hasMicPermission) return;
|
||||
Offset pos = details.globalPosition;
|
||||
double swipeY = MediaQuery.of(context).size.height - 120;
|
||||
double swipeX = MediaQuery.of(context).size.width / 2 + 50;
|
||||
@ -1952,6 +2284,7 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
|
||||
},
|
||||
onPanEnd: (details) {
|
||||
logger.w('停止录音');
|
||||
if (!hasMicPermission) return;
|
||||
setState(() {
|
||||
switch (voiceType) {
|
||||
case 1:
|
||||
|
||||
@ -8,22 +8,30 @@ import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:loopin/IM/controller/chat_detail_controller.dart';
|
||||
import 'package:loopin/IM/controller/im_user_info_controller.dart';
|
||||
import 'package:loopin/IM/im_message.dart';
|
||||
import 'package:loopin/IM/im_result.dart';
|
||||
import 'package:loopin/IM/im_service.dart';
|
||||
import 'package:loopin/api/common_api.dart';
|
||||
import 'package:loopin/components/image_viewer.dart';
|
||||
import 'package:loopin/components/my_toast.dart';
|
||||
import 'package:loopin/components/network_or_asset_image.dart';
|
||||
import 'package:loopin/components/preview_video.dart';
|
||||
import 'package:loopin/models/summary_type.dart';
|
||||
import 'package:loopin/pages/groupChat/controller/group_detail_controller.dart';
|
||||
import 'package:loopin/pages/groupChat/groupDetail.dart';
|
||||
import 'package:loopin/service/http.dart';
|
||||
import 'package:loopin/utils/audio_player_service.dart';
|
||||
import 'package:loopin/utils/parse_message_summary.dart';
|
||||
import 'package:loopin/utils/permissions.dart';
|
||||
import 'package:loopin/utils/snapshot.dart';
|
||||
import 'package:loopin/utils/voice_service.dart';
|
||||
import 'package:mime/mime.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:shirne_dialog/shirne_dialog.dart';
|
||||
import 'package:tencent_cloud_chat_sdk/enum/group_type.dart';
|
||||
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart';
|
||||
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_info.dart';
|
||||
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
import 'package:wechat_assets_picker/wechat_assets_picker.dart';
|
||||
@ -42,6 +50,7 @@ class ChatGroup extends StatefulWidget {
|
||||
|
||||
class _ChatGroupState extends State<ChatGroup> with SingleTickerProviderStateMixin {
|
||||
final ImagePicker _picker = ImagePicker();
|
||||
bool hasMicPermission = false; // 麦克风权限
|
||||
|
||||
late final ChatDetailController controller;
|
||||
// 接收参数
|
||||
@ -79,7 +88,7 @@ class _ChatGroupState extends State<ChatGroup> with SingleTickerProviderStateMix
|
||||
List chooseOptions = [
|
||||
{'key': 'photo', 'name': '相册', 'icon': 'assets/images/icon_photo.webp'},
|
||||
{'key': 'camera', 'name': '拍摄', 'icon': 'assets/images/icon_camera.webp'},
|
||||
{'key': 'location', 'name': '位置', 'icon': 'assets/images/icon_location.webp'},
|
||||
// {'key': 'location', 'name': '位置', 'icon': 'assets/images/icon_location.webp'},
|
||||
{'key': 'redpacket', 'name': '红包', 'icon': 'assets/images/icon_hb.webp'},
|
||||
];
|
||||
|
||||
@ -377,6 +386,7 @@ class _ChatGroupState extends State<ChatGroup> with SingleTickerProviderStateMix
|
||||
borderRadius: BorderRadius.circular(10.0),
|
||||
child: NetworkOrAssetImage(
|
||||
imageUrl: item.videoElem?.snapshotUrl ?? '',
|
||||
placeholderAsset: 'assets/images/bk.jpg',
|
||||
width: 120,
|
||||
),
|
||||
),
|
||||
@ -535,6 +545,7 @@ class _ChatGroupState extends State<ChatGroup> with SingleTickerProviderStateMix
|
||||
children: [
|
||||
NetworkOrAssetImage(
|
||||
imageUrl: url,
|
||||
placeholderAsset: 'assets/images/bk.jpg',
|
||||
width: 160.0,
|
||||
),
|
||||
Container(
|
||||
@ -662,10 +673,16 @@ class _ChatGroupState extends State<ChatGroup> with SingleTickerProviderStateMix
|
||||
}
|
||||
// 红包模板=自定义=2;
|
||||
else if (item.elemType == 2 && item.cloudCustomData == SummaryType.hongbao) {
|
||||
// 定位
|
||||
final selfId = Get.find<ImUserInfoController>().userID.value;
|
||||
final obj = jsonDecode(item.customElem!.data!);
|
||||
final open = obj['open'] ?? false;
|
||||
final open = obj['open'] ?? false; // 默认未开启,总开关,直到消费完才改成true;
|
||||
final remark = obj['remark'];
|
||||
// final maxNum = obj['maxNum'];
|
||||
final totalCount = obj['totalCount']; // 红包数量
|
||||
String idsString = obj['ids'] ?? '';
|
||||
final ids = idsString.isEmpty ? <String>[] : idsString.split(',');
|
||||
// 自己是否抢过了
|
||||
final hasGet = ids.contains(selfId);
|
||||
msgtpl.add(RenderChatItem(
|
||||
data: item,
|
||||
child: Ink(
|
||||
@ -688,7 +705,7 @@ class _ChatGroupState extends State<ChatGroup> with SingleTickerProviderStateMix
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
open
|
||||
open || hasGet
|
||||
? Icon(Icons.check_circle, size: 32.0, color: Colors.white70)
|
||||
: Image.asset(
|
||||
'assets/images/hbico.png',
|
||||
@ -1151,17 +1168,50 @@ class _ChatGroupState extends State<ChatGroup> with SingleTickerProviderStateMix
|
||||
|
||||
// 发红包消息
|
||||
void sendHongbao(date) async {
|
||||
final amount = date['amount']; //用户输入的金额
|
||||
final totalAmount = date['amount']; //用户输入的金额
|
||||
final remark = date['remark']; //用户输入的留言
|
||||
final maxNum = date['maxNum']; //红包数量
|
||||
final totalCount = date['maxNum']; //红包数量
|
||||
|
||||
// 先检测可用余额
|
||||
final makeJson = jsonEncode({
|
||||
"amount": amount,
|
||||
|
||||
final makeData = {
|
||||
"chatType": "2",
|
||||
"totalAmount": totalAmount,
|
||||
"totalCount": totalCount,
|
||||
"groupId": arguments.groupID,
|
||||
"remark": remark,
|
||||
"maxNum": maxNum,
|
||||
"open": false,
|
||||
};
|
||||
|
||||
// 检测钱包余额
|
||||
final resB = await Http.get(CommonApi.userAccount);
|
||||
final resData = resB['data']['wallet']; // 余额
|
||||
if (resData == null) {
|
||||
// balance.value = double.parse(resData);
|
||||
MyToast().tip(title: '余额不足');
|
||||
return;
|
||||
}
|
||||
if (double.parse(totalAmount) > double.parse(resData)) {
|
||||
MyToast().tip(title: '余额不足');
|
||||
return;
|
||||
}
|
||||
final packRes = await Http.post(CommonApi.redpacket, data: makeData);
|
||||
logger.e('');
|
||||
logger.w('红包发送成功响应:$packRes');
|
||||
|
||||
final makeJson = jsonEncode({
|
||||
"packetId": packRes['data']['packetId'], // 红包id
|
||||
// "totalAmount": totalAmount,
|
||||
"totalAmount": packRes['data']['totalAmount'], //总金额
|
||||
"remark": remark,
|
||||
"totalCount": packRes['data']['totalCount'], // 数量
|
||||
// "totalCount": totalCount, // 数量
|
||||
"open": false, // 默认未开启
|
||||
"ids": '', //默认为空,记录抢红包人的数据,用,隔开
|
||||
});
|
||||
|
||||
logger.e('红包数据:$makeJson');
|
||||
logger.e('请求数据:$makeData');
|
||||
|
||||
final res = await IMMessage().createCustomMessage(data: makeJson);
|
||||
if (res.success && (res.data != null)) {
|
||||
final custMsg = res.data!.messageInfo;
|
||||
@ -1281,6 +1331,11 @@ class _ChatGroupState extends State<ChatGroup> with SingleTickerProviderStateMix
|
||||
|
||||
/// 拍照片
|
||||
Future<void> _pickPhoto() async {
|
||||
final hasPer = await Permissions.requestCameraPermission(title: '相机权限使用说明', content: '聊天时发送拍摄的照片');
|
||||
if (!hasPer) {
|
||||
Permissions.showPermissionDialog('相机');
|
||||
return;
|
||||
}
|
||||
final XFile? photo = await _picker.pickImage(
|
||||
source: ImageSource.camera,
|
||||
maxWidth: 1920,
|
||||
@ -1295,6 +1350,11 @@ class _ChatGroupState extends State<ChatGroup> with SingleTickerProviderStateMix
|
||||
|
||||
/// 拍视频
|
||||
Future<void> _pickVideo() async {
|
||||
final hasPer = await Permissions.requestCameraPermission(title: '相机权限使用说明', content: '聊天时发送拍摄的视频');
|
||||
if (!hasPer) {
|
||||
Permissions.showPermissionDialog('相机');
|
||||
return;
|
||||
}
|
||||
final XFile? video = await _picker.pickVideo(
|
||||
source: ImageSource.camera,
|
||||
maxDuration: const Duration(minutes: 1),
|
||||
@ -1419,9 +1479,18 @@ class _ChatGroupState extends State<ChatGroup> with SingleTickerProviderStateMix
|
||||
context: context,
|
||||
builder: (context) {
|
||||
final obj = jsonDecode(data.customElem!.data!);
|
||||
final amount = obj['amount'];
|
||||
final remark = obj['remark'];
|
||||
final totalAmount = obj['totalAmount'];
|
||||
final remark = obj['remark']; // 默认未开启,总开关,直到消费完才改成true;
|
||||
final open = obj['open'] ?? false;
|
||||
logger.e('开了吗$open');
|
||||
final packetId = obj['packetId'];
|
||||
|
||||
final selfId = Get.find<ImUserInfoController>().userID.value;
|
||||
final totalCount = obj['totalCount'] ?? '0'; // 红包数量
|
||||
String idsString = obj['ids'] ?? '';
|
||||
final ids = idsString.isEmpty ? <String>[] : idsString.split(',');
|
||||
// 自己是否抢过了
|
||||
final hasGet = ids.contains(selfId);
|
||||
|
||||
return Material(
|
||||
type: MaterialType.transparency,
|
||||
@ -1453,7 +1522,7 @@ class _ChatGroupState extends State<ChatGroup> with SingleTickerProviderStateMix
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
Text(
|
||||
amount,
|
||||
totalAmount,
|
||||
style: const TextStyle(color: Color(0xFFFFF9C7), fontWeight: FontWeight.w500, fontSize: 20.0),
|
||||
),
|
||||
SizedBox(height: 20.0),
|
||||
@ -1464,7 +1533,10 @@ class _ChatGroupState extends State<ChatGroup> with SingleTickerProviderStateMix
|
||||
SizedBox(
|
||||
height: 100.0,
|
||||
),
|
||||
if (open == false && data.isSelf == false)
|
||||
if (open == false) ...[
|
||||
// 总开未结束,一定显示
|
||||
if (hasGet == false)
|
||||
// 自己没领过才显示
|
||||
AnimatedBuilder(
|
||||
animation: animTurns,
|
||||
builder: (context, child) {
|
||||
@ -1490,20 +1562,63 @@ class _ChatGroupState extends State<ChatGroup> with SingleTickerProviderStateMix
|
||||
// 执行消费红包动作
|
||||
//--------
|
||||
// 成功后修改消息体
|
||||
obj['open'] = true; //成功标记为true
|
||||
final ctl = Get.find<ImUserInfoController>();
|
||||
logger.w(packetId);
|
||||
try {
|
||||
final res = await Http.post(CommonApi.getpacket, data: {"packetId": packetId, "memberId": ctl.userID.value});
|
||||
logger.e('开群红包$res');
|
||||
if (res != null) {
|
||||
// 是否还有可抢次数
|
||||
final canGetNum = totalCount - ids.length;
|
||||
if (canGetNum > 0) {
|
||||
// 还有可以抢的次数
|
||||
obj['totalCount'] = totalCount - 1;
|
||||
} else {
|
||||
// 消费完了
|
||||
obj['totalCount'] = 0;
|
||||
obj['open'] = true; //总开关标记为true
|
||||
}
|
||||
// 记录自己的id
|
||||
ids.add(selfId);
|
||||
obj['ids'] = ids.join(',');
|
||||
data.customElem!.data = jsonEncode(obj);
|
||||
ImService.instance.modifyMessage(message: data);
|
||||
// 修改消息体
|
||||
await ImService.instance.modifyMessage(message: data);
|
||||
// 模拟开红包逻辑,1 秒后停止动画
|
||||
Future.delayed(Duration(seconds: 1), () {
|
||||
animController.stop();
|
||||
animController.reset();
|
||||
Get.back();
|
||||
MyToast().tip(
|
||||
title: '恭喜!抢到了${res['data']['receiveAmount']}元',
|
||||
type: 'success',
|
||||
position: 'center',
|
||||
);
|
||||
});
|
||||
} else {
|
||||
// 接口报500红包被抢完,关闭红包弹窗
|
||||
obj['totalCount'] = 0;
|
||||
obj['open'] = true; //总开关标记为true
|
||||
data.customElem!.data = jsonEncode(obj);
|
||||
await ImService.instance.modifyMessage(message: data);
|
||||
|
||||
Future.delayed(Duration(seconds: 1), () {
|
||||
animController.stop();
|
||||
animController.reset();
|
||||
Navigator.pop(context);
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
logger.e(e);
|
||||
logger.e('开红包失败');
|
||||
Get.back();
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -1565,7 +1680,14 @@ class _ChatGroupState extends State<ChatGroup> with SingleTickerProviderStateMix
|
||||
}
|
||||
|
||||
// 发群红包弹窗
|
||||
void sendRedPacketDialog() {
|
||||
void sendRedPacketDialog() async {
|
||||
final max = await getGroupMaxNumber();
|
||||
if (max == -1) {
|
||||
MyToast().tip(title: '获取群资料失败');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
showModalBottomSheet(
|
||||
backgroundColor: Colors.grey[50],
|
||||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(15.0))),
|
||||
@ -1579,11 +1701,27 @@ class _ChatGroupState extends State<ChatGroup> with SingleTickerProviderStateMix
|
||||
builder: (context) {
|
||||
return RedPacket(
|
||||
flag: true,
|
||||
maxNum: max,
|
||||
onSend: (date) {
|
||||
sendHongbao(date);
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// 获取群资料
|
||||
Future<int> getGroupMaxNumber() async {
|
||||
final res = await ImService.instance.getGroupsInfo(groupIDList: [arguments.groupID!]);
|
||||
if (res.success && res.data != null) {
|
||||
V2TimGroupInfo info = res.data!.first.groupInfo ?? V2TimGroupInfo(groupID: '', groupType: GroupType.Work);
|
||||
if (info.groupID.isEmpty) {
|
||||
return -1;
|
||||
}
|
||||
return info.memberCount ?? -1;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> isInGroup() async {
|
||||
@ -1603,6 +1741,7 @@ class _ChatGroupState extends State<ChatGroup> with SingleTickerProviderStateMix
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
//editorFocusNode
|
||||
|
||||
return Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
@ -1625,7 +1764,7 @@ class _ChatGroupState extends State<ChatGroup> with SingleTickerProviderStateMix
|
||||
),
|
||||
titleSpacing: 1.0,
|
||||
title: Text(
|
||||
'${arguments.showName}',
|
||||
arguments.showName ?? '群聊',
|
||||
style: const TextStyle(fontSize: 18.0, fontFamily: 'Arial'),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
@ -1797,6 +1936,14 @@ class _ChatGroupState extends State<ChatGroup> with SingleTickerProviderStateMix
|
||||
),
|
||||
onPanStart: (details) async {
|
||||
// 开始录音
|
||||
final hasPer = await Permission.microphone.status;
|
||||
logger.w('开始录音:${hasPer.toString()}');
|
||||
|
||||
if (hasPer.isGranted) {
|
||||
// 有权限直接开始
|
||||
setState(() {
|
||||
hasMicPermission = true;
|
||||
});
|
||||
final res = await VoiceService().startRecording();
|
||||
if (res) {
|
||||
setState(() {
|
||||
@ -1806,8 +1953,22 @@ class _ChatGroupState extends State<ChatGroup> with SingleTickerProviderStateMix
|
||||
} else {
|
||||
MyDialog.toast('未获得麦克风权限');
|
||||
}
|
||||
} else {
|
||||
// 去获取权限
|
||||
hasMicPermission = false;
|
||||
final granted = await Permissions.requestMicrophonePermission(
|
||||
title: '麦克风权限使用说明',
|
||||
content: '聊天时发送语音消息',
|
||||
);
|
||||
if (!granted) {
|
||||
Permissions.showPermissionDialog('麦克风');
|
||||
}
|
||||
return;
|
||||
}
|
||||
},
|
||||
onPanUpdate: (details) {
|
||||
if (!hasMicPermission) return;
|
||||
|
||||
Offset pos = details.globalPosition;
|
||||
double swipeY = MediaQuery.of(context).size.height - 120;
|
||||
double swipeX = MediaQuery.of(context).size.width / 2 + 50;
|
||||
@ -1823,6 +1984,8 @@ class _ChatGroupState extends State<ChatGroup> with SingleTickerProviderStateMix
|
||||
},
|
||||
onPanEnd: (details) {
|
||||
// print('停止录音');
|
||||
if (!hasMicPermission) return;
|
||||
|
||||
setState(() {
|
||||
switch (voiceType) {
|
||||
case 1:
|
||||
|
||||
@ -77,7 +77,7 @@ class _ChatNoFriendState extends State<ChatNoFriend> with SingleTickerProviderSt
|
||||
List chooseOptions = [
|
||||
{'key': 'photo', 'name': '相册', 'icon': 'assets/images/icon_photo.webp'},
|
||||
{'key': 'camera', 'name': '拍摄', 'icon': 'assets/images/icon_camera.webp'},
|
||||
{'key': 'location', 'name': '位置', 'icon': 'assets/images/icon_location.webp'},
|
||||
// {'key': 'location', 'name': '位置', 'icon': 'assets/images/icon_location.webp'},
|
||||
{'key': 'redpacket', 'name': '红包', 'icon': 'assets/images/icon_hb.webp'},
|
||||
];
|
||||
|
||||
@ -441,6 +441,7 @@ class _ChatNoFriendState extends State<ChatNoFriend> with SingleTickerProviderSt
|
||||
child: NetworkOrAssetImage(
|
||||
imageUrl: item.videoElem?.snapshotUrl ?? '',
|
||||
width: 120,
|
||||
placeholderAsset: 'assets/images/bk.jpg',
|
||||
),
|
||||
),
|
||||
const Align(
|
||||
@ -605,6 +606,7 @@ class _ChatNoFriendState extends State<ChatNoFriend> with SingleTickerProviderSt
|
||||
children: [
|
||||
NetworkOrAssetImage(
|
||||
imageUrl: url,
|
||||
placeholderAsset: 'assets/images/bk.jpg',
|
||||
width: 160.0,
|
||||
),
|
||||
Container(
|
||||
@ -1515,7 +1517,7 @@ class _ChatNoFriendState extends State<ChatNoFriend> with SingleTickerProviderSt
|
||||
title: Obx(() {
|
||||
return Text(
|
||||
// '${arguments['title']}',
|
||||
'${arguments.value.showName}',
|
||||
arguments.value.showName ?? '陌生人',
|
||||
style: const TextStyle(fontSize: 18.0, fontFamily: 'Arial'),
|
||||
);
|
||||
}),
|
||||
|
||||
@ -3,12 +3,13 @@ library;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:loopin/components/my_toast.dart';
|
||||
import 'package:shirne_dialog/shirne_dialog.dart';
|
||||
|
||||
class RedPacket extends StatefulWidget {
|
||||
final bool flag; // true=群,false=单
|
||||
final void Function(Map<String, dynamic>)? onSend;
|
||||
final int? maxNum; // 红包最大数量
|
||||
final int? maxNum; // 限制红包最大数量
|
||||
const RedPacket({super.key, required this.flag, this.onSend, this.maxNum});
|
||||
|
||||
@override
|
||||
@ -22,6 +23,7 @@ class _RedPacketState extends State<RedPacket> {
|
||||
|
||||
String amount = '0.00';
|
||||
String remark = '恭喜发财,大吉大利';
|
||||
String outMax = ''; // 用户输入的数量
|
||||
// 限制只能输入数字和小数点,且最多两位小数
|
||||
final List<TextInputFormatter> _decimalInputFormatters = [
|
||||
FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d{0,2}')),
|
||||
@ -58,7 +60,7 @@ class _RedPacketState extends State<RedPacket> {
|
||||
const Text('红包个数'),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
maxLength: widget.maxNum,
|
||||
maxLength: widget.maxNum ?? 1, //默认为1
|
||||
controller: _maxNumController,
|
||||
textAlign: TextAlign.right,
|
||||
buildCounter: (_, {required currentLength, maxLength, required isFocused}) => null, // 隐藏计数器
|
||||
@ -66,8 +68,19 @@ class _RedPacketState extends State<RedPacket> {
|
||||
hintText: "填写个数", isDense: true, hintStyle: TextStyle(fontSize: 14.0), border: OutlineInputBorder(borderSide: BorderSide.none)),
|
||||
onChanged: (value) {
|
||||
// 输入的红包个数
|
||||
if (value.isEmpty) return;
|
||||
final intValue = int.tryParse(value) ?? 0;
|
||||
if (intValue > (widget.maxNum ?? 1)) {
|
||||
// 超过最大值,回退到最大值
|
||||
_maxNumController.text = widget.maxNum.toString();
|
||||
MyToast().tip(title: '当前群内人数为${widget.maxNum}人', position: 'top');
|
||||
// 光标移到末尾
|
||||
_maxNumController.selection = TextSelection.fromPosition(
|
||||
TextPosition(offset: _maxNumController.text.length),
|
||||
);
|
||||
}
|
||||
setState(() {
|
||||
remark = value;
|
||||
outMax = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
@ -169,12 +182,13 @@ class _RedPacketState extends State<RedPacket> {
|
||||
shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0))),
|
||||
),
|
||||
onPressed: () {
|
||||
FocusScope.of(context).unfocus();
|
||||
double amountDouble = double.tryParse(amount) ?? 0.0;
|
||||
if (amountDouble > 0) {
|
||||
//发送红包
|
||||
widget.onSend!(
|
||||
{
|
||||
'maxNum': widget.maxNum ?? 1,
|
||||
'maxNum': outMax.isEmpty ? '1' : outMax,
|
||||
'amount': amount,
|
||||
'remark': remark,
|
||||
},
|
||||
|
||||
249
lib/pages/chat/components/reportChat.dart
Normal file
@ -0,0 +1,249 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:loopin/api/video_api.dart';
|
||||
import 'package:loopin/behavior/custom_scroll_behavior.dart';
|
||||
import 'package:loopin/components/my_toast.dart';
|
||||
import 'package:loopin/service/http.dart';
|
||||
import 'package:loopin/utils/getCommonDictionary.dart';
|
||||
|
||||
class ReportChat extends StatefulWidget {
|
||||
final String convsationID;
|
||||
const ReportChat({super.key, required this.convsationID});
|
||||
|
||||
@override
|
||||
State<ReportChat> createState() => _ReportChatState();
|
||||
}
|
||||
|
||||
class _ReportChatState extends State<ReportChat> with SingleTickerProviderStateMixin {
|
||||
int? _selectedIndex;
|
||||
late dynamic args;
|
||||
final TextEditingController _reportController = TextEditingController();
|
||||
final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey();
|
||||
List<dynamic> reasonTypeData = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
args = Get.arguments ?? {};
|
||||
getReportReasons('ums_chat_report'); // 获取投诉枚举
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_reportController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
// 加载数据的方法
|
||||
Future<void> getReportReasons(String key) async {
|
||||
try {
|
||||
final data = await Commondictionary.getCommonDictionary(key);
|
||||
setState(() {
|
||||
reasonTypeData = data;
|
||||
});
|
||||
} catch (e) {
|
||||
print('加载失败: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// 举报
|
||||
Future<void> doReport(String dictValue, String reasonText) async {
|
||||
try {
|
||||
final res = await Http.post(VideoApi.reportVideoApi, data: {
|
||||
'type': '1', // 类型 1 举报 2 投诉 3 建议
|
||||
'content': reasonText,
|
||||
'aimId': widget.convsationID,
|
||||
'aimType': '5', // 1 会员 2 群组 3 评论 4 视频 5 聊天
|
||||
'reasonType': dictValue
|
||||
});
|
||||
if (res['code'] == 200) {
|
||||
// 投诉成功,返回视频页面
|
||||
MyToast().tip(
|
||||
title: '投诉成功',
|
||||
position: 'center',
|
||||
type: 'success',
|
||||
);
|
||||
Get.back(result: {'returnTo': '/'});
|
||||
} else {
|
||||
MyToast().tip(
|
||||
title: '投诉失败',
|
||||
position: 'center',
|
||||
type: 'error',
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
print('投诉失败: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
key: scaffoldKey,
|
||||
backgroundColor: Colors.white,
|
||||
appBar: AppBar(
|
||||
centerTitle: true,
|
||||
backgroundColor: Colors.white,
|
||||
foregroundColor: Colors.black,
|
||||
elevation: 0,
|
||||
title: const Text(
|
||||
'举报',
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
body: ScrollConfiguration(
|
||||
behavior: CustomScrollBehavior().copyWith(scrollbars: false),
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'您的举报我们将尽快受理,核实后我们将第一时间告知受理结果,请尽量提交完整的举报描述',
|
||||
style: TextStyle(fontSize: 13, color: Colors.grey, height: 1.4),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
// 两列布局的投诉选项
|
||||
reasonTypeData.isEmpty
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: GridView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 2,
|
||||
crossAxisSpacing: 8,
|
||||
mainAxisSpacing: 2,
|
||||
childAspectRatio: 4,
|
||||
),
|
||||
itemCount: reasonTypeData.length,
|
||||
itemBuilder: (context, index) {
|
||||
return _buildRadioItem(index);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
const Text(
|
||||
'举报描述(选填)',
|
||||
style: TextStyle(fontSize: 14, color: Colors.black54, fontWeight: FontWeight.w500),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.grey.shade300),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: TextField(
|
||||
controller: _reportController,
|
||||
maxLines: 5,
|
||||
maxLength: 32,
|
||||
cursorColor: Colors.black54,
|
||||
style: const TextStyle(
|
||||
color: Colors.black54,
|
||||
fontSize: 14,
|
||||
),
|
||||
decoration: const InputDecoration(
|
||||
contentPadding: EdgeInsets.all(12),
|
||||
border: InputBorder.none,
|
||||
hintText: '请输入内容',
|
||||
hintStyle: TextStyle(color: Colors.grey, fontSize: 13),
|
||||
),
|
||||
),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Text(
|
||||
'${_reportController.text.length}/32',
|
||||
style: const TextStyle(fontSize: 12, color: Colors.grey),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: _selectedIndex != null ? _submitReport : null,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.red,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
),
|
||||
child: const Text('提交', style: TextStyle(fontSize: 15)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRadioItem(int index) {
|
||||
final item = reasonTypeData[index];
|
||||
final isSelected = _selectedIndex == index;
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_selectedIndex = index;
|
||||
});
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 20,
|
||||
height: 20,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: isSelected ? Colors.green : Colors.grey,
|
||||
width: 1.5,
|
||||
),
|
||||
),
|
||||
child: isSelected
|
||||
? Center(
|
||||
child: Container(
|
||||
width: 12,
|
||||
height: 12,
|
||||
decoration: const BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.green,
|
||||
),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
item['dictLabel'] ?? '未知原因',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: isSelected ? Colors.green : Colors.black54,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _submitReport() {
|
||||
if (_selectedIndex == null) {
|
||||
MyToast().tip(
|
||||
title: '请选择举报原因!',
|
||||
position: 'center',
|
||||
type: 'error',
|
||||
);
|
||||
return;
|
||||
}
|
||||
// 获取选中的原因
|
||||
final selectedReason = reasonTypeData[_selectedIndex!];
|
||||
final reasonText = _reportController.text;
|
||||
doReport(selectedReason['dictValue'], reasonText);
|
||||
}
|
||||
}
|
||||
@ -6,10 +6,12 @@ import 'package:flutter_slidable/flutter_slidable.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:loopin/IM/controller/chat_controller.dart';
|
||||
import 'package:loopin/IM/controller/im_user_info_controller.dart';
|
||||
import 'package:loopin/IM/global_badge.dart';
|
||||
import 'package:loopin/IM/im_service.dart';
|
||||
import 'package:loopin/api/shop_api.dart';
|
||||
import 'package:loopin/components/empty_tip.dart';
|
||||
import 'package:loopin/components/my_toast.dart';
|
||||
import 'package:loopin/components/network_or_asset_image.dart';
|
||||
import 'package:loopin/components/scan_util.dart';
|
||||
import 'package:loopin/models/conversation_type.dart' as myConversationType;
|
||||
@ -66,12 +68,19 @@ class ChatPageState extends State<ChatPage> {
|
||||
// 处理扫码结果
|
||||
void handleScanResult(String code) {
|
||||
print('扫码结果11111111111111111111111:$code');
|
||||
|
||||
ScanCodeType.promotion;
|
||||
// 使用外部枚举的方法检查扫码类型
|
||||
final scanType = ScanCodeType.fromCode(code);
|
||||
|
||||
if (scanType != null) {
|
||||
logger.e(scanType);
|
||||
|
||||
if (code.contains('HXM')) {
|
||||
_processScanCode(scanType, code);
|
||||
} else {
|
||||
final value = code.substring(scanType.prefix.length + 1); // 获取后缀值
|
||||
_processScanCode(scanType, value);
|
||||
}
|
||||
} else {
|
||||
// 未知类型的码
|
||||
Get.snackbar('未知的二维码类型', '无法识别此二维码: $code');
|
||||
@ -95,9 +104,18 @@ class ChatPageState extends State<ChatPage> {
|
||||
|
||||
// 处理核销码
|
||||
void _handleVerificationCode(String value) async {
|
||||
print('处理核销码: $value');
|
||||
logger.e('处理核销码: $value');
|
||||
final controller = Get.find<ImUserInfoController>();
|
||||
final role = controller.role.value;
|
||||
final isSeller = Utils.hasRole(role, 2);
|
||||
// writeOffCodeId
|
||||
if (isSeller) {
|
||||
// 带着核销码,跳转到商家的商品详情页面,引导商家去手动点击核销按钮
|
||||
Get.toNamed('/sellerOrder/detail', arguments: {'writeOffCodeId': value});
|
||||
} else {
|
||||
//
|
||||
MyToast().tip(title: '未入驻的商家不可操作');
|
||||
}
|
||||
}
|
||||
|
||||
// 处理好友码
|
||||
@ -315,19 +333,19 @@ class ChatPageState extends State<ChatPage> {
|
||||
],
|
||||
),
|
||||
),
|
||||
PopupMenuItem<String>(
|
||||
value: 'cs',
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.qr_code_scanner, color: Colors.white, size: 18),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
'测试',
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// PopupMenuItem<String>(
|
||||
// value: 'cs',
|
||||
// child: Row(
|
||||
// children: [
|
||||
// Icon(Icons.qr_code_scanner, color: Colors.white, size: 18),
|
||||
// SizedBox(width: 8),
|
||||
// Text(
|
||||
// '测试',
|
||||
// style: TextStyle(color: Colors.white),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
);
|
||||
|
||||
@ -345,10 +363,11 @@ class ChatPageState extends State<ChatPage> {
|
||||
logger.w('点击了扫一扫');
|
||||
ScanUtil.openScanner(onResult: handleScanResult);
|
||||
break;
|
||||
case 'cs':
|
||||
logger.w('去互动');
|
||||
Get.toNamed('/newFoucs');
|
||||
break;
|
||||
// case 'cs':
|
||||
// logger.w('去互动');
|
||||
// // order newFoucs interaction
|
||||
// Get.toNamed('/interaction');
|
||||
// break;
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -563,9 +582,10 @@ class ChatPageState extends State<ChatPage> {
|
||||
const SizedBox(height: 2.0),
|
||||
// 消息内容
|
||||
Text(
|
||||
chatList[index].conversation.lastMessage != null
|
||||
? parseMessageSummary(chatList[index].conversation.lastMessage!)
|
||||
: '',
|
||||
_handleLast(index),
|
||||
// chatList[index].conversation.lastMessage != null
|
||||
// ? parseMessageSummary(chatList[index].conversation.lastMessage!)
|
||||
// : '',
|
||||
style: const TextStyle(color: Colors.grey, fontSize: 13.0),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
@ -624,6 +644,8 @@ class ChatPageState extends State<ChatPage> {
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
// logger.e(chatList[index].isCustomAdmin);
|
||||
|
||||
if (conversationTypeFromString(chatList[index].isCustomAdmin) != null) {
|
||||
// 跳转对应的通知消息页 notify下的内容
|
||||
logger.e(chatList[index].isCustomAdmin);
|
||||
@ -660,4 +682,24 @@ class ChatPageState extends State<ChatPage> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _handleLast(index) {
|
||||
// logger.e('开始处理显示');
|
||||
|
||||
// 被踢出群处理
|
||||
// final outConv = controller.chatList.firstWhere(
|
||||
// (conv) => conv.conversation.customData == 'out',
|
||||
// orElse: () => ConversationViewModel(conversation: V2TimConversation(conversationID: '')),
|
||||
// );
|
||||
// logger.e('conv=${controller.chatList[index].conversation.toLogString()}');
|
||||
|
||||
if (controller.chatList[index].conversation.customData == 'out') {
|
||||
// logger.e('找到了');
|
||||
// 找到有标记的群,修改本地渲染
|
||||
return '你已被移出群聊';
|
||||
} else {
|
||||
final str = controller.chatList[index].conversation.lastMessage != null ? parseMessageSummary(controller.chatList[index].conversation.lastMessage!) : '';
|
||||
return str;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ 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/index.dart';
|
||||
import 'package:loopin/utils/scan_code_type.dart';
|
||||
|
||||
class AddFriend extends StatefulWidget {
|
||||
@ -55,8 +56,18 @@ class _AddFriendState extends State<AddFriend> {
|
||||
// 处理核销码
|
||||
void _handleVerificationCode(String value) async {
|
||||
logger.w('处理核销码: $value');
|
||||
// 带着核销码,跳转到商家的商品详情页面,引导商家去手动点击核销按钮
|
||||
final controller = Get.find<ImUserInfoController>();
|
||||
final role = controller.role.value;
|
||||
final isSeller = Utils.hasRole(role, 2);
|
||||
if (isSeller) {
|
||||
// 带着核销码,跳转到商家的商品详情页面,引导商家去手动点击核销按钮
|
||||
Get.toNamed('/sellerOrder/detail', arguments: {'writeOffCodeId': value});
|
||||
} else {
|
||||
//
|
||||
MyToast().tip(title: '无法识别');
|
||||
}
|
||||
// Get.toNamed('/sellerOrder/detail', arguments: {'writeOffCodeId': value});
|
||||
}
|
||||
|
||||
// 处理好友码
|
||||
|
||||
@ -44,7 +44,7 @@ class InteractionState extends State<Interaction> with SingleTickerProviderState
|
||||
{'value': 'all', 'label': '全部', 'icon': Icons.all_inclusive},
|
||||
{'value': NotifyMessageTypeConstants.interactionLike, 'label': '点赞', 'icon': Icons.favorite_border},
|
||||
{'value': NotifyMessageTypeConstants.interactionComment, 'label': '评论', 'icon': Icons.comment},
|
||||
{'value': NotifyMessageTypeConstants.interactionAt, 'label': '@我', 'icon': Icons.alternate_email},
|
||||
// {'value': NotifyMessageTypeConstants.interactionAt, 'label': '@我', 'icon': Icons.alternate_email},
|
||||
{'value': NotifyMessageTypeConstants.interactionReply, 'label': '回复', 'icon': Icons.reply},
|
||||
];
|
||||
RxList demoList = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15].obs;
|
||||
@ -58,6 +58,7 @@ class InteractionState extends State<Interaction> with SingleTickerProviderState
|
||||
final lastmsg = conv?.lastMessage;
|
||||
if (lastmsg != null) {
|
||||
msgList.add(lastmsg);
|
||||
getMsgData();
|
||||
}
|
||||
}
|
||||
// chatController.addListener(() {
|
||||
@ -74,11 +75,23 @@ class InteractionState extends State<Interaction> with SingleTickerProviderState
|
||||
// });
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
cleanUnRead();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void cleanUnRead() async {
|
||||
if ((conv?.unreadCount ?? 0) > 0) {
|
||||
await ImService.instance.clearConversationUnreadCount(conversationID: conv?.conversationID ?? '');
|
||||
}
|
||||
}
|
||||
|
||||
// 分页获取全部数据
|
||||
Future<void> getMsgData() async {
|
||||
// 获取最旧一条消息作为游标
|
||||
V2TimMessage? lastRealMsg;
|
||||
lastRealMsg = msgList.last;
|
||||
lastRealMsg = msgList.isEmpty ? null : msgList.last;
|
||||
final res = await ImService.instance.getHistoryMessageList(
|
||||
userID: ConversationType.interaction.name, // userID为固定的interaction
|
||||
lastMsg: lastRealMsg,
|
||||
@ -103,13 +116,34 @@ class InteractionState extends State<Interaction> with SingleTickerProviderState
|
||||
// 获取不同的消息分类数据
|
||||
void _filterMessages(String filterType) async {
|
||||
logger.e(filterType);
|
||||
if (filterType == 'all') {
|
||||
getMsgData();
|
||||
return;
|
||||
}
|
||||
// 非全部
|
||||
String keyword;
|
||||
// interactionComment, //互动->评论
|
||||
// interactionAt, //互动->视频评论中的@
|
||||
// interactionLike, //互动->点赞
|
||||
// interactionReply, //互动->评论回复
|
||||
if (filterType == 'interactionComment') {
|
||||
keyword = '评论';
|
||||
} else if (filterType == 'interactionLike') {
|
||||
keyword = '点赞';
|
||||
} else if (filterType == 'interactionReply') {
|
||||
keyword = '回复';
|
||||
} else {
|
||||
keyword = '视频'; //这个兜底
|
||||
}
|
||||
final res = await ImService.instance.searchLocalMessages(
|
||||
page: page,
|
||||
conversationID: 'c2c_${ConversationType.interaction.name}',
|
||||
keywordList: ['action', filterType],
|
||||
keywordList: [keyword],
|
||||
// keywordList: ['action', filterType], //interactionLike
|
||||
);
|
||||
logger.e(res.data!.toLogString());
|
||||
logger.e(res.data!.toJson());
|
||||
if (res.success && res.data != null) {
|
||||
msgList.clear();
|
||||
final resultList = res.data?.messageSearchResultItems ?? [];
|
||||
if (resultList.isNotEmpty) {
|
||||
for (var item in resultList) {
|
||||
@ -117,6 +151,7 @@ class InteractionState extends State<Interaction> with SingleTickerProviderState
|
||||
msgList.addAll(item.messageList ?? []);
|
||||
}
|
||||
} else {
|
||||
msgList.clear();
|
||||
logger.e('数据为空${res.desc}');
|
||||
}
|
||||
}
|
||||
@ -229,15 +264,15 @@ class InteractionState extends State<Interaction> with SingleTickerProviderState
|
||||
child: EasyRefresh.builder(
|
||||
callLoadOverOffset: 20, //触底距离
|
||||
callRefreshOverOffset: 20, // 下拉距离
|
||||
header: ClassicHeader(
|
||||
dragText: '下拉刷新',
|
||||
armedText: '释放刷新',
|
||||
readyText: '加载中...',
|
||||
processingText: '加载中...',
|
||||
processedText: '加载完成',
|
||||
failedText: '加载失败,请重试',
|
||||
messageText: '最后更新于 %T',
|
||||
),
|
||||
// header: ClassicHeader(
|
||||
// dragText: '下拉刷新',
|
||||
// armedText: '释放刷新',
|
||||
// readyText: '加载中...',
|
||||
// processingText: '加载中...',
|
||||
// processedText: '加载完成',
|
||||
// failedText: '加载失败,请重试',
|
||||
// messageText: '最后更新于 %T',
|
||||
// ),
|
||||
footer: ClassicFooter(
|
||||
dragText: '加载更多',
|
||||
armedText: '释放加载',
|
||||
@ -248,11 +283,11 @@ class InteractionState extends State<Interaction> with SingleTickerProviderState
|
||||
failedText: '加载失败,请重试',
|
||||
messageText: '最后更新于 %T',
|
||||
),
|
||||
onRefresh: () async {
|
||||
await handleRefresh();
|
||||
},
|
||||
// onRefresh: () async {
|
||||
// await handleRefresh();
|
||||
// },
|
||||
onLoad: () async {
|
||||
if (hasMore.value) {
|
||||
if (selectedMessageType.value != 'all' && hasMore.value) {
|
||||
await getMsgData();
|
||||
return hasMore.value ? IndicatorResult.success : IndicatorResult.noMore;
|
||||
}
|
||||
@ -260,14 +295,14 @@ class InteractionState extends State<Interaction> with SingleTickerProviderState
|
||||
childBuilder: (context, physics) {
|
||||
return Obx(
|
||||
() {
|
||||
// if (msgList.isEmpty) return EmptyTip();
|
||||
if (demoList.isEmpty) return EmptyTip();
|
||||
if (msgList.isEmpty) return EmptyTip();
|
||||
// if (demoList.isEmpty) return EmptyTip();
|
||||
return ListView.builder(
|
||||
// controller: chatController,
|
||||
shrinkWrap: true,
|
||||
physics: physics,
|
||||
// itemCount: msgList.length,
|
||||
itemCount: demoList.length,
|
||||
itemCount: msgList.length,
|
||||
// itemCount: demoList.length,
|
||||
itemBuilder: (context, index) {
|
||||
//检测cloudCustomData
|
||||
// interactionComment, //互动->评论 评论
|
||||
@ -276,17 +311,20 @@ class InteractionState extends State<Interaction> with SingleTickerProviderState
|
||||
// interactionReply, //互动->评论回复 评论
|
||||
|
||||
//----
|
||||
// final element =msgList[index].customElem!;
|
||||
// final cloudCustomData = msgList[index].cloudCustomData;
|
||||
// final desc = msgList[index].customElem!.desc!;
|
||||
// final jsonData = msgList[index].customElem!.data;
|
||||
final element = msgList[index];
|
||||
// final element = msgList[index].customElem!;
|
||||
final cloudCustomData = msgList[index].cloudCustomData;
|
||||
final desc = msgList[index].customElem!.desc!;
|
||||
final jsonData = msgList[index].customElem!.data ?? '{"faceUrl":"","nickName":"data为null","userID":"213213"}';
|
||||
final item = jsonDecode(jsonData); // 数据
|
||||
logger.e(item);
|
||||
|
||||
// ----测试数据
|
||||
final jsonData = '{"faceUrl":"","nickName":"测试昵称","userID":"213213"}';
|
||||
final item = jsonDecode(jsonData); // 数据
|
||||
final desc = '测试desc';
|
||||
final cloudCustomData = 'interactionLike';
|
||||
V2TimMessage element = V2TimMessage(elemType: 2, isRead: index > 2 ? true : false);
|
||||
// final jsonData = '{"faceUrl":"","nickName":"测试昵称","userID":"213213"}';
|
||||
// final item = jsonDecode(jsonData); // 数据
|
||||
// final desc = '测试desc';
|
||||
// final cloudCustomData = 'interactionLike';
|
||||
// V2TimMessage element = V2TimMessage(elemType: 2, isRead: index > 2 ? true : false);
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 10.0),
|
||||
@ -294,16 +332,14 @@ class InteractionState extends State<Interaction> with SingleTickerProviderState
|
||||
spacing: 10.0,
|
||||
children: <Widget>[
|
||||
// 头像
|
||||
InkWell(
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
// 点击头像转到对方主页
|
||||
// 先获取视频详情
|
||||
// 如果cloudCustomData是interactionComment,interactionAt,interactionReply,传参时带上评论id,这三个是评论相关的
|
||||
//
|
||||
// final res = await Http.get('${VideoApi.detail}/${item['vlogID']}');
|
||||
// 此人存在才做跳转
|
||||
// Get.toNamed('/vloger', arguments: res['data']);
|
||||
Get.toNamed('/vloger');
|
||||
Get.toNamed('/vloger', arguments: {'memberId': item['userID']});
|
||||
},
|
||||
child: ClipOval(
|
||||
child: NetworkOrAssetImage(
|
||||
@ -316,15 +352,13 @@ class InteractionState extends State<Interaction> with SingleTickerProviderState
|
||||
|
||||
// 消息
|
||||
Expanded(
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
child: GestureDetector(
|
||||
onTap: () async {
|
||||
// 点击头像转到对方主页
|
||||
// 先获取视频详情
|
||||
// 如果cloudCustomData是interactionComment,interactionAt,interactionReply,传参时带上评论id,
|
||||
//
|
||||
// final res = await Http.get('${VideoApi.detail}/${item['vlogID']}');
|
||||
// Get.toNamed('/vloger', arguments: res['data']);
|
||||
Get.toNamed('/vloger');
|
||||
logger.e(item);
|
||||
Get.toNamed('/videoDetail', arguments: {'videoId': item['vlogID']});
|
||||
},
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@ -343,8 +377,7 @@ class InteractionState extends State<Interaction> with SingleTickerProviderState
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(color: Colors.grey, fontSize: 12.0),
|
||||
// desc,
|
||||
'很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容',
|
||||
desc,
|
||||
),
|
||||
Text(
|
||||
Utils.formatTime(element.timestamp ?? DateTime.now().millisecondsSinceEpoch ~/ 1000),
|
||||
@ -359,7 +392,11 @@ class InteractionState extends State<Interaction> with SingleTickerProviderState
|
||||
),
|
||||
|
||||
// 右侧
|
||||
Row(
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
Get.toNamed('/videoDetail', arguments: {'videoId': item['vlogID']});
|
||||
},
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Visibility(
|
||||
@ -380,6 +417,7 @@ class InteractionState extends State<Interaction> with SingleTickerProviderState
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@ -7,12 +7,10 @@ import 'package:easy_refresh/easy_refresh.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:loopin/IM/im_service.dart';
|
||||
import 'package:loopin/api/video_api.dart';
|
||||
import 'package:loopin/behavior/custom_scroll_behavior.dart';
|
||||
import 'package:loopin/components/empty_tip.dart';
|
||||
import 'package:loopin/components/network_or_asset_image.dart';
|
||||
import 'package:loopin/models/conversation_type.dart';
|
||||
import 'package:loopin/service/http.dart';
|
||||
import 'package:loopin/styles/index.dart';
|
||||
import 'package:loopin/utils/index.dart';
|
||||
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart';
|
||||
@ -48,10 +46,23 @@ class NewfoucsState extends State<Newfoucs> with SingleTickerProviderStateMixin
|
||||
final lastmsg = conv?.lastMessage;
|
||||
if (lastmsg != null) {
|
||||
msgList.add(lastmsg);
|
||||
getMsgData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
cleanUnRead();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void cleanUnRead() async {
|
||||
if ((conv?.unreadCount ?? 0) > 0) {
|
||||
await ImService.instance.clearConversationUnreadCount(conversationID: conv?.conversationID ?? '');
|
||||
}
|
||||
}
|
||||
|
||||
// 分页获取全部数据
|
||||
Future<void> getMsgData() async {
|
||||
// 获取最旧一条消息作为游标
|
||||
@ -172,7 +183,8 @@ class NewfoucsState extends State<Newfoucs> with SingleTickerProviderStateMixin
|
||||
String? jsonData = msgList[index].customElem!.data;
|
||||
jsonData = (jsonData == null || jsonData.isEmpty) ? '{"faceUrl":"","nickName":"data为空","userID":"213213"}' : jsonData;
|
||||
|
||||
final item = jsonDecode(jsonData ?? '{"faceUrl":"","nickName":"测试昵称","userID":"213213"}');
|
||||
final item = jsonDecode(jsonData);
|
||||
logger.e(item);
|
||||
|
||||
logger.w(element.toJson());
|
||||
|
||||
@ -196,9 +208,7 @@ class NewfoucsState extends State<Newfoucs> with SingleTickerProviderStateMixin
|
||||
// 先获取视频详情
|
||||
// 如果cloudCustomData是interactionComment,interactionAt,interactionReply,传参时带上评论id,
|
||||
//
|
||||
final res = await Http.get('${VideoApi.detail}/${item['vlogID']}');
|
||||
Get.toNamed('/vloger', arguments: res['data']);
|
||||
// Get.toNamed('/vloger');
|
||||
Get.toNamed('/vloger', arguments: {'memberId': item['userID']});
|
||||
},
|
||||
child: ClipOval(
|
||||
child: NetworkOrAssetImage(
|
||||
@ -214,8 +224,8 @@ class NewfoucsState extends State<Newfoucs> with SingleTickerProviderStateMixin
|
||||
child: InkWell(
|
||||
onTap: () async {
|
||||
// 点击头像转到对方主页,先获取视频详情
|
||||
final res = await Http.get('${VideoApi.detail}/${item['vlogID']}');
|
||||
Get.toNamed('/vloger', arguments: res['data']);
|
||||
Get.toNamed('/vloger', arguments: {'memberId': item['userID']});
|
||||
|
||||
// Get.toNamed('/vloger');
|
||||
},
|
||||
child: Column(
|
||||
|
||||
@ -1,290 +0,0 @@
|
||||
/// 新关注通知
|
||||
library;
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:easy_refresh/easy_refresh.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:loopin/IM/im_service.dart';
|
||||
import 'package:loopin/api/video_api.dart';
|
||||
import 'package:loopin/behavior/custom_scroll_behavior.dart';
|
||||
import 'package:loopin/components/empty_tip.dart';
|
||||
import 'package:loopin/components/network_or_asset_image.dart';
|
||||
import 'package:loopin/models/conversation_type.dart';
|
||||
import 'package:loopin/service/http.dart';
|
||||
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_custom_elem.dart';
|
||||
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message.dart';
|
||||
|
||||
// newFocus, 新的关注
|
||||
|
||||
class Newfoucs extends StatefulWidget {
|
||||
const Newfoucs({super.key});
|
||||
|
||||
@override
|
||||
State<Newfoucs> createState() => NewfoucsState();
|
||||
}
|
||||
|
||||
class NewfoucsState extends State<Newfoucs> with SingleTickerProviderStateMixin {
|
||||
bool isLoading = false; // 是否在加载中
|
||||
RxBool hasMore = true.obs; // 是否还有更多数据
|
||||
String page = '';
|
||||
|
||||
///-------------------
|
||||
V2TimConversation? conv;
|
||||
RxList<V2TimMessage> msgList = <V2TimMessage>[].obs;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (Get.arguments != null && Get.arguments is V2TimConversation) {
|
||||
// 如果有参数
|
||||
conv = Get.arguments as V2TimConversation;
|
||||
logger.e('lastmsg:$conv');
|
||||
|
||||
final lastmsg = conv?.lastMessage;
|
||||
if (lastmsg != null) {
|
||||
msgList.add(lastmsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 分页获取全部数据
|
||||
Future<void> 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) {
|
||||
msgList.addAll(res.data!);
|
||||
logger.e(msgList);
|
||||
if (res.data!.isEmpty) {
|
||||
hasMore.value = false;
|
||||
}
|
||||
logger.i('聊天数据加载成功');
|
||||
} else {
|
||||
logger.e('聊天数据加载失败:${res.desc}');
|
||||
}
|
||||
}
|
||||
|
||||
// 下拉刷新
|
||||
Future<void> handleRefresh() async {
|
||||
await Future.delayed(Duration(seconds: 5));
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey[50],
|
||||
appBar: AppBar(
|
||||
centerTitle: true,
|
||||
forceMaterialTransparency: true,
|
||||
bottom: PreferredSize(
|
||||
preferredSize: Size.fromHeight(1.0),
|
||||
child: Container(
|
||||
color: Colors.grey[300],
|
||||
height: 1.0,
|
||||
),
|
||||
),
|
||||
title: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'新的关注',
|
||||
style: TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [],
|
||||
),
|
||||
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: '加载完成',
|
||||
noMoreText: '没有更多了~',
|
||||
failedText: '加载失败,请重试',
|
||||
messageText: '最后更新于 %T',
|
||||
),
|
||||
onRefresh: () async {
|
||||
await handleRefresh();
|
||||
},
|
||||
onLoad: () async {
|
||||
if (hasMore.value) {
|
||||
await getMsgData();
|
||||
return hasMore.value ? IndicatorResult.success : IndicatorResult.noMore;
|
||||
}
|
||||
},
|
||||
childBuilder: (context, physics) {
|
||||
return Obx(
|
||||
() {
|
||||
if (msgList.isEmpty) return EmptyTip();
|
||||
|
||||
return ListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: physics,
|
||||
itemCount: msgList.length,
|
||||
itemBuilder: (context, index) {
|
||||
//检测cloudCustomData
|
||||
///发起关注人的[userID],
|
||||
///发起关注人的昵称[nickName],
|
||||
///发起关注人的头像地址[faceUrl],
|
||||
|
||||
//----正式数据
|
||||
V2TimMessage msg = msgList[index];
|
||||
V2TimCustomElem element = msgList[index].customElem!;
|
||||
final cloudCustomData = msgList[index].cloudCustomData;
|
||||
logger.w(cloudCustomData);
|
||||
final desc = msgList[index].customElem!.desc!;
|
||||
String? jsonData = msgList[index].customElem!.data;
|
||||
jsonData = (jsonData == null || jsonData.isEmpty) ? '{"faceUrl":"","nickName":"data为空","userID":"213213"}' : jsonData;
|
||||
|
||||
final item = jsonDecode(jsonData ?? '{"faceUrl":"","nickName":"测试昵称","userID":"213213"}');
|
||||
|
||||
logger.w(element.toJson());
|
||||
|
||||
// ----测试数据
|
||||
// final jsonData = '{"faceUrl":"","nickName":"测试昵称","userID":"213213"}';
|
||||
// final item = jsonDecode(jsonData); // 数据
|
||||
// final desc = '测试desc';
|
||||
// final cloudCustomData = 'newFocus';
|
||||
// V2TimCustomElem msg = V2TimMessage(elemType: 2, isRead: index > 2 ? true : false);
|
||||
// -----------
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 10.0),
|
||||
child: Row(
|
||||
spacing: 10.0,
|
||||
children: <Widget>[
|
||||
// 头像
|
||||
InkWell(
|
||||
onTap: () async {
|
||||
// 点击头像转到对方主页
|
||||
// 先获取视频详情
|
||||
// 如果cloudCustomData是interactionComment,interactionAt,interactionReply,传参时带上评论id,
|
||||
//
|
||||
final res = await Http.get('${VideoApi.detail}/${item['vlogID']}');
|
||||
Get.toNamed('/vloger', arguments: res['data']);
|
||||
// Get.toNamed('/vloger');
|
||||
},
|
||||
child: ClipOval(
|
||||
child: NetworkOrAssetImage(
|
||||
imageUrl: item['faceUrl'],
|
||||
width: 50,
|
||||
height: 50,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// 消息
|
||||
Expanded(
|
||||
child: InkWell(
|
||||
onTap: () async {
|
||||
// 点击头像转到对方主页,先获取视频详情
|
||||
final res = await Http.get('${VideoApi.detail}/${item['vlogID']}');
|
||||
Get.toNamed('/vloger', arguments: res['data']);
|
||||
// Get.toNamed('/vloger');
|
||||
},
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
// 昵称
|
||||
Text(
|
||||
item['nickName'] ?? '未知',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2.0),
|
||||
// 描述内容
|
||||
Text(
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(color: Colors.grey, fontSize: 12.0),
|
||||
desc,
|
||||
// '很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容',
|
||||
),
|
||||
Text(
|
||||
Utils.formatTime(msg.timestamp ?? DateTime.now().millisecondsSinceEpoch ~/ 1000),
|
||||
style: TextStyle(
|
||||
color: Colors.grey[600],
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// 右侧
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Visibility(
|
||||
visible: false, // 新的关注不需要
|
||||
// 视频首图
|
||||
child: NetworkOrAssetImage(
|
||||
imageUrl: item['firstFrameImg'],
|
||||
placeholderAsset: 'assets/images/bk.jpg',
|
||||
width: 40,
|
||||
height: 60,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 5.0),
|
||||
// 角标
|
||||
Visibility(
|
||||
visible: !(msg.isRead ?? true),
|
||||
child: FStyle.badge(0, isdot: true),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
391
lib/pages/chat/notify/orderNotify.dart
Normal file
@ -0,0 +1,391 @@
|
||||
/// 订单通知
|
||||
library;
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:easy_refresh/easy_refresh.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:loopin/IM/im_service.dart';
|
||||
import 'package:loopin/behavior/custom_scroll_behavior.dart';
|
||||
import 'package:loopin/components/empty_tip.dart';
|
||||
import 'package:loopin/components/network_or_asset_image.dart';
|
||||
import 'package:loopin/models/conversation_type.dart';
|
||||
import 'package:loopin/models/notify_message.type.dart';
|
||||
import 'package:loopin/pages/my/merchant/balance/balance.dart';
|
||||
import 'package:loopin/pages/my/merchant/balance/controller.dart';
|
||||
import 'package:loopin/styles/index.dart';
|
||||
import 'package:loopin/utils/index.dart';
|
||||
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart';
|
||||
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message.dart';
|
||||
|
||||
// order, 订单
|
||||
|
||||
class OrderNotify extends StatefulWidget {
|
||||
const OrderNotify({super.key});
|
||||
|
||||
@override
|
||||
State<OrderNotify> createState() => OrderNotifyState();
|
||||
}
|
||||
|
||||
class OrderNotifyState extends State<OrderNotify> with SingleTickerProviderStateMixin {
|
||||
bool isLoading = false; // 是否在加载中
|
||||
RxBool hasMore = true.obs; // 是否还有更多数据
|
||||
String page = '';
|
||||
|
||||
///-------------------
|
||||
V2TimConversation? conv;
|
||||
RxList<V2TimMessage> msgList = <V2TimMessage>[].obs;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
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);
|
||||
getMsgData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
cleanUnRead();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void cleanUnRead() async {
|
||||
if ((conv?.unreadCount ?? 0) > 0) {
|
||||
await ImService.instance.clearConversationUnreadCount(conversationID: conv?.conversationID ?? '');
|
||||
}
|
||||
}
|
||||
|
||||
// 分页获取全部数据
|
||||
Future<void> getMsgData() async {
|
||||
// 获取最旧一条消息作为游标
|
||||
V2TimMessage? lastRealMsg;
|
||||
lastRealMsg = msgList.last;
|
||||
final res = await ImService.instance.getHistoryMessageList(
|
||||
userID: ConversationType.order.name, // userID为固定的order
|
||||
lastMsg: lastRealMsg,
|
||||
);
|
||||
if (res.success && res.data != null) {
|
||||
msgList.addAll(res.data!);
|
||||
logger.e(msgList);
|
||||
if (res.data!.isEmpty) {
|
||||
hasMore.value = false;
|
||||
}
|
||||
logger.i('聊天数据加载成功');
|
||||
} else {
|
||||
logger.e('聊天数据加载失败:${res.desc}');
|
||||
}
|
||||
}
|
||||
|
||||
// 下拉刷新
|
||||
Future<void> handleRefresh() async {
|
||||
await Future.delayed(Duration(seconds: 5));
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey[50],
|
||||
appBar: AppBar(
|
||||
centerTitle: true,
|
||||
forceMaterialTransparency: true,
|
||||
bottom: PreferredSize(
|
||||
preferredSize: Size.fromHeight(1.0),
|
||||
child: Container(
|
||||
color: Colors.grey[300],
|
||||
height: 1.0,
|
||||
),
|
||||
),
|
||||
title: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'订单通知',
|
||||
style: TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [],
|
||||
),
|
||||
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: '加载完成',
|
||||
noMoreText: '没有更多了~',
|
||||
failedText: '加载失败,请重试',
|
||||
messageText: '最后更新于 %T',
|
||||
),
|
||||
onRefresh: () async {
|
||||
await handleRefresh();
|
||||
},
|
||||
onLoad: () async {
|
||||
if (hasMore.value) {
|
||||
await getMsgData();
|
||||
return hasMore.value ? IndicatorResult.success : IndicatorResult.noMore;
|
||||
}
|
||||
},
|
||||
childBuilder: (context, physics) {
|
||||
return Obx(
|
||||
() {
|
||||
if (msgList.isEmpty) return EmptyTip();
|
||||
|
||||
return ListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: physics,
|
||||
itemCount: msgList.length,
|
||||
itemBuilder: (context, index) {
|
||||
//检测cloudCustomData
|
||||
/// 订单编号[orderID]
|
||||
/// 订单金额[amount]
|
||||
/// 商品名称[name]
|
||||
/// 商品描述[describe]
|
||||
/// 商品价格[price]
|
||||
/// 商品主图[pic]
|
||||
|
||||
//----正式数据
|
||||
V2TimMessage msg = msgList[index];
|
||||
// V2TimCustomElem element = msgList[index].customElem;
|
||||
final cloudCustomData = msgList[index].cloudCustomData ?? '';
|
||||
logger.w(cloudCustomData);
|
||||
final desc = msgList[index].customElem?.desc;
|
||||
String? jsonData = msgList[index].customElem?.data;
|
||||
logger.e(msgList[index].toJson());
|
||||
jsonData = (jsonData == null || jsonData.isEmpty) ? '{"faceUrl":"","nickName":"data为空","userID":"213213"}' : jsonData;
|
||||
var item = jsonDecode(jsonData);
|
||||
if (item is Map<String, dynamic>) {
|
||||
item = jsonDecode(jsonData);
|
||||
} else {
|
||||
// 如果不是 Map,兜底处理
|
||||
item = {
|
||||
"faceUrl": "",
|
||||
"nickName": "数据异常",
|
||||
"userID": "",
|
||||
};
|
||||
}
|
||||
final showName = conv?.showName ?? '未知';
|
||||
// logger.w(element.toJson());
|
||||
|
||||
// ----测试数据
|
||||
// final jsonData = '{"faceUrl":"","nickName":"测试昵称","userID":"213213"}';
|
||||
// final item = jsonDecode(jsonData); // 数据
|
||||
// final desc = '您购买的巧克力已经下单成功您购买的巧克力已经下单成功您购买的巧克力已经下单成功';
|
||||
// final cloudCustomData = 'orderRefund';
|
||||
// V2TimMessage msg = V2TimMessage(elemType: 2, isRead: index > 2 ? true : false);
|
||||
// -----------
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 10.0),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: InkWell(
|
||||
onTap: () async {
|
||||
if (cloudCustomData == NotifyMessageTypeConstants.orderWithDraw ||
|
||||
cloudCustomData == NotifyMessageTypeConstants.orderRecharge) {
|
||||
// 去余额
|
||||
Get.put(BalanceController());
|
||||
Get.to(() => Balance());
|
||||
} else {
|
||||
// 点击去订单详情
|
||||
Get.toNamed('/order/detail', arguments: {'orderId': item['orderID']});
|
||||
}
|
||||
},
|
||||
child: Card(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
elevation: 2,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
// 顶部:头像 + 商品名
|
||||
Row(
|
||||
children: [
|
||||
ClipOval(
|
||||
child: NetworkOrAssetImage(
|
||||
imageUrl: msg.faceUrl,
|
||||
width: 25,
|
||||
height: 25,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
showName,
|
||||
// item['name'] ?? '未知商品名称',
|
||||
// cloudCustomData == NotifyMessageTypeConstants.orderWithDraw
|
||||
// ? (conv?.showName ?? '未知')
|
||||
// : ((item['name'] as String?) ?? '未知商品名称'),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Colors.black54,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 8),
|
||||
const Divider(height: 1, thickness: 1, color: Color(0xFFE0E0E0)),
|
||||
const SizedBox(height: 4),
|
||||
|
||||
Text(
|
||||
_transLabel(cloudCustom: cloudCustomData, status: item['status']),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 16.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 2),
|
||||
|
||||
// 时间
|
||||
Text(
|
||||
Utils.formatTime(msg.timestamp ?? DateTime.now().millisecondsSinceEpoch ~/ 1000),
|
||||
style: TextStyle(
|
||||
color: Colors.grey[600],
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// 内容:描述 + 时间 + 图片
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
// 商品首图
|
||||
Visibility(
|
||||
visible: item['pic'] != null && item['pic'].toString().isNotEmpty,
|
||||
child: NetworkOrAssetImage(
|
||||
imageUrl: item['pic'],
|
||||
placeholderAsset: 'assets/images/bk.jpg',
|
||||
width: 50,
|
||||
height: 50,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
// 商品描述
|
||||
Expanded(
|
||||
child: Text(
|
||||
desc ?? '未知描述',
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(color: Colors.black87, fontSize: 12.0),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(width: 20),
|
||||
|
||||
// 未读角标
|
||||
Visibility(
|
||||
visible: !(msg.isRead ?? true),
|
||||
child: FStyle.badge(0, isdot: true),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
const Divider(height: 1, thickness: 1, color: Color(0xFFE0E0E0)),
|
||||
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 8.0),
|
||||
child: Row(
|
||||
children: [
|
||||
const Text('查看详情'),
|
||||
const Spacer(),
|
||||
const Icon(Icons.arrow_forward_ios, size: 16, color: Colors.grey),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 解析标签
|
||||
String _transLabel({required String cloudCustom, String? status}) {
|
||||
//
|
||||
var str = status == null
|
||||
? '未知状态'
|
||||
: status == '1'
|
||||
? '成功'
|
||||
: '失败';
|
||||
if (NotifyMessageTypeConstants.orderPay == cloudCustom) {
|
||||
return '下单成功';
|
||||
} else if (NotifyMessageTypeConstants.orderRecharge == cloudCustom) {
|
||||
return '充值成功';
|
||||
} else if (NotifyMessageTypeConstants.orderRefund == cloudCustom) {
|
||||
return '退款$str';
|
||||
} else if (NotifyMessageTypeConstants.orderWithDraw == cloudCustom) {
|
||||
return '提现$str';
|
||||
} else {
|
||||
return '未知action';
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,517 +1 @@
|
||||
/// 聊天首页模板
|
||||
library;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:loopin/service/http.dart';
|
||||
import 'package:loopin/IM/controller/chat_controller.dart';
|
||||
import 'package:loopin/IM/global_badge.dart';
|
||||
import 'package:loopin/IM/im_service.dart';
|
||||
import 'package:loopin/behavior/custom_scroll_behavior.dart';
|
||||
import 'package:loopin/api/shop_api.dart';
|
||||
import 'package:loopin/components/network_or_asset_image.dart';
|
||||
import 'package:loopin/components/scan_util.dart';
|
||||
import 'package:loopin/models/conversation_type.dart';
|
||||
import 'package:loopin/models/conversation_view_model.dart';
|
||||
import 'package:loopin/styles/index.dart';
|
||||
import 'package:loopin/utils/parse_message_summary.dart';
|
||||
import 'package:loopin/utils/scan_code_type.dart'; // 导入外部枚举
|
||||
import 'package:shirne_dialog/shirne_dialog.dart';
|
||||
|
||||
// systemNotify, // 系统->通知
|
||||
// systemReport, // 系统->举报下架(视频,视频评论)
|
||||
// systemCheck, // 系统->审核结果(复审,驳回 ,通过)
|
||||
// systemPush, //系统->推广类的
|
||||
|
||||
class System extends StatefulWidget {
|
||||
const System({super.key});
|
||||
|
||||
@override
|
||||
State<System> createState() => SystemState();
|
||||
}
|
||||
|
||||
class SystemState extends State<System> {
|
||||
late final ChatController controller;
|
||||
dynamic writeOffInfo;
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
controller = Get.find<ChatController>();
|
||||
}
|
||||
|
||||
void deletConv(context, ConversationViewModel item) async {
|
||||
final res = await ImService.instance.deleteConversation(conversationID: item.conversation.conversationID);
|
||||
if (res.success) {
|
||||
Navigator.of(context).pop();
|
||||
controller.chatList.remove(item);
|
||||
}
|
||||
}
|
||||
|
||||
// 长按坐标点
|
||||
double posDX = 0.0;
|
||||
double posDY = 0.0;
|
||||
|
||||
// 下拉刷新
|
||||
Future<void> handleRefresh() async {
|
||||
await Future.delayed(Duration(seconds: 1));
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
// 长按菜单
|
||||
void showContextMenu(BuildContext context, ConversationViewModel item) {
|
||||
bool isLeft = posDX > MediaQuery.of(context).size.width / 2 ? false : true;
|
||||
bool isTop = posDY > MediaQuery.of(context).size.height / 2 ? false : true;
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierColor: Colors.black12, // 遮罩透明
|
||||
builder: (context) {
|
||||
return Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
top: isTop ? posDY : posDY - 135,
|
||||
left: isLeft ? posDX : posDX - 135,
|
||||
width: 135,
|
||||
child: Material(
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15.0)),
|
||||
color: Colors.white,
|
||||
elevation: 2.0,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
child: Column(
|
||||
children: [
|
||||
ListTile(
|
||||
title: const Text(
|
||||
'设为免打扰',
|
||||
style: TextStyle(color: Colors.black87, fontSize: 14.0),
|
||||
),
|
||||
dense: true,
|
||||
onTap: () {},
|
||||
),
|
||||
ListTile(
|
||||
title: const Text(
|
||||
'置顶消息',
|
||||
style: TextStyle(color: Colors.black87, fontSize: 14.0),
|
||||
),
|
||||
dense: true,
|
||||
onTap: () {},
|
||||
),
|
||||
ListTile(
|
||||
title: const Text(
|
||||
'不显示该消息',
|
||||
style: TextStyle(color: Colors.black87, fontSize: 14.0),
|
||||
),
|
||||
dense: true,
|
||||
onTap: () {},
|
||||
),
|
||||
ListTile(
|
||||
title: const Text(
|
||||
'删除',
|
||||
style: TextStyle(color: Colors.black87, fontSize: 14.0),
|
||||
),
|
||||
dense: true,
|
||||
onTap: () {
|
||||
deletConv(context, item);
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// 处理扫码结果
|
||||
void handleScanResult(String code) {
|
||||
print('扫码结果:$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 {
|
||||
print('处理核销码: $value');
|
||||
// 带着核销码,跳转到商家的商品详情页面,引导商家去手动点击核销按钮
|
||||
Get.toNamed('/sellerOrder/detail', arguments: {'writeOffCodeId':value});
|
||||
}
|
||||
|
||||
// 处理好友码
|
||||
void _handleFriendCode(String value) {
|
||||
print('处理好友码: $value');
|
||||
// 模仿抖音,去个人页面手动点击关注
|
||||
Get.toNamed('/vloger', arguments: {'memberId':value});
|
||||
}
|
||||
|
||||
// 处理推广码
|
||||
void _handlePromotionCode(String value)async {
|
||||
try {
|
||||
print('处理推广码111: $value');
|
||||
final res = await Http.post('${ShopApi.bindSpreadCodeId}', data: {
|
||||
"socialCode": value
|
||||
});
|
||||
if(res != null && res['code'] == 200){
|
||||
MyDialog.toast('推广码绑定成功', icon: const Icon(Icons.check_circle), style: ToastStyle(backgroundColor: Colors.green.withAlpha(200)));
|
||||
Get.toNamed('/vloger', arguments: {'memberId':value});
|
||||
}
|
||||
} catch (e) {
|
||||
MyDialog.toast('推广码绑定失败');
|
||||
}
|
||||
}
|
||||
|
||||
// 绑定推广码和人员关系
|
||||
bindSpreadCode(code) async {
|
||||
try {
|
||||
final res = await Http.post('${ShopApi.bindSpreadCodeId}', data: {
|
||||
"socialCode": "01901839908031348736"
|
||||
});
|
||||
print('绑定结果-------------->${res['res']}');
|
||||
|
||||
} catch (e) {
|
||||
|
||||
MyDialog.toast('推广码绑定失败');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey[50],
|
||||
appBar: AppBar(
|
||||
forceMaterialTransparency: true,
|
||||
title: Row(
|
||||
spacing: 8.0,
|
||||
children: [
|
||||
Text('消息'),
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 6.0, vertical: 4.0),
|
||||
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(20.0), boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withAlpha(20),
|
||||
offset: Offset(0.0, 1.0),
|
||||
blurRadius: 2.0,
|
||||
spreadRadius: 0.0,
|
||||
),
|
||||
]),
|
||||
child: InkWell(
|
||||
onTap: () async {
|
||||
if (Get.find<GlobalBadge>().totalUnread > 0) {
|
||||
final res = await ImService.instance.clearConversationUnreadCount(conversationID: '');
|
||||
if (res.success) {
|
||||
MyDialog.toast('操作成功', icon: const Icon(Icons.check_circle), style: ToastStyle(backgroundColor: Colors.green.withAlpha(200)));
|
||||
} else {
|
||||
MyDialog.toast(res.desc, icon: Icon(Icons.warning), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200)));
|
||||
}
|
||||
}
|
||||
},
|
||||
child: Row(
|
||||
spacing: 3.0,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.cleaning_services_sharp,
|
||||
size: 14.0,
|
||||
),
|
||||
Text(
|
||||
'清除未读',
|
||||
style: TextStyle(fontSize: 12.0),
|
||||
)
|
||||
],
|
||||
),
|
||||
)),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
/// 先不做搜索功能了后面再说
|
||||
// IconButton(
|
||||
// icon: const Icon(Icons.search),
|
||||
// onPressed: () {},
|
||||
// ),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.add_circle_outline),
|
||||
onPressed: () async {
|
||||
final paddingTop = MediaQuery.of(Get.context!).padding.top;
|
||||
|
||||
final selected = await showMenu(
|
||||
context: Get.context!,
|
||||
position: RelativeRect.fromLTRB(
|
||||
double.infinity,
|
||||
kToolbarHeight + paddingTop - 12,
|
||||
8,
|
||||
double.infinity,
|
||||
),
|
||||
color: FStyle.primaryColor,
|
||||
elevation: 8,
|
||||
items: [
|
||||
PopupMenuItem<String>(
|
||||
value: 'group',
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.group, color: Colors.white, size: 18),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
'发起群聊',
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
PopupMenuItem<String>(
|
||||
value: 'friend',
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.person_add, color: Colors.white, size: 18),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
'添加朋友',
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
PopupMenuItem<String>(
|
||||
value: 'scan',
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.qr_code_scanner, color: Colors.white, size: 18),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
'扫一扫',
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
if (selected != null) {
|
||||
switch (selected) {
|
||||
case 'group':
|
||||
print('点击了发起群聊');
|
||||
break;
|
||||
case 'friend':
|
||||
print('点击了添加朋友');
|
||||
break;
|
||||
case 'scan':
|
||||
print('点击了扫一扫');
|
||||
ScanUtil.openScanner(onResult: handleScanResult);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
body: ScrollConfiguration(
|
||||
behavior: CustomScrollBehavior().copyWith(scrollbars: false),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
margin: EdgeInsets.symmetric(horizontal: 15.0, vertical: 10.0),
|
||||
padding: EdgeInsets.all(10.0),
|
||||
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(15.0), boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withAlpha(10),
|
||||
offset: Offset(0.0, 1.0),
|
||||
blurRadius: 2.0,
|
||||
spreadRadius: 0.0,
|
||||
),
|
||||
]),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
'assets/images/svg/order.svg',
|
||||
height: 36.0,
|
||||
width: 36.0,
|
||||
),
|
||||
Text('群聊'),
|
||||
],
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
'assets/images/svg/kefu.svg',
|
||||
height: 36.0,
|
||||
width: 36.0,
|
||||
),
|
||||
Text('互关'),
|
||||
],
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
'assets/images/svg/comment.svg',
|
||||
height: 36.0,
|
||||
width: 36.0,
|
||||
),
|
||||
Text('粉丝'),
|
||||
],
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
'assets/images/svg/comment.svg',
|
||||
height: 36.0,
|
||||
width: 36.0,
|
||||
),
|
||||
Text('关注'),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: RefreshIndicator(
|
||||
backgroundColor: Colors.white,
|
||||
color: Color(0xFFFF5000),
|
||||
displacement: 10.0,
|
||||
onRefresh: handleRefresh,
|
||||
child: Obx(() {
|
||||
final chatList = controller.chatList;
|
||||
|
||||
return ListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: BouncingScrollPhysics(),
|
||||
itemCount: chatList.length,
|
||||
itemBuilder: (context, index) {
|
||||
return Ink(
|
||||
// color: chatList[index]['topMost'] == null ? Colors.white : Colors.grey[100], //置顶颜色
|
||||
child: InkWell(
|
||||
splashColor: Colors.grey[200],
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 10.0),
|
||||
child: Row(
|
||||
spacing: 10.0,
|
||||
children: <Widget>[
|
||||
// 头图
|
||||
ClipOval(
|
||||
child: NetworkOrAssetImage(
|
||||
imageUrl: chatList[index].faceUrl,
|
||||
width: 50,
|
||||
height: 50,
|
||||
),
|
||||
),
|
||||
|
||||
// 消息
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
chatList[index].conversation.showName ?? '未知',
|
||||
style: TextStyle(
|
||||
fontSize: (chatList[index].conversation.conversationGroupList!.contains(ConversationType.noFriend.name)) ||
|
||||
chatList[index].isCustomAdmin != '0'
|
||||
? 20
|
||||
: 16,
|
||||
fontWeight: (chatList[index].conversation.conversationGroupList!.contains(ConversationType.noFriend.name)) ||
|
||||
chatList[index].isCustomAdmin != '0'
|
||||
? FontWeight.bold
|
||||
: FontWeight.normal),
|
||||
),
|
||||
const SizedBox(height: 2.0),
|
||||
Text(
|
||||
chatList[index].conversation.lastMessage != null
|
||||
? parseMessageSummary(chatList[index].conversation.lastMessage!)
|
||||
: '',
|
||||
style: const TextStyle(color: Colors.grey, fontSize: 13.0),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// 右侧
|
||||
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: <Widget>[
|
||||
Visibility(
|
||||
visible: !(chatList[index].isCustomAdmin != '0' ||
|
||||
chatList[index].conversation.conversationGroupList!.contains(ConversationType.noFriend.name)),
|
||||
child: Text(
|
||||
// 转成日期字符串显示
|
||||
DateTime.fromMillisecondsSinceEpoch(
|
||||
(chatList[index].conversation.lastMessage!.timestamp ?? 0) * 1000,
|
||||
).toLocal().toString().substring(0, 16), // 简单截取年月日时分
|
||||
style: const TextStyle(color: Colors.grey, fontSize: 12.0),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 5.0),
|
||||
// 数字角标
|
||||
Visibility(
|
||||
visible: (chatList[index].conversation.unreadCount ?? 0) > 0,
|
||||
child: FStyle.badge(chatList[index].conversation.unreadCount ?? 0),
|
||||
),
|
||||
],
|
||||
),
|
||||
Visibility(
|
||||
visible: chatList[index].isCustomAdmin != '0' ||
|
||||
chatList[index].conversation.conversationGroupList!.contains(ConversationType.noFriend.name),
|
||||
child: const Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
color: Colors.blueGrey,
|
||||
size: 14.0,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
if (conversationTypeFromString(chatList[index].isCustomAdmin)) {
|
||||
// 跳转对应的通知消息页
|
||||
logger.e(chatList[index].isCustomAdmin);
|
||||
} else if (chatList[index].conversation.conversationGroupList!.contains(ConversationType.noFriend.name)) {
|
||||
// 跳转陌生人消息页面
|
||||
logger.e(chatList[index].conversation.conversationGroupList);
|
||||
} else {
|
||||
// 会话id查询会话详情
|
||||
Get.toNamed('/chat', arguments: chatList[index].conversation);
|
||||
}
|
||||
},
|
||||
onTapDown: (TapDownDetails details) {
|
||||
posDX = details.globalPosition.dx;
|
||||
posDY = details.globalPosition.dy;
|
||||
},
|
||||
onLongPress: () {
|
||||
showContextMenu(context, chatList[index]);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
})),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -108,8 +108,9 @@ class _GoodsState extends State<Goods> {
|
||||
Get.back();
|
||||
}
|
||||
}
|
||||
|
||||
// 根据选中的属性定位到对应的SKU
|
||||
void locateSelectedSku() {
|
||||
void locateSelectedSku() {
|
||||
if (shopObj != null && shopObj['skuList'] != null) {
|
||||
for (var sku in shopObj['skuList']) {
|
||||
try {
|
||||
@ -136,29 +137,34 @@ void locateSelectedSku() {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理属性选择
|
||||
void handleAttributeSelect(String attrName, String optionName) {
|
||||
void handleAttributeSelect(String attrName, String optionName) {
|
||||
setState(() {
|
||||
selectedAttributes[attrName] = optionName;
|
||||
locateSelectedSku(); // 选择属性后重新定位SKU
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
///创建定点杆
|
||||
createOrder(String goodsId) async {
|
||||
var params = {
|
||||
"type": 1, // 订单类型:1->团购;2->拼团;3->秒杀;
|
||||
"distribution": 1, // 配送方式 1->到店核销;2->自提;3->配送;
|
||||
"skuItemBOList": [
|
||||
{"skuId": goodsId, "quantity": _quantity}
|
||||
{
|
||||
"skuId": goodsId,
|
||||
"quantity": _quantity,
|
||||
"shareMemberId": Get.arguments['userID'] ?? '',
|
||||
}
|
||||
]
|
||||
};
|
||||
print('下单请求参数---->$params');
|
||||
try {
|
||||
final res = await Http.post(ShopApi.createGoodsOrder, data: params);
|
||||
var resData = res['data'];
|
||||
print('1111111111111111111111111---->$res');
|
||||
logger.w(res);
|
||||
if (resData['id'].isNotEmpty) {
|
||||
return resData['id'];
|
||||
} else {
|
||||
@ -184,7 +190,8 @@ void handleAttributeSelect(String attrName, String optionName) {
|
||||
|
||||
void handlCoverClick(V2TimConversation conv) async {
|
||||
// 发送自定义消息 商品信息
|
||||
final userId = conv.userID;
|
||||
final isGroup = conv.groupID != null && (conv.groupID ?? '').isNotEmpty;
|
||||
final id = isGroup ? conv.groupID : conv.userID;
|
||||
//price,title,url,sell
|
||||
logger.w(shopObj['name']);
|
||||
final makeJson = jsonEncode({
|
||||
@ -199,7 +206,12 @@ void handleAttributeSelect(String attrName, String optionName) {
|
||||
data: makeJson,
|
||||
);
|
||||
if (res.success) {
|
||||
final sendRes = await IMMessage().sendMessage(msg: res.data!.messageInfo!, toUserID: userId, cloudCustomData: SummaryType.shareTuangou);
|
||||
final sendRes = await IMMessage().sendMessage(
|
||||
msg: res.data!.messageInfo!,
|
||||
groupID: isGroup ? id : null,
|
||||
toUserID: isGroup ? null : id,
|
||||
cloudCustomData: SummaryType.shareTuangou,
|
||||
);
|
||||
if (sendRes.success) {
|
||||
MyToast().tip(
|
||||
title: '分享成功',
|
||||
@ -338,6 +350,7 @@ void handleAttributeSelect(String attrName, String optionName) {
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// 检查属性是否被选中
|
||||
bool isAttributeSelected(String attrName, String optionName) {
|
||||
return selectedAttributes[attrName] == optionName;
|
||||
@ -545,7 +558,7 @@ void handleAttributeSelect(String attrName, String optionName) {
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
image: DecorationImage(
|
||||
image: NetworkImage(selectedSku['pic'] != null ? selectedSku['pic'] : shopObj['pic']),
|
||||
image: NetworkImage(selectedSku['pic'] ?? shopObj['pic']),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
@ -687,7 +700,7 @@ void handleAttributeSelect(String attrName, String optionName) {
|
||||
SizedBox(height: 12),
|
||||
],
|
||||
);
|
||||
}).toList(),
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -736,7 +749,8 @@ void handleAttributeSelect(String attrName, String optionName) {
|
||||
color: Color(0xFFFFEBEB),
|
||||
borderRadius: BorderRadius.circular(30.0),
|
||||
),
|
||||
child: shopObj['canOrder'] == true?Row(
|
||||
child: shopObj['canOrder'] == true
|
||||
? Row(
|
||||
children: [
|
||||
Container(
|
||||
alignment: Alignment.center,
|
||||
@ -746,6 +760,7 @@ void handleAttributeSelect(String attrName, String optionName) {
|
||||
onTap: () async {
|
||||
// 这里走生成预支付订单,拿到orderId
|
||||
String skuId = selectedSku != null ? selectedSku['id'] : shopObj['skuList'][0]['id'];
|
||||
logger.e(skuId);
|
||||
String orderId = await createOrder(skuId);
|
||||
if (orderId.isNotEmpty) {
|
||||
Get.toNamed('/order/detail', arguments: {'orderId': orderId});
|
||||
@ -760,7 +775,8 @@ void handleAttributeSelect(String attrName, String optionName) {
|
||||
),
|
||||
),
|
||||
],
|
||||
):null,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import 'package:easy_refresh/easy_refresh.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:loopin/IM/im_service.dart';
|
||||
import 'package:loopin/components/network_or_asset_image.dart';
|
||||
import 'package:loopin/styles/index.dart';
|
||||
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_member_full_info.dart';
|
||||
import 'package:tencent_cloud_chat_sdk/models/v2_tim_user_full_info.dart';
|
||||
@ -184,6 +185,7 @@ class _MemberActionSheetState extends State<InviteActionSheet> {
|
||||
final showName = uname.isEmpty ? nickname : uname;
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
FocusScope.of(context).unfocus();
|
||||
setState(() {
|
||||
if (_selectedIDs.contains(id)) {
|
||||
_selectedIDs.remove(id);
|
||||
@ -196,12 +198,15 @@ class _MemberActionSheetState extends State<InviteActionSheet> {
|
||||
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
|
||||
child: Row(
|
||||
children: [
|
||||
// 左侧圆形头像
|
||||
CircleAvatar(
|
||||
radius: 20,
|
||||
backgroundImage: m.faceUrl != null ? NetworkImage(m.faceUrl!) : null,
|
||||
child: m.faceUrl == null ? const Icon(Icons.person) : null,
|
||||
// 头像
|
||||
ClipOval(
|
||||
child: NetworkOrAssetImage(
|
||||
imageUrl: m.faceUrl ?? '',
|
||||
width: 40.0,
|
||||
height: 40.0,
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(width: 12),
|
||||
|
||||
// 用户名
|
||||
|
||||
@ -2,6 +2,7 @@ import 'package:easy_refresh/easy_refresh.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:loopin/IM/im_service.dart';
|
||||
import 'package:loopin/components/network_or_asset_image.dart';
|
||||
import 'package:loopin/pages/groupChat/controller/group_detail_controller.dart';
|
||||
import 'package:loopin/styles/index.dart';
|
||||
import 'package:tencent_cloud_chat_sdk/enum/group_member_filter_enum.dart';
|
||||
@ -50,6 +51,7 @@ class _MemberActionSheetState extends State<MemberActionSheet> {
|
||||
final self = Get.find<GroupDetailController>().selfInfo.value!;
|
||||
members.insert(0, self);
|
||||
}
|
||||
|
||||
getMemberData();
|
||||
}
|
||||
|
||||
@ -59,7 +61,7 @@ class _MemberActionSheetState extends State<MemberActionSheet> {
|
||||
loading = true;
|
||||
final res = await ImService.instance.getGroupMemberList(
|
||||
groupID: widget.groupID,
|
||||
filter: GroupMemberFilterTypeEnum.V2TIM_GROUP_MEMBER_FILTER_COMMON,
|
||||
filter: GroupMemberFilterTypeEnum.V2TIM_GROUP_MEMBER_FILTER_ALL,
|
||||
nextSeq: nextSeq,
|
||||
count: 100,
|
||||
);
|
||||
@ -67,8 +69,13 @@ class _MemberActionSheetState extends State<MemberActionSheet> {
|
||||
logger.e(res.data!.nextSeq);
|
||||
nextSeq = res.data!.nextSeq ?? '0';
|
||||
final mem = res.data!.memberInfoList ?? [];
|
||||
setState(() {
|
||||
//去重自己
|
||||
if (widget.showSelf) {
|
||||
members.addAll(mem);
|
||||
members = {for (var m in members) m.userID: m}.values.toList();
|
||||
}
|
||||
|
||||
setState(() {
|
||||
hasMore = res.data!.nextSeq == '0' ? false : true;
|
||||
loading = false;
|
||||
});
|
||||
@ -131,7 +138,7 @@ class _MemberActionSheetState extends State<MemberActionSheet> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
List filteredMembers;
|
||||
List<V2TimGroupMemberFullInfo> filteredMembers;
|
||||
if (_query.isEmpty) {
|
||||
filteredMembers = members;
|
||||
} else {
|
||||
@ -223,23 +230,36 @@ class _MemberActionSheetState extends State<MemberActionSheet> {
|
||||
final showName = uname.isEmpty ? nickname : uname;
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
FocusScope.of(context).unfocus();
|
||||
setState(() {
|
||||
if (widget.showSelf) {
|
||||
// 显示自己=查看群成员,这里后面加查看群成员资料,先跳去博主的主页吧
|
||||
final selfInfo = Get.find<GroupDetailController>().selfInfo.value ?? V2TimGroupMemberFullInfo(userID: '');
|
||||
final currentUserID = selfInfo.userID;
|
||||
if (m.userID != currentUserID) {
|
||||
Get.toNamed('/vloger', arguments: {'memberId': m.userID});
|
||||
}
|
||||
} else {
|
||||
// false=移除群成员
|
||||
if (_selectedIDs.contains(id)) {
|
||||
_selectedIDs.remove(id);
|
||||
} else {
|
||||
_selectedIDs.add(id);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
|
||||
child: Row(
|
||||
children: [
|
||||
// 左侧圆形头像
|
||||
CircleAvatar(
|
||||
radius: 20,
|
||||
backgroundImage: m.faceUrl != null ? NetworkImage(m.faceUrl!) : null,
|
||||
child: m.faceUrl == null ? const Icon(Icons.person) : null,
|
||||
// 头像
|
||||
ClipOval(
|
||||
child: NetworkOrAssetImage(
|
||||
imageUrl: m.faceUrl ?? '',
|
||||
width: 40.0,
|
||||
height: 40.0,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
|
||||
|
||||
@ -10,6 +10,7 @@ import 'package:loopin/pages/groupChat/components/invite_action_sheet.dart';
|
||||
import 'package:loopin/pages/groupChat/components/member_action_sheet.dart';
|
||||
import 'package:loopin/pages/groupChat/components/set_group_info.dart';
|
||||
import 'package:loopin/pages/groupChat/controller/group_detail_controller.dart';
|
||||
import 'package:loopin/pages/groupChat/reportGp.dart';
|
||||
import 'package:loopin/service/http.dart';
|
||||
import 'package:loopin/utils/index.dart';
|
||||
import 'package:loopin/utils/permissions.dart';
|
||||
@ -165,16 +166,34 @@ class GroupdetailState extends State<Groupdetail> {
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
Utils.handleText(controller.info.value?.groupName, '', "未命名群聊"),
|
||||
// '很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长',
|
||||
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
if (controller.isOwner.value)
|
||||
Icon(
|
||||
Icons.edit,
|
||||
size: 14,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
// 群简介
|
||||
subtitle: Obx(
|
||||
subtitle: Padding(
|
||||
padding: const EdgeInsets.only(top: 6.0),
|
||||
child: Obx(
|
||||
() => GestureDetector(
|
||||
onTap: () {
|
||||
// 去setinfo页
|
||||
@ -199,17 +218,64 @@ class GroupdetailState extends State<Groupdetail> {
|
||||
},
|
||||
),
|
||||
);
|
||||
} else {
|
||||
// 普通成员点击,查看完成的描述内容
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
|
||||
),
|
||||
isScrollControlled: true, // 超出可滚动
|
||||
builder: (ctx) => SafeArea(
|
||||
child: Container(
|
||||
width: double.infinity, // 👈 占满宽度
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min, // 根据内容自适应高度
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
"群简介",
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
Utils.handleText(controller.info.value?.introduction, '', "暂无群介绍"),
|
||||
style: const TextStyle(fontSize: 14),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Row(mainAxisSize: MainAxisSize.max, children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
Utils.handleText(controller.info.value?.introduction, '', "暂无群介绍"),
|
||||
// '非常长的内容非常长的内容非常长的内容非常长的内容非常长的内容非常长的内容',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(color: Colors.grey),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
if (controller.isOwner.value)
|
||||
Icon(
|
||||
Icons.edit,
|
||||
size: 14,
|
||||
)
|
||||
]),
|
||||
),
|
||||
),
|
||||
),
|
||||
// trailing: const Icon(Icons.chevron_right),
|
||||
),
|
||||
const Divider(height: 1),
|
||||
|
||||
@ -251,19 +317,12 @@ class GroupdetailState extends State<Groupdetail> {
|
||||
);
|
||||
},
|
||||
),
|
||||
// 群成员头像区域
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
child: Wrap(
|
||||
spacing: 16,
|
||||
runSpacing: 12,
|
||||
children: [
|
||||
Obx(
|
||||
() {
|
||||
return Wrap(
|
||||
spacing: 16,
|
||||
runSpacing: 12,
|
||||
children: controller.memberList.take(8).map((m) {
|
||||
// 点击成员头像
|
||||
child: Obx(() {
|
||||
// 成员头像
|
||||
final memberWidgets = controller.memberList.take(8).map((m) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
final currentUserID = controller.selfInfo.value?.userID ?? '';
|
||||
@ -287,25 +346,22 @@ class GroupdetailState extends State<Groupdetail> {
|
||||
child: Text(
|
||||
m.nickName ?? '未知昵称',
|
||||
maxLines: 1,
|
||||
softWrap: false,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: false,
|
||||
style: const TextStyle(fontSize: 12),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
},
|
||||
),
|
||||
// 邀请 +
|
||||
GestureDetector(
|
||||
}).toList();
|
||||
|
||||
// 邀请按钮
|
||||
final inviteWidget = GestureDetector(
|
||||
onTap: () {
|
||||
//检测群人数上限,
|
||||
logger.w('当前人数${controller.info.value?.memberCount ?? 0}---上限人数${controller.info.value?.memberMaxCount ?? 0}');
|
||||
if ((controller.info.value?.memberCount ?? 0) < (controller.info.value?.memberMaxCount ?? 0)) {
|
||||
// 群人数未达到上限,可以继续加人
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
barrierColor: Colors.white,
|
||||
@ -330,19 +386,17 @@ class GroupdetailState extends State<Groupdetail> {
|
||||
}
|
||||
},
|
||||
child: Column(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 25,
|
||||
child: const Icon(Icons.add),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
const Text("邀请", style: TextStyle(fontSize: 12)),
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: const [
|
||||
CircleAvatar(radius: 25, child: Icon(Icons.add)),
|
||||
SizedBox(height: 4),
|
||||
Text("邀请", style: TextStyle(fontSize: 12)),
|
||||
],
|
||||
),
|
||||
),
|
||||
// 移除 -
|
||||
Obx(
|
||||
() => controller.isOwner.value
|
||||
);
|
||||
|
||||
// 移除按钮(只有群主显示)
|
||||
final removeWidget = controller.isOwner.value
|
||||
? GestureDetector(
|
||||
onTap: () {
|
||||
showModalBottomSheet(
|
||||
@ -366,20 +420,32 @@ class GroupdetailState extends State<Groupdetail> {
|
||||
);
|
||||
},
|
||||
child: Column(
|
||||
children: [
|
||||
const CircleAvatar(
|
||||
radius: 25,
|
||||
child: Icon(Icons.remove),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
const Text("移除", style: TextStyle(fontSize: 12)),
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: const [
|
||||
CircleAvatar(radius: 25, child: Icon(Icons.remove)),
|
||||
SizedBox(height: 4),
|
||||
Text("移除", style: TextStyle(fontSize: 12)),
|
||||
],
|
||||
),
|
||||
)
|
||||
: SizedBox.shrink(),
|
||||
)
|
||||
],
|
||||
),
|
||||
: const SizedBox.shrink();
|
||||
|
||||
final allWidgets = <Widget>[
|
||||
...memberWidgets,
|
||||
inviteWidget,
|
||||
];
|
||||
if (controller.isOwner.value) allWidgets.add(removeWidget);
|
||||
|
||||
return GridView.count(
|
||||
shrinkWrap: true, // 适应内容高度
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
crossAxisCount: 4, // 每行显示 4 个
|
||||
mainAxisSpacing: 0, // 行间距
|
||||
crossAxisSpacing: 16, // 列间距
|
||||
childAspectRatio: 1, // 调整宽高比
|
||||
children: allWidgets,
|
||||
);
|
||||
}),
|
||||
),
|
||||
const Divider(height: 1),
|
||||
|
||||
@ -500,13 +566,14 @@ class GroupdetailState extends State<Groupdetail> {
|
||||
const Divider(height: 1),
|
||||
|
||||
// 举报
|
||||
// ListTile(
|
||||
// title: const Text("举报"),
|
||||
// trailing: const Icon(Icons.chevron_right),
|
||||
// onTap: () {
|
||||
// //
|
||||
// },
|
||||
// ),
|
||||
ListTile(
|
||||
title: const Text("举报"),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () {
|
||||
//
|
||||
Get.to(() => ReportGp(groupID: controller.info.value!.groupID));
|
||||
},
|
||||
),
|
||||
|
||||
// 退出群聊
|
||||
const SizedBox(height: 20),
|
||||
|
||||
250
lib/pages/groupChat/reportGp.dart
Normal file
@ -0,0 +1,250 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:loopin/api/video_api.dart';
|
||||
import 'package:loopin/components/my_toast.dart';
|
||||
import 'package:loopin/service/http.dart';
|
||||
import 'package:loopin/utils/getCommonDictionary.dart';
|
||||
|
||||
import '../../behavior/custom_scroll_behavior.dart';
|
||||
|
||||
class ReportGp extends StatefulWidget {
|
||||
final String groupID;
|
||||
const ReportGp({super.key, required this.groupID});
|
||||
|
||||
@override
|
||||
State<ReportGp> createState() => _ReportGpState();
|
||||
}
|
||||
|
||||
class _ReportGpState extends State<ReportGp> with SingleTickerProviderStateMixin {
|
||||
int? _selectedIndex;
|
||||
late dynamic args;
|
||||
final TextEditingController _reportController = TextEditingController();
|
||||
final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey();
|
||||
List<dynamic> reasonTypeData = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
args = Get.arguments ?? {};
|
||||
getReportReasons('ums_chat_report'); // 获取投诉枚举
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_reportController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
// 加载数据的方法
|
||||
Future<void> getReportReasons(String key) async {
|
||||
try {
|
||||
final data = await Commondictionary.getCommonDictionary(key);
|
||||
setState(() {
|
||||
reasonTypeData = data;
|
||||
});
|
||||
} catch (e) {
|
||||
print('加载失败: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// 举报
|
||||
Future<void> doReport(String dictValue, String reasonText) async {
|
||||
try {
|
||||
final res = await Http.post(VideoApi.reportVideoApi, data: {
|
||||
'type': '1', // 类型 1 举报 2 投诉 3 建议
|
||||
'content': reasonText,
|
||||
'aimId': widget.groupID,
|
||||
'aimType': '2', // 1 会员 2 群组 3 评论 4 视频 5 聊天
|
||||
'reasonType': dictValue
|
||||
});
|
||||
if (res['code'] == 200) {
|
||||
// 投诉成功,返回视频页面
|
||||
MyToast().tip(
|
||||
title: '投诉成功',
|
||||
position: 'center',
|
||||
type: 'success',
|
||||
);
|
||||
Get.back(result: {'returnTo': '/'});
|
||||
} else {
|
||||
MyToast().tip(
|
||||
title: '投诉失败',
|
||||
position: 'center',
|
||||
type: 'error',
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
print('投诉失败: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
key: scaffoldKey,
|
||||
backgroundColor: Colors.white,
|
||||
appBar: AppBar(
|
||||
centerTitle: true,
|
||||
backgroundColor: Colors.white,
|
||||
foregroundColor: Colors.black,
|
||||
elevation: 0,
|
||||
title: const Text(
|
||||
'举报',
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
body: ScrollConfiguration(
|
||||
behavior: CustomScrollBehavior().copyWith(scrollbars: false),
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'您的举报我们将尽快受理,核实后我们将第一时间告知受理结果,请尽量提交完整的举报描述',
|
||||
style: TextStyle(fontSize: 13, color: Colors.grey, height: 1.4),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
// 两列布局的投诉选项
|
||||
reasonTypeData.isEmpty
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: GridView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 2,
|
||||
crossAxisSpacing: 8,
|
||||
mainAxisSpacing: 2,
|
||||
childAspectRatio: 4,
|
||||
),
|
||||
itemCount: reasonTypeData.length,
|
||||
itemBuilder: (context, index) {
|
||||
return _buildRadioItem(index);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
const Text(
|
||||
'举报描述(选填)',
|
||||
style: TextStyle(fontSize: 14, color: Colors.black54, fontWeight: FontWeight.w500),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.grey.shade300),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: TextField(
|
||||
controller: _reportController,
|
||||
maxLines: 5,
|
||||
maxLength: 32,
|
||||
cursorColor: Colors.black54,
|
||||
style: const TextStyle(
|
||||
color: Colors.black54,
|
||||
fontSize: 14,
|
||||
),
|
||||
decoration: const InputDecoration(
|
||||
contentPadding: EdgeInsets.all(12),
|
||||
border: InputBorder.none,
|
||||
hintText: '请输入内容',
|
||||
hintStyle: TextStyle(color: Colors.grey, fontSize: 13),
|
||||
),
|
||||
),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Text(
|
||||
'${_reportController.text.length}/32',
|
||||
style: const TextStyle(fontSize: 12, color: Colors.grey),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: _selectedIndex != null ? _submitReport : null,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.red,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
),
|
||||
child: const Text('提交', style: TextStyle(fontSize: 15)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRadioItem(int index) {
|
||||
final item = reasonTypeData[index];
|
||||
final isSelected = _selectedIndex == index;
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_selectedIndex = index;
|
||||
});
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 20,
|
||||
height: 20,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: isSelected ? Colors.green : Colors.grey,
|
||||
width: 1.5,
|
||||
),
|
||||
),
|
||||
child: isSelected
|
||||
? Center(
|
||||
child: Container(
|
||||
width: 12,
|
||||
height: 12,
|
||||
decoration: const BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.green,
|
||||
),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
item['dictLabel'] ?? '未知原因',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: isSelected ? Colors.green : Colors.black54,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _submitReport() {
|
||||
if (_selectedIndex == null) {
|
||||
MyToast().tip(
|
||||
title: '请选择举报原因!',
|
||||
position: 'center',
|
||||
type: 'error',
|
||||
);
|
||||
return;
|
||||
}
|
||||
// 获取选中的原因
|
||||
final selectedReason = reasonTypeData[_selectedIndex!];
|
||||
final reasonText = _reportController.text;
|
||||
doReport(selectedReason['dictValue'], reasonText);
|
||||
}
|
||||
}
|
||||
@ -1,19 +1,26 @@
|
||||
/// 首页模板
|
||||
library;
|
||||
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:card_swiper/card_swiper.dart';
|
||||
import 'package:dynamic_tabbar/dynamic_tabbar.dart';
|
||||
import 'package:easy_refresh/easy_refresh.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:loopin/IM/im_friend_listeners.dart';
|
||||
import 'package:loopin/components/backtop.dart';
|
||||
import 'package:loopin/api/shop_api.dart';
|
||||
import 'package:loopin/components/custom_pageview_indicator.dart';
|
||||
import 'package:loopin/components/custom_sticky_header.dart';
|
||||
import 'package:loopin/components/empty_tip.dart';
|
||||
import 'package:loopin/components/loading.dart';
|
||||
import 'package:loopin/components/network_or_asset_image.dart';
|
||||
import 'package:loopin/controller/shop_index_controller.dart';
|
||||
import 'package:loopin/service/http.dart';
|
||||
import 'package:loopin/styles/index.dart';
|
||||
import 'package:loopin/utils/index.dart';
|
||||
import 'package:loopin/utils/network_utils.dart';
|
||||
|
||||
import '../../behavior/custom_scroll_behavior.dart';
|
||||
import '../../components/backtop.dart';
|
||||
|
||||
class IndexPage extends StatefulWidget {
|
||||
const IndexPage({super.key});
|
||||
@ -22,30 +29,237 @@ class IndexPage extends StatefulWidget {
|
||||
State<IndexPage> createState() => _IndexPageState();
|
||||
}
|
||||
|
||||
class _IndexPageState extends State<IndexPage> with SingleTickerProviderStateMixin {
|
||||
class _IndexPageState extends State<IndexPage> with TickerProviderStateMixin {
|
||||
///-----------
|
||||
|
||||
// 瀑布流列表
|
||||
List waterfallData = [
|
||||
{
|
||||
'price': 199.00,
|
||||
'title': '韩料界的萨莉亚!',
|
||||
'shop': '萨莉亚专卖店',
|
||||
'image': 'https://qcloud.dpfile.com/pc/1c3egbzM_ICz90dhi6MAiTsazjxWYQcHCd-sbpD1Wqtph2eIJA04NCRvoGqL4_opG45IiB1YIyNuDTtqzVRwesm_qA1Pf8rFcayTY-n-rG8.jpg',
|
||||
'saleNum': '2.1万'
|
||||
},
|
||||
{
|
||||
'price': 1499.90,
|
||||
'title': '茅台(MOUTAI)飞天 53%vol 500ml 贵州茅台酒(带杯)',
|
||||
'shop': '茅台京东自营旗舰店',
|
||||
'image': 'https://img13.360buyimg.com/n1/jfs/t1/97097/12/15694/245806/5e7373e6Ec4d1b0ac/9d8c13728cc2544d.jpg',
|
||||
'saleNum': '1254'
|
||||
},
|
||||
{
|
||||
'price': 18.90,
|
||||
'title': '上海街头苹果糖!一口一个不吱声',
|
||||
'shop': '芝洛洛自营旗舰店',
|
||||
'image': 'https://p0.meituan.net/coverpic/f0eefdfa02619fb09ca53eacd4d97231123115.jpg',
|
||||
'saleNum': '1.2万'
|
||||
},
|
||||
{
|
||||
'price': 59.00,
|
||||
'title': '谁懂,就是这个菜,尝了第一口,立马决定加单了,真正的咸甜永动机啊🍬 去过云南的朋友都知道,当地的乳扇真的很好吃。',
|
||||
'shop': '薄荷牛舌卷旗舰店',
|
||||
'image': 'https://qcloud.dpfile.com/pc/UcW-v6AN1TxVTt9--5Kaw2-t4W55jUhEG_pM5S-w_AQ4IP3z9WxHzwJ9fOthIjEYY0q73sB2DyQcgmKUxZFQtw.jpg',
|
||||
'saleNum': '1639'
|
||||
},
|
||||
{
|
||||
'price': 2499.00,
|
||||
'title': '小米 REDMI K80 国家补贴 第三代骁龙 8 6550mAh大电池 澎湃OS 玄夜黑 12GB+256GB 红米5G至尊手机',
|
||||
'shop': '小米京东自营旗舰店',
|
||||
'image': 'https://img10.360buyimg.com/n1/s450x450_jfs/t1/264409/38/13856/102861/678dcfdaFb723c58f/5b97cf154bbba96c.jpg',
|
||||
'saleNum': '9726'
|
||||
},
|
||||
{
|
||||
'price': 1.00,
|
||||
'title': '圣菲尔伯爵法国红酒Saintfilcount干红葡萄酒珍藏13.5度单瓶送礼红酒 一元试饮',
|
||||
'shop': '小森葡萄酒专营店',
|
||||
'image': 'https://img10.360buyimg.com/n7/jfs/t1/226168/23/3411/118733/65537e5fF2db2d109/7d1d11a8013d6e8f.jpg',
|
||||
'saleNum': '9.9万'
|
||||
},
|
||||
{
|
||||
'price': 42.00,
|
||||
'title': '美的(Midea)LED便携充电小台灯书桌学习阅读灯学生宿舍卧室床头灯学习台灯',
|
||||
'shop': '美的(Midea)旗舰店',
|
||||
'image': 'https://img14.360buyimg.com/mobilecms/s360x360_jfs/t1/226233/4/10194/156936/658e8f88Fcfc9cb40/cea4a48783f11a7a.jpg',
|
||||
'saleNum': '5106'
|
||||
},
|
||||
{
|
||||
'price': 22.90,
|
||||
'title': '蒙都 羊杂500g 加热即食 京东超市肉干肉脯及礼包11.11真便宜',
|
||||
'shop': '蒙都旗舰店',
|
||||
'image': 'https://img10.360buyimg.com/n7/jfs/t1/155306/32/25324/231912/62d22fb8E4ffab855/c6001ee702fb240a.jpg',
|
||||
'saleNum': '1.6万'
|
||||
},
|
||||
{
|
||||
'price': 19.90,
|
||||
'title': '『 江西炒米粉 』本次最佳😋香就一个字话。锅气的香🔥干辣椒的焦香🌶️油的润香🐷蔬菜混合的清香🥬',
|
||||
'shop': '去月球野餐嗎',
|
||||
'image': 'https://qcloud.dpfile.com/pc/pOAOL-DQRBWfkVZIWYVoy0mMQf6_UutNlOpEpGkT_nz3b1n7ZbpikPgtXMhMsjXNY0q73sB2DyQcgmKUxZFQtw.jpg',
|
||||
'saleNum': '3.2万'
|
||||
},
|
||||
{
|
||||
'price': 109.00,
|
||||
'title': '附近新开业的,作为江西人当然要去试试。点了几个家常菜。',
|
||||
'shop': '辣评新开江西菜',
|
||||
'image': 'https://qcloud.dpfile.com/pc/HePD48CFNnS0kMZyf3Q391wxaW_zVgHimctthH__J6UI54HLPUkNt5e3qtP4Nl2G_aW_B6sGElzX-tSmYRvRnQxxxek7cKy7_R0W-KdxWUk.jpg',
|
||||
'saleNum': '8764'
|
||||
},
|
||||
];
|
||||
// 列表
|
||||
RxList dataList = [].obs;
|
||||
// 是否加载中
|
||||
RxBool isLoading = false.obs;
|
||||
RxBool isInitLoading = true.obs;
|
||||
RxBool hasMore = true.obs;
|
||||
|
||||
//
|
||||
RxInt currentIndex = 0.obs;
|
||||
TextEditingController textEditingController = TextEditingController();
|
||||
FocusNode focusNode = FocusNode();
|
||||
TabController? tabController;
|
||||
// 分类列表
|
||||
// List cateList = [
|
||||
// {
|
||||
// 'id': 1,
|
||||
// 'list': [
|
||||
// {
|
||||
// 'icon': 'order.svg',
|
||||
// 'label': '我的订单',
|
||||
// },
|
||||
// {
|
||||
// 'icon': 'chongzhi.svg',
|
||||
// 'label': '充值中心',
|
||||
// },
|
||||
// {'icon': 'qianbao.svg', 'label': '余额'},
|
||||
// {'icon': 'comment.svg', 'label': '评价中心'}
|
||||
// ]
|
||||
// }
|
||||
// ];
|
||||
final ScrollController pageScrollController = ScrollController();
|
||||
late ShopIndexController controller;
|
||||
RxList<dynamic> tabList = <dynamic>[].obs;
|
||||
|
||||
///轮播图数据
|
||||
RxList<dynamic> swiperData = <dynamic>[].obs;
|
||||
late ScrollController scrollController = ScrollController();
|
||||
final PageController pageController = PageController();
|
||||
// 滚动位置
|
||||
double scrollOffset = 0;
|
||||
int page = 1;
|
||||
|
||||
final NetworkUtils networkUtils = NetworkUtils();
|
||||
|
||||
/// 初始化 Tab 分类
|
||||
Future<void> initTabs() async {
|
||||
page = 1;
|
||||
currentIndex.value = 0;
|
||||
isLoading.value = false;
|
||||
hasMore.value = true;
|
||||
dataList.value = [];
|
||||
isInitLoading.value = true;
|
||||
|
||||
// 赋值 tab 数据
|
||||
final res = await Http.post(ShopApi.shopCategory, data: {
|
||||
'level': 1,
|
||||
});
|
||||
final data = res['data'] as List<dynamic>;
|
||||
tabList.value = data;
|
||||
// 如果之前有 controller,要先释放掉
|
||||
tabController?.dispose();
|
||||
tabController = TabController(
|
||||
initialIndex: 0,
|
||||
length: tabList.length,
|
||||
vsync: this,
|
||||
);
|
||||
if (tabList.isNotEmpty) {
|
||||
loadSwiperData();
|
||||
loadData(currentIndex.value);
|
||||
}
|
||||
}
|
||||
|
||||
/// 加载swiper数据
|
||||
Future<void> loadSwiperData() async {
|
||||
final res = await Http.post(ShopApi.shopSwiperList, data: {
|
||||
'type': 1,
|
||||
});
|
||||
final data = res['data'];
|
||||
logger.w(res);
|
||||
swiperData.assignAll(data);
|
||||
}
|
||||
|
||||
/// 切换数据
|
||||
Future<void> changeData(int index) async {
|
||||
dataList.clear();
|
||||
if (isLoading.value) return;
|
||||
isLoading.value = true;
|
||||
final res = await Http.post(ShopApi.shopList, data: {
|
||||
'size': 10,
|
||||
'current': page,
|
||||
'categoryId': tabList[index]['id'],
|
||||
});
|
||||
|
||||
final data = res['data']['records'];
|
||||
final total = res['data']['total'];
|
||||
logger.w(res);
|
||||
if (dataList.length >= total) {
|
||||
hasMore.value = false;
|
||||
isLoading.value = false;
|
||||
return;
|
||||
}
|
||||
dataList.value = data;
|
||||
page += 1;
|
||||
isLoading.value = false;
|
||||
isInitLoading.value = false;
|
||||
}
|
||||
|
||||
/// 加载pageview数据
|
||||
Future<void> loadData(int index) async {
|
||||
if (isLoading.value) return;
|
||||
isLoading.value = true;
|
||||
final res = await Http.post(ShopApi.shopList, data: {
|
||||
'size': 10,
|
||||
'current': page,
|
||||
'categoryId': tabList[index]['id'],
|
||||
});
|
||||
|
||||
final data = res['data']['records'];
|
||||
final total = res['data']['total'];
|
||||
logger.w(res);
|
||||
if (dataList.length >= total) {
|
||||
hasMore.value = false;
|
||||
isLoading.value = false;
|
||||
return;
|
||||
}
|
||||
dataList.addAll(data);
|
||||
page += 1;
|
||||
logger.e(page);
|
||||
isLoading.value = false;
|
||||
isInitLoading.value = false;
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
scrollController.addListener(() {
|
||||
setState(() {
|
||||
scrollOffset = scrollController.offset;
|
||||
});
|
||||
if (scrollController.position.pixels == scrollController.position.maxScrollExtent) {
|
||||
debugPrint('[index]滚动到底部');
|
||||
if (!isLoading.value) {
|
||||
loadData(currentIndex.value);
|
||||
}
|
||||
}
|
||||
});
|
||||
// 监听网络恢复
|
||||
ever(
|
||||
networkUtils.isConnected,
|
||||
(status) {
|
||||
logger.e('商品首页当前网络状态:$status');
|
||||
if (status) {
|
||||
initTabs();
|
||||
}
|
||||
},
|
||||
);
|
||||
// 初始化加载
|
||||
initTabs();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
scrollController.dispose();
|
||||
pageController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
// 瀑布流卡片
|
||||
Widget cardList(item) {
|
||||
if (item == null) {
|
||||
return Loading(
|
||||
title: '加载中',
|
||||
);
|
||||
}
|
||||
return GestureDetector(
|
||||
child: Container(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
@ -59,11 +273,10 @@ class _IndexPageState extends State<IndexPage> with SingleTickerProviderStateMix
|
||||
]),
|
||||
child: Column(
|
||||
children: [
|
||||
// Image.network(),
|
||||
NetworkOrAssetImage(
|
||||
imageUrl: '${item['pic']}',
|
||||
width: double.infinity,
|
||||
placeholderAsset: 'assets/images/bk.jpg',
|
||||
placeholderAsset: 'assets/images/wait_loading.png',
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 5.0),
|
||||
@ -113,219 +326,256 @@ class _IndexPageState extends State<IndexPage> with SingleTickerProviderStateMix
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
controller = Get.find<ShopIndexController>();
|
||||
// controller.initTabs(vsync: this);
|
||||
controller.initTabs();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey[50],
|
||||
body: Column(
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
focusNode.unfocus();
|
||||
},
|
||||
child: Scaffold(
|
||||
body: ScrollConfiguration(
|
||||
behavior: CustomScrollBehavior().copyWith(scrollbars: false),
|
||||
child: RefreshIndicator(
|
||||
color: FStyle.primaryColor,
|
||||
onRefresh: () async {
|
||||
await initTabs();
|
||||
},
|
||||
child: CustomScrollView(
|
||||
scrollBehavior: CustomScrollBehavior().copyWith(scrollbars: false),
|
||||
controller: scrollController,
|
||||
slivers: [
|
||||
SliverAppBar(
|
||||
backgroundColor: Colors.transparent,
|
||||
foregroundColor: Colors.white,
|
||||
pinned: true,
|
||||
expandedHeight: 200.0,
|
||||
titleSpacing: 10.0,
|
||||
// 搜索框(高斯模糊背景)
|
||||
title: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(30.0),
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0),
|
||||
child: Container(
|
||||
height: 45.0,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withAlpha(150),
|
||||
),
|
||||
child: TextField(
|
||||
focusNode: focusNode,
|
||||
controller: textEditingController,
|
||||
decoration: InputDecoration(
|
||||
isDense: true,
|
||||
hintText: "热销商品",
|
||||
hintStyle: TextStyle(fontSize: 15.0),
|
||||
prefixIcon: Icon(
|
||||
Icons.search,
|
||||
color: Colors.black38,
|
||||
size: 21.0,
|
||||
),
|
||||
suffixIcon: Container(
|
||||
padding: EdgeInsets.only(right: 15.0),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: 10.0,
|
||||
children: [
|
||||
_buildTopSection(),
|
||||
// 内容区域
|
||||
Expanded(
|
||||
child: controller.tabList.isEmpty
|
||||
? Center(child: CircularProgressIndicator())
|
||||
: Obx(
|
||||
() {
|
||||
final tabs = controller.tabList.asMap().entries.map((entry) {
|
||||
final idx = entry.key;
|
||||
final item = entry.value;
|
||||
return TabData(
|
||||
index: idx,
|
||||
title: Tab(
|
||||
child: Center(
|
||||
child: Text(
|
||||
item['name'] ?? '',
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: false,
|
||||
),
|
||||
),
|
||||
),
|
||||
content: _buildTabContent(idx),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
focusNode.unfocus();
|
||||
if (textEditingController.text.isNotEmpty) {
|
||||
// 去搜索结果页,支持带着搜索文字和搜索tab索引
|
||||
Get.toNamed(
|
||||
'/search-result',
|
||||
arguments: {'searchWords': textEditingController.text, 'tab': 1},
|
||||
);
|
||||
}).toList();
|
||||
return DynamicTabBarWidget(
|
||||
onTabControllerUpdated: (tabController) {
|
||||
// controller.tabController = tabController;
|
||||
// controller.initTabs(changeController: tabController, vsync: this);
|
||||
controller.addTabListener(tabController);
|
||||
// 强制选中第一个
|
||||
if (tabController.index != 0) {
|
||||
tabController.animateTo(0);
|
||||
}
|
||||
},
|
||||
onTabChanged: (index) {
|
||||
logger.w("当前选中ssssstab: $index");
|
||||
},
|
||||
dynamicTabs: tabs,
|
||||
tabAlignment: TabAlignment.start,
|
||||
isScrollable: true,
|
||||
showNextIcon: false, // 显示前进图标
|
||||
showBackIcon: false, // 显示后退图标
|
||||
labelColor: FStyle.primaryColor,
|
||||
indicatorColor: FStyle.primaryColor,
|
||||
overlayColor: WidgetStateProperty.all(Colors.transparent),
|
||||
unselectedLabelColor: Colors.black87,
|
||||
indicator: const UnderlineTabIndicator(
|
||||
borderSide: BorderSide(color: Color.fromARGB(255, 236, 108, 49), width: 2.0),
|
||||
),
|
||||
unselectedLabelStyle: const TextStyle(
|
||||
fontSize: 14.0,
|
||||
fontFamily: 'Microsoft YaHei',
|
||||
),
|
||||
labelStyle: const TextStyle(
|
||||
fontSize: 14.0,
|
||||
fontFamily: 'Microsoft YaHei',
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
dividerHeight: 0,
|
||||
);
|
||||
},
|
||||
),
|
||||
child: Text('搜索'),
|
||||
),
|
||||
],
|
||||
),
|
||||
floatingActionButton: Obx(() {
|
||||
final tabIndex = controller.currentTabIndex.value;
|
||||
final currentTab = controller.tabs[tabIndex];
|
||||
if (currentTab == null) return const SizedBox.shrink();
|
||||
|
||||
return Backtop(
|
||||
controller: currentTab.scrollController,
|
||||
offset: currentTab.scrollOffset.value,
|
||||
);
|
||||
}),
|
||||
),
|
||||
contentPadding: EdgeInsets.symmetric(vertical: 0, horizontal: 10.0),
|
||||
border: OutlineInputBorder(borderSide: BorderSide.none, borderRadius: BorderRadius.circular(30.0))),
|
||||
cursorColor: Colors.black,
|
||||
onSubmitted: (value) {
|
||||
focusNode.unfocus();
|
||||
if (value.isNotEmpty) {
|
||||
// 去搜索结果页,支持带着搜索文字和搜索tab索引
|
||||
Get.toNamed(
|
||||
'/search-result',
|
||||
arguments: {'searchWords': value, 'tab': 1},
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// 自定义伸缩区域(轮播图)
|
||||
flexibleSpace: Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [Color(0xFFFF5000), Color(0xFFfcaec4)],
|
||||
),
|
||||
),
|
||||
child: FlexibleSpaceBar(
|
||||
background: Obx(() {
|
||||
// 如果数据为空,返回一个空容器或占位图
|
||||
if (swiperData.isEmpty) return SizedBox.shrink();
|
||||
|
||||
// 构建顶部固定区域
|
||||
Widget _buildTopSection() {
|
||||
if (controller.swiperData.isEmpty) {
|
||||
return SizedBox();
|
||||
}
|
||||
return Column(
|
||||
children: [
|
||||
// 轮播图
|
||||
Obx(() {
|
||||
if (controller.swiperData.isEmpty) return const SizedBox.shrink();
|
||||
if (controller.swiperData.length == 1) {
|
||||
final imageUrl = controller.swiperData.first['images'] ?? '';
|
||||
return Image.network(imageUrl, fit: BoxFit.fill, width: double.infinity, height: 240);
|
||||
}
|
||||
return SizedBox(
|
||||
width: double.infinity,
|
||||
height: 240,
|
||||
child: Swiper(
|
||||
itemCount: controller.swiperData.length,
|
||||
autoplay: true,
|
||||
loop: true,
|
||||
pagination: const SwiperPagination(
|
||||
child: Swiper.children(
|
||||
pagination: SwiperPagination(
|
||||
builder: DotSwiperPaginationBuilder(
|
||||
color: Colors.white70,
|
||||
activeColor: Colors.white,
|
||||
size: 6.0,
|
||||
activeSize: 8.0,
|
||||
space: 4.0,
|
||||
),
|
||||
),
|
||||
itemBuilder: (context, index) {
|
||||
final imageUrl = controller.swiperData[index]['images'] ?? '';
|
||||
return imageUrl.isNotEmpty ? Image.network(imageUrl, fit: BoxFit.fill) : const SizedBox.shrink();
|
||||
},
|
||||
indicatorLayout: PageIndicatorLayout.SCALE,
|
||||
children: swiperData.map<Widget>((itm) {
|
||||
return NetworkOrAssetImage(
|
||||
imageUrl: itm['images'],
|
||||
placeholderAsset: 'assets/images/bk.jpg',
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
);
|
||||
}),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// 构建标签页内容
|
||||
Widget _buildTabContent(int index) {
|
||||
final tabState = controller.tabs[index]!;
|
||||
|
||||
return Obx(() {
|
||||
if (tabState.dataList.isEmpty && tabState.isLoading.value) {
|
||||
return Center(
|
||||
child: RefreshProgressIndicator(
|
||||
backgroundColor: Colors.white,
|
||||
color: Color(0xFFFF5000),
|
||||
),
|
||||
);
|
||||
}
|
||||
),
|
||||
),
|
||||
|
||||
// 添加 下拉刷新
|
||||
return EasyRefresh(
|
||||
onRefresh: () async {
|
||||
await controller.refreshData(index);
|
||||
// 分类
|
||||
SliverPersistentHeader(
|
||||
pinned: false,
|
||||
delegate: CustomStickyHeader(
|
||||
child: PreferredSize(
|
||||
preferredSize: Size.fromHeight(110.0),
|
||||
child: Obx(
|
||||
() {
|
||||
return Container(
|
||||
margin: EdgeInsets.all(10.0),
|
||||
padding: EdgeInsets.symmetric(vertical: 10.0),
|
||||
height: 110.0,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(15.0),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: PageView.builder(
|
||||
controller: pageController,
|
||||
itemCount: (tabList.length / 4).ceil(),
|
||||
itemBuilder: (context, pageIndex) {
|
||||
final start = pageIndex * 4;
|
||||
final end = (start + 4) > tabList.length ? tabList.length : (start + 4);
|
||||
final pageItems = tabList.sublist(start, end);
|
||||
return GridView.builder(
|
||||
shrinkWrap: true,
|
||||
padding: EdgeInsets.zero,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 4, // 一行
|
||||
mainAxisSpacing: 0, // 行间距
|
||||
),
|
||||
itemCount: pageItems.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final citem = pageItems[index];
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () {
|
||||
//切换index
|
||||
final globalIndex = start + index;
|
||||
logger.e(globalIndex);
|
||||
if (globalIndex != currentIndex.value) {
|
||||
currentIndex.value = globalIndex;
|
||||
page = 1;
|
||||
isLoading.value = false;
|
||||
changeData(globalIndex);
|
||||
}
|
||||
},
|
||||
header: ClassicHeader(
|
||||
dragText: '下拉刷新',
|
||||
armedText: '释放刷新',
|
||||
readyText: '加载中...',
|
||||
processingText: '加载中...',
|
||||
processedText: '加载完成',
|
||||
messageText: '最后更新于 %T',
|
||||
child: Column(
|
||||
spacing: 3.0,
|
||||
children: [
|
||||
ClipOval(
|
||||
child: NetworkOrAssetImage(
|
||||
imageUrl: citem['icon'],
|
||||
width: 30.0,
|
||||
height: 30.0,
|
||||
placeholderAsset: 'assets/images/wait_loading.png',
|
||||
),
|
||||
child: CustomScrollView(
|
||||
controller: tabState.scrollController,
|
||||
key: PageStorageKey('tab_$index'),
|
||||
physics: const AlwaysScrollableScrollPhysics(), // 确保可下拉
|
||||
slivers: [
|
||||
SliverPadding(
|
||||
padding: EdgeInsets.fromLTRB(10, 10, 10, 10),
|
||||
sliver: tabState.dataList.isEmpty
|
||||
? SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
height: MediaQuery.of(context).size.height - 500, // 给个足够高度让下拉触发
|
||||
child: Center(child: _emptyTip('暂无数据')),
|
||||
),
|
||||
)
|
||||
: SliverMasonryGrid.count(
|
||||
Text(citem['name']),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
// 数量够翻页才显示
|
||||
CustomPageViewIndicator(
|
||||
controller: pageController,
|
||||
count: (tabList.length / 4).ceil(),
|
||||
color: Color(0xFFCECECE),
|
||||
activeColor: Color(0xFFFF5000),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// 瀑布流列表
|
||||
SliverToBoxAdapter(
|
||||
child: Obx(
|
||||
() {
|
||||
if (dataList.isEmpty) {
|
||||
return EmptyTip();
|
||||
}
|
||||
return Container(
|
||||
padding: EdgeInsets.all(10.0),
|
||||
child: Column(
|
||||
children: [
|
||||
MasonryGridView.count(
|
||||
shrinkWrap: true,
|
||||
padding: EdgeInsets.zero,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
crossAxisCount: 2,
|
||||
mainAxisSpacing: 10.0,
|
||||
crossAxisSpacing: 10.0,
|
||||
childCount: tabState.dataList.length,
|
||||
itemBuilder: (context, idx) => cardList(tabState.dataList[idx]),
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 20.0),
|
||||
child: Center(
|
||||
child: tabState.isLoading.value
|
||||
? const Loading(title: 'loading...')
|
||||
: (tabState.dataList.isNotEmpty ? const Text('没有更多数据了') : const SizedBox.shrink()),
|
||||
),
|
||||
),
|
||||
itemCount: dataList.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return cardList(dataList[index]);
|
||||
},
|
||||
),
|
||||
Opacity(opacity: dataList.isNotEmpty && isLoading.value ? 1 : 0, child: Loading(title: 'loading...')),
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// 空状态提示
|
||||
Widget _emptyTip(String text) {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 50),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Image.asset('assets/images/empty.png', width: 50, height: 50),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
text,
|
||||
style: const TextStyle(color: Colors.grey, fontSize: 13),
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
// 返回顶部
|
||||
floatingActionButton: Backtop(controller: scrollController, offset: scrollOffset),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
331
lib/pages/index/index2.dart
Normal file
@ -0,0 +1,331 @@
|
||||
/// 首页模板
|
||||
library;
|
||||
|
||||
import 'package:card_swiper/card_swiper.dart';
|
||||
import 'package:dynamic_tabbar/dynamic_tabbar.dart';
|
||||
import 'package:easy_refresh/easy_refresh.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:loopin/IM/im_friend_listeners.dart';
|
||||
import 'package:loopin/components/backtop.dart';
|
||||
import 'package:loopin/components/loading.dart';
|
||||
import 'package:loopin/components/network_or_asset_image.dart';
|
||||
import 'package:loopin/controller/shop_index_controller.dart';
|
||||
import 'package:loopin/styles/index.dart';
|
||||
import 'package:loopin/utils/index.dart';
|
||||
|
||||
class IndexPage extends StatefulWidget {
|
||||
const IndexPage({super.key});
|
||||
|
||||
@override
|
||||
State<IndexPage> createState() => _IndexPageState();
|
||||
}
|
||||
|
||||
class _IndexPageState extends State<IndexPage> with SingleTickerProviderStateMixin {
|
||||
// 分类列表
|
||||
// List cateList = [
|
||||
// {
|
||||
// 'id': 1,
|
||||
// 'list': [
|
||||
// {
|
||||
// 'icon': 'order.svg',
|
||||
// 'label': '我的订单',
|
||||
// },
|
||||
// {
|
||||
// 'icon': 'chongzhi.svg',
|
||||
// 'label': '充值中心',
|
||||
// },
|
||||
// {'icon': 'qianbao.svg', 'label': '余额'},
|
||||
// {'icon': 'comment.svg', 'label': '评价中心'}
|
||||
// ]
|
||||
// }
|
||||
// ];
|
||||
final ScrollController pageScrollController = ScrollController();
|
||||
late ShopIndexController controller;
|
||||
|
||||
// 瀑布流卡片
|
||||
Widget cardList(item) {
|
||||
return GestureDetector(
|
||||
child: Container(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(15.0), boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withAlpha(5),
|
||||
offset: Offset(0.0, 1.0),
|
||||
blurRadius: 1.0,
|
||||
spreadRadius: 0.0,
|
||||
),
|
||||
]),
|
||||
child: Column(
|
||||
children: [
|
||||
// Image.network(),
|
||||
NetworkOrAssetImage(
|
||||
imageUrl: '${item['pic']}',
|
||||
width: double.infinity,
|
||||
placeholderAsset: 'assets/images/bk.jpg',
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 5.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
spacing: 5.0,
|
||||
children: [
|
||||
Text(
|
||||
'${item['name']}',
|
||||
style: TextStyle(fontSize: 14.0, height: 1.2),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Row(
|
||||
spacing: 5.0,
|
||||
children: [
|
||||
Text.rich(
|
||||
TextSpan(style: TextStyle(color: Colors.red, fontSize: 12.0, fontWeight: FontWeight.w700, fontFamily: 'Arial'), children: [
|
||||
TextSpan(text: '¥'),
|
||||
TextSpan(
|
||||
text: '${item['price']}',
|
||||
style: TextStyle(
|
||||
fontSize: 16.0,
|
||||
)),
|
||||
]),
|
||||
),
|
||||
Text(
|
||||
'已售${Utils.graceNumber(int.parse(item['sales'] ?? '0'))}件',
|
||||
style: TextStyle(color: Colors.grey, fontSize: 10.0),
|
||||
),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
'${item['storeName']}',
|
||||
style: TextStyle(color: Colors.grey, fontSize: 12.0),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
// Get.toNamed('/goods', arguments: item['id']);
|
||||
Get.toNamed('/goods', arguments: {'goodsId': item['id']});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
controller = Get.find<ShopIndexController>();
|
||||
// controller.initTabs(vsync: this);
|
||||
controller.initTabs();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey[50],
|
||||
body: Column(
|
||||
children: [
|
||||
_buildTopSection(),
|
||||
// 内容区域
|
||||
Expanded(
|
||||
child: controller.tabList.isEmpty
|
||||
? Center(child: CircularProgressIndicator())
|
||||
: Obx(
|
||||
() {
|
||||
final tabs = controller.tabList.asMap().entries.map((entry) {
|
||||
final idx = entry.key;
|
||||
final item = entry.value;
|
||||
return TabData(
|
||||
index: idx,
|
||||
title: Tab(
|
||||
child: Center(
|
||||
child: Text(
|
||||
item['name'] ?? '',
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: false,
|
||||
),
|
||||
),
|
||||
),
|
||||
content: _buildTabContent(idx),
|
||||
);
|
||||
}).toList();
|
||||
return DynamicTabBarWidget(
|
||||
onTabControllerUpdated: (tabController) {
|
||||
// controller.tabController = tabController;
|
||||
// controller.initTabs(changeController: tabController, vsync: this);
|
||||
controller.addTabListener(tabController);
|
||||
// 强制选中第一个
|
||||
if (tabController.index != 0) {
|
||||
tabController.animateTo(0);
|
||||
}
|
||||
},
|
||||
onTabChanged: (index) {
|
||||
logger.w("当前选中ssssstab: $index");
|
||||
},
|
||||
dynamicTabs: tabs,
|
||||
tabAlignment: TabAlignment.start,
|
||||
isScrollable: true,
|
||||
showNextIcon: false, // 显示前进图标
|
||||
showBackIcon: false, // 显示后退图标
|
||||
labelColor: FStyle.primaryColor,
|
||||
indicatorColor: FStyle.primaryColor,
|
||||
overlayColor: WidgetStateProperty.all(Colors.transparent),
|
||||
unselectedLabelColor: Colors.black87,
|
||||
indicator: const UnderlineTabIndicator(
|
||||
borderSide: BorderSide(color: Color.fromARGB(255, 236, 108, 49), width: 2.0),
|
||||
),
|
||||
unselectedLabelStyle: const TextStyle(
|
||||
fontSize: 14.0,
|
||||
fontFamily: 'Microsoft YaHei',
|
||||
),
|
||||
labelStyle: const TextStyle(
|
||||
fontSize: 14.0,
|
||||
fontFamily: 'Microsoft YaHei',
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
dividerHeight: 0,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
floatingActionButton: Obx(() {
|
||||
final tabIndex = controller.currentTabIndex.value;
|
||||
final currentTab = controller.tabs[tabIndex];
|
||||
if (currentTab == null) return const SizedBox.shrink();
|
||||
|
||||
return Backtop(
|
||||
controller: currentTab.scrollController,
|
||||
offset: currentTab.scrollOffset.value,
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// 构建顶部固定区域
|
||||
Widget _buildTopSection() {
|
||||
if (controller.swiperData.isEmpty) {
|
||||
return SizedBox();
|
||||
}
|
||||
return Column(
|
||||
children: [
|
||||
// 轮播图
|
||||
Obx(() {
|
||||
if (controller.swiperData.isEmpty) return const SizedBox.shrink();
|
||||
if (controller.swiperData.length == 1) {
|
||||
final imageUrl = controller.swiperData.first['images'] ?? '';
|
||||
return Image.network(imageUrl, fit: BoxFit.fill, width: double.infinity, height: 240);
|
||||
}
|
||||
return SizedBox(
|
||||
width: double.infinity,
|
||||
height: 240,
|
||||
child: Swiper(
|
||||
itemCount: controller.swiperData.length,
|
||||
autoplay: true,
|
||||
loop: true,
|
||||
pagination: const SwiperPagination(
|
||||
builder: DotSwiperPaginationBuilder(
|
||||
color: Colors.white70,
|
||||
activeColor: Colors.white,
|
||||
),
|
||||
),
|
||||
itemBuilder: (context, index) {
|
||||
final imageUrl = controller.swiperData[index]['images'] ?? '';
|
||||
return imageUrl.isNotEmpty ? Image.network(imageUrl, fit: BoxFit.fill) : const SizedBox.shrink();
|
||||
},
|
||||
),
|
||||
);
|
||||
}),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// 构建标签页内容
|
||||
Widget _buildTabContent(int index) {
|
||||
final tabState = controller.tabs[index]!;
|
||||
|
||||
return Obx(() {
|
||||
if (tabState.dataList.isEmpty && tabState.isLoading.value) {
|
||||
return Center(
|
||||
child: RefreshProgressIndicator(
|
||||
backgroundColor: Colors.white,
|
||||
color: Color(0xFFFF5000),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 添加 下拉刷新
|
||||
return EasyRefresh(
|
||||
onRefresh: () async {
|
||||
await controller.refreshData(index);
|
||||
},
|
||||
header: ClassicHeader(
|
||||
dragText: '下拉刷新',
|
||||
armedText: '释放刷新',
|
||||
readyText: '加载中...',
|
||||
processingText: '加载中...',
|
||||
processedText: '加载完成',
|
||||
messageText: '最后更新于 %T',
|
||||
),
|
||||
child: CustomScrollView(
|
||||
controller: tabState.scrollController,
|
||||
key: PageStorageKey('tab_$index'),
|
||||
physics: const AlwaysScrollableScrollPhysics(), // 确保可下拉
|
||||
slivers: [
|
||||
SliverPadding(
|
||||
padding: EdgeInsets.fromLTRB(10, 10, 10, 10),
|
||||
sliver: tabState.dataList.isEmpty
|
||||
? SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
height: MediaQuery.of(context).size.height - 500, // 给个足够高度让下拉触发
|
||||
child: Center(child: _emptyTip('暂无数据')),
|
||||
),
|
||||
)
|
||||
: SliverMasonryGrid.count(
|
||||
crossAxisCount: 2,
|
||||
mainAxisSpacing: 10.0,
|
||||
crossAxisSpacing: 10.0,
|
||||
childCount: tabState.dataList.length,
|
||||
itemBuilder: (context, idx) => cardList(tabState.dataList[idx]),
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 20.0),
|
||||
child: Center(
|
||||
child: tabState.isLoading.value
|
||||
? const Loading(title: 'loading...')
|
||||
: (tabState.dataList.isNotEmpty ? const Text('没有更多数据了') : const SizedBox.shrink()),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// 空状态提示
|
||||
Widget _emptyTip(String text) {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 50),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Image.asset('assets/images/empty.png', width: 50, height: 50),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
text,
|
||||
style: const TextStyle(color: Colors.grey, fontSize: 13),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -240,8 +240,8 @@ class _AllFunctionsPageState extends State<AllFunctionsPage> {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
MyQrcode(
|
||||
prefix: QrTypeCode.tgm,
|
||||
text: isLeader ? '推广码' : '',
|
||||
prefix: isLeader ? QrTypeCode.tgm : QrTypeCode.hym,
|
||||
text: isLeader ? '团长码' : '',
|
||||
)
|
||||
],
|
||||
),
|
||||
|
||||
@ -64,7 +64,10 @@ class _DeleteState extends State<Delete> {
|
||||
MyDialog.toast('验证码不能为空', icon: Icon(Icons.warning), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200)));
|
||||
} else {
|
||||
final dialogController = MyDialog.loading('注销中...');
|
||||
// 执行退出登录逻辑,执行注销逻辑
|
||||
// 执行注销逻辑
|
||||
final del = await Http.post('${CommonApi.revoked}?smsCode=${authObj['smsCode']}');
|
||||
logger.w(del);
|
||||
// 执行退出登录逻辑
|
||||
final loginRes = await ImService.instance.logout();
|
||||
if (loginRes.success) {
|
||||
// 清除存储信息
|
||||
@ -72,11 +75,12 @@ class _DeleteState extends State<Delete> {
|
||||
// 初始化视频
|
||||
final videoController = Get.find<VideoModuleController>();
|
||||
videoController.init();
|
||||
// 执行主席到逻辑
|
||||
final del = await Http.post('${CommonApi.revoked}?smsCode=${authObj['smsCode']}');
|
||||
logger.w(del);
|
||||
//
|
||||
Get.offAllNamed('/');
|
||||
Get.offAllNamed(
|
||||
'/login',
|
||||
predicate: (route) {
|
||||
return route.settings.name == '/';
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,13 +21,14 @@ import 'package:loopin/utils/scan_code_type.dart';
|
||||
import 'package:nested_scroll_view_plus/nested_scroll_view_plus.dart';
|
||||
import 'package:shirne_dialog/shirne_dialog.dart';
|
||||
import 'package:tencent_cloud_chat_sdk/models/v2_tim_follow_info.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import '../../utils/common.dart';
|
||||
|
||||
class PageParams {
|
||||
int page;
|
||||
int pageSize;
|
||||
bool isLoading;
|
||||
RxBool isLoading;
|
||||
RxBool hasMore;
|
||||
int total;
|
||||
bool isInitLoading;
|
||||
@ -35,16 +36,21 @@ class PageParams {
|
||||
PageParams({
|
||||
this.page = 1,
|
||||
this.pageSize = 10,
|
||||
this.isLoading = false,
|
||||
bool isLoading = false,
|
||||
bool hasMore = true,
|
||||
this.total = 0,
|
||||
this.isInitLoading = true,
|
||||
}) : hasMore = hasMore.obs;
|
||||
}) : hasMore = hasMore.obs,
|
||||
isLoading = isLoading.obs;
|
||||
@override
|
||||
String toString() {
|
||||
return 'PageParams(page: $page, pageSize: $pageSize, isLoading: ${isLoading.value}, hasMore: ${hasMore.value})';
|
||||
}
|
||||
|
||||
void init() {
|
||||
page = 1;
|
||||
pageSize = 10;
|
||||
isLoading = false;
|
||||
isLoading.value = false;
|
||||
hasMore.value = true;
|
||||
total = 0;
|
||||
isInitLoading = true;
|
||||
@ -66,7 +72,8 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
|
||||
//用户基本信息
|
||||
// late Rx<V2TimUserFullInfo?> userInfo = Rx<V2TimUserFullInfo?>(null);
|
||||
|
||||
ImUserInfoController? imUserInfoController;
|
||||
// ImUserInfoController? imUserInfoController;
|
||||
late ImUserInfoController imUserInfoController;
|
||||
|
||||
// 关注,互关,粉丝数量
|
||||
late Rx<V2TimFollowInfo?> followInfo = Rx<V2TimFollowInfo?>(null);
|
||||
@ -87,24 +94,25 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
|
||||
|
||||
late Callback tabListener;
|
||||
late Callback scrollListener;
|
||||
late int vlogLikeCount = 0; // 点赞数量
|
||||
late RxInt vlogLikeCount = 0.obs; // 点赞数量
|
||||
|
||||
RxBool isPinned = false.obs; // 是否吸顶
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
imUserInfoController = Get.find<ImUserInfoController>();
|
||||
initControllers();
|
||||
|
||||
// 这里保证委托父类处理滚动时的触底加载
|
||||
scrollListener = () {
|
||||
final pos = scrollController.position;
|
||||
final isNearBottom = pos.pixels >= pos.maxScrollExtent - 100;
|
||||
|
||||
if (!isNearBottom) return;
|
||||
|
||||
if (currentTabIndex.value == 0 && !itemsParams.isLoading && itemsParams.hasMore.value) {
|
||||
if (currentTabIndex.value == 0 && !itemsParams.isLoading.value && itemsParams.hasMore.value) {
|
||||
loadData(0);
|
||||
} else if (currentTabIndex.value == 1 && !favoriteParams.isLoading && favoriteParams.hasMore.value) {
|
||||
} else if (currentTabIndex.value == 1 && !favoriteParams.isLoading.value && favoriteParams.hasMore.value) {
|
||||
loadData(1);
|
||||
}
|
||||
};
|
||||
@ -121,8 +129,6 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
|
||||
}
|
||||
};
|
||||
tabController.addListener(tabListener);
|
||||
|
||||
// loadData(0);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -140,7 +146,7 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
|
||||
try {
|
||||
final resData = await Http.get(CommonApi.accountInfo);
|
||||
if (resData != null && resData['code'] == 200) {
|
||||
vlogLikeCount = resData['data']['vlogLikeCount'] ?? 0;
|
||||
vlogLikeCount.value = resData['data']['vlogLikeCount'] ?? 0;
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
@ -162,13 +168,13 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
|
||||
Future<void> loadData([int? tabIndex]) async {
|
||||
final index = tabIndex ?? currentTabIndex.value;
|
||||
if (index == 0) {
|
||||
if (itemsParams.isLoading || !itemsParams.hasMore.value) return;
|
||||
|
||||
itemsParams.isLoading = true;
|
||||
// itemsParams.isInitLoading = true;
|
||||
if (itemsParams.isLoading.value || !itemsParams.hasMore.value) return;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
itemsParams.isLoading.value = true;
|
||||
});
|
||||
|
||||
final res = await Http.post(VideoApi.myPublicList, data: {
|
||||
"userId": imUserInfoController?.userID.value,
|
||||
"userId": imUserInfoController.userID.value,
|
||||
"yesOrNo": 0,
|
||||
"current": itemsParams.page,
|
||||
"size": itemsParams.pageSize,
|
||||
@ -176,28 +182,25 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
|
||||
final obj = res['data'];
|
||||
final total = obj['total'];
|
||||
final row = obj['records'] ?? [];
|
||||
logger.i(res['data']);
|
||||
// 判断是否还有更多数据
|
||||
logger.e(items.length);
|
||||
// 添加新数据,触发响应式更新
|
||||
items.addAll(row);
|
||||
logger.e(obj);
|
||||
itemsParams.page++;
|
||||
|
||||
if (items.length >= total) {
|
||||
itemsParams.hasMore.value = false;
|
||||
}
|
||||
// 页码加一
|
||||
itemsParams.page++;
|
||||
//
|
||||
itemsParams.isLoading = false;
|
||||
itemsParams.isLoading.value = false;
|
||||
itemsParams.isInitLoading = false;
|
||||
} else if (index == 1) {
|
||||
if (favoriteParams.isLoading || !favoriteParams.hasMore.value) return;
|
||||
|
||||
favoriteParams.isLoading = true;
|
||||
// favoriteParams.isInitLoading = true;
|
||||
if (favoriteParams.isLoading.value || !favoriteParams.hasMore.value) return;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
favoriteParams.isLoading.value = true;
|
||||
});
|
||||
|
||||
final res = await Http.post(VideoApi.myLikedList, data: {
|
||||
"userId": imUserInfoController?.userID.value,
|
||||
"userId": imUserInfoController.userID.value,
|
||||
"yesOrNo": 0,
|
||||
"current": favoriteParams.page,
|
||||
"size": favoriteParams.pageSize,
|
||||
@ -206,14 +209,14 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
|
||||
final total = obj['total'];
|
||||
final row = obj['records'] ?? [];
|
||||
favoriteItems.addAll(row);
|
||||
favoriteParams.page++;
|
||||
|
||||
if (favoriteItems.length >= total) {
|
||||
favoriteParams.hasMore.value = false;
|
||||
}
|
||||
|
||||
favoriteParams.page++;
|
||||
favoriteParams.isLoading = false;
|
||||
favoriteParams.isLoading.value = false;
|
||||
favoriteParams.isInitLoading = false;
|
||||
logger.e(favoriteParams.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@ -255,7 +258,7 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
|
||||
logger.e('用户信息controller未注册');
|
||||
return;
|
||||
}
|
||||
final res = await ImService.instance.getUserFollowInfo(userIDList: [imUserInfoController?.userID.value ?? '']);
|
||||
final res = await ImService.instance.getUserFollowInfo(userIDList: [imUserInfoController.userID.value ?? '']);
|
||||
if (res.success) {
|
||||
//这里少个点赞,从服务端获取
|
||||
// followersCount粉丝,多少人关注了我,mutualFollowersCount互关,followingCount我关注了多少人
|
||||
@ -269,18 +272,18 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
|
||||
void deletePersonalVideo(videoId) async {
|
||||
logger.i('删除视频$videoId');
|
||||
try {
|
||||
final res = await Http.post('${VideoApi.deleteVideo}?id=$videoId', data: {});
|
||||
final res = await Http.get('${VideoApi.deleteVideo}?id=$videoId');
|
||||
logger.i('删除成功响应$res');
|
||||
if (res != null && res['code'] == 200) {
|
||||
MyDialog.toast('删除成功', icon: const Icon(Icons.check_circle), style: ToastStyle(backgroundColor: Colors.green.withAlpha(200)));
|
||||
loadData(0);
|
||||
refreshData();
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
// 二维码名片弹窗
|
||||
void qrcodeAlertDialog(BuildContext context) {
|
||||
final role = imUserInfoController?.role.value ?? 0;
|
||||
final role = imUserInfoController.role.value ?? 0;
|
||||
final isLeader = Utils.hasRole(role, 5);
|
||||
showDialog(
|
||||
context: context,
|
||||
@ -384,9 +387,49 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
|
||||
// 加载刷新逻辑
|
||||
},
|
||||
actions: [
|
||||
// _buildIcon('assets/images/svg/service.svg', () {
|
||||
// logger.i('点击客服按钮');
|
||||
// }),
|
||||
_buildIcon('assets/images/svg/service.svg', () {
|
||||
logger.i('点击客服按钮');
|
||||
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.mobile_friendly, color: Colors.black),
|
||||
title: const Text('联系客服', style: TextStyle(color: Colors.black)),
|
||||
onTap: () async {
|
||||
Get.back();
|
||||
final phoneNumber = '18832510385'; // 客服电话
|
||||
final url = Uri.parse('tel:$phoneNumber');
|
||||
try {
|
||||
await launchUrl(
|
||||
url,
|
||||
mode: LaunchMode.platformDefault,
|
||||
);
|
||||
} catch (e) {
|
||||
Get.snackbar(
|
||||
'错误',
|
||||
'无法拨打电话',
|
||||
duration: Duration(seconds: 3),
|
||||
backgroundColor: Colors.red.withAlpha(230),
|
||||
colorText: Colors.white,
|
||||
icon: const Icon(Icons.error_outline, color: Colors.white),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}),
|
||||
const SizedBox(width: 8.0),
|
||||
_buildIcon('assets/images/svg/setting.svg', () {
|
||||
logger.i('点击设置按钮');
|
||||
@ -402,9 +445,9 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 10),
|
||||
Obx(() => _buildStatsCard()),
|
||||
_buildStatsCard(),
|
||||
const SizedBox(height: 10),
|
||||
Obx(() => _buildInfoDesc(context)),
|
||||
_buildInfoDesc(context),
|
||||
const SizedBox(height: 10.0),
|
||||
_buildOrderCard(context),
|
||||
const SizedBox(height: 10.0),
|
||||
@ -499,8 +542,8 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
|
||||
}
|
||||
|
||||
Widget _buildInfoDesc(BuildContext context) {
|
||||
final tx = imUserInfoController?.signature;
|
||||
if (tx == null || tx.isEmpty) {
|
||||
final tx = imUserInfoController.signature;
|
||||
if (tx.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return Container(
|
||||
@ -520,11 +563,13 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(
|
||||
'${imUserInfoController!.signature}',
|
||||
child: Obx(
|
||||
() => Text(
|
||||
'${imUserInfoController.signature}',
|
||||
style: const TextStyle(fontSize: 16),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
));
|
||||
}
|
||||
@ -538,11 +583,19 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
|
||||
if (listToShow.isEmpty) {
|
||||
return emptyTip('暂无相关数据');
|
||||
}
|
||||
return NotificationListener<ScrollNotification>(
|
||||
onNotification: (notification) {
|
||||
if (notification is ScrollUpdateNotification) {
|
||||
final metrics = notification.metrics;
|
||||
final isNearBottom = metrics.pixels == metrics.maxScrollExtent;
|
||||
|
||||
return Obx(() {
|
||||
return CustomScrollView(
|
||||
// physics: !isPinned.value ? NeverScrollableScrollPhysics() : AlwaysScrollableScrollPhysics(),
|
||||
// physics: AlwaysScrollableScrollPhysics(),
|
||||
if (isNearBottom && !params.isLoading.value && params.hasMore.value) {
|
||||
loadData(tabIndex);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
child: CustomScrollView(
|
||||
key: PageStorageKey('myindex_$tabIndex'),
|
||||
slivers: [
|
||||
SliverPadding(
|
||||
@ -571,15 +624,25 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 20.0),
|
||||
padding: EdgeInsets.symmetric(vertical: 20.0),
|
||||
child: Center(
|
||||
child: params.hasMore.value ? CircularProgressIndicator() : Text('没有更多数据了'),
|
||||
child: Obx(
|
||||
() {
|
||||
if (params.isLoading.value) {
|
||||
return SafeArea(top: false, child: CircularProgressIndicator());
|
||||
} else if (!params.hasMore.value) {
|
||||
return SafeArea(top: false, child: Text('没有更多数据了'));
|
||||
} else {
|
||||
return SizedBox(height: 20);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildVdCard(item, tabIndex) {
|
||||
@ -688,6 +751,8 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
|
||||
}
|
||||
|
||||
Widget _buildFlexibleSpace() {
|
||||
logger.e('定位');
|
||||
logger.w(imUserInfoController);
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final double maxHeight = 180;
|
||||
@ -699,14 +764,17 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
// 背景图 Obx
|
||||
Obx(() {
|
||||
final bgUrl = imUserInfoController?.customInfo['coverBg'] ?? '';
|
||||
Obx(
|
||||
() {
|
||||
final map = imUserInfoController.customInfo ?? {};
|
||||
final bgUrl = map['coverBg'] ?? '';
|
||||
return NetworkOrAssetImage(
|
||||
imageUrl: bgUrl,
|
||||
placeholderAsset: 'assets/images/bk.jpg',
|
||||
fit: BoxFit.cover,
|
||||
);
|
||||
}),
|
||||
},
|
||||
),
|
||||
|
||||
Positioned(
|
||||
left: 15,
|
||||
@ -719,10 +787,10 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
|
||||
children: [
|
||||
// 头像 Obx
|
||||
Obx(() {
|
||||
final faceUrl = imUserInfoController?.faceUrl.value ?? '';
|
||||
final faceUrl = imUserInfoController.faceUrl.value;
|
||||
return ClipOval(
|
||||
child: NetworkOrAssetImage(
|
||||
imageUrl: faceUrl,
|
||||
imageUrl: faceUrl ?? '',
|
||||
width: 80,
|
||||
height: 80,
|
||||
),
|
||||
@ -735,27 +803,24 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
// 昵称 Obx
|
||||
Obx(
|
||||
() {
|
||||
final nickname = imUserInfoController?.nickname.value ?? '';
|
||||
return Container(
|
||||
// 昵称
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withAlpha(76),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Text(
|
||||
child: Obx(() {
|
||||
final nickname = imUserInfoController.nickname.value ?? '';
|
||||
return Text(
|
||||
nickname.isNotEmpty ? nickname : '昵称',
|
||||
// '啊啊啊啊啊啊啊啊',
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
}),
|
||||
),
|
||||
|
||||
// 团长的二维码
|
||||
@ -809,15 +874,15 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
// 用户ID Obx
|
||||
Obx(() {
|
||||
final userId = imUserInfoController?.userID.value ?? '';
|
||||
return Container(
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withAlpha(76),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: InkWell(
|
||||
child: Obx(() {
|
||||
final userId = imUserInfoController.userID.value ?? '';
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
Clipboard.setData(ClipboardData(text: userId));
|
||||
MyDialog.toast(
|
||||
@ -827,9 +892,9 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
|
||||
);
|
||||
},
|
||||
child: Text('ID:$userId', style: const TextStyle(fontSize: 12, color: Colors.white)),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -856,9 +921,10 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
// '已售${Utils.graceNumber(int.tryParse(vlogLikeCount?.toString() ?? '0') ?? 0)}',
|
||||
Column(children: [
|
||||
Text(Utils.graceNumber(vlogLikeCount), style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold)),
|
||||
Obx(() {
|
||||
return Text(Utils.graceNumber(vlogLikeCount.value), style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold));
|
||||
}),
|
||||
SizedBox(height: 3.0),
|
||||
Text('获赞')
|
||||
]),
|
||||
@ -869,7 +935,7 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
|
||||
refreshData();
|
||||
},
|
||||
child: Column(children: [
|
||||
Text('${followInfo.value?.mutualFollowersCount ?? 0}', style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold)),
|
||||
Obx(() => Text('${followInfo.value?.mutualFollowersCount ?? 0}', style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold))),
|
||||
SizedBox(height: 3.0),
|
||||
Text('互关')
|
||||
]),
|
||||
@ -881,7 +947,7 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
|
||||
refreshData();
|
||||
},
|
||||
child: Column(children: [
|
||||
Text('${followInfo.value?.followingCount ?? 0}', style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold)),
|
||||
Obx(() => Text('${followInfo.value?.followingCount ?? 0}', style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold))),
|
||||
SizedBox(height: 3.0),
|
||||
Text('关注')
|
||||
]),
|
||||
@ -893,10 +959,12 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
|
||||
},
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
Obx(
|
||||
() => Text(
|
||||
'${followInfo.value?.followersCount ?? 0}',
|
||||
style: const TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 3.0),
|
||||
const Text('粉丝'),
|
||||
],
|
||||
@ -908,7 +976,7 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
|
||||
}
|
||||
|
||||
Widget _buildOrderCard(BuildContext context) {
|
||||
final role = imUserInfoController?.role.value ?? 0;
|
||||
final role = imUserInfoController.role.value ?? 0;
|
||||
final isLeader = Utils.hasRole(role, 5);
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
|
||||
@ -5,6 +5,7 @@ import 'package:get/get.dart';
|
||||
import 'package:loopin/IM/controller/im_user_info_controller.dart';
|
||||
import 'package:loopin/behavior/custom_scroll_behavior.dart';
|
||||
import 'package:loopin/components/empty_tip.dart';
|
||||
import 'package:loopin/components/my_toast.dart';
|
||||
import 'package:loopin/pages/my/merchant/balance/controller.dart';
|
||||
import 'package:loopin/utils/index.dart';
|
||||
|
||||
@ -61,10 +62,52 @@ class _BalanceState extends State<Balance> {
|
||||
Row(
|
||||
children: [
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
onPressed: () async {
|
||||
// 加充值
|
||||
controller.recharge(money: '0.1');
|
||||
// http
|
||||
// controller.recharge(money: '0.1');
|
||||
final money = await showDialog<String>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
String input = "";
|
||||
return AlertDialog(
|
||||
title: Text("请输入充值金额"),
|
||||
content: TextField(
|
||||
keyboardType: TextInputType.numberWithOptions(decimal: true),
|
||||
decoration: InputDecoration(
|
||||
hintText: "请输入50~200之间的金额",
|
||||
),
|
||||
onChanged: (value) => input = value,
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(), // 取消
|
||||
child: Text("取消"),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
final num? value = num.tryParse(input);
|
||||
if (value == null) {
|
||||
Navigator.of(context).pop();
|
||||
MyToast().tip(title: '请输入正确的金额');
|
||||
return;
|
||||
}
|
||||
if (value < 0 || value > 200) {
|
||||
Navigator.of(context).pop();
|
||||
MyToast().tip(title: '金额必须在 50 ~ 200 之间');
|
||||
return;
|
||||
}
|
||||
Navigator.of(context).pop(input); // 返回合法输入
|
||||
},
|
||||
child: Text("确定"),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
if (money != null && money.isNotEmpty) {
|
||||
controller.recharge(money: money);
|
||||
}
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.orange,
|
||||
@ -74,11 +117,63 @@ class _BalanceState extends State<Balance> {
|
||||
SizedBox(width: 20),
|
||||
//
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
onPressed: () async {
|
||||
// onWithdrawPressed();
|
||||
// 提现
|
||||
// controller.recharge(money: '0.1');
|
||||
controller.withDraw(money: '1000');
|
||||
// http
|
||||
if (controller.balance.value < 10) {
|
||||
MyToast().tip(title: '钱包余额不足');
|
||||
return;
|
||||
}
|
||||
final money = await showDialog<String>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
String input = "";
|
||||
return AlertDialog(
|
||||
title: Text("请输入提现金额"),
|
||||
content: TextField(
|
||||
maxLength: 5,
|
||||
maxLines: 1,
|
||||
keyboardType: TextInputType.numberWithOptions(decimal: true),
|
||||
decoration: InputDecoration(
|
||||
hintText: "当前可提现金额为:${controller.balance.value}",
|
||||
),
|
||||
onChanged: (value) => input = value,
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(), // 取消
|
||||
child: Text("取消"),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
final num? value = num.tryParse(input);
|
||||
if (value == null || value < 0.1) {
|
||||
Navigator.of(context).pop();
|
||||
MyToast().tip(title: '请输入正确的金额');
|
||||
return;
|
||||
}
|
||||
if (value > controller.balance.value) {
|
||||
Navigator.of(context).pop();
|
||||
MyToast().tip(title: '您当前可提现余额为:${controller.balance.value}');
|
||||
return;
|
||||
}
|
||||
if (value > 200) {
|
||||
Navigator.of(context).pop();
|
||||
MyToast().tip(title: '提现金额上限为200');
|
||||
return;
|
||||
}
|
||||
Navigator.of(context).pop(input); // 返回合法输入
|
||||
},
|
||||
child: Text("确定"),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
if (money != null && money.isNotEmpty) {
|
||||
controller.withDraw(money: money);
|
||||
}
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.orange,
|
||||
@ -92,6 +187,25 @@ class _BalanceState extends State<Balance> {
|
||||
}),
|
||||
),
|
||||
|
||||
//提示内容
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: SizedBox(
|
||||
width: double.infinity, // 占满父级宽度
|
||||
child: Text(
|
||||
'提示:\n'
|
||||
'1. 余额不可用于购买商品\n'
|
||||
'2. 余额只支持提现到授权的微信账户内\n'
|
||||
'3. 提现申请通过后将于一个工作日内到账\n',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.red[700],
|
||||
),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// 下半部分:流水记录分页列表
|
||||
Expanded(
|
||||
child: EasyRefresh.builder(
|
||||
@ -146,10 +260,10 @@ class _BalanceState extends State<Balance> {
|
||||
final tx = controller.data[index];
|
||||
|
||||
return ListTile(
|
||||
title: Text(tx.source),
|
||||
subtitle: Text(tx.createTime),
|
||||
title: Text(tx.sourceName), // 来源
|
||||
subtitle: Text(tx.createTime), // 时间
|
||||
trailing: Text(
|
||||
"变动金额:${tx.changeType == 1 ? '+' : '-'}${tx.changeAmount}${tx.id}", // 变动金额
|
||||
"变动金额:${tx.changeType == 1 ? '+' : '-'}${tx.changeAmount}", // 变动金额
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: tx.changeType == 1 ? Colors.green : Colors.red,
|
||||
@ -169,3 +283,21 @@ class _BalanceState extends State<Balance> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _handleSource(String source) {
|
||||
String res;
|
||||
if (source == '3') {
|
||||
res = '充值';
|
||||
} else if (source == '2') {
|
||||
res = '待确认';
|
||||
} else if (source == '1') {
|
||||
res = '提现';
|
||||
} else if (source == '5') {
|
||||
res = '提现退款';
|
||||
} else if (source == '4') {
|
||||
res = '充值退款';
|
||||
} else {
|
||||
res = '未知';
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
@ -5,6 +5,8 @@ import 'package:loopin/IM/im_friend_listeners.dart';
|
||||
import 'package:loopin/api/common_api.dart';
|
||||
import 'package:loopin/pages/my/merchant/balance/model.dart';
|
||||
import 'package:loopin/service/http.dart';
|
||||
import 'package:loopin/styles/index.dart';
|
||||
import 'package:loopin/utils/wechat_business_view.dart';
|
||||
import 'package:loopin/utils/wxsdk.dart';
|
||||
|
||||
class BalanceController extends GetxController {
|
||||
@ -20,17 +22,39 @@ class BalanceController extends GetxController {
|
||||
/// 是否还有更多
|
||||
var hasMore = true.obs;
|
||||
|
||||
//
|
||||
final RxBool buttonLoading = false.obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
getAccount();
|
||||
getData(reset: true);
|
||||
}
|
||||
|
||||
///获取用户账户信息
|
||||
Future<void> getAccount() async {
|
||||
final res = await Http.get(CommonApi.userAccount);
|
||||
logger.w(res);
|
||||
final resData = res['data']['wallet'];
|
||||
if (resData != null) {
|
||||
balance.value = double.parse(resData);
|
||||
} else {
|
||||
balance.value = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
/// 充值
|
||||
Future<void> recharge({required String money}) async {
|
||||
// 获取支付参数
|
||||
final data = {"orderType": "RECHARGE", "clientType": "APP", "paymentMethod": "WECHAT", "paymentClient": "APP", "money": money};
|
||||
final res = await Http.post(CommonApi.addBalance, data: data);
|
||||
final data = {
|
||||
"orderType": "RECHARGE",
|
||||
"clientType": "APP",
|
||||
"paymentMethod": "WECHAT",
|
||||
"paymentClient": "APP",
|
||||
"money": money,
|
||||
};
|
||||
final res = await Http.post(CommonApi.wechatPay, data: data);
|
||||
logger.w(res);
|
||||
final payParams = res['data'];
|
||||
logger.w(payParams);
|
||||
@ -79,30 +103,91 @@ class BalanceController extends GetxController {
|
||||
);
|
||||
return;
|
||||
}
|
||||
//
|
||||
// final res = await Http.post(CommonApi.withdraw, data: {
|
||||
// "money": money,
|
||||
// "method": "1",
|
||||
// });
|
||||
// MyDialog.('提现成功,系统将在一个工作日内将余额提现至您绑定的微信内');
|
||||
|
||||
showDialog(
|
||||
context: Get.context!,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: Text('提现成功'),
|
||||
content: const Text('系统将在一个工作日内将余额提现至您绑定的微信内', style: TextStyle(fontSize: 16.0)),
|
||||
title: Text('提示!'),
|
||||
content: const Text('发起提现申请后,请及时进行确认收款,未进行确认收款金额将于24小后退回', style: TextStyle(fontSize: 16.0)),
|
||||
backgroundColor: Colors.white,
|
||||
surfaceTintColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15.0)),
|
||||
elevation: 2.0,
|
||||
actionsPadding: const EdgeInsets.all(15.0),
|
||||
actions: [
|
||||
TextButton(onPressed: () => {Get.back()}, child: Text('确认')),
|
||||
Obx(() {
|
||||
return TextButton(
|
||||
onPressed: buttonLoading.value
|
||||
? null
|
||||
: () async {
|
||||
buttonLoading.value = true;
|
||||
try {
|
||||
final res = await Http.post(CommonApi.withdraw, data: {
|
||||
"money": money,
|
||||
"method": "1",
|
||||
});
|
||||
logger.w(res);
|
||||
final pkg = res['data']['packageInfo'];
|
||||
await getData(reset: true);
|
||||
await getAccount();
|
||||
|
||||
await onWithdrawPressed(packageInfo: pkg);
|
||||
Get.back();
|
||||
} catch (e) {
|
||||
logger.e("提现失败$e");
|
||||
} finally {
|
||||
buttonLoading.value = false;
|
||||
}
|
||||
},
|
||||
child: buttonLoading.value
|
||||
? const SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(FStyle.primaryColor),
|
||||
),
|
||||
)
|
||||
: const Text('确认'),
|
||||
);
|
||||
}),
|
||||
// TextButton(
|
||||
// onPressed: () async {
|
||||
// //去提现
|
||||
// final res = await Http.post(CommonApi.withdraw, data: {
|
||||
// "money": money,
|
||||
// "method": "1",
|
||||
// });
|
||||
// logger.w(res);
|
||||
// final pkg = res['data']['packageInfo'];
|
||||
// await getData(reset: true);
|
||||
// await getAccount();
|
||||
|
||||
// await onWithdrawPressed(packageInfo: pkg);
|
||||
// Get.back();
|
||||
// },
|
||||
// child: Text('确认'),
|
||||
// ),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
getData(reset: true);
|
||||
}
|
||||
|
||||
Future<void> onWithdrawPressed({required String packageInfo}) async {
|
||||
String encodedPackage = Uri.encodeComponent(packageInfo);
|
||||
String query = 'mchId=1658665710&appId=wxebcdaea31881caab&package=$encodedPackage';
|
||||
logger.e(query);
|
||||
bool success = await WechatBusinessView.openBusinessView(
|
||||
packageInfo: query,
|
||||
);
|
||||
|
||||
if (success) {
|
||||
logger.w("已唤起微信确认收款码");
|
||||
} else {
|
||||
logger.e("唤起失败");
|
||||
}
|
||||
}
|
||||
|
||||
/// 分页数据
|
||||
@ -111,12 +196,16 @@ class BalanceController extends GetxController {
|
||||
logger.w('正在加载中,跳过');
|
||||
return;
|
||||
}
|
||||
|
||||
isLoading.value = true;
|
||||
logger.w('开始加载数据,reset: $reset, currentPage: $currentPage');
|
||||
|
||||
await Future.delayed(const Duration(seconds: 2));
|
||||
final res = await Http.post(CommonApi.bills, data: {
|
||||
"size": 10,
|
||||
"current": currentPage,
|
||||
});
|
||||
logger.e(res);
|
||||
final List resData = res['data']?['records'] ?? [];
|
||||
final bills = resData.map((item) => AccountBill.fromJson(item)).toList();
|
||||
|
||||
final total = res['data']['total'] ?? 0;
|
||||
if (reset) {
|
||||
logger.w('重置数据');
|
||||
currentPage = 1;
|
||||
@ -124,32 +213,16 @@ class BalanceController extends GetxController {
|
||||
hasMore.value = true;
|
||||
}
|
||||
|
||||
List<AccountBill> newData = List.generate(
|
||||
10,
|
||||
(index) {
|
||||
int id = currentPage * 10 + index + 1;
|
||||
// logger.w('生成数据: id=$id');
|
||||
return AccountBill(
|
||||
id: id,
|
||||
source: '来源 $currentPage-${index + 1}',
|
||||
changeAmount: (index + 1) * 10.0,
|
||||
changeType: index % 2 + 1,
|
||||
createTime: DateTime.now().toString(),
|
||||
);
|
||||
},
|
||||
);
|
||||
data.addAll(bills);
|
||||
|
||||
data.addAll(newData);
|
||||
logger.w('添加了 ${newData.length} 条数据,总数据量: ${data.length}');
|
||||
|
||||
currentPage++;
|
||||
logger.w('页码增加到: $currentPage');
|
||||
|
||||
if (currentPage > 5) {
|
||||
if (data.length >= total) {
|
||||
hasMore.value = false;
|
||||
logger.w('没有更多数据了');
|
||||
}
|
||||
|
||||
currentPage++;
|
||||
logger.w('页码增加到: $currentPage');
|
||||
|
||||
isLoading.value = false;
|
||||
logger.w('加载完成');
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@ class AccountBill {
|
||||
final int changeType; // 变动类型(1收入 2支出)
|
||||
final String changeDesc; // 变动描述
|
||||
final String source; // 变动来源
|
||||
final String sourceName; // 变动来源文案
|
||||
final String createTime; // 创建时间
|
||||
final int createBy;
|
||||
final String updateTime;
|
||||
@ -24,6 +25,7 @@ class AccountBill {
|
||||
this.changeType = 1,
|
||||
this.changeDesc = '',
|
||||
this.source = '未知来源',
|
||||
this.sourceName = '未知来源',
|
||||
this.createTime = '',
|
||||
this.createBy = 0,
|
||||
this.updateTime = '',
|
||||
@ -31,19 +33,20 @@ class AccountBill {
|
||||
|
||||
factory AccountBill.fromJson(Map<String, dynamic> json) {
|
||||
return AccountBill(
|
||||
id: json['id'] ?? 0,
|
||||
memberId: json['member_id'],
|
||||
accountId: json['account_id'],
|
||||
moneyBalance: (json['money_balance'] as num?)?.toDouble() ?? 0.0,
|
||||
beforeBalance: (json['before_balance'] as num?)?.toDouble() ?? 0.0,
|
||||
afterBalance: (json['after_balance'] as num?)?.toDouble() ?? 0.0,
|
||||
changeAmount: (json['change_amount'] as num?)?.toDouble() ?? 0.0,
|
||||
changeType: json['change_type'] ?? 1,
|
||||
changeDesc: json['change_desc'] ?? '',
|
||||
source: json['source'] ?? '未知来源',
|
||||
createTime: json['create_time'] ?? '',
|
||||
createBy: json['create_by'] ?? 0,
|
||||
updateTime: json['update_time'] ?? '',
|
||||
id: int.tryParse(json['id']?.toString() ?? '') ?? 0,
|
||||
memberId: json['memberId'] != null ? int.tryParse(json['memberId'].toString()) : null,
|
||||
accountId: json['accountId'] != null ? int.tryParse(json['accountId'].toString()) : null,
|
||||
moneyBalance: double.tryParse(json['moneyBalance']?.toString() ?? '0') ?? 0.0,
|
||||
beforeBalance: double.tryParse(json['beforeBalance']?.toString() ?? '0') ?? 0.0,
|
||||
afterBalance: double.tryParse(json['afterBalance']?.toString() ?? '0') ?? 0.0,
|
||||
changeAmount: double.tryParse(json['changeAmount']?.toString() ?? '0') ?? 0.0,
|
||||
changeType: int.tryParse(json['changeType']?.toString() ?? '1') ?? 1,
|
||||
changeDesc: json['changeDesc'] ?? '',
|
||||
source: json['source']?.toString() ?? '未知来源',
|
||||
sourceName: json['sourceName']?.toString() ?? '未知sourceName',
|
||||
createTime: json['createTime'] ?? '',
|
||||
createBy: int.tryParse(json['createBy']?.toString() ?? '0') ?? 0,
|
||||
updateTime: json['updateTime'] ?? '',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:loopin/components/web_page.dart';
|
||||
import 'package:loopin/styles/index.dart';
|
||||
|
||||
class Setting extends StatelessWidget {
|
||||
@ -23,14 +24,19 @@ class Setting extends StatelessWidget {
|
||||
},
|
||||
{
|
||||
'icon': Icons.lock,
|
||||
'title': '隐私',
|
||||
'onTap': () => Get.toNamed('/privacy'),
|
||||
'title': '隐私协议',
|
||||
'onTap': () => Get.to(() => SafeArea(child: WebPage(url: 'https://www.wuzhongjie.com.cn/download/yinsizhengce.html', title: '隐私协议'))),
|
||||
},
|
||||
{
|
||||
'icon': Icons.info,
|
||||
'title': '关于我们',
|
||||
'onTap': () => Get.toNamed('/about'),
|
||||
'icon': Icons.lock,
|
||||
'title': '用户协议',
|
||||
'onTap': () => Get.to(() => SafeArea(child: WebPage(url: 'https://www.wuzhongjie.com.cn/download/yonhuxieyi.html', title: '用户协议'))),
|
||||
},
|
||||
// {
|
||||
// 'icon': Icons.info,
|
||||
// 'title': '关于我们',
|
||||
// 'onTap': () => Get.toNamed('/about'),
|
||||
// },
|
||||
];
|
||||
|
||||
return Scaffold(
|
||||
|
||||
@ -242,8 +242,8 @@ class _UserInfoState extends State<UserInfo> {
|
||||
if (file != null) {
|
||||
final fileSizeInBytes = await file.length();
|
||||
final sizeInMB = fileSizeInBytes / (1024 * 1024);
|
||||
if (sizeInMB > 200) {
|
||||
MyDialog.toast('图片大小不能超过200MB', icon: const Icon(Icons.check_circle), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200)));
|
||||
if (sizeInMB > 20) {
|
||||
MyDialog.toast('图片大小不能超过20MB', icon: const Icon(Icons.check_circle), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200)));
|
||||
} else {
|
||||
print("图片合法,大小:$sizeInMB MB");
|
||||
//走upload(file)上传图片拿到url地址
|
||||
@ -291,7 +291,6 @@ class _UserInfoState extends State<UserInfo> {
|
||||
} else {
|
||||
print("图片合法,大小:$sizeInMB MB");
|
||||
//走upload(file)上传图片拿到url地址
|
||||
|
||||
final croppedFile = await ImageCropper().cropImage(
|
||||
sourcePath: file.path,
|
||||
maxWidth: 1024,
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:get/get_rx/src/rx_typedefs/rx_typedefs.dart';
|
||||
import 'package:loopin/IM/controller/chat_controller.dart';
|
||||
@ -16,20 +17,26 @@ import 'package:nested_scroll_view_plus/nested_scroll_view_plus.dart';
|
||||
import 'package:shirne_dialog/shirne_dialog.dart';
|
||||
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart';
|
||||
import 'package:tencent_cloud_chat_sdk/models/v2_tim_follow_info.dart';
|
||||
import 'package:tencent_cloud_chat_sdk/models/v2_tim_friend_info.dart';
|
||||
import 'package:tencent_cloud_chat_sdk/models/v2_tim_user_full_info.dart';
|
||||
|
||||
class PageParams {
|
||||
int page;
|
||||
int pageSize;
|
||||
bool isLoading;
|
||||
bool hasMore;
|
||||
RxBool isLoading;
|
||||
RxBool hasMore;
|
||||
|
||||
PageParams({
|
||||
this.page = 1,
|
||||
this.pageSize = 10,
|
||||
this.isLoading = false,
|
||||
this.hasMore = true,
|
||||
});
|
||||
this.pageSize = 12,
|
||||
bool isLoading = false,
|
||||
bool hasMore = true,
|
||||
}) : hasMore = hasMore.obs,
|
||||
isLoading = isLoading.obs;
|
||||
@override
|
||||
String toString() {
|
||||
return 'PageParams(page: $page, pageSize: $pageSize, isLoading: ${isLoading.value}, hasMore: ${hasMore.value})';
|
||||
}
|
||||
}
|
||||
|
||||
class Vloger extends StatefulWidget {
|
||||
@ -55,6 +62,7 @@ class MyPageState extends State<Vloger> with SingleTickerProviderStateMixin {
|
||||
},
|
||||
role: 0,
|
||||
));
|
||||
String blackTxt = '拉黑';
|
||||
|
||||
late RxInt followed = 0.obs; // 是否关注
|
||||
// followersCount粉丝,多少人关注了我,mutualFollowersCount互关,followingCount我关注了多少人
|
||||
@ -64,7 +72,7 @@ class MyPageState extends State<Vloger> with SingleTickerProviderStateMixin {
|
||||
));
|
||||
|
||||
RxBool get shouldFixHeader => (currentTabIndex.value == 0 && items.isEmpty) || (currentTabIndex.value == 1 && favoriteItems.isEmpty) ? true.obs : false.obs;
|
||||
|
||||
RxInt totalCount = 0.obs;
|
||||
List tabList = [
|
||||
{'name': "作品"},
|
||||
];
|
||||
@ -82,24 +90,21 @@ class MyPageState extends State<Vloger> with SingleTickerProviderStateMixin {
|
||||
void initState() {
|
||||
super.initState();
|
||||
args = Get.arguments ?? {};
|
||||
print('argsssssssssssssssssssssss$args');
|
||||
logger.e(args);
|
||||
itemsParams = PageParams();
|
||||
favoriteParams = PageParams();
|
||||
selfInfo();
|
||||
flowInfo();
|
||||
checkFollowType();
|
||||
initControllers();
|
||||
|
||||
// 这里保证委托父类处理滚动时的触底加载
|
||||
scrollListener = () {
|
||||
final pos = scrollController.position;
|
||||
final isNearBottom = pos.pixels >= pos.maxScrollExtent - 100;
|
||||
|
||||
if (!isNearBottom) return;
|
||||
|
||||
if (currentTabIndex.value == 0 && !itemsParams.isLoading && itemsParams.hasMore) {
|
||||
if (currentTabIndex.value == 0 && !itemsParams.isLoading.value && itemsParams.hasMore.value) {
|
||||
loadData(0);
|
||||
} else if (currentTabIndex.value == 1 && !favoriteParams.isLoading && favoriteParams.hasMore) {
|
||||
loadData(1);
|
||||
}
|
||||
};
|
||||
scrollController.addListener(scrollListener);
|
||||
@ -113,6 +118,7 @@ class MyPageState extends State<Vloger> with SingleTickerProviderStateMixin {
|
||||
|
||||
loadData(0);
|
||||
getUserLikesCount();
|
||||
getBlackList();
|
||||
}
|
||||
|
||||
@override
|
||||
@ -129,18 +135,27 @@ class MyPageState extends State<Vloger> with SingleTickerProviderStateMixin {
|
||||
void getUserLikesCount() async {
|
||||
try {
|
||||
final resData = await Http.get('${CommonApi.accountInfo}?memberId=${args['memberId']}');
|
||||
print('aaaaaaaaaaaaaaaaaaa$resData');
|
||||
logger.e('$resData');
|
||||
if (resData != null && resData['code'] == 200) {
|
||||
vlogLikeCount = resData['data']['vlogLikeCount'] ?? 0;
|
||||
}
|
||||
} catch (e) {}
|
||||
} catch (e) {
|
||||
logger.e(e);
|
||||
}
|
||||
}
|
||||
|
||||
void loadData([int? tabIndex]) async {
|
||||
final index = tabIndex ?? currentTabIndex.value;
|
||||
if (index == 0) {
|
||||
if (itemsParams.isLoading || !itemsParams.hasMore) return;
|
||||
itemsParams.isLoading = true;
|
||||
if (itemsParams.isLoading.value || !itemsParams.hasMore.value) return;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
itemsParams.isLoading.value = true;
|
||||
});
|
||||
|
||||
// await Future.delayed(Duration(seconds: 5), () {
|
||||
// logger.e('5秒后执行完毕');
|
||||
// });
|
||||
|
||||
final res = await Http.post(VideoApi.getVideoListByMemberId, data: {
|
||||
"memberId": args['memberId'],
|
||||
"current": itemsParams.page,
|
||||
@ -149,19 +164,20 @@ class MyPageState extends State<Vloger> with SingleTickerProviderStateMixin {
|
||||
final obj = res['data'];
|
||||
final total = obj['total'];
|
||||
final row = obj['records'] ?? [];
|
||||
logger.i(res['data']);
|
||||
// logger.i(res['data']);
|
||||
totalCount.value = total;
|
||||
// 判断是否还有更多数据
|
||||
logger.e(items.length);
|
||||
// 添加新数据,触发响应式更新
|
||||
items.addAll(row);
|
||||
logger.e(obj);
|
||||
if (items.length >= total) {
|
||||
itemsParams.hasMore = false;
|
||||
}
|
||||
// 页码加一
|
||||
itemsParams.page++;
|
||||
//
|
||||
itemsParams.isLoading = false;
|
||||
|
||||
if (items.length >= total) {
|
||||
itemsParams.hasMore.value = false;
|
||||
}
|
||||
itemsParams.isLoading.value = false;
|
||||
|
||||
logger.e(itemsParams.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@ -175,7 +191,7 @@ class MyPageState extends State<Vloger> with SingleTickerProviderStateMixin {
|
||||
final resIm = await ImService.instance.otherInfo(args['memberId']);
|
||||
if (resIm.success && resIm.data != null) {
|
||||
userInfo.value = resIm.data!;
|
||||
logger.i(userInfo.value.toLogString());
|
||||
// logger.i(userInfo.value.toLogString());
|
||||
} else {
|
||||
logger.e(resIm.desc);
|
||||
}
|
||||
@ -210,6 +226,75 @@ class MyPageState extends State<Vloger> with SingleTickerProviderStateMixin {
|
||||
}
|
||||
}
|
||||
|
||||
// 获取拉黑列表
|
||||
void getBlackList() async {
|
||||
final res = await ImService.instance.blackList();
|
||||
if (res.success && res.data != null) {
|
||||
//
|
||||
final data = res.data;
|
||||
for (V2TimFriendInfo element in data ?? []) {
|
||||
logger.w(element.toJson());
|
||||
if (element.userID == args['memberId']) {
|
||||
// 在拉黑列表
|
||||
blackTxt = '取消拉黑';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 取消拉黑
|
||||
void _cancelBlack() async {
|
||||
logger.w('开始执行取消拉黑${args['memberId']}');
|
||||
final res = await ImService.instance.deleteFromBlackList(userIDList: [args['memberId'] ?? '']);
|
||||
logger.w(res.success);
|
||||
// 成功后返回并删除会话
|
||||
if (res.success) {
|
||||
blackTxt = '拉黑';
|
||||
//删除会话,退出聊天
|
||||
Get.back();
|
||||
}
|
||||
}
|
||||
|
||||
// 拉黑
|
||||
void _addBlack() async {
|
||||
logger.w('开始执行拉黑${args['memberId']}');
|
||||
final res = await ImService.instance.addToBlackList(userIDList: [args['memberId'] ?? '']);
|
||||
logger.w(res.success);
|
||||
// 成功后返回并删除会话
|
||||
if (res.success) {
|
||||
blackTxt = '取消拉黑';
|
||||
//删除会话,退出聊天
|
||||
await ImService.instance.deleteConversation(conversationID: 'c2c_${args['memberId']}');
|
||||
final ctl = Get.find<ChatController>();
|
||||
ctl.chatList.removeWhere(
|
||||
(conv) => conv.conversation.conversationID == 'c2c_${args['memberId']}',
|
||||
);
|
||||
ctl.chatList.refresh();
|
||||
Get.back();
|
||||
}
|
||||
}
|
||||
|
||||
//拉黑/取消
|
||||
void _handleBlack() {
|
||||
if (blackTxt == '拉黑') {
|
||||
//_addBlack
|
||||
_addBlack();
|
||||
} else {
|
||||
_cancelBlack();
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildIcon(String assetPath, VoidCallback onTap) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 5.0, vertical: 5.0),
|
||||
decoration: BoxDecoration(color: Colors.black.withAlpha((0.3 * 255).round()), borderRadius: BorderRadius.circular(20.0)),
|
||||
child: SvgPicture.asset(assetPath, height: 20.0, width: 20.0, colorFilter: const ColorFilter.mode(Colors.white70, BlendMode.srcIn)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PopScope(
|
||||
@ -235,6 +320,72 @@ class MyPageState extends State<Vloger> with SingleTickerProviderStateMixin {
|
||||
collapsedHeight: 120.0,
|
||||
pinned: true,
|
||||
stretch: true,
|
||||
actions: [
|
||||
_buildIcon('assets/images/svg/more.svg', () async {
|
||||
final paddingTop = MediaQuery.of(Get.context!).padding.top;
|
||||
|
||||
final selected = await showMenu(
|
||||
context: Get.context!,
|
||||
position: RelativeRect.fromLTRB(
|
||||
double.infinity,
|
||||
kToolbarHeight + paddingTop - 12,
|
||||
8,
|
||||
double.infinity,
|
||||
),
|
||||
color: FStyle.primaryColor,
|
||||
elevation: 8,
|
||||
items: [
|
||||
PopupMenuItem<String>(
|
||||
value: 'block',
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.block, color: Colors.white, size: 18),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
blackTxt,
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
if (selected != null) {
|
||||
switch (selected) {
|
||||
case 'block':
|
||||
// print('点击了拉黑');
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
content: Text('确认要$blackTxt对方吗?', style: TextStyle(fontSize: 16.0)),
|
||||
backgroundColor: Colors.white,
|
||||
surfaceTintColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15.0)),
|
||||
elevation: 2.0,
|
||||
actionsPadding: const EdgeInsets.all(15.0),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
},
|
||||
child: const Text('取消', style: TextStyle(color: Colors.black54)),
|
||||
),
|
||||
TextButton(onPressed: _handleBlack, child: const Text('确认', style: TextStyle(color: Colors.red))),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
break;
|
||||
// case 'foucs':
|
||||
// print('点击了取关');
|
||||
// break;
|
||||
}
|
||||
}
|
||||
}),
|
||||
const SizedBox(width: 10.0),
|
||||
],
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
onPressed: () {
|
||||
@ -279,14 +430,20 @@ class MyPageState extends State<Vloger> with SingleTickerProviderStateMixin {
|
||||
controller: tabController,
|
||||
tabs: tabList.map((item) {
|
||||
return Tab(
|
||||
child: Badge.count(
|
||||
backgroundColor: Colors.red,
|
||||
count: item['badge'] ?? 0,
|
||||
isLabelVisible: item['badge'] != null,
|
||||
alignment: Alignment.topRight,
|
||||
offset: const Offset(14, -6),
|
||||
child: Text(item['name'], style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||
child: Obx(
|
||||
() => Text(
|
||||
'${item['name']}(${totalCount.value})',
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
// child: Badge.count(
|
||||
// backgroundColor: Colors.red,
|
||||
// count: item['badge'] ?? 0,
|
||||
// isLabelVisible: item['badge'] != null,
|
||||
// alignment: Alignment.topRight,
|
||||
// offset: const Offset(14, -6),
|
||||
// child: Text(item['name'], style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||
// ),
|
||||
);
|
||||
}).toList(),
|
||||
isScrollable: true, //禁止左右滑动
|
||||
@ -357,8 +514,19 @@ class MyPageState extends State<Vloger> with SingleTickerProviderStateMixin {
|
||||
if (listToShow.isEmpty) {
|
||||
return emptyTip('暂无相关数据');
|
||||
}
|
||||
return NotificationListener<ScrollNotification>(
|
||||
onNotification: (notification) {
|
||||
if (notification is ScrollUpdateNotification) {
|
||||
final metrics = notification.metrics;
|
||||
final isNearBottom = metrics.pixels >= metrics.maxScrollExtent;
|
||||
|
||||
return CustomScrollView(
|
||||
if (isNearBottom && !params.isLoading.value && params.hasMore.value) {
|
||||
loadData(0);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
@ -449,7 +617,7 @@ class MyPageState extends State<Vloger> with SingleTickerProviderStateMixin {
|
||||
crossAxisCount: 3,
|
||||
crossAxisSpacing: 10.0,
|
||||
mainAxisSpacing: 10.0,
|
||||
childAspectRatio: 0.7, // 调整为更适合视频封面的比例
|
||||
childAspectRatio: 0.6, // 宽高比1=正方
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -457,11 +625,22 @@ class MyPageState extends State<Vloger> with SingleTickerProviderStateMixin {
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 20.0),
|
||||
child: Center(
|
||||
child: params.hasMore ? const CircularProgressIndicator() : const Text('没有更多数据了'),
|
||||
child: Obx(
|
||||
() {
|
||||
if (params.isLoading.value) {
|
||||
return SafeArea(top: false, child: CircularProgressIndicator());
|
||||
} else if (!params.hasMore.value) {
|
||||
return SafeArea(top: false, child: Text('没有更多数据了'));
|
||||
} else {
|
||||
return SizedBox(height: 20);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -1,14 +1,20 @@
|
||||
library;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:loopin/service/http.dart';
|
||||
import 'package:loopin/IM/controller/im_user_info_controller.dart';
|
||||
import 'package:loopin/IM/im_friend_listeners.dart';
|
||||
import 'package:loopin/api/common_api.dart';
|
||||
import 'package:loopin/api/shop_api.dart';
|
||||
import 'package:loopin/components/my_toast.dart';
|
||||
import 'package:shirne_dialog/shirne_dialog.dart';
|
||||
import 'package:timer_count_down/timer_count_down.dart';
|
||||
import 'package:loopin/service/http.dart';
|
||||
import 'package:loopin/utils/index.dart';
|
||||
import 'package:loopin/utils/wxsdk.dart';
|
||||
import 'package:qr_flutter/qr_flutter.dart';
|
||||
import 'package:shirne_dialog/shirne_dialog.dart';
|
||||
import 'package:timer_count_down/timer_count_down.dart';
|
||||
|
||||
import '../../behavior/custom_scroll_behavior.dart';
|
||||
import '../../utils/lifecycle_handler.dart';
|
||||
|
||||
@ -20,6 +26,8 @@ class OrderDetail extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _OrderDetailState extends State<OrderDetail> with SingleTickerProviderStateMixin {
|
||||
late List orderStatusDict;
|
||||
|
||||
final String _orderId = Get.arguments['orderId'] ?? '';
|
||||
dynamic orderGoodsInfo;
|
||||
int _initialSeconds = 30 * 60; // 存储初始秒数
|
||||
@ -31,7 +39,8 @@ class _OrderDetailState extends State<OrderDetail> with SingleTickerProviderStat
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
getOrderDetail(orderId: _orderId);
|
||||
getOrderStatusDict();
|
||||
// getOrderDetail(orderId: _orderId);
|
||||
LifecycleHandler.onAppResumed = _onAppResumed;
|
||||
}
|
||||
|
||||
@ -41,32 +50,39 @@ class _OrderDetailState extends State<OrderDetail> with SingleTickerProviderStat
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _onAppResumed() {
|
||||
print('App回到前台,刷新订单状态,订单Id${_orderId}');
|
||||
getOrderDetail(orderId: _orderId); // 刷新订单详情数据
|
||||
void _onAppResumed() async {
|
||||
print('App回到前台,刷新订单状态,订单Id$_orderId');
|
||||
await getOrderDetail(orderId: _orderId); // 刷新订单详情数据
|
||||
//先检测订单状态,
|
||||
logger.e(orderGoodsInfo['status']);
|
||||
final int status = orderGoodsInfo['status'];
|
||||
if (status == 0) {
|
||||
//未支付状态,展示过渡
|
||||
_showPaymentResultDialog(); // 展示支付结果弹框
|
||||
}
|
||||
}
|
||||
|
||||
// 获取订单状态
|
||||
void getOrderRealStatus({required String orderId}) async {
|
||||
Future<void> getOrderRealStatus({required String orderId}) async {
|
||||
try {
|
||||
final res = await Http.get('${ShopApi.goodsOrderStatus}/$orderId');
|
||||
Get.toNamed('/myOrder');
|
||||
} catch (e) {
|
||||
print('报错-------------->${e}');
|
||||
print('报错-------------->$e');
|
||||
}
|
||||
}
|
||||
|
||||
// 获取订单详情信息,包含商品参数
|
||||
void getOrderDetail({required String orderId}) async {
|
||||
Future<void> getOrderDetail({required String orderId}) async {
|
||||
try {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
final res = await Http.get('${ShopApi.goodsOrderDetail}/$orderId');
|
||||
debugPrint(res['data'].toString(), wrapWidth: 600);
|
||||
logger.w(res);
|
||||
setState(() {
|
||||
orderGoodsInfo = res['data'];
|
||||
_initialSeconds = Utils.calcTime(orderGoodsInfo['createTime']);
|
||||
_isLoading = false;
|
||||
});
|
||||
} catch (e) {
|
||||
@ -77,6 +93,12 @@ class _OrderDetailState extends State<OrderDetail> with SingleTickerProviderStat
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> getOrderStatusDict() async {
|
||||
final res = await Http.get('${CommonApi.dictionaryApi}oms_order_status');
|
||||
orderStatusDict = res['data'] as List;
|
||||
getOrderDetail(orderId: _orderId);
|
||||
}
|
||||
|
||||
// 取消订单
|
||||
void _cancelOrder() async {
|
||||
try {
|
||||
@ -89,9 +111,11 @@ class _OrderDetailState extends State<OrderDetail> with SingleTickerProviderStat
|
||||
|
||||
// 核销
|
||||
void _writeOffQrCode(verificationCodes) async {
|
||||
if (verificationCodes != null && verificationCodes.isNotEmpty) {
|
||||
logger.e(orderGoodsInfo);
|
||||
final orderInfo = orderGoodsInfo['items'];
|
||||
if (orderInfo != null) {
|
||||
// 过滤可用的核销码(status为0)
|
||||
List<dynamic> newVerifyList = verificationCodes.where((item) => item['status'] == 0).toList();
|
||||
List<dynamic> newVerifyList = orderInfo.where((item) => item['status'] == 0).toList();
|
||||
|
||||
if (newVerifyList.isNotEmpty) {
|
||||
setState(() {
|
||||
@ -108,7 +132,7 @@ class _OrderDetailState extends State<OrderDetail> with SingleTickerProviderStat
|
||||
} else {
|
||||
MyToast().tip(
|
||||
title: '暂无可用的核销码',
|
||||
position: 'center',
|
||||
position: 'top',
|
||||
type: 'error',
|
||||
);
|
||||
}
|
||||
@ -229,8 +253,19 @@ class _OrderDetailState extends State<OrderDetail> with SingleTickerProviderStat
|
||||
);
|
||||
}
|
||||
|
||||
// 获取状态文本
|
||||
String getOrderStatusText(dynamic status) {
|
||||
int statusCode = status is String ? int.tryParse(status) ?? -1 : (status is int ? status : -1);
|
||||
final match = orderStatusDict.firstWhere(
|
||||
(item) => item['dictValue'].toString() == statusCode.toString(),
|
||||
orElse: () => {'dictLabel': '未知状态'},
|
||||
);
|
||||
|
||||
return match['dictLabel'] ?? '未知状态';
|
||||
}
|
||||
|
||||
// 获取订单状态文本
|
||||
String getOrderStatusText(int status) {
|
||||
String getOrderStatusText_old(int status) {
|
||||
print('111111111111$status');
|
||||
switch (status) {
|
||||
case 0:
|
||||
@ -276,10 +311,7 @@ class _OrderDetailState extends State<OrderDetail> with SingleTickerProviderStat
|
||||
|
||||
// 获取第一个商品信息
|
||||
dynamic _getFirstProductInfo() {
|
||||
if (orderGoodsInfo == null ||
|
||||
orderGoodsInfo['items'] == null ||
|
||||
orderGoodsInfo['items'] is! List ||
|
||||
orderGoodsInfo['items'].isEmpty) {
|
||||
if (orderGoodsInfo == null || orderGoodsInfo['items'] == null || orderGoodsInfo['items'] is! List || orderGoodsInfo['items'].isEmpty) {
|
||||
return {};
|
||||
}
|
||||
return orderGoodsInfo['items'][0];
|
||||
@ -288,6 +320,8 @@ class _OrderDetailState extends State<OrderDetail> with SingleTickerProviderStat
|
||||
// 构建商品图片
|
||||
Widget _buildProductImage() {
|
||||
final productInfo = _getFirstProductInfo();
|
||||
logger.e(orderGoodsInfo);
|
||||
|
||||
final picUrl = productInfo?['pic'];
|
||||
|
||||
if (picUrl == null || picUrl.isEmpty) {
|
||||
@ -337,9 +371,7 @@ class _OrderDetailState extends State<OrderDetail> with SingleTickerProviderStat
|
||||
),
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(
|
||||
value: loadingProgress.expectedTotalBytes != null
|
||||
? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes!
|
||||
: null,
|
||||
value: loadingProgress.expectedTotalBytes != null ? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes! : null,
|
||||
),
|
||||
),
|
||||
);
|
||||
@ -372,9 +404,63 @@ class _OrderDetailState extends State<OrderDetail> with SingleTickerProviderStat
|
||||
),
|
||||
const SizedBox(width: 10.0),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
onPressed: () async {
|
||||
// 打开微信小程序的某个页面地址,如pages/index/index
|
||||
Wxsdk.openMiniApp(orderId: _orderId);
|
||||
// Wxsdk.openMiniApp(orderId: _orderId);
|
||||
|
||||
//-----------
|
||||
final infoCtl = Get.find<ImUserInfoController>();
|
||||
final openId = infoCtl.customInfo['openId'];
|
||||
if (openId == null || openId.isEmpty) {
|
||||
showDialog(
|
||||
context: Get.context!,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: Text('微信授权'),
|
||||
content: const Text('当前支付需要您进行微信授权', style: TextStyle(fontSize: 16.0)),
|
||||
backgroundColor: Colors.white,
|
||||
surfaceTintColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15.0)),
|
||||
elevation: 2.0,
|
||||
actionsPadding: const EdgeInsets.all(15.0),
|
||||
actions: [
|
||||
TextButton(onPressed: () => {Get.back()}, child: Text('取消', style: TextStyle(color: Colors.red))),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
//去授权
|
||||
await Wxsdk.login();
|
||||
Get.back();
|
||||
},
|
||||
child: Text('去授权')),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取支付参数
|
||||
final data = {
|
||||
"orderType": "ORDER",
|
||||
"clientType": "APP",
|
||||
"paymentMethod": "WECHAT",
|
||||
"paymentClient": "APP",
|
||||
"sn": _orderId,
|
||||
};
|
||||
final res = await Http.post(CommonApi.wechatPay, data: data);
|
||||
logger.w(res);
|
||||
final payParams = res['data'];
|
||||
logger.w(payParams);
|
||||
// 拉起支付
|
||||
await Wxsdk.payWithWx(
|
||||
appId: payParams['appid'],
|
||||
partnerId: payParams['partnerid'],
|
||||
prepayId: payParams['prepayid'],
|
||||
packageValue: payParams['package'],
|
||||
nonceStr: payParams['noncestr'],
|
||||
timestamp: int.parse(payParams['timestamp']),
|
||||
sign: payParams['sign'],
|
||||
);
|
||||
},
|
||||
style: ButtonStyle(
|
||||
backgroundColor: WidgetStateProperty.all(Color(0xff07c160)),
|
||||
@ -385,7 +471,7 @@ class _OrderDetailState extends State<OrderDetail> with SingleTickerProviderStat
|
||||
],
|
||||
);
|
||||
|
||||
case 1: // 待核销
|
||||
case 2: // 待核销
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
@ -401,21 +487,21 @@ class _OrderDetailState extends State<OrderDetail> with SingleTickerProviderStat
|
||||
],
|
||||
);
|
||||
|
||||
case 2: // 已完成
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
const SizedBox(width: 10.0),
|
||||
ElevatedButton(
|
||||
onPressed: () {},
|
||||
style: ButtonStyle(
|
||||
backgroundColor: WidgetStateProperty.all(Colors.green),
|
||||
foregroundColor: WidgetStateProperty.all(Colors.white),
|
||||
),
|
||||
child: const Text('已完成'),
|
||||
),
|
||||
],
|
||||
);
|
||||
// case 2: // 已完成
|
||||
// return Row(
|
||||
// mainAxisAlignment: MainAxisAlignment.end,
|
||||
// children: [
|
||||
// const SizedBox(width: 10.0),
|
||||
// ElevatedButton(
|
||||
// onPressed: () {},
|
||||
// style: ButtonStyle(
|
||||
// backgroundColor: WidgetStateProperty.all(Colors.green),
|
||||
// foregroundColor: WidgetStateProperty.all(Colors.white),
|
||||
// ),
|
||||
// child: const Text('已完成'),
|
||||
// ),
|
||||
// ],
|
||||
// );
|
||||
|
||||
case 3: // 已关闭
|
||||
return Row(
|
||||
@ -701,6 +787,7 @@ class _OrderDetailState extends State<OrderDetail> with SingleTickerProviderStat
|
||||
build: (_, double time) {
|
||||
int m = ((time % 3600) ~/ 60).toInt();
|
||||
int s = (time % 60).toInt();
|
||||
|
||||
String formatted = "${m.toString().padLeft(2, '0')}:${s.toString().padLeft(2, '0')}";
|
||||
|
||||
return Text(
|
||||
@ -721,9 +808,7 @@ class _OrderDetailState extends State<OrderDetail> with SingleTickerProviderStat
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
Text(
|
||||
_countdownFinished
|
||||
? '订单已自动取消'
|
||||
: '超过30分钟未支付,订单将自动取消',
|
||||
_countdownFinished ? '订单已自动取消' : '超过30分钟未支付,订单将自动取消',
|
||||
style: TextStyle(color: Colors.grey, fontSize: 12.0),
|
||||
),
|
||||
],
|
||||
@ -750,7 +835,15 @@ class _OrderDetailState extends State<OrderDetail> with SingleTickerProviderStat
|
||||
Row(
|
||||
children: [
|
||||
Spacer(),
|
||||
Text(
|
||||
orderGoodsInfo['items'][0]['status'] == 1
|
||||
? Text(
|
||||
'已核销',
|
||||
style: TextStyle(
|
||||
color: Colors.red,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
)
|
||||
: Text(
|
||||
getOrderStatusText(orderGoodsInfo?['status']),
|
||||
style: TextStyle(
|
||||
color: getOrderStatusColor(orderGoodsInfo?['status']),
|
||||
@ -842,7 +935,7 @@ class _OrderDetailState extends State<OrderDetail> with SingleTickerProviderStat
|
||||
_buildOrderInfoRow('下单时间', orderGoodsInfo?['createTime'] ?? ''),
|
||||
_buildOrderInfoRow('购买数量', (productInfo['quantity'] ?? 0).toString()),
|
||||
_buildOrderInfoRow('订单金额', '¥${orderGoodsInfo?['totalAmount'] ?? '0.00'}'),
|
||||
_buildOrderInfoRow('实付金额', '¥${orderGoodsInfo?['payAmount'] ?? '0.00'}'),
|
||||
// _buildOrderInfoRow('实付金额', '¥${orderGoodsInfo?['payAmount'] ?? '0.00'}'),
|
||||
],
|
||||
)
|
||||
],
|
||||
@ -856,9 +949,21 @@ class _OrderDetailState extends State<OrderDetail> with SingleTickerProviderStat
|
||||
if (_showQrCodeDialog) _buildQrCodeDialog(),
|
||||
],
|
||||
),
|
||||
bottomNavigationBar: orderGoodsInfo == null
|
||||
? null
|
||||
: SafeArea(
|
||||
// bottomNavigationBar: orderGoodsInfo == null
|
||||
// ? null
|
||||
// : SafeArea(
|
||||
// bottom: true,
|
||||
// minimum: const EdgeInsets.all(10),
|
||||
// child: Container(
|
||||
// height: 60.0,
|
||||
// color: Colors.white,
|
||||
// padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 5.0),
|
||||
// child: buildBottomButtons(),
|
||||
// ),
|
||||
// ),
|
||||
bottomNavigationBar: (orderGoodsInfo != null && !_showQrCodeDialog)
|
||||
? SafeArea(
|
||||
bottom: true,
|
||||
minimum: const EdgeInsets.all(10),
|
||||
child: Container(
|
||||
height: 60.0,
|
||||
@ -866,7 +971,8 @@ class _OrderDetailState extends State<OrderDetail> with SingleTickerProviderStat
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 5.0),
|
||||
child: buildBottomButtons(),
|
||||
),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -3,10 +3,13 @@ library;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:loopin/utils/wxsdk.dart';
|
||||
import 'package:loopin/IM/controller/im_user_info_controller.dart';
|
||||
import 'package:loopin/IM/push_service.dart';
|
||||
import 'package:loopin/api/common_api.dart';
|
||||
import 'package:loopin/api/shop_api.dart';
|
||||
import 'package:loopin/components/my_toast.dart';
|
||||
import 'package:loopin/service/http.dart';
|
||||
import 'package:loopin/utils/wxsdk.dart';
|
||||
|
||||
import '../../behavior/custom_scroll_behavior.dart';
|
||||
|
||||
@ -28,36 +31,39 @@ class _MyOrderState extends State<MyOrder> with SingleTickerProviderStateMixin {
|
||||
List<Map<String, dynamic>> _allOrders = [];
|
||||
List<Map<String, dynamic>> _pendingPaymentOrders = [];
|
||||
List<Map<String, dynamic>> _pendingVerificationOrders = [];
|
||||
List<Map<String, dynamic>> _completedOrders = [];
|
||||
// List<Map<String, dynamic>> _completedOrders = [];
|
||||
List<Map<String, dynamic>> _closedOrders = [];
|
||||
List<Map<String, dynamic>> _refundingOrders = [];
|
||||
List<Map<String, dynamic>> _refundedOrders = [];
|
||||
List<Map<String, dynamic>> _cancelledOrders = [];
|
||||
|
||||
// 每个Tab的分页状态
|
||||
Map<int, int> _pageNums = {};
|
||||
Map<int, int> _totalCounts = {};
|
||||
Map<int, bool> _hasMoreData = {};
|
||||
Map<int, bool> _isLoading = {};
|
||||
final Map<int, int> _pageNums = {};
|
||||
final Map<int, int> _totalCounts = {};
|
||||
final Map<int, bool> _hasMoreData = {};
|
||||
final Map<int, bool> _isLoading = {};
|
||||
|
||||
// 订单状态:0->待付款;1->待核销;2->已完成;3->已关闭;4->退款中;5->已退款 6->已取消 精确匹配
|
||||
List tabList = [
|
||||
{'id': '', 'name': "全部", 'index': 0},
|
||||
{'id': 0, 'name': "待付款", 'index': 1},
|
||||
{'id': 1, 'name': "待核销", 'index': 2},
|
||||
{'id': 2, 'name': "已完成", 'index': 3},
|
||||
{'id': 2, 'name': "已支付", 'index': 2},
|
||||
// {'id': 2, 'name': "已完成", 'index': 3},
|
||||
{'id': 3, 'name': "已关闭", 'index': 4},
|
||||
{'id': 4, 'name': "退款中", 'index': 5},
|
||||
{'id': 5, 'name': "已退款", 'index': 6},
|
||||
{'id': 6, 'name': "已取消", 'index': 7}
|
||||
];
|
||||
|
||||
late List orderStatusDict;
|
||||
|
||||
late ScrollController scrollController = ScrollController();
|
||||
late TabController tabController = TabController(initialIndex: 0, length: tabList.length, vsync: this);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
getOrderStatusDict();
|
||||
// 初始化分页状态
|
||||
for (var tab in tabList) {
|
||||
_pageNums[tab['index']] = 1;
|
||||
@ -83,19 +89,37 @@ class _MyOrderState extends State<MyOrder> with SingleTickerProviderStateMixin {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> getOrderStatusDict() async {
|
||||
final res = await Http.get('${CommonApi.dictionaryApi}oms_order_status');
|
||||
orderStatusDict = res['data'] as List;
|
||||
await Future.delayed(Duration.zero); // 部分机型在debug下偶尔出现问题
|
||||
//获取初始订单列表
|
||||
getOrderList(false);
|
||||
// dictValue
|
||||
}
|
||||
|
||||
// 获取当前Tab的订单列表
|
||||
List<Map<String, dynamic>> _getCurrentOrderList() {
|
||||
final currentIndex = tabController.index;
|
||||
switch (currentIndex) {
|
||||
case 0: return _allOrders;
|
||||
case 1: return _pendingPaymentOrders;
|
||||
case 2: return _pendingVerificationOrders;
|
||||
case 3: return _completedOrders;
|
||||
case 4: return _closedOrders;
|
||||
case 5: return _refundingOrders;
|
||||
case 6: return _refundedOrders;
|
||||
case 7: return _cancelledOrders;
|
||||
default: return _allOrders;
|
||||
case 0:
|
||||
return _allOrders;
|
||||
case 1:
|
||||
return _pendingPaymentOrders;
|
||||
case 2:
|
||||
return _pendingVerificationOrders;
|
||||
// case 3:
|
||||
// return _completedOrders;
|
||||
case 3:
|
||||
return _closedOrders;
|
||||
case 4:
|
||||
return _refundingOrders;
|
||||
case 5:
|
||||
return _refundedOrders;
|
||||
case 6:
|
||||
return _cancelledOrders;
|
||||
default:
|
||||
return _allOrders;
|
||||
}
|
||||
}
|
||||
|
||||
@ -125,35 +149,35 @@ class _MyOrderState extends State<MyOrder> with SingleTickerProviderStateMixin {
|
||||
_pendingVerificationOrders = orders;
|
||||
}
|
||||
break;
|
||||
// case 3:
|
||||
// if (loadMore) {
|
||||
// _completedOrders.addAll(orders);
|
||||
// } else {
|
||||
// _completedOrders = orders;
|
||||
// }
|
||||
// break;
|
||||
case 3:
|
||||
if (loadMore) {
|
||||
_completedOrders.addAll(orders);
|
||||
} else {
|
||||
_completedOrders = orders;
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
if (loadMore) {
|
||||
_closedOrders.addAll(orders);
|
||||
} else {
|
||||
_closedOrders = orders;
|
||||
}
|
||||
break;
|
||||
case 5:
|
||||
case 4:
|
||||
if (loadMore) {
|
||||
_refundingOrders.addAll(orders);
|
||||
} else {
|
||||
_refundingOrders = orders;
|
||||
}
|
||||
break;
|
||||
case 6:
|
||||
case 5:
|
||||
if (loadMore) {
|
||||
_refundedOrders.addAll(orders);
|
||||
} else {
|
||||
_refundedOrders = orders;
|
||||
}
|
||||
break;
|
||||
case 7:
|
||||
case 6:
|
||||
if (loadMore) {
|
||||
_cancelledOrders.addAll(orders);
|
||||
} else {
|
||||
@ -167,9 +191,7 @@ class _MyOrderState extends State<MyOrder> with SingleTickerProviderStateMixin {
|
||||
// 滚动监听
|
||||
void _scrollListener() {
|
||||
final currentIndex = tabController.index;
|
||||
if (scrollController.position.pixels == scrollController.position.maxScrollExtent &&
|
||||
!_isLoading[currentIndex]! &&
|
||||
_hasMoreData[currentIndex]!) {
|
||||
if (scrollController.position.pixels == scrollController.position.maxScrollExtent && !_isLoading[currentIndex]! && _hasMoreData[currentIndex]!) {
|
||||
getOrderList(true);
|
||||
}
|
||||
}
|
||||
@ -212,6 +234,7 @@ class _MyOrderState extends State<MyOrder> with SingleTickerProviderStateMixin {
|
||||
|
||||
if (res['code'] == 200 && res['data'] != null) {
|
||||
final data = res['data'];
|
||||
logger.e(data);
|
||||
final List<Map<String, dynamic>> newOrders = List<Map<String, dynamic>>.from(data['records'] ?? []);
|
||||
final int total = data['total'] ?? 0;
|
||||
|
||||
@ -267,10 +290,10 @@ class _MyOrderState extends State<MyOrder> with SingleTickerProviderStateMixin {
|
||||
switch (statusCode) {
|
||||
case 0: // 待付款
|
||||
return '待付款: ';
|
||||
case 1: // 待核销
|
||||
case 2: // 待核销
|
||||
return '已付款: ';
|
||||
case 2: // 已完成
|
||||
return '实付款: ';
|
||||
// case 2: // 已完成
|
||||
// return '实付款: ';
|
||||
case 3: // 已关闭
|
||||
return '订单金额: ';
|
||||
case 4: // 退款中
|
||||
@ -310,13 +333,12 @@ class _MyOrderState extends State<MyOrder> with SingleTickerProviderStateMixin {
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
spacing: 5.0,
|
||||
children: [
|
||||
ClipOval(
|
||||
child: Image.asset(
|
||||
'assets/images/avatar/img11.jpg',
|
||||
width: 25.0,
|
||||
),
|
||||
),
|
||||
Text(order['items'][0]['tenantName'] ?? '商家名称'),
|
||||
// ClipOval(
|
||||
// child: NetworkOrAssetImage(
|
||||
// imageUrl: order['items'][0]['faceUrl'] ?? '',
|
||||
// width: 25,
|
||||
// )),
|
||||
Text(order['items'][0]['tenantName'] ?? '未知商家名称'),
|
||||
Icon(
|
||||
Icons.arrow_forward_ios_rounded,
|
||||
color: Colors.grey,
|
||||
@ -325,7 +347,12 @@ class _MyOrderState extends State<MyOrder> with SingleTickerProviderStateMixin {
|
||||
],
|
||||
),
|
||||
Spacer(),
|
||||
Text(
|
||||
order['items'][0]['status'] == 1
|
||||
? Text(
|
||||
'已核销',
|
||||
style: TextStyle(color: Colors.red),
|
||||
)
|
||||
: Text(
|
||||
_getStatusText(order['status']),
|
||||
style: TextStyle(color: _getStatusColor(order['status'])),
|
||||
)
|
||||
@ -334,7 +361,8 @@ class _MyOrderState extends State<MyOrder> with SingleTickerProviderStateMixin {
|
||||
SizedBox(height: 10),
|
||||
// 商品信息
|
||||
if (order['items'] != null && order['items'].isNotEmpty)
|
||||
...order['items'].map<Widget>((item) => Row(
|
||||
...order['items']
|
||||
.map<Widget>((item) => Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
@ -386,7 +414,8 @@ class _MyOrderState extends State<MyOrder> with SingleTickerProviderStateMixin {
|
||||
),
|
||||
)
|
||||
],
|
||||
)).toList(),
|
||||
))
|
||||
.toList(),
|
||||
SizedBox(height: 10),
|
||||
// 金额信息
|
||||
Container(
|
||||
@ -422,8 +451,18 @@ class _MyOrderState extends State<MyOrder> with SingleTickerProviderStateMixin {
|
||||
);
|
||||
}
|
||||
|
||||
// 获取状态文本
|
||||
String _getStatusText(dynamic status) {
|
||||
int statusCode = status is String ? int.tryParse(status) ?? -1 : (status is int ? status : -1);
|
||||
final match = orderStatusDict.firstWhere(
|
||||
(item) => item['dictValue'].toString() == statusCode.toString(),
|
||||
orElse: () => {'dictLabel': '未知状态'},
|
||||
);
|
||||
|
||||
return match['dictLabel'] ?? '未知状态';
|
||||
}
|
||||
|
||||
// 获取状态文本
|
||||
String _getStatusTextold(dynamic status) {
|
||||
// 处理字符串或数字类型的状态
|
||||
int statusCode = status is String ? int.tryParse(status) ?? 0 : (status is int ? status : 0);
|
||||
|
||||
@ -515,7 +554,7 @@ class _MyOrderState extends State<MyOrder> with SingleTickerProviderStateMixin {
|
||||
void _cancelOrder(String orderId) async {
|
||||
try {
|
||||
final res = await Http.post('${ShopApi.cancelGoodsOrder}/$orderId');
|
||||
print('取消订单成功-------------->${res}');
|
||||
print('取消订单成功-------------->$res');
|
||||
|
||||
if (res['code'] == 200) {
|
||||
MyToast().tip(
|
||||
@ -527,7 +566,7 @@ class _MyOrderState extends State<MyOrder> with SingleTickerProviderStateMixin {
|
||||
_refreshAllRelatedTabs();
|
||||
}
|
||||
} catch (e) {
|
||||
print('取消订单失败-------------->${e}');
|
||||
print('取消订单失败-------------->$e');
|
||||
}
|
||||
}
|
||||
|
||||
@ -546,9 +585,61 @@ class _MyOrderState extends State<MyOrder> with SingleTickerProviderStateMixin {
|
||||
}
|
||||
}
|
||||
|
||||
void _payOrder(_orderId) {
|
||||
void _payOrder(orderId) async {
|
||||
// 打开微信小程序的某个页面地址,如pages/index/index
|
||||
Wxsdk.openMiniApp(orderId: _orderId);
|
||||
// Wxsdk.openMiniApp(orderId: orderId);
|
||||
|
||||
//------------
|
||||
final infoCtl = Get.find<ImUserInfoController>();
|
||||
final openId = infoCtl.customInfo['openId'];
|
||||
if (openId == null || openId.isEmpty) {
|
||||
showDialog(
|
||||
context: Get.context!,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: Text('微信授权'),
|
||||
content: const Text('当前支付需要您进行微信授权', style: TextStyle(fontSize: 16.0)),
|
||||
backgroundColor: Colors.white,
|
||||
surfaceTintColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15.0)),
|
||||
elevation: 2.0,
|
||||
actionsPadding: const EdgeInsets.all(15.0),
|
||||
actions: [
|
||||
TextButton(onPressed: () => {Get.back()}, child: Text('取消', style: TextStyle(color: Colors.red))),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
//去授权
|
||||
await Wxsdk.login();
|
||||
Get.back();
|
||||
},
|
||||
child: Text('去授权')),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
return;
|
||||
}
|
||||
//------
|
||||
|
||||
final data = {
|
||||
"orderType": "ORDER",
|
||||
"clientType": "APP",
|
||||
"paymentMethod": "WECHAT",
|
||||
"paymentClient": "APP",
|
||||
"sn": orderId,
|
||||
};
|
||||
final res = await Http.post(CommonApi.wechatPay, data: data);
|
||||
final payParams = res['data'];
|
||||
// 拉起支付
|
||||
await Wxsdk.payWithWx(
|
||||
appId: payParams['appid'],
|
||||
partnerId: payParams['partnerid'],
|
||||
prepayId: payParams['prepayid'],
|
||||
packageValue: payParams['package'],
|
||||
nonceStr: payParams['noncestr'],
|
||||
timestamp: int.parse(payParams['timestamp']),
|
||||
sign: payParams['sign'],
|
||||
);
|
||||
}
|
||||
|
||||
// 重置指定tab的数据
|
||||
@ -560,14 +651,30 @@ class _MyOrderState extends State<MyOrder> with SingleTickerProviderStateMixin {
|
||||
_hasMoreData[index] = true;
|
||||
|
||||
switch (index) {
|
||||
case 0: _allOrders = []; break;
|
||||
case 1: _pendingPaymentOrders = []; break;
|
||||
case 2: _pendingVerificationOrders = []; break;
|
||||
case 3: _completedOrders = []; break;
|
||||
case 4: _closedOrders = []; break;
|
||||
case 5: _refundingOrders = []; break;
|
||||
case 6: _refundedOrders = []; break;
|
||||
case 7: _cancelledOrders = []; break;
|
||||
case 0:
|
||||
_allOrders = [];
|
||||
break;
|
||||
case 1:
|
||||
_pendingPaymentOrders = [];
|
||||
break;
|
||||
case 2:
|
||||
_pendingVerificationOrders = [];
|
||||
break;
|
||||
// case 3:
|
||||
// _completedOrders = [];
|
||||
// break;
|
||||
case 3:
|
||||
_closedOrders = [];
|
||||
break;
|
||||
case 4:
|
||||
_refundingOrders = [];
|
||||
break;
|
||||
case 5:
|
||||
_refundedOrders = [];
|
||||
break;
|
||||
case 6:
|
||||
_cancelledOrders = [];
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -638,8 +745,7 @@ class _MyOrderState extends State<MyOrder> with SingleTickerProviderStateMixin {
|
||||
behavior: CustomScrollBehavior().copyWith(scrollbars: false),
|
||||
child: Container(
|
||||
color: Colors.grey[50],
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
child: Builder(builder: (context) {
|
||||
final currentOrders = _getOrderListByIndex(index);
|
||||
final isLoading = _isLoading[index] ?? false;
|
||||
final hasMoreData = _hasMoreData[index] ?? false;
|
||||
@ -658,29 +764,36 @@ class _MyOrderState extends State<MyOrder> with SingleTickerProviderStateMixin {
|
||||
return _buildOrderItem(currentOrders[itemIndex]);
|
||||
},
|
||||
);
|
||||
}
|
||||
),
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
// 根据索引获取订单列表
|
||||
List<Map<String, dynamic>> _getOrderListByIndex(int index) {
|
||||
switch (index) {
|
||||
case 0: return _allOrders;
|
||||
case 1: return _pendingPaymentOrders;
|
||||
case 2: return _pendingVerificationOrders;
|
||||
case 3: return _completedOrders;
|
||||
case 4: return _closedOrders;
|
||||
case 5: return _refundingOrders;
|
||||
case 6: return _refundedOrders;
|
||||
case 7: return _cancelledOrders;
|
||||
default: return _allOrders;
|
||||
case 0:
|
||||
return _allOrders;
|
||||
case 1:
|
||||
return _pendingPaymentOrders;
|
||||
case 2:
|
||||
return _pendingVerificationOrders;
|
||||
// case 3:
|
||||
// return _completedOrders;
|
||||
case 3:
|
||||
return _closedOrders;
|
||||
case 4:
|
||||
return _refundingOrders;
|
||||
case 5:
|
||||
return _refundedOrders;
|
||||
case 6:
|
||||
return _cancelledOrders;
|
||||
default:
|
||||
return _allOrders;
|
||||
}
|
||||
}
|
||||
|
||||
@ -708,7 +821,7 @@ class _MyOrderState extends State<MyOrder> with SingleTickerProviderStateMixin {
|
||||
}
|
||||
|
||||
Widget emptyTip() {
|
||||
return Container(
|
||||
return SizedBox(
|
||||
width: double.infinity,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
|
||||
@ -1,11 +1,15 @@
|
||||
library;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:loopin/service/http.dart';
|
||||
import 'package:loopin/IM/push_service.dart';
|
||||
import 'package:loopin/api/common_api.dart';
|
||||
import 'package:loopin/api/shop_api.dart';
|
||||
import 'package:shirne_dialog/shirne_dialog.dart';
|
||||
import 'package:loopin/components/my_toast.dart';
|
||||
import 'package:loopin/service/http.dart';
|
||||
import 'package:shirne_dialog/shirne_dialog.dart';
|
||||
|
||||
import '../../behavior/custom_scroll_behavior.dart';
|
||||
|
||||
class SellerOrderDetail extends StatefulWidget {
|
||||
@ -16,28 +20,27 @@ class SellerOrderDetail extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _SellerOrderDetailState extends State<SellerOrderDetail> with SingleTickerProviderStateMixin {
|
||||
late String ? _orderId;
|
||||
late String ?_writeOffCodeId;
|
||||
late List orderStatusDict;
|
||||
|
||||
late String _orderId;
|
||||
late String _writeOffCodeId;
|
||||
dynamic orderGoodsInfo;
|
||||
bool _isLoading = true; // 添加加载状态
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_orderId = Get.arguments['orderId'] ?? ''; // 从商家订单列表进入此页面,此时带着订单id
|
||||
_writeOffCodeId = Get.arguments['writeOffCodeId'] ?? ''; // 从扫码核销进入此页面,此时带着核销码
|
||||
if(_orderId != null){
|
||||
getOrderDetailByOrderId(_orderId);
|
||||
}else if(_writeOffCodeId != null){
|
||||
getOrderDetailByWriteOffId(_writeOffCodeId);
|
||||
|
||||
getOrderStatusDict();
|
||||
// _orderId = Get.arguments['orderId'] ?? ''; // 从商家订单列表进入此页面,此时带着订单id
|
||||
// _writeOffCodeId = Get.arguments['writeOffCodeId'] ?? ''; // 从扫码核销进入此页面,此时带着核销码
|
||||
// if (_orderId.isNotEmpty) {
|
||||
// getOrderDetailByOrderId(_orderId);
|
||||
// } else if (_writeOffCodeId.isNotEmpty) {
|
||||
// getOrderDetailByWriteOffId(_writeOffCodeId);
|
||||
// }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
// 获取订单详情信息,根据订单id
|
||||
void getOrderDetailByOrderId(orderId) async {
|
||||
try {
|
||||
@ -57,14 +60,33 @@ class _SellerOrderDetailState extends State<SellerOrderDetail> with SingleTicker
|
||||
MyDialog.toast('获取订单详情失败');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> getOrderStatusDict() async {
|
||||
final res = await Http.get('${CommonApi.dictionaryApi}oms_order_status');
|
||||
// logger.e(res);
|
||||
orderStatusDict = res['data'] as List;
|
||||
//获取订单数据
|
||||
_orderId = Get.arguments['orderId'] ?? ''; // 从商家订单列表进入此页面,此时带着订单id
|
||||
_writeOffCodeId = Get.arguments['writeOffCodeId'] ?? ''; // 从扫码核销进入此页面,此时带着核销码
|
||||
if (_orderId.isNotEmpty) {
|
||||
getOrderDetailByOrderId(_orderId);
|
||||
} else if (_writeOffCodeId.isNotEmpty) {
|
||||
getOrderDetailByWriteOffId(_writeOffCodeId);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取订单详情信息,根据核销码
|
||||
void getOrderDetailByWriteOffId(code) async {
|
||||
try {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
logger.e('code=$code');
|
||||
final res = await Http.get('${ShopApi.getWriteOffDetail}?code=$code');
|
||||
print('订单详情-------------->${res['data']}');
|
||||
logger.w('订单详情-------------->${res['data']}');
|
||||
if (res == null) {
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
orderGoodsInfo = res['data'];
|
||||
_isLoading = false;
|
||||
@ -78,45 +100,59 @@ class _SellerOrderDetailState extends State<SellerOrderDetail> with SingleTicker
|
||||
}
|
||||
|
||||
// 确认核销订单
|
||||
void _confirmVerifyCode () async {
|
||||
late String writeOffCode;
|
||||
if(_orderId != null){ // 商家可以直接从订单详情获取核销码,并过滤后进行核销
|
||||
var verificationCodesList = orderGoodsInfo['verificationCodes'];
|
||||
if (verificationCodesList != null && verificationCodesList.isNotEmpty) {
|
||||
// 过滤可用的核销码(status为0)
|
||||
List<dynamic> newVerifyList = verificationCodesList.where((item) => item['status'] == 0).toList();
|
||||
if (newVerifyList.isNotEmpty) {
|
||||
writeOffCode = newVerifyList[0]['code'] ?? '';
|
||||
} else {
|
||||
return MyToast().tip(
|
||||
title: '暂无可用的核销码',
|
||||
position: 'center',
|
||||
type: 'error',
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return MyToast().tip(
|
||||
title: '暂无可用的核销码',
|
||||
position: 'center',
|
||||
type: 'error',
|
||||
);
|
||||
}
|
||||
}else if(_writeOffCodeId != null){
|
||||
writeOffCode = _writeOffCodeId?? '';
|
||||
}
|
||||
void _confirmVerifyCode() async {
|
||||
logger.e(orderGoodsInfo);
|
||||
final status = orderGoodsInfo['verificationCodeStatus'];
|
||||
final code = orderGoodsInfo['code'];
|
||||
if (status == 0) {
|
||||
try {
|
||||
final res = await Http.get('${ShopApi.confirmWriteOff}?code=$writeOffCode');
|
||||
debugPrint(res['data'].toString(), wrapWidth: 600);
|
||||
final res = await Http.get('${ShopApi.confirmWriteOff}?code=$code');
|
||||
logger.e(res);
|
||||
MyToast().tip(
|
||||
title: '核销成功',
|
||||
position: 'center',
|
||||
type: 'error',
|
||||
type: 'success',
|
||||
);
|
||||
} catch (e) {
|
||||
logger.e('失败:$e');
|
||||
}
|
||||
}
|
||||
// // 商家可以直接从订单详情获取核销码,并过滤后进行核销
|
||||
// var verificationCodesList = orderGoodsInfo['verificationCodes'];
|
||||
// logger.e(verificationCodesList);
|
||||
// if (verificationCodesList != null && verificationCodesList.isNotEmpty) {
|
||||
// // 过滤可用的核销码(status为0)
|
||||
// List<dynamic> newVerifyList = verificationCodesList.where((item) => item['status'] == 0).toList();
|
||||
// if (newVerifyList.isNotEmpty) {
|
||||
// writeOffCode = newVerifyList[0]['code'] ?? '';
|
||||
// } else {
|
||||
// return MyToast().tip(
|
||||
// title: '暂无可用的核销码',
|
||||
// position: 'center',
|
||||
// type: 'error',
|
||||
// );
|
||||
// }
|
||||
// } else {
|
||||
// return MyToast().tip(
|
||||
// title: '暂无可用的核销码',
|
||||
// position: 'center',
|
||||
// type: 'error',
|
||||
// );
|
||||
// }
|
||||
}
|
||||
|
||||
String getOrderStatusText(dynamic status) {
|
||||
int statusCode = status is String ? int.tryParse(status) ?? -1 : (status is int ? status : -1);
|
||||
final match = orderStatusDict.firstWhere(
|
||||
(item) => item['dictValue'].toString() == statusCode.toString(),
|
||||
orElse: () => {'dictLabel': '未知状态'},
|
||||
);
|
||||
|
||||
return match['dictLabel'] ?? '未知状态';
|
||||
}
|
||||
|
||||
// 获取订单状态文本
|
||||
String getOrderStatusText(int status) {
|
||||
String _getOrderStatusText_old(int status) {
|
||||
switch (status) {
|
||||
case 0:
|
||||
return '待付款';
|
||||
@ -161,14 +197,18 @@ class _SellerOrderDetailState extends State<SellerOrderDetail> with SingleTicker
|
||||
|
||||
// 获取商品信息列表
|
||||
List<dynamic> getProductInfoList() {
|
||||
// 优先使用 items 字段
|
||||
// 优先使用 items 字段 --------
|
||||
if (orderGoodsInfo?['items'] != null && orderGoodsInfo!['items'] is List) {
|
||||
return orderGoodsInfo!['items'];
|
||||
}
|
||||
// 如果 items 为空,使用 productInfo 字段
|
||||
// 如果 items 为空,使用 productInfo 字段---------
|
||||
if (orderGoodsInfo?['productInfo'] != null && orderGoodsInfo!['productInfo'] is List) {
|
||||
return orderGoodsInfo!['productInfo'];
|
||||
}
|
||||
// 扫码进入
|
||||
if (orderGoodsInfo != null) {
|
||||
return [orderGoodsInfo];
|
||||
}
|
||||
// 如果都为空,返回空列表
|
||||
return [];
|
||||
}
|
||||
@ -230,9 +270,7 @@ class _SellerOrderDetailState extends State<SellerOrderDetail> with SingleTicker
|
||||
),
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(
|
||||
value: loadingProgress.expectedTotalBytes != null
|
||||
? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes!
|
||||
: null,
|
||||
value: loadingProgress.expectedTotalBytes != null ? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes! : null,
|
||||
),
|
||||
),
|
||||
);
|
||||
@ -310,7 +348,7 @@ class _SellerOrderDetailState extends State<SellerOrderDetail> with SingleTicker
|
||||
children: [],
|
||||
);
|
||||
|
||||
case 1: // 待核销
|
||||
case 2: // 待核销
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
@ -328,11 +366,11 @@ class _SellerOrderDetailState extends State<SellerOrderDetail> with SingleTicker
|
||||
],
|
||||
);
|
||||
|
||||
case 2: // 已完成
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [],
|
||||
);
|
||||
// case 2: // 已完成
|
||||
// return Row(
|
||||
// mainAxisAlignment: MainAxisAlignment.end,
|
||||
// children: [],
|
||||
// );
|
||||
|
||||
case 3: // 已关闭
|
||||
return Row(
|
||||
@ -393,7 +431,9 @@ class _SellerOrderDetailState extends State<SellerOrderDetail> with SingleTicker
|
||||
body: _isLoading
|
||||
? Center(child: CircularProgressIndicator())
|
||||
: orderGoodsInfo == null
|
||||
? emptyTip()
|
||||
? Center(
|
||||
child: emptyTip(),
|
||||
)
|
||||
: ScrollConfiguration(
|
||||
behavior: CustomScrollBehavior().copyWith(scrollbars: false),
|
||||
child: ListView(
|
||||
@ -432,8 +472,6 @@ class _SellerOrderDetailState extends State<SellerOrderDetail> with SingleTicker
|
||||
style: TextStyle(color: Colors.orange),
|
||||
),
|
||||
SizedBox(width: 4),
|
||||
|
||||
|
||||
],
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
@ -461,7 +499,16 @@ class _SellerOrderDetailState extends State<SellerOrderDetail> with SingleTicker
|
||||
Row(
|
||||
children: [
|
||||
Spacer(),
|
||||
Text(
|
||||
// orderGoodsInfo['items'][0]['status'] == 1
|
||||
orderGoodsInfo['verificationCodeStatus'] == 1
|
||||
? Text(
|
||||
'已核销',
|
||||
style: TextStyle(
|
||||
color: Colors.red,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
)
|
||||
: Text(
|
||||
getOrderStatusText(orderGoodsInfo?['status'] ?? 0),
|
||||
style: TextStyle(
|
||||
color: getOrderStatusColor(orderGoodsInfo?['status'] ?? 0),
|
||||
@ -507,7 +554,7 @@ class _SellerOrderDetailState extends State<SellerOrderDetail> with SingleTicker
|
||||
size: 18.0,
|
||||
),
|
||||
onTap: () async {
|
||||
await Clipboard.setData(ClipboardData(text: _orderId??''));
|
||||
await Clipboard.setData(ClipboardData(text: _orderId ?? ''));
|
||||
MyDialog.toast('订单已复制到剪切板', icon: Icon(Icons.check_circle));
|
||||
},
|
||||
)
|
||||
@ -516,11 +563,14 @@ class _SellerOrderDetailState extends State<SellerOrderDetail> with SingleTicker
|
||||
SizedBox(height: 10),
|
||||
Column(
|
||||
children: [
|
||||
_buildOrderInfoRow('订单号', orderGoodsInfo?['orderId']?.toString() ?? ''),
|
||||
_buildOrderInfoRow('订单号', orderGoodsInfo?['orderSn']?.toString() ?? ''),
|
||||
_buildOrderInfoRow('下单时间', orderGoodsInfo?['createTime']?.toString() ?? ''),
|
||||
_buildOrderInfoRow('购买数量', _calculateTotalQuantity().toString()),
|
||||
_buildOrderInfoRow('订单金额', '¥${orderGoodsInfo?['totalAmount']?.toString() ?? '0.00'}'),
|
||||
_buildOrderInfoRow('实付金额', '¥${orderGoodsInfo?['payAmount']?.toString() ?? '0.00'}'),
|
||||
_orderId.isNotEmpty
|
||||
? _buildOrderInfoRow('订单金额', '¥${orderGoodsInfo?['totalAmount']?.toString() ?? '0.00'}')
|
||||
: _buildOrderInfoRow('订单金额', '¥${orderGoodsInfo?['salePrice']?.toString() ?? '0.00'}'),
|
||||
|
||||
// _buildOrderInfoRow('实付金额', '¥${orderGoodsInfo?['payAmount']?.toString() ?? '0.00'}'),
|
||||
],
|
||||
)
|
||||
],
|
||||
@ -538,7 +588,7 @@ class _SellerOrderDetailState extends State<SellerOrderDetail> with SingleTicker
|
||||
height: 60.0,
|
||||
color: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 5.0),
|
||||
// child: buildBottomButtons(),
|
||||
child: _orderId.isEmpty ? buildBottomButtons() : null,
|
||||
),
|
||||
),
|
||||
);
|
||||
@ -546,15 +596,24 @@ class _SellerOrderDetailState extends State<SellerOrderDetail> with SingleTicker
|
||||
|
||||
// 计算总购买数量
|
||||
int _calculateTotalQuantity() {
|
||||
// final productList = getProductInfoList();
|
||||
// int total = 0;
|
||||
// for (var product in productList) {
|
||||
// total += (product?['buyNum'] as int? ?? 0);
|
||||
// }
|
||||
// return total;
|
||||
|
||||
final productList = getProductInfoList();
|
||||
int total = 0;
|
||||
// logger.e(productList);
|
||||
var total = 0;
|
||||
for (var product in productList) {
|
||||
total += (product?['buyNum'] as int? ?? 0);
|
||||
total = product['quantity'];
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
Widget _buildOrderInfoRow(String label, String value) {
|
||||
// logger.e(orderGoodsInfo);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 6.0),
|
||||
child: Row(
|
||||
|
||||
@ -3,7 +3,8 @@ library;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:loopin/utils/wxsdk.dart';
|
||||
import 'package:loopin/IM/push_service.dart';
|
||||
import 'package:loopin/api/common_api.dart';
|
||||
import 'package:loopin/api/shop_api.dart';
|
||||
import 'package:loopin/components/my_toast.dart';
|
||||
import 'package:loopin/service/http.dart';
|
||||
@ -28,33 +29,45 @@ class _SellerState extends State<SellerOrder> with SingleTickerProviderStateMixi
|
||||
List<Map<String, dynamic>> _allOrders = [];
|
||||
List<Map<String, dynamic>> _pendingPaymentOrders = [];
|
||||
List<Map<String, dynamic>> _pendingVerificationOrders = [];
|
||||
List<Map<String, dynamic>> _completedOrders = [];
|
||||
// List<Map<String, dynamic>> _completedOrders = [];
|
||||
List<Map<String, dynamic>> _closedOrders = [];
|
||||
List<Map<String, dynamic>> _refundingOrders = [];
|
||||
List<Map<String, dynamic>> _refundedOrders = [];
|
||||
List<Map<String, dynamic>> _cancelledOrders = [];
|
||||
|
||||
// 每个Tab的分页状态
|
||||
Map<int, int> _pageNums = {};
|
||||
Map<int, int> _totalCounts = {};
|
||||
Map<int, bool> _hasMoreData = {};
|
||||
Map<int, bool> _isLoading = {};
|
||||
final Map<int, int> _pageNums = {};
|
||||
final Map<int, int> _totalCounts = {};
|
||||
final Map<int, bool> _hasMoreData = {};
|
||||
final Map<int, bool> _isLoading = {};
|
||||
|
||||
// 订单状态:0->待付款;1->待核销;2->已完成;3->已关闭;4->退款中;5->已退款 6->已取消 精确匹配
|
||||
// List tabList = [
|
||||
// {'id': '', 'name': "全部", 'index': 0},
|
||||
// {'id': 0, 'name': "待付款", 'index': 1},
|
||||
// {'id': 1, 'name': "待核销", 'index': 2},
|
||||
// {'id': 2, 'name': "已完成", 'index': 3},
|
||||
// {'id': 3, 'name': "已关闭", 'index': 4},
|
||||
// {'id': 4, 'name': "退款中", 'index': 5},
|
||||
// {'id': 5, 'name': "已退款", 'index': 6},
|
||||
// {'id': 6, 'name': "已取消", 'index': 7}
|
||||
// ];
|
||||
List tabList = [
|
||||
{'id': '', 'name': "全部", 'index': 0},
|
||||
{'id': 0, 'name': "待付款", 'index': 1},
|
||||
{'id': 1, 'name': "待核销", 'index': 2},
|
||||
{'id': 2, 'name': "已完成", 'index': 3},
|
||||
{'id': 3, 'name': "已关闭", 'index': 4},
|
||||
{'id': 4, 'name': "退款中", 'index': 5},
|
||||
{'id': 5, 'name': "已退款", 'index': 6},
|
||||
{'id': 6, 'name': "已取消", 'index': 7}
|
||||
{'id': 2, 'name': "已支付", 'index': 2},
|
||||
// {'id': 2, 'name': "已完成", 'index': 3},
|
||||
{'id': 3, 'name': "已关闭", 'index': 3},
|
||||
{'id': 4, 'name': "退款中", 'index': 4},
|
||||
{'id': 5, 'name': "已退款", 'index': 5},
|
||||
{'id': 6, 'name': "已取消", 'index': 6}
|
||||
];
|
||||
|
||||
late ScrollController scrollController = ScrollController();
|
||||
late TabController tabController = TabController(initialIndex: 0, length: tabList.length, vsync: this);
|
||||
|
||||
late List orderStatusDict;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@ -70,8 +83,9 @@ class _SellerState extends State<SellerOrder> with SingleTickerProviderStateMixi
|
||||
scrollController.addListener(_scrollListener);
|
||||
// 监听tab切换事件
|
||||
tabController.addListener(_tabChanged);
|
||||
getOrderStatusDict();
|
||||
// 获取初始订单列表
|
||||
getOrderList(false);
|
||||
// getOrderList(false);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -83,19 +97,37 @@ class _SellerState extends State<SellerOrder> with SingleTickerProviderStateMixi
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> getOrderStatusDict() async {
|
||||
final res = await Http.get('${CommonApi.dictionaryApi}oms_order_status');
|
||||
logger.e(res);
|
||||
orderStatusDict = res['data'] as List;
|
||||
//获取初始订单列表
|
||||
getOrderList(false);
|
||||
// dictValue
|
||||
}
|
||||
|
||||
// 获取当前Tab的订单列表
|
||||
List<Map<String, dynamic>> _getCurrentOrderList() {
|
||||
final currentIndex = tabController.index;
|
||||
switch (currentIndex) {
|
||||
case 0: return _allOrders;
|
||||
case 1: return _pendingPaymentOrders;
|
||||
case 2: return _pendingVerificationOrders;
|
||||
case 3: return _completedOrders;
|
||||
case 4: return _closedOrders;
|
||||
case 5: return _refundingOrders;
|
||||
case 6: return _refundedOrders;
|
||||
case 7: return _cancelledOrders;
|
||||
default: return _allOrders;
|
||||
case 0:
|
||||
return _allOrders;
|
||||
case 1:
|
||||
return _pendingPaymentOrders;
|
||||
case 2:
|
||||
return _pendingVerificationOrders;
|
||||
// case 3:
|
||||
// return _completedOrders;
|
||||
case 3:
|
||||
return _closedOrders;
|
||||
case 4:
|
||||
return _refundingOrders;
|
||||
case 5:
|
||||
return _refundedOrders;
|
||||
case 6:
|
||||
return _cancelledOrders;
|
||||
default:
|
||||
return _allOrders;
|
||||
}
|
||||
}
|
||||
|
||||
@ -125,35 +157,35 @@ class _SellerState extends State<SellerOrder> with SingleTickerProviderStateMixi
|
||||
_pendingVerificationOrders = orders;
|
||||
}
|
||||
break;
|
||||
// case 3:
|
||||
// if (loadMore) {
|
||||
// _completedOrders.addAll(orders);
|
||||
// } else {
|
||||
// _completedOrders = orders;
|
||||
// }
|
||||
// break;
|
||||
case 3:
|
||||
if (loadMore) {
|
||||
_completedOrders.addAll(orders);
|
||||
} else {
|
||||
_completedOrders = orders;
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
if (loadMore) {
|
||||
_closedOrders.addAll(orders);
|
||||
} else {
|
||||
_closedOrders = orders;
|
||||
}
|
||||
break;
|
||||
case 5:
|
||||
case 4:
|
||||
if (loadMore) {
|
||||
_refundingOrders.addAll(orders);
|
||||
} else {
|
||||
_refundingOrders = orders;
|
||||
}
|
||||
break;
|
||||
case 6:
|
||||
case 5:
|
||||
if (loadMore) {
|
||||
_refundedOrders.addAll(orders);
|
||||
} else {
|
||||
_refundedOrders = orders;
|
||||
}
|
||||
break;
|
||||
case 7:
|
||||
case 6:
|
||||
if (loadMore) {
|
||||
_cancelledOrders.addAll(orders);
|
||||
} else {
|
||||
@ -167,9 +199,7 @@ class _SellerState extends State<SellerOrder> with SingleTickerProviderStateMixi
|
||||
// 滚动监听
|
||||
void _scrollListener() {
|
||||
final currentIndex = tabController.index;
|
||||
if (scrollController.position.pixels == scrollController.position.maxScrollExtent &&
|
||||
!_isLoading[currentIndex]! &&
|
||||
_hasMoreData[currentIndex]!) {
|
||||
if (scrollController.position.pixels == scrollController.position.maxScrollExtent && !_isLoading[currentIndex]! && _hasMoreData[currentIndex]!) {
|
||||
getOrderList(true);
|
||||
}
|
||||
}
|
||||
@ -179,6 +209,7 @@ class _SellerState extends State<SellerOrder> with SingleTickerProviderStateMixi
|
||||
if (tabController.index != tabController.previousIndex) {
|
||||
// 如果当前Tab没有数据,则加载数据
|
||||
final currentOrders = _getCurrentOrderList();
|
||||
|
||||
if (currentOrders.isEmpty) {
|
||||
getOrderList(false);
|
||||
}
|
||||
@ -207,16 +238,17 @@ class _SellerState extends State<SellerOrder> with SingleTickerProviderStateMixi
|
||||
final res = await Http.post(ShopApi.sellerOrderList, data: {
|
||||
'current': _pageNums[currentIndex], // 当前页码
|
||||
'size': pageSize,
|
||||
'status': status == '' ? '' : status.toString(),
|
||||
'orderStatus': status == '' ? '' : status.toString(),
|
||||
});
|
||||
logger.w(status);
|
||||
|
||||
logger.w(res);
|
||||
|
||||
if (res['code'] == 200 && res['data'] != null) {
|
||||
final data = res['data'];
|
||||
final List<Map<String, dynamic>> newOrders = List<Map<String, dynamic>>.from(data['records'] ?? []);
|
||||
final int total = data['total'] ?? 0;
|
||||
|
||||
debugPrint(data.toString(), wrapWidth: 1024);
|
||||
|
||||
// 更新当前Tab的数据
|
||||
_setCurrentOrderList(newOrders, loadMore);
|
||||
|
||||
@ -236,7 +268,7 @@ class _SellerState extends State<SellerOrder> with SingleTickerProviderStateMixi
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
print('获取订单列表失败: $e');
|
||||
logger.w('获取订单列表失败: $e');
|
||||
setState(() {
|
||||
_hasMoreData[currentIndex] = false;
|
||||
});
|
||||
@ -266,11 +298,11 @@ class _SellerState extends State<SellerOrder> with SingleTickerProviderStateMixi
|
||||
|
||||
switch (statusCode) {
|
||||
case 0: // 待付款
|
||||
return '待付款: ';
|
||||
case 1: // 待核销
|
||||
return '用户待付款: ';
|
||||
case 2: // 待核销
|
||||
return '已付款: ';
|
||||
case 2: // 已完成
|
||||
return '实付款: ';
|
||||
// case 2: // 已完成
|
||||
// return '实付款: ';
|
||||
case 3: // 已关闭
|
||||
return '订单金额: ';
|
||||
case 4: // 退款中
|
||||
@ -310,13 +342,13 @@ class _SellerState extends State<SellerOrder> with SingleTickerProviderStateMixi
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
spacing: 5.0,
|
||||
children: [
|
||||
ClipOval(
|
||||
child: Image.asset(
|
||||
'assets/images/avatar/img11.jpg',
|
||||
width: 25.0,
|
||||
),
|
||||
),
|
||||
Text(order['items'][0]['tenantName'] ?? '商家名称'),
|
||||
// ClipOval(
|
||||
// child: Image.asset(
|
||||
// 'assets/images/avatar/img11.jpg',
|
||||
// width: 25.0,
|
||||
// ),
|
||||
// ),
|
||||
Text(order['tenantName'] ?? '商家名称'),
|
||||
Icon(
|
||||
Icons.arrow_forward_ios_rounded,
|
||||
color: Colors.grey,
|
||||
@ -325,7 +357,12 @@ class _SellerState extends State<SellerOrder> with SingleTickerProviderStateMixi
|
||||
],
|
||||
),
|
||||
Spacer(),
|
||||
Text(
|
||||
order['verificationCodeStatus'] == 1
|
||||
? Text(
|
||||
'已核销',
|
||||
style: TextStyle(color: Colors.red),
|
||||
)
|
||||
: Text(
|
||||
_getStatusText(order['status']),
|
||||
style: TextStyle(color: _getStatusColor(order['status'])),
|
||||
)
|
||||
@ -333,23 +370,25 @@ class _SellerState extends State<SellerOrder> with SingleTickerProviderStateMixi
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
// 商品信息
|
||||
if (order['items'] != null && order['items'].isNotEmpty)
|
||||
...order['items'].map<Widget>((item) => Row(
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: 80.0,
|
||||
height: 80.0,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[200],
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(4.0),
|
||||
),
|
||||
child: order['items'][0]['pic'] != null && order['items'][0]['pic'].isNotEmpty
|
||||
? Image.network(
|
||||
order['items'][0]['pic'],
|
||||
child: order['pic'] != null && order['pic'].isNotEmpty
|
||||
? ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
child: Image.network(
|
||||
order['pic'],
|
||||
width: 80.0,
|
||||
height: 80.0,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
)
|
||||
: Icon(
|
||||
Icons.shopping_bag_outlined,
|
||||
@ -363,7 +402,7 @@ class _SellerState extends State<SellerOrder> with SingleTickerProviderStateMixi
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
item['productName'] ?? '商品名称',
|
||||
order['productName'] ?? '商品名称',
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(fontSize: 14),
|
||||
@ -372,12 +411,12 @@ class _SellerState extends State<SellerOrder> with SingleTickerProviderStateMixi
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
'¥${item['salePrice'] ?? '0'}',
|
||||
'¥${order['salePrice'] ?? '0'}',
|
||||
style: TextStyle(color: Colors.red, fontSize: 16),
|
||||
),
|
||||
Spacer(),
|
||||
Text(
|
||||
'x${item['quantity'] ?? '1'}',
|
||||
'x${order['quantity'] ?? '1'}',
|
||||
style: TextStyle(color: Colors.grey),
|
||||
),
|
||||
],
|
||||
@ -386,13 +425,13 @@ class _SellerState extends State<SellerOrder> with SingleTickerProviderStateMixi
|
||||
),
|
||||
)
|
||||
],
|
||||
)).toList(),
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
// 金额信息
|
||||
Container(
|
||||
padding: EdgeInsets.all(5.0),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[50],
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(5.0),
|
||||
),
|
||||
child: Row(
|
||||
@ -402,7 +441,7 @@ class _SellerState extends State<SellerOrder> with SingleTickerProviderStateMixi
|
||||
TextSpan(children: [
|
||||
TextSpan(text: _getPaymentText(order['status'])), // 根据状态获取文案
|
||||
TextSpan(
|
||||
text: '¥${order['totalAmount'] ?? '0'}',
|
||||
text: '¥${order['salePrice'] ?? '0'}',
|
||||
style: TextStyle(color: Colors.red, fontSize: 16),
|
||||
),
|
||||
]),
|
||||
@ -417,16 +456,27 @@ class _SellerState extends State<SellerOrder> with SingleTickerProviderStateMixi
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
Get.toNamed('/sellerOrder/detail', arguments: {'orderId': order['id']});
|
||||
// logger.e(order);
|
||||
Get.toNamed('/sellerOrder/detail', arguments: {'orderId': order['orderId']});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// 获取状态文本
|
||||
String _getStatusText(dynamic status) {
|
||||
int statusCode = status is String ? int.tryParse(status) ?? -1 : (status is int ? status : -1);
|
||||
final match = orderStatusDict.firstWhere(
|
||||
(item) => item['dictValue'].toString() == statusCode.toString(),
|
||||
orElse: () => {'dictLabel': '未知状态'},
|
||||
);
|
||||
|
||||
return match['dictLabel'] ?? '未知状态';
|
||||
}
|
||||
|
||||
// 获取状态文本
|
||||
String _getStatusText_old(dynamic status) {
|
||||
// 处理字符串或数字类型的状态
|
||||
int statusCode = status is String ? int.tryParse(status) ?? 0 : (status is int ? status : 0);
|
||||
|
||||
switch (statusCode) {
|
||||
case 0:
|
||||
return '待付款';
|
||||
@ -479,33 +529,34 @@ class _SellerState extends State<SellerOrder> with SingleTickerProviderStateMixi
|
||||
int statusCode = status is String ? int.tryParse(status) ?? 0 : (status is int ? status : 0);
|
||||
switch (statusCode) {
|
||||
case 0: // 待付款
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
ElevatedButton(
|
||||
onPressed: () => _cancelOrder(orderId),
|
||||
style: ButtonStyle(backgroundColor: WidgetStateProperty.all(Colors.white)),
|
||||
child: Text('取消订单'),
|
||||
),
|
||||
SizedBox(width: 10),
|
||||
ElevatedButton(
|
||||
onPressed: () => _payOrder(orderId),
|
||||
style: ButtonStyle(
|
||||
backgroundColor: WidgetStateProperty.all(Color(0xff07c160)),
|
||||
foregroundColor: WidgetStateProperty.all(Colors.white),
|
||||
),
|
||||
child: Text('去支付'),
|
||||
),
|
||||
],
|
||||
);
|
||||
case 1: // 待核销
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
);
|
||||
case 2: // 已完成
|
||||
return SizedBox();
|
||||
// return Row(
|
||||
// mainAxisAlignment: MainAxisAlignment.end,
|
||||
// children: [
|
||||
// ElevatedButton(
|
||||
// onPressed: () => _cancelOrder(orderId),
|
||||
// style: ButtonStyle(backgroundColor: WidgetStateProperty.all(Colors.white)),
|
||||
// child: Text('取消订单'),
|
||||
// ),
|
||||
// SizedBox(width: 10),
|
||||
// ElevatedButton(
|
||||
// onPressed: () => _payOrder(orderId),
|
||||
// style: ButtonStyle(
|
||||
// backgroundColor: WidgetStateProperty.all(Color(0xff07c160)),
|
||||
// foregroundColor: WidgetStateProperty.all(Colors.white),
|
||||
// ),
|
||||
// child: Text('用户待付款'),
|
||||
// ),
|
||||
// ],
|
||||
// );
|
||||
case 2: // 待核销
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
);
|
||||
// case 2: // 已完成
|
||||
// return Row(
|
||||
// mainAxisAlignment: MainAxisAlignment.end,
|
||||
// );
|
||||
default:
|
||||
return SizedBox.shrink();
|
||||
}
|
||||
@ -515,7 +566,7 @@ class _SellerState extends State<SellerOrder> with SingleTickerProviderStateMixi
|
||||
void _cancelOrder(String orderId) async {
|
||||
try {
|
||||
final res = await Http.post('${ShopApi.cancelGoodsOrder}/$orderId');
|
||||
print('取消订单成功-------------->${res}');
|
||||
logger.w('取消订单成功-------------->$res');
|
||||
|
||||
if (res['code'] == 200) {
|
||||
MyToast().tip(
|
||||
@ -527,7 +578,7 @@ class _SellerState extends State<SellerOrder> with SingleTickerProviderStateMixi
|
||||
_refreshAllRelatedTabs();
|
||||
}
|
||||
} catch (e) {
|
||||
print('取消订单失败-------------->${e}');
|
||||
logger.w('取消订单失败-------------->$e');
|
||||
}
|
||||
}
|
||||
|
||||
@ -546,9 +597,9 @@ class _SellerState extends State<SellerOrder> with SingleTickerProviderStateMixi
|
||||
}
|
||||
}
|
||||
|
||||
void _payOrder(_orderId) {
|
||||
void _payOrder(orderId) {
|
||||
// 打开微信小程序的某个页面地址,如pages/index/index
|
||||
Wxsdk.openMiniApp(orderId: _orderId);
|
||||
// Wxsdk.openMiniApp(orderId: orderId);
|
||||
}
|
||||
|
||||
// 重置指定tab的数据
|
||||
@ -560,14 +611,30 @@ class _SellerState extends State<SellerOrder> with SingleTickerProviderStateMixi
|
||||
_hasMoreData[index] = true;
|
||||
|
||||
switch (index) {
|
||||
case 0: _allOrders = []; break;
|
||||
case 1: _pendingPaymentOrders = []; break;
|
||||
case 2: _pendingVerificationOrders = []; break;
|
||||
case 3: _completedOrders = []; break;
|
||||
case 4: _closedOrders = []; break;
|
||||
case 5: _refundingOrders = []; break;
|
||||
case 6: _refundedOrders = []; break;
|
||||
case 7: _cancelledOrders = []; break;
|
||||
case 0:
|
||||
_allOrders = [];
|
||||
break;
|
||||
case 1:
|
||||
_pendingPaymentOrders = []; // 待支付
|
||||
break;
|
||||
case 2:
|
||||
_pendingVerificationOrders = []; // 待核销
|
||||
break;
|
||||
// case 3:
|
||||
// _completedOrders = []; // 已完成
|
||||
// break;
|
||||
case 3:
|
||||
_closedOrders = []; // 关闭
|
||||
break;
|
||||
case 4:
|
||||
_refundingOrders = []; // 退款中
|
||||
break;
|
||||
case 5:
|
||||
_refundedOrders = []; // 已退款
|
||||
break;
|
||||
case 6:
|
||||
_cancelledOrders = []; // 已取消
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -627,7 +694,9 @@ class _SellerState extends State<SellerOrder> with SingleTickerProviderStateMixi
|
||||
),
|
||||
),
|
||||
),
|
||||
body: TabBarView(
|
||||
body: SafeArea(
|
||||
bottom: true,
|
||||
child: TabBarView(
|
||||
controller: tabController,
|
||||
children: List.generate(tabList.length, (index) {
|
||||
return RefreshIndicator(
|
||||
@ -638,8 +707,7 @@ class _SellerState extends State<SellerOrder> with SingleTickerProviderStateMixi
|
||||
behavior: CustomScrollBehavior().copyWith(scrollbars: false),
|
||||
child: Container(
|
||||
color: Colors.grey[50],
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
child: Builder(builder: (context) {
|
||||
final currentOrders = _getOrderListByIndex(index);
|
||||
final isLoading = _isLoading[index] ?? false;
|
||||
final hasMoreData = _hasMoreData[index] ?? false;
|
||||
@ -658,28 +726,37 @@ class _SellerState extends State<SellerOrder> with SingleTickerProviderStateMixi
|
||||
return _buildOrderItem(currentOrders[itemIndex]);
|
||||
},
|
||||
);
|
||||
}
|
||||
),
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 根据索引获取订单列表
|
||||
List<Map<String, dynamic>> _getOrderListByIndex(int index) {
|
||||
switch (index) {
|
||||
case 0: return _allOrders;
|
||||
case 1: return _pendingPaymentOrders;
|
||||
case 2: return _pendingVerificationOrders;
|
||||
case 3: return _completedOrders;
|
||||
case 4: return _closedOrders;
|
||||
case 5: return _refundingOrders;
|
||||
case 6: return _refundedOrders;
|
||||
case 7: return _cancelledOrders;
|
||||
default: return _allOrders;
|
||||
case 0:
|
||||
return _allOrders;
|
||||
case 1:
|
||||
return _pendingPaymentOrders;
|
||||
case 2:
|
||||
return _pendingVerificationOrders;
|
||||
// case 3:
|
||||
// return _completedOrders;
|
||||
case 3:
|
||||
return _closedOrders;
|
||||
case 4:
|
||||
return _refundingOrders;
|
||||
case 5:
|
||||
return _refundedOrders;
|
||||
case 6:
|
||||
return _cancelledOrders;
|
||||
default:
|
||||
return _allOrders;
|
||||
}
|
||||
}
|
||||
|
||||
@ -707,7 +784,7 @@ class _SellerState extends State<SellerOrder> with SingleTickerProviderStateMixi
|
||||
}
|
||||
|
||||
Widget emptyTip() {
|
||||
return Container(
|
||||
return SizedBox(
|
||||
width: double.infinity,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
// 搜索
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:loopin/IM/im_service.dart' hide logger;
|
||||
import 'package:loopin/IM/controller/im_user_info_controller.dart';
|
||||
import 'package:loopin/IM/im_service.dart';
|
||||
import 'package:loopin/api/common_api.dart';
|
||||
import 'package:loopin/components/my_toast.dart';
|
||||
import 'package:loopin/service/http.dart';
|
||||
@ -41,6 +43,9 @@ class _SearchResultPageState extends State<SearchResultPage> with SingleTickerPr
|
||||
// 统一的高度常量
|
||||
static const double _itemCornerRadius = 8;
|
||||
|
||||
// 个人信息
|
||||
final ctl = Get.find<ImUserInfoController>();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@ -269,6 +274,11 @@ class _SearchResultPageState extends State<SearchResultPage> with SingleTickerPr
|
||||
|
||||
// 点击关注按钮
|
||||
onFocusBtnClick(user, index) async {
|
||||
if (ctl.userID == user['id']) {
|
||||
MyToast().tip(title: '不能关注自己');
|
||||
return;
|
||||
}
|
||||
//检测是不是自己
|
||||
final vlogerId = user['id'];
|
||||
final doIFollowVloger = user['doIFollowVloger'];
|
||||
print('是否关注此用户------------->$doIFollowVloger');
|
||||
@ -507,6 +517,7 @@ class _SearchResultPageState extends State<SearchResultPage> with SingleTickerPr
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 视频项构建
|
||||
Widget _buildVideoItem(Map<String, dynamic> video) {
|
||||
return GestureDetector(
|
||||
@ -608,9 +619,7 @@ class _SearchResultPageState extends State<SearchResultPage> with SingleTickerPr
|
||||
)
|
||||
: null,
|
||||
),
|
||||
child: video['avatar'] == null || video['avatar'].toString().isEmpty
|
||||
? const Icon(Icons.person, size: 10, color: Colors.grey)
|
||||
: null,
|
||||
child: video['avatar'] == null || video['avatar'].toString().isEmpty ? const Icon(Icons.person, size: 10, color: Colors.grey) : null,
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
// 用户名
|
||||
@ -683,6 +692,7 @@ class _SearchResultPageState extends State<SearchResultPage> with SingleTickerPr
|
||||
|
||||
return Container();
|
||||
}
|
||||
|
||||
// 商品Tab
|
||||
Widget _buildProductTab() {
|
||||
if (_productResults.isEmpty && !_isLoading) {
|
||||
@ -920,7 +930,23 @@ class _SearchResultPageState extends State<SearchResultPage> with SingleTickerPr
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
logger.w(user);
|
||||
// 检测是否自己
|
||||
if (ctl.userID.value == user['id']) {
|
||||
return;
|
||||
}
|
||||
//点击头像
|
||||
final vloggerId = user['id'];
|
||||
final result = await Get.toNamed('/vloger', arguments: {'memberId': vloggerId});
|
||||
if (result != null) {
|
||||
setState(() {
|
||||
user['doIFollowVloger'] = result['followStatus'];
|
||||
});
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
width: 45,
|
||||
height: 45,
|
||||
decoration: BoxDecoration(
|
||||
@ -935,9 +961,27 @@ class _SearchResultPageState extends State<SearchResultPage> with SingleTickerPr
|
||||
),
|
||||
child: user['avatar'] == null ? const Icon(Icons.person, color: Colors.grey, size: 20) : null,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () async {
|
||||
// 检测是否自己
|
||||
if (ctl.userID.value == user['id']) {
|
||||
return;
|
||||
}
|
||||
//点击内容区域
|
||||
final vloggerId = user['id'];
|
||||
final result = await Get.toNamed('/vloger', arguments: {'memberId': vloggerId});
|
||||
if (result != null) {
|
||||
setState(() {
|
||||
user['doIFollowVloger'] = result['followStatus'];
|
||||
});
|
||||
}
|
||||
},
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
@ -958,6 +1002,8 @@ class _SearchResultPageState extends State<SearchResultPage> with SingleTickerPr
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
// 关注按钮
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
await onFocusBtnClick(user, index);
|
||||
@ -56,8 +56,8 @@ class _UploadVideoPageState extends State<UploadVideoPage> {
|
||||
|
||||
Future<void> pickVideo() async {
|
||||
descFocusNode.unfocus();
|
||||
final hasPer = await Permissions.requestPhotoPermission();
|
||||
|
||||
final hasPer = await Permissions.requestVideoPermission();
|
||||
logger.e(hasPer);
|
||||
if (!hasPer) {
|
||||
Permissions.showPermissionDialog('相册');
|
||||
return;
|
||||
@ -77,7 +77,7 @@ class _UploadVideoPageState extends State<UploadVideoPage> {
|
||||
filterOptions: FilterOptionGroup(
|
||||
videoOption: const FilterOption(
|
||||
durationConstraint: DurationConstraint(
|
||||
max: Duration(seconds: 30),
|
||||
max: Duration(minutes: 30),
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -90,8 +90,9 @@ class _UploadVideoPageState extends State<UploadVideoPage> {
|
||||
if (file != null) {
|
||||
final fileSizeInBytes = await file.length();
|
||||
final sizeInMB = fileSizeInBytes / (1024 * 1024);
|
||||
if (sizeInMB > 200) {
|
||||
MyDialog.toast('文件大小不能超过200MB', icon: const Icon(Icons.check_circle), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200)));
|
||||
// 时间长度
|
||||
if (sizeInMB > 300) {
|
||||
MyDialog.toast('文件大小不能超过300MB', icon: const Icon(Icons.check_circle), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200)));
|
||||
} else {
|
||||
logger.i("文件合法,大小:$sizeInMB MB");
|
||||
selectedVideo.value = pickedAssets.first;
|
||||
|
||||
@ -285,14 +285,17 @@ class _VideoDetailPageState extends State<VideoDetailPage> {
|
||||
),
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return CommentBottomSheet(
|
||||
return SafeArea(
|
||||
child: CommentBottomSheet(
|
||||
videoId: videoId,
|
||||
onCommentCountChanged: (newCount) {
|
||||
// 更新视频的评论数量
|
||||
setState(() {
|
||||
videoData['commentsCounts'] = newCount;
|
||||
});
|
||||
});
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -455,13 +458,9 @@ class _VideoDetailPageState extends State<VideoDetailPage> {
|
||||
Future<void> _downloadVideoWithDio(String videoUrl, String fileName) async {
|
||||
try {
|
||||
// 请求存储权限
|
||||
var status = await Permissions.requestStoragePermission();
|
||||
var status = await Permissions.requestStoragePermission(title: '文件存储使用说明', content: '用于下载视频');
|
||||
if (!status) {
|
||||
MyToast().tip(
|
||||
title: '需要存储权限才能下载视频',
|
||||
position: 'center',
|
||||
type: 'error',
|
||||
);
|
||||
Permissions.showPermissionDialog('存储');
|
||||
return;
|
||||
}
|
||||
await DownloadManager.downloadFile(
|
||||
@ -603,8 +602,15 @@ class _VideoDetailPageState extends State<VideoDetailPage> {
|
||||
AnimatedOpacity(
|
||||
opacity: position > Duration(milliseconds: 100) ? 0.0 : 1.0,
|
||||
duration: Duration(milliseconds: 50),
|
||||
child: Image.network(
|
||||
videoData['firstFrameImg'] ?? 'https://wuzhongjie.com.cn/download/logo.png',
|
||||
// child: Image.network(
|
||||
// videoData['firstFrameImg'] ?? 'https://wuzhongjie.com.cn/download/logo.png',
|
||||
// fit: isHorizontal ? BoxFit.contain : BoxFit.cover,
|
||||
// width: double.infinity,
|
||||
// height: double.infinity,
|
||||
// ),
|
||||
child: NetworkOrAssetImage(
|
||||
imageUrl: videoData['firstFrameImg'] ?? '',
|
||||
placeholderAsset: 'assets/images/bk.jpg',
|
||||
fit: isHorizontal ? BoxFit.contain : BoxFit.cover,
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
@ -676,7 +682,7 @@ class _VideoDetailPageState extends State<VideoDetailPage> {
|
||||
),
|
||||
child: ClipOval(
|
||||
child: NetworkOrAssetImage(
|
||||
imageUrl: videoData['vlogerFace'] ?? videoData['commentUserFace'],
|
||||
imageUrl: videoData['vlogerFace'] ?? videoData['avatar'],
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -801,7 +807,7 @@ class _VideoDetailPageState extends State<VideoDetailPage> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'@${videoData['vlogerNickname'] ?? videoData['commentUserNickname'] ?? '未知用户'}',
|
||||
'@${videoData['nickname'] ?? '未知用户'}',
|
||||
style: const TextStyle(color: Colors.white, fontSize: 16.0),
|
||||
),
|
||||
LayoutBuilder(
|
||||
@ -1172,8 +1178,8 @@ class _CommentBottomSheetState extends State<CommentBottomSheet> {
|
||||
|
||||
final comment = commentList[index];
|
||||
final hasReplies = comment['childCount'] > 0;
|
||||
final isExpanded = expandedReplies[comment['id']] == true;
|
||||
final replies = replyData[comment['id']] ?? [];
|
||||
final isExpanded = expandedReplies[comment['commentId']] == true;
|
||||
final replies = replyData[comment['commentId']] ?? [];
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
@ -1204,7 +1210,7 @@ class _CommentBottomSheetState extends State<CommentBottomSheet> {
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
replyingCommentId = comment['id'];
|
||||
replyingCommentId = comment['commentId'];
|
||||
replyingCommentUser = comment['commentUserNickname'] ?? '未知用户';
|
||||
});
|
||||
},
|
||||
@ -1232,10 +1238,10 @@ class _CommentBottomSheetState extends State<CommentBottomSheet> {
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
expandedReplies[comment['id']] = !isExpanded;
|
||||
if (expandedReplies[comment['id']] == true &&
|
||||
(replyData[comment['id']] == null || replyData[comment['id']]!.isEmpty)) {
|
||||
fetchReplies(comment['id'], false);
|
||||
expandedReplies[comment['commentId']] = !isExpanded;
|
||||
if (expandedReplies[comment['commentId']] == true &&
|
||||
(replyData[comment['commentId']] == null || replyData[comment['commentId']]!.isEmpty)) {
|
||||
fetchReplies(comment['commentId'], false);
|
||||
}
|
||||
});
|
||||
},
|
||||
@ -1296,7 +1302,7 @@ class _CommentBottomSheetState extends State<CommentBottomSheet> {
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
replyingCommentId = comment['id'];
|
||||
replyingCommentId = comment['commentId'];
|
||||
replyingCommentUser = reply['commentUserNickname'] ?? '未知用户';
|
||||
});
|
||||
},
|
||||
@ -1330,8 +1336,8 @@ class _CommentBottomSheetState extends State<CommentBottomSheet> {
|
||||
if (replies.length < comment['childCount'])
|
||||
Center(
|
||||
child: TextButton(
|
||||
onPressed: () => fetchReplies(comment['id'], true),
|
||||
child: isLoadingReplies[comment['id']] == true ? CircularProgressIndicator() : Text('加载更多回复'),
|
||||
onPressed: () => fetchReplies(comment['commentId'], true),
|
||||
child: isLoadingReplies[comment['commentId']] == true ? CircularProgressIndicator() : Text('加载更多回复'),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@ -9,16 +9,10 @@ import '../../IM/im_core.dart';
|
||||
import '../../behavior/custom_scroll_behavior.dart';
|
||||
import '../../components/keepalive_wrapper.dart';
|
||||
import '../../controller/video_module_controller.dart';
|
||||
|
||||
import './module/attention.dart';
|
||||
// import './module/browse.dart';
|
||||
// import './module/buying.dart';
|
||||
// import './module/drama.dart';
|
||||
// import './module/live.dart';
|
||||
import './module/friend.dart';
|
||||
import './module/recommend.dart';
|
||||
// 引入tab内容模块
|
||||
// import './module/subscribe.dart';
|
||||
|
||||
class VideoPage extends StatefulWidget {
|
||||
const VideoPage({super.key});
|
||||
@ -137,12 +131,18 @@ class _VideoPageState extends State<VideoPage> with SingleTickerProviderStateMix
|
||||
),
|
||||
onPressed: () {
|
||||
print('当前tab索引:${videoModuleController.videoTabIndex.value}');
|
||||
if(videoModuleController.videoTabIndex.value == 0){
|
||||
AttentionModule.pauseVideo();
|
||||
}else if(videoModuleController.videoTabIndex.value ==1){
|
||||
if (videoModuleController.videoTabIndex.value == 0) {
|
||||
AttentionModule.playVideo();
|
||||
FriendModule.pauseVideo();
|
||||
}else if(videoModuleController.videoTabIndex.value ==2){
|
||||
RecommendModule.pauseVideo();
|
||||
} else if (videoModuleController.videoTabIndex.value == 1) {
|
||||
AttentionModule.pauseVideo();
|
||||
FriendModule.playVideo();
|
||||
RecommendModule.pauseVideo();
|
||||
} else if (videoModuleController.videoTabIndex.value == 2) {
|
||||
AttentionModule.pauseVideo();
|
||||
FriendModule.pauseVideo();
|
||||
RecommendModule.playVideo();
|
||||
}
|
||||
Get.toNamed('/search');
|
||||
},
|
||||
@ -159,15 +159,15 @@ class _VideoPageState extends State<VideoPage> with SingleTickerProviderStateMix
|
||||
// 根据当前 tab 控制对应播放器播放,其它暂停
|
||||
if (index == 0) {
|
||||
AttentionModule.playVideo();
|
||||
// FriendModule.pauseVideo();
|
||||
FriendModule.pauseVideo();
|
||||
RecommendModule.pauseVideo();
|
||||
} else if (index == 1) {
|
||||
AttentionModule.pauseVideo();
|
||||
// FriendModule.playVideo();
|
||||
FriendModule.playVideo();
|
||||
RecommendModule.pauseVideo();
|
||||
} else if (index == 2) {
|
||||
AttentionModule.pauseVideo();
|
||||
// FriendModule.pauseVideo();
|
||||
FriendModule.pauseVideo();
|
||||
RecommendModule.playVideo();
|
||||
}
|
||||
videoModuleController.updateVideoTabIndex(index);
|
||||
|
||||
@ -295,8 +295,8 @@ class _CommentBottomSheetState extends State<CommentBottomSheet> {
|
||||
|
||||
final comment = commentList[index];
|
||||
final hasReplies = comment['childCount'] > 0;
|
||||
final isExpanded = expandedReplies[comment['id']] == true;
|
||||
final replies = replyData[comment['id']] ?? [];
|
||||
final isExpanded = expandedReplies[comment['commentId']] == true;
|
||||
final replies = replyData[comment['commentId']] ?? [];
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
@ -327,7 +327,7 @@ class _CommentBottomSheetState extends State<CommentBottomSheet> {
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
replyingCommentId = comment['id'];
|
||||
replyingCommentId = comment['commentId'];
|
||||
replyingCommentUser = comment['commentUserNickname'] ?? '未知用户';
|
||||
});
|
||||
},
|
||||
@ -355,10 +355,10 @@ class _CommentBottomSheetState extends State<CommentBottomSheet> {
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
expandedReplies[comment['id']] = !isExpanded;
|
||||
if (expandedReplies[comment['id']] == true &&
|
||||
(replyData[comment['id']] == null || replyData[comment['id']]!.isEmpty)) {
|
||||
fetchReplies(comment['id'], false);
|
||||
expandedReplies[comment['commentId']] = !isExpanded;
|
||||
if (expandedReplies[comment['commentId']] == true &&
|
||||
(replyData[comment['commentId']] == null || replyData[comment['commentId']]!.isEmpty)) {
|
||||
fetchReplies(comment['commentId'], false);
|
||||
}
|
||||
});
|
||||
},
|
||||
@ -419,7 +419,7 @@ class _CommentBottomSheetState extends State<CommentBottomSheet> {
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
replyingCommentId = comment['id'];
|
||||
replyingCommentId = comment['commentId'];
|
||||
replyingCommentUser = reply['commentUserNickname'] ?? '未知用户';
|
||||
});
|
||||
},
|
||||
@ -453,8 +453,8 @@ class _CommentBottomSheetState extends State<CommentBottomSheet> {
|
||||
if (replies.length < comment['childCount'])
|
||||
Center(
|
||||
child: TextButton(
|
||||
onPressed: () => fetchReplies(comment['id'], true),
|
||||
child: isLoadingReplies[comment['id']] == true ? CircularProgressIndicator() : Text('加载更多回复'),
|
||||
onPressed: () => fetchReplies(comment['commentId'], true),
|
||||
child: isLoadingReplies[comment['commentId']] == true ? CircularProgressIndicator() : Text('加载更多回复'),
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -557,7 +557,7 @@ class _AttentionModuleState extends State<AttentionModule> {
|
||||
|
||||
// 页面controller
|
||||
late PageController pageController = PageController(
|
||||
initialPage: videoModuleController.videoPlayIndex.value,
|
||||
initialPage: videoModuleController.videoPlayIndexFoucs.value,
|
||||
viewportFraction: 1.0,
|
||||
);
|
||||
|
||||
@ -656,7 +656,7 @@ class _AttentionModuleState extends State<AttentionModule> {
|
||||
page = 1;
|
||||
isLoadingMore = false;
|
||||
videoList.clear();
|
||||
videoModuleController.updateVideoPlayIndex(0);
|
||||
videoModuleController.updateVideoPlayIndex2(0);
|
||||
sliderValue = 0.0;
|
||||
sliderDraging = false;
|
||||
position = Duration.zero;
|
||||
@ -708,7 +708,7 @@ class _AttentionModuleState extends State<AttentionModule> {
|
||||
// 初始化播放器
|
||||
player.open(
|
||||
Media(
|
||||
videoList[videoModuleController.videoPlayIndex.value]['url'],
|
||||
videoList[videoModuleController.videoPlayIndexFoucs.value]['url'],
|
||||
),
|
||||
play: false);
|
||||
player.setPlaylistMode(PlaylistMode.loop); // 循环播放;
|
||||
@ -771,7 +771,8 @@ class _AttentionModuleState extends State<AttentionModule> {
|
||||
),
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return CommentBottomSheet(
|
||||
return SafeArea(
|
||||
child: CommentBottomSheet(
|
||||
videoId: videoId,
|
||||
onCommentCountChanged: (newCount) {
|
||||
// 更新对应视频的评论数量
|
||||
@ -780,7 +781,9 @@ class _AttentionModuleState extends State<AttentionModule> {
|
||||
videoList[index]['commentsCounts'] = newCount;
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -902,9 +905,9 @@ class _AttentionModuleState extends State<AttentionModule> {
|
||||
|
||||
void handleShareClick(int index) {
|
||||
print("分享项 $index 被点击");
|
||||
final videoId = videoList[videoModuleController.videoPlayIndex.value]['id'];
|
||||
final videoUrl = videoList[videoModuleController.videoPlayIndex.value]['url'];
|
||||
final description = videoList[videoModuleController.videoPlayIndex.value]['title'] ?? '快来看看这个视频';
|
||||
final videoId = videoList[videoModuleController.videoPlayIndexFoucs.value]['id'];
|
||||
final videoUrl = videoList[videoModuleController.videoPlayIndexFoucs.value]['url'];
|
||||
final description = videoList[videoModuleController.videoPlayIndexFoucs.value]['title'] ?? '快来看看这个视频';
|
||||
logger.i('分享链接地址----------------: ${ShareType.video.name}?id=$videoId');
|
||||
if (index == 0) {
|
||||
// 分享好友
|
||||
@ -944,13 +947,9 @@ class _AttentionModuleState extends State<AttentionModule> {
|
||||
try {
|
||||
// 请求存储权限
|
||||
String? toastId; // 用于存储toast的ID,以便后续关闭
|
||||
var status = await Permissions.requestStoragePermission();
|
||||
var status = await Permissions.requestStoragePermission(title: '文件存储使用说明', content: '用于下载视频');
|
||||
if (!status) {
|
||||
MyToast().tip(
|
||||
title: '需要存储权限才能下载视频',
|
||||
position: 'center',
|
||||
type: 'success',
|
||||
);
|
||||
Permissions.showPermissionDialog('存储');
|
||||
return;
|
||||
}
|
||||
await DownloadManager.downloadFile(
|
||||
@ -983,10 +982,10 @@ class _AttentionModuleState extends State<AttentionModule> {
|
||||
void handlCoverClick(V2TimConversation conv) async {
|
||||
// 发送VideoMsg,获取当前视频信息
|
||||
final userId = conv.userID;
|
||||
final String url = videoList[videoModuleController.videoPlayIndex.value]['url'];
|
||||
final img = videoList[videoModuleController.videoPlayIndex.value]['firstFrameImg'];
|
||||
final width = videoList[videoModuleController.videoPlayIndex.value]['width'];
|
||||
final height = videoList[videoModuleController.videoPlayIndex.value]['height'];
|
||||
final String url = videoList[videoModuleController.videoPlayIndexFoucs.value]['url'];
|
||||
final img = videoList[videoModuleController.videoPlayIndexFoucs.value]['firstFrameImg'];
|
||||
final width = videoList[videoModuleController.videoPlayIndexFoucs.value]['width'];
|
||||
final height = videoList[videoModuleController.videoPlayIndexFoucs.value]['height'];
|
||||
final makeJson = jsonEncode({
|
||||
"width": width,
|
||||
"height": height,
|
||||
@ -1050,7 +1049,7 @@ class _AttentionModuleState extends State<AttentionModule> {
|
||||
scrollDirection: Axis.vertical,
|
||||
controller: pageController,
|
||||
onPageChanged: (index) async {
|
||||
videoModuleController.updateVideoPlayIndex(index);
|
||||
videoModuleController.updateVideoPlayIndex2(index);
|
||||
setState(() {
|
||||
sliderValue = 0.0;
|
||||
sliderDraging = false;
|
||||
@ -1082,7 +1081,7 @@ class _AttentionModuleState extends State<AttentionModule> {
|
||||
child: Stack(
|
||||
children: [
|
||||
Visibility(
|
||||
visible: videoModuleController.videoPlayIndex.value == index && position > Duration.zero,
|
||||
visible: videoModuleController.videoPlayIndexFoucs.value == index && position > Duration.zero,
|
||||
child: Video(
|
||||
controller: videoController,
|
||||
fit: isHorizontal ? BoxFit.contain : BoxFit.cover,
|
||||
@ -1090,7 +1089,7 @@ class _AttentionModuleState extends State<AttentionModule> {
|
||||
),
|
||||
),
|
||||
AnimatedOpacity(
|
||||
opacity: videoModuleController.videoPlayIndex.value == index && position > Duration(milliseconds: 100) ? 0.0 : 1.0,
|
||||
opacity: videoModuleController.videoPlayIndexFoucs.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',
|
||||
@ -1144,7 +1143,7 @@ class _AttentionModuleState extends State<AttentionModule> {
|
||||
onTap: () async {
|
||||
player.pause();
|
||||
// 跳转到 Vloger 页面并等待返回结果
|
||||
final vloggerId = videoList[videoModuleController.videoPlayIndex.value]['memberId'];
|
||||
final vloggerId = videoList[videoModuleController.videoPlayIndexFoucs.value]['memberId'];
|
||||
final result = await Get.toNamed('/vloger', arguments: {'memberId': vloggerId});
|
||||
if (result != null) {
|
||||
// 处理返回的参数
|
||||
@ -1164,7 +1163,7 @@ class _AttentionModuleState extends State<AttentionModule> {
|
||||
),
|
||||
child: ClipOval(
|
||||
child: NetworkOrAssetImage(
|
||||
imageUrl: videoList[index]['commentUserFace'],
|
||||
imageUrl: videoList[index]['avatar'],
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -1278,7 +1277,7 @@ class _AttentionModuleState extends State<AttentionModule> {
|
||||
onTap: () async {
|
||||
player.pause();
|
||||
// 跳转到举报页面并等待返回结果
|
||||
final result = await Get.toNamed('/report', arguments: videoList[videoModuleController.videoPlayIndex.value]);
|
||||
final result = await Get.toNamed('/report', arguments: videoList[videoModuleController.videoPlayIndexFoucs.value]);
|
||||
if (result != null) {
|
||||
player.play();
|
||||
}
|
||||
@ -1294,13 +1293,20 @@ class _AttentionModuleState extends State<AttentionModule> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (videoModuleController.videoPlayIndexFoucs.value < videoList.length)
|
||||
Text(
|
||||
'@${videoList[videoModuleController.videoPlayIndex.value]['nickname'] ?? '未知'}',
|
||||
'@${videoList[videoModuleController.videoPlayIndexFoucs.value]['nickname'] ?? '未知'}',
|
||||
style: const TextStyle(color: Colors.white, fontSize: 16.0),
|
||||
)
|
||||
else
|
||||
Text(
|
||||
'@未知',
|
||||
style: const TextStyle(color: Colors.white, fontSize: 16.0),
|
||||
),
|
||||
if (videoModuleController.videoPlayIndexFoucs.value < videoList.length)
|
||||
LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final text = videoList[videoModuleController.videoPlayIndex.value]['title'] ?? '未知';
|
||||
final text = videoList[videoModuleController.videoPlayIndexFoucs.value]['title'] ?? '未知';
|
||||
final span = TextSpan(
|
||||
text: text,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 14.0),
|
||||
@ -1318,8 +1324,8 @@ class _AttentionModuleState extends State<AttentionModule> {
|
||||
children: [
|
||||
Text(
|
||||
text,
|
||||
maxLines: videoList[videoModuleController.videoPlayIndex.value]['expanded'] ? null : 3,
|
||||
overflow: videoList[videoModuleController.videoPlayIndex.value]['expanded']
|
||||
maxLines: videoList[videoModuleController.videoPlayIndexFoucs.value]['expanded'] ? null : 3,
|
||||
overflow: videoList[videoModuleController.videoPlayIndexFoucs.value]['expanded']
|
||||
? TextOverflow.visible
|
||||
: TextOverflow.ellipsis,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 14.0),
|
||||
@ -1330,12 +1336,12 @@ class _AttentionModuleState extends State<AttentionModule> {
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
videoList[videoModuleController.videoPlayIndex.value]['expanded'] =
|
||||
!videoList[videoModuleController.videoPlayIndex.value]['expanded'];
|
||||
videoList[videoModuleController.videoPlayIndexFoucs.value]['expanded'] =
|
||||
!videoList[videoModuleController.videoPlayIndexFoucs.value]['expanded'];
|
||||
});
|
||||
},
|
||||
child: Text(
|
||||
videoList[videoModuleController.videoPlayIndex.value]['expanded'] ? '收起' : '展开更多',
|
||||
videoList[videoModuleController.videoPlayIndexFoucs.value]['expanded'] ? '收起' : '展开更多',
|
||||
textAlign: TextAlign.right,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
@ -1348,7 +1354,9 @@ class _AttentionModuleState extends State<AttentionModule> {
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
else
|
||||
SizedBox()
|
||||
],
|
||||
)),
|
||||
Positioned(
|
||||
@ -1356,7 +1364,7 @@ class _AttentionModuleState extends State<AttentionModule> {
|
||||
left: 6.0,
|
||||
right: 6.0,
|
||||
child: Visibility(
|
||||
visible: videoModuleController.videoPlayIndex.value == index && position > Duration.zero,
|
||||
visible: videoModuleController.videoPlayIndexFoucs.value == index && position > Duration.zero,
|
||||
child: Listener(
|
||||
child: SliderTheme(
|
||||
data: SliderThemeData(
|
||||
|
||||
@ -296,8 +296,8 @@ class _CommentBottomSheetState extends State<CommentBottomSheet> {
|
||||
|
||||
final comment = commentList[index];
|
||||
final hasReplies = comment['childCount'] > 0;
|
||||
final isExpanded = expandedReplies[comment['id']] == true;
|
||||
final replies = replyData[comment['id']] ?? [];
|
||||
final isExpanded = expandedReplies[comment['commentId']] == true;
|
||||
final replies = replyData[comment['commentId']] ?? [];
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
@ -328,7 +328,7 @@ class _CommentBottomSheetState extends State<CommentBottomSheet> {
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
replyingCommentId = comment['id'];
|
||||
replyingCommentId = comment['commentId'];
|
||||
replyingCommentUser = comment['commentUserNickname'] ?? '未知用户';
|
||||
});
|
||||
},
|
||||
@ -356,10 +356,10 @@ class _CommentBottomSheetState extends State<CommentBottomSheet> {
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
expandedReplies[comment['id']] = !isExpanded;
|
||||
if (expandedReplies[comment['id']] == true &&
|
||||
(replyData[comment['id']] == null || replyData[comment['id']]!.isEmpty)) {
|
||||
fetchReplies(comment['id'], false);
|
||||
expandedReplies[comment['commentId']] = !isExpanded;
|
||||
if (expandedReplies[comment['commentId']] == true &&
|
||||
(replyData[comment['commentId']] == null || replyData[comment['commentId']]!.isEmpty)) {
|
||||
fetchReplies(comment['commentId'], false);
|
||||
}
|
||||
});
|
||||
},
|
||||
@ -420,7 +420,7 @@ class _CommentBottomSheetState extends State<CommentBottomSheet> {
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
replyingCommentId = comment['id'];
|
||||
replyingCommentId = comment['commentId'];
|
||||
replyingCommentUser = reply['commentUserNickname'] ?? '未知用户';
|
||||
});
|
||||
},
|
||||
@ -454,8 +454,8 @@ class _CommentBottomSheetState extends State<CommentBottomSheet> {
|
||||
if (replies.length < comment['childCount'])
|
||||
Center(
|
||||
child: TextButton(
|
||||
onPressed: () => fetchReplies(comment['id'], true),
|
||||
child: isLoadingReplies[comment['id']] == true ? CircularProgressIndicator() : Text('加载更多回复'),
|
||||
onPressed: () => fetchReplies(comment['commentId'], true),
|
||||
child: isLoadingReplies[comment['commentId']] == true ? CircularProgressIndicator() : Text('加载更多回复'),
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -558,7 +558,7 @@ class _FriendModuleState extends State<FriendModule> {
|
||||
|
||||
// 页面controller
|
||||
late PageController pageController = PageController(
|
||||
initialPage: videoModuleController.videoPlayIndex.value,
|
||||
initialPage: videoModuleController.videoPlayFriend.value,
|
||||
viewportFraction: 1.0,
|
||||
);
|
||||
|
||||
@ -657,7 +657,7 @@ class _FriendModuleState extends State<FriendModule> {
|
||||
page = 1;
|
||||
isLoadingMore = false;
|
||||
videoList.clear();
|
||||
videoModuleController.updateVideoPlayIndex(0);
|
||||
videoModuleController.updateVideoPlayIndex1(0);
|
||||
sliderValue = 0.0;
|
||||
sliderDraging = false;
|
||||
position = Duration.zero;
|
||||
@ -709,7 +709,7 @@ class _FriendModuleState extends State<FriendModule> {
|
||||
// 初始化播放器
|
||||
player.open(
|
||||
Media(
|
||||
videoList[videoModuleController.videoPlayIndex.value]['url'],
|
||||
videoList[videoModuleController.videoPlayFriend.value]['url'],
|
||||
),
|
||||
play: false);
|
||||
player.setPlaylistMode(PlaylistMode.loop); // 循环播放;
|
||||
@ -772,7 +772,8 @@ class _FriendModuleState extends State<FriendModule> {
|
||||
),
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return CommentBottomSheet(
|
||||
return SafeArea(
|
||||
child: CommentBottomSheet(
|
||||
videoId: videoId,
|
||||
onCommentCountChanged: (newCount) {
|
||||
// 更新对应视频的评论数量
|
||||
@ -781,7 +782,9 @@ class _FriendModuleState extends State<FriendModule> {
|
||||
videoList[index]['commentsCounts'] = newCount;
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -902,9 +905,9 @@ class _FriendModuleState extends State<FriendModule> {
|
||||
|
||||
void handleShareClick(int index) {
|
||||
print("分享项 $index 被点击");
|
||||
final videoId = videoList[videoModuleController.videoPlayIndex.value]['id'];
|
||||
final videoUrl = videoList[videoModuleController.videoPlayIndex.value]['url'];
|
||||
final description = videoList[videoModuleController.videoPlayIndex.value]['title'] ?? '快来看看这个视频';
|
||||
final videoId = videoList[videoModuleController.videoPlayFriend.value]['id'];
|
||||
final videoUrl = videoList[videoModuleController.videoPlayFriend.value]['url'];
|
||||
final description = videoList[videoModuleController.videoPlayFriend.value]['title'] ?? '快来看看这个视频';
|
||||
logger.i('分享链接地址----------------: ${ShareType.video.name}?id=$videoId');
|
||||
if (index == 0) {
|
||||
// 分享好友
|
||||
@ -944,13 +947,9 @@ class _FriendModuleState extends State<FriendModule> {
|
||||
try {
|
||||
// 请求存储权限
|
||||
String? toastId; // 用于存储toast的ID,以便后续关闭
|
||||
var status = await Permissions.requestStoragePermission();
|
||||
var status = await Permissions.requestStoragePermission(title: '文件存储使用说明', content: '用于下载视频');
|
||||
if (!status) {
|
||||
MyToast().tip(
|
||||
title: '需要存储权限才能下载视频',
|
||||
position: 'center',
|
||||
type: 'success',
|
||||
);
|
||||
Permissions.showPermissionDialog('存储');
|
||||
return;
|
||||
}
|
||||
await DownloadManager.downloadFile(
|
||||
@ -983,7 +982,7 @@ class _FriendModuleState extends State<FriendModule> {
|
||||
void handlCoverClick(V2TimConversation conv) async {
|
||||
// 发送VideoMsg,获取当前视频信息
|
||||
final userId = conv.userID;
|
||||
final currentVideo = videoList[videoModuleController.videoPlayIndex.value];
|
||||
final currentVideo = videoList[videoModuleController.videoPlayFriend.value];
|
||||
logger.w(currentVideo);
|
||||
final img = (currentVideo['cover'] != null && currentVideo['cover'].toString().isNotEmpty) ? currentVideo['cover'] : currentVideo['firstFrameImg'];
|
||||
final url = currentVideo['url'];
|
||||
@ -1055,7 +1054,7 @@ class _FriendModuleState extends State<FriendModule> {
|
||||
scrollDirection: Axis.vertical,
|
||||
controller: pageController,
|
||||
onPageChanged: (index) async {
|
||||
videoModuleController.updateVideoPlayIndex(index);
|
||||
videoModuleController.updateVideoPlayIndex1(index);
|
||||
setState(() {
|
||||
sliderValue = 0.0;
|
||||
sliderDraging = false;
|
||||
@ -1087,7 +1086,7 @@ class _FriendModuleState extends State<FriendModule> {
|
||||
child: Stack(
|
||||
children: [
|
||||
Visibility(
|
||||
visible: videoModuleController.videoPlayIndex.value == index && position > Duration.zero,
|
||||
visible: videoModuleController.videoPlayFriend.value == index && position > Duration.zero,
|
||||
child: Video(
|
||||
controller: videoController,
|
||||
fit: isHorizontal ? BoxFit.contain : BoxFit.cover,
|
||||
@ -1095,7 +1094,7 @@ class _FriendModuleState extends State<FriendModule> {
|
||||
),
|
||||
),
|
||||
AnimatedOpacity(
|
||||
opacity: videoModuleController.videoPlayIndex.value == index && position > Duration(milliseconds: 100) ? 0.0 : 1.0,
|
||||
opacity: videoModuleController.videoPlayFriend.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',
|
||||
@ -1149,7 +1148,7 @@ class _FriendModuleState extends State<FriendModule> {
|
||||
onTap: () async {
|
||||
player.pause();
|
||||
// 跳转到 Vloger 页面并等待返回结果
|
||||
final vloggerId = videoList[videoModuleController.videoPlayIndex.value]['memberId'];
|
||||
final vloggerId = videoList[videoModuleController.videoPlayFriend.value]['memberId'];
|
||||
final result = await Get.toNamed('/vloger', arguments: {'memberId': vloggerId});
|
||||
if (result != null) {
|
||||
// 处理返回的参数
|
||||
@ -1169,7 +1168,7 @@ class _FriendModuleState extends State<FriendModule> {
|
||||
),
|
||||
child: ClipOval(
|
||||
child: NetworkOrAssetImage(
|
||||
imageUrl: videoList[index]['commentUserFace'],
|
||||
imageUrl: videoList[index]['avatar'],
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -1283,7 +1282,7 @@ class _FriendModuleState extends State<FriendModule> {
|
||||
onTap: () async {
|
||||
player.pause();
|
||||
// 跳转到举报页面并等待返回结果
|
||||
final result = await Get.toNamed('/report', arguments: videoList[videoModuleController.videoPlayIndex.value]);
|
||||
final result = await Get.toNamed('/report', arguments: videoList[videoModuleController.videoPlayFriend.value]);
|
||||
if (result != null) {
|
||||
player.play();
|
||||
}
|
||||
@ -1299,13 +1298,21 @@ class _FriendModuleState extends State<FriendModule> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (videoModuleController.videoPlayFriend.value < videoList.length)
|
||||
Text(
|
||||
'@${videoList[videoModuleController.videoPlayIndex.value]['nickname'] ?? '未知'}',
|
||||
'@${videoList[videoModuleController.videoPlayFriend.value]['nickname'] ?? '未知'}',
|
||||
style: const TextStyle(color: Colors.white, fontSize: 16.0),
|
||||
)
|
||||
else
|
||||
Text(
|
||||
'@未知',
|
||||
style: const TextStyle(color: Colors.white, fontSize: 16.0),
|
||||
),
|
||||
// 对下面的 LayoutBuilder 也需要同样的检查
|
||||
if (videoModuleController.videoPlayFriend.value < videoList.length)
|
||||
LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final text = videoList[videoModuleController.videoPlayIndex.value]['title'] ?? '未知';
|
||||
final text = videoList[videoModuleController.videoPlayFriend.value]['title'] ?? '未知';
|
||||
final span = TextSpan(
|
||||
text: text,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 14.0),
|
||||
@ -1323,8 +1330,8 @@ class _FriendModuleState extends State<FriendModule> {
|
||||
children: [
|
||||
Text(
|
||||
text,
|
||||
maxLines: videoList[videoModuleController.videoPlayIndex.value]['expanded'] ? null : 3,
|
||||
overflow: videoList[videoModuleController.videoPlayIndex.value]['expanded']
|
||||
maxLines: videoList[videoModuleController.videoPlayFriend.value]['expanded'] ? null : 3,
|
||||
overflow: videoList[videoModuleController.videoPlayFriend.value]['expanded']
|
||||
? TextOverflow.visible
|
||||
: TextOverflow.ellipsis,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 14.0),
|
||||
@ -1335,12 +1342,12 @@ class _FriendModuleState extends State<FriendModule> {
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
videoList[videoModuleController.videoPlayIndex.value]['expanded'] =
|
||||
!videoList[videoModuleController.videoPlayIndex.value]['expanded'];
|
||||
videoList[videoModuleController.videoPlayFriend.value]['expanded'] =
|
||||
!videoList[videoModuleController.videoPlayFriend.value]['expanded'];
|
||||
});
|
||||
},
|
||||
child: Text(
|
||||
videoList[videoModuleController.videoPlayIndex.value]['expanded'] ? '收起' : '展开更多',
|
||||
videoList[videoModuleController.videoPlayFriend.value]['expanded'] ? '收起' : '展开更多',
|
||||
textAlign: TextAlign.right,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
@ -1353,7 +1360,9 @@ class _FriendModuleState extends State<FriendModule> {
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
else
|
||||
SizedBox()
|
||||
],
|
||||
)),
|
||||
Positioned(
|
||||
@ -1361,7 +1370,7 @@ class _FriendModuleState extends State<FriendModule> {
|
||||
left: 6.0,
|
||||
right: 6.0,
|
||||
child: Visibility(
|
||||
visible: videoModuleController.videoPlayIndex.value == index && position > Duration.zero,
|
||||
visible: videoModuleController.videoPlayFriend.value == index && position > Duration.zero,
|
||||
child: Listener(
|
||||
child: SliderTheme(
|
||||
data: SliderThemeData(
|
||||
|
||||
@ -8,7 +8,6 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:loopin/utils/common.dart';
|
||||
import 'package:loopin/IM/controller/chat_controller.dart';
|
||||
import 'package:loopin/IM/controller/im_user_info_controller.dart';
|
||||
import 'package:loopin/IM/im_core.dart';
|
||||
@ -21,7 +20,9 @@ import 'package:loopin/models/conversation_type.dart';
|
||||
import 'package:loopin/models/share_type.dart';
|
||||
import 'package:loopin/models/summary_type.dart';
|
||||
import 'package:loopin/service/http.dart';
|
||||
import 'package:loopin/utils/common.dart';
|
||||
import 'package:loopin/utils/download_video.dart';
|
||||
import 'package:loopin/utils/network_utils.dart';
|
||||
import 'package:loopin/utils/permissions.dart';
|
||||
import 'package:loopin/utils/wxsdk.dart';
|
||||
import 'package:media_kit/media_kit.dart';
|
||||
@ -50,6 +51,10 @@ class RecommendModule extends StatefulWidget {
|
||||
_player?.play();
|
||||
}
|
||||
|
||||
static void playDispose() {
|
||||
_player?.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
State<RecommendModule> createState() => _RecommendModuleState();
|
||||
}
|
||||
@ -298,8 +303,8 @@ class _CommentBottomSheetState extends State<CommentBottomSheet> {
|
||||
|
||||
final comment = commentList[index];
|
||||
final hasReplies = comment['childCount'] > 0;
|
||||
final isExpanded = expandedReplies[comment['id']] == true;
|
||||
final replies = replyData[comment['id']] ?? [];
|
||||
final isExpanded = expandedReplies[comment['commentId']] == true;
|
||||
final replies = replyData[comment['commentId']] ?? [];
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
@ -328,9 +333,12 @@ class _CommentBottomSheetState extends State<CommentBottomSheet> {
|
||||
),
|
||||
SizedBox(width: 20.0),
|
||||
GestureDetector(
|
||||
//回复评论
|
||||
onTap: () {
|
||||
setState(() {
|
||||
replyingCommentId = comment['id'];
|
||||
logger.e(comment);
|
||||
|
||||
replyingCommentId = comment['commentId'];
|
||||
replyingCommentUser = comment['commentUserNickname'] ?? '未知用户';
|
||||
});
|
||||
},
|
||||
@ -357,11 +365,13 @@ class _CommentBottomSheetState extends State<CommentBottomSheet> {
|
||||
if (hasReplies)
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
logger.e(comment);
|
||||
setState(() {
|
||||
expandedReplies[comment['id']] = !isExpanded;
|
||||
if (expandedReplies[comment['id']] == true &&
|
||||
(replyData[comment['id']] == null || replyData[comment['id']]!.isEmpty)) {
|
||||
fetchReplies(comment['id'], false);
|
||||
logger.e(replyData);
|
||||
expandedReplies[comment['commentId']] = !isExpanded;
|
||||
if (expandedReplies[comment['commentId']] == true &&
|
||||
(replyData[comment['commentId']] == null || replyData[comment['commentId']]!.isEmpty)) {
|
||||
fetchReplies(comment['commentId'], false);
|
||||
}
|
||||
});
|
||||
},
|
||||
@ -422,7 +432,7 @@ class _CommentBottomSheetState extends State<CommentBottomSheet> {
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
replyingCommentId = comment['id'];
|
||||
replyingCommentId = comment['commentId'];
|
||||
replyingCommentUser = reply['commentUserNickname'] ?? '未知用户';
|
||||
});
|
||||
},
|
||||
@ -456,8 +466,8 @@ class _CommentBottomSheetState extends State<CommentBottomSheet> {
|
||||
if (replies.length < comment['childCount'])
|
||||
Center(
|
||||
child: TextButton(
|
||||
onPressed: () => fetchReplies(comment['id'], true),
|
||||
child: isLoadingReplies[comment['id']] == true ? CircularProgressIndicator() : Text('加载更多回复'),
|
||||
onPressed: () => fetchReplies(comment['commentId'], true),
|
||||
child: isLoadingReplies[comment['commentId']] == true ? CircularProgressIndicator() : Text('加载更多回复'),
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -570,7 +580,7 @@ class _RecommendModuleState extends State<RecommendModule> {
|
||||
|
||||
final List<StreamSubscription> subscriptions = [];
|
||||
// 进度条slider当前阈值
|
||||
double sliderValue = 0.0;
|
||||
double sliderValue = 0.1;
|
||||
bool sliderDraging = false;
|
||||
late Duration position = Duration.zero; // 当前时长
|
||||
late Duration duration = Duration.zero; // 总时长
|
||||
@ -595,6 +605,8 @@ class _RecommendModuleState extends State<RecommendModule> {
|
||||
{'icon': 'assets/images/share-download.png', 'label': '下载'},
|
||||
];
|
||||
|
||||
final NetworkUtils networkUtils = NetworkUtils();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@ -604,9 +616,31 @@ class _RecommendModuleState extends State<RecommendModule> {
|
||||
videoModuleController.clearNeedRefresh();
|
||||
}
|
||||
});
|
||||
|
||||
RecommendModule.setPlayer(player);
|
||||
// 获取视频数据
|
||||
fetchVideoList();
|
||||
// 监听网络恢复
|
||||
ever(
|
||||
networkUtils.isConnected,
|
||||
(status) {
|
||||
logger.e('当前网络状态:$status');
|
||||
if (status) {
|
||||
fetchVideoList();
|
||||
}
|
||||
},
|
||||
);
|
||||
// final NetworkUtils networkUtils = NetworkUtils();
|
||||
// networkUtils.initialize(
|
||||
// onStatusChange: (status) {
|
||||
// //
|
||||
// logger.e('当前网络状态:$status');
|
||||
// if (!status) {
|
||||
// // 获取视频数据
|
||||
// fetchVideoList();
|
||||
// }
|
||||
// },
|
||||
// );
|
||||
}
|
||||
|
||||
@override
|
||||
@ -709,7 +743,9 @@ class _RecommendModuleState extends State<RecommendModule> {
|
||||
Media(
|
||||
videoList[videoModuleController.videoPlayIndex.value]['url'],
|
||||
),
|
||||
play: false);
|
||||
play: false,
|
||||
);
|
||||
|
||||
player.setPlaylistMode(PlaylistMode.loop); // 循环播放;
|
||||
|
||||
// 第一次加载后播放第一个视频
|
||||
@ -734,6 +770,7 @@ class _RecommendModuleState extends State<RecommendModule> {
|
||||
final resCode = res['code'];
|
||||
if (resCode == 200) {
|
||||
item['doILikeThisVlog'] = !item['doILikeThisVlog'];
|
||||
setState(() {});
|
||||
}
|
||||
} catch (e) {
|
||||
logger.i('点击取消喜欢按钮报错: $e');
|
||||
@ -747,6 +784,7 @@ class _RecommendModuleState extends State<RecommendModule> {
|
||||
final resCode = res['code'];
|
||||
if (resCode == 200) {
|
||||
item['doILikeThisVlog'] = !item['doILikeThisVlog'];
|
||||
setState(() {});
|
||||
}
|
||||
logger.i('点赞返回信息----------->: $res');
|
||||
} catch (e) {
|
||||
@ -770,7 +808,8 @@ class _RecommendModuleState extends State<RecommendModule> {
|
||||
),
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return CommentBottomSheet(
|
||||
return SafeArea(
|
||||
child: CommentBottomSheet(
|
||||
videoId: videoId,
|
||||
onCommentCountChanged: (newCount) {
|
||||
// 更新对应视频的评论数量
|
||||
@ -779,7 +818,9 @@ class _RecommendModuleState extends State<RecommendModule> {
|
||||
videoList[index]['commentsCounts'] = newCount;
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -943,13 +984,9 @@ class _RecommendModuleState extends State<RecommendModule> {
|
||||
try {
|
||||
// 请求存储权限
|
||||
String? toastId; // 用于存储toast的ID,以便后续关闭
|
||||
var status = await Permissions.requestStoragePermission();
|
||||
var status = await Permissions.requestStoragePermission(title: '文件存储使用说明', content: '用于下载视频');
|
||||
if (!status) {
|
||||
MyToast().tip(
|
||||
title: '需要存储权限才能下载视频',
|
||||
position: 'center',
|
||||
type: 'success',
|
||||
);
|
||||
Permissions.showPermissionDialog('存储');
|
||||
return;
|
||||
}
|
||||
await DownloadManager.downloadFile(
|
||||
@ -1132,9 +1169,14 @@ class _RecommendModuleState extends State<RecommendModule> {
|
||||
width: 48.0,
|
||||
child: GestureDetector(
|
||||
onTap: () async {
|
||||
//跳转前先检测是不是自己
|
||||
final vloggerId = videoList[videoModuleController.videoPlayIndex.value]['memberId'];
|
||||
final myID = Get.find<ImUserInfoController>().userID.value;
|
||||
if (myID == vloggerId) {
|
||||
return;
|
||||
}
|
||||
player.pause();
|
||||
// 跳转到 Vloger 页面并等待返回结果
|
||||
final vloggerId = videoList[videoModuleController.videoPlayIndex.value]['memberId'];
|
||||
final result = await Get.toNamed('/vloger', arguments: {'memberId': vloggerId});
|
||||
if (result != null) {
|
||||
// 处理返回的参数
|
||||
@ -1154,13 +1196,14 @@ class _RecommendModuleState extends State<RecommendModule> {
|
||||
),
|
||||
child: ClipOval(
|
||||
child: NetworkOrAssetImage(
|
||||
imageUrl: videoList[index]['commentUserFace'],
|
||||
imageUrl: videoList[index]['avatar'],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
// 关注区域
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 15.0,
|
||||
@ -1184,6 +1227,12 @@ class _RecommendModuleState extends State<RecommendModule> {
|
||||
return;
|
||||
}
|
||||
final vlogerId = videoList[index]['memberId'];
|
||||
//不能关注自己
|
||||
final myID = Get.find<ImUserInfoController>().userID.value;
|
||||
if (myID == vlogerId) {
|
||||
MyToast().tip(title: '不能关注自己');
|
||||
return;
|
||||
}
|
||||
final doIFollowVloger = videoList[index]['doIFollowVloger'];
|
||||
// 未关注点击才去关注
|
||||
if (doIFollowVloger == false) {
|
||||
@ -1300,10 +1349,16 @@ class _RecommendModuleState extends State<RecommendModule> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (videoModuleController.videoPlayIndex.value < videoList.length)
|
||||
Text(
|
||||
'@${videoList[videoModuleController.videoPlayIndex.value]['nickname'] ?? '未知'}',
|
||||
style: const TextStyle(color: Colors.white, fontSize: 16.0),
|
||||
) else
|
||||
Text(
|
||||
'@未知',
|
||||
style: const TextStyle(color: Colors.white, fontSize: 16.0),
|
||||
),
|
||||
if (videoModuleController.videoPlayIndex.value < videoList.length)
|
||||
LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final text = videoList[videoModuleController.videoPlayIndex.value]['title'] ?? '未知';
|
||||
@ -1353,7 +1408,8 @@ class _RecommendModuleState extends State<RecommendModule> {
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
)else
|
||||
SizedBox()
|
||||
],
|
||||
)),
|
||||
Positioned(
|
||||
|
||||
@ -10,7 +10,7 @@ import 'package:loopin/pages/chat/chat_no_friend.dart';
|
||||
import 'package:loopin/pages/chat/notify/interaction.dart';
|
||||
import 'package:loopin/pages/chat/notify/newFoucs.dart';
|
||||
import 'package:loopin/pages/chat/notify/noFriend.dart';
|
||||
import 'package:loopin/pages/chat/notify/system.dart';
|
||||
import 'package:loopin/pages/chat/notify/orderNotify.dart';
|
||||
import 'package:loopin/pages/groupChat/groupList.dart';
|
||||
import 'package:loopin/pages/groupChat/index.dart';
|
||||
import 'package:loopin/pages/my/all_function.dart';
|
||||
@ -27,7 +27,7 @@ import 'package:loopin/pages/my/vloger.dart';
|
||||
import 'package:loopin/pages/order/my_order.dart';
|
||||
import 'package:loopin/pages/order/seller_order.dart';
|
||||
import 'package:loopin/pages/search/index.dart';
|
||||
import 'package:loopin/pages/search/search-result.dart';
|
||||
import 'package:loopin/pages/search/search_result.dart';
|
||||
import 'package:loopin/pages/video/commonVideo.dart';
|
||||
import 'package:loopin/pages/video/report.dart';
|
||||
|
||||
@ -37,8 +37,6 @@ import '../pages/auth/login.dart';
|
||||
// 商品详细
|
||||
import '../pages/goods/detail.dart';
|
||||
import '../pages/order/detail.dart';
|
||||
// 订单
|
||||
import '../pages/order/index.dart';
|
||||
import '../pages/order/seller_detail.dart';
|
||||
// 引入工具类
|
||||
import '../utils/common.dart';
|
||||
@ -49,7 +47,7 @@ final Map<String, Widget> routes = {
|
||||
'/goods': const Goods(),
|
||||
// '/chatNoFriend': const ChatNoFriend(),
|
||||
// '/chatGroup': const ChatGroup(),
|
||||
'/order': const Order(),
|
||||
// '/order': const Order(),
|
||||
'/sellerOrder': const SellerOrder(),
|
||||
'/sellerOrder/detail': const SellerOrderDetail(),
|
||||
'/myOrder': const MyOrder(),
|
||||
@ -73,9 +71,11 @@ final Map<String, Widget> routes = {
|
||||
|
||||
//通知相关
|
||||
'/noFriend': const Nofriend(),
|
||||
'/newFoucs': const Newfoucs(),
|
||||
'/system': const System(),
|
||||
'/newFocus': const Newfoucs(),
|
||||
// '/system': const System(),
|
||||
'/interaction': const Interaction(),
|
||||
'/order': const OrderNotify(),
|
||||
|
||||
//关系链
|
||||
'/fans': const Fans(),
|
||||
'/flow': const Flowing(),
|
||||
|
||||
@ -12,10 +12,10 @@ class HttpConfig {
|
||||
// baseUrl: 'http://111.62.22.190:8080',
|
||||
// baseUrl: 'http://cjh.wuzhongjie.com.cn',
|
||||
// baseUrl: 'http://82.156.121.2:8880',
|
||||
// baseUrl: 'https://www.wuzhongjie.com.cn/prod-api',
|
||||
baseUrl: 'https://www.wuzhongjie.com.cn/prod-api',
|
||||
|
||||
// baseUrl: 'http://192.168.1.65:8880',
|
||||
baseUrl: 'http://192.168.1.22:8080',
|
||||
// baseUrl: 'http://192.168.1.22:8080',
|
||||
|
||||
// connectTimeout: Duration(seconds: 30),
|
||||
// receiveTimeout: Duration(seconds: 30),
|
||||
@ -89,9 +89,11 @@ class HttpConfig {
|
||||
// 其他异常
|
||||
if (data['code'] != 200) {
|
||||
Get.snackbar(
|
||||
'错误码${data['code']}',
|
||||
'${response.requestOptions.uri}\n${response.requestOptions.data}\n${data['msg']}' ?? '请求失败',
|
||||
duration: Duration(minutes: 1),
|
||||
// '错误码${data['code']}',
|
||||
'提示!',
|
||||
// '${response.requestOptions.uri}\n${response.requestOptions.data}\n${data['msg']}' ?? '请求失败',
|
||||
'${data['msg']}' ?? '请求失败',
|
||||
duration: Duration(seconds: 5),
|
||||
backgroundColor: Colors.red.withAlpha(230),
|
||||
colorText: Colors.white,
|
||||
icon: const Icon(Icons.error_outline, color: Colors.white),
|
||||
@ -109,13 +111,13 @@ class HttpConfig {
|
||||
},
|
||||
onError: (e, handler) {
|
||||
// 网络异常处理
|
||||
Get.snackbar(
|
||||
'网络异常',
|
||||
e.message ?? '未知错误',
|
||||
backgroundColor: Colors.red.withAlpha(230),
|
||||
colorText: Colors.white,
|
||||
icon: const Icon(Icons.error_outline, color: Colors.white),
|
||||
);
|
||||
// Get.snackbar(
|
||||
// '网络异常',
|
||||
// '网络连接失败',
|
||||
// backgroundColor: Colors.red.withAlpha(230),
|
||||
// colorText: Colors.white,
|
||||
// icon: const Icon(Icons.error_outline, color: Colors.white),
|
||||
// );
|
||||
handler.next(e);
|
||||
},
|
||||
),
|
||||
|
||||
@ -17,7 +17,9 @@ class UpgradeDialog extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Dialog(
|
||||
return PopScope(
|
||||
canPop: !force, // 强制更新时不能返回,非强制更新时可以返回
|
||||
child: Dialog(
|
||||
insetPadding: EdgeInsets.all(30),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
child: Padding(
|
||||
@ -52,6 +54,7 @@ class UpgradeDialog extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,15 +22,16 @@ class UpgradeService {
|
||||
});
|
||||
if (!state.mounted) return;
|
||||
|
||||
logger.w(res);
|
||||
logger.e('版本:$res');
|
||||
final result = res['data']['records'] as List;
|
||||
if (result.isEmpty) return;
|
||||
final data = result.first;
|
||||
final currentVersion = info.buildNumber;
|
||||
if (currentVersion != data['versionCode']) {
|
||||
if (int.parse(data['versionCode'] ?? 0) > int.parse(currentVersion)) {
|
||||
// 版本号不一致
|
||||
// 0 表示 false非强制,非 0 表示 true强制
|
||||
final bool force = (data['isForceUpdate'] ?? 0) != 0;
|
||||
// final bool force = (data['isForceUpdate'] ?? 0) != 0;
|
||||
final bool force = true;
|
||||
// 弹窗
|
||||
showDialog(
|
||||
context: state.context,
|
||||
|
||||
56
lib/utils/deep_link_listener.dart
Normal file
@ -0,0 +1,56 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:app_links/app_links.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:loopin/IM/im_friend_listeners.dart';
|
||||
|
||||
/// 全局监听schem启动参数 wuzhongjie://open?xxx=xxx
|
||||
class DeepLinkListener extends StatefulWidget {
|
||||
final Widget child;
|
||||
|
||||
const DeepLinkListener({required this.child, super.key});
|
||||
|
||||
@override
|
||||
State<DeepLinkListener> createState() => _DeepLinkListenerState();
|
||||
}
|
||||
|
||||
class _DeepLinkListenerState extends State<DeepLinkListener> {
|
||||
final AppLinks _appLinks = AppLinks();
|
||||
StreamSubscription? _sub;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_handleLinks();
|
||||
}
|
||||
|
||||
/// 统一处理冷启动和热启动
|
||||
void _handleLinks() {
|
||||
_sub = _appLinks.uriLinkStream.listen((Uri? uri) {
|
||||
if (uri != null) {
|
||||
_parseAppParameter(uri);
|
||||
}
|
||||
}, onError: (err) {
|
||||
logger.e('启动参数监听报错: $err');
|
||||
});
|
||||
}
|
||||
|
||||
// 小程序返回热启动参数
|
||||
void _parseAppParameter(Uri uri) {
|
||||
final appParameter = uri.queryParameters['appParameter'];
|
||||
if (appParameter != null) {
|
||||
logger.e('收到启动参数 appParameter: $appParameter');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_sub?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return widget.child;
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,11 @@
|
||||
/// 精选推荐模块
|
||||
library;
|
||||
import 'dart:io';
|
||||
|
||||
import 'dart:async';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
// 下载管理器类
|
||||
class DownloadManager {
|
||||
|
||||
@ -243,4 +243,22 @@ class Utils {
|
||||
static bool hasRole(int roleValue, int targetRole) {
|
||||
return roleValue.toString().contains(targetRole.toString());
|
||||
}
|
||||
|
||||
/// 计算时间差
|
||||
static int calcTime(String createTimeStr, {int validMinutes = 30}) {
|
||||
try {
|
||||
// 替换空格为 T,让 DateTime.parse 能识别
|
||||
String isoTimeStr = createTimeStr.replaceFirst(' ', 'T');
|
||||
DateTime createTime = DateTime.parse(isoTimeStr);
|
||||
DateTime now = DateTime.now();
|
||||
|
||||
int totalSeconds = validMinutes * 60;
|
||||
int elapsedSeconds = now.difference(createTime).inSeconds;
|
||||
|
||||
int remainingSeconds = totalSeconds - elapsedSeconds;
|
||||
return remainingSeconds > 0 ? remainingSeconds : 0;
|
||||
} catch (e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,8 +25,17 @@ class LifecycleHandler with WidgetsBindingObserver {
|
||||
logger.i("App 进入后台 (paused)");
|
||||
break;
|
||||
case AppLifecycleState.detached:
|
||||
// 处理安卓悬浮导航切后台视频会自动开始播放的问题
|
||||
isInForeground = false;
|
||||
logger.i("App 被分离 (detached)");
|
||||
// if (Get.isRegistered<VideoModuleController>()) {
|
||||
// final videoModuleController = Get.find<VideoModuleController>();
|
||||
// logger.w(videoModuleController.videoTabIndex.value);
|
||||
// logger.w(videoModuleController.videoPlayFriend.value);
|
||||
// logger.w(videoModuleController.videoPlayIndex.value);
|
||||
// RecommendModule.playDispose();
|
||||
// }
|
||||
|
||||
break;
|
||||
case AppLifecycleState.hidden:
|
||||
isInForeground = false;
|
||||
|
||||
58
lib/utils/network_utils.dart
Normal file
@ -0,0 +1,58 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
typedef NetworkStatusCallback = void Function(bool isConnected);
|
||||
|
||||
class NetworkUtils {
|
||||
static final NetworkUtils _instance = NetworkUtils._internal();
|
||||
|
||||
factory NetworkUtils() => _instance;
|
||||
|
||||
NetworkUtils._internal();
|
||||
|
||||
final Connectivity _connectivity = Connectivity();
|
||||
StreamSubscription<List<ConnectivityResult>>? _subscription;
|
||||
|
||||
/// RxBool 响应式网络状态
|
||||
final RxBool _isConnected = false.obs;
|
||||
|
||||
/// 初始化网络监听
|
||||
void initialize({NetworkStatusCallback? onStatusChange}) async {
|
||||
// 先检查一次当前网络
|
||||
_isConnected.value = await checkNetworkAvailable();
|
||||
onStatusChange?.call(_isConnected.value);
|
||||
|
||||
// 监听网络变化
|
||||
_subscription = _connectivity.onConnectivityChanged.listen((result) async {
|
||||
bool newStatus = await checkNetworkAvailable();
|
||||
if (newStatus != _isConnected.value) {
|
||||
_isConnected.value = newStatus;
|
||||
onStatusChange?.call(_isConnected.value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// 检查网络是否可用
|
||||
Future<bool> checkNetworkAvailable() async {
|
||||
final connectivityResult = await _connectivity.checkConnectivity();
|
||||
if (connectivityResult.first == ConnectivityResult.none) return false;
|
||||
|
||||
try {
|
||||
final result = await InternetAddress.lookup('wuzhongjie.com.cn');
|
||||
return result.isNotEmpty && result[0].rawAddress.isNotEmpty;
|
||||
} on SocketException catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// 当前网络状态(响应式访问)
|
||||
RxBool get isConnected => _isConnected;
|
||||
|
||||
/// 取消监听
|
||||
void dispose() {
|
||||
_subscription?.cancel();
|
||||
}
|
||||
}
|
||||
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:loopin/IM/im_service.dart';
|
||||
import 'package:loopin/components/network_or_asset_image.dart';
|
||||
import 'package:loopin/models/conversation_type.dart';
|
||||
import 'package:loopin/styles/index.dart';
|
||||
import 'package:loopin/utils/parse_message_summary.dart';
|
||||
import 'package:shirne_dialog/shirne_dialog.dart';
|
||||
@ -20,6 +21,8 @@ class NotificationBanner {
|
||||
name = gpInfo?.groupName ?? "未知群名";
|
||||
avatar = gpInfo?.faceUrl ?? "";
|
||||
} else {
|
||||
logger.e('获取群名称失败');
|
||||
logger.e(msg.toJson());
|
||||
name = '获取群名称失败';
|
||||
}
|
||||
} else {
|
||||
@ -71,18 +74,25 @@ class NotificationBanner {
|
||||
),
|
||||
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}';
|
||||
// 这里得单独处理一下消息通知
|
||||
bool isGroup = msg.groupID != null && msg.groupID!.isNotEmpty;
|
||||
String id = isGroup ? (msg.groupID ?? '') : (msg.userID ?? '');
|
||||
String conversationID = isGroup ? 'group_$id' : 'c2c_$id';
|
||||
String? router;
|
||||
// 如果是群聊,直接走,不是群聊鉴别一下
|
||||
final cRes = await ImService.instance.getConversation(conversationID: conversationID);
|
||||
if (!isGroup) {
|
||||
// 不是群聊,去匹配是不是系统通知
|
||||
router = conversationTypeFromString(msg.userID); //这里就是解析用于发送消息的管理员的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) {
|
||||
if (router != null) {
|
||||
// 取对应的系统通知界面
|
||||
Get.toNamed('/$router', arguments: cRes.data);
|
||||
} else if (isGroup) {
|
||||
Get.toNamed('/chatGroup', arguments: cRes.data);
|
||||
} else {
|
||||
Get.toNamed('/chat', arguments: cRes.data);
|
||||
}
|
||||
} else {
|
||||
MyDialog.toast(
|
||||
|
||||
@ -186,8 +186,7 @@ String _parseCustomMessage(V2TimMessage? msg) {
|
||||
// final sum = jsonDecode(msg.cloudCustomData!);
|
||||
final elment = msg.customElem; // 所有服务端发送的通知消息都走【自定义消息类型】
|
||||
// logger.w('解析自定义消息:$sum,自定义属性:${msg.cloudCustomData}');
|
||||
logger.w(sum);
|
||||
logger.w('解析element:${elment?.desc ?? 'summary_error'}');
|
||||
logger.w('解析element:aciton=$sum,${elment?.desc ?? 'summary_error'}');
|
||||
try {
|
||||
switch (sum) {
|
||||
case SummaryType.hongbao:
|
||||
@ -234,6 +233,8 @@ String _parseCustomMessage(V2TimMessage? msg) {
|
||||
|
||||
///系统推广类的先不管了
|
||||
return elment!.desc!;
|
||||
|
||||
// interaction required key={type} value={interactionComment} 对应类型的枚举值
|
||||
case NotifyMessageTypeConstants.interactionComment:
|
||||
// 评论视频
|
||||
|
||||
@ -275,22 +276,28 @@ String _parseCustomMessage(V2TimMessage? msg) {
|
||||
/// 回复的评论内容[comment]
|
||||
return elment!.desc!;
|
||||
case NotifyMessageTypeConstants.orderRecharge:
|
||||
// 充值成功通知
|
||||
// 充值成功通知,
|
||||
/// 订单编号[orderID]
|
||||
/// 充值金额[amount]
|
||||
/// 充值后帐户余额[totalAmount]
|
||||
return elment!.desc!;
|
||||
case NotifyMessageTypeConstants.orderPay:
|
||||
// 订单交易结果通知(商品购买成功后)
|
||||
// 订单交易结果通知(商品购买成功后--下单成功)
|
||||
/// 订单编号[orderID]
|
||||
/// 订单金额[amount]
|
||||
/// 商品名称[name]
|
||||
/// 商品描述[describe]
|
||||
/// 商品价格[price]
|
||||
/// 商品主图[pic]
|
||||
return elment!.desc!;
|
||||
case NotifyMessageTypeConstants.orderWithDraw:
|
||||
// 提现结果通知(成功/失败-----提现成功/提现失败)
|
||||
/// 提现状态[status] 0=失败 1=成功
|
||||
|
||||
return elment!.desc!;
|
||||
case NotifyMessageTypeConstants.orderRefund:
|
||||
// 订单退款通知
|
||||
/// 退款状态[status] 0=失败 1=成功
|
||||
/// 订单编号[orderID]
|
||||
/// 订单金额[amount]
|
||||
/// 商品名称[name]
|
||||
|
||||
@ -1,15 +1,41 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:loopin/IM/im_service.dart';
|
||||
import 'package:loopin/components/my_confirm.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:photo_manager/photo_manager.dart';
|
||||
|
||||
class Permissions {
|
||||
// 请求视频访问权限
|
||||
static Future<bool> requestVideoPermission() async {
|
||||
/// 判断是否为华为设备
|
||||
static Future<bool> isHuaweiDevice() async {
|
||||
if (Platform.isAndroid) {
|
||||
final deviceInfoPlugin = DeviceInfoPlugin();
|
||||
final androidInfo = await deviceInfoPlugin.androidInfo;
|
||||
// 判断品牌是否为华为
|
||||
return androidInfo.brand.toLowerCase().contains('huawei') || androidInfo.manufacturer.toLowerCase().contains('huawei');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 请求视频访问权限
|
||||
static Future<bool> requestVideoPermission({String? title, String? content}) async {
|
||||
if (Platform.isAndroid) {
|
||||
SnackbarController? ctl;
|
||||
|
||||
final isHw = await isHuaweiDevice();
|
||||
if (isHw) {
|
||||
ctl = Get.snackbar(
|
||||
title ?? '相册权限使用说明:',
|
||||
content ?? '我们需要读取您的手机相册,以便您从相册中选择文件',
|
||||
duration: Duration(days: 1),
|
||||
backgroundColor: Colors.red.withAlpha(230),
|
||||
colorText: Colors.white,
|
||||
icon: const Icon(Icons.error_outline, color: Colors.white),
|
||||
);
|
||||
}
|
||||
final deviceInfoPlugin = DeviceInfoPlugin();
|
||||
final androidInfo = await deviceInfoPlugin.androidInfo;
|
||||
final sdkInt = androidInfo.version.sdkInt;
|
||||
@ -17,23 +43,42 @@ class Permissions {
|
||||
if (sdkInt >= 33) {
|
||||
// Android 13 及以上
|
||||
final status = await Permission.videos.request();
|
||||
if (ctl != null) {
|
||||
ctl.close();
|
||||
}
|
||||
return status.isGranted;
|
||||
} else {
|
||||
// Android 12 及以下
|
||||
final status = await Permission.storage.request();
|
||||
if (ctl != null) {
|
||||
ctl.close();
|
||||
}
|
||||
return status.isGranted;
|
||||
}
|
||||
} else if (Platform.isIOS) {
|
||||
final status = await Permission.photos.request();
|
||||
return status.isGranted || status.isLimited;
|
||||
// return status.isGranted || status.isLimited;
|
||||
return handleStatus(status, isAndroid: false);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 请求相册权限
|
||||
static Future<bool> requestPhotoPermission() async {
|
||||
static Future<bool> requestPhotoPermission({String? title, String? content}) async {
|
||||
if (Platform.isAndroid) {
|
||||
final isHw = await isHuaweiDevice();
|
||||
SnackbarController? ctl;
|
||||
if (isHw) {
|
||||
ctl = Get.snackbar(
|
||||
title ?? '相册权限使用说明:',
|
||||
content ?? '我们需要读取您的手机相册,以便您从相册中选择文件',
|
||||
duration: Duration(days: 1),
|
||||
backgroundColor: Colors.red.withAlpha(230),
|
||||
colorText: Colors.white,
|
||||
icon: const Icon(Icons.error_outline, color: Colors.white),
|
||||
);
|
||||
}
|
||||
final deviceInfoPlugin = DeviceInfoPlugin();
|
||||
final androidInfo = await deviceInfoPlugin.androidInfo;
|
||||
final sdkInt = androidInfo.version.sdkInt;
|
||||
@ -41,11 +86,17 @@ class Permissions {
|
||||
if (sdkInt >= 33) {
|
||||
// Android 13 及以上
|
||||
final status = await Permission.photos.request();
|
||||
if (ctl != null) {
|
||||
ctl.close();
|
||||
}
|
||||
// return status.isGranted;
|
||||
return handleStatus(status, isAndroid: true);
|
||||
} else {
|
||||
// Android 12 及以下
|
||||
final status = await Permission.storage.request();
|
||||
if (ctl != null) {
|
||||
ctl.close();
|
||||
}
|
||||
// return status.isGranted;
|
||||
return handleStatus(status, isAndroid: true);
|
||||
}
|
||||
@ -59,44 +110,86 @@ class Permissions {
|
||||
}
|
||||
}
|
||||
|
||||
// 请求相机权限
|
||||
static Future<bool> requestCameraPermission() async {
|
||||
return await _checkAndRequest(Permission.camera);
|
||||
// 请求相机
|
||||
static Future<bool> requestCameraPermission({String? title, String? content}) async {
|
||||
return await _checkAndRequest(Permission.camera, title: title, content: content);
|
||||
}
|
||||
|
||||
// 请求麦克风权限
|
||||
static Future<bool> requestMicrophonePermission() async {
|
||||
return await _checkAndRequest(Permission.microphone);
|
||||
static Future<bool> requestMicrophonePermission({String? title, String? content}) async {
|
||||
return await _checkAndRequest(Permission.microphone, title: title, content: content);
|
||||
}
|
||||
|
||||
// 请求本地存储权限
|
||||
static Future<bool> requestStoragePermission() async {
|
||||
return await _checkAndRequest(Permission.storage);
|
||||
static Future<bool> requestStoragePermission({String? title, String? content}) async {
|
||||
return await _checkAndRequest(Permission.storage, title: title, content: content);
|
||||
}
|
||||
|
||||
// 封装公共权限处理逻辑
|
||||
static Future<bool> _checkAndRequest(Permission permission) async {
|
||||
final status = await permission.status;
|
||||
|
||||
if (status.isGranted) return true;
|
||||
|
||||
static Future<bool> _checkAndRequest(Permission permission, {String? title, String? content}) async {
|
||||
if (Platform.isAndroid) {
|
||||
final isHw = await isHuaweiDevice();
|
||||
SnackbarController? ctl;
|
||||
if (isHw) {
|
||||
ctl = Get.snackbar(
|
||||
title ?? '麦克风权限使用说明:',
|
||||
content ?? '用于发送语音消息',
|
||||
duration: Duration(days: 1),
|
||||
backgroundColor: Colors.red.withAlpha(230),
|
||||
colorText: Colors.white,
|
||||
icon: const Icon(Icons.error_outline, color: Colors.white),
|
||||
);
|
||||
}
|
||||
final result = await permission.request();
|
||||
|
||||
if (result.isGranted) return true;
|
||||
|
||||
if (result.isPermanentlyDenied) {
|
||||
// 永久拒绝 只能去设置
|
||||
return false;
|
||||
if (ctl != null) {
|
||||
ctl.close();
|
||||
}
|
||||
return handleStatus(result, isAndroid: true);
|
||||
} else {
|
||||
// 临时拒绝 提示
|
||||
Get.snackbar('权限请求失败', '无法访问,请授权对应权限后重试');
|
||||
final result = await permission.request();
|
||||
|
||||
return handleStatus(result, isAndroid: false);
|
||||
}
|
||||
|
||||
return false;
|
||||
// final status = await permission.status;
|
||||
|
||||
// if (status.isGranted) {
|
||||
// // 有权限直接关闭
|
||||
// if (ctl != null) {
|
||||
// ctl.close();
|
||||
// }
|
||||
// return true;
|
||||
// }
|
||||
|
||||
// final result = await permission.request();
|
||||
|
||||
// if (result.isGranted) {
|
||||
// if (ctl != null) {
|
||||
// ctl.close();
|
||||
// }
|
||||
// return true;
|
||||
// }
|
||||
|
||||
// if (result.isPermanentlyDenied) {
|
||||
// // 永久拒绝 只能去设置
|
||||
// if (ctl != null) {
|
||||
// ctl.close();
|
||||
// }
|
||||
// return false;
|
||||
// } else {
|
||||
// if (ctl != null) {
|
||||
// ctl.close();
|
||||
// }
|
||||
// // 临时拒绝 提示
|
||||
// Get.snackbar('权限请求失败', '无法访问,请授权对应权限后重试');
|
||||
// }
|
||||
|
||||
// return false;
|
||||
}
|
||||
|
||||
/// 处理权限状态
|
||||
static bool handleStatus(PermissionStatus status, {required bool isAndroid}) {
|
||||
static Future<bool> handleStatus(PermissionStatus status, {required bool isAndroid, SnackbarController? ctl}) async {
|
||||
logger.w("当前权限状态 = $status");
|
||||
logger.e(status.isPermanentlyDenied);
|
||||
if (status.isGranted) {
|
||||
@ -110,10 +203,22 @@ class Permissions {
|
||||
return true; // Limited 状态下也能用,但受限制
|
||||
}
|
||||
if (status.isPermanentlyDenied) {
|
||||
// debug 模式或已授权的真机,有时误判为 permanentlyDenied
|
||||
// 直接尝试访问,或者返回 true
|
||||
logger.w("可能已授权,直接允许访问");
|
||||
return true;
|
||||
// ios 二次检测
|
||||
// permission_handler在ios下只能检测出limited和granted;其余被拒或手动修改均为permanentlyDenied的问题
|
||||
final result = await PhotoManager.requestPermissionExtend();
|
||||
logger.e(result);
|
||||
switch (result) {
|
||||
case PermissionState.authorized:
|
||||
return true; // 完全授权
|
||||
case PermissionState.limited:
|
||||
return true; // 部分授权,也可用
|
||||
case PermissionState.denied:
|
||||
return false;
|
||||
case PermissionState.restricted:
|
||||
return false;
|
||||
default:
|
||||
return false; // 用户拒绝,跳转设置页
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
// 扫码 类型
|
||||
class QrTypeCode {
|
||||
static const String hxm = 'hxm-';
|
||||
static const String hxm = 'HXM-';
|
||||
static const String hym = 'hym-';
|
||||
static const String tgm = 'tgm-';
|
||||
}
|
||||
|
||||
enum ScanCodeType {
|
||||
verification('hxm'), // 核销码
|
||||
verification('HXM'), // 核销码
|
||||
friend('hym'), // 好友码
|
||||
promotion('tgm'); // 推广码
|
||||
|
||||
|
||||
19
lib/utils/wechat_business_view.dart
Normal file
@ -0,0 +1,19 @@
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:loopin/IM/im_friend_listeners.dart';
|
||||
|
||||
class WechatBusinessView {
|
||||
static const MethodChannel _channel = MethodChannel('wechat_business_view'); // mainactivity.kt,appdelegate.swift
|
||||
|
||||
/// 调起微信确认收款码
|
||||
static Future<bool> openBusinessView({required String packageInfo}) async {
|
||||
try {
|
||||
final result = await _channel.invokeMethod('openBusinessView', {
|
||||
'package': packageInfo,
|
||||
});
|
||||
return result == true;
|
||||
} on PlatformException catch (e) {
|
||||
logger.w('调用失败: ${e.message}');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -43,6 +43,7 @@ class Wxsdk {
|
||||
"clientId": "428a8310cd442757ae699df5d894f051",
|
||||
"grantType": "social"
|
||||
});
|
||||
logger.e(serverRes);
|
||||
final info = Get.find<ImUserInfoController>();
|
||||
info.customInfo['openId'] = serverRes['data']['openId'];
|
||||
info.updateOpenId();
|
||||
@ -63,6 +64,8 @@ class Wxsdk {
|
||||
final ctl = Get.find<BalanceController>();
|
||||
// 刷新记录
|
||||
ctl.getData(reset: true);
|
||||
// 刷新钱包
|
||||
ctl.getAccount();
|
||||
} else {
|
||||
if (res.errCode == -2) {
|
||||
logger.w("用户取消支付");
|
||||
@ -156,7 +159,7 @@ class Wxsdk {
|
||||
var miniProgram = MiniProgram(
|
||||
username: "gh_2ffaecc5508e", // 小程序原始ID
|
||||
path: "/pages/index/index?id=$orderId", // 打开时带的路径参数
|
||||
miniProgramType: WXMiniProgramType.preview,
|
||||
miniProgramType: WXMiniProgramType.preview, // 预览模式,正式环境更换release
|
||||
);
|
||||
Fluwx().open(target: miniProgram);
|
||||
}
|
||||
|
||||
100
pubspec.lock
@ -17,6 +17,38 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.3"
|
||||
app_links:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: app_links
|
||||
sha256: "5f88447519add627fe1cbcab4fd1da3d4fed15b9baf29f28b22535c95ecee3e8"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "6.4.1"
|
||||
app_links_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: app_links_linux
|
||||
sha256: f5f7173a78609f3dfd4c2ff2c95bd559ab43c80a87dc6a095921d96c05688c81
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.0.3"
|
||||
app_links_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: app_links_platform_interface
|
||||
sha256: "05f5379577c513b534a29ddea68176a4d4802c46180ee8e2e966257158772a3f"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
app_links_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: app_links_web
|
||||
sha256: af060ed76183f9e2b87510a9480e56a5352b6c249778d07bd2c95fc35632a555
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
archive:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -201,6 +233,22 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.4.1"
|
||||
connectivity_plus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: connectivity_plus
|
||||
sha256: "33bae12a398f841c6cda09d1064212957265869104c478e5ad51e2fb26c3973c"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "7.0.0"
|
||||
connectivity_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: connectivity_plus_platform_interface
|
||||
sha256: "42657c1715d48b167930d5f34d00222ac100475f73d10162ddf43e714932f204"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
cross_file:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -529,10 +577,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: fluwx
|
||||
sha256: "9db31d54043363c9c8283b5f0bc4df982edb45ba19d800df9d7de96a205371ae"
|
||||
sha256: "9bac596b34f37b08fd4cae0d0e1e205d0dc395dbec4200c588eed9b4b446f69f"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "5.7.0"
|
||||
version: "5.7.2"
|
||||
form_builder_validators:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -605,6 +653,14 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
gtk:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: gtk
|
||||
sha256: e8ce9ca4b1df106e4d72dad201d345ea1a036cc12c360f1a7d5a758f78ffa42c
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
html:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -973,6 +1029,14 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
nm:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: nm
|
||||
sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.5.0"
|
||||
octo_image:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1682,6 +1746,38 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
webview_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: webview_flutter
|
||||
sha256: c3e4fe614b1c814950ad07186007eff2f2e5dd2935eba7b9a9a1af8e5885f1ba
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "4.13.0"
|
||||
webview_flutter_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: webview_flutter_android
|
||||
sha256: "9a25f6b4313978ba1c2cda03a242eea17848174912cfb4d2d8ee84a556f248e3"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "4.10.1"
|
||||
webview_flutter_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: webview_flutter_platform_interface
|
||||
sha256: "63d26ee3aca7256a83ccb576a50272edd7cfc80573a4305caa98985feb493ee0"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.14.0"
|
||||
webview_flutter_wkwebview:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: webview_flutter_wkwebview
|
||||
sha256: fb46db8216131a3e55bcf44040ca808423539bc6732e7ed34fb6d8044e3d512f
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.23.0"
|
||||
wechat_assets_picker:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
||||
39
pubspec.yaml
@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# In Windows, build-name is used as the major, minor, and patch parts
|
||||
# of the product and file versions while build-number is used as the build suffix.
|
||||
version: 4.1.0+407
|
||||
version: 4.1.4+416
|
||||
|
||||
environment:
|
||||
sdk: ^3.6.0
|
||||
@ -59,6 +59,10 @@ dependencies:
|
||||
media_kit: ^1.1.11
|
||||
media_kit_video: ^1.2.5
|
||||
media_kit_libs_video: ^1.0.5
|
||||
# media_kit: ^1.2.0
|
||||
# media_kit_video: ^1.3.0
|
||||
# media_kit_libs_video: ^1.0.6
|
||||
|
||||
|
||||
photo_view: ^0.15.0
|
||||
shirne_dialog: ^4.8.3
|
||||
@ -74,7 +78,7 @@ dependencies:
|
||||
|
||||
wechat_assets_picker: ^9.5.1
|
||||
device_info_plus: ^11.5.0
|
||||
photo_manager: ^3.7.1 #翻译媒体库
|
||||
photo_manager: ^3.7.1 #翻译媒体,权限库
|
||||
flutter_form_builder: ^10.0.1
|
||||
form_builder_validators: ^11.1.2
|
||||
geolocator: ^14.0.1
|
||||
@ -85,7 +89,8 @@ dependencies:
|
||||
city_pickers: ^1.3.0
|
||||
bottom_picker: ^3.2.1
|
||||
|
||||
fluwx: ^5.7.0 #微信sdk
|
||||
# fluwx: ^5.7.0 #微信sdk
|
||||
fluwx: ^5.7.2 #微信sdk
|
||||
flutter_image_compress: ^2.4.0 #处理图片
|
||||
video_thumbnail: ^0.5.6 #视频首帧截取
|
||||
record: ^6.0.0 #音频
|
||||
@ -100,6 +105,9 @@ dependencies:
|
||||
image_cropper: ^9.1.0
|
||||
pretty_qr_code: ^3.5.0
|
||||
lottie: ^3.3.1
|
||||
webview_flutter: ^4.13.0
|
||||
app_links: ^6.4.1
|
||||
connectivity_plus: ^7.0.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_launcher_icons: ^0.13.1 # 使用最新版本
|
||||
@ -188,21 +196,28 @@ flutter_launcher_icons:
|
||||
|
||||
|
||||
#dart run flutter_native_splash:create
|
||||
#flutter pub run flutter_native_splash:create
|
||||
#dart run flutter_native_splash:remove
|
||||
flutter_native_splash:
|
||||
ios: true
|
||||
android: true
|
||||
color: "#000000"
|
||||
color: "#ffffff"
|
||||
image: assets/images/logo/start.png
|
||||
fullscreen: true
|
||||
# background_image: assets/images/logo/start1.png
|
||||
# color_dark: "#000000"
|
||||
# image_dark: assets/images/logo/start1.png
|
||||
color_dark: "#ffffff"
|
||||
image_dark: assets/images/logo/start.png
|
||||
# iOS 图片显示模式 - 填满屏幕(可能裁剪)
|
||||
ios_content_mode: scaleAspectFill
|
||||
# Android 11 及以下图片显示模式 - 填满屏幕
|
||||
android_gravity: fill
|
||||
fullscreen: true #隐藏通知栏
|
||||
|
||||
android_12:
|
||||
image: assets/images/logo/logo.png
|
||||
color: "#000000"
|
||||
color_dark: "#000000"
|
||||
|
||||
image: assets/images/logo/androidlogo.png
|
||||
icon_background_color: "#000000"
|
||||
# image_dark: assets/images/logo/start.png
|
||||
# icon_background_color_dark: "#000000"
|
||||
|
||||
image_dark: assets/images/logo/androidlogo.png
|
||||
icon_background_color_dark: "#000000"
|
||||
|
||||
web: false
|
||||