From 0e539d91fe295c124e2850bd81d69b9bdc131f3c Mon Sep 17 00:00:00 2001
From: Matiss Janis Aboltins <matiss@mja.lv>
Date: Thu, 7 Sep 2023 06:56:02 +0100
Subject: [PATCH] :sparkles: (mobile) pull down to trigger bank-sync (#1663)

* :sparkles: (mobile) pull down to trigger bank-sync

* Release notes

* Remove canSync checks
---
 packages/desktop-client/package.json          |   1 +
 .../src/components/accounts/MobileAccount.js  |  77 ++++------
 .../accounts/MobileAccountDetails.js          |  34 +++--
 .../src/components/accounts/MobileAccounts.js | 135 ++++++------------
 .../transactions/MobileTransaction.js         |   8 +-
 upcoming-release-notes/1663.md                |   6 +
 yarn.lock                                     |  11 ++
 7 files changed, 117 insertions(+), 155 deletions(-)
 create mode 100644 upcoming-release-notes/1663.md

diff --git a/packages/desktop-client/package.json b/packages/desktop-client/package.json
index ffa368101..b8dc8f842 100644
--- a/packages/desktop-client/package.json
+++ b/packages/desktop-client/package.json
@@ -53,6 +53,7 @@
     "react-redux": "7.2.1",
     "react-router-dom": "6.11.2",
     "react-scripts": "^5.0.1",
+    "react-simple-pull-to-refresh": "^1.3.3",
     "react-spring": "^9.7.1",
     "react-virtualized-auto-sizer": "^1.0.2",
     "redux": "^4.0.5",
diff --git a/packages/desktop-client/src/components/accounts/MobileAccount.js b/packages/desktop-client/src/components/accounts/MobileAccount.js
index c206fca0e..83a3f6c5b 100644
--- a/packages/desktop-client/src/components/accounts/MobileAccount.js
+++ b/packages/desktop-client/src/components/accounts/MobileAccount.js
@@ -19,11 +19,9 @@ import {
   ungroupTransactions,
 } from 'loot-core/src/shared/transactions';
 
-import { useActions } from '../../hooks/useActions';
 import useCategories from '../../hooks/useCategories';
 import { useSetThemeColor } from '../../hooks/useSetThemeColor';
 import { theme } from '../../style';
-import SyncRefresh from '../SyncRefresh';
 
 import AccountDetails from './MobileAccountDetails';
 
@@ -71,7 +69,6 @@ let paged;
 
 export default function Account(props) {
   const accounts = useSelector(state => state.queries.accounts);
-  const { syncAndDownload } = useActions();
 
   const navigate = useNavigate();
   const [transactions, setTransactions] = useState([]);
@@ -191,54 +188,40 @@ export default function Account(props) {
     }
   };
 
-  const onRefresh = async () => {
-    await syncAndDownload();
-  };
-
   let balance = queries.accountBalance(account);
   let numberFormat = state.prefs.numberFormat || 'comma-dot';
   let hideFraction = state.prefs.hideFraction || false;
 
   return (
-    <SyncRefresh onSync={onRefresh}>
-      {({ refreshing, onRefresh }) => (
-        <SchedulesProvider
-          transform={getSchedulesTransform(accountId, searchText !== '')}
-        >
-          <PreviewTransactions accountId={props.accountId}>
-            {prependTransactions =>
-              prependTransactions == null ? null : (
-                <AccountDetails
-                  // This key forces the whole table rerender when the number
-                  // format changes
-                  {...state}
-                  {...actionCreators}
-                  key={numberFormat + hideFraction}
-                  account={account}
-                  accounts={accounts}
-                  categories={categories.list}
-                  payees={state.payees}
-                  transactions={transactions}
-                  prependTransactions={prependTransactions || []}
-                  balance={balance}
-                  isNewTransaction={isNewTransaction}
-                  // refreshControl={
-                  //   <RefreshControl
-                  //     refreshing={refreshing}
-                  //     onRefresh={onRefresh}
-                  //   />
-                  // }
-                  onLoadMore={() => {
-                    paged?.fetchNext();
-                  }}
-                  onSearch={onSearch}
-                  onSelectTransaction={onSelectTransaction}
-                />
-              )
-            }
-          </PreviewTransactions>
-        </SchedulesProvider>
-      )}
-    </SyncRefresh>
+    <SchedulesProvider
+      transform={getSchedulesTransform(accountId, searchText !== '')}
+    >
+      <PreviewTransactions accountId={props.accountId}>
+        {prependTransactions =>
+          prependTransactions == null ? null : (
+            <AccountDetails
+              // This key forces the whole table rerender when the number
+              // format changes
+              {...state}
+              {...actionCreators}
+              key={numberFormat + hideFraction}
+              account={account}
+              accounts={accounts}
+              categories={categories.list}
+              payees={state.payees}
+              transactions={transactions}
+              prependTransactions={prependTransactions || []}
+              balance={balance}
+              isNewTransaction={isNewTransaction}
+              onLoadMore={() => {
+                paged?.fetchNext();
+              }}
+              onSearch={onSearch}
+              onSelectTransaction={onSelectTransaction}
+            />
+          )
+        }
+      </PreviewTransactions>
+    </SchedulesProvider>
   );
 }
