diff --git a/e2e/lnurl.e2e.js b/e2e/lnurl.e2e.js index 1f852e719..ae2680664 100644 --- a/e2e/lnurl.e2e.js +++ b/e2e/lnurl.e2e.js @@ -199,6 +199,11 @@ d('LNURL', () => { .toBeVisible() .withTimeout(10000); await element(by.id('Close')).tap(); + // check if comment is displayed + await element(by.id('WalletsScrollView')).scrollTo('bottom', NaN, 0.85); + await element(by.id('ActivityShort-1')).tap(); + await expect(element(by.id('InvoiceComment'))).toHaveText('test comment'); + await element(by.id('NavigationClose')).tap(); // test lnurl-pay, with min == max amount, no comment const payRequest2 = await lnurl.generateNewUrl('payRequest', { diff --git a/src/screens/Activity/ActivityDetail.tsx b/src/screens/Activity/ActivityDetail.tsx index df50eb531..2b5e79d21 100644 --- a/src/screens/Activity/ActivityDetail.tsx +++ b/src/screens/Activity/ActivityDetail.tsx @@ -72,7 +72,12 @@ import { } from '../../store/slices/metadata'; import { getTransactions } from '../../utils/wallet/electrum'; import { ITransaction, ITxHash } from '../../utils/wallet'; -import { ellipsis, getDurationForBlocks, openURL } from '../../utils/helpers'; +import { + ellipsis, + getDurationForBlocks, + openURL, + vibrate, +} from '../../utils/helpers'; import { getBoostedTransactionParents } from '../../utils/boost'; import { showToast } from '../../utils/notifications'; import { @@ -81,6 +86,7 @@ import { transferSelector, } from '../../store/reselect/wallet'; import { + commentSelector, slashTagsUrlSelector, tagSelector, } from '../../store/reselect/metadata'; @@ -684,6 +690,7 @@ const LightningActivityDetail = ({ const dispatch = useAppDispatch(); const tags = useAppSelector((state) => tagSelector(state, id)); + const comment = useAppSelector((state) => commentSelector(state, id)); const slashTagsUrl = useAppSelector((state) => { return slashTagsUrlSelector(state, id); }); @@ -717,6 +724,7 @@ const LightningActivityDetail = ({ const onCopy = (text: string): void => { Clipboard.setString(text); + vibrate(); showToast({ type: 'success', title: t('copied'), @@ -910,7 +918,9 @@ const LightningActivityDetail = ({ )} {message ? ( - + onCopy(message)}> {t('activity_invoice_note')} @@ -922,7 +932,25 @@ const LightningActivityDetail = ({ {message} - + + ) : null} + + {comment ? ( + onCopy(comment)}> + + {t('activity_invoice_comment')} + + + + + + + {comment} + + + ) : null} diff --git a/src/screens/Wallets/LNURLPay/Confirm.tsx b/src/screens/Wallets/LNURLPay/Confirm.tsx index 720c95ac2..8c3a190c0 100644 --- a/src/screens/Wallets/LNURLPay/Confirm.tsx +++ b/src/screens/Wallets/LNURLPay/Confirm.tsx @@ -26,6 +26,7 @@ import { settingsSelector, } from '../../../store/reselect/settings'; import { addPendingPayment } from '../../../store/slices/lightning'; +import { updateMetaTxComment } from '../../../store/slices/metadata'; import { EActivityType } from '../../../store/types/activity'; import { AnimatedView, BottomSheetTextInput } from '../../../styles/components'; import { Checkmark, LightningHollow } from '../../../styles/icons'; @@ -111,6 +112,15 @@ const LNURLConfirm = ({ setIsLoading(false); + if (comment) { + dispatch( + updateMetaTxComment({ + txId: decodedInvoice.payment_hash, + comment, + }), + ); + } + if (payInvoiceResponse.isErr()) { const errorMessage = payInvoiceResponse.error.message; if (errorMessage === 'Timed Out.') { diff --git a/src/store/index.ts b/src/store/index.ts index 3209832e9..39fd4b371 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -32,7 +32,7 @@ const persistConfig = { key: 'root', storage: reduxStorage, // increase version after store shape changes - version: 46, + version: 47, stateReconciler: autoMergeLevel2, blacklist: ['receive', 'ui'], migrate: createMigrate(migrations, { debug: __ENABLE_MIGRATION_DEBUG__ }), diff --git a/src/store/migrations/index.ts b/src/store/migrations/index.ts index 3f5b781fe..dd5f30988 100644 --- a/src/store/migrations/index.ts +++ b/src/store/migrations/index.ts @@ -45,6 +45,15 @@ const migrations = { }, }; }, + 47: (state): PersistedState => { + return { + ...state, + metadata: { + ...state.metadata, + comments: {}, + }, + }; + }, }; export default migrations; diff --git a/src/store/reselect/metadata.ts b/src/store/reselect/metadata.ts index ce6e758df..65fb49de1 100644 --- a/src/store/reselect/metadata.ts +++ b/src/store/reselect/metadata.ts @@ -33,3 +33,7 @@ export const slashTagsUrlSelector = createSelector( return metadata.slashTagsUrls[id]; }, ); +export const commentSelector = createSelector( + [metadataState, (_state, txId: string): string => txId], + (metadata, txId): string => metadata.comments[txId] ?? '', +); diff --git a/src/store/slices/metadata.ts b/src/store/slices/metadata.ts index 70b93c9f3..58f7c6ffc 100644 --- a/src/store/slices/metadata.ts +++ b/src/store/slices/metadata.ts @@ -7,6 +7,7 @@ export const initialMetadataState: TMetadataState = { lastUsedTags: [], pendingInvoices: [], slashTagsUrls: {}, + comments: {}, }; export const metadataSlice = createSlice({ @@ -26,6 +27,16 @@ export const metadataSlice = createSlice({ state.tags[action.payload.txId] = action.payload.tags; } }, + updateMetaTxComment: ( + state, + action: PayloadAction<{ txId: string; comment: string }>, + ) => { + if (action.payload.comment.length === 0) { + delete state.comments[action.payload.txId]; + } else { + state.comments[action.payload.txId] = action.payload.comment; + } + }, addMetaTxTag: ( state, action: PayloadAction<{ txId: string; tag: string }>, @@ -120,6 +131,7 @@ const { actions, reducer } = metadataSlice; export const { updateMetadata, updateMetaTxTags, + updateMetaTxComment, addMetaTxTag, deleteMetaTxTag, updatePendingInvoice, diff --git a/src/store/types/metadata.ts b/src/store/types/metadata.ts index f3ccf47bc..d5c36156f 100644 --- a/src/store/types/metadata.ts +++ b/src/store/types/metadata.ts @@ -1,6 +1,7 @@ export type TTags = { [txId: string]: string[] }; export type TLastUsedTags = string[]; export type TSlashTagsUrls = { [txId: string]: string | undefined }; +export type TTxComments = { [txId: string]: string }; export type TPendingInvoice = { id: string; // uuid used to identify the invoice 'session' @@ -16,4 +17,5 @@ export type TMetadataState = { // Keep track of pending invoices, right now this is only used to map tags to incoming transactions pendingInvoices: TPendingInvoice[]; slashTagsUrls: TSlashTagsUrls; + comments: TTxComments; }; diff --git a/src/store/utils/backup.ts b/src/store/utils/backup.ts index 692acc03a..fac222d98 100644 --- a/src/store/utils/backup.ts +++ b/src/store/utils/backup.ts @@ -363,6 +363,11 @@ const performMetadataRestore = async (): Promise< return ok({ backupExists: false }); } + // apply migrations + if (backupRes.value.metadata.version < 47) { + backup.comments = {}; + } + dispatch(updateMetadata({ ...expectedBackupShape, ...backup })); dispatch(backupSuccess({ category: EBackupCategories.metadata })); diff --git a/src/utils/i18n/locales/en/wallet.json b/src/utils/i18n/locales/en/wallet.json index 16e36022d..851f76592 100644 --- a/src/utils/i18n/locales/en/wallet.json +++ b/src/utils/i18n/locales/en/wallet.json @@ -506,6 +506,9 @@ "activity_invoice_note": { "string": "Invoice note" }, + "activity_invoice_comment": { + "string": "Comment" + }, "activity_invoice": { "string": "Invoice" },