主体功能完成
@ -6,6 +6,6 @@
|
|||||||
<item>
|
<item>
|
||||||
<bitmap
|
<bitmap
|
||||||
android:gravity="fill"
|
android:gravity="fill"
|
||||||
android:src="@mipmap/launch_image" />
|
android:src="@mipmap/launch_bg" />
|
||||||
</item>
|
</item>
|
||||||
</layer-list>
|
</layer-list>
|
||||||
@ -6,6 +6,6 @@
|
|||||||
<item>
|
<item>
|
||||||
<bitmap
|
<bitmap
|
||||||
android:gravity="fill"
|
android:gravity="fill"
|
||||||
android:src="@mipmap/launch_image" />
|
android:src="@mipmap/launch_bg" />
|
||||||
</item>
|
</item>
|
||||||
</layer-list>
|
</layer-list>
|
||||||
|
|||||||
BIN
android/app/src/main/res/mipmap-xhdpi/launch_bg.png
Executable file
|
After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 101 KiB |
BIN
android/app/src/main/res/mipmap-xxhdpi/launch_bg.png
Executable file
|
After Width: | Height: | Size: 101 KiB |
|
Before Width: | Height: | Size: 216 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/launch_bg.png
Executable file
|
After Width: | Height: | Size: 225 KiB |
|
Before Width: | Height: | Size: 367 KiB |
@ -19,7 +19,8 @@ pluginManagement {
|
|||||||
plugins {
|
plugins {
|
||||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||||
id "com.android.application" version "7.3.0" apply false
|
id "com.android.application" version "7.3.0" apply false
|
||||||
id "org.jetbrains.kotlin.android" version "1.7.10" apply false
|
// id "org.jetbrains.kotlin.android" version "1.7.10" apply false
|
||||||
|
id "org.jetbrains.kotlin.android" version "1.9.20" apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
include ":app"
|
include ":app"
|
||||||
|
|||||||
BIN
assets/images/side_a/launch_bg.png
Executable file
|
After Width: | Height: | Size: 101 KiB |
|
Before Width: | Height: | Size: 101 KiB |
BIN
assets/images/side_b/add_to_playlist.png
Executable file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
assets/images/side_b/add_to_queue.png
Executable file
|
After Width: | Height: | Size: 366 B |
BIN
assets/images/side_b/collected.png
Normal file
|
After Width: | Height: | Size: 653 B |
BIN
assets/images/side_b/delete_history.png
Normal file
|
After Width: | Height: | Size: 895 B |
BIN
assets/images/side_b/delete_white.png
Normal file
|
After Width: | Height: | Size: 332 B |
BIN
assets/images/side_b/downloaded.png
Normal file
|
After Width: | Height: | Size: 729 B |
BIN
assets/images/side_b/empty.jpg
Normal file
|
After Width: | Height: | Size: 8.1 KiB |
|
Before Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
BIN
assets/images/side_b/love_songs_bg.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
assets/images/side_b/more.png
Normal file
|
After Width: | Height: | Size: 417 B |
BIN
assets/images/side_b/more_edit.png
Normal file
|
After Width: | Height: | Size: 388 B |
BIN
assets/images/side_b/more_remove.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
assets/images/side_b/music_bar_next.png
Normal file
|
After Width: | Height: | Size: 672 B |
BIN
assets/images/side_b/music_placeholder.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 1016 B After Width: | Height: | Size: 1016 B |
BIN
assets/images/side_b/not_download2.png
Normal file
|
After Width: | Height: | Size: 961 B |
|
Before Width: | Height: | Size: 216 KiB After Width: | Height: | Size: 88 KiB |
BIN
assets/images/side_b/playlist_play_all.png
Normal file
|
After Width: | Height: | Size: 911 B |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 9.2 KiB |
BIN
assets/images/side_b/privacy_policy.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
assets/images/side_b/report.png
Executable file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
assets/images/side_b/search.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
assets/images/side_b/search_white.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
assets/images/side_b/setting_bg.png
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
assets/images/side_b/terms_of_service.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
@ -503,7 +503,7 @@
|
|||||||
PRODUCT_BUNDLE_IDENTIFIER = com.tone.music.offline;
|
PRODUCT_BUNDLE_IDENTIFIER = com.tone.music.offline;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = tone_snap;
|
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = ToneSnap_DEV;
|
||||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||||
SUPPORTS_MACCATALYST = NO;
|
SUPPORTS_MACCATALYST = NO;
|
||||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||||
@ -702,7 +702,7 @@
|
|||||||
PRODUCT_BUNDLE_IDENTIFIER = com.tone.music.offline;
|
PRODUCT_BUNDLE_IDENTIFIER = com.tone.music.offline;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = tone_snap;
|
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = ToneSnap_DEV;
|
||||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||||
SUPPORTS_MACCATALYST = NO;
|
SUPPORTS_MACCATALYST = NO;
|
||||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||||
@ -736,7 +736,7 @@
|
|||||||
PRODUCT_BUNDLE_IDENTIFIER = com.tone.music.offline;
|
PRODUCT_BUNDLE_IDENTIFIER = com.tone.music.offline;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = tone_snap;
|
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = ToneSnap_DEV;
|
||||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||||
SUPPORTS_MACCATALYST = NO;
|
SUPPORTS_MACCATALYST = NO;
|
||||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||||
|
|||||||
@ -51,7 +51,7 @@
|
|||||||
</Testables>
|
</Testables>
|
||||||
</TestAction>
|
</TestAction>
|
||||||
<LaunchAction
|
<LaunchAction
|
||||||
buildConfiguration = "Debug"
|
buildConfiguration = "Profile"
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
launchStyle = "0"
|
launchStyle = "0"
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 101 KiB |
|
Before Width: | Height: | Size: 216 KiB After Width: | Height: | Size: 225 KiB |
@ -24,18 +24,224 @@
|
|||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||||
|
<key>GADApplicationIdentifier</key>
|
||||||
|
<string>ca-app-pub-5684307632319406~7113477061</string>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>NSAppleMusicUsageDescription</key>
|
||||||
|
<string>We need to access the device media library to select audio files.</string>
|
||||||
<key>NSMicrophoneUsageDescription</key>
|
<key>NSMicrophoneUsageDescription</key>
|
||||||
<string>We need to access the microphone to record or select audio files.</string>
|
<string>We need to access the microphone to record or select audio files.</string>
|
||||||
<key>NSPhotoLibraryUsageDescription</key>
|
<key>NSPhotoLibraryUsageDescription</key>
|
||||||
<string>We need access to the photo library to pick audio files.</string>
|
<string>We need access to the photo library to pick audio files.</string>
|
||||||
<key>NSAppleMusicUsageDescription</key>
|
<key>NSUserTrackingUsageDescription</key>
|
||||||
<string>We need to access the device media library to select audio files.</string>
|
<string>We need your permission to access the advertising identifier to provide better ad services.</string>
|
||||||
<key>NSUserTrackingUsageDescription</key>
|
<key>SKAdNetworkItems</key>
|
||||||
<string>We need your permission to access the advertising identifier to provide better ad services.</string>
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>cstr6suwn9.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>4fzdc2evr5.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>4pfyvq9l8r.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>2fnua5tdw4.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>ydx93a7ass.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>5a6flpkh64.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>p78axxw29g.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>v72qych5uu.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>ludvb6z3bs.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>cp8zw746q7.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>3sh42y64q3.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>c6k4g5qg8m.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>s39g8k73mm.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>3qy4746246.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>f38h382jlk.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>hs6bdukanm.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>v4nxqhlyqp.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>wzmmz9fp6w.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>yclnxrl5pm.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>t38b2kh725.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>7ug5zh24hu.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>gta9lk7p23.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>vutu7akeur.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>y5ghdn5j9k.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>n6fk4nfna4.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>v9wttpbfk9.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>n38lu8286q.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>47vhws6wlr.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>kbd757ywx3.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>9t245vhmpl.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>eh6m2bh4zr.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>a2p9lx4jpn.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>22mmun2rn5.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>4468km3ulz.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>2u9pt9hc89.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>8s468mfl3y.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>klf5c3l5u5.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>ppxm28t8ap.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>ecpz2srf59.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>uw77j35x4d.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>pwa73g5rt2.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>mlmmfzh3r3.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>578prtvx9j.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>4dzt52r2t5.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>e5fvkxwrpn.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>8c4e2ghe7u.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>zq492l623r.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>3rd42ekr43.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>3qcr597p9d.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>UIBackgroundModes</key>
|
||||||
|
<array>
|
||||||
|
<string>audio</string>
|
||||||
|
<string>fetch</string>
|
||||||
|
</array>
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
<string>LaunchScreen</string>
|
<string>LaunchScreen</string>
|
||||||
<key>UIMainStoryboardFile</key>
|
<key>UIMainStoryboardFile</key>
|
||||||
@ -53,206 +259,5 @@
|
|||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
</array>
|
</array>
|
||||||
<key>GADApplicationIdentifier</key>
|
|
||||||
<string>ca-app-pub-5684307632319406~7113477061</string>
|
|
||||||
<key>SKAdNetworkItems</key>
|
|
||||||
<array>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>cstr6suwn9.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>4fzdc2evr5.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>4pfyvq9l8r.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>2fnua5tdw4.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>ydx93a7ass.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>5a6flpkh64.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>p78axxw29g.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>v72qych5uu.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>ludvb6z3bs.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>cp8zw746q7.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>3sh42y64q3.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>c6k4g5qg8m.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>s39g8k73mm.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>3qy4746246.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>f38h382jlk.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>hs6bdukanm.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>v4nxqhlyqp.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>wzmmz9fp6w.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>yclnxrl5pm.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>t38b2kh725.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>7ug5zh24hu.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>gta9lk7p23.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>vutu7akeur.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>y5ghdn5j9k.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>n6fk4nfna4.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>v9wttpbfk9.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>n38lu8286q.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>47vhws6wlr.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>kbd757ywx3.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>9t245vhmpl.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>eh6m2bh4zr.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>a2p9lx4jpn.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>22mmun2rn5.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>4468km3ulz.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>2u9pt9hc89.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>8s468mfl3y.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>klf5c3l5u5.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>ppxm28t8ap.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>ecpz2srf59.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>uw77j35x4d.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>pwa73g5rt2.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>mlmmfzh3r3.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>578prtvx9j.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>4dzt52r2t5.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>e5fvkxwrpn.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>8c4e2ghe7u.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>zq492l623r.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>3rd42ekr43.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>3qcr597p9d.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
</array>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
@ -7,7 +7,10 @@ import 'dart:io' show Platform;
|
|||||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
import 'package:google_mobile_ads/google_mobile_ads.dart';
|
import 'package:google_mobile_ads/google_mobile_ads.dart';
|
||||||
|
import 'package:tone_snap/data/storage/music_box.dart';
|
||||||
|
import 'package:tone_snap/modules/launch/launch_controller.dart';
|
||||||
import 'package:tone_snap/utils/log_util.dart';
|
import 'package:tone_snap/utils/log_util.dart';
|
||||||
|
|
||||||
class AppOpenAdManager {
|
class AppOpenAdManager {
|
||||||
@ -26,13 +29,16 @@ class AppOpenAdManager {
|
|||||||
AppOpenAd? _appOpenAd;
|
AppOpenAd? _appOpenAd;
|
||||||
bool _isShowingAd = false;
|
bool _isShowingAd = false;
|
||||||
|
|
||||||
|
/// 记录关闭时的时间,用于下一次展示时计算时间差
|
||||||
|
DateTime? closeDateTime;
|
||||||
|
|
||||||
/// 开屏广告单元id
|
/// 开屏广告单元id
|
||||||
final adUnitId = Platform.isAndroid
|
final adUnitId = Platform.isAndroid
|
||||||
? (kDebugMode ? 'ca-app-pub-3940256099942544/9257395921' : '')
|
? (kReleaseMode ? '' : 'ca-app-pub-3940256099942544/9257395921')
|
||||||
: (kDebugMode ? 'ca-app-pub-3940256099942544/5575463023' : 'ca-app-pub-5684307632319406/2523581084');
|
: (kReleaseMode ? 'ca-app-pub-5684307632319406/2523581084' : 'ca-app-pub-3940256099942544/5575463023');
|
||||||
|
|
||||||
/// 加载广告
|
/// 加载广告
|
||||||
void loadAd() async {
|
Future<void> loadAd() async {
|
||||||
final List<ConnectivityResult> connectivityResult = await (Connectivity().checkConnectivity());
|
final List<ConnectivityResult> connectivityResult = await (Connectivity().checkConnectivity());
|
||||||
if (connectivityResult.contains(ConnectivityResult.none)) {
|
if (connectivityResult.contains(ConnectivityResult.none)) {
|
||||||
return;
|
return;
|
||||||
@ -40,7 +46,7 @@ class AppOpenAdManager {
|
|||||||
if (isAdAvailable) {
|
if (isAdAvailable) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
AppOpenAd.load(
|
await AppOpenAd.load(
|
||||||
adUnitId: adUnitId,
|
adUnitId: adUnitId,
|
||||||
request: const AdRequest(),
|
request: const AdRequest(),
|
||||||
adLoadCallback: AppOpenAdLoadCallback(
|
adLoadCallback: AppOpenAdLoadCallback(
|
||||||
@ -48,6 +54,11 @@ class AppOpenAdManager {
|
|||||||
LogUtil.d('开屏广告加载完成');
|
LogUtil.d('开屏广告加载完成');
|
||||||
_appOpenAd = ad;
|
_appOpenAd = ad;
|
||||||
_appOpenLoadTime = DateTime.now();
|
_appOpenLoadTime = DateTime.now();
|
||||||
|
if (AppOpenAdManager().isAdAvailable) {
|
||||||
|
if (Get.isRegistered<LaunchController>()) {
|
||||||
|
LaunchController.to.editChangeValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onAdFailedToLoad: (error) {
|
onAdFailedToLoad: (error) {
|
||||||
LogUtil.e('开屏广告加载失败: $error');
|
LogUtil.e('开屏广告加载失败: $error');
|
||||||
@ -84,6 +95,17 @@ class AppOpenAdManager {
|
|||||||
if(onTap != null) onTap();
|
if(onTap != null) onTap();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (closeDateTime != null) {
|
||||||
|
// 计算时间差
|
||||||
|
Duration timeDifference = DateTime.now().difference(closeDateTime!);
|
||||||
|
// 获取配置的 openAppEventDuration
|
||||||
|
int openAppEventDuration = MusicBox().getOpenAppEventDuration();
|
||||||
|
// 检查时间差是否小于10秒
|
||||||
|
if (timeDifference < Duration(seconds: openAppEventDuration)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 设置 fullScreenContentCallback 并显示广告
|
// 设置 fullScreenContentCallback 并显示广告
|
||||||
_appOpenAd!.fullScreenContentCallback = FullScreenContentCallback(
|
_appOpenAd!.fullScreenContentCallback = FullScreenContentCallback(
|
||||||
// 暂停应用程序中的活动或记录广告展示的时间
|
// 暂停应用程序中的活动或记录广告展示的时间
|
||||||
@ -108,6 +130,8 @@ class AppOpenAdManager {
|
|||||||
},
|
},
|
||||||
onAdDismissedFullScreenContent: (ad) {
|
onAdDismissedFullScreenContent: (ad) {
|
||||||
LogUtil.d('$ad onAdDismissedFullScreenContent');
|
LogUtil.d('$ad onAdDismissedFullScreenContent');
|
||||||
|
closeDateTime = DateTime.now();
|
||||||
|
|
||||||
// 显示状态栏(用户关闭广告后)
|
// 显示状态栏(用户关闭广告后)
|
||||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: SystemUiOverlay.values);
|
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: SystemUiOverlay.values);
|
||||||
|
|
||||||
|
|||||||
@ -27,8 +27,8 @@ class InterstitialAdManager {
|
|||||||
|
|
||||||
/// 插页广告单元id
|
/// 插页广告单元id
|
||||||
final adUnitId = Platform.isAndroid
|
final adUnitId = Platform.isAndroid
|
||||||
? (kDebugMode ? 'ca-app-pub-3940256099942544/1033173712' : '')
|
? (kReleaseMode ? '' : 'ca-app-pub-3940256099942544/1033173712')
|
||||||
: (kDebugMode ? 'ca-app-pub-3940256099942544/4411468910' : 'ca-app-pub-5684307632319406/2760767691');
|
: (kReleaseMode ? 'ca-app-pub-5684307632319406/2760767691' : 'ca-app-pub-3940256099942544/4411468910');
|
||||||
|
|
||||||
/// 加载广告
|
/// 加载广告
|
||||||
void loadAd() async {
|
void loadAd() async {
|
||||||
|
|||||||
@ -43,7 +43,7 @@ class BaseEasyLoading {
|
|||||||
bool show = true,
|
bool show = true,
|
||||||
}) {
|
}) {
|
||||||
EasyLoading.instance.userInteractions = false;
|
EasyLoading.instance.userInteractions = false;
|
||||||
if (show) EasyLoading.show(status: value, dismissOnTap: true);
|
if (show) EasyLoading.show(status: value, dismissOnTap: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void dismiss({bool dismiss = true}) {
|
static void dismiss({bool dismiss = true}) {
|
||||||
|
|||||||
143
lib/components/dialog/add_playlist_dialog.dart
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
// Author: fengshengxiong
|
||||||
|
// Date: 2024/5/11
|
||||||
|
// Description: 创建播放列表弹框
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:tone_snap/components/divider_widget.dart';
|
||||||
|
import 'package:tone_snap/res/themes/app_colors.dart';
|
||||||
|
import 'package:tone_snap/utils/obj_util.dart';
|
||||||
|
|
||||||
|
class CreatePlaylistDialog extends Dialog {
|
||||||
|
CreatePlaylistDialog({super.key,this.name, required this.onTap});
|
||||||
|
|
||||||
|
final _textEditingController = TextEditingController();
|
||||||
|
final String? name;
|
||||||
|
final Function(String name) onTap;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
_textEditingController.text = name ?? '';
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
body: Material(
|
||||||
|
type: MaterialType.transparency,
|
||||||
|
child: Center(
|
||||||
|
child: IntrinsicHeight(
|
||||||
|
child: Container(
|
||||||
|
width: 1.sw * 0.72,
|
||||||
|
clipBehavior: Clip.antiAlias,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFF282A2C),
|
||||||
|
borderRadius: BorderRadius.circular(12.r),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
SizedBox(height: 19.h),
|
||||||
|
Text(
|
||||||
|
'Create playlist',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 17.sp,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 19.h),
|
||||||
|
Container(
|
||||||
|
height: 30.h,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
margin: EdgeInsets.symmetric(horizontal: 16.w),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFF1c1c1e),
|
||||||
|
borderRadius: BorderRadius.circular(6).r,
|
||||||
|
border: Border.all(
|
||||||
|
color: const Color(0xff333437),
|
||||||
|
width: 1.w,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: TextField(
|
||||||
|
maxLines: 1,
|
||||||
|
controller: _textEditingController,
|
||||||
|
textInputAction: TextInputAction.done,
|
||||||
|
keyboardType: TextInputType.text,
|
||||||
|
style: TextStyle(color: Colors.white, fontSize: 15.sp),
|
||||||
|
textAlignVertical: TextAlignVertical.center,
|
||||||
|
maxLength: 30,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
counterText: '',
|
||||||
|
hintText: 'My playlist1',
|
||||||
|
hintStyle: TextStyle(
|
||||||
|
color: const Color(0xFF5a635f),
|
||||||
|
fontSize: 15.sp,
|
||||||
|
),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 10).w,
|
||||||
|
isCollapsed: true,
|
||||||
|
border: InputBorder.none,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 19.h),
|
||||||
|
DividerWidget(
|
||||||
|
height: 0.5.h,
|
||||||
|
color: const Color(0xA6545458),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 44.h,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
_optionButton('Cancel', false),
|
||||||
|
Container(
|
||||||
|
width: 0.5.w,
|
||||||
|
height: double.infinity,
|
||||||
|
color: const Color(0xA6545458),
|
||||||
|
),
|
||||||
|
_optionButton('Confirm', true),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _optionButton(String label, bool isConfirm) {
|
||||||
|
return Expanded(
|
||||||
|
child: Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
if (isConfirm) {
|
||||||
|
if (ObjUtil.isNotEmpty(_textEditingController.text)) {
|
||||||
|
onTap(_textEditingController.text);
|
||||||
|
} else {
|
||||||
|
onTap('My playlist1');
|
||||||
|
}
|
||||||
|
Get.back();
|
||||||
|
} else {
|
||||||
|
Get.back();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: SizedBox(
|
||||||
|
height: double.infinity,
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
label,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
color: isConfirm ? seedColor : Colors.white,
|
||||||
|
fontSize: 17.sp,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -134,7 +134,7 @@ class RenameDialogState extends State<RenameDialog> {
|
|||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (isConfirm) {
|
if (isConfirm) {
|
||||||
if (!ObjUtil.isNotEmptyStr(_textEditingController.text)) {
|
if (!ObjUtil.isNotEmpty(_textEditingController.text)) {
|
||||||
BaseEasyLoading.toast('name cannot be empty');
|
BaseEasyLoading.toast('name cannot be empty');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
96
lib/components/get_bind_widget.dart
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
|
/// [author] Fson
|
||||||
|
/// [date] 2022/12/7
|
||||||
|
/// [description]
|
||||||
|
|
||||||
|
/// GetBindWidget can bind GetxController, and when the page is disposed,
|
||||||
|
/// it can automatically destroy the bound related GetXController
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// Sample:
|
||||||
|
///
|
||||||
|
/// class SampleController extends GetxController {
|
||||||
|
/// final String title = 'My Awesome View';
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// class SamplePage extends StatelessWidget {
|
||||||
|
/// final controller = SampleController();
|
||||||
|
///
|
||||||
|
/// @override
|
||||||
|
/// Widget build(BuildContext context) {
|
||||||
|
/// return GetBindWidget(
|
||||||
|
/// bind: controller,
|
||||||
|
/// child: Container(),
|
||||||
|
/// );
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
class GetBindWidget extends StatefulWidget {
|
||||||
|
const GetBindWidget({
|
||||||
|
super.key,
|
||||||
|
this.bind,
|
||||||
|
this.tag,
|
||||||
|
this.binds,
|
||||||
|
this.tags,
|
||||||
|
required this.child,
|
||||||
|
}) : assert(
|
||||||
|
binds == null || tags == null || binds.length == tags.length,
|
||||||
|
'The binds and tags arrays length should be equal\n'
|
||||||
|
'and the elements in the two arrays correspond one-to-one',
|
||||||
|
);
|
||||||
|
|
||||||
|
final GetxController? bind;
|
||||||
|
final String? tag;
|
||||||
|
|
||||||
|
final List<GetxController>? binds;
|
||||||
|
final List<String>? tags;
|
||||||
|
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
@override
|
||||||
|
GetBindWidgetState createState() => GetBindWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class GetBindWidgetState extends State<GetBindWidget>{
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return widget.child;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_closeGetXController();
|
||||||
|
_closeGetXControllers();
|
||||||
|
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
///Close GetxController bound to the current page
|
||||||
|
void _closeGetXController() {
|
||||||
|
if (widget.bind == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var key = widget.bind.runtimeType.toString() + (widget.tag ?? '');
|
||||||
|
GetInstance().delete(key: key);
|
||||||
|
}
|
||||||
|
|
||||||
|
///Batch close GetxController bound to the current page
|
||||||
|
void _closeGetXControllers() {
|
||||||
|
if (widget.binds == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < widget.binds!.length; i++) {
|
||||||
|
var type = widget.binds![i].runtimeType.toString();
|
||||||
|
|
||||||
|
if (widget.tags == null) {
|
||||||
|
GetInstance().delete(key: type);
|
||||||
|
} else {
|
||||||
|
var key = type + (widget.tags?[i] ?? '');
|
||||||
|
GetInstance().delete(key: key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,49 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:tone_snap/routes/app_routes.dart';
|
|
||||||
|
|
||||||
class MusicBarController extends GetxController with GetSingleTickerProviderStateMixin {
|
|
||||||
static MusicBarController get to => Get.find<MusicBarController>();
|
|
||||||
AnimationController? _controller;
|
|
||||||
var bottom = 0.0.obs;
|
|
||||||
double bottomPadding = 0.0;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void onInit() {
|
|
||||||
super.onInit();
|
|
||||||
_controller = AnimationController(vsync: this)..duration = const Duration(milliseconds: 200);
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
||||||
bottomPadding = MediaQuery.of(Get.context!).padding.bottom;
|
|
||||||
bottom.value = kBottomNavigationBarHeight + bottomPadding;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void onClose() {
|
|
||||||
_controller?.dispose();
|
|
||||||
super.onClose();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 底部导航栏消失时沉底
|
|
||||||
void toBottom() {
|
|
||||||
var animation = Tween<double>(begin: bottom.value, end: bottomPadding).animate(_controller!);
|
|
||||||
animation.addListener(() {
|
|
||||||
bottom.value = animation.value;
|
|
||||||
});
|
|
||||||
_controller!.forward();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 底部导航栏出现时抬高
|
|
||||||
void riseUp() {
|
|
||||||
var animation = Tween<double>(begin: bottom.value, end: kBottomNavigationBarHeight + bottomPadding).animate(_controller!);
|
|
||||||
animation.addListener(() {
|
|
||||||
bottom.value = animation.value;
|
|
||||||
});
|
|
||||||
_controller!.forward();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 打开播放页面
|
|
||||||
void openPlayPage() {
|
|
||||||
Get.toNamed(AppRoutes.playPage, arguments: {'isMusicBarOpen': true});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,95 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:tone_snap/components/my_marquee_text.dart';
|
|
||||||
import 'package:tone_snap/components/network_image_widget.dart';
|
|
||||||
import 'package:tone_snap/modules/sideb/controllers/music_player_controller.dart';
|
|
||||||
import 'package:tone_snap/res/themes/app_sizes.dart';
|
|
||||||
import 'package:tone_snap/utils/obj_util.dart';
|
|
||||||
|
|
||||||
import 'music_bar_controller.dart';
|
|
||||||
|
|
||||||
class MusicBarView extends StatelessWidget {
|
|
||||||
MusicBarView({super.key});
|
|
||||||
|
|
||||||
final controller = Get.put(MusicBarController(), permanent: true);
|
|
||||||
final musicPlayerController = MusicPlayerController.to;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Obx(() {
|
|
||||||
return Stack(
|
|
||||||
fit: StackFit.expand,
|
|
||||||
children: [
|
|
||||||
Positioned(
|
|
||||||
bottom: controller.bottom.value,
|
|
||||||
left: 16.w,
|
|
||||||
right: 16.w,
|
|
||||||
child: GestureDetector(
|
|
||||||
onTap: controller.openPlayPage,
|
|
||||||
child: Container(
|
|
||||||
width: 1.sw - 32.w,
|
|
||||||
height: musicBarHeight,
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 26.w),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: const Color(0xFF80F988),
|
|
||||||
borderRadius: BorderRadius.circular(36).r,
|
|
||||||
boxShadow: const [
|
|
||||||
BoxShadow(
|
|
||||||
color: Color(0x40040604),
|
|
||||||
offset: Offset(0, 4),
|
|
||||||
blurRadius: 4,
|
|
||||||
spreadRadius: 0,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
ClipOval(
|
|
||||||
child: Obx(() {
|
|
||||||
return NetworkImageWidget(
|
|
||||||
url: musicPlayerController.musicModel.value.thumbnail,
|
|
||||||
width: 48.w,
|
|
||||||
height: 48.w,
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
SizedBox(width: 12.w),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Obx(() {
|
|
||||||
return MyMarqueeText(
|
|
||||||
text: ObjUtil.getStr(musicPlayerController.musicModel.value.title),
|
|
||||||
textStyle: TextStyle(
|
|
||||||
color: Colors.black,
|
|
||||||
fontSize: 16.sp,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
SizedBox(height: 4.h),
|
|
||||||
Obx(() {
|
|
||||||
return MyMarqueeText(
|
|
||||||
text: ObjUtil.getStr(musicPlayerController.musicModel.value.subTitle),
|
|
||||||
textStyle: TextStyle(
|
|
||||||
color: Colors.black,
|
|
||||||
fontSize: 12.sp,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -20,8 +20,8 @@ class MyMarqueeText extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Marquee(
|
return Marquee(
|
||||||
delay: const Duration(seconds: 2),
|
delay: Duration.zero,
|
||||||
duration: Duration(seconds: enable ? 12 : 0),
|
duration: Duration(seconds: enable ? 16 : 0),
|
||||||
pause: Duration.zero,
|
pause: Duration.zero,
|
||||||
gap: 80,
|
gap: 80,
|
||||||
child: Text(text, style: textStyle),
|
child: Text(text, style: textStyle),
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:tone_snap/generated/assets.dart';
|
import 'package:tone_snap/generated/assets.dart';
|
||||||
|
|
||||||
class NetworkImageWidget extends StatelessWidget {
|
class NetworkImageWidget extends StatelessWidget {
|
||||||
@ -14,7 +15,9 @@ class NetworkImageWidget extends StatelessWidget {
|
|||||||
this.radius = 0.0,
|
this.radius = 0.0,
|
||||||
this.url,
|
this.url,
|
||||||
this.fit = BoxFit.cover,
|
this.fit = BoxFit.cover,
|
||||||
this.placeholder,
|
this.placeholderWidth,
|
||||||
|
this.placeholderHeight,
|
||||||
|
this.placeholderImg,
|
||||||
this.noPlaceholder = false,
|
this.noPlaceholder = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -23,7 +26,9 @@ class NetworkImageWidget extends StatelessWidget {
|
|||||||
final double radius;
|
final double radius;
|
||||||
final String? url;
|
final String? url;
|
||||||
final BoxFit fit;
|
final BoxFit fit;
|
||||||
final String? placeholder;
|
final double? placeholderWidth;
|
||||||
|
final double? placeholderHeight;
|
||||||
|
final String? placeholderImg;
|
||||||
final bool noPlaceholder;
|
final bool noPlaceholder;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -35,20 +40,27 @@ class NetworkImageWidget extends StatelessWidget {
|
|||||||
height: height,
|
height: height,
|
||||||
imageUrl: '$url',
|
imageUrl: '$url',
|
||||||
fit: fit,
|
fit: fit,
|
||||||
placeholder: noPlaceholder ? null : (context, url) {
|
placeholder: (context, url) {
|
||||||
return _placeholderWidget(Assets.sideBImgPlaceholder);
|
return noPlaceholder ? Container() : _placeholderWidget();
|
||||||
},
|
},
|
||||||
errorWidget: noPlaceholder ? null : (context, url, error) {
|
errorWidget: (context, url, error) {
|
||||||
return _placeholderWidget(Assets.sideBImgError);
|
return noPlaceholder ? Container() : _placeholderWidget();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _placeholderWidget(String img) {
|
Widget _placeholderWidget() {
|
||||||
return Image.asset(
|
return Container(
|
||||||
placeholder ?? img,
|
color: const Color(0xFF242529),
|
||||||
color: Colors.white12,
|
child: FittedBox(
|
||||||
|
fit: BoxFit.none,
|
||||||
|
child: Image.asset(
|
||||||
|
placeholderImg ?? Assets.sideBMusicPlaceholder,
|
||||||
|
width: placeholderWidth ?? 24.w,
|
||||||
|
height: placeholderHeight ?? 24.w,
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,58 +1,42 @@
|
|||||||
// Author: fengshengxiong
|
|
||||||
// Date: 2024/5/7
|
|
||||||
// Description: 下拉刷新、上拉加载
|
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:easy_refresh/easy_refresh.dart';
|
import 'package:easy_refresh/easy_refresh.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
|
||||||
import 'package:tone_snap/components/view_state_widget.dart';
|
|
||||||
|
|
||||||
class BaseEasyRefresh extends StatelessWidget {
|
class BaseEasyRefresh extends StatelessWidget {
|
||||||
const BaseEasyRefresh({
|
final EasyRefreshController? controller;
|
||||||
super.key,
|
final bool noMoreRefresh;
|
||||||
required this.controller,
|
final bool noMoreLoad;
|
||||||
this.header,
|
final bool refreshOnStart;
|
||||||
this.footer,
|
|
||||||
this.onRefresh,
|
|
||||||
this.onLoad,
|
|
||||||
this.refreshOnStart = false,
|
|
||||||
required this.viewState,
|
|
||||||
required this.child,
|
|
||||||
this.width,
|
|
||||||
this.height,
|
|
||||||
});
|
|
||||||
|
|
||||||
final EasyRefreshController controller;
|
|
||||||
final Header? header;
|
final Header? header;
|
||||||
final Footer? footer;
|
final Footer? footer;
|
||||||
final FutureOr Function()? onRefresh;
|
final FutureOr Function()? onRefresh;
|
||||||
final FutureOr Function()? onLoad;
|
final FutureOr Function()? onLoad;
|
||||||
final bool refreshOnStart;
|
|
||||||
final ViewState viewState;
|
|
||||||
final Widget child;
|
final Widget child;
|
||||||
final double? width;
|
|
||||||
final double? height;
|
const BaseEasyRefresh({
|
||||||
|
super.key,
|
||||||
|
this.controller,
|
||||||
|
this.noMoreRefresh = false,
|
||||||
|
this.noMoreLoad = false,
|
||||||
|
this.refreshOnStart = false,
|
||||||
|
this.header,
|
||||||
|
this.footer,
|
||||||
|
required this.child,
|
||||||
|
this.onRefresh,
|
||||||
|
this.onLoad,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return EasyRefresh(
|
return EasyRefresh(
|
||||||
|
refreshOnStart: refreshOnStart,
|
||||||
controller: controller,
|
controller: controller,
|
||||||
header: header ?? const ClassicHeader(),
|
header: header,
|
||||||
footer: footer ?? const ClassicFooter(),
|
footer: footer,
|
||||||
onRefresh: onRefresh,
|
onRefresh: onRefresh,
|
||||||
onLoad: onLoad,
|
onLoad: onLoad,
|
||||||
refreshOnStart: refreshOnStart,
|
child: child,
|
||||||
child: viewState == ViewState.normal ? child : SingleChildScrollView(
|
|
||||||
child: SizedBox(
|
|
||||||
width: width ?? 1.sw,
|
|
||||||
height: height ?? 300.h,
|
|
||||||
child: ViewStateWidget(
|
|
||||||
viewState: viewState,
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,10 @@
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
import 'package:tone_snap/data/enum/app_side_enum.dart';
|
||||||
|
import 'package:tone_snap/generated/assets.dart';
|
||||||
|
import 'package:tone_snap/global/app_config.dart';
|
||||||
|
import 'package:tone_snap/res/themes/app_colors.dart';
|
||||||
|
|
||||||
/// 四种视图状态
|
/// 四种视图状态
|
||||||
enum ViewState { normal, error, loading, empty }
|
enum ViewState { normal, error, loading, empty }
|
||||||
@ -26,11 +30,9 @@ class ViewStateWidget extends StatelessWidget {
|
|||||||
case ViewState.normal:
|
case ViewState.normal:
|
||||||
return child;
|
return child;
|
||||||
case ViewState.loading:
|
case ViewState.loading:
|
||||||
return loadingView(
|
return loadingView(backgroundColor: cpiBgColor);
|
||||||
backgroundColor: cpiBgColor,
|
|
||||||
);
|
|
||||||
case ViewState.empty:
|
case ViewState.empty:
|
||||||
return emptyView();
|
return AppConfig.appSideEnum == AppSideEnum.sideA ? emptyViewA() : emptyViewB();
|
||||||
case ViewState.error:
|
case ViewState.error:
|
||||||
return errorView();
|
return errorView();
|
||||||
}
|
}
|
||||||
@ -40,23 +42,19 @@ class ViewStateWidget extends StatelessWidget {
|
|||||||
/// 加载中视图
|
/// 加载中视图
|
||||||
Widget loadingView({
|
Widget loadingView({
|
||||||
Color? color,
|
Color? color,
|
||||||
Animation<Color?>? valueColor,
|
|
||||||
Color? backgroundColor,
|
Color? backgroundColor,
|
||||||
double? value,
|
|
||||||
}) {
|
}) {
|
||||||
return Center(
|
return Center(
|
||||||
child: CircularProgressIndicator(
|
child: CircularProgressIndicator(
|
||||||
strokeWidth: 3,
|
strokeWidth: 3,
|
||||||
color: color,
|
color: AppConfig.appSideEnum == AppSideEnum.sideA ? color : seedColor,
|
||||||
valueColor: valueColor,
|
|
||||||
backgroundColor: backgroundColor,
|
backgroundColor: backgroundColor,
|
||||||
value: value,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 空视图
|
/// 空视图
|
||||||
Widget emptyView({String? msg, Color? textColor}) {
|
Widget emptyViewA({String? msg, Color? textColor}) {
|
||||||
return Center(
|
return Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
msg ?? 'No data',
|
msg ?? 'No data',
|
||||||
@ -69,6 +67,17 @@ Widget emptyView({String? msg, Color? textColor}) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 空视图2
|
||||||
|
Widget emptyViewB() {
|
||||||
|
return Center(
|
||||||
|
child: Image.asset(
|
||||||
|
Assets.sideBEmpty,
|
||||||
|
width: 180.w,
|
||||||
|
height: 160.h,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// 错误视图
|
/// 错误视图
|
||||||
Widget errorView({String? msg, Color? textColor}) {
|
Widget errorView({String? msg, Color? textColor}) {
|
||||||
return Center(
|
return Center(
|
||||||
|
|||||||
@ -2,65 +2,72 @@
|
|||||||
// Date: 2024/6/18
|
// Date: 2024/6/18
|
||||||
// Description: 音乐播放器Api
|
// Description: 音乐播放器Api
|
||||||
|
|
||||||
import 'package:devicelocale/devicelocale.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:tone_snap/data/models/player_model.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:tone_snap/global/app_config.dart';
|
|
||||||
import 'package:tone_snap/data/models/browse_model.dart';
|
|
||||||
import 'package:tone_snap/data/network/dio_client.dart';
|
|
||||||
import 'package:tone_snap/data/models/next_model.dart';
|
import 'package:tone_snap/data/models/next_model.dart';
|
||||||
import 'package:tone_snap/utils/date_util.dart';
|
import 'package:tone_snap/data/models/player_model.dart';
|
||||||
|
import 'package:tone_snap/data/models/search_suggestions_model.dart';
|
||||||
|
import 'package:tone_snap/data/network/base_error.dart';
|
||||||
|
import 'package:tone_snap/data/network/dio_client.dart';
|
||||||
|
import 'package:tone_snap/data/storage/music_box.dart';
|
||||||
|
import 'package:tone_snap/global/app_config.dart';
|
||||||
|
|
||||||
class MusicApi {
|
class MusicApi {
|
||||||
static const String baseUrl = 'https://music.youtube.com/youtubei/v1/';
|
static const String baseUrl = 'https://music.youtube.com/youtubei/v1/';
|
||||||
|
|
||||||
/// 首页browse接口
|
/// browse
|
||||||
static Future<T?> browse<T>({
|
static Future<T?> browse<T>({
|
||||||
String? visitorData,
|
String? visitorData,
|
||||||
Map<String, dynamic>? queryParameters,
|
Map<String, dynamic>? queryParameters,
|
||||||
T Function(Map<String, dynamic>)? formJson,
|
T Function(Map<String, dynamic>)? formJson,
|
||||||
|
bool showLoading = false,
|
||||||
|
bool showToast = false,
|
||||||
}) async {
|
}) async {
|
||||||
String date = DateUtil.getSevenDaysAgo();
|
String clientVersion = MusicBox().getClientVersion();
|
||||||
String locale = await Devicelocale.currentLocale ?? AppConfig.defaultLocale;
|
String preferredLanguages = WidgetsBinding.instance.platformDispatcher.locale.languageCode;
|
||||||
final body = {
|
final body = {
|
||||||
"context": {
|
"context": {
|
||||||
"client": {
|
"client": {
|
||||||
"visitorData": visitorData,
|
"visitorData": visitorData,
|
||||||
"clientName": "WEB_REMIX",
|
"clientName": "WEB_REMIX",
|
||||||
// "clientVersion": "1.$date.01.00",
|
"clientVersion": clientVersion,
|
||||||
"clientVersion": "1.20240607.01.00",
|
|
||||||
"platform": "DESKTOP",
|
"platform": "DESKTOP",
|
||||||
"hl": locale,
|
"hl": preferredLanguages,
|
||||||
// "gl": AppConfig.isoCode
|
"gl": AppConfig.isoCode
|
||||||
"gl": 'HK'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
T? resultModel;
|
T? model;
|
||||||
await DioClient().request<T>(
|
await DioClient().request<T>(
|
||||||
'browse',
|
'browse',
|
||||||
|
showLoading: showLoading,
|
||||||
|
showToast: showToast,
|
||||||
requestMethod: RequestMethod.post,
|
requestMethod: RequestMethod.post,
|
||||||
data: body,
|
data: body,
|
||||||
queryParameters: queryParameters,
|
queryParameters: queryParameters,
|
||||||
formJson: formJson,
|
formJson: formJson,
|
||||||
success: (model) => resultModel = model,
|
success: (data) => model = data,
|
||||||
);
|
);
|
||||||
return resultModel;
|
return model;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// next接口
|
/// next
|
||||||
static Future<NextModel?> next({String? visitorData, String? playlistId, String? videoId}) async {
|
static Future<NextModel?> next({
|
||||||
String date = DateUtil.getSevenDaysAgo();
|
String? playlistId,
|
||||||
String locale = await Devicelocale.currentLocale ?? AppConfig.defaultLocale;
|
String? videoId,
|
||||||
|
bool showLoading = false,
|
||||||
|
bool showToast = true,
|
||||||
|
}) async {
|
||||||
|
String clientVersion = MusicBox().getClientVersion();
|
||||||
|
String preferredLanguages = WidgetsBinding.instance.platformDispatcher.locale.languageCode;
|
||||||
final body = {
|
final body = {
|
||||||
"context": {
|
"context": {
|
||||||
"client": {
|
"client": {
|
||||||
"visitorData": visitorData,
|
|
||||||
"clientName": "WEB_REMIX",
|
"clientName": "WEB_REMIX",
|
||||||
"clientVersion": "1.$date",
|
"clientVersion": clientVersion,
|
||||||
"platform": "DESKTOP",
|
"platform": "DESKTOP",
|
||||||
"hl": locale,
|
"hl": preferredLanguages,
|
||||||
// "gl": AppConfig.isoCode
|
"gl": AppConfig.isoCode
|
||||||
"gl": 'HK'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -69,28 +76,34 @@ class MusicApi {
|
|||||||
'playlistId': playlistId,
|
'playlistId': playlistId,
|
||||||
'videoId': videoId
|
'videoId': videoId
|
||||||
};
|
};
|
||||||
NextModel? nextModel;
|
NextModel? model;
|
||||||
await DioClient().request<NextModel>(
|
await DioClient().request<NextModel>(
|
||||||
'next',
|
'next',
|
||||||
showLoading: true,
|
showLoading: showLoading,
|
||||||
showToast: true,
|
showToast: showToast,
|
||||||
requestMethod: RequestMethod.post,
|
requestMethod: RequestMethod.post,
|
||||||
data: body,
|
data: body,
|
||||||
queryParameters: queryParameters,
|
queryParameters: queryParameters,
|
||||||
formJson: NextModel.fromMap,
|
formJson: NextModel.fromMap,
|
||||||
success: (model) => nextModel = model,
|
success: (data) => model = data,
|
||||||
);
|
);
|
||||||
return nextModel;
|
return model;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// player接口
|
/// player
|
||||||
static Future<PlayerModel?> player({String? visitorData, String? videoId}) async {
|
static Future<PlayerModel?> player({
|
||||||
String date = DateUtil.getSevenDaysAgo();
|
String? videoId,
|
||||||
|
CancelToken? cancelToken,
|
||||||
|
Function(BaseError baseError)? fail,
|
||||||
|
bool showLoading = false,
|
||||||
|
bool showToast = true,
|
||||||
|
}) async {
|
||||||
|
String playerVersion = MusicBox().getPlayerVersion();
|
||||||
final body = {
|
final body = {
|
||||||
"context": {
|
"context": {
|
||||||
"client": {
|
"client": {
|
||||||
"clientName": "ANDROID_MUSIC",
|
"clientName": "ANDROID_MUSIC",
|
||||||
"clientVersion": "6.07.1",
|
"clientVersion": playerVersion,
|
||||||
"platform": "MOBILE",
|
"platform": "MOBILE",
|
||||||
"browserVersion":"125.0.0.0"
|
"browserVersion":"125.0.0.0"
|
||||||
}
|
}
|
||||||
@ -101,17 +114,92 @@ class MusicApi {
|
|||||||
'playlistId': null,
|
'playlistId': null,
|
||||||
'videoId': videoId
|
'videoId': videoId
|
||||||
};
|
};
|
||||||
PlayerModel? playerModel;
|
PlayerModel? model;
|
||||||
await DioClient().request<PlayerModel>(
|
await DioClient().request<PlayerModel>(
|
||||||
'player',
|
'player',
|
||||||
showLoading: true,
|
showLoading: showLoading,
|
||||||
showToast: true,
|
showToast: showToast,
|
||||||
requestMethod: RequestMethod.post,
|
requestMethod: RequestMethod.post,
|
||||||
data: body,
|
data: body,
|
||||||
queryParameters: queryParameters,
|
queryParameters: queryParameters,
|
||||||
|
cancelToken: cancelToken,
|
||||||
formJson: PlayerModel.fromMap,
|
formJson: PlayerModel.fromMap,
|
||||||
success: (model) => playerModel = model,
|
success: (data) => model = data,
|
||||||
|
fail: fail,
|
||||||
);
|
);
|
||||||
return playerModel;
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 搜索建议
|
||||||
|
static Future<SearchSuggestionsModel?> searchSuggestions({
|
||||||
|
String? input,
|
||||||
|
CancelToken? cancelToken,
|
||||||
|
bool showLoading = false,
|
||||||
|
bool showToast = false,
|
||||||
|
}) async {
|
||||||
|
String clientVersion = MusicBox().getClientVersion();
|
||||||
|
String preferredLanguages = WidgetsBinding.instance.platformDispatcher.locale.languageCode;
|
||||||
|
final body = {
|
||||||
|
"context": {
|
||||||
|
"client": {
|
||||||
|
"clientName": "WEB_REMIX",
|
||||||
|
"clientVersion": clientVersion,
|
||||||
|
"platform": "DESKTOP",
|
||||||
|
"hl": preferredLanguages,
|
||||||
|
"gl": AppConfig.isoCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Map<String, dynamic>? queryParameters = {
|
||||||
|
'prettyPrint': false,
|
||||||
|
'input': input,
|
||||||
|
};
|
||||||
|
SearchSuggestionsModel? model;
|
||||||
|
await DioClient().request<SearchSuggestionsModel>(
|
||||||
|
'music/get_search_suggestions',
|
||||||
|
showLoading: showLoading,
|
||||||
|
showToast: showToast,
|
||||||
|
requestMethod: RequestMethod.post,
|
||||||
|
data: body,
|
||||||
|
queryParameters: queryParameters,
|
||||||
|
cancelToken: cancelToken,
|
||||||
|
formJson: SearchSuggestionsModel.fromMap,
|
||||||
|
success: (data) => model = data,
|
||||||
|
);
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 搜索
|
||||||
|
static Future<T?> search<T>({
|
||||||
|
Map<String, dynamic>? queryParameters,
|
||||||
|
T Function(Map<String, dynamic>)? formJson,
|
||||||
|
bool showLoading = false,
|
||||||
|
bool showToast = true,
|
||||||
|
}) async {
|
||||||
|
String clientVersion = MusicBox().getClientVersion();
|
||||||
|
String preferredLanguages = WidgetsBinding.instance.platformDispatcher.locale.languageCode;
|
||||||
|
final body = {
|
||||||
|
"context": {
|
||||||
|
"client": {
|
||||||
|
"clientName": "WEB_REMIX",
|
||||||
|
"clientVersion": clientVersion,
|
||||||
|
"platform": "DESKTOP",
|
||||||
|
"hl": preferredLanguages,
|
||||||
|
"gl": AppConfig.isoCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
T? model;
|
||||||
|
await DioClient().request<T>(
|
||||||
|
'search',
|
||||||
|
showLoading: showLoading,
|
||||||
|
showToast: showToast,
|
||||||
|
requestMethod: RequestMethod.post,
|
||||||
|
data: body,
|
||||||
|
queryParameters: queryParameters,
|
||||||
|
formJson: formJson,
|
||||||
|
success: (data) => model = data,
|
||||||
|
);
|
||||||
|
return model;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,13 +9,13 @@ import 'package:tone_snap/data/models/isocode_model.dart';
|
|||||||
class TikUsTokApi {
|
class TikUsTokApi {
|
||||||
static const String baseUrl = 'https://api.tikustok.com/';
|
static const String baseUrl = 'https://api.tikustok.com/';
|
||||||
|
|
||||||
/// 获取所在区域、ip
|
/// 获取所在区域
|
||||||
static Future<BaseModel<IosCodeModel>?> getIp() async {
|
static Future<BaseModel<IsoCodeModel>?> getIsoCode() async {
|
||||||
BaseModel<IosCodeModel>? baseModel;
|
BaseModel<IsoCodeModel>? baseModel;
|
||||||
await DioClient(baseUrl: baseUrl).request<BaseModel<IosCodeModel>>(
|
await DioClient(baseUrl: baseUrl).request<BaseModel<IsoCodeModel>>(
|
||||||
'app/common/getIPInfo',
|
'app/common/getIPInfo',
|
||||||
requestMethod: RequestMethod.get,
|
requestMethod: RequestMethod.get,
|
||||||
formJson: (json) => BaseModel<IosCodeModel>.fromMap(json, IosCodeModel.fromMap),
|
formJson: (json) => BaseModel<IsoCodeModel>.fromMap(json, IsoCodeModel.fromMap),
|
||||||
success: (model) => baseModel = model,
|
success: (model) => baseModel = model,
|
||||||
);
|
);
|
||||||
return baseModel;
|
return baseModel;
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
// Author: fengshengxiong
|
// Author: fengshengxiong
|
||||||
// Date: 2024/6/13
|
// Date: 2024/6/13
|
||||||
// Description: 资源类型
|
// Description: 音乐类型
|
||||||
|
|
||||||
enum BrowseType {
|
enum MusicType {
|
||||||
/// 电台/单曲
|
/// 单曲
|
||||||
musicVideoTypeAtv(name: 'MUSIC_VIDEO_TYPE_ATV'),
|
musicVideoTypeAtv(name: 'MUSIC_VIDEO_TYPE_ATV'),
|
||||||
|
|
||||||
/// 视频
|
/// 视频
|
||||||
@ -12,11 +12,11 @@ enum BrowseType {
|
|||||||
/// 专辑
|
/// 专辑
|
||||||
musicPageTypeAlbum(name: 'MUSIC_PAGE_TYPE_ALBUM'),
|
musicPageTypeAlbum(name: 'MUSIC_PAGE_TYPE_ALBUM'),
|
||||||
|
|
||||||
/// 艺术家
|
|
||||||
// musicPageTypeArtist(name: 'MUSIC_PAGE_TYPE_ARTIST'),
|
|
||||||
|
|
||||||
/// 歌单/列表
|
/// 歌单/列表
|
||||||
musicPageTypePlaylist(name: 'MUSIC_PAGE_TYPE_PLAYLIST');
|
musicPageTypePlaylist(name: 'MUSIC_PAGE_TYPE_PLAYLIST'),
|
||||||
|
|
||||||
|
/// 艺术家
|
||||||
|
musicPageTypeArtist(name: 'MUSIC_PAGE_TYPE_ARTIST');
|
||||||
|
|
||||||
/// 歌词
|
/// 歌词
|
||||||
// musicPageTypeTrackLyrics(name: 'MUSIC_PAGE_TYPE_TRACK_LYRICS'),
|
// musicPageTypeTrackLyrics(name: 'MUSIC_PAGE_TYPE_TRACK_LYRICS'),
|
||||||
@ -24,20 +24,21 @@ enum BrowseType {
|
|||||||
/// 相关内容
|
/// 相关内容
|
||||||
// musicPageTypeTrackRelated(name: 'MUSIC_PAGE_TYPE_TRACK_RELATED');
|
// musicPageTypeTrackRelated(name: 'MUSIC_PAGE_TYPE_TRACK_RELATED');
|
||||||
|
|
||||||
const BrowseType({
|
const MusicType({
|
||||||
required this.name,
|
required this.name,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String name;
|
final String name;
|
||||||
}
|
}
|
||||||
|
|
||||||
extension BrowseTypeExtension on BrowseType {
|
extension MusicTypeExtension on MusicType {
|
||||||
static bool isThereAny(String? type) {
|
static bool isThereAny(String? type) {
|
||||||
if (type == BrowseType.musicVideoTypeAtv.name ||
|
if (type == MusicType.musicVideoTypeAtv.name ||
|
||||||
type == BrowseType.musicVideoTypeOmv.name ||
|
// type == BrowseType.musicVideoTypeOmv.name ||
|
||||||
type == BrowseType.musicPageTypeAlbum.name ||
|
type == MusicType.musicPageTypeAlbum.name ||
|
||||||
// type == BrowseType.musicPageTypeArtist.name ||
|
type == MusicType.musicPageTypePlaylist.name
|
||||||
type == BrowseType.musicPageTypePlaylist.name) {
|
// type == BrowseType.musicPageTypeArtist.name
|
||||||
|
) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
35
lib/data/models/browse_group_model.dart
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// Author: fengshengxiong
|
||||||
|
// Date: 2024/6/20
|
||||||
|
// Description: Browse分组模型
|
||||||
|
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:tone_snap/data/models/music_model.dart';
|
||||||
|
|
||||||
|
class BrowseGroupModel {
|
||||||
|
String? groupTitle;
|
||||||
|
String? musicType;
|
||||||
|
List<MusicModel>? browseList;
|
||||||
|
|
||||||
|
BrowseGroupModel({
|
||||||
|
this.groupTitle,
|
||||||
|
this.musicType,
|
||||||
|
this.browseList,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory BrowseGroupModel.fromJson(String str) => BrowseGroupModel.fromMap(json.decode(str));
|
||||||
|
|
||||||
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
|
factory BrowseGroupModel.fromMap(Map<String, dynamic> json) => BrowseGroupModel(
|
||||||
|
groupTitle: json["groupTitle"],
|
||||||
|
musicType: json["musicType"],
|
||||||
|
browseList: json["browseList"] == null ? <MusicModel>[] : List<MusicModel>.from(json["browseList"]!.map((x) => MusicModel.fromMap(x))),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() => {
|
||||||
|
"groupTitle": groupTitle,
|
||||||
|
"musicType": musicType,
|
||||||
|
"browseList": browseList == null ? [] : List<dynamic>.from(browseList!.map((x) => x.toMap())),
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -1,77 +0,0 @@
|
|||||||
// Author: fengshengxiong
|
|
||||||
// Date: 2024/6/20
|
|
||||||
// Description: 首页实际展示数据模型
|
|
||||||
|
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
class HomeModel {
|
|
||||||
String? headerTitle;
|
|
||||||
List<Content>? contents;
|
|
||||||
String? browseType;
|
|
||||||
|
|
||||||
HomeModel({
|
|
||||||
this.headerTitle,
|
|
||||||
this.contents,
|
|
||||||
this.browseType,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory HomeModel.fromJson(String str) => HomeModel.fromMap(json.decode(str));
|
|
||||||
|
|
||||||
String toJson() => json.encode(toMap());
|
|
||||||
|
|
||||||
factory HomeModel.fromMap(Map<String, dynamic> json) => HomeModel(
|
|
||||||
headerTitle: json["headerTitle"],
|
|
||||||
contents: json["contents"] == null ? [] : List<Content>.from(json["contents"]!.map((x) => Content.fromMap(x))),
|
|
||||||
browseType: json["browseType"]
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> toMap() => {
|
|
||||||
"headerTitle": headerTitle,
|
|
||||||
"contents": contents == null ? [] : List<dynamic>.from(contents!.map((x) => x.toMap())),
|
|
||||||
"browseType": browseType,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
class Content {
|
|
||||||
String? title;
|
|
||||||
String? subTitle;
|
|
||||||
String? thumbnail;
|
|
||||||
String? videoId;
|
|
||||||
String? playlistId;
|
|
||||||
String? browseId;
|
|
||||||
String? params;
|
|
||||||
|
|
||||||
Content({
|
|
||||||
this.title,
|
|
||||||
this.subTitle,
|
|
||||||
this.thumbnail,
|
|
||||||
this.videoId,
|
|
||||||
this.playlistId,
|
|
||||||
this.browseId,
|
|
||||||
this.params,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory Content.fromJson(String str) => Content.fromMap(json.decode(str));
|
|
||||||
|
|
||||||
String toJson() => json.encode(toMap());
|
|
||||||
|
|
||||||
factory Content.fromMap(Map<String, dynamic> json) => Content(
|
|
||||||
title: json["title"],
|
|
||||||
subTitle: json["subTitle"],
|
|
||||||
thumbnail: json["thumbnail"],
|
|
||||||
videoId: json["videoId"],
|
|
||||||
playlistId: json["playlistId"],
|
|
||||||
browseId: json["browseId"],
|
|
||||||
params: json["params"],
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> toMap() => {
|
|
||||||
"title": title,
|
|
||||||
"subTitle": subTitle,
|
|
||||||
"thumbnail": thumbnail,
|
|
||||||
"videoId": videoId,
|
|
||||||
"playlistId": playlistId,
|
|
||||||
"browseId": browseId,
|
|
||||||
"params": params,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -4,20 +4,20 @@
|
|||||||
|
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
class IosCodeModel {
|
class IsoCodeModel {
|
||||||
String? isoCode;
|
String? isoCode;
|
||||||
String? ip;
|
String? ip;
|
||||||
|
|
||||||
IosCodeModel({
|
IsoCodeModel({
|
||||||
this.isoCode,
|
this.isoCode,
|
||||||
this.ip,
|
this.ip,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory IosCodeModel.fromJson(String str) => IosCodeModel.fromMap(json.decode(str));
|
factory IsoCodeModel.fromJson(String str) => IsoCodeModel.fromMap(json.decode(str));
|
||||||
|
|
||||||
String toJson() => json.encode(toMap());
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
factory IosCodeModel.fromMap(Map<String, dynamic> json) => IosCodeModel(
|
factory IsoCodeModel.fromMap(Map<String, dynamic> json) => IsoCodeModel(
|
||||||
isoCode: json["isoCode"],
|
isoCode: json["isoCode"],
|
||||||
ip: json["ip"],
|
ip: json["ip"],
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
// Author: fengshengxiong
|
// Author: fengshengxiong
|
||||||
// Date: 2024/6/24
|
// Date: 2024/6/24
|
||||||
// Description: 音乐模型
|
// Description: 音乐个体模型
|
||||||
|
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:background_downloader/background_downloader.dart';
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
|
|
||||||
part 'music_model.g.dart';
|
part 'music_model.g.dart';
|
||||||
@ -11,42 +13,82 @@ part 'music_model.g.dart';
|
|||||||
@HiveType(typeId: 2)
|
@HiveType(typeId: 2)
|
||||||
class MusicModel extends HiveObject {
|
class MusicModel extends HiveObject {
|
||||||
@HiveField(0)
|
@HiveField(0)
|
||||||
String? title;
|
|
||||||
@HiveField(1)
|
|
||||||
String? subTitle;
|
|
||||||
@HiveField(2)
|
|
||||||
String? thumbnail;
|
|
||||||
@HiveField(3)
|
|
||||||
String? videoId;
|
String? videoId;
|
||||||
|
@HiveField(1)
|
||||||
|
String? title;
|
||||||
|
@HiveField(2)
|
||||||
|
String? subtitle;
|
||||||
|
@HiveField(3)
|
||||||
|
String? coverUrl;
|
||||||
@HiveField(4)
|
@HiveField(4)
|
||||||
String? playlistId;
|
|
||||||
@HiveField(5)
|
|
||||||
String? url;
|
String? url;
|
||||||
|
@HiveField(5)
|
||||||
|
String? localPath;
|
||||||
|
@HiveField(6)
|
||||||
|
String? musicType;
|
||||||
|
@HiveField(7)
|
||||||
|
String? playlistId;
|
||||||
|
@HiveField(8)
|
||||||
|
String? browseId;
|
||||||
|
@HiveField(9)
|
||||||
|
String? params;
|
||||||
|
|
||||||
|
/// 收藏状态
|
||||||
|
bool isLove = false;
|
||||||
|
|
||||||
|
/// 下载进度、任务状态
|
||||||
|
double progress;
|
||||||
|
TaskStatus? taskStatus;
|
||||||
|
CancelToken? cancelToken;
|
||||||
|
|
||||||
MusicModel({
|
MusicModel({
|
||||||
this.title,
|
|
||||||
this.subTitle,
|
|
||||||
this.thumbnail,
|
|
||||||
this.videoId,
|
this.videoId,
|
||||||
this.playlistId,
|
this.title,
|
||||||
|
this.subtitle,
|
||||||
|
this.coverUrl,
|
||||||
this.url,
|
this.url,
|
||||||
|
this.localPath,
|
||||||
|
this.musicType,
|
||||||
|
this.playlistId,
|
||||||
|
this.browseId,
|
||||||
|
this.params,
|
||||||
|
this.isLove = false,
|
||||||
|
this.progress = 0.0,
|
||||||
|
this.taskStatus,
|
||||||
|
this.cancelToken,
|
||||||
});
|
});
|
||||||
|
|
||||||
MusicModel copyWith({
|
MusicModel copyWith({
|
||||||
String? title,
|
|
||||||
String? subTitle,
|
|
||||||
String? thumbnail,
|
|
||||||
String? url,
|
|
||||||
String? videoId,
|
String? videoId,
|
||||||
|
String? title,
|
||||||
|
String? subtitle,
|
||||||
|
String? coverUrl,
|
||||||
|
String? url,
|
||||||
|
String? localPath,
|
||||||
|
String? musicType,
|
||||||
String? playlistId,
|
String? playlistId,
|
||||||
|
String? browseId,
|
||||||
|
String? params,
|
||||||
|
bool? isLove,
|
||||||
|
double? progress,
|
||||||
|
TaskStatus? taskStatus,
|
||||||
|
CancelToken? cancelToken,
|
||||||
}) =>
|
}) =>
|
||||||
MusicModel(
|
MusicModel(
|
||||||
title: title ?? this.title,
|
|
||||||
subTitle: subTitle ?? this.subTitle,
|
|
||||||
thumbnail: thumbnail ?? this.thumbnail,
|
|
||||||
url: url ?? this.url,
|
|
||||||
videoId: videoId ?? this.videoId,
|
videoId: videoId ?? this.videoId,
|
||||||
|
title: title ?? this.title,
|
||||||
|
subtitle: subtitle ?? this.subtitle,
|
||||||
|
coverUrl: coverUrl ?? this.coverUrl,
|
||||||
|
url: url ?? this.url,
|
||||||
|
localPath: localPath ?? this.localPath,
|
||||||
|
musicType: musicType ?? this.musicType,
|
||||||
playlistId: playlistId ?? this.playlistId,
|
playlistId: playlistId ?? this.playlistId,
|
||||||
|
browseId: browseId ?? this.browseId,
|
||||||
|
params: params ?? this.params,
|
||||||
|
isLove: isLove ?? this.isLove,
|
||||||
|
progress: progress ?? this.progress,
|
||||||
|
taskStatus: taskStatus ?? this.taskStatus,
|
||||||
|
cancelToken: cancelToken ?? this.cancelToken,
|
||||||
);
|
);
|
||||||
|
|
||||||
factory MusicModel.fromJson(String str) =>
|
factory MusicModel.fromJson(String str) =>
|
||||||
@ -55,20 +97,36 @@ class MusicModel extends HiveObject {
|
|||||||
String toJson() => json.encode(toMap());
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
factory MusicModel.fromMap(Map<String, dynamic> json) => MusicModel(
|
factory MusicModel.fromMap(Map<String, dynamic> json) => MusicModel(
|
||||||
title: json["title"],
|
|
||||||
subTitle: json["subTitle"],
|
|
||||||
thumbnail: json["thumbnail"],
|
|
||||||
videoId: json["videoId"],
|
videoId: json["videoId"],
|
||||||
playlistId: json["playlistId"],
|
title: json["title"],
|
||||||
|
subtitle: json["subtitle"],
|
||||||
|
coverUrl: json["coverUrl"],
|
||||||
url: json["url"],
|
url: json["url"],
|
||||||
|
localPath: json["localPath"],
|
||||||
|
musicType: json["musicType"],
|
||||||
|
playlistId: json["playlistId"],
|
||||||
|
browseId: json["browseId"],
|
||||||
|
params: json["params"],
|
||||||
|
isLove: json["isLove"] ?? false,
|
||||||
|
progress: json["progress"] ?? 0.0,
|
||||||
|
taskStatus: json["taskStatus"],
|
||||||
|
cancelToken: json["cancelToken"],
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> toMap() => {
|
Map<String, dynamic> toMap() => {
|
||||||
"title": title,
|
|
||||||
"subTitle": subTitle,
|
|
||||||
"thumbnail": thumbnail,
|
|
||||||
"videoId": videoId,
|
"videoId": videoId,
|
||||||
"playlistId": playlistId,
|
"title": title,
|
||||||
|
"subTitle": subtitle,
|
||||||
|
"coverUrl": coverUrl,
|
||||||
"url": url,
|
"url": url,
|
||||||
|
"localPath": localPath,
|
||||||
|
"musicType": musicType,
|
||||||
|
"playlistId": playlistId,
|
||||||
|
"browseId": browseId,
|
||||||
|
"params": params,
|
||||||
|
"isLove": isLove,
|
||||||
|
"progress": progress,
|
||||||
|
"taskStatus": taskStatus,
|
||||||
|
"cancelToken": cancelToken,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,31 +17,40 @@ class MusicModelAdapter extends TypeAdapter<MusicModel> {
|
|||||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||||
};
|
};
|
||||||
return MusicModel(
|
return MusicModel(
|
||||||
title: fields[0] as String?,
|
videoId: fields[0] as String?,
|
||||||
subTitle: fields[1] as String?,
|
title: fields[1] as String?,
|
||||||
thumbnail: fields[2] as String?,
|
subtitle: fields[2] as String?,
|
||||||
videoId: fields[3] as String?,
|
coverUrl: fields[3] as String?,
|
||||||
playlistId: fields[4] as String?,
|
url: fields[4] as String?,
|
||||||
url: fields[5] as String?,
|
localPath: fields[5] as String?,
|
||||||
|
musicType: fields[6] as String?,
|
||||||
|
playlistId: fields[7] as String?,
|
||||||
|
browseId: fields[8] as String?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void write(BinaryWriter writer, MusicModel obj) {
|
void write(BinaryWriter writer, MusicModel obj) {
|
||||||
writer
|
writer
|
||||||
..writeByte(6)
|
..writeByte(9)
|
||||||
..writeByte(0)
|
..writeByte(0)
|
||||||
..write(obj.title)
|
|
||||||
..writeByte(1)
|
|
||||||
..write(obj.subTitle)
|
|
||||||
..writeByte(2)
|
|
||||||
..write(obj.thumbnail)
|
|
||||||
..writeByte(3)
|
|
||||||
..write(obj.videoId)
|
..write(obj.videoId)
|
||||||
|
..writeByte(1)
|
||||||
|
..write(obj.title)
|
||||||
|
..writeByte(2)
|
||||||
|
..write(obj.subtitle)
|
||||||
|
..writeByte(3)
|
||||||
|
..write(obj.coverUrl)
|
||||||
..writeByte(4)
|
..writeByte(4)
|
||||||
..write(obj.playlistId)
|
..write(obj.url)
|
||||||
..writeByte(5)
|
..writeByte(5)
|
||||||
..write(obj.url);
|
..write(obj.localPath)
|
||||||
|
..writeByte(6)
|
||||||
|
..write(obj.musicType)
|
||||||
|
..writeByte(7)
|
||||||
|
..write(obj.playlistId)
|
||||||
|
..writeByte(8)
|
||||||
|
..write(obj.browseId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
85
lib/data/models/playlist_model.dart
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
// Author: fengshengxiong
|
||||||
|
// Date: 2024/6/24
|
||||||
|
// Description: 播放列表模型
|
||||||
|
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:tone_snap/data/models/music_model.dart';
|
||||||
|
|
||||||
|
part 'playlist_model.g.dart';
|
||||||
|
|
||||||
|
@HiveType(typeId: 3)
|
||||||
|
class PlaylistModel extends HiveObject {
|
||||||
|
@HiveField(0)
|
||||||
|
String id;
|
||||||
|
@HiveField(1)
|
||||||
|
String title;
|
||||||
|
|
||||||
|
/// playlists
|
||||||
|
@HiveField(2)
|
||||||
|
int? milliseconds;
|
||||||
|
@HiveField(3)
|
||||||
|
List<MusicModel>? musicList;
|
||||||
|
|
||||||
|
/// collect_playlists
|
||||||
|
@HiveField(4)
|
||||||
|
String? params;
|
||||||
|
@HiveField(5)
|
||||||
|
String? coverUrl;
|
||||||
|
@HiveField(6)
|
||||||
|
String? subtitle;
|
||||||
|
|
||||||
|
PlaylistModel({
|
||||||
|
required this.id,
|
||||||
|
required this.title,
|
||||||
|
this.milliseconds,
|
||||||
|
this.musicList,
|
||||||
|
this.params,
|
||||||
|
this.coverUrl,
|
||||||
|
this.subtitle,
|
||||||
|
});
|
||||||
|
|
||||||
|
PlaylistModel copyWith({
|
||||||
|
required String id,
|
||||||
|
required String title,
|
||||||
|
int? milliseconds,
|
||||||
|
List<MusicModel>? musicList,
|
||||||
|
String? params,
|
||||||
|
String? coverUrl,
|
||||||
|
String? subtitle,
|
||||||
|
}) =>
|
||||||
|
PlaylistModel(
|
||||||
|
id: id,
|
||||||
|
title: title,
|
||||||
|
milliseconds: milliseconds ?? this.milliseconds,
|
||||||
|
musicList: musicList ?? this.musicList,
|
||||||
|
params: params ?? this.params,
|
||||||
|
coverUrl: coverUrl ?? this.coverUrl,
|
||||||
|
subtitle: subtitle ?? this.subtitle,
|
||||||
|
);
|
||||||
|
|
||||||
|
factory PlaylistModel.fromJson(String str) => PlaylistModel.fromMap(json.decode(str));
|
||||||
|
|
||||||
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
|
factory PlaylistModel.fromMap(Map<String, dynamic> json) => PlaylistModel(
|
||||||
|
id: json["id"],
|
||||||
|
title: json["title"],
|
||||||
|
milliseconds: json["milliseconds"],
|
||||||
|
musicList: json["musicList"],
|
||||||
|
params: json["params"],
|
||||||
|
coverUrl: json["coverUrl"],
|
||||||
|
subtitle: json["subtitle"],
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() => {
|
||||||
|
"id": id,
|
||||||
|
"title": title,
|
||||||
|
"milliseconds": milliseconds,
|
||||||
|
"musicList": musicList,
|
||||||
|
"params": params,
|
||||||
|
"coverUrl": coverUrl,
|
||||||
|
"subtitle": subtitle,
|
||||||
|
};
|
||||||
|
}
|
||||||
59
lib/data/models/playlist_model.g.dart
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'playlist_model.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// TypeAdapterGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
class PlaylistModelAdapter extends TypeAdapter<PlaylistModel> {
|
||||||
|
@override
|
||||||
|
final int typeId = 3;
|
||||||
|
|
||||||
|
@override
|
||||||
|
PlaylistModel read(BinaryReader reader) {
|
||||||
|
final numOfFields = reader.readByte();
|
||||||
|
final fields = <int, dynamic>{
|
||||||
|
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||||
|
};
|
||||||
|
return PlaylistModel(
|
||||||
|
id: fields[0] as String,
|
||||||
|
title: fields[1] as String,
|
||||||
|
milliseconds: fields[2] as int?,
|
||||||
|
musicList: (fields[3] as List?)?.cast<MusicModel>(),
|
||||||
|
params: fields[4] as String?,
|
||||||
|
coverUrl: fields[5] as String?,
|
||||||
|
subtitle: fields[6] as String?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void write(BinaryWriter writer, PlaylistModel obj) {
|
||||||
|
writer
|
||||||
|
..writeByte(7)
|
||||||
|
..writeByte(0)
|
||||||
|
..write(obj.id)
|
||||||
|
..writeByte(1)
|
||||||
|
..write(obj.title)
|
||||||
|
..writeByte(2)
|
||||||
|
..write(obj.milliseconds)
|
||||||
|
..writeByte(3)
|
||||||
|
..write(obj.musicList)
|
||||||
|
..writeByte(4)
|
||||||
|
..write(obj.params)
|
||||||
|
..writeByte(5)
|
||||||
|
..write(obj.coverUrl)
|
||||||
|
..writeByte(6)
|
||||||
|
..write(obj.subtitle);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => typeId.hashCode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
identical(this, other) ||
|
||||||
|
other is PlaylistModelAdapter &&
|
||||||
|
runtimeType == other.runtimeType &&
|
||||||
|
typeId == other.typeId;
|
||||||
|
}
|
||||||
1925
lib/data/models/search_result_more_model.dart
Normal file
52
lib/data/models/search_result_tabbar_model.dart
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
// Author: fengshengxiong
|
||||||
|
// Date: 2024/7/28
|
||||||
|
// Description: 搜索结果TabBar模型
|
||||||
|
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:tone_snap/data/models/music_model.dart';
|
||||||
|
|
||||||
|
class SearchResultTabBarModel {
|
||||||
|
String? title;
|
||||||
|
String? params;
|
||||||
|
String? uniqueId;
|
||||||
|
List<MusicModel>? musicList;
|
||||||
|
|
||||||
|
SearchResultTabBarModel({
|
||||||
|
this.title,
|
||||||
|
this.params,
|
||||||
|
this.uniqueId,
|
||||||
|
this.musicList,
|
||||||
|
});
|
||||||
|
|
||||||
|
SearchResultTabBarModel copyWith({
|
||||||
|
String? title,
|
||||||
|
String? params,
|
||||||
|
String? uniqueId,
|
||||||
|
List<MusicModel>? musicList,
|
||||||
|
}) =>
|
||||||
|
SearchResultTabBarModel(
|
||||||
|
title: title ?? this.title,
|
||||||
|
params: params ?? this.params,
|
||||||
|
uniqueId: uniqueId ?? this.uniqueId,
|
||||||
|
musicList: musicList ?? this.musicList,
|
||||||
|
);
|
||||||
|
|
||||||
|
factory SearchResultTabBarModel.fromJson(String str) => SearchResultTabBarModel.fromMap(json.decode(str));
|
||||||
|
|
||||||
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
|
factory SearchResultTabBarModel.fromMap(Map<String, dynamic> json) => SearchResultTabBarModel(
|
||||||
|
title: json["title"],
|
||||||
|
params: json["params"],
|
||||||
|
uniqueId: json["uniqueId"],
|
||||||
|
musicList: json["musicList"] == null ? <MusicModel>[] : List<MusicModel>.from(json["musicList"]!.map((x) => MusicModel.fromMap(x))),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() => {
|
||||||
|
"title": title,
|
||||||
|
"params": params,
|
||||||
|
"uniqueId": uniqueId,
|
||||||
|
"musicList": musicList == null ? [] : List<dynamic>.from(musicList!.map((x) => x.toMap())),
|
||||||
|
};
|
||||||
|
}
|
||||||
305
lib/data/models/search_suggestions_model.dart
Normal file
@ -0,0 +1,305 @@
|
|||||||
|
// Author: fengshengxiong
|
||||||
|
// Date: 2024/7/28
|
||||||
|
// Description: 搜索建议模型
|
||||||
|
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
class SearchSuggestionsModel {
|
||||||
|
ResponseContext? responseContext;
|
||||||
|
List<SearchSuggestionsModelContent>? contents;
|
||||||
|
String? trackingParams;
|
||||||
|
|
||||||
|
SearchSuggestionsModel({
|
||||||
|
this.responseContext,
|
||||||
|
this.contents,
|
||||||
|
this.trackingParams,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory SearchSuggestionsModel.fromJson(String str) => SearchSuggestionsModel.fromMap(json.decode(str));
|
||||||
|
|
||||||
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
|
factory SearchSuggestionsModel.fromMap(Map<String, dynamic> json) => SearchSuggestionsModel(
|
||||||
|
responseContext: json["responseContext"] == null ? null : ResponseContext.fromMap(json["responseContext"]),
|
||||||
|
contents: json["contents"] == null ? [] : List<SearchSuggestionsModelContent>.from(json["contents"]!.map((x) => SearchSuggestionsModelContent.fromMap(x))),
|
||||||
|
trackingParams: json["trackingParams"],
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() => {
|
||||||
|
"responseContext": responseContext?.toMap(),
|
||||||
|
"contents": contents == null ? [] : List<dynamic>.from(contents!.map((x) => x.toMap())),
|
||||||
|
"trackingParams": trackingParams,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class SearchSuggestionsModelContent {
|
||||||
|
SearchSuggestionsSectionRenderer? searchSuggestionsSectionRenderer;
|
||||||
|
|
||||||
|
SearchSuggestionsModelContent({
|
||||||
|
this.searchSuggestionsSectionRenderer,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory SearchSuggestionsModelContent.fromJson(String str) => SearchSuggestionsModelContent.fromMap(json.decode(str));
|
||||||
|
|
||||||
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
|
factory SearchSuggestionsModelContent.fromMap(Map<String, dynamic> json) => SearchSuggestionsModelContent(
|
||||||
|
searchSuggestionsSectionRenderer: json["searchSuggestionsSectionRenderer"] == null ? null : SearchSuggestionsSectionRenderer.fromMap(json["searchSuggestionsSectionRenderer"]),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() => {
|
||||||
|
"searchSuggestionsSectionRenderer": searchSuggestionsSectionRenderer?.toMap(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class SearchSuggestionsSectionRenderer {
|
||||||
|
List<SearchSuggestionsSectionRendererContent>? contents;
|
||||||
|
|
||||||
|
SearchSuggestionsSectionRenderer({
|
||||||
|
this.contents,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory SearchSuggestionsSectionRenderer.fromJson(String str) => SearchSuggestionsSectionRenderer.fromMap(json.decode(str));
|
||||||
|
|
||||||
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
|
factory SearchSuggestionsSectionRenderer.fromMap(Map<String, dynamic> json) => SearchSuggestionsSectionRenderer(
|
||||||
|
contents: json["contents"] == null ? [] : List<SearchSuggestionsSectionRendererContent>.from(json["contents"]!.map((x) => SearchSuggestionsSectionRendererContent.fromMap(x))),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() => {
|
||||||
|
"contents": contents == null ? [] : List<dynamic>.from(contents!.map((x) => x.toMap())),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class SearchSuggestionsSectionRendererContent {
|
||||||
|
SearchSuggestionRenderer? searchSuggestionRenderer;
|
||||||
|
|
||||||
|
SearchSuggestionsSectionRendererContent({
|
||||||
|
this.searchSuggestionRenderer,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory SearchSuggestionsSectionRendererContent.fromJson(String str) => SearchSuggestionsSectionRendererContent.fromMap(json.decode(str));
|
||||||
|
|
||||||
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
|
factory SearchSuggestionsSectionRendererContent.fromMap(Map<String, dynamic> json) => SearchSuggestionsSectionRendererContent(
|
||||||
|
searchSuggestionRenderer: json["searchSuggestionRenderer"] == null ? null : SearchSuggestionRenderer.fromMap(json["searchSuggestionRenderer"]),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() => {
|
||||||
|
"searchSuggestionRenderer": searchSuggestionRenderer?.toMap(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class SearchSuggestionRenderer {
|
||||||
|
Suggestion? suggestion;
|
||||||
|
NavigationEndpoint? navigationEndpoint;
|
||||||
|
String? trackingParams;
|
||||||
|
Icon? icon;
|
||||||
|
|
||||||
|
SearchSuggestionRenderer({
|
||||||
|
this.suggestion,
|
||||||
|
this.navigationEndpoint,
|
||||||
|
this.trackingParams,
|
||||||
|
this.icon,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory SearchSuggestionRenderer.fromJson(String str) => SearchSuggestionRenderer.fromMap(json.decode(str));
|
||||||
|
|
||||||
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
|
factory SearchSuggestionRenderer.fromMap(Map<String, dynamic> json) => SearchSuggestionRenderer(
|
||||||
|
suggestion: json["suggestion"] == null ? null : Suggestion.fromMap(json["suggestion"]),
|
||||||
|
navigationEndpoint: json["navigationEndpoint"] == null ? null : NavigationEndpoint.fromMap(json["navigationEndpoint"]),
|
||||||
|
trackingParams: json["trackingParams"],
|
||||||
|
icon: json["icon"] == null ? null : Icon.fromMap(json["icon"]),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() => {
|
||||||
|
"suggestion": suggestion?.toMap(),
|
||||||
|
"navigationEndpoint": navigationEndpoint?.toMap(),
|
||||||
|
"trackingParams": trackingParams,
|
||||||
|
"icon": icon?.toMap(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class Icon {
|
||||||
|
String? iconType;
|
||||||
|
|
||||||
|
Icon({
|
||||||
|
this.iconType,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory Icon.fromJson(String str) => Icon.fromMap(json.decode(str));
|
||||||
|
|
||||||
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
|
factory Icon.fromMap(Map<String, dynamic> json) => Icon(
|
||||||
|
iconType: json["iconType"],
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() => {
|
||||||
|
"iconType": iconType,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class NavigationEndpoint {
|
||||||
|
String? clickTrackingParams;
|
||||||
|
SearchEndpoint? searchEndpoint;
|
||||||
|
|
||||||
|
NavigationEndpoint({
|
||||||
|
this.clickTrackingParams,
|
||||||
|
this.searchEndpoint,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory NavigationEndpoint.fromJson(String str) => NavigationEndpoint.fromMap(json.decode(str));
|
||||||
|
|
||||||
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
|
factory NavigationEndpoint.fromMap(Map<String, dynamic> json) => NavigationEndpoint(
|
||||||
|
clickTrackingParams: json["clickTrackingParams"],
|
||||||
|
searchEndpoint: json["searchEndpoint"] == null ? null : SearchEndpoint.fromMap(json["searchEndpoint"]),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() => {
|
||||||
|
"clickTrackingParams": clickTrackingParams,
|
||||||
|
"searchEndpoint": searchEndpoint?.toMap(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class SearchEndpoint {
|
||||||
|
String? query;
|
||||||
|
|
||||||
|
SearchEndpoint({
|
||||||
|
this.query,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory SearchEndpoint.fromJson(String str) => SearchEndpoint.fromMap(json.decode(str));
|
||||||
|
|
||||||
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
|
factory SearchEndpoint.fromMap(Map<String, dynamic> json) => SearchEndpoint(
|
||||||
|
query: json["query"],
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() => {
|
||||||
|
"query": query,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class Suggestion {
|
||||||
|
List<Run>? runs;
|
||||||
|
|
||||||
|
Suggestion({
|
||||||
|
this.runs,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory Suggestion.fromJson(String str) => Suggestion.fromMap(json.decode(str));
|
||||||
|
|
||||||
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
|
factory Suggestion.fromMap(Map<String, dynamic> json) => Suggestion(
|
||||||
|
runs: json["runs"] == null ? [] : List<Run>.from(json["runs"]!.map((x) => Run.fromMap(x))),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() => {
|
||||||
|
"runs": runs == null ? [] : List<dynamic>.from(runs!.map((x) => x.toMap())),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class Run {
|
||||||
|
String? text;
|
||||||
|
bool? bold;
|
||||||
|
|
||||||
|
Run({
|
||||||
|
this.text,
|
||||||
|
this.bold,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory Run.fromJson(String str) => Run.fromMap(json.decode(str));
|
||||||
|
|
||||||
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
|
factory Run.fromMap(Map<String, dynamic> json) => Run(
|
||||||
|
text: json["text"],
|
||||||
|
bold: json["bold"],
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() => {
|
||||||
|
"text": text,
|
||||||
|
"bold": bold,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class ResponseContext {
|
||||||
|
String? visitorData;
|
||||||
|
List<ServiceTrackingParam>? serviceTrackingParams;
|
||||||
|
|
||||||
|
ResponseContext({
|
||||||
|
this.visitorData,
|
||||||
|
this.serviceTrackingParams,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory ResponseContext.fromJson(String str) => ResponseContext.fromMap(json.decode(str));
|
||||||
|
|
||||||
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
|
factory ResponseContext.fromMap(Map<String, dynamic> json) => ResponseContext(
|
||||||
|
visitorData: json["visitorData"],
|
||||||
|
serviceTrackingParams: json["serviceTrackingParams"] == null ? [] : List<ServiceTrackingParam>.from(json["serviceTrackingParams"]!.map((x) => ServiceTrackingParam.fromMap(x))),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() => {
|
||||||
|
"visitorData": visitorData,
|
||||||
|
"serviceTrackingParams": serviceTrackingParams == null ? [] : List<dynamic>.from(serviceTrackingParams!.map((x) => x.toMap())),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class ServiceTrackingParam {
|
||||||
|
String? service;
|
||||||
|
List<Param>? params;
|
||||||
|
|
||||||
|
ServiceTrackingParam({
|
||||||
|
this.service,
|
||||||
|
this.params,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory ServiceTrackingParam.fromJson(String str) => ServiceTrackingParam.fromMap(json.decode(str));
|
||||||
|
|
||||||
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
|
factory ServiceTrackingParam.fromMap(Map<String, dynamic> json) => ServiceTrackingParam(
|
||||||
|
service: json["service"],
|
||||||
|
params: json["params"] == null ? [] : List<Param>.from(json["params"]!.map((x) => Param.fromMap(x))),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() => {
|
||||||
|
"service": service,
|
||||||
|
"params": params == null ? [] : List<dynamic>.from(params!.map((x) => x.toMap())),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class Param {
|
||||||
|
String? key;
|
||||||
|
String? value;
|
||||||
|
|
||||||
|
Param({
|
||||||
|
this.key,
|
||||||
|
this.value,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory Param.fromJson(String str) => Param.fromMap(json.decode(str));
|
||||||
|
|
||||||
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
|
factory Param.fromMap(Map<String, dynamic> json) => Param(
|
||||||
|
key: json["key"],
|
||||||
|
value: json["value"],
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() => {
|
||||||
|
"key": key,
|
||||||
|
"value": value,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -44,8 +44,8 @@ class DioClient {
|
|||||||
_dio = Dio();
|
_dio = Dio();
|
||||||
final baseOptions = BaseOptions(
|
final baseOptions = BaseOptions(
|
||||||
baseUrl: MusicApi.baseUrl,
|
baseUrl: MusicApi.baseUrl,
|
||||||
connectTimeout: const Duration(seconds: 15),
|
connectTimeout: const Duration(seconds: 30),
|
||||||
receiveTimeout: const Duration(seconds: 15),
|
receiveTimeout: const Duration(seconds: 30),
|
||||||
);
|
);
|
||||||
_dio.options = baseOptions;
|
_dio.options = baseOptions;
|
||||||
_dio.interceptors.add(DioInterceptor());
|
_dio.interceptors.add(DioInterceptor());
|
||||||
@ -103,6 +103,7 @@ class DioClient {
|
|||||||
Future<void> download(
|
Future<void> download(
|
||||||
String urlPath,
|
String urlPath,
|
||||||
dynamic savePath, {
|
dynamic savePath, {
|
||||||
|
bool showToast = false,
|
||||||
ProgressCallback? onReceiveProgress,
|
ProgressCallback? onReceiveProgress,
|
||||||
Map<String, dynamic>? queryParameters,
|
Map<String, dynamic>? queryParameters,
|
||||||
CancelToken? cancelToken,
|
CancelToken? cancelToken,
|
||||||
@ -125,7 +126,7 @@ class DioClient {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
BaseError baseError = getError(e);
|
BaseError baseError = getError(e);
|
||||||
if (fail != null) fail(baseError);
|
if (fail != null) fail(baseError);
|
||||||
BaseEasyLoading.toast(baseError.message);
|
BaseEasyLoading.toast(baseError.message, show: showToast);
|
||||||
LogUtil.e(baseError.message);
|
LogUtil.e(baseError.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -134,19 +135,19 @@ class DioClient {
|
|||||||
if (e.runtimeType == DioException) {
|
if (e.runtimeType == DioException) {
|
||||||
switch ((e as DioException).type) {
|
switch ((e as DioException).type) {
|
||||||
case DioExceptionType.connectionTimeout:
|
case DioExceptionType.connectionTimeout:
|
||||||
return OtherError(statusCode: -1, statusMessage: 'connection timed out');
|
return OtherError(statusCode: DioExceptionType.connectionTimeout.index, statusMessage: 'Connection timed out');
|
||||||
case DioExceptionType.sendTimeout:
|
case DioExceptionType.sendTimeout:
|
||||||
return OtherError(statusCode: -1, statusMessage: 'send timeout');
|
return OtherError(statusCode: DioExceptionType.sendTimeout.index, statusMessage: 'Send timeout');
|
||||||
case DioExceptionType.receiveTimeout:
|
case DioExceptionType.receiveTimeout:
|
||||||
return OtherError(statusCode: -1, statusMessage: 'receive timeout');
|
return OtherError(statusCode: DioExceptionType.receiveTimeout.index, statusMessage: 'Receive timeout');
|
||||||
case DioExceptionType.badCertificate:
|
case DioExceptionType.badCertificate:
|
||||||
return OtherError(statusCode: -1, statusMessage: 'certificate error');
|
return OtherError(statusCode: DioExceptionType.badCertificate.index, statusMessage: 'Certificate error');
|
||||||
case DioExceptionType.cancel:
|
case DioExceptionType.cancel:
|
||||||
return OtherError(statusCode: -1, statusMessage: 'request canceled');
|
return OtherError(statusCode: DioExceptionType.cancel.index, statusMessage: 'Request canceled');
|
||||||
case DioExceptionType.connectionError:
|
case DioExceptionType.connectionError:
|
||||||
return OtherError(statusCode: -1, statusMessage: 'connection error');
|
return OtherError(statusCode: DioExceptionType.connectionError.index, statusMessage: 'Connection error');
|
||||||
case DioExceptionType.unknown:
|
case DioExceptionType.unknown:
|
||||||
return OtherError(statusCode: -1, statusMessage: 'unknown error');
|
return OtherError(statusCode: DioExceptionType.unknown.index, statusMessage: 'Unknown error');
|
||||||
case DioExceptionType.badResponse:
|
case DioExceptionType.badResponse:
|
||||||
final response = e.response;
|
final response = e.response;
|
||||||
if (response!.statusCode == 401) {
|
if (response!.statusCode == 401) {
|
||||||
@ -161,7 +162,7 @@ class DioClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return OtherError(statusCode: -1, statusMessage: 'unknown error');
|
return OtherError(statusCode: -1, statusMessage: 'Unknown error');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
59
lib/data/storage/collect_playlists_box.dart
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
// Author: fengshengxiong
|
||||||
|
// Date: 2024/5/8
|
||||||
|
// Description: 收藏播放列表盒子
|
||||||
|
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:tone_snap/data/models/playlist_model.dart';
|
||||||
|
import 'package:tone_snap/data/storage/hive_storage.dart';
|
||||||
|
|
||||||
|
class CollectPlaylistsBox {
|
||||||
|
CollectPlaylistsBox._();
|
||||||
|
|
||||||
|
static final CollectPlaylistsBox _instance = CollectPlaylistsBox._();
|
||||||
|
|
||||||
|
factory CollectPlaylistsBox() {
|
||||||
|
return _instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 声明盒子
|
||||||
|
/// 注意, main函数中这个盒子已经打开, 可以进行存储操作
|
||||||
|
final _box = Hive.box<PlaylistModel>(collectPlaylistsBox);
|
||||||
|
|
||||||
|
/// 获取列表
|
||||||
|
List<PlaylistModel> getList() {
|
||||||
|
return _box.values.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 获取倒序列表
|
||||||
|
List<PlaylistModel> getReversedList() {
|
||||||
|
return _box.values.toList().reversed.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 添加数据
|
||||||
|
Future<int> add({required String id, required String title, String? params, String? coverUrl, String? subtitle}) async {
|
||||||
|
return await _box.add(PlaylistModel(
|
||||||
|
id: id,
|
||||||
|
title: title,
|
||||||
|
params: params,
|
||||||
|
coverUrl: coverUrl,
|
||||||
|
subtitle: subtitle,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 删除
|
||||||
|
Future<void> delete(String id) async {
|
||||||
|
var list = getList();
|
||||||
|
var model = list.firstWhereOrNull((e) => e.id == id);
|
||||||
|
if (model != null) {
|
||||||
|
await _box.deleteAt(list.indexOf(model));
|
||||||
|
await _box.flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 清空所有数据
|
||||||
|
Future<void> clear() async {
|
||||||
|
await _box.clear();
|
||||||
|
await _box.flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,7 +1,8 @@
|
|||||||
// Author: fengshengxiong
|
// Author: fengshengxiong
|
||||||
// Date: 2024/5/8
|
// Date: 2024/5/8
|
||||||
// Description: 收藏数据
|
// Description: 收藏音频盒子
|
||||||
|
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
import 'package:tone_snap/data/models/voice_model.dart';
|
import 'package:tone_snap/data/models/voice_model.dart';
|
||||||
import 'package:tone_snap/data/storage/hive_storage.dart';
|
import 'package:tone_snap/data/storage/hive_storage.dart';
|
||||||
|
|
||||||
@ -19,7 +20,7 @@ class FavoriteBox {
|
|||||||
|
|
||||||
/// 声明盒子
|
/// 声明盒子
|
||||||
/// 注意, main函数中这个盒子已经打开, 可以进行存储操作
|
/// 注意, main函数中这个盒子已经打开, 可以进行存储操作
|
||||||
final _box = getFavoriteBox();
|
final _box = Hive.box<VoiceModel>(favoriteBox);
|
||||||
|
|
||||||
/// 获取数据
|
/// 获取数据
|
||||||
List<VoiceModel> getList() {
|
List<VoiceModel> getList() {
|
||||||
|
|||||||
@ -5,12 +5,16 @@
|
|||||||
import 'package:hive_flutter/hive_flutter.dart';
|
import 'package:hive_flutter/hive_flutter.dart';
|
||||||
import 'package:tone_snap/data/enum/play_mode.dart';
|
import 'package:tone_snap/data/enum/play_mode.dart';
|
||||||
import 'package:tone_snap/data/models/music_model.dart';
|
import 'package:tone_snap/data/models/music_model.dart';
|
||||||
|
import 'package:tone_snap/data/models/playlist_model.dart';
|
||||||
import 'package:tone_snap/data/models/voice_model.dart';
|
import 'package:tone_snap/data/models/voice_model.dart';
|
||||||
|
|
||||||
const myVoiceBox = 'myVoiceBox';
|
const myVoiceBox = 'myVoiceBox';
|
||||||
const favoriteBox = 'favoriteBox';
|
const favoriteBox = 'favoriteBox';
|
||||||
const musicBox = 'musicBox';
|
const musicBox = 'musicBox';
|
||||||
const loveSongsBox = 'loveSongsBox';
|
const loveSongsBox = 'loveSongsBox';
|
||||||
|
const offlineBox = 'offlineBox';
|
||||||
|
const playlistsBox = 'playlistsBox';
|
||||||
|
const collectPlaylistsBox = 'collectPlaylistsBox';
|
||||||
|
|
||||||
Future initHive() async {
|
Future initHive() async {
|
||||||
// 初始化
|
// 初始化
|
||||||
@ -19,25 +23,14 @@ Future initHive() async {
|
|||||||
Hive.registerAdapter(VoiceModelAdapter());
|
Hive.registerAdapter(VoiceModelAdapter());
|
||||||
Hive.registerAdapter(PlayModeAdapter());
|
Hive.registerAdapter(PlayModeAdapter());
|
||||||
Hive.registerAdapter(MusicModelAdapter());
|
Hive.registerAdapter(MusicModelAdapter());
|
||||||
|
Hive.registerAdapter(PlaylistModelAdapter());
|
||||||
|
|
||||||
// 打开盒子
|
// 打开盒子
|
||||||
await Hive.openBox<VoiceModel>(myVoiceBox);
|
await Hive.openBox<VoiceModel>(myVoiceBox);
|
||||||
await Hive.openBox<VoiceModel>(favoriteBox);
|
await Hive.openBox<VoiceModel>(favoriteBox);
|
||||||
await Hive.openBox(musicBox);
|
await Hive.openBox(musicBox);
|
||||||
await Hive.openBox<MusicModel>(loveSongsBox);
|
await Hive.openBox<MusicModel>(loveSongsBox);
|
||||||
}
|
await Hive.openBox<MusicModel>(offlineBox);
|
||||||
|
await Hive.openBox<PlaylistModel>(playlistsBox);
|
||||||
Box<VoiceModel> getMyVoiceBox() {
|
await Hive.openBox<PlaylistModel>(collectPlaylistsBox);
|
||||||
return Hive.box<VoiceModel>(myVoiceBox);
|
|
||||||
}
|
|
||||||
|
|
||||||
Box<VoiceModel> getFavoriteBox() {
|
|
||||||
return Hive.box<VoiceModel>(favoriteBox);
|
|
||||||
}
|
|
||||||
|
|
||||||
Box getMusicBox() {
|
|
||||||
return Hive.box(musicBox);
|
|
||||||
}
|
|
||||||
|
|
||||||
Box<MusicModel> getLoveSongsBox() {
|
|
||||||
return Hive.box<MusicModel>(loveSongsBox);
|
|
||||||
}
|
}
|
||||||
@ -1,8 +1,9 @@
|
|||||||
// Author: fengshengxiong
|
// Author: fengshengxiong
|
||||||
// Date: 2024/5/8
|
// Date: 2024/5/8
|
||||||
// Description: 喜欢的歌曲
|
// Description: 收藏歌曲盒子
|
||||||
|
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
import 'package:tone_snap/data/models/music_model.dart';
|
import 'package:tone_snap/data/models/music_model.dart';
|
||||||
import 'package:tone_snap/data/storage/hive_storage.dart';
|
import 'package:tone_snap/data/storage/hive_storage.dart';
|
||||||
|
|
||||||
@ -17,22 +18,21 @@ class LoveSongsBox {
|
|||||||
|
|
||||||
/// 声明盒子
|
/// 声明盒子
|
||||||
/// 注意, main函数中这个盒子已经打开, 可以进行存储操作
|
/// 注意, main函数中这个盒子已经打开, 可以进行存储操作
|
||||||
final _box = getLoveSongsBox();
|
final _box = Hive.box<MusicModel>(loveSongsBox);
|
||||||
|
|
||||||
/// 获取数据
|
/// 获取列表
|
||||||
List<MusicModel> getList() {
|
List<MusicModel> getList() {
|
||||||
return _box.values.toList();
|
return _box.values.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 添加数据
|
/// 获取倒序列表
|
||||||
Future<int> addData(MusicModel model) async {
|
List<MusicModel> getReversedList() {
|
||||||
return await _box.add(model);
|
return _box.values.toList().reversed.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 校验该歌曲是否加入喜欢
|
/// 添加数据
|
||||||
bool isLove(String videoId) {
|
Future<int> add(MusicModel model) async {
|
||||||
var model = getList().firstWhereOrNull((e) => e.videoId == videoId);
|
return await _box.add(model);
|
||||||
return model != null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 删除
|
/// 删除
|
||||||
@ -40,7 +40,7 @@ class LoveSongsBox {
|
|||||||
var list = getList();
|
var list = getList();
|
||||||
var model = list.firstWhereOrNull((e) => e.videoId == videoId);
|
var model = list.firstWhereOrNull((e) => e.videoId == videoId);
|
||||||
if (model != null) {
|
if (model != null) {
|
||||||
await _box.delete(list.indexOf(model));
|
await _box.deleteAt(list.indexOf(model));
|
||||||
await _box.flush();
|
await _box.flush();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -50,4 +50,18 @@ class LoveSongsBox {
|
|||||||
await _box.clear();
|
await _box.clear();
|
||||||
await _box.flush();
|
await _box.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 获取第一条的封面
|
||||||
|
String? getFirstCoverUrl() {
|
||||||
|
if (getReversedList().isNotEmpty) {
|
||||||
|
return getReversedList().first.coverUrl;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 校验该歌曲是否收藏
|
||||||
|
bool checkLove(String? videoId) {
|
||||||
|
var model = getList().firstWhereOrNull((e) => e.videoId == videoId);
|
||||||
|
return model != null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,16 @@
|
|||||||
// Author: fengshengxiong
|
// Author: fengshengxiong
|
||||||
// Date: 2024/5/8
|
// Date: 2024/5/8
|
||||||
// Description: 音乐数据盒子
|
// Description: 音乐非结构化数据盒子
|
||||||
|
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
import 'package:tone_snap/data/enum/play_mode.dart';
|
import 'package:tone_snap/data/enum/play_mode.dart';
|
||||||
import 'package:tone_snap/data/storage/hive_storage.dart';
|
import 'package:tone_snap/data/storage/hive_storage.dart';
|
||||||
|
import 'package:tone_snap/global/app_config.dart';
|
||||||
|
import 'package:tone_snap/utils/log_util.dart';
|
||||||
|
import 'package:tone_snap/utils/obj_util.dart';
|
||||||
|
|
||||||
class MusicBox {
|
class MusicBox {
|
||||||
MusicBox._();
|
MusicBox._();
|
||||||
@ -14,17 +21,159 @@ class MusicBox {
|
|||||||
return _instance;
|
return _instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// RemoteConfig openStatus
|
||||||
|
final _openStatusKey = 'openStatusKey';
|
||||||
|
|
||||||
|
/// RemoteConfig dataVersion
|
||||||
|
final _dataVersionKey = 'dataVersionKey';
|
||||||
|
|
||||||
|
/// 是否进入过B面
|
||||||
|
final _isOpenedSideBKey = 'isOpenedSideBKey';
|
||||||
|
|
||||||
|
/// 开屏启动插页事件间隔时长
|
||||||
|
final _openAppEventDurationKey = 'openAppEventDurationKey';
|
||||||
|
|
||||||
|
/// 插页广告事件间隔时长
|
||||||
|
final _interstitialEventDurationKey = 'interstitialEventDurationKey';
|
||||||
|
|
||||||
|
/// 播放模式
|
||||||
|
final _playModeKey = 'playModeKey';
|
||||||
|
|
||||||
|
/// 搜索历史
|
||||||
|
final _searchHistoryKey = 'searchHistoryKey';
|
||||||
|
|
||||||
/// 声明盒子
|
/// 声明盒子
|
||||||
/// 注意, main函数中这个盒子已经打开, 可以进行存储操作
|
/// 注意, main函数中这个盒子已经打开, 可以进行存储操作
|
||||||
final _box = getMusicBox();
|
final _box = Hive.box(musicBox);
|
||||||
|
|
||||||
|
/// 设置 openStatus
|
||||||
|
Future<void> putOpenStatus(String openStatus) async {
|
||||||
|
return await _box.put(_openStatusKey, openStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 获取远程版本
|
||||||
|
Future<String> getVersionCode() async {
|
||||||
|
String? openStatus = _box.get(_openStatusKey);
|
||||||
|
String? versionCode;
|
||||||
|
if (ObjUtil.isNotEmpty(openStatus)) {
|
||||||
|
try {
|
||||||
|
versionCode = jsonDecode(openStatus!)['versionCode'];
|
||||||
|
} catch (e) {
|
||||||
|
LogUtil.e(e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final packageInfo = await PackageInfo.fromPlatform();
|
||||||
|
return ObjUtil.isEmpty(versionCode) ? packageInfo.version : versionCode!;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 获取开关
|
||||||
|
bool getEnter() {
|
||||||
|
String? openStatus = _box.get(_openStatusKey);
|
||||||
|
if (ObjUtil.isNotEmpty(openStatus)) {
|
||||||
|
try {
|
||||||
|
return jsonDecode(openStatus!)['enter'] ?? false;
|
||||||
|
} catch (e) {
|
||||||
|
LogUtil.e(e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 设置 dataVersion
|
||||||
|
Future<void> putDataVersion(String dataVersion) async {
|
||||||
|
return await _box.put(_dataVersionKey, dataVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 获取 ClientVersion
|
||||||
|
String getClientVersion() {
|
||||||
|
String? dataVersion = _box.get(_dataVersionKey);
|
||||||
|
if (ObjUtil.isNotEmpty(dataVersion)) {
|
||||||
|
try {
|
||||||
|
if (ObjUtil.isNotEmpty(jsonDecode(dataVersion!)['ClientVersion'])) {
|
||||||
|
return jsonDecode(dataVersion)['ClientVersion'];
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
LogUtil.e(e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return AppConfig.clientVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 获取 PlayerVersion
|
||||||
|
String getPlayerVersion() {
|
||||||
|
String? dataVersion = _box.get(_dataVersionKey);
|
||||||
|
if (ObjUtil.isNotEmpty(dataVersion)) {
|
||||||
|
try {
|
||||||
|
if (ObjUtil.isNotEmpty(jsonDecode(dataVersion!)['PlayerVersion'])) {
|
||||||
|
return jsonDecode(dataVersion)['PlayerVersion'];
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
LogUtil.e(e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return AppConfig.playerVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 设置是否打开过B面
|
||||||
|
Future<void> putIsOpenedSideB(bool isOpen) async {
|
||||||
|
return await _box.put(_isOpenedSideBKey, isOpen);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 获取是否打开过B面
|
||||||
|
bool getIsOpenedSideB() {
|
||||||
|
return _box.get(_isOpenedSideBKey, defaultValue: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 设置开屏启动插页事件间隔时长
|
||||||
|
Future<void> putOpenAppEventDuration(int time) async {
|
||||||
|
return await _box.put(_openAppEventDurationKey, time);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 获取开屏启动插页事件间隔时长
|
||||||
|
int getOpenAppEventDuration() {
|
||||||
|
return _box.get(_openAppEventDurationKey, defaultValue: AppConfig.openAppEventDurationTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 设置插页广告事件间隔时长
|
||||||
|
Future<void> putInterstitialEventDuration(int time) async {
|
||||||
|
return await _box.put(_interstitialEventDurationKey, time);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 获取插页广告事件间隔时长
|
||||||
|
int getInterstitialEventDuration() {
|
||||||
|
return _box.get(_interstitialEventDurationKey, defaultValue: AppConfig.interstitialEventDuration);
|
||||||
|
}
|
||||||
|
|
||||||
/// 设置播放模式
|
/// 设置播放模式
|
||||||
Future<void> putPlayMode(PlayMode playMode) {
|
Future<void> putPlayMode(PlayMode playMode) async {
|
||||||
return _box.put('play_mode', playMode);
|
return await _box.put(_playModeKey, playMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 获取播放模式
|
/// 获取播放模式
|
||||||
PlayMode getPlayMode() {
|
PlayMode getPlayMode() {
|
||||||
return _box.get('play_mode') ?? PlayMode.listLoop;
|
return _box.get(_playModeKey) ?? PlayMode.listLoop;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 添加搜索历史
|
||||||
|
Future<void> putSearchHistory(String history) async {
|
||||||
|
var historyList = getAllSearchHistory();
|
||||||
|
if (!historyList.contains(history)) {
|
||||||
|
if (historyList.length >= 9) {
|
||||||
|
historyList.removeLast();
|
||||||
|
}
|
||||||
|
historyList.insert(0, history);
|
||||||
|
await _box.put(_searchHistoryKey, historyList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 获取所有搜索历史
|
||||||
|
List<String> getAllSearchHistory() {
|
||||||
|
return _box.get(_searchHistoryKey, defaultValue: <String>[]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 删除所有搜索历史
|
||||||
|
Future<void> deleteAllSearchHistory() async {
|
||||||
|
await _box.delete(_searchHistoryKey);
|
||||||
|
await _box.flush();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
// Author: fengshengxiong
|
// Author: fengshengxiong
|
||||||
// Date: 2024/5/8
|
// Date: 2024/5/8
|
||||||
// Description: 我的音频
|
// Description: 我的音频盒子
|
||||||
|
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
import 'package:tone_snap/data/models/voice_model.dart';
|
import 'package:tone_snap/data/models/voice_model.dart';
|
||||||
import 'package:tone_snap/data/storage/hive_storage.dart';
|
import 'package:tone_snap/data/storage/hive_storage.dart';
|
||||||
|
|
||||||
@ -19,7 +20,7 @@ class MyVoiceBox {
|
|||||||
|
|
||||||
/// 声明盒子
|
/// 声明盒子
|
||||||
/// 注意, main函数中这个盒子已经打开, 可以进行存储操作
|
/// 注意, main函数中这个盒子已经打开, 可以进行存储操作
|
||||||
final _box = getMyVoiceBox();
|
final _box = Hive.box<VoiceModel>(myVoiceBox);
|
||||||
|
|
||||||
/// 获取数据
|
/// 获取数据
|
||||||
List<VoiceModel> getList() {
|
List<VoiceModel> getList() {
|
||||||
|
|||||||
67
lib/data/storage/offline_box.dart
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
// Author: fengshengxiong
|
||||||
|
// Date: 2024/5/8
|
||||||
|
// Description: 下载歌曲盒子
|
||||||
|
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:tone_snap/data/models/music_model.dart';
|
||||||
|
import 'package:tone_snap/data/storage/hive_storage.dart';
|
||||||
|
|
||||||
|
class OfflineBox {
|
||||||
|
OfflineBox._();
|
||||||
|
|
||||||
|
static final OfflineBox _instance = OfflineBox._();
|
||||||
|
|
||||||
|
factory OfflineBox() {
|
||||||
|
return _instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 声明盒子
|
||||||
|
/// 注意, main函数中这个盒子已经打开, 可以进行存储操作
|
||||||
|
final _box = Hive.box<MusicModel>(offlineBox);
|
||||||
|
|
||||||
|
/// 获取列表
|
||||||
|
List<MusicModel> getList() {
|
||||||
|
return _box.values.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 获取倒序列表
|
||||||
|
List<MusicModel> getReversedList() {
|
||||||
|
return _box.values.toList().reversed.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 添加数据
|
||||||
|
Future<int> add(MusicModel model) async {
|
||||||
|
return await _box.add(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 删除
|
||||||
|
Future<void> delete(String videoId) async {
|
||||||
|
var list = getList();
|
||||||
|
var model = list.firstWhereOrNull((e) => e.videoId == videoId);
|
||||||
|
if (model != null) {
|
||||||
|
await _box.deleteAt(list.indexOf(model));
|
||||||
|
await _box.flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 清空所有数据
|
||||||
|
Future<void> clear() async {
|
||||||
|
await _box.clear();
|
||||||
|
await _box.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 获取第一条的封面
|
||||||
|
String? getFirstCoverUrl() {
|
||||||
|
if (getReversedList().isNotEmpty) {
|
||||||
|
return getReversedList().first.coverUrl;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 校验是否已下载
|
||||||
|
bool checkDownloaded(String? videoId) {
|
||||||
|
var model = getList().firstWhereOrNull((e) => e.videoId == videoId);
|
||||||
|
return model != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
117
lib/data/storage/playlists_box.dart
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
// Author: fengshengxiong
|
||||||
|
// Date: 2024/5/8
|
||||||
|
// Description: 自定义播放列表盒子
|
||||||
|
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:tone_snap/components/base_easyloading.dart';
|
||||||
|
import 'package:tone_snap/data/models/music_model.dart';
|
||||||
|
import 'package:tone_snap/data/models/playlist_model.dart';
|
||||||
|
import 'package:tone_snap/data/storage/hive_storage.dart';
|
||||||
|
import 'package:tone_snap/utils/date_util.dart';
|
||||||
|
|
||||||
|
class PlaylistsBox {
|
||||||
|
PlaylistsBox._();
|
||||||
|
|
||||||
|
static final PlaylistsBox _instance = PlaylistsBox._();
|
||||||
|
|
||||||
|
factory PlaylistsBox() {
|
||||||
|
return _instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 声明盒子
|
||||||
|
/// 注意, main函数中这个盒子已经打开, 可以进行存储操作
|
||||||
|
final _box = Hive.box<PlaylistModel>(playlistsBox);
|
||||||
|
|
||||||
|
/// 获取列表
|
||||||
|
List<PlaylistModel> getList() {
|
||||||
|
return _box.values.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 获取倒序列表
|
||||||
|
List<PlaylistModel> getReversedList() {
|
||||||
|
return _box.values.toList().reversed.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 添加数据
|
||||||
|
Future<void> add(String title) async {
|
||||||
|
final milliseconds = DateUtil.getNowTimestamp();
|
||||||
|
final id = '$title-$milliseconds';
|
||||||
|
await _box.put(id, PlaylistModel(
|
||||||
|
id: id,
|
||||||
|
title: title,
|
||||||
|
milliseconds: milliseconds,
|
||||||
|
musicList: <MusicModel>[],
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 删除
|
||||||
|
Future<void> delete(String id) async {
|
||||||
|
var list = getList();
|
||||||
|
final playlistModel = list.firstWhereOrNull((e) => e.id == id);
|
||||||
|
if (playlistModel != null) {
|
||||||
|
await _box.deleteAt(list.indexOf(playlistModel));
|
||||||
|
await _box.flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 清空所有数据
|
||||||
|
Future<void> clear() async {
|
||||||
|
await _box.clear();
|
||||||
|
await _box.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
PlaylistModel? getPlaylistModel(String id) {
|
||||||
|
return _box.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 编辑
|
||||||
|
void editTitle(String id, String title) {
|
||||||
|
var list = getList();
|
||||||
|
final playlistModel = list.firstWhereOrNull((e) => e.id == id);
|
||||||
|
if (playlistModel != null) {
|
||||||
|
playlistModel.title = title;
|
||||||
|
_box.put(id, playlistModel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 添加歌曲
|
||||||
|
Future<bool> addMusic(String id, MusicModel musicModel) async {
|
||||||
|
final playlistModel = _box.get(id);
|
||||||
|
if (playlistModel != null) {
|
||||||
|
playlistModel.musicList ??= <MusicModel>[];
|
||||||
|
if (playlistModel.musicList!.firstWhereOrNull((e) => e.videoId == musicModel.videoId) != null) {
|
||||||
|
BaseEasyLoading.toast('The current song is already in this playlist');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
playlistModel.musicList!.insert(0, musicModel);
|
||||||
|
_box.put(id, playlistModel);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 删除歌曲
|
||||||
|
Future<bool> removeMusic(String id, String videoId) async {
|
||||||
|
final playlistModel = _box.get(id);
|
||||||
|
if (playlistModel != null && playlistModel.musicList != null) {
|
||||||
|
if (playlistModel.musicList!.firstWhereOrNull((e) => e.videoId == videoId) != null) {
|
||||||
|
playlistModel.musicList!.removeWhere((e) => e.videoId == videoId);
|
||||||
|
_box.put(id, playlistModel);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 获取当前列表的封面
|
||||||
|
String? getFirstCoverUrl(String id) {
|
||||||
|
final playlistModel = _box.get(id);
|
||||||
|
if (playlistModel != null) {
|
||||||
|
if (playlistModel.musicList != null && playlistModel.musicList!.isNotEmpty) {
|
||||||
|
return playlistModel.musicList!.first.coverUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,14 +3,9 @@
|
|||||||
// Description: firebase_analytics管理
|
// Description: firebase_analytics管理
|
||||||
|
|
||||||
import 'package:firebase_analytics/firebase_analytics.dart';
|
import 'package:firebase_analytics/firebase_analytics.dart';
|
||||||
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
|
|
||||||
class FirebaseAnalyticsManager {
|
class FirebaseAnalyticsManager {
|
||||||
/// 仅在非调试版本中启用它
|
static const homeApv = 'home_a_pv';
|
||||||
static Future<void> setCrashlyticsCollectionEnabled() async {
|
|
||||||
await FirebaseCrashlytics.instance.setCrashlyticsCollectionEnabled(!kDebugMode);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 埋点
|
/// 埋点
|
||||||
/// name:事件名
|
/// name:事件名
|
||||||
|
|||||||
35
lib/firebase/firebase_crashlytics_manager.dart
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// Author: fengshengxiong
|
||||||
|
// Date: 2024/6/26
|
||||||
|
// Description: firebase_crashlytics管理
|
||||||
|
|
||||||
|
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:tone_snap/utils/log_util.dart';
|
||||||
|
|
||||||
|
class FirebaseCrashlyticsManager {
|
||||||
|
static Future<void> setEnabled() async {
|
||||||
|
await FirebaseCrashlytics.instance.setCrashlyticsCollectionEnabled(kReleaseMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void recordFlutterError() {
|
||||||
|
// 非异步错误
|
||||||
|
FlutterError.onError = (errorDetails) {
|
||||||
|
// 插件错误 flutter_cache_manager
|
||||||
|
// https://github.com/Baseflow/flutter_cache_manager/issues/460
|
||||||
|
if (errorDetails.exceptionAsString().contains('No host specified in URI')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LogUtil.e(errorDetails.exception);
|
||||||
|
FirebaseCrashlytics.instance.recordFlutterError(errorDetails);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static void recordError() {
|
||||||
|
// 异步错误
|
||||||
|
PlatformDispatcher.instance.onError = (error, stack) {
|
||||||
|
LogUtil.e(error);
|
||||||
|
FirebaseCrashlytics.instance.recordError(error, stack);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
69
lib/firebase/firebase_remote_config_manager.dart
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
// Author: fengshengxiong
|
||||||
|
// Date: 2024/6/26
|
||||||
|
// Description: firebase_remote_config管理
|
||||||
|
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:firebase_remote_config/firebase_remote_config.dart';
|
||||||
|
import 'package:tone_snap/data/storage/music_box.dart';
|
||||||
|
import 'package:tone_snap/utils/log_util.dart';
|
||||||
|
import 'package:tone_snap/utils/obj_util.dart';
|
||||||
|
|
||||||
|
class FirebaseRemoteConfigManager {
|
||||||
|
static Future<void> getAll() async {
|
||||||
|
final remoteConfig = FirebaseRemoteConfig.instance;
|
||||||
|
await remoteConfig.setConfigSettings(RemoteConfigSettings(
|
||||||
|
fetchTimeout: const Duration(minutes: 1),
|
||||||
|
minimumFetchInterval: Duration.zero,
|
||||||
|
));
|
||||||
|
bool result = await remoteConfig.fetchAndActivate();
|
||||||
|
if (result) {
|
||||||
|
Map<String, RemoteConfigValue> allData = remoteConfig.getAll();
|
||||||
|
try {
|
||||||
|
if (allData.isNotEmpty) {
|
||||||
|
LogUtil.d('远程配置获取成功');
|
||||||
|
|
||||||
|
// 获取 openStatus
|
||||||
|
var openStatus = allData['openStatus']?.asString();
|
||||||
|
if (ObjUtil.isNotEmpty(openStatus)) {
|
||||||
|
await MusicBox().putOpenStatus(openStatus!);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取 dataVersion
|
||||||
|
var dataVersion = allData['dataVersion']?.asString();
|
||||||
|
if (ObjUtil.isNotEmpty(dataVersion)) {
|
||||||
|
await MusicBox().putDataVersion(dataVersion!);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取 openAppEventDuration
|
||||||
|
var openAppEventDuration = allData['openAppEventDuration']?.asString();
|
||||||
|
if (ObjUtil.isNotEmpty(openAppEventDuration)) {
|
||||||
|
try {
|
||||||
|
var times = jsonDecode(openAppEventDuration!)['times'];
|
||||||
|
if (ObjUtil.isNotEmpty(times)) {
|
||||||
|
await MusicBox().putOpenAppEventDuration(times);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
LogUtil.e(e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取 interstitialEventDuration
|
||||||
|
var interstitialEventDuration = allData['interstitialEventDuration']?.asString();
|
||||||
|
if (ObjUtil.isNotEmpty(interstitialEventDuration)) {
|
||||||
|
try {
|
||||||
|
var times = jsonDecode(interstitialEventDuration!)['times'];
|
||||||
|
if (ObjUtil.isNotEmpty(times)) {
|
||||||
|
await MusicBox().putInterstitialEventDuration(times);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
LogUtil.e(e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
LogUtil.e(e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -44,7 +44,7 @@ class Assets {
|
|||||||
static const String sideAHomeBg = 'assets/images/side_a/home_bg.png';
|
static const String sideAHomeBg = 'assets/images/side_a/home_bg.png';
|
||||||
static const String sideAHomeBnbBg = 'assets/images/side_a/home_bnb_bg.png';
|
static const String sideAHomeBnbBg = 'assets/images/side_a/home_bnb_bg.png';
|
||||||
static const String sideAIconChevronRight = 'assets/images/side_a/icon_chevron_right.png';
|
static const String sideAIconChevronRight = 'assets/images/side_a/icon_chevron_right.png';
|
||||||
static const String sideALaunchImage = 'assets/images/side_a/launch_image.png';
|
static const String sideALaunchBg = 'assets/images/side_a/launch_bg.png';
|
||||||
static const String sideAMore = 'assets/images/side_a/more.png';
|
static const String sideAMore = 'assets/images/side_a/more.png';
|
||||||
static const String sideANotFavorite = 'assets/images/side_a/not_favorite.png';
|
static const String sideANotFavorite = 'assets/images/side_a/not_favorite.png';
|
||||||
static const String sideANotPlayed = 'assets/images/side_a/not_played.png';
|
static const String sideANotPlayed = 'assets/images/side_a/not_played.png';
|
||||||
@ -63,8 +63,8 @@ class Assets {
|
|||||||
static const String sideAUploadRecordSound = 'assets/images/side_a/upload_record_sound.png';
|
static const String sideAUploadRecordSound = 'assets/images/side_a/upload_record_sound.png';
|
||||||
static const String sideAUserAgreement = 'assets/images/side_a/user_agreement.png';
|
static const String sideAUserAgreement = 'assets/images/side_a/user_agreement.png';
|
||||||
static const String sideAVoiceDefault = 'assets/images/side_a/voice_default.png';
|
static const String sideAVoiceDefault = 'assets/images/side_a/voice_default.png';
|
||||||
static const String sideBAlbumTitleBg = 'assets/images/side_b/album_title_bg.png';
|
static const String sideBAddToPlaylist = 'assets/images/side_b/add_to_playlist.png';
|
||||||
static const String sideBAlbumTotal = 'assets/images/side_b/album_total.png';
|
static const String sideBAddToQueue = 'assets/images/side_b/add_to_queue.png';
|
||||||
static const String sideBArrowDownBack = 'assets/images/side_b/arrow_down_back.png';
|
static const String sideBArrowDownBack = 'assets/images/side_b/arrow_down_back.png';
|
||||||
static const String sideBArrowLeftBack = 'assets/images/side_b/arrow_left_back.png';
|
static const String sideBArrowLeftBack = 'assets/images/side_b/arrow_left_back.png';
|
||||||
static const String sideBArrowRightItem = 'assets/images/side_b/arrow_right_item.png';
|
static const String sideBArrowRightItem = 'assets/images/side_b/arrow_right_item.png';
|
||||||
@ -77,29 +77,48 @@ class Assets {
|
|||||||
static const String sideBBnb3Unselected = 'assets/images/side_b/bnb3_unselected.png';
|
static const String sideBBnb3Unselected = 'assets/images/side_b/bnb3_unselected.png';
|
||||||
static const String sideBBnbBg = 'assets/images/side_b/bnb_bg.png';
|
static const String sideBBnbBg = 'assets/images/side_b/bnb_bg.png';
|
||||||
static const String sideBBottomSheetIndicator = 'assets/images/side_b/bottom_sheet_indicator.png';
|
static const String sideBBottomSheetIndicator = 'assets/images/side_b/bottom_sheet_indicator.png';
|
||||||
|
static const String sideBCollected = 'assets/images/side_b/collected.png';
|
||||||
static const String sideBCollectionAlbum = 'assets/images/side_b/collection_album.png';
|
static const String sideBCollectionAlbum = 'assets/images/side_b/collection_album.png';
|
||||||
static const String sideBCrossCircle = 'assets/images/side_b/cross_circle.png';
|
static const String sideBCrossCircle = 'assets/images/side_b/cross_circle.png';
|
||||||
static const String sideBDownload = 'assets/images/side_b/download.png';
|
static const String sideBDeleteHistory = 'assets/images/side_b/delete_history.png';
|
||||||
|
static const String sideBDeleteWhite = 'assets/images/side_b/delete_white.png';
|
||||||
|
static const String sideBDownloaded = 'assets/images/side_b/downloaded.png';
|
||||||
|
static const String sideBEmpty = 'assets/images/side_b/empty.jpg';
|
||||||
static const String sideBHomeBg = 'assets/images/side_b/home_bg.png';
|
static const String sideBHomeBg = 'assets/images/side_b/home_bg.png';
|
||||||
static const String sideBImgError = 'assets/images/side_b/img_error.png';
|
|
||||||
static const String sideBImgPlaceholder = 'assets/images/side_b/img_placeholder.png';
|
|
||||||
static const String sideBItemPlayer1 = 'assets/images/side_b/item_player1.png';
|
static const String sideBItemPlayer1 = 'assets/images/side_b/item_player1.png';
|
||||||
static const String sideBLineMenu = 'assets/images/side_b/line_menu.png';
|
static const String sideBLineMenu = 'assets/images/side_b/line_menu.png';
|
||||||
static const String sideBListLoop = 'assets/images/side_b/list_loop.png';
|
static const String sideBListLoop = 'assets/images/side_b/list_loop.png';
|
||||||
static const String sideBLove = 'assets/images/side_b/love.png';
|
static const String sideBLove = 'assets/images/side_b/love.png';
|
||||||
static const String sideBLoveSolid = 'assets/images/side_b/love_solid.png';
|
static const String sideBLoveSolid = 'assets/images/side_b/love_solid.png';
|
||||||
|
static const String sideBLoveSongsBg = 'assets/images/side_b/love_songs_bg.png';
|
||||||
|
static const String sideBMore = 'assets/images/side_b/more.png';
|
||||||
|
static const String sideBMoreEdit = 'assets/images/side_b/more_edit.png';
|
||||||
|
static const String sideBMoreRemove = 'assets/images/side_b/more_remove.png';
|
||||||
|
static const String sideBMusicBarNext = 'assets/images/side_b/music_bar_next.png';
|
||||||
|
static const String sideBMusicPlaceholder = 'assets/images/side_b/music_placeholder.png';
|
||||||
static const String sideBNextTrack = 'assets/images/side_b/next_track.png';
|
static const String sideBNextTrack = 'assets/images/side_b/next_track.png';
|
||||||
static const String sideBNotCollectionAlbum = 'assets/images/side_b/not_collection_album.png';
|
static const String sideBNotCollectionAlbum = 'assets/images/side_b/not_collection_album.png';
|
||||||
|
static const String sideBNotDownload1 = 'assets/images/side_b/not_download1.png';
|
||||||
|
static const String sideBNotDownload2 = 'assets/images/side_b/not_download2.png';
|
||||||
static const String sideBOfflineDownload = 'assets/images/side_b/offline_download.png';
|
static const String sideBOfflineDownload = 'assets/images/side_b/offline_download.png';
|
||||||
static const String sideBPausePlay = 'assets/images/side_b/pause_play.png';
|
static const String sideBPausePlay = 'assets/images/side_b/pause_play.png';
|
||||||
static const String sideBPersonalMusicLibraryBg = 'assets/images/side_b/personal_music_library_bg.png';
|
static const String sideBPersonalMusicLibraryBg = 'assets/images/side_b/personal_music_library_bg.png';
|
||||||
static const String sideBPlaceholderLibrary = 'assets/images/side_b/placeholder_library.png';
|
static const String sideBPlaceholderLibrary = 'assets/images/side_b/placeholder_library.png';
|
||||||
static const String sideBPlayList = 'assets/images/side_b/play_list.png';
|
static const String sideBPlayList = 'assets/images/side_b/play_list.png';
|
||||||
static const String sideBPlayListDelete = 'assets/images/side_b/play_list_delete.png';
|
static const String sideBPlayListDelete = 'assets/images/side_b/play_list_delete.png';
|
||||||
|
static const String sideBPlaylistPlayAll = 'assets/images/side_b/playlist_play_all.png';
|
||||||
|
static const String sideBPlaylistPlayAllRandom = 'assets/images/side_b/playlist_play_all_random.png';
|
||||||
|
static const String sideBPlaylistTitleBg = 'assets/images/side_b/playlist_title_bg.png';
|
||||||
static const String sideBPlaylistsAdd = 'assets/images/side_b/playlists_add.png';
|
static const String sideBPlaylistsAdd = 'assets/images/side_b/playlists_add.png';
|
||||||
static const String sideBPreviousTrack = 'assets/images/side_b/previous_track.png';
|
static const String sideBPreviousTrack = 'assets/images/side_b/previous_track.png';
|
||||||
|
static const String sideBPrivacyPolicy = 'assets/images/side_b/privacy_policy.png';
|
||||||
|
static const String sideBReport = 'assets/images/side_b/report.png';
|
||||||
|
static const String sideBSearch = 'assets/images/side_b/search.png';
|
||||||
|
static const String sideBSearchWhite = 'assets/images/side_b/search_white.png';
|
||||||
|
static const String sideBSettingBg = 'assets/images/side_b/setting_bg.png';
|
||||||
static const String sideBShufflePlayback = 'assets/images/side_b/shuffle_playback.png';
|
static const String sideBShufflePlayback = 'assets/images/side_b/shuffle_playback.png';
|
||||||
static const String sideBSingleCycle = 'assets/images/side_b/single_cycle.png';
|
static const String sideBSingleCycle = 'assets/images/side_b/single_cycle.png';
|
||||||
static const String sideBStartPlay = 'assets/images/side_b/start_play.png';
|
static const String sideBStartPlay = 'assets/images/side_b/start_play.png';
|
||||||
|
static const String sideBTermsOfService = 'assets/images/side_b/terms_of_service.png';
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,13 +5,24 @@
|
|||||||
import 'package:tone_snap/data/enum/app_side_enum.dart';
|
import 'package:tone_snap/data/enum/app_side_enum.dart';
|
||||||
|
|
||||||
class AppConfig {
|
class AppConfig {
|
||||||
static const appName = 'ToneSnap';
|
static const String appName = 'ToneSnap';
|
||||||
|
|
||||||
/// 当前App展示的一面
|
/// 当前App展示的一面
|
||||||
static const AppSideEnum appSideEnum = AppSideEnum.sideB;
|
static AppSideEnum appSideEnum = AppSideEnum.sideA;
|
||||||
|
|
||||||
/// 默认语言环境和所在区域
|
/// 开屏启动插页事件间隔时长/秒,默认值
|
||||||
static String defaultLocale = 'zh-CN';
|
static const int openAppEventDurationTime = 10;
|
||||||
|
|
||||||
|
/// 插页广告事件间隔时长/秒,默认值
|
||||||
|
static const int interstitialEventDuration = 40;
|
||||||
|
|
||||||
|
/// ClientVersion,默认值
|
||||||
|
static const String clientVersion = '1.20240618.01.00';
|
||||||
|
|
||||||
|
/// PlayerVersion,默认值
|
||||||
|
static const String playerVersion = '6.18.1';
|
||||||
|
|
||||||
|
/// 所在区域,默认值
|
||||||
static String isoCode = 'HK';
|
static String isoCode = 'HK';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,12 +7,17 @@ import 'package:tone_snap/ads/app_open_ad_manager.dart';
|
|||||||
import 'package:tone_snap/ads/interstitial_ad_manager.dart';
|
import 'package:tone_snap/ads/interstitial_ad_manager.dart';
|
||||||
|
|
||||||
class AppLifecycleReactor {
|
class AppLifecycleReactor {
|
||||||
AppLifecycleReactor();
|
AppLifecycleReactor._();
|
||||||
|
|
||||||
|
static final AppLifecycleReactor _instance = AppLifecycleReactor._();
|
||||||
|
|
||||||
|
factory AppLifecycleReactor() {
|
||||||
|
return _instance;
|
||||||
|
}
|
||||||
|
|
||||||
void listenToAppStateChanges() {
|
void listenToAppStateChanges() {
|
||||||
AppStateEventNotifier.startListening();
|
AppStateEventNotifier.startListening();
|
||||||
AppStateEventNotifier.appStateStream
|
AppStateEventNotifier.appStateStream.forEach((state) => _onAppStateChanged(state));
|
||||||
.forEach((state) => _onAppStateChanged(state));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onAppStateChanged(AppState appState) {
|
void _onAppStateChanged(AppState appState) {
|
||||||
|
|||||||
165
lib/global/download_manager.dart
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
// Author: fengshengxiong
|
||||||
|
// Date: 2024/5/10
|
||||||
|
// Description: 下载管理
|
||||||
|
|
||||||
|
import 'package:background_downloader/background_downloader.dart';
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:tone_snap/components/base_easyloading.dart';
|
||||||
|
import 'package:tone_snap/data/api/music_api.dart';
|
||||||
|
import 'package:tone_snap/data/models/music_model.dart';
|
||||||
|
import 'package:tone_snap/data/models/player_model.dart';
|
||||||
|
import 'package:tone_snap/data/storage/offline_box.dart';
|
||||||
|
import 'package:tone_snap/modules/sideb/offline/offline_controller.dart';
|
||||||
|
import 'package:tone_snap/modules/sideb/personal_music_library/personal_music_library_controller.dart';
|
||||||
|
import 'package:tone_snap/utils/date_util.dart';
|
||||||
|
import 'package:tone_snap/utils/local_path_util.dart';
|
||||||
|
import 'package:tone_snap/utils/log_util.dart';
|
||||||
|
import 'package:tone_snap/utils/obj_util.dart';
|
||||||
|
|
||||||
|
class DownloadManager {
|
||||||
|
static final DownloadManager _instance = DownloadManager._getInstance();
|
||||||
|
|
||||||
|
factory DownloadManager() => _instance;
|
||||||
|
|
||||||
|
static MemoryTaskQueue? tq;
|
||||||
|
|
||||||
|
List<Rx<MusicModel>> downloadList = [];
|
||||||
|
|
||||||
|
DownloadManager._getInstance() {
|
||||||
|
if (tq == null) {
|
||||||
|
tq ??= MemoryTaskQueue();
|
||||||
|
tq!.maxConcurrent = 3; // no more than 5 tasks active at any one time
|
||||||
|
tq!.maxConcurrentByHost = 3; // no more than two tasks talking to the same host at the same time
|
||||||
|
tq!.maxConcurrentByGroup = 3; // no more than three tasks from the same group active at the same time
|
||||||
|
FileDownloader().addTaskQueue(tq!); // 'connects' the TaskQueue to the FileDownloader
|
||||||
|
FileDownloader().updates.listen((update) async { // listen to updates as per usual
|
||||||
|
Rx<MusicModel>? musicModel = downloadList.firstWhereOrNull((e) => e.value.videoId == update.task.taskId);
|
||||||
|
if (musicModel == null) return;
|
||||||
|
|
||||||
|
if (update.runtimeType == TaskStatusUpdate) {
|
||||||
|
TaskStatus taskStatus = (update as TaskStatusUpdate).status;
|
||||||
|
LogUtil.d('${update.task.filename},任务状态: $taskStatus');
|
||||||
|
musicModel.update((fn) => fn?.taskStatus = taskStatus);
|
||||||
|
switch (taskStatus) {
|
||||||
|
case TaskStatus.enqueued:
|
||||||
|
break;
|
||||||
|
case TaskStatus.running:
|
||||||
|
break;
|
||||||
|
case TaskStatus.complete:
|
||||||
|
LogUtil.d('音乐下载路径:${await update.task.filePath()}');
|
||||||
|
musicModel.value.localPath = await update.task.filePath();
|
||||||
|
OfflineBox().add(musicModel.value.copyWith());
|
||||||
|
downloadList.remove(musicModel);
|
||||||
|
BaseEasyLoading.toast('Download completed');
|
||||||
|
if (Get.isRegistered<PersonalMusicLibraryController>()) {
|
||||||
|
PersonalMusicLibraryController.to.refreshOffline();
|
||||||
|
}
|
||||||
|
if (Get.isRegistered<OfflineController>()) {
|
||||||
|
OfflineController.to.getOfflineList();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TaskStatus.notFound:
|
||||||
|
BaseEasyLoading.toast('Download failed');
|
||||||
|
downloadList.remove(musicModel);
|
||||||
|
break;
|
||||||
|
case TaskStatus.failed:
|
||||||
|
BaseEasyLoading.toast('Download failed');
|
||||||
|
downloadList.remove(musicModel);
|
||||||
|
break;
|
||||||
|
case TaskStatus.canceled:
|
||||||
|
BaseEasyLoading.toast('Download cancelled');
|
||||||
|
downloadList.remove(musicModel);
|
||||||
|
break;
|
||||||
|
case TaskStatus.waitingToRetry:
|
||||||
|
break;
|
||||||
|
case TaskStatus.paused:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (update.runtimeType == TaskProgressUpdate) {
|
||||||
|
LogUtil.d('${update.task.filename},下载进度: $update');
|
||||||
|
musicModel.update((fn) => fn?.progress = (update as TaskProgressUpdate).progress);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 下载文件
|
||||||
|
void downloadMusic(Rx<MusicModel>? musicModel) {
|
||||||
|
if (musicModel == null) return;
|
||||||
|
musicModel.update((fn) {
|
||||||
|
fn?.taskStatus = TaskStatus.enqueued;
|
||||||
|
fn?.cancelToken = CancelToken();
|
||||||
|
});
|
||||||
|
_getMusicUrl(musicModel, (url, mimeType) async {
|
||||||
|
if (ObjUtil.isEmpty(url)) {
|
||||||
|
BaseEasyLoading.toast('Resource acquisition failed');
|
||||||
|
musicModel.update((fn) => fn?.taskStatus = TaskStatus.failed);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String extension = mimeType ?? 'mp4';
|
||||||
|
if (ObjUtil.isNotEmpty(mimeType)) {
|
||||||
|
// 从 mimeType 中提取主类型和子类型
|
||||||
|
String type = mimeType!.split(';')[0].trim();
|
||||||
|
// 获取文件扩展名
|
||||||
|
extension = type.split('/')[1];
|
||||||
|
}
|
||||||
|
final filename = '${musicModel.value.title}_${DateUtil.getNowTimestamp()}.$extension';
|
||||||
|
final task = DownloadTask(
|
||||||
|
taskId: musicModel.value.videoId,
|
||||||
|
url: url!,
|
||||||
|
filename: filename,
|
||||||
|
directory: LocalPathUtil.getMusicDownloadDir(),
|
||||||
|
updates: Updates.statusAndProgress,
|
||||||
|
requiresWiFi: false,
|
||||||
|
retries: 0,
|
||||||
|
allowPause: false,
|
||||||
|
metaData: '',
|
||||||
|
);
|
||||||
|
tq?.add(task);
|
||||||
|
downloadList.add(musicModel);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _getMusicUrl(Rx<MusicModel> musicModel, Function(String? url, String? mimeType) onTap) async {
|
||||||
|
PlayerModel? playerModel = await MusicApi.player(
|
||||||
|
videoId: musicModel.value.videoId,
|
||||||
|
cancelToken: musicModel.value.cancelToken,
|
||||||
|
fail: (baseError) {
|
||||||
|
if (baseError.code == DioExceptionType.cancel.index) {
|
||||||
|
musicModel.update((fn) {
|
||||||
|
fn?.taskStatus = TaskStatus.canceled;
|
||||||
|
fn?.cancelToken = null;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
musicModel.update((fn) {
|
||||||
|
fn?.taskStatus = TaskStatus.failed;
|
||||||
|
fn?.cancelToken = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (playerModel != null) {
|
||||||
|
if (ObjUtil.isEmpty(musicModel.value.coverUrl)) {
|
||||||
|
var thumbnails = playerModel.videoDetails?.thumbnail?.thumbnails;
|
||||||
|
if (thumbnails != null && thumbnails.isNotEmpty) {
|
||||||
|
musicModel.value.coverUrl = thumbnails[0].url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ObjUtil.isEmpty(musicModel.value.musicType)) {
|
||||||
|
musicModel.value.musicType = playerModel.videoDetails?.musicVideoType;
|
||||||
|
}
|
||||||
|
var formats = playerModel.streamingData?.formats;
|
||||||
|
if (formats != null && formats.isNotEmpty) {
|
||||||
|
onTap(formats[0].url, formats[0].mimeType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void cancelDownload(Rx<MusicModel>? musicModel) {
|
||||||
|
if (musicModel == null || musicModel.value.videoId == null) return;
|
||||||
|
musicModel.value.cancelToken?.cancel();
|
||||||
|
FileDownloader().cancelTaskWithId(musicModel.value.videoId!);
|
||||||
|
}
|
||||||
|
}
|
||||||
44
lib/global/download_queue_task.dart
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
// Author: fengshengxiong
|
||||||
|
// Date: 2024/7/17
|
||||||
|
// Description: 下载队列
|
||||||
|
|
||||||
|
import 'dart:collection';
|
||||||
|
|
||||||
|
class DownloadQueueTask {
|
||||||
|
int maxThread = 1;
|
||||||
|
|
||||||
|
DownloadQueueTask(this.maxThread);
|
||||||
|
|
||||||
|
///当前任务队列
|
||||||
|
final Queue<_TaskInfo> _queue = Queue();
|
||||||
|
|
||||||
|
///是否工作中
|
||||||
|
int _taskCount = 0;
|
||||||
|
|
||||||
|
void create(String taskName, Function(String name) workTask) {
|
||||||
|
_queue.add(_TaskInfo(taskName, workTask));
|
||||||
|
_exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _exec() async {
|
||||||
|
if (_taskCount >= maxThread) return;
|
||||||
|
if (_queue.isEmpty) return;
|
||||||
|
|
||||||
|
for (int i = 0; i < maxThread; i++) {
|
||||||
|
if (_queue.isEmpty) continue;
|
||||||
|
|
||||||
|
_TaskInfo taskInfo = _queue.removeFirst();
|
||||||
|
_taskCount += 1;
|
||||||
|
await taskInfo.workTask.call(taskInfo.taskName);
|
||||||
|
_taskCount -= 1;
|
||||||
|
}
|
||||||
|
_exec();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TaskInfo {
|
||||||
|
String taskName;
|
||||||
|
Function(String taskName) workTask;
|
||||||
|
|
||||||
|
_TaskInfo(this.taskName, this.workTask);
|
||||||
|
}
|
||||||
@ -8,22 +8,34 @@ import 'package:connectivity_plus/connectivity_plus.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:tone_snap/ads/app_open_ad_manager.dart';
|
import 'package:tone_snap/ads/app_open_ad_manager.dart';
|
||||||
import 'package:tone_snap/ads/interstitial_ad_manager.dart';
|
import 'package:tone_snap/ads/interstitial_ad_manager.dart';
|
||||||
|
import 'package:tone_snap/firebase/firebase_remote_config_manager.dart';
|
||||||
|
import 'package:tone_snap/modules/launch/launch_controller.dart';
|
||||||
import 'package:tone_snap/utils/log_util.dart';
|
import 'package:tone_snap/utils/log_util.dart';
|
||||||
|
|
||||||
class NetworkConnectivityService extends GetxService {
|
class NetworkConnectivityService extends GetxService {
|
||||||
StreamSubscription<List<ConnectivityResult>>? subscription;
|
StreamSubscription<List<ConnectivityResult>>? subscription;
|
||||||
List<ConnectivityResult> recordResult = [];
|
/// 是否执行过以下任务,任务只执行一次
|
||||||
|
var isExecutedTask = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
subscription = Connectivity().onConnectivityChanged.listen((List<ConnectivityResult> result) {
|
subscription = Connectivity().onConnectivityChanged.listen((List<ConnectivityResult> result) {
|
||||||
LogUtil.d('网络连接类型变化:$result');
|
LogUtil.d('当前网络连接类型:$result');
|
||||||
if (recordResult.contains(ConnectivityResult.none) && !result.contains(ConnectivityResult.none)) {
|
if (result.contains(ConnectivityResult.wifi) || result.contains(ConnectivityResult.mobile)) {
|
||||||
AppOpenAdManager().loadAd();
|
if (!isExecutedTask) {
|
||||||
InterstitialAdManager().loadAd();
|
isExecutedTask = true;
|
||||||
|
|
||||||
|
FirebaseRemoteConfigManager.getAll();
|
||||||
|
|
||||||
|
if (Get.isRegistered<LaunchController>()) {
|
||||||
|
LaunchController.to.getIsoCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
InterstitialAdManager().loadAd();
|
||||||
|
AppOpenAdManager().loadAd();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
recordResult = result;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
135
lib/main.dart
@ -1,23 +1,22 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:firebase_core/firebase_core.dart';
|
import 'package:firebase_core/firebase_core.dart';
|
||||||
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_easyloading/flutter_easyloading.dart';
|
import 'package:flutter_easyloading/flutter_easyloading.dart';
|
||||||
|
import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:google_mobile_ads/google_mobile_ads.dart';
|
import 'package:google_mobile_ads/google_mobile_ads.dart';
|
||||||
import 'package:tone_snap/components/base_easyloading.dart';
|
import 'package:tone_snap/components/base_easyloading.dart';
|
||||||
import 'package:tone_snap/components/music_bar.dart';
|
|
||||||
import 'package:tone_snap/components/music_bar/music_bar_controller.dart';
|
|
||||||
import 'package:tone_snap/data/enum/app_side_enum.dart';
|
|
||||||
import 'package:tone_snap/data/storage/hive_storage.dart';
|
import 'package:tone_snap/data/storage/hive_storage.dart';
|
||||||
|
import 'package:tone_snap/firebase/firebase_crashlytics_manager.dart';
|
||||||
import 'package:tone_snap/firebase/firebase_options.dart';
|
import 'package:tone_snap/firebase/firebase_options.dart';
|
||||||
import 'package:tone_snap/global/app_config.dart';
|
import 'package:tone_snap/global/app_config.dart';
|
||||||
import 'package:tone_snap/global/network_connectivity_service.dart';
|
import 'package:tone_snap/modules/sideb/controllers/main_controller.dart';
|
||||||
import 'package:tone_snap/modules/sideb/controllers/music_player_controller.dart';
|
import 'package:tone_snap/modules/sideb/controllers/music_player_controller.dart';
|
||||||
|
import 'package:tone_snap/modules/sideb/music_bar/music_bar.dart';
|
||||||
|
import 'package:tone_snap/modules/sideb/music_bar/music_bar_controller.dart';
|
||||||
import 'package:tone_snap/res/themes/app_themes.dart';
|
import 'package:tone_snap/res/themes/app_themes.dart';
|
||||||
import 'package:tone_snap/routes/app_pages.dart';
|
import 'package:tone_snap/routes/app_pages.dart';
|
||||||
import 'package:tone_snap/routes/app_routes.dart';
|
import 'package:tone_snap/routes/app_routes.dart';
|
||||||
@ -28,34 +27,19 @@ import 'package:tone_snap/utils/log_util.dart';
|
|||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
if (Platform.isIOS) {
|
// 初始化Firebase
|
||||||
// 初始化Firebase
|
try {
|
||||||
try {
|
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
|
||||||
await Firebase.initializeApp(
|
await FirebaseCrashlyticsManager.setEnabled();
|
||||||
options: DefaultFirebaseOptions.currentPlatform,
|
FirebaseCrashlyticsManager.recordFlutterError();
|
||||||
);
|
FirebaseCrashlyticsManager.recordError();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
LogUtil.e("Firebase initialization error: $e");
|
LogUtil.e("Firebase initialization error: $e");
|
||||||
}
|
|
||||||
|
|
||||||
// 非异步错误
|
|
||||||
FlutterError.onError = (errorDetails) {
|
|
||||||
FirebaseCrashlytics.instance.recordFlutterError(errorDetails);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 异步错误
|
|
||||||
PlatformDispatcher.instance.onError = (error, stack) {
|
|
||||||
FirebaseCrashlytics.instance.recordError(error, stack, fatal: false);
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 初始化广告 SDK
|
|
||||||
MobileAds.instance.initialize();
|
|
||||||
|
|
||||||
// 监听网络变化
|
|
||||||
await Get.putAsync(() async => NetworkConnectivityService());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 初始化广告 SDK
|
||||||
|
MobileAds.instance.initialize();
|
||||||
|
|
||||||
// 初始化Hive
|
// 初始化Hive
|
||||||
await initHive();
|
await initHive();
|
||||||
|
|
||||||
@ -87,53 +71,62 @@ class MyApp extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final easyLoading = EasyLoading.init();
|
final easyLoading = EasyLoading.init();
|
||||||
ThemeData appTheme;
|
|
||||||
List<NavigatorObserver> navigatorObservers = const <NavigatorObserver>[];
|
List<NavigatorObserver> navigatorObservers = const <NavigatorObserver>[];
|
||||||
if (AppConfig.appSideEnum == AppSideEnum.sideA) {
|
navigatorObservers = [
|
||||||
appTheme = sideATheme;
|
GetObserver((_) {
|
||||||
} else {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
appTheme = sideBTheme;
|
if (Get.currentRoute == AppRoutes.playPage) {
|
||||||
navigatorObservers = [
|
MusicBar().hide();
|
||||||
GetObserver((_) {
|
} else {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
if (Get.isRegistered<MusicPlayerController>()) {
|
||||||
if (Get.currentRoute == AppRoutes.playPage) {
|
if (MusicPlayerController.to.getMusicModel()?.value.videoId != null) {
|
||||||
MusicBar().hide();
|
if (Get.isBottomSheetOpen != null && Get.isBottomSheetOpen!) {
|
||||||
|
MusicBar().hide();
|
||||||
|
} else {
|
||||||
|
MusicBar().show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Get.isRegistered<MusicBarController>()) {
|
||||||
|
if (Get.currentRoute == AppRoutes.initialB) {
|
||||||
|
MusicBarController.to.riseUp();
|
||||||
} else {
|
} else {
|
||||||
if (Get.isRegistered<MusicPlayerController>() && MusicPlayerController.to.musicModel.value.videoId != null) {
|
MusicBarController.to.toBottom();
|
||||||
MusicBar().show();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (Get.isRegistered<MusicBarController>()) {
|
}
|
||||||
if (Get.currentRoute == AppRoutes.initialB) {
|
});
|
||||||
MusicBarController.to.riseUp();
|
}),
|
||||||
} else {
|
];
|
||||||
MusicBarController.to.toBottom();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
return ScreenUtilInit(
|
return ScreenUtilInit(
|
||||||
// 以设计稿的尺寸为基准进行适配
|
// 以设计稿的尺寸为基准进行适配
|
||||||
designSize: const Size(375, 812),
|
designSize: const Size(375, 812),
|
||||||
minTextAdapt: true,
|
minTextAdapt: true,
|
||||||
builder: (context, child) {
|
builder: (context, child) {
|
||||||
return GetMaterialApp(
|
return KeyboardDismissOnTap(
|
||||||
title: AppConfig.appName,
|
dismissOnCapturedTaps: true,
|
||||||
debugShowCheckedModeBanner: false,
|
child: GetBuilder(
|
||||||
theme: appTheme,
|
id: 'changeTheme',
|
||||||
darkTheme: appTheme,
|
init: MainController(),
|
||||||
themeMode: ThemeMode.dark,
|
builder: (logic) {
|
||||||
initialRoute: AppRoutes.splash,
|
return GetMaterialApp(
|
||||||
getPages: AppPages.routes,
|
title: AppConfig.appName,
|
||||||
navigatorObservers: navigatorObservers,
|
debugShowCheckedModeBanner: false,
|
||||||
builder: (context, widget) {
|
theme: logic.isSideBTheme.value ? sideBTheme : sideATheme,
|
||||||
BaseEasyLoading.configLoading();
|
darkTheme: logic.isSideBTheme.value ? sideBTheme : sideATheme,
|
||||||
widget = easyLoading(context, widget);
|
themeMode: ThemeMode.dark,
|
||||||
// 设置文字大小不随系统设置改变
|
initialRoute: AppRoutes.launch,
|
||||||
return MediaQuery.withNoTextScaling(child: widget);
|
getPages: AppPages.routes,
|
||||||
},
|
navigatorObservers: navigatorObservers,
|
||||||
|
builder: (context, widget) {
|
||||||
|
BaseEasyLoading.configLoading();
|
||||||
|
widget = easyLoading(context, widget);
|
||||||
|
// 设置文字大小不随系统设置改变
|
||||||
|
return MediaQuery.withNoTextScaling(child: widget);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
9
lib/modules/launch/launch_binding.dart
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:tone_snap/modules/launch/launch_controller.dart';
|
||||||
|
|
||||||
|
class LaunchBinding extends Bindings {
|
||||||
|
@override
|
||||||
|
void dependencies() {
|
||||||
|
Get.lazyPut(() => LaunchController());
|
||||||
|
}
|
||||||
|
}
|
||||||
125
lib/modules/launch/launch_controller.dart
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
|
import 'package:tone_snap/ads/app_open_ad_manager.dart';
|
||||||
|
import 'package:tone_snap/data/api/tikustok_api.dart';
|
||||||
|
import 'package:tone_snap/data/enum/app_side_enum.dart';
|
||||||
|
import 'package:tone_snap/data/models/base_model.dart';
|
||||||
|
import 'package:tone_snap/data/models/isocode_model.dart';
|
||||||
|
import 'package:tone_snap/data/storage/music_box.dart';
|
||||||
|
import 'package:tone_snap/global/app_config.dart';
|
||||||
|
import 'package:tone_snap/global/app_lifecycle_reactor.dart';
|
||||||
|
import 'package:tone_snap/global/network_connectivity_service.dart';
|
||||||
|
import 'package:tone_snap/modules/sideb/controllers/main_controller.dart';
|
||||||
|
import 'package:tone_snap/res/themes/app_themes.dart';
|
||||||
|
import 'package:tone_snap/routes/app_routes.dart';
|
||||||
|
import 'package:tone_snap/utils/log_util.dart';
|
||||||
|
|
||||||
|
class LaunchController extends GetxController with GetSingleTickerProviderStateMixin {
|
||||||
|
static LaunchController get to => Get.find<LaunchController>();
|
||||||
|
Timer? _timer;
|
||||||
|
/// 进度总时长
|
||||||
|
var timeTotal = 10 * 1000;
|
||||||
|
/// 当前进度
|
||||||
|
var currentProcess = 0.obs;
|
||||||
|
/// 进度每次变化值
|
||||||
|
var changeValue = 10;
|
||||||
|
|
||||||
|
late AppLifecycleReactor _appLifecycleReactor;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInit() {
|
||||||
|
super.onInit();
|
||||||
|
_appLifecycleReactor = AppLifecycleReactor();
|
||||||
|
_appLifecycleReactor.listenToAppStateChanges();
|
||||||
|
|
||||||
|
// 监听网络变化
|
||||||
|
Get.putAsync(() async => NetworkConnectivityService());
|
||||||
|
|
||||||
|
_startTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onClose() {
|
||||||
|
_stopTimer();
|
||||||
|
super.onClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 开始定时器
|
||||||
|
void _startTimer() {
|
||||||
|
_timer = Timer.periodic(Duration(milliseconds: changeValue), (Timer t) {
|
||||||
|
if (currentProcess.value + changeValue >= timeTotal) {
|
||||||
|
currentProcess.value = timeTotal;
|
||||||
|
if (currentProcess >= timeTotal) {
|
||||||
|
_stopTimer();
|
||||||
|
_checkEnter();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
currentProcess.value += changeValue;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 停止定时器
|
||||||
|
void _stopTimer() {
|
||||||
|
_timer?.cancel();
|
||||||
|
_timer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 修改进度变化值
|
||||||
|
void editChangeValue() {
|
||||||
|
changeValue = 3000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 校验开关和版本,决定进A还是B
|
||||||
|
Future<void> _checkEnter() async {
|
||||||
|
bool isOpenedSideB = MusicBox().getIsOpenedSideB();
|
||||||
|
if (isOpenedSideB) {
|
||||||
|
LogUtil.d('进入过B面');
|
||||||
|
_openSideB();
|
||||||
|
} else {
|
||||||
|
bool enter = MusicBox().getEnter();
|
||||||
|
String versionCode = await MusicBox().getVersionCode();
|
||||||
|
final packageInfo = await PackageInfo.fromPlatform();
|
||||||
|
if (versionCode != packageInfo.version) {
|
||||||
|
LogUtil.d('版本不相同,进入B面');
|
||||||
|
_openSideB();
|
||||||
|
} else {
|
||||||
|
if (enter) {
|
||||||
|
LogUtil.d('开关:打开');
|
||||||
|
_openSideB();
|
||||||
|
} else {
|
||||||
|
LogUtil.d('开关:关闭');
|
||||||
|
_openSideA();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _openSideA() {
|
||||||
|
AppOpenAdManager().showAdIfAvailable(onTap: () {
|
||||||
|
AppConfig.appSideEnum = AppSideEnum.sideA;
|
||||||
|
Get.offNamed(AppRoutes.initialA);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _openSideB() {
|
||||||
|
AppOpenAdManager().showAdIfAvailable(onTap: () {
|
||||||
|
AppConfig.appSideEnum = AppSideEnum.sideB;
|
||||||
|
MainController.to.changeTheme();
|
||||||
|
Get.offNamed(AppRoutes.initialB);
|
||||||
|
MusicBox().putIsOpenedSideB(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 获取所在区域
|
||||||
|
Future<void> getIsoCode() async {
|
||||||
|
BaseModel<IsoCodeModel>? model = await TikUsTokApi.getIsoCode();
|
||||||
|
if (model != null && model.success && model.data?.isoCode != null) {
|
||||||
|
AppConfig.isoCode = model.data!.isoCode!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,14 +1,15 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:step_progress_indicator/step_progress_indicator.dart';
|
||||||
import 'package:tone_snap/generated/assets.dart';
|
import 'package:tone_snap/generated/assets.dart';
|
||||||
import 'package:tone_snap/modules/splash/splash_controller.dart';
|
import 'package:tone_snap/modules/launch/launch_controller.dart';
|
||||||
import 'package:tone_snap/res/themes/app_colors.dart';
|
import 'package:tone_snap/res/themes/app_colors.dart';
|
||||||
|
|
||||||
class SplashView extends StatelessWidget {
|
class LaunchView extends StatelessWidget {
|
||||||
SplashView({super.key});
|
LaunchView({super.key});
|
||||||
|
|
||||||
final controller = Get.find<SplashController>();
|
final controller = Get.find<LaunchController>();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -24,7 +25,7 @@ class SplashView extends StatelessWidget {
|
|||||||
|
|
||||||
Widget _buildImageBg() {
|
Widget _buildImageBg() {
|
||||||
return Image.asset(
|
return Image.asset(
|
||||||
Assets.sideALaunchImage,
|
Assets.sideALaunchBg,
|
||||||
width: 1.sw,
|
width: 1.sw,
|
||||||
height: 1.sh,
|
height: 1.sh,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
@ -39,17 +40,31 @@ class SplashView extends StatelessWidget {
|
|||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 0.5.sw,
|
width: 0.6.sw,
|
||||||
child: Obx(() {
|
child: Obx(() {
|
||||||
return LinearProgressIndicator(
|
return ClipRRect(
|
||||||
value: controller.processValue.value,
|
|
||||||
backgroundColor: Colors.white,
|
|
||||||
valueColor: const AlwaysStoppedAnimation<Color>(seedColor),
|
|
||||||
borderRadius: BorderRadius.circular(8).r,
|
borderRadius: BorderRadius.circular(8).r,
|
||||||
|
child: StepProgressIndicator(
|
||||||
|
totalSteps: controller.timeTotal,
|
||||||
|
currentStep: controller.currentProcess.value,
|
||||||
|
size: 6,
|
||||||
|
padding: 0,
|
||||||
|
unselectedColor: Colors.white,
|
||||||
|
roundedEdges: const Radius.circular(8).r,
|
||||||
|
selectedGradientColor: const LinearGradient(
|
||||||
|
begin: Alignment.centerLeft,
|
||||||
|
end: Alignment.centerRight,
|
||||||
|
colors: [
|
||||||
|
Color(0xffAC42FF),
|
||||||
|
Color(0xff5738D3),
|
||||||
|
Color(0xffC1ED02),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
SizedBox(height: 14.h),
|
SizedBox(height: 10.h),
|
||||||
Text(
|
Text(
|
||||||
'Resource Loading...',
|
'Resource Loading...',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
@ -1,5 +1,3 @@
|
|||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:ffmpeg_kit_flutter_audio/ffmpeg_kit.dart';
|
import 'package:ffmpeg_kit_flutter_audio/ffmpeg_kit.dart';
|
||||||
import 'package:ffmpeg_kit_flutter_audio/ffmpeg_session.dart';
|
import 'package:ffmpeg_kit_flutter_audio/ffmpeg_session.dart';
|
||||||
import 'package:ffmpeg_kit_flutter_audio/return_code.dart';
|
import 'package:ffmpeg_kit_flutter_audio/return_code.dart';
|
||||||
@ -48,9 +46,7 @@ class ChangeVoiceController extends GetxController {
|
|||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
if (Platform.isIOS) {
|
InterstitialAdManager().loadAd();
|
||||||
InterstitialAdManager().loadAd();
|
|
||||||
}
|
|
||||||
|
|
||||||
filePath = Get.arguments;
|
filePath = Get.arguments;
|
||||||
// playerController.setFilePath(filePath);
|
// playerController.setFilePath(filePath);
|
||||||
@ -108,122 +104,65 @@ class ChangeVoiceController extends GetxController {
|
|||||||
|
|
||||||
/// 保存
|
/// 保存
|
||||||
Future<void> save() async {
|
Future<void> save() async {
|
||||||
if (Platform.isIOS) {
|
// 显示插页广告
|
||||||
// 显示插页广告
|
InterstitialAdManager().showAdIfAvailable(
|
||||||
InterstitialAdManager().showAdIfAvailable(
|
onTap: () async {
|
||||||
onTap: () async {
|
// 停止播放
|
||||||
// 停止播放
|
if (playerController.isPlaying.value) playerController.stopPlay();
|
||||||
if (playerController.isPlaying.value) playerController.stopPlay();
|
BaseEasyLoading.loading();
|
||||||
BaseEasyLoading.loading();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 若是assets路径,转换为文件路径
|
// 若是assets路径,转换为文件路径
|
||||||
if (filePath.contains('assets')) {
|
if (filePath.contains('assets')) {
|
||||||
filePath = await FileUtil.getAssetsToFilePath(filePath);
|
filePath = await FileUtil.getAssetsToFilePath(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 输出目录
|
||||||
|
final outputDir = await LocalPathUtil.getVoiceChangeOutputDir();
|
||||||
|
String fileName = '${DateUtil.getNowTimeStr().replaceAll(' ', '_')}_output.mp3';
|
||||||
|
String outputPath = '${outputDir.path}/$fileName';
|
||||||
|
|
||||||
|
var filter = "";
|
||||||
|
// var timber = timberList.firstWhereOrNull((e) => e.check);
|
||||||
|
// if (timber != null) {
|
||||||
|
// int index = timberList.indexOf(timber);
|
||||||
|
// if (index == 5) filter = ",afftdn=nf=-30";
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (currentIndex.value == 5) filter = ",afftdn=nf=-30,aecho=0.8:0.88:60:0.4";
|
||||||
|
|
||||||
|
// 获取原始采样率
|
||||||
|
String sampleRate = await _getSampleRate() ?? '24000';
|
||||||
|
|
||||||
|
// 构建 FFmpeg 命令
|
||||||
|
final String command = '-i $filePath -af "asetrate=$sampleRate*${toneValue.value},atempo=${soundSpeedValue.value}$filter" $outputPath';
|
||||||
|
|
||||||
|
// 执行 FFmpeg 命令
|
||||||
|
FFmpegSession session = await FFmpegKit.execute(command);
|
||||||
|
|
||||||
|
// 获取执行结果
|
||||||
|
final returnCode = await session.getReturnCode();
|
||||||
|
if (ReturnCode.isSuccess(returnCode)) {
|
||||||
|
LogUtil.d('Audio processing successful');
|
||||||
|
try {
|
||||||
|
await MyVoiceBox().addData(VoiceModel(name: fileName, path: outputPath));
|
||||||
|
BaseEasyLoading.toast('Save successful');
|
||||||
|
|
||||||
|
// 回到首页-我的页面
|
||||||
|
Get.until((route) => route.settings.name == AppRoutes.initialA);
|
||||||
|
InitialController.to.onBottomAppBarItemChanged(2);
|
||||||
|
} catch (e) {
|
||||||
|
BaseEasyLoading.toast('Save failed');
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
// 输出目录
|
LogUtil.d('Audio processing failed');
|
||||||
final outputDir = await LocalPathUtil.getVoiceChangeOutputDir();
|
|
||||||
String fileName = '${DateUtil.getNowTimeStr().replaceAll(' ', '_')}_output.mp3';
|
|
||||||
String outputPath = '${outputDir.path}/$fileName';
|
|
||||||
|
|
||||||
var filter = "";
|
|
||||||
// var timber = timberList.firstWhereOrNull((e) => e.check);
|
|
||||||
// if (timber != null) {
|
|
||||||
// int index = timberList.indexOf(timber);
|
|
||||||
// if (index == 5) filter = ",afftdn=nf=-30";
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (currentIndex.value == 5) filter = ",afftdn=nf=-30,aecho=0.8:0.88:60:0.4";
|
|
||||||
|
|
||||||
// 获取原始采样率
|
|
||||||
String sampleRate = await _getSampleRate() ?? '24000';
|
|
||||||
|
|
||||||
// 构建 FFmpeg 命令
|
|
||||||
final String command = '-i $filePath -af "asetrate=$sampleRate*${toneValue.value},atempo=${soundSpeedValue.value}$filter" $outputPath';
|
|
||||||
|
|
||||||
// 执行 FFmpeg 命令
|
|
||||||
FFmpegSession session = await FFmpegKit.execute(command);
|
|
||||||
|
|
||||||
// 获取执行结果
|
|
||||||
final returnCode = await session.getReturnCode();
|
|
||||||
if (ReturnCode.isSuccess(returnCode)) {
|
|
||||||
LogUtil.d('Audio processing successful');
|
|
||||||
try {
|
|
||||||
await MyVoiceBox().addData(VoiceModel(name: fileName, path: outputPath));
|
|
||||||
BaseEasyLoading.toast('Save successful');
|
|
||||||
|
|
||||||
// 回到首页-我的页面
|
|
||||||
Get.until((route) => route.settings.name == AppRoutes.initialA);
|
|
||||||
InitialController.to.onBottomAppBarItemChanged(2);
|
|
||||||
} catch (e) {
|
|
||||||
BaseEasyLoading.toast('Save failed');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
LogUtil.d('Audio processing failed');
|
|
||||||
BaseEasyLoading.toast('Audio processing failed');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
BaseEasyLoading.toast('Audio processing failed');
|
BaseEasyLoading.toast('Audio processing failed');
|
||||||
}
|
}
|
||||||
},
|
} catch (e) {
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// 停止播放
|
|
||||||
if (playerController.isPlaying.value) playerController.stopPlay();
|
|
||||||
BaseEasyLoading.loading();
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 若是assets路径,转换为文件路径
|
|
||||||
if (filePath.contains('assets')) {
|
|
||||||
filePath = await FileUtil.getAssetsToFilePath(filePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 输出目录
|
|
||||||
final outputDir = await LocalPathUtil.getVoiceChangeOutputDir();
|
|
||||||
String fileName = '${DateUtil.getNowTimeStr().replaceAll(' ', '_')}_output.mp3';
|
|
||||||
String outputPath = '${outputDir.path}/$fileName';
|
|
||||||
|
|
||||||
var filter = "";
|
|
||||||
// var timber = timberList.firstWhereOrNull((e) => e.check);
|
|
||||||
// if (timber != null) {
|
|
||||||
// int index = timberList.indexOf(timber);
|
|
||||||
// if (index == 5) filter = ",afftdn=nf=-30";
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (currentIndex.value == 5) filter = ",afftdn=nf=-30,aecho=0.8:0.88:60:0.4";
|
|
||||||
|
|
||||||
// 获取原始采样率
|
|
||||||
String sampleRate = await _getSampleRate() ?? '24000';
|
|
||||||
|
|
||||||
// 构建 FFmpeg 命令
|
|
||||||
final String command = '-i $filePath -af "asetrate=$sampleRate*${toneValue.value},atempo=${soundSpeedValue.value}$filter" $outputPath';
|
|
||||||
|
|
||||||
// 执行 FFmpeg 命令
|
|
||||||
FFmpegSession session = await FFmpegKit.execute(command);
|
|
||||||
|
|
||||||
// 获取执行结果
|
|
||||||
final returnCode = await session.getReturnCode();
|
|
||||||
if (ReturnCode.isSuccess(returnCode)) {
|
|
||||||
LogUtil.d('Audio processing successful');
|
|
||||||
try {
|
|
||||||
await MyVoiceBox().addData(VoiceModel(name: fileName, path: outputPath));
|
|
||||||
BaseEasyLoading.toast('Save successful');
|
|
||||||
|
|
||||||
// 回到首页-我的页面
|
|
||||||
Get.until((route) => route.settings.name == AppRoutes.initialA);
|
|
||||||
InitialController.to.onBottomAppBarItemChanged(2);
|
|
||||||
} catch (e) {
|
|
||||||
BaseEasyLoading.toast('Save failed');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
LogUtil.d('Audio processing failed');
|
|
||||||
BaseEasyLoading.toast('Audio processing failed');
|
BaseEasyLoading.toast('Audio processing failed');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
},
|
||||||
BaseEasyLoading.toast('Audio processing failed');
|
);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 获取音频的采样率
|
/// 获取音频的采样率
|
||||||
|
|||||||
@ -57,7 +57,6 @@ class FavouriteController extends GetxController {
|
|||||||
|
|
||||||
void onTapDelete(VoiceModel item) {
|
void onTapDelete(VoiceModel item) {
|
||||||
Get.dialog(
|
Get.dialog(
|
||||||
barrierDismissible: false,
|
|
||||||
RemindDialog(
|
RemindDialog(
|
||||||
content: 'Are you sure to delete it?',
|
content: 'Are you sure to delete it?',
|
||||||
confirmOnTap: () async {
|
confirmOnTap: () async {
|
||||||
|
|||||||
@ -1,9 +0,0 @@
|
|||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:tone_snap/modules/sidea/home/home_controller.dart';
|
|
||||||
|
|
||||||
class HomeBinding extends Bindings {
|
|
||||||
@override
|
|
||||||
void dependencies() {
|
|
||||||
Get.lazyPut(() => HomeController());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -14,7 +14,8 @@ class HomeView extends GetView<HomeController> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Get.find<HomeController>();
|
Get.put(HomeController());
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
HeadLabel(
|
HeadLabel(
|
||||||
@ -71,7 +72,7 @@ class HomeView extends GetView<HomeController> {
|
|||||||
clipBehavior: Clip.none,
|
clipBehavior: Clip.none,
|
||||||
children: [
|
children: [
|
||||||
Visibility(
|
Visibility(
|
||||||
visible: ObjUtil.isNotEmptyStr(item.cover),
|
visible: ObjUtil.isNotEmpty(item.cover),
|
||||||
child: ClipPath(
|
child: ClipPath(
|
||||||
clipper: CircularNotchClipper(
|
clipper: CircularNotchClipper(
|
||||||
notchRadius: 20,
|
notchRadius: 20,
|
||||||
|
|||||||
@ -1,12 +1,10 @@
|
|||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:tone_snap/data/models/voice_model.dart';
|
import 'package:tone_snap/data/models/voice_model.dart';
|
||||||
import 'package:tone_snap/data/storage/favorite_box.dart';
|
import 'package:tone_snap/data/storage/favorite_box.dart';
|
||||||
import 'package:tone_snap/firebase/firebase_analytics_manager.dart';
|
import 'package:tone_snap/firebase/firebase_analytics_manager.dart';
|
||||||
import 'package:tone_snap/generated/assets.dart';
|
import 'package:tone_snap/generated/assets.dart';
|
||||||
import 'package:tone_snap/global/app_lifecycle_reactor.dart';
|
import 'package:tone_snap/global/app_tracking_transparency_manager.dart';
|
||||||
import 'package:tone_snap/modules/sidea/controllers/player_controller.dart';
|
import 'package:tone_snap/modules/sidea/controllers/player_controller.dart';
|
||||||
import 'package:tone_snap/modules/sidea/favourite/favourite_controller.dart';
|
import 'package:tone_snap/modules/sidea/favourite/favourite_controller.dart';
|
||||||
import 'package:tone_snap/modules/sidea/home/home_view.dart';
|
import 'package:tone_snap/modules/sidea/home/home_view.dart';
|
||||||
@ -28,25 +26,20 @@ class InitialController extends GetxController {
|
|||||||
var currentIndex = 0.obs;
|
var currentIndex = 0.obs;
|
||||||
Rx<VoiceModel?> currentPlayVoiceModel = Rx<VoiceModel?>(null);
|
Rx<VoiceModel?> currentPlayVoiceModel = Rx<VoiceModel?>(null);
|
||||||
|
|
||||||
/// 是否加入喜欢列表
|
/// 是否收藏
|
||||||
var isFavourite = false.obs;
|
var isFavourite = false.obs;
|
||||||
|
|
||||||
late AppLifecycleReactor _appLifecycleReactor;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
// _appLifecycleReactor = AppLifecycleReactor();
|
AppTrackingTransparencyManager().requestATT();
|
||||||
// _appLifecycleReactor.listenToAppStateChanges();
|
|
||||||
|
|
||||||
pageController = PageController(initialPage: currentIndex.value);
|
pageController = PageController(initialPage: currentIndex.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onReady() async {
|
void onReady() async {
|
||||||
super.onReady();
|
super.onReady();
|
||||||
// await FirebaseAnalyticsManager.setCrashlyticsCollectionEnabled();
|
_addEventLog();
|
||||||
// _addEventLog();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -60,9 +53,9 @@ class InitialController extends GetxController {
|
|||||||
await PlayerController.to.stopPlay();
|
await PlayerController.to.stopPlay();
|
||||||
Get.toNamed(AppRoutes.uploadMethod);
|
Get.toNamed(AppRoutes.uploadMethod);
|
||||||
} else {
|
} else {
|
||||||
// if (index == 0) {
|
if (index == 0) {
|
||||||
// _addEventLog();
|
_addEventLog();
|
||||||
// }
|
}
|
||||||
if (index == 2) _refreshMe();
|
if (index == 2) _refreshMe();
|
||||||
currentIndex.value = index;
|
currentIndex.value = index;
|
||||||
pageController.jumpToPage(index);
|
pageController.jumpToPage(index);
|
||||||
@ -105,7 +98,7 @@ class InitialController extends GetxController {
|
|||||||
|
|
||||||
/// 埋点
|
/// 埋点
|
||||||
void _addEventLog() {
|
void _addEventLog() {
|
||||||
FirebaseAnalyticsManager.logEvent('home_a_pv');
|
FirebaseAnalyticsManager.logEvent(FirebaseAnalyticsManager.homeApv);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +0,0 @@
|
|||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:tone_snap/modules/sidea/me/me_controller.dart';
|
|
||||||
|
|
||||||
class MeBinding extends Bindings {
|
|
||||||
@override
|
|
||||||
void dependencies() {
|
|
||||||
Get.lazyPut(() => MeController());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -12,7 +12,8 @@ class MeView extends GetView<MeController> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Get.find<MeController>();
|
Get.put(MeController());
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
|||||||