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

Pull-to-Refresh for NIM and BTC transaction list #112

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
21 changes: 20 additions & 1 deletion src/components/BtcTransactionList.vue
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
<template>
<div class="transaction-list flex-row" ref="root">
<Pull2RefreshIndicator :pulledDistance="pulledDistance" />
<RecycleScroller
v-if="isFetchingTxHistory || transactions.length"
:items="transactions"
:item-size="itemSize"
key-field="transactionHash"
:buffer="scrollerBuffer"
ref="scroller"
:style="`transform: translateY(${pulledDistance / 2}px)`"
:class="{'smooth': !pulledDistance}"
>
<template v-slot:default="{ item, index }">
<div
Expand Down Expand Up @@ -94,6 +97,9 @@ import { useWindowSize } from '../composables/useWindowSize';
import { useAccountStore } from '../stores/Account';
import { useBtcLabelsStore } from '../stores/BtcLabels';
// import { ENV_MAIN } from '../lib/Constants';
import Pull2RefreshIndicator from './Pull2RefreshIndicator.vue';
import { usePull2Refresh } from '../composables/usePull2Refresh';
import { refetchActiveAccount } from '../electrum';

function processTimestamp(timestamp: number) {
const date: Date = new Date(timestamp);
Expand Down Expand Up @@ -350,13 +356,20 @@ export default defineComponent({

// Scroll to top when
// - Active address changes
const scroller = ref<{ scrollToPosition(position: number, smooth?: boolean): void } | null>(null);
const scroller = ref<{
scrollToPosition(position: number, smooth?: boolean): void,
$el: HTMLDivElement,
} | null>(null);
// watch(activeAccountId, () => {
// if (scroller.value) {
// scroller.value.scrollToPosition(0, false); // No smooth scrolling on address change
// }
// });

const { pulledDistance } = usePull2Refresh(scroller, () => {
refetchActiveAccount();
});

return {
scrollerBuffer,
itemSize,
Expand All @@ -367,12 +380,14 @@ export default defineComponent({
// isMainnet,
scroller,
consensus,
pulledDistance,
};
},
components: {
BtcTransactionListItem,
CircleSpinner,
AlertTriangleIcon,
Pull2RefreshIndicator,
},
});
</script>
Expand Down Expand Up @@ -412,6 +427,10 @@ export default defineComponent({
touch-action: pan-y;

@extend %custom-scrollbar;

&.smooth {
transition: transform var(--transition-time) var(--nimiq-ease);
}
}

.list-element {
Expand Down
39 changes: 39 additions & 0 deletions src/components/Pull2RefreshIndicator.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<template>
<div class="pull2refresh"
:style="{
transform: `translateY(${(pulledDistance / 4) - 5}px)`,
opacity: pulledDistance / 200,
}"
>
{{ $t('Pull to refresh') }} <GroundedArrowDownIcon />
</div>
</template>

<script lang="ts">
import { defineComponent } from '@vue/composition-api';
import GroundedArrowDownIcon from './icons/GroundedArrowDownIcon.vue';

export default defineComponent({
props: {
pulledDistance: {
type: Number,
required: true,
},
},
components: {
GroundedArrowDownIcon,
},
});
</script>

<style lang="scss" scoped>
.pull2refresh {
margin: 0 auto;
height: fit-content;
color: var(--text-60);

svg {
margin-left: 1rem;
}
}
</style>
19 changes: 19 additions & 0 deletions src/components/TransactionList.vue
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
<template>
<div class="transaction-list flex-row" ref="root">
<Pull2RefreshIndicator :pulledDistance="pulledDistance" />
<RecycleScroller
v-if="isFetchingTxHistory || transactions.length"
:items="transactions"
:item-size="itemSize"
key-field="transactionHash"
:buffer="scrollerBuffer"
ref="scroller"
:style="`transform: translateY(${pulledDistance / 2}px)`"
:class="{'smooth': !pulledDistance}"
>
<template v-if="showUnclaimedCashlinkList" v-slot:before>
<div class="unclaimed-cashlink-list">
Expand Down Expand Up @@ -102,6 +105,9 @@ import { ENV_MAIN } from '../lib/Constants';
import { isProxyData, ProxyType, ProxyTransactionDirection } from '../lib/ProxyDetection';
import { createCashlink } from '../hub';
import { useWindowSize } from '../composables/useWindowSize';
import { checkHistory, updateBalances } from '../network';
import Pull2RefreshIndicator from './Pull2RefreshIndicator.vue';
import { usePull2Refresh } from '../composables/usePull2Refresh';

function processTimestamp(timestamp: number) {
const date: Date = new Date(timestamp);
Expand Down Expand Up @@ -390,6 +396,13 @@ export default defineComponent({
context.emit('scroll');
}

const { pulledDistance } = usePull2Refresh(scroller, () => {
if (activeAddress.value) {
checkHistory(activeAddress.value);
updateBalances([activeAddress.value]);
}
});

// @scroll / @scroll.native doesn't seem to work, so using standard event system
onMounted(() => {
if (!scroller.value) return;
Expand All @@ -413,6 +426,7 @@ export default defineComponent({
unclaimedCashlinkTxs,
onCreateCashlink,
scroller,
pulledDistance,
};
},
components: {
Expand All @@ -421,6 +435,7 @@ export default defineComponent({
CrossCloseButton,
CircleSpinner,
HexagonIcon,
Pull2RefreshIndicator,
},
});
</script>
Expand Down Expand Up @@ -460,6 +475,10 @@ export default defineComponent({
touch-action: pan-y;

@extend %custom-scrollbar;

&.smooth {
transition: transform var(--transition-time) var(--nimiq-ease);
}
}

.unclaimed-cashlink-list {
Expand Down
56 changes: 56 additions & 0 deletions src/composables/usePull2Refresh.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { computed, onBeforeUnmount, onMounted, reactive, Ref } from '@vue/composition-api';

export function usePull2Refresh(scroller: Ref<{$el: HTMLDivElement} | null>, onRefresh: () => void) {
const P2R = reactive({
start: null as number | null,
current: null as number | null,
});

function startP2R(event: TouchEvent) {
if (!scroller.value || scroller.value.$el.scrollTop > 0) return;
P2R.start = event.touches[0].clientY;
(event.target as HTMLElement).addEventListener('touchmove', moveP2R);
}

function moveP2R(event: TouchEvent) {
if (!P2R.start) return;
P2R.current = event.touches[0].clientY;
const distance = P2R.current - P2R.start;
if (distance > window.innerHeight / 3) {
// console.log('REFRESH!'); // eslint-disable-line no-console
onRefresh();
cancelP2R(event);
}
}

function cancelP2R(event: TouchEvent) {
P2R.start = null;
P2R.current = null;
(event.target as HTMLElement).removeEventListener('touchmove', moveP2R);
// console.log('Pull canceled'); // eslint-disable-line no-console
}

const pulledDistance = computed(() => {
if (!P2R.start || !P2R.current) return 0;
const distance = P2R.current - P2R.start;
return Math.max(0, distance);
});

onMounted(() => {
if (!scroller.value) return;
scroller.value.$el.addEventListener('touchstart', startP2R);
scroller.value.$el.addEventListener('touchend', cancelP2R);
scroller.value.$el.addEventListener('touchcancel', cancelP2R);
});

onBeforeUnmount(() => {
if (!scroller.value) return;
scroller.value.$el.removeEventListener('touchstart', startP2R);
scroller.value.$el.removeEventListener('touchend', cancelP2R);
scroller.value.$el.removeEventListener('touchcancel', cancelP2R);
});

return {
pulledDistance,
};
}
25 changes: 19 additions & 6 deletions src/electrum.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable no-console */
import { watch, computed, ref } from '@vue/composition-api';
import { watch, computed, ref, Ref } from '@vue/composition-api';
import { ElectrumClient, ElectrumClientOptions, TransactionDetails } from '@nimiq/electrum-client';
import { SignedBtcTransaction } from '@nimiq/hub-api';
import Config from 'config';
Expand Down Expand Up @@ -167,6 +167,22 @@ export async function checkHistory(
return gap;
}

const fetchedAccounts = new Set<string>();

let txFetchTrigger: Ref<number> | undefined;
export function useTxFetchTrigger() {
txFetchTrigger = txFetchTrigger || ref(0);
return txFetchTrigger;
}

export function refetchActiveAccount() {
const { activeAccountInfo } = useAccountStore();
if (!activeAccountInfo.value) return;

fetchedAccounts.delete(activeAccountInfo.value.id);
useTxFetchTrigger().value += 1;
}

export async function launchElectrum() {
if (isLaunched) return;
isLaunched = true;
Expand All @@ -178,14 +194,11 @@ export async function launchElectrum() {
const btcTransactionsStore = useBtcTransactionsStore();
const btcAddressStore = useBtcAddressStore();

const fetchedAccounts = new Set<string>();

const txFetchTrigger = ref(0);
function invalidateTransationHistory() {
// Invalidate fetched addresses
fetchedAccounts.clear();
// Trigger watcher
txFetchTrigger.value += 1;
useTxFetchTrigger().value += 1;
}

client.addHeadChangedListener((header) => {
Expand Down Expand Up @@ -227,7 +240,7 @@ export async function launchElectrum() {
});

// Fetch transactions for active account
watch([activeAccountInfo, txFetchTrigger], async ([accountInfo]) => {
watch([activeAccountInfo, useTxFetchTrigger()], async ([accountInfo]) => {
const account = accountInfo as AccountInfo | null;
if (!account) return;

Expand Down