diff --git a/packages/desktop-client/src/components/accounts/MobileAccountDetails.js b/packages/desktop-client/src/components/accounts/MobileAccountDetails.js
index 611b87778..e3dd20985 100644
--- a/packages/desktop-client/src/components/accounts/MobileAccountDetails.js
+++ b/packages/desktop-client/src/components/accounts/MobileAccountDetails.js
@@ -1,6 +1,8 @@
 import React, { useState, useMemo } from 'react';
 import { Link } from 'react-router-dom';
+import PullToRefresh from 'react-simple-pull-to-refresh';
 
+import { useActions } from '../../hooks/useActions';
 import Add from '../../icons/v1/Add';
 import CheveronLeft from '../../icons/v1/CheveronLeft';
 import SearchAlternate from '../../icons/v2/SearchAlternate';
@@ -75,12 +77,16 @@ export default function AccountDetails({
   onSearch,
   onSelectTransaction,
   pushModal,
-  // refreshControl
 }) {
   let allTransactions = useMemo(() => {
     return prependTransactions.concat(transactions);
   }, [prependTransactions, transactions]);
 
+  const { syncAndDownload } = useActions();
+  const onRefresh = async () => {
+    await syncAndDownload(account.id);
+  };
+
   return (
     <View
       style={{
@@ -161,18 +167,20 @@ export default function AccountDetails({
           onSearch={onSearch}
         />
       </View>
-      <TransactionList
-        transactions={allTransactions}
-        categories={categories}
-        accounts={accounts}
-        payees={payees}
-        showCategory={!account.offbudget}
-        isNew={isNewTransaction}
-        // refreshControl={refreshControl}
-        onLoadMore={onLoadMore}
-        onSelect={onSelectTransaction}
-        pushModal={pushModal}
-      />
+
+      <PullToRefresh onRefresh={onRefresh}>
+        <TransactionList
+          transactions={allTransactions}
+          categories={categories}
+          accounts={accounts}
+          payees={payees}
+          showCategory={!account.offbudget}
+          isNew={isNewTransaction}
+          onLoadMore={onLoadMore}
+          onSelect={onSelectTransaction}
+          pushModal={pushModal}
+        />
+      </PullToRefresh>
     </View>
   );
 }
diff --git a/packages/desktop-client/src/components/accounts/MobileAccounts.js b/packages/desktop-client/src/components/accounts/MobileAccounts.js
index 07902c091..457c645a4 100644
--- a/packages/desktop-client/src/components/accounts/MobileAccounts.js
+++ b/packages/desktop-client/src/components/accounts/MobileAccounts.js
@@ -1,6 +1,7 @@
-import React, { Component, useEffect, useState } from 'react';
+import React, { useEffect, useState } from 'react';
 import { useSelector } from 'react-redux';
 import { useNavigate } from 'react-router-dom';
+import PullToRefresh from 'react-simple-pull-to-refresh';
 
 import * as queries from 'loot-core/src/client/queries';
 
@@ -149,93 +150,58 @@ function EmptyMessage({ onAdd }) {
   );
 }
 
-class AccountList extends Component {
-  isNewTransaction = id => {
-    return this.props.newTransactions.includes(id);
-  };
+function AccountList({
+  accounts,
+  updatedAccounts,
+  getBalanceQuery,
+  getOnBudgetBalance,
+  getOffBudgetBalance,
+  onAddAccount,
+  onSelectAccount,
+}) {
+  const { syncAndDownload } = useActions();
 
-  render() {
-    const {
-      accounts,
-      updatedAccounts,
-      // transactions,
-      // categories,
-      getBalanceQuery,
-      getOnBudgetBalance,
-      getOffBudgetBalance,
-      onAddAccount,
-      onSelectAccount,
-      // onSelectTransaction,
-      // refreshControl
-    } = this.props;
-    const budgetedAccounts = accounts.filter(
-      account => account.offbudget === 0,
-    );
-    const offbudgetAccounts = accounts.filter(
-      account => account.offbudget === 1,
-    );
+  const budgetedAccounts = accounts.filter(account => account.offbudget === 0);
+  const offbudgetAccounts = accounts.filter(account => account.offbudget === 1);
 
-    // If there are no accounts, show a helpful message
-    if (accounts.length === 0) {
-      return <EmptyMessage onAdd={onAddAccount} />;
-    }
+  // If there are no accounts, show a helpful message
+  if (accounts.length === 0) {
+    return <EmptyMessage onAdd={onAddAccount} />;
+  }
 
-    const accountContent = (
+  return (
+    <View style={{ flex: 1 }}>
       <Page title="Accounts">
-        <AccountHeader name="For Budget" amount={getOnBudgetBalance()} />
-        {budgetedAccounts.map((acct, idx) => (
-          <AccountCard
-            account={acct}
-            key={acct.id}
-            updated={updatedAccounts.includes(acct.id)}
-            getBalanceQuery={getBalanceQuery}
-            onSelect={onSelectAccount}
-          />
-        ))}
+        <PullToRefresh onRefresh={syncAndDownload}>
+          <AccountHeader name="For Budget" amount={getOnBudgetBalance()} />
+          {budgetedAccounts.map(acct => (
+            <AccountCard
+              account={acct}
+              key={acct.id}
+              updated={updatedAccounts.includes(acct.id)}
+              getBalanceQuery={getBalanceQuery}
+              onSelect={onSelectAccount}
+            />
+          ))}
 
-        <AccountHeader
-          name="Off budget"
-          amount={getOffBudgetBalance()}
-          style={{ marginTop: 30 }}
-        />
-        {offbudgetAccounts.map((acct, idx) => (
-          <AccountCard
-            account={acct}
-            key={acct.id}
-            updated={updatedAccounts.includes(acct.id)}
-            getBalanceQuery={getBalanceQuery}
-            onSelect={onSelectAccount}
+          <AccountHeader
+            name="Off budget"
+            amount={getOffBudgetBalance()}
+            style={{ marginTop: 30 }}
           />
-        ))}
-
-        {/*<Label
-          title="RECENT TRANSACTIONS"
-          style={{
-            textAlign: 'center',
-            marginTop: 50,
-            marginBottom: 20,
-            marginLeft: 10
-          }}
-          />*/}
+          {offbudgetAccounts.map(acct => (
+            <AccountCard
+              account={acct}
+              key={acct.id}
+              updated={updatedAccounts.includes(acct.id)}
+              getBalanceQuery={getBalanceQuery}
+              onSelect={onSelectAccount}
+            />
+          ))}
+        </PullToRefresh>
       </Page>
-    );
-
-    return (
-      <View style={{ flex: 1 }}>
-        {/* <TransactionList
-          transactions={transactions}
-          categories={categories}
-          isNew={this.isNewTransaction}
-          scrollProps={{
-            ListHeaderComponent: accountContent
-          }}
-          // refreshControl={refreshControl}
-          onSelect={onSelectTransaction}
-        /> */}
-        {accountContent}
-      </View>
-    );
-  }
+    </View>
+  );
 }
 
 export default function Accounts() {
@@ -259,10 +225,6 @@ export default function Accounts() {
     (async () => getAccounts())();
   }, []);
 
-  // const sync = async () => {
-  //   await props.syncAndDownload();
-  // };
-
   const onSelectAccount = id => {
     navigate(`/accounts/${id}`);
   };
@@ -290,9 +252,6 @@ export default function Accounts() {
         onAddAccount={() => {}} // () => navigate('AddAccountModal')
         onSelectAccount={onSelectAccount}
         onSelectTransaction={onSelectTransaction}
-        // refreshControl={
-        //   <RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
-        // }
       />
     </View>
   );
diff --git a/packages/desktop-client/src/components/transactions/MobileTransaction.js b/packages/desktop-client/src/components/transactions/MobileTransaction.js
index 65aa9a3e2..12120b578 100644
--- a/packages/desktop-client/src/components/transactions/MobileTransaction.js
+++ b/packages/desktop-client/src/components/transactions/MobileTransaction.js
@@ -934,7 +934,6 @@ class Transaction extends PureComponent {
           backgroundColor: 'white',
           border: 'none',
           width: '100%',
-          '&:active': { opacity: 0.1 },
         }}
       >
         <ListItem
@@ -1057,12 +1056,7 @@ export class TransactionList extends Component {
   });
 
   render() {
-    const {
-      transactions,
-      scrollProps = {},
-      onLoadMore,
-      // refreshControl
-    } = this.props;
+    const { transactions, scrollProps = {}, onLoadMore } = this.props;
 
     const sections = this.makeData(transactions);
 
diff --git a/upcoming-release-notes/1663.md b/upcoming-release-notes/1663.md
new file mode 100644
index 000000000..02f02c0d2
--- /dev/null
+++ b/upcoming-release-notes/1663.md
@@ -0,0 +1,6 @@
+---
+category: Features
+authors: [MatissJanis]
+---
+
+Mobile: pull down to trigger a bank-sync
diff --git a/yarn.lock b/yarn.lock
index 691518072..b0c828515 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -99,6 +99,7 @@ __metadata:
     react-redux: 7.2.1
     react-router-dom: 6.11.2
     react-scripts: ^5.0.1
+    react-simple-pull-to-refresh: ^1.3.3
     react-spring: ^9.7.1
     react-virtualized-auto-sizer: ^1.0.2
     redux: ^4.0.5
@@ -16797,6 +16798,16 @@ __metadata:
   languageName: node
   linkType: hard
 
+"react-simple-pull-to-refresh@npm:^1.3.3":
+  version: 1.3.3
+  resolution: "react-simple-pull-to-refresh@npm:1.3.3"
+  peerDependencies:
+    react: ^16.10.2 || ^17.0.0 || ^18.0.0
+    react-dom: ^16.10.2 || ^17.0.0 || ^18.0.0
+  checksum: 8b04ec1ccf9eae2d7bf9d8304c5a49ddb0a00c0289f703ba780e74af174df234642449ad26813daf146c3991999d8fdcbb75e8fca90e2d11d6239b91d033a666
+  languageName: node
+  linkType: hard
+
 "react-spring@npm:^9.7.1":
   version: 9.7.1
   resolution: "react-spring@npm:9.7.1"
-- 
GitLab