diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 289353912..1b91251a2 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -34,6 +34,8 @@ 5144EA52227B8E4500D19003 /* AccountsFeedbin.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5144EA50227B8E4500D19003 /* AccountsFeedbin.xib */; }; 514B7C8323205EFB00BAC947 /* RootSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514B7C8223205EFB00BAC947 /* RootSplitViewController.swift */; }; 514B7D1F23219F3C00BAC947 /* AddControllerType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514B7D1E23219F3C00BAC947 /* AddControllerType.swift */; }; + 5152E0F923248F6200E5C7AD /* SettingsLocalAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510D707D22B02A4B004E8F65 /* SettingsLocalAccountView.swift */; }; + 5152E1022324900D00E5C7AD /* SettingsAddAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510D707322B028E1004E8F65 /* SettingsAddAccountView.swift */; }; 51543685228F6753005E1CDF /* DetailAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51543684228F6753005E1CDF /* DetailAccountViewController.swift */; }; 515436882291D75D005E1CDF /* AddLocalAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515436872291D75D005E1CDF /* AddLocalAccountViewController.swift */; }; 5154368A2291FED9005E1CDF /* FeedbinAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515436892291FED9005E1CDF /* FeedbinAccountViewController.swift */; }; @@ -59,7 +61,15 @@ 51938DF2231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51938DF1231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift */; }; 51938DF3231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51938DF1231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift */; }; 519B8D332143397200FA689C /* SharingServiceDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519B8D322143397200FA689C /* SharingServiceDelegate.swift */; }; + 519D73FB2323FF35008BB345 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51F35D0822AFD4760003CE1B /* SettingsView.swift */; }; + 519D740623243CC0008BB345 /* RefreshInterval-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519D740523243CC0008BB345 /* RefreshInterval-Extensions.swift */; }; + 519D740723243FE7008BB345 /* SettingsSubscriptionsExportDocumentPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5194B5F122B69FCC00144881 /* SettingsSubscriptionsExportDocumentPickerView.swift */; }; + 519D740823243FEA008BB345 /* SettingsSubscriptionsImportDocumentPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5194B5ED22B6965300144881 /* SettingsSubscriptionsImportDocumentPickerView.swift */; }; 519E743D22C663F900A78E47 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519E743422C663F900A78E47 /* SceneDelegate.swift */; }; + 51AF45E123246731001742EF /* SettingsAccountLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510D708122B041CC004E8F65 /* SettingsAccountLabelView.swift */; }; + 51AF460323247321001742EF /* SettingsDetailAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51F772EC22B2789B0087D9D1 /* SettingsDetailAccountView.swift */; }; + 51AF460C23247F11001742EF /* SettingsFeedbinAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510D707F22B02A5F004E8F65 /* SettingsFeedbinAccountView.swift */; }; + 51AF460E232488C6001742EF /* Account-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51AF460D232488C6001742EF /* Account-Extensions.swift */; }; 51C451A9226377C200C03939 /* ArticlesDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8407167F2262A61100344432 /* ArticlesDatabase.framework */; }; 51C451AA226377C200C03939 /* ArticlesDatabase.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 8407167F2262A61100344432 /* ArticlesDatabase.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 51C451B9226377C900C03939 /* Articles.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 840716732262A60F00344432 /* Articles.framework */; }; @@ -726,7 +736,9 @@ 5194B5ED22B6965300144881 /* SettingsSubscriptionsImportDocumentPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSubscriptionsImportDocumentPickerView.swift; sourceTree = ""; }; 5194B5F122B69FCC00144881 /* SettingsSubscriptionsExportDocumentPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSubscriptionsExportDocumentPickerView.swift; sourceTree = ""; }; 519B8D322143397200FA689C /* SharingServiceDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharingServiceDelegate.swift; sourceTree = ""; }; + 519D740523243CC0008BB345 /* RefreshInterval-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RefreshInterval-Extensions.swift"; sourceTree = ""; }; 519E743422C663F900A78E47 /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 51AF460D232488C6001742EF /* Account-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Account-Extensions.swift"; sourceTree = ""; }; 51C4524E226506F400C03939 /* UIStoryboard-Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIStoryboard-Extensions.swift"; sourceTree = ""; }; 51C45250226506F400C03939 /* String-Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String-Extensions.swift"; sourceTree = ""; }; 51C45254226507D200C03939 /* AppAssets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppAssets.swift; sourceTree = ""; }; @@ -784,8 +796,8 @@ 55E15BC1229D65A900D6602A /* AccountsReaderAPI.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AccountsReaderAPI.xib; sourceTree = ""; }; 55E15BCA229D65A900D6602A /* AccountsReaderAPIWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountsReaderAPIWindowController.swift; sourceTree = ""; }; 5F323808231DF9F000706F6B /* NNWTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NNWTableViewCell.swift; sourceTree = ""; }; - 6581C73320CED60000F4AD34 /* Subscribe to Feed.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Subscribe to Feed.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; 6543108B2322D90900658221 /* common */ = {isa = PBXFileReference; lastKnownFileType = folder; path = common; sourceTree = ""; }; + 6581C73320CED60000F4AD34 /* Subscribe to Feed.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Subscribe to Feed.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; 6581C73420CED60100F4AD34 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 6581C73720CED60100F4AD34 /* SafariExtensionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariExtensionHandler.swift; sourceTree = ""; }; 6581C73920CED60100F4AD34 /* SafariExtensionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariExtensionViewController.swift; sourceTree = ""; }; @@ -1128,6 +1140,15 @@ path = Wrappers; sourceTree = ""; }; + 519D740423243C68008BB345 /* Model Extensions */ = { + isa = PBXGroup; + children = ( + 519D740523243CC0008BB345 /* RefreshInterval-Extensions.swift */, + 51AF460D232488C6001742EF /* Account-Extensions.swift */, + ); + path = "Model Extensions"; + sourceTree = ""; + }; 51C45245226506C800C03939 /* Extensions */ = { isa = PBXGroup; children = ( @@ -1787,6 +1808,7 @@ 5183CCEB227117C70010922C /* Settings */, 5183CCDB226F1EEB0010922C /* Progress */, 51C45245226506C800C03939 /* Extensions */, + 519D740423243C68008BB345 /* Model Extensions */, 5F3237FF231DF9D000706F6B /* Views */, 5194B5E222B693EC00144881 /* Wrappers */, 84C9FC9A2262A1A900D921D6 /* Resources */, @@ -2429,6 +2451,7 @@ 5183CCDA226E31A50010922C /* NonIntrinsicImageView.swift in Sources */, 51EAED96231363EF00A9EEE3 /* NonIntrinsicButton.swift in Sources */, 51C4527B2265091600C03939 /* MasterUnreadIndicatorView.swift in Sources */, + 5152E1022324900D00E5C7AD /* SettingsAddAccountView.swift in Sources */, 515ADE4022E11FAE006B2460 /* SystemMessageViewController.swift in Sources */, 51F85BF92274AA7B00C787DC /* UIBarButtonItem-Extensions.swift in Sources */, 51C45296226509D300C03939 /* OPMLExporter.swift in Sources */, @@ -2437,12 +2460,15 @@ 51C45269226508F600C03939 /* MasterFeedTableViewCell.swift in Sources */, 51F85BFD2275DCA800C787DC /* SingleLineUILabelSizer.swift in Sources */, 51C4528F226509BD00C03939 /* UnreadFeed.swift in Sources */, + 51AF460E232488C6001742EF /* Account-Extensions.swift in Sources */, 5183CCDD226F1F5C0010922C /* NavigationProgressView.swift in Sources */, + 51AF45E123246731001742EF /* SettingsAccountLabelView.swift in Sources */, 51D87EE12311D34700E63F03 /* ActivityType.swift in Sources */, 51C452772265091600C03939 /* MultilineUILabelSizer.swift in Sources */, 51C452A522650A2D00C03939 /* SmallIconProvider.swift in Sources */, 51D5948722668EFA00DFC836 /* MarkStatusCommand.swift in Sources */, 514B7C8323205EFB00BAC947 /* RootSplitViewController.swift in Sources */, + 5152E0F923248F6200E5C7AD /* SettingsLocalAccountView.swift in Sources */, 51C4525C226508DF00C03939 /* String-Extensions.swift in Sources */, 51C452792265091600C03939 /* MasterTimelineTableViewCell.swift in Sources */, 51C452852265093600C03939 /* AddFeedFolderPickerData.swift in Sources */, @@ -2452,9 +2478,12 @@ 51EF0F77227716200050506E /* FaviconGenerator.swift in Sources */, 51938DF3231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift in Sources */, 51C4525A226508D600C03939 /* UIStoryboard-Extensions.swift in Sources */, + 519D740823243FEA008BB345 /* SettingsSubscriptionsImportDocumentPickerView.swift in Sources */, 5183CCEF227125970010922C /* SettingsViewController.swift in Sources */, 51F85BE5227217D000C787DC /* RefreshIntervalViewController.swift in Sources */, + 51AF460C23247F11001742EF /* SettingsFeedbinAccountView.swift in Sources */, 51F85BF52273625800C787DC /* Bundle-Extensions.swift in Sources */, + 519D740723243FE7008BB345 /* SettingsSubscriptionsExportDocumentPickerView.swift in Sources */, 51C452A622650A3500C03939 /* Node-Extensions.swift in Sources */, 5183CCDF226F1FCC0010922C /* UINavigationController+Progress.swift in Sources */, 51C45294226509C800C03939 /* SearchFeedDelegate.swift in Sources */, @@ -2490,6 +2519,7 @@ 51E595A6228CC36500FCC42B /* ArticleStatusSyncTimer.swift in Sources */, 51C45290226509C100C03939 /* PseudoFeed.swift in Sources */, 51C452A922650DC600C03939 /* ArticleRenderer.swift in Sources */, + 51AF460323247321001742EF /* SettingsDetailAccountView.swift in Sources */, 5115CAF42266301400B21BCE /* AddContainerViewController.swift in Sources */, 51C45297226509E300C03939 /* DefaultFeedsImporter.swift in Sources */, 512E094D2268B8AB00BDCFDD /* DeleteCommand.swift in Sources */, @@ -2504,6 +2534,7 @@ 51C4529B22650A1000C03939 /* FaviconDownloader.swift in Sources */, 84DEE56622C32CA4005FC42C /* SmartFeedDelegate.swift in Sources */, 512E09012268907400BDCFDD /* MasterFeedTableViewSectionHeader.swift in Sources */, + 519D740623243CC0008BB345 /* RefreshInterval-Extensions.swift in Sources */, 51C45268226508F600C03939 /* MasterFeedUnreadCountView.swift in Sources */, 5183CCD0226E1E880010922C /* NonIntrinsicLabel.swift in Sources */, 51C4529F22650A1900C03939 /* AuthorAvatarDownloader.swift in Sources */, @@ -2515,6 +2546,7 @@ 51934CD023108953006127BE /* ActivityID.swift in Sources */, 51C452782265091600C03939 /* MasterTimelineCellData.swift in Sources */, 51C45259226508D300C03939 /* AppDefaults.swift in Sources */, + 519D73FB2323FF35008BB345 /* SettingsView.swift in Sources */, 511D4419231FC02D00FB1562 /* KeyboardManager.swift in Sources */, 51C45293226509C800C03939 /* StarredFeedDelegate.swift in Sources */, 51D6A5BC23199C85001C27D8 /* MasterTimelineDataSource.swift in Sources */, diff --git a/iOS/Model Extensions/Account-Extensions.swift b/iOS/Model Extensions/Account-Extensions.swift new file mode 100644 index 000000000..9574f3553 --- /dev/null +++ b/iOS/Model Extensions/Account-Extensions.swift @@ -0,0 +1,16 @@ +// +// Account-Extensions.swift +// NetNewsWire-iOS +// +// Created by Maurice Parker on 9/7/19. +// Copyright © 2019 Ranchero Software. All rights reserved. +// + +import Foundation +import Account + +extension Account: Identifiable { + public var id: String { + return accountID + } +} diff --git a/iOS/Model Extensions/RefreshInterval-Extensions.swift b/iOS/Model Extensions/RefreshInterval-Extensions.swift new file mode 100644 index 000000000..5906d10a2 --- /dev/null +++ b/iOS/Model Extensions/RefreshInterval-Extensions.swift @@ -0,0 +1,15 @@ +// +// RefreshInterval-Extensions.swift +// NetNewsWire-iOS +// +// Created by Maurice Parker on 9/7/19. +// Copyright © 2019 Ranchero Software. All rights reserved. +// + +import Foundation + +extension RefreshInterval: Identifiable { + var id: Int { + return rawValue + } +} diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index cc0cc47f8..2953669ac 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -6,7 +6,8 @@ // Copyright © 2019 Ranchero Software. All rights reserved. // -import Foundation +import UIKit +import SwiftUI import Account import Articles import RSCore @@ -852,14 +853,15 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { } func showSettings() { - let settingsNavViewController = UIStoryboard.settings.instantiateInitialViewController() as! UINavigationController - settingsNavViewController.modalPresentationStyle = .formSheet - let settingsViewController = settingsNavViewController.topViewController as! SettingsViewController - settingsViewController.presentingParentController = rootSplitViewController - rootSplitViewController.present(settingsNavViewController, animated: true) +// let settingsNavViewController = UIStoryboard.settings.instantiateInitialViewController() as! UINavigationController +// settingsNavViewController.modalPresentationStyle = .formSheet +// let settingsViewController = settingsNavViewController.topViewController as! SettingsViewController +// settingsViewController.presentingParentController = rootSplitViewController +// rootSplitViewController.present(settingsNavViewController, animated: true) - // let settings = UIHostingController(rootView: SettingsView(viewModel: SettingsView.ViewModel())) - // self.present(settings, animated: true) + rootSplitViewController.present(style: .formSheet) { + SettingsView(viewModel: SettingsView.ViewModel()) + } } func showAdd(_ type: AddControllerType) { diff --git a/iOS/Settings/SettingsAccountLabelView.swift b/iOS/Settings/SettingsAccountLabelView.swift index 8c7f6d41f..0d5ead2e3 100644 --- a/iOS/Settings/SettingsAccountLabelView.swift +++ b/iOS/Settings/SettingsAccountLabelView.swift @@ -14,19 +14,13 @@ struct SettingsAccountLabelView : View { var body: some View { HStack { - Spacer() - HStack { - Image(accountImage) - .resizable() - .aspectRatio(1, contentMode: .fit) - .frame(height: 32) - Text(verbatim: accountLabel).font(.title) - - } - .layoutPriority(1) - Spacer() + Image(accountImage) + .resizable() + .aspectRatio(1, contentMode: .fit) + .frame(height: 32) + Text(verbatim: accountLabel).font(.title) } - .foregroundColor(.primary) + .foregroundColor(.primary).padding(4.0) } } diff --git a/iOS/Settings/SettingsAddAccountView.swift b/iOS/Settings/SettingsAddAccountView.swift index 22fd2ca7c..10f1b7108 100644 --- a/iOS/Settings/SettingsAddAccountView.swift +++ b/iOS/Settings/SettingsAddAccountView.swift @@ -10,17 +10,38 @@ import SwiftUI import Account struct SettingsAddAccountView : View { + + @State private var isAddPresented = false + @State private var selectedAccountType: AccountType = nil + var body: some View { Form { - PresentationButton(destination: SettingsLocalAccountView(name: "")) { + + Button(action: { + self.selectedAccountType = AccountType.onMyMac + self.isAddPresented.toggle() + }) { SettingsAccountLabelView(accountImage: "accountLocal", accountLabel: Account.defaultLocalAccountName) } - .padding(4) - PresentationButton(destination: SettingsFeedbinAccountView(viewModel: SettingsFeedbinAccountView.ViewModel())) { + + Button(action: { + self.selectedAccountType = AccountType.feedbin + self.isAddPresented.toggle() + }) { SettingsAccountLabelView(accountImage: "accountFeedbin", accountLabel: "Feedbin") } - .padding(4) + + } + .sheet(isPresented: $isAddPresented) { + if self.selectedAccountType == .onMyMac { + SettingsLocalAccountView(name: "") + } + if self.selectedAccountType == .feedbin { + SettingsFeedbinAccountView(viewModel: SettingsFeedbinAccountView.ViewModel()) + } + } + .navigationBarTitle(Text("Add Account"), displayMode: .inline) } } diff --git a/iOS/Settings/SettingsDetailAccountView.swift b/iOS/Settings/SettingsDetailAccountView.swift index 8bc4d5edf..b97177f35 100644 --- a/iOS/Settings/SettingsDetailAccountView.swift +++ b/iOS/Settings/SettingsDetailAccountView.swift @@ -12,17 +12,15 @@ import Account import RSWeb struct SettingsDetailAccountView : View { - @ObjectBinding var viewModel: ViewModel - @State private var verifyDelete = false - @State private var showFeedbinCredentials = false - + @ObservedObject var viewModel: ViewModel + @State private var isFeedbinCredentialsPresented = false + @State private var isDeleteAlertPresented = false + var body: some View { Form { Section { HStack { - Text("Name") - Divider() - TextField($viewModel.name, placeholder: Text("(Optional)")) + TextField("Name", text: $viewModel.name) } Toggle(isOn: $viewModel.isActive) { Text("Active") @@ -33,33 +31,33 @@ struct SettingsDetailAccountView : View { HStack { Spacer() Button(action: { - self.showFeedbinCredentials = true + self.isFeedbinCredentialsPresented.toggle() }) { Text("Credentials") } - .presentation(showFeedbinCredentials ? feedbinCredentialsModal : nil) - .onDisappear() { self.showFeedbinCredentials = false } Spacer() } } + .sheet(isPresented: $isFeedbinCredentialsPresented) { + self.settingsFeedbinAccountView + } } if viewModel.isDeletable { Section { HStack { Spacer() Button(action: { - self.verifyDelete = true + self.isDeleteAlertPresented.toggle() }) { - Text("Delete Account") - .foregroundColor(.red) - } - .presentation($verifyDelete) { - Alert(title: Text("Are you sure you want to delete \"\(viewModel.nameForDisplay)\"?"), - primaryButton: Alert.Button.default(Text("Delete"), onTrigger: { self.viewModel.delete() }), - secondaryButton: Alert.Button.cancel()) + Text("Delete Account").foregroundColor(.red) } Spacer() } + .alert(isPresented: $isDeleteAlertPresented) { + Alert(title: Text("Are you sure you want to delete \"\(viewModel.nameForDisplay)\"?"), + primaryButton: Alert.Button.default(Text("Delete"), action: { self.viewModel.delete() }), + secondaryButton: Alert.Button.cancel()) + } } } } @@ -67,13 +65,15 @@ struct SettingsDetailAccountView : View { } - var feedbinCredentialsModal: Modal { + var settingsFeedbinAccountView: SettingsFeedbinAccountView { let feedbinViewModel = SettingsFeedbinAccountView.ViewModel(account: viewModel.account) - return Modal(SettingsFeedbinAccountView(viewModel: feedbinViewModel)) + return SettingsFeedbinAccountView(viewModel: feedbinViewModel) } - class ViewModel: BindableObject { - let didChange = PassthroughSubject() + class ViewModel: ObservableObject { + + let objectWillChange = ObservableObjectPublisher() + let account: Account init(_ account: Account) { @@ -89,8 +89,8 @@ struct SettingsDetailAccountView : View { account.name ?? "" } set { + objectWillChange.send() account.name = newValue.isEmpty ? nil : newValue - didChange.send(self) } } @@ -99,8 +99,8 @@ struct SettingsDetailAccountView : View { account.isActive } set { + objectWillChange.send() account.isActive = newValue - didChange.send(self) } } @@ -114,7 +114,7 @@ struct SettingsDetailAccountView : View { func delete() { AccountManager.shared.deleteAccount(account) - ActivityManager.shared.cleanUp(account) +// ActivityManager.shared.cleanUp(account) } } } diff --git a/iOS/Settings/SettingsFeedbinAccountView.swift b/iOS/Settings/SettingsFeedbinAccountView.swift index 62e88069a..00ef485b7 100644 --- a/iOS/Settings/SettingsFeedbinAccountView.swift +++ b/iOS/Settings/SettingsFeedbinAccountView.swift @@ -12,10 +12,10 @@ import Account import RSWeb struct SettingsFeedbinAccountView : View { - @Environment(\.isPresented) private var isPresented - @ObjectBinding var viewModel: ViewModel + @Environment(\.presentationMode) var presentation + @ObservedObject var viewModel: ViewModel @State var busy: Bool = false - @State var error: Text = Text("") + @State var error: String = "" var body: some View { NavigationView { @@ -23,22 +23,13 @@ struct SettingsFeedbinAccountView : View { Section(header: SettingsAccountLabelView(accountImage: "accountFeedbin", accountLabel: "Feedbin").padding() ) { - HStack { - Text("Email:") - Divider() - TextField($viewModel.email) - .textContentType(.username) - } - HStack { - Text("Password:") - Divider() - SecureField($viewModel.password) - } + TextField("Email", text: $viewModel.email).textContentType(.emailAddress) + SecureField("Password", text: $viewModel.password) } Section(footer: HStack { Spacer() - error.color(.red) + Text(verbatim: error).foregroundColor(.red) Spacer() } ) { @@ -67,7 +58,7 @@ struct SettingsFeedbinAccountView : View { private func addAccount() { busy = true - error = Text("") + error = "" let emailAddress = viewModel.email.trimmingCharacters(in: .whitespaces) let credentials = Credentials.basic(username: emailAddress, password: viewModel.password) @@ -104,15 +95,15 @@ struct SettingsFeedbinAccountView : View { self.dismiss() } catch { - self.error = Text("Keychain error while storing credentials.") + self.error = "Keychain error while storing credentials." } } else { - self.error = Text("Invalid email/password combination.") + self.error = "Invalid email/password combination." } case .failure: - self.error = Text("Network error. Try again later.") + self.error = "Network error. Try again later." } } @@ -120,11 +111,12 @@ struct SettingsFeedbinAccountView : View { } private func dismiss() { - isPresented?.value = false + presentation.wrappedValue.dismiss() } - class ViewModel: BindableObject { - let didChange = PassthroughSubject() + class ViewModel: ObservableObject { + + let objectWillChange = ObservableObjectPublisher() var account: Account? = nil init() { @@ -139,13 +131,14 @@ struct SettingsFeedbinAccountView : View { } var email: String = "" { - didSet { - didChange.send(self) + willSet { + objectWillChange.send() } } + var password: String = "" { - didSet { - didChange.send(self) + willSet { + objectWillChange.send() } } diff --git a/iOS/Settings/SettingsLocalAccountView.swift b/iOS/Settings/SettingsLocalAccountView.swift index 0c2b68569..c54e65cf8 100644 --- a/iOS/Settings/SettingsLocalAccountView.swift +++ b/iOS/Settings/SettingsLocalAccountView.swift @@ -10,7 +10,7 @@ import SwiftUI import Account struct SettingsLocalAccountView : View { - @Environment(\.isPresented) private var isPresented + @Environment(\.presentationMode) var presentation @State var name: String var body: some View { @@ -20,9 +20,7 @@ struct SettingsLocalAccountView : View { SettingsAccountLabelView(accountImage: "accountLocal", accountLabel: Account.defaultLocalAccountName).padding() ) { HStack { - Text("Name") - Divider() - TextField($name, placeholder: Text("(Optional)")) + TextField("Name", text: $name) } } Section { @@ -47,7 +45,7 @@ struct SettingsLocalAccountView : View { } private func dismiss() { - isPresented?.value = false + presentation.wrappedValue.dismiss() } } diff --git a/iOS/Settings/SettingsView.swift b/iOS/Settings/SettingsView.swift index bf7eacc1a..c6b927476 100644 --- a/iOS/Settings/SettingsView.swift +++ b/iOS/Settings/SettingsView.swift @@ -10,88 +10,136 @@ import SwiftUI import Combine import Account + struct SettingsView : View { - @ObjectBinding var viewModel: ViewModel - @State var subscriptionsImportAccounts: ActionSheet? = nil - @State var subscriptionsImportDocumentPicker: Modal? = nil - @State var subscriptionsExportAccounts: ActionSheet? = nil - @State var subscriptionsExportDocumentPicker: Modal? = nil + + @ObservedObject var viewModel: ViewModel + + @Environment(\.viewController) private var viewController: UIViewController? + + @State private var isWebsitePresented: Bool = false + @State private var website: String? = nil + + @State private var isOPMLImportPresented: Bool = false + @State private var isOPMLImportDocPickerPresented: Bool = false + @State private var isOPMLExportPresented: Bool = false + @State private var isOPMLExportDocPickerPresented: Bool = false + @State private var opmlAccount: Account? = nil var body: some View { NavigationView { Form { - - Section(header: Text("ACCOUNTS")) { - ForEach(viewModel.accounts.identified(by: \.self)) { account in - NavigationButton(destination: SettingsDetailAccountView(viewModel: SettingsDetailAccountView.ViewModel(account)), isDetail: false) { - Text(verbatim: account.nameForDisplay) - } - } - NavigationButton(destination: SettingsAddAccountView(), isDetail: false) { - Text("Add Account") - } - } - - Section(header: Text("TIMELINE")) { - Toggle(isOn: $viewModel.sortOldestToNewest) { - Text("Sort Oldest to Newest") - } - Stepper(value: $viewModel.timelineNumberOfLines, in: 2...6) { - Text("Number of Text Lines: \(viewModel.timelineNumberOfLines)") - } - } - - Section(header: Text("DATABASE")) { - Picker(selection: $viewModel.refreshInterval, label: Text("Refresh Interval")) { - ForEach(RefreshInterval.allCases.identified(by: \.self)) { interval in - Text(interval.description()).tag(interval) - } - } - Button(action: { - self.subscriptionsImportAccounts = self.createSubscriptionsImportAccounts - }) { - Text("Import Subscriptions...") - } - .presentation(subscriptionsImportAccounts) - .presentation(subscriptionsImportDocumentPicker) - Button(action: { - self.subscriptionsExportAccounts = self.createSubscriptionsExportAccounts - }) { - Text("Export Subscriptions...") - } - .presentation(subscriptionsExportAccounts) - .presentation(subscriptionsExportDocumentPicker) - } - .foregroundColor(.primary) - - Section(header: Text("ABOUT"), footer: buildFooter) { - Text("About NetNewsWire") - PresentationButton(destination: SafariView(url: URL(string: "https://ranchero.com/netnewswire/")!)) { - Text("Website") - } - PresentationButton(destination: SafariView(url: URL(string: "https://github.com/brentsimmons/NetNewsWire")!)) { - Text("Github Repository") - } - PresentationButton(destination: SafariView(url: URL(string: "https://github.com/brentsimmons/NetNewsWire/issues")!)) { - Text("Bug Tracker") - } - PresentationButton(destination: SafariView(url: URL(string: "https://github.com/brentsimmons/NetNewsWire/tree/master/Technotes")!)) { - Text("Technotes") - } - PresentationButton(destination: SafariView(url: URL(string: "https://github.com/brentsimmons/NetNewsWire/blob/master/Technotes/HowToSupportNetNewsWire.markdown")!)) { - Text("How to Support NetNewsWire") - } - Text("Add NetNewsWire News Feed") - } - .foregroundColor(.primary) - + buildAccountsSection() + buildTimelineSection() + buildDatabaseSection() + buildAboutSection() } .navigationBarTitle(Text("Settings"), displayMode: .inline) - + .navigationBarItems(leading: Button(action: { self.viewController?.dismiss(animated: true) }) { Text("Done") } ) } } - var createSubscriptionsImportAccounts: ActionSheet { + func buildAccountsSection() -> some View { + Section(header: Text("ACCOUNTS")) { + ForEach(viewModel.accounts) { account in + NavigationLink(destination: SettingsDetailAccountView(viewModel: SettingsDetailAccountView.ViewModel(account))) { + Text(verbatim: account.nameForDisplay) + } + } + NavigationLink(destination: SettingsAddAccountView()) { + Text("Add Account") + } + } + } + + func buildTimelineSection() -> some View { + Section(header: Text("TIMELINE")) { + Toggle(isOn: $viewModel.sortOldestToNewest) { + Text("Sort Oldest to Newest") + } + Stepper(value: $viewModel.timelineNumberOfLines, in: 2...6) { + Text("Number of Text Lines: \(viewModel.timelineNumberOfLines)") + } + } + } + + func buildDatabaseSection() -> some View { + Section(header: Text("DATABASE")) { + Picker(selection: $viewModel.refreshInterval, label: Text("Refresh Interval")) { + ForEach(RefreshInterval.allCases) { interval in + Text(interval.description()).tag(interval) + } + } + + VStack { + Button("Import Subscriptions...") { + self.isOPMLImportPresented = true + } + }.actionSheet(isPresented: $isOPMLImportPresented) { + buildSubscriptionsImportAccounts() + }.sheet(isPresented: $isOPMLImportDocPickerPresented) { + SettingsSubscriptionsImportDocumentPickerView(account: self.opmlAccount!) + }.foregroundColor(.primary) + + VStack { + Button("Export Subscriptions...") { + self.isOPMLExportPresented = true + } + }.actionSheet(isPresented: $isOPMLExportPresented) { + buildSubscriptionsExportAccounts() + }.sheet(isPresented: $isOPMLExportDocPickerPresented) { + SettingsSubscriptionsExportDocumentPickerView(account: self.opmlAccount!) + }.foregroundColor(.primary) + } + } + + func buildAboutSection() -> some View { + Section(header: Text("ABOUT"), footer: buildFooter()) { + Text("About NetNewsWire") + + Button(action: { + self.isWebsitePresented.toggle() + self.website = "https://ranchero.com/netnewswire/" + }) { + Text("Website") + }.foregroundColor(.primary) + + Button(action: { + self.isWebsitePresented.toggle() + self.website = "https://github.com/brentsimmons/NetNewsWire" + }) { + Text("Github Repository") + }.foregroundColor(.primary) + + Button(action: { + self.isWebsitePresented.toggle() + self.website = "https://github.com/brentsimmons/NetNewsWire/issues" + }) { + Text("Bug Tracker") + }.foregroundColor(.primary) + + Button(action: { + self.isWebsitePresented.toggle() + self.website = "https://github.com/brentsimmons/NetNewsWire/tree/master/Technotes" + }) { + Text("Technotes") + }.foregroundColor(.primary) + + Button(action: { + self.isWebsitePresented.toggle() + self.website = "https://github.com/brentsimmons/NetNewsWire/blob/master/Technotes/HowToSupportNetNewsWire.markdown" + }) { + Text("How To Support NetNewsWire") + }.foregroundColor(.primary) + + Text("Add NetNewsWire News Feed") + + }.sheet(isPresented: $isWebsitePresented) { + SafariView(url: URL(string: self.website!)!) + } + } + + func buildSubscriptionsImportAccounts() -> ActionSheet { var buttons = [ActionSheet.Button]() for account in viewModel.activeAccounts { @@ -100,33 +148,33 @@ struct SettingsView : View { } let button = ActionSheet.Button.default(Text(verbatim: account.nameForDisplay)) { - self.subscriptionsImportAccounts = nil - self.subscriptionsImportDocumentPicker = Modal(SettingsSubscriptionsImportDocumentPickerView(account: account)) + self.opmlAccount = account + self.isOPMLImportDocPickerPresented = true } buttons.append(button) } - buttons.append(.cancel { self.subscriptionsImportAccounts = nil }) + buttons.append(.cancel()) return ActionSheet(title: Text("Import Subscriptions..."), message: Text("Select the account to import your OPML file into."), buttons: buttons) } - var createSubscriptionsExportAccounts: ActionSheet { + func buildSubscriptionsExportAccounts() -> ActionSheet { var buttons = [ActionSheet.Button]() for account in viewModel.accounts { let button = ActionSheet.Button.default(Text(verbatim: account.nameForDisplay)) { - self.subscriptionsExportAccounts = nil - self.subscriptionsExportDocumentPicker = Modal(SettingsSubscriptionsExportDocumentPickerView(account: account)) + self.opmlAccount = account + self.isOPMLExportDocPickerPresented = true } buttons.append(button) } - buttons.append(.cancel { self.subscriptionsExportAccounts = nil }) + buttons.append(.cancel()) return ActionSheet(title: Text("Export Subscriptions..."), message: Text("Select the account to export out of."), buttons: buttons) } - var buildFooter: some View { + func buildFooter() -> some View { return Text(verbatim: "\(Bundle.main.appName) v \(Bundle.main.versionNumber) (Build \(Bundle.main.buildNumber))") .font(.footnote) .foregroundColor(.secondary) @@ -134,9 +182,9 @@ struct SettingsView : View { // MARK: ViewModel - class ViewModel: BindableObject { + class ViewModel: ObservableObject { - let didChange = PassthroughSubject() + let objectWillChange = ObservableObjectPublisher() init() { NotificationCenter.default.addObserver(self, selector: #selector(accountsDidChange(_:)), name: .AccountsDidChange, object: nil) @@ -164,12 +212,12 @@ struct SettingsView : View { return AppDefaults.timelineSortDirection == .orderedDescending } set { + objectWillChange.send() if newValue == true { AppDefaults.timelineSortDirection = .orderedDescending } else { AppDefaults.timelineSortDirection = .orderedAscending } - didChange.send(self) } } @@ -178,8 +226,8 @@ struct SettingsView : View { return AppDefaults.timelineNumberOfLines } set { + objectWillChange.send() AppDefaults.timelineNumberOfLines = newValue - didChange.send(self) } } @@ -188,17 +236,17 @@ struct SettingsView : View { return AppDefaults.refreshInterval } set { + objectWillChange.send() AppDefaults.refreshInterval = newValue - didChange.send(self) } } @objc func accountsDidChange(_ notification: Notification) { - didChange.send(self) + objectWillChange.send() } @objc func displayNameDidChange(_ notification: Notification) { - didChange.send(self) + objectWillChange.send() } }