Skip to content

Commit e71681d

Browse files
authored
feat(mobile): iOS 26 header compatibility and header icon fixes (#2523)
* feat(mobile): add iOS 26 header compatibility Disable headerBlurEffect on iOS 26 where the system provides native Liquid Glass blur. Add isIOS26 constant in lib/ios.ts for platform detection. * fix(mobile): remove excess padding and use theme colors for header right icons Remove px-4 from header right icon wrappers that caused oval-shaped tap targets. Use theme foreground color instead of hardcoded blue/gray for header icons so they match the navigation bar style. * Upgrade react-native-screens to fix icon alignment issue in round iOS 26 right header buttons * fix padding in text header right buttons * fix(mobile): patch react-native-screens 4.19.0 to fix Android header inset react-native-screens 4.19.0 (PR #3240) changed CustomToolbar to use ancestor-propagated insets instead of rootWindowInsets. This conflicts with react-native-keyboard-controller's rootView inset listener, which modifies insets before they reach the toolbar, causing the Android header to be too short (missing status bar height). Apply upstream fix from software-mansion/react-native-screens#3500: read the top inset directly from the DecorView instead of relying on ancestor-propagated insets which may have been consumed earlier. This patch can be removed once #3500 is merged and released upstream. * fix(mobile): patch react-native-keyboard-controller instead of react-native-screens for Android header inset The root cause of the Android header inset bug is in react-native-keyboard-controller, not react-native-screens. When edge-to-edge mode is enabled (Expo 55 default), KeyboardProvider forces statusBarTranslucent=true, and replaceStatusBarInsets() zeroes out the top system bar inset before dispatching to child views. This means react-native-screens' CustomToolbar receives top=0 and applies no status bar padding. The fix: always pass through the real system bar top inset in replaceStatusBarInsets(). The content margin in setupWindowInsets() already handles the layout offset independently. This replaces the previous react-native-screens patch (upstream PR #3500) with a 2-line fix at the actual root cause. Upstream issue: kirillzyusko/react-native-keyboard-controller#1013 Related: kirillzyusko/react-native-keyboard-controller#1292 * fix lockfile
1 parent 2702f0f commit e71681d

18 files changed

Lines changed: 104 additions & 44 deletions

File tree

apps/mobile/app/_layout.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { ShareIntentProvider, useShareIntent } from "expo-share-intent";
1212
import { StatusBar } from "expo-status-bar";
1313
import { StyledStack } from "@/components/navigation/stack";
1414
import SplashScreenController from "@/components/SplashScreenController";
15+
import { isIOS26 } from "@/lib/ios";
1516
import { Providers } from "@/lib/providers";
1617
import { useColorScheme, useInitialAndroidBarSync } from "@/lib/useColorScheme";
1718
import { cn } from "@/lib/utils";
@@ -70,7 +71,7 @@ export default Sentry.wrap(function RootLayout() {
7071
...Platform.select({
7172
ios: {
7273
headerTransparent: true,
73-
headerBlurEffect: "systemMaterial",
74+
headerBlurEffect: isIOS26 ? undefined : "systemMaterial",
7475
headerLargeTitle: true,
7576
headerLargeTitleShadowVisible: false,
7677
headerLargeStyle: { backgroundColor: "transparent" },

apps/mobile/app/dashboard/(tabs)/(highlights)/_layout.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Platform } from "react-native";
22
import { Stack } from "expo-router/stack";
3+
import { isIOS26 } from "@/lib/ios";
34

45
export default function Layout() {
56
return (
@@ -9,7 +10,7 @@ export default function Layout() {
910
ios: {
1011
headerLargeTitle: true,
1112
headerTransparent: true,
12-
headerBlurEffect: "systemMaterial",
13+
headerBlurEffect: isIOS26 ? undefined : "systemMaterial",
1314
headerLargeTitleShadowVisible: false,
1415
headerLargeStyle: { backgroundColor: "transparent" },
1516
},

apps/mobile/app/dashboard/(tabs)/(home)/_layout.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Platform } from "react-native";
22
import { Stack } from "expo-router/stack";
3+
import { isIOS26 } from "@/lib/ios";
34

45
export default function Layout() {
56
return (
@@ -9,7 +10,7 @@ export default function Layout() {
910
ios: {
1011
headerLargeTitle: true,
1112
headerTransparent: true,
12-
headerBlurEffect: "systemMaterial",
13+
headerBlurEffect: isIOS26 ? undefined : "systemMaterial",
1314
headerLargeTitleShadowVisible: false,
1415
headerLargeStyle: { backgroundColor: "transparent" },
1516
},

apps/mobile/app/dashboard/(tabs)/(home)/index.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { TailwindResolver } from "@/components/TailwindResolver";
88
import { Text } from "@/components/ui/Text";
99
import useAppSettings from "@/lib/settings";
1010
import { useUploadAsset } from "@/lib/upload";
11+
import { useColorScheme } from "@/lib/useColorScheme";
1112
import { useMenuIconColors } from "@/lib/useMenuIconColors";
1213
import { MenuView } from "@react-native-menu/menu";
1314
import { Plus, Search } from "lucide-react-native";
@@ -19,6 +20,7 @@ function HeaderRight({
1920
openNewBookmarkModal: () => void;
2021
}) {
2122
const { settings } = useAppSettings();
23+
const { colors } = useColorScheme();
2224
const { menuIconColor } = useMenuIconColors();
2325
const uploadToastIdRef = useRef<string | number | null>(null);
2426
const { uploadAsset } = useUploadAsset(settings, {
@@ -99,9 +101,9 @@ function HeaderRight({
99101
]}
100102
shouldOpenOnLongPress={false}
101103
>
102-
<View className="my-auto px-4">
104+
<View className="my-auto">
103105
<Plus
104-
color="rgb(0, 122, 255)"
106+
color={colors.foreground}
105107
onPress={() => Haptics.selectionAsync()}
106108
/>
107109
</View>

apps/mobile/app/dashboard/(tabs)/(lists)/_layout.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Platform } from "react-native";
22
import { Stack } from "expo-router/stack";
3+
import { isIOS26 } from "@/lib/ios";
34

45
export default function Layout() {
56
return (
@@ -9,7 +10,7 @@ export default function Layout() {
910
ios: {
1011
headerLargeTitle: true,
1112
headerTransparent: true,
12-
headerBlurEffect: "systemMaterial",
13+
headerBlurEffect: isIOS26 ? undefined : "systemMaterial",
1314
headerLargeTitleShadowVisible: false,
1415
headerLargeStyle: { backgroundColor: "transparent" },
1516
},

apps/mobile/app/dashboard/(tabs)/(lists)/index.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,16 @@ import { useTRPC } from "@karakeep/shared-react/trpc";
1616
import { ZBookmarkListTreeNode } from "@karakeep/shared/utils/listUtils";
1717

1818
function HeaderRight({ openNewListModal }: { openNewListModal: () => void }) {
19+
const { colors } = useColorScheme();
1920
return (
2021
<Pressable
21-
className="my-auto px-4"
22+
className="my-auto"
2223
onPress={() => {
2324
Haptics.selectionAsync();
2425
openNewListModal();
2526
}}
2627
>
27-
<Plus color="rgb(0, 122, 255)" />
28+
<Plus color={colors.foreground} />
2829
</Pressable>
2930
);
3031
}

apps/mobile/app/dashboard/(tabs)/(settings)/_layout.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Platform } from "react-native";
22
import { Stack } from "expo-router/stack";
3+
import { isIOS26 } from "@/lib/ios";
34

45
export default function Layout() {
56
return (
@@ -9,7 +10,7 @@ export default function Layout() {
910
ios: {
1011
headerLargeTitle: true,
1112
headerTransparent: true,
12-
headerBlurEffect: "systemMaterial",
13+
headerBlurEffect: isIOS26 ? undefined : "systemMaterial",
1314
headerLargeTitleShadowVisible: false,
1415
headerLargeStyle: { backgroundColor: "transparent" },
1516
},

apps/mobile/app/dashboard/(tabs)/(tags)/_layout.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Platform } from "react-native";
22
import { Stack } from "expo-router/stack";
3+
import { isIOS26 } from "@/lib/ios";
34

45
export default function Layout() {
56
return (
@@ -9,7 +10,7 @@ export default function Layout() {
910
ios: {
1011
headerLargeTitle: true,
1112
headerTransparent: true,
12-
headerBlurEffect: "systemMaterial",
13+
headerBlurEffect: isIOS26 ? undefined : "systemMaterial",
1314
headerLargeTitleShadowVisible: false,
1415
headerLargeStyle: { backgroundColor: "transparent" },
1516
},

apps/mobile/app/dashboard/_layout.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useEffect } from "react";
33
import { AppState, Platform } from "react-native";
44
import { useRouter } from "expo-router";
55
import { Stack } from "expo-router/stack";
6+
import { isIOS26 } from "@/lib/ios";
67
import { useIsLoggedIn } from "@/lib/session";
78
import { focusManager } from "@tanstack/react-query";
89

@@ -34,7 +35,7 @@ export default function Dashboard() {
3435
...Platform.select({
3536
ios: {
3637
headerTransparent: true,
37-
headerBlurEffect: "systemMaterial",
38+
headerBlurEffect: isIOS26 ? undefined : "systemMaterial",
3839
headerLargeTitle: true,
3940
headerLargeTitleShadowVisible: false,
4041
headerLargeStyle: { backgroundColor: "transparent" },

apps/mobile/app/dashboard/bookmarks/[slug]/index.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import BookmarkTextView from "@/components/bookmarks/BookmarkTextView";
1111
import BottomActions from "@/components/bookmarks/BottomActions";
1212
import FullPageError from "@/components/FullPageError";
1313
import FullPageSpinner from "@/components/ui/FullPageSpinner";
14+
import { isIOS26 } from "@/lib/ios";
1415
import useAppSettings from "@/lib/settings";
1516
import { useQuery } from "@tanstack/react-query";
1617
import { Settings } from "lucide-react-native";
@@ -95,14 +96,16 @@ export default function BookmarkView() {
9596
headerTintColor: isDark ? "#fff" : "#000",
9697
headerRight: () =>
9798
bookmark.content.type === BookmarkTypes.LINK ? (
98-
<View className="flex-row items-center gap-3 px-4">
99+
<View
100+
className={`flex-row items-center gap-3${isIOS26 ? " px-2" : ""}`}
101+
>
99102
{bookmarkLinkType === "reader" && (
100103
<Pressable
101104
onPress={() =>
102105
router.push("/dashboard/settings/reader-settings")
103106
}
104107
>
105-
<Settings size={20} color="gray" />
108+
<Settings size={20} color={isDark ? "#fff" : "#000"} />
106109
</Pressable>
107110
)}
108111
<BookmarkLinkTypeSelector

0 commit comments

Comments
 (0)