diff options
author | Allan Wang <me@allanwang.ca> | 2017-07-31 23:02:01 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-07-31 23:02:01 -0700 |
commit | 48213d0b427c478865c75fee912ff1ae8bbaffb5 (patch) | |
tree | 7aef1d8400fc3403ee5a40aba945f33a95319359 | |
parent | 8a4e9fd44dfbcf58aa7ab63167dcbdf8752db7d0 (diff) | |
download | kau-48213d0b427c478865c75fee912ff1ae8bbaffb5.tar.gz kau-48213d0b427c478865c75fee912ff1ae8bbaffb5.tar.bz2 kau-48213d0b427c478865c75fee912ff1ae8bbaffb5.zip |
Major update to core and kotterknife; create mediapicker (#15)
* Readme
* Fix kau direction bits
* Truly support transparent ripples
* Update changelog
* Test rect as base
* Replace fab transition with generic fade scale transition
* Add scalexy func
* Add scaleXY
* Add arguments to fadeScaleTransition
* Clean up ink indicator
* Create setOnSingleTapListener
* Fix lint and add rndColor
* Create kotterknife resettables
* Add readme and missing object
* Create lazy resettable registered
* Update core docs
* Opt for separate class for resettable registry
* Clean up resettable registry
* Rename functions
* Add ripple callback listener
* Adjust kprefactivity desc color
* Add more transitions
* Add delete keys option
* Add instrumentation tests
* switch id
* Revert automatic instrumental tests
* Generify imagepickercore and prepare video alternative
* Create working video picker
* Address possible null issue
* Update searchview
* Make layouts public
* Add changelog test
* Update logo link
* Add custom color gif
84 files changed, 1064 insertions, 356 deletions
@@ -43,7 +43,7 @@ dependencies { compile "ca.allanwang.kau:about:$KAU" compile "ca.allanwang.kau:colorpicker:$KAU" - compile "ca.allanwang.kau:imagepicker:$KAU" + compile "ca.allanwang.kau:mediapicker:$KAU" compile "ca.allanwang.kau:kpref-activity:$KAU" compile "ca.allanwang.kau:searchview:$KAU" } @@ -89,8 +89,8 @@ dependencies { * Includes `:core`, [`Material Dialogs (commons)`](https://github.com/afollestad/material-dialogs) -## [Image Picker](imagepicker#readme) -* WIP - Overlaying media chooser +## [Media Picker](mediapicker#readme) +* Fully functional image and video pickers, both as an overlay and as a requested activity. * Includes `:core-ui`, [`Glide`](https://github.com/bumptech/glide), [`Blurry`](https://github.com/wasabeef/Blurry) @@ -110,6 +110,7 @@ dependencies { ![About Activity Gif](https://raw.githubusercontent.com/AllanWang/Storage-Hub/master/kau/kau_about_activity.gif) ![Ink Indicator Gif](https://raw.githubusercontent.com/AllanWang/Storage-Hub/master/kau/kau_ink_indicator.gif) ![Color Picker Gif](https://raw.githubusercontent.com/AllanWang/Storage-Hub/master/kau/kau_color_picker.gif) +![Color Picker Custom Gif](https://raw.githubusercontent.com/AllanWang/Storage-Hub/master/kau/kau_color_picker_custom.gif) ![KPref Items Gif](https://raw.githubusercontent.com/AllanWang/Storage-Hub/master/kau/kau_kpref_items.gif) ![SearchView Gif](https://raw.githubusercontent.com/AllanWang/Storage-Hub/master/kau/kau_search_view.gif) diff --git a/_layouts/default.html b/_layouts/default.html index 0bec489..6868eca 100644 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -15,7 +15,7 @@ <body> <div class="wrapper"> <header> - <a href="{{ site.github.url }}"><img src="https://cdn.rawgit.com/AllanWang/KAU/master/files/logo.svg" alt="KAU" width="30%"/></a> + <a href="{{ site.github.url }}"><img src="https://cdn.rawgit.com/AllanWang/KAU/master/files/images/logo.svg" alt="KAU" width="30%"/></a> <!--<h1><a href="{{ site.github.url }}">{{ site.title | default: site.github.repository_name }}</a></h1>--> <p>{{ site.description | default: site.github.project_tagline }}</p> diff --git a/adapter/build.gradle b/adapter/build.gradle index 772adca..15708dc 100644 --- a/adapter/build.gradle +++ b/adapter/build.gradle @@ -3,7 +3,6 @@ ext.kauSubModuleMinSdk = project.CORE_MIN_SDK apply from: '../android-lib.gradle' dependencies { - compile project(':core') compile "com.mikepenz:fastadapter:${FAST_ADAPTER}@aar" diff --git a/adapter/src/main/kotlin/ca/allanwang/kau/animators/FadeScaleAnimator.kt b/adapter/src/main/kotlin/ca/allanwang/kau/animators/FadeScaleAnimator.kt index cc73100..bf38cac 100644 --- a/adapter/src/main/kotlin/ca/allanwang/kau/animators/FadeScaleAnimator.kt +++ b/adapter/src/main/kotlin/ca/allanwang/kau/animators/FadeScaleAnimator.kt @@ -3,6 +3,7 @@ package ca.allanwang.kau.animators import android.support.v7.widget.RecyclerView import android.view.View import android.view.ViewPropertyAnimator +import ca.allanwang.kau.utils.scaleXY /** * Created by Allan Wang on 2017-07-11. @@ -10,20 +11,17 @@ import android.view.ViewPropertyAnimator class FadeScaleAnimatorAdd(val scaleFactor: Float = 1.0f, override var itemDelayFactor: Float = 0.125f) : KauAnimatorAdd { override fun animationPrepare(holder: RecyclerView.ViewHolder): View.() -> Unit = { - scaleX = scaleFactor - scaleY = scaleFactor + scaleXY = scaleFactor alpha = 0f } override fun animation(holder: RecyclerView.ViewHolder): ViewPropertyAnimator.() -> Unit = { - scaleX(1f) - scaleY(1f) + scaleXY(1f) alpha(1f) } override fun animationCleanup(holder: RecyclerView.ViewHolder): View.() -> Unit = { - scaleX = 1f - scaleY = 1f + scaleXY = 1f alpha = 1f } @@ -34,14 +32,12 @@ class FadeScaleAnimatorAdd(val scaleFactor: Float = 1.0f, override var itemDelay class FadeScaleAnimatorRemove(val scaleFactor: Float = 1.0f, override var itemDelayFactor: Float = 0.125f) : KauAnimatorRemove { override fun animation(holder: RecyclerView.ViewHolder): ViewPropertyAnimator.() -> Unit = { - scaleX(scaleFactor) - scaleY(scaleFactor) + scaleXY(scaleFactor) alpha(0f) } override fun animationCleanup(holder: RecyclerView.ViewHolder): View.() -> Unit = { - scaleX = 1f - scaleY = 1f + scaleXY = 1f alpha = 1f } diff --git a/android-lib.gradle b/android-lib.gradle index 78526e5..f45aa02 100644 --- a/android-lib.gradle +++ b/android-lib.gradle @@ -28,6 +28,7 @@ android { minSdkVersion Integer.parseInt(kauMinSdk) targetSdkVersion Integer.parseInt(project.TARGET_SDK) consumerProguardFiles 'progress-proguard.txt' + multiDexEnabled true testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } @@ -52,6 +53,29 @@ android { sourceSets { main.java.srcDirs += 'src/main/kotlin' test.java.srcDirs += 'src/test/kotlin' + androidTest.java.srcDirs += 'src/androidTest/kotlin' main.res.srcDirs += 'src/main/res-public' } + + testOptions.unitTests { + // Don't throw runtime exceptions for android calls that are not mocked + returnDefaultValues = true + + // Always show the result of every unit test, even if it passes. + all { + testLogging { + events 'passed', 'skipped', 'failed', 'standardOut', 'standardError' + } + } + } +} + +dependencies { + androidTestCompile 'com.android.support.test:runner:0.5' + androidTestCompile 'com.android.support.test:rules:0.5' + androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { + exclude group: 'com.android.support', module: 'support-annotations' + }) + testCompile 'junit:junit:4.12' + testCompile "org.jetbrains.kotlin:kotlin-test-junit:${KOTLIN}" }
\ No newline at end of file diff --git a/colorpicker/README.md b/colorpicker/README.md index 36e96a7..c90809e 100644 --- a/colorpicker/README.md +++ b/colorpicker/README.md @@ -6,4 +6,5 @@ The color picker also animates the selection, and uses Kotlin's DSL to provide e To use it, call `Context.colorPickerDialog` and specify and configs as required through the builder. -![Color Picker Gif](https://raw.githubusercontent.com/AllanWang/Storage-Hub/master/kau/kau_color_picker.gif)
\ No newline at end of file +![Color Picker Gif](https://raw.githubusercontent.com/AllanWang/Storage-Hub/master/kau/kau_color_picker.gif) +![Color Picker Custom Gif](https://raw.githubusercontent.com/AllanWang/Storage-Hub/master/kau/kau_color_picker_custom.gif)
\ No newline at end of file diff --git a/core-ui/build.gradle b/core-ui/build.gradle index c4dcc72..da6715c 100644 --- a/core-ui/build.gradle +++ b/core-ui/build.gradle @@ -3,7 +3,6 @@ ext.kauSubModuleMinSdk = project.CORE_MIN_SDK apply from: '../android-lib.gradle' dependencies { - compile project(':core') compile project(':adapter') diff --git a/core-ui/src/main/kotlin/ca/allanwang/kau/ui/widgets/ElasticDragDismissFrameLayout.kt b/core-ui/src/main/kotlin/ca/allanwang/kau/ui/widgets/ElasticDragDismissFrameLayout.kt index 5cdfc92..3c477c1 100644 --- a/core-ui/src/main/kotlin/ca/allanwang/kau/ui/widgets/ElasticDragDismissFrameLayout.kt +++ b/core-ui/src/main/kotlin/ca/allanwang/kau/ui/widgets/ElasticDragDismissFrameLayout.kt @@ -119,8 +119,7 @@ class ElasticDragDismissFrameLayout @JvmOverloads constructor( } else { // settle back to natural position animate() .translationY(0f) - .scaleX(1f) - .scaleY(1f) + .scaleXY(1f) .setDuration(200L) .setInterpolator(AnimHolder.fastOutSlowInInterpolator(context)) .setListener(null) @@ -176,11 +175,7 @@ class ElasticDragDismissFrameLayout @JvmOverloads constructor( } translationY = dragTo - if (shouldScale) { - val scale = 1 - (1 - dragDismissScale) * dragFraction - scaleX = scale - scaleY = scale - } + if (shouldScale) scaleXY = 1 - (1 - dragDismissScale) * dragFraction // if we've reversed direction and gone past the settle point then clear the flags to // allow the list to get the scroll events & reset any transforms @@ -191,8 +186,7 @@ class ElasticDragDismissFrameLayout @JvmOverloads constructor( draggingUp = false draggingDown = draggingUp translationY = 0f - scaleX = 1f - scaleY = 1f + scaleXY = 1f } dispatchDragCallback(dragFraction, dragTo, Math.min(1f, Math.abs(totalDrag) / dragDismissDistance), totalDrag) diff --git a/core-ui/src/main/kotlin/ca/allanwang/kau/ui/widgets/InkPageIndicator.java b/core-ui/src/main/kotlin/ca/allanwang/kau/ui/widgets/InkPageIndicator.java index cad6997..65eb5b7 100644 --- a/core-ui/src/main/kotlin/ca/allanwang/kau/ui/widgets/InkPageIndicator.java +++ b/core-ui/src/main/kotlin/ca/allanwang/kau/ui/widgets/InkPageIndicator.java @@ -36,6 +36,7 @@ import android.view.animation.Interpolator; import java.util.Arrays; +import ca.allanwang.kau.logging.KL; import ca.allanwang.kau.ui.R; import ca.allanwang.kau.utils.AnimHolder; import ca.allanwang.kau.utils.ColorUtilsKt; @@ -384,7 +385,7 @@ public class InkPageIndicator extends View implements ViewPager.OnPageChangeList if ((joiningFraction == 0f || joiningFraction == INVALID_FRACTION) && dotRevealFraction == 0f - && !(page == currentPage && selectedDotInPosition == true)) { + && !(page == currentPage && selectedDotInPosition)) { // case #1 – At rest unselectedDotPath.addCircle(dotCenterX[page], dotCenterY, dotRadius, Path.Direction.CW); @@ -632,8 +633,8 @@ public class InkPageIndicator extends View implements ViewPager.OnPageChangeList }); // slightly delay the start to give the joins a chance to run // unless dot isn't in position yet – then don't delay! - moveSelected.setStartDelay(selectedDotInPosition ? animDuration / 4l : 0l); - moveSelected.setDuration(animDuration * 3l / 4l); + moveSelected.setStartDelay(selectedDotInPosition ? animDuration / 4L : 0L); + moveSelected.setDuration(animDuration * 3L / 4L); moveSelected.setInterpolator(interpolator); return moveSelected; } @@ -641,9 +642,7 @@ public class InkPageIndicator extends View implements ViewPager.OnPageChangeList private void setJoiningFraction(int leftDot, float fraction) { if (leftDot < joiningFractions.length) { - if (leftDot == 1) { - Log.d("PageIndicator", "dot 1 fraction:\t" + fraction); - } + if (leftDot == 1) KL.INSTANCE.v("PageIndicator dot 1 fraction:\t$fraction"); joiningFractions[leftDot] = fraction; postInvalidateOnAnimation(); diff --git a/core-ui/src/main/res/layout/kau_elastic_recycler_activity.xml b/core-ui/src/main/res-public/layout/kau_elastic_recycler_activity.xml index 055d61d..055d61d 100644 --- a/core-ui/src/main/res/layout/kau_elastic_recycler_activity.xml +++ b/core-ui/src/main/res-public/layout/kau_elastic_recycler_activity.xml diff --git a/core-ui/src/main/res/layout/kau_recycler_detached_background.xml b/core-ui/src/main/res-public/layout/kau_recycler_detached_background.xml index 7295d66..7295d66 100644 --- a/core-ui/src/main/res/layout/kau_recycler_detached_background.xml +++ b/core-ui/src/main/res-public/layout/kau_recycler_detached_background.xml diff --git a/core-ui/src/main/res/layout/kau_recycler_textslider.xml b/core-ui/src/main/res-public/layout/kau_recycler_textslider.xml index eacd5be..eacd5be 100644 --- a/core-ui/src/main/res/layout/kau_recycler_textslider.xml +++ b/core-ui/src/main/res-public/layout/kau_recycler_textslider.xml diff --git a/core-ui/src/main/res-public/values/public.xml b/core-ui/src/main/res-public/values/public.xml index 59e34a6..aed5f9f 100644 --- a/core-ui/src/main/res-public/values/public.xml +++ b/core-ui/src/main/res-public/values/public.xml @@ -1,5 +1,8 @@ <resources xmlns:tools='http://schemas.android.com/tools' tools:ignore='ResourceName'> <!-- AUTO-GENERATED FILE. DO NOT MODIFY. public.xml is generated by the generatepublicxml gradle task --> + <public name='kau_elastic_recycler_activity' type='layout' /> + <public name='kau_recycler_detached_background' type='layout' /> + <public name='kau_recycler_textslider' type='layout' /> <public name='kau_shadow_overlay' type='color' /> <public name='Kau.Translucent' type='style' /> <public name='Kau.Translucent.NoAnimation' type='style' /> diff --git a/core/README.md b/core/README.md index 63313ac..c26836e 100644 --- a/core/README.md +++ b/core/README.md @@ -6,10 +6,13 @@ * [KPrefs](#kprefs) * [Changelog XML](#changelog) +* [Kotterknife](#kotterknife) * [Ripple Canvas](#ripple-canvas) +* [MeasureSpecDelegate](#measure-spec-delegate) * [Timber Logger](#timber-logger) * [Email Builder](#email-builder) * [Extensions](#extensions) +* [Lazy Resettable](#lazy-resettable) <a name="kprefs"></a> ## KPrefs @@ -103,6 +106,17 @@ Here is a template xml changelog file: </resources> ``` +<a name="kotterknife"></a> +## Kotterknife + +KAU comes shipped with [Kotterknife](https://github.com/JakeWharton/kotterknife) by Jake Wharton. +It is a powerful collection of lazy view bindings that only calls the expensive `findViewById` once. + +In KAU, there are also resettable versions (suffixed with `Resettable`) for all bindings. +These variants are weakly held in the private `KotterknifeRegistry` object, and can be used to invalidate the lazy +values through the `Kotterknife.reset` method. This is typically useful for Fragments, as they do not follow +the same lifecycle as Activities and Views. + <a name="ripple-canvas"></a> ## Ripple Canvas @@ -114,6 +128,13 @@ They can be used as transitions, or as a toolbar background to replicate the loo Many ripples can be stacked on top of each other to run at the same time from different locations. The canvas also supports color fading and direct color setting so it can effectively replace any background. +<a name="measure-spec-delegate"></a> +## Measure Spec Delegate + +If you ever have a view needing exact aspect ratios with its parent and/or itself, this delegate is here to help. +Implementing this in any view class unlocks its attributes, giving you three layers of view measuring to ensure exact sizing. +More information can be found in the [klass file](https://github.com/AllanWang/KAU/blob/master/core/src/main/kotlin/ca/allanwang/kau/ui/views/MeasureSpecDelegate.kt) + <a name="timber-logger"></a> ## Timber Logger @@ -134,4 +155,14 @@ Include your email and subject, along with other optional configurations such as Note that since KAU depends on [ANKO](https://github.com/Kotlin/anko), all of the extensions in its core package is also in KAU. KAU's vast collection of extensions is one of its strongest features. -There are too many to explain here, but you may check out the [utils package](https://github.com/AllanWang/KAU/tree/master/core/src/main/kotlin/ca/allanwang/kau/utils)
\ No newline at end of file +There are too many to explain here, but you may check out the [utils package](https://github.com/AllanWang/KAU/tree/master/core/src/main/kotlin/ca/allanwang/kau/utils) + +<a name="lazy-resettable></a> +## Lazy Resettable + +In the spirit of Kotlin's Lazy delegate, KAU supports a resettable version. Calling `lazyResettable` produces the same delegate, +but with an additional `invalidate` method. + +To further simplify this, there is also a `LazyResettableRegistry` class that can be used to hold all resettables. +The instance can be passed through the `lazyResettable` method, or classes can provide their own extension functions to +register the delegates by default.
\ No newline at end of file diff --git a/core/build.gradle b/core/build.gradle index ee413ba..0960d59 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -3,11 +3,7 @@ ext.kauSubModuleMinSdk = project.CORE_MIN_SDK apply from: '../android-lib.gradle' dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - testCompile 'junit:junit:4.12' - compile "org.jetbrains.kotlin:kotlin-stdlib:${KOTLIN}" - testCompile "org.jetbrains.kotlin:kotlin-test-junit:${KOTLIN}" compile "com.android.support:appcompat-v7:${ANDROID_SUPPORT_LIBS}" compile "com.android.support:support-v13:${ANDROID_SUPPORT_LIBS}" diff --git a/core/src/androidTest/kotlin/ca/allanwang/kau/utils/KotterknifeTest.kt b/core/src/androidTest/kotlin/ca/allanwang/kau/utils/KotterknifeTest.kt new file mode 100644 index 0000000..3282911 --- /dev/null +++ b/core/src/androidTest/kotlin/ca/allanwang/kau/utils/KotterknifeTest.kt @@ -0,0 +1,198 @@ +package ca.allanwang.kau.utils + +import android.content.Context +import android.support.test.InstrumentationRegistry +import android.support.test.filters.MediumTest +import android.support.test.runner.AndroidJUnit4 +import android.view.View +import android.widget.FrameLayout +import android.widget.TextView +import org.junit.Assert.* +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Created by Allan Wang on 2017-07-30. + */ +@RunWith(AndroidJUnit4::class) +@MediumTest +class KotterknifeTest { + + lateinit var context: Context + + @Before + fun init() { + context = InstrumentationRegistry.getContext() + } + + @Test + fun testCast() { + class Example(context: Context) : FrameLayout(context) { + val name: TextView by bindView(1) + } + + val example = Example(context) + example.addView(textViewWithId(1)) + assertNotNull(example.name) + } + + @Test + fun testFindCached() { + class Example(context: Context) : FrameLayout(context) { + val name: View by bindView(1) + } + + val example = Example(context) + example.addView(viewWithId(1)) + assertNotNull(example.name) + example.removeAllViews() + assertNotNull(example.name) + } + + @Test + fun testOptional() { + class Example(context: Context) : FrameLayout(context) { + val present: View? by bindOptionalView(1) + val missing: View? by bindOptionalView(2) + } + + val example = Example(context) + example.addView(viewWithId(1)) + assertNotNull(example.present) + assertNull(example.missing) + } + + @Test + fun testOptionalCached() { + class Example(context: Context) : FrameLayout(context) { + val present: View? by bindOptionalView(1) + val missing: View? by bindOptionalView(2) + } + + val example = Example(context) + example.addView(viewWithId(1)) + assertNotNull(example.present) + assertNull(example.missing) + example.removeAllViews() + example.addView(viewWithId(2)) + assertNotNull(example.present) + assertNull(example.missing) + } + + @Test + fun testMissingFails() { + class Example(context: Context) : FrameLayout(context) { + val name: TextView? by bindView(1) + } + + val example = Example(context) + try { + example.name + } catch (e: IllegalStateException) { + assertEquals("View ID 1 for 'name' not found.", e.message) + } + } + + @Test + fun testList() { + class Example(context: Context) : FrameLayout(context) { + val name: List<TextView> by bindViews(1, 2, 3) + } + + val example = Example(context) + example.addView(viewWithId(1)) + example.addView(viewWithId(2)) + example.addView(viewWithId(3)) + assertNotNull(example.name) + assertEquals(3, example.name.size) + } + + @Test + fun testListCaches() { + class Example(context: Context) : FrameLayout(context) { + val name: List<TextView> by bindViews(1, 2, 3) + } + + val example = Example(context) + example.addView(viewWithId(1)) + example.addView(viewWithId(2)) + example.addView(viewWithId(3)) + assertNotNull(example.name) + assertEquals(3, example.name.size) + example.removeAllViews() + assertNotNull(example.name) + assertEquals(3, example.name.size) + } + + @Test + fun testListMissingFails() { + class Example(context: Context) : FrameLayout(context) { + val name: List<TextView> by bindViews(1, 2, 3) + } + + val example = Example(context) + example.addView(viewWithId(1)) + example.addView(viewWithId(3)) + try { + example.name + } catch (e: IllegalStateException) { + assertEquals("View ID 2 for 'name' not found.", e.message) + } + } + + @Test + fun testOptionalList() { + class Example(context: Context) : FrameLayout(context) { + val name: List<TextView> by bindOptionalViews(1, 2, 3) + } + + val example = Example(context) + example.addView(viewWithId(1)) + example.addView(viewWithId(3)) + assertNotNull(example.name) + assertEquals(2, example.name.size) + } + + @Test + fun testOptionalListCaches() { + class Example(context: Context) : FrameLayout(context) { + val name: List<TextView> by bindOptionalViews(1, 2, 3) + } + + val example = Example(context) + example.addView(viewWithId(1)) + example.addView(viewWithId(3)) + assertNotNull(example.name) + assertEquals(2, example.name.size) + example.removeAllViews() + assertNotNull(example.name) + assertEquals(2, example.name.size) + } + + @Test + fun testReset() { + class Example(context: Context) : FrameLayout(context) { + val name: View? by bindOptionalViewResettable(1) + } + + val example = Example(context) + example.addView(viewWithId(1)) + assertNotNull(example.name) + example.removeAllViews() + Kotterknife.reset(example) + assertNull(example.name) + } + + private fun viewWithId(id: Int): View { + val view = View(context) + view.id = id + return view + } + + private fun textViewWithId(id: Int): View { + val view = TextView(context) + view.id = id + return view + } +}
\ No newline at end of file diff --git a/core/src/androidTest/kotlin/ca/allanwang/kau/xml/ChangelogTest.kt b/core/src/androidTest/kotlin/ca/allanwang/kau/xml/ChangelogTest.kt new file mode 100644 index 0000000..dcff70e --- /dev/null +++ b/core/src/androidTest/kotlin/ca/allanwang/kau/xml/ChangelogTest.kt @@ -0,0 +1,22 @@ +package ca.allanwang.kau.xml + +import android.support.test.InstrumentationRegistry +import android.support.test.filters.MediumTest +import android.support.test.runner.AndroidJUnit4 +import ca.allanwang.kau.test.R +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Created by Allan Wang on 2017-07-31. + */ +@RunWith(AndroidJUnit4::class) +@MediumTest +class ChangelogTest { + + @Test + fun simpleTest() { + val data = parse(InstrumentationRegistry.getTargetContext(), R.xml.text_changelog) + } + +}
\ No newline at end of file diff --git a/core/src/androidTest/res/xml/text_changelog.xml b/core/src/androidTest/res/xml/text_changelog.xml new file mode 100644 index 0000000..6294144 --- /dev/null +++ b/core/src/androidTest/res/xml/text_changelog.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + + <version title="title1"/> + <item text="test case" /> + <item text="" /> + + <version title="title2"/> + <item text="potato" /> + +</resources>
\ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/kotlin/LazyResettable.kt b/core/src/main/kotlin/ca/allanwang/kau/kotlin/LazyResettable.kt index 2981dda..701cb07 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/kotlin/LazyResettable.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/kotlin/LazyResettable.kt @@ -13,7 +13,7 @@ internal object UNINITIALIZED fun <T : Any> lazyResettable(initializer: () -> T): LazyResettable<T> = LazyResettable<T>(initializer) -class LazyResettable<T : Any>(private val initializer: () -> T, lock: Any? = null) : ILazyResettable<T>, Serializable { +open class LazyResettable<T : Any>(private val initializer: () -> T, lock: Any? = null) : ILazyResettable<T>, Serializable { @Volatile private var _value: Any = UNINITIALIZED private val lock = lock ?: this @@ -52,4 +52,32 @@ class LazyResettable<T : Any>(private val initializer: () -> T, lock: Any? = nul interface ILazyResettable<T> : Lazy<T> { fun invalidate() +} + +interface ILazyResettableRegistry { + fun <T : Any> lazy(initializer: () -> T): LazyResettable<T> + fun <T : Any> add(resettable: LazyResettable<T>): LazyResettable<T> + fun invalidateAll() + fun clear() +} + +/** + * The following below is a helper class that registers all resettables into a weakly held list + * All resettables can therefore be invalidated at once + */ +class LazyResettableRegistry : ILazyResettableRegistry { + + var lazyRegistry: MutableList<LazyResettable<*>> = mutableListOf() + + override fun <T : Any> lazy(initializer: () -> T): LazyResettable<T> + = add(lazyResettable(initializer)) + + override fun <T : Any> add(resettable: LazyResettable<T>): LazyResettable<T> { + lazyRegistry.add(resettable) + return resettable + } + + override fun invalidateAll() = lazyRegistry.forEach { it.invalidate() } + + override fun clear() = lazyRegistry.clear() }
\ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/kpref/KPref.kt b/core/src/main/kotlin/ca/allanwang/kau/kpref/KPref.kt index fa6c5a9..c1ce282 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/kpref/KPref.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/kpref/KPref.kt @@ -6,6 +6,19 @@ import ca.allanwang.kau.kotlin.ILazyResettable /** * Created by Allan Wang on 2017-06-07. + * + * Base class for shared preferences + * All objects extending this class must be called in + * the app's [android.app.Application] class + * + * See the [KPref.kpref] extensions for more details + * + * Furthermore, all kprefs are held in the [prefMap], + * so if you wish to reset a preference, you must also invalidate the kpref + * from that map + * + * You may optionally override [deleteKeys]. This will be called on initialization + * And delete all keys returned from that method */ open class KPref { @@ -18,6 +31,12 @@ open class KPref { initialized = true this.c = c.applicationContext PREFERENCE_NAME = preferenceName + val toDelete = deleteKeys() + if (toDelete.isNotEmpty()) { + val edit = sp.edit() + toDelete.forEach { edit.remove(it) } + edit.apply() + } } internal val sp: SharedPreferences by lazy { @@ -33,4 +52,6 @@ open class KPref { operator fun get(key: String): ILazyResettable<*>? = prefMap[key] + open fun deleteKeys(): Array<String> = arrayOf() + }
\ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/kpref/KPrefDelegate.kt b/core/src/main/kotlin/ca/allanwang/kau/kpref/KPrefDelegate.kt index f742078..8a582d8 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/kpref/KPrefDelegate.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/kpref/KPrefDelegate.kt @@ -2,7 +2,6 @@ package ca.allanwang.kau.kpref import ca.allanwang.kau.kotlin.ILazyResettable -object UNINITIALIZED fun KPref.kpref(key: String, fallback: Boolean, postSetter: (value: Boolean) -> Unit = {}) = KPrefDelegate(key, fallback, this, postSetter) fun KPref.kpref(key: String, fallback: Double, postSetter: (value: Float) -> Unit = {}) = KPrefDelegate(key, fallback.toFloat(), this, postSetter) @@ -25,6 +24,8 @@ class KPrefDelegate<T : Any> internal constructor( private val key: String, private val fallback: T, private val pref: KPref, var postSetter: (value: T) -> Unit = {}, lock: Any? = null ) : ILazyResettable<T>, java.io.Serializable { + private object UNINITIALIZED + @Volatile private var _value: Any = UNINITIALIZED private val lock = lock ?: this diff --git a/core/src/main/kotlin/ca/allanwang/kau/ui/views/RippleCanvas.kt b/core/src/main/kotlin/ca/allanwang/kau/ui/views/RippleCanvas.kt index 773490c..f587e60 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/ui/views/RippleCanvas.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/ui/views/RippleCanvas.kt @@ -1,14 +1,13 @@ package ca.allanwang.kau.ui.views +import android.animation.Animator +import android.animation.AnimatorListenerAdapter import android.animation.ArgbEvaluator import android.animation.ValueAnimator import android.content.Context -import android.graphics.Canvas -import android.graphics.Color -import android.graphics.Paint +import android.graphics.* import android.util.AttributeSet import android.view.View -import ca.allanwang.kau.utils.adjustAlpha /** * Created by Allan Wang on 2016-11-17. @@ -21,63 +20,45 @@ import ca.allanwang.kau.utils.adjustAlpha class RippleCanvas @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : View(context, attrs, defStyleAttr) { - private val paint: Paint = Paint() + private val paint: Paint = Paint().apply { + isAntiAlias = true + style = Paint.Style.FILL + } + private val eraser: Paint = Paint().apply { + style = Paint.Style.FILL + xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR) + } private var baseColor = Color.TRANSPARENT private val ripples: MutableList<Ripple> = mutableListOf() - init { - paint.isAntiAlias = true - paint.style = Paint.Style.FILL - } - /** - * Drawing the ripples involves having access to the next layer if it exists, - * and using its values to decide on the current color. - * If the next layer requests a fade, we will adjust the alpha of our current layer before drawing. - * Otherwise we will just draw the color as intended + * Draw ripples one at a time in the order given + * To support transparent ripples, we simply erase the overlapping base before adding a new circle */ override fun onDraw(canvas: Canvas) { - val itr = ripples.listIterator() - if (!itr.hasNext()) return canvas.drawColor(baseColor) - var next = itr.next() - canvas.drawColor(colorToDraw(baseColor, next.fade, next.radius, next.maxRadius)) - var last = false - while (!last) { - val current = next - if (itr.hasNext()) next = itr.next() - else last = true - //We may fade any layer except for the last one - paint.color = colorToDraw(current.color, next.fade && !last, next.radius, next.maxRadius) - canvas.drawCircle(current.x, current.y, current.radius, paint) - if (current.radius == current.maxRadius) { - if (!last) { - itr.previous() - itr.remove() - itr.next() - } else { - itr.remove() - } - baseColor = current.color + paint.color = baseColor + canvas.drawRect(0f, 0f, canvas.width.toFloat(), canvas.height.toFloat(), paint) + val itr = ripples.iterator() + while (itr.hasNext()) { + val r = itr.next() + paint.color = r.color + canvas.drawCircle(r.x, r.y, r.radius, eraser) + canvas.drawCircle(r.x, r.y, r.radius, paint) + if (r.radius == r.maxRadius) { + itr.remove() + baseColor = r.color } } } /** - * Given our current color and next layer's radius & max, - * we will decide on the alpha of our current layer - */ - internal fun colorToDraw(color: Int, fade: Boolean, current: Float, goal: Float): Int { - if (!fade || (current / goal <= FADE_PIVOT)) return color - val factor = (goal - current) / (goal - FADE_PIVOT * goal) - return color.adjustAlpha(factor) - } - - /** * Creates a ripple effect from the given starting values - * [fade] will gradually transition previous ripples to a transparent color so the resulting background is what we want - * this is typically only necessary if the ripple color has transparency */ - fun ripple(color: Int, startX: Float = 0f, startY: Float = 0f, duration: Long = 600L, fade: Boolean = Color.alpha(color) != 255) { + fun ripple(color: Int, + startX: Float = 0f, + startY: Float = 0f, + duration: Long = 600L, + callback: (() -> Unit)? = null) { val w = width.toFloat() val h = height.toFloat() val x = when (startX) { @@ -91,7 +72,7 @@ class RippleCanvas @JvmOverloads constructor( else -> startY } val maxRadius = Math.hypot(Math.max(x, w - x).toDouble(), Math.max(y, h - y).toDouble()).toFloat() - val ripple = Ripple(color, x, y, 0f, maxRadius, fade) + val ripple = Ripple(color, x, y, 0f, maxRadius) ripples.add(ripple) val animator = ValueAnimator.ofFloat(0f, maxRadius) animator.duration = duration @@ -99,6 +80,11 @@ class RippleCanvas @JvmOverloads constructor( ripple.radius = animation.animatedValue as Float invalidate() } + if (callback != null) + animator.addListener(object : AnimatorListenerAdapter() { + override fun onAnimationCancel(animation: Animator?) = callback() + override fun onAnimationEnd(animation: Animator?) = callback() + }) animator.start() } @@ -111,6 +97,8 @@ class RippleCanvas @JvmOverloads constructor( invalidate() } + override fun setBackgroundColor(color: Int) = set(color) + /** * Sets a color directly but with a transition */ @@ -129,12 +117,10 @@ class RippleCanvas @JvmOverloads constructor( val x: Float, val y: Float, var radius: Float, - val maxRadius: Float, - val fade: Boolean) + val maxRadius: Float) companion object { const val MIDDLE = -1.0f const val END = -2.0f - const val FADE_PIVOT = 0.5f } } diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/AnimUtils.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/AnimUtils.kt index 112c8ec..5da21bb 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/utils/AnimUtils.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/utils/AnimUtils.kt @@ -6,6 +6,7 @@ import android.annotation.SuppressLint import android.support.annotation.StringRes import android.view.View import android.view.ViewAnimationUtils +import android.view.ViewPropertyAnimator import android.view.animation.Animation import android.view.animation.AnimationUtils import android.widget.TextView @@ -123,4 +124,6 @@ import android.widget.TextView }) } -@KauUtils fun TextView.setTextWithFade(@StringRes textId: Int, duration: Long = 200, onFinish: (() -> Unit)? = null) = setTextWithFade(context.getString(textId), duration, onFinish)
\ No newline at end of file +@KauUtils fun TextView.setTextWithFade(@StringRes textId: Int, duration: Long = 200, onFinish: (() -> Unit)? = null) = setTextWithFade(context.getString(textId), duration, onFinish) + +@KauUtils inline fun ViewPropertyAnimator.scaleXY(value: Float) = scaleX(value).scaleY(value)
\ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/ColorUtils.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/ColorUtils.kt index 50d117c..8537185 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/utils/ColorUtils.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/utils/ColorUtils.kt @@ -15,10 +15,25 @@ import android.support.v7.widget.AppCompatEditText import android.support.v7.widget.Toolbar import android.widget.* import com.afollestad.materialdialogs.R +import java.util.* /** * Created by Allan Wang on 2017-06-08. */ + +/** + * Generates a random opaque color + * Note that this is mainly for testing + * Should you require this method often, consider + * rewriting the method and storing the [Random] instance + * rather than generating one each time + */ +inline val rndColor: Int + get() { + val rnd = Random() + return Color.rgb(rnd.nextInt(256), rnd.nextInt(256), rnd.nextInt(256)) + } + inline val Int.isColorDark: Boolean get() = isColorDark(0.5f) 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 f267a60..3e90926 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/utils/Const.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/utils/Const.kt @@ -9,6 +9,6 @@ const val KAU_LEFT = 1 const val KAU_TOP = 2 const val KAU_RIGHT = 4 const val KAU_BOTTOM = 8 -const val KAU_HORIZONTAL = KAU_LEFT and KAU_RIGHT -const val KAU_VERTICAL = KAU_TOP and KAU_BOTTOM -const val KAU_ALL = KAU_HORIZONTAL and KAU_VERTICAL
\ No newline at end of file +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
\ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/Kotterknife.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/Kotterknife.kt index 3783931..f3c08bd 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/utils/Kotterknife.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/utils/Kotterknife.kt @@ -1,3 +1,5 @@ +@file:Suppress("UNCHECKED_CAST") + package ca.allanwang.kau.utils /** @@ -13,6 +15,7 @@ import android.app.DialogFragment import android.app.Fragment import android.support.v7.widget.RecyclerView.ViewHolder import android.view.View +import java.util.* import kotlin.properties.ReadOnlyProperty import kotlin.reflect.KProperty import android.support.v4.app.DialogFragment as SupportDialogFragment @@ -30,13 +33,13 @@ fun <V : View> Dialog.bindView(id: Int) fun <V : View> DialogFragment.bindView(id: Int) : ReadOnlyProperty<DialogFragment, V> = required(id, viewFinder) -fun <V : View> android.support.v4.app.DialogFragment.bindView(id: Int) +fun <V : View> SupportDialogFragment.bindView(id: Int) : ReadOnlyProperty<android.support.v4.app.DialogFragment, V> = required(id, viewFinder) fun <V : View> Fragment.bindView(id: Int) : ReadOnlyProperty<Fragment, V> = required(id, viewFinder) -fun <V : View> android.support.v4.app.Fragment.bindView(id: Int) +fun <V : View> SupportFragment.bindView(id: Int) : ReadOnlyProperty<android.support.v4.app.Fragment, V> = required(id, viewFinder) fun <V : View> ViewHolder.bindView(id: Int) @@ -54,13 +57,13 @@ fun <V : View> Dialog.bindOptionalView(id: Int) fun <V : View> DialogFragment.bindOptionalView(id: Int) : ReadOnlyProperty<DialogFragment, V?> = optional(id, viewFinder) -fun <V : View> android.support.v4.app.DialogFragment.bindOptionalView(id: Int) +fun <V : View> SupportDialogFragment.bindOptionalView(id: Int) : ReadOnlyProperty<android.support.v4.app.DialogFragment, V?> = optional(id, viewFinder) fun <V : View> Fragment.bindOptionalView(id: Int) : ReadOnlyProperty<Fragment, V?> = optional(id, viewFinder) -fun <V : View> android.support.v4.app.Fragment.bindOptionalView(id: Int) +fun <V : View> SupportFragment.bindOptionalView(id: Int) : ReadOnlyProperty<android.support.v4.app.Fragment, V?> = optional(id, viewFinder) fun <V : View> ViewHolder.bindOptionalView(id: Int) @@ -78,13 +81,13 @@ fun <V : View> Dialog.bindViews(vararg ids: Int) fun <V : View> DialogFragment.bindViews(vararg ids: Int) : ReadOnlyProperty<DialogFragment, List<V>> = required(ids, viewFinder) -fun <V : View> android.support.v4.app.DialogFragment.bindViews(vararg ids: Int) +fun <V : View> SupportDialogFragment.bindViews(vararg ids: Int) : ReadOnlyProperty<android.support.v4.app.DialogFragment, List<V>> = required(ids, viewFinder) fun <V : View> Fragment.bindViews(vararg ids: Int) : ReadOnlyProperty<Fragment, List<V>> = required(ids, viewFinder) -fun <V : View> android.support.v4.app.Fragment.bindViews(vararg ids: Int) +fun <V : View> SupportFragment.bindViews(vararg ids: Int) : ReadOnlyProperty<android.support.v4.app.Fragment, List<V>> = required(ids, viewFinder) fun <V : View> ViewHolder.bindViews(vararg ids: Int) @@ -102,13 +105,13 @@ fun <V : View> Dialog.bindOptionalViews(vararg ids: Int) fun <V : View> DialogFragment.bindOptionalViews(vararg ids: Int) : ReadOnlyProperty<DialogFragment, List<V>> = optional(ids, viewFinder) -fun <V : View> android.support.v4.app.DialogFragment.bindOptionalViews(vararg ids: Int) +fun <V : View> SupportDialogFragment.bindOptionalViews(vararg ids: Int) : ReadOnlyProperty<android.support.v4.app.DialogFragment, List<V>> = optional(ids, viewFinder) fun <V : View> Fragment.bindOptionalViews(vararg ids: Int) : ReadOnlyProperty<Fragment, List<V>> = optional(ids, viewFinder) -fun <V : View> android.support.v4.app.Fragment.bindOptionalViews(vararg ids: Int) +fun <V : View> SupportFragment.bindOptionalViews(vararg ids: Int) : ReadOnlyProperty<android.support.v4.app.Fragment, List<V>> = optional(ids, viewFinder) fun <V : View> ViewHolder.bindOptionalViews(vararg ids: Int) @@ -122,11 +125,11 @@ private inline val Dialog.viewFinder: Dialog.(Int) -> View? get() = { findViewById(it) } private inline val DialogFragment.viewFinder: DialogFragment.(Int) -> View? get() = { dialog.findViewById(it) } -private inline val android.support.v4.app.DialogFragment.viewFinder: android.support.v4.app.DialogFragment.(Int) -> View? +private inline val SupportDialogFragment.viewFinder: SupportDialogFragment.(Int) -> View? get() = { dialog.findViewById(it) } private inline val Fragment.viewFinder: Fragment.(Int) -> View? get() = { view.findViewById(it) } -private inline val android.support.v4.app.Fragment.viewFinder: android.support.v4.app.Fragment.(Int) -> View? +private inline val SupportFragment.viewFinder: SupportFragment.(Int) -> View? get() = { view!!.findViewById(it) } private inline val ViewHolder.viewFinder: ViewHolder.(Int) -> View? get() = { itemView.findViewById(it) } @@ -134,33 +137,173 @@ private inline val ViewHolder.viewFinder: ViewHolder.(Int) -> View? private fun viewNotFound(id: Int, desc: KProperty<*>): Nothing = throw IllegalStateException("View ID $id for '${desc.name}' not found.") -@Suppress("UNCHECKED_CAST") private fun <T, V : View> required(id: Int, finder: T.(Int) -> View?) = Lazy { t: T, desc -> (t.finder(id) as V?)?.apply { } ?: viewNotFound(id, desc) } -@Suppress("UNCHECKED_CAST") private fun <T, V : View> optional(id: Int, finder: T.(Int) -> View?) = Lazy { t: T, _ -> t.finder(id) as V? } -@Suppress("UNCHECKED_CAST") private fun <T, V : View> required(ids: IntArray, finder: T.(Int) -> View?) = Lazy { t: T, desc -> ids.map { t.finder(it) as V? ?: viewNotFound(it, desc) } } -@Suppress("UNCHECKED_CAST") private fun <T, V : View> optional(ids: IntArray, finder: T.(Int) -> View?) = Lazy { t: T, _ -> ids.map { t.finder(it) as V? }.filterNotNull() } // Like Kotlin's lazy delegate but the initializer gets the target and metadata passed to it -private class Lazy<T, V>(private val initializer: (T, KProperty<*>) -> V) : ReadOnlyProperty<T, V> { - private object EMPTY +private open class Lazy<in T, out V>(private val initializer: (T, KProperty<*>) -> V) : ReadOnlyProperty<T, V> { + protected object EMPTY - private var value: Any? = EMPTY + protected var value: Any? = EMPTY override fun getValue(thisRef: T, property: KProperty<*>): V { - if (value == EMPTY) { + if (value == EMPTY) value = initializer(thisRef, property) - } - @Suppress("UNCHECKED_CAST") + return value as V } +} + +/* + * The components below are a variant of the view bindings with lazy resettables + * All bindings are weakly held so that they may be reset through KotterknifeRegistry.reset + * + * This is typically only needed in cases such as Fragments, + * where their lifecycle doesn't match that of an Activity or View + * + * Credits to <a href="https://github.com/MichaelRocks">MichaelRocks</a> + */ + +fun <V : View> View.bindViewResettable(id: Int) + : ReadOnlyProperty<View, V> = requiredResettable(id, viewFinder) + +fun <V : View> Activity.bindViewResettable(id: Int) + : ReadOnlyProperty<Activity, V> = requiredResettable(id, viewFinder) + +fun <V : View> Dialog.bindViewResettable(id: Int) + : ReadOnlyProperty<Dialog, V> = requiredResettable(id, viewFinder) + +fun <V : View> DialogFragment.bindViewResettable(id: Int) + : ReadOnlyProperty<DialogFragment, V> = requiredResettable(id, viewFinder) + +fun <V : View> SupportDialogFragment.bindViewResettable(id: Int) + : ReadOnlyProperty<android.support.v4.app.DialogFragment, V> = requiredResettable(id, viewFinder) + +fun <V : View> Fragment.bindViewResettable(id: Int) + : ReadOnlyProperty<Fragment, V> = requiredResettable(id, viewFinder) + +fun <V : View> SupportFragment.bindViewResettable(id: Int) + : ReadOnlyProperty<android.support.v4.app.Fragment, V> = requiredResettable(id, viewFinder) + +fun <V : View> ViewHolder.bindViewResettable(id: Int) + : ReadOnlyProperty<ViewHolder, V> = requiredResettable(id, viewFinder) + +fun <V : View> View.bindOptionalViewResettable(id: Int) + : ReadOnlyProperty<View, V?> = optionalResettable(id, viewFinder) + +fun <V : View> Activity.bindOptionalViewResettable(id: Int) + : ReadOnlyProperty<Activity, V?> = optionalResettable(id, viewFinder) + +fun <V : View> Dialog.bindOptionalViewResettable(id: Int) + : ReadOnlyProperty<Dialog, V?> = optionalResettable(id, viewFinder) + +fun <V : View> DialogFragment.bindOptionalViewResettable(id: Int) + : ReadOnlyProperty<DialogFragment, V?> = optionalResettable(id, viewFinder) + +fun <V : View> SupportDialogFragment.bindOptionalViewResettable(id: Int) + : ReadOnlyProperty<android.support.v4.app.DialogFragment, V?> = optionalResettable(id, viewFinder) + +fun <V : View> Fragment.bindOptionalViewResettable(id: Int) + : ReadOnlyProperty<Fragment, V?> = optionalResettable(id, viewFinder) + +fun <V : View> SupportFragment.bindOptionalViewResettable(id: Int) + : ReadOnlyProperty<android.support.v4.app.Fragment, V?> = optionalResettable(id, viewFinder) + +fun <V : View> ViewHolder.bindOptionalViewResettable(id: Int) + : ReadOnlyProperty<ViewHolder, V?> = optionalResettable(id, viewFinder) + +fun <V : View> View.bindViewsResettable(vararg ids: Int) + : ReadOnlyProperty<View, List<V>> = requiredResettable(ids, viewFinder) + +fun <V : View> Activity.bindViewsResettable(vararg ids: Int) + : ReadOnlyProperty<Activity, List<V>> = requiredResettable(ids, viewFinder) + +fun <V : View> Dialog.bindViewsResettable(vararg ids: Int) + : ReadOnlyProperty<Dialog, List<V>> = requiredResettable(ids, viewFinder) + +fun <V : View> DialogFragment.bindViewsResettable(vararg ids: Int) + : ReadOnlyProperty<DialogFragment, List<V>> = requiredResettable(ids, viewFinder) + +fun <V : View> SupportDialogFragment.bindViewsResettable(vararg ids: Int) + : ReadOnlyProperty<android.support.v4.app.DialogFragment, List<V>> = requiredResettable(ids, viewFinder) + +fun <V : View> Fragment.bindViewsResettable(vararg ids: Int) + : ReadOnlyProperty<Fragment, List<V>> = requiredResettable(ids, viewFinder) + +fun <V : View> SupportFragment.bindViewsResettable(vararg ids: Int) + : ReadOnlyProperty<android.support.v4.app.Fragment, List<V>> = requiredResettable(ids, viewFinder) + +fun <V : View> ViewHolder.bindViewsResettable(vararg ids: Int) + : ReadOnlyProperty<ViewHolder, List<V>> = requiredResettable(ids, viewFinder) + +fun <V : View> View.bindOptionalViewsResettable(vararg ids: Int) + : ReadOnlyProperty<View, List<V>> = optionalResettable(ids, viewFinder) + +fun <V : View> Activity.bindOptionalViewsResettable(vararg ids: Int) + : ReadOnlyProperty<Activity, List<V>> = optionalResettable(ids, viewFinder) + +fun <V : View> Dialog.bindOptionalViewsResettable(vararg ids: Int) + : ReadOnlyProperty<Dialog, List<V>> = optionalResettable(ids, viewFinder) + +fun <V : View> DialogFragment.bindOptionalViewsResettable(vararg ids: Int) + : ReadOnlyProperty<DialogFragment, List<V>> = optionalResettable(ids, viewFinder) + +fun <V : View> SupportDialogFragment.bindOptionalViewsResettable(vararg ids: Int) + : ReadOnlyProperty<android.support.v4.app.DialogFragment, List<V>> = optionalResettable(ids, viewFinder) + +fun <V : View> Fragment.bindOptionalViewsResettable(vararg ids: Int) + : ReadOnlyProperty<Fragment, List<V>> = optionalResettable(ids, viewFinder) + +fun <V : View> SupportFragment.bindOptionalViewsResettable(vararg ids: Int) + : ReadOnlyProperty<android.support.v4.app.Fragment, List<V>> = optionalResettable(ids, viewFinder) + +fun <V : View> ViewHolder.bindOptionalViewsResettable(vararg ids: Int) + : ReadOnlyProperty<ViewHolder, List<V>> = optionalResettable(ids, viewFinder) + +private fun <T, V : View> requiredResettable(id: Int, finder: T.(Int) -> View?) + = LazyResettable { t: T, desc -> (t.finder(id) as V?)?.apply { } ?: viewNotFound(id, desc) } + +private fun <T, V : View> optionalResettable(id: Int, finder: T.(Int) -> View?) + = LazyResettable { t: T, _ -> t.finder(id) as V? } + +private fun <T, V : View> requiredResettable(ids: IntArray, finder: T.(Int) -> View?) + = LazyResettable { t: T, desc -> ids.map { t.finder(it) as V? ?: viewNotFound(it, desc) } } + +private fun <T, V : View> optionalResettable(ids: IntArray, finder: T.(Int) -> View?) + = LazyResettable { t: T, _ -> ids.map { t.finder(it) as V? }.filterNotNull() } + +//Like Kotterknife's lazy delegate but is resettable +private class LazyResettable<in T, out V>(initializer: (T, KProperty<*>) -> V) : Lazy<T, V>(initializer) { + override fun getValue(thisRef: T, property: KProperty<*>): V { + KotterknifeRegistry.register(thisRef!!, this) + return super.getValue(thisRef, property) + } + + fun reset() { + value = EMPTY + } +} + +object Kotterknife { + fun reset(target: Any) { + KotterknifeRegistry.reset(target) + } +} + +private object KotterknifeRegistry { + private val lazyMap = WeakHashMap<Any, MutableCollection<LazyResettable<*, *>>>() + + fun register(target: Any, lazy: LazyResettable<*, *>) + = lazyMap.getOrPut(target, { Collections.newSetFromMap(WeakHashMap()) }).add(lazy) + + fun reset(target: Any) = lazyMap[target]?.forEach { it.reset() } }
\ 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 36bcc93..42d150e 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/utils/PackageUtils.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/utils/PackageUtils.kt @@ -1,5 +1,6 @@ package ca.allanwang.kau.utils +import android.annotation.SuppressLint import android.content.Context import android.content.pm.PackageManager import android.os.Build @@ -31,11 +32,9 @@ import android.os.Build } inline val buildIsMarshmallowAndUp: Boolean - get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M inline val buildIsLollipopAndUp: Boolean - @SuppressWarnings("NewApi") get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP inline val buildIsNougatAndUp: Boolean diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/ViewUtils.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/ViewUtils.kt index ead2cb7..53d711d 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/utils/ViewUtils.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/utils/ViewUtils.kt @@ -3,20 +3,21 @@ package ca.allanwang.kau.utils import android.animation.ValueAnimator +import android.annotation.SuppressLint import android.content.Context import android.graphics.Color import android.os.Build -import android.support.annotation.* +import android.support.annotation.ColorInt +import android.support.annotation.ColorRes +import android.support.annotation.RequiresApi +import android.support.annotation.StringRes import android.support.design.widget.FloatingActionButton import android.support.design.widget.Snackbar import android.support.design.widget.TextInputEditText -import android.support.transition.AutoTransition -import android.support.transition.Transition -import android.support.transition.TransitionInflater -import android.support.transition.TransitionManager import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.RecyclerView import android.view.LayoutInflater +import android.view.MotionEvent import android.view.View import android.view.ViewGroup import android.view.inputmethod.InputMethodManager @@ -135,7 +136,6 @@ fun FloatingActionButton.hideIf(hide: Boolean) = if (hide) hide() else show() if (flag and KAU_RIGHT > 0) margin else p.rightMargin, if (flag and KAU_BOTTOM > 0) margin else p.bottomMargin ) - requestLayout() return true } @@ -184,7 +184,6 @@ fun FloatingActionButton.hideIf(hide: Boolean) = if (hide) hide() else show() if (flag and KAU_RIGHT > 0) padding else paddingRight, if (flag and KAU_BOTTOM > 0) padding else paddingBottom ) - requestLayout() } @KauUtils fun View.hideKeyboard() { @@ -222,23 +221,19 @@ fun Context.fullLinearRecycler(rvAdapter: RecyclerView.Adapter<*>? = null, confi } /** - * Animate a transition for a FloatinActionButton + * Animate a transition a given imageview * If it is not shown, the action will be invoked directly and the fab will be shown * If it is already shown, scaling and alpha animations will be added to the action */ -inline fun FloatingActionButton.transition(crossinline action: FloatingActionButton.() -> Unit) { - if (isHidden) { - action() - show() - } else { +inline fun <T : ImageView> T.fadeScaleTransition(duration: Long = 500L, minScale: Float = 0.7f, crossinline action: T.() -> Unit) { + if (!isVisible) action() + else { var transitioned = false ValueAnimator.ofFloat(1.0f, 0.0f, 1.0f).apply { - duration = 500L + this.duration = duration addUpdateListener { val x = it.animatedValue as Float - val scale = x * 0.3f + 0.7f - scaleX = scale - scaleY = scale + scaleXY = x * (1 - minScale) + minScale imageAlpha = (x * 255).toInt() if (it.animatedFraction > 0.5f && !transitioned) { transitioned = true @@ -266,4 +261,29 @@ fun FloatingActionButton.hideOnDownwardsScroll(recycler: RecyclerView) { else if (dy < 0 && isHidden) show() } }) +} + +inline var View.scaleXY + get() = Math.max(scaleX, scaleY) + set(value) { + scaleX = value + scaleY = value + } + +/** + * Creates an on touch listener that only emits on a short single tap + */ +@SuppressLint("ClickableViewAccessibility") +inline fun View.setOnSingleTapListener(crossinline onSingleTap: (v: View, event: MotionEvent) -> Unit) { + setOnTouchListener { v, event -> + when (event.actionMasked) { + MotionEvent.ACTION_DOWN -> true + MotionEvent.ACTION_UP -> { + if (event.eventTime - event.downTime < 100) + onSingleTap(v, event) + true + } + else -> false + } + } }
\ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/changelog/Changelog.kt b/core/src/main/kotlin/ca/allanwang/kau/xml/Changelog.kt index 43546ae..4bf1836 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/changelog/Changelog.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/xml/Changelog.kt @@ -1,4 +1,4 @@ -package ca.allanwang.kau.changelog +package ca.allanwang.kau.xml import android.content.Context import android.content.res.XmlResourceParser diff --git a/core/src/main/kotlin/ca/allanwang/kau/xml/FAQ.kt b/core/src/main/kotlin/ca/allanwang/kau/xml/FAQ.kt new file mode 100644 index 0000000..dedfbbf --- /dev/null +++ b/core/src/main/kotlin/ca/allanwang/kau/xml/FAQ.kt @@ -0,0 +1,42 @@ +package ca.allanwang.kau.xml + +import android.content.Context +import android.content.res.XmlResourceParser +import android.support.annotation.XmlRes +import android.text.Html +import android.text.Spanned +import ca.allanwang.kau.utils.use +import org.xmlpull.v1.XmlPullParser + +/** + * Created by Allan Wang on 2017-07-30. + */ + +/** + * Parse an xml with two tags, <question>Text</question> and <answer>Text</answer>, + * and return them as a list of string pairs + */ +fun Context.kauParseFaq(@XmlRes xmlRes: Int, withNumbering: Boolean = true): List<Pair<Spanned, Spanned>> { + val items = mutableListOf<Pair<Spanned, Spanned>>() + resources.getXml(xmlRes).use { + parser: XmlResourceParser -> + var eventType = parser.eventType + var question: Spanned? = null + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + if (parser.name == "question") { + var q = parser.text.replace("\n", "<br/>") + if (withNumbering) q = "${items.size + 1}. $q" + question = Html.fromHtml(q) + } else if (parser.name == "answer") { + items.add(Pair(question ?: throw IllegalArgumentException("KAU FAQ answer found without a question"), + Html.fromHtml(parser.text.replace("\n", "<br/>")))) + question = null + } + } + + eventType = parser.next() + } + } + return items +}
\ No newline at end of file diff --git a/core/src/main/res-public/anim/kau_slide_out_left_top.xml b/core/src/main/res-public/anim/kau_slide_out_left_top.xml new file mode 100644 index 0000000..0707c49 --- /dev/null +++ b/core/src/main/res-public/anim/kau_slide_out_left_top.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<set xmlns:android="http://schemas.android.com/apk/res/android" + android:zAdjustment="top"> + <translate + android:duration="@android:integer/config_shortAnimTime" + android:fromXDelta="0" + android:toXDelta="-100%p" /> +</set>
\ No newline at end of file diff --git a/core/src/main/res-public/transition-v21/kau_enter_slide_bottom.xml b/core/src/main/res-public/transition-v21/kau_enter_slide_bottom.xml index 575e189..bbd22c5 100644 --- a/core/src/main/res-public/transition-v21/kau_enter_slide_bottom.xml +++ b/core/src/main/res-public/transition-v21/kau_enter_slide_bottom.xml @@ -1,9 +1,8 @@ <?xml version="1.0" encoding="utf-8"?> -<transitionSet - xmlns:android="http://schemas.android.com/apk/res/android" - android:transitionOrdering="together" +<transitionSet xmlns:android="http://schemas.android.com/apk/res/android" android:duration="400" - android:interpolator="@android:interpolator/linear_out_slow_in"> + android:interpolator="@android:interpolator/linear_out_slow_in" + android:transitionOrdering="together"> <slide android:slideEdge="bottom"> <targets> diff --git a/core/src/main/res-public/transition-v21/kau_enter_slide_left.xml b/core/src/main/res-public/transition-v21/kau_enter_slide_left.xml new file mode 100644 index 0000000..f91a268 --- /dev/null +++ b/core/src/main/res-public/transition-v21/kau_enter_slide_left.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<transitionSet xmlns:android="http://schemas.android.com/apk/res/android" + android:duration="400" + android:interpolator="@android:interpolator/linear_out_slow_in" + android:transitionOrdering="together"> + + <slide android:slideEdge="left"> + <targets> + <target android:excludeId="@android:id/navigationBarBackground" /> + <target android:excludeId="@android:id/statusBarBackground" /> + </targets> + </slide> + + <fade android:duration="@android:integer/config_mediumAnimTime"> + <targets> + <target android:targetId="@android:id/navigationBarBackground" /> + <target android:targetId="@android:id/statusBarBackground" /> + </targets> + </fade> + +</transitionSet> diff --git a/core/src/main/res-public/transition-v21/kau_enter_slide_right.xml b/core/src/main/res-public/transition-v21/kau_enter_slide_right.xml new file mode 100644 index 0000000..3e8c173 --- /dev/null +++ b/core/src/main/res-public/transition-v21/kau_enter_slide_right.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<transitionSet xmlns:android="http://schemas.android.com/apk/res/android" + android:duration="400" + android:interpolator="@android:interpolator/linear_out_slow_in" + android:transitionOrdering="together"> + + <slide android:slideEdge="right"> + <targets> + <target android:excludeId="@android:id/navigationBarBackground" /> + <target android:excludeId="@android:id/statusBarBackground" /> + </targets> + </slide> + + <fade android:duration="@android:integer/config_mediumAnimTime"> + <targets> + <target android:targetId="@android:id/navigationBarBackground" /> + <target android:targetId="@android:id/statusBarBackground" /> + </targets> + </fade> + +</transitionSet> diff --git a/core/src/main/res-public/transition-v21/kau_enter_slide_top.xml b/core/src/main/res-public/transition-v21/kau_enter_slide_top.xml index 8cf613b..91dd5d6 100644 --- a/core/src/main/res-public/transition-v21/kau_enter_slide_top.xml +++ b/core/src/main/res-public/transition-v21/kau_enter_slide_top.xml @@ -1,9 +1,8 @@ <?xml version="1.0" encoding="utf-8"?> -<transitionSet - xmlns:android="http://schemas.android.com/apk/res/android" - android:transitionOrdering="together" +<transitionSet xmlns:android="http://schemas.android.com/apk/res/android" android:duration="400" - android:interpolator="@android:interpolator/linear_out_slow_in"> + android:interpolator="@android:interpolator/linear_out_slow_in" + android:transitionOrdering="together"> <slide android:slideEdge="top"> <targets> diff --git a/core/src/main/res-public/transition-v21/kau_exit_slide_bottom.xml b/core/src/main/res-public/transition-v21/kau_exit_slide_bottom.xml index e0967ec..fa7d152 100644 --- a/core/src/main/res-public/transition-v21/kau_exit_slide_bottom.xml +++ b/core/src/main/res-public/transition-v21/kau_exit_slide_bottom.xml @@ -1,12 +1,11 @@ <?xml version="1.0" encoding="utf-8"?> -<transitionSet - xmlns:android="http://schemas.android.com/apk/res/android" - android:transitionOrdering="together" - android:interpolator="@android:interpolator/fast_out_linear_in"> +<transitionSet xmlns:android="http://schemas.android.com/apk/res/android" + android:interpolator="@android:interpolator/fast_out_linear_in" + android:transitionOrdering="together"> <slide - android:slideEdge="bottom" - android:duration="400"> + android:duration="400" + android:slideEdge="bottom"> <targets> <target android:excludeId="@android:id/navigationBarBackground" /> <target android:excludeId="@android:id/statusBarBackground" /> diff --git a/core/src/main/res-public/transition-v21/kau_exit_slide_left.xml b/core/src/main/res-public/transition-v21/kau_exit_slide_left.xml new file mode 100644 index 0000000..fe76552 --- /dev/null +++ b/core/src/main/res-public/transition-v21/kau_exit_slide_left.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<transitionSet xmlns:android="http://schemas.android.com/apk/res/android" + android:interpolator="@android:interpolator/fast_out_linear_in" + android:transitionOrdering="together"> + + <slide + android:duration="400" + android:slideEdge="left"> + <targets> + <target android:excludeId="@android:id/navigationBarBackground" /> + <target android:excludeId="@android:id/statusBarBackground" /> + </targets> + </slide> + + <fade android:duration="400"> + <targets> + <target android:targetId="@android:id/navigationBarBackground" /> + <target android:targetId="@android:id/statusBarBackground" /> + </targets> + </fade> + +</transitionSet> diff --git a/core/src/main/res-public/transition-v21/kau_exit_slide_right.xml b/core/src/main/res-public/transition-v21/kau_exit_slide_right.xml new file mode 100644 index 0000000..12c7503 --- /dev/null +++ b/core/src/main/res-public/transition-v21/kau_exit_slide_right.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<transitionSet xmlns:android="http://schemas.android.com/apk/res/android" + android:interpolator="@android:interpolator/fast_out_linear_in" + android:transitionOrdering="together"> + + <slide + android:duration="400" + android:slideEdge="right"> + <targets> + <target android:excludeId="@android:id/navigationBarBackground" /> + <target android:excludeId="@android:id/statusBarBackground" /> + </targets> + </slide> + + <fade android:duration="400"> + <targets> + <target android:targetId="@android:id/navigationBarBackground" /> + <target android:targetId="@android:id/statusBarBackground" /> + </targets> + </fade> + +</transitionSet> diff --git a/core/src/main/res-public/transition-v21/kau_exit_slide_top.xml b/core/src/main/res-public/transition-v21/kau_exit_slide_top.xml index a9849c0..9c81b78 100644 --- a/core/src/main/res-public/transition-v21/kau_exit_slide_top.xml +++ b/core/src/main/res-public/transition-v21/kau_exit_slide_top.xml @@ -1,12 +1,11 @@ <?xml version="1.0" encoding="utf-8"?> -<transitionSet - xmlns:android="http://schemas.android.com/apk/res/android" - android:transitionOrdering="together" - android:interpolator="@android:interpolator/fast_out_linear_in"> +<transitionSet xmlns:android="http://schemas.android.com/apk/res/android" + android:interpolator="@android:interpolator/fast_out_linear_in" + android:transitionOrdering="together"> <slide - android:slideEdge="top" - android:duration="400"> + android:duration="400" + android:slideEdge="top"> <targets> <target android:excludeId="@android:id/navigationBarBackground" /> <target android:excludeId="@android:id/statusBarBackground" /> diff --git a/core/src/main/res-public/values/public.xml b/core/src/main/res-public/values/public.xml index 7057f1f..fb1c9df 100644 --- a/core/src/main/res-public/values/public.xml +++ b/core/src/main/res-public/values/public.xml @@ -8,14 +8,19 @@ <public name='kau_slide_in_top' type='anim' /> <public name='kau_slide_out_bottom' type='anim' /> <public name='kau_slide_out_left' type='anim' /> + <public name='kau_slide_out_left_top' type='anim' /> <public name='kau_slide_out_right' type='anim' /> <public name='kau_slide_out_right_top' type='anim' /> <public name='kau_slide_out_top' type='anim' /> <public name='kau_selectable_white' type='drawable' /> <public name='kau_transparent' type='drawable' /> <public name='kau_enter_slide_bottom' type='transition' /> + <public name='kau_enter_slide_left' type='transition' /> + <public name='kau_enter_slide_right' type='transition' /> <public name='kau_enter_slide_top' type='transition' /> <public name='kau_exit_slide_bottom' type='transition' /> + <public name='kau_exit_slide_left' type='transition' /> + <public name='kau_exit_slide_right' type='transition' /> <public name='kau_exit_slide_top' type='transition' /> <public name='kau_activity_horizontal_margin' type='dimen' /> <public name='kau_activity_vertical_margin' type='dimen' /> diff --git a/core/src/test/kotlin/ca/allanwang/kau/kotlin/LazyResettableTest.kt b/core/src/test/kotlin/ca/allanwang/kau/kotlin/LazyResettableTest.kt new file mode 100644 index 0000000..1997bd1 --- /dev/null +++ b/core/src/test/kotlin/ca/allanwang/kau/kotlin/LazyResettableTest.kt @@ -0,0 +1,35 @@ +package ca.allanwang.kau.kotlin + +import org.junit.Before +import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotEquals + +/** + * Created by Allan Wang on 2017-07-29. + */ +class LazyResettableTest { + + lateinit var registry: LazyResettableRegistry + + @Before + fun init() { + registry = LazyResettableRegistry() + } + + @Test + fun basic() { + val timeDelegate = lazyResettable { System.currentTimeMillis() } + val time: Long by timeDelegate + registry.add(timeDelegate) + val t1 = time + Thread.sleep(5) + val t2 = time + registry.invalidateAll() + Thread.sleep(5) + val t3 = time + assertEquals(t1, t2, "Lazy resettable not returning same value after second call") + assertNotEquals(t1, t3, "Lazy resettable not invalidated by registry") + } + +}
\ No newline at end of file diff --git a/core/src/test/kotlin/ca/allanwang/kprefs/library/UtilsTest.kt b/core/src/test/kotlin/ca/allanwang/kau/utils/UtilsTest.kt index c2c96e4..071ee9c 100644 --- a/core/src/test/kotlin/ca/allanwang/kprefs/library/UtilsTest.kt +++ b/core/src/test/kotlin/ca/allanwang/kau/utils/UtilsTest.kt @@ -1,8 +1,6 @@ -package ca.allanwang.kprefs.library +package ca.allanwang.kau.utils import android.graphics.Color -import ca.allanwang.kau.utils.round -import ca.allanwang.kau.utils.toHexString import org.junit.Test import kotlin.test.assertEquals diff --git a/docs/Changelog.md b/docs/Changelog.md index 8e62fba..af26ff9 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -1,5 +1,19 @@ # Changelog +## v3.2.1 +* :core: Remove requestLayout call from setMargin and setPadding +* :core: Fix kau direction bits +* :core: Greatly simplify ripple canvas and truly support transparent ripples +* :core: Generalize fab transition to fade scale transition for all imageviews +* :core: Create ViewPropertyAnimator.scaleXY() and View.scaleXY +* :core: Create View.setOnSingleTapListener() +* :core: Create rndColor, which generates a random opaque color for testing +* :core: Add resettable view binding variants to Kotterknife +* :core: Create lazy resettable registry +* :core: Add more transitions and anims +* :kpref-activity: Reduce alpha color for desc +* :imagepicker: [breaking] Rename to mediapicker and add support for videos + ## v3.2.0 * :adapter: Make KauAnimator extensible * :imagepicker: Add uri val to ImageModel diff --git a/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImageModel.kt b/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImageModel.kt deleted file mode 100644 index 202805c..0000000 --- a/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImageModel.kt +++ /dev/null @@ -1,58 +0,0 @@ -package ca.allanwang.kau.imagepicker - -import android.database.Cursor -import android.net.Uri -import android.os.Parcel -import android.os.Parcelable -import android.provider.MediaStore -import android.support.annotation.NonNull -import java.io.File - - -/** - * Created by Allan Wang on 2017-07-14. - */ - -data class ImageModel(val size: Long, val dateModified: Long, val data: String, val displayName: String) : Parcelable { - - constructor(@NonNull cursor: Cursor) : this( - cursor.getLong(MediaStore.Images.Media.SIZE), - cursor.getLong(MediaStore.Images.Media.DATE_MODIFIED), - cursor.getString(MediaStore.Images.Media.DATA), - cursor.getString(MediaStore.Images.Media.DISPLAY_NAME) - ) - - constructor(parcel: Parcel) : this( - parcel.readLong(), - parcel.readLong(), - parcel.readString(), - parcel.readString()) - - - override fun writeToParcel(parcel: Parcel, flags: Int) { - parcel.writeLong(this.size) - parcel.writeLong(this.dateModified) - parcel.writeString(this.data) - parcel.writeString(this.displayName) - } - - val uri: Uri by lazy { Uri.fromFile(File(data)) } - - override fun describeContents(): Int { - return 0 - } - - companion object CREATOR : Parcelable.Creator<ImageModel> { - override fun createFromParcel(parcel: Parcel): ImageModel { - return ImageModel(parcel) - } - - override fun newArray(size: Int): Array<ImageModel?> { - return arrayOfNulls(size) - } - } - -} - -private fun Cursor.getString(name: String) = getString(getColumnIndex(name)) -private fun Cursor.getLong(name: String) = getLong(getColumnIndex(name))
\ No newline at end of file diff --git a/imagepicker/src/main/res/values/strings.xml b/imagepicker/src/main/res/values/strings.xml deleted file mode 100644 index 17af1d8..0000000 --- a/imagepicker/src/main/res/values/strings.xml +++ /dev/null @@ -1,7 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<resources> - <string name="kau_no_images_found">No images found</string> - <string name="kau_no_images_selected">No images have been selected</string> - <string name="kau_blurrable_imageview">Blurrable ImageView</string> - <string name="kau_no_images_loaded">No images loaded</string> -</resources>
\ No newline at end of file diff --git a/kpref-activity/build.gradle b/kpref-activity/build.gradle index fd1c992..8375e91 100644 --- a/kpref-activity/build.gradle +++ b/kpref-activity/build.gradle @@ -5,7 +5,6 @@ ext.kauSubModuleResourcePrefix = "kau_pref_" apply from: '../android-lib.gradle' dependencies { - compile project(':core-ui') compile project(':adapter') compile project(':colorpicker') diff --git a/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/items/KPrefItemCore.kt b/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/items/KPrefItemCore.kt index bd245af..96f8903 100644 --- a/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/items/KPrefItemCore.kt +++ b/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/items/KPrefItemCore.kt @@ -49,7 +49,7 @@ abstract class KPrefItemCore(val core: CoreContract) : AbstractItem<KPrefItemCor val textColor = core.globalOptions.textColor?.invoke() if (textColor != null) { title.setTextColor(textColor) - desc?.setTextColor(textColor) + desc?.setTextColor(textColor.adjustAlpha(0.65f)) } val accentColor = core.globalOptions.accentColor?.invoke() if (accentColor != null && buildIsLollipopAndUp) { diff --git a/imagepicker/.gitignore b/mediapicker/.gitignore index 796b96d..796b96d 100644 --- a/imagepicker/.gitignore +++ b/mediapicker/.gitignore diff --git a/imagepicker/README.md b/mediapicker/README.md index f70de2b..a743a47 100644 --- a/imagepicker/README.md +++ b/mediapicker/README.md @@ -1,28 +1,30 @@ -# KAU :imagepicker +# KAU :mediapicker + +MediaPicker is a beautiful collection of gallery activities that allow you to pick images or videos +from your storage. It is backed by FastAdapter and Glide, and stems from the PickerCore model. -ImagePicker is a beautiful gallery activity that allows you to pick images -from your storage. It is backed by FastAdapter and Glide, and stems from the ImagePickerCore model Currently, there are two options: +Each takes in a MediaType argument, to specify whether it queries images or videos -------------------------------- -## ImagePickerActivityBase +## MediaPickerActivityBase -A full screen multi image picker with beautiful animations. -Images are blurred when selected, and there is a counter on the top right. +A full screen multi media picker with beautiful animations. +Items are blurred when selected, and there is a counter on the top right. There is a FAB to send back the response. -`R.style.Kau.ImagePicker` is added for your convenience. +`R.style.Kau.MediaPicker` is added for your convenience. -## ImagePickerActivityOverlayBase +## MediaPickerActivityOverlayBase This overlaying activity makes use of transitions and nested scrolling, and is only for Lollipop and up. -Only one image can be selected, so the overlay exists immediately upon the first selection. +Only one item can be selected, so the overlay exists immediately upon the first selection. Having this model also means that each item is only one simple image, as opposed to the blurrable image view above. As a result, this activity has faster loading on scrolling. -`R.style.Kau.ImagePicker.Overlay` is added for your convenience. +`R.style.Kau.MediaPicker.Overlay` is added for your convenience. -------------------------------- @@ -31,11 +33,11 @@ Their convenience styles default to a slide in slide out animation from the bott You may also easily launch either activity through the simple binder: ``` -Activity.kauLaunchImagePicker(YourClass::class.java, yourRequestCode) +Activity.kauLaunchMediaPicker(YourClass::class.java, yourRequestCode) ``` Note that this launches the activity through a `startActivityForResult` call You may get the activity response by overriding your `onActivityResult` method -to first verify that the request code matches and then call `kauOnImagePickerResult`, -which will return the list of ImageModels.
\ No newline at end of file +to first verify that the request code matches and then call `kauOnMediaPickerResult`, +which will return the list of MediaModels.
\ No newline at end of file diff --git a/imagepicker/build.gradle b/mediapicker/build.gradle index 93f52ac..813ec20 100644 --- a/imagepicker/build.gradle +++ b/mediapicker/build.gradle @@ -3,7 +3,6 @@ ext.kauSubModuleMinSdk = project.CORE_MIN_SDK apply from: '../android-lib.gradle' dependencies { - compile project(':core-ui') compile "com.github.bumptech.glide:glide:${GLIDE}" diff --git a/imagepicker/progress-proguard.txt b/mediapicker/progress-proguard.txt index 8b13789..8b13789 100644 --- a/imagepicker/progress-proguard.txt +++ b/mediapicker/progress-proguard.txt diff --git a/imagepicker/src/main/AndroidManifest.xml b/mediapicker/src/main/AndroidManifest.xml index 89dccc2..89dccc2 100644 --- a/imagepicker/src/main/AndroidManifest.xml +++ b/mediapicker/src/main/AndroidManifest.xml diff --git a/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/BlurredImageView.kt b/mediapicker/src/main/kotlin/ca/allanwang/kau/imagepicker/BlurredImageView.kt index 2ce00ba..0dcd7a2 100644 --- a/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/BlurredImageView.kt +++ b/mediapicker/src/main/kotlin/ca/allanwang/kau/imagepicker/BlurredImageView.kt @@ -2,7 +2,6 @@ package ca.allanwang.kau.imagepicker import android.content.Context import android.graphics.Color -import android.support.annotation.StyleRes import android.util.AttributeSet import android.view.View import android.widget.FrameLayout @@ -11,7 +10,6 @@ import ca.allanwang.kau.ui.views.MeasureSpecContract import ca.allanwang.kau.ui.views.MeasureSpecDelegate import ca.allanwang.kau.utils.* import com.mikepenz.google_material_typeface_library.GoogleMaterial -import com.mikepenz.iconics.IconicsDrawable import jp.wasabeef.blurry.internal.BlurFactor import jp.wasabeef.blurry.internal.BlurTask @@ -51,7 +49,7 @@ class BlurredImageView @JvmOverloads constructor( imageForeground.clearAnimation() } - private fun View.scaleAnimate(scale: Float) = animate().scaleX(scale).scaleY(scale).setDuration(ANIMATION_DURATION) + private fun View.scaleAnimate(scale: Float) = animate().scaleXY(scale).setDuration(ANIMATION_DURATION) private fun View.alphaAnimate(alpha: Float) = animate().alpha(alpha).setDuration(ANIMATION_DURATION) @@ -69,7 +67,7 @@ class BlurredImageView @JvmOverloads constructor( val factor = BlurFactor() factor.width = width factor.height = height - BlurTask(imageBase, factor) { + BlurTask(imageBase, factor) { imageBlur.setImageDrawable(it) scaleAnimate(ANIMATION_SCALE).start() imageBlur.alphaAnimate(1f).start() @@ -90,8 +88,7 @@ class BlurredImageView @JvmOverloads constructor( factor.height = height BlurTask(imageBase, factor) { drawable -> imageBlur.setImageDrawable(drawable) - scaleX = ANIMATION_SCALE - scaleY = ANIMATION_SCALE + scaleXY = ANIMATION_SCALE imageBlur.alpha = 1f imageForeground.alpha = 1f }.execute() diff --git a/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImageItem.kt b/mediapicker/src/main/kotlin/ca/allanwang/kau/imagepicker/MediaItem.kt index ffeb560..4b70638 100644 --- a/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImageItem.kt +++ b/mediapicker/src/main/kotlin/ca/allanwang/kau/imagepicker/MediaItem.kt @@ -15,13 +15,13 @@ import com.mikepenz.fastadapter.FastAdapter /** * Created by Allan Wang on 2017-07-04. */ -class ImageItem(val data: ImageModel) - : KauIItem<ImageItem, ImageItem.ViewHolder>(R.layout.kau_iitem_image, { ViewHolder(it) }) { +class MediaItem(val data: MediaModel) + : KauIItem<MediaItem, MediaItem.ViewHolder>(R.layout.kau_iitem_image, { ViewHolder(it) }) { private var failedToLoad = false companion object { - fun bindEvents(fastAdapter: FastAdapter<ImageItem>) { + fun bindEvents(fastAdapter: FastAdapter<MediaItem>) { fastAdapter.withMultiSelect(true) .withSelectable(true) //adapter selector occurs before the on click event @@ -43,7 +43,7 @@ class ImageItem(val data: ImageModel) .listener(object : RequestListener<Drawable> { override fun onLoadFailed(e: GlideException?, model: Any, target: Target<Drawable>, isFirstResource: Boolean): Boolean { failedToLoad = true - holder.container.imageBase.setImageDrawable(ImagePickerCore.getErrorDrawable(holder.itemView.context)) + holder.container.imageBase.setImageDrawable(MediaPickerCore.getErrorDrawable(holder.itemView.context)) return true; } diff --git a/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImageItemBasic.kt b/mediapicker/src/main/kotlin/ca/allanwang/kau/imagepicker/MediaItemBasic.kt index bc2a4e1..7a1ebf7 100644 --- a/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImageItemBasic.kt +++ b/mediapicker/src/main/kotlin/ca/allanwang/kau/imagepicker/MediaItemBasic.kt @@ -21,18 +21,18 @@ import com.mikepenz.fastadapter.FastAdapter /** * Created by Allan Wang on 2017-07-04. */ -class ImageItemBasic(val data: ImageModel) - : KauIItem<ImageItem, ImageItemBasic.ViewHolder>(R.layout.kau_iitem_image_basic, { ViewHolder(it) }) { +class MediaItemBasic(val data: MediaModel) + : KauIItem<MediaItem, MediaItemBasic.ViewHolder>(R.layout.kau_iitem_image_basic, { ViewHolder(it) }) { companion object { @SuppressLint("NewApi") - fun bindEvents(activity: Activity, fastAdapter: FastAdapter<ImageItemBasic>) { + fun bindEvents(activity: Activity, fastAdapter: FastAdapter<MediaItemBasic>) { fastAdapter.withSelectable(false) //add image data and return right away .withOnClickListener { _, _, item, _ -> val intent = Intent() val data = arrayListOf(item.data) - intent.putParcelableArrayListExtra(IMAGE_PICKER_RESULT, data) + intent.putParcelableArrayListExtra(MEDIA_PICKER_RESULT, data) activity.setResult(AppCompatActivity.RESULT_OK, intent) if (buildIsLollipopAndUp) activity.finishAfterTransition() else activity.finish() @@ -49,7 +49,7 @@ class ImageItemBasic(val data: ImageModel) .load(data.data) .listener(object : RequestListener<Drawable> { override fun onLoadFailed(e: GlideException?, model: Any, target: Target<Drawable>, isFirstResource: Boolean): Boolean { - holder.image.setImageDrawable(ImagePickerCore.getErrorDrawable(holder.itemView.context)) + holder.image.setImageDrawable(MediaPickerCore.getErrorDrawable(holder.itemView.context)) return true; } diff --git a/mediapicker/src/main/kotlin/ca/allanwang/kau/imagepicker/MediaModel.kt b/mediapicker/src/main/kotlin/ca/allanwang/kau/imagepicker/MediaModel.kt new file mode 100644 index 0000000..c384d48 --- /dev/null +++ b/mediapicker/src/main/kotlin/ca/allanwang/kau/imagepicker/MediaModel.kt @@ -0,0 +1,78 @@ +package ca.allanwang.kau.imagepicker + +import android.database.Cursor +import android.database.SQLException +import android.net.Uri +import android.os.Parcel +import android.os.Parcelable +import android.provider.MediaStore +import android.support.annotation.NonNull +import java.io.File + + +/** + * Created by Allan Wang on 2017-07-14. + */ + +data class MediaModel( + val data: String, val mimeType: String, val size: Long, val dateModified: Long, val displayName: String? +) : Parcelable { + + @Throws(SQLException::class) + constructor(@NonNull cursor: Cursor) : this( + cursor.getString(0), + cursor.getString(1), + cursor.getLong(2), + cursor.getLong(3), + cursor.getString(4) + ) + + constructor(parcel: Parcel) : this( + parcel.readString(), + parcel.readString(), + parcel.readLong(), + parcel.readLong(), + parcel.readString()) + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeString(this.data) + parcel.writeString(this.mimeType) + parcel.writeLong(this.size) + parcel.writeLong(this.dateModified) + parcel.writeString(this.displayName) + } + + val isGif + get() = mimeType.endsWith("gif") + + val isImage + get() = mimeType.endsWith("image") + + val isVideo + get() = mimeType.endsWith("video") + + val uri: Uri by lazy { Uri.fromFile(File(data)) } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator<MediaModel> { + val projection = arrayOf( + MediaStore.MediaColumns.DATA, + MediaStore.MediaColumns.MIME_TYPE, + MediaStore.MediaColumns.SIZE, + MediaStore.MediaColumns.DATE_MODIFIED, + MediaStore.MediaColumns.DISPLAY_NAME + ) + + override fun createFromParcel(parcel: Parcel): MediaModel { + return MediaModel(parcel) + } + + override fun newArray(size: Int): Array<MediaModel?> { + return arrayOfNulls(size) + } + } + +}
\ No newline at end of file diff --git a/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImagePickerActivityBase.kt b/mediapicker/src/main/kotlin/ca/allanwang/kau/imagepicker/MediaPickerActivityBase.kt index 3a9809c..ee68f42 100644 --- a/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImagePickerActivityBase.kt +++ b/mediapicker/src/main/kotlin/ca/allanwang/kau/imagepicker/MediaPickerActivityBase.kt @@ -19,9 +19,9 @@ import com.mikepenz.google_material_typeface_library.GoogleMaterial * * Base activity for selecting images from storage * Images are blurred when selected, and multiple images can be selected at a time. - * Having three layered images makes this slightly slower than [ImagePickerActivityOverlayBase] + * Having three layered images makes this slightly slower than [MediaPickerActivityOverlayBase] */ -abstract class ImagePickerActivityBase : ImagePickerCore<ImageItem>() { +abstract class MediaPickerActivityBase(mediaType: MediaType) : MediaPickerCore<MediaItem>(mediaType) { val coordinator: CoordinatorLayout by bindView(R.id.kau_coordinator) val toolbar: Toolbar by bindView(R.id.kau_toolbar) @@ -40,26 +40,26 @@ abstract class ImagePickerActivityBase : ImagePickerCore<ImageItem>() { supportActionBar?.apply { setDisplayHomeAsUpEnabled(true) setDisplayShowHomeEnabled(true) - setHomeAsUpIndicator(GoogleMaterial.Icon.gmd_close.toDrawable(this@ImagePickerActivityBase, 18)) + setHomeAsUpIndicator(GoogleMaterial.Icon.gmd_close.toDrawable(this@MediaPickerActivityBase, 18)) } toolbar.setNavigationOnClickListener { onBackPressed() } initializeRecycler(recycler) - ImageItem.bindEvents(imageAdapter) - imageAdapter.withSelectionListener({ _, _ -> selectionCount.text = imageAdapter.selections.size.toString() }) + MediaItem.bindEvents(adapter) + adapter.withSelectionListener({ _, _ -> selectionCount.text = adapter.selections.size.toString() }) fab.apply { show() setIcon(GoogleMaterial.Icon.gmd_send) setOnClickListener { - val selection = imageAdapter.selectedItems + val selection = adapter.selectedItems if (selection.isEmpty()) { - toast(R.string.kau_no_images_selected) + toast(R.string.kau_no_items_selected) } else { val intent = Intent() val data = ArrayList(selection.map { it.data }) - intent.putParcelableArrayListExtra(IMAGE_PICKER_RESULT, data) + intent.putParcelableArrayListExtra(MEDIA_PICKER_RESULT, data) setResult(RESULT_OK, intent) finish() } @@ -67,10 +67,10 @@ abstract class ImagePickerActivityBase : ImagePickerCore<ImageItem>() { hideOnDownwardsScroll(recycler) } - loadImages() + loadItems() } - override fun converter(model: ImageModel): ImageItem = ImageItem(model) + override fun converter(model: MediaModel): MediaItem = MediaItem(model) /** * Decide whether the toolbar can hide itself @@ -90,7 +90,7 @@ abstract class ImagePickerActivityBase : ImagePickerCore<ImageItem>() { override fun onLoadFinished(loader: Loader<Cursor>?, data: Cursor?) { super.onLoadFinished(loader, data) - setToolbarScrollable((recycler.layoutManager as LinearLayoutManager).findLastCompletelyVisibleItemPosition() < imageAdapter.getItemCount() - 1) + setToolbarScrollable((recycler.layoutManager as LinearLayoutManager).findLastCompletelyVisibleItemPosition() < adapter.getItemCount() - 1) } override fun onStatusChange(loaded: Boolean) { diff --git a/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImagePickerActivityOverlayBase.kt b/mediapicker/src/main/kotlin/ca/allanwang/kau/imagepicker/MediaPickerActivityOverlayBase.kt index ed8eee4..5161c08 100644 --- a/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImagePickerActivityOverlayBase.kt +++ b/mediapicker/src/main/kotlin/ca/allanwang/kau/imagepicker/MediaPickerActivityOverlayBase.kt @@ -13,11 +13,11 @@ import ca.allanwang.kau.utils.toast * * Base activity for selecting images from storage * This variant is an overlay and selects one image only before returning directly - * It is more efficient than [ImagePickerActivityBase], as all images are one layer deep + * It is more efficient than [MediaPickerActivityBase], as all images are one layer deep * as opposed to three layers deep */ @RequiresApi(Build.VERSION_CODES.LOLLIPOP) -abstract class ImagePickerActivityOverlayBase : ImagePickerCore<ImageItemBasic>() { +abstract class MediaPickerActivityOverlayBase(mediaType: MediaType) : MediaPickerCore<MediaItemBasic>(mediaType) { val draggable: ElasticDragDismissFrameLayout by bindView(R.id.kau_draggable) val recycler: RecyclerView by bindView(R.id.kau_recyclerview) @@ -26,12 +26,12 @@ abstract class ImagePickerActivityOverlayBase : ImagePickerCore<ImageItemBasic>( super.onCreate(savedInstanceState) setContentView(R.layout.kau_activity_image_picker_overlay) initializeRecycler(recycler) - ImageItemBasic.bindEvents(this, imageAdapter) + MediaItemBasic.bindEvents(this, adapter) draggable.addExitListener(this, R.transition.kau_image_exit_bottom, R.transition.kau_image_exit_top) draggable.setOnClickListener { finishAfterTransition() } - loadImages() + loadItems() } override fun finishAfterTransition() { @@ -40,10 +40,10 @@ abstract class ImagePickerActivityOverlayBase : ImagePickerCore<ImageItemBasic>( } override fun onStatusChange(loaded: Boolean) { - if (!loaded) toast(R.string.kau_no_images_loaded) + if (!loaded) toast(R.string.kau_no_items_loaded) } - override fun converter(model: ImageModel): ImageItemBasic = ImageItemBasic(model) + override fun converter(model: MediaModel): MediaItemBasic = MediaItemBasic(model) override fun onBackPressed() { finishAfterTransition() diff --git a/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImagePickerBinder.kt b/mediapicker/src/main/kotlin/ca/allanwang/kau/imagepicker/MediaPickerBinder.kt index db8d113..e423e84 100644 --- a/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImagePickerBinder.kt +++ b/mediapicker/src/main/kotlin/ca/allanwang/kau/imagepicker/MediaPickerBinder.kt @@ -14,9 +14,8 @@ import ca.allanwang.kau.utils.startActivityForResult /** * Image picker launchers */ -fun Activity.kauLaunchImagePicker(clazz: Class<out ImagePickerCore<*>>, requestCode: Int) { -// startActivityForResult(clazz, requestCode, true) - startActivityForResult(clazz, requestCode, transition = ImagePickerActivityOverlayBase::class.java.isAssignableFrom(clazz)) +fun Activity.kauLaunchMediaPicker(clazz: Class<out MediaPickerCore<*>>, requestCode: Int) { + startActivityForResult(clazz, requestCode, transition = MediaPickerActivityOverlayBase::class.java.isAssignableFrom(clazz)) } /** @@ -24,10 +23,10 @@ fun Activity.kauLaunchImagePicker(clazz: Class<out ImagePickerCore<*>>, requestC * call under [Activity.onActivityResult] * and make sure that the requestCode matches first */ -fun Activity.kauOnImagePickerResult(resultCode: Int, data: Intent?) = ImagePickerCore.onImagePickerResult(resultCode, data) +fun Activity.kauOnMediaPickerResult(resultCode: Int, data: Intent?) = MediaPickerCore.onMediaPickerResult(resultCode, data) internal const val LOADER_ID = 42 -internal const val IMAGE_PICKER_RESULT = "image_picker_result" +internal const val MEDIA_PICKER_RESULT = "media_picker_result" internal const val ANIMATION_DURATION = 200L internal const val ANIMATION_SCALE = 0.95f diff --git a/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImagePickerCore.kt b/mediapicker/src/main/kotlin/ca/allanwang/kau/imagepicker/MediaPickerCore.kt index f197a5e..4b922f9 100644 --- a/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImagePickerCore.kt +++ b/mediapicker/src/main/kotlin/ca/allanwang/kau/imagepicker/MediaPickerCore.kt @@ -28,9 +28,9 @@ import com.mikepenz.iconics.IconicsDrawable /** * Created by Allan Wang on 2017-07-23. * - * Container for the main logic behind the image pickers + * Container for the main logic behind the both pickers */ -abstract class ImagePickerCore<T : IItem<*, *>> : AppCompatActivity(), LoaderManager.LoaderCallbacks<Cursor> { +abstract class MediaPickerCore<T : IItem<*, *>>(val mediaType: MediaType) : AppCompatActivity(), LoaderManager.LoaderCallbacks<Cursor> { companion object { /** @@ -57,7 +57,7 @@ abstract class ImagePickerCore<T : IItem<*, *>> : AppCompatActivity(), LoaderMan * Create error tile for a given item */ fun getErrorDrawable(context: Context): Drawable { - val sizePx = ImagePickerCore.computeViewSize(context) + val sizePx = MediaPickerCore.computeViewSize(context) return IconicsDrawable(context, GoogleMaterial.Icon.gmd_error) .sizePx(sizePx) .backgroundColor(accentColor) @@ -68,29 +68,23 @@ abstract class ImagePickerCore<T : IItem<*, *>> : AppCompatActivity(), LoaderMan var accentColor: Int = 0xff666666.toInt() /** - * Helper method to retrieve the images from our iamge picker + * Helper method to retrieve the media from our media picker * This is used for both single and multiple photo picks */ - fun onImagePickerResult(resultCode: Int, data: Intent?): List<ImageModel> { - if (resultCode != Activity.RESULT_OK || data == null || !data.hasExtra(IMAGE_PICKER_RESULT)) + fun onMediaPickerResult(resultCode: Int, data: Intent?): List<MediaModel> { + if (resultCode != Activity.RESULT_OK || data == null || !data.hasExtra(MEDIA_PICKER_RESULT)) return emptyList() - return data.getParcelableArrayListExtra(IMAGE_PICKER_RESULT) + return data.getParcelableArrayListExtra(MEDIA_PICKER_RESULT) } /** - * Number of loaded images we should cache + * Number of loaded items we should cache * This is arbitrary */ const val CACHE_SIZE = 80 - - /** - * We know that Glide takes a while to initially fetch the images - * My as well make it look pretty - */ - const val INITIAL_LOAD_DELAY = 600L } - val imageAdapter: FastItemAdapter<T> = FastItemAdapter() + val adapter: FastItemAdapter<T> = FastItemAdapter() /** * Further improve preloading by extending the layout space @@ -101,32 +95,32 @@ abstract class ImagePickerCore<T : IItem<*, *>> : AppCompatActivity(), LoaderMan recycler.apply { val manager = object : GridLayoutManager(context, computeColumnCount(context)) { override fun getExtraLayoutSpace(state: RecyclerView.State?): Int { - return extraSpace + return if (mediaType != MediaType.VIDEO) extraSpace else super.getExtraLayoutSpace(state) } } setItemViewCacheSize(CACHE_SIZE) isDrawingCacheEnabled = true layoutManager = manager - adapter = imageAdapter + adapter = this@MediaPickerCore.adapter setHasFixedSize(true) - itemAnimator = object : KauAnimator(FadeScaleAnimatorAdd(0.8f)) { - override fun startDelay(holder: RecyclerView.ViewHolder, duration: Long, factor: Float): Long { - return super.startDelay(holder, duration, factor) + INITIAL_LOAD_DELAY - } - } + itemAnimator = KauAnimator(FadeScaleAnimatorAdd(0.8f)) } } //Sort by descending date - var sortQuery = MediaStore.Images.Media.DATE_MODIFIED + " DESC" + var sortQuery = MediaStore.MediaColumns.DATE_MODIFIED + " DESC" + + override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> { + return CursorLoader(this, mediaType.contentUri, MediaModel.projection, null, null, sortQuery) + } /** - * Request read permissions and load all external images + * Request read permissions and load all external items * The result will be filtered through {@link #onLoadFinished(Loader, Cursor)} * Call this to make sure that we request permissions each time * The adapter will be cleared on each successful call */ - open fun loadImages() { + open fun loadItems() { kauRequestPermissions(Manifest.permission.READ_EXTERNAL_STORAGE) { granted, _ -> if (granted) { @@ -139,35 +133,23 @@ abstract class ImagePickerCore<T : IItem<*, *>> : AppCompatActivity(), LoaderMan } } - override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> { - val columns = arrayOf( - MediaStore.Images.Media._ID, - MediaStore.Images.Media.TITLE, - MediaStore.Images.Media.DATA, - MediaStore.Images.Media.SIZE, - MediaStore.Images.Media.DISPLAY_NAME, - MediaStore.Images.Media.DATE_MODIFIED - ) - return CursorLoader(this, MediaStore.Images.Media.EXTERNAL_CONTENT_URI, columns, null, null, sortQuery) - } - override fun onLoadFinished(loader: Loader<Cursor>?, data: Cursor?) { reset() if (data == null || !data.moveToFirst()) { - toast(R.string.kau_no_images_found) + toast(R.string.kau_no_items_found) onStatusChange(false) return } val items = mutableListOf<T>() do { - val model = ImageModel(data) + val model = MediaModel(data) if (!shouldLoad(model)) continue items.add(converter(model)) } while (data.moveToNext()) addItems(items) } - abstract fun converter(model: ImageModel): T + abstract fun converter(model: MediaModel): T override fun onLoaderReset(loader: Loader<Cursor>?) = reset() @@ -176,24 +158,24 @@ abstract class ImagePickerCore<T : IItem<*, *>> : AppCompatActivity(), LoaderMan * when the adapter should add the items */ open fun addItems(items: List<T>) { - imageAdapter.add(items) + adapter.add(items) } /** * Clears the adapter to prepare for a new load */ open fun reset() { - imageAdapter.clear() + adapter.clear() } /** - * Optional filter to decide which images get displayed + * Optional filter to decide which items get displayed * Defaults to checking their sizes to filter out - * very small images such as lurking drawables/icons + * very small items such as lurking drawables/icons * * Returns true if model should be displayed, false otherwise */ - open fun shouldLoad(model: ImageModel): Boolean = model.size > 10000L + open fun shouldLoad(model: MediaModel): Boolean = model.size > 10000L open fun onStatusChange(loaded: Boolean) {} diff --git a/mediapicker/src/main/kotlin/ca/allanwang/kau/imagepicker/MediaType.kt b/mediapicker/src/main/kotlin/ca/allanwang/kau/imagepicker/MediaType.kt new file mode 100644 index 0000000..c934e04 --- /dev/null +++ b/mediapicker/src/main/kotlin/ca/allanwang/kau/imagepicker/MediaType.kt @@ -0,0 +1,13 @@ +package ca.allanwang.kau.imagepicker + +import android.net.Uri +import android.provider.MediaStore +import com.bumptech.glide.load.engine.DiskCacheStrategy + +/** + * Created by Allan Wang on 2017-07-30. + */ +enum class MediaType(val cacheStrategy: DiskCacheStrategy, val contentUri: Uri) { + IMAGE(DiskCacheStrategy.AUTOMATIC, MediaStore.Images.Media.EXTERNAL_CONTENT_URI), + VIDEO(DiskCacheStrategy.AUTOMATIC, MediaStore.Video.Media.EXTERNAL_CONTENT_URI) +}
\ No newline at end of file diff --git a/imagepicker/src/main/res-public/values-v21/styles.xml b/mediapicker/src/main/res-public/values-v21/styles.xml index bae68da..a5b9d96 100644 --- a/imagepicker/src/main/res-public/values-v21/styles.xml +++ b/mediapicker/src/main/res-public/values-v21/styles.xml @@ -1,6 +1,6 @@ <resources> - <style name="Kau.ImagePicker.Overlay" parent="Kau.Translucent"> + <style name="Kau.MediaPicker.Overlay" parent="Kau.Translucent"> <item name="android:statusBarColor">@android:color/transparent</item> <item name="android:windowEnterTransition">@transition/kau_image_enter</item> <item name="android:windowReturnTransition">@transition/kau_image_exit_bottom</item> diff --git a/imagepicker/src/main/res-public/values/colors.xml b/mediapicker/src/main/res-public/values/colors.xml index ebaa3f7..ebaa3f7 100644 --- a/imagepicker/src/main/res-public/values/colors.xml +++ b/mediapicker/src/main/res-public/values/colors.xml diff --git a/imagepicker/src/main/res-public/values/dimens.xml b/mediapicker/src/main/res-public/values/dimens.xml index 3ff2dd7..3ff2dd7 100644 --- a/imagepicker/src/main/res-public/values/dimens.xml +++ b/mediapicker/src/main/res-public/values/dimens.xml diff --git a/imagepicker/src/main/res-public/values/public.xml b/mediapicker/src/main/res-public/values/public.xml index a892651..ac608bb 100644 --- a/imagepicker/src/main/res-public/values/public.xml +++ b/mediapicker/src/main/res-public/values/public.xml @@ -2,6 +2,6 @@ <!-- AUTO-GENERATED FILE. DO NOT MODIFY. public.xml is generated by the generatepublicxml gradle task --> <public name='kau_blurred_image_selection_overlay' type='color' /> <public name='kau_image_minimum_size' type='dimen' /> - <public name='Kau.ImagePicker' type='style' /> - <public name='Kau.ImagePicker.Overlay' type='style' /> + <public name='Kau.MediaPicker' type='style' /> + <public name='Kau.MediaPicker.Overlay' type='style' /> </resources>
\ No newline at end of file diff --git a/imagepicker/src/main/res-public/values/styles.xml b/mediapicker/src/main/res-public/values/styles.xml index 99f294a..77bf2bd 100644 --- a/imagepicker/src/main/res-public/values/styles.xml +++ b/mediapicker/src/main/res-public/values/styles.xml @@ -1,11 +1,11 @@ <resources> - <style name="Kau.ImagePicker"> + <style name="Kau.MediaPicker"> <item name="android:windowAnimationStyle">@style/KauSlideInSlideOutBottom</item> </style> <!--Just as a placeholder for public.xml--> - <style name="Kau.ImagePicker.Overlay" parent="Kau.Translucent"> + <style name="Kau.MediaPicker.Overlay" parent="Kau.Translucent"> <item name="android:windowAnimationStyle">@null</item> </style> diff --git a/imagepicker/src/main/res/layout-v21/kau_activity_image_picker_overlay.xml b/mediapicker/src/main/res/layout-v21/kau_activity_image_picker_overlay.xml index a0ce301..a0ce301 100644 --- a/imagepicker/src/main/res/layout-v21/kau_activity_image_picker_overlay.xml +++ b/mediapicker/src/main/res/layout-v21/kau_activity_image_picker_overlay.xml diff --git a/imagepicker/src/main/res/layout/kau_activity_image_picker.xml b/mediapicker/src/main/res/layout/kau_activity_image_picker.xml index 6d991b0..6d991b0 100644 --- a/imagepicker/src/main/res/layout/kau_activity_image_picker.xml +++ b/mediapicker/src/main/res/layout/kau_activity_image_picker.xml diff --git a/imagepicker/src/main/res/layout/kau_blurred_imageview.xml b/mediapicker/src/main/res/layout/kau_blurred_imageview.xml index e28cb9a..e28cb9a 100644 --- a/imagepicker/src/main/res/layout/kau_blurred_imageview.xml +++ b/mediapicker/src/main/res/layout/kau_blurred_imageview.xml diff --git a/imagepicker/src/main/res/layout/kau_iitem_image.xml b/mediapicker/src/main/res/layout/kau_iitem_image.xml index 9d51d77..9d51d77 100644 --- a/imagepicker/src/main/res/layout/kau_iitem_image.xml +++ b/mediapicker/src/main/res/layout/kau_iitem_image.xml diff --git a/imagepicker/src/main/res/layout/kau_iitem_image_basic.xml b/mediapicker/src/main/res/layout/kau_iitem_image_basic.xml index b89e41d..b89e41d 100644 --- a/imagepicker/src/main/res/layout/kau_iitem_image_basic.xml +++ b/mediapicker/src/main/res/layout/kau_iitem_image_basic.xml diff --git a/imagepicker/src/main/res/transition-v21/kau_image_enter.xml b/mediapicker/src/main/res/transition-v21/kau_image_enter.xml index 447c0c9..447c0c9 100644 --- a/imagepicker/src/main/res/transition-v21/kau_image_enter.xml +++ b/mediapicker/src/main/res/transition-v21/kau_image_enter.xml diff --git a/imagepicker/src/main/res/transition-v21/kau_image_exit_bottom.xml b/mediapicker/src/main/res/transition-v21/kau_image_exit_bottom.xml index 447c0c9..447c0c9 100644 --- a/imagepicker/src/main/res/transition-v21/kau_image_exit_bottom.xml +++ b/mediapicker/src/main/res/transition-v21/kau_image_exit_bottom.xml diff --git a/imagepicker/src/main/res/transition-v21/kau_image_exit_top.xml b/mediapicker/src/main/res/transition-v21/kau_image_exit_top.xml index 8d64f48..8d64f48 100644 --- a/imagepicker/src/main/res/transition-v21/kau_image_exit_top.xml +++ b/mediapicker/src/main/res/transition-v21/kau_image_exit_top.xml diff --git a/mediapicker/src/main/res/values/strings.xml b/mediapicker/src/main/res/values/strings.xml new file mode 100644 index 0000000..39ab16b --- /dev/null +++ b/mediapicker/src/main/res/values/strings.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <string name="kau_no_items_found">No items found</string> + <string name="kau_no_items_selected">No items have been selected</string> + <string name="kau_blurrable_imageview">Blurrable ImageView</string> + <string name="kau_no_items_loaded">No items loaded</string> +</resources>
\ No newline at end of file diff --git a/sample/build.gradle b/sample/build.gradle index 3350b3b..d20a972 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -84,7 +84,7 @@ dependencies { compile project(':core-ui') compile project(':kpref-activity') compile project(':searchview') - compile project(':imagepicker') + compile project(':mediapicker') androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index 85fb199..a3bbf45 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -30,10 +30,16 @@ android:theme="@style/Kau.About" /> <activity android:name=".ImagePickerActivity" - android:theme="@style/Kau.ImagePicker" /> + android:theme="@style/Kau.MediaPicker" /> <activity android:name=".ImagePickerActivityOverlay" - android:theme="@style/Kau.ImagePicker.Overlay" /> + android:theme="@style/Kau.MediaPicker.Overlay" /> + <activity + android:name=".VideoPickerActivity" + android:theme="@style/Kau.MediaPicker" /> + <activity + android:name=".VideoPickerActivityOverlay" + android:theme="@style/Kau.MediaPicker.Overlay" /> <activity android:name=".AdapterActivity" android:theme="@style/Kau.Translucent.SlideBottom" /> diff --git a/sample/src/main/kotlin/ca/allanwang/kau/sample/ImagePicker.kt b/sample/src/main/kotlin/ca/allanwang/kau/sample/ImagePicker.kt index c7f28bc..eebc5ca 100644 --- a/sample/src/main/kotlin/ca/allanwang/kau/sample/ImagePicker.kt +++ b/sample/src/main/kotlin/ca/allanwang/kau/sample/ImagePicker.kt @@ -1,11 +1,16 @@ package ca.allanwang.kau.sample -import ca.allanwang.kau.imagepicker.ImagePickerActivityBase -import ca.allanwang.kau.imagepicker.ImagePickerActivityOverlayBase +import ca.allanwang.kau.imagepicker.MediaPickerActivityBase +import ca.allanwang.kau.imagepicker.MediaPickerActivityOverlayBase +import ca.allanwang.kau.imagepicker.MediaType /** * Created by Allan Wang on 2017-07-23. */ -class ImagePickerActivity : ImagePickerActivityBase() +class ImagePickerActivity : MediaPickerActivityBase(MediaType.IMAGE) -class ImagePickerActivityOverlay : ImagePickerActivityOverlayBase()
\ No newline at end of file +class ImagePickerActivityOverlay : MediaPickerActivityOverlayBase(MediaType.IMAGE) + +class VideoPickerActivity : MediaPickerActivityBase(MediaType.VIDEO) + +class VideoPickerActivityOverlay : MediaPickerActivityOverlayBase(MediaType.VIDEO)
\ No newline at end of file diff --git a/sample/src/main/kotlin/ca/allanwang/kau/sample/MainActivity.kt b/sample/src/main/kotlin/ca/allanwang/kau/sample/MainActivity.kt index 7ce10af..0cc41fb 100644 --- a/sample/src/main/kotlin/ca/allanwang/kau/sample/MainActivity.kt +++ b/sample/src/main/kotlin/ca/allanwang/kau/sample/MainActivity.kt @@ -2,13 +2,12 @@ package ca.allanwang.kau.sample import android.content.Intent import android.os.Bundle -import android.os.PersistableBundle import android.view.Menu import android.view.MenuItem import ca.allanwang.kau.about.kauLaunchAbout import ca.allanwang.kau.email.sendEmail -import ca.allanwang.kau.imagepicker.kauLaunchImagePicker -import ca.allanwang.kau.imagepicker.kauOnImagePickerResult +import ca.allanwang.kau.imagepicker.kauLaunchMediaPicker +import ca.allanwang.kau.imagepicker.kauOnMediaPickerResult import ca.allanwang.kau.kpref.activity.CoreAttributeContract import ca.allanwang.kau.kpref.activity.KPrefActivity import ca.allanwang.kau.kpref.activity.KPrefAdapterBuilder @@ -78,7 +77,7 @@ class MainActivity : KPrefActivity() { "fill", "table", "east", "travel", "weight", "less", "language", "morning", "among") } - const val REQUEST_IMAGE = 27 + const val REQUEST_MEDIA = 27 } override fun kPrefCoreAttributes(): CoreAttributeContract.() -> Unit = { @@ -164,12 +163,12 @@ class MainActivity : KPrefActivity() { descRes = R.string.sub_item_desc } - plainText(R.string.gallery_showcase) { - onClick = { _, _, _ -> kauLaunchImagePicker(ImagePickerActivity::class.java, REQUEST_IMAGE); false } + plainText(R.string.image_showcase) { + onClick = { _, _, _ -> kauLaunchMediaPicker(ImagePickerActivity::class.java, REQUEST_MEDIA); false } } - plainText(R.string.gallery_overlay_showcase) { - onClick = { _, _, _ -> kauLaunchImagePicker(ImagePickerActivityOverlay::class.java, REQUEST_IMAGE); false } + plainText(R.string.video_overlay_showcase) { + onClick = { _, _, _ -> kauLaunchMediaPicker(VideoPickerActivityOverlay::class.java, REQUEST_MEDIA); false } } plainText(R.string.adapter_showcase) { @@ -251,7 +250,7 @@ class MainActivity : KPrefActivity() { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) when (requestCode) { - REQUEST_IMAGE -> toast("${kauOnImagePickerResult(resultCode, data).size} images selected") + REQUEST_MEDIA -> toast("${kauOnMediaPickerResult(resultCode, data).size} items selected") } } } diff --git a/sample/src/main/res/values/strings.xml b/sample/src/main/res/values/strings.xml index c64b623..11a897a 100644 --- a/sample/src/main/res/values/strings.xml +++ b/sample/src/main/res/values/strings.xml @@ -19,8 +19,8 @@ <string name="sub_item_desc">Press this to view the next subset of preferences</string> <string name="your_email">your.email@here.com</string> <string name="your_subject">Your subject</string> - <string name="gallery_showcase">Gallery Showcase</string> - <string name="gallery_overlay_showcase">Gallery Overlay Showcase</string> + <string name="image_showcase">Image Showcase</string> + <string name="video_overlay_showcase">Video Overlay Showcase</string> <string name="adapter_showcase">Adapter Showcase</string> <string name="about_kau">KAU (Kotlin Android Utils) is a collection of common extension functions and complex UIs that can be used in almost all apps. It is meant to implement the shared components, so you can focus on what makes your app unique.</string> </resources> diff --git a/sample/src/main/res/xml/kau_changelog.xml b/sample/src/main/res/xml/kau_changelog.xml index cfb8bd1..b7bb8e4 100644 --- a/sample/src/main/res/xml/kau_changelog.xml +++ b/sample/src/main/res/xml/kau_changelog.xml @@ -6,6 +6,24 @@ <item text="" /> --> + <version title="v3.2.1"/> + <item text=":core: Remove requestLayout call from setMargin and setPadding" /> + <item text=":core: Fix kau direction bits" /> + <item text=":core: Greatly simplify ripple canvas and truly support transparent ripples" /> + <item text=":core: Generalize fab transition to fade scale transition for all imageviews" /> + <item text=":core: Create ViewPropertyAnimator.scaleXY() and View.scaleXY" /> + <item text=":core: Create View.setOnSingleTapListener()" /> + <item text=":core: Create rndColor, which generates a random opaque color for testing" /> + <item text=":core: Add resettable view binding variants to Kotterknife" /> + <item text=":core: Create lazy resettable registry" /> + <item text=":core: Add more transitions and anims" /> + <item text=":kpref-activity: Reduce alpha color for desc" /> + <item text=":imagepicker: [breaking] Rename to mediapicker and add support for videos" /> + <item text="" /> + <item text="" /> + <item text="" /> + <item text="" /> + <version title="v3.2.0"/> <item text=":adapter: Make KauAnimator extensible" /> <item text=":imagepicker: Add uri val to ImageModel" /> diff --git a/searchview/build.gradle b/searchview/build.gradle index e503ce4..b76a61a 100644 --- a/searchview/build.gradle +++ b/searchview/build.gradle @@ -3,7 +3,6 @@ ext.kauSubModuleResourcePrefix = "kau_search_" apply from: '../android-lib.gradle' dependencies { - compile project(':core-ui') compile project(':adapter') diff --git a/searchview/src/main/kotlin/ca/allanwang/kau/searchview/SearchView.kt b/searchview/src/main/kotlin/ca/allanwang/kau/searchview/SearchView.kt index 30e224e..4058f16 100644 --- a/searchview/src/main/kotlin/ca/allanwang/kau/searchview/SearchView.kt +++ b/searchview/src/main/kotlin/ca/allanwang/kau/searchview/SearchView.kt @@ -16,7 +16,6 @@ import android.view.* import android.widget.FrameLayout import android.widget.ImageView import android.widget.ProgressBar -import ca.allanwang.kau.animators.NoAnimator import ca.allanwang.kau.kotlin.nonReadable import ca.allanwang.kau.searchview.SearchView.Configs import ca.allanwang.kau.ui.views.BoundedCardView @@ -286,7 +285,7 @@ class SearchView @JvmOverloads constructor( fun bind(menu: Menu, @IdRes id: Int, @ColorInt menuIconColor: Int = Color.WHITE, config: Configs.() -> Unit = {}): SearchView { config(config) configs.textObserver(textEvents.filter { it.isNotBlank() }, this) - menuItem = menu.findItem(id) + menuItem = menu.findItem(id) ?: throw IllegalArgumentException("Menu item with given id doesn't exist") if (menuItem!!.icon == null) menuItem!!.icon = GoogleMaterial.Icon.gmd_search.toDrawable(context, 18, menuIconColor) card.gone() menuItem!!.setOnMenuItemClickListener { configureCoords(it); revealOpen(); true } @@ -294,9 +293,11 @@ class SearchView @JvmOverloads constructor( return this } - fun unBind(replacementMenuItemClickListener: MenuItem.OnMenuItemClickListener? = null) { + fun unBind(replacementMenuItemClickListener: ((item: MenuItem) -> Boolean)? = null) { parentViewGroup.removeView(this) - menuItem?.setOnMenuItemClickListener(replacementMenuItemClickListener) + if (replacementMenuItemClickListener != null) + menuItem?.setOnMenuItemClickListener(replacementMenuItemClickListener) + menuItem = null } fun configureCoords(item: MenuItem) { @@ -309,11 +310,7 @@ class SearchView @JvmOverloads constructor( card.viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener { override fun onPreDraw(): Boolean { view.viewTreeObserver.removeOnPreDrawListener(this) - val topAlignment = menuY - card.height / 2 - val params = (card.layoutParams as MarginLayoutParams).apply { - topMargin = topAlignment - } - card.layoutParams = params + card.setMarginTop(menuY - card.height / 2) return false } }) @@ -376,7 +373,6 @@ class SearchView @JvmOverloads constructor( onFinish = { configs.closeListener?.invoke(this@SearchView) if (configs.shouldClearOnClose) editText.text.clear() - recycler.gone() }) } } diff --git a/settings.gradle b/settings.gradle index 3564f5c..1a883b6 100644 --- a/settings.gradle +++ b/settings.gradle @@ -5,6 +5,6 @@ include ':core', ':adapter', ':colorpicker', ':core-ui', - ':imagepicker', + ':mediapicker', ':kpref-activity', ':searchview' |