Add banner image upload to profiles

zio/stable
Paul Frazee 2022-12-12 13:47:18 -06:00
parent 22ddfaa2b5
commit 693d6bfd0a
4 changed files with 50 additions and 35 deletions

View File

@ -39,15 +39,13 @@ export class ProfileViewModel {
displayName?: string
description?: string
avatar?: string
banner?: string
followersCount: number = 0
followsCount: number = 0
membersCount: number = 0
postsCount: number = 0
myState = new ProfileViewMyStateModel()
// TODO TEMP data to be implemented in the protocol
userBanner: string | null = null
// added data
descriptionEntities?: Entity[]
@ -123,11 +121,8 @@ export class ProfileViewModel {
async updateProfile(
updates: Profile.Record,
newUserAvatar: PickedImage | undefined,
userBanner: string | null, // TODO TEMP
newUserBanner: PickedImage | undefined,
) {
// TODO TEMP add userBanner to the protocol when suported
this.userBanner = userBanner
if (newUserAvatar) {
const res = await this.rootStore.api.com.atproto.blob.upload(
newUserAvatar.path, // this will be special-cased by the fetch monkeypatch in /src/state/lib/api.ts
@ -140,6 +135,18 @@ export class ProfileViewModel {
mimeType: newUserAvatar.mime,
}
}
if (newUserBanner) {
const res = await this.rootStore.api.com.atproto.blob.upload(
newUserBanner.path, // this will be special-cased by the fetch monkeypatch in /src/state/lib/api.ts
{
encoding: newUserBanner.mime,
},
)
updates.banner = {
cid: res.data.cid,
mimeType: newUserBanner.mime,
}
}
await this.rootStore.api.app.bsky.actor.updateProfile(updates)
await this.rootStore.me.load()
await this.refresh()
@ -187,6 +194,7 @@ export class ProfileViewModel {
this.displayName = res.data.displayName
this.description = res.data.description
this.avatar = res.data.avatar
this.banner = res.data.banner
this.followersCount = res.data.followersCount
this.followsCount = res.data.followsCount
this.membersCount = res.data.membersCount

View File

@ -41,12 +41,13 @@ export function Component({
const [description, setDescription] = useState<string>(
profileView.description || '',
)
const [userBanner, setUserBanner] = useState<string | null>(
profileView.userBanner,
const [userBanner, setUserBanner] = useState<string | undefined>(
profileView.banner,
)
const [userAvatar, setUserAvatar] = useState<string | undefined>(
profileView.avatar,
)
const [newUserBanner, setNewUserBanner] = useState<PickedImage | undefined>()
const [newUserAvatar, setNewUserAvatar] = useState<PickedImage | undefined>()
const onPressCancel = () => {
store.shell.closeModal()
@ -55,6 +56,10 @@ export function Component({
setNewUserAvatar(img)
setUserAvatar(img.path)
}
const onSelectNewBanner = (img: PickedImage) => {
setNewUserBanner(img)
setUserBanner(img.path)
}
const onPressSave = async () => {
setProcessing(true)
if (error) {
@ -67,7 +72,7 @@ export function Component({
description,
},
newUserAvatar,
userBanner, // TEMP
newUserBanner,
)
Toast.show('Profile updated')
onUpdate?.()
@ -90,8 +95,8 @@ export function Component({
<Text style={styles.title}>Edit my profile</Text>
<View style={styles.photos}>
<UserBanner
userBanner={userBanner}
setUserBanner={setUserBanner}
banner={userBanner}
onSelectNewBanner={onSelectNewBanner}
handle={profileView.handle}
/>
<View style={styles.avi}>

View File

@ -152,7 +152,7 @@ export const ProfileHeader = observer(function ProfileHeader({
}
return (
<View style={styles.outer}>
<UserBanner handle={view.handle} userBanner={view.userBanner} />
<UserBanner handle={view.handle} banner={view.banner} />
<View style={styles.avi}>
<UserAvatar
size={80}

View File

@ -2,6 +2,7 @@ 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'
import {Image as PickedImage} from 'react-native-image-crop-picker'
import {getGradient} from '../../lib/asset-gen'
import {colors} from '../../lib/styles'
import {
@ -9,16 +10,15 @@ import {
openCropper,
openPicker,
} from 'react-native-image-crop-picker'
import {IMAGES_ENABLED} from '../../../build-flags'
export function UserBanner({
handle,
userBanner,
setUserBanner,
banner,
onSelectNewBanner,
}: {
handle: string
userBanner?: string | null
setUserBanner?: React.Dispatch<React.SetStateAction<string | null>>
banner?: string | null
onSelectNewBanner?: (img: PickedImage) => void
}) {
const gradient = getGradient(handle)
@ -30,13 +30,14 @@ export function UserBanner({
openCamera({
mediaType: 'photo',
cropping: true,
compressImageMaxWidth: 1500,
width: 1500,
compressImageMaxHeight: 500,
height: 500,
}).then(item => {
if (setUserBanner != null) {
setUserBanner(item.path)
}
})
forceJpg: true, // ios only
compressImageQuality: 0.4,
includeExif: true,
}).then(onSelectNewBanner)
},
},
{
@ -48,18 +49,19 @@ export function UserBanner({
await openCropper({
mediaType: 'photo',
path: item.path,
compressImageMaxWidth: 1500,
width: 1500,
compressImageMaxHeight: 500,
height: 500,
}).then(croppedItem => {
if (setUserBanner != null) {
setUserBanner(croppedItem.path)
}
})
forceJpg: true, // ios only
compressImageQuality: 0.4,
includeExif: true,
}).then(onSelectNewBanner)
})
},
},
])
}, [setUserBanner])
}, [onSelectNewBanner])
const renderSvg = () => (
<Svg width="100%" height="120" viewBox="50 0 200 100">
@ -79,10 +81,10 @@ export function UserBanner({
)
// setUserBanner is only passed as prop on the EditProfile component
return setUserBanner != null && IMAGES_ENABLED ? (
return onSelectNewBanner ? (
<TouchableOpacity onPress={handleEditBanner}>
{userBanner ? (
<Image style={styles.bannerImage} source={{uri: userBanner}} />
{banner ? (
<Image style={styles.bannerImage} source={{uri: banner}} />
) : (
renderSvg()
)}
@ -94,11 +96,11 @@ export function UserBanner({
/>
</View>
</TouchableOpacity>
) : userBanner ? (
) : banner ? (
<Image
style={styles.bannerImage}
resizeMode="stretch"
source={{uri: userBanner}}
resizeMode="cover"
source={{uri: banner}}
/>
) : (
renderSvg()