Change settings from using SwiftUI to using UIKit

This commit is contained in:
Maurice Parker 2019-10-21 11:51:33 -05:00
parent 94f31b18bc
commit effec24674
26 changed files with 1916 additions and 1198 deletions

View File

@ -9,7 +9,6 @@
/* Begin PBXBuildFile section */
49F40DF82335B71000552BF4 /* newsfoot.js in Resources */ = {isa = PBXBuildFile; fileRef = 49F40DEF2335B71000552BF4 /* newsfoot.js */; };
49F40DF92335B71000552BF4 /* newsfoot.js in Resources */ = {isa = PBXBuildFile; fileRef = 49F40DEF2335B71000552BF4 /* newsfoot.js */; };
510BD15D232D765D002692E4 /* SettingsReaderAPIAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 557EE1A522B6F4E1004206FA /* SettingsReaderAPIAccountView.swift */; };
51102165233A7D6C0007A5F7 /* ArticleExtractorButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51102164233A7D6C0007A5F7 /* ArticleExtractorButton.swift */; };
51126DA4225FDE2F00722696 /* RSImage-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51126DA3225FDE2F00722696 /* RSImage-Extensions.swift */; };
5115CAF42266301400B21BCE /* AddContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51121B5A22661FEF00BC0EC1 /* AddContainerViewController.swift */; };
@ -43,15 +42,11 @@
513146C5235A8FDB00387FDC /* SyncDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51554C01228B6EB50055115A /* SyncDatabase.framework */; };
51314704235C41FC00387FDC /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 51314707235C41FC00387FDC /* Intents.intentdefinition */; };
51314705235C41FC00387FDC /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 51314707235C41FC00387FDC /* Intents.intentdefinition */; };
51314716235C862200387FDC /* SettingsSubscriptionsImportAccountPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51314715235C862200387FDC /* SettingsSubscriptionsImportAccountPickerView.swift */; };
51314718235C89ED00387FDC /* SettingsSubscriptionsExportAccountPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51314717235C89ED00387FDC /* SettingsSubscriptionsExportAccountPickerView.swift */; };
51322855232EED360033D4ED /* VibrantSelectAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51322854232EED360033D4ED /* VibrantSelectAction.swift */; };
51322859232FDDB80033D4ED /* VibrantButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51322858232FDDB80033D4ED /* VibrantButtonStyle.swift */; };
5132285B232FF2C40033D4ED /* SettingsRefreshSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5132285A232FF2C40033D4ED /* SettingsRefreshSelectionView.swift */; };
513228FB233037630033D4ED /* Reachability.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513228F2233037620033D4ED /* Reachability.swift */; };
513228FC233037630033D4ED /* Reachability.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513228F2233037620033D4ED /* Reachability.swift */; };
513229312330523F0033D4ED /* AttributedStringView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513229302330523F0033D4ED /* AttributedStringView.swift */; };
5132293B23305D4C0033D4ED /* SettingsAboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5132293A23305D4C0033D4ED /* SettingsAboutView.swift */; };
513C5CE9232571C2003D4054 /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513C5CE8232571C2003D4054 /* ShareViewController.swift */; };
513C5CEC232571C2003D4054 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 513C5CEA232571C2003D4054 /* MainInterface.storyboard */; };
513C5CF0232571C2003D4054 /* NetNewsWire iOS Share Extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 513C5CE6232571C2003D4054 /* NetNewsWire iOS Share Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
@ -81,8 +76,6 @@
5148F4552336DB7000F8CD8B /* MasterTimelineTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5148F4542336DB7000F8CD8B /* MasterTimelineTitleView.swift */; };
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 */; };
5154368B229404D1005E1CDF /* FaviconGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F76227716200050506E /* FaviconGenerator.swift */; };
51554C24228B71910055115A /* SyncDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51554C01228B6EB50055115A /* SyncDatabase.framework */; };
51554C25228B71910055115A /* SyncDatabase.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 51554C01228B6EB50055115A /* SyncDatabase.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
@ -112,14 +105,18 @@
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 */; };
51A16997235E10D700EB091F /* RefreshIntervalViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A1698D235E10D600EB091F /* RefreshIntervalViewController.swift */; };
51A16998235E10D700EB091F /* SettingsTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 51A1698E235E10D600EB091F /* SettingsTableViewCell.xib */; };
51A16999235E10D700EB091F /* AddLocalAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A1698F235E10D600EB091F /* AddLocalAccountViewController.swift */; };
51A1699A235E10D700EB091F /* Settings.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 51A16990235E10D600EB091F /* Settings.storyboard */; };
51A1699B235E10D700EB091F /* DetailAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A16991235E10D600EB091F /* DetailAccountViewController.swift */; };
51A1699C235E10D700EB091F /* AddAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A16992235E10D600EB091F /* AddAccountViewController.swift */; };
51A1699D235E10D700EB091F /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A16993235E10D600EB091F /* SettingsViewController.swift */; };
51A1699E235E10D700EB091F /* TimelineNumberOfLinesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A16994235E10D600EB091F /* TimelineNumberOfLinesViewController.swift */; };
51A1699F235E10D700EB091F /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A16995235E10D600EB091F /* AboutViewController.swift */; };
51A169A0235E10D700EB091F /* FeedbinAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A16996235E10D700EB091F /* FeedbinAccountViewController.swift */; };
51AF460E232488C6001742EF /* Account-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51AF460D232488C6001742EF /* Account-Extensions.swift */; };
51B62E68233186730085F949 /* MasterTimelineAvatarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B62E67233186730085F949 /* MasterTimelineAvatarView.swift */; };
51BB7C272335A8E5008E8144 /* ArticleActivityItemSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BB7C262335A8E5008E8144 /* ArticleActivityItemSource.swift */; };
@ -730,10 +727,6 @@
/* Begin PBXFileReference section */
49F40DEF2335B71000552BF4 /* newsfoot.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = newsfoot.js; sourceTree = "<group>"; };
510D707322B028E1004E8F65 /* SettingsAddAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAddAccountView.swift; sourceTree = "<group>"; };
510D707D22B02A4B004E8F65 /* SettingsLocalAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsLocalAccountView.swift; sourceTree = "<group>"; };
510D707F22B02A5F004E8F65 /* SettingsFeedbinAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsFeedbinAccountView.swift; sourceTree = "<group>"; };
510D708122B041CC004E8F65 /* SettingsAccountLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAccountLabelView.swift; sourceTree = "<group>"; };
51102164233A7D6C0007A5F7 /* ArticleExtractorButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleExtractorButton.swift; sourceTree = "<group>"; };
51121AA12265430A00BC0EC1 /* NetNewsWire_iOSapp_target.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = NetNewsWire_iOSapp_target.xcconfig; sourceTree = "<group>"; };
51121B5A22661FEF00BC0EC1 /* AddContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddContainerViewController.swift; sourceTree = "<group>"; };
@ -754,14 +747,10 @@
513146B1235A81A400387FDC /* AddFeedIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddFeedIntentHandler.swift; sourceTree = "<group>"; };
51314706235C41FC00387FDC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; name = Base; path = Base.lproj/Intents.intentdefinition; sourceTree = "<group>"; };
51314714235C420900387FDC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Intents.strings; sourceTree = "<group>"; };
51314715235C862200387FDC /* SettingsSubscriptionsImportAccountPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSubscriptionsImportAccountPickerView.swift; sourceTree = "<group>"; };
51314717235C89ED00387FDC /* SettingsSubscriptionsExportAccountPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSubscriptionsExportAccountPickerView.swift; sourceTree = "<group>"; };
51322854232EED360033D4ED /* VibrantSelectAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VibrantSelectAction.swift; sourceTree = "<group>"; };
51322858232FDDB80033D4ED /* VibrantButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VibrantButtonStyle.swift; sourceTree = "<group>"; };
5132285A232FF2C40033D4ED /* SettingsRefreshSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsRefreshSelectionView.swift; sourceTree = "<group>"; };
513228F2233037620033D4ED /* Reachability.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Reachability.swift; sourceTree = "<group>"; };
513229302330523F0033D4ED /* AttributedStringView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributedStringView.swift; sourceTree = "<group>"; };
5132293A23305D4C0033D4ED /* SettingsAboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAboutView.swift; sourceTree = "<group>"; };
513C5CE6232571C2003D4054 /* NetNewsWire iOS Share Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "NetNewsWire iOS Share Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
513C5CE8232571C2003D4054 /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = "<group>"; };
513C5CEB232571C2003D4054 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = "<group>"; };
@ -803,11 +792,19 @@
51934CC1230F5963006127BE /* ThemedNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemedNavigationController.swift; sourceTree = "<group>"; };
51934CCD2310792F006127BE /* ActivityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityManager.swift; sourceTree = "<group>"; };
51938DF1231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchTimelineFeedDelegate.swift; sourceTree = "<group>"; };
5194B5ED22B6965300144881 /* SettingsSubscriptionsImportDocumentPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSubscriptionsImportDocumentPickerView.swift; sourceTree = "<group>"; };
5194B5F122B69FCC00144881 /* SettingsSubscriptionsExportDocumentPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSubscriptionsExportDocumentPickerView.swift; sourceTree = "<group>"; };
519B8D322143397200FA689C /* SharingServiceDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharingServiceDelegate.swift; sourceTree = "<group>"; };
519D740523243CC0008BB345 /* RefreshInterval-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RefreshInterval-Extensions.swift"; sourceTree = "<group>"; };
519E743422C663F900A78E47 /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
51A1698D235E10D600EB091F /* RefreshIntervalViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RefreshIntervalViewController.swift; sourceTree = "<group>"; };
51A1698E235E10D600EB091F /* SettingsTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SettingsTableViewCell.xib; sourceTree = "<group>"; };
51A1698F235E10D600EB091F /* AddLocalAccountViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddLocalAccountViewController.swift; sourceTree = "<group>"; };
51A16990235E10D600EB091F /* Settings.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Settings.storyboard; sourceTree = "<group>"; };
51A16991235E10D600EB091F /* DetailAccountViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetailAccountViewController.swift; sourceTree = "<group>"; };
51A16992235E10D600EB091F /* AddAccountViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddAccountViewController.swift; sourceTree = "<group>"; };
51A16993235E10D600EB091F /* SettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = "<group>"; };
51A16994235E10D600EB091F /* TimelineNumberOfLinesViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimelineNumberOfLinesViewController.swift; sourceTree = "<group>"; };
51A16995235E10D600EB091F /* AboutViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = "<group>"; };
51A16996235E10D700EB091F /* FeedbinAccountViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedbinAccountViewController.swift; sourceTree = "<group>"; };
51AF460D232488C6001742EF /* Account-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Account-Extensions.swift"; sourceTree = "<group>"; };
51B62E67233186730085F949 /* MasterTimelineAvatarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterTimelineAvatarView.swift; sourceTree = "<group>"; };
51BB7C262335A8E5008E8144 /* ArticleActivityItemSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleActivityItemSource.swift; sourceTree = "<group>"; };
@ -852,8 +849,6 @@
51EF0F8D2279C9260050506E /* AccountsAdd.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AccountsAdd.xib; sourceTree = "<group>"; };
51EF0F8F2279C9500050506E /* AccountsAddViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsAddViewController.swift; sourceTree = "<group>"; };
51EF0F912279CA620050506E /* AccountsAddTableCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsAddTableCellView.swift; sourceTree = "<group>"; };
51F35D0822AFD4760003CE1B /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
51F772EC22B2789B0087D9D1 /* SettingsDetailAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsDetailAccountView.swift; sourceTree = "<group>"; };
51F85BEA22724CB600C787DC /* About.rtf */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; path = About.rtf; sourceTree = "<group>"; };
51F85BEC227251DF00C787DC /* Acknowledgments.rtf */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; path = Acknowledgments.rtf; sourceTree = "<group>"; };
51F85BEE2272520B00C787DC /* Thanks.rtf */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; path = Thanks.rtf; sourceTree = "<group>"; };
@ -871,7 +866,6 @@
51FD40BD2341555600880194 /* UIImage-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage-Extensions.swift"; sourceTree = "<group>"; };
51FD413A2342BD0500880194 /* MasterTimelineUnreadCountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterTimelineUnreadCountView.swift; sourceTree = "<group>"; };
51FE10022345529D0056195D /* UserNotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserNotificationManager.swift; sourceTree = "<group>"; };
557EE1A522B6F4E1004206FA /* SettingsReaderAPIAccountView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsReaderAPIAccountView.swift; sourceTree = "<group>"; };
55E15BC1229D65A900D6602A /* AccountsReaderAPI.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AccountsReaderAPI.xib; sourceTree = "<group>"; };
55E15BCA229D65A900D6602A /* AccountsReaderAPIWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountsReaderAPIWindowController.swift; sourceTree = "<group>"; };
5F323808231DF9F000706F6B /* NNWTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NNWTableViewCell.swift; sourceTree = "<group>"; };
@ -1265,19 +1259,6 @@
name = Products;
sourceTree = "<group>";
};
515E4F06232506240057B0E7 /* Account */ = {
isa = PBXGroup;
children = (
510D708122B041CC004E8F65 /* SettingsAccountLabelView.swift */,
510D707322B028E1004E8F65 /* SettingsAddAccountView.swift */,
51F772EC22B2789B0087D9D1 /* SettingsDetailAccountView.swift */,
510D707F22B02A5F004E8F65 /* SettingsFeedbinAccountView.swift */,
510D707D22B02A4B004E8F65 /* SettingsLocalAccountView.swift */,
557EE1A522B6F4E1004206FA /* SettingsReaderAPIAccountView.swift */,
);
path = Account;
sourceTree = "<group>";
};
5183CCDB226F1EEB0010922C /* Progress */ = {
isa = PBXGroup;
children = (
@ -1300,14 +1281,16 @@
5183CCEB227117C70010922C /* Settings */ = {
isa = PBXGroup;
children = (
51F35D0822AFD4760003CE1B /* SettingsView.swift */,
5132293A23305D4C0033D4ED /* SettingsAboutView.swift */,
5132285A232FF2C40033D4ED /* SettingsRefreshSelectionView.swift */,
51314717235C89ED00387FDC /* SettingsSubscriptionsExportAccountPickerView.swift */,
5194B5F122B69FCC00144881 /* SettingsSubscriptionsExportDocumentPickerView.swift */,
51314715235C862200387FDC /* SettingsSubscriptionsImportAccountPickerView.swift */,
5194B5ED22B6965300144881 /* SettingsSubscriptionsImportDocumentPickerView.swift */,
515E4F06232506240057B0E7 /* Account */,
51A16990235E10D600EB091F /* Settings.storyboard */,
51A16995235E10D600EB091F /* AboutViewController.swift */,
51A16992235E10D600EB091F /* AddAccountViewController.swift */,
51A1698F235E10D600EB091F /* AddLocalAccountViewController.swift */,
51A16991235E10D600EB091F /* DetailAccountViewController.swift */,
51A16996235E10D700EB091F /* FeedbinAccountViewController.swift */,
51A1698D235E10D600EB091F /* RefreshIntervalViewController.swift */,
51A1698E235E10D600EB091F /* SettingsTableViewCell.xib */,
51A16993235E10D600EB091F /* SettingsViewController.swift */,
51A16994235E10D600EB091F /* TimelineNumberOfLinesViewController.swift */,
);
path = Settings;
sourceTree = "<group>";
@ -2648,6 +2631,7 @@
51C452862265093600C03939 /* Add.storyboard in Resources */,
511D43EF231FBDE900FB1562 /* LaunchScreenPad.storyboard in Resources */,
511D43D2231FA62C00FB1562 /* GlobalKeyboardShortcuts.plist in Resources */,
51A16998235E10D700EB091F /* SettingsTableViewCell.xib in Resources */,
84C9FCA12262A1B300D921D6 /* Main.storyboard in Resources */,
51BB7C312335ACDE008E8144 /* page.html in Resources */,
51F85BF32272531500C787DC /* Dedication.rtf in Resources */,
@ -2660,6 +2644,7 @@
51F85BF12272524100C787DC /* Credits.rtf in Resources */,
84A3EE61223B667F00557320 /* DefaultFeeds.opml in Resources */,
511D43CF231FA62200FB1562 /* DetailKeyboardShortcuts.plist in Resources */,
51A1699A235E10D700EB091F /* Settings.storyboard in Resources */,
49F40DF92335B71000552BF4 /* newsfoot.js in Resources */,
51F85BEF2272520B00C787DC /* Thanks.rtf in Resources */,
84C9FC9D2262A1A900D921D6 /* Assets.xcassets in Resources */,
@ -2913,7 +2898,6 @@
5183CCDA226E31A50010922C /* NonIntrinsicImageView.swift in Sources */,
51EAED96231363EF00A9EEE3 /* NonIntrinsicButton.swift in Sources */,
51C4527B2265091600C03939 /* MasterUnreadIndicatorView.swift in Sources */,
5152E1022324900D00E5C7AD /* SettingsAddAccountView.swift in Sources */,
51F85BF92274AA7B00C787DC /* UIBarButtonItem-Extensions.swift in Sources */,
51B62E68233186730085F949 /* MasterTimelineAvatarView.swift in Sources */,
51C45296226509D300C03939 /* OPMLExporter.swift in Sources */,
@ -2927,36 +2911,30 @@
51FD413B2342BD0500880194 /* MasterTimelineUnreadCountView.swift in Sources */,
513146B2235A81A400387FDC /* AddFeedIntentHandler.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 */,
51322859232FDDB80033D4ED /* VibrantButtonStyle.swift in Sources */,
51A1699C235E10D700EB091F /* AddAccountViewController.swift in Sources */,
51A16999235E10D700EB091F /* AddLocalAccountViewController.swift in Sources */,
514B7C8323205EFB00BAC947 /* RootSplitViewController.swift in Sources */,
5152E0F923248F6200E5C7AD /* SettingsLocalAccountView.swift in Sources */,
51FA73A52332BE110090D516 /* ArticleExtractor.swift in Sources */,
51314704235C41FC00387FDC /* Intents.intentdefinition in Sources */,
FF3ABF162325AF5D0074C542 /* ArticleSorter.swift in Sources */,
510BD15D232D765D002692E4 /* SettingsReaderAPIAccountView.swift in Sources */,
51C4525C226508DF00C03939 /* String-Extensions.swift in Sources */,
51C452792265091600C03939 /* MasterTimelineTableViewCell.swift in Sources */,
51FA73AB2332C2FD0090D516 /* ArticleExtractorConfig.swift in Sources */,
5132285B232FF2C40033D4ED /* SettingsRefreshSelectionView.swift in Sources */,
51C452852265093600C03939 /* FlattenedAccountFolderPickerData.swift in Sources */,
51C4526B226508F600C03939 /* MasterFeedViewController.swift in Sources */,
5126EE97226CB48A00C22AFC /* SceneCoordinator.swift in Sources */,
51FD40C72341555A00880194 /* UIImage-Extensions.swift in Sources */,
5132293B23305D4C0033D4ED /* SettingsAboutView.swift in Sources */,
84CAFCB022BC8C35007694F0 /* FetchRequestOperation.swift in Sources */,
51EF0F77227716200050506E /* FaviconGenerator.swift in Sources */,
51938DF3231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift in Sources */,
51C4525A226508D600C03939 /* UIStoryboard-Extensions.swift in Sources */,
519D740823243FEA008BB345 /* SettingsSubscriptionsImportDocumentPickerView.swift in Sources */,
51BB7C272335A8E5008E8144 /* ArticleActivityItemSource.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 */,
@ -2991,22 +2969,24 @@
84CAFCA522BC8C08007694F0 /* FetchRequestQueue.swift in Sources */,
51C4529C22650A1000C03939 /* SingleFaviconDownloader.swift in Sources */,
51E595A6228CC36500FCC42B /* ArticleStatusSyncTimer.swift in Sources */,
51A1699F235E10D700EB091F /* AboutViewController.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 */,
51F85BFB2275D85000C787DC /* Array-Extensions.swift in Sources */,
51C452AC22650FD200C03939 /* AppNotifications.swift in Sources */,
51EF0F7E2277A57D0050506E /* MasterTimelineAccessibilityCellLayout.swift in Sources */,
51A1699B235E10D700EB091F /* DetailAccountViewController.swift in Sources */,
51C452762265091600C03939 /* MasterTimelineViewController.swift in Sources */,
51314718235C89ED00387FDC /* SettingsSubscriptionsExportAccountPickerView.swift in Sources */,
5183CCE9226F68D90010922C /* AccountRefreshTimer.swift in Sources */,
51C452882265093600C03939 /* AddFeedViewController.swift in Sources */,
51A169A0235E10D700EB091F /* FeedbinAccountViewController.swift in Sources */,
51934CCE2310792F006127BE /* ActivityManager.swift in Sources */,
518651DA235621840078E021 /* ImageTransition.swift in Sources */,
514219372352510100E07E2C /* ImageScrollView.swift in Sources */,
51A16997235E10D700EB091F /* RefreshIntervalViewController.swift in Sources */,
DF999FF722B5AEFA0064B687 /* SafariView.swift in Sources */,
51C4529B22650A1000C03939 /* FaviconDownloader.swift in Sources */,
84DEE56622C32CA4005FC42C /* SmartFeedDelegate.swift in Sources */,
@ -3025,13 +3005,13 @@
5148F4552336DB7000F8CD8B /* MasterTimelineTitleView.swift in Sources */,
513228FC233037630033D4ED /* Reachability.swift in Sources */,
51C45259226508D300C03939 /* AppDefaults.swift in Sources */,
519D73FB2323FF35008BB345 /* SettingsView.swift in Sources */,
511D4419231FC02D00FB1562 /* KeyboardManager.swift in Sources */,
51A1699D235E10D700EB091F /* SettingsViewController.swift in Sources */,
51A1699E235E10D700EB091F /* TimelineNumberOfLinesViewController.swift in Sources */,
51C45293226509C800C03939 /* StarredFeedDelegate.swift in Sources */,
513229312330523F0033D4ED /* AttributedStringView.swift in Sources */,
51D6A5BC23199C85001C27D8 /* MasterTimelineDataSource.swift in Sources */,
51934CCB230F599B006127BE /* ThemedNavigationController.swift in Sources */,
51314716235C862200387FDC /* SettingsSubscriptionsImportAccountPickerView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -786,9 +786,10 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
}
func showSettings() {
rootSplitViewController.present(style: .formSheet) {
SettingsView(viewModel: SettingsView.ViewModel()).environment(\.sceneCoordinator, self)
}
let settingsNavController = UIStoryboard.settings.instantiateInitialViewController() as! UINavigationController
settingsNavController.modalPresentationStyle = .formSheet
settingsNavController.preferredContentSize = SettingsViewController.preferredContentSizeForFormSheetDisplay
masterFeedViewController.present(settingsNavController, animated: true)
}
func showFeedInspector() {

View File

@ -0,0 +1,56 @@
//
// AboutViewController.swift
// NetNewsWire-iOS
//
// Created by Maurice Parker on 4/25/19.
// Copyright © 2019 Ranchero Software. All rights reserved.
//
import UIKit
class AboutViewController: UITableViewController {
@IBOutlet weak var aboutTextView: UITextView!
@IBOutlet weak var creditsTextView: UITextView!
@IBOutlet weak var acknowledgmentsTextView: UITextView!
@IBOutlet weak var thanksTextView: UITextView!
@IBOutlet weak var dedicationTextView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
configureCell(file: "About", textView: aboutTextView)
configureCell(file: "Credits", textView: creditsTextView)
configureCell(file: "Acknowledgments", textView: acknowledgmentsTextView)
configureCell(file: "Thanks", textView: thanksTextView)
configureCell(file: "Dedication", textView: dedicationTextView)
let buildLabel = NonIntrinsicLabel(frame: CGRect(x: 20.0, y: 0.0, width: 0.0, height: 0.0))
buildLabel.font = UIFont.systemFont(ofSize: 11.0)
buildLabel.textColor = UIColor.gray
buildLabel.text = NSLocalizedString("Copyright © 2002-2019 Ranchero Software", comment: "Copyright")
buildLabel.numberOfLines = 0
buildLabel.sizeToFit()
buildLabel.translatesAutoresizingMaskIntoConstraints = false
tableView.tableFooterView = buildLabel
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
}
private extension AboutViewController {
func configureCell(file: String, textView: UITextView) {
let url = Bundle.main.url(forResource: file, withExtension: "rtf")!
let string = try! NSAttributedString(url: url, options: [NSAttributedString.DocumentReadingOptionKey.documentType: NSAttributedString.DocumentType.rtf], documentAttributes: nil)
textView.attributedText = string
textView.adjustsFontForContentSizeCategory = true
textView.font = .preferredFont(forTextStyle: .body)
}
}

View File

@ -1,34 +0,0 @@
//
// SettingsAccountLabelView.swift
// NetNewsWire-iOS
//
// Created by Maurice Parker on 6/11/19.
// Copyright © 2019 Ranchero Software. All rights reserved.
//
import SwiftUI
struct SettingsAccountLabelView : View {
let accountImage: String
let accountLabel: String
var body: some View {
HStack {
Image(accountImage)
.resizable()
.aspectRatio(1, contentMode: .fit)
.frame(height: 32)
Text(verbatim: accountLabel).font(.title)
}
.foregroundColor(.primary).padding(4.0)
}
}
#if DEBUG
struct SettingsAccountLabelView_Previews : PreviewProvider {
static var previews: some View {
SettingsAccountLabelView(accountImage: "accountLocal", accountLabel: "On My Device")
.previewLayout(.fixed(width: 300, height: 44))
}
}
#endif

View File

@ -1,52 +0,0 @@
//
// SettingsAddAccountView.swift
// NetNewsWire-iOS
//
// Created by Maurice Parker on 6/11/19.
// Copyright © 2019 Ranchero Software. All rights reserved.
//
import SwiftUI
import Account
struct SettingsAddAccountView : View {
@Environment(\.presentationMode) var presentation
@State private var accountAddAction: Int? = nil
var body: some View {
Form {
NavigationLink(destination: SettingsLocalAccountView(name: ""), tag: 1, selection: $accountAddAction) {
SettingsAccountLabelView(accountImage: "accountLocal", accountLabel: Account.defaultLocalAccountName)
}
.modifier(VibrantSelectAction(action: {
self.accountAddAction = 1
})).padding(.vertical, 16)
NavigationLink(destination: SettingsFeedbinAccountView(viewModel: SettingsFeedbinAccountView.ViewModel()), tag: 2, selection: $accountAddAction) {
SettingsAccountLabelView(accountImage: "accountFeedbin", accountLabel: "Feedbin")
}
.modifier(VibrantSelectAction(action: {
self.accountAddAction = 2
})).padding(.vertical, 16)
// NavigationLink(destination: SettingsReaderAPIAccountView(viewModel: SettingsReaderAPIAccountView.ViewModel(accountType: .freshRSS)), tag: 3, selection: $accountAddAction) {
// SettingsAccountLabelView(accountImage: "accountFreshRSS", accountLabel: "Fresh RSS")
// }
// .modifier(VibrantSelectAction(action: {
// self.accountAddAction = 3
// }))
}
.navigationBarTitle(Text("Add Account"), displayMode: .inline)
}
}
#if DEBUG
struct AddAccountView_Previews : PreviewProvider {
static var previews: some View {
SettingsAddAccountView()
}
}
#endif

View File

@ -1,137 +0,0 @@
//
// SettingsDetailAccountView.swift
// NetNewsWire
//
// Created by Maurice Parker on 6/13/19.
// Copyright © 2019 Ranchero Software. All rights reserved.
//
import SwiftUI
import Combine
import Account
import RSWeb
struct SettingsDetailAccountView : View {
@Environment(\.presentationMode) var presentation
@ObservedObject var viewModel: ViewModel
@State private var credentialsAction: Int? = nil
@State private var isDeleteAlertPresented = false
var body: some View {
Form {
Section {
HStack {
TextField("Name", text: $viewModel.name)
}
Toggle(isOn: $viewModel.isActive) {
Text("Active")
}
}
if viewModel.isCreditialsAvailable {
if viewModel.account.type == .feedbin {
NavigationLink(destination: self.settingsFeedbinAccountView, tag: 1, selection: $credentialsAction) {
Text("Credentials")
}
.modifier(VibrantSelectAction(action: {
self.credentialsAction = 1
}))
}
if viewModel.account.type == .freshRSS {
NavigationLink(destination: self.settingsReaderAPIAccountView, tag: 1, selection: $credentialsAction) {
Text("Credentials")
}
.modifier(VibrantSelectAction(action: {
self.credentialsAction = 1
}))
}
}
if viewModel.isDeletable {
Section {
Button(action: {
self.isDeleteAlertPresented.toggle()
}) {
Text("Delete Account").foregroundColor(.red)
}
.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()
self.presentation.wrappedValue.dismiss()
}),
secondaryButton: Alert.Button.cancel())
}
}
}
}
.buttonStyle(VibrantButtonStyle(alignment: .center))
.navigationBarTitle(Text(verbatim: viewModel.nameForDisplay), displayMode: .inline)
}
var settingsFeedbinAccountView: SettingsFeedbinAccountView {
let feedbinViewModel = SettingsFeedbinAccountView.ViewModel(account: viewModel.account)
return SettingsFeedbinAccountView(viewModel: feedbinViewModel)
}
var settingsReaderAPIAccountView: SettingsReaderAPIAccountView {
let readerAPIModel = SettingsReaderAPIAccountView.ViewModel(account: viewModel.account)
return SettingsReaderAPIAccountView(viewModel: readerAPIModel)
}
class ViewModel: ObservableObject {
let objectWillChange = ObservableObjectPublisher()
let account: Account
init(_ account: Account) {
self.account = account
}
var nameForDisplay: String {
account.nameForDisplay
}
var name: String {
get {
account.name ?? ""
}
set {
objectWillChange.send()
account.name = newValue.isEmpty ? nil : newValue
}
}
var isActive: Bool {
get {
account.isActive
}
set {
objectWillChange.send()
account.isActive = newValue
}
}
var isCreditialsAvailable: Bool {
return account.type != .onMyMac
}
var isDeletable: Bool {
return AccountManager.shared.defaultAccount != account
}
func delete() {
AccountManager.shared.deleteAccount(account)
ActivityManager.cleanUp(account)
}
}
}
#if DEBUG
struct SettingsDetailAccountView_Previews : PreviewProvider {
static var previews: some View {
let viewModel = SettingsDetailAccountView.ViewModel(AccountManager.shared.defaultAccount)
return SettingsDetailAccountView(viewModel: viewModel)
}
}
#endif

View File

@ -1,161 +0,0 @@
//
// SettingsFeedbinAccountView.swift
// NetNewsWire-iOS
//
// Created by Maurice Parker on 6/11/19.
// Copyright © 2019 Ranchero Software. All rights reserved.
//
import SwiftUI
import Combine
import Account
import RSWeb
struct SettingsFeedbinAccountView : View {
@Environment(\.presentationMode) var presentation
@ObservedObject var viewModel: ViewModel
@State var busy: Bool = false
@State var error: String = ""
var body: some View {
Form {
Section(header:
HStack {
Spacer()
SettingsAccountLabelView(accountImage: "accountFeedbin", accountLabel: "Feedbin")
.padding()
.layoutPriority(1.0)
Spacer()
}
) {
TextField("Email", text: $viewModel.email)
.keyboardType(.emailAddress)
.textContentType(.emailAddress)
PasswordField(password: $viewModel.password)
}
Section(footer:
HStack {
Spacer()
Text(verbatim: error).foregroundColor(.red)
Spacer()
}
) {
Button(action: { self.addAccount() }) {
if viewModel.isUpdate {
Text("Update Account")
} else {
Text("Add Account")
}
}
.buttonStyle(VibrantButtonStyle(alignment: .center))
.disabled(!viewModel.isValid)
}
}
// .disabled(busy) // Maybe someday we can do this, but right now it crashes on the iPad
.navigationBarTitle(Text(""), displayMode: .inline)
}
private func addAccount() {
busy = true
error = ""
let emailAddress = viewModel.email.trimmingCharacters(in: .whitespaces)
let credentials = Credentials(type: .basic, username: emailAddress, secret: viewModel.password)
Account.validateCredentials(type: .feedbin, credentials: credentials) { result in
self.busy = false
switch result {
case .success(let authenticated):
if (authenticated != nil) {
var newAccount = false
let workAccount: Account
if self.viewModel.account == nil {
workAccount = AccountManager.shared.createAccount(type: .feedbin)
newAccount = true
} else {
workAccount = self.viewModel.account!
}
do {
do {
try workAccount.removeCredentials(type: .basic)
} catch {}
try workAccount.storeCredentials(credentials)
if newAccount {
workAccount.refreshAll() { result in }
}
self.dismiss()
} catch {
self.error = "Keychain error while storing credentials."
}
} else {
self.error = "Invalid email/password combination."
}
case .failure:
self.error = "Network error. Try again later."
}
}
}
private func dismiss() {
presentation.wrappedValue.dismiss()
}
class ViewModel: ObservableObject {
let objectWillChange = ObservableObjectPublisher()
var account: Account? = nil
init() {
}
init(account: Account) {
self.account = account
if let credentials = try? account.retrieveCredentials(type: .basic) {
self.email = credentials.username
}
}
var email: String = "" {
willSet {
objectWillChange.send()
}
}
var password: String = "" {
willSet {
objectWillChange.send()
}
}
var isUpdate: Bool {
return account != nil
}
var isValid: Bool {
return !email.isEmpty && !password.isEmpty
}
}
}
#if DEBUG
struct SettingsFeedbinAccountView_Previews : PreviewProvider {
static var previews: some View {
SettingsFeedbinAccountView(viewModel: SettingsFeedbinAccountView.ViewModel())
}
}
#endif

View File

@ -1,59 +0,0 @@
//
// SettingsLocalAccountView.swift
// NetNewsWire-iOS
//
// Created by Maurice Parker on 6/11/19.
// Copyright © 2019 Ranchero Software. All rights reserved.
//
import SwiftUI
import Account
struct SettingsLocalAccountView : View {
@Environment(\.presentationMode) var presentation
@State var name: String
var body: some View {
Form {
Section(header:
HStack {
Spacer()
SettingsAccountLabelView(accountImage: "accountLocal", accountLabel: Account.defaultLocalAccountName)
.padding()
.layoutPriority(1.0)
Spacer()
}
) {
HStack {
TextField("Name", text: $name)
}
}
Section {
Button(action: { self.addAccount() }) {
Text("Add Account")
}
.buttonStyle(VibrantButtonStyle(alignment: .center))
}
}
.navigationBarTitle(Text(""), displayMode: .inline)
}
private func addAccount() {
let account = AccountManager.shared.createAccount(type: .onMyMac)
account.name = name
dismiss()
}
private func dismiss() {
presentation.wrappedValue.dismiss()
}
}
#if DEBUG
struct SettingsLocalAccountView_Previews : PreviewProvider {
static var previews: some View {
SettingsLocalAccountView(name: "")
}
}
#endif

View File

@ -1,178 +0,0 @@
//
// SettingsReaderAPIAccountView.swift
// NetNewsWire-iOS
//
// Created by Jeremy Beker on 5/28/2019.
// Copyright © 2019 Ranchero Software. All rights reserved.
//
import SwiftUI
import Combine
import Account
import RSWeb
struct SettingsReaderAPIAccountView : View {
@Environment(\.presentationMode) var presentation
@ObservedObject var viewModel: ViewModel
@State var busy: Bool = false
@State var error: String = ""
var body: some View {
Form {
Section(header:
HStack {
Spacer()
SettingsAccountLabelView(accountImage: "accountFreshRSS", accountLabel: "FreshRSS")
.padding()
.layoutPriority(1.0)
Spacer()
}
) {
TextField("Email", text: $viewModel.email)
.keyboardType(.emailAddress)
.textContentType(.emailAddress)
SecureField("Password", text: $viewModel.password)
TextField("API URL:", text: $viewModel.apiURL).textContentType(.URL)
}
Section(footer:
HStack {
Spacer()
Text(verbatim: error).foregroundColor(.red)
Spacer()
}
) {
Button(action: { self.addAccount() }) {
if viewModel.isUpdate {
Text("Update Account")
} else {
Text("Add Account")
}
}
.buttonStyle(VibrantButtonStyle(alignment: .center))
.disabled(!viewModel.isValid)
}
}
// .disabled(busy)
}
private func addAccount() {
busy = true
error = ""
let emailAddress = viewModel.email.trimmingCharacters(in: .whitespaces)
let credentials = Credentials(type: .readerBasic, username: emailAddress, secret: viewModel.password)
guard let apiURL = URL(string: viewModel.apiURL) else {
self.error = "Invalid API URL."
return
}
Account.validateCredentials(type: viewModel.accountType, credentials: credentials, endpoint: apiURL) { result in
self.busy = false
switch result {
case .success(let validatedCredentials):
guard let validatedCredentials = validatedCredentials else {
self.error = "Invalid email/password combination."
return
}
var newAccount = false
let workAccount: Account
if self.viewModel.account == nil {
workAccount = AccountManager.shared.createAccount(type: self.viewModel.accountType)
newAccount = true
} else {
workAccount = self.viewModel.account!
}
do {
do {
try workAccount.removeCredentials(type: .readerBasic)
try workAccount.removeCredentials(type: .readerAPIKey)
} catch {}
workAccount.endpointURL = apiURL
try workAccount.storeCredentials(credentials)
try workAccount.storeCredentials(validatedCredentials)
if newAccount {
workAccount.refreshAll() { result in }
}
self.dismiss()
} catch {
self.error = "Keychain error while storing credentials."
}
case .failure:
self.error = "Network error. Try again later."
}
}
}
private func dismiss() {
presentation.wrappedValue.dismiss()
}
class ViewModel: ObservableObject {
let objectWillChange = ObservableObjectPublisher()
var accountType: AccountType
var account: Account? = nil
init(accountType: AccountType) {
self.accountType = accountType
}
init(account: Account) {
self.account = account
self.accountType = account.type
if let credentials = try? account.retrieveCredentials(type: .readerBasic) {
self.email = credentials.username
self.apiURL = account.endpointURL?.absoluteString ?? ""
}
}
var email: String = "" {
willSet {
objectWillChange.send()
}
}
var password: String = "" {
willSet {
objectWillChange.send()
}
}
var apiURL: String = "" {
willSet {
objectWillChange.send()
}
}
var isUpdate: Bool {
return account != nil
}
var isValid: Bool {
return !email.isEmpty && !password.isEmpty
}
}
}
#if DEBUG
struct SettingsReaderAPIAccountView_Previews : PreviewProvider {
static var previews: some View {
SettingsReaderAPIAccountView(viewModel: SettingsReaderAPIAccountView.ViewModel(accountType: .freshRSS))
}
}
#endif

View File

@ -0,0 +1,49 @@
//
// AddAccountViewController.swift
// NetNewsWire-iOS
//
// Created by Maurice Parker on 5/16/19.
// Copyright © 2019 Ranchero Software. All rights reserved.
//
import Account
import UIKit
protocol AddAccountDismissDelegate: UIViewController {
func dismiss()
}
class AddAccountViewController: UITableViewController, AddAccountDismissDelegate {
@IBOutlet private weak var localAccountNameLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
localAccountNameLabel.text = Account.defaultLocalAccountName
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
switch indexPath.row {
case 0:
let navController = UIStoryboard.settings.instantiateViewController(withIdentifier: "AddLocalAccountNavigationViewController") as! UINavigationController
navController.modalPresentationStyle = .currentContext
let addViewController = navController.topViewController as! AddLocalAccountViewController
addViewController.delegate = self
present(navController, animated: true)
case 1:
let navController = UIStoryboard.settings.instantiateViewController(withIdentifier: "FeedbinAccountNavigationViewController") as! UINavigationController
navController.modalPresentationStyle = .currentContext
let addViewController = navController.topViewController as! FeedbinAccountViewController
addViewController.delegate = self
present(navController, animated: true)
default:
break
}
}
func dismiss() {
navigationController?.popViewController(animated: false)
}
}

View File

@ -0,0 +1,48 @@
//
// AddLocalAccountViewController.swift
// NetNewsWire-iOS
//
// Created by Maurice Parker on 5/19/19.
// Copyright © 2019 Ranchero Software. All rights reserved.
//
import UIKit
import Account
class AddLocalAccountViewController: UIViewController {
@IBOutlet weak var cancelBarButtonItem: UIBarButtonItem!
@IBOutlet private weak var localAccountNameLabel: UILabel!
@IBOutlet weak var nameTextField: UITextField!
weak var delegate: AddAccountDismissDelegate?
override func viewDidLoad() {
super.viewDidLoad()
localAccountNameLabel.text = Account.defaultLocalAccountName
nameTextField.delegate = self
}
@IBAction func cancel(_ sender: Any) {
dismiss(animated: true, completion: nil)
delegate?.dismiss()
}
@IBAction func addAccountTapped(_ sender: Any) {
let account = AccountManager.shared.createAccount(type: .onMyMac)
account.name = nameTextField.text
dismiss(animated: true, completion: nil)
delegate?.dismiss()
}
}
extension AddLocalAccountViewController: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
}

View File

@ -0,0 +1,135 @@
//
// DetailAccountViewController.swift
// NetNewsWire-iOS
//
// Created by Maurice Parker on 5/17/19.
// Copyright © 2019 Ranchero Software. All rights reserved.
//
import UIKit
import Account
class DetailAccountViewController: UITableViewController {
@IBOutlet weak var nameTextField: UITextField!
@IBOutlet weak var activeSwitch: UISwitch!
weak var account: Account?
override func viewDidLoad() {
super.viewDidLoad()
guard let account = account else { return }
nameTextField.placeholder = account.defaultName
nameTextField.text = account.name
nameTextField.delegate = self
activeSwitch.isOn = account.isActive
}
override func viewWillDisappear(_ animated: Bool) {
account?.name = nameTextField.text
account?.isActive = activeSwitch.isOn
}
}
extension DetailAccountViewController {
override func numberOfSections(in tableView: UITableView) -> Int {
guard let account = account else { return 0 }
if account == AccountManager.shared.defaultAccount {
return 1
} else if account.type == .onMyMac {
return 2
} else {
return super.numberOfSections(in: tableView)
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: UITableViewCell
if indexPath.section == 1, let account = account, account.type == .onMyMac {
cell = super.tableView(tableView, cellForRowAt: IndexPath(row: 0, section: 2))
} else {
cell = super.tableView(tableView, cellForRowAt: indexPath)
}
return cell
}
override func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool {
if indexPath.section > 0 {
return true
}
return false
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let account = account, account.type == .onMyMac {
if indexPath.section == 1 {
deleteAccount()
}
} else {
switch indexPath.section {
case 1:
credentials()
case 2:
deleteAccount()
default:
break
}
}
tableView.selectRow(at: nil, animated: true, scrollPosition: .none)
}
}
private extension DetailAccountViewController {
func credentials() {
guard let account = account else { return }
switch account.type {
case .feedbin:
let navController = UIStoryboard.settings.instantiateViewController(withIdentifier: "FeedbinAccountNavigationViewController") as! UINavigationController
let addViewController = navController.topViewController as! FeedbinAccountViewController
addViewController.account = account
present(navController, animated: true)
default:
break
}
}
func deleteAccount() {
let title = NSLocalizedString("Delete Account", comment: "Delete Account")
let message = NSLocalizedString("Are you sure you want to delete this account? This can not be undone.", comment: "Delete Account")
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
let cancelTitle = NSLocalizedString("Cancel", comment: "Cancel")
let cancelAction = UIAlertAction(title: cancelTitle, style: .cancel)
alertController.addAction(cancelAction)
let markTitle = NSLocalizedString("Delete", comment: "Delete")
let markAction = UIAlertAction(title: markTitle, style: .default) { [weak self] (action) in
guard let account = self?.account else { return }
AccountManager.shared.deleteAccount(account)
self?.navigationController?.popViewController(animated: true)
}
alertController.addAction(markAction)
present(alertController, animated: true)
}
}
extension DetailAccountViewController: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
}

View File

@ -0,0 +1,137 @@
//
// AddFeedbinAccountViewController.swift
// NetNewsWire-iOS
//
// Created by Maurice Parker on 5/19/19.
// Copyright © 2019 Ranchero Software. All rights reserved.
//
import UIKit
import Account
import RSWeb
class FeedbinAccountViewController: UIViewController {
@IBOutlet weak var activityIndicator: UIActivityIndicatorView!
@IBOutlet weak var cancelBarButtonItem: UIBarButtonItem!
@IBOutlet weak var emailTextField: UITextField!
@IBOutlet weak var passwordTextField: UITextField!
@IBOutlet weak var actionButton: UIButton!
@IBOutlet weak var errorMessageLabel: UILabel!
weak var account: Account?
weak var delegate: AddAccountDismissDelegate?
override func viewDidLoad() {
super.viewDidLoad()
activityIndicator.isHidden = true
emailTextField.delegate = self
passwordTextField.delegate = self
if let account = account, let credentials = try? account.retrieveCredentials(type: .basic) {
actionButton.setTitle(NSLocalizedString("Update Credentials", comment: "Update Credentials"), for: .normal)
emailTextField.text = credentials.username
passwordTextField.text = credentials.secret
} else {
actionButton.setTitle(NSLocalizedString("Add Account", comment: "Update Credentials"), for: .normal)
}
}
@IBAction func cancel(_ sender: Any) {
dismiss(animated: true, completion: nil)
delegate?.dismiss()
}
@IBAction func action(_ sender: Any) {
self.errorMessageLabel.text = nil
guard emailTextField.text != nil && passwordTextField.text != nil else {
self.errorMessageLabel.text = NSLocalizedString("Username & password required.", comment: "Credentials Error")
return
}
startAnimatingActivityIndicator()
disableNavigation()
// When you fill in the email address via auto-complete it adds extra whitespace
let emailAddress = emailTextField.text?.trimmingCharacters(in: .whitespaces)
let credentials = Credentials(type: .basic, username: emailAddress ?? "", secret: passwordTextField.text ?? "")
Account.validateCredentials(type: .feedbin, credentials: credentials) { result in
self.stopAnimtatingActivityIndicator()
self.enableNavigation()
switch result {
case .success(let credentials):
if let credentials = credentials {
var newAccount = false
if self.account == nil {
self.account = AccountManager.shared.createAccount(type: .feedbin)
newAccount = true
}
do {
do {
try self.account?.removeCredentials(type: .basic)
} catch {}
try self.account?.storeCredentials(credentials)
if newAccount {
self.account?.refreshAll() { result in
switch result {
case .success:
break
case .failure(let error):
self.presentError(error)
}
}
}
self.dismiss(animated: true, completion: nil)
self.delegate?.dismiss()
} catch {
self.errorMessageLabel.text = NSLocalizedString("Keychain error while storing credentials.", comment: "Credentials Error")
}
} else {
self.errorMessageLabel.text = NSLocalizedString("Invalid email/password combination.", comment: "Credentials Error")
}
case .failure:
self.errorMessageLabel.text = NSLocalizedString("Network error. Try again later.", comment: "Credentials Error")
}
}
}
private func enableNavigation() {
self.cancelBarButtonItem.isEnabled = true
self.actionButton.isEnabled = true
}
private func disableNavigation() {
cancelBarButtonItem.isEnabled = false
actionButton.isEnabled = false
}
private func startAnimatingActivityIndicator() {
activityIndicator.isHidden = false
activityIndicator.startAnimating()
}
private func stopAnimtatingActivityIndicator() {
self.activityIndicator.isHidden = true
self.activityIndicator.stopAnimating()
}
}
extension FeedbinAccountViewController: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
}

View File

@ -0,0 +1,111 @@
//
// RefreshIntervalViewController.swift
// NetNewsWire
//
// Created by Maurice Parker on 4/25/19.
// Copyright © 2019 Ranchero Software. All rights reserved.
//
import UIKit
class RefreshIntervalViewController: UITableViewController {
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 7
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.textLabel?.adjustsFontForContentSizeCategory = true
let userRefreshInterval = AppDefaults.refreshInterval
switch indexPath.row {
case 0:
cell.textLabel?.text = RefreshInterval.manually.description()
if userRefreshInterval == RefreshInterval.manually {
cell.accessoryType = .checkmark
} else {
cell.accessoryType = .none
}
case 1:
cell.textLabel?.text = RefreshInterval.every10Minutes.description()
if userRefreshInterval == RefreshInterval.every10Minutes {
cell.accessoryType = .checkmark
} else {
cell.accessoryType = .none
}
case 2:
cell.textLabel?.text = RefreshInterval.every30Minutes.description()
if userRefreshInterval == RefreshInterval.every30Minutes {
cell.accessoryType = .checkmark
} else {
cell.accessoryType = .none
}
case 3:
cell.textLabel?.text = RefreshInterval.everyHour.description()
if userRefreshInterval == RefreshInterval.everyHour {
cell.accessoryType = .checkmark
} else {
cell.accessoryType = .none
}
case 4:
cell.textLabel?.text = RefreshInterval.every2Hours.description()
if userRefreshInterval == RefreshInterval.every2Hours {
cell.accessoryType = .checkmark
} else {
cell.accessoryType = .none
}
case 5:
cell.textLabel?.text = RefreshInterval.every4Hours.description()
if userRefreshInterval == RefreshInterval.every4Hours {
cell.accessoryType = .checkmark
} else {
cell.accessoryType = .none
}
default:
cell.textLabel?.text = RefreshInterval.every8Hours.description()
if userRefreshInterval == RefreshInterval.every8Hours {
cell.accessoryType = .checkmark
} else {
cell.accessoryType = .none
}
}
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let refreshInterval: RefreshInterval
switch indexPath.row {
case 0:
refreshInterval = RefreshInterval.manually
case 1:
refreshInterval = RefreshInterval.every10Minutes
case 2:
refreshInterval = RefreshInterval.every30Minutes
case 3:
refreshInterval = RefreshInterval.everyHour
case 4:
refreshInterval = RefreshInterval.every2Hours
case 5:
refreshInterval = RefreshInterval.every4Hours
default:
refreshInterval = RefreshInterval.every8Hours
}
AppDefaults.refreshInterval = refreshInterval
self.navigationController?.popViewController(animated: true)
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,67 +0,0 @@
//
// SettingsAboutView.swift
// NetNewsWire-iOS
//
// Created by Maurice Parker on 9/16/19.
// Copyright © 2019 Ranchero Software. All rights reserved.
//
import SwiftUI
import Combine
struct SettingsAboutView: View {
@ObservedObject var viewModel: ViewModel
var body: some View {
GeometryReader { geometry in
List {
Text("NetNewsWire").font(.largeTitle)
AttributedStringView(string: self.viewModel.about, preferredMaxLayoutWidth: geometry.size.width - 20)
Section(header: Text("CREDITS")) {
AttributedStringView(string: self.viewModel.credits, preferredMaxLayoutWidth: geometry.size.width - 20)
}
Section(header: Text("ACKNOWLEDGEMENTS")) {
AttributedStringView(string: self.viewModel.acknowledgements, preferredMaxLayoutWidth: geometry.size.width - 20)
}
Section(header: Text("THANKS")) {
AttributedStringView(string: self.viewModel.thanks, preferredMaxLayoutWidth: geometry.size.width - 20)
}
Section(header: Text("DEDICATION"), footer: Text("Copyright © 2002-2019 Ranchero Software").font(.footnote)) {
AttributedStringView(string: self.viewModel.dedication, preferredMaxLayoutWidth: geometry.size.width - 20)
}
}
}
}
class ViewModel: ObservableObject {
let objectWillChange = ObservableObjectPublisher()
var about: NSAttributedString
var credits: NSAttributedString
var acknowledgements: NSAttributedString
var thanks: NSAttributedString
var dedication: NSAttributedString
init() {
about = ViewModel.loadResource("About")
credits = ViewModel.loadResource("Credits")
acknowledgements = ViewModel.loadResource("Acknowledgments")
thanks = ViewModel.loadResource("Thanks")
dedication = ViewModel.loadResource("Dedication")
}
private static func loadResource(_ resource: String) -> NSAttributedString {
let url = Bundle.main.url(forResource: resource, withExtension: "rtf")!
return try! NSAttributedString(url: url, options: [NSAttributedString.DocumentReadingOptionKey.documentType: NSAttributedString.DocumentType.rtf], documentAttributes: nil)
}
}
}
struct SettingsAboutView_Previews: PreviewProvider {
static var previews: some View {
SettingsAboutView(viewModel: SettingsAboutView.ViewModel())
}
}

View File

@ -1,35 +0,0 @@
//
// SettingsRefreshSelectionView.swift
// NetNewsWire-iOS
//
// Created by Maurice Parker on 9/16/19.
// Copyright © 2019 Ranchero Software. All rights reserved.
//
import SwiftUI
struct SettingsRefreshSelectionView: View {
@Environment(\.presentationMode) var presentation
@Binding var selectedInterval: RefreshInterval
var body: some View {
Form {
ForEach(RefreshInterval.allCases) { interval in
Button(action: {
self.selectedInterval = interval
self.presentation.wrappedValue.dismiss()
}) {
HStack {
Text(interval.description())
Spacer()
if interval == self.selectedInterval {
Image(systemName: "checkmark")
}
}
}.buttonStyle(VibrantButtonStyle(alignment: .leading))
}
}
}
}

View File

@ -1,34 +0,0 @@
//
// SettingsSubscriptionsExportAccountPickerView.swift
// NetNewsWire-iOS
//
// Created by Maurice Parker on 10/20/19.
// Copyright © 2019 Ranchero Software. All rights reserved.
//
import SwiftUI
import Account
struct SettingsSubscriptionsExportAccountPickerView: View {
@Environment(\.presentationMode) var presentation
@State private var selectedAccount: Account?
@State private var isOPMLExportDocPickerPresented: Bool = false
var body: some View {
Form {
ForEach(AccountManager.shared.sortedAccounts) { account in
Button(action: {
self.selectedAccount = account
self.isOPMLExportDocPickerPresented = true
}) {
Text(verbatim: account.nameForDisplay)
}.buttonStyle(VibrantButtonStyle(alignment: .leading))
}
}.sheet(isPresented: $isOPMLExportDocPickerPresented, onDismiss: { self.presentation.wrappedValue.dismiss() }) {
SettingsSubscriptionsExportDocumentPickerView(account: self.selectedAccount!)
}
.navigationBarTitle(Text("Select Account"), displayMode: .inline)
}
}

View File

@ -1,31 +0,0 @@
//
// SettingsSubscriptionsExportDocumentPickerView.swift
// NetNewsWire-iOS
//
// Created by Maurice Parker on 6/16/19.
// Copyright © 2019 Ranchero Software. All rights reserved.
//
import SwiftUI
import Account
struct SettingsSubscriptionsExportDocumentPickerView : UIViewControllerRepresentable {
var account: Account
func makeUIViewController(context: UIViewControllerRepresentableContext<SettingsSubscriptionsExportDocumentPickerView>) -> UIDocumentPickerViewController {
let accountName = account.nameForDisplay.replacingOccurrences(of: " ", with: "").trimmingCharacters(in: .whitespaces)
let filename = "Subscriptions-\(accountName).opml"
let tempFile = FileManager.default.temporaryDirectory.appendingPathComponent(filename)
let opmlString = OPMLExporter.OPMLString(with: account, title: filename)
try? opmlString.write(to: tempFile, atomically: true, encoding: String.Encoding.utf8)
return UIDocumentPickerViewController(url: tempFile, in: .exportToService)
}
func updateUIViewController(_ uiViewController: UIDocumentPickerViewController, context: UIViewControllerRepresentableContext<SettingsSubscriptionsExportDocumentPickerView>) {
//
}
}

View File

@ -1,34 +0,0 @@
//
// SettingsSubscriptionsImportAccountPickerView.swift
// NetNewsWire-iOS
//
// Created by Maurice Parker on 10/20/19.
// Copyright © 2019 Ranchero Software. All rights reserved.
//
import SwiftUI
import Account
struct SettingsSubscriptionsImportAccountPickerView: View {
@Environment(\.presentationMode) var presentation
@State private var selectedAccount: Account?
@State private var isOPMLImportDocPickerPresented: Bool = false
var body: some View {
Form {
ForEach(AccountManager.shared.sortedActiveAccounts) { account in
Button(action: {
self.selectedAccount = account
self.isOPMLImportDocPickerPresented = true
}) {
Text(verbatim: account.nameForDisplay)
}.buttonStyle(VibrantButtonStyle(alignment: .leading))
}
}.sheet(isPresented: $isOPMLImportDocPickerPresented, onDismiss: { self.presentation.wrappedValue.dismiss() }) {
SettingsSubscriptionsImportDocumentPickerView(account: self.selectedAccount!)
}
.navigationBarTitle(Text("Select Account"), displayMode: .inline)
}
}

View File

@ -1,43 +0,0 @@
//
// SettingsSubscriptionsImportDocumentPickerView.swift
// NetNewsWire-iOS
//
// Created by Maurice Parker on 6/16/19.
// Copyright © 2019 Ranchero Software. All rights reserved.
//
import SwiftUI
import Account
struct SettingsSubscriptionsImportDocumentPickerView : UIViewControllerRepresentable {
var account: Account
func makeUIViewController(context: UIViewControllerRepresentableContext<SettingsSubscriptionsImportDocumentPickerView>) -> UIDocumentPickerViewController {
let docPicker = UIDocumentPickerViewController(documentTypes: ["public.xml", "org.opml.opml"], in: .import)
docPicker.delegate = context.coordinator
return docPicker
}
func updateUIViewController(_ uiViewController: UIDocumentPickerViewController, context: UIViewControllerRepresentableContext<SettingsSubscriptionsImportDocumentPickerView>) {
//
}
func makeCoordinator() -> Coordinator {
return Coordinator(self)
}
class Coordinator : NSObject, UIDocumentPickerDelegate {
var parent: SettingsSubscriptionsImportDocumentPickerView
init(_ view: SettingsSubscriptionsImportDocumentPickerView) {
self.parent = view
}
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
for url in urls {
parent.account.importOPML(url) { result in}
}
}
}
}

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" id="JCb-QB-CrO">
<rect key="frame" x="0.0" y="0.0" width="414" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="JCb-QB-CrO" id="FzD-t2-JGy">
<rect key="frame" x="0.0" y="0.0" width="376" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
</tableViewCellContentView>
<point key="canvasLocation" x="7" y="-9"/>
</tableViewCell>
</objects>
</document>

View File

@ -1,270 +0,0 @@
//
// SettingsView.swift
// NetNewsWire-iOS
//
// Created by Maurice Parker on 6/11/19.
// Copyright © 2019 Ranchero Software. All rights reserved.
//
import SwiftUI
import Combine
import Account
struct SettingsView : View {
@ObservedObject var viewModel: ViewModel
@Environment(\.viewController) private var viewController: UIViewController?
@Environment(\.sceneCoordinator) private var coordinator: SceneCoordinator?
@State private var accountAction: Int? = nil
@State private var refreshAction: Int? = nil
@State private var importOPMLAction: Int? = nil
@State private var exportOPMLAction: Int? = nil
@State private var aboutAction: Int? = nil
@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 {
buildAccountsSection()
buildTimelineSection()
buildDatabaseSection()
buildAboutSection()
}
.buttonStyle(VibrantButtonStyle(alignment: .leading))
.navigationBarTitle(Text("Settings"), displayMode: .inline)
.navigationBarItems(leading: Button(action: { self.viewController?.dismiss(animated: true) }) { Text("Done") } )
}
}
func buildAccountsSection() -> some View {
Section(header: Text("ACCOUNTS").padding(.top, 22.0)) {
ForEach(viewModel.accounts.indices, id: \.self) { index in
NavigationLink(destination: SettingsDetailAccountView(viewModel: SettingsDetailAccountView.ViewModel(self.viewModel.accounts[index])), tag: index, selection: self.$accountAction) {
Text(verbatim: self.viewModel.accounts[index].nameForDisplay)
}
.modifier(VibrantSelectAction(action: {
self.accountAction = index
}))
}
NavigationLink(destination: SettingsAddAccountView(), tag: 1000, selection: $accountAction) {
Text("Add Account")
}
.modifier(VibrantSelectAction(action: {
self.accountAction = 1000
}))
}
}
func buildTimelineSection() -> some View {
Section(header: Text("TIMELINE")) {
Toggle(isOn: $viewModel.sortOldestToNewest) {
Text("Sort Newest to Oldest")
}
Toggle(isOn: $viewModel.groupByFeed) {
Text("Group By Feed")
}
Stepper(value: $viewModel.timelineNumberOfLines, in: 2...6) {
Text("Number of Text Lines: \(viewModel.timelineNumberOfLines)")
}
}
}
func buildDatabaseSection() -> some View {
Section(header: Text("DATABASE")) {
NavigationLink(destination: SettingsRefreshSelectionView(selectedInterval: $viewModel.refreshInterval), tag: 1, selection: $refreshAction) {
HStack {
Text("Refresh Interval")
Spacer()
Text(verbatim: self.viewModel.refreshInterval.description()).foregroundColor(.secondary)
}
}
.modifier(VibrantSelectAction(action: {
self.refreshAction = 1
}))
NavigationLink(destination: SettingsSubscriptionsImportAccountPickerView(), tag: 1, selection: $importOPMLAction) {
Text("Import Subscriptions")
}
.modifier(VibrantSelectAction(action: {
self.importOPMLAction = 1
}))
NavigationLink(destination: SettingsSubscriptionsExportAccountPickerView(), tag: 1, selection: $exportOPMLAction) {
Text("Export Subscriptions")
}
.modifier(VibrantSelectAction(action: {
self.exportOPMLAction = 1
}))
}
}
func buildAboutSection() -> some View {
Section(header: Text("ABOUT"), footer: buildFooter()) {
NavigationLink(destination: SettingsAboutView(viewModel: SettingsAboutView.ViewModel()), tag: 1, selection: $aboutAction) {
Text("About NetNewsWire")
}
.modifier(VibrantSelectAction(action: {
self.aboutAction = 1
}))
Button(action: {
self.isWebsitePresented.toggle()
self.website = "https://ranchero.com/netnewswire/"
}) {
Text("Website")
}
Button(action: {
self.isWebsitePresented.toggle()
self.website = "https://github.com/brentsimmons/NetNewsWire"
}) {
Text("Github Repository")
}
Button(action: {
self.isWebsitePresented.toggle()
self.website = "https://github.com/brentsimmons/NetNewsWire/issues"
}) {
Text("Bug Tracker")
}
Button(action: {
self.isWebsitePresented.toggle()
self.website = "https://github.com/brentsimmons/NetNewsWire/tree/master/Technotes"
}) {
Text("Technotes")
}
Button(action: {
self.isWebsitePresented.toggle()
self.website = "https://github.com/brentsimmons/NetNewsWire/blob/master/Technotes/HowToSupportNetNewsWire.markdown"
}) {
Text("How To Support NetNewsWire")
}
if !AccountManager.shared.anyAccountHasFeedWithURL("https://nnw.ranchero.com/feed.json") {
Button(action: {
self.viewController?.dismiss(animated: true) {
let feedName = NSLocalizedString("NetNewsWire News", comment: "NetNewsWire News")
self.coordinator?.showAdd(.feed, initialFeed: "https://nnw.ranchero.com/feed.json", initialFeedName: feedName)
}
}) {
Text("Add NetNewsWire News Feed")
}
}
}.sheet(isPresented: $isWebsitePresented) {
SafariView(url: URL(string: self.website!)!)
}
}
func buildFooter() -> some View {
return Text(verbatim: "\(Bundle.main.appName) v \(Bundle.main.versionNumber) (Build \(Bundle.main.buildNumber))")
.font(.footnote)
.foregroundColor(.secondary)
}
// MARK: ViewModel
class ViewModel: ObservableObject {
let objectWillChange = ObservableObjectPublisher()
init() {
NotificationCenter.default.addObserver(self, selector: #selector(accountsDidChange(_:)), name: .UserDidAddAccount, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(accountsDidChange(_:)), name: .UserDidDeleteAccount, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(displayNameDidChange(_:)), name: .DisplayNameDidChange, object: nil)
}
var accounts: [Account] {
get {
return AccountManager.shared.sortedAccounts
}
set {
}
}
var activeAccounts: [Account] {
get {
return AccountManager.shared.sortedActiveAccounts
}
set {
}
}
var sortOldestToNewest: Bool {
get {
return AppDefaults.timelineSortDirection == .orderedDescending
}
set {
objectWillChange.send()
if newValue == true {
AppDefaults.timelineSortDirection = .orderedDescending
} else {
AppDefaults.timelineSortDirection = .orderedAscending
}
}
}
var groupByFeed: Bool {
get {
return AppDefaults.timelineGroupByFeed
}
set {
objectWillChange.send()
AppDefaults.timelineGroupByFeed = newValue
}
}
var timelineNumberOfLines: Int {
get {
return AppDefaults.timelineNumberOfLines
}
set {
objectWillChange.send()
AppDefaults.timelineNumberOfLines = newValue
}
}
var refreshInterval: RefreshInterval {
get {
return AppDefaults.refreshInterval
}
set {
objectWillChange.send()
AppDefaults.refreshInterval = newValue
}
}
@objc func accountsDidChange(_ notification: Notification) {
objectWillChange.send()
}
@objc func displayNameDidChange(_ notification: Notification) {
objectWillChange.send()
}
}
}
#if DEBUG
struct SettingsView_Previews : PreviewProvider {
static var previews: some View {
SettingsView(viewModel: SettingsView.ViewModel())
}
}
#endif

View File

@ -0,0 +1,266 @@
//
// SettingsViewController.swift
// NetNewsWire-iOS
//
// Created by Maurice Parker on 4/24/19.
// Copyright © 2019 Ranchero Software. All rights reserved.
//
import UIKit
import Account
class SettingsViewController: UITableViewController {
static let preferredContentSizeForFormSheetDisplay = CGSize(width: 460.0, height: 400.0)
@IBOutlet weak var refreshIntervalLabel: UILabel!
@IBOutlet weak var timelineSortOrderSwitch: UISwitch!
@IBOutlet weak var timelineNumberOfLinesLabel: UILabel!
weak var presentingParentController: UIViewController?
override func viewDidLoad() {
// This hack mostly works around a bug in static tables with dynamic type. See: https://spin.atomicobject.com/2018/10/15/dynamic-type-static-uitableview/
NotificationCenter.default.removeObserver(tableView!, name: UIContentSizeCategory.didChangeNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChange), name: UIContentSizeCategory.didChangeNotification, object: nil)
tableView.register(UINib(nibName: "SettingsTableViewCell", bundle: nil), forCellReuseIdentifier: "SettingsTableViewCell")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if AppDefaults.timelineSortDirection == .orderedAscending {
timelineSortOrderSwitch.isOn = true
} else {
timelineSortOrderSwitch.isOn = false
}
refreshIntervalLabel.text = AppDefaults.refreshInterval.description()
let numberOfLinesText = NSLocalizedString(" lines", comment: "Lines")
timelineNumberOfLinesLabel.text = "\(AppDefaults.timelineNumberOfLines)" + numberOfLinesText
let buildLabel = NonIntrinsicLabel(frame: CGRect(x: 20.0, y: 0.0, width: 0.0, height: 0.0))
buildLabel.font = UIFont.systemFont(ofSize: 11.0)
buildLabel.textColor = UIColor.gray
buildLabel.text = "\(Bundle.main.appName) v \(Bundle.main.versionNumber) (Build \(Bundle.main.buildNumber))"
buildLabel.sizeToFit()
buildLabel.translatesAutoresizingMaskIntoConstraints = false
tableView.tableFooterView = buildLabel
tableView.reloadData()
}
// MARK: UITableView
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch section {
case 0:
return AccountManager.shared.accounts.count + 1
case 1:
let defaultNumberOfRows = super.tableView(tableView, numberOfRowsInSection: section)
if AccountManager.shared.activeAccounts.isEmpty {
// Hide the add NetNewsWire feed row if they don't have any active accounts
return defaultNumberOfRows - 1
}
return defaultNumberOfRows
default:
return super.tableView(tableView, numberOfRowsInSection: section)
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: UITableViewCell
switch indexPath.section {
case 0:
cell = tableView.dequeueReusableCell(withIdentifier: "SettingsTableViewCell", for: indexPath)
cell.textLabel?.adjustsFontForContentSizeCategory = true
let sortedAccounts = AccountManager.shared.sortedAccounts
if indexPath.row == sortedAccounts.count {
cell.textLabel?.text = NSLocalizedString("Add Account", comment: "Accounts")
} else {
cell.textLabel?.text = sortedAccounts[indexPath.row].nameForDisplay
}
default:
cell = super.tableView(tableView, cellForRowAt: indexPath)
}
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
switch indexPath.section {
case 0:
let sortedAccounts = AccountManager.shared.sortedAccounts
if indexPath.row == sortedAccounts.count {
let controller = UIStoryboard.settings.instantiateController(ofType: AddAccountViewController.self)
self.navigationController?.pushViewController(controller, animated: true)
} else {
let controller = UIStoryboard.settings.instantiateController(ofType: DetailAccountViewController.self)
controller.account = sortedAccounts[indexPath.row]
self.navigationController?.pushViewController(controller, animated: true)
}
case 1:
switch indexPath.row {
case 0:
let timeline = UIStoryboard.settings.instantiateController(ofType: AboutViewController.self)
self.navigationController?.pushViewController(timeline, animated: true)
case 1:
UIApplication.shared.open(URL(string: "https://ranchero.com/netnewswire/")!, options: [:])
case 2:
UIApplication.shared.open(URL(string: "https://github.com/brentsimmons/NetNewsWire")!, options: [:])
case 3:
UIApplication.shared.open(URL(string: "https://github.com/brentsimmons/NetNewsWire/issues")!, options: [:])
case 4:
UIApplication.shared.open(URL(string: "https://github.com/brentsimmons/NetNewsWire/tree/master/Technotes")!, options: [:])
case 5:
addFeed()
default:
UIApplication.shared.open(URL(string: "https://ranchero.com/netnewswire/")!, options: [:])
}
case 2:
if indexPath.row == 1 {
let timeline = UIStoryboard.settings.instantiateController(ofType: TimelineNumberOfLinesViewController.self)
self.navigationController?.pushViewController(timeline, animated: true)
}
case 3:
switch indexPath.row {
case 0:
let timeline = UIStoryboard.settings.instantiateController(ofType: RefreshIntervalViewController.self)
self.navigationController?.pushViewController(timeline, animated: true)
case 1:
importOPML()
case 2:
exportOPML()
default:
print("export")
}
default:
break
}
tableView.selectRow(at: nil, animated: true, scrollPosition: .none)
}
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return false
}
override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
return false
}
override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
return .none
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.section == 0 {
return super.tableView(tableView, heightForRowAt: IndexPath(row: 0, section: 0))
} else {
return super.tableView(tableView, heightForRowAt: indexPath)
}
}
override func tableView(_ tableView: UITableView, indentationLevelForRowAt indexPath: IndexPath) -> Int {
if indexPath.section == 0 {
return super.tableView(tableView, indentationLevelForRowAt: IndexPath(row: 0, section: 0))
} else {
return super.tableView(tableView, indentationLevelForRowAt: indexPath)
}
}
// MARK: Actions
@IBAction func done(_ sender: Any) {
dismiss(animated: true)
}
@IBAction func switchTimelineOrder(_ sender: Any) {
if timelineSortOrderSwitch.isOn {
AppDefaults.timelineSortDirection = .orderedAscending
} else {
AppDefaults.timelineSortDirection = .orderedDescending
}
}
@objc func contentSizeCategoryDidChange() {
tableView.reloadData()
}
}
// MARK: OPML Document Picker
extension SettingsViewController: UIDocumentPickerDelegate {
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
for url in urls {
AccountManager.shared.defaultAccount.importOPML(url) { result in}
}
}
}
// MARK: Private
private extension SettingsViewController {
func addFeed() {
let appNewsURLString = "https://nnw.ranchero.com/feed.json"
if AccountManager.shared.anyAccountHasFeedWithURL(appNewsURLString) {
presentError(title: "Subscribe", message: "You are already subscribed to the NetNewsWire news feed.")
return
}
self.dismiss(animated: true)
let addNavViewController = UIStoryboard.add.instantiateInitialViewController() as! UINavigationController
let addViewController = addNavViewController.topViewController as! AddContainerViewController
addNavViewController.modalPresentationStyle = .formSheet
addNavViewController.preferredContentSize = AddContainerViewController.preferredContentSizeForFormSheetDisplay
addViewController.initialFeed = appNewsURLString
addViewController.initialFeedName = "NetNewsWire News"
presentingParentController?.present(addNavViewController, animated: true)
}
func importOPML() {
let docPicker = UIDocumentPickerViewController(documentTypes: ["public.xml", "org.opml.opml"], in: .import)
docPicker.delegate = self
docPicker.modalPresentationStyle = .formSheet
self.present(docPicker, animated: true)
}
func exportOPML() {
let filename = "MySubscriptions.opml"
let tempFile = FileManager.default.temporaryDirectory.appendingPathComponent(filename)
let opmlString = OPMLExporter.OPMLString(with: AccountManager.shared.defaultAccount, title: filename)
do {
try opmlString.write(to: tempFile, atomically: true, encoding: String.Encoding.utf8)
} catch {
self.presentError(title: "OPML Export Error", message: error.localizedDescription)
}
let docPicker = UIDocumentPickerViewController(url: tempFile, in: .exportToService)
docPicker.modalPresentationStyle = .formSheet
self.present(docPicker, animated: true)
}
}

View File

@ -0,0 +1,41 @@
//
// TimelineNumberOfLinesViewController.swift
// NetNewsWire-iOS
//
// Created by Maurice Parker on 4/29/19.
// Copyright © 2019 Ranchero Software. All rights reserved.
//
import UIKit
class TimelineNumberOfLinesViewController: UITableViewController {
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.textLabel?.adjustsFontForContentSizeCategory = true
cell.textLabel?.text = "\(2 + indexPath.row)" + NSLocalizedString(" lines", comment: "Lines")
let numberOfLines = AppDefaults.timelineNumberOfLines
if indexPath.row + 2 == numberOfLines {
cell.accessoryType = .checkmark
} else {
cell.accessoryType = .none
}
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
AppDefaults.timelineNumberOfLines = indexPath.row + 2
self.navigationController?.popViewController(animated: true)
}
}

View File

@ -10,6 +10,8 @@ import UIKit
extension UIStoryboard {
static let preferredContentSizeForFormSheetDisplay = CGSize(width: 460.0, height: 400.0)
static var main: UIStoryboard {
return UIStoryboard(name: "Main", bundle: nil)
}