mirror of
https://github.com/Ranchero-Software/NetNewsWire
synced 2025-08-12 06:26:36 +00:00
Article themes moved to SwiftUI
This commit is contained in:
140
iOS/Settings/Appearance/ArticleThemeManagerView.swift
Normal file
140
iOS/Settings/Appearance/ArticleThemeManagerView.swift
Normal file
@@ -0,0 +1,140 @@
|
||||
//
|
||||
// ArticleThemeManagerView.swift
|
||||
// NetNewsWire-iOS
|
||||
//
|
||||
// Created by Stuart Breckenridge on 20/12/2022.
|
||||
// Copyright © 2022 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ArticleThemeManagerView: View {
|
||||
|
||||
@StateObject private var themeManager = ArticleThemesManager.shared
|
||||
@State private var showDeleteConfirmation: (String, Bool) = ("", false)
|
||||
@State private var showImportThemeView: Bool = false
|
||||
@State private var showImportConfirmationAlert: (ArticleTheme?, Bool) = (nil, false)
|
||||
@State private var showImportErrorAlert: (Error?, Bool) = (nil, false)
|
||||
@State private var showImportSuccessAlert: Bool = false
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
Section(header: Text("INSTALLED_THEMES", tableName: "Settings")) {
|
||||
articleThemeRow("Default")
|
||||
ForEach(themeManager.themeNames, id: \.self) {theme in
|
||||
articleThemeRow(theme)
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle(Text("ARTICLE_THEMES_TITLE", tableName: "Settings"))
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button {
|
||||
showImportThemeView = true
|
||||
} label: {
|
||||
Label {
|
||||
Text("IMPORT_THEME_BUTTON_TITLE", tableName: "Buttons")
|
||||
} icon: {
|
||||
Image(systemName: "plus")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.fileImporter(isPresented: $showImportThemeView, allowedContentTypes: NNWThemeDocument.readableContentTypes) { result in
|
||||
switch result {
|
||||
case .success(let success):
|
||||
do {
|
||||
let theme = try ArticleTheme(path: success.path, isAppTheme: true)
|
||||
showImportConfirmationAlert = (theme, true)
|
||||
} catch {
|
||||
showImportErrorAlert = (error, true)
|
||||
}
|
||||
case .failure(let failure):
|
||||
showImportErrorAlert = (failure, true)
|
||||
}
|
||||
}
|
||||
.alert(Text("DELETE_THEME_ALERT_TITLE_\(showDeleteConfirmation.0)", tableName: "Settings"), isPresented: $showDeleteConfirmation.1, actions: {
|
||||
Button(role: .destructive) {
|
||||
ArticleThemesManager.shared.deleteTheme(themeName: showDeleteConfirmation.0)
|
||||
} label: {
|
||||
Text("DELETE_THEME_BUTTON_TITLE", tableName: "Buttons")
|
||||
}
|
||||
|
||||
Button(role: .cancel) {
|
||||
|
||||
} label: {
|
||||
Text("CANCEL_BUTTON_TITLE", tableName: "Buttons")
|
||||
}
|
||||
}, message: {
|
||||
Text("DELETE_THEME_ALERT_MESSAGE", tableName: "Settings")
|
||||
})
|
||||
.alert(Text("IMPORT_THEME_CONFIRMATION_TITLE", tableName: "Settings"),
|
||||
isPresented: $showImportConfirmationAlert.1,
|
||||
actions: {
|
||||
Button {
|
||||
do {
|
||||
try ArticleThemesManager.shared.importTheme(filename: showImportConfirmationAlert.0!.path!)
|
||||
showImportSuccessAlert = true
|
||||
} catch {
|
||||
showImportErrorAlert = (error, true)
|
||||
}
|
||||
|
||||
} label: {
|
||||
Text("IMPORT_THEME_BUTTON_TITLE", tableName: "Buttons")
|
||||
}
|
||||
|
||||
Button(role: .cancel) {
|
||||
|
||||
} label: {
|
||||
Text("CANCEL_BUTTON_TITLE", tableName: "Buttons")
|
||||
}
|
||||
}, message: {
|
||||
Text("IMPORT_THEME_CONFIRMATION_MESSAGE_\(showImportConfirmationAlert.0?.name ?? "")_\(showImportConfirmationAlert.0?.creatorName ?? "")", tableName: "Settings")
|
||||
})
|
||||
.alert(Text("IMPORT_THEME_SUCCESS_TITLE", tableName: "Settings"),
|
||||
isPresented: $showImportSuccessAlert,
|
||||
actions: {
|
||||
Button(role: .cancel) {
|
||||
|
||||
} label: {
|
||||
Text("DISMISS_BUTTON_TITLE", tableName: "Buttons")
|
||||
}
|
||||
}, message: {
|
||||
Text("IMPORT_THEME_SUCCESS_MESSAGE_\(showImportConfirmationAlert.0?.name ?? "")", tableName: "Settings")
|
||||
})
|
||||
}
|
||||
|
||||
func articleThemeRow(_ theme: String) -> some View {
|
||||
Button {
|
||||
ArticleThemesManager.shared.currentThemeName = theme
|
||||
} label: {
|
||||
HStack {
|
||||
Text(theme)
|
||||
.foregroundColor(.primary)
|
||||
Spacer()
|
||||
if ArticleThemesManager.shared.currentThemeName == theme {
|
||||
Image(systemName: "checkmark")
|
||||
.foregroundColor(Color(uiColor: AppAssets.primaryAccentColor))
|
||||
}
|
||||
}
|
||||
}
|
||||
.swipeActions(edge: .trailing, allowsFullSwipe: false) {
|
||||
if theme == "Default" || ArticleThemesManager.shared.currentThemeName == theme { }
|
||||
else {
|
||||
Button {
|
||||
showDeleteConfirmation = (theme, true)
|
||||
} label: {
|
||||
Text("DELETE_BUTTON_TITLE", tableName: "Buttons")
|
||||
Image(systemName: "trash")
|
||||
}
|
||||
.tint(.red)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ArticleThemeImporterView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ArticleThemeManagerView()
|
||||
}
|
||||
}
|
||||
87
iOS/Settings/Appearance/ColorPaletteSelectorView.swift
Normal file
87
iOS/Settings/Appearance/ColorPaletteSelectorView.swift
Normal file
@@ -0,0 +1,87 @@
|
||||
//
|
||||
// ColorPaletteSelectorView.swift
|
||||
// NetNewsWire-iOS
|
||||
//
|
||||
// Created by Stuart Breckenridge on 13/11/2022.
|
||||
// Copyright © 2022 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ColorPaletteSelectorView: View {
|
||||
|
||||
@StateObject private var appDefaults = AppDefaults.shared
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
appLightButton()
|
||||
Spacer()
|
||||
appDarkButton()
|
||||
Spacer()
|
||||
appAutomaticButton()
|
||||
}
|
||||
}
|
||||
|
||||
func appLightButton() -> some View {
|
||||
VStack(spacing: 4) {
|
||||
Image("app.appearance.light")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 40.0, height: 40.0)
|
||||
Text("ALWAYS_LIGHT_MODE", tableName: "Settings")
|
||||
.font(.subheadline)
|
||||
if AppDefaults.userInterfaceColorPalette == .light {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.foregroundColor(Color(uiColor: AppAssets.primaryAccentColor))
|
||||
} else {
|
||||
Image(systemName: "circle")
|
||||
}
|
||||
}.onTapGesture {
|
||||
AppDefaults.userInterfaceColorPalette = .light
|
||||
}
|
||||
}
|
||||
|
||||
func appDarkButton() -> some View {
|
||||
VStack(spacing: 4) {
|
||||
Image("app.appearance.dark")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 40.0, height: 40.0)
|
||||
Text("ALWAYS_DARK_MODE", tableName: "Settings")
|
||||
.font(.subheadline)
|
||||
if AppDefaults.userInterfaceColorPalette == .dark {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.foregroundColor(Color(uiColor: AppAssets.primaryAccentColor))
|
||||
} else {
|
||||
Image(systemName: "circle")
|
||||
}
|
||||
}.onTapGesture {
|
||||
AppDefaults.userInterfaceColorPalette = .dark
|
||||
}
|
||||
}
|
||||
|
||||
func appAutomaticButton() -> some View {
|
||||
VStack(spacing: 4) {
|
||||
Image("app.appearance.automatic")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 40.0, height: 40.0)
|
||||
Text("USE_SYSTEM_DISPLAY_MODE", tableName: "Settings")
|
||||
.font(.subheadline)
|
||||
if AppDefaults.userInterfaceColorPalette == .automatic {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.foregroundColor(Color(uiColor: AppAssets.primaryAccentColor))
|
||||
} else {
|
||||
Image(systemName: "circle")
|
||||
}
|
||||
}.onTapGesture {
|
||||
AppDefaults.userInterfaceColorPalette = .automatic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct DisplayModeView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ColorPaletteSelectorView()
|
||||
}
|
||||
}
|
||||
51
iOS/Settings/Appearance/DisplayAndBehaviorsView.swift
Normal file
51
iOS/Settings/Appearance/DisplayAndBehaviorsView.swift
Normal file
@@ -0,0 +1,51 @@
|
||||
//
|
||||
// DisplayAndBehaviorsView.swift
|
||||
// NetNewsWire-iOS
|
||||
//
|
||||
// Created by Stuart Breckenridge on 12/11/2022.
|
||||
// Copyright © 2022 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct DisplayAndBehaviorsView: View {
|
||||
|
||||
@StateObject private var appDefaults = AppDefaults.shared
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
Section(header: Text("APPLICATION_HEADER", tableName: "Settings")) {
|
||||
ColorPaletteSelectorView()
|
||||
.listRowBackground(Color.clear)
|
||||
}
|
||||
|
||||
Section(header: Text("TIMELINE_HEADER", tableName: "Settings")) {
|
||||
SettingsViewRows.sortOldestToNewest($appDefaults.timelineSortDirectionBool)
|
||||
SettingsViewRows.groupByFeed($appDefaults.timelineGroupByFeed)
|
||||
SettingsViewRows.refreshToClearReadArticles($appDefaults.refreshClearsReadArticles)
|
||||
SettingsViewRows.timelineLayout
|
||||
}
|
||||
|
||||
Section(header: Text("ARTICLE_HEADER", tableName: "Settings")) {
|
||||
SettingsViewRows.themeSelection
|
||||
SettingsViewRows.confirmMarkAllAsRead($appDefaults.confirmMarkAllAsRead)
|
||||
SettingsViewRows.openLinksInNetNewsWire(Binding<Bool>(
|
||||
get: { !appDefaults.useSystemBrowser },
|
||||
set: { appDefaults.useSystemBrowser = !$0 }
|
||||
))
|
||||
// TODO: Add Reader Mode Defaults here. See #3684.
|
||||
}
|
||||
}
|
||||
.navigationTitle(Text("Display & Behaviors"))
|
||||
.tint(Color(uiColor: AppAssets.primaryAccentColor))
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
struct AppearanceManagementView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
DisplayAndBehaviorsView()
|
||||
}
|
||||
}
|
||||
118
iOS/Settings/Appearance/TimelineCustomizerView.swift
Normal file
118
iOS/Settings/Appearance/TimelineCustomizerView.swift
Normal file
@@ -0,0 +1,118 @@
|
||||
//
|
||||
// TimelineCustomizerView.swift
|
||||
// NetNewsWire-iOS
|
||||
//
|
||||
// Created by Stuart Breckenridge on 20/12/2022.
|
||||
// Copyright © 2022 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
|
||||
struct TimelineCustomizerView: View {
|
||||
|
||||
@StateObject private var appDefaults = AppDefaults.shared
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
Section {
|
||||
ZStack {
|
||||
Picker(selection: $appDefaults.timelineIconSize) {
|
||||
ForEach(IconSize.allCases, id: \.self) { size in
|
||||
Text(size.description)
|
||||
}
|
||||
} label: {
|
||||
Text("ICON_SIZE", tableName: "Settings")
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.vertical, 4)
|
||||
.listRowInsets(EdgeInsets(top: 0, leading: 15, bottom: 0, trailing: 15))
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 8)
|
||||
.foregroundColor(Color(uiColor: UIColor.secondarySystemGroupedBackground))
|
||||
)
|
||||
}
|
||||
.listRowInsets(EdgeInsets(top: 0, leading: 30, bottom: 0, trailing: 30))
|
||||
.listRowBackground(Color.clear)
|
||||
.listRowSeparator(.hidden)
|
||||
|
||||
Section {
|
||||
ZStack {
|
||||
Picker(selection: $appDefaults.timelineNumberOfLines) {
|
||||
ForEach(1...5, id: \.self) { size in
|
||||
Text("\(size)")
|
||||
}
|
||||
} label: {
|
||||
Text("NUMBER_OF_LINES", tableName: "Settings")
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.vertical, 4)
|
||||
.listRowInsets(EdgeInsets(top: 0, leading: 15, bottom: 0, trailing: 15))
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 8)
|
||||
.foregroundColor(Color(uiColor: UIColor.secondarySystemGroupedBackground))
|
||||
)
|
||||
}
|
||||
.listRowInsets(EdgeInsets(top: 0, leading: 30, bottom: 0, trailing: 30))
|
||||
.listRowBackground(Color.clear)
|
||||
.listRowSeparator(.hidden)
|
||||
|
||||
Section {
|
||||
withAnimation {
|
||||
timeLinePreviewRow
|
||||
.listRowInsets(EdgeInsets(top: 8, leading: 4, bottom: 4, trailing: 24))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
.listStyle(.grouped)
|
||||
.navigationTitle(Text("TIMELINE_LAYOUT", tableName: "Settings"))
|
||||
.onAppear {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
var timeLinePreviewRow: some View {
|
||||
HStack(alignment: .top, spacing: 6) {
|
||||
VStack {
|
||||
Circle()
|
||||
.foregroundColor(Color(uiColor: AppAssets.primaryAccentColor))
|
||||
.frame(width: 12, height: 12)
|
||||
Spacer()
|
||||
}.frame(width: 12)
|
||||
VStack {
|
||||
Image("faviconTemplateImage")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.frame(width: appDefaults.timelineIconSize.size.width, height: appDefaults.timelineIconSize.size.height)
|
||||
.foregroundColor(Color(uiColor: AppAssets.primaryAccentColor))
|
||||
Spacer()
|
||||
}.frame(width: appDefaults.timelineIconSize.size.width)
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text("Enim ut tellus elementum sagittis vitae et. Nibh praesent tristique magna sit amet purus gravida quis blandit. Neque volutpat ac tincidunt vitae semper quis lectus nulla. Massa id neque aliquam vestibulum morbi blandit. Ultrices vitae auctor eu augue. Enim eu turpis egestas pretium aenean pharetra magna. Eget gravida cum sociis natoque. Sit amet consectetur adipiscing elit. Auctor eu augue ut lectus arcu bibendum. Maecenas volutpat blandit aliquam etiam erat velit. Ut pharetra sit amet aliquam id diam maecenas ultricies. In hac habitasse platea dictumst quisque sagittis purus sit amet.")
|
||||
.bold()
|
||||
.lineLimit(appDefaults.timelineNumberOfLines)
|
||||
HStack {
|
||||
Text("Feed name")
|
||||
.foregroundColor(.secondary)
|
||||
.font(.caption)
|
||||
Spacer()
|
||||
Text("08:51")
|
||||
.foregroundColor(.secondary)
|
||||
.font(.caption)
|
||||
}.padding(0)
|
||||
}
|
||||
}
|
||||
.edgesIgnoringSafeArea(.all)
|
||||
.padding(.vertical, 4)
|
||||
.padding(.leading, 4)
|
||||
}
|
||||
}
|
||||
|
||||
struct TimelineCustomizerView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
TimelineCustomizerView()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user