From 7627386ec24a56b2a04c7d8e5f9acf1b8e871ef3 Mon Sep 17 00:00:00 2001
From: yanas <yanas@yanas.cz>
Date: Mon, 27 Jan 2025 09:31:02 +0100
Subject: [PATCH] fix(suite-native): use linear gradient in screen footers with
 buttons

---
 suite-native/coin-enabling/package.json       |  2 -
 .../src/screens/CoinEnablingInitScreen.tsx    | 68 ++++---------------
 suite-native/coin-enabling/tsconfig.json      |  3 -
 .../components/AccountImportSummaryScreen.tsx |  5 +-
 .../FirmwareUpdateScreen.tsx                  |  1 +
 .../src/screens/SendOutputsScreen.tsx         | 35 ++++------
 suite-native/navigation/package.json          |  1 +
 .../src/components/ScreenFooterGradient.tsx   | 28 ++++++++
 suite-native/navigation/src/index.ts          |  1 +
 yarn.lock                                     |  5 +-
 10 files changed, 63 insertions(+), 86 deletions(-)
 create mode 100644 suite-native/navigation/src/components/ScreenFooterGradient.tsx

diff --git a/suite-native/coin-enabling/package.json b/suite-native/coin-enabling/package.json
index cbd354c87b1..eb061a710e5 100644
--- a/suite-native/coin-enabling/package.json
+++ b/suite-native/coin-enabling/package.json
@@ -13,7 +13,6 @@
         "@mobily/ts-belt": "^3.13.1",
         "@react-navigation/native": "6.1.18",
         "@reduxjs/toolkit": "1.9.5",
-        "@suite-common/suite-utils": "workspace:*",
         "@suite-common/wallet-config": "workspace:*",
         "@suite-common/wallet-core": "workspace:*",
         "@suite-native/alerts": "workspace:*",
@@ -27,7 +26,6 @@
         "@suite-native/toasts": "workspace:*",
         "@suite-native/tokens": "workspace:*",
         "@trezor/styles": "workspace:*",
-        "expo-linear-gradient": "^14.0.1",
         "react": "18.2.0",
         "react-native": "0.76.1",
         "react-native-reanimated": "^3.16.7",
diff --git a/suite-native/coin-enabling/src/screens/CoinEnablingInitScreen.tsx b/suite-native/coin-enabling/src/screens/CoinEnablingInitScreen.tsx
index a06320165dd..bc1f989d80a 100644
--- a/suite-native/coin-enabling/src/screens/CoinEnablingInitScreen.tsx
+++ b/suite-native/coin-enabling/src/screens/CoinEnablingInitScreen.tsx
@@ -1,12 +1,14 @@
 import { useDispatch, useSelector } from 'react-redux';
 import Animated, { SlideInDown, SlideOutDown } from 'react-native-reanimated';
-import { View } from 'react-native';
 
 import { A } from '@mobily/ts-belt';
 import { useNavigation } from '@react-navigation/native';
-import { LinearGradient } from 'expo-linear-gradient';
 
