diff --git a/.github/workflows/version-and-release.yml b/.github/workflows/version-and-release.yml index 451da6e716..8040c9a49e 100644 --- a/.github/workflows/version-and-release.yml +++ b/.github/workflows/version-and-release.yml @@ -4,11 +4,11 @@ env: STREAM_SECRET: ${{ secrets.CLIENT_TEST_SECRET }} on: - push: - branches: - - main - paths: - - 'packages/**' + # push: + # branches: + # - main + # paths: + # - 'packages/**' workflow_dispatch: diff --git a/packages/client/CHANGELOG.md b/packages/client/CHANGELOG.md index 1e0cfca737..2b611a5538 100644 --- a/packages/client/CHANGELOG.md +++ b/packages/client/CHANGELOG.md @@ -2,6 +2,13 @@ This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver). +## [1.8.3](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.8.2...@stream-io/video-client-1.8.3) (2024-10-10) + + +### Bug Fixes + +* do not release track if track was not removed from stream ([#1517](https://github.com/GetStream/stream-video-js/issues/1517)) ([5bfc528](https://github.com/GetStream/stream-video-js/commit/5bfc52850c36ffe0de37e47066538a8a14dc9e01)) + ## [1.8.2](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.8.1...@stream-io/video-client-1.8.2) (2024-10-10) diff --git a/packages/client/package.json b/packages/client/package.json index a045aa715b..09d3e14105 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@stream-io/video-client", - "version": "1.8.2", + "version": "1.8.3", "packageManager": "yarn@3.2.4", "main": "dist/index.cjs.js", "module": "dist/index.es.js", diff --git a/packages/client/src/devices/InputMediaDeviceManager.ts b/packages/client/src/devices/InputMediaDeviceManager.ts index f5977c6762..0b13138165 100644 --- a/packages/client/src/devices/InputMediaDeviceManager.ts +++ b/packages/client/src/devices/InputMediaDeviceManager.ts @@ -279,14 +279,7 @@ export abstract class InputMediaDeviceManager< private stopTracks() { this.getTracks().forEach((track) => { - if (track.readyState === 'live') { - track.stop(); - // @ts-expect-error release() is present in react-native-webrtc and must be called to dispose the track - if (typeof track.release === 'function') { - // @ts-expect-error - track.release(); - } - } + if (track.readyState === 'live') track.stop(); }); } diff --git a/packages/client/src/devices/devices.ts b/packages/client/src/devices/devices.ts index f0eacf008e..684da44b14 100644 --- a/packages/client/src/devices/devices.ts +++ b/packages/client/src/devices/devices.ts @@ -276,12 +276,6 @@ export const disposeOfMediaStream = (stream: MediaStream) => { if (!stream.active) return; stream.getTracks().forEach((track) => { track.stop(); - stream.removeTrack(track); - // @ts-expect-error release() is present in react-native-webrtc and must be called to dispose the track - if (typeof track.release === 'function') { - // @ts-expect-error - track.release(); - } }); // @ts-expect-error release() is present in react-native-webrtc and must be called to dispose the stream if (typeof stream.release === 'function') { diff --git a/packages/client/src/helpers/RNSpeechDetector.ts b/packages/client/src/helpers/RNSpeechDetector.ts index 97dba7aaa3..828ed1d155 100644 --- a/packages/client/src/helpers/RNSpeechDetector.ts +++ b/packages/client/src/helpers/RNSpeechDetector.ts @@ -106,14 +106,7 @@ export class RNSpeechDetector { if (!this.audioStream) { return; } - this.audioStream.getTracks().forEach((track) => { - track.stop(); - // @ts-expect-error release() is present in react-native-webrtc and must be called to dispose the track - if (typeof track.release === 'function') { - // @ts-expect-error - track.release(); - } - }); + this.audioStream.getTracks().forEach((track) => track.stop()); if ( // @ts-expect-error release() is present in react-native-webrtc typeof this.audioStream.release === 'function' diff --git a/packages/client/src/rtc/Publisher.ts b/packages/client/src/rtc/Publisher.ts index af66f92c7c..8ef17b23a8 100644 --- a/packages/client/src/rtc/Publisher.ts +++ b/packages/client/src/rtc/Publisher.ts @@ -316,11 +316,6 @@ export class Publisher { if (previousTrack && previousTrack !== track) { previousTrack.stop(); previousTrack.removeEventListener('ended', handleTrackEnded); - // @ts-expect-error release() is present in react-native-webrtc and must be called to dispose the track - if (typeof previousTrack.release === 'function') { - // @ts-expect-error - track.release(); - } track.addEventListener('ended', handleTrackEnded); } if (!track.enabled) { @@ -342,18 +337,16 @@ export class Publisher { const transceiver = this.pc .getTransceivers() .find((t) => t === this.transceiverRegistry[trackType] && t.sender.track); - const track = transceiver?.sender.track; - if (track && (stopTrack ? track.readyState === 'live' : track.enabled)) { - if (stopTrack) { - track.stop(); - // @ts-expect-error release() is present in react-native-webrtc and must be called to dispose the track - if (typeof track.release === 'function') { - // @ts-expect-error - track.release(); - } - } else { - track.enabled = false; - } + if ( + transceiver && + transceiver.sender.track && + (stopTrack + ? transceiver.sender.track.readyState === 'live' + : transceiver.sender.track.enabled) + ) { + stopTrack + ? transceiver.sender.track.stop() + : (transceiver.sender.track.enabled = false); // We don't need to notify SFU if unpublishing in response to remote soft mute if (this.state.localParticipant?.publishedTracks.includes(trackType)) { await this.notifyTrackMuteStateChanged(undefined, trackType, true); @@ -406,13 +399,7 @@ export class Publisher { private stopPublishing = () => { this.logger('debug', 'Stopping publishing all tracks'); this.pc.getSenders().forEach((s) => { - const track = s.track; - track?.stop(); - // @ts-expect-error release() is present in react-native-webrtc and must be called to dispose the track - if (typeof track?.release === 'function') { - // @ts-expect-error - track.release(); - } + s.track?.stop(); if (this.pc.signalingState !== 'closed') { this.pc.removeTrack(s); } diff --git a/packages/react-bindings/CHANGELOG.md b/packages/react-bindings/CHANGELOG.md index 6b3a64ddb5..86ff8e62ac 100644 --- a/packages/react-bindings/CHANGELOG.md +++ b/packages/react-bindings/CHANGELOG.md @@ -2,6 +2,11 @@ This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver). +## [1.1.3](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-bindings-1.1.2...@stream-io/video-react-bindings-1.1.3) (2024-10-10) + +### Dependency Updates + +* `@stream-io/video-client` updated to version `1.8.3` ## [1.1.2](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-bindings-1.1.1...@stream-io/video-react-bindings-1.1.2) (2024-10-10) ### Dependency Updates diff --git a/packages/react-bindings/package.json b/packages/react-bindings/package.json index 224b5ea1e1..622bf76c44 100644 --- a/packages/react-bindings/package.json +++ b/packages/react-bindings/package.json @@ -1,6 +1,6 @@ { "name": "@stream-io/video-react-bindings", - "version": "1.1.2", + "version": "1.1.3", "packageManager": "yarn@3.2.4", "main": "./dist/index.cjs.js", "module": "./dist/index.es.js", diff --git a/packages/react-native-sdk/CHANGELOG.md b/packages/react-native-sdk/CHANGELOG.md index 677380093b..960a0e358a 100644 --- a/packages/react-native-sdk/CHANGELOG.md +++ b/packages/react-native-sdk/CHANGELOG.md @@ -2,6 +2,19 @@ This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver). +## [1.1.5](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-native-sdk-1.1.4...@stream-io/video-react-native-sdk-1.1.5) (2024-10-16) + + +### Bug Fixes + +* **react-native:** set objectFit based on actual video track dimensions ([#1520](https://github.com/GetStream/stream-video-js/issues/1520)) ([44ef7d2](https://github.com/GetStream/stream-video-js/commit/44ef7d2e69a910be45b2d3a7643c3f58e0f29803)) + +## [1.1.4](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-native-sdk-1.1.3...@stream-io/video-react-native-sdk-1.1.4) (2024-10-10) + +### Dependency Updates + +* `@stream-io/video-client` updated to version `1.8.3` +* `@stream-io/video-react-bindings` updated to version `1.1.3` ## [1.1.3](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-native-sdk-1.1.2...@stream-io/video-react-native-sdk-1.1.3) (2024-10-10) ### Dependency Updates diff --git a/packages/react-native-sdk/__tests__/components/CallControls.test.tsx b/packages/react-native-sdk/__tests__/components/CallControls.test.tsx index 49188e04b4..2b611fc123 100644 --- a/packages/react-native-sdk/__tests__/components/CallControls.test.tsx +++ b/packages/react-native-sdk/__tests__/components/CallControls.test.tsx @@ -3,11 +3,10 @@ import { mockClientWithUser } from '../mocks/client'; import mockParticipant from '../mocks/participant'; import { ButtonTestIds, ComponentTestIds } from '../../src/constants/TestIds'; import { mockCall } from '../mocks/call'; -import { fireEvent, render, screen, waitFor } from '../utils/RNTLTools'; +import { fireEvent, render, screen } from '../utils/RNTLTools'; import { OwnCapability } from '@stream-io/video-client'; import { defaultEmojiReactions } from '../../src/constants'; -import { CallControls } from '../../src/components/Call/CallControls/CallControls'; -import { ChatButton } from '../../src/components/Call/CallControls/ChatButton'; +import { CallControls } from '../../src'; import { HangUpCallButton } from '../../src/components/Call/CallControls/HangupCallButton'; import { ReactionsButton } from '../../src/components/Call/CallControls/ReactionsButton'; @@ -18,48 +17,6 @@ enum P_IDS { LOCAL_1 = 'local-1', } -describe('ChatButton', () => { - it('should render an unread badge indicator when the value is defined in the chatButton prop', async () => { - const call = mockCall(mockClientWithUser(), [ - mockParticipant({ - isLocalParticipant: true, - sessionId: P_IDS.LOCAL_1, - userId: P_IDS.LOCAL_1, - }), - ]); - - render(, { - call, - }); - - const indicator = await screen.findByText('1'); - - expect(indicator).toBeVisible(); - }); - - it('should not render an unread badge indicator when the value is 0 in the chatButton prop', async () => { - const call = mockCall(mockClientWithUser(), [ - mockParticipant({ - isLocalParticipant: true, - sessionId: P_IDS.LOCAL_1, - userId: P_IDS.LOCAL_1, - }), - ]); - - render(, { - call, - }); - - await waitFor(() => - expect(() => - screen.getByTestId(ComponentTestIds.CHAT_UNREAD_BADGE_COUNT_INDICATOR) - ).toThrow( - /Unable to find an element with testID: chat-unread-badge-count-indicator/i - ) - ); - }); -}); - describe('ReactionsButton', () => { it('render reaction button in call controls component', async () => { const call = mockCall( diff --git a/packages/react-native-sdk/__tests__/components/ParticipantView.test.tsx b/packages/react-native-sdk/__tests__/components/ParticipantView.test.tsx index 15077486ba..c604478f83 100644 --- a/packages/react-native-sdk/__tests__/components/ParticipantView.test.tsx +++ b/packages/react-native-sdk/__tests__/components/ParticipantView.test.tsx @@ -43,7 +43,6 @@ describe('ParticipantView', () => { expect( await screen.findByTestId(ComponentTestIds.PARTICIPANT_AVATAR) ).toBeOnTheScreen(); - expect(screen.getByTestId(IconTestIds.MUTED_VIDEO)).toBeOnTheScreen(); expect(screen.getByText(testParticipant.name)).toBeOnTheScreen(); // reaction is visible and then disappears after 5500 ms expect(screen.getByText('🎉')).toBeOnTheScreen(); diff --git a/packages/react-native-sdk/docusaurus/docs/reactnative/03-core/01-client-auth.mdx b/packages/react-native-sdk/docusaurus/docs/reactnative/03-core/01-client-auth.mdx index 5c9415602e..641ce941c7 100644 --- a/packages/react-native-sdk/docusaurus/docs/reactnative/03-core/01-client-auth.mdx +++ b/packages/react-native-sdk/docusaurus/docs/reactnative/03-core/01-client-auth.mdx @@ -71,6 +71,25 @@ const apiKey = 'my-stream-api-key'; const client = StreamVideoClient.getOrCreateInstance({ apiKey, user }); ``` +Anonymous users don't establish an active web socket connection, therefore they won't receive any events. They are just able to watch a livestream or join a call. + +The token for an anonymous user should contain the `call_cids` field, which is an array of the call `cid`'s that the user is allowed to join. + +Here's an example JWT token payload for an anonymous user: + +```ts +{ + "iss": "@stream-io/dashboard", + "iat": 1726406693, + "exp": 1726493093, + "user_id": "!anon", + "role": "viewer", + "call_cids": [ + "livestream:123" + ] +} +``` + ## Client options ### `token` or `tokenProvider` diff --git a/packages/react-native-sdk/docusaurus/docs/reactnative/04-ui-components/call/call-content.mdx b/packages/react-native-sdk/docusaurus/docs/reactnative/04-ui-components/call/call-content.mdx index bf561290fd..63086107a8 100644 --- a/packages/react-native-sdk/docusaurus/docs/reactnative/04-ui-components/call/call-content.mdx +++ b/packages/react-native-sdk/docusaurus/docs/reactnative/04-ui-components/call/call-content.mdx @@ -10,7 +10,6 @@ import CallContentSpotlight from '../../assets/04-ui-components/call/call-conten import CallTopView from '../../common-content/ui-components/call/call-content/call-top-view.mdx'; import CallControls from '../../common-content/ui-components/call/call-content/call-controls.mdx'; -import ParticipantsInfoBadge from '../../common-content/ui-components/call/call-content/participants-info-badge.mdx'; import Landscape from '../../common-content/ui-components/call/call-content/landscape.mdx'; import OnBackPressed from '../../common-content/ui-components/call/call-content/on-back-pressed.mdx'; import OnParticipantInfoPress from '../../common-content/ui-components/call/call-content/on-participant-info-press.mdx'; diff --git a/packages/react-native-sdk/docusaurus/docs/reactnative/04-ui-components/call/call-top-view.mdx b/packages/react-native-sdk/docusaurus/docs/reactnative/04-ui-components/call/call-top-view.mdx index 6f82a92401..27f7aad3ce 100644 --- a/packages/react-native-sdk/docusaurus/docs/reactnative/04-ui-components/call/call-top-view.mdx +++ b/packages/react-native-sdk/docusaurus/docs/reactnative/04-ui-components/call/call-top-view.mdx @@ -60,10 +60,6 @@ Style to override the container of the `CallTopView`. | ---------------------------------------------------------- | | [ViewStyle](https://reactnative.dev/docs/view-style-props) | -### `ParticipantsInfoBadge` - - - ## Customization You can create your own custom `CallTopView` using the [Call Top View UI Cookbook guide](../../../ui-cookbook/replacing-call-top-view/). diff --git a/packages/react-native-sdk/docusaurus/docs/reactnative/common-content/ui-components/call/call-content/participants-info-badge.mdx b/packages/react-native-sdk/docusaurus/docs/reactnative/common-content/ui-components/call/call-content/participants-info-badge.mdx deleted file mode 100644 index 92a7cb3c9d..0000000000 --- a/packages/react-native-sdk/docusaurus/docs/reactnative/common-content/ui-components/call/call-content/participants-info-badge.mdx +++ /dev/null @@ -1,5 +0,0 @@ -Component to customize the ParticipantInfoBadge of the CallTopView. - -| Type | Default Value | -| ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ComponentType`\| `undefined` | [`ParticipantsInfoBadge`](https://github.com/GetStream/stream-video-js/blob/main/packages/react-native-sdk/src/components/Call/CallTopView/ParticipantsInfoBadge.tsx) | diff --git a/packages/react-native-sdk/package.json b/packages/react-native-sdk/package.json index fa7478dc21..283f6252b9 100644 --- a/packages/react-native-sdk/package.json +++ b/packages/react-native-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@stream-io/video-react-native-sdk", - "version": "1.1.3", + "version": "1.1.5", "packageManager": "yarn@3.2.4", "main": "dist/commonjs/index.js", "module": "dist/module/index.js", diff --git a/packages/react-native-sdk/src/components/Call/CallContent/CallContent.tsx b/packages/react-native-sdk/src/components/Call/CallContent/CallContent.tsx index b40120eeaf..ffb44c1609 100644 --- a/packages/react-native-sdk/src/components/Call/CallContent/CallContent.tsx +++ b/packages/react-native-sdk/src/components/Call/CallContent/CallContent.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState } from 'react'; -import { StyleSheet, View, ViewStyle } from 'react-native'; +import { StyleSheet, View } from 'react-native'; import InCallManager from 'react-native-incall-manager'; import { @@ -194,10 +194,6 @@ export const CallContent = ({ supportedReactions, }; - const landscapeStyles: ViewStyle = { - flexDirection: landscape ? 'row' : 'column', - }; - return ( <> {!disablePictureInPicture && ( @@ -205,7 +201,7 @@ export const CallContent = ({ includeLocalParticipantVideo={iOSPiPIncludeLocalParticipantVideo} /> )} - + {!isInPiPMode && CallTopView && ( @@ -249,6 +245,7 @@ export const CallContent = ({ const styles = StyleSheet.create({ container: { flex: 1 }, + content: { flex: 1 }, view: { // backgroundColor: 'red', ...StyleSheet.absoluteFillObject, diff --git a/packages/react-native-sdk/src/components/Call/CallControls/CallControlsButton.tsx b/packages/react-native-sdk/src/components/Call/CallControls/CallControlsButton.tsx index 37c5125808..d7d9b814da 100644 --- a/packages/react-native-sdk/src/components/Call/CallControls/CallControlsButton.tsx +++ b/packages/react-native-sdk/src/components/Call/CallControls/CallControlsButton.tsx @@ -57,8 +57,9 @@ export const CallControlsButton = ( const { theme: { - variants: { buttonSizes }, colors, + defaults, + variants: { roundButtonSizes }, callControlsButton: { container }, }, } = useTheme(); @@ -66,17 +67,19 @@ export const CallControlsButton = ( const pressableStyle: PressableProps['style'] = ({ pressed }) => [ styles.container, { - backgroundColor: disabled ? colors.disabled : colorProp || colors.base1, + backgroundColor: disabled + ? colors.buttonPrimaryDisabled + : colorProp || colors.buttonSecondaryDefault, opacity: pressed ? 0.2 : 1, - height: size || buttonSizes.sm, - width: size || buttonSizes.sm, - borderRadius: (size || buttonSizes.sm) / 2, - borderColor: colors.background1, + height: size || roundButtonSizes.lg, + width: size || roundButtonSizes.lg, + borderRadius: defaults.borderRadius, }, styleProp?.container ?? null, container, ]; + const childrenSize = (size || roundButtonSizes.lg) / 2 - 5; return ( diff --git a/packages/react-native-sdk/src/components/Call/CallControls/ChatButton.tsx b/packages/react-native-sdk/src/components/Call/CallControls/ChatButton.tsx deleted file mode 100644 index 66a0bda86c..0000000000 --- a/packages/react-native-sdk/src/components/Call/CallControls/ChatButton.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import React from 'react'; -import { StyleSheet, Text, View } from 'react-native'; -import { CallControlsButton } from './CallControlsButton'; -import { Chat } from '../../../icons'; -import { ComponentTestIds } from '../../../constants/TestIds'; -import { Z_INDEX } from '../../../constants'; -import { useTheme } from '../../../contexts/ThemeContext'; - -/** - * The props for the Chat Button in the Call Controls. - */ -export type ChatButtonProps = { - /** - * Handler to be called when the chat button is pressed. - * @returns void - */ - onPressHandler?: () => void; - /** - * The count of the current unread message to be displayed above on the Chat button. - */ - unreadBadgeCount?: number; -}; - -/** - * Button to open the Chat window while in the call. - * - * This call also display the unread count indicator/badge is there messages that are unread. - */ -export const ChatButton = ({ - onPressHandler, - unreadBadgeCount, -}: ChatButtonProps) => { - const { - theme: { colors, chatButton }, - } = useTheme(); - return ( - - - - - ); -}; - -const UnreadBadgeCountIndicator = ({ - count, -}: { - count: ChatButtonProps['unreadBadgeCount']; -}) => { - const { - theme: { colors, typefaces }, - } = useTheme(); - - // Don't show badge if count is 0 or undefined - if (!count) { - return null; - } - - return ( - - - {count} - - - ); -}; - -const styles = StyleSheet.create({ - chatBadge: { - borderRadius: 30, - position: 'absolute', - left: 15, - bottom: 20, - zIndex: Z_INDEX.IN_FRONT, - height: 24, - width: 24, - justifyContent: 'center', - }, - chatBadgeText: { - textAlign: 'center', - }, -}); diff --git a/packages/react-native-sdk/src/components/Call/CallControls/ToggleAudioPreviewButton.tsx b/packages/react-native-sdk/src/components/Call/CallControls/ToggleAudioPreviewButton.tsx index 3a30741189..faeefc5478 100644 --- a/packages/react-native-sdk/src/components/Call/CallControls/ToggleAudioPreviewButton.tsx +++ b/packages/react-native-sdk/src/components/Call/CallControls/ToggleAudioPreviewButton.tsx @@ -1,7 +1,7 @@ import { useCallStateHooks } from '@stream-io/video-react-bindings'; import React from 'react'; import { useTheme } from '../../../contexts'; -import { Mic, MicOff } from '../../../icons'; +import { IconWrapper, Mic, MicOff } from '../../../icons'; import { CallControlsButton } from './CallControlsButton'; /** @@ -26,6 +26,7 @@ export const ToggleAudioPreviewButton = ({ colors, toggleAudioPreviewButton, variants: { buttonSizes }, + defaults, }, } = useTheme(); const { useMicrophoneState } = useCallStateHooks(); @@ -42,21 +43,24 @@ export const ToggleAudioPreviewButton = ({ return ( - {!optimisticIsMute ? ( - - ) : ( - - )} + + {!optimisticIsMute ? ( + + ) : ( + + )} + ); }; diff --git a/packages/react-native-sdk/src/components/Call/CallControls/ToggleAudioPublishingButton.tsx b/packages/react-native-sdk/src/components/Call/CallControls/ToggleAudioPublishingButton.tsx index c0aed84ee3..1e8e8b45fc 100644 --- a/packages/react-native-sdk/src/components/Call/CallControls/ToggleAudioPublishingButton.tsx +++ b/packages/react-native-sdk/src/components/Call/CallControls/ToggleAudioPublishingButton.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { OwnCapability } from '@stream-io/video-client'; import { Restricted, useCallStateHooks } from '@stream-io/video-react-bindings'; import { CallControlsButton } from './CallControlsButton'; -import { Mic, MicOff } from '../../../icons'; +import { IconWrapper, Mic, MicOff } from '../../../icons'; import { useTheme } from '../../../contexts/ThemeContext'; /** @@ -26,7 +26,7 @@ export const ToggleAudioPublishingButton = ({ const { optimisticIsMute, microphone } = useMicrophoneState(); const { - theme: { colors, toggleAudioPublishingButton }, + theme: { colors, toggleAudioPublishingButton, defaults }, } = useTheme(); const onPress = async () => { if (onPressHandler) { @@ -41,14 +41,23 @@ export const ToggleAudioPublishingButton = ({ - {!optimisticIsMute ? ( - - ) : ( - - )} + + {!optimisticIsMute ? ( + + ) : ( + + )} + ); diff --git a/packages/react-native-sdk/src/components/Call/CallControls/ToggleVideoPreviewButton.tsx b/packages/react-native-sdk/src/components/Call/CallControls/ToggleVideoPreviewButton.tsx index f3da81b65e..6b772dd8d4 100644 --- a/packages/react-native-sdk/src/components/Call/CallControls/ToggleVideoPreviewButton.tsx +++ b/packages/react-native-sdk/src/components/Call/CallControls/ToggleVideoPreviewButton.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { useCallStateHooks } from '@stream-io/video-react-bindings'; import { useTheme } from '../../../contexts'; import { CallControlsButton } from './CallControlsButton'; -import { Video, VideoSlash } from '../../../icons'; +import { IconWrapper, Video, VideoSlash } from '../../../icons'; /** * Props for the Toggle Video preview button @@ -26,6 +26,7 @@ export const ToggleVideoPreviewButton = ({ colors, toggleVideoPreviewButton, variants: { buttonSizes }, + defaults, }, } = useTheme(); const { useCameraState, useCallSettings } = useCallStateHooks(); @@ -47,21 +48,27 @@ export const ToggleVideoPreviewButton = ({ return ( - {!optimisticIsMute ? ( - ); }; diff --git a/packages/react-native-sdk/src/components/Call/CallControls/ToggleVideoPublishingButton.tsx b/packages/react-native-sdk/src/components/Call/CallControls/ToggleVideoPublishingButton.tsx index a2441a0ce3..ecab938bbb 100644 --- a/packages/react-native-sdk/src/components/Call/CallControls/ToggleVideoPublishingButton.tsx +++ b/packages/react-native-sdk/src/components/Call/CallControls/ToggleVideoPublishingButton.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { OwnCapability } from '@stream-io/video-client'; import { Restricted, useCallStateHooks } from '@stream-io/video-react-bindings'; import { CallControlsButton } from './CallControlsButton'; -import { Video, VideoSlash } from '../../../icons'; +import { IconWrapper, Video, VideoSlash } from '../../../icons'; import { useTheme } from '../../../contexts/ThemeContext'; /** @@ -27,7 +27,7 @@ export const ToggleVideoPublishingButton = ({ const callSettings = useCallSettings(); const isVideoEnabledInCall = callSettings?.video.enabled; const { - theme: { colors }, + theme: { colors, defaults }, } = useTheme(); const onPress = async () => { if (onPressHandler) { @@ -45,13 +45,22 @@ export const ToggleVideoPublishingButton = ({ - {!optimisticIsMute ? ( - ); diff --git a/packages/react-native-sdk/src/components/Call/CallControls/index.tsx b/packages/react-native-sdk/src/components/Call/CallControls/index.tsx index d8f1d9ca0a..f921e3da75 100644 --- a/packages/react-native-sdk/src/components/Call/CallControls/index.tsx +++ b/packages/react-native-sdk/src/components/Call/CallControls/index.tsx @@ -6,7 +6,6 @@ export * from './ToggleVideoPreviewButton'; export * from './ToggleAudioPublishingButton'; export * from './ToggleVideoPublishingButton'; export * from './ToggleCameraFaceButton'; -export * from './ChatButton'; export * from './ReactionsButton'; export * from './CallControls'; export * from './CallControlsButton'; diff --git a/packages/react-native-sdk/src/components/Call/CallTopView/CallTopView.tsx b/packages/react-native-sdk/src/components/Call/CallTopView/CallTopView.tsx index 67f98b6a5f..c5d7196bff 100644 --- a/packages/react-native-sdk/src/components/Call/CallTopView/CallTopView.tsx +++ b/packages/react-native-sdk/src/components/Call/CallTopView/CallTopView.tsx @@ -28,10 +28,6 @@ export type CallTopViewProps = { * Style to override the container of the CallTopView. */ style?: StyleProp; - /** - * Component to customize the ParticipantInfoBadge of the CallTopView. - */ - ParticipantsInfoBadge?: React.ComponentType | null; }; export const CallTopView = ({ diff --git a/packages/react-native-sdk/src/components/Call/CallTopView/ParticipantsInfoBadge.tsx b/packages/react-native-sdk/src/components/Call/CallTopView/ParticipantsInfoBadge.tsx deleted file mode 100644 index 15d24f6ea2..0000000000 --- a/packages/react-native-sdk/src/components/Call/CallTopView/ParticipantsInfoBadge.tsx +++ /dev/null @@ -1,119 +0,0 @@ -import React from 'react'; -import { Pressable, StyleSheet, Text, View } from 'react-native'; -import { Participants } from '../../../icons'; -import { useCallStateHooks } from '@stream-io/video-react-bindings'; -import { Z_INDEX } from '../../../constants'; -import { CallTopViewProps } from '..'; -import { ButtonTestIds } from '../../../constants/TestIds'; -import { useTheme } from '../../../contexts/ThemeContext'; -import { CallingState } from '@stream-io/video-client'; - -/** - * Props for the ParticipantsInfoBadge component. - */ -export type ParticipantsInfoBadgeProps = Pick< - CallTopViewProps, - 'onParticipantInfoPress' ->; - -/** - * Badge that shows the number of participants in the call. - * When pressed, it opens the ParticipantsInfoList. - */ -export const ParticipantsInfoBadge = ({ - onParticipantInfoPress, -}: ParticipantsInfoBadgeProps) => { - const { - theme: { - colors, - participantInfoBadge, - typefaces, - variants: { iconSizes }, - }, - } = useTheme(); - const { useParticipantCount, useCallMembers, useCallCallingState } = - useCallStateHooks(); - const participantCount = useParticipantCount(); - const members = useCallMembers(); - const callingState = useCallCallingState(); - - let count = 0; - /** - * We show member's length if Incoming and Outgoing Call Views are rendered. - * Else we show the count of the participants that are in the call. - * Since the members count also includes caller/callee, we reduce the count by 1. - **/ - if (callingState === CallingState.RINGING) { - count = members.length - 1; - } else { - count = participantCount; - } - - if (count === 0) { - return null; - } - - return ( - [ - styles.container, - { opacity: pressed ? 0.2 : 1 }, - participantInfoBadge.container, - ]} - disabled={!onParticipantInfoPress} - testID={ButtonTestIds.PARTICIPANTS_INFO} - > - - - - - - {count} - - - - ); -}; - -const styles = StyleSheet.create({ - container: { - flexDirection: 'row', - }, - participantCountContainer: { - justifyContent: 'center', - paddingHorizontal: 8, - borderRadius: 30, - zIndex: Z_INDEX.IN_FRONT, - bottom: 12, - right: 12, - }, - participantCountText: { - includeFontPadding: false, - textAlign: 'center', - }, -}); diff --git a/packages/react-native-sdk/src/components/Call/CallTopView/index.ts b/packages/react-native-sdk/src/components/Call/CallTopView/index.ts index 7734edc003..bb5d521f5b 100644 --- a/packages/react-native-sdk/src/components/Call/CallTopView/index.ts +++ b/packages/react-native-sdk/src/components/Call/CallTopView/index.ts @@ -1,2 +1 @@ export * from './CallTopView'; -export * from './ParticipantsInfoBadge'; diff --git a/packages/react-native-sdk/src/components/Call/Lobby/Lobby.tsx b/packages/react-native-sdk/src/components/Call/Lobby/Lobby.tsx index 4ebbbbba8e..23db831fba 100644 --- a/packages/react-native-sdk/src/components/Call/Lobby/Lobby.tsx +++ b/packages/react-native-sdk/src/components/Call/Lobby/Lobby.tsx @@ -1,6 +1,5 @@ import React, { ComponentType } from 'react'; import { StyleSheet, Text, View, ViewStyle } from 'react-native'; -import { MicOff } from '../../../icons'; import { useCallStateHooks, useConnectedUser, @@ -158,17 +157,10 @@ export const Lobby = ({ const ParticipantStatus = () => { const { - theme: { - colors, - typefaces, - lobby, - variants: { iconSizes }, - }, + theme: { colors, typefaces, lobby }, } = useTheme(); const connectedUser = useConnectedUser(); - const { useMicrophoneState } = useCallStateHooks(); const participantLabel = connectedUser?.name ?? connectedUser?.id; - const { status: micStatus } = useMicrophoneState(); return ( { > {participantLabel} - {(!micStatus || micStatus === 'disabled') && ( - - - - )} ); }; diff --git a/packages/react-native-sdk/src/components/Livestream/LivestreamControls/LivestreamAudioControlButton.tsx b/packages/react-native-sdk/src/components/Livestream/LivestreamControls/LivestreamAudioControlButton.tsx index 2fb2defaed..005d542298 100644 --- a/packages/react-native-sdk/src/components/Livestream/LivestreamControls/LivestreamAudioControlButton.tsx +++ b/packages/react-native-sdk/src/components/Livestream/LivestreamControls/LivestreamAudioControlButton.tsx @@ -15,6 +15,7 @@ export const LivestreamAudioControlButton = () => { colors, variants: { iconSizes, buttonSizes }, livestreamAudioControlButton, + defaults, }, } = useTheme(); @@ -46,9 +47,9 @@ export const LivestreamAudioControlButton = () => { ]} > {!optimisticIsMute ? ( - + ) : ( - + )} diff --git a/packages/react-native-sdk/src/components/Livestream/LivestreamControls/LivestreamVideoControlButton.tsx b/packages/react-native-sdk/src/components/Livestream/LivestreamControls/LivestreamVideoControlButton.tsx index 1f47b71be5..e73f6c7a84 100644 --- a/packages/react-native-sdk/src/components/Livestream/LivestreamControls/LivestreamVideoControlButton.tsx +++ b/packages/react-native-sdk/src/components/Livestream/LivestreamControls/LivestreamVideoControlButton.tsx @@ -17,6 +17,7 @@ export const LivestreamVideoControlButton = () => { colors, variants: { iconSizes, buttonSizes }, livestreamVideoControlButton, + defaults, }, } = useTheme(); @@ -52,9 +53,9 @@ export const LivestreamVideoControlButton = () => { ]} > {!optimisticIsMute ? ( - diff --git a/packages/react-native-sdk/src/components/Participant/FloatingParticipantView/index.tsx b/packages/react-native-sdk/src/components/Participant/FloatingParticipantView/index.tsx index d2e34f9a2c..b2ae9cf498 100644 --- a/packages/react-native-sdk/src/components/Participant/FloatingParticipantView/index.tsx +++ b/packages/react-native-sdk/src/components/Participant/FloatingParticipantView/index.tsx @@ -66,6 +66,7 @@ const CustomLocalParticipantViewVideoFallback = () => { colors, floatingParticipantsView, variants: { iconSizes }, + defaults, }, } = useTheme(); @@ -78,7 +79,7 @@ const CustomLocalParticipantViewVideoFallback = () => { ]} > - + ); diff --git a/packages/react-native-sdk/src/components/Participant/ParticipantView/ParticipantLabel.tsx b/packages/react-native-sdk/src/components/Participant/ParticipantView/ParticipantLabel.tsx index c1127a022c..de58feced2 100644 --- a/packages/react-native-sdk/src/components/Participant/ParticipantView/ParticipantLabel.tsx +++ b/packages/react-native-sdk/src/components/Participant/ParticipantView/ParticipantLabel.tsx @@ -1,16 +1,10 @@ import React from 'react'; import { Pressable, StyleSheet, Text, View } from 'react-native'; -import { - MicOff, - PinVertical, - ScreenShareIndicator, - VideoSlash, -} from '../../../icons'; +import { PinVertical, ScreenShareIndicator } from '../../../icons'; import { useCall, useI18n } from '@stream-io/video-react-bindings'; import { ComponentTestIds } from '../../../constants/TestIds'; import { ParticipantViewProps } from './ParticipantView'; import { Z_INDEX } from '../../../constants'; -import { hasAudio, hasVideo } from '@stream-io/video-client'; import { useTheme } from '../../../contexts/ThemeContext'; /** @@ -36,8 +30,6 @@ export const ParticipantLabel = ({ participantLabel: { container, userNameLabel, - audioMutedIconContainer, - videoMutedIconContainer, pinIconContainer, screenShareIconContainer, }, @@ -50,8 +42,6 @@ export const ParticipantLabel = ({ const participantLabel = isLocalParticipant ? t('You') : participantName; const isPinningEnabled = pin?.isLocalPin; - const isAudioMuted = !hasAudio(participant); - const isVideoMuted = !hasVideo(participant); const unPinParticipantHandler = () => { call?.unpin(sessionId); @@ -118,34 +108,6 @@ export const ParticipantLabel = ({ > {participantLabel} - {isAudioMuted && ( - - - - )} - {isVideoMuted && ( - - - - )} {isPinningEnabled && ( (); const subscribedVideoLayoutRef = useRef(); const { direction } = useCameraState(); + const videoDimensions = useTrackDimensions(participant, trackType); const { isLocalParticipant, sessionId, @@ -260,7 +262,12 @@ export const VideoRenderer = ({ style={[styles.videoStream, videoRenderer.videoStream]} streamURL={videoStreamToRender.toURL()} mirror={mirror} - objectFit={objectFit ?? (isScreenSharing ? 'contain' : 'cover')} + objectFit={ + objectFit ?? + (videoDimensions.width > videoDimensions.height + ? 'contain' + : 'cover') + } zOrder={videoZOrder} /> ) : ( diff --git a/packages/react-native-sdk/src/constants/TestIds.ts b/packages/react-native-sdk/src/constants/TestIds.ts index 90ebe99448..aadf5b2682 100644 --- a/packages/react-native-sdk/src/constants/TestIds.ts +++ b/packages/react-native-sdk/src/constants/TestIds.ts @@ -16,6 +16,7 @@ export enum ComponentTestIds { PARTICIPANT_SCREEN_SHARING = 'participant-screen-sharing', REACTIONS_PICKER = 'reactions-picker', CHAT_UNREAD_BADGE_COUNT_INDICATOR = 'chat-unread-badge-count-indicator', + BADGE_COUNT_INDICATOR = 'badge-count-indicator', } export enum ButtonTestIds { diff --git a/packages/react-native-sdk/src/hooks/index.ts b/packages/react-native-sdk/src/hooks/index.ts index 4242f446c5..8806546e29 100644 --- a/packages/react-native-sdk/src/hooks/index.ts +++ b/packages/react-native-sdk/src/hooks/index.ts @@ -7,3 +7,4 @@ export * from './useIsInPiPMode'; export * from './useAutoEnterPiPEffect'; export * from './useApplyDefaultMediaStreamSettings'; export * from './useScreenShareButton'; +export * from './useTrackDimensions'; diff --git a/packages/react-native-sdk/src/hooks/useTrackDimensions.ts b/packages/react-native-sdk/src/hooks/useTrackDimensions.ts new file mode 100644 index 0000000000..d0c0f835c9 --- /dev/null +++ b/packages/react-native-sdk/src/hooks/useTrackDimensions.ts @@ -0,0 +1,79 @@ +import { + StreamVideoParticipant, + VideoTrackType, +} from '@stream-io/video-client'; +import { useCall } from '@stream-io/video-react-bindings'; +import { useState, useEffect } from 'react'; + +/** + * This is a utility hook to get the dimensions of the video track of the participant. + * Note: the `tracktype` is used only for local participants. + * `tracktype` should be 'videoTrack' for video track and 'screenShareTrack' for screen share track. + */ +export function useTrackDimensions( + participant: StreamVideoParticipant, + trackType: VideoTrackType +) { + const [trackDimensions, setTrackDimensions] = useState({ + width: 0, + height: 0, + }); + const call = useCall(); + const { isLocalParticipant, sessionId, videoStream, screenShareStream } = + participant; + + // for local participant we can get from track.getSettings() + useEffect(() => { + if (call && isLocalParticipant) { + const stream = + trackType === 'screenShareTrack' ? screenShareStream : videoStream; + if (!stream) return; + const [track] = stream?.getVideoTracks(); + if (!track) return; + const { width = 0, height = 0 } = track.getSettings(); + setTrackDimensions((prev) => { + if (prev.width !== width || prev.height !== height) { + return { width, height }; + } + return prev; + }); + } + }, [call, isLocalParticipant, screenShareStream, trackType, videoStream]); + + // start reporting stats for the remote participant + useEffect(() => { + if (!call) return; + if (isLocalParticipant) return; + call.startReportingStatsFor(sessionId); + return () => { + call.stopReportingStatsFor(sessionId); + }; + }, [call, sessionId, isLocalParticipant]); + + // for remote participants track.getSettings() is not supported it returns an empty object + // so we need to rely on call stats to get the dimensions + useEffect(() => { + if (!call) return; + const sub = call.state.callStatsReport$.subscribe((report) => { + if (!report) return; + const reportForTracks = report.participants[sessionId]; + const trackStats = reportForTracks + ?.flatMap((r) => r.streams) + .filter((track) => track.kind === 'video'); + if (!trackStats) return; + const stat = trackStats[0]; + if (stat) { + const { frameWidth = 0, frameHeight = 0 } = stat; + setTrackDimensions((prev) => { + if (prev.width !== frameWidth || prev.height !== frameHeight) { + return { width: frameWidth, height: frameHeight }; + } + return prev; + }); + } + }); + return () => sub.unsubscribe(); + }, [call, sessionId]); + + return trackDimensions; +} diff --git a/packages/react-native-sdk/src/icons/Mic.tsx b/packages/react-native-sdk/src/icons/Mic.tsx index 229c63f7dc..e46bd03c20 100644 --- a/packages/react-native-sdk/src/icons/Mic.tsx +++ b/packages/react-native-sdk/src/icons/Mic.tsx @@ -4,16 +4,13 @@ import { ColorValue } from 'react-native/types'; type Props = { color: ColorValue; + size: number; }; -export const Mic = ({ color }: Props) => ( - +export const Mic = ({ color, size }: Props) => ( + - diff --git a/packages/react-native-sdk/src/icons/MicOff.tsx b/packages/react-native-sdk/src/icons/MicOff.tsx index 405bc1c4ee..da7510a9ba 100644 --- a/packages/react-native-sdk/src/icons/MicOff.tsx +++ b/packages/react-native-sdk/src/icons/MicOff.tsx @@ -4,12 +4,13 @@ import { ColorValue } from 'react-native/types'; type Props = { color: ColorValue; + size: number; }; -export const MicOff = ({ color }: Props) => ( - +export const MicOff = ({ color, size }: Props) => ( + diff --git a/packages/react-native-sdk/src/icons/Participants.tsx b/packages/react-native-sdk/src/icons/Participants.tsx deleted file mode 100644 index 82be4a3fe5..0000000000 --- a/packages/react-native-sdk/src/icons/Participants.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react'; -import { Svg, Path } from 'react-native-svg'; -import { ColorValue } from 'react-native/types'; - -type Props = { - color: ColorValue; -}; - -export const Participants = ({ color }: Props) => ( - - - -); diff --git a/packages/react-native-sdk/src/icons/Video.tsx b/packages/react-native-sdk/src/icons/Video.tsx index 331cae6d32..62471ca577 100644 --- a/packages/react-native-sdk/src/icons/Video.tsx +++ b/packages/react-native-sdk/src/icons/Video.tsx @@ -4,12 +4,13 @@ import { ColorValue } from 'react-native/types'; type Props = { color: ColorValue; + size: number; }; -export const Video = ({ color }: Props) => ( - +export const Video = ({ color, size }: Props) => ( + diff --git a/packages/react-native-sdk/src/icons/VideoSlash.tsx b/packages/react-native-sdk/src/icons/VideoSlash.tsx index 6f583561e4..84f02035fd 100644 --- a/packages/react-native-sdk/src/icons/VideoSlash.tsx +++ b/packages/react-native-sdk/src/icons/VideoSlash.tsx @@ -5,12 +5,18 @@ import { IconTestIds } from '../constants/TestIds'; type Props = { color: ColorValue; + size: number; }; -export const VideoSlash = ({ color }: Props) => ( - +export const VideoSlash = ({ color, size }: Props) => ( + diff --git a/packages/react-native-sdk/src/icons/index.tsx b/packages/react-native-sdk/src/icons/index.tsx index 2de769c80b..4da04dcd0f 100644 --- a/packages/react-native-sdk/src/icons/index.tsx +++ b/packages/react-native-sdk/src/icons/index.tsx @@ -6,8 +6,6 @@ export * from './PhoneDown'; export * from './Settings'; export * from './Video'; export * from './VideoSlash'; -export * from './Chat'; -export * from './Participants'; export * from './ThreeDots'; export * from './PinVertical'; export * from './Spotlight'; @@ -22,3 +20,4 @@ export * from './StopScreenShare'; export * from './EndStreamIcon'; export * from './LeaveStreamIcon'; export * from './CallDuration'; +export * from './IconWrapper'; diff --git a/packages/react-native-sdk/src/theme/colors.ts b/packages/react-native-sdk/src/theme/colors.ts index 1d09982558..39c3beb0d3 100644 --- a/packages/react-native-sdk/src/theme/colors.ts +++ b/packages/react-native-sdk/src/theme/colors.ts @@ -28,7 +28,14 @@ const colors: ColorScheme = { background5: palette.grey900, background6: palette.grey950 + opacityToHex(0.85), + // Colors candidates for the default theme buttonPrimaryDefault: '#005fff', + buttonSecondaryDefault: '#19232d', + buttonSecondaryWarningDefault: '#dc433b', + iconPrimaryDefault: '#eff0f1', + iconAlertSuccess: '#00e2a1', + sheetPrimary: '#000000', + buttonPrimaryDisabled: '#1b2c43', buttonPrimaryHover: '#4c8fff', buttonPrimaryPressed: '#123d82', @@ -37,14 +44,12 @@ const colors: ColorScheme = { buttonQuaternaryDisabled: '#31292f', buttonQuaternaryHover: '#e77b76', buttonQuaternaryPressed: '#7d3535', - buttonSecondaryDefault: '#19232d', buttonSecondaryDisabled: '#1e262e29', buttonSecondaryHover: '#323b44', buttonSecondaryPressed: '#101213', buttonSecondaryActiveDefault: '#005fff', buttonSecondaryActiveHover: '#4c8fff', buttonSecondaryActivePressed: '#123d82', - buttonSecondaryWarningDefault: '#dc433b', buttonSecondaryWarningHover: '#e77b76', buttonSecondaryWarningPressed: '#7d3535', buttonTertiaryActive: '#19232d', @@ -60,16 +65,13 @@ const colors: ColorScheme = { containerTertiary: '#2d3042', containerWarning: '#31292f', iconAlertCaution: '#ffd646', - iconAlertSuccess: '#00e2a1', iconAlertWarning: '#dc433b', iconPrimaryAccent: '#005fff', - iconPrimaryDefault: '#eff0f1', iconPrimaryDisabled: '#7e8389', iconPrimaryHover: '#e3e4e5', iconPrimaryOnAccent: '#eff0f1', iconPrimaryPressed: '#656b72', sheetOverlay: '#0c0d0ea6', - sheetPrimary: '#000000', sheetSecondary: '#101213', sheetTertiary: '#19232d', typeAccent: '#00e2a1', diff --git a/packages/react-native-sdk/src/theme/theme.ts b/packages/react-native-sdk/src/theme/theme.ts index 488257cb48..5b7eead4f3 100644 --- a/packages/react-native-sdk/src/theme/theme.ts +++ b/packages/react-native-sdk/src/theme/theme.ts @@ -1,15 +1,23 @@ import { ImageStyle, TextStyle, ViewStyle } from 'react-native/types'; import { colors } from './colors'; -import { ColorScheme, DimensionType, FontStyle, FontTypes } from './types'; +import { + ColorScheme, + DimensionType, + FontStyle, + FontTypes, + Insets, +} from './types'; import { ColorValue } from 'react-native'; export type Theme = { variants: { buttonSizes: DimensionType; + roundButtonSizes: DimensionType; iconSizes: DimensionType; avatarSizes: DimensionType; fontSizes: DimensionType; spacingSizes: DimensionType; + insets: Insets; }; typefaces: Record; defaults: { @@ -18,6 +26,7 @@ export type Theme = { margin: number; padding: number; fontSize: number; + iconSize: number; fontWeight: TextStyle['fontWeight']; borderRadius: ViewStyle['borderRadius']; borderColor: ColorValue; @@ -282,6 +291,13 @@ export type Theme = { export const defaultTheme: Theme = { variants: { + roundButtonSizes: { + xs: 16, + sm: 24, + md: 36, + lg: 44, + xl: 56, + }, buttonSizes: { xs: 40, sm: 50, @@ -317,6 +333,12 @@ export const defaultTheme: Theme = { lg: 20, xl: 24, }, + insets: { + top: 0, + right: 0, + bottom: 0, + left: 0, + }, }, typefaces: { heading4: { @@ -355,7 +377,8 @@ export const defaultTheme: Theme = { padding: 10, fontSize: 16, fontWeight: '500', - borderRadius: 10, + borderRadius: 32, + iconSize: 28, borderColor: colors.primary, borderWidth: 1, }, diff --git a/packages/react-native-sdk/src/theme/types.ts b/packages/react-native-sdk/src/theme/types.ts index d2ba8bf903..4c9c0db527 100644 --- a/packages/react-native-sdk/src/theme/types.ts +++ b/packages/react-native-sdk/src/theme/types.ts @@ -99,6 +99,13 @@ export type DimensionType = { xl: number; }; +export type Insets = { + top: number; + right: number; + bottom: number; + left: number; +}; + export type FontsScheme = Record; export type Theme = ColorType; diff --git a/packages/react-sdk/CHANGELOG.md b/packages/react-sdk/CHANGELOG.md index 14a2f499f1..8a17f925e0 100644 --- a/packages/react-sdk/CHANGELOG.md +++ b/packages/react-sdk/CHANGELOG.md @@ -2,6 +2,19 @@ This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver). +## [1.6.6](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-1.6.5...@stream-io/video-react-sdk-1.6.6) (2024-10-14) + + +### Bug Fixes + +* check for user capabilities before rendering call control buttons ([#1513](https://github.com/GetStream/stream-video-js/issues/1513)) ([9b11219](https://github.com/GetStream/stream-video-js/commit/9b1121966d3e3f7610fbbca386b8837563203e86)) + +## [1.6.5](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-1.6.4...@stream-io/video-react-sdk-1.6.5) (2024-10-10) + +### Dependency Updates + +* `@stream-io/video-client` updated to version `1.8.3` +* `@stream-io/video-react-bindings` updated to version `1.1.3` ## [1.6.4](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-1.6.3...@stream-io/video-react-sdk-1.6.4) (2024-10-10) ### Dependency Updates diff --git a/packages/react-sdk/docusaurus/docs/React/02-guides/01-client-auth.mdx b/packages/react-sdk/docusaurus/docs/React/02-guides/01-client-auth.mdx index da40abbe38..202f2c143e 100644 --- a/packages/react-sdk/docusaurus/docs/React/02-guides/01-client-auth.mdx +++ b/packages/react-sdk/docusaurus/docs/React/02-guides/01-client-auth.mdx @@ -71,6 +71,25 @@ const apiKey = 'my-stream-api-key'; const client = new StreamVideoClient({ apiKey, user }); ``` +Anonymous users don't establish an active web socket connection, therefore they won't receive any events. They are just able to watch a livestream or join a call. + +The token for an anonymous user should contain the `call_cids` field, which is an array of the call `cid`'s that the user is allowed to join. + +Here's an example JWT token payload for an anonymous user: + +```ts +{ + "iss": "@stream-io/dashboard", + "iat": 1726406693, + "exp": 1726493093, + "user_id": "!anon", + "role": "viewer", + "call_cids": [ + "livestream:123" + ] +} +``` + ## Client options ### `token` or `tokenProvider` diff --git a/packages/react-sdk/package.json b/packages/react-sdk/package.json index 6c6172d6a7..8b7a05eb35 100644 --- a/packages/react-sdk/package.json +++ b/packages/react-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@stream-io/video-react-sdk", - "version": "1.6.4", + "version": "1.6.6", "packageManager": "yarn@3.2.4", "main": "./dist/index.cjs.js", "module": "./dist/index.es.js", diff --git a/packages/react-sdk/src/components/CallControls/CallControls.tsx b/packages/react-sdk/src/components/CallControls/CallControls.tsx index e897dfa6d9..b7a09e1175 100644 --- a/packages/react-sdk/src/components/CallControls/CallControls.tsx +++ b/packages/react-sdk/src/components/CallControls/CallControls.tsx @@ -1,3 +1,5 @@ +import { OwnCapability } from '@stream-io/video-client'; +import { Restricted } from '@stream-io/video-react-bindings'; import { SpeakingWhileMutedNotification } from '../Notification'; import { RecordCallButton } from './RecordCallButton'; import { ReactionsButton } from './ReactionsButton'; @@ -12,13 +14,28 @@ export type CallControlsProps = { export const CallControls = ({ onLeave }: CallControlsProps) => (
- - - - - - - + + + + + + + + + + + + + + + + +
); diff --git a/sample-apps/react-native/dogfood/ios/Podfile.lock b/sample-apps/react-native/dogfood/ios/Podfile.lock index 348033fe71..fa7ea9fdbd 100644 --- a/sample-apps/react-native/dogfood/ios/Podfile.lock +++ b/sample-apps/react-native/dogfood/ios/Podfile.lock @@ -1148,7 +1148,7 @@ PODS: - stream-react-native-webrtc (118.1.0): - JitsiWebRTC (~> 118.0.0) - React-Core - - stream-video-react-native (1.1.3): + - stream-video-react-native (1.1.1): - glog - RCT-Folly (= 2022.05.16.00) - React-Core @@ -1479,7 +1479,7 @@ SPEC CHECKSUMS: SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 stream-io-video-filters-react-native: 8c345e6adf5164646696d45f9962c4199b2fe3b9 stream-react-native-webrtc: 4ccf61161f77c57b9aa45f78cb7f69b7d91f3e9f - stream-video-react-native: 363520e27b8cf267eb824cb32d10ea7f09bf09e8 + stream-video-react-native: ff287d6bc897fffdf7b07ed3828e53bbf74bf576 TOCropViewController: 80b8985ad794298fb69d3341de183f33d1853654 Yoga: 1b901a6d6eeba4e8a2e8f308f708691cdb5db312 diff --git a/sample-apps/react-native/dogfood/package.json b/sample-apps/react-native/dogfood/package.json index 820bfd044e..4fd56a9881 100644 --- a/sample-apps/react-native/dogfood/package.json +++ b/sample-apps/react-native/dogfood/package.json @@ -1,6 +1,6 @@ { "name": "@stream-io/video-react-native-dogfood", - "version": "4.3.9", + "version": "4.3.11", "private": true, "scripts": { "android": "react-native run-android", diff --git a/sample-apps/react-native/dogfood/src/assets/Audio.tsx b/sample-apps/react-native/dogfood/src/assets/Audio.tsx new file mode 100644 index 0000000000..ae5c26767f --- /dev/null +++ b/sample-apps/react-native/dogfood/src/assets/Audio.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { Svg, Path } from 'react-native-svg'; +import { ColorValue } from 'react-native/types'; + +type Props = { + color: ColorValue; + size: number; +}; + +const Audio = ({ color, size }: Props) => ( + + + +); + +export default Audio; diff --git a/sample-apps/react-native/dogfood/src/assets/Chat.tsx b/sample-apps/react-native/dogfood/src/assets/Chat.tsx index adc7b30f55..a6da4abe45 100644 --- a/sample-apps/react-native/dogfood/src/assets/Chat.tsx +++ b/sample-apps/react-native/dogfood/src/assets/Chat.tsx @@ -4,13 +4,16 @@ import { ColorValue } from 'react-native/types'; type Props = { color: ColorValue; + size: number; }; -export const Chat = ({ color }: Props) => ( - +const Chat = ({ color, size }: Props) => ( + ); + +export default Chat; diff --git a/packages/react-native-sdk/src/icons/Chat.tsx b/sample-apps/react-native/dogfood/src/assets/LiveStreamChat.tsx similarity index 91% rename from packages/react-native-sdk/src/icons/Chat.tsx rename to sample-apps/react-native/dogfood/src/assets/LiveStreamChat.tsx index adc7b30f55..2c7073acea 100644 --- a/packages/react-native-sdk/src/icons/Chat.tsx +++ b/sample-apps/react-native/dogfood/src/assets/LiveStreamChat.tsx @@ -6,7 +6,7 @@ type Props = { color: ColorValue; }; -export const Chat = ({ color }: Props) => ( +export const LiveStreamChat = ({ color }: Props) => ( ( + + + +); + +export default MoreActions; diff --git a/sample-apps/react-native/dogfood/src/assets/Participants.tsx b/sample-apps/react-native/dogfood/src/assets/Participants.tsx new file mode 100644 index 0000000000..02b95ca1c0 --- /dev/null +++ b/sample-apps/react-native/dogfood/src/assets/Participants.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { Svg, Path } from 'react-native-svg'; +import { ColorValue } from 'react-native/types'; + +type Props = { + color: ColorValue; + size: number; +}; + +const Participants = ({ color, size }: Props) => ( + + + +); + +export default Participants; diff --git a/sample-apps/react-native/dogfood/src/assets/RecordCall.tsx b/sample-apps/react-native/dogfood/src/assets/RecordCall.tsx new file mode 100644 index 0000000000..c07c995b43 --- /dev/null +++ b/sample-apps/react-native/dogfood/src/assets/RecordCall.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { ColorValue } from 'react-native'; +import { Svg, Path } from 'react-native-svg'; + +type Props = { + color: ColorValue; + size: number; +}; + +const RecordCall = ({ color, size }: Props) => ( + + + + +); + +export default RecordCall; diff --git a/sample-apps/react-native/dogfood/src/components/ActiveCall.tsx b/sample-apps/react-native/dogfood/src/components/ActiveCall.tsx index 8189c23467..47af50a4e0 100644 --- a/sample-apps/react-native/dogfood/src/components/ActiveCall.tsx +++ b/sample-apps/react-native/dogfood/src/components/ActiveCall.tsx @@ -1,17 +1,18 @@ -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useState, useMemo } from 'react'; import { useCall, CallContent, - CallControlProps, + useTheme, } from '@stream-io/video-react-native-sdk'; -import { ActivityIndicator, StyleSheet } from 'react-native'; +import { ActivityIndicator, StyleSheet, View } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { ParticipantsInfoList } from './ParticipantsInfoList'; import { CallControlsComponent, CallControlsComponentProps, -} from './CallControlsComponent'; +} from './CallControlls/CallControlsComponent'; import { useOrientation } from '../hooks/useOrientation'; +import { Z_INDEX } from '../constants'; type ActiveCallProps = CallControlsComponentProps & { onBackPressed?: () => void; @@ -30,10 +31,11 @@ export const ActiveCall = ({ useState(false); const call = useCall(); const currentOrientation = useOrientation(); + const styles = useStyles(); - const onOpenCallParticipantsInfo = () => { + const onOpenCallParticipantsInfo = useCallback(() => { setIsCallParticipantsVisible(true); - }; + }, []); useEffect(() => { return call?.on('call.ended', () => { @@ -41,42 +43,61 @@ export const ActiveCall = ({ }); }, [call, onCallEnded]); - const CustomControlsComponent = useCallback( - ({ landscape }: CallControlProps) => { - return ( - - ); - }, - [onChatOpenHandler, onHangupCallHandler, unreadCountIndicator], - ); + const CustomControlsComponent = useCallback(() => { + return ( + + ); + }, [onChatOpenHandler, onOpenCallParticipantsInfo, unreadCountIndicator]); if (!call) { return ; } return ( - - - - + + + + + + + ); }; -const styles = StyleSheet.create({ - container: { - flex: 1, - }, -}); +const useStyles = () => { + const { theme } = useTheme(); + + return useMemo( + () => + StyleSheet.create({ + container: { flex: 1 }, + callContent: { flex: 1 }, + safeArea: { flex: 1, paddingBottom: theme.variants.insets.bottom }, + unsafeArea: { + position: 'absolute', + left: 0, + right: 0, + bottom: 0, + height: theme.variants.insets.bottom, + backgroundColor: theme.colors.sheetPrimary, + }, + view: { + ...StyleSheet.absoluteFillObject, + zIndex: Z_INDEX.IN_FRONT, + }, + }), + [theme], + ); +}; diff --git a/sample-apps/react-native/dogfood/src/components/CallControlls/AudioButton.tsx b/sample-apps/react-native/dogfood/src/components/CallControlls/AudioButton.tsx new file mode 100644 index 0000000000..d53e4fbbfc --- /dev/null +++ b/sample-apps/react-native/dogfood/src/components/CallControlls/AudioButton.tsx @@ -0,0 +1,49 @@ +import React, { useState } from 'react'; +import { + CallControlsButton, + useTheme, +} from '@stream-io/video-react-native-sdk'; +import { IconWrapper } from '@stream-io/video-react-native-sdk/src/icons'; +import Audio from '../../assets/Audio'; + +/** + * The props for the Audio Button in the Call Controls. + */ +export type AudioButtonProps = { + /** + * Handler to be called when the audio button is pressed. + */ + onPressHandler?: () => void; +}; + +/** + * A button that can be used to switch audio output options + * like speaker, headphones, etc. + */ +export const AudioButton = ({ onPressHandler }: AudioButtonProps) => { + const { + theme: { colors, audioButton, variants }, + } = useTheme(); + + const [isPressed, setIsPressed] = useState(false); + const buttonColor = isPressed + ? colors.buttonPrimaryDefault + : colors.buttonSecondaryDefault; + + return ( + { + if (onPressHandler) { + onPressHandler(); + } + setIsPressed(!isPressed); + }} + style={audioButton} + color={buttonColor} + > + + + + + ); +}; diff --git a/sample-apps/react-native/dogfood/src/components/CallControlls/BadgeCountIndicator.tsx b/sample-apps/react-native/dogfood/src/components/CallControlls/BadgeCountIndicator.tsx new file mode 100644 index 0000000000..7b4323a2fc --- /dev/null +++ b/sample-apps/react-native/dogfood/src/components/CallControlls/BadgeCountIndicator.tsx @@ -0,0 +1,68 @@ +import React, { useMemo } from 'react'; +import { StyleSheet, Text, View } from 'react-native'; +import { useTheme } from '@stream-io/video-react-native-sdk'; +import { ComponentTestIds } from '@stream-io/video-react-native-sdk/src/constants/TestIds'; +import { Z_INDEX } from '../../constants'; + +/** + * A badge that displays a number. + * + * @prop {number} count - The number to display in the badge. + * + * @returns {ReactElement} A View with a Text that displays the count in the badge. + */ +export const BadgeCountIndicator = ({ + count, +}: { + count: number | undefined; +}) => { + const { + theme: { colors, typefaces }, + } = useTheme(); + const styles = useStyles(); + + // Don't show badge if count is 0 or undefined + if (!count) { + return null; + } + + return ( + + + {count} + + + ); +}; + +const useStyles = () => { + const { theme } = useTheme(); + return useMemo( + () => + StyleSheet.create({ + badge: { + position: 'absolute', + justifyContent: 'center', + borderRadius: theme.defaults.borderRadius, + left: 13, + bottom: 17, + zIndex: Z_INDEX.IN_FRONT, + height: theme.variants.roundButtonSizes.xs, + width: theme.variants.roundButtonSizes.xs, + }, + badgeText: { + textAlign: 'center', + }, + }), + [theme], + ); +}; diff --git a/sample-apps/react-native/dogfood/src/components/CallControlls/CallControlsComponent.tsx b/sample-apps/react-native/dogfood/src/components/CallControlls/CallControlsComponent.tsx new file mode 100644 index 0000000000..add4173789 --- /dev/null +++ b/sample-apps/react-native/dogfood/src/components/CallControlls/CallControlsComponent.tsx @@ -0,0 +1,95 @@ +import { + CallContentProps, + ToggleAudioPublishingButton, + ToggleVideoPublishingButton, + useCallStateHooks, +} from '@stream-io/video-react-native-sdk'; +import React from 'react'; +import { StyleSheet, Text, View } from 'react-native'; +import { appTheme } from '../../theme'; +import { Z_INDEX } from '../../constants'; +import { MoreActionsButton } from './MoreActionsButton'; +import { ParticipantsButton } from './ParticipantsButton'; +import { ChatButton } from './ChatButton'; +import { RecordCallButton } from './RecordCallButton'; +import { AudioButton } from './AudioButton'; + +export type CallControlsComponentProps = Pick< + CallContentProps, + 'supportedReactions' +> & { + onChatOpenHandler?: () => void; + onParticipantInfoPress?: () => void; + unreadCountIndicator?: number; +}; + +export const CallControlsComponent = ({ + onChatOpenHandler, + unreadCountIndicator, + onParticipantInfoPress, +}: CallControlsComponentProps) => { + const { useMicrophoneState } = useCallStateHooks(); + const { isSpeakingWhileMuted } = useMicrophoneState(); + + return ( + + {isSpeakingWhileMuted && ( + + You are muted. Unmute to speak. + + )} + + + + + + + + + + + + + + + ); +}; + +const styles = StyleSheet.create({ + speakingLabelContainer: { + backgroundColor: appTheme.colors.static_overlay, + paddingVertical: 10, + width: '100%', + }, + label: { + textAlign: 'center', + color: appTheme.colors.static_white, + }, + callControlsWrapper: { + display: 'flex', + flexDirection: 'row', + justifyContent: 'flex-start', + zIndex: Z_INDEX.IN_FRONT, + }, + container: { + paddingVertical: 16, + paddingHorizontal: 16, + backgroundColor: 'black', + height: 76, + }, + left: { + flex: 2.5, + flexDirection: 'row', + alignItems: 'flex-start', + gap: 6, + }, + right: { + flex: 1, + flexDirection: 'row', + justifyContent: 'flex-end', + gap: 6, + }, +}); diff --git a/sample-apps/react-native/dogfood/src/components/CallControlls/ChatButton.tsx b/sample-apps/react-native/dogfood/src/components/CallControlls/ChatButton.tsx new file mode 100644 index 0000000000..dce03ccb20 --- /dev/null +++ b/sample-apps/react-native/dogfood/src/components/CallControlls/ChatButton.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { + CallControlsButton, + useTheme, +} from '@stream-io/video-react-native-sdk'; +import { IconWrapper } from '@stream-io/video-react-native-sdk/src/icons'; +import { BadgeCountIndicator } from './BadgeCountIndicator'; +import Chat from '../../assets/Chat'; + +/** + * The props for the Chat Button in the Call Controls. + */ +export type ChatButtonProps = { + /** + * Handler to be called when the chat button is pressed. + * @returns void + */ + onPressHandler?: () => void; + /** + * The count of the current unread message to be displayed above on the Chat button. + */ + unreadBadgeCount?: number; +}; + +/** + * Button to open the Chat window while in the call. + * + * This call also display the unread count indicator/badge is there messages that are unread. + */ +export const ChatButton = ({ + onPressHandler, + unreadBadgeCount, +}: ChatButtonProps) => { + const { + theme: { colors, chatButton, defaults }, + } = useTheme(); + return ( + + + + + + + ); +}; diff --git a/sample-apps/react-native/dogfood/src/components/CallControlls/MoreActionsButton.tsx b/sample-apps/react-native/dogfood/src/components/CallControlls/MoreActionsButton.tsx new file mode 100644 index 0000000000..a884cadd37 --- /dev/null +++ b/sample-apps/react-native/dogfood/src/components/CallControlls/MoreActionsButton.tsx @@ -0,0 +1,56 @@ +import React, { useState } from 'react'; +import { + CallControlsButton, + useTheme, +} from '@stream-io/video-react-native-sdk'; +import { IconWrapper } from '@stream-io/video-react-native-sdk/src/icons'; +import MoreActions from '../../assets/MoreActions'; + +/** + * The props for the More Actions Button in the Call Controls. + */ +export type MoreActionsButtonProps = { + /** + * Handler to be called when the more actions button is pressed. + */ + onPressHandler?: () => void; +}; + +/** + * A button that can be used to toggle the visibility + * of a menu or bottom sheet with more actions. + * + */ +export const MoreActionsButton = ({ + onPressHandler, +}: MoreActionsButtonProps) => { + const { + theme: { colors, moreActionsButton, defaults }, + } = useTheme(); + + const [isPressed, setIsPressed] = useState(false); + const buttonColor = isPressed + ? colors.buttonPrimaryDefault + : colors.buttonSecondaryDefault; + + return ( + { + // TODO: Implement PBE-5870 [Demo App] Component for "More" menu items + if (onPressHandler) { + onPressHandler(); + } + setIsPressed(!isPressed); + }} + style={moreActionsButton} + color={buttonColor} + > + + + + + ); +}; diff --git a/sample-apps/react-native/dogfood/src/components/CallControlls/ParticipantsButton.tsx b/sample-apps/react-native/dogfood/src/components/CallControlls/ParticipantsButton.tsx new file mode 100644 index 0000000000..593cd5981c --- /dev/null +++ b/sample-apps/react-native/dogfood/src/components/CallControlls/ParticipantsButton.tsx @@ -0,0 +1,68 @@ +import React from 'react'; +import { + CallControlsButton, + useTheme, +} from '@stream-io/video-react-native-sdk'; +import { IconWrapper } from '@stream-io/video-react-native-sdk/src/icons'; +import { useCallStateHooks } from '@stream-io/video-react-bindings'; +import { CallingState } from '@stream-io/video-client'; +import { BadgeCountIndicator } from './BadgeCountIndicator'; +import Participants from '../../assets/Participants'; + +/** + * The props for the Participants Button in the Call Controls. + */ +export type ParticipantsButtonProps = { + /** + * Handler to be called when the participants button is pressed. + * @returns void + */ + onParticipantInfoPress?: () => void; + /** + * The count of the current participants present in the call. + */ + participantCount?: number; +}; + +/** + * Button to open the Participant window while in the call. + * + * This button also displays the participant count of the participants in the call. + */ +export const ParticipantsButton = ({ + onParticipantInfoPress, + participantCount, +}: ParticipantsButtonProps) => { + const { + theme: { colors, chatButton, defaults }, + } = useTheme(); + + const { useCallMembers, useCallCallingState } = useCallStateHooks(); + const members = useCallMembers(); + const callingState = useCallCallingState(); + + let count = 0; + /** + * We show member's length if Incoming and Outgoing Call Views are rendered. + * Else we show the count of the participants that are in the call. + * Since the members count also includes caller/callee, we reduce the count by 1. + **/ + if (callingState === CallingState.RINGING) { + count = members.length - 1; + } else { + count = participantCount ?? 0; + } + + // TODO: PBE-5873 [Demo App] On click implement showing the Participant List + return ( + + + + + + + ); +}; diff --git a/sample-apps/react-native/dogfood/src/components/CallControlls/RecordCallButton.tsx b/sample-apps/react-native/dogfood/src/components/CallControlls/RecordCallButton.tsx new file mode 100644 index 0000000000..7f80e3e406 --- /dev/null +++ b/sample-apps/react-native/dogfood/src/components/CallControlls/RecordCallButton.tsx @@ -0,0 +1,54 @@ +import React, { useState } from 'react'; +import { + CallControlsButton, + useTheme, +} from '@stream-io/video-react-native-sdk'; +import RecordCall from '../../assets/RecordCall'; +import { IconWrapper } from '@stream-io/video-react-native-sdk/src/icons'; + +/** + * The props for the Record Call Button in the Call Controls. + */ +export type RecordCallButtonProps = { + /** + * Handler to be called when the record call button is pressed. + * @returns void + */ + onPressHandler?: () => void; +}; + +/** + * The Record Call Button is used in the Call Controls component + * and allows the user to toggle call recording. + */ +export const RecordCallButton = ({ onPressHandler }: RecordCallButtonProps) => { + const { + theme: { colors, recordCallButton, defaults, variants }, + } = useTheme(); + const [isRecording, setIsRecording] = useState(false); + const buttonColor = isRecording + ? colors.buttonSecondaryWarningDefault + : colors.buttonSecondaryDefault; + + // TODO: implement PBE-5871 [Demo App] Call Recording flow + return ( + { + if (onPressHandler) { + onPressHandler(); + } + setIsRecording(!isRecording); + }} + color={buttonColor} + style={recordCallButton} + > + + + + + ); +}; diff --git a/sample-apps/react-native/dogfood/src/components/CallControlsComponent.tsx b/sample-apps/react-native/dogfood/src/components/CallControlsComponent.tsx deleted file mode 100644 index 9e20823073..0000000000 --- a/sample-apps/react-native/dogfood/src/components/CallControlsComponent.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import { - CallContentProps, - ChatButton, - HangUpCallButton, - ReactionsButton, - ToggleAudioPublishingButton, - ToggleCameraFaceButton, - ToggleVideoPublishingButton, - ScreenShareToggleButton, - useCallStateHooks, -} from '@stream-io/video-react-native-sdk'; -import React from 'react'; -import { StyleSheet, Text, View, ViewStyle } from 'react-native'; -import { appTheme } from '../theme'; -import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import { Z_INDEX } from '../constants'; -import { VideoEffectsButton } from './VideoEffectsButton'; - -export type CallControlsComponentProps = Pick< - CallContentProps, - 'supportedReactions' -> & { - onChatOpenHandler?: () => void; - onHangupCallHandler?: () => void; - unreadCountIndicator?: number; - landscape?: boolean; -}; - -export const CallControlsComponent = ({ - onChatOpenHandler, - onHangupCallHandler, - unreadCountIndicator, - landscape, -}: CallControlsComponentProps) => { - const { bottom } = useSafeAreaInsets(); - const { useMicrophoneState } = useCallStateHooks(); - const { isSpeakingWhileMuted } = useMicrophoneState(); - const landscapeStyles: ViewStyle = { - flexDirection: landscape ? 'column-reverse' : 'row', - paddingHorizontal: landscape ? 12 : 0, - paddingVertical: landscape ? 0 : 12, - paddingBottom: landscape ? 0 : Math.max(bottom, appTheme.spacing.lg), - }; - - return ( - - {isSpeakingWhileMuted && ( - - You are muted. Unmute to speak. - - )} - - - - - - - - - - - - ); -}; - -const styles = StyleSheet.create({ - speakingLabelContainer: { - backgroundColor: appTheme.colors.static_overlay, - paddingVertical: 10, - width: '100%', - }, - label: { - textAlign: 'center', - color: appTheme.colors.static_white, - }, - callControlsWrapper: { - justifyContent: 'space-evenly', - zIndex: Z_INDEX.IN_FRONT, - backgroundColor: appTheme.colors.static_grey, - }, -}); diff --git a/sample-apps/react-native/dogfood/src/components/LiveStream/LiveStreamChatControlButton.tsx b/sample-apps/react-native/dogfood/src/components/LiveStream/LiveStreamChatControlButton.tsx index 87214f0939..ee39eb8dbc 100644 --- a/sample-apps/react-native/dogfood/src/components/LiveStream/LiveStreamChatControlButton.tsx +++ b/sample-apps/react-native/dogfood/src/components/LiveStream/LiveStreamChatControlButton.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Pressable, View } from 'react-native'; import { appTheme } from '../../theme'; -import { Chat } from '../../assets/Chat'; +import { LiveStreamChat } from '../../assets/LiveStreamChat'; import { StyleSheet } from 'react-native'; type LiveStreamChatControlButtonProps = { @@ -22,7 +22,7 @@ export const LiveStreamChatControlButton = ({ ]} > - +
); diff --git a/sample-apps/react-native/dogfood/src/components/MeetingUI.tsx b/sample-apps/react-native/dogfood/src/components/MeetingUI.tsx index dcae405b3e..f17a03e19a 100644 --- a/sample-apps/react-native/dogfood/src/components/MeetingUI.tsx +++ b/sample-apps/react-native/dogfood/src/components/MeetingUI.tsx @@ -130,7 +130,6 @@ export const MeetingUI = ({ callId, navigation, route }: Props) => { onCallEnded={onCallEnded} onChatOpenHandler={onChatOpenHandler} unreadCountIndicator={unreadCountIndicator} - onHangupCallHandler={onHangupCallHandler} onBackPressed={onHangupCallHandler} /> ); diff --git a/sample-apps/react-native/dogfood/src/components/VideoWrapper.tsx b/sample-apps/react-native/dogfood/src/components/VideoWrapper.tsx index 146075ddc5..9a4064b285 100644 --- a/sample-apps/react-native/dogfood/src/components/VideoWrapper.tsx +++ b/sample-apps/react-native/dogfood/src/components/VideoWrapper.tsx @@ -9,8 +9,10 @@ import { } from '../contexts/AppContext'; import { createToken } from '../modules/helpers/createToken'; import translations from '../translations'; +import { useCustomTheme } from '../theme'; export const VideoWrapper = ({ children }: PropsWithChildren<{}>) => { + const customTheme = useCustomTheme(); const userId = useAppGlobalStoreValue((store) => store.userId); const userName = useAppGlobalStoreValue((store) => store.userName); const userImageUrl = useAppGlobalStoreValue((store) => store.userImageUrl); @@ -64,7 +66,11 @@ export const VideoWrapper = ({ children }: PropsWithChildren<{}>) => { } return ( - + {children} ); diff --git a/sample-apps/react-native/dogfood/src/theme.ts b/sample-apps/react-native/dogfood/src/theme.ts index c1621e8d93..e144d412f8 100644 --- a/sample-apps/react-native/dogfood/src/theme.ts +++ b/sample-apps/react-native/dogfood/src/theme.ts @@ -1,3 +1,6 @@ +import { DeepPartial, Theme } from '@stream-io/video-react-native-sdk'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; + const opacityToHex = (opacity: number) => { return Math.round(opacity * 255) .toString(16) @@ -30,3 +33,17 @@ export const appTheme = { IN_FRONT: 2, }, }; + +export const useCustomTheme = (): DeepPartial => { + const { top, right, bottom, left } = useSafeAreaInsets(); + return { + variants: { + insets: { + top, + right, + bottom, + left, + }, + }, + } as DeepPartial; +}; diff --git a/sample-apps/react-native/expo-video-sample/android/.gitignore b/sample-apps/react-native/expo-video-sample/android/.gitignore index 877b87e9a5..8a6be07718 100644 --- a/sample-apps/react-native/expo-video-sample/android/.gitignore +++ b/sample-apps/react-native/expo-video-sample/android/.gitignore @@ -10,6 +10,7 @@ build/ local.properties *.iml *.hprof +.cxx/ # Bundle artifacts *.jsbundle diff --git a/sample-apps/react-native/expo-video-sample/android/app/build.gradle b/sample-apps/react-native/expo-video-sample/android/app/build.gradle index 67786b4df9..efdaeeead7 100644 --- a/sample-apps/react-native/expo-video-sample/android/app/build.gradle +++ b/sample-apps/react-native/expo-video-sample/android/app/build.gradle @@ -4,6 +4,27 @@ apply plugin: "com.facebook.react" def projectRoot = rootDir.getAbsoluteFile().getParentFile().getAbsolutePath() +static def versionToNumber(major, minor, patch) { + return patch * 100 + minor * 10000 + major * 1000000 +} + +def getRNVersion() { + def version = providers.exec { + workingDir(projectDir) + commandLine("node", "-e", "console.log(require('react-native/package.json').version);") + }.standardOutput.asText.get().trim() + + def coreVersion = version.split("-")[0] + def (major, minor, patch) = coreVersion.tokenize('.').collect { it.toInteger() } + + return versionToNumber( + major, + minor, + patch + ) +} +def rnVersion = getRNVersion() + /** * This is the configuration block to customize your React Native Android app. * By default you don't need to apply any configuration, just uncomment the lines you need. @@ -57,6 +78,11 @@ react { // // The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map" // hermesFlags = ["-O", "-output-source-map"] + + if (rnVersion >= versionToNumber(0, 75, 0)) { + /* Autolinking */ + autolinkLibrariesWithApp() + } } /** @@ -95,8 +121,6 @@ android { targetSdkVersion rootProject.ext.targetSdkVersion versionCode 1 versionName "1.0.0" - - buildConfigField("boolean", "REACT_NATIVE_UNSTABLE_USE_RUNTIME_SCHEDULER_ALWAYS", (findProperty("reactNative.unstable_useRuntimeSchedulerAlways") ?: true).toString()) } signingConfigs { debug { @@ -117,6 +141,7 @@ android { shrinkResources (findProperty('android.enableShrinkResourcesInReleaseBuilds')?.toBoolean() ?: false) minifyEnabled enableProguardInReleaseBuilds proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" + crunchPngs (findProperty('android.enablePngCrunchInReleaseBuilds')?.toBoolean() ?: true) } } packagingOptions { @@ -168,16 +193,18 @@ dependencies { } } - implementation("com.facebook.react:flipper-integration") - if (hermesEnabled.toBoolean()) { implementation("com.facebook.react:hermes-android") } else { implementation jscFlavor } + + implementation project(':notifee_react-native') } -apply from: new File(["node", "--print", "require.resolve('@react-native-community/cli-platform-android/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim(), "../native_modules.gradle"); -applyNativeModulesAppBuildGradle(project) +if (rnVersion < versionToNumber(0, 75, 0)) { + apply from: new File(["node", "--print", "require.resolve('@react-native-community/cli-platform-android/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim(), "../native_modules.gradle"); + applyNativeModulesAppBuildGradle(project) +} apply plugin: 'com.google.gms.google-services' \ No newline at end of file diff --git a/sample-apps/react-native/expo-video-sample/android/app/src/main/AndroidManifest.xml b/sample-apps/react-native/expo-video-sample/android/app/src/main/AndroidManifest.xml index 83aecde96c..c6e6e2c383 100644 --- a/sample-apps/react-native/expo-video-sample/android/app/src/main/AndroidManifest.xml +++ b/sample-apps/react-native/expo-video-sample/android/app/src/main/AndroidManifest.xml @@ -1,4 +1,4 @@ - + @@ -15,7 +15,7 @@ - + @@ -28,7 +28,7 @@ - + diff --git a/sample-apps/react-native/expo-video-sample/android/app/src/main/java/io/getstream/expovideosample/MainApplication.kt b/sample-apps/react-native/expo-video-sample/android/app/src/main/java/io/getstream/expovideosample/MainApplication.kt index 82e09215f2..19488a0561 100644 --- a/sample-apps/react-native/expo-video-sample/android/app/src/main/java/io/getstream/expovideosample/MainApplication.kt +++ b/sample-apps/react-native/expo-video-sample/android/app/src/main/java/io/getstream/expovideosample/MainApplication.kt @@ -2,18 +2,14 @@ package io.getstream.expovideosample import android.app.Application import android.content.res.Configuration -import androidx.annotation.NonNull import com.facebook.react.PackageList import com.facebook.react.ReactApplication import com.facebook.react.ReactNativeHost import com.facebook.react.ReactPackage import com.facebook.react.ReactHost -import com.facebook.react.config.ReactFeatureFlags import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load -import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost import com.facebook.react.defaults.DefaultReactNativeHost -import com.facebook.react.flipper.ReactNativeFlipper import com.facebook.soloader.SoLoader import expo.modules.ApplicationLifecycleDispatcher @@ -40,21 +36,15 @@ class MainApplication : Application(), ReactApplication { ) override val reactHost: ReactHost - get() = getDefaultReactHost(this.applicationContext, reactNativeHost) + get() = ReactNativeHostWrapper.createReactHost(applicationContext, reactNativeHost) override fun onCreate() { super.onCreate() SoLoader.init(this, false) - if (!BuildConfig.REACT_NATIVE_UNSTABLE_USE_RUNTIME_SCHEDULER_ALWAYS) { - ReactFeatureFlags.unstable_useRuntimeSchedulerAlways = false - } if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { // If you opted-in for the New Architecture, we load the native entry point for this app. load() } - if (BuildConfig.DEBUG) { - ReactNativeFlipper.initializeFlipper(this, reactNativeHost.reactInstanceManager) - } ApplicationLifecycleDispatcher.onApplicationCreate(this) } diff --git a/sample-apps/react-native/expo-video-sample/android/app/src/main/res/drawable/rn_edit_text_material.xml b/sample-apps/react-native/expo-video-sample/android/app/src/main/res/drawable/rn_edit_text_material.xml index 73b37e4d99..5c25e728ea 100644 --- a/sample-apps/react-native/expo-video-sample/android/app/src/main/res/drawable/rn_edit_text_material.xml +++ b/sample-apps/react-native/expo-video-sample/android/app/src/main/res/drawable/rn_edit_text_material.xml @@ -17,7 +17,8 @@ android:insetLeft="@dimen/abc_edit_text_inset_horizontal_material" android:insetRight="@dimen/abc_edit_text_inset_horizontal_material" android:insetTop="@dimen/abc_edit_text_inset_top_material" - android:insetBottom="@dimen/abc_edit_text_inset_bottom_material"> + android:insetBottom="@dimen/abc_edit_text_inset_bottom_material" + >