diff --git a/assets/images/logo/androidlogo.png b/assets/images/logo/androidlogo.png new file mode 100644 index 0000000..6e705cf Binary files /dev/null and b/assets/images/logo/androidlogo.png differ diff --git a/assets/images/logo/logo.png b/assets/images/logo/logo.png index 9e277e3..c8118ac 100644 Binary files a/assets/images/logo/logo.png and b/assets/images/logo/logo.png differ diff --git a/assets/images/logo/start.png b/assets/images/logo/start.png index 8bdd175..c232bde 100644 Binary files a/assets/images/logo/start.png and b/assets/images/logo/start.png differ diff --git a/assets/images/wait_loading.png b/assets/images/wait_loading.png new file mode 100644 index 0000000..ea51fa8 Binary files /dev/null and b/assets/images/wait_loading.png differ diff --git a/ios/Podfile b/ios/Podfile index 2c53880..7e1c0ce 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -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 diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 1a22b91..2b5b4e8 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,4 +1,6 @@ PODS: + - app_links (6.4.1): + - Flutter - audioplayers_darwin (0.0.1): - Flutter - FlutterMacOS @@ -19,7 +21,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 +35,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 +69,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 +89,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,9 +102,13 @@ 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`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - Flutter (from `Flutter`) @@ -131,6 +137,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,6 +152,8 @@ SPEC REPOS: - WechatOpenSDK-XCFramework EXTERNAL SOURCES: + app_links: + :path: ".symlinks/plugins/app_links/ios" audioplayers_darwin: :path: ".symlinks/plugins/audioplayers_darwin/darwin" device_info_plus: @@ -201,22 +210,25 @@ 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 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 +238,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 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index c95476e..ce1b628 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -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 = ""; }; 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 = ""; }; 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 = ""; }; - C8092B202E34A78000D25A0B /* WechatOpenSDK-XCFramework.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = "WechatOpenSDK-XCFramework.xcframework"; path = "Pods/WechatOpenSDK-XCFramework/WechatOpenSDK-XCFramework.xcframework"; sourceTree = ""; }; C891EF1D2E43F9730021EB39 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; + C8F379722E85F00600E7B665 /* WechatOpenSDK.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = WechatOpenSDK.xcframework; path = "Pods/WechatOpenSDK-XCFramework/WechatOpenSDK.xcframework"; sourceTree = ""; }; 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 = ""; }; 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 = ""; }; /* 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; diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 5cc1db3..efc547d 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -5,16 +5,49 @@ 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 { diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png index 2a6511c..e815fd6 100644 Binary files a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png index e68a29a..e815fd6 100644 Binary files a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png index dc7ca7f..e815fd6 100644 Binary files a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/ios/Runner/Base.lproj/LaunchScreen.storyboard b/ios/Runner/Base.lproj/LaunchScreen.storyboard index 6e24a20..55c0cae 100644 --- a/ios/Runner/Base.lproj/LaunchScreen.storyboard +++ b/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -38,7 +38,7 @@ - + diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 2b101d6..dfe0782 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -1,97 +1,112 @@ - - CADisableMinimumFrameDurationOnPhone - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleDisplayName - 无终街 - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - loopin - CFBundlePackageType - APPL - CFBundleShortVersionString - $(FLUTTER_BUILD_NAME) - CFBundleSignature - ???? - CFBundleURLTypes - - - CFBundleTypeRole - Editor - CFBundleURLName - weixin - CFBundleURLSchemes - - wxebcdaea31881caab - - - - CFBundleVersion - $(FLUTTER_BUILD_NUMBER) - LSApplicationQueriesSchemes - - weixin - wechat - weixinULAPI - - LSRequiresIPhoneOS - - NSAppTransportSecurity - NSAllowsArbitraryLoads + CADisableMinimumFrameDurationOnPhone + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + 无终街 + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + loopin + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLName + weixin + CFBundleURLSchemes + + wxebcdaea31881caab + + + + CFBundleTypeRole + Editor + CFBundleURLName + startApp + CFBundleURLSchemes + + wuzhongjie + + + + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSApplicationQueriesSchemes + + weixin + wechat + weixinULAPI + + LSRequiresIPhoneOS + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + NSCameraUsageDescription + App需要使用您的相机进行拍摄 + NSMicrophoneUsageDescription + App需要访问麦克风用于发送语音消息 + NSPhotoLibraryAddUsageDescription + App需要权限以保存图片或视频到您的相册 + NSPhotoLibraryLimitedUsageDescription + App需要访问部分照片用于选择图片或视频 + NSPhotoLibraryUsageDescription + App需要访问您的相册用于选择图片或视频 + UIApplicationSupportsIndirectInputEvents + + UIBackgroundModes + + remote-notification + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIStatusBarHidden + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + com.apple.developer.associated-domains + + applinks:wuzhongjie.com.cn + + LSApplicationQueriesSchemes + + weixin + weixinULAPI + weixinURLParamsAPI + + - NSCameraUsageDescription - App需要使用您的相机进行拍摄 - NSMicrophoneUsageDescription - App需要访问麦克风用于发送语音消息 - NSPhotoLibraryAddUsageDescription - App需要权限以保存图片或视频到您的相册 - NSPhotoLibraryLimitedUsageDescription - App需要访问部分照片用于选择图片或视频 - NSPhotoLibraryUsageDescription - App需要访问您的相册用于选择图片或视频 - UIApplicationSupportsIndirectInputEvents - - UILaunchStoryboardName - LaunchScreen.storyboard - UIMainStoryboardFile - Main - UIStatusBarHidden - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UIViewControllerBasedStatusBarAppearance - - - UIBackgroundModes - - remote-notification - - - com.apple.developer.associated-domains - - applinks:wuzhongjie.com.cn - - diff --git a/lib/IM/controller/chat_controller.dart b/lib/IM/controller/chat_controller.dart index 11f0fe2..1e12f1f 100644 --- a/lib/IM/controller/chat_controller.dart +++ b/lib/IM/controller/chat_controller.dart @@ -147,9 +147,9 @@ class ChatController extends GetxController { } final List 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(); diff --git a/lib/IM/controller/im_user_info_controller.dart b/lib/IM/controller/im_user_info_controller.dart index 5d6a3b0..eab5739 100644 --- a/lib/IM/controller/im_user_info_controller.dart +++ b/lib/IM/controller/im_user_info_controller.dart @@ -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 = {}; + // 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; diff --git a/lib/IM/im_core.dart b/lib/IM/im_core.dart index 37adfe8..2d052a9 100644 --- a/lib/IM/im_core.dart +++ b/lib/IM/im_core.dart @@ -23,7 +23,12 @@ class ImCore { // 初始化视频 final videoController = Get.find(); videoController.init(); - Get.toNamed('/login'); + Get.offAllNamed( + '/login', + predicate: (route) { + return route.settings.name == '/'; + }, + ); } } diff --git a/lib/IM/im_group_listeners.dart b/lib/IM/im_group_listeners.dart index c28f59b..1ef37f1 100644 --- a/lib/IM/im_group_listeners.dart +++ b/lib/IM/im_group_listeners.dart @@ -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 memberList) { + void onMemberInvited(String groupID, List memberList) async { if (Get.isRegistered()) { final ctl = Get.find(); ctl.init(); } + //在群标记,目的为了清除被踢后的标记 + await ImService.instance.setConversationCustomData(customData: 'int', conversationIDList: ['group_$groupID']); } // 成员被踢出群 - void onMemberKicked(String groupID, List memberList) { + void onMemberKicked(String groupID, List memberList) async { if (Get.isRegistered()) { final ctl = Get.find(); ctl.init(); } + // 这里处理会话 + final infoCtl = Get.find(); + 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(); + 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); + } + } + } + } } // 成员信息变更 diff --git a/lib/IM/im_service.dart b/lib/IM/im_service.dart index 577f17a..83c4684 100644 --- a/lib/IM/im_service.dart +++ b/lib/IM/im_service.dart @@ -84,13 +84,6 @@ class ImService { if (result.success) { logger.i("IM 登录成功:$userID"); - // 初始化会话数据 - final ctl = Get.find(); - await ctl.getConversationList(); - - /// 初始化微信 SDK - await Wxsdk.init(); - // 注册用户信息(基本信息+自定义信息) if (!Get.isRegistered()) { final imInfo = Get.put(ImUserInfoController(), permanent: true); @@ -99,6 +92,13 @@ class ImService { await Get.find().refreshUserInfo(); } + // 初始化会话数据 + final ctl = Get.find(); + await ctl.getConversationList(); + + /// 初始化微信 SDK + await Wxsdk.init(); + // 登录成功后注册高级消息监听器 final messageService = ImMessageListenerService(); Get.put(messageService, permanent: true); @@ -275,7 +275,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 +297,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 +504,14 @@ class ImService { ); } + /// 标记单聊消息为已读 + Future markC2CMessageAsReadResult({ + required String userID, + }) async { + final res = await TIMMessageManager.instance.markC2CMessageAsRead(userID: userID); + return ImResult.wrapNoData(res); + } + /// /// 清理指定单聊会话的未读数 /// @@ -646,6 +654,12 @@ class ImService { return ImResult.wrap(res); } + /// 拉黑列表 + Future>> blackList() async { + final res = await TIMFriendshipManager.instance.getBlackList(); + return ImResult.wrap(res); + } + ///获取好友列表 Future>> getFriendList() async { final res = await TIMFriendshipManager.instance.getFriendList(); diff --git a/lib/IM/push_service.dart b/lib/IM/push_service.dart index 07266c4..c877549 100644 --- a/lib/IM/push_service.dart +++ b/lib/IM/push_service.dart @@ -48,20 +48,25 @@ class PushService { } // 注册推送(初始化) - // if (Platform.isAndroid) { - // await compute(_registerPushInIsolate, { - // 'sdkAppId': sdkAppId, - // 'appKey': appKey, - // 'apnsCertificateID': apnsCertificateID, - // }); - // } else { - await TencentCloudChatPush().registerPush( - onNotificationClicked: _onNotificationClicked, - sdkAppId: sdkAppId, - appKey: appKey, - apnsCertificateID: apnsCertificateID, - ); - // } + if (Platform.isAndroid) { + // await compute(_registerPushInIsolate, { + // 'sdkAppId': sdkAppId, + // 'appKey': appKey, + // 'apnsCertificateID': apnsCertificateID, + // }); + 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,11 @@ class PushService { if (Platform.isAndroid) { await TencentImSDKPlugin.v2TIMManager.login(userID: Storage.read('userId'), userSig: Storage.read('userSig')); } + final token = await TencentCloudChatPush().getAndroidPushToken(); - logger.i('推送服务已注册,手机:$devices,证书ID:$apnsCertificateID'); + logger.i( + '推送服务已注册,手机:$devices,证书ID:$apnsCertificateID,token=${token.code},tk=${token.data}', + ); // 添加在线时监听器 _addPushListener(); @@ -123,19 +131,21 @@ 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 isGroup = groupID != null && groupID.isNotEmpty; + final data = jsonDecode(ext); logger.i(data); - final type = data['type']; - final router = conversationTypeFromString(type); + // final type = data['type']; + final router = conversationTypeFromString(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 +170,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"); diff --git a/lib/api/common_api.dart b/lib/api/common_api.dart index eef33cd..6e60b31 100644 --- a/lib/api/common_api.dart +++ b/lib/api/common_api.dart @@ -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'; } diff --git a/lib/api/video_api.dart b/lib/api/video_api.dart index 451142e..643c5a7 100644 --- a/lib/api/video_api.dart +++ b/lib/api/video_api.dart @@ -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'; //查询某人的作品 } diff --git a/lib/components/network_or_asset_image.dart b/lib/components/network_or_asset_image.dart index c7362e4..8a78d60 100644 --- a/lib/components/network_or_asset_image.dart +++ b/lib/components/network_or_asset_image.dart @@ -27,12 +27,14 @@ class NetworkOrAssetImage extends StatelessWidget { width: width, height: height, fit: fit, - placeholder: (context, url) => Image.asset( - placeholderAsset.isEmpty ? 'assets/images/avatar/default.png' : placeholderAsset, - width: width, - height: height, - fit: fit, - ), + 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, diff --git a/lib/components/web_page.dart b/lib/components/web_page.dart new file mode 100644 index 0000000..9a9a6f2 --- /dev/null +++ b/lib/components/web_page.dart @@ -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 createState() => _WebPageState(); +} + +class _WebPageState extends State { + 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), + ); + } +} diff --git a/lib/layouts/index.dart b/lib/layouts/index.dart index 18b371d..82c1750 100644 --- a/lib/layouts/index.dart +++ b/lib/layouts/index.dart @@ -32,15 +32,15 @@ class _LayoutState extends State { 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 +126,62 @@ class _LayoutState extends State { return Scaffold( backgroundColor: Colors.grey[50], // body: pageList[pageCurrent], - body: Obx(() { - return Stack( - 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( + // 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: [ + 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底部导航栏的水波纹 @@ -241,6 +269,6 @@ class _LayoutState extends State { myPageKey.currentState?.refreshData(); } videoModuleController.updateLayoutPage(index); - setState(() {}); + // setState(() {}); } } diff --git a/lib/main.dart b/lib/main.dart index 09d3c7f..9b3a6c5 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -7,12 +7,13 @@ 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/storage.dart'; import 'package:media_kit/media_kit.dart'; @@ -24,18 +25,20 @@ import 'layouts/index.dart'; import 'router/index.dart'; void main() async { + WidgetsFlutterBinding.ensureInitialized(); + // 注入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,35 +89,37 @@ class App extends StatelessWidget { @override Widget build(BuildContext context) { - return GetMaterialApp( - title: '无终街', - locale: const Locale('zh'), - supportedLocales: const [ - Locale('zh'), - ], - localizationsDelegates: [ - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - GlobalCupertinoLocalizations.delegate, - ], - debugShowCheckedModeBanner: false, - theme: ThemeData( - // colorScheme: ColorScheme.fromSeed(seedColor: FStyle.primaryColor), - useMaterial3: true, + return DeepLinkListener( + child: GetMaterialApp( + title: '无终街', + locale: const Locale('zh'), + supportedLocales: const [ + Locale('zh'), + ], + localizationsDelegates: [ + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + debugShowCheckedModeBanner: false, + theme: ThemeData( + // colorScheme: ColorScheme.fromSeed(seedColor: FStyle.primaryColor), + useMaterial3: true, + ), + home: const Layout(), + initialRoute: '/', + getPages: routePages, + navigatorKey: MyDialog.navigatorKey, + + // 注入 RouteObserver + navigatorObservers: [routeObserver], + + // 注入到 GetX + builder: (context, child) { + Get.put>(routeObserver); + return child!; + }, ), - home: const Layout(), - initialRoute: '/', - getPages: routePages, - navigatorKey: MyDialog.navigatorKey, - - // 注入 RouteObserver - navigatorObservers: [routeObserver], - - // 注入到 GetX - builder: (context, child) { - Get.put>(routeObserver); - return child!; - }, ); } } diff --git a/lib/models/notify_message.type.dart b/lib/models/notify_message.type.dart index 1a7b4dc..1bda753 100644 --- a/lib/models/notify_message.type.dart +++ b/lib/models/notify_message.type.dart @@ -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': diff --git a/lib/pages/auth/login.dart b/lib/pages/auth/login.dart index ddf3662..85ce290 100644 --- a/lib/pages/auth/login.dart +++ b/lib/pages/auth/login.dart @@ -30,7 +30,7 @@ class _LoginState extends State { Timer? timer; String vcodeText = '获取验证码'; bool disabled = false; - int time = 6; + int time = 60; @override void initState() { diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index b0361c8..a3e2601 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -8,20 +8,27 @@ 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/snapshot.dart'; import 'package:loopin/utils/voice_service.dart'; import 'package:mime/mime.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_message.dart'; import 'package:video_player/video_player.dart'; @@ -81,7 +88,7 @@ class _ChatState extends State 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'}, ]; @@ -285,6 +292,7 @@ class _ChatState extends State with SingleTickerProviderStateMixin { List renderChatList() { List msgtpl = []; for (var item in controller.chatList) { + // logger.e(item.status); // 时间提示,公告提示 if (item.localCustomData == 'time_label') { msgtpl.add(Container( @@ -322,22 +330,38 @@ class _ChatState extends State with SingleTickerProviderStateMixin { msgtpl.add( RenderChatItem( data: item, - child: Ink( - decoration: BoxDecoration( - color: !(item.isSelf ?? false) ? Color(0xFFFFFFFF) : Color(0xFF89E45B), - borderRadius: BorderRadius.circular(10.0), - ), - child: InkWell( - overlayColor: WidgetStateProperty.all(Colors.transparent), - 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: 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) ? Color(0xFFFFFFFF) : Color(0xFF89E45B), + borderRadius: BorderRadius.circular(10.0), + ), + child: InkWell( + overlayColor: WidgetStateProperty.all(Colors.transparent), + 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/网址/电话 + ), + onLongPress: () { + contextMenuDialog(); + }, + ), ), - onLongPress: () { - contextMenuDialog(); - }, - ), + ], ), ), ); @@ -346,21 +370,34 @@ class _ChatState extends State with SingleTickerProviderStateMixin { else if (item.elemType == 8) { msgtpl.add(RenderChatItem( data: item, - child: Ink( - child: InkWell( - overlayColor: WidgetStateProperty.all(Colors.transparent), - child: Container( - constraints: const BoxConstraints( - maxHeight: 100.0, - maxWidth: 100.0, + 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( + constraints: const BoxConstraints( + maxHeight: 100.0, + maxWidth: 100.0, + ), + // child: Image.asset('assets/images/emotion/${item.faceElem?.data}'), + child: Image.asset('${item.faceElem?.data}'), + ), + onLongPress: () { + contextMenuDialog(); + }, ), - // child: Image.asset('assets/images/emotion/${item.faceElem?.data}'), - child: Image.asset('${item.faceElem?.data}'), ), - onLongPress: () { - contextMenuDialog(); - }, - ), + ], ), )); } @@ -371,54 +408,68 @@ class _ChatState extends State with SingleTickerProviderStateMixin { List imagePaths = originImage != null ? [originImage.url!] : []; msgtpl.add(RenderChatItem( data: item, - child: Ink( - child: InkWell( - onTap: () { - // 预览图片 - Get.to(() => ImageViewer( - images: [imagePaths.first], - index: 0, - )); - }, - overlayColor: WidgetStateProperty.all(Colors.transparent), - child: ClipRRect( - borderRadius: BorderRadius.circular(10.0), - // child: ImageGroup( - // images: imagePaths, - // width: 120, - // ), - child: Image.network( - imagePaths.first, - width: 120, - fit: BoxFit.cover, - loadingBuilder: (context, child, loadingProgress) { - if (loadingProgress == null) { - // controller.scrollToBottom(); - return child; // 加载完成,显示图片 - } - return Container( - width: 120, - height: 240, - color: Colors.grey[300], - alignment: Alignment.center, - child: CircularProgressIndicator( - value: loadingProgress.expectedTotalBytes != null ? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes! : null, - ), - ); + 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: () { + // 预览图片 + Get.to(() => ImageViewer( + images: [imagePaths.first], + index: 0, + )); }, - errorBuilder: (context, error, stackTrace) { - return Container( - color: Colors.grey[300], - alignment: Alignment.center, - child: Icon(Icons.broken_image, color: Colors.grey, size: 40), - ); + overlayColor: WidgetStateProperty.all(Colors.transparent), + child: ClipRRect( + borderRadius: BorderRadius.circular(10.0), + // child: ImageGroup( + // images: imagePaths, + // width: 120, + // ), + child: Image.network( + imagePaths.first, + width: 120, + fit: BoxFit.cover, + loadingBuilder: (context, child, loadingProgress) { + if (loadingProgress == null) { + // controller.scrollToBottom(); + return child; // 加载完成,显示图片 + } + return Container( + width: 120, + height: 240, + color: Colors.grey[300], + alignment: Alignment.center, + child: CircularProgressIndicator( + value: + loadingProgress.expectedTotalBytes != null ? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes! : null, + ), + ); + }, + errorBuilder: (context, error, stackTrace) { + return Container( + color: Colors.grey[300], + alignment: Alignment.center, + child: Icon(Icons.broken_image, color: Colors.grey, size: 40), + ); + }, + ), + ), + onLongPress: () { + contextMenuDialog(); }, ), ), - onLongPress: () { - contextMenuDialog(); - }, - ), + ], ), )); } @@ -427,56 +478,69 @@ class _ChatState extends State with SingleTickerProviderStateMixin { // print(item.videoElem!.toLogString()); msgtpl.add(RenderChatItem( data: item, - child: Ink( - child: InkWell( - overlayColor: WidgetStateProperty.all(Colors.transparent), - child: SizedBox( - width: 120.0, - child: Stack( - alignment: Alignment.center, - children: [ - ClipRRect( - borderRadius: BorderRadius.circular(10.0), - child: NetworkOrAssetImage( - imageUrl: item.videoElem?.snapshotUrl ?? '', - width: 120, - ), - ), - const Align( - alignment: Alignment.center, - child: Icon( - Icons.play_circle, - color: Colors.white, - size: 30.0, - ), - ), - ], + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (item.status == MessageStatus.V2TIM_MSG_STATUS_SEND_FAIL) ...[ + Icon( + Icons.error, + color: Colors.red, + size: 18, ), - ), - onTap: () { - showGeneralDialog( - context: context, - // barrierDismissible: true, - barrierColor: Colors.black.withAlpha((1.0 * 255).round()), - pageBuilder: (_, __, ___) { - return SafeArea( - child: PreviewVideo( - videoUrl: item.videoElem?.videoUrl ?? '', - width: item.videoElem?.snapshotWidth?.toDouble(), - height: item.videoElem?.snapshotHeight?.toDouble(), - ), + SizedBox(width: 4), + ], + Ink( + child: InkWell( + overlayColor: WidgetStateProperty.all(Colors.transparent), + child: SizedBox( + width: 120.0, + child: Stack( + alignment: Alignment.center, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(10.0), + child: NetworkOrAssetImage( + imageUrl: item.videoElem?.snapshotUrl ?? '', + width: 120, + ), + ), + const Align( + alignment: Alignment.center, + child: Icon( + Icons.play_circle, + color: Colors.white, + size: 30.0, + ), + ), + ], + ), + ), + onTap: () { + showGeneralDialog( + context: context, + // barrierDismissible: true, + barrierColor: Colors.black.withAlpha((1.0 * 255).round()), + pageBuilder: (_, __, ___) { + return SafeArea( + child: PreviewVideo( + videoUrl: item.videoElem?.videoUrl ?? '', + width: item.videoElem?.snapshotWidth?.toDouble(), + height: item.videoElem?.snapshotHeight?.toDouble(), + ), + ); + }, + transitionBuilder: (_, anim, __, child) { + return FadeTransition(opacity: anim, child: child); + }, + transitionDuration: const Duration(milliseconds: 200), ); }, - transitionBuilder: (_, anim, __, child) { - return FadeTransition(opacity: anim, child: child); + onLongPress: () { + contextMenuDialog(); }, - transitionDuration: const Duration(milliseconds: 200), - ); - }, - onLongPress: () { - contextMenuDialog(); - }, - ), + ), + ), + ], ), )); } @@ -487,69 +551,82 @@ class _ChatState extends State with SingleTickerProviderStateMixin { final maxWidth = (durationSeconds / 60 * 230).clamp(80.0, 230.0); List audiobody = [ - Ink( - decoration: BoxDecoration( - color: !(item.isSelf ?? false) ? const Color(0xFFFFFFFF) : const Color(0xFF89E45B), - borderRadius: BorderRadius.circular(10.0), - ), - child: InkWell( - overlayColor: WidgetStateProperty.all(Colors.transparent), - borderRadius: BorderRadius.circular(10.0), - child: Container( - padding: const EdgeInsets.all(10.0), - constraints: BoxConstraints( - maxWidth: maxWidth, - // maxWidth: (item.soundElem!.duration! / 1000) / 60 * 230, + Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (item.status == MessageStatus.V2TIM_MSG_STATUS_SEND_FAIL) ...[ + Icon( + Icons.error, + color: Colors.red, + size: 18, ), - child: Obx( - () => Row( - mainAxisAlignment: !(item.isSelf ?? false) ? MainAxisAlignment.start : MainAxisAlignment.end, - children: !(item.isSelf ?? false) - ? [ - // const Icon(Icons.multitrack_audio), - VoiceAnimation( - isPlaying: voicePlayingMap[item.id ?? '${item.timestamp ?? 0}'] ?? false, - ), - const SizedBox( - width: 5.0, - ), - Text('$durationSeconds"'), - ] - : [ - Text('$durationSeconds"'), - const SizedBox( - width: 5.0, - ), - // const Icon(Icons.multitrack_audio), - VoiceAnimation( - isPlaying: voicePlayingMap[item.id ?? '${item.timestamp ?? 0}'] ?? false, - ), - ], + SizedBox(width: 4), + ], + Ink( + decoration: BoxDecoration( + color: !(item.isSelf ?? false) ? const Color(0xFFFFFFFF) : const Color(0xFF89E45B), + borderRadius: BorderRadius.circular(10.0), + ), + child: InkWell( + overlayColor: WidgetStateProperty.all(Colors.transparent), + borderRadius: BorderRadius.circular(10.0), + child: Container( + padding: const EdgeInsets.all(10.0), + constraints: BoxConstraints( + maxWidth: maxWidth, + // maxWidth: (item.soundElem!.duration! / 1000) / 60 * 230, + ), + child: Obx( + () => Row( + mainAxisAlignment: !(item.isSelf ?? false) ? MainAxisAlignment.start : MainAxisAlignment.end, + children: !(item.isSelf ?? false) + ? [ + // const Icon(Icons.multitrack_audio), + VoiceAnimation( + isPlaying: voicePlayingMap[item.id ?? '${item.timestamp ?? 0}'] ?? false, + ), + const SizedBox( + width: 5.0, + ), + Text('$durationSeconds"'), + ] + : [ + Text('$durationSeconds"'), + const SizedBox( + width: 5.0, + ), + // const Icon(Icons.multitrack_audio), + VoiceAnimation( + isPlaying: voicePlayingMap[item.id ?? '${item.timestamp ?? 0}'] ?? false, + ), + ], + ), + ), ), + onTap: () async { + final locUrl = item.soundElem?.path ?? ''; + final netUrl = item.soundElem?.url ?? ''; + final msgId = item.id ?? '${item.timestamp ?? 0}'; + logger.w('本地地址$locUrl'); + logger.w('网络地址$netUrl'); + if (locUrl.isNotEmpty) { + voicePlayingMap[msgId] = true; + await AudioPlayerService().playLocal(locUrl); + voicePlayingMap[msgId] = false; + } else if (netUrl.isNotEmpty) { + voicePlayingMap[msgId] = true; + await AudioPlayerService().playNetwork(netUrl); + voicePlayingMap[msgId] = false; + } else { + MyDialog.toast('音频文件已过期', icon: Icon(Icons.warning), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200))); + } + }, + onLongPress: () { + contextMenuDialog(); + }, ), ), - onTap: () async { - final locUrl = item.soundElem?.path ?? ''; - final netUrl = item.soundElem?.url ?? ''; - final msgId = item.id ?? '${item.timestamp ?? 0}'; - logger.w('本地地址$locUrl'); - logger.w('网络地址$netUrl'); - if (locUrl.isNotEmpty) { - voicePlayingMap[msgId] = true; - await AudioPlayerService().playLocal(locUrl); - voicePlayingMap[msgId] = false; - } else if (netUrl.isNotEmpty) { - voicePlayingMap[msgId] = true; - await AudioPlayerService().playNetwork(netUrl); - voicePlayingMap[msgId] = false; - } else { - MyDialog.toast('音频文件已过期', icon: Icon(Icons.warning), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200))); - } - }, - onLongPress: () { - contextMenuDialog(); - }, - ), + ], ), const SizedBox( width: 5.0, @@ -597,66 +674,79 @@ class _ChatState extends State with SingleTickerProviderStateMixin { // 这里带上分享人的ID Get.toNamed('/goods', arguments: {'goodsId': goodsId, 'userID': userID}); }, - child: Container( - width: 160, - 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: [ - NetworkOrAssetImage( - imageUrl: url, - width: 160.0, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (item.status == MessageStatus.V2TIM_MSG_STATUS_SEND_FAIL) ...[ + Icon( + Icons.error, + color: Colors.red, + size: 18, ), - Container( - padding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 5.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - spacing: 5.0, - children: [ - Text( - '$title', - style: TextStyle(fontSize: 14.0, height: 1.2), - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + SizedBox(width: 4), + ], + Container( + width: 160, + 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: [ + NetworkOrAssetImage( + imageUrl: url, + width: 160.0, + ), + Container( + padding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 5.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 5.0, children: [ - Flexible( - child: Text.rich( - overflow: TextOverflow.ellipsis, - maxLines: 1, - TextSpan(style: TextStyle(color: Colors.red, fontSize: 12.0, fontWeight: FontWeight.w700, fontFamily: 'Arial'), children: [ - TextSpan(text: '¥'), - TextSpan( - text: '$price', - style: TextStyle( - fontSize: 14.0, - )), - ]), - ), - ), - SizedBox( - width: 5, - ), Text( - '已售$sell件', - style: TextStyle(color: Colors.grey, fontSize: 10.0), + '$title', + style: TextStyle(fontSize: 14.0, height: 1.2), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: Text.rich( + overflow: TextOverflow.ellipsis, + maxLines: 1, + TextSpan(style: TextStyle(color: Colors.red, fontSize: 12.0, fontWeight: FontWeight.w700, fontFamily: 'Arial'), children: [ + TextSpan(text: '¥'), + TextSpan( + text: '$price', + style: TextStyle( + fontSize: 14.0, + )), + ]), + ), + ), + SizedBox( + width: 5, + ), + Text( + '已售$sell件', + style: TextStyle(color: Colors.grey, fontSize: 10.0), + ), + ], ), ], ), - ], - ), - ) - ], - ), + ) + ], + ), + ), + ], ), ), )); @@ -672,70 +762,85 @@ class _ChatState extends State with SingleTickerProviderStateMixin { final width = obj['width'] as num; final height = obj['height'] as num; final isHorizontal = width > height; - msgtpl.add(RenderChatItem( - data: item, - child: Ink( - child: InkWell( - overlayColor: WidgetStateProperty.all(Colors.transparent), - child: SizedBox( - width: 120.0, - child: Stack( - alignment: Alignment.center, - children: [ - ClipRRect( - borderRadius: BorderRadius.circular(10.0), - child: Container( - width: 120, - height: 240, - color: Colors.black, - child: NetworkOrAssetImage( - imageUrl: imgUrl, - fit: isHorizontal ? BoxFit.contain : BoxFit.cover, - placeholderAsset: 'assets/images/bk.jpg', - ), + 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), + ], + Ink( + child: InkWell( + overlayColor: WidgetStateProperty.all(Colors.transparent), + child: SizedBox( + width: 120.0, + child: Stack( + alignment: Alignment.center, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(10.0), + child: Container( + width: 120, + height: 240, + color: Colors.black, + child: NetworkOrAssetImage( + imageUrl: imgUrl, + fit: isHorizontal ? BoxFit.contain : BoxFit.cover, + placeholderAsset: 'assets/images/bk.jpg', + ), + ), + ), + const Align( + alignment: Alignment.center, + child: Icon( + Icons.play_circle, + color: Colors.white, + size: 30.0, + ), + ), + ], ), ), - const Align( - alignment: Alignment.center, - child: Icon( - Icons.play_circle, - color: Colors.white, - size: 30.0, - ), - ), - ], + onTap: () { + Get.toNamed('/videoDetail', arguments: {'videoId': videoId}); + // showGeneralDialog( + // context: context, + // barrierColor: Colors.black.withAlpha((1.0 * 255).round()), + // pageBuilder: (_, __, ___) { + // return SafeArea( + // bottom: true, + // child: Padding( + // padding: const EdgeInsets.only(bottom: 4), + // child: PreviewVideo( + // videoUrl: videoUrl, + // width: width.toDouble(), + // height: height.toDouble(), + // ), + // ), + // ); + // }, + // transitionBuilder: (_, anim, __, child) { + // return FadeTransition(opacity: anim, child: child); + // }, + // transitionDuration: const Duration(milliseconds: 200), + // ); + }, + // onLongPress: () { + // contextMenuDialog(); + // }, + ), ), - ), - onTap: () { - Get.toNamed('/videoDetail', arguments: {'videoId': videoId}); - // showGeneralDialog( - // context: context, - // barrierColor: Colors.black.withAlpha((1.0 * 255).round()), - // pageBuilder: (_, __, ___) { - // return SafeArea( - // bottom: true, - // child: Padding( - // padding: const EdgeInsets.only(bottom: 4), - // child: PreviewVideo( - // videoUrl: videoUrl, - // width: width.toDouble(), - // height: height.toDouble(), - // ), - // ), - // ); - // }, - // transitionBuilder: (_, anim, __, child) { - // return FadeTransition(opacity: anim, child: child); - // }, - // transitionDuration: const Duration(milliseconds: 200), - // ); - }, - // onLongPress: () { - // contextMenuDialog(); - // }, + ], ), ), - )); + ); } // 红包模板=自定义=2; else if (item.elemType == 2 && item.cloudCustomData == SummaryType.hongbao) { @@ -745,65 +850,78 @@ class _ChatState extends State with SingleTickerProviderStateMixin { // final maxNum = obj['maxNum']; msgtpl.add(RenderChatItem( data: item, - child: Ink( - decoration: BoxDecoration( - color: const Color(0xFFFF7F43), - borderRadius: BorderRadius.circular(10.0), - ), - child: InkWell( - overlayColor: WidgetStateProperty.all(Colors.transparent), - borderRadius: BorderRadius.circular(10.0), - child: Container( - constraints: const BoxConstraints( - maxWidth: 210.0, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (item.status == MessageStatus.V2TIM_MSG_STATUS_SEND_FAIL) ...[ + Icon( + Icons.error, + color: Colors.red, + size: 18, ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - width: 210.0, - padding: const EdgeInsets.all(10.0), - child: Row( - children: [ - open - ? Icon(Icons.check_circle, size: 32.0, color: Colors.white70) - : Image.asset( - 'assets/images/hbico.png', - width: 32.0, - fit: BoxFit.contain, + SizedBox(width: 4), + ], + Ink( + decoration: BoxDecoration( + color: const Color(0xFFFF7F43), + borderRadius: BorderRadius.circular(10.0), + ), + child: InkWell( + overlayColor: WidgetStateProperty.all(Colors.transparent), + borderRadius: BorderRadius.circular(10.0), + child: Container( + constraints: const BoxConstraints( + maxWidth: 210.0, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 210.0, + padding: const EdgeInsets.all(10.0), + child: Row( + children: [ + open + ? Icon(Icons.check_circle, size: 32.0, color: Colors.white70) + : Image.asset( + 'assets/images/hbico.png', + width: 32.0, + fit: BoxFit.contain, + ), + const SizedBox(width: 10), + Expanded( + child: Text( + '$remark', + style: const TextStyle(color: Colors.white, fontSize: 14.0), + maxLines: 1, + overflow: TextOverflow.ellipsis, ), - const SizedBox(width: 10), - Expanded( - child: Text( - '$remark', - style: const TextStyle(color: Colors.white, fontSize: 14.0), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), + ), + ], ), - ], - ), + ), + Container( + margin: const EdgeInsets.symmetric(horizontal: 10.0), + padding: const EdgeInsets.symmetric(vertical: 5.0), + width: double.infinity, + decoration: const BoxDecoration(border: Border(top: BorderSide(color: Colors.white30, width: .5))), + child: const Text( + '红包', + style: TextStyle(color: Colors.white70, fontSize: 11.0), + ), + ), + ], ), - Container( - margin: const EdgeInsets.symmetric(horizontal: 10.0), - padding: const EdgeInsets.symmetric(vertical: 5.0), - width: double.infinity, - decoration: const BoxDecoration(border: Border(top: BorderSide(color: Colors.white30, width: .5))), - child: const Text( - '红包', - style: TextStyle(color: Colors.white70, fontSize: 11.0), - ), - ), - ], + ), + onTap: () { + receiveRedPacketDialog(item); + }, + onLongPress: () { + contextMenuDialog(); + }, ), ), - onTap: () { - receiveRedPacketDialog(item); - }, - onLongPress: () { - contextMenuDialog(); - }, - ), + ], ), )); } @@ -1088,7 +1206,7 @@ class _ChatState extends State with SingleTickerProviderStateMixin { } // 发送消息队列 - Future sendMessage(message) async { + Future sendMessage(message, {bool? isHb}) async { // 待插入的消息 List messagesToInsert = []; V2TimMessage? lastRealMsg; @@ -1134,9 +1252,15 @@ class _ChatState extends State 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 +1334,69 @@ class _ChatState extends State 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); @@ -1479,7 +1646,8 @@ class _ChatState extends State 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 +1681,7 @@ class _ChatState extends State 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 +1692,7 @@ class _ChatState extends State with SingleTickerProviderStateMixin { SizedBox( height: 100.0, ), - if (open == false && data.isSelf == false) + if (open == false) AnimatedBuilder( animation: animTurns, builder: (context, child) { @@ -1550,15 +1718,21 @@ class _ChatState extends State with SingleTickerProviderStateMixin { // 执行消费红包动作 //-------- // 成功后修改消息体 - obj['open'] = true; //成功标记为true - data.customElem!.data = jsonEncode(obj); - ImService.instance.modifyMessage(message: data); - // 模拟开红包逻辑,1 秒后停止动画 - Future.delayed(Duration(seconds: 1), () { - animController.stop(); - animController.reset(); - Get.back(); - }); + final ctl = Get.find(); + 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); + // 模拟开红包逻辑,1 秒后停止动画 + Future.delayed(Duration(seconds: 1), () { + animController.stop(); + animController.reset(); + Get.back(); + }); + } catch (e) { + logger.e(e); + } }, ), ); @@ -1624,6 +1798,25 @@ class _ChatState extends State 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(); + ctl.chatList.removeWhere( + (conv) => conv.conversation.conversationID == arguments.value.conversationID, + ); + ctl.chatList.refresh(); + Get.back(); + Get.back(); + } + } + // 发群红包弹窗 void sendRedPacketDialog() { showModalBottomSheet( @@ -1639,7 +1832,15 @@ class _ChatState extends State 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); }); }, @@ -1788,10 +1989,33 @@ class _ChatState extends State with SingleTickerProviderStateMixin { // break; case 'report': - print('点击了举报'); + // print('点击了举报'); + Get.to(() => ReportChat(convsationID: arguments.value.conversationID)); break; case 'block': - print('点击了拉黑'); + // print('点击了拉黑'); + showDialog( + context: context, + builder: (context) { + return AlertDialog( + 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: const Text('取消', style: TextStyle(color: Colors.black54)), + ), + TextButton(onPressed: _handleBlack, child: const Text('确认拉黑', style: TextStyle(color: Colors.red))), + ], + ); + }, + ); break; // case 'foucs': // print('点击了取关'); diff --git a/lib/pages/chat/chat_group.dart b/lib/pages/chat/chat_group.dart index 6b8070f..68bda48 100644 --- a/lib/pages/chat/chat_group.dart +++ b/lib/pages/chat/chat_group.dart @@ -8,22 +8,28 @@ 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/snapshot.dart'; import 'package:loopin/utils/voice_service.dart'; import 'package:mime/mime.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'; @@ -79,7 +85,7 @@ class _ChatGroupState extends State 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'}, ]; @@ -662,10 +668,16 @@ class _ChatGroupState extends State with SingleTickerProviderStateMix } // 红包模板=自定义=2; else if (item.elemType == 2 && item.cloudCustomData == SummaryType.hongbao) { + // 定位 + final selfId = Get.find().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 ? [] : idsString.split(','); + // 自己是否抢过了 + final hasGet = ids.contains(selfId); msgtpl.add(RenderChatItem( data: item, child: Ink( @@ -688,7 +700,7 @@ class _ChatGroupState extends State with SingleTickerProviderStateMix padding: const EdgeInsets.all(10.0), child: Row( children: [ - open + open || hasGet ? Icon(Icons.check_circle, size: 32.0, color: Colors.white70) : Image.asset( 'assets/images/hbico.png', @@ -1151,17 +1163,50 @@ class _ChatGroupState extends State 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; @@ -1419,9 +1464,18 @@ class _ChatGroupState extends State 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().userID.value; + final totalCount = obj['totalCount'] ?? '0'; // 红包数量 + String idsString = obj['ids'] ?? ''; + final ids = idsString.isEmpty ? [] : idsString.split(','); + // 自己是否抢过了 + final hasGet = ids.contains(selfId); return Material( type: MaterialType.transparency, @@ -1453,7 +1507,7 @@ class _ChatGroupState extends State 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,46 +1518,92 @@ class _ChatGroupState extends State with SingleTickerProviderStateMix SizedBox( height: 100.0, ), - if (open == false && data.isSelf == false) - AnimatedBuilder( - animation: animTurns, - builder: (context, child) { - return Transform( - transform: Matrix4.rotationY(animTurns.value), - alignment: Alignment.center, - child: FilledButton( - style: ButtonStyle( - backgroundColor: WidgetStateProperty.all(const Color(0xFFFFF9C7)), - padding: WidgetStateProperty.all(EdgeInsets.zero), - minimumSize: WidgetStateProperty.all(const Size(80.0, 80.0)), - shape: WidgetStateProperty.all(const CircleBorder()), - elevation: WidgetStateProperty.all(3.0), + if (open == false) ...[ + // 总开未结束,一定显示 + if (hasGet == false) + // 自己没领过才显示 + AnimatedBuilder( + animation: animTurns, + builder: (context, child) { + return Transform( + transform: Matrix4.rotationY(animTurns.value), + alignment: Alignment.center, + child: FilledButton( + style: ButtonStyle( + backgroundColor: WidgetStateProperty.all(const Color(0xFFFFF9C7)), + padding: WidgetStateProperty.all(EdgeInsets.zero), + minimumSize: WidgetStateProperty.all(const Size(80.0, 80.0)), + shape: WidgetStateProperty.all(const CircleBorder()), + elevation: WidgetStateProperty.all(3.0), + ), + child: Text( + '开', + style: TextStyle(color: Color(0xFF3B3B3B), fontSize: 28.0), + ), + onPressed: () async { + // 点击开红包,开始动画 + animController.repeat(); + // 执行抢红包结果查询,(群)展示抢红包人员信息,单不用管 + // 执行消费红包动作 + //-------- + // 成功后修改消息体 + final ctl = Get.find(); + 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); + // 模拟开红包逻辑,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); + 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(); + } + }, ), - child: Text( - '开', - style: TextStyle(color: Color(0xFF3B3B3B), fontSize: 28.0), - ), - onPressed: () async { - // 点击开红包,开始动画 - animController.repeat(); - // 执行抢红包结果查询,(群)展示抢红包人员信息,单不用管 - // 执行消费红包动作 - //-------- - // 成功后修改消息体 - obj['open'] = true; //成功标记为true - data.customElem!.data = jsonEncode(obj); - ImService.instance.modifyMessage(message: data); - // 模拟开红包逻辑,1 秒后停止动画 - Future.delayed(Duration(seconds: 1), () { - animController.stop(); - animController.reset(); - Get.back(); - }); - }, - ), - ); - }, - ), + ); + }, + ), + ] ], ), ), @@ -1565,7 +1665,14 @@ class _ChatGroupState extends State 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))), @@ -1578,14 +1685,30 @@ class _ChatGroupState extends State with SingleTickerProviderStateMix context: context, builder: (context) { return RedPacket( - flag: true, - onSend: (date) { - sendHongbao(date); - }); + flag: true, + maxNum: max, + onSend: (date) { + sendHongbao(date); + }, + ); }, ); } + // 获取群资料 + Future 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 isInGroup() async { final res = await ImService.instance.getJoinedGroupList(); if (res.success && res.data != null) { diff --git a/lib/pages/chat/chat_no_friend.dart b/lib/pages/chat/chat_no_friend.dart index 5cd03df..d780ece 100644 --- a/lib/pages/chat/chat_no_friend.dart +++ b/lib/pages/chat/chat_no_friend.dart @@ -77,7 +77,7 @@ class _ChatNoFriendState extends State 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'}, ]; diff --git a/lib/pages/chat/components/redpacket.dart b/lib/pages/chat/components/redpacket.dart index 26e46a6..fcd8c75 100644 --- a/lib/pages/chat/components/redpacket.dart +++ b/lib/pages/chat/components/redpacket.dart @@ -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)? 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 { String amount = '0.00'; String remark = '恭喜发财,大吉大利'; + String outMax = ''; // 用户输入的数量 // 限制只能输入数字和小数点,且最多两位小数 final List _decimalInputFormatters = [ FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d{0,2}')), @@ -58,7 +60,7 @@ class _RedPacketState extends State { 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 { 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 { 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, }, diff --git a/lib/pages/chat/components/reportChat.dart b/lib/pages/chat/components/reportChat.dart new file mode 100644 index 0000000..f38d54c --- /dev/null +++ b/lib/pages/chat/components/reportChat.dart @@ -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 createState() => _ReportChatState(); +} + +class _ReportChatState extends State with SingleTickerProviderStateMixin { + int? _selectedIndex; + late dynamic args; + final TextEditingController _reportController = TextEditingController(); + final GlobalKey scaffoldKey = GlobalKey(); + List reasonTypeData = []; + + @override + void initState() { + super.initState(); + args = Get.arguments ?? {}; + getReportReasons('ums_chat_report'); // 获取投诉枚举 + } + + @override + void dispose() { + _reportController.dispose(); + super.dispose(); + } + + // 加载数据的方法 + Future getReportReasons(String key) async { + try { + final data = await Commondictionary.getCommonDictionary(key); + setState(() { + reasonTypeData = data; + }); + } catch (e) { + print('加载失败: $e'); + } + } + + // 举报 + Future 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); + } +} diff --git a/lib/pages/chat/index.dart b/lib/pages/chat/index.dart index 5d83701..f33bb29 100644 --- a/lib/pages/chat/index.dart +++ b/lib/pages/chat/index.dart @@ -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; @@ -69,9 +71,11 @@ class ChatPageState extends State { // 使用外部枚举的方法检查扫码类型 final scanType = ScanCodeType.fromCode(code); + if (scanType != null) { - final value = code.substring(scanType.prefix.length + 1); // 获取后缀值 - _processScanCode(scanType, value); + // final value = code.substring(scanType.prefix.length + 1); // 获取后缀值 + // _processScanCode(scanType, value); + _processScanCode(scanType, code); } else { // 未知类型的码 Get.snackbar('未知的二维码类型', '无法识别此二维码: $code'); @@ -96,8 +100,16 @@ class ChatPageState extends State { // 处理核销码 void _handleVerificationCode(String value) async { print('处理核销码: $value'); - // 带着核销码,跳转到商家的商品详情页面,引导商家去手动点击核销按钮 - Get.toNamed('/sellerOrder/detail', arguments: {'writeOffCodeId': value}); + final controller = Get.find(); + final role = controller.role.value; + final isSeller = Utils.hasRole(role, 2); + if (isSeller) { + // 带着核销码,跳转到商家的商品详情页面,引导商家去手动点击核销按钮 + Get.toNamed('/sellerOrder/detail', arguments: {'writeOffCodeId': value}); + } else { + // + MyToast().tip(title: '未入驻的商家不可操作'); + } } // 处理好友码 @@ -315,19 +327,19 @@ class ChatPageState extends State { ], ), ), - PopupMenuItem( - 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( + // 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 +357,11 @@ class ChatPageState extends State { 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 +576,10 @@ class ChatPageState extends State { 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, @@ -660,4 +674,24 @@ class ChatPageState extends State { ), ); } + + 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; + } + } } diff --git a/lib/pages/chat/menu/add_friend.dart b/lib/pages/chat/menu/add_friend.dart index 205d09f..fb5dc9a 100644 --- a/lib/pages/chat/menu/add_friend.dart +++ b/lib/pages/chat/menu/add_friend.dart @@ -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 { @@ -56,7 +57,17 @@ class _AddFriendState extends State { void _handleVerificationCode(String value) async { logger.w('处理核销码: $value'); // 带着核销码,跳转到商家的商品详情页面,引导商家去手动点击核销按钮 - Get.toNamed('/sellerOrder/detail', arguments: {'writeOffCodeId': value}); + final controller = Get.find(); + 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}); } // 处理好友码 diff --git a/lib/pages/chat/notify/interaction.dart b/lib/pages/chat/notify/interaction.dart index 23b48aa..498081e 100644 --- a/lib/pages/chat/notify/interaction.dart +++ b/lib/pages/chat/notify/interaction.dart @@ -7,11 +7,13 @@ 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/models/notify_message.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'; @@ -44,7 +46,7 @@ class InteractionState extends State 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 +60,7 @@ class InteractionState extends State with SingleTickerProviderState final lastmsg = conv?.lastMessage; if (lastmsg != null) { msgList.add(lastmsg); + getMsgData(); } } // chatController.addListener(() { @@ -74,6 +77,18 @@ class InteractionState extends State with SingleTickerProviderState // }); } + @override + void dispose() { + cleanUnRead(); + super.dispose(); + } + + void cleanUnRead() async { + if ((conv?.unreadCount ?? 0) > 0) { + await ImService.instance.clearConversationUnreadCount(conversationID: conv?.conversationID ?? ''); + } + } + // 分页获取全部数据 Future getMsgData() async { // 获取最旧一条消息作为游标 @@ -260,14 +275,14 @@ class InteractionState extends State 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 +291,18 @@ class InteractionState extends State 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 jsonData = '{"faceUrl":"","nickName":"测试昵称","userID":"213213"}'; + 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); // 数据 - 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), @@ -300,10 +316,9 @@ class InteractionState extends State with SingleTickerProviderState // 先获取视频详情 // 如果cloudCustomData是interactionComment,interactionAt,interactionReply,传参时带上评论id,这三个是评论相关的 // - // final res = await Http.get('${VideoApi.detail}/${item['vlogID']}'); + final res = await Http.get('${VideoApi.detail}/${item['vlogID']}'); // 此人存在才做跳转 - // Get.toNamed('/vloger', arguments: res['data']); - Get.toNamed('/vloger'); + Get.toNamed('/vloger', arguments: res['data']); }, child: ClipOval( child: NetworkOrAssetImage( @@ -317,14 +332,14 @@ class InteractionState extends State with SingleTickerProviderState // 消息 Expanded( child: InkWell( - onTap: () { + 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'); + final res = await Http.get('${VideoApi.detail}/${item['vlogID']}'); + Get.toNamed('/vloger', arguments: res['data']); + // Get.toNamed('/vloger'); }, child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -343,8 +358,7 @@ class InteractionState extends State 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), diff --git a/lib/pages/chat/notify/newFoucs.dart b/lib/pages/chat/notify/newFoucs.dart index 956db5d..128ecab 100644 --- a/lib/pages/chat/notify/newFoucs.dart +++ b/lib/pages/chat/notify/newFoucs.dart @@ -10,6 +10,7 @@ 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/my_toast.dart'; import 'package:loopin/components/network_or_asset_image.dart'; import 'package:loopin/models/conversation_type.dart'; import 'package:loopin/service/http.dart'; @@ -48,10 +49,23 @@ class NewfoucsState extends State 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 getMsgData() async { // 获取最旧一条消息作为游标 @@ -172,7 +186,7 @@ class NewfoucsState extends State 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.w(element.toJson()); @@ -197,8 +211,12 @@ class NewfoucsState extends State 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'); + if (res['data'] == null) { + MyToast().tip(title: '视频数据不存在', position: 'top'); + } else { + // 这里跟视频首页一样,跳转博主主页时传视频详情数据 + Get.toNamed('/vloger', arguments: res['data']); + } }, child: ClipOval( child: NetworkOrAssetImage( diff --git a/lib/pages/chat/notify/order.dart b/lib/pages/chat/notify/order.dart deleted file mode 100644 index 956db5d..0000000 --- a/lib/pages/chat/notify/order.dart +++ /dev/null @@ -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 createState() => NewfoucsState(); -} - -class NewfoucsState extends State with SingleTickerProviderStateMixin { - bool isLoading = false; // 是否在加载中 - RxBool hasMore = true.obs; // 是否还有更多数据 - String page = ''; - - ///------------------- - V2TimConversation? conv; - RxList msgList = [].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 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 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: [ - // 头像 - 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: [ - // 昵称 - 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: [ - 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), - ), - ], - ), - ], - ), - ); - }, - ); - }, - ); - }, - ), - ), - ], - ), - ), - ); - } -} diff --git a/lib/pages/chat/notify/orderNotify.dart b/lib/pages/chat/notify/orderNotify.dart new file mode 100644 index 0000000..a6eb9df --- /dev/null +++ b/lib/pages/chat/notify/orderNotify.dart @@ -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 createState() => OrderNotifyState(); +} + +class OrderNotifyState extends State with SingleTickerProviderStateMixin { + bool isLoading = false; // 是否在加载中 + RxBool hasMore = true.obs; // 是否还有更多数据 + String page = ''; + + ///------------------- + V2TimConversation? conv; + RxList msgList = [].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 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 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) { + 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: [ + 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: [ + // 顶部:头像 + 商品名 + 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: [ + // 商品首图 + 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'; + } + } +} diff --git a/lib/pages/chat/notify/system.dart b/lib/pages/chat/notify/system.dart index 3fa5245..8b13789 100644 --- a/lib/pages/chat/notify/system.dart +++ b/lib/pages/chat/notify/system.dart @@ -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 createState() => SystemState(); -} - -class SystemState extends State { - late final ChatController controller; - dynamic writeOffInfo; - @override - void initState() { - super.initState(); - controller = Get.find(); - } - - 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 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().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( - value: 'group', - child: Row( - children: [ - Icon(Icons.group, color: Colors.white, size: 18), - SizedBox(width: 8), - Text( - '发起群聊', - style: TextStyle(color: Colors.white), - ), - ], - ), - ), - PopupMenuItem( - value: 'friend', - child: Row( - children: [ - Icon(Icons.person_add, color: Colors.white, size: 18), - SizedBox(width: 8), - Text( - '添加朋友', - style: TextStyle(color: Colors.white), - ), - ], - ), - ), - PopupMenuItem( - 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: [ - // 头图 - ClipOval( - child: NetworkOrAssetImage( - imageUrl: chatList[index].faceUrl, - width: 50, - height: 50, - ), - ), - - // 消息 - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - 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: [ - 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]); - }, - ), - ); - }, - ); - })), - ), - ], - ), - ), - ); - } -} \ No newline at end of file diff --git a/lib/pages/goods/detail.dart b/lib/pages/goods/detail.dart index 0476695..96c272b 100644 --- a/lib/pages/goods/detail.dart +++ b/lib/pages/goods/detail.dart @@ -78,8 +78,8 @@ class _GoodsState extends State { shopObj = res['data']; // 注意取 data 部分 // 初始化选中的SKU为第一个 if (shopObj != null && shopObj['skuList'] != null && shopObj['skuList'].isNotEmpty) { - // selectedSku = shopObj['skuList'][0]; - // 默认选中每一个分类中的第一条数据 + // selectedSku = shopObj['skuList'][0]; + // 默认选中每一个分类中的第一条数据 dynamic attr = shopObj['productAttr']; List attrList = []; if (!Utils.isEmpty(attr)) { @@ -108,57 +108,63 @@ class _GoodsState extends State { Get.back(); } } + // 根据选中的属性定位到对应的SKU -void locateSelectedSku() { - if (shopObj != null && shopObj['skuList'] != null) { - for (var sku in shopObj['skuList']) { - try { - final spData = jsonDecode(sku['spData'] ?? '{}'); - bool match = true; + void locateSelectedSku() { + if (shopObj != null && shopObj['skuList'] != null) { + for (var sku in shopObj['skuList']) { + try { + final spData = jsonDecode(sku['spData'] ?? '{}'); + bool match = true; - // 检查所有已选属性是否匹配 - selectedAttributes.forEach((key, value) { - if (spData[key] != value) { - match = false; - } - }); - - if (match) { - setState(() { - selectedSku = sku; + // 检查所有已选属性是否匹配 + selectedAttributes.forEach((key, value) { + if (spData[key] != value) { + match = false; + } }); - print('333333333333333333'); - print(sku); - break; + + if (match) { + setState(() { + selectedSku = sku; + }); + print('333333333333333333'); + print(sku); + break; + } + } catch (e) { + logger.e('解析spData错误: $e'); } - } catch (e) { - logger.e('解析spData错误: $e'); } } } -} // 处理属性选择 -void handleAttributeSelect(String attrName, String optionName) { - setState(() { - selectedAttributes[attrName] = optionName; - locateSelectedSku(); // 选择属性后重新定位SKU - }); -} + 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 { @@ -338,6 +344,7 @@ void handleAttributeSelect(String attrName, String optionName) { }, ); } + // 检查属性是否被选中 bool isAttributeSelected(String attrName, String optionName) { return selectedAttributes[attrName] == optionName; @@ -545,7 +552,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 +694,7 @@ void handleAttributeSelect(String attrName, String optionName) { SizedBox(height: 12), ], ); - }).toList(), + }), ], ), ), @@ -736,31 +743,34 @@ void handleAttributeSelect(String attrName, String optionName) { color: Color(0xFFFFEBEB), borderRadius: BorderRadius.circular(30.0), ), - child: shopObj['canOrder'] == true?Row( - children: [ - Container( - alignment: Alignment.center, - padding: const EdgeInsets.symmetric(horizontal: 20.0), - color: Color(0xFFFF5000), - child: GestureDetector( - onTap: () async { - // 这里走生成预支付订单,拿到orderId - String skuId = selectedSku != null ? selectedSku['id'] : shopObj['skuList'][0]['id']; - String orderId = await createOrder(skuId); - if (orderId.isNotEmpty) { - Get.toNamed('/order/detail', arguments: {'orderId': orderId}); - } else { - MyDialog.toast('生成订单失败', icon: const Icon(Icons.warning), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200))); - } - }, - child: Text( - '立即购买', - style: TextStyle(color: Colors.white, fontSize: 14.0), - ), - ), - ), - ], - ):null, + child: shopObj['canOrder'] == true + ? Row( + children: [ + Container( + alignment: Alignment.center, + padding: const EdgeInsets.symmetric(horizontal: 20.0), + color: Color(0xFFFF5000), + child: GestureDetector( + 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}); + } else { + MyDialog.toast('生成订单失败', icon: const Icon(Icons.warning), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200))); + } + }, + child: Text( + '立即购买', + style: TextStyle(color: Colors.white, fontSize: 14.0), + ), + ), + ), + ], + ) + : null, ), ], ), @@ -770,4 +780,4 @@ void handleAttributeSelect(String attrName, String optionName) { floatingActionButton: Backtop(controller: scrollController, offset: scrollOffset), ); } -} \ No newline at end of file +} diff --git a/lib/pages/groupChat/groupDetail.dart b/lib/pages/groupChat/groupDetail.dart index 1410725..c05e8dd 100644 --- a/lib/pages/groupChat/groupDetail.dart +++ b/lib/pages/groupChat/groupDetail.dart @@ -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'; @@ -500,13 +501,14 @@ class GroupdetailState extends State { 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), diff --git a/lib/pages/groupChat/reportGp.dart b/lib/pages/groupChat/reportGp.dart new file mode 100644 index 0000000..5374f36 --- /dev/null +++ b/lib/pages/groupChat/reportGp.dart @@ -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 createState() => _ReportGpState(); +} + +class _ReportGpState extends State with SingleTickerProviderStateMixin { + int? _selectedIndex; + late dynamic args; + final TextEditingController _reportController = TextEditingController(); + final GlobalKey scaffoldKey = GlobalKey(); + List reasonTypeData = []; + + @override + void initState() { + super.initState(); + args = Get.arguments ?? {}; + getReportReasons('ums_chat_report'); // 获取投诉枚举 + } + + @override + void dispose() { + _reportController.dispose(); + super.dispose(); + } + + // 加载数据的方法 + Future getReportReasons(String key) async { + try { + final data = await Commondictionary.getCommonDictionary(key); + setState(() { + reasonTypeData = data; + }); + } catch (e) { + print('加载失败: $e'); + } + } + + // 举报 + Future 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); + } +} diff --git a/lib/pages/index/index.dart b/lib/pages/index/index.dart index 041af63..c298917 100644 --- a/lib/pages/index/index.dart +++ b/lib/pages/index/index.dart @@ -1,331 +1,576 @@ -/// 首页模板 -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 createState() => _IndexPageState(); -} - -class _IndexPageState extends State 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(); - // 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), - ), - ], - ), - ), - ); - } -} +/// 首页模板 +library; + +import 'dart:ui'; + +import 'package:card_swiper/card_swiper.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/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/service/http.dart'; +import 'package:loopin/styles/index.dart'; +import 'package:loopin/utils/index.dart'; + +import '../../behavior/custom_scroll_behavior.dart'; +import '../../components/backtop.dart'; + +class IndexPage extends StatefulWidget { + const IndexPage({super.key}); + + @override + State createState() => _IndexPageState(); +} + +class _IndexPageState extends State 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; + // 分类列表 + RxList tabList = [].obs; + + ///轮播图数据 + RxList swiperData = [].obs; + late ScrollController scrollController = ScrollController(); + final PageController pageController = PageController(); + // 滚动位置 + double scrollOffset = 0; + int page = 1; + + /// 初始化 Tab 分类 + Future 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; + tabList.value = data; + // 如果之前有 controller,要先释放掉 + tabController?.dispose(); + tabController = TabController( + initialIndex: 0, + length: tabList.length, + vsync: this, + ); + if (tabList.isNotEmpty) { + loadSwiperData(); + loadData(currentIndex.value); + } + } + + /// 加载swiper数据 + Future loadSwiperData() async { + final res = await Http.post(ShopApi.shopSwiperList, data: { + 'type': 1, + }); + final data = res['data']; + logger.w(res); + swiperData.assignAll(data); + } + + /// 切换数据 + Future changeData(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); + dataList.value = data; + if (dataList.length >= total) { + hasMore.value = false; + } + + // logger.w(res); + page += 1; + isLoading.value = false; + isInitLoading.value = false; + } + + /// 加载pageview数据 + Future 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); + dataList.addAll(data); + if (dataList.length >= total) { + hasMore.value = false; + } + + // logger.w(res); + page += 1; + 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); + } + } + }); + // 初始化加载 + 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, + 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: [ + NetworkOrAssetImage( + imageUrl: '${item['pic']}', + width: double.infinity, + placeholderAsset: 'assets/images/wait_loading.png', + ), + 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 + Widget build(BuildContext context) { + 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: [ + TextButton( + onPressed: () { + focusNode.unfocus(); + if (textEditingController.text.isNotEmpty) { + // 去搜索结果页,支持带着搜索文字和搜索tab索引 + Get.toNamed( + '/search-result', + arguments: {'searchWords': textEditingController.text, 'tab': 1}, + ); + } + }, + child: Text('搜索'), + ), + ], + ), + ), + 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(); + + return SizedBox( + width: double.infinity, + child: Swiper.children( + pagination: SwiperPagination( + builder: DotSwiperPaginationBuilder( + color: Colors.white70, + activeColor: Colors.white, + size: 6.0, + activeSize: 8.0, + space: 4.0, + ), + ), + indicatorLayout: PageIndicatorLayout.SCALE, + children: swiperData.map((itm) { + return NetworkOrAssetImage( + imageUrl: itm['images'], + placeholderAsset: 'assets/images/bk.jpg', + ); + }).toList(), + ), + ); + }), + ), + ), + ), + + // 分类 + 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); + } + }, + child: Column( + spacing: 3.0, + children: [ + ClipOval( + child: NetworkOrAssetImage( + imageUrl: citem['icon'], + width: 30.0, + height: 30.0, + placeholderAsset: 'assets/images/wait_loading.png', + ), + ), + Text(citem['name']), + ], + ), + ); + }, + ); + }, + ), + ), + // 数量够翻页才显示 + CustomPageViewIndicator( + controller: pageController, + count: (tabList.length / 4).ceil(), + color: Color(0xFFCECECE), + activeColor: Color(0xFFFF5000), + ), + ], + ), + ); + }, + ), + ), + ), + ), + + // 瀑布流列表 + SliverToBoxAdapter( + child: Obx( + () { + if (isInitLoading.value) { + return Column( + children: [ + RefreshProgressIndicator( + backgroundColor: Colors.white, + color: Color(0xFFFF5000), + ), + ], + ); + } + 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, + itemCount: dataList.length, + itemBuilder: (BuildContext context, int index) { + return cardList(dataList[index]); + }, + ), + Opacity(opacity: dataList.isNotEmpty && isLoading.value ? 1 : 0, child: Loading(title: 'loading...')), + ], + ), + ); + }, + ), + ), + ], + ), + ), + ), + // 返回顶部 + floatingActionButton: Backtop(controller: scrollController, offset: scrollOffset), + ), + ); + } +} diff --git a/lib/pages/index/index2.dart b/lib/pages/index/index2.dart new file mode 100644 index 0000000..041af63 --- /dev/null +++ b/lib/pages/index/index2.dart @@ -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 createState() => _IndexPageState(); +} + +class _IndexPageState extends State 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(); + // 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), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/my/all_function.dart b/lib/pages/my/all_function.dart index 7df67c0..5bc3beb 100644 --- a/lib/pages/my/all_function.dart +++ b/lib/pages/my/all_function.dart @@ -240,8 +240,8 @@ class _AllFunctionsPageState extends State { mainAxisSize: MainAxisSize.min, children: [ MyQrcode( - prefix: QrTypeCode.tgm, - text: isLeader ? '推广码' : '', + prefix: isLeader ? QrTypeCode.tgm : QrTypeCode.hym, + text: isLeader ? '团长码' : '', ) ], ), diff --git a/lib/pages/my/delete.dart b/lib/pages/my/delete.dart index c5de581..3922d6a 100644 --- a/lib/pages/my/delete.dart +++ b/lib/pages/my/delete.dart @@ -64,7 +64,10 @@ class _DeleteState extends State { 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 { // 初始化视频 final videoController = Get.find(); 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 == '/'; + }, + ); } } } diff --git a/lib/pages/my/index.dart b/lib/pages/my/index.dart index 0dda29d..48ace7d 100644 --- a/lib/pages/my/index.dart +++ b/lib/pages/my/index.dart @@ -66,7 +66,8 @@ class MyPageState extends State with SingleTickerProviderStateMixin { //用户基本信息 // late Rx userInfo = Rx(null); - ImUserInfoController? imUserInfoController; + // ImUserInfoController? imUserInfoController; + late ImUserInfoController imUserInfoController; // 关注,互关,粉丝数量 late Rx followInfo = Rx(null); @@ -87,13 +88,14 @@ class MyPageState extends State 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(); initControllers(); scrollListener = () { @@ -140,7 +142,7 @@ class MyPageState extends State 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) {} } @@ -168,7 +170,7 @@ class MyPageState extends State with SingleTickerProviderStateMixin { // itemsParams.isInitLoading = 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, @@ -197,7 +199,7 @@ class MyPageState extends State with SingleTickerProviderStateMixin { // favoriteParams.isInitLoading = 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, @@ -255,7 +257,7 @@ class MyPageState extends State 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 +271,18 @@ class MyPageState extends State 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, @@ -402,9 +404,9 @@ class MyPageState extends State 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 +501,8 @@ class MyPageState extends State 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,9 +522,11 @@ class MyPageState extends State with SingleTickerProviderStateMixin { color: Colors.white, borderRadius: BorderRadius.circular(8), ), - child: Text( - '${imUserInfoController!.signature}', - style: const TextStyle(fontSize: 16), + child: Obx( + () => Text( + '${imUserInfoController.signature}', + style: const TextStyle(fontSize: 16), + ), ), ), ], @@ -538,48 +542,47 @@ class MyPageState extends State with SingleTickerProviderStateMixin { if (listToShow.isEmpty) { return emptyTip('暂无相关数据'); } - - return Obx(() { - return CustomScrollView( - // physics: !isPinned.value ? NeverScrollableScrollPhysics() : AlwaysScrollableScrollPhysics(), - // physics: AlwaysScrollableScrollPhysics(), - key: PageStorageKey('myindex_$tabIndex'), - slivers: [ - SliverPadding( - padding: EdgeInsets.all(10.0), - sliver: SliverGrid( - delegate: SliverChildBuilderDelegate( - (context, index) { - return Container( - decoration: BoxDecoration( - color: Colors.blue[100 * ((index % 8) + 1)], - borderRadius: BorderRadius.circular(10.0), - ), - alignment: Alignment.center, - child: _buildVdCard(listToShow[index], tabIndex), - ); - }, - childCount: listToShow.length, - ), - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 3, - crossAxisSpacing: 10.0, - mainAxisSpacing: 10.0, - childAspectRatio: 0.6, + return CustomScrollView( + // physics: !isPinned.value ? NeverScrollableScrollPhysics() : AlwaysScrollableScrollPhysics(), + // physics: AlwaysScrollableScrollPhysics(), + key: PageStorageKey('myindex_$tabIndex'), + slivers: [ + SliverPadding( + padding: EdgeInsets.all(10.0), + sliver: SliverGrid( + delegate: SliverChildBuilderDelegate( + (context, index) { + return Container( + decoration: BoxDecoration( + color: Colors.blue[100 * ((index % 8) + 1)], + borderRadius: BorderRadius.circular(10.0), + ), + alignment: Alignment.center, + child: _buildVdCard(listToShow[index], tabIndex), + ); + }, + childCount: listToShow.length, + ), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + crossAxisSpacing: 10.0, + mainAxisSpacing: 10.0, + childAspectRatio: 0.6, + ), + ), + ), + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 20.0), + child: Center( + child: Obx( + () => params.hasMore.value ? CircularProgressIndicator() : Text('没有更多数据了'), ), ), ), - SliverToBoxAdapter( - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 20.0), - child: Center( - child: params.hasMore.value ? CircularProgressIndicator() : Text('没有更多数据了'), - ), - ), - ), - ], - ); - }); + ), + ], + ); } Widget _buildVdCard(item, tabIndex) { @@ -688,6 +691,8 @@ class MyPageState extends State with SingleTickerProviderStateMixin { } Widget _buildFlexibleSpace() { + logger.e('定位'); + logger.w(imUserInfoController); return LayoutBuilder( builder: (context, constraints) { final double maxHeight = 180; @@ -699,14 +704,17 @@ class MyPageState extends State with SingleTickerProviderStateMixin { fit: StackFit.expand, children: [ // 背景图 Obx - Obx(() { - final bgUrl = imUserInfoController?.customInfo['coverBg'] ?? ''; - return NetworkOrAssetImage( - imageUrl: bgUrl, - placeholderAsset: 'assets/images/bk.jpg', - fit: BoxFit.cover, - ); - }), + 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 +727,10 @@ class MyPageState extends State 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 +743,24 @@ class MyPageState extends State with SingleTickerProviderStateMixin { children: [ Row( children: [ - // 昵称 Obx - Obx( - () { - final nickname = imUserInfoController?.nickname.value ?? ''; - return Container( - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), - decoration: BoxDecoration( - color: Colors.black.withAlpha(76), - borderRadius: BorderRadius.circular(20), - ), - child: Text( - nickname.isNotEmpty ? nickname : '昵称', - // '啊啊啊啊啊啊啊啊', - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - color: Colors.white, - ), + // 昵称 + Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), + decoration: BoxDecoration( + color: Colors.black.withAlpha(76), + borderRadius: BorderRadius.circular(20), + ), + 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 +814,15 @@ class MyPageState extends State with SingleTickerProviderStateMixin { ), const SizedBox(height: 8), // 用户ID Obx - Obx(() { - final userId = imUserInfoController?.userID.value ?? ''; - return Container( - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), - decoration: BoxDecoration( - color: Colors.black.withAlpha(76), - borderRadius: BorderRadius.circular(20), - ), - child: InkWell( + Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), + decoration: BoxDecoration( + color: Colors.black.withAlpha(76), + borderRadius: BorderRadius.circular(20), + ), + child: Obx(() { + final userId = imUserInfoController.userID.value ?? ''; + return InkWell( onTap: () { Clipboard.setData(ClipboardData(text: userId)); MyDialog.toast( @@ -827,9 +832,9 @@ class MyPageState extends State with SingleTickerProviderStateMixin { ); }, child: Text('ID:$userId', style: const TextStyle(fontSize: 12, color: Colors.white)), - ), - ); - }), + ); + }), + ), ], ), ), @@ -856,9 +861,10 @@ class MyPageState extends State 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 +875,7 @@ class MyPageState extends State 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 +887,7 @@ class MyPageState extends State 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,9 +899,11 @@ class MyPageState extends State with SingleTickerProviderStateMixin { }, child: Column( children: [ - Text( - '${followInfo.value?.followersCount ?? 0}', - style: const TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold), + Obx( + () => Text( + '${followInfo.value?.followersCount ?? 0}', + style: const TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold), + ), ), const SizedBox(height: 3.0), const Text('粉丝'), @@ -908,7 +916,7 @@ class MyPageState extends State 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( diff --git a/lib/pages/my/merchant/balance/balance.dart b/lib/pages/my/merchant/balance/balance.dart index 128e904..971c470 100644 --- a/lib/pages/my/merchant/balance/balance.dart +++ b/lib/pages/my/merchant/balance/balance.dart @@ -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 { Row( children: [ ElevatedButton( - onPressed: () { + onPressed: () async { // 加充值 - controller.recharge(money: '0.1'); - // http + // controller.recharge(money: '0.1'); + final money = await showDialog( + 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 { 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( + 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 { }), ), + //提示内容 + 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 { 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 { ); } } + +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; +} diff --git a/lib/pages/my/merchant/balance/controller.dart b/lib/pages/my/merchant/balance/controller.dart index c843bde..73b7b63 100644 --- a/lib/pages/my/merchant/balance/controller.dart +++ b/lib/pages/my/merchant/balance/controller.dart @@ -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 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 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(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 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 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('加载完成'); } diff --git a/lib/pages/my/merchant/balance/model.dart b/lib/pages/my/merchant/balance/model.dart index 65c195e..1ddd972 100644 --- a/lib/pages/my/merchant/balance/model.dart +++ b/lib/pages/my/merchant/balance/model.dart @@ -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 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'] ?? '', ); } } diff --git a/lib/pages/my/setting.dart b/lib/pages/my/setting.dart index 6e7147e..a400423 100644 --- a/lib/pages/my/setting.dart +++ b/lib/pages/my/setting.dart @@ -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( diff --git a/lib/pages/my/vloger.dart b/lib/pages/my/vloger.dart index 4fbf501..f4167e7 100644 --- a/lib/pages/my/vloger.dart +++ b/lib/pages/my/vloger.dart @@ -16,6 +16,7 @@ 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 { @@ -55,6 +56,7 @@ class MyPageState extends State with SingleTickerProviderStateMixin { }, role: 0, )); + String blackTxt = '拉黑'; late RxInt followed = 0.obs; // 是否关注 // followersCount粉丝,多少人关注了我,mutualFollowersCount互关,followingCount我关注了多少人 @@ -113,6 +115,7 @@ class MyPageState extends State with SingleTickerProviderStateMixin { loadData(0); getUserLikesCount(); + getBlackList(); } @override @@ -175,7 +178,7 @@ class MyPageState extends State 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 +213,64 @@ class MyPageState extends State 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(); + ctl.chatList.removeWhere( + (conv) => conv.conversation.conversationID == 'c2c_${args['memberId']}', + ); + ctl.chatList.refresh(); + Get.back(); + } + } + + //拉黑/取消 + void _handleBlack() { + if (blackTxt == '拉黑') { + //_addBlack + _addBlack(); + } else { + _cancelBlack(); + } + } + @override Widget build(BuildContext context) { return PopScope( @@ -235,6 +296,77 @@ class MyPageState extends State with SingleTickerProviderStateMixin { collapsedHeight: 120.0, pinned: true, stretch: true, + actions: [ + IconButton( + icon: const Icon( + Icons.more_horiz, + color: Colors.black, + ), + 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( + 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; + } + } + }, + ), + ], leading: IconButton( icon: const Icon(Icons.arrow_back), onPressed: () { diff --git a/lib/pages/order/detail.dart b/lib/pages/order/detail.dart index fcccbdf..fd95e1d 100644 --- a/lib/pages/order/detail.dart +++ b/lib/pages/order/detail.dart @@ -1,14 +1,19 @@ 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/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,9 +25,11 @@ class OrderDetail extends StatefulWidget { } class _OrderDetailState extends State with SingleTickerProviderStateMixin { + late List orderStatusDict; + final String _orderId = Get.arguments['orderId'] ?? ''; dynamic orderGoodsInfo; - int _initialSeconds = 30 * 60; // 存储初始秒数 + final int _initialSeconds = 30 * 60; // 存储初始秒数 bool _countdownFinished = false; // 新增标志位,用于跟踪倒计时是否结束 bool _isLoading = true; // 添加加载状态 bool _showQrCodeDialog = false; @@ -31,7 +38,8 @@ class _OrderDetailState extends State with SingleTickerProviderStat @override void initState() { super.initState(); - getOrderDetail(orderId: _orderId); + getOrderStatusDict(); + // getOrderDetail(orderId: _orderId); LifecycleHandler.onAppResumed = _onAppResumed; } @@ -42,7 +50,7 @@ class _OrderDetailState extends State with SingleTickerProviderStat } void _onAppResumed() { - print('App回到前台,刷新订单状态,订单Id${_orderId}'); + print('App回到前台,刷新订单状态,订单Id$_orderId'); getOrderDetail(orderId: _orderId); // 刷新订单详情数据 _showPaymentResultDialog(); // 展示支付结果弹框 } @@ -53,7 +61,7 @@ class _OrderDetailState extends State with SingleTickerProviderStat final res = await Http.get('${ShopApi.goodsOrderStatus}/$orderId'); Get.toNamed('/myOrder'); } catch (e) { - print('报错-------------->${e}'); + print('报错-------------->$e'); } } @@ -64,7 +72,7 @@ class _OrderDetailState extends State with SingleTickerProviderStat _isLoading = true; }); final res = await Http.get('${ShopApi.goodsOrderDetail}/$orderId'); - debugPrint(res['data'].toString(), wrapWidth: 600); + logger.w(res); setState(() { orderGoodsInfo = res['data']; _isLoading = false; @@ -77,6 +85,12 @@ class _OrderDetailState extends State with SingleTickerProviderStat } } + Future 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 +103,11 @@ class _OrderDetailState extends State with SingleTickerProviderStat // 核销 void _writeOffQrCode(verificationCodes) async { - if (verificationCodes != null && verificationCodes.isNotEmpty) { + logger.e(orderGoodsInfo); + final orderInfo = orderGoodsInfo['items']; + if (orderInfo != null) { // 过滤可用的核销码(status为0) - List newVerifyList = verificationCodes.where((item) => item['status'] == 0).toList(); + List newVerifyList = orderInfo.where((item) => item['status'] == 0).toList(); if (newVerifyList.isNotEmpty) { setState(() { @@ -108,7 +124,7 @@ class _OrderDetailState extends State with SingleTickerProviderStat } else { MyToast().tip( title: '暂无可用的核销码', - position: 'center', + position: 'top', type: 'error', ); } @@ -229,8 +245,19 @@ class _OrderDetailState extends State 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 +303,7 @@ class _OrderDetailState extends State 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 +312,8 @@ class _OrderDetailState extends State with SingleTickerProviderStat // 构建商品图片 Widget _buildProductImage() { final productInfo = _getFirstProductInfo(); + logger.e(orderGoodsInfo); + final picUrl = productInfo?['pic']; if (picUrl == null || picUrl.isEmpty) { @@ -337,9 +363,7 @@ class _OrderDetailState extends State 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 +396,63 @@ class _OrderDetailState extends State 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(); + 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 +463,7 @@ class _OrderDetailState extends State with SingleTickerProviderStat ], ); - case 1: // 待核销 + case 2: // 待核销 return Row( mainAxisAlignment: MainAxisAlignment.end, children: [ @@ -401,21 +479,21 @@ class _OrderDetailState extends State 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 +779,7 @@ class _OrderDetailState extends State 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 +800,7 @@ class _OrderDetailState extends State with SingleTickerProviderStat ), SizedBox(height: 4), Text( - _countdownFinished - ? '订单已自动取消' - : '超过30分钟未支付,订单将自动取消', + _countdownFinished ? '订单已自动取消' : '超过30分钟未支付,订单将自动取消', style: TextStyle(color: Colors.grey, fontSize: 12.0), ), ], @@ -750,13 +827,21 @@ class _OrderDetailState extends State with SingleTickerProviderStat Row( children: [ Spacer(), - Text( - getOrderStatusText(orderGoodsInfo?['status']), - style: TextStyle( - color: getOrderStatusColor(orderGoodsInfo?['status']), - fontWeight: FontWeight.bold, - ), - ) + orderGoodsInfo['items'][0]['status'] == 1 + ? Text( + '已核销', + style: TextStyle( + color: Colors.red, + fontWeight: FontWeight.bold, + ), + ) + : Text( + getOrderStatusText(orderGoodsInfo?['status']), + style: TextStyle( + color: getOrderStatusColor(orderGoodsInfo?['status']), + fontWeight: FontWeight.bold, + ), + ) ], ), SizedBox(height: 10), @@ -842,7 +927,7 @@ class _OrderDetailState extends State 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 +941,21 @@ class _OrderDetailState extends State 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 +963,8 @@ class _OrderDetailState extends State with SingleTickerProviderStat padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 5.0), child: buildBottomButtons(), ), - ), + ) + : null, ); } -} \ No newline at end of file +} diff --git a/lib/pages/order/my_order.dart b/lib/pages/order/my_order.dart index 038bdc6..a230c5f 100644 --- a/lib/pages/order/my_order.dart +++ b/lib/pages/order/my_order.dart @@ -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 with SingleTickerProviderStateMixin { List> _allOrders = []; List> _pendingPaymentOrders = []; List> _pendingVerificationOrders = []; - List> _completedOrders = []; + // List> _completedOrders = []; List> _closedOrders = []; List> _refundingOrders = []; List> _refundedOrders = []; List> _cancelledOrders = []; // 每个Tab的分页状态 - Map _pageNums = {}; - Map _totalCounts = {}; - Map _hasMoreData = {}; - Map _isLoading = {}; + final Map _pageNums = {}; + final Map _totalCounts = {}; + final Map _hasMoreData = {}; + final Map _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,36 @@ class _MyOrderState extends State with SingleTickerProviderStateMixin { super.dispose(); } + Future getOrderStatusDict() async { + final res = await Http.get('${CommonApi.dictionaryApi}oms_order_status'); + orderStatusDict = res['data'] as List; + //获取初始订单列表 + getOrderList(false); + // dictValue + } + // 获取当前Tab的订单列表 List> _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 +148,35 @@ class _MyOrderState extends State 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 +190,7 @@ class _MyOrderState extends State 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 +233,7 @@ class _MyOrderState extends State with SingleTickerProviderStateMixin { if (res['code'] == 200 && res['data'] != null) { final data = res['data']; + logger.e(data); final List> newOrders = List>.from(data['records'] ?? []); final int total = data['total'] ?? 0; @@ -267,10 +289,10 @@ class _MyOrderState extends State 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 +332,12 @@ class _MyOrderState extends State 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,68 +346,75 @@ class _MyOrderState extends State with SingleTickerProviderStateMixin { ], ), Spacer(), - Text( - _getStatusText(order['status']), - style: TextStyle(color: _getStatusColor(order['status'])), - ) + order['items'][0]['status'] == 1 + ? Text( + '已核销', + style: TextStyle(color: Colors.red), + ) + : Text( + _getStatusText(order['status']), + style: TextStyle(color: _getStatusColor(order['status'])), + ) ], ), SizedBox(height: 10), // 商品信息 if (order['items'] != null && order['items'].isNotEmpty) - ...order['items'].map((item) => Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - width: 80.0, - height: 80.0, - decoration: BoxDecoration( - color: Colors.grey[200], - borderRadius: BorderRadius.circular(4.0), - ), - child: order['items'][0]['pic'] != null && order['items'][0]['pic'].isNotEmpty - ? Image.network( - order['items'][0]['pic'], - width: 80.0, - height: 80.0, - fit: BoxFit.cover, - ) - : Icon( - Icons.shopping_bag_outlined, - size: 40.0, - color: Colors.grey[400], - ), - ), - SizedBox(width: 10), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - item['productName'] ?? '商品名称', - maxLines: 2, - overflow: TextOverflow.ellipsis, - style: TextStyle(fontSize: 14), + ...order['items'] + .map((item) => Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 80.0, + height: 80.0, + decoration: BoxDecoration( + color: Colors.grey[200], + borderRadius: BorderRadius.circular(4.0), ), - SizedBox(height: 5), - Row( + child: order['items'][0]['pic'] != null && order['items'][0]['pic'].isNotEmpty + ? Image.network( + order['items'][0]['pic'], + width: 80.0, + height: 80.0, + fit: BoxFit.cover, + ) + : Icon( + Icons.shopping_bag_outlined, + size: 40.0, + color: Colors.grey[400], + ), + ), + SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - '¥${item['salePrice'] ?? '0'}', - style: TextStyle(color: Colors.red, fontSize: 16), + item['productName'] ?? '商品名称', + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: TextStyle(fontSize: 14), ), - Spacer(), - Text( - 'x${item['quantity'] ?? '1'}', - style: TextStyle(color: Colors.grey), + SizedBox(height: 5), + Row( + children: [ + Text( + '¥${item['salePrice'] ?? '0'}', + style: TextStyle(color: Colors.red, fontSize: 16), + ), + Spacer(), + Text( + 'x${item['quantity'] ?? '1'}', + style: TextStyle(color: Colors.grey), + ), + ], ), ], ), - ], - ), - ) - ], - )).toList(), + ) + ], + )) + .toList(), SizedBox(height: 10), // 金额信息 Container( @@ -422,8 +450,18 @@ class _MyOrderState extends State 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 +553,7 @@ class _MyOrderState extends State 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 +565,7 @@ class _MyOrderState extends State with SingleTickerProviderStateMixin { _refreshAllRelatedTabs(); } } catch (e) { - print('取消订单失败-------------->${e}'); + print('取消订单失败-------------->$e'); } } @@ -546,9 +584,61 @@ class _MyOrderState extends State with SingleTickerProviderStateMixin { } } - void _payOrder(_orderId) { - // 打开微信小程序的某个页面地址,如pages/index/index - Wxsdk.openMiniApp(orderId: _orderId); + void _payOrder(orderId) async { + // 打开微信小程序的某个页面地址,如pages/index/index + // Wxsdk.openMiniApp(orderId: orderId); + + //------------ + final infoCtl = Get.find(); + 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 +650,30 @@ class _MyOrderState extends State 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; } }); } @@ -586,44 +692,44 @@ class _MyOrderState extends State with SingleTickerProviderStateMixin { bottom: PreferredSize( preferredSize: Size.fromHeight(45.0), child: Container( - height: 45.0, - alignment: Alignment.centerLeft, - child: TabBar( - controller: tabController, - tabAlignment: TabAlignment.start, - isScrollable: true, - padding: EdgeInsets.only(left: 0), - indicatorPadding: EdgeInsets.zero, - labelPadding: EdgeInsets.symmetric(horizontal: 10.0), - tabs: tabList - .map((item) => Container( - constraints: BoxConstraints(minWidth: 70), - alignment: Alignment.center, - child: Badge.count( - backgroundColor: Colors.red, - offset: Offset(14, -4), - count: item['badge'] ?? 0, - isLabelVisible: item['badge'] != null && item['badge'] > 0, - child: Text( - item['name'], - style: TextStyle(fontSize: 16), - overflow: TextOverflow.ellipsis, - ), + height: 45.0, + alignment: Alignment.centerLeft, + child: TabBar( + controller: tabController, + tabAlignment: TabAlignment.start, + isScrollable: true, + padding: EdgeInsets.only(left: 0), + indicatorPadding: EdgeInsets.zero, + labelPadding: EdgeInsets.symmetric(horizontal: 10.0), + tabs: tabList + .map((item) => Container( + constraints: BoxConstraints(minWidth: 70), + alignment: Alignment.center, + child: Badge.count( + backgroundColor: Colors.red, + offset: Offset(14, -4), + count: item['badge'] ?? 0, + isLabelVisible: item['badge'] != null && item['badge'] > 0, + child: Text( + item['name'], + style: TextStyle(fontSize: 16), + overflow: TextOverflow.ellipsis, ), - )) - .toList(), - overlayColor: WidgetStateProperty.all(Colors.transparent), - unselectedLabelColor: Colors.black87, - labelColor: Color(0xFFFF5000), - indicator: UnderlineTabIndicator( - borderRadius: BorderRadius.circular(10.0), - borderSide: BorderSide(color: Color(0xFFFF5000), width: 2.0), - ), - indicatorSize: TabBarIndicatorSize.label, - unselectedLabelStyle: TextStyle(fontSize: 16.0, fontFamily: 'Microsoft YaHei'), - labelStyle: TextStyle(fontSize: 16.0, fontFamily: 'Microsoft YaHei', fontWeight: FontWeight.w700), - dividerHeight: 0, - ), + ), + )) + .toList(), + overlayColor: WidgetStateProperty.all(Colors.transparent), + unselectedLabelColor: Colors.black87, + labelColor: Color(0xFFFF5000), + indicator: UnderlineTabIndicator( + borderRadius: BorderRadius.circular(10.0), + borderSide: BorderSide(color: Color(0xFFFF5000), width: 2.0), + ), + indicatorSize: TabBarIndicatorSize.label, + unselectedLabelStyle: TextStyle(fontSize: 16.0, fontFamily: 'Microsoft YaHei'), + labelStyle: TextStyle(fontSize: 16.0, fontFamily: 'Microsoft YaHei', fontWeight: FontWeight.w700), + dividerHeight: 0, + ), ), ), ), @@ -638,49 +744,55 @@ class _MyOrderState extends State with SingleTickerProviderStateMixin { behavior: CustomScrollBehavior().copyWith(scrollbars: false), child: Container( color: Colors.grey[50], - child: Builder( - builder: (context) { - final currentOrders = _getOrderListByIndex(index); - final isLoading = _isLoading[index] ?? false; - final hasMoreData = _hasMoreData[index] ?? false; + child: Builder(builder: (context) { + final currentOrders = _getOrderListByIndex(index); + final isLoading = _isLoading[index] ?? false; + final hasMoreData = _hasMoreData[index] ?? false; - return currentOrders.isEmpty && !isRefreshing - ? emptyTip() - : ListView.builder( - controller: scrollController, - physics: AlwaysScrollableScrollPhysics(), - padding: EdgeInsets.all(10.0), - itemCount: currentOrders.length + (hasMoreData ? 1 : 0), - itemBuilder: (context, itemIndex) { - if (itemIndex == currentOrders.length) { - return _buildLoadMoreIndicator(isLoading); - } - return _buildOrderItem(currentOrders[itemIndex]); - }, - ); - } - ), + return currentOrders.isEmpty && !isRefreshing + ? emptyTip() + : ListView.builder( + controller: scrollController, + physics: AlwaysScrollableScrollPhysics(), + padding: EdgeInsets.all(10.0), + itemCount: currentOrders.length + (hasMoreData ? 1 : 0), + itemBuilder: (context, itemIndex) { + if (itemIndex == currentOrders.length) { + return _buildLoadMoreIndicator(isLoading); + } + return _buildOrderItem(currentOrders[itemIndex]); + }, + ); + }), ), ), ); }), ), - ); } // 根据索引获取订单列表 List> _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 +820,7 @@ class _MyOrderState extends State with SingleTickerProviderStateMixin { } Widget emptyTip() { - return Container( + return SizedBox( width: double.infinity, child: Column( mainAxisAlignment: MainAxisAlignment.center, @@ -726,4 +838,4 @@ class _MyOrderState extends State with SingleTickerProviderStateMixin { ), ); } -} \ No newline at end of file +} diff --git a/lib/pages/order/seller_detail.dart b/lib/pages/order/seller_detail.dart index 0dcdcb2..2b1fffb 100644 --- a/lib/pages/order/seller_detail.dart +++ b/lib/pages/order/seller_detail.dart @@ -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 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 { @@ -45,7 +48,7 @@ class _SellerOrderDetailState extends State with SingleTicker _isLoading = true; }); final res = await Http.get('${ShopApi.goodsOrderDetail}/$orderId'); - debugPrint(res['data'].toString(), wrapWidth: 600); + debugPrint(res['data'].toString(), wrapWidth: 600); setState(() { orderGoodsInfo = res['data']; _isLoading = false; @@ -57,14 +60,30 @@ class _SellerOrderDetailState extends State with SingleTicker MyDialog.toast('获取订单详情失败'); } } + + Future 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']}'); setState(() { orderGoodsInfo = res['data']; _isLoading = false; @@ -78,45 +97,59 @@ class _SellerOrderDetailState extends State with SingleTicker } // 确认核销订单 - void _confirmVerifyCode () async { - late String writeOffCode; - if(_orderId != null){ // 商家可以直接从订单详情获取核销码,并过滤后进行核销 - var verificationCodesList = orderGoodsInfo['verificationCodes']; - if (verificationCodesList != null && verificationCodesList.isNotEmpty) { - // 过滤可用的核销码(status为0) - List 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?? ''; - } - try { - final res = await Http.get('${ShopApi.confirmWriteOff}?code=$writeOffCode'); - debugPrint(res['data'].toString(), wrapWidth: 600); + 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=$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 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 +194,18 @@ class _SellerOrderDetailState extends State with SingleTicker // 获取商品信息列表 List 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 +267,7 @@ class _SellerOrderDetailState extends State 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,14 +345,14 @@ class _SellerOrderDetailState extends State with SingleTicker children: [], ); - case 1: // 待核销 + case 2: // 待核销 return Row( mainAxisAlignment: MainAxisAlignment.end, children: [ const SizedBox(width: 10.0), ElevatedButton( onPressed: () { - _confirmVerifyCode(); + _confirmVerifyCode(); }, style: ButtonStyle( backgroundColor: WidgetStateProperty.all(Color(0xFFFF5000)), @@ -328,11 +363,11 @@ class _SellerOrderDetailState extends State with SingleTicker ], ); - case 2: // 已完成 - return Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [], - ); + // case 2: // 已完成 + // return Row( + // mainAxisAlignment: MainAxisAlignment.end, + // children: [], + // ); case 3: // 已关闭 return Row( @@ -432,8 +467,6 @@ class _SellerOrderDetailState extends State with SingleTicker style: TextStyle(color: Colors.orange), ), SizedBox(width: 4), - - ], ), SizedBox(height: 4), @@ -461,13 +494,21 @@ class _SellerOrderDetailState extends State with SingleTicker Row( children: [ Spacer(), - Text( - getOrderStatusText(orderGoodsInfo?['status'] ?? 0), - style: TextStyle( - color: getOrderStatusColor(orderGoodsInfo?['status'] ?? 0), - fontWeight: FontWeight.bold, - ), - ) + orderGoodsInfo['items'][0]['status'] == 1 + ? Text( + '已核销', + style: TextStyle( + color: Colors.red, + fontWeight: FontWeight.bold, + ), + ) + : Text( + getOrderStatusText(orderGoodsInfo?['status'] ?? 0), + style: TextStyle( + color: getOrderStatusColor(orderGoodsInfo?['status'] ?? 0), + fontWeight: FontWeight.bold, + ), + ) ], ), SizedBox(height: 10), @@ -507,7 +548,7 @@ class _SellerOrderDetailState extends State 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)); }, ) @@ -519,8 +560,11 @@ class _SellerOrderDetailState extends State with SingleTicker _buildOrderInfoRow('订单号', orderGoodsInfo?['orderId']?.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 +582,7 @@ class _SellerOrderDetailState extends State 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,10 +590,18 @@ class _SellerOrderDetailState extends State 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; } @@ -569,4 +621,4 @@ class _SellerOrderDetailState extends State with SingleTicker ), ); } -} \ No newline at end of file +} diff --git a/lib/pages/order/seller_order.dart b/lib/pages/order/seller_order.dart index 651bff5..8d1a732 100644 --- a/lib/pages/order/seller_order.dart +++ b/lib/pages/order/seller_order.dart @@ -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 with SingleTickerProviderStateMixi List> _allOrders = []; List> _pendingPaymentOrders = []; List> _pendingVerificationOrders = []; - List> _completedOrders = []; + // List> _completedOrders = []; List> _closedOrders = []; List> _refundingOrders = []; List> _refundedOrders = []; List> _cancelledOrders = []; // 每个Tab的分页状态 - Map _pageNums = {}; - Map _totalCounts = {}; - Map _hasMoreData = {}; - Map _isLoading = {}; + final Map _pageNums = {}; + final Map _totalCounts = {}; + final Map _hasMoreData = {}; + final Map _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 with SingleTickerProviderStateMixi scrollController.addListener(_scrollListener); // 监听tab切换事件 tabController.addListener(_tabChanged); + getOrderStatusDict(); // 获取初始订单列表 - getOrderList(false); + // getOrderList(false); } @override @@ -83,19 +97,37 @@ class _SellerState extends State with SingleTickerProviderStateMixi super.dispose(); } + Future 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> _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 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 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 with SingleTickerProviderStateMixi if (tabController.index != tabController.previousIndex) { // 如果当前Tab没有数据,则加载数据 final currentOrders = _getCurrentOrderList(); + if (currentOrders.isEmpty) { getOrderList(false); } @@ -207,16 +238,17 @@ class _SellerState extends State 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> newOrders = List>.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 with SingleTickerProviderStateMixi }); } } catch (e) { - print('获取订单列表失败: $e'); + logger.w('获取订单列表失败: $e'); setState(() { _hasMoreData[currentIndex] = false; }); @@ -266,11 +298,11 @@ class _SellerState extends State 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 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,74 +357,81 @@ class _SellerState extends State with SingleTickerProviderStateMixi ], ), Spacer(), - Text( - _getStatusText(order['status']), - style: TextStyle(color: _getStatusColor(order['status'])), - ) + order['verificationCodeStatus'] == 1 + ? Text( + '已核销', + style: TextStyle(color: Colors.red), + ) + : Text( + _getStatusText(order['status']), + style: TextStyle(color: _getStatusColor(order['status'])), + ) ], ), SizedBox(height: 10), // 商品信息 - if (order['items'] != null && order['items'].isNotEmpty) - ...order['items'].map((item) => Row( + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 80.0, + height: 80.0, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(4.0), + ), + 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, + size: 40.0, + color: Colors.grey[400], + ), + ), + SizedBox(width: 10), + Expanded( + child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Container( - width: 80.0, - height: 80.0, - decoration: BoxDecoration( - color: Colors.grey[200], - borderRadius: BorderRadius.circular(4.0), - ), - child: order['items'][0]['pic'] != null && order['items'][0]['pic'].isNotEmpty - ? Image.network( - order['items'][0]['pic'], - width: 80.0, - height: 80.0, - fit: BoxFit.cover, - ) - : Icon( - Icons.shopping_bag_outlined, - size: 40.0, - color: Colors.grey[400], - ), + Text( + order['productName'] ?? '商品名称', + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: TextStyle(fontSize: 14), + ), + SizedBox(height: 5), + Row( + children: [ + Text( + '¥${order['salePrice'] ?? '0'}', + style: TextStyle(color: Colors.red, fontSize: 16), + ), + Spacer(), + Text( + 'x${order['quantity'] ?? '1'}', + style: TextStyle(color: Colors.grey), + ), + ], ), - SizedBox(width: 10), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - item['productName'] ?? '商品名称', - maxLines: 2, - overflow: TextOverflow.ellipsis, - style: TextStyle(fontSize: 14), - ), - SizedBox(height: 5), - Row( - children: [ - Text( - '¥${item['salePrice'] ?? '0'}', - style: TextStyle(color: Colors.red, fontSize: 16), - ), - Spacer(), - Text( - 'x${item['quantity'] ?? '1'}', - style: TextStyle(color: Colors.grey), - ), - ], - ), - ], - ), - ) ], - )).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 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 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 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 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 with SingleTickerProviderStateMixi _refreshAllRelatedTabs(); } } catch (e) { - print('取消订单失败-------------->${e}'); + logger.w('取消订单失败-------------->$e'); } } @@ -546,9 +597,9 @@ class _SellerState extends State with SingleTickerProviderStateMixi } } - void _payOrder(_orderId) { - // 打开微信小程序的某个页面地址,如pages/index/index - Wxsdk.openMiniApp(orderId: _orderId); + void _payOrder(orderId) { + // 打开微信小程序的某个页面地址,如pages/index/index + // Wxsdk.openMiniApp(orderId: orderId); } // 重置指定tab的数据 @@ -560,14 +611,30 @@ class _SellerState extends State 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; } }); } @@ -586,60 +653,61 @@ class _SellerState extends State with SingleTickerProviderStateMixi bottom: PreferredSize( preferredSize: Size.fromHeight(45.0), child: Container( - height: 45.0, - alignment: Alignment.centerLeft, - child: TabBar( - controller: tabController, - tabAlignment: TabAlignment.start, - isScrollable: true, - padding: EdgeInsets.only(left: 0), - indicatorPadding: EdgeInsets.zero, - labelPadding: EdgeInsets.symmetric(horizontal: 10.0), - tabs: tabList - .map((item) => Container( - constraints: BoxConstraints(minWidth: 70), - alignment: Alignment.center, - child: Badge.count( - backgroundColor: Colors.red, - offset: Offset(14, -4), - count: item['badge'] ?? 0, - isLabelVisible: item['badge'] != null && item['badge'] > 0, - child: Text( - item['name'], - style: TextStyle(fontSize: 16), - overflow: TextOverflow.ellipsis, - ), + height: 45.0, + alignment: Alignment.centerLeft, + child: TabBar( + controller: tabController, + tabAlignment: TabAlignment.start, + isScrollable: true, + padding: EdgeInsets.only(left: 0), + indicatorPadding: EdgeInsets.zero, + labelPadding: EdgeInsets.symmetric(horizontal: 10.0), + tabs: tabList + .map((item) => Container( + constraints: BoxConstraints(minWidth: 70), + alignment: Alignment.center, + child: Badge.count( + backgroundColor: Colors.red, + offset: Offset(14, -4), + count: item['badge'] ?? 0, + isLabelVisible: item['badge'] != null && item['badge'] > 0, + child: Text( + item['name'], + style: TextStyle(fontSize: 16), + overflow: TextOverflow.ellipsis, ), - )) - .toList(), - overlayColor: WidgetStateProperty.all(Colors.transparent), - unselectedLabelColor: Colors.black87, - labelColor: Color(0xFFFF5000), - indicator: UnderlineTabIndicator( - borderRadius: BorderRadius.circular(10.0), - borderSide: BorderSide(color: Color(0xFFFF5000), width: 2.0), - ), - indicatorSize: TabBarIndicatorSize.label, - unselectedLabelStyle: TextStyle(fontSize: 16.0, fontFamily: 'Microsoft YaHei'), - labelStyle: TextStyle(fontSize: 16.0, fontFamily: 'Microsoft YaHei', fontWeight: FontWeight.w700), - dividerHeight: 0, - ), + ), + )) + .toList(), + overlayColor: WidgetStateProperty.all(Colors.transparent), + unselectedLabelColor: Colors.black87, + labelColor: Color(0xFFFF5000), + indicator: UnderlineTabIndicator( + borderRadius: BorderRadius.circular(10.0), + borderSide: BorderSide(color: Color(0xFFFF5000), width: 2.0), + ), + indicatorSize: TabBarIndicatorSize.label, + unselectedLabelStyle: TextStyle(fontSize: 16.0, fontFamily: 'Microsoft YaHei'), + labelStyle: TextStyle(fontSize: 16.0, fontFamily: 'Microsoft YaHei', fontWeight: FontWeight.w700), + dividerHeight: 0, + ), ), ), ), - body: TabBarView( - controller: tabController, - children: List.generate(tabList.length, (index) { - return RefreshIndicator( - onRefresh: () async { - _refreshData(); - }, - child: ScrollConfiguration( - behavior: CustomScrollBehavior().copyWith(scrollbars: false), - child: Container( - color: Colors.grey[50], - child: Builder( - builder: (context) { + body: SafeArea( + bottom: true, + child: TabBarView( + controller: tabController, + children: List.generate(tabList.length, (index) { + return RefreshIndicator( + onRefresh: () async { + _refreshData(); + }, + child: ScrollConfiguration( + behavior: CustomScrollBehavior().copyWith(scrollbars: false), + child: Container( + color: Colors.grey[50], + child: Builder(builder: (context) { final currentOrders = _getOrderListByIndex(index); final isLoading = _isLoading[index] ?? false; final hasMoreData = _hasMoreData[index] ?? false; @@ -658,12 +726,12 @@ class _SellerState extends State with SingleTickerProviderStateMixi return _buildOrderItem(currentOrders[itemIndex]); }, ); - } + }), ), ), - ), - ); - }), + ); + }), + ), ), ); } @@ -671,15 +739,24 @@ class _SellerState extends State with SingleTickerProviderStateMixi // 根据索引获取订单列表 List> _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 with SingleTickerProviderStateMixi } Widget emptyTip() { - return Container( + return SizedBox( width: double.infinity, child: Column( mainAxisAlignment: MainAxisAlignment.center, @@ -725,4 +802,4 @@ class _SellerState extends State with SingleTickerProviderStateMixi ), ); } -} \ No newline at end of file +} diff --git a/lib/pages/search/search-result.dart b/lib/pages/search/search_result.dart similarity index 91% rename from lib/pages/search/search-result.dart rename to lib/pages/search/search_result.dart index a1d5d1e..d712ff8 100644 --- a/lib/pages/search/search-result.dart +++ b/lib/pages/search/search_result.dart @@ -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 with SingleTickerPr // 统一的高度常量 static const double _itemCornerRadius = 8; + // 个人信息 + final ctl = Get.find(); + @override void initState() { super.initState(); @@ -269,6 +274,11 @@ class _SearchResultPageState extends State 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 with SingleTickerPr ), ); } + // 视频项构建 Widget _buildVideoItem(Map video) { return GestureDetector( @@ -608,9 +619,7 @@ class _SearchResultPageState extends State 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 with SingleTickerPr return Container(); } + // 商品Tab Widget _buildProductTab() { if (_productResults.isEmpty && !_isLoading) { @@ -920,44 +930,80 @@ class _SearchResultPageState extends State with SingleTickerPr ), child: Row( children: [ - Container( - width: 45, - height: 45, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Colors.grey[200], - image: user['avatar'] != null - ? DecorationImage( - image: NetworkImage(user['avatar'].toString()), - fit: BoxFit.cover, - ) - : null, + 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( + shape: BoxShape.circle, + color: Colors.grey[200], + image: user['avatar'] != null + ? DecorationImage( + image: NetworkImage(user['avatar'].toString()), + fit: BoxFit.cover, + ) + : null, + ), + child: user['avatar'] == null ? const Icon(Icons.person, color: Colors.grey, size: 20) : null, ), - child: user['avatar'] == null ? const Icon(Icons.person, color: Colors.grey, size: 20) : null, ), const SizedBox(width: 10), Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - user['nickname']?.toString() ?? '未知用户', - style: const TextStyle( - fontSize: 13, - fontWeight: FontWeight.w500, + 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( + user['nickname']?.toString() ?? '未知用户', + style: const TextStyle( + fontSize: 13, + fontWeight: FontWeight.w500, + ), ), - ), - const SizedBox(height: 2), - Text( - '粉丝: ${user['fansCount']?.toString() ?? '0'}', - style: const TextStyle( - fontSize: 11, - color: Colors.grey, + const SizedBox(height: 2), + Text( + '粉丝: ${user['fansCount']?.toString() ?? '0'}', + style: const TextStyle( + fontSize: 11, + color: Colors.grey, + ), ), - ), - ], + ], + ), ), ), + // 关注按钮 ElevatedButton( onPressed: () async { await onFocusBtnClick(user, index); diff --git a/lib/pages/video/commonVideo.dart b/lib/pages/video/commonVideo.dart index b16df86..c0794f3 100644 --- a/lib/pages/video/commonVideo.dart +++ b/lib/pages/video/commonVideo.dart @@ -603,8 +603,15 @@ class _VideoDetailPageState extends State { 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, diff --git a/lib/pages/video/module/recommend.dart b/lib/pages/video/module/recommend.dart index 1180a8b..5006c61 100644 --- a/lib/pages/video/module/recommend.dart +++ b/lib/pages/video/module/recommend.dart @@ -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,6 +20,7 @@ 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/permissions.dart'; import 'package:loopin/utils/wxsdk.dart'; @@ -298,8 +298,8 @@ class _CommentBottomSheetState extends State { 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 +328,12 @@ class _CommentBottomSheetState extends State { ), SizedBox(width: 20.0), GestureDetector( + //回复评论 onTap: () { setState(() { - replyingCommentId = comment['id']; + logger.e(comment); + // replyingCommentId = comment['id']; //null + replyingCommentId = comment['commentId']; replyingCommentUser = comment['commentUserNickname'] ?? '未知用户'; }); }, @@ -357,11 +360,13 @@ class _CommentBottomSheetState extends State { 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); } }); }, @@ -570,7 +575,7 @@ class _RecommendModuleState extends State { final List subscriptions = []; // 进度条slider当前阈值 - double sliderValue = 0.0; + double sliderValue = 0.1; bool sliderDraging = false; late Duration position = Duration.zero; // 当前时长 late Duration duration = Duration.zero; // 总时长 @@ -734,6 +739,7 @@ class _RecommendModuleState extends State { final resCode = res['code']; if (resCode == 200) { item['doILikeThisVlog'] = !item['doILikeThisVlog']; + setState(() {}); } } catch (e) { logger.i('点击取消喜欢按钮报错: $e'); @@ -747,6 +753,7 @@ class _RecommendModuleState extends State { final resCode = res['code']; if (resCode == 200) { item['doILikeThisVlog'] = !item['doILikeThisVlog']; + setState(() {}); } logger.i('点赞返回信息----------->: $res'); } catch (e) { @@ -770,7 +777,8 @@ class _RecommendModuleState extends State { ), context: context, builder: (context) { - return CommentBottomSheet( + return SafeArea( + child: CommentBottomSheet( videoId: videoId, onCommentCountChanged: (newCount) { // 更新对应视频的评论数量 @@ -779,7 +787,9 @@ class _RecommendModuleState extends State { videoList[index]['commentsCounts'] = newCount; } }); - }); + }, + ), + ); }, ); } @@ -1132,9 +1142,14 @@ class _RecommendModuleState extends State { width: 48.0, child: GestureDetector( onTap: () async { + //跳转前先检测是不是自己 + final vloggerId = videoList[videoModuleController.videoPlayIndex.value]['memberId']; + final myID = Get.find().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) { // 处理返回的参数 @@ -1180,10 +1195,16 @@ class _RecommendModuleState extends State { ), onTap: () async { if (!Common.isLogin()) { - Get.toNamed('/login'); - return; + Get.toNamed('/login'); + return; } final vlogerId = videoList[index]['memberId']; + //不能关注自己 + final myID = Get.find().userID.value; + if (myID == vlogerId) { + MyToast().tip(title: '不能关注自己'); + return; + } final doIFollowVloger = videoList[index]['doIFollowVloger']; // 未关注点击才去关注 if (doIFollowVloger == false) { @@ -1217,8 +1238,8 @@ class _RecommendModuleState extends State { onTap: () { logger.d('点击了点赞按钮${videoList[index]['doILikeThisVlog']}'); if (!Common.isLogin()) { - Get.toNamed('/login'); - return; + Get.toNamed('/login'); + return; } if (videoList[index]['doILikeThisVlog'] == true) { logger.d('点击了点赞按钮${videoList[index]['doILikeThisVlog']}'); @@ -1245,8 +1266,8 @@ class _RecommendModuleState extends State { ), onTap: () { if (!Common.isLogin()) { - Get.toNamed('/login'); - return; + Get.toNamed('/login'); + return; } handleComment(index); }, @@ -1264,8 +1285,8 @@ class _RecommendModuleState extends State { ), onTap: () { if (!Common.isLogin()) { - Get.toNamed('/login'); - return; + Get.toNamed('/login'); + return; } handleShare(index); }, diff --git a/lib/router/index.dart b/lib/router/index.dart index 0c96b78..34872ef 100644 --- a/lib/router/index.dart +++ b/lib/router/index.dart @@ -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 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 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(), diff --git a/lib/service/http_config.dart b/lib/service/http_config.dart index 15f8b93..4b86b35 100644 --- a/lib/service/http_config.dart +++ b/lib/service/http_config.dart @@ -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), diff --git a/lib/utils/deep_link_listener.dart b/lib/utils/deep_link_listener.dart new file mode 100644 index 0000000..4ffcc45 --- /dev/null +++ b/lib/utils/deep_link_listener.dart @@ -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 createState() => _DeepLinkListenerState(); +} + +class _DeepLinkListenerState extends State { + 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; + } +} diff --git a/lib/utils/download_video.dart b/lib/utils/download_video.dart index 565a1ae..0bce899 100644 --- a/lib/utils/download_video.dart +++ b/lib/utils/download_video.dart @@ -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 { @@ -53,4 +55,4 @@ class DownloadManager { static String _getSafeFileName(String fileName) { return fileName.replaceAll(RegExp(r'[<>:"/\\|?*]'), '_'); } -} \ No newline at end of file +} diff --git a/lib/utils/parse_message_summary.dart b/lib/utils/parse_message_summary.dart index ad902f1..2fd5247 100644 --- a/lib/utils/parse_message_summary.dart +++ b/lib/utils/parse_message_summary.dart @@ -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: @@ -275,22 +274,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] diff --git a/lib/utils/scan_code_type.dart b/lib/utils/scan_code_type.dart index bd3c72e..7e4d7d7 100644 --- a/lib/utils/scan_code_type.dart +++ b/lib/utils/scan_code_type.dart @@ -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'); // 推广码 diff --git a/lib/utils/wechat_business_view.dart b/lib/utils/wechat_business_view.dart new file mode 100644 index 0000000..b7e0c68 --- /dev/null +++ b/lib/utils/wechat_business_view.dart @@ -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 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; + } + } +} diff --git a/lib/utils/wxsdk.dart b/lib/utils/wxsdk.dart index 4119614..4a6b6cc 100644 --- a/lib/utils/wxsdk.dart +++ b/lib/utils/wxsdk.dart @@ -43,6 +43,7 @@ class Wxsdk { "clientId": "428a8310cd442757ae699df5d894f051", "grantType": "social" }); + logger.e(serverRes); final info = Get.find(); info.customInfo['openId'] = serverRes['data']['openId']; info.updateOpenId(); @@ -63,6 +64,8 @@ class Wxsdk { final ctl = Get.find(); // 刷新记录 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); } diff --git a/pubspec.lock b/pubspec.lock index f185a7a..b7b4c39 100644 --- a/pubspec.lock +++ b/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: @@ -529,10 +561,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 +637,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: @@ -1682,6 +1722,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: diff --git a/pubspec.yaml b/pubspec.yaml index c64eb30..a6676fe 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -85,7 +85,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 +101,8 @@ 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 dev_dependencies: flutter_launcher_icons: ^0.13.1 # 使用最新版本 @@ -188,21 +191,23 @@ 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" - image: assets/images/logo/start.png + 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 + background_image: assets/images/logo/start.png + # color_dark: "#ffffff" + # image_dark: assets/images/logo/start.png android_12: - image: assets/images/logo/logo.png + image: assets/images/logo/androidlogo.png icon_background_color: "#000000" - # image_dark: assets/images/logo/start.png - # icon_background_color_dark: "#000000" + # background_image: assets/images/logo/start.png + + image_dark: assets/images/logo/androidlogo.png + icon_background_color_dark: "#000000" web: false \ No newline at end of file