Handling States and Configs
Source Code: https://github.com/dyte-io/react-native-samples/tree/main/samples/create_your_own_ui
DyteMeeting component does a lot more than just providing the user interface.
It does the following things internally.
- Keeps a mapping of components and show them according to the preset's view_type such as group_call, webinar, and livestream.
 - Provides background color, text colors and other such CSS properties.
 - Maintains states of modals, sidebars between web-core & ui-kit
 - Shifts the control bar buttons to More menu if the screen size is small.
 - Passes config, states, translation, icon packs to all child components.
 - It is the target element that gets full screened on click of full screen toggle.
 - Joins the meeting automatically if showSetupScreen is false.
 
Since we are splitting DyteMeeting component in pieces, we need to do these ourselves now.
import React, {useEffect, useState} from 'react';
import {
  DyteProvider,
  useDyteClient,
  useDyteMeeting,
} from '@dytesdk/react-native-core';
import DyteClient from '@dytesdk/web-core';
import {
  DyteUIProvider,
  UIConfig,
  defaultConfig,
  generateConfig,
} from '@dytesdk/react-native-ui-kit';
import {DyteThemePresetV1} from '@dytesdk/web-core';
import {DyteStateListenersUtils} from './dyte-state-listeners';
import {CustomStates} from './types';
import {store} from './utils/store';
import {Provider} from 'react-redux';
function Meeting() {
  const {meeting} = useDyteMeeting();
  const [config, setConfig] = useState<UIConfig>(defaultConfig);
  const [states, setStates] = useState<CustomStates>({
    meeting: 'setup',
    sidebar: 'chat',
    activeMoreMenu: false,
    activeLeaveConfirmation: false,
    permissionGranted: true,
    prefs: {
      mirrorVideo: true,
      muteNotificationSounds: false,
      autoScroll: true,
    },
    designSystem: {
      colors: {
        brand: {
          300: '#497CFD',
          400: '#356EFD',
          500: '#2160FD',
          600: '#0D51FD',
          700: '#2160FD',
        },
        background: {
          1000: '#080808',
          900: '#1A1A1A',
          800: '#333333',
          700: '#4C4C4C',
          600: '#666666',
        },
        text: '#FFFFFF',
        textOnBrand: '#FFFFFF',
        videoBg: '#333333',
        success: '#83D017',
        danger: '#FF2D2D',
        warning: '#FFCD07',
      },
    },
  });
  useEffect(() => {
    async function setupMeetingConfigs() {
      const theme = meeting!.self.config;
      const generatedConfig = generateConfig(theme as DyteThemePresetV1, {});
      const newConfig = generatedConfig.config;
      setConfig({...newConfig});
      const stateListenersUtils = new DyteStateListenersUtils(
        () => meeting,
        () => states,
        () => setStates,
      );
      stateListenersUtils.addDyteEventListeners();
    }
      setupMeetingConfigs();
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [meeting]);
  return <CustomDyteMeetingUI meeting={meeting} config={config} states={states} setStates={setStates} />;
}
function CustomDyteMeetingUI({ meeting, config, states, setStates }: { meeting: DyteClient, config: UIConfig, states: CustomStates, setStates: SetStates}) {
  return (
    <View>
      <DyteText>Your Custom UI will come here </DyteText>
    </View>
  );
}
class DyteStateListenersUtils {
  getStates: () => CustomStates;
  getStateSetter: () => (newState: CustomStates) => void;
  getMeeting: () => DyteClient;
  get states() {
    return this.getStates();
  }
  get setGlobalStates() {
    return this.getStateSetter();
  }
  get meeting() {
    return this.getMeeting();
  }
  constructor(
    getMeeting: () => DyteClient,
    getGlobalStates: () => CustomStates,
    getGlobalStateSetter: () => (newState: CustomStates) => void,
  ) {
    this.getMeeting = getMeeting;
    this.getStates = getGlobalStates;
    this.getStateSetter = getGlobalStateSetter;
  }
  private updateStates(newState: CustomStates) {
    this.setGlobalStates((oldState: CustomStates) => {
      return {
        ...oldState,
        ...newState,
      };
    });
  }
  private roomJoinedListener = () => {
    this.updateStates({meeting: 'joined'});
  };
  private socketServiceRoomJoinedListener = () => {
    if (
      this.meeting.stage.status === 'ON_STAGE' ||
      this.meeting.stage.status === undefined
    ) {
      return;
    }
    this.updateStates({meeting: 'joined'});
  };
  private waitlistedListener = () => {
    this.updateStates({meeting: 'waiting'});
  };
  private roomLeftListener = ({state}: {state: RoomLeftState}) => {
    const states = this.states;
    if (states?.roomLeftState === 'disconnected') {
      this.updateStates({meeting: 'ended', roomLeftState: state});
      return;
    }
    this.updateStates({meeting: 'ended', roomLeftState: state});
  };
  private mediaPermissionUpdateListener = ({
    kind,
    message,
  }: {
    kind: any; // PermissionSettings['kind'];
    message: string;
  }) => {
    if (['audio', 'video'].includes(kind!)) {
      if (
        message === 'ACCEPTED' ||
        message === 'NOT_REQUESTED' ||
        this.states.activeDebugger
      ) {
        return;
      }
      const permissionModalSettings: any = {
        enabled: true,
        kind,
      };
      this.updateStates({activePermissionsMessage: permissionModalSettings});
    }
  };
  private joinStateAcceptedListener = () => {
    this.updateStates({activeJoinStage: true});
  };
  private handleChangingMeeting(destinationMeetingId: string) {
    this.updateStates({
      activeBreakoutRoomsManager: {
        ...this.states.activeBreakoutRoomsManager,
        active: this.states.activeBreakoutRoomsManager!.active,
        destinationMeetingId,
      },
    });
  }
  addDyteEventListeners() {
    if (this.meeting.meta.viewType === 'LIVESTREAM') {
      this.meeting.self.addListener(
        'socketServiceRoomJoined',
        this.socketServiceRoomJoinedListener,
      );
    }
    this.meeting.self.addListener('roomJoined', this.roomJoinedListener);
    this.meeting.self.addListener('waitlisted', this.waitlistedListener);
    this.meeting.self.addListener('roomLeft', this.roomLeftListener);
    this.meeting.self.addListener(
      'mediaPermissionUpdate',
      this.mediaPermissionUpdateListener,
    );
    this.meeting.self.addListener(
      'joinStageRequestAccepted',
      this.joinStateAcceptedListener,
    );
    if (this.meeting.connectedMeetings.supportsConnectedMeetings) {
      this.meeting.connectedMeetings.once(
        'changingMeeting',
        this.handleChangingMeeting,
      );
    }
  }
  cleanupDyteEventListeners() {}
}
Let's discuss the bits and pieces one by one.
const theme = meeting!.self.config;
const { config } = generateConfig(theme, meeting!);
In the above code snippets, we are generating configs using the preset configurations & meeting configs.
Post this, We are extending the config to pass the targetElement to full screen toggle and storing this config to be passed to child components.
setConfig({ ...config });
We need to also ensure that web-core & ui-kit states are in sync. Since we are handling states now, we will have to add web-core & ui-kit listeners.
To add react-native-core listeners, DyteStateListenersUtils class, is being used.
const stateListenersUtils = new DyteStateListenersUtils(
  () => meeting,
  () => states,
  () => setStates
);
stateListenersUtils.addDyteEventListeners();
To join the meeting, we are using await meeting.join();.
Now that we know the extra overhead that comes with splitting DyteMeeting component, let's start with showing custom UIs as per the meeting state.