fix: keep the notification prompt clear of the reader's bottom bar on tablets

This commit is contained in:
Linus Rath
2026-10-31 13:54:48 +01:00
parent ce21f273a3
commit e0184cfb46
3 changed files with 42 additions and 2 deletions
+5 -2
View File
@@ -1,7 +1,8 @@
import React from 'react';
import { View, Text, StyleSheet, Pressable, Animated, Easing } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { useFloatingCardFrame } from '../lib/use-layout';
import { useFloatingCardFrame, useLayout } from '../lib/use-layout';
import { pushPromptBottom } from '../lib/push-prompt-frame';
import { BellRing, X } from './icons';
import Button from './Button';
import { spacing, radius, typography, type ThemePalette } from '../theme/tokens';
@@ -52,6 +53,8 @@ export function PushOnboardingPrompt(): React.ReactElement | null {
const insets = useSafeAreaInsets();
// Full width above the tab bar on a phone; bottom right on a tablet.
const frame = useFloatingCardFrame();
// Above the reading pane's bottom bar on a split view.
const { isSplit } = useLayout();
const duration = useAnimDuration(200);
const appear = React.useRef(new Animated.Value(0)).current;
@@ -118,7 +121,7 @@ export function PushOnboardingPrompt(): React.ReactElement | null {
style={[
styles.wrap,
{
left: frame.left, right: frame.right, bottom: insets.bottom + frame.bottom,
left: frame.left, right: frame.right, bottom: insets.bottom + pushPromptBottom(frame.bottom, isSplit),
opacity: appear,
transform: [{ translateY: appear.interpolate({ inputRange: [0, 1], outputRange: [24, 0] }) }],
},
@@ -0,0 +1,24 @@
import { describe, expect, it } from 'vitest';
import fs from 'node:fs';
import path from 'node:path';
import { pushPromptBottom, READER_BAR_CLEARANCE } from '../push-prompt-frame';
// On a tablet split view the notification prompt sat in the bottom right
// corner, over the reading pane's Prev / Reply / Forward bar, until it was
// dismissed.
describe('push prompt placement', () => {
it('sits above the reading pane bar on a split view', () => {
expect(pushPromptBottom(20, true)).toBe(20 + READER_BAR_CLEARANCE);
expect(READER_BAR_CLEARANCE).toBeGreaterThanOrEqual(64);
});
it('keeps the toast placement above the tab bar on a phone', () => {
expect(pushPromptBottom(64, false)).toBe(64);
});
it('is what the prompt uses', () => {
const src = fs.readFileSync(path.resolve(__dirname, '..', '..', 'components/PushOnboardingPrompt.tsx'), 'utf8');
expect(src).toMatch(/bottom: insets\.bottom \+ pushPromptBottom\(frame\.bottom, isSplit\)/);
});
});
+13
View File
@@ -0,0 +1,13 @@
// Where the push notification prompt floats. It takes the toast placement
// (lib/use-layout floatingCardFrame), but stays until it is answered: on a
// tablet split view the bottom right corner is the reading pane's Prev /
// Reply / Forward bar, which the card covered until it was dismissed. There
// it sits above that bar.
/** The reader's bottom bar (EmailThreadScreen: 60 + at least 4 for the inset) and a gap. */
export const READER_BAR_CLEARANCE = 64;
/** The prompt's distance from the bottom, before the safe-area inset. */
export function pushPromptBottom(frameBottom: number, split: boolean): number {
return split ? frameBottom + READER_BAR_CLEARANCE : frameBottom;
}