Enabling Extensions now possible in SwiftUI

This commit is contained in:
Stuart Breckenridge
2022-12-19 12:14:56 +08:00
parent dbef68c8f8
commit ef6b90d594
10 changed files with 299 additions and 268 deletions

View File

@@ -280,8 +280,6 @@
519B8D332143397200FA689C /* SharingServiceDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519B8D322143397200FA689C /* SharingServiceDelegate.swift */; };
519CA8E525841DB700EB079A /* CrashReporter in Frameworks */ = {isa = PBXBuildFile; productRef = 519CA8E425841DB700EB079A /* CrashReporter */; };
519E743D22C663F900A78E47 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519E743422C663F900A78E47 /* SceneDelegate.swift */; };
519ED456244828C3007F8E94 /* AddExtensionPointViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519ED455244828C3007F8E94 /* AddExtensionPointViewController.swift */; };
519ED47A24482AEB007F8E94 /* EnableExtensionPointViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519ED47924482AEB007F8E94 /* EnableExtensionPointViewController.swift */; };
51A052CE244FB9D7006C2024 /* AddFeedWIndowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A052CD244FB9D6006C2024 /* AddFeedWIndowController.swift */; };
51A052CF244FB9D7006C2024 /* AddFeedWIndowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A052CD244FB9D6006C2024 /* AddFeedWIndowController.swift */; };
51A1699A235E10D700EB091F /* Settings.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 51A16990235E10D600EB091F /* Settings.storyboard */; };
@@ -840,6 +838,9 @@
DF28B44D294ED52700C4D8CA /* View+DismissOnExternalContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF28B44C294ED52700C4D8CA /* View+DismissOnExternalContext.swift */; };
DF28B44F294ED92F00C4D8CA /* NewsBlurAddAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF28B44E294ED92F00C4D8CA /* NewsBlurAddAccountView.swift */; };
DF28B451294EFC6C00C4D8CA /* View+DismissOnAccountAdd.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF28B450294EFC6C00C4D8CA /* View+DismissOnAccountAdd.swift */; };
DF28B453294FE6C600C4D8CA /* EnableExtensionPointView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF28B452294FE6C600C4D8CA /* EnableExtensionPointView.swift */; };
DF28B455294FE74A00C4D8CA /* ExtensionSectionHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF28B454294FE74A00C4D8CA /* ExtensionSectionHeader.swift */; };
DF28B4572950163F00C4D8CA /* EnableExtensionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF28B4562950163F00C4D8CA /* EnableExtensionViewModel.swift */; };
DF32ABE829493193008E3A12 /* SettingsComboTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF32ABE629493192008E3A12 /* SettingsComboTableViewCell.swift */; };
DF32ABE929493193008E3A12 /* SettingsComboTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = DF32ABE729493193008E3A12 /* SettingsComboTableViewCell.xib */; };
DF32ABEB29494CF1008E3A12 /* Settings.strings in Resources */ = {isa = PBXBuildFile; fileRef = DF32ABEA29494CF0008E3A12 /* Settings.strings */; };
@@ -1305,8 +1306,6 @@
5195C1DB2720BD3000888867 /* MasterFeedRowIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterFeedRowIdentifier.swift; sourceTree = "<group>"; };
519B8D322143397200FA689C /* SharingServiceDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharingServiceDelegate.swift; sourceTree = "<group>"; };
519E743422C663F900A78E47 /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
519ED455244828C3007F8E94 /* AddExtensionPointViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddExtensionPointViewController.swift; sourceTree = "<group>"; };
519ED47924482AEB007F8E94 /* EnableExtensionPointViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnableExtensionPointViewController.swift; sourceTree = "<group>"; };
51A052CD244FB9D6006C2024 /* AddFeedWIndowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AddFeedWIndowController.swift; path = AddFeed/AddFeedWIndowController.swift; sourceTree = "<group>"; };
51A16990235E10D600EB091F /* Settings.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Settings.storyboard; sourceTree = "<group>"; };
51A66684238075AE00CB272D /* AddWebFeedDefaultContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddWebFeedDefaultContainer.swift; sourceTree = "<group>"; };
@@ -1607,6 +1606,9 @@
DF28B44C294ED52700C4D8CA /* View+DismissOnExternalContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+DismissOnExternalContext.swift"; sourceTree = "<group>"; };
DF28B44E294ED92F00C4D8CA /* NewsBlurAddAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewsBlurAddAccountView.swift; sourceTree = "<group>"; };
DF28B450294EFC6C00C4D8CA /* View+DismissOnAccountAdd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+DismissOnAccountAdd.swift"; sourceTree = "<group>"; };
DF28B452294FE6C600C4D8CA /* EnableExtensionPointView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnableExtensionPointView.swift; sourceTree = "<group>"; };
DF28B454294FE74A00C4D8CA /* ExtensionSectionHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionSectionHeader.swift; sourceTree = "<group>"; };
DF28B4562950163F00C4D8CA /* EnableExtensionViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnableExtensionViewModel.swift; sourceTree = "<group>"; };
DF32ABE629493192008E3A12 /* SettingsComboTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsComboTableViewCell.swift; sourceTree = "<group>"; };
DF32ABE729493193008E3A12 /* SettingsComboTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SettingsComboTableViewCell.xib; sourceTree = "<group>"; };
DF32ABEA29494CF0008E3A12 /* Settings.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = Settings.strings; sourceTree = "<group>"; };
@@ -2027,10 +2029,8 @@
children = (
DF32ABEA29494CF0008E3A12 /* Settings.strings */,
DFD406F8291FB5D500C02962 /* Views */,
519ED455244828C3007F8E94 /* AddExtensionPointViewController.swift */,
5137C2E926F63AE6009EFEDB /* ArticleThemeImporter.swift */,
510FFAB226EEA22C00F32265 /* ArticleThemesTableViewController.swift */,
519ED47924482AEB007F8E94 /* EnableExtensionPointViewController.swift */,
51A16990235E10D600EB091F /* Settings.storyboard */,
DF32ABE629493192008E3A12 /* SettingsComboTableViewCell.swift */,
DF32ABE729493193008E3A12 /* SettingsComboTableViewCell.xib */,
@@ -2958,6 +2958,8 @@
children = (
DF766FEC29377FD9006FBBE2 /* ExtensionsManagementView.swift */,
DF47CDB1294803AB00FCD57E /* AddExtensionListView.swift */,
DF28B452294FE6C600C4D8CA /* EnableExtensionPointView.swift */,
DF28B4562950163F00C4D8CA /* EnableExtensionViewModel.swift */,
);
path = Extensions;
sourceTree = "<group>";
@@ -2994,6 +2996,7 @@
children = (
DFB34987294B447F00BC81AD /* InjectedNavigationView.swift */,
DFB349A3294E914D00BC81AD /* AccountSectionHeader.swift */,
DF28B454294FE74A00C4D8CA /* ExtensionSectionHeader.swift */,
DF28B44C294ED52700C4D8CA /* View+DismissOnExternalContext.swift */,
DF28B450294EFC6C00C4D8CA /* View+DismissOnAccountAdd.swift */,
);
@@ -4200,6 +4203,7 @@
512E08E72268801200BDCFDD /* WebFeedTreeControllerDelegate.swift in Sources */,
51C452A422650A2D00C03939 /* ArticleUtilities.swift in Sources */,
51EF0F79227716380050506E /* ColorHash.swift in Sources */,
DF28B4572950163F00C4D8CA /* EnableExtensionViewModel.swift in Sources */,
DF59F072292085B800ACD33D /* ColorPaletteSelectorView.swift in Sources */,
51F9F3FB23DFB25700A314FD /* Animations.swift in Sources */,
DFB34988294B447F00BC81AD /* InjectedNavigationView.swift in Sources */,
@@ -4225,7 +4229,6 @@
51F85BFD2275DCA800C787DC /* SingleLineUILabelSizer.swift in Sources */,
517630232336657E00E15FFF /* WebViewProvider.swift in Sources */,
51E43962238037C400015C31 /* AddFeedFolderViewController.swift in Sources */,
519ED47A24482AEB007F8E94 /* EnableExtensionPointViewController.swift in Sources */,
51C4528F226509BD00C03939 /* UnreadFeed.swift in Sources */,
51FD413B2342BD0500880194 /* MasterTimelineUnreadCountView.swift in Sources */,
DFB3497A294A962D00BC81AD /* AddAccountListView.swift in Sources */,
@@ -4233,6 +4236,7 @@
51D87EE12311D34700E63F03 /* ActivityType.swift in Sources */,
51C452772265091600C03939 /* MultilineUILabelSizer.swift in Sources */,
51C452A522650A2D00C03939 /* SmallIconProvider.swift in Sources */,
DF28B453294FE6C600C4D8CA /* EnableExtensionPointView.swift in Sources */,
51AB8AB323B7F4C6008F147D /* WebViewController.swift in Sources */,
DFD406F7291FB1A600C02962 /* SafariView.swift in Sources */,
DF3630ED2936183D00326FB8 /* OPMLDocument.swift in Sources */,
@@ -4261,6 +4265,7 @@
51938DF3231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift in Sources */,
51C4525A226508D600C03939 /* UIStoryboard-Extensions.swift in Sources */,
517A745B2443665000B553B9 /* UIPageViewController-Extensions.swift in Sources */,
DF28B455294FE74A00C4D8CA /* ExtensionSectionHeader.swift in Sources */,
51BB7C272335A8E5008E8144 /* ArticleActivityItemSource.swift in Sources */,
51F85BF52273625800C787DC /* Bundle-Extensions.swift in Sources */,
DF790D6228E990A900455FC7 /* AboutData.swift in Sources */,
@@ -4366,7 +4371,6 @@
512E09012268907400BDCFDD /* MasterFeedTableViewSectionHeader.swift in Sources */,
512392BF24E33A3C00F11704 /* RedditSelectSortTableViewController.swift in Sources */,
516AE9E02372269A007DEEAA /* IconImage.swift in Sources */,
519ED456244828C3007F8E94 /* AddExtensionPointViewController.swift in Sources */,
51C45268226508F600C03939 /* MasterFeedUnreadCountView.swift in Sources */,
DFB349A4294E914D00BC81AD /* AccountSectionHeader.swift in Sources */,
D3A39865246505DF00F9A366 /* FindInArticleActivity.swift in Sources */,

