<|start_filename|>ios/RNDemo/AppDelegate.h<|end_filename|>
//
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: MIT-0
//

#import <React/RCTBridgeDelegate.h>
#import <UIKit/UIKit.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate, RCTBridgeDelegate>

@property (nonatomic, strong) UIWindow *window;

@end


<|start_filename|>src/components/MuteButton.js<|end_filename|>
/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: MIT-0
 */

import React from 'react'
import {TouchableOpacity, Image} from 'react-native'
import styles from '../Style';

let mutedImg = require('../assets/microphone-muted.png');
let normalImg = require('../assets/microphone.png');

export const MuteButton = ({muted, onPress}) => {
  return (  
  <TouchableOpacity 
    onPress={() => {
      onPress();
  }}>
    <Image
      style={styles.meetingButton}
      source={muted ? mutedImg : normalImg}
    />
  </TouchableOpacity>
  ) 
}


<|start_filename|>src/containers/Login.js<|end_filename|>
/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: MIT-0
 */

import React from 'react';
import { View, Text, TextInput, Button, Alert } from 'react-native';
import styles from '../Style';

export class Login extends React.Component {
  constructor() {
    super();
    this.meetingName = "";
    this.userName = "";
  }

  startMeeting = () => {
    if (!this.meetingName || !this.userName) {
      Alert.alert("Meeting name and user name can not be empty");
    } else {
      this.props.onSubmit(this.meetingName, this.userName);
    }
  }

  renderForm() {
    return (
      <React.Fragment>
        <TextInput style={styles.inputBox} placeholder="Meeting ID" onChangeText={(val) => this.meetingName = val.trim()} />
        <TextInput style={styles.inputBox} placeholder="<NAME>" onChangeText={(val) => this.userName = val.trim()} />
        <Button title="Start" onPress={this.startMeeting} />
      </React.Fragment>
    );
  }

  renderLoading() {
    return <Text>Loading, please wait...</Text>
  }

  render() {
    return (
      <View style={styles.container}>
        <Text style={styles.title}>Mobile SDK Demo</Text>
        <Text style={styles.subtitle}>powered by React Native</Text>
        { !!this.props.isLoading ? this.renderLoading() : this.renderForm() }
      </View>
    );
  }
}


<|start_filename|>android/app/src/main/java/com/amazonaws/services/chime/rndemo/NativeMobileSDKBridgePackage.kt<|end_filename|>
/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: MIT-0
 */

package com.amazonaws.services.chime.rndemo

import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ViewManager
import java.util.Collections.singletonList

import kotlin.collections.ArrayList

class NativeMobileSDKBridgePackage : ReactPackage {
    override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
        val eventEmitter = RNEventEmitter(reactContext)
        val meetingObservers = MeetingObservers(eventEmitter)

        val modules = ArrayList<NativeModule>()
        modules.add(NativeMobileSDKBridge(reactContext, eventEmitter, meetingObservers))
        return modules
    }

    override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
        return singletonList(RNVideoViewManager())
    }
}


<|start_filename|>ios/RNDemo/RNVideoViewManager.h<|end_filename|>
//
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: MIT-0
//

#import <UIKit/UIKit.h>
#import <React/RCTViewManager.h>
#import <AmazonChimeSDK/AmazonChimeSDK-Swift.h>
#import <Foundation/Foundation.h>

@interface RNVideoViewManager : RCTViewManager
@end


<|start_filename|>__tests__/App-test.js<|end_filename|>
/**
 * @format
 */

import 'react-native';
import React from 'react';
import { Login } from '../src/containers/Login';

// Note: test renderer must be required after react-native.
import renderer from 'react-test-renderer';

test('renders correctly', () => {
  const tree = renderer.create(<Login />).toJSON();
  // jest snapshot testing: https://jestjs.io/docs/en/snapshot-testing
  expect(tree).toMatchSnapshot()
});


<|start_filename|>src/components/CameraButton.js<|end_filename|>
/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: MIT-0
 */

import React from 'react'
import {TouchableOpacity, Image} from 'react-native'
import styles from '../Style';

let videoDisabledImg = require('../assets/video-disabled.png');
let videoImg = require('../assets/video.png');

export const CameraButton = ({disabled, onPress}) => {
  return (  
  <TouchableOpacity 
    onPress={() => {
      onPress();
  }}>
    <Image
      style={styles.meetingButton}
      source={disabled ? videoDisabledImg : videoImg}
    />
  </TouchableOpacity>
  ) 
}


<|start_filename|>ios/RNDemo/NativeMobileSDKBridge.h<|end_filename|>
//
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: MIT-0
//

#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>
#import <AmazonChimeSDK/AmazonChimeSDK-Swift.h>

#define kMeetingId @"MeetingId"
#define kExternalMeetingId @"ExternalMeetingId"
#define kMediaRegion @"MediaRegion"

#define kAttendeeId @"AttendeeId"
#define kExternalUserId @"ExternalUserId"
#define kJoinToken @"JoinToken"

#define kMediaPlacement @"MediaPlacement"
#define kAudioFallbackUrl @"AudioFallbackUrl"
#define kAudioHostUrl @"AudioHostUrl"
#define kTurnControlUrl @"TurnControlUrl"
#define kSignalingUrl @"SignalingUrl"

@interface NativeMobileSDKBridge : RCTEventEmitter <RCTBridgeModule>
@end


<|start_filename|>src/components/AttendeeItem.js<|end_filename|>
/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: MIT-0
 */

