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
This commit is contained in:
undeaD_D 2025-06-23 21:03:37 +02:00 committed by GitHub
parent 82eec0688f
commit 78df4df73b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 121 additions and 87 deletions

View file

@ -16,48 +16,57 @@ struct ContentView_Previews: PreviewProvider {
} }
struct ContentView: View { struct ContentView: View {
@AppStorage("useNativeTabBar") private var useNativeTabBar: Bool = false
@StateObject private var tabBarController = TabBarController() @StateObject private var tabBarController = TabBarController()
@State var selectedTab: Int = 0 @State var selectedTab: Int = 0
@State var lastTab: Int = 0 @State var lastTab: Int = 0
@State private var searchQuery: String = "" @State private var searchQuery: String = ""
let tabs: [TabItem] = [ let tabs: [TabItem] = [
TabItem(icon: "square.stack", title: ""), TabItem(icon: "square.stack", title: NSLocalizedString("LibraryTab", comment: "")),
TabItem(icon: "arrow.down.circle", title: ""), TabItem(icon: "arrow.down.circle", title: NSLocalizedString("DownloadsTab", comment: "")),
TabItem(icon: "gearshape", title: ""), TabItem(icon: "gearshape", title: NSLocalizedString("SettingsTab", comment: "")),
TabItem(icon: "magnifyingglass", title: "") TabItem(icon: "magnifyingglass", title: NSLocalizedString("SearchTab", comment: ""))
] ]
var body: some View { private func tabView(for index: Int) -> some View {
ZStack(alignment: .bottom) { switch index {
switch selectedTab { case 1: return AnyView(DownloadView())
case 0: case 2: return AnyView(SettingsView())
LibraryView() case 3: return AnyView(SearchView(searchQuery: $searchQuery))
.environmentObject(tabBarController) default: return AnyView(LibraryView())
case 1: }
DownloadView() }
.environmentObject(tabBarController)
case 2: var body: some View {
SettingsView() if #available(iOS 26, *), useNativeTabBar == true {
.environmentObject(tabBarController) TabView {
case 3: ForEach(Array(tabs.enumerated()), id: \.offset) { index, item in
SearchView(searchQuery: $searchQuery) Tab(item.title, systemImage: item.icon, role: index == 3 ? .search : nil) {
.environmentObject(tabBarController) tabView(for: index)
default: }
LibraryView() }
.environmentObject(tabBarController) }
} .searchable(text: $searchQuery)
.environmentObject(tabBarController)
TabBar( } else {
tabs: tabs, ZStack(alignment: .bottom) {
selectedTab: $selectedTab, Group {
lastTab: $lastTab, tabView(for: selectedTab)
searchQuery: $searchQuery, }
controller: tabBarController .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)
} }
} }

View file

@ -49,6 +49,9 @@
"Copied to Clipboard" = "In die Zwischenablage kopiert"; "Copied to Clipboard" = "In die Zwischenablage kopiert";
"Copy to Clipboard" = "In die Zwischenablage kopieren"; "Copy to Clipboard" = "In die Zwischenablage kopieren";
"Copy URL" = "URL 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 */ /* Episodes */
"%lld Episodes" = "%lld Folgen"; "%lld Episodes" = "%lld Folgen";
@ -84,7 +87,7 @@
"Download Episode" = "Folge herunterladen"; "Download Episode" = "Folge herunterladen";
"Download Summary" = "Download-Übersicht"; "Download Summary" = "Download-Übersicht";
"Download This Episode" = "Diese Folge herunterladen"; "Download This Episode" = "Diese Folge herunterladen";
"Downloaded" = "Heruntergeladen"; "Downloaded" = "Geladen";
"Downloaded Shows" = "Heruntergeladene Serien"; "Downloaded Shows" = "Heruntergeladene Serien";
"Downloading" = "Lädt herunter"; "Downloading" = "Lädt herunter";
"Downloads" = "Downloads"; "Downloads" = "Downloads";
@ -115,6 +118,7 @@
"General events and activities." = "Allgemeine Aktivitäten."; "General events and activities." = "Allgemeine Aktivitäten.";
"General Preferences" = "Allgemeine Einstellungen"; "General Preferences" = "Allgemeine Einstellungen";
"Hide Splash Screen" = "Startbildschirm ausblenden"; "Hide Splash Screen" = "Startbildschirm ausblenden";
"Use Native Tab Bar" = "System Bar verwenden";
"HLS video downloading." = "HLS Video-Downloads."; "HLS video downloading." = "HLS Video-Downloads.";
"Hold Speed" = "Geschwindigkeit halten"; "Hold Speed" = "Geschwindigkeit halten";
@ -383,6 +387,12 @@
"Library cleared successfully" = "Bibliothek erfolgreich geleert"; "Library cleared successfully" = "Bibliothek erfolgreich geleert";
"All downloads deleted successfully" = "Alle Downloads erfolgreich gelöscht"; "All downloads deleted successfully" = "Alle Downloads erfolgreich gelöscht";
/* TabView */
"LibraryTab" = "Bibliothek";
"DownloadsTab" = "Downloads";
"SettingsTab" = "Einstellungen";
"SearchTab" = "Suchen";
/* New additions */ /* New additions */
"Recent searches" = "Letzte Suchanfragen"; "Recent searches" = "Letzte Suchanfragen";
"me frfr" = "Ich, ohne Witz"; "me frfr" = "Ich, ohne Witz";

View file

@ -49,6 +49,9 @@
"Copied to Clipboard" = "Copied to Clipboard"; "Copied to Clipboard" = "Copied to Clipboard";
"Copy to Clipboard" = "Copy to Clipboard"; "Copy to Clipboard" = "Copy to Clipboard";
"Copy URL" = "Copy URL"; "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 */ /* Episodes */
"%lld Episodes" = "%lld Episodes"; "%lld Episodes" = "%lld Episodes";
@ -115,6 +118,7 @@
"General events and activities." = "General events and activities."; "General events and activities." = "General events and activities.";
"General Preferences" = "General Preferences"; "General Preferences" = "General Preferences";
"Hide Splash Screen" = "Hide Splash Screen"; "Hide Splash Screen" = "Hide Splash Screen";
"Use Native Tab Bar" = "Use Native Tabs";
"HLS video downloading." = "HLS video downloading."; "HLS video downloading." = "HLS video downloading.";
"Hold Speed" = "Hold Speed"; "Hold Speed" = "Hold Speed";
@ -384,6 +388,12 @@
"Library cleared successfully" = "Library cleared successfully"; "Library cleared successfully" = "Library cleared successfully";
"All downloads deleted successfully" = "All downloads deleted successfully"; "All downloads deleted successfully" = "All downloads deleted successfully";
/* TabView */
"LibraryTab" = "Library";
"DownloadsTab" = "Downloads";
"SettingsTab" = "Settings";
"SearchTab" = "Search";
/* New additions */ /* New additions */
"Recent searches" = "Recent searches"; "Recent searches" = "Recent searches";
"me frfr" = "me frfr"; "me frfr" = "me frfr";

View file

@ -23,12 +23,17 @@ class DownloadManager: NSObject, ObservableObject {
} }
private func initializeDownloadSession() { private func initializeDownloadSession() {
let configuration = URLSessionConfiguration.background(withIdentifier: "hls-downloader") #if targetEnvironment(simulator)
assetDownloadURLSession = AVAssetDownloadURLSession( Logger.shared.log("Download Sessions are not available on Simulator", type: "Error")
configuration: configuration, #else
assetDownloadDelegate: self, let configuration = URLSessionConfiguration.background(withIdentifier: "hls-downloader")
delegateQueue: .main
) assetDownloadURLSession = AVAssetDownloadURLSession(
configuration: configuration,
assetDownloadDelegate: self,
delegateQueue: .main
)
#endif
} }
func downloadAsset(from url: URL) { func downloadAsset(from url: URL) {

View file

@ -43,24 +43,29 @@ extension JSController {
private static var progressUpdateTimer: Timer? private static var progressUpdateTimer: Timer?
func initializeDownloadSession() { func initializeDownloadSession() {
// Create a unique identifier for the background session #if targetEnvironment(simulator)
let sessionIdentifier = "hls-downloader-\(UUID().uuidString)" 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) let configuration = URLSessionConfiguration.background(withIdentifier: sessionIdentifier)
// Configure session // Configure session
configuration.allowsCellularAccess = true configuration.allowsCellularAccess = true
configuration.shouldUseExtendedBackgroundIdleMode = true configuration.shouldUseExtendedBackgroundIdleMode = true
configuration.waitsForConnectivity = true configuration.waitsForConnectivity = true
// Create session with configuration // Create session with configuration
downloadURLSession = AVAssetDownloadURLSession( downloadURLSession = AVAssetDownloadURLSession(
configuration: configuration, configuration: configuration,
assetDownloadDelegate: self, assetDownloadDelegate: self,
delegateQueue: .main delegateQueue: .main
) )
print("Download session initialized with ID: \(sessionIdentifier)")
#endif
print("Download session initialized with ID: \(sessionIdentifier)")
loadSavedAssets() loadSavedAssets()
} }

View file

@ -227,33 +227,14 @@ struct TabBar: View {
} }
} }
}) { }) {
if tab.title.isEmpty { Image(systemName: tab.icon + (selectedTab == index ? ".fill" : ""))
Image(systemName: tab.icon + (selectedTab == index ? ".fill" : "")) .frame(width: 28, height: 28)
.frame(width: 28, height: 28) .matchedGeometryEffect(id: tab.icon, in: animation)
.matchedGeometryEffect(id: tab.icon, in: animation) .foregroundStyle(selectedTab == index ? .black : .gray)
.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)
}
.padding(.vertical, 8) .padding(.vertical, 8)
.padding(.horizontal, 10) .padding(.horizontal, 10)
.frame(width: 80) .frame(width: 70)
.opacity(selectedTab == index ? 1 : 0.5) .opacity(selectedTab == index ? 1 : 0.5)
}
} }
.background( .background(
selectedTab == index ? selectedTab == index ?

View file

@ -154,6 +154,7 @@ struct SettingsViewGeneral: View {
@AppStorage("fetchEpisodeMetadata") private var fetchEpisodeMetadata: Bool = true @AppStorage("fetchEpisodeMetadata") private var fetchEpisodeMetadata: Bool = true
@AppStorage("analyticsEnabled") private var analyticsEnabled: Bool = false @AppStorage("analyticsEnabled") private var analyticsEnabled: Bool = false
@AppStorage("hideSplashScreen") private var hideSplashScreenEnable: Bool = false @AppStorage("hideSplashScreen") private var hideSplashScreenEnable: Bool = false
@AppStorage("useNativeTabBar") private var useNativeTabBar: Bool = false
@AppStorage("metadataProvidersOrder") private var metadataProvidersOrderData: Data = { @AppStorage("metadataProvidersOrder") private var metadataProvidersOrderData: Data = {
try! JSONEncoder().encode(["TMDB","AniList"]) try! JSONEncoder().encode(["TMDB","AniList"])
}() }()
@ -172,6 +173,10 @@ struct SettingsViewGeneral: View {
@EnvironmentObject var settings: Settings @EnvironmentObject var settings: Settings
@State private var showRestartAlert = false @State private var showRestartAlert = false
private let isiOS26OrLater: Bool = {
if #available(iOS 26, *) { return true } else { return false }
}()
var body: some View { var body: some View {
ScrollView(showsIndicators: false) { ScrollView(showsIndicators: false) {
VStack(spacing: 24) { VStack(spacing: 24) {
@ -194,8 +199,17 @@ struct SettingsViewGeneral: View {
icon: "wand.and.rays.inverse", icon: "wand.and.rays.inverse",
title: NSLocalizedString("Hide Splash Screen", comment: ""), title: NSLocalizedString("Hide Splash Screen", comment: ""),
isOn: $hideSplashScreenEnable, 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: "")) { SettingsSection(title: NSLocalizedString("Language", comment: "")) {