Fix profile preview jump (#1693)
* Add top inset for profile preview to match target screen * Avoid flicker by waiting for profile screen navigation * Fix glimmer to align with the content * A more reliable (but non-scientific) fix for the flash * Lower the timeoutzio/stable
parent
d5ccbd76d5
commit
f447eaa669
|
@ -483,9 +483,21 @@ function navigate<K extends keyof AllNavigatorParams>(
|
||||||
params?: AllNavigatorParams[K],
|
params?: AllNavigatorParams[K],
|
||||||
) {
|
) {
|
||||||
if (navigationRef.isReady()) {
|
if (navigationRef.isReady()) {
|
||||||
|
return Promise.race([
|
||||||
|
new Promise<void>(resolve => {
|
||||||
|
const handler = () => {
|
||||||
|
resolve()
|
||||||
|
navigationRef.removeListener('state', handler)
|
||||||
|
}
|
||||||
|
navigationRef.addListener('state', handler)
|
||||||
|
|
||||||
// @ts-ignore I dont know what would make typescript happy but I have a life -prf
|
// @ts-ignore I dont know what would make typescript happy but I have a life -prf
|
||||||
navigationRef.navigate(name, params)
|
navigationRef.navigate(name, params)
|
||||||
|
}),
|
||||||
|
timeout(1e3),
|
||||||
|
])
|
||||||
}
|
}
|
||||||
|
return Promise.resolve()
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetToTab(tabName: 'HomeTab' | 'SearchTab' | 'NotificationsTab') {
|
function resetToTab(tabName: 'HomeTab' | 'SearchTab' | 'NotificationsTab') {
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import React, {useRef, useEffect} from 'react'
|
import React, {useRef, useEffect} from 'react'
|
||||||
import {StyleSheet} from 'react-native'
|
import {StyleSheet} from 'react-native'
|
||||||
import {SafeAreaView} from 'react-native-safe-area-context'
|
import {SafeAreaView, useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||||
import {observer} from 'mobx-react-lite'
|
import {observer} from 'mobx-react-lite'
|
||||||
import BottomSheet from '@gorhom/bottom-sheet'
|
import BottomSheet from '@gorhom/bottom-sheet'
|
||||||
import {useStores} from 'state/index'
|
import {useStores} from 'state/index'
|
||||||
import {createCustomBackdrop} from '../util/BottomSheetCustomBackdrop'
|
import {createCustomBackdrop} from '../util/BottomSheetCustomBackdrop'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
|
import {timeout} from 'lib/async/timeout'
|
||||||
import {navigate} from '../../../Navigation'
|
import {navigate} from '../../../Navigation'
|
||||||
import once from 'lodash.once'
|
import once from 'lodash.once'
|
||||||
|
|
||||||
|
@ -36,11 +37,13 @@ import * as SwitchAccountModal from './SwitchAccount'
|
||||||
import * as LinkWarningModal from './LinkWarning'
|
import * as LinkWarningModal from './LinkWarning'
|
||||||
|
|
||||||
const DEFAULT_SNAPPOINTS = ['90%']
|
const DEFAULT_SNAPPOINTS = ['90%']
|
||||||
|
const HANDLE_HEIGHT = 24
|
||||||
|
|
||||||
export const ModalsContainer = observer(function ModalsContainer() {
|
export const ModalsContainer = observer(function ModalsContainer() {
|
||||||
const store = useStores()
|
const store = useStores()
|
||||||
const bottomSheetRef = useRef<BottomSheet>(null)
|
const bottomSheetRef = useRef<BottomSheet>(null)
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
|
const safeAreaInsets = useSafeAreaInsets()
|
||||||
|
|
||||||
const activeModal =
|
const activeModal =
|
||||||
store.shell.activeModals[store.shell.activeModals.length - 1]
|
store.shell.activeModals[store.shell.activeModals.length - 1]
|
||||||
|
@ -53,12 +56,16 @@ export const ModalsContainer = observer(function ModalsContainer() {
|
||||||
navigateOnce('Profile', {name: activeModal.did})
|
navigateOnce('Profile', {name: activeModal.did})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const onBottomSheetChange = (snapPoint: number) => {
|
const onBottomSheetChange = async (snapPoint: number) => {
|
||||||
if (snapPoint === -1) {
|
if (snapPoint === -1) {
|
||||||
store.shell.closeModal()
|
store.shell.closeModal()
|
||||||
} else if (activeModal?.name === 'profile-preview' && snapPoint === 1) {
|
} else if (activeModal?.name === 'profile-preview' && snapPoint === 1) {
|
||||||
// ensure we navigate to Profile and close the modal
|
await navigateOnce('Profile', {name: activeModal.did})
|
||||||
navigateOnce('Profile', {name: activeModal.did})
|
// There is no particular callback for when the view has actually been presented.
|
||||||
|
// This delay gives us a decent chance the navigation has flushed *and* images have loaded.
|
||||||
|
// It's acceptable because the data is already being fetched + it usually takes longer anyway.
|
||||||
|
// TODO: Figure out why avatar/cover don't always show instantly from cache.
|
||||||
|
await timeout(200)
|
||||||
store.shell.closeModal()
|
store.shell.closeModal()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -75,6 +82,7 @@ export const ModalsContainer = observer(function ModalsContainer() {
|
||||||
}
|
}
|
||||||
}, [store.shell.isModalActive, bottomSheetRef, activeModal?.name])
|
}, [store.shell.isModalActive, bottomSheetRef, activeModal?.name])
|
||||||
|
|
||||||
|
let needsSafeTopInset = false
|
||||||
let snapPoints: (string | number)[] = DEFAULT_SNAPPOINTS
|
let snapPoints: (string | number)[] = DEFAULT_SNAPPOINTS
|
||||||
let element
|
let element
|
||||||
if (activeModal?.name === 'confirm') {
|
if (activeModal?.name === 'confirm') {
|
||||||
|
@ -86,6 +94,7 @@ export const ModalsContainer = observer(function ModalsContainer() {
|
||||||
} else if (activeModal?.name === 'profile-preview') {
|
} else if (activeModal?.name === 'profile-preview') {
|
||||||
snapPoints = ProfilePreviewModal.snapPoints
|
snapPoints = ProfilePreviewModal.snapPoints
|
||||||
element = <ProfilePreviewModal.Component {...activeModal} />
|
element = <ProfilePreviewModal.Component {...activeModal} />
|
||||||
|
needsSafeTopInset = true // Need to align with the target profile screen.
|
||||||
} else if (activeModal?.name === 'server-input') {
|
} else if (activeModal?.name === 'server-input') {
|
||||||
snapPoints = ServerInputModal.snapPoints
|
snapPoints = ServerInputModal.snapPoints
|
||||||
element = <ServerInputModal.Component {...activeModal} />
|
element = <ServerInputModal.Component {...activeModal} />
|
||||||
|
@ -164,10 +173,13 @@ export const ModalsContainer = observer(function ModalsContainer() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const topInset = needsSafeTopInset ? safeAreaInsets.top - HANDLE_HEIGHT : 0
|
||||||
return (
|
return (
|
||||||
<BottomSheet
|
<BottomSheet
|
||||||
ref={bottomSheetRef}
|
ref={bottomSheetRef}
|
||||||
snapPoints={snapPoints}
|
snapPoints={snapPoints}
|
||||||
|
topInset={topInset}
|
||||||
|
handleHeight={HANDLE_HEIGHT}
|
||||||
index={store.shell.isModalActive ? 0 : -1}
|
index={store.shell.isModalActive ? 0 : -1}
|
||||||
enablePanDownToClose
|
enablePanDownToClose
|
||||||
android_keyboardInputMode="adjustResize"
|
android_keyboardInputMode="adjustResize"
|
||||||
|
|
|
@ -9,7 +9,6 @@ import {useAnalytics} from 'lib/analytics/analytics'
|
||||||
import {ProfileHeader} from '../profile/ProfileHeader'
|
import {ProfileHeader} from '../profile/ProfileHeader'
|
||||||
import {InfoCircleIcon} from 'lib/icons'
|
import {InfoCircleIcon} from 'lib/icons'
|
||||||
import {useNavigationState} from '@react-navigation/native'
|
import {useNavigationState} from '@react-navigation/native'
|
||||||
import {isIOS} from 'platform/detection'
|
|
||||||
import {s} from 'lib/styles'
|
import {s} from 'lib/styles'
|
||||||
|
|
||||||
export const snapPoints = [520, '100%']
|
export const snapPoints = [520, '100%']
|
||||||
|
@ -36,11 +35,7 @@ export const Component = observer(function ProfilePreviewImpl({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View testID="profilePreview" style={[pal.view, s.flex1]}>
|
<View testID="profilePreview" style={[pal.view, s.flex1]}>
|
||||||
<View
|
<View style={[styles.headerWrapper]}>
|
||||||
style={[
|
|
||||||
styles.headerWrapper,
|
|
||||||
isLoading && isIOS && styles.headerPositionAdjust,
|
|
||||||
]}>
|
|
||||||
<ProfileHeader
|
<ProfileHeader
|
||||||
view={model}
|
view={model}
|
||||||
hideBackButton
|
hideBackButton
|
||||||
|
@ -70,10 +65,6 @@ const styles = StyleSheet.create({
|
||||||
headerWrapper: {
|
headerWrapper: {
|
||||||
height: 440,
|
height: 440,
|
||||||
},
|
},
|
||||||
headerPositionAdjust: {
|
|
||||||
// HACK align the header for the profilescreen transition -prf
|
|
||||||
paddingTop: 23,
|
|
||||||
},
|
|
||||||
hintWrapper: {
|
hintWrapper: {
|
||||||
height: 80,
|
height: 80,
|
||||||
},
|
},
|
||||||
|
|
|
@ -60,14 +60,14 @@ export const ProfileHeader = observer(function ProfileHeaderImpl({
|
||||||
if (!view || !view.hasLoaded) {
|
if (!view || !view.hasLoaded) {
|
||||||
return (
|
return (
|
||||||
<View style={pal.view}>
|
<View style={pal.view}>
|
||||||
<LoadingPlaceholder width="100%" height={120} />
|
<LoadingPlaceholder width="100%" height={153} />
|
||||||
<View
|
<View
|
||||||
style={[pal.view, {borderColor: pal.colors.background}, styles.avi]}>
|
style={[pal.view, {borderColor: pal.colors.background}, styles.avi]}>
|
||||||
<LoadingPlaceholder width={80} height={80} style={styles.br40} />
|
<LoadingPlaceholder width={80} height={80} style={styles.br40} />
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.content}>
|
<View style={styles.content}>
|
||||||
<View style={[styles.buttonsLine]}>
|
<View style={[styles.buttonsLine]}>
|
||||||
<LoadingPlaceholder width={100} height={31} style={styles.br50} />
|
<LoadingPlaceholder width={167} height={31} style={styles.br50} />
|
||||||
</View>
|
</View>
|
||||||
<View>
|
<View>
|
||||||
<Text type="title-2xl" style={[pal.text, styles.title]}>
|
<Text type="title-2xl" style={[pal.text, styles.title]}>
|
||||||
|
|
Loading…
Reference in New Issue