import React from 'react';
import { View, Text, Image } from 'react-native';
import styles from '../Style';

let mutedImg = require('../assets/microphone-muted.png');

export const AttendeeItem = ({ attendeeName, muted }) => {
  return (
    <View style={styles.attendeeContainer}>
      <Text >{attendeeName}</Text>
      {
        muted && <Image source={mutedImg} style={styles.attendeeMuteImage}/>
      }
    </View>
  );
}


<|start_filename|>src/components/RNVideoRenderView.js<|end_filename|>
/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: MIT-0
 */

import PropTypes from 'prop-types';
import React from 'react';
import { requireNativeComponent, findNodeHandle } from 'react-native';
import { NativeFunction } from '../utils/Bridge';

export class RNVideoRenderView extends React.Component {

  componentDidMount() {
    // we need to delay the bind video 
    // Because "componentDidMount" will be called "immediately after the initial rendering occurs"
    // This is *before* RCTUIManager add this view to register (so that viewForReactTag() can return a view)
    // So we need to dispatch bindVideoView after this function complete
    setTimeout(() => {
      NativeFunction.bindVideoView(findNodeHandle(this), this.props.tileId);
    });
  }

  componentWillUnmount() {
    NativeFunction.unbindVideoView(this.props.tileId);
  }

  render() {
    return <RNVideoRenderViewNative {...this.props} />;
  }
}

RNVideoRenderView.propTypes = {
  /**
   * A int value to identifier the Video view, will be used to bind video stream later
   */
  tileId: PropTypes.number,
};

var RNVideoRenderViewNative = requireNativeComponent('RNVideoView', RNVideoRenderView);


<|start_filename|>android/app/src/main/java/com/amazonaws/services/chime/rndemo/MainActivity.java<|end_filename|>
/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: MIT-0
 */

package com.amazonaws.services.chime.rndemo;

import com.facebook.react.ReactActivity;

public class MainActivity extends ReactActivity {

    /**
     * Returns the name of the main component registered from JavaScript. This is used to schedule
     * rendering of the component.
     */
    @Override
    protected String getMainComponentName() {
        return "RNDemo";
    }
}


<|start_filename|>src/App.js<|end_filename|>
/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: MIT-0
 */

import React from 'react';
import {
  SafeAreaView,
  StatusBar,
  Alert,
  Text
} from 'react-native';
import { Login } from './containers/Login';
import { Meeting } from './containers/Meeting';
import { createMeetingRequest } from './utils/Api';
import { getSDKEventEmitter, MobileSDKEvent, NativeFunction } from './utils/Bridge';
import styles from './Style';

class App extends React.Component {
  constructor() {
    super();

    this.state = {
      isInMeeting: false,
      isLoading: false,
      meetingTitle: '',
      selfAttendeeId: ''
    }
  }

  componentDidMount() {
    this.onMeetingStartSubscription = getSDKEventEmitter().addListener(MobileSDKEvent.OnMeetingStart, () => {
      this.setState({ isInMeeting: true, isLoading: false });
    });

    this.onMeetingEndSubscription = getSDKEventEmitter().addListener(MobileSDKEvent.OnMeetingEnd, () => {
      this.setState({ isInMeeting: false, isLoading: false });
    });

    this.onErrorSubscription = getSDKEventEmitter().addListener(MobileSDKEvent.OnError, (message) => {
      Alert.alert("SDK Error", message);
    });
  }

  componentWillUnmount() {
    if (this.onMeetingEndSubscription) {
      this.onMeetingEndSubscription.remove();
    }
    if (this.onMeetingStartSubscription) {
      this.onMeetingStartSubscription.remove();
    }
    if (this.onErrorSubscription) {
      this.onErrorSubscription.remove();
    }
  }

  initializeMeetingSession = (meetingName, userName) => {
    this.setState({
      isLoading: true,
    })

    createMeetingRequest(meetingName, userName).then(meetingResponse => {
      this.setState({
        meetingTitle: meetingName,
        selfAttendeeId: meetingResponse.JoinInfo.Attendee.Attendee.AttendeeId
      })
      NativeFunction.startMeeting(meetingResponse.JoinInfo.Meeting.Meeting, meetingResponse.JoinInfo.Attendee.Attendee);
    }).catch(error => {
      Alert.alert("Unable to find meeting", `There was an issue finding that meeting. The meeting may have already ended, or your authorization may have expired.\n ${error}`);
      this.setState({ isLoading: false });
    });
  }

  renderRoute() {
    if (this.state.isInMeeting) {
      return <Meeting meetingTitle={this.state.meetingTitle} selfAttendeeId={this.state.selfAttendeeId} />;
    } else {
      return <Login isLoading={this.state.isLoading} onSubmit={(meetingName, userName) => this.initializeMeetingSession(meetingName, userName)} />;
    }
  }

  render() {
    return (
      <React.Fragment>
        <StatusBar />
        <SafeAreaView>
          { this.renderRoute() }
        </SafeAreaView>
      </React.Fragment>
    );
  }
}
export default App;


<|start_filename|>src/components/HangOffButton.js<|end_filename|>
/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: MIT-0
 */

import React from 'react'
import {TouchableOpacity, Image} from 'react-native'
import styles from '../Style';

export const HangOffButton = ({onPress}) => {
  return (  
  <TouchableOpacity 
    onPress={() => {
      onPress();
  }}>
    <Image
      style={styles.meetingButton}
      source={require('../assets/hang-off.png')}
    />
  </TouchableOpacity>
  ) 
}
