adding sticky headers where missing

This commit is contained in:
balzack 2024-11-09 10:22:53 -08:00
parent b8abef126a
commit 136fbf4b5f
12 changed files with 799 additions and 733 deletions

View File

@ -66,7 +66,7 @@ export function Access() {
autoCapitalize="none"
autoComplete="off"
autoCorrect={false}
label={state.strings.node}
label={state.strings.server}
value={state.node}
left={<TextInput.Icon style={styles.icon} icon="server" />}
onChangeText={value => actions.setNode(value)}
@ -131,7 +131,7 @@ export function Access() {
autoCapitalize="none"
autoComplete="off"
autoCorrect={false}
label={state.strings.node}
label={state.strings.server}
value={state.node}
left={<TextInput.Icon style={styles.icon} icon="server" />}
onChangeText={value => actions.setNode(value)}
@ -170,7 +170,7 @@ export function Access() {
autoCapitalize="none"
autoComplete="off"
autoCorrect={false}
label={state.strings.node}
label={state.strings.server}
value={state.node}
left={<TextInput.Icon style={styles.icon} icon="server" />}
onChangeText={value => actions.setNode(value)}
@ -247,7 +247,7 @@ export function Access() {
autoCapitalize="none"
autoComplete="off"
autoCorrect={false}
label={state.strings.node}
label={state.strings.server}
value={state.node}
left={<TextInput.Icon style={styles.icon} icon="server" />}
onChangeText={value => actions.setNode(value)}

View File

@ -4,6 +4,7 @@ export const en = {
unknown: 'Unknown',
sealed: 'Sealed',
notes: 'Notes',
server: 'Server',
code: 'en',
settings: 'Settings',
@ -254,6 +255,8 @@ export const fr = {
unknown: 'Inconnu',
sealed: 'Scellé',
notes: 'Notes',
server: 'Serveur',
code: 'fr',
settings: 'Paramètres',
contacts: 'Contacts',
@ -504,7 +507,8 @@ export const sp = {
unknown: 'Desconocido',
sealed: 'Sellado',
notes: 'Notas',
server: 'Server',
code: 'sp',
settings: 'Configuración',
contacts: 'Contactos',
@ -754,6 +758,7 @@ export const pt = {
unknown: 'Desconhecido',
sealed: 'Selado',
notes: 'Notas',
server: 'Servidor',
code: 'pt',
settings: 'Configurações',
@ -1004,6 +1009,7 @@ export const de = {
unknown: 'Unbekannt',
sealed: 'Versiegelt',
notes: 'Notizen',
server: 'Servierer',
code: 'de',
settings: 'Einstellungen',
@ -1254,6 +1260,7 @@ export const ru = {
unknown: 'Неизвестно',
sealed: 'Запечатано',
notes: 'Заметки',
server: 'Сервер',
code: 'ru',
settings: 'Настройки',

View File

@ -61,7 +61,7 @@ export function Contacts({openRegistry, openContact}: {openRegistry: () => void;
style={styles.cards}
data={state.filtered}
initialNumToRender={32}
contentContainerStyle={styles.cardsContainer}
contentContainerStyle={state.layout === 'large' ? styles.cardsContainer : {}}
showsVerticalScrollIndicator={false}
renderItem={({item}) => {
const syncStatus = item.offsync ? 'offsync' : item.status;

View File

@ -8,6 +8,7 @@ export function useContacts() {
const app = useContext(AppContext) as ContextType;
const display = useContext(DisplayContext) as ContextType;
const [state, setState] = useState({
layout: '',
strings: display.state.strings,
cards: [] as Card[],
filtered: [] as Card[],
@ -46,6 +47,11 @@ export function useContacts() {
return false;
};
useEffect(() => {
const { layout } = display.state;
updateState({ layout });
}, [display.state]);
useEffect(() => {
const contact = app.state.session?.getContact();
const setCards = (cards: Card[]) => {

View File

@ -3,7 +3,7 @@ import {DatabagSDK, Session} from 'databag-client-sdk';
import {SessionStore} from '../SessionStore';
import {NativeCrypto} from '../NativeCrypto';
import {LocalStore} from '../LocalStore';
const DATABAG_DB = 'db_v223.db';
const DATABAG_DB = 'db_v230.db';
const SETTINGS_DB = 'ls_v001.db';
const databag = new DatabagSDK(

View File

@ -3,6 +3,15 @@ import {Colors} from '../constants/Colors';
export const styles = StyleSheet.create({
profile: {
width: '100%',
height: '100%',
display: 'flex',
},
scrollWrapper: {
flexGrow: 1,
height: 1,
},
scrollContainer: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
@ -49,6 +58,8 @@ export const styles = StyleSheet.create({
flexShrink: 0,
marginRight: 0,
marginLeft: 0,
marginTop: 0,
marginBottom: 0,
backgroundColor: 'transparent',
},
image: {
@ -72,6 +83,10 @@ export const styles = StyleSheet.create({
height: 2,
width: '100%',
},
border: {
width: '100%',
height: 2,
},
attributes: {
display: 'flex',
flexDirection: 'column',

File diff suppressed because it is too large Load Diff

View File

@ -18,11 +18,14 @@ export const styles = StyleSheet.create({
paddingLeft: 8,
width: '100%',
zIndex: 1,
height: 48,
},
close: {
flexShrink: 0,
marginRight: 0,
marginLeft: 0,
marginTop: 0,
marginBottom: 0,
backgroundColor: 'transparent',
},
divider: {

View File

@ -13,7 +13,7 @@ export function Registry({close, openContact}: {close: () => void; openContact:
return (
<View style={styles.registry}>
<SafeAreaView style={styles.header}>
{close && <IconButton style={styles.close} compact="true" mode="contained" icon="arrow-left" size={24} onPress={close} />}
{close && <IconButton style={styles.close} compact="true" mode="contained" icon="arrow-left" size={28} onPress={close} />}
<Surface mode="flat" style={styles.inputUsername}>
<TextInput
dense={true}
@ -48,7 +48,7 @@ export function Registry({close, openContact}: {close: () => void; openContact:
style={styles.cards}
data={state.profiles}
initialNumToRender={32}
contentContainerStyle={styles.cardsContainer}
contentContainerStyle={state.layout === 'large' ? styles.cardsContainer : {}}
showsVerticalScrollIndicator={false}
renderItem={({item}) => {
const select = () => {

View File

@ -11,6 +11,7 @@ export function useRegistry() {
const app = useContext(AppContext) as ContextType;
const display = useContext(DisplayContext) as ContextType;
const [state, setState] = useState({
layout: '',
strings: display.state.strings,
username: '',
server: '',
@ -43,6 +44,11 @@ export function useRegistry() {
}
};
useEffect(() => {
const { layout } = display.state;
updateState({ layout });
}, [display.state]);
useEffect(() => {
if (!state.username && !state.server) {
clearTimeout(debounce.current);

View File

@ -8,6 +8,9 @@ export const styles = StyleSheet.create({
height: '100%',
alignItems: 'center',
},
border: {
height: 2,
},
blur: {
position: 'absolute',
top: 0,
@ -42,11 +45,22 @@ export const styles = StyleSheet.create({
paddingTop: 8,
},
settings: {
width: '100%',
height: '100%',
position: 'relative',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
},
scrollWrapper: {
width: '100%',
flexGrow: 1,
height: 1,
},
scrollContainer: {
display: 'flex',
alignItems: 'center',
},
modalHeader: {
width: '100%',
display: 'flex',
@ -73,13 +87,18 @@ export const styles = StyleSheet.create({
modalDescription: {
paddingTop: 16,
},
title: {
width: '100%',
height: 48,
},
header: {
fontSize: 22,
textAlign: 'center',
textWrap: 'nowrap',
textOverflow: 'ellipsis',
overflow: 'hidden',
paddingTop: 8,
padding: 8,
height: 48,
},
image: {
position: 'relative',

View File

@ -322,243 +322,248 @@ export function Settings({showLogout}: {showLogout: boolean}) {
return (
<View>
<ScrollView showsVerticalScrollIndicator={false} style={styles.full}>
<View style={styles.settings}>
<View style={styles.settings}>
<View style={styles.title}>
<Text style={styles.header} adjustsFontSizeToFit={true} numberOfLines={1}>{`${state.profile.handle}${state.profile.node ? '/' + state.profile.node : ''}`}</Text>
<View style={styles.image}>
{!state.profile.imageSet && <Image style={styles.logoUnset} resizeMode={'contain'} source={{uri: state.imageUrl}} />}
{state.profile.imageSet && <Image style={styles.logoSet} resizeMode={'contain'} source={{uri: state.imageUrl}} />}
<View style={styles.editBar}>
<TouchableOpacity onPress={selectImage}>
<Surface style={styles.editBorder} elevation={0}>
<Text style={styles.editLogo}>{state.strings.edit}</Text>
<Divider style={styles.border} bold={true} />
</View>
<View style={styles.scrollWrapper}>
<ScrollView showsVerticalScrollIndicator={false} contentContainerStyle={styles.scrollContainer}>
<View style={styles.image}>
{!state.profile.imageSet && <Image style={styles.logoUnset} resizeMode={'contain'} source={{uri: state.imageUrl}} />}
{state.profile.imageSet && <Image style={styles.logoSet} resizeMode={'contain'} source={{uri: state.imageUrl}} />}
<View style={styles.editBar}>
<TouchableOpacity onPress={selectImage}>
<Surface style={styles.editBorder} elevation={0}>
<Text style={styles.editLogo}>{state.strings.edit}</Text>
</Surface>
</TouchableOpacity>
</View>
</View>
<View style={styles.divider}>
<Divider style={styles.line} bold={true} />
</View>
<View style={styles.attributes}>
{!state.profile.name && <Text style={styles.nameUnset}>{state.strings.name}</Text>}
{state.profile.name && (
<Text style={styles.nameSet} adjustsFontSizeToFit={true} numberOfLines={1}>
{state.profile.name}
</Text>
)}
<View style={styles.attribute}>
<View style={styles.icon}>
<Icon size={24} source="map-marker-outline" />
</View>
{!state.profile.location && <Text style={styles.labelUnset}>{state.strings.location}</Text>}
{state.profile.location && <Text style={styles.labelSet}>{state.profile.location}</Text>}
</View>
<View style={styles.attribute}>
<View style={styles.icon}>
<Icon size={24} source="book-open-outline" />
</View>
{!state.profile.description && <Text style={styles.labelUnset}>{state.strings.description}</Text>}
{state.profile.description && <Text style={styles.labelSet}>{state.profile.description}</Text>}
</View>
<TouchableOpacity style={styles.editDetails} onPress={() => setDetails(true)}>
<Surface elevation={4} mode="flat">
<Text style={styles.editDetailsLabel}>{state.strings.edit}</Text>
</Surface>
</TouchableOpacity>
</View>
</View>
<View style={styles.divider}>
<Divider style={styles.line} bold={true} />
</View>
<View style={styles.attributes}>
{!state.profile.name && <Text style={styles.nameUnset}>{state.strings.name}</Text>}
{state.profile.name && (
<Text style={styles.nameSet} adjustsFontSizeToFit={true} numberOfLines={1}>
{state.profile.name}
</Text>
)}
<View style={styles.attribute}>
<View style={styles.icon}>
<Icon size={24} source="map-marker-outline" />
</View>
{!state.profile.location && <Text style={styles.labelUnset}>{state.strings.location}</Text>}
{state.profile.location && <Text style={styles.labelSet}>{state.profile.location}</Text>}
<View style={styles.divider}>
<Divider style={styles.line} bold={true} />
</View>
<View style={styles.attribute}>
<View style={styles.icon}>
<Icon size={24} source="book-open-outline" />
</View>
{!state.profile.description && <Text style={styles.labelUnset}>{state.strings.description}</Text>}
{state.profile.description && <Text style={styles.labelSet}>{state.profile.description}</Text>}
</View>
<TouchableOpacity style={styles.editDetails} onPress={() => setDetails(true)}>
<Surface elevation={4} mode="flat">
<Text style={styles.editDetailsLabel}>{state.strings.edit}</Text>
</Surface>
</TouchableOpacity>
</View>
<View style={styles.divider}>
<Divider style={styles.line} bold={true} />
</View>
<View style={styles.attributes}>
<View style={styles.attribute}>
<View style={styles.controlIcon}>
<Icon size={24} source="eye-outline" />
</View>
<View style={styles.control}>
<TouchableOpacity activeOpacity={1} onPress={() => setRegistry(!state.config.searchable)}>
<Text style={styles.controlLabel}>{state.strings.registry}</Text>
</TouchableOpacity>
<Switch style={styles.controlSwitch} value={state.config.searchable} onValueChange={setRegistry} />
</View>
</View>
<View style={styles.attribute}>
<View style={styles.controlIcon}>
<Icon size={24} source="cloud-lock-outline" />
</View>
<View style={styles.control}>
<TouchableOpacity activeOpacity={1} onPress={setSeal}>
<Text style={styles.controlLabel}>{state.strings.manageTopics}</Text>
</TouchableOpacity>
</View>
</View>
<View style={styles.attribute}>
<View style={styles.controlIcon}>
<Icon size={24} source="bell-outline" />
</View>
<View style={styles.control}>
<TouchableOpacity activeOpacity={1} onPress={() => setRegistry(!state.config.pushEnabled)}>
<Text style={styles.controlLabel}>{state.strings.enableNotifications}</Text>
</TouchableOpacity>
<Switch style={styles.controlSwitch} value={state.config.pushEnabled} onValueChange={setNotifications} />
</View>
</View>
</View>
<View style={styles.divider}>
<Divider style={styles.line} bold={true} />
</View>
<View style={styles.attributes}>
<View style={styles.attribute}>
<View style={styles.controlIcon}>
<Icon size={24} source="ticket-confirmation-outline" />
</View>
<View style={styles.control}>
<TouchableOpacity activeOpacity={1} onPress={() => setRegistry(!state.config.searchable)}>
<Text style={styles.controlLabel}>{state.strings.mfaTitle}</Text>
</TouchableOpacity>
<Switch style={styles.controlSwitch} value={state.config.mfaEnabled} onValueChange={setMfa} />
</View>
</View>
<View style={styles.attribute}>
<View style={styles.controlIcon}>
<Icon size={24} source="login" />
</View>
<View style={styles.control}>
<TouchableOpacity activeOpacity={1} onPress={changeLogin}>
<Text style={styles.controlLabel}>{state.strings.changeLogin}</Text>
</TouchableOpacity>
</View>
</View>
{showLogout && (
<View style={styles.attributes}>
<View style={styles.attribute}>
<View style={styles.controlIcon}>
<Icon size={24} source="logout" />
<Icon size={24} source="eye-outline" />
</View>
<View style={styles.control}>
<TouchableOpacity activeOpacity={1} onPress={() => setLogout(true)}>
<Text style={styles.controlLabel}>{state.strings.logout}</Text>
<TouchableOpacity activeOpacity={1} onPress={() => setRegistry(!state.config.searchable)}>
<Text style={styles.controlLabel}>{state.strings.registry}</Text>
</TouchableOpacity>
<Switch style={styles.controlSwitch} value={state.config.searchable} onValueChange={setRegistry} />
</View>
</View>
<View style={styles.attribute}>
<View style={styles.controlIcon}>
<Icon size={24} source="cloud-lock-outline" />
</View>
<View style={styles.control}>
<TouchableOpacity activeOpacity={1} onPress={setSeal}>
<Text style={styles.controlLabel}>{state.strings.manageTopics}</Text>
</TouchableOpacity>
</View>
</View>
)}
<View style={styles.attribute}>
<View style={styles.controlIcon}>
<Icon size={24} source="account-remove" />
</View>
<View style={styles.control}>
<TouchableOpacity activeOpacity={1} onPress={() => setRemove(true)}>
<Text style={styles.dangerLabel}>{state.strings.deleteAccount}</Text>
</TouchableOpacity>
</View>
</View>
</View>
<View style={styles.divider}>
<Divider style={styles.line} bold={true} />
</View>
<View style={styles.attributes}>
<View style={styles.attribute}>
<View style={styles.controlIcon}>
<Icon size={24} source="account-cancel-outline" />
</View>
<View style={styles.control}>
<TouchableOpacity activeOpacity={1} onPress={() => manageSeal}>
<Text style={styles.controlLabel}>{state.strings.blockedContacts}</Text>
</TouchableOpacity>
</View>
</View>
<View style={styles.attribute}>
<View style={styles.controlIcon}>
<Icon size={24} source="archive-cancel-outline" />
</View>
<View style={styles.control}>
<TouchableOpacity activeOpacity={1} onPress={() => manageSeal}>
<Text style={styles.controlLabel}>{state.strings.blockedTopics}</Text>
</TouchableOpacity>
</View>
</View>
<View style={styles.attribute}>
<View style={styles.controlIcon}>
<Icon size={24} source="file-cancel-outline" />
</View>
<View style={styles.control}>
<TouchableOpacity activeOpacity={1} onPress={() => manageSeal}>
<Text style={styles.controlLabel}>{state.strings.blockedMessages}</Text>
</TouchableOpacity>
</View>
</View>
</View>
<View style={styles.divider}>
<Divider style={styles.line} bold={true} />
</View>
<View style={styles.options}>
<View style={styles.attribute}>
<View style={styles.radioIcon}>
<Icon size={24} source="clock-outline" />
</View>
<View style={styles.radioControl}>
<Text style={styles.smallLabel}>{state.strings.timeFormat}:</Text>
<View style={styles.radioButtons}>
<RadioButton.Item
style={styles.radio}
label={state.strings.timeHalf}
labelStyle={styles.option}
mode="android"
status={state.fullDayTime ? 'unchecked' : 'checked'}
onPress={() => {
actions.setFullDayTime(false);
}}
/>
<RadioButton.Item
style={styles.radio}
label={state.strings.timeFull}
labelStyle={styles.option}
mode="android"
status={state.fullDayTime ? 'checked' : 'unchecked'}
onPress={() => {
actions.setFullDayTime(true);
}}
/>
<View style={styles.attribute}>
<View style={styles.controlIcon}>
<Icon size={24} source="bell-outline" />
</View>
<View style={styles.control}>
<TouchableOpacity activeOpacity={1} onPress={() => setRegistry(!state.config.pushEnabled)}>
<Text style={styles.controlLabel}>{state.strings.enableNotifications}</Text>
</TouchableOpacity>
<Switch style={styles.controlSwitch} value={state.config.pushEnabled} onValueChange={setNotifications} />
</View>
</View>
</View>
<View style={styles.attribute}>
<View style={styles.radioIcon}>
<Icon size={24} source="calendar-text-outline" />
<View style={styles.divider}>
<Divider style={styles.line} bold={true} />
</View>
<View style={styles.attributes}>
<View style={styles.attribute}>
<View style={styles.controlIcon}>
<Icon size={24} source="ticket-confirmation-outline" />
</View>
<View style={styles.control}>
<TouchableOpacity activeOpacity={1} onPress={() => setRegistry(!state.config.searchable)}>
<Text style={styles.controlLabel}>{state.strings.mfaTitle}</Text>
</TouchableOpacity>
<Switch style={styles.controlSwitch} value={state.config.mfaEnabled} onValueChange={setMfa} />
</View>
</View>
<View style={styles.radioControl}>
<Text style={styles.smallLabel}>{state.strings.dateFormat}:</Text>
<View style={styles.radioButtons}>
<RadioButton.Item
style={styles.radio}
label={state.strings.monthStart}
labelStyle={styles.option}
mode="android"
status={state.monthFirstDate ? 'checked' : 'unchecked'}
onPress={() => {
actions.setMonthFirstDate(true);
}}
/>
<RadioButton.Item
style={styles.radio}
label={state.strings.monthEnd}
labelStyle={styles.option}
mode="android"
status={state.monthFirstDate ? 'unchecked' : 'checked'}
onPress={() => {
actions.setMonthFirstDate(false);
}}
/>
<View style={styles.attribute}>
<View style={styles.controlIcon}>
<Icon size={24} source="login" />
</View>
<View style={styles.control}>
<TouchableOpacity activeOpacity={1} onPress={changeLogin}>
<Text style={styles.controlLabel}>{state.strings.changeLogin}</Text>
</TouchableOpacity>
</View>
</View>
{showLogout && (
<View style={styles.attribute}>
<View style={styles.controlIcon}>
<Icon size={24} source="logout" />
</View>
<View style={styles.control}>
<TouchableOpacity activeOpacity={1} onPress={() => setLogout(true)}>
<Text style={styles.controlLabel}>{state.strings.logout}</Text>
</TouchableOpacity>
</View>
</View>
)}
<View style={styles.attribute}>
<View style={styles.controlIcon}>
<Icon size={24} source="account-remove" />
</View>
<View style={styles.control}>
<TouchableOpacity activeOpacity={1} onPress={() => setRemove(true)}>
<Text style={styles.dangerLabel}>{state.strings.deleteAccount}</Text>
</TouchableOpacity>
</View>
</View>
</View>
</View>
<View style={styles.divider}>
<Divider style={styles.line} bold={true} />
</View>
<View style={styles.attributes}>
<View style={styles.attribute}>
<View style={styles.controlIcon}>
<Icon size={24} source="account-cancel-outline" />
</View>
<View style={styles.control}>
<TouchableOpacity activeOpacity={1} onPress={() => manageSeal}>
<Text style={styles.controlLabel}>{state.strings.blockedContacts}</Text>
</TouchableOpacity>
</View>
</View>
<View style={styles.attribute}>
<View style={styles.controlIcon}>
<Icon size={24} source="archive-cancel-outline" />
</View>
<View style={styles.control}>
<TouchableOpacity activeOpacity={1} onPress={() => manageSeal}>
<Text style={styles.controlLabel}>{state.strings.blockedTopics}</Text>
</TouchableOpacity>
</View>
</View>
<View style={styles.attribute}>
<View style={styles.controlIcon}>
<Icon size={24} source="file-cancel-outline" />
</View>
<View style={styles.control}>
<TouchableOpacity activeOpacity={1} onPress={() => manageSeal}>
<Text style={styles.controlLabel}>{state.strings.blockedMessages}</Text>
</TouchableOpacity>
</View>
</View>
</View>
<View style={styles.divider}>
<Divider style={styles.line} bold={true} />
</View>
<View style={styles.options}>
<View style={styles.attribute}>
<View style={styles.radioIcon}>
<Icon size={24} source="clock-outline" />
</View>
<View style={styles.radioControl}>
<Text style={styles.smallLabel}>{state.strings.timeFormat}:</Text>
<View style={styles.radioButtons}>
<RadioButton.Item
style={styles.radio}
label={state.strings.timeHalf}
labelStyle={styles.option}
mode="android"
status={state.fullDayTime ? 'unchecked' : 'checked'}
onPress={() => {
actions.setFullDayTime(false);
}}
/>
<RadioButton.Item
style={styles.radio}
label={state.strings.timeFull}
labelStyle={styles.option}
mode="android"
status={state.fullDayTime ? 'checked' : 'unchecked'}
onPress={() => {
actions.setFullDayTime(true);
}}
/>
</View>
</View>
</View>
<View style={styles.attribute}>
<View style={styles.radioIcon}>
<Icon size={24} source="calendar-text-outline" />
</View>
<View style={styles.radioControl}>
<Text style={styles.smallLabel}>{state.strings.dateFormat}:</Text>
<View style={styles.radioButtons}>
<RadioButton.Item
style={styles.radio}
label={state.strings.monthStart}
labelStyle={styles.option}
mode="android"
status={state.monthFirstDate ? 'checked' : 'unchecked'}
onPress={() => {
actions.setMonthFirstDate(true);
}}
/>
<RadioButton.Item
style={styles.radio}
label={state.strings.monthEnd}
labelStyle={styles.option}
mode="android"
status={state.monthFirstDate ? 'unchecked' : 'checked'}
onPress={() => {
actions.setMonthFirstDate(false);
}}
/>
</View>
</View>
</View>
</View>
</ScrollView>
</View>
</ScrollView>
</View>
<Modal animationType="fade" transparent={true} visible={sealing} supportedOrientations={['portrait', 'landscape']} onRequestClose={() => setSealing(false)}>
<View style={styles.modal}>
<BlurView style={styles.blur} blurType="dark" blurAmount={2} reducedTransparencyFallbackColor="dark" />