View File

@@ -11,7 +11,9 @@
"DONE_BUTTON_TITLE" = "Done";
"REMOVE_ACCOUNT_BUTTON_TITLE" = "Remove Account";
"DEACTIVATE_BUTTON_TITLE" = "Deactivate";
"DEACTIVATE_EXTENSION_BUTTON_TITLE" = "Deactivate Extension";
"CREDENTIALS_BUTTON_TITLE" = "Credentials";
"ADD_ACCOUNT_BUTTON_TITLE" = "Add Account";
"ENABLE_EXTENSION_BUTTON_TITLE" = "Enable Extension";
"UPDATE_CREDENTIALS_BUTTON_TITLE" = "Update Credentials";
"USE_CLOUDKIT_BUTTON_TITLE" = "Use iCloud";

View File

@@ -10,6 +10,7 @@ import SwiftUI
struct ExtensionInspectorView: View {
@Environment(\.dismiss) var dismiss
@State private var showDeactivateConfirmation: Bool = false
var extensionPoint: ExtensionPoint?
@@ -25,14 +26,15 @@ struct ExtensionInspectorView: View {
Button(role: .destructive) {
showDeactivateConfirmation = true
} label: {
Text("DEACTIVATE_EXTENSION_TITLE", tableName: "Settings")
Text("DEACTIVATE_EXTENSION_BUTTON_TITLE", tableName: "Buttons")
}
.confirmationDialog(Text("DEACTIVATE_EXTENSION_TITLE", tableName: "Settings") , isPresented: $showDeactivateConfirmation, titleVisibility: .visible) {
Button(role: .destructive) {
ExtensionPointManager.shared.deactivateExtensionPoint(extensionPoint!.extensionPointID)
dismiss()
} label: {
Text("DEACTIVATE_BUTTON_TITLE", tableName: "Buttons")
Text("DEACTIVATE_EXTENSION_BUTTON_TITLE", tableName: "Buttons")
}
Button(role: .cancel) {

View File

@@ -1,63 +0,0 @@
//
// AddExtensionPointViewController.swift
// NetNewsWire-iOS
//
// Created by Maurice Parker on 4/16/20.
// Copyright © 2020 Ranchero Software. All rights reserved.
//
import UIKit
protocol AddExtensionPointDismissDelegate: UIViewController {
func dismiss()
}
class AddExtensionPointViewController: UITableViewController, AddExtensionPointDismissDelegate {
private var availableExtensionPointTypes = [ExtensionPoint.Type]()
override func viewDidLoad() {
super.viewDidLoad()
availableExtensionPointTypes = ExtensionPointManager.shared.availableExtensionPointTypes.sorted(by: { $0.title < $1.title })
}
override func numberOfSections(in tableView: UITableView) -> Int {
1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return availableExtensionPointTypes.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SettingsExtensionTableViewCell", for: indexPath) as! SettingsComboTableViewCell
let extensionPointType = availableExtensionPointTypes[indexPath.row]
cell.comboNameLabel?.text = extensionPointType.title
cell.comboImage?.image = extensionPointType.image
return cell
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return NSLocalizedString("Feed Provider", comment: "Feed Provider Header")
}
override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
return NSLocalizedString("Feed Providers allow you to subscribe to some pages as if they were RSS feeds.", comment: "Feed Provider Footer")
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let navController = UIStoryboard.settings.instantiateViewController(withIdentifier: "EnableExtensionPointNavigationViewController") as! UINavigationController
navController.modalPresentationStyle = .currentContext
let enableViewController = navController.topViewController as! EnableExtensionPointViewController
enableViewController.delegate = self
enableViewController.extensionPointType = availableExtensionPointTypes[indexPath.row]
present(navController, animated: true)
}
func dismiss() {
navigationController?.popViewController(animated: false)
}
}

View File

@@ -1,192 +0,0 @@
//
// EnableExtensionPointViewController.swift
// NetNewsWire-iOS
//
// Created by Maurice Parker on 4/16/20.
// Copyright © 2020 Ranchero Software. All rights reserved.
//
import UIKit
import AuthenticationServices
import Account
import OAuthSwift
import Secrets
import SwiftUI
struct EnableExtensionPointViewWrapper: UIViewControllerRepresentable {
var extensionPoint: ExtensionPoint.Type?
func makeUIViewController(context: Context) -> EnableExtensionPointViewController {
let controller = UIStoryboard.settings.instantiateViewController(withIdentifier: "EnableExtensionPointViewController") as! EnableExtensionPointViewController
controller.extensionPointType = extensionPoint
return controller
}
func updateUIViewController(_ uiViewController: EnableExtensionPointViewController, context: Context) {
}
typealias UIViewControllerType = EnableExtensionPointViewController
}
class EnableExtensionPointViewController: UITableViewController {
@IBOutlet weak var extensionDescription: UILabel!
private var callbackURL: URL? = nil
private var oauth: OAuthSwift?
weak var delegate: AddExtensionPointDismissDelegate?
var extensionPointType: ExtensionPoint.Type?
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = extensionPointType?.title
extensionDescription.attributedText = extensionPointType?.description
tableView.register(ImageHeaderView.self, forHeaderFooterViewReuseIdentifier: "SectionHeader")
}
@IBAction func cancel(_ sender: Any) {
dismiss(animated: true, completion: nil)
delegate?.dismiss()
}
@IBAction func enable(_ sender: Any) {
guard let extensionPointType = extensionPointType else { return }
if let oauth1 = extensionPointType as? OAuth1SwiftProvider.Type {
enableOauth1(oauth1)
} else if let oauth2 = extensionPointType as? OAuth2SwiftProvider.Type {
enableOauth2(oauth2)
} else {
ExtensionPointManager.shared.activateExtensionPoint(extensionPointType) { result in
if case .failure(let error) = result {
self.presentError(error)
}
self.dismiss(animated: true, completion: nil)
self.delegate?.dismiss()
}
}
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return section == 0 ? ImageHeaderView.rowHeight : super.tableView(tableView, heightForHeaderInSection: section)
}
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
if section == 0 {
let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "SectionHeader") as! ImageHeaderView
headerView.imageView.image = extensionPointType?.image
return headerView
} else {
return super.tableView(tableView, viewForHeaderInSection: section)
}
}
}
extension EnableExtensionPointViewController: OAuthSwiftURLHandlerType {
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!!!")
}
}
}
extension EnableExtensionPointViewController: ASWebAuthenticationPresentationContextProviding {
public func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
return view.window!
}
}
private extension EnableExtensionPointViewController {
func enableOauth1(_ provider: OAuth1SwiftProvider.Type) {
callbackURL = provider.callbackURL
let oauth1 = provider.oauth1Swift
self.oauth = oauth1
oauth1.authorizeURLHandler = self
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
if case .failure(let error) = result {
self.presentError(error)
}
self.dismiss(animated: true, completion: nil)
self.delegate?.dismiss()
}
case .failure(let oauthSwiftError):
self.presentError(oauthSwiftError)
}
self.oauth?.cancel()
self.oauth = nil
}
}
func enableOauth2(_ provider: OAuth2SwiftProvider.Type) {
callbackURL = provider.callbackURL
let oauth2 = provider.oauth2Swift
self.oauth = oauth2
oauth2.authorizeURLHandler = self
let oauth2Vars = provider.oauth2Vars
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) { result in
if case .failure(let error) = result {
self.presentError(error)
}
self.dismiss(animated: true, completion: nil)
self.delegate?.dismiss()
}
case .failure(let oauthSwiftError):
self.presentError(oauthSwiftError)
}
self.oauth?.cancel()
self.oauth = nil
}
}
}

