Added setting to select in app or default browser to open external links

This commit is contained in:
Christian Putzke 2025-09-18 22:35:43 +02:00
parent 26990c59fa
commit f78de1f740
11 changed files with 83 additions and 17 deletions

View File

@ -14,6 +14,8 @@ struct Settings {
var theme: Theme? = nil
var cardLayoutStyle: CardLayoutStyle? = nil
var urlOpener: UrlOpener? = nil
var isLoggedIn: Bool {
token != nil && !token!.isEmpty
}
@ -91,6 +93,10 @@ class SettingsRepository: PSettingsRepository {
existingSettings.theme = theme.rawValue
}
if let urlOpener = settings.urlOpener {
existingSettings.urlOpener = urlOpener.rawValue
}
if let cardLayoutStyle = settings.cardLayoutStyle {
existingSettings.cardLayoutStyle = cardLayoutStyle.rawValue
}
@ -132,7 +138,8 @@ class SettingsRepository: PSettingsRepository {
fontSize: FontSize(rawValue: settingEntity?.fontSize ?? FontSize.medium.rawValue),
enableTTS: settingEntity?.enableTTS,
theme: Theme(rawValue: settingEntity?.theme ?? Theme.system.rawValue),
cardLayoutStyle: CardLayoutStyle(rawValue: settingEntity?.cardLayoutStyle ?? CardLayoutStyle.magazine.rawValue)
cardLayoutStyle: CardLayoutStyle(rawValue: settingEntity?.cardLayoutStyle ?? CardLayoutStyle.magazine.rawValue),
urlOpener: UrlOpener(rawValue: settingEntity?.urlOpener ?? UrlOpener.inAppBrowser.rawValue)
)
continuation.resume(returning: settings)
} catch {

View File

@ -0,0 +1,13 @@
import SwiftUI
enum UrlOpener: String, CaseIterable {
case inAppBrowser = "inAppBrowser"
case defaultBrowser = "defaultBrowser"
var displayName: String {
switch self {
case .inAppBrowser: return "In App Browser"
case .defaultBrowser: return "Default Browser"
}
}
}

View File

@ -4,6 +4,7 @@ protocol PSaveSettingsUseCase {
func execute(selectedFontFamily: FontFamily, selectedFontSize: FontSize) async throws
func execute(enableTTS: Bool) async throws
func execute(theme: Theme) async throws
func execute(urlOpener: UrlOpener) async throws
}
class SaveSettingsUseCase: PSaveSettingsUseCase {
@ -33,4 +34,10 @@ class SaveSettingsUseCase: PSaveSettingsUseCase {
.init(theme: theme)
)
}
func execute(urlOpener: UrlOpener) async throws {
try await settingsRepository.saveSettings(
.init(urlOpener: urlOpener)
)
}
}

View File

