From 8ded9553dd3e0efcffd905699865829d1514d9af Mon Sep 17 00:00:00 2001 From: balzack Date: Wed, 18 Sep 2024 12:51:37 -0700 Subject: [PATCH] rendering profile image in mobile app --- app/client/mobile/src/session/Session.tsx | 7 +- .../mobile/src/settings/Settings.styled.ts | 45 ++++ app/client/mobile/src/settings/Settings.tsx | 24 +- .../mobile/src/settings/useSettings.hook.ts | 235 ++++++++++++++++++ app/sdk/src/identity.ts | 5 + 5 files changed, 307 insertions(+), 9 deletions(-) create mode 100644 app/client/mobile/src/settings/Settings.styled.ts create mode 100644 app/client/mobile/src/settings/useSettings.hook.ts diff --git a/app/client/mobile/src/session/Session.tsx b/app/client/mobile/src/session/Session.tsx index 3b737d8c..91f2c2f7 100644 --- a/app/client/mobile/src/session/Session.tsx +++ b/app/client/mobile/src/session/Session.tsx @@ -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 = () => ; @@ -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' && ( - + )} diff --git a/app/client/mobile/src/settings/Settings.styled.ts b/app/client/mobile/src/settings/Settings.styled.ts new file mode 100644 index 00000000..26a12543 --- /dev/null +++ b/app/client/mobile/src/settings/Settings.styled.ts @@ -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', + }, +}) diff --git a/app/client/mobile/src/settings/Settings.tsx b/app/client/mobile/src/settings/Settings.tsx index a3ba1971..ce383dee 100644 --- a/app/client/mobile/src/settings/Settings.tsx +++ b/app/client/mobile/src/settings/Settings.tsx @@ -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 ( - - + + + + + diff --git a/app/client/mobile/src/settings/useSettings.hook.ts b/app/client/mobile/src/settings/useSettings.hook.ts new file mode 100644 index 00000000..17725aa1 --- /dev/null +++ b/app/client/mobile/src/settings/useSettings.hook.ts @@ -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 } +} diff --git a/app/sdk/src/identity.ts b/app/sdk/src/identity.ts index 5a6bafd8..ede9bbba 100644 --- a/app/sdk/src/identity.ts +++ b/app/sdk/src/identity.ts @@ -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(); }