Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TW-1305 Add Persona ads #1045

Merged
merged 31 commits into from
Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
1336a62
TW-1307 Integrate Hypelab ads
keshan3262 Feb 22, 2024
9377875
TW-1307 Add analytics for new ads
keshan3262 Feb 26, 2024
b326bec
TW-1307 Add new env variables to Github actions
keshan3262 Feb 26, 2024
a5a6f2d
TW-1307 Rename some components
keshan3262 Feb 26, 2024
2bd8ff6
TW-1307 Other refactoring
keshan3262 Feb 26, 2024
e58d955
Merge branch 'development' of https://github.com/madfish-solutions/te…
keshan3262 Feb 27, 2024
76ba8d9
TW-1307 Use SWR instead of Redux to fetch Optimal ads
keshan3262 Feb 27, 2024
8ddafa0
TW-1307 Refactoring according to comments
keshan3262 Feb 28, 2024
e472fe4
TW-1307 Resolve merge conflicts
keshan3262 Feb 29, 2024
1043355
TW-1307 Remove unused exports
keshan3262 Feb 29, 2024
066c534
TW-1307 Revert commenting out a warning
keshan3262 Feb 29, 2024
beeefdb
TW-1307 Additional changes according to the comments
keshan3262 Feb 29, 2024
279096d
TW-1307 Fix the bugs that are specified in the task
keshan3262 Mar 1, 2024
908385b
TW-1305 Add Persona ads
keshan3262 Mar 4, 2024
b1c26dc
TW-1305 Add a final newline
keshan3262 Mar 4, 2024
8df0d43
TW-1305 Remove a redundant export
keshan3262 Mar 4, 2024
24d8339
TW-1305 Resolve merge conflicts
keshan3262 Mar 4, 2024
49a8665
TW-1305 Remove an unused file
keshan3262 Mar 4, 2024
6b86764
TW-1305 Resolve merge conflicts
keshan3262 Mar 5, 2024
622e969
TW-1305 Refactoring according to comments
keshan3262 Mar 7, 2024
faa8fa4
TW-1305 Resolve merge conflicts
keshan3262 Mar 11, 2024
9846dc5
TW-1305 Minor refactoring
keshan3262 Mar 11, 2024
cbcd0cb
TW-1305 Add some forgotten changes
keshan3262 Mar 11, 2024
cdc29ca
TW-1305 Add PERSONA_PLACEMENT_SLUG env variable to workflows
keshan3262 Mar 12, 2024
6393019
Merge branch 'development' of https://github.com/madfish-solutions/te…
keshan3262 Mar 21, 2024
5777a90
TW-1305 Increase pods deployment target version
keshan3262 Mar 22, 2024
0c03b32
TW-1305 Use StyleSheet.create for WebViewPromotion styles
keshan3262 Mar 22, 2024
27e1bd5
Merge branch 'development' of https://github.com/madfish-solutions/te…
keshan3262 Mar 25, 2024
a4cde80
TW-1305 Revert Podfile changes
keshan3262 Mar 25, 2024
dd7d9bd
TW-1305 Fix determination of ads visibility at 'Notifications' page
keshan3262 Mar 26, 2024
33154a5
TW-1305 Resolve merge conflicts
keshan3262 Mar 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env.dist
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,5 @@ DYNAMIC_LINKS_DOMAIN_URI_PREFIX=https://templenft.page.link
HYPELAB_AD_FRAME_URL=
HYPELAB_SMALL_PLACEMENT_SLUG=
HYPELAB_NATIVE_PLACEMENT_SLUG=

PERSONA_PLACEMENT_SLUG=
182 changes: 23 additions & 159 deletions src/components/hypelab-promotion/index.tsx
Original file line number Diff line number Diff line change
@@ -1,172 +1,36 @@
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';
import { LayoutChangeEvent, LayoutRectangle, View } from 'react-native';
import { WebView, WebViewMessageEvent } from 'react-native-webview';
import React, { memo } from 'react';

