Skip to content

Commit 3da4922

Browse files
committed
Merge branch 'isTablet'
2 parents ac08b35 + ddd954c commit 3da4922

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+5504
-9743
lines changed

app/client/mobile/App.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import AntIcon from 'react-native-vector-icons/AntDesign';
1212
import FeatherIcon from 'react-native-vector-icons/Feather';
1313
import CommunityIcon from 'react-native-vector-icons/MaterialCommunityIcons';
1414
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
15-
import {PaperPlaneRight, PlusSquare, CaretLeft, ChatTeardropSlash, ChatTeardrop, ArrowCounterClockwise, Camera, EnvelopeSimple, Warning, HouseSimple, Star, HardDrive, LockOpen, UserMinus, PlayCircle, StopCircle, UsersFour, DotsThreeCircle, VideoCamera, VideoCameraSlash, ArrowsInSimple, BellSlash, Phone, Microphone, MicrophoneSlash, FrameCorners, WarningCircle, Link, LinkBreak, AddressBook, ChatCircle, GearSix} from 'phosphor-react-native';
15+
import {SignOut, PaperPlaneRight, PlusSquare, CaretLeft, ChatTeardropSlash, ChatTeardrop, ArrowCounterClockwise, Camera, EnvelopeSimple, Warning, HouseSimple, Star, HardDrive, LockOpen, UserMinus, PlayCircle, StopCircle, UsersFour, DotsThreeCircle, VideoCamera, VideoCameraSlash, ArrowsInSimple, BellSlash, Phone, Microphone, MicrophoneSlash, FrameCorners, WarningCircle, Link, LinkBreak, AddressBook, ChatCircle, GearSix} from 'phosphor-react-native';
1616
import {useColorScheme} from 'react-native';
1717
import {MD3LightTheme, MD3DarkTheme, PaperProvider} from 'react-native-paper';
1818

@@ -119,15 +119,15 @@ const databagColors = {
119119
level6: 'rgb(200, 200, 200)',
120120
level7: 'rgb(200, 200, 200)',
121121
level8: 'transparent',
122-
level9: '#191919',
122+
level9: '#8FBEA7',
123123
level10: '#224433',
124124
level11: 'rgba(80, 80, 80, 0.5)',
125125
level12: 'rgba(96,096,96, 0.5)',
126126
},
127127
surfaceDisabled: 'rgba(225, 227, 223, 0.12)',
128128
onSurfaceDisabled: 'rgba(225, 227, 223, 0.38)',
129129
backdrop: 'rgba(42, 50, 45, 0.4)',
130-
base: 'rgb(0,0,0)',
130+
base: 'rgb(32,32,32)',
131131
bar: 'rgba(16, 10, 8, 0.8)',
132132
connected: '#44cc44',
133133
requested: '#EDB612',
@@ -228,6 +228,8 @@ function FontMix(props: {name: string, color: string}) {
228228
return <ChatTeardrop color={props.color} size={props.size} />;
229229
} else if (props.name === 'disable-chat') {
230230
return <ChatTeardropSlash color={props.color} size={props.size} />;
231+
} else if (props.name === 'logout') {
232+
return <SignOut color={props.color} size={props.size} />;
231233
} else {
232234
return <CommunityIcon {...props} />;
233235
}
Lines changed: 339 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,342 @@
1-
import React from 'react';
2-
import {AccountsSmall} from './AccountsSmall';
3-
import {AccountsLarge} from './AccountsLarge';
4-
import {LayoutSelector} from '../utils/LayoutSelector';
1+
import React, {useEffect, useState} from 'react';
2+
import {FlatList, View, Pressable, TouchableOpacity, Platform, Modal} from 'react-native';
3+
import {Text, Button, TextInput, Menu, IconButton, Divider, Surface, Icon, useTheme} from 'react-native-paper';
4+
import {useAccounts} from './useAccounts.hook';
5+
import {styles} from './Accounts.styled';
6+
import {Card} from '../card/Card';
7+
import {Confirm} from '../confirm/Confirm';
8+
import {BlurView} from '@react-native-community/blur';
9+
import Clipboard from '@react-native-clipboard/clipboard';
10+
import {SafeAreaView} from 'react-native-safe-area-context';
511

