From faa7700ff7beea1290604b90dbaf5a0b61415c48 Mon Sep 17 00:00:00 2001 From: 50/50 <80717571+50n50@users.noreply.github.com> Date: Thu, 24 Jul 2025 23:13:04 +0200 Subject: [PATCH] Separate library settings --- .../SettingsViewGeneral.swift | 149 ---------- .../SettingsViewLibrary.swift | 280 ++++++++++++++++++ Sora/Views/SettingsView/SettingsView.swift | 5 + Sulfur.xcodeproj/project.pbxproj | 4 + 4 files changed, 289 insertions(+), 149 deletions(-) create mode 100644 Sora/Views/SettingsView/SettingsSubViews/SettingsViewLibrary.swift diff --git a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewGeneral.swift b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewGeneral.swift index 7b43cf8..56838ca 100644 --- a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewGeneral.swift +++ b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewGeneral.swift @@ -158,31 +158,13 @@ struct SettingsViewGeneral: View { try! JSONEncoder().encode(["TMDB","AniList"]) }() @AppStorage("tmdbImageWidth") private var TMDBimageWidht: String = "original" - @AppStorage("mediaColumnsPortrait") private var mediaColumnsPortrait: Int = 2 - @AppStorage("mediaColumnsLandscape") private var mediaColumnsLandscape: Int = 4 @AppStorage("metadataProviders") private var metadataProviders: String = "TMDB" - @AppStorage("librarySectionsOrderData") private var librarySectionsOrderData: Data = { - try! JSONEncoder().encode(["continueWatching", "continueReading", "collections"]) - }() - @AppStorage("disabledLibrarySectionsData") private var disabledLibrarySectionsData: Data = { - try! JSONEncoder().encode([String]()) - }() private var metadataProvidersOrder: [String] { get { (try? JSONDecoder().decode([String].self, from: metadataProvidersOrderData)) ?? ["AniList","TMDB"] } set { metadataProvidersOrderData = try! JSONEncoder().encode(newValue) } } - private var librarySectionsOrder: [String] { - get { (try? JSONDecoder().decode([String].self, from: librarySectionsOrderData)) ?? ["continueWatching", "continueReading", "collections"] } - set { librarySectionsOrderData = try! JSONEncoder().encode(newValue) } - } - - private var disabledLibrarySections: [String] { - get { (try? JSONDecoder().decode([String].self, from: disabledLibrarySectionsData)) ?? [] } - set { disabledLibrarySectionsData = try! JSONEncoder().encode(newValue) } - } - private let TMDBimageWidhtList = ["300", "500", "780", "1280", "original"] private let sortOrderOptions = ["Ascending", "Descending"] private let metadataProvidersList = ["TMDB", "AniList"] @@ -348,30 +330,6 @@ struct SettingsViewGeneral: View { .environment(\.editMode, .constant(.active)) } - SettingsSection( - title: NSLocalizedString("Media Grid Layout", comment: ""), - footer: NSLocalizedString("Adjust the number of media items per row in portrait and landscape modes.", comment: "") - ) { - SettingsPickerRow( - icon: "rectangle.portrait", - title: NSLocalizedString("Portrait Columns", comment: ""), - options: UIDevice.current.userInterfaceIdiom == .pad ? Array(1...5) : Array(1...4), - optionToString: { "\($0)" }, - selection: $mediaColumnsPortrait - ) - - SettingsPickerRow( - icon: "rectangle", - title: NSLocalizedString("Landscape Columns", comment: ""), - options: UIDevice.current.userInterfaceIdiom == .pad ? Array(2...8) : Array(2...5), - optionToString: { "\($0)" }, - selection: $mediaColumnsLandscape, - showDivider: false - ) - } - - - SettingsSection( title: NSLocalizedString("Advanced", comment: ""), footer: NSLocalizedString("Anonymous data is collected to improve the app. No personal information is collected. This can be disabled at any time.", comment: "") @@ -383,70 +341,6 @@ struct SettingsViewGeneral: View { showDivider: false ) } - - SettingsSection( - title: NSLocalizedString("Library View", comment: ""), - footer: NSLocalizedString("Customize the sections shown in your library. You can reorder sections or disable them completely.", comment: "") - ) { - VStack(spacing: 0) { - HStack { - Image(systemName: "arrow.up.arrow.down") - .frame(width: 24, height: 24) - .foregroundStyle(.primary) - - Text(NSLocalizedString("Library Sections Order", comment: "")) - .foregroundStyle(.primary) - - Spacer() - } - .padding(.horizontal, 16) - .padding(.vertical, 12) - - List { - ForEach(Array(librarySectionsOrder.enumerated()), id: \.element) { index, section in - HStack { - Text("\(index + 1)") - .frame(width: 24, height: 24) - .foregroundStyle(.gray) - - Image(systemName: sectionIcon(for: section)) - .frame(width: 24, height: 24) - - Text(sectionName(for: section)) - .foregroundStyle(.primary) - - Spacer() - - Toggle("", isOn: toggleBinding(for: section)) - .labelsHidden() - .tint(.accentColor.opacity(0.7)) - } - .padding(.horizontal, 16) - .padding(.vertical, 12) - .listRowBackground(Color.clear) - .listRowSeparator(.visible) - .listRowSeparatorTint(.gray.opacity(0.3)) - .listRowInsets(EdgeInsets()) - } - .onMove { from, to in - var arr = librarySectionsOrder - arr.move(fromOffsets: from, toOffset: to) - librarySectionsOrderData = try! JSONEncoder().encode(arr) - } - } - .listStyle(.plain) - .frame(height: CGFloat(librarySectionsOrder.count * 70)) - .background(Color.clear) - - Text(NSLocalizedString("Drag to reorder sections", comment: "")) - .font(.caption) - .foregroundStyle(.gray) - .frame(maxWidth: .infinity, alignment: .center) - .padding(.top, -6) - .padding(.bottom, 8) - } - .environment(\.editMode, .constant(.active)) - } } .padding(.vertical, 20) } @@ -460,47 +354,4 @@ struct SettingsViewGeneral: View { ) } } - - private func sectionName(for section: String) -> String { - switch section { - case "continueWatching": - return NSLocalizedString("Continue Watching", comment: "") - case "continueReading": - return NSLocalizedString("Continue Reading", comment: "") - case "collections": - return NSLocalizedString("Collections", comment: "") - default: - return section - } - } - - private func sectionIcon(for section: String) -> String { - switch section { - case "continueWatching": - return "play.fill" - case "continueReading": - return "book.fill" - case "collections": - return "folder.fill" - default: - return "questionmark" - } - } - - private func toggleBinding(for section: String) -> Binding { - return Binding( - get: { !self.disabledLibrarySections.contains(section) }, - set: { isEnabled in - var sections = self.disabledLibrarySections - if isEnabled { - sections.removeAll { $0 == section } - } else { - if !sections.contains(section) { - sections.append(section) - } - } - self.disabledLibrarySectionsData = try! JSONEncoder().encode(sections) - } - ) - } } diff --git a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewLibrary.swift b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewLibrary.swift new file mode 100644 index 0000000..48e66b0 --- /dev/null +++ b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewLibrary.swift @@ -0,0 +1,280 @@ +// +// SettingsViewLibrary.swift +// Sora +// +// Created by paul on 05/02/25. +// + +import SwiftUI +import UIKit + +fileprivate struct SettingsSection: View { + let title: String + let footer: String? + let content: Content + + init(title: String, footer: String? = nil, @ViewBuilder content: () -> Content) { + self.title = title + self.footer = footer + self.content = content() + } + + var body: some View { + VStack(alignment: .leading, spacing: 4) { + Text(title.uppercased()) + .font(.footnote) + .foregroundStyle(.gray) + .padding(.horizontal, 20) + + VStack(spacing: 0) { + content + } + .background(.ultraThinMaterial) + .clipShape(RoundedRectangle(cornerRadius: 12)) + .overlay( + RoundedRectangle(cornerRadius: 12) + .strokeBorder( + LinearGradient( + gradient: Gradient(stops: [ + .init(color: Color.accentColor.opacity(0.3), location: 0), + .init(color: Color.accentColor.opacity(0), location: 1) + ]), + startPoint: .top, + endPoint: .bottom + ), + lineWidth: 0.5 + ) + ) + .padding(.horizontal, 20) + + if let footer = footer { + Text(footer) + .font(.footnote) + .foregroundStyle(.gray) + .padding(.horizontal, 20) + .padding(.top, 4) + } + } + } +} + +fileprivate struct SettingsToggleRow: View { + let icon: String + let title: String + @Binding var isOn: Bool + var showDivider: Bool = true + + var body: some View { + VStack(spacing: 0) { + HStack { + Image(systemName: icon) + .frame(width: 24, height: 24) + .foregroundStyle(.primary) + + Text(title) + .foregroundStyle(.primary) + + Spacer() + + Toggle("", isOn: $isOn) + .labelsHidden() + .tint(.accentColor.opacity(0.7)) + } + .padding(.horizontal, 16) + .padding(.vertical, 12) + + if showDivider { + Divider().padding(.horizontal, 16) + } + } + } +} + +fileprivate struct SettingsPickerRow: View { + let icon: String + let title: String + let options: [T] + let optionToString: (T) -> String + @Binding var selection: T + var showDivider: Bool = true + + var body: some View { + VStack(spacing: 0) { + HStack { + Image(systemName: icon) + .frame(width: 24, height: 24) + .foregroundStyle(.primary) + + Text(title) + .foregroundStyle(.primary) + + Spacer() + + Picker("", selection: $selection) { + ForEach(options, id: \.self) { option in + Text(optionToString(option)).tag(option) + } + } + .pickerStyle(MenuPickerStyle()) + .labelsHidden() + } + .padding(.horizontal, 16) + .padding(.vertical, 12) + + if showDivider { + Divider().padding(.horizontal, 16) + } + } + } +} + +struct SettingsViewLibrary: View { + @AppStorage("mediaColumnsPortrait") private var mediaColumnsPortrait: Int = 2 + @AppStorage("mediaColumnsLandscape") private var mediaColumnsLandscape: Int = 4 + @AppStorage("librarySectionsOrderData") private var librarySectionsOrderData: Data = { + try! JSONEncoder().encode(["continueWatching", "continueReading", "collections"]) + }() + @AppStorage("disabledLibrarySectionsData") private var disabledLibrarySectionsData: Data = { + try! JSONEncoder().encode([String]()) + }() + + private var librarySectionsOrder: [String] { + get { (try? JSONDecoder().decode([String].self, from: librarySectionsOrderData)) ?? ["continueWatching", "continueReading", "collections"] } + set { librarySectionsOrderData = try! JSONEncoder().encode(newValue) } + } + + private var disabledLibrarySections: [String] { + get { (try? JSONDecoder().decode([String].self, from: disabledLibrarySectionsData)) ?? [] } + set { disabledLibrarySectionsData = try! JSONEncoder().encode(newValue) } + } + + var body: some View { + ScrollView(showsIndicators: false) { + VStack(spacing: 24) { + SettingsSection( + title: NSLocalizedString("Media Grid Layout", comment: ""), + footer: NSLocalizedString("Adjust the number of media items per row in portrait and landscape modes.", comment: "") + ) { + SettingsPickerRow( + icon: "rectangle.portrait", + title: NSLocalizedString("Portrait Columns", comment: ""), + options: UIDevice.current.userInterfaceIdiom == .pad ? Array(1...5) : Array(1...4), + optionToString: { "\($0)" }, + selection: $mediaColumnsPortrait + ) + + SettingsPickerRow( + icon: "rectangle", + title: NSLocalizedString("Landscape Columns", comment: ""), + options: UIDevice.current.userInterfaceIdiom == .pad ? Array(2...8) : Array(2...5), + optionToString: { "\($0)" }, + selection: $mediaColumnsLandscape, + showDivider: false + ) + } + + SettingsSection( + title: NSLocalizedString("Library View", comment: ""), + footer: NSLocalizedString("Customize the sections shown in your library. You can reorder sections or disable them completely.", comment: "") + ) { + VStack(spacing: 0) { + HStack { + Image(systemName: "arrow.up.arrow.down") + .frame(width: 24, height: 24) + .foregroundStyle(.primary) + + Text(NSLocalizedString("Library Sections Order", comment: "")) + .foregroundStyle(.primary) + + Spacer() + } + .padding(.horizontal, 16) + .padding(.vertical, 12) + + List { + ForEach(Array(librarySectionsOrder.enumerated()), id: \.element) { index, section in + HStack { + Text("\(index + 1)") + .frame(width: 24, height: 24) + .foregroundStyle(.gray) + + Image(systemName: sectionIcon(for: section)) + .frame(width: 24, height: 24) + + Text(sectionName(for: section)) + .foregroundStyle(.primary) + + Spacer() + + Toggle("", isOn: toggleBinding(for: section)) + .labelsHidden() + .tint(.accentColor.opacity(0.7)) + } + .padding(.horizontal, 16) + .padding(.vertical, 12) + .listRowBackground(Color.clear) + .listRowSeparator(.visible) + .listRowSeparatorTint(.gray.opacity(0.3)) + .listRowInsets(EdgeInsets()) + } + .onMove { from, to in + var arr = librarySectionsOrder + arr.move(fromOffsets: from, toOffset: to) + librarySectionsOrderData = try! JSONEncoder().encode(arr) + } + } + .listStyle(.plain) + .frame(height: CGFloat(librarySectionsOrder.count * 70)) + .environment(\.editMode, .constant(.active)) + } + } + } + .padding(.vertical, 20) + } + .navigationTitle(NSLocalizedString("Library", comment: "")) + .scrollViewBottomPadding() + } + + private func sectionName(for section: String) -> String { + switch section { + case "continueWatching": + return NSLocalizedString("Continue Watching", comment: "") + case "continueReading": + return NSLocalizedString("Continue Reading", comment: "") + case "collections": + return NSLocalizedString("Collections", comment: "") + default: + return section.capitalized + } + } + + private func sectionIcon(for section: String) -> String { + switch section { + case "continueWatching": + return "play.circle" + case "continueReading": + return "book" + case "collections": + return "folder" + default: + return "questionmark.circle" + } + } + + private func toggleBinding(for section: String) -> Binding { + return Binding( + get: { !self.disabledLibrarySections.contains(section) }, + set: { isEnabled in + var sections = self.disabledLibrarySections + if isEnabled { + sections.removeAll { $0 == section } + } else { + if !sections.contains(section) { + sections.append(section) + } + } + self.disabledLibrarySectionsData = try! JSONEncoder().encode(sections) + } + ) + } +} \ No newline at end of file diff --git a/Sora/Views/SettingsView/SettingsView.swift b/Sora/Views/SettingsView/SettingsView.swift index d593ad5..dd2c147 100644 --- a/Sora/Views/SettingsView/SettingsView.swift +++ b/Sora/Views/SettingsView/SettingsView.swift @@ -171,6 +171,11 @@ struct SettingsView: View { } Divider().padding(.horizontal, 16) + NavigationLink(destination: SettingsViewLibrary().navigationBarBackButtonHidden(false)) { + SettingsNavigationRow(icon: "books.vertical", titleKey: "Library") + } + Divider().padding(.horizontal, 16) + NavigationLink(destination: SettingsViewPlayer().navigationBarBackButtonHidden(false)) { SettingsNavigationRow(icon: "play.circle", titleKey: "Video Player") } diff --git a/Sulfur.xcodeproj/project.pbxproj b/Sulfur.xcodeproj/project.pbxproj index 6e67ad5..eec4ca5 100644 --- a/Sulfur.xcodeproj/project.pbxproj +++ b/Sulfur.xcodeproj/project.pbxproj @@ -21,6 +21,7 @@ 0414ECFE2E32D6EF00A7E76A /* Bundle+Language.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0414ECFC2E32D6EF00A7E76A /* Bundle+Language.swift */; }; 0414ECFF2E32D6EF00A7E76A /* LocalizationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0414ECFD2E32D6EF00A7E76A /* LocalizationManager.swift */; }; 0414ED032E32D72400A7E76A /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 0414ED012E32D72400A7E76A /* Localizable.strings */; }; + 0414ED052E32D90000A7E76A /* SettingsViewLibrary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0414ED042E32D90000A7E76A /* SettingsViewLibrary.swift */; }; 041E9D722E11D71F0025F150 /* SettingsViewBackup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 041E9D712E11D71F0025F150 /* SettingsViewBackup.swift */; }; 04536F712E04BA3B00A11248 /* JSController-Novel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04536F702E04BA3B00A11248 /* JSController-Novel.swift */; }; 04536F742E04BA5600A11248 /* ReaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04536F722E04BA5600A11248 /* ReaderView.swift */; }; @@ -143,6 +144,7 @@ 0414ECFC2E32D6EF00A7E76A /* Bundle+Language.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+Language.swift"; sourceTree = ""; }; 0414ECFD2E32D6EF00A7E76A /* LocalizationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizationManager.swift; sourceTree = ""; }; 0414ED002E32D72400A7E76A /* mn-Cyrl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "mn-Cyrl"; path = Localizable.strings; sourceTree = ""; }; + 0414ED042E32D90000A7E76A /* SettingsViewLibrary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewLibrary.swift; sourceTree = ""; }; 041E9D712E11D71F0025F150 /* SettingsViewBackup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewBackup.swift; sourceTree = ""; }; 0452339E2E02149C002EA23C /* bos */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bos; path = Localizable.strings; sourceTree = ""; }; 04536F702E04BA3B00A11248 /* JSController-Novel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "JSController-Novel.swift"; sourceTree = ""; }; @@ -594,6 +596,7 @@ 133D7C832D2BE2630075467E /* SettingsSubViews */ = { isa = PBXGroup; children = ( + 0414ED042E32D90000A7E76A /* SettingsViewLibrary.swift */, 041E9D712E11D71F0025F150 /* SettingsViewBackup.swift */, 1E9FF1D22D403E42008AC100 /* SettingsViewLoggerFilter.swift */, 1399FAD32D3AB38C00E97C31 /* SettingsViewLogger.swift */, @@ -1015,6 +1018,7 @@ 047F170E2E0C93F30081B5FB /* ContinueReadingItem.swift in Sources */, 047F170F2E0C93F30081B5FB /* ContinueReadingManager.swift in Sources */, 13DB46902D900A38008CBC03 /* URL.swift in Sources */, + 0414ED052E32D90000A7E76A /* SettingsViewLibrary.swift in Sources */, 138FE1D02DECA00D00936D81 /* TMDB-FetchID.swift in Sources */, 04AD07162E03704700EB74C1 /* BookmarkCell.swift in Sources */, 130C6BFA2D53AB1F00DC1432 /* SettingsViewData.swift in Sources */,