aboutsummaryrefslogtreecommitdiff
path: root/app/src/main/kotlin/com/pitchedapps/frost/views/SwipeRefreshLayout.kt
blob: 2154ce2da20d1f97d8eb4988a2d86244d37a625b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
package com.pitchedapps.frost.views

import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.view.ViewConfiguration
import android.webkit.WebView
import android.widget.ListView
import androidx.core.widget.ListViewCompat
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnChildScrollUpCallback
import com.pitchedapps.frost.utils.L

/**
 * Variant that forbids refreshing if child layout is not at the top
 * Inspired by https://github.com/slapperwan/gh4a/blob/master/app/src/main/java/com/gh4a/widget/SwipeRefreshLayout.java
 *
 */
class SwipeRefreshLayout @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
    SwipeRefreshLayout(context, attrs) {

    private var preventRefresh: Boolean = false
    private var downY: Float = -1f
    private val touchSlop = ViewConfiguration.get(context).scaledTouchSlop

    private var nestedCanChildScrollUp: OnChildScrollUpCallback? = null

    /**
     * Copy of [canChildScrollUp], with additional support if necessary
     */
    private val canChildScrollUp = OnChildScrollUpCallback { parent, child ->
        nestedCanChildScrollUp?.canChildScrollUp(parent, child) ?: when (child) {
            is WebView -> child.canScrollVertically(-1).apply {
                L.d { "Webview can scroll up $this" }
            }
            is ListView -> ListViewCompat.canScrollList(child, -1)
            // Supports webviews as well
            else -> child?.canScrollVertically(-1) ?: false
        }
    }

    init {
        setOnChildScrollUpCallback(canChildScrollUp)
    }

    override fun setOnChildScrollUpCallback(callback: OnChildScrollUpCallback?) {
        this.nestedCanChildScrollUp = callback
    }

    override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
        if (ev.action != MotionEvent.ACTION_DOWN && preventRefresh) {
            return false
        }
        when (ev.action) {
            MotionEvent.ACTION_DOWN -> {
                downY = ev.y
                preventRefresh = canChildScrollUp()
            }
            MotionEvent.ACTION_MOVE -> {
                if (downY - ev.y > touchSlop) {
                    preventRefresh = true
                    return false
                }
            }
        }
        return super.onInterceptTouchEvent(ev)
    }

    override fun onNestedScroll(target: View, dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int, dyUnconsumed: Int) {
        if (preventRefresh) {
            /*
             * Ignoring offsetInWindow since
             * 1. It doesn't seem to matter in the typical use case
             * 2. It isn't being transferred to the underlying array used by the super class
             */
            dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, null)
        } else {
            super.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed)
        }
    }

    /**
     * Alias for adding on refresh listener
     */
    interface OnRefreshListener : SwipeRefreshLayout.OnRefreshListener
}