// // arcade-upsell.ts // AppStoreKit // // Created by Jonathan Ellenbogen on 11/19/19. // Copyright (c) 2016 Apple Inc. All rights reserved. // import * as models from "../../api/models"; import * as serverData from "../../foundation/json-parsing/server-data"; import * as mediaAttributes from "../../foundation/media/attributes"; import * as color from "../../foundation/util/color-util"; import * as heroCommon from "../grouping/hero/hero-common"; import * as metricsHelpersClicks from "../metrics/helpers/clicks"; import * as metricsHelpersImpressions from "../metrics/helpers/impressions"; import * as metricsHelpersLocation from "../metrics/helpers/location"; import * as metricsHelpersUtil from "../metrics/helpers/util"; import * as offers from "../offers/offers"; import * as breakoutsCommon from "./breakouts-common"; import { ArcadeSubscriptionStateAction } from "../../api/models"; import { isFeatureEnabledForCurrentUser } from "../util/lottery"; export function createUpsellBreakout(objectGraph, upsellData, metricsOptions, style = "white") { if (serverData.isNullOrEmpty(upsellData) || (serverData.isNullOrEmpty(upsellData.attributes) && !objectGraph.client.isVision)) { return null; } const heroVideoData = heroCommon.heroVideoFromData(objectGraph, upsellData, true); const heroArtworkData = heroCommon.heroArtworkFromData(objectGraph, upsellData); const backgroundColor = heroVideoData.backgroundColor || heroArtworkData.backgroundColor; const detailPosition = breakoutsCommon.detailPositionFromData(objectGraph, upsellData, true, true); const supportsMaterial = objectGraph.client.isTV; const wantsBlur = breakoutsCommon.wantsBlur(objectGraph, breakoutsCommon.detailBackgroundStyleFromData(objectGraph, upsellData, supportsMaterial, true, true), true); const details = createBreakoutDetailsFromData(objectGraph, upsellData, null, supportsMaterial, wantsBlur, "wordmark"); let buttonCallToAction = null; if (objectGraph.client.deviceType !== "tv") { buttonCallToAction = details.description; details.description = null; } const offerTint = offerTintFromMarketingItem(objectGraph, upsellData); const offerDisplayProperties = new models.OfferDisplayProperties("arcade", objectGraph.bag.arcadeAppAdamId, null, style, null, "arcade", offerTint, null, null, null, null, null, null, null, null, null, null, null, objectGraph.bag.arcadeProductFamilyId); const displayProperties = { backgroundColor: backgroundColor, wantsMaterialDetailBackground: false, wantsBlur: wantsBlur, badgeColor: null, titleColor: null, descriptionColor: null, callToActionColor: null, textAlignment: null, detailsPosition: detailPosition, }; const upsellBreakout = new models.UpsellBreakout(details, offerDisplayProperties, displayProperties, null, buttonCallToAction, heroArtworkData.artwork, heroVideoData.video); const impressionOptions = metricsHelpersImpressions.impressionOptions(objectGraph, upsellData, upsellBreakout.details.title, { ...metricsOptions, targetType: "upsellBreakout", }); impressionOptions.displaysArcadeUpsell = true; metricsHelpersImpressions.addImpressionFields(objectGraph, upsellBreakout, impressionOptions); // Push the breakout here so that the click action has the breakout in its location // but we do not want to add it to the overall location tracker, so pop it right after adding it to the button // action // Metrics: Arcade: Container values requested in Location field metricsHelpersLocation.pushContentLocation(objectGraph, impressionOptions, upsellBreakout.details.title); upsellBreakout.offerButtonAction = arcadeOfferButtonActionFromData(objectGraph, upsellData, models.marketingItemContextFromString("arcadeTabHeader"), metricsOptions); metricsHelpersLocation.popLocation(metricsOptions.locationTracker); return upsellBreakout; } export function pageTitleEffectFromData(objectGraph, upsellData) { const heroVideoData = heroCommon.heroVideoFromData(objectGraph, upsellData, true); const heroArtworkData = heroCommon.heroArtworkFromData(objectGraph, upsellData); const titleEffectArtwork = heroVideoData.artworkData || heroArtworkData.artworkData; return breakoutsCommon.titleEffectFromArtwork(objectGraph, titleEffectArtwork, true); } export function callToActionLabelFromData(objectGraph, data) { return breakoutsCommon.callToActionLabelFromData(objectGraph, data); } export function artworkDictionaryFromData(objectGraph, data) { return breakoutsCommon.artworkDictionaryFromData(objectGraph, data); } export function artworkDataFromData(objectGraph, data, attributePath) { const dictionary = artworkDictionaryFromData(objectGraph, data); return serverData.asDictionary(dictionary, attributePath); } export function titleFromData(objectGraph, data) { return mediaAttributes.attributeAsString(data, "title"); } export function descriptionFromData(objectGraph, data) { return mediaAttributes.attributeAsString(data, "subtitle"); } export function createBreakoutDetailsFromData(objectGraph, data, contextualAppDataOrNull, supportsMaterial, wantsBlur, badgeType) { let badge = null; switch (badgeType) { case "wordmark": badge = { type: "wordmark", }; break; case "text": const badgeTitle = mediaAttributes.attributeAsString(data, "badge"); if ((badgeTitle === null || badgeTitle === void 0 ? void 0 : badgeTitle.length) > 0) { badge = { type: "text", title: badgeTitle, }; } else { badge = { type: "none", }; } break; default: badge = { type: "none", }; break; } let backgroundStyle = breakoutsCommon.detailBackgroundStyleFromData(objectGraph, data, supportsMaterial, true, true); const detailPosition = breakoutsCommon.detailPositionFromData(objectGraph, data, true, true); if (wantsBlur) { backgroundStyle = "dark"; } const details = new models.BreakoutDetails(titleFromData(objectGraph, data), descriptionFromData(objectGraph, data), badge, null, backgroundStyle, breakoutsCommon.detailTextAlignmentForDetailPosition(objectGraph, detailPosition, data, true)); return details; } // region Offer Action export function arcadeOfferButtonActionFromData(objectGraph, upsellData, context, metricsOptions) { const arcadeUpsellData = { marketingItemData: upsellData, }; // "arcadeTabHeader" context covers both hero and navbar button on the Arcade page const showStarterPackOnboarding = context === models.marketingItemContextFromString("arcadeTabHeader") && objectGraph.bag.arcadeDownloadPackPostSubscribeTrigger && isFeatureEnabledForCurrentUser(objectGraph, objectGraph.bag.arcadeDownloadPackRolloutRate); return arcadeActionFromUpsellData(objectGraph, arcadeUpsellData, callToActionLabelFromData(objectGraph, upsellData), metricsOptions, showStarterPackOnboarding); } export function arcadeActionFromUpsellData(objectGraph, data, title, baseMetricsOptions, showStarterPackOnboarding) { const marketingItemData = data.marketingItemData; if (!serverData.isDefinedNonNull(marketingItemData)) { return null; } const offerData = offers.offerDataFromMarketingItem(objectGraph, marketingItemData); const isLinkAction = serverData.asString(offerData, "kind") === "link"; const linkUrlString = serverData.asString(offerData, "url"); if (isLinkAction && linkUrlString) { const linkAction = arcadeLinkActionFromMarketingItemLinkUrl(objectGraph, linkUrlString); const linkClickOptions = { id: objectGraph.bag.arcadeAppAdamId, actionType: "buy", actionContext: "Arcade", contextualAdamId: objectGraph.bag.arcadeAppAdamId, offerType: "subscribe", targetType: "button", mercuryMetricsData: metricsHelpersUtil.marketingItemTopLevelBaseFieldsFromData(objectGraph, marketingItemData), ...baseMetricsOptions, }; linkAction.title = title; metricsHelpersClicks.addClickEventToAction(objectGraph, linkAction, linkClickOptions); return linkAction; } else { const arcadeAction = arcadeBuyActionFromMarketingItemData(objectGraph, marketingItemData, title, baseMetricsOptions); if (showStarterPackOnboarding) { const downloadPackOnboarding = new models.FlowAction("arcadeDownloadPackCategories"); const subscriptionStatus = "new"; downloadPackOnboarding.pageData = subscriptionStatus; downloadPackOnboarding.presentationContext = "presentModalFormSheet"; // Passing `undefined` for other than subscribed actions instead of `BlankAction` to record an error. const subscriptionCheckAction = new ArcadeSubscriptionStateAction(undefined, undefined, downloadPackOnboarding, undefined); const action = new models.CompoundAction([arcadeAction, subscriptionCheckAction]); action.title = title; return action; } else { return arcadeAction; } } } function arcadeLinkActionFromMarketingItemLinkUrl(objectGraph, urlString) { /** * Check against the bag routing regex and dispatch one of the AMS actions. */ // Dynamic UI const dynamicUIRegexPatterns = objectGraph.bag.dynamicUIRegexStrings; for (const pattern of dynamicUIRegexPatterns) { const dynamicUIPattern = new RegExp(pattern); if (dynamicUIPattern.test(urlString)) { const action = new models.FlowAction("dynamicUI", urlString); action.pageData = new models.DynamicUIRequestInfo(objectGraph.bag.metricsTopic); return action; } } // Finance UI regex check const financeUIRegexPatterns = objectGraph.bag.financeUIRegexStrings; for (const pattern of financeUIRegexPatterns) { const financeUIPattern = new RegExp(pattern); if (financeUIPattern.test(urlString)) { return new models.FlowAction("finance", urlString); } } // Web UI regex check const webViewRegexPatterns = objectGraph.bag.webViewRegexStrings; for (const pattern of webViewRegexPatterns) { const webViewPattern = new RegExp(pattern); if (webViewPattern.test(urlString)) { return new models.FlowAction("webView", urlString); } } return new models.ExternalUrlAction(urlString, false); } function arcadeBuyActionFromMarketingItemData(objectGraph, data, title, baseMetricsOptions) { const offerData = offers.offerDataFromMarketingItem(objectGraph, data); const offerName = serverData.asString(offerData, "offerName"); const buyParams = serverData.asString(offerData, "buyParams"); if (!serverData.isDefinedNonNull(offerName) || !serverData.isDefinedNonNull(buyParams)) { return null; } const offerServiceTypes = serverData.asArrayOrEmpty(offerData, "serviceTypes"); const isAristotleOffer = offerServiceTypes.length > 1 && objectGraph.bag.aristotleParentAppAdamId; const parentAdamId = isAristotleOffer ? objectGraph.bag.aristotleParentAppAdamId : objectGraph.bag.arcadeAppAdamId; const arcadeAction = new models.ArcadeAction(offerName, parentAdamId, { buyParams: buyParams, productIdentifier: offerName, pageInformation: baseMetricsOptions.pageInformation, }); const metricsClickOptions = { id: parentAdamId, actionType: "buy", targetType: "button", subscriptionSKU: offerName, actionContext: "Arcade", contextualAdamId: parentAdamId, actionDetails: { buyParams: buyParams }, offerType: "subscribe", mercuryMetricsData: metricsHelpersUtil.marketingItemTopLevelBaseFieldsFromData(objectGraph, data), ...baseMetricsOptions, }; arcadeAction.title = title; metricsHelpersClicks.addClickEventToAction(objectGraph, arcadeAction, metricsClickOptions); return arcadeAction; } // endregion // region Helpers function offerTintFromMarketingItem(objectGraph, data) { const templateParameters = breakoutsCommon.templateParametersFromData(objectGraph, data); const fillColor = serverData.asString(templateParameters, "ctaButtonBackgroundColor"); const textColor = serverData.asString(templateParameters, "ctaButtonTextColor"); if (!serverData.isDefinedNonNull(fillColor) || !serverData.isDefinedNonNull(textColor)) { return { type: "blue", fillColor: null, textColor: null }; } return { type: "custom", fillColor: color.fromHex(fillColor), textColor: color.fromHex(textColor), }; } // endregion //# sourceMappingURL=arcade-upsell.js.map