deep-linking-sample/Apps/Wikipedia/Wikipedia/Code/ExploreFeedSettingsViewController.swift
Javier Cicchelli 9bcdaa697b [Setup] Basic project structure (#1)
This PR contains all the work related to setting up this project as required to implement the [Assignment](https://repo.rock-n-code.com/rock-n-code/deep-linking-assignment/wiki/Assignment) on top, as intended.

To summarise this work:
- [x] created a new **Xcode** project;
- [x] cloned the `Wikipedia` app and inserted it into the **Xcode** project;
- [x] created the `Locations` app and also, its `Libraries` package;
- [x] created the `Shared` package to share dependencies between the apps;
- [x] added a `Makefile` file and implemented some **environment** and **help** commands.

Co-authored-by: Javier Cicchelli <javier@rock-n-code.com>
Reviewed-on: rock-n-code/deep-linking-assignment#1
2023-04-08 18:37:13 +00:00

337 lines
17 KiB
Swift

import UIKit
private class FeedCard: ExploreFeedSettingsItem {
let contentGroupKind: WMFContentGroupKind
let title: String
var subtitle: String?
let disclosureType: WMFSettingsMenuItemDisclosureType
var disclosureText: String? = nil
let iconName: String?
let iconColor: UIColor?
let iconBackgroundColor: UIColor?
var controlTag: Int = 0
var isOn: Bool = true
init(contentGroupKind: WMFContentGroupKind, displayType: ExploreFeedSettingsDisplayType) {
self.contentGroupKind = contentGroupKind
var singleLanguageDescription: String?
switch contentGroupKind {
case .news:
title = CommonStrings.inTheNewsTitle
singleLanguageDescription = WMFLocalizedString("explore-feed-preferences-in-the-news-description", value: "Articles about current events", comment: "Description of In the news section of Explore feed")
iconName = "in-the-news-mini"
iconColor = .gray400
iconBackgroundColor = .gray200
case .onThisDay:
title = CommonStrings.onThisDayTitle
singleLanguageDescription = WMFLocalizedString("explore-feed-preferences-on-this-day-description", value: "Events in history on this day", comment: "Description of On this day section of Explore feed")
iconName = "on-this-day-mini"
iconColor = .blue600
iconBackgroundColor = .blue100
case .featuredArticle:
title = CommonStrings.featuredArticleTitle
singleLanguageDescription = WMFLocalizedString("explore-feed-preferences-featured-article-description", value: "Daily featured article on Wikipedia", comment: "Description of Featured article section of Explore feed")
iconName = "featured-mini"
iconColor = .yellow600
iconBackgroundColor = .yellow600.withAlphaComponent(0.3)
case .topRead:
title = CommonStrings.topReadTitle
singleLanguageDescription = WMFLocalizedString("explore-feed-preferences-top-read-description", value: "Daily most read articles", comment: "Description of Top read section of Explore feed")
iconName = "trending-mini"
iconColor = .blue600
iconBackgroundColor = .blue100
case .location:
fallthrough
case .locationPlaceholder:
title = CommonStrings.placesTabTitle
singleLanguageDescription = WMFLocalizedString("explore-feed-preferences-places-description", value: "Wikipedia articles near your location", comment: "Description of Places section of Explore feed")
iconName = "nearby-mini"
iconColor = .green600
iconBackgroundColor = .green100
case .random:
title = CommonStrings.randomizerTitle
singleLanguageDescription = WMFLocalizedString("explore-feed-preferences-randomizer-description", value: "Generate random articles to read", comment: "Description of Randomizer section of Explore feed")
iconName = "random-mini"
iconColor = .red600
iconBackgroundColor = .red100
case .pictureOfTheDay:
title = CommonStrings.pictureOfTheDayTitle
singleLanguageDescription = WMFLocalizedString("explore-feed-preferences-potd-description", value: "Daily featured image from Commons", comment: "Description of Picture of the day section of Explore feed")
iconName = "potd-mini"
iconColor = .purple600
iconBackgroundColor = .purple600.withAlphaComponent(0.3)
case .continueReading:
title = CommonStrings.continueReadingTitle
singleLanguageDescription = WMFLocalizedString("explore-feed-preferences-continue-reading-description", value: "Quick link back to reading an open article", comment: "Description of Continue reading section of Explore feed")
iconName = "today-mini"
iconColor = .gray400
iconBackgroundColor = .gray200
case .relatedPages:
title = CommonStrings.relatedPagesTitle
singleLanguageDescription = WMFLocalizedString("explore-feed-preferences-related-pages-description", value: "Suggestions based on reading history", comment: "Description of Related pages section of Explore feed")
iconName = "recent-mini"
iconColor = .gray400
iconBackgroundColor = .gray200
default:
assertionFailure("Group of kind \(contentGroupKind) is not customizable")
title = ""
iconName = nil
iconColor = nil
iconBackgroundColor = nil
}
if displayType == .singleLanguage {
subtitle = singleLanguageDescription
disclosureType = .switch
controlTag = Int(contentGroupKind.rawValue)
isOn = contentGroupKind.isInFeed
} else {
disclosureType = .viewControllerWithDisclosureText
disclosureText = multipleLanguagesDisclosureText(for: contentGroupKind)
subtitle = multipleLanguagesSubtitle(for: contentGroupKind)
}
}
private func multipleLanguagesDisclosureText(for contentGroupKind: WMFContentGroupKind) -> String {
guard contentGroupKind.isGlobal else {
let preferredLanguages = MWKDataStore.shared().languageLinkController.preferredLanguages
let contentLanguageCodes = contentGroupKind.contentLanguageCodes
switch contentLanguageCodes.count {
case preferredLanguages.count:
return CommonStrings.onAllTitle
case 1...:
return CommonStrings.onTitle(contentLanguageCodes.count)
default:
return CommonStrings.offTitle
}
}
if contentGroupKind.isInFeed {
return CommonStrings.onTitle
} else {
return CommonStrings.offTitle
}
}
func updateIsOn(for displayType: ExploreFeedSettingsDisplayType) {
guard displayType == .singleLanguage else {
return
}
isOn = contentGroupKind.isInFeed
}
func updateDisclosureText(for displayType: ExploreFeedSettingsDisplayType) {
guard displayType == .multipleLanguages else {
return
}
disclosureText = multipleLanguagesDisclosureText(for: contentGroupKind)
}
private func multipleLanguagesSubtitle(for contentGroupKind: WMFContentGroupKind) -> String {
if contentGroupKind.isGlobal {
return WMFLocalizedString("explore-feed-preferences-global-cards-subtitle", value: "Not language specific", comment: "Subtitle describing non-language specific feed cards")
} else {
let contentLanguageCodes = contentGroupKind.contentLanguageCodes
let preferredContentLanguageCodes = MWKDataStore.shared().languageLinkController.preferredLanguages.map { $0.contentLanguageCode }
let filteredLanguages = preferredContentLanguageCodes.filter { contentLanguageCodes.contains($0) }
return filteredLanguages.joined(separator: ", ").uppercased()
}
}
func updateSubtitle(for displayType: ExploreFeedSettingsDisplayType) {
guard displayType == .multipleLanguages else {
return
}
subtitle = multipleLanguagesSubtitle(for: contentGroupKind)
}
}
@objc(WMFExploreFeedSettingsViewController)
class ExploreFeedSettingsViewController: BaseExploreFeedSettingsViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
tableView.reloadData()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if updateFeedBeforeViewDisappears {
feedContentController?.updateFeedSourcesUserInitiated(true)
}
}
public var showCloseButton = false {
didSet {
if showCloseButton {
navigationItem.leftBarButtonItem = UIBarButtonItem.wmf_buttonType(.X, target: self, action: #selector(closeButtonPressed))
} else {
navigationItem.leftBarButtonItem = nil
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
title = CommonStrings.exploreFeedTitle
assert(!preferredLanguages.isEmpty)
displayType = preferredLanguages.count == 1 ? .singleLanguage : .multipleLanguages
}
@objc private func closeButtonPressed() {
dismiss(animated: true)
}
// MARK: Items
private lazy var feedCards: [FeedCard] = {
let inTheNews = FeedCard(contentGroupKind: .news, displayType: displayType)
let onThisDay = FeedCard(contentGroupKind: .onThisDay, displayType: displayType)
let featuredArticle = FeedCard(contentGroupKind: .featuredArticle, displayType: displayType)
let topRead = FeedCard(contentGroupKind: .topRead, displayType: displayType)
let places = FeedCard(contentGroupKind: .location, displayType: displayType)
let randomizer = FeedCard(contentGroupKind: .random, displayType: displayType)
let pictureOfTheDay = FeedCard(contentGroupKind: .pictureOfTheDay, displayType: displayType)
let continueReading = FeedCard(contentGroupKind: .continueReading, displayType: displayType)
let relatedPages = FeedCard(contentGroupKind: .relatedPages, displayType: displayType)
return [inTheNews, onThisDay, featuredArticle, topRead, places, randomizer, pictureOfTheDay, continueReading, relatedPages]
}()
private lazy var globalCards: ExploreFeedSettingsGlobalCards = {
return ExploreFeedSettingsGlobalCards()
}()
// MARK: Sections
let togglingFeedCardsFooterText = WMFLocalizedString("explore-feed-preferences-languages-footer-text", value: "Hiding all Explore feed cards in all of your languages will turn off the Explore tab.", comment: "Text for explaining the effects of hiding all feed cards")
private lazy var customizationSection: ExploreFeedSettingsSection = {
return ExploreFeedSettingsSection(headerTitle: WMFLocalizedString("explore-feed-preferences-customize-explore-feed", value: "Customize the Explore feed", comment: "Title of the Settings section that allows users to customize the Explore feed"), footerTitle: String.localizedStringWithFormat("%@ %@", WMFLocalizedString("explore-feed-preferences-customize-explore-feed-footer-text", value: "Hiding a card type will stop this card type from appearing in the Explore feed.", comment: "Text for explaining the effects of hiding feed cards"), togglingFeedCardsFooterText), items: feedCards)
}()
private lazy var mainSection: ExploreFeedSettingsSection = {
return ExploreFeedSettingsSection(headerTitle: nil, footerTitle: WMFLocalizedString("explore-feed-preferences-turn-off-feed-disclosure", value: "Turning off the Explore tab will replace the Explore tab with a Settings tab.", comment: "Text for explaining the effects of turning off the Explore tab"), items: [ExploreFeedSettingsPrimary(for: .entireFeed)])
}()
private lazy var languagesSection: ExploreFeedSettingsSection? = {
guard displayType == .multipleLanguages else {
return nil
}
var items: [ExploreFeedSettingsItem] = languages
items.append(globalCards)
return ExploreFeedSettingsSection(headerTitle: CommonStrings.languagesTitle, footerTitle: togglingFeedCardsFooterText, items: items)
}()
override var sections: [ExploreFeedSettingsSection] {
guard displayType == .multipleLanguages else {
return [customizationSection, mainSection]
}
guard let languagesSection = languagesSection else {
return [customizationSection, mainSection]
}
return [customizationSection, languagesSection, mainSection]
}
// MARK: Toggling Explore feed
private func turnOnExploreAlertController(turnedOn: @escaping () -> Void, cancelled: @escaping () -> Void) -> UIAlertController {
let alertController = UIAlertController(title: CommonStrings.turnOnExploreTabTitle, message: WMFLocalizedString("explore-feed-preferences-turn-on-explore-tab-message", value: "This will replace the Settings tab with the Explore tab, you can access Settings from the top of the Explore tab by tapping on the gear icon", comment: "Message for alert that allows users to turn on the Explore tab"), preferredStyle: .alert)
let turnOnExplore = UIAlertAction(title: CommonStrings.turnOnExploreActionTitle, style: .default, handler: { _ in
turnedOn()
})
let cancel = UIAlertAction(title: CommonStrings.cancelActionTitle, style: .cancel, handler: { _ in
cancelled()
})
alertController.addAction(turnOnExplore)
alertController.addAction(cancel)
return alertController
}
private func turnOffExploreAlertController(turnedOff: @escaping () -> Void, cancelled: @escaping () -> Void) -> UIAlertController {
let alertController = UIAlertController(title: WMFLocalizedString("explore-feed-preferences-turn-off-explore-tab-title", value: "Turn off the Explore tab?", comment: "Title for alert that allows users to turn off the Explore tab"), message: WMFLocalizedString("explore-feed-preferences-turn-off-explore-tab-message", value: "The Explore tab can be turned back on in Explore feed settings", comment: "Message for alert that allows users to turn off the Explore tab"), preferredStyle: .alert)
let turnOffExplore = UIAlertAction(title: WMFLocalizedString("explore-feed-preferences-turn-off-explore-tab-action-title", value: "Turn off Explore", comment: "Title for action that allows users to turn off the Explore tab"), style: .destructive, handler: { _ in
turnedOff()
})
let cancel = UIAlertAction(title: CommonStrings.cancelActionTitle, style: .cancel, handler: { _ in
cancelled()
})
alertController.addAction(turnOffExplore)
alertController.addAction(cancel)
return alertController
}
}
// MARK: - UITableViewDelegate
extension ExploreFeedSettingsViewController {
@objc func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
defer {
tableView.deselectRow(at: indexPath, animated: true)
}
guard displayType == .multipleLanguages else {
return
}
let item = getItem(at: indexPath)
guard let feedCard = item as? FeedCard else {
return
}
let feedCardSettingsViewController = FeedCardSettingsViewController()
feedCardSettingsViewController.configure(with: item.title, dataStore: dataStore, contentGroupKind: feedCard.contentGroupKind, theme: theme)
navigationController?.pushViewController(feedCardSettingsViewController, animated: true)
}
}
// MARK: - WMFSettingsTableViewCellDelegate
extension ExploreFeedSettingsViewController {
override func settingsTableViewCell(_ settingsTableViewCell: WMFSettingsTableViewCell!, didToggleDisclosureSwitch sender: UISwitch!) {
activeSwitch = sender
let controlTag = sender.tag
guard let feedContentController = feedContentController else {
assertionFailure("feedContentController is nil")
return
}
guard controlTag != -1 else { // main switch
if sender.isOn {
present(turnOnExploreAlertController(turnedOn: {
self.dataStore?.feedContentController.toggleAllContentGroupKinds(true, updateFeed: false)
UserDefaults.standard.defaultTabType = .explore
}, cancelled: {
sender.setOn(false, animated: true)
}), animated: true)
} else {
present(turnOffExploreAlertController(turnedOff: {
self.dataStore?.feedContentController.toggleAllContentGroupKinds(false, updateFeed: false)
UserDefaults.standard.defaultTabType = .settings
}, cancelled: {
sender.setOn(true, animated: true)
}), animated: true)
}
return
}
guard controlTag != -2 else { // global cards
feedContentController.toggleGlobalContentGroupKinds(sender.isOn, updateFeed: false)
return
}
if displayType == .singleLanguage {
guard let contentGroupKind = WMFContentGroupKind(rawValue: Int32(controlTag)) else {
assertionFailure("No content group kind for given control tag")
return
}
guard contentGroupKind.isCustomizable || contentGroupKind.isGlobal else {
assertionFailure("Content group kind \(contentGroupKind) is not customizable nor global")
return
}
feedContentController.toggleContentGroup(of: contentGroupKind, isOn: sender.isOn, updateFeed: false)
} else {
guard let language = languages.first(where: { $0.controlTag == controlTag }) else {
assertionFailure("No language for given control tag")
return
}
feedContentController.toggleContent(forSiteURL: language.siteURL, isOn: sender.isOn, waitForCallbackFromCoordinator: true, updateFeed: false)
}
}
}