├── .eslintrc.js ├── .github └── ISSUE_TEMPLATE │ └── issue-report.md ├── .gitignore ├── .prettierrc.js ├── Components.ts ├── Contexts.ts ├── LICENSE ├── README.md ├── UI Kit.png ├── docs ├── .nojekyll ├── UI Kit.png ├── assets │ ├── highlight.css │ ├── icons.css │ ├── icons.png │ ├── icons@2x.png │ ├── main.js │ ├── search.js │ ├── style.css │ ├── widgets.png │ └── widgets@2x.png ├── classes │ ├── Agora_UIKit._internal_.Component.html │ ├── Agora_UIKit._internal_.RtcConnection.html │ ├── Agora_UIKit._internal_.RtmAttribute.html │ ├── Agora_UIKit._internal_.RtmChannelAttribute.html │ ├── Agora_UIKit._internal_.RtmMessage.html │ ├── React_Contexts._internal_.EventEmitter.html │ ├── React_Contexts._internal_.EventSubscriptionVendor.html │ ├── React_Contexts._internal_.RtmAttribute.html │ ├── React_Contexts._internal_.RtmChannelAttribute.html │ ├── React_Contexts._internal_.RtmEngine.html │ └── React_Contexts._internal_.RtmMessage.html ├── enums │ ├── Agora_UIKit.ChannelProfileType.html │ ├── Agora_UIKit.ClientRoleType.html │ ├── Agora_UIKit.DualStreamMode.html │ ├── Agora_UIKit.Layout.html │ ├── Agora_UIKit.RenderModeType.html │ ├── Agora_UIKit.ToggleState.html │ ├── Agora_UIKit._internal_.ConnectionChangeReason.html │ ├── Agora_UIKit._internal_.LocalInvitationError.html │ ├── Agora_UIKit._internal_.LocalInvitationState.html │ ├── Agora_UIKit._internal_.PeerOnlineState.html │ ├── Agora_UIKit._internal_.RemoteAudioState.html │ ├── Agora_UIKit._internal_.RemoteAudioStateReason.html │ ├── Agora_UIKit._internal_.RemoteInvitationError.html │ ├── Agora_UIKit._internal_.RemoteInvitationState.html │ ├── Agora_UIKit._internal_.RemoteVideoState.html │ ├── Agora_UIKit._internal_.RemoteVideoStateReason.html │ ├── Agora_UIKit._internal_.RtmConnectionState.html │ ├── Agora_UIKit._internal_.UserOfflineReasonType.html │ ├── React_Contexts._internal_.ConnectionChangeReason.html │ ├── React_Contexts._internal_.LocalInvitationError.html │ ├── React_Contexts._internal_.LocalInvitationState.html │ ├── React_Contexts._internal_.LogLevel.html │ ├── React_Contexts._internal_.PeerOnlineState.html │ ├── React_Contexts._internal_.RemoteInvitationError.html │ ├── React_Contexts._internal_.RemoteInvitationState.html │ ├── React_Contexts._internal_.RtmConnectionState.html │ ├── React_Contexts._internal_.clientRoleRaw.html │ ├── React_Contexts._internal_.mutingDevice.html │ ├── React_Contexts._internal_.popUpStateEnum.html │ └── React_Contexts._internal_.rtmStatusEnum.html ├── index.html ├── interfaces │ ├── Agora_UIKit.AgoraUIKitProps.html │ ├── Agora_UIKit.ConnectionData.html │ ├── Agora_UIKit.IconsInterface.html │ ├── Agora_UIKit.Settings.html │ ├── Agora_UIKit.StylePropInterface.html │ ├── Agora_UIKit.UidInterface.html │ ├── Agora_UIKit._internal_.CallbacksInterface.html │ ├── Agora_UIKit._internal_.ComponentLifecycle.html │ ├── Agora_UIKit._internal_.ConcatArray.html │ ├── Agora_UIKit._internal_.ConsumerProps.html │ ├── Agora_UIKit._internal_.Context.html │ ├── Agora_UIKit._internal_.DeprecatedLifecycle.html │ ├── Agora_UIKit._internal_.Element.html │ ├── Agora_UIKit._internal_.Error.html │ ├── Agora_UIKit._internal_.ErrorInfo.html │ ├── Agora_UIKit._internal_.ExoticComponent.html │ ├── Agora_UIKit._internal_.FlexStyle.html │ ├── Agora_UIKit._internal_.FunctionComponent.html │ ├── Agora_UIKit._internal_.Iterable.html │ ├── Agora_UIKit._internal_.IterableIterator.html │ ├── Agora_UIKit._internal_.Iterator.html │ ├── Agora_UIKit._internal_.IteratorReturnResult.html │ ├── Agora_UIKit._internal_.IteratorYieldResult.html │ ├── Agora_UIKit._internal_.LocalInvitation.html │ ├── Agora_UIKit._internal_.MatrixTransform.html │ ├── Agora_UIKit._internal_.NewLifecycle.html │ ├── Agora_UIKit._internal_.PeersOnlineStatus.html │ ├── Agora_UIKit._internal_.PerpectiveTransform.html │ ├── Agora_UIKit._internal_.ProviderExoticComponent.html │ ├── Agora_UIKit._internal_.ProviderProps.html │ ├── Agora_UIKit._internal_.ReactElement.html │ ├── Agora_UIKit._internal_.ReactPortal.html │ ├── Agora_UIKit._internal_.RecursiveArray.html │ ├── Agora_UIKit._internal_.RemoteInvitation.html │ ├── Agora_UIKit._internal_.RotateTransform.html │ ├── Agora_UIKit._internal_.RotateXTransform.html │ ├── Agora_UIKit._internal_.RotateYTransform.html │ ├── Agora_UIKit._internal_.RotateZTransform.html │ ├── Agora_UIKit._internal_.RtcConnectionData.html │ ├── Agora_UIKit._internal_.RtcSettings.html │ ├── Agora_UIKit._internal_.RtmChannelMember.html │ ├── Agora_UIKit._internal_.RtmClientEvents.html │ ├── Agora_UIKit._internal_.RtmConnectionData.html │ ├── Agora_UIKit._internal_.RtmSettings.html │ ├── Agora_UIKit._internal_.ScaleTransform.html │ ├── Agora_UIKit._internal_.ScaleXTransform.html │ ├── Agora_UIKit._internal_.ScaleYTransform.html │ ├── Agora_UIKit._internal_.ShadowStyleIOS.html │ ├── Agora_UIKit._internal_.SkewXTransform.html │ ├── Agora_UIKit._internal_.SkewYTransform.html │ ├── Agora_UIKit._internal_.TextStyle.html │ ├── Agora_UIKit._internal_.TextStyleAndroid.html │ ├── Agora_UIKit._internal_.TextStyleIOS.html │ ├── Agora_UIKit._internal_.TransformsStyle.html │ ├── Agora_UIKit._internal_.TranslateXTransform.html │ ├── Agora_UIKit._internal_.TranslateYTransform.html │ ├── Agora_UIKit._internal_.Validator.html │ ├── Agora_UIKit._internal_.ViewStyle.html │ ├── Agora_UIKit._internal_.localBtnStylesInterface.html │ ├── Agora_UIKit._internal_.remoteBtnStylesInterface.html │ ├── Built_in_Components._internal_.AbstractView.html │ ├── Built_in_Components._internal_.AnchorHTMLAttributes.html │ ├── Built_in_Components._internal_.AnimationEvent-1.html │ ├── Built_in_Components._internal_.AnimationEvent.html │ ├── Built_in_Components._internal_.AreaHTMLAttributes.html │ ├── Built_in_Components._internal_.AriaAttributes.html │ ├── Built_in_Components._internal_.Attributes.html │ ├── Built_in_Components._internal_.AudioHTMLAttributes.html │ ├── Built_in_Components._internal_.BaseHTMLAttributes.html │ ├── Built_in_Components._internal_.BaseInterface.html │ ├── Built_in_Components._internal_.BaseInterfaceWithIcon.html │ ├── Built_in_Components._internal_.BaseInterfaceWithName.html │ ├── Built_in_Components._internal_.BaseSyntheticEvent.html │ ├── Built_in_Components._internal_.BlockquoteHTMLAttributes.html │ ├── Built_in_Components._internal_.BtnTemplateBasicInterface.html │ ├── Built_in_Components._internal_.BtnTemplateInterfaceWithIcon.html │ ├── Built_in_Components._internal_.BtnTemplateInterfaceWithName.html │ ├── Built_in_Components._internal_.ButtonHTMLAttributes.html │ ├── Built_in_Components._internal_.CSSProperties.html │ ├── Built_in_Components._internal_.CanvasHTMLAttributes.html │ ├── Built_in_Components._internal_.ChangeEvent.html │ ├── Built_in_Components._internal_.ClassAttributes.html │ ├── Built_in_Components._internal_.ClipboardEvent-1.html │ ├── Built_in_Components._internal_.ClipboardEvent.html │ ├── Built_in_Components._internal_.ColHTMLAttributes.html │ ├── Built_in_Components._internal_.ColgroupHTMLAttributes.html │ ├── Built_in_Components._internal_.ComponentClass.html │ ├── Built_in_Components._internal_.CompositionEvent-1.html │ ├── Built_in_Components._internal_.CompositionEvent.html │ ├── Built_in_Components._internal_.ControlsPropsInterface.html │ ├── Built_in_Components._internal_.DOMAttributes.html │ ├── Built_in_Components._internal_.DataHTMLAttributes.html │ ├── Built_in_Components._internal_.DataTransfer.html │ ├── Built_in_Components._internal_.DelHTMLAttributes.html │ ├── Built_in_Components._internal_.DetailsHTMLAttributes.html │ ├── Built_in_Components._internal_.DialogHTMLAttributes.html │ ├── Built_in_Components._internal_.Document.html │ ├── Built_in_Components._internal_.DragEvent-1.html │ ├── Built_in_Components._internal_.DragEvent.html │ ├── Built_in_Components._internal_.EmbedHTMLAttributes.html │ ├── Built_in_Components._internal_.Event.html │ ├── Built_in_Components._internal_.EventTarget.html │ ├── Built_in_Components._internal_.FieldsetHTMLAttributes.html │ ├── Built_in_Components._internal_.FocusEvent-1.html │ ├── Built_in_Components._internal_.FocusEvent.html │ ├── Built_in_Components._internal_.FormEvent.html │ ├── Built_in_Components._internal_.FormHTMLAttributes.html │ ├── Built_in_Components._internal_.ForwardRefExoticComponent.html │ ├── Built_in_Components._internal_.GestureResponderEvent.html │ ├── Built_in_Components._internal_.HTMLAnchorElement.html │ ├── Built_in_Components._internal_.HTMLAreaElement.html │ ├── Built_in_Components._internal_.HTMLAttributes.html │ ├── Built_in_Components._internal_.HTMLAudioElement.html │ ├── Built_in_Components._internal_.HTMLBRElement.html │ ├── Built_in_Components._internal_.HTMLBaseElement.html │ ├── Built_in_Components._internal_.HTMLBodyElement.html │ ├── Built_in_Components._internal_.HTMLButtonElement.html │ ├── Built_in_Components._internal_.HTMLCanvasElement.html │ ├── Built_in_Components._internal_.HTMLDListElement.html │ ├── Built_in_Components._internal_.HTMLDataElement.html │ ├── Built_in_Components._internal_.HTMLDataListElement.html │ ├── Built_in_Components._internal_.HTMLDetailsElement.html │ ├── Built_in_Components._internal_.HTMLDialogElement.html │ ├── Built_in_Components._internal_.HTMLDivElement.html │ ├── Built_in_Components._internal_.HTMLElement.html │ ├── Built_in_Components._internal_.HTMLEmbedElement.html │ ├── Built_in_Components._internal_.HTMLFieldSetElement.html │ ├── Built_in_Components._internal_.HTMLFormElement.html │ ├── Built_in_Components._internal_.HTMLHRElement.html │ ├── Built_in_Components._internal_.HTMLHeadElement.html │ ├── Built_in_Components._internal_.HTMLHeadingElement.html │ ├── Built_in_Components._internal_.HTMLHtmlElement.html │ ├── Built_in_Components._internal_.HTMLIFrameElement.html │ ├── Built_in_Components._internal_.HTMLImageElement.html │ ├── Built_in_Components._internal_.HTMLInputElement.html │ ├── Built_in_Components._internal_.HTMLLIElement.html │ ├── Built_in_Components._internal_.HTMLLabelElement.html │ ├── Built_in_Components._internal_.HTMLLegendElement.html │ ├── Built_in_Components._internal_.HTMLLinkElement.html │ ├── Built_in_Components._internal_.HTMLMapElement.html │ ├── Built_in_Components._internal_.HTMLMetaElement.html │ ├── Built_in_Components._internal_.HTMLMeterElement.html │ ├── Built_in_Components._internal_.HTMLModElement.html │ ├── Built_in_Components._internal_.HTMLOListElement.html │ ├── Built_in_Components._internal_.HTMLObjectElement.html │ ├── Built_in_Components._internal_.HTMLOptGroupElement.html │ ├── Built_in_Components._internal_.HTMLOptionElement.html │ ├── Built_in_Components._internal_.HTMLOutputElement.html │ ├── Built_in_Components._internal_.HTMLParagraphElement.html │ ├── Built_in_Components._internal_.HTMLParamElement.html │ ├── Built_in_Components._internal_.HTMLPreElement.html │ ├── Built_in_Components._internal_.HTMLProgressElement.html │ ├── Built_in_Components._internal_.HTMLQuoteElement.html │ ├── Built_in_Components._internal_.HTMLScriptElement.html │ ├── Built_in_Components._internal_.HTMLSelectElement.html │ ├── Built_in_Components._internal_.HTMLSlotElement.html │ ├── Built_in_Components._internal_.HTMLSourceElement.html │ ├── Built_in_Components._internal_.HTMLSpanElement.html │ ├── Built_in_Components._internal_.HTMLStyleElement.html │ ├── Built_in_Components._internal_.HTMLTableColElement.html │ ├── Built_in_Components._internal_.HTMLTableDataCellElement.html │ ├── Built_in_Components._internal_.HTMLTableElement.html │ ├── Built_in_Components._internal_.HTMLTableHeaderCellElement.html │ ├── Built_in_Components._internal_.HTMLTableRowElement.html │ ├── Built_in_Components._internal_.HTMLTableSectionElement.html │ ├── Built_in_Components._internal_.HTMLTemplateElement.html │ ├── Built_in_Components._internal_.HTMLTextAreaElement.html │ ├── Built_in_Components._internal_.HTMLTimeElement.html │ ├── Built_in_Components._internal_.HTMLTitleElement.html │ ├── Built_in_Components._internal_.HTMLTrackElement.html │ ├── Built_in_Components._internal_.HTMLUListElement.html │ ├── Built_in_Components._internal_.HTMLVideoElement.html │ ├── Built_in_Components._internal_.HTMLWebViewElement.html │ ├── Built_in_Components._internal_.HostComponent.html │ ├── Built_in_Components._internal_.HtmlHTMLAttributes.html │ ├── Built_in_Components._internal_.IframeHTMLAttributes.html │ ├── Built_in_Components._internal_.ImgHTMLAttributes.html │ ├── Built_in_Components._internal_.InputHTMLAttributes.html │ ├── Built_in_Components._internal_.InsHTMLAttributes.html │ ├── Built_in_Components._internal_.IntrinsicElements.html │ ├── Built_in_Components._internal_.KeyboardEvent-1.html │ ├── Built_in_Components._internal_.KeyboardEvent.html │ ├── Built_in_Components._internal_.KeygenHTMLAttributes.html │ ├── Built_in_Components._internal_.LabelHTMLAttributes.html │ ├── Built_in_Components._internal_.LiHTMLAttributes.html │ ├── Built_in_Components._internal_.LinkHTMLAttributes.html │ ├── Built_in_Components._internal_.LocalAudioMuteProps.html │ ├── Built_in_Components._internal_.LocalUserContextInterface.html │ ├── Built_in_Components._internal_.LocalVideoMuteProps.html │ ├── Built_in_Components._internal_.MapHTMLAttributes.html │ ├── Built_in_Components._internal_.MaxViewInterface.html │ ├── Built_in_Components._internal_.MediaHTMLAttributes.html │ ├── Built_in_Components._internal_.MenuHTMLAttributes.html │ ├── Built_in_Components._internal_.MetaHTMLAttributes.html │ ├── Built_in_Components._internal_.MeterHTMLAttributes.html │ ├── Built_in_Components._internal_.MinViewInterface.html │ ├── Built_in_Components._internal_.MouseEvent-1.html │ ├── Built_in_Components._internal_.MouseEvent.html │ ├── Built_in_Components._internal_.MutableRefObject.html │ ├── Built_in_Components._internal_.NamedExoticComponent.html │ ├── Built_in_Components._internal_.NativeMethods.html │ ├── Built_in_Components._internal_.NativeSyntheticEvent.html │ ├── Built_in_Components._internal_.NativeTouchEvent.html │ ├── Built_in_Components._internal_.ObjectHTMLAttributes.html │ ├── Built_in_Components._internal_.ObsoleteProperties.html │ ├── Built_in_Components._internal_.OlHTMLAttributes.html │ ├── Built_in_Components._internal_.OptgroupHTMLAttributes.html │ ├── Built_in_Components._internal_.OptionHTMLAttributes.html │ ├── Built_in_Components._internal_.OutputHTMLAttributes.html │ ├── Built_in_Components._internal_.ParamHTMLAttributes.html │ ├── Built_in_Components._internal_.PointerEvent-1.html │ ├── Built_in_Components._internal_.PointerEvent.html │ ├── Built_in_Components._internal_.ProgressHTMLAttributes.html │ ├── Built_in_Components._internal_.Properties.html │ ├── Built_in_Components._internal_.QuoteHTMLAttributes.html │ ├── Built_in_Components._internal_.RefAttributes.html │ ├── Built_in_Components._internal_.RefObject.html │ ├── Built_in_Components._internal_.RemoteAudioMuteInterface.html │ ├── Built_in_Components._internal_.RemoteControlsInterface.html │ ├── Built_in_Components._internal_.RemoteSwapInterface.html │ ├── Built_in_Components._internal_.RemoteVideoMuteInterface.html │ ├── Built_in_Components._internal_.RtcPropsInterface.html │ ├── Built_in_Components._internal_.SVGAttributes.html │ ├── Built_in_Components._internal_.SVGCircleElement.html │ ├── Built_in_Components._internal_.SVGClipPathElement.html │ ├── Built_in_Components._internal_.SVGDefsElement.html │ ├── Built_in_Components._internal_.SVGDescElement.html │ ├── Built_in_Components._internal_.SVGElement.html │ ├── Built_in_Components._internal_.SVGEllipseElement.html │ ├── Built_in_Components._internal_.SVGFEBlendElement.html │ ├── Built_in_Components._internal_.SVGFEColorMatrixElement.html │ ├── Built_in_Components._internal_.SVGFEComponentTransferElement.html │ ├── Built_in_Components._internal_.SVGFECompositeElement.html │ ├── Built_in_Components._internal_.SVGFEConvolveMatrixElement.html │ ├── Built_in_Components._internal_.SVGFEDiffuseLightingElement.html │ ├── Built_in_Components._internal_.SVGFEDisplacementMapElement.html │ ├── Built_in_Components._internal_.SVGFEDistantLightElement.html │ ├── Built_in_Components._internal_.SVGFEDropShadowElement.html │ ├── Built_in_Components._internal_.SVGFEFloodElement.html │ ├── Built_in_Components._internal_.SVGFEFuncAElement.html │ ├── Built_in_Components._internal_.SVGFEFuncBElement.html │ ├── Built_in_Components._internal_.SVGFEFuncGElement.html │ ├── Built_in_Components._internal_.SVGFEFuncRElement.html │ ├── Built_in_Components._internal_.SVGFEGaussianBlurElement.html │ ├── Built_in_Components._internal_.SVGFEImageElement.html │ ├── Built_in_Components._internal_.SVGFEMergeElement.html │ ├── Built_in_Components._internal_.SVGFEMergeNodeElement.html │ ├── Built_in_Components._internal_.SVGFEMorphologyElement.html │ ├── Built_in_Components._internal_.SVGFEOffsetElement.html │ ├── Built_in_Components._internal_.SVGFEPointLightElement.html │ ├── Built_in_Components._internal_.SVGFESpecularLightingElement.html │ ├── Built_in_Components._internal_.SVGFESpotLightElement.html │ ├── Built_in_Components._internal_.SVGFETileElement.html │ ├── Built_in_Components._internal_.SVGFETurbulenceElement.html │ ├── Built_in_Components._internal_.SVGFilterElement.html │ ├── Built_in_Components._internal_.SVGForeignObjectElement.html │ ├── Built_in_Components._internal_.SVGGElement.html │ ├── Built_in_Components._internal_.SVGImageElement.html │ ├── Built_in_Components._internal_.SVGLineElement.html │ ├── Built_in_Components._internal_.SVGLinearGradientElement.html │ ├── Built_in_Components._internal_.SVGMarkerElement.html │ ├── Built_in_Components._internal_.SVGMaskElement.html │ ├── Built_in_Components._internal_.SVGMetadataElement.html │ ├── Built_in_Components._internal_.SVGPathElement.html │ ├── Built_in_Components._internal_.SVGPatternElement.html │ ├── Built_in_Components._internal_.SVGPolygonElement.html │ ├── Built_in_Components._internal_.SVGPolylineElement.html │ ├── Built_in_Components._internal_.SVGProps.html │ ├── Built_in_Components._internal_.SVGRadialGradientElement.html │ ├── Built_in_Components._internal_.SVGRectElement.html │ ├── Built_in_Components._internal_.SVGSVGElement.html │ ├── Built_in_Components._internal_.SVGStopElement.html │ ├── Built_in_Components._internal_.SVGSwitchElement.html │ ├── Built_in_Components._internal_.SVGSymbolElement.html │ ├── Built_in_Components._internal_.SVGTSpanElement.html │ ├── Built_in_Components._internal_.SVGTextElement.html │ ├── Built_in_Components._internal_.SVGTextPathElement.html │ ├── Built_in_Components._internal_.SVGUseElement.html │ ├── Built_in_Components._internal_.SVGViewElement.html │ ├── Built_in_Components._internal_.ScriptHTMLAttributes.html │ ├── Built_in_Components._internal_.SelectHTMLAttributes.html │ ├── Built_in_Components._internal_.SlotHTMLAttributes.html │ ├── Built_in_Components._internal_.SourceHTMLAttributes.html │ ├── Built_in_Components._internal_.StandardLonghandProperties.html │ ├── Built_in_Components._internal_.StandardProperties.html │ ├── Built_in_Components._internal_.StandardShorthandProperties.html │ ├── Built_in_Components._internal_.StaticLifecycle.html │ ├── Built_in_Components._internal_.StyleHTMLAttributes.html │ ├── Built_in_Components._internal_.StyleMedia.html │ ├── Built_in_Components._internal_.SvgProperties.html │ ├── Built_in_Components._internal_.SyntheticEvent.html │ ├── Built_in_Components._internal_.TableHTMLAttributes.html │ ├── Built_in_Components._internal_.TdHTMLAttributes.html │ ├── Built_in_Components._internal_.TextareaHTMLAttributes.html │ ├── Built_in_Components._internal_.ThHTMLAttributes.html │ ├── Built_in_Components._internal_.TimeHTMLAttributes.html │ ├── Built_in_Components._internal_.Touch.html │ ├── Built_in_Components._internal_.TouchEvent-1.html │ ├── Built_in_Components._internal_.TouchEvent.html │ ├── Built_in_Components._internal_.TouchList.html │ ├── Built_in_Components._internal_.TrackHTMLAttributes.html │ ├── Built_in_Components._internal_.TransitionEvent-1.html │ ├── Built_in_Components._internal_.TransitionEvent.html │ ├── Built_in_Components._internal_.UIEvent-1.html │ ├── Built_in_Components._internal_.UIEvent.html │ ├── Built_in_Components._internal_.UidStateInterface.html │ ├── Built_in_Components._internal_.VendorLonghandProperties.html │ ├── Built_in_Components._internal_.VendorProperties.html │ ├── Built_in_Components._internal_.VendorShorthandProperties.html │ ├── Built_in_Components._internal_.VideoHTMLAttributes.html │ ├── Built_in_Components._internal_.WebViewHTMLAttributes.html │ ├── Built_in_Components._internal_.WheelEvent-1.html │ ├── Built_in_Components._internal_.WheelEvent.html │ ├── React_Contexts._internal_.AgoraPeerMessage.html │ ├── React_Contexts._internal_.ChannelAttributeOptions.html │ ├── React_Contexts._internal_.ConnectionState.html │ ├── React_Contexts._internal_.EmitterSubscription.html │ ├── React_Contexts._internal_.EventSubscription.html │ ├── React_Contexts._internal_.ListPeerStatus.html │ ├── React_Contexts._internal_.LocalInvitation.html │ ├── React_Contexts._internal_.LocalInvitationProps.html │ ├── React_Contexts._internal_.MemberInfo.html │ ├── React_Contexts._internal_.MemberStatus.html │ ├── React_Contexts._internal_.Members.html │ ├── React_Contexts._internal_.Object.html │ ├── React_Contexts._internal_.PeersOnlineStatus.html │ ├── React_Contexts._internal_.PropsInterface.html │ ├── React_Contexts._internal_.RTMChannelMessage.html │ ├── React_Contexts._internal_.RTMLocalInvitationErrorMessage.html │ ├── React_Contexts._internal_.RTMLocalInvitationMessage.html │ ├── React_Contexts._internal_.RTMMemberInfo.html │ ├── React_Contexts._internal_.RTMPeerMessage.html │ ├── React_Contexts._internal_.RTMRemoteInvitationErrorMessage.html │ ├── React_Contexts._internal_.RTMRemoteInvitationMessage.html │ ├── React_Contexts._internal_.RemoteInvitation.html │ ├── React_Contexts._internal_.RemoteInvitationProps.html │ ├── React_Contexts._internal_.RtcContextInterface.html │ ├── React_Contexts._internal_.RtmChannelMember.html │ ├── React_Contexts._internal_.RtmClientEvents.html │ ├── React_Contexts._internal_.RtmEngineEvents.html │ ├── React_Contexts._internal_.RtmPropsInterface.html │ ├── React_Contexts._internal_.SendMessageOptions.html │ ├── React_Contexts._internal_.UserAttribute.html │ ├── React_Contexts._internal_.UserInfo.html │ ├── React_Contexts._internal_.UserProfile.html │ └── React_Contexts._internal_.rtmContext.html ├── modules.html └── modules │ ├── Agora_UIKit._internal_.html │ ├── Agora_UIKit.html │ ├── Built_in_Components._internal_.html │ ├── Built_in_Components.html │ ├── React_Contexts._internal_.html │ └── React_Contexts.html ├── index.ts ├── package-lock.json ├── package.json ├── src ├── AgoraUIKit.tsx ├── Contexts │ ├── LocalUserContext.tsx │ ├── MaxUidContext.tsx │ ├── MinUidContext.tsx │ ├── PropsContext.tsx │ ├── RtcContext.tsx │ └── RtmContext.tsx ├── Controls │ ├── BtnTemplate.tsx │ ├── Icons.ts │ ├── ImageIcon.tsx │ ├── Local │ │ ├── EndCall.tsx │ │ ├── LocalAudioMute.tsx │ │ ├── LocalVideoMute.tsx │ │ └── SwitchCamera.tsx │ ├── LocalControls.tsx │ ├── Remote │ │ ├── RemoteAudioMute.tsx │ │ ├── RemoteMutePopUp.tsx │ │ ├── RemoteSwap.tsx │ │ └── RemoteVideoMute.tsx │ ├── RemoteControls.tsx │ └── types.ts ├── RTMEngine.tsx ├── Reducer │ ├── BecomeAudience.ts │ ├── LocalMuteAudio.ts │ ├── LocalMuteVideo.ts │ ├── RemoteAudioStateChanged.ts │ ├── RemoteVideoStateChanged.ts │ ├── UpdateDualStreamMode.ts │ ├── UserJoined.ts │ ├── UserMuteRemoteAudio.ts │ ├── UserMuteRemoteVideo.ts │ ├── UserOffline.ts │ └── index.ts ├── Rtc │ ├── Create.tsx │ └── Join.tsx ├── RtcConfigure.tsx ├── RtmConfigure.tsx ├── Style.ts ├── Utils │ ├── actionTypeGuard.tsx │ ├── isSafariBrowser.ts │ ├── permission.ts │ └── quality.tsx ├── Views │ ├── GridVideo.tsx │ ├── MaxVideoView.native.tsx │ ├── MaxVideoView.tsx │ ├── MinVideoView.tsx │ ├── PinnedVideo.tsx │ └── Usernames.tsx └── hooks │ └── useImageDelay.tsx └── tsconfig.json /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: '@react-native-community', 4 | parser: '@typescript-eslint/parser', 5 | plugins: ['@typescript-eslint'], 6 | overrides: [ 7 | { 8 | files: ['*.ts', '*.tsx'], 9 | rules: { 10 | '@typescript-eslint/no-shadow': ['error'], 11 | 'react-native/no-inline-styles': 'off', 12 | 'no-shadow': 'off', 13 | 'no-undef': 'off', 14 | }, 15 | }, 16 | ], 17 | }; 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/issue-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Issue report 3 | about: Report any issues while trying to use the UIKit 4 | title: '' 5 | labels: '' 6 | --- 7 | 8 | **Target device:** (Note: simulators are not supported for running the UIKit) 9 | - Device: [e.g. iPhone6] 10 | - OS: 11 | - [X] IOS 12 | - [ ] Android 13 | - OS Version: [e.g. 22, 14.1] 14 | 15 | **App Info** 16 | - Type: 17 | - [X] React Native CLI 18 | - [ ] Expo CLI (using custom development clients) 19 | - CLI Version: [e.g. 22] 20 | 21 | **Describe the issue** 22 | A clear and concise description of what the issue is. 23 | 24 | **To Reproduce** 25 | Steps to reproduce the behavior: 26 | 1. Go to '...' 27 | 2. Click on '....' 28 | 3. See error 29 | 30 | **Screenshots** 31 | If applicable, add screenshots to help explain your problem. 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | 24 | # Android/IntelliJ 25 | # 26 | build/ 27 | .idea 28 | .gradle 29 | local.properties 30 | *.iml 31 | 32 | # Visual Studio Code 33 | # 34 | .vscode/ 35 | 36 | # node.js 37 | # 38 | node_modules/ 39 | npm-debug.log 40 | yarn-error.log 41 | 42 | # BUCK 43 | buck-out/ 44 | \.buckd/ 45 | *.keystore 46 | !debug.keystore 47 | 48 | # fastlane 49 | # 50 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 51 | # screenshots whenever they are needed. 52 | # For more information about the recommended setup visit: 53 | # https://docs.fastlane.tools/best-practices/source-control/ 54 | 55 | */fastlane/report.xml 56 | */fastlane/Preview.html 57 | */fastlane/screenshots 58 | 59 | # Bundle artifact 60 | *.jsbundle 61 | 62 | # CocoaPods 63 | /ios/Pods/ 64 | 65 | example 66 | notes.md -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | bracketSpacing: false, 3 | jsxBracketSameLine: true, 4 | singleQuote: true, 5 | trailingComma: 'all', 6 | }; 7 | -------------------------------------------------------------------------------- /Components.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @module Built-in Components 3 | */ 4 | import Endcall from './src/Controls/Local/EndCall'; 5 | import LocalAudioMute from './src/Controls/Local/LocalAudioMute'; 6 | import LocalVideoMute from './src/Controls/Local/LocalVideoMute'; 7 | import SwitchCamera from './src/Controls/Local/SwitchCamera'; 8 | import LocalControls from './src/Controls/LocalControls'; 9 | import RemoteAudioMute from './src/Controls/Remote/RemoteAudioMute'; 10 | import RemoteSwap from './src/Controls/Remote/RemoteSwap'; 11 | import RemoteVideoMute from './src/Controls/Remote/RemoteVideoMute'; 12 | import RemoteControls from './src/Controls/RemoteControls'; 13 | import MaxVideoView from './src/Views/MaxVideoView'; 14 | import MinVideoView from './src/Views/MinVideoView'; 15 | import RtcConfigure from './src/RtcConfigure'; 16 | import RtmConfigure from './src/RtmConfigure'; 17 | import LocalUserContext from './src/Contexts/LocalUserContext'; 18 | import BtnTemplate from './src/Controls/BtnTemplate'; 19 | import PinnedVideo from './src/Views/PinnedVideo'; 20 | import GridVideo from './src/Views/GridVideo'; 21 | import ImageIcon from './src/Controls/ImageIcon'; 22 | import Create from './src/Rtc/Create'; 23 | import Join from './src/Rtc/Join'; 24 | import Username from './src/Views/Usernames'; 25 | export { 26 | Endcall, 27 | LocalAudioMute, 28 | LocalVideoMute, 29 | SwitchCamera, 30 | LocalControls, 31 | RemoteAudioMute, 32 | RemoteSwap, 33 | RemoteVideoMute, 34 | RemoteControls, 35 | Create, 36 | Join, 37 | MaxVideoView, 38 | MinVideoView, 39 | RtcConfigure, 40 | RtmConfigure, 41 | LocalUserContext, 42 | BtnTemplate, 43 | PinnedVideo, 44 | GridVideo, 45 | Username, 46 | ImageIcon, 47 | }; 48 | -------------------------------------------------------------------------------- /Contexts.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @module React Contexts 3 | */ 4 | import MaxUidContext from './src/Contexts/MaxUidContext'; 5 | import MinUidContext from './src/Contexts/MinUidContext'; 6 | import PropsContext from './src/Contexts/PropsContext'; 7 | import RtcContext from './src/Contexts/RtcContext'; 8 | import RtmContext from './src/Contexts/RtmContext'; 9 | import {LocalContext} from './src/Contexts/LocalUserContext'; 10 | 11 | export { 12 | MaxUidContext, 13 | MinUidContext, 14 | PropsContext, 15 | RtcContext, 16 | RtmContext, 17 | LocalContext, 18 | }; 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Agora.io Community 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /UI Kit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/VideoUIKit-ReactNative/4f7b24ae23725058dd189973689b6db7a860ee36/UI Kit.png -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- 1 | TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. -------------------------------------------------------------------------------- /docs/UI Kit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/VideoUIKit-ReactNative/4f7b24ae23725058dd189973689b6db7a860ee36/docs/UI Kit.png -------------------------------------------------------------------------------- /docs/assets/highlight.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --light-hl-0: #001080; 3 | --dark-hl-0: #9CDCFE; 4 | --light-hl-1: #000000; 5 | --dark-hl-1: #D4D4D4; 6 | --light-hl-2: #AF00DB; 7 | --dark-hl-2: #C586C0; 8 | --light-hl-3: #A31515; 9 | --dark-hl-3: #CE9178; 10 | --light-hl-4: #0000FF; 11 | --dark-hl-4: #569CD6; 12 | --light-hl-5: #795E26; 13 | --dark-hl-5: #DCDCAA; 14 | --light-hl-6: #0070C1; 15 | --dark-hl-6: #4FC1FF; 16 | --light-hl-7: #800000; 17 | --dark-hl-7: #808080; 18 | --light-hl-8: #267F99; 19 | --dark-hl-8: #4EC9B0; 20 | --light-hl-9: #FF0000; 21 | --dark-hl-9: #9CDCFE; 22 | --light-hl-10: #000000FF; 23 | --dark-hl-10: #D4D4D4; 24 | --light-hl-11: #008000; 25 | --dark-hl-11: #6A9955; 26 | --light-hl-12: #000000; 27 | --dark-hl-12: #C8C8C8; 28 | --light-hl-13: #098658; 29 | --dark-hl-13: #B5CEA8; 30 | --light-hl-14: #CD3131; 31 | --dark-hl-14: #F44747; 32 | --light-code-background: #F5F5F5; 33 | --dark-code-background: #1E1E1E; 34 | } 35 | 36 | @media (prefers-color-scheme: light) { :root { 37 | --hl-0: var(--light-hl-0); 38 | --hl-1: var(--light-hl-1); 39 | --hl-2: var(--light-hl-2); 40 | --hl-3: var(--light-hl-3); 41 | --hl-4: var(--light-hl-4); 42 | --hl-5: var(--light-hl-5); 43 | --hl-6: var(--light-hl-6); 44 | --hl-7: var(--light-hl-7); 45 | --hl-8: var(--light-hl-8); 46 | --hl-9: var(--light-hl-9); 47 | --hl-10: var(--light-hl-10); 48 | --hl-11: var(--light-hl-11); 49 | --hl-12: var(--light-hl-12); 50 | --hl-13: var(--light-hl-13); 51 | --hl-14: var(--light-hl-14); 52 | --code-background: var(--light-code-background); 53 | } } 54 | 55 | @media (prefers-color-scheme: dark) { :root { 56 | --hl-0: var(--dark-hl-0); 57 | --hl-1: var(--dark-hl-1); 58 | --hl-2: var(--dark-hl-2); 59 | --hl-3: var(--dark-hl-3); 60 | --hl-4: var(--dark-hl-4); 61 | --hl-5: var(--dark-hl-5); 62 | --hl-6: var(--dark-hl-6); 63 | --hl-7: var(--dark-hl-7); 64 | --hl-8: var(--dark-hl-8); 65 | --hl-9: var(--dark-hl-9); 66 | --hl-10: var(--dark-hl-10); 67 | --hl-11: var(--dark-hl-11); 68 | --hl-12: var(--dark-hl-12); 69 | --hl-13: var(--dark-hl-13); 70 | --hl-14: var(--dark-hl-14); 71 | --code-background: var(--dark-code-background); 72 | } } 73 | 74 | body.light { 75 | --hl-0: var(--light-hl-0); 76 | --hl-1: var(--light-hl-1); 77 | --hl-2: var(--light-hl-2); 78 | --hl-3: var(--light-hl-3); 79 | --hl-4: var(--light-hl-4); 80 | --hl-5: var(--light-hl-5); 81 | --hl-6: var(--light-hl-6); 82 | --hl-7: var(--light-hl-7); 83 | --hl-8: var(--light-hl-8); 84 | --hl-9: var(--light-hl-9); 85 | --hl-10: var(--light-hl-10); 86 | --hl-11: var(--light-hl-11); 87 | --hl-12: var(--light-hl-12); 88 | --hl-13: var(--light-hl-13); 89 | --hl-14: var(--light-hl-14); 90 | --code-background: var(--light-code-background); 91 | } 92 | 93 | body.dark { 94 | --hl-0: var(--dark-hl-0); 95 | --hl-1: var(--dark-hl-1); 96 | --hl-2: var(--dark-hl-2); 97 | --hl-3: var(--dark-hl-3); 98 | --hl-4: var(--dark-hl-4); 99 | --hl-5: var(--dark-hl-5); 100 | --hl-6: var(--dark-hl-6); 101 | --hl-7: var(--dark-hl-7); 102 | --hl-8: var(--dark-hl-8); 103 | --hl-9: var(--dark-hl-9); 104 | --hl-10: var(--dark-hl-10); 105 | --hl-11: var(--dark-hl-11); 106 | --hl-12: var(--dark-hl-12); 107 | --hl-13: var(--dark-hl-13); 108 | --hl-14: var(--dark-hl-14); 109 | --code-background: var(--dark-code-background); 110 | } 111 | 112 | .hl-0 { color: var(--hl-0); } 113 | .hl-1 { color: var(--hl-1); } 114 | .hl-2 { color: var(--hl-2); } 115 | .hl-3 { color: var(--hl-3); } 116 | .hl-4 { color: var(--hl-4); } 117 | .hl-5 { color: var(--hl-5); } 118 | .hl-6 { color: var(--hl-6); } 119 | .hl-7 { color: var(--hl-7); } 120 | .hl-8 { color: var(--hl-8); } 121 | .hl-9 { color: var(--hl-9); } 122 | .hl-10 { color: var(--hl-10); } 123 | .hl-11 { color: var(--hl-11); } 124 | .hl-12 { color: var(--hl-12); } 125 | .hl-13 { color: var(--hl-13); } 126 | .hl-14 { color: var(--hl-14); } 127 | pre, code { background: var(--code-background); } 128 | -------------------------------------------------------------------------------- /docs/assets/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/VideoUIKit-ReactNative/4f7b24ae23725058dd189973689b6db7a860ee36/docs/assets/icons.png -------------------------------------------------------------------------------- /docs/assets/icons@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/VideoUIKit-ReactNative/4f7b24ae23725058dd189973689b6db7a860ee36/docs/assets/icons@2x.png -------------------------------------------------------------------------------- /docs/assets/widgets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/VideoUIKit-ReactNative/4f7b24ae23725058dd189973689b6db7a860ee36/docs/assets/widgets.png -------------------------------------------------------------------------------- /docs/assets/widgets@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/VideoUIKit-ReactNative/4f7b24ae23725058dd189973689b6db7a860ee36/docs/assets/widgets@2x.png -------------------------------------------------------------------------------- /docs/interfaces/Built_in_Components._internal_.AnimationEvent-1.html: -------------------------------------------------------------------------------- 1 | AnimationEvent | agora-rn-uikit
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/Built_in_Components._internal_.ClipboardEvent-1.html: -------------------------------------------------------------------------------- 1 | ClipboardEvent | agora-rn-uikit
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/Built_in_Components._internal_.DataTransfer.html: -------------------------------------------------------------------------------- 1 | DataTransfer | agora-rn-uikit
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

  • DataTransfer

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/Built_in_Components._internal_.Document.html: -------------------------------------------------------------------------------- 1 | Document | agora-rn-uikit
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

  • Document

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/Built_in_Components._internal_.DragEvent-1.html: -------------------------------------------------------------------------------- 1 | DragEvent | agora-rn-uikit
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/Built_in_Components._internal_.EventTarget.html: -------------------------------------------------------------------------------- 1 | EventTarget | agora-rn-uikit
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

  • EventTarget

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/Built_in_Components._internal_.FocusEvent-1.html: -------------------------------------------------------------------------------- 1 | FocusEvent | agora-rn-uikit
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/Built_in_Components._internal_.HTMLBRElement.html: -------------------------------------------------------------------------------- 1 | HTMLBRElement | agora-rn-uikit
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/Built_in_Components._internal_.HTMLDivElement.html: -------------------------------------------------------------------------------- 1 | HTMLDivElement | agora-rn-uikit
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/Built_in_Components._internal_.HTMLHRElement.html: -------------------------------------------------------------------------------- 1 | HTMLHRElement | agora-rn-uikit
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/Built_in_Components._internal_.HTMLLIElement.html: -------------------------------------------------------------------------------- 1 | HTMLLIElement | agora-rn-uikit
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/Built_in_Components._internal_.HTMLMapElement.html: -------------------------------------------------------------------------------- 1 | HTMLMapElement | agora-rn-uikit
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/Built_in_Components._internal_.HTMLModElement.html: -------------------------------------------------------------------------------- 1 | HTMLModElement | agora-rn-uikit
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/Built_in_Components._internal_.HTMLPreElement.html: -------------------------------------------------------------------------------- 1 | HTMLPreElement | agora-rn-uikit
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/Built_in_Components._internal_.KeyboardEvent-1.html: -------------------------------------------------------------------------------- 1 | KeyboardEvent | agora-rn-uikit
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/Built_in_Components._internal_.MouseEvent-1.html: -------------------------------------------------------------------------------- 1 | MouseEvent | agora-rn-uikit
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/Built_in_Components._internal_.PointerEvent-1.html: -------------------------------------------------------------------------------- 1 | PointerEvent | agora-rn-uikit
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/Built_in_Components._internal_.SVGDefsElement.html: -------------------------------------------------------------------------------- 1 | SVGDefsElement | agora-rn-uikit
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/Built_in_Components._internal_.SVGDescElement.html: -------------------------------------------------------------------------------- 1 | SVGDescElement | agora-rn-uikit
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/Built_in_Components._internal_.SVGGElement.html: -------------------------------------------------------------------------------- 1 | SVGGElement | agora-rn-uikit
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/Built_in_Components._internal_.SVGLineElement.html: -------------------------------------------------------------------------------- 1 | SVGLineElement | agora-rn-uikit
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/Built_in_Components._internal_.SVGMaskElement.html: -------------------------------------------------------------------------------- 1 | SVGMaskElement | agora-rn-uikit
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/Built_in_Components._internal_.SVGPathElement.html: -------------------------------------------------------------------------------- 1 | SVGPathElement | agora-rn-uikit
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/Built_in_Components._internal_.SVGRectElement.html: -------------------------------------------------------------------------------- 1 | SVGRectElement | agora-rn-uikit
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/Built_in_Components._internal_.SVGSVGElement.html: -------------------------------------------------------------------------------- 1 | SVGSVGElement | agora-rn-uikit
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/Built_in_Components._internal_.SVGStopElement.html: -------------------------------------------------------------------------------- 1 | SVGStopElement | agora-rn-uikit
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/Built_in_Components._internal_.SVGUseElement.html: -------------------------------------------------------------------------------- 1 | SVGUseElement | agora-rn-uikit
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/Built_in_Components._internal_.StyleMedia.html: -------------------------------------------------------------------------------- 1 | StyleMedia | agora-rn-uikit
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

  • StyleMedia

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/Built_in_Components._internal_.TouchEvent-1.html: -------------------------------------------------------------------------------- 1 | TouchEvent | agora-rn-uikit
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/Built_in_Components._internal_.UIEvent-1.html: -------------------------------------------------------------------------------- 1 | UIEvent | agora-rn-uikit
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/Built_in_Components._internal_.WheelEvent-1.html: -------------------------------------------------------------------------------- 1 | WheelEvent | agora-rn-uikit
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/modules.html: -------------------------------------------------------------------------------- 1 | agora-rn-uikit
Options
All
  • Public
  • Public/Protected
  • All
