This commit is contained in:
abu 2025-08-21 10:50:38 +08:00
parent 430e23daf7
commit 078cf90efe
76 changed files with 13043 additions and 1619 deletions

View File

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
assets/images/bk.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

BIN
assets/images/notify/dd.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1755509146760" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5991" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M906.66183476 910.82713117H117.33816524c-20.82648205 0-39.57031588-10.41324101-49.9835569-29.15707488s-10.41324101-39.57031588 0-57.27282562l394.66183476-683.10861105c10.41324101-17.70250974 29.15707487-29.15707487 49.9835569-29.15707487s39.57031588 10.41324101 49.9835569 29.15707487l394.66183476 683.10861105c10.41324101 17.70250974 10.41324101 39.57031588 0 57.27282562s-29.15707487 29.15707487-49.9835569 29.15707488zM512 144.41259189c-9.37191692 0-17.70250974 5.2066205-22.90913024 13.53721338l-394.66183475 683.10861102c-5.2066205 8.33059282-5.2066205 17.70250974 0 26.03310256s13.53721332 13.53721332 22.90913023 13.53721332h789.32366952c9.37191692 0 17.70250974-5.2066205 22.90913023-12.49588922 5.2066205-8.33059282 5.2066205-17.70250974 0-26.03310254l-394.66183475-683.10861104c-5.2066205-10.41324101-13.53721332-14.57853743-22.90913024-14.57853748z" p-id="5992" fill="#ffffff"></path><path d="M512 620.29770663c-8.33059282 0-15.61986151-7.2892687-15.61986151-15.61986152v-239.50454351c0-8.33059282 7.2892687-15.61986151 15.61986151-15.61986155s15.61986151 7.2892687 15.61986151 15.61986155v239.50454351c0 8.33059282-7.2892687 15.61986151-15.61986151 15.61986152zM512 768.16572913c-8.33059282 0-15.61986151-7.2892687-15.61986151-15.61986154v-62.47944612c0-8.33059282 7.2892687-15.61986151 15.61986151-15.61986152s15.61986151 7.2892687 15.61986151 15.61986152v62.47944612c0 8.33059282-7.2892687 15.61986151-15.61986151 15.61986154z" p-id="5993" fill="#ffffff"></path></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -1,9 +1,11 @@
# Uncomment this line to define a global platform for your project
# platform :ios, '12.0'
platform :ios, '12.0'
# 允许拉取http资源
# ENV['COCOAPODS_ALLOW_INSECURE_SOURCES'] = 'true'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,

View File

