Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

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

Use `<Modal>` for Composer (#3588)

* use <Modal> to display composer

* trigger `onPressCancel` on modal cancel

* remove android top padding

* use light statusbar on ios

* use KeyboardStickyView from r-n-keyboard-controller

* make extra bottom padding ios-only

* make cancelRef optional

* scope legacy modals

* don't change bg color on ios

* use fullScreen instead of formSheet

* adjust padding on keyboardaccessory to account for new buttons

* Revert "use KeyboardStickyView from r-n-keyboard-controller"

This reverts commit 426c812904f427bdd08107cffc32e4be1d9b83bc.

* fix insets

* tweaks and merge

* revert 89f51c72

* nit

* import keyboard provider

---------

Co-authored-by: Hailey <me@haileyok.com>
Co-authored-by: Dan Abramov <dan.abramov@gmail.com>

authored by

Samuel Newman
Hailey
Dan Abramov
and committed by
GitHub
c4abaa1a fba4a9ca

+99 -96
+3 -3
src/alf/util/useColorModeTheme.ts
··· 1 1 import React from 'react' 2 2 import {ColorSchemeName, useColorScheme} from 'react-native' 3 + import * as SystemUI from 'expo-system-ui' 3 4 4 - import {useThemePrefs} from 'state/shell' 5 5 import {isWeb} from 'platform/detection' 6 - import {ThemeName, light, dark, dim} from '#/alf/themes' 7 - import * as SystemUI from 'expo-system-ui' 6 + import {useThemePrefs} from 'state/shell' 7 + import {dark, dim, light, ThemeName} from '#/alf/themes' 8 8 9 9 export function useColorModeTheme(): ThemeName { 10 10 const colorScheme = useColorScheme()
+24 -27
src/view/com/composer/Composer.tsx
··· 1 - import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react' 1 + import React, { 2 + useCallback, 3 + useEffect, 4 + useImperativeHandle, 5 + useMemo, 6 + useRef, 7 + useState, 8 + } from 'react' 2 9 import { 3 10 ActivityIndicator, 4 - BackHandler, 5 11 Keyboard, 6 12 ScrollView, 7 13 StyleSheet, ··· 79 85 import {ThreadgateBtn} from './threadgate/ThreadgateBtn' 80 86 import {useExternalLinkFetch} from './useExternalLinkFetch' 81 87 88 + type CancelRef = { 89 + onPressCancel: () => void 90 + } 91 + 82 92 type Props = ComposerOpts 83 93 export const ComposePost = observer(function ComposePost({ 84 94 replyTo, ··· 88 98 openPicker, 89 99 text: initText, 90 100 imageUris: initImageUris, 91 - }: Props) { 101 + cancelRef, 102 + }: Props & { 103 + cancelRef?: React.RefObject<CancelRef> 104 + }) { 92 105 const {currentAccount} = useSession() 93 106 const agent = useAgent() 94 107 const {data: currentProfile} = useProfileQuery({did: currentAccount!.did}) ··· 145 158 () => ({ 146 159 paddingBottom: 147 160 isAndroid || (isIOS && !isKeyboardVisible) ? insets.bottom : 0, 148 - paddingTop: isAndroid ? insets.top : isMobile ? 15 : 0, 161 + paddingTop: isMobile && isWeb ? 15 : insets.top, 149 162 }), 150 163 [insets, isKeyboardVisible, isMobile], 151 164 ) ··· 167 180 discardPromptControl, 168 181 onClose, 169 182 ]) 170 - // android back button 171 - useEffect(() => { 172 - if (!isAndroid) { 173 - return 174 - } 175 - const backHandler = BackHandler.addEventListener( 176 - 'hardwareBackPress', 177 - () => { 178 - onPressCancel() 179 - return true 180 - }, 181 - ) 182 183 183 - return () => { 184 - backHandler.remove() 185 - } 186 - }, [onPressCancel]) 184 + useImperativeHandle(cancelRef, () => ({onPressCancel})) 187 185 188 186 // listen to escape key on desktop web 189 187 const onEscape = useCallback( ··· 583 581 ) 584 582 }) 585 583 584 + export function useComposerCancelRef() { 585 + return useRef<CancelRef>(null) 586 + } 587 + 586 588 const styles = StyleSheet.create({ 587 - outer: { 588 - flexDirection: 'column', 589 - flex: 1, 590 - height: '100%', 591 - }, 592 589 topbar: { 593 590 flexDirection: 'row', 594 591 alignItems: 'center', 595 - paddingTop: 6, 592 + marginTop: -14, 596 593 paddingBottom: 4, 597 594 paddingHorizontal: 20, 598 - height: 55, 595 + height: 50, 599 596 gap: 4, 600 597 }, 601 598 topbarDesktop: {
+34
src/view/com/composer/KeyboardAccessory.tsx
··· 1 + import React from 'react' 2 + import {View} from 'react-native' 3 + import {KeyboardStickyView} from 'react-native-keyboard-controller' 4 + import {useSafeAreaInsets} from 'react-native-safe-area-context' 5 + 6 + import {isWeb} from '#/platform/detection' 7 + import {atoms as a, useTheme} from '#/alf' 8 + 9 + export function KeyboardAccessory({children}: {children: React.ReactNode}) { 10 + const t = useTheme() 11 + const {bottom} = useSafeAreaInsets() 12 + 13 + const style = [ 14 + a.flex_row, 15 + a.py_xs, 16 + a.pl_sm, 17 + a.pr_xl, 18 + a.align_center, 19 + a.border_t, 20 + t.atoms.border_contrast_medium, 21 + t.atoms.bg, 22 + ] 23 + 24 + // todo: when iPad support is added, it should also not use the KeyboardStickyView 25 + if (isWeb) { 26 + return <View style={style}>{children}</View> 27 + } 28 + 29 + return ( 30 + <KeyboardStickyView offset={{closed: -bottom}} style={style}> 31 + {children} 32 + </KeyboardStickyView> 33 + ) 34 + }
+38 -66
src/view/shell/Composer.tsx
··· 1 - import React, {useEffect} from 'react' 1 + import React from 'react' 2 + import {Modal, View} from 'react-native' 2 3 import {observer} from 'mobx-react-lite' 3 - import {Animated, Easing, Platform, StyleSheet, View} from 'react-native' 4 - import {ComposePost} from '../com/composer/Composer' 4 + 5 + import {Provider as LegacyModalProvider} from '#/state/modals' 5 6 import {useComposerState} from 'state/shell/composer' 6 - import {useAnimatedValue} from 'lib/hooks/useAnimatedValue' 7 - import {usePalette} from 'lib/hooks/usePalette' 7 + import {ModalsContainer as LegacyModalsContainer} from '#/view/com/modals/Modal' 8 + import {useTheme} from '#/alf' 9 + import { 10 + Outlet as PortalOutlet, 11 + Provider as PortalProvider, 12 + } from '#/components/Portal' 13 + import {ComposePost, useComposerCancelRef} from '../com/composer/Composer' 8 14 9 - export const Composer = observer(function ComposerImpl({ 10 - winHeight, 11 - }: { 15 + export const Composer = observer(function ComposerImpl({}: { 12 16 winHeight: number 13 17 }) { 18 + const t = useTheme() 14 19 const state = useComposerState() 15 - const pal = usePalette('default') 16 - const initInterp = useAnimatedValue(0) 17 - 18 - useEffect(() => { 19 - if (state) { 20 - Animated.timing(initInterp, { 21 - toValue: 1, 22 - duration: 300, 23 - easing: Easing.out(Easing.exp), 24 - useNativeDriver: true, 25 - }).start() 26 - } else { 27 - initInterp.setValue(0) 28 - } 29 - }, [initInterp, state]) 30 - const wrapperAnimStyle = { 31 - transform: [ 32 - { 33 - translateY: initInterp.interpolate({ 34 - inputRange: [0, 1], 35 - outputRange: [winHeight, 0], 36 - }), 37 - }, 38 - ], 39 - } 40 - 41 - // rendering 42 - // = 43 - 44 - if (!state) { 45 - return <View /> 46 - } 20 + const ref = useComposerCancelRef() 47 21 48 22 return ( 49 - <Animated.View 50 - style={[styles.wrapper, pal.view, wrapperAnimStyle]} 23 + <Modal 51 24 aria-modal 52 - accessibilityViewIsModal> 53 - <ComposePost 54 - replyTo={state.replyTo} 55 - onPost={state.onPost} 56 - quote={state.quote} 57 - mention={state.mention} 58 - text={state.text} 59 - imageUris={state.imageUris} 60 - /> 61 - </Animated.View> 25 + accessibilityViewIsModal 26 + visible={!!state} 27 + presentationStyle="overFullScreen" 28 + animationType="slide" 29 + onRequestClose={() => ref.current?.onPressCancel()}> 30 + <View style={[t.atoms.bg, {flex: 1}]}> 31 + <LegacyModalProvider> 32 + <PortalProvider> 33 + <ComposePost 34 + cancelRef={ref} 35 + replyTo={state?.replyTo} 36 + onPost={state?.onPost} 37 + quote={state?.quote} 38 + mention={state?.mention} 39 + text={state?.text} 40 + imageUris={state?.imageUris} 41 + /> 42 + <LegacyModalsContainer /> 43 + <PortalOutlet /> 44 + </PortalProvider> 45 + </LegacyModalProvider> 46 + </View> 47 + </Modal> 62 48 ) 63 49 }) 64 - 65 - const styles = StyleSheet.create({ 66 - wrapper: { 67 - position: 'absolute', 68 - top: 0, 69 - bottom: 0, 70 - width: '100%', 71 - ...Platform.select({ 72 - ios: { 73 - paddingTop: 24, 74 - }, 75 - }), 76 - }, 77 - })