6-
type AccountsProps = {
7-
setup: () => void;
8-
};
12+
export function Accounts() {
13+
const {state, actions} = useAccounts();
14+
const theme = useTheme();
15+
const [failed, setFailed] = useState(false);
16+
const [remove, setRemove] = useState(null as null | number);
17+
const [removing, setRemoving] = useState(null);
18+
const [blocking, setBlocking] = useState(null);
19+
const [loading, setLoading] = useState(false);
20+
const [accessing, setAccessing] = useState(null);
21+
const [adding, setAdding] = useState(false);
22+
const [showAccessModal, setShowAccessModal] = useState(false);
23+
const [showAddModal, setShowAddModal] = useState(false);
24+
const [token, setToken] = useState('');
25+
const [tokenCopy, setTokenCopy] = useState(false);
26+
const [more, setMore] = useState(null as null | string);
27+
const layout = Platform.isPad ? 'large' : 'small';
928

10-
export function Accounts({setup}: AccountsProps) {
11-
return <LayoutSelector SmallComponent={AccountsSmall} LargeComponent={AccountsLarge} props={{setup}} />;
29+
useEffect(() => {
30+
loadAccounts();
31+
// eslint-disable-next-line react-hooks/exhaustive-deps
32+
}, []);
33+
34+
const loadAccounts = async () => {
35+
if (!loading) {
36+
setLoading(true);
37+
try {
38+
await actions.reload();
39+
} catch (err) {
40+
console.log(err);
41+
}
42+
setLoading(false);
43+
}
44+
};
45+
46+
const accessAccount = async (accountId: number) => {
47+
setMore(null);
48+
if (!accessing) {
49+
setAccessing(accountId);
50+
try {
51+
const access = await actions.accessAccount(accountId);
52+
setToken(access);
53+
setShowAccessModal(true);
54+
} catch (err) {
55+
console.log(err);
56+
setFailed(true);
57+
}
58+
setAccessing(null);
59+
}
60+
};
61+
62+
const addAccount = async () => {
63+
if (!adding) {
64+
setAdding(true);
65+
try {
66+
const access = await actions.addAccount();
67+
setToken(access);
68+
setShowAddModal(true);
69+
} catch (err) {
70+
console.log(err);
71+
setFailed(true);
72+
}
73+
setAdding(false);
74+
}
75+
};
76+
77+
const failedParams = {
78+
title: state.strings.operationFailed,
79+
prompt: state.strings.tryAgain,
80+
close: {
81+
label: state.strings.close,
82+
action: () => {
83+
setFailed(false);
84+
},
85+
},
86+
};
87+
88+
const blockAccount = async (accountId: number, block: boolean) => {
89+
setMore(null);
90+
if (!blocking) {
91+
setBlocking(accountId);
92+
try {
93+
await actions.blockAccount(accountId, block);
94+
} catch (err) {
95+
console.log(err);
96+
setFailed(true);
97+
}
98+
setBlocking(null);
99+
}
100+
};
101+
102+
const showRemove = (accountId: number) => {
103+
setMore(null);
104+
actions.setRemove('');
105+
setRemove(accountId);
106+
};
107+
108+
const removeAccount = async (accountId: number) => {
109+
if (!removing) {
110+
setRemoving(true);
111+
try {
112+
await actions.removeAccount(accountId);
113+
} catch (err) {
114+
console.log(err);
115+
setFailed(true);
116+
}
117+
setRemoving(false);
118+
setRemove(null);
119+
}
120+
};
121+
122+
const copyToken = async () => {
123+
if (!tokenCopy) {
124+
setTokenCopy(true);
125+
Clipboard.setString(token);
126+
setTimeout(() => {
127+
setTokenCopy(false);
128+
}, 2000);
129+
}
130+
};
131+
132+
return (
133+
<View style={styles.component}>
134+
<Surface elevation={1} mode="flat" style={styles.fullSurface}>
135+
<Surface elevation={layout === 'large' ? 4 : 9} mode="flat">
136+
<SafeAreaView edges={['top', 'left', 'right']}>
137+
<View style={styles.headerLayout}>
138+
<Surface mode="flat" elevation={0} style={styles.searchSurface}>
139+
<TextInput
140+
dense={true}
141+
style={styles.input}
142+
outlineStyle={styles.inputBorder}
143+
autoCapitalize="none"
144+
autoComplete="off"
145+
autoCorrect={false}
146+
mode="outlined"
147+
placeholder={state.strings.searchAccounts}
148+
left={<TextInput.Icon style={styles.icon} icon="search" />}
149+
value={state.filter}
150+
onChangeText={value => actions.setFilter(value)}
151+
/>
152+
</Surface>
153+
<Button icon="user-plus" loading={adding} mode="contained" textColor="white" style={styles.newContactButton} onPress={addAccount}>
154+
{state.strings.new}
155+
</Button>
156+
</View>
157+
</SafeAreaView>
158+
</Surface>
159+
160+
{state.members.length !== 0 && (
161+
<FlatList
162+
style={styles.contacts}
163+
contentContainerStyle={styles.listPad}
164+
data={state.filtered}
165+
initialNumToRender={32}
166+
showsVerticalScrollIndicator={false}
167+
renderItem={({item}) => {
168+
const action = (
169+
<Menu
170+
mode={Platform.OS === 'ios' ? 'flat' : 'elevated'}
171+
elevation={Platform.OS === 'ios' ? 8 : 2}
172+
key="actions"
173+
visible={more === item.accountId}
174+
onDismiss={() => setMore(null)}
175+
anchor={
176+
<IconButton
177+
style={styles.action}
178+
loading={accessing === item.accountId || blocking === item.accountId || removing === item.accountId}
179+
icon="dots-horizontal-circle-outline"
180+
size={22}
181+
onPress={() => setMore(item.accountId)}
182+
/>
183+
}>
184+
{Platform.OS === 'ios' && (
185+
<Surface elevation={11}>
186+
<BlurView style={styles.blur} blurType={theme.colors.name} blurAmount={8} reducedTransparencyFallbackSize={theme.colors.name} />
187+
<Pressable key="storage" style={styles.menuOption}>
188+
<Icon style={styles.button} source="hard-drive" size={24} color={theme.colors.secondary} />
189+
<Text style={{color: theme.colors.secondary}}>{`${Math.floor(item.storageUsed / 1048576)} MB`}</Text>
190+
</Pressable>
191+
<Pressable key="access" style={styles.menuOption} onPress={() => accessAccount(item.accountId)}>
192+
<Icon style={styles.button} source="reset" size={24} color={theme.colors.onSecondary} />
193+
<Text>{state.strings.resetAccount}</Text>
194+
</Pressable>
195+
{item.disabled && (
196+
<Pressable key="enable" style={styles.menuOption} onPress={() => blockAccount(item.accountId, false)}>
197+
<Icon style={styles.button} source="enable-chat" size={24} color={theme.colors.onSecondary} />
198+
<Text>{state.strings.enableAccount}</Text>
199+
</Pressable>
200+
)}
201+
{!item.disabled && (
202+
<Pressable key="disable" style={styles.menuOption} onPress={() => blockAccount(item.accountId, true)}>
203+
<Icon style={styles.button} source="disable-chat" size={24} color={theme.colors.onSecondary} />
204+
<Text>{state.strings.disableAccount}</Text>
205+
</Pressable>
206+
)}
207+
<Pressable key="delete" style={styles.menuOption} onPress={() => showRemove(item.accountId)}>
208+
<Icon style={styles.button} source="trash-2" size={24} color={theme.colors.onSecondary} />
209+
<Text>{state.strings.deleteAccount}</Text>
210+
</Pressable>
211+
</Surface>
212+
)}
213+
{Platform.OS !== 'ios' && <Menu.Item key="storage" disabled={true} leadingIcon="hard-drive" title={`${Math.floor(item.storageUsed / 1048576)} MB`} />}
214+
{Platform.OS !== 'ios' && <Menu.Item key="access" leadingIcon="reset" onPress={() => accessAccount(item.accountId)} title={state.strings.resetAccount} />}
215+
{Platform.OS !== 'ios' && item.disabled && (
216+
<Menu.Item key="enable" leadingIcon="enable-chat" onPress={() => blockAccount(item.accountId, false)} title={state.strings.enableAccount} />
217+
)}
218+
{Platform.OS !== 'ios' && !item.disabled && (
219+
<Menu.Item key="disable" leadingIcon="disable-chat" onPress={() => blockAccount(item.accountId, true)} title={state.strings.disableAccount} />
220+
)}
221+
{Platform.OS !== 'ios' && <Menu.Item key="delete" leadingIcon="trash-2" onPress={() => showRemove(item.accountId)} title={state.strings.deleteAccount} />}
222+
</Menu>
223+
);
224+
return (
225+
<Card
226+
containerStyle={{...styles.contact, handle: {...styles.contactHandle, color: theme.colors.onSecondary}}}
227+
imageUrl={item.imageUrl}
228+
name={item.name}
229+
handle={item.handle}
230+
node={item.node}
231+
placeholder={state.strings.name}
232+
select={() => {}}
233+
actions={[action]}
234+
/>
235+
);
236+
}}
237+
/>
238+
)}
239+
{state.members.length === 0 && (
240+
<View style={styles.empty}>
241+
<Text style={styles.label}>{state.strings.noAccounts}</Text>
242+
</View>
243+
)}
244+
</Surface>
245+
<Confirm show={failed} params={failedParams} />
246+
<Modal animationType="fade" transparent={true} supportedOrientations={['portrait', 'landscape']} visible={showAccessModal} onRequestClose={() => setShowAccessModal(false)}>
247+
<View style={styles.modal}>
248+
<BlurView style={styles.blur} blurType={theme.colors.name} blurAmount={2} reducedTransparencyFallbackColor="dark" />
249+
<View style={styles.modalArea}>
250+
<Surface elevation={1} style={{...styles.modalSurface, backgroundColor: theme.colors.elevation.level12}}>
251+
<View style={styles.modalContent}>
252+
<Text style={styles.modalLabel}>{state.strings.resetAccount}</Text>
253+
<Text style={styles.modalDescription}>{state.strings.accessingToken}</Text>
254+
<Divider style={styles.divider} />
255+
<View style={styles.secretText}>
256+
<Text style={styles.secret}>
257+
{token}
258+
</Text>
259+
<TouchableOpacity onPress={copyToken}>
260+
<Icon style={styles.secretIcon} size={20} source={tokenCopy ? 'check' : 'content-copy'} />
261+
</TouchableOpacity>
262+
</View>
263+
<View style={styles.modalControls}>
264+
<Button style={styles.modalControl} mode="contained" onPress={() => setShowAccessModal(false)}>
265+
{state.strings.close}
266+
</Button>
267+
</View>
268+
</View>
269+
</Surface>
270+
</View>
271+
</View>
272+
</Modal>
273+
<Modal animationType="fade" transparent={true} supportedOrientations={['portrait', 'landscape']} visible={showAddModal} onRequestClose={() => setShowAddModal(false)}>
274+
<View style={styles.modal}>
275+
<BlurView style={styles.blur} blurType={theme.colors.name} blurAmount={4} reducedTransparencyFallbackColor="dark" />
276+
<View style={styles.modalArea}>
277+
<Surface elevation={1} style={{...styles.modalSurface, backgroundColor: theme.colors.elevation.level12}}>
278+
<View style={styles.modalContent}>
279+
<Text style={styles.modalLabel}>{state.strings.addAccount}</Text>
280+
<Text style={styles.modalDescription}>{state.strings.addingToken}</Text>
281+
<Divider style={styles.divider} />
282+
<View style={styles.secretText}>
283+
<Text style={styles.secret}>
284+
{token}
285+
</Text>
286+
<TouchableOpacity onPress={copyToken}>
287+
<Icon style={styles.secretIcon} size={20} source={tokenCopy ? 'check' : 'content-copy'} />
288+
</TouchableOpacity>
289+
</View>
290+
<View style={styles.modalControls}>
291+
<Button style={styles.modalControl} textColor="white" mode="contained" onPress={() => setShowAddModal(false)}>
292+
{state.strings.close}
293+
</Button>
294+
</View>
295+
</View>
296+
</Surface>
297+
</View>
298+
</View>
299+
</Modal>
300+
<Modal animationType="fade" transparent={true} supportedOrientations={['portrait', 'landscape']} visible={Boolean(remove)} onRequestClose={() => setRemove(false)}>
301+
<View style={styles.modal}>
302+
<BlurView style={styles.blur} blurType={theme.colors.name} blurAmount={4} reducedTransparencyFallbackColor="dark" />
303+
<View style={styles.modalArea}>
304+
<Surface elevation={2} style={{...styles.modalSurface, backgroundColor: theme.colors.elevation.level12}}>
305+
<View style={styles.modalContent}>
306+
<Text style={styles.modalLabel}>{state.strings.deleteAccount}</Text>
307+
308+
<TextInput
309+
style={{...styles.input, backgroundColor: theme.colors.elevation.level1}}
310+
mode="outlined"
311+
outlineStyle={{...styles.modalInputBorder, borderColor: theme.colors.outlineVariant}}
312+
autoCapitalize="none"
313+
autoComplete="off"
314+
autoCorrect={false}
315+
value={state.remove}
316+
placeholder={state.strings.typeDelete}
317+
onChangeText={value => actions.setRemove(value)}
318+
/>
319+
320+
<View style={styles.modalControls}>
321+
<Button style={{...styles.modalControl, borderColor: theme.colors.outlineVariant}} textColor={theme.colors.onSecondary} mode="outlined" onPress={() => setRemove(null)}>
322+
{state.strings.cancel}
323+
</Button>
324+
<Button
325+
style={{...styles.modalControl, ...styles.modalControlLarge, backgroundColor: theme.colors.offsync}}
326+
textColor="white"
327+
mode="contained"
328+
loading={removing}
329+
icon="trash-2"
330+
disabled={state.remove !== state.strings.delete}
331+
onPress={() => removeAccount(remove)}>
332+
{state.strings.deleteAccount}
333+
</Button>
334+
</View>
335+
</View>
336+
</Surface>
337+
</View>
338+
</View>
339+
</Modal>
340+
</View>
341+
);
12342
}

0 commit comments

Comments
 (0)