Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[LLM] When deleting a token from the list of hidden tokens, it should be shown in the application #1617

Closed
wants to merge 12 commits into from
5 changes: 5 additions & 0 deletions .changeset/eleven-suits-rescue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"live-mobile": patch
---

When removing a token from the list of hidden tokens, it is redisplayed as a subaccount
6 changes: 3 additions & 3 deletions apps/ledger-live-desktop/release-notes.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[
{
"tag_name": "2.46.0",
"body": "\nWe're releasing a slew of new features and improvements. Here is what’s new in Ledger Live πŸ‘‡\n\n### πŸš€ Features\n\n- We've added **Filecoin support** to the Ledger Live roaster. Beginning today, you can send and receive its native currency FIL.\n- **Empowering our external partners**. With more coins and tokens added to the Ledger platform, the account pages now display banners stating if integration has been done externally and by who.\n\n### πŸ› Fixes\n\nWe've tidied up some minor bugs.\n\nDon’t forget to update your Ledger Live app so that you can access these new features.\n"
"tag_name": "2.48.0",
"body": "\n### πŸš€ Features\n\n- Introducing a new way to swap crypto β€” **compare quotes from different platforms and choose the best offer**. Select the coin you want to swap and explore providers, KYC requirements, exchange rates, and more. In addition, you can now fully leverage DeFi protocols to swap ERC-20 tokens on decentralised exchanges.\n- OlΓ‘! From today, **Ledger Live supports Brazilian Portuguese** πŸ‡§πŸ‡· You can change the language in the settings menu under General β†’ Display language.\n- When sending BTC, a **warning message** will alert you if there are any **pending incoming transactions**.\n\n### πŸ› Fixes\n\nWe’ve tidied up some bugs that caused issues when sending Zcash and signing transactions. Everything is running smoothly again.\n"
}
]
]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 5 additions & 1 deletion apps/ledger-live-mobile/src/actions/general.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,15 @@ import { flushAll } from "../components/DBSave";
const extraSessionTrackingPairsChanges: BehaviorSubject<TrackingPair[]> =
new BehaviorSubject([]);

