Add profile image lightbox

zio/stable
Paul Frazee 2022-12-12 14:54:56 -06:00
parent b32bf69be7
commit b2239228e7
11 changed files with 154 additions and 47 deletions

View File

@ -1,7 +1,7 @@
import {makeAutoObservable} from 'mobx' import {makeAutoObservable} from 'mobx'
import {ProfileViewModel} from './profile-view' import {ProfileViewModel} from './profile-view'
export class ConfirmModel { export class ConfirmModal {
name = 'confirm' name = 'confirm'
constructor( constructor(
@ -13,7 +13,7 @@ export class ConfirmModel {
} }
} }
export class EditProfileModel { export class EditProfileModal {
name = 'edit-profile' name = 'edit-profile'
constructor( constructor(
@ -24,7 +24,7 @@ export class EditProfileModel {
} }
} }
export class CreateSceneModel { export class CreateSceneModal {
name = 'create-scene' name = 'create-scene'
constructor() { constructor() {
@ -32,7 +32,7 @@ export class CreateSceneModel {
} }
} }
export class InviteToSceneModel { export class InviteToSceneModal {
name = 'invite-to-scene' name = 'invite-to-scene'
constructor(public profileView: ProfileViewModel) { constructor(public profileView: ProfileViewModel) {
@ -40,7 +40,7 @@ export class InviteToSceneModel {
} }
} }
export class ServerInputModel { export class ServerInputModal {
name = 'server-input' name = 'server-input'
constructor( constructor(
@ -51,6 +51,13 @@ export class ServerInputModel {
} }
} }
export class ProfileImageLightbox {
name = 'profile-image'
constructor(public profileView: ProfileViewModel) {
makeAutoObservable(this)
}
}
export interface ComposerOptsPostRef { export interface ComposerOptsPostRef {
uri: string uri: string
cid: string cid: string
@ -70,11 +77,13 @@ export class ShellUiModel {
isMainMenuOpen = false isMainMenuOpen = false
isModalActive = false isModalActive = false
activeModal: activeModal:
| ConfirmModel | ConfirmModal
| EditProfileModel | EditProfileModal
| CreateSceneModel | CreateSceneModal
| ServerInputModel | ServerInputModal
| undefined | undefined
isLightboxActive = false
activeLightbox: ProfileImageLightbox | undefined
isComposerActive = false isComposerActive = false
composerOpts: ComposerOpts | undefined composerOpts: ComposerOpts | undefined
@ -88,10 +97,10 @@ export class ShellUiModel {
openModal( openModal(
modal: modal:
| ConfirmModel | ConfirmModal
| EditProfileModel | EditProfileModal
| CreateSceneModel | CreateSceneModal
| ServerInputModel, | ServerInputModal,
) { ) {
this.isModalActive = true this.isModalActive = true
this.activeModal = modal this.activeModal = modal
@ -102,6 +111,16 @@ export class ShellUiModel {
this.activeModal = undefined this.activeModal = undefined
} }
openLightbox(lightbox: ProfileImageLightbox) {
this.isLightboxActive = true
this.activeLightbox = lightbox
}
closeLightbox() {
this.isLightboxActive = false
this.activeLightbox = undefined
}
openComposer(opts: ComposerOpts) { openComposer(opts: ComposerOpts) {
this.isComposerActive = true this.isComposerActive = true
this.composerOpts = opts this.composerOpts = opts

View File

@ -0,0 +1,62 @@
import React from 'react'
import {StyleSheet, TouchableOpacity, View} from 'react-native'
import {observer} from 'mobx-react-lite'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {useStores} from '../../../state'
import * as models from '../../../state/models/shell-ui'
import * as ProfileImageLightbox from './ProfileImage'
export const Lightbox = observer(function Lightbox() {
const store = useStores()
const onClose = () => {
store.shell.closeLightbox()
}
if (!store.shell.isLightboxActive) {
return <View />
}
let element
if (store.shell.activeLightbox?.name === 'profile-image') {
element = (
<ProfileImageLightbox.Component
{...(store.shell.activeLightbox as models.ProfileImageLightbox)}
/>
)
} else {
return <View />
}
return (
<>
<TouchableOpacity style={styles.bg} onPress={onClose} />
<TouchableOpacity style={styles.xIcon} onPress={onClose}>
<FontAwesomeIcon icon="x" size={24} style={{color: '#fff'}} />
</TouchableOpacity>
{element}
</>
)
})
const styles = StyleSheet.create({
bg: {
position: 'absolute',
top: 0,
left: 0,
bottom: 0,
right: 0,
backgroundColor: '#000',
opacity: 0.9,
},
xIcon: {
position: 'absolute',
top: 30,
right: 30,
},
container: {
position: 'absolute',
},
})

View File

@ -0,0 +1,26 @@
import React from 'react'
import {StyleSheet, useWindowDimensions, View} from 'react-native'
import {UserAvatar} from '../util/UserAvatar'
import {ProfileViewModel} from '../../../state/models/profile-view'
export function Component({profileView}: {profileView: ProfileViewModel}) {
const winDim = useWindowDimensions()
const top = winDim.height / 2 - (winDim.width - 40) / 2 - 100
return (
<View style={[styles.container, {top}]}>
<UserAvatar
handle={profileView.handle}
displayName={profileView.displayName}
avatar={profileView.avatar}
size={winDim.width - 40}
/>
</View>
)
}
const styles = StyleSheet.create({
container: {
position: 'absolute',
left: 20,
},
})

View File

@ -43,14 +43,14 @@ export const Modal = observer(function Modal() {
snapPoints = ConfirmModal.snapPoints snapPoints = ConfirmModal.snapPoints
element = ( element = (
<ConfirmModal.Component <ConfirmModal.Component
{...(store.shell.activeModal as models.ConfirmModel)} {...(store.shell.activeModal as models.ConfirmModal)}
/> />
) )
} else if (store.shell.activeModal?.name === 'edit-profile') { } else if (store.shell.activeModal?.name === 'edit-profile') {
snapPoints = EditProfileModal.snapPoints snapPoints = EditProfileModal.snapPoints
element = ( element = (
<EditProfileModal.Component <EditProfileModal.Component
{...(store.shell.activeModal as models.EditProfileModel)} {...(store.shell.activeModal as models.EditProfileModal)}
/> />
) )
} else if (store.shell.activeModal?.name === 'create-scene') { } else if (store.shell.activeModal?.name === 'create-scene') {
@ -60,14 +60,14 @@ export const Modal = observer(function Modal() {
snapPoints = InviteToSceneModal.snapPoints snapPoints = InviteToSceneModal.snapPoints
element = ( element = (
<InviteToSceneModal.Component <InviteToSceneModal.Component
{...(store.shell.activeModal as models.InviteToSceneModel)} {...(store.shell.activeModal as models.InviteToSceneModal)}
/> />
) )
} else if (store.shell.activeModal?.name === 'server-input') { } else if (store.shell.activeModal?.name === 'server-input') {
snapPoints = ServerInputModal.snapPoints snapPoints = ServerInputModal.snapPoints
element = ( element = (
<ServerInputModal.Component <ServerInputModal.Component
{...(store.shell.activeModal as models.ServerInputModel)} {...(store.shell.activeModal as models.ServerInputModal)}
/> />
) )
} else { } else {

View File

@ -4,7 +4,7 @@ import LinearGradient from 'react-native-linear-gradient'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import * as apilib from '../../../state/lib/api' import * as apilib from '../../../state/lib/api'
import {NotificationsViewItemModel} from '../../../state/models/notifications-view' import {NotificationsViewItemModel} from '../../../state/models/notifications-view'
import {ConfirmModel} from '../../../state/models/shell-ui' import {ConfirmModal} from '../../../state/models/shell-ui'
import {useStores} from '../../../state' import {useStores} from '../../../state'
import {ProfileCard} from '../profile/ProfileCard' import {ProfileCard} from '../profile/ProfileCard'
import * as Toast from '../util/Toast' import * as Toast from '../util/Toast'
@ -17,7 +17,7 @@ export function InviteAccepter({item}: {item: NotificationsViewItemModel}) {
confirmationUri !== '' || store.me.memberships?.isMemberOf(item.author.did) confirmationUri !== '' || store.me.memberships?.isMemberOf(item.author.did)
const onPressAccept = async () => { const onPressAccept = async () => {
store.shell.openModal( store.shell.openModal(
new ConfirmModel( new ConfirmModal(
'Join this scene?', 'Join this scene?',
() => ( () => (
<View> <View>

View File

@ -7,9 +7,10 @@ import {AtUri} from '../../../third-party/uri'
import {ProfileViewModel} from '../../../state/models/profile-view' import {ProfileViewModel} from '../../../state/models/profile-view'
import {useStores} from '../../../state' import {useStores} from '../../../state'
import { import {
ConfirmModel, ConfirmModal,
EditProfileModel, EditProfileModal,
InviteToSceneModel, InviteToSceneModal,
ProfileImageLightbox,
} from '../../../state/models/shell-ui' } from '../../../state/models/shell-ui'
import {pluralize} from '../../../lib/strings' import {pluralize} from '../../../lib/strings'
import {s, colors} from '../../lib/styles' import {s, colors} from '../../lib/styles'
@ -35,11 +36,8 @@ export const ProfileHeader = observer(function ProfileHeader({
[view.myState.member], [view.myState.member],
) )
const onPressBack = () => { const onPressAvi = () => {
store.nav.tab.goBack() store.shell.openLightbox(new ProfileImageLightbox(view))
}
const onPressSearch = () => {
store.nav.navigate(`/search`)
} }
const onPressToggleFollow = () => { const onPressToggleFollow = () => {
view?.toggleFollowing().then( view?.toggleFollowing().then(
@ -54,7 +52,7 @@ export const ProfileHeader = observer(function ProfileHeader({
) )
} }
const onPressEditProfile = () => { const onPressEditProfile = () => {
store.shell.openModal(new EditProfileModel(view, onRefreshAll)) store.shell.openModal(new EditProfileModal(view, onRefreshAll))
} }
const onPressFollowers = () => { const onPressFollowers = () => {
store.nav.navigate(`/profile/${view.handle}/followers`) store.nav.navigate(`/profile/${view.handle}/followers`)
@ -66,11 +64,11 @@ export const ProfileHeader = observer(function ProfileHeader({
store.nav.navigate(`/profile/${view.handle}/members`) store.nav.navigate(`/profile/${view.handle}/members`)
} }
const onPressInviteMembers = () => { const onPressInviteMembers = () => {
store.shell.openModal(new InviteToSceneModel(view)) store.shell.openModal(new InviteToSceneModal(view))
} }
const onPressLeaveScene = () => { const onPressLeaveScene = () => {
store.shell.openModal( store.shell.openModal(
new ConfirmModel( new ConfirmModal(
'Leave this scene?', '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, onPressConfirmLeaveScene,
@ -153,14 +151,6 @@ export const ProfileHeader = observer(function ProfileHeader({
return ( return (
<View style={styles.outer}> <View style={styles.outer}>
<UserBanner handle={view.handle} banner={view.banner} /> <UserBanner handle={view.handle} banner={view.banner} />
<View style={styles.avi}>
<UserAvatar
size={80}
handle={view.handle}
displayName={view.displayName}
avatar={view.avatar}
/>
</View>
<View style={styles.content}> <View style={styles.content}>
<View style={[styles.buttonsLine]}> <View style={[styles.buttonsLine]}>
{isMe ? ( {isMe ? (
@ -304,6 +294,14 @@ export const ProfileHeader = observer(function ProfileHeader({
</TouchableOpacity> </TouchableOpacity>
</View> </View>
) : undefined} ) : undefined}
<TouchableOpacity style={styles.avi} onPress={onPressAvi}>
<UserAvatar
size={80}
handle={view.handle}
displayName={view.displayName}
avatar={view.avatar}
/>
</TouchableOpacity>
</View> </View>
) )
}) })

View File

@ -15,7 +15,7 @@ import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {colors} from '../../lib/styles' import {colors} from '../../lib/styles'
import {toShareUrl} from '../../../lib/strings' import {toShareUrl} from '../../../lib/strings'
import {useStores} from '../../../state' import {useStores} from '../../../state'
import {ConfirmModel} from '../../../state/models/shell-ui' import {ConfirmModal} from '../../../state/models/shell-ui'
import {TABS_ENABLED} from '../../../build-flags' import {TABS_ENABLED} from '../../../build-flags'
const HITSLOP = {left: 10, top: 10, right: 10, bottom: 10} const HITSLOP = {left: 10, top: 10, right: 10, bottom: 10}
@ -122,7 +122,7 @@ export function PostDropdownBtn({
label: 'Delete post', label: 'Delete post',
onPress() { onPress() {
store.shell.openModal( store.shell.openModal(
new ConfirmModel( new ConfirmModal(
'Delete this post?', 'Delete this post?',
'Are you sure? This can not be undone.', 'Are you sure? This can not be undone.',
onDeletePost, onDeletePost,

View File

@ -24,7 +24,7 @@ import {
} from '../../lib/strings' } from '../../lib/strings'
import {useStores, DEFAULT_SERVICE} from '../../state' import {useStores, DEFAULT_SERVICE} from '../../state'
import {ServiceDescription} from '../../state/models/session' import {ServiceDescription} from '../../state/models/session'
import {ServerInputModel} from '../../state/models/shell-ui' import {ServerInputModal} from '../../state/models/shell-ui'
import {ComAtprotoAccountCreate} from '../../third-party/api/index' import {ComAtprotoAccountCreate} from '../../third-party/api/index'
import {isNetworkError} from '../../lib/errors' import {isNetworkError} from '../../lib/errors'
@ -149,7 +149,7 @@ const Signin = ({onPressBack}: {onPressBack: () => void}) => {
}, [serviceUrl]) }, [serviceUrl])
const onPressSelectService = () => { const onPressSelectService = () => {
store.shell.openModal(new ServerInputModel(serviceUrl, setServiceUrl)) store.shell.openModal(new ServerInputModal(serviceUrl, setServiceUrl))
} }
const onPressNext = async () => { const onPressNext = async () => {
@ -309,7 +309,7 @@ const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => {
}, [serviceUrl]) }, [serviceUrl])
const onPressSelectService = () => { const onPressSelectService = () => {
store.shell.openModal(new ServerInputModel(serviceUrl, setServiceUrl)) store.shell.openModal(new ServerInputModal(serviceUrl, setServiceUrl))
} }
const onPressNext = async () => { const onPressNext = async () => {

View File

@ -7,7 +7,7 @@ import {ScreenParams} from '../routes'
import {ProfileUiModel, Sections} from '../../state/models/profile-ui' import {ProfileUiModel, Sections} from '../../state/models/profile-ui'
import {MembershipItem} from '../../state/models/memberships-view' import {MembershipItem} from '../../state/models/memberships-view'
import {useStores} from '../../state' import {useStores} from '../../state'
import {ConfirmModel} from '../../state/models/shell-ui' import {ConfirmModal} from '../../state/models/shell-ui'
import {ProfileHeader} from '../com/profile/ProfileHeader' import {ProfileHeader} from '../com/profile/ProfileHeader'
import {FeedItem} from '../com/posts/FeedItem' import {FeedItem} from '../com/posts/FeedItem'
import {ProfileCard} from '../com/profile/ProfileCard' import {ProfileCard} from '../com/profile/ProfileCard'
@ -73,7 +73,7 @@ export const Profile = observer(({navIdx, visible, params}: ScreenParams) => {
} }
const onPressRemoveMember = (membership: MembershipItem) => { const onPressRemoveMember = (membership: MembershipItem) => {
store.shell.openModal( store.shell.openModal(
new ConfirmModel( new ConfirmModal(
`Remove ${membership.displayName || membership.handle}?`, `Remove ${membership.displayName || membership.handle}?`,
`You'll be able to invite them again if you change your mind.`, `You'll be able to invite them again if you change your mind.`,
async () => { async () => {

View File

@ -18,7 +18,7 @@ import {
MagnifyingGlassIcon, MagnifyingGlassIcon,
} from '../../lib/icons' } from '../../lib/icons'
import {UserAvatar} from '../../com/util/UserAvatar' import {UserAvatar} from '../../com/util/UserAvatar'
import {CreateSceneModel} from '../../../state/models/shell-ui' import {CreateSceneModal} from '../../../state/models/shell-ui'
export const Menu = ({ export const Menu = ({
visible, visible,
@ -53,7 +53,7 @@ export const Menu = ({
} }
const onPressCreateScene = () => { const onPressCreateScene = () => {
onClose() onClose()
store.shell.openModal(new CreateSceneModel()) store.shell.openModal(new CreateSceneModal())
} }
// rendering // rendering

View File

@ -28,6 +28,7 @@ import {Menu} from './Menu'
import {Onboard} from '../../screens/Onboard' import {Onboard} from '../../screens/Onboard'
import {HorzSwipe} from '../../com/util/gestures/HorzSwipe' import {HorzSwipe} from '../../com/util/gestures/HorzSwipe'
import {Modal} from '../../com/modals/Modal' import {Modal} from '../../com/modals/Modal'
import {Lightbox} from '../../com/lightbox/Lightbox'
import {TabsSelector} from './TabsSelector' import {TabsSelector} from './TabsSelector'
import {Composer} from './Composer' import {Composer} from './Composer'
import {s, colors} from '../../lib/styles' import {s, colors} from '../../lib/styles'
@ -420,6 +421,7 @@ export const MobileShell: React.FC = observer(() => {
/> />
</View> </View>
<Modal /> <Modal />
<Lightbox />
<Composer <Composer
active={store.shell.isComposerActive} active={store.shell.isComposerActive}
onClose={() => store.shell.closeComposer()} onClose={() => store.shell.closeComposer()}