1import { Audio } from 'expo-av';
2import React from 'react';
3import { PixelRatio, Switch, Text, View } from 'react-native';
4
5import Button from '../../components/Button';
6import ListButton from '../../components/ListButton';
7
8interface Mode {
9  interruptionModeAndroid: number;
10  shouldDuckAndroid: boolean;
11  playThroughEarpieceAndroid: boolean;
12  staysActiveInBackground: boolean;
13}
14
15interface State {
16  modeToSet: Mode;
17  setMode: Mode;
18}
19
20// See: https://github.com/expo/expo/pull/10229#discussion_r490961694
21// eslint-disable-next-line @typescript-eslint/ban-types
22export default class AudioModeSelector extends React.Component<{}, State> {
23  readonly state: State = {
24    modeToSet: {
25      interruptionModeAndroid: Audio.INTERRUPTION_MODE_ANDROID_DUCK_OTHERS,
26      shouldDuckAndroid: true,
27      playThroughEarpieceAndroid: false,
28      staysActiveInBackground: false,
29    },
30    setMode: {
31      interruptionModeAndroid: Audio.INTERRUPTION_MODE_ANDROID_DUCK_OTHERS,
32      shouldDuckAndroid: true,
33      playThroughEarpieceAndroid: false,
34      staysActiveInBackground: false,
35    },
36  };
37
38  _applyMode = async () => {
39    try {
40      await Audio.setAudioModeAsync({
41        ...this.state.modeToSet,
42        // iOS values don't matter, this is Android-only selector
43        allowsRecordingIOS: false,
44        playsInSilentModeIOS: false,
45        interruptionModeIOS: Audio.INTERRUPTION_MODE_IOS_DO_NOT_MIX,
46      });
47      const { modeToSet } = this.state;
48      this.setState({ setMode: modeToSet });
49    } catch (error) {
50      alert(error.message);
51    }
52  };
53
54  _modesEqual = (modeA: Mode, modeB: Mode) =>
55    modeA.interruptionModeAndroid === modeB.interruptionModeAndroid &&
56    modeA.playThroughEarpieceAndroid === modeB.playThroughEarpieceAndroid &&
57    modeA.shouldDuckAndroid === modeB.shouldDuckAndroid &&
58    modeA.staysActiveInBackground === modeB.staysActiveInBackground;
59
60  _setMode = (interruptionModeAndroid: number) => () =>
61    this.setState(state => ({ modeToSet: { ...state.modeToSet, interruptionModeAndroid } }));
62
63  _renderToggle = ({
64    title,
65    disabled,
66    valueName,
67    value,
68  }: {
69    title: string;
70    disabled?: boolean;
71    valueName:
72      | 'interruptionModeAndroid'
73      | 'shouldDuckAndroid'
74      | 'playThroughEarpieceAndroid'
75      | 'staysActiveInBackground';
76    value?: boolean;
77  }) => (
78    <View
79      style={{
80        flexDirection: 'row',
81        alignItems: 'center',
82        justifyContent: 'space-between',
83        paddingVertical: 5,
84        borderBottomWidth: 1.0 / PixelRatio.get(),
85        borderBottomColor: '#cccccc',
86      }}>
87      <Text style={{ flex: 1, fontSize: 16 }}>{title}</Text>
88      <Switch
89        disabled={disabled}
90        value={value !== undefined ? value : Boolean(this.state.modeToSet[valueName])}
91        onValueChange={() =>
92          this.setState(state => ({
93            modeToSet: { ...state.modeToSet, [valueName]: !state.modeToSet[valueName] },
94          }))
95        }
96      />
97    </View>
98  );
99
100  _renderModeSelector = ({
101    title,
102    disabled,
103    value,
104  }: {
105    title: string;
106    disabled?: boolean;
107    value: number;
108  }) => (
109    <ListButton
110      disabled={disabled}
111      title={`${this.state.modeToSet.interruptionModeAndroid === value ? '✓ ' : ''}${title}`}
112      onPress={this._setMode(value)}
113    />
114  );
115
116  render() {
117    return (
118      <View style={{ marginTop: 5 }}>
119        {this._renderToggle({
120          title: 'Should be ducked',
121          valueName: 'shouldDuckAndroid',
122        })}
123        {this._renderToggle({
124          title: 'Play through earpiece',
125          valueName: 'playThroughEarpieceAndroid',
126        })}
127        {this._renderToggle({
128          title: 'Stay active in background',
129          valueName: 'staysActiveInBackground',
130        })}
131        {this._renderModeSelector({
132          title: 'Do not mix',
133          value: Audio.INTERRUPTION_MODE_ANDROID_DO_NOT_MIX,
134        })}
135        {this._renderModeSelector({
136          title: 'Duck others',
137          value: Audio.INTERRUPTION_MODE_ANDROID_DUCK_OTHERS,
138        })}
139        <Button
140          title="Apply changes"
141          onPress={this._applyMode}
142          style={{ marginTop: 10 }}
143          disabled={this._modesEqual(this.state.modeToSet, this.state.setMode)}
144        />
145      </View>
146    );
147  }
148}
149