diff --git a/relax.offline.mp3.music.xcodeproj/project.pbxproj b/relax.offline.mp3.music.xcodeproj/project.pbxproj index 0fa6dcb..b0212e7 100644 --- a/relax.offline.mp3.music.xcodeproj/project.pbxproj +++ b/relax.offline.mp3.music.xcodeproj/project.pbxproj @@ -14,6 +14,7 @@ CB0033F62C295E3100B18FD3 /* MPPositive_MoreOperationShowTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB0033F52C295E3100B18FD3 /* MPPositive_MoreOperationShowTableViewCell.swift */; }; CB0033F82C29626900B18FD3 /* MPPositive_ChoosePlayListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB0033F72C29626900B18FD3 /* MPPositive_ChoosePlayListViewController.swift */; }; CB0033FC2C29753D00B18FD3 /* MPPositive_PlayListMoreViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB0033FB2C29753D00B18FD3 /* MPPositive_PlayListMoreViewController.swift */; }; + CB091A202D1A5B070015BF62 /* MPPositive_DefaultTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB091A1F2D1A5B070015BF62 /* MPPositive_DefaultTableView.swift */; }; CB0968752C2121410045E55B /* GADTSmallTemplateView.m in Sources */ = {isa = PBXBuildFile; fileRef = CB09686C2C2121410045E55B /* GADTSmallTemplateView.m */; }; CB0968762C2121410045E55B /* GADTSmallTemplateView.xib in Resources */ = {isa = PBXBuildFile; fileRef = CB09686D2C2121410045E55B /* GADTSmallTemplateView.xib */; }; CB0968772C2121410045E55B /* GADTTemplateView.m in Sources */ = {isa = PBXBuildFile; fileRef = CB09686F2C2121410045E55B /* GADTTemplateView.m */; }; @@ -37,6 +38,7 @@ CB51340E2C9C1E4800833AD5 /* MP_ADSimpleManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB51340D2C9C1E4800833AD5 /* MP_ADSimpleManager.swift */; }; CB6EEB8E2C5DFE6100AEC414 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CB6EEB8D2C5DFE6100AEC414 /* StoreKit.framework */; }; CB7FC5482C2AC25C00292A43 /* MPPositive_CenterListSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB7FC5472C2AC25C00292A43 /* MPPositive_CenterListSearchView.swift */; }; + CB8D21982D196F51001D8DB8 /* MPPositive_BatchDeletionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB8D21972D196F51001D8DB8 /* MPPositive_BatchDeletionViewController.swift */; }; CBAFCAE62C0A10500054500E /* MP_BaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBAFC9F22C0A10500054500E /* MP_BaseViewController.swift */; }; CBAFCAE72C0A10500054500E /* MP_LunchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBAFC9F32C0A10500054500E /* MP_LunchViewController.swift */; }; CBAFCAE82C0A10500054500E /* MP_LunchViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = CBAFC9F42C0A10500054500E /* MP_LunchViewController.xib */; }; @@ -238,9 +240,6 @@ CBB720542C6A040000D1B504 /* MP_IAPViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = CBB720532C6A040000D1B504 /* MP_IAPViewController.xib */; }; CBB75FDD2C4F7AA60041665D /* UIImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBB75FDC2C4F7AA60041665D /* UIImageView.swift */; }; CBBAF8CD2C339CF200B3C838 /* MPPositive_JsonCharts.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBBAF8CC2C339CF200B3C838 /* MPPositive_JsonCharts.swift */; }; - CBBE53382CF065800036D2D9 /* FacebookAEM in Frameworks */ = {isa = PBXBuildFile; productRef = CBBE53372CF065800036D2D9 /* FacebookAEM */; }; - CBBE533A2CF065800036D2D9 /* FacebookBasics in Frameworks */ = {isa = PBXBuildFile; productRef = CBBE53392CF065800036D2D9 /* FacebookBasics */; }; - CBBE533C2CF065800036D2D9 /* FacebookCore in Frameworks */ = {isa = PBXBuildFile; productRef = CBBE533B2CF065800036D2D9 /* FacebookCore */; }; CBBFD8212C8018D100BD67BC /* Reload_Animation.json in Resources */ = {isa = PBXBuildFile; fileRef = CBBFD8202C8018D100BD67BC /* Reload_Animation.json */; }; CBC1FB7A2C50999800AC0633 /* MPPositive_LibraryItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBC1FB792C50999800AC0633 /* MPPositive_LibraryItemModel.swift */; }; CBC1FB7C2C509B7300AC0633 /* MPPositive_LibraryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBC1FB7B2C509B7300AC0633 /* MPPositive_LibraryViewModel.swift */; }; @@ -271,6 +270,9 @@ CBDBDDF42C40D03F00767F0B /* MPPositive_SearchGrideCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBDBDDF32C40D03F00767F0B /* MPPositive_SearchGrideCollectionViewCell.swift */; }; CBDBDDF62C40FFC600767F0B /* MPPositive_GrideMoodViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBDBDDF52C40FFC600767F0B /* MPPositive_GrideMoodViewController.swift */; }; CBDC4A292C61B88300960649 /* relax.offline.mp3.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = CBDC4A272C61B88300960649 /* relax.offline.mp3.xcdatamodeld */; }; + CBDFD6C02D153B6400009948 /* FacebookAEM in Frameworks */ = {isa = PBXBuildFile; productRef = CBDFD6BF2D153B6400009948 /* FacebookAEM */; }; + CBDFD6C22D153B6400009948 /* FacebookBasics in Frameworks */ = {isa = PBXBuildFile; productRef = CBDFD6C12D153B6400009948 /* FacebookBasics */; }; + CBDFD6C42D153B6400009948 /* FacebookCore in Frameworks */ = {isa = PBXBuildFile; productRef = CBDFD6C32D153B6400009948 /* FacebookCore */; }; CBEC78BF2CA28FB700666A7F /* MPPositive_AddSongsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBEC78BE2CA28FB700666A7F /* MPPositive_AddSongsViewController.swift */; }; CBEC78C12CA2963100666A7F /* MPPositive_AddSongsShowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBEC78C02CA2963100666A7F /* MPPositive_AddSongsShowView.swift */; }; CBEC78C32CA2A0CB00666A7F /* MPPositive_AddSongTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBEC78C22CA2A0CB00666A7F /* MPPositive_AddSongTableViewCell.swift */; }; @@ -288,6 +290,7 @@ CB0033F52C295E3100B18FD3 /* MPPositive_MoreOperationShowTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_MoreOperationShowTableViewCell.swift; sourceTree = ""; }; CB0033F72C29626900B18FD3 /* MPPositive_ChoosePlayListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_ChoosePlayListViewController.swift; sourceTree = ""; }; CB0033FB2C29753D00B18FD3 /* MPPositive_PlayListMoreViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_PlayListMoreViewController.swift; sourceTree = ""; }; + CB091A1F2D1A5B070015BF62 /* MPPositive_DefaultTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_DefaultTableView.swift; sourceTree = ""; }; CB09686B2C2121410045E55B /* GADTSmallTemplateView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GADTSmallTemplateView.h; sourceTree = ""; }; CB09686C2C2121410045E55B /* GADTSmallTemplateView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GADTSmallTemplateView.m; sourceTree = ""; }; CB09686D2C2121410045E55B /* GADTSmallTemplateView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = GADTSmallTemplateView.xib; sourceTree = ""; }; @@ -320,6 +323,7 @@ CB51340D2C9C1E4800833AD5 /* MP_ADSimpleManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MP_ADSimpleManager.swift; sourceTree = ""; }; CB6EEB8D2C5DFE6100AEC414 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; }; CB7FC5472C2AC25C00292A43 /* MPPositive_CenterListSearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_CenterListSearchView.swift; sourceTree = ""; }; + CB8D21972D196F51001D8DB8 /* MPPositive_BatchDeletionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_BatchDeletionViewController.swift; sourceTree = ""; }; CBAFC9F22C0A10500054500E /* MP_BaseViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MP_BaseViewController.swift; sourceTree = ""; }; CBAFC9F32C0A10500054500E /* MP_LunchViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MP_LunchViewController.swift; sourceTree = ""; }; CBAFC9F42C0A10500054500E /* MP_LunchViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MP_LunchViewController.xib; sourceTree = ""; }; @@ -583,9 +587,9 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - CBBE533A2CF065800036D2D9 /* FacebookBasics in Frameworks */, - CBBE533C2CF065800036D2D9 /* FacebookCore in Frameworks */, - CBBE53382CF065800036D2D9 /* FacebookAEM in Frameworks */, + CBDFD6C22D153B6400009948 /* FacebookBasics in Frameworks */, + CBDFD6C42D153B6400009948 /* FacebookCore in Frameworks */, + CBDFD6C02D153B6400009948 /* FacebookAEM in Frameworks */, CBAFCBAD2C0A10DA0054500E /* FirebaseCrashlytics in Frameworks */, CBD4570D2C2EC38400CE766D /* AppTrackingTransparency.framework in Frameworks */, CBAFCBAB2C0A10DA0054500E /* FirebaseAnalytics in Frameworks */, @@ -937,6 +941,7 @@ CBAFCA682C0A10500054500E /* MPPositive_LoveSongsViewController.swift */, CBAFCA672C0A10500054500E /* MPPositive_LoveArtistsViewController.swift */, CBAFCA692C0A10500054500E /* MPPositive_OfflineSongsViewController.swift */, + CB8D21972D196F51001D8DB8 /* MPPositive_BatchDeletionViewController.swift */, ); path = "Center(个人曲库页)"; sourceTree = ""; @@ -991,6 +996,7 @@ CBAFCA7B2C0A10500054500E /* MPPositive_CustomTabBarView.swift */, CBAFCA7A2C0A10500054500E /* MPPositive_CustomTabBarItem.swift */, CBAFCA782C0A10500054500E /* MPPositive_BottomShowView.swift */, + CB091A1F2D1A5B070015BF62 /* MPPositive_DefaultTableView.swift */, CBAFCA7C2C0A10500054500E /* MPPositive_MoreOperationDownLoadTableViewCell.swift */, CB0033F52C295E3100B18FD3 /* MPPositive_MoreOperationShowTableViewCell.swift */, CB0B36902C65EBFC004036E2 /* MPPositive_BaseShowView.swift */, @@ -1323,9 +1329,9 @@ CBAFCBAC2C0A10DA0054500E /* FirebaseCrashlytics */, CBAFCBAE2C0A10DA0054500E /* FirebaseRemoteConfig */, CB0B368A2C65AE3A004036E2 /* Lottie */, - CBBE53372CF065800036D2D9 /* FacebookAEM */, - CBBE53392CF065800036D2D9 /* FacebookBasics */, - CBBE533B2CF065800036D2D9 /* FacebookCore */, + CBDFD6BF2D153B6400009948 /* FacebookAEM */, + CBDFD6C12D153B6400009948 /* FacebookBasics */, + CBDFD6C32D153B6400009948 /* FacebookCore */, ); productName = relax.offline.mp3.music; productReference = CBC2D6E82BFDF3D700E17703 /* relax.offline.mp3.music.app */; @@ -1365,7 +1371,7 @@ packageReferences = ( CBAFCBA92C0A10DA0054500E /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */, CB0B36892C65AE3A004036E2 /* XCRemoteSwiftPackageReference "lottie-spm" */, - CBBE53362CF065800036D2D9 /* XCRemoteSwiftPackageReference "facebook-ios-sdk" */, + CBDFD6BE2D153B6400009948 /* XCRemoteSwiftPackageReference "facebook-ios-sdk" */, ); productRefGroup = CBC2D6E92BFDF3D700E17703 /* Products */; projectDirPath = ""; @@ -1528,6 +1534,7 @@ CB1182932CA8EF5A00BAEDC0 /* MP_DownloadButton.swift in Sources */, CBD344DE2C3FD8230095F18F /* MPPositive_GridViewModel.swift in Sources */, CBAFCB622C0A10500054500E /* MPPositive_HomeListFirstCollectionViewCell.swift in Sources */, + CB091A202D1A5B070015BF62 /* MPPositive_DefaultTableView.swift in Sources */, CBC5E51D2C7D82A200336746 /* MPPositive_RecentlyModel.swift in Sources */, CBAFCB112C0A10500054500E /* MP_PlayerManager.swift in Sources */, CBDC4A292C61B88300960649 /* relax.offline.mp3.xcdatamodeld in Sources */, @@ -1538,6 +1545,7 @@ CBAFCB782C0A10500054500E /* MPSideA_MusicViewModel.swift in Sources */, CBAFCB642C0A10500054500E /* MPPositive_HomeListSecondCollectionViewCell.swift in Sources */, CBAFCB0A2C0A10500054500E /* MP_CacheManager.swift in Sources */, + CB8D21982D196F51001D8DB8 /* MPPositive_BatchDeletionViewController.swift in Sources */, CBAFCB892C0A10500054500E /* MPSideA_RenameViewController.swift in Sources */, CBAFCB632C0A10500054500E /* MPPositive_HomeListFourthCollectionViewCell.swift in Sources */, CB1E3B682C23E09100071DEA /* MPPositive_CustomVideoModel.swift in Sources */, @@ -2050,12 +2058,12 @@ minimumVersion = 10.27.0; }; }; - CBBE53362CF065800036D2D9 /* XCRemoteSwiftPackageReference "facebook-ios-sdk" */ = { + CBDFD6BE2D153B6400009948 /* XCRemoteSwiftPackageReference "facebook-ios-sdk" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/facebook/facebook-ios-sdk"; requirement = { - kind = exactVersion; - version = 17.1.0; + kind = upToNextMajorVersion; + minimumVersion = 14.1.0; }; }; /* End XCRemoteSwiftPackageReference section */ @@ -2081,19 +2089,19 @@ package = CBAFCBA92C0A10DA0054500E /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; productName = FirebaseRemoteConfig; }; - CBBE53372CF065800036D2D9 /* FacebookAEM */ = { + CBDFD6BF2D153B6400009948 /* FacebookAEM */ = { isa = XCSwiftPackageProductDependency; - package = CBBE53362CF065800036D2D9 /* XCRemoteSwiftPackageReference "facebook-ios-sdk" */; + package = CBDFD6BE2D153B6400009948 /* XCRemoteSwiftPackageReference "facebook-ios-sdk" */; productName = FacebookAEM; }; - CBBE53392CF065800036D2D9 /* FacebookBasics */ = { + CBDFD6C12D153B6400009948 /* FacebookBasics */ = { isa = XCSwiftPackageProductDependency; - package = CBBE53362CF065800036D2D9 /* XCRemoteSwiftPackageReference "facebook-ios-sdk" */; + package = CBDFD6BE2D153B6400009948 /* XCRemoteSwiftPackageReference "facebook-ios-sdk" */; productName = FacebookBasics; }; - CBBE533B2CF065800036D2D9 /* FacebookCore */ = { + CBDFD6C32D153B6400009948 /* FacebookCore */ = { isa = XCSwiftPackageProductDependency; - package = CBBE53362CF065800036D2D9 /* XCRemoteSwiftPackageReference "facebook-ios-sdk" */; + package = CBDFD6BE2D153B6400009948 /* XCRemoteSwiftPackageReference "facebook-ios-sdk" */; productName = FacebookCore; }; /* End XCSwiftPackageProductDependency section */ diff --git a/relax.offline.mp3.music.xcworkspace/xcshareddata/swiftpm/Package.resolved b/relax.offline.mp3.music.xcworkspace/xcshareddata/swiftpm/Package.resolved index a9cd4b6..4605295 100644 --- a/relax.offline.mp3.music.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/relax.offline.mp3.music.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -24,8 +24,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/facebook/facebook-ios-sdk", "state" : { - "revision" : "3cebc3b1d13dbe85868cc04f5bed63648e8c410c", - "version" : "17.1.0" + "revision" : "c19607d535864533523d1f437c84035e5fb101cf", + "version" : "14.1.0" } }, { diff --git a/relax.offline.mp3.music.xcworkspace/xcuserdata/zhou.xcuserdatad/UserInterfaceState.xcuserstate b/relax.offline.mp3.music.xcworkspace/xcuserdata/zhou.xcuserdatad/UserInterfaceState.xcuserstate index f41e827..8737692 100644 Binary files a/relax.offline.mp3.music.xcworkspace/xcuserdata/zhou.xcuserdatad/UserInterfaceState.xcuserstate and b/relax.offline.mp3.music.xcworkspace/xcuserdata/zhou.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/relax.offline.mp3.music/AppDelegate.swift b/relax.offline.mp3.music/AppDelegate.swift index bc0a7fb..dcbdf91 100644 --- a/relax.offline.mp3.music/AppDelegate.swift +++ b/relax.offline.mp3.music/AppDelegate.swift @@ -34,13 +34,16 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var backgroundTask: UIBackgroundTaskIdentifier = .invalid func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { //请求通知权限 - UNUserNotificationCenter.current() - .requestAuthorization(options: [.alert, .sound, .badge]) { + UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { (accepted, error) in if !accepted { print("Users are not allowed to be notified of messages.") } } + //检查应用冷启动是否由通知触发 + if let notificationData = launchOptions?[.remoteNotification] as? [String: AnyObject] { + MP_AnalyticsManager.shared.handle_notificationAction() + } //切换通知代理 UNUserNotificationCenter.current().delegate = notificationHandler //广告默认ID @@ -61,6 +64,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate { //关联faceBook ApplicationDelegate.shared.application(application,didFinishLaunchingWithOptions: launchOptions) Settings.shared.isAdvertiserIDCollectionEnabled = true + //启用内购项 + MP_IAPManager.shared.observeVIPStoreKit() + MP_IAPManager.shared.getVIPAllProducts() switch_lunch() //执行用户启动事件日志 MP_AnalyticsManager.shared.user_launchAction() @@ -288,13 +294,26 @@ class AppDelegate: UIResponder, UIApplicationDelegate { let accessAppdelegate = ( UIApplication.shared.delegate as! AppDelegate) ///在应用内展示通知 class NotificationHandler: NSObject, UNUserNotificationCenterDelegate { - func userNotificationCenter(_ center: UNUserNotificationCenter, - willPresent notification: UNNotification, - withCompletionHandler completionHandler: - @escaping (UNNotificationPresentationOptions) -> Void) { + //通知展示 + func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { completionHandler([.alert, .sound, .badge]) - - // 如果不想显示某个通知,可以直接用空 options 调用 completionHandler: - // completionHandler([]) + } + //点击通知 + func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { + let content = response.notification.request.content + let userInfo = content.userInfo // 通知附带的用户信息 + switch response.actionIdentifier { + case UNNotificationDefaultActionIdentifier: + // 用户点击了通知本身 + print("用户点击了通知: \(content.title), \(content.body)") + MP_AnalyticsManager.shared.handle_notificationAction() + case UNNotificationDismissActionIdentifier: + // 用户忽略了通知 + print("用户忽略了通知") + default: + // 自定义按钮的动作 + print("用户点击了自定义按钮: \(response.actionIdentifier)") + } + completionHandler() } } diff --git a/relax.offline.mp3.music/Assets.xcassets/Positive/Center/Center_BulkDelete'logo.imageset/Contents.json b/relax.offline.mp3.music/Assets.xcassets/Positive/Center/Center_BulkDelete'logo.imageset/Contents.json new file mode 100644 index 0000000..6b57fb5 --- /dev/null +++ b/relax.offline.mp3.music/Assets.xcassets/Positive/Center/Center_BulkDelete'logo.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "delete@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "delete@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/relax.offline.mp3.music/Assets.xcassets/Positive/Center/Center_BulkDelete'logo.imageset/delete@2x.png b/relax.offline.mp3.music/Assets.xcassets/Positive/Center/Center_BulkDelete'logo.imageset/delete@2x.png new file mode 100644 index 0000000..f35d365 Binary files /dev/null and b/relax.offline.mp3.music/Assets.xcassets/Positive/Center/Center_BulkDelete'logo.imageset/delete@2x.png differ diff --git a/relax.offline.mp3.music/Assets.xcassets/Positive/Center/Center_BulkDelete'logo.imageset/delete@3x.png b/relax.offline.mp3.music/Assets.xcassets/Positive/Center/Center_BulkDelete'logo.imageset/delete@3x.png new file mode 100644 index 0000000..c01af8f Binary files /dev/null and b/relax.offline.mp3.music/Assets.xcassets/Positive/Center/Center_BulkDelete'logo.imageset/delete@3x.png differ diff --git a/relax.offline.mp3.music/Assets.xcassets/SideA/Center/Restore'logo.imageset/Contents.json b/relax.offline.mp3.music/Assets.xcassets/SideA/Center/Restore'logo.imageset/Contents.json new file mode 100644 index 0000000..c8d4192 --- /dev/null +++ b/relax.offline.mp3.music/Assets.xcassets/SideA/Center/Restore'logo.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Restor@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Restor@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/relax.offline.mp3.music/Assets.xcassets/SideA/Center/Restore'logo.imageset/Restor@2x.png b/relax.offline.mp3.music/Assets.xcassets/SideA/Center/Restore'logo.imageset/Restor@2x.png new file mode 100644 index 0000000..81fe45d Binary files /dev/null and b/relax.offline.mp3.music/Assets.xcassets/SideA/Center/Restore'logo.imageset/Restor@2x.png differ diff --git a/relax.offline.mp3.music/Assets.xcassets/SideA/Center/Restore'logo.imageset/Restor@3x.png b/relax.offline.mp3.music/Assets.xcassets/SideA/Center/Restore'logo.imageset/Restor@3x.png new file mode 100644 index 0000000..9828f4d Binary files /dev/null and b/relax.offline.mp3.music/Assets.xcassets/SideA/Center/Restore'logo.imageset/Restor@3x.png differ diff --git a/relax.offline.mp3.music/Assets.xcassets/SideA/Center/VIPSet'logo.imageset/Contents.json b/relax.offline.mp3.music/Assets.xcassets/SideA/Center/VIPSet'logo.imageset/Contents.json new file mode 100644 index 0000000..a9d72f8 --- /dev/null +++ b/relax.offline.mp3.music/Assets.xcassets/SideA/Center/VIPSet'logo.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Upgrade@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Upgrade@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/relax.offline.mp3.music/Assets.xcassets/SideA/Center/VIPSet'logo.imageset/Upgrade@2x.png b/relax.offline.mp3.music/Assets.xcassets/SideA/Center/VIPSet'logo.imageset/Upgrade@2x.png new file mode 100644 index 0000000..2d7d65e Binary files /dev/null and b/relax.offline.mp3.music/Assets.xcassets/SideA/Center/VIPSet'logo.imageset/Upgrade@2x.png differ diff --git a/relax.offline.mp3.music/Assets.xcassets/SideA/Center/VIPSet'logo.imageset/Upgrade@3x.png b/relax.offline.mp3.music/Assets.xcassets/SideA/Center/VIPSet'logo.imageset/Upgrade@3x.png new file mode 100644 index 0000000..b84570a Binary files /dev/null and b/relax.offline.mp3.music/Assets.xcassets/SideA/Center/VIPSet'logo.imageset/Upgrade@3x.png differ diff --git a/relax.offline.mp3.music/MP/Common/Base(公用基类)/Controllers/MP_LunchViewController.swift b/relax.offline.mp3.music/MP/Common/Base(公用基类)/Controllers/MP_LunchViewController.swift index 690ef06..ccfc0c7 100644 --- a/relax.offline.mp3.music/MP/Common/Base(公用基类)/Controllers/MP_LunchViewController.swift +++ b/relax.offline.mp3.music/MP/Common/Base(公用基类)/Controllers/MP_LunchViewController.swift @@ -116,9 +116,6 @@ class MP_LunchViewController: UIViewController { private func switchAOrBAction() { //首先检测之前是否进入过B面 guard UserDefaults.standard.bool(forKey: "MP_Into_B") != true else { - //启用内购项 - MP_IAPManager.shared.observeVIPStoreKit() - MP_IAPManager.shared.getVIPAllProducts() loadAds() //更新开关以及响应的数据 MP_AnalyticsManager.shared.getOpenStatus(0) { open in @@ -156,9 +153,6 @@ class MP_LunchViewController: UIViewController { MP_NetWorkManager.shared.performTaskNetWrokAvailable { [weak self] in guard let self = self else {return} - //启用内购项 - MP_IAPManager.shared.observeVIPStoreKit() - MP_IAPManager.shared.getVIPAllProducts() //广告加载 loadAds() //进行开关检测 diff --git a/relax.offline.mp3.music/MP/Common/Tool(工具封装)/MP_ADSimpleManager.swift b/relax.offline.mp3.music/MP/Common/Tool(工具封装)/MP_ADSimpleManager.swift index 6a7754e..558b2bd 100644 --- a/relax.offline.mp3.music/MP/Common/Tool(工具封装)/MP_ADSimpleManager.swift +++ b/relax.offline.mp3.music/MP/Common/Tool(工具封装)/MP_ADSimpleManager.swift @@ -59,7 +59,7 @@ class MP_ADSimpleManager: NSObject { } } ///内部使用广告开光 - var internalAdStatus:Bool = true + var internalAdStatus:Bool = false ///设置广告总开关 func setOpenAdStatus(_ bool:Bool) { DispatchQueue.main.async { diff --git a/relax.offline.mp3.music/MP/Common/Tool(工具封装)/MP_AnalyticsManager.swift b/relax.offline.mp3.music/MP/Common/Tool(工具封装)/MP_AnalyticsManager.swift index 34ae1f7..140ec37 100644 --- a/relax.offline.mp3.music/MP/Common/Tool(工具封装)/MP_AnalyticsManager.swift +++ b/relax.offline.mp3.music/MP/Common/Tool(工具封装)/MP_AnalyticsManager.swift @@ -107,6 +107,8 @@ class MP_AnalyticsManager: NSObject { private let update_reminder_cancel = "update_reminder_cancel" ///确认更新 private let update_reminder_sure = "update_reminder_sure" + ///用户点击了通知 + private let handle_notification = "handle_notification" private override init() { //获取用户是新用户还是老用户 if UserDefaults.standard.bool(forKey: "UserStatus") { @@ -333,9 +335,19 @@ class MP_AnalyticsManager: NSObject { func config_success_eventAction() { Analytics.logEvent(config_success_event, parameters: ["USER_STATU":isOLD ? "Old":"New"]) } - ///启动页进度结束时间 + + /// 启动页进度结束时间 + /// - Parameters: + /// - showAd: 是否展示广告 func launch_progress_endAction(_ showAd:String) { - Analytics.logEvent(launch_progress_end, parameters: ["USER_STATU":isOLD ? "Old":"New","showAd":showAd]) + var statu:String = "" + if MP_NetWorkManager.shared.netWorkStatu == .reachable { + //网络可用 + statu = "Yes" + }else { + statu = "No" + } + Analytics.logEvent(launch_progress_end, parameters: ["USER_STATU":isOLD ? "Old":"New","showAd":showAd, "network":statu]) } ///跳转事件 func jump_eventAction(_ side:String, reason:String) { @@ -588,6 +600,10 @@ class MP_AnalyticsManager: NSObject { func update_reminder_cancelAction() { Analytics.logEvent(update_reminder_cancel, parameters: ["USER_STATU":isOLD ? "Old":"New"]) } + ///用户点击了通知 + func handle_notificationAction() { + Analytics.logEvent(handle_notification, parameters: nil) + } //MARK: - 广告埋点事件 //冷启动展示机会 private let cold_ads_chance:String = "cold_ads_chance" diff --git a/relax.offline.mp3.music/MP/Common/Tool(工具封装)/MP_IAPManager.swift b/relax.offline.mp3.music/MP/Common/Tool(工具封装)/MP_IAPManager.swift index 495d73b..6b40ef2 100644 --- a/relax.offline.mp3.music/MP/Common/Tool(工具封装)/MP_IAPManager.swift +++ b/relax.offline.mp3.music/MP/Common/Tool(工具封装)/MP_IAPManager.swift @@ -24,9 +24,14 @@ class MP_IAPManager: NSObject { private var showHUD:Bool = false ///是否是VIP var isVIP:Bool { - //获取VIP过期时间 - guard let lastDate = UserDefaults.standard.object(forKey: "PurchaseVIPDate") as? Date, lastDate.timeIntervalSince(Date()) > 0 else { return false } - return true + if let purchasedProducts = UserDefaults.standard.array(forKey: "PurchasedProducts") as? [String], !purchasedProducts.isEmpty { + //存在旧版购买数据,默认为VIP + return true + }else { + //获取VIP过期时间 + guard let lastDate = UserDefaults.standard.object(forKey: "PurchaseVIPDate") as? Date, lastDate.timeIntervalSince(Date()) > 0 else { return false } + return true + } } override init() { @@ -38,8 +43,6 @@ class MP_IAPManager: NSObject { } ///校正内购产品交易情况 func observeVIPStoreKit() { - //检索本地是否存在旧版交易记录 -// guard //清理原有的交易信息组 transactions.removeAll() //执行交易信息组校正处理 @@ -47,6 +50,7 @@ class MP_IAPManager: NSObject { guard let self = self else { return } var productId = "" //检查交易情况 + print("检索交易") purchases.forEach { item in switch item.transaction.transactionState { case .purchased, .restored://交易完成/处理中 @@ -78,6 +82,9 @@ class MP_IAPManager: NSObject { print("当前用户非VIP") } } + if let purchasedProducts = UserDefaults.standard.array(forKey: "PurchasedProducts") as? [String], !purchasedProducts.isEmpty { + restorePurchases(true) + } } ///获取内购产品信息 func getVIPAllProducts() { @@ -108,9 +115,13 @@ class MP_IAPManager: NSObject { } ///用户重新检索交易 - func restorePurchases() { - MP_HUD.loading() - showHUD = true + func restorePurchases(_ isLaunch:Bool = false) { + if isLaunch == true { + showHUD = false + }else { + MP_HUD.loading() + showHUD = true + } self.transactions.removeAll() SwiftyStoreKit.restorePurchases(atomically: false) { [weak self] results in guard let self = self else { return } @@ -128,33 +139,7 @@ class MP_IAPManager: NSObject { } } -// ///启动时检索是否购买了广告 -// func startLunchStatus() { -// //判断是否存在用户收据 -// let receiptURL = Bundle.main.appStoreReceiptURL -// guard let receipt = try? Data(contentsOf: receiptURL!) else { -// // 收据不存在 -// print("没有收据,广告默认开") -// //不能调用AppStore,默认使用广告 -// MP_ADSimpleManager.shared.setOpenAdStatus(true) -// return -// } -// //有收据 -// print("有收据,开始检索本地信息") -// reloadOpenStatus() -// } -// - -// ///更新广告状态 -// func reloadOpenStatus() { -// //更新广告开关状态 -// if isProductPurchased(productId: productIdentifiers[0]) || isProductPurchased(productId: productIdentifiers[1]) || isProductPurchased(productId: productIdentifiers[2]){ -// //设置广告开关为关 -// MP_ADSimpleManager.shared.setOpenAdStatus(false) -// }else { -// MP_ADSimpleManager.shared.setOpenAdStatus(true) -// } -// } + } //MARK: - 交易执行 @@ -226,6 +211,11 @@ extension MP_IAPManager { self.showFailedHUD() } } + //清理旧版信息 + if let purchasedProducts = UserDefaults.standard.array(forKey: "PurchasedProducts") as? [String], !purchasedProducts.isEmpty { + print("清理旧版VIP信息") + UserDefaults.standard.set(nil, forKey: "PurchasedProducts") + } case .error(let error)://验证失败 self.finishedAllTransactionsVIP() self.showFailedHUD() @@ -261,255 +251,4 @@ extension MP_IAPManager { return isSandbox } } - -//MARK: - SKProductsRequestDelegate -//extension MP_IAPManager: SKProductsRequestDelegate, SKPaymentTransactionObserver { -// ///产品请求回调执行 -// func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) { -// //获得可用产品 -// availableProducts = response.products -// //判空 -// guard availableProducts.isEmpty == false else { -// //无可用产品 -// print("无可用产品") -// return -// } -// //产品可用,检索产品内容 -// for (index, item) in availableProducts.enumerated() { -// print("第\(index)号产品--\(item)") -// } -// } -// ///产品请求失败 -// func request(_ request: SKRequest, didFailWithError error: any Error) { -// print("Failed to fetch products: \(error.localizedDescription)") -// } -// ///当交易的状况发生变化后 -// func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) { -// //遍历交易项 -// transactions.forEach { item in -// //检索每笔交易的情况 -// switch item.transactionState { -// case .purchasing://交易中 -// break -// case .purchased://用户已经付款了 -// complete(transaction: item) -// case .failed://交易失败了 -// fail(transaction: item) -// case .restored://交易重置 -// restore(transaction: item) -// case .deferred://等待外部操作 -// break -// @unknown default: -// break -// } -// } -// } -// ///重置交易状况 -// func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) { -// print("已重启交易") -// MP_HUD.hideNow() -// //确定用户是否购买过订单 -// if queue.transactions.isEmpty { -// //用户ID没有购买过任何与当前产品有关的交易 -// print("没有购买产品/交易已过期") -// productIdentifiers.forEach { item in -// cleanPurchase(productId: item) -// } -// MP_ADSimpleManager.shared.setOpenAdStatus(true) -// } -// } -// func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: any Error) { -// print("重启交易失败") -// MP_HUD.error("The current transaction failed".localizableString(), delay: 1.0, completion: nil) -// //清理所有的VIP信息 -// productIdentifiers.forEach { item in -// cleanPurchase(productId: item) -// } -// MP_ADSimpleManager.shared.setOpenAdStatus(true) -// } -// //存入交易信息值 -// private func storePurchase(productId: String) { -// if isProductPurchased(productId: productId) == false { -// var purchasedProducts = UserDefaults.standard.array(forKey: "PurchasedProducts") as? [String] ?? [] -// purchasedProducts.append(productId) -// UserDefaults.standard.set(purchasedProducts, forKey: "PurchasedProducts") -// } -// } -// //检索用户是否购买该产品VIP产品 -// private func isProductPurchased(productId: String) -> Bool { -// let purchasedProducts = UserDefaults.standard.array(forKey: "PurchasedProducts") as? [String] ?? [] -// return purchasedProducts.contains(productId) -// } -// //清理对应的广告ID -// private func cleanPurchase(productId: String) { -// if isProductPurchased(productId: productId) { -// var purchasedProducts = UserDefaults.standard.array(forKey: "PurchasedProducts") as? [String] ?? [] -// purchasedProducts.removeAll(where: {$0 == productId}) -// UserDefaults.standard.set(purchasedProducts, forKey: "PurchasedProducts") -// } -// } -// ///交易完成 -// private func complete(transaction: SKPaymentTransaction) { -// MP_HUD.success("Successfully purchased", delay: 1.0, completion: nil) -// print("Transaction completed successfully.") -// MP_AnalyticsManager.shared.VIP_buy_successAction(transaction.payment.productIdentifier) -// validateReceipt { [weak self] status in -// guard let self = self else {return} -// if status { -// // 存储购买信息 -// self.storePurchase(productId: transaction.payment.productIdentifier) -// SKPaymentQueue.default().finishTransaction(transaction) -// //更新广告开关状态 -// reloadOpenStatus() -// } -// } -// } -// //重启交易 -// private func restore(transaction: SKPaymentTransaction) { -// print("Transaction restored.") -// validateReceipt { [weak self] status in -// guard let self = self else {return} -// if status { -// // 存储购买信息 -// self.storePurchase(productId: transaction.payment.productIdentifier) -// SKPaymentQueue.default().finishTransaction(transaction) -// //更新广告开关状态 -// reloadOpenStatus() -// } -// } -// } -// ///交易失败 -// private func fail(transaction: SKPaymentTransaction) { -// //检索错误 -// if let error = transaction.error as NSError? { -// MP_AnalyticsManager.shared.VIP_buy_failureAction(transaction.payment.productIdentifier, error: error.localizedDescription) -// if error.code != SKError.paymentCancelled.rawValue { -// MP_HUD.error("The current transaction failed".localizableString(), delay: 1.0, completion: nil) -// } else { -// MP_HUD.onlytext("The current transaction has been canceled".localizableString(), delay: 1.0, completion: nil) -// } -// } -// } -// //获取收据信息 -// func fetchReceipt() -> String? { -// guard let receiptURL = Bundle.main.appStoreReceiptURL else { return nil } -// guard let receiptData = try? Data(contentsOf: receiptURL) else { return nil } -// return receiptData.base64EncodedString(options: []) -// } -// //验证收据信息 -// func validateReceipt(completion: @escaping (Bool) -> Void) { -// guard MP_NetWorkManager.shared.netWorkStatu == .reachable else { -// completion(false) -// return -// } -// //获取收据信息 -// guard let receiptString = fetchReceipt() else { -// completion(false) -// return -// } -// //生成请求参数 -// let requestDictionary = ["receipt-data": receiptString, -// "password": "d29627e4f78b4b50a0ce5166acd8aa9f" ] -// guard JSONSerialization.isValidJSONObject(requestDictionary) else { -// completion(false) -// return -// } -// -// do { -// let requestData = try JSONSerialization.data(withJSONObject: requestDictionary) -// #if DEBUG -// let validationURLString = "https://sandbox.itunes.apple.com/verifyReceipt" -// #else -// let validationURLString = "https://buy.itunes.apple.com/verifyReceipt" -// #endif -// guard let validationURL = URL(string: validationURLString) else { -// completion(false) -// return -// } -// //创建请求 -// var request = URLRequest(url: validationURL) -// request.httpMethod = "POST" -// request.cachePolicy = .reloadIgnoringCacheData -// request.httpBody = requestData -// //设置会话 -// let session = URLSession.shared -// //执行任务 -// let task = session.dataTask(with: request) { data, response, error in -// guard error == nil, let data = data else { -// completion(false) -// return -// } -// -// do { -// if let jsonResponse = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] { -// if let status = jsonResponse["status"] as? Int { -// if status == 0 { -// completion(true) -// } else if status == 21007 { -// self.validateReceiptInSandbox(receiptString: receiptString, completion: completion) -// } else { -// print("Receipt validation failed with status: \(status)") -// completion(false) -// } -// } else { -// completion(false) -// } -// } else { -// completion(false) -// } -// } catch { -// completion(false) -// } -// } -// task.resume() -// } catch { -// completion(false) -// } -// } -// func validateReceiptInSandbox(receiptString: String, completion: @escaping (Bool) -> Void) { -// let requestDictionary = ["receipt-data": receiptString, -// "password": "d29627e4f78b4b50a0ce5166acd8aa9f"] -// guard JSONSerialization.isValidJSONObject(requestDictionary) else { -// completion(false) -// return -// } -// -// do { -// let requestData = try JSONSerialization.data(withJSONObject: requestDictionary) -// let validationURLString = "https://sandbox.itunes.apple.com/verifyReceipt" -// guard let validationURL = URL(string: validationURLString) else { -// completion(false) -// return -// } -// -// var request = URLRequest(url: validationURL) -// request.httpMethod = "POST" -// request.cachePolicy = .reloadIgnoringCacheData -// request.httpBody = requestData -// -// let session = URLSession.shared -// let task = session.dataTask(with: request) { data, response, error in -// guard error == nil, let data = data else { -// completion(false) -// return -// } -// do { -// if let jsonResponse = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] { -// if let status = jsonResponse["status"] as? Int, status == 0 { -// completion(true) -// } else { -// completion(false) -// } -// } else { -// completion(false) -// } -// } catch { -// completion(false) -// } -// } -// task.resume() -// } catch { -// completion(false) -// } -// } -//} + diff --git a/relax.offline.mp3.music/MP/MPPositive/Models/Models/MPPositive_CustomPlayListModel.swift b/relax.offline.mp3.music/MP/MPPositive/Models/Models/MPPositive_CustomPlayListModel.swift index eea757a..3ead1ff 100644 --- a/relax.offline.mp3.music/MP/MPPositive/Models/Models/MPPositive_CustomPlayListModel.swift +++ b/relax.offline.mp3.music/MP/MPPositive/Models/Models/MPPositive_CustomPlayListModel.swift @@ -27,15 +27,21 @@ class MPPositive_CustomPlayListModel: NSManagedObject, MP_CoreDataManageableDele extension MPPositive_CustomPlayListModel { ///将videos转为[MPPositive_CustomVideoModel] var videosArray:[MPPositive_CustomVideoModel]{ + guard let context = self.managedObjectContext else { return [] } // 明确告诉 Core Data 将要访问属性 self.willAccessValue(forKey: "videos") - let set = (videos as? Set)?.compactMap { $0 } ?? [] + guard let set = (self.videos as? Set) else { + // 告诉 Core Data 已经访问完属性 + self.didAccessValue(forKey: "videos") + return [] + } // 告诉 Core Data 已经访问完属性 self.didAccessValue(forKey: "videos") //转化成数组模型并进行排序(根据添加时间决定顺序) let array = Array(set).sorted { item1, item2 in - - return (item1.addTime ?? .init()) > (item2.addTime ?? .init()) + let time1 = item1.addTime ?? .distantPast + let time2 = item2.addTime ?? .distantPast + return time1 > time2 } return array } diff --git a/relax.offline.mp3.music/MP/MPPositive/ViewControllers/Center(个人曲库页)/MPPositive_BatchDeletionViewController.swift b/relax.offline.mp3.music/MP/MPPositive/ViewControllers/Center(个人曲库页)/MPPositive_BatchDeletionViewController.swift new file mode 100644 index 0000000..d12869a --- /dev/null +++ b/relax.offline.mp3.music/MP/MPPositive/ViewControllers/Center(个人曲库页)/MPPositive_BatchDeletionViewController.swift @@ -0,0 +1,113 @@ +// +// MPPositive_BatchDeletionViewController.swift +// relax.offline.mp3.music +// +// Created by Mr.Zhou on 2024/12/23. +// + +import UIKit +///批量删除 +class MPPositive_BatchDeletionViewController: MPPositive_BaseViewController { + //确认按钮 + private lazy var confirmBtn:UIButton = { + let btn:UIButton = .init() + btn.setTitle("Confirm", for: .normal) + btn.setTitleColor(.white, for: .normal) + btn.titleLabel?.font = .systemFont(ofSize: 16*width, weight: .medium) + btn.addTarget(self, action: #selector(deleteAction(_ :)), for: .touchUpInside) + return btn + }() + //tableView + private lazy var tableView:MPPositive_DefaultTableView = { + let tableView:MPPositive_DefaultTableView = .init(frame: .zero, style: .plain) + tableView.dataSource = self + tableView.delegate = self + tableView.register(MPPositive_AddSongTableViewCell.self, forCellReuseIdentifier: MPPositive_AddSongTableViewCellID) + return tableView + }() + private let MPPositive_AddSongTableViewCellID = "MPPositive_AddSongTableViewCell" + private var status:[Bool] = [] + private var offlines:[MPPositive_DownloadViewModel] + init(withOfflines offlines:[MPPositive_DownloadViewModel]) { + self.offlines = offlines + super.init(nibName: nil, bundle: nil) + refreshStatus() + } + + required init?(coder: NSCoder) { + self.offlines = [] + super.init(coder: coder) + refreshStatus() + } + override func viewDidLoad() { + super.viewDidLoad() + setPopBtn() + setTitle("Batch Deletion") + config() + } + private func config() { + navView.addSubview(confirmBtn) + confirmBtn.snp.makeConstraints { make in + make.right.equalToSuperview().offset(-16*width) + make.centerY.equalToSuperview() + } + view.addSubview(tableView) + tableView.snp.makeConstraints { make in + make.left.bottom.right.equalToSuperview() + make.top.equalTo(navView.snp.bottom) + } + } + //补充status + private func refreshStatus() { + //每一个离线资源都产生对应的状态值(默认false) + self.status = offlines.compactMap({_ in false}) + //刷新页面 + DispatchQueue.main.async { + [weak self] in + guard let self = self else {return} + tableView.showMessage(status.count) + tableView.reloadData() + } + } + //确认删除 + @objc private func deleteAction(_ sender:UIButton) { + view.endEditing(true) + //将状态值为ture的数据取出 + var array:[MPPositive_DownloadViewModel] = [] + for (index, item) in status.enumerated() { + if item == true { + array.append(offlines[index]) + } + } + guard !array.isEmpty else { + MP_HUD.showWithStatus(hudStatus: .error, text: "Please select the song", delay: 1.0, completion: nil) + return + } + MP_HUD.loading() + array.forEach { item in + //确定删除 + MP_DownloadManager.shared.deleteFileDocuments(item.loadItem.videoId ?? "") { videoId in + } + } + MP_HUD.success("Removed".localizableString(), delay: 1.0) { [weak self] in + self?.navigationController?.popViewController(animated: true) + } + } +} +//MARK: - tableView +extension MPPositive_BatchDeletionViewController: UITableViewDataSource, UITableViewDelegate { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return offlines.count + } + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: MPPositive_AddSongTableViewCellID, for: indexPath) as! MPPositive_AddSongTableViewCell + cell.download = offlines[indexPath.row] + cell.statu = status[indexPath.row] + return cell + } + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + //调整当前选中的状态值 + status[indexPath.row] = !status[indexPath.row] + tableView.reloadData() + } +} diff --git a/relax.offline.mp3.music/MP/MPPositive/ViewControllers/Center(个人曲库页)/MPPositive_OfflineSongsViewController.swift b/relax.offline.mp3.music/MP/MPPositive/ViewControllers/Center(个人曲库页)/MPPositive_OfflineSongsViewController.swift index 68c695b..3318752 100644 --- a/relax.offline.mp3.music/MP/MPPositive/ViewControllers/Center(个人曲库页)/MPPositive_OfflineSongsViewController.swift +++ b/relax.offline.mp3.music/MP/MPPositive/ViewControllers/Center(个人曲库页)/MPPositive_OfflineSongsViewController.swift @@ -75,6 +75,16 @@ class MPPositive_OfflineSongsViewController: MPPositive_BaseViewController { btn.addTarget(self, action: #selector(shuffleClick(_ :)), for: .touchUpInside) return btn }() + ///批量删除按钮 + private lazy var bulkDeleteBtn:UIButton = { + let btn = UIButton() + btn.setImage(UIImage(named: "Center_BulkDelete'logo"), for: .normal) + btn.setTitle(" Delete", for: .normal) + btn.setTitleColor(.white, for: .normal) + btn.titleLabel?.font = .systemFont(ofSize: 14*width, weight: .regular) + btn.addTarget(self, action: #selector(batchDeleteClick(_ :)), for: .touchUpInside) + return btn + }() ///搜索按钮 private lazy var searchBtn:UIButton = { let btn = UIButton() @@ -275,6 +285,11 @@ class MPPositive_OfflineSongsViewController: MPPositive_BaseViewController { make.left.equalTo(playAllBtn.snp.right).offset(18*width) make.centerY.equalTo(playAllBtn) } + sectionShowView.addSubview(bulkDeleteBtn) + bulkDeleteBtn.snp.makeConstraints { make in + make.left.equalTo(shuffleBtn.snp.right).offset(18*width) + make.centerY.equalTo(playAllBtn) + } //搜索 sectionShowView.addSubview(searchBtn) searchBtn.snp.makeConstraints { make in @@ -395,6 +410,7 @@ class MPPositive_OfflineSongsViewController: MPPositive_BaseViewController { } //随机播放 @objc private func shuffleClick(_ sender:UIButton) { + view.endEditing(true) guard offlines.count != 0 else {return} MPPositive_Debouncer.shared.call { [weak self] in @@ -435,6 +451,14 @@ class MPPositive_OfflineSongsViewController: MPPositive_BaseViewController { MP_AnalyticsManager.shared.player_b_listAction() } } + //批量删除事件 + @objc private func batchDeleteClick(_ sender:UIButton) { + view.endEditing(true) + print("点击了批量删除") + //同时执行弹出事件 + let showVC:MPPositive_BatchDeletionViewController = .init(withOfflines: offlines) + navigationController?.pushViewController(showVC, animated: true) + } } //MARK: - tableView extension MPPositive_OfflineSongsViewController: UITableViewDataSource, UITableViewDelegate, UIViewControllerTransitioningDelegate{ diff --git a/relax.offline.mp3.music/MP/MPPositive/ViewControllers/Home(首页,各项列表页,艺术家页)/MPPositive_ListShowViewController.swift b/relax.offline.mp3.music/MP/MPPositive/ViewControllers/Home(首页,各项列表页,艺术家页)/MPPositive_ListShowViewController.swift index a882ff1..206cc73 100644 --- a/relax.offline.mp3.music/MP/MPPositive/ViewControllers/Home(首页,各项列表页,艺术家页)/MPPositive_ListShowViewController.swift +++ b/relax.offline.mp3.music/MP/MPPositive/ViewControllers/Home(首页,各项列表页,艺术家页)/MPPositive_ListShowViewController.swift @@ -258,17 +258,23 @@ class MPPositive_ListShowViewController: MPPositive_BaseViewController, UIViewCo //弹出播放器 NotificationCenter.notificationKey.post(notificationName: .pup_player_vc) MP_AnalyticsManager.shared.player_b_impAction() - //触发next请求,优先获取列表全部单曲基础数据(不完善) - MP_NetWorkManager.shared.requestNextList(item.browseItem.playListId ?? "", videoId: item.browseItem.videoId ?? "", clickTrackingParams: item.browseItem.clickTrackingParams){ [weak self] listSongs in - guard let self = self else {return} - //回掉的数据并不完善,生成一个playerloadViewModel - let lodaViewModel = MPPositive_PlayerLoadViewModel(listSongs, currentVideoId: item.browseItem.videoId ?? "") - lodaViewModel.improveData(item.browseItem.videoId ?? "") - //更改播放器播放类型 - MP_PlayerManager.shared.setPlayType(.normal) - MP_PlayerManager.shared.loadPlayer = lodaViewModel - MP_AnalyticsManager.shared.player_b_listAction() + var array:[MPPositive_SongItemModel] = [] + //将当前歌单/专辑中所有歌曲都调整为可播放类型 + listOrAlbum.items.forEach { browse in + let song = MPPositive_SongItemModel() + song.coverUrls = browse.browseItem.coverUrls + song.reviewUrls = browse.browseItem.coverUrls + song.title = browse.title + song.shortBylineText = browse.subtitle + song.videoId = browse.browseItem.videoId + array.append(song) } + let lodaViewModel = MPPositive_PlayerLoadViewModel(array, currentVideoId: item.browseItem.videoId ?? "") + lodaViewModel.improveData(item.browseItem.videoId ?? "") + //更改播放器播放类型 + MP_PlayerManager.shared.setPlayType(.normal) + MP_PlayerManager.shared.loadPlayer = lodaViewModel + MP_AnalyticsManager.shared.player_b_listAction() } } //随机播放事件 @@ -286,17 +292,23 @@ class MPPositive_ListShowViewController: MPPositive_BaseViewController, UIViewCo //弹出播放器 NotificationCenter.notificationKey.post(notificationName: .pup_player_vc) MP_AnalyticsManager.shared.player_b_impAction() - //触发next请求,优先获取列表全部单曲基础数据(不完善) - MP_NetWorkManager.shared.requestNextList(item.browseItem.playListId ?? "", videoId: item.browseItem.videoId ?? "", clickTrackingParams: item.browseItem.clickTrackingParams){ [weak self] listSongs in - guard let self = self else {return} - //回掉的数据并不完善,生成一个playerloadViewModel - let lodaViewModel = MPPositive_PlayerLoadViewModel(listSongs, currentVideoId: item.browseItem.videoId ?? "") - lodaViewModel.improveData(item.browseItem.videoId ?? "") - //更改播放器播放类型 - MP_PlayerManager.shared.setPlayType(.random) - MP_PlayerManager.shared.loadPlayer = lodaViewModel - MP_AnalyticsManager.shared.player_b_listAction() + var array:[MPPositive_SongItemModel] = [] + //将当前歌单/专辑中所有歌曲都调整为可播放类型 + listOrAlbum.items.forEach { browse in + let song = MPPositive_SongItemModel() + song.coverUrls = browse.browseItem.coverUrls + song.reviewUrls = browse.browseItem.coverUrls + song.title = browse.title + song.shortBylineText = browse.subtitle + song.videoId = browse.browseItem.videoId + array.append(song) } + let lodaViewModel = MPPositive_PlayerLoadViewModel(array, currentVideoId: item.browseItem.videoId ?? "") + lodaViewModel.improveData(item.browseItem.videoId ?? "") + //更改播放器播放类型 + MP_PlayerManager.shared.setPlayType(.random) + MP_PlayerManager.shared.loadPlayer = lodaViewModel + MP_AnalyticsManager.shared.player_b_listAction() } } //切换当前列表收藏状态 @@ -400,20 +412,28 @@ extension MPPositive_ListShowViewController: UITableViewDataSource, UITableViewD return } MP_AnalyticsManager.shared.song_clickAction("List") + //优先清除数据 + MP_PlayerManager.shared.loadPlayer = nil //弹出播放器 NotificationCenter.notificationKey.post(notificationName: .pup_player_vc) MP_AnalyticsManager.shared.player_b_impAction() - //触发next请求,优先获取列表全部单曲基础数据(不完善) - MP_NetWorkManager.shared.requestNextList(item.browseItem.playListId ?? "", videoId: item.browseItem.videoId ?? "", clickTrackingParams: item.browseItem.clickTrackingParams){ [weak self] listSongs in - guard let self = self else {return} - //回掉的数据并不完善,生成一个playerloadViewModel - let lodaViewModel = MPPositive_PlayerLoadViewModel(listSongs, currentVideoId: listOrAlbum.items[indexPath.row].browseItem.videoId ?? "") - lodaViewModel.improveData(listOrAlbum.items[indexPath.row].browseItem.videoId ?? "") - //更改播放器播放类型 - MP_PlayerManager.shared.setPlayType(.normal) - MP_PlayerManager.shared.loadPlayer = lodaViewModel - MP_AnalyticsManager.shared.player_b_listAction() + var array:[MPPositive_SongItemModel] = [] + //将当前歌单/专辑中所有歌曲都调整为可播放类型 + listOrAlbum.items.forEach { browse in + let song = MPPositive_SongItemModel() + song.coverUrls = browse.browseItem.coverUrls + song.reviewUrls = browse.browseItem.coverUrls + song.title = browse.title + song.shortBylineText = browse.subtitle + song.videoId = browse.browseItem.videoId + array.append(song) } + let lodaViewModel = MPPositive_PlayerLoadViewModel(array, currentVideoId: item.browseItem.videoId ?? "") + lodaViewModel.improveData(item.browseItem.videoId ?? "") + //更改播放器播放类型 + MP_PlayerManager.shared.setPlayType(.normal) + MP_PlayerManager.shared.loadPlayer = lodaViewModel + MP_AnalyticsManager.shared.player_b_listAction() } } func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? { diff --git a/relax.offline.mp3.music/MP/MPPositive/Views/Base/MPPositive_BottomShowView.swift b/relax.offline.mp3.music/MP/MPPositive/Views/Base/MPPositive_BottomShowView.swift index bc287e0..2ee10d8 100644 --- a/relax.offline.mp3.music/MP/MPPositive/Views/Base/MPPositive_BottomShowView.swift +++ b/relax.offline.mp3.music/MP/MPPositive/Views/Base/MPPositive_BottomShowView.swift @@ -11,9 +11,15 @@ import Kingfisher class MPPositive_BottomShowView: UIView { //绿色背景图片 private lazy var bgGreenImageView:UIImageView = UIImageView(image: .init(named: "BottomShow'bg")) + //封面承载View + private lazy var coverContentView:UIView = { + let view:UIView = .init(frame: .init(x: 26*width, y: 13*width, width: 48*width, height: 48*width)) + view.backgroundColor = .clear + return view + }() //音乐封面展示圆框 private lazy var coverImageView:UIImageView = { - let imageView:UIImageView = .init(frame: .init(x: 26*width, y: 13*width, width: 48*width, height: 48*width)) + let imageView:UIImageView = .init() imageView.contentMode = .scaleAspectFill imageView.layer.cornerRadius = 24*width imageView.layer.masksToBounds = true @@ -47,6 +53,8 @@ class MPPositive_BottomShowView: UIView { btn.addTarget(self, action: #selector(switchStatuClick(_ :)), for: .touchUpInside) return btn }() + //封面是否旋转 + private var isRotating:Bool = false //展开音乐播放列表 var showListBlock:(() -> Void)? override init(frame: CGRect) { @@ -73,13 +81,25 @@ class MPPositive_BottomShowView: UIView { deinit { NotificationCenter.default.removeObserver(self) } + override func didMoveToWindow() { + super.didMoveToWindow() + if isRotating { + addRotationAnimation() + }else { + stopRotationAnimation() + } + } //配置 private func confirgue() { addSubview(bgGreenImageView) bgGreenImageView.snp.makeConstraints { make in make.left.right.top.bottom.equalToSuperview() } - addSubview(coverImageView) + addSubview(coverContentView) + coverContentView.addSubview(coverImageView) + coverImageView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } addSubview(nextBtn) nextBtn.snp.makeConstraints { make in make.right.equalToSuperview().offset(-24*width) @@ -129,15 +149,22 @@ class MPPositive_BottomShowView: UIView { } //切换播放器状态时 @objc private func statusSwitchAction(_ sender:Notification) { - if sender.object != nil { - let state:MP_PlayerStateType = sender.object as! MP_PlayerStateType - DispatchQueue.main.async { - [weak self] in - switch state { - case .Playing: - self?.playStatuBtn.isSelected = true - default: - self?.playStatuBtn.isSelected = false + guard let state:MP_PlayerStateType = sender.object as? MP_PlayerStateType else { + return + } + DispatchQueue.main.async { + [weak self] in + guard let self = self else {return} + switch state { + case .Playing: + playStatuBtn.isSelected = true + if isRotating == false { + addRotationAnimation() + } + default: + playStatuBtn.isSelected = false + if isRotating == true { + stopRotationAnimation() } } } @@ -181,4 +208,27 @@ class MPPositive_BottomShowView: UIView { } } } + //添加无限旋转Layer + private func addRotationAnimation() { + isRotating = true + DispatchQueue.main.asyncAfter(deadline: .now()) {[weak self] in + guard let self = self, coverImageView.window != nil else {return} + coverContentView.layer.removeAnimation(forKey: "rotationAnimation") + let rotation = CABasicAnimation(keyPath: "transform.rotation.z") + rotation.fromValue = 0 + rotation.toValue = Double.pi * 2 + rotation.duration = 8 // 一圈旋转时间 + rotation.repeatCount = Float.infinity // 无限循环 + coverContentView.layer.add(rotation, forKey: "rotationAnimation") + } + } + //移除无限旋转Layer + private func stopRotationAnimation() { + isRotating = false // 如果未旋转,则无需停止 + DispatchQueue.main.asyncAfter(deadline: .now()) {[weak self] in + guard let self = self else {return} + coverContentView.layer.removeAnimation(forKey: "rotationAnimation") + coverContentView.layer.transform = CATransform3DIdentity + } + } } diff --git a/relax.offline.mp3.music/MP/MPPositive/Views/Base/MPPositive_DefaultTableView.swift b/relax.offline.mp3.music/MP/MPPositive/Views/Base/MPPositive_DefaultTableView.swift new file mode 100644 index 0000000..b1d0a58 --- /dev/null +++ b/relax.offline.mp3.music/MP/MPPositive/Views/Base/MPPositive_DefaultTableView.swift @@ -0,0 +1,34 @@ +// +// MPPositive_DefaultTableView.swift +// relax.offline.mp3.music +// +// Created by Mr.Zhou on 2024/12/24. +// + +import UIKit +///默认tableView +class MPPositive_DefaultTableView: UITableView { + + override init(frame: CGRect, style: UITableView.Style) { + super.init(frame: frame, style: style) + configure() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + configure() + } + //通用配置 + private func configure() { + self.backgroundColor = .clear + self.showsVerticalScrollIndicator = false + self.showsHorizontalScrollIndicator = false + self.separatorStyle = .none + self.estimatedRowHeight = 200 + self.rowHeight = UITableView.automaticDimension + self.contentInset = .init(top: 0, left: 0, bottom: 70*width, right: 0) + if #available(iOS 15.0, *) { + self.sectionHeaderTopPadding = 0 + } + } +} diff --git a/relax.offline.mp3.music/MP/MPPositive/Views/Center/MPPositive_AddSongTableViewCell.swift b/relax.offline.mp3.music/MP/MPPositive/Views/Center/MPPositive_AddSongTableViewCell.swift index d8bbb0d..01eb02b 100644 --- a/relax.offline.mp3.music/MP/MPPositive/Views/Center/MPPositive_AddSongTableViewCell.swift +++ b/relax.offline.mp3.music/MP/MPPositive/Views/Center/MPPositive_AddSongTableViewCell.swift @@ -51,6 +51,23 @@ class MPPositive_AddSongTableViewCell: UITableViewCell { } } } + var download:MPPositive_DownloadViewModel!{ + didSet{ + download.setImage(iconImageView) + titleLabel.text = download.title + subtitleLabel.text = download.subtitle + + } + } + var statu:Bool!{ + didSet{ + if statu == false { + statuImageView.image = UIImage(named: "UnAdd_PlayLists'logo") + }else { + statuImageView.image = UIImage(named: "Added_PlayList'logo") + } + } + } override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) selectionStyle = .none