export function useDistribution(opts) {
export function useDistribution(opts: {
showEmptyAccounts: boolean;
hideEmptyTokenAccount: boolean;
}) {
const accounts = useSelector(accountsSelector);
const to = useSelector(counterValueCurrencySelector);
return useDistributionCommon({ accounts, to, ...opts });
}

export function useCalculateCountervalueCallback() {
const to = useSelector(counterValueCurrencySelector);
return useCalculateCountervalueCallbackCommon({
Expand Down
11 changes: 8 additions & 3 deletions apps/ledger-live-mobile/src/actions/portfolio.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { useSelector } from "react-redux";
import type { AccountLike, PortfolioRange } from "@ledgerhq/types-live";
import type {
Account,
AccountLike,
PortfolioRange,
} from "@ledgerhq/types-live";
import type {
TokenCurrency,
CryptoCurrency,
Expand Down Expand Up @@ -32,8 +36,8 @@ export function useBalanceHistoryWithCountervalue({
}

export function usePortfolio(
accounts: AccountLike[],
options: GetPortfolioOptionsType,
accounts?: Account[],
This conversation was marked as resolved.
Show resolved Hide resolved
options?: GetPortfolioOptionsType,
) {
const to = useSelector(counterValueCurrencySelector);
const accountsSelected = useSelector(accountsSelector);
Expand All @@ -45,6 +49,7 @@ export function usePortfolio(
options,
});
}

export function useCurrencyPortfolio({
currency,
range,
Expand Down
2 changes: 0 additions & 2 deletions apps/ledger-live-mobile/src/components/GraphCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,7 @@ function GraphCard({
percent
show0Delta
valueChange={countervalueChange}
// range={portfolio.range}
/>
<Text> </Text>
<Delta unit={unit} valueChange={countervalueChange} />
</>
)}
Expand Down
43 changes: 25 additions & 18 deletions apps/ledger-live-mobile/src/logic/storeWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,44 +5,49 @@
// It's based on https://github.com/jasonmerino/react-native-simple-store
// with the new React-native-async-store package
import AsyncStorage from "@react-native-async-storage/async-storage";
import { KeyValuePair } from "@react-native-async-storage/async-storage/lib/typescript/types";
import { merge } from "lodash";

const CHUNKED_KEY = "_-_CHUNKED";
const CHUNK_SIZE = 1000000;

const getChunks = (str, size) => {
const getChunks = (str: string, size: number) => {
const strLength = str.length;
const numChunks = Math.ceil(strLength / size);
const chunks = new Array(numChunks);
const chunks: string[] = new Array(numChunks);
let i = 0;
let o = 0;

for (; i < numChunks; ++i, o += size) {
chunks[i] = str.substr(o, size);
chunks[i] = str.substring(o, o + size);
}

return chunks;
};

const stringifyPairs = pairs =>
const stringifyPairs = (pairs: [string, unknown][]) =>
pairs.reduce((acc, current) => {
const key = current[0];
const data = JSON.stringify(current[1]);

if (data.length > CHUNK_SIZE) {
const chunks = getChunks(data, CHUNK_SIZE);
const numberOfChunks = chunks.length;

return [
...acc,
[current[0], CHUNKED_KEY + numberOfChunks],
...chunks.map((chunk, index) => [key + CHUNKED_KEY + index, chunk]),
...chunks.map<[string, string]>((chunk, index) => [
key + CHUNKED_KEY + index,
chunk,
]),
];
}

return [...acc, [key, data]];
}, []);
}, [] as string[][]);

const getCompressedValue = async (key, value) => {
const getCompressedValue = async (key: string, value: string | null) => {
try {
if (value && value.includes(CHUNKED_KEY)) {
const numberOfChunk = Number(value.replace(CHUNKED_KEY, ""));
Expand All @@ -52,7 +57,7 @@ const getCompressedValue = async (key, value) => {
keys.push(key + CHUNKED_KEY + i);
}

let values = [];
let values: KeyValuePair[] = [];

// multiget will failed when you got keys with a tons of data
// it crash with 13 CHUNKS of 1MB string so we had splice it.
Expand All @@ -67,10 +72,11 @@ const getCompressedValue = async (key, value) => {
(acc, current) => acc + current[1],
"",
);

return JSON.parse(concatString);
}

return JSON.parse(value);
return value ? JSON.parse(value) : value;
} catch (e) {
return undefined;
}
Expand All @@ -82,7 +88,7 @@ const deviceStorage = {
* @param {String|Array} key A key or array of keys
* @return {Promise}
*/
async get(key) {
async get(key: string) {
if (!Array.isArray(key)) {
const value = await AsyncStorage.getItem(key);
return getCompressedValue(key, value);
Expand All @@ -99,16 +105,16 @@ const deviceStorage = {
* @param {Any} value The value to save
* @return {Promise}
*/
save(key, value) {
let pairs = [];
async save(key: string | [string, unknown][], value?: unknown) {
let pairs: [string, unknown][] = [];

if (!Array.isArray(key)) {
pairs.push([key, value]);
} else {
pairs = key.map(pair => [pair[0], pair[1]]);
}

return AsyncStorage.multiSet(stringifyPairs(pairs));
return AsyncStorage.multiSet(stringifyPairs(pairs) as [string, string][]);
},

/**
Expand All @@ -117,7 +123,7 @@ const deviceStorage = {
* @param {Value} value The value to update with
* @return {Promise}
*/
update(key, value) {
async update(key: string, value: unknown) {
return deviceStorage
.get(key)
.then(item =>
Expand All @@ -133,12 +139,12 @@ const deviceStorage = {
* @param {String|Array} key The key or an array of keys to be deleted
* @return {Promise}
*/
async delete(key) {
async delete(key: string | string[]) {
let keys;
const existingKeys = await AsyncStorage.getAllKeys();

if (!Array.isArray(key)) {
keys = existingKeys.filter(existingKey => existingKey.include(key));
keys = existingKeys.filter(existingKey => existingKey.includes(key));
} else {
keys = existingKeys.filter(existingKey =>
key.some(keyToDelete => existingKey.includes(keyToDelete)),
Expand All @@ -152,7 +158,7 @@ const deviceStorage = {
* Get all keys in AsyncStorage.
* @return {Promise} A promise which when it resolves gets passed the saved keys in AsyncStorage.
*/
keys() {
async keys() {
return AsyncStorage.getAllKeys().then(keys =>
keys.filter(key => !key.includes(CHUNKED_KEY)),
);
Expand All @@ -164,7 +170,7 @@ const deviceStorage = {
* @param {Any} value The value to push onto the array
* @return {Promise}
*/
push(key, value) {
async push(key: string, value: unknown) {
return deviceStorage.get(key).then(currentValue => {
if (currentValue === null) {
// if there is no current value populate it with the new value
Expand All @@ -181,4 +187,5 @@ const deviceStorage = {
});
},
};

export default deviceStorage;
28 changes: 22 additions & 6 deletions apps/ledger-live-mobile/src/reducers/accounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
importAccountsReduce,
isUpToDateAccount,
withoutToken,
enableToken,
clearAccount,
nestedSortAccounts,
makeEmptyTokenAccount,
Expand All @@ -35,9 +36,11 @@ import accountModel from "../logic/accountModel";
export type AccountsState = {
active: Account[];
};

const initialState: AccountsState = {
active: [],
};

const handlers: Record<string, any> = {
ACCOUNTS_IMPORT: (s, { state }) => state,
ACCOUNTS_USER_IMPORT: (
Expand Down Expand Up @@ -108,26 +111,36 @@ const handlers: Record<string, any> = {
CLEAN_CACHE: (state: AccountsState): AccountsState => ({
active: state.active.map(clearAccount),
}),
SHOW_TOKEN: (
state: AccountsState,
{ payload: tokenId }: { payload: string },
) => {
return {
active: state.active.map(activeAccount =>
enableToken(activeAccount, tokenId),
),
};
},
BLACKLIST_TOKEN: (
state: AccountsState,
{
payload: tokenId,
}: {
payload: string;
},
) => ({
active: state.active.map(a => withoutToken(a, tokenId)),
}),
) => {
return { active: state.active.map(a => withoutToken(a, tokenId)) };
},
DANGEROUSLY_OVERRIDE_STATE: (state: AccountsState): AccountsState => ({
...state,
}),
};
// Selectors
export const exportSelector = (s: any) => ({
export const exportSelector = (s: State) => ({
active: s.accounts.active.map(accountModel.encode),
});

export const accountsSelector = (s: any): Account[] => s.accounts.active;
export const accountsSelector = (s: State): Account[] => s.accounts.active;

// NB some components don't need to refresh every time an account is updated, usually it's only
// when the balance/name/length/starred/swapHistory of accounts changes.
Expand All @@ -151,7 +164,7 @@ export const shallowAccountsSelector: OutputSelector<
Account[]
> = shallowAccountsSelectorCreator(accountsSelector, a => a);

export const migratableAccountsSelector = (s: any): Account[] =>
export const migratableAccountsSelector = (s: State): Account[] =>
s.accounts.active.filter(canBeMigrated);

export const flattenAccountsSelector = createSelector(
Expand All @@ -173,18 +186,21 @@ export const someAccountsNeedMigrationSelector = createSelector(
accountsSelector,
accounts => accounts.some(canBeMigrated),
);

export const currenciesSelector = createSelector(accountsSelector, accounts =>
uniq(flattenAccounts(accounts).map(a => getAccountCurrency(a))).sort((a, b) =>
a.name.localeCompare(b.name),
),
);

export const cryptoCurrenciesSelector = createSelector(
accountsSelector,
accounts =>
uniq(accounts.map(a => a.currency)).sort((a, b) =>
a.name.localeCompare(b.name),
),
);

export const accountsTuplesByCurrencySelector = createSelector(
accountsSelector,
(_, { currency }) => currency,
Expand Down
15 changes: 11 additions & 4 deletions apps/ledger-live-mobile/src/screens/Account/SubAccountsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,15 @@ import React, { useCallback, useState, useMemo } from "react";
import { Trans } from "react-i18next";
import take from "lodash/take";
import { StyleSheet, View, FlatList } from "react-native";
import { useNavigation, useTheme } from "@react-navigation/native";
import {
ParamListBase,
useNavigation,
useTheme,
} from "@react-navigation/native";
import { Account, SubAccount, TokenAccount } from "@ledgerhq/types-live";
import useEnv from "@ledgerhq/live-common/hooks/useEnv";
import { StackNavigationProp } from "@react-navigation/stack";

import { listSubAccounts } from "@ledgerhq/live-common/account/index";
import { listTokenTypesForCryptoCurrency } from "@ledgerhq/live-common/currencies/index";
import { Button, Flex, Text } from "@ledgerhq/native-ui";
Expand All @@ -20,7 +26,7 @@ import TokenContextualModal from "../Settings/Accounts/TokenContextualModal";
import perFamilySubAccountList from "../../generated/SubAccountList";
import SectionTitle from "../WalletCentricSections/SectionTitle";

const keyExtractor = (o: any) => o.id;
const keyExtractor = (subAccount: SubAccount) => subAccount.id;

const styles = StyleSheet.create({
footer: {
Expand Down Expand Up @@ -58,7 +64,7 @@ export default function SubAccountsList({
useEnv("HIDE_EMPTY_TOKEN_ACCOUNTS");

const { colors } = useTheme();
const navigation = useNavigation();
const navigation = useNavigation<StackNavigationProp<ParamListBase>>();
const [account, setAccount] = useState<TokenAccount | typeof undefined>();
const [isCollapsed, setIsCollapsed] = useState(true);
const subAccounts = listSubAccounts(parentAccount);
Expand Down Expand Up @@ -147,6 +153,7 @@ export default function SubAccountsList({
</Touchable>
);
}

// If there is 3 or less sub accounts, no need for collapse button
if (subAccounts.length <= 3) return null;

Expand Down Expand Up @@ -198,7 +205,7 @@ export default function SubAccountsList({
]);

const renderItem = useCallback(
({ item }) => (
({ item }: { item: SubAccount }) => (
<Flex alignItems={"center"}>
<SubAccountRow
account={item}
Expand Down
12 changes: 7 additions & 5 deletions apps/ledger-live-mobile/src/screens/Accounts/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,11 +157,13 @@ function Accounts({ navigation, route }: Props) {
}}
/>
<MigrateAccountsBanner />
<TokenContextualModal
onClose={() => setAccount(undefined)}
isOpened={!!account}
account={account}
/>
{account && (
<TokenContextualModal
onClose={() => setAccount(undefined)}
isOpened={!!account}
account={account}
/>
)}
</Flex>
</TabBarSafeAreaView>
);
Expand Down
Loading