From 6c6734fd29fa773d8a27cb3a993c7a1301cb4f0b Mon Sep 17 00:00:00 2001 From: WindowsMEMZ Date: Fri, 19 Apr 2024 22:32:03 +0800 Subject: [PATCH] feat: Search in some lists, accessbility, new audio player(alpha). dev: Private SF Symbols. --- DarockBili.xcodeproj/project.pbxproj | 58 ++- Localizable.xcstrings | 22 ++ MeowBili/Extension/UIExt.swift | 13 + MeowBili/InMain/ContentView.swift | 20 ++ MeowBili/MeowBili-Bridging-Header.h | 1 + MeowBili/MeowBiliApp.swift | 51 +++ MeowBili/Models/Audio.swift | 26 ++ MeowBili/Others/CCodes/NowPlayingExtension.m | 14 + MeowBili/Others/CCodes/PrivateSymbols.h | 39 ++ MeowBili/Others/CCodes/PrivateSymbols.m | 31 ++ MeowBili/Others/SettingsView.swift | 37 +- MeowBili/PersonalCenter/DownloadsView.swift | 150 ++++---- MeowBili/PersonalCenter/HistoryView.swift | 66 ++-- .../PersonalCenter/PersonAccountView.swift | 6 +- MeowBili/Video/AudioPlayerView.swift | 333 ++++++------------ MeowBili/Video/VideoDetailView.swift | 82 +---- 16 files changed, 514 insertions(+), 435 deletions(-) create mode 100644 MeowBili/Models/Audio.swift create mode 100644 MeowBili/Others/CCodes/PrivateSymbols.h create mode 100644 MeowBili/Others/CCodes/PrivateSymbols.m diff --git a/DarockBili.xcodeproj/project.pbxproj b/DarockBili.xcodeproj/project.pbxproj index abfcb1a44..22170fea5 100644 --- a/DarockBili.xcodeproj/project.pbxproj +++ b/DarockBili.xcodeproj/project.pbxproj @@ -40,6 +40,14 @@ 8C82F7942BB6EAB0009AEFD2 /* SemanticVersion.drkdatas in Resources */ = {isa = PBXBuildFile; fileRef = 8C82F7922BB6EAB0009AEFD2 /* SemanticVersion.drkdatas */; }; 8C82F7952BB6EAB0009AEFD2 /* SemanticVersion.drkdatas in Resources */ = {isa = PBXBuildFile; fileRef = 8C82F7922BB6EAB0009AEFD2 /* SemanticVersion.drkdatas */; }; 8C82F7962BB6EAB0009AEFD2 /* SemanticVersion.drkdatas in Resources */ = {isa = PBXBuildFile; fileRef = 8C82F7922BB6EAB0009AEFD2 /* SemanticVersion.drkdatas */; }; + 8C9776082BCECBC3006EDB58 /* Audio.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C9776072BCECBC3006EDB58 /* Audio.swift */; }; + 8C9776092BCECBC3006EDB58 /* Audio.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C9776072BCECBC3006EDB58 /* Audio.swift */; }; + 8C97760A2BCECBC3006EDB58 /* Audio.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C9776072BCECBC3006EDB58 /* Audio.swift */; }; + 8C97760B2BCECBC3006EDB58 /* Audio.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C9776072BCECBC3006EDB58 /* Audio.swift */; }; + 8CA1BA112BD2AF9E009BCDFB /* PrivateSymbols.m in Sources */ = {isa = PBXBuildFile; fileRef = 8CA1BA102BD2AF9E009BCDFB /* PrivateSymbols.m */; }; + 8CA1BA122BD2AF9E009BCDFB /* PrivateSymbols.m in Sources */ = {isa = PBXBuildFile; fileRef = 8CA1BA102BD2AF9E009BCDFB /* PrivateSymbols.m */; }; + 8CA1BA132BD2AF9E009BCDFB /* PrivateSymbols.m in Sources */ = {isa = PBXBuildFile; fileRef = 8CA1BA102BD2AF9E009BCDFB /* PrivateSymbols.m */; }; + 8CA1BA142BD2AF9E009BCDFB /* PrivateSymbols.m in Sources */ = {isa = PBXBuildFile; fileRef = 8CA1BA102BD2AF9E009BCDFB /* PrivateSymbols.m */; }; 8CA370D42B82690700CE0E9E /* Mixpanel in Frameworks */ = {isa = PBXBuildFile; productRef = 8CA370D32B82690700CE0E9E /* Mixpanel */; }; 8CA370D62B82691100CE0E9E /* Mixpanel in Frameworks */ = {isa = PBXBuildFile; productRef = 8CA370D52B82691100CE0E9E /* Mixpanel */; }; 8CA370DB2B82724400CE0E9E /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 8CA370D92B82724400CE0E9E /* PrivacyInfo.xcprivacy */; }; @@ -415,6 +423,9 @@ 8C82F82B2BB70AA7009AEFD2 /* statuscheck-runner.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = "statuscheck-runner.yml"; sourceTree = ""; }; 8C82F82C2BB70AA7009AEFD2 /* tf.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = tf.yml; sourceTree = ""; }; 8C82F82E2BB70AA7009AEFD2 /* CODE_OF_CONDUCT.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CODE_OF_CONDUCT.md; sourceTree = ""; }; + 8C9776072BCECBC3006EDB58 /* Audio.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Audio.swift; sourceTree = ""; }; + 8CA1BA102BD2AF9E009BCDFB /* PrivateSymbols.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PrivateSymbols.m; sourceTree = ""; }; + 8CA1BA152BD2AFAB009BCDFB /* PrivateSymbols.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PrivateSymbols.h; sourceTree = ""; }; 8CA370D92B82724400CE0E9E /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; 8CA370E82B83BB4F00CE0E9E /* MeowBili Vision App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "MeowBili Vision App.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 8CA371332B83BE7D00CE0E9E /* MeowBili Vision App.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "MeowBili Vision App.entitlements"; sourceTree = ""; }; @@ -695,6 +706,7 @@ children = ( 8CA7CC692B77AE62008E587F /* Bangumi.swift */, 8CA7CC6B2B77AE80008E587F /* DownloadObj.swift */, + 8C9776072BCECBC3006EDB58 /* Audio.swift */, ); path = Models; sourceTree = ""; @@ -769,6 +781,8 @@ 8CC3D5592B7CB95D005636DC /* NowPlayingExtension.m */, 8CC3D55E2B7CC941005636DC /* OCCodeExt.h */, 8CC3D55C2B7CC936005636DC /* OCCodeExt.m */, + 8CA1BA152BD2AFAB009BCDFB /* PrivateSymbols.h */, + 8CA1BA102BD2AF9E009BCDFB /* PrivateSymbols.m */, ); path = CCodes; sourceTree = ""; @@ -1368,6 +1382,7 @@ 8CA371062B83BB9100CE0E9E /* SignalErrorView.swift in Sources */, 8CA3710E2B83BB9100CE0E9E /* bMessageSendView.swift in Sources */, 8CA371022B83BB9100CE0E9E /* FeedbackView.swift in Sources */, + 8CA1BA132BD2AF9E009BCDFB /* PrivateSymbols.m in Sources */, 8CA370FF2B83BB9100CE0E9E /* NoticeView.swift in Sources */, 8CA371132B83BB9100CE0E9E /* PersonAccountView.swift in Sources */, 8CA3710C2B83BB9100CE0E9E /* backtrace.swift in Sources */, @@ -1378,6 +1393,7 @@ 8CA371292B83BB9100CE0E9E /* SearchView.swift in Sources */, 8CA3712D2B83BB9100CE0E9E /* LoginView.swift in Sources */, 8C81D2B32BCBA58000EC58F4 /* FansListView.swift in Sources */, + 8C97760A2BCECBC3006EDB58 /* Audio.swift in Sources */, 8CA371252B83BB9100CE0E9E /* WatchLaterView.swift in Sources */, 8CA3710A2B83BB9100CE0E9E /* Bangumi.swift in Sources */, 8CA3712C2B83BB9100CE0E9E /* ErrorGetView.swift in Sources */, @@ -1431,6 +1447,7 @@ 8CA7CCCA2B77B4B2008E587F /* UserDynamicMainView.swift in Sources */, 8CA7CCAE2B77B36E008E587F /* PersonAccountView.swift in Sources */, 8CA7CCD52B77B530008E587F /* BangumiPlayerView.swift in Sources */, + 8CA1BA112BD2AF9E009BCDFB /* PrivateSymbols.m in Sources */, 8CA7CC9B2B77B13D008E587F /* Passthroughs.swift in Sources */, 8CA7CCB02B77B383008E587F /* FavoriteView.swift in Sources */, 8CA7CC9D2B77B150008E587F /* AboutView.swift in Sources */, @@ -1441,6 +1458,7 @@ 8CA7CC8A2B77B061008E587F /* AppFileManager.swift in Sources */, 8CA7CCB22B77B398008E587F /* FollowListView.swift in Sources */, 8C81D2B12BCBA57F00EC58F4 /* FansListView.swift in Sources */, + 8C9776082BCECBC3006EDB58 /* Audio.swift in Sources */, 8CA7CC7B2B77AF93008E587F /* NetwokFixView.swift in Sources */, 8CA7CCD32B77B51A008E587F /* BangumiDownloadView.swift in Sources */, 8CA7CC922B77B0D4008E587F /* CodingTime.m in Sources */, @@ -1459,6 +1477,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 8CA1BA122BD2AF9E009BCDFB /* PrivateSymbols.m in Sources */, 8CBFF0C02B85E55F00350E0F /* SkinDownloadView.swift in Sources */, 8CBFF0C82B85E55F00350E0F /* BangumiPlayerView.swift in Sources */, 8CBFF0C42B85E55F00350E0F /* backtrace.swift in Sources */, @@ -1493,6 +1512,7 @@ 8CBFF0EB2B85E55F00350E0F /* FavoriteView.swift in Sources */, 8CBFF0E12B85E55F00350E0F /* UserDynamicMainView.swift in Sources */, 8CA53B032BB963D700D6D692 /* WatchUIDebugView.swift in Sources */, + 8C9776092BCECBC3006EDB58 /* Audio.swift in Sources */, 8CBFF0BA2B85E55F00350E0F /* Passthroughs.swift in Sources */, 8CBFF0B92B85E55F00350E0F /* LinkDetectText.swift in Sources */, 8CBFF0CB2B85E55F00350E0F /* bMessageSendView.swift in Sources */, @@ -1556,6 +1576,7 @@ 8CC5B6DF2B873FA000BAD89E /* FeedbackView.swift in Sources */, 8CC5B7092B873FA000BAD89E /* AVExtension.m in Sources */, 8CC5B6F02B873FA000BAD89E /* ErrorGetView.swift in Sources */, + 8CA1BA142BD2AF9E009BCDFB /* PrivateSymbols.m in Sources */, 8CC5B6E72B873FA000BAD89E /* FavoriteView.swift in Sources */, 8CC5B70C2B873FA000BAD89E /* AboutView.swift in Sources */, 8CC5B6FE2B873FA000BAD89E /* PersonAccountView.swift in Sources */, @@ -1571,6 +1592,7 @@ 8CC5B6E02B873FA000BAD89E /* UserDynamicListView.swift in Sources */, 8CC5B7002B873FA000BAD89E /* DynamicDetailView.swift in Sources */, 8CC5B6FD2B873FA000BAD89E /* LinkDetectText.swift in Sources */, + 8C97760B2BCECBC3006EDB58 /* Audio.swift in Sources */, 8CC5B6F72B873FA000BAD89E /* AudioPlayerView.swift in Sources */, 8CC5B6EA2B873FA000BAD89E /* CodingTime.m in Sources */, 8CC5B7102B873FA000BAD89E /* LiveMessagesView.swift in Sources */, @@ -1619,7 +1641,7 @@ isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1130; + CURRENT_PROJECT_VERSION = 1175; DEVELOPMENT_TEAM = B57D8PP775; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = SafariExtension/Info.plist; @@ -1650,7 +1672,7 @@ isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1130; + CURRENT_PROJECT_VERSION = 1175; DEVELOPMENT_TEAM = B57D8PP775; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = SafariExtension/Info.plist; @@ -1747,7 +1769,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = MeowBili/MeowBiliAlternative.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1130; + CURRENT_PROJECT_VERSION = 1175; DEVELOPMENT_ASSET_PATHS = "\"MeowBili/Preview Content\""; DEVELOPMENT_TEAM = B38QUJMY47; ENABLE_PREVIEWS = YES; @@ -1792,7 +1814,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIconWatch; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1130; + CURRENT_PROJECT_VERSION = 1175; DEVELOPMENT_ASSET_PATHS = "\"MeowBili/Preview\\ Content\""; DEVELOPMENT_TEAM = B38QUJMY47; ENABLE_PREVIEWS = YES; @@ -1832,7 +1854,7 @@ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; CODE_SIGN_ENTITLEMENTS = "MeowBili/MeowBili Vision App.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1130; + CURRENT_PROJECT_VERSION = 1175; DEVELOPMENT_ASSET_PATHS = "\"MeowBili/Preview Content\""; DEVELOPMENT_TEAM = B57D8PP775; ENABLE_PREVIEWS = YES; @@ -1868,7 +1890,7 @@ CODE_SIGN_ENTITLEMENTS = "MeowBili/MeowBili Mac App.entitlements"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1130; + CURRENT_PROJECT_VERSION = 1175; DEVELOPMENT_ASSET_PATHS = "\"MeowBili/Preview Content\""; DEVELOPMENT_TEAM = B57D8PP775; ENABLE_HARDENED_RUNTIME = YES; @@ -1897,7 +1919,7 @@ isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1130; + CURRENT_PROJECT_VERSION = 1175; DEVELOPMENT_TEAM = B57D8PP775; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 16.0; @@ -1921,7 +1943,7 @@ isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1130; + CURRENT_PROJECT_VERSION = 1175; DEVELOPMENT_TEAM = B38QUJMY47; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = SafariExtension/Info.plist; @@ -1988,7 +2010,7 @@ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; CODE_SIGN_ENTITLEMENTS = "MeowBili/MeowBili Vision App.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1130; + CURRENT_PROJECT_VERSION = 1175; DEVELOPMENT_ASSET_PATHS = "\"MeowBili/Preview Content\""; DEVELOPMENT_TEAM = B57D8PP775; ENABLE_PREVIEWS = YES; @@ -2023,7 +2045,7 @@ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; CODE_SIGN_ENTITLEMENTS = "MeowBili/MeowBili Vision App.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1130; + CURRENT_PROJECT_VERSION = 1175; DEVELOPMENT_ASSET_PATHS = "\"MeowBili/Preview Content\""; DEVELOPMENT_TEAM = B57D8PP775; ENABLE_PREVIEWS = YES; @@ -2060,7 +2082,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = MeowBili/MeowBili.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1130; + CURRENT_PROJECT_VERSION = 1175; DEVELOPMENT_ASSET_PATHS = "\"MeowBili/Preview Content\""; DEVELOPMENT_TEAM = B57D8PP775; ENABLE_PREVIEWS = YES; @@ -2107,7 +2129,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = MeowBili/MeowBili.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1130; + CURRENT_PROJECT_VERSION = 1175; DEVELOPMENT_ASSET_PATHS = "\"MeowBili/Preview Content\""; DEVELOPMENT_TEAM = B57D8PP775; ENABLE_PREVIEWS = YES; @@ -2152,7 +2174,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIconWatch; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1130; + CURRENT_PROJECT_VERSION = 1175; DEVELOPMENT_ASSET_PATHS = "\"MeowBili/Preview\\ Content\""; DEVELOPMENT_TEAM = B57D8PP775; ENABLE_PREVIEWS = YES; @@ -2188,7 +2210,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIconWatch; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1130; + CURRENT_PROJECT_VERSION = 1175; DEVELOPMENT_ASSET_PATHS = "\"MeowBili/Preview\\ Content\""; DEVELOPMENT_TEAM = B57D8PP775; ENABLE_PREVIEWS = YES; @@ -2228,7 +2250,7 @@ CODE_SIGN_ENTITLEMENTS = "MeowBili/MeowBili Mac App.entitlements"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1130; + CURRENT_PROJECT_VERSION = 1175; DEVELOPMENT_ASSET_PATHS = "\"MeowBili/Preview Content\""; DEVELOPMENT_TEAM = B57D8PP775; ENABLE_HARDENED_RUNTIME = YES; @@ -2261,7 +2283,7 @@ CODE_SIGN_ENTITLEMENTS = "MeowBili/MeowBili Mac App.entitlements"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1130; + CURRENT_PROJECT_VERSION = 1175; DEVELOPMENT_ASSET_PATHS = "\"MeowBili/Preview Content\""; DEVELOPMENT_TEAM = B57D8PP775; ENABLE_HARDENED_RUNTIME = YES; @@ -2290,7 +2312,7 @@ isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1130; + CURRENT_PROJECT_VERSION = 1175; DEVELOPMENT_TEAM = B57D8PP775; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 16.0; @@ -2313,7 +2335,7 @@ isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1130; + CURRENT_PROJECT_VERSION = 1175; DEVELOPMENT_TEAM = B57D8PP775; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 16.0; diff --git a/Localizable.xcstrings b/Localizable.xcstrings index 742e3c0c1..93f1f66bc 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -1734,6 +1734,9 @@ }, "Darock 会收集必要的诊断信息以便进行改进。如果您不愿意被收集信息,请勿发送。" : { + }, + "Darock 建议您保持更新最新版本系统" : { + }, "Darock 祝您新年快乐!" : { @@ -8019,6 +8022,7 @@ } }, "Video.play-in-audio" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -8214,6 +8218,9 @@ }, "使用恢复密钥" : { + }, + "停止支持后,将不再为此版本系统提供功能性更新" : { + }, "储存空间" : { @@ -8566,6 +8573,9 @@ } } } + }, + "垂下手腕时隐藏内容" : { + }, "声音与触感" : { @@ -8824,6 +8834,9 @@ }, "您已被 Darock 永久封禁且在设备上施行" : { + }, + "您的 Apple Watch 运行的 watchOS 已过时,喵哩喵哩将在 2024/6/10 之后停止支持" : { + }, "我的收藏" : { "localizations" : { @@ -9416,9 +9429,15 @@ } } } + }, + "辅助功能" : { + }, "输入错误" : { + }, + "过时的 watchOS" : { + }, "退出 App" : { @@ -9498,6 +9517,9 @@ }, "错误/异常行为" : { + }, + "降低亮度" : { + }, "隐私与安全性" : { diff --git a/MeowBili/Extension/UIExt.swift b/MeowBili/Extension/UIExt.swift index 25c5dd10d..aff0cdf5a 100644 --- a/MeowBili/Extension/UIExt.swift +++ b/MeowBili/Extension/UIExt.swift @@ -617,3 +617,16 @@ struct UIImageTransfer: Transferable { } } } + +extension Image { + init(privateSystemName systemName: String) { + self.init(uiImage: UIImage(privateSystemName: systemName)) + } +} +@ViewBuilder +func Label(_ titleKey: LocalizedStringKey, privateSystemImage systemName: String) -> some View { + HStack { + Image(uiImage: UIImage(privateSystemName: systemName)) + Text(titleKey) + } +} diff --git a/MeowBili/InMain/ContentView.swift b/MeowBili/InMain/ContentView.swift index 6d31558ee..43b981bb0 100644 --- a/MeowBili/InMain/ContentView.swift +++ b/MeowBili/InMain/ContentView.swift @@ -27,6 +27,7 @@ struct ContentView: View { @AppStorage("IsFirstUsing") var isFirstUsing = true @AppStorage("LastUsingVer") var lastUsingVer = "" @AppStorage("IsReadTerms") var isReadTerms = false + @AppStorage("IsOldSystemTipped") var isOldSystemTipped = false @AppStorage("DedeUserID") var dedeUserID = "" @AppStorage("DedeUserID__ckMd5") var dedeUserID__ckMd5 = "" @AppStorage("SESSDATA") var sessdata = "" @@ -34,6 +35,7 @@ struct ContentView: View { @State var mainTabSelection = 1 @State var isTermsPresented = false @State var userFaceUrl = "" + @State var isOldSystemTipPresented = false @FocusState var isSearchKeyboardFocused: Bool var body: some View { #if !os(visionOS) && !os(macOS) @@ -92,11 +94,29 @@ struct ContentView: View { .sheet(isPresented: $isTermsPresented, onDismiss: { isReadTerms = true }, content: { TermsListView() }) + .sheet(isPresented: $isOldSystemTipPresented, onDismiss: { + isOldSystemTipped = true + }, content: { + ScrollView { + VStack { + Text("过时的 watchOS") + .font(.title3) + Text("您的 Apple Watch 运行的 watchOS 已过时,喵哩喵哩将在 2024/6/10 之后停止支持") + Text("停止支持后,将不再为此版本系统提供功能性更新") + Text("Darock 建议您保持更新最新版本系统") + } + } + }) .onAppear { #if os(watchOS) if !isReadTerms { isTermsPresented = true } + if #unavailable(watchOS 10) { + if !isOldSystemTipped { + isOldSystemTipPresented = true + } + } #endif } } diff --git a/MeowBili/MeowBili-Bridging-Header.h b/MeowBili/MeowBili-Bridging-Header.h index d11b75099..27bcaed20 100644 --- a/MeowBili/MeowBili-Bridging-Header.h +++ b/MeowBili/MeowBili-Bridging-Header.h @@ -9,5 +9,6 @@ #import "CodingTime.h" #import "AVExtension.h" #import "NowPlayingExtension.h" +#import "PrivateSymbols.h" #endif /* DarockBili_Bridging_Header_h */ diff --git a/MeowBili/MeowBiliApp.swift b/MeowBili/MeowBiliApp.swift index e2716e2b8..c39fb8368 100644 --- a/MeowBili/MeowBiliApp.swift +++ b/MeowBili/MeowBiliApp.swift @@ -134,11 +134,15 @@ struct DarockBili_Watch_AppApp: App { @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate #endif @Environment(\.scenePhase) var scenePhase + @Environment(\.isLuminanceReduced) var isLuminanceReduced // Screen Time @AppStorage("isSleepNotificationOn") var isSleepNotificationOn = false @AppStorage("notifyHour") var notifyHour = 0 @AppStorage("notifyMinute") var notifyMinute = 0 @AppStorage("IsScreenTimeEnabled") var isScreenTimeEnabled = true + @AppStorage("BlurWhenScreenSleep") var blurWhenScreenSleep = false + @AppStorage("IsReduceBrightness") var isReduceBrightness = false + @AppStorage("ReduceBrightnessPercent") var reduceBrightnessPercent = 0.1 @State var screenTimeCaculateTimer: Timer? @State var showTipText = "" @State var showTipSymbol = "" @@ -163,6 +167,11 @@ struct DarockBili_Watch_AppApp: App { // Navigators @State var isUrlOpenVideoPresented = false @State var urlOpenVideoDetails = [String: String]() + // Audio Player + @State var shouldShowAudioPlayerPresenter = false + @State var audioPlayerPresenterOffset: CGFloat = 0 + @State var isAudioPlayerPresented = false // Update from AudioPlayer.swift + @State var audioPlayerShouldPlayWhenEnter = true #if os(watchOS) @State var isMemoryWarningPresented = false #else @@ -210,6 +219,34 @@ struct DarockBili_Watch_AppApp: App { #if !os(visionOS) #if os(watchOS) ContentView() + if shouldShowAudioPlayerPresenter { + VStack { + Spacer() + HStack { + Spacer() + Image(systemName: "chevron.compact.up") + .font(.system(size: 20)) + Spacer() + } + .gesture( + DragGesture() + .onChanged { v in + if v.translation.height > 0 { + audioPlayerPresenterOffset = v.translation.height + } + } + .onEnded { v in + audioPlayerPresenterOffset = 0 + if v.translation.height > 30 { + audioPlayerShouldPlayWhenEnter = false + isAudioPlayerPresented = true + } + } + ) + } + .ignoresSafeArea() + .offset(y: audioPlayerPresenterOffset) + } VStack { Spacer() if isShowingTip { @@ -256,6 +293,13 @@ struct DarockBili_Watch_AppApp: App { } .ignoresSafeArea() .allowsHitTesting(false) + if isReduceBrightness { + Rectangle() + .fill(Color.black) + .ignoresSafeArea() + .opacity(reduceBrightnessPercent) + .allowsHitTesting(false) + } #else NavigationStack { ZStack { @@ -319,8 +363,13 @@ struct DarockBili_Watch_AppApp: App { #endif } + .blur(radius: isLuminanceReduced && blurWhenScreenSleep ? 12 : 0) #if os(watchOS) .sheet(isPresented: $isMemoryWarningPresented, content: { MemoryWarningView() }) + .sheet(isPresented: $isAudioPlayerPresented, onDismiss: { + pIsAudioPlayerPresented = false + isAudioPlayerPresented = false + }, content: { AudioPlayerView(shouldPlayWhenEnter: $audioPlayerShouldPlayWhenEnter) }) #endif .onAppear { #if os(watchOS) @@ -330,6 +379,8 @@ struct DarockBili_Watch_AppApp: App { Timer.scheduledTimer(withTimeInterval: 0.05, repeats: true) { _ in showTipText = pShowTipText showTipSymbol = pShowTipSymbol + shouldShowAudioPlayerPresenter = !audioPlayerPlayItems.isEmpty + isAudioPlayerPresented = pIsAudioPlayerPresented ? true : isAudioPlayerPresented UserDefaults.standard.set(isLowBatteryMode, forKey: "IsInLowBatteryMode") Timer.scheduledTimer(withTimeInterval: 0.2, repeats: false) { _ in isShowingTip = pIsShowingTip diff --git a/MeowBili/Models/Audio.swift b/MeowBili/Models/Audio.swift new file mode 100644 index 000000000..02393e93a --- /dev/null +++ b/MeowBili/Models/Audio.swift @@ -0,0 +1,26 @@ +// +// +// Audio.swift +// DarockBili +// +// Created by memz233 on 2024/4/16. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the MeowBili open source project +// +// Copyright (c) 2024 Darock Studio and the MeowBili project authors +// Licensed under GNU General Public License v3 +// +// See https://darock.top/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import Foundation + +struct AudioPlayItem { + var videoDetails: [String: String] + var videoLink: String + var videoBvid: String + var videoCID: Int64 +} diff --git a/MeowBili/Others/CCodes/NowPlayingExtension.m b/MeowBili/Others/CCodes/NowPlayingExtension.m index 3ddc7bb9b..b7aa5d155 100644 --- a/MeowBili/Others/CCodes/NowPlayingExtension.m +++ b/MeowBili/Others/CCodes/NowPlayingExtension.m @@ -18,6 +18,9 @@ #import #import +#if TARGET_OS_WATCH +#import +#endif #import "NowPlayingExtension.h" @implementation NowPlayingExtension: NSObject @@ -29,6 +32,17 @@ +(void) setPlayingInfoTitle: (NSString *) title artist: (NSString *) artist artw [session setActive:true error:nil]; [[UIApplication sharedApplication] beginReceivingRemoteControlEvents]; + NSMutableDictionary *info = [[NSMutableDictionary alloc] init]; + [info setValue:title forKey:MPMediaItemPropertyTitle]; + [info setValue:artist forKey:MPMediaItemPropertyArtist]; + MPMediaItemArtwork *relArtwork = [[MPMediaItemArtwork alloc] initWithBoundsSize:artwork.size requestHandler:^(CGSize _){ return artwork; }]; + [info setValue:relArtwork forKey:MPMediaItemPropertyArtwork]; + MPNowPlayingInfoCenter.defaultCenter.nowPlayingInfo = info; + #else + AVAudioSession *session = [AVAudioSession sharedInstance]; + [session setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionDuckOthers error:nil]; + [session setActive:true error:nil]; + NSMutableDictionary *info = [[NSMutableDictionary alloc] init]; [info setValue:title forKey:MPMediaItemPropertyTitle]; [info setValue:artist forKey:MPMediaItemPropertyArtist]; diff --git a/MeowBili/Others/CCodes/PrivateSymbols.h b/MeowBili/Others/CCodes/PrivateSymbols.h new file mode 100644 index 000000000..aab43f67c --- /dev/null +++ b/MeowBili/Others/CCodes/PrivateSymbols.h @@ -0,0 +1,39 @@ +// +// +// PrivateSymbols.h +// DarockBili +// +// Created by memz233 on 2024/4/19. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the MeowBili open source project +// +// Copyright (c) 2024 Darock Studio and the MeowBili project authors +// Licensed under GNU General Public License v3 +// +// See https://darock.top/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +#ifndef PrivateSymbols_h +#define PrivateSymbols_h + +#import + +@interface SFSCoreGlyphsBundle: NSObject +@property (nonatomic, class, readonly) NSBundle *private; +@end + +@interface _UIAssetManager : NSObject ++ (instancetype)assetManagerForBundle:(NSBundle *)bundle; +- (UIImage *)imageNamed:(NSString *)name; +@end + +@interface UIImage (SFSCoreGlyphsBundle) + +- (instancetype)initWithPrivateSystemName:(NSString *)name; + +@end + +#endif /* PrivateSymbols_h */ diff --git a/MeowBili/Others/CCodes/PrivateSymbols.m b/MeowBili/Others/CCodes/PrivateSymbols.m new file mode 100644 index 000000000..dfb562631 --- /dev/null +++ b/MeowBili/Others/CCodes/PrivateSymbols.m @@ -0,0 +1,31 @@ +// +// +// PrivateSymbols.m +// DarockBili +// +// Created by memz233 on 2024/4/19. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the MeowBili open source project +// +// Copyright (c) 2024 Darock Studio and the MeowBili project authors +// Licensed under GNU General Public License v3 +// +// See https://darock.top/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +#import +#import "PrivateSymbols.h" + +@implementation UIImage (SFCoreGlyphBundle) + +- (instancetype)initWithPrivateSystemName:(NSString *)name { + NSBundle *const bundle = [NSClassFromString(@"SFSCoreGlyphsBundle") private]; + _UIAssetManager *const assetManager = [_UIAssetManager assetManagerForBundle:bundle]; + self = [[assetManager imageNamed:name] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + return self; +} + +@end diff --git a/MeowBili/Others/SettingsView.swift b/MeowBili/Others/SettingsView.swift index 83a9e9a02..d9341c42a 100644 --- a/MeowBili/Others/SettingsView.swift +++ b/MeowBili/Others/SettingsView.swift @@ -252,8 +252,8 @@ struct SettingsView: View { Color.blue .frame(width: 20, height: 20) .clipShape(Circle()) - Image(systemName: "hand.wave.fill") - .font(.system(size: 12)) + Image(privateSystemName: "hand.side.pinch.fill") + .scaleEffect(0.7) } Text("Settings.gesture") } @@ -270,6 +270,18 @@ struct SettingsView: View { Text("Settings.screen-time") } }) + NavigationLink(destination: { AccessibilitySettingsView().navigationTitle("辅助功能") }, label: { + HStack { + ZStack { + Color.blue + .frame(width: 20, height: 20) + .clipShape(Circle()) + Image(systemName: "accessibility") + .font(.system(size: 18)) + } + Text("辅助功能") + } + }) NavigationLink(destination: { StorageSettingsView().navigationTitle("储存空间") }, label: { HStack { ZStack { @@ -344,7 +356,7 @@ struct SettingsView: View { Text("Settings.about") } }) - if (Bundle.main.infoDictionary?["CFBundleIdentifier"] as! String) != "com.darock.DarockBili.watchkitapp" { + if (Bundle.main.infoDictionary?["CFBundleIdentifier"] as! String) == "com.darock.DarockBili.watchkitapp" { NavigationLink(destination: { SoftwareUpdateView().navigationTitle("Settings.update") }, label: { HStack { ZStack { @@ -609,6 +621,21 @@ struct ScreenTimeSettingsView: View { } } +struct AccessibilitySettingsView: View { + @AppStorage("IsReduceBrightness") var isReduceBrightness = false + @AppStorage("ReduceBrightnessPercent") var reduceBrightnessPercent = 0.1 + var body: some View { + List { + Section { + Toggle("降低亮度", isOn: $isReduceBrightness) + if isReduceBrightness { + Slider(value: $reduceBrightnessPercent, in: 0.0...0.8, step: 0.05) + } + } + } + } +} + struct StorageSettingsView: View { @State var isLoading = true @State var docSize: UInt64 = 0 @@ -1085,8 +1112,12 @@ struct DebugMenuView: View { } struct PrivacySettingsView: View { + @AppStorage("BlurWhenScreenSleep") var blurWhenScreenSleep = false var body: some View { List { + #if os(watchOS) + Toggle("垂下手腕时隐藏内容", isOn: $blurWhenScreenSleep) + #endif Section { NavigationLink(destination: { AnalyzeAImprove() }, label: { Text("分析与改进") diff --git a/MeowBili/PersonalCenter/DownloadsView.swift b/MeowBili/PersonalCenter/DownloadsView.swift index 6449098f0..801d4bfda 100644 --- a/MeowBili/PersonalCenter/DownloadsView.swift +++ b/MeowBili/PersonalCenter/DownloadsView.swift @@ -31,101 +31,91 @@ struct DownloadsView: View { @State var metadatas = [[String: String]]() @State var isPlayerPresented = false @State var vRootPath = "" + @State var searchInput = "" var body: some View { List { #if os(watchOS) if #unavailable(watchOS 10) { - NavigationLink(destination: { DownloadingListView() }, label: { - Label("Download.list", systemImage: "list.bullet.below.rectangle") - }) + Section { + NavigationLink(destination: { DownloadingListView() }, label: { + Label("Download.list", systemImage: "list.bullet.below.rectangle") + }) + } } #endif - if metadatas.count != 0 { - ForEach(0...metadatas.count - 1, id: \.self) { i in - if metadatas[i]["notGet"] == nil { -// NavigationLink(destination: {VideoDetailView(videoDetails: metadatas[i])}, label: { -// HStack { -// AsyncImage(url: URL(string: metadatas[i]["Pic"]! + "@40w_30h")) -// .cornerRadius(5) -// VStack { -// Text(metadatas[i]["Title"]!) -// .font(.system(size: 15, weight: .bold)) -// .lineLimit(3) -// HStack { -// Label(metadatas[i]["View"]!, systemImage: "play.circle") -// Label(metadatas[i]["UP"]!, systemImage: "person") -// } -// .font(.system(size: 11)) -// .foregroundColor(.gray) -// .lineLimit(1) -// } -// } -// }) - Button(action: { - DownloadsView.willPlayVideoPath = vRootPath + metadatas[i]["Path"]! - isPlayerPresented = true - }, label: { - HStack { - #if !os(visionOS) - WebImage(url: URL(string: metadatas[i]["Pic"]! + "@100w")!, options: [.progressiveLoad, .scaleDownLargeImages]) - .placeholder { - RoundedRectangle(cornerRadius: 7) - .frame(width: 50, height: 30) - .foregroundColor(Color(hex: 0x3D3D3D)) - .redacted(reason: .placeholder) - } - .resizable() - .scaledToFit() - .frame(width: 50) - .cornerRadius(7) - #else - AsyncImage(url: URL(string: metadatas[i]["Pic"]! + "@100w")!) { phase in - switch phase { - case .empty: - RoundedRectangle(cornerRadius: 7) - .frame(width: 50, height: 30) - .foregroundColor(Color(hex: 0x3D3D3D)) - .redacted(reason: .placeholder) - case .success(let image): - image.resizable() - case .failure(let error): - RoundedRectangle(cornerRadius: 7) - .frame(width: 50, height: 30) - .foregroundColor(Color(hex: 0x3D3D3D)) - .redacted(reason: .placeholder) + Section { + if metadatas.count != 0 { + ForEach(0...metadatas.count - 1, id: \.self) { i in + if metadatas[i]["notGet"] == nil { + if searchInput.isEmpty || metadatas[i]["Title"]!.contains(searchInput) { + Button(action: { + DownloadsView.willPlayVideoPath = vRootPath + metadatas[i]["Path"]! + isPlayerPresented = true + }, label: { + HStack { +#if !os(visionOS) + WebImage(url: URL(string: metadatas[i]["Pic"]! + "@100w")!, options: [.progressiveLoad, .scaleDownLargeImages]) + .placeholder { + RoundedRectangle(cornerRadius: 7) + .frame(width: 50, height: 30) + .foregroundColor(Color(hex: 0x3D3D3D)) + .redacted(reason: .placeholder) + } + .resizable() + .scaledToFit() + .frame(width: 50) + .cornerRadius(7) +#else + AsyncImage(url: URL(string: metadatas[i]["Pic"]! + "@100w")!) { phase in + switch phase { + case .empty: + RoundedRectangle(cornerRadius: 7) + .frame(width: 50, height: 30) + .foregroundColor(Color(hex: 0x3D3D3D)) + .redacted(reason: .placeholder) + case .success(let image): + image.resizable() + case .failure(let error): + RoundedRectangle(cornerRadius: 7) + .frame(width: 50, height: 30) + .foregroundColor(Color(hex: 0x3D3D3D)) + .redacted(reason: .placeholder) + } + } + .scaledToFit() + .frame(width: 50) + .cornerRadius(7) +#endif + VStack { + Text(metadatas[i]["Title"]!) + .font(.system(size: 14, weight: .bold)) + .lineLimit(3) + Label(metadatas[i]["UP"]!, systemImage: "person") + .font(.system(size: 11)) + .foregroundColor(.gray) + .lineLimit(1) + } } - } - .scaledToFit() - .frame(width: 50) - .cornerRadius(7) - #endif - VStack { - Text(metadatas[i]["Title"]!) - .font(.system(size: 14, weight: .bold)) - .lineLimit(3) - Label(metadatas[i]["UP"]!, systemImage: "person") - .font(.system(size: 11)) - .foregroundColor(.gray) - .lineLimit(1) + }) + .swipeActions(edge: .trailing, allowsFullSwipe: true) { + Button(role: .destructive, action: { + try! FileManager.default.removeItem(atPath: vRootPath + metadatas[i]["Path"]!) + }, label: { + Image(systemName: "xmark.bin.fill") + }) } } - }) - .swipeActions(edge: .trailing, allowsFullSwipe: true) { - Button(role: .destructive, action: { - try! FileManager.default.removeItem(atPath: vRootPath + metadatas[i]["Path"]!) - }, label: { - Image(systemName: "xmark.bin.fill") - }) + } else { + } - } else { - } } } } .navigationTitle("离线缓存") - .navigationBarTitleDisplayMode(.large) + .navigationBarTitleDisplayMode(.inline) .sheet(isPresented: $isPlayerPresented, content: { OfflineVideoPlayer() }) + .searchable(text: $searchInput) .toolbar { #if !os(watchOS) ToolbarItem(placement: .topBarTrailing) { @@ -165,7 +155,7 @@ struct DownloadsView: View { } } } - metadatas.sort { Int($0["Date"] ?? "0")! < Int($1["Date"] ?? "0")! } + metadatas.sort { Double($0["Time"] ?? "0.0")! > Double($1["Time"] ?? "0.0")! } } } } diff --git a/MeowBili/PersonalCenter/HistoryView.swift b/MeowBili/PersonalCenter/HistoryView.swift index c694a2de8..9b172c060 100644 --- a/MeowBili/PersonalCenter/HistoryView.swift +++ b/MeowBili/PersonalCenter/HistoryView.swift @@ -35,41 +35,44 @@ struct HistoryView: View { @State var isEmptyHistoryPresented = false @State var selectedEmptyAction = 0 @State var isDoingEmpty = false + @State var searchInput = "" var body: some View { List { Group { if histories.count != 0 { ForEach(0...histories.count - 1, id: \.self) { i in - if (histories[i] as! [String: Any])["Type"]! as! String == "archive" { - VideoCard(histories[i] as! [String: String]) - .swipeActions { - Button(role: .destructive, action: { - let headers: HTTPHeaders = [ - "cookie": "SESSDATA=\(sessdata);", - "User-Agent": "Mozilla/5.0 (X11; CrOS x86_64 14541.0.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" - ] - AF.request("https://api.bilibili.com/x/v2/history/delete?kid=archive_\(bv2av(bvid: (histories[i] as! [String: String])["BV"]!))&csrf=\(biliJct)", method: .post, headers: headers).response { response in - debugPrint(response) - } - }, label: { - Image(systemName: "xmark.bin.fill") - }) - } - } else if (histories[i] as! [String: Any])["Type"]! as! String == "pgc" { - BangumiCard((histories[i] as! [String: Any])["Data"] as! BangumiData) - .swipeActions { - Button(role: .destructive, action: { - let headers: HTTPHeaders = [ - "cookie": "SESSDATA=\(sessdata);", - "User-Agent": "Mozilla/5.0 (X11; CrOS x86_64 14541.0.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" - ] - AF.request("https://api.bilibili.com/x/v2/history/delete?kid=pgc_\(((histories[i] as! [String: Any])["Data"] as! BangumiData).seasonId)&csrf=\(biliJct)", method: .post, headers: headers).response { response in - debugPrint(response) - } - }, label: { - Image(systemName: "xmark.bin.fill") - }) - } + if searchInput.isEmpty || (((histories[i] as! [String: Any])["Title"] as? String) ?? "").contains(searchInput) { + if (histories[i] as! [String: Any])["Type"]! as! String == "archive" { + VideoCard(histories[i] as! [String: String]) + .swipeActions { + Button(role: .destructive, action: { + let headers: HTTPHeaders = [ + "cookie": "SESSDATA=\(sessdata);", + "User-Agent": "Mozilla/5.0 (X11; CrOS x86_64 14541.0.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" + ] + AF.request("https://api.bilibili.com/x/v2/history/delete?kid=archive_\(bv2av(bvid: (histories[i] as! [String: String])["BV"]!))&csrf=\(biliJct)", method: .post, headers: headers).response { response in + debugPrint(response) + } + }, label: { + Image(systemName: "xmark.bin.fill") + }) + } + } else if (histories[i] as! [String: Any])["Type"]! as! String == "pgc" { + BangumiCard((histories[i] as! [String: Any])["Data"] as! BangumiData) + .swipeActions { + Button(role: .destructive, action: { + let headers: HTTPHeaders = [ + "cookie": "SESSDATA=\(sessdata);", + "User-Agent": "Mozilla/5.0 (X11; CrOS x86_64 14541.0.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" + ] + AF.request("https://api.bilibili.com/x/v2/history/delete?kid=pgc_\(((histories[i] as! [String: Any])["Data"] as! BangumiData).seasonId)&csrf=\(biliJct)", method: .post, headers: headers).response { response in + debugPrint(response) + } + }, label: { + Image(systemName: "xmark.bin.fill") + }) + } + } } } } else { @@ -107,8 +110,9 @@ struct HistoryView: View { } #endif } + .searchable(text: $searchInput) .navigationTitle("历史记录") - .navigationBarTitleDisplayMode(.large) + .navigationBarTitleDisplayMode(.inline) .onAppear { if !isLoaded { let headers: HTTPHeaders = [ diff --git a/MeowBili/PersonalCenter/PersonAccountView.swift b/MeowBili/PersonalCenter/PersonAccountView.swift index fac7a5f9c..2de6e6ba0 100644 --- a/MeowBili/PersonalCenter/PersonAccountView.swift +++ b/MeowBili/PersonalCenter/PersonAccountView.swift @@ -86,12 +86,12 @@ struct PersonAccountView: View { List { if sessdata == "" { NavigationLink(destination: { LoginView() }, label: { - Label("User.tap-to-login", systemImage: "rectangle.and.pencil.and.ellipsis") + Label("User.tap-to-login", privateSystemImage: "apple.logo.lock.open") }) Button(action: { isUserSwitchPresented = true }, label: { HStack { HStack { - Image(systemName: "person.2.badge.key.fill") + Image(privateSystemName: "person.lanyardcard.fill") .foregroundColor(.accentColor) Text("User.switch") } @@ -237,7 +237,7 @@ struct PersonAccountView: View { Button(action: { isUserSwitchPresented = true }, label: { HStack { HStack { - Image(systemName: "person.2.badge.key.fill") + Image(privateSystemName: "person.lanyardcard.fill") .foregroundColor(.accentColor) Text("User.switch") } diff --git a/MeowBili/Video/AudioPlayerView.swift b/MeowBili/Video/AudioPlayerView.swift index 64f62935b..8173e386c 100644 --- a/MeowBili/Video/AudioPlayerView.swift +++ b/MeowBili/Video/AudioPlayerView.swift @@ -25,215 +25,101 @@ import MediaPlayer import AVFoundation import SDWebImageSwiftUI +var pIsAudioPlayerPresented = false +var audioPlayerPlayItems = [AudioPlayItem]() +var audioPlayerNowPlayingItemIndex = 0 +var audioPlayerMainPlayer = AVPlayer() +var audioPlayerMainCurrentItem: AVPlayerItem? + struct AudioPlayerView: View { - var videoDetails: [String: String] - @Binding var videoLink: String - @Binding var videoBvid: String - @Binding var videoCID: Int64 + @Binding var shouldPlayWhenEnter: Bool @AppStorage("AudioPlayBehavior") var audioPlayBehavior = AudioPlayerBehavior.singleLoop.rawValue - @State var audioPlayer = AVPlayer() - @State var playerItem: AVPlayerItem! = nil @State var isPlaying = true @State var finishObserver: AnyCancellable? @State var startObserver: AnyCancellable? @State var backgroundPicOpacity = 0.0 - @State var nowPlayTimeTimer: Timer? + @State var nowPlayMediaTimer: Timer? + @State var nowPlayingItemIndex = 0 + @State var isNoMedia = true var body: some View { VStack { - #if !os(watchOS) - VStack { - AsyncImage(url: URL(string: videoDetails["Pic"]!)) { phase in - switch phase { - case .empty: - RoundedRectangle(cornerRadius: 10) - .redacted(reason: .placeholder) - case .success(let image): - image.resizable() - case .failure: - RoundedRectangle(cornerRadius: 10) - .redacted(reason: .placeholder) - @unknown default: - RoundedRectangle(cornerRadius: 10) - .redacted(reason: .placeholder) - } - } - .cornerRadius(10) - .scaledToFit() - #if !os(visionOS) - .frame(width: UIScreen.main.bounds.width - 40) - #else - .frame(width: globalWindowSize.width - 40) - #endif - Text(videoDetails["Title"]!) - .font(.system(size: 14, weight: .bold)) - .lineLimit(1) - .padding(.vertical, 7) - .padding(.horizontal, 20) - Button(action: { - let behaviorCase = AudioPlayerBehavior(rawValue: audioPlayBehavior)! - switch behaviorCase { - case .singleLoop: - audioPlayBehavior = AudioPlayerBehavior.pauseWhenFinish.rawValue - case .pauseWhenFinish: - audioPlayBehavior = AudioPlayerBehavior.singleLoop.rawValue - case .listLoop: - audioPlayBehavior = AudioPlayerBehavior.singleLoop.rawValue - case .exitWhenFinish: - audioPlayBehavior = AudioPlayerBehavior.singleLoop.rawValue - } - RefreshPlayerBehavior(player: audioPlayer, playerItem: playerItem) - }, label: { - Image(systemName: { () -> String in - let behaviorCase = AudioPlayerBehavior(rawValue: audioPlayBehavior)! - switch behaviorCase { - case .singleLoop: - return "repeat.1" - case .pauseWhenFinish: - return "pause" - case .listLoop: - return "repeat" - case .exitWhenFinish: - return "rectangle.portrait.and.arrow.forward" - } - }()) - }) - .buttonStyle(.borderedProminent) - Button(action: { - isPlaying.toggle() - if isPlaying { - audioPlayer.play() - } else { - audioPlayer.pause() - } - }, label: { - Image(systemName: isPlaying ? "pause.fill" : "play.fill") - }) - .buttonStyle(.borderedProminent) - } - #else + #if os(watchOS) if #available(watchOS 10, *) { - VStack { - AsyncImage(url: URL(string: videoDetails["Pic"]! + "@110w_85h")) - .cornerRadius(10) - Text(videoDetails["Title"]!) - .font(.system(size: 14, weight: .bold)) - .lineLimit(1) - .padding(.vertical, 7) - .padding(.horizontal, 20) - } - .containerBackground(for: .navigation) { - ZStack { - WebImage(url: URL(string: videoDetails["Pic"]!)) + if !isNoMedia { + VStack { + WebImage(url: URL(string: audioPlayerPlayItems[nowPlayingItemIndex].videoDetails["Pic"]! + "@220w_170h")) .resizable() - .onSuccess { _, _, _ in - backgroundPicOpacity = 1.0 - } - .blur(radius: 20) - .opacity(backgroundPicOpacity) - .animation(.easeOut(duration: 1.2), value: backgroundPicOpacity) - Color.black - .opacity(0.4) + .cornerRadius(10) + .scaledToFit() + .frame(width: 110, height: 85) + Text(audioPlayerPlayItems[nowPlayingItemIndex].videoDetails["Title"]!) + .font(.system(size: 14, weight: .bold)) + .lineLimit(1) + .padding(.vertical, 7) + .padding(.horizontal, 20) } - } - .toolbar { - ToolbarItemGroup(placement: .bottomBar) { - Button(action: { - let behaviorCase = AudioPlayerBehavior(rawValue: audioPlayBehavior)! - switch behaviorCase { - case .singleLoop: - audioPlayBehavior = AudioPlayerBehavior.pauseWhenFinish.rawValue - case .pauseWhenFinish: - audioPlayBehavior = AudioPlayerBehavior.singleLoop.rawValue - case .listLoop: - audioPlayBehavior = AudioPlayerBehavior.singleLoop.rawValue - case .exitWhenFinish: - audioPlayBehavior = AudioPlayerBehavior.singleLoop.rawValue - } - RefreshPlayerBehavior(player: audioPlayer, playerItem: playerItem) - }, label: { - Image(systemName: { () -> String in + .containerBackground(for: .navigation) { + ZStack { + WebImage(url: URL(string: audioPlayerPlayItems[nowPlayingItemIndex].videoDetails["Pic"]!)) + .resizable() + .onSuccess { _, _, _ in + backgroundPicOpacity = 1.0 + } + .blur(radius: 20) + .opacity(backgroundPicOpacity) + .animation(.easeOut(duration: 1.2), value: backgroundPicOpacity) + Color.black + .opacity(0.4) + } + } + .toolbar { + ToolbarItemGroup(placement: .bottomBar) { + Button(action: { let behaviorCase = AudioPlayerBehavior(rawValue: audioPlayBehavior)! switch behaviorCase { case .singleLoop: - return "repeat.1" + audioPlayBehavior = AudioPlayerBehavior.pauseWhenFinish.rawValue case .pauseWhenFinish: - return "pause" + audioPlayBehavior = AudioPlayerBehavior.singleLoop.rawValue case .listLoop: - return "repeat" + audioPlayBehavior = AudioPlayerBehavior.singleLoop.rawValue case .exitWhenFinish: - return "rectangle.portrait.and.arrow.forward" + audioPlayBehavior = AudioPlayerBehavior.singleLoop.rawValue } - }()) - }) - Button(action: { - isPlaying.toggle() - if isPlaying { - audioPlayer.play() - } else { - audioPlayer.pause() - } - }, label: { - Image(systemName: isPlaying ? "pause.fill" : "play.fill") - }) - .controlSize(.large) - VolumeControlView() - .controlSize(.regular) + RefreshPlayerBehavior(player: audioPlayerMainPlayer, playerItem: audioPlayerMainCurrentItem) + }, label: { + Image(systemName: { () -> String in + let behaviorCase = AudioPlayerBehavior(rawValue: audioPlayBehavior)! + switch behaviorCase { + case .singleLoop: + return "repeat.1" + case .pauseWhenFinish: + return "pause" + case .listLoop: + return "repeat" + case .exitWhenFinish: + return "rectangle.portrait.and.arrow.forward" + } + }()) + }) + Button(action: { + isPlaying.toggle() + if isPlaying { + audioPlayerMainPlayer.play() + } else { + audioPlayerMainPlayer.pause() + } + }, label: { + Image(systemName: isPlaying ? "pause.fill" : "play.fill") + }) + .controlSize(.large) + VolumeControlView() + .controlSize(.regular) + } } } } else { - VStack { - AsyncImage(url: URL(string: videoDetails["Pic"]! + "@110w_85h")) - .cornerRadius(10) - Text(videoDetails["Title"]!) - .font(.system(size: 14, weight: .bold)) - .lineLimit(1) - .padding(.vertical, 7) - .padding(.horizontal, 20) - Text(videoDetails["Title"]!) - .font(.system(size: 14, weight: .bold)) - .lineLimit(1) - .padding(.vertical, 7) - .padding(.horizontal, 20) - Button(action: { - let behaviorCase = AudioPlayerBehavior(rawValue: audioPlayBehavior)! - switch behaviorCase { - case .singleLoop: - audioPlayBehavior = AudioPlayerBehavior.pauseWhenFinish.rawValue - case .pauseWhenFinish: - audioPlayBehavior = AudioPlayerBehavior.singleLoop.rawValue - case .listLoop: - audioPlayBehavior = AudioPlayerBehavior.singleLoop.rawValue - case .exitWhenFinish: - audioPlayBehavior = AudioPlayerBehavior.singleLoop.rawValue - } - RefreshPlayerBehavior(player: audioPlayer, playerItem: playerItem) - }, label: { - Image(systemName: { () -> String in - let behaviorCase = AudioPlayerBehavior(rawValue: audioPlayBehavior)! - switch behaviorCase { - case .singleLoop: - return "repeat.1" - case .pauseWhenFinish: - return "pause" - case .listLoop: - return "repeat" - case .exitWhenFinish: - return "rectangle.portrait.and.arrow.forward" - } - }()) - }) - Button(action: { - isPlaying.toggle() - if isPlaying { - audioPlayer.play() - } else { - audioPlayer.pause() - } - }, label: { - Image(systemName: isPlaying ? "pause.fill" : "play.fill") - }) - VolumeControlView() - } + } #endif } @@ -250,63 +136,42 @@ struct AudioPlayerView: View { print(error) } - let asset = AVURLAsset(url: URL(string: videoLink)!, options: ["AVURLAssetHTTPHeaderFieldsKey": ["User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15", "Referer": "https://www.bilibili.com"]]) - playerItem = AVPlayerItem(asset: asset) - audioPlayer = AVPlayer(playerItem: playerItem) - - let cover = UIImage(data: try! Data(contentsOf: URL(string: videoDetails["Pic"]!)!))! - NowPlayingExtension.setPlayingInfoTitle(videoDetails["Title"]!, artist: videoDetails["UP"]!, artwork: cover) - - MPRemoteCommandCenter.shared().playCommand.addTarget { _ in - if !isPlaying { - isPlaying.toggle() - audioPlayer.play() - } - return .success - } - MPRemoteCommandCenter.shared().pauseCommand.addTarget { _ in - if isPlaying { - isPlaying.toggle() - audioPlayer.pause() + nowPlayMediaTimer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { _ in + if audioPlayerNowPlayingItemIndex < audioPlayerPlayItems.count && audioPlayerNowPlayingItemIndex >= 0 { + isNoMedia = false + } else { + isNoMedia = true } - return .success - } - MPRemoteCommandCenter.shared().seekForwardCommand.addTarget { _ in - audioPlayer.seek(to: CMTime(seconds: audioPlayer.currentTime().seconds + 15, preferredTimescale: 1)) - return .success - } - MPRemoteCommandCenter.shared().seekBackwardCommand.addTarget { _ in - audioPlayer.seek(to: CMTime(seconds: audioPlayer.currentTime().seconds - 15, preferredTimescale: 1)) - return .success } - startObserver = playerItem.publisher(for: \.status) - .sink { status in - if status == .readyToPlay { - audioPlayer.play() - } + if shouldPlayWhenEnter && audioPlayerNowPlayingItemIndex < audioPlayerPlayItems.count && audioPlayerNowPlayingItemIndex >= 0 { + let asset = AVURLAsset(url: URL(string: audioPlayerPlayItems[audioPlayerNowPlayingItemIndex].videoLink)!, options: ["AVURLAssetHTTPHeaderFieldsKey": ["User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15", "Referer": "https://www.bilibili.com"]]) + audioPlayerMainCurrentItem = AVPlayerItem(asset: asset) + audioPlayerMainPlayer = AVPlayer(playerItem: audioPlayerMainCurrentItem) + + if let cd = try? Data(contentsOf: URL(string: audioPlayerPlayItems[audioPlayerNowPlayingItemIndex].videoDetails["Pic"]!)!) { + let cover = UIImage(data: cd)! + NowPlayingExtension.setPlayingInfoTitle(audioPlayerPlayItems[audioPlayerNowPlayingItemIndex].videoDetails["Title"]!, artist: audioPlayerPlayItems[audioPlayerNowPlayingItemIndex].videoDetails["UP"]!, artwork: cover) + } else { + NowPlayingExtension.setPlayingInfoTitle(audioPlayerPlayItems[audioPlayerNowPlayingItemIndex].videoDetails["Title"]!, artist: audioPlayerPlayItems[audioPlayerNowPlayingItemIndex].videoDetails["UP"]!, artwork: nil) } - - Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { timer in - nowPlayTimeTimer = timer - debugPrint(audioPlayer.currentTime().seconds) + + startObserver = audioPlayerMainCurrentItem?.publisher(for: \.status) + .sink { status in + if status == .readyToPlay { + audioPlayerMainPlayer.play() + } + } } - RefreshPlayerBehavior(player: audioPlayer, playerItem: playerItem) + RefreshPlayerBehavior(player: audioPlayerMainPlayer, playerItem: audioPlayerMainCurrentItem) } .onDisappear { - startObserver?.cancel() - finishObserver?.cancel() - nowPlayTimeTimer?.invalidate() - - MPRemoteCommandCenter.shared().playCommand.removeTarget(self) - MPRemoteCommandCenter.shared().pauseCommand.removeTarget(self) - MPRemoteCommandCenter.shared().seekForwardCommand.removeTarget(self) - MPRemoteCommandCenter.shared().seekBackwardCommand.removeTarget(self) + nowPlayMediaTimer?.invalidate() } } - func RefreshPlayerBehavior(player: AVPlayer, playerItem: AVPlayerItem) { + func RefreshPlayerBehavior(player: AVPlayer, playerItem: AVPlayerItem?) { let behaviorCase = AudioPlayerBehavior(rawValue: audioPlayBehavior)! switch behaviorCase { case .singleLoop: diff --git a/MeowBili/Video/VideoDetailView.swift b/MeowBili/Video/VideoDetailView.swift index 847d074b5..4e2efabfb 100644 --- a/MeowBili/Video/VideoDetailView.swift +++ b/MeowBili/Video/VideoDetailView.swift @@ -66,7 +66,6 @@ struct VideoDetailView: View { @State var nowPlayingCount = "0" @State var publishTime = "" @State var videoDesc = "" - @State var isAudioPlayerPresented = false @State var backgroundPicOpacity = 0.0 @State var mainVerticalTabViewSelection = 1 @State var videoPartShouldShowDownloadTip = false @@ -93,8 +92,8 @@ struct VideoDetailView: View { @FocusState var isEditingDanmaku: Bool #else @State var isVideoPlayerPresented = false - @State var isNowPlayingPresented = false @State var continueQr: CGImage? + @State var isFirstLoaded = false #endif var body: some View { Group { @@ -521,9 +520,6 @@ struct VideoDetailView: View { Group { TabView(selection: $mainVerticalTabViewSelection) { VStack { - NavigationLink("", isActive: $isNowPlayingPresented, destination: { AudioPlayerView(videoDetails: videoDetails, videoLink: $videoLink, videoBvid: $videoBvid, videoCID: $videoCID) }) - .frame(width: 0, height: 0) - DetailViewFirstPageBase(videoDetails: $videoDetails, videoPages: $videoPages, honors: $honors, subTitles: $subTitles, isLoading: $isLoading, videoLink: $videoLink, videoBvid: $videoBvid, videoCID: $videoCID) .offset(y: 16) .toolbar { @@ -595,7 +591,7 @@ struct VideoDetailView: View { ToolbarItemGroup(placement: .bottomBar) { Button(action: { isLoading = true - DecodeVideo() + DecodeVideo(isAudio: true) }, label: { Image(systemName: "waveform") }) @@ -709,6 +705,8 @@ struct VideoDetailView: View { .onAppear { #if !os(watchOS) if isDecoded { return } // After user enter a new video then exit, this onAppear method will be re-call + #else + if isFirstLoaded { return } #endif let headers: HTTPHeaders = [ @@ -828,6 +826,8 @@ struct VideoDetailView: View { if recordHistoryTime == "into" { AF.request("https://api.bilibili.com/x/click-interface/web/heartbeat", method: .post, parameters: ["bvid": videoDetails["BV"]!, "mid": dedeUserID, "type": 3, "dt": 2, "play_type": 2, "csrf": biliJct], headers: headers).response { _ in } } + + isFirstLoaded = true #endif if isAllowMixpanel { @@ -835,14 +835,6 @@ struct VideoDetailView: View { } } .onDisappear { - #if os(watchOS) - goodVideos = [[String: String]]() - owner = [String: String]() - stat = [String: String]() - videoDesc = "" - SDImageCache.shared.clearMemory() - #endif - if isAllowMixpanel { Mixpanel.mainInstance().track(event: "Watch Video", properties: videoDetails) } @@ -860,15 +852,6 @@ struct VideoDetailView: View { .sheet(isPresented: $isMoreMenuPresented) { NavigationStack { List { - Section { - Button(action: { - shouldPausePlayer = true - isAudioPlayerPresented = true - isMoreMenuPresented = false - }, label: { - Label("Video.play-in-audio", systemImage: "waveform") - }) - } Section { Button(action: { isDownloadPresented = true @@ -925,19 +908,19 @@ struct VideoDetailView: View { } .presentationDetents([.medium, .large]) } - .sheet(isPresented: $isAudioPlayerPresented, content: { AudioPlayerView(videoDetails: videoDetails, videoLink: $videoLink, videoBvid: $videoBvid, videoCID: $videoCID) }) .userActivity("com.darock.DarockBili.video-play", element: videoDetails) { url, activity in // swiftlint:disable:this unused_closure_parameter activity.addUserInfoEntries(from: videoDetails) } #endif } - func DecodeVideo() { + @inline(__always) + func DecodeVideo(isAudio: Bool = false) { let headers: HTTPHeaders = [ "cookie": "SESSDATA=\(sessdata); buvid3=\(globalBuvid3); buvid4=\(globalBuvid4)", "User-Agent": "Mozilla/5.0 (X11; CrOS x86_64 14541.0.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" ] - if videoGetterSource == "official" { + if videoGetterSource == "official" || isAudio { DarockKit.Network.shared.requestJSON("https://api.bilibili.com/x/web-interface/view?bvid=\(videoDetails["BV"]!)", headers: headers) { respJson, isSuccess in if isSuccess { if !CheckBApiError(from: respJson) { return } @@ -951,7 +934,13 @@ struct VideoDetailView: View { #if !os(watchOS) isDecoded = true #else - isVideoPlayerPresented = true + if !isAudio { + isVideoPlayerPresented = true + } else { + audioPlayerPlayItems.append(.init(videoDetails: videoDetails, videoLink: videoLink, videoBvid: videoBvid, videoCID: videoCID)) + audioPlayerNowPlayingItemIndex = audioPlayerPlayItems.count - 1 + pIsAudioPlayerPresented = true + } isLoading = false #endif } @@ -1169,45 +1158,6 @@ struct VideoDetailView: View { VideoPlayerView(videoDetails: $videoDetails, videoLink: $videoLink, videoBvid: $videoBvid, videoCID: $videoCID) .navigationBarHidden(true) }) - NavigationLink("", isActive: $isNowPlayingPresented, destination: { AudioPlayerView(videoDetails: videoDetails, videoLink: $videoLink, videoBvid: $videoBvid, videoCID: $videoCID) }) - .frame(width: 0, height: 0) - Button(action: { - isLoading = true - - if videoGetterSource == "official" { - let headers: HTTPHeaders = [ - "cookie": "SESSDATA=\(sessdata)", - "User-Agent": "Mozilla/5.0 (X11; CrOS x86_64 14541.0.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" - ] - DarockKit.Network.shared.requestJSON("https://api.bilibili.com/x/web-interface/view?bvid=\(videoDetails["BV"]!)", headers: headers) { respJson, isSuccess in - if isSuccess { - if !CheckBApiError(from: respJson) { return } - let cid = respJson["data"]["pages"][0]["cid"].int64! - DarockKit.Network.shared.requestJSON("https://api.bilibili.com/x/player/playurl?bvid=\(videoDetails["BV"]!)&cid=\(cid)&qn=\(sessdata == "" ? 64 : 80)", headers: headers) { respJson, isSuccess in - if isSuccess { - if !CheckBApiError(from: respJson) { return } - videoLink = respJson["data"]["durl"][0]["url"].string!.replacingOccurrences(of: "\\u0026", with: "&") - videoCID = cid - videoBvid = videoDetails["BV"]! - isNowPlayingPresented = true - isLoading = false - } - } - } - } - } else if videoGetterSource == "injahow" { - DarockKit.Network.shared.requestString("https://api.injahow.cn/bparse/?bv=\(videoDetails["BV"]!.dropFirst().dropFirst())&p=1&type=video&q=32&format=mp4&otype=url") { respStr, isSuccess in - if isSuccess { - videoLink = respStr - videoBvid = videoDetails["BV"]! - isNowPlayingPresented = true - isLoading = false - } - } - } - }, label: { - Label("Video.play-in-audio", systemImage: "waveform") - }) Button(action: { isMoreMenuPresented = true }, label: {