Skip to content

Commit

Permalink
Merge pull request #7559 from LedgerHQ/feat/LIVE-13188
Browse files Browse the repository at this point in the history
feat: add header and bottom bar animations on webview scroll [LIVE-13180]
  • Loading branch information
Justkant authored Aug 12, 2024
2 parents c01f17e + ec6c41f commit c407b7a
Show file tree
Hide file tree
Showing 30 changed files with 629 additions and 460 deletions.
5 changes: 5 additions & 0 deletions .changeset/plenty-chairs-refuse.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"live-mobile": minor
---

feat: add header and bottom bar animations on webview scroll
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ function renderLoading() {
);
}
export const PlatformAPIWebview = forwardRef<WebviewAPI, WebviewProps>(
({ manifest, inputs = {}, onStateChange }, ref) => {
({ manifest, inputs = {}, onStateChange, onScroll }, ref) => {
const tracking = useMemo(
() =>
trackingWrapper((eventName: string, properties?: Record<string, unknown> | null) =>
Expand Down Expand Up @@ -120,7 +120,7 @@ export const PlatformAPIWebview = forwardRef<WebviewAPI, WebviewProps>(
currencies: safeCurrencyIds,
includeTokens,
});
// handle no curencies selected case
// handle no currencies selected case
const cryptoCurrencyIds =
safeCurrencyIds && safeCurrencyIds.length > 0
? safeCurrencyIds
Expand Down Expand Up @@ -527,6 +527,8 @@ export const PlatformAPIWebview = forwardRef<WebviewAPI, WebviewProps>(
return (
<RNWebView
ref={webviewRef}
onScroll={onScroll}
decelerationRate="normal"
allowsBackForwardNavigationGestures
startInLoadingState={true}
showsHorizontalScrollIndicator={false}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export const WalletAPIWebview = forwardRef<WebviewAPI, WebviewProps>(
customHandlers,
onStateChange,
allowsBackForwardNavigationGestures = true,
onScroll,
},
ref,
) => {
Expand Down Expand Up @@ -49,6 +50,8 @@ export const WalletAPIWebview = forwardRef<WebviewAPI, WebviewProps>(
return (
<RNWebView
ref={webviewRef}
onScroll={onScroll}
decelerationRate="normal"
startInLoadingState={true}
showsHorizontalScrollIndicator={false}
allowsBackForwardNavigationGestures={allowsBackForwardNavigationGestures}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ export const Web3AppWebview = forwardRef<WebviewAPI, WebviewProps>(
customHandlers,
onStateChange,
allowsBackForwardNavigationGestures,
onScroll,
},
ref,
) => {
if (semver.satisfies(WALLET_API_VERSION, manifest.apiVersion)) {
return (
<WalletAPIWebview
ref={ref}
onScroll={onScroll}
manifest={manifest}
currentAccountHistDb={currentAccountHistDb}
inputs={inputs}
Expand All @@ -33,6 +35,7 @@ export const Web3AppWebview = forwardRef<WebviewAPI, WebviewProps>(
return (
<PlatformAPIWebview
ref={ref}
onScroll={onScroll}
currentAccountHistDb={currentAccountHistDb}
manifest={manifest}
inputs={inputs}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { ComponentProps } from "react";
import { LiveAppManifest } from "@ledgerhq/live-common/platform/types";
import { CurrentAccountHistDB } from "@ledgerhq/live-common/wallet-api/react";
import { WalletAPICustomHandlers } from "@ledgerhq/live-common/wallet-api/types";
Expand All @@ -10,6 +11,7 @@ export type WebviewProps = {
onStateChange?: (webviewState: WebviewState) => void;
allowsBackForwardNavigationGestures?: boolean;
customHandlers?: WalletAPICustomHandlers;
onScroll?: ComponentProps<typeof WebView>["onScroll"];
};

export type WebviewState = {
Expand Down
21 changes: 13 additions & 8 deletions apps/ledger-live-mobile/src/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -6838,16 +6838,21 @@
}
},
"web3hub": {
"disclaimer": {
"clearSigningEnabled": "Clear signing enabled",
"checkbox": "Do not remind me again.",
"CTA": "Open {{app}}"
},
"manifestsList": {
"title": "Explore",
"description": "Discover the best of web3 curated by Ledger"
"components": {
"disclaimer": {
"clearSigningEnabled": "Clear signing enabled",
"checkbox": "Do not remind me again.",
"CTA": "Open {{app}}"
},
"manifestsList": {
"title": "Explore",
"description": "Discover the best of web3 curated by Ledger"
}
},
"main": {
"manifestsList": {
"title": "Discover"
},
"header": {
"title": "Explore web3",
"placeholder": "Search or type a URL"
Expand Down

This file was deleted.

74 changes: 15 additions & 59 deletions apps/ledger-live-mobile/src/newArch/features/Web3Hub/Navigator.tsx
Original file line number Diff line number Diff line change
@@ -1,73 +1,29 @@
import React, { useState } from "react";
import { createNativeStackNavigator } from "@react-navigation/native-stack";
import { useSharedValue } from "react-native-reanimated";
import React from "react";
import {
createNativeStackNavigator,
NativeStackNavigationOptions,
} from "@react-navigation/native-stack";
import { ScreenName } from "~/const";
import { HeaderContext } from "./HeaderContext";
import Web3HubSearch from "./screens/Web3HubSearch";
import Web3HubSearchHeader from "./screens/Web3HubSearch/components/Header";
import Web3HubTabs from "./screens/Web3HubTabs";
import Web3HubTabsHeader from "./screens/Web3HubTabs/components/Header";
import Web3HubApp from "./screens/Web3HubApp";
import Web3HubAppHeader from "./screens/Web3HubApp/components/Header";
import type { AppProps, SearchProps, TabsProps, Web3HubStackParamList } from "./types";
import type { Web3HubStackParamList } from "./types";

// Uncomment to use mocks (you need to reload the app)
// process.env.MOCK_WEB3HUB = "1";

const Stack = createNativeStackNavigator<Web3HubStackParamList>();

export default function Navigator() {
const layoutY = useSharedValue(0);
const [search, setSearch] = useState("");
const screenOptions: NativeStackNavigationOptions = {
headerShown: false,
};

export default function Navigator() {
return (
<HeaderContext.Provider
value={{
layoutY,
search,
}}
>
<Stack.Navigator>
<Stack.Screen
name={ScreenName.Web3HubSearch}
component={Web3HubSearch}
options={{
header: props => (
<Web3HubSearchHeader
// Using as here because we cannot use generics on the header props
navigation={props.navigation as SearchProps["navigation"]}
onSearch={setSearch}
/>
),
}}
/>
<Stack.Screen
name={ScreenName.Web3HubTabs}
component={Web3HubTabs}
options={{
title: "N Tabs", // Temporary, will probably be changed
header: props => (
<Web3HubTabsHeader
title={props.options.title}
// Using as here because we cannot use generics on the header props
navigation={props.navigation as TabsProps["navigation"]}
/>
),
}}
/>
<Stack.Screen
name={ScreenName.Web3HubApp}
component={Web3HubApp}
options={{
header: props => (
<Web3HubAppHeader
// Using as here because we cannot use generics on the header props
navigation={props.navigation as AppProps["navigation"]}
/>
),
}}
/>
</Stack.Navigator>
</HeaderContext.Provider>
<Stack.Navigator screenOptions={screenOptions}>
<Stack.Screen name={ScreenName.Web3HubSearch} component={Web3HubSearch} />
<Stack.Screen name={ScreenName.Web3HubTabs} component={Web3HubTabs} />
<Stack.Screen name={ScreenName.Web3HubApp} component={Web3HubApp} />
</Stack.Navigator>
);
}
Original file line number Diff line number Diff line change
@@ -1,44 +1,22 @@
import React from "react";
import { useTranslation } from "react-i18next";
import { createNativeStackNavigator } from "@react-navigation/native-stack";
import { useSharedValue } from "react-native-reanimated";
import {
createNativeStackNavigator,
NativeStackNavigationOptions,
} from "@react-navigation/native-stack";
import { ScreenName } from "~/const";
import { HeaderContext } from "./HeaderContext";
import Web3HubMain from "./screens/Web3HubMain";
import Web3HubMainHeader from "./screens/Web3HubMain/components/Header";
import { MainProps, Web3HubTabStackParamList } from "./types";
import { Web3HubTabStackParamList } from "./types";

const Stack = createNativeStackNavigator<Web3HubTabStackParamList>();

export default function TabNavigator() {
const { t } = useTranslation();
const layoutY = useSharedValue(0);
const screenOptions: NativeStackNavigationOptions = {
headerShown: false,
};

export default function TabNavigator() {
return (
<HeaderContext.Provider
value={{
layoutY,
}}
>
<Stack.Navigator initialRouteName={ScreenName.Web3HubMain}>
<Stack.Screen
name={ScreenName.Web3HubMain}
component={Web3HubMain}
options={{
title: t("web3hub.main.header.title"),
// Never just pass a component to header like `header: Web3HubMainHeader,`
// as it would break the fast-refresh for the header
header: props => (
<Web3HubMainHeader
title={props.options.title}
// Using as here because we cannot use generics on the header props
navigation={props.navigation as MainProps["navigation"]}
/>
),
animation: "none",
}}
/>
</Stack.Navigator>
</HeaderContext.Provider>
<Stack.Navigator initialRouteName={ScreenName.Web3HubMain} screenOptions={screenOptions}>
<Stack.Screen name={ScreenName.Web3HubMain} component={Web3HubMain} />
</Stack.Navigator>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import React, { PropsWithChildren } from "react";
import { ColorValue, StyleProp, ViewStyle } from "react-native";
import Animated, {
useAnimatedStyle,
interpolate,
Extrapolation,
SharedValue,
AnimatedStyle,
} from "react-native-reanimated";

type Props = PropsWithChildren<{
pt?: number;
style?: StyleProp<AnimatedStyle<StyleProp<ViewStyle>>>;
layoutY: SharedValue<number>;
totalHeight: number;
opacityHeight: number;
animationHeight: number;
backgroundColor?: ColorValue;
opacityChildren?: React.ReactNode;
}>;

export default function AnimatedBar({
pt = 0,
style,
layoutY,
backgroundColor,
totalHeight,
opacityHeight,
animationHeight,
opacityChildren,
children,
}: Props) {
const heightStyle = useAnimatedStyle(() => {
if (!layoutY) return {};

const headerHeight = interpolate(
layoutY.value,
[0, animationHeight],
[totalHeight, totalHeight - animationHeight],
Extrapolation.CLAMP,
);

return {
backgroundColor: backgroundColor,
paddingTop: pt,
height: headerHeight + pt,
};
});

const transformStyle = useAnimatedStyle(() => {
if (!layoutY) return {};

return {
// Height necessary for proper transform
height: totalHeight,
transform: [
{
translateY: interpolate(
layoutY.value,
[0, animationHeight],
[0, -animationHeight],
Extrapolation.CLAMP,
),
},
],
};
});

const opacityStyle = useAnimatedStyle(() => {
if (!layoutY) return {};

return {
height: opacityHeight,
opacity: interpolate(layoutY.value, [0, animationHeight], [1, 0], Extrapolation.CLAMP),
};
});

return (
<Animated.View style={[style, heightStyle]}>
<Animated.View style={transformStyle}>
<Animated.View style={opacityStyle}>{opacityChildren}</Animated.View>
{children}
</Animated.View>
</Animated.View>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export default function Disclaimer({

{description ? (
<Flex mt={6}>
<Text fontSize={14} lineHeight={"22px"} color="smoke">
<Text numberOfLines={12} fontSize={14} lineHeight={"22px"} color="smoke">
{description}
</Text>
</Flex>
Expand All @@ -48,21 +48,21 @@ export default function Disclaimer({
<Icons.Eye color={"smoke"} />
</Box>
<Text fontSize={14} color="smoke">
{t("web3hub.disclaimer.clearSigningEnabled")}
{t("web3hub.components.disclaimer.clearSigningEnabled")}
</Text>
</Flex>

<Flex mt={6}>
<Checkbox
label={" " + t("web3hub.disclaimer.checkbox")}
label={" " + t("web3hub.components.disclaimer.checkbox")}
checked={isChecked}
onChange={toggleCheck}
/>
</Flex>

<Flex mt={6}>
<Button type="main" onPress={onConfirm}>
{t("web3hub.disclaimer.CTA", { app: manifest?.name })}
{t("web3hub.components.disclaimer.CTA", { app: manifest?.name })}
</Button>
</Flex>
</QueuedDrawer>
Expand Down
Loading

0 comments on commit c407b7a

Please sign in to comment.