Skip to content

Commit

Permalink
feat(timeline): swipe to refresh (close #547)
Browse files Browse the repository at this point in the history
Signed-off-by: Varun Patil <[email protected]>
  • Loading branch information
pulsejet committed Nov 1, 2023
1 parent b1edd24 commit 910cb4a
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 7 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file.

- **Feature**: RAW files are now hidden (stacked) when another file with the same basename exists ([#537](https://github.com/pulsejet/memories/issues/537), [#152](https://github.com/pulsejet/memories/issues/152), [#419](https://github.com/pulsejet/memories/issues/419))
- **Feature**: Icon animation when playing live photos ([#898](https://github.com/pulsejet/memories/issues/898))
- **Feature**: Swipe to refresh on timeline ([#547](https://github.com/pulsejet/memories/issues/547))
- **Bugfix**: Allow switching video to direct on Safari ([#650](https://github.com/pulsejet/memories/issues/650))
- Many other [bug fixes](https://github.com/pulsejet/memories/milestone/18?closed=1)
- Android app is now open source ([see](https://github.com/pulsejet/memories/tree/master/android))
Expand Down
7 changes: 5 additions & 2 deletions src/components/ScrollerManager.vue
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export default defineComponent({
emits: {
interactend: () => true,
scroll: (event: { current: number; previous: number }) => true,
},
data: () => ({
Expand Down Expand Up @@ -223,11 +224,13 @@ export default defineComponent({
const scroll = this.recycler?.$el?.scrollTop || 0;
// Emit scroll event
utils.bus.emit('memories.recycler.scroll', {
const event = {
current: scroll,
previous: this.lastKnownRecyclerScroll,
dynTopMatterVisible: scroll < this.dynTopMatterHeight,
});
};
utils.bus.emit('memories.recycler.scroll', event);
this.$emit('scroll', event);
this.lastKnownRecyclerScroll = scroll;
// Get cursor px position
Expand Down
97 changes: 97 additions & 0 deletions src/components/SwipeRefresh.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<template>
<div @touchstart.passive="touchstart" @touchmove.passive="touchmove" @touchend.passive="touchend">
<div v-if="on && progress" class="swipe-progress" :style="{ background: gradient }"></div>
<slot></slot>
</div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
const SWIPE_PX = 250;
export default defineComponent({
name: 'SwipeRefresh',
props: {
allowSwipe: {
type: Boolean,
default: true,
},
},
emits: {
refresh: () => true,
},
data: () => ({
on: false,
start: 0,
end: 0,
updateFrame: 0,
progress: 0,
}),
computed: {
gradient() {
const start = 50 - this.progress / 2;
const end = 50 + this.progress / 2;
const out = 'transparent';
const progress = 'var(--color-primary)';
return `linear-gradient(to right, ${out} ${start}%, ${progress} ${start}%, ${progress} ${end}%, ${out} ${end}%)`;
},
},
methods: {
/** Start gesture on container (passive) */
touchstart(event: TouchEvent) {
if (!this.allowSwipe) return;
const touch = event.touches[0];
this.end = this.start = touch.clientY;
this.progress = 0;
this.on = true;
},
/** Execute gesture on container (passive) */
touchmove(event: TouchEvent) {
if (!this.allowSwipe) return;
const touch = event.touches[0];
this.end = touch.clientY;
// Update progress only once per frame
this.updateFrame ||= requestAnimationFrame(() => {
this.updateFrame = 0;
// Compute percentage of swipe
const delta = (this.end - this.start) / SWIPE_PX;
this.progress = Math.min(Math.max(0, delta * 100), 100);
// Execute action on threshold
if (this.progress >= 100) {
this.on = false;
this.$emit('refresh');
}
});
},
/** End gesture on container (passive) */
touchend(event: TouchEvent) {
this.on = false;
},
},
});
</script>

<style lang="scss" scoped>
.swipe-progress {
position: absolute;
z-index: 100;
top: 0;
width: 100%;
height: 3px;
html.native & {
top: 2px;
}
}
</style>
20 changes: 15 additions & 5 deletions src/components/Timeline.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<div class="container no-user-select" ref="container">
<SwipeRefresh class="container no-user-select" ref="container" :allowSwipe="allowSwipe" @refresh="softRefresh">
<!-- Loading indicator -->
<XLoadingIcon class="loading-icon centered" v-if="loading" />

Expand Down Expand Up @@ -74,7 +74,8 @@
:fullHeight="scrollerHeight"
:recycler="refs.recycler"
:recyclerBefore="refs.recyclerBefore"
@interactend="loadScrollView()"
@interactend="loadScrollView"
@scroll="currentScroll = $event.current"
/>

<SelectionManager
Expand All @@ -85,7 +86,7 @@
:recycler="refs.recycler?.$el"
@updateLoading="updateLoading"
/>
</div>
</SwipeRefresh>
</template>

<script lang="ts">
Expand All @@ -103,6 +104,7 @@ import Photo from '@components/frame/Photo.vue';
import ScrollerManager from '@components/ScrollerManager.vue';
import SelectionManager from '@components/SelectionManager.vue';
import Viewer from '@components/viewer/Viewer.vue';
import SwipeRefresh from './SwipeRefresh.vue';
import EmptyContent from '@components/top-matter/EmptyContent.vue';
import TopMatter from '@components/top-matter/TopMatter.vue';
Expand Down Expand Up @@ -133,6 +135,7 @@ export default defineComponent({
SelectionManager,
ScrollerManager,
Viewer,
SwipeRefresh,
},
mixins: [UserConfig],
Expand Down Expand Up @@ -166,6 +169,8 @@ export default defineComponent({
currentStart: 0,
/** Current end index */
currentEnd: 0,
/** Current physical scroll position */
currentScroll: 0,
/** Resizing timer */
resizeTimer: null as number | null,
/** Height of the scroller */
Expand Down Expand Up @@ -219,7 +224,7 @@ export default defineComponent({
computed: {
refs() {
return this.$refs as {
container?: HTMLDivElement;
container?: InstanceType<typeof SwipeRefresh>;
topmatter?: InstanceType<typeof TopMatter>;
dtm?: InstanceType<typeof DynamicTopMatter>;
recycler?: VueRecyclerType;
Expand Down Expand Up @@ -251,6 +256,11 @@ export default defineComponent({
showEmpty(): boolean {
return !this.loading && this.empty;
},
/** Whether to allow swipe refresh */
allowSwipe(): boolean {
return !this.loading && this.currentScroll === 0;
},
},
methods: {
Expand Down Expand Up @@ -384,7 +394,7 @@ export default defineComponent({
/** Recompute static sizes of containers */
recomputeSizes() {
// Size of outer container
const e = this.refs.container!;
const e = this.refs.container!.$el;
const height = e.clientHeight;
const width = e.clientWidth;
this.containerSize = [width, height];
Expand Down

0 comments on commit 910cb4a

Please sign in to comment.