Commit 0d5742e2 by Demid Merzlyakov

IOS-176: feature availability control: move most part of the stuff to the new control.

parent f916bb10
......@@ -76,6 +76,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
FirebaseApp.configure()
ConfigManager.shared.updateConfig()
FeatureAvailabilityManager.shared = configureFeatureAvailabilityManager(with: LocationManager.shared, configManager: ConfigManager.shared)
//App UI
let appCoordinator = AppCoordinator(window: self.window!, launchOptions: launchOptions)
appCoordinator.start()
......@@ -179,6 +181,46 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
}
}
}
private func configureFeatureAvailabilityManager(with locationManager: LocationManager, configManager: ConfigManager) -> FeatureAvailabilityManager {
let usOnly = USOnlyFeatureAvailabilityChecker { [weak locationManager] in
return locationManager?.selectedLocation
}
let premium = PremiumInAppFeatureAvailabilityChecker()
let proSubscription = ProSubscriptionAvailabilityChecker()
let isPhone = DeviceTypeFeatureAvailabilityChecker(deviceType: .phone)
var availabilityCheckers = [AppFeature: FeatureAvailabilityChecker]()
for feature in AppFeature.allCases {
// To make sure all features are explicitly configured.
switch feature {
case .ads:
availabilityCheckers[feature] = !premium && !proSubscription
case .minutelyForecast:
availabilityCheckers[feature] = proSubscription
case .shorts:
availabilityCheckers[feature] = usOnly && isPhone
case .airQualityIndex:
break
case .attPrompt:
break // config only
case .nwsAlertsViaMoEngage:
break // config only
case .onboarding:
break // config only
case .shortsLastNudge:
break // config only
}
}
let manager = FeatureAvailabilityManager(configFetcher: { [weak configManager] in
configManager?.config
},
checkers: availabilityCheckers)
return manager
}
}
extension AppDelegate: AppsFlyerLibDelegate {
......
......@@ -26,6 +26,9 @@ class AppCoordinator: Coordinator {
var parentCoordinator: Coordinator?
var childCoordinators = [Coordinator]()
private var featureAvailabilityManager: FeatureAvailabilityManager {
FeatureAvailabilityManager.shared
}
init(window:UIWindow, launchOptions: [UIApplication.LaunchOptionsKey: Any]?) {
self.window = window
......@@ -109,7 +112,7 @@ class AppCoordinator: Coordinator {
DispatchQueue.main.async {
self.logAppLaunchEvents()
if ConfigManager.shared.config.showOnboarding {
if self.featureAvailabilityManager.isAvailable(feature: .onboarding) {
if Settings.shared.initialOnboardingShowed {
self.finishInitialOnboarding()
}
......@@ -158,7 +161,7 @@ class AppCoordinator: Coordinator {
firstOpenSource = .url
}
let flow = ConfigManager.shared.config.showOnboarding ? "FTUX" : "default"
let flow = featureAvailabilityManager.isAvailable(feature: .onboarding) ? "FTUX" : "default"
analytics(log: .ANALYTICS_FIRST_OPEN,
params: [.ANALYTICS_KEY_FIRST_OPEN_SOURCE : firstOpenSource.rawValue,
.ANALYTICS_KEY_FIRST_OPEN_FLOW : flow])
......
......@@ -20,12 +20,8 @@ public class ConfigManager {
private static let adConfigKey = "ads_config_ios"
private static let popularCitiesConfigKey = "search_screen_popular_cities"
private static let ccpaUpdateIntervalConfigKey = "ccpa_update_interval_ios"
private static let nwsAlertsViaMoEngageEnabledKey = "ios_nws_alerts_via_moengage_enabled"
private static let showAttPromptKey = "ios_show_att_prompt"
private static let shortsLeftBelowCountKey = "shorts_left_below_nudge_every_x_cards"
private static let shortsLastNudgeEnabledKey = "shorts_swipe_down_nudge_enabled"
private static let shortsSwipeUpNudgeCountKey = "shorts_swipe_up_nudge_on_x_cards"
private static let showOnboardingKey = "ios_show_onboarding"
private let delegates = MulticastDelegate<ConfigManagerDelegate>()
......@@ -44,12 +40,15 @@ public class ConfigManager {
public var config: AppConfig = AppConfig(popularCities: nil,
adConfig: AdConfig(),
ccpaUpdateInterval: nil,
nwsAlertsViaMoEngageEnabled: true,
showAttPrompt: false,
shortsLeftBelowCountKey: 0,
shortsLastNudgeEnabledKey: false,
shortsSwipeUpNudgeCountKey: 0,
showOnboarding: false)
explicitFeatureAvailability: [
.nwsAlertsViaMoEngage: true,
.attPrompt: false,
.shortsLastNudge: false,
.onboarding: false
]
)
public func updateConfig() {
log.info("update config")
......@@ -87,11 +86,23 @@ public class ConfigManager {
delegates.remove(delegate: delegate)
}
private func parseFeatureAvailability() -> [AppFeature: Bool] {
var featureAvailability = [AppFeature: Bool]()
for feature in AppFeature.allCases {
if let configName = feature.configVariableName {
let configValue = remoteConfig.configValue(forKey: configName)
featureAvailability[feature] = configValue.boolValue
}
}
return featureAvailability
}
private func parseConfigFromFirebase(source: String) {
log.info("Got config from \(source)")
var configErrors = [Error]()
let decoder = JSONDecoder()
var adConfig = AdConfig()
do {
let adConfigData = remoteConfig.configValue(forKey: ConfigManager.adConfigKey).dataValue
......@@ -123,34 +134,22 @@ public class ConfigManager {
if ccpaUpdateIntervalConfigValue.source == RemoteConfigSource.remote {
ccpaUpdateInterval = ccpaUpdateIntervalConfigValue.numberValue.doubleValue
}
let nwsAlertsViaMoEngageEnabledValue = remoteConfig.configValue(forKey: ConfigManager.nwsAlertsViaMoEngageEnabledKey)
let nwsAlertsViaMoEngageEnabled = nwsAlertsViaMoEngageEnabledValue.boolValue
let showAttPromptValue = remoteConfig.configValue(forKey: ConfigManager.showAttPromptKey)
let showAttPrompt = showAttPromptValue.boolValue
let shortsLeftBelowCountValue = remoteConfig.configValue(forKey: ConfigManager.shortsLeftBelowCountKey)
let shortsLeftBelowCount = shortsLeftBelowCountValue.numberValue.intValue
let shortsLastNudgeEnabledValue = remoteConfig.configValue(forKey: ConfigManager.shortsLastNudgeEnabledKey)
let shortsLastNudgeEnabled = shortsLastNudgeEnabledValue.boolValue
let shortsSwipeNudgeCountValue = remoteConfig.configValue(forKey: ConfigManager.shortsSwipeUpNudgeCountKey)
let shortsSwipeNudgeCount = shortsSwipeNudgeCountValue.numberValue.intValue
let showOnboardingValue = remoteConfig.configValue(forKey: ConfigManager.showOnboardingKey)
let showOnboarding = showOnboardingValue.boolValue
let featureAvailability = parseFeatureAvailability()
DispatchQueue.main.async {
self.config = AppConfig(popularCities: popularCities,
adConfig: adConfig,
ccpaUpdateInterval:ccpaUpdateInterval,
nwsAlertsViaMoEngageEnabled: nwsAlertsViaMoEngageEnabled,
showAttPrompt: showAttPrompt,
shortsLeftBelowCountKey: shortsLeftBelowCount,
shortsLastNudgeEnabledKey: shortsLastNudgeEnabled,
shortsSwipeUpNudgeCountKey: shortsSwipeNudgeCount,
showOnboarding: showOnboarding)
explicitFeatureAvailability: featureAvailability
)
self.notifyAboutConfigUpdate()
DispatchQueue.global().async {
let encoder = JSONEncoder()
......@@ -171,3 +170,21 @@ public class ConfigManager {
}
}
}
fileprivate extension AppFeature {
var configVariableName: String? {
switch self {
case .nwsAlertsViaMoEngage:
return "ios_nws_alerts_via_moengage_enabled"
case .attPrompt:
return "ios_show_att_prompt"
case .shortsLastNudge:
return "shorts_swipe_down_nudge_enabled"
case .onboarding:
return "ios_show_onboarding"
case .ads, .airQualityIndex, .minutelyForecast, .shorts:
return nil
// don't use 'default', so that we didn't add new features here in the future.
}
}
}
......@@ -56,8 +56,12 @@ public class PushNotificationsManager: NSObject, PushNotificationsManagerProtoco
internal var lastSetFIPSList: String? = ""
internal var lastSetFIPSCode: String? = ""
private var featureAvailabilityManager: FeatureAvailabilityManager {
FeatureAvailabilityManager.shared
}
private func updateNwsSubscriptions(with fipsList: String?, currentFipsCode: String?) {
if configManager.config.nwsAlertsViaMoEngageEnabled {
if featureAvailabilityManager.isAvailable(feature: .nwsAlertsViaMoEngage) {
if fipsList != lastSetFIPSList {
log.info("Set \(AnalyticsAttribute.fipsList.attributeName) to '\(fipsList ?? "")'")
lastSetFIPSList = fipsList
......
......@@ -20,8 +20,12 @@ class ShortsManager {
static let shared = ShortsManager()
let multicastDelegate = MulticastDelegate<ShortsManagerDelegate>()
private(set) var shorts = [ShortsItem]()
private var featureAvailabilityManager: FeatureAvailabilityManager {
FeatureAvailabilityManager.shared
}
var shortsAvailable: Bool {
return LocationManager.shared.selectedLocation?.countryCode == "US" && UIDevice.current.userInterfaceIdiom == .phone
return featureAvailabilityManager.isAvailable(feature: .shorts)
}
//Private
......
......@@ -50,6 +50,10 @@ class ShortsViewController: UIViewController {
return scheduler
}()
private var featureAvailabilityManager: FeatureAvailabilityManager {
FeatureAvailabilityManager.shared
}
deinit {
coordinator.viewControllerDidEnd(controller: self)
NotificationCenter.default.removeObserver(self)
......@@ -150,7 +154,7 @@ class ShortsViewController: UIViewController {
}
//Show the swipe helper view if needed
if ConfigManager.shared.config.shortsLastNudgeEnabled {
if featureAvailabilityManager.isAvailable(feature: .shortsLastNudge) {
//Check for the last row
if rowIndex == viewModel.shorts.count - 1 {
swipeHelperView.configure(forState: .downViewedAll)
......
......@@ -126,9 +126,13 @@ class MenuViewModel: NSObject, ViewModelProtocol {
// MARK: - Help section
extension MenuViewModel {
private var featureAvailabilityManager: FeatureAvailabilityManager {
FeatureAvailabilityManager.shared
}
private func helpRequestBodyString() -> String {
var str = String()
str.append("Weather Alerts Enabled: \(ConfigManager.shared.config.nwsAlertsViaMoEngageEnabled)\n")
str.append("Weather Alerts Enabled: \(featureAvailabilityManager.isAvailable(feature: .nwsAlertsViaMoEngage))\n")
let isRegistered = UIApplication.shared.isRegisteredForRemoteNotifications
str.append("Push Enabled: \(isRegistered)\n")
......
......@@ -35,6 +35,10 @@ class TodayViewModel: ViewModelProtocol {
shortsManager.shorts
}
private var featureAvailabilityManager: FeatureAvailabilityManager {
FeatureAvailabilityManager.shared
}
public lazy var todayCellFactory:TodayCellFactory = {
let factory = TodayCellFactory(viewModel: self)
factory.delegate = self
......@@ -127,7 +131,7 @@ class TodayViewModel: ViewModelProtocol {
// not calling onboardingFlowCompleted, because it will be called in the ATT prompt completion handler.
return
}
if self.configManager.config.showAttPrompt
if self.featureAvailabilityManager.isAvailable(feature: .attPrompt)
&& !self.ccpaHelper.isNewUser
&& ATTrackingManager.trackingAuthorizationStatus == .notDetermined {
......
......@@ -88,6 +88,7 @@
CE72A76A26D676A000F13CF7 /* USOnlyFeatureAvailabilityChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE72A76926D676A000F13CF7 /* USOnlyFeatureAvailabilityChecker.swift */; };
CE72A76C26D6782F00F13CF7 /* PremiumInAppFeatureAvailabilityChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE72A76B26D6782F00F13CF7 /* PremiumInAppFeatureAvailabilityChecker.swift */; };
CE72A76E26D680DF00F13CF7 /* ProSubscriptionAvailabilityChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE72A76D26D680DF00F13CF7 /* ProSubscriptionAvailabilityChecker.swift */; };
CE72A77026D6917300F13CF7 /* DeviceTypeFeatureAvailabilityChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE72A76F26D6917300F13CF7 /* DeviceTypeFeatureAvailabilityChecker.swift */; };
CEFE851826948C15003C67D3 /* SmartTextProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEFE851726948C15003C67D3 /* SmartTextProvider.swift */; };
CEFE851C2694986D003C67D3 /* SmartText.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEFE851B2694986D003C67D3 /* SmartText.swift */; };
CEFE85202694C4BC003C67D3 /* DefaultSmartText.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEFE851F2694C4BC003C67D3 /* DefaultSmartText.swift */; };
......@@ -198,6 +199,7 @@
CE72A76926D676A000F13CF7 /* USOnlyFeatureAvailabilityChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = USOnlyFeatureAvailabilityChecker.swift; sourceTree = "<group>"; };
CE72A76B26D6782F00F13CF7 /* PremiumInAppFeatureAvailabilityChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PremiumInAppFeatureAvailabilityChecker.swift; sourceTree = "<group>"; };
CE72A76D26D680DF00F13CF7 /* ProSubscriptionAvailabilityChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProSubscriptionAvailabilityChecker.swift; sourceTree = "<group>"; };
CE72A76F26D6917300F13CF7 /* DeviceTypeFeatureAvailabilityChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceTypeFeatureAvailabilityChecker.swift; sourceTree = "<group>"; };
CEFE851726948C15003C67D3 /* SmartTextProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmartTextProvider.swift; sourceTree = "<group>"; };
CEFE851B2694986D003C67D3 /* SmartText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmartText.swift; sourceTree = "<group>"; };
CEFE851D2694C477003C67D3 /* SmartTextMacro.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmartTextMacro.swift; sourceTree = "<group>"; };
......@@ -500,6 +502,7 @@
CE72A76926D676A000F13CF7 /* USOnlyFeatureAvailabilityChecker.swift */,
CE72A76B26D6782F00F13CF7 /* PremiumInAppFeatureAvailabilityChecker.swift */,
CE72A76D26D680DF00F13CF7 /* ProSubscriptionAvailabilityChecker.swift */,
CE72A76F26D6917300F13CF7 /* DeviceTypeFeatureAvailabilityChecker.swift */,
);
path = FeatureAvailabilityCheckers;
sourceTree = "<group>";
......@@ -763,6 +766,7 @@
CDFE459426566D7B0021A29F /* HealthSource.swift in Sources */,
CD615F9A265526E700B717DB /* CurrentWeather.swift in Sources */,
CD615F9B265526E700B717DB /* DailyWeather.swift in Sources */,
CE72A77026D6917300F13CF7 /* DeviceTypeFeatureAvailabilityChecker.swift in Sources */,
CD615F9C265526E700B717DB /* HourlyWeather.swift in Sources */,
CD615F9D265526E700B717DB /* DayTimeWeather.swift in Sources */,
CD615F9E265526E700B717DB /* Health.swift in Sources */,
......
//
// DeviceTypeFeatureAvailabilityChecker.swift
// OneWeatherCore
//
// Created by Demid Merzlyakov on 25.08.2021.
//
import UIKit
public struct DeviceTypeFeatureAvailabilityChecker: FeatureAvailabilityChecker {
let deviceType: UIUserInterfaceIdiom
public init(deviceType: UIUserInterfaceIdiom) {
self.deviceType = deviceType
}
public var isAvailable: Bool {
UIDevice.current.userInterfaceIdiom == deviceType
}
}
......@@ -11,9 +11,6 @@ public struct PremiumInAppFeatureAvailabilityChecker: FeatureAvailabilityChecker
public init() {}
public var isAvailable: Bool {
if let metricsLog = UserDefaults.standard.dictionary(forKey: kOLAppMetricsKey) {
return metricsLog[kEventInAppPurchasedCompleted] != nil
}
return false
isAppPro()
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment