diff --git a/Localizable.xcstrings b/Localizable.xcstrings index 8e7cc97..64e2a0d 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -33,23 +33,6 @@ } } }, - "%@ - Episode %lld" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "%1$@ - Episode %2$lld" - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "%@ - Episode %lld" - } - } - } - }, "%@ %@" : { "localizations" : { "de" : { @@ -64,7 +47,8 @@ "value" : "%@ %@" } } - } + }, + "shouldTranslate" : false }, "%lld" : { "localizations" : { @@ -96,7 +80,8 @@ "value" : "%lld-%lld" } } - } + }, + "shouldTranslate" : false }, "%llds" : { "localizations" : { @@ -226,6 +211,22 @@ } } }, + "Add Modules" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Module hinzufügen" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Add Modules" + } + } + } + }, "Adjust the number of media items per row in portrait and landscape modes." : { "localizations" : { "de" : { @@ -867,21 +868,17 @@ } }, "Download Stream" : { - - }, - "Downloads" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Downloads" + "value" : "Stream Herunterladen" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Downloads" + "value" : "Download Stream" } } } @@ -935,7 +932,20 @@ } }, "Enter HLS URL" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "HLS Link einfügen" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Enter HLS URL" + } + } + } }, "Episode %lld" : { "localizations" : { @@ -1259,7 +1269,20 @@ } }, "HLS Downloader" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "HLS Manager" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "HLS Downloader" + } + } + } }, "Hold Speed:" : { "localizations" : { @@ -1977,8 +2000,37 @@ } } }, + "No modules available" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keine Module verfügbar" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "No Modules available" + } + } + } + }, "No offline content available" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kein Offline Inhalt verfügbar" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "No offline content available" + } + } + } }, "No Results Found" : { "localizations" : { @@ -2061,7 +2113,20 @@ } }, "Play Offline Content" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Offline Inhalt abspielen" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Play Offline Content" + } + } + } }, "Player" : { "localizations" : { diff --git a/Sora/Utils/Extensions/View.swift b/Sora/Utils/Extensions/View.swift index 9604a94..fa83775 100644 --- a/Sora/Utils/Extensions/View.swift +++ b/Sora/Utils/Extensions/View.swift @@ -23,3 +23,18 @@ struct SeparatorAlignmentModifier: ViewModifier { } } } + +struct HideToolbarModifier: ViewModifier { + func body(content: Content) -> some View { + if #available(iOS 18.0, *) { + content + .toolbarVisibility(.hidden, for: .tabBar) + } else if #available(iOS 16.0, *) { + content + .toolbar(.hidden, for: .tabBar) + } else { + content + } + } +} + diff --git a/Sora/Utils/ProfileStore/ProfileStore.swift b/Sora/Utils/ProfileStore/ProfileStore.swift index 42d7eb2..4120be3 100644 --- a/Sora/Utils/ProfileStore/ProfileStore.swift +++ b/Sora/Utils/ProfileStore/ProfileStore.swift @@ -75,16 +75,18 @@ class ProfileStore: ObservableObject { setCurrentProfile(profiles[index]) } - public func deleteCurrentProfile() { - if profiles.count == 1 { return } + public func deleteProfile(removalID: UUID?) { + guard let removalID, + profiles.count == 1 + else { return } - if let suite = UserDefaults(suiteName: currentProfile.id.uuidString) { + if let suite = UserDefaults(suiteName: removalID.uuidString) { for key in suite.dictionaryRepresentation().keys { suite.removeObject(forKey: key) } } - profiles.removeAll { $0.id == currentProfile.id } + profiles.removeAll { $0.id == removalID } if let firstProfile = profiles.first { saveProfiles() diff --git a/Sora/Views/ExploreView/ExploreView.swift b/Sora/Views/ExploreView/ExploreView.swift index d811df3..8da5d61 100644 --- a/Sora/Views/ExploreView/ExploreView.swift +++ b/Sora/Views/ExploreView/ExploreView.swift @@ -32,6 +32,7 @@ struct ExploreView: View { @State private var isLandscape: Bool = UIDevice.current.orientation.isLandscape @State private var isModuleSelectorPresented = false @State private var showProfileSettings = false + @State private var showModuleSettings = false @State private var isLoading = false private var selectedModule: ScrapingModule? { @@ -149,6 +150,13 @@ struct ExploreView: View { label: { EmptyView() } ) .hidden() + + NavigationLink( + destination: SettingsViewModule(), + isActive: $showModuleSettings, + label: { EmptyView() } + ) + .hidden() } .navigationTitle("Explore") .navigationBarTitleDisplayMode(.large) @@ -189,22 +197,35 @@ struct ExploreView: View { ToolbarItem(placement: .navigationBarTrailing) { Menu { - ForEach(getModuleLanguageGroups(), id: \.self) { language in - Menu(language) { - ForEach(getModulesForLanguage(language), id: \.id) { module in - Button { - selectedModuleId = module.id.uuidString - } label: { - HStack { - KFImage(URL(string: module.metadata.iconUrl)) - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 20, height: 20) - .cornerRadius(4) - Text(module.metadata.sourceName) - if module.id.uuidString == selectedModuleId { - Image(systemName: "checkmark") - .foregroundColor(.accentColor) + if getModuleLanguageGroups().isEmpty { + Button("No modules available") { } + .disabled(true) + + Divider() + + Button { + showModuleSettings = true + } label: { + Label("Add Modules", systemImage: "plus.app") + } + } else { + ForEach(getModuleLanguageGroups(), id: \.self) { language in + Menu(language) { + ForEach(getModulesForLanguage(language), id: \.id) { module in + Button { + selectedModuleId = module.id.uuidString + } label: { + HStack { + KFImage(URL(string: module.metadata.iconUrl)) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 20, height: 20) + .cornerRadius(4) + Text(module.metadata.sourceName) + if module.id.uuidString == selectedModuleId { + Image(systemName: "checkmark") + .foregroundColor(.accentColor) + } } } } diff --git a/Sora/Views/SearchView/SearchView.swift b/Sora/Views/SearchView/SearchView.swift index c4314af..49e545e 100644 --- a/Sora/Views/SearchView/SearchView.swift +++ b/Sora/Views/SearchView/SearchView.swift @@ -34,6 +34,7 @@ struct SearchView: View { @State private var isLandscape: Bool = UIDevice.current.orientation.isLandscape @State private var isModuleSelectorPresented = false @State private var showProfileSettings = false + @State private var showModuleSettings = false private var selectedModule: ScrapingModule? { guard let id = selectedModuleId else { return nil } @@ -168,6 +169,13 @@ struct SearchView: View { label: { EmptyView() } ) .hidden() + + NavigationLink( + destination: SettingsViewModule(), + isActive: $showModuleSettings, + label: { EmptyView() } + ) + .hidden() } .navigationTitle("Search") .navigationBarTitleDisplayMode(.large) @@ -207,22 +215,35 @@ struct SearchView: View { } ToolbarItem(placement: .navigationBarTrailing) { Menu { - ForEach(getModuleLanguageGroups(), id: \.self) { language in - Menu(language) { - ForEach(getModulesForLanguage(language), id: \.id) { module in - Button { - selectedModuleId = module.id.uuidString - } label: { - HStack { - KFImage(URL(string: module.metadata.iconUrl)) - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 20, height: 20) - .cornerRadius(4) - Text(module.metadata.sourceName) - if module.id.uuidString == selectedModuleId { - Image(systemName: "checkmark") - .foregroundColor(.accentColor) + if getModuleLanguageGroups().isEmpty { + Button("No modules available") { } + .disabled(true) + + Divider() + + Button { + showModuleSettings = true + } label: { + Label("Add Modules", systemImage: "plus.app") + } + } else { + ForEach(getModuleLanguageGroups(), id: \.self) { language in + Menu(language) { + ForEach(getModulesForLanguage(language), id: \.id) { module in + Button { + selectedModuleId = module.id.uuidString + } label: { + HStack { + KFImage(URL(string: module.metadata.iconUrl)) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 20, height: 20) + .cornerRadius(4) + Text(module.metadata.sourceName) + if module.id.uuidString == selectedModuleId { + Image(systemName: "checkmark") + .foregroundColor(.accentColor) + } } } } diff --git a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewAlternateAppIconPicker.swift b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewAlternateAppIconPicker.swift index 96c1861..a735f1e 100644 --- a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewAlternateAppIconPicker.swift +++ b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewAlternateAppIconPicker.swift @@ -52,9 +52,9 @@ struct SettingsViewAlternateAppIconPicker: View { } .padding() } - Spacer() } + .modifier(HideToolbarModifier()) } private func setAppIcon(named iconName: String) { diff --git a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewData.swift b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewData.swift index a32fda5..86cea84 100644 --- a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewData.swift +++ b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewData.swift @@ -54,6 +54,7 @@ struct SettingsViewData: View { } .navigationTitle("App Data") .navigationViewStyle(StackNavigationViewStyle()) + .modifier(HideToolbarModifier()) } func eraseAppData() { diff --git a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewGeneral.swift b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewGeneral.swift index e36593c..4ef34bb 100644 --- a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewGeneral.swift +++ b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewGeneral.swift @@ -153,5 +153,6 @@ struct SettingsViewGeneral: View { SettingsViewAlternateAppIconPicker(isPresented: $showAppIconPicker) } } + .modifier(HideToolbarModifier()) } } diff --git a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewLogger.swift b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewLogger.swift index df23f30..4738249 100644 --- a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewLogger.swift +++ b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewLogger.swift @@ -54,5 +54,6 @@ struct SettingsViewLogger: View { } } } + .modifier(HideToolbarModifier()) } } diff --git a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewLoggerFilter.swift b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewLoggerFilter.swift index 953291b..32424aa 100644 --- a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewLoggerFilter.swift +++ b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewLoggerFilter.swift @@ -83,5 +83,6 @@ struct SettingsViewLoggerFilter: View { } } .navigationTitle("Log Filters") + .modifier(HideToolbarModifier()) } } diff --git a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewModule.swift b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewModule.swift index 9b767cf..dee6693 100644 --- a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewModule.swift +++ b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewModule.swift @@ -180,6 +180,7 @@ struct SettingsViewModule: View { } message: { Text(errorMessage ?? "Unknown error") } + .modifier(HideToolbarModifier()) } func showAddModuleAlert() { diff --git a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewPlayer.swift b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewPlayer.swift index 6346172..f420118 100644 --- a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewPlayer.swift +++ b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewPlayer.swift @@ -108,6 +108,7 @@ struct SettingsViewPlayer: View { SubtitleSettingsSection() } .navigationTitle("Player") + .modifier(HideToolbarModifier()) } } diff --git a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewProfile.swift b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewProfile.swift index f7975b5..c1be937 100644 --- a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewProfile.swift +++ b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewProfile.swift @@ -41,6 +41,7 @@ struct SettingsViewProfile: View { @EnvironmentObject var profileStore: ProfileStore @State private var showDeleteAlert = false + @State private var profileIDToRemove: UUID? var body: some View { Form { @@ -53,6 +54,16 @@ struct SettingsViewProfile: View { isSelected: profile.id == profileStore.currentProfile.id ) } + .swipeActions(edge: .trailing, allowsFullSwipe: true) { + if profileStore.profiles.count > 1 { + Button(role: .destructive) { + profileIDToRemove = profile.id + showDeleteAlert = true + } label: { + Label("Delete", systemImage: "trash") + } + } + } } } @@ -92,6 +103,7 @@ struct SettingsViewProfile: View { if profileStore.profiles.count > 1 { Button(action: { + profileIDToRemove = profileStore.currentProfile.id showDeleteAlert = true }) { Text("Delete Selected Profile") @@ -107,7 +119,7 @@ struct SettingsViewProfile: View { title: Text("Delete Profile"), message: Text("Are you sure you want to delete this profile? This action cannot be undone."), primaryButton: .destructive(Text("Delete")) { - profileStore.deleteCurrentProfile() + profileStore.deleteProfile(removalID: profileIDToRemove) }, secondaryButton: .cancel() ) @@ -133,5 +145,6 @@ struct SettingsViewProfile: View { } } } + .modifier(HideToolbarModifier()) } } diff --git a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewTrackers.swift b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewTrackers.swift index c1cf3f4..0490792 100644 --- a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewTrackers.swift +++ b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewTrackers.swift @@ -118,6 +118,7 @@ struct SettingsViewTrackers: View { .onDisappear { removeNotificationObservers() } + .modifier(HideToolbarModifier()) } func removeNotificationObservers() {