Menu

agora-rn-uikit

Generated using TypeDoc

-------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @module Agora UIKit 3 | */ 4 | import AgoraUIKit from './src/AgoraUIKit'; 5 | export { 6 | StreamFallbackOptions, 7 | RenderModeType, 8 | ChannelProfileType, 9 | ClientRoleType, 10 | } from 'react-native-agora'; 11 | export {Layout, DualStreamMode, ToggleState} from './src/Contexts/PropsContext'; 12 | export {default as icons} from './src/Controls/Icons'; 13 | export type { 14 | IconsInterface, 15 | UidInterface, 16 | AgoraUIKitProps, 17 | ConnectionData, 18 | Settings, 19 | StylePropInterface, 20 | rtcCallbacks, 21 | } from './src/Contexts/PropsContext'; 22 | export type {rtmCallbacks} from './src/Contexts/RtmContext'; 23 | 24 | export default AgoraUIKit; 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "agora-rn-uikit", 3 | "version": "5.0.2", 4 | "author": { 5 | "name": "Ekaansh Arora", 6 | "email": "ekaansh@agora.io" 7 | }, 8 | "main": "./index.ts", 9 | "contributors": [ 10 | { 11 | "name": "Vineeth S", 12 | "email": "vineeth@agora.io" 13 | } 14 | ], 15 | "license": "MIT", 16 | "files": [ 17 | "Components.ts", 18 | "Contexts.ts", 19 | "index.ts", 20 | "/src/**/*" 21 | ], 22 | "description": "Agora react native UI KIT. Rapidly integrate video calling into your React Native application with built in UI Elements.", 23 | "repository": { 24 | "type": "git", 25 | "url": "https://github.com/AgoraIO-Community/ReactNative-UIKit" 26 | }, 27 | "bugs": { 28 | "url": "https://github.com/AgoraIO-Community/ReactNative-UIKit/issues" 29 | }, 30 | "peerDependencies": { 31 | "agora-react-native-rtm": "^1.5.1", 32 | "react": "*", 33 | "react-native": ">=0.62.2", 34 | "react-native-agora": "^4.1.0" 35 | }, 36 | "scripts": { 37 | "docs": "typedoc --entryPoints index.ts --entryPoints Components.ts --entryPoints Contexts.ts && cp 'UI Kit.png' './docs/UI Kit.png'" 38 | }, 39 | "devDependencies": { 40 | "@react-native-community/eslint-config": "^3.1.0", 41 | "@tsconfig/react-native": "^2.0.2", 42 | "@types/react": "^18.0.21", 43 | "@types/react-native": "^0.70.6", 44 | "@typescript-eslint/eslint-plugin": "^5.40.1", 45 | "@typescript-eslint/parser": "^5.40.1", 46 | "eslint": "^8.25.0", 47 | "typedoc": "^0.22.15", 48 | "typedoc-plugin-missing-exports": "^0.22.6", 49 | "typedoc-plugin-rename-defaults": "^0.6.4" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/AgoraUIKit.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @module AgoraUIKit 3 | */ 4 | import React from 'react'; 5 | import {View} from 'react-native'; 6 | import RtcConfigure from './RtcConfigure'; 7 | import { 8 | PropsProvider, 9 | PropsInterface, 10 | Layout, 11 | AgoraUIKitProps, 12 | } from './Contexts/PropsContext'; 13 | import LocalControls from './Controls/LocalControls'; 14 | import GridVideo from './Views/GridVideo'; 15 | import PinnedVideo from './Views/PinnedVideo'; 16 | import RtmConfigure from './RtmConfigure'; 17 | import LocalUserContext from './Contexts/LocalUserContext'; 18 | import PopUp from './Controls/Remote/RemoteMutePopUp'; 19 | 20 | /** 21 | * Agora UIKit component following the v3 props 22 | * @returns Renders the UIKit 23 | */ 24 | const AgoraUIKitv3: React.FC = (props) => { 25 | const {layout} = props.rtcProps; 26 | return ( 27 | 28 | 29 | 30 | 31 | {props.rtcProps.disableRtm ? ( 32 | <> 33 | {layout === Layout.Grid ? : } 34 | 35 | 36 | ) : ( 37 | 38 | {layout === Layout.Grid ? : } 39 | 40 | 41 | 42 | )} 43 | 44 | 45 | 46 | 47 | ); 48 | }; 49 | 50 | /** 51 | * Agora UIKit component 52 | * @returns Renders the UIKit 53 | */ 54 | const AgoraUIKit: React.FC = (props) => { 55 | const {rtcUid, rtcToken, rtmToken, rtmUid, ...restConnectonData} = 56 | props.connectionData; 57 | const adaptedProps: PropsInterface = { 58 | rtcProps: { 59 | uid: rtcUid, 60 | token: rtcToken, 61 | ...restConnectonData, 62 | ...props.settings, 63 | callActive: true, 64 | }, 65 | rtmProps: { 66 | token: rtmToken, 67 | uid: rtmUid, 68 | ...restConnectonData, 69 | ...props.settings, 70 | }, 71 | }; 72 | 73 | return ( 74 | 81 | ); 82 | }; 83 | 84 | const containerStyle = {backgroundColor: '#000', flex: 1}; 85 | 86 | export default AgoraUIKit; 87 | -------------------------------------------------------------------------------- /src/Contexts/LocalUserContext.tsx: -------------------------------------------------------------------------------- 1 | import React, {useContext, createContext} from 'react'; 2 | import MaxUidContext from './MaxUidContext'; 3 | import MinUidContext from './MinUidContext'; 4 | import {UidInterface} from './PropsContext'; 5 | 6 | export const LocalContext = createContext({} as UidInterface); 7 | export const LocalProvider = LocalContext.Provider; 8 | export const LocalConsumer = LocalContext.Consumer; 9 | 10 | interface LocalUserContextInterface { 11 | children: React.ReactNode; 12 | } 13 | 14 | const LocalUserContext: React.FC = (props) => { 15 | const max = useContext(MaxUidContext); 16 | const min = useContext(MinUidContext); 17 | // if(min && min[0] && max ) 18 | let localUser: UidInterface = max[0].uid === 'local' ? max[0] : min[0]; 19 | return ( 20 | 21 | {props.children} 22 | 23 | ); 24 | }; 25 | 26 | export default LocalUserContext; 27 | -------------------------------------------------------------------------------- /src/Contexts/MaxUidContext.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {UidStateInterface} from './RtcContext'; 3 | 4 | const MaxUidContext = React.createContext([]); 5 | export const MaxUidProvider = MaxUidContext.Provider; 6 | export const MaxUidConsumer = MaxUidContext.Consumer; 7 | export default MaxUidContext; 8 | -------------------------------------------------------------------------------- /src/Contexts/MinUidContext.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {UidStateInterface} from './RtcContext'; 3 | 4 | const MinUidContext = React.createContext([]); 5 | 6 | export const MinUidProvider = MinUidContext.Provider; 7 | export const MinUidConsumer = MinUidContext.Consumer; 8 | export default MinUidContext; 9 | -------------------------------------------------------------------------------- /src/Contexts/RtcContext.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {CallbacksInterface} from './PropsContext'; 3 | import {IRtcEngine} from 'react-native-agora'; 4 | import type {DualStreamMode, UidInterface} from './PropsContext'; 5 | 6 | export interface UidStateInterface { 7 | min: Array; 8 | max: Array; 9 | } 10 | 11 | export interface ActionInterface { 12 | type: T; 13 | value: Parameters; 14 | } 15 | 16 | export type DispatchType = < 17 | T extends keyof CallbacksInterface, 18 | V extends Parameters, 19 | >(action: { 20 | type: T; 21 | value: V; 22 | }) => void; 23 | 24 | export type ActionType = ActionInterface; 25 | 26 | export interface RtcContextInterface { 27 | RtcEngine: IRtcEngine; 28 | dispatch: DispatchType; 29 | rtcUidRef: React.MutableRefObject; 30 | rtcChannelJoined: boolean; 31 | setDualStreamMode: React.Dispatch>; 32 | } 33 | 34 | const RtcContext = React.createContext( 35 | {} as RtcContextInterface, 36 | ); 37 | 38 | export const RtcProvider = RtcContext.Provider; 39 | export const RtcConsumer = RtcContext.Consumer; 40 | export default RtcContext; 41 | -------------------------------------------------------------------------------- /src/Contexts/RtmContext.tsx: -------------------------------------------------------------------------------- 1 | import RtmClient from 'agora-react-native-rtm'; 2 | import {RtmClientEvents} from 'agora-react-native-rtm/src/RtmEngine'; 3 | import React, {createContext} from 'react'; 4 | 5 | /** 6 | * Callbacks to pass to RTM events 7 | */ 8 | export type rtmCallbacks = Partial; 9 | 10 | export enum rtmStatusEnum { 11 | // Initialisation failed 12 | initFailed, 13 | // Login has not been attempted 14 | offline, 15 | // RTM is initialising, process is not yet complete 16 | initialising, 17 | // Currently attempting to log in 18 | loggingIn, 19 | // RTM has logged in 20 | loggedIn, 21 | // RTM is logged in, and connected to the current channel 22 | connected, 23 | // RTM Login Failed 24 | loginFailed, 25 | } 26 | 27 | export type messageType = 'UserData' | 'MuteRequest'; 28 | 29 | export type messageObjectType = userDataType | muteRequest | genericAction; 30 | 31 | export enum clientRoleRaw { 32 | broadcaster, 33 | audience, 34 | } 35 | 36 | export enum mutingDevice { 37 | camera, 38 | microphone, 39 | } 40 | 41 | export type genericAction = { 42 | messageType: 'RtmDataRequest'; 43 | type: 'ping' | 'pong' | 'userData'; 44 | }; 45 | 46 | export type muteRequest = { 47 | messageType: 'MuteRequest'; 48 | rtcId: number; 49 | mute: boolean; 50 | device: mutingDevice; 51 | isForceful: boolean; 52 | }; 53 | 54 | export type userDataType = { 55 | messageType: 'UserData'; 56 | rtmId: string; 57 | rtcId: number; 58 | username?: string; 59 | role: clientRoleRaw; 60 | uikit: { 61 | platform: string; 62 | framework: string; 63 | version: string; 64 | }; 65 | agora: { 66 | rtm: string; 67 | rtc: string; 68 | }; 69 | }; 70 | 71 | export enum popUpStateEnum { 72 | closed, 73 | muteMic, 74 | muteCamera, 75 | unmuteMic, 76 | unmuteCamera, 77 | } 78 | 79 | type NewType = React.Dispatch>; 80 | 81 | /** 82 | * Interface for RTM Context 83 | */ 84 | interface rtmContext { 85 | /** 86 | * rtm connection status 87 | */ 88 | rtmStatus: rtmStatusEnum; 89 | /** 90 | * send message to everyone in the channel 91 | */ 92 | sendChannelMessage: (msg: messageObjectType) => void; 93 | /** 94 | * send message to a specific user 95 | */ 96 | sendPeerMessage: (msg: messageObjectType, uid: string) => void; 97 | /** 98 | * RTM Client instance 99 | */ 100 | rtmClient: RtmClient; 101 | /** 102 | * map with userdata for each rtc uid in the channel 103 | */ 104 | userDataMap: Object; 105 | /** 106 | * map with rtm uid for each rtc uid in the channel 107 | */ 108 | uidMap: Record; 109 | /** 110 | * Send a mute request 111 | */ 112 | sendMuteRequest: (device: mutingDevice, rtcId: number, mute: boolean) => void; 113 | /** 114 | * RTM usernames 115 | */ 116 | usernames: Record; 117 | /** 118 | * state to display pop up on remote mute request 119 | */ 120 | popUpState: popUpStateEnum; 121 | /** 122 | * set state to hide pop up 123 | */ 124 | setPopUpState: NewType; 125 | } 126 | /** 127 | * Context to access RTM data. It's setup by {@link RtmConfigure}. 128 | */ 129 | const RtmContext = createContext(null as unknown as rtmContext); 130 | 131 | export const RtmProvider = RtmContext.Provider; 132 | export const RtmConsumer = RtmContext.Consumer; 133 | export default RtmContext; 134 | -------------------------------------------------------------------------------- /src/Controls/BtnTemplate.tsx: -------------------------------------------------------------------------------- 1 | import React, {useContext} from 'react'; 2 | import { 3 | TouchableOpacity, 4 | Image, 5 | StyleProp, 6 | TouchableOpacityProps, 7 | ViewStyle, 8 | Text, 9 | View, 10 | Platform, 11 | } from 'react-native'; 12 | import PropsContext, {IconsInterface} from '../Contexts/PropsContext'; 13 | import styles from '../Style'; 14 | import icons from './Icons'; 15 | import useImageDelay from '../hooks/useImageDelay'; 16 | import {Either} from './types'; 17 | 18 | interface BtnTemplateBasicInterface { 19 | color?: string; 20 | onPress?: TouchableOpacityProps['onPress']; 21 | style?: StyleProp; 22 | btnText?: string; 23 | disabled?: boolean; 24 | } 25 | interface BtnTemplateInterfaceWithName extends BtnTemplateBasicInterface { 26 | name?: keyof IconsInterface; 27 | } 28 | interface BtnTemplateInterfaceWithIcon extends BtnTemplateBasicInterface { 29 | icon?: any; 30 | } 31 | type BtnTemplateInterface = Either< 32 | BtnTemplateInterfaceWithIcon, 33 | BtnTemplateInterfaceWithName 34 | >; 35 | 36 | const BtnTemplate: React.FC = (props) => { 37 | const {disabled = false} = props; 38 | const {styleProps} = useContext(PropsContext); 39 | const {BtnTemplateStyles, theme, iconSize, customIcon} = styleProps || {}; 40 | 41 | const imageRef = React.useRef(null); 42 | 43 | // This fixes the tint issue in safari browser 44 | useImageDelay(imageRef, 10, '', props?.color || ''); 45 | 46 | return ( 47 | 51 | 56 | 73 | 74 | 81 | {props.btnText} 82 | 83 | 84 | ); 85 | }; 86 | 87 | export default BtnTemplate; 88 | -------------------------------------------------------------------------------- /src/Controls/ImageIcon.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | ******************************************** 3 | Copyright © 2021 Agora Lab, Inc., all rights reserved. 4 | AppBuilder and all associated components, source code, APIs, services, and documentation 5 | (the “Materials”) are owned by Agora Lab, Inc. and its licensors. The Materials may not be 6 | accessed, used, modified, or distributed for any purpose without a license from Agora Lab, Inc. 7 | Use without a license or in violation of any license terms and conditions (including use for 8 | any purpose competitive to Agora Lab, Inc.’s business) is strictly prohibited. For more 9 | information visit https://appbuilder.agora.io. 10 | ********************************************* 11 | */ 12 | import React, {useContext} from 'react'; 13 | import {Image, Platform, StyleProp, ViewStyle} from 'react-native'; 14 | import icons from './Icons'; 15 | import PropsContext, {IconsInterface} from './../Contexts/PropsContext'; 16 | import useImageDelay from '../hooks/useImageDelay'; 17 | import {Either} from './types'; 18 | 19 | interface BaseInterface { 20 | color?: string; 21 | style?: StyleProp; 22 | } 23 | 24 | interface BaseInterfaceWithName extends BaseInterface { 25 | name?: keyof IconsInterface; 26 | } 27 | interface BaseInterfaceWithIcon extends BaseInterface { 28 | icon?: any; 29 | } 30 | 31 | type ImageIconInterface = Either; 32 | 33 | const ImageIcon: React.FC = (props) => { 34 | const {styleProps} = useContext(PropsContext); 35 | const {theme} = styleProps || {}; 36 | const imageRef = React.useRef(null); 37 | 38 | useImageDelay(imageRef, 10, props?.name || '', props?.color); 39 | 40 | return ( 41 | 54 | ); 55 | }; 56 | 57 | export default ImageIcon; 58 | -------------------------------------------------------------------------------- /src/Controls/Local/EndCall.tsx: -------------------------------------------------------------------------------- 1 | import React, {useContext} from 'react'; 2 | import PropsContext from '../../Contexts/PropsContext'; 3 | import RtcContext from '../../Contexts/RtcContext'; 4 | import BtnTemplate from '../BtnTemplate'; 5 | import styles from '../../Style'; 6 | 7 | /** 8 | * React Component that renders the endcall button 9 | * @returns Renders the endcall button 10 | */ 11 | const EndCall: React.FC = () => { 12 | const {styleProps} = useContext(PropsContext); 13 | const {localBtnStyles} = styleProps || {}; 14 | const {endCall} = localBtnStyles || {}; 15 | const {dispatch} = useContext(RtcContext); 16 | 17 | return ( 18 | 24 | dispatch({ 25 | type: 'EndCall', 26 | value: [], 27 | }) 28 | } 29 | /> 30 | ); 31 | }; 32 | 33 | export default EndCall; 34 | -------------------------------------------------------------------------------- /src/Controls/Local/LocalAudioMute.tsx: -------------------------------------------------------------------------------- 1 | import React, {useContext} from 'react'; 2 | import PropsContext, { 3 | ToggleState, 4 | UidInterface, 5 | } from '../../Contexts/PropsContext'; 6 | import RtcContext from '../../Contexts/RtcContext'; 7 | import BtnTemplate from '../BtnTemplate'; 8 | import styles from '../../Style'; 9 | import {LocalContext} from '../../Contexts/LocalUserContext'; 10 | import {DispatchType} from '../../Contexts/RtcContext'; 11 | import {IRtcEngine} from 'react-native-agora'; 12 | interface LocalAudioMuteProps { 13 | btnText?: string; 14 | variant?: 'outlined' | 'text'; 15 | } 16 | 17 | const LocalAudioMute: React.FC = (props) => { 18 | const {btnText = 'Audio', variant = 'Outlined'} = props; 19 | const {styleProps} = useContext(PropsContext); 20 | const {localBtnStyles, remoteBtnStyles} = styleProps || {}; 21 | const {muteLocalAudio} = localBtnStyles || {}; 22 | const {muteRemoteAudio} = remoteBtnStyles || {}; 23 | const {RtcEngine, dispatch} = useContext(RtcContext); 24 | const localUser = useContext(LocalContext); 25 | 26 | return ( 27 | muteAudio(localUser, dispatch, RtcEngine)} 37 | /> 38 | ); 39 | }; 40 | 41 | export const muteAudio = async ( 42 | local: UidInterface, 43 | dispatch: DispatchType, 44 | RtcEngine: IRtcEngine, 45 | ) => { 46 | const localState = local.audio; 47 | // Don't do anything if it is in a transitional state 48 | if ( 49 | localState === ToggleState.enabled || 50 | localState === ToggleState.disabled 51 | ) { 52 | // Disable UI 53 | dispatch({ 54 | type: 'LocalMuteAudio', 55 | value: [ 56 | localState === ToggleState.enabled 57 | ? ToggleState.disabling 58 | : ToggleState.enabling, 59 | ], 60 | }); 61 | 62 | try { 63 | await RtcEngine.muteLocalAudioStream(localState === ToggleState.enabled); 64 | // Enable UI 65 | dispatch({ 66 | type: 'LocalMuteAudio', 67 | value: [ 68 | localState === ToggleState.enabled 69 | ? ToggleState.disabled 70 | : ToggleState.enabled, 71 | ], 72 | }); 73 | } catch (e) { 74 | console.error(e); 75 | dispatch({ 76 | type: 'LocalMuteAudio', 77 | value: [localState], 78 | }); 79 | } 80 | } else { 81 | console.log('LocalMuteAudio in transition', local, ToggleState); 82 | } 83 | }; 84 | 85 | export default LocalAudioMute; 86 | -------------------------------------------------------------------------------- /src/Controls/Local/LocalVideoMute.tsx: -------------------------------------------------------------------------------- 1 | import React, {useContext} from 'react'; 2 | import PropsContext, { 3 | ToggleState, 4 | UidInterface, 5 | } from '../../Contexts/PropsContext'; 6 | import RtcContext from '../../Contexts/RtcContext'; 7 | import BtnTemplate from '../BtnTemplate'; 8 | import styles from '../../Style'; 9 | import {LocalContext} from '../../Contexts/LocalUserContext'; 10 | import {DispatchType} from '../../Contexts/RtcContext'; 11 | import {IRtcEngine} from 'react-native-agora'; 12 | 13 | interface LocalVideoMuteProps { 14 | btnText?: string; 15 | variant?: 'outlined' | 'text'; 16 | } 17 | 18 | const LocalVideoMute: React.FC = (props) => { 19 | const {btnText = 'Video', variant = 'Outlined'} = props; 20 | const {styleProps} = useContext(PropsContext); 21 | const {localBtnStyles, remoteBtnStyles} = styleProps || {}; 22 | const {muteLocalVideo} = localBtnStyles || {}; 23 | const {muteRemoteVideo} = remoteBtnStyles || {}; 24 | const {RtcEngine, dispatch} = useContext(RtcContext); 25 | const local = useContext(LocalContext); 26 | 27 | return ( 28 | muteVideo(local, dispatch, RtcEngine)} 38 | /> 39 | ); 40 | }; 41 | 42 | export const muteVideo = async ( 43 | local: UidInterface, 44 | dispatch: DispatchType, 45 | RtcEngine: IRtcEngine, 46 | ) => { 47 | const localState = local.video; 48 | // Don't do anything if it is in a transitional state 49 | if ( 50 | localState === ToggleState.enabled || 51 | localState === ToggleState.disabled 52 | ) { 53 | // Disable UI 54 | dispatch({ 55 | type: 'LocalMuteVideo', 56 | value: [ 57 | localState === ToggleState.enabled 58 | ? ToggleState.disabling 59 | : ToggleState.enabling, 60 | ], 61 | }); 62 | 63 | try { 64 | await RtcEngine.muteLocalVideoStream(localState === ToggleState.enabled); 65 | // Enable UI 66 | dispatch({ 67 | type: 'LocalMuteVideo', 68 | value: [ 69 | localState === ToggleState.enabled 70 | ? ToggleState.disabled 71 | : ToggleState.enabled, 72 | ], 73 | }); 74 | } catch (e) { 75 | console.error(e); 76 | dispatch({ 77 | type: 'LocalMuteVideo', 78 | value: [localState], 79 | }); 80 | } 81 | } else { 82 | console.log('LocalMuteVideo in transition', local, ToggleState); 83 | } 84 | }; 85 | 86 | export default LocalVideoMute; 87 | -------------------------------------------------------------------------------- /src/Controls/Local/SwitchCamera.tsx: -------------------------------------------------------------------------------- 1 | import React, {useContext} from 'react'; 2 | import PropsContext, {ToggleState} from '../../Contexts/PropsContext'; 3 | import {LocalContext} from '../../Contexts/LocalUserContext'; 4 | import RtcContext from '../../Contexts/RtcContext'; 5 | import BtnTemplate from '../BtnTemplate'; 6 | import styles from '../../Style'; 7 | 8 | const SwitchCamera: React.FC = () => { 9 | const {styleProps, callbacks} = useContext(PropsContext); 10 | const {localBtnStyles} = styleProps || {}; 11 | const {switchCamera} = localBtnStyles || {}; 12 | const {RtcEngine} = useContext(RtcContext); 13 | const local = useContext(LocalContext); 14 | return ( 15 | { 21 | RtcEngine.switchCamera(); 22 | callbacks?.SwitchCamera && callbacks.SwitchCamera(); 23 | }} 24 | /> 25 | ); 26 | }; 27 | 28 | export default SwitchCamera; 29 | -------------------------------------------------------------------------------- /src/Controls/LocalControls.tsx: -------------------------------------------------------------------------------- 1 | import React, {useContext} from 'react'; 2 | import {View} from 'react-native'; 3 | import styles from '../Style'; 4 | import EndCall from './Local/EndCall'; 5 | import LocalAudioMute from './Local/LocalAudioMute'; 6 | import LocalVideoMute from './Local/LocalVideoMute'; 7 | import SwitchCamera from './Local/SwitchCamera'; 8 | import RemoteControls from './RemoteControls'; 9 | import {MaxUidConsumer} from '../Contexts/MaxUidContext'; 10 | import PropsContext, {Layout} from '../Contexts/PropsContext'; 11 | import {ClientRoleType} from 'react-native-agora'; 12 | 13 | interface ControlsPropsInterface { 14 | showButton?: boolean; 15 | } 16 | 17 | const Controls: React.FC = (props) => { 18 | const {styleProps, rtcProps} = useContext(PropsContext); 19 | const {localBtnContainer} = styleProps || {}; 20 | const showButton = props.showButton !== undefined ? props.showButton : true; 21 | return ( 22 | <> 23 | 24 | {rtcProps.role !== ClientRoleType.ClientRoleAudience && ( 25 | <> 26 | 27 | 28 | 29 | 30 | )} 31 | 32 | 33 | {showButton ? ( 34 | 35 | {(users) => ( 36 | 41 | {rtcProps.layout !== Layout.Grid && ( 42 | 43 | )} 44 | 45 | )} 46 | 47 | ) : ( 48 | <> 49 | )} 50 | 51 | ); 52 | }; 53 | 54 | export default Controls; 55 | -------------------------------------------------------------------------------- /src/Controls/Remote/RemoteAudioMute.tsx: -------------------------------------------------------------------------------- 1 | import React, {useContext} from 'react'; 2 | import PropsContext, { 3 | ToggleState, 4 | UidInterface, 5 | } from '../../Contexts/PropsContext'; 6 | import RtmContext, {mutingDevice} from '../../Contexts/RtmContext'; 7 | import BtnTemplate from '../BtnTemplate'; 8 | import styles from '../../Style'; 9 | 10 | interface RemoteAudioMuteInterface { 11 | user: UidInterface; 12 | } 13 | 14 | const RemoteAudioMute: React.FC = (props) => { 15 | const {user} = props; 16 | const {sendMuteRequest, uidMap} = useContext(RtmContext || {}); 17 | const {styleProps} = useContext(PropsContext); 18 | const {remoteBtnStyles} = styleProps || {}; 19 | const {muteRemoteAudio} = remoteBtnStyles || {}; 20 | const isMuted = user.audio === ToggleState.disabled; 21 | 22 | return user.uid !== 0 && uidMap[user.uid as number] ? ( 23 | { 27 | sendMuteRequest(mutingDevice.microphone, user.uid as number, !isMuted); 28 | }} 29 | /> 30 | ) : ( 31 | <> 32 | ); 33 | }; 34 | 35 | export default RemoteAudioMute; 36 | -------------------------------------------------------------------------------- /src/Controls/Remote/RemoteMutePopUp.tsx: -------------------------------------------------------------------------------- 1 | import React, {useContext} from 'react'; 2 | import PropsContext, {ToggleState} from '../../Contexts/PropsContext'; 3 | import RtmContext, {popUpStateEnum} from '../../Contexts/RtmContext'; 4 | import RtcContext from '../../Contexts/RtcContext'; 5 | import {muteVideo} from '../Local/LocalVideoMute'; 6 | import {LocalContext} from '../../Contexts/LocalUserContext'; 7 | import {muteAudio} from '../Local/LocalAudioMute'; 8 | import { 9 | Dimensions, 10 | StyleSheet, 11 | Text, 12 | TouchableOpacity, 13 | View, 14 | } from 'react-native'; 15 | 16 | function PopUp() { 17 | const {styleProps} = useContext(PropsContext); 18 | const {popUpState, setPopUpState} = useContext(RtmContext || {}); 19 | const {popUpContainer} = styleProps || {}; 20 | const {dispatch, RtcEngine} = useContext(RtcContext); 21 | const local = useContext(LocalContext); 22 | 23 | return popUpState !== popUpStateEnum.closed ? ( 24 | 25 | 26 | {popUpState === popUpStateEnum.muteCamera || 27 | popUpState === popUpStateEnum.muteMic 28 | ? 'Mute ' 29 | : 'Unmute '} 30 | {popUpState === popUpStateEnum.muteCamera || 31 | popUpState === popUpStateEnum.unmuteCamera 32 | ? 'Camera' 33 | : 'Mic'} 34 | ? 35 | 36 | 37 | { 39 | switch (popUpState) { 40 | case popUpStateEnum.muteCamera: 41 | local.video === ToggleState.enabled && 42 | muteVideo(local, dispatch, RtcEngine); 43 | break; 44 | case popUpStateEnum.muteMic: 45 | local.audio === ToggleState.enabled && 46 | muteAudio(local, dispatch, RtcEngine); 47 | break; 48 | case popUpStateEnum.unmuteCamera: 49 | local.video === ToggleState.disabled && 50 | muteVideo(local, dispatch, RtcEngine); 51 | break; 52 | case popUpStateEnum.unmuteMic: 53 | local.audio === ToggleState.disabled && 54 | muteAudio(local, dispatch, RtcEngine); 55 | break; 56 | } 57 | setPopUpState(popUpStateEnum.closed); 58 | }} 59 | style={styles.button}> 60 | Confirm 61 | 62 | setPopUpState(popUpStateEnum.closed)}> 65 | Close 66 | 67 | 68 | 69 | ) : null; 70 | } 71 | 72 | const styles = StyleSheet.create({ 73 | button: { 74 | color: '#fff', 75 | borderWidth: 2, 76 | borderStyle: 'solid', 77 | borderColor: '#fff', 78 | padding: 2, 79 | borderRadius: 4, 80 | }, 81 | buttonClose: { 82 | color: '#fff', 83 | borderWidth: 2, 84 | borderStyle: 'solid', 85 | borderColor: '#fff', 86 | padding: 2, 87 | borderRadius: 4, 88 | }, 89 | container: { 90 | backgroundColor: '#007bffaa', 91 | position: 'absolute', 92 | width: 240, 93 | height: 80, 94 | top: Dimensions.get('screen').height / 2 - 80, 95 | left: Dimensions.get('screen').width / 2 - 120, 96 | zIndex: 100, 97 | display: 'flex', 98 | flexDirection: 'column', 99 | justifyContent: 'space-evenly', 100 | alignItems: 'center', 101 | }, 102 | fontStyle: {color: '#fff', fontWeight: '700', fontSize: 18}, 103 | buttonText: {color: '#fff', paddingHorizontal: 8}, 104 | buttonHolder: { 105 | flexDirection: 'row', 106 | display: 'flex', 107 | width: '100%', 108 | justifyContent: 'space-around', 109 | }, 110 | }); 111 | 112 | export default PopUp; 113 | -------------------------------------------------------------------------------- /src/Controls/Remote/RemoteSwap.tsx: -------------------------------------------------------------------------------- 1 | import React, {useContext} from 'react'; 2 | import RtcContext from '../../Contexts/RtcContext'; 3 | import BtnTemplate from '../BtnTemplate'; 4 | import styles from '../../Style'; 5 | import PropsContext, {UidInterface} from '../../Contexts/PropsContext'; 6 | 7 | interface RemoteSwapInterface { 8 | user: UidInterface; 9 | } 10 | 11 | const RemoteSwap: React.FC = (props) => { 12 | const {dispatch} = useContext(RtcContext); 13 | const {styleProps} = useContext(PropsContext); 14 | const {remoteBtnStyles, theme} = styleProps || {}; 15 | const {remoteSwap} = remoteBtnStyles || {}; 16 | 17 | return ( 18 | { 32 | dispatch({ 33 | type: 'SwapVideo', 34 | value: [props.user], 35 | }); 36 | }} 37 | /> 38 | ); 39 | }; 40 | 41 | export default RemoteSwap; 42 | -------------------------------------------------------------------------------- /src/Controls/Remote/RemoteVideoMute.tsx: -------------------------------------------------------------------------------- 1 | import React, {useContext} from 'react'; 2 | import PropsContext, { 3 | ToggleState, 4 | UidInterface, 5 | } from '../../Contexts/PropsContext'; 6 | import RtmContext, {mutingDevice} from '../../Contexts/RtmContext'; 7 | import BtnTemplate from '../BtnTemplate'; 8 | import styles from '../../Style'; 9 | 10 | interface RemoteVideoMuteInterface { 11 | user: UidInterface; 12 | rightButton: boolean; 13 | } 14 | 15 | const RemoteVideoMute: React.FC = (props) => { 16 | const {user} = props; 17 | const {sendMuteRequest, uidMap} = useContext(RtmContext || {}); 18 | const {styleProps} = useContext(PropsContext); 19 | const {remoteBtnStyles, theme} = styleProps || {}; 20 | const {muteRemoteVideo} = remoteBtnStyles || {}; 21 | const isMuted = user.video === ToggleState.disabled; 22 | 23 | return user.uid !== 0 && uidMap[user.uid as number] ? ( 24 | { 38 | sendMuteRequest(mutingDevice.camera, user.uid as number, !isMuted); 39 | }} 40 | /> 41 | ) : ( 42 | <> 43 | ); 44 | }; 45 | 46 | export default RemoteVideoMute; 47 | -------------------------------------------------------------------------------- /src/Controls/RemoteControls.tsx: -------------------------------------------------------------------------------- 1 | import React, {useContext} from 'react'; 2 | import {View} from 'react-native'; 3 | import PropsContext, {UidInterface} from '../Contexts/PropsContext'; 4 | import styles from '../Style'; 5 | import RemoteAudioMute from './Remote/RemoteAudioMute'; 6 | import RemoteSwap from './Remote/RemoteSwap'; 7 | import RemoteVideoMute from './Remote/RemoteVideoMute'; 8 | 9 | interface RemoteControlsInterface { 10 | showMuteRemoteVideo?: boolean; 11 | showMuteRemoteAudio?: boolean; 12 | showRemoteSwap?: boolean; 13 | user: UidInterface; 14 | } 15 | 16 | const RemoteControls: React.FC = (props) => { 17 | const {styleProps, rtcProps} = useContext(PropsContext); 18 | const {remoteBtnContainer} = styleProps || {}; 19 | 20 | return ( 21 | 23 | {rtcProps.disableRtm ? ( 24 | <> 25 | ) : ( 26 | <> 27 | {props.showMuteRemoteAudio !== false ? ( 28 | 29 | ) : ( 30 | <> 31 | )} 32 | {props.showMuteRemoteVideo !== false ? ( 33 | 37 | ) : ( 38 | <> 39 | )} 40 | 41 | )} 42 | {props.showRemoteSwap !== false ? ( 43 | 44 | ) : ( 45 | <> 46 | )} 47 | 48 | ); 49 | }; 50 | 51 | export default RemoteControls; 52 | -------------------------------------------------------------------------------- /src/Controls/types.ts: -------------------------------------------------------------------------------- 1 | export type Only = {[P in keyof T]: T[P]} & Omit< 2 | {[P in keyof U]?: never}, 3 | keyof T 4 | >; 5 | 6 | export type Either = Only | Only; 7 | -------------------------------------------------------------------------------- /src/RTMEngine.tsx: -------------------------------------------------------------------------------- 1 | import RtmEngine from 'agora-react-native-rtm'; 2 | 3 | class RTMEngine { 4 | engine!: RtmEngine; 5 | private localUID: string = ''; 6 | private channelId: string = ''; 7 | 8 | private static _instance: RTMEngine | null = null; 9 | 10 | public static getInstance(appId: string) { 11 | if (!RTMEngine._instance) { 12 | return new RTMEngine(appId); 13 | } 14 | return RTMEngine._instance; 15 | } 16 | 17 | private async createClientInstance(appId: string) { 18 | await this.engine.createInstance(appId); 19 | } 20 | 21 | private async destroyClientInstance() { 22 | try { 23 | await this.engine?.logout(); 24 | } catch (error) { 25 | console.log('Error logout: ', error); 26 | } 27 | try { 28 | await this.engine?.release(); 29 | } catch (error) { 30 | console.log('Error release: ', error); 31 | } 32 | } 33 | 34 | private constructor(appId: string) { 35 | if (RTMEngine._instance) { 36 | return RTMEngine._instance; 37 | } 38 | RTMEngine._instance = this; 39 | this.engine = new RtmEngine(); 40 | this.localUID = ''; 41 | this.channelId = ''; 42 | this.createClientInstance(appId); 43 | 44 | return RTMEngine._instance; 45 | } 46 | 47 | setLoginInfo(localUID: string, channelID: string) { 48 | this.localUID = localUID; 49 | this.channelId = channelID; 50 | } 51 | get localUid() { 52 | return this.localUID; 53 | } 54 | get channelUid() { 55 | return this.channelId; 56 | } 57 | destroy() { 58 | try { 59 | if (RTMEngine._instance) { 60 | this.destroyClientInstance(); 61 | RTMEngine._instance = null; 62 | } else { 63 | console.log('destroy called but no instance'); 64 | } 65 | } catch (error) { 66 | console.log('Error destroying instance error: ', error); 67 | } 68 | } 69 | } 70 | 71 | export default RTMEngine; 72 | -------------------------------------------------------------------------------- /src/Reducer/BecomeAudience.ts: -------------------------------------------------------------------------------- 1 | import {UidStateInterface} from '../Contexts/RtcContext'; 2 | 3 | export default function BecomeAudience(state: UidStateInterface) { 4 | console.log('!BecomeAudience'); 5 | if (state.max[0].uid === 'local' && state.min.length !== 0) { 6 | let stateUpdate = {}; 7 | let minUpdate = [...state.min]; 8 | stateUpdate = { 9 | max: [minUpdate.pop()], 10 | min: [...minUpdate, state.max[0]], 11 | }; 12 | return stateUpdate; 13 | } else { 14 | return state; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Reducer/LocalMuteAudio.ts: -------------------------------------------------------------------------------- 1 | import {UidInterface} from '../Contexts/PropsContext'; 2 | import {ActionType, UidStateInterface} from '../Contexts/RtcContext'; 3 | 4 | export default function LocalMuteAudio( 5 | state: UidStateInterface, 6 | action: ActionType<'LocalMuteAudio'>, 7 | ) { 8 | let stateUpdate = {}; 9 | const LocalAudioMute = (user: UidInterface) => { 10 | if (user.uid === 'local') { 11 | user.audio = action.value[0]; 12 | } 13 | return user; 14 | }; 15 | stateUpdate = { 16 | min: state.min.map(LocalAudioMute), 17 | max: state.max.map(LocalAudioMute), 18 | }; 19 | return stateUpdate; 20 | } 21 | -------------------------------------------------------------------------------- /src/Reducer/LocalMuteVideo.ts: -------------------------------------------------------------------------------- 1 | import {UidInterface} from '../Contexts/PropsContext'; 2 | import {ActionType, UidStateInterface} from '../Contexts/RtcContext'; 3 | 4 | export default function LocalMuteVideo( 5 | state: UidStateInterface, 6 | action: ActionType<'LocalMuteVideo'>, 7 | ) { 8 | let stateUpdate = {}; 9 | const LocalVideoMute = (user: UidInterface) => { 10 | if (user.uid === 'local') { 11 | user.video = action.value[0]; 12 | } 13 | return user; 14 | }; 15 | stateUpdate = { 16 | min: state.min.map(LocalVideoMute), 17 | max: state.max.map(LocalVideoMute), 18 | }; 19 | return stateUpdate; 20 | } 21 | -------------------------------------------------------------------------------- /src/Reducer/RemoteAudioStateChanged.ts: -------------------------------------------------------------------------------- 1 | import {RemoteAudioState} from 'react-native-agora'; 2 | import {ToggleState, UidInterface} from '../Contexts/PropsContext'; 3 | import {ActionType, UidStateInterface} from '../Contexts/RtcContext'; 4 | 5 | export default function RemoteAudioStateChanged( 6 | state: UidStateInterface, 7 | action: ActionType<'RemoteAudioStateChanged'>, 8 | ) { 9 | let stateUpdate = {}; 10 | let audioState: ToggleState; 11 | if ( 12 | action.value[2] === RemoteAudioState.RemoteAudioStateStarting || 13 | action.value[2] === RemoteAudioState.RemoteAudioStateDecoding 14 | ) { 15 | audioState = ToggleState.enabled; 16 | } else { 17 | audioState = ToggleState.disabled; 18 | } 19 | const audioChange = (user: UidInterface) => { 20 | if (user.uid === action.value[1]) { 21 | user.audio = audioState; 22 | } 23 | return user; 24 | }; 25 | stateUpdate = { 26 | min: state.min.map(audioChange), 27 | max: state.max.map(audioChange), 28 | }; 29 | return stateUpdate; 30 | } 31 | -------------------------------------------------------------------------------- /src/Reducer/RemoteVideoStateChanged.ts: -------------------------------------------------------------------------------- 1 | import {RemoteVideoState} from 'react-native-agora'; 2 | import {ToggleState, UidInterface} from '../Contexts/PropsContext'; 3 | import {ActionType, UidStateInterface} from '../Contexts/RtcContext'; 4 | 5 | export default function RemoteVideoStateChanged( 6 | state: UidStateInterface, 7 | action: ActionType<'RemoteVideoStateChanged'>, 8 | ) { 9 | let stateUpdate = {}; 10 | let videoState: ToggleState; 11 | if (action.value[2] === RemoteVideoState.RemoteVideoStateStopped) { 12 | videoState = ToggleState.disabled; 13 | } else if ( 14 | // action.value[1] === RemoteVideoState.RemoteVideoStateStarting || 15 | action.value[2] === RemoteVideoState.RemoteVideoStateDecoding 16 | ) { 17 | videoState = ToggleState.enabled; 18 | } 19 | const videoChange = (user: UidInterface) => { 20 | if (user.uid === action.value[1] && videoState !== undefined) { 21 | user.video = videoState; 22 | } 23 | return user; 24 | }; 25 | stateUpdate = { 26 | min: state.min.map(videoChange), 27 | max: state.max.map(videoChange), 28 | }; 29 | return stateUpdate; 30 | } 31 | -------------------------------------------------------------------------------- /src/Reducer/UpdateDualStreamMode.ts: -------------------------------------------------------------------------------- 1 | import {DualStreamMode, UidInterface} from '../Contexts/PropsContext'; 2 | import {ActionType, UidStateInterface} from '../Contexts/RtcContext'; 3 | 4 | export default function UpdateDualStreamMode( 5 | state: UidStateInterface, 6 | action: ActionType<'UpdateDualStreamMode'>, 7 | ) { 8 | const newMode = action.value[0]; 9 | let stateUpdate: UidStateInterface; 10 | if (newMode === DualStreamMode.HIGH) { 11 | // Update everybody to high 12 | const maxStateUpdate: UidInterface[] = state.max.map((user) => ({ 13 | ...user, 14 | streamType: 'high', 15 | })); 16 | const minStateUpdate: UidInterface[] = state.min.map((user) => ({ 17 | ...user, 18 | streamType: 'high', 19 | })); 20 | stateUpdate = {min: minStateUpdate, max: maxStateUpdate}; 21 | } else if (newMode === DualStreamMode.LOW) { 22 | // Update everybody to low 23 | const maxStateUpdate: UidInterface[] = state.max.map((user) => ({ 24 | ...user, 25 | streamType: 'low', 26 | })); 27 | const minStateUpdate: UidInterface[] = state.min.map((user) => ({ 28 | ...user, 29 | streamType: 'low', 30 | })); 31 | stateUpdate = {min: minStateUpdate, max: maxStateUpdate}; 32 | } else { 33 | // if (newMode === DualStreamMode.DYNAMIC) 34 | // Max users are high other are low 35 | const maxStateUpdate: UidInterface[] = state.max.map((user) => ({ 36 | ...user, 37 | streamType: 'high', 38 | })); 39 | const minStateUpdate: UidInterface[] = state.min.map((user) => ({ 40 | ...user, 41 | streamType: 'low', 42 | })); 43 | stateUpdate = {min: minStateUpdate, max: maxStateUpdate}; 44 | } 45 | return stateUpdate; 46 | } 47 | -------------------------------------------------------------------------------- /src/Reducer/UserJoined.ts: -------------------------------------------------------------------------------- 1 | import {DualStreamMode, ToggleState} from '../Contexts/PropsContext'; 2 | import {ActionType, UidStateInterface} from '../Contexts/RtcContext'; 3 | 4 | export default function UserJoined( 5 | state: UidStateInterface, 6 | action: ActionType<'UserJoined'>, 7 | dualStreamMode: DualStreamMode, 8 | uids: (string | number)[], 9 | ) { 10 | let stateUpdate = {}; 11 | if (uids.indexOf(action.value[1]) === -1) { 12 | // If new user has joined 13 | // By default add to minimized 14 | let minUpdate = [ 15 | ...state.min, 16 | { 17 | uid: action.value[1], 18 | audio: ToggleState.disabled, 19 | video: ToggleState.disabled, 20 | streamType: dualStreamMode === DualStreamMode.HIGH ? 'high' : 'low', // Low if DualStreamMode is LOW or DYNAMIC by default 21 | }, 22 | ]; 23 | 24 | if (minUpdate.length === 1 && state.max[0].uid === 'local') { 25 | //Only one remote and local is maximized 26 | //Change stream type to high if dualStreaMode is DYNAMIC 27 | if (dualStreamMode === DualStreamMode.DYNAMIC) { 28 | minUpdate[0].streamType = 'high'; 29 | } 30 | //Swap max and min 31 | stateUpdate = { 32 | max: minUpdate, 33 | min: state.max, 34 | }; 35 | } else { 36 | //More than one remote 37 | stateUpdate = { 38 | min: minUpdate, 39 | }; 40 | } 41 | 42 | // console.log('new user joined!\n', state, stateUpdate, { 43 | // dualStreamMode, 44 | // }); 45 | } 46 | return stateUpdate; 47 | } 48 | -------------------------------------------------------------------------------- /src/Reducer/UserMuteRemoteAudio.ts: -------------------------------------------------------------------------------- 1 | import {UidInterface} from '../Contexts/PropsContext'; 2 | import {ActionType, UidStateInterface} from '../Contexts/RtcContext'; 3 | 4 | export default function UserMuteRemoteAudio( 5 | state: UidStateInterface, 6 | action: ActionType<'UserMuteRemoteAudio'>, 7 | ) { 8 | let stateUpdate = {}; 9 | const audioMute = (user: UidInterface) => { 10 | if (user.uid === action.value[0].uid) { 11 | user.audio = action.value[1]; 12 | } 13 | return user; 14 | }; 15 | stateUpdate = { 16 | min: state.min.map(audioMute), 17 | max: state.max.map(audioMute), 18 | }; 19 | return stateUpdate; 20 | } 21 | -------------------------------------------------------------------------------- /src/Reducer/UserMuteRemoteVideo.ts: -------------------------------------------------------------------------------- 1 | import {UidInterface} from '../Contexts/PropsContext'; 2 | import {ActionType, UidStateInterface} from '../Contexts/RtcContext'; 3 | 4 | export default function UserMuteRemoteVideo( 5 | state: UidStateInterface, 6 | action: ActionType<'UserMuteRemoteVideo'>, 7 | ) { 8 | let stateUpdate = {}; 9 | const videoMute = (user: UidInterface) => { 10 | if (user.uid === action.value[0].uid) { 11 | user.video = action.value[1]; 12 | } 13 | return user; 14 | }; 15 | stateUpdate = { 16 | min: state.min.map(videoMute), 17 | max: state.max.map(videoMute), 18 | }; 19 | return stateUpdate; 20 | } 21 | -------------------------------------------------------------------------------- /src/Reducer/UserOffline.ts: -------------------------------------------------------------------------------- 1 | import {ActionType, UidStateInterface} from '../Contexts/RtcContext'; 2 | 3 | export default function UserOffline( 4 | state: UidStateInterface, 5 | action: ActionType<'UserOffline'>, 6 | ) { 7 | let stateUpdate = {}; 8 | if (state.max[0].uid === action.value[1]) { 9 | //If max has the remote video 10 | let minUpdate = [...state.min]; 11 | stateUpdate = { 12 | max: [minUpdate.pop()], 13 | min: minUpdate, 14 | }; 15 | } else { 16 | stateUpdate = { 17 | min: state.min.filter((user) => user.uid !== action.value[1]), 18 | }; 19 | } 20 | return stateUpdate; 21 | } 22 | -------------------------------------------------------------------------------- /src/Reducer/index.ts: -------------------------------------------------------------------------------- 1 | export {default as UpdateDualStreamMode} from './UpdateDualStreamMode'; 2 | export {default as UserJoined} from './UserJoined'; 3 | export {default as UserOffline} from './UserOffline'; 4 | export {default as UserMuteRemoteAudio} from './UserMuteRemoteAudio'; 5 | export {default as UserMuteRemoteVideo} from './UserMuteRemoteVideo'; 6 | export {default as LocalMuteAudio} from './LocalMuteAudio'; 7 | export {default as LocalMuteVideo} from './LocalMuteVideo'; 8 | export {default as RemoteAudioStateChanged} from './RemoteAudioStateChanged'; 9 | export {default as RemoteVideoStateChanged} from './RemoteVideoStateChanged'; 10 | export {default as BecomeAudience} from './BecomeAudience'; 11 | -------------------------------------------------------------------------------- /src/Style.ts: -------------------------------------------------------------------------------- 1 | import {StyleSheet} from 'react-native'; 2 | 3 | export default StyleSheet.create({ 4 | max: { 5 | flex: 1, 6 | }, 7 | buttonHolder: { 8 | height: 100, 9 | alignItems: 'center', 10 | flex: 1, 11 | flexDirection: 'row', 12 | justifyContent: 'space-evenly', 13 | }, 14 | button: { 15 | paddingHorizontal: 20, 16 | paddingVertical: 10, 17 | backgroundColor: '#0093E9', 18 | borderRadius: 25, 19 | }, 20 | buttonText: { 21 | color: '#fff', 22 | }, 23 | fullView: { 24 | width: '100%', 25 | height: '100%', 26 | }, 27 | minView: { 28 | width: 240, 29 | height: 135, 30 | }, 31 | minViewFallback: { 32 | width: 240, 33 | justifyContent: 'center', 34 | height: 135, 35 | backgroundColor: '#111', 36 | }, 37 | minContainer: { 38 | position: 'absolute', 39 | top: 15, 40 | left: 0, 41 | padding: 0, 42 | margin: 0, 43 | // height: 135, 44 | }, 45 | Controls: { 46 | position: 'absolute', 47 | bottom: 25, 48 | left: 0, 49 | width: '100%', 50 | height: 70, 51 | zIndex: 10, 52 | display: 'flex', 53 | flexDirection: 'row', 54 | justifyContent: 'space-evenly', 55 | alignItems: 'center', 56 | }, 57 | minOverlay: { 58 | ...(StyleSheet.absoluteFill as object), 59 | backgroundColor: 'black', 60 | opacity: 0.7, 61 | zIndex: 2, 62 | height: '100%', 63 | }, 64 | minCloseBtn: { 65 | alignItems: 'center', 66 | justifyContent: 'center', 67 | backgroundColor: 'rgba(0,0,0,0.1)', 68 | width: 46, 69 | height: 46, 70 | borderRadius: 23, 71 | position: 'absolute', 72 | right: 5, 73 | top: 5, 74 | }, 75 | controlBtn: { 76 | width: 40, 77 | height: 40, 78 | backgroundColor: 'rgba(0,0,0,0.1)', 79 | alignItems: 'center', 80 | justifyContent: 'center', 81 | }, 82 | leftRemoteBtn: { 83 | borderTopLeftRadius: 23, 84 | borderBottomLeftRadius: 23, 85 | borderRightWidth: 4 * StyleSheet.hairlineWidth, 86 | borderColor: '#fff', 87 | }, 88 | rightRemoteBtn: { 89 | borderTopRightRadius: 23, 90 | borderBottomRightRadius: 23, 91 | borderLeftWidth: 4 * StyleSheet.hairlineWidth, 92 | borderColor: '#fff', 93 | }, 94 | remoteBtnContainer: { 95 | width: '100%', 96 | display: 'flex', 97 | marginTop: 50, 98 | flexDirection: 'row', 99 | justifyContent: 'center', 100 | alignSelf: 'center', 101 | }, 102 | localBtn: { 103 | borderRadius: 23, 104 | borderWidth: 4 * StyleSheet.hairlineWidth, 105 | borderColor: '#007aff', 106 | backgroundColor: '#007aff', 107 | }, 108 | endCall: { 109 | borderRadius: 30, 110 | borderWidth: 4 * StyleSheet.hairlineWidth, 111 | borderColor: '#ff0000', 112 | width: 48, 113 | height: 48, 114 | backgroundColor: '#ff0000', 115 | }, 116 | placeholderIcon: { 117 | width: 50, 118 | height: 50, 119 | alignSelf: 'center', 120 | opacity: 0.5, 121 | }, 122 | }); 123 | -------------------------------------------------------------------------------- /src/Utils/actionTypeGuard.tsx: -------------------------------------------------------------------------------- 1 | import {CallbacksInterface} from '../Contexts/PropsContext'; 2 | import {ActionType} from '../Contexts/RtcContext'; 3 | 4 | export function actionTypeGuard( 5 | _act: ActionType, 6 | _type: T, 7 | ): _act is ActionType { 8 | return true; 9 | } 10 | -------------------------------------------------------------------------------- /src/Utils/isSafariBrowser.ts: -------------------------------------------------------------------------------- 1 | //@ts-nocheck 2 | import {Platform} from 'react-native'; 3 | 4 | const isSafariBrowser = () => { 5 | if (Platform.OS !== 'web') { 6 | return false; 7 | } 8 | if (!('userAgent' in navigator)) { 9 | console.warn('unable to detect browser'); 10 | return false; 11 | } 12 | 13 | const userAgentString = navigator.userAgent; 14 | // Detect Chrome 15 | const chromeAgent = userAgentString.indexOf('Chrome') > -1; 16 | // Detect Safari 17 | const safariAgent = userAgentString.indexOf('Safari') > -1; 18 | 19 | // One additional check is required in the case of the Safari browser 20 | // as the user-agent of the Chrome browser also includes the Safari browser’s user-agent. 21 | // If both the user-agents of Chrome and Safari are in the user-agent, 22 | // it means that the browser is Chrome, and hence the Safari browser value is discarded. 23 | 24 | if (chromeAgent && safariAgent) { 25 | return false; 26 | } // Discard Safari since it also matches Chrome 27 | return true; 28 | }; 29 | 30 | export default isSafariBrowser; 31 | -------------------------------------------------------------------------------- /src/Utils/permission.ts: -------------------------------------------------------------------------------- 1 | import {PermissionsAndroid} from 'react-native'; 2 | /** 3 | * @name requestCameraAndAudioPermission 4 | * @description Function to request permission for Audio and Camera 5 | */ 6 | export default async function requestCameraAndAudioPermission() { 7 | try { 8 | const granted = await PermissionsAndroid.requestMultiple([ 9 | PermissionsAndroid.PERMISSIONS.CAMERA, 10 | PermissionsAndroid.PERMISSIONS.RECORD_AUDIO, 11 | ]); 12 | if ( 13 | granted['android.permission.RECORD_AUDIO'] === 14 | PermissionsAndroid.RESULTS.GRANTED && 15 | granted['android.permission.CAMERA'] === 16 | PermissionsAndroid.RESULTS.GRANTED 17 | ) { 18 | console.log('You can use the cameras & mic'); 19 | } else { 20 | console.log('Permission denied'); 21 | } 22 | } catch (err) { 23 | console.warn(err); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Views/GridVideo.tsx: -------------------------------------------------------------------------------- 1 | import React, { 2 | Dispatch, 3 | SetStateAction, 4 | useContext, 5 | useMemo, 6 | useState, 7 | } from 'react'; 8 | import {Dimensions, StyleSheet, View} from 'react-native'; 9 | import MaxVideoView from './MaxVideoView'; 10 | import MinUidContext from '../Contexts/MinUidContext'; 11 | import MaxUidContext from '../Contexts/MaxUidContext'; 12 | import PropsContext from '../Contexts/PropsContext'; 13 | import {ClientRoleType} from 'react-native-agora'; 14 | 15 | const layout = (len: number, isDesktop: boolean = true) => { 16 | console.log('layout'); 17 | const rows = Math.round(Math.sqrt(len)); 18 | const cols = Math.ceil(len / rows); 19 | let [r, c] = isDesktop ? [rows, cols] : [cols, rows]; 20 | return { 21 | matrix: 22 | len > 0 23 | ? [ 24 | ...Array(r - 1) 25 | .fill(null) 26 | .map(() => Array(c).fill('X')), 27 | Array(len - (r - 1) * c).fill('X'), 28 | ] 29 | : [], 30 | dims: {r, c}, 31 | }; 32 | }; 33 | 34 | const GridVideo: React.FC = () => { 35 | const max = useContext(MaxUidContext); 36 | const min = useContext(MinUidContext); 37 | const {rtcProps, styleProps} = useContext(PropsContext); 38 | const users = 39 | rtcProps.role === ClientRoleType.ClientRoleAudience 40 | ? [...max, ...min].filter((user) => user.uid !== 'local') 41 | : [...max, ...min]; 42 | let onLayout = (e: any) => { 43 | setDim([ 44 | e.nativeEvent.layout.width, 45 | e.nativeEvent.layout.height, 46 | e.nativeEvent.layout.width > e.nativeEvent.layout.height, 47 | ]); 48 | }; 49 | const [dim, setDim]: [ 50 | [number, number, boolean], 51 | Dispatch>, 52 | ] = useState([ 53 | Dimensions.get('window').width, 54 | Dimensions.get('window').height, 55 | Dimensions.get('window').width > Dimensions.get('window').height, 56 | ]); 57 | const isDesktop = dim[0] > dim[1] + 100; 58 | let {matrix, dims} = useMemo( 59 | () => layout(users.length, isDesktop), 60 | [users.length, isDesktop], 61 | ); 62 | return ( 63 | 64 | {matrix.map((r, ridx) => ( 65 | 66 | {r.map((c, cidx) => ( 67 | 68 | 73 | {rtcProps.role === ClientRoleType.ClientRoleAudience && 74 | users[ridx * dims.c + cidx].uid === 'local' ? null : ( 75 | 79 | )} 80 | 81 | 82 | ))} 83 | 84 | ))} 85 | 86 | ); 87 | }; 88 | 89 | const style = StyleSheet.create({ 90 | full: { 91 | width: '100%', 92 | // flex: 1, 93 | height: '100%', 94 | }, 95 | gridRow: { 96 | flex: 1, 97 | flexDirection: 'row', 98 | width: '100%', 99 | }, 100 | gridVideoContainerInner: { 101 | // borderColor: '#fff', 102 | // borderWidth:2, 103 | flex: 1, 104 | margin: 1, 105 | }, 106 | col: { 107 | flex: 1, 108 | marginHorizontal: 'auto', 109 | }, 110 | }); 111 | 112 | export default GridVideo; 113 | -------------------------------------------------------------------------------- /src/Views/MaxVideoView.native.tsx: -------------------------------------------------------------------------------- 1 | import React, {useContext} from 'react'; 2 | import {RenderModeType, RtcSurfaceView} from 'react-native-agora'; 3 | import styles from '../Style'; 4 | import PropsContext, {UidInterface} from '../Contexts/PropsContext'; 5 | import {StyleSheet, View} from 'react-native'; 6 | import ImageIcon from '../Controls/ImageIcon'; 7 | import Username from './Usernames'; 8 | 9 | // const LocalView = RtcLocalView.SurfaceView; 10 | // const RemoteView = RtcRemoteView.SurfaceView; 11 | 12 | interface MaxViewInterface { 13 | user: UidInterface; 14 | fallback?: React.ComponentType; 15 | } 16 | /** 17 | * MaxVideoView takes in a user and renders the video 18 | */ 19 | const MaxVideoView: React.FC = (props) => { 20 | const {styleProps, rtcProps} = useContext(PropsContext); 21 | const {maxViewStyles} = styleProps || {}; 22 | const Fallback = props.fallback; 23 | 24 | return ( 25 | 26 | {!rtcProps.disableRtm && } 27 | {props.user.uid === 'local' ? ( 28 | props.user.video ? ( 29 | 33 | ) : Fallback ? ( 34 | 35 | ) : ( 36 | 37 | ) 38 | ) : props.user.video ? ( 39 | <> 40 | 47 | 48 | ) : Fallback ? ( 49 | 50 | ) : ( 51 | 52 | )} 53 | 54 | ); 55 | }; 56 | 57 | const DefaultFallback = () => { 58 | const {styleProps} = useContext(PropsContext); 59 | const {videoPlaceholderContainer} = styleProps || {}; 60 | return ( 61 | 62 | 66 | 67 | ); 68 | }; 69 | 70 | const style = StyleSheet.create({ 71 | placeholderContainer: { 72 | flex: 1, 73 | backgroundColor: '#000', 74 | justifyContent: 'center', 75 | }, 76 | }); 77 | 78 | export default MaxVideoView; 79 | -------------------------------------------------------------------------------- /src/Views/MaxVideoView.tsx: -------------------------------------------------------------------------------- 1 | import React, {useContext} from 'react'; 2 | import {RenderModeType, RtcSurfaceView} from 'react-native-agora'; 3 | import styles from '../Style'; 4 | import PropsContext, {UidInterface} from '../Contexts/PropsContext'; 5 | import {View} from 'react-native'; 6 | // import {RtcContext} from 'Contexts'; 7 | 8 | interface MaxViewInterface { 9 | user: UidInterface; 10 | fallback?: React.ComponentType; 11 | } 12 | 13 | const MaxVideoView: React.FC = (props) => { 14 | const {styleProps} = useContext(PropsContext); 15 | // const {rtcUidRef} = useContext(RtcContext); 16 | const {maxViewStyles} = styleProps || {}; 17 | const Fallback = props.fallback; 18 | 19 | return props.user.uid === 'local' ? ( 20 | props.user.video ? ( 21 | 25 | ) : Fallback ? ( 26 | 27 | ) : ( 28 | 29 | ) 30 | ) : ( 31 | <> 32 |
33 | 40 |
41 | {props.user.video ? ( 42 | <> 43 | ) : ( 44 | <> 45 | {Fallback ? ( 46 | 47 | ) : ( 48 | 49 | )} 50 | 51 | )} 52 | 53 | ); 54 | }; 55 | 56 | export default MaxVideoView; 57 | -------------------------------------------------------------------------------- /src/Views/MinVideoView.tsx: -------------------------------------------------------------------------------- 1 | import React, {useState, useContext} from 'react'; 2 | import {View, TouchableOpacity, Image} from 'react-native'; 3 | import {RenderModeType, RtcSurfaceView} from 'react-native-agora'; 4 | import styles from '../Style'; 5 | import icons from '../Controls/Icons'; 6 | import RemoteControls from '../Controls/RemoteControls'; 7 | import PropsContext, {UidInterface} from '../Contexts/PropsContext'; 8 | import ImageIcon from '../Controls/ImageIcon'; 9 | import Username from './Usernames'; 10 | // import RtcContext from '../Contexts/RtcContext'; 11 | 12 | interface MinViewInterface { 13 | user: UidInterface; 14 | color?: string; 15 | showOverlay?: boolean; 16 | Fallback?: React.ComponentType; 17 | } 18 | 19 | const MinVideoView: React.FC = (props) => { 20 | const [overlay, setOverlay] = useState(false); 21 | const {styleProps, rtcProps} = useContext(PropsContext); 22 | const {theme, remoteBtnStyles, customIcon} = styleProps || {}; 23 | const {minCloseBtnStyles} = remoteBtnStyles || {}; 24 | const {showOverlay} = props || {}; 25 | 26 | return ( 27 | 28 | {showOverlay ? ( 29 | setOverlay(true)}> 30 | 31 | 32 | ) : ( 33 | 34 | )} 35 | 36 | {overlay && showOverlay ? ( 37 | 38 | setOverlay(!overlay)}> 41 | 51 | 52 | 53 | 54 | ) : ( 55 | <> 56 | )} 57 | {!rtcProps.disableRtm && } 58 | 59 | ); 60 | }; 61 | 62 | const UserVideoWithFallback = (props: { 63 | user: UidInterface; 64 | Fallback?: React.ComponentType; 65 | }) => { 66 | const {Fallback, user} = props; 67 | const {styleProps} = useContext(PropsContext); 68 | const {videoPlaceholderContainer, videoPlaceholderIcon} = styleProps || {}; 69 | 70 | return user.video ? ( 71 | 72 | ) : Fallback ? ( 73 | 74 | ) : ( 75 | 76 | 80 | 81 | ); 82 | }; 83 | 84 | const UserVideo = (props: {user: UidInterface}) => { 85 | const {styleProps} = useContext(PropsContext); 86 | // const {rtcUidRef} = useContext(RtcContext); 87 | const {minViewStyles} = styleProps || {}; 88 | return props.user.uid === 'local' ? ( 89 | 94 | ) : ( 95 | 103 | ); 104 | }; 105 | 106 | export default MinVideoView; 107 | -------------------------------------------------------------------------------- /src/Views/PinnedVideo.tsx: -------------------------------------------------------------------------------- 1 | import React, {useContext, useEffect, useState} from 'react'; 2 | import {Dimensions, ScrollView} from 'react-native'; 3 | import MaxVideoView from './MaxVideoView'; 4 | import MinVideoView from './MinVideoView'; 5 | import {MinUidConsumer} from '../Contexts/MinUidContext'; 6 | import {MaxUidConsumer} from '../Contexts/MaxUidContext'; 7 | import styles from '../Style'; 8 | import PropsContext from '../Contexts/PropsContext'; 9 | import {ClientRoleType} from 'react-native-agora'; 10 | 11 | const PinnedVideo: React.FC = () => { 12 | const {rtcProps, styleProps} = useContext(PropsContext); 13 | const [width, setWidth] = useState(Dimensions.get('screen').width); 14 | 15 | useEffect(() => { 16 | Dimensions.addEventListener('change', () => { 17 | setWidth(Dimensions.get('screen').width); 18 | }); 19 | }); 20 | 21 | return ( 22 | <> 23 | 24 | {(maxUsers) => 25 | maxUsers[0] ? ( // check if audience & live don't render if uid === local 26 | 27 | ) : null 28 | } 29 | 30 | 38 | 39 | {(minUsers) => 40 | minUsers.map((user) => 41 | rtcProps.role === ClientRoleType.ClientRoleAudience && 42 | user.uid === 'local' ? null : ( 43 | 44 | ), 45 | ) 46 | } 47 | 48 | 49 | 50 | ); 51 | }; 52 | 53 | export default PinnedVideo; 54 | -------------------------------------------------------------------------------- /src/Views/Usernames.tsx: -------------------------------------------------------------------------------- 1 | import React, {useContext} from 'react'; 2 | import RtmContext from '../Contexts/RtmContext'; 3 | import PropsContext, {UidInterface} from '../Contexts/PropsContext'; 4 | import {StyleSheet, Text} from 'react-native'; 5 | 6 | const Username: React.FC<{user: UidInterface; style?: React.CSSProperties}> = ( 7 | props, 8 | ) => { 9 | const {usernames} = useContext(RtmContext); 10 | const {rtmProps, styleProps} = useContext(PropsContext); 11 | const {user} = props; 12 | 13 | return rtmProps?.displayUsername ? ( 14 | 15 | {usernames[user.uid]} 16 | 17 | ) : ( 18 | 19 | ); 20 | }; 21 | 22 | const styles = StyleSheet.create({ 23 | username: { 24 | position: 'absolute', 25 | paddingHorizontal: 6, 26 | paddingVertical: 2, 27 | backgroundColor: '#007bffaa', 28 | color: '#fff', 29 | margin: 0, 30 | bottom: 0, 31 | right: 0, 32 | zIndex: 1, 33 | }, 34 | }); 35 | 36 | export default Username; 37 | -------------------------------------------------------------------------------- /src/hooks/useImageDelay.tsx: -------------------------------------------------------------------------------- 1 | //@ts-nocheck 2 | import React from 'react'; 3 | import isSafariBrowser from '../Utils/isSafariBrowser'; 4 | 5 | function useImageDelay( 6 | elementRef: any, 7 | delay: number | null, 8 | imageName: string, 9 | tintColor?: string, 10 | ) { 11 | React.useEffect(() => { 12 | if (!isSafariBrowser()) { 13 | return; 14 | } 15 | // The following block allows to repaint the icon 16 | let imageElement: any; 17 | if (elementRef && elementRef.current) { 18 | const imageContainer = elementRef.current as any; 19 | if (imageContainer && imageContainer.children.length > 0) { 20 | imageElement = imageContainer.children[0]; 21 | imageElement.style.display = 'none'; 22 | } 23 | } 24 | const timer = setTimeout(() => { 25 | if (imageElement) { 26 | imageElement.style.display = 'initial'; 27 | } 28 | }, delay); 29 | 30 | return () => clearTimeout(timer); 31 | // eslint-disable-next-line react-hooks/exhaustive-deps 32 | }, [imageName, tintColor]); 33 | } 34 | 35 | export default useImageDelay; 36 | --------------------------------------------------------------------------------