App.tsx (6477B)
1 import * as React from 'react'; 2 import { 3 SafeAreaView, 4 View, 5 Text, 6 Image, 7 TouchableOpacity, 8 StyleSheet, 9 Keyboard, 10 Dimensions, 11 Platform 12 } from 'react-native'; 13 import { connect, ConnectedProps } from 'react-redux'; 14 15 import DrawerLayout from 'react-native-drawer-layout-polyfill'; 16 17 import BufferGate from './buffers/ui/BufferGate'; 18 import BufferList from './buffers/ui/BufferList'; 19 import { StoreState } from '../store'; 20 import { registerForPushNotificationsAsync } from '../lib/helpers/push-notifications'; 21 22 const connector = connect((state: StoreState) => { 23 const currentBufferId = state.app.currentBufferId; 24 const currentBuffer = 25 (currentBufferId && state.buffers[currentBufferId]) || null; 26 27 const numHighlights = Object.values(state.hotlists).reduce( 28 (sum, hlist) => sum + hlist.highlight, 29 0 30 ); 31 32 return { 33 buffers: state.buffers, 34 currentBufferId: currentBuffer && currentBufferId, 35 currentBuffer, 36 hasHighlights: numHighlights > 0 37 }; 38 }); 39 40 type PropsFromRedux = ConnectedProps<typeof connector>; 41 42 type Props = PropsFromRedux & { 43 disconnect: () => void; 44 fetchBufferInfo: (bufferId: string) => void; 45 sendMessageToBuffer: (fullBufferName: string, message: string) => void; 46 clearHotlistForBuffer: (fullBufferName: string) => void; 47 }; 48 49 interface State { 50 showTopic: boolean; 51 drawerWidth: number; 52 } 53 54 class App extends React.Component<Props, State> { 55 drawer: DrawerLayout; 56 57 drawerWidth = () => { 58 /* 59 * Default drawer width is screen width - header height 60 * with a max width of 280 on mobile and 320 on tablet 61 * https://material.io/guidelines/patterns/navigation-drawer.html 62 */ 63 const { height, width } = Dimensions.get('window'); 64 const smallerAxisSize = Math.min(height, width); 65 const isLandscape = width > height; 66 const isTablet = smallerAxisSize >= 600; 67 const appBarHeight = Platform.OS === 'ios' ? (isLandscape ? 32 : 44) : 56; 68 const maxWidth = isTablet ? 320 : 280; 69 70 return Math.min(smallerAxisSize - appBarHeight, maxWidth); 71 }; 72 73 state: State = { 74 showTopic: false, 75 drawerWidth: this.drawerWidth() 76 }; 77 78 changeCurrentBuffer = (buffer: WeechatBuffer) => { 79 const { currentBufferId, fetchBufferInfo } = this.props; 80 81 this.drawer.closeDrawer(); 82 if (currentBufferId !== buffer.id) { 83 this.props.dispatch({ 84 type: 'CHANGE_CURRENT_BUFFER', 85 bufferId: buffer.id 86 }); 87 this.props.clearHotlistForBuffer(buffer.full_name); 88 fetchBufferInfo(buffer.id); 89 } 90 }; 91 92 toggleShowTopic = () => { 93 this.setState((state) => ({ 94 showTopic: !state.showTopic 95 })); 96 }; 97 98 openDrawer = () => { 99 this.drawer.openDrawer(); 100 Keyboard.dismiss(); 101 }; 102 103 sendMessage = (message: string) => { 104 const { currentBuffer, sendMessageToBuffer } = this.props; 105 106 sendMessageToBuffer(currentBuffer.full_name, message); 107 }; 108 109 updateWidth = () => { 110 if (this.state.drawerWidth !== this.drawerWidth()) { 111 this.setState({ drawerWidth: this.drawerWidth() }); 112 } 113 }; 114 115 componentDidMount() { 116 Dimensions.addEventListener('change', this.updateWidth); 117 118 const { currentBufferId, fetchBufferInfo } = this.props; 119 if (currentBufferId) { 120 fetchBufferInfo(currentBufferId); 121 } else { 122 this.drawer.openDrawer(); 123 } 124 125 registerForPushNotificationsAsync(); 126 } 127 128 componentWillUnmount() { 129 Dimensions.removeEventListener('change', this.updateWidth); 130 } 131 132 componentDidUpdate(prevProps: Props) { 133 const { currentBufferId } = this.props; 134 if (currentBufferId !== prevProps.currentBufferId && !currentBufferId) { 135 this.drawer.openDrawer(); 136 } 137 } 138 139 render() { 140 const { 141 buffers, 142 currentBufferId, 143 currentBuffer, 144 hasHighlights 145 } = this.props; 146 147 const { showTopic, drawerWidth } = this.state; 148 149 const sidebar = () => ( 150 <BufferList 151 buffers={Object.values(buffers).sort((a, b) => a.number - b.number)} 152 currentBufferId={currentBufferId} 153 onSelectBuffer={this.changeCurrentBuffer} 154 /> 155 ); 156 157 return ( 158 <View style={styles.container}> 159 <DrawerLayout 160 ref={(d: DrawerLayout) => (this.drawer = d)} 161 renderNavigationView={sidebar} 162 keyboardDismissMode={'on-drag'} 163 drawerWidth={drawerWidth} 164 useNativeAnimations={true} 165 > 166 <SafeAreaView style={styles.container}> 167 <View style={styles.topbar}> 168 <View style={styles.channels}> 169 <TouchableOpacity 170 style={styles.channelsButton} 171 onPress={this.openDrawer} 172 > 173 <Text 174 style={[ 175 styles.channelsButtonText, 176 hasHighlights && { 177 color: '#ffcf7f' 178 } 179 ]} 180 > 181 # 182 </Text> 183 </TouchableOpacity> 184 </View> 185 <TouchableOpacity onPress={this.toggleShowTopic}> 186 <Text style={styles.topbarText}> 187 {currentBuffer && currentBuffer.short_name} 188 </Text> 189 </TouchableOpacity> 190 <View style={styles.channels}> 191 <TouchableOpacity 192 style={styles.channelsButton} 193 onPress={this.props.disconnect} 194 > 195 <Image source={require('./icons/eject.png')} /> 196 </TouchableOpacity> 197 </View> 198 </View> 199 <BufferGate 200 showTopic={showTopic} 201 sendMessage={this.sendMessage} 202 bufferId={currentBufferId} 203 /> 204 </SafeAreaView> 205 </DrawerLayout> 206 </View> 207 ); 208 } 209 } 210 211 export default connector(App); 212 213 const styles = StyleSheet.create({ 214 topbar: { 215 flexDirection: 'row', 216 backgroundColor: '#333', 217 justifyContent: 'space-between', 218 alignItems: 'center', 219 paddingBottom: 10 220 }, 221 channels: { 222 paddingHorizontal: 5 223 }, 224 channelsButton: { 225 paddingVertical: 5, 226 paddingHorizontal: 10, 227 width: 40 228 }, 229 channelsButtonText: { 230 textAlign: 'center', 231 fontSize: 20, 232 fontFamily: 'Gill Sans', 233 color: '#eee', 234 fontWeight: 'bold' 235 }, 236 topbarText: { 237 color: '#eee', 238 fontFamily: 'Thonburi', 239 fontWeight: 'bold', 240 fontSize: 15 241 }, 242 container: { 243 flex: 1, 244 backgroundColor: '#333' 245 } 246 });