Skip to content

fix: nano-contract history pagination and list order #512

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

Merged
merged 13 commits into from
Aug 22, 2024
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
182 changes: 102 additions & 80 deletions locale/da/texts.po

Large diffs are not rendered by default.

189 changes: 107 additions & 82 deletions locale/pt-br/texts.po

Large diffs are not rendered by default.

182 changes: 102 additions & 80 deletions locale/ru-ru/texts.po

Large diffs are not rendered by default.

182 changes: 102 additions & 80 deletions locale/texts.pot

Large diffs are not rendered by default.

8 changes: 6 additions & 2 deletions src/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -1126,8 +1126,12 @@ export const nanoContractHistoryLoading = (ncEntry) => ({
* Nano Contract history has loaded success.
* @param {Object} payload
* @param {string} payload.ncId Nano Contract ID.
* @param {Object[]} payload.history Nano Contract's history chunk as array.
* @param {string} payload.after A new history chunk will be fetched after this hash.
* @param {Object[]?} payload.history A chunk of txs to initialize history
* @param {Object[]?} payload.beforeHistory A chunk of newer txs.
* @param {Object[]?} payload.afterHistory A chunk of older txs.
*
* @description
* The history options are mutually exclusive.
*/
export const nanoContractHistorySuccess = (payload) => ({
type: types.NANOCONTRACT_HISTORY_SUCCESS,
Expand Down
76 changes: 60 additions & 16 deletions src/components/NanoContract/NanoContractDetails.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/

import { useNavigation } from '@react-navigation/native';
import React, { useEffect, useState } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import {
StyleSheet,
View,
Expand All @@ -23,6 +23,7 @@ import Spinner from '../Spinner';
import errorIcon from '../../assets/images/icErrorBig.png';
import SimpleButton from '../SimpleButton';
import { FeedbackContent } from '../FeedbackContent';
import NewHathorButton from '../NewHathorButton';

/**
* Retrieves Nano Contract details from Redux.
Expand All @@ -33,7 +34,6 @@ import { FeedbackContent } from '../FeedbackContent';
* txHistory: Object[];
* isLoading: boolean;
* error: string;
* after: string;
* }}
*/
const getNanoContractDetails = (ncId) => (state) => {
Expand All @@ -42,14 +42,13 @@ const getNanoContractDetails = (ncId) => (state) => {
* and let it step aside while coming back to Dashboard screen. This transition happens
* quickly, therefore the user will not have time to see the default state.
*/
const defaultMeta = { isLoading: false, error: null, after: null };
const defaultMeta = { isLoading: false, error: null };
const txHistory = state.nanoContract.history[ncId] || [];
const { isLoading, error, after } = state.nanoContract.historyMeta[ncId] || defaultMeta;
const { isLoading, error } = state.nanoContract.historyMeta[ncId] || defaultMeta;
return {
txHistory,
isLoading,
error,
after,
};
}

Expand All @@ -65,7 +64,11 @@ export const NanoContractDetails = ({ nc }) => {
const dispatch = useDispatch();
const navigation = useNavigation();

const { txHistory, isLoading, error, after } = useSelector(getNanoContractDetails(nc.ncId));
const {
txHistory,
isLoading,
error,
} = useSelector(getNanoContractDetails(nc.ncId));
const [ncAddress, changeNcAddress] = useState(nc.address);

const onAddressChange = (newAddress) => {
Expand All @@ -77,6 +80,7 @@ export const NanoContractDetails = ({ nc }) => {
navigation.navigate('NanoContractTransactionScreen', { tx });
};

// This effect runs only once when the component is first built.
useEffect(() => {
if (txHistory.length === 0) {
/* The first time we load the Nano Contract details its transaction history is empty.
Expand All @@ -85,18 +89,18 @@ export const NanoContractDetails = ({ nc }) => {
* For the first transaction history load we don't need to specify the `after` param,
* it will be set during the load.
*/
dispatch(nanoContractHistoryRequest({ ncId: nc.ncId, after: null }));
dispatch(nanoContractHistoryRequest({ ncId: nc.ncId }));
} else {
// Fetch new transactions when there are some transactions in the history.
dispatch(nanoContractHistoryRequest({ ncId: nc.ncId, before: txHistory[0].txId }));
}
}, []);

const handleMoreTransactions = () => {
if (after == null) {
/* This situation is unlikely to happen because on the first transactions history load
* the `after` is assigned with the hash of the last transaction in the list.
*/
return;
}
dispatch(nanoContractHistoryRequest({ ncId: nc.ncId, after }));
/**
* Triggered when a user makes the pull gesture on the transaction history content.
*/
const handleNewerTransactions = () => {
dispatch(nanoContractHistoryRequest({ ncId: nc.ncId, before: txHistory[0].txId }));
};

/* If an error happens on loading transactions history, an error feedback
Expand Down Expand Up @@ -133,13 +137,53 @@ export const NanoContractDetails = ({ nc }) => {
)}
keyExtractor={(item) => item.txId}
refreshing={isLoading}
onRefresh={handleMoreTransactions}
// Enables the pull gesture to get newer transactions
onRefresh={handleNewerTransactions}
// Enables a button to load more of older transactions until the end
// By reaching the end, the button ceases to render
ListFooterComponent={<LoadMoreButton lastTx={txHistory.slice(-1,).pop()} />}
extraData={[isLoading, error]}
/>
)}
</Wrapper>
);
};

/**
* It shows a button to 'Load More' transactions after the last one.
* It hides the button when the last transaction is the initialize.
*
* @param {Object} prop Properties object
* @param {{
* ncId: string;
* txId: string;
* }} prop.lastTx A transaction item from transaction history
*/
const LoadMoreButton = ({ lastTx }) => {
const dispatch = useDispatch();
const isInitializeTx = lastTx.ncMethod === 'initialize';

/**
* This handling will dispatch an action to request for
* older transactions after a txId.
*/
const handleLoadMore = () => useCallback(dispatch(nanoContractHistoryRequest({
ncId: lastTx.ncId,
after: lastTx.txId,
})), [lastTx]);

return !isInitializeTx && (
<NewHathorButton
title={t`Load More`}
onPress={handleLoadMore}
discrete
wrapperStyle={{
marginTop: 16,
}}
/>
)
};

/**
* @param {Object} props
* @param {Object?} props.children Either a react component or a react element
Expand Down
15 changes: 6 additions & 9 deletions src/reducers/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -384,15 +384,13 @@ const initialState = {
* [ncId: string]: {
* isLoading: boolean;
* error: string;
* after: string;
* };
* }} holds the load state for each nano contract, including the after hash
* from which a new history chunk should be fetched, exclusively.
* @example
* {
* '000001342d3c5b858a4d4835baea93fcc683fa615ff5892bd044459621a0340a': {
* isLoading: false,
* after: '000075e15f015dc768065763acd9b563ec002e37182869965ff2c712bed83e1e',
* },
* }
*/
Expand Down Expand Up @@ -1702,7 +1700,6 @@ export const onNanoContractHistoryClean = (state, { payload }) => ({
[payload.ncId]: {
...(state.nanoContract.historyMeta[payload.ncId]),
isLoading: false,
after: null,
error: null,
},
},
Expand All @@ -1714,8 +1711,9 @@ export const onNanoContractHistoryClean = (state, { payload }) => ({
* @param {{
* payload: {
* ncId: string;
* history: Object[];
* after: string;
* history?: Object[];
* beforeHistory?: Object[];
* afterHistory?: Object[];
* }
* }} action
*/
Expand All @@ -1726,17 +1724,16 @@ export const onNanoContractHistorySuccess = (state, { payload }) => ({
history: {
...state.nanoContract.history,
[payload.ncId]: [
...(state.nanoContract.history[payload.ncId] || []),
// we are putting at the bottom because we expect an array with descending order.
...payload.history,
...(payload.beforeHistory || []),
...(payload.history || state.nanoContract.history[payload.ncId] || []),
...(payload.afterHistory || []),
],
},
historyMeta: {
...state.nanoContract.historyMeta,
[payload.ncId]: {
...(state.nanoContract.historyMeta[payload.ncId]),
isLoading: false,
after: payload.after,
error: null,
},
},
Expand Down
Loading
Loading