building details screen

This commit is contained in:
balzack 2025-01-08 21:58:58 -08:00
parent f551e6b5f5
commit 03f64e8257
7 changed files with 11253 additions and 7770 deletions

View File

@ -2018,9 +2018,9 @@ SPEC CHECKSUMS:
DoubleConversion: 76ab83afb40bddeeee456813d9c04f67f78771b5
FBLazyVector: 38bb611218305c3bc61803e287b8a81c6f63b619
fmt: 4c2741a687cc09f0634a2e2c72a838b99f1ff120
glog: fdfdfe5479092de0c4bdbebedd9056951f092c4f
glog: 69ef571f3de08433d766d614c73a9838a06bf7eb
hermes-engine: 3b6e0717ca847e2fc90a201e59db36caf04dee88
RCT-Folly: 02617c592a293bd6d418e0a88ff4ee1f88329b47
RCT-Folly: 4464f4d875961fce86008d45f4ecf6cef6de0740
RCTDeprecation: 34cbf122b623037ea9facad2e92e53434c5c7422
RCTRequired: 24c446d7bcd0f517d516b6265d8df04dc3eb1219
RCTTypeSafety: ef5e91bd791abd3a99b2c75fd565791102a66352
@ -2094,7 +2094,7 @@ SPEC CHECKSUMS:
RNVectorIcons: 6382277afab3c54658e9d555ee0faa7a37827136
SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d
TOCropViewController: 80b8985ad794298fb69d3341de183f33d1853654
Yoga: 2a45d7e59592db061217551fd3bbe2dd993817ae
Yoga: a1d7895431387402a674fd0d1c04ec85e87909b8
PODFILE CHECKSUM: 8461018d8deceb200962c829584af7c2eb345c80

View File

