rendering profile image in mobile app

This commit is contained in:
balzack 2024-09-18 12:51:37 -07:00
parent 5991ec9b0c
commit 8ded9553dd
5 changed files with 307 additions and 9 deletions

View File

@ -1,5 +1,5 @@
import React, {useState, useContext} from 'react';
import {View, SafeAreaView} from 'react-native';
import {View, SafeAreaView, useColorScheme} from 'react-native';
import {styles} from './Session.styled';
import {BottomNavigation, Button, Text} from 'react-native-paper';
import {DisplayContext} from '../context/DisplayContext';
@ -10,7 +10,7 @@ import {Registry} from '../registry/Registry';
import {Profile} from '../profile/Profile';
import {Details} from '../details/Details';
import {NavigationContainer} from '@react-navigation/native';
import {NavigationContainer, DefaultTheme, DarkTheme} from '@react-navigation/native';
import {createDrawerNavigator} from '@react-navigation/drawer';
const ChannelsRoute = () => <Channels />;
@ -24,6 +24,7 @@ const ProfileDrawer = createDrawerNavigator();
const DetailsDrawer = createDrawerNavigator();
export function Session() {
const scheme = useColorScheme();
const display = useContext(DisplayContext);
const [index, setIndex] = useState(0);
const [routes] = useState([
@ -65,7 +66,7 @@ export function Session() {
/>
)}
{display.state.layout === 'large' && (
<NavigationContainer>
<NavigationContainer theme={scheme === 'dark' ? DarkTheme : DefaultTheme}>
<DetailsScreen nav={sessionNav} />
</NavigationContainer>
)}

View File

@ -0,0 +1,45 @@
import {StyleSheet} from 'react-native';
export const styles = StyleSheet.create({
settings: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
padding: 8,
gap: 2,
},
header: {
fontSize: 22,
textAlign: 'center',
textWrap: 'nowrap',
textOverflow: 'ellipsis',
overflow: 'hidden',
},
image: {
position: 'relative',
width: '90%',
maxWidth: 250,
marginTop: 8,
marginBottom: 24,
},
logo: {
aspectRatio: 1,
resizeMode: 'contain',
borderRadius: 8,
width: null,
height: null,
},
editBar: {
position: 'absolute',
top: 0,
width: '100%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
},
editLogo: {
fontSize: 18,
borderRadius: 8,
backgroundColor: '#44444444',
},
})

View File

@ -1,14 +1,26 @@
import React, {useContext} from 'react';
import {AppContext} from '../context/AppContext';
import {Button} from 'react-native-paper';
import {SafeAreaView} from 'react-native';
import {Button, Text} from 'react-native-paper';
import {SafeAreaView, View, Image} from 'react-native';
import {styles} from './Settings.styled';
import {useSettings} from './useSettings.hook';
export function Settings() {
const app = useContext(AppContext);
const { state, actions } = useSettings();
console.log("HERE");
return (
<SafeAreaView>
<Button mode="contained" onPress={app.actions.accountLogout}>
<SafeAreaView style={styles.settings}>
<Text style={styles.header}>{`${state.profile.handle}${state.profile.node ? '/' + state.profile.node : ''}`}</Text>
<View style={styles.image}>
<Image style={styles.logo} resizeMode={'contain'} source={{ uri: state.imageUrl }} />
<View style={styles.editBar}>
<Button style={styles.editLogo} mode="text">{state.strings.edit}</Button>
</View>
</View>
<Button mode="contained" onPress={actions.logout}>
Logout
</Button>
</SafeAreaView>

View File

@ -0,0 +1,235 @@
import { useEffect, useState, useContext, useRef } from 'react'
import { AppContext } from '../context/AppContext'
import { DisplayContext } from '../context/DisplayContext'
import { ContextType } from '../context/ContextType'
const DEBOUNCE_MS = 1000
export function useSettings() {
const display = useContext(DisplayContext) as ContextType
const app = useContext(AppContext) as ContextType
const debounce = useRef(setTimeout(() => {}, 0))
const [state, setState] = useState({
config: {} as Config,
profile: {} as Profile,
profileSet: false,
imageUrl: null,
strings: display.state.strings,
timeFormat: '12h',
dateFormat: 'mm/dd',
all: false,
password: '',
confirm: '',
username: '',
taken: false,
checked: true,
name: '',
description: '',
location: '',
handle: '',
secretText: '',
secretImage: '',
code: '',
sealPassword: '',
sealConfirm: '',
sealDelete: '',
secretCopied: false,
})
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const updateState = (value: any) => {
setState((s) => ({ ...s, ...value }))
}
const getSession = () => {
const session = app.state?.session
const settings = session?.getSettings()
const identity = session?.getIdentity()
if (!settings || !identity) {
console.log('session not set in settings hook')
}
return { settings, identity }
}
useEffect(() => {
const { settings, identity } = getSession()
const setConfig = (config: Config) => {
updateState({ config })
}
settings.addConfigListener(setConfig)
const setProfile = (profile: Profile) => {
const { handle, name, location, description } = profile
const url = identity.getProfileImageUrl()
updateState({
profile,
handle,
name,
location,
description,
imageUrl: url,
profileSet: true,
})
}
identity.addProfileListener(setProfile)
return () => {
settings.removeConfigListener(setConfig)
identity.removeProfileListener(setProfile)
}
}, [])
useEffect(() => {
const {
strings,
dateFormat,
timeFormat,
} = display.state
updateState({
strings,
dateFormat,
timeFormat,
})
}, [display.state])
const actions = {
getUsernameStatus: async (username: string) => {
const { settings } = getSession()
return await settings.getUsernameStatus(username)
},
setLogin: async () => {
const { settings } = getSession()
await settings.setLogin(state.handle, state.password)
},
enableNotifications: async () => {
const { settings } = getSession()
await settings.enableNotifications()
},
disableNotifications: async () => {
const { settings } = getSession()
await settings.disableNotifications()
},
enableRegistry: async () => {
const { settings } = getSession()
await settings.enableRegistry()
},
disableRegistry: async () => {
const { settings } = getSession()
await settings.disableRegistry()
},
enableMFA: async () => {
const { settings } = getSession()
const { secretImage, secretText } = await settings.enableMFA()
updateState({ secretImage, secretText });
},
disableMFA: async () => {
const { settings } = getSession()
await settings.disableMFA()
},
confirmMFA: async () => {
const { settings } = getSession()
await settings.confirmMFA(state.code)
},
setCode: (code: string) => {
updateState({ code });
},
copySecret: () => {
navigator.clipboard.writeText(state.secretText);
updateState({ secretCopied: true });
setTimeout(() => {
updateState({ secretCopied: false });
}, 1000);
},
setSeal: async () => {
const { settings } = getSession()
await settings.setSeal(state.sealPassword)
},
clearSeal: async () => {
const { settings } = getSession()
await settings.clearSeal()
},
unlockSeal: async () => {
const { settings } = getSession()
await settings.unlockSeal(state.sealPassword)
},
forgetSeal: async () => {
const { settings } = getSession()
await settings.forgetSeal()
},
updateSeal: async () => {
const { settings } = getSession();
await settings.updateSeal(state.sealPassword);
},
setProfileData: async (
name: string,
location: string,
description: string
) => {
const { identity } = getSession()
await identity.setProfileData(name, location, description)
},
setProfileImage: async (image: string) => {
const { identity } = getSession()
await identity.setProfileImage(image)
},
getProfileImageUrl: () => {
const { identity } = getSession()
return identity.getProfileImageUrl()
},
setDateFormat: (format: string) => {
display.actions.setDateFormat(format)
},
setTimeFormat: (format: string) => {
display.actions.setTimeFormat(format)
},
setAll: (all: boolean) => {
updateState({ all })
},
logout: async () => {
await app.actions.accountLogout(state.all)
},
setHandle: (handle: string) => {
updateState({ handle, taken: false, checked: false })
clearTimeout(debounce.current)
if (!handle || handle === state.profile.handle) {
updateState({ available: true, checked: true })
} else {
debounce.current = setTimeout(async () => {
const { settings } = getSession()
const available = await settings.getUsernameStatus(handle)
updateState({ taken: !available, checked: true })
}, DEBOUNCE_MS)
}
},
setPassword: (password: string) => {
updateState({ password })
},
setConfirm: (confirm: string) => {
updateState({ confirm })
},
setName: (name: string) => {
updateState({ name })
},
setLocation: (location: string) => {
updateState({ location })
},
setDescription: (description: string) => {
updateState({ description })
},
setDetails: async () => {
const { identity } = getSession()
const { name, location, description } = state
await identity.setProfileData(name, location, description)
},
setSealDelete: (sealDelete: string) => {
updateState({ sealDelete });
},
setSealPassword: (sealPassword: string) => {
updateState({ sealPassword });
},
setSealConfirm: (sealConfirm: string) => {
updateState({ sealConfirm });
},
}
return { state, actions }
}

View File

@ -47,6 +47,11 @@ export class IdentityModule implements Identity {
private async init() {
this.revision = await this.store.getProfileRevision(this.guid);
this.profile = await this.store.getProfileData(this.guid);
if (this.profile.image) {
this.imageUrl = `data:image/png;base64,${this.profile.image}`
} else {
this.imageUrl = avatar
}
this.syncing = false;
await this.sync();
}