diff --git a/readeck/Data/Repository/SettingsRepository.swift b/readeck/Data/Repository/SettingsRepository.swift index 6a30a0c..2d4eaf8 100644 --- a/readeck/Data/Repository/SettingsRepository.swift +++ b/readeck/Data/Repository/SettingsRepository.swift @@ -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 { diff --git a/readeck/Domain/Model/UrlOpener.swift b/readeck/Domain/Model/UrlOpener.swift new file mode 100644 index 0000000..5189b6b --- /dev/null +++ b/readeck/Domain/Model/UrlOpener.swift @@ -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" + } + } +} diff --git a/readeck/Domain/UseCase/SaveSettingsUseCase.swift b/readeck/Domain/UseCase/SaveSettingsUseCase.swift index 4217318..e9ba64a 100644 --- a/readeck/Domain/UseCase/SaveSettingsUseCase.swift +++ b/readeck/Domain/UseCase/SaveSettingsUseCase.swift @@ -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) + ) + } } diff --git a/readeck/UI/BookmarkDetail/BookmarkDetailView.swift b/readeck/UI/BookmarkDetail/BookmarkDetailView.swift index 4a2e833..105eabe 100644 --- a/readeck/UI/BookmarkDetail/BookmarkDetailView.swift +++ b/readeck/UI/BookmarkDetail/BookmarkDetailView.swift @@ -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) diff --git a/readeck/UI/Bookmarks/BookmarkCardView.swift b/readeck/UI/Bookmarks/BookmarkCardView.swift index 9cb430b..32a2080 100644 --- a/readeck/UI/Bookmarks/BookmarkCardView.swift +++ b/readeck/UI/Bookmarks/BookmarkCardView.swift @@ -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()) } -} \ No newline at end of file +} diff --git a/readeck/UI/Factory/MockUseCaseFactory.swift b/readeck/UI/Factory/MockUseCaseFactory.swift index c752f2c..21dc142 100644 --- a/readeck/UI/Factory/MockUseCaseFactory.swift +++ b/readeck/UI/Factory/MockUseCaseFactory.swift @@ -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 { diff --git a/readeck/UI/Models/AppSettings.swift b/readeck/UI/Models/AppSettings.swift index 857d124..4ab7c18 100644 --- a/readeck/UI/Models/AppSettings.swift +++ b/readeck/UI/Models/AppSettings.swift @@ -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 diff --git a/readeck/UI/Settings/SettingsGeneralView.swift b/readeck/UI/Settings/SettingsGeneralView.swift index b1da02a..eb6c3ca 100644 --- a/readeck/UI/Settings/SettingsGeneralView.swift +++ b/readeck/UI/Settings/SettingsGeneralView.swift @@ -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()) } diff --git a/readeck/UI/Settings/SettingsGeneralViewModel.swift b/readeck/UI/Settings/SettingsGeneralViewModel.swift index a6f92e9..5514cf8 100644 --- a/readeck/UI/Settings/SettingsGeneralViewModel.swift +++ b/readeck/UI/Settings/SettingsGeneralViewModel.swift @@ -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" diff --git a/readeck/UI/Utils/SafariUtil.swift b/readeck/UI/Utils/URLUtil.swift similarity index 62% rename from readeck/UI/Utils/SafariUtil.swift rename to readeck/UI/Utils/URLUtil.swift index de2966f..7a9b9a7 100644 --- a/readeck/UI/Utils/SafariUtil.swift +++ b/readeck/UI/Utils/URLUtil.swift @@ -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: "") diff --git a/readeck/readeck.xcdatamodeld/readeck.xcdatamodel/contents b/readeck/readeck.xcdatamodeld/readeck.xcdatamodel/contents index cde8c61..d371f92 100644 --- a/readeck/readeck.xcdatamodeld/readeck.xcdatamodel/contents +++ b/readeck/readeck.xcdatamodeld/readeck.xcdatamodel/contents @@ -1,5 +1,5 @@ - + @@ -57,6 +57,7 @@ +