-import { Screen, useHandleHardwareBackNavigation } from '@suite-native/navigation';
+import {
+    Screen,
+    ScreenFooterGradient,
+    useHandleHardwareBackNavigation,
+} from '@suite-native/navigation';
 import { Box, Button, Text, VStack } from '@suite-native/atoms';
 import {
     applyDiscoveryChangesThunk,
@@ -14,46 +16,15 @@ import {
     setIsCoinEnablingInitFinished,
 } from '@suite-native/discovery';
 import { Translation } from '@suite-native/intl';
-import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';
 import { analytics, EventType } from '@suite-native/analytics';
-import { hexToRgba } from '@suite-common/suite-utils';
 
 import { DiscoveryCoinsFilter } from '../components/DiscoveryCoinsFilter';
 
-const BOTTOM_OFFSET = 12;
-const BUTTON_HEIGHT = 48;
-
-const contentStyle = prepareNativeStyle(_ => ({
-    paddingBottom: BOTTOM_OFFSET,
-}));
-
-const gradientBackgroundBottomStyle = prepareNativeStyle<{ showButton: boolean }>(
-    (utils, { showButton }) => ({
-        width: '100%',
-        height: utils.spacings.sp16,
-        position: 'absolute',
-        bottom: -BOTTOM_OFFSET,
-        pointerEvents: 'none',
-        extend: [{ condition: showButton, style: { bottom: BUTTON_HEIGHT } }],
-    }),
-);
-
-const buttonWrapperStyle = prepareNativeStyle(utils => ({
-    bottom: BOTTOM_OFFSET,
-    backgroundColor: utils.colors.backgroundSurfaceElevation0,
-    width: '100%',
-}));
-
-const buttonStyle = prepareNativeStyle(utils => ({
-    paddingHorizontal: utils.spacings.sp16,
-}));
-
 export const CoinEnablingInitScreen = () => {
     const dispatch = useDispatch();
     const navigation = useNavigation();
     useHandleHardwareBackNavigation();
 
-    const { applyStyle, utils } = useNativeStyles();
     const enabledNetworkSymbols = useSelector(selectDeviceEnabledDiscoveryNetworkSymbols);
 
     const handleSave = () => {
@@ -69,9 +40,6 @@ export const CoinEnablingInitScreen = () => {
         }
     };
 
-    // 'transparent' color is not working in context of LinearGradient on iOS. RGBA has to be used instead.
-    const transparentColor = hexToRgba(utils.colors.backgroundSurfaceElevation0, 0.01);
-
     const canBeSaved = A.isNotEmpty(enabledNetworkSymbols);
 
     return (
@@ -87,29 +55,19 @@ export const CoinEnablingInitScreen = () => {
                 </VStack>
             }
             footer={
-                <View style={applyStyle(buttonWrapperStyle)}>
-                    <LinearGradient
-                        dither={false}
-                        colors={[transparentColor, utils.colors.backgroundSurfaceElevation0]}
-                        style={applyStyle(gradientBackgroundBottomStyle, {
-                            showButton: canBeSaved,
-                        })}
-                    />
-                    {canBeSaved && (
-                        <Animated.View
-                            entering={SlideInDown}
-                            exiting={SlideOutDown}
-                            style={applyStyle(buttonStyle)}
-                        >
+                canBeSaved && (
+                    <Animated.View entering={SlideInDown} exiting={SlideOutDown}>
+                        <ScreenFooterGradient />
+                        <Box marginHorizontal="sp16">
                             <Button onPress={handleSave} testID="@coin-enabling/button-save">
                                 <Translation id="moduleSettings.coinEnabling.initialSetup.button" />
                             </Button>
-                        </Animated.View>
-                    )}
-                </View>
+                        </Box>
+                    </Animated.View>
+                )
             }
         >
-            <Box style={applyStyle(contentStyle)}>
+            <Box>
                 <DiscoveryCoinsFilter allowDeselectLastCoin={true} allowChangeAnalytics={false} />
             </Box>
         </Screen>
diff --git a/suite-native/coin-enabling/tsconfig.json b/suite-native/coin-enabling/tsconfig.json
index c6891577ae2..ae71565942b 100644
--- a/suite-native/coin-enabling/tsconfig.json
+++ b/suite-native/coin-enabling/tsconfig.json
@@ -2,9 +2,6 @@
     "extends": "../../tsconfig.base.json",
     "compilerOptions": { "outDir": "libDev" },
     "references": [
-        {
-            "path": "../../suite-common/suite-utils"
-        },
         {
             "path": "../../suite-common/wallet-config"
         },
diff --git a/suite-native/module-accounts-import/src/components/AccountImportSummaryScreen.tsx b/suite-native/module-accounts-import/src/components/AccountImportSummaryScreen.tsx
index 2bcb6967437..f456125d8ff 100644
--- a/suite-native/module-accounts-import/src/components/AccountImportSummaryScreen.tsx
+++ b/suite-native/module-accounts-import/src/components/AccountImportSummaryScreen.tsx
@@ -2,7 +2,7 @@ import { ReactNode } from 'react';
 import { KeyboardAvoidingView, Platform } from 'react-native';
 
 import { Box, PictogramTitleHeader, VStack } from '@suite-native/atoms';
-import { Screen } from '@suite-native/navigation';
+import { Screen, ScreenFooterGradient } from '@suite-native/navigation';
 
 import { AccountImportScreenHeader } from './AccountImportScreenHeader';
 
@@ -25,7 +25,8 @@ export const AccountImportSummaryScreen = ({
         header={<AccountImportScreenHeader />}
         footer={
             <KeyboardAvoidingView behavior={Platform.OS === 'ios' ? 'padding' : 'height'}>
-                <Box margin="sp16">{footer}</Box>
+                <ScreenFooterGradient />
+                <Box marginHorizontal="sp16">{footer}</Box>
             </KeyboardAvoidingView>
         }
     >
diff --git a/suite-native/module-device-settings/src/screens/FirmwareUpdateScreen/FirmwareUpdateScreen.tsx b/suite-native/module-device-settings/src/screens/FirmwareUpdateScreen/FirmwareUpdateScreen.tsx
index d6f83caa6af..118d254e8b7 100644
--- a/suite-native/module-device-settings/src/screens/FirmwareUpdateScreen/FirmwareUpdateScreen.tsx
+++ b/suite-native/module-device-settings/src/screens/FirmwareUpdateScreen/FirmwareUpdateScreen.tsx
@@ -26,6 +26,7 @@ import { FirmwareUpdateVersionCard } from './FirmwareVersionCard';
 
 const firmwareUpdateButtonStyle = prepareNativeStyle(utils => ({
     marginHorizontal: utils.spacings.sp16,
+    marginBottom: utils.spacings.sp16,
 }));
 
 type NavigationProp = StackNavigationProps<
diff --git a/suite-native/module-send/src/screens/SendOutputsScreen.tsx b/suite-native/module-send/src/screens/SendOutputsScreen.tsx
index 5c5de716adb..58af71e17fd 100644
--- a/suite-native/module-send/src/screens/SendOutputsScreen.tsx
+++ b/suite-native/module-send/src/screens/SendOutputsScreen.tsx
@@ -21,6 +21,7 @@ import {
     updateFeeInfoThunk,
 } from '@suite-common/wallet-core';
 import {
+    ScreenFooterGradient,
     SendStackParamList,
     SendStackRoutes,
     StackNavigationProps,
@@ -30,7 +31,6 @@ import { getNetwork } from '@suite-common/wallet-config';
 import { Box, Button } from '@suite-native/atoms';
 import { Translation } from '@suite-native/intl';
 import { useDebounce } from '@trezor/react-utils';
-import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';
 import { useForm, Form } from '@suite-native/forms';
 import { selectIsAmountInSats, SettingsSliceRootState } from '@suite-native/settings';
 import { TokenAddress } from '@suite-common/wallet-types';
@@ -46,11 +46,6 @@ import { FeeLevelsMaxAmount } from '../types';
 import { storeFeeLevels } from '../sendFormSlice';
 import { useSubscribeForSolanaBlockUpdates } from '../hooks/useSubscribeForSolanaBlockUpdates';
 
-const buttonWrapperStyle = prepareNativeStyle(utils => ({
-    width: '100%',
-    padding: utils.spacings.sp16,
-}));
-
 const getDefaultValues = ({
     tokenContract,
 }: {
@@ -72,7 +67,6 @@ export const SendOutputsScreen = ({
 }: StackProps<SendStackParamList, SendStackRoutes.SendOutputs>) => {
     const { accountKey, tokenContract } = params;
     const dispatch = useDispatch();
-    const { applyStyle } = useNativeStyles();
     const debounce = useDebounce();
     const navigation =
         useNavigation<StackNavigationProps<SendStackParamList, SendStackRoutes.SendOutputs>>();
@@ -275,20 +269,19 @@ export const SendOutputsScreen = ({
             }
             footer={
                 isValid && (
-                    <Animated.View
-                        entering={SlideInDown}
-                        exiting={SlideOutDown}
-                        style={applyStyle(buttonWrapperStyle)}
-                    >
-                        <Button
-                            accessibilityRole="button"
-                            accessibilityLabel="validate send form"
-                            testID="@send/form-submit-button"
-                            onPress={handleNavigateToReviewScreen}
-                            isDisabled={isSubmitting}
-                        >
-                            <Translation id="generic.buttons.continue" />
-                        </Button>
+                    <Animated.View entering={SlideInDown} exiting={SlideOutDown}>
+                        <ScreenFooterGradient />
+                        <Box marginHorizontal="sp16">
+                            <Button
+                                accessibilityRole="button"
+                                accessibilityLabel="validate send form"
+                                testID="@send/form-submit-button"
+                                onPress={handleNavigateToReviewScreen}
+                                isDisabled={isSubmitting}
+                            >
+                                <Translation id="generic.buttons.continue" />
+                            </Button>
+                        </Box>
                     </Animated.View>
                 )
             }
diff --git a/suite-native/navigation/package.json b/suite-native/navigation/package.json
index 657c27dc654..9078592844b 100644
--- a/suite-native/navigation/package.json
+++ b/suite-native/navigation/package.json
@@ -26,6 +26,7 @@
         "@trezor/connect": "workspace:*",
         "@trezor/styles": "workspace:*",
         "@trezor/theme": "workspace:*",
+        "expo-linear-gradient": "14.0.1",
         "expo-linking": "^7.0.2",
         "expo-navigation-bar": "^4.0.2",
         "expo-system-ui": "^4.0.2",
diff --git a/suite-native/navigation/src/components/ScreenFooterGradient.tsx b/suite-native/navigation/src/components/ScreenFooterGradient.tsx
new file mode 100644
index 00000000000..07a2eedbaca
--- /dev/null
+++ b/suite-native/navigation/src/components/ScreenFooterGradient.tsx
@@ -0,0 +1,28 @@
+import { LinearGradient } from 'expo-linear-gradient';
+
+import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';
+import { hexToRgba } from '@suite-common/suite-utils';
+
+const screenFooterGradientStyle = prepareNativeStyle(utils => ({
+    width: '100%',
+    height: utils.spacings.sp16,
+    top: -utils.spacings.sp16,
+    marginBottom: -utils.spacings.sp16,
+    pointerEvents: 'none',
+}));
+
+export const ScreenFooterGradient = () => {
+    const { applyStyle, utils } = useNativeStyles();
+
+    // 'transparent' color does not work in context of LinearGradient on iOS, RGBA has to be used instead.
+    const backgroundColor = utils.colors.backgroundSurfaceElevation0;
+    const transparentColor = hexToRgba(backgroundColor, 0.01);
+
+    return (
+        <LinearGradient
+            dither={false}
+            colors={[transparentColor, backgroundColor]}
+            style={applyStyle(screenFooterGradientStyle)}
+        />
+    );
+};
diff --git a/suite-native/navigation/src/index.ts b/suite-native/navigation/src/index.ts
index 4d7fea50d5b..ae94deccfde 100644
--- a/suite-native/navigation/src/index.ts
+++ b/suite-native/navigation/src/index.ts
@@ -8,6 +8,7 @@ export * from './useScrollDivider';
 export * from './components/TabBar';
 export * from './components/Screen';
 export * from './components/ScreenHeader';
+export * from './components/ScreenFooterGradient';
 export * from './components/NavigationContainerWithAnalytics';
 export * from './components/GoBackIcon';
 export * from './components/ScrollViewContext';
diff --git a/yarn.lock b/yarn.lock
index 6548b83c975..f25cccbb642 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -10172,7 +10172,6 @@ __metadata:
     "@mobily/ts-belt": "npm:^3.13.1"
     "@react-navigation/native": "npm:6.1.18"
     "@reduxjs/toolkit": "npm:1.9.5"
-    "@suite-common/suite-utils": "workspace:*"
     "@suite-common/wallet-config": "workspace:*"
     "@suite-common/wallet-core": "workspace:*"
     "@suite-native/alerts": "workspace:*"
@@ -10186,7 +10185,6 @@ __metadata:
     "@suite-native/toasts": "workspace:*"
     "@suite-native/tokens": "workspace:*"
     "@trezor/styles": "workspace:*"
-    expo-linear-gradient: "npm:^14.0.1"
     react: "npm:18.2.0"
     react-native: "npm:0.76.1"
     react-native-reanimated: "npm:^3.16.7"
@@ -10967,6 +10965,7 @@ __metadata:
     "@trezor/connect": "workspace:*"
     "@trezor/styles": "workspace:*"
     "@trezor/theme": "workspace:*"
+    expo-linear-gradient: "npm:14.0.1"
     expo-linking: "npm:^7.0.2"
     expo-navigation-bar: "npm:^4.0.2"
     expo-system-ui: "npm:^4.0.2"
@@ -23121,7 +23120,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"expo-linear-gradient@npm:^14.0.1":
+"expo-linear-gradient@npm:14.0.1, expo-linear-gradient@npm:^14.0.1":
   version: 14.0.1
   resolution: "expo-linear-gradient@npm:14.0.1"
   peerDependencies: