From 78df4df73b8ebbe49caf68bd83c18393595429f4 Mon Sep 17 00:00:00 2001 From: undeaD_D <8116188+undeaDD@users.noreply.github.com> Date: Mon, 23 Jun 2025 21:03:37 +0200 Subject: [PATCH] Fix simulator crash & add conditional support for iOS 26+ native TabView (#209) * add ability to use native tabbar on ios 26 or later * add tabview lcalization * fix library / home tab label and localization * add missing localization & german translation --- Sora/ContentView.swift | 79 +++++++++++-------- .../Localization/de.lproj/Localizable.strings | 14 +++- .../Localization/en.lproj/Localizable.strings | 10 +++ .../Utils/DownloadUtils/DownloadManager.swift | 17 ++-- .../Downloads/JSController-Downloads.swift | 41 +++++----- Sora/Utils/TabBar/TabBar.swift | 29 ++----- .../SettingsViewGeneral.swift | 18 ++++- 7 files changed, 121 insertions(+), 87 deletions(-) diff --git a/Sora/ContentView.swift b/Sora/ContentView.swift index 8238bd1..4cde703 100644 --- a/Sora/ContentView.swift +++ b/Sora/ContentView.swift @@ -16,48 +16,57 @@ struct ContentView_Previews: PreviewProvider { } struct ContentView: View { + @AppStorage("useNativeTabBar") private var useNativeTabBar: Bool = false @StateObject private var tabBarController = TabBarController() @State var selectedTab: Int = 0 @State var lastTab: Int = 0 @State private var searchQuery: String = "" let tabs: [TabItem] = [ - TabItem(icon: "square.stack", title: ""), - TabItem(icon: "arrow.down.circle", title: ""), - TabItem(icon: "gearshape", title: ""), - TabItem(icon: "magnifyingglass", title: "") + TabItem(icon: "square.stack", title: NSLocalizedString("LibraryTab", comment: "")), + TabItem(icon: "arrow.down.circle", title: NSLocalizedString("DownloadsTab", comment: "")), + TabItem(icon: "gearshape", title: NSLocalizedString("SettingsTab", comment: "")), + TabItem(icon: "magnifyingglass", title: NSLocalizedString("SearchTab", comment: "")) ] - - var body: some View { - ZStack(alignment: .bottom) { - switch selectedTab { - case 0: - LibraryView() - .environmentObject(tabBarController) - case 1: - DownloadView() - .environmentObject(tabBarController) - case 2: - SettingsView() - .environmentObject(tabBarController) - case 3: - SearchView(searchQuery: $searchQuery) - .environmentObject(tabBarController) - default: - LibraryView() - .environmentObject(tabBarController) - } - - TabBar( - tabs: tabs, - selectedTab: $selectedTab, - lastTab: $lastTab, - searchQuery: $searchQuery, - controller: tabBarController - ) + + private func tabView(for index: Int) -> some View { + switch index { + case 1: return AnyView(DownloadView()) + case 2: return AnyView(SettingsView()) + case 3: return AnyView(SearchView(searchQuery: $searchQuery)) + default: return AnyView(LibraryView()) + } + } + + var body: some View { + if #available(iOS 26, *), useNativeTabBar == true { + TabView { + ForEach(Array(tabs.enumerated()), id: \.offset) { index, item in + Tab(item.title, systemImage: item.icon, role: index == 3 ? .search : nil) { + tabView(for: index) + } + } + } + .searchable(text: $searchQuery) + .environmentObject(tabBarController) + } else { + ZStack(alignment: .bottom) { + Group { + tabView(for: selectedTab) + } + .environmentObject(tabBarController) + + TabBar( + tabs: tabs, + selectedTab: $selectedTab, + lastTab: $lastTab, + searchQuery: $searchQuery, + controller: tabBarController + ) + } + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottom) + .ignoresSafeArea(.keyboard, edges: .bottom) + .padding(.bottom, -20) } - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottom) - .ignoresSafeArea(.keyboard, edges: .bottom) - .padding(.bottom, -20) } } diff --git a/Sora/Localization/de.lproj/Localizable.strings b/Sora/Localization/de.lproj/Localizable.strings index 948d5c3..98aed9b 100644 --- a/Sora/Localization/de.lproj/Localizable.strings +++ b/Sora/Localization/de.lproj/Localizable.strings @@ -49,6 +49,9 @@ "Copied to Clipboard" = "In die Zwischenablage kopiert"; "Copy to Clipboard" = "In die Zwischenablage kopieren"; "Copy URL" = "URL kopieren"; +"Collections" = "Sammlungen"; +"No Collections" = "Keine Sammlungen vorhanden"; +"Create a collection to organize your bookmarks" = "Erstelle eine Sammlung für mehr Organisation"; /* Episodes */ "%lld Episodes" = "%lld Folgen"; @@ -84,7 +87,7 @@ "Download Episode" = "Folge herunterladen"; "Download Summary" = "Download-Übersicht"; "Download This Episode" = "Diese Folge herunterladen"; -"Downloaded" = "Heruntergeladen"; +"Downloaded" = "Geladen"; "Downloaded Shows" = "Heruntergeladene Serien"; "Downloading" = "Lädt herunter"; "Downloads" = "Downloads"; @@ -115,6 +118,7 @@ "General events and activities." = "Allgemeine Aktivitäten."; "General Preferences" = "Allgemeine Einstellungen"; "Hide Splash Screen" = "Startbildschirm ausblenden"; +"Use Native Tab Bar" = "System Bar verwenden"; "HLS video downloading." = "HLS Video-Downloads."; "Hold Speed" = "Geschwindigkeit halten"; @@ -383,8 +387,14 @@ "Library cleared successfully" = "Bibliothek erfolgreich geleert"; "All downloads deleted successfully" = "Alle Downloads erfolgreich gelöscht"; +/* TabView */ +"LibraryTab" = "Bibliothek"; +"DownloadsTab" = "Downloads"; +"SettingsTab" = "Einstellungen"; +"SearchTab" = "Suchen"; + /* New additions */ "Recent searches" = "Letzte Suchanfragen"; "me frfr" = "Ich, ohne Witz"; "Data" = "Daten"; -"Maximum Quality Available" = "Maximal verfügbare Qualität"; \ No newline at end of file +"Maximum Quality Available" = "Maximal verfügbare Qualität"; diff --git a/Sora/Localization/en.lproj/Localizable.strings b/Sora/Localization/en.lproj/Localizable.strings index 2e83aae..4f55cbb 100644 --- a/Sora/Localization/en.lproj/Localizable.strings +++ b/Sora/Localization/en.lproj/Localizable.strings @@ -49,6 +49,9 @@ "Copied to Clipboard" = "Copied to Clipboard"; "Copy to Clipboard" = "Copy to Clipboard"; "Copy URL" = "Copy URL"; +"Collections" = "Collections"; +"No Collections" = "No Collections"; +"Create a collection to organize your bookmarks" = "Create a collection to organize your bookmarks"; /* Episodes */ "%lld Episodes" = "%lld Episodes"; @@ -115,6 +118,7 @@ "General events and activities." = "General events and activities."; "General Preferences" = "General Preferences"; "Hide Splash Screen" = "Hide Splash Screen"; +"Use Native Tab Bar" = "Use Native Tabs"; "HLS video downloading." = "HLS video downloading."; "Hold Speed" = "Hold Speed"; @@ -384,6 +388,12 @@ "Library cleared successfully" = "Library cleared successfully"; "All downloads deleted successfully" = "All downloads deleted successfully"; +/* TabView */ +"LibraryTab" = "Library"; +"DownloadsTab" = "Downloads"; +"SettingsTab" = "Settings"; +"SearchTab" = "Search"; + /* New additions */ "Recent searches" = "Recent searches"; "me frfr" = "me frfr"; diff --git a/Sora/Utils/DownloadUtils/DownloadManager.swift b/Sora/Utils/DownloadUtils/DownloadManager.swift index 3c09ea9..2adab56 100644 --- a/Sora/Utils/DownloadUtils/DownloadManager.swift +++ b/Sora/Utils/DownloadUtils/DownloadManager.swift @@ -23,12 +23,17 @@ class DownloadManager: NSObject, ObservableObject { } private func initializeDownloadSession() { - let configuration = URLSessionConfiguration.background(withIdentifier: "hls-downloader") - assetDownloadURLSession = AVAssetDownloadURLSession( - configuration: configuration, - assetDownloadDelegate: self, - delegateQueue: .main - ) + #if targetEnvironment(simulator) + Logger.shared.log("Download Sessions are not available on Simulator", type: "Error") + #else + let configuration = URLSessionConfiguration.background(withIdentifier: "hls-downloader") + + assetDownloadURLSession = AVAssetDownloadURLSession( + configuration: configuration, + assetDownloadDelegate: self, + delegateQueue: .main + ) + #endif } func downloadAsset(from url: URL) { diff --git a/Sora/Utils/JSLoader/Downloads/JSController-Downloads.swift b/Sora/Utils/JSLoader/Downloads/JSController-Downloads.swift index be358fe..42a37b6 100644 --- a/Sora/Utils/JSLoader/Downloads/JSController-Downloads.swift +++ b/Sora/Utils/JSLoader/Downloads/JSController-Downloads.swift @@ -43,24 +43,29 @@ extension JSController { private static var progressUpdateTimer: Timer? func initializeDownloadSession() { - // Create a unique identifier for the background session - let sessionIdentifier = "hls-downloader-\(UUID().uuidString)" - - let configuration = URLSessionConfiguration.background(withIdentifier: sessionIdentifier) - - // Configure session - configuration.allowsCellularAccess = true - configuration.shouldUseExtendedBackgroundIdleMode = true - configuration.waitsForConnectivity = true - - // Create session with configuration - downloadURLSession = AVAssetDownloadURLSession( - configuration: configuration, - assetDownloadDelegate: self, - delegateQueue: .main - ) - - print("Download session initialized with ID: \(sessionIdentifier)") + #if targetEnvironment(simulator) + Logger.shared.log("Download Sessions are not available on Simulator", type: "Error") + #else + // Create a unique identifier for the background session + let sessionIdentifier = "hls-downloader-\(UUID().uuidString)" + + let configuration = URLSessionConfiguration.background(withIdentifier: sessionIdentifier) + + // Configure session + configuration.allowsCellularAccess = true + configuration.shouldUseExtendedBackgroundIdleMode = true + configuration.waitsForConnectivity = true + + // Create session with configuration + downloadURLSession = AVAssetDownloadURLSession( + configuration: configuration, + assetDownloadDelegate: self, + delegateQueue: .main + ) + + print("Download session initialized with ID: \(sessionIdentifier)") + #endif + loadSavedAssets() } diff --git a/Sora/Utils/TabBar/TabBar.swift b/Sora/Utils/TabBar/TabBar.swift index a646e58..148f1c5 100644 --- a/Sora/Utils/TabBar/TabBar.swift +++ b/Sora/Utils/TabBar/TabBar.swift @@ -227,33 +227,14 @@ struct TabBar: View { } } }) { - if tab.title.isEmpty { - Image(systemName: tab.icon + (selectedTab == index ? ".fill" : "")) - .frame(width: 28, height: 28) - .matchedGeometryEffect(id: tab.icon, in: animation) - .foregroundStyle(selectedTab == index ? .black : .gray) - .padding(.vertical, 8) - .padding(.horizontal, 10) - .frame(width: 70) - .opacity(selectedTab == index ? 1 : 0.5) - } else { - VStack { - Image(systemName: tab.icon + (selectedTab == index ? ".fill" : "")) - .frame(width: 36, height: 18) - .matchedGeometryEffect(id: tab.icon, in: animation) - .foregroundStyle(selectedTab == index ? .black : .gray) - - Text(tab.title) - .font(.caption) - .frame(width: 60) - .lineLimit(1) - .truncationMode(.tail) - } + Image(systemName: tab.icon + (selectedTab == index ? ".fill" : "")) + .frame(width: 28, height: 28) + .matchedGeometryEffect(id: tab.icon, in: animation) + .foregroundStyle(selectedTab == index ? .black : .gray) .padding(.vertical, 8) .padding(.horizontal, 10) - .frame(width: 80) + .frame(width: 70) .opacity(selectedTab == index ? 1 : 0.5) - } } .background( selectedTab == index ? diff --git a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewGeneral.swift b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewGeneral.swift index 68b2f96..cb7bdcb 100644 --- a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewGeneral.swift +++ b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewGeneral.swift @@ -154,6 +154,7 @@ struct SettingsViewGeneral: View { @AppStorage("fetchEpisodeMetadata") private var fetchEpisodeMetadata: Bool = true @AppStorage("analyticsEnabled") private var analyticsEnabled: Bool = false @AppStorage("hideSplashScreen") private var hideSplashScreenEnable: Bool = false + @AppStorage("useNativeTabBar") private var useNativeTabBar: Bool = false @AppStorage("metadataProvidersOrder") private var metadataProvidersOrderData: Data = { try! JSONEncoder().encode(["TMDB","AniList"]) }() @@ -171,7 +172,11 @@ struct SettingsViewGeneral: View { private let metadataProvidersList = ["TMDB", "AniList"] @EnvironmentObject var settings: Settings @State private var showRestartAlert = false - + + private let isiOS26OrLater: Bool = { + if #available(iOS 26, *) { return true } else { return false } + }() + var body: some View { ScrollView(showsIndicators: false) { VStack(spacing: 24) { @@ -194,8 +199,17 @@ struct SettingsViewGeneral: View { icon: "wand.and.rays.inverse", title: NSLocalizedString("Hide Splash Screen", comment: ""), isOn: $hideSplashScreenEnable, - showDivider: false + showDivider: isiOS26OrLater ) + + if isiOS26OrLater { + SettingsToggleRow( + icon: "inset.filled.bottomthird.rectangle", + title: NSLocalizedString("Use Native Tab Bar", comment: ""), + isOn: $useNativeTabBar, + showDivider: false + ) + } } SettingsSection(title: NSLocalizedString("Language", comment: "")) {