@ -72,7 +72,7 @@ struct BookmarkDetailView: View {
.padding()
} else {
Button(action: {
SafariUtil.openInSafari(url: viewModel.bookmarkDetail.url)
URLUtil.open(url: viewModel.bookmarkDetail.url, urlOpener: appSettings.urlOpener)
}) {
HStack {
Image(systemName: "safari")
@ -263,7 +263,7 @@ struct BookmarkDetailView: View {
.padding()
} else {
Button(action: {
SafariUtil.openInSafari(url: viewModel.bookmarkDetail.url)
URLUtil.open(url: viewModel.bookmarkDetail.url, urlOpener: appSettings.urlOpener)
}) {
HStack {
Image(systemName: "safari")
@ -319,7 +319,7 @@ struct BookmarkDetailView: View {
metaRow(icon: "safari") {
Button(action: {
SafariUtil.openInSafari(url: viewModel.bookmarkDetail.url)
URLUtil.open(url: viewModel.bookmarkDetail.url, urlOpener: appSettings.urlOpener)
}) {
Text((URLUtil.extractDomain(from: viewModel.bookmarkDetail.url) ?? "Open original page") + " open")
.font(.subheadline)

View File

@ -14,6 +14,7 @@ extension View {
struct BookmarkCardView: View {
@Environment(\.colorScheme) var colorScheme
@EnvironmentObject var appSettings: AppSettings
let bookmark: Bookmark
let currentState: BookmarkState
@ -255,7 +256,7 @@ struct BookmarkCardView: View {
HStack {
Label((URLUtil.extractDomain(from: bookmark.url) ?? "Original Site") + " open", systemImage: "safari")
.onTapGesture {
SafariUtil.openInSafari(url: bookmark.url)
URLUtil.open(url: bookmark.url, urlOpener: appSettings.urlOpener)
}
}
}
@ -336,7 +337,7 @@ struct BookmarkCardView: View {
HStack {
Label((URLUtil.extractDomain(from: bookmark.url) ?? "Original Site") + " open", systemImage: "safari")
.onTapGesture {
SafariUtil.openInSafari(url: bookmark.url)
URLUtil.open(url: bookmark.url, urlOpener: appSettings.urlOpener)
}
}
}
@ -434,4 +435,4 @@ struct IconBadge: View {
.foregroundColor(.white)
.clipShape(Circle())
}
}
}

View File

@ -150,6 +150,7 @@ class MockSaveSettingsUseCase: PSaveSettingsUseCase {
func execute(selectedFontFamily: FontFamily, selectedFontSize: FontSize) async throws {}
func execute(enableTTS: Bool) async throws {}
func execute(theme: Theme) async throws {}
func execute(urlOpener: UrlOpener) async throws {}
}
class MockGetBookmarkUseCase: PGetBookmarkUseCase {

View File

@ -26,6 +26,10 @@ class AppSettings: ObservableObject {
var theme: Theme {
settings?.theme ?? .system
}
var urlOpener: UrlOpener {
settings?.urlOpener ?? .inAppBrowser
}
init(settings: Settings? = nil) {
self.settings = settings

View File

@ -8,7 +8,7 @@
import SwiftUI
struct SettingsGeneralView: View {
@State private var viewModel: SettingsGeneralViewModel
@State private var viewModel: SettingsGeneralViewModel
init(viewModel: SettingsGeneralViewModel = SettingsGeneralViewModel()) {
self.viewModel = viewModel
@ -33,6 +33,23 @@ struct SettingsGeneralView: View {
.font(.footnote)
}
// Reading Settings
VStack(alignment: .leading, spacing: 12) {
Text("Open external links in")
.font(.headline)
Picker("urlOpener", selection: $viewModel.urlOpener) {
ForEach(UrlOpener.allCases, id: \.self) { urlOpener in
Text(urlOpener.displayName).tag(urlOpener)
}
}
.pickerStyle(.segmented)
.onChange(of: viewModel.urlOpener) {
Task {
await viewModel.saveGeneralSettings()
}
}
}
#if DEBUG
// Sync Settings
VStack(alignment: .leading, spacing: 12) {
@ -55,8 +72,6 @@ struct SettingsGeneralView: View {
.font(.headline)
Toggle("Safari Reader Mode", isOn: $viewModel.enableReaderMode)
.toggleStyle(SwitchToggleStyle())
Toggle("Open external links in in-app Safari", isOn: $viewModel.openExternalLinksInApp)
.toggleStyle(SwitchToggleStyle())
Toggle("Automatically mark articles as read", isOn: $viewModel.autoMarkAsRead)
.toggleStyle(SwitchToggleStyle())
}

View File

@ -15,8 +15,8 @@ class SettingsGeneralViewModel {
// MARK: - Reading Settings
var enableReaderMode: Bool = false
var enableTTS: Bool = false
var openExternalLinksInApp: Bool = true
var autoMarkAsRead: Bool = false
var urlOpener: UrlOpener = .inAppBrowser
// MARK: - Messages
@ -36,6 +36,7 @@ class SettingsGeneralViewModel {
if let settings = try await loadSettingsUseCase.execute() {
enableTTS = settings.enableTTS ?? false
selectedTheme = settings.theme ?? .system
urlOpener = settings.urlOpener ?? .inAppBrowser
autoSyncEnabled = false
}
} catch {
@ -48,6 +49,7 @@ class SettingsGeneralViewModel {
do {
try await saveSettingsUseCase.execute(enableTTS: enableTTS)
try await saveSettingsUseCase.execute(theme: selectedTheme)
try await saveSettingsUseCase.execute(urlOpener: urlOpener)
successMessage = "Settings saved"

View File

@ -1,8 +1,25 @@
import UIKit
import SafariServices
class SafariUtil {
static func openInSafari(url: String) {
struct URLUtil {
static func open(url: String, urlOpener: UrlOpener = .inAppBrowser) {
// Could be extended to open in other browsers like Firefox, Brave etc. if somebody has a multi browser setup
// and wants readeck links to always opened in a specific browser
switch urlOpener {
case .defaultBrowser:
openUrlInDefaultBrowser(url: url)
default:
openUrlInInAppBrowser(url: url)
}
}
static func openUrlInDefaultBrowser(url: String) {
guard let url = URL(string: url) else { return }
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
static func openUrlInInAppBrowser(url: String) {
guard let url = URL(string: url) else { return }
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
@ -22,9 +39,7 @@ class SafariUtil {
presentingViewController.present(safariViewController, animated: true)
}
}
}
struct URLUtil {
static func extractDomain(from urlString: String) -> String? {
guard let url = URL(string: urlString), let host = url.host else { return nil }
return host.replacingOccurrences(of: "www.", with: "")

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="23788.4" systemVersion="24G84" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="24299" systemVersion="25A354" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="ArticleURLEntity" representedClassName="ArticleURLEntity" syncable="YES" codeGenerationType="class">
<attribute name="id" optional="YES" attributeType="UUID" usesScalarValueType="NO"/>
<attribute name="tags" optional="YES" attributeType="String"/>
@ -57,6 +57,7 @@
<attribute name="fontSize" optional="YES" attributeType="String"/>
<attribute name="theme" optional="YES" attributeType="String"/>
<attribute name="token" optional="YES" attributeType="String"/>
<attribute name="urlOpener" optional="YES" attributeType="String"/>
</entity>
<entity name="TagEntity" representedClassName="TagEntity" syncable="YES" codeGenerationType="class">
<attribute name="name" optional="YES" attributeType="String"/>