Add avatar images and fix some type signatures

This commit is contained in:
Paul Frazee 2022-12-08 13:45:23 -06:00
parent 273e6d2973
commit 539bf5d350
56 changed files with 543 additions and 370 deletions

View file

@ -205,6 +205,7 @@ export const ComposePost = observer(function ComposePost({
<UserAvatar
handle={replyTo.author.handle}
displayName={replyTo.author.displayName}
avatar={replyTo.author.avatar}
size={50}
/>
<View style={styles.replyToPost}>
@ -223,6 +224,7 @@ export const ComposePost = observer(function ComposePost({
<UserAvatar
handle={store.me.handle || ''}
displayName={store.me.displayName}
avatar={store.me.avatar}
size={50}
/>
<TextInput

View file

@ -29,6 +29,7 @@ export function ComposePrompt({
size={50}
handle={store.me.handle || ''}
displayName={store.me.displayName}
avatar={store.me.avatar}
/>
</TouchableOpacity>
) : undefined}

View file

@ -149,6 +149,7 @@ const User = ({
size={40}
displayName={item.displayName}
handle={item.handle}
avatar={item.avatar}
/>
</View>
<View style={styles.actorContent}>

View file

@ -1,8 +1,10 @@
import React, {useState} from 'react'
import {ComAtprotoBlobUpload} from '../../../third-party/api/index'
import * as Toast from '../util/Toast'
import {StyleSheet, Text, TouchableOpacity, View} from 'react-native'
import LinearGradient from 'react-native-linear-gradient'
import {BottomSheetScrollView, BottomSheetTextInput} from '@gorhom/bottom-sheet'
import {Image as PickedImage} from 'react-native-image-crop-picker'
import {ErrorMessage} from '../util/ErrorMessage'
import {useStores} from '../../../state'
import {ProfileViewModel} from '../../../state/models/profile-view'
@ -12,7 +14,6 @@ import {
MAX_DISPLAY_NAME,
MAX_DESCRIPTION,
} from '../../../lib/strings'
import * as Profile from '../../../third-party/api/src/client/types/app/bsky/actor/profile'
import {UserBanner} from '../util/UserBanner'
import {UserAvatar} from '../util/UserAvatar'
@ -36,40 +37,44 @@ export function Component({
const [userBanner, setUserBanner] = useState<string | null>(
profileView.userBanner,
)
const [userAvatar, setUserAvatar] = useState<string | null>(
profileView.userAvatar,
const [userAvatar, setUserAvatar] = useState<string | undefined>(
profileView.avatar,
)
const [newUserAvatar, setNewUserAvatar] = useState<PickedImage | undefined>()
const onPressCancel = () => {
store.shell.closeModal()
}
const onSelectNewAvatar = (img: PickedImage) => {
console.log(img)
setNewUserAvatar(img)
setUserAvatar(img.path)
}
const onPressSave = async () => {
if (error) {
setError('')
}
try {
await profileView.updateProfile(
(existing?: Profile.Record): Profile.Record => {
if (existing) {
existing.displayName = displayName
existing.description = description
return existing
}
return {
displayName,
description,
}
{
displayName,
description,
},
userAvatar, // TEMP
newUserAvatar,
userBanner, // TEMP
)
Toast.show('Profile updated')
onUpdate?.()
store.shell.closeModal()
} catch (e: any) {
console.error(e)
setError(
'Failed to save your profile. Check your internet connection and try again.',
)
if (e instanceof ComAtprotoBlobUpload.InvalidBlobError) {
setError(e.message)
} else {
// TODO replace when error detection is correct
setError(e.message)
// setError(
// 'Failed to save your profile. Check your internet connection and try again.',
// )
}
}
}
@ -86,15 +91,15 @@ export function Component({
<View style={styles.avi}>
<UserAvatar
size={80}
userAvatar={userAvatar}
avatar={userAvatar}
handle={profileView.handle}
setUserAvatar={setUserAvatar}
onSelectNewAvatar={onSelectNewAvatar}
displayName={profileView.displayName}
/>
</View>
</View>
{error !== '' && (
<View style={s.mb10}>
<View style={{marginTop: 20}}>
<ErrorMessage message={error} />
</View>
)}

View file

@ -130,6 +130,7 @@ export const Component = observer(function Component({
did={item.did}
handle={item.handle}
displayName={item.displayName}
avatar={item.avatar}
renderButton={() =>
!createdInvite ? (
<>
@ -162,6 +163,7 @@ export const Component = observer(function Component({
did={item.subject.did}
handle={item.subject.handle}
displayName={item.subject.displayName}
avatar={item.subject.avatar}
renderButton={() => (
<>
<FontAwesomeIcon icon="x" style={[s.mr5]} size={14} />

View file

@ -139,6 +139,7 @@ export const FeedItem = observer(function FeedItem({
size={30}
displayName={author.displayName}
handle={author.handle}
avatar={author.avatar}
/>
</Link>
))}

View file

@ -26,6 +26,7 @@ export function InviteAccepter({item}: {item: NotificationsViewItemModel}) {
did={item.author.did}
handle={item.author.handle}
displayName={item.author.displayName}
avatar={item.author.avatar}
/>
</View>
</View>

View file

@ -93,6 +93,7 @@ const RepostedByItem = ({item}: {item: RepostedByViewItemModel}) => {
size={40}
displayName={item.displayName}
handle={item.handle}
avatar={item.avatar}
/>
</View>
<View style={styles.layoutContent}>

View file

@ -61,6 +61,7 @@ export const PostThreadItem = observer(function PostThreadItem({
author: {
handle: item.author.handle,
displayName: item.author.displayName,
avatar: item.author.avatar,
},
},
onPost: onPostReply,
@ -113,6 +114,7 @@ export const PostThreadItem = observer(function PostThreadItem({
size={50}
displayName={item.author.displayName}
handle={item.author.handle}
avatar={item.author.avatar}
/>
</Link>
</View>
@ -236,6 +238,7 @@ export const PostThreadItem = observer(function PostThreadItem({
<UserAvatar
handle={item.replyingTo.author.handle}
displayName={item.replyingTo.author.displayName}
avatar={item.replyingTo.author.avatar}
size={30}
/>
</View>
@ -251,6 +254,7 @@ export const PostThreadItem = observer(function PostThreadItem({
size={50}
displayName={item.author.displayName}
handle={item.author.handle}
avatar={item.author.avatar}
/>
</Link>
</View>

View file

@ -93,6 +93,7 @@ const LikedByItem = ({item}: {item: VotesViewItemModel}) => {
size={40}
displayName={item.actor.displayName}
handle={item.actor.handle}
avatar={item.actor.avatar}
/>
</View>
<View style={styles.layoutContent}>

View file

@ -97,6 +97,7 @@ export const Post = observer(function Post({
author: {
handle: item.author.handle,
displayName: item.author.displayName,
avatar: item.author.avatar,
},
},
})
@ -137,6 +138,7 @@ export const Post = observer(function Post({
size={50}
displayName={item.author.displayName}
handle={item.author.handle}
avatar={item.author.avatar}
/>
</Link>
</View>

View file

@ -54,6 +54,7 @@ export const FeedItem = observer(function FeedItem({
author: {
handle: item.author.handle,
displayName: item.author.displayName,
avatar: item.author.avatar,
},
},
})
@ -139,6 +140,7 @@ export const FeedItem = observer(function FeedItem({
displayName={
item.additionalParentPost?.thread?.author.displayName
}
avatar={item.additionalParentPost?.thread?.author.avatar}
size={32}
/>
</View>
@ -159,6 +161,7 @@ export const FeedItem = observer(function FeedItem({
size={item._isThreadChild ? 30 : 50}
displayName={item.author.displayName}
handle={item.author.handle}
avatar={item.author.avatar}
/>
</Link>
</View>

View file

@ -8,14 +8,14 @@ export function ProfileCard({
did,
handle,
displayName,
description,
avatar,
renderButton,
onPressButton,
}: {
did: string
handle: string
displayName?: string
description?: string
avatar?: string
renderButton?: () => JSX.Element
onPressButton?: () => void
}) {
@ -23,7 +23,12 @@ export function ProfileCard({
<Link style={styles.outer} href={`/profile/${handle}`} title={handle}>
<View style={styles.layout}>
<View style={styles.layoutAvi}>
<UserAvatar size={40} displayName={displayName} handle={handle} />
<UserAvatar
size={40}
displayName={displayName}
handle={handle}
avatar={avatar}
/>
</View>
<View style={styles.layoutContent}>
<Text style={[s.f16, s.bold]} numberOfLines={1}>

View file

@ -91,6 +91,7 @@ const User = ({item}: {item: FollowerItem}) => {
size={40}
displayName={item.displayName}
handle={item.handle}
avatar={item.avatar}
/>
</View>
<View style={styles.layoutContent}>

View file

@ -91,6 +91,7 @@ const User = ({item}: {item: FollowItem}) => {
size={40}
displayName={item.displayName}
handle={item.handle}
avatar={item.avatar}
/>
</View>
<View style={styles.layoutContent}>

View file

@ -158,7 +158,7 @@ export const ProfileHeader = observer(function ProfileHeader({
size={80}
handle={view.handle}
displayName={view.displayName}
userAvatar={view.userAvatar}
avatar={view.avatar}
/>
</View>
<View style={styles.content}>

View file

@ -65,6 +65,7 @@ export const ProfileMembers = observer(function ProfileMembers({
did={item.did}
handle={item.handle}
displayName={item.displayName}
avatar={item.avatar}
/>
)
return (

View file

@ -6,23 +6,23 @@ import {
openCamera,
openCropper,
openPicker,
Image as PickedImage,
} from 'react-native-image-crop-picker'
import {getGradient} from '../../lib/asset-gen'
import {colors} from '../../lib/styles'
import {IMAGES_ENABLED} from '../../../build-flags'
export function UserAvatar({
size,
handle,
userAvatar,
avatar,
displayName,
setUserAvatar,
onSelectNewAvatar,
}: {
size: number
handle: string
displayName: string | undefined
userAvatar?: string | null
setUserAvatar?: React.Dispatch<React.SetStateAction<string | null>>
avatar?: string | null
onSelectNewAvatar?: (img: PickedImage) => void
}) {
const initials = getInitials(displayName || handle)
const gradient = getGradient(handle)
@ -35,14 +35,12 @@ export function UserAvatar({
openCamera({
mediaType: 'photo',
cropping: true,
width: 80,
height: 80,
width: 400,
height: 400,
cropperCircleOverlay: true,
}).then(item => {
if (setUserAvatar != null) {
setUserAvatar(item.path)
}
})
forceJpg: true, // ios only
compressImageQuality: 0.7,
}).then(onSelectNewAvatar)
},
},
{
@ -54,19 +52,17 @@ export function UserAvatar({
await openCropper({
mediaType: 'photo',
path: item.path,
width: 80,
height: 80,
width: 400,
height: 400,
cropperCircleOverlay: true,
}).then(croppedItem => {
if (setUserAvatar != null) {
setUserAvatar(croppedItem.path)
}
})
forceJpg: true, // ios only
compressImageQuality: 0.7,
}).then(onSelectNewAvatar)
})
},
},
])
}, [setUserAvatar])
}, [onSelectNewAvatar])
const renderSvg = (size: number, initials: string) => (
<Svg width={size} height={size} viewBox="0 0 100 100">
@ -89,11 +85,14 @@ export function UserAvatar({
</Svg>
)
// setUserAvatar is only passed as prop on the EditProfile component
return setUserAvatar != null && IMAGES_ENABLED ? (
// onSelectNewAvatar is only passed as prop on the EditProfile component
return onSelectNewAvatar ? (
<TouchableOpacity onPress={handleEditAvatar}>
{userAvatar ? (
<Image style={styles.avatarImage} source={{uri: userAvatar}} />
{avatar ? (
<Image
style={{width: size, height: size, borderRadius: (size / 2) | 0}}
source={{uri: avatar}}
/>
) : (
renderSvg(size, initials)
)}
@ -105,11 +104,11 @@ export function UserAvatar({
/>
</View>
</TouchableOpacity>
) : userAvatar ? (
) : avatar ? (
<Image
style={styles.avatarImage}
style={{width: size, height: size, borderRadius: (size / 2) | 0}}
resizeMode="stretch"
source={{uri: userAvatar}}
source={{uri: avatar}}
/>
) : (
renderSvg(size, initials)

View file

@ -103,6 +103,7 @@ export const Menu = ({navIdx, visible}: ScreenParams) => {
size={24}
displayName={store.me.displayName}
handle={store.me.handle}
avatar={store.me.avatar}
/>
}
label={store.me.displayName || store.me.handle}
@ -163,6 +164,7 @@ export const Menu = ({navIdx, visible}: ScreenParams) => {
size={24}
displayName={membership.displayName}
handle={membership.handle}
avatar={membership.avatar}
/>
}
label={membership.displayName || membership.handle}

View file

@ -165,6 +165,7 @@ export const Profile = observer(({navIdx, visible, params}: ScreenParams) => {
did={item.did}
handle={item.handle}
displayName={item.displayName}
avatar={item.avatar}
/>
)
}
@ -199,6 +200,7 @@ export const Profile = observer(({navIdx, visible, params}: ScreenParams) => {
did={item.did}
handle={item.handle}
displayName={item.displayName}
avatar={item.avatar}
renderButton={renderButton}
onPressButton={() => onPressRemoveMember(item)}
/>

View file

@ -74,6 +74,7 @@ export const Search = ({navIdx, visible, params}: ScreenParams) => {
<UserAvatar
handle={item.handle}
displayName={item.displayName}
avatar={item.avatar}
size={36}
/>
<View style={[s.ml10]}>

View file

@ -42,6 +42,7 @@ export const Settings = observer(function Settings({
size={40}
displayName={store.me.displayName}
handle={store.me.handle || ''}
avatar={store.me.avatar}
/>
<View style={[s.ml10]}>
<Text style={[s.f18]}>