<commit_before>package com.nextfaze.devfun.overlay

import android.animation.ValueAnimator
import android.app.Activity
import android.app.Application
import android.graphics.PixelFormat
import android.graphics.PointF
import android.graphics.Rect
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.support.annotation.LayoutRes
import android.support.v4.math.MathUtils.clamp
import android.view.*
import android.view.animation.OvershootInterpolator
import com.nextfaze.devfun.internal.android.*
import com.nextfaze.devfun.internal.log.*
import com.nextfaze.devfun.internal.pref.*
import java.lang.Math.abs

private const val MIN_ANIMATION_MILLIS = 250L
private const val MAX_ANIMATION_MILLIS = 500L

typealias ClickListener = (View) -> Unit
typealias OverlayReason = () -> CharSequence
typealias VisibilityPredicate = (Activity) -> Boolean

enum class Dock { TOP, BOTTOM, LEFT, RIGHT, TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT }

class OverlayWindow(
    private val application: Application,
    private val overlays: OverlayManager,
    @LayoutRes layoutId: Int,
    internal val prefsName: String,
    internal val reason: OverlayReason,
    private val onClick: ClickListener? = null,
    private val onLongClick: ClickListener? = null,
    internal val visibilityPredicate: VisibilityPredicate? = null,
    initialDock: Dock = Dock.TOP_LEFT,
    initialDelta: Float = 0f,
    snapToEdge: Boolean = true,
    initialLeft: Float = 0f,
    initialTop: Float = 0f
) {
    private val log = logger()
    private val windowManager = application.windowManager
    private val handler = Handler(Looper.getMainLooper())

    private val preferences = KSharedPreferences.named(application, prefsName)
    private var dock by preferences["dock", initialDock]
    private var delta by preferences["delta", initialDelta]
    private var left by preferences["left", initialLeft]
    private var top by preferences["top", initialTop]
    var snapToEdge: Boolean by preferences["snapToEdge", snapToEdge, { _, _ -> updatePosition(false) }]
    var enabled by preferences["enabled", true, { _, _ -> overlays.updateVisibilities() }]

    var viewInset = Rect()

    val canDrawOverlays get() = overlays.canDrawOverlays

    private val isAdded get() = view.parent != null
    private val params = createOverlayWindowParams()
    private val overlayBounds = Rect()

    fun updateOverlayBounds(bounds: Rect, postUpdate: Boolean = true) {
        overlayBounds.set(bounds)
        loadSavedPosition(postUpdate)
    }

    val view: View by lazy {
        View.inflate(application, layoutId, null).apply {
            setOnTouchListener(object : View.OnTouchListener {
                private val tapTimeout = ViewConfiguration.getTapTimeout().toLong()
                private val longPressTimeout = ViewConfiguration.getLongPressTimeout().toLong()
                private val downTimeout = if (onLongClick == null) tapTimeout else longPressTimeout

                private val onLongClickRunnable = Runnable {
                    if (onLongClick != null) {
                        longClickPerformed = true
                        performLongClick()
                    }
                }
                private var postedOnLongClickRunnable: Runnable? = null

                private fun postOnLongClick() {
                    if (onLongClick != null) {
                        postedOnLongClickRunnable = onLongClickRunnable
                        handler.postDelayed(postedOnLongClickRunnable, longPressTimeout)
                    }
                }

                private fun removeOnLongClick() {
                    if (onLongClick != null && postedOnLongClickRunnable != null) {
                        handler.removeCallbacks(postedOnLongClickRunnable)
                        postedOnLongClickRunnable = null
                    }
                }

                private var currentPosition = PointF(0f, 0f)
                private var initialPosition = PointF(0f, 0f)
                private var initialWindowPosition = PointF(0f, 0f)

                private var actionDownTime = 0L
                private var moved = false
                private var longClickPerformed = false

                override fun onTouch(v: View, event: MotionEvent): Boolean {
                    return when (event.action) {
                        MotionEvent.ACTION_DOWN -> {
                            postOnLongClick()
                            actionDownTime = System.currentTimeMillis()

                            initialPosition.apply {
                                x = event.rawX
                                y = event.rawY
                            }

                            currentPosition.set(initialPosition)

                            initialWindowPosition.apply {
                                x = params.x.toFloat()
                                y = params.y.toFloat()
                            }

                            moved = false

                            true
                        }
                        MotionEvent.ACTION_MOVE -> {
                            if (longClickPerformed) return true

                            val rawX = event.rawX
                            val rawY = event.rawY
                            val dx = abs(rawX - currentPosition.x)
                            val dy = abs(rawY - currentPosition.y)
                            val slop = ViewConfiguration.get(application).scaledTouchSlop
                            val sinceDown = System.currentTimeMillis() - actionDownTime

                            if (moved || dx >= slop || dy >= slop || sinceDown > downTimeout) {
                                removeOnLongClick()
                                moved = true
                                params.x = (rawX - initialPosition.x + initialWindowPosition.x).toInt()
                                params.y = (rawY - initialPosition.y + initialWindowPosition.y).toInt()

                                currentPosition.apply {
                                    x = rawX
                                    y = rawY
                                }

                                windowManager.updateViewLayout(v, params)
                            }

                            true
                        }
                        MotionEvent.ACTION_UP -> {
                            if (!moved) {
                                val sinceDown = System.currentTimeMillis() - actionDownTime
                                if (sinceDown < longPressTimeout) {
                                    removeOnLongClick()
                                    v.performClick()
                                }
                            } else {
                                updatePosition(true)
                            }

                            moved = false
                            actionDownTime = 0
                            longClickPerformed = false
                            true
                        }
                        else -> false
                    }
                }
            })

            setOnClickListener { onClick?.invoke(it) }
            setOnLongClickListener { onLongClick?.invoke(it); true }

            addOnLayoutChangeListener { _, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom ->
                val prevWidth = oldRight - oldLeft
                val newWidth = right - left
                val prevHeight = oldBottom - oldTop
                val newHeight = bottom - top
                if (prevWidth != newWidth || prevHeight != newHeight) {
                    updatePosition(false)
                }
            }
        }
    }
<commit_msg>Stop overlay window jumping back when moving if size changed<commit_after>package com.nextfaze.devfun.overlay

import android.animation.ValueAnimator
import android.app.Activity
import android.app.Application
import android.graphics.PixelFormat
import android.graphics.PointF
import android.graphics.Rect
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.support.annotation.LayoutRes
import android.support.v4.math.MathUtils.clamp
import android.view.*
import android.view.animation.OvershootInterpolator
import com.nextfaze.devfun.internal.android.*
import com.nextfaze.devfun.internal.log.*
import com.nextfaze.devfun.internal.pref.*
import java.lang.Math.abs

private const val MIN_ANIMATION_MILLIS = 250L
private const val MAX_ANIMATION_MILLIS = 500L

typealias ClickListener = (View) -> Unit
typealias OverlayReason = () -> CharSequence
typealias VisibilityPredicate = (Activity) -> Boolean

enum class Dock { TOP, BOTTOM, LEFT, RIGHT, TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT }

class OverlayWindow(
    private val application: Application,
    private val overlays: OverlayManager,
    @LayoutRes layoutId: Int,
    internal val prefsName: String,
    internal val reason: OverlayReason,
    private val onClick: ClickListener? = null,
    private val onLongClick: ClickListener? = null,
    internal val visibilityPredicate: VisibilityPredicate? = null,
    initialDock: Dock = Dock.TOP_LEFT,
    initialDelta: Float = 0f,
    snapToEdge: Boolean = true,
    initialLeft: Float = 0f,
    initialTop: Float = 0f
) {
    private val log = logger()
    private val windowManager = application.windowManager
    private val handler = Handler(Looper.getMainLooper())

    private val preferences = KSharedPreferences.named(application, prefsName)
    private var dock by preferences["dock", initialDock]
    private var delta by preferences["delta", initialDelta]
    private var left by preferences["left", initialLeft]
    private var top by preferences["top", initialTop]
    var snapToEdge: Boolean by preferences["snapToEdge", snapToEdge, { _, _ -> updatePosition(false) }]
    var enabled by preferences["enabled", true, { _, _ -> overlays.updateVisibilities() }]

    var viewInset = Rect()

    val canDrawOverlays get() = overlays.canDrawOverlays

    private val isAdded get() = view.parent != null
    private val params = createOverlayWindowParams()
    private val overlayBounds = Rect()
    private var moving = false

    fun updateOverlayBounds(bounds: Rect, postUpdate: Boolean = true) {
        overlayBounds.set(bounds)
        loadSavedPosition(postUpdate)
    }

    val view: View by lazy {
        View.inflate(application, layoutId, null).apply {
            setOnTouchListener(object : View.OnTouchListener {
                private val tapTimeout = ViewConfiguration.getTapTimeout().toLong()
                private val longPressTimeout = ViewConfiguration.getLongPressTimeout().toLong()
                private val downTimeout = if (onLongClick == null) tapTimeout else longPressTimeout

                private val onLongClickRunnable = Runnable {
                    if (onLongClick != null) {
                        longClickPerformed = true
                        performLongClick()
                    }
                }
                private var postedOnLongClickRunnable: Runnable? = null

                private fun postOnLongClick() {
                    if (onLongClick != null) {
                        postedOnLongClickRunnable = onLongClickRunnable
                        handler.postDelayed(postedOnLongClickRunnable, longPressTimeout)
                    }
                }

                private fun removeOnLongClick() {
                    if (onLongClick != null && postedOnLongClickRunnable != null) {
                        handler.removeCallbacks(postedOnLongClickRunnable)
                        postedOnLongClickRunnable = null
                    }
                }

                private var currentPosition = PointF(0f, 0f)
                private var initialPosition = PointF(0f, 0f)
                private var initialWindowPosition = PointF(0f, 0f)

                private var actionDownTime = 0L
                private var longClickPerformed = false

                override fun onTouch(v: View, event: MotionEvent): Boolean {
                    return when (event.action) {
                        MotionEvent.ACTION_DOWN -> {
                            postOnLongClick()
                            actionDownTime = System.currentTimeMillis()

                            initialPosition.apply {
                                x = event.rawX
                                y = event.rawY
                            }

                            currentPosition.set(initialPosition)

                            initialWindowPosition.apply {
                                x = params.x.toFloat()
                                y = params.y.toFloat()
                            }

                            moving = false

                            true
                        }
                        MotionEvent.ACTION_MOVE -> {
                            if (longClickPerformed) return true

                            val rawX = event.rawX
                            val rawY = event.rawY
                            val dx = abs(rawX - currentPosition.x)
                            val dy = abs(rawY - currentPosition.y)
                            val slop = ViewConfiguration.get(application).scaledTouchSlop
                            val sinceDown = System.currentTimeMillis() - actionDownTime

                            if (moving || dx >= slop || dy >= slop || sinceDown > downTimeout) {
                                removeOnLongClick()
                                moving = true
                                params.x = (rawX - initialPosition.x + initialWindowPosition.x).toInt()
                                params.y = (rawY - initialPosition.y + initialWindowPosition.y).toInt()

                                currentPosition.apply {
                                    x = rawX
                                    y = rawY
                                }

                                windowManager.updateViewLayout(v, params)
                            }

                            true
                        }
                        MotionEvent.ACTION_UP -> {
                            if (!moving) {
                                val sinceDown = System.currentTimeMillis() - actionDownTime
                                if (sinceDown < longPressTimeout) {
                                    removeOnLongClick()
                                    v.performClick()
                                }
                            } else {
                                updatePosition(true)
                            }

                            moving = false
                            actionDownTime = 0
                            longClickPerformed = false
                            true
                        }
                        else -> false
                    }
                }
            })

            setOnClickListener { onClick?.invoke(it) }
            setOnLongClickListener { onLongClick?.invoke(it); true }

            addOnLayoutChangeListener { _, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom ->
                if (moving) return@addOnLayoutChangeListener

                val prevWidth = oldRight - oldLeft
                val newWidth = right - left
                val prevHeight = oldBottom - oldTop
                val newHeight = bottom - top
                if (prevWidth != newWidth || prevHeight != newHeight) {
                    updatePosition(false)
                }
            }
        }
    }

{'fraction_non_alphanumeric': 0.06775865378463114, 'fraction_numerical': 0.0023034103269562994, 'mean_word_length': 1.4247595408004965, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 29, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 20, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2496535', 'n_tokens_mistral': 3760, 'n_tokens_neox': 3503, 'n_words': 876}