From 11b878576107adc8eaa4b478b4419bc30fa965ae Mon Sep 17 00:00:00 2001 From: Artem Grintsevich Date: Fri, 6 Nov 2026 15:45:45 +0100 Subject: [PATCH] chore: added ring state debug runtime toggle + adjusted layout --- sample-apps/react-native/dogfood/App.tsx | 5 +- .../src/components/AppSettingsSheet.tsx | 145 ++++++++++++++++++ .../CallControls/MoreActionsButton/index.tsx | 35 ----- .../src/components/NavigationHeader.tsx | 17 +- .../components/Ringing/RingStateDebugPane.tsx | 14 +- .../dogfood/src/constants/TestIds.ts | 1 + .../dogfood/src/contexts/AppContext.tsx | 3 + .../dogfood/src/navigators/Call.tsx | 9 +- 8 files changed, 183 insertions(+), 46 deletions(-) create mode 100644 sample-apps/react-native/dogfood/src/components/AppSettingsSheet.tsx diff --git a/sample-apps/react-native/dogfood/App.tsx b/sample-apps/react-native/dogfood/App.tsx index ad326c81b9..813c562d50 100755 --- a/sample-apps/react-native/dogfood/App.tsx +++ b/sample-apps/react-native/dogfood/App.tsx @@ -34,6 +34,7 @@ import { LiveStream } from './src/navigators/Livestream'; import { StreamTheme, useCalls } from '@stream-io/video-react-native-sdk'; import Toast from 'react-native-toast-message'; import { TestRecording } from './src/navigators/TestRecording'; +import { BottomSheetModalProvider } from '@gorhom/bottom-sheet'; // only enable warning and error logs from webrtc library Logger.enable(`${Logger.ROOT_PREFIX}:(WARN|ERROR)`); @@ -159,7 +160,9 @@ const StackNavigator = () => { - {mode} + + {mode} + diff --git a/sample-apps/react-native/dogfood/src/components/AppSettingsSheet.tsx b/sample-apps/react-native/dogfood/src/components/AppSettingsSheet.tsx new file mode 100644 index 0000000000..be9109a709 --- /dev/null +++ b/sample-apps/react-native/dogfood/src/components/AppSettingsSheet.tsx @@ -0,0 +1,145 @@ +import React, { useCallback, useEffect, useMemo, useRef } from 'react'; +import { StyleSheet, Switch, Text, View } from 'react-native'; +import { + BottomSheetBackdrop, + BottomSheetBackdropProps, + BottomSheetModal, + BottomSheetView, +} from '@gorhom/bottom-sheet'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { useTheme } from '@stream-io/video-react-native-sdk'; +import { + useAppGlobalStoreSetState, + useAppGlobalStoreValue, +} from '../contexts/AppContext'; +import { useAppI18n } from '../hooks/useAppI18n'; + +type AppSettingsSheetProps = { + visible: boolean; + onClose: () => void; +}; + +export const AppSettingsSheet = ({ + visible, + onClose, +}: AppSettingsSheetProps) => { + const styles = useStyles(); + const { t } = useAppI18n(); + const insets = useSafeAreaInsets(); + const sheetRef = useRef(null); + const setState = useAppGlobalStoreSetState(); + const themeMode = useAppGlobalStoreValue((store) => store.themeMode); + const devMode = useAppGlobalStoreValue((store) => store.devMode); + const showRingStateDebugPane = useAppGlobalStoreValue( + (store) => store.showRingStateDebugPane, + ); + + useEffect(() => { + if (visible) { + sheetRef.current?.present(); + } + }, [visible]); + + const renderBackdrop = useCallback( + (props: BottomSheetBackdropProps) => ( + + ), + [], + ); + + return ( + + + + {t('appSettings.title.text', 'Settings')} + + + setState({ themeMode: enabled ? 'dark' : 'light' }) + } + /> + {(__DEV__ || devMode) && ( + + setState({ showRingStateDebugPane: enabled }) + } + /> + )} + + + ); +}; + +const SettingRow = ({ + label, + value, + onValueChange, +}: { + label: string; + value: boolean; + onValueChange: (value: boolean) => void; +}) => { + const styles = useStyles(); + return ( + + {label} + + + ); +}; + +const useStyles = () => { + const { + theme: { semantics, primitives }, + } = useTheme(); + return useMemo( + () => + StyleSheet.create({ + background: { + backgroundColor: semantics.backgroundCoreElevation2, + borderTopLeftRadius: primitives.radiusXl, + borderTopRightRadius: primitives.radiusXl, + }, + handleIndicator: { + backgroundColor: semantics.borderCoreDefault, + }, + content: { + paddingHorizontal: primitives.spacingLg, + }, + title: { + fontSize: primitives.typographyFontSizeLg, + fontWeight: primitives.typographyFontWeightSemiBold, + color: semantics.textPrimary, + marginBottom: primitives.spacingSm, + }, + row: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + paddingVertical: primitives.spacingMd, + }, + rowLabel: { + fontSize: primitives.typographyFontSizeMd, + color: semantics.textPrimary, + }, + }), + [semantics, primitives], + ); +}; diff --git a/sample-apps/react-native/dogfood/src/components/CallControls/MoreActionsButton/index.tsx b/sample-apps/react-native/dogfood/src/components/CallControls/MoreActionsButton/index.tsx index 60f7125626..c6e39570dd 100644 --- a/sample-apps/react-native/dogfood/src/components/CallControls/MoreActionsButton/index.tsx +++ b/sample-apps/react-native/dogfood/src/components/CallControls/MoreActionsButton/index.tsx @@ -25,12 +25,6 @@ import { import { BottomControlsDrawer, DrawerOption } from './BottomControlsDrawer'; import Feedback from '../../../assets/Feedback'; import FeedbackModal from '../../FeedbackModal'; -import { - ThemeMode, - useAppGlobalStoreSetState, - useAppGlobalStoreValue, -} from '../../../contexts/AppContext'; -import LightDark from '../../../assets/LightDark'; import Stats from '../../../assets/Stats'; import ClosedCaptions from '../../../assets/ClosedCaptions'; import Screenshot from '../../../assets/Screenshot'; @@ -77,8 +71,6 @@ export const MoreActionsButton = ({ const [feedbackModalVisible, setFeedbackModalVisible] = useState(false); const [screenshotModalVisible, setScreenshotModalVisible] = useState(false); const [screenshotImage, setScreenshotImage] = useState(null); - const setState = useAppGlobalStoreSetState(); - const themeMode = useAppGlobalStoreValue((store) => store.themeMode); const call = useCall(); const timeoutRef = useRef | null>(null); const { @@ -115,13 +107,6 @@ export const MoreActionsButton = ({ setFeedbackModalVisible(false); }; - const getName = (theme: ThemeMode) => { - if (theme === 'light') { - return 'Dark mode'; - } - return 'Light mode'; - }; - const getCaptionsLabel = () => isCaptioningInProgress ? 'Disable closed captions' @@ -199,26 +184,6 @@ export const MoreActionsButton = ({ setIsDrawerVisible(true); }, }, - { - id: '3', - label: getName(themeMode), - icon: ( - - - - ), - onPress: () => { - if (themeMode === 'light') { - setState({ themeMode: 'dark' }); - } else { - setState({ themeMode: 'light' }); - } - setIsDrawerVisible(false); - }, - }, { id: '4', label: 'Take Screenshot', diff --git a/sample-apps/react-native/dogfood/src/components/NavigationHeader.tsx b/sample-apps/react-native/dogfood/src/components/NavigationHeader.tsx index 484934323b..7f0cd9288c 100755 --- a/sample-apps/react-native/dogfood/src/components/NavigationHeader.tsx +++ b/sample-apps/react-native/dogfood/src/components/NavigationHeader.tsx @@ -6,7 +6,7 @@ import { useTheme, } from '@stream-io/video-react-native-sdk'; import { useAppI18n } from '../hooks/useAppI18n'; -import React, { useMemo } from 'react'; +import React, { useMemo, useState } from 'react'; import { Alert, Pressable, StyleSheet, Text } from 'react-native'; import { useAppGlobalStoreSetState, @@ -18,6 +18,7 @@ import { AVATAR_SIZE } from '../constants'; import { ButtonTestIds } from '../constants/TestIds'; import Close from '../assets/Close'; import { Leave } from '../assets/Leave'; +import { AppSettingsSheet } from './AppSettingsSheet'; export const NavigationHeader = ({ route }: NativeStackHeaderProps) => { const videoClient = useStreamVideoClient(); @@ -27,6 +28,7 @@ export const NavigationHeader = ({ route }: NativeStackHeaderProps) => { const userName = useAppGlobalStoreValue((store) => store.userName); const environment = useAppGlobalStoreValue((store) => store.appEnvironment); const appStoreSetState = useAppGlobalStoreSetState(); + const [settingsVisible, setSettingsVisible] = useState(false); const logoutHandler = () => { Alert.alert( @@ -70,7 +72,14 @@ export const NavigationHeader = ({ route }: NativeStackHeaderProps) => { return ( - {user && } + {user && ( + setSettingsVisible(true)} + > + + + )} {userName} {!showChooseModeButton ? ( @@ -92,6 +101,10 @@ export const NavigationHeader = ({ route }: NativeStackHeaderProps) => { )} + setSettingsVisible(false)} + /> ); }; diff --git a/sample-apps/react-native/dogfood/src/components/Ringing/RingStateDebugPane.tsx b/sample-apps/react-native/dogfood/src/components/Ringing/RingStateDebugPane.tsx index 30fa04b0fb..4d83dfc532 100644 --- a/sample-apps/react-native/dogfood/src/components/Ringing/RingStateDebugPane.tsx +++ b/sample-apps/react-native/dogfood/src/components/Ringing/RingStateDebugPane.tsx @@ -118,9 +118,13 @@ const formatMap = (map?: Record) => { const useStyles = () => { const { - theme: { semantics, primitives }, + theme: { semantics, primitives, callAppBar, callControlsButton }, } = useTheme(); const insets = useSafeAreaInsets(); + const callAppBarHeight = + Number(callAppBar.container.padding) * 2 + + Number(callControlsButton.container.height) + + Number(callControlsButton.container.margin) * 2; return useMemo(() => { return StyleSheet.create({ @@ -128,7 +132,7 @@ const useStyles = () => { position: 'absolute', left: primitives.spacingMd, right: primitives.spacingMd, - top: insets.top + primitives.spacingMd, + top: insets.top + callAppBarHeight + primitives.spacingMd, zIndex: Z_INDEX.IN_FRONT, backgroundColor: semantics.backgroundCoreOverlayLight, borderRadius: 8, @@ -139,7 +143,7 @@ const useStyles = () => { paddingHorizontal: primitives.spacingMd, }, headerText: { - color: semantics.textOnAccent, + color: semantics.textPrimary, fontSize: 14, fontWeight: 'bold', }, @@ -159,7 +163,7 @@ const useStyles = () => { marginRight: primitives.spacingSm, }, rowValue: { - color: semantics.textOnAccent, + color: semantics.textPrimary, fontSize: 12, flexShrink: 1, }, @@ -194,5 +198,5 @@ const useStyles = () => { fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace', }, }); - }, [semantics, primitives, insets]); + }, [semantics, primitives, insets, callAppBarHeight]); }; diff --git a/sample-apps/react-native/dogfood/src/constants/TestIds.ts b/sample-apps/react-native/dogfood/src/constants/TestIds.ts index 1c809a68a3..8339545493 100644 --- a/sample-apps/react-native/dogfood/src/constants/TestIds.ts +++ b/sample-apps/react-native/dogfood/src/constants/TestIds.ts @@ -1,6 +1,7 @@ export enum ButtonTestIds { LOG_OUT = 'log-out', CHOOSE_MODE = 'choose-mode', + APP_SETTINGS = 'app-settings', EXIT_PARTICIPANTS_INFO = 'exit-participants-info', } diff --git a/sample-apps/react-native/dogfood/src/contexts/AppContext.tsx b/sample-apps/react-native/dogfood/src/contexts/AppContext.tsx index 4623cf96a1..bfcf1c3dc9 100644 --- a/sample-apps/react-native/dogfood/src/contexts/AppContext.tsx +++ b/sample-apps/react-native/dogfood/src/contexts/AppContext.tsx @@ -21,6 +21,7 @@ type AppGlobalStore = { e2eeKeyInput: string; coordinatorBaseUrl?: string; disableRingStatePolling?: boolean; + showRingStateDebugPane?: boolean; }; export const { @@ -44,6 +45,7 @@ export const { e2eeKeyInput: '', coordinatorBaseUrl: '', disableRingStatePolling: false, + showRingStateDebugPane: true, }, [ 'apiKey', @@ -58,5 +60,6 @@ export const { 'e2eeKeyInput', 'coordinatorBaseUrl', 'disableRingStatePolling', + 'showRingStateDebugPane', ], ); diff --git a/sample-apps/react-native/dogfood/src/navigators/Call.tsx b/sample-apps/react-native/dogfood/src/navigators/Call.tsx index 7c1f455891..4b22941658 100644 --- a/sample-apps/react-native/dogfood/src/navigators/Call.tsx +++ b/sample-apps/react-native/dogfood/src/navigators/Call.tsx @@ -19,8 +19,6 @@ import { RingStateDebugPane } from '../components/Ringing/RingStateDebugPane'; import { useAppGlobalStoreValue } from '../contexts/AppContext'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; -const ENABLE_RING_STATE_DEBUG = __DEV__; - const CallStack = createNativeStackNavigator(); const Calls = () => { @@ -28,6 +26,9 @@ const Calls = () => { const orientation = useOrientation(); const insets = useSafeAreaInsets(); const devMode = useAppGlobalStoreValue((store) => store.devMode); + const showRingStateDebugPane = useAppGlobalStoreValue( + (store) => store.showRingStateDebugPane, + ); const firstCall = calls.at(-1); @@ -55,7 +56,9 @@ const Calls = () => { CallContent={customCallContent} insets={insets} /> - {(ENABLE_RING_STATE_DEBUG || devMode) && } + {(__DEV__ || devMode) && showRingStateDebugPane && ( + + )} );