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

✨ [AMP Story Paywall] Enable developers to specify a custom subscriptions page index #38175

Merged
merged 11 commits into from
May 16, 2022
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import * as Preact from '#core/dom/jsx';
import {Layout_Enum} from '#core/dom/layout';
import {setImportantStyles} from '#core/dom/style';
import {clamp} from '#core/math';

import {Services} from '#service';
import {LocalizedStringId_Enum} from '#service/localization/strings';

import {dev} from '#utils/log';
import {dev, devAssert} from '#utils/log';

import {CSS} from '../../../build/amp-story-subscriptions-0.1.css';
import {
Expand All @@ -19,6 +20,12 @@ import {getStoryAttributeSrc} from '../../amp-story/1.0/utils';

const TAG = 'amp-story-subscriptions';

/**
* The index of the page where the paywall would be triggered.
* @const {number}
*/
export const DEFAULT_SUBSCRIPTIONS_PAGE_INDEX = 2;

/**
* The number of milliseconds to wait before showing the skip button on dialog banner.
* @const {number}
Expand Down Expand Up @@ -62,6 +69,27 @@ export class AmpStorySubscriptions extends AMP.BaseElement {
this.subscriptionService_ = subscriptionService;
this.localizationService_ = localizationService;

const pages = this.win.document.querySelectorAll(
'amp-story-page:not([ad])'
);
devAssert(
pages.length >= 4,
'The number of pages should be at least 4 to enable subscriptions feature, got %s',
pages.length
);

const subscriptionsPageIndex = this.element.getAttribute(
'subscriptions-page-index'
);
this.storeService_.dispatch(
Action.SET_SUBSCRIPTIONS_PAGE_INDEX,
clamp(
subscriptionsPageIndex,
ychsieh marked this conversation as resolved.
Show resolved Hide resolved
DEFAULT_SUBSCRIPTIONS_PAGE_INDEX,
pages.length - 1
)
);

// Get grant status immediately to set up the initial subscriptions state.
this.getGrantStatusAndUpdateState_();
// When the user finishes any of the actions, e.g. log in or subscribe, new entitlements would be
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ describes.realWin(
<amp-story-subscriptions layout="container"> </amp-story-subscriptions>
);
storyEl.appendChild(subscriptionsEl);

for (let i = 0; i < 4; i++) {
storyEl.appendChild(win.document.createElement('amp-story-page'));
}

win.document.body.appendChild(storyEl);
await subscriptionsEl.whenBuilt();
});
Expand Down
9 changes: 9 additions & 0 deletions extensions/amp-story/1.0/amp-story-store-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ export let ShoppingDataDef;
* newPageAvailableId: string,
* pageSize: {width: number, height: number},
* subscriptionsDialogState: boolean,
* subscriptionsPageIndex: number,
* }}
*/
export let State;
Expand Down Expand Up @@ -209,6 +210,7 @@ const StateProperty = mangleObjectValues({
// AMP Story paywall states.
SUBSCRIPTIONS_DIALOG_UI_STATE: 'subscriptionsDialogUiState',
SUBSCRIPTIONS_STATE: 'subscriptionsState',
SUBSCRIPTIONS_PAGE_INDEX: 'subscriptionsPageIndex',
});

export {StateProperty};
Expand All @@ -228,6 +230,7 @@ const Action = mangleObjectValues({
SET_PAGE_IDS: 'setPageIds',
SET_PAGE_SIZE: 'updatePageSize',
SET_VIEWER_CUSTOM_CONTROLS: 'setCustomControls',
SET_SUBSCRIPTIONS_PAGE_INDEX: 'setSubscriptionsPageIndex',
TOGGLE_AD: 'toggleAd',
TOGGLE_EDUCATION: 'toggleEducation',
TOGGLE_INFO_DIALOG: 'toggleInfoDialog',
Expand Down Expand Up @@ -477,6 +480,11 @@ const actions = (state, action, data) => {
...state,
[StateProperty.VIEWER_CUSTOM_CONTROLS]: data,
});
case Action.SET_SUBSCRIPTIONS_PAGE_INDEX:
return /** @type {!State} */ ({
...state,
[StateProperty.SUBSCRIPTIONS_PAGE_INDEX]: data,
});
case Action.TOGGLE_SUBSCRIPTIONS_DIALOG_UI_STATE:
return /** @type {!State} */ ({
...state,
Expand Down Expand Up @@ -629,6 +637,7 @@ export class AmpStoryStoreService {
[StateProperty.PREVIEW_STATE]: false,
[StateProperty.SUBSCRIPTIONS_DIALOG_UI_STATE]: false,
[StateProperty.SUBSCRIPTIONS_STATE]: SubscriptionsState.DISABLED,
[StateProperty.SUBSCRIPTIONS_PAGE_INDEX]: -1,
});
}

Expand Down
77 changes: 56 additions & 21 deletions extensions/amp-story/1.0/amp-story.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ import {getConsentPolicyState} from '../../../src/consent';
import {Gestures} from '../../../src/gesture';
import {SwipeXYRecognizer} from '../../../src/gesture-recognizers';
import {getMode, isModeDevelopment} from '../../../src/mode';
// import {DEFAULT_SUBSCRIPTIONS_PAGE_INDEX} from '../../amp-story-subscriptions/0.1/amp-story-subscriptions';
ychsieh marked this conversation as resolved.
Show resolved Hide resolved

/** @private @const {number} */
const DESKTOP_WIDTH_THRESHOLD = 1024;
Expand Down Expand Up @@ -192,17 +193,11 @@ const MAX_MEDIA_ELEMENT_COUNTS = {
[MediaType_Enum.VIDEO]: 8,
};

/**
* The index of the page where the paywall would be triggered.
* @const {number}
*/
export const PAYWALL_PAGE_INDEX = 2;

/**
* The number of milliseconds to wait before showing the paywall on paywall page.
* @const {number}
*/
export const PAYWALL_DELAY_DURATION = 500;
export const SUBSCRIPTIONS_DELAY_DURATION = 500;

/** @type {string} */
const TAG = 'amp-story';
Expand Down Expand Up @@ -324,10 +319,16 @@ export class AmpStory extends AMP.BaseElement {
this.pageAfterSubscriptionsGranted_ = null;

/** @private {!Deferred} a promise that is resolved once the subscription state is received */
this.subscriptionsStatePromise_ = new Deferred();
this.subscriptionsStateDeferred_ = new Deferred();

/** @private {?number} the timeout to show subscriptions dialog after delay */
this.showSubscriptionsUITimeout_ = null;

/** @private {?number} the index of the page where the paywall would be triggered. */
this.subscriptionsPageIndex_ = -1;

/** @private {!Deferred} a promise that is resolved once the subscriptions page index is extracted from amp-story-subscriptions. */
this.subscriptionsPageIndexDeferred_ = new Deferred();
}

/** @override */
Expand Down Expand Up @@ -723,14 +724,25 @@ export class AmpStory extends AMP.BaseElement {
true /** callToInitialize */
);

this.storeService_.subscribe(
mszylkowski marked this conversation as resolved.
Show resolved Hide resolved
StateProperty.SUBSCRIPTIONS_PAGE_INDEX,
(subscriptionsPageIndex) => {
if (subscriptionsPageIndex !== -1) {
this.subscriptionsPageIndexDeferred_.resolve();
this.subscriptionsPageIndex_ = subscriptionsPageIndex;
}
},
true
);

this.storeService_.subscribe(
StateProperty.SUBSCRIPTIONS_STATE,
(subscriptionsState) => {
if (subscriptionsState === SubscriptionsState.PENDING) {
return;
}

this.subscriptionsStatePromise_.resolve();
this.subscriptionsStateDeferred_.resolve();
if (subscriptionsState === SubscriptionsState.GRANTED) {
this.hideSubscriptionsDialog_();
}
Expand Down Expand Up @@ -1350,26 +1362,41 @@ export class AmpStory extends AMP.BaseElement {
return Promise.resolve();
}

// Block until the subscription state gets resolved.
// Block until the subscriptions page index gets resolved.
const subscriptionsState = this.storeService_.get(
StateProperty.SUBSCRIPTIONS_STATE
);
if (
pageIndex >= PAYWALL_PAGE_INDEX &&
subscriptionsState !== SubscriptionsState.DISABLED &&
this.subscriptionsPageIndex_ === -1
) {
return this.blockOnPendingSubscriptionsData_(
this.subscriptionsPageIndexDeferred_,
targetPageId,
direction
);
}
// Block until the subscription state gets resolved.
if (
pageIndex >= this.subscriptionsPageIndex_ &&
subscriptionsState === SubscriptionsState.PENDING
) {
return this.blockOnPendingSubscriptionsState_(targetPageId, direction);
return this.blockOnPendingSubscriptionsData_(
this.subscriptionsStateDeferred_,
targetPageId,
direction
);
}
// Navigation to the locked pages after the paywall page should be redirected to the paywall page.
// This is necessary for deeplinking case to make sure the paywall dialog shows on the paywall page.
if (
pageIndex > PAYWALL_PAGE_INDEX &&
pageIndex > this.subscriptionsPageIndex_ &&
subscriptionsState === SubscriptionsState.BLOCKED
) {
this.pageAfterSubscriptionsGranted_ = targetPageId;
this.showSubscriptionsDialog_();
return this.switchTo_(
this.pages_[PAYWALL_PAGE_INDEX].element.id,
this.pages_[this.subscriptionsPageIndex_].element.id,
direction
);
}
Expand Down Expand Up @@ -1561,15 +1588,20 @@ export class AmpStory extends AMP.BaseElement {

/**
* Block while waiting to resolve subscriptions state.
* @param {Promise} subscriptionsDataDeferred
* @param {string} targetPageId
* @param {!NavigationDirection} direction
* @return {!Promise}
* @private
*/
blockOnPendingSubscriptionsState_(targetPageId, direction) {
return this.subscriptionsStatePromise_.promise.then(() => {
return this.switchTo_(targetPageId, direction);
});
blockOnPendingSubscriptionsData_(
subscriptionsDataDeferred,
targetPageId,
direction
) {
return subscriptionsDataDeferred.promise.then(() =>
this.switchTo_(targetPageId, direction)
);
}

/**
Expand Down Expand Up @@ -1604,7 +1636,7 @@ export class AmpStory extends AMP.BaseElement {
);
// Navigation to the paywall page should show dialog after delay if not already shown.
if (
pageIndex === PAYWALL_PAGE_INDEX &&
pageIndex === this.subscriptionsPageIndex_ &&
!subscriptionsDialogUIState &&
this.storeService_.get(StateProperty.SUBSCRIPTIONS_STATE) ===
SubscriptionsState.BLOCKED
Expand All @@ -1613,10 +1645,13 @@ export class AmpStory extends AMP.BaseElement {
this.pageAfterSubscriptionsGranted_ = targetPageId;
this.showSubscriptionsDialog_();
this.showSubscriptionsUITimeout_ = null;
}, PAYWALL_DELAY_DURATION);
}, SUBSCRIPTIONS_DELAY_DURATION);
}
// Hide paywall UI when navigating back to the previous page.
if (pageIndex < PAYWALL_PAGE_INDEX && subscriptionsDialogUIState) {
if (
pageIndex < this.subscriptionsPageIndex_ &&
subscriptionsDialogUIState
) {
this.hideSubscriptionsDialog_();
}
}
Expand Down
Loading