diff --git a/react/features/app/actions.native.js b/react/features/app/actions.native.ts similarity index 85% rename from react/features/app/actions.native.js rename to react/features/app/actions.native.ts index 7004eeed6..050e67176 100644 --- a/react/features/app/actions.native.js +++ b/react/features/app/actions.native.ts @@ -1,38 +1,41 @@ -// @flow - -import type { Dispatch } from 'redux'; - -import { setRoom } from '../base/conference'; +/* eslint-disable lines-around-comment */ +import { setRoom } from '../base/conference/actions'; import { configWillLoad, - createFakeConfig, loadConfigError, - restoreConfig, setConfig, storeConfig -} from '../base/config'; -import { connect, disconnect, setLocationURL } from '../base/connection'; +} from '../base/config/actions'; +import { + createFakeConfig, + restoreConfig +} from '../base/config/functions'; +import { connect, disconnect, setLocationURL } from '../base/connection/actions'; import { loadConfig } from '../base/lib-jitsi-meet/functions.native'; -import { createDesiredLocalTracks } from '../base/tracks'; +import { createDesiredLocalTracks } from '../base/tracks/actions'; +import { parseURLParams } from '../base/util/parseURLParams'; import { appendURLParam, getBackendSafeRoomName, parseURIString, - parseURLParams, toURLString -} from '../base/util'; +} from '../base/util/uri'; +// @ts-ignore import { isPrejoinPageEnabled } from '../mobile/navigation/functions'; import { goBackToRoot, navigateRoot + // @ts-ignore } from '../mobile/navigation/rootNavigationContainerRef'; +// @ts-ignore import { screen } from '../mobile/navigation/routes'; -import { clearNotifications } from '../notifications'; +import { clearNotifications } from '../notifications/actions'; +// @ts-ignore import { setFatalError } from '../overlay'; -import { getDefaultURL } from './functions'; -import { addTrackStateToURL } from './functions.native'; +import { addTrackStateToURL, getDefaultURL } from './functions.native'; import logger from './logger'; +import { IStore } from './types'; export * from './actions.any'; @@ -48,7 +51,7 @@ export * from './actions.any'; export function appNavigate(uri?: string) { logger.info(`appNavigate to ${uri}`); - return async (dispatch: Dispatch, getState: Function) => { + return async (dispatch: IStore['dispatch'], getState: IStore['getState']) => { let location = parseURIString(uri); // If the specified location (URI) does not identify a host, use the app's @@ -93,7 +96,7 @@ export function appNavigate(uri?: string) { let url = `${baseURL}config.js`; // XXX In order to support multiple shards, tell the room to the deployment. - room && (url = appendURLParam(url, 'room', getBackendSafeRoomName(room))); + room && (url = appendURLParam(url, 'room', getBackendSafeRoomName(room) ?? '')); const { release } = parseURLParams(location, true, 'search'); @@ -110,7 +113,7 @@ export function appNavigate(uri?: string) { try { config = await loadConfig(url); dispatch(storeConfig(baseURL, config)); - } catch (error) { + } catch (error: any) { config = restoreConfig(baseURL); if (!config) { @@ -160,13 +163,14 @@ export function appNavigate(uri?: string) { * @returns {Function} */ export function reloadNow() { - return (dispatch: Dispatch, getState: Function) => { + return (dispatch: IStore['dispatch'], getState: IStore['getState']) => { dispatch(setFatalError(undefined)); const state = getState(); const { locationURL } = state['features/base/connection']; // Preserve the local tracks muted state after the reload. + // @ts-ignore const newURL = addTrackStateToURL(locationURL, state); logger.info(`Reloading the conference using URL: ${locationURL}`); diff --git a/react/features/app/functions.native.js b/react/features/app/functions.native.ts similarity index 79% rename from react/features/app/functions.native.js rename to react/features/app/functions.native.ts index 0c0425fcc..9c43aeea4 100644 --- a/react/features/app/functions.native.js +++ b/react/features/app/functions.native.ts @@ -1,9 +1,8 @@ -// @flow - import { NativeModules } from 'react-native'; -import { toState } from '../base/redux'; -import { getServerURL } from '../base/settings'; +import { IStateful } from '../base/app/types'; +import { toState } from '../base/redux/functions'; +import { getServerURL } from '../base/settings/functions'; export * from './functions.any'; @@ -15,7 +14,7 @@ export * from './functions.any'; * function. * @returns {string} - Default URL for the app. */ -export function getDefaultURL(stateful: Function | Object) { +export function getDefaultURL(stateful: IStateful) { const state = toState(stateful); return getServerURL(state); diff --git a/react/features/base/connection/actions.native.ts b/react/features/base/connection/actions.native.ts index 945376c46..8ee27494a 100644 --- a/react/features/base/connection/actions.native.ts +++ b/react/features/base/connection/actions.native.ts @@ -141,12 +141,16 @@ function _connectionWillConnect(connection: Object) { }; } + +/* eslint-disable @typescript-eslint/no-unused-vars */ /** * Closes connection. * + * @param {boolean} _ - Used in web. * @returns {Function} */ -export function disconnect() { +export function disconnect(_?: boolean) { + /* eslint-enable @typescript-eslint/no-unused-vars */ return (dispatch: IStore['dispatch'], getState: IStore['getState']): Promise => { const state = getState(); diff --git a/react/features/base/lib-jitsi-meet/functions.native.js b/react/features/base/lib-jitsi-meet/functions.native.ts similarity index 93% rename from react/features/base/lib-jitsi-meet/functions.native.js rename to react/features/base/lib-jitsi-meet/functions.native.ts index 0061ff6e7..1bfc6d4c5 100644 --- a/react/features/base/lib-jitsi-meet/functions.native.js +++ b/react/features/base/lib-jitsi-meet/functions.native.ts @@ -1,9 +1,8 @@ -// @flow - +// @ts-ignore import Bourne from '@hapi/bourne'; import { NativeModules } from 'react-native'; -import { loadScript } from '../util'; +import { loadScript } from '../util/loadScript.native'; import logger from './logger'; diff --git a/react/features/base/logging/LogTransport.native.ts b/react/features/base/logging/LogTransport.native.ts index 4af8677a4..4bcb7ef55 100644 --- a/react/features/base/logging/LogTransport.native.ts +++ b/react/features/base/logging/LogTransport.native.ts @@ -1,4 +1,6 @@ import { NativeModules } from 'react-native'; +// eslint-disable-next-line lines-around-comment +// @ts-ignore import { format } from 'util'; // Some code adapted from https://github.com/houserater/react-native-lumberjack diff --git a/react/features/base/react/components/native/IconButton.tsx b/react/features/base/react/components/native/IconButton.tsx index 9b240c994..7b03f1653 100644 --- a/react/features/base/react/components/native/IconButton.tsx +++ b/react/features/base/react/components/native/IconButton.tsx @@ -4,13 +4,13 @@ import { TouchableRipple } from 'react-native-paper'; import Icon from '../../../icons/components/Icon'; import BaseTheme from '../../../ui/components/BaseTheme.native'; import { BUTTON_TYPES } from '../../../ui/constants'; -import { IconButtonProps } from '../../types'; +import { IIconButtonProps } from '../../types'; // @ts-ignore import styles from './styles'; -const IconButton: React.FC = ({ +const IconButton: React.FC = ({ accessibilityLabel, color: iconColor, disabled, @@ -20,7 +20,7 @@ const IconButton: React.FC = ({ style, tapColor, type -}: IconButtonProps) => { +}: IIconButtonProps) => { const { PRIMARY, SECONDARY, TERTIARY } = BUTTON_TYPES; let color; diff --git a/react/features/base/sounds/functions.android.js b/react/features/base/sounds/functions.android.ts similarity index 100% rename from react/features/base/sounds/functions.android.js rename to react/features/base/sounds/functions.android.ts diff --git a/react/features/base/sounds/functions.ios.js b/react/features/base/sounds/functions.ios.ts similarity index 82% rename from react/features/base/sounds/functions.ios.js rename to react/features/base/sounds/functions.ios.ts index d086d82fb..185d0109b 100644 --- a/react/features/base/sounds/functions.ios.js +++ b/react/features/base/sounds/functions.ios.ts @@ -1,4 +1,4 @@ -import { getSdkBundlePath } from '../../app/functions'; +import { getSdkBundlePath } from '../../app/functions.native'; /** * Returns the location of the sounds. On iOS it's the location of the SDK diff --git a/react/features/base/tracks/actions.native.ts b/react/features/base/tracks/actions.native.ts index 0bd85008d..acacf1c36 100644 --- a/react/features/base/tracks/actions.native.ts +++ b/react/features/base/tracks/actions.native.ts @@ -17,7 +17,7 @@ export * from './actions.any'; * @param {boolean} enabled - The state to toggle screen sharing to. * @returns {Function} */ -export function toggleScreensharing(enabled: boolean): Function { +export function toggleScreensharing(enabled: boolean) { return (dispatch: IStore['dispatch'], getState: IStore['getState']) => { const state = getState(); diff --git a/react/features/base/ui/hooks/useContextMenu.ts b/react/features/base/ui/hooks/useContextMenu.web.ts similarity index 100% rename from react/features/base/ui/hooks/useContextMenu.ts rename to react/features/base/ui/hooks/useContextMenu.web.ts diff --git a/react/features/base/util/uri.ts b/react/features/base/util/uri.ts index 0327e0a53..eabd38b05 100644 --- a/react/features/base/util/uri.ts +++ b/react/features/base/util/uri.ts @@ -419,7 +419,7 @@ export function safeDecodeURIComponent(text: string) { * @returns {string} - A {@code String} representation of the specified * {@code obj} which is supposed to represent a URL. */ -export function toURLString(obj?: (Object | string)): string | undefined | null { +export function toURLString(obj?: (Object | string)) { let str; switch (typeof obj) { diff --git a/react/features/chat/middleware.ts b/react/features/chat/middleware.ts index cb71ed8db..49edc4494 100644 --- a/react/features/chat/middleware.ts +++ b/react/features/chat/middleware.ts @@ -21,7 +21,7 @@ import StateListenerRegistry from '../base/redux/StateListenerRegistry'; import { playSound, registerSound, unregisterSound } from '../base/sounds/actions'; import { addGif } from '../gifs/actions'; import { GIF_PREFIX } from '../gifs/constants'; -import { getGifDisplayMode, isGifMessage } from '../gifs/functions'; +import { getGifDisplayMode, isGifMessage } from '../gifs/function.any'; import { showMessageNotification } from '../notifications/actions'; import { NOTIFICATION_TIMEOUT_TYPE } from '../notifications/constants'; import { resetNbUnreadPollsMessages } from '../polls/actions'; diff --git a/react/features/gifs/functions.ts b/react/features/gifs/function.any.ts similarity index 84% rename from react/features/gifs/functions.ts rename to react/features/gifs/function.any.ts index 714176d53..4d1d3f221 100644 --- a/react/features/gifs/functions.ts +++ b/react/features/gifs/function.any.ts @@ -1,5 +1,4 @@ import { IReduxState } from '../app/types'; -import { showOverflowDrawer } from '../toolbox/functions.web'; import { GIF_PREFIX } from './constants'; import { IGif } from './reducer'; @@ -26,19 +25,6 @@ export function isGifMessage(message: string) { .startsWith(GIF_PREFIX); } -/** - * Returns the visibility state of the gifs menu. - * - * @param {IReduxState} state - The state of the application. - * @returns {boolean} - */ -export function isGifsMenuOpen(state: IReduxState) { - const overflowDrawer = showOverflowDrawer(state); - const { drawerVisible, menuOpen } = state['features/gifs']; - - return overflowDrawer ? drawerVisible : menuOpen; -} - /** * Returns the url of the gif selected in the gifs menu. * diff --git a/react/features/gifs/functions.native.ts b/react/features/gifs/functions.native.ts new file mode 100644 index 000000000..2e60257da --- /dev/null +++ b/react/features/gifs/functions.native.ts @@ -0,0 +1 @@ +export * from './function.any'; diff --git a/react/features/gifs/functions.web.ts b/react/features/gifs/functions.web.ts new file mode 100644 index 000000000..582677f4b --- /dev/null +++ b/react/features/gifs/functions.web.ts @@ -0,0 +1,17 @@ +import { IReduxState } from '../app/types'; +import { showOverflowDrawer } from '../toolbox/functions.web'; + +export * from './function.any'; + +/** + * Returns the visibility state of the gifs menu. + * + * @param {IReduxState} state - The state of the application. + * @returns {boolean} + */ +export function isGifsMenuOpen(state: IReduxState) { + const overflowDrawer = showOverflowDrawer(state); + const { drawerVisible, menuOpen } = state['features/gifs']; + + return overflowDrawer ? drawerVisible : menuOpen; +} diff --git a/react/features/gifs/middleware.any.ts b/react/features/gifs/middleware.any.ts index b16e184e9..dde1efa32 100644 --- a/react/features/gifs/middleware.any.ts +++ b/react/features/gifs/middleware.any.ts @@ -4,7 +4,7 @@ import MiddlewareRegistry from '../base/redux/MiddlewareRegistry'; import { ADD_GIF_FOR_PARTICIPANT, HIDE_GIF_FOR_PARTICIPANT, SHOW_GIF_FOR_PARTICIPANT } from './actionTypes'; import { removeGif } from './actions'; import { GIF_DEFAULT_TIMEOUT } from './constants'; -import { getGifForParticipant } from './functions'; +import { getGifForParticipant } from './function.any'; /** * Middleware which intercepts Gifs actions to handle changes to the @@ -57,5 +57,5 @@ MiddlewareRegistry.register(store => next => action => { function _clearGifTimeout(state: IReduxState, id: string) { const gif = getGifForParticipant(state, id); - clearTimeout(gif?.timeoutID); + clearTimeout(gif?.timeoutID ?? -1); } diff --git a/react/features/gifs/subscriber.native.ts b/react/features/gifs/subscriber.native.ts index 011932fab..aba2ce6e3 100644 --- a/react/features/gifs/subscriber.native.ts +++ b/react/features/gifs/subscriber.native.ts @@ -2,7 +2,7 @@ import { GiphySDK } from '@giphy/react-native-sdk'; import StateListenerRegistry from '../base/redux/StateListenerRegistry'; -import { isGifEnabled } from './functions'; +import { isGifEnabled } from './function.any'; /** * Listens for changes in the number of participants to calculate the dimensions of the tile view grid and the tiles. diff --git a/react/features/participants-pane/components/breakout-rooms/components/web/RoomList.js b/react/features/participants-pane/components/breakout-rooms/components/web/RoomList.js index 99bf16b1a..02516c9a7 100644 --- a/react/features/participants-pane/components/breakout-rooms/components/web/RoomList.js +++ b/react/features/participants-pane/components/breakout-rooms/components/web/RoomList.js @@ -5,7 +5,7 @@ import { useSelector } from 'react-redux'; import { isLocalParticipantModerator } from '../../../../../base/participants'; import { equals } from '../../../../../base/redux'; -import useContextMenu from '../../../../../base/ui/hooks/useContextMenu'; +import useContextMenu from '../../../../../base/ui/hooks/useContextMenu.web'; import { getBreakoutRooms, getBreakoutRoomsConfig, diff --git a/react/features/participants-pane/components/web/MeetingParticipants.tsx b/react/features/participants-pane/components/web/MeetingParticipants.tsx index 2b7c80d4a..7989af32e 100644 --- a/react/features/participants-pane/components/web/MeetingParticipants.tsx +++ b/react/features/participants-pane/components/web/MeetingParticipants.tsx @@ -15,7 +15,7 @@ import { getParticipantById, isScreenShareParticipant } from '../../../base/part import { connect } from '../../../base/redux/functions'; import { withPixelLineHeight } from '../../../base/styles/functions.web'; import Input from '../../../base/ui/components/web/Input'; -import useContextMenu from '../../../base/ui/hooks/useContextMenu'; +import useContextMenu from '../../../base/ui/hooks/useContextMenu.web'; import { normalizeAccents } from '../../../base/util/strings.web'; import { getBreakoutRooms, getCurrentRoomId, isInBreakoutRoom } from '../../../breakout-rooms/functions'; import { showOverflowDrawer } from '../../../toolbox/functions.web'; diff --git a/react/features/prejoin/components/Prejoin.native.tsx b/react/features/prejoin/components/Prejoin.native.tsx index bbe386d6b..1c545f9f0 100644 --- a/react/features/prejoin/components/Prejoin.native.tsx +++ b/react/features/prejoin/components/Prejoin.native.tsx @@ -45,13 +45,13 @@ import AudioMuteButton from '../../toolbox/components/AudioMuteButton'; // @ts-ignore import VideoMuteButton from '../../toolbox/components/VideoMuteButton'; import { isDisplayNameRequired } from '../functions'; -import { PrejoinProps } from '../types'; +import { IPrejoinProps } from '../types'; // @ts-ignore import styles from './styles'; -const Prejoin: React.FC = ({ navigation }: PrejoinProps) => { +const Prejoin: React.FC = ({ navigation }: IPrejoinProps) => { const dispatch = useDispatch(); const isFocused = useIsFocused(); const { t } = useTranslation(); diff --git a/react/features/recording/components/Recording/LocalRecordingManager.native.ts b/react/features/recording/components/Recording/LocalRecordingManager.native.ts index ce3a1a121..8d9cd46f6 100644 --- a/react/features/recording/components/Recording/LocalRecordingManager.native.ts +++ b/react/features/recording/components/Recording/LocalRecordingManager.native.ts @@ -1,7 +1,7 @@ import { IStore } from '../../../app/types'; interface ILocalRecordingManager { - addAudioTrackToLocalRecording: (track: MediaStreamTrack) => void; + addAudioTrackToLocalRecording: (track: any) => void; isRecordingLocally: () => boolean; startLocalRecording: (store: IStore) => void; stopLocalRecording: () => void; @@ -11,7 +11,7 @@ const LocalRecordingManager: ILocalRecordingManager = { /** * Adds audio track to the recording stream. * - * @param {MediaStreamTrack} track - Track to be added,. + * @param {any} track - Track to be added,. * @returns {void} */ addAudioTrackToLocalRecording() { }, // eslint-disable-line @typescript-eslint/no-empty-function diff --git a/react/features/recording/components/Recording/native/StartRecordingDialogContent.tsx b/react/features/recording/components/Recording/native/StartRecordingDialogContent.tsx index 32f78dbbf..e3b75465b 100644 --- a/react/features/recording/components/Recording/native/StartRecordingDialogContent.tsx +++ b/react/features/recording/components/Recording/native/StartRecordingDialogContent.tsx @@ -14,7 +14,7 @@ import { RECORDING_TYPES } from '../../../constants'; // @ts-ignore import { getRecordingDurationEstimation } from '../../../functions'; import AbstractStartRecordingDialogContent, { - Props, + IProps, mapStateToProps } from '../AbstractStartRecordingDialogContent'; import { @@ -29,7 +29,7 @@ import { /** * The start recording dialog content for the mobile application. */ -class StartRecordingDialogContent extends AbstractStartRecordingDialogContent { +class StartRecordingDialogContent extends AbstractStartRecordingDialogContent { /** * Renders the component. * diff --git a/react/features/subtitles/components/LanguageList.tsx b/react/features/subtitles/components/LanguageList.web.tsx similarity index 94% rename from react/features/subtitles/components/LanguageList.tsx rename to react/features/subtitles/components/LanguageList.web.tsx index f32c9f07d..416737534 100644 --- a/react/features/subtitles/components/LanguageList.tsx +++ b/react/features/subtitles/components/LanguageList.web.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { makeStyles } from 'tss-react/mui'; -import LanguageListItem from './LanguageListItem'; +import LanguageListItem from './LanguageListItem.web'; interface ILanguageListProps { items: Array; diff --git a/react/features/subtitles/components/LanguageListItem.tsx b/react/features/subtitles/components/LanguageListItem.web.tsx similarity index 100% rename from react/features/subtitles/components/LanguageListItem.tsx rename to react/features/subtitles/components/LanguageListItem.web.tsx diff --git a/react/features/subtitles/components/LanguageSelectorDialog.web.tsx b/react/features/subtitles/components/LanguageSelectorDialog.web.tsx index c69b3f4c3..c8bd0e3cd 100644 --- a/react/features/subtitles/components/LanguageSelectorDialog.web.tsx +++ b/react/features/subtitles/components/LanguageSelectorDialog.web.tsx @@ -18,7 +18,7 @@ import { SETTINGS_TABS } from '../../settings/constants'; // @ts-ignore import { setRequestingSubtitles, toggleLanguageSelectorDialog, updateTranslationLanguage } from '../actions'; -import LanguageList from './LanguageList'; +import LanguageList from './LanguageList.web'; interface ILanguageSelectorDialogProps extends WithTranslation { diff --git a/react/features/toolbox/components/native/ScreenSharingButton.tsx b/react/features/toolbox/components/native/ScreenSharingButton.tsx index a2b356ca3..d257a6935 100644 --- a/react/features/toolbox/components/native/ScreenSharingButton.tsx +++ b/react/features/toolbox/components/native/ScreenSharingButton.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { Platform } from 'react-native'; +import { IReduxState } from '../../../app/types'; import { connect } from '../../../base/redux/functions'; // @ts-ignore import { isDesktopShareButtonDisabled } from '../../functions.native'; @@ -30,7 +31,7 @@ const ScreenSharingButton = (props: any) => ( * @private * @returns {Object} */ -function _mapStateToProps(state: object): object { +function _mapStateToProps(state: IReduxState) { return { _disabled: isDesktopShareButtonDisabled(state) }; diff --git a/react/features/toolbox/functions.native.ts b/react/features/toolbox/functions.native.ts index 10f9feae0..a20061494 100644 --- a/react/features/toolbox/functions.native.ts +++ b/react/features/toolbox/functions.native.ts @@ -1,10 +1,11 @@ import { IReduxState } from '../app/types'; import { IStateful } from '../base/app/types'; -import { hasAvailableDevices } from '../base/devices'; -import { TOOLBOX_ALWAYS_VISIBLE, TOOLBOX_ENABLED, getFeatureFlag } from '../base/flags'; -import { getParticipantCountWithFake } from '../base/participants'; -import { toState } from '../base/redux'; -import { isLocalVideoTrackDesktop } from '../base/tracks'; +import { hasAvailableDevices } from '../base/devices/functions'; +import { TOOLBOX_ALWAYS_VISIBLE, TOOLBOX_ENABLED } from '../base/flags/constants'; +import { getFeatureFlag } from '../base/flags/functions'; +import { getParticipantCountWithFake } from '../base/participants/functions'; +import { toState } from '../base/redux/functions'; +import { isLocalVideoTrackDesktop } from '../base/tracks/functions'; export * from './functions.any'; diff --git a/react/features/video-quality/components/Slider.tsx b/react/features/video-quality/components/Slider.web.tsx similarity index 100% rename from react/features/video-quality/components/Slider.tsx rename to react/features/video-quality/components/Slider.web.tsx diff --git a/react/features/video-quality/components/VideoQualitySlider.web.tsx b/react/features/video-quality/components/VideoQualitySlider.web.tsx index 705e68010..70297a760 100644 --- a/react/features/video-quality/components/VideoQualitySlider.web.tsx +++ b/react/features/video-quality/components/VideoQualitySlider.web.tsx @@ -19,7 +19,7 @@ import { setPreferredVideoQuality } from '../actions'; import { DEFAULT_LAST_N, VIDEO_QUALITY_LEVELS } from '../constants'; import logger from '../logger'; -import Slider from './Slider'; +import Slider from './Slider.web'; const { ULTRA, diff --git a/tsconfig.native.json b/tsconfig.native.json index 5c26eb29a..d28c182a8 100644 --- a/tsconfig.native.json +++ b/tsconfig.native.json @@ -12,12 +12,13 @@ "noImplicitAny": true, "strictPropertyInitialization": false, "resolveJsonModule": true, - "moduleSuffixes": [".native", ""] + "moduleSuffixes": [".native", ".ios", ".android", ""] }, "exclude": [ "node_modules", "react/features/base/components/participants-pane-list", "react/features/connection-stats", + "react/features/desktop-picker", "react/features/e2ee", "react/features/embed-meeting", "react/features/face-landmarks", @@ -26,6 +27,7 @@ "react/features/stream-effects/noise-suppression", "react/features/stream-effects/rnnoise", "react/features/virtual-background", + "react/features/whiteboard", "**/web/*", "**/*.web.ts", "**/*.web.tsx" diff --git a/tsconfig.web.json b/tsconfig.web.json index 3d2a4ac89..b511eaa49 100644 --- a/tsconfig.web.json +++ b/tsconfig.web.json @@ -19,6 +19,8 @@ "**/mobile/*", "**/native/*", "**/*.native.ts", - "**/*.native.tsx" + "**/*.native.tsx", + "**/*.ios.ts", + "**/*.android.ts" ] }