fix: keep screens clear of the camera cutout and side navigation bar in landscape

This commit is contained in:
Linus Rath
2026-10-15 03:29:59 +02:00
parent d7cee7ca78
commit e19229a5c6
10 changed files with 20 additions and 20 deletions
+5 -1
View File
@@ -1,4 +1,5 @@
import React from 'react';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { Animated, Easing, Pressable, StyleSheet, Text, View } from 'react-native';
import { CalendarPlus, CircleCheck, Plus } from 'lucide-react-native';
import { componentSizes, radius, spacing, typography, type ThemePalette } from '../../theme/tokens';
@@ -39,6 +40,8 @@ export function CalendarFab({ onNewEvent, onNewTask }: CalendarFabProps) {
action();
};
const insets = useSafeAreaInsets();
const onMainPress = () => {
if (!onNewTask) {
onNewEvent();
@@ -63,7 +66,8 @@ export function CalendarFab({ onNewEvent, onNewTask }: CalendarFabProps) {
/>
</Animated.View>
)}
<View style={styles.anchor} pointerEvents="box-none">
{/* Clear of a side inset (camera cutout, navigation bar) in landscape. */}
<View style={[styles.anchor, { right: spacing.lg + insets.right }]} pointerEvents="box-none">
{open && onNewTask && (
<Animated.View style={[styles.option, { opacity: progress, transform: [{ translateY: lift }] }]}>
<Text style={styles.optionLabel}>{t('calendar.fab.task', 'Task')}</Text>
+1 -1
View File
@@ -988,7 +988,7 @@ export default function CalendarScreen() {
const titleDate = (monthPanelOpen && panelMonth) || visibleDate || focus.date;
return (
<SafeAreaView style={styles.container} edges={['top']}>
<SafeAreaView style={styles.container} edges={['top', 'left', 'right']}>
<View style={styles.header}>
<Pressable
onPress={() => setSidebarVisible(true)}
+1 -1
View File
@@ -361,7 +361,7 @@ export default function ContactDetailScreen() {
].filter(Boolean) as MoreItem[];
return (
<SafeAreaView style={styles.container} edges={['top']}>
<SafeAreaView style={styles.container} edges={['top', 'left', 'right']}>
<View style={[styles.header, inPane && styles.headerInPane]}>
{/* In a split view's pane the list is right next to it: no back. */}
{!inPane && (
+2 -2
View File
@@ -1070,7 +1070,7 @@ export default function ContactFormScreen() {
if (asGroup) {
// Groups are a name plus a member list - none of the person fields apply.
return (
<SafeAreaView style={styles.container} edges={['top']}>
<SafeAreaView style={styles.container} edges={['top', 'left', 'right']}>
{header}
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
@@ -1131,7 +1131,7 @@ export default function ContactFormScreen() {
}
return (
<SafeAreaView style={styles.container} edges={['top']}>
<SafeAreaView style={styles.container} edges={['top', 'left', 'right']}>
{header}
<KeyboardAvoidingView
+1 -1
View File
@@ -381,7 +381,7 @@ export default function ContactsScreen({ onOpenContact, activeContactId = null }
}, [addressBooks, groups, selectedCategory, t]);
return (
<SafeAreaView style={styles.container} edges={['top']}>
<SafeAreaView style={styles.container} edges={['top', 'left', 'right']}>
<ContactsSidebarDrawer visible={drawerOpen} onClose={() => setDrawerOpen(false)} />
<View style={styles.header}>
+1 -1
View File
@@ -1151,7 +1151,7 @@ export default function EmailListScreen({
);
return (
<View style={[styles.container, { paddingTop: insets.top }]}>
<View style={[styles.container, { paddingTop: insets.top, paddingLeft: insets.left, paddingRight: insets.right }]}>
{/* Header */}
{selectionMode ? (
<View style={styles.header}>
+1 -1
View File
@@ -65,7 +65,7 @@ export default function EmailSourceScreen({ route, navigation }: Props) {
};
return (
<SafeAreaView style={styles.container} edges={['top']}>
<SafeAreaView style={styles.container} edges={['top', 'left', 'right']}>
<View style={styles.header}>
<Pressable
onPress={() => navigation.goBack()}
+2 -2
View File
@@ -689,7 +689,7 @@ function EmailViewer({ route, navigation, embedded = false, onActiveChange }: Pr
: false;
return (
<SafeAreaView style={styles.container} edges={['top']}>
<SafeAreaView style={styles.container} edges={['top', 'left', 'right']}>
{/* Toolbar */}
<View style={[styles.toolbar, embedded && styles.toolbarEmbedded]}>
{!embedded && (
@@ -827,7 +827,7 @@ function EmailViewer({ route, navigation, embedded = false, onActiveChange }: Pr
{/* Bottom action bar (hidden while keyboard is up so quick reply stays visible) */}
{kbObstruction === 0 && (
<View style={[styles.bottomBar, { paddingBottom: Math.max(insets.bottom, 4) }]}>
<View style={[styles.bottomBar, { paddingBottom: Math.max(insets.bottom, 4), paddingLeft: insets.left, paddingRight: insets.right }]}>
<BottomBarButton
icon={<ChevronLeft size={20} color={c.textMuted} />}
label={t('email_viewer.previous', 'Prev')}
+4 -8
View File
@@ -168,14 +168,10 @@ export default function FilesScreen() {
const styles = React.useMemo(() => makeStyles(c), [c]);
const t = useLocaleStore((s) => s.t);
const formatModified = useFormatModified();
// Clear of the status bar and, in landscape, the side insets; the header
// used a fixed 60 px top padding instead.
// Clear of the status bar (the header used a fixed 60 px top padding); the
// whole screen keeps clear of the side insets in landscape.
const insets = useSafeAreaInsets();
const headerInsets = {
paddingTop: insets.top + spacing.sm,
paddingLeft: spacing.md + insets.left,
paddingRight: spacing.md + insets.right,
};
const headerInsets = { paddingTop: insets.top + spacing.sm, paddingHorizontal: spacing.md };
// Folder navigation stack. Hierarchy is real parentId nesting (#379) — each
// entry is the folder's node id (namespaced "accountId:nodeId" inside a
@@ -1035,7 +1031,7 @@ export default function FilesScreen() {
// On a wide window an open file shows beside the list instead of covering it.
const previewDocked = isSplit && previewTarget != null;
return (
<View style={[styles.container, previewDocked && styles.splitRow]}>
<View style={[styles.container, previewDocked && styles.splitRow, { paddingLeft: insets.left, paddingRight: insets.right }]}>
<View style={styles.listColumn}>
{renderHeader()}
{body}
+2 -2
View File
@@ -62,7 +62,7 @@ export default function GroupDetailScreen() {
if (!group) {
return (
<SafeAreaView style={styles.container} edges={['top']}>
<SafeAreaView style={styles.container} edges={['top', 'left', 'right']}>
<View style={styles.header}>
<Pressable
onPress={() => navigation.goBack()}
@@ -133,7 +133,7 @@ export default function GroupDetailScreen() {
};
return (
<SafeAreaView style={styles.container} edges={['top']}>
<SafeAreaView style={styles.container} edges={['top', 'left', 'right']}>
<View style={[styles.header, inPane && styles.headerInPane]}>
{/* In a split view's pane the list is right next to it: no back. */}
{!inPane && (