Separate library settings

This commit is contained in:
50/50 2025-07-24 23:13:04 +02:00
parent f015db7e34
commit faa7700ff7
4 changed files with 289 additions and 149 deletions

View file

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

View file

@ -0,0 +1,280 @@
//
// SettingsViewLibrary.swift
// Sora
//
// Created by paul on 05/02/25.
//
import SwiftUI
import UIKit
fileprivate struct SettingsSection<Content: View>: 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<T: Hashable>: 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<Bool> {
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)
}
)
}
}

View file

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

View file

@ -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 = "<group>"; };
0414ECFD2E32D6EF00A7E76A /* LocalizationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizationManager.swift; sourceTree = "<group>"; };
0414ED002E32D72400A7E76A /* mn-Cyrl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "mn-Cyrl"; path = Localizable.strings; sourceTree = "<group>"; };
0414ED042E32D90000A7E76A /* SettingsViewLibrary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewLibrary.swift; sourceTree = "<group>"; };
041E9D712E11D71F0025F150 /* SettingsViewBackup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewBackup.swift; sourceTree = "<group>"; };
0452339E2E02149C002EA23C /* bos */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bos; path = Localizable.strings; sourceTree = "<group>"; };
04536F702E04BA3B00A11248 /* JSController-Novel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "JSController-Novel.swift"; sourceTree = "<group>"; };
@ -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 */,