import { Icon } from 'src/components/icon/icon';
import { IconNameEnum } from 'src/components/icon/icon-name.enum';
import { ImagePromotionView } from 'src/components/image-promotion-view';
import { TextPromotionItemSelectors } from 'src/components/text-promotion-view/selectors';
import { TouchableWithAnalytics } from 'src/components/touchable-with-analytics';
import { layoutScale } from 'src/config/styles';
import { AdFrameMessageType } from 'src/enums/ad-frame-message-type.enum';
import { PromotionProviderEnum } from 'src/enums/promotion-provider.enum';
import { PromotionVariantEnum } from 'src/enums/promotion-variant.enum';
import { ThemesEnum } from 'src/interfaces/theme.enum';
import { useThemeSelector } from 'src/store/settings/settings-selectors';
import { formatSize } from 'src/styles/format-size';
import { useColors } from 'src/styles/use-colors';
import { AdFrameMessage, SingleProviderPromotionProps } from 'src/types/promotion';
import { AnalyticsEventCategory } from 'src/utils/analytics/analytics-event.enum';
import { useAnalytics } from 'src/utils/analytics/use-analytics.hook';
import { HYPELAB_AD_FRAME_URL, HYPELAB_NATIVE_PLACEMENT_SLUG, HYPELAB_SMALL_PLACEMENT_SLUG } from 'src/utils/env.utils';
import { useTimeout } from 'src/utils/hooks';
import { SingleProviderPromotionProps } from 'src/types/promotion';
import { HYPELAB_NATIVE_PLACEMENT_SLUG, HYPELAB_SMALL_PLACEMENT_SLUG } from 'src/utils/env.utils';
import { isDefined } from 'src/utils/is-defined';
import { isString } from 'src/utils/is-string';
import { openUrl } from 'src/utils/linking';

import { useHypelabPromotionStyles } from './styles';
import { WebViewPromotion } from '../webview-promotion';

const AD_CONTENT_RELATED_URL_SEARCH_PARAMS = ['campaign_slug', 'creative_set_slug', 'placement_slug'];

