Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
- Bitcoin: display zero amounts without decimal places
- Add option to navigate to "Used addresses" in sign-message workflow
- Floating mobile bottom navigation bar
- Mobile: Move settings into bottom navigation

## v4.51.4
- Bundle BitBox02 and BitBox02 Nova firmware version v9.26.5
Expand Down
4 changes: 1 addition & 3 deletions frontends/web/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ const AppFrame = ({
activeAccounts={activeAccounts}
devices={devices}
devicesKey={devicesKey}
showBottomNavigation={showMobileBottomNavigation}
/>
</div>
<RouterWatcher />
Expand Down Expand Up @@ -150,9 +151,6 @@ export const App = () => {
|| currentURL.startsWith('/add-account')
|| currentURL.startsWith('/settings/manage-accounts')
|| currentURL.startsWith('/accounts/')
// Workaround on mobile where the bottom menu is not shown when there are no devices/accounts.
// If one is on "More" and the bottom menu disappears, one is stuck.
|| currentURL === '/settings/more'
)) {
navigate('/');
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@
position: relative;
}

.moreLabel {
.settingsLabel {
align-items: center;
display: inline-flex;
position: relative;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import { useTranslation } from 'react-i18next';
import { Link, useLocation } from 'react-router-dom';
import type { TAccount } from '@/api/account';
import type { TDevices } from '@/api/devices';
import { AccountIconSVG, MarketIconSVG, MoreIconSVG, PortfolioIconSVG } from '@/components/bottom-navigation/menu-icons';
import { AccountIconSVG, MarketIconSVG, PortfolioIconSVG } from '@/components/bottom-navigation/menu-icons';
import { useLoad } from '@/hooks/api';
import { getVersion } from '@/api/bitbox02';
import { RedDot } from '@/components/icon';
import { CogBlue, CogDark, CogLight, RedDot } from '@/components/icon';
import { NewBadge } from '@/components/new-badge/new-badge';
import { useDarkmode } from '@/hooks/darkmode';
import { useAndroidKeyboardVisible } from './use-android-keyboard-visible';
import { useSlidingIndicator } from './use-sliding-indicator';
import { getBottomNavIndex, getBottomNavKey } from './utils';
Expand All @@ -24,6 +25,7 @@ export const BottomNavigation = ({
devices,
}: Props) => {
const { t } = useTranslation();
const { isDarkMode } = useDarkmode();
const { pathname } = useLocation();
const deviceID = Object.keys(devices)[0];
const isBitBox02 = deviceID && devices[deviceID] === 'bitbox02';
Expand All @@ -35,19 +37,21 @@ export const BottomNavigation = ({
const accountLabel = onlyHasOneAccount ? t('account.account') : t('account.accounts');
const portfolioLabel = t('accountSummary.portfolio');
const marketLabel = t('generic.buySell');
const moreLabel = t('settings.more');
const settingsLabel = t('sidebar.settings');

const bottomNavKey = getBottomNavKey(pathname);
const portfolioActive = bottomNavKey === 'portfolio';
const accountsActive = bottomNavKey === 'accounts';
const marketActive = bottomNavKey === 'market';
const moreActive = bottomNavKey === 'more';
const settingsActive = bottomNavKey === 'settings';
const InactiveSettingsIcon = isDarkMode ? CogLight : CogDark;
const SettingsIcon = settingsActive ? CogBlue : InactiveSettingsIcon;
const activeIndex = getBottomNavIndex(bottomNavKey);
const {
containerRef,
indicatorStyle,
labelRefs,
} = useSlidingIndicator(activeIndex, `${portfolioLabel}:${accountLabel}:${marketLabel}:${moreLabel}`);
} = useSlidingIndicator(activeIndex, `${portfolioLabel}:${accountLabel}:${marketLabel}:${settingsLabel}`);
const androidKeyboardVisible = useAndroidKeyboardVisible();

if (androidKeyboardVisible) {
Expand Down Expand Up @@ -110,14 +114,14 @@ export const BottomNavigation = ({
<Link
className={`
${styles.link || ''}
${moreActive ? (styles.active || '') : ''}
${settingsActive ? (styles.active || '') : ''}
`}
to="/settings/more"
to="/settings"
>
<MoreIconSVG />
<span className={styles.moreLabel}>
<SettingsIcon alt="" height={24} width={24} />
<span className={styles.settingsLabel}>
<span ref={element => labelRefs.current[3] = element}>
{moreLabel}
{settingsLabel}
</span>
{canUpgrade && (
<RedDot
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,3 @@ export const MarketIconSVG = () => (
<path d="M10.9231 3.93519V2.2037C10.9231 0.787037 8.07692 0 5.46154 0C2.84615 0 0 0.787037 0 2.2037C0 2.36111 -7.45058e-08 2.51852 0.076923 2.59722H0V4.3257C0 5.35185 1.38462 5.98148 3.07692 6.2963V8.02478C3.07692 8.18519 3.15385 8.34259 3.23077 8.5787C2 8.97222 1.30769 9.60185 1.30769 10.3102V14.7963C1.30769 16.213 4.15385 17 6.76923 17C9.38461 17 12.2308 16.213 12.2308 14.7963V12.5926V10.2315C12.2308 10.0741 12.1538 9.91667 12.0769 9.68056C13.2308 9.25704 14 8.73611 14 7.94907V5.90248C14 5.03704 12.8462 4.25 10.9231 3.93519ZM5.46154 0.708333C8.38461 0.708333 10.2308 1.57407 10.2308 2.2037C10.2308 2.83333 8.38461 3.69907 5.46154 3.69907C2.53846 3.69907 0.692308 2.83333 0.692308 2.2037C0.692308 1.57407 2.53846 0.708333 5.46154 0.708333ZM0.692308 4.40741V3.30556C1.69231 4.01389 3.61538 4.40741 5.46154 4.40741C7.30769 4.40741 9.23077 4.01389 10.2308 3.30556V4.40741C10.2308 5.03704 8.38461 5.90248 5.46154 5.90248C2.53846 5.90248 0.692308 5.03704 0.692308 4.40741ZM11.5385 14.7963C11.5385 15.4259 9.69231 16.2917 6.76923 16.2917C3.84615 16.2917 2 15.4259 2 14.7963V13.6157C3 14.3241 4.92308 14.7176 6.76923 14.7176C8.61538 14.7176 10.5385 14.3241 11.5385 13.6157V14.7963ZM11.5385 12.4352C11.5385 13.0648 9.69231 13.9306 6.76923 13.9306C3.84615 13.9306 2 13.0648 2 12.4352V11.3333C3 12.0417 4.92308 12.4352 6.76923 12.4352C8.61538 12.4352 10.5385 12.0417 11.5385 11.3333V12.4352ZM6.76923 11.8056C3.84615 11.8056 2 10.9398 2 10.3102C2 9.99537 2.53846 9.52315 3.76923 9.20833C4.76923 9.91667 6.69231 10.3102 8.53846 10.3102C9.53846 10.3102 10.4615 10.2315 11.3846 9.99537C11.4615 10.0741 11.5385 10.2315 11.5385 10.3102C11.5385 10.9398 9.69231 11.8056 6.76923 11.8056ZM13.3077 8.10648C13.3077 8.73611 11.4615 9.60185 8.53846 9.60185C5.61538 9.60185 3.76923 8.73611 3.76923 8.10648V7.00463C4.76923 7.71296 6.69231 8.10648 8.53846 8.10648C10.3846 8.10648 12.3077 7.71296 13.3077 7.00463V8.10648ZM8.53846 7.39815C6.53846 7.39815 5 7.00463 4.30769 6.53241C4.69231 6.53241 5.07692 6.61111 5.53846 6.61111C8 6.61111 10.6923 5.90248 10.9231 4.64352C12.4615 4.95833 13.3077 5.50926 13.3077 5.90248C13.3077 6.53241 11.4615 7.39815 8.53846 7.39815Z" fill="currentColor"/>
</svg>
);

export const MoreIconSVG = () => (
<svg width="25" height="25" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.125 11.9766C4.125 12.1489 4.15895 12.3196 4.22491 12.4788C4.29087 12.6381 4.38755 12.7828 4.50942 12.9046C4.6313 13.0265 4.77599 13.1232 4.93523 13.1892C5.09447 13.2551 5.26514 13.2891 5.4375 13.2891C5.60986 13.2891 5.78053 13.2551 5.93977 13.1892C6.09901 13.1232 6.2437 13.0265 6.36558 12.9046C6.48745 12.7828 6.58413 12.6381 6.65009 12.4788C6.71605 12.3196 6.75 12.1489 6.75 11.9766C6.75 11.8042 6.71605 11.6335 6.65009 11.4743C6.58413 11.3151 6.48745 11.1704 6.36558 11.0485C6.2437 10.9266 6.09901 10.8299 5.93977 10.764C5.78053 10.698 5.60986 10.6641 5.4375 10.6641C5.26514 10.6641 5.09447 10.698 4.93523 10.764C4.77599 10.8299 4.6313 10.9266 4.50942 11.0485C4.38755 11.1704 4.29087 11.3151 4.22491 11.4743C4.15895 11.6335 4.125 11.8042 4.125 11.9766V11.9766ZM10.6875 11.9766C10.6875 12.3247 10.8258 12.6585 11.0719 12.9046C11.3181 13.1508 11.6519 13.2891 12 13.2891C12.3481 13.2891 12.6819 13.1508 12.9281 12.9046C13.1742 12.6585 13.3125 12.3247 13.3125 11.9766C13.3125 11.6285 13.1742 11.2946 12.9281 11.0485C12.6819 10.8023 12.3481 10.6641 12 10.6641C11.6519 10.6641 11.3181 10.8023 11.0719 11.0485C10.8258 11.2946 10.6875 11.6285 10.6875 11.9766V11.9766ZM17.25 11.9766C17.25 12.3247 17.3883 12.6585 17.6344 12.9046C17.8806 13.1508 18.2144 13.2891 18.5625 13.2891C18.9106 13.2891 19.2444 13.1508 19.4906 12.9046C19.7367 12.6585 19.875 12.3247 19.875 11.9766C19.875 11.6285 19.7367 11.2946 19.4906 11.0485C19.2444 10.8023 18.9106 10.6641 18.5625 10.6641C18.2144 10.6641 17.8806 10.8023 17.6344 11.0485C17.3883 11.2946 17.25 11.6285 17.25 11.9766V11.9766Z" fill="currentColor"/>
</svg>

);
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,20 @@ describe('getBottomNavKey', () => {
expect(getBottomNavKey('/market/bitrefill/spend/btc')).toBe('market');
expect(getBottomNavKey('/market/pocket/buy/btc')).toBe('market');
});

it('maps settings routes to the settings tab', () => {
expect(getBottomNavKey('/settings')).toBe('settings');
expect(getBottomNavKey('/settings/general')).toBe('settings');
expect(getBottomNavKey('/settings/device-settings/deviceID')).toBe('settings');
});
});

describe('getBottomNavIndex', () => {
it('maps bottom-navigation keys to their visible tab indexes', () => {
expect(getBottomNavIndex('portfolio')).toBe(0);
expect(getBottomNavIndex('accounts')).toBe(1);
expect(getBottomNavIndex('market')).toBe(2);
expect(getBottomNavIndex('more')).toBe(3);
expect(getBottomNavIndex('settings')).toBe(3);
expect(getBottomNavIndex('other')).toBeUndefined();
});
});
Expand Down
4 changes: 2 additions & 2 deletions frontends/web/src/components/bottom-navigation/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import type { TAccount } from '@/api/account';
import type { TDevices } from '@/api/devices';

const bottomNavKeys = ['portfolio', 'accounts', 'market', 'more'] as const;
const bottomNavKeys = ['portfolio', 'accounts', 'market', 'settings'] as const;

export type TBottomNavItem = typeof bottomNavKeys[number];
export type TBottomNavKey = TBottomNavItem | 'other';
Expand All @@ -23,7 +23,7 @@ export const getBottomNavKey = (pathname: string): TBottomNavKey => {
return 'market';
}
if (pathname.startsWith('/settings')) {
return 'more';
return 'settings';
}
return 'other';
};
Expand Down
19 changes: 11 additions & 8 deletions frontends/web/src/routes/router.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: Apache-2.0

import React, { ReactChild } from 'react';
import { Route, Routes, useParams } from 'react-router-dom';
import { Navigate, Route, Routes, useParams } from 'react-router-dom';
import { TAccount } from '@/api/account';
import { TDevices } from '@/api/devices';
import { AddAccount } from './account/add/add-account';
Expand Down Expand Up @@ -42,13 +42,13 @@ import { BitsuranceDashboard } from './bitsurance/dashboard';
import { ConnectScreenWalletConnect } from './account/walletconnect/connect';
import { DashboardWalletConnect } from './account/walletconnect/dashboard';
import { AllAccounts } from '@/routes/accounts/all-accounts';
import { More } from '@/routes/settings/more';

type TAppRouterProps = {
devices: TDevices;
accounts: TAccount[];
activeAccounts: TAccount[];
devicesKey: ((input: string) => string);
showBottomNavigation: boolean;
};

type TInjectParamsProps = {
Expand All @@ -60,7 +60,13 @@ const InjectParams = ({ children }: TInjectParamsProps) => {
return React.cloneElement(children as React.ReactElement, params);
};

export const AppRouter = ({ devices, devicesKey, accounts, activeAccounts }: TAppRouterProps) => {
export const AppRouter = ({
devices,
devicesKey,
accounts,
activeAccounts,
showBottomNavigation,
}: TAppRouterProps) => {
const hasAccounts = accounts.length > 0;
const Homepage = (<DeviceSwitch
key={devicesKey('device-switch-default')}
Expand Down Expand Up @@ -260,13 +266,10 @@ export const AppRouter = ({ devices, devicesKey, accounts, activeAccounts }: TAp
<MobileSettings
devices={devices}
hasAccounts={hasAccounts}
showBottomNavigation={showBottomNavigation}
/>
</InjectParams>);

const MoreEl = (<InjectParams>
<More devices={devices} />
</InjectParams>);

const GeneralEl = (<InjectParams>
<General
devices={devices}
Expand Down Expand Up @@ -348,7 +351,7 @@ export const AppRouter = ({ devices, devicesKey, accounts, activeAccounts }: TAp
<Route path="accounts/all" element={AllAccountsEl} />
<Route path="settings">
<Route index element={MobileSettingsEl} />
<Route path="more" element={MoreEl} />
<Route path="more" element={<Navigate replace to="/settings" />} />
<Route path="general" element={GeneralEl} />
<Route path="about" element={AboutEl} />
<Route path="device-settings/:deviceID" element={Device} />
Expand Down
26 changes: 11 additions & 15 deletions frontends/web/src/routes/settings/mobile-settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@ import { Tabs, WithSettingsTabs } from './components/tabs';
import { TPagePropsWithSettingsTabs } from './types';
import { ContentWrapper } from '@/components/contentwrapper/contentwrapper';
import { GlobalBanners } from '@/components/banners';
import { useBackNavigation } from '@/contexts/BackNavigationContext';
import { useOnlyVisitableOnMobile } from '@/hooks/onlyvisitableonmobile';
import { MobileHeader } from '@/routes/settings/components/mobile-header';
import { useNavigate } from 'react-router-dom';

type TProps = TPagePropsWithSettingsTabs & {
showBottomNavigation: boolean;
};

/**
* The "index" page of the settings
* that will only be shown on Mobile.
Expand All @@ -19,30 +23,22 @@ import { useNavigate } from 'react-router-dom';
* we see on Desktop, as it's the equivalent
* of "tabs" on Mobile.
**/
export const MobileSettings = ({ devices, hasAccounts }: TPagePropsWithSettingsTabs) => {
export const MobileSettings = ({ devices, hasAccounts, showBottomNavigation }: TProps) => {
const { t } = useTranslation();
const navigate = useNavigate();
const { goBack } = useBackNavigation();
useOnlyVisitableOnMobile('/settings/general');
const handleClick = () => {
if (goBack()) {
return;
}
// go to home page if no devices or accounts (waiting.tsx will be shown)
if (Object.keys(devices).length === 0 && !hasAccounts) {
navigate('/');
} else {
navigate('/settings/more');
}
};
return (
<Main>
<ContentWrapper>
<GlobalBanners devices={devices} />
</ContentWrapper>
<Header
title={
<MobileHeader onClick={handleClick} title={t('settings.title')} />
<MobileHeader
onClick={showBottomNavigation ? undefined : () => navigate('/')}
title={t('settings.title')}
variant={showBottomNavigation ? 'titleOnly' : 'back'}
/>
} />
<View fullscreen={false}>
<ViewContent>
Expand Down
11 changes: 0 additions & 11 deletions frontends/web/src/routes/settings/more.module.css

This file was deleted.

67 changes: 0 additions & 67 deletions frontends/web/src/routes/settings/more.tsx

This file was deleted.