@ -1,11 +1,25 @@
PODS:
- audioplayers_darwin (0.0.1):
- Flutter
- FlutterMacOS
- device_info_plus (0.0.1):
- Flutter
- Flutter (1.0.0)
- flutter_image_compress_common (1.0.0):
- Flutter
- Mantle
- SDWebImage
- SDWebImageWebPCoder
- flutter_native_splash (2.4.3):
- Flutter
- flutter_upgrader (1.0.7):
- Flutter
- fluwx (0.0.1):
- Flutter
- fluwx/pay (= 0.0.1)
- fluwx/pay (0.0.1):
- Flutter
- WechatOpenSDK-XCFramework (~> 2.0.4)
- geolocator_apple (1.2.0):
- Flutter
- FlutterMacOS
@ -14,6 +28,21 @@ 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/webp
- libwebp/mux (1.3.2):
- libwebp/demux
- libwebp/sharpyuv (1.3.2)
- libwebp/webp (1.3.2):
- libwebp/sharpyuv
- Mantle (2.2.0):
- Mantle/extobjc (= 2.2.0)
- Mantle/extobjc (2.2.0)
- media_kit_libs_ios_video (1.0.4):
- Flutter
- media_kit_video (0.0.1):
@ -31,26 +60,47 @@ PODS:
- photo_manager (3.7.1):
- Flutter
- FlutterMacOS
- record_ios (1.0.0):
- Flutter
- SDWebImage (5.20.0):
- SDWebImage/Core (= 5.20.0)
- SDWebImage/Core (5.20.0)
- SDWebImageWebPCoder (0.14.6):
- libwebp (~> 1.0)
- SDWebImage/Core (~> 5.17)
- tencent_cloud_chat_push (8.6.7019):
- Flutter
- TIMPush (= 8.6.7019)
- TXIMSDK_Plus_iOS_XCFramework
- tencent_cloud_chat_sdk (8.0.0):
- Flutter
- HydraAsync
- TXIMSDK_Plus_iOS_XCFramework (~> 8.6.7019)
- TIMPush (8.6.7019):
- TXIMSDK_Plus_iOS_XCFramework (>= 8.6.7019)
- TXIMSDK_Plus_iOS_XCFramework (8.6.7019)
- url_launcher_ios (0.0.1):
- Flutter
- video_player_avfoundation (0.0.1):
- Flutter
- FlutterMacOS
- video_thumbnail (0.0.1):
- Flutter
- libwebp
- volume_controller (0.0.1):
- Flutter
- wakelock_plus (0.0.1):
- Flutter
- WechatOpenSDK-XCFramework (2.0.4)
DEPENDENCIES:
- audioplayers_darwin (from `.symlinks/plugins/audioplayers_darwin/darwin`)
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- Flutter (from `Flutter`)
- flutter_image_compress_common (from `.symlinks/plugins/flutter_image_compress_common/ios`)
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
- flutter_upgrader (from `.symlinks/plugins/flutter_upgrader/ios`)
- fluwx (from `.symlinks/plugins/fluwx/ios`)
- geolocator_apple (from `.symlinks/plugins/geolocator_apple/darwin`)
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
- install_plugin (from `.symlinks/plugins/install_plugin/ios`)
@ -61,26 +111,41 @@ DEPENDENCIES:
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- photo_manager (from `.symlinks/plugins/photo_manager/ios`)
- record_ios (from `.symlinks/plugins/record_ios/ios`)
- tencent_cloud_chat_push (from `.symlinks/plugins/tencent_cloud_chat_push/ios`)
- tencent_cloud_chat_sdk (from `.symlinks/plugins/tencent_cloud_chat_sdk/ios`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`)
- video_thumbnail (from `.symlinks/plugins/video_thumbnail/ios`)
- volume_controller (from `.symlinks/plugins/volume_controller/ios`)
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
SPEC REPOS:
trunk:
- HydraAsync
- libwebp
- Mantle
- SDWebImage
- SDWebImageWebPCoder
- TIMPush
- TXIMSDK_Plus_iOS_XCFramework
- WechatOpenSDK-XCFramework
EXTERNAL SOURCES:
audioplayers_darwin:
:path: ".symlinks/plugins/audioplayers_darwin/darwin"
device_info_plus:
:path: ".symlinks/plugins/device_info_plus/ios"
Flutter:
:path: Flutter
flutter_image_compress_common:
:path: ".symlinks/plugins/flutter_image_compress_common/ios"
flutter_native_splash:
:path: ".symlinks/plugins/flutter_native_splash/ios"
flutter_upgrader:
:path: ".symlinks/plugins/flutter_upgrader/ios"
fluwx:
:path: ".symlinks/plugins/fluwx/ios"
geolocator_apple:
:path: ".symlinks/plugins/geolocator_apple/darwin"
image_picker_ios:
@ -101,26 +166,37 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/permission_handler_apple/ios"
photo_manager:
:path: ".symlinks/plugins/photo_manager/ios"
record_ios:
:path: ".symlinks/plugins/record_ios/ios"
tencent_cloud_chat_push:
:path: ".symlinks/plugins/tencent_cloud_chat_push/ios"
tencent_cloud_chat_sdk:
:path: ".symlinks/plugins/tencent_cloud_chat_sdk/ios"
url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios"
video_player_avfoundation:
:path: ".symlinks/plugins/video_player_avfoundation/darwin"
video_thumbnail:
:path: ".symlinks/plugins/video_thumbnail/ios"
volume_controller:
:path: ".symlinks/plugins/volume_controller/ios"
wakelock_plus:
:path: ".symlinks/plugins/wakelock_plus/ios"
SPEC CHECKSUMS:
audioplayers_darwin: 4f9ca89d92d3d21cec7ec580e78ca888e5fb68bd
device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_image_compress_common: 1697a328fd72bfb335507c6bca1a65fa5ad87df1
flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
flutter_upgrader: 16a975eb987fc210cdf6bebffe0069a480f80523
fluwx: 6bf9c5a3a99ad31b0de137dd92370a0d10a60f4b
geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e
HydraAsync: 8d589bd725b0224f899afafc9a396327405f8063
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
install_plugin: e17e38d6f504857748a3ec1299d8a2bbeeeea854
libwebp: 1786c9f4ff8a279e4dac1e8f385004d5fc253009
Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d
media_kit_libs_ios_video: 5a18affdb97d1f5d466dc79988b13eff6c5e2854
media_kit_video: 1746e198cb697d1ffb734b1d05ec429d1fcd1474
mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93
@ -128,13 +204,20 @@ SPEC CHECKSUMS:
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
photo_manager: 1d80ae07a89a67dfbcae95953a1e5a24af7c3e62
record_ios: fee1c924aa4879b882ebca2b4bce6011bcfc3d8b
SDWebImage: 73c6079366fea25fa4bb9640d5fb58f0893facd8
SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380
tencent_cloud_chat_push: f87ae58098c2062b06e81f39fc53afc528395916
tencent_cloud_chat_sdk: 0a406f1854a65aad2f853494c02a2e084a027ab2
TIMPush: d0dfe96355ee413a7cacb2576f8aaa66f6073ab2
TXIMSDK_Plus_iOS_XCFramework: cb54f7de6e30e1368c6831c6eff31c25393bbb98
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
video_player_avfoundation: 2cef49524dd1f16c5300b9cd6efd9611ce03639b
video_thumbnail: b637e0ad5f588ca9945f6e2c927f73a69a661140
volume_controller: 3657a1f65bedb98fa41ff7dc5793537919f31b12
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
WechatOpenSDK-XCFramework: 36fb2bea0754266c17184adf4963d7e6ff98b69f
PODFILE CHECKSUM: 4305caec6b40dde0ae97be1573c53de1882a07e5
PODFILE CHECKSUM: 866435f3a12ad92d8fb66fa46b52776da7e16ce5
COCOAPODS: 1.16.2

View File

@ -15,6 +15,8 @@
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, ); }; };
ECDFBB33253E89949730F7D8 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 898AE91CA73F2F6E910D884D /* Pods_Runner.framework */; };
/* End PBXBuildFile section */
@ -35,6 +37,7 @@
dstPath = "";
dstSubfolderSpec = 10;
files = (
C8092B222E34A78000D25A0B /* WechatOpenSDK-XCFramework.xcframework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
@ -63,6 +66,8 @@
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
A9774DDA95C7FD895F6925A4 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
BB84C2FA9C50ACAF0C376254 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
C8092B202E34A78000D25A0B /* WechatOpenSDK-XCFramework.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = "WechatOpenSDK-XCFramework.xcframework"; path = "Pods/WechatOpenSDK-XCFramework/WechatOpenSDK-XCFramework.xcframework"; sourceTree = "<group>"; };
C891EF1D2E43F9730021EB39 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; };
DCA23AF172275D04ECB63EFB /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
E3EC116A6CCDD06C6D4615E2 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
@ -72,6 +77,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
C8092B212E34A78000D25A0B /* WechatOpenSDK-XCFramework.xcframework in Frameworks */,
ECDFBB33253E89949730F7D8 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -143,6 +149,7 @@
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
C891EF1D2E43F9730021EB39 /* Runner.entitlements */,
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
@ -158,6 +165,7 @@
9CE4C2341F34F9A85A1D3EED /* Frameworks */ = {
isa = PBXGroup;
children = (
C8092B202E34A78000D25A0B /* WechatOpenSDK-XCFramework.xcframework */,
898AE91CA73F2F6E910D884D /* Pods_Runner.framework */,
1AE799326ED7557212A901E0 /* Pods_RunnerTests.framework */,
);
@ -487,8 +495,11 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = VZ6V44Q3T4;
DEVELOPMENT_TEAM = 9C9VWBX77X;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
@ -496,8 +507,9 @@
"@executable_path/Frameworks",
);
MARKETING_VERSION = 4.1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.wzj41.test1;
PRODUCT_BUNDLE_IDENTIFIER = cn.net.wzj.mall;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
@ -671,8 +683,11 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = VZ6V44Q3T4;
DEVELOPMENT_TEAM = 9C9VWBX77X;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
@ -680,8 +695,9 @@
"@executable_path/Frameworks",
);
MARKETING_VERSION = 4.1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.wzj41.test1;
PRODUCT_BUNDLE_IDENTIFIER = cn.net.wzj.mall;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
@ -695,8 +711,11 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = VZ6V44Q3T4;
DEVELOPMENT_TEAM = 9C9VWBX77X;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
@ -704,8 +723,9 @@
"@executable_path/Frameworks",
);
MARKETING_VERSION = 4.1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.wzj41.test1;
PRODUCT_BUNDLE_IDENTIFIER = cn.net.wzj.mall;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";

View File

@ -1,13 +1,39 @@
import Flutter
import UIKit
import Flutter
@main
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
// Add these two import lines
import TIMPush
import tencent_cloud_chat_push
// Add `, TIMPushDelegate` to the following line
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate, TIMPushDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
// To be deprecatedplease use the new field businessID below.
@objc func offlinePushCertificateID() -> Int32 {
return TencentCloudChatPushFlutterModal.shared.offlinePushCertificateID();
}
// Add this function
@objc func businessID() -> Int32 {
return TencentCloudChatPushFlutterModal.shared.businessID();
}
// Add this function
@objc func applicationGroupID() -> String {
return TencentCloudChatPushFlutterModal.shared.applicationGroupID()
}
// Add this function
@objc func onRemoteNotificationReceived(_ notice: String?) -> Bool {
TencentCloudChatPushPlugin.shared.tryNotifyDartOnNotificationClickEvent(notice)
return true
}
}

View File

@ -1,8 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22685"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
@ -14,13 +16,14 @@
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-16" y="-40"/>
</scene>
</scenes>
</document>

View File

@ -7,7 +7,7 @@
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>无终见41</string>
<string>无终</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
@ -22,20 +22,44 @@
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>weixin</string>
<key>CFBundleURLSchemes</key>
<array>
<string>wxebcdaea31881caab</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>weixin</string>
<string>wechat</string>
<string>weixinULAPI</string>
</array>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>NSCameraUsageDescription</key>
<string>App需要使用您的相机进行拍摄</string>
<key>NSMicrophoneUsageDescription</key>
<string>App需要访问麦克风用于视频录制</string>
<string>App需要访问麦克风用于发送语音消息</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>App需要权限以保存视频到您的相册</string>
<string>App需要权限以保存图片或视频到您的相册</string>
<key>NSPhotoLibraryLimitedUsageDescription</key>
<string>App需要访问部分照片用于选择视频</string>
<string>App需要访问部分照片用于选择图片或视频</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>App需要访问您的相册用于选择视频</string>
<string>App需要访问您的相册用于选择图片或视频</string>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchStoryboardName</key>
@ -59,5 +83,15 @@
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>UIBackgroundModes</key>
<array>
<string>remote-notification</string>
</array>
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:wuzhongjie.com.cn</string>
</array>
</dict>
</plist>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>aps-environment</key>
<string>development</string>
<!-- <string>production</string> -->
</dict>
</plist>

View File

@ -1,24 +1,118 @@
import 'package:get/get.dart';
import 'package:loopin/IM/im_service.dart';
import 'package:loopin/models/conversation_type.dart' as myConversationType;
import 'package:loopin/models/conversation_view_model.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation_filter.dart';
class ChatController extends GetxController {
RxInt count = 100.obs; //
RxInt count = 20.obs; //
RxString nextSeq = '0'.obs; //
RxBool isFinished = false.obs; //
final chatList = <ConversationViewModel>[].obs;
//
void initChatData() {
chatList.value = <ConversationViewModel>[];
nextSeq.value = '0';
isFinished.value = false;
}
//
void getConversationList() async {
if (isFinished.value) {
//
return;
}
final res = await ImService.instance.getConversationList(nextSeq.value, count.value);
if (!res.success || res.data == null) return;
final List<ConversationViewModel> convList = res.data;
// for (var conv in convList) {
// logger.i('基本会话: ${conv.conversation.toLogString()}, 头像: ${conv.faceUrl}');
// }
for (var conv in convList) {
logger.i('基本会话: ${conv.conversation.toJson()}, 会话ID: ${conv.conversation.conversationID}');
}
chatList.value = convList;
chatList.addAll(convList);
// noFriend才执行加载数据逻辑,
final hasNoFriend = chatList.any((item) => item.conversation.conversationGroupList?.contains(myConversationType.ConversationType.noFriend.name) ?? false);
if (!hasNoFriend) {
getNoFriendData();
}
}
///
void getNoFriendData({V2TimConversation? csion}) async {
//
final hasNoFriend = chatList.any((item) => item.conversation.conversationGroupList?.contains(myConversationType.ConversationType.noFriend.name) ?? false);
if (hasNoFriend) {
//
final ConversationViewModel matchItem = chatList.firstWhere(
(item) => item.conversation.conversationGroupList?.contains(myConversationType.ConversationType.noFriend.name) ?? false,
);
//
final unreadTotal = await ImService.instance.getUnreadMessageCountByFilter(
filter: V2TimConversationFilter(
conversationGroup: myConversationType.ConversationType.noFriend.name,
hasUnreadCount: true,
),
);
matchItem.conversation.lastMessage = csion!.lastMessage;
matchItem.conversation.unreadCount = unreadTotal.data;
chatList.refresh();
return;
}
//
final res = await ImService.instance.getConversationListByFilter(
filter: V2TimConversationFilter(conversationGroup: myConversationType.ConversationType.noFriend.name),
nextSeq: 0,
count: 1,
);
if (res.success && res.data != null) {
final convList = res.data!.conversationList ?? [];
if (convList.isNotEmpty) {
// logger.i(res.data!.toJson());
// 1.2.converstaionviewmodel
final unread = await ImService.instance.getUnreadMessageCountByFilter(
filter: V2TimConversationFilter(
conversationGroup: myConversationType.ConversationType.noFriend.name,
hasUnreadCount: true,
),
);
if (unread.success) {
final conv = convList.first;
final faceUrl = 'assets/images/notify/msr.png';
conv.showName = '陌生人消息';
conv.unreadCount = unread.data;
final createItem = ConversationViewModel(
conversation: conv,
faceUrl: faceUrl,
);
final newList = List<ConversationViewModel>.from(chatList);
newList.add(createItem);
newList.sort((a, b) {
final atime = a.conversation.lastMessage?.timestamp ?? 0;
final btime = b.conversation.lastMessage?.timestamp ?? 0;
return btime.compareTo(atime); //
});
chatList.value = newList;
}
}
}
}
/// getConversationListByFilter
// void getConversationList() async {
// final res = await ImService.instance.getConversationListByFilter(
// filter: V2TimConversationFilter(conversationGroup: null),
// nextSeq: nextSeq.value,
// );
// final convList = res.data!.conversationList;
// logger.i(res.data!.toJson());
// chatList.value = convList;
// // for (var element in convList ?? []) {
// // logger.i(element.toJson());
// // // element
// // }
// }
}

View File

@ -1,3 +1,4 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:loopin/IM/im_message.dart';
import 'package:loopin/IM/im_service.dart';
@ -8,8 +9,10 @@ class ChatDetailController extends GetxController {
final String userID;
ChatDetailController({required this.userID});
final ScrollController chatController = ScrollController();
final RxList<V2TimMessage> chatList = <V2TimMessage>[].obs;
final RxBool isFriend = true.obs;
void updateChatListWithTimeLabels(List<V2TimMessage> originMessages) async {
final idRes = await ImService.instance.selfUserId();
@ -37,17 +40,54 @@ class ChatDetailController extends GetxController {
}
}
//
displayMessages.add(current);
// if (i == 0) {
// //
// needInsertLabel = true;
// } else {
// final prev = originMessages[i - 1];
// final prevTimestamp = prev.timestamp ?? 0;
// final diff = currentTimestamp - prevTimestamp;
// if (diff > 180) {
// needInsertLabel = true;
// }
// }
// label后插入
displayMessages.add(current);
if (needInsertLabel) {
final labelTime = Utils().formatChatTime(currentTimestamp);
final timeLabel = await IMMessage().insertTimeLabel(labelTime, selfUserId);
displayMessages.add(timeLabel.data);
}
}
//
chatList.addAll(displayMessages);
}
}
///
void scrollToBottom() {
if (chatController.hasClients) {
chatController.animateTo(
0,
duration: Duration(milliseconds: 200),
curve: Curves.easeOut,
);
}
// Future.delayed(Duration(milliseconds: 300), () {
// if (chatController.hasClients) {
// chatController.animateTo(
// chatController.position.maxScrollExtent,
// duration: Duration(milliseconds: 200),
// curve: Curves.easeOut,
// );
// }
// });
}
@override
void onClose() {
chatController.dispose();
super.onClose();
}
}

View File

@ -0,0 +1,137 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:loopin/IM/im_service.dart';
import 'package:shirne_dialog/shirne_dialog.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_user_full_info.dart';
class ImUserInfoController extends GetxController {
@override
void onInit() {
super.onInit();
refreshUserInfo();
logger.i('IM用户信息初始化');
}
V2TimUserFullInfo? rawUserInfo;
final userID = ''.obs;
final nickname = ''.obs;
final faceUrl = ''.obs;
final signature = ''.obs;
final gender = 0.obs;
final allowType = 0.obs;
final customInfo = <String, String>{
"coverBg": "",
"area": "",
"areaCode": "",
"openId": "",
}.obs;
final role = 0.obs;
final level = 0.obs;
final birthday = 0.obs;
void init(V2TimUserFullInfo userInfo) {
logger.i(userInfo.toJson());
rawUserInfo = userInfo;
userID.value = userInfo.userID ?? '';
nickname.value = userInfo.nickName ?? '';
faceUrl.value = userInfo.faceUrl ?? '';
signature.value = userInfo.selfSignature ?? '';
gender.value = userInfo.gender ?? 0;
allowType.value = userInfo.allowType ?? 0;
customInfo.assignAll(userInfo.customInfo ??
{
"coverBg": "",
"area": "",
"areaCode": "",
"openId": "",
});
role.value = userInfo.role ?? 0;
level.value = userInfo.level ?? 0;
birthday.value = userInfo.birthday ?? 0;
}
void refreshUserInfo() async {
try {
final updatedUserInfo = await ImService.instance.selfInfo();
if (updatedUserInfo.success) {
init(updatedUserInfo.data);
}
} catch (e) {
print('刷新用户信息失败: $e');
}
}
///
Future<bool> updateNickname(newnickname) async {
final res = await ImService.instance.setSelfInfo(userFullInfo: V2TimUserFullInfo(nickName: newnickname));
if (res.success) {
nickname.value = newnickname;
} else {
logger.i(res.desc);
if (res.code == 80001) {
MyDialog.toast('昵称违规', icon: Icon(Icons.warning), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200)));
}
}
return res.success;
}
///
Future<bool> updateSignature(newsignature) async {
final res = await ImService.instance.setSelfInfo(userFullInfo: V2TimUserFullInfo(selfSignature: newsignature));
if (res.success) {
signature.value = newsignature;
} else {
logger.i(res.desc);
if (res.code == 80001) {
MyDialog.toast('简介内容违规', icon: Icon(Icons.warning), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200)));
}
}
return res.success;
}
///
Future<void> updateFaceUrl() async {
if (faceUrl.value.trim().isEmpty) return;
await ImService.instance.setSelfInfo(userFullInfo: V2TimUserFullInfo(faceUrl: faceUrl.value));
}
///
Future<void> updateCover() async {
final coverBg = customInfo['coverBg'];
if (coverBg == null || coverBg.trim().isEmpty) return;
await ImService.instance.setSelfInfo(userFullInfo: V2TimUserFullInfo(customInfo: customInfo));
}
/// openId
Future<void> updateOpenId() async {
final openId = customInfo['openId'];
if (openId == null || openId.trim().isEmpty) return;
await ImService.instance.setSelfInfo(userFullInfo: V2TimUserFullInfo(customInfo: customInfo));
}
// customInfo.update("coverBg", (value) => coverBgUrl);
///
Future<void> updateArea() async {
final area = customInfo['area'];
if (area == null || area.trim().isEmpty) return;
final areaCode = customInfo['areaCode'];
if (areaCode == null || areaCode.trim().isEmpty) return;
await ImService.instance.setSelfInfo(userFullInfo: V2TimUserFullInfo(customInfo: customInfo));
}
///
Future<void> updateBirthday() async {
if (birthday.value < 0) return;
await ImService.instance.setSelfInfo(userFullInfo: V2TimUserFullInfo(birthday: birthday.value));
}
///
Future<void> updateGender() async {
if (gender.value < 0) return;
await ImService.instance.setSelfInfo(userFullInfo: V2TimUserFullInfo(gender: gender.value));
}
/// updateAvatarupdateSignature
}

View File

@ -1,9 +1,12 @@
import 'package:get/get.dart';
import 'package:loopin/IM/controller/chat_controller.dart';
import 'package:loopin/IM/controller/tab_bar_controller.dart';
import 'package:loopin/IM/im_core.dart';
import 'package:loopin/IM/im_service.dart';
import 'package:loopin/models/conversation_type.dart';
import 'package:loopin/models/tab_type.dart';
import 'package:tencent_cloud_chat_sdk/enum/V2TimConversationListener.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation_filter.dart';
import 'package:tencent_cloud_chat_sdk/tencent_im_sdk_plugin.dart';
class GlobalBadge extends GetxController {
@ -21,14 +24,103 @@ class GlobalBadge extends GetxController {
logger.i('未读数发生变化$count');
totalUnread.value = count;
Get.find<TabBarController>().setBadge(TabType.chat, totalUnread.value);
//
Get.find<ChatController>().getConversationList();
},
onNewConversation: (List<V2TimConversation> conversationList) {
for (var conv in conversationList) {
logger.i("新会话创建:${conv.conversationGroupList}");
handleCoverstion(conv);
}
},
onConversationChanged: (List<V2TimConversation> conversationList) async {
logger.w('会话变更:会话分组:${conversationList.first.conversationGroupList},会话内容${conversationList.first.toLogString()}');
final ctl = Get.find<ChatController>();
final updatedIds = conversationList.map((e) => e.conversationID).toSet();
logger.w('要变更的会话id$updatedIds');
for (int i = 0; i < ctl.chatList.length; i++) {
final chatItem = ctl.chatList[i];
logger.w('需要更新的ID:${chatItem.conversation.conversationID}');
if (updatedIds.contains(chatItem.conversation.conversationID)) {
final updatedConv = conversationList.firstWhere(
(c) => c.conversationID == chatItem.conversation.conversationID,
orElse: () => V2TimConversation(conversationID: ''),
);
if (updatedConv.conversationID != '' && (updatedConv.conversationGroupList?.contains(ConversationType.noFriend.name) ?? false)) {
//
final unread = await ImService.instance.getUnreadMessageCountByFilter(
filter: V2TimConversationFilter(
conversationGroup: ConversationType.noFriend.name,
hasUnreadCount: true,
),
);
chatItem.conversation.lastMessage = updatedConv.lastMessage;
chatItem.conversation.unreadCount = unread.data; //
} else {
//
chatItem.conversation = updatedConv;
}
}
}
//
ctl.chatList.sort((a, b) {
final atime = a.conversation.lastMessage?.timestamp ?? 0;
final btime = b.conversation.lastMessage?.timestamp ?? 0;
return btime.compareTo(atime); //
});
ctl.chatList.refresh();
},
);
final ctl = Get.find<ChatController>();
ctl.getConversationList();
_initUnreadCount();
_addListener();
}
// final rr = await ImService.instance.deleteConversationsFromGroup(
// conversationIDList: [cov.conversationID],
// groupName: 'noFriend',
// );
// logger.w(rr.desc);
/// ,
void handleCoverstion(V2TimConversation cov) async {
final message = cov.lastMessage;
final isSelfSend = message!.isSelf; //
final typeEnum = conversationTypeFromString(message.cloudCustomData); //
final needAdd = cov.conversationGroupList!.isEmpty == true; //
if (typeEnum != null && needAdd && isSelfSend == false) {
logger.i('当前会话的类型要加入的组是:$typeEnum');
//
final hasGroupRes = await ImService.instance.getConversationGroupList();
if (hasGroupRes.success) {
final exists = hasGroupRes.data?.any((item) => item == typeEnum) ?? false;
if (!exists) {
// group中
await ImService.instance.createConversationGroup(
groupName: typeEnum,
conversationIDList: ['c2c_${message.sender}'],
);
logger.i('首次创建会话分组$typeEnum');
} else {
//
await ImService.instance.addConversationsToGroup(
groupName: typeEnum,
conversationIDList: ['c2c_${message.sender}'],
);
logger.i('添加会话分组$typeEnum成功');
}
if (typeEnum == ConversationType.noFriend.name) {
// ,
final ctl = Get.find<ChatController>();
//
ctl.getNoFriendData(csion: cov);
}
}
} else {
logger.w('不需要进行分组');
}
}
///
void _initUnreadCount() async {
final res = await TencentImSDKPlugin.v2TIMManager.getConversationManager().getTotalUnreadMessageCount();

View File

@ -14,14 +14,16 @@ class ImCore {
final res = await TencentImSDKPlugin.v2TIMManager.initSDK(
sdkAppID: sdkAppId,
loglevel: LogLevelEnum.V2TIM_LOG_ALL,
loglevel: LogLevelEnum.V2TIM_LOG_ERROR,
listener: V2TimSDKListener(
onConnectSuccess: () => logger.i("IM连接成功"),
onConnectSuccess: () {
logger.i("IM连接成功");
},
onConnectFailed: (code, error) => logger.e("IM连接失败: $code $error"),
onKickedOffline: () => logger.w("IM被踢下线"),
onUserSigExpired: () => logger.w("UserSig 过期"),
onSelfInfoUpdated: (V2TimUserFullInfo info) {
logger.i("用户信息更新: ${info.nickName}");
logger.i("用户信息更新: ${info.toJson()}");
},
),
);
@ -29,6 +31,7 @@ class ImCore {
if (res.code == 0) {
_isInitialized = true;
logger.i("IM SDK 初始化成功");
return true;
} else {
logger.e("IM SDK 初始化失败: ${res.code} - ${res.desc}");

View File

@ -1,4 +1,5 @@
import 'package:logger/logger.dart';
import 'package:loopin/utils/notification_banner.dart';
import 'package:tencent_cloud_chat_sdk/enum/V2TimFriendshipListener.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_friend_application.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_friend_info.dart';
@ -16,7 +17,6 @@ class ImFriendListeners {
_listener = V2TimFriendshipListener(onFriendApplicationListAdded: (List<V2TimFriendApplication> list) async {
//
//applicationList
logger.i('收到好友申请: ${list.map((e) => e.userID).join(",")}');
}, onFriendApplicationListRead: () async {
//
}, onFriendApplicationListDeleted: (List<String> userIDList) async {
@ -25,11 +25,15 @@ class ImFriendListeners {
}, onFriendListAdded: (List<V2TimFriendInfo> users) async {
//
//users
logger.i('新增好友: ${users.map((u) => u.userID).join(",")}');
for (var item in users) {
logger.i('新增好友:${item.toLogString()}');
}
}, onFriendListDeleted: (List<String> userList) async {
//
//userList id列表
logger.i('删除好友: ${userList.join(",")}');
for (var item in userList) {
logger.i('新增好友:$item');
}
}, onFriendInfoChanged: (List<V2TimFriendInfo> list) async {
//
//infoList
@ -43,14 +47,27 @@ class ImFriendListeners {
}, onMyFollowingListChanged: (List<V2TimUserFullInfo> userInfoList, bool isAdd) async {
if (isAdd) {
//
for (var item in userInfoList) {
logger.i('我新关注的人:${item.toJson()}');
}
} else {
//
for (var item in userInfoList) {
logger.i('我取消关注了:${item.toJson()}');
}
}
}, onMyFollowersListChanged: (List<V2TimUserFullInfo> userInfoList, bool isAdd) async {
if (isAdd) {
//
for (var item in userInfoList) {
logger.i('新增粉丝:${item.toJson()}');
}
NotificationBanner.foucs(userInfoList.last);
} else {
//
for (var item in userInfoList) {
logger.i('掉粉:${item.toJson()}');
}
}
}, onMutualFollowersListChanged: (List<V2TimUserFullInfo> userInfoList, bool isAdd) async {
if (isAdd) {

View File

@ -1,20 +1,27 @@
import 'dart:convert';
import 'dart:io';
import 'package:get/get.dart';
import 'package:logger/logger.dart';
import 'package:loopin/IM/controller/im_user_info_controller.dart';
import 'package:loopin/IM/im_result.dart';
import 'package:loopin/utils/parse_message_summary.dart';
import 'package:tencent_cloud_chat_sdk/enum/message_priority_enum.dart';
import 'package:tencent_cloud_chat_sdk/enum/offlinePushInfo.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_msg_create_info_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_value_callback.dart';
import 'package:tencent_cloud_chat_sdk/tencent_im_sdk_plugin.dart';
final logger = Logger();
class IMMessage {
///
Future<ImResult> sendText({
required String text,
final logger = Logger();
/// 1.
Future<ImResult> sendMessage({
required V2TimMessage msg,
String? toUserID,
String? groupID,
String? data,
String? cloudCustomData,
}) async {
// toUserID groupID
if ((toUserID == null && groupID == null) || (toUserID != null && groupID != null)) {
@ -24,40 +31,44 @@ class IMMessage {
desc: "只能指定一个 receivertoUserID或 groupID",
);
}
//
final createRes = await TencentImSDKPlugin.v2TIMManager.getMessageManager().createTextMessage(text: text);
if (createRes.code != 0 || createRes.data == null) {
return ImResult(
success: false,
code: createRes.code,
desc: "创建消息失败",
);
if (cloudCustomData != null) {
msg.cloudCustomData = cloudCustomData;
}
final V2TimMessage? messageInfo = createRes.data?.messageInfo;
//
V2TimValueCallback<V2TimMessage> sendRes;
// final controller = Get.find<ChatDetailController>();
final myInfo = Get.find<ImUserInfoController>();
logger.w('启用默认title${myInfo.nickname.value}');
//
if (toUserID != null) {
sendRes = await TencentImSDKPlugin.v2TIMManager.getMessageManager().sendMessage(
message: messageInfo,
message: msg,
receiver: toUserID,
// onSyncMsgID: (msgID) async {
// ID
// elem
// logger.w(msg.imageElem!.toLogString());
// controller.chatList.add(msg.imageElem);
// controller.scrollToBottom();
// },
groupID: "",
priority: MessagePriorityEnum.V2TIM_PRIORITY_DEFAULT,
onlineUserOnly: false,
isExcludedFromUnreadCount: false,
isExcludedFromLastMessage: false,
needReadReceipt: false,
offlinePushInfo: OfflinePushInfo(title: "新消息", desc: text),
cloudCustomData: "",
offlinePushInfo: OfflinePushInfo(
title: myInfo.nickname.value,
desc: parseMessageSummary(msg),
ext: jsonEncode({"userID": myInfo.userID.value, "title": myInfo.nickname.value}),
),
cloudCustomData: cloudCustomData,
localCustomData: "",
);
} else {
//
sendRes = await TencentImSDKPlugin.v2TIMManager.getMessageManager().sendMessage(
message: messageInfo,
message: msg,
receiver: "",
groupID: groupID!,
priority: MessagePriorityEnum.V2TIM_PRIORITY_DEFAULT,
@ -65,7 +76,7 @@ class IMMessage {
isExcludedFromUnreadCount: false,
isExcludedFromLastMessage: false,
needReadReceipt: false,
offlinePushInfo: OfflinePushInfo(title: "新群聊消息", desc: text),
offlinePushInfo: OfflinePushInfo(title: '群聊消息', desc: parseMessageSummary(msg)),
cloudCustomData: "",
localCustomData: "",
);
@ -79,62 +90,18 @@ class IMMessage {
);
}
///
Future<ImResult> sendCustomMessage({
/// 2=
Future<ImResult<V2TimMsgCreateInfoResult>> createCustomMessage({
required String data,
String? toUserID,
String? groupID,
String? description,
String? extension,
String desc = "",
String extension = "",
}) async {
//
if ((toUserID == null && groupID == null) || (toUserID != null && groupID != null)) {
return ImResult(
success: false,
code: -1,
desc: "只能指定一个 receivertoUserID或 groupID",
);
}
// 1.
final createRes = await TencentImSDKPlugin.v2TIMManager.getMessageManager().createCustomMessage(
final res = await TencentImSDKPlugin.v2TIMManager.getMessageManager().createCustomMessage(
data: data,
desc: description ?? '',
extension: extension ?? '',
desc: desc,
extension: extension,
);
if (createRes.code != 0 || createRes.data?.id == null) {
return ImResult(
success: false,
code: createRes.code,
desc: "创建自定义消息失败",
);
}
final V2TimMessage? messageInfo = createRes.data?.messageInfo;
// 2.
final sendRes = await TencentImSDKPlugin.v2TIMManager.getMessageManager().sendMessage(
message: messageInfo,
receiver: toUserID ?? '',
groupID: groupID ?? '',
priority: MessagePriorityEnum.V2TIM_PRIORITY_DEFAULT,
onlineUserOnly: false,
isExcludedFromUnreadCount: false,
isExcludedFromLastMessage: false,
needReadReceipt: false,
offlinePushInfo: OfflinePushInfo(
title: "自定义消息",
desc: description ?? '您收到一条自定义消息',
),
cloudCustomData: "",
localCustomData: "",
);
return ImResult(
success: sendRes.code == 0,
code: sendRes.code,
desc: sendRes.desc,
);
return ImResult.wrap(res);
}
///
@ -162,24 +129,124 @@ class IMMessage {
desc: "success",
data: timeMsg,
);
}
// final sendRes = await TencentImSDKPlugin.v2TIMManager.getMessageManager().sendMessage(
// message: timeMsg,
// receiver: userId,
// groupID: "",
// onlineUserOnly: false,
// isExcludedFromUnreadCount: true,
// isExcludedFromLastMessage: true,
// needReadReceipt: false,
// cloudCustomData: "",
// localCustomData: "time_label",
// );
/// ==1
Future<ImResult<V2TimMsgCreateInfoResult>> createTextMessage({
required String text,
}) async {
final res = await TencentImSDKPlugin.v2TIMManager.getMessageManager().createTextMessage(text: text);
return ImResult.wrap(res);
}
// return ImResult(
// success: sendRes.code == 0,
// code: sendRes.code,
// desc: sendRes.code == 0 ? "时间标签发送成功" : sendRes.desc,
// data: timeMsg,
// );
/// ==3
Future<ImResult<V2TimMsgCreateInfoResult>> createImageMessage({
required String imagePath,
String? imageName,
}) async {
final fileExists = await File(imagePath).exists();
if (fileExists) {
final res = await TencentImSDKPlugin.v2TIMManager.getMessageManager().createImageMessage(
imagePath: imagePath,
imageName: imageName,
);
return ImResult.wrap(res);
} else {
//
final failed = V2TimValueCallback<V2TimMsgCreateInfoResult>.fromJson({
"code": -5,
"desc": "imagePath is not found",
"data": V2TimMsgCreateInfoResult.fromJson({}),
});
return ImResult.wrap(failed);
}
}
///==5
Future<ImResult<V2TimMsgCreateInfoResult>> createVideoMessage({
//100MB
required String videoFilePath, //
required String type, // mp4/avi==
required int duration, //
required String snapshotPath, //
}) async {
final videoExists = await File(videoFilePath).exists();
final snapshotExists = await File(snapshotPath).exists();
if (videoExists && snapshotExists) {
final res = await TencentImSDKPlugin.v2TIMManager.getMessageManager().createVideoMessage(
videoFilePath: videoFilePath,
type: type,
duration: duration,
snapshotPath: snapshotPath,
);
return ImResult.wrap(res);
}
//
final failed = V2TimValueCallback<V2TimMsgCreateInfoResult>.fromJson({
"code": -5,
"desc": "视频或首帧图缺失",
"data": V2TimMsgCreateInfoResult.fromJson({}),
});
return ImResult.wrap(failed);
}
/// ==4
Future<ImResult<V2TimMsgCreateInfoResult>> createSoundMessage({
required String soundPath,
required int duration,
String? path,
}) async {
final soundExists = await File(soundPath).exists();
if (soundExists) {
final res = await TencentImSDKPlugin.v2TIMManager.getMessageManager().createSoundMessage(
soundPath: soundPath,
duration: duration,
);
return ImResult.wrap(res);
}
final failed = V2TimValueCallback<V2TimMsgCreateInfoResult>.fromJson({
"code": -5,
"desc": "音频文件缺失",
"data": V2TimMsgCreateInfoResult.fromJson({}),
});
return ImResult.wrap(failed);
}
/// == 8
Future<ImResult<V2TimMsgCreateInfoResult>> createFaceMessage({
required int index,
required String data,
}) async {
final res = await TencentImSDKPlugin.v2TIMManager.getMessageManager().createFaceMessage(
index: index,
data: data,
);
return ImResult.wrap(res);
}
/// sendMessage()
Future<ImResult<V2TimMessage>> insertC2CMessageToLocalStorageV2({
required String userID,
required String senderID,
V2TimMessage? message,
String? createdMsgID,
}) async {
final res = await TencentImSDKPlugin.v2TIMManager.getMessageManager().insertC2CMessageToLocalStorageV2(
userID: userID,
senderID: senderID,
message: message,
createdMsgID: createdMsgID,
);
return ImResult.wrap(res);
}
}

View File

@ -8,6 +8,7 @@ import 'package:loopin/IM/im_service.dart';
import 'package:loopin/utils/index.dart';
import 'package:loopin/utils/lifecycle_handler.dart';
import 'package:loopin/utils/notification_banner.dart';
import 'package:shirne_dialog/shirne_dialog.dart';
import 'package:tencent_cloud_chat_sdk/enum/V2TimAdvancedMsgListener.dart';
import 'package:tencent_cloud_chat_sdk/enum/message_elem_type.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message.dart';
@ -15,14 +16,18 @@ import 'package:tencent_cloud_chat_sdk/models/v2_tim_message_receipt.dart';
import 'package:tencent_cloud_chat_sdk/tencent_im_sdk_plugin.dart';
class ImMessageListenerService extends GetxService {
ProgressController? _sendProgressController; //
final logger = Logger();
V2TimAdvancedMsgListener? _listener;
Timer? _debounceTimer;
///
bool needInsertTimeLabel(int lastTimestamp, int newTimestamp, {int interval = 3 * 60}) {
return (newTimestamp - lastTimestamp) > interval * 1000;
}
///
void insertTimeLabel(message) async {
//
List<V2TimMessage> messagesToInsert = [];
@ -54,21 +59,26 @@ class ImMessageListenerService extends GetxService {
messagesToInsert.add(resMsg.data);
}
messagesToInsert.insert(0, message);
// messagesToInsert.add(message);
//
chatDetailController.chatList.insertAll(0, messagesToInsert);
// chatDetailController.chatList.addAll(messagesToInsert);
//
chatDetailController.scrollToBottom();
}
///
void _handleNewMessage(V2TimMessage message) async {
final userID = message.sender ?? '';
if (userID.isEmpty) return;
//
if (Get.currentRoute == '/chat' && Get.isRegistered<ChatDetailController>()) {
///
if ((Get.currentRoute == '/chat' || Get.currentRoute == '/chatNoFriend' || Get.currentRoute == '/chatGroup') && Get.isRegistered<ChatDetailController>()) {
final chatDetailController = Get.find<ChatDetailController>();
//
if (chatDetailController.userID == userID) {
//
//
//
insertTimeLabel(message);
//
await ImService.instance.clearConversationUnreadCount(conversationID: 'c2c_$userID');
@ -142,9 +152,29 @@ class ImMessageListenerService extends GetxService {
},
onRecvMessageModified: (V2TimMessage message) {
logger.i("消息被修改: ${message.msgID}");
//
if ((Get.currentRoute == '/chat' || Get.currentRoute == '/chatNoFriend' || Get.currentRoute == '/chatGroup') &&
Get.isRegistered<ChatDetailController>()) {
final controller = Get.find<ChatDetailController>();
final index = controller.chatList.indexWhere((m) => m.msgID == message.msgID);
if (index != -1) {
final newJson = message.customElem!.data!;
controller.chatList[index].customElem!.data = newJson;
controller.chatList.refresh();
}
}
},
onSendMessageProgress: (V2TimMessage message, int progress) {
logger.i("发送中: ${message.msgID} -> $progress%");
if (progress < 100) {
_sendProgressController ??= MyDialog.loading(
"发送中...",
duration: null, //
);
} else {
_sendProgressController?.close();
_sendProgressController = null;
}
},
onRecvC2CReadReceipt: (List<V2TimMessageReceipt> receiptList) {
for (var receipt in receiptList) {
@ -159,7 +189,6 @@ class ImMessageListenerService extends GetxService {
);
TencentImSDKPlugin.v2TIMManager.getMessageManager().addAdvancedMsgListener(listener: _listener!);
logger.i("$_listener");
logger.i("高级消息监听器已注册");
return this;

View File

@ -1,3 +1,6 @@
import 'package:tencent_cloud_chat_sdk/models/v2_tim_callback.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_value_callback.dart';
class ImResult<T> {
final bool success;
final int code;
@ -10,4 +13,21 @@ class ImResult<T> {
required this.desc,
this.data,
});
static ImResult<T> wrap<T>(V2TimValueCallback<T> res) {
return ImResult(
success: res.code == 0,
code: res.code,
desc: res.desc,
data: res.data,
);
}
static ImResult<void> wrapNoData(V2TimCallback res) {
return ImResult<void>(
success: res.code == 0,
code: res.code,
desc: res.desc,
data: null,
);
}
}

View File

@ -1,19 +1,40 @@
import 'package:flutter/material.dart';
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/controller/tab_bar_controller.dart';
import 'package:loopin/IM/global_badge.dart';
import 'package:loopin/IM/im_core.dart';
import 'package:loopin/IM/im_friend_listeners.dart';
import 'package:loopin/IM/im_message_listeners.dart';
import 'package:loopin/IM/im_result.dart';
import 'package:loopin/IM/push_service.dart';
import 'package:loopin/models/conversation_view_model.dart';
import 'package:loopin/utils/wxsdk.dart';
import 'package:tencent_cloud_chat_sdk/enum/friend_application_type_enum.dart';
import 'package:tencent_cloud_chat_sdk/enum/friend_response_type_enum.dart';
import 'package:tencent_cloud_chat_sdk/enum/friend_type_enum.dart';
import 'package:tencent_cloud_chat_sdk/enum/history_msg_get_type_enum.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_callback.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation_filter.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation_operation_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_follow_info.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_follow_operation_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_follow_type_check_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_friend_info.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_friend_info_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_friend_operation_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message_change_info.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_user_full_info.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_user_info_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_value_callback.dart';
import 'package:tencent_cloud_chat_sdk/native_im/adapter/tim_conversation_manager.dart';
import 'package:tencent_cloud_chat_sdk/native_im/adapter/tim_friendship_manager.dart';
import 'package:tencent_cloud_chat_sdk/native_im/adapter/tim_message_manager.dart';
import 'package:tencent_cloud_chat_sdk/tencent_im_sdk_plugin.dart';
final logger = Logger();
@ -40,6 +61,16 @@ class ImService {
if (result.success) {
logger.i("IM 登录成功:$userID");
// push服务
PushService().initPush(
sdkAppId: 1600080789,
appKey: 'vkFpe55aYqfV7Sk5uGaoxhEstJ3tcI9dquk7JwG1GloDSLD2HeMWeQweWWXgNlhC',
);
// SDK
await Wxsdk.init();
// (+)
Get.put(ImUserInfoController(), permanent: true);
//
final messageService = ImMessageListenerService();
Get.put<ImMessageListenerService>(messageService, permanent: true);
@ -48,13 +79,11 @@ class ImService {
//
final friendListener = ImFriendListeners();
logger.i(friendListener);
Get.put<ImFriendListeners>(friendListener, permanent: true);
friendListener.register();
///
Get.put(GlobalBadge(), permanent: true);
// Get.lazyPut<GlobalBadge>(() => GlobalBadge());
} else {
logger.i("IM 登录失败:${result.code} - ${result.desc}");
Get.snackbar(
@ -72,6 +101,9 @@ class ImService {
Future<ImResult> logout() async {
final res = await TencentImSDKPlugin.v2TIMManager.logout();
if (res.code == 0) {
///
Get.delete<ImUserInfoController>(force: true);
///
Get.find<ImMessageListenerService>().onClose();
Get.delete<ImMessageListenerService>(force: true);
@ -83,25 +115,114 @@ class ImService {
/// tabbar
Get.find<TabBarController>().badgeMap.clear();
///
Get.find<ChatController>().initChatData();
///
Get.find<GlobalBadge>().onClose();
Get.delete<GlobalBadge>(force: true);
///
PushService.unInitPush();
///
ImCore.unInit();
}
return ImResult(
success: res.code == 0,
code: res.code,
desc: res.desc,
return ImResult.wrapNoData(res);
}
///
Future<ImResult<List<V2TimConversationOperationResult>>> setConversationCustomData({
required String customData,
required List<String> conversationIDList,
}) async {
final res = await TIMConversationManager.instance.setConversationCustomData(customData: customData, conversationIDList: conversationIDList);
return ImResult.wrap(res);
}
///
Future<ImResult<int>> getUnreadMessageCountByFilter({
required V2TimConversationFilter filter,
}) async {
final res = await TIMConversationManager.instance.getUnreadMessageCountByFilter(
filter: filter,
);
return ImResult.wrap(res);
}
///
Future<ImResult<void>> deleteConversation({
required String conversationID,
}) async {
final res = await TIMConversationManager.instance.deleteConversation(conversationID: conversationID);
return ImResult.wrapNoData(res);
}
///
Future<ImResult<List<V2TimConversationOperationResult>>> createConversationGroup({
required String groupName,
required List<String> conversationIDList,
}) async {
final res = await TIMConversationManager.instance.createConversationGroup(
groupName: groupName,
conversationIDList: conversationIDList,
);
return ImResult.wrap(res);
}
///
Future<ImResult<List<String>>> getConversationGroupList() async {
final res = await TIMConversationManager.instance.getConversationGroupList();
return ImResult.wrap(res);
}
///
Future<ImResult<List<V2TimConversationOperationResult>>> addConversationsToGroup({
required String groupName,
required List<String> conversationIDList,
}) async {
final res = await TIMConversationManager.instance.addConversationsToGroup(
groupName: groupName,
conversationIDList: conversationIDList,
);
return ImResult.wrap(res);
}
///
Future<ImResult<List<V2TimConversationOperationResult>>> deleteConversationsFromGroup({
required String groupName,
required List<String> conversationIDList,
}) async {
final res = await TIMConversationManager.instance.deleteConversationsFromGroup(
groupName: groupName,
conversationIDList: conversationIDList,
);
return ImResult.wrap(res);
}
///
Future<ImResult<V2TimConversationResult>> getConversationListByFilter({
required V2TimConversationFilter filter,
required int nextSeq,
int count = 20,
}) async {
final res = await TIMConversationManager.instance.getConversationListByFilter(
filter: filter,
nextSeq: nextSeq,
count: count,
);
return ImResult.wrap(res);
}
///
Future<ImResult> getConversationList(String nextSeq, int count) async {
final res = await TencentImSDKPlugin.v2TIMManager.getConversationManager().getConversationList(nextSeq: nextSeq, count: count);
if (res.code != 0) {
// final res = await TencentImSDKPlugin.v2TIMManager.getConversationManager().getConversationList(nextSeq: nextSeq, count: count);
final res = await getConvData(nextSeq, count);
if (res.success == false) {
return ImResult(
success: false,
code: res.code,
@ -138,7 +259,7 @@ class ImService {
//
final customInfo = user.customInfo;
if (customInfo != null) {
isCustomAdmin = customInfo['Tag_Profile_Custom_admin'] ?? '0';
isCustomAdmin = customInfo['admin'] ?? '0';
}
}
}
@ -171,6 +292,40 @@ class ImService {
return ConversationViewModel(conversation: conv, faceUrl: faceUrl, isCustomAdmin: isCustomAdmin);
}).toList();
// ,
viewList.removeWhere((conv) {
final special = conv.conversation.conversationGroupList ?? [];
return special.contains('noFriend');
});
ChatController chatcontroller = Get.find<ChatController>();
logger.e('新的分页内容:${res.data!.toLogString()},控制器中的:${chatcontroller.nextSeq.value}');
String newNextSeq = res.data?.nextSeq ?? '0';
bool isEnd = res.data?.isFinished ?? true;
if (isEnd) {
//;
chatcontroller.isFinished.value = isEnd;
} else {
//
chatcontroller.nextSeq.value = newNextSeq;
}
//
chatcontroller.nextSeq.value = res.data!.nextSeq!;
if (res.data!.isFinished == false) {
if (viewList.length < 20) {
//
final nextRes = await getConversationList(
res.data!.nextSeq!,
count,
);
if (nextRes.success && nextRes.data != null) {
viewList.addAll(nextRes.data as List<ConversationViewModel>);
}
}
}
return ImResult(
success: res.code == 0,
code: res.code,
@ -179,30 +334,13 @@ class ImService {
);
}
/// userId
Future<ImResult> selfUserId() async {
V2TimValueCallback<String> self = await TencentImSDKPlugin.v2TIMManager.getLoginUser();
String? userId = self.data;
return ImResult(
success: self.code == 0,
code: self.code,
desc: self.desc,
data: userId,
);
}
///
Future<ImResult> selfInfo() async {
// ID
final idRes = await selfUserId();
//
V2TimValueCallback<List<V2TimUserFullInfo>> res = await TencentImSDKPlugin.v2TIMManager.getUsersInfo(userIDList: [idRes.data]);
return ImResult(
success: res.code == 0,
code: res.code,
desc: res.desc,
data: res.data?.isNotEmpty == true ? res.data![0] : null,
);
///
Future<ImResult<V2TimConversationResult>> getConvData(String nextSeq, int count) async {
final res = await TencentImSDKPlugin.v2TIMManager.getConversationManager().getConversationList(nextSeq: nextSeq, count: count);
// for (var element in res.data!.conversationList) {
// logger.e('所有的会话数据:${element.toJson()}');
// }
return ImResult.wrap(res);
}
///
@ -219,13 +357,29 @@ class ImService {
);
}
///
Future<ImResult<List<V2TimMessage>>> findMessages({
required List<String> messageIDList,
}) async {
final res = await TIMMessageManager.instance.findMessages(messageIDList: messageIDList);
return ImResult.wrap(res);
}
///
Future<ImResult<V2TimMessageChangeInfo>> modifyMessage({
required V2TimMessage message,
}) async {
final res = await TIMMessageManager.instance.modifyMessage(message: message);
return ImResult.wrap(res);
}
/// groupID userID
Future<ImResult<List<V2TimMessage>>> getHistoryMessageList({
HistoryMsgGetTypeEnum getType = HistoryMsgGetTypeEnum.V2TIM_GET_LOCAL_OLDER_MSG,
String? userID,
String? groupID,
int? lastMsgSeq,
int count = 20,
int count = 10,
V2TimMessage? lastMsg,
List<int>? messageTypeList,
List<int>? messageSeqList,
@ -298,10 +452,223 @@ class ImService {
cleanSequence: cleanSequence, //
);
return ImResult.wrapNoData(res);
}
/// userId
Future<ImResult> selfUserId() async {
V2TimValueCallback<String> self = await TencentImSDKPlugin.v2TIMManager.getLoginUser();
String? userId = self.data;
return ImResult(
success: self.code == 0,
code: self.code,
desc: self.desc,
data: userId,
);
}
///
Future<ImResult> selfInfo() async {
// ID
final idRes = await selfUserId();
//
V2TimValueCallback<List<V2TimUserFullInfo>> res = await TencentImSDKPlugin.v2TIMManager.getUsersInfo(userIDList: [idRes.data]);
return ImResult(
success: res.code == 0,
code: res.code,
desc: res.desc,
data: res.data?.isNotEmpty == true ? res.data!.first : null,
);
}
///
Future<ImResult> otherInfo(id) async {
//
V2TimValueCallback<List<V2TimUserFullInfo>> res = await TencentImSDKPlugin.v2TIMManager.getUsersInfo(userIDList: [id]);
return ImResult(
success: res.code == 0,
code: res.code,
desc: res.desc,
data: res.data?.isNotEmpty == true ? res.data!.first : null,
);
}
///
Future<ImResult> setSelfInfo({
required V2TimUserFullInfo userFullInfo,
}) async {
final res = await TencentImSDKPlugin.v2TIMManager.setSelfInfo(
userFullInfo: userFullInfo,
);
return ImResult.wrapNoData(res);
}
///
Future<ImResult> isMyFriend(String userID, FriendTypeEnum checkType) async {
final res = await TIMFriendshipManager.instance.checkFriend(
userIDList: [userID],
checkType: checkType, //V2TIM_FRIEND_TYPE_BOTH V2TIM_FRIEND_TYPE_SINGLE
);
if (res.code == 0 && res.data != null && res.data!.isNotEmpty) {
final resultType = res.data!.first.resultType;
final isFriend = resultType == 3; //0= 1= 2=3=
return ImResult(
success: true,
desc: res.desc,
code: res.code,
data: isFriend,
);
} else {
return ImResult(
success: false,
code: res.code,
desc: res.desc,
data: false,
);
}
}
///
Future<ImResult<V2TimFriendOperationResult>> addFriend({
required String userID,
String? remark,
String? friendGroup,
String? addWording,
String? addSource,
required FriendTypeEnum addType,
}) async {
final res = await TIMFriendshipManager.instance.addFriend(
userID: userID,
remark: remark,
friendGroup: friendGroup,
addWording: addWording,
addSource: addSource,
addType: addType,
);
return ImResult.wrap(res);
}
///
Future<ImResult<V2TimFriendOperationResult>> acceptFriendApplication({
required FriendResponseTypeEnum responseType,
required FriendApplicationTypeEnum type, // V2TIM_FRIEND_ACCEPT_AGREEV2TIM_FRIEND_ACCEPT_AGREE_AND_ADD
required String userID,
}) async {
final res = await TIMFriendshipManager.instance.acceptFriendApplication(
responseType: responseType,
type: type,
userID: userID,
);
return ImResult.wrap(res);
}
///
Future<ImResult<List<V2TimFriendOperationResult>>> addToBlackList({
required List<String> userIDList,
}) async {
final res = await TIMFriendshipManager.instance.addToBlackList(userIDList: userIDList);
return ImResult.wrap(res);
}
///
Future<ImResult<List<V2TimFriendOperationResult>>> deleteFromBlackList({
required List<String> userIDList,
}) async {
final res = await TIMFriendshipManager.instance.deleteFromBlackList(userIDList: userIDList);
return ImResult.wrap(res);
}
///
Future<ImResult<List<V2TimFriendInfo>>> getFriendList() async {
final res = await TIMFriendshipManager.instance.getFriendList();
return ImResult.wrap(res);
}
/// set好友备注
Future<ImResult> setFriendInfo({
required String userID,
String? friendRemark,
Map<String, String>? friendCustomInfo,
}) async {
late V2TimCallback res;
res = await TIMFriendshipManager.instance.setFriendInfo(
userID: userID,
friendRemark: friendRemark,
friendCustomInfo: friendCustomInfo,
);
return ImResult.wrapNoData(res);
}
///
Future<ImResult<List<V2TimFriendInfoResult>>> getFriendInfo({
required List<String> userIDList,
}) async {
final res = await TIMFriendshipManager.instance.getFriendsInfo(userIDList: userIDList);
return ImResult.wrap(res);
}
///
Future<ImResult<List<V2TimFollowOperationResult>>> followUser({
required List<String> userIDList,
}) async {
final res = await TIMFriendshipManager.instance.followUser(
userIDList: userIDList,
);
return ImResult.wrap(res);
}
///
Future<ImResult<List<V2TimFollowOperationResult>>> unfollowUser({
required List<String> userIDList,
}) async {
final res = await TIMFriendshipManager.instance.unfollowUser(
userIDList: userIDList,
);
return ImResult.wrap(res);
}
/// check关注的类型
/// 0
/// 1
/// 2
/// 3
Future<ImResult<List<V2TimFollowTypeCheckResult>>> checkFollowType({
required List<String> userIDList,
}) async {
final res = await TIMFriendshipManager.instance.checkFollowType(
userIDList: userIDList,
);
return ImResult.wrap(res);
}
/// //
Future<ImResult<List<V2TimFollowInfo>>> getUserFollowInfo({
required List<String> userIDList,
}) async {
final res = await TIMFriendshipManager.instance.getUserFollowInfo(userIDList: userIDList);
return ImResult.wrap(res);
}
///
/// [nextCursor]
Future<ImResult<V2TimUserInfoResult>> getMutualFollowersList({
required String nextCursor,
}) async {
final res = await TIMFriendshipManager.instance.getMutualFollowersList(
nextCursor: nextCursor,
);
return ImResult.wrap(res);
}
///
/// [nextCursor]
Future<ImResult<V2TimUserInfoResult>> getMyFollowersList({
required String nextCursor,
}) async {
final res = await TIMFriendshipManager.instance.getMyFollowersList(
nextCursor: nextCursor,
);
return ImResult.wrap(res);
}
}

208
lib/IM/push_service.dart Normal file
View File

@ -0,0 +1,208 @@
import 'dart:convert';
import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:get/get.dart';
import 'package:logger/logger.dart';
import 'package:loopin/IM/im_service.dart';
import 'package:loopin/models/conversation_type.dart';
import 'package:loopin/utils/storage.dart';
import 'package:tencent_cloud_chat_push/common/tim_push_listener.dart';
import 'package:tencent_cloud_chat_push/common/tim_push_message.dart';
import 'package:tencent_cloud_chat_push/tencent_cloud_chat_push.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart';
import 'package:tencent_cloud_chat_sdk/tencent_im_sdk_plugin.dart';
final logger = Logger();
class PushService {
static late TIMPushListener _timPushListener;
Future<void> _registerPushInIsolate(Map<String, dynamic> args) async {
final sdkAppId = args['sdkAppId'] as int;
final appKey = args['appKey'] as String;
final apnsCertificateID = args['apnsCertificateID'] as int;
try {
await TencentCloudChatPush().registerPush(
sdkAppId: sdkAppId,
appKey: appKey,
apnsCertificateID: apnsCertificateID,
onNotificationClicked: _onNotificationClicked,
);
} catch (e) {
logger.e('注册推送失败: $e');
}
}
///
Future<void> initPush({
required int sdkAppId,
required String appKey, //
}) async {
int apnsCertificateID;
final devices = await _getDeviceBrand();
apnsCertificateID = _getApnsCertificateIDForBrand(devices);
if (apnsCertificateID == 0) {
logger.w('手机厂商:$devices, 未配置证书');
}
//
// if (Platform.isAndroid) {
// await compute(_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);
///
if (Platform.isAndroid) {
await TencentImSDKPlugin.v2TIMManager.login(userID: Storage.read('userId'), userSig: Storage.read('userSig'));
}
logger.i('推送服务已注册,手机:$devices,证书ID:$apnsCertificateID');
// 线
_addPushListener();
}
/// 退
static Future<void> unInitPush() async {
try {
await TencentCloudChatPush().unRegisterPush();
_removePushListener();
} catch (e) {
logger.i("注销推送失败: $e");
}
}
///
static void _addPushListener() {
_timPushListener = TIMPushListener(
onRecvPushMessage: (TimPushMessage message) {
logger.i("[推送] 收到消息: ${message.toLogString()}");
},
onRevokePushMessage: (String messageId) {
logger.i("[推送] 消息被撤回: $messageId");
},
onNotificationClicked: (String ext) {
logger.i("[推送] 点击横幅 ext: $ext");
_handleNotificationClick(ext);
},
);
TencentCloudChatPush().addPushListener(listener: _timPushListener);
logger.i('推送服务在线监听器已注册');
}
///
static void _removePushListener() {
TencentCloudChatPush().removePushListener(listener: _timPushListener);
}
///
static void _onNotificationClicked({
required String ext,
String? userID,
String? groupID,
}) {
logger.i("[点击通知回调] ext: $ext, userID: $userID, groupID: $groupID");
_handleNotificationClick(ext, userID: userID, groupID: groupID);
}
///
static void _handleNotificationClick(String ext, {String? userID, String? groupID}) async {
try {
// ext={id:ID,type:'newFoucs',userID:idgroupIDID}
// final ext = jsonEncode({
// "userID": "123456",
// "groupID": "654321",
// });
final data = jsonDecode(ext);
logger.i(data);
final type = data['type'];
final router = conversationTypeFromString(type);
logger.w(router);
if (router == null || router != '') {
//
if (data['userID'] != null) {
logger.w('有userID');
//
final covRes = await ImService.instance.getConversation(conversationID: 'c2c_${data['userID']}');
final V2TimConversation conversation = covRes.data;
logger.w(conversation.toJson());
if (conversation.conversationGroupList?.contains(ConversationType.noFriend.name) ?? false) {
// nofriend会话,
conversation.showName = conversation.showName ?? data['title'];
Get.toNamed('/chatNoFriend', arguments: conversation);
} else {
//
Get.toNamed('/chat', arguments: conversation);
}
} else {
logger.w('没有userID');
//
final groupRes = await ImService.instance.getConversation(conversationID: 'group_${data['groupID']}');
Get.toNamed('/chatGroup', arguments: groupRes.data);
}
} else {
//
Get.toNamed('/$router', arguments: data['id'] ?? '');
}
} catch (e) {
logger.i("[推送点击] ext 解析失败: $e");
}
}
///
static Future<String> _getDeviceBrand() async {
final deviceInfo = DeviceInfoPlugin();
try {
if (Platform.isAndroid) {
final androidInfo = await deviceInfo.androidInfo;
return androidInfo.brand.toLowerCase();
} else if (Platform.isIOS) {
return 'apple';
} else {
return 'unknown';
}
} catch (e, stack) {
logger.w("获取设备品牌失败: $e\n$stack");
return 'unknown';
}
}
/// ID
static int _getApnsCertificateIDForBrand(String brand) {
switch (brand) {
case 'xiaomi':
case 'redmi':
return 41169;
case 'oppo':
return 41170;
case 'vivo':
return 41177;
case 'meizu':
return 41176;
case 'apple':
return 45356;
case 'huawei':
return 41171;
case 'honor':
return 41178;
default:
return 0;
}
}
}

View File

@ -1,7 +1,15 @@
class CommonApi {
static const String checkVersion = '/check/version'; //
static const String getCode = '/resource/sms/code'; //
static const String login = '/auth/login'; //
static const String uploadFile = '/upload'; //
static const String accountInfo = '/ums/member/account/'; //
///----------get
static const String getCode = '/resource/sms/code'; // {'phonenumber'}
static const String accountInfo = '/app/member/info'; //
///---------post
static const String login = '/auth/login'; // {'phonenumber': '', 'smsCode': '', 'clientId': '428a8310cd442757ae699df5d894f051', 'grantType': 'sms'};
static const String checkVersion = '/system/version/list'; // app版本 {'platformType': Platform.isAndroid ? 'android' : 'ios','status': 1}
static const String uploadFile = '/resource/oss/upload';
///[source]=wechat_open [clientId]=428a8310cd442757ae699df5d894f051 [grantType]=social [socialState]=1
static const String wxLogin = '/app/member/bind/wechat';
///resource/oss/upload
}

17
lib/api/shop_api.dart Normal file
View File

@ -0,0 +1,17 @@
class ShopApi {
///---------------------post
/// [size]
/// [current]
/// [categoryId] id
/// [nameLike]
static const String shopList = '/app/product/page'; //
/// [showStatus]1= [nameLike]
static const String shopCategory = '/app/productCategory/page'; //
/// []
static const String shopSwiperList = '/app/article/carousel'; //
///---------------------get
/// [url参数/id]
static const String shopDetail = '/app/product'; //
}

View File

@ -1,18 +1,19 @@
class VideoApi {
// get
static const String vlogList = '/vlog/indexList'; //
static const String myPublicList = '/vlog/myPublicList'; //
static const String myPrivateList = '/vlog/myPrivateList'; //
static const String myLikedList = '/vlog/myLikedList'; //
static const String friendList = '/vlog/friendList'; //
static const String followList = '/vlog/followList'; //
static const String detail = '/vlog/detail'; //
static const String vlogList = '/app/vlog/indexList'; //
static const String myPrivateList = '/app/vlog/myPrivateList'; //
static const String friendList = '/app/vlog/friendList'; //
static const String followList = '/app/vlog/followList'; //
static const String detail = '/app/vlog/detail'; //
// post
static const String unlike = '/vlog/unlike'; //
static const String totalLikedCounts = '/vlog/totalLikedCounts'; //
static const String publish = '/vlog/publish'; //
static const String like = '/vlog/like'; //
static const String changeVlogStatus = '/vlog/changeVlogStatus'; //
static const String changeToPublic = '/vlog/changeToPublic'; //
static const String changeToPrivate = '/vlog/changeToPrivate'; //
static const String myPublicList = '/app/vlog/myPublicList'; //
static const String myLikedList = '/app/vlog/myLikedList'; //
static const String unlike = '/app/vlog/unlike'; //
static const String totalLikedCounts = '/app/vlog/totalLikedCounts'; //
static const String publish = '/app/vlog/publish'; //
static const String like = '/app/vlog/like'; //
static const String changeVlogStatus = '/app/vlog/changeVlogStatus'; //
static const String changeToPublic = '/app/vlog/changeToPublic'; //
static const String changeToPrivate = '/app/vlog/changeToPrivate'; //
}

View File

@ -0,0 +1,11 @@
import 'package:get/get.dart';
import 'package:loopin/IM/controller/chat_detail_controller.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart';
class ChatBinding extends Bindings {
@override
void dependencies() {
V2TimConversation conversation = Get.arguments;
Get.put(ChatDetailController(userID: conversation.userID!));
}
}

View File

@ -2,11 +2,20 @@
library;
import 'package:flutter/material.dart';
import 'package:get/get.dart';
typedef OnPinnedChanged = void Function(bool pinned);
class CustomStickyHeader extends SliverPersistentHeaderDelegate {
final PreferredSize child;
RxBool? isPinned;
RxDouble? positions;
CustomStickyHeader({required this.child});
CustomStickyHeader({
required this.child,
this.isPinned,
this.positions,
});
@override
double get minExtent => child.preferredSize.height;
@ -21,6 +30,21 @@ class CustomStickyHeader extends SliverPersistentHeaderDelegate {
@override
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
// overlapsContent shrinkOffset >= maxExtent - minExtent
bool pinned = overlapsContent; // true
if (isPinned != null && isPinned!.value != pinned) {
WidgetsBinding.instance.addPostFrameCallback((_) {
isPinned!.value = pinned;
});
}
if (positions != null) {
if ((maxExtent - minExtent) >= shrinkOffset) {
WidgetsBinding.instance.addPostFrameCallback((_) {
positions!.value = shrinkOffset;
});
}
}
return child;
}
}

View File

@ -0,0 +1,25 @@
import 'package:flutter/material.dart';
import 'package:shirne_dialog/shirne_dialog.dart';
class MyToast {
///
void tip({
required String title,
String? type, //
String? position, //
}) {
final baseStyle = position == 'top'
? MyDialog.theme.toastStyle?.top()
: position == 'center'
? MyDialog.theme.toastStyle?.center()
: MyDialog.theme.toastStyle?.bottom();
MyDialog.toast(
title,
icon: type == 'success' ? const Icon(Icons.check_circle) : Icon(Icons.warning),
duration: Duration(milliseconds: 5000),
style: baseStyle?.copyWith(
backgroundColor: type == 'success' ? Colors.green.withAlpha(200) : Colors.red.withAlpha(200),
),
);
}
}

View File

@ -0,0 +1,47 @@
import 'package:flutter/material.dart';
class NetworkOrAssetImage extends StatelessWidget {
final String? imageUrl;
final double width;
final double? height;
final BoxFit fit;
final String placeholderAsset;
const NetworkOrAssetImage({
super.key,
required this.imageUrl,
this.width = 60.0,
this.height,
this.fit = BoxFit.cover,
this.placeholderAsset = 'assets/images/avatar/default.png',
});
@override
Widget build(BuildContext context) {
final isNetwork = imageUrl != null && imageUrl!.isNotEmpty && (imageUrl!.startsWith('http://') || imageUrl!.startsWith('https://'));
if (isNetwork) {
return Image.network(
imageUrl!,
width: width,
height: height,
fit: fit,
errorBuilder: (context, error, stackTrace) {
return Image.asset(
placeholderAsset,
width: width,
height: height,
fit: fit,
);
},
);
} else {
return Image.asset(
(imageUrl != null && imageUrl!.isNotEmpty) ? imageUrl! : placeholderAsset,
width: width,
height: height,
fit: fit,
);
}
}
}

View File

@ -0,0 +1,76 @@
import 'package:flutter/material.dart';
import 'package:loopin/components/shark_video.dart';
import 'package:media_kit/media_kit.dart';
import 'package:media_kit_video/media_kit_video.dart';
class PreviewVideo extends StatefulWidget {
final String videoUrl;
final double? width;
final double? height;
const PreviewVideo({
super.key,
required this.videoUrl,
this.width,
this.height,
});
@override
State<PreviewVideo> createState() => _PreviewVideoPageState();
}
class _PreviewVideoPageState extends State<PreviewVideo> {
late final Player _player = Player();
late VideoController videoController = VideoController(_player);
@override
void initState() {
super.initState();
_player.open(Media(widget.videoUrl));
}
@override
void dispose() {
_player.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final videoWidth = widget.width ?? 1.0;
final videoHeight = widget.height ?? 1.0;
final isHorizontal = videoWidth > videoHeight;
return SafeArea(
child: Stack(
children: [
Scaffold(
backgroundColor: Colors.black,
body: Center(
child: Video(
controller: videoController,
fit: isHorizontal ? BoxFit.contain : BoxFit.cover,
controls: (state) => MyMaterialVideoControls(state),
),
),
),
//
Positioned(
top: 20,
left: 20,
child: GestureDetector(
onTap: () {
Navigator.pop(context);
},
child: const Icon(
Icons.close,
color: Colors.white,
size: 28,
),
),
),
],
),
);
}
}

View File

@ -3,6 +3,7 @@ import 'dart:async';
import 'package:ai_barcode_scanner/ai_barcode_scanner.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:permission_handler/permission_handler.dart';
class ScanUtil {
static Future<void> openScanner({required void Function(String) onResult}) async {
@ -42,6 +43,29 @@ class ScanUtil {
width: MediaQuery.of(Get.context!).size.width * 0.8,
height: MediaQuery.of(Get.context!).size.height * 0.5,
),
//
errorBuilder: (context, error) {
String message = "无法启动摄像头,请检查权限";
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.error, color: Colors.red, size: 60),
const SizedBox(height: 16),
Text(
message,
style: const TextStyle(fontSize: 18, color: Colors.red),
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
ElevatedButton(
onPressed: () async => await openAppSettings(),
child: const Text('去开启权限'),
),
],
),
);
},
),
);

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,145 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:loopin/IM/im_service.dart';
import 'package:loopin/api/shop_api.dart';
import 'package:loopin/service/http.dart';
/// Tab
class TabState {
final ScrollController scrollController;
final RxInt currentPage;
final RxDouble scrollOffset;
final RxList dataList;
final RxBool isLoading;
final RxBool hasLoaded;
TabState({
required this.scrollController,
required this.currentPage,
required this.scrollOffset,
required this.dataList,
required this.isLoading,
required this.hasLoaded,
});
}
class ShopIndexController extends GetxController with GetSingleTickerProviderStateMixin {
TabController? tabController;
///
RxList<dynamic> swiperData = <dynamic>[].obs;
/// tab
RxList<dynamic> tabList = <dynamic>[].obs;
/// tab
final Map<int, TabState> tabs = {};
/// tab index
RxInt currentTabIndex = 0.obs;
/// Tab
void initTabs() async {
// ScrollController
tabs.forEach((_, state) => state.scrollController.dispose());
tabs.clear();
tabList.clear();
// TabController
tabController?.removeListener(_tabListener);
tabController?.dispose();
// tab
final res = await Http.post(ShopApi.shopCategory, data: {
'showStatus': 1,
});
final data = res['data']['records'] as List<dynamic>;
logger.w(data);
tabList.addAll(data);
// tab
for (int i = 0; i < tabList.length; i++) {
final controller = ScrollController();
tabs[i] = TabState(
scrollController: controller,
currentPage: 1.obs,
scrollOffset: 0.0.obs,
dataList: <dynamic>[].obs,
isLoading: false.obs,
hasLoaded: false.obs,
);
}
// TabController
tabController = TabController(length: tabList.length, vsync: this);
tabController!.addListener(_tabListener);
// tab
if (tabList.isNotEmpty) {
loadSwiperData();
loadData(0);
}
}
/// Tab
void _tabListener() {
if (!tabController!.indexIsChanging) {
currentTabIndex.value = tabController!.index;
final tab = tabs[currentTabIndex.value];
if (tab != null && !tab.hasLoaded.value) {
loadData(currentTabIndex.value);
}
}
}
Future<void> refreshData(int index) async {
await loadSwiperData();
final tab = tabs[index];
if (tab == null) return;
tab.currentPage.value = 1;
tab.dataList.clear();
tab.isLoading.value = false;
tab.hasLoaded.value = false;
await loadData(index);
}
/// pageview数据
Future<void> loadData(int index) async {
final tab = tabs[index];
if (tab == null || tab.isLoading.value) return;
tab.isLoading.value = true;
final res = await Http.post(ShopApi.shopList, data: {
'size': 10,
'current': tab.currentPage.value,
'categoryId': tabList[index]['id'],
});
final data = res['data']['records'];
tab.dataList.addAll(data);
logger.w(res);
tab.currentPage.value += 1;
tab.isLoading.value = false;
tab.hasLoaded.value = true;
}
/// swiper数据
Future<void> loadSwiperData() async {
final res = await Http.post(ShopApi.shopSwiperList, data: {
'type': 1,
});
final data = res['data'];
logger.w(res);
swiperData.assignAll(data);
}
@override
void onClose() {
tabController?.removeListener(_tabListener);
tabController?.dispose();
tabs.forEach((_, state) => state.scrollController.dispose());
super.onClose();
}
}

View File

@ -5,7 +5,6 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:loopin/IM/controller/chat_controller.dart';
import 'package:loopin/IM/controller/tab_bar_controller.dart';
import 'package:loopin/IM/im_service.dart';
import 'package:loopin/models/tab_type.dart';
import 'package:loopin/pages/video/module/recommend.dart';
import 'package:loopin/update/upgrade_service.dart';
@ -38,7 +37,7 @@ class _LayoutState extends State<Layout> {
// tabs选项
List navItems = [
BottomNavigationBarItem(icon: Icon(Icons.play_circle_outline), label: '视频'),
BottomNavigationBarItem(icon: Icon(Icons.local_mall), label: '团购'),
BottomNavigationBarItem(icon: Icon(Icons.local_mall), label: '易选'),
BottomNavigationBarItem(
icon: Icon(
Icons.camera_alt_rounded,
@ -79,7 +78,7 @@ class _LayoutState extends State<Layout> {
super.initState();
//
WidgetsBinding.instance.addPostFrameCallback((_) {
UpgradeService.checkUpgrade(context);
UpgradeService.checkUpgrade(this);
});
}
@ -209,7 +208,7 @@ class _LayoutState extends State<Layout> {
//
void onTabTap(int index) {
logger.i(index);
// logger.i(index);
if (index == 0) {
if (videoModuleController.videoTabIndex.value == 2) {
RecommendModule.playVideo();
@ -223,7 +222,10 @@ class _LayoutState extends State<Layout> {
}
if (index == 3) {
//
Get.find<ChatController>().getConversationList();
final ctl = Get.find<ChatController>();
if (ctl.chatList.isEmpty) {
Get.find<ChatController>().getConversationList();
}
}
if (index == 4) {
myPageKey.currentState?.refreshData();

View File

@ -28,6 +28,7 @@ void main() async {
Get.put(TabBarController());
//
Get.put(ChatController());
// app前后台状态
WidgetsFlutterBinding.ensureInitialized();
WidgetsBinding.instance.addObserver(LifecycleHandler());
@ -52,13 +53,23 @@ void main() async {
//
if (Common.isLogin()) {
// ImIm登录
await im_core.ImCore.init(sdkAppId: 1600080789);
// userSig先用固定值ios用1587188
await ImService.instance.login(userID: Storage.read('userId'), userSig: Storage.read('userSig'));
// String userId = '1909990634551795712'; //15877777777
// String userId = '18832510385';
final res = await im_core.ImCore.init(sdkAppId: 1600080789);
// userSig先用固定值ios用93650385
try {
if (res) {
await ImService.instance.login(userID: Storage.read('userId'), userSig: Storage.read('userSig'));
} else {
logger.w('初始化未完成');
}
} catch (e) {
logger.w(e.toString());
Common.logout();
}
// String userId = '1940667704585248769'; //13212279365
// String userId = '1943510443312078850'; //18832510385
// String userSig =
// 'eJwtzcsOgjAQBdB-6dqQKThth8QdG*JrIRHjTqGYiagNDzUx-rsVWN5zJ3M-IlvtgqdtRCzCAMRsyFzae8cVDywJiAhUNEeUmlDLcDpry*vJOS5FLBUAGNCGxsa*HTfWOyKGvhq149vftH8DhHLSli9*Jdrk2hqTVgdK2dKxXSfKbYuXpnPSu1zl*0fd18skg2Ihvj*7ADL4';
// 'eJwtjcEKgkAURf9l1iFPm*e8EdoYYUWFURAtg5nk5VRiEln0703q8p57Ofcj9qtd8LS1SEQUgBh1mY29NXzmDodaQhwrBRIJI0kq1sPsYcpTVbERSRgDAIEi3Tf2VXFtPUfEyFc9bfj6ZwrH4J1Ig4UL-6LX0ihyS7U5bi-Wzd8LzrK8TFs6TJ1sZwWGxlGas71PxPcHwH4y9Q__';
// 'eJwtzLEKwjAUheF3ySwlNzXNbcHFxSIOaqTWUUgsF1FDG2tEfHdj2-F8P5wPO2x00tuWFUwknM2GTcbePV1oYEBMhQSeopxyZ65n58iwAjLOOXKF*VhscNTa6FJKEdOonm5-UxJQpZhN2lET3599Xllbv9ZBH2uHuDfvst5tG6FX0EFYVhpOpZ973z8W7PsDmYwyIw__';
// await ImService.instance.login(userID: userId, userSig: userSig);
}

View File

@ -0,0 +1,48 @@
///
enum ConversationType {
noFriend, //
system, //
newFoucs, //
interaction, //
order, //
groupNotify, //
}
extension ConversationTypeExtension on ConversationType {
String get name {
switch (this) {
case ConversationType.noFriend:
return 'noFriend';
case ConversationType.system:
return 'system';
case ConversationType.newFoucs:
return 'newFoucs';
case ConversationType.interaction:
return 'interaction';
case ConversationType.order:
return 'order';
case ConversationType.groupNotify:
return 'groupNotify';
}
}
}
conversationTypeFromString(String? type) {
if (type == null) return null;
if (type.contains('noFriend')) {
return ConversationType.noFriend.name;
} else if (type.contains('system')) {
return ConversationType.system.name;
} else if (type.contains('newFoucs')) {
return ConversationType.newFoucs.name;
} else if (type.contains('interaction')) {
return ConversationType.interaction.name;
} else if (type.contains('order')) {
return ConversationType.order.name;
} else if (type.contains('groupNotify')) {
return ConversationType.groupNotify.name;
}
return null;
}

View File

@ -1,9 +1,9 @@
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart';
class ConversationViewModel {
final V2TimConversation conversation;
final String? faceUrl;
final String isCustomAdmin;
late V2TimConversation conversation;
String? faceUrl;
String? isCustomAdmin;
ConversationViewModel({
required this.conversation,

View File

@ -0,0 +1,97 @@
///
enum NotifyMessageType {
newFoucs, //
systemNotify, // ->
systemReport, // ->
systemCheck, // ->
systemPush, //->广
interactionComment, //->
interactionAt, //->@
interactionLike, //->
interactionReply, //->
orderRecharge, //-> online
orderPay, //-> online
orderRefund, //->退
groupNotifyCheck, //-> online
groupNotifyAccpet, // -> online
groupNotifyFail, // -> online
groupNotifyLeaveUp, // ->
}
extension NotifyMessageTypeExtension on NotifyMessageType {
String get name {
switch (this) {
case NotifyMessageType.newFoucs:
return 'newFoucs';
case NotifyMessageType.systemNotify:
return 'systemNotify';
case NotifyMessageType.systemReport:
return 'systemReport';
case NotifyMessageType.systemCheck:
return 'systemCheck';
case NotifyMessageType.systemPush:
return 'systemPush';
case NotifyMessageType.interactionComment:
return 'interactionComment';
case NotifyMessageType.interactionAt:
return 'interactionAt';
case NotifyMessageType.interactionLike:
return 'interactionLike';
case NotifyMessageType.interactionReply:
return 'interactionReply';
case NotifyMessageType.orderRecharge:
return 'orderRecharge';
case NotifyMessageType.orderPay:
return 'orderPay';
case NotifyMessageType.orderRefund:
return 'orderRefund';
case NotifyMessageType.groupNotifyCheck:
return 'groupNotifyCheck';
case NotifyMessageType.groupNotifyAccpet:
return 'groupNotifyAccpet';
case NotifyMessageType.groupNotifyFail:
return 'groupNotifyFail';
case NotifyMessageType.groupNotifyLeaveUp:
return 'groupNotifyLeaveUp';
}
}
}
notifyMessageTypeFromString(String? type) {
switch (type) {
case 'newFoucs':
return NotifyMessageType.newFoucs.name;
case 'systemNotify':
return NotifyMessageType.systemNotify.name;
case 'systemReport':
return NotifyMessageType.systemReport.name;
case 'systemCheck':
return NotifyMessageType.systemCheck.name;
case 'systemPush':
return NotifyMessageType.systemPush.name;
case 'interactionComment':
return NotifyMessageType.interactionComment.name;
case 'interactionAt':
return NotifyMessageType.interactionAt.name;
case 'interactionLike':
return NotifyMessageType.interactionLike.name;
case 'interactionReply':
return NotifyMessageType.interactionReply.name;
case 'orderRecharge':
return NotifyMessageType.orderRecharge.name;
case 'orderPay':
return NotifyMessageType.orderPay.name;
case 'orderRefund':
return NotifyMessageType.orderRefund.name;
case 'groupNotifyCheck':
return NotifyMessageType.groupNotifyCheck.name;
case 'groupNotifyAccpet':
return NotifyMessageType.groupNotifyAccpet.name;
case 'groupNotifyFail':
return NotifyMessageType.groupNotifyFail.name;
case 'groupNotifyLeaveUp':
return NotifyMessageType.groupNotifyLeaveUp.name;
default:
return null;
}
}

View File

@ -0,0 +1,6 @@
///
class SummaryType {
static const hongbao = 'hongbao';
static const shareVideo = 'shareVideo';
static const shareTuangou = 'shareTuangou';
}

View File

@ -74,10 +74,10 @@ class _LoginState extends State<Login> {
// im_sdk
await im_core.ImCore.init(sdkAppId: 1600080789);
// String userId = '1909990634551795712'; //15877777777
// String userId = '18832510385';
// String userId = '1940667704585248769'; //13212279365
// String userId = '1943510443312078850'; //18832510385
// String userSig =
// 'eJwtzcsOgjAQBdB-6dqQKThth8QdG*JrIRHjTqGYiagNDzUx-rsVWN5zJ3M-IlvtgqdtRCzCAMRsyFzae8cVDywJiAhUNEeUmlDLcDpry*vJOS5FLBUAGNCGxsa*HTfWOyKGvhq149vftH8DhHLSli9*Jdrk2hqTVgdK2dKxXSfKbYuXpnPSu1zl*0fd18skg2Ihvj*7ADL4';
// 'eJwtjcEKgkAURf9l1iFPm*e8EdoYYUWFURAtg5nk5VRiEln0703q8p57Ofcj9qtd8LS1SEQUgBh1mY29NXzmDodaQhwrBRIJI0kq1sPsYcpTVbERSRgDAIEi3Tf2VXFtPUfEyFc9bfj6ZwrH4J1Ig4UL-6LX0ihyS7U5bi-Wzd8LzrK8TFs6TJ1sZwWGxlGas71PxPcHwH4y9Q__';
// 'eJwtzLEKwjAUheF3ySwlNzXNbcHFxSIOaqTWUUgsF1FDG2tEfHdj2-F8P5wPO2x00tuWFUwknM2GTcbePV1oYEBMhQSeopxyZ65n58iwAjLOOXKF*VhscNTa6FJKEdOonm5-UxJQpZhN2lET3599Xllbv9ZBH2uHuDfvst5tG6FX0EFYVhpOpZ973z8W7PsDmYwyIw__';
try {
@ -87,13 +87,17 @@ class _LoginState extends State<Login> {
if (loginRes.success) {
//
Storage.write('hasLogged', true);
// Storage.write('userSig', userSig);
Storage.write('userSig', userSig);
Storage.write('userId', userId);
// Storage.write('token', obj['access_token']);
Storage.write('token', obj['access_token']);
//
final accountRes = await Http.get('${CommonApi.accountInfo}/$userId');
logger.i(accountRes);
//
final videoController = Get.find<VideoModuleController>();
videoController.markNeedRefresh();
dialogController.close();
Get.back();
}
} catch (e) {
@ -126,7 +130,7 @@ class _LoginState extends State<Login> {
vcodeText = '获取验证码(${time--})';
} else {
vcodeText = '获取验证码';
time = 6;
time = 60;
disabled = false;
timer.cancel();
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -2,20 +2,41 @@
library;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:shirne_dialog/shirne_dialog.dart';
class RedPacket extends StatefulWidget {
const RedPacket({super.key});
final bool flag; // true=false=
final void Function(Map<String, dynamic>)? onSend;
final int? maxNum; //
const RedPacket({super.key, required this.flag, this.onSend, this.maxNum});
@override
State<RedPacket> createState() => _RedPacketState();
@override
State<RedPacket> createState() => _RedPacketState();
}
class _RedPacketState extends State<RedPacket> {
String amount = '0.00';
final TextEditingController _amountController = TextEditingController();
final TextEditingController _maxNumController = TextEditingController();
final TextEditingController _remarkController = TextEditingController();
@override
Widget build(BuildContext context) {
return Material(
String amount = '0.00';
String remark = '恭喜发财,大吉大利';
//
final List<TextInputFormatter> _decimalInputFormatters = [
FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d{0,2}')),
];
@override
void dispose() {
_amountController.dispose();
_maxNumController.dispose();
_remarkController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Material(
type: MaterialType.transparency,
child: Column(
children: [
@ -23,55 +44,67 @@ class _RedPacketState extends State<RedPacket> {
shrinkWrap: true,
padding: const EdgeInsets.only(bottom: 50.0),
children: [
const SizedBox(height: 10.0),
Container(
margin: const EdgeInsets.symmetric(horizontal: 15.0),
padding: const EdgeInsets.symmetric(horizontal: 10.0),
decoration: BoxDecoration(
color: Colors.white, borderRadius: BorderRadius.circular(10.0),
),
child: Row(
children: <Widget>[
const Text('红包个数'),
Expanded(
child: TextField(
textAlign: TextAlign.right,
decoration: const InputDecoration(
hintText: "填写个数",
isDense: true,
hintStyle: TextStyle(fontSize: 14.0),
border: OutlineInputBorder(borderSide: BorderSide.none)
if (widget.flag) const SizedBox(height: 10.0),
if (widget.flag)
Container(
margin: const EdgeInsets.symmetric(horizontal: 15.0),
padding: const EdgeInsets.symmetric(horizontal: 10.0),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10.0),
),
child: Row(
children: <Widget>[
const Text('红包个数'),
Expanded(
child: TextField(
maxLength: widget.maxNum,
controller: _maxNumController,
textAlign: TextAlign.right,
buildCounter: (_, {required currentLength, maxLength, required isFocused}) => null, //
decoration: const InputDecoration(
hintText: "填写个数", isDense: true, hintStyle: TextStyle(fontSize: 14.0), border: OutlineInputBorder(borderSide: BorderSide.none)),
onChanged: (value) {
//
setState(() {
remark = value;
});
},
),
onChanged: (value) {},
),
),
const Text(''),
],
const Text(''),
],
),
),
),
const SizedBox(height: 10.0),
Container(
margin: const EdgeInsets.symmetric(horizontal: 15.0),
padding: const EdgeInsets.symmetric(horizontal: 10.0),
decoration: BoxDecoration(
color: Colors.white, borderRadius: BorderRadius.circular(10.0),
color: Colors.white,
borderRadius: BorderRadius.circular(10.0),
),
child: Row(
children: <Widget>[
const Text('总金额'),
Expanded(
child: TextField(
controller: _amountController,
keyboardType: const TextInputType.numberWithOptions(decimal: true),
textAlign: TextAlign.right,
inputFormatters: _decimalInputFormatters,
maxLength: 6,
buildCounter: (_, {required currentLength, maxLength, required isFocused}) => null, //
decoration: const InputDecoration(
hintText: "¥0.00",
isDense: true,
hintStyle: TextStyle(fontSize: 14.0),
border: OutlineInputBorder(borderSide: BorderSide.none)
),
hintText: "¥0.00", isDense: true, hintStyle: TextStyle(fontSize: 14.0), border: OutlineInputBorder(borderSide: BorderSide.none)),
onChanged: (value) {
double val = double.tryParse(value) ?? 0.0;
if (val > 200) {
_amountController.text = '200';
val = 200;
}
setState(() {
amount = value != '' ? value : '0.00';
amount = val.toStringAsFixed(2);
});
},
),
@ -85,7 +118,8 @@ class _RedPacketState extends State<RedPacket> {
margin: const EdgeInsets.symmetric(horizontal: 15.0),
padding: const EdgeInsets.symmetric(horizontal: 10.0),
decoration: BoxDecoration(
color: Colors.white, borderRadius: BorderRadius.circular(10.0),
color: Colors.white,
borderRadius: BorderRadius.circular(10.0),
),
child: Row(
children: <Widget>[
@ -93,15 +127,26 @@ class _RedPacketState extends State<RedPacket> {
Expanded(
child: TextField(
maxLines: null,
maxLength: 16,
controller: _remarkController,
keyboardType: TextInputType.multiline,
textAlign: TextAlign.right,
buildCounter: (_, {required currentLength, maxLength, required isFocused}) => null, //
decoration: const InputDecoration(
hintText: "恭喜发财,大吉大利",
isDense: true,
hintStyle: TextStyle(fontSize: 14.0),
border: OutlineInputBorder(borderSide: BorderSide.none)
),
onChanged: (value) {},
hintText: "恭喜发财,大吉大利",
isDense: true,
hintStyle: TextStyle(fontSize: 14.0),
border: OutlineInputBorder(borderSide: BorderSide.none)),
onChanged: (value) {
//
setState(() {
if (value.isEmpty) {
remark = '恭喜发财,大吉大利';
} else {
remark = value;
}
});
},
),
),
],
@ -109,12 +154,11 @@ class _RedPacketState extends State<RedPacket> {
),
const SizedBox(height: 30.0),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text('', style: TextStyle(fontSize: 24.0)), Text(amount, style: const TextStyle(fontSize: 36.0))
]
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[const Text('', style: TextStyle(fontSize: 24.0)), Text(amount, style: const TextStyle(fontSize: 36.0))]),
const SizedBox(
height: 20.0,
),
const SizedBox(height: 20.0,),
UnconstrainedBox(
constrainedAxis: Axis.vertical,
child: FilledButton(
@ -122,23 +166,51 @@ class _RedPacketState extends State<RedPacket> {
backgroundColor: WidgetStateProperty.all(Color(0xFFFF7F43)),
padding: WidgetStateProperty.all(EdgeInsets.zero),
minimumSize: WidgetStateProperty.all(const Size(180.0, 45.0)),
shape: WidgetStatePropertyAll(
RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0))
),
shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0))),
),
onPressed: () {
double amountDouble = double.tryParse(amount) ?? 0.0;
if (amountDouble > 0) {
//
widget.onSend!(
{
'maxNum': widget.maxNum ?? 1,
'amount': amount,
'remark': remark,
},
);
} else {
final baseStyle = MyDialog.theme.toastStyle?.top();
MyDialog.toast(
'未输入金额',
icon: const Icon(Icons.check_circle),
duration: Duration(milliseconds: 5000),
style: baseStyle?.copyWith(
backgroundColor: Colors.red.withAlpha(200),
),
);
}
},
child: const Text(
'塞钱进红包',
style: TextStyle(fontSize: 16.0),
),
onPressed: () {},
child: const Text('塞钱进红包', style: TextStyle(fontSize: 16.0),),
),
),
const SizedBox(height: 10.0,),
const SizedBox(
height: 10.0,
),
const Align(
alignment: Alignment.center,
child: Text('未领取的红包将于24小时后发起退款', style: TextStyle(color: Colors.grey, fontSize: 12.0),),
child: Text(
'未领取的红包将于24小时后发起退款',
style: TextStyle(color: Colors.grey, fontSize: 12.0),
),
),
],
),
],
),
);
}
}
}

View File

@ -8,6 +8,7 @@ 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/components/scan_util.dart';
import 'package:loopin/models/conversation_view_model.dart';
import 'package:loopin/utils/parse_message_summary.dart';
import 'package:shirne_dialog/shirne_dialog.dart';
@ -30,6 +31,14 @@ class ChatPageState extends State<ChatPage> {
controller = Get.find<ChatController>();
}
void deletConv(context, ConversationViewModel item) async {
final res = await ImService.instance.deleteConversation(conversationID: item.conversation.conversationID);
if (res.success) {
Navigator.of(context).pop();
controller.chatList.remove(item);
}
}
//
double posDX = 0.0;
double posDY = 0.0;
@ -41,7 +50,7 @@ class ChatPageState extends State<ChatPage> {
}
//
void showContextMenu(BuildContext context) {
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;
@ -92,7 +101,9 @@ class ChatPageState extends State<ChatPage> {
style: TextStyle(color: Colors.black87, fontSize: 14.0),
),
dense: true,
onTap: () {},
onTap: () {
deletConv(context, item);
},
)
],
),
@ -306,6 +317,7 @@ class ChatPageState extends State<ChatPage> {
onRefresh: handleRefresh,
child: Obx(() {
final chatList = controller.chatList;
return ListView.builder(
shrinkWrap: true,
physics: BouncingScrollPhysics(),
@ -322,11 +334,35 @@ class ChatPageState extends State<ChatPage> {
children: <Widget>[
//
ClipOval(
child: Image.network(
chatList[index].faceUrl ?? 'https://wuzhongjie.com.cn/download/logo.png',
height: 50.0,
width: 50.0,
fit: BoxFit.cover,
child: Builder(
builder: (context) {
final faceUrl = chatList[index].faceUrl;
final isNetwork =
faceUrl != null && faceUrl.isNotEmpty && (faceUrl.startsWith('http://') || faceUrl.startsWith('https://'));
if (isNetwork) {
return Image.network(
faceUrl,
height: 50.0,
width: 50.0,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return Image.asset(
'assets/images/pic1.jpg',
height: 50.0,
width: 50.0,
fit: BoxFit.cover,
);
},
);
} else {
return Image.asset(
(faceUrl != null && faceUrl.isNotEmpty) ? faceUrl : 'assets/images/pic1.jpg',
height: 50.0,
width: 50.0,
fit: BoxFit.cover,
);
}
},
),
),
@ -335,9 +371,17 @@ class ChatPageState extends State<ChatPage> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// Text(
// chatList[index].conversation.showName ?? '',
// style: const TextStyle(fontSize: 16.0),
// ),
Text(
chatList[index].conversation.showName ?? '',
style: const TextStyle(fontSize: 16.0),
style: TextStyle(
fontSize: (chatList[index].conversation.conversationGroupList?.isNotEmpty ?? false) ? 20 : 16,
fontWeight:
(chatList[index].conversation.conversationGroupList?.isNotEmpty ?? false) ? FontWeight.bold : FontWeight.normal,
),
),
const SizedBox(height: 2.0),
Text(
@ -351,6 +395,7 @@ class ChatPageState extends State<ChatPage> {
),
),
//
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
@ -396,7 +441,7 @@ class ChatPageState extends State<ChatPage> {
posDY = details.globalPosition.dy;
},
onLongPress: () {
showContextMenu(context);
showContextMenu(context, chatList[index]);
},
),
);

View File

@ -1,9 +1,24 @@
///
library;
import 'dart:convert';
import 'package:card_swiper/card_swiper.dart';
import 'package:flutter/material.dart';
import 'package:flutter_html/flutter_html.dart';
import 'package:get/get.dart';
import 'package:loopin/IM/controller/chat_controller.dart';
import 'package:loopin/IM/im_message.dart';
import 'package:loopin/IM/im_service.dart';
import 'package:loopin/api/shop_api.dart';
import 'package:loopin/components/my_toast.dart';
import 'package:loopin/components/network_or_asset_image.dart';
import 'package:loopin/models/summary_type.dart';
import 'package:loopin/service/http.dart';
import 'package:loopin/utils/index.dart';
import 'package:loopin/utils/wxsdk.dart';
import 'package:shirne_dialog/shirne_dialog.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart';
import '../../behavior/custom_scroll_behavior.dart';
import '../../components/backtop.dart';
@ -16,18 +31,29 @@ class Goods extends StatefulWidget {
}
class _GoodsState extends State<Goods> {
// late int shopId; //id
dynamic shopObj;
late ScrollController scrollController = ScrollController();
final ChatController chatController = Get.find<ChatController>();
//
double scrollOffset = 0;
//
List shareList = [
{'icon': 'assets/images/share-wx.png', 'label': '好友'},
{'icon': 'assets/images/share-wx.png', 'label': '微信'},
{'icon': 'assets/images/share-pyq.png', 'label': '朋友圈'},
];
@override
void initState() {
super.initState();
final shopId = Get.arguments;
scrollController.addListener(() {
setState(() {
scrollOffset = scrollController.offset;
});
});
shopDetail(shopId);
}
@override
@ -36,8 +62,199 @@ class _GoodsState extends State<Goods> {
super.dispose();
}
///
void shopDetail(shopId) async {
try {
final res = await Http.get('${ShopApi.shopDetail}/$shopId');
logger.e(res['data']);
setState(() {
shopObj = res['data']; // data
});
} catch (e) {
logger.e(e);
Get.back();
}
}
void handleShareClick(int index) {
final description = shopObj['describe']; //
if (index == 1) {
//
Wxsdk.shareToFriend(title: '快看看我分享的商品', description: description, webpageUrl: 'https://baidu.com');
} else if (index == 2) {
//
Wxsdk.shareToTimeline(title: '快看看我分享的商品', webpageUrl: 'https://baidu.com');
}
}
void handlCoverClick(V2TimConversation conv) async {
// VideoMsg,
final userId = conv.userID;
//price,title,url,sell
final makeJson = jsonEncode({
"price": shopObj['price'],
"title": shopObj['describe'],
"url": shopObj['pic'],
"sell": Utils().graceNumber(int.parse(shopObj['sales'] ?? '0')),
});
final res = await IMMessage().createCustomMessage(
data: makeJson,
);
if (res.success) {
final sendRes = await IMMessage().sendMessage(msg: res.data!.messageInfo!, toUserID: userId, cloudCustomData: SummaryType.shareTuangou);
if (sendRes.success) {
MyToast().tip(
title: '分享成功',
position: 'center',
type: 'success',
);
Get.back();
} else {
logger.e(res.desc);
}
} else {
logger.e(res.desc);
}
}
//
void handleShare() {
if (chatController.chatList.isNotEmpty) {
chatController.getConversationList();
}
showModalBottomSheet(
backgroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(15.0)),
),
clipBehavior: Clip.antiAlias,
context: context,
isScrollControlled: true,
builder: (context) {
return Material(
color: Colors.white,
child: Padding(
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
//
SizedBox(
height: 110,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: shareList.length,
padding: EdgeInsets.symmetric(horizontal: 0, vertical: 20.0),
itemBuilder: (context, index) {
return GestureDetector(
onTap: () => handleShareClick(index),
child: Container(
width: 64,
margin: EdgeInsets.symmetric(horizontal: 8.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Image.asset('${shareList[index]['icon']}', width: 48.0),
SizedBox(height: 5),
Text(
'${shareList[index]['label']}',
style: TextStyle(fontSize: 12.0),
overflow: TextOverflow.ellipsis,
),
],
),
),
);
},
),
),
//
Obx(() {
//
final filteredList = chatController.chatList.where((item) => item.conversation.conversationGroupList?.isEmpty == true).toList();
if (filteredList.isEmpty) return SizedBox.shrink();
return SizedBox(
height: 110,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: filteredList.length,
padding: EdgeInsets.symmetric(horizontal: 0, vertical: 20.0),
itemBuilder: (context, index) {
return GestureDetector(
//
onTap: () => handlCoverClick(filteredList[index].conversation),
child: Container(
width: 64,
margin: EdgeInsets.symmetric(horizontal: 8.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Image.asset('${chatController.chatList[index].faceUrl}', width: 48.0),
NetworkOrAssetImage(
imageUrl: filteredList[index].faceUrl,
width: 48.0,
height: 48.0,
),
SizedBox(height: 5),
Text(
'${filteredList[index].conversation.showName}',
style: TextStyle(fontSize: 12.0),
overflow: TextOverflow.ellipsis,
),
],
),
),
);
},
),
);
}),
//
SafeArea(
top: false,
child: InkWell(
onTap: () => Get.back(),
child: Container(
alignment: Alignment.center,
width: double.infinity,
height: 50.0,
color: Colors.grey[50],
child: Text(
'取消',
style: TextStyle(color: Colors.black87),
),
),
),
),
],
),
),
);
},
);
}
@override
Widget build(BuildContext context) {
if (shopObj == null) {
return Center(child: CircularProgressIndicator());
}
String swiperInfo = shopObj['albumPics'] ?? "";
List<String> swiperList;
if (swiperInfo.isNotEmpty) {
swiperList = swiperInfo.split(','); //
} else {
swiperList = [];
}
dynamic attr = shopObj['productAttr']; //json数据
List<dynamic> attrList = [];
if (!Utils.isEmpty(attr)) {
attrList = jsonDecode(attr);
}
logger.e(attrList);
return Scaffold(
backgroundColor: Colors.grey[50],
body: CustomScrollView(
@ -63,32 +280,26 @@ class _GoodsState extends State<Goods> {
},
),
actions: [
IconButton(
icon: Icon(
Icons.search,
size: 20.0,
),
onPressed: () {},
),
IconButton(
icon: Icon(
Icons.favorite_border,
size: 20.0,
),
onPressed: () {},
),
IconButton(
icon: Icon(
Icons.share,
size: 20.0,
),
onPressed: () {},
onPressed: () {
//
handleShare();
},
),
],
// ()
flexibleSpace: Container(
decoration:
BoxDecoration(gradient: LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [Color(0xFFFF5000), Color(0xFFFFAA00)])),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [Color(0xFFFF5000), Color(0xFFFFAA00)],
),
),
child: FlexibleSpaceBar(
background: ScrollConfiguration(
behavior: CustomScrollBehavior(),
@ -99,20 +310,7 @@ class _GoodsState extends State<Goods> {
activeColor: Colors.white,
)),
indicatorLayout: PageIndicatorLayout.SCALE,
children: [
Image.network(
'https://img13.360buyimg.com/n1/jfs/t1/263909/5/4187/123220/676eb220F3e481086/0cee829b1894fc4c.jpg',
fit: BoxFit.cover,
),
Image.network(
'https://img13.360buyimg.com/n1/jfs/t1/245928/34/24374/150795/673b0a0cFdb8831f9/9235d1ed7654aa44.jpg',
fit: BoxFit.cover,
),
Image.network(
'https://img30.360buyimg.com/n1/jfs/t1/240005/25/26374/136411/6756e1f9Fb685b2ec/3be83b3e1a08169d.jpg',
fit: BoxFit.cover,
),
],
children: swiperList.map((sw) => NetworkOrAssetImage(imageUrl: sw)).toList(),
),
),
),
@ -126,7 +324,12 @@ class _GoodsState extends State<Goods> {
Container(
padding: EdgeInsets.fromLTRB(15.0, 10.0, 15.0, 25.0),
decoration: BoxDecoration(
gradient: LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [Color(0xFFFF5000), Color(0xFFFFAA00)])),
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [Color(0xFFFF5000), Color(0xFFFFAA00)],
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 5.0,
@ -134,16 +337,17 @@ class _GoodsState extends State<Goods> {
Row(
spacing: 5.0,
children: [
Text(
'¥3838',
style: TextStyle(
color: Colors.white,
fontSize: 16.0,
decoration: TextDecoration.lineThrough,
decorationColor: Colors.black,
decorationThickness: 1.5,
),
),
//
// Text(
// '¥${shopObj['price']}',
// style: TextStyle(
// color: Colors.white,
// fontSize: 16.0,
// decoration: TextDecoration.lineThrough,
// decorationColor: Colors.black,
// decorationThickness: 1.5,
// ),
// ),
Container(
padding: EdgeInsets.symmetric(horizontal: 8.0, vertical: 3.0),
decoration: BoxDecoration(
@ -151,21 +355,23 @@ class _GoodsState extends State<Goods> {
borderRadius: BorderRadius.circular(50.0),
),
child: Text(
'现价¥3800',
'¥${shopObj['price']}',
style: TextStyle(color: Colors.red, fontSize: 12.0),
),
),
Text(
// '已售${Utils().graceNumber(shopObj['sales'] ?? 0)}',
'已售${Utils().graceNumber(int.tryParse(shopObj['sales']?.toString() ?? '0') ?? 0)}',
style: TextStyle(color: Colors.white, fontSize: 12.0),
),
],
),
Text(
'已售1.1w',
style: TextStyle(color: Colors.white, fontSize: 12.0),
),
],
),
),
Container(
padding: EdgeInsets.fromLTRB(10.0, 10.0, 10.0, 0),
width: double.infinity,
decoration: BoxDecoration(
color: Color(0xFFFAFAFA),
borderRadius: BorderRadius.vertical(top: Radius.circular(15.0)),
@ -176,18 +382,45 @@ class _GoodsState extends State<Goods> {
//
Container(
padding: EdgeInsets.all(5.0),
child: Text.rich(
TextSpan(children: [
TextSpan(text: ' 年货节 ', style: TextStyle(fontSize: 11.0, backgroundColor: const Color(0xFFFF5000), color: Colors.white)),
child: Align(
alignment: Alignment.centerLeft,
child: Text.rich(
TextSpan(
text: ' 茅台MOUTAI飞天 53度 酱香型白酒 500ml*2 海外版送礼袋年货送礼',
style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.w700),
children: [
WidgetSpan(
alignment: PlaceholderAlignment.middle,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
decoration: BoxDecoration(
color: const Color(0xFFFF5000),
borderRadius: BorderRadius.circular(4),
),
child: Text(
shopObj['productCategoryName'] ?? '未知分类名称',
style: const TextStyle(
fontSize: 12.0,
color: Colors.white,
),
),
),
),
const WidgetSpan(child: SizedBox(width: 4)),
TextSpan(
text: '${shopObj['describe'] ?? ''}',
style: TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.w700,
),
),
],
),
]),
maxLines: 2,
overflow: TextOverflow.ellipsis,
maxLines: 2,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.left,
),
),
),
//
Container(
margin: EdgeInsets.only(top: 10.0),
@ -196,84 +429,90 @@ class _GoodsState extends State<Goods> {
color: Colors.white,
borderRadius: BorderRadius.circular(15.0),
),
// child: Column(
// spacing: 10.0,
// children: [
// Row(
// spacing: 5.0,
// children: [
// Icon(
// Icons.timer,
// size: 16.0,
// ),
// Expanded(
// child: Text(
// '本商品请于2025.01.25前进行核销',
// style: TextStyle(fontSize: 12.0),
// ),
// ),
// ],
// ),
// Row(
// spacing: 5.0,
// children: [
// Icon(
// Icons.house_outlined,
// size: 16.0,
// ),
// Expanded(
// child: Text(
// '营业时间7x24',
// style: TextStyle(fontSize: 12.0),
// ),
// ),
// ],
// ),
// Row(
// spacing: 5.0,
// children: [
// Icon(
// Icons.location_on,
// size: 16.0,
// ),
// Expanded(
// child: Text(
// '河北省唐山市玉田县',
// style: TextStyle(fontSize: 12.0),
// ),
// ),
// ],
// ),
// ],
// ),
child: Column(
spacing: 10.0,
children: [
Row(
spacing: 5.0,
crossAxisAlignment: CrossAxisAlignment.start,
children: attrList.map<Widget>((attr) {
final attrName = attr['name'] ?? '';
final options = attr['options'] as List<dynamic>? ?? [];
final optionNames = options.map((o) => o['name']).join(' / ');
return Row(
children: [
Icon(
Icons.timer,
size: 16.0,
Text(
'$attrName: ',
style: TextStyle(fontSize: 12, fontWeight: FontWeight.bold),
),
Expanded(
child: Text(
'本商品请于2025.01.25前进行核销',
style: TextStyle(fontSize: 12.0),
optionNames,
style: TextStyle(fontSize: 12),
),
),
],
),
Row(
spacing: 5.0,
children: [
Icon(
Icons.house_outlined,
size: 16.0,
),
Expanded(
child: Text(
'营业时间7x24',
style: TextStyle(fontSize: 12.0),
),
),
],
),
Row(
spacing: 5.0,
children: [
Icon(
Icons.location_on,
size: 16.0,
),
Expanded(
child: Text(
'河北省唐山市玉田县',
style: TextStyle(fontSize: 12.0),
),
),
],
),
],
);
}).toList(),
),
),
//
Container(
margin: EdgeInsets.only(top: 10.0),
padding: EdgeInsets.all(10.0),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(15.0),
),
child: Column(
spacing: 10.0,
children: [
Text('【飞天茅台】传承悠久,酱香型白酒典范,四大名酒之一。 【爆款直降】纯粮酿造,固态发酵。 【精髓制作】工艺精湛,入口绵、落口甜、饮后余香。'),
Image.network(
'https://img30.360buyimg.com/n1/jfs/t1/187328/18/54595/115429/6756e1c7F126ab0d4/fe96f6fd5dfe125d.jpg',
fit: BoxFit.contain,
),
Image.network(
'https://img30.360buyimg.com/n1/jfs/t1/240005/25/26374/136411/6756e1f9Fb685b2ec/3be83b3e1a08169d.jpg',
fit: BoxFit.contain,
),
Image.network(
'https://img30.360buyimg.com/n1/jfs/t1/247398/2/28177/97778/6756da95F518f621c/746dc23032c171ca.jpg',
fit: BoxFit.contain,
),
],
),
),
margin: EdgeInsets.only(top: 10.0),
padding: EdgeInsets.all(10.0),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(15.0),
),
child: Html(
data: shopObj['detailMobileHtml'] ?? '暂无',
)),
],
),
),
@ -296,50 +535,66 @@ class _GoodsState extends State<Goods> {
child: Row(
spacing: 15.0,
children: [
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.store,
color: Color(0xFFFF5000),
size: 18.0,
),
Text(
'店铺',
style: TextStyle(fontSize: 12.0),
)
],
),
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.child_care_outlined,
size: 18.0,
),
Text(
'客服',
style: TextStyle(fontSize: 12.0),
)
],
),
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Badge.count(
backgroundColor: Color(0xFFFF5000),
count: 6,
child: Icon(
Icons.shopping_cart_outlined,
// Column(
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// Icon(
// Icons.store,
// color: Color(0xFFFF5000),
// size: 18.0,
// ),
// Text(
// '店铺',
// style: TextStyle(fontSize: 12.0),
// )
// ],
// ),
GestureDetector(
onTap: () async {
//
logger.i('联系客服');
final res = await ImService.instance.getConversation(conversationID: 'c2c_${shopObj['shoperId']}');
V2TimConversation conversation = res.data;
logger.i(conversation.toLogString());
if (res.success) {
//
conversation.showName = conversation.showName ?? shopObj['storeName'];
Get.toNamed('/chat', arguments: conversation);
} else {
MyDialog.toast(res.desc, icon: const Icon(Icons.warning), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200)));
}
},
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.child_care_outlined,
size: 18.0,
),
),
Text(
'购物车',
style: TextStyle(fontSize: 12.0),
)
],
),
Text(
'联系商家',
style: TextStyle(fontSize: 12.0),
)
],
),
)
// Column(
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// Badge.count(
// backgroundColor: Color(0xFFFF5000),
// count: 6,
// child: Icon(
// Icons.shopping_cart_outlined,
// size: 18.0,
// ),
// ),
// Text(
// '购物车',
// style: TextStyle(fontSize: 12.0),
// )
// ],
// ),
],
),
),
@ -353,19 +608,19 @@ class _GoodsState extends State<Goods> {
),
child: Row(
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: 10.0),
child: Text(
'加入购物车',
style: TextStyle(color: Color(0xFFFF5000), fontSize: 14.0),
),
),
// Padding(
// padding: EdgeInsets.symmetric(horizontal: 10.0),
// child: Text(
// '加入购物车',
// style: TextStyle(color: Color(0xFFFF5000), fontSize: 14.0),
// ),
// ),
Container(
alignment: Alignment.center,
padding: const EdgeInsets.symmetric(horizontal: 20.0),
color: Color(0xFFFF5000),
child: Text(
'领券购买',
'立即购买',
style: TextStyle(color: Colors.white, fontSize: 14.0),
),
),

View File

@ -1,18 +1,15 @@
///
library;
import 'dart:ui';
import 'package:card_swiper/card_swiper.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_message.dart';
import 'package:loopin/components/custom_sticky_header.dart';
import '../../behavior/custom_scroll_behavior.dart';
import '../../components/backtop.dart';
import '../../components/loading.dart';
import 'package:loopin/components/backtop.dart';
import 'package:loopin/components/loading.dart';
import 'package:loopin/controller/shop_index_controller.dart';
import 'package:loopin/utils/index.dart';
class IndexPage extends StatefulWidget {
const IndexPage({super.key});
@ -23,133 +20,25 @@ class IndexPage extends StatefulWidget {
class _IndexPageState extends State<IndexPage> with SingleTickerProviderStateMixin {
//
List cateList = [
{
'id': 1,
'list': [
{
'icon': 'order.svg',
'label': '我的订单',
},
{
'icon': 'chongzhi.svg',
'label': '充值中心',
},
{'icon': 'qianbao.svg', 'label': '余额'},
{'icon': 'comment.svg', 'label': '评价中心'}
]
}
];
List<String> tabList = ['推荐', '美食', '娱乐', '文旅', '医疗', '房产'];
//
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': '美的MideaLED便携充电小台灯书桌学习阅读灯学生宿舍卧室床头灯学习台灯',
'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'
},
];
//
List dataList = [];
//
bool isLoading = false;
late ScrollController scrollController = ScrollController();
late TabController tabController = TabController(initialIndex: 0, length: tabList.length, vsync: this);
final PageController pageController = PageController();
//
double scrollOffset = 0;
//
loadMoreData() async {
setState(() {
isLoading = true;
});
await Future.delayed(Duration(seconds: 1));
setState(() {
dataList.addAll(waterfallData);
isLoading = false;
});
}
//
Future<void> handleRefresh() async {
await Future.delayed(Duration(seconds: 1));
dataList.clear();
for (int i = 0; i < waterfallData.length; i++) {
dataList.add(waterfallData[i]);
}
if (mounted) {
setState(() {});
}
}
// 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();
final ShopIndexController controller = Get.put(ShopIndexController());
//
Widget cardList(item) {
@ -166,7 +55,7 @@ class _IndexPageState extends State<IndexPage> with SingleTickerProviderStateMix
]),
child: Column(
children: [
Image.network('${item['image']}'),
Image.network('${item['pic']}'),
Container(
padding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 5.0),
child: Column(
@ -174,7 +63,7 @@ class _IndexPageState extends State<IndexPage> with SingleTickerProviderStateMix
spacing: 5.0,
children: [
Text(
'${item['title']}',
'${item['name']}',
style: TextStyle(fontSize: 14.0, height: 1.2),
maxLines: 2,
overflow: TextOverflow.ellipsis,
@ -193,13 +82,13 @@ class _IndexPageState extends State<IndexPage> with SingleTickerProviderStateMix
]),
),
Text(
'已售${item['saleNum']}',
'已售${Utils().graceNumber(int.parse(item['sales'] ?? '0'))}',
style: TextStyle(color: Colors.grey, fontSize: 10.0),
),
],
),
Text(
'${item['shop']}',
'${item['storeName']}',
style: TextStyle(color: Colors.grey, fontSize: 12.0),
),
],
@ -209,7 +98,7 @@ class _IndexPageState extends State<IndexPage> with SingleTickerProviderStateMix
),
),
onTap: () {
Get.toNamed('/goods');
Get.toNamed('/goods', arguments: item['id']);
},
);
}
@ -217,282 +106,194 @@ class _IndexPageState extends State<IndexPage> with SingleTickerProviderStateMix
@override
void initState() {
super.initState();
scrollController.addListener(() {
setState(() {
scrollOffset = scrollController.offset;
});
if (scrollController.position.pixels == scrollController.position.maxScrollExtent) {
debugPrint('[index]滚动到底部');
if (!isLoading) {
loadMoreData();
}
}
});
//
handleRefresh();
}
@override
void dispose() {
scrollController.dispose();
tabController.dispose();
pageController.dispose();
super.dispose();
controller.initTabs();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[50],
body: ScrollConfiguration(
behavior: CustomScrollBehavior().copyWith(scrollbars: false),
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(200),
),
child: TextField(
decoration: InputDecoration(
isDense: true,
hintText: "2025百亿补贴",
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: [
Icon(
Icons.keyboard_voice,
color: Colors.black45,
size: 21.0,
),
Icon(
Icons.camera_alt_outlined,
color: Colors.black45,
size: 21.0,
),
],
),
),
contentPadding: EdgeInsets.symmetric(vertical: 0, horizontal: 10.0),
border: OutlineInputBorder(borderSide: BorderSide.none, borderRadius: BorderRadius.circular(30.0))),
cursorColor: Colors.black,
onChanged: (val) {
debugPrint(val);
},
),
),
),
return Obx(() {
final tabIndex = controller.currentTabIndex.value;
final currentTab = controller.tabs[tabIndex];
return Scaffold(
backgroundColor: Colors.grey[50],
body: Column(
children: [
// + TabBar
_buildTopSection(),
//
Expanded(
child: TabBarView(
controller: controller.tabController,
children: controller.tabList.asMap().entries.map((entry) {
final index = entry.key;
return _buildTabContent(index);
}).toList(),
),
actions: [
IconButton(
icon: Icon(Icons.shopping_cart_outlined),
onPressed: () {},
),
],
// ()
flexibleSpace: Container(
decoration: BoxDecoration(
gradient: LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [Color(0xFFFF5000), Color(0xFFfcaec4)])),
child: FlexibleSpaceBar(
background: Swiper.children(
pagination: SwiperPagination(
builder: DotSwiperPaginationBuilder(
),
],
),
floatingActionButton: currentTab != null
? Backtop(
controller: currentTab.scrollController,
offset: currentTab.scrollOffset.value,
)
: null,
);
});
}
//
Widget _buildTopSection() {
double screenWidth = MediaQuery.of(context).size.width;
int tabCount = controller.tabList.length;
// Tab
double minTabWidth = 80;
//
bool isScrollable = tabCount * minTabWidth > screenWidth;
return Column(
children: [
//
Container(
width: double.infinity,
height: 240,
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [Color(0xFFFF5000), Color(0xFFfcaec4)],
),
),
child: controller.swiperData.length <= 1
? (controller.swiperData.isNotEmpty
? Image.network(
controller.swiperData.first['images'] ?? '',
fit: BoxFit.fill,
)
: const SizedBox.shrink())
: Swiper(
itemCount: controller.swiperData.length,
autoplay: true,
loop: true,
pagination: SwiperPagination(
builder: DotSwiperPaginationBuilder(
color: Colors.white70,
activeColor: Colors.white,
)),
indicatorLayout: PageIndicatorLayout.SCALE,
children: [
Image.network(
'https://m.360buyimg.com/babel/jfs/t20271217/224114/35/38178/150060/6760d559Fd654f946/968c156726b6e822.png',
fit: BoxFit.fill,
),
Image.network(
'https://m.360buyimg.com/babel/jfs/t20280117/88832/5/48468/139826/6789cbcfF4e0b2a3d/9dc54355b6f65c40.jpg',
fit: BoxFit.fill,
),
Image.network(
'https://m.360buyimg.com/babel/jfs/t20280108/255505/29/10540/137372/677ddbc1F6cdbbed0/bc477fadedef22a8.jpg',
fit: BoxFit.fill,
),
],
),
),
),
),
//
// SliverToBoxAdapter(
// child: Container(
// margin: EdgeInsets.all(10.0),
// padding: EdgeInsets.symmetric(vertical: 10.0),
// height: 90.0,
// clipBehavior: Clip.antiAlias,
// decoration: BoxDecoration(
// color: Colors.white,
// borderRadius: BorderRadius.circular(15.0),
// ),
// child: Column(
// children: [
// Expanded(
// child: PageView.builder(
// controller: pageController,
// itemCount: cateList.length,
// itemBuilder: (context, index) {
// final item = cateList[index];
// return GridView.builder(
// shrinkWrap: true,
// padding: EdgeInsets.zero,
// physics: NeverScrollableScrollPhysics(),
// gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
// crossAxisCount: 4,
// ),
// itemCount: item['list'].length,
// itemBuilder: (BuildContext context, int index) {
// final citem = item['list'][index];
// // return Column(
// // spacing: 3.0,
// // children: [
// // if (citem['icon'] != null)
// // SvgPicture.asset(
// // 'assets/images/svg/${citem['icon']}',
// // height: 30.0,
// // width: 30.0,
// // ),
// // Text(citem['label']),
// // ],
// // );
// return GestureDetector(
// onTap: () {
// logger.i('点击了$index');
// //
// Get.toNamed('/order');
// },
// child: Column(
// mainAxisSize: MainAxisSize.min,
// children: [
// if (citem['icon'] != null)
// SvgPicture.asset(
// 'assets/images/svg/${citem['icon']}',
// height: 30.0,
// width: 30.0,
// ),
// Text(citem['label']),
// ],
// ),
// );
// },
// );
// },
// ),
// ),
// CustomPageViewIndicator(
// controller: pageController,
// count: cateList.length,
// color: Color(0xFFCECECE),
// activeColor: Color(0xFFFF5000),
// ),
// ],
// )),
// ),
// tabbar列表
SliverPersistentHeader(
pinned: true,
delegate: CustomStickyHeader(
child: PreferredSize(
preferredSize: Size.fromHeight(45.0),
child: Container(
color: Colors.white,
height: 45.0,
child: TabBar(
controller: tabController,
onTap: (index) {
logger.i('点击了第 $index 个 tab');
},
tabs: tabList.map((v) => Tab(text: v)).toList(),
isScrollable: false,
overlayColor: WidgetStateProperty.all(Colors.transparent),
unselectedLabelColor: Colors.black87,
labelColor: Color(0xFFFF5000),
indicatorColor: Color(0xFFFF5000),
indicatorSize: TabBarIndicatorSize.tab,
unselectedLabelStyle: TextStyle(fontSize: 15.0, fontFamily: 'Microsoft YaHei'),
labelStyle: TextStyle(fontSize: 15.0, fontFamily: 'Microsoft YaHei', fontWeight: FontWeight.w700),
dividerHeight: 0,
padding: EdgeInsets.symmetric(horizontal: 10.0),
labelPadding: EdgeInsets.symmetric(horizontal: 7.5),
indicatorPadding: EdgeInsets.symmetric(horizontal: 15.0, vertical: 5.0),
),
),
itemBuilder: (context, index) {
final imageUrl = controller.swiperData[index]['images'] ?? '';
return imageUrl.isNotEmpty ? Image.network(imageUrl, fit: BoxFit.fill) : const SizedBox.shrink();
},
),
),
),
),
//
// TabBar
Container(
color: Colors.white,
child: TabBar(
controller: controller.tabController,
tabs: controller.tabList.map((item) {
return Tab(
child: Text(item['name'], style: const TextStyle(fontWeight: FontWeight.bold)),
);
}).toList(),
isScrollable: isScrollable,
overlayColor: WidgetStateProperty.all(Colors.transparent),
unselectedLabelColor: Colors.black87,
labelColor: Color.fromARGB(255, 236, 108, 49),
indicator: const UnderlineTabIndicator(borderSide: BorderSide(color: Color.fromARGB(255, 236, 108, 49), width: 2.0)),
unselectedLabelStyle: const TextStyle(fontSize: 16.0, fontFamily: 'Microsoft YaHei'),
labelStyle: const TextStyle(fontSize: 18.0, fontFamily: 'Microsoft YaHei', fontWeight: FontWeight.bold),
dividerHeight: 0,
),
),
],
);
}
//
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: '刷新完成',
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: Container(
padding: EdgeInsets.all(10.0),
child: Column(
children: [
dataList.isEmpty
?
// loading提示
Column(
children: [
RefreshProgressIndicator(
backgroundColor: Colors.white,
color: Color(0xFFFF5000),
),
],
)
: MasonryGridView.count(
shrinkWrap: true,
padding: EdgeInsets.zero,
physics: NeverScrollableScrollPhysics(),
crossAxisCount: 2,
mainAxisSpacing: 10.0,
crossAxisSpacing: 10.0,
itemCount: dataList.length + (isLoading ? 1 : 0),
itemBuilder: (BuildContext context, int index) {
if (index < dataList.length) {
return cardList(dataList[index]);
} else {
return SizedBox.shrink();
}
},
),
Opacity(opacity: dataList.isNotEmpty && isLoading ? 1 : 0, child: Loading(title: 'loading...')),
],
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: 100),
const SizedBox(height: 8),
Text(
text,
style: const TextStyle(color: Colors.grey, fontSize: 13),
),
],
),
),
//
floatingActionButton: Backtop(controller: scrollController, offset: scrollOffset),
);
}
}

View File

@ -0,0 +1,497 @@
///
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/behavior/custom_scroll_behavior.dart';
import 'package:loopin/components/backtop.dart';
import 'package:loopin/components/custom_sticky_header.dart';
import 'package:loopin/components/loading.dart';
import 'package:loopin/components/only_down_scroll_physics.dart';
import 'package:loopin/controller/shop_index_controller.dart';
class IndexPage extends StatefulWidget {
const IndexPage({super.key});
@override
State<IndexPage> createState() => _IndexPageState();
}
class _IndexPageState extends State<IndexPage> with SingleTickerProviderStateMixin {
//
// List cateList = [
// {
// 'id': 1,
// 'list': [
// {
// 'icon': 'order.svg',
// 'label': '我的订单',
// },
// {
// 'icon': 'chongzhi.svg',
// 'label': '充值中心',
// },
// {'icon': 'qianbao.svg', 'label': '余额'},
// {'icon': 'comment.svg', 'label': '评价中心'}
// ]
// }
// ];
final ScrollController pageScrollController = ScrollController();
final ShopIndexController controller = Get.put(ShopIndexController());
//
Future<void> handleRefresh() async {}
///
void shopDetail() async {
// final res = await Http.get('${ShopApi.shopDetail}/1938137499482869762');
// logger.e(res['data']);
}
//
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('${item['pic']}'),
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(
'已售${item['sales']}',
style: TextStyle(color: Colors.grey, fontSize: 10.0),
),
],
),
Text(
'${item['shop']}',
style: TextStyle(color: Colors.grey, fontSize: 12.0),
),
],
),
)
],
),
),
onTap: () {
Get.toNamed('/goods');
},
);
}
@override
void initState() {
super.initState();
controller.initTabs();
}
@override
Widget build(BuildContext context) {
return Obx(() {
final tabIndex = controller.currentTabIndex.value;
final scrollController = controller.tabs[tabIndex]?.scrollController;
final pagesView = controller.tabs[tabIndex];
return Scaffold(
backgroundColor: Colors.grey[50],
body: ScrollConfiguration(
behavior: CustomScrollBehavior().copyWith(scrollbars: false),
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(200),
// ),
// child: TextField(
// decoration: InputDecoration(
// isDense: true,
// hintText: "2025百亿补贴",
// 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: [
// Icon(
// Icons.keyboard_voice,
// color: Colors.black45,
// size: 21.0,
// ),
// Icon(
// Icons.camera_alt_outlined,
// color: Colors.black45,
// size: 21.0,
// ),
// ],
// ),
// ),
// contentPadding: EdgeInsets.symmetric(vertical: 0, horizontal: 10.0),
// border: OutlineInputBorder(borderSide: BorderSide.none, borderRadius: BorderRadius.circular(30.0))),
// cursorColor: Colors.black,
// onChanged: (val) {
// debugPrint(val);
// },
// ),
// ),
// ),
// ),
// actions: [
// IconButton(
// icon: Icon(Icons.shopping_cart_outlined),
// onPressed: () {},
// ),
// ],
// ()
flexibleSpace: Container(
decoration: BoxDecoration(
gradient: LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [Color(0xFFFF5000), Color(0xFFfcaec4)])),
child: FlexibleSpaceBar(
background: Swiper.children(
pagination: SwiperPagination(
builder: DotSwiperPaginationBuilder(
color: Colors.white70,
activeColor: Colors.white,
),
),
indicatorLayout: PageIndicatorLayout.SCALE,
children: [
...controller.swiperData.map((item) {
final imageUrl = item['images'] ?? '';
return imageUrl.isNotEmpty
? Image.network(
imageUrl,
fit: BoxFit.fill,
)
: SizedBox.shrink();
}),
],
),
),
),
),
// tabbar列表
SliverPersistentHeader(
pinned: true,
delegate: CustomStickyHeader(
child: PreferredSize(
preferredSize: Size.fromHeight(45.0),
child: Container(
color: Colors.white,
height: 45.0,
child: TabBar(
controller: controller.tabController,
onTap: (index) {
print('点击了第 $index 个 tab');
},
tabs: controller.tabList.map((v) => Tab(text: v['name'])).toList(),
isScrollable: true,
overlayColor: WidgetStateProperty.all(Colors.transparent),
unselectedLabelColor: Colors.black87,
labelColor: Color(0xFFFF5000),
indicatorColor: Color(0xFFFF5000),
indicatorSize: TabBarIndicatorSize.tab,
unselectedLabelStyle: TextStyle(fontSize: 15.0, fontFamily: 'Microsoft YaHei'),
labelStyle: TextStyle(fontSize: 15.0, fontFamily: 'Microsoft YaHei', fontWeight: FontWeight.w700),
dividerHeight: 0,
padding: EdgeInsets.symmetric(horizontal: 10.0),
labelPadding: EdgeInsets.symmetric(horizontal: 7.5),
indicatorPadding: EdgeInsets.symmetric(horizontal: 15.0, vertical: 5.0),
),
),
),
),
),
//
SliverToBoxAdapter(
child: Container(
padding: EdgeInsets.all(10.0),
child: Column(
children: [
pagesView?.dataList.isEmpty ?? true
?
// loading提示
Column(
children: [
RefreshProgressIndicator(
backgroundColor: Colors.white,
color: Color(0xFFFF5000),
),
],
)
: MasonryGridView.count(
shrinkWrap: true,
padding: EdgeInsets.zero,
physics: NeverScrollableScrollPhysics(),
crossAxisCount: 2,
mainAxisSpacing: 10.0,
crossAxisSpacing: 10.0,
itemCount: ((pagesView?.dataList.length ?? 0) + (pagesView?.isLoading.value == true ? 1 : 0)),
itemBuilder: (BuildContext context, int index) {
final dataList = pagesView?.dataList ?? [];
if (index < dataList.length) {
return cardList(dataList[index]);
} else {
return SizedBox.shrink();
}
},
),
Opacity(
opacity: ((pagesView?.dataList.isNotEmpty ?? false) && (pagesView?.isLoading.value ?? false)) ? 1 : 0,
child: Loading(title: 'loading...'),
),
],
),
),
),
],
),
),
//
floatingActionButton: pagesView != null
? Backtop(
controller: pagesView.scrollController,
offset: pagesView.scrollOffset.value,
)
: null,
);
});
}
// @override
// Widget build(BuildContext context) {
// return Obx(() {
// final tabIndex = controller.currentTabIndex.value;
// final pagesView = controller.tabs[tabIndex];
// return Scaffold(
// backgroundColor: Colors.grey[50],
// body: NestedScrollViewPlus(
// // controller: pageScrollController,
// overscrollBehavior: OverscrollBehavior.outer,
// physics: (pagesView!.dataList.length > 4 && pagesView.currentPage > 1)
// ? const OnlyDownScrollPhysics(parent: AlwaysScrollableScrollPhysics())
// : const AlwaysScrollableScrollPhysics(),
// headerSliverBuilder: (context, innerBoxIsScrolled) {
// return [
// SliverAppBar(
// backgroundColor: Colors.transparent,
// foregroundColor: Colors.white,
// pinned: true,
// stretch: false,
// onStretchTrigger: () async {
// print('触发 stretch 拉伸');
// //
// },
// expandedHeight: 180.0,
// // collapsedHeight: kToolbarHeight,
// collapsedHeight: 180.0,
// // ()
// flexibleSpace: Container(
// decoration: BoxDecoration(
// gradient: LinearGradient(
// begin: Alignment.topLeft,
// end: Alignment.bottomRight,
// colors: [
// Color(0xFFFF5000),
// Color(0xFFfcaec4),
// ],
// ),
// ),
// child: FlexibleSpaceBar(
// background: Swiper.children(
// pagination: SwiperPagination(
// builder: DotSwiperPaginationBuilder(
// color: Colors.white70,
// activeColor: Colors.white,
// ),
// ),
// indicatorLayout: PageIndicatorLayout.SCALE,
// children: [
// ...controller.swiperData.map((item) {
// final imageUrl = item['images'] ?? '';
// return imageUrl.isNotEmpty
// ? Image.network(
// imageUrl,
// fit: BoxFit.fill,
// )
// : SizedBox.shrink();
// }),
// ],
// ),
// ),
// ),
// ),
// // tab吸顶
// SliverPersistentHeader(
// pinned: true,
// delegate: CustomStickyHeader(
// child: PreferredSize(
// preferredSize: const Size.fromHeight(48.0),
// child: Container(
// color: Colors.white,
// child: TabBar(
// controller: controller.tabController,
// tabs: controller.tabList.map((item) {
// return Tab(
// child: Text(item['name'], style: TextStyle(fontWeight: FontWeight.bold)),
// );
// }).toList(),
// isScrollable: false,
// overlayColor: WidgetStateProperty.all(Colors.transparent),
// unselectedLabelColor: Colors.black87,
// labelColor: const Color(0xFFFF5000),
// indicator: const UnderlineTabIndicator(borderSide: BorderSide(color: Color(0xFFFF5000), width: 2.0)),
// indicatorSize: TabBarIndicatorSize.tab,
// unselectedLabelStyle: const TextStyle(fontSize: 16.0, fontFamily: 'Microsoft YaHei'),
// labelStyle: const TextStyle(fontSize: 18.0, fontFamily: 'Microsoft YaHei', fontWeight: FontWeight.bold),
// dividerHeight: 0,
// padding: const EdgeInsets.symmetric(horizontal: 10.0),
// labelPadding: const EdgeInsets.symmetric(horizontal: 15.0),
// ),
// ),
// ),
// ),
// ),
// ];
// },
// body: TabBarView(
// controller: controller.tabController,
// children: controller.tabList.map((tabItem) {
// final index = controller.tabList.indexOf(tabItem);
// return buildTabContent(index);
// }).toList(),
// ),
// ),
// );
// });
// }
//view
Widget buildTabContent(int index) {
final tabState = controller.tabs[index]!;
return Obx(() {
if (tabState.dataList.isEmpty && tabState.isLoading.value) {
return const Center(
child: RefreshProgressIndicator(
backgroundColor: Colors.white,
color: Color(0xFFFF5000),
),
);
}
return CustomScrollView(
primary: false,
controller: tabState.scrollController,
slivers: [
SliverPadding(
padding: const EdgeInsets.all(10.0),
sliver: SliverMasonryGrid.count(
crossAxisCount: 2,
mainAxisSpacing: 10.0,
crossAxisSpacing: 10.0,
childCount: tabState.dataList.length,
itemBuilder: (context, idx) => cardList(tabState.dataList[idx]),
),
),
SliverFillRemaining(
hasScrollBody: false,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 20.0),
child: Center(
child: tabState.isLoading.value ? const Loading(title: 'loading...') : const Text('没有更多数据了'),
),
),
),
],
);
});
}
//
//
Widget emptyTip(String text) {
return CustomScrollView(
physics: const OnlyDownScrollPhysics(),
slivers: [
SliverFillRemaining(
hasScrollBody: false,
child: Align(
alignment: Alignment.topCenter,
child: Padding(
padding: const EdgeInsets.only(top: 50.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Image.asset('assets/images/empty.png', width: 100.0),
const SizedBox(height: 8.0),
Text(
text,
style: const TextStyle(color: Colors.grey, fontSize: 13.0),
),
],
),
),
),
),
],
);
}
}

88
lib/pages/my/des.dart Normal file
View File

@ -0,0 +1,88 @@
import 'package:flutter/material.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:form_builder_validators/form_builder_validators.dart';
import 'package:get/get.dart';
import 'package:loopin/IM/controller/im_user_info_controller.dart';
class Des extends StatefulWidget {
const Des({super.key});
@override
State<Des> createState() => _DesState();
}
class _DesState extends State<Des> {
final _formKey = GlobalKey<FormBuilderState>();
final userInfoController = Get.find<ImUserInfoController>();
void _save() async {
if (_formKey.currentState?.saveAndValidate() ?? false) {
final signature = _formKey.currentState?.fields['signature']?.value;
final result = await userInfoController.updateSignature(signature);
if (result) {
Get.back();
}
}
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => FocusScope.of(context).unfocus(),
child: Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
backgroundColor: Colors.white,
title: const Text('修改简介'),
actions: [
TextButton(
onPressed: _save,
child: const Text(
'保存',
style: TextStyle(color: Colors.red, fontSize: 16),
),
),
],
),
body: Padding(
padding: const EdgeInsets.all(16),
child: FormBuilder(
key: _formKey,
child: Obx(() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'简介',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
FormBuilderTextField(
name: 'signature',
initialValue: userInfoController.signature.value,
maxLines: 6, // 6
minLines: 3, // 3
decoration: const InputDecoration(
hintText: '请输入内容',
border: OutlineInputBorder(),
contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 10),
),
maxLength: 100,
validator: FormBuilderValidators.compose([
FormBuilderValidators.required(errorText: '内容不能为空'),
FormBuilderValidators.maxLength(100, errorText: '内容不能超过100个字符'),
]),
),
const SizedBox(height: 8),
const Text(
'最长支持100个字符请文明用语',
style: TextStyle(fontSize: 12, color: Colors.grey),
),
],
);
})),
),
),
);
}
}

View File

@ -3,12 +3,17 @@ import 'package:flutter/services.dart';
import 'package:flutter_svg/svg.dart';
import 'package:get/get.dart';
import 'package:get/get_rx/src/rx_typedefs/rx_typedefs.dart';
import 'package:loopin/IM/controller/im_user_info_controller.dart';
import 'package:loopin/IM/im_service.dart';
import 'package:loopin/api/video_api.dart';
import 'package:loopin/components/custom_sticky_header.dart';
import 'package:loopin/components/network_or_asset_image.dart';
import 'package:loopin/components/only_down_scroll_physics.dart';
import 'package:loopin/controller/video_module_controller.dart';
import 'package:loopin/service/http.dart';
import 'package:nested_scroll_view_plus/nested_scroll_view_plus.dart';
import 'package:shirne_dialog/shirne_dialog.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_follow_info.dart';
import '../../utils/common.dart';
@ -17,13 +22,26 @@ class PageParams {
int pageSize;
bool isLoading;
bool hasMore;
int total;
bool isInitLoading;
PageParams({
this.page = 1,
this.pageSize = 10,
this.isLoading = false,
this.hasMore = true,
this.total = 0,
this.isInitLoading = true,
});
void init() {
page = 1;
pageSize = 10;
isLoading = false;
hasMore = true;
total = 0;
isInitLoading = true;
}
}
class MyPage extends StatefulWidget {
@ -37,28 +55,38 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
late RxInt currentTabIndex = 0.obs;
late RxList items = [].obs;
late RxList favoriteItems = [].obs;
late RxMap userInfo = {}.obs;
RxBool isLogin = Common.isLogin().obs;
//
// late Rx<V2TimUserFullInfo?> userInfo = Rx<V2TimUserFullInfo?>(null);
ImUserInfoController? imUserInfoController;
//
late Rx<V2TimFollowInfo?> followInfo = Rx<V2TimFollowInfo?>(null);
RxBool get shouldFixHeader => (currentTabIndex.value == 0 && items.isEmpty) || (currentTabIndex.value == 1 && favoriteItems.isEmpty) ? true.obs : false.obs;
List tabList = [
{'name': "作品", 'badge': 99},
{'name': "作品"},
{'name': "喜欢"},
];
late PageParams itemsParams;
late PageParams favoriteParams;
PageParams itemsParams = PageParams();
PageParams favoriteParams = PageParams();
late TabController tabController;
late ScrollController scrollController;
RxDouble positions = 0.0.obs;
late Callback tabListener;
late Callback scrollListener;
RxBool isPinned = false.obs; //
@override
void initState() {
super.initState();
itemsParams = PageParams();
favoriteParams = PageParams();
initControllers();
scrollListener = () {
@ -77,16 +105,17 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
tabListener = () {
currentTabIndex.value = tabController.index;
scrollController.animateTo(0, duration: const Duration(milliseconds: 300), curve: Curves.easeIn);
if (tabController.index == 0 && items.isEmpty) {
loadData(0);
scrollInnerList();
} else if (tabController.index == 1 && favoriteItems.isEmpty) {
loadData(1);
scrollInnerList();
}
};
tabController.addListener(tabListener);
loadData(0);
// loadData(0);
}
@override
@ -99,87 +128,121 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
super.dispose();
}
void loadData([int? tabIndex]) async {
//
void scrollInnerList([double? offset]) async {
if (isPinned.value) {
//
WidgetsBinding.instance.addPostFrameCallback((_) {
isPinned.value = false;
//
scrollController.jumpTo(positions.value);
//
positions.value = 0.0;
});
}
}
Future<void> loadData([int? tabIndex]) async {
final index = tabIndex ?? currentTabIndex.value;
if (index == 0) {
if (itemsParams.isLoading || !itemsParams.hasMore) return;
itemsParams.isLoading = true;
// itemsParams.isInitLoading = true;
await Future.delayed(const Duration(seconds: 1));
try {
final res = await Http.post(VideoApi.myPublicList, data: {
"userId": imUserInfoController?.userID.value,
"yesOrNo": 0,
"current": itemsParams.page,
"size": itemsParams.pageSize,
});
final obj = res['data'];
final total = obj['total'];
final row = obj['rows'];
logger.i(res['data']);
//
if (items.length >= total) {
itemsParams.hasMore = false;
}
//
items.addAll(row);
//
List<String> newItems = List.generate(
itemsParams.pageSize,
(i) => '作品 ${(itemsParams.page - 1) * itemsParams.pageSize + i + 1}',
);
//
if (itemsParams.page >= 2) {
itemsParams.hasMore = false;
//
itemsParams.page++;
} finally {
itemsParams.isLoading = false;
itemsParams.isInitLoading = false;
}
//
items.addAll(newItems);
//
itemsParams.page++;
itemsParams.isLoading = false;
} else if (index == 1) {
//
if (favoriteParams.isLoading || !favoriteParams.hasMore) return;
favoriteParams.isLoading = true;
// favoriteParams.isInitLoading = true;
await Future.delayed(const Duration(seconds: 1));
try {
final res = await Http.post(VideoApi.myPublicList, data: {
"userId": imUserInfoController?.userID.value,
"yesOrNo": 0,
"current": itemsParams.page,
"size": itemsParams.pageSize,
});
final obj = res['data'];
final total = obj['total'];
final row = obj['rows'];
List<String> newFavorites = List.generate(
favoriteParams.pageSize,
(i) => '喜欢 ${(favoriteParams.page - 1) * favoriteParams.pageSize + i + 1}',
);
if (favoriteItems.length >= total) {
itemsParams.hasMore = false;
}
if (favoriteParams.page >= 2) {
favoriteParams.hasMore = false;
favoriteItems.addAll(row);
favoriteParams.page++;
} finally {
favoriteParams.isLoading = false;
favoriteParams.isInitLoading = false;
}
favoriteItems.addAll(newFavorites);
favoriteParams.page++;
favoriteParams.isLoading = false;
}
}
void initControllers() {
tabController = TabController(initialIndex: 0, length: tabList.length, vsync: this);
scrollController = ScrollController();
if (Common.isLogin()) {
imUserInfoController = Get.find<ImUserInfoController>();
}
}
//
void refreshData() {
void refreshData([int? tabIndex]) async {
if (!mounted) {
logger.i('未挂载');
return;
}
isLogin.value = Common.isLogin();
if (!Common.isLogin()) return;
itemsParams = PageParams();
favoriteParams = PageParams();
currentTabIndex.value = 0;
final idx = tabIndex ?? currentTabIndex.value;
//
WidgetsBinding.instance.addPostFrameCallback((_) {
scrollInnerList();
});
items.clear();
favoriteItems.clear();
scrollController.animateTo(0, duration: const Duration(milliseconds: 100), curve: Curves.easeIn);
itemsParams.init();
favoriteParams.init();
// currentTabIndex.value = 0;
selfInfo();
loadData();
loadData(idx);
}
//
void selfInfo() async {
final resIm = await ImService.instance.selfInfo();
if (resIm.success) {
for (var user in resIm.data ?? []) {
logger.i(user.toLogString());
}
// imUserInfoController = Get.find<ImUserInfoController>();
final res = await ImService.instance.getUserFollowInfo(userIDList: [imUserInfoController!.userID.value]);
if (res.success) {
//
// followersCount粉丝,mutualFollowersCount互关,followingCount我关注了多少人
followInfo.value = res.data!.first;
logger.i(followInfo.value!.toJson());
}
}
@ -260,103 +323,118 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFFAF6F9),
body: Obx(() {
return NestedScrollViewPlus(
controller: scrollController,
physics: shouldFixHeader.value ? const OnlyDownScrollPhysics(parent: AlwaysScrollableScrollPhysics()) : const AlwaysScrollableScrollPhysics(),
overscrollBehavior: OverscrollBehavior.outer,
headerSliverBuilder: (context, innerBoxIsScrolled) {
return [
SliverAppBar(
backgroundColor: Colors.transparent,
surfaceTintColor: Colors.transparent,
expandedHeight: 180.0,
collapsedHeight: 120.0,
pinned: true,
stretch: true,
onStretchTrigger: () async {
logger.i('触发 stretch 拉伸');
//
},
actions: [
_buildIcon('assets/images/svg/service.svg', () {
logger.i('点击客服按钮');
}),
const SizedBox(width: 8.0),
_buildIcon('assets/images/svg/setting.svg', () {
logger.i('点击设置按钮');
}),
const SizedBox(width: 10.0),
],
flexibleSpace: _buildFlexibleSpace(),
),
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Column(
children: [
_buildStatsCard(),
const SizedBox(height: 10.0),
_buildOrderCard(context),
const SizedBox(height: 10.0),
//
return Obx(() {
if (!isLogin.value) {
return SizedBox();
} else {
return Scaffold(
backgroundColor: const Color(0xFFFAF6F9),
body: Obx(() {
return NestedScrollViewPlus(
controller: scrollController,
physics: shouldFixHeader.value
? OnlyDownScrollPhysics(parent: AlwaysScrollableScrollPhysics())
: isPinned.value
? NeverScrollableScrollPhysics()
: AlwaysScrollableScrollPhysics(),
// physics: shouldFixHeader.value ? OnlyDownScrollPhysics(parent: AlwaysScrollableScrollPhysics()) : AlwaysScrollableScrollPhysics(),
overscrollBehavior: OverscrollBehavior.outer,
headerSliverBuilder: (context, innerBoxIsScrolled) {
return [
SliverAppBar(
backgroundColor: Colors.transparent,
surfaceTintColor: Colors.transparent,
expandedHeight: 180.0,
collapsedHeight: 120.0,
pinned: true,
stretch: true,
onStretchTrigger: () async {
logger.i('触发 stretch 拉伸');
//
},
actions: [
// _buildIcon('assets/images/svg/service.svg', () {
// logger.i('点击客服按钮');
// }),
const SizedBox(width: 8.0),
_buildIcon('assets/images/svg/setting.svg', () {
logger.i('点击设置按钮');
Get.toNamed('/setting');
}),
const SizedBox(width: 10.0),
],
flexibleSpace: _buildFlexibleSpace(),
),
),
),
SliverPersistentHeader(
pinned: true,
delegate: CustomStickyHeader(
child: PreferredSize(
preferredSize: const Size.fromHeight(48.0),
child: Container(
color: Colors.white,
child: TabBar(
controller: tabController,
tabs: tabList.map((item) {
return Tab(
child: Badge.count(
backgroundColor: Colors.red,
count: item['badge'] ?? 0,
isLabelVisible: item['badge'] != null,
alignment: Alignment.topRight,
offset: const Offset(14, -6),
child: Text(item['name'], style: const TextStyle(fontWeight: FontWeight.bold)),
),
);
}).toList(),
isScrollable: false,
overlayColor: WidgetStateProperty.all(Colors.transparent),
unselectedLabelColor: Colors.black87,
labelColor: const Color(0xFFFF5000),
indicator: const UnderlineTabIndicator(borderSide: BorderSide(color: Color(0xFFFF5000), width: 2.0)),
indicatorSize: TabBarIndicatorSize.tab,
unselectedLabelStyle: const TextStyle(fontSize: 16.0, fontFamily: 'Microsoft YaHei'),
labelStyle: const TextStyle(fontSize: 18.0, fontFamily: 'Microsoft YaHei', fontWeight: FontWeight.bold),
dividerHeight: 0,
padding: const EdgeInsets.symmetric(horizontal: 10.0),
labelPadding: const EdgeInsets.symmetric(horizontal: 15.0),
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Column(
children: [
Obx(() => _buildStatsCard()),
const SizedBox(height: 10.0),
_buildOrderCard(context),
const SizedBox(height: 10.0),
],
),
),
),
),
),
];
},
body: TabBarView(
controller: tabController,
children: [
// Tab 1:
Obx(() => _buildGridTab(0)),
SliverPersistentHeader(
pinned: true,
delegate: CustomStickyHeader(
isPinned: isPinned,
positions: positions,
child: PreferredSize(
preferredSize: const Size.fromHeight(48.0),
child: Container(
color: Colors.white,
child: TabBar(
controller: tabController,
tabs: tabList.map((item) {
return Tab(
child: Badge.count(
backgroundColor: Colors.red,
count: item['badge'] ?? 0,
isLabelVisible: item['badge'] != null,
alignment: Alignment.topRight,
offset: const Offset(14, -6),
child: Text(item['name'], style: const TextStyle(fontWeight: FontWeight.bold)),
),
);
}).toList(),
isScrollable: false,
overlayColor: WidgetStateProperty.all(Colors.transparent),
unselectedLabelColor: Colors.black87,
labelColor: const Color(0xFFFF5000),
indicator: const UnderlineTabIndicator(borderSide: BorderSide(color: Color(0xFFFF5000), width: 2.0)),
indicatorSize: TabBarIndicatorSize.tab,
unselectedLabelStyle: const TextStyle(fontSize: 16.0, fontFamily: 'Microsoft YaHei'),
labelStyle: const TextStyle(fontSize: 18.0, fontFamily: 'Microsoft YaHei', fontWeight: FontWeight.bold),
dividerHeight: 0,
padding: const EdgeInsets.symmetric(horizontal: 10.0),
labelPadding: const EdgeInsets.symmetric(horizontal: 15.0),
),
),
),
),
),
];
},
body: TabBarView(
controller: tabController,
children: [
// Tab 1:
_buildGridTab(0),
// Tab 2:
Obx(() => _buildGridTab(1))
],
),
// Tab 2:
_buildGridTab(1)
],
),
);
}),
);
}),
);
}
});
}
//
@ -390,47 +468,134 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
Widget _buildGridTab(int tabIndex) {
final listToShow = tabIndex == 0 ? items : favoriteItems;
final params = tabIndex == 0 ? itemsParams : favoriteParams;
PageParams params = tabIndex == 0 ? itemsParams : favoriteParams;
if (params.isInitLoading) {
return Center(child: CircularProgressIndicator());
}
if (listToShow.isEmpty) {
return emptyTip('暂无相关数据');
}
return CustomScrollView(
slivers: [
SliverPadding(
padding: const 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),
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]),
);
},
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: params.hasMore ? const CircularProgressIndicator() : const Text('没有更多数据了'),
),
),
),
],
);
});
}
Widget _buildVdCard(item) {
return InkWell(
onTap: () {
//
},
onLongPress: () {
showModalBottomSheet(
context: Get.context!,
backgroundColor: Colors.black.withOpacity(0.8),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
builder: (context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
leading: const Icon(Icons.lock, color: Colors.white),
title: const Text('设为私密', style: TextStyle(color: Colors.white)),
onTap: () {
Navigator.pop(context);
// TODO:
},
),
ListTile(
leading: const Icon(Icons.delete, color: Colors.redAccent),
title: const Text('删除视频', style: TextStyle(color: Colors.redAccent)),
onTap: () {
Navigator.pop(context);
// TODO:
},
),
],
);
},
);
},
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12.0),
color: Colors.grey[900],
),
child: Stack(
children: [
///
ClipRRect(
borderRadius: BorderRadius.circular(12.0),
child: Image.network(
item['cover'] ?? item['firstFrameImg'],
fit: BoxFit.cover,
width: double.infinity,
height: double.infinity,
),
),
///
Positioned(
right: 8,
bottom: 8,
child: Row(
children: [
const Icon(Icons.favorite, color: Colors.white, size: 16),
const SizedBox(width: 4),
Text(
'${item['likeCounts'] ?? 0}',
style: const TextStyle(
color: Colors.white,
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
alignment: Alignment.center,
child: Text(listToShow[index], style: const TextStyle(fontWeight: FontWeight.bold)),
);
},
childCount: listToShow.length,
],
),
),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: 10.0,
mainAxisSpacing: 10.0,
childAspectRatio: 1.0,
),
),
],
),
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 20.0),
child: Center(
child: params.hasMore ? const CircularProgressIndicator() : const Text('没有更多数据了'),
),
),
),
],
),
);
}
@ -449,7 +614,7 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
final double maxHeight = 180.0;
final double minHeight = 100.0;
final double minHeight = 120.0;
final double currentHeight = constraints.maxHeight;
double ratio = (currentHeight - minHeight) / (maxHeight - minHeight);
ratio = ratio.clamp(0.0, 1.0);
@ -457,7 +622,12 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
return Stack(
fit: StackFit.expand,
children: [
Positioned.fill(child: Opacity(opacity: 1.0, child: Image.asset('assets/images/pic2.jpg', fit: BoxFit.cover))),
Positioned.fill(
child: Opacity(
opacity: 1.0,
child: NetworkOrAssetImage(imageUrl: imUserInfoController?.customInfo['coverBg'], placeholderAsset: 'assets/images/bk.jpg'),
),
),
Positioned(
left: 15.0,
bottom: 0,
@ -467,7 +637,19 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
ClipOval(child: Image.asset('assets/images/avatar/img11.jpg', height: 60.0, width: 60.0, fit: BoxFit.cover)),
ClipOval(
child: Obx(() {
final faceUrl = imUserInfoController?.faceUrl.value;
return ClipRRect(
borderRadius: BorderRadius.circular(30),
child: NetworkOrAssetImage(
imageUrl: faceUrl,
width: 80,
height: 80,
),
);
}),
),
const SizedBox(width: 15.0),
Expanded(
child: Column(
@ -476,13 +658,14 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
Row(
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 5.0),
decoration: BoxDecoration(color: Colors.black.withAlpha((0.3 * 255).round()), borderRadius: BorderRadius.circular(20.0)),
child: const Text(
'新用户2025',
style: TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold, fontFamily: 'Arial', color: Colors.white),
),
),
padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 5.0),
decoration: BoxDecoration(color: Colors.black.withAlpha((0.3 * 255).round()), borderRadius: BorderRadius.circular(20.0)),
child: Obx(
() => Text(
imUserInfoController!.nickname.value.isNotEmpty == true ? imUserInfoController!.nickname.value : '昵称',
style: TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold, fontFamily: 'Arial', color: Colors.white),
),
)),
const SizedBox(width: 8.0),
InkWell(
onTap: () {
@ -502,11 +685,11 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
decoration: BoxDecoration(color: Colors.black.withAlpha((0.3 * 255).round()), borderRadius: BorderRadius.circular(20.0)),
child: InkWell(
onTap: () {
logger.i('点击个人简介');
Clipboard.setData(const ClipboardData(text: '1234'));
logger.i('点击id');
Clipboard.setData(ClipboardData(text: imUserInfoController!.userID.value));
MyDialog.toast('ID已复制', icon: const Icon(Icons.check_circle), style: ToastStyle(backgroundColor: Colors.green.withAlpha(200)));
},
child: const Text('ID:32938293892839232', style: TextStyle(fontSize: 12.0, color: Colors.white)),
child: Text('ID:${imUserInfoController!.userID.value}', style: TextStyle(fontSize: 12.0, color: Colors.white)),
),
),
],
@ -531,17 +714,28 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
),
clipBehavior: Clip.antiAlias,
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Column(children: const [Text('9999', style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold)), SizedBox(height: 3.0), Text('获赞')]),
Column(children: const [Text('25', style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold)), SizedBox(height: 3.0), Text('互关')]),
Column(children: const [Text('11', style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold)), SizedBox(height: 3.0), Text('关注')]),
Column(children: const [Text('10', style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold)), SizedBox(height: 3.0), Text('粉丝')]),
],
),
),
padding: const EdgeInsets.all(10.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Column(children: [Text('9999', style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold)), SizedBox(height: 3.0), Text('获赞')]),
Column(children: [
Text('${followInfo.value?.mutualFollowersCount ?? 0}', style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold)),
SizedBox(height: 3.0),
Text('互关')
]),
Column(children: [
Text('${followInfo.value?.followingCount ?? 0}', style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold)),
SizedBox(height: 3.0),
Text('关注')
]),
Column(children: [
Text('${followInfo.value?.followersCount ?? 0}', style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold)),
SizedBox(height: 3.0),
Text('粉丝')
]),
],
)),
);
}
@ -583,10 +777,12 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
_buildOrderIcon('assets/images/ico_order.png', '订单', () {
Get.toNamed('/order');
}),
_buildOrderIcon('assets/images/ico_dhx.png', '余额', () {
_buildOrderIcon('assets/images/ico_dhx.png', '余额logout', () {
showLogoutDialog(context);
}),
_buildOrderIcon('assets/images/ico_sh.png', '提现', () {}),
_buildOrderIcon('assets/images/ico_sh.png', '提现vloger', () {
Get.toNamed('/vloger');
}),
_buildOrderIcon('assets/images/ico_tgm.png', '推广码', () {}),
],
),

View File

@ -0,0 +1,86 @@
import 'package:flutter/material.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:form_builder_validators/form_builder_validators.dart';
import 'package:get/get.dart';
import 'package:loopin/IM/controller/im_user_info_controller.dart';
class NickName extends StatefulWidget {
const NickName({super.key});
@override
State<NickName> createState() => _NickNameState();
}
class _NickNameState extends State<NickName> {
final _formKey = GlobalKey<FormBuilderState>();
final userInfoController = Get.find<ImUserInfoController>();
void _save() async {
if (_formKey.currentState?.saveAndValidate() ?? false) {
final nickname = _formKey.currentState?.fields['nickname']?.value;
final result = await userInfoController.updateNickname(nickname);
if (result) {
Get.back();
}
}
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => FocusScope.of(context).unfocus(),
child: Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
backgroundColor: Colors.white,
title: const Text('修改昵称'),
actions: [
TextButton(
onPressed: _save,
child: const Text(
'保存',
style: TextStyle(color: Colors.red, fontSize: 16),
),
),
],
),
body: Padding(
padding: const EdgeInsets.all(16),
child: FormBuilder(
key: _formKey,
child: Obx(() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'我的昵称',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
FormBuilderTextField(
name: 'nickname',
initialValue: userInfoController.nickname.value,
decoration: const InputDecoration(
hintText: '请输入昵称',
border: OutlineInputBorder(),
contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 10),
),
maxLength: 20,
validator: FormBuilderValidators.compose([
FormBuilderValidators.required(errorText: '昵称不能为空'),
FormBuilderValidators.maxLength(20, errorText: '昵称不能超过20个字符'),
]),
),
const SizedBox(height: 8),
const Text(
'昵称最长支持20个字符请文明用语',
style: TextStyle(fontSize: 12, color: Colors.grey),
),
],
);
})),
),
),
);
}
}

70
lib/pages/my/setting.dart Normal file
View File

@ -0,0 +1,70 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:loopin/styles/index.dart';
class Setting extends StatelessWidget {
const Setting({super.key});
@override
Widget build(BuildContext context) {
final List<Map<String, dynamic>> settings = [
{
'icon': Icons.person,
'title': '账号设置',
'onTap': () => Get.toNamed('/userInfo'),
},
{
'icon': Icons.notifications,
'title': '通知设置',
'onTap': () => Get.toNamed('/notifications'),
},
{
'icon': Icons.lock,
'title': '隐私',
'onTap': () => Get.toNamed('/privacy'),
},
{
'icon': Icons.info,
'title': '关于我们',
'onTap': () => Get.toNamed('/about'),
},
];
return Scaffold(
appBar: AppBar(title: const Text('设置')),
backgroundColor: Colors.grey[200],
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Card(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
color: Colors.white,
child: Column(
children: settings.map((item) {
return Column(
children: [
ListTile(
leading: Icon(item['icon'] as IconData, color: FStyle.c999),
title: Text(item['title']),
trailing: const Icon(
Icons.chevron_right,
color: FStyle.c999,
),
onTap: item['onTap'],
),
if (item != settings.last) const Divider(height: 1, indent: 16, endIndent: 16),
],
);
}).toList(),
),
),
],
),
),
);
}
}

490
lib/pages/my/user_info.dart Normal file
View File

@ -0,0 +1,490 @@
import 'package:bottom_picker/bottom_picker.dart';
import 'package:city_pickers/city_pickers.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:loopin/IM/controller/im_user_info_controller.dart';
import 'package:loopin/api/common_api.dart';
import 'package:loopin/service/http.dart';
import 'package:loopin/styles/index.dart';
import 'package:loopin/utils/index.dart';
import 'package:loopin/utils/wxsdk.dart';
import 'package:shirne_dialog/shirne_dialog.dart';
import 'package:wechat_assets_picker/wechat_assets_picker.dart';
class UserInfo extends StatefulWidget {
const UserInfo({super.key});
@override
State<UserInfo> createState() => _UserInfoState();
}
class _UserInfoState extends State<UserInfo> {
final userInfoController = Get.find<ImUserInfoController>();
late List<Map<String, dynamic>> items;
@override
void initState() {
super.initState();
items = [
{
'title': '昵称',
'value': userInfoController.nickname,
'onTap': () => Get.toNamed('/nickName'),
},
{
'title': '简介',
'value': userInfoController.signature,
'onTap': () => Get.toNamed('/des'),
},
{
'title': '性别',
'value': userInfoController.gender,
'onTap': () => setGender(context),
},
{
'title': '生日',
'value': userInfoController.birthday,
'onTap': () => selectDate(context),
},
{
'title': '区域',
'value': userInfoController.customInfo,
'onTap': () => pickCity(),
},
{
'title': '绑定微信',
'value': userInfoController.customInfo,
'onTap': () => wechatLogin(),
},
];
}
///
Future<void> wechatLogin() async {
await Wxsdk.login();
}
///
void setGender(BuildContext context) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (BuildContext context) {
return SafeArea(
child: Container(
height: 300,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
child: Column(
children: [
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ListTile(
title: Center(child: Text('')),
onTap: () {
userInfoController.gender.value = 1;
userInfoController.updateGender();
Get.back();
},
),
Divider(),
ListTile(
title: Center(child: Text('')),
onTap: () {
userInfoController.gender.value = 2;
userInfoController.updateGender();
Get.back();
}),
Divider(),
ListTile(
title: Center(child: Text('保密')),
onTap: () {
userInfoController.gender.value = 0;
userInfoController.updateGender();
Get.back();
},
),
Container(
height: 10,
color: Colors.grey[200],
),
],
),
),
ListTile(
title: Center(child: Text('取消')),
onTap: () => Get.back(),
),
],
),
),
);
},
);
}
///
void selectDate(BuildContext context) {
DateTime selectedDate = DateTime.now(); //
BottomPicker.date(
pickerTitle: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
TextButton(
onPressed: () {
Get.back();
},
child: Text('取消'),
),
TextButton(
onPressed: () {
//
final dateStr = "${selectedDate.year}-${selectedDate.month.toString().padLeft(2, '0')}-${selectedDate.day.toString().padLeft(2, '0')}";
print(dateStr);
DateTime birthdayDate = DateTime(selectedDate.year, selectedDate.month, selectedDate.day);
userInfoController.birthday.value = birthdayDate.millisecondsSinceEpoch ~/ 1000; //
userInfoController.updateBirthday();
Get.back();
},
child: Text('确认'),
),
],
),
displaySubmitButton: false,
displayCloseIcon: false,
dismissable: true, //
initialDateTime: DateTime.now(),
minDateTime: DateTime(1900),
maxDateTime: DateTime(
DateTime.now().year,
DateTime.now().month,
DateTime.now().day,
23,
59,
59,
),
dateOrder: DatePickerDateOrder.ymd,
pickerTextStyle: const TextStyle(fontSize: 16, color: Colors.black87),
height: 300,
onChange: (date) {
selectedDate = date as DateTime;
},
).show(context);
}
///
void pickCity() async {
final result = await CityPickers.showCityPicker(
context: context,
showType: ShowType.pca, //
height: 300.0,
borderRadius: 16.0,
barrierDismissible: true,
theme: Theme.of(context).copyWith(
scaffoldBackgroundColor: Colors.white,
),
);
if (result != null) {
final areaName = '${result.provinceName}-${result.cityName}-${result.areaName}';
print(result.toString());
print('${result.provinceName}-${result.cityName}-${result.areaName}-${result.areaId}');
//
userInfoController.customInfo['area'] = areaName;
userInfoController.customInfo['areaCode'] = '${result.areaId}';
userInfoController.updateArea();
userInfoController.customInfo.refresh();
}
}
///
void pickCover(BuildContext context) async {
final pickedAssets = await AssetPicker.pickAssets(
context,
pickerConfig: AssetPickerConfig(
textDelegate: const AssetPickerTextDelegate(),
pathNameBuilder: (AssetPathEntity album) {
return Utils.translateAlbumName(album);
},
maxAssets: 1,
requestType: RequestType.image,
filterOptions: FilterOptionGroup(
imageOption: const FilterOption(),
),
),
);
if (pickedAssets != null && pickedAssets.isNotEmpty) {
final asset = pickedAssets.first;
final file = await asset.file; //
if (file != null) {
final fileSizeInBytes = await file.length();
final sizeInMB = fileSizeInBytes / (1024 * 1024);
if (sizeInMB > 100) {
MyDialog.toast('图片大小不能超过100MB', icon: const Icon(Icons.check_circle), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200)));
} else {
print("图片合法,大小:$sizeInMB MB");
//upload(file)url地址
final istance = MyDialog.loading('上传中');
final res = await Http.upload(CommonApi.uploadFile, filePath: file.path);
userInfoController.customInfo['coverBg'] = res['data']['url'];
userInfoController.updateCover();
userInfoController.customInfo.refresh();
istance.close();
}
}
}
}
///
void pickFaceUrl(BuildContext context) async {
final pickedAssets = await AssetPicker.pickAssets(
context,
pickerConfig: AssetPickerConfig(
textDelegate: const AssetPickerTextDelegate(),
pathNameBuilder: (AssetPathEntity album) {
return Utils.translateAlbumName(album);
},
maxAssets: 1,
requestType: RequestType.image,
filterOptions: FilterOptionGroup(
imageOption: const FilterOption(),
),
),
);
if (pickedAssets != null && pickedAssets.isNotEmpty) {
final asset = pickedAssets.first;
final file = await asset.file; //
if (file != null) {
final fileSizeInBytes = await file.length();
final sizeInMB = fileSizeInBytes / (1024 * 1024);
if (sizeInMB > 100) {
MyDialog.toast('图片大小不能超过100MB', icon: const Icon(Icons.check_circle), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200)));
} else {
print("图片合法,大小:$sizeInMB MB");
//upload(file)url地址
final istance = MyDialog.loading('上传中');
final res = await Http.upload(CommonApi.uploadFile, filePath: file.path);
userInfoController.faceUrl.value = res['data']['url'];
userInfoController.updateFaceUrl();
userInfoController.customInfo.refresh();
istance.close();
}
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
extendBodyBehindAppBar: true,
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
actions: [
Padding(
padding: const EdgeInsets.only(right: 12.0),
child: GestureDetector(
onTap: () => pickCover(context),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 6.0),
decoration: BoxDecoration(
color: Colors.black.withAlpha(100),
borderRadius: BorderRadius.circular(20.0),
),
child: Row(
children: const [
Icon(Icons.camera_alt, color: Colors.white, size: 16),
SizedBox(width: 4),
Text('更换封面', style: TextStyle(color: Colors.white, fontSize: 14)),
],
),
),
),
),
],
),
body: SizedBox.expand(
child: Stack(
children: [
//
SizedBox(
height: 240,
width: double.infinity,
child: Obx(() {
final imageUrl = userInfoController.customInfo['coverBg'];
return GestureDetector(
onTap: () => pickCover(context),
child: Image(
image: (imageUrl != null && imageUrl.isNotEmpty) ? NetworkImage(imageUrl) : const AssetImage('assets/images/pic2.jpg') as ImageProvider,
fit: BoxFit.cover,
),
);
}),
),
//
Positioned(
top: 220,
left: 0,
right: 0,
bottom: 0,
child: ClipRRect(
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(30),
topRight: Radius.circular(30),
),
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(30),
topRight: Radius.circular(30),
),
),
child: SingleChildScrollView(
padding: const EdgeInsets.only(top: 60, bottom: 40),
child: Column(
children: [
const SizedBox(height: 0),
const SizedBox(height: 20),
Card(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
color: Colors.white,
child: Column(
children: items.map((item) {
return Column(
children: [
ListTile(
title: Row(
children: [
Text(
item['title'],
style: const TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(width: 50),
Expanded(child: Obx(() {
final val = item['value'];
if (val is RxString) {
return Text(
val.value,
style: const TextStyle(color: Colors.black),
overflow: TextOverflow.ellipsis,
maxLines: 1,
);
} else if (val is RxInt) {
String displayText;
if (item['title'] == '性别') {
displayText = val.value == 0
? '保密'
: val.value == 1
? ''
: val.value == 2
? ''
: '';
} else {
//
// displayText = val.value == 0 ? '' : val.value.toString();
if (val.value == 0) {
displayText = '';
} else {
final date = DateTime.fromMillisecondsSinceEpoch(val.value * 1000);
displayText = "${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}";
}
}
return Text(
displayText,
style: const TextStyle(color: Colors.black),
overflow: TextOverflow.ellipsis,
maxLines: 1,
);
} else if (val is String) {
return Text(
val,
style: const TextStyle(color: Colors.black),
overflow: TextOverflow.ellipsis,
maxLines: 1,
);
} else if (val is Map) {
if (item['title'] == '区域') {
return Text(
val['area'] ?? '',
style: const TextStyle(color: Colors.black),
overflow: TextOverflow.ellipsis,
maxLines: 1,
);
} else {
String wxText = val['unionId'] == null || val['unionId'] == '' ? '未授权' : '已授权';
return Row(
children: [
Spacer(),
Text(
wxText,
style: const TextStyle(color: Colors.black),
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
],
);
}
} else {
return const SizedBox.shrink();
}
}))
],
),
trailing: const Icon(
Icons.chevron_right,
color: FStyle.c999,
),
onTap: item['onTap'],
),
],
);
}).toList(),
),
)
],
),
),
),
),
),
// Stack最后面
Positioned(
top: 220 - 60, // 使线线
left: 0,
right: 0,
child: Obx(() {
final avatar = userInfoController.faceUrl.value;
return Center(
child: GestureDetector(
onTap: () => pickFaceUrl(context),
child: CircleAvatar(
radius: 60,
backgroundColor: Colors.white,
child: CircleAvatar(
radius: 57,
backgroundImage: avatar.isNotEmpty ? NetworkImage(avatar) : const AssetImage('assets/images/avatar/img11.jpg') as ImageProvider,
),
),
),
);
}),
),
],
),
),
);
}
}

648
lib/pages/my/vloger.dart Normal file
View File

@ -0,0 +1,648 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:get/get_rx/src/rx_typedefs/rx_typedefs.dart';
import 'package:loopin/IM/im_service.dart';
import 'package:loopin/components/custom_sticky_header.dart';
import 'package:loopin/components/network_or_asset_image.dart';
import 'package:loopin/components/only_down_scroll_physics.dart';
import 'package:loopin/styles/index.dart';
import 'package:nested_scroll_view_plus/nested_scroll_view_plus.dart';
import 'package:shirne_dialog/shirne_dialog.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_follow_info.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_user_full_info.dart';
class PageParams {
int page;
int pageSize;
bool isLoading;
bool hasMore;
PageParams({
this.page = 1,
this.pageSize = 10,
this.isLoading = false,
this.hasMore = true,
});
}
class Vloger extends StatefulWidget {
const Vloger({super.key});
@override
State<Vloger> createState() => MyPageState();
}
class MyPageState extends State<Vloger> with SingleTickerProviderStateMixin {
late dynamic args;
late RxInt currentTabIndex = 0.obs;
late RxList items = [].obs;
late RxList favoriteItems = [].obs;
late final Rx<V2TimUserFullInfo> userInfo = Rx(V2TimUserFullInfo(
userID: '',
nickName: '',
faceUrl: '',
selfSignature: '',
gender: 0,
customInfo: {
'coverBg': '',
},
role: 0,
));
late RxInt followed = 0.obs; //
// followersCount粉丝,mutualFollowersCount互关,followingCount我关注了多少人
late final Rx<V2TimFollowInfo> followInfo = Rx(V2TimFollowInfo(
followersCount: 0,
followingCount: 0,
));
RxBool get shouldFixHeader => (currentTabIndex.value == 0 && items.isEmpty) || (currentTabIndex.value == 1 && favoriteItems.isEmpty) ? true.obs : false.obs;
List tabList = [
{'name': "作品"},
];
late PageParams itemsParams;
late PageParams favoriteParams;
late TabController tabController;
late ScrollController scrollController;
late Callback tabListener;
late Callback scrollListener;
@override
void initState() {
super.initState();
args = Get.arguments ?? {};
itemsParams = PageParams();
favoriteParams = PageParams();
selfInfo();
flowInfo();
checkFollowType();
initControllers();
scrollListener = () {
final pos = scrollController.position;
final isNearBottom = pos.pixels >= pos.maxScrollExtent - 100;
if (!isNearBottom) return;
if (currentTabIndex.value == 0 && !itemsParams.isLoading && itemsParams.hasMore) {
loadData(0);
} else if (currentTabIndex.value == 1 && !favoriteParams.isLoading && favoriteParams.hasMore) {
loadData(1);
}
};
scrollController.addListener(scrollListener);
tabListener = () {
currentTabIndex.value = tabController.index;
scrollController.animateTo(0, duration: const Duration(milliseconds: 300), curve: Curves.easeIn);
loadData(0);
};
tabController.addListener(tabListener);
loadData(0);
}
@override
void dispose() {
tabController.removeListener(tabListener);
scrollController.removeListener(scrollListener);
tabController.dispose();
scrollController.dispose();
super.dispose();
}
void loadData([int? tabIndex]) async {
final index = tabIndex ?? currentTabIndex.value;
if (index == 0) {
if (itemsParams.isLoading || !itemsParams.hasMore) return;
itemsParams.isLoading = true;
await Future.delayed(const Duration(seconds: 1));
//
List<String> newItems = List.generate(
itemsParams.pageSize,
(i) => '作品 ${(itemsParams.page - 1) * itemsParams.pageSize + i + 1}',
);
//
if (itemsParams.page >= 2) {
itemsParams.hasMore = false;
}
//
items.addAll(newItems);
//
itemsParams.page++;
itemsParams.isLoading = false;
}
}
void initControllers() {
tabController = TabController(initialIndex: 0, length: tabList.length, vsync: this);
scrollController = ScrollController();
}
//
void selfInfo() async {
final resIm = await ImService.instance.otherInfo(args['memberId']);
if (resIm.success && resIm.data != null) {
userInfo.value = resIm.data!;
logger.i(userInfo.value.toLogString());
} else {
logger.e(resIm.desc);
}
}
//
void flowInfo() async {
logger.w(args.toString());
final res = await ImService.instance.getUserFollowInfo(userIDList: [args['memberId']]);
if (res.success && res.data?.first != null) {
//
// followersCount粉丝,mutualFollowersCount互关,followingCount我关注了多少人
followInfo.value = res.data!.first;
logger.i(followInfo.value.toJson());
} else {
logger.e(res.desc);
}
}
//
void checkFollowType() async {
/// 0
/// 1
/// 2
/// 3
final res = await ImService.instance.checkFollowType(userIDList: [args['memberId']]);
if (res.success) {
final followType = res.data?.first.followType ?? 0;
logger.i(res.data?.first.toJson());
followed.value = followType;
logger.i(followed.value);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFFAF6F9),
body: Obx(() {
return NestedScrollViewPlus(
controller: scrollController,
physics: shouldFixHeader.value ? const OnlyDownScrollPhysics(parent: AlwaysScrollableScrollPhysics()) : const AlwaysScrollableScrollPhysics(),
overscrollBehavior: OverscrollBehavior.outer,
headerSliverBuilder: (context, innerBoxIsScrolled) {
return [
SliverAppBar(
backgroundColor: Colors.transparent,
surfaceTintColor: Colors.transparent,
expandedHeight: 180.0,
collapsedHeight: 120.0,
pinned: true,
stretch: true,
onStretchTrigger: () async {
logger.i('触发 stretch 拉伸');
//
},
flexibleSpace: Obx(() {
userInfo.value;
return _buildFlexibleSpace();
}),
),
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Column(
children: [
Obx(() => _buildStatsCard()),
const SizedBox(height: 10.0),
Obx(() => _buildInfoDesc(context)),
const SizedBox(height: 10.0),
Obx(() => _buildFoucsButton(context)),
],
),
),
),
SliverPersistentHeader(
pinned: true,
delegate: CustomStickyHeader(
child: PreferredSize(
preferredSize: const Size.fromHeight(48.0),
child: Container(
color: Colors.white,
child: TabBar(
controller: tabController,
tabs: tabList.map((item) {
return Tab(
child: Badge.count(
backgroundColor: Colors.red,
count: item['badge'] ?? 0,
isLabelVisible: item['badge'] != null,
alignment: Alignment.topRight,
offset: const Offset(14, -6),
child: Text(item['name'], style: const TextStyle(fontWeight: FontWeight.bold)),
),
);
}).toList(),
isScrollable: true, //
tabAlignment: TabAlignment.start,
overlayColor: WidgetStateProperty.all(Colors.transparent),
unselectedLabelColor: Colors.black87,
labelColor: Colors.black,
indicator: const UnderlineTabIndicator(borderSide: BorderSide(color: Colors.transparent, width: 2.0)),
indicatorSize: TabBarIndicatorSize.label,
unselectedLabelStyle: const TextStyle(fontSize: 16.0, fontFamily: 'Microsoft YaHei'),
labelStyle: const TextStyle(fontSize: 18.0, fontFamily: 'Microsoft YaHei', fontWeight: FontWeight.bold),
dividerHeight: 0,
padding: const EdgeInsets.symmetric(horizontal: 10.0),
labelPadding: const EdgeInsets.symmetric(horizontal: 15.0),
),
),
),
),
),
];
},
body: TabBarView(
controller: tabController,
children: [
// Tab 1:
Obx(() => _buildGridTab(0)),
],
),
);
}),
);
}
//
Widget emptyTip(String text) {
return CustomScrollView(
physics: const OnlyDownScrollPhysics(),
slivers: [
SliverFillRemaining(
hasScrollBody: false,
child: Align(
alignment: Alignment.topCenter,
child: Padding(
padding: const EdgeInsets.only(top: 50.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Image.asset('assets/images/empty.png', width: 100.0),
const SizedBox(height: 8.0),
Text(
text,
style: const TextStyle(color: Colors.grey, fontSize: 13.0),
),
],
),
),
),
),
],
);
}
Widget _buildGridTab(int tabIndex) {
final listToShow = tabIndex == 0 ? items : favoriteItems;
final params = tabIndex == 0 ? itemsParams : favoriteParams;
if (listToShow.isEmpty) {
return emptyTip('暂无相关数据');
}
return CustomScrollView(
slivers: [
SliverPadding(
padding: const 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: Text(listToShow[index], style: const TextStyle(fontWeight: FontWeight.bold)),
);
},
childCount: listToShow.length,
),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: 10.0,
mainAxisSpacing: 10.0,
childAspectRatio: 1.0,
),
),
),
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 20.0),
child: Center(
child: params.hasMore ? const CircularProgressIndicator() : const Text('没有更多数据了'),
),
),
),
],
);
}
Widget _buildFlexibleSpace() {
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
final double maxHeight = 180.0;
final double minHeight = 100.0;
final double currentHeight = constraints.maxHeight;
double ratio = (currentHeight - minHeight) / (maxHeight - minHeight);
ratio = ratio.clamp(0.0, 1.0);
String coverBg = userInfo.value.customInfo?['coverBg'] ?? '';
coverBg = coverBg.isEmpty ? 'assets/images/pic2.jpg' : coverBg;
logger.w(coverBg);
return Stack(
fit: StackFit.expand,
children: [
Positioned.fill(
child: Opacity(
opacity: 1.0,
child: Image.asset(
coverBg,
fit: BoxFit.cover,
))),
Positioned(
left: 15.0,
bottom: 0,
right: 15.0,
child: Container(
padding: const EdgeInsets.symmetric(vertical: 10.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
ClipOval(
child: NetworkOrAssetImage(imageUrl: userInfo.value.faceUrl),
// child: Image.asset(
// userInfo.value.faceUrl ?? 'assets/images/pic1.jpg',
// height: 60.0,
// width: 60.0,
// fit: BoxFit.cover,
// errorBuilder: (context, error, stackTrace) {
// return Image.asset(
// 'assets/images/pic1.jpg',
// height: 60.0,
// width: 60.0,
// fit: BoxFit.cover,
// );
// },
// ),
),
const SizedBox(width: 15.0),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 5.0),
decoration: BoxDecoration(color: Colors.black.withAlpha((0.3 * 255).round()), borderRadius: BorderRadius.circular(20.0)),
child: Text(
userInfo.value.nickName ?? '未知',
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold,
fontFamily: 'Arial',
color: Colors.white,
),
),
),
],
),
const SizedBox(height: 8.0),
Container(
padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 5.0),
decoration: BoxDecoration(color: Colors.black.withAlpha((0.3 * 255).round()), borderRadius: BorderRadius.circular(20.0)),
child: InkWell(
onTap: () {
logger.i('点击个ID');
Clipboard.setData(ClipboardData(text: '${userInfo.value.userID}'));
MyDialog.toast('ID已复制', icon: const Icon(Icons.check_circle), style: ToastStyle(backgroundColor: Colors.green.withAlpha(200)));
},
child: Text('ID:${userInfo.value.userID}', style: TextStyle(fontSize: 12.0, color: Colors.white)),
),
),
],
),
),
],
),
),
),
],
);
},
);
}
Widget _buildStatsCard() {
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(15.0),
boxShadow: [BoxShadow(color: Colors.black.withAlpha(10), offset: const Offset(0.0, 1.0), blurRadius: 2.0, spreadRadius: 0.0)],
),
clipBehavior: Clip.antiAlias,
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Column(children: [Text('9999', style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold)), SizedBox(height: 3.0), Text('获赞')]),
Column(children: [
Text('${followInfo.value.followingCount}', style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold)),
SizedBox(height: 3.0),
Text('关注')
]),
Column(children: [
Text('${followInfo.value.followersCount}', style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold)),
SizedBox(height: 3.0),
Text('粉丝')
]),
],
),
),
);
}
Widget _buildInfoDesc(BuildContext context) {
final tx = userInfo.value.selfSignature;
if (tx == null || tx.isEmpty) {
return const SizedBox.shrink();
}
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(15.0),
boxShadow: [BoxShadow(color: Colors.black.withAlpha(10), offset: const Offset(0.0, 1.0), blurRadius: 2.0, spreadRadius: 0.0)],
),
clipBehavior: Clip.antiAlias,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: const EdgeInsets.all(12),
width: double.infinity,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
),
child: Text(
'${userInfo.value.selfSignature}',
style: const TextStyle(fontSize: 16),
),
),
],
));
}
///
Widget _buildFoucsButton(BuildContext context) {
// final vlogerId = '1943510443312078850'; // 18832510385,id
final vlogerId = args['vlogerId'];
return AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
transitionBuilder: (child, animation) {
final offsetAnimation = Tween<Offset>(
begin: Offset((followed.value == 0) ? -1 : 1, 0),
end: Offset.zero,
).animate(animation);
return SlideTransition(
position: offsetAnimation,
child: child,
);
},
child: [1, 3].contains(followed.value)
? Row(
key: const ValueKey('followed'),
children: [
Expanded(
child: ElevatedButton(
onPressed: () async {
print('点击已关注');
final res = await ImService.instance.unfollowUser(userIDList: [vlogerId]);
if (res.success) {
// 1032
followed.value = followed.value == 1 ? 0 : 2;
// group
}
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.grey[300],
foregroundColor: Colors.black87,
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: Text(followed.value == 1
? '已关注'
: followed.value == 3
? '互关'
: '未知状态'),
),
),
const SizedBox(width: 12),
Expanded(
child: ElevatedButton(
onPressed: () async {
print('私信');
//
final res = await ImService.instance.getConversation(conversationID: 'c2c_$vlogerId');
V2TimConversation conversation = res.data;
logger.i(conversation.toLogString());
if (res.success) {
// final isFriend = await ImService.instance.isMyFriend(vlogerId, FriendTypeEnum.V2TIM_FRIEND_TYPE_BOTH);
//
//
if (followed.value == 3) {
Get.toNamed('/chat', arguments: conversation);
} else {
logger.i('对方没关注我');
logger.i(conversation.toLogString());
conversation.showName = conversation.showName ?? userInfo.value.nickName;
Get.toNamed('/chatNoFriend', arguments: conversation);
}
} else {
MyDialog.toast(res.desc, icon: const Icon(Icons.warning), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200)));
}
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.grey[300],
foregroundColor: Colors.black87,
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: const Text('私信'),
),
),
],
)
: SizedBox(
key: const ValueKey('not_followed'),
width: double.infinity,
child: ElevatedButton.icon(
onPressed: () async {
// 02
print('点击关注');
final res = await ImService.instance.followUser(userIDList: [vlogerId]);
if (res.success) {
followed.value = followed.value == 0 ? 1 : 3;
if (followed.value == 3) {
// 3noFriend会话组
final res = await ImService.instance.getConversation(conversationID: 'c2c_$vlogerId');
if (res.success) {
V2TimConversation conversation = res.data;
if (conversation.conversationGroupList?.isNotEmpty == true) {
//
await ImService.instance.deleteConversationsFromGroup(
groupName: conversation.conversationGroupList!.first!,
conversationIDList: [conversation.conversationID],
);
//
}
}
}
}
},
icon: const Icon(Icons.add),
label: const Text('关注'),
style: ElevatedButton.styleFrom(
backgroundColor: FStyle.primaryColor,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
),
);
}
}

View File

@ -80,20 +80,20 @@ class _VideoPageState extends State<VideoPage> with SingleTickerProviderStateMix
backgroundColor: ![0, 1, 2].contains(videoModuleController.videoTabIndex.value) ? null : Colors.transparent,
foregroundColor: ![0, 1, 2].contains(videoModuleController.videoTabIndex.value) ? Colors.black : Colors.white,
titleSpacing: 1.0,
leading: Obx(() => IconButton(
icon: Badge.count(
backgroundColor: Colors.red,
count: 6,
child: Icon(
Icons.sort_rounded,
color: tabColor(),
),
),
onPressed: () {
// drawer
scaffoldKey.currentState?.openDrawer();
},
)),
// leading: Obx(() => IconButton(
// icon: Badge.count(
// backgroundColor: Colors.red,
// count: 6,
// child: Icon(
// Icons.sort_rounded,
// color: tabColor(),
// ),
// ),
// onPressed: () {
// // drawer
// scaffoldKey.currentState?.openDrawer();
// },
// )),
title: Obx(() {
return ScrollConfiguration(
behavior: CustomScrollBehavior().copyWith(scrollbars: false),
@ -160,30 +160,30 @@ class _VideoPageState extends State<VideoPage> with SingleTickerProviderStateMix
),
),
//
drawer: Drawer(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.horizontal(right: Radius.circular(15.0))),
clipBehavior: Clip.antiAlias,
width: 300,
child: Container(
color: Colors.grey[50],
child: Column(
children: [
SizedBox(
height: 80.0,
),
Icon(
Icons.tips_and_updates_outlined,
color: Colors.grey,
size: 50.0,
),
Text(
'自定义侧边栏~',
style: TextStyle(color: Colors.grey, fontSize: 12.0),
)
],
),
),
),
// drawer: Drawer(
// shape: RoundedRectangleBorder(borderRadius: BorderRadius.horizontal(right: Radius.circular(15.0))),
// clipBehavior: Clip.antiAlias,
// width: 300,
// child: Container(
// color: Colors.grey[50],
// child: Column(
// children: [
// SizedBox(
// height: 80.0,
// ),
// Icon(
// Icons.tips_and_updates_outlined,
// color: Colors.grey,
// size: 50.0,
// ),
// Text(
// '自定义侧边栏~',
// style: TextStyle(color: Colors.grey, fontSize: 12.0),
// )
// ],
// ),
// ),
// ),
);
}
}

View File

@ -2,16 +2,24 @@
library;
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.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/im_core.dart';
import 'package:loopin/IM/im_message.dart';
import 'package:loopin/api/video_api.dart';
import 'package:loopin/components/my_toast.dart';
import 'package:loopin/components/network_or_asset_image.dart';
import 'package:loopin/models/summary_type.dart';
import 'package:loopin/service/http.dart';
import 'package:loopin/utils/wxsdk.dart';
import 'package:media_kit/media_kit.dart';
import 'package:media_kit_video/media_kit_video.dart';
import 'package:media_kit_video/media_kit_video_controls/src/controls/extensions/duration.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart';
import '../../../behavior/custom_scroll_behavior.dart';
import '../../../controller/video_module_controller.dart';
@ -40,6 +48,7 @@ class RecommendModule extends StatefulWidget {
class _RecommendModuleState extends State<RecommendModule> {
VideoModuleController videoModuleController = Get.put(VideoModuleController());
final ChatController chatController = Get.find<ChatController>();
// class _RecommendModuleState extends State<RecommendModule> with AutomaticKeepAliveClientMixin {
// @override
@ -80,10 +89,16 @@ class _RecommendModuleState extends State<RecommendModule> {
];
//
List shareList = [
{'icon': 'assets/images/share-wx.png', 'label': '好友'},
{'icon': 'assets/images/share-wx.png', 'label': '微信'},
{'icon': 'assets/images/share-pyq.png', 'label': '朋友圈'},
{'icon': 'assets/images/share-link.png', 'label': '复制链接'},
{'icon': 'assets/images/share-download.png', 'label': '下载'},
{'icon': 'assets/images/share-download.png', 'label': '下载'},
{'icon': 'assets/images/share-download.png', 'label': '下载'},
{'icon': 'assets/images/share-download.png', 'label': '下载'},
{'icon': 'assets/images/share-download.png', 'label': '下载下载下载下载下载下载下载下载下载下载下载下载'},
{'icon': 'assets/images/share-download.png', 'label': '下载下载下载下载下载下载下载下载下载下载下载下载'},
];
@override
@ -178,10 +193,12 @@ class _RecommendModuleState extends State<RecommendModule> {
if (data['rows'] is List) {
List videos = data['rows'];
// for (var item in videos) {
// print("喜欢:${item['likeCounts']}");
// print("评论:${item['commentsCounts']}");
// }
for (var item in videos) {
// print("喜欢:${item['likeCounts']}");
// print("评论:${item['commentsCounts']}");
// logger.i(item);
item['expanded'] = false;
}
setState(() {
if (page == 1) {
@ -221,6 +238,8 @@ class _RecommendModuleState extends State<RecommendModule> {
//
void handleComment(index) {
//
showModalBottomSheet(
backgroundColor: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(15.0))),
@ -423,59 +442,114 @@ class _RecommendModuleState extends State<RecommendModule> {
//
void handleShare(index) {
if (chatController.chatList.isNotEmpty) {
chatController.getConversationList();
}
showModalBottomSheet(
backgroundColor: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(15.0))),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(15.0)),
),
clipBehavior: Clip.antiAlias,
context: context,
isScrollControlled: true,
builder: (context) {
return Material(
color: Colors.white,
child: SizedBox(
height: 170,
width: double.infinity,
child: Padding(
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Expanded(
child: ScrollConfiguration(
behavior: CustomScrollBehavior().copyWith(scrollbars: false),
child: ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
padding: EdgeInsets.symmetric(vertical: 20.0, horizontal: 10.0),
itemCount: shareList.length,
itemBuilder: (context, index) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 12.0),
//
SizedBox(
height: 110,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: shareList.length,
padding: EdgeInsets.symmetric(horizontal: 0, vertical: 20.0),
itemBuilder: (context, index) {
return GestureDetector(
onTap: () => handleShareClick(index),
child: Container(
width: 64,
margin: EdgeInsets.symmetric(horizontal: 8.0),
child: Column(
spacing: 5.0,
mainAxisSize: MainAxisSize.min,
children: [
Image.asset('${shareList[index]['icon']}', width: 48.0),
SizedBox(height: 5),
Text(
'${shareList[index]['label']}',
style: TextStyle(fontSize: 12.0),
)
overflow: TextOverflow.ellipsis,
),
],
),
),
);
},
),
),
//
Obx(() {
//
final filteredList = chatController.chatList.where((item) => item.conversation.conversationGroupList?.isEmpty == true).toList();
if (filteredList.isEmpty) return SizedBox.shrink();
return SizedBox(
height: 110,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: filteredList.length,
padding: EdgeInsets.symmetric(horizontal: 0, vertical: 20.0),
itemBuilder: (context, index) {
return GestureDetector(
//
onTap: () => handlCoverClick(filteredList[index].conversation),
child: Container(
width: 64,
margin: EdgeInsets.symmetric(horizontal: 8.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Image.asset('${chatController.chatList[index].faceUrl}', width: 48.0),
NetworkOrAssetImage(
imageUrl: filteredList[index].faceUrl,
width: 48.0,
height: 48.0,
),
SizedBox(height: 5),
Text(
'${filteredList[index].conversation.showName}',
style: TextStyle(fontSize: 12.0),
overflow: TextOverflow.ellipsis,
),
],
),
),
);
},
),
),
),
InkWell(
child: Container(
alignment: Alignment.center,
width: double.infinity,
height: 50.0,
color: Colors.grey[50],
child: Text(
'取消',
style: TextStyle(color: Colors.black87),
);
}),
//
SafeArea(
top: false,
child: InkWell(
onTap: () => Get.back(),
child: Container(
alignment: Alignment.center,
width: double.infinity,
height: 50.0,
color: Colors.grey[50],
child: Text(
'取消',
style: TextStyle(color: Colors.black87),
),
),
),
onTap: () {
Get.back();
},
),
],
),
@ -485,6 +559,52 @@ class _RecommendModuleState extends State<RecommendModule> {
);
}
void handleShareClick(int index) {
print("分享项 $index 被点击");
final description = videoList[videoModuleController.videoPlayIndex.value]['title'] ?? '获取title失败';
if (index == 1) {
//
Wxsdk.shareToFriend(title: '快来看看这个视频', description: description, webpageUrl: 'https://baidu.com');
} else if (index == 2) {
//
Wxsdk.shareToTimeline(title: '快来看看这个视频', webpageUrl: 'https://baidu.com');
}
}
void handlCoverClick(V2TimConversation conv) async {
// VideoMsg,
final userId = conv.userID;
final String url = videoList[videoModuleController.videoPlayIndex.value]['url'];
final img = videoList[videoModuleController.videoPlayIndex.value]['firstFrameImg'];
final width = videoList[videoModuleController.videoPlayIndex.value]['width'];
final height = videoList[videoModuleController.videoPlayIndex.value]['height'];
final makeJson = jsonEncode({
"width": width,
"height": height,
"imgUrl": img,
"videoUrl": url,
});
final res = await IMMessage().createCustomMessage(
data: makeJson,
);
if (res.success) {
final sendRes = await IMMessage().sendMessage(msg: res.data!.messageInfo!, toUserID: userId, cloudCustomData: SummaryType.shareVideo);
if (sendRes.success) {
MyToast().tip(
title: '分享成功',
position: 'center',
type: 'success',
);
Get.back();
} else {
logger.e(res.desc);
}
} else {
logger.e(res.desc);
}
}
@override
Widget build(BuildContext context) {
// super.build(context);
@ -601,18 +721,25 @@ class _RecommendModuleState extends State<RecommendModule> {
SizedBox(
height: 55.0,
width: 48.0,
child: UnconstrainedBox(
alignment: Alignment.topCenter,
child: Container(
height: 48.0,
width: 48.0,
decoration: BoxDecoration(
border: Border.all(color: Colors.white, width: 2.0),
borderRadius: BorderRadius.circular(100.0),
),
child: ClipOval(
child:
Image.network(videoList[index]['vlogerFace'] ?? 'https://wuzhongjie.com.cn/download/logo.png', fit: BoxFit.cover),
child: GestureDetector(
onTap: () {
player.pause();
Get.toNamed('/vloger', arguments: videoList[videoModuleController.videoPlayIndex.value]);
},
child: UnconstrainedBox(
alignment: Alignment.topCenter,
child: Container(
height: 48.0,
width: 48.0,
decoration: BoxDecoration(
border: Border.all(color: Colors.white, width: 2.0),
borderRadius: BorderRadius.circular(100.0),
),
child: ClipOval(
child: NetworkOrAssetImage(
imageUrl: videoList[index]['avatar'],
),
),
),
),
),
@ -643,6 +770,7 @@ class _RecommendModuleState extends State<RecommendModule> {
),
],
),
//
GestureDetector(
child: Column(
children: [
@ -664,6 +792,7 @@ class _RecommendModuleState extends State<RecommendModule> {
});
},
),
//
GestureDetector(
child: Column(
children: [
@ -697,6 +826,7 @@ class _RecommendModuleState extends State<RecommendModule> {
// ),
// ],
// ),
//
GestureDetector(
child: Column(
children: [
@ -706,39 +836,96 @@ class _RecommendModuleState extends State<RecommendModule> {
height: 40.0,
width: 40.0,
),
// Text(
// '${videoList[index]['shareNum']}',
// style: TextStyle(color: Colors.white, fontSize: 12.0),
// ),
],
),
onTap: () {
handleShare(index);
},
),
//
GestureDetector(
child: Column(
children: [
SvgPicture.asset(
'assets/images/svg/report.svg',
colorFilter: ColorFilter.mode(Colors.white, BlendMode.srcIn),
height: 40.0,
width: 40.0,
),
],
),
onTap: () {
//
},
),
],
),
),
//
Positioned(
bottom: 15.0,
left: 10.0,
right: 80.0,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 5.0,
children: [
Text(
'@${videoList[index]['vlogerName'] ?? '未知'}',
style: TextStyle(color: Colors.white, fontSize: 16.0),
),
Text(
'${videoList[index]['content'] ?? '未知'}',
style: TextStyle(color: Colors.white, fontSize: 14.0),
),
],
),
),
bottom: 15.0,
left: 10.0,
right: 80.0,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'@${videoList[videoModuleController.videoPlayIndex.value]['nickname'] ?? '未知'}',
style: const TextStyle(color: Colors.white, fontSize: 16.0),
),
LayoutBuilder(
builder: (context, constraints) {
final text = videoList[videoModuleController.videoPlayIndex.value]['title'] ?? '未知';
// TextPainter 3
final span = TextSpan(
text: text,
style: const TextStyle(color: Colors.white, fontSize: 14.0),
);
final tp = TextPainter(
text: span,
maxLines: 3,
textDirection: TextDirection.ltr,
);
tp.layout(maxWidth: constraints.maxWidth);
final isOverflow = tp.didExceedMaxLines;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
text,
maxLines: videoList[videoModuleController.videoPlayIndex.value]['expanded'] ? null : 3,
overflow:
videoList[videoModuleController.videoPlayIndex.value]['expanded'] ? TextOverflow.visible : TextOverflow.ellipsis,
style: const TextStyle(color: Colors.white, fontSize: 14.0),
),
if (isOverflow)
Padding(
padding: const EdgeInsets.only(top: 6.0),
child: GestureDetector(
onTap: () {
setState(() {
videoList[videoModuleController.videoPlayIndex.value]['expanded'] =
!videoList[videoModuleController.videoPlayIndex.value]['expanded'];
});
},
child: Text(
videoList[videoModuleController.videoPlayIndex.value]['expanded'] ? '收起' : '展开更多',
textAlign: TextAlign.right,
style: const TextStyle(
color: Colors.white,
fontSize: 14,
fontWeight: FontWeight.bold,
),
),
),
),
],
);
},
),
],
)),
// mini播放进度条
Positioned(
bottom: 0.0,

View File

@ -3,7 +3,15 @@ library;
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:loopin/bings/chat_binding.dart';
import 'package:loopin/pages/chat/chat.dart';
import 'package:loopin/pages/chat/chat_group.dart';
import 'package:loopin/pages/chat/chat_no_friend.dart';
import 'package:loopin/pages/my/des.dart';
import 'package:loopin/pages/my/nick_name.dart';
import 'package:loopin/pages/my/setting.dart';
import 'package:loopin/pages/my/user_info.dart';
import 'package:loopin/pages/my/vloger.dart';
import '../layouts/index.dart';
/* 引入路由页面 */
@ -19,25 +27,61 @@ import '../utils/common.dart';
//
final Map<String, Widget> routes = {
'/': const Layout(),
// '/upload': const UploadVideoPage(),
'/goods': const Goods(),
'/chat': const Chat(),
// '/chat': const Chat(),
// '/chatNoFriend': const ChatNoFriend(),
// '/chatGroup': const ChatGroup(),
'/order': const Order(),
'/order/detail': const OrderDetail(),
'/vloger': const Vloger(),
//settins
'/setting': const Setting(),
'/userInfo': const UserInfo(),
'/notifications': const Setting(),
'/privacy': const Setting(),
'/about': const Setting(),
'/des': const Des(),
'/nickName': const NickName(),
};
final List<GetPage> routeList = routes.entries
.map((e) => GetPage(
name: e.key, //
page: () => e.value, //
transition: Transition.rightToLeftWithFade, //
// transition: Transition.rightToLeftWithFade, //
transition: Transition.rightToLeft, //
middlewares: [RouteMiddleware()], //
))
.toList();
final List<GetPage> bingsRoutes = [
GetPage(
name: '/chat',
page: () => const Chat(),
binding: ChatBinding(),
transition: Transition.rightToLeft,
middlewares: [RouteMiddleware()],
),
GetPage(
name: '/chatNoFriend',
page: () => const ChatNoFriend(),
binding: ChatBinding(),
transition: Transition.rightToLeft,
middlewares: [RouteMiddleware()],
),
GetPage(
name: '/chatGroup',
page: () => const ChatGroup(),
binding: ChatBinding(),
transition: Transition.rightToLeft,
middlewares: [RouteMiddleware()],
),
];
final List<GetPage> routePages = [
GetPage(name: '/login', page: () => const Login()),
...routeList,
...bingsRoutes,
];
//

View File

@ -1,4 +1,5 @@
import 'package:dio/dio.dart';
import 'package:loopin/IM/push_service.dart';
import 'http_config.dart';
@ -15,12 +16,16 @@ class Http {
}
static Future<dynamic> post(String url, {dynamic data, Map<String, dynamic>? headers}) async {
final res = await _dio.post(
url,
data: data,
options: Options(extra: headers ?? {}),
);
return res.data;
try {
final res = await _dio.post(
url,
data: data,
options: Options(extra: headers ?? {}),
);
return res.data;
} catch (e) {
logger.e('$e--------$url');
}
}
static Future<dynamic> put(String url, {dynamic data, Map<String, dynamic>? headers}) async {

View File

@ -6,10 +6,11 @@ import 'package:get_storage/get_storage.dart';
class HttpConfig {
static final Dio dio = Dio(BaseOptions(
// baseUrl: 'http://43.143.227.203:8099',
baseUrl: 'http://111.62.22.190:8080',
// baseUrl: 'http://111.62.22.190:8080',
// baseUrl: 'http://cjh.wuzhongjie.com.cn',
connectTimeout: Duration(seconds: 30),
receiveTimeout: Duration(seconds: 30),
baseUrl: 'http://82.156.121.2:8880',
// connectTimeout: Duration(seconds: 30),
// receiveTimeout: Duration(seconds: 30),
));
static final box = GetStorage();
@ -33,13 +34,14 @@ class HttpConfig {
handler.next(options);
},
onResponse: (response, handler) {
// logger.e(response.requestOptions.data);
final data = response.data;
// data['code'] = 200; //
if (data is Map<String, dynamic>) {
if (data['code'] != 200) {
Get.snackbar(
'错误',
data['msg'] ?? '请求失败',
'错误码${data['code']}',
'${response.requestOptions.uri}\n${response.requestOptions.data}\n${data['msg']}' ?? '请求失败',
duration: Duration(minutes: 1),
backgroundColor: Colors.red.withAlpha(230),
colorText: Colors.white,
icon: const Icon(Icons.error_outline, color: Colors.white),

View File

@ -27,7 +27,7 @@ class UpgradeDialog extends StatelessWidget {
children: [
Image.asset('assets/images/update/rocket.png', width: 80, height: 80),
const SizedBox(height: 12),
Text("发现新版本 v$version", style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
Text("发现新版本 $version", style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 10),
// Text(content, style: TextStyle(fontSize: 14)),
Column(

View File

@ -1,52 +1,64 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:loopin/IM/im_core.dart';
import 'package:loopin/api/common_api.dart';
import 'package:loopin/service/http.dart';
import 'package:package_info_plus/package_info_plus.dart';
// import 'package:loopin/api/common_api.dart';
// import 'package:loopin/service/http.dart';
import 'upgrade_dialog.dart';
import 'upgrade_util.dart';
class UpgradeService {
static Future<void> checkUpgrade(BuildContext context) async {
static Future<void> checkUpgrade(State state) async {
final info = await PackageInfo.fromPlatform();
print('App version: ${info.version}');
print('version_code: ${info.buildNumber}');
final currentVersion = info.version;
// final res = await Http.get(CommonApi.checkVersion);
// final data = res['data'];
final data = {
"version": "4.1.0",
"content": [
"新增火箭弹窗",
"修复若干 Bug",
"优化界面动画",
],
"force": 0,
"apkUrl": "https://wuzhongjie.com.cn/download/wzj.apk",
"iosUrl": "https://apps.apple.com/cn/app/无终街/id6479185362",
};
// 0 false非强制 0 true强制
final bool force = (data['force'] ?? 0) != 0;
//
showDialog(
context: context,
barrierDismissible: !force,
builder: (_) => UpgradeDialog(
version: data['version']?.toString() ?? '',
content: (data['content'] as List<dynamic>).map((e) => e.toString()).toList(),
force: force,
onConfirm: () {
if (Platform.isAndroid) {
Navigator.pop(context);
UpgradeUtil.downloadAndInstallAPK(context, data['apkUrl']?.toString() ?? '');
} else if (Platform.isIOS) {
UpgradeUtil.launchAppStore(data['iosUrl']?.toString() ?? '');
}
},
),
);
if (!state.mounted) return;
logger.i('App version: ${info.version}');
logger.i('version_code: ${info.buildNumber}');
final res = await Http.post(CommonApi.checkVersion, data: {
'platformType': Platform.isAndroid ? 'android' : 'ios',
'status': 1,
});
if (!state.mounted) return;
// logger.i(res);
final result = res['data']['records'] as List;
final data = result.first;
final currentVersion = info.buildNumber;
if (currentVersion != data['versionCode']) {
//
// 0 false非强制 0 true强制
final bool force = (data['isForceUpdate'] ?? 0) != 0;
//
showDialog(
context: state.context,
barrierDismissible: !force,
builder: (_) => UpgradeDialog(
version: data['versionName']?.toString() ?? '',
content: (data['releaseNotes'] as String).split(',').map((e) => e.trim()).toList(),
force: force,
onConfirm: () {
if (Platform.isAndroid) {
Navigator.pop(state.context);
UpgradeUtil.downloadAndInstallAPK(state.context, data['downloadUrl']?.toString() ?? '');
} else if (Platform.isIOS) {
UpgradeUtil.launchAppStore(data['downloadUrl']?.toString() ?? '');
}
},
),
);
}
// final data = {
// "version": "4.1.0",
// "content": [
// "新增火箭弹窗",
// "修复若干 Bug",
// "优化界面动画",
// ],
// "force": 0,
// "apkUrl": "https://wuzhongjie.com.cn/download/wzj.apk",
// "iosUrl": "https://apps.apple.com/cn/app/无终街/id6479185362",
// };
}
}

View File

@ -0,0 +1,57 @@
import 'package:audioplayers/audioplayers.dart';
import 'package:loopin/IM/im_core.dart';
class AudioPlayerService {
static final AudioPlayerService _instance = AudioPlayerService._internal();
factory AudioPlayerService() => _instance;
AudioPlayerService._internal();
final AudioPlayer _audioPlayer = AudioPlayer();
///
Future<void> playLocal(String filePath) async {
try {
await _audioPlayer.setSourceDeviceFile(filePath);
await _audioPlayer.resume();
} catch (e) {
logger.e('播放本地音频失败: $e');
}
}
///
Future<void> playNetwork(String url) async {
try {
await _audioPlayer.setSourceUrl(url);
await _audioPlayer.resume();
} catch (e) {
logger.e('播放网络音频失败: $e');
}
}
///
Future<void> pause() async {
try {
await _audioPlayer.pause();
} catch (e) {
logger.e('暂停播放失败: $e');
}
}
///
Future<void> stop() async {
try {
await _audioPlayer.stop();
} catch (e) {
logger.e('停止播放失败: $e');
}
}
///
Future<void> dispose() async {
try {
await _audioPlayer.dispose();
} catch (e) {
logger.e('释放播放器失败: $e');
}
}
}

View File

@ -5,7 +5,7 @@ import './storage.dart';
class Common {
///
static isLogin() {
static bool isLogin() {
return Storage.hasData('hasLogged');
}

View File

@ -55,7 +55,7 @@ class Utils {
}
///
static isEmpty(val) {
static bool isEmpty(val) {
if (val == null) return true;
if (val is bool && val == false) return true;
if (val is String) return val.isEmpty;
@ -87,6 +87,7 @@ class Utils {
//title
static String translateAlbumName(AssetPathEntity album) {
// logger.i(album.name);
final Map<String, String> albumNameMap = {
'recents': '最近项目',
'favorites': '收藏',
@ -105,6 +106,8 @@ class Utils {
'animated': '动图',
'raw': 'RAW格式',
'hidden': '已隐藏',
'recent': '最近项目', //
'camera': '相机', //
};
return albumNameMap[album.name.toLowerCase()] ?? album.name;
}

View File

@ -4,6 +4,7 @@ import 'package:loopin/IM/im_service.dart';
import 'package:loopin/utils/parse_message_summary.dart';
import 'package:shirne_dialog/shirne_dialog.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_user_full_info.dart';
class NotificationBanner {
static void show(V2TimMessage msg) {
@ -49,4 +50,32 @@ class NotificationBanner {
},
);
}
///
static void foucs(V2TimUserFullInfo msg) {
final nickname = msg.nickName ?? '未知用户';
final avatar = msg.faceUrl ?? '';
final text = '$nickname:关注了你';
Get.snackbar(
'新的关注',
text,
duration: const Duration(seconds: 5),
snackPosition: SnackPosition.TOP,
margin: const EdgeInsets.all(12),
backgroundColor: Get.theme.cardColor,
colorText: Get.theme.textTheme.bodyLarge?.color,
icon: avatar.isNotEmpty
? CircleAvatar(
backgroundImage: NetworkImage(avatar),
radius: 16,
)
: null,
onTap: (_) async {
//
Get.closeCurrentSnackbar();
//
},
);
}
}

View File

@ -1,5 +1,6 @@
import 'dart:convert';
import 'package:loopin/models/summary_type.dart';
import 'package:tencent_cloud_chat_sdk/enum/message_elem_type.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message.dart';
@ -14,37 +15,38 @@ String parseMessageSummary(V2TimMessage msg) {
case MessageElemType.V2TIM_ELEM_TYPE_VIDEO:
return '[视频]';
case MessageElemType.V2TIM_ELEM_TYPE_FILE:
return '[文件]';
return '[文件]'; //
case MessageElemType.V2TIM_ELEM_TYPE_LOCATION:
return '[位置]';
return '[位置]'; //
case MessageElemType.V2TIM_ELEM_TYPE_FACE:
return '[表情]';
case MessageElemType.V2TIM_ELEM_TYPE_CUSTOM:
return _parseCustomMessage(msg.customElem?.data);
return _parseCustomMessage(msg);
case MessageElemType.V2TIM_ELEM_TYPE_MERGER:
return '[合并转发消息]';
case MessageElemType.V2TIM_ELEM_TYPE_GROUP_TIPS:
return '[群提示]';
default:
return '[未知消息类型]';
return '[未知消息类型1]';
}
}
String _parseCustomMessage(String? data) {
if (data == null || data.isEmpty) return '[自定义消息]';
String _parseCustomMessage(V2TimMessage? msg) {
if (msg == null) return '[null]';
final sum = msg.cloudCustomData;
try {
final jsonData = json.decode(data);
final type = jsonData['type'];
switch (type) {
case 'interaction':
return '[互动] ${jsonData['action'] ?? ''}';
case 'forward':
return '[转发] ${jsonData['title'] ?? ''}';
switch (sum) {
case SummaryType.hongbao:
final hbData = jsonDecode(msg.customElem!.data!);
return '[红包]${hbData['remark']}';
case SummaryType.shareTuangou:
return '[分享商品]';
case SummaryType.shareVideo:
return '[分享视频]';
default:
return '[自定义消息]';
return '[未知消息类型2]';
}
} catch (_) {
return '[自定义消息]';
return '[未知消息类型3]';
}
}

15
lib/utils/snapshot.dart Normal file
View File

@ -0,0 +1,15 @@
import 'package:path_provider/path_provider.dart';
import 'package:video_thumbnail/video_thumbnail.dart';
///
Future<String?> generateVideoThumbnail(String videoPath) async {
final tempDir = await getTemporaryDirectory();
final thumbnailPath = await VideoThumbnail.thumbnailFile(
video: videoPath,
thumbnailPath: tempDir.path,
imageFormat: ImageFormat.JPEG,
maxWidth: 120,
quality: 75,
);
return thumbnailPath;
}

View File

@ -0,0 +1,80 @@
import 'dart:io';
import 'package:loopin/IM/im_core.dart';
import 'package:path_provider/path_provider.dart';
import 'package:record/record.dart';
class VoiceService {
static final VoiceService _instance = VoiceService._internal();
factory VoiceService() => _instance;
VoiceService._internal();
final AudioRecorder _recorder = AudioRecorder();
String? _voiceFilePath;
DateTime? _startTime;
///
Future<bool> startRecording() async {
if (await _recorder.hasPermission()) {
final dir = await getTemporaryDirectory(); //
final filePath = '${dir.path}/${DateTime.now().millisecondsSinceEpoch}.m4a';
_voiceFilePath = filePath;
_startTime = DateTime.now();
await _recorder.start(
const RecordConfig(
encoder: AudioEncoder.aacLc,
bitRate: 128000,
sampleRate: 44100,
),
path: filePath,
);
logger.i('开始录音,文件路径: $filePath');
return true;
} else {
logger.e("没有录音权限");
return false;
}
}
///
Future<Map<String, dynamic>?> stopRecording() async {
await _recorder.stop();
if (_voiceFilePath != null && File(_voiceFilePath!).existsSync() && _startTime != null) {
final duration = DateTime.now().difference(_startTime!);
final durationSeconds = duration.inSeconds;
if (durationSeconds < 1 || durationSeconds > 60) {
logger.w('录音时长不在允许范围1-60秒删除文件: $_voiceFilePath,时长: $durationSeconds');
try {
await File(_voiceFilePath!).delete();
} catch (e) {
logger.e('删除录音文件失败: $e');
}
return null;
}
logger.i('录音完成: $_voiceFilePath,时长: ${duration.inSeconds}');
return {
'path': _voiceFilePath,
'duration': duration.inMilliseconds,
};
}
logger.e("没有录到音频文件");
return null;
}
///
Future<void> cancelRecording() async {
await _recorder.stop();
if (_voiceFilePath != null) {
final file = File(_voiceFilePath!);
if (await file.exists()) {
await file.delete();
logger.i('录音已取消并删除');
}
}
_voiceFilePath = null;
}
}

144
lib/utils/wxsdk.dart Normal file
View File

@ -0,0 +1,144 @@
import 'package:flutter/services.dart';
import 'package:flutter_image_compress/flutter_image_compress.dart';
import 'package:fluwx/fluwx.dart';
import 'package:get/get.dart';
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/service/http.dart';
class Wxsdk {
static bool _inited = false;
static final Fluwx fluwx = Fluwx();
static Future<bool> init() async {
if (_inited) {
return true;
}
_inited = true;
final initRes = await fluwx.registerApi(
appId: 'wxebcdaea31881caab',
doOnAndroid: true,
doOnIOS: true,
universalLink: 'https://wuzhongjie.com.cn/',
);
if (initRes) {
logger.i('微信sdk初始化成功');
//
fluwx.addSubscriber(
(res) async {
//
if (res is WeChatAuthResponse) {
if (res.isSuccessful) {
final code = res.code;
logger.i('微信回调code: $code,类型:${res.state}');
if (res.state == 'getOpenId') {
// TODO: 使 code access_tokenunionid
final serverRes = await Http.post(CommonApi.wxLogin, data: {
"source": "wechat_open",
"socialCode": "${res.code}",
"socialState": "1",
"clientId": "428a8310cd442757ae699df5d894f051",
"grantType": "social"
});
final info = Get.find<ImUserInfoController>();
info.customInfo['openId'] = serverRes['data']['openId'];
info.updateOpenId();
info.customInfo.refresh();
logger.w(serverRes['data']['openId']);
}
} else {
logger.w('微信授权失败: ${res.errStr}-类型:${res.state}');
}
}
//
if (res is WeChatShareResponse) {
logger.w(res.isSuccessful);
//
if (res.isSuccessful) {}
}
},
);
} else {
logger.i('微信SDK初始化失败$initRes');
}
return initRes;
}
///
static Future<void> login() async {
final result = await fluwx.authBy(
which: NormalAuth(
scope: 'snsapi_userinfo',
state: 'getOpenId',
),
);
if (!result) {
logger.e('微信授权请求发送失败');
}
}
///
static Future<bool> shareToFriend({
required String title,
required String description,
required String webpageUrl,
String thumbnailAssetPath = 'assets/images/logo/logo.png',
}) async {
Uint8List? thumbData;
thumbData = await _loadLocalThumbnail(thumbnailAssetPath);
final model = WeChatShareWebPageModel(
webpageUrl,
title: title,
description: description,
thumbData: thumbData,
scene: WeChatScene.session,
);
return Fluwx().share(model);
}
///
static Future<bool> shareToTimeline({
required String title,
required String webpageUrl,
String thumbnailAssetPath = 'assets/images/logo/logo.png',
}) async {
Uint8List? thumbData;
thumbData = await _loadLocalThumbnail(thumbnailAssetPath);
final model = WeChatShareWebPageModel(
webpageUrl,
title: title,
thumbData: thumbData,
scene: WeChatScene.timeline,
);
return Fluwx().share(model);
}
static Future<Uint8List> _loadLocalThumbnail(String assetPath) async {
final byteData = await rootBundle.load(assetPath);
final originBytes = byteData.buffer.asUint8List();
final compressedBytes = await FlutterImageCompress.compressWithList(
originBytes,
minWidth: 120, // 120X120且小于32kb
minHeight: 120,
quality: 80, //
format: CompressFormat.jpeg, // JPEG
);
logger.i("thumbData size: ${compressedBytes.length} bytes");
return compressedBytes;
}
///
static Future<void> openMiniApp({required orderId}) async {
var miniProgram = MiniProgram(
username: "gh_2ffaecc5508e", // ID
path: "/pages/index/index?id=$orderId", //
miniProgramType: WXMiniProgramType.test,
);
Fluwx().open(target: miniProgram);
}
}

View File

@ -41,6 +41,62 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.12.0"
audioplayers:
dependency: "direct main"
description:
name: audioplayers
sha256: e653f162ddfcec1da2040ba2d8553fff1662b5c2a5c636f4c21a3b11bee497de
url: "https://pub.flutter-io.cn"
source: hosted
version: "6.5.0"
audioplayers_android:
dependency: transitive
description:
name: audioplayers_android
sha256: "60a6728277228413a85755bd3ffd6fab98f6555608923813ce383b190a360605"
url: "https://pub.flutter-io.cn"
source: hosted
version: "5.2.1"
audioplayers_darwin:
dependency: transitive
description:
name: audioplayers_darwin
sha256: "0811d6924904ca13f9ef90d19081e4a87f7297ddc19fc3d31f60af1aaafee333"
url: "https://pub.flutter-io.cn"
source: hosted
version: "6.3.0"
audioplayers_linux:
dependency: transitive
description:
name: audioplayers_linux
sha256: f75bce1ce864170ef5e6a2c6a61cd3339e1a17ce11e99a25bae4474ea491d001
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.2.1"
audioplayers_platform_interface:
dependency: transitive
description:
name: audioplayers_platform_interface
sha256: "0e2f6a919ab56d0fec272e801abc07b26ae7f31980f912f24af4748763e5a656"
url: "https://pub.flutter-io.cn"
source: hosted
version: "7.1.1"
audioplayers_web:
dependency: transitive
description:
name: audioplayers_web
sha256: "1c0f17cec68455556775f1e50ca85c40c05c714a99c5eb1d2d57cc17ba5522d7"
url: "https://pub.flutter-io.cn"
source: hosted
version: "5.1.1"
audioplayers_windows:
dependency: transitive
description:
name: audioplayers_windows
sha256: "4048797865105b26d47628e6abb49231ea5de84884160229251f37dfcbe52fd7"
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.2.1"
boolean_selector:
dependency: transitive
description:
@ -49,6 +105,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.2"
bottom_picker:
dependency: "direct main"
description:
name: bottom_picker
sha256: b83c35861314aafdef6857be1a8d900d82fa90c979a12af9b653d5d9e7d35beb
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.2.1"
card_swiper:
dependency: "direct main"
description:
@ -73,6 +137,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.3"
city_pickers:
dependency: "direct main"
description:
name: city_pickers
sha256: "583102c8d9eecb1f7abc5ff52a22d7cb019b9808cdb24b80c7692c769f8da153"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.3.0"
cli_util:
dependency: transitive
description:
@ -177,6 +249,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.1"
easy_refresh:
dependency: "direct main"
description:
name: easy_refresh
sha256: "486e30abfcaae66c0f2c2798a10de2298eb9dc5e0bb7e1dba9328308968cae0c"
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.4.0"
extended_image:
dependency: transitive
description:
@ -270,6 +350,62 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "10.0.1"
flutter_html:
dependency: "direct main"
description:
name: flutter_html
sha256: "38a2fd702ffdf3243fb7441ab58aa1bc7e6922d95a50db76534de8260638558d"
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.0.0"
flutter_image_compress:
dependency: "direct main"
description:
name: flutter_image_compress
sha256: "51d23be39efc2185e72e290042a0da41aed70b14ef97db362a6b5368d0523b27"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.4.0"
flutter_image_compress_common:
dependency: transitive
description:
name: flutter_image_compress_common
sha256: c5c5d50c15e97dd7dc72ff96bd7077b9f791932f2076c5c5b6c43f2c88607bfb
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.6"
flutter_image_compress_macos:
dependency: transitive
description:
name: flutter_image_compress_macos
sha256: "20019719b71b743aba0ef874ed29c50747461e5e8438980dfa5c2031898f7337"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.3"
flutter_image_compress_ohos:
dependency: transitive
description:
name: flutter_image_compress_ohos
sha256: e76b92bbc830ee08f5b05962fc78a532011fcd2041f620b5400a593e96da3f51
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.0.3"
flutter_image_compress_platform_interface:
dependency: transitive
description:
name: flutter_image_compress_platform_interface
sha256: "579cb3947fd4309103afe6442a01ca01e1e6f93dc53bb4cbd090e8ce34a41889"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.5"
flutter_image_compress_web:
dependency: transitive
description:
name: flutter_image_compress_web
sha256: b9b141ac7c686a2ce7bb9a98176321e1182c9074650e47bb140741a44b6f5a96
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.1.5"
flutter_launcher_icons:
dependency: "direct dev"
description:
@ -341,6 +477,14 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
fluwx:
dependency: "direct main"
description:
name: fluwx
sha256: "9db31d54043363c9c8283b5f0bc4df982edb45ba19d800df9d7de96a205371ae"
url: "https://pub.flutter-io.cn"
source: hosted
version: "5.7.0"
form_builder_validators:
dependency: "direct main"
description:
@ -581,6 +725,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "5.1.1"
list_counter:
dependency: transitive
description:
name: list_counter
sha256: c447ae3dfcd1c55f0152867090e67e219d42fe6d4f2807db4bbe8b8d69912237
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.2"
logger:
dependency: "direct main"
description:
@ -597,6 +749,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.3.0"
lpinyin:
dependency: transitive
description:
name: lpinyin
sha256: "0bb843363f1f65170efd09fbdfc760c7ec34fc6354f9fcb2f89e74866a0d814a"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.3"
matcher:
dependency: transitive
description:
@ -749,6 +909,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.9.1"
path_drawing:
dependency: transitive
description:
name: path_drawing
sha256: bbb1934c0cbb03091af082a6389ca2080345291ef07a5fa6d6e078ba8682f977
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.1"
path_parsing:
dependency: transitive
description:
@ -917,6 +1085,70 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "6.1.5"
record:
dependency: "direct main"
description:
name: record
sha256: daeb3f9b3fea9797094433fe6e49a879d8e4ca4207740bc6dc7e4a58764f0817
url: "https://pub.flutter-io.cn"
source: hosted
version: "6.0.0"
record_android:
dependency: transitive
description:
name: record_android
sha256: "97d7122455f30de89a01c6c244c839085be6b12abca251fc0e78f67fed73628b"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.3.3"
record_ios:
dependency: transitive
description:
name: record_ios
sha256: "73706ebbece6150654c9d6f57897cf9b622c581148304132ba85dba15df0fdfb"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.0"
record_linux:
dependency: transitive
description:
name: record_linux
sha256: "0626678a092c75ce6af1e32fe7fd1dea709b92d308bc8e3b6d6348e2430beb95"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.1"
record_macos:
dependency: transitive
description:
name: record_macos
sha256: "02240833fde16c33fcf2c589f3e08d4394b704761b4a3bb609d872ff3043fbbd"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.0"
record_platform_interface:
dependency: transitive
description:
name: record_platform_interface
sha256: c1ad38f51e4af88a085b3e792a22c685cb3e7c23fc37aa7ce44c4cf18f25fe89
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.3.0"
record_web:
dependency: transitive
description:
name: record_web
sha256: a12856d0b3dd03d336b4b10d7520a8b3e21649a06a8f95815318feaa8f07adbb
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.9"
record_windows:
dependency: transitive
description:
name: record_windows
sha256: "85a22fc97f6d73ecd67c8ba5f2f472b74ef1d906f795b7970f771a0914167e99"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.6"
safe_local_storage:
dependency: transitive
description:
@ -941,6 +1173,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.0"
scrollable_positioned_list:
dependency: transitive
description:
name: scrollable_positioned_list
sha256: "1b54d5f1329a1e263269abc9e2543d90806131aa14fe7c6062a8054d57249287"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.3.8"
shirne_dialog:
dependency: "direct main"
description:
@ -1002,6 +1242,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.3.1"
tencent_cloud_chat_push:
dependency: "direct main"
description:
name: tencent_cloud_chat_push
sha256: "7a76d107715e99fd4ed11489b9aa662e2f22d3f4614d9cee72e3f9d97c6f8b0f"
url: "https://pub.flutter-io.cn"
source: hosted
version: "8.6.7019+1"
tencent_cloud_chat_sdk:
dependency: "direct main"
description:
@ -1202,6 +1450,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.3.5"
video_thumbnail:
dependency: "direct main"
description:
name: video_thumbnail
sha256: "181a0c205b353918954a881f53a3441476b9e301641688a581e0c13f00dc588b"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.5.6"
visibility_detector:
dependency: transitive
description:

View File

@ -41,8 +41,11 @@ dependencies:
cupertino_icons: ^1.0.8
card_swiper: ^3.0.1
flutter_svg: ^2.0.16
easy_refresh: ^3.4.0
# 腾讯IM
tencent_cloud_chat_sdk: ^8.6.7019+2
# 离线推送
tencent_cloud_chat_push: ^8.6.7019+1
# 瀑布流组件
flutter_staggered_grid_view: ^0.7.0
# 状态管理
@ -67,14 +70,23 @@ dependencies:
wechat_assets_picker: ^9.5.1
device_info_plus: ^11.5.0
photo_manager: ^3.7.1
photo_manager: ^3.7.1 #翻译媒体库
flutter_form_builder: ^10.0.1
form_builder_validators: ^11.1.2
geolocator: ^14.0.1
nested_scroll_view_plus: ^3.0.0
nested_scroll_view_plus: ^3.0.0 #滚动
ai_barcode_scanner: ^7.0.0
city_pickers: ^1.3.0
bottom_picker: ^3.2.1
fluwx: ^5.7.0 #微信sdk
flutter_image_compress: ^2.4.0 #处理图片
video_thumbnail: ^0.5.6 #视频首帧截取
record: ^6.0.0 #音频
audioplayers: ^6.5.0 #音频播放
flutter_html: ^3.0.0
dev_dependencies:
flutter_launcher_icons: ^0.13.1 # 使用最新版本
@ -101,6 +113,8 @@ flutter:
assets:
- assets/images/
#notify
- assets/images/notify/
#logo
- assets/images/logo/
#avatar