Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

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 timeout

authored by

dan and committed by
GitHub
f447eaa6 d5ccbd76

+33 -18
+14 -2
src/Navigation.tsx
··· 483 483 params?: AllNavigatorParams[K], 484 484 ) { 485 485 if (navigationRef.isReady()) { 486 - // @ts-ignore I dont know what would make typescript happy but I have a life -prf 487 - navigationRef.navigate(name, params) 486 + return Promise.race([ 487 + new Promise<void>(resolve => { 488 + const handler = () => { 489 + resolve() 490 + navigationRef.removeListener('state', handler) 491 + } 492 + navigationRef.addListener('state', handler) 493 + 494 + // @ts-ignore I dont know what would make typescript happy but I have a life -prf 495 + navigationRef.navigate(name, params) 496 + }), 497 + timeout(1e3), 498 + ]) 488 499 } 500 + return Promise.resolve() 489 501 } 490 502 491 503 function resetToTab(tabName: 'HomeTab' | 'SearchTab' | 'NotificationsTab') {
+16 -4
src/view/com/modals/Modal.tsx
··· 1 1 import React, {useRef, useEffect} from 'react' 2 2 import {StyleSheet} from 'react-native' 3 - import {SafeAreaView} from 'react-native-safe-area-context' 3 + import {SafeAreaView, useSafeAreaInsets} from 'react-native-safe-area-context' 4 4 import {observer} from 'mobx-react-lite' 5 5 import BottomSheet from '@gorhom/bottom-sheet' 6 6 import {useStores} from 'state/index' 7 7 import {createCustomBackdrop} from '../util/BottomSheetCustomBackdrop' 8 8 import {usePalette} from 'lib/hooks/usePalette' 9 + import {timeout} from 'lib/async/timeout' 9 10 import {navigate} from '../../../Navigation' 10 11 import once from 'lodash.once' 11 12 ··· 36 37 import * as LinkWarningModal from './LinkWarning' 37 38 38 39 const DEFAULT_SNAPPOINTS = ['90%'] 40 + const HANDLE_HEIGHT = 24 39 41 40 42 export const ModalsContainer = observer(function ModalsContainer() { 41 43 const store = useStores() 42 44 const bottomSheetRef = useRef<BottomSheet>(null) 43 45 const pal = usePalette('default') 46 + const safeAreaInsets = useSafeAreaInsets() 44 47 45 48 const activeModal = 46 49 store.shell.activeModals[store.shell.activeModals.length - 1] ··· 53 56 navigateOnce('Profile', {name: activeModal.did}) 54 57 } 55 58 } 56 - const onBottomSheetChange = (snapPoint: number) => { 59 + const onBottomSheetChange = async (snapPoint: number) => { 57 60 if (snapPoint === -1) { 58 61 store.shell.closeModal() 59 62 } else if (activeModal?.name === 'profile-preview' && snapPoint === 1) { 60 - // ensure we navigate to Profile and close the modal 61 - navigateOnce('Profile', {name: activeModal.did}) 63 + await navigateOnce('Profile', {name: activeModal.did}) 64 + // There is no particular callback for when the view has actually been presented. 65 + // This delay gives us a decent chance the navigation has flushed *and* images have loaded. 66 + // It's acceptable because the data is already being fetched + it usually takes longer anyway. 67 + // TODO: Figure out why avatar/cover don't always show instantly from cache. 68 + await timeout(200) 62 69 store.shell.closeModal() 63 70 } 64 71 } ··· 75 82 } 76 83 }, [store.shell.isModalActive, bottomSheetRef, activeModal?.name]) 77 84 85 + let needsSafeTopInset = false 78 86 let snapPoints: (string | number)[] = DEFAULT_SNAPPOINTS 79 87 let element 80 88 if (activeModal?.name === 'confirm') { ··· 86 94 } else if (activeModal?.name === 'profile-preview') { 87 95 snapPoints = ProfilePreviewModal.snapPoints 88 96 element = <ProfilePreviewModal.Component {...activeModal} /> 97 + needsSafeTopInset = true // Need to align with the target profile screen. 89 98 } else if (activeModal?.name === 'server-input') { 90 99 snapPoints = ServerInputModal.snapPoints 91 100 element = <ServerInputModal.Component {...activeModal} /> ··· 164 173 ) 165 174 } 166 175 176 + const topInset = needsSafeTopInset ? safeAreaInsets.top - HANDLE_HEIGHT : 0 167 177 return ( 168 178 <BottomSheet 169 179 ref={bottomSheetRef} 170 180 snapPoints={snapPoints} 181 + topInset={topInset} 182 + handleHeight={HANDLE_HEIGHT} 171 183 index={store.shell.isModalActive ? 0 : -1} 172 184 enablePanDownToClose 173 185 android_keyboardInputMode="adjustResize"
+1 -10
src/view/com/modals/ProfilePreview.tsx
··· 9 9 import {ProfileHeader} from '../profile/ProfileHeader' 10 10 import {InfoCircleIcon} from 'lib/icons' 11 11 import {useNavigationState} from '@react-navigation/native' 12 - import {isIOS} from 'platform/detection' 13 12 import {s} from 'lib/styles' 14 13 15 14 export const snapPoints = [520, '100%'] ··· 36 35 37 36 return ( 38 37 <View testID="profilePreview" style={[pal.view, s.flex1]}> 39 - <View 40 - style={[ 41 - styles.headerWrapper, 42 - isLoading && isIOS && styles.headerPositionAdjust, 43 - ]}> 38 + <View style={[styles.headerWrapper]}> 44 39 <ProfileHeader 45 40 view={model} 46 41 hideBackButton ··· 69 64 const styles = StyleSheet.create({ 70 65 headerWrapper: { 71 66 height: 440, 72 - }, 73 - headerPositionAdjust: { 74 - // HACK align the header for the profilescreen transition -prf 75 - paddingTop: 23, 76 67 }, 77 68 hintWrapper: { 78 69 height: 80,
+2 -2
src/view/com/profile/ProfileHeader.tsx
··· 60 60 if (!view || !view.hasLoaded) { 61 61 return ( 62 62 <View style={pal.view}> 63 - <LoadingPlaceholder width="100%" height={120} /> 63 + <LoadingPlaceholder width="100%" height={153} /> 64 64 <View 65 65 style={[pal.view, {borderColor: pal.colors.background}, styles.avi]}> 66 66 <LoadingPlaceholder width={80} height={80} style={styles.br40} /> 67 67 </View> 68 68 <View style={styles.content}> 69 69 <View style={[styles.buttonsLine]}> 70 - <LoadingPlaceholder width={100} height={31} style={styles.br50} /> 70 + <LoadingPlaceholder width={167} height={31} style={styles.br50} /> 71 71 </View> 72 72 <View> 73 73 <Text type="title-2xl" style={[pal.text, styles.title]}>