diff --git a/.eslintrc b/.eslintrc index f79af39e1..81ce0e19d 100644 --- a/.eslintrc +++ b/.eslintrc @@ -26,6 +26,7 @@ { "allow": [ "active_messengers", + "device_id", "error_description", "first_name", "message_id", @@ -36,6 +37,7 @@ "messenger_journey_reports", "messenger_journey_steps", "messenger_journey_step_id", + "media_view_time", "step_kind", "screen_name", "screen_class", diff --git a/src/actions/requests.ts b/src/actions/requests.ts index fa1e35e8c..f31c77b1a 100755 --- a/src/actions/requests.ts +++ b/src/actions/requests.ts @@ -12,6 +12,7 @@ import { TAdventures, TDataState, TInvitation, + TAuthState, } from 'utils/types'; import { AsyncAction } from 'reducers'; import { Action } from 'redux'; @@ -1091,59 +1092,55 @@ export function markMessageAsRead(params: markMessageAsRead) { }; } -// Send an interaction when the user press play. -export function interactionAdventureVideoPlay({ adventureId, stepId }) { - return async (dispatch: Dispatch, getState: any) => { - const deviceId = getState().auth.device.id; - // See: https://docs.vokeapp.com/#me-journeys-steps-interactions-create-interaction - const data: any = { - interaction: { - action: 'started', // Message read. - device_id: deviceId, - }, - }; - // SEND INTERACTION DATA TO THE SERVER. - const result = await request({ - ...ROUTES.CREATE_INTERACTION_PLAY_ADVENTURE_VIDEO, - pathParams: { - adventureId, - stepId, - }, - data, - authToken: getState().auth.authToken, - }); - return result; - }; -} - -type interactionVideoPlay = { +type TReportVideoInteraction = { videoId: string; - context: 'resource' | 'journey' | 'step' | 'notifications'; // Where the interaction is comming from? + context: 'resource' | 'journey' | 'step' | 'notifications'; + action: 'started' | 'paused' | 'resumed' | 'finished'; + time?: number; }; +interface TInteraction { + id: string; + action: string; + messenger_id: string; + device_id: string; + item_id: string; + media_view_time: number; + created_at: string; +} + // Send an interaction when the user press play. // https://docs.vokeapp.com/#items-interactions-create-item-interaction -export function interactionVideoPlay(params: interactionVideoPlay) { - return async (dispatch: Dispatch, getState: any) => { - const deviceId = getState().auth.device.id; - const data: any = { - interaction: { - action: 'started', // Message read. - device_id: deviceId, - context: params.context, - }, - }; - // SEND INTERACTION DATA TO THE SERVER. - const result = await request({ - ...ROUTES.CREATE_INTERACTION_PLAY_VIDEO, - pathParams: { - videoId: params.videoId, - }, - data, - authToken: getState().auth.authToken, - }); - return result; +export function reportVideoInteraction( + params: TReportVideoInteraction, +): AsyncAction { + return async ( + dispatch: Dispatch, + getState: () => { auth: TAuthState }, + ): Promise => { + try { + const deviceId = getState().auth.device.id; + const data = { + interaction: { + action: params.action, + device_id: deviceId, + context: params.context, + media_view_time: params?.time ? params.time.toFixed(0) : 0, + }, + }; + const result = await request({ + ...ROUTES.CREATE_INTERACTION_PLAY_VIDEO, + pathParams: { + videoId: params.videoId, + }, + data, + authToken: getState().auth.authToken, + }); + return result; + } catch (error) { + return error; + } }; } diff --git a/src/actions/routes.js b/src/actions/routes.js index 5070fd6bc..7303fda4d 100644 --- a/src/actions/routes.js +++ b/src/actions/routes.js @@ -121,6 +121,8 @@ const ROUTES = { method: 'post', url: `me/conversations/{adventureConversationId}/messages`, }, + + // Interactions: CREATE_INTERACTION_READ: { method: 'post', url: `me/conversations/{conversationId}/messages/{messageId}/interactions/`, @@ -133,6 +135,7 @@ const ROUTES = { method: 'post', url: `items/{videoId}/interactions`, }, + START_ADVENTURE: { method: 'post', url: `me/journeys` }, // https://docs.vokeapp.com/#me-journeys-cancels-a-messenger-journey DELETE_ADVENTURE: { diff --git a/src/components/AdventureStepCard/index.tsx b/src/components/AdventureStepCard/index.tsx index 85e057fc7..96423557f 100644 --- a/src/components/AdventureStepCard/index.tsx +++ b/src/components/AdventureStepCard/index.tsx @@ -13,11 +13,16 @@ import theme from 'utils/theme'; import st from 'utils/st'; import { TAdventureSingle, TDataState, TError, TStep } from 'utils/types'; import analytics from '@react-native-firebase/analytics'; -import { Pressable, View } from 'react-native'; +import { Pressable, View, Alert } from 'react-native'; import Button from 'components/Button'; import { getAdventureSteps, unlockNextAdventureStep } from 'actions/requests'; import Communications from 'react-native-communications'; import CONSTANTS from 'utils/constants'; +import Flex from 'components/Flex'; +import Text from 'components/Text'; +import VokeIcon from 'components/VokeIcon'; +import Image from 'components/Image'; +import Touchable from 'components/Touchable'; import { RootState, useDispatchTs } from '../../reducers'; import Spacer from '../Spacer'; diff --git a/src/components/Video/index.tsx b/src/components/Video/index.tsx index 462fdee4b..99f7ea6b6 100644 --- a/src/components/Video/index.tsx +++ b/src/components/Video/index.tsx @@ -55,8 +55,9 @@ interface RefArcLight { interface Props { onOrientationChange?: (orientation: string) => void; - onPlay?: () => void; - onStop?: () => void; + onPlay?: (time: number) => void; + onPause?: (time: number) => void; + onStop?: (time: number) => void; hideBack?: boolean; item: TStep['item']['content']; onCancel?: () => void; @@ -73,6 +74,7 @@ function Video({ //void }, onPlay = () => {}, + onPause = () => {}, onStop = () => {}, hideBack = false, item, @@ -255,12 +257,12 @@ function Video({ setIsBuffering(true); break; case 'paused': - setIsPlaying(false); - setIsBuffering(false); - if (started) { + if (started && isPlaying && sliderValue <= item.duration - 1) { // Send an interaction when the user press pause. - onStop(); + onPause(sliderValue); } + setIsPlaying(false); + setIsBuffering(false); break; case 'play': case 'playing': @@ -269,7 +271,9 @@ function Video({ if (!started) { setStarted(true); } - onPlay(); + if (event === 'play') { + onPlay(sliderValue); + } break; case 'ready': setVideoReady(true); @@ -278,6 +282,9 @@ function Video({ handleVideoStateChange('play'); } break; + case 'ended': + onStop(sliderValue); + break; // default: // break; } @@ -353,9 +360,6 @@ function Video({ setIsPlaying(false); }} onPlaybackQualityChange={(q): void => console.log(q)} - onEnd={(): void => { - console.log('Player -> onEnd'); - }} volume={100} initialPlayerParams={{ controls: false, @@ -390,6 +394,7 @@ function Video({ if (sliderValue >= 1) { handleVideoStateChange('paused'); setSliderValue(0); + onStop(sliderValue); } }} onError={e => { diff --git a/src/domain/Adventure/Active/index.tsx b/src/domain/Adventure/Active/index.tsx index bdcebc90d..3b05ac284 100644 --- a/src/domain/Adventure/Active/index.tsx +++ b/src/domain/Adventure/Active/index.tsx @@ -23,7 +23,7 @@ import Video from 'components/Video'; import AdventureStepsList from 'components/AdventureStepsList'; import VokeIcon from 'components/VokeIcon'; import Touchable from 'components/Touchable'; -import { getMyAdventure, interactionVideoPlay } from 'actions/requests'; +import { getMyAdventure, reportVideoInteraction } from 'actions/requests'; import { setCurrentScreen } from 'actions/info'; import { AdventureStackParamList, TDataState } from 'utils/types'; import theme from 'utils/theme'; @@ -211,11 +211,44 @@ function AdventureActive({ navigation, route }: Props): React.ReactElement {