View File

@@ -15,7 +15,8 @@ public final class AddAccountListViewModel: ObservableObject, OAuthAccountAuthor
@Published public var showAddAccountSheet: (Bool, accountType: AccountType) = (false, .onMyMac)
public var webAccountTypes: [AccountType] {
if AppDefaults.shared.isDeveloperBuild {
return [.bazQux, .feedbin, .feedly, .inoreader, .newsBlur, .theOldReader].filter({ $0.isDeveloperRestricted == false })
return [.bazQux, .feedbin, .feedly, .inoreader, .newsBlur, .theOldReader]
//.filter({ $0.isDeveloperRestricted == false })
} else {
return [.bazQux, .feedbin, .feedly, .inoreader, .newsBlur, .theOldReader]
}

View File

@@ -21,8 +21,7 @@ struct AddExtensionListView: View {
footer: Text("FEED_PROVIDER_FOOTER", tableName: "Settings")) {
ForEach(0..<availableExtensionPointTypes.count, id: \.self) { i in
NavigationLink {
EnableExtensionPointViewWrapper(extensionPoint: availableExtensionPointTypes[i])
.edgesIgnoringSafeArea(.all)
EnableExtensionPointView(extensionPoint: availableExtensionPointTypes[i])
} label: {
Image(uiImage: availableExtensionPointTypes[i].image)
.resizable()

View File

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

View File

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

View File

@@ -0,0 +1,30 @@
//
// ExtensionSectionHeader.swift
// NetNewsWire-iOS
//
// Created by Stuart Breckenridge on 19/12/2022.
// Copyright © 2022 Ranchero Software. All rights reserved.
//
import SwiftUI
import Account
struct ExtensionSectionHeader: View {
var extensionPoint: ExtensionPoint.Type
var body: some View {
Section(header: headerImage) {}
}
var headerImage: some View {
HStack {
Spacer()
Image(uiImage: extensionPoint.image)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 48, height: 48)
Spacer()
}
}
}