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:
@@ -0,0 +1,59 @@
|
||||
//
|
||||
// AddExtensionListView.swift
|
||||
// NetNewsWire-iOS
|
||||
//
|
||||
// Created by Stuart Breckenridge on 13/12/2022.
|
||||
// Copyright © 2022 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Accounts
|
||||
|
||||
struct AddExtensionListView: View {
|
||||
|
||||
@State private var availableExtensionPointTypes = ExtensionPointManager.shared.availableExtensionPointTypes.sorted(by: { $0.title < $1.title })
|
||||
@Environment(\.dismiss) var dismiss
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
List {
|
||||
Section(header: Text("FEED_PROVIDER_HEADER", tableName: "Settings"),
|
||||
footer: Text("FEED_PROVIDER_FOOTER", tableName: "Settings")) {
|
||||
ForEach(0..<availableExtensionPointTypes.count, id: \.self) { i in
|
||||
NavigationLink {
|
||||
EnableExtensionPointView(extensionPoint: availableExtensionPointTypes[i])
|
||||
} label: {
|
||||
Image(uiImage: availableExtensionPointTypes[i].image)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 25, height: 25)
|
||||
|
||||
Text("\(availableExtensionPointTypes[i].title)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationTitle(Text("ADD_EXTENSIONS_TITLE", tableName: "Settings"))
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button(role: .cancel) {
|
||||
dismiss()
|
||||
} label: {
|
||||
Text("CANCEL_BUTTON_TITLE", tableName: "Buttons")
|
||||
}
|
||||
}
|
||||
}
|
||||
.onReceive(NotificationCenter.default.publisher(for: .ActiveExtensionPointsDidChange)) { _ in
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
struct AddExtensionListView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
AddExtensionListView()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
//
|
||||
// EnableExtensionPointView.swift
|
||||
// NetNewsWire-iOS
|
||||
//
|
||||
// Created by Stuart Breckenridge on 19/12/2022.
|
||||
// Copyright © 2022 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct EnableExtensionPointView: View {
|
||||
|
||||
@Environment(\.dismiss) var dismiss
|
||||
@StateObject private var viewModel = EnableExtensionViewModel()
|
||||
@State private var extensionError: (Error?, Bool) = (nil, false)
|
||||
var extensionPoint: ExtensionPoint.Type
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
ExtensionSectionHeader(extensionPoint: extensionPoint)
|
||||
Section(footer: extensionExplainer) {}
|
||||
Section { enableButton }
|
||||
}
|
||||
.alert(Text("ERROR_TITLE", tableName: "Errors"), isPresented: $extensionError.1, actions: {
|
||||
Button(action: {}, label: { Text("DISMISS_BUTTON_TITLE", tableName: "Buttons") })
|
||||
}, message: {
|
||||
Text(extensionError.0?.localizedDescription ?? "Unknown Error")
|
||||
})
|
||||
.navigationTitle(extensionPoint.title)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.dismissOnExternalContextLaunch()
|
||||
.onReceive(NotificationCenter.default.publisher(for: .ActiveExtensionPointsDidChange)) { _ in
|
||||
dismiss()
|
||||
}
|
||||
.edgesIgnoringSafeArea(.bottom)
|
||||
}
|
||||
|
||||
var extensionExplainer: some View {
|
||||
Text(extensionPoint.description.string)
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
|
||||
var enableButton: some View {
|
||||
Button {
|
||||
Task {
|
||||
viewModel.configure(extensionPoint)
|
||||
do {
|
||||
try await viewModel.enableExtension()
|
||||
} catch {
|
||||
extensionError = (error, true)
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
Spacer()
|
||||
Text("ENABLE_EXTENSION_BUTTON_TITLE", tableName: "Buttons")
|
||||
Spacer()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
//
|
||||
// EnableExtensionViewModel.swift
|
||||
// NetNewsWire-iOS
|
||||
//
|
||||
// Created by Stuart Breckenridge on 19/12/2022.
|
||||
// Copyright © 2022 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import AuthenticationServices
|
||||
import Account
|
||||
import OAuthSwift
|
||||
import Secrets
|
||||
import RSCore
|
||||
|
||||
@MainActor
|
||||
public final class EnableExtensionViewModel: NSObject, ObservableObject, OAuthSwiftURLHandlerType, ASWebAuthenticationPresentationContextProviding, Logging {
|
||||
|
||||
private var extensionPointType: ExtensionPoint.Type?
|
||||
private var oauth: OAuthSwift?
|
||||
private var callbackURL: URL? = nil
|
||||
|
||||
|
||||
func configure(_ extensionPointType: ExtensionPoint.Type) {
|
||||
self.extensionPointType = extensionPointType
|
||||
}
|
||||
|
||||
func enableExtension() async throws {
|
||||
guard let extensionPointType = extensionPointType else { return }
|
||||
if let oauth1 = extensionPointType as? OAuth1SwiftProvider.Type {
|
||||
try await enableOAuth1(oauth1)
|
||||
} else if let oauth2 = extensionPointType as? OAuth2SwiftProvider.Type {
|
||||
try await enableOAuth2(oauth2)
|
||||
} else {
|
||||
try await activateExtensionPoint(extensionPointType)
|
||||
}
|
||||
}
|
||||
|
||||
private func activateExtensionPoint(_ point: ExtensionPoint.Type) async throws {
|
||||
return try await withCheckedThrowingContinuation { continuation in
|
||||
ExtensionPointManager.shared.activateExtensionPoint(point) { result in
|
||||
switch result {
|
||||
case .success(_):
|
||||
continuation.resume()
|
||||
return
|
||||
case .failure(let failure):
|
||||
continuation.resume(throwing: failure)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Enable OAuth
|
||||
private func enableOAuth1(_ provider: OAuth1SwiftProvider.Type) async throws {
|
||||
callbackURL = provider.callbackURL
|
||||
|
||||
let oauth1 = provider.oauth1Swift
|
||||
self.oauth = oauth1
|
||||
oauth1.authorizeURLHandler = self
|
||||
|
||||
return try await withCheckedThrowingContinuation { continuation in
|
||||
oauth1.authorize(withCallbackURL: callbackURL!) { [weak self] result in
|
||||
|
||||
guard let self = self, let extensionPointType = self.extensionPointType else { return }
|
||||
|
||||
switch result {
|
||||
case .success(let tokenSuccess):
|
||||
ExtensionPointManager.shared.activateExtensionPoint(extensionPointType, tokenSuccess: tokenSuccess) { result in
|
||||
switch result {
|
||||
case .success(_):
|
||||
continuation.resume()
|
||||
return
|
||||
case .failure(let failure):
|
||||
continuation.resume(throwing: failure)
|
||||
return
|
||||
}
|
||||
}
|
||||
case .failure(let error):
|
||||
continuation.resume(throwing: error)
|
||||
return
|
||||
}
|
||||
|
||||
self.oauth?.cancel()
|
||||
self.oauth = nil
|
||||
}
|
||||
continuation.resume()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private func enableOAuth2(_ provider: OAuth2SwiftProvider.Type) async throws {
|
||||
|
||||
callbackURL = provider.callbackURL
|
||||
|
||||
let oauth2 = provider.oauth2Swift
|
||||
self.oauth = oauth2
|
||||
oauth2.authorizeURLHandler = self
|
||||
|
||||
let oauth2Vars = provider.oauth2Vars
|
||||
|
||||
return try await withCheckedThrowingContinuation { continuation in
|
||||
oauth2.authorize(withCallbackURL: callbackURL!, scope: oauth2Vars.scope, state: oauth2Vars.state, parameters: oauth2Vars.params) { [weak self] result in
|
||||
guard let self = self, let extensionPointType = self.extensionPointType else { return }
|
||||
|
||||
switch result {
|
||||
case .success(let tokenSuccess):
|
||||
ExtensionPointManager.shared.activateExtensionPoint(extensionPointType, tokenSuccess: tokenSuccess) { [weak self] result in
|
||||
switch result {
|
||||
case .success(_):
|
||||
self?.logger.debug("Enabled extension successfully.")
|
||||
case .failure(let failure):
|
||||
continuation.resume(throwing: failure)
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
case .failure(let oauthSwiftError):
|
||||
continuation.resume(throwing: oauthSwiftError)
|
||||
return
|
||||
}
|
||||
|
||||
self.oauth?.cancel()
|
||||
self.oauth = nil
|
||||
}
|
||||
continuation.resume()
|
||||
}
|
||||
}
|
||||
|
||||
public func handle(_ url: URL) {
|
||||
let session = ASWebAuthenticationSession(url: url, callbackURLScheme: callbackURL!.scheme, completionHandler: { (url, error) in
|
||||
if let callbackedURL = url {
|
||||
OAuth1Swift.handle(url: callbackedURL)
|
||||
}
|
||||
|
||||
guard let error = error else { return }
|
||||
|
||||
self.oauth?.cancel()
|
||||
self.oauth = nil
|
||||
|
||||
DispatchQueue.main.async {
|
||||
//self.dismiss(animated: true, completion: nil)
|
||||
//self.delegate?.dismiss()
|
||||
}
|
||||
|
||||
if case ASWebAuthenticationSessionError.canceledLogin = error {
|
||||
print("Login cancelled.")
|
||||
} else {
|
||||
//self.presentError(error)
|
||||
}
|
||||
})
|
||||
|
||||
session.presentationContextProvider = self
|
||||
if !session.start() {
|
||||
print("Session failed to start!!!")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
|
||||
return rootViewController!.view.window!
|
||||
}
|
||||
|
||||
public var rootViewController: UIViewController? {
|
||||
var currentKeyWindow: UIWindow? {
|
||||
UIApplication.shared.connectedScenes
|
||||
.filter { $0.activationState == .foregroundActive }
|
||||
.map { $0 as? UIWindowScene }
|
||||
.compactMap { $0 }
|
||||
.first?.windows
|
||||
.filter { $0.isKeyWindow }
|
||||
.first
|
||||
}
|
||||
|
||||
var rootViewController: UIViewController? {
|
||||
currentKeyWindow?.rootViewController
|
||||
}
|
||||
|
||||
return rootViewController
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
//
|
||||
// ExtensionsManagementView.swift
|
||||
// NetNewsWire-iOS
|
||||
//
|
||||
// Created by Stuart Breckenridge on 30/11/2022.
|
||||
// Copyright © 2022 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Account
|
||||
|
||||
struct ExtensionsManagementView: View {
|
||||
|
||||
@State private var availableExtensionPointTypes = ExtensionPointManager.shared.availableExtensionPointTypes.sorted(by: { $0.title < $1.title })
|
||||
@State private var showAddExtensionView: Bool = false
|
||||
@State private var showDeactivateAlert: Bool = false
|
||||
@State private var extensionToDeactivate: Dictionary<ExtensionPointIdentifer, any ExtensionPoint>.Element? = nil
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
activeExtensionsSection
|
||||
}
|
||||
.navigationTitle(Text("MANAGE_EXTENSIONS", tableName: "Settings"))
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button {
|
||||
showAddExtensionView = true
|
||||
} label: {
|
||||
Image(systemName: "plus")
|
||||
}
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $showAddExtensionView) {
|
||||
AddExtensionListView()
|
||||
}
|
||||
.alert(Text("DEACTIVATE_EXTENSION_TITLE", tableName: "Settings"),
|
||||
isPresented: $showDeactivateAlert) {
|
||||
|
||||
Button(role: .destructive) {
|
||||
ExtensionPointManager.shared.deactivateExtensionPoint(extensionToDeactivate!.value.extensionPointID)
|
||||
} label: {
|
||||
Text("DEACTIVATE", tableName: "Settings")
|
||||
}
|
||||
|
||||
Button(role: .cancel) {
|
||||
extensionToDeactivate = nil
|
||||
} label: {
|
||||
Text("CANCEL_BUTTON_TITLE", tableName: "Buttons")
|
||||
}
|
||||
|
||||
} message: {
|
||||
Text("DEACTIVATE_EXTENSION \(extensionToDeactivate?.value.title ?? "")", tableName: "Settings")
|
||||
}
|
||||
.onReceive(NotificationCenter.default.publisher(for: .ActiveExtensionPointsDidChange)) { _ in
|
||||
availableExtensionPointTypes = ExtensionPointManager.shared.availableExtensionPointTypes.sorted(by: { $0.title < $1.title })
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private var activeExtensionsSection: some View {
|
||||
Section(header: Text("ACTIVE_EXTENSIONS", tableName: "Settings")) {
|
||||
ForEach(0..<ExtensionPointManager.shared.activeExtensionPoints.count, id: \.self) { i in
|
||||
let point = Array(ExtensionPointManager.shared.activeExtensionPoints)[i]
|
||||
NavigationLink {
|
||||
ExtensionInspectorView(extensionPoint: point.value)
|
||||
} label: {
|
||||
Image(uiImage: point.value.image)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 25, height: 25)
|
||||
Text(point.value.title)
|
||||
}.swipeActions(edge: .trailing, allowsFullSwipe: false) {
|
||||
Button(role: .destructive) {
|
||||
extensionToDeactivate = point
|
||||
showDeactivateAlert = true
|
||||
} label: {
|
||||
Text("DEACTIVATE", tableName: "Settings")
|
||||
Image(systemName: "minus.circle")
|
||||
}.tint(.red)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct ExtensionsManagementView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ExtensionsManagementView()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user