Add banner image upload to profiles
parent
22ddfaa2b5
commit
693d6bfd0a
|
@ -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
|
||||
|
|
|
@ -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}>
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue