Composer - fix modals, and other tweaks (#4298)
* fix depreciated import * add animations to old dropdown * wrap modals in fullwindowoverlay * move errors inside header * add background to bottom bar and stop overlap * nest dialogs on android * fix android (wrap in gesturehandlerrootview) * make borders all the same color * revert threadgate button back to solidzio/stable
parent
d614f6cb71
commit
05b55c1966
|
@ -32,6 +32,7 @@ import {
|
||||||
DialogOuterProps,
|
DialogOuterProps,
|
||||||
} from '#/components/Dialog/types'
|
} from '#/components/Dialog/types'
|
||||||
import {createInput} from '#/components/forms/TextField'
|
import {createInput} from '#/components/forms/TextField'
|
||||||
|
import {FullWindowOverlay} from '#/components/FullWindowOverlay'
|
||||||
import {Portal} from '#/components/Portal'
|
import {Portal} from '#/components/Portal'
|
||||||
|
|
||||||
export {useDialogContext, useDialogControl} from '#/components/Dialog/context'
|
export {useDialogContext, useDialogControl} from '#/components/Dialog/context'
|
||||||
|
@ -170,47 +171,49 @@ export function Outer({
|
||||||
return (
|
return (
|
||||||
isOpen && (
|
isOpen && (
|
||||||
<Portal>
|
<Portal>
|
||||||
<View
|
<FullWindowOverlay>
|
||||||
// iOS
|
<View
|
||||||
accessibilityViewIsModal
|
// iOS
|
||||||
// Android
|
accessibilityViewIsModal
|
||||||
importantForAccessibility="yes"
|
// Android
|
||||||
style={[a.absolute, a.inset_0]}
|
importantForAccessibility="yes"
|
||||||
testID={testID}
|
style={[a.absolute, a.inset_0]}
|
||||||
onTouchMove={() => Keyboard.dismiss()}>
|
testID={testID}
|
||||||
<BottomSheet
|
onTouchMove={() => Keyboard.dismiss()}>
|
||||||
enableDynamicSizing={!hasSnapPoints}
|
<BottomSheet
|
||||||
enablePanDownToClose
|
enableDynamicSizing={!hasSnapPoints}
|
||||||
keyboardBehavior="interactive"
|
enablePanDownToClose
|
||||||
android_keyboardInputMode="adjustResize"
|
keyboardBehavior="interactive"
|
||||||
keyboardBlurBehavior="restore"
|
android_keyboardInputMode="adjustResize"
|
||||||
topInset={insets.top}
|
keyboardBlurBehavior="restore"
|
||||||
{...sheetOptions}
|
topInset={insets.top}
|
||||||
snapPoints={sheetOptions.snapPoints || ['100%']}
|
{...sheetOptions}
|
||||||
ref={sheet}
|
snapPoints={sheetOptions.snapPoints || ['100%']}
|
||||||
index={openIndex}
|
ref={sheet}
|
||||||
backgroundStyle={{backgroundColor: 'transparent'}}
|
index={openIndex}
|
||||||
backdropComponent={Backdrop}
|
backgroundStyle={{backgroundColor: 'transparent'}}
|
||||||
handleIndicatorStyle={{backgroundColor: t.palette.primary_500}}
|
backdropComponent={Backdrop}
|
||||||
handleStyle={{display: 'none'}}
|
handleIndicatorStyle={{backgroundColor: t.palette.primary_500}}
|
||||||
onClose={onCloseAnimationComplete}>
|
handleStyle={{display: 'none'}}
|
||||||
<Context.Provider value={context}>
|
onClose={onCloseAnimationComplete}>
|
||||||
<View
|
<Context.Provider value={context}>
|
||||||
style={[
|
<View
|
||||||
a.absolute,
|
style={[
|
||||||
a.inset_0,
|
a.absolute,
|
||||||
t.atoms.bg,
|
a.inset_0,
|
||||||
{
|
t.atoms.bg,
|
||||||
borderTopLeftRadius: 40,
|
{
|
||||||
borderTopRightRadius: 40,
|
borderTopLeftRadius: 40,
|
||||||
height: Dimensions.get('window').height * 2,
|
borderTopRightRadius: 40,
|
||||||
},
|
height: Dimensions.get('window').height * 2,
|
||||||
]}
|
},
|
||||||
/>
|
]}
|
||||||
{children}
|
/>
|
||||||
</Context.Provider>
|
{children}
|
||||||
</BottomSheet>
|
</Context.Provider>
|
||||||
</View>
|
</BottomSheet>
|
||||||
|
</View>
|
||||||
|
</FullWindowOverlay>
|
||||||
</Portal>
|
</Portal>
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
export {FullWindowOverlay} from 'react-native-screens'
|
|
@ -0,0 +1 @@
|
||||||
|
export {Fragment as FullWindowOverlay} from 'react'
|
|
@ -181,12 +181,7 @@ export const ComposePost = observer(function ComposePost({
|
||||||
borderColor: interpolateColor(
|
borderColor: interpolateColor(
|
||||||
hasScrolled.value,
|
hasScrolled.value,
|
||||||
[0, 1],
|
[0, 1],
|
||||||
[
|
['transparent', t.atoms.border_contrast_medium.borderColor],
|
||||||
'transparent',
|
|
||||||
isWeb
|
|
||||||
? t.atoms.border_contrast_low.borderColor
|
|
||||||
: t.atoms.border_contrast_high.borderColor,
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -405,106 +400,112 @@ export const ComposePost = observer(function ComposePost({
|
||||||
<KeyboardAvoidingView
|
<KeyboardAvoidingView
|
||||||
testID="composePostView"
|
testID="composePostView"
|
||||||
behavior="padding"
|
behavior="padding"
|
||||||
style={s.flex1}
|
style={a.flex_1}
|
||||||
keyboardVerticalOffset={replyTo ? 60 : isAndroid ? 120 : 100}>
|
keyboardVerticalOffset={replyTo ? 120 : isAndroid ? 180 : 150}>
|
||||||
<View style={[s.flex1, viewStyles]} aria-modal accessibilityViewIsModal>
|
<View
|
||||||
|
style={[a.flex_1, viewStyles]}
|
||||||
|
aria-modal
|
||||||
|
accessibilityViewIsModal>
|
||||||
<Animated.View
|
<Animated.View
|
||||||
style={[
|
style={[
|
||||||
styles.topbar,
|
styles.topbar,
|
||||||
topBarAnimatedStyle,
|
topBarAnimatedStyle,
|
||||||
isWeb && isTabletOrDesktop && styles.topbarDesktop,
|
isWeb && isTabletOrDesktop && styles.topbarDesktop,
|
||||||
]}>
|
]}>
|
||||||
<TouchableOpacity
|
<View style={styles.topbarInner}>
|
||||||
testID="composerDiscardButton"
|
<TouchableOpacity
|
||||||
onPress={onPressCancel}
|
testID="composerDiscardButton"
|
||||||
onAccessibilityEscape={onPressCancel}
|
onPress={onPressCancel}
|
||||||
accessibilityRole="button"
|
onAccessibilityEscape={onPressCancel}
|
||||||
accessibilityLabel={_(msg`Cancel`)}
|
accessibilityRole="button"
|
||||||
accessibilityHint={_(
|
accessibilityLabel={_(msg`Cancel`)}
|
||||||
msg`Closes post composer and discards post draft`,
|
accessibilityHint={_(
|
||||||
)}
|
msg`Closes post composer and discards post draft`,
|
||||||
hitSlop={HITSLOP_10}>
|
|
||||||
<Text style={[pal.link, s.f18]}>
|
|
||||||
<Trans>Cancel</Trans>
|
|
||||||
</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
<View style={s.flex1} />
|
|
||||||
{isProcessing ? (
|
|
||||||
<>
|
|
||||||
<Text style={pal.textLight}>{processingState}</Text>
|
|
||||||
<View style={styles.postBtn}>
|
|
||||||
<ActivityIndicator />
|
|
||||||
</View>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<LabelsBtn
|
|
||||||
labels={labels}
|
|
||||||
onChange={setLabels}
|
|
||||||
hasMedia={hasMedia}
|
|
||||||
/>
|
|
||||||
{canPost ? (
|
|
||||||
<TouchableOpacity
|
|
||||||
testID="composerPublishBtn"
|
|
||||||
onPress={onPressPublish}
|
|
||||||
accessibilityRole="button"
|
|
||||||
accessibilityLabel={
|
|
||||||
replyTo ? _(msg`Publish reply`) : _(msg`Publish post`)
|
|
||||||
}
|
|
||||||
accessibilityHint="">
|
|
||||||
<LinearGradient
|
|
||||||
colors={[
|
|
||||||
gradients.blueLight.start,
|
|
||||||
gradients.blueLight.end,
|
|
||||||
]}
|
|
||||||
start={{x: 0, y: 0}}
|
|
||||||
end={{x: 1, y: 1}}
|
|
||||||
style={styles.postBtn}>
|
|
||||||
<Text style={[s.white, s.f16, s.bold]}>
|
|
||||||
{replyTo ? (
|
|
||||||
<Trans context="action">Reply</Trans>
|
|
||||||
) : (
|
|
||||||
<Trans context="action">Post</Trans>
|
|
||||||
)}
|
|
||||||
</Text>
|
|
||||||
</LinearGradient>
|
|
||||||
</TouchableOpacity>
|
|
||||||
) : (
|
|
||||||
<View style={[styles.postBtn, pal.btn]}>
|
|
||||||
<Text style={[pal.textLight, s.f16, s.bold]}>
|
|
||||||
<Trans context="action">Post</Trans>
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
)}
|
)}
|
||||||
</>
|
hitSlop={HITSLOP_10}>
|
||||||
|
<Text style={[pal.link, s.f18]}>
|
||||||
|
<Trans>Cancel</Trans>
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<View style={a.flex_1} />
|
||||||
|
{isProcessing ? (
|
||||||
|
<>
|
||||||
|
<Text style={pal.textLight}>{processingState}</Text>
|
||||||
|
<View style={styles.postBtn}>
|
||||||
|
<ActivityIndicator />
|
||||||
|
</View>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<LabelsBtn
|
||||||
|
labels={labels}
|
||||||
|
onChange={setLabels}
|
||||||
|
hasMedia={hasMedia}
|
||||||
|
/>
|
||||||
|
{canPost ? (
|
||||||
|
<TouchableOpacity
|
||||||
|
testID="composerPublishBtn"
|
||||||
|
onPress={onPressPublish}
|
||||||
|
accessibilityRole="button"
|
||||||
|
accessibilityLabel={
|
||||||
|
replyTo ? _(msg`Publish reply`) : _(msg`Publish post`)
|
||||||
|
}
|
||||||
|
accessibilityHint="">
|
||||||
|
<LinearGradient
|
||||||
|
colors={[
|
||||||
|
gradients.blueLight.start,
|
||||||
|
gradients.blueLight.end,
|
||||||
|
]}
|
||||||
|
start={{x: 0, y: 0}}
|
||||||
|
end={{x: 1, y: 1}}
|
||||||
|
style={styles.postBtn}>
|
||||||
|
<Text style={[s.white, s.f16, s.bold]}>
|
||||||
|
{replyTo ? (
|
||||||
|
<Trans context="action">Reply</Trans>
|
||||||
|
) : (
|
||||||
|
<Trans context="action">Post</Trans>
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
</LinearGradient>
|
||||||
|
</TouchableOpacity>
|
||||||
|
) : (
|
||||||
|
<View style={[styles.postBtn, pal.btn]}>
|
||||||
|
<Text style={[pal.textLight, s.f16, s.bold]}>
|
||||||
|
<Trans context="action">Post</Trans>
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{isAltTextRequiredAndMissing && (
|
||||||
|
<View style={[styles.reminderLine, pal.viewLight]}>
|
||||||
|
<View style={styles.errorIcon}>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon="exclamation"
|
||||||
|
style={{color: colors.red4}}
|
||||||
|
size={10}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<Text style={[pal.text, a.flex_1]}>
|
||||||
|
<Trans>One or more images is missing alt text.</Trans>
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
{error !== '' && (
|
||||||
|
<View style={styles.errorLine}>
|
||||||
|
<View style={styles.errorIcon}>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon="exclamation"
|
||||||
|
style={{color: colors.red4}}
|
||||||
|
size={10}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<Text style={[s.red4, a.flex_1]}>{error}</Text>
|
||||||
|
</View>
|
||||||
)}
|
)}
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
{isAltTextRequiredAndMissing && (
|
|
||||||
<View style={[styles.reminderLine, pal.viewLight]}>
|
|
||||||
<View style={styles.errorIcon}>
|
|
||||||
<FontAwesomeIcon
|
|
||||||
icon="exclamation"
|
|
||||||
style={{color: colors.red4}}
|
|
||||||
size={10}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
<Text style={[pal.text, s.flex1]}>
|
|
||||||
<Trans>One or more images is missing alt text.</Trans>
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
{error !== '' && (
|
|
||||||
<View style={styles.errorLine}>
|
|
||||||
<View style={styles.errorIcon}>
|
|
||||||
<FontAwesomeIcon
|
|
||||||
icon="exclamation"
|
|
||||||
style={{color: colors.red4}}
|
|
||||||
size={10}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
<Text style={[s.red4, s.flex1]}>{error}</Text>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
<Animated.ScrollView
|
<Animated.ScrollView
|
||||||
onScroll={scrollHandler}
|
onScroll={scrollHandler}
|
||||||
style={styles.scrollView}
|
style={styles.scrollView}
|
||||||
|
@ -576,7 +577,12 @@ export const ComposePost = observer(function ComposePost({
|
||||||
{replyTo ? null : (
|
{replyTo ? null : (
|
||||||
<ThreadgateBtn threadgate={threadgate} onChange={setThreadgate} />
|
<ThreadgateBtn threadgate={threadgate} onChange={setThreadgate} />
|
||||||
)}
|
)}
|
||||||
<View style={[pal.border, styles.bottomBar]}>
|
<View
|
||||||
|
style={[
|
||||||
|
t.atoms.bg,
|
||||||
|
t.atoms.border_contrast_medium,
|
||||||
|
styles.bottomBar,
|
||||||
|
]}>
|
||||||
<View style={[a.flex_row, a.align_center, a.gap_xs]}>
|
<View style={[a.flex_row, a.align_center, a.gap_xs]}>
|
||||||
<SelectPhotoBtn gallery={gallery} disabled={!canSelectImages} />
|
<SelectPhotoBtn gallery={gallery} disabled={!canSelectImages} />
|
||||||
<OpenCameraBtn gallery={gallery} disabled={!canSelectImages} />
|
<OpenCameraBtn gallery={gallery} disabled={!canSelectImages} />
|
||||||
|
@ -598,7 +604,7 @@ export const ComposePost = observer(function ComposePost({
|
||||||
</Button>
|
</Button>
|
||||||
) : null}
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
<View style={s.flex1} />
|
<View style={a.flex_1} />
|
||||||
<SelectLangBtn />
|
<SelectLangBtn />
|
||||||
<CharProgress count={graphemeLength} />
|
<CharProgress count={graphemeLength} />
|
||||||
</View>
|
</View>
|
||||||
|
@ -621,11 +627,6 @@ export function useComposerCancelRef() {
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
topbar: {
|
topbar: {
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
marginHorizontal: 16,
|
|
||||||
height: 54,
|
|
||||||
gap: 4,
|
|
||||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||||
},
|
},
|
||||||
topbarDesktop: {
|
topbarDesktop: {
|
||||||
|
@ -633,6 +634,13 @@ const styles = StyleSheet.create({
|
||||||
paddingBottom: 10,
|
paddingBottom: 10,
|
||||||
height: 50,
|
height: 50,
|
||||||
},
|
},
|
||||||
|
topbarInner: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
paddingHorizontal: 16,
|
||||||
|
height: 54,
|
||||||
|
gap: 4,
|
||||||
|
},
|
||||||
postBtn: {
|
postBtn: {
|
||||||
borderRadius: 20,
|
borderRadius: 20,
|
||||||
paddingHorizontal: 20,
|
paddingHorizontal: 20,
|
||||||
|
@ -643,19 +651,19 @@ const styles = StyleSheet.create({
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
backgroundColor: colors.red1,
|
backgroundColor: colors.red1,
|
||||||
borderRadius: 6,
|
borderRadius: 6,
|
||||||
marginHorizontal: 15,
|
marginHorizontal: 16,
|
||||||
paddingHorizontal: 8,
|
paddingHorizontal: 8,
|
||||||
paddingVertical: 6,
|
paddingVertical: 6,
|
||||||
marginVertical: 6,
|
marginBottom: 8,
|
||||||
},
|
},
|
||||||
reminderLine: {
|
reminderLine: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
borderRadius: 6,
|
borderRadius: 6,
|
||||||
marginHorizontal: 15,
|
marginHorizontal: 16,
|
||||||
paddingHorizontal: 8,
|
paddingHorizontal: 8,
|
||||||
paddingVertical: 6,
|
paddingVertical: 6,
|
||||||
marginBottom: 6,
|
marginBottom: 8,
|
||||||
},
|
},
|
||||||
errorIcon: {
|
errorIcon: {
|
||||||
borderWidth: hairlineWidth,
|
borderWidth: hairlineWidth,
|
||||||
|
@ -690,8 +698,8 @@ const styles = StyleSheet.create({
|
||||||
bottomBar: {
|
bottomBar: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
paddingVertical: 4,
|
paddingVertical: 4,
|
||||||
paddingLeft: 15,
|
paddingLeft: 8,
|
||||||
paddingRight: 20,
|
paddingRight: 16,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
borderTopWidth: hairlineWidth,
|
borderTopWidth: hairlineWidth,
|
||||||
},
|
},
|
||||||
|
|
|
@ -10,7 +10,6 @@ import {
|
||||||
import {msg} from '@lingui/macro'
|
import {msg} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
import {isWeb} from '#/platform/detection'
|
|
||||||
import {sanitizeDisplayName} from 'lib/strings/display-names'
|
import {sanitizeDisplayName} from 'lib/strings/display-names'
|
||||||
import {sanitizeHandle} from 'lib/strings/handles'
|
import {sanitizeHandle} from 'lib/strings/handles'
|
||||||
import {ComposerOptsPostRef} from 'state/shell/composer'
|
import {ComposerOptsPostRef} from 'state/shell/composer'
|
||||||
|
@ -76,10 +75,7 @@ export function ComposerReplyTo({replyTo}: {replyTo: ComposerOptsPostRef}) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Pressable
|
<Pressable
|
||||||
style={[
|
style={[t.atoms.border_contrast_medium, styles.replyToLayout]}
|
||||||
isWeb ? t.atoms.border_contrast_low : t.atoms.border_contrast_high,
|
|
||||||
styles.replyToLayout,
|
|
||||||
]}
|
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
accessibilityRole="button"
|
accessibilityRole="button"
|
||||||
accessibilityLabel={_(
|
accessibilityLabel={_(
|
||||||
|
|
|
@ -7,7 +7,7 @@ import {isNative} from '#/platform/detection'
|
||||||
import {useModalControls} from '#/state/modals'
|
import {useModalControls} from '#/state/modals'
|
||||||
import {ThreadgateSetting} from '#/state/queries/threadgate'
|
import {ThreadgateSetting} from '#/state/queries/threadgate'
|
||||||
import {useAnalytics} from 'lib/analytics/analytics'
|
import {useAnalytics} from 'lib/analytics/analytics'
|
||||||
import {atoms as a} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||||
import {CircleBanSign_Stroke2_Corner0_Rounded as CircleBanSign} from '#/components/icons/CircleBanSign'
|
import {CircleBanSign_Stroke2_Corner0_Rounded as CircleBanSign} from '#/components/icons/CircleBanSign'
|
||||||
import {Earth_Stroke2_Corner0_Rounded as Earth} from '#/components/icons/Globe'
|
import {Earth_Stroke2_Corner0_Rounded as Earth} from '#/components/icons/Globe'
|
||||||
|
@ -22,6 +22,7 @@ export function ThreadgateBtn({
|
||||||
}) {
|
}) {
|
||||||
const {track} = useAnalytics()
|
const {track} = useAnalytics()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
|
const t = useTheme()
|
||||||
const {openModal} = useModalControls()
|
const {openModal} = useModalControls()
|
||||||
|
|
||||||
const onPress = () => {
|
const onPress = () => {
|
||||||
|
@ -45,7 +46,7 @@ export function ThreadgateBtn({
|
||||||
: _(msg`Some people can reply`)
|
: _(msg`Some people can reply`)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[a.flex_row, a.pb_sm, a.px_md]}>
|
<View style={[a.flex_row, a.py_xs, a.px_sm, t.atoms.bg]}>
|
||||||
<Button
|
<Button
|
||||||
variant="solid"
|
variant="solid"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import React, {useEffect, useRef} from 'react'
|
import React, {Fragment, useEffect, useRef} from 'react'
|
||||||
import {StyleSheet} from 'react-native'
|
import {StyleSheet} from 'react-native'
|
||||||
import {SafeAreaView} from 'react-native-safe-area-context'
|
import {SafeAreaView} from 'react-native-safe-area-context'
|
||||||
import BottomSheet from '@discord/bottom-sheet/src'
|
import BottomSheet from '@discord/bottom-sheet/src'
|
||||||
|
|
||||||
import {useModalControls, useModals} from '#/state/modals'
|
import {useModalControls, useModals} from '#/state/modals'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
|
import {FullWindowOverlay} from '#/components/FullWindowOverlay'
|
||||||
import {KeyboardPadding} from '#/components/KeyboardPadding'
|
import {KeyboardPadding} from '#/components/KeyboardPadding'
|
||||||
import {createCustomBackdrop} from '../util/BottomSheetCustomBackdrop'
|
import {createCustomBackdrop} from '../util/BottomSheetCustomBackdrop'
|
||||||
import * as AddAppPassword from './AddAppPasswords'
|
import * as AddAppPassword from './AddAppPasswords'
|
||||||
|
@ -127,24 +128,28 @@ export function ModalsContainer() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Container = activeModal ? FullWindowOverlay : Fragment
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BottomSheet
|
<Container>
|
||||||
ref={bottomSheetRef}
|
<BottomSheet
|
||||||
snapPoints={snapPoints}
|
ref={bottomSheetRef}
|
||||||
handleHeight={HANDLE_HEIGHT}
|
snapPoints={snapPoints}
|
||||||
index={isModalActive ? 0 : -1}
|
handleHeight={HANDLE_HEIGHT}
|
||||||
enablePanDownToClose
|
index={isModalActive ? 0 : -1}
|
||||||
android_keyboardInputMode="adjustResize"
|
enablePanDownToClose
|
||||||
keyboardBlurBehavior="restore"
|
android_keyboardInputMode="adjustResize"
|
||||||
backdropComponent={
|
keyboardBlurBehavior="restore"
|
||||||
isModalActive ? createCustomBackdrop(onClose) : undefined
|
backdropComponent={
|
||||||
}
|
isModalActive ? createCustomBackdrop(onClose) : undefined
|
||||||
handleIndicatorStyle={{backgroundColor: pal.text.color}}
|
}
|
||||||
handleStyle={[styles.handle, pal.view]}
|
handleIndicatorStyle={{backgroundColor: pal.text.color}}
|
||||||
onChange={onBottomSheetChange}>
|
handleStyle={[styles.handle, pal.view]}
|
||||||
{element}
|
onChange={onBottomSheetChange}>
|
||||||
<KeyboardPadding />
|
{element}
|
||||||
</BottomSheet>
|
<KeyboardPadding />
|
||||||
|
</BottomSheet>
|
||||||
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,18 +7,19 @@ import {
|
||||||
View,
|
View,
|
||||||
ViewStyle,
|
ViewStyle,
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import {Text} from '../util/text/Text'
|
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||||
import {s, colors} from 'lib/styles'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
|
import {useLingui} from '@lingui/react'
|
||||||
|
import isEqual from 'lodash.isequal'
|
||||||
|
|
||||||
|
import {useModalControls} from '#/state/modals'
|
||||||
|
import {useMyListsQuery} from '#/state/queries/my-lists'
|
||||||
|
import {ThreadgateSetting} from '#/state/queries/threadgate'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
|
import {colors, s} from 'lib/styles'
|
||||||
import {isWeb} from 'platform/detection'
|
import {isWeb} from 'platform/detection'
|
||||||
import {ScrollView} from 'view/com/modals/util'
|
import {ScrollView} from 'view/com/modals/util'
|
||||||
import {Trans, msg} from '@lingui/macro'
|
import {Text} from '../util/text/Text'
|
||||||
import {useLingui} from '@lingui/react'
|
|
||||||
import {useModalControls} from '#/state/modals'
|
|
||||||
import {ThreadgateSetting} from '#/state/queries/threadgate'
|
|
||||||
import {useMyListsQuery} from '#/state/queries/my-lists'
|
|
||||||
import isEqual from 'lodash.isequal'
|
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
|
||||||
|
|
||||||
export const snapPoints = ['60%']
|
export const snapPoints = ['60%']
|
||||||
|
|
||||||
|
@ -155,7 +156,7 @@ function Selectable({
|
||||||
accessibilityLabel={label}
|
accessibilityLabel={label}
|
||||||
accessibilityHint=""
|
accessibilityHint=""
|
||||||
style={[styles.selectable, pal.border, pal.view, style]}>
|
style={[styles.selectable, pal.border, pal.view, style]}>
|
||||||
<Text type="xl" style={[pal.text]}>
|
<Text type="lg" style={[pal.text]}>
|
||||||
{label}
|
{label}
|
||||||
</Text>
|
</Text>
|
||||||
{isSelected ? (
|
{isSelected ? (
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React, {useMemo} from 'react'
|
import React, {useMemo} from 'react'
|
||||||
import {TouchableWithoutFeedback} from 'react-native'
|
import {TouchableWithoutFeedback} from 'react-native'
|
||||||
import Animated, {
|
import Animated, {
|
||||||
Extrapolate,
|
Extrapolation,
|
||||||
interpolate,
|
interpolate,
|
||||||
useAnimatedStyle,
|
useAnimatedStyle,
|
||||||
} from 'react-native-reanimated'
|
} from 'react-native-reanimated'
|
||||||
|
@ -21,7 +21,7 @@ export function createCustomBackdrop(
|
||||||
animatedIndex.value, // current snap index
|
animatedIndex.value, // current snap index
|
||||||
[-1, 0], // input range
|
[-1, 0], // input range
|
||||||
[0, 0.5], // output range
|
[0, 0.5], // output range
|
||||||
Extrapolate.CLAMP,
|
Extrapolation.CLAMP,
|
||||||
),
|
),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@ import React, {PropsWithChildren, useMemo, useRef} from 'react'
|
||||||
import {
|
import {
|
||||||
Dimensions,
|
Dimensions,
|
||||||
GestureResponderEvent,
|
GestureResponderEvent,
|
||||||
Platform,
|
|
||||||
StyleProp,
|
StyleProp,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
|
@ -11,8 +10,8 @@ import {
|
||||||
View,
|
View,
|
||||||
ViewStyle,
|
ViewStyle,
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
|
import Animated, {FadeIn, FadeInDown, FadeInUp} from 'react-native-reanimated'
|
||||||
import RootSiblings from 'react-native-root-siblings'
|
import RootSiblings from 'react-native-root-siblings'
|
||||||
import {FullWindowOverlay} from 'react-native-screens'
|
|
||||||
import {IconProp} from '@fortawesome/fontawesome-svg-core'
|
import {IconProp} from '@fortawesome/fontawesome-svg-core'
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||||
import {msg} from '@lingui/macro'
|
import {msg} from '@lingui/macro'
|
||||||
|
@ -23,6 +22,8 @@ import {usePalette} from 'lib/hooks/usePalette'
|
||||||
import {colors} from 'lib/styles'
|
import {colors} from 'lib/styles'
|
||||||
import {useTheme} from 'lib/ThemeContext'
|
import {useTheme} from 'lib/ThemeContext'
|
||||||
import {isWeb} from 'platform/detection'
|
import {isWeb} from 'platform/detection'
|
||||||
|
import {native} from '#/alf'
|
||||||
|
import {FullWindowOverlay} from '#/components/FullWindowOverlay'
|
||||||
import {Text} from '../text/Text'
|
import {Text} from '../text/Text'
|
||||||
import {Button, ButtonType} from './Button'
|
import {Button, ButtonType} from './Button'
|
||||||
|
|
||||||
|
@ -127,6 +128,7 @@ export function DropdownButton({
|
||||||
pageY,
|
pageY,
|
||||||
menuWidth,
|
menuWidth,
|
||||||
items.filter(v => !!v) as DropdownItem[],
|
items.filter(v => !!v) as DropdownItem[],
|
||||||
|
openUpwards,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -181,6 +183,7 @@ function createDropdownMenu(
|
||||||
pageY: number,
|
pageY: number,
|
||||||
width: number,
|
width: number,
|
||||||
items: DropdownItem[],
|
items: DropdownItem[],
|
||||||
|
opensUpwards = false,
|
||||||
): RootSiblings {
|
): RootSiblings {
|
||||||
const onPressItem = (index: number) => {
|
const onPressItem = (index: number) => {
|
||||||
sibling.destroy()
|
sibling.destroy()
|
||||||
|
@ -200,6 +203,7 @@ function createDropdownMenu(
|
||||||
width={width}
|
width={width}
|
||||||
items={items}
|
items={items}
|
||||||
onPressItem={onPressItem}
|
onPressItem={onPressItem}
|
||||||
|
openUpwards={opensUpwards}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -214,6 +218,7 @@ type DropDownItemProps = {
|
||||||
width: number
|
width: number
|
||||||
items: DropdownItem[]
|
items: DropdownItem[]
|
||||||
onPressItem: (index: number) => void
|
onPressItem: (index: number) => void
|
||||||
|
openUpwards: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const DropdownItems = ({
|
const DropdownItems = ({
|
||||||
|
@ -224,6 +229,7 @@ const DropdownItems = ({
|
||||||
width,
|
width,
|
||||||
items,
|
items,
|
||||||
onPressItem,
|
onPressItem,
|
||||||
|
openUpwards,
|
||||||
}: DropDownItemProps) => {
|
}: DropDownItemProps) => {
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
|
@ -242,13 +248,14 @@ const DropdownItems = ({
|
||||||
// - (On mobile) be buttons by default, accept `label` and `nativeID`
|
// - (On mobile) be buttons by default, accept `label` and `nativeID`
|
||||||
// props, and always have an explicit label
|
// props, and always have an explicit label
|
||||||
return (
|
return (
|
||||||
<Wrapper>
|
<FullWindowOverlay>
|
||||||
{/* This TouchableWithoutFeedback renders the background so if the user clicks outside, the dropdown closes */}
|
{/* This TouchableWithoutFeedback renders the background so if the user clicks outside, the dropdown closes */}
|
||||||
<TouchableWithoutFeedback
|
<TouchableWithoutFeedback
|
||||||
onPress={onOuterPress}
|
onPress={onOuterPress}
|
||||||
accessibilityLabel={_(msg`Toggle dropdown`)}
|
accessibilityLabel={_(msg`Toggle dropdown`)}
|
||||||
accessibilityHint="">
|
accessibilityHint="">
|
||||||
<View
|
<Animated.View
|
||||||
|
entering={FadeIn}
|
||||||
style={[
|
style={[
|
||||||
styles.bg,
|
styles.bg,
|
||||||
// On web we need to adjust the top and bottom relative to the scroll position
|
// On web we need to adjust the top and bottom relative to the scroll position
|
||||||
|
@ -264,7 +271,10 @@ const DropdownItems = ({
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</TouchableWithoutFeedback>
|
</TouchableWithoutFeedback>
|
||||||
<View
|
<Animated.View
|
||||||
|
entering={native(
|
||||||
|
openUpwards ? FadeInDown.springify(1000) : FadeInUp.springify(1000),
|
||||||
|
)}
|
||||||
style={[
|
style={[
|
||||||
styles.menu,
|
styles.menu,
|
||||||
{left: x, top: y, width},
|
{left: x, top: y, width},
|
||||||
|
@ -306,18 +316,11 @@ const DropdownItems = ({
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
})}
|
})}
|
||||||
</View>
|
</Animated.View>
|
||||||
</Wrapper>
|
</FullWindowOverlay>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// on iOS, due to formSheet presentation style, we need to render the overlay
|
|
||||||
// as a full screen overlay
|
|
||||||
const Wrapper = Platform.select({
|
|
||||||
ios: FullWindowOverlay,
|
|
||||||
default: ({children}) => <>{children}</>,
|
|
||||||
})
|
|
||||||
|
|
||||||
function isSep(item: DropdownItem): item is DropdownItemSeparator {
|
function isSep(item: DropdownItem): item is DropdownItemSeparator {
|
||||||
return 'sep' in item && item.sep
|
return 'sep' in item && item.sep
|
||||||
}
|
}
|
||||||
|
@ -333,14 +336,12 @@ const styles = StyleSheet.create({
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
left: 0,
|
left: 0,
|
||||||
width: '100%',
|
width: '100%',
|
||||||
backgroundColor: '#000',
|
backgroundColor: 'rgba(0, 0, 0, 0.1)',
|
||||||
opacity: 0.1,
|
|
||||||
},
|
},
|
||||||
menu: {
|
menu: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
backgroundColor: '#fff',
|
backgroundColor: '#fff',
|
||||||
borderRadius: 14,
|
borderRadius: 14,
|
||||||
opacity: 1,
|
|
||||||
paddingVertical: 6,
|
paddingVertical: 6,
|
||||||
},
|
},
|
||||||
menuItem: {
|
menuItem: {
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import React, {useLayoutEffect} from 'react'
|
import React, {useLayoutEffect} from 'react'
|
||||||
import {Modal, View} from 'react-native'
|
import {Modal, View} from 'react-native'
|
||||||
|
import {GestureHandlerRootView} from 'react-native-gesture-handler'
|
||||||
|
import {RootSiblingParent} from 'react-native-root-siblings'
|
||||||
import {StatusBar} from 'expo-status-bar'
|
import {StatusBar} from 'expo-status-bar'
|
||||||
import * as SystemUI from 'expo-system-ui'
|
import * as SystemUI from 'expo-system-ui'
|
||||||
import {observer} from 'mobx-react-lite'
|
import {observer} from 'mobx-react-lite'
|
||||||
|
@ -34,27 +36,56 @@ export const Composer = observer(function ComposerImpl({}: {
|
||||||
animationType="slide"
|
animationType="slide"
|
||||||
onRequestClose={() => ref.current?.onPressCancel()}>
|
onRequestClose={() => ref.current?.onPressCancel()}>
|
||||||
<View style={[t.atoms.bg, a.flex_1]}>
|
<View style={[t.atoms.bg, a.flex_1]}>
|
||||||
<LegacyModalProvider>
|
<Providers open={open}>
|
||||||
<PortalProvider>
|
<ComposePost
|
||||||
<ComposePost
|
cancelRef={ref}
|
||||||
cancelRef={ref}
|
replyTo={state?.replyTo}
|
||||||
replyTo={state?.replyTo}
|
onPost={state?.onPost}
|
||||||
onPost={state?.onPost}
|
quote={state?.quote}
|
||||||
quote={state?.quote}
|
mention={state?.mention}
|
||||||
mention={state?.mention}
|
text={state?.text}
|
||||||
text={state?.text}
|
imageUris={state?.imageUris}
|
||||||
imageUris={state?.imageUris}
|
/>
|
||||||
/>
|
</Providers>
|
||||||
<LegacyModalsContainer />
|
|
||||||
<PortalOutlet />
|
|
||||||
</PortalProvider>
|
|
||||||
</LegacyModalProvider>
|
|
||||||
{isIOS && <IOSModalBackground active={open} />}
|
|
||||||
</View>
|
</View>
|
||||||
</Modal>
|
</Modal>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function Providers({
|
||||||
|
children,
|
||||||
|
open,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode
|
||||||
|
open: boolean
|
||||||
|
}) {
|
||||||
|
// on iOS, it's a native formSheet. We use FullWindowOverlay to make
|
||||||
|
// the dialogs appear over it
|
||||||
|
if (isIOS) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{children}
|
||||||
|
<IOSModalBackground active={open} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// on Android we just nest the dialogs within it
|
||||||
|
return (
|
||||||
|
<GestureHandlerRootView style={a.flex_1}>
|
||||||
|
<RootSiblingParent>
|
||||||
|
<LegacyModalProvider>
|
||||||
|
<PortalProvider>
|
||||||
|
{children}
|
||||||
|
<LegacyModalsContainer />
|
||||||
|
<PortalOutlet />
|
||||||
|
</PortalProvider>
|
||||||
|
</LegacyModalProvider>
|
||||||
|
</RootSiblingParent>
|
||||||
|
</GestureHandlerRootView>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Generally, the backdrop of the app is the theme color, but when this is open
|
// Generally, the backdrop of the app is the theme color, but when this is open
|
||||||
// we want it to be black due to the modal being a form sheet.
|
// we want it to be black due to the modal being a form sheet.
|
||||||
function IOSModalBackground({active}: {active: boolean}) {
|
function IOSModalBackground({active}: {active: boolean}) {
|
||||||
|
|
Loading…
Reference in New Issue