export const HypelabPromotion: FC<SingleProviderPromotionProps> = ({
variant,
isVisible,
shouldShowCloseButton,
onClose,
onReady,
onError,
...testIDProps
}) => {
const { testID, testIDProperties } = testIDProps;
const isImageAd = variant === PromotionVariantEnum.Image;
const colors = useColors();
const styles = useHypelabPromotionStyles();
const theme = useThemeSelector();
const { trackEvent } = useAnalytics();
const [adHref, setAdHref] = useState<string>();

const [layoutRect, setLayoutRect] = useState<LayoutRectangle | undefined>();
const initialSize = useMemo(() => {
if (isImageAd) {
return { w: 320, h: 50 };
}

return layoutRect ? { w: Math.round(layoutRect.width / layoutScale), h: 80 } : undefined;
}, [isImageAd, layoutRect]);
const [size, setSize] = useState(initialSize);
useEffect(() => void (initialSize && setSize(prevSize => prevSize ?? initialSize)), [initialSize]);

const adFrameSource = useMemo(() => {
const placementSlug = isImageAd ? HYPELAB_SMALL_PLACEMENT_SLUG : HYPELAB_NATIVE_PLACEMENT_SLUG;
const origin = theme === ThemesEnum.dark ? 'mobile-dark' : 'mobile-light';

if (!initialSize) {
return undefined;
}

const searchParams = new URLSearchParams({
p: placementSlug,
o: origin,
vw: formatSize(Number(initialSize.w)).toString(),
w: Number(initialSize.w).toString(),
h: Number(initialSize.h).toString()
});

return { uri: `${HYPELAB_AD_FRAME_URL}/?${searchParams.toString()}` };
}, [isImageAd, initialSize, theme]);

const handleMainLayout = useCallback((e: LayoutChangeEvent) => {
e.persist();
setLayoutRect(e.nativeEvent.layout);
}, []);
const adChanged = (prevUrl: string | undefined, newUrl: string) => {
const prevAdHrefSearchParams = isDefined(prevUrl) ? new URL(prevUrl).searchParams : new URLSearchParams();
const newAdHrefSearchParams = new URL(newUrl).searchParams;

useTimeout(
() => {
if (!isString(adHref)) {
onError();
}
},
30000,
[adHref, onError]
return AD_CONTENT_RELATED_URL_SEARCH_PARAMS.some(
paramName => prevAdHrefSearchParams.get(paramName) !== newAdHrefSearchParams.get(paramName)
);
};

const handleAdFrameMessage = useCallback(
(e: WebViewMessageEvent) => {
try {
const message: AdFrameMessage = JSON.parse(e.nativeEvent.data);

switch (message.type) {
case AdFrameMessageType.Resize:
setSize({ w: Math.round(message.width / layoutScale), h: Math.round(message.height / layoutScale) });
break;
case AdFrameMessageType.Ready:
const prevAdHrefSearchParams = isDefined(adHref) ? new URL(adHref).searchParams : new URLSearchParams();
const newAdHrefSearchParams = new URL(message.ad.cta_url).searchParams;
setAdHref(message.ad.cta_url);
if (
AD_CONTENT_RELATED_URL_SEARCH_PARAMS.some(
paramName => prevAdHrefSearchParams.get(paramName) !== newAdHrefSearchParams.get(paramName)
)
) {
onReady();
}
break;
case AdFrameMessageType.Error:
onError();
break;
case AdFrameMessageType.Click:
if (isString(adHref)) {
trackEvent(testID, AnalyticsEventCategory.ButtonPress, testIDProperties);
openUrl(adHref);
}
}
} catch (err) {
console.error(err);
}
},
[adHref, onError, onReady, testID, testIDProperties, trackEvent]
);

const webViewCommonProps = {
source: adFrameSource,
onError: onError,
onMessage: handleAdFrameMessage,
webviewDebuggingEnabled: __DEV__,
scrollEnabled: false,
scalesPageToFit: false,
textZoom: 100
};

if (isImageAd) {
return (
<ImagePromotionView
onClose={onClose}
shouldShowCloseButton={shouldShowCloseButton}
href={adHref ?? ''}
isVisible={isVisible}
shouldShowAdBage
{...testIDProps}
>
<View style={styles.imageAdFrameWrapper}>
<WebView {...webViewCommonProps} containerStyle={styles.imageAdFrame} />
</View>
</ImagePromotionView>
);
}
export const HypelabPromotion = memo<SingleProviderPromotionProps>(({ variant, ...restProps }) => {
const isImageAd = variant === PromotionVariantEnum.Image;

return (
<View style={[styles.textAdFrameContainer, !isVisible && styles.invisible]} onLayout={handleMainLayout}>
{adFrameSource && size && (
<WebView {...webViewCommonProps} containerStyle={[styles.textAdFrame, { aspectRatio: size.w / size.h }]} />
)}
{shouldShowCloseButton && (
<TouchableWithAnalytics
style={styles.closeButton}
onPress={onClose}
testID={TextPromotionItemSelectors.closeButton}
>
<Icon name={IconNameEnum.X} size={formatSize(16)} color={colors.peach} />
</TouchableWithAnalytics>
)}
</View>
<WebViewPromotion
{...restProps}
variant={variant}
provider={PromotionProviderEnum.HypeLab}
placementSlug={isImageAd ? HYPELAB_SMALL_PLACEMENT_SLUG : HYPELAB_NATIVE_PLACEMENT_SLUG}
initialOriginalWidth={isImageAd ? 320 : undefined}
initialOriginalHeight={isImageAd ? 50 : 80}
adChanged={adChanged}
/>
);
};
});
27 changes: 27 additions & 0 deletions src/components/persona-promotion/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React, { memo } from 'react';

import { WebViewPromotion } from 'src/components/webview-promotion';
import { PromotionProviderEnum } from 'src/enums/promotion-provider.enum';
import { PromotionVariantEnum } from 'src/enums/promotion-variant.enum';
import { SingleProviderPromotionProps } from 'src/types/promotion';
import { PERSONA_PLACEMENT_SLUG } from 'src/utils/env.utils';

const adChanged = () => true;

export const PersonaPromotion = memo<SingleProviderPromotionProps>(({ variant, ...restProps }) => {
if (variant === PromotionVariantEnum.Text) {
return null;
}

return (
<WebViewPromotion
{...restProps}
variant={variant}
provider={PromotionProviderEnum.Persona}
placementSlug={PERSONA_PLACEMENT_SLUG}
initialOriginalWidth={321}
initialOriginalHeight={101}
adChanged={adChanged}
/>
);
});
53 changes: 32 additions & 21 deletions src/components/promotion-item/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { StyleProp, View, ViewProps, ViewStyle } from 'react-native';
import { ActivityIndicator } from 'src/components/activity-indicator';
import { HypelabPromotion } from 'src/components/hypelab-promotion';
import { OptimalPromotion } from 'src/components/optimal-promotion';
import { PersonaPromotion } from 'src/components/persona-promotion';
import { PROMO_SYNC_INTERVAL } from 'src/config/fixed-times';
import { isAndroid } from 'src/config/system';
import { PromotionProviderEnum } from 'src/enums/promotion-provider.enum';
Expand All @@ -21,7 +22,7 @@ interface Props extends TestIdProps {
style?: StyleProp<ViewStyle>;
shouldRefreshAd?: boolean;
shouldShowCloseButton?: boolean;
shouldTryHypelabAd?: boolean;
onlyOptimalAd?: boolean;
variant?: PromotionVariantEnum;
onError?: EmptyFn;
onLoad?: SyncFn<PromotionProviderEnum>;
Expand All @@ -36,7 +37,7 @@ export const PromotionItem = forwardRef<View, Props>(
shouldRefreshAd = false,
shouldShowCloseButton = true,
variant = PromotionVariantEnum.Image,
shouldTryHypelabAd = true,
onlyOptimalAd = false,
onError,
onLoad,
onLayout,
Expand Down Expand Up @@ -89,12 +90,19 @@ export const PromotionItem = forwardRef<View, Props>(
}, [onError]);

const handleOptimalError = useCallback(() => {
if (!shouldTryHypelabAd) {
if (onlyOptimalAd) {
handleAdError();
} else {
setAdsState(prevState => ({ ...prevState, currentProvider: PromotionProviderEnum.HypeLab }));
}
setAdsState(prevState => ({ ...prevState, currentProvider: PromotionProviderEnum.HypeLab }));
}, [handleAdError, shouldTryHypelabAd]);
const handleHypelabError = useCallback(() => handleAdError(), [handleAdError]);
}, [handleAdError, onlyOptimalAd]);
const handleHypelabError = useCallback(() => {
if (variant === PromotionVariantEnum.Text) {
handleAdError();
} else {
setAdsState(prevState => ({ ...prevState, currentProvider: PromotionProviderEnum.Persona }));
}
}, [handleAdError, variant]);

const handleAdReadyFactory = useCallback(
(provider: PromotionProviderEnum) => () => {
Expand All @@ -111,11 +119,23 @@ export const PromotionItem = forwardRef<View, Props>(
() => handleAdReadyFactory(PromotionProviderEnum.HypeLab),
[handleAdReadyFactory]
);
const handlePersonaAdReady = useMemo(
() => handleAdReadyFactory(PromotionProviderEnum.Persona),
[handleAdReadyFactory]
);

if (!partnersPromotionEnabled || adError || isHiddenTemporarily) {
return null;
}

const promotionCommonProps = {
...testIDProps,
variant: variant,
isVisible: adIsReady,
shouldShowCloseButton: shouldShowCloseButton,
onClose: hidePromotion
};

return (
<View
style={[
Expand All @@ -129,26 +149,17 @@ export const PromotionItem = forwardRef<View, Props>(
>
{currentProvider === PromotionProviderEnum.Optimal && isFocused && (
<OptimalPromotion
{...testIDProps}
variant={variant}
isVisible={adIsReady}
shouldShowCloseButton={shouldShowCloseButton}
{...promotionCommonProps}
shouldRefreshAd={shouldRefreshAd}
onClose={hidePromotion}
onReady={handleOptimalAdReady}
onError={handleOptimalError}
/>
)}
{currentProvider === PromotionProviderEnum.HypeLab && shouldTryHypelabAd && isFocused && (
<HypelabPromotion
{...testIDProps}
variant={variant}
isVisible={adIsReady}
shouldShowCloseButton={shouldShowCloseButton}
onClose={hidePromotion}
onReady={handleHypelabAdReady}
onError={handleHypelabError}
/>
{currentProvider === PromotionProviderEnum.HypeLab && isFocused && (
<HypelabPromotion {...promotionCommonProps} onReady={handleHypelabAdReady} onError={handleHypelabError} />
)}
{currentProvider === PromotionProviderEnum.Persona && isFocused && (
<PersonaPromotion {...promotionCommonProps} onReady={handlePersonaAdReady} onError={handleAdError} />
)}
{!adIsReady && (
<View style={styles.loaderContainer}>
Expand Down
Loading
Loading