From 8f2b5ac043f47cc44f43c3788d1377083fb339a2 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Tue, 18 Jul 2017 20:16:23 -0700 Subject: Dev 2.1 (#8) * Rewrite animation interfaces * Update changelog * Add scale factor for slide * Remove margins in iitems and replace with decorators * Remove mutable list * Switch cardiitem to use lambdas for click * status * Utils update and imagepicker fixes * Remove stringholder * Add fade in fade out * Increment about version * Rename fromedge to direction in javadocs * More logging * Add logging and docs * Make card icons visible * Update email builder and icon padding * Create elastic recycler activity * Fix card iitem * Add lint check and plurals * Inline all the things * Format and sort xml * Update dependencies and increment version --- .travis.yml | 2 +- README.md | 2 + about/build.gradle | 2 +- .../ca/allanwang/kau/about/AboutActivityBase.kt | 22 ++- .../kotlin/ca/allanwang/kau/about/LibraryIItem.kt | 1 + about/src/main/res/layout/kau_iitem_library.xml | 1 - .../res/transition/kau_about_return_downward.xml | 49 ------ .../res/transition/kau_about_return_upwards.xml | 50 ------- about/src/main/res/values/strings_about.xml | 2 +- about/src/main/res/values/styles.xml | 5 +- .../kau/adapters/FastItemThemedAdapter.kt | 15 +- .../allanwang/kau/animators/AnimatorInterfaces.kt | 31 ++++ .../allanwang/kau/animators/BaseDelayAnimator.kt | 45 ------ .../kau/animators/BaseSlideAlphaAnimator.kt | 52 ------- .../allanwang/kau/animators/FadeScaleAnimator.kt | 76 +++++----- .../ca/allanwang/kau/animators/KauAnimator.kt | 76 ++++++++++ .../ca/allanwang/kau/animators/SlideAnimator.kt | 64 ++++++++ .../kau/animators/SlideUpExitRightAnimator.kt | 23 --- .../kotlin/ca/allanwang/kau/iitems/CardIItem.kt | 41 ++--- adapter/src/main/res/layout/kau_iitem_card.xml | 33 ++-- adapter/src/main/res/layout/kau_iitem_header.xml | 1 - android-lib.gradle | 10 +- build.gradle | 2 +- .../ca/allanwang/kau/colorpicker/CircleView.kt | 7 +- .../kau/ui/activities/ElasticRecyclerActivity.kt | 81 ++++++++++ .../ca/allanwang/kau/ui/views/BoundedCardView.kt | 8 +- .../kotlin/ca/allanwang/kau/ui/views/CutoutView.kt | 6 +- .../ca/allanwang/kau/ui/views/MeasuredImageView.kt | 23 +++ .../src/main/res/drawable/kau_selectable_white.xml | 6 + .../res/layout/kau_elastic_recycler_activity.xml | 51 +++++++ core-ui/src/main/res/values/attr.xml | 58 ++++--- core-ui/src/main/res/values/colors.xml | 4 +- core-ui/src/main/res/values/strings.xml | 4 + core-ui/src/main/res/values/styles.xml | 16 +- core/README.md | 17 +++ .../kotlin/ca/allanwang/kau/email/EmailBuilder.kt | 6 +- .../allanwang/kau/permissions/PermissionManager.kt | 5 +- .../ca/allanwang/kau/permissions/Permissions.kt | 6 + .../allanwang/kau/ui/views/MeasureSpecDelegate.kt | 107 +++++++++++++ .../kotlin/ca/allanwang/kau/utils/ActivityUtils.kt | 6 +- .../kotlin/ca/allanwang/kau/utils/ColorUtils.kt | 2 +- .../kotlin/ca/allanwang/kau/utils/ContextUtils.kt | 23 +-- .../main/kotlin/ca/allanwang/kau/utils/Either.kt | 32 ---- .../kotlin/ca/allanwang/kau/utils/Kotterknife.kt | 16 +- .../kotlin/ca/allanwang/kau/utils/PackageUtils.kt | 18 ++- .../kotlin/ca/allanwang/kau/utils/RecyclerUtils.kt | 25 ++++ .../kotlin/ca/allanwang/kau/utils/StringHolder.kt | 22 --- .../main/kotlin/ca/allanwang/kau/utils/Utils.kt | 16 +- .../kotlin/ca/allanwang/kau/utils/ViewUtils.kt | 61 ++++++-- .../main/res/transition/kau_enter_slide_bottom.xml | 16 -- .../main/res/transition/kau_enter_slide_top.xml | 16 -- .../main/res/transition/kau_exit_slide_bottom.xml | 23 +++ .../src/main/res/transition/kau_exit_slide_top.xml | 23 +++ core/src/main/res/values/attr.xml | 11 ++ core/src/main/res/values/dimens.xml | 1 + core/src/main/res/values/ids.xml | 36 ++--- core/src/main/res/values/strings.xml | 14 +- core/src/main/res/values/strings_commons.xml | 27 +++- core/src/main/res/values/styles_animations.xml | 35 +++-- core/src/main/res/xml/kau_changelog.xml | 14 -- docs/Changelog.md | 12 ++ gradle.properties | 19 +-- imagepicker/build.gradle | 1 + .../allanwang/kau/imagepicker/BlurredImageView.kt | 166 +++++++++++++++++++++ .../ca/allanwang/kau/imagepicker/ImageItem.kt | 79 +++++++++- .../ca/allanwang/kau/imagepicker/ImageModel.kt | 21 +++ .../kau/imagepicker/ImagePickerActivityBase.kt | 80 +++++++--- .../src/main/res/layout/kau_blurred_imageview.xml | 32 ++++ .../src/main/res/layout/kau_iitem_image.xml | 18 +-- imagepicker/src/main/res/values/colors.xml | 6 + imagepicker/src/main/res/values/dimens.xml | 6 + imagepicker/src/main/res/values/styles.xml | 5 +- kpref-activity/src/main/res/values/ids.xml | 36 ++--- sample/src/main/AndroidManifest.xml | 3 + .../ca/allanwang/kau/sample/AdapterActivity.kt | 51 +++++++ .../kotlin/ca/allanwang/kau/sample/MainActivity.kt | 18 ++- sample/src/main/res/values/strings.xml | 2 + sample/src/main/res/xml/changelog.xml | 68 --------- sample/src/main/res/xml/kau_changelog.xml | 75 ++++++++++ searchview/src/main/res/values/dimens.xml | 34 ++--- searchview/src/main/res/values/ids.xml | 30 ++-- 81 files changed, 1462 insertions(+), 747 deletions(-) delete mode 100644 about/src/main/res/transition/kau_about_return_downward.xml delete mode 100644 about/src/main/res/transition/kau_about_return_upwards.xml create mode 100644 adapter/src/main/kotlin/ca/allanwang/kau/animators/AnimatorInterfaces.kt delete mode 100644 adapter/src/main/kotlin/ca/allanwang/kau/animators/BaseDelayAnimator.kt delete mode 100644 adapter/src/main/kotlin/ca/allanwang/kau/animators/BaseSlideAlphaAnimator.kt create mode 100644 adapter/src/main/kotlin/ca/allanwang/kau/animators/KauAnimator.kt create mode 100644 adapter/src/main/kotlin/ca/allanwang/kau/animators/SlideAnimator.kt delete mode 100644 adapter/src/main/kotlin/ca/allanwang/kau/animators/SlideUpExitRightAnimator.kt create mode 100644 core-ui/src/main/kotlin/ca/allanwang/kau/ui/activities/ElasticRecyclerActivity.kt create mode 100644 core-ui/src/main/kotlin/ca/allanwang/kau/ui/views/MeasuredImageView.kt create mode 100644 core-ui/src/main/res/drawable/kau_selectable_white.xml create mode 100644 core-ui/src/main/res/layout/kau_elastic_recycler_activity.xml create mode 100644 core-ui/src/main/res/values/strings.xml create mode 100644 core/src/main/kotlin/ca/allanwang/kau/ui/views/MeasureSpecDelegate.kt delete mode 100644 core/src/main/kotlin/ca/allanwang/kau/utils/Either.kt create mode 100644 core/src/main/kotlin/ca/allanwang/kau/utils/RecyclerUtils.kt delete mode 100644 core/src/main/kotlin/ca/allanwang/kau/utils/StringHolder.kt create mode 100644 core/src/main/res/transition/kau_exit_slide_bottom.xml create mode 100644 core/src/main/res/transition/kau_exit_slide_top.xml create mode 100644 core/src/main/res/values/attr.xml delete mode 100644 core/src/main/res/xml/kau_changelog.xml create mode 100644 imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/BlurredImageView.kt create mode 100644 imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImageModel.kt create mode 100644 imagepicker/src/main/res/layout/kau_blurred_imageview.xml create mode 100644 imagepicker/src/main/res/values/colors.xml create mode 100644 imagepicker/src/main/res/values/dimens.xml create mode 100644 sample/src/main/kotlin/ca/allanwang/kau/sample/AdapterActivity.kt delete mode 100644 sample/src/main/res/xml/changelog.xml create mode 100644 sample/src/main/res/xml/kau_changelog.xml diff --git a/.travis.yml b/.travis.yml index 7e0a791..fb97b18 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,7 @@ android: - extra-google-m2repository licenses: - '.+' -script: ./gradlew clean test +script: ./gradlew lintRelease test branches: except: - gh-pages diff --git a/README.md b/README.md index 946deaa..e4e71f4 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,9 @@ To apply, add the following to your root build.gradle: allprojects { repositories { ... + jcenter() maven { url "https://jitpack.io" } + maven { url "https://maven.google.com" } } } ``` diff --git a/about/build.gradle b/about/build.gradle index bb51f9f..6898217 100644 --- a/about/build.gradle +++ b/about/build.gradle @@ -9,7 +9,7 @@ dependencies { compile project(':adapter') compile("com.mikepenz:aboutlibraries:${ABOUT_LIBRARIES}@aar") { - transitive = true + transitive = false } } diff --git a/about/src/main/kotlin/ca/allanwang/kau/about/AboutActivityBase.kt b/about/src/main/kotlin/ca/allanwang/kau/about/AboutActivityBase.kt index 90dbfd3..b3e3e41 100644 --- a/about/src/main/kotlin/ca/allanwang/kau/about/AboutActivityBase.kt +++ b/about/src/main/kotlin/ca/allanwang/kau/about/AboutActivityBase.kt @@ -13,7 +13,8 @@ import android.view.ViewGroup import ca.allanwang.kau.adapters.FastItemThemedAdapter import ca.allanwang.kau.adapters.ThemableIItemColors import ca.allanwang.kau.adapters.ThemableIItemColorsDelegate -import ca.allanwang.kau.animators.FadeScaleAnimator +import ca.allanwang.kau.animators.FadeScaleAnimatorAdd +import ca.allanwang.kau.animators.KauAnimator import ca.allanwang.kau.iitems.HeaderIItem import ca.allanwang.kau.ui.widgets.ElasticDragDismissFrameLayout import ca.allanwang.kau.ui.widgets.InkPageIndicator @@ -94,12 +95,9 @@ abstract class AboutActivityBase(val rClass: Class<*>?, val configBuilder: Confi indicator.setViewPager(pager) draggableFrame.addListener(object : ElasticDragDismissFrameLayout.SystemChromeFader(this) { override fun onDragDismissed() { - // if we drag dismiss downward then the default reversal of the enter - // transition would slide content upward which looks weird. So reverse it. - if (draggableFrame.translationY > 0) { - window.returnTransition = TransitionInflater.from(this@AboutActivityBase) - .inflateTransition(configs.transitionExitReversed) - } + window.returnTransition = TransitionInflater.from(this@AboutActivityBase) + .inflateTransition(if (draggableFrame.translationY > 0) configs.transitionExitBottom else configs.transitionExitTop) + libRecycler?.stopScroll() finishAfterTransition() } @@ -114,10 +112,9 @@ abstract class AboutActivityBase(val rClass: Class<*>?, val configBuilder: Confi var cutoutForeground: Int? = null var libPageTitleRes: Int = -1 var libPageTitle: String? = string(R.string.kau_about_libraries_intro) //This is in the string by default since it's lower priority - /** - * Transition to be called if the view is dragged down - */ - var transitionExitReversed: Int = R.transition.kau_about_return_downward + + var transitionExitTop: Int = R.transition.kau_exit_slide_top + var transitionExitBottom: Int = R.transition.kau_exit_slide_bottom } /** @@ -174,8 +171,9 @@ abstract class AboutActivityBase(val rClass: Class<*>?, val configBuilder: Confi val v = layoutInflater.inflate(R.layout.kau_recycler_detached_background, parent, false) val recycler = v.findViewById(R.id.kau_recycler_detached) libRecycler = recycler + recycler.withMarginDecoration(16, KAU_BOTTOM) recycler.adapter = libAdapter - recycler.itemAnimator = FadeScaleAnimator(itemDelayFactor = 0.2f).apply { addDuration = 300; interpolator = AnimHolder.decelerateInterpolator(this@AboutActivityBase) } + recycler.itemAnimator = KauAnimator(addAnimator = FadeScaleAnimatorAdd(scaleFactor = 0.7f, itemDelayFactor = 0.2f)).apply { addDuration = 300; interpolator = AnimHolder.decelerateInterpolator(this@AboutActivityBase) } val background = v.findViewById(R.id.kau_recycler_detached_background) if (configs.backgroundColor != null) background.setBackgroundColor(configs.backgroundColor!!.colorToForeground()) doAsync { diff --git a/about/src/main/kotlin/ca/allanwang/kau/about/LibraryIItem.kt b/about/src/main/kotlin/ca/allanwang/kau/about/LibraryIItem.kt index b5e2c28..518cd54 100644 --- a/about/src/main/kotlin/ca/allanwang/kau/about/LibraryIItem.kt +++ b/about/src/main/kotlin/ca/allanwang/kau/about/LibraryIItem.kt @@ -49,6 +49,7 @@ class LibraryIItem(val lib: Library with(holder) { name.text = lib.libraryName creator.text = lib.author + @Suppress("DEPRECATION") description.text = if (lib.libraryDescription.isBlank()) lib.libraryDescription else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) Html.fromHtml(lib.libraryDescription, Html.FROM_HTML_MODE_LEGACY) diff --git a/about/src/main/res/layout/kau_iitem_library.xml b/about/src/main/res/layout/kau_iitem_library.xml index 1c3de5c..3ad4fb4 100644 --- a/about/src/main/res/layout/kau_iitem_library.xml +++ b/about/src/main/res/layout/kau_iitem_library.xml @@ -5,7 +5,6 @@ android:id="@+id/lib_item_card" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginBottom="@dimen/kau_padding_normal" android:background="?android:selectableItemBackground" android:clickable="true"> diff --git a/about/src/main/res/transition/kau_about_return_downward.xml b/about/src/main/res/transition/kau_about_return_downward.xml deleted file mode 100644 index b040b1b..0000000 --- a/about/src/main/res/transition/kau_about_return_downward.xml +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/about/src/main/res/transition/kau_about_return_upwards.xml b/about/src/main/res/transition/kau_about_return_upwards.xml deleted file mode 100644 index 64b3f5e..0000000 --- a/about/src/main/res/transition/kau_about_return_upwards.xml +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/about/src/main/res/values/strings_about.xml b/about/src/main/res/values/strings_about.xml index 35ffeb2..f538e59 100644 --- a/about/src/main/res/values/strings_about.xml +++ b/about/src/main/res/values/strings_about.xml @@ -1,5 +1,5 @@ - 1.5 + 2.1 diff --git a/about/src/main/res/values/styles.xml b/about/src/main/res/values/styles.xml index 59b2470..f878950 100644 --- a/about/src/main/res/values/styles.xml +++ b/about/src/main/res/values/styles.xml @@ -1,9 +1,6 @@ - + - \ No newline at end of file + + + + diff --git a/core/README.md b/core/README.md index db602b6..63313ac 100644 --- a/core/README.md +++ b/core/README.md @@ -86,6 +86,23 @@ There is an optional `customize` argument to modify the builder before showing t As mentioned, blank items will be ignored, so feel free to create a bunch of empty lines to facilitate updating the items in the future. +Here is a template xml changelog file: + +```xml + + + + + + + + + +``` + ## Ripple Canvas diff --git a/core/src/main/kotlin/ca/allanwang/kau/email/EmailBuilder.kt b/core/src/main/kotlin/ca/allanwang/kau/email/EmailBuilder.kt index b03a620..88a0945 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/email/EmailBuilder.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/email/EmailBuilder.kt @@ -40,9 +40,9 @@ class EmailBuilder(val email: String, val subject: String) { if (deviceDetails) { val deviceItems = mutableMapOf( "OS Version" to "${System.getProperty("os.version")} (${Build.VERSION.INCREMENTAL})", - "OS API Level" to Build.DEVICE, - "Manufacturer" to Build.MANUFACTURER, - "Model (and Product)" to "${Build.MODEL} (${Build.PRODUCT})", + "OS SDK" to Build.VERSION.SDK_INT, + "Device (Manufacturer)" to "${Build.DEVICE} (${Build.MANUFACTURER})", + "Model (Product)" to "${Build.MODEL} (${Build.PRODUCT})", "Package Installer" to (context.installerPackageName ?: "None") ) if (context is Activity) { diff --git a/core/src/main/kotlin/ca/allanwang/kau/permissions/PermissionManager.kt b/core/src/main/kotlin/ca/allanwang/kau/permissions/PermissionManager.kt index 6f93c9f..d6e17db 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/permissions/PermissionManager.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/permissions/PermissionManager.kt @@ -18,7 +18,7 @@ internal object PermissionManager { val pendingResults: MutableList> by lazy { mutableListOf>() } operator fun invoke(context: Context, permissions: Array, callback: (granted: Boolean, deniedPerm: String?) -> Unit) { - KL.d("Requesting permissions: ${permissions.contentToString()}") + KL.d("Permission manager for: ${permissions.contentToString()}") if (!buildIsMarshmallowAndUp) return callback(true, null) val missingPermissions = permissions.filter { !context.hasPermission(it) } if (missingPermissions.isEmpty()) return callback(true, null) @@ -31,10 +31,12 @@ internal object PermissionManager { @Synchronized internal fun requestPermissions(context: Context, permissions: Array) { val activity = (context as? Activity) ?: throw KauException("Context is not an instance of an activity; cannot request permissions") + KL.d("Requesting permissions ${permissions.contentToString()}") ActivityCompat.requestPermissions(activity, permissions, 1) } fun onRequestPermissionsResult(context: Context, permissions: Array, grantResults: IntArray) { + KL.d("On permission result: pending ${pendingResults.size}") val count = Math.min(permissions.size, grantResults.size) val iter = pendingResults.iterator() while (iter.hasNext()) { @@ -53,6 +55,7 @@ internal object PermissionManager { } requestPermissions(context, action.permissions.toTypedArray()) } + KL.d("Post on permission result: pending ${pendingResults.size}") } } \ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/permissions/Permissions.kt b/core/src/main/kotlin/ca/allanwang/kau/permissions/Permissions.kt index fd43102..36ad52f 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/permissions/Permissions.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/permissions/Permissions.kt @@ -9,6 +9,12 @@ import android.content.Context * Created by Allan Wang on 2017-07-02. * * Bindings for the permission manager + * This is the only class you need to worry about when using KAU's manager + * + * MAKE SURE [kauOnRequestPermissionsResult] is added to your activities, + * and don't forget to request the permissions in your manifest. + * A collection of constants redirecting to the [Manifest.permission] counterparts + * are added for your convenience */ /** diff --git a/core/src/main/kotlin/ca/allanwang/kau/ui/views/MeasureSpecDelegate.kt b/core/src/main/kotlin/ca/allanwang/kau/ui/views/MeasureSpecDelegate.kt new file mode 100644 index 0000000..edc1536 --- /dev/null +++ b/core/src/main/kotlin/ca/allanwang/kau/ui/views/MeasureSpecDelegate.kt @@ -0,0 +1,107 @@ +package ca.allanwang.kau.ui.views + +import android.content.Context +import android.graphics.Rect +import android.util.AttributeSet +import android.view.View +import ca.allanwang.kau.R +import ca.allanwang.kau.utils.parentViewGroup + +/** + * Created by Allan Wang on 2017-07-14. + * + * Handles relative sizes for any view + */ +interface MeasureSpecContract { + + /** + * Width will be calculated as a percentage of the parent + * This takes precedence over relativeWidth + */ + var relativeWidthToParent: Float + /** + * Height will be calculated as a percentage of the parent + * This takes precedence over relativeHeight + */ + var relativeHeightToParent: Float + /** + * Width will be calculated based on the measured height + */ + var relativeWidth: Float + /** + * Height will be calculated based on the measure width + */ + var relativeHeight: Float + /** + * Width will be once again calculated from the current measured height + * This is the last step + */ + var postRelativeWidth: Float + /** + * Height will be once again calculated from the current measured width + * This is the last step + */ + var postRelativeHeight: Float + + /** + * Retrieves relative values from the [AttributeSet] + * Call this on init + */ + fun initAttrs(context: Context, attrs: AttributeSet?) + + /** + * Calculates the final measure specs + * Call this from [View.onMeasure] and send the Pair result as the specs + * The pair is of the format (width, height) + */ + fun onMeasure(view: View, widthMeasureSpec: Int, heightMeasureSpec: Int): Pair +} + +class MeasureSpecDelegate : MeasureSpecContract { + + override var relativeWidth = -1f + override var relativeHeight = -1f + override var relativeWidthToParent = -1f + override var relativeHeightToParent = -1f + override var postRelativeWidth: Float = -1f + override var postRelativeHeight: Float = -1f + private val parentFrame = Rect() + + override fun initAttrs(context: Context, attrs: AttributeSet?) { + if (attrs == null) return + val styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.MeasureSpecDelegate) + relativeWidth = styledAttrs.getFloat(R.styleable.MeasureSpecDelegate_relativeWidth, relativeWidth) + relativeHeight = styledAttrs.getFloat(R.styleable.MeasureSpecDelegate_relativeHeight, relativeHeight) + relativeWidthToParent = styledAttrs.getFloat(R.styleable.MeasureSpecDelegate_relativeWidthToParent, relativeWidthToParent) + relativeHeightToParent = styledAttrs.getFloat(R.styleable.MeasureSpecDelegate_relativeHeightToParent, relativeHeightToParent) + postRelativeWidth = styledAttrs.getFloat(R.styleable.MeasureSpecDelegate_postRelativeWidth, postRelativeWidth) + postRelativeHeight = styledAttrs.getFloat(R.styleable.MeasureSpecDelegate_postRelativeHeight, postRelativeHeight) + styledAttrs.recycle() + } + + override fun onMeasure(view: View, widthMeasureSpec: Int, heightMeasureSpec: Int): Pair { + view.parentViewGroup.getWindowVisibleDisplayFrame(parentFrame) + var width = View.MeasureSpec.getSize(widthMeasureSpec).toFloat() + var height = View.MeasureSpec.getSize(heightMeasureSpec).toFloat() + //first cycle - relative to parent + if (relativeHeightToParent > 0) + height = relativeHeightToParent * parentFrame.height() + if (relativeWidthToParent > 0) + width = relativeWidthToParent * parentFrame.width() + //second cycle - relative to each other + if (relativeHeight > 0) + height = relativeHeight * width + else if (relativeWidth > 0) + width = relativeWidth * height + //third cycle - relative to each other + if (postRelativeHeight > 0) + height = postRelativeHeight * width + else if (postRelativeWidth > 0) + width = postRelativeWidth * height + return Pair(width.measureSpec, height.measureSpec) + } + + private val Float.measureSpec: Int + get() = View.MeasureSpec.makeMeasureSpec(this.toInt(), View.MeasureSpec.EXACTLY) + +} diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/ActivityUtils.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/ActivityUtils.kt index 3b99c46..cd6e089 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/utils/ActivityUtils.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/utils/ActivityUtils.kt @@ -38,19 +38,19 @@ fun Activity.finishSlideOut() { overridePendingTransition(R.anim.kau_fade_in, R.anim.kau_slide_out_right_top) } -var Activity.navigationBarColor: Int +inline var Activity.navigationBarColor: Int get() = if (buildIsLollipopAndUp) window.navigationBarColor else Color.BLACK set(value) { if (buildIsLollipopAndUp) window.navigationBarColor = value } -var Activity.statusBarColor: Int +inline var Activity.statusBarColor: Int get() = if (buildIsLollipopAndUp) window.statusBarColor else Color.BLACK set(value) { if (buildIsLollipopAndUp) window.statusBarColor = value } -var Activity.statusBarLight: Boolean +inline var Activity.statusBarLight: Boolean @SuppressLint("InlinedApi") get() = if (buildIsMarshmallowAndUp) window.decorView.systemUiVisibility and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR > 0 else false @SuppressLint("InlinedApi") 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 81bf0d9..50d117c 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/utils/ColorUtils.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/utils/ColorUtils.kt @@ -65,7 +65,7 @@ fun Int.adjustAlpha(factor: Float): Int { return Color.argb(alpha, Color.red(this), Color.green(this), Color.blue(this)) } -val Int.isColorTransparent: Boolean +inline val Int.isColorTransparent: Boolean get() = Color.alpha(this) != 255 @ColorInt diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/ContextUtils.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/ContextUtils.kt index 3759c75..a8e0715 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/utils/ContextUtils.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/utils/ContextUtils.kt @@ -15,6 +15,7 @@ import android.support.v4.app.ActivityOptionsCompat import android.support.v4.content.ContextCompat import android.util.TypedValue import android.view.View +import android.view.animation.AnimationUtils import android.widget.Toast import ca.allanwang.kau.R import ca.allanwang.kau.logging.KL @@ -85,16 +86,18 @@ fun Context.toast(text: String, duration: Int = Toast.LENGTH_LONG) { } //Resource retrievers -fun Context.string(@StringRes id: Int): String = getString(id) - -fun Context.string(@StringRes id: Int, fallback: String?): String? = if (id > 0) string(id) else fallback -fun Context.string(holder: StringHolder?): String? = holder?.getString(this) -fun Context.color(@ColorRes id: Int): Int = ContextCompat.getColor(this, id) -fun Context.integer(@IntegerRes id: Int): Int = resources.getInteger(id) -fun Context.dimen(@DimenRes id: Int): Float = resources.getDimension(id) -fun Context.dimenPixelSize(@DimenRes id: Int): Int = resources.getDimensionPixelSize(id) -fun Context.drawable(@DrawableRes id: Int): Drawable = ContextCompat.getDrawable(this, id) -fun Context.drawable(@DrawableRes id: Int, fallback: Drawable?): Drawable? = if (id > 0) drawable(id) else fallback +inline fun Context.string(@StringRes id: Int): String = getString(id) + +inline fun Context.string(@StringRes id: Int, fallback: String?): String? = if (id > 0) string(id) else fallback +inline fun Context.color(@ColorRes id: Int): Int = ContextCompat.getColor(this, id) +inline fun Context.integer(@IntegerRes id: Int): Int = resources.getInteger(id) +inline fun Context.dimen(@DimenRes id: Int): Float = resources.getDimension(id) +inline fun Context.dimenPixelSize(@DimenRes id: Int): Int = resources.getDimensionPixelSize(id) +inline fun Context.drawable(@DrawableRes id: Int): Drawable = ContextCompat.getDrawable(this, id) +inline fun Context.drawable(@DrawableRes id: Int, fallback: Drawable?): Drawable? = if (id > 0) drawable(id) else fallback +inline fun Context.interpolator(@InterpolatorRes id: Int) = AnimationUtils.loadInterpolator(this, id) +inline fun Context.animation(@AnimRes id: Int) = AnimationUtils.loadAnimation(this, id) +inline fun Context.plural(@PluralsRes id: Int, quantity: Number) = resources.getQuantityString(id, quantity.toInt()) //Attr retrievers fun Context.resolveColor(@AttrRes attr: Int, fallback: Int = 0): Int { diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/Either.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/Either.kt deleted file mode 100644 index dab5810..0000000 --- a/core/src/main/kotlin/ca/allanwang/kau/utils/Either.kt +++ /dev/null @@ -1,32 +0,0 @@ -package ca.allanwang.kau.utils - -/** - * Created by Allan Wang on 2017-06-17. - * - * Courtesy of adelnizamutdinov - * - * https://github.com/adelnizamutdinov/kotlin-either - */ -@Suppress("unused") -sealed class Either - -data class Left(val value: T) : Either() -data class Right(val value: T) : Either() - -inline fun Either.fold(left: (L) -> T, right: (R) -> T): T = - when (this) { - is Left -> left(value) - is Right -> right(value) - } - -inline fun Either.flatMap(f: (R) -> Either): Either = - fold({ this as Left }, f) - -inline fun Either.map(f: (R) -> T): Either = - flatMap { Right(f(it)) } - -val Either.isLeft: Boolean - get() = this is Left - -val Either<*, T>.isRight: Boolean - get() = this is Right \ 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 247bbc7..3783931 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/utils/Kotterknife.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/utils/Kotterknife.kt @@ -114,21 +114,21 @@ fun android.support.v4.app.Fragment.bindOptionalViews(vararg ids: Int fun ViewHolder.bindOptionalViews(vararg ids: Int) : ReadOnlyProperty> = optional(ids, viewFinder) -private val View.viewFinder: View.(Int) -> View? +private inline val View.viewFinder: View.(Int) -> View? get() = { findViewById(it) } -private val Activity.viewFinder: Activity.(Int) -> View? +private inline val Activity.viewFinder: Activity.(Int) -> View? get() = { findViewById(it) } -private val Dialog.viewFinder: Dialog.(Int) -> View? +private inline val Dialog.viewFinder: Dialog.(Int) -> View? get() = { findViewById(it) } -private val DialogFragment.viewFinder: DialogFragment.(Int) -> View? +private inline val DialogFragment.viewFinder: DialogFragment.(Int) -> View? get() = { dialog.findViewById(it) } -private val android.support.v4.app.DialogFragment.viewFinder: android.support.v4.app.DialogFragment.(Int) -> View? +private inline val android.support.v4.app.DialogFragment.viewFinder: android.support.v4.app.DialogFragment.(Int) -> View? get() = { dialog.findViewById(it) } -private val Fragment.viewFinder: Fragment.(Int) -> View? +private inline val Fragment.viewFinder: Fragment.(Int) -> View? get() = { view.findViewById(it) } -private val android.support.v4.app.Fragment.viewFinder: android.support.v4.app.Fragment.(Int) -> View? +private inline val android.support.v4.app.Fragment.viewFinder: android.support.v4.app.Fragment.(Int) -> View? get() = { view!!.findViewById(it) } -private val ViewHolder.viewFinder: ViewHolder.(Int) -> View? +private inline val ViewHolder.viewFinder: ViewHolder.(Int) -> View? get() = { itemView.findViewById(it) } private fun viewNotFound(id: Int, desc: KProperty<*>): Nothing = 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 d04538c..89d64e5 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/utils/PackageUtils.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/utils/PackageUtils.kt @@ -22,22 +22,30 @@ import android.os.Build } } -val buildIsLollipopAndUp: Boolean +@KauUtils fun Context.isAppEnabled(packageName: String): Boolean { + try { + return packageManager.getApplicationInfo(packageName, 0).enabled + } catch (e: Exception) { + return false + } +} + +inline val buildIsLollipopAndUp: Boolean get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP -val buildIsMarshmallowAndUp: Boolean +inline val buildIsMarshmallowAndUp: Boolean get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -val buildIsNougatAndUp: Boolean +inline val buildIsNougatAndUp: Boolean get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N const val INSTALLER_GOOGLE_PLAY_VENDING = "com.android.vending" const val INSTALLER_GOOGLE_PLAY_FEEDBACK = "com.google.android.feedback" -val Context.installerPackageName: String? +inline val Context.installerPackageName: String? get() = packageManager.getInstallerPackageName(packageName) -val Context.isFromGooglePlay: Boolean +inline val Context.isFromGooglePlay: Boolean get() { val installer = installerPackageName return arrayOf(INSTALLER_GOOGLE_PLAY_FEEDBACK, INSTALLER_GOOGLE_PLAY_VENDING).any { it == installer } diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/RecyclerUtils.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/RecyclerUtils.kt new file mode 100644 index 0000000..f80c85e --- /dev/null +++ b/core/src/main/kotlin/ca/allanwang/kau/utils/RecyclerUtils.kt @@ -0,0 +1,25 @@ +package ca.allanwang.kau.utils + +import android.graphics.Rect +import android.support.v7.widget.RecyclerView +import android.view.View + +/** + * Created by Allan Wang on 2017-07-11. + */ +fun RecyclerView.withMarginDecoration(sizeDp: Int, edgeFlags: Int) { + addItemDecoration(MarginItemDecoration(sizeDp, edgeFlags)) +} + +class MarginItemDecoration(sizeDp: Int, val edgeFlags: Int) : RecyclerView.ItemDecoration() { + + val sizePx = sizeDp.dpToPx + + override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) { + super.getItemOffsets(outRect, view, parent, state) + if (edgeFlags and KAU_LEFT > 0) outRect.left += sizePx + if (edgeFlags and KAU_TOP > 0) outRect.top += sizePx + if (edgeFlags and KAU_RIGHT > 0) outRect.right += sizePx + if (edgeFlags and KAU_BOTTOM > 0) outRect.bottom += sizePx + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/StringHolder.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/StringHolder.kt deleted file mode 100644 index e70a2d1..0000000 --- a/core/src/main/kotlin/ca/allanwang/kau/utils/StringHolder.kt +++ /dev/null @@ -1,22 +0,0 @@ -package ca.allanwang.kau.utils - -import android.content.Context -import android.support.annotation.StringRes - -/** - * Created by Allan Wang on 2017-06-08. - */ -class StringHolder { - var text: String? = null - var textRes: Int = 0 - - constructor(@StringRes textRes: Int) { - this.textRes = textRes - } - - constructor(text: String) { - this.text = text - } - - fun getString(context: Context) = context.string(textRes, text) -} \ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/Utils.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/Utils.kt index a2043db..e8f385a 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/utils/Utils.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/utils/Utils.kt @@ -26,16 +26,16 @@ import java.text.DecimalFormat @DslMarker annotation class KauUtils -@KauUtils val Float.dpToPx: Float +@KauUtils inline val Float.dpToPx: Float get() = this * Resources.getSystem().displayMetrics.density -@KauUtils val Int.dpToPx: Int +@KauUtils inline val Int.dpToPx: Int get() = (this * Resources.getSystem().displayMetrics.density).toInt() -@KauUtils val Float.pxToDp: Float +@KauUtils inline val Float.pxToDp: Float get() = this / Resources.getSystem().displayMetrics.density -@KauUtils val Int.pxToDp: Int +@KauUtils inline val Int.pxToDp: Int get() = (this / Resources.getSystem().displayMetrics.density).toInt() /** @@ -52,11 +52,9 @@ annotation class KauUtils */ @KauUtils fun Context.minuteToText(minutes: Long): String = with(minutes) { if (this < 0L) string(R.string.kau_none) - else if (this == 60L) string(R.string.kau_one_hour) - else if (this == 1440L) string(R.string.kau_one_day) - else if (this % 1440L == 0L) String.format(string(R.string.kau_x_days), this / 1440L) - else if (this % 60L == 0L) String.format(string(R.string.kau_x_hours), this / 60L) - else String.format(string(R.string.kau_x_minutes), this) + else if (this % 1440L == 0L) plural(R.plurals.kau_x_days, this / 1440L) + else if (this % 60L == 0L) plural(R.plurals.kau_x_hours, this / 60L) + else plural(R.plurals.kau_x_minutes, this) } @KauUtils fun Number.round(@IntRange(from = 1L) decimalCount: Int): String { 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 53d6d05..59ae204 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/utils/ViewUtils.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/utils/ViewUtils.kt @@ -1,9 +1,12 @@ +@file:Suppress("NOTHING_TO_INLINE") + package ca.allanwang.kau.utils +import android.animation.ValueAnimator import android.content.Context import android.graphics.Color -import android.graphics.Rect import android.support.annotation.ColorInt +import android.support.annotation.ColorRes import android.support.annotation.StringRes import android.support.annotation.TransitionRes import android.support.design.widget.FloatingActionButton @@ -58,6 +61,8 @@ import com.mikepenz.iconics.typeface.IIcon @KauUtils inline val View.isGone: Boolean get() = visibility == View.GONE +@KauUtils inline fun View.setBackgroundColorRes(@ColorRes color: Int) = setBackgroundColor(context.color(color)) + fun View.snackbar(text: String, duration: Int = Snackbar.LENGTH_LONG, builder: Snackbar.() -> Unit = {}): Snackbar { val snackbar = Snackbar.make(this, text, duration) snackbar.builder() @@ -85,12 +90,24 @@ fun FloatingActionButton.hideIf(hide: Boolean) = if (hide) hide() else show() @KauUtils fun ViewGroup.inflate(layoutId: Int, attachToRoot: Boolean = false): View = LayoutInflater.from(context).inflate(layoutId, this, attachToRoot) +/** + * Set left margin to a value in px + */ @KauUtils fun View.updateLeftMargin(margin: Int) = updateMargins(margin, KAU_LEFT) +/** + * Set top margin to a value in px + */ @KauUtils fun View.updateTopMargin(margin: Int) = updateMargins(margin, KAU_TOP) +/** + * Set right margin to a value in px + */ @KauUtils fun View.updateRightMargin(margin: Int) = updateMargins(margin, KAU_RIGHT) +/** + * Set bottom margin to a value in px + */ @KauUtils fun View.updateBottomMargin(margin: Int) = updateMargins(margin, KAU_BOTTOM) @KauUtils private fun View.updateMargins(margin: Int, flag: Int) { @@ -130,18 +147,11 @@ fun FloatingActionButton.hideIf(hide: Boolean) = if (hide) hide() else show() background = createSimpleRippleDrawable(foregroundColor, backgroundColor) } -@KauUtils val View.parentViewGroup: ViewGroup get() = parent as ViewGroup - -@KauUtils val View.parentVisibleHeight: Int - get() { - val r = Rect() - parentViewGroup.getWindowVisibleDisplayFrame(r) - return r.height() - } +@KauUtils inline val View.parentViewGroup: ViewGroup get() = parent as ViewGroup -val EditText.value: String get() = text.toString().trim() +inline val EditText.value: String get() = text.toString().trim() -val TextInputEditText.value: String get() = text.toString().trim() +inline val TextInputEditText.value: String get() = text.toString().trim() /** * Generates a recycler view with match parent and a linearlayoutmanager, since it's so commonly used @@ -153,4 +163,33 @@ fun Context.fullLinearRecycler(rvAdapter: RecyclerView.Adapter<*>? = null, confi if (rvAdapter != null) adapter = rvAdapter configs() } +} + +/** + * Animate a transition for a FloatinActionButton + * 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 { + var transitioned = false + ValueAnimator.ofFloat(1.0f, 0.0f, 1.0f).apply { + duration = 500L + addUpdateListener { + val x = it.animatedValue as Float + val scale = x * 0.3f + 0.7f + scaleX = scale + scaleY = scale + imageAlpha = (x * 255).toInt() + if (it.animatedFraction > 0.5f && !transitioned) { + transitioned = true + action() + } + } + start() + } + } } \ No newline at end of file diff --git a/core/src/main/res/transition/kau_enter_slide_bottom.xml b/core/src/main/res/transition/kau_enter_slide_bottom.xml index 7eb2097..575e189 100644 --- a/core/src/main/res/transition/kau_enter_slide_bottom.xml +++ b/core/src/main/res/transition/kau_enter_slide_bottom.xml @@ -1,20 +1,4 @@ - - - - + + + + + + + + + + + + + + + + + diff --git a/core/src/main/res/transition/kau_exit_slide_top.xml b/core/src/main/res/transition/kau_exit_slide_top.xml new file mode 100644 index 0000000..a9849c0 --- /dev/null +++ b/core/src/main/res/transition/kau_exit_slide_top.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + diff --git a/core/src/main/res/values/attr.xml b/core/src/main/res/values/attr.xml new file mode 100644 index 0000000..f02c219 --- /dev/null +++ b/core/src/main/res/values/attr.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/core/src/main/res/values/dimens.xml b/core/src/main/res/values/dimens.xml index a459443..b7c237a 100644 --- a/core/src/main/res/values/dimens.xml +++ b/core/src/main/res/values/dimens.xml @@ -13,6 +13,7 @@ 24dp 112dp + 168dp 8dp 4dp diff --git a/core/src/main/res/values/ids.xml b/core/src/main/res/values/ids.xml index 0b4322c..003e8a7 100644 --- a/core/src/main/res/values/ids.xml +++ b/core/src/main/res/values/ids.xml @@ -1,19 +1,19 @@ - + - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml index 5ea5a23..7ad6e38 100644 --- a/core/src/main/res/values/strings.xml +++ b/core/src/main/res/values/strings.xml @@ -1,15 +1,13 @@ + - + This app would not be possible without the following great libraries. Color Picker - + Dependencies Used + Title Placeholder + Color Palette Custom Presets - Color Palette - - Title Placeholder Pref Icon - - This app would not be possible without the following great libraries. - Dependencies Used + diff --git a/core/src/main/res/values/strings_commons.xml b/core/src/main/res/values/strings_commons.xml index 389b8a2..560a478 100644 --- a/core/src/main/res/values/strings_commons.xml +++ b/core/src/main/res/values/strings_commons.xml @@ -1,8 +1,8 @@ + - About App About %s @@ -13,6 +13,7 @@ Most resources are verbatim and x represents a formatted item Changelog Close Contact Us + Copy Custom Dark Default @@ -38,8 +39,6 @@ Most resources are verbatim and x represents a formatted item No Results Found None @android:string/ok - 1 day - 1 hour Play Store Rate Report A Bug @@ -48,13 +47,25 @@ Most resources are verbatim and x represents a formatted item Send via Settings Share + Text copied to clipboard. Thank You Uh Oh Warning - %d days - %d hours - %d minutes + + %d day + %d days + + + %d hour + %d hours + + + %d minute + %d minutes + + + %d second + %d seconds + Yes - Text copied to clipboard. - Copy diff --git a/core/src/main/res/values/styles_animations.xml b/core/src/main/res/values/styles_animations.xml index a991132..fc872bd 100644 --- a/core/src/main/res/values/styles_animations.xml +++ b/core/src/main/res/values/styles_animations.xml @@ -1,4 +1,22 @@ + + + + - - + diff --git a/core/src/main/res/xml/kau_changelog.xml b/core/src/main/res/xml/kau_changelog.xml deleted file mode 100644 index e570995..0000000 --- a/core/src/main/res/xml/kau_changelog.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/docs/Changelog.md b/docs/Changelog.md index 75fc1d0..e958476 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -1,6 +1,18 @@ # Changelog ## v2.1 +* :adapter: Fix up CardIItem +* :adapter: Modularized kau animators +* :adapter: Switched from mutablelist to list inputs for themed animator +* :core-ui: Create ElasticRecyclerActivity +* :core-ui: Create MeasuredImageView +* :core: Create MeasureSpecDelegate +* :core: Improve PermissionManager logging +* :core: Inline all util variables with getters +* :core: Introduce fade animation style templates +* :core: Introduce slide transition style templates +* :core: Update utils and remove StringHolder +* :imagepicker: Create full image picker with blurrable selections ## v2.0 * Huge refactoring to separate functions to their own submodules diff --git a/gradle.properties b/gradle.properties index 8e2555e..102d6a7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -23,18 +23,19 @@ BUILD_TOOLS=26.0.0 ANDROID_SUPPORT_LIBS=26.0.0-beta2 KOTLIN=1.1.3-2 -MATERIAL_DIALOG=0.9.4.5 -ICONICS=2.8.9 -IICON_GOOGLE=3.0.1.1 -TIMBER=4.5.1 +ABOUT_LIBRARIES=5.9.7 +ANKO=0.10.1 +BLURRY=2.1.1 CONSTRAINT_LAYOUT=1.1.0-beta1 FAST_ADAPTER=2.6.2 FAST_ADAPTER_COMMONS=2.6.0 -ANKO=0.10.1 -RX_JAVA=2.1.1 -RX_KOTLIN=2.1.0 +GLIDE=4.0.0-RC1 +ICONICS=2.9.0 +IICON_GOOGLE=3.0.1.1 +MATERIAL_DIALOG=0.9.4.5 RX_ANDROID=2.0.1 RX_BINDING=2.0.0 -ABOUT_LIBRARIES=5.9.6 -GLIDE=4.0.0-RC1 +RX_JAVA=2.1.1 +RX_KOTLIN=2.1.0 +TIMBER=4.5.1 diff --git a/imagepicker/build.gradle b/imagepicker/build.gradle index 14c07ac..d63f5fd 100644 --- a/imagepicker/build.gradle +++ b/imagepicker/build.gradle @@ -10,6 +10,7 @@ dependencies { compile "com.github.bumptech.glide:glide:${GLIDE}" kapt "com.github.bumptech.glide:compiler:${GLIDE}" + compile "jp.wasabeef:blurry:${BLURRY}" } apply from: '../artifacts.gradle' diff --git a/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/BlurredImageView.kt b/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/BlurredImageView.kt new file mode 100644 index 0000000..8fb5cf3 --- /dev/null +++ b/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/BlurredImageView.kt @@ -0,0 +1,166 @@ +package ca.allanwang.kau.imagepicker + +import android.content.Context +import android.graphics.Color +import android.util.AttributeSet +import android.view.View +import android.widget.FrameLayout +import android.widget.ImageView +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 + +/** + * Created by Allan Wang on 2017-07-14. + * + * ImageView that is can be blurred and selected + * The frame is composed of three layers: the base, the blur, and the foreground + * Images should be placed in the base view, and the blur view should not be touched + * as the class will handle it + * The foreground by default contains a white checkmark, but can be customized or hidden depending on the situation + */ +class BlurredImageView @JvmOverloads constructor( + context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, defStyleRes: Int = 0 +) : FrameLayout(context, attrs, defStyleAttr, defStyleRes), MeasureSpecContract by MeasureSpecDelegate() { + + private var blurred = false + val imageBase: ImageView by bindView(R.id.image_base) + internal val imageBlur: ImageView by bindView(R.id.image_blur) + val imageForeground: ImageView by bindView(R.id.image_foreground) + + init { + inflate(R.layout.kau_blurred_imageview, true) + initAttrs(context, attrs) + imageForeground.setIcon(GoogleMaterial.Icon.gmd_check, 30) + } + + companion object { + const val ANIMATION_DURATION = 200L + const val ANIMATION_SCALE = 0.95f + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + val result = onMeasure(this, widthMeasureSpec, heightMeasureSpec) + super.onMeasure(result.first, result.second) + } + + override fun clearAnimation() { + super.clearAnimation() + imageBase.clearAnimation() + imageBlur.clearAnimation() + imageForeground.clearAnimation() + } + + private fun View.scaleAnimate(scale: Float) = animate().scaleX(scale).scaleY(scale).setDuration(ANIMATION_DURATION) + private fun View.alphaAnimate(alpha: Float) = animate().alpha(alpha).setDuration(ANIMATION_DURATION) + + + fun isBlurred(): Boolean { + return blurred + } + + /** + * Applies a blur and fills the blur image asynchronously + * When ready, scales the image down and shows the blur & foreground + */ + fun blur() { + if (blurred) return + blurred = true + val factor = BlurFactor() + factor.width = width + factor.height = height + val task = BlurTask(imageBase, factor) { + imageBlur.setImageDrawable(it) + scaleAnimate(ANIMATION_SCALE).start() + imageBlur.alphaAnimate(1f).start() + imageForeground.alphaAnimate(1f).start() + } + task.execute() + } + + /** + * Clears animations and blurs the image without further animations + * This method is relatively instantaneous, as retrieving the blurred image + * is still asynchronous and takes time + */ + fun blurInstantly() { + blurred = true + clearAnimation() + val factor = BlurFactor() + factor.width = width + factor.height = height + BlurTask(imageBase, factor) { drawable -> + imageBlur.setImageDrawable(drawable) + scaleX = ANIMATION_SCALE + scaleY = ANIMATION_SCALE + imageBlur.alpha = 1f + imageForeground.alpha = 1f + }.execute() + } + + /** + * Animate view back to original state and remove drawable when finished + */ + fun removeBlur() { + if (!blurred) return + blurred = false + scaleAnimate(1.0f).start() + imageBlur.alphaAnimate(0f).withEndAction { imageBlur.setImageDrawable(null) }.start() + imageForeground.alphaAnimate(0f).start() + } + + + /** + * Clear all animations and unblur the image + */ + fun removeBlurInstantly() { + blurred = false + clearAnimation() + scaleX = 1.0f + scaleX = 1.0f + imageBlur.alpha = 0f + imageBlur.setImageDrawable(null) + imageForeground.alpha = 0f + } + + /** + * Switch blur state and apply transition + * + * @return true if new state is blurred; false otherwise + */ + fun toggleBlur(): Boolean { + if (blurred) removeBlur() + else blur() + return blurred + } + + /** + * Clears all of the blur effects to restore the original states + * If views were modified in other ways, this method won't affect it + */ + fun reset() { + removeBlurInstantly() + imageBase.setImageDrawable(null) + } + + /** + * Reset most of possible changes to the view + */ + fun fullReset() { + reset() + fullAction({ it.visible().background = null }) + imageForeground.setBackgroundColorRes(R.color.kau_blurred_image_selection_overlay) + imageForeground.setIcon(GoogleMaterial.Icon.gmd_check, 30, Color.WHITE) + } + + private fun fullAction(action: (View) -> Unit) { + action(this) + action(imageBase) + action(imageBlur) + action(imageForeground) + } +} \ No newline at end of file diff --git a/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImageItem.kt b/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImageItem.kt index 0b13a30..852e1e8 100644 --- a/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImageItem.kt +++ b/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImageItem.kt @@ -1,18 +1,89 @@ package ca.allanwang.kau.imagepicker +import android.content.Context +import android.graphics.Color +import android.graphics.drawable.Drawable import android.support.v7.widget.RecyclerView import android.view.View -import android.widget.ImageView import ca.allanwang.kau.iitems.KauIItem import ca.allanwang.kau.utils.bindView +import ca.allanwang.kau.utils.gone +import com.bumptech.glide.Glide +import com.bumptech.glide.load.DataSource +import com.bumptech.glide.load.engine.GlideException +import com.bumptech.glide.request.RequestListener +import com.bumptech.glide.request.target.Target +import com.mikepenz.fastadapter.FastAdapter +import com.mikepenz.google_material_typeface_library.GoogleMaterial +import com.mikepenz.iconics.IconicsDrawable +import com.mikepenz.iconics.typeface.IIcon /** * Created by Allan Wang on 2017-07-04. */ -class ImageItem(data:String) - : KauIItem(R.layout.kau_iitem_card, { ViewHolder(it) }) { +class ImageItem(val data: ImageModel) + : KauIItem(R.layout.kau_iitem_image, { ViewHolder(it) }) { + + private var failedToLoad = false + + fun bindEvents(fastAdapter: FastAdapter) { + fastAdapter.withMultiSelect(true) + fastAdapter.withSelectable(true) + fastAdapter.withOnClickListener { v, _, _, _ -> + val image = v as BlurredImageView + image.toggleBlur() + true + } + } + + override fun bindView(holder: ViewHolder, payloads: List?) { + super.bindView(holder, payloads) + holder.container.alpha = 0f + Glide.with(holder.itemView) + .load(data.data) + .listener(object : RequestListener { + override fun onLoadFailed(e: GlideException?, model: Any, target: Target, isFirstResource: Boolean): Boolean { + failedToLoad = true; + holder.container.setIcon(GoogleMaterial.Icon.gmd_error); + holder.container.animate().alpha(1f).start(); + return true; + } + + override fun onResourceReady(resource: Drawable, model: Any, target: Target, dataSource: DataSource, isFirstResource: Boolean): Boolean { + holder.container.animate().alpha(1f).start(); + return false; + } + }) + .into(holder.container.imageBase) + } + + private fun BlurredImageView.setIcon(icon: IIcon) { + val sizePx = computeViewSize(context) + imageBase.setImageDrawable(IconicsDrawable(context, icon) + .sizePx(sizePx) + .paddingPx(sizePx / 3) + .color(Color.WHITE)) + //todo add background + imageBase.setBackgroundColor(ImagePickerActivityBase.accentColor) + imageForeground.gone() + } + + private fun computeViewSize(context: Context): Int { + val screenWidthPx = context.resources.displayMetrics.widthPixels + return screenWidthPx / ImagePickerActivityBase.computeColumnCount(context) + } + + override fun unbindView(holder: ViewHolder) { + super.unbindView(holder) + if (!failedToLoad) { + Glide.with(holder.itemView).clear(holder.container.imageBase) + holder.container.removeBlurInstantly() + } else { + holder.container.fullReset() + } + } class ViewHolder(v: View) : RecyclerView.ViewHolder(v) { - val image: ImageView by bindView(R.id.kau_image) + val container: BlurredImageView by bindView(R.id.kau_image) } } \ No newline at end of file diff --git a/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImageModel.kt b/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImageModel.kt new file mode 100644 index 0000000..26e4137 --- /dev/null +++ b/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImageModel.kt @@ -0,0 +1,21 @@ +package ca.allanwang.kau.imagepicker + +import android.database.Cursor +import android.provider.MediaStore +import android.support.annotation.NonNull + + +/** + * Created by Allan Wang on 2017-07-14. + */ +class ImageModel(@NonNull cursor: Cursor) { + + val size = cursor.getLong(MediaStore.Images.Media.SIZE) + val dateModified = cursor.getLong(MediaStore.Images.Media.DATE_MODIFIED) + val data = cursor.getString(MediaStore.Images.Media.DATA) + val displayName = cursor.getString(MediaStore.Images.Media.DISPLAY_NAME) + + 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/kotlin/ca/allanwang/kau/imagepicker/ImagePickerActivityBase.kt b/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImagePickerActivityBase.kt index 8dfbeab..24c2db7 100644 --- a/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImagePickerActivityBase.kt +++ b/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImagePickerActivityBase.kt @@ -1,5 +1,7 @@ package ca.allanwang.kau.imagepicker +import android.Manifest +import android.content.Context import android.database.Cursor import android.os.Bundle import android.provider.MediaStore @@ -10,9 +12,10 @@ import android.support.v7.app.AppCompatActivity import android.support.v7.widget.GridLayoutManager import android.support.v7.widget.RecyclerView import android.support.v7.widget.Toolbar -import ca.allanwang.kau.logging.KL +import ca.allanwang.kau.permissions.kauRequestPermissions import ca.allanwang.kau.ui.widgets.ElasticDragDismissFrameLayout import ca.allanwang.kau.utils.bindView +import ca.allanwang.kau.utils.dimenPixelSize import com.mikepenz.fastadapter.commons.adapters.FastItemAdapter @@ -27,14 +30,37 @@ abstract class ImagePickerActivityBase : AppCompatActivity(), LoaderManager.Load val recycler: RecyclerView by bindView(R.id.kau_recycler) val imageAdapter = FastItemAdapter() + companion object { + /** + * Given the dimensions of our device and a minimum image size, + * Computer the optimal column count for our grid layout + * + * @return column count + */ + fun computeColumnCount(context: Context): Int { + val minImageSizePx = context.dimenPixelSize(R.dimen.kau_image_minimum_size) + val screenWidthPx = context.resources.displayMetrics.widthPixels + return screenWidthPx / minImageSizePx + } + + var accentColor: Int = 0xff666666.toInt() + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.kau_activity_image_picker) - with(recycler) { - layoutManager = GridLayoutManager(this@ImagePickerActivityBase, 3) - adapter = this@ImagePickerActivityBase.imageAdapter + recycler.layoutManager = GridLayoutManager(this, computeColumnCount(this)) + recycler.adapter = imageAdapter + + with(imageAdapter) { + withPositionBasedStateManagement(false) + withMultiSelect(true) + withSelectable(true) + withOnClickListener { v, _, _, _ -> + (v as BlurredImageView).toggleBlur() + true + } } - imageAdapter.add(arrayOf("a", "b", "c").map { ImageItem(it) }) draggableFrame.addListener(object : ElasticDragDismissFrameLayout.SystemChromeFader(this) { override fun onDragDismissed() { if (draggableFrame.translationY < 0) { @@ -44,33 +70,45 @@ abstract class ImagePickerActivityBase : AppCompatActivity(), LoaderManager.Load finishAfterTransition() } }) + kauRequestPermissions(Manifest.permission.READ_EXTERNAL_STORAGE) { + granted, _ -> + if (granted) { + supportLoaderManager.initLoader(42, null, this) + } + } } override fun onCreateLoader(id: Int, args: Bundle?): Loader { 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, null) + MediaStore.Images.Media.DATE_MODIFIED + ) + return CursorLoader(this, + MediaStore.Images.Media.EXTERNAL_CONTENT_URI, + columns, + null, + null, + MediaStore.Images.Media.DATE_MODIFIED + " DESC") } - override fun onLoadFinished(loader: Loader, data: Cursor) { - val dataIndex = data.getColumnIndex(MediaStore.Images.Media.DATA) - val alstPhotos = mutableListOf() - - data.moveToLast() - while (!data.isBeforeFirst) { - val photoPath = data.getString(dataIndex) - KL.d(photoPath) - alstPhotos.add(photoPath) - data.moveToPrevious() + override fun onLoadFinished(loader: Loader, data: Cursor?) { + reset() + if (data == null) return + if (data.moveToFirst()) { + do { + val img = ImageModel(data) + imageAdapter.add(ImageItem(img)) + } while (data.moveToNext()) } - imageAdapter.add(alstPhotos.map { ImageItem(it) }) } - override fun onLoaderReset(loader: Loader) { - imageAdapter.clear() + private fun reset() { + imageAdapter.clear(); } + + override fun onLoaderReset(loader: Loader) = reset() } \ No newline at end of file diff --git a/imagepicker/src/main/res/layout/kau_blurred_imageview.xml b/imagepicker/src/main/res/layout/kau_blurred_imageview.xml new file mode 100644 index 0000000..e28cb9a --- /dev/null +++ b/imagepicker/src/main/res/layout/kau_blurred_imageview.xml @@ -0,0 +1,32 @@ + + + + + + + + + + \ No newline at end of file diff --git a/imagepicker/src/main/res/layout/kau_iitem_image.xml b/imagepicker/src/main/res/layout/kau_iitem_image.xml index 22cc998..9d51d77 100644 --- a/imagepicker/src/main/res/layout/kau_iitem_image.xml +++ b/imagepicker/src/main/res/layout/kau_iitem_image.xml @@ -1,15 +1,9 @@ - - - - - \ No newline at end of file + android:layout_height="match_parent" + android:layout_margin="2dp" + android:foreground="@drawable/kau_selectable_white" + app:relativeHeight="1" /> \ No newline at end of file diff --git a/imagepicker/src/main/res/values/colors.xml b/imagepicker/src/main/res/values/colors.xml new file mode 100644 index 0000000..ebaa3f7 --- /dev/null +++ b/imagepicker/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ + + + + #30000000 + + \ No newline at end of file diff --git a/imagepicker/src/main/res/values/dimens.xml b/imagepicker/src/main/res/values/dimens.xml new file mode 100644 index 0000000..3ff2dd7 --- /dev/null +++ b/imagepicker/src/main/res/values/dimens.xml @@ -0,0 +1,6 @@ + + + + 120dp + + \ No newline at end of file diff --git a/imagepicker/src/main/res/values/styles.xml b/imagepicker/src/main/res/values/styles.xml index e2d7280..1fbb184 100644 --- a/imagepicker/src/main/res/values/styles.xml +++ b/imagepicker/src/main/res/values/styles.xml @@ -1,8 +1,5 @@ - +