Skip to content

Commit

Permalink
Merge pull request #474 from Automattic/task/376-require-registration
Browse files Browse the repository at this point in the history
End of Year: require account
  • Loading branch information
leandroalonso authored Nov 3, 2022
2 parents 56e6ad9 + 7915788 commit ff974bb
Show file tree
Hide file tree
Showing 10 changed files with 143 additions and 7 deletions.
12 changes: 10 additions & 2 deletions podcasts/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -268,16 +268,24 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
Constants.RemoteParams.periodicSaveTimeMs: NSNumber(value: Constants.RemoteParams.periodicSaveTimeMsDefault),
Constants.RemoteParams.episodeSearchDebounceMs: NSNumber(value: Constants.RemoteParams.episodeSearchDebounceMsDefault),
Constants.RemoteParams.podcastSearchDebounceMs: NSNumber(value: Constants.RemoteParams.podcastSearchDebounceMsDefault),
Constants.RemoteParams.customStorageLimitGB: NSNumber(value: Constants.RemoteParams.customStorageLimitGBDefault)
Constants.RemoteParams.customStorageLimitGB: NSNumber(value: Constants.RemoteParams.customStorageLimitGBDefault),
Constants.RemoteParams.endOfYearRequireAccount: NSNumber(value: Constants.RemoteParams.endOfYearRequireAccountDefault)
])

remoteConfig.fetch(withExpirationDuration: 2.hour) { status, _ in
remoteConfig.fetch(withExpirationDuration: 2.hour) { [weak self] status, _ in
if status == .success {
remoteConfig.activate(completion: nil)

self?.updateEndOfYearRemoteValue()
}
}
}

private func updateEndOfYearRemoteValue() {
// Update if EOY requires an account to be seen
EndOfYear.requireAccount = Settings.endOfYearRequireAccount
}

