save profile photos on profile view state (wip)

This commit is contained in:
João Ferreiro
2022-12-06 11:12:13 +00:00
parent a067312f19
commit 8d98c99705
5 changed files with 68 additions and 59 deletions
+21
View File
@@ -6,6 +6,7 @@ import {extractEntities} from '../../lib/strings'
import {Declaration} from './_common'
import {RootStoreModel} from './root-store'
import * as apilib from '../lib/api'
import {Image} from 'react-native-image-crop-picker'
export const ACTOR_TYPE_USER = 'app.bsky.system.actorUser'
export const ACTOR_TYPE_SCENE = 'app.bsky.system.actorScene'
@@ -46,6 +47,10 @@ export class ProfileViewModel {
// added data
descriptionEntities?: Entity[]
// temp state while imagery is not supported by the protocol
userAvatar: Image | null = null
userBanner: Image | null = null
constructor(
public rootStore: RootStoreModel,
params: GetProfile.QueryParams,
@@ -81,6 +86,14 @@ export class ProfileViewModel {
return this.declaration.actorType === ACTOR_TYPE_SCENE
}
get userAvatarPath() {
return this.userAvatar?.path
}
get userBannerPath() {
return this.userBanner?.path
}
// public api
// =
@@ -123,6 +136,14 @@ export class ProfileViewModel {
// state transitions
// =
updateUserAvatar(img: Image) {
this.userAvatar = img
}
updateUserBanner(img: Image) {
this.userBanner = img
}
private _xLoading(isRefreshing = false) {
this.isLoading = true
this.isRefreshing = isRefreshing
+9 -3
View File
@@ -70,14 +70,20 @@ export function Component({
<BottomSheetScrollView style={styles.inner}>
<Text style={styles.title}>Edit my profile</Text>
<View style={styles.photos}>
<UserBanner isEditable isMe handle={profileView.handle} />
<UserBanner
isMe
isEditable
handle={profileView.handle}
profileView={profileView}
/>
<View style={styles.avi}>
<UserAvatar
isEditable
isMe
isEditable
size={80}
displayName={profileView.displayName}
handle={profileView.handle}
profileView={profileView}
displayName={profileView.displayName}
/>
</View>
</View>
+13 -12
View File
@@ -32,15 +32,15 @@ export const ProfileHeader = observer(function ProfileHeader({
const store = useStores()
const isMember = useMemo(
() => view.isScene && view.myState.member,
[view.myState.member],
[view.isScene, view.myState.member],
)
const onPressBack = () => {
store.nav.tab.goBack()
}
const onPressSearch = () => {
store.nav.navigate(`/search`)
}
// const onPressBack = () => {
// store.nav.tab.goBack()
// }
// const onPressSearch = () => {
// store.nav.navigate('/search')
// }
const onPressToggleFollow = () => {
view?.toggleFollowing().then(
() => {
@@ -72,7 +72,7 @@ export const ProfileHeader = observer(function ProfileHeader({
store.shell.openModal(
new ConfirmModel(
'Leave this scene?',
`You'll be able to come back unless your invite is revoked.`,
"You'll be able to come back unless your invite is revoked.",
onPressConfirmLeaveScene,
),
)
@@ -83,7 +83,7 @@ export const ProfileHeader = observer(function ProfileHeader({
did: store.me.did || '',
rkey: new AtUri(view.myState.member).rkey,
})
Toast.show(`Scene left`)
Toast.show('Scene left')
}
onRefreshAll()
}
@@ -152,13 +152,14 @@ export const ProfileHeader = observer(function ProfileHeader({
}
return (
<View style={styles.outer}>
<UserBanner isMe={isMe} handle={view.handle} />
<UserBanner isMe={isMe} handle={view.handle} profileView={view} />
<View style={styles.avi}>
<UserAvatar
isMe={isMe}
size={80}
displayName={view.displayName}
isMe={isMe}
handle={view.handle}
displayName={view.displayName}
profileView={view}
/>
</View>
<View style={styles.content}>
+14 -22
View File
@@ -1,4 +1,4 @@
import React, {useCallback, useEffect, useState} from 'react'
import React, {useCallback} from 'react'
import {StyleSheet, View, TouchableOpacity, Alert, Image} from 'react-native'
import Svg, {Circle, Text, Defs, LinearGradient, Stop} from 'react-native-svg'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
@@ -10,11 +10,12 @@ import {
import {getGradient} from '../../lib/asset-gen'
import {colors} from '../../lib/styles'
import {IMAGES_ENABLED} from '../../../build-flags'
import {loadString, saveString} from '../../../state/lib/storage'
import {ProfileViewModel} from '../../../state/models/profile-view'
export function UserAvatar({
size,
handle,
profileView,
displayName,
isMe = false,
isEditable = false,
@@ -24,23 +25,11 @@ export function UserAvatar({
isMe?: boolean
isEditable?: boolean
displayName: string | undefined
profileView: ProfileViewModel
}) {
const initials = getInitials(displayName || handle)
const gradient = getGradient(handle)
const [userAvatarImage, setUserAvatarImage] = useState<string | null>(null)
useEffect(() => {
async function loadLocalImage() {
if (isMe) {
await loadString('userAvatarImage').then(uri => {
setUserAvatarImage(uri)
})
}
}
loadLocalImage()
}, [isMe])
const handleEditAvatar = useCallback(() => {
Alert.alert('Select upload method', '', [
{
@@ -53,7 +42,7 @@ export function UserAvatar({
height: 80,
cropperCircleOverlay: true,
}).then(item => {
saveString('userAvatarImage', item.path)
profileView.updateUserAvatar(item)
})
},
},
@@ -70,13 +59,13 @@ export function UserAvatar({
height: 80,
cropperCircleOverlay: true,
}).then(croppedItem => {
saveString('userAvatarImage', croppedItem.path)
profileView.updateUserAvatar(croppedItem)
})
})
},
},
])
}, [])
}, [profileView])
const renderSvg = (size: number, initials: string) => (
<Svg width={size} height={size} viewBox="0 0 100 100">
@@ -102,8 +91,11 @@ export function UserAvatar({
return isEditable && IMAGES_ENABLED ? (
<TouchableOpacity onPress={handleEditAvatar}>
{/* Added a react state temporary photo while the protocol does not support imagery */}
{userAvatarImage != null ? (
<Image style={styles.avatarImage} source={{uri: userAvatarImage}} />
{profileView.userAvatar != null ? (
<Image
style={styles.avatarImage}
source={{uri: profileView.userAvatarPath}}
/>
) : (
renderSvg(size, initials)
)}
@@ -115,11 +107,11 @@ export function UserAvatar({
/>
</View>
</TouchableOpacity>
) : isMe && userAvatarImage != null ? (
) : isMe && profileView.userAvatar != null ? (
<Image
style={styles.avatarImage}
resizeMode="stretch"
source={{uri: userAvatarImage}}
source={{uri: profileView.userAvatarPath}}
/>
) : (
renderSvg(size, initials)
+11 -22
View File
@@ -1,4 +1,4 @@
import React, {useCallback, useEffect, useState} from 'react'
import React, {useCallback} from 'react'
import {StyleSheet, View, TouchableOpacity, Alert, Image} from 'react-native'
import Svg, {Rect, Defs, LinearGradient, Stop} from 'react-native-svg'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
@@ -10,32 +10,21 @@ import {
openPicker,
} from 'react-native-image-crop-picker'
import {IMAGES_ENABLED} from '../../../build-flags'
import {saveString, loadString} from '../../../state/lib/storage'
import {ProfileViewModel} from '../../../state/models/profile-view'
export function UserBanner({
handle,
profileView,
isMe = false,
isEditable = false,
}: {
handle: string
isMe?: boolean
profileView: ProfileViewModel
isEditable?: boolean
}) {
const gradient = getGradient(handle)
const [userBannerImage, setUserBannerImage] = useState<string | null>(null)
useEffect(() => {
async function loadLocalImage() {
if (isMe) {
await loadString('userBannerImage').then(uri => {
setUserBannerImage(uri)
})
}
}
loadLocalImage()
}, [isMe])
const handleEditBanner = useCallback(() => {
Alert.alert('Select upload method', '', [
{
@@ -47,7 +36,7 @@ export function UserBanner({
width: 1500,
height: 500,
}).then(item => {
saveString('userBannerImage', item.path)
profileView.updateUserBanner(item)
})
},
},
@@ -63,13 +52,13 @@ export function UserBanner({
width: 1500,
height: 500,
}).then(croppedItem => {
saveString('userBannerImage', croppedItem.path)
profileView.updateUserBanner(croppedItem)
})
})
},
},
])
}, [])
}, [profileView])
const renderSvg = () => (
<Svg width="100%" height="120" viewBox="50 0 200 100">
@@ -91,11 +80,11 @@ export function UserBanner({
return isEditable && IMAGES_ENABLED ? (
<TouchableOpacity onPress={handleEditBanner}>
{/* Added a react state temporary photo while the protocol does not support imagery */}
{userBannerImage != null ? (
{profileView.userBanner != null ? (
<Image
style={styles.bannerImage}
resizeMode="stretch"
source={{uri: userBannerImage}}
source={{uri: profileView.userBannerPath}}
/>
) : (
renderSvg()
@@ -108,11 +97,11 @@ export function UserBanner({
/>
</View>
</TouchableOpacity>
) : isMe && userBannerImage != null ? (
) : isMe && profileView.userBanner != null ? (
<Image
style={styles.bannerImage}
resizeMode="stretch"
source={{uri: userBannerImage}}
source={{uri: profileView.userBannerPath}}
/>
) : (
renderSvg()