Add banner image upload to profiles
parent
22ddfaa2b5
commit
693d6bfd0a
|
@ -39,15 +39,13 @@ export class ProfileViewModel {
|
||||||
displayName?: string
|
displayName?: string
|
||||||
description?: string
|
description?: string
|
||||||
avatar?: string
|
avatar?: string
|
||||||
|
banner?: string
|
||||||
followersCount: number = 0
|
followersCount: number = 0
|
||||||
followsCount: number = 0
|
followsCount: number = 0
|
||||||
membersCount: number = 0
|
membersCount: number = 0
|
||||||
postsCount: number = 0
|
postsCount: number = 0
|
||||||
myState = new ProfileViewMyStateModel()
|
myState = new ProfileViewMyStateModel()
|
||||||
|
|
||||||
// TODO TEMP data to be implemented in the protocol
|
|
||||||
userBanner: string | null = null
|
|
||||||
|
|
||||||
// added data
|
// added data
|
||||||
descriptionEntities?: Entity[]
|
descriptionEntities?: Entity[]
|
||||||
|
|
||||||
|
@ -123,11 +121,8 @@ export class ProfileViewModel {
|
||||||
async updateProfile(
|
async updateProfile(
|
||||||
updates: Profile.Record,
|
updates: Profile.Record,
|
||||||
newUserAvatar: PickedImage | undefined,
|
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) {
|
if (newUserAvatar) {
|
||||||
const res = await this.rootStore.api.com.atproto.blob.upload(
|
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
|
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,
|
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.api.app.bsky.actor.updateProfile(updates)
|
||||||
await this.rootStore.me.load()
|
await this.rootStore.me.load()
|
||||||
await this.refresh()
|
await this.refresh()
|
||||||
|
@ -187,6 +194,7 @@ export class ProfileViewModel {
|
||||||
this.displayName = res.data.displayName
|
this.displayName = res.data.displayName
|
||||||
this.description = res.data.description
|
this.description = res.data.description
|
||||||
this.avatar = res.data.avatar
|
this.avatar = res.data.avatar
|
||||||
|
this.banner = res.data.banner
|
||||||
this.followersCount = res.data.followersCount
|
this.followersCount = res.data.followersCount
|
||||||
this.followsCount = res.data.followsCount
|
this.followsCount = res.data.followsCount
|
||||||
this.membersCount = res.data.membersCount
|
this.membersCount = res.data.membersCount
|
||||||
|
|
|
@ -41,12 +41,13 @@ export function Component({
|
||||||
const [description, setDescription] = useState<string>(
|
const [description, setDescription] = useState<string>(
|
||||||
profileView.description || '',
|
profileView.description || '',
|
||||||
)
|
)
|
||||||
const [userBanner, setUserBanner] = useState<string | null>(
|
const [userBanner, setUserBanner] = useState<string | undefined>(
|
||||||
profileView.userBanner,
|
profileView.banner,
|
||||||
)
|
)
|
||||||
const [userAvatar, setUserAvatar] = useState<string | undefined>(
|
const [userAvatar, setUserAvatar] = useState<string | undefined>(
|
||||||
profileView.avatar,
|
profileView.avatar,
|
||||||
)
|
)
|
||||||
|
const [newUserBanner, setNewUserBanner] = useState<PickedImage | undefined>()
|
||||||
const [newUserAvatar, setNewUserAvatar] = useState<PickedImage | undefined>()
|
const [newUserAvatar, setNewUserAvatar] = useState<PickedImage | undefined>()
|
||||||
const onPressCancel = () => {
|
const onPressCancel = () => {
|
||||||
store.shell.closeModal()
|
store.shell.closeModal()
|
||||||
|
@ -55,6 +56,10 @@ export function Component({
|
||||||
setNewUserAvatar(img)
|
setNewUserAvatar(img)
|
||||||
setUserAvatar(img.path)
|
setUserAvatar(img.path)
|
||||||
}
|
}
|
||||||
|
const onSelectNewBanner = (img: PickedImage) => {
|
||||||
|
setNewUserBanner(img)
|
||||||
|
setUserBanner(img.path)
|
||||||
|
}
|
||||||
const onPressSave = async () => {
|
const onPressSave = async () => {
|
||||||
setProcessing(true)
|
setProcessing(true)
|
||||||
if (error) {
|
if (error) {
|
||||||
|
@ -67,7 +72,7 @@ export function Component({
|
||||||
description,
|
description,
|
||||||
},
|
},
|
||||||
newUserAvatar,
|
newUserAvatar,
|
||||||
userBanner, // TEMP
|
newUserBanner,
|
||||||
)
|
)
|
||||||
Toast.show('Profile updated')
|
Toast.show('Profile updated')
|
||||||
onUpdate?.()
|
onUpdate?.()
|
||||||
|
@ -90,8 +95,8 @@ export function Component({
|
||||||
<Text style={styles.title}>Edit my profile</Text>
|
<Text style={styles.title}>Edit my profile</Text>
|
||||||
<View style={styles.photos}>
|
<View style={styles.photos}>
|
||||||
<UserBanner
|
<UserBanner
|
||||||
userBanner={userBanner}
|
banner={userBanner}
|
||||||
setUserBanner={setUserBanner}
|
onSelectNewBanner={onSelectNewBanner}
|
||||||
handle={profileView.handle}
|
handle={profileView.handle}
|
||||||
/>
|
/>
|
||||||
<View style={styles.avi}>
|
<View style={styles.avi}>
|
||||||
|
|
|
@ -152,7 +152,7 @@ export const ProfileHeader = observer(function ProfileHeader({
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<View style={styles.outer}>
|
<View style={styles.outer}>
|
||||||
<UserBanner handle={view.handle} userBanner={view.userBanner} />
|
<UserBanner handle={view.handle} banner={view.banner} />
|
||||||
<View style={styles.avi}>
|
<View style={styles.avi}>
|
||||||
<UserAvatar
|
<UserAvatar
|
||||||
size={80}
|
size={80}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import React, {useCallback} from 'react'
|
||||||
import {StyleSheet, View, TouchableOpacity, Alert, Image} from 'react-native'
|
import {StyleSheet, View, TouchableOpacity, Alert, Image} from 'react-native'
|
||||||
import Svg, {Rect, Defs, LinearGradient, Stop} from 'react-native-svg'
|
import Svg, {Rect, Defs, LinearGradient, Stop} from 'react-native-svg'
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||||
|
import {Image as PickedImage} from 'react-native-image-crop-picker'
|
||||||
import {getGradient} from '../../lib/asset-gen'
|
import {getGradient} from '../../lib/asset-gen'
|
||||||
import {colors} from '../../lib/styles'
|
import {colors} from '../../lib/styles'
|
||||||
import {
|
import {
|
||||||
|
@ -9,16 +10,15 @@ import {
|
||||||
openCropper,
|
openCropper,
|
||||||
openPicker,
|
openPicker,
|
||||||
} from 'react-native-image-crop-picker'
|
} from 'react-native-image-crop-picker'
|
||||||
import {IMAGES_ENABLED} from '../../../build-flags'
|
|
||||||
|
|
||||||
export function UserBanner({
|
export function UserBanner({
|
||||||
handle,
|
handle,
|
||||||
userBanner,
|
banner,
|
||||||
setUserBanner,
|
onSelectNewBanner,
|
||||||
}: {
|
}: {
|
||||||
handle: string
|
handle: string
|
||||||
userBanner?: string | null
|
banner?: string | null
|
||||||
setUserBanner?: React.Dispatch<React.SetStateAction<string | null>>
|
onSelectNewBanner?: (img: PickedImage) => void
|
||||||
}) {
|
}) {
|
||||||
const gradient = getGradient(handle)
|
const gradient = getGradient(handle)
|
||||||
|
|
||||||
|
@ -30,13 +30,14 @@ export function UserBanner({
|
||||||
openCamera({
|
openCamera({
|
||||||
mediaType: 'photo',
|
mediaType: 'photo',
|
||||||
cropping: true,
|
cropping: true,
|
||||||
|
compressImageMaxWidth: 1500,
|
||||||
width: 1500,
|
width: 1500,
|
||||||
|
compressImageMaxHeight: 500,
|
||||||
height: 500,
|
height: 500,
|
||||||
}).then(item => {
|
forceJpg: true, // ios only
|
||||||
if (setUserBanner != null) {
|
compressImageQuality: 0.4,
|
||||||
setUserBanner(item.path)
|
includeExif: true,
|
||||||
}
|
}).then(onSelectNewBanner)
|
||||||
})
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -48,18 +49,19 @@ export function UserBanner({
|
||||||
await openCropper({
|
await openCropper({
|
||||||
mediaType: 'photo',
|
mediaType: 'photo',
|
||||||
path: item.path,
|
path: item.path,
|
||||||
|
compressImageMaxWidth: 1500,
|
||||||
width: 1500,
|
width: 1500,
|
||||||
|
compressImageMaxHeight: 500,
|
||||||
height: 500,
|
height: 500,
|
||||||
}).then(croppedItem => {
|
forceJpg: true, // ios only
|
||||||
if (setUserBanner != null) {
|
compressImageQuality: 0.4,
|
||||||
setUserBanner(croppedItem.path)
|
includeExif: true,
|
||||||
}
|
}).then(onSelectNewBanner)
|
||||||
})
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
}, [setUserBanner])
|
}, [onSelectNewBanner])
|
||||||
|
|
||||||
const renderSvg = () => (
|
const renderSvg = () => (
|
||||||
<Svg width="100%" height="120" viewBox="50 0 200 100">
|
<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
|
// setUserBanner is only passed as prop on the EditProfile component
|
||||||
return setUserBanner != null && IMAGES_ENABLED ? (
|
return onSelectNewBanner ? (
|
||||||
<TouchableOpacity onPress={handleEditBanner}>
|
<TouchableOpacity onPress={handleEditBanner}>
|
||||||
{userBanner ? (
|
{banner ? (
|
||||||
<Image style={styles.bannerImage} source={{uri: userBanner}} />
|
<Image style={styles.bannerImage} source={{uri: banner}} />
|
||||||
) : (
|
) : (
|
||||||
renderSvg()
|
renderSvg()
|
||||||
)}
|
)}
|
||||||
|
@ -94,11 +96,11 @@ export function UserBanner({
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
) : userBanner ? (
|
) : banner ? (
|
||||||
<Image
|
<Image
|
||||||
style={styles.bannerImage}
|
style={styles.bannerImage}
|
||||||
resizeMode="stretch"
|
resizeMode="cover"
|
||||||
source={{uri: userBanner}}
|
source={{uri: banner}}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
renderSvg()
|
renderSvg()
|
||||||
|
|
Loading…
Reference in New Issue