@ -43,7 +43,7 @@ export function Conversation({close, openDetails, wide}: {close: ()=>void, openD
const scale = useAnimatedValue(0)
const alertParams = {
title: state.strings.error,
title: state.strings.operationFailed,
prompt: state.strings.tryAgain,
cancel: {
label: state.strings.close,

View File

@ -23,6 +23,8 @@ export const styles = StyleSheet.create({
},
title: {
fontSize: 20,
flexGrow: 1,
textAlign: 'center',
},
close: {
width: 32,
@ -39,4 +41,37 @@ export const styles = StyleSheet.create({
width: '100%',
height: 2,
},
info: {
width: '80%',
},
subject: {
width: '100%',
height: 52,
display: 'flex',
alignItems: 'center',
flexDirection: 'row',
paddingRight: 4,
marginTop: 16,
borderRadius: 8,
},
input: {
flexGrow: 1,
backgroundColor: 'transparent',
},
underline: {
display: 'none',
},
icon: {
backgroundColor: 'transparent',
padding: 0,
margin: 0,
},
inputControl: {
width: 32,
height: 32,
backgroundColor: 'yellow',
},
members: {
flexGrow: 1,
},
});

View File

@ -1,11 +1,38 @@
import React from 'react';
import { SafeAreaView, View } from 'react-native';
import {Divider, IconButton, Text} from 'react-native-paper';
import React, { useState } from 'react';
import { SafeAreaView, ScrollView, View } from 'react-native';
import {Surface, Divider, IconButton, Text, TextInput} from 'react-native-paper';
import {styles} from './Details.styled';
import {useDetails} from './useDetails.hook';
import { Confirm } from '../confirm/Confirm';
export function Details({close, closeAll}: {close: ()=>void, closeAll: ()=>void}) {
const { state, actions } = useDetails();
const [alert, setAlert] = useState(false);
const [saving, setSaving] = useState(false);
const alertParams = {
title: state.strings.operationFailer,
prompt: state.strings.tryAgain,
cancel: {
label: state.strings.close,
action: () => {
setAlert(false);
},
},
};
const saveSubject = async () => {
if (!saving) {
setSaving(true);
try {
await actions.saveSubject();
} catch (err) {
console.log(err);
setAlert(true);
}
setSaving(false);
}
}
return (
<View style={styles.details}>
@ -21,6 +48,38 @@ export function Details({close, closeAll}: {close: ()=>void, closeAll: ()=>void}
)}
</SafeAreaView>
<Divider style={styles.divider} />
<View style={styles.info}>
{ state.host && (
<Surface style={styles.subject} elevation={4}>
<TextInput
style={styles.input}
underlineStyle={styles.underline}
mode="flat"
autoCapitalize="none"
autoComplete="off"
autoCorrect={false}
value={state.editSubject}
label={state.strings.subject}
disabled={state.locked}
left={<TextInput.Icon style={styles.icon} icon="label-outline" />}
onChangeText={value => actions.setEditSubject(value)}
/>
{ state.subject !== state.editSubject && (
<IconButton style={styles.icon} icon="undo-variant" onPress={actions.undoSubject} />
)}
{ state.subject !== state.editSubject && (
<IconButton style={styles.icon} icon="content-save-outline" loading={saving} onPress={saveSubject} />
)}
</Surface>
)}
</View>
<ScrollView style={styles.members}>
</ScrollView>
<Confirm show={alert} params={alertParams} />
</View>
)
}
// input if host and unsealed
// text otherwise

View File

@ -1,19 +1,164 @@
import {useState, useContext, useEffect, useRef} from 'react';
import {DisplayContext} from '../context/DisplayContext';
import {ContextType} from '../context/ContextType';
import { useState, useContext, useEffect } from 'react'
import { AppContext } from '../context/AppContext'
import { DisplayContext } from '../context/DisplayContext'
import { ContextType } from '../context/ContextType'
import { FocusDetail, Card, Profile } from 'databag-client-sdk';
export function useDetails() {
const display = useContext(DisplayContext) as ContextType;
const display = useContext(DisplayContext) as ContextType
const app = useContext(AppContext) as ContextType
const [state, setState] = useState({
cardId: null as null | string,
channelId: '',
detail: undefined as undefined | FocusDetail,
access: false,
host: false,
sealed: false,
locked: false,
strings: display.state.strings,
});
timeFormat: display.state.timeFormat,
dateFormat: display.state.dateFormat,
subject: '',
editSubject: '',
created: '',
profile: null as null | Profile,
cards: [] as Card[],
hostCard: null as null | Card,
channelCards: [] as Card[],
unknownContacts: 0,
})
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const updateState = (value: any) => {
setState(s => ({...s, ...value}));
};
setState((s) => ({ ...s, ...value }))
}
const getTimestamp = (created: number) => {
const now = Math.floor((new Date()).getTime() / 1000)
const date = new Date(created * 1000);
const offset = now - created;
if(offset < 43200) {
if (state.timeFormat === '12h') {
return date.toLocaleTimeString("en-US", {hour: 'numeric', minute:'2-digit'});
}
else {
return date.toLocaleTimeString("en-GB", {hour: 'numeric', minute:'2-digit'});
}
}
else if (offset < 31449600) {
if (state.dateFormat === 'mm/dd') {
return date.toLocaleDateString("en-US", {day: 'numeric', month:'numeric'});
}
else {
return date.toLocaleDateString("en-GB", {day: 'numeric', month:'numeric'});
}
}
else {
if (state.dateFormat === 'mm/dd') {
return date.toLocaleDateString("en-US");
}
else {
return date.toLocaleDateString("en-GB");
}
}
}
useEffect(() => {
const { strings, timeFormat, dateFormat } = display.state;
updateState({ strings, timeFormat, dateFormat });
}, [display.state]);
useEffect(() => {
const hostCard = state.cards.find(entry => entry.cardId == state.cardId);
const profileRemoved = state.detail?.members ? state.detail.members.filter(member => state.profile?.guid != member.guid) : [];
const contactCards = profileRemoved.map(member => state.cards.find(card => card.guid === member.guid));
const channelCards = contactCards.filter(member => Boolean(member));
const unknownContacts = contactCards.length - channelCards.length;
updateState({ hostCard, channelCards, unknownContacts });
}, [state.detail, state.cards, state.profile, state.cardId]);
useEffect(() => {
const focus = app.state.focus;
const { contact, identity } = app.state.session || { };
if (focus && contact && identity) {
const setCards = (cards: Card[]) => {
const filtered = cards.filter(card => !card.blocked);
const sorted = filtered.sort((a, b) => {
if (a.handle > b.handle) {
return 1;
} else if (a.handle < b.handle) {
return -1;
} else {
return 0;
}
});
updateState({ cards: sorted });
}
const setProfile = (profile: Profile) => {
updateState({ profile });
}
const setDetail = (focused: { cardId: string | null, channelId: string, detail: FocusDetail | null }) => {
const detail = focused ? focused.detail : null;
const cardId = focused.cardId;
const channelId = focused.channelId;
const access = Boolean(detail);
const sealed = detail?.sealed;
const locked = detail?.locked;
const host = cardId == null;
const subject = detail?.data?.subject ? detail.data.subject : '';
const created = detail?.created ? getTimestamp(detail.created) : '';
updateState({ detail, editSubject: subject, subject, channelId, cardId, access, sealed, locked, host, created });
}
focus.addDetailListener(setDetail);
contact.addCardListener(setCards);
identity.addProfileListener(setProfile);
return () => {
focus.removeDetailListener(setDetail);
contact.removeCardListener(setCards);
identity.removeProfileListener(setProfile);
}
}
}, [app.state.focus, state.timeFormat, state.dateFormat]);
const actions = {
};
remove: async () => {
const content = app.state.session.getContent()
await content.removeChannel(state.channelId);
app.actions.clearFocus();
},
leave: async () => {
const content = app.state.session.getContent()
await content.leaveChannel(state.cardId, state.channelId);
app.actions.clearFocus();
},
block: async () => {
const content = app.state.session.getContent();
await content.setBlockedChannel(state.cardId, state.channelId, true);
app.actions.clearFocus();
},
report: async () => {
const content = app.state.session.getContent();
await content.flagChannel(state.cardId, state.channelId);
},
setMember: async (cardId: string) => {
const content = app.state.session.getContent();
await content.setChannelCard(state.channelId, cardId);
},
clearMember: async (cardId: string) => {
const content = app.state.session.getContent();
await content.clearChannelCard(state.channelId, cardId);
},
setEditSubject: (editSubject: string) => {
updateState({ editSubject });
},
undoSubject: () => {
updateState({ editSubject: state.subject });
},
saveSubject: async () => {
const content = app.state.session.getContent()
await content.setChannelSubject(state.channelId, state.sealed ? 'sealed' : 'superbasic', { subject: state.editSubject });
},
}
return {state, actions};
return { state, actions }
}

View File

@ -39,7 +39,7 @@ export function Settings({showLogout}: {showLogout: boolean}) {
const [authMessage, setAuthMessage] = useState('');
const alertParams = {
title: state.strings.error,
title: state.strings.operationFailed,
prompt: state.strings.tryAgain,
cancel: {
label: state.strings.close,

File diff suppressed because it is too large Load Diff