diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index cfb318ad1..dcaaca48f 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -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 = ""; }; - 510D707322B028E1004E8F65 /* SettingsAddAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAddAccountView.swift; sourceTree = ""; }; - 510D707D22B02A4B004E8F65 /* SettingsLocalAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsLocalAccountView.swift; sourceTree = ""; }; - 510D707F22B02A5F004E8F65 /* SettingsFeedbinAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsFeedbinAccountView.swift; sourceTree = ""; }; - 510D708122B041CC004E8F65 /* SettingsAccountLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAccountLabelView.swift; sourceTree = ""; }; 51102164233A7D6C0007A5F7 /* ArticleExtractorButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleExtractorButton.swift; sourceTree = ""; }; 51121AA12265430A00BC0EC1 /* NetNewsWire_iOSapp_target.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = NetNewsWire_iOSapp_target.xcconfig; sourceTree = ""; }; 51121B5A22661FEF00BC0EC1 /* AddContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddContainerViewController.swift; sourceTree = ""; }; @@ -754,14 +747,10 @@ 513146B1235A81A400387FDC /* AddFeedIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddFeedIntentHandler.swift; sourceTree = ""; }; 51314706235C41FC00387FDC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; name = Base; path = Base.lproj/Intents.intentdefinition; sourceTree = ""; }; 51314714235C420900387FDC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Intents.strings; sourceTree = ""; }; - 51314715235C862200387FDC /* SettingsSubscriptionsImportAccountPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSubscriptionsImportAccountPickerView.swift; sourceTree = ""; }; - 51314717235C89ED00387FDC /* SettingsSubscriptionsExportAccountPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSubscriptionsExportAccountPickerView.swift; sourceTree = ""; }; 51322854232EED360033D4ED /* VibrantSelectAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VibrantSelectAction.swift; sourceTree = ""; }; 51322858232FDDB80033D4ED /* VibrantButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VibrantButtonStyle.swift; sourceTree = ""; }; - 5132285A232FF2C40033D4ED /* SettingsRefreshSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsRefreshSelectionView.swift; sourceTree = ""; }; 513228F2233037620033D4ED /* Reachability.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Reachability.swift; sourceTree = ""; }; 513229302330523F0033D4ED /* AttributedStringView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributedStringView.swift; sourceTree = ""; }; - 5132293A23305D4C0033D4ED /* SettingsAboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAboutView.swift; sourceTree = ""; }; 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 = ""; }; 513C5CEB232571C2003D4054 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; }; @@ -803,11 +792,19 @@ 51934CC1230F5963006127BE /* ThemedNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemedNavigationController.swift; sourceTree = ""; }; 51934CCD2310792F006127BE /* ActivityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityManager.swift; sourceTree = ""; }; 51938DF1231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchTimelineFeedDelegate.swift; sourceTree = ""; }; - 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 = ""; }; + 51A1698D235E10D600EB091F /* RefreshIntervalViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RefreshIntervalViewController.swift; sourceTree = ""; }; + 51A1698E235E10D600EB091F /* SettingsTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SettingsTableViewCell.xib; sourceTree = ""; }; + 51A1698F235E10D600EB091F /* AddLocalAccountViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddLocalAccountViewController.swift; sourceTree = ""; }; + 51A16990235E10D600EB091F /* Settings.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Settings.storyboard; sourceTree = ""; }; + 51A16991235E10D600EB091F /* DetailAccountViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetailAccountViewController.swift; sourceTree = ""; }; + 51A16992235E10D600EB091F /* AddAccountViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddAccountViewController.swift; sourceTree = ""; }; + 51A16993235E10D600EB091F /* SettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; + 51A16994235E10D600EB091F /* TimelineNumberOfLinesViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimelineNumberOfLinesViewController.swift; sourceTree = ""; }; + 51A16995235E10D600EB091F /* AboutViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = ""; }; + 51A16996235E10D700EB091F /* FeedbinAccountViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedbinAccountViewController.swift; sourceTree = ""; }; 51AF460D232488C6001742EF /* Account-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Account-Extensions.swift"; sourceTree = ""; }; 51B62E67233186730085F949 /* MasterTimelineAvatarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterTimelineAvatarView.swift; sourceTree = ""; }; 51BB7C262335A8E5008E8144 /* ArticleActivityItemSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleActivityItemSource.swift; sourceTree = ""; }; @@ -852,8 +849,6 @@ 51EF0F8D2279C9260050506E /* AccountsAdd.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AccountsAdd.xib; sourceTree = ""; }; 51EF0F8F2279C9500050506E /* AccountsAddViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsAddViewController.swift; sourceTree = ""; }; 51EF0F912279CA620050506E /* AccountsAddTableCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsAddTableCellView.swift; sourceTree = ""; }; - 51F35D0822AFD4760003CE1B /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; - 51F772EC22B2789B0087D9D1 /* SettingsDetailAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsDetailAccountView.swift; sourceTree = ""; }; 51F85BEA22724CB600C787DC /* About.rtf */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; path = About.rtf; sourceTree = ""; }; 51F85BEC227251DF00C787DC /* Acknowledgments.rtf */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; path = Acknowledgments.rtf; sourceTree = ""; }; 51F85BEE2272520B00C787DC /* Thanks.rtf */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; path = Thanks.rtf; sourceTree = ""; }; @@ -871,7 +866,6 @@ 51FD40BD2341555600880194 /* UIImage-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage-Extensions.swift"; sourceTree = ""; }; 51FD413A2342BD0500880194 /* MasterTimelineUnreadCountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterTimelineUnreadCountView.swift; sourceTree = ""; }; 51FE10022345529D0056195D /* UserNotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserNotificationManager.swift; sourceTree = ""; }; - 557EE1A522B6F4E1004206FA /* SettingsReaderAPIAccountView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsReaderAPIAccountView.swift; sourceTree = ""; }; 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 = ""; }; @@ -1265,19 +1259,6 @@ name = Products; sourceTree = ""; }; - 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 = ""; - }; 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 = ""; @@ -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; }; diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index 7d9598c8c..e4ac6208e 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -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() { diff --git a/iOS/Settings/AboutViewController.swift b/iOS/Settings/AboutViewController.swift new file mode 100644 index 000000000..31da63e9e --- /dev/null +++ b/iOS/Settings/AboutViewController.swift @@ -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) + } + +} diff --git a/iOS/Settings/Account/SettingsAccountLabelView.swift b/iOS/Settings/Account/SettingsAccountLabelView.swift deleted file mode 100644 index 0d5ead2e3..000000000 --- a/iOS/Settings/Account/SettingsAccountLabelView.swift +++ /dev/null @@ -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 diff --git a/iOS/Settings/Account/SettingsAddAccountView.swift b/iOS/Settings/Account/SettingsAddAccountView.swift deleted file mode 100644 index f26769971..000000000 --- a/iOS/Settings/Account/SettingsAddAccountView.swift +++ /dev/null @@ -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 diff --git a/iOS/Settings/Account/SettingsDetailAccountView.swift b/iOS/Settings/Account/SettingsDetailAccountView.swift deleted file mode 100644 index f67b29833..000000000 --- a/iOS/Settings/Account/SettingsDetailAccountView.swift +++ /dev/null @@ -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 diff --git a/iOS/Settings/Account/SettingsFeedbinAccountView.swift b/iOS/Settings/Account/SettingsFeedbinAccountView.swift deleted file mode 100644 index a685a8ada..000000000 --- a/iOS/Settings/Account/SettingsFeedbinAccountView.swift +++ /dev/null @@ -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 diff --git a/iOS/Settings/Account/SettingsLocalAccountView.swift b/iOS/Settings/Account/SettingsLocalAccountView.swift deleted file mode 100644 index d92cd6f9b..000000000 --- a/iOS/Settings/Account/SettingsLocalAccountView.swift +++ /dev/null @@ -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 diff --git a/iOS/Settings/Account/SettingsReaderAPIAccountView.swift b/iOS/Settings/Account/SettingsReaderAPIAccountView.swift deleted file mode 100644 index 9e4218e9a..000000000 --- a/iOS/Settings/Account/SettingsReaderAPIAccountView.swift +++ /dev/null @@ -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 diff --git a/iOS/Settings/AddAccountViewController.swift b/iOS/Settings/AddAccountViewController.swift new file mode 100644 index 000000000..d1587758a --- /dev/null +++ b/iOS/Settings/AddAccountViewController.swift @@ -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) + } + +} diff --git a/iOS/Settings/AddLocalAccountViewController.swift b/iOS/Settings/AddLocalAccountViewController.swift new file mode 100644 index 000000000..c89569eaf --- /dev/null +++ b/iOS/Settings/AddLocalAccountViewController.swift @@ -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 + } + +} diff --git a/iOS/Settings/DetailAccountViewController.swift b/iOS/Settings/DetailAccountViewController.swift new file mode 100644 index 000000000..6f8c45636 --- /dev/null +++ b/iOS/Settings/DetailAccountViewController.swift @@ -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 + } + +} diff --git a/iOS/Settings/FeedbinAccountViewController.swift b/iOS/Settings/FeedbinAccountViewController.swift new file mode 100644 index 000000000..6a8ba2129 --- /dev/null +++ b/iOS/Settings/FeedbinAccountViewController.swift @@ -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 + } + +} diff --git a/iOS/Settings/RefreshIntervalViewController.swift b/iOS/Settings/RefreshIntervalViewController.swift new file mode 100644 index 000000000..8f3d89ba9 --- /dev/null +++ b/iOS/Settings/RefreshIntervalViewController.swift @@ -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) + + } + +} diff --git a/iOS/Settings/Settings.storyboard b/iOS/Settings/Settings.storyboard new file mode 100644 index 000000000..3dfa1b16a --- /dev/null +++ b/iOS/Settings/Settings.storyboard @@ -0,0 +1,1003 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOS/Settings/SettingsAboutView.swift b/iOS/Settings/SettingsAboutView.swift deleted file mode 100644 index e76fce4ec..000000000 --- a/iOS/Settings/SettingsAboutView.swift +++ /dev/null @@ -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()) - } -} diff --git a/iOS/Settings/SettingsRefreshSelectionView.swift b/iOS/Settings/SettingsRefreshSelectionView.swift deleted file mode 100644 index 22fcce129..000000000 --- a/iOS/Settings/SettingsRefreshSelectionView.swift +++ /dev/null @@ -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)) - } - } - } - -} diff --git a/iOS/Settings/SettingsSubscriptionsExportAccountPickerView.swift b/iOS/Settings/SettingsSubscriptionsExportAccountPickerView.swift deleted file mode 100644 index 33bc3ea40..000000000 --- a/iOS/Settings/SettingsSubscriptionsExportAccountPickerView.swift +++ /dev/null @@ -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) - } - -} diff --git a/iOS/Settings/SettingsSubscriptionsExportDocumentPickerView.swift b/iOS/Settings/SettingsSubscriptionsExportDocumentPickerView.swift deleted file mode 100644 index a471c7d27..000000000 --- a/iOS/Settings/SettingsSubscriptionsExportDocumentPickerView.swift +++ /dev/null @@ -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) -> 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) { - // - } - -} diff --git a/iOS/Settings/SettingsSubscriptionsImportAccountPickerView.swift b/iOS/Settings/SettingsSubscriptionsImportAccountPickerView.swift deleted file mode 100644 index 0ce3fbdc6..000000000 --- a/iOS/Settings/SettingsSubscriptionsImportAccountPickerView.swift +++ /dev/null @@ -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) - } - -} diff --git a/iOS/Settings/SettingsSubscriptionsImportDocumentPickerView.swift b/iOS/Settings/SettingsSubscriptionsImportDocumentPickerView.swift deleted file mode 100644 index 4f4043a92..000000000 --- a/iOS/Settings/SettingsSubscriptionsImportDocumentPickerView.swift +++ /dev/null @@ -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) -> UIDocumentPickerViewController { - let docPicker = UIDocumentPickerViewController(documentTypes: ["public.xml", "org.opml.opml"], in: .import) - docPicker.delegate = context.coordinator - return docPicker - } - - func updateUIViewController(_ uiViewController: UIDocumentPickerViewController, context: UIViewControllerRepresentableContext) { - // - } - - 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} - } - } - - } -} diff --git a/iOS/Settings/SettingsTableViewCell.xib b/iOS/Settings/SettingsTableViewCell.xib new file mode 100644 index 000000000..aca59947e --- /dev/null +++ b/iOS/Settings/SettingsTableViewCell.xib @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOS/Settings/SettingsView.swift b/iOS/Settings/SettingsView.swift deleted file mode 100644 index 40b6929f0..000000000 --- a/iOS/Settings/SettingsView.swift +++ /dev/null @@ -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 diff --git a/iOS/Settings/SettingsViewController.swift b/iOS/Settings/SettingsViewController.swift new file mode 100644 index 000000000..84a177d27 --- /dev/null +++ b/iOS/Settings/SettingsViewController.swift @@ -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) + + } + +} diff --git a/iOS/Settings/TimelineNumberOfLinesViewController.swift b/iOS/Settings/TimelineNumberOfLinesViewController.swift new file mode 100644 index 000000000..5c0453b2e --- /dev/null +++ b/iOS/Settings/TimelineNumberOfLinesViewController.swift @@ -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) + } + +} diff --git a/iOS/UIKit Extensions/UIStoryboard-Extensions.swift b/iOS/UIKit Extensions/UIStoryboard-Extensions.swift index f0b67d6ff..4d309a729 100644 --- a/iOS/UIKit Extensions/UIStoryboard-Extensions.swift +++ b/iOS/UIKit Extensions/UIStoryboard-Extensions.swift @@ -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) }