Commit 38815a08 by Demid Merzlyakov

Merge branch 'release/5.0.2' into develop

parents 2d06a627 9d045ff3
......@@ -178,6 +178,7 @@
CE14445F2638B6CF008E2162 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE14445E2638B6CF008E2162 /* StoreKit.framework */; };
CE376C98261EE484000B1159 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CE376C97261EE484000B1159 /* LaunchScreen.storyboard */; };
CE3C1DB6265536360031BD72 /* AppsFlyerAnalyticsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE3C1DB5265536360031BD72 /* AppsFlyerAnalyticsService.swift */; };
CE46711F265FAD4300EAD03E /* AnalyticsAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE46711E265FAD4300EAD03E /* AnalyticsAttribute.swift */; };
CE578FE525FB415F00E8B85D /* CityCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE578FE225FB415F00E8B85D /* CityCell.swift */; };
CE578FE625FB415F00E8B85D /* LocationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE578FE325FB415F00E8B85D /* LocationViewController.swift */; };
CE578FE725FB415F00E8B85D /* LocationsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE578FE425FB415F00E8B85D /* LocationsViewModel.swift */; };
......@@ -429,6 +430,7 @@
CE14445E2638B6CF008E2162 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; };
CE376C97261EE484000B1159 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = "<group>"; };
CE3C1DB5265536360031BD72 /* AppsFlyerAnalyticsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppsFlyerAnalyticsService.swift; sourceTree = "<group>"; };
CE46711E265FAD4300EAD03E /* AnalyticsAttribute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsAttribute.swift; sourceTree = "<group>"; };
CE578FE225FB415F00E8B85D /* CityCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CityCell.swift; sourceTree = "<group>"; };
CE578FE325FB415F00E8B85D /* LocationViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocationViewController.swift; sourceTree = "<group>"; };
CE578FE425FB415F00E8B85D /* LocationsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocationsViewModel.swift; sourceTree = "<group>"; };
......@@ -1224,6 +1226,7 @@
CEF959972600C88100975FAA /* AnalyticsParameter.swift */,
CEF959922600C63500975FAA /* AppAnalytics.swift */,
CEF959952600C84D00975FAA /* Services */,
CE46711E265FAD4300EAD03E /* AnalyticsAttribute.swift */,
);
path = Analytics;
sourceTree = "<group>";
......@@ -1549,6 +1552,7 @@
CD32CE08260C743B00235081 /* MenuViewModel.swift in Sources */,
CD866A76260F77C500E96A5C /* SettingsDetailsCoordinator.swift in Sources */,
CE0457902632B3BC00B3C19A /* NotificationsViewModel.swift in Sources */,
CE46711F265FAD4300EAD03E /* AnalyticsAttribute.swift in Sources */,
CE13B81A262480B3007CBD4D /* AdManager.swift in Sources */,
CE13B76326246743007CBD4D /* CCPAHelper.swift in Sources */,
CDE2BF222609D4250085C930 /* ForecastWindSpeedCell.swift in Sources */,
......@@ -1926,6 +1930,7 @@
buildSettings = {
CODE_SIGN_ENTITLEMENTS = OneWeatherNotificationServiceExtension/OneWeatherNotificationServiceExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = SET_BY_BUILD_SCRIPT;
DEVELOPMENT_TEAM = 24W4XMQ38L;
INFOPLIST_FILE = OneWeatherNotificationServiceExtension/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.4;
......@@ -1949,6 +1954,7 @@
buildSettings = {
CODE_SIGN_ENTITLEMENTS = OneWeatherNotificationServiceExtension/OneWeatherNotificationServiceExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = SET_BY_BUILD_SCRIPT;
DEVELOPMENT_TEAM = 24W4XMQ38L;
INFOPLIST_FILE = OneWeatherNotificationServiceExtension/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.4;
......
......@@ -98,12 +98,12 @@ extension NativeAdItem: GADNativeAdDelegate {
}
extension NativeAdItem: GADBannerViewDelegate {
func adViewDidRecordImpression(_ bannerView: GADBannerView) {
func bannerViewDidRecordImpression(_ bannerView: GADBannerView) {
log.info("Impression recorded (banner)")
analytics(log: .ANALYTICS_AD_IMPRESSION)
}
func adViewWillPresentScreen(_ bannerView: GADBannerView) {
func bannerViewWillPresentScreen(_ bannerView: GADBannerView) {
log.info("Click recorded (will present screen)")
analytics(log: .ANALYTICS_AD_CLICKED)
}
......
//
// AnalyticsAttribute.swift
// 1Weather
//
// Created by Demid Merzlyakov on 27.05.2021.
//
import Foundation
public enum AnalyticsAttribute {
/// Format: Country Code:State Code:City name. Example: US:NY:New York, US:CA:San Francisco
case lastSeenCity
case fipsList
case lastSeenFipsCodeNonTracfone
/// True/False; Wether the splash screen has been shown to the user for the first time or not.
case splashFTUE
/// Use this instead of rawValue, because we might have different attributes with the same string value.
var attributeName: String {
switch self {
case .lastSeenCity:
return "LAST_SEEN_CITY"
case .fipsList:
return "FIPS_LIST"
case .lastSeenFipsCodeNonTracfone:
return "LAST_SEEN_FIPS_CODE_NON_TRACFONE"
case .splashFTUE:
return "SPLASH_FTUE"
}
}
}
......@@ -96,4 +96,7 @@ public enum AnalyticsEvent: String {
case ANALYTICS_D3_RETAINED = "D3_RETAINED"
/// FTUE Funnel: When the user opens the app for the first time
case ANALYTICS_FIRST_OPEN = "FIRST_OPEN"
/// Splash screen: Fired when a user sees the splash screen (for the first time)
case ANALYTICS_FTUE_SPLASH_SEEN = "FTUE_SPLASH_SEEN"
}
......@@ -10,3 +10,7 @@ import Foundation
public func analytics(log event: AnalyticsEvent, params: [AnalyticsParameter: Any]? = nil) {
AppAnalytics.shared.log(event: event, params: params)
}
public func analytics(set attribute: AnalyticsAttribute, to value: Any?) {
AppAnalytics.shared.set(attribute: attribute, to: value)
}
......@@ -41,4 +41,25 @@ public class AppAnalytics {
log.info("Event \(event.rawValue) sent to \(loggedToString)")
}
}
public func set(attribute: AnalyticsAttribute, to value: Any?) {
var servicesSetTo = [String]()
for service in services {
if let attributesWhitelist = service.attributesWhitelist {
if !attributesWhitelist.contains(attribute) {
continue
}
}
servicesSetTo.append(service.name)
service.set(attribute: attribute, to: value)
}
let servicesSetToString = servicesSetTo.joined(separator: ", ")
if let value = value {
log.info("Attribute \(attribute.attributeName) set to '\(value)' for \(servicesSetToString)")
}
else {
log.info("Attribute \(attribute.attributeName) set to nil for \(servicesSetToString)")
}
}
}
......@@ -10,7 +10,9 @@ import Foundation
public protocol AnalyticsService {
var name: String { get }
var eventsWhitelist: Set<AnalyticsEvent>? { get }
var attributesWhitelist: Set<AnalyticsAttribute>? { get }
func log(event: AnalyticsEvent, params: [AnalyticsParameter: Any]?)
func set(attribute: AnalyticsAttribute, to value: Any?)
}
extension AnalyticsService {
......
......@@ -11,8 +11,13 @@ import AppsFlyerLib
internal struct AppsFlyerAnalyticsService: AnalyticsService {
public let name: String = "AppsFlyer"
let eventsWhitelist: Set<AnalyticsEvent>? = [.ANALYTICS_APP_OPEN, .ANALYTICS_USER_QUALIFIED, .ANALYTICS_D3_RETAINED, .ANALYTICS_FIRST_OPEN]
let attributesWhitelist: Set<AnalyticsAttribute>? = [] // block all
func log(event: AnalyticsEvent, params: [AnalyticsParameter : Any]?) {
AppsFlyerLib.shared().logEvent(event.rawValue, withValues: stringKeyedParams(from: params))
}
func set(attribute: AnalyticsAttribute, to value: Any?) {
// do nothing
}
}
......@@ -11,8 +11,13 @@ import Flurry_iOS_SDK
internal struct FlurryAnalyticsService: AnalyticsService {
public let name = "Flurry"
let eventsWhitelist: Set<AnalyticsEvent>? = nil
let attributesWhitelist: Set<AnalyticsAttribute>? = [] // block all
func log(event: AnalyticsEvent, params: [AnalyticsParameter : Any]?) {
Flurry.logEvent(event.rawValue, withParameters: stringKeyedParams(from: params))
}
func set(attribute: AnalyticsAttribute, to value: Any?) {
// do nothing
}
}
......@@ -11,6 +11,7 @@ import MoEngage
internal struct MoEngageAnalyticsService: AnalyticsService {
public let name = "MoEngage"
let eventsWhitelist: Set<AnalyticsEvent>? = nil
let attributesWhitelist: Set<AnalyticsAttribute>? = nil // allow all
func log(event: AnalyticsEvent, params: [AnalyticsParameter : Any]?) {
let stringKeyedParams = stringKeyedParams(from: params)
......@@ -25,4 +26,8 @@ internal struct MoEngageAnalyticsService: AnalyticsService {
MoEngage.sharedInstance().trackEvent(event.rawValue, with: nil)
}
}
func set(attribute: AnalyticsAttribute, to value: Any?) {
MoEngage.sharedInstance().setUserAttribute(value, forKey: attribute.attributeName)
}
}
......@@ -33,7 +33,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
ThemeManager.refreshAppearance()
if let launchOptions = launchOptions {
log.debug("Launch options: \(launchOptions)")
}
......@@ -52,26 +51,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
// Note that the Amazon SDK will get assigned a proper value for consent status inside CCPAHelper.onAppLaunch
// So, make sure this call happens after the
CCPAHelper.shared.onAppLaunch()
if CCPAHelper.shared.isNewUser && Settings.shared.firstOpenDate == nil {
var firstOpenSource = FirstOpenSource.icon
if launchOptions?[.remoteNotification] != nil {
firstOpenSource = .pushNotification
}
else if launchOptions?[.url] != nil {
firstOpenSource = .url
}
analytics(log: .ANALYTICS_FIRST_OPEN, params: [.ANALYTICS_KEY_FIST_OPEN_SOURCE: firstOpenSource.rawValue])
Settings.shared.firstOpenDate = Date()
}
if let userQualifiedDate = Settings.shared.userQualifiedDate {
let timeSinceQualified = Date().timeIntervalSince(userQualifiedDate)
let day = TimeInterval(3600 * 24)
if timeSinceQualified >= 3 * day && timeSinceQualified < 6 * day {
analytics(log: .ANALYTICS_D3_RETAINED)
}
}
// As of DTB 3.4.6 the Amazon SDK freezes the main thread on startup for a couple of seconds, which got us rejected during an AppStore Review. We're going to move this initialization to the AppDelegate for now. The consent status is going to be set earlier within CCPAHelper.shared.onAppLaunch(), so make sure CCPAHelper's onAppLaunch is called before Amazon's setAppKey
// TODO: remove setAppKey from here, if Amazon has fixed the freeze on startup.
......@@ -112,7 +91,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
MoEngage.sharedInstance().initializeLive(with: moEngageConfig, andLaunchOptions: launchOptions)
#endif
analytics(log: .ANALYTICS_APP_OPEN)
logAppLaunchEvents(launchOptions: launchOptions)
return true
}
......@@ -138,6 +117,36 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
PushNotificationsManager.shared.set(pushToken: deviceToken)
}
private func logAppLaunchEvents(launchOptions: [UIApplication.LaunchOptionsKey: Any]?) {
let settings = Settings.shared
if settings.firstOpenDate == nil && CCPAHelper.shared.isNewUser {
var firstOpenSource = FirstOpenSource.icon
if launchOptions?[.remoteNotification] != nil {
firstOpenSource = .pushNotification
}
else if launchOptions?[.url] != nil {
firstOpenSource = .url
}
analytics(log: .ANALYTICS_FIRST_OPEN, params: [.ANALYTICS_KEY_FIST_OPEN_SOURCE: firstOpenSource.rawValue])
settings.firstOpenDate = Date()
}
if settings.d3RetentionDate == nil {
if let userQualifiedDate = Settings.shared.userQualifiedDate {
let timeSinceQualified = Date().timeIntervalSince(userQualifiedDate)
let day = TimeInterval(3600 * 24)
if timeSinceQualified >= 3 * day && timeSinceQualified < 6 * day {
analytics(log: .ANALYTICS_D3_RETAINED)
settings.d3RetentionDate = Date()
}
}
}
analytics(log: .ANALYTICS_APP_OPEN)
}
func initializeAppsFlyer() {
appsFlyerLog.info("AppsFlyer initialize with AppsFlyerId: \(kAppsFlyerId), Apple App ID: \(kAppsFlyerAppId)")
appsFlyer.appsFlyerDevKey = kAppsFlyerId
......
......@@ -132,7 +132,7 @@ class CCPAHelper {
// https://ams.amazon.com/webpublisher/uam/docs/web-integration-documentation/integration-guide/uam-ccpa.html?source=menu
if self.canCollectData == nil {
A9Cache.shared.ccpaPrivacyString = "1Y-"
A9Cache.shared.ccpaPrivacyString = "1\(self.shownPrivacyNoticeBefore ? "Y" : "N")-"
log.debug("Amazon A9: set consent status UNKNOWN")
DTBAds.sharedInstance().setConsentStatus(.UNKNOWN)
}
......
......@@ -91,7 +91,8 @@ public class LocationManager {
.ANALYTICS_KEY_LAST_SEEN_CITY_CITY: newLocation?.cityName ?? "",
.ANALYTICS_KEY_LAST_SEEN_CITY_CITY_ID: newLocation?.cityId ?? "::"
])
MoEngage.sharedInstance().setUserAttribute(newLocation?.cityId, forKey: "LAST_SEEN_CITY")
analytics(set: .lastSeenCity, to: newLocation?.cityId)
lastSeenCityId = newLocation?.cityId
}
}
......
......@@ -56,25 +56,24 @@ public class PushNotificationsManager: NSObject {
internal var lastSetFIPSCode: String? = ""
private func updateNwsSubscriptions(with fipsList: String?, currentFipsCode: String?) {
let lastFipsCodeAttributeName = "LAST_SEEN_FIPS_CODE_NON_TRACFONE"
if configManager.config.nwsAlertsViaMoEngageEnabled {
if fipsList != lastSetFIPSList {
log.info("Set FIPS_LIST to '\(fipsList ?? "")'")
log.info("Set \(AnalyticsAttribute.fipsList.attributeName) to '\(fipsList ?? "")'")
lastSetFIPSList = fipsList
}
if currentFipsCode != lastSetFIPSCode {
log.info("Set \(lastFipsCodeAttributeName) to '\(currentFipsCode ?? "")'")
log.info("Set \(AnalyticsAttribute.lastSeenFipsCodeNonTracfone.attributeName) to '\(currentFipsCode ?? "")'")
lastSetFIPSCode = currentFipsCode
}
}
else {
log.info("Set FIPS_LIST to nil, because nwsAlertsViaMoEngage are disabled via config.")
log.info("Set \(AnalyticsAttribute.fipsList.attributeName) to nil, because nwsAlertsViaMoEngage are disabled via config.")
lastSetFIPSList = nil
log.info("Set \(lastFipsCodeAttributeName) to nil, because nwsAlertsViaMoEngage are disabled via config")
log.info("Set \(AnalyticsAttribute.lastSeenFipsCodeNonTracfone.attributeName) to nil, because nwsAlertsViaMoEngage are disabled via config")
lastSetFIPSCode = nil
}
MoEngage.sharedInstance().setUserAttribute(lastSetFIPSList, forKey: "FIPS_LIST")
MoEngage.sharedInstance().setUserAttribute(lastSetFIPSCode, forKey: lastFipsCodeAttributeName)
analytics(set: .fipsList, to: lastSetFIPSList)
analytics(set: .lastSeenFipsCodeNonTracfone, to: lastSetFIPSCode)
}
public func updateNwsSubscriptions(for locations: [Location], selectedLocation: Location?) {
......
......@@ -59,6 +59,8 @@ class SplashAnimationViewController: UIViewController {
}
private func noCityAnimation() {
analytics(log: .ANALYTICS_FTUE_SPLASH_SEEN)
analytics(set: .splashFTUE, to: true)
self.animationView?.play(completion: {[weak self] _ in
DispatchQueue.global().asyncAfter(deadline: .now() + 0.5) {
self?.appCoordinator.finishAnimation()
......
......@@ -89,6 +89,9 @@ public class Settings {
@UserDefaultsOptionalValue("firstOpenDate")
public var firstOpenDate: Date?
@UserDefaultsOptionalValue("d3RetentionDate")
public var d3RetentionDate: Date?
@UserDefaultsBasicValue(key: "locationDidAdded")
public var locationDidAdded:Bool = false
......
......@@ -19,7 +19,7 @@
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>1</string>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
......
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