From 935839d7baa5776c807017d87a1adcfa6193620a Mon Sep 17 00:00:00 2001 From: Daniele Bogo Date: Thu, 6 Feb 2025 19:55:38 +0000 Subject: [PATCH 1/5] Add new strings for tip --- podcasts/Strings+Generated.swift | 4 ++++ podcasts/en.lproj/Localizable.strings | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/podcasts/Strings+Generated.swift b/podcasts/Strings+Generated.swift index 35e7ed270..a827874f9 100644 --- a/podcasts/Strings+Generated.swift +++ b/podcasts/Strings+Generated.swift @@ -2271,6 +2271,10 @@ internal enum L10n { internal static var podcastFeedReloadNewEpisodesFound: String { return L10n.tr("Localizable", "podcast_feed_reload_new_episodes_found") } /// No episodes found. internal static var podcastFeedReloadNoEpisodesFound: String { return L10n.tr("Localizable", "podcast_feed_reload_no_episodes_found") } + /// Pull down or use this menu to see if there's something new. + internal static var podcastFeedReloadTipMessage: String { return L10n.tr("Localizable", "podcast_feed_reload_tip_message") } + /// Fresh episodes, coming right up! + internal static var podcastFeedReloadTipTitle: String { return L10n.tr("Localizable", "podcast_feed_reload_tip_title") } /// Discover Podcasts internal static var podcastGridDiscoverPodcasts: String { return L10n.tr("Localizable", "podcast_grid_discover_podcasts") } /// Coming from another app? Import your podcasts via Profile > Settings > Import & Export. diff --git a/podcasts/en.lproj/Localizable.strings b/podcasts/en.lproj/Localizable.strings index 453b2c5ff..990398230 100644 --- a/podcasts/en.lproj/Localizable.strings +++ b/podcasts/en.lproj/Localizable.strings @@ -4752,3 +4752,9 @@ /* After the podcast feed reloading completes, this is the message we display if there's no new episodes to load */ "podcast_feed_reload_no_episodes_found" = "No episodes found."; + +/* Title used in the tooltip showed in the podcast view controller the first time the view appears */ +"podcast_feed_reload_tip_title" = "Fresh episodes, coming right up!"; + +/* Description used in the tooltip showed in the podcast view controller the first time the view appears */ +"podcast_feed_reload_tip_message" = "Pull down or use this menu to see if there's something new."; From 1d0e57755f673d95a4919590e51ec94bd98b7c2a Mon Sep 17 00:00:00 2001 From: Daniele Bogo Date: Thu, 6 Feb 2025 19:56:08 +0000 Subject: [PATCH 2/5] Add tip --- podcasts/Analytics/AnalyticsEvent.swift | 2 + podcasts/Constants.swift | 3 ++ podcasts/PodcastViewController.swift | 61 ++++++++++++++++++++++++- podcasts/Settings.swift | 11 +++++ podcasts/TipView.swift | 59 ++++++++++++++---------- 5 files changed, 112 insertions(+), 24 deletions(-) diff --git a/podcasts/Analytics/AnalyticsEvent.swift b/podcasts/Analytics/AnalyticsEvent.swift index c16906316..ea86e37ff 100644 --- a/podcasts/Analytics/AnalyticsEvent.swift +++ b/podcasts/Analytics/AnalyticsEvent.swift @@ -838,4 +838,6 @@ enum AnalyticsEvent: String { case podcastScreenRefreshEpisodeList case podcastScreenRefreshNoEpisodesFound case podcastScreenRefreshNewEpisodeFound + case podcastRefreshEpisodeTooltipShown + case podcastRefreshEpisodeTooltipDismissed } diff --git a/podcasts/Constants.swift b/podcasts/Constants.swift index 191f131c8..fcfcb3ac6 100644 --- a/podcasts/Constants.swift +++ b/podcasts/Constants.swift @@ -208,6 +208,9 @@ struct Constants { static let lastCheckDate = "manageDownloadsLastCheckDate" } + enum podcastFeedReload { + static let showTip = "podcastFeedReload.showtip" + } } enum Values { diff --git a/podcasts/PodcastViewController.swift b/podcasts/PodcastViewController.swift index 18e66d29c..0665a8191 100644 --- a/podcasts/PodcastViewController.swift +++ b/podcasts/PodcastViewController.swift @@ -5,6 +5,7 @@ import PocketCastsServer import PocketCastsUtils import UIKit import UIDeviceIdentifier +import SwiftUI enum PodcastFeedReloadSource { case menu @@ -193,6 +194,7 @@ class PodcastViewController: FakeNavViewController, PodcastActionsDelegate, Sync private var cancellables = Set() private var podcastFeedViewModel: PodcastFeedViewModel? private var refreshControl: CustomRefreshControl? + private var podcastFeedReloadTooltip: UIViewController? lazy var ratingView: UIView = { let view = StarRatingView(viewModel: podcastRatingViewModel, @@ -351,8 +353,14 @@ class PodcastViewController: FakeNavViewController, PodcastActionsDelegate, Sync addCustomObserver(Constants.Notifications.podcastColorsDownloaded, selector: #selector(colorsDidDownload(_:))) updateColors() - if FeatureFlag.podcastFeedUpdate.enabled { + if FeatureFlag.podcastFeedUpdate.enabled, Settings.shouldShowPodcastFeeReloadTip { refreshControl?.parentViewControllerDidAppear() + if let vc = showPodcastFeedReloadTip() { + present(vc, animated: true) { + Analytics.track(.podcastRefreshEpisodeTooltipShown) + } + podcastFeedReloadTooltip = vc + } } addCustomObserver(Constants.Notifications.episodeArchiveStatusChanged, selector: #selector(refreshEpisodes)) @@ -1033,6 +1041,46 @@ class PodcastViewController: FakeNavViewController, PodcastActionsDelegate, Sync reloadPodcastFeed(source: .refreshControl) } + private func dismissPodcastFeedReloadTip() { + guard Settings.shouldShowPodcastFeeReloadTip else { + return + } + Analytics.track(.podcastRefreshEpisodeTooltipDismissed) + Settings.shouldShowPodcastFeeReloadTip = false + podcastFeedReloadTooltip?.dismiss(animated: true) { [weak self] in + self?.podcastFeedReloadTooltip = nil + } + } + + private func showPodcastFeedReloadTip() -> UIViewController? { + let vc = UIHostingController(rootView: AnyView (EmptyView()) ) + let idealSize = CGSizeMake(290, 100) + let tipView = TipViewStatic(title: L10n.podcastFeedReloadTipTitle, + message: L10n.podcastFeedReloadTipMessage, + onTap: { [weak self] in + self?.dismissPodcastFeedReloadTip() + }) + .frame(idealWidth: idealSize.width, minHeight: idealSize.height) + .setupDefaultEnvironment() + vc.rootView = AnyView(tipView) + vc.view.backgroundColor = .clear + vc.view.clipsToBounds = false + vc.modalPresentationStyle = .popover + if #available(iOS 16.0, *) { + vc.sizingOptions = [.preferredContentSize] + } else { + vc.preferredContentSize = idealSize + } + if let popoverPresentationController = vc.popoverPresentationController { + popoverPresentationController.delegate = self + popoverPresentationController.permittedArrowDirections = [.down] + popoverPresentationController.sourceView = searchController?.overflowButton + popoverPresentationController.sourceRect = searchController?.overflowButton.bounds ?? .zero + popoverPresentationController.backgroundColor = ThemeColor.primaryUi01() + } + return vc + } + // MARK: - Long press actions func archiveAll(startingAt: Episode) { @@ -1091,3 +1139,14 @@ private extension PodcastViewController { podcast?.uuid ?? podcastInfo?.analyticsDescription ?? "unknown" } } + +extension PodcastViewController: UIPopoverPresentationControllerDelegate { + func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle { + // Return no adaptive presentation style, use default presentation behaviour + return .none + } + + func popoverPresentationControllerDidDismissPopover(_ popoverPresentationController: UIPopoverPresentationController) { + dismissPodcastFeedReloadTip() + } +} diff --git a/podcasts/Settings.swift b/podcasts/Settings.swift index b7006d176..fbc322751 100644 --- a/podcasts/Settings.swift +++ b/podcasts/Settings.swift @@ -1373,6 +1373,17 @@ class Settings: NSObject { } } + // MARK: - Podcast Feed Reload + + static var shouldShowPodcastFeeReloadTip: Bool { + get { + UserDefaults.standard.value(forKey: Constants.UserDefaults.podcastFeedReload.showTip) as? Bool ?? true + } + set { + UserDefaults.standard.setValue(newValue, forKey: Constants.UserDefaults.podcastFeedReload.showTip) + } + } + // MARK: - Manage Downloads class var manageDownloadsLastCheckDate: Date? { diff --git a/podcasts/TipView.swift b/podcasts/TipView.swift index ab467cdda..298dfa822 100644 --- a/podcasts/TipView.swift +++ b/podcasts/TipView.swift @@ -11,34 +11,47 @@ struct TipView: View { var body: some View { ContentSizeGeometryReader { proxy in - VStack { - HStack { - VStack(alignment: .leading) { - Text(title) - .font(size: 15, style: .body, weight: .bold) - .foregroundColor(theme.primaryText01) - .lineLimit(2) + TipViewStatic(title: title, message: message, onTap: onTap) + } contentSizeUpdated: { size in + sizeChanged(size) + } + } +} + +struct TipViewStatic: View { + let title: String + let message: String? + let onTap: (()->())? + + @EnvironmentObject var theme: Theme + + var body: some View { + VStack { + HStack { + VStack(alignment: .leading) { + Text(title) + .font(size: 15, style: .body, weight: .bold) + .foregroundColor(theme.primaryText01) + .lineLimit(2) + .multilineTextAlignment(.leading) + .fixedSize(horizontal: false, vertical: true) + if let message { + Text(message) + .font(size: 14, style: .body, weight: .regular) + .foregroundColor(theme.primaryText02) + .lineLimit(4) .multilineTextAlignment(.leading) .fixedSize(horizontal: false, vertical: true) - if let message { - Text(message) - .font(size: 14, style: .body, weight: .regular) - .foregroundColor(theme.primaryText02) - .lineLimit(4) - .multilineTextAlignment(.leading) - .fixedSize(horizontal: false, vertical: true) - } + .padding(.top, 2) } - Spacer() - } - .padding(16) - .frame(maxHeight: .infinity) - .onTapGesture { - onTap?() } + Spacer() + } + .padding(16) + .frame(maxHeight: .infinity) + .onTapGesture { + onTap?() } - } contentSizeUpdated: { size in - sizeChanged(size) } } } From 8762eb0066dc2e41d8c589588125ce888576e5cd Mon Sep 17 00:00:00 2001 From: Daniele Bogo Date: Thu, 6 Feb 2025 20:12:52 +0000 Subject: [PATCH 3/5] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5790dd601..a63966116 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ 7.83 ----- +- Add the functionality to reload the podcast feed [#2703](https://github.com/Automattic/pocket-casts-ios/issues/2703) 7.82 From 6095f2776e8c8a53b4fd02fa1df122d066daf8b7 Mon Sep 17 00:00:00 2001 From: Daniele Bogo Date: Fri, 7 Feb 2025 10:31:49 +0000 Subject: [PATCH 4/5] Work on tooltip --- .../PodcastViewController+NetworkLoad.swift | 6 +++ podcasts/PodcastViewController.swift | 49 ++++++++++++++----- 2 files changed, 43 insertions(+), 12 deletions(-) diff --git a/podcasts/PodcastViewController+NetworkLoad.swift b/podcasts/PodcastViewController+NetworkLoad.swift index 4142b63af..c7463c90d 100644 --- a/podcasts/PodcastViewController+NetworkLoad.swift +++ b/podcasts/PodcastViewController+NetworkLoad.swift @@ -42,6 +42,8 @@ extension PodcastViewController { self.podcast = podcast summaryExpanded = !podcast.isSubscribed() + forceCollapsingHeaderIfNeeded() + if SyncManager.isUserLoggedIn() { guard let episodes = ApiServerHandler.shared.retrieveEpisodeTaskSynchronouusly(podcastUuid: uuid) else { return } @@ -81,6 +83,10 @@ extension PodcastViewController { self.loadingIndicator.stopAnimating() UIView.animate(withDuration: 0.5) { self.episodesTable.alpha = 1 + } completion: { success in + if FeatureFlag.podcastFeedUpdate.enabled { + self.showPodcastFeedReloadTipIfNeeded() + } } } } diff --git a/podcasts/PodcastViewController.swift b/podcasts/PodcastViewController.swift index 0665a8191..377091654 100644 --- a/podcasts/PodcastViewController.swift +++ b/podcasts/PodcastViewController.swift @@ -255,6 +255,9 @@ class PodcastViewController: FakeNavViewController, PodcastActionsDelegate, Sync if FeatureFlag.podcastFeedUpdate.enabled { podcastFeedViewModel = PodcastFeedViewModel(uuid: podcast?.uuid ?? podcastInfo?.uuid) + + // Let's collapse the header if the tooltip has never been showed before + forceCollapsingHeaderIfNeeded() } closeTapped = { [weak self] in @@ -353,16 +356,6 @@ class PodcastViewController: FakeNavViewController, PodcastActionsDelegate, Sync addCustomObserver(Constants.Notifications.podcastColorsDownloaded, selector: #selector(colorsDidDownload(_:))) updateColors() - if FeatureFlag.podcastFeedUpdate.enabled, Settings.shouldShowPodcastFeeReloadTip { - refreshControl?.parentViewControllerDidAppear() - if let vc = showPodcastFeedReloadTip() { - present(vc, animated: true) { - Analytics.track(.podcastRefreshEpisodeTooltipShown) - } - podcastFeedReloadTooltip = vc - } - } - addCustomObserver(Constants.Notifications.episodeArchiveStatusChanged, selector: #selector(refreshEpisodes)) addCustomObserver(Constants.Notifications.manyEpisodesChanged, selector: #selector(refreshEpisodes)) addCustomObserver(Constants.Notifications.episodeStarredChanged, selector: #selector(refreshEpisodes)) @@ -397,6 +390,11 @@ class PodcastViewController: FakeNavViewController, PodcastActionsDelegate, Sync properties["list_id"] = listUuid } Analytics.track(.podcastScreenShown, properties: properties) + + if FeatureFlag.podcastFeedUpdate.enabled { + refreshControl?.parentViewControllerDidAppear() + showPodcastFeedReloadTipIfNeeded() + } } override func viewWillDisappear(_ animated: Bool) { @@ -1052,7 +1050,34 @@ class PodcastViewController: FakeNavViewController, PodcastActionsDelegate, Sync } } + func forceCollapsingHeaderIfNeeded() { + if FeatureFlag.podcastFeedUpdate.enabled { + if Settings.shouldShowPodcastFeeReloadTip, summaryExpanded { + summaryExpanded = false + } + } + } + + func showPodcastFeedReloadTipIfNeeded() { + guard + Settings.shouldShowPodcastFeeReloadTip, + FeatureFlag.podcastFeedUpdate.enabled, + podcastFeedReloadTooltip != nil + else { + return + } + if let vc = showPodcastFeedReloadTip() { + present(vc, animated: true) { + Analytics.track(.podcastRefreshEpisodeTooltipShown) + } + podcastFeedReloadTooltip = vc + } + } + private func showPodcastFeedReloadTip() -> UIViewController? { + guard let button = searchController?.overflowButton else { + return nil + } let vc = UIHostingController(rootView: AnyView (EmptyView()) ) let idealSize = CGSizeMake(290, 100) let tipView = TipViewStatic(title: L10n.podcastFeedReloadTipTitle, @@ -1074,8 +1099,8 @@ class PodcastViewController: FakeNavViewController, PodcastActionsDelegate, Sync if let popoverPresentationController = vc.popoverPresentationController { popoverPresentationController.delegate = self popoverPresentationController.permittedArrowDirections = [.down] - popoverPresentationController.sourceView = searchController?.overflowButton - popoverPresentationController.sourceRect = searchController?.overflowButton.bounds ?? .zero + popoverPresentationController.sourceView = button + popoverPresentationController.sourceRect = button.bounds popoverPresentationController.backgroundColor = ThemeColor.primaryUi01() } return vc From effef9766806416c4a0dde7038caef2ed8bad92c Mon Sep 17 00:00:00 2001 From: Daniele Bogo Date: Mon, 10 Feb 2025 20:37:46 +0530 Subject: [PATCH 5/5] Fix guard --- podcasts/PodcastViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/podcasts/PodcastViewController.swift b/podcasts/PodcastViewController.swift index 377091654..094806a62 100644 --- a/podcasts/PodcastViewController.swift +++ b/podcasts/PodcastViewController.swift @@ -1062,7 +1062,7 @@ class PodcastViewController: FakeNavViewController, PodcastActionsDelegate, Sync guard Settings.shouldShowPodcastFeeReloadTip, FeatureFlag.podcastFeedUpdate.enabled, - podcastFeedReloadTooltip != nil + podcastFeedReloadTooltip == nil else { return }