private func postLaunchSetup() {
if !UserDefaults.standard.bool(forKey: "CreatedDefPlaylistsV2") {
PlaylistManager.createDefaultFilters()
Expand Down
4 changes: 4 additions & 0 deletions podcasts/Constants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ struct Constants {
static let reviewRequestDates = "reviewRequestDates"

static let showBadgeFor2022EndOfYear = "showBadgeFor2022EndOfYear"
static let modal2022HasBeenShown = "modal2022HasBeenShown"
}

enum Values {
Expand Down Expand Up @@ -243,6 +244,9 @@ struct Constants {

static let customStorageLimitGB = "custom_storage_limit_gb"
static let customStorageLimitGBDefault: Int = 10

static let endOfYearRequireAccount = "end_of_year_require_account"
static let endOfYearRequireAccountDefault: Bool = true
}

static let defaultDebounceTime: TimeInterval = 0.5
Expand Down
73 changes: 72 additions & 1 deletion podcasts/End of Year/EndOfYear.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import SwiftUI
import PocketCastsServer
import MaterialComponents.MaterialBottomSheet
import PocketCastsDataModel

Expand All @@ -8,6 +9,21 @@ struct EndOfYear {
FeatureFlag.endOfYear && DataManager.sharedManager.isEligibleForEndOfYearStories()
}

/// Internal state machine to determine how we should react to login changes
/// and when to show the modal vs go directly to the stories
private static var state: EndOfYearState = .showModalIfNeeded

static var requireAccount: Bool = Settings.endOfYearRequireAccount {
didSet {
// If registration is not needed anymore and this user is logged out
// Show the prompt again.
if oldValue && !requireAccount && !SyncManager.isUserLoggedIn() {
Settings.endOfYearModalHasBeenShown = false
NotificationCenter.postOnMainThread(notification: .eoyRegistrationNotRequired, object: nil)
}
}
}

var presentationMode: UIModalPresentationStyle {
UIDevice.current.isiPad() ? .formSheet : .fullScreen
}
Expand All @@ -16,19 +32,54 @@ struct EndOfYear {
.init(top: 0, leading: 0, bottom: UIDevice.current.isiPad() ? 5 : 0, trailing: 0)
}

init() {
Self.requireAccount = Settings.endOfYearRequireAccount
}

func showPrompt(in viewController: UIViewController) {
guard Self.isEligible else {
guard Self.isEligible, !Settings.endOfYearModalHasBeenShown else {
return
}

MDCSwiftUIWrapper.present(EndOfYearModal(), in: viewController)
}

func showPromptBasedOnState(in viewController: UIViewController) {
switch Self.state {

// If we're in the default state, then check to see if we should show the prompt
case .showModalIfNeeded:
showPrompt(in: viewController)

// If we were in the waiting state, but the user has logged in, then show stories
case .loggedIn:
Self.state = .showModalIfNeeded
showStories(in: viewController)

// If the user has seen the prompt, and chosen to login, but then has cancelled out of the flow without logging in,
// When this code is ran from MainTabController viewDidAppear we will still be in the waiting state
// reset the state to the default to restart the process over again
case .waitingForLogin:
Self.state = .showModalIfNeeded
}
}

func showStories(in viewController: UIViewController) {
guard FeatureFlag.endOfYear else {
return
}

if Self.requireAccount && !SyncManager.isUserLoggedIn() {
Self.state = .waitingForLogin

let profileIntroController = ProfileIntroViewController()
profileIntroController.infoLabelText = L10n.eoyCreateAccountToSee
let navigationController = UINavigationController(rootViewController: profileIntroController)
navigationController.modalPresentationStyle = .fullScreen
viewController.present(navigationController, animated: true)
return
}

let storiesViewController = StoriesHostingController(rootView: StoriesView(dataSource: EndOfYearStoriesDataSource()).padding(storiesPadding))
storiesViewController.view.backgroundColor = .black
storiesViewController.modalPresentationStyle = presentationMode
Expand Down Expand Up @@ -59,6 +110,22 @@ struct EndOfYear {
StoryShareableProvider.generatedItem = asset() as? UIImage
}
}

func resetStateIfNeeded() {
// When a user logs in (or creates an account) we mark the EOY modal as not
// shown to show it again.
if Self.state == .showModalIfNeeded {
Settings.endOfYearModalHasBeenShown = false
return
}

guard Self.state == .waitingForLogin else { return }

// If we're in the waiting for login state (the user has seen the prompt, and chosen to login)
// Update the current state based on whether the user is logged in or not
// If the user did not login, then just reset the state to the default showModalIfNeeded
Self.state = SyncManager.isUserLoggedIn() ? .loggedIn : .showModalIfNeeded
}
}

class StoriesHostingController<ContentView: View>: UIHostingController<ContentView> {
Expand All @@ -70,3 +137,7 @@ class StoriesHostingController<ContentView: View>: UIHostingController<ContentVi
.portrait
}
}

private enum EndOfYearState {
case showModalIfNeeded, waitingForLogin, loggedIn
}
3 changes: 3 additions & 0 deletions podcasts/End of Year/Views/EndOfYearModal.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ struct EndOfYearModal: View {
}
.frame(maxWidth: Constants.maxWidth)
.applyDefaultThemeOptions()
.onAppear {
Settings.endOfYearModalHasBeenShown = true
}
}

var pill: some View {
Expand Down
28 changes: 26 additions & 2 deletions podcasts/MainTabBarController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ class MainTabBarController: UITabBarController, NavigationProtocol {
NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(unhideNavBar), name: Constants.Notifications.unhideNavBarRequested, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(profileSeen), name: Constants.Notifications.profileSeen, object: nil)

observersForEndOfYearStats()
}

override func viewDidAppear(_ animated: Bool) {
Expand All @@ -61,7 +63,7 @@ class MainTabBarController: UITabBarController, NavigationProtocol {
checkPromotionFinishedAcknowledged()
checkWhatsNewAcknowledged()

endOfYear.showPrompt(in: self)
endOfYear.showPromptBasedOnState(in: self)
}

override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
Expand Down Expand Up @@ -394,13 +396,35 @@ class MainTabBarController: UITabBarController, NavigationProtocol {
return true
}

// MARK: - End of Year badge
// MARK: - End of Year

@objc private func profileSeen() {
profileTabBarItem.badgeValue = nil
Settings.showBadgeFor2022EndOfYear = false
}

func observersForEndOfYearStats() {
guard FeatureFlag.endOfYear else {
return
}

NotificationCenter.default.addObserver(forName: .userSignedIn, object: nil, queue: .main) { notification in
self.endOfYear.resetStateIfNeeded()
}

// If the requirement for EOY changes and registration is not required anymore
// Show the modal
NotificationCenter.default.addObserver(forName: .eoyRegistrationNotRequired, object: nil, queue: .main) { [weak self] _ in
guard let self else {
return
}

if self.presentedViewController == nil {
self.endOfYear.showPrompt(in: self)
}
}
}

// MARK: - Orientation

// we implement this here to lock all views (except presented modal VCs to portrait)
Expand Down
1 change: 1 addition & 0 deletions podcasts/NewEmailViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ class NewEmailViewController: UIViewController, UITextFieldDelegate {
ServerSettings.setSyncingEmail(email: username)

NotificationCenter.default.post(name: .userLoginDidChange, object: nil)
NotificationCenter.postOnMainThread(notification: .userSignedIn)

Analytics.track(.userAccountCreated)
}
Expand Down
6 changes: 6 additions & 0 deletions podcasts/Notifications.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,10 @@ import Foundation
extension NSNotification.Name {
/// When a user has signed in, signed out, or been signed in during account creation
static let userLoginDidChange = NSNotification.Name("User.LoginChanged")

/// When a user logs via login or account creation
static let userSignedIn = NSNotification.Name("User.SignedOrCreatedAccount")

/// When the requirement for having an account or not to see End Of Year Stats changes
static let eoyRegistrationNotRequired = NSNotification.Name("EOY.RegistrationNotRequired")
}
4 changes: 3 additions & 1 deletion podcasts/ProfileIntroViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,13 @@ class ProfileIntroViewController: PCViewController, SyncSigninDelegate {

@IBOutlet var infoLabel: ThemeableLabel! {
didSet {
infoLabel.text = L10n.signInMessage
infoLabel.text = infoLabelText ?? L10n.signInMessage
infoLabel.style = .primaryText02
}
}

var infoLabelText: String?

override func viewDidLoad() {
super.viewDidLoad()

Expand Down
17 changes: 16 additions & 1 deletion podcasts/Settings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -719,7 +719,7 @@ class Settings: NSObject {
UserDefaults.standard.bool(forKey: Constants.UserDefaults.analyticsOptOut)
}

// MARK: - Profile Badge for End of Year 2022
// MARK: - End of Year 2022

class var showBadgeFor2022EndOfYear: Bool {
set {
Expand All @@ -731,6 +731,16 @@ class Settings: NSObject {
}
}

class var endOfYearModalHasBeenShown: Bool {
set {
UserDefaults.standard.set(newValue, forKey: Constants.UserDefaults.modal2022HasBeenShown)
}

get {
UserDefaults.standard.bool(forKey: Constants.UserDefaults.modal2022HasBeenShown)
}
}

// MARK: - Variables that are loaded/changed through Firebase

#if !os(watchOS)
Expand All @@ -746,6 +756,11 @@ class Settings: NSObject {
remoteMsToTime(key: Constants.RemoteParams.episodeSearchDebounceMs)
}

static var endOfYearRequireAccount: Bool {
let remote = RemoteConfig.remoteConfig().configValue(forKey: Constants.RemoteParams.endOfYearRequireAccount)
return remote.boolValue
}

private class func remoteMsToTime(key: String) -> TimeInterval {
let remoteMs = RemoteConfig.remoteConfig().configValue(forKey: key)

Expand Down
2 changes: 2 additions & 0 deletions podcasts/SyncSigninViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,8 @@ class SyncSigninViewController: PCViewController, UITextFieldDelegate {
RefreshManager.shared.refreshPodcasts(forceEvenIfRefreshedRecently: true)
Settings.setPromotionFinishedAcknowledged(true)
Settings.setLoginDetailsUpdated()

NotificationCenter.postOnMainThread(notification: .userSignedIn)
})
}
}
Expand Down

0 comments on commit ff974bb

Please sign in to comment.