From e59f373b4d30de1d00812d52568eec1188dea54e Mon Sep 17 00:00:00 2001 From: aurimasmi Date: Sun, 22 May 2022 20:57:44 +0300 Subject: [PATCH 01/10] add composite components to template --- packages/harness/renative.json | 251 ++++++++++++++++-- .../sdk/src/complexComponents/Card/card.tsx | 23 +- .../src/complexComponents/Card/index.lng.js | 9 +- .../src/complexComponents/List/index.lng.js | 2 +- .../sdk/src/complexComponents/List/index.tsx | 10 +- .../src/complexComponents/Row/index.lng.js | 3 + .../sdk/src/complexComponents/Row/index.tsx | 66 ++--- .../src/components/App/index.native.tv.tsx | 8 +- packages/sdk/src/focusManager/core.ts | 38 +-- .../sdk/src/focusManager/layoutManager.ts | 17 ++ packages/sdk/src/focusManager/types.ts | 3 + .../sdk/src/hooks/useDimensionsCalculator.ts | 18 +- .../template/src/screens/carousels.lng.js | 109 ++++---- packages/template/src/screens/carousels.tsx | 149 +++-------- packages/template/src/utils.ts | 4 +- 15 files changed, 436 insertions(+), 274 deletions(-) diff --git a/packages/harness/renative.json b/packages/harness/renative.json index dfbd4382d..ee5ce8856 100644 --- a/packages/harness/renative.json +++ b/packages/harness/renative.json @@ -1,8 +1,15 @@ { - "extendsTemplate": "renative.json", + "engines": { + "@rnv/engine-lightning": "source:rnv", + "@rnv/engine-rn": "source:rnv", + "@rnv/engine-rn-electron": "source:rnv", + "@rnv/engine-rn-next": "source:rnv", + "@rnv/engine-rn-tvos": "source:rnv", + "@rnv/engine-rn-web": "source:rnv", + "@rnv/engine-rn-windows": "source:rnv" + }, "projectName": "app-harness", "workspaceID": "flexn", - "currentTemplate": "@flexn/template", "paths": { "appConfigsDir": "./appConfigs", "entryDir": "./", @@ -15,12 +22,9 @@ } } }, - "templates": { - "@flexn/template": { - "version": "0.13.2" - } - }, "defaults": { + "title": "Template App", + "id": "io.flexn.template.app", "supportedPlatforms": [ "android", "androidtv", @@ -31,35 +35,236 @@ "linux", "firetv", "chromecast", - "androidwear" + "androidwear", + "tizen" ] }, + "isMonorepo": true, "plugins": { - "@sentry/react-native": "source:flexn", - "@sentry/react": "source:flexn", - "@sentry/tracing": "source:flexn", - "@react-navigation": "source:flexn", - "react-native-screens": "source:flexn", - "react-native-safe-area-context": "source:flexn", - "react-native-gesture-handler": "source:flexn", - "react-native-google-cast": "source:flexn", + "@flexn/sdk": { + "version": "0.13.2", + "webpack": { + "modulePaths": true, + "moduleAliases": true + }, + "tvos": { + "podName": "FlexnSDK" + }, + "androidtv": { + "package": "io.flexn.sdk.FlexnSdkPackage", + "projectName": "flexn-io-sdk", + "activityImports": [ + "io.flexn.sdk.TvRemoteHandlerModule", + "android.view.KeyEvent;" + ], + "activityMethods": [ + "override fun dispatchKeyEvent(event: KeyEvent): Boolean {", + " if (event.action == KeyEvent.ACTION_DOWN || event.action == KeyEvent.ACTION_UP) {", + " TvRemoteHandlerModule.getInstance().onKeyEvent(event);", + " }", + " return super.dispatchKeyEvent(event);", + "}" + ] + }, + "firetv": { + "package": "io.flexn.sdk.FlexnSdkPackage", + "projectName": "flexn-io-sdk", + "activityImports": [ + "io.flexn.sdk.TvRemoteHandlerModule", + "android.view.KeyEvent;" + ], + "activityMethods": [ + "override fun dispatchKeyEvent(event: KeyEvent): Boolean {", + " if (event.action == KeyEvent.ACTION_DOWN || event.action == KeyEvent.ACTION_UP) {", + " TvRemoteHandlerModule.getInstance().onKeyEvent(event);", + " }", + " return super.dispatchKeyEvent(event);", + "}" + ] + } + }, + "@rnv/renative": "source:flexn", + "react-native": "source:rnv", + "react-native-web": "source:rnv", + "react-native-tvos": "source:rnv", + "react-native-windows": "source:rnv", + "react-native-vector-icons": "source:flexn", "@lightningjs/sdk": "source:flexn", - "react-native-carplay": "source:flexn", - "react-native-snap-carousel": "3.9.1", + "@lightningjs/cli": "source:flexn", "react": "source:rnv", "react-art": "source:rnv", "react-dom": "source:rnv", - "react-native": "source:rnv", "@react-native-community/cli-platform-ios": "source:rnv", "@react-native-community/cli": "source:rnv", - "react-native-tvos": "source:rnv", - "react-native-web": "source:rnv", - "next": "source:rnv" + "next": "source:rnv", + "@react-native-windows/cli": "source:rnv", + "@react-navigation": "source:flexn", + "react-native-screens": "source:flexn", + "react-native-safe-area-context": "source:flexn", + "react-native-google-cast": "source:flexn", + "react-native-reanimated": "source:flexn", + "react-native-gesture-handler": "source:flexn", + "react-native-media-query": "source:flexn", + "@flexn/typescript": "source:flexn", + "tslib": "source:flexn", + "@sentry/react-native": "source:flexn", + "@sentry/react": "source:flexn", + "@sentry/tracing": "source:flexn", + "react-native-carplay": "source:flexn", + "react-native-snap-carousel": "3.9.1" }, "platforms": { + "android": { + "targetSdkVersion": 30, + "compileSdkVersion": 30, + "reactNativeEngine": "hermes", + "enableAndroidX": true, + "gradle.properties": { + "android.useDeprecatedNdk": true, + "android.enableJetifier": true, + "android.useAndroidX": true, + "android.debug.obsoleteApi": true, + "org.gradle.jvmargs": "-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8", + "org.gradle.daemon": true, + "org.gradle.parallel": true, + "org.gradle.configureondemand": true + }, + "AndroidManifest": { + "children": [ + { + "tag": "application", + "android:name": ".MainApplication", + "android:usesCleartextTraffic": true, + "tools:targetApi": 28 + } + ] + }, + "mainActivity": { + "onCreate": "super.onCreate(null)" + } + }, + "androidtv": { + "engine": "engine-rn-tvos", + "reactNativeEngine": "hermes", + "targetSdkVersion": 30, + "compileSdkVersion": 30, + "enableAndroidX": true, + "gradle.properties": { + "android.useDeprecatedNdk": true, + "android.enableJetifier": true, + "android.useAndroidX": true, + "android.debug.obsoleteApi": true, + "org.gradle.jvmargs": "-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8", + "org.gradle.daemon": true, + "org.gradle.parallel": true, + "org.gradle.configureondemand": true + }, + "AndroidManifest": { + "children": [ + { + "tag": "application", + "android:name": ".MainApplication", + "android:usesCleartextTraffic": true, + "tools:targetApi": 30 + } + ] + }, + "mainActivity": { + "onCreate": "super.onCreate(null)" + } + }, + "firetv": { + "engine": "engine-rn-tvos", + "targetSdkVersion": 30, + "compileSdkVersion": 30, + "reactNativeEngine": "hermes", + "enableAndroidX": true, + "gradle.properties": { + "android.useDeprecatedNdk": true, + "android.enableJetifier": true, + "android.useAndroidX": true, + "android.debug.obsoleteApi": true, + "org.gradle.jvmargs": "-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8", + "org.gradle.daemon": true, + "org.gradle.parallel": true, + "org.gradle.configureondemand": true + }, + "AndroidManifest": { + "children": [ + { + "tag": "application", + "android:name": ".MainApplication", + "android:usesCleartextTraffic": true, + "tools:targetApi": 30 + } + ] + }, + "mainActivity": { + "onCreate": "super.onCreate(null)" + } + }, + "ios": { + "engine": "engine-rn", + "reactNativeEngine": "hermes" + }, + "web": { + "engine": "engine-rn-next", + "webpackConfig": { + "nextTranspileModules": [ + "lodash-es" + ] + } + }, + "chromecast": { + "engine": "engine-rn-next", + "webpackConfig": { + "nextTranspileModules": [ + "lodash-es" + ] + } + }, + "tizen": { + "engine": "engine-lightning", + "target": "es5" + }, + "webos": { + "engine": "engine-lightning", + "target": "es5" + }, + "tvos": { + "engine": "engine-rn-tvos" + }, "macos": { "engine": "engine-rn", - "excludedPlugins": ["react-native-linear-gradient"] + "excludedPlugins": [ + "react-native-linear-gradient" + ] + }, + "windows": { + "engine": "engine-rn-windows" + }, + "xbox": { + "engine": "engine-rn-windows" + } + }, + "permissions": { + "ios": {}, + "android": { + "INTERNET": { + "key": "android.permission.INTERNET", + "security": "normal" + }, + "SYSTEM_ALERT_WINDOW": { + "key": "android.permission.SYSTEM_ALERT_WINDOW", + "security": "signature" + } + } + }, + "extendsTemplate": "renative.json", + "currentTemplate": "@flexn/template", + "templates": { + "@flexn/template": { + "version": "0.13.2" } } } diff --git a/packages/sdk/src/complexComponents/Card/card.tsx b/packages/sdk/src/complexComponents/Card/card.tsx index 675c3e8c6..328bc69f5 100644 --- a/packages/sdk/src/complexComponents/Card/card.tsx +++ b/packages/sdk/src/complexComponents/Card/card.tsx @@ -3,7 +3,6 @@ import React from 'react'; import Image from '../../components/Image'; import Text from '../../components/Text'; import Pressable from '../../components/Pressable'; -import StyleSheet from '../../apis/StyleSheet'; import { CardProps } from './types'; import useStyleFlattener from '../../hooks/useStyleFlattener'; @@ -25,6 +24,16 @@ const Card = React.forwardRef( ) => { const styles = useStyleFlattener(style); + const titleStyles = { + fontSize: styles.fontSize || baseStyles.title.fontSize, + color: styles.color || baseStyles.title.color, + textAlign: styles.textAlign || baseStyles.title.textAlign, + }; + + const posterStyles = { + borderRadius: styles.borderRadius, + }; + return ( ( onPress={onPress} focusOptions={focusOptions} > - - + + {title} @@ -45,23 +54,21 @@ const Card = React.forwardRef( } ); -const baseStyles = StyleSheet.create({ +const baseStyles = { card: { - borderRadius: 5, width: 250, height: 250, }, poster: { width: '100%', height: '100%', - borderRadius: 5, }, title: { fontSize: 26, - color: 'white', + color: '#000000', textAlign: 'center', }, -}); +}; Card.displayName = 'Card'; diff --git a/packages/sdk/src/complexComponents/Card/index.lng.js b/packages/sdk/src/complexComponents/Card/index.lng.js index 5f2ed6a97..bc7188450 100644 --- a/packages/sdk/src/complexComponents/Card/index.lng.js +++ b/packages/sdk/src/complexComponents/Card/index.lng.js @@ -263,16 +263,19 @@ export default class Card extends Lightning.Component { } _handleEnter() { - this.fireAncestors('$onCardPress', this.item); + this.fireAncestors('$onCardPress', this.eventValue); + this.signal('onPress', this.eventValue); } _focus() { - this.fireAncestors('$onCardFocus', this.item); + this.fireAncestors('$onCardFocus', this.eventValue); + this.signal('onFocus', this.eventValue); this.patch({ Image: this._setAnimationValues().focus }); } _unfocus() { - this.fireAncestors('$onCardBlur', this.item); + this.fireAncestors('$onCardBlur', this.eventValue); + this.signal('onBlur', this.eventValue); this.patch({ Image: this._setAnimationValues().unfocus }); } } diff --git a/packages/sdk/src/complexComponents/List/index.lng.js b/packages/sdk/src/complexComponents/List/index.lng.js index cb4508de2..c82449b17 100644 --- a/packages/sdk/src/complexComponents/List/index.lng.js +++ b/packages/sdk/src/complexComponents/List/index.lng.js @@ -1,6 +1,6 @@ import { Lightning } from '@lightningjs/sdk'; import Row from '../Row/index.lng'; -import Column from '../Row/index.lng'; +import Column from '../../lng/Column/index.lng'; export default class List extends Lightning.Component { static _template() { return { diff --git a/packages/sdk/src/complexComponents/List/index.tsx b/packages/sdk/src/complexComponents/List/index.tsx index fe7d32496..7d310e32a 100644 --- a/packages/sdk/src/complexComponents/List/index.tsx +++ b/packages/sdk/src/complexComponents/List/index.tsx @@ -17,8 +17,10 @@ type RowItem = { interface ListProps { parentContext?: Context; focusOptions?: RecyclableListFocusOptions; - itemsInViewport: number; + animatorOptions?: any; + itemsInViewport?: number; style?: StyleProp; + cardStyle?: StyleProp; onFocus?(data: any): void; onBlur?(data: any): void; onPress?(data: any): void; @@ -38,7 +40,9 @@ const List = ({ items, itemsInViewport = 5, style = {}, + cardStyle = {}, focusOptions, + animatorOptions, itemSpacing = 30, itemDimensions, onPress, @@ -84,9 +88,11 @@ const List = ({ width: boundaries.width, height: rowHeight, }} + cardStyle={cardStyle} itemDimensions={itemDimensions} itemSpacing={itemSpacing} initialXOffset={initialXOffset} + animatorOptions={animatorOptions} /> ); }; @@ -105,7 +111,7 @@ const List = ({ return renderRow({ index, data: rowData, - nestedParentContext: repeatContext.parentContext, + nestedParentContext: repeatContext?.parentContext, repeatContext: repeatContext, }); }} diff --git a/packages/sdk/src/complexComponents/Row/index.lng.js b/packages/sdk/src/complexComponents/Row/index.lng.js index 5f511465e..9aafa7794 100644 --- a/packages/sdk/src/complexComponents/Row/index.lng.js +++ b/packages/sdk/src/complexComponents/Row/index.lng.js @@ -173,14 +173,17 @@ export default class Row extends Lightning.Component { } $onCardPress(eventValue) { + this.fireAncestors('$onCardPress', eventValue); this.signal('onPress', eventValue); } $onCardFocus(eventValue) { + this.fireAncestors('$onCardFocus', eventValue); this.signal('onFocus', eventValue); } $onCardBlur(eventValue) { + this.fireAncestors('$onCardBlur', eventValue); this.signal('onBlur', eventValue); } diff --git a/packages/sdk/src/complexComponents/Row/index.tsx b/packages/sdk/src/complexComponents/Row/index.tsx index 436992d03..5981748c7 100644 --- a/packages/sdk/src/complexComponents/Row/index.tsx +++ b/packages/sdk/src/complexComponents/Row/index.tsx @@ -6,7 +6,7 @@ import RecyclableList, { RecyclableListDataProvider, } from '../../components/RecyclableList'; import { Ratio } from '../../helpers'; -import { Context, ForbiddenFocusDirections } from '../../focusManager/types'; +import { Context, RecyclableListFocusOptions } from '../../focusManager/types'; import { PosterCard } from '../Card'; import useDimensionsCalculator from '../../hooks/useDimensionsCalculator'; @@ -20,9 +20,11 @@ interface RowProps { repeatContext?: Context; nestedParentContext?: Context; title?: string; - forbiddenFocusDirections?: ForbiddenFocusDirections; + focusOptions?: RecyclableListFocusOptions; + animatorOptions?: any; itemsInViewport: number; style?: StyleProp; + cardStyle?: StyleProp; onFocus?(data: any): void; onBlur?(data: any): void; onPress?(data: any): void; @@ -40,8 +42,10 @@ const Row = ({ parentContext, repeatContext, nestedParentContext, - forbiddenFocusDirections, + focusOptions, + animatorOptions, style = {}, + cardStyle = {}, onFocus, onPress, onBlur, @@ -52,7 +56,7 @@ const Row = ({ const ref: any = useRef(); const layoutProvider: any = useRef(); const [dataProvider] = useState(new RecyclableListDataProvider((r1, r2) => r1 !== r2).cloneWithRows(items)); - const { boundaries, spacings, onLayout, rowDimensions } = useDimensionsCalculator({ + const { boundaries, isLoading, spacings, onLayout, rowDimensions } = useDimensionsCalculator({ style, itemSpacing, itemDimensions, @@ -77,42 +81,42 @@ const Row = ({ onFocus?.(data)} onBlur={() => onBlur?.(data)} onPress={() => onPress?.(data)} repeatContext={_repeatContext} - focusOptions={ - { - // initialFocus: index === 0 && _index === 0, - } - } + focusOptions={{ + animatorOptions, + }} /> ); }; const renderRecycler = () => { - return ( - - ); + if (!isLoading) { + return ( + + ); + } + + return null; }; const renderTitle = () => { diff --git a/packages/sdk/src/components/App/index.native.tv.tsx b/packages/sdk/src/components/App/index.native.tv.tsx index bf27291fa..c856a37ab 100644 --- a/packages/sdk/src/components/App/index.native.tv.tsx +++ b/packages/sdk/src/components/App/index.native.tv.tsx @@ -1,6 +1,12 @@ import React, { useEffect, useRef } from 'react'; import { useTVRemoteHandler } from '../../remoteHandler'; -import { TVEventHandler, View as RNView, ScrollView as RNScrollView, TouchableOpacity, findNodeHandle } from 'react-native'; +import { + TVEventHandler, + View as RNView, + ScrollView as RNScrollView, + TouchableOpacity, + findNodeHandle, +} from 'react-native'; import { isPlatformAndroidtv, isPlatformTvos, isPlatformFiretv } from '@rnv/renative'; import CoreManager from '../../focusManager/core'; import { DIRECTION, SCREEN_STATES } from '../../focusManager/constants'; diff --git a/packages/sdk/src/focusManager/core.ts b/packages/sdk/src/focusManager/core.ts index 785cca734..63b0ef765 100644 --- a/packages/sdk/src/focusManager/core.ts +++ b/packages/sdk/src/focusManager/core.ts @@ -49,6 +49,7 @@ class CoreManager { if (context.initialFocus && !!parentContext) { this._contextMap[parentContext.id].initialFocus = context; } + context.screen = parentContext; } @@ -161,26 +162,14 @@ class CoreManager { if (context.initialFocus) { return context.initialFocus; } - } - - for (let index = 0; index < context.children.length; index++) { - const ch: Context = context.children[index]; - if (ch.isFocusable) { - return ch; + if (context.firstFocusable) { + return context.firstFocusable; } - const next = this.findFirstFocusableOnScreen(ch); - - if (next?.isFocusable) { - return next; - } + return null; } - if (context.isFocusable) { - return context; - } - - return null; + return context; }; public focusElementByFocusKey = (focusKey: string) => { @@ -264,8 +253,21 @@ class CoreManager { this.findClosestNode(ctx, direction, output); } } - const closestContext: Context | undefined = - output.match1Context || output.match2Context || output.match3Context; + const closestContextFn = (): Context | undefined => { + if (output.match1Context || output.match2Context || output.match3Context) { + if (output.match3 < output.match2) { + return output.match3Context; + } + if (output.match2 < output.match1) { + return output.match2Context; + } + if (output.match3 < output.match1) { + return output.match3Context; + } + return output.match1Context || output.match2Context || output.match3Context; + } + }; + const closestContext = closestContextFn(); if (!closestContext) { const parentHasForbiddenDirection = parentContext.forbiddenFocusDirections?.includes(direction); diff --git a/packages/sdk/src/focusManager/layoutManager.ts b/packages/sdk/src/focusManager/layoutManager.ts index fb39cc594..7f2fb6acf 100644 --- a/packages/sdk/src/focusManager/layoutManager.ts +++ b/packages/sdk/src/focusManager/layoutManager.ts @@ -1,4 +1,19 @@ import type { Context } from './types'; +import { CONTEXT_TYPES } from './constants'; + +function findLowestRelativeCoordinates(context: Context) { + if (context.screen && context.type === CONTEXT_TYPES.VIEW) { + const { screen } = context; + const { layout } = screen.firstFocusable || {}; + if (!screen.firstFocusable) { + context.screen.firstFocusable = context; + } else if (layout.yMin === context.layout.yMin && layout.xMin >= context.layout.xMin) { + context.screen.firstFocusable = context; + } else if (layout.yMin > context.layout.yMin) { + context.screen.firstFocusable = context; + } + } +} function recalculateAbsolutes(context: Context) { const { layout } = context; @@ -79,6 +94,8 @@ function measure(context: Context, ref: any) { context.layout = layout; + findLowestRelativeCoordinates(context); + // Calculate max X and Y width to prevent over scroll if (context.parent?.isScrollable && context.parent.layout) { const pCtx = context?.repeatContext?.parentContext; diff --git a/packages/sdk/src/focusManager/types.ts b/packages/sdk/src/focusManager/types.ts index 6425a6403..78fe1aa89 100644 --- a/packages/sdk/src/focusManager/types.ts +++ b/packages/sdk/src/focusManager/types.ts @@ -132,7 +132,10 @@ export interface Context { isFocusable?: boolean; focusKey?: string; state?: string; // proper type + initialFocus?: Context; + firstFocusable?: Context; + lastFocused?: Context; repeatContext?: Context; parentContext?: Context; diff --git a/packages/sdk/src/hooks/useDimensionsCalculator.ts b/packages/sdk/src/hooks/useDimensionsCalculator.ts index e0673fa0a..68d4b0225 100644 --- a/packages/sdk/src/hooks/useDimensionsCalculator.ts +++ b/packages/sdk/src/hooks/useDimensionsCalculator.ts @@ -15,6 +15,8 @@ interface Props { export default function useDimensionsCalculator({ style, itemSpacing, itemDimensions, itemsInViewport, ref }: Props) { const spacing = Ratio(itemSpacing); + const [isLoading, setIsLoading] = useState(true); + const [spacings] = useState(() => { return { left: spacing, @@ -63,18 +65,22 @@ export default function useDimensionsCalculator({ style, itemSpacing, itemDimens const onLayout = () => { ref.current.measure( (_fx: number, _fy: number, _width: number, _height: number, pageX: number, pageY: number) => { - setRowDimensions(calculateRowDimensions(boundaries.width - pageX)); - setBoundaries((prev) => ({ - width: prev.width - pageX, - relativeHeight: prev.height - pageY, - height: prev.height, - })); + if (isLoading) { + setRowDimensions(calculateRowDimensions(boundaries.width - pageX)); + setBoundaries((prev) => ({ + width: prev.width - pageX, + relativeHeight: prev.height - pageY, + height: prev.height, + })); + setIsLoading(false); + } } ); }; return { spacings, + isLoading, boundaries, rowDimensions, onLayout, diff --git a/packages/template/src/screens/carousels.lng.js b/packages/template/src/screens/carousels.lng.js index 41b1f6b5e..21934bec0 100644 --- a/packages/template/src/screens/carousels.lng.js +++ b/packages/template/src/screens/carousels.lng.js @@ -1,50 +1,9 @@ /* eslint-disable no-underscore-dangle */ import { Lightning, Router } from '@lightningjs/sdk'; -import { Row, Column } from '@lightningjs/ui-components'; -import { getRandomData, getHexColor } from '../utils'; -import { LAYOUT, THEME_LIGHT, THEME } from '../config'; +import { List } from '@flexn/sdk'; +import { getRandomData, getHexColor, interval } from '../utils'; +import { LAYOUT, THEME_LIGHT } from '../config'; import { ROUTES } from '../config.lng'; - -const itemsInRows = [3, 4, 5, 6, 4, 5]; -class Card extends Lightning.Component { - static _template() { - return { - rect: true, - h: 250, - src: '', - Text: { - y: 250, - w: (w) => w - 50, - text: { - fontSize: 28, - maxLinesSuffix: '...', - maxLines: 1, - textColor: getHexColor('#000000'), - text: '', - fontFace: THEME.light.primaryFontFamily, - }, - }, - }; - } - - _init() { - const textColor = window.theme === THEME_LIGHT ? getHexColor('#000000') : getHexColor('#FFFFFF'); - this.patch({ Text: { text: { text: this.item.title, textColor } } }); - } - - _handleEnter() { - Router.navigate(ROUTES.DETAILS, { row: this.item.rowNumber, index: this.item.index }); - } - - _focus() { - this.smooth = { scale: 1.1 }; - } - - _unfocus() { - this.smooth = { scale: 1 }; - } -} - export default class Carousels extends Lightning.Component { static _template() { return { @@ -59,29 +18,49 @@ export default class Carousels extends Lightning.Component { } static _populateData() { - const carouselsData = itemsInRows.map((count, rowNumber) => getRandomData(rowNumber, 0, count)); + const data = [...Array(10).keys()].map((rowNumber) => { + return { + items: getRandomData(rowNumber, 0, interval(3, 5)), + }; + }); return { - type: Column, - itemSpacing: 30, - plinko: true, - zIndex: 1, - y: 20, + w: LAYOUT.w, h: LAYOUT.h, x: 150, - items: carouselsData.map((column, index) => ({ - type: Row, - h: 280, - w: 1920, - itemSpacing: 25, - items: column.map((item) => ({ - type: Card, - src: item.backgroundImage, - item: { ...item, rowNumber: index }, - w: LAYOUT.w / itemsInRows[index], - })), - })), + y: 50, + List: { + type: List, + data, + itemSpacing: 30, + card: { + w: 350, + h: 300, + }, + row: { + h: 420, + title: { + containerStyle: {}, + textStyle: {}, + }, + }, + focusOptions: { + animatorOptions: { + type: 'scale_with_border', + scale: 1.1, + borderColor: getHexColor('#0A74E6'), + borderRadius: 4, + borderWidth: 6, + }, + }, + signals: { + onFocus: '_onFocus', + onBlur: '_onBlur', + onPress: '_onPress', + }, + }, }; + } _init() { @@ -89,7 +68,11 @@ export default class Carousels extends Lightning.Component { this.patch({ color }); } + _onPress(data) { + Router.navigate(ROUTES.DETAILS, { row: data.rowNumber, index: data.index }); + } + _getFocused() { - return this.tag('Wrapper').tag('Cards'); + return this.tag('Wrapper').tag('List'); } } diff --git a/packages/template/src/screens/carousels.tsx b/packages/template/src/screens/carousels.tsx index 6daeb5bb9..7dd311b84 100644 --- a/packages/template/src/screens/carousels.tsx +++ b/packages/template/src/screens/carousels.tsx @@ -1,132 +1,47 @@ -import { - Image, - TouchableOpacity, - RecyclableList, - RecyclableListDataProvider, - RecyclableListLayoutProvider, - View, - ScrollView, - Text, -} from '@flexn/sdk'; -import { testProps } from '../utils'; -import React, { useContext, useEffect, useRef, useState } from 'react'; -import { Dimensions } from 'react-native'; +import { List } from '@flexn/sdk'; +import React, { useContext } from 'react'; import { isFactorMobile } from '@rnv/renative'; -import { Ratio, ThemeContext, ROUTES } from '../config'; -import { useNavigate } from '../hooks'; -import { getRandomData } from '../utils'; +import { ThemeContext, ROUTES } from '../config'; +import { getRandomData, interval } from '../utils'; import Screen from './screen'; -const { width } = Dimensions.get('window'); -const MARGIN_GUTTER = Ratio(20); - -const itemsInRows = [ - [1, 3], - [2, 4], - [3, 5], - [4, 6], - [2, 4], - [3, 5], -]; - -function getRecyclerDimensions(itemsInViewport: number) { - return { - layout: { width: width / itemsInViewport, height: Ratio(270) }, - item: { width: width / itemsInViewport - MARGIN_GUTTER, height: Ratio(250) }, - }; -} - -const RecyclerExample = ({ items, rowNumber, dimensions: { layout, item }, parentContext, navigation }: any) => { - const navigate = useNavigate({ navigation }); +const ScreenCarousels = ({ navigation }: { navigation?: any }) => { const { theme } = useContext(ThemeContext); - const [dataProvider] = useState( - new RecyclableListDataProvider((r1: number, r2: number) => r1 !== r2).cloneWithRows(items) - ); - - const layoutProvider = useRef( - new RecyclableListLayoutProvider( - () => '_', - (_: string | number, dim: { width: number; height: number }) => { - dim.width = layout.width; - dim.height = layout.height; - } - ) - ).current; + const data = [...Array(10).keys()].map((rowNumber) => { + return { + items: getRandomData(rowNumber, undefined), + itemsInViewport: interval(isFactorMobile ? 1 : 3, isFactorMobile ? 3 : 5), + }; + }); return ( - - { - return ( - { - navigate(ROUTES.DETAILS, { row: rowNumber, index: data.index }); - }} - {...testProps(`template-my-page-image-pressable-${index}`)} - > - - - {data.title} - - - ); - }} - isHorizontal - style={theme.styles.recycler} - contentContainerStyle={theme.styles.recyclerContent} - scrollViewProps={{ - showsHorizontalScrollIndicator: false, - }} - focusOptions={{ - forbiddenFocusDirections: ['right'], + + { + navigation.navigate(ROUTES.DETAILS, { row: data.rowNumber, index: data.index }); }} /> - + ); }; -const ScreenCarousels = ({ navigation }: { navigation?: any }) => { - const { theme } = useContext(ThemeContext); - const [recyclers, setRecyclers] = useState< - { - items: any; - dimensions: { - layout: { - width: number; - height: number; - }; - item: { - width: number; - height: number; - }; - }; - }[] - >([]); - - useEffect(() => { - setRecyclers( - itemsInRows.map(([smallScreenItems, bigScreenItems], rowNumber) => ({ - dimensions: getRecyclerDimensions(isFactorMobile ? smallScreenItems : bigScreenItems), - items: getRandomData(rowNumber, undefined), - })) - ); - }, []); - - const renderRecyclers = () => - recyclers.map((recyclerInfo, i) => ( - - )); - - return ( - - {renderRecyclers()} - - ); +const styles = { + screen: { + left: isFactorMobile ? 0 : 100, + }, + cardStyle: { + borderWidth: isFactorMobile ? 0 : 5, + borderRadius: 5, + borderColor: '#0A74E6', + }, }; export default ScreenCarousels; diff --git a/packages/template/src/utils.ts b/packages/template/src/utils.ts index f0cf44732..d7932c50c 100644 --- a/packages/template/src/utils.ts +++ b/packages/template/src/utils.ts @@ -123,7 +123,7 @@ const kittyNames = [ 'Zoey', ]; -function interval(min = 0, max = kittyNames.length - 1) { +export function interval(min = 0, max = kittyNames.length - 1) { return Math.floor(Math.random() * (max - min + 1) + min); } @@ -149,6 +149,8 @@ export function getRandomData(row, idx, countInRow = 6, items = 50) { backgroundImage: `https://placekitten.com/${isSmartTV ? width : width + row}/${height + index}`, //@ts-expect-error for web TVs to compile title: `${kittyNames[interval()]} ${kittyNames[interval()]} ${kittyNames[interval()]}`, + //@ts-expect-error for web TVs to compile + rowNumber: row, }); } From e35c18880a90cace258cba1a5f6e1e820821b827 Mon Sep 17 00:00:00 2001 From: aurimasmi Date: Mon, 23 May 2022 21:27:15 +0300 Subject: [PATCH 02/10] add debounce to atv remote events, fix androidtv dimensions --- packages/sdk/package.json | 3 ++- packages/sdk/src/complexComponents/Card/card.tsx | 5 +++-- packages/sdk/src/complexComponents/List/index.tsx | 6 +++--- packages/sdk/src/hooks/useDimensionsCalculator.ts | 4 ++-- packages/sdk/src/hooks/useStyleFlattener.ts | 14 ++++++++++++-- packages/sdk/src/remoteHandler/index.androidtv.tsx | 7 +++++-- packages/template/src/screens/carousels.tsx | 12 ++++++------ yarn.lock | 2 +- 8 files changed, 34 insertions(+), 19 deletions(-) diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 3528d9516..5c498d5da 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -38,6 +38,7 @@ "dependencies": { "lodash-es": "4.17.21", "lodash.debounce": "^4.0.8", + "lodash.throttle": "^4.0.8", "ts-object-utils": "0.0.5", "@lightningjs/ui-components": "^1.2.1", "@lightningjs/sdk": "4.3.3", @@ -47,4 +48,4 @@ "react": "^17.0.2", "react-native": "0.67.2" } -} +} \ No newline at end of file diff --git a/packages/sdk/src/complexComponents/Card/card.tsx b/packages/sdk/src/complexComponents/Card/card.tsx index 328bc69f5..47e38d4ff 100644 --- a/packages/sdk/src/complexComponents/Card/card.tsx +++ b/packages/sdk/src/complexComponents/Card/card.tsx @@ -5,6 +5,7 @@ import Text from '../../components/Text'; import Pressable from '../../components/Pressable'; import { CardProps } from './types'; import useStyleFlattener from '../../hooks/useStyleFlattener'; +import { Ratio } from '../../helpers'; const Card = React.forwardRef( ( @@ -22,7 +23,7 @@ const Card = React.forwardRef( }, ref ) => { - const styles = useStyleFlattener(style); + const styles = useStyleFlattener(style, ['width', 'height']); const titleStyles = { fontSize: styles.fontSize || baseStyles.title.fontSize, @@ -64,7 +65,7 @@ const baseStyles = { height: '100%', }, title: { - fontSize: 26, + fontSize: Ratio(26), color: '#000000', textAlign: 'center', }, diff --git a/packages/sdk/src/complexComponents/List/index.tsx b/packages/sdk/src/complexComponents/List/index.tsx index 7d310e32a..d0e0c20fd 100644 --- a/packages/sdk/src/complexComponents/List/index.tsx +++ b/packages/sdk/src/complexComponents/List/index.tsx @@ -6,7 +6,7 @@ import RecyclableList, { RecyclableListDataProvider, } from '../../components/RecyclableList'; import Carousel from '../Row'; - +import { Ratio } from '../../helpers'; import useDimensionsCalculator from '../../hooks/useDimensionsCalculator'; import { Context, RecyclableListFocusOptions } from '../../focusManager/types'; @@ -67,7 +67,7 @@ const List = ({ () => '_', (_: string | number, dim: { width: number; height: number }) => { dim.width = boundaries.width; - dim.height = rowHeight; + dim.height = Ratio(rowHeight); } ); }, [boundaries]); @@ -86,7 +86,7 @@ const List = ({ nestedParentContext={nestedParentContext} style={{ width: boundaries.width, - height: rowHeight, + height: Ratio(rowHeight), }} cardStyle={cardStyle} itemDimensions={itemDimensions} diff --git a/packages/sdk/src/hooks/useDimensionsCalculator.ts b/packages/sdk/src/hooks/useDimensionsCalculator.ts index 68d4b0225..f8da56e27 100644 --- a/packages/sdk/src/hooks/useDimensionsCalculator.ts +++ b/packages/sdk/src/hooks/useDimensionsCalculator.ts @@ -19,8 +19,8 @@ export default function useDimensionsCalculator({ style, itemSpacing, itemDimens const [spacings] = useState(() => { return { - left: spacing, - top: spacing, + paddingLeft: spacing, + paddingTop: spacing, paddingBottom: spacing, paddingRight: spacing, }; diff --git a/packages/sdk/src/hooks/useStyleFlattener.ts b/packages/sdk/src/hooks/useStyleFlattener.ts index 2ff063eb4..bf7e0224b 100644 --- a/packages/sdk/src/hooks/useStyleFlattener.ts +++ b/packages/sdk/src/hooks/useStyleFlattener.ts @@ -1,9 +1,19 @@ -export default function useStyleFlattener(style: any) { +import { Ratio } from '../helpers'; + +export default function useStyleFlattener(style: any, ignoreRatioConversion: string[] = []) { if (style !== null) { if (Array.isArray(style)) { const flatted: any = {}; style.map((item) => { - item && Object.keys(item) && Object.keys(item).map((key) => (flatted[key] = item[key])); + item && + Object.keys(item) && + Object.keys(item).map((key) => { + if (ignoreRatioConversion.includes(key)) { + return (flatted[key] = item[key]); + } else { + return (flatted[key] = isNaN(item[key]) ? item[key] : Ratio(item[key])); + } + }); }); return flatted; diff --git a/packages/sdk/src/remoteHandler/index.androidtv.tsx b/packages/sdk/src/remoteHandler/index.androidtv.tsx index 3ec52c476..c648c8dc9 100644 --- a/packages/sdk/src/remoteHandler/index.androidtv.tsx +++ b/packages/sdk/src/remoteHandler/index.androidtv.tsx @@ -1,12 +1,15 @@ import { DeviceEventEmitter } from 'react-native'; -import { useEffect, useRef } from 'react'; +import { useEffect, useRef, useCallback } from 'react'; +import throttle from 'lodash.throttle'; function useTVRemoteHandler(callback: any) { const listener: any = useRef(); + const handler = useCallback(throttle(callback, 100), []); + useEffect(() => { listener.current = DeviceEventEmitter.addListener('onTVRemoteKey', ({ eventKeyAction, eventType }) => { - return callback({ + return handler({ eventType, eventKeyAction, }); diff --git a/packages/template/src/screens/carousels.tsx b/packages/template/src/screens/carousels.tsx index 7dd311b84..329182a70 100644 --- a/packages/template/src/screens/carousels.tsx +++ b/packages/template/src/screens/carousels.tsx @@ -1,7 +1,7 @@ import { List } from '@flexn/sdk'; import React, { useContext } from 'react'; import { isFactorMobile } from '@rnv/renative'; -import { ThemeContext, ROUTES } from '../config'; +import { ThemeContext, ROUTES, Ratio } from '../config'; import { getRandomData, interval } from '../utils'; import Screen from './screen'; @@ -11,7 +11,7 @@ const ScreenCarousels = ({ navigation }: { navigation?: any }) => { const data = [...Array(10).keys()].map((rowNumber) => { return { items: getRandomData(rowNumber, undefined), - itemsInViewport: interval(isFactorMobile ? 1 : 3, isFactorMobile ? 3 : 5), + itemsInViewport: interval(3, 5), }; }); @@ -19,11 +19,11 @@ const ScreenCarousels = ({ navigation }: { navigation?: any }) => { { navigation.navigate(ROUTES.DETAILS, { row: data.rowNumber, index: data.index }); @@ -35,7 +35,7 @@ const ScreenCarousels = ({ navigation }: { navigation?: any }) => { const styles = { screen: { - left: isFactorMobile ? 0 : 100, + left: Ratio(100), }, cardStyle: { borderWidth: isFactorMobile ? 0 : 5, diff --git a/yarn.lock b/yarn.lock index 78507b9a5..d5d6b3036 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18019,7 +18019,7 @@ lodash.templatesettings@^4.0.0: dependencies: lodash._reinterpolate "^3.0.0" -lodash.throttle@^4.1.1: +lodash.throttle@^4.0.8, lodash.throttle@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4" integrity sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ= From 59ed3e78363a87eb1adb7ed520593f622d29342a Mon Sep 17 00:00:00 2001 From: aurimasmi Date: Mon, 23 May 2022 23:18:02 +0300 Subject: [PATCH 03/10] optimize lng card component --- package.json | 4 ++- .../src/complexComponents/Card/index.lng.js | 32 ++++--------------- yarn.lock | 7 ++++ 3 files changed, 16 insertions(+), 27 deletions(-) diff --git a/package.json b/package.json index 52de4d4ed..518eb5a54 100644 --- a/package.json +++ b/package.json @@ -111,5 +111,7 @@ "**/@lightningjs/cli/**" ] }, - "dependencies": {} + "dependencies": { + "@types/lodash.throttle": "^4.1.7" + } } diff --git a/packages/sdk/src/complexComponents/Card/index.lng.js b/packages/sdk/src/complexComponents/Card/index.lng.js index bc7188450..c0bf4de3c 100644 --- a/packages/sdk/src/complexComponents/Card/index.lng.js +++ b/packages/sdk/src/complexComponents/Card/index.lng.js @@ -13,14 +13,6 @@ export default class Card extends Lightning.Component { rtt: true, h: DEFAULT_DIMENSIONS.h, w: DEFAULT_DIMENSIONS.w, - texture: { - type: Lightning.textures.ImageTexture, - resizeMode: { - type: 'cover', - w: DEFAULT_DIMENSIONS.w, - h: DEFAULT_DIMENSIONS.h, - }, - }, shader: { type: Lightning.shaders.RoundedRectangle, }, @@ -63,11 +55,6 @@ export default class Card extends Lightning.Component { this._whenEnabled.then(() => { this._Image.w = this.w; - this._Image.texture = { - resizeMode: { - w: this.w, - }, - }; }); } } @@ -83,11 +70,6 @@ export default class Card extends Lightning.Component { this._whenEnabled.then(() => { this._Image.h = this.h; - this._Image.texture = { - resizeMode: { - w: this.h, - }, - }; this._Text.y = this.h + 30; }); } @@ -102,9 +84,7 @@ export default class Card extends Lightning.Component { this._src = value; this.src = value; this._whenEnabled.then(() => { - this._Image.texture = { - src: this.src, - }; + this._Image.src = this.src; }); } } @@ -239,7 +219,7 @@ export default class Card extends Lightning.Component { switch (type) { case 'scale': template.focus = { smooth: scale }; - template.unfocus = { shader: scaleDefault }; + template.unfocus = { smooth: scaleDefault }; break; case 'scale_with_border': template.focus = { smooth: scale, shader }; @@ -268,14 +248,14 @@ export default class Card extends Lightning.Component { } _focus() { - this.fireAncestors('$onCardFocus', this.eventValue); - this.signal('onFocus', this.eventValue); + // this.fireAncestors('$onCardFocus', this.eventValue); + // this.signal('onFocus', this.eventValue); this.patch({ Image: this._setAnimationValues().focus }); } _unfocus() { - this.fireAncestors('$onCardBlur', this.eventValue); - this.signal('onBlur', this.eventValue); + // this.fireAncestors('$onCardBlur', this.eventValue); + // this.signal('onBlur', this.eventValue); this.patch({ Image: this._setAnimationValues().unfocus }); } } diff --git a/yarn.lock b/yarn.lock index d5d6b3036..db24cbcf3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6113,6 +6113,13 @@ dependencies: "@types/lodash" "*" +"@types/lodash.throttle@^4.1.7": + version "4.1.7" + resolved "https://registry.yarnpkg.com/@types/lodash.throttle/-/lodash.throttle-4.1.7.tgz#4ef379eb4f778068022310ef166625f420b6ba58" + integrity sha512-znwGDpjCHQ4FpLLx19w4OXDqq8+OvREa05H89obtSyXyOFKL3dDjCslsmfBz0T2FU8dmf5Wx1QvogbINiGIu9g== + dependencies: + "@types/lodash" "*" + "@types/lodash.union@^4.6.6": version "4.6.6" resolved "https://registry.yarnpkg.com/@types/lodash.union/-/lodash.union-4.6.6.tgz#2f77f2088326ed147819e9e384182b99aae8d4b0" From f7b8d900ff8b47afa36a87b3fab3b4ead0060bc7 Mon Sep 17 00:00:00 2001 From: aurimasmi Date: Tue, 24 May 2022 16:09:24 +0300 Subject: [PATCH 04/10] fix: add different dimensions for mobile components --- packages/template/src/screens/carousels.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/template/src/screens/carousels.tsx b/packages/template/src/screens/carousels.tsx index 329182a70..953527369 100644 --- a/packages/template/src/screens/carousels.tsx +++ b/packages/template/src/screens/carousels.tsx @@ -11,7 +11,7 @@ const ScreenCarousels = ({ navigation }: { navigation?: any }) => { const data = [...Array(10).keys()].map((rowNumber) => { return { items: getRandomData(rowNumber, undefined), - itemsInViewport: interval(3, 5), + itemsInViewport: interval(isFactorMobile ? 1 : 3, isFactorMobile ? 3 : 5), }; }); @@ -19,11 +19,11 @@ const ScreenCarousels = ({ navigation }: { navigation?: any }) => { { navigation.navigate(ROUTES.DETAILS, { row: data.rowNumber, index: data.index }); @@ -35,7 +35,7 @@ const ScreenCarousels = ({ navigation }: { navigation?: any }) => { const styles = { screen: { - left: Ratio(100), + left: isFactorMobile ? 0 : Ratio(100), }, cardStyle: { borderWidth: isFactorMobile ? 0 : 5, From 33b382211fc5bc0ebf1202d23d434f1d357aa862 Mon Sep 17 00:00:00 2001 From: aurimasmi Date: Tue, 24 May 2022 17:19:17 +0300 Subject: [PATCH 05/10] lng respect itemsInViewport property --- .../TestCases/Components/List/index.lng.js | 2 +- .../src/complexComponents/Card/index.lng.js | 5 ++-- .../src/complexComponents/List/index.lng.js | 2 ++ .../src/complexComponents/Row/index.lng.js | 24 ++++++++++++++++++- .../sdk/src/hooks/useDimensionsCalculator.ts | 2 +- 5 files changed, 30 insertions(+), 5 deletions(-) diff --git a/packages/harness/src/screens/TestCases/Components/List/index.lng.js b/packages/harness/src/screens/TestCases/Components/List/index.lng.js index 0da741145..fd79c3138 100644 --- a/packages/harness/src/screens/TestCases/Components/List/index.lng.js +++ b/packages/harness/src/screens/TestCases/Components/List/index.lng.js @@ -26,6 +26,7 @@ export default class Press extends Lightning.Component { return { rowTitle: kittyNames[interval()], items: generateData(400, 250), + itemsInViewport: interval(3, 8), }; }); @@ -39,7 +40,6 @@ export default class Press extends Lightning.Component { data, itemSpacing: 30, card: { - w: 250, h: 250, }, row: { diff --git a/packages/sdk/src/complexComponents/Card/index.lng.js b/packages/sdk/src/complexComponents/Card/index.lng.js index c0bf4de3c..cac3bf523 100644 --- a/packages/sdk/src/complexComponents/Card/index.lng.js +++ b/packages/sdk/src/complexComponents/Card/index.lng.js @@ -16,6 +16,7 @@ export default class Card extends Lightning.Component { shader: { type: Lightning.shaders.RoundedRectangle, }, + resizeMode: 'contain', }, Text: { y: DEFAULT_DIMENSIONS.h + 30, @@ -250,12 +251,12 @@ export default class Card extends Lightning.Component { _focus() { // this.fireAncestors('$onCardFocus', this.eventValue); // this.signal('onFocus', this.eventValue); - this.patch({ Image: this._setAnimationValues().focus }); + this.patch({ Image: this._setAnimationValues().focus, zIndex: 1 }); } _unfocus() { // this.fireAncestors('$onCardBlur', this.eventValue); // this.signal('onBlur', this.eventValue); - this.patch({ Image: this._setAnimationValues().unfocus }); + this.patch({ Image: this._setAnimationValues().unfocus, zIndex: 0 }); } } diff --git a/packages/sdk/src/complexComponents/List/index.lng.js b/packages/sdk/src/complexComponents/List/index.lng.js index c82449b17..fa5112042 100644 --- a/packages/sdk/src/complexComponents/List/index.lng.js +++ b/packages/sdk/src/complexComponents/List/index.lng.js @@ -60,6 +60,8 @@ export default class List extends Lightning.Component { this._List.items = this.data.map((rowData) => ({ type: Row, data: rowData.items, + itemsInViewport: rowData.itemsInViewport, + itemSpacing: this.itemSpacing, focusOptions: this.focusOptions, lazyScroll: this.lazyScroll, card: this.card, diff --git a/packages/sdk/src/complexComponents/Row/index.lng.js b/packages/sdk/src/complexComponents/Row/index.lng.js index 9aafa7794..1d901115c 100644 --- a/packages/sdk/src/complexComponents/Row/index.lng.js +++ b/packages/sdk/src/complexComponents/Row/index.lng.js @@ -1,4 +1,3 @@ -// import { Row as LngRow } from '@lightningjs/ui-components'; import { Lightning } from '@lightningjs/sdk'; import Card from '../Card'; import { getHexColor } from '../../helpers'; @@ -34,6 +33,7 @@ export default class Row extends Lightning.Component { _construct() { this._whenEnabled = new Promise((resolve) => (this._enable = resolve)); + this._itemsInViewport = 5; } _init() { @@ -119,6 +119,7 @@ export default class Row extends Lightning.Component { set data(value) { this._data = value; this._whenEnabled.then(() => { + this._calculateCardWidth(); this._Row.items = this.data.map((item) => ({ type: Card, src: item.backgroundImage, @@ -130,6 +131,14 @@ export default class Row extends Lightning.Component { }); } + get itemsInViewport() { + return this._itemsInViewport; + } + + set itemsInViewport(value) { + this._itemsInViewport = value; + } + get row() { return this._row || {}; } @@ -160,7 +169,12 @@ export default class Row extends Lightning.Component { this._focusOptions = value; } + get itemSpacing() { + return this._itemSpacing || 0; + } + set itemSpacing(value) { + this._itemSpacing = value; this._whenEnabled.then(() => { this._Row.itemSpacing = value; }); @@ -172,6 +186,14 @@ export default class Row extends Lightning.Component { }); } + _calculateCardWidth() { + const w = this.w || this.stage.w; + if (w) { + const actualWidth = w - this.itemSpacing; + this.card.w = actualWidth / this.itemsInViewport - this.itemSpacing; + } + } + $onCardPress(eventValue) { this.fireAncestors('$onCardPress', eventValue); this.signal('onPress', eventValue); diff --git a/packages/sdk/src/hooks/useDimensionsCalculator.ts b/packages/sdk/src/hooks/useDimensionsCalculator.ts index f8da56e27..52132408f 100644 --- a/packages/sdk/src/hooks/useDimensionsCalculator.ts +++ b/packages/sdk/src/hooks/useDimensionsCalculator.ts @@ -52,7 +52,7 @@ export default function useDimensionsCalculator({ style, itemSpacing, itemDimens const calculateRowDimensions = (width: number) => { const itemHeight = Ratio(itemDimensions.height); - const actualWidth = width - itemSpacing; + const actualWidth = width - itemSpacing; // todo: calculate both sides??? return { layout: { width: actualWidth / itemsInViewport, height: itemHeight + spacing }, From f9cad71bc46e380c60823bdeebd1f4236fb474a2 Mon Sep 17 00:00:00 2001 From: aurimasmi Date: Tue, 24 May 2022 17:25:31 +0300 Subject: [PATCH 06/10] chore: change data generation fn --- .../harness/src/screens/TestCases/Components/Grid/index.lng.js | 2 +- .../harness/src/screens/TestCases/Components/Grid/index.tsx | 2 +- .../harness/src/screens/TestCases/Components/List/index.lng.js | 2 +- .../harness/src/screens/TestCases/Components/List/index.tsx | 2 +- .../harness/src/screens/TestCases/Components/Row/index.lng.js | 2 +- packages/harness/src/screens/TestCases/Components/Row/index.tsx | 2 +- packages/template/src/utils.ts | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/harness/src/screens/TestCases/Components/Grid/index.lng.js b/packages/harness/src/screens/TestCases/Components/Grid/index.lng.js index 7183528ad..759ae4631 100644 --- a/packages/harness/src/screens/TestCases/Components/Grid/index.lng.js +++ b/packages/harness/src/screens/TestCases/Components/Grid/index.lng.js @@ -12,7 +12,7 @@ function generateData(width, height, items = 30) { for (let index = 0; index < items; index++) { temp.push({ index, - backgroundImage: `https://placekitten.com/${width + index}/${height + index}`, + backgroundImage: `https://placekitten.com/${width + index}/${height}`, // title: `${kittyNames[interval()]} ${kittyNames[interval()]} ${kittyNames[interval()]}`, }); } diff --git a/packages/harness/src/screens/TestCases/Components/Grid/index.tsx b/packages/harness/src/screens/TestCases/Components/Grid/index.tsx index 0a65f1767..685d1bec5 100644 --- a/packages/harness/src/screens/TestCases/Components/Grid/index.tsx +++ b/packages/harness/src/screens/TestCases/Components/Grid/index.tsx @@ -7,7 +7,7 @@ function generateData(width, height, items = 30) { for (let index = 0; index < items; index++) { temp.push({ index, - backgroundImage: `https://placekitten.com/${width + index}/${height + index}`, + backgroundImage: `https://placekitten.com/${width + index}/${height}`, }); } diff --git a/packages/harness/src/screens/TestCases/Components/List/index.lng.js b/packages/harness/src/screens/TestCases/Components/List/index.lng.js index fd79c3138..b1b3601df 100644 --- a/packages/harness/src/screens/TestCases/Components/List/index.lng.js +++ b/packages/harness/src/screens/TestCases/Components/List/index.lng.js @@ -12,7 +12,7 @@ function generateData(width, height, items = 30) { for (let index = 0; index < items; index++) { temp.push({ index, - backgroundImage: `https://placekitten.com/${width + index}/${height + index}`, + backgroundImage: `https://placekitten.com/${width + index}/${height}`, title: `${kittyNames[interval()]} ${kittyNames[interval()]} ${kittyNames[interval()]}`, }); } diff --git a/packages/harness/src/screens/TestCases/Components/List/index.tsx b/packages/harness/src/screens/TestCases/Components/List/index.tsx index c693bfee6..44e680a26 100644 --- a/packages/harness/src/screens/TestCases/Components/List/index.tsx +++ b/packages/harness/src/screens/TestCases/Components/List/index.tsx @@ -13,7 +13,7 @@ function generateData(width, height, items = 30) { for (let index = 0; index < items; index++) { temp.push({ index, - backgroundImage: `https://placekitten.com/${width + index}/${height + index}`, + backgroundImage: `https://placekitten.com/${width + index}/${height}`, title: `${kittyNames[interval()]} ${kittyNames[interval()]} ${kittyNames[interval()]}`, }); } diff --git a/packages/harness/src/screens/TestCases/Components/Row/index.lng.js b/packages/harness/src/screens/TestCases/Components/Row/index.lng.js index 00908d8ce..eb704aee2 100644 --- a/packages/harness/src/screens/TestCases/Components/Row/index.lng.js +++ b/packages/harness/src/screens/TestCases/Components/Row/index.lng.js @@ -13,7 +13,7 @@ function generateData(width, height, items = 30) { for (let index = 0; index < items; index++) { temp.push({ index, - backgroundImage: `https://placekitten.com/${width + index}/${height + index}`, + backgroundImage: `https://placekitten.com/${width + index}/${height}`, title: `${kittyNames[interval()]} ${kittyNames[interval()]} ${kittyNames[interval()]}`, }); } diff --git a/packages/harness/src/screens/TestCases/Components/Row/index.tsx b/packages/harness/src/screens/TestCases/Components/Row/index.tsx index 22726bcfc..788d4730f 100644 --- a/packages/harness/src/screens/TestCases/Components/Row/index.tsx +++ b/packages/harness/src/screens/TestCases/Components/Row/index.tsx @@ -13,7 +13,7 @@ function generateData(width, height, items = 30) { for (let index = 0; index < items; index++) { temp.push({ index, - backgroundImage: `https://placekitten.com/${width + index}/${height + index}`, + backgroundImage: `https://placekitten.com/${width + index}/${height}`, title: `${kittyNames[interval()]} ${kittyNames[interval()]} ${kittyNames[interval()]}`, }); } diff --git a/packages/template/src/utils.ts b/packages/template/src/utils.ts index d7932c50c..8a5d57893 100644 --- a/packages/template/src/utils.ts +++ b/packages/template/src/utils.ts @@ -146,7 +146,7 @@ export function getRandomData(row, idx, countInRow = 6, items = 50) { //@ts-expect-error for web TVs to compile index, //@ts-expect-error for web TVs to compile - backgroundImage: `https://placekitten.com/${isSmartTV ? width : width + row}/${height + index}`, + backgroundImage: `https://placekitten.com/${isSmartTV ? width : width + row}/${height}`, //@ts-expect-error for web TVs to compile title: `${kittyNames[interval()]} ${kittyNames[interval()]} ${kittyNames[interval()]}`, //@ts-expect-error for web TVs to compile From d61f3eb3c64bfaebdd50cf043caf5dcd2edf4628 Mon Sep 17 00:00:00 2001 From: aurimasmi Date: Tue, 24 May 2022 22:00:03 +0300 Subject: [PATCH 07/10] fix: grid manage items sizes, do not lost focus on odd number of items --- .../TestCases/Components/Grid/index.lng.js | 8 ++--- .../src/complexComponents/Grid/index.lng.js | 36 ++++++++++++++----- .../src/complexComponents/Row/index.lng.js | 2 +- .../sdk/src/lng/FocusManager/index.lng.js | 9 +++-- 4 files changed, 39 insertions(+), 16 deletions(-) diff --git a/packages/harness/src/screens/TestCases/Components/Grid/index.lng.js b/packages/harness/src/screens/TestCases/Components/Grid/index.lng.js index 759ae4631..6308eb1d8 100644 --- a/packages/harness/src/screens/TestCases/Components/Grid/index.lng.js +++ b/packages/harness/src/screens/TestCases/Components/Grid/index.lng.js @@ -22,19 +22,19 @@ function generateData(width, height, items = 30) { export default class Grid extends Lightning.Component { static _template() { - const data = generateData(400, 250); + const data = generateData(400, 250, 27); return { w: 1920, h: 1080, - x: 50, - y: 50, + x: 0, + y: 0, Grid: { type: FlexnGrid, data, itemSpacing: 30, + itemsInViewport: 6, card: { - w: 250, h: 250, }, focusOptions: { diff --git a/packages/sdk/src/complexComponents/Grid/index.lng.js b/packages/sdk/src/complexComponents/Grid/index.lng.js index 52f4ae064..e89f4e56a 100644 --- a/packages/sdk/src/complexComponents/Grid/index.lng.js +++ b/packages/sdk/src/complexComponents/Grid/index.lng.js @@ -7,12 +7,9 @@ export default class Grid extends Lightning.Component { static _template() { return { Grid: { - h: 500, - w: 1920, type: Column, independentNavigation: true, plinko: true, - itemSpacing: 25, scrollIndex: 1, items: [], }, @@ -21,6 +18,12 @@ export default class Grid extends Lightning.Component { _construct() { this._whenEnabled = new Promise((resolve) => (this._enable = resolve)); + this._itemSpacing = 30; + this._itemsInViewport = 6; + } + + _init() { + this._setItemSpacing(); } get _Grid() { @@ -64,16 +67,16 @@ export default class Grid extends Lightning.Component { set data(value) { this._data = value; this._whenEnabled.then(() => { - //TODO: measure how much to fit into one line const arrayOfRows = []; while (this.data.length) { - arrayOfRows.push(this.data.splice(0, 5)); + arrayOfRows.push(this.data.splice(0, this.itemsInViewport)); } - this._Grid.items = arrayOfRows.map((rowData) => ({ type: Row, data: rowData, focusOptions: this.focusOptions, + itemSpacing: this.itemSpacing, + itemsInViewport: this.itemsInViewport, independentNavigation: true, lazyScroll: this.lazyScroll, card: this.card, @@ -124,9 +127,7 @@ export default class Grid extends Lightning.Component { set itemSpacing(value) { if (value !== this._itemSpacing) { this._itemSpacing = value; - this._whenEnabled.then(() => { - this._Grid.itemSpacing = value; - }); + this._setItemSpacing(); } } @@ -140,6 +141,23 @@ export default class Grid extends Lightning.Component { } } + get itemsInViewport() { + return this._itemsInViewport; + } + + set itemsInViewport(value) { + if (value !== this.itemsInViewport) { + this._itemsInViewport = value; + } + } + + _setItemSpacing() { + this._whenEnabled.then(() => { + this._Grid.x = this.itemSpacing; + this._Grid.y = this.itemSpacing; + }); + } + $onCardPress(eventValue) { this.signal('onPress', eventValue); } diff --git a/packages/sdk/src/complexComponents/Row/index.lng.js b/packages/sdk/src/complexComponents/Row/index.lng.js index 1d901115c..c2365b2dc 100644 --- a/packages/sdk/src/complexComponents/Row/index.lng.js +++ b/packages/sdk/src/complexComponents/Row/index.lng.js @@ -189,7 +189,7 @@ export default class Row extends Lightning.Component { _calculateCardWidth() { const w = this.w || this.stage.w; if (w) { - const actualWidth = w - this.itemSpacing; + const actualWidth = w - this.itemSpacing * 2; this.card.w = actualWidth / this.itemsInViewport - this.itemSpacing; } } diff --git a/packages/sdk/src/lng/FocusManager/index.lng.js b/packages/sdk/src/lng/FocusManager/index.lng.js index b626a0948..6bb7f417a 100644 --- a/packages/sdk/src/lng/FocusManager/index.lng.js +++ b/packages/sdk/src/lng/FocusManager/index.lng.js @@ -85,12 +85,17 @@ export default class FocusManager extends lng.Component { if (this._independentNavigation) { const state = this._getState(); if (state === 'Row') { + const rowLength = this.parent.data.length - 1; const prevIndex = this.parent?.parentColumn?.prevRowSelection; - + if (prevIndex === undefined || Math.abs(prevIndex - this._selectedIndex) === 0) { return this._selectedIndex; + } else if (prevIndex > rowLength) { + this.parent.parentColumn.prevRowSelection = rowLength; + this._selectedIndex = prevIndex; + return this._selectedIndex; } - + this._selectedIndex = prevIndex; return prevIndex; } From 3fca18990eb56ec0d3be61301dffcf77150e9c1f Mon Sep 17 00:00:00 2001 From: aurimasmi Date: Wed, 25 May 2022 17:15:54 +0300 Subject: [PATCH 08/10] fix: recalculate layout instead only absolutes to prevent incorrect offset --- packages/sdk/src/focusManager/layoutManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sdk/src/focusManager/layoutManager.ts b/packages/sdk/src/focusManager/layoutManager.ts index 7f2fb6acf..9a2fe61ec 100644 --- a/packages/sdk/src/focusManager/layoutManager.ts +++ b/packages/sdk/src/focusManager/layoutManager.ts @@ -106,7 +106,7 @@ function measure(context: Context, ref: any) { } } - recalculateAbsolutes(context); + recalculateLayout(context); }); // get the layout of innerView in scroll From a7035209f6b2f94e6c7ce716cad4f9fd52dbf74b Mon Sep 17 00:00:00 2001 From: aurimasmi Date: Wed, 25 May 2022 17:45:51 +0300 Subject: [PATCH 09/10] fix: if scroll target is equal current offset do not scroll --- packages/harness/src/app/index.js | 2 +- packages/sdk/src/focusManager/focusManager.ts | 21 ++++++++++--------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/packages/harness/src/app/index.js b/packages/harness/src/app/index.js index 1575130d0..bc9bcf7e0 100644 --- a/packages/harness/src/app/index.js +++ b/packages/harness/src/app/index.js @@ -40,7 +40,7 @@ const Entry = () => { }} > {/* initialRouteName="*" */} - + {Object.entries({ ...(!isFactorDesktop ? FocusTestScreens : {}), // don't add focus test screens to navigation for macos/windows platforms (react-native-gesture-handler is not supported yet) diff --git a/packages/sdk/src/focusManager/focusManager.ts b/packages/sdk/src/focusManager/focusManager.ts index cdaf007f8..04cca7672 100644 --- a/packages/sdk/src/focusManager/focusManager.ts +++ b/packages/sdk/src/focusManager/focusManager.ts @@ -189,16 +189,17 @@ const executeScroll = (direction: string, contextParameters: any) => { : calculateVerticalScrollViewTarget(direction, p, contextParameters); if (scrollTarget) { - // log('SCROLLING!!!!', scrollTarget); - p.node.current.scrollTo(scrollTarget); - p.scrollOffsetX = scrollTarget.x; - p.scrollOffsetY = scrollTarget.y; - if (isDebuggerEnabled) { - Object.values(contextMap).forEach((v) => { - recalculateLayout(v); - }); - } else { - recalculateLayout(currentContext); + if (p.scrollOffsetX !== scrollTarget.x || p.scrollOffsetY !== scrollTarget.y) { + p.node.current.scrollTo(scrollTarget); + p.scrollOffsetX = scrollTarget.x; + p.scrollOffsetY = scrollTarget.y; + if (isDebuggerEnabled) { + Object.values(contextMap).forEach((v) => { + recalculateLayout(v); + }); + } else { + recalculateLayout(currentContext); + } } } }); From ce3207f10a01cfd4f6a725787c8c082ce63de158 Mon Sep 17 00:00:00 2001 From: aurimasmi Date: Wed, 25 May 2022 18:15:50 +0300 Subject: [PATCH 10/10] fix: prevent overscroll moving down --- packages/sdk/src/focusManager/focusManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sdk/src/focusManager/focusManager.ts b/packages/sdk/src/focusManager/focusManager.ts index 04cca7672..0cbd9df26 100644 --- a/packages/sdk/src/focusManager/focusManager.ts +++ b/packages/sdk/src/focusManager/focusManager.ts @@ -300,7 +300,7 @@ const calculateVerticalScrollViewTarget = (direction: string, scrollView: any, c if (yMaxScroll >= targetY) { scrollTarget.y = currentLayout.yMin - scrollView.layout.yMin - verticalViewportOffset; } else { - scrollTarget.y = yMaxScroll - windowHeight + verticalViewportOffset; + scrollTarget.y = yMaxScroll - windowHeight; } } }