Skip to content

Commit fea1126

Browse files
NateIsernclaude
andcommitted
Revolut-style parallax home + back buttons on modal screens
Add a Revolut-style hero image with parallax scroll on the home screen: - 280px hero rendered absolutely behind the scroll content - Reanimated useAnimatedScrollHandler drives translateY (-0.5x) and an overscroll zoom (scale 1 → 1.4 over 200px of pull-down, clamped) - LinearGradient overlay fades the hero into the background color - Top bar (wallet name, sync status) is overlaid on the hero with white text + drop shadow for readability Add an `onBack` convenience prop to ScreenHeader so consumers don't have to inline a Pressable + arrow icon. Apply it on every modal-presented screen that lost its native nav header (wallets, masternode, coin control, export key, transaction). Adds expo-linear-gradient as a native dep. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 7a8ba97 commit fea1126

8 files changed

Lines changed: 278 additions & 126 deletions

File tree

app/(tabs)/index.tsx

Lines changed: 240 additions & 117 deletions
Large diffs are not rendered by default.

app/coin-control.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ export default function CoinControlScreen() {
167167
<ScreenHeader
168168
title="Coin Control"
169169
subtitle={`${utxos.length} UTXO${utxos.length !== 1 ? "s" : ""} available`}
170+
onBack={() => router.back()}
170171
/>
171172
<ScrollView className="flex-1" contentContainerClassName="px-5 pb-4">
172173
{/* Selection actions */}

app/export-key.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import { useCallback, useMemo, useState } from "react";
99
import { View, Text, TextInput, ScrollView, Pressable } from "react-native";
1010
import { SafeAreaView } from "react-native-safe-area-context";
11+
import { useRouter } from "expo-router";
1112
import * as Clipboard from "expo-clipboard";
1213
import MaterialCommunityIcons from "@expo/vector-icons/MaterialCommunityIcons";
1314
import { useWalletStore } from "../src/wallet/wallet-store";
@@ -41,6 +42,7 @@ type ExportStep = "pin" | "select" | "passphrase" | "result";
4142
// ---------------------------------------------------------------------------
4243

4344
export default function ExportKeyScreen() {
45+
const router = useRouter();
4446
const addresses = useWalletStore((s) => s.addresses);
4547
const network = useWalletStore((s) => s.network);
4648
const theme = useTheme();
@@ -247,6 +249,7 @@ export default function ExportKeyScreen() {
247249
<ScreenHeader
248250
title="Select Address"
249251
subtitle="Choose the address whose private key you want to export"
252+
onBack={() => router.back()}
250253
/>
251254
<ScrollView
252255
className="flex-1"
@@ -286,6 +289,7 @@ export default function ExportKeyScreen() {
286289
<ScreenHeader
287290
title="Set Encryption Passphrase"
288291
subtitle="This passphrase will be needed to decrypt the exported key. Choose a strong passphrase and store it safely."
292+
onBack={() => setStep("select")}
289293
/>
290294
<ScrollView
291295
className="flex-1"
@@ -356,6 +360,7 @@ export default function ExportKeyScreen() {
356360
<ScreenHeader
357361
title="Encrypted Key"
358362
subtitle="Your BIP38 encrypted private key"
363+
onBack={() => router.back()}
359364
/>
360365
<ScrollView
361366
className="flex-1"

app/masternode.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import { useState, useCallback } from "react";
99
import { View, Text, ScrollView, Modal, TextInput } from "react-native";
1010
import { SafeAreaView } from "react-native-safe-area-context";
11-
import { useFocusEffect } from "expo-router";
11+
import { useFocusEffect, useRouter } from "expo-router";
1212
import { useWalletStore } from "../src/wallet/wallet-store";
1313
import {
1414
Section,
@@ -65,6 +65,7 @@ function parseIpPort(input: string): { ip: string; port: number } | null {
6565
}
6666

6767
export default function MasternodeScreen() {
68+
const router = useRouter();
6869
const masternodeUTXOs = useWalletStore((s) => s.masternodeUTXOs);
6970
const refreshMasternodeUTXOs = useWalletStore(
7071
(s) => s.refreshMasternodeUTXOs,
@@ -144,7 +145,7 @@ export default function MasternodeScreen() {
144145
className="flex-1 bg-background"
145146
edges={["top", "bottom", "left", "right"]}
146147
>
147-
<ScreenHeader title="Masternode" />
148+
<ScreenHeader title="Masternode" onBack={() => router.back()} />
148149
<ScrollView
149150
className="flex-1"
150151
contentContainerClassName="px-5 pt-4 pb-8"

app/wallets.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,7 @@ export default function WalletsScreen() {
545545
<ScreenHeader
546546
title="Wallets"
547547
subtitle={`${wallets.length} wallet${wallets.length !== 1 ? "s" : ""} - Tap to switch, long-press to delete`}
548+
onBack={() => router.back()}
548549
/>
549550
<ScrollView
550551
className="flex-1"

assets/home-hero.jpg

1.21 MB
Loading

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"expo-crypto": "~55.0.0",
3535
"expo-font": "~55.0.0",
3636
"expo-haptics": "~55.0.0",
37+
"expo-linear-gradient": "^55.0.13",
3738
"expo-linking": "~55.0.0",
3839
"expo-local-authentication": "~55.0.0",
3940
"expo-localization": "~55.0.0",

src/ui/components/ScreenHeader.tsx

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,49 @@
22
* ScreenHeader — common header layout with centered title and side actions.
33
*/
44

5-
import { View, Text } from "react-native";
5+
import { View, Text, Pressable } from "react-native";
6+
import MaterialCommunityIcons from "@expo/vector-icons/MaterialCommunityIcons";
7+
import { useTheme } from "@oxyhq/bloom/theme";
68

79
interface ScreenHeaderProps {
810
title: string;
911
subtitle?: string;
1012
leftAction?: React.ReactNode;
1113
rightAction?: React.ReactNode;
14+
/** Convenience: show a back-arrow on the left that calls this handler. */
15+
onBack?: () => void;
1216
}
1317

1418
export function ScreenHeader({
1519
title,
1620
subtitle,
1721
leftAction,
1822
rightAction,
23+
onBack,
1924
}: ScreenHeaderProps) {
25+
const theme = useTheme();
26+
27+
const resolvedLeft =
28+
leftAction ??
29+
(onBack ? (
30+
<Pressable
31+
onPress={onBack}
32+
className="w-11 h-11 items-center justify-center rounded-full active:bg-surface"
33+
accessibilityLabel="Back"
34+
accessibilityRole="button"
35+
>
36+
<MaterialCommunityIcons
37+
name="arrow-left"
38+
size={24}
39+
color={theme.colors.text}
40+
/>
41+
</Pressable>
42+
) : null);
43+
2044
return (
2145
<View className="flex-row items-center px-4 py-3">
2246
{/* Left action slot */}
23-
<View className="w-12 items-start">
24-
{leftAction ?? null}
25-
</View>
47+
<View className="w-12 items-start">{resolvedLeft}</View>
2648

2749
{/* Center: title + subtitle */}
2850
<View className="flex-1 items-center">
@@ -37,9 +59,7 @@ export function ScreenHeader({
3759
</View>
3860

3961
{/* Right action slot */}
40-
<View className="w-12 items-end">
41-
{rightAction ?? null}
42-
</View>
62+
<View className="w-12 items-end">{rightAction ?? null}</View>
4363
</View>
4464
);
4565
}

0 commit comments

Comments
 (0)