diff --git a/Podfile b/Podfile index a36fb19aea95..620e45c22a80 100644 --- a/Podfile +++ b/Podfile @@ -43,9 +43,9 @@ def wordpress_ui end def wordpress_kit - pod 'WordPressKit', '~> 4.9.0-beta.1' + pod 'WordPressKit', '~> 4.9.0-beta.2' #pod 'WordPressKit', :git => 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', :tag => '' - #pod 'WordPressKit', :git => 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', :branch => '' + #pod 'WordPressKit', :git => 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', :branch => 'feature/homepage-settings' #pod 'WordPressKit', :git => 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', :commit => '' #pod 'WordPressKit', :path => '../WordPressKit-iOS' end diff --git a/Podfile.lock b/Podfile.lock index 45ba8be6b8f3..474a8e2840fc 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -392,7 +392,7 @@ PODS: - WordPressKit (~> 4.9.0-beta.1) - WordPressShared (~> 1.8.16) - WordPressUI (~> 1.7.0) - - WordPressKit (4.9.0-beta.1): + - WordPressKit (4.9.0-beta.2): - Alamofire (~> 4.8.0) - CocoaLumberjack (~> 3.4) - NSObject-SafeExpectations (= 0.0.4) @@ -484,7 +484,7 @@ DEPENDENCIES: - SVProgressHUD (= 2.2.5) - WordPress-Editor-iOS (~> 1.19.1) - WordPressAuthenticator (~> 1.17.0-beta.7) - - WordPressKit (~> 4.9.0-beta.1) + - WordPressKit (~> 4.9.0-beta.2) - WordPressMocks (~> 0.0.8) - WordPressShared (~> 1.8.16) - WordPressUI (~> 1.7.0) @@ -709,7 +709,7 @@ SPEC CHECKSUMS: WordPress-Aztec-iOS: 25a9cbe204a22dd6d540d66d90b8a889421e0b42 WordPress-Editor-iOS: 9f3d720086b90fd8b73f8eccd744c15abbdb7607 WordPressAuthenticator: aa6c0a219c17cdfdb19fe6c2a1859b2577857cd5 - WordPressKit: e8aab0ace717c9b9be307ccfdda08821f31b655c + WordPressKit: e4985f2f9834ea67d1f88e114564103e0d6f94c9 WordPressMocks: b4064b99a073117bbc304abe82df78f2fbe60992 WordPressShared: 1bc316ed162f42af4e0fa2869437e9e28b532b01 WordPressUI: 1cf47a3b78154faf69caa18569ee7ece1e510fa0 @@ -725,6 +725,6 @@ SPEC CHECKSUMS: ZendeskSupportSDK: a87ab1e4badace92c75eb11dc77ede1e995b2adc ZIPFoundation: 249fa8890597086cd536bb2df5c9804d84e122b0 -PODFILE CHECKSUM: 163eb7f9c122cdb34d0147af94628a7bb40f62d8 +PODFILE CHECKSUM: 101e0cc713d0978244471453c85f8c85d0408049 COCOAPODS: 1.8.4 diff --git a/WordPress/Classes/Models/Blog+HomepageSettings.swift b/WordPress/Classes/Models/Blog+HomepageSettings.swift new file mode 100644 index 000000000000..dff01d848e04 --- /dev/null +++ b/WordPress/Classes/Models/Blog+HomepageSettings.swift @@ -0,0 +1,80 @@ +import Foundation + +enum HomepageType: String { + case page + case posts + + var title: String { + switch self { + case .page: + return NSLocalizedString("Static Homepage", comment: "Name of setting configured when a site uses a static page as its homepage") + case .posts: + return NSLocalizedString("Classic Blog", comment: "Name of setting configured when a site uses a list of blog posts as its homepage") + } + } + + var remoteType: RemoteHomepageType { + switch self { + case .page: + return .page + case .posts: + return .posts + } + } +} + +extension Blog { + private enum OptionsKeys { + static let homepageType = "show_on_front" + static let homepageID = "page_on_front" + static let postsPageID = "page_for_posts" + } + + /// The type of homepage used for the site: blog posts, or static pages + /// + var homepageType: HomepageType? { + get { + guard let options = options, + !options.isEmpty, + let type = getOptionString(name: OptionsKeys.homepageType) + else { + return nil + } + + return HomepageType(rawValue: type) + } + set { + if let value = newValue?.rawValue { + setValue(value, forOption: OptionsKeys.homepageType) + } + } + } + + /// The ID of the page to use for the site's 'posts' page, + /// if `homepageType` is set to `.posts` + /// + var homepagePostsPageID: Int? { + guard let options = options, + !options.isEmpty, + let pageID = getOptionNumeric(name: OptionsKeys.postsPageID) + else { + return nil + } + + return pageID.intValue + } + + /// The ID of the page to use for the site's homepage, + /// if `homepageType` is set to `.page` + /// + var homepagePageID: Int? { + guard let options = options, + !options.isEmpty, + let pageID = getOptionNumeric(name: OptionsKeys.homepageID) + else { + return nil + } + + return pageID.intValue + } +} diff --git a/WordPress/Classes/Models/Blog+Jetpack.swift b/WordPress/Classes/Models/Blog+Jetpack.swift index 3e7a83ad34b5..b96f890f12c5 100644 --- a/WordPress/Classes/Models/Blog+Jetpack.swift +++ b/WordPress/Classes/Models/Blog+Jetpack.swift @@ -3,11 +3,11 @@ extension Blog { return getOptionValue(name) as? T } - private func getOptionString(name: String) -> String? { + func getOptionString(name: String) -> String? { return (getOption(name: name) as NSString?).map(String.init) } - private func getOptionNumeric(name: String) -> NSNumber? { + func getOptionNumeric(name: String) -> NSNumber? { switch getOptionValue(name) { case let numericValue as NSNumber: return numericValue diff --git a/WordPress/Classes/Models/Blog.h b/WordPress/Classes/Models/Blog.h index 0b5fc47471d8..911cd15262d5 100644 --- a/WordPress/Classes/Models/Blog.h +++ b/WordPress/Classes/Models/Blog.h @@ -70,7 +70,9 @@ typedef NS_ENUM(NSUInteger, BlogFeature) { /// Does the blog support deleting media? BlogFeatureMediaDeletion, /// Does the blog support Stock Photos feature (free photos library) - BlogFeatureStockPhotos + BlogFeatureStockPhotos, + /// Does the blog support setting the homepage type and pages? + BlogFeatureHomepageSettings }; typedef NS_ENUM(NSInteger, SiteVisibility) { @@ -195,6 +197,7 @@ typedef NS_ENUM(NSInteger, SiteVisibility) { - (BOOL)isPrivateAtWPCom; - (nullable NSArray *)sortedCategories; - (nullable id)getOptionValue:(NSString *) name; +- (void)setValue:(id)value forOption:(NSString *)name; - (NSString *)loginUrl; - (NSString *)urlWithPath:(NSString *)path; - (NSString *)adminUrlWithPath:(NSString *)path; diff --git a/WordPress/Classes/Models/Blog.m b/WordPress/Classes/Models/Blog.m index e7136af59f45..40ebb969791a 100644 --- a/WordPress/Classes/Models/Blog.m +++ b/WordPress/Classes/Models/Blog.m @@ -507,6 +507,8 @@ - (BOOL)supports:(BlogFeature)feature return [self supportsRestApi] && [self isAdmin]; case BlogFeatureMediaDeletion: return [self isAdmin]; + case BlogFeatureHomepageSettings: + return [self supportsRestApi] && [self isAdmin]; } } @@ -797,4 +799,20 @@ - (id)getOptionValue:(NSString *)name return optionValue; } +- (void)setValue:(id)value forOption:(NSString *)name +{ + [self.managedObjectContext performBlockAndWait:^{ + if ( self.options == nil || (self.options.count == 0) ) { + return; + } + + NSMutableDictionary *mutableOptions = [self.options mutableCopy]; + + NSDictionary *valueDict = @{ @"value": value }; + mutableOptions[name] = valueDict; + + self.options = [NSDictionary dictionaryWithDictionary:mutableOptions]; + }]; +} + @end diff --git a/WordPress/Classes/Services/HomepageSettingsService.swift b/WordPress/Classes/Services/HomepageSettingsService.swift new file mode 100644 index 000000000000..f8cfce3557fd --- /dev/null +++ b/WordPress/Classes/Services/HomepageSettingsService.swift @@ -0,0 +1,59 @@ +import Foundation +import WordPressKit + +/// Service allowing updating of homepage settings +/// +struct HomepageSettingsService { + public enum ResponseError: Error { + case decodingFailed + } + + let blog: Blog + + fileprivate let context: NSManagedObjectContext + fileprivate let remote: HomepageSettingsServiceRemote + fileprivate let siteID: Int + + init?(blog: Blog, context: NSManagedObjectContext) { + guard let api = blog.wordPressComRestApi(), let dotComID = blog.dotComID as? Int else { + return nil + } + + self.remote = HomepageSettingsServiceRemote(wordPressComRestApi: api) + self.siteID = dotComID + self.blog = blog + self.context = context + } + + public func setHomepageType(_ type: HomepageType, + withPostsPageID postsPageID: Int? = nil, + homePageID: Int? = nil, + success: @escaping () -> Void, + failure: @escaping (Error) -> Void) { + + let originalHomepageType = blog.homepageType + + switch type { + case .page: + blog.homepageType = .page + case .posts: + blog.homepageType = .posts + } + + ContextManager.sharedInstance().save(context) + + remote.setHomepageType(type: type.remoteType, + for: siteID, + withPostsPageID: postsPageID, + homePageID: homePageID, + success: success, + failure: { error in + self.context.performAndWait { + self.blog.homepageType = originalHomepageType + ContextManager.sharedInstance().saveContextAndWait(self.context) + } + + failure(error) + }) + } +} diff --git a/WordPress/Classes/Services/PostService.h b/WordPress/Classes/Services/PostService.h index 8c0989308a23..a10826da89a4 100644 --- a/WordPress/Classes/Services/PostService.h +++ b/WordPress/Classes/Services/PostService.h @@ -30,7 +30,7 @@ extern const NSUInteger PostServiceDefaultNumberToSync; - (Post *)createDraftPostForBlog:(Blog *)blog; - (Page *)createDraftPageForBlog:(Blog *)blog; -- (AbstractPost *)findPostWithID:(NSNumber *)postID inBlog:(Blog *)blog; +- (nullable AbstractPost *)findPostWithID:(NSNumber *)postID inBlog:(Blog *)blog; - (NSUInteger)countPostsWithoutRemote; diff --git a/WordPress/Classes/Utility/BuildInformation/FeatureFlag.swift b/WordPress/Classes/Utility/BuildInformation/FeatureFlag.swift index 5f1b002f3868..d7429ea0298c 100644 --- a/WordPress/Classes/Utility/BuildInformation/FeatureFlag.swift +++ b/WordPress/Classes/Utility/BuildInformation/FeatureFlag.swift @@ -11,6 +11,7 @@ enum FeatureFlag: Int, CaseIterable { case floatingCreateButton case newReaderNavigation case tenor + case homepageSettings /// Returns a boolean indicating if the feature is enabled var enabled: Bool { @@ -37,6 +38,8 @@ enum FeatureFlag: Int, CaseIterable { return true case .tenor: return BuildConfiguration.current ~= [.localDeveloper, .a8cBranchTest] + case .homepageSettings: + return BuildConfiguration.current == .localDeveloper } } } @@ -73,6 +76,8 @@ extension FeatureFlag: OverrideableFlag { return "New Reader Navigation" case .tenor: return "Tenor GIF media source" + case .homepageSettings: + return "Homepage Settings" } } diff --git a/WordPress/Classes/Utility/WPImmuTableRows.swift b/WordPress/Classes/Utility/WPImmuTableRows.swift index 05cc81a7cbf1..96ea1ba19de3 100644 --- a/WordPress/Classes/Utility/WPImmuTableRows.swift +++ b/WordPress/Classes/Utility/WPImmuTableRows.swift @@ -129,6 +129,30 @@ struct CheckmarkRow: ImmuTableRow { } +struct ActivityIndicatorRow: ImmuTableRow { + static let cell = ImmuTableCell.class(WPTableViewCellDefault.self) + + let title: String + let animating: Bool + let action: ImmuTableAction? + + func configureCell(_ cell: UITableViewCell) { + cell.textLabel?.text = title + + let indicator = UIActivityIndicatorView(style: .gray) + indicator.tintColor = WPStyleGuide.cellGridiconAccessoryColor() + + if animating { + indicator.startAnimating() + } + + cell.accessoryView = indicator + + WPStyleGuide.configureTableViewCell(cell) + } + +} + struct LinkRow: ImmuTableRow { static let cell = ImmuTableCell.class(WPTableViewCellDefault.self) diff --git a/WordPress/Classes/ViewRelated/Blog/Site Settings/HomepageSettingsViewController.swift b/WordPress/Classes/ViewRelated/Blog/Site Settings/HomepageSettingsViewController.swift new file mode 100644 index 000000000000..db28bc15b16b --- /dev/null +++ b/WordPress/Classes/ViewRelated/Blog/Site Settings/HomepageSettingsViewController.swift @@ -0,0 +1,175 @@ +import UIKit +import WordPressFlux +import WordPressShared + +@objc open class HomepageSettingsViewController: UITableViewController { + + /// Are we currently updating the homepage type? + fileprivate var updating: Bool = false + + fileprivate var postService: PostService! + + fileprivate lazy var handler: ImmuTableViewHandler = { + return ImmuTableViewHandler(takeOver: self) + }() + + /// Designated Initializer + /// + /// - Parameter blog: The blog for which we want to configure Homepage settings + /// + @objc public convenience init(blog: Blog) { + self.init(style: .grouped) + + self.blog = blog + + let context = blog.managedObjectContext ?? ContextManager.shared.mainContext + postService = PostService(managedObjectContext: context) + } + + open override func viewDidLoad() { + super.viewDidLoad() + + title = Strings.title + clearsSelectionOnViewWillAppear = false + + // Setup tableView + WPStyleGuide.configureColors(view: view, tableView: tableView) + WPStyleGuide.configureAutomaticHeightRows(for: tableView) + + ImmuTable.registerRows([CheckmarkRow.self, NavigationItemRow.self, ActivityIndicatorRow.self], tableView: tableView) + reloadViewModel() + + fetchAllPages() + } + + private func fetchAllPages() { + let options = PostServiceSyncOptions() + options.number = 20 + + postService.syncPosts(ofType: .page, with: options, for: blog, success: { [weak self] posts in + self?.reloadViewModel() + }, failure: { _ in + + }) + } + + // MARK: - Model + + fileprivate func reloadViewModel() { + handler.viewModel = tableViewModel + } + + fileprivate var tableViewModel: ImmuTable { + guard let homepageType = blog.homepageType else { + return ImmuTable(sections: []) + } + + let changeTypeSection = ImmuTableSection(headerText: nil, + rows: updating ? updatingHomepageTypeRows : homepageTypeRows, + footerText: Strings.footerText) + + let choosePagesSection = ImmuTableSection(headerText: Strings.choosePagesHeaderText, rows: selectedPagesRows) + + var sections = [changeTypeSection] + if homepageType == .page { + sections.append(choosePagesSection) + } + + return ImmuTable(sections: sections) + } + + var updatingHomepageTypeRows: [ImmuTableRow] { + guard let homepageType = blog.homepageType else { + return [] + } + + return [ + ActivityIndicatorRow(title: HomepageType.posts.title, animating: homepageType == .posts, action: nil), + ActivityIndicatorRow(title: HomepageType.page.title, animating: homepageType == .page, action: nil) + ] + } + + var homepageTypeRows: [ImmuTableRow] { + guard let homepageType = blog.homepageType else { + return [] + } + + return [ + CheckmarkRow(title: HomepageType.posts.title, checked: homepageType == .posts, action: { [weak self] _ in + self?.setHomepageType(.posts) + }), + CheckmarkRow(title: HomepageType.page.title, checked: homepageType == .page, action: { [weak self] _ in + self?.setHomepageType(.page) + }) + ] + } + + var selectedPagesRows: [ImmuTableRow] { + var homepageTitle = "" + var postsPageTitle = "" + + if let homepageID = blog.homepagePageID, + let homepage = postService.findPost(withID: NSNumber(value: homepageID), in: blog) { + homepageTitle = homepage.titleForDisplay() + } + + if let postsPageID = blog.homepagePostsPageID, + let postsPage = postService.findPost(withID: NSNumber(value: postsPageID), in: blog) { + postsPageTitle = postsPage.titleForDisplay() + } + + let selectPage: ImmuTableAction = { _ in + } + + let homepageRow = NavigationItemRow(title: Strings.homepage, detail: homepageTitle, action: selectPage) + let postsPageRow = NavigationItemRow(title: Strings.postsPage, detail: postsPageTitle, action: selectPage) + + return [homepageRow, postsPageRow] + } + + // MARK: - Updating Settings + + fileprivate func startUpdating() { + updating = true + } + + fileprivate func endUpdating() { + updating = false + reloadViewModel() + } + + fileprivate func setHomepageType(_ type: HomepageType) { + guard let blogType = blog.homepageType, + blogType != type, + updating == false else { + return + } + + startUpdating() + + let service = HomepageSettingsService(blog: blog, context: blog.managedObjectContext!) + service?.setHomepageType(type, success: { [weak self] in + self?.endUpdating() + }, failure: { [weak self] error in + self?.endUpdating() + + let notice = Notice(title: Strings.updateErrorTitle, message: Strings.updateErrorMessage, feedbackType: .error) + ActionDispatcher.global.dispatch(NoticeAction.post(notice)) + }) + + reloadViewModel() + } + + // MARK: - Private Properties + fileprivate var blog: Blog! + + fileprivate enum Strings { + static let title = NSLocalizedString("Homepage Settings", comment: "Title for the Homepage Settings screen") + static let footerText = NSLocalizedString("Choose from a homepage that displays your latest posts (classic blog) or a fixed / static page.", comment: "Explanatory text for Homepage Settings homepage type selection.") + static let homepage = NSLocalizedString("Homepage", comment: "Title for setting which shows the current page assigned as a site's homepage") + static let postsPage = NSLocalizedString("Posts Page", comment: "Title for setting which shows the current page assigned as a site's posts page") + static let choosePagesHeaderText = NSLocalizedString("Choose Pages", comment: "Title for settings section which allows user to select their home page and posts page") + static let updateErrorTitle = NSLocalizedString("Unable to update homepage settings", comment: "Error informing the user that their homepage settings could not be updated") + static let updateErrorMessage = NSLocalizedString("Please try again later.", comment: "Prompt for the user to retry a failed action again later") + } +} diff --git a/WordPress/Classes/ViewRelated/Blog/Site Settings/SiteSettingsViewController+Swift.swift b/WordPress/Classes/ViewRelated/Blog/Site Settings/SiteSettingsViewController+Swift.swift index e5c5a3bfda6c..659abee243a1 100644 --- a/WordPress/Classes/ViewRelated/Blog/Site Settings/SiteSettingsViewController+Swift.swift +++ b/WordPress/Classes/ViewRelated/Blog/Site Settings/SiteSettingsViewController+Swift.swift @@ -58,6 +58,21 @@ extension SiteSettingsViewController { } } + // MARK: - Homepage Settings + + @objc var homepageSettingsCell: SettingTableViewCell? { + let cell = SettingTableViewCell(label: NSLocalizedString("Homepage Settings", comment: "Label for Homepage Settings site settings section"), editable: true, reuseIdentifier: nil) + cell?.textValue = blog.homepageType?.title + return cell + } + + // MARK: - Navigation + + @objc(showHomepageSettingsForBlog:) func showHomepageSettings(for blog: Blog) { + let settingsViewController = HomepageSettingsViewController(blog: blog) + navigationController?.pushViewController(settingsViewController, animated: true) + } + @objc func showTimezoneSelector() { let controller = TimeZoneSelectorViewController(selectedValue: timezoneValue) { [weak self] (newValue) in self?.navigationController?.popViewController(animated: true) diff --git a/WordPress/Classes/ViewRelated/Blog/Site Settings/SiteSettingsViewController.m b/WordPress/Classes/ViewRelated/Blog/Site Settings/SiteSettingsViewController.m index 37497460a791..a694157e8d89 100644 --- a/WordPress/Classes/ViewRelated/Blog/Site Settings/SiteSettingsViewController.m +++ b/WordPress/Classes/ViewRelated/Blog/Site Settings/SiteSettingsViewController.m @@ -37,6 +37,11 @@ SiteSettingsAccountCount, }; +NS_ENUM(NSInteger, SiteSettingsHomepage) { + SiteSettingsHomepageSettings = 0, + SiteSettingsHomepageCount, +}; + NS_ENUM(NSInteger, SiteSettingsEditor) { SiteSettingsEditorSelector = 0, SiteSettingsEditorCount, @@ -68,6 +73,7 @@ NS_ENUM(NSInteger, SiteSettingsSection) { SiteSettingsSectionGeneral = 0, + SiteSettingsSectionHomepage, SiteSettingsSectionAccount, SiteSettingsSectionEditor, SiteSettingsSectionWriting, @@ -181,6 +187,10 @@ - (NSArray *)tableSections { NSMutableArray *sections = [NSMutableArray arrayWithObjects:@(SiteSettingsSectionGeneral), nil]; + if ([Feature enabled:FeatureFlagHomepageSettings] && [self.blog supports:BlogFeatureHomepageSettings]) { + [sections addObject:@(SiteSettingsSectionHomepage)]; + } + if (!self.blog.account) { [sections addObject:@(SiteSettingsSectionAccount)]; } @@ -236,6 +246,10 @@ - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger return rowCount; } + case SiteSettingsSectionHomepage: + { + return SiteSettingsHomepageCount; + } case SiteSettingsSectionAccount: { return SiteSettingsAccountCount; @@ -746,6 +760,9 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N case SiteSettingsSectionGeneral: return [self tableView:tableView cellForGeneralSettingsInRow:indexPath.row]; + case SiteSettingsSectionHomepage: + return self.homepageSettingsCell; + case SiteSettingsSectionAccount: return [self tableView:tableView cellForAccountSettingsInRow:indexPath.row]; @@ -801,6 +818,10 @@ - (NSString *)titleForHeaderInSection:(NSInteger)section headingTitle = NSLocalizedString(@"General", @"Title for the general section in site settings screen"); break; + case SiteSettingsSectionHomepage: + headingTitle = NSLocalizedString(@"Homepage", @"Title for the homepage section in site settings screen"); + break; + case SiteSettingsSectionAccount: headingTitle = NSLocalizedString(@"Account", @"Title for the account section in site settings screen"); break; @@ -1140,6 +1161,9 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath [self tableView:tableView didSelectInGeneralSectionRow:indexPath.row]; break; + case SiteSettingsSectionHomepage: + [self showHomepageSettingsForBlog:self.blog]; + case SiteSettingsSectionAccount: [self tableView:tableView didSelectInAccountSectionRow:indexPath.row]; break; diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index 563dc306681c..d5141ee27fd1 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -161,6 +161,7 @@ 1750BD6D201144DB0050F13A /* MediaNoticeNavigationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1750BD6C201144DB0050F13A /* MediaNoticeNavigationCoordinator.swift */; }; 1751E5911CE0E552000CA08D /* KeyValueDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1751E5901CE0E552000CA08D /* KeyValueDatabase.swift */; }; 1751E5931CE23801000CA08D /* NSAttributedString+StyledHTML.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1751E5921CE23801000CA08D /* NSAttributedString+StyledHTML.swift */; }; + 17523381246C4F9200870B4A /* HomepageSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17523380246C4F9200870B4A /* HomepageSettingsViewController.swift */; }; 1752D4F9238D702D002B79E7 /* KeyValueDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1751E5901CE0E552000CA08D /* KeyValueDatabase.swift */; }; 1752D4FA238D702E002B79E7 /* KeyValueDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1751E5901CE0E552000CA08D /* KeyValueDatabase.swift */; }; 1752D4FB238D702F002B79E7 /* KeyValueDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1751E5901CE0E552000CA08D /* KeyValueDatabase.swift */; }; @@ -206,6 +207,8 @@ 17D2FDC21C6A468A00944265 /* PlanComparisonViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D2FDC11C6A468A00944265 /* PlanComparisonViewController.swift */; }; 17D4153C22C2308D006378EF /* StatsPeriodHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D4153B22C2308D006378EF /* StatsPeriodHelper.swift */; }; 17D5C3F71FFCF2D800EB70FF /* MediaProgressCoordinatorNoticeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D5C3F61FFCF2D800EB70FF /* MediaProgressCoordinatorNoticeViewModel.swift */; }; + 17D9362324729FB6008B2205 /* Blog+HomepageSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D9362224729FB6008B2205 /* Blog+HomepageSettings.swift */; }; + 17D9362724769579008B2205 /* HomepageSettingsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D9362624769579008B2205 /* HomepageSettingsService.swift */; }; 17D975AF1EF7F6F100303D63 /* WPStyleGuide+Aztec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D975AE1EF7F6F100303D63 /* WPStyleGuide+Aztec.swift */; }; 17DC4C2E22C5E6910059CA11 /* open_source_dark_icon_20pt@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 17DC4C1F22C5E6910059CA11 /* open_source_dark_icon_20pt@2x.png */; }; 17DC4C2F22C5E6910059CA11 /* open_source_dark_icon_40pt@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 17DC4C2022C5E6910059CA11 /* open_source_dark_icon_40pt@3x.png */; }; @@ -2525,6 +2528,7 @@ 1750BD6C201144DB0050F13A /* MediaNoticeNavigationCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaNoticeNavigationCoordinator.swift; sourceTree = ""; }; 1751E5901CE0E552000CA08D /* KeyValueDatabase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyValueDatabase.swift; sourceTree = ""; }; 1751E5921CE23801000CA08D /* NSAttributedString+StyledHTML.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSAttributedString+StyledHTML.swift"; sourceTree = ""; }; + 17523380246C4F9200870B4A /* HomepageSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomepageSettingsViewController.swift; sourceTree = ""; }; 1759F16F1FE017BF0003EC81 /* Queue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Queue.swift; sourceTree = ""; }; 1759F1711FE017F20003EC81 /* QueueTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueTests.swift; sourceTree = ""; }; 1759F17F1FE1460C0003EC81 /* NoticeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticeView.swift; sourceTree = ""; }; @@ -2567,6 +2571,8 @@ 17D2FDC11C6A468A00944265 /* PlanComparisonViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlanComparisonViewController.swift; sourceTree = ""; }; 17D4153B22C2308D006378EF /* StatsPeriodHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsPeriodHelper.swift; sourceTree = ""; }; 17D5C3F61FFCF2D800EB70FF /* MediaProgressCoordinatorNoticeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaProgressCoordinatorNoticeViewModel.swift; sourceTree = ""; }; + 17D9362224729FB6008B2205 /* Blog+HomepageSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Blog+HomepageSettings.swift"; sourceTree = ""; }; + 17D9362624769579008B2205 /* HomepageSettingsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomepageSettingsService.swift; sourceTree = ""; }; 17D975AE1EF7F6F100303D63 /* WPStyleGuide+Aztec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "WPStyleGuide+Aztec.swift"; sourceTree = ""; }; 17DC4C1F22C5E6910059CA11 /* open_source_dark_icon_20pt@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "open_source_dark_icon_20pt@2x.png"; sourceTree = ""; }; 17DC4C2022C5E6910059CA11 /* open_source_dark_icon_40pt@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "open_source_dark_icon_40pt@3x.png"; sourceTree = ""; }; @@ -5530,7 +5536,7 @@ path = Networking; sourceTree = ""; }; - 29B97314FDCFA39411CA2CEA = { + 29B97314FDCFA39411CA2CEA /* CustomTemplate */ = { isa = PBXGroup; children = ( 98D31BBF239720E4009CFF43 /* MainInterface.storyboard */, @@ -6058,6 +6064,7 @@ FFDA7E4F1B8DF6E500B83C56 /* BlogSiteVisibilityHelper.m */, 821738081FE04A9E00BEC94C /* DateAndTimeFormatSettingsViewController.swift */, B5AC00671BE3C4E100F8E7C3 /* DiscussionSettingsViewController.swift */, + 17523380246C4F9200870B4A /* HomepageSettingsViewController.swift */, B54346951C6A707D0010B3AD /* LanguageViewController.swift */, 827704F01F607C0E002E8A03 /* JetpackConnectionViewController.swift */, 822D60B01F4C747E0016C46D /* JetpackSecuritySettingsViewController.swift */, @@ -7840,6 +7847,7 @@ E16A76F21FC4766900A661E3 /* CredentialsService.swift */, 1702BBDF1CF3034E00766A33 /* DomainsService.swift */, B5772AC31C9C7A070031F97E /* GravatarService.swift */, + 17D9362624769579008B2205 /* HomepageSettingsService.swift */, 9A2D0B35225E2511009E585F /* JetpackService.swift */, 1751E5901CE0E552000CA08D /* KeyValueDatabase.swift */, 930284B618EAF7B600CB0BF4 /* LocalCoreDataService.h */, @@ -8615,6 +8623,7 @@ 73713582208EA4B900CCDFC8 /* Blog+Files.swift */, B5EEDB961C91F10400676B2B /* Blog+Interface.swift */, E11C4B6F2010930B00A6619C /* Blog+Jetpack.swift */, + 17D9362224729FB6008B2205 /* Blog+HomepageSettings.swift */, B5D889401BEBE30A007C4684 /* BlogSettings.swift */, B55F1AA71C10936600FD04D4 /* BlogSettings+Discussion.swift */, 8217380A1FE05EE600BEC94C /* BlogSettings+DateAndTimeFormat.swift */, @@ -10834,7 +10843,7 @@ bg, sk, ); - mainGroup = 29B97314FDCFA39411CA2CEA; + mainGroup = 29B97314FDCFA39411CA2CEA /* CustomTemplate */; productRefGroup = 19C28FACFE9D520D11CA2CBB /* Products */; projectDirPath = ""; projectRoot = ""; @@ -11930,6 +11939,7 @@ 9A2B28F52192121400458F2A /* RevisionOperation.swift in Sources */, 984B4EF320742FCC00F87888 /* ZendeskUtils.swift in Sources */, F580C3CB23D8F9B40038E243 /* AbstractPost+Dates.swift in Sources */, + 17D9362324729FB6008B2205 /* Blog+HomepageSettings.swift in Sources */, 1E9D544D23C4C56300F6A9E0 /* GutenbergRollout.swift in Sources */, 17E24F5420FCF1D900BD70A3 /* Routes+MySites.swift in Sources */, 0828D7FA1E6E09AE00C7C7D4 /* WPAppAnalytics+Media.swift in Sources */, @@ -12611,12 +12621,14 @@ 7E4123BA20F4097B00DF8486 /* FormattableContentGroup.swift in Sources */, 08216FD21CDBF96000304BA7 /* MenuItemSourceViewController.m in Sources */, 74558369201A1FD3007809BB /* WPReusableTableViewCells.swift in Sources */, + 17D9362724769579008B2205 /* HomepageSettingsService.swift in Sources */, E6DAABDD1CF632EC0069D933 /* ReaderSearchSuggestionsViewController.swift in Sources */, 8B3DECAB2388506400A459C2 /* SentryStartupEvent.swift in Sources */, D8C31CC62188490000A33B35 /* SiteSegmentsCell.swift in Sources */, 98921EFB21372E57004949AA /* NewsService.swift in Sources */, E6431DE51C4E892900FD8D90 /* SharingConnectionsViewController.m in Sources */, 5D8D53F119250412003C8859 /* BlogSelectorViewController.m in Sources */, + 17523381246C4F9200870B4A /* HomepageSettingsViewController.swift in Sources */, C81CCD7F243BF7A600A83E27 /* TenorMediaGroup.swift in Sources */, 4388FF0020A4E19C00783948 /* NotificationsViewController+PushPrimer.swift in Sources */, 5D3D559718F88C3500782892 /* ReaderPostService.m in Sources */,