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 {
@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)
}
}

View file

@ -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";
"Maximum Quality Available" = "Maximal verfügbare Qualität";

View file

@ -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";

View file

@ -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) {

View file

@ -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()
}

View file

@ -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 ?

View file

@ -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: "")) {