Settle the compose pill: border follows the pill, less width change, light tint
- Blend the bar's top border away from the pill's own visibility rather than a per-screen border contribution. The latter was faded on a spring when a screen was popped, so the border flashed back mid-pop between two screens that both show the pill. - Give the current label a small head start when picking the most present screen, so the two cannot trade places repeatedly as they cross. - Grow the up-state side inset to 16pt so the pill changes width less on its way down to the safe-area inset. - Add a half-strength tint to the clear glass. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QoggCVt9XgpKbjrTZjoJD9
This commit is contained in:
@@ -15,7 +15,7 @@ import {useSession} from '#/state/session'
|
||||
import {useMinimalShellMode} from '#/state/shell/minimal-mode'
|
||||
import {useShellLayout} from '#/state/shell/shell-layout'
|
||||
import {UserAvatar} from '#/view/com/util/UserAvatar'
|
||||
import {atoms as a, ios, tokens, useTheme} from '#/alf'
|
||||
import {atoms as a, ios, tokens, useTheme, utils} from '#/alf'
|
||||
import {Button} from '#/components/Button'
|
||||
import {GlassView, IS_GLASS_AVAILABLE} from '#/components/GlassView'
|
||||
import {Camera_Stroke2_Corner0_Rounded as CameraIcon} from '#/components/icons/Camera'
|
||||
@@ -28,9 +28,11 @@ import {useComposePromptMedia} from './useComposePromptMedia'
|
||||
|
||||
const AVATAR_SIZE = 32
|
||||
/**
|
||||
* Horizontal inset of the pill while the bottom bar is showing.
|
||||
* Horizontal inset of the pill while the bottom bar is showing. Once the bar
|
||||
* hides, the inset grows to the bottom safe area for equal spacing, so the
|
||||
* closer this is to that, the less the pill changes width on the way down.
|
||||
*/
|
||||
const SIDE_MARGIN_UP = tokens.space.sm
|
||||
const SIDE_MARGIN_UP = tokens.space.lg
|
||||
/**
|
||||
* Gap between the pill and the top of the bottom bar.
|
||||
*/
|
||||
@@ -145,6 +147,7 @@ export function ComposePromptPill() {
|
||||
style: glassVisible ? 'clear' : 'none',
|
||||
animate: true,
|
||||
}}
|
||||
tintColor={utils.alpha(t.palette.contrast_50, 0.5)}
|
||||
style={[a.rounded_full]}
|
||||
fallbackStyle={[
|
||||
a.border,
|
||||
|
||||
@@ -19,7 +19,6 @@ import {
|
||||
import {scheduleOnRN} from 'react-native-worklets'
|
||||
|
||||
import {SHELL_SPRING_CONFIG} from '#/lib/custom-animations/springs'
|
||||
import {useHideBottomBarBorderForScreen} from '#/lib/hooks/useHideBottomBarBorder'
|
||||
import {
|
||||
useScreenCoverage,
|
||||
useScreenPresence,
|
||||
@@ -101,10 +100,12 @@ const actionsContext = createContext<ActionsContext | null>(null)
|
||||
actionsContext.displayName = 'ComposePromptActionsContext'
|
||||
|
||||
let nextId = 0
|
||||
const LABEL_SWITCH_MARGIN = 0.1
|
||||
|
||||
export function Provider({children}: {children: React.ReactNode}) {
|
||||
const [entries, setEntries] = useState<Entry[]>([])
|
||||
const [activeId, setActiveId] = useState<number | null>(null)
|
||||
const activeIdOnUI = useSharedValue<number | null>(null)
|
||||
const coverage = useScreenCoverage()
|
||||
|
||||
const visibility = useDerivedValue(() => {
|
||||
@@ -118,13 +119,17 @@ export function Provider({children}: {children: React.ReactNode}) {
|
||||
/*
|
||||
* The pill shows the label of whichever screen is the most present, so
|
||||
* that during a push or swipe-back the text switches at the midpoint of
|
||||
* the transition rather than when the outgoing screen unmounts.
|
||||
* the transition rather than when the outgoing screen unmounts. The current
|
||||
* label gets a small head start so the two cannot trade places repeatedly
|
||||
* as they cross.
|
||||
*/
|
||||
const mostPresentId = useDerivedValue(() => {
|
||||
let best: number | null = null
|
||||
let bestPresence = 0
|
||||
for (const entry of entries) {
|
||||
const presence = entryPresence(entry, coverage.get())
|
||||
const presence =
|
||||
entryPresence(entry, coverage.get()) +
|
||||
(entry.id === activeIdOnUI.get() ? LABEL_SWITCH_MARGIN : 0)
|
||||
if (presence > bestPresence) {
|
||||
best = entry.id
|
||||
bestPresence = presence
|
||||
@@ -138,6 +143,7 @@ export function Provider({children}: {children: React.ReactNode}) {
|
||||
(current, previous) => {
|
||||
// keep the last label while everything fades out
|
||||
if (current !== previous && current !== null) {
|
||||
activeIdOnUI.set(current)
|
||||
scheduleOnRN(setActiveId, current)
|
||||
}
|
||||
},
|
||||
@@ -223,9 +229,6 @@ export function useComposePromptForScreen(config: ComposePromptConfig | null) {
|
||||
const idRef = useRef<number | null>(null)
|
||||
const enabled = config !== null
|
||||
|
||||
// the bar's top border would cut across the pill's gradient
|
||||
useHideBottomBarBorderForScreen({enabled})
|
||||
|
||||
const getConfig = useEffectEvent(() => config)
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -36,7 +36,7 @@ function useHideBottomBarBorderSetter() {
|
||||
* Hides the bottom bar's top border while the surrounding screen is present,
|
||||
* fading it with the screen transition.
|
||||
*/
|
||||
export function useHideBottomBarBorderForScreen({enabled} = {enabled: true}) {
|
||||
export function useHideBottomBarBorderForScreen() {
|
||||
const register = useHideBottomBarBorderSetter()
|
||||
const {presence} = useScreenPresence()
|
||||
const contribution = useSharedValue(0)
|
||||
@@ -44,18 +44,13 @@ export function useHideBottomBarBorderForScreen({enabled} = {enabled: true}) {
|
||||
useAnimatedReaction(
|
||||
() => presence.get(),
|
||||
(current, previous) => {
|
||||
if (enabled && current !== previous) {
|
||||
if (current !== previous) {
|
||||
contribution.set(current)
|
||||
}
|
||||
},
|
||||
[enabled],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (!enabled) return
|
||||
contribution.set(presence.get())
|
||||
return register(contribution)
|
||||
}, [enabled, register, contribution, presence])
|
||||
useEffect(() => register(contribution), [register, contribution])
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,16 +68,17 @@ export function useHideBottomBarBorder() {
|
||||
|
||||
/**
|
||||
* Animated border color for the bottom bar, blending the border into the
|
||||
* background as screens that hide it come and go.
|
||||
* background as screens that hide it come and go. `alsoHidden` is a further
|
||||
* 0..1 hidden amount to combine in, for shell UI that sits on the bar.
|
||||
*/
|
||||
export function useBottomBarBorderStyle() {
|
||||
export function useBottomBarBorderStyle(alsoHidden?: SharedValue<number>) {
|
||||
const t = useTheme()
|
||||
const hideBorder = useHideBottomBarBorder()
|
||||
const visibleColor = t.atoms.border_contrast_low.borderColor
|
||||
const hiddenColor = t.atoms.bg.backgroundColor
|
||||
return useAnimatedStyle(() => ({
|
||||
borderColor: interpolateColor(
|
||||
hideBorder.get(),
|
||||
Math.max(hideBorder.get(), alsoHidden?.get() ?? 0),
|
||||
[0, 1],
|
||||
[visibleColor, hiddenColor],
|
||||
),
|
||||
|
||||
@@ -59,6 +59,7 @@ import {useAnalytics} from '#/analytics'
|
||||
import {
|
||||
ComposePromptGradient,
|
||||
ComposePromptPill,
|
||||
useComposePromptState,
|
||||
} from '#/features/composePrompt'
|
||||
import {useActorStatus} from '#/features/liveNow'
|
||||
import {useDemoMode} from '#/storage/hooks/demo-mode'
|
||||
@@ -84,7 +85,9 @@ export function BottomBar({navigation}: BottomTabBarProps) {
|
||||
const accountSwitchControl = useDialogControl()
|
||||
const messagesMenuControl = Menu.useMenuControl()
|
||||
const playHaptic = useHaptics()
|
||||
const borderStyle = useBottomBarBorderStyle()
|
||||
const {visibility: composePromptVisibility} = useComposePromptState()
|
||||
// the border would cut across the compose pill's gradient
|
||||
const borderStyle = useBottomBarBorderStyle(composePromptVisibility)
|
||||
const iconWidth = 28
|
||||
|
||||
const showSignIn = useCallback(() => {
|
||||
|
||||
@@ -48,6 +48,7 @@ import {useAnalytics} from '#/analytics'
|
||||
import {
|
||||
ComposePromptGradient,
|
||||
ComposePromptPill,
|
||||
useComposePromptState,
|
||||
} from '#/features/composePrompt'
|
||||
import {styles} from './BottomBarStyles'
|
||||
|
||||
@@ -61,7 +62,9 @@ export function BottomBarWeb() {
|
||||
const {requestSwitchToAccount} = useLoggedOutViewControls()
|
||||
const closeAllActiveElements = useCloseAllActiveElements()
|
||||
const {footerHeight} = useShellLayout()
|
||||
const borderStyle = useBottomBarBorderStyle()
|
||||
const {visibility: composePromptVisibility} = useComposePromptState()
|
||||
// the border would cut across the compose pill's gradient
|
||||
const borderStyle = useBottomBarBorderStyle(composePromptVisibility)
|
||||
const accountSwitchControl = useDialogControl()
|
||||
const {data: profile} = useProfileQuery({did: currentAccount?.did})
|
||||
const iconWidth = 26
|
||||
|
||||
Reference in New Issue
Block a user