fixed lint warnings

This commit is contained in:
balzack 2025-03-24 14:22:27 -07:00
parent 1c93ad161f
commit 6c4f0f1181
70 changed files with 710 additions and 824 deletions

View File

@ -1,6 +1,6 @@
import { Staging } from 'databag-client-sdk' import { Staging } from 'databag-client-sdk';
import RNFS from 'react-native-fs'; import RNFS from 'react-native-fs';
import fileType from 'react-native-file-type' import fileType from 'react-native-file-type';
export class StagingFiles implements Staging { export class StagingFiles implements Staging {
@ -10,7 +10,7 @@ export class StagingFiles implements Staging {
if (entry.name.startsWith('dbTmp_')) { if (entry.name.startsWith('dbTmp_')) {
await RNFS.unlink(entry.path); await RNFS.unlink(entry.path);
} }
}; }
} }
public async read(source: any): Promise<{ size: number, getData: (position: number, length: number)=>Promise<string>, close: ()=>Promise<void> }> { public async read(source: any): Promise<{ size: number, getData: (position: number, length: number)=>Promise<string>, close: ()=>Promise<void> }> {
@ -19,19 +19,19 @@ export class StagingFiles implements Staging {
const size = stat.size; const size = stat.size;
const getData = async (position: number, length: number) => { const getData = async (position: number, length: number) => {
return await RNFS.read(path, length, position, 'base64'); return await RNFS.read(path, length, position, 'base64');
} };
const close = async ()=>{} const close = async ()=>{};
return { size, getData, close }; return { size, getData, close };
} }
public async write(): Promise<{ setData: (data: string)=>Promise<void>, getUrl: ()=>Promise<string>, close: ()=>Promise<void> }> { public async write(): Promise<{ setData: (data: string)=>Promise<void>, getUrl: ()=>Promise<string>, close: ()=>Promise<void> }> {
let set = false; let set = false;
let extension = ''; let extension = '';
const path = RNFS.DocumentDirectoryPath + `/dbTmp_${Date.now()}` const path = RNFS.DocumentDirectoryPath + `/dbTmp_${Date.now()}`;
const setData = async (data: string) => { const setData = async (data: string) => {
set = true; set = true;
await RNFS.appendFile(path, data, 'base64'); await RNFS.appendFile(path, data, 'base64');
} };
const getUrl = async () => { const getUrl = async () => {
if (!extension) { if (!extension) {
try { try {
@ -44,8 +44,8 @@ export class StagingFiles implements Staging {
extension = '.dat'; extension = '.dat';
} }
} }
return `file://${path}${extension}` return `file://${path}${extension}`;
} };
const close = async () => { const close = async () => {
if (set) { if (set) {
try { try {
@ -54,8 +54,8 @@ export class StagingFiles implements Staging {
console.log(err); console.log(err);
} }
} }
} };
return { setData, getUrl, close }; return { setData, getUrl, close };
} }
} }

View File

@ -68,6 +68,8 @@ export const styles = StyleSheet.create({
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
gap: 8, gap: 8,
minHeight: 0,
flexGrow: 1,
}, },
scroll: { scroll: {
flexGrow: 1, flexGrow: 1,
@ -190,10 +192,6 @@ export const styles = StyleSheet.create({
modalClose: { modalClose: {
backgroundColor: 'transparent', backgroundColor: 'transparent',
}, },
frame: {
minHeight: 0,
flexGrow: 1,
},
line: { line: {
width: '100%', width: '100%',
}, },

View File

@ -80,8 +80,8 @@ export function Access() {
autoCapitalize="none" autoCapitalize="none"
autoComplete="off" autoComplete="off"
autoCorrect={false} autoCorrect={false}
label={Platform.OS==='ios'?state.strings.server:undefined} label={Platform.OS === 'ios' ? state.strings.server : undefined}
placeholder={Platform.OS!=='ios'?state.strings.server:undefined} placeholder={Platform.OS !== 'ios' ? state.strings.server : undefined}
value={state.node} value={state.node}
left={<TextInput.Icon style={styles.icon} icon="server" />} left={<TextInput.Icon style={styles.icon} icon="server" />}
onChangeText={value => actions.setNode(value)} onChangeText={value => actions.setNode(value)}
@ -92,8 +92,8 @@ export function Access() {
autoCapitalize="none" autoCapitalize="none"
autoComplete="off" autoComplete="off"
autoCorrect={false} autoCorrect={false}
label={Platform.OS==='ios'?state.strings.username:undefined} label={Platform.OS === 'ios' ? state.strings.username : undefined}
placeholder={Platform.OS!=='ios'?state.strings.username:undefined} placeholder={Platform.OS !== 'ios' ? state.strings.username : undefined}
value={state.username} value={state.username}
left={<TextInput.Icon style={styles.icon} icon="account" />} left={<TextInput.Icon style={styles.icon} icon="account" />}
onChangeText={value => actions.setUsername(value)} onChangeText={value => actions.setUsername(value)}
@ -105,8 +105,8 @@ export function Access() {
autoComplete="off" autoComplete="off"
autoCorrect={false} autoCorrect={false}
value={state.password} value={state.password}
label={Platform.OS==='ios'?state.strings.password:undefined} label={Platform.OS === 'ios' ? state.strings.password : undefined}
placeholder={Platform.OS!=='ios'?state.strings.password:undefined} placeholder={Platform.OS !== 'ios' ? state.strings.password : undefined}
secureTextEntry={!showPassword} secureTextEntry={!showPassword}
left={<TextInput.Icon style={styles.icon} icon="lock" />} left={<TextInput.Icon style={styles.icon} icon="lock" />}
right={ right={
@ -152,8 +152,8 @@ export function Access() {
autoCapitalize="none" autoCapitalize="none"
autoComplete="off" autoComplete="off"
autoCorrect={false} autoCorrect={false}
label={Platform.OS==='ios'?state.strings.token:undefined} label={Platform.OS === 'ios' ? state.strings.token : undefined}
placeholder={Platform.OS!=='ios'?state.strings.token:undefined} placeholder={Platform.OS !== 'ios' ? state.strings.token : undefined}
left={<TextInput.Icon style={styles.icon} icon="ticket-account" />} left={<TextInput.Icon style={styles.icon} icon="ticket-account" />}
onChangeText={value => actions.setToken(value)} onChangeText={value => actions.setToken(value)}
/> />
@ -163,8 +163,8 @@ export function Access() {
autoCapitalize="none" autoCapitalize="none"
autoComplete="off" autoComplete="off"
autoCorrect={false} autoCorrect={false}
label={Platform.OS==='ios'?state.strings.server:undefined} label={Platform.OS === 'ios' ? state.strings.server : undefined}
placeholder={Platform.OS!=='ios'?state.strings.server:undefined} placeholder={Platform.OS !== 'ios' ? state.strings.server : undefined}
value={state.node} value={state.node}
left={<TextInput.Icon style={styles.icon} icon="server" />} left={<TextInput.Icon style={styles.icon} icon="server" />}
onChangeText={value => actions.setNode(value)} onChangeText={value => actions.setNode(value)}
@ -198,8 +198,8 @@ export function Access() {
autoCapitalize="none" autoCapitalize="none"
autoComplete="off" autoComplete="off"
autoCorrect={false} autoCorrect={false}
label={Platform.OS==='ios'?state.strings.token:undefined} label={Platform.OS === 'ios' ? state.strings.token : undefined}
placeholder={Platform.OS!=='ios'?state.strings.token:undefined} placeholder={Platform.OS !== 'ios' ? state.strings.token : undefined}
left={<TextInput.Icon style={styles.icon} icon="ticket-account" />} left={<TextInput.Icon style={styles.icon} icon="ticket-account" />}
onChangeText={value => actions.setToken(value)} onChangeText={value => actions.setToken(value)}
/> />
@ -211,8 +211,8 @@ export function Access() {
autoCapitalize="none" autoCapitalize="none"
autoComplete="off" autoComplete="off"
autoCorrect={false} autoCorrect={false}
label={Platform.OS==='ios'?state.strings.server:undefined} label={Platform.OS === 'ios' ? state.strings.server : undefined}
placeholder={Platform.OS!=='ios'?state.strings.server:undefined} placeholder={Platform.OS !== 'ios' ? state.strings.server : undefined}
value={state.node} value={state.node}
left={<TextInput.Icon style={styles.icon} icon="server" />} left={<TextInput.Icon style={styles.icon} icon="server" />}
onChangeText={value => actions.setNode(value)} onChangeText={value => actions.setNode(value)}
@ -224,8 +224,8 @@ export function Access() {
autoComplete="off" autoComplete="off"
autoCorrect={false} autoCorrect={false}
error={state.taken} error={state.taken}
label={Platform.OS==='ios'?state.strings.username:undefined} label={Platform.OS === 'ios' ? state.strings.username : undefined}
placeholder={Platform.OS!=='ios'?state.strings.username:undefined} placeholder={Platform.OS !== 'ios' ? state.strings.username : undefined}
value={state.username} value={state.username}
left={<TextInput.Icon style={styles.icon} icon="account" />} left={<TextInput.Icon style={styles.icon} icon="account" />}
onChangeText={value => actions.setUsername(value)} onChangeText={value => actions.setUsername(value)}
@ -238,8 +238,8 @@ export function Access() {
autoCorrect={false} autoCorrect={false}
textContentType={'oneTimeCode'} textContentType={'oneTimeCode'}
value={state.password} value={state.password}
label={Platform.OS==='ios'?state.strings.password:undefined} label={Platform.OS === 'ios' ? state.strings.password : undefined}
placeholder={Platform.OS!=='ios'?state.strings.password:undefined} placeholder={Platform.OS !== 'ios' ? state.strings.password : undefined}
secureTextEntry={!showPassword} secureTextEntry={!showPassword}
left={<TextInput.Icon style={styles.icon} icon="lock" />} left={<TextInput.Icon style={styles.icon} icon="lock" />}
right={ right={
@ -259,8 +259,8 @@ export function Access() {
autoCorrect={false} autoCorrect={false}
textContentType={'oneTimeCode'} textContentType={'oneTimeCode'}
value={state.confirm} value={state.confirm}
label={Platform.OS==='ios'?state.strings.confirmPassword:undefined} label={Platform.OS === 'ios' ? state.strings.confirmPassword : undefined}
placeholder={Platform.OS!=='ios'?state.strings.confirmPassword:undefined} placeholder={Platform.OS !== 'ios' ? state.strings.confirmPassword : undefined}
secureTextEntry={!showConfirm} secureTextEntry={!showConfirm}
left={<TextInput.Icon style={styles.icon} icon="lock" />} left={<TextInput.Icon style={styles.icon} icon="lock" />}
right={ right={
@ -299,8 +299,8 @@ export function Access() {
autoCapitalize="none" autoCapitalize="none"
autoComplete="off" autoComplete="off"
autoCorrect={false} autoCorrect={false}
label={Platform.OS==='ios'?state.strings.server:undefined} label={Platform.OS === 'ios' ? state.strings.server : undefined}
placeholder={Platform.OS!=='ios'?state.strings.server:undefined} placeholder={Platform.OS !== 'ios' ? state.strings.server : undefined}
value={state.node} value={state.node}
left={<TextInput.Icon style={styles.icon} icon="server" />} left={<TextInput.Icon style={styles.icon} icon="server" />}
onChangeText={value => actions.setNode(value)} onChangeText={value => actions.setNode(value)}
@ -311,8 +311,8 @@ export function Access() {
autoCapitalize="none" autoCapitalize="none"
autoComplete="off" autoComplete="off"
autoCorrect={false} autoCorrect={false}
label={Platform.OS==='ios'?state.strings.password:undefined} label={Platform.OS === 'ios' ? state.strings.password : undefined}
placeholder={Platform.OS!=='ios'?state.strings.password:undefined} placeholder={Platform.OS !== 'ios' ? state.strings.password : undefined}
value={state.password} value={state.password}
secureTextEntry={!showPassword} secureTextEntry={!showPassword}
left={<TextInput.Icon style={styles.icon} icon="lock" />} left={<TextInput.Icon style={styles.icon} icon="lock" />}

View File

@ -32,7 +32,7 @@ export function useAccess() {
useEffect(() => { useEffect(() => {
SplashScreen.hide(); SplashScreen.hide();
}, []); }, []);
useEffect(() => { useEffect(() => {
const {username, token, node, secure, mode} = state; const {username, token, node, secure, mode} = state;

View File

@ -1,5 +1,5 @@
import React, {useEffect, useState} from 'react'; import React, {useEffect, useState} from 'react';
import {FlatList, SafeAreaView, Image, View, Pressable, TouchableOpacity, Modal} from 'react-native'; import {FlatList, View, TouchableOpacity, Modal} from 'react-native';
import {Text, Button, IconButton, Divider, Surface, Icon, useTheme} from 'react-native-paper'; import {Text, Button, IconButton, Divider, Surface, Icon, useTheme} from 'react-native-paper';
import {useAccounts} from './useAccounts.hook'; import {useAccounts} from './useAccounts.hook';
import {styles} from './Accounts.styled'; import {styles} from './Accounts.styled';
@ -27,6 +27,7 @@ export function Accounts({ setup }: { setup: ()=>void }) {
useEffect(() => { useEffect(() => {
loadAccounts(); loadAccounts();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, []);
const loadAccounts = async () => { const loadAccounts = async () => {
@ -39,7 +40,7 @@ export function Accounts({ setup }: { setup: ()=>void }) {
} }
setLoading(false); setLoading(false);
} }
} };
const accessAccount = async (accountId: number) => { const accessAccount = async (accountId: number) => {
if (!accessing) { if (!accessing) {
@ -54,7 +55,7 @@ export function Accounts({ setup }: { setup: ()=>void }) {
} }
setAccessing(null); setAccessing(null);
} }
} };
const addAccount = async () => { const addAccount = async () => {
if (!adding) { if (!adding) {
@ -69,14 +70,14 @@ export function Accounts({ setup }: { setup: ()=>void }) {
} }
setAdding(false); setAdding(false);
} }
} };
const failedParams = { const failedParams = {
title: state.strings.operationFailed, title: state.strings.operationFailed,
prompt: state.strings.tryAgain, prompt: state.strings.tryAgain,
cancel: { cancel: {
label: state.strings.close, label: state.strings.close,
action: ()=>{setFailed(false)}, action: ()=>{setFailed(false);},
}, },
}; };
@ -91,7 +92,7 @@ export function Accounts({ setup }: { setup: ()=>void }) {
} }
setBlocking(null); setBlocking(null);
} }
} };
const removeAccount = (accountId: number) => { const removeAccount = (accountId: number) => {
if (!remove) { if (!remove) {
@ -116,12 +117,12 @@ export function Accounts({ setup }: { setup: ()=>void }) {
}, },
cancel: { cancel: {
label: state.strings.cancel, label: state.strings.cancel,
action: () => {setRemove(false)}, action: () => {setRemove(false);},
}, },
}) });
setRemove(true); setRemove(true);
} }
} };
const copyToken = async () => { const copyToken = async () => {
if (!tokenCopy) { if (!tokenCopy) {
@ -159,12 +160,12 @@ export function Accounts({ setup }: { setup: ()=>void }) {
showsVerticalScrollIndicator={false} showsVerticalScrollIndicator={false}
renderItem={({item}) => { renderItem={({item}) => {
const options = [ const options = [
<IconButton key="disable" style={styles.icon} loading={accessing === item.accountId} iconColor={Colors.primary} mode="contained" icon="lock-open-variant-outline" onPress={() => {accessAccount(item.accountId)}} />, <IconButton key="disable" style={styles.icon} loading={accessing === item.accountId} iconColor={Colors.primary} mode="contained" icon="lock-open-variant-outline" onPress={() => {accessAccount(item.accountId);}} />,
<IconButton key="reset" style={styles.icon} loading={blocking === item.accountId} iconColor={Colors.pending} mode="contained" icon={item.disabled ? 'account-check-outline' : 'account-cancel-outline'} onPress={()=>{blockAccount(item.accountId, !item.disabled)}} />, <IconButton key="reset" style={styles.icon} loading={blocking === item.accountId} iconColor={Colors.pending} mode="contained" icon={item.disabled ? 'account-check-outline' : 'account-cancel-outline'} onPress={()=>{blockAccount(item.accountId, !item.disabled);}} />,
<IconButton key="remove" style={styles.icon} loading={removing === item.accountId} iconColor={Colors.offsync} mode="contained" icon="trash-can-outline" onPress={()=>{removeAccount(item.accountId)}} /> <IconButton key="remove" style={styles.icon} loading={removing === item.accountId} iconColor={Colors.offsync} mode="contained" icon="trash-can-outline" onPress={()=>{removeAccount(item.accountId);}} />,
]; ];
return ( return (
<Card <Card
containerStyle={{ containerStyle={{
...styles.card, ...styles.card,
borderColor: theme.colors.outlineVariant, borderColor: theme.colors.outlineVariant,

View File

@ -1,4 +1,4 @@
import {useEffect, useState, useContext, useRef} from 'react'; import {useEffect, useState, useContext} from 'react';
import {AppContext} from '../context/AppContext'; import {AppContext} from '../context/AppContext';
import {DisplayContext} from '../context/DisplayContext'; import {DisplayContext} from '../context/DisplayContext';
import {ContextType} from '../context/ContextType'; import {ContextType} from '../context/ContextType';
@ -31,7 +31,7 @@ export function useAccounts() {
updateState({ loading: false }); updateState({ loading: false });
} }
} }
} };
useEffect(() => { useEffect(() => {
const { layout, strings} = display.state; const { layout, strings} = display.state;

View File

@ -1,4 +1,4 @@
import React from 'react' import React from 'react';
import { View, Image } from 'react-native'; import { View, Image } from 'react-native';
import {useTheme, Text, Icon} from 'react-native-paper'; import {useTheme, Text, Icon} from 'react-native-paper';
import { styles } from './Base.styled'; import { styles } from './Base.styled';
@ -9,13 +9,13 @@ import { Colors } from '../constants/Colors';
export function Base() { export function Base() {
const theme = useTheme(); const theme = useTheme();
const { state, actions } = useBase(); const { state } = useBase();
return ( return (
<View style={{ ...styles.base, backgroundColor: theme.colors.base }}> <View style={{ ...styles.base, backgroundColor: theme.colors.base }}>
<Text style={styles.title}>Databag</Text> <Text style={styles.title}>Databag</Text>
<Text style={styles.description}>{ state.strings.communication }</Text> <Text style={styles.description}>{ state.strings.communication }</Text>
<Image style={styles.image} source={theme.colors.name == 'light' ? light : dark} resizeMode="contain" /> <Image style={styles.image} source={theme.colors.name === 'light' ? light : dark} resizeMode="contain" />
{ (state.profileSet === false || state.cardSet === false || state.channelSet === false) && ( { (state.profileSet === false || state.cardSet === false || state.channelSet === false) && (
<View style={styles.steps}> <View style={styles.steps}>
{ state.profileSet === false && ( { state.profileSet === false && (

View File

@ -1,48 +1,48 @@
import { useState, useContext, useEffect } from 'react' import { useState, useContext, useEffect } from 'react';
import { DisplayContext } from '../context/DisplayContext'; import { DisplayContext } from '../context/DisplayContext';
import { AppContext } from '../context/AppContext'; import { AppContext } from '../context/AppContext';
import { ContextType } from '../context/ContextType' import { ContextType } from '../context/ContextType';
export function useBase() { export function useBase() {
const app = useContext(AppContext) as ContextType const app = useContext(AppContext) as ContextType;
const display = useContext(DisplayContext) as ContextType const display = useContext(DisplayContext) as ContextType;
const [state, setState] = useState({ const [state, setState] = useState({
strings: display.state.strings, strings: display.state.strings,
profileSet: null as null | boolean, profileSet: null as null | boolean,
cardSet: null as null | boolean, cardSet: null as null | boolean,
channelSet: null as null | boolean, channelSet: null as null | boolean,
}) });
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const updateState = (value: any) => { const updateState = (value: any) => {
setState((s) => ({ ...s, ...value })) setState((s) => ({ ...s, ...value }));
} };
useEffect(() => { useEffect(() => {
const setProfile = (profile: Profile) => { const setProfile = (profile: Profile) => {
updateState({ profileSet: Boolean(profile.name) }); updateState({ profileSet: Boolean(profile.name) });
} };
const setCards = (cards: Card[]) => { const setCards = (cards: Card[]) => {
updateState({ cardSet: cards.length > 0 }); updateState({ cardSet: cards.length > 0 });
} };
const setChannels = ({ channels, cardId }: { channels: Channel[]; cardId: string | null }) => { const setChannels = ({ channels, cardId }: { channels: Channel[]; cardId: string | null }) => {
updateState({ channelSet: channels.length > 0 }); updateState({ channelSet: cardId && channels.length > 0 });
} };
const { identity, contact, content } = app.state.session const { identity, contact, content } = app.state.session;
identity.addProfileListener(setProfile) identity.addProfileListener(setProfile);
contact.addCardListener(setCards) contact.addCardListener(setCards);
content.addChannelListener(setChannels) content.addChannelListener(setChannels);
return () => { return () => {
identity.removeProfileListener(setProfile); identity.removeProfileListener(setProfile);
contact.removeCardListener(setCards); contact.removeCardListener(setCards);
content.removeChannelListener(setChannels); content.removeChannelListener(setChannels);
} };
}, []); }, [app.state.session]);
const actions = { const actions = {
} };
return { state, actions } return { state, actions };
} }

View File

@ -1,5 +1,4 @@
import {StyleSheet} from 'react-native'; import {StyleSheet} from 'react-native';
import { Colors } from '../constants/Colors';
export const styles = StyleSheet.create({ export const styles = StyleSheet.create({
active: { active: {

View File

@ -1,9 +1,8 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Animated, useAnimatedValue, useWindowDimensions, Dimensions, Image, View } from 'react-native'; import { Animated, useAnimatedValue, Image, View } from 'react-native';
import { useCall } from './useCall.hook'; import { useCall } from './useCall.hook';
import { styles } from './Call.styled' import { styles } from './Call.styled';
import { Card as Contact } from '../card/Card'; import { Text, Surface, IconButton } from 'react-native-paper';
import { Text, Surface, IconButton, ActivityIndicator } from 'react-native-paper';
import { Confirm } from '../confirm/Confirm'; import { Confirm } from '../confirm/Confirm';
import { Colors } from '../constants/Colors'; import { Colors } from '../constants/Colors';
import { RTCView } from 'react-native-webrtc'; import { RTCView } from 'react-native-webrtc';
@ -14,11 +13,7 @@ export function Call() {
const [ending, setEnding] = useState(false); const [ending, setEnding] = useState(false);
const [applyingAudio, setApplyingAudio] = useState(false); const [applyingAudio, setApplyingAudio] = useState(false);
const [applyingVideo, setApplyingVideo] = useState(false); const [applyingVideo, setApplyingVideo] = useState(false);
const [accepting, setAccepting] = useState(null as null|string); const opacity = useAnimatedValue(0);
const [ignoring, setIgnoring] = useState(null as null|string);
const [declining, setDeclining] = useState(null as null|string);
const {height, width} = useWindowDimensions();
const opacity = useAnimatedValue(0)
const toggleAudio = async () => { const toggleAudio = async () => {
if (!applyingAudio) { if (!applyingAudio) {
@ -35,7 +30,7 @@ export function Call() {
} }
setApplyingAudio(false); setApplyingAudio(false);
} }
} };
const toggleVideo = async () => { const toggleVideo = async () => {
if (!applyingVideo) { if (!applyingVideo) {
@ -52,7 +47,7 @@ export function Call() {
} }
setApplyingVideo(false); setApplyingVideo(false);
} }
} };
const end = async () => { const end = async () => {
if (!ending) { if (!ending) {
@ -65,7 +60,7 @@ export function Call() {
} }
setEnding(false); setEnding(false);
} }
} };
const alertParams = { const alertParams = {
title: state.strings.operationFailed, title: state.strings.operationFailed,
@ -92,6 +87,7 @@ export function Call() {
useNativeDriver: true, useNativeDriver: true,
}).start(); }).start();
} }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [state.calling, state.fullscreen]); }, [state.calling, state.fullscreen]);
const viewStyle = state.calling && state.fullscreen ? styles.active : styles.inactive; const viewStyle = state.calling && state.fullscreen ? styles.active : styles.inactive;
@ -114,7 +110,7 @@ export function Call() {
resizeMode="contain" resizeMode="contain"
source={{ uri: state.calling.imageUrl }} source={{ uri: state.calling.imageUrl }}
/> />
<Text style={styles.duration}>{ `${Math.floor(state.duration/60)}:${(state.duration % 60).toString().padStart(2, '0')}` }</Text> <Text style={styles.duration}>{ `${Math.floor(state.duration / 60)}:${(state.duration % 60).toString().padStart(2, '0')}` }</Text>
</View> </View>
)} )}

View File

@ -1,7 +1,7 @@
import { useState, useContext, useEffect, useRef } from 'react' import { useState, useContext, useEffect, useRef } from 'react';
import { RingContext } from '../context/RingContext' import { RingContext } from '../context/RingContext';
import { DisplayContext } from '../context/DisplayContext' import { DisplayContext } from '../context/DisplayContext';
import { ContextType } from '../context/ContextType' import { ContextType } from '../context/ContextType';
import { Card } from 'databag-client-sdk'; import { Card } from 'databag-client-sdk';
export function useCall() { export function useCall() {
@ -25,12 +25,12 @@ export function useCall() {
failed: false, failed: false,
width: 0, width: 0,
height: 0, height: 0,
}) });
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const updateState = (value: any) => { const updateState = (value: any) => {
setState((s) => ({ ...s, ...value })) setState((s) => ({ ...s, ...value }));
} };
useEffect(() => { useEffect(() => {
const { width, height, strings } = display.state; const { width, height, strings } = display.state;
@ -41,14 +41,14 @@ export function useCall() {
const interval = setInterval(() => { const interval = setInterval(() => {
if (offset.current) { if (offset.current) {
const now = new Date(); const now = new Date();
const duration = Math.floor((now.getTime() / 1000) - offsetTime.current); const duration = Math.floor((now.getTime() / 1000) - offsetTime.current);
updateState({ duration }); updateState({ duration });
} }
}, 1000); }, 1000);
return () => { return () => {
clearInterval(interval); clearInterval(interval);
} };
}, []); }, []);
useEffect(() => { useEffect(() => {
const { calls, calling, fullscreen, localStream, remoteStream, remoteVideo, localVideo, audioEnabled, videoEnabled, connected, connectedTime, failed } = ring.state; const { calls, calling, fullscreen, localStream, remoteStream, remoteVideo, localVideo, audioEnabled, videoEnabled, connected, connectedTime, failed } = ring.state;

View File

@ -20,7 +20,7 @@ export const en = {
server: 'Server', server: 'Server',
token: 'Token', token: 'Token',
delayMessage: 'Key generation can take several minutes.', delayMessage: 'Key generation can take several minutes.',
noAccess: 'No Access', noAccess: 'No Access',
connecting: 'Connecting', connecting: 'Connecting',
setup: 'Setup', setup: 'Setup',
@ -294,7 +294,7 @@ export const en = {
disable: 'Disable', disable: 'Disable',
confirmDisable: 'Disabling Multi-Factor Authentication', confirmDisable: 'Disabling Multi-Factor Authentication',
disablePrompt: 'Are you sure you want to disable multi-factor authentication', disablePrompt: 'Are you sure you want to disable multi-factor authentication',
} };
export const fr = { export const fr = {
viewTerms: 'Voir les conditions d\'utilisation', viewTerms: 'Voir les conditions d\'utilisation',
@ -590,7 +590,7 @@ export const fr = {
disable: 'Désactiver', disable: 'Désactiver',
confirmDisable: "Désactivation de l'authentification multi-facteurs", confirmDisable: "Désactivation de l'authentification multi-facteurs",
disablePrompt: "Êtes-vous sûr de vouloir désactiver l'authentification multi-facteurs", disablePrompt: "Êtes-vous sûr de vouloir désactiver l'authentification multi-facteurs",
} };
export const sp = { export const sp = {
viewTerms: 'Ver los términos de servicio', viewTerms: 'Ver los términos de servicio',
@ -630,7 +630,7 @@ export const sp = {
accessingLink: 'Utilice el siguiente enlace para acceder a la cuenta especificada', accessingLink: 'Utilice el siguiente enlace para acceder a la cuenta especificada',
accessingToken: 'Utilice el siguiente token para acceder a la cuenta especificada desde la pantalla de inicio de sesión', accessingToken: 'Utilice el siguiente token para acceder a la cuenta especificada desde la pantalla de inicio de sesión',
noAccess: 'Sin Acceso', noAccess: 'Sin Acceso',
membership: 'Afiliación', membership: 'Afiliación',
channelHost: 'Anfitrión del Tema', channelHost: 'Anfitrión del Tema',
channelGuest: 'Invitado de Tema', channelGuest: 'Invitado de Tema',
@ -885,7 +885,7 @@ export const sp = {
disable: 'Desactivar', disable: 'Desactivar',
confirmDisable: 'Desactivación de la autenticación de dos factores', confirmDisable: 'Desactivación de la autenticación de dos factores',
disablePrompt: '¿Estás seguro de que quieres desactivar la autenticación de dos factores?', disablePrompt: '¿Estás seguro de que quieres desactivar la autenticación de dos factores?',
} };
export const pt = { export const pt = {
viewTerms: 'Ver os termos de serviço', viewTerms: 'Ver os termos de serviço',
@ -1180,7 +1180,7 @@ export const pt = {
disable: 'Desativar', disable: 'Desativar',
confirmDisable: 'Desativando Autenticação de Dois Fatores', confirmDisable: 'Desativando Autenticação de Dois Fatores',
disablePrompt: 'Tem certeza de que deseja desativar a autenticação de dois fatores?', disablePrompt: 'Tem certeza de que deseja desativar a autenticação de dois fatores?',
} };
export const de = { export const de = {
viewTerms: 'Nutzungsbedingungen anzeigen', viewTerms: 'Nutzungsbedingungen anzeigen',
@ -1213,7 +1213,7 @@ export const de = {
accounts: 'Konten', accounts: 'Konten',
noAccounts: 'Keine Konten', noAccounts: 'Keine Konten',
selectShare: 'Wählen Sie Das Thema Zum Teilen Aus', selectShare: 'Wählen Sie Das Thema Zum Teilen Aus',
addingTitle: 'Konto hinzufügen', addingTitle: 'Konto hinzufügen',
addingLink: 'Verwenden Sie den folgenden Link, um ein Konto zu erstellen', addingLink: 'Verwenden Sie den folgenden Link, um ein Konto zu erstellen',
addingToken: 'Verwenden Sie das folgende Token, um ein Konto vom Anmeldebildschirm aus zu erstellen', addingToken: 'Verwenden Sie das folgende Token, um ein Konto vom Anmeldebildschirm aus zu erstellen',
@ -1475,7 +1475,7 @@ export const de = {
disable: 'Deaktivieren', disable: 'Deaktivieren',
confirmDisable: 'Deaktivierung der Zwei-Faktor-Authentifizierung', confirmDisable: 'Deaktivierung der Zwei-Faktor-Authentifizierung',
disablePrompt: 'Sind Sie sicher, dass Sie die Zwei-Faktor-Authentifizierung deaktivieren möchten?', disablePrompt: 'Sind Sie sicher, dass Sie die Zwei-Faktor-Authentifizierung deaktivieren möchten?',
} };
export const ru = { export const ru = {
viewTerms: 'Просмотреть условия обслуживания', viewTerms: 'Просмотреть условия обслуживания',
@ -1770,7 +1770,7 @@ export const ru = {
disable: 'Отключить', disable: 'Отключить',
confirmDisable: 'Отключение двухфакторной аутентификации', confirmDisable: 'Отключение двухфакторной аутентификации',
disablePrompt: 'Вы уверены, что хотите отключить двухфакторную аутентификацию?', disablePrompt: 'Вы уверены, что хотите отключить двухфакторную аутентификацию?',
} };
export const el = { export const el = {
viewTerms: 'Δείτε τους όρους υπηρεσίας', viewTerms: 'Δείτε τους όρους υπηρεσίας',
@ -1972,7 +1972,7 @@ export const el = {
enableService: 'Υπηρεσία cloudflare', enableService: 'Υπηρεσία cloudflare',
serviceHint: 'Ενεργοποίηση υπηρεσίας CloudFlare', serviceHint: 'Ενεργοποίηση υπηρεσίας CloudFlare',
serverUrl: 'URL διακομιστή WebRTC', serverUrl: 'URL διακομιστή WebRTC',
urlHint: 'turn:ip:port?transport=udp', urlHint: 'turn:ip:port?transport=udp',
webUsername: 'Όνομα χρήστη WebRTC', webUsername: 'Όνομα χρήστη WebRTC',
webPassword: 'Κωδικός πρόσβασης WebRTC', webPassword: 'Κωδικός πρόσβασης WebRTC',
failedLoad: 'Αποτυχία φόρτωσης', failedLoad: 'Αποτυχία φόρτωσης',
@ -2037,7 +2037,7 @@ export const el = {
disable: 'Καθιστώ ανίκανο', disable: 'Καθιστώ ανίκανο',
confirmDisable: 'Απενεργοποίηση ελέγχου ταυτότητας πολλαπλών παραγόντων', confirmDisable: 'Απενεργοποίηση ελέγχου ταυτότητας πολλαπλών παραγόντων',
disablePrompt: 'Είστε βέβαιοι ότι θέλετε να απενεργοποιήσετε τον έλεγχο ταυτότητας πολλαπλών παραγόντων', disablePrompt: 'Είστε βέβαιοι ότι θέλετε να απενεργοποιήσετε τον έλεγχο ταυτότητας πολλαπλών παραγόντων',
} };
export function getLanguageStrings() { export function getLanguageStrings() {
const locale = Platform.OS === 'ios' ? NativeModules.SettingsManager?.settings.AppleLocale || NativeModules.SettingsManager?.settings.AppleLanguages[0] : NativeModules.I18nManager?.localeIdentifier; const locale = Platform.OS === 'ios' ? NativeModules.SettingsManager?.settings.AppleLocale || NativeModules.SettingsManager?.settings.AppleLanguages[0] : NativeModules.I18nManager?.localeIdentifier;

View File

@ -478,7 +478,7 @@ Falls Sie Fragen zu diesen Bedingungen oder zum Dienst haben, können Sie uns ü
Wir freuen uns, Sie als Teil der Databag-Community begrüßen zu dürfen, und wir hoffen, dass Sie unsere Dienste genießen. Wir freuen uns, Sie als Teil der Databag-Community begrüßen zu dürfen, und wir hoffen, dass Sie unsere Dienste genießen.
`, `,
pt: pt:
`Agradeço por se juntar à comunidade Databag! Estes Termos de Serviço (os "Termos") abrangem seus direitos e obrigações relacionados ao seu acesso e uso dos serviços da Databag, incluindo, mas não se limitando ao Databag (coletivamente, o "Serviço"). Todas as referências a "você", "seu" ou "usuário" se referem ao usuário do Serviço. Além destes Termos, por favor, reveja a Política de Privacidade da Databag, que descreve nossas práticas relacionadas à coleta e uso de suas informações. Estes Termos se aplicam também à nossa Política de Privacidade. Ao usar o Serviço, você declara e concorda que leu, entende e concorda em cumprir tanto estes Termos quanto nossa Política de Privacidade como acordos vinculativos. Além disso, você concorda que estes Termos e nossa Política de Privacidade se aplicam ao seu uso anterior, se houver. `Agradeço por se juntar à comunidade Databag! Estes Termos de Serviço (os "Termos") abrangem seus direitos e obrigações relacionados ao seu acesso e uso dos serviços da Databag, incluindo, mas não se limitando ao Databag (coletivamente, o "Serviço"). Todas as referências a "você", "seu" ou "usuário" se referem ao usuário do Serviço. Além destes Termos, por favor, reveja a Política de Privacidade da Databag, que descreve nossas práticas relacionadas à coleta e uso de suas informações. Estes Termos se aplicam também à nossa Política de Privacidade. Ao usar o Serviço, você declara e concorda que leu, entende e concorda em cumprir tanto estes Termos quanto nossa Política de Privacidade como acordos vinculativos. Além disso, você concorda que estes Termos e nossa Política de Privacidade se aplicam ao seu uso anterior, se houver.
POR FAVOR, LEIA CUIDADOSAMENTE ESTES TERMOS, POIS CONTÊM INFORMAÇÕES IMPORTANTES SOBRE SEUS DIREITOS E RESPONSABILIDADES, INCLUINDO A LIMITAÇÃO DE NOSSA RESPONSABILIDADE. SE VOCÊ NÃO ACEITAR ESTE ACORDO EM SUA TOTALIDADE, NÃO PODERÁ ACESSAR OU UTILIZAR O SERVIÇO. POR FAVOR, LEIA CUIDADOSAMENTE ESTES TERMOS, POIS CONTÊM INFORMAÇÕES IMPORTANTES SOBRE SEUS DIREITOS E RESPONSABILIDADES, INCLUINDO A LIMITAÇÃO DE NOSSA RESPONSABILIDADE. SE VOCÊ NÃO ACEITAR ESTE ACORDO EM SUA TOTALIDADE, NÃO PODERÁ ACESSAR OU UTILIZAR O SERVIÇO.
@ -825,5 +825,5 @@ support@databag.com
© 2022 Databag, Inc. © 2022 Databag, Inc.
` `,
} };

View File

@ -4,7 +4,6 @@ import {SafeAreaView, Modal, FlatList, View} from 'react-native';
import {styles} from './Content.styled'; import {styles} from './Content.styled';
import {useContent} from './useContent.hook'; import {useContent} from './useContent.hook';
import {Channel} from '../channel/Channel'; import {Channel} from '../channel/Channel';
import {Focus} from 'databag-client-sdk';
import {BlurView} from '@react-native-community/blur'; import {BlurView} from '@react-native-community/blur';
import {Card} from '../card/Card'; import {Card} from '../card/Card';
import {Confirm} from '../confirm/Confirm'; import {Confirm} from '../confirm/Confirm';
@ -35,23 +34,25 @@ export function Content({share, closeAll, openConversation, textCard}: { share:
actions.setSharing({ cardId, channelId, filePath, mimeType }); actions.setSharing({ cardId, channelId, filePath, mimeType });
} }
open(cardId, channelId); open(cardId, channelId);
} };
const open = (cardId: string | null, channelId: string) => { const open = (cardId: string | null, channelId: string) => {
actions.setFocus(cardId, channelId); actions.setFocus(cardId, channelId);
openConversation(); openConversation();
} };
useEffect(() => { useEffect(() => {
if (share) { if (share) {
closeAll(); closeAll();
} }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [share]); }, [share]);
useEffect(() => { useEffect(() => {
if (textCard.cardId) { if (textCard.cardId) {
openTopic(textCard.cardId); openTopic(textCard.cardId);
} }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [textCard]); }, [textCard]);
const openTopic = async (cardId: string) => { const openTopic = async (cardId: string) => {
@ -65,7 +66,7 @@ export function Content({share, closeAll, openConversation, textCard}: { share:
setAlert(true); setAlert(true);
} }
setAdding(false); setAdding(false);
} };
const addTopic = async () => { const addTopic = async () => {
setAdding(true); setAdding(true);
@ -73,7 +74,7 @@ export function Content({share, closeAll, openConversation, textCard}: { share:
const id = await actions.addTopic( const id = await actions.addTopic(
sealedTopic, sealedTopic,
subjectTopic, subjectTopic,
members.filter(id => Boolean(cards.find(card => card.cardId === id))), members.filter(memberId => Boolean(cards.find(card => card.cardId === memberId))),
); );
setAdd(false); setAdd(false);
setSubjectTopic(''); setSubjectTopic('');

View File

@ -48,7 +48,7 @@ export function useContent() {
return 0; return 0;
}; };
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const updateState = (value: any) => { const updateState = (value: any) => {
setState(s => ({...s, ...value})); setState(s => ({...s, ...value}));
}; };
@ -94,19 +94,19 @@ export function useContent() {
}; };
const selectImage = () => { const selectImage = () => {
if (contacts.length == 0) { if (contacts.length === 0) {
return notes; return notes;
} else if (contacts.length == 1) { } else if (contacts.length === 1) {
if (contacts[0]) { if (contacts[0]) {
return contacts[0].imageUrl; return contacts[0].imageUrl;
} else { } else {
return unknown; return unknown;
} }
} else if (contacts.length == 2) { } else if (contacts.length === 2) {
return iii_group; return iii_group;
} else if (contacts.length == 3) { } else if (contacts.length === 3) {
return iiii_group; return iiii_group;
} else if (contacts.length == 4) { } else if (contacts.length === 4) {
return iiiii_group; return iiiii_group;
} else { } else {
return group; return group;
@ -204,7 +204,7 @@ export function useContent() {
const sorted = filtered.sort((a, b) => { const sorted = filtered.sort((a, b) => {
const aUpdated = a?.lastTopic?.created; const aUpdated = a?.lastTopic?.created;
const bUpdated = b?.lastTopic?.created; const bUpdated = b?.lastTopic?.created;
if (aUpdated == bUpdated) { if (aUpdated === bUpdated) {
return 0; return 0;
} else if (!aUpdated) { } else if (!aUpdated) {
return 1; return 1;
@ -231,7 +231,7 @@ export function useContent() {
content.removeChannelListener(setChannels); content.removeChannelListener(setChannels);
settings.removeConfigListener(setConfig); settings.removeConfigListener(setConfig);
}; };
}, []); }, [app.state.session]);
const actions = { const actions = {
setSharing: app.actions.setSharing, setSharing: app.actions.setSharing,
@ -244,9 +244,9 @@ export function useContent() {
setFocus: async (cardId: string | null, channelId: string) => { setFocus: async (cardId: string | null, channelId: string) => {
await app.actions.setFocus(cardId, channelId); await app.actions.setFocus(cardId, channelId);
}, },
openTopic: async (cardId: string) => { openTopic: async (contactId: string) => {
const content = app.state.session.getContent() const content = app.state.session.getContent();
const card = state.cards.find(card => card.cardId === cardId) const card = state.cards.find(member => member.cardId === contactId);
if (card) { if (card) {
const sealable = card.sealable && state.sealSet; const sealable = card.sealable && state.sealSet;
const thread = state.sorted.find(channel => { const thread = state.sorted.find(channel => {
@ -256,21 +256,21 @@ export function useContent() {
} }
return false; return false;
}); });
if (thread) { if (thread) {
return thread.channelId; return thread.channelId;
} else { } else {
const topic = await content.addChannel(sealable, sealable ? 'sealed' : 'superbasic', {}, [cardId]); const topic = await content.addChannel(sealable, sealable ? 'sealed' : 'superbasic', {}, [cardId]);
return topic.id; return topic.id;
} }
} }
}, },
addTopic: async (sealed: boolean, subject: string, contacts: string[]) => { addTopic: async (sealed: boolean, subject: string, contacts: string[]) => {
const content = app.state.session.getContent() const content = app.state.session.getContent();
if (sealed) { if (sealed) {
const topic = await content.addChannel(true, 'sealed', { subject }, contacts) const topic = await content.addChannel(true, 'sealed', { subject }, contacts);
return topic.id; return topic.id;
} else { } else {
const topic = await content.addChannel(false, 'superbasic', { subject }, contacts) const topic = await content.addChannel(false, 'superbasic', { subject }, contacts);
return topic.id; return topic.id;
} }
}, },

View File

@ -1,9 +1,9 @@
import React, { ReactNode, createContext } from 'react' import React, { ReactNode, createContext } from 'react';
import { useRingContext } from './useRingContext.hook' import { useRingContext } from './useRingContext.hook';
export const RingContext = createContext({}) export const RingContext = createContext({});
export function RingContextProvider({ children }: { children: ReactNode }) { export function RingContextProvider({ children }: { children: ReactNode }) {
const { state, actions } = useRingContext() const { state, actions } = useRingContext();
return <RingContext.Provider value={{ state, actions }}>{children}</RingContext.Provider> return <RingContext.Provider value={{ state, actions }}>{children}</RingContext.Provider>;
} }

View File

@ -4,7 +4,7 @@ import {Platform, PermissionsAndroid} from 'react-native';
import {SessionStore} from '../SessionStore'; import {SessionStore} from '../SessionStore';
import {NativeCrypto} from '../NativeCrypto'; import {NativeCrypto} from '../NativeCrypto';
import {LocalStore} from '../LocalStore'; import {LocalStore} from '../LocalStore';
import { StagingFiles } from '../StagingFiles' import { StagingFiles } from '../StagingFiles';
import messaging from '@react-native-firebase/messaging'; import messaging from '@react-native-firebase/messaging';
const DATABAG_DB = 'db_v244.db'; const DATABAG_DB = 'db_v244.db';
@ -85,7 +85,7 @@ export function useAppContext() {
console.log(err); console.log(err);
return { token: '', type: '' }; return { token: '', type: '' };
} }
} };
const actions = { const actions = {
setMonthFirstDate: async (monthFirstDate: boolean) => { setMonthFirstDate: async (monthFirstDate: boolean) => {
@ -171,7 +171,7 @@ export function useAppContext() {
} }
}, },
clearFocus: () => { clearFocus: () => {
if (state.session) { if (state.session) {
state.session.clearFocus(); state.session.clearFocus();
updateState({ focus: null }); updateState({ focus: null });
} }

View File

@ -20,7 +20,7 @@ export function useDisplayContext() {
useEffect(() => { useEffect(() => {
const layout = dim.width < SMALL_LARGE ? 'small' : 'large'; const layout = dim.width < SMALL_LARGE ? 'small' : 'large';
updateState({layout, width: dim.width, height: dim.height}); updateState({layout, width: dim.width, height: dim.height});
}, [dim.width]); }, [dim.height, dim.width]);
const actions = {}; const actions = {};

View File

@ -1,27 +1,21 @@
import { useState, useContext, useEffect, useRef } from 'react' import { useState, useContext, useEffect, useRef } from 'react';
import { DisplayContext } from '../context/DisplayContext'; import { AppContext } from '../context/AppContext';
import { AppContext } from '../context/AppContext' import { ContextType } from '../context/ContextType';
import { ContextType } from '../context/ContextType'
import { Link, type Card } from 'databag-client-sdk'; import { Link, type Card } from 'databag-client-sdk';
import InCallManager from 'react-native-incall-manager'; import InCallManager from 'react-native-incall-manager';
import { import {
ScreenCapturePickerView,
RTCPeerConnection, RTCPeerConnection,
RTCIceCandidate, RTCIceCandidate,
RTCSessionDescription, RTCSessionDescription,
RTCView,
MediaStream, MediaStream,
MediaiStreamTrack,
mediaDevices, mediaDevices,
registerGlobals
} from 'react-native-webrtc'; } from 'react-native-webrtc';
const CLOSE_POLL_MS = 100; const CLOSE_POLL_MS = 100;
export function useRingContext() { export function useRingContext() {
const app = useContext(AppContext) as ContextType; const app = useContext(AppContext) as ContextType;
const display = useContext(DisplayContext) as ContextType;
const call = useRef(null as { peer: RTCPeerConnection, link: Link, candidates: RTCIceCandidate[] } | null); const call = useRef(null as { peer: RTCPeerConnection, link: Link, candidates: RTCIceCandidate[] } | null);
const sourceStream = useRef(null as null|MediaStream); const sourceStream = useRef(null as null|MediaStream);
const localStream = useRef(null as null|MediaStream); const localStream = useRef(null as null|MediaStream);
@ -52,12 +46,12 @@ export function useRingContext() {
connectedTime: 0, connectedTime: 0,
failed: false, failed: false,
fullscreen: false, fullscreen: false,
}) });
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const updateState = (value: any) => { const updateState = (value: any) => {
setState((s) => ({ ...s, ...value })) setState((s) => ({ ...s, ...value }));
} };
useEffect(() => { useEffect(() => {
const calls = ringing.map(ring => ({ callId: ring.callId, card: cards.find(card => ring.cardId === card.cardId) })) const calls = ringing.map(ring => ({ callId: ring.callId, card: cards.find(card => ring.cardId === card.cardId) }))
@ -65,18 +59,9 @@ export function useRingContext() {
updateState({ calls }); updateState({ calls });
}, [ringing, cards]); }, [ringing, cards]);
const constraints = {
mandatory: {
OfferToReceiveAudio: true,
OfferToReceiveVideo: false,
VoiceActivityDetection: true
}
};
const linkStatus = async (status: string) => { const linkStatus = async (status: string) => {
if (call.current) { if (call.current) {
try { try {
const { peer, link } = call.current;
if (status === 'connected') { if (status === 'connected') {
const now = new Date(); const now = new Date();
const connectedTime = Math.floor(now.getTime() / 1000); const connectedTime = Math.floor(now.getTime() / 1000);
@ -89,10 +74,10 @@ export function useRingContext() {
console.log(err); console.log(err);
} }
} }
} };
const updatePeer = async (type: string, data?: any) => { const updatePeer = async (mode: string, value?: any) => {
peerUpdate.current.push({ type, data }); peerUpdate.current.push({ type: mode, data: value });
if (!updatingPeer.current) { if (!updatingPeer.current) {
updatingPeer.current = true; updatingPeer.current = true;
@ -114,13 +99,13 @@ export function useRingContext() {
const offer = new RTCSessionDescription(data.description); const offer = new RTCSessionDescription(data.description);
await peer.setRemoteDescription(offer); await peer.setRemoteDescription(offer);
if (data.description.type === 'offer') { if (data.description.type === 'offer') {
const description = await peer.createAnswer(); const desc = await peer.createAnswer();
await peer.setLocalDescription(description); await peer.setLocalDescription(desc);
link.sendMessage({ description }); link.sendMessage({ description: desc });
} }
for (const candidate of candidates) { for (const candidate of candidates) {
await peer.addIceCandidate(candidate); await peer.addIceCandidate(candidate);
}; }
call.current.candidates = []; call.current.candidates = [];
} else if (data.candidate) { } else if (data.candidate) {
const candidate = new RTCIceCandidate(data.candidate); const candidate = new RTCIceCandidate(data.candidate);
@ -153,7 +138,7 @@ export function useRingContext() {
} }
if (data.kind === 'video') { if (data.kind === 'video') {
InCallManager.setForceSpeakerphoneOn(true); InCallManager.setForceSpeakerphoneOn(true);
updateState({ localVideo: true }) updateState({ localVideo: true });
} }
break; break;
default: default:
@ -167,7 +152,7 @@ export function useRingContext() {
} }
updatingPeer.current = false; updatingPeer.current = false;
} }
} };
const setup = async (link: Link, card: Card, polite: boolean) => { const setup = async (link: Link, card: Card, polite: boolean) => {
@ -179,8 +164,8 @@ export function useRingContext() {
audio: true, audio: true,
video: { video: {
frameRate: 30, frameRate: 30,
facingMode: 'user' facingMode: 'user',
} },
}); });
InCallManager.start({media: 'audio'}); InCallManager.start({media: 'audio'});
localAudio.current = sourceStream.current.getTracks().find(track => track.kind === 'audio'); localAudio.current = sourceStream.current.getTracks().find(track => track.kind === 'audio');
@ -201,7 +186,7 @@ export function useRingContext() {
localStream: localStream.current, remoteStream: remoteStream.current }); localStream: localStream.current, remoteStream: remoteStream.current });
link.setStatusListener(linkStatus); link.setStatusListener(linkStatus);
link.setMessageListener((msg: any) => updatePeer('message', msg)); link.setMessageListener((msg: any) => updatePeer('message', msg));
} };
const cleanup = async () => { const cleanup = async () => {
closing.current = true; closing.current = true;
@ -232,11 +217,11 @@ export function useRingContext() {
updateState({ calling: null, connected: false, connectedTime: 0, fullscreen: false, failed: false, updateState({ calling: null, connected: false, connectedTime: 0, fullscreen: false, failed: false,
localStream: null, remoteStream: null, localVideo: false, remoteVideo: false }); localStream: null, remoteStream: null, localVideo: false, remoteVideo: false });
closing.current = false; closing.current = false;
} };
const transmit = (ice: { urls: string; username: string; credential: string }[]) => { const transmit = (ice: { urls: string; username: string; credential: string }[]) => {
const peerConnection = new RTCPeerConnection({ iceServers: ice }); const peerConnection = new RTCPeerConnection({ iceServers: ice });
peerConnection.addEventListener( 'connectionstatechange', event => { peerConnection.addEventListener( 'connectionstatechange', () => {
if (peerConnection.connectionState === 'failed') { if (peerConnection.connectionState === 'failed') {
cleanup(); cleanup();
} }
@ -245,31 +230,31 @@ export function useRingContext() {
updatePeer('candidate', event.candidate); updatePeer('candidate', event.candidate);
}); });
peerConnection.addEventListener( 'icecandidateerror', event => { peerConnection.addEventListener( 'icecandidateerror', event => {
console.log("ICE ERROR"); console.log('ICE ERROR', event);
}); });
peerConnection.addEventListener( 'iceconnectionstatechange', event => { peerConnection.addEventListener( 'iceconnectionstatechange', event => {
console.log("ICE STATE CHANGE", event); console.log('ICE STATE CHANGE', event);
}); });
peerConnection.addEventListener( 'negotiationneeded', event => { peerConnection.addEventListener( 'negotiationneeded', () => {
updatePeer('negotiate'); updatePeer('negotiate');
}); });
peerConnection.addEventListener( 'signalingstatechange', event => { peerConnection.addEventListener( 'signalingstatechange', event => {
console.log("ICE SIGNALING", event); console.log('ICE SIGNALING', event);
}); });
peerConnection.addEventListener( 'track', event => { peerConnection.addEventListener( 'track', event => {
updatePeer('remote_track', event.track); updatePeer('remote_track', event.track);
}); });
return peerConnection; return peerConnection;
} };
useEffect(() => { useEffect(() => {
if (app.state.session) { if (app.state.session) {
const setRing = (ringing: { cardId: string, callId: string }[]) => { const setRing = (incoming: { cardId: string, callId: string }[]) => {
setRinging(ringing); setRinging(incoming);
} };
const setContacts = (cards: Card[]) => { const setContacts = (contacts: Card[]) => {
setCards(cards); setCards(contacts);
} };
const ring = app.state.session.getRing(); const ring = app.state.session.getRing();
ring.addRingingListener(setRinging); ring.addRingingListener(setRinging);
const contact = app.state.session.getContact(); const contact = app.state.session.getContact();
@ -278,8 +263,9 @@ export function useRingContext() {
ring.removeRingingListener(setRing); ring.removeRingingListener(setRing);
contact.removeCardListener(setContacts); contact.removeCardListener(setContacts);
cleanup(); cleanup();
} };
} }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [app.state.session]); }, [app.state.session]);
const actions = { const actions = {
@ -330,7 +316,7 @@ export function useRingContext() {
}, },
enableAudio: async () => { enableAudio: async () => {
if (closing.current || !call.current) { if (closing.current || !call.current) {
throw new Error('cannot unmute audio') throw new Error('cannot unmute audio');
} }
if (!localAudio.current) { if (!localAudio.current) {
throw new Error('audio not available'); throw new Error('audio not available');
@ -377,8 +363,8 @@ export function useRingContext() {
} }
updateState({ videoEnabled: false }); updateState({ videoEnabled: false });
}, },
} };
return { state, actions } return { state, actions };
} }

View File

@ -95,10 +95,10 @@ export const styles = StyleSheet.create({
color: Colors.placeholder, color: Colors.placeholder,
}, },
add: { add: {
height: 72,
display: 'flex', display: 'flex',
alignItems: 'center', flexDirection: 'column',
justifyContent: 'center', paddingTop: 8,
paddingBottom: 8,
}, },
icon: { icon: {
flexShrink: 0, flexShrink: 0,
@ -138,7 +138,6 @@ export const styles = StyleSheet.create({
alignItems: 'center', alignItems: 'center',
width: '100%', width: '100%',
minWidth: 0, minWidth: 0,
alignItems: 'center',
}, },
largeHeader: { largeHeader: {
paddingLeft: 16, paddingLeft: 16,
@ -159,12 +158,6 @@ export const styles = StyleSheet.create({
minWidth: 0, minWidth: 0,
flexShrink: 1, flexShrink: 1,
}, },
add: {
display: 'flex',
flexDirection: 'column',
paddingTop: 8,
paddingBottom: 8,
},
message: { message: {
width: '100%', width: '100%',
fontSize: 14, fontSize: 14,
@ -200,5 +193,5 @@ export const styles = StyleSheet.create({
separator: { separator: {
width: 1, width: 1,
height: '100%', height: '100%',
} },
}); });

View File

@ -1,5 +1,5 @@
import React, { useEffect, useState, useRef } from 'react'; import React, { useEffect, useState, useRef } from 'react';
import {Animated, useAnimatedValue, KeyboardAvoidingView, Modal, Platform, ScrollView, Pressable, View, FlatList, TouchableOpacity} from 'react-native'; import {Animated, useAnimatedValue, Modal, ScrollView, Pressable, View, FlatList } from 'react-native';
import {styles} from './Conversation.styled'; import {styles} from './Conversation.styled';
import {useConversation} from './useConversation.hook'; import {useConversation} from './useConversation.hook';
import {Message} from '../message/Message'; import {Message} from '../message/Message';
@ -7,14 +7,14 @@ import {Surface, Icon, Text, TextInput, Menu, IconButton, Divider} from 'react-n
import { ActivityIndicator } from 'react-native-paper'; import { ActivityIndicator } from 'react-native-paper';
import { Colors } from '../constants/Colors'; import { Colors } from '../constants/Colors';
import { Confirm } from '../confirm/Confirm'; import { Confirm } from '../confirm/Confirm';
import ColorPicker from 'react-native-wheel-color-picker' import ColorPicker from 'react-native-wheel-color-picker';
import {BlurView} from '@react-native-community/blur'; import {BlurView} from '@react-native-community/blur';
import ImagePicker from 'react-native-image-crop-picker' import ImagePicker from 'react-native-image-crop-picker';
import { ImageFile } from './imageFile/ImageFile'; import { ImageFile } from './imageFile/ImageFile';
import { VideoFile } from './videoFile/VideoFile'; import { VideoFile } from './videoFile/VideoFile';
import { AudioFile } from './audioFile/AudioFile'; import { AudioFile } from './audioFile/AudioFile';
import { BinaryFile } from './binaryFile/BinaryFile'; import { BinaryFile } from './binaryFile/BinaryFile';
import DocumentPicker from 'react-native-document-picker' import DocumentPicker from 'react-native-document-picker';
const SCROLL_THRESHOLD = 16; const SCROLL_THRESHOLD = 16;
@ -40,8 +40,8 @@ export function Conversation({close, openDetails, wide}: {close: ()=>void, openD
const contentHeight = useRef(0); const contentHeight = useRef(0);
const contentLead = useRef(null); const contentLead = useRef(null);
const scrollOffset = useRef(0); const scrollOffset = useRef(0);
const busy = useRef(false); const busy = useRef(false);
const scale = useAnimatedValue(0) const scale = useAnimatedValue(0);
const alertParams = { const alertParams = {
title: state.strings.operationFailed, title: state.strings.operationFailed,
@ -68,6 +68,7 @@ export function Conversation({close, openDetails, wide}: {close: ()=>void, openD
useNativeDriver: false, useNativeDriver: false,
}).start(); }).start();
} }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [state.assets]); }, [state.assets]);
const sendMessage = async () => { const sendMessage = async () => {
@ -84,7 +85,7 @@ export function Conversation({close, openDetails, wide}: {close: ()=>void, openD
setSending(false); setSending(false);
busy.current = false; busy.current = false;
} }
} };
const loadMore = async () => { const loadMore = async () => {
if (!more) { if (!more) {
@ -92,12 +93,12 @@ export function Conversation({close, openDetails, wide}: {close: ()=>void, openD
await actions.more(); await actions.more();
setMore(false); setMore(false);
} }
} };
const onClose = () => { const onClose = () => {
actions.close(); actions.close();
close(); close();
} };
const onContent = (width, height) => { const onContent = (width, height) => {
const currentLead = state.topics.length > 0 ? state.topics[0].topicId : null; const currentLead = state.topics.length > 0 ? state.topics[0].topicId : null;
@ -110,7 +111,7 @@ export function Conversation({close, openDetails, wide}: {close: ()=>void, openD
} }
contentLead.current = currentLead; contentLead.current = currentLead;
contentHeight.current = height; contentHeight.current = height;
} };
const onScroll = (ev) => { const onScroll = (ev) => {
const { contentOffset } = ev.nativeEvent; const { contentOffset } = ev.nativeEvent;
@ -125,7 +126,7 @@ export function Conversation({close, openDetails, wide}: {close: ()=>void, openD
} }
} }
scrollOffset.current = offset; scrollOffset.current = offset;
} };
const addImage = async () => { const addImage = async () => {
try { try {
@ -135,7 +136,7 @@ export function Conversation({close, openDetails, wide}: {close: ()=>void, openD
catch (err) { catch (err) {
console.log(err); console.log(err);
} }
} };
const addVideo = async () => { const addVideo = async () => {
try { try {
@ -145,7 +146,7 @@ export function Conversation({close, openDetails, wide}: {close: ()=>void, openD
catch (err) { catch (err) {
console.log(err); console.log(err);
} }
} };
const addAudio = async () => { const addAudio = async () => {
try { try {
@ -153,12 +154,12 @@ export function Conversation({close, openDetails, wide}: {close: ()=>void, openD
presentationStyle: 'fullScreen', presentationStyle: 'fullScreen',
copyTo: 'cachesDirectory', copyTo: 'cachesDirectory',
type: DocumentPicker.types.audio, type: DocumentPicker.types.audio,
}) });
actions.addAudio(audio.fileCopyUri, audio.name); actions.addAudio(audio.fileCopyUri, audio.name);
} catch (err) { } catch (err) {
console.log(err); console.log(err);
} }
} };
const addBinary = async () => { const addBinary = async () => {
try { try {
@ -166,27 +167,27 @@ export function Conversation({close, openDetails, wide}: {close: ()=>void, openD
presentationStyle: 'fullScreen', presentationStyle: 'fullScreen',
copyTo: 'cachesDirectory', copyTo: 'cachesDirectory',
type: DocumentPicker.types.allFiles, type: DocumentPicker.types.allFiles,
}) });
actions.addBinary(binary.fileCopyUri, binary.name); actions.addBinary(binary.fileCopyUri, binary.name);
} catch (err) { } catch (err) {
console.log(err); console.log(err);
} }
} };
const media = state.assets.map((asset, index) => { const media = state.assets.map((asset, index) => {
if (asset.type === 'image') { if (asset.type === 'image') {
return <ImageFile key={index} path={asset.path} disabled={sending} remove={()=>actions.removeAsset(index)} /> return <ImageFile key={index} path={asset.path} disabled={sending} remove={()=>actions.removeAsset(index)} />;
} else if (asset.type === 'video') { } else if (asset.type === 'video') {
return <VideoFile key={index} path={asset.path} thumbPosition={(position: number) => actions.setThumbPosition(index, position)} disabled={sending} remove={()=>actions.removeAsset(index)} /> return <VideoFile key={index} path={asset.path} thumbPosition={(position: number) => actions.setThumbPosition(index, position)} disabled={sending} remove={()=>actions.removeAsset(index)} />;
} else if (asset.type === 'audio') { } else if (asset.type === 'audio') {
return <AudioFile key={index} path={asset.path} disabled={sending} remove={()=>actions.removeAsset(index)} /> return <AudioFile key={index} path={asset.path} disabled={sending} remove={()=>actions.removeAsset(index)} />;
} else { } else {
return <BinaryFile key={index} path={asset.path} disabled={sending} remove={()=>actions.removeAsset(index)} /> return <BinaryFile key={index} path={asset.path} disabled={sending} remove={()=>actions.removeAsset(index)} />;
} }
}); });
const containerStyle = state.layout === 'large' ? { ...styles.conversation, ...styles.largeConversation } : styles.conversation; const containerStyle = state.layout === 'large' ? { ...styles.conversation, ...styles.largeConversation } : styles.conversation;
const headerStyle = state.layout === 'large' ? { ...styles.header, ...styles.largeHeader } : styles.header; const headerStyle = state.layout === 'large' ? { ...styles.header, ...styles.largeHeader, flexDirection: 'row-reverse' } : { ...styles.header, flexDirection: 'row' };
const padStyle = state.layout === 'large' ? styles.pad : styles.nopad; const padStyle = state.layout === 'large' ? styles.pad : styles.nopad;
const inputPadStyle = state.layout === 'large' ? styles.pad : styles.indent; const inputPadStyle = state.layout === 'large' ? styles.pad : styles.indent;
const offset = state.layout === 'large' ? state.avoid - 64 : state.avoid - 120; const offset = state.layout === 'large' ? state.avoid - 64 : state.avoid - 120;
@ -194,18 +195,18 @@ export function Conversation({close, openDetails, wide}: {close: ()=>void, openD
const disableVideo = !state.detailSet || !state.detail?.enableVideo; const disableVideo = !state.detailSet || !state.detail?.enableVideo;
const disableAudio = !state.detailSet || !state.detail?.enableAudio; const disableAudio = !state.detailSet || !state.detail?.enableAudio;
const disableBinary = !state.detailSet || !state.detail?.enableBinary; const disableBinary = !state.detailSet || !state.detail?.enableBinary;
const statusStyle = state.layout === 'large' ? { ...styles.status, flexDirection: 'row-reverse' } : { ...styles.status, flexDirection: 'row' };
return ( return (
<View style={containerStyle}> <View style={containerStyle}>
<View style={{ ...headerStyle, flexDirection: state.layout === 'large' ? 'row-reverse' : 'row' }}> <View style={headerStyle}>
<IconButton style={styles.icon} mode="contained" icon={wide ? 'close' : 'arrow-left'} size={28} onPress={onClose} /> <IconButton style={styles.icon} mode="contained" icon={wide ? 'close' : 'arrow-left'} size={28} onPress={onClose} />
<View style={styles.status}> <View style={styles.status} />
</View>
<View style={styles.title}> <View style={styles.title}>
{ state.detailSet && state.subject && ( { state.detailSet && state.subject && (
<Text adjustsFontSizeToFit={true} numberOfLines={1} style={styles.label}>{ state.subject }</Text> <Text adjustsFontSizeToFit={true} numberOfLines={1} style={styles.label}>{ state.subject }</Text>
)} )}
{ state.detailSet && state.host && !state.subject && state.subjectNames.length == 0 && ( { state.detailSet && state.host && !state.subject && state.subjectNames.length === 0 && (
<Text adjustsFontSizeToFit={true} numberOfLines={1} style={styles.label}>{ state.strings.notes }</Text> <Text adjustsFontSizeToFit={true} numberOfLines={1} style={styles.label}>{ state.strings.notes }</Text>
)} )}
{ state.detailSet && !state.subject && state.subjectNames.length > 0 && ( { state.detailSet && !state.subject && state.subjectNames.length > 0 && (
@ -215,7 +216,7 @@ export function Conversation({close, openDetails, wide}: {close: ()=>void, openD
<Text adjustsFontSizeToFit={true} numberOfLines={1} style={styles.unknown}>{ `, ${state.strings.unknownContact} (${state.unknownContacts})` }</Text> <Text adjustsFontSizeToFit={true} numberOfLines={1} style={styles.unknown}>{ `, ${state.strings.unknownContact} (${state.unknownContacts})` }</Text>
)} )}
</View> </View>
<View style={{ ...styles.status, flexDirection: state.layout === 'large' ? 'row-reverse' : 'row' }}> <View style={statusStyle}>
{ state.detailSet && !state.access && ( { state.detailSet && !state.access && (
<Icon source="alert-circle-outline" size={20} color={Colors.offsync} /> <Icon source="alert-circle-outline" size={20} color={Colors.offsync} />
)} )}
@ -229,7 +230,7 @@ export function Conversation({close, openDetails, wide}: {close: ()=>void, openD
<Icon source="shield-outline" size={20} /> <Icon source="shield-outline" size={20} />
)} )}
</View> </View>
<IconButton style={styles.icon} mode="contained" icon={state.layout==='large' ? 'cog-transfer-outline' : 'dots-vertical'} size={28} onPress={openDetails} /> <IconButton style={styles.icon} mode="contained" icon={state.layout === 'large' ? 'cog-transfer-outline' : 'dots-vertical'} size={28} onPress={openDetails} />
</View> </View>
<View style={padStyle}> <View style={padStyle}>
<Divider style={styles.topBorder} bold={true} /> <Divider style={styles.topBorder} bold={true} />
@ -260,13 +261,13 @@ export function Conversation({close, openDetails, wide}: {close: ()=>void, openD
select={(id)=>setSelected(id)} select={(id)=>setSelected(id)}
selected={selected} selected={selected}
/> />
) );
}} }}
keyExtractor={topic => (topic.topicId)} keyExtractor={topic => (topic.topicId)}
/> />
{ state.loaded && state.topics.length === 0 && ( { state.loaded && state.topics.length === 0 && (
<Text style={styles.empty}>{state.strings.noMessages}</Text> <Text style={styles.empty}>{state.strings.noMessages}</Text>
)} )}
{ !state.loaded && ( { !state.loaded && (
<View style={styles.loading}> <View style={styles.loading}>
<ActivityIndicator size="large" /> <ActivityIndicator size="large" />
@ -334,9 +335,9 @@ export function Conversation({close, openDetails, wide}: {close: ()=>void, openD
</Surface> </Surface>
</Pressable> </Pressable>
)}> )}>
<Menu.Item onPress={() => { actions.setTextSize(12); setSizeMenu(false) }} title={state.strings.textSmall} /> <Menu.Item onPress={() => { actions.setTextSize(12); setSizeMenu(false); }} title={state.strings.textSmall} />
<Menu.Item onPress={() => { actions.setTextSize(16); setSizeMenu(false) }} title={state.strings.textMedium} /> <Menu.Item onPress={() => { actions.setTextSize(16); setSizeMenu(false); }} title={state.strings.textMedium} />
<Menu.Item onPress={() => { actions.setTextSize(20); setSizeMenu(false) }} title={state.strings.textLarge} /> <Menu.Item onPress={() => { actions.setTextSize(20); setSizeMenu(false); }} title={state.strings.textLarge} />
</Menu> </Menu>
<View style={styles.end}> <View style={styles.end}>
@ -345,10 +346,10 @@ export function Conversation({close, openDetails, wide}: {close: ()=>void, openD
{ sending && ( { sending && (
<ActivityIndicator size="small" /> <ActivityIndicator size="small" />
)} )}
{ !sending && state.access && state.validShare && (state.message || state.assets.length != 0) && ( { !sending && state.access && state.validShare && (state.message || state.assets.length !== 0) && (
<Icon style={styles.button} source="send" size={24} color={Colors.primary} /> <Icon style={styles.button} source="send" size={24} color={Colors.primary} />
)} )}
{ !sending && (!state.access || !state.validShare || (!state.message && state.assets.length == 0)) && ( { !sending && (!state.access || !state.validShare || (!state.message && state.assets.length === 0)) && (
<Icon style={styles.button} source="send" size={24} color={Colors.placeholder} /> <Icon style={styles.button} source="send" size={24} color={Colors.placeholder} />
)} )}
</Surface> </Surface>

View File

@ -1,5 +1,4 @@
import {StyleSheet} from 'react-native'; import {StyleSheet} from 'react-native';
import {Colors} from '../constants/Colors';
export const styles = StyleSheet.create({ export const styles = StyleSheet.create({
audio: { audio: {

View File

@ -1,13 +1,10 @@
import React, { useEffect } from 'react'; import React from 'react';
import { View, Image } from 'react-native' import { View, Image } from 'react-native';
import { IconButton } from 'react-native-paper'; import { IconButton } from 'react-native-paper';
import { useAudioFile } from './useAudioFile.hook'; import {styles} from './AudioFile.styled';
import {styles} from './AudioFile.styled'
import thumb from '../../images/audio.png'; import thumb from '../../images/audio.png';
export function AudioFile({ path, disabled, remove }: {path: string, disabled: boolean, remove: ()=>void}) { export function AudioFile({ disabled, remove }: {path: string, disabled: boolean, remove: ()=>void}) {
const { state, actions } = useAudioFile();
return ( return (
<View style={styles.audio}> <View style={styles.audio}>
<Image <Image

View File

@ -1,16 +0,0 @@
import { useState, useEffect } from 'react'
export function useAudioFile(path: string) {
const [state, setState] = useState({
})
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const updateState = (value: any) => {
setState((s) => ({ ...s, ...value }))
}
const actions = {
}
return { state, actions }
}

View File

@ -1,5 +1,4 @@
import {StyleSheet} from 'react-native'; import {StyleSheet} from 'react-native';
import {Colors} from '../constants/Colors';
export const styles = StyleSheet.create({ export const styles = StyleSheet.create({
binary: { binary: {

View File

@ -1,12 +1,10 @@
import React, { useEffect } from 'react'; import React from 'react';
import { View, Image } from 'react-native' import { View, Image } from 'react-native';
import { IconButton } from 'react-native-paper' import { IconButton } from 'react-native-paper';
import { useBinaryFile } from './useBinaryFile.hook'; import {styles} from './BinaryFile.styled';
import {styles} from './BinaryFile.styled'
import thumb from '../../images/binary.png'; import thumb from '../../images/binary.png';
export function BinaryFile({ path, disabled, remove }: {path: string, disabled: boolean, remove: ()=>void}) { export function BinaryFile({ disabled, remove }: {path: string, disabled: boolean, remove: ()=>void}) {
const { state, actions } = useBinaryFile();
return ( return (
<View style={styles.binary}> <View style={styles.binary}>

View File

@ -1,16 +0,0 @@
import { useState, useEffect } from 'react'
export function useBinaryFile(path: string) {
const [state, setState] = useState({
})
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const updateState = (value: any) => {
setState((s) => ({ ...s, ...value }))
}
const actions = {
}
return { state, actions }
}

View File

@ -1,5 +1,4 @@
import {StyleSheet} from 'react-native'; import {StyleSheet} from 'react-native';
import {Colors} from '../constants/Colors';
export const styles = StyleSheet.create({ export const styles = StyleSheet.create({
image: { image: {

View File

@ -1,8 +1,8 @@
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { Image, View, Animated, useAnimatedValue } from 'react-native' import { Image, View, Animated, useAnimatedValue } from 'react-native';
import { IconButton, Text } from 'react-native-paper'; import { IconButton } from 'react-native-paper';
import { useImageFile } from './useImageFile.hook'; import { useImageFile } from './useImageFile.hook';
import {styles} from './ImageFile.styled' import {styles} from './ImageFile.styled';
export function ImageFile({ path, disabled, remove }: {path: string, disabled: boolean, remove: ()=>void}) { export function ImageFile({ path, disabled, remove }: {path: string, disabled: boolean, remove: ()=>void}) {
const { state, actions } = useImageFile(); const { state, actions } = useImageFile();
@ -16,21 +16,12 @@ export function ImageFile({ path, disabled, remove }: {path: string, disabled: b
useNativeDriver: true, useNativeDriver: true,
}).start(); }).start();
} }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [state.loaded]); }, [state.loaded]);
const showImage = () => {
setModal(true);
actions.loadImage();
};
const hideImage = () => {
setModal(false);
actions.cancelLoad();
}
return ( return (
<View style={styles.image}> <View style={styles.image}>
<Animated.View style={[styles.thumb,{opacity},]}> <Animated.View style={[styles.thumb,{opacity}]}>
<Image <Image
resizeMode="contain" resizeMode="contain"
height={72} height={72}

View File

@ -1,22 +1,22 @@
import { useState, useEffect } from 'react' import { useState } from 'react';
export function useImageFile(source: any) { export function useImageFile() {
const [state, setState] = useState({ const [state, setState] = useState({
loaded: false, loaded: false,
ratio: 1, ratio: 1,
}) });
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const updateState = (value: any) => { const updateState = (value: any) => {
setState((s) => ({ ...s, ...value })) setState((s) => ({ ...s, ...value }));
} };
const actions = { const actions = {
loaded: (e) => { loaded: (e) => {
const { width, height } = e.nativeEvent.source; const { width, height } = e.nativeEvent.source;
updateState({ loaded: true, ratio: width / height }); updateState({ loaded: true, ratio: width / height });
}, },
} };
return { state, actions } return { state, actions };
} }

View File

@ -1,44 +1,41 @@
import { useState, useContext, useEffect, useRef } from 'react' import { useState, useContext, useEffect, useRef } from 'react';
import { Keyboard } from 'react-native' import { Keyboard } from 'react-native';
import { AppContext } from '../context/AppContext' import { AppContext } from '../context/AppContext';
import { DisplayContext } from '../context/DisplayContext' import { DisplayContext } from '../context/DisplayContext';
import { Focus, FocusDetail, Topic, Profile, Card, AssetType, AssetSource, HostingMode, TransformType } from 'databag-client-sdk' import { Focus, FocusDetail, Topic, Profile, Card, AssetType, AssetSource, TransformType } from 'databag-client-sdk';
import { ContextType } from '../context/ContextType' import { ContextType } from '../context/ContextType';
import { placeholder } from '../constants/Icons';
import RNFS from 'react-native-fs'; import RNFS from 'react-native-fs';
import ImageResizer from '@bam.tech/react-native-image-resizer'; import ImageResizer from '@bam.tech/react-native-image-resizer';
import { createThumbnail } from "react-native-create-thumbnail"; import { createThumbnail } from 'react-native-create-thumbnail';
import fileType from 'react-native-file-type' import fileType from 'react-native-file-type';
const IMAGE_SCALE_SIZE = (128 * 1024); const IMAGE_SCALE_SIZE = (128 * 1024);
const GIF_TYPE = 'image/gif';
const WEBP_TYPE = 'image/webp';
const LOAD_DEBOUNCE = 1000; const LOAD_DEBOUNCE = 1000;
async function getImageThumb(path: string, type: string, size: number) { async function getImageThumb(path: string, type: string, size: number) {
if (size < IMAGE_SCALE_SIZE) { if (size < IMAGE_SCALE_SIZE) {
const type = await fileType(path); const info = await fileType(path);
const base = await RNFS.readFile(path, 'base64') const base = await RNFS.readFile(path, 'base64');
return `data:image/${type.ext};base64,${base}`; return `data:image/${info.ext};base64,${base}`;
} else { } else {
const thumb = await ImageResizer.createResizedImage(path, 192, 192, "JPEG", 50, 0, null); const thumb = await ImageResizer.createResizedImage(path, 192, 192, 'JPEG', 50, 0, null);
const base = await RNFS.readFile(thumb.path, 'base64') const base = await RNFS.readFile(thumb.path, 'base64');
return `data:image/jpeg;base64,${base}`; return `data:image/jpeg;base64,${base}`;
} }
} }
async function getVideoThumb(path: string, position?: number) { async function getVideoThumb(path: string, position?: number) {
const timeStamp = position ? position * 1000 : 0; const timeStamp = position ? position * 1000 : 0;
const shot = await createThumbnail({ url: path, timeStamp }) const shot = await createThumbnail({ url: path, timeStamp });
const thumb = await ImageResizer.createResizedImage('file://' + shot.path, 192, 192, "JPEG", 50, 0, null); const thumb = await ImageResizer.createResizedImage('file://' + shot.path, 192, 192, 'JPEG', 50, 0, null);
const base = await RNFS.readFile(thumb.path, 'base64') const base = await RNFS.readFile(thumb.path, 'base64');
return `data:image/jpeg;base64,${base}`; return `data:image/jpeg;base64,${base}`;
} }
export function useConversation() { export function useConversation() {
const mute = useRef(false); const mute = useRef(false);
const app = useContext(AppContext) as ContextType const app = useContext(AppContext) as ContextType;
const display = useContext(DisplayContext) as ContextType const display = useContext(DisplayContext) as ContextType;
const [state, setState] = useState({ const [state, setState] = useState({
detail: undefined as FocusDetail | null | undefined, detail: undefined as FocusDetail | null | undefined,
strings: display.state.strings, strings: display.state.strings,
@ -66,19 +63,19 @@ export function useConversation() {
progress: 0, progress: 0,
avoid: 0, avoid: 0,
validShare: true, validShare: true,
}) });
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const updateState = (value: any) => { const updateState = (value: any) => {
setState((s) => ({ ...s, ...value })) setState((s) => ({ ...s, ...value }));
} };
const updateAsset = (index: number, value: any) => { const updateAsset = (index: number, value: any) => {
setState((s) => { setState((s) => {
s.assets[index] = { ...s.assets[index], ...value }; s.assets[index] = { ...s.assets[index], ...value };
return { ...s }; return { ...s };
}); });
} };
useEffect(() => { useEffect(() => {
let validShare = true; let validShare = true;
@ -104,14 +101,14 @@ export function useConversation() {
const { sharing, focus } = app.state; const { sharing, focus } = app.state;
if (sharing && focus && state.loaded) { if (sharing && focus && state.loaded) {
const focused = focus.getFocused(); const focused = focus.getFocused();
if (focused.cardId == sharing.cardId && focused.channelId == sharing.channelId) { if (focused.cardId === sharing.cardId && focused.channelId === sharing.channelId) {
const { mimeType, filePath } = sharing; const { mimeType, filePath } = sharing;
const ext = mimeType.toLowerCase(); const ext = mimeType.toLowerCase();
if (ext == '.jpg' || ext == 'image/jpeg' || ext == '.png' || ext == 'image/png' || ext == '.webp' || ext == 'image/webp' || ext == '.bmp' || ext == 'image/bmp' || ext == '.gif' || ext == 'image/gif') { if (ext === '.jpg' || ext === 'image/jpeg' || ext === '.png' || ext === 'image/png' || ext === '.webp' || ext === 'image/webp' || ext === '.bmp' || ext === 'image/bmp' || ext === '.gif' || ext === 'image/gif') {
actions.addImage(filePath, mimeType, IMAGE_SCALE_SIZE); actions.addImage(filePath, mimeType, IMAGE_SCALE_SIZE);
} else if (ext == '.mp4' || ext == 'videp/mp4' || ext == '.mov' || ext == 'video/mov') { } else if (ext === '.mp4' || ext === 'videp/mp4' || ext === '.mov' || ext === 'video/mov') {
actions.addVideo(filePath, mimeType); actions.addVideo(filePath, mimeType);
} else if (ext == '.mp3' || ext == 'audio/mp3' || ext == '.aac' || ext == 'audio/aac') { } else if (ext === '.mp3' || ext === 'audio/mp3' || ext === '.aac' || ext === 'audio/aac') {
actions.addAudio(filePath, mimeType); actions.addAudio(filePath, mimeType);
} else { } else {
actions.addBinary(filePath, filePath.split('/').pop()); actions.addBinary(filePath, filePath.split('/').pop());
@ -119,12 +116,13 @@ export function useConversation() {
app.actions.clearSharing(); app.actions.clearSharing();
} }
} }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [app.state, state.loaded]); }, [app.state, state.loaded]);
useEffect(() => { useEffect(() => {
const { layout, strings } = display.state const { layout, strings } = display.state;
updateState({ layout, strings }) updateState({ layout, strings });
}, [display.state]) }, [display.state]);
useEffect(() => { useEffect(() => {
const host = state.cardId == null; const host = state.cardId == null;
@ -132,8 +130,8 @@ export function useConversation() {
const access = (state.detail != null); const access = (state.detail != null);
const subject = state.detail?.data?.subject ? state.detail.data.subject : null; const subject = state.detail?.data?.subject ? state.detail.data.subject : null;
const cards = Array.from(state.cards.values()); const cards = Array.from(state.cards.values());
const card = cards.find(entry => entry.cardId == state.cardId); const card = cards.find(entry => entry.cardId === state.cardId);
const profileRemoved = state.detail?.members ? state.detail.members.filter(member => state.profile?.guid != member.guid) : []; const profileRemoved = state.detail?.members ? state.detail.members.filter(member => state.profile?.guid !== member.guid) : [];
const unhostedCards = profileRemoved.map(member => state.cards.get(member.guid)); const unhostedCards = profileRemoved.map(member => state.cards.get(member.guid));
const contactCards = card ? [ card, ...unhostedCards ] : unhostedCards; const contactCards = card ? [ card, ...unhostedCards ] : unhostedCards;
const subjectCards = contactCards.filter(member => Boolean(member)); const subjectCards = contactCards.filter(member => Boolean(member));
@ -160,25 +158,25 @@ export function useConversation() {
}); });
updateState({ topics: sorted, loaded: true }); updateState({ topics: sorted, loaded: true });
} }
} };
const setCards = (cards: Card[]) => { const setCards = (cards: Card[]) => {
const contacts = new Map<string, Card>(); const contacts = new Map<string, Card>();
cards.forEach(card => { cards.forEach(card => {
contacts.set(card.guid, card); contacts.set(card.guid, card);
}); });
updateState({ cards: contacts }); updateState({ cards: contacts });
} };
const setProfile = (profile: Profile) => { const setProfile = (profile: Profile) => {
updateState({ profile }); updateState({ profile });
} };
const setDetail = (focused: { cardId: string | null, channelId: string, detail: FocusDetail | null }) => { const setDetail = (focused: { cardId: string | null, channelId: string, detail: FocusDetail | null }) => {
const detail = focused ? focused.detail : null; const detail = focused ? focused.detail : null;
const cardId = focused.cardId; const cardId = focused.cardId;
updateState({ detail, cardId }); updateState({ detail, cardId });
} };
const setKeyboard = (event: KeyboardEvent) => { const setKeyboard = (event: KeyboardEvent) => {
updateState({ avoid: event.endCoordinates.height }); updateState({ avoid: event.endCoordinates.height });
} };
updateState({ assets: [], message: null, topics: [], loaded: false }); updateState({ assets: [], message: null, topics: [], loaded: false });
focus.addTopicListener(setTopics); focus.addTopicListener(setTopics);
focus.addDetailListener(setDetail); focus.addDetailListener(setDetail);
@ -191,9 +189,9 @@ export function useConversation() {
contact.removeCardListener(setCards); contact.removeCardListener(setCards);
identity.removeProfileListener(setProfile); identity.removeProfileListener(setProfile);
keyboard.remove(); keyboard.remove();
} };
} }
}, [app.state.focus]); }, [app.state.session, app.state.focus]);
const actions = { const actions = {
close: () => { close: () => {
@ -246,55 +244,55 @@ export function useConversation() {
if (sealed) { if (sealed) {
sources.push({ type: AssetType.Image, source: asset.path, transforms: [ sources.push({ type: AssetType.Image, source: asset.path, transforms: [
{ type: TransformType.Thumb, appId: `it${sources.length}`, thumb: async () => await getImageThumb(asset.path, asset.type, asset.size) }, { type: TransformType.Thumb, appId: `it${sources.length}`, thumb: async () => await getImageThumb(asset.path, asset.type, asset.size) },
{ type: TransformType.Copy, appId: `ic${sources.length}` } { type: TransformType.Copy, appId: `ic${sources.length}` },
]}); ]});
return { encrypted: { type: 'image', thumb: `it${sources.length-1}`, parts: `ic${sources.length-1}` } }; return { encrypted: { type: 'image', thumb: `it${sources.length - 1}`, parts: `ic${sources.length - 1}` } };
} else { } else {
sources.push({ type: AssetType.Image, source: asset.path, transforms: [ sources.push({ type: AssetType.Image, source: asset.path, transforms: [
{ type: TransformType.Thumb, appId: `it${sources.length}` }, { type: TransformType.Thumb, appId: `it${sources.length}` },
{ type: TransformType.Copy, appId: `ic${sources.length}` } { type: TransformType.Copy, appId: `ic${sources.length}` },
]}); ]});
return { image: { thumb: `it${sources.length-1}`, full: `ic${sources.length-1}` } }; return { image: { thumb: `it${sources.length - 1}`, full: `ic${sources.length - 1}` } };
} }
} else if (asset.type === 'video') { } else if (asset.type === 'video') {
if (sealed) { if (sealed) {
sources.push({ type: AssetType.Video, source: asset.path, transforms: [ sources.push({ type: AssetType.Video, source: asset.path, transforms: [
{ type: TransformType.Thumb, appId: `vt${sources.length}`, thumb: async () => await getVideoThumb(asset.path, asset.position) }, { type: TransformType.Thumb, appId: `vt${sources.length}`, thumb: async () => await getVideoThumb(asset.path, asset.position) },
{ type: TransformType.Copy, appId: `vc${sources.length}` } { type: TransformType.Copy, appId: `vc${sources.length}` },
]}); ]});
return { encrypted: { type: 'video', thumb: `vt${sources.length-1}`, parts: `vc${sources.length-1}` } }; return { encrypted: { type: 'video', thumb: `vt${sources.length - 1}`, parts: `vc${sources.length - 1}` } };
} else { } else {
sources.push({ type: AssetType.Video, source: asset.path, transforms: [ sources.push({ type: AssetType.Video, source: asset.path, transforms: [
{ type: TransformType.Thumb, appId: `vt${sources.length}`, position: asset.position}, { type: TransformType.Thumb, appId: `vt${sources.length}`, position: asset.position},
{ type: TransformType.HighQuality, appId: `vh${sources.length}` }, { type: TransformType.HighQuality, appId: `vh${sources.length}` },
{ type: TransformType.LowQuality, appId: `vl${sources.length}` } { type: TransformType.LowQuality, appId: `vl${sources.length}` },
]}); ]});
return { video: { thumb: `vt${sources.length-1}`, hd: `vh${sources.length-1}`, lq: `vl${sources.length-1}` } }; return { video: { thumb: `vt${sources.length - 1}`, hd: `vh${sources.length - 1}`, lq: `vl${sources.length - 1}` } };
} }
} else if (asset.type === 'audio') { } else if (asset.type === 'audio') {
if (sealed) { if (sealed) {
sources.push({ type: AssetType.Audio, source: asset.path, transforms: [ sources.push({ type: AssetType.Audio, source: asset.path, transforms: [
{ type: TransformType.Copy, appId: `ac${sources.length}` } { type: TransformType.Copy, appId: `ac${sources.length}` },
]}); ]});
return { encrypted: { type: 'audio', label: asset.label, parts: `ac${sources.length-1}` } }; return { encrypted: { type: 'audio', label: asset.label, parts: `ac${sources.length - 1}` } };
} else { } else {
sources.push({ type: AssetType.Audio, source: asset.path, transforms: [ sources.push({ type: AssetType.Audio, source: asset.path, transforms: [
{ type: TransformType.Copy, appId: `ac${sources.length}` } { type: TransformType.Copy, appId: `ac${sources.length}` },
]}); ]});
return { audio: { label: asset.label, full: `ac${sources.length-1}` } }; return { audio: { label: asset.label, full: `ac${sources.length - 1}` } };
} }
} else { } else {
const { label, extension } = asset; const { label, extension } = asset;
if (sealed) { if (sealed) {
sources.push({ type: AssetType.Binary, source: asset.path, transforms: [ sources.push({ type: AssetType.Binary, source: asset.path, transforms: [
{ type: TransformType.Copy, appId: `bc${sources.length}` } { type: TransformType.Copy, appId: `bc${sources.length}` },
]}); ]});
return { encrypted: { type: 'binary', label, extension, parts: `bc${sources.length-1}` } }; return { encrypted: { type: 'binary', label, extension, parts: `bc${sources.length - 1}` } };
} else { } else {
sources.push({ type: AssetType.Binary, source: asset.path, transforms: [ sources.push({ type: AssetType.Binary, source: asset.path, transforms: [
{ type: TransformType.Copy, appId: `bc${sources.length}` } { type: TransformType.Copy, appId: `bc${sources.length}` },
]}); ]});
return { binary: { label, extension, data: `bc${sources.length-1}` } }; return { binary: { label, extension, data: `bc${sources.length - 1}` } };
} }
} }
}); });
@ -333,8 +331,8 @@ export function useConversation() {
} }
}); });
return { text: state.message, textColor: state.textColorSet ? state.textColor : null, textSize: state.textSizeSet ? state.textSize : null, assets: assets.length > 0 ? assets : null }; return { text: state.message, textColor: state.textColorSet ? state.textColor : null, textSize: state.textSizeSet ? state.textSize : null, assets: assets.length > 0 ? assets : null };
} };
const upload = (progress: number) => { updateState({ progress }) }; const upload = (progress: number) => { updateState({ progress }); };
await focus.addTopic(sealed, sealed ? 'sealedtopic' : 'superbasictopic', subject, sources, upload); await focus.addTopic(sealed, sealed ? 'sealedtopic' : 'superbasictopic', subject, sources, upload);
mute.current = true; mute.current = true;
@ -343,7 +341,7 @@ export function useConversation() {
}, 1000); }, 1000);
updateState({ message: null, assets: [], progress: 0 }); updateState({ message: null, assets: [], progress: 0 });
} }
}, },
addImage: (path: string, mime: string, size: number) => { addImage: (path: string, mime: string, size: number) => {
const type = 'image'; const type = 'image';
updateState({ assets: [ ...state.assets, { type, path, mime, size } ]}); updateState({ assets: [ ...state.assets, { type, path, mime, size } ]});
@ -360,7 +358,7 @@ export function useConversation() {
const type = 'binary'; const type = 'binary';
updateState({ assets: [ ...state.assets, { type, path, label: name.split('.').shift(), extension: name.split('.').pop() } ]}); updateState({ assets: [ ...state.assets, { type, path, label: name.split('.').shift(), extension: name.split('.').pop() } ]});
}, },
} };
return { state, actions } return { state, actions };
} }

View File

@ -1,5 +1,4 @@
import {StyleSheet} from 'react-native'; import {StyleSheet} from 'react-native';
import {Colors} from '../constants/Colors';
export const styles = StyleSheet.create({ export const styles = StyleSheet.create({
video: { video: {
@ -18,5 +17,5 @@ export const styles = StyleSheet.create({
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
justifyContent: 'flex-end', justifyContent: 'flex-end',
} },
}); });

View File

@ -1,9 +1,9 @@
import React, { useEffect, useRef, useState } from 'react'; import React, { useEffect, useRef, useState } from 'react';
import { Pressable, View, Animated, useAnimatedValue } from 'react-native' import { Pressable, View, Animated, useAnimatedValue } from 'react-native';
import { Icon, IconButton, Text } from 'react-native-paper'; import { Icon, IconButton } from 'react-native-paper';
import { useVideoFile } from './useVideoFile.hook'; import { useVideoFile } from './useVideoFile.hook';
import {styles} from './VideoFile.styled' import {styles} from './VideoFile.styled';
import Video, { VideoRef } from 'react-native-video' import Video, { VideoRef } from 'react-native-video';
export function VideoFile({ path, thumbPosition, disabled, remove }: {path: string, thumbPosition: (position: number)=>void, disabled: boolean, remove: ()=>void}) { export function VideoFile({ path, thumbPosition, disabled, remove }: {path: string, thumbPosition: (position: number)=>void, disabled: boolean, remove: ()=>void}) {
const { state, actions } = useVideoFile(); const { state, actions } = useVideoFile();
@ -17,7 +17,7 @@ export function VideoFile({ path, thumbPosition, disabled, remove }: {path: stri
thumbPosition(pos); thumbPosition(pos);
videoRef.current.seek(pos); videoRef.current.seek(pos);
setSeek(pos); setSeek(pos);
} };
useEffect(() => { useEffect(() => {
if (state.loaded) { if (state.loaded) {
@ -27,11 +27,12 @@ export function VideoFile({ path, thumbPosition, disabled, remove }: {path: stri
useNativeDriver: true, useNativeDriver: true,
}).start(); }).start();
} }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [state.loaded]); }, [state.loaded]);
return ( return (
<View style={styles.video}> <View style={styles.video}>
<Animated.View style={[{...styles.thumb, width: 72 * state.ratio},{opacity},]}> <Animated.View style={[{...styles.thumb, width: 72 * state.ratio},{opacity}]}>
<Video ref={videoRef} source={{ uri: path }} height={72} width={72 * state.ratio} paused={true} controls={false} resizeMode="contain" onLoad={actions.loaded} /> <Video ref={videoRef} source={{ uri: path }} height={72} width={72 * state.ratio} paused={true} controls={false} resizeMode="contain" onLoad={actions.loaded} />
{ !disabled && ( { !disabled && (
<Pressable style={styles.next} height={72} width={72 * state.ratio} onPress={next}> <Pressable style={styles.next} height={72} width={72 * state.ratio} onPress={next}>

View File

@ -1,23 +1,23 @@
import { useState, useEffect } from 'react' import { useState } from 'react';
export function useVideoFile(source: any) { export function useVideoFile() {
const [state, setState] = useState({ const [state, setState] = useState({
loaded: false, loaded: false,
ratio: 1, ratio: 1,
duration: 0, duration: 0,
}) });
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const updateState = (value: any) => { const updateState = (value: any) => {
setState((s) => ({ ...s, ...value })) setState((s) => ({ ...s, ...value }));
} };
const actions = { const actions = {
loaded: (e) => { loaded: (e) => {
const { width, height } = e.naturalSize; const { width, height } = e.naturalSize;
updateState({ loaded: true, ratio: width / height, duration: e.duration }); updateState({ loaded: true, ratio: width / height, duration: e.duration });
}, },
} };
return { state, actions } return { state, actions };
} }

View File

@ -107,6 +107,7 @@ export const styles = StyleSheet.create({
backgroundColor: 'yellow', backgroundColor: 'yellow',
}, },
members: { members: {
width: '100%',
flexGrow: 1, flexGrow: 1,
}, },
actions: { actions: {
@ -133,13 +134,10 @@ export const styles = StyleSheet.create({
top: 8, top: 8,
backgroundColor: 'transparent', backgroundColor: 'transparent',
padding: 0, padding: 0,
margin: 0, margin: 0,
}, },
membership: { membership: {
}, },
members: {
width: '100%',
},
card: { card: {
paddingBottom: 8, paddingBottom: 8,
paddingTop: 8, paddingTop: 8,

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react'; import React, { useState } from 'react';
import { SafeAreaView, Platform, Modal, ScrollView, View } from 'react-native'; import { SafeAreaView, Platform, Modal, ScrollView, View } from 'react-native';
import {useTheme, Switch, Surface, Icon, Divider, Button, IconButton, Text, TextInput} from 'react-native-paper'; import {useTheme, Switch, Surface, Icon, Divider, Button, IconButton, Text, TextInput} from 'react-native-paper';
import {styles} from './Details.styled'; import {styles} from './Details.styled';
@ -24,34 +24,34 @@ export function Details({close, closeAll}: {close: ()=>void, closeAll: ()=>void}
const membership = () => { const membership = () => {
setError(false); setError(false);
setMemberModal(true); setMemberModal(true);
} };
const remove = () => { const remove = () => {
const apply = async () => { const apply = async () => {
await actions.remove(); await actions.remove();
closeAll(); closeAll();
} };
confirmAction(state.strings.confirmTopic, state.strings.sureTopic, state.strings.remove, setRemoving, apply); confirmAction(state.strings.confirmTopic, state.strings.sureTopic, state.strings.remove, setRemoving, apply);
} };
const leave = () => { const leave = () => {
const apply = async () => { const apply = async () => {
await actions.leave(); await actions.leave();
closeAll(); closeAll();
} };
confirmAction(state.strings.confirmLeave, state.strings.sureLeave, state.strings.leave, setRemoving, apply); confirmAction(state.strings.confirmLeave, state.strings.sureLeave, state.strings.leave, setRemoving, apply);
} };
const block = () => { const block = () => {
const apply = async () => { const apply = async () => {
await actions.block(); await actions.block();
} };
confirmAction(state.strings.blockTopic, state.strings.blockTopicPrompt, state.strings.block, setBlocking, apply); confirmAction(state.strings.blockTopic, state.strings.blockTopicPrompt, state.strings.block, setBlocking, apply);
} };
const report = () => { const report = () => {
confirmAction(state.strings.reportTopic, state.strings.reportTopicPrompt, state.strings.report, setReporting, actions.report); confirmAction(state.strings.reportTopic, state.strings.reportTopicPrompt, state.strings.report, setReporting, actions.report);
} };
const confirmAction = (title: string, prompt: string, label: string, loading: (boolean) => void, action: () => Promise<void>) => { const confirmAction = (title: string, prompt: string, label: string, loading: (boolean) => void, action: () => Promise<void>) => {
setConfirmParams({ setConfirmParams({
@ -77,16 +77,6 @@ export function Details({close, closeAll}: {close: ()=>void, closeAll: ()=>void}
setConfirm(true); setConfirm(true);
}; };
const applyAction = async (loading: (boolean) => void, action: () => Promise<void>) => {
if (!busy) {
setBusy(true);
loading(true);
await setAction(action);
loading(false);
setBusy(false);
}
};
const setAction = async (action: () => Promise<void>) => { const setAction = async (action: () => Promise<void>) => {
try { try {
await action(); await action();
@ -126,13 +116,13 @@ export function Details({close, closeAll}: {close: ()=>void, closeAll: ()=>void}
setAlert(true); setAlert(true);
} }
setSaving(false); setSaving(false);
} }
} };
const cards = state.channelCards.map((card, index) => ( const cards = state.channelCards.map((card, index) => (
<Card containerStyle={{...styles.card, borderColor: theme.colors.outlineVariant }} key={index} imageUrl={card.imageUrl} name={card.name} placeholder={state.strings.name} <Card containerStyle={{...styles.card, borderColor: theme.colors.outlineVariant }} key={index} imageUrl={card.imageUrl} name={card.name} placeholder={state.strings.name}
handle={card.handle} node={card.node} actions={[]} /> handle={card.handle} node={card.node} actions={[]} />
)) ));
const members = state.cards.filter(card => { const members = state.cards.filter(card => {
if (state.detail && state.detail.members.find(member => member.guid === card.guid)) { if (state.detail && state.detail.members.find(member => member.guid === card.guid)) {
@ -161,12 +151,12 @@ export function Details({close, closeAll}: {close: ()=>void, closeAll: ()=>void}
setError(true); setError(true);
} }
}} }}
/> />,
]; ];
return ( return (
<Card containerStyle={{ ...styles.card, borderColor: theme.colors.outlineVariant }} key={index} imageUrl={card.imageUrl} name={card.name} placeholder={state.strings.name} handle={card.handle} node={card.node} actions={enable} /> <Card containerStyle={{ ...styles.card, borderColor: theme.colors.outlineVariant }} key={index} imageUrl={card.imageUrl} name={card.name} placeholder={state.strings.name} handle={card.handle} node={card.node} actions={enable} />
) );
}); });
return ( return (
@ -180,7 +170,7 @@ export function Details({close, closeAll}: {close: ()=>void, closeAll: ()=>void}
<Text style={styles.title}>{ state.strings.details }</Text> <Text style={styles.title}>{ state.strings.details }</Text>
{close && ( {close && (
<View style={styles.close} /> <View style={styles.close} />
)} )}
</SafeAreaView> </SafeAreaView>
<Divider style={styles.divider} /> <Divider style={styles.divider} />
{ !state.access && ( { !state.access && (
@ -201,8 +191,8 @@ export function Details({close, closeAll}: {close: ()=>void, closeAll: ()=>void}
autoComplete="off" autoComplete="off"
autoCorrect={false} autoCorrect={false}
value={state.editSubject} value={state.editSubject}
label={Platform.OS==='ios'?state.strings.subject:undefined} label={Platform.OS === 'ios' ? state.strings.subject : undefined}
placeholder={Platform.OS!=='ios'?state.strings.subject:undefined} placeholder={Platform.OS !== 'ios' ? state.strings.subject : undefined}
disabled={state.locked} disabled={state.locked}
left={<TextInput.Icon style={styles.icon} icon="label-outline" />} left={<TextInput.Icon style={styles.icon} icon="label-outline" />}
onChangeText={value => actions.setEditSubject(value)} onChangeText={value => actions.setEditSubject(value)}
@ -211,8 +201,8 @@ export function Details({close, closeAll}: {close: ()=>void, closeAll: ()=>void}
<IconButton style={styles.icon} icon="undo-variant" onPress={actions.undoSubject} /> <IconButton style={styles.icon} icon="undo-variant" onPress={actions.undoSubject} />
)} )}
{ state.subject !== state.editSubject && ( { state.subject !== state.editSubject && (
<IconButton style={styles.icon} icon="content-save-outline" loading={saving} onPress={saveSubject} /> <IconButton style={styles.icon} icon="content-save-outline" loading={saving} onPress={saveSubject} />
)} )}
</Surface> </Surface>
)} )}
{ !state.host && !state.locked && ( { !state.host && !state.locked && (
@ -335,6 +325,7 @@ export function Details({close, closeAll}: {close: ()=>void, closeAll: ()=>void}
<Text style={styles.unknown}>{ state.strings.unknown }: {state.unknownContacts}</Text> <Text style={styles.unknown}>{ state.strings.unknown }: {state.unknownContacts}</Text>
)} )}
</ScrollView> </ScrollView>
<Confirm show={alert} params={alertParams} />
<Confirm show={confirm} busy={busy} params={confirmParams} /> <Confirm show={confirm} busy={busy} params={confirmParams} />
<Modal animationType="fade" transparent={true} supportedOrientations={['portrait', 'landscape']} visible={memberModal} onRequestClose={() => setMemberModal(false)}> <Modal animationType="fade" transparent={true} supportedOrientations={['portrait', 'landscape']} visible={memberModal} onRequestClose={() => setMemberModal(false)}>
<View style={styles.memberModal}> <View style={styles.memberModal}>
@ -368,8 +359,5 @@ export function Details({close, closeAll}: {close: ()=>void, closeAll: ()=>void}
</View> </View>
</Modal> </Modal>
</View> </View>
) );
} }
// input if host and unsealed
// text otherwise

View File

@ -1,12 +1,12 @@
import { useState, useContext, useEffect } from 'react' import { useState, useContext, useEffect } from 'react';
import { AppContext } from '../context/AppContext' import { AppContext } from '../context/AppContext';
import { DisplayContext } from '../context/DisplayContext' import { DisplayContext } from '../context/DisplayContext';
import { ContextType } from '../context/ContextType' import { ContextType } from '../context/ContextType';
import { FocusDetail, Card, Profile } from 'databag-client-sdk'; import { FocusDetail, Card, Profile } from 'databag-client-sdk';
export function useDetails() { export function useDetails() {
const display = useContext(DisplayContext) as ContextType const display = useContext(DisplayContext) as ContextType;
const app = useContext(AppContext) as ContextType const app = useContext(AppContext) as ContextType;
const [state, setState] = useState({ const [state, setState] = useState({
cardId: null as null | string, cardId: null as null | string,
channelId: '', channelId: '',
@ -26,51 +26,51 @@ export function useDetails() {
hostCard: null as null | Card, hostCard: null as null | Card,
channelCards: [] as Card[], channelCards: [] as Card[],
unknownContacts: 0, unknownContacts: 0,
}) });
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const updateState = (value: any) => { const updateState = (value: any) => {
setState((s) => ({ ...s, ...value })) setState((s) => ({ ...s, ...value }));
} };
const getTimestamp = (created: number) => { const getTimestamp = (created: number) => {
const now = Math.floor((new Date()).getTime() / 1000) const now = Math.floor((new Date()).getTime() / 1000);
const date = new Date(created * 1000); const date = new Date(created * 1000);
const offset = now - created; const offset = now - created;
if(offset < 43200) { if(offset < 43200) {
if (state.timeFormat === '12h') { if (state.timeFormat === '12h') {
return date.toLocaleTimeString("en-US", {hour: 'numeric', minute:'2-digit'}); return date.toLocaleTimeString('en-US', {hour: 'numeric', minute:'2-digit'});
} }
else { else {
return date.toLocaleTimeString("en-GB", {hour: 'numeric', minute:'2-digit'}); return date.toLocaleTimeString('en-GB', {hour: 'numeric', minute:'2-digit'});
} }
} }
else if (offset < 31449600) { else if (offset < 31449600) {
if (state.dateFormat === 'mm/dd') { if (state.dateFormat === 'mm/dd') {
return date.toLocaleDateString("en-US", {day: 'numeric', month:'numeric'}); return date.toLocaleDateString('en-US', {day: 'numeric', month:'numeric'});
} }
else { else {
return date.toLocaleDateString("en-GB", {day: 'numeric', month:'numeric'}); return date.toLocaleDateString('en-GB', {day: 'numeric', month:'numeric'});
} }
} }
else { else {
if (state.dateFormat === 'mm/dd') { if (state.dateFormat === 'mm/dd') {
return date.toLocaleDateString("en-US"); return date.toLocaleDateString('en-US');
} }
else { else {
return date.toLocaleDateString("en-GB"); return date.toLocaleDateString('en-GB');
} }
} }
} };
useEffect(() => { useEffect(() => {
const { strings, timeFormat, dateFormat } = display.state; const { strings, timeFormat, dateFormat } = display.state;
updateState({ strings, timeFormat, dateFormat }); updateState({ strings, timeFormat, dateFormat });
}, [display.state]); }, [display.state]);
useEffect(() => { useEffect(() => {
const hostCard = state.cards.find(entry => entry.cardId == state.cardId); 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 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 contactCards = profileRemoved.map(member => state.cards.find(card => card.guid === member.guid));
const channelCards = contactCards.filter(member => Boolean(member)); const channelCards = contactCards.filter(member => Boolean(member));
const unknownContacts = contactCards.length - channelCards.length; const unknownContacts = contactCards.length - channelCards.length;
@ -93,10 +93,10 @@ export function useDetails() {
} }
}); });
updateState({ cards: sorted }); updateState({ cards: sorted });
} };
const setProfile = (profile: Profile) => { const setProfile = (profile: Profile) => {
updateState({ profile }); updateState({ profile });
} };
const setDetail = (focused: { cardId: string | null, channelId: string, detail: FocusDetail | null }) => { const setDetail = (focused: { cardId: string | null, channelId: string, detail: FocusDetail | null }) => {
const detail = focused ? focused.detail : null; const detail = focused ? focused.detail : null;
const cardId = focused.cardId; const cardId = focused.cardId;
@ -108,7 +108,7 @@ export function useDetails() {
const subject = detail?.data?.subject ? detail.data.subject : ''; const subject = detail?.data?.subject ? detail.data.subject : '';
const created = detail?.created ? getTimestamp(detail.created) : ''; const created = detail?.created ? getTimestamp(detail.created) : '';
updateState({ detail, editSubject: subject, subject, channelId, cardId, access, sealed, locked, host, created }); updateState({ detail, editSubject: subject, subject, channelId, cardId, access, sealed, locked, host, created });
} };
focus.addDetailListener(setDetail); focus.addDetailListener(setDetail);
contact.addCardListener(setCards); contact.addCardListener(setCards);
identity.addProfileListener(setProfile); identity.addProfileListener(setProfile);
@ -116,18 +116,19 @@ export function useDetails() {
focus.removeDetailListener(setDetail); focus.removeDetailListener(setDetail);
contact.removeCardListener(setCards); contact.removeCardListener(setCards);
identity.removeProfileListener(setProfile); identity.removeProfileListener(setProfile);
} };
} }
}, [app.state.focus, state.timeFormat, state.dateFormat]); // eslint-disable-next-line react-hooks/exhaustive-deps
}, [app.state.session, app.state.focus, state.timeFormat, state.dateFormat]);
const actions = { const actions = {
remove: async () => { remove: async () => {
const content = app.state.session.getContent() const content = app.state.session.getContent();
await content.removeChannel(state.channelId); await content.removeChannel(state.channelId);
app.actions.clearFocus(); app.actions.clearFocus();
}, },
leave: async () => { leave: async () => {
const content = app.state.session.getContent() const content = app.state.session.getContent();
await content.leaveChannel(state.cardId, state.channelId); await content.leaveChannel(state.cardId, state.channelId);
app.actions.clearFocus(); app.actions.clearFocus();
}, },
@ -155,10 +156,10 @@ export function useDetails() {
updateState({ editSubject: state.subject }); updateState({ editSubject: state.subject });
}, },
saveSubject: async () => { saveSubject: async () => {
const content = app.state.session.getContent() const content = app.state.session.getContent();
await content.setChannelSubject(state.channelId, state.sealed ? 'sealed' : 'superbasic', { subject: state.editSubject }); await content.setChannelSubject(state.channelId, state.sealed ? 'sealed' : 'superbasic', { subject: state.editSubject });
}, },
} };
return { state, actions } return { state, actions };
} }

View File

@ -1,5 +1,5 @@
import { Platform, Share } from 'react-native'; import { Platform, Share } from 'react-native';
import fileType from 'react-native-file-type' import fileType from 'react-native-file-type';
import RNFS from 'react-native-fs'; import RNFS from 'react-native-fs';
import RNFetchBlob from 'rn-fetch-blob'; import RNFetchBlob from 'rn-fetch-blob';
@ -7,7 +7,7 @@ export async function Download(uri: string, name: string, extension?: string) {
if (Platform.OS === 'ios') { if (Platform.OS === 'ios') {
const options = { fileCache: true, filename: name }; const options = { fileCache: true, filename: name };
const download = await RNFetchBlob.config(options).fetch("GET", uri); const download = await RNFetchBlob.config(options).fetch('GET', uri);
const downloadPath = download.path(); const downloadPath = download.path();
const type = extension ? extension : (await fileType(downloadPath))?.ext; const type = extension ? extension : (await fileType(downloadPath))?.ext;
@ -31,7 +31,7 @@ export async function Download(uri: string, name: string, extension?: string) {
await RNFS.scanFile(sharePath); await RNFS.scanFile(sharePath);
} else { } else {
const options = { fileCache: true, filename: name }; const options = { fileCache: true, filename: name };
const download = await RNFetchBlob.config(options).fetch("GET", uri); const download = await RNFetchBlob.config(options).fetch('GET', uri);
const downloadPath = download.path(); const downloadPath = download.path();
const type = extension ? extension : (await fileType(downloadPath))?.ext; const type = extension ? extension : (await fileType(downloadPath))?.ext;
@ -42,5 +42,5 @@ export async function Download(uri: string, name: string, extension?: string) {
await RNFS.moveFile(downloadPath, sharePath); await RNFS.moveFile(downloadPath, sharePath);
await RNFS.scanFile(sharePath); await RNFS.scanFile(sharePath);
} }
} }
} }

View File

@ -6,6 +6,8 @@ export const styles = StyleSheet.create({
paddingTop: 8, paddingTop: 8,
width: '100%', width: '100%',
minWidth: 0, minWidth: 0,
fontSize: 14,
padding: 0,
}, },
topic: { topic: {
paddingTop: 8, paddingTop: 8,
@ -132,11 +134,6 @@ export const styles = StyleSheet.create({
height: 64, height: 64,
backgroundColor: 'yellow', backgroundColor: 'yellow',
}, },
message: {
width: '100%',
fontSize: 14,
padding: 0,
},
modal: { modal: {
width: '100%', width: '100%',
height: '100%', height: '100%',
@ -183,4 +180,4 @@ export const styles = StyleSheet.create({
paddingTop: 16, paddingTop: 16,
gap: 8, gap: 8,
}, },
}) });

View File

@ -1,7 +1,7 @@
import { useRef, useEffect, useState, useCallback } from 'react'; import React, { useRef, useEffect, useState } from 'react';
import { avatar } from '../constants/Icons' import { avatar } from '../constants/Icons';
import { Pressable, Linking, ScrollView, View, Image, Modal } from 'react-native'; import { Pressable, Linking, ScrollView, View, Image, Modal } from 'react-native';
import {Icon, Text, TextInput, IconButton, Button, Surface, Divider} from 'react-native-paper'; import { Text, TextInput, IconButton, Button, Surface, Divider} from 'react-native-paper';
import { Topic, Card, Profile } from 'databag-client-sdk'; import { Topic, Card, Profile } from 'databag-client-sdk';
import { ImageAsset } from './imageAsset/ImageAsset'; import { ImageAsset } from './imageAsset/ImageAsset';
import { AudioAsset } from './audioAsset/AudioAsset'; import { AudioAsset } from './audioAsset/AudioAsset';
@ -10,7 +10,7 @@ import { BinaryAsset } from './binaryAsset/BinaryAsset';
import { useMessage } from './useMessage.hook'; import { useMessage } from './useMessage.hook';
import {styles} from './Message.styled'; import {styles} from './Message.styled';
import { MediaAsset } from '../conversation/Conversatin'; import { MediaAsset } from '../conversation/Conversatin';
import { Confirm } from '../confirm/Confirm'; import { Confirm } from '../confirm/Confirm';
import { Shimmer } from './shimmer/Shimmer'; import { Shimmer } from './shimmer/Shimmer';
import {BlurView} from '@react-native-community/blur'; import {BlurView} from '@react-native-community/blur';
import { sanitizeUrl } from '@braintree/sanitize-url'; import { sanitizeUrl } from '@braintree/sanitize-url';
@ -18,9 +18,9 @@ import { sanitizeUrl } from '@braintree/sanitize-url';
export function Message({ topic, card, profile, host, select, selected }: { topic: Topic, card: Card | null, profile: Profile | null, host: boolean, select: (id: null | string)=>void, selected: string }) { export function Message({ topic, card, profile, host, select, selected }: { topic: Topic, card: Card | null, profile: Profile | null, host: boolean, select: (id: null | string)=>void, selected: string }) {
const { state, actions } = useMessage(); const { state, actions } = useMessage();
const { locked, data, created, topicId, status, transform } = topic; const { locked, data, created, topicId, status, transform } = topic;
const { name, handle, node } = profile || card || { name: null, handle: null, node: null } const { name, handle, node } = profile || card || { name: null, handle: null, node: null };
const { text, textColor, textSize, assets } = data || { text: null, textColor: null, textSize: null } const { text, textColor, textSize, assets } = data || { text: null, textColor: null, textSize: null };
const textStyle = textColor && textSize ? { fontSize: textSize, color: textColor } : textColor ? { color: textColor } : textSize ? { fontSize: textSize } : {} const textStyle = textColor && textSize ? { fontSize: textSize, color: textColor } : textColor ? { color: textColor } : textSize ? { fontSize: textSize } : {};
const logoUrl = profile ? profile.imageUrl : card ? card.imageUrl : avatar; const logoUrl = profile ? profile.imageUrl : card ? card.imageUrl : avatar;
const timestamp = actions.getTimestamp(created); const timestamp = actions.getTimestamp(created);
const [editing, setEditing] = useState(false); const [editing, setEditing] = useState(false);
@ -34,6 +34,7 @@ export function Message({ topic, card, profile, host, select, selected }: { topi
const loadedCount = useRef(0); const loadedCount = useRef(0);
const [showAsset, setShowAsset] = useState(false); const [showAsset, setShowAsset] = useState(false);
const [message, setMessage] = useState([]); const [message, setMessage] = useState([]);
const fontStyle = { fontStyle: 'italic' };
useEffect(() => { useEffect(() => {
setTimeout(() => setShowAsset(true), 2000); setTimeout(() => setShowAsset(true), 2000);
@ -54,22 +55,23 @@ export function Message({ topic, card, profile, host, select, selected }: { topi
if (parsed?.length > 0) { if (parsed?.length > 0) {
const words = parsed as string[]; const words = parsed as string[];
words.forEach((word, index) => { words.forEach((word, index) => {
if (!!urlPattern.test(word)) { if (urlPattern.test(word)) {
clickable.push(<Text key={index} style={textStyle}>{ plain }</Text>); clickable.push(<Text key={index} style={textStyle}>{ plain }</Text>);
plain = ''; plain = '';
const url = !!hostPattern.test(word) ? word : `https://${word}`; const url = hostPattern.test(word) ? word : `https://${word}`;
clickable.push(<Text key={'link-' + index} onPress={() => Linking.openURL(sanitizeUrl(url))} style={{ fontStyle: 'italic' }}>{ sanitizeUrl(word) + ' ' }</Text>); clickable.push(<Text key={'link-' + index} onPress={() => Linking.openURL(sanitizeUrl(url))} style={fontStyle}>{ sanitizeUrl(word) + ' ' }</Text>);
} }
else { else {
plain += `${word} `; plain += `${word} `;
} }
}) });
} }
if (plain) { if (plain) {
clickable.push(<Text key={parsed.length} style={textStyle}>{ plain }</Text>); clickable.push(<Text key={parsed.length} style={textStyle}>{ plain }</Text>);
} }
setMessage(clickable) setMessage(clickable);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [text, locked]); }, [text, locked]);
const loaded = () => { const loaded = () => {
@ -77,12 +79,12 @@ export function Message({ topic, card, profile, host, select, selected }: { topi
if (loadedCount.current >= assets.length) { if (loadedCount.current >= assets.length) {
setShowAsset(true); setShowAsset(true);
} }
} };
const edit = () => { const edit = () => {
setEditing(true); setEditing(true);
select(null); select(null);
} };
const save = async () => { const save = async () => {
setSaving(true); setSaving(true);
@ -94,7 +96,7 @@ export function Message({ topic, card, profile, host, select, selected }: { topi
} }
setSaving(false); setSaving(false);
setEditing(false); setEditing(false);
} };
const block = () => { const block = () => {
setConfirmParams({ setConfirmParams({
@ -118,7 +120,7 @@ export function Message({ topic, card, profile, host, select, selected }: { topi
} }
setBlocking(false); setBlocking(false);
} }
} },
}, },
}); });
setConfirmShow(true); setConfirmShow(true);
@ -146,7 +148,7 @@ export function Message({ topic, card, profile, host, select, selected }: { topi
} }
setReporting(false); setReporting(false);
} }
} },
}, },
}); });
setConfirmShow(true); setConfirmShow(true);
@ -174,7 +176,7 @@ export function Message({ topic, card, profile, host, select, selected }: { topi
} }
setRemoving(false); setRemoving(false);
} }
} },
}, },
}); });
setConfirmShow(true); setConfirmShow(true);
@ -190,19 +192,19 @@ export function Message({ topic, card, profile, host, select, selected }: { topi
}, },
}); });
setConfirmShow(true); setConfirmShow(true);
} };
const media = !assets ? [] : assets.map((asset: MediaAsset, index: number) => { const media = !assets ? [] : assets.map((asset: MediaAsset, index: number) => {
if (asset.image || asset.encrypted?.type === 'image') { if (asset.image || asset.encrypted?.type === 'image') {
return <ImageAsset key={index} topicId={topicId} asset={asset as MediaAsset} loaded={loaded} show={showAsset} /> return <ImageAsset key={index} topicId={topicId} asset={asset as MediaAsset} loaded={loaded} show={showAsset} />;
} else if (asset.audio || asset.encrypted?.type === 'audio') { } else if (asset.audio || asset.encrypted?.type === 'audio') {
return <AudioAsset key={index} topicId={topicId} asset={asset as MediaAsset} loaded={loaded} show={showAsset} /> return <AudioAsset key={index} topicId={topicId} asset={asset as MediaAsset} loaded={loaded} show={showAsset} />;
} else if (asset.video || asset.encrypted?.type === 'video') { } else if (asset.video || asset.encrypted?.type === 'video') {
return <VideoAsset key={index} topicId={topicId} asset={asset as MediaAsset} loaded={loaded} show={showAsset} /> return <VideoAsset key={index} topicId={topicId} asset={asset as MediaAsset} loaded={loaded} show={showAsset} />;
} else if (asset.binary || asset.encrypted?.type === 'binary') { } else if (asset.binary || asset.encrypted?.type === 'binary') {
return <BinaryAsset key={index} topicId={topicId} asset={asset as MediaAsset} loaded={loaded} show={showAsset} /> return <BinaryAsset key={index} topicId={topicId} asset={asset as MediaAsset} loaded={loaded} show={showAsset} />;
} else { } else {
return <View key={index}></View> return <View key={index} />;
} }
}); });
@ -212,7 +214,7 @@ export function Message({ topic, card, profile, host, select, selected }: { topi
<View style={styles.content}> <View style={styles.content}>
<Image style={styles.logo} resizeMode={'contain'} source={{uri: logoUrl}} /> <Image style={styles.logo} resizeMode={'contain'} source={{uri: logoUrl}} />
<View style={styles.body}> <View style={styles.body}>
<Pressable style={styles.header} onPress={()=>select(topicId == selected ? null : topicId)}> <Pressable style={styles.header} onPress={()=>select(topicId === selected ? null : topicId)}>
<View style={styles.name}> <View style={styles.name}>
{ name && ( { name && (
<Text style={styles.handle}>{ name }</Text> <Text style={styles.handle}>{ name }</Text>
@ -243,7 +245,7 @@ export function Message({ topic, card, profile, host, select, selected }: { topi
</View> </View>
</View> </View>
</View> </View>
{ !locked && assets?.length > 0 && transform === 'complete' && ( { !locked && assets?.length > 0 && transform === 'complete' && (
<ScrollView horizontal={true} showsHorizontalScrollIndicator={false} style={styles.carousel} contentContainerStyle={styles.assets}> <ScrollView horizontal={true} showsHorizontalScrollIndicator={false} style={styles.carousel} contentContainerStyle={styles.assets}>
{ media } { media }
</ScrollView> </ScrollView>
@ -280,11 +282,11 @@ export function Message({ topic, card, profile, host, select, selected }: { topi
<BlurView style={styles.blur} blurType="dark" blurAmount={6} reducedTransparencyFallbackColor="dark" /> <BlurView style={styles.blur} blurType="dark" blurAmount={6} reducedTransparencyFallbackColor="dark" />
</Pressable> </Pressable>
<View style={styles.editArea}> <View style={styles.editArea}>
<Surface elevation={2} style={styles.editContent}> <Surface elevation={2} style={styles.editContent}>
<Text style={styles.title}>{ state.strings.edit }</Text> <Text style={styles.title}>{ state.strings.edit }</Text>
<TextInput multiline={true} mode="outlined" style={styles.message} <TextInput multiline={true} mode="outlined" style={styles.message}
outlineColor="transparent" activeOutlineColor="transparent" spellcheck={false} outlineColor="transparent" activeOutlineColor="transparent" spellcheck={false}
autoComplete="off" autoCapitalize="none" autoCorrect={false} placeholder={state.strings.newMessage} autoComplete="off" autoCapitalize="none" autoCorrect={false} placeholder={state.strings.newMessage}
value={editText} onChangeText={value => setEditText(value)} /> value={editText} onChangeText={value => setEditText(value)} />
<View style={styles.controls}> <View style={styles.controls}>
<Button mode="outlined" onPress={()=>setEditing(false)}> <Button mode="outlined" onPress={()=>setEditing(false)}>

View File

@ -1,14 +1,13 @@
import React, { useState, useEffect, useRef } from 'react'; import React, { useState, useEffect, useRef } from 'react';
import { SafeAreaView, Modal, Share, Pressable, View, Image, Animated, useAnimatedValue } from 'react-native' import { SafeAreaView, Modal, Pressable, View, Image, Animated, useAnimatedValue } from 'react-native';
import { Surface, Icon, Text, ProgressBar, IconButton } from 'react-native-paper' import { Surface, Icon, Text, ProgressBar, IconButton } from 'react-native-paper';
import { useAudioAsset } from './useAudioAsset.hook'; import { useAudioAsset } from './useAudioAsset.hook';
import { MediaAsset } from '../../conversation/Conversation'; import { MediaAsset } from '../../conversation/Conversation';
import { styles } from './AudioAsset.styled' import { styles } from './AudioAsset.styled';
import {BlurView} from '@react-native-community/blur'; import {BlurView} from '@react-native-community/blur';
import Video, { VideoRef } from 'react-native-video' import Video, { VideoRef } from 'react-native-video';
import thumb from '../../images/audio.png'; import thumb from '../../images/audio.png';
import {Colors} from '../../constants/Colors'; import { activateKeepAwake, deactivateKeepAwake} from '@sayem314/react-native-keep-awake';
import { activateKeepAwake, deactivateKeepAwake} from "@sayem314/react-native-keep-awake";
export function AudioAsset({ topicId, asset, loaded, show }: { topicId: string, asset: MediaAsset, loaded: ()=>void, show: boolean }) { export function AudioAsset({ topicId, asset, loaded, show }: { topicId: string, asset: MediaAsset, loaded: ()=>void, show: boolean }) {
const { state, actions } = useAudioAsset(topicId, asset); const { state, actions } = useAudioAsset(topicId, asset);
@ -19,38 +18,39 @@ export function AudioAsset({ topicId, asset, loaded, show }: { topicId: string,
const [downloading, setDownloading] = useState(false); const [downloading, setDownloading] = useState(false);
useEffect(() => { useEffect(() => {
if (show) { if (show) {
Animated.timing(opacity, { Animated.timing(opacity, {
toValue: 1, toValue: 1,
duration: 100, duration: 100,
useNativeDriver: true, useNativeDriver: true,
}).start(); }).start();
} }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [show]); }, [show]);
const showAudio = () => { const showAudio = () => {
setModal(true); setModal(true);
actions.loadAudio(); actions.loadAudio();
activateKeepAwake() activateKeepAwake();
}; };
const hideAudio = () => { const hideAudio = () => {
setModal(false); setModal(false);
actions.cancelLoad(); actions.cancelLoad();
deactivateKeepAwake(); deactivateKeepAwake();
} };
const play = () => { const play = () => {
videoRef.current.resume(); videoRef.current.resume();
} };
const pause = () => { const pause = () => {
videoRef.current.pause(); videoRef.current.pause();
} };
const end = () => { const end = () => {
videoRef.current.seek(0); videoRef.current.seek(0);
} };
const playbackRateChange = (e) => { const playbackRateChange = (e) => {
if (e.playbackRate === 0) { if (e.playbackRate === 0) {
@ -58,7 +58,7 @@ export function AudioAsset({ topicId, asset, loaded, show }: { topicId: string,
} else { } else {
setStatus('playing'); setStatus('playing');
} }
} };
const download = async () => { const download = async () => {
if (!downloading) { if (!downloading) {
@ -70,12 +70,12 @@ export function AudioAsset({ topicId, asset, loaded, show }: { topicId: string,
} }
setDownloading(false); setDownloading(false);
} }
} };
return ( return (
<View style={styles.audio}> <View style={styles.audio}>
<Pressable onPress={showAudio}> <Pressable onPress={showAudio}>
<Animated.View style={[styles.container,{opacity},]}> <Animated.View style={[styles.container,{opacity}]}>
<Image <Image
style={styles.thumb} style={styles.thumb}
resizeMode="contain" resizeMode="contain"

View File

@ -1,14 +1,13 @@
import { useState, useContext, useEffect, useRef } from 'react' import { useState, useContext, useRef } from 'react';
import { AppContext } from '../../context/AppContext' import { AppContext } from '../../context/AppContext';
import { DisplayContext } from '../../context/DisplayContext' import { DisplayContext } from '../../context/DisplayContext';
import { Focus } from 'databag-client-sdk' import { ContextType } from '../../context/ContextType';
import { ContextType } from '../../context/ContextType'
import { MediaAsset } from '../../conversation/Conversation'; import { MediaAsset } from '../../conversation/Conversation';
import { Download } from '../../download'; import { Download } from '../../download';
export function useAudioAsset(topicId: string, asset: MediaAsset) { export function useAudioAsset(topicId: string, asset: MediaAsset) {
const app = useContext(AppContext) as ContextType const app = useContext(AppContext) as ContextType;
const display = useContext(DisplayContext) as ContextType const display = useContext(DisplayContext) as ContextType;
const [state, setState] = useState({ const [state, setState] = useState({
strings: display.state.strings, strings: display.state.strings,
dataUrl: null, dataUrl: null,
@ -16,13 +15,13 @@ export function useAudioAsset(topicId: string, asset: MediaAsset) {
loaded: false, loaded: false,
loadPercent: 0, loadPercent: 0,
failed: false, failed: false,
}) });
const cancelled = useRef(false); const cancelled = useRef(false);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const updateState = (value: any) => { const updateState = (value: any) => {
setState((s) => ({ ...s, ...value })) setState((s) => ({ ...s, ...value }));
} };
const actions = { const actions = {
cancelLoad: () => { cancelLoad: () => {
@ -44,10 +43,10 @@ export function useAudioAsset(topicId: string, asset: MediaAsset) {
const { focus } = app.state; const { focus } = app.state;
const assetId = asset.audio ? asset.audio.full : asset.encrypted ? asset.encrypted.parts : null; const assetId = asset.audio ? asset.audio.full : asset.encrypted ? asset.encrypted.parts : null;
if (focus && assetId != null && !state.loading && !state.dataUrl) { if (focus && assetId != null && !state.loading && !state.dataUrl) {
cancelled.current = false; cancelled.current = false;
updateState({ loading: true, loadPercent: 0 }); updateState({ loading: true, loadPercent: 0 });
try { try {
const dataUrl = await focus.getTopicAssetUrl(topicId, assetId, (loadPercent: number)=>{ updateState({ loadPercent }); return !cancelled.current }); const dataUrl = await focus.getTopicAssetUrl(topicId, assetId, (loadPercent: number)=>{ updateState({ loadPercent }); return !cancelled.current; });
updateState({ dataUrl }); updateState({ dataUrl });
} catch (err) { } catch (err) {
console.log(err); console.log(err);
@ -56,7 +55,7 @@ export function useAudioAsset(topicId: string, asset: MediaAsset) {
updateState({ loading: false }); updateState({ loading: false });
} }
}, },
} };
return { state, actions } return { state, actions };
} }

View File

@ -1,11 +1,10 @@
import React, { useState, useEffect, useRef } from 'react'; import React, { useState, useEffect } from 'react';
import { SafeAreaView, Modal, Share, Pressable, View, Image, Animated, useAnimatedValue } from 'react-native' import { SafeAreaView, Modal, Pressable, View, Image, Animated, useAnimatedValue } from 'react-native';
import { Text, Surface, Icon, ProgressBar, IconButton } from 'react-native-paper' import { Text, Surface, Icon, ProgressBar, IconButton } from 'react-native-paper';
import { useBinaryAsset } from './useBinaryAsset.hook'; import { useBinaryAsset } from './useBinaryAsset.hook';
import { MediaAsset } from '../../conversation/Conversation'; import { MediaAsset } from '../../conversation/Conversation';
import { styles } from './BinaryAsset.styled' import { styles } from './BinaryAsset.styled';
import {BlurView} from '@react-native-community/blur'; import {BlurView} from '@react-native-community/blur';
import Video from 'react-native-video'
import thumb from '../../images/binary.png'; import thumb from '../../images/binary.png';
export function BinaryAsset({ topicId, asset, loaded, show }: { topicId: string, asset: MediaAsset, loaded: ()=>void, show: boolean }) { export function BinaryAsset({ topicId, asset, loaded, show }: { topicId: string, asset: MediaAsset, loaded: ()=>void, show: boolean }) {
@ -23,18 +22,9 @@ export function BinaryAsset({ topicId, asset, loaded, show }: { topicId: string,
useNativeDriver: true, useNativeDriver: true,
}).start(); }).start();
} }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [show]); }, [show]);
const share = async () => {
try {
setAlert('');
await actions.download();
} catch (err) {
console.log(err);
setAlert(state.strings.operationFailed)
}
}
const showBinary = () => { const showBinary = () => {
setAlert(''); setAlert('');
setModal(true); setModal(true);
@ -44,7 +34,7 @@ export function BinaryAsset({ topicId, asset, loaded, show }: { topicId: string,
const hideBinary = () => { const hideBinary = () => {
setModal(false); setModal(false);
actions.cancelLoad(); actions.cancelLoad();
} };
const download = async () => { const download = async () => {
if (!downloading) { if (!downloading) {
@ -56,12 +46,12 @@ export function BinaryAsset({ topicId, asset, loaded, show }: { topicId: string,
} }
setDownloading(false); setDownloading(false);
} }
} };
return ( return (
<View style={styles.binary}> <View style={styles.binary}>
<Pressable onPress={showBinary}> <Pressable onPress={showBinary}>
<Animated.View style={[styles.container,{opacity},]}> <Animated.View style={[styles.container,{opacity}]}>
<Image <Image
style={styles.thumb} style={styles.thumb}
resizeMode="contain" resizeMode="contain"

View File

@ -1,14 +1,13 @@
import { useState, useContext, useEffect, useRef } from 'react' import { useState, useContext, useRef } from 'react';
import { AppContext } from '../../context/AppContext' import { AppContext } from '../../context/AppContext';
import { DisplayContext } from '../../context/DisplayContext'; import { DisplayContext } from '../../context/DisplayContext';
import { Focus } from 'databag-client-sdk' import { ContextType } from '../../context/ContextType';
import { ContextType } from '../../context/ContextType'
import { MediaAsset } from '../../conversation/Conversation'; import { MediaAsset } from '../../conversation/Conversation';
import { Download } from '../../download'; import { Download } from '../../download';
export function useBinaryAsset(topicId: string, asset: MediaAsset) { export function useBinaryAsset(topicId: string, asset: MediaAsset) {
const app = useContext(AppContext) as ContextType const app = useContext(AppContext) as ContextType;
const display = useContext(DisplayContext) as ContextType const display = useContext(DisplayContext) as ContextType;
const [state, setState] = useState({ const [state, setState] = useState({
strings: display.state.strings, strings: display.state.strings,
dataUrl: null, dataUrl: null,
@ -16,13 +15,13 @@ export function useBinaryAsset(topicId: string, asset: MediaAsset) {
loaded: false, loaded: false,
loadPercent: 0, loadPercent: 0,
failed: false, failed: false,
}) });
const cancelled = useRef(false); const cancelled = useRef(false);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const updateState = (value: any) => { const updateState = (value: any) => {
setState((s) => ({ ...s, ...value })) setState((s) => ({ ...s, ...value }));
} };
const actions = { const actions = {
cancelLoad: () => { cancelLoad: () => {
@ -43,10 +42,10 @@ export function useBinaryAsset(topicId: string, asset: MediaAsset) {
const { focus } = app.state; const { focus } = app.state;
const assetId = asset.binary ? asset.binary.data : asset.encrypted ? asset.encrypted.parts : null; const assetId = asset.binary ? asset.binary.data : asset.encrypted ? asset.encrypted.parts : null;
if (focus && assetId != null && !state.loading && !state.dataUrl) { if (focus && assetId != null && !state.loading && !state.dataUrl) {
cancelled.current = false; cancelled.current = false;
updateState({ loading: true, loadPercent: 0 }); updateState({ loading: true, loadPercent: 0 });
try { try {
const dataUrl = await focus.getTopicAssetUrl(topicId, assetId, (loadPercent: number)=>{ updateState({ loadPercent }); return !cancelled.current }); const dataUrl = await focus.getTopicAssetUrl(topicId, assetId, (loadPercent: number)=>{ updateState({ loadPercent }); return !cancelled.current; });
updateState({ dataUrl }); updateState({ dataUrl });
} catch (err) { } catch (err) {
console.log(err); console.log(err);
@ -54,8 +53,8 @@ export function useBinaryAsset(topicId: string, asset: MediaAsset) {
} }
updateState({ loading: false }); updateState({ loading: false });
} }
} },
} };
return { state, actions } return { state, actions };
} }

View File

@ -48,10 +48,6 @@ export const styles = StyleSheet.create({
bottom: '10%', bottom: '10%',
width: '50%', width: '50%',
}, },
alert: {
position: 'absolute',
bottom: '10%'
},
alert: { alert: {
position: 'absolute', position: 'absolute',
bottom: 0, bottom: 0,

View File

@ -1,12 +1,12 @@
import React, { useState, useEffect, useRef } from 'react'; import React, { useState, useEffect } from 'react';
import { SafeAreaView, Modal, Pressable, Animated, View, Image, useAnimatedValue } from 'react-native' import { SafeAreaView, Modal, Pressable, Animated, View, Image, useAnimatedValue } from 'react-native';
import { Text, ProgressBar, IconButton } from 'react-native-paper' import { Text, ProgressBar, IconButton } from 'react-native-paper';
import { useImageAsset } from './useImageAsset.hook'; import { useImageAsset } from './useImageAsset.hook';
import { MediaAsset } from '../../conversation/Conversation'; import { MediaAsset } from '../../conversation/Conversation';
import { styles } from './ImageAsset.styled' import { styles } from './ImageAsset.styled';
import {BlurView} from '@react-native-community/blur'; import {BlurView} from '@react-native-community/blur';
import { ReactNativeZoomableView } from '@openspacelabs/react-native-zoomable-view'; import { ReactNativeZoomableView } from '@openspacelabs/react-native-zoomable-view';
import FastImage from 'react-native-fast-image' import FastImage from 'react-native-fast-image';
export function ImageAsset({ topicId, asset, loaded, show }: { topicId: string, asset: MediaAsset, loaded: ()=>void, show: boolean }) { export function ImageAsset({ topicId, asset, loaded, show }: { topicId: string, asset: MediaAsset, loaded: ()=>void, show: boolean }) {
const { state, actions } = useImageAsset(topicId, asset); const { state, actions } = useImageAsset(topicId, asset);
@ -26,6 +26,7 @@ export function ImageAsset({ topicId, asset, loaded, show }: { topicId: string,
if (state.loaded) { if (state.loaded) {
loaded(); loaded();
} }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [state.loaded, show]); }, [state.loaded, show]);
const showImage = () => { const showImage = () => {
@ -37,7 +38,7 @@ export function ImageAsset({ topicId, asset, loaded, show }: { topicId: string,
const hideImage = () => { const hideImage = () => {
setModal(false); setModal(false);
actions.cancelLoad(); actions.cancelLoad();
} };
const download = async () => { const download = async () => {
if (!downloading) { if (!downloading) {
@ -49,14 +50,14 @@ export function ImageAsset({ topicId, asset, loaded, show }: { topicId: string,
} }
setDownloading(false); setDownloading(false);
} }
} };
return ( return (
<View style={styles.image}> <View style={styles.image}>
{ state.thumbUrl && ( { state.thumbUrl && (
<Pressable onPress={showImage}> <Pressable onPress={showImage}>
<Animated.Image <Animated.Image
style={[styles.thumb,{opacity},]} style={[styles.thumb,{opacity}]}
resizeMode="contain" resizeMode="contain"
height={92} height={92}
width={92 * state.ratio} width={92 * state.ratio}

View File

@ -1,15 +1,13 @@
import { useState, useContext, useEffect, useRef } from 'react' import { useState, useContext, useEffect, useRef } from 'react';
import { Share } from 'react-native' import { AppContext } from '../../context/AppContext';
import { AppContext } from '../../context/AppContext' import { DisplayContext } from '../../context/DisplayContext';
import { DisplayContext } from '../../context/DisplayContext' import { ContextType } from '../../context/ContextType';
import { Focus } from 'databag-client-sdk'
import { ContextType } from '../../context/ContextType'
import { MediaAsset } from '../../conversation/Conversation'; import { MediaAsset } from '../../conversation/Conversation';
import { Download } from '../../download'; import { Download } from '../../download';
export function useImageAsset(topicId: string, asset: MediaAsset) { export function useImageAsset(topicId: string, asset: MediaAsset) {
const app = useContext(AppContext) as ContextType const app = useContext(AppContext) as ContextType;
const display = useContext(DisplayContext) as ContextType const display = useContext(DisplayContext) as ContextType;
const [state, setState] = useState({ const [state, setState] = useState({
strings: display.state.strings, strings: display.state.strings,
thumbUrl: null, thumbUrl: null,
@ -21,13 +19,13 @@ export function useImageAsset(topicId: string, asset: MediaAsset) {
width: 0, width: 0,
height: 0, height: 0,
failed: false, failed: false,
}) });
const cancelled = useRef(false); const cancelled = useRef(false);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const updateState = (value: any) => { const updateState = (value: any) => {
setState((s) => ({ ...s, ...value })) setState((s) => ({ ...s, ...value }));
} };
const setThumb = async () => { const setThumb = async () => {
const { focus } = app.state; const { focus } = app.state;
@ -44,7 +42,8 @@ export function useImageAsset(topicId: string, asset: MediaAsset) {
useEffect(() => { useEffect(() => {
setThumb(); setThumb();
}, [asset]); // eslint-disable-next-line react-hooks/exhaustive-deps
}, [asset]);
const actions = { const actions = {
loaded: (e) => { loaded: (e) => {
@ -74,10 +73,10 @@ export function useImageAsset(topicId: string, asset: MediaAsset) {
const { focus } = app.state; const { focus } = app.state;
const assetId = asset.image ? asset.image.full : asset.encrypted ? asset.encrypted.parts : null; const assetId = asset.image ? asset.image.full : asset.encrypted ? asset.encrypted.parts : null;
if (focus && assetId != null && !state.loading && !state.dataUrl) { if (focus && assetId != null && !state.loading && !state.dataUrl) {
cancelled.current = false; cancelled.current = false;
updateState({ loading: true, loadPercent: 0 }); updateState({ loading: true, loadPercent: 0 });
try { try {
const dataUrl = await focus.getTopicAssetUrl(topicId, assetId, (loadPercent: number)=>{ updateState({ loadPercent }); return !cancelled.current }); const dataUrl = await focus.getTopicAssetUrl(topicId, assetId, (loadPercent: number)=>{ updateState({ loadPercent }); return !cancelled.current; });
updateState({ dataUrl }); updateState({ dataUrl });
} catch (err) { } catch (err) {
console.log(err); console.log(err);
@ -85,8 +84,8 @@ export function useImageAsset(topicId: string, asset: MediaAsset) {
} }
updateState({ loading: false }); updateState({ loading: false });
} }
} },
} };
return { state, actions } return { state, actions };
} }

View File

@ -1,4 +1,4 @@
import { useEffect } from 'react'; import React, { useEffect } from 'react';
import { View, Animated, useAnimatedValue } from 'react-native'; import { View, Animated, useAnimatedValue } from 'react-native';
export function Shimmer({ contentStyle }: { contentStyle: any }) { export function Shimmer({ contentStyle }: { contentStyle: any }) {
@ -19,11 +19,12 @@ export function Shimmer({ contentStyle }: { contentStyle: any }) {
}), }),
]) ])
).start(); ).start();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, []);
return ( return (
<Animated.View style={[{},{opacity: shimmer},]}> <Animated.View style={[{},{opacity: shimmer}]}>
<View style={contentStyle}></View> <View style={contentStyle} />
</Animated.View> </Animated.View>
) );
} }

View File

@ -1,21 +1,21 @@
import { useState, useContext, useEffect } from 'react' import { useState, useContext, useEffect } from 'react';
import { DisplayContext } from '../context/DisplayContext' import { DisplayContext } from '../context/DisplayContext';
import { AppContext } from '../context/AppContext'; import { AppContext } from '../context/AppContext';
import { ContextType } from '../context/ContextType' import { ContextType } from '../context/ContextType';
export function useMessage() { export function useMessage() {
const app = useContext(AppContext) as ContextType const app = useContext(AppContext) as ContextType;
const display = useContext(DisplayContext) as ContextType const display = useContext(DisplayContext) as ContextType;
const [state, setState] = useState({ const [state, setState] = useState({
strings: display.state.strings, strings: display.state.strings,
timeFormat: display.state.timeFormat, timeFormat: display.state.timeFormat,
dateFormat: display.state.dateFormat, dateFormat: display.state.dateFormat,
}) });
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const updateState = (value: any) => { const updateState = (value: any) => {
setState((s) => ({ ...s, ...value })) setState((s) => ({ ...s, ...value }));
} };
useEffect(() => { useEffect(() => {
const { strings, timeFormat, dateFormat } = display.state; const { strings, timeFormat, dateFormat } = display.state;
@ -48,35 +48,35 @@ export function useMessage() {
} }
}, },
getTimestamp: (created: number) => { getTimestamp: (created: number) => {
const now = Math.floor((new Date()).getTime() / 1000) const now = Math.floor((new Date()).getTime() / 1000);
const date = new Date(created * 1000); const date = new Date(created * 1000);
const offset = now - created; const offset = now - created;
if(offset < 43200) { if(offset < 43200) {
if (state.timeFormat === '12h') { if (state.timeFormat === '12h') {
return date.toLocaleTimeString("en-US", {hour: 'numeric', minute:'2-digit'}); return date.toLocaleTimeString('en-US', {hour: 'numeric', minute:'2-digit'});
} }
else { else {
return date.toLocaleTimeString("en-GB", {hour: 'numeric', minute:'2-digit'}); return date.toLocaleTimeString('en-GB', {hour: 'numeric', minute:'2-digit'});
} }
} }
else if (offset < 31449600) { else if (offset < 31449600) {
if (state.dateFormat === 'mm/dd') { if (state.dateFormat === 'mm/dd') {
return date.toLocaleDateString("en-US", {day: 'numeric', month:'numeric'}); return date.toLocaleDateString('en-US', {day: 'numeric', month:'numeric'});
} }
else { else {
return date.toLocaleDateString("en-GB", {day: 'numeric', month:'numeric'}); return date.toLocaleDateString('en-GB', {day: 'numeric', month:'numeric'});
} }
} }
else { else {
if (state.dateFormat === 'mm/dd') { if (state.dateFormat === 'mm/dd') {
return date.toLocaleDateString("en-US"); return date.toLocaleDateString('en-US');
} }
else { else {
return date.toLocaleDateString("en-GB"); return date.toLocaleDateString('en-GB');
} }
} }
} },
} };
return { state, actions } return { state, actions };
} }

View File

@ -75,11 +75,4 @@ export const styles = StyleSheet.create({
spacer: { spacer: {
flexGrow: 1, flexGrow: 1,
}, },
alert: {
position: 'absolute',
bottom: 0,
},
alertLabel: {
color: Colors.offsync,
},
}); });

View File

@ -1,13 +1,12 @@
import React, { useState, useEffect, useRef } from 'react'; import React, { useState, useEffect, useRef } from 'react';
import { SafeAreaView, Share, Modal, Pressable, Animated, View, Image, useAnimatedValue } from 'react-native' import { SafeAreaView, Modal, Pressable, Animated, View, Image, useAnimatedValue } from 'react-native';
import { Text, Surface, Icon, ProgressBar, IconButton } from 'react-native-paper' import { Text, Surface, Icon, ProgressBar, IconButton } from 'react-native-paper';
import { useVideoAsset } from './useVideoAsset.hook'; import { useVideoAsset } from './useVideoAsset.hook';
import { MediaAsset } from '../../conversation/Conversation'; import { MediaAsset } from '../../conversation/Conversation';
import { styles } from './VideoAsset.styled' import { styles } from './VideoAsset.styled';
import {BlurView} from '@react-native-community/blur'; import {BlurView} from '@react-native-community/blur';
import Video, { VideoRef } from 'react-native-video' import Video, { VideoRef } from 'react-native-video';
import { Colors } from '../../constants/Colors'; import { activateKeepAwake, deactivateKeepAwake} from '@sayem314/react-native-keep-awake';
import { activateKeepAwake, deactivateKeepAwake} from "@sayem314/react-native-keep-awake";
export function VideoAsset({ topicId, asset, loaded, show }: { topicId: string, asset: MediaAsset, loaded: ()=>void, show: boolean }) { export function VideoAsset({ topicId, asset, loaded, show }: { topicId: string, asset: MediaAsset, loaded: ()=>void, show: boolean }) {
const { state, actions } = useVideoAsset(topicId, asset); const { state, actions } = useVideoAsset(topicId, asset);
@ -18,7 +17,7 @@ export function VideoAsset({ topicId, asset, loaded, show }: { topicId: string,
const [showControl, setShowControl] = useState(false); const [showControl, setShowControl] = useState(false);
const clear = useRef(); const clear = useRef();
const [downloading, setDownloading] = useState(false); const [downloading, setDownloading] = useState(false);
useEffect(() => { useEffect(() => {
if (state.loaded && show) { if (state.loaded && show) {
Animated.timing(opacity, { Animated.timing(opacity, {
@ -30,6 +29,7 @@ export function VideoAsset({ topicId, asset, loaded, show }: { topicId: string,
if (state.loaded) { if (state.loaded) {
loaded(); loaded();
} }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [state.loaded, show]); }, [state.loaded, show]);
const showVideo = () => { const showVideo = () => {
@ -42,7 +42,7 @@ export function VideoAsset({ topicId, asset, loaded, show }: { topicId: string,
setModal(false); setModal(false);
actions.cancelLoad(); actions.cancelLoad();
deactivateKeepAwake(); deactivateKeepAwake();
} };
const controls = () => { const controls = () => {
clearTimeout(clear.current); clearTimeout(clear.current);
@ -50,19 +50,19 @@ export function VideoAsset({ topicId, asset, loaded, show }: { topicId: string,
clear.current = setTimeout(() => { clear.current = setTimeout(() => {
setShowControl(false); setShowControl(false);
}, 3000); }, 3000);
} };
const play = () => { const play = () => {
videoRef.current.resume(); videoRef.current.resume();
} };
const pause = () => { const pause = () => {
videoRef.current.pause(); videoRef.current.pause();
} };
const end = () => { const end = () => {
videoRef.current.seek(0); videoRef.current.seek(0);
} };
const playbackRateChange = (e) => { const playbackRateChange = (e) => {
if (e.playbackRate === 0) { if (e.playbackRate === 0) {
@ -70,7 +70,7 @@ export function VideoAsset({ topicId, asset, loaded, show }: { topicId: string,
} else { } else {
setStatus('playing'); setStatus('playing');
} }
} };
const download = async () => { const download = async () => {
if (!downloading) { if (!downloading) {
@ -82,13 +82,13 @@ export function VideoAsset({ topicId, asset, loaded, show }: { topicId: string,
} }
setDownloading(false); setDownloading(false);
} }
} };
return ( return (
<View style={styles.video}> <View style={styles.video}>
{ state.thumbUrl && ( { state.thumbUrl && (
<Pressable style={styles.container} onPress={showVideo}> <Pressable style={styles.container} onPress={showVideo}>
<Animated.View style={[styles.thumb,{opacity},]}> <Animated.View style={[styles.thumb,{opacity}]}>
<Image <Image
resizeMode="contain" resizeMode="contain"
height={92} height={92}

View File

@ -1,14 +1,13 @@
import { useState, useContext, useEffect, useRef } from 'react' import { useState, useContext, useEffect, useRef } from 'react';
import { AppContext } from '../../context/AppContext' import { AppContext } from '../../context/AppContext';
import { DisplayContext } from '../../context/DisplayContext'; import { DisplayContext } from '../../context/DisplayContext';
import { Focus } from 'databag-client-sdk' import { ContextType } from '../../context/ContextType';
import { ContextType } from '../../context/ContextType'
import { MediaAsset } from '../../conversation/Conversation'; import { MediaAsset } from '../../conversation/Conversation';
import { Download } from '../../download'; import { Download } from '../../download';
export function useVideoAsset(topicId: string, asset: MediaAsset) { export function useVideoAsset(topicId: string, asset: MediaAsset) {
const app = useContext(AppContext) as ContextType const app = useContext(AppContext) as ContextType;
const display = useContext(DisplayContext) as ContextType const display = useContext(DisplayContext) as ContextType;
const [state, setState] = useState({ const [state, setState] = useState({
strings: display.state.strings, strings: display.state.strings,
thumbUrl: null, thumbUrl: null,
@ -18,13 +17,13 @@ export function useVideoAsset(topicId: string, asset: MediaAsset) {
loaded: false, loaded: false,
loadPercent: 0, loadPercent: 0,
failed: false, failed: false,
}) });
const cancelled = useRef(false); const cancelled = useRef(false);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const updateState = (value: any) => { const updateState = (value: any) => {
setState((s) => ({ ...s, ...value })) setState((s) => ({ ...s, ...value }));
} };
const setThumb = async () => { const setThumb = async () => {
const { focus } = app.state; const { focus } = app.state;
@ -41,7 +40,8 @@ export function useVideoAsset(topicId: string, asset: MediaAsset) {
useEffect(() => { useEffect(() => {
setThumb(); setThumb();
}, [asset]); // eslint-disable-next-line react-hooks/exhaustive-deps
}, [asset]);
const actions = { const actions = {
loaded: (e) => { loaded: (e) => {
@ -67,10 +67,10 @@ export function useVideoAsset(topicId: string, asset: MediaAsset) {
const { focus } = app.state; const { focus } = app.state;
const assetId = asset.video ? asset.video.hd || asset.video.lq : asset.encrypted ? asset.encrypted.parts : null; const assetId = asset.video ? asset.video.hd || asset.video.lq : asset.encrypted ? asset.encrypted.parts : null;
if (focus && assetId != null && !state.loading && !state.dataUrl) { if (focus && assetId != null && !state.loading && !state.dataUrl) {
cancelled.current = false; cancelled.current = false;
updateState({ loading: true, loadPercent: 0 }); updateState({ loading: true, loadPercent: 0 });
try { try {
const dataUrl = await focus.getTopicAssetUrl(topicId, assetId, (loadPercent: number)=>{ updateState({ loadPercent }); return !cancelled.current }); const dataUrl = await focus.getTopicAssetUrl(topicId, assetId, (loadPercent: number)=>{ updateState({ loadPercent }); return !cancelled.current; });
updateState({ dataUrl }); updateState({ dataUrl });
} catch (err) { } catch (err) {
console.log(err); console.log(err);
@ -78,8 +78,8 @@ export function useVideoAsset(topicId: string, asset: MediaAsset) {
} }
updateState({ loading: false }); updateState({ loading: false });
} }
} },
} };
return { state, actions } return { state, actions };
} }

View File

@ -1,6 +1,6 @@
import React, {useState} from 'react'; import React, {useState} from 'react';
import {Icon, Text, IconButton, Divider} from 'react-native-paper'; import {Icon, Text, IconButton, Divider} from 'react-native-paper';
import {ScrollView, Image, SafeAreaView, View} from 'react-native'; import {ScrollView, Image, View} from 'react-native';
import {styles} from './Profile.styled'; import {styles} from './Profile.styled';
import {useProfile} from './useProfile.hook'; import {useProfile} from './useProfile.hook';
import {Confirm} from '../confirm/Confirm'; import {Confirm} from '../confirm/Confirm';

View File

@ -126,7 +126,7 @@ export function useProfile(params: ContactParams) {
}, },
saveAndConnect: async () => { saveAndConnect: async () => {
const contact = app.state.session?.getContact(); const contact = app.state.session?.getContact();
const added = await contact.addAndConnectCard(state.node, state.guid); await contact.addAndConnectCard(state.node, state.guid);
}, },
remove: async () => { remove: async () => {
const contact = app.state.session?.getContact(); const contact = app.state.session?.getContact();

View File

@ -1,7 +1,7 @@
import React, { useRef, useEffect, useState } from 'react'; import React, { useRef, useEffect, useState } from 'react';
import { Animated, useAnimatedValue, View } from 'react-native'; import { Animated, useAnimatedValue, View } from 'react-native';
import { useRing } from './useRing.hook'; import { useRing } from './useRing.hook';
import { styles } from './Ring.styled' import { styles } from './Ring.styled';
import { Card as Contact } from '../card/Card'; import { Card as Contact } from '../card/Card';
import { Icon, Text, Surface, IconButton, ActivityIndicator } from 'react-native-paper'; import { Icon, Text, Surface, IconButton, ActivityIndicator } from 'react-native-paper';
import { Confirm } from '../confirm/Confirm'; import { Confirm } from '../confirm/Confirm';
@ -20,7 +20,7 @@ export function Ring() {
const [accepting, setAccepting] = useState(null as null|string); const [accepting, setAccepting] = useState(null as null|string);
const [ignoring, setIgnoring] = useState(null as null|string); const [ignoring, setIgnoring] = useState(null as null|string);
const [declining, setDeclining] = useState(null as null|string); const [declining, setDeclining] = useState(null as null|string);
const scale = useAnimatedValue(0) const scale = useAnimatedValue(0);
useEffect(() => { useEffect(() => {
const ringing = setInterval(() => { const ringing = setInterval(() => {
@ -44,6 +44,7 @@ export function Ring() {
useNativeDriver: false, useNativeDriver: false,
}).start(); }).start();
} }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [accepting, state.calling, state.calls]); }, [accepting, state.calling, state.calls]);
const toggleAudio = async () => { const toggleAudio = async () => {
@ -61,7 +62,7 @@ export function Ring() {
} }
setApplyingAudio(false); setApplyingAudio(false);
} }
} };
const end = async () => { const end = async () => {
if (!ending) { if (!ending) {
@ -74,7 +75,7 @@ export function Ring() {
} }
setEnding(false); setEnding(false);
} }
} };
const accept = async (callId, card) => { const accept = async (callId, card) => {
if (!accepting) { if (!accepting) {
@ -88,7 +89,7 @@ export function Ring() {
} }
setAccepting(null); setAccepting(null);
} }
} };
const ignore = async (callId, card) => { const ignore = async (callId, card) => {
if (!ignoring) { if (!ignoring) {
@ -101,7 +102,7 @@ export function Ring() {
} }
setIgnoring(null); setIgnoring(null);
} }
} };
const decline = async (callId, card) => { const decline = async (callId, card) => {
if (!declining) { if (!declining) {
@ -114,7 +115,7 @@ export function Ring() {
} }
setDeclining(null); setDeclining(null);
} }
} };
const alertParams = { const alertParams = {
title: state.strings.operationFailed, title: state.strings.operationFailed,
@ -127,22 +128,25 @@ export function Ring() {
}, },
}; };
const calls = state.calls.map((contact, index) => { const calls = state.calls.map((contact) => {
const { callId, card } = contact; const { callId, card } = contact;
const { name, handle, node, imageUrl } = card; const { name, handle, node, imageUrl } = card;
const ignoreButton = <IconButton key="ignore" style={styles.circleIcon} iconColor="white" containerColor={Colors.pending} icon="eye-off-outline" compact="true" mode="contained" size={24} loading={ignoring===callId} onPress={()=>ignore(callId, card)} /> const ignoreButton = <IconButton key="ignore" style={styles.circleIcon} iconColor="white" containerColor={Colors.pending} icon="eye-off-outline" compact="true" mode="contained" size={24} loading={ignoring === callId} onPress={()=>ignore(callId, card)} />;
const declineButton = <IconButton key="decline" style={styles.flipIcon} iconColor="white" containerColor={Colors.offsync} icon="phone-outline" compact="true" mode="contained" size={24} loading={declining===callId} onPress={()=>decline(callId, card)} /> const declineButton = <IconButton key="decline" style={styles.flipIcon} iconColor="white" containerColor={Colors.offsync} icon="phone-outline" compact="true" mode="contained" size={24} loading={declining === callId} onPress={()=>decline(callId, card)} />;
const acceptButton = <IconButton key="accept" style={styles.circleIcon} iconColor="white" containerColor={Colors.primary} icon="phone-outline" compact="true" mode="contained" size={24} loading={accepting===callId} onPress={()=>accept(callId, card)} /> const acceptButton = <IconButton key="accept" style={styles.circleIcon} iconColor="white" containerColor={Colors.primary} icon="phone-outline" compact="true" mode="contained" size={24} loading={accepting === callId} onPress={()=>accept(callId, card)} />;
return ( return (
<Contact containerStyle={styles.card} placeholder={state.strings.name} imageUrl={imageUrl} name={name} node={node} handle={handle} actions={[ignoreButton, declineButton, acceptButton]} /> <Contact containerStyle={styles.card} placeholder={state.strings.name} imageUrl={imageUrl} name={name} node={node} handle={handle} actions={[ignoreButton, declineButton, acceptButton]} />
) );
}); });
const sizeStyle = { width: '100%', height: scale };
const borderStyle = state.layout === 'large' ? { ...styles.ring, borderRadius: 16 } : { ...styles.ring, borderRadius: 0 };
return ( return (
<Animated.View style={{ width: '100%', height: scale }}> <Animated.View style={sizeStyle}>
<View style={(accepting || state.calling || state.calls.length > 0) ? styles.active : styles.inactive}> <View style={(accepting || state.calling || state.calls.length > 0) ? styles.active : styles.inactive}>
{ state.calls.length > 0 && !accepting && !state.calling && ( { state.calls.length > 0 && !accepting && !state.calling && (
<Surface elevation={4} mode="flat" style={{ ...styles.ring, borderRadius: state.layout === 'large' ? 16 : 0 }}> <Surface elevation={4} mode="flat" style={borderStyle}>
{ calls[0] } { calls[0] }
</Surface> </Surface>
)} )}
@ -152,7 +156,7 @@ export function Ring() {
</Surface> </Surface>
)} )}
{ state.calling && ( { state.calling && (
<Surface elevation={4} mode="flat" style={{ ...styles.ring, borderRadius: state.layout === 'large' ? 16 : 0 }}> <Surface elevation={4} mode="flat" style={borderStyle}>
<IconButton style={styles.circleIcon} iconColor="white" disabled={!state.connected} containerColor={Colors.primary} icon={state.audioEnabled ? 'microphone' : 'microphone-off'} compact="true" mode="contained" size={24} onPress={toggleAudio} /> <IconButton style={styles.circleIcon} iconColor="white" disabled={!state.connected} containerColor={Colors.primary} icon={state.audioEnabled ? 'microphone' : 'microphone-off'} compact="true" mode="contained" size={24} onPress={toggleAudio} />
<IconButton style={styles.circleIcon} iconColor="white" disabled={!state.connected} containerColor={Colors.confirmed} icon={(state.remoteVideo || state.localVideo) ? 'video-switch-outline' : 'arrow-expand-all'} compact="true" mode="contained" size={24} onPress={()=>actions.setFullscreen(true)} /> <IconButton style={styles.circleIcon} iconColor="white" disabled={!state.connected} containerColor={Colors.confirmed} icon={(state.remoteVideo || state.localVideo) ? 'video-switch-outline' : 'arrow-expand-all'} compact="true" mode="contained" size={24} onPress={()=>actions.setFullscreen(true)} />
<View style={styles.name}> <View style={styles.name}>
@ -165,7 +169,7 @@ export function Ring() {
</View> </View>
<View style={styles.status}> <View style={styles.status}>
{ state.connected && ( { state.connected && (
<Text style={styles.duration}>{ `${Math.floor(state.duration/60)}:${(state.duration % 60).toString().padStart(2, '0')}` }</Text> <Text style={styles.duration}>{ `${Math.floor(state.duration / 60)}:${(state.duration % 60).toString().padStart(2, '0')}` }</Text>
)} )}
{ !state.connected && ( { !state.connected && (
<View style={{ transform: [{ rotate: counter % 2 ? '15deg' : '-15deg' }] }}> <View style={{ transform: [{ rotate: counter % 2 ? '15deg' : '-15deg' }] }}>

View File

@ -1,7 +1,7 @@
import { useState, useContext, useEffect, useRef } from 'react' import { useState, useContext, useEffect, useRef } from 'react';
import { RingContext } from '../context/RingContext' import { RingContext } from '../context/RingContext';
import { DisplayContext } from '../context/DisplayContext' import { DisplayContext } from '../context/DisplayContext';
import { ContextType } from '../context/ContextType' import { ContextType } from '../context/ContextType';
import { Card } from 'databag-client-sdk'; import { Card } from 'databag-client-sdk';
export function useRing() { export function useRing() {
@ -21,12 +21,12 @@ export function useRing() {
connected: false, connected: false,
duration: 0, duration: 0,
failed: false, failed: false,
}) });
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const updateState = (value: any) => { const updateState = (value: any) => {
setState((s) => ({ ...s, ...value })) setState((s) => ({ ...s, ...value }));
} };
useEffect(() => { useEffect(() => {
const { layout, strings } = display.state; const { layout, strings } = display.state;
@ -37,14 +37,14 @@ export function useRing() {
const interval = setInterval(() => { const interval = setInterval(() => {
if (offset.current) { if (offset.current) {
const now = new Date(); const now = new Date();
const duration = Math.floor((now.getTime() / 1000) - offsetTime.current); const duration = Math.floor((now.getTime() / 1000) - offsetTime.current);
updateState({ duration }); updateState({ duration });
} }
}, 1000); }, 1000);
return () => { return () => {
clearInterval(interval); clearInterval(interval);
} };
}, []); }, []);
useEffect(() => { useEffect(() => {
const { calls, calling, localVideo, remoteVideo, audioEnabled, connected, connectedTime, failed } = ring.state; const { calls, calling, localVideo, remoteVideo, audioEnabled, connected, connectedTime, failed } = ring.state;

View File

@ -34,7 +34,7 @@ export const styles = StyleSheet.create({
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
borderRadius: 8, borderRadius: 8,
overflow: 'hidden' overflow: 'hidden',
}, },
header: { header: {
width: '100%', width: '100%',

View File

@ -19,6 +19,7 @@ export function Selector({ share, selected, channels }: { share: { filePath: str
setShow(true); setShow(true);
actions.clearFocus(); actions.clearFocus();
} }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [share]); }, [share]);
const select = () => { const select = () => {
@ -28,7 +29,7 @@ export function Selector({ share, selected, channels }: { share: { filePath: str
setTopic(null); setTopic(null);
selected(cardId, channelId); selected(cardId, channelId);
} }
} };
return ( return (
<Modal animationType="fade" transparent={true} supportedOrientations={['portrait', 'landscape']} visible={show} onRequestClose={()=>setShow(false)}> <Modal animationType="fade" transparent={true} supportedOrientations={['portrait', 'landscape']} visible={show} onRequestClose={()=>setShow(false)}>
@ -51,8 +52,8 @@ export function Selector({ share, selected, channels }: { share: { filePath: str
initialNumToRender={32} initialNumToRender={32}
showsVerticalScrollIndicator={false} showsVerticalScrollIndicator={false}
renderItem={({item}) => { renderItem={({item}) => {
const {cardId, channelId, sealed, focused, hosted, unread, imageUrl, subject, message} = item; const {cardId, channelId, sealed, hosted, imageUrl, subject, message} = item;
const select = () => { const selection = () => {
setTopic({ cardId, channelId }); setTopic({ cardId, channelId });
}; };
return ( return (
@ -62,7 +63,7 @@ export function Selector({ share, selected, channels }: { share: { filePath: str
...styles.channel, ...styles.channel,
borderColor: theme.colors.outlineVariant, borderColor: theme.colors.outlineVariant,
}} }}
select={select} select={selection}
unread={false} unread={false}
sealed={sealed} sealed={sealed}
hosted={hosted} hosted={hosted}
@ -85,7 +86,7 @@ export function Selector({ share, selected, channels }: { share: { filePath: str
<Button style={styles.control} mode="outlined" onPress={()=>setShow(false)}> <Button style={styles.control} mode="outlined" onPress={()=>setShow(false)}>
{state.strings.cancel} {state.strings.cancel}
</Button> </Button>
<Button style={styles.control} disabled={topic==null} mode="contained" onPress={select}> <Button style={styles.control} disabled={topic == null} mode="contained" onPress={select}>
{state.strings.selectImage} {state.strings.selectImage}
</Button> </Button>
</View> </View>

View File

@ -1,25 +1,19 @@
import { useState, useContext, useEffect } from 'react' import { useContext } from 'react';
import { AppContext } from '../context/AppContext' import { AppContext } from '../context/AppContext';
import { DisplayContext } from '../context/DisplayContext'; import { DisplayContext } from '../context/DisplayContext';
import { ContextType } from '../context/ContextType' import { ContextType } from '../context/ContextType';
import { Channel } from 'databag-client-sdk';
export function useSelector() { export function useSelector() {
const app = useContext(AppContext) as ContextType const app = useContext(AppContext) as ContextType;
const display = useContext(DisplayContext) as ContextType const display = useContext(DisplayContext) as ContextType;
const [state, setState] = useState({
strings: display.state.strings,
channels: [] as Channel[],
})
// eslint-disable-next-line @typescript-eslint/no-explicit-any const state = {
const updateState = (value: any) => { strings: display.state.strings,
setState((s) => ({ ...s, ...value })) };
}
const actions = { const actions = {
clearFocus: app.actions.clearFocus, clearFocus: app.actions.clearFocus,
} };
return { state, actions } return { state, actions };
} }

View File

@ -1,25 +1,24 @@
import React, {useState, useCallback, useEffect} from 'react'; import React, {useState, useCallback} from 'react';
import {SafeAreaView, Pressable, View, useColorScheme} from 'react-native'; import {SafeAreaView, View, useColorScheme} from 'react-native';
import {styles} from './Service.styled'; import {styles} from './Service.styled';
import {IconButton, Surface, Text, Icon} from 'react-native-paper'; import {IconButton, Surface} from 'react-native-paper';
import {Accounts} from '../accounts/Accounts'; import {Accounts} from '../accounts/Accounts';
import {Setup} from '../setup/Setup'; import {Setup} from '../setup/Setup';
import {useService} from './useService.hook'; import {useService} from './useService.hook';
import {NavigationContainer, DefaultTheme, DarkTheme} from '@react-navigation/native'; import {NavigationContainer, DefaultTheme, DarkTheme} from '@react-navigation/native';
import {createDrawerNavigator} from '@react-navigation/drawer'; import {createDrawerNavigator} from '@react-navigation/drawer';
import {Colors} from '../constants/Colors';
const SetupDrawer = createDrawerNavigator(); const SetupDrawer = createDrawerNavigator();
export function Service() { export function Service() {
const { state, actions } = useService(); const { state } = useService();
const [tab, setTab] = useState('accounts'); const [tab, setTab] = useState('accounts');
const scheme = useColorScheme(); const scheme = useColorScheme();
const showAccounts = {display: tab === 'accounts' ? 'flex' : 'none'}; const showAccounts = {display: tab === 'accounts' ? 'flex' : 'none'};
const showSetup = {display: tab === 'setup' ? 'flex' : 'none'}; const showSetup = {display: tab === 'setup' ? 'flex' : 'none'};
return ( return (
<View style={styles.service}> <Surface elevation={0} style={styles.service}>
{state.layout !== 'large' && ( {state.layout !== 'large' && (
<View> <View>
<View style={styles.full}> <View style={styles.full}>
@ -95,7 +94,7 @@ export function Service() {
</View> </View>
</NavigationContainer> </NavigationContainer>
)} )}
</View> </Surface>
); );
} }

View File

@ -1,4 +1,4 @@
import {useEffect, useState, useContext, useRef} from 'react'; import {useEffect, useState, useContext} from 'react';
import {AppContext} from '../context/AppContext'; import {AppContext} from '../context/AppContext';
import {DisplayContext} from '../context/DisplayContext'; import {DisplayContext} from '../context/DisplayContext';
import {ContextType} from '../context/ContextType'; import {ContextType} from '../context/ContextType';

View File

@ -1,5 +1,5 @@
import React, {useState, useCallback, useEffect} from 'react'; import React, {useState, useCallback, useEffect} from 'react';
import {SafeAreaView, Modal, Pressable, View, useColorScheme} from 'react-native'; import {SafeAreaView, Pressable, View, useColorScheme} from 'react-native';
import {RingContextProvider} from '../context/RingContext'; import {RingContextProvider} from '../context/RingContext';
import {styles} from './Session.styled'; import {styles} from './Session.styled';
import {IconButton, Surface, Text, Icon} from 'react-native-paper'; import {IconButton, Surface, Text, Icon} from 'react-native-paper';
@ -13,8 +13,6 @@ import {Identity} from '../identity/Identity';
import {Base} from '../base/Base'; import {Base} from '../base/Base';
import {Conversation} from '../conversation/Conversation'; import {Conversation} from '../conversation/Conversation';
import {useSession} from './useSession.hook'; import {useSession} from './useSession.hook';
import {TransitionPresets} from '@react-navigation/stack';
import {Focus, Card} from 'databag-client-sdk';
import {NavigationContainer, DefaultTheme, DarkTheme} from '@react-navigation/native'; import {NavigationContainer, DefaultTheme, DarkTheme} from '@react-navigation/native';
import {createDrawerNavigator} from '@react-navigation/drawer'; import {createDrawerNavigator} from '@react-navigation/drawer';
import {createNativeStackNavigator} from '@react-navigation/native-stack'; import {createNativeStackNavigator} from '@react-navigation/native-stack';
@ -45,11 +43,11 @@ export function Session({ share }: { share: { filePath: string, mimeType: string
const textContact = (cardId: null|string) => { const textContact = (cardId: null|string) => {
setTextCard({ cardId }); setTextCard({ cardId });
} };
const callContact = (card: null|Card) => { const callContact = (card: null|Card) => {
setCallCard({ card }); setCallCard({ card });
} };
const sessionNav = {strings: state.strings, callContact, callCard, textContact, textCard, focus, setFocus, share}; const sessionNav = {strings: state.strings, callContact, callCard, textContact, textCard, focus, setFocus, share};
const showContent = {display: tab === 'content' ? 'flex' : 'none'}; const showContent = {display: tab === 'content' ? 'flex' : 'none'};
@ -61,18 +59,19 @@ export function Session({ share }: { share: { filePath: string, mimeType: string
setTimeout(() => { setTimeout(() => {
setDismissed(false); setDismissed(false);
}, 60000); }, 60000);
} };
const contentTab = () => { const contentTab = () => {
if (tab !== 'content') { if (tab !== 'content') {
setTab('content'); setTab('content');
} }
} };
useEffect(() => { useEffect(() => {
if (share) { if (share) {
contentTab(); contentTab();
} }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [share]); }, [share]);
useEffect(() => { useEffect(() => {
@ -219,7 +218,7 @@ function ContentTab({scheme, textCard, contentTab, share}: {scheme: string, text
const openConversation = (props) => { const openConversation = (props) => {
props.navigation.navigate('conversation'); props.navigation.navigate('conversation');
contentTab(); contentTab();
} };
return ( return (
<NavigationContainer theme={scheme === 'dark' ? DarkTheme : DefaultTheme}> <NavigationContainer theme={scheme === 'dark' ? DarkTheme : DefaultTheme}>
@ -293,7 +292,7 @@ function DetailsScreen({nav}) {
const closeAll = (props) => { const closeAll = (props) => {
props.navigation.closeDrawer(); props.navigation.closeDrawer();
nav.setFocus(false); nav.setFocus(false);
} };
const DetailsComponent = useCallback( const DetailsComponent = useCallback(
(props) => ( (props) => (
@ -303,6 +302,7 @@ function DetailsScreen({nav}) {
/> />
</Surface> </Surface>
), ),
// eslint-disable-next-line react-hooks/exhaustive-deps
[nav], [nav],
); );
@ -448,6 +448,7 @@ function SettingsScreen({nav}) {
function HomeScreen({nav}) { function HomeScreen({nav}) {
useEffect(() => { useEffect(() => {
nav.contacts.closeDrawer(); nav.contacts.closeDrawer();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [nav.callCard]); }, [nav.callCard]);
return ( return (
@ -460,7 +461,7 @@ function HomeScreen({nav}) {
<Content share={nav.share} textCard={nav.textCard} closeAll={()=>{}} openConversation={()=>nav.setFocus(true)} /> <Content share={nav.share} textCard={nav.textCard} closeAll={()=>{}} openConversation={()=>nav.setFocus(true)} />
</Surface> </Surface>
</View> </View>
<Surface style={styles.right} mode="flat"> <Surface style={styles.right} mode="flat">
{ !nav.focus && ( { !nav.focus && (
<Base /> <Base />
)} )}

View File

@ -1,4 +1,4 @@
import {useEffect, useState, useContext, useRef} from 'react'; import {useEffect, useState, useContext} from 'react';
import { AppState } from 'react-native'; import { AppState } from 'react-native';
import {AppContext} from '../context/AppContext'; import {AppContext} from '../context/AppContext';
import {DisplayContext} from '../context/DisplayContext'; import {DisplayContext} from '../context/DisplayContext';
@ -25,13 +25,13 @@ export function useSession() {
if (loaded) { if (loaded) {
SplashScreen.hide(); SplashScreen.hide();
} }
} };
const setSdkState = (state: string) => { const setSdkState = (sdkState: string) => {
updateState({ sdkState: state === 'connected' }); updateState({ sdkState: sdkState === 'connected' });
} };
const setAppState = (state: string) => { const setAppState = (appState: string) => {
updateState({ appState: state === 'active' }); updateState({ appState: appState === 'active' });
} };
const session = app.state.session; const session = app.state.session;
if (session) { if (session) {
const content = session.getContent(); const content = session.getContent();
@ -42,7 +42,7 @@ export function useSession() {
session.removeStatusListener(setSdkState); session.removeStatusListener(setSdkState);
content.removeLoadedListener(setContentState); content.removeLoadedListener(setContentState);
sub.remove(); sub.remove();
} };
} }
}, [app.state.session]); }, [app.state.session]);

View File

@ -52,7 +52,7 @@ export function Settings({showLogout}: {showLogout: boolean}) {
setBlockedError(true); setBlockedError(true);
} }
setBlockedMessage(true); setBlockedMessage(true);
} };
const unblockMessage = async (blocked: {cardId: string | null, channelId: string, topicId: string, timestamp: number}) => { const unblockMessage = async (blocked: {cardId: string | null, channelId: string, topicId: string, timestamp: number}) => {
try { try {
@ -62,7 +62,7 @@ export function Settings({showLogout}: {showLogout: boolean}) {
console.log(err); console.log(err);
setBlockedError(true); setBlockedError(true);
} }
} };
const blockedMessages = state.blockedMessages.map((blocked, index) => ( const blockedMessages = state.blockedMessages.map((blocked, index) => (
<View key={index} style={{ ...styles.blockedItem, borderColor: theme.colors.outlineVariant }}> <View key={index} style={{ ...styles.blockedItem, borderColor: theme.colors.outlineVariant }}>
@ -80,7 +80,7 @@ export function Settings({showLogout}: {showLogout: boolean}) {
setBlockedError(true); setBlockedError(true);
} }
setBlockedChannel(true); setBlockedChannel(true);
} };
const unblockChannel = async (blocked: {cardId: string | null, channelId: string, topicId: string, timestamp: number}) => { const unblockChannel = async (blocked: {cardId: string | null, channelId: string, topicId: string, timestamp: number}) => {
try { try {
@ -90,7 +90,7 @@ export function Settings({showLogout}: {showLogout: boolean}) {
console.log(err); console.log(err);
setBlockedError(true); setBlockedError(true);
} }
} };
const blockedChannels = state.blockedChannels.map((blocked, index) => ( const blockedChannels = state.blockedChannels.map((blocked, index) => (
<View key={index} style={{ ...styles.blockedItem, borderColor: theme.colors.outlineVariant }}> <View key={index} style={{ ...styles.blockedItem, borderColor: theme.colors.outlineVariant }}>
@ -108,7 +108,7 @@ export function Settings({showLogout}: {showLogout: boolean}) {
setBlockedError(true); setBlockedError(true);
} }
setBlockedContact(true); setBlockedContact(true);
} };
const unblockContact = async (blocked: {cardId: string | null, channelId: string, topicId: string, timestamp: number}) => { const unblockContact = async (blocked: {cardId: string | null, channelId: string, topicId: string, timestamp: number}) => {
try { try {
@ -118,7 +118,7 @@ export function Settings({showLogout}: {showLogout: boolean}) {
console.log(err); console.log(err);
setBlockedError(true); setBlockedError(true);
} }
} };
const blockedContacts = state.blockedContacts.map((blocked, index) => ( const blockedContacts = state.blockedContacts.map((blocked, index) => (
<View key={index} style={{ ...styles.blockedItem, borderColor: theme.colors.outlineVariant }}> <View key={index} style={{ ...styles.blockedItem, borderColor: theme.colors.outlineVariant }}>
@ -669,7 +669,7 @@ export function Settings({showLogout}: {showLogout: boolean}) {
<View style={styles.modalControls}> <View style={styles.modalControls}>
<View style={styles.modalOption}> <View style={styles.modalOption}>
<IconButton <IconButton
style={styles.optionIcon} style={styles.optionIcon}
iconColor={Colors.primary} iconColor={Colors.primary}
icon="menu-right-outline" icon="menu-right-outline"
size={32} size={32}
@ -696,7 +696,7 @@ export function Settings({showLogout}: {showLogout: boolean}) {
</Button> </Button>
<View style={styles.modalOther}> <View style={styles.modalOther}>
<IconButton <IconButton
style={styles.optionIcon} style={styles.optionIcon}
iconColor={Colors.primary} iconColor={Colors.primary}
icon="menu-left-outline" icon="menu-left-outline"
size={32} size={32}
@ -719,8 +719,8 @@ export function Settings({showLogout}: {showLogout: boolean}) {
autoComplete="off" autoComplete="off"
autoCorrect={false} autoCorrect={false}
value={state.sealPassword} value={state.sealPassword}
label={Platform.OS==='ios'?state.strings.password:undefined} label={Platform.OS === 'ios' ? state.strings.password : undefined}
placeholder={Platform.OS!=='ios'?state.strings.password:undefined} placeholder={Platform.OS !== 'ios' ? state.strings.password : undefined}
secureTextEntry={!showPassword} secureTextEntry={!showPassword}
left={<TextInput.Icon style={styles.icon} icon="lock" />} left={<TextInput.Icon style={styles.icon} icon="lock" />}
right={ right={
@ -739,8 +739,8 @@ export function Settings({showLogout}: {showLogout: boolean}) {
autoComplete="off" autoComplete="off"
autoCorrect={false} autoCorrect={false}
value={state.sealConfirm} value={state.sealConfirm}
label={Platform.OS==='ios'?state.strings.confirmPassword:undefined} label={Platform.OS === 'ios' ? state.strings.confirmPassword : undefined}
placeholder={Platform.OS!=='ios'?state.strings.confirmPassword:undefined} placeholder={Platform.OS !== 'ios' ? state.strings.confirmPassword : undefined}
secureTextEntry={!showConfirm} secureTextEntry={!showConfirm}
left={<TextInput.Icon style={styles.icon} icon="lock" />} left={<TextInput.Icon style={styles.icon} icon="lock" />}
right={ right={
@ -772,8 +772,8 @@ export function Settings({showLogout}: {showLogout: boolean}) {
autoComplete="off" autoComplete="off"
autoCorrect={false} autoCorrect={false}
value={state.sealPassword} value={state.sealPassword}
label={Platform.OS==='ios'?state.strings.password:undefined} label={Platform.OS === 'ios' ? state.strings.password : undefined}
placeholder={Platform.OS!=='ios'?state.strings.password:undefined} placeholder={Platform.OS !== 'ios' ? state.strings.password : undefined}
secureTextEntry={!showPassword} secureTextEntry={!showPassword}
left={<TextInput.Icon style={styles.icon} icon="lock" />} left={<TextInput.Icon style={styles.icon} icon="lock" />}
right={ right={
@ -789,7 +789,7 @@ export function Settings({showLogout}: {showLogout: boolean}) {
<View style={styles.modalControls}> <View style={styles.modalControls}>
<View style={styles.modalOption}> <View style={styles.modalOption}>
<IconButton <IconButton
style={styles.optionIcon} style={styles.optionIcon}
iconColor={Colors.primary} iconColor={Colors.primary}
icon="menu-right-outline" icon="menu-right-outline"
size={32} size={32}
@ -813,7 +813,7 @@ export function Settings({showLogout}: {showLogout: boolean}) {
</Button> </Button>
<View style={styles.modalOther}> <View style={styles.modalOther}>
<IconButton <IconButton
style={styles.optionIcon} style={styles.optionIcon}
iconColor={Colors.primary} iconColor={Colors.primary}
icon="menu-left-outline" icon="menu-left-outline"
size={32} size={32}
@ -836,8 +836,8 @@ export function Settings({showLogout}: {showLogout: boolean}) {
autoComplete="off" autoComplete="off"
autoCorrect={false} autoCorrect={false}
value={state.sealDelete} value={state.sealDelete}
label={Platform.OS==='ios'?state.strings.deleteKey:undefined} label={Platform.OS === 'ios' ? state.strings.deleteKey : undefined}
placeholder={Platform.OS!=='ios'?state.strings.deleteKey:undefined} placeholder={Platform.OS !== 'ios' ? state.strings.deleteKey : undefined}
left={<TextInput.Icon style={styles.icon} icon="lock" />} left={<TextInput.Icon style={styles.icon} icon="lock" />}
onChangeText={value => actions.setSealDelete(value)} onChangeText={value => actions.setSealDelete(value)}
/> />
@ -859,8 +859,8 @@ export function Settings({showLogout}: {showLogout: boolean}) {
autoComplete="off" autoComplete="off"
autoCorrect={false} autoCorrect={false}
value={state.sealPassword} value={state.sealPassword}
label={Platform.OS==='ios'?state.strings.password:undefined} label={Platform.OS === 'ios' ? state.strings.password : undefined}
placeholder={Platform.OS!=='ios'?state.strings.password:undefined} placeholder={Platform.OS !== 'ios' ? state.strings.password : undefined}
secureTextEntry={!showPassword} secureTextEntry={!showPassword}
left={<TextInput.Icon style={styles.icon} icon="lock" />} left={<TextInput.Icon style={styles.icon} icon="lock" />}
right={ right={
@ -899,8 +899,8 @@ export function Settings({showLogout}: {showLogout: boolean}) {
autoCapitalize="none" autoCapitalize="none"
autoComplete="off" autoComplete="off"
autoCorrect={false} autoCorrect={false}
label={Platform.OS==='ios'?state.strings.name:undefined} label={Platform.OS === 'ios' ? state.strings.name : undefined}
placeholder={Platform.OS!=='ios'?state.strings.name:undefined} placeholder={Platform.OS !== 'ios' ? state.strings.name : undefined}
value={state.name} value={state.name}
left={<TextInput.Icon style={styles.inputIcon} icon="account" />} left={<TextInput.Icon style={styles.inputIcon} icon="account" />}
onChangeText={value => actions.setName(value)} onChangeText={value => actions.setName(value)}
@ -911,8 +911,8 @@ export function Settings({showLogout}: {showLogout: boolean}) {
autoCapitalize="none" autoCapitalize="none"
autoComplete="off" autoComplete="off"
autoCorrect={false} autoCorrect={false}
label={Platform.OS==='ios'?state.strings.location:undefined} label={Platform.OS === 'ios' ? state.strings.location : undefined}
placeholder={Platform.OS!=='ios'?state.strings.location:undefined} placeholder={Platform.OS !== 'ios' ? state.strings.location : undefined}
value={state.location} value={state.location}
left={<TextInput.Icon style={styles.inputIcon} icon="map-marker-outline" />} left={<TextInput.Icon style={styles.inputIcon} icon="map-marker-outline" />}
onChangeText={value => actions.setLocation(value)} onChangeText={value => actions.setLocation(value)}
@ -923,8 +923,8 @@ export function Settings({showLogout}: {showLogout: boolean}) {
autoCapitalize="none" autoCapitalize="none"
autoComplete="off" autoComplete="off"
autoCorrect={false} autoCorrect={false}
label={Platform.OS==='ios'?state.strings.description:undefined} label={Platform.OS === 'ios' ? state.strings.description : undefined}
placeholder={Platform.OS!=='ios'?state.strings.description:undefined} placeholder={Platform.OS !== 'ios' ? state.strings.description : undefined}
value={state.description} value={state.description}
left={<TextInput.Icon style={styles.inputIcon} icon="book-open-outline" />} left={<TextInput.Icon style={styles.inputIcon} icon="book-open-outline" />}
onChangeText={value => actions.setDescription(value)} onChangeText={value => actions.setDescription(value)}
@ -1013,8 +1013,8 @@ export function Settings({showLogout}: {showLogout: boolean}) {
autoCapitalize="none" autoCapitalize="none"
autoComplete="off" autoComplete="off"
autoCorrect={false} autoCorrect={false}
label={Platform.OS==='ios' ? state.strings.username : undefined} label={Platform.OS === 'ios' ? state.strings.username : undefined}
placeholder={Platform.OS!=='ios' ? state.strings.username : undefined} placeholder={Platform.OS !== 'ios' ? state.strings.username : undefined}
value={state.handle} value={state.handle}
left={<TextInput.Icon style={styles.inputIcon} icon="account" />} left={<TextInput.Icon style={styles.inputIcon} icon="account" />}
right={ right={
@ -1035,8 +1035,8 @@ export function Settings({showLogout}: {showLogout: boolean}) {
autoComplete="off" autoComplete="off"
autoCorrect={false} autoCorrect={false}
value={state.password} value={state.password}
label={Platform.OS==='ios' ? state.strings.password : undefined} label={Platform.OS === 'ios' ? state.strings.password : undefined}
placeholder={Platform.OS!=='ios' ? state.strings.password : undefined} placeholder={Platform.OS !== 'ios' ? state.strings.password : undefined}
secureTextEntry={!showPassword} secureTextEntry={!showPassword}
left={<TextInput.Icon style={styles.icon} icon="lock" />} left={<TextInput.Icon style={styles.icon} icon="lock" />}
right={ right={
@ -1055,8 +1055,8 @@ export function Settings({showLogout}: {showLogout: boolean}) {
autoComplete="off" autoComplete="off"
autoCorrect={false} autoCorrect={false}
value={state.confirm} value={state.confirm}
label={Platform.OS==='ios' ? state.strings.confirmPassword : undefined} label={Platform.OS === 'ios' ? state.strings.confirmPassword : undefined}
placeholder={Platform.OS!=='ios' ? state.strings.confirmPassword : undefined} placeholder={Platform.OS !== 'ios' ? state.strings.confirmPassword : undefined}
secureTextEntry={!showConfirm} secureTextEntry={!showConfirm}
left={<TextInput.Icon style={styles.icon} icon="lock" />} left={<TextInput.Icon style={styles.icon} icon="lock" />}
right={ right={
@ -1121,8 +1121,8 @@ export function Settings({showLogout}: {showLogout: boolean}) {
autoComplete="off" autoComplete="off"
autoCorrect={false} autoCorrect={false}
value={state.remove} value={state.remove}
label={Platform.OS==='ios'?state.strings.deleteKey:undefined} label={Platform.OS === 'ios' ? state.strings.deleteKey : undefined}
placeholder={Platform.OS!=='ios'?state.strings.deleteKey:undefined} placeholder={Platform.OS !== 'ios' ? state.strings.deleteKey : undefined}
left={<TextInput.Icon style={styles.icon} icon="delete-outline" />} left={<TextInput.Icon style={styles.icon} icon="delete-outline" />}
onChangeText={value => actions.setRemove(value)} onChangeText={value => actions.setRemove(value)}
/> />
@ -1149,7 +1149,7 @@ export function Settings({showLogout}: {showLogout: boolean}) {
<IconButton style={styles.blockedClose} icon="close" size={24} onPress={() => setBlockedMessage(false)} /> <IconButton style={styles.blockedClose} icon="close" size={24} onPress={() => setBlockedMessage(false)} />
</View> </View>
<Surface style={styles.blocked} elevation={1} mode="flat"> <Surface style={styles.blocked} elevation={1} mode="flat">
{ state.blockedMessages.length == 0 && ( { state.blockedMessages.length === 0 && (
<View style={styles.blockedEmpty}> <View style={styles.blockedEmpty}>
<Text style={styles.blockedLabel}>{ state.strings.noMessages }</Text> <Text style={styles.blockedLabel}>{ state.strings.noMessages }</Text>
</View> </View>
@ -1159,7 +1159,7 @@ export function Settings({showLogout}: {showLogout: boolean}) {
{ blockedMessages } { blockedMessages }
</View> </View>
)} )}
</Surface> </Surface>
<View style={styles.blockedDone}> <View style={styles.blockedDone}>
{ blockedError && ( { blockedError && (
<Text style={styles.blockedError}>{ state.strings.operationFailed }</Text> <Text style={styles.blockedError}>{ state.strings.operationFailed }</Text>
@ -1182,17 +1182,17 @@ export function Settings({showLogout}: {showLogout: boolean}) {
<IconButton style={styles.blockedClose} icon="close" size={24} onPress={() => setBlockedChannel(false)} /> <IconButton style={styles.blockedClose} icon="close" size={24} onPress={() => setBlockedChannel(false)} />
</View> </View>
<Surface style={styles.blocked} elevation={1} mode="flat"> <Surface style={styles.blocked} elevation={1} mode="flat">
{ state.blockedChannels.length == 0 && ( { state.blockedChannels.length === 0 && (
<View style={styles.blockedEmpty}> <View style={styles.blockedEmpty}>
<Text style={styles.blockedLabel}>{ state.strings.noTopics }</Text> <Text style={styles.blockedLabel}>{ state.strings.noTopics }</Text>
</View> </View>
)} )}
{ state.blockedChannels.length > 0 && ( { state.blockedChannels.length > 0 && (
<View> <View>
{ blockedChannels } { blockedChannels }
</View> </View>
)} )}
</Surface> </Surface>
<View style={styles.blockedDone}> <View style={styles.blockedDone}>
{ blockedError && ( { blockedError && (
<Text style={styles.blockedError}>{ state.strings.operationFailed }</Text> <Text style={styles.blockedError}>{ state.strings.operationFailed }</Text>
@ -1215,17 +1215,17 @@ export function Settings({showLogout}: {showLogout: boolean}) {
<IconButton style={styles.blockedClose} icon="close" size={24} onPress={() => setBlockedContact(false)} /> <IconButton style={styles.blockedClose} icon="close" size={24} onPress={() => setBlockedContact(false)} />
</View> </View>
<Surface style={styles.blocked} elevation={1} mode="flat"> <Surface style={styles.blocked} elevation={1} mode="flat">
{ state.blockedContacts.length == 0 && ( { state.blockedContacts.length === 0 && (
<View style={styles.blockedEmpty}> <View style={styles.blockedEmpty}>
<Text style={styles.blockedLabel}>{ state.strings.noContacts }</Text> <Text style={styles.blockedLabel}>{ state.strings.noContacts }</Text>
</View> </View>
)} )}
{ state.blockedContacts.length > 0 && ( { state.blockedContacts.length > 0 && (
<View> <View>
{ blockedContacts } { blockedContacts }
</View> </View>
)} )}
</Surface> </Surface>
<View style={styles.blockedDone}> <View style={styles.blockedDone}>
{ blockedError && ( { blockedError && (
<Text style={styles.blockedError}>{ state.strings.operationFailed }</Text> <Text style={styles.blockedError}>{ state.strings.operationFailed }</Text>

View File

@ -258,7 +258,7 @@ export function useSettings() {
unblockMessage: async (cardId: string | null, channelId: string, topicId: string) => { unblockMessage: async (cardId: string | null, channelId: string, topicId: string) => {
const content = app.state.session.getContent(); const content = app.state.session.getContent();
await content.clearBlockedChannelTopic(cardId, channelId, topicId); await content.clearBlockedChannelTopic(cardId, channelId, topicId);
const blockedMessages = state.blockedMessages.filter(blocked => (blocked.cardId != cardId || blocked.channelId != channelId || blocked.topicId != topicId)); const blockedMessages = state.blockedMessages.filter(blocked => (blocked.cardId !== cardId || blocked.channelId !== channelId || blocked.topicId !== topicId));
updateState({ blockedMessages }); updateState({ blockedMessages });
}, },
loadBlockedChannels: async () => { loadBlockedChannels: async () => {
@ -269,7 +269,7 @@ export function useSettings() {
unblockChannel: async (cardId: string | null, channelId: string) => { unblockChannel: async (cardId: string | null, channelId: string) => {
const content = app.state.session.getContent(); const content = app.state.session.getContent();
await content.setBlockedChannel(cardId, channelId, false); await content.setBlockedChannel(cardId, channelId, false);
const blockedChannels = state.blockedChannels.filter(blocked => (blocked.cardId != cardId || blocked.channelId != channelId)); const blockedChannels = state.blockedChannels.filter(blocked => (blocked.cardId !== cardId || blocked.channelId !== channelId));
updateState({ blockedChannels }); updateState({ blockedChannels });
}, },
loadBlockedContacts: async () => { loadBlockedContacts: async () => {
@ -280,38 +280,38 @@ export function useSettings() {
unblockContact: async (cardId: string) => { unblockContact: async (cardId: string) => {
const contact = app.state.session.getContact(); const contact = app.state.session.getContact();
await contact.setBlockedCard(cardId, false); await contact.setBlockedCard(cardId, false);
const blockedContacts = state.blockedContacts.filter(blocked => blocked.cardId != cardId); const blockedContacts = state.blockedContacts.filter(blocked => blocked.cardId !== cardId);
updateState({ blockedContacts }); updateState({ blockedContacts });
}, },
getTimestamp: (created: number) => { getTimestamp: (created: number) => {
const now = Math.floor((new Date()).getTime() / 1000) const now = Math.floor((new Date()).getTime() / 1000);
const date = new Date(created * 1000); const date = new Date(created * 1000);
const offset = now - created; const offset = now - created;
if(offset < 43200) { if(offset < 43200) {
if (state.timeFormat === '12h') { if (state.timeFormat === '12h') {
return date.toLocaleTimeString("en-US", {hour: 'numeric', minute:'2-digit'}); return date.toLocaleTimeString('en-US', {hour: 'numeric', minute:'2-digit'});
} }
else { else {
return date.toLocaleTimeString("en-GB", {hour: 'numeric', minute:'2-digit'}); return date.toLocaleTimeString('en-GB', {hour: 'numeric', minute:'2-digit'});
} }
} }
else if (offset < 31449600) { else if (offset < 31449600) {
if (state.dateFormat === 'mm/dd') { if (state.dateFormat === 'mm/dd') {
return date.toLocaleDateString("en-US", {day: 'numeric', month:'numeric'}); return date.toLocaleDateString('en-US', {day: 'numeric', month:'numeric'});
} }
else { else {
return date.toLocaleDateString("en-GB", {day: 'numeric', month:'numeric'}); return date.toLocaleDateString('en-GB', {day: 'numeric', month:'numeric'});
} }
} }
else { else {
if (state.dateFormat === 'mm/dd') { if (state.dateFormat === 'mm/dd') {
return date.toLocaleDateString("en-US"); return date.toLocaleDateString('en-US');
} }
else { else {
return date.toLocaleDateString("en-GB"); return date.toLocaleDateString('en-GB');
} }
} }
} },
}; };
return {state, actions}; return {state, actions};

View File

@ -1,5 +1,5 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import {SafeAreaView, TouchableOpacity, Modal, Image, View, Pressable} from 'react-native'; import {TouchableOpacity, Modal, Image, View} from 'react-native';
import {ActivityIndicator, Icon, Button, IconButton, RadioButton, Switch, Surface, Divider, TextInput, Text} from 'react-native-paper'; import {ActivityIndicator, Icon, Button, IconButton, RadioButton, Switch, Surface, Divider, TextInput, Text} from 'react-native-paper';
import {styles} from './Setup.styled'; import {styles} from './Setup.styled';
import {useSetup} from './useSetup.hook'; import {useSetup} from './useSetup.hook';
@ -12,18 +12,17 @@ import Clipboard from '@react-native-clipboard/clipboard';
export function Setup() { export function Setup() {
const { state, actions } = useSetup(); const { state, actions } = useSetup();
const [mfaCode, setMfaCode] = useState('');
const [updating, setUpdating] = useState(false); const [updating, setUpdating] = useState(false);
const [secretCopy, setSecretCopy] = useState(false); const [secretCopy, setSecretCopy] = useState(false);
const [confirmingAuth, setConfirmingAuth] = useState(false); const [confirmingAuth, setConfirmingAuth] = useState(false);
const errorParams = { const errorParams = {
title: state.strings.operationFailed, title: state.strings.operationFailed,
prompt: state.strings.tryAgain, prompt: state.strings.tryAgain,
cancel: { cancel: {
label: state.strings.close, label: state.strings.close,
action: actions.clearError, action: actions.clearError,
}, },
}; };
const confirmAuth = async () => { const confirmAuth = async () => {
@ -32,7 +31,7 @@ export function Setup() {
await actions.confirmMFAuth(); await actions.confirmMFAuth();
setConfirmingAuth(false); setConfirmingAuth(false);
} }
} };
const copySecret = async () => { const copySecret = async () => {
if (!secretCopy) { if (!secretCopy) {
@ -54,7 +53,7 @@ export function Setup() {
} }
setUpdating(false); setUpdating(false);
} }
} };
return ( return (
<View style={styles.setup}> <View style={styles.setup}>
@ -76,14 +75,14 @@ export function Setup() {
<View style={styles.radioSelect}> <View style={styles.radioSelect}>
<View style={styles.radio}> <View style={styles.radio}>
<Text style={styles.radioLabel}>RSA2048</Text> <Text style={styles.radioLabel}>RSA2048</Text>
<RadioButton.Item <RadioButton.Item
disabled={state.loading} disabled={state.loading}
rippleColor="transparent" rippleColor="transparent"
style={styles.radioButton} style={styles.radioButton}
label="" label=""
mode="android" mode="android"
status={state.setup?.keyType === 'RSA2048' ? 'checked' : 'unchecked'} status={state.setup?.keyType === 'RSA2048' ? 'checked' : 'unchecked'}
onPress={() => { actions.setKeyType('RSA2048') }} onPress={() => { actions.setKeyType('RSA2048'); }}
/> />
</View> </View>
<View style={styles.radio}> <View style={styles.radio}>
@ -95,7 +94,7 @@ export function Setup() {
label="" label=""
mode="android" mode="android"
status={state.setup?.keyType === 'RSA4096' ? 'checked' : 'unchecked'} status={state.setup?.keyType === 'RSA4096' ? 'checked' : 'unchecked'}
onPress={() => { actions.setKeyType('RSA4096') }} onPress={() => { actions.setKeyType('RSA4096'); }}
/> />
</View> </View>
</View> </View>

View File

@ -41,14 +41,14 @@ export function useSetup() {
const mfaEnabled = await service.checkMFAuth(); const mfaEnabled = await service.checkMFAuth();
setup.current = await service.getSetup(); setup.current = await service.getSetup();
loading.current = false; loading.current = false;
const storage = Math.floor((setup.current?.accountStorage || 0) / 1073741824); const storage = Math.floor((setup.current?.accountStorage || 0) / 1073741824);
updateState({ setup: setup.current, mfaEnabled, accountStorage: storage.toString(), loading: false }); updateState({ setup: setup.current, mfaEnabled, accountStorage: storage.toString(), loading: false });
} catch (err) { } catch (err) {
console.log(err); console.log(err);
await new Promise((r) => setTimeout(r, DELAY_MS)); await new Promise((r) => setTimeout(r, DELAY_MS));
} }
} }
} };
const save = () => { const save = () => {
updated.current = true; updated.current = true;
@ -60,7 +60,7 @@ export function useSetup() {
const service = app.state.service; const service = app.state.service;
await service.setSetup(setup.current); await service.setSetup(setup.current);
if (updated.current) { if (updated.current) {
save() save();
} else { } else {
updateState({ updating: false }); updateState({ updating: false });
} }
@ -69,7 +69,7 @@ export function useSetup() {
updateState({ error: true }); updateState({ error: true });
} }
}, DEBOUNCE_MS); }, DEBOUNCE_MS);
} };
useEffect(() => { useEffect(() => {
const { layout, strings} = display.state; const { layout, strings} = display.state;
@ -82,9 +82,10 @@ export function useSetup() {
sync(); sync();
return () => { return () => {
loading.current = false; loading.current = false;
} };
} }
}, []); // eslint-disable-next-line react-hooks/exhaustive-deps
}, [app.state.service]);
const actions = { const actions = {
logout: app.actions.adminLogout, logout: app.actions.adminLogout,
@ -117,7 +118,7 @@ export function useSetup() {
await service.confirmMFAuth(state.mfaCode); await service.confirmMFAuth(state.mfaCode);
updateState({ confirmingMFAuth: false, mfaEnabled: true }); updateState({ confirmingMFAuth: false, mfaEnabled: true });
} catch (err) { } catch (err) {
const { message } = err as { message: string } const { message } = err as { message: string };
if (message === '401') { if (message === '401') {
updateState({ mfaMessage: state.strings.mfaError }); updateState({ mfaMessage: state.strings.mfaError });
} else if (message === '429') { } else if (message === '429') {
@ -142,7 +143,7 @@ export function useSetup() {
}, },
setAccountStorage: (accountStorage: string) => { setAccountStorage: (accountStorage: string) => {
if (setup.current) { if (setup.current) {
const storage = parseInt(accountStorage) * 1073741824; const storage = parseInt(accountStorage, 10) * 1073741824;
if (storage >= 0) { if (storage >= 0) {
setup.current.accountStorage = storage; setup.current.accountStorage = storage;
updateState({ setup: setup.current, accountStorage }); updateState({ setup: setup.current, accountStorage });
@ -170,7 +171,7 @@ export function useSetup() {
}, },
setOpenAccessLimit: (openAccessLimit: string) => { setOpenAccessLimit: (openAccessLimit: string) => {
if (setup.current) { if (setup.current) {
const limit = parseInt(openAccessLimit); const limit = parseInt(openAccessLimit, 10);
if (limit >= 0) { if (limit >= 0) {
setup.current.openAccessLimit = limit; setup.current.openAccessLimit = limit;
updateState({ setup: setup.current, openAccessLimit }); updateState({ setup: setup.current, openAccessLimit });

View File

@ -1,5 +1,4 @@
import {StyleSheet} from 'react-native'; import {StyleSheet} from 'react-native';
import { Colors } from '../constants/Colors';
export const styles = StyleSheet.create({ export const styles = StyleSheet.create({
base: { base: {
@ -41,7 +40,7 @@ export const styles = StyleSheet.create({
display: 'flex', display: 'flex',
flexDirection: 'row', flexDirection: 'row',
gap: 8, gap: 8,
align: 'center', align: 'center',
}, },
label: { label: {
fontSize: 18, fontSize: 18,

View File

@ -1,4 +1,4 @@
import React from 'react' import React from 'react';
import { View, Image } from 'react-native'; import { View, Image } from 'react-native';
import { styles } from './Welcome.styled'; import { styles } from './Welcome.styled';
import { useWelcome } from './useWelcome.hook'; import { useWelcome } from './useWelcome.hook';
@ -16,7 +16,7 @@ export function Welcome() {
<View style={{ ...styles.base, backgroundColor: theme.colors.base }}> <View style={{ ...styles.base, backgroundColor: theme.colors.base }}>
<Text style={styles.title}>Databag</Text> <Text style={styles.title}>Databag</Text>
<Text style={styles.description}>{ state.strings.communication }</Text> <Text style={styles.description}>{ state.strings.communication }</Text>
<Image style={styles.image} source={theme.colors.name == 'light' ? light : dark} resizeMode="contain" /> <Image style={styles.image} source={theme.colors.name === 'light' ? light : dark} resizeMode="contain" />
<View style={styles.steps}> <View style={styles.steps}>
<View style={styles.step}> <View style={styles.step}>
<Icon size={18} source="chevron-right" color={Colors.placeholder} /> <Icon size={18} source="chevron-right" color={Colors.placeholder} />

View File

@ -1,20 +1,20 @@
import { useState, useContext, useEffect } from 'react' import { useState, useContext, useEffect } from 'react';
import { DisplayContext } from '../context/DisplayContext'; import { DisplayContext } from '../context/DisplayContext';
import { AppContext } from '../context/AppContext'; import { AppContext } from '../context/AppContext';
import { ContextType } from '../context/ContextType' import { ContextType } from '../context/ContextType';
export function useWelcome() { export function useWelcome() {
const app = useContext(AppContext) as ContextType const app = useContext(AppContext) as ContextType;
const display = useContext(DisplayContext) as ContextType const display = useContext(DisplayContext) as ContextType;
const [state, setState] = useState({ const [state, setState] = useState({
strings: display.state.strings, strings: display.state.strings,
showWelcome: null as null | boolean, showWelcome: null as null | boolean,
}) });
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const updateState = (value: any) => { const updateState = (value: any) => {
setState((s) => ({ ...s, ...value })) setState((s) => ({ ...s, ...value }));
} };
useEffect(() => { useEffect(() => {
const showWelcome = app.state.showWelcome; const showWelcome = app.state.showWelcome;
@ -25,7 +25,7 @@ export function useWelcome() {
clearWelcome: async () => { clearWelcome: async () => {
await app.actions.setShowWelcome(false); await app.actions.setShowWelcome(false);
}, },
} };
return { state, actions } return { state, actions };
} }