From c707d42b311f96cbabc1971f98598c8b8922ba16 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Sat, 5 Aug 2017 14:49:47 -0700 Subject: Swipe (#24) * Test emulator * Update readme * Update fastadapter and about listing * Make faq parser asynchronous * Modularize about panels * Add basis for faq * Test and finalize the faq panel * Update readme * Update changelog * Remove emulator for now * Update sample * Change back to manual versioning to avoid suggestion errors * Add awesome-kt banner * Fix faq background color * Fix merge conflicts 2 * Add waffle badge * Update readme * Fix lint * Create FileUtils and NotificationUtils * Remove frost hardcode * Fix simple date * Update swipe to use weak references * Initializing test dependencies * Update to gradle 4.1 * Fix lint warnings * Drop back down and fix errors * Finalize swipe with example * Finalize weak reference and ordering * Update test code * Make loggers inline --- .../main/kotlin/ca/allanwang/kau/kotlin/Streams.kt | 17 ++++++++ .../ca/allanwang/kau/logging/TimberLogger.kt | 16 ++++--- .../ca/allanwang/kau/swipe/RelativeSlider.kt | 7 +-- .../ca/allanwang/kau/swipe/SwipeBackHelper.kt | 37 +++++++++------- .../ca/allanwang/kau/swipe/SwipeBackLayout.kt | 50 ++++++++++++++-------- .../kotlin/ca/allanwang/kau/swipe/SwipeBackPage.kt | 18 +++++--- .../ca/allanwang/kau/swipe/ViewDragHelper.java | 2 +- .../main/kotlin/ca/allanwang/kau/utils/Const.kt | 10 +++-- .../kotlin/ca/allanwang/kau/utils/FileUtils.kt | 33 ++++++++++++++ .../ca/allanwang/kau/utils/NotificationUtils.kt | 11 +++++ .../kotlin/ca/allanwang/kau/utils/PackageUtils.kt | 1 - .../ca/allanwang/kau/utils/TransitionUtils.kt | 2 +- 12 files changed, 145 insertions(+), 59 deletions(-) create mode 100644 core/src/main/kotlin/ca/allanwang/kau/kotlin/Streams.kt create mode 100644 core/src/main/kotlin/ca/allanwang/kau/utils/FileUtils.kt create mode 100644 core/src/main/kotlin/ca/allanwang/kau/utils/NotificationUtils.kt (limited to 'core/src/main/kotlin/ca/allanwang') diff --git a/core/src/main/kotlin/ca/allanwang/kau/kotlin/Streams.kt b/core/src/main/kotlin/ca/allanwang/kau/kotlin/Streams.kt new file mode 100644 index 0000000..5dc44a8 --- /dev/null +++ b/core/src/main/kotlin/ca/allanwang/kau/kotlin/Streams.kt @@ -0,0 +1,17 @@ +package ca.allanwang.kau.kotlin + +/** + * Created by Allan Wang on 2017-08-05. + */ + +/** + * Replica of [Vector.removeIf] in Java + * Since we don't have access to the internals of our extended class, + * We will simply iterate and remove when the filter returns {@code false} + */ +@Synchronized inline fun > C.kauRemoveIf(filter: (item: T) -> Boolean): C { + val iter = iterator() + while (iter.hasNext()) + if (filter(iter.next())) iter.remove() + return this +} \ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/logging/TimberLogger.kt b/core/src/main/kotlin/ca/allanwang/kau/logging/TimberLogger.kt index e0f6cae..4c6d655 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/logging/TimberLogger.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/logging/TimberLogger.kt @@ -1,3 +1,5 @@ +@file:Suppress("NOTHING_TO_INLINE") + package ca.allanwang.kau.logging import timber.log.Timber @@ -9,11 +11,11 @@ import timber.log.Timber * Timber extension that will embed the tag as part of the message for each log item */ open class TimberLogger(tag: String) { - internal val TAG = "$tag: %s" - fun e(s: String) = Timber.e(TAG, s) - fun e(t: Throwable?, s: String = "error") = if (t == null) e(s) else Timber.e(t, TAG, s) - fun d(s: String) = Timber.d(TAG, s) - fun i(s: String) = Timber.i(TAG, s) - fun v(s: String) = Timber.v(TAG, s) - fun eThrow(s: String) = e(Throwable(s)) + val TAG = "$tag: %s" + inline fun e(s: String) = Timber.e(TAG, s) + inline fun e(t: Throwable?, s: String = "error") = if (t == null) e(s) else Timber.e(t, TAG, s) + inline fun d(s: String) = Timber.d(TAG, s) + inline fun i(s: String) = Timber.i(TAG, s) + inline fun v(s: String) = Timber.v(TAG, s) + inline fun eThrow(s: String) = e(Throwable(s)) } \ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/swipe/RelativeSlider.kt b/core/src/main/kotlin/ca/allanwang/kau/swipe/RelativeSlider.kt index 2ffb2ef..f14f5cf 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/swipe/RelativeSlider.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/swipe/RelativeSlider.kt @@ -1,7 +1,5 @@ package ca.allanwang.kau.swipe -import ca.allanwang.kau.kotlin.nonReadable - /** * Created by Mr.Jude on 2015/8/26. * @@ -9,13 +7,12 @@ import ca.allanwang.kau.kotlin.nonReadable * * Helper class to give the previous activity an offset as the main page is pulled */ -class RelativeSlider(var curPage: SwipeBackPage) : SwipeListener { +internal class RelativeSlider(var curPage: SwipeBackPage) : SwipeListener { var offset = 0f var enabled: Boolean - @Deprecated(level = DeprecationLevel.ERROR, message = "Cannot use enabled as getter") - get() = nonReadable() + get() = curPage.hasListener(this) set(value) { if (value) curPage.addListener(this) else curPage.removeListener(this) diff --git a/core/src/main/kotlin/ca/allanwang/kau/swipe/SwipeBackHelper.kt b/core/src/main/kotlin/ca/allanwang/kau/swipe/SwipeBackHelper.kt index 503f1fc..0859ac5 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/swipe/SwipeBackHelper.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/swipe/SwipeBackHelper.kt @@ -2,27 +2,27 @@ package ca.allanwang.kau.swipe import android.app.Activity import ca.allanwang.kau.R +import ca.allanwang.kau.kotlin.kauRemoveIf +import ca.allanwang.kau.logging.KL import ca.allanwang.kau.swipe.SwipeBackHelper.onDestroy import java.util.* -class SwipeBackException(message: String = "You Should call kauSwipeOnCreate() first") : RuntimeException(message) +internal class SwipeBackException(message: String = "You Should call kauSwipeOnCreate() first") : RuntimeException(message) /** * Singleton to hold our swipe stack * All activity pages held with strong references, so it is crucial to call * [onDestroy] whenever an activity should be disposed */ -object SwipeBackHelper { +internal object SwipeBackHelper { private val pageStack = Stack() - private operator fun get(activity: Activity): SwipeBackPage - = pageStack.firstOrNull { it.activity === activity } ?: throw SwipeBackException() - - fun getCurrentPage(activity: Activity): SwipeBackPage = this[activity] + private operator fun get(activity: Activity): SwipeBackPage? + = pageStack.firstOrNull { it.activityRef.get() === activity } fun onCreate(activity: Activity, builder: SwipeBackContract.() -> Unit = {}) { - val page = pageStack.firstOrNull { it.activity === activity } ?: pageStack.push(SwipeBackPage(activity).apply { builder() }) + val page = this[activity] ?: pageStack.push(SwipeBackPage(activity).apply { builder() }) val startAnimation: Int = when (page.edgeFlag) { SWIPE_EDGE_LEFT -> R.anim.kau_slide_in_right SWIPE_EDGE_RIGHT -> R.anim.kau_slide_in_left @@ -30,23 +30,28 @@ object SwipeBackHelper { else -> R.anim.kau_slide_in_top } activity.overridePendingTransition(startAnimation, 0) + KL.v("KauSwipe onCreate ${activity.localClassName}") } - fun onPostCreate(activity: Activity) = this[activity].onPostCreate() + fun onPostCreate(activity: Activity) { + this[activity]?.onPostCreate() ?: throw SwipeBackException() + KL.v("KauSwipe onPostCreate ${activity.localClassName}") + } fun onDestroy(activity: Activity) { - val page: SwipeBackPage = this[activity] - pageStack.remove(page) - page.activity = null + val page: SwipeBackPage? = this[activity] + pageStack.kauRemoveIf { it.activityRef.get() == null || it === page } + page?.activityRef?.clear() + KL.v("KauSwipe onDestroy ${activity.localClassName}") } - fun finish(activity: Activity) = this[activity].scrollToFinishActivity() + fun finish(activity: Activity) = this[activity]?.scrollToFinishActivity() - internal fun getPrePage(activity: SwipeBackPage): SwipeBackPage? { - val index = pageStack.indexOf(activity) - return if (index > 0) pageStack[index - 1] else null + internal fun getPrePage(page: SwipeBackPage): SwipeBackPage? { + //clean invalid pages + pageStack.kauRemoveIf { it.activityRef.get() == null } + return pageStack.getOrNull(pageStack.indexOf(page) - 1) } - } /** diff --git a/core/src/main/kotlin/ca/allanwang/kau/swipe/SwipeBackLayout.kt b/core/src/main/kotlin/ca/allanwang/kau/swipe/SwipeBackLayout.kt index 1474c1a..51cd17f 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/swipe/SwipeBackLayout.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/swipe/SwipeBackLayout.kt @@ -10,6 +10,7 @@ import android.view.MotionEvent import android.view.View import android.view.ViewGroup import android.widget.FrameLayout +import ca.allanwang.kau.logging.KL import ca.allanwang.kau.utils.adjustAlpha import ca.allanwang.kau.utils.navigationBarColor import ca.allanwang.kau.utils.statusBarColor @@ -22,8 +23,8 @@ import java.lang.ref.WeakReference * If an edge detection occurs, this layout consumes all the touch events * Use the [swipeEnabled] toggle if you need the scroll events on the same axis */ -class SwipeBackLayout @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0 -) : FrameLayout(context, attrs, defStyle), SwipeBackContract { +internal class SwipeBackLayout @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0 +) : FrameLayout(context, attrs, defStyle), SwipeBackContract, SwipeBackContractInternal { override val swipeBackLayout: SwipeBackLayout get() = this @@ -51,7 +52,7 @@ class SwipeBackLayout @JvmOverloads constructor(context: Context, attrs: Attribu override var disallowIntercept = false - private var contentView: View? = null + private lateinit var contentViewRef: WeakReference private val dragHelper: ViewDragHelper @@ -156,16 +157,6 @@ class SwipeBackLayout @JvmOverloads constructor(context: Context, attrs: Attribu addListener(chromeFadeListener) } - - /** - * Set up contentView which will be moved by user gesture - - * @param view - */ - private fun setContentView(view: View) { - contentView = view - } - override fun setEdgeSizePercent(swipeEdgePercent: Float) { edgeSize = ((if (horizontal) resources.displayMetrics.widthPixels else resources.displayMetrics.heightPixels) * swipeEdgePercent).toInt() } @@ -181,6 +172,7 @@ class SwipeBackLayout @JvmOverloads constructor(context: Context, attrs: Attribu /** * Removes a listener from the set of listeners + * and scans our list for invalid ones * @param listener */ @@ -193,11 +185,28 @@ class SwipeBackLayout @JvmOverloads constructor(context: Context, attrs: Attribu } } + /** + * Checks if a listener exists in our list, + * and remove invalid ones at the same time + */ + override fun hasListener(listener: SwipeListener): Boolean { + val iter = listeners.iterator() + while (iter.hasNext()) { + val l = iter.next().get() + if (l == null) + iter.remove() + else if (l == listener) + return true + } + return false + } + /** * Scroll out contentView and finish the activity */ override fun scrollToFinishActivity() { - val childWidth = contentView!!.width + val contentView = contentViewRef.get() ?: return KL.e("KauSwipe cannot scroll to finish as contentView is null. Is onPostCreate called?") + val childWidth = contentView.width val top = 0 val left = childWidth + OVERSCROLL_DISTANCE dragHelper.smoothSlideViewTo(contentView, left, top) @@ -224,6 +233,7 @@ class SwipeBackLayout @JvmOverloads constructor(context: Context, attrs: Attribu } override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { + val contentView = contentViewRef.get() ?: return KL.e("KauSwipe cannot change layout as contentView is null. Is onPostCreate called?") inLayout = true val xOffset: Int val yOffset: Int @@ -234,7 +244,7 @@ class SwipeBackLayout @JvmOverloads constructor(context: Context, attrs: Attribu xOffset = 0 yOffset = contentOffset } - contentView?.layout(xOffset, yOffset, xOffset + contentView!!.measuredWidth, yOffset + contentView!!.measuredHeight) + contentView.layout(xOffset, yOffset, xOffset + contentView.measuredWidth, yOffset + contentView.measuredHeight) inLayout = false } @@ -243,7 +253,7 @@ class SwipeBackLayout @JvmOverloads constructor(context: Context, attrs: Attribu } override fun drawChild(canvas: Canvas, child: View, drawingTime: Long): Boolean { - val drawContent = child === contentView + val drawContent = child === contentViewRef.get() val ret = super.drawChild(canvas, child, drawingTime) if (scrimOpacity > 0 && drawContent && dragHelper.viewDragState != ViewDragHelper.STATE_IDLE) drawScrim(canvas, child) @@ -272,7 +282,7 @@ class SwipeBackLayout @JvmOverloads constructor(context: Context, attrs: Attribu val contentChild = content.getChildAt(0) content.removeView(contentChild) addView(contentChild) - setContentView(contentChild) + contentViewRef = WeakReference(contentChild) content.addView(this) } @@ -286,6 +296,7 @@ class SwipeBackLayout @JvmOverloads constructor(context: Context, attrs: Attribu content.removeView(this) removeView(contentChild) content.addView(contentChild) + contentViewRef.clear() } override fun computeScroll() { @@ -324,10 +335,11 @@ class SwipeBackLayout @JvmOverloads constructor(context: Context, attrs: Attribu override fun onViewPositionChanged(changedView: View, left: Int, top: Int, dx: Int, dy: Int) { super.onViewPositionChanged(changedView, left, top, dx, dy) + val contentView = contentViewRef.get() ?: return KL.e("KauSwipe cannot change view position as contentView is null; is onPostCreate called?") //make sure that we are using the proper axis scrollPercent = Math.abs( - if (horizontal) left.toFloat() / contentView!!.width - else (top.toFloat() / contentView!!.height)) + if (horizontal) left.toFloat() / contentView.width + else (top.toFloat() / contentView.height)) contentOffset = if (horizontal) left else top invalidate() if (scrollPercent < scrollThreshold && !isScrollOverValid) diff --git a/core/src/main/kotlin/ca/allanwang/kau/swipe/SwipeBackPage.kt b/core/src/main/kotlin/ca/allanwang/kau/swipe/SwipeBackPage.kt index 81ddb62..3132f8c 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/swipe/SwipeBackPage.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/swipe/SwipeBackPage.kt @@ -4,15 +4,17 @@ import android.app.Activity import android.graphics.Color import android.graphics.drawable.ColorDrawable import android.view.ViewGroup +import ca.allanwang.kau.logging.KL +import java.lang.ref.WeakReference /** * Created by Mr.Jude on 2015/8/3. * * Updated by Allan Wang on 2017/07/05 */ -class SwipeBackPage(activity: Activity) : SwipeBackContract by SwipeBackLayout(activity) { +internal class SwipeBackPage(activity: Activity) : SwipeBackContractInternal by SwipeBackLayout(activity) { - var activity: Activity? = activity + var activityRef = WeakReference(activity) var slider: RelativeSlider /** @@ -38,8 +40,9 @@ class SwipeBackPage(activity: Activity) : SwipeBackContract by SwipeBackLayout(a } private fun handleLayout() { - if (swipeEnabled) swipeBackLayout.attachToActivity(activity!!) - else swipeBackLayout.removeFromActivity(activity!!) + val activity = activityRef.get() ?: return KL.v("KauSwipe activity ref gone during handleLayout") + if (swipeEnabled) swipeBackLayout.attachToActivity(activity) + else swipeBackLayout.removeFromActivity(activity) } fun setClosePercent(percent: Float): SwipeBackPage { @@ -49,6 +52,10 @@ class SwipeBackPage(activity: Activity) : SwipeBackContract by SwipeBackLayout(a } +internal interface SwipeBackContractInternal : SwipeBackContract { + val swipeBackLayout: SwipeBackLayout +} + interface SwipeBackContract { /** * Toggle main touch intercept @@ -59,7 +66,6 @@ interface SwipeBackContract { * This dynamically fades as the page gets closer to exiting */ var scrimColor: Int - val swipeBackLayout: SwipeBackLayout var edgeSize: Int /** * Set the flag for which edge the page is scrolling from @@ -92,7 +98,9 @@ interface SwipeBackContract { * Sets edge size based on screen size */ fun setEdgeSizePercent(swipeEdgePercent: Float) + fun addListener(listener: SwipeListener) fun removeListener(listener: SwipeListener) + fun hasListener(listener: SwipeListener): Boolean fun scrollToFinishActivity() } \ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/swipe/ViewDragHelper.java b/core/src/main/kotlin/ca/allanwang/kau/swipe/ViewDragHelper.java index 3368e10..566e9e5 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/swipe/ViewDragHelper.java +++ b/core/src/main/kotlin/ca/allanwang/kau/swipe/ViewDragHelper.java @@ -28,7 +28,7 @@ import static ca.allanwang.kau.swipe.SwipeBackHelperKt.SWIPE_EDGE_TOP; * This is an extension of {@link android.support.v4.widget.ViewDragHelper} * Along with additional methods defined in {@link ViewDragHelperExtras} */ -public class ViewDragHelper implements ViewDragHelperExtras { +class ViewDragHelper implements ViewDragHelperExtras { private static final String TAG = "ViewDragHelper"; /** diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/Const.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/Const.kt index dad01f1..889f347 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/utils/Const.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/utils/Const.kt @@ -1,14 +1,16 @@ package ca.allanwang.kau.utils +import android.support.v4.widget.ViewDragHelper + /** * Created by Allan Wang on 2017-06-08. */ const val ANDROID_NAMESPACE = "http://schemas.android.com/apk/res/android" -const val KAU_LEFT = 1 -const val KAU_TOP = 2 -const val KAU_RIGHT = 4 -const val KAU_BOTTOM = 8 +const val KAU_LEFT = ViewDragHelper.EDGE_LEFT +const val KAU_RIGHT = ViewDragHelper.EDGE_RIGHT +const val KAU_TOP = ViewDragHelper.EDGE_TOP +const val KAU_BOTTOM = ViewDragHelper.EDGE_BOTTOM const val KAU_HORIZONTAL = KAU_LEFT or KAU_RIGHT const val KAU_VERTICAL = KAU_TOP or KAU_BOTTOM const val KAU_ALL = KAU_HORIZONTAL or KAU_VERTICAL diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/FileUtils.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/FileUtils.kt new file mode 100644 index 0000000..25e0519 --- /dev/null +++ b/core/src/main/kotlin/ca/allanwang/kau/utils/FileUtils.kt @@ -0,0 +1,33 @@ +package ca.allanwang.kau.utils + +import android.content.Context +import android.os.Environment +import java.io.File +import java.io.IOException +import java.io.InputStream +import java.text.SimpleDateFormat +import java.util.* + +/** + * Created by Allan Wang on 2017-08-04. + */ +@Throws(IOException::class) +fun createMediaFile(prefix: String, extension: String): File { + val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date()) + val imageFileName = "${prefix}_${timeStamp}_" + val storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + val frostDir = File(storageDir, prefix) + if (!frostDir.exists()) frostDir.mkdirs() + return File.createTempFile(imageFileName, extension, frostDir) +} + +@Throws(IOException::class) +fun Context.createPrivateMediaFile(prefix: String, extension: String): File { + val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date()) + val imageFileName = "${prefix}_${timeStamp}_" + val storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES) + return File.createTempFile(imageFileName, extension, storageDir) +} + +fun File.copyFromInputStream(inputStream: InputStream) + = inputStream.use { input -> outputStream().use { output -> input.copyTo(output) } } \ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/NotificationUtils.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/NotificationUtils.kt new file mode 100644 index 0000000..23a8370 --- /dev/null +++ b/core/src/main/kotlin/ca/allanwang/kau/utils/NotificationUtils.kt @@ -0,0 +1,11 @@ +package ca.allanwang.kau.utils + +import android.content.Context +import android.support.v4.app.NotificationManagerCompat + + +/** + * Created by Allan Wang on 2017-08-04. + */ +fun Context.cancelNotification(notifId: Int) + = NotificationManagerCompat.from(this).cancel(notifId) \ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/PackageUtils.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/PackageUtils.kt index 42d150e..538208d 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/utils/PackageUtils.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/utils/PackageUtils.kt @@ -1,6 +1,5 @@ package ca.allanwang.kau.utils -import android.annotation.SuppressLint import android.content.Context import android.content.pm.PackageManager import android.os.Build diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/TransitionUtils.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/TransitionUtils.kt index fa062f7..1f4536b 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/utils/TransitionUtils.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/utils/TransitionUtils.kt @@ -6,9 +6,9 @@ import android.support.annotation.TransitionRes import android.support.transition.AutoTransition import android.support.transition.TransitionInflater import android.support.transition.TransitionManager -import android.support.transition.Transition as SupportTransition import android.transition.Transition import android.view.ViewGroup +import android.support.transition.Transition as SupportTransition /** * Created by Allan Wang on 2017-06-24. -- cgit v1.2.3