Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 28ee0a69 authored by cketti's avatar cketti
Browse files

Replace ShowcaseView with SimpleHighlightView

It's ShowcaseView stripped of everything we don't need.
parent 6a6e9d5f
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -29,12 +29,13 @@ dependencies {
    implementation "androidx.navigation:navigation-ui-ktx:${versions.androidxNavigation}"
    implementation "androidx.constraintlayout:constraintlayout:${versions.androidxConstraintLayout}"
    implementation "androidx.cardview:cardview:${versions.androidxCardView}"
    implementation "androidx.localbroadcastmanager:localbroadcastmanager:1.0.0"
    implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
    implementation "com.google.android.material:material:${versions.materialComponents}"
    implementation "de.cketti.library.changelog:ckchangelog:1.2.1"
    implementation "com.github.bumptech.glide:glide:3.6.1"
    implementation "com.splitwise:tokenautocomplete:2.0.7"
    implementation "de.cketti.safecontentresolver:safe-content-resolver-v21:0.9.0"
    implementation "com.github.amlcurran.showcaseview:library:5.4.1"
    implementation "com.xwray:groupie:2.8.0"
    implementation "com.xwray:groupie-kotlin-android-extensions:2.8.0"
    implementation 'com.mikepenz:materialdrawer:7.0.0'
+230 −0
Original line number Diff line number Diff line
/*
 * Copyright 2020 The K-9 Dog Walkers
 *
 * Based on ShowcaseView (https://github.com/amlcurran/ShowcaseView)
 * Copyright 2014 Alex Curran
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.fsck.k9.ui.compose

import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ObjectAnimator
import android.app.Activity
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Point
import android.graphics.PorterDuff
import android.graphics.PorterDuffXfermode
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import com.fsck.k9.ui.R

/**
 * A view which allows you to highlight a view in your Activity.
 */
class SimpleHighlightView private constructor(context: Context, style: Int) : FrameLayout(context) {
    private val backgroundColor: Int

    private val fadeInMillis = resources.getInteger(android.R.integer.config_mediumAnimTime).toLong()
    private val fadeOutMillis = resources.getInteger(android.R.integer.config_mediumAnimTime).toLong()

    private val radius: Float = resources.getDimension(R.dimen.highlight_radius)

    private val basicPaint = Paint()
    private val eraserPaint = Paint().apply {
        color = 0xFFFFFF
        alpha = 0
        xfermode = PorterDuffXfermode(PorterDuff.Mode.MULTIPLY)
        isAntiAlias = true
    }

    private val positionInWindow = IntArray(2)
    private var parent: ViewGroup? = null
    private var highlightX = -1
    private var highlightY = -1
    private var bitmapBuffer: Bitmap? = null

    init {
        val styled = getContext().obtainStyledAttributes(style, R.styleable.SimpleHighlightView)
        backgroundColor = styled.getColor(
            R.styleable.SimpleHighlightView_highlightBackgroundColor,
            Color.argb(128, 80, 80, 80)
        )
        styled.recycle()
    }

    fun remove() {
        fadeOutHighlightAndRemoveFromParent()
    }

    override fun dispatchDraw(canvas: Canvas) {
        val highlightX = this.highlightX
        val highlightY = this.highlightY
        val bitmapBuffer = this.bitmapBuffer

        if (highlightX < 0 || highlightY < 0 || bitmapBuffer == null) {
            super.dispatchDraw(canvas)
            return
        }

        // Draw background color
        erase(bitmapBuffer)

        drawHighlightCircle(bitmapBuffer, highlightX.toFloat(), highlightY.toFloat())
        drawToCanvas(canvas, bitmapBuffer)

        super.dispatchDraw(canvas)
    }

    private fun erase(bitmapBuffer: Bitmap) {
        bitmapBuffer.eraseColor(backgroundColor)
    }

    private fun drawHighlightCircle(buffer: Bitmap, x: Float, y: Float) {
        Canvas(buffer).apply {
            drawCircle(x, y, radius, eraserPaint)
        }
    }

    private fun drawToCanvas(canvas: Canvas, bitmapBuffer: Bitmap) {
        canvas.drawBitmap(bitmapBuffer, 0f, 0f, basicPaint)
    }

    private fun setHighlightPosition(x: Int, y: Int) {
        getLocationInWindow(positionInWindow)
        highlightX = x - positionInWindow[0]
        highlightY = y - positionInWindow[1]
        invalidate()
    }

    private fun setParent(parent: ViewGroup) {
        this.parent = parent
    }

    private fun setTarget(targetView: View) {
        postDelayed({
            if (canUpdateBitmap()) {
                updateBitmap()
            }

            val point = targetView.getHighlightPoint()
            setHighlightPosition(point.x, point.y)
        }, 100)
    }

    private fun canUpdateBitmap(): Boolean {
        return measuredHeight > 0 && measuredWidth > 0
    }

    private fun updateBitmap() {
        bitmapBuffer.let { bitmapBuffer ->
            if (bitmapBuffer == null || bitmapBuffer.haveBoundsChanged()) {
                bitmapBuffer?.recycle()
                this.bitmapBuffer = Bitmap.createBitmap(measuredWidth, measuredHeight, Bitmap.Config.ARGB_8888)
            }
        }
    }

    private fun Bitmap.haveBoundsChanged(): Boolean {
        return measuredWidth != width || measuredHeight != height
    }

    private fun View.getHighlightPoint(): Point {
        val location = IntArray(2)
        getLocationInWindow(location)
        val x = location[0] + width / 2
        val y = location[1] + height / 2
        return Point(x, y)
    }

    private fun clearBitmap() {
        bitmapBuffer?.let { bitmapBuffer ->
            if (!bitmapBuffer.isRecycled) {
                bitmapBuffer.recycle()
                this.bitmapBuffer = null
            }
        }
    }

    private fun show() {
        if (canUpdateBitmap()) {
            updateBitmap()
        }

        fadeInHighlight()
    }

    private fun fadeInHighlight() {
        ObjectAnimator.ofFloat(this, ALPHA, INVISIBLE, VISIBLE)
            .setDuration(fadeInMillis)
            .onAnimationStart { visibility = View.VISIBLE }
            .start()
    }

    private fun fadeOutHighlightAndRemoveFromParent() {
        ObjectAnimator.ofFloat(this, ALPHA, INVISIBLE)
            .setDuration(fadeOutMillis)
            .onAnimationEnd {
                visibility = View.GONE
                clearBitmap()
                parent?.removeView(this@SimpleHighlightView)
            }
            .start()
    }

    private inline fun ObjectAnimator.onAnimationStart(crossinline block: () -> Unit): ObjectAnimator {
        addListener(object : AnimatorListenerAdapter() {
            override fun onAnimationStart(animation: Animator) {
                block()
            }
        })
        return this
    }

    private inline fun ObjectAnimator.onAnimationEnd(crossinline block: () -> Unit): ObjectAnimator {
        addListener(object : AnimatorListenerAdapter() {
            override fun onAnimationEnd(animation: Animator) {
                block()
            }
        })
        return this
    }

    companion object {
        private const val ALPHA = "alpha"
        private const val INVISIBLE = 0f
        private const val VISIBLE = 1f

        @JvmStatic
        fun createAndInsert(activity: Activity, targetView: View, style: Int): SimpleHighlightView {
            val highlightView = SimpleHighlightView(activity, style)
            highlightView.setTarget(targetView)

            val parent = activity.findViewById<ViewGroup>(android.R.id.content)
            highlightView.setParent(parent)

            val parentIndex = parent.childCount
            parent.addView(highlightView, parentIndex)

            highlightView.show()

            return highlightView
        }
    }
}
+11 −19
Original line number Diff line number Diff line
@@ -10,9 +10,7 @@ import android.view.View;
import android.view.inputmethod.InputMethodManager;

import com.fsck.k9.ui.R;
import com.github.amlcurran.showcaseview.ShowcaseView;
import com.github.amlcurran.showcaseview.ShowcaseView.Builder;
import com.github.amlcurran.showcaseview.targets.ViewTarget;
import com.fsck.k9.ui.compose.SimpleHighlightView;


public class HighlightDialogFragment extends DialogFragment {
@@ -20,7 +18,7 @@ public class HighlightDialogFragment extends DialogFragment {
    public static final float BACKGROUND_DIM_AMOUNT = 0.25f;


    private ShowcaseView showcaseView;
    private SimpleHighlightView highlightView;


    protected void highlightViewInBackground() {
@@ -33,20 +31,14 @@ public class HighlightDialogFragment extends DialogFragment {
            throw new IllegalStateException("fragment must be attached to set highlight!");
        }

        boolean alreadyShowing = showcaseView != null && showcaseView.isShowing();
        boolean alreadyShowing = highlightView != null;
        if (alreadyShowing) {
            return;
        }

        int highlightedView = getArguments().getInt(ARG_HIGHLIGHT_VIEW);
        showcaseView = new Builder(activity)
                .setTarget(new ViewTarget(highlightedView, activity))
                .hideOnTouchOutside()
                .blockAllTouches()
                .withMaterialShowcase()
                .setStyle(R.style.ShowcaseTheme)
                .build();
        showcaseView.hideButton();
        int highlightedViewId = getArguments().getInt(ARG_HIGHLIGHT_VIEW);
        View highlightedView = activity.findViewById(highlightedViewId);
        highlightView = SimpleHighlightView.createAndInsert(activity, highlightedView, R.style.MessageComposeHighlight);
    }

    @Override
@@ -62,7 +54,7 @@ public class HighlightDialogFragment extends DialogFragment {
    public void onDismiss(DialogInterface dialog) {
        super.onDismiss(dialog);

        hideShowcaseView();
        hideHighlightView();
    }

    private void setDialogBackgroundDim() {
@@ -89,10 +81,10 @@ public class HighlightDialogFragment extends DialogFragment {
        inputManager.hideSoftInputFromWindow(v.getWindowToken(), 0);
    }

    private void hideShowcaseView() {
        if (showcaseView != null && showcaseView.isShowing()) {
            showcaseView.hide();
    private void hideHighlightView() {
        if (highlightView != null) {
            highlightView.remove();
            highlightView = null;
        }
        showcaseView = null;
    }
}
+4 −0
Original line number Diff line number Diff line
@@ -128,4 +128,8 @@
        <attr name="summaryOff" format="string" />
    </declare-styleable>

    <declare-styleable name="SimpleHighlightView">
        <attr name="highlightBackgroundColor" format="color|reference" />
    </declare-styleable>

</resources>
+1 −0
Original line number Diff line number Diff line
@@ -2,4 +2,5 @@
<resources>
    <dimen name="button_minWidth">100sp</dimen>
    <dimen name="widget_padding">8dp</dimen>
    <dimen name="highlight_radius">48dp</dimen>
</resources>
Loading