mirror of
https://github.com/actualbudget/actual.git
synced 2026-03-22 00:13:45 -05:00
Mobile: improve performance for transaction list usage (#6755)
* Mobile: add usePullToRefreshOnScrollContainer hook, bypass PTR library, refactor transaction list * Remove usePullToRefreshOnScrollContainer hook from desktop client * Enhance PullToRefresh component with optional style prop and refactor TransactionList layout * Refactor TransactionListItem component: remove unused imports and hooks, update props type to use ListBoxItemRenderProps
This commit is contained in:
committed by
GitHub
parent
f55a42d817
commit
c3e3a258e0
@@ -1,9 +1,12 @@
|
||||
import React, { type ComponentProps } from 'react';
|
||||
import BasePullToRefresh from 'react-simple-pull-to-refresh';
|
||||
|
||||
import { type CSSProperties } from '@actual-app/components/styles';
|
||||
import { css } from '@emotion/css';
|
||||
|
||||
type PullToRefreshProps = ComponentProps<typeof BasePullToRefresh>;
|
||||
type PullToRefreshProps = ComponentProps<typeof BasePullToRefresh> & {
|
||||
style?: CSSProperties;
|
||||
};
|
||||
|
||||
export function PullToRefresh(props: PullToRefreshProps) {
|
||||
return (
|
||||
@@ -18,6 +21,7 @@ export function PullToRefresh(props: PullToRefreshProps) {
|
||||
'& .ptr__children': {
|
||||
overflow: 'hidden auto',
|
||||
},
|
||||
...(props.style || {}),
|
||||
})}
|
||||
{...props}
|
||||
// Force async because the library errors out when a sync onRefresh method is provided.
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
Collection,
|
||||
Header,
|
||||
ListBox,
|
||||
ListBoxItem,
|
||||
ListBoxSection,
|
||||
ListLayout,
|
||||
Virtualizer,
|
||||
@@ -161,14 +162,14 @@ export function TransactionList({
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<View style={{ flex: 1 }}>
|
||||
{isLoading && (
|
||||
<Loading
|
||||
style={{ paddingBottom: 8 }}
|
||||
style={{ flex: 'none', paddingBottom: 8 }}
|
||||
aria-label={t('Loading transactions...')}
|
||||
/>
|
||||
)}
|
||||
<View style={{ flex: 1, overflow: 'auto' }}>
|
||||
<View style={{ flex: 1 }}>
|
||||
<Virtualizer
|
||||
layout={ListLayout}
|
||||
layoutOptions={{
|
||||
@@ -181,6 +182,7 @@ export function TransactionList({
|
||||
selectionMode={
|
||||
selectedTransactions.size > 0 ? 'multiple' : 'single'
|
||||
}
|
||||
style={{ flex: 1, overflow: 'auto' }}
|
||||
selectedKeys={selectedTransactions}
|
||||
dependencies={[
|
||||
selectedTransactions,
|
||||
@@ -232,14 +234,18 @@ export function TransactionList({
|
||||
)}
|
||||
>
|
||||
{transaction => (
|
||||
<TransactionListItem
|
||||
key={transaction.id}
|
||||
showRunningBalance={showRunningBalances}
|
||||
runningBalance={runningBalances?.get(transaction.id)}
|
||||
value={transaction}
|
||||
onPress={trans => onTransactionPress(trans)}
|
||||
onLongPress={trans => onTransactionPress(trans, true)}
|
||||
/>
|
||||
<ListBoxItem textValue={transaction.id} value={transaction}>
|
||||
{itemProps => (
|
||||
<TransactionListItem
|
||||
{...itemProps}
|
||||
showRunningBalance={showRunningBalances}
|
||||
runningBalance={runningBalances?.get(transaction.id)}
|
||||
transaction={transaction}
|
||||
onPress={trans => onTransactionPress(trans)}
|
||||
onLongPress={trans => onTransactionPress(trans, true)}
|
||||
/>
|
||||
)}
|
||||
</ListBoxItem>
|
||||
)}
|
||||
</Collection>
|
||||
</ListBoxSection>
|
||||
@@ -264,7 +270,7 @@ export function TransactionList({
|
||||
showMakeTransfer={showMakeTransfer}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import React, {
|
||||
type ComponentPropsWithoutRef,
|
||||
type CSSProperties,
|
||||
} from 'react';
|
||||
import React, { type CSSProperties } from 'react';
|
||||
import { mergeProps } from 'react-aria';
|
||||
import { ListBoxItem } from 'react-aria-components';
|
||||
import { type ListBoxItemRenderProps } from 'react-aria-components';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { Button } from '@actual-app/components/button';
|
||||
@@ -74,10 +71,8 @@ const getScheduleIconStyle = ({ isPreview }: { isPreview: boolean }) => ({
|
||||
color: isPreview ? theme.pageTextLight : theme.menuItemText,
|
||||
});
|
||||
|
||||
type TransactionListItemProps = Omit<
|
||||
ComponentPropsWithoutRef<typeof ListBoxItem<TransactionEntity>>,
|
||||
'onPress'
|
||||
> & {
|
||||
type TransactionListItemProps = ListBoxItemRenderProps & {
|
||||
transaction?: TransactionEntity;
|
||||
showRunningBalance?: boolean;
|
||||
runningBalance?: IntegerAmount;
|
||||
onPress: (transaction: TransactionEntity) => void;
|
||||
@@ -89,13 +84,12 @@ export function TransactionListItem({
|
||||
runningBalance,
|
||||
onPress,
|
||||
onLongPress,
|
||||
...props
|
||||
transaction,
|
||||
...itemProps
|
||||
}: TransactionListItemProps) {
|
||||
const { t } = useTranslation();
|
||||
const { list: categories } = useCategories();
|
||||
|
||||
const { value: transaction } = props;
|
||||
|
||||
const payee = usePayee(transaction?.payee || '');
|
||||
const displayPayee = useDisplayPayee({ transaction });
|
||||
|
||||
@@ -156,173 +150,162 @@ export function TransactionListItem({
|
||||
const textStyle = getTextStyle({ isPreview });
|
||||
|
||||
return (
|
||||
<ListBoxItem textValue={id} {...props}>
|
||||
{itemProps => (
|
||||
<PressResponder {...mergeProps(pressProps, longPressProps)}>
|
||||
<Button
|
||||
{...itemProps}
|
||||
style={{
|
||||
userSelect: 'none',
|
||||
height: ROW_HEIGHT,
|
||||
width: '100%',
|
||||
borderRadius: 0,
|
||||
...(itemProps.isSelected
|
||||
? {
|
||||
borderWidth: '0 0 0 4px',
|
||||
borderColor: theme.mobileTransactionSelected,
|
||||
borderStyle: 'solid',
|
||||
}
|
||||
: {
|
||||
borderWidth: '0 0 1px 0',
|
||||
borderColor: theme.tableBorder,
|
||||
borderStyle: 'solid',
|
||||
}),
|
||||
...(isPreview
|
||||
? {
|
||||
backgroundColor: theme.tableRowHeaderBackground,
|
||||
}
|
||||
: {
|
||||
backgroundColor: theme.tableBackground,
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
padding: '0 4px',
|
||||
}}
|
||||
>
|
||||
<View style={{ flex: 1 }}>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||
<PayeeIcons
|
||||
transaction={transaction}
|
||||
transferAccount={transferAccount}
|
||||
/>
|
||||
<TextOneLine
|
||||
<PressResponder {...mergeProps(pressProps, longPressProps)}>
|
||||
<Button
|
||||
{...itemProps}
|
||||
style={{
|
||||
userSelect: 'none',
|
||||
height: ROW_HEIGHT,
|
||||
width: '100%',
|
||||
borderRadius: 0,
|
||||
...(itemProps.isSelected
|
||||
? {
|
||||
borderWidth: '0 0 0 4px',
|
||||
borderColor: theme.mobileTransactionSelected,
|
||||
borderStyle: 'solid',
|
||||
}
|
||||
: {
|
||||
borderWidth: '0 0 1px 0',
|
||||
borderColor: theme.tableBorder,
|
||||
borderStyle: 'solid',
|
||||
}),
|
||||
...(isPreview
|
||||
? {
|
||||
backgroundColor: theme.tableRowHeaderBackground,
|
||||
}
|
||||
: {
|
||||
backgroundColor: theme.tableBackground,
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
padding: '0 4px',
|
||||
}}
|
||||
>
|
||||
<View style={{ flex: 1 }}>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||
<PayeeIcons
|
||||
transaction={transaction}
|
||||
transferAccount={transferAccount}
|
||||
/>
|
||||
<TextOneLine
|
||||
style={{
|
||||
...textStyle,
|
||||
fontWeight: isAdded ? '600' : '400',
|
||||
...(!displayPayee && !isPreview
|
||||
? {
|
||||
color: theme.pageTextLight,
|
||||
fontStyle: 'italic',
|
||||
}
|
||||
: {}),
|
||||
}}
|
||||
>
|
||||
{displayPayee || t('(No payee)')}
|
||||
</TextOneLine>
|
||||
</View>
|
||||
{isPreview ? (
|
||||
<Status status={previewStatus} isSplit={isParent || isChild} />
|
||||
) : (
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginTop: 3,
|
||||
}}
|
||||
>
|
||||
{isReconciled ? (
|
||||
<SvgLockClosed
|
||||
style={{
|
||||
...textStyle,
|
||||
fontWeight: isAdded ? '600' : '400',
|
||||
...(!displayPayee && !isPreview
|
||||
? {
|
||||
color: theme.pageTextLight,
|
||||
fontStyle: 'italic',
|
||||
}
|
||||
: {}),
|
||||
width: 11,
|
||||
height: 11,
|
||||
color: theme.noticeTextLight,
|
||||
marginRight: 5,
|
||||
}}
|
||||
>
|
||||
{displayPayee || t('(No payee)')}
|
||||
</TextOneLine>
|
||||
</View>
|
||||
{isPreview ? (
|
||||
<Status
|
||||
status={previewStatus}
|
||||
isSplit={isParent || isChild}
|
||||
/>
|
||||
) : (
|
||||
<View
|
||||
<SvgCheckCircle1
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginTop: 3,
|
||||
width: 11,
|
||||
height: 11,
|
||||
color: isCleared
|
||||
? theme.noticeTextLight
|
||||
: theme.pageTextSubdued,
|
||||
marginRight: 5,
|
||||
}}
|
||||
>
|
||||
{isReconciled ? (
|
||||
<SvgLockClosed
|
||||
style={{
|
||||
width: 11,
|
||||
height: 11,
|
||||
color: theme.noticeTextLight,
|
||||
marginRight: 5,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<SvgCheckCircle1
|
||||
style={{
|
||||
width: 11,
|
||||
height: 11,
|
||||
color: isCleared
|
||||
? theme.noticeTextLight
|
||||
: theme.pageTextSubdued,
|
||||
marginRight: 5,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{(isParent || isChild) && (
|
||||
<SvgSplit
|
||||
style={{
|
||||
width: 12,
|
||||
height: 12,
|
||||
marginRight: 5,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<TextOneLine
|
||||
style={{
|
||||
fontSize: 11,
|
||||
marginTop: 1,
|
||||
fontWeight: '400',
|
||||
color: prettyCategory
|
||||
? theme.tableText
|
||||
: theme.menuItemTextSelected,
|
||||
fontStyle:
|
||||
specialCategory || !prettyCategory
|
||||
? 'italic'
|
||||
: undefined,
|
||||
textAlign: 'left',
|
||||
}}
|
||||
>
|
||||
{prettyCategory || t('Uncategorized')}
|
||||
</TextOneLine>
|
||||
</View>
|
||||
/>
|
||||
)}
|
||||
{notes && (
|
||||
<TextOneLine
|
||||
{(isParent || isChild) && (
|
||||
<SvgSplit
|
||||
style={{
|
||||
fontSize: 11,
|
||||
marginTop: 4,
|
||||
fontWeight: '400',
|
||||
color: theme.tableText,
|
||||
textAlign: 'left',
|
||||
opacity: 0.85,
|
||||
width: 12,
|
||||
height: 12,
|
||||
marginRight: 5,
|
||||
}}
|
||||
>
|
||||
<NotesTagFormatter notes={notes} />
|
||||
</TextOneLine>
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
<View
|
||||
style={{ justifyContent: 'center', alignItems: 'flex-end' }}
|
||||
>
|
||||
<Text
|
||||
<TextOneLine
|
||||
style={{
|
||||
...textStyle,
|
||||
...styles.tnum,
|
||||
...makeAmountFullStyle(amount),
|
||||
fontSize: 11,
|
||||
marginTop: 1,
|
||||
fontWeight: '400',
|
||||
color: prettyCategory
|
||||
? theme.tableText
|
||||
: theme.menuItemTextSelected,
|
||||
fontStyle:
|
||||
specialCategory || !prettyCategory ? 'italic' : undefined,
|
||||
textAlign: 'left',
|
||||
}}
|
||||
>
|
||||
{integerToCurrency(amount)}
|
||||
</Text>
|
||||
{showRunningBalance && runningBalance !== undefined && (
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 11,
|
||||
fontWeight: '400',
|
||||
...styles.tnum,
|
||||
...makeBalanceAmountStyle(runningBalance),
|
||||
}}
|
||||
>
|
||||
{integerToCurrency(runningBalance)}
|
||||
</Text>
|
||||
)}
|
||||
{prettyCategory || t('Uncategorized')}
|
||||
</TextOneLine>
|
||||
</View>
|
||||
</View>
|
||||
</Button>
|
||||
</PressResponder>
|
||||
)}
|
||||
</ListBoxItem>
|
||||
)}
|
||||
{notes && (
|
||||
<TextOneLine
|
||||
style={{
|
||||
fontSize: 11,
|
||||
marginTop: 4,
|
||||
fontWeight: '400',
|
||||
color: theme.tableText,
|
||||
textAlign: 'left',
|
||||
opacity: 0.85,
|
||||
}}
|
||||
>
|
||||
<NotesTagFormatter notes={notes} />
|
||||
</TextOneLine>
|
||||
)}
|
||||
</View>
|
||||
<View style={{ justifyContent: 'center', alignItems: 'flex-end' }}>
|
||||
<Text
|
||||
style={{
|
||||
...textStyle,
|
||||
...styles.tnum,
|
||||
...makeAmountFullStyle(amount),
|
||||
}}
|
||||
>
|
||||
{integerToCurrency(amount)}
|
||||
</Text>
|
||||
{showRunningBalance && runningBalance !== undefined && (
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 11,
|
||||
fontWeight: '400',
|
||||
...styles.tnum,
|
||||
...makeBalanceAmountStyle(runningBalance),
|
||||
}}
|
||||
>
|
||||
{integerToCurrency(runningBalance)}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</Button>
|
||||
</PressResponder>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -147,6 +147,11 @@ export function TransactionListWithBalances({
|
||||
<PullToRefresh
|
||||
isPullable={!isLoading && !!onRefresh}
|
||||
onRefresh={async () => onRefresh?.()}
|
||||
style={{
|
||||
'& .ptr__children': {
|
||||
display: 'flex',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<TransactionList
|
||||
isLoading={isLoading}
|
||||
|
||||
Reference in New Issue
Block a user