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  interruptionModeIOS: number;
10  playsInSilentModeIOS: boolean;
11  allowsRecordingIOS: 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      interruptionModeIOS: Audio.INTERRUPTION_MODE_IOS_MIX_WITH_OTHERS,
26      playsInSilentModeIOS: false,
27      allowsRecordingIOS: false,
28      staysActiveInBackground: false,
29    },
30    setMode: {
31      interruptionModeIOS: Audio.INTERRUPTION_MODE_IOS_MIX_WITH_OTHERS,
32      playsInSilentModeIOS: false,
33      allowsRecordingIOS: false,
34      staysActiveInBackground: false,
35    },
36  };
37
38  _applyMode = async () => {
39    try {
40      await Audio.setAudioModeAsync({
41        ...this.state.modeToSet,
42        // Android values don't matter, this is iOS-only selector
43        shouldDuckAndroid: false,
44        interruptionModeAndroid: Audio.INTERRUPTION_MODE_ANDROID_DO_NOT_MIX,
45        playThroughEarpieceAndroid: false,
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.interruptionModeIOS === modeB.interruptionModeIOS &&
56    modeA.playsInSilentModeIOS === modeB.playsInSilentModeIOS &&
57    modeA.allowsRecordingIOS === modeB.allowsRecordingIOS &&
58    modeA.staysActiveInBackground === modeB.staysActiveInBackground;
59
60  _setMode = (interruptionModeIOS: number) => () =>
61    this.setState(state => ({ modeToSet: { ...state.modeToSet, interruptionModeIOS } }));
62
63  _renderToggle = ({
64    title,
65    disabled,
66    valueName,
67    value,
68  }: {
69    title: string;
70    disabled?: boolean;
71    valueName:
72      | 'interruptionModeIOS'
73      | 'playsInSilentModeIOS'
74      | 'allowsRecordingIOS'
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.interruptionModeIOS === value ? '✓ ' : ''}${title}`}
112      onPress={this._setMode(value)}
113    />
114  );
115
116  render() {
117    return (
118      <View style={{ marginTop: 5 }}>
119        {this._renderToggle({
120          title: 'Plays in silent mode',
121          valueName: 'playsInSilentModeIOS',
122        })}
123        {this._renderToggle({
124          title: 'Allows recording',
125          valueName: 'allowsRecordingIOS',
126          disabled: !this.state.modeToSet.playsInSilentModeIOS,
127          value: !this.state.modeToSet.playsInSilentModeIOS ? false : undefined,
128        })}
129        {this._renderToggle({
130          title: 'Stay active in background',
131          valueName: 'staysActiveInBackground',
132          disabled: !this.state.modeToSet.playsInSilentModeIOS,
133          value: !this.state.modeToSet.playsInSilentModeIOS ? false : undefined,
134        })}
135        {this._renderModeSelector({
136          title: 'Mix with others',
137          value: Audio.INTERRUPTION_MODE_IOS_MIX_WITH_OTHERS,
138        })}
139        {this._renderModeSelector({
140          title: 'Do not mix',
141          value: Audio.INTERRUPTION_MODE_IOS_DO_NOT_MIX,
142        })}
143        {this._renderModeSelector({
144          disabled: this.state.modeToSet.playsInSilentModeIOS === false,
145          title: 'Duck others',
146          value: Audio.INTERRUPTION_MODE_IOS_DUCK_OTHERS,
147        })}
148        <Button
149          title="Apply changes"
150          onPress={this._applyMode}
151          style={{ marginTop: 10 }}
152          disabled={this._modesEqual(this.state.modeToSet, this.state.setMode)}
153        />
154      </View>
155    );
156  }
157}
158