diff options
author | Allan Wang <me@allanwang.ca> | 2017-07-08 20:25:23 -0700 |
---|---|---|
committer | Allan Wang <me@allanwang.ca> | 2017-07-08 20:25:23 -0700 |
commit | 81996038462de1be86643e95d262933c4b96c551 (patch) | |
tree | 39423b28217251e7051f86e9e4f80c47bcba20b2 /core/src/main/kotlin | |
parent | 880d433e475e5be4e5d91afac145b490f9a959b7 (diff) | |
download | kau-81996038462de1be86643e95d262933c4b96c551.tar.gz kau-81996038462de1be86643e95d262933c4b96c551.tar.bz2 kau-81996038462de1be86643e95d262933c4b96c551.zip |
Move components to separate modules
Diffstat (limited to 'core/src/main/kotlin')
42 files changed, 3 insertions, 5708 deletions
diff --git a/core/src/main/kotlin/ca/allanwang/kau/about/AboutActivityBase.kt b/core/src/main/kotlin/ca/allanwang/kau/about/AboutActivityBase.kt deleted file mode 100644 index 77dbef1..0000000 --- a/core/src/main/kotlin/ca/allanwang/kau/about/AboutActivityBase.kt +++ /dev/null @@ -1,248 +0,0 @@ -package ca.allanwang.kau.about - -import android.graphics.drawable.Drawable -import android.os.Bundle -import android.support.v4.view.PagerAdapter -import android.support.v4.view.ViewPager -import android.support.v7.app.AppCompatActivity -import android.support.v7.widget.RecyclerView -import android.transition.TransitionInflater -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import ca.allanwang.kau.R -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.iitems.CutoutIItem -import ca.allanwang.kau.iitems.HeaderIItem -import ca.allanwang.kau.iitems.LibraryIItem -import ca.allanwang.kau.utils.* -import ca.allanwang.kau.widgets.ElasticDragDismissFrameLayout -import ca.allanwang.kau.widgets.InkPageIndicator -import com.mikepenz.aboutlibraries.Libs -import com.mikepenz.aboutlibraries.entity.Library -import com.mikepenz.fastadapter.IItem -import org.jetbrains.anko.doAsync -import org.jetbrains.anko.uiThread -import java.security.InvalidParameterException - -/** - * Created by Allan Wang on 2017-06-28. - * - * Floating About Activity Panel for your app - * This contains all the necessary layouts, and can be extended and configured using the [configBuilder] - * The [rClass] is necessary to generate the list of libraries used in your app, and should point to your app's - * R.string::class.java - * If you don't need auto detect, you can pass null instead - * Note that for the auto detection to work, the R fields must be excluded from Proguard - * Manual lib listings and other extra modifications can be done so by overriding the open functions - */ -abstract class AboutActivityBase(val rClass: Class<*>?, val configBuilder: Configs.() -> Unit = {}) : AppCompatActivity(), ViewPager.OnPageChangeListener { - - val draggableFrame: ElasticDragDismissFrameLayout by bindView(R.id.about_draggable_frame) - val pager: ViewPager by bindView(R.id.about_pager) - val indicator: InkPageIndicator by bindView(R.id.about_indicator) - /** - * Holds some common configurations that may be added directly from the constructor - * Applied lazily since it needs the context to fetch resources - */ - val configs: Configs by lazy { Configs().apply { configBuilder() } } - /** - * Number of pages in the adapter - * Defaults to just the main view and lib view - */ - open val pageCount: Int = 2 - /** - * Page position for the libs - * This is generated automatically if [inflateLibPage] is called - */ - private var libPage: Int = -2 - /** - * Holds that status of each page - * 0 means nothing has happened - * 1 means this page has been in view at least once - * The rest is up to you - */ - lateinit var pageStatus: IntArray - /** - * Holds the lib items once they are fetched asynchronously - */ - var libItems: List<LibraryIItem>? = null - /** - * Holds the adapter for the library page; this is generated later because it uses the config colors - */ - lateinit var libAdapter: FastItemThemedAdapter<IItem<*, *>> - /** - * Global reference of the library recycler - * This is set by default through [inflateLibPage] and is used to stop scrolling - * When the draggable frame exits - * It is not required, hence its nullability - */ - private var libRecycler: RecyclerView? = null - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.kau_activity_about) - pageStatus = IntArray(pageCount) - libAdapter = FastItemThemedAdapter(configs) - LibraryIItem.bindClickEvents(libAdapter) - if (configs.textColor != null) indicator.setColour(configs.textColor!!) - with(pager) { - adapter = AboutPagerAdapter() - pageMargin = dimenPixelSize(R.dimen.kau_spacing_normal) - addOnPageChangeListener(this@AboutActivityBase) - } - 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) - } - libRecycler?.stopScroll() - finishAfterTransition() - } - }) - } - - inner class Configs : ThemableIItemColors by ThemableIItemColorsDelegate() { - var cutoutTextRes: Int = -1 - var cutoutText: String? = null - var cutoutDrawableRes: Int = -1 - var cutoutDrawable: Drawable? = null - 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 - } - - /** - * Method to fetch the library list - * This is fetched asynchronously and you may override it to customize the list - */ - open fun getLibraries(libs: Libs): List<Library> = libs.prepareLibraries(this, null, null, true, true) - - /** - * Gets the view associated with the given page position - * Keep in mind that when inflating, do NOT add the view to the viewgroup - * Use layoutInflater.inflate(id, parent, false) - */ - open fun getPage(position: Int, layoutInflater: LayoutInflater, parent: ViewGroup): View { - return when (position) { - 0 -> inflateMainPage(layoutInflater, parent, position) - pageCount - 1 -> inflateLibPage(layoutInflater, parent, position) - else -> throw InvalidParameterException() - } - } - - /** - * Create the main view with the cutout - */ - open fun inflateMainPage(layoutInflater: LayoutInflater, parent: ViewGroup, position: Int): View { - val fastAdapter = FastItemThemedAdapter<IItem<*, *>>(configs) - val recycler = fullLinearRecycler(fastAdapter) - fastAdapter.add(CutoutIItem { - with(configs) { - text = string(cutoutTextRes, cutoutText) - drawable = drawable(cutoutDrawableRes, cutoutDrawable) - if (configs.cutoutForeground != null) foregroundColor = configs.cutoutForeground!! - } - }.apply { - themeEnabled = configs.cutoutForeground == null - }) - postInflateMainPage(fastAdapter) - return recycler - } - - /** - * Open hook called just before the main page view is returned - * Feel free to add your own items to the adapter in here - */ - open fun postInflateMainPage(adapter: FastItemThemedAdapter<IItem<*, *>>) { - - } - - /** - * Create the lib view with the list of libraries - */ - open fun inflateLibPage(layoutInflater: LayoutInflater, parent: ViewGroup, position: Int): View { - libPage = position - val v = layoutInflater.inflate(R.layout.kau_recycler_detached_background, parent, false) - val recycler = v.findViewById<RecyclerView>(R.id.kau_recycler_detached) - libRecycler = recycler - recycler.adapter = libAdapter - recycler.itemAnimator = FadeScaleAnimator(itemDelayFactor = 0.2f).apply { addDuration = 300; interpolator = AnimHolder.decelerateInterpolator(this@AboutActivityBase) } - val background = v.findViewById<View>(R.id.kau_recycler_detached_background) - if (configs.backgroundColor != null) background.setBackgroundColor(configs.backgroundColor!!.colorToForeground()) - doAsync { - libItems = getLibraries( - if (rClass == null) Libs(this@AboutActivityBase) else Libs(this@AboutActivityBase, Libs.toStringArray(rClass.fields)) - ).map { LibraryIItem(it) } - if (libPage >= 0 && pageStatus[libPage] == 1) - uiThread { addLibItems() } - } - return v - } - - inner class AboutPagerAdapter : PagerAdapter() { - - private val layoutInflater: LayoutInflater = LayoutInflater.from(this@AboutActivityBase) - private val views = Array<View?>(pageCount) { null } - - override fun instantiateItem(collection: ViewGroup, position: Int): Any { - val layout = getPage(position, collection) - collection.addView(layout) - return layout - } - - override fun destroyItem(collection: ViewGroup, position: Int, view: Any) { - collection.removeView(view as View) - views[position] = null - } - - override fun getCount(): Int = pageCount - - override fun isViewFromObject(view: View, `object`: Any): Boolean = view === `object` - - /** - * Only get page if view does not exist - */ - private fun getPage(position: Int, parent: ViewGroup): View { - if (views[position] == null) views[position] = getPage(position, layoutInflater, parent) - return views[position]!! - } - } - - override fun onPageScrollStateChanged(state: Int) {} - - override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {} - - override fun onPageSelected(position: Int) { - if (pageStatus[position] == 0) pageStatus[position] = 1 // mark as seen if previously null - if (position == libPage && libItems != null && pageStatus[position] == 1) { - pageStatus[position] = 2 //add libs and mark as such - postDelayed(300) { addLibItems() } //delay so that the animations occur once the page is fully switched - } - } - - /** - * Function that is called when the view is ready to add the lib items - * Feel free to add your own items here - */ - open fun addLibItems() { - libAdapter.add(HeaderIItem(text = configs.libPageTitle, textRes = configs.libPageTitleRes)) - .add(libItems) - } - - override fun onDestroy() { - AnimHolder.decelerateInterpolator.invalidate() //clear the reference to the interpolators we've used - super.onDestroy() - } -}
\ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/adapters/ChainedAdapters.kt b/core/src/main/kotlin/ca/allanwang/kau/adapters/ChainedAdapters.kt deleted file mode 100644 index e1c5c18..0000000 --- a/core/src/main/kotlin/ca/allanwang/kau/adapters/ChainedAdapters.kt +++ /dev/null @@ -1,85 +0,0 @@ -package ca.allanwang.kau.adapters - -import android.support.v7.widget.LinearLayoutManager -import android.support.v7.widget.RecyclerView -import com.mikepenz.fastadapter.IItem -import com.mikepenz.fastadapter.adapters.HeaderAdapter -import com.mikepenz.fastadapter.commons.adapters.FastItemAdapter -import org.jetbrains.anko.collections.forEachReversedWithIndex -import java.util.* - -/** - * Created by Allan Wang on 2017-06-27. - * - * Once bounded to a [RecyclerView], this will - * - Chain together a list of [HeaderAdapter]s, backed by a generic [FastItemAdapter] - * - Add a [LinearLayoutManager] to the recycler - * - Add a listener for when a new adapter segment is being used - */ -class ChainedAdapters<T>(vararg items: Pair<T, SectionAdapter<*>>) { - private val chain: MutableList<Pair<T, SectionAdapter<*>>> = mutableListOf(*items) - val baseAdapter: FastItemAdapter<IItem<*, *>> = FastItemAdapter() - private val indexStack = Stack<Int>() - var recycler: RecyclerView? = null - val firstVisibleItemPosition: Int - get() = (recycler?.layoutManager as LinearLayoutManager?)?.findFirstVisibleItemPosition() ?: throw IllegalArgumentException("No recyclerview was bounded to the chain adapters") - - fun add(vararg items: Pair<T, SectionAdapter<*>>) = add(items.toList()) - - fun add(items: Collection<Pair<T, SectionAdapter<*>>>): ChainedAdapters<T> { - if (recycler != null) throw IllegalAccessException("Chain adapter is already bounded to a recycler; cannot add directly.") - items.map { it.second }.forEachIndexed { index, sectionAdapter -> sectionAdapter.sectionOrder = chain.size + 1 + index } - chain.addAll(items) - return this - } - - operator fun get(index: Int) = chain[index] - - /** - * Attaches the chain to a recycler - * After this stage, any modifications to the adapters must be done through external references - * You may still get the generic header adapters through the get operator - * Binding the recycler also involves supplying a callback, which returns - * the item (T) associated with the adapter, - * the index (Int) of the current adapter - * and the dy (Int) as given by the scroll listener - */ - fun bindRecyclerView(recyclerView: RecyclerView, onAdapterSectionChanged: (item: T, index: Int, dy: Int) -> Unit) { - if (recycler != null) throw IllegalStateException("Chain adapter is already bounded") - if (chain.isEmpty()) throw IllegalArgumentException("No adapters have been added to the adapters list") - //wrap adapters - chain.map { it.second }.forEachReversedWithIndex { i, headerAdapter -> - if (i == chain.size - 1) headerAdapter.wrap(baseAdapter) - else headerAdapter.wrap(chain[i + 1].second) - } - recycler = recyclerView - indexStack.push(0) - with(recyclerView) { - layoutManager = LinearLayoutManager(context) - adapter = chain.first().second - addOnScrollListener(object : RecyclerView.OnScrollListener() { - override fun onScrolled(rv: RecyclerView, dx: Int, dy: Int) { - super.onScrolled(rv, dx, dy) - val topPosition = firstVisibleItemPosition - val currentAdapterIndex = indexStack.peek() - if (dy > 0) { - //look ahead from current adapter - val nextAdapterIndex = (currentAdapterIndex until chain.size).asSequence() - .firstOrNull { - val adapter = chain[it].second - adapter.adapterItemCount > 0 && adapter.getGlobalPosition(adapter.adapterItemCount - 1) >= topPosition - } ?: currentAdapterIndex - if (nextAdapterIndex == currentAdapterIndex) return - indexStack.push(nextAdapterIndex) - onAdapterSectionChanged(chain[indexStack.peek()].first, indexStack.peek(), dy) - } else if (currentAdapterIndex == 0) { - return //All adapters may be empty; in this case, if we are already at the beginning, don't bother checking - } else if (chain[currentAdapterIndex].second.getGlobalPosition(0) > topPosition) { - indexStack.pop() - onAdapterSectionChanged(chain[indexStack.peek()].first, indexStack.peek(), dy) - } - } - }) - } - } -}
\ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/adapters/FastItemThemedAdapter.kt b/core/src/main/kotlin/ca/allanwang/kau/adapters/FastItemThemedAdapter.kt deleted file mode 100644 index 66fec4b..0000000 --- a/core/src/main/kotlin/ca/allanwang/kau/adapters/FastItemThemedAdapter.kt +++ /dev/null @@ -1,189 +0,0 @@ -package ca.allanwang.kau.adapters - -import android.content.res.ColorStateList -import android.view.View -import android.widget.ImageView -import android.widget.TextView -import ca.allanwang.kau.utils.adjustAlpha -import ca.allanwang.kau.views.createSimpleRippleDrawable -import com.mikepenz.fastadapter.IExpandable -import com.mikepenz.fastadapter.IItem -import com.mikepenz.fastadapter.ISubItem -import com.mikepenz.fastadapter.commons.adapters.FastItemAdapter - -/** - * Created by Allan Wang on 2017-06-29. - * - * Adapter with a set of colors that will be added to all subsequent items - * Changing a color while the adapter is not empty will reload all items - * - * This adapter overrides every method where an item is added - * If that item extends [ThemableIItem], then the colors will be set - */ -class FastItemThemedAdapter<Item : IItem<*, *>>( - textColor: Int? = null, - backgroundColor: Int? = null, - accentColor: Int? = null -) : FastItemAdapter<Item>() { - constructor(colors: ThemableIItemColors) : this(colors.textColor, colors.backgroundColor, colors.accentColor) - - var textColor: Int? = textColor - set(value) { - if (field == value) return - field = value - themeChanged() - } - var backgroundColor: Int? = backgroundColor - set(value) { - if (field == value) return - field = value - themeChanged() - } - var accentColor: Int? = accentColor - set(value) { - if (field == value) return - field = value - themeChanged() - } - - fun setColors(colors: ThemableIItemColors) { - this.textColor = colors.textColor - this.backgroundColor = colors.backgroundColor - this.accentColor = colors.accentColor - } - - fun themeChanged() { - if (adapterItemCount == 0) return - injectTheme(adapterItems) - notifyAdapterDataSetChanged() - } - - override fun add(position: Int, items: MutableList<Item>): FastItemAdapter<Item> { - injectTheme(items) - return super.add(position, items) - } - - override fun add(position: Int, item: Item): FastItemAdapter<Item> { - injectTheme(item) - return super.add(position, item) - } - - override fun add(item: Item): FastItemAdapter<Item> { - injectTheme(item) - return super.add(item) - } - - override fun add(items: MutableList<Item>): FastItemAdapter<Item> { - injectTheme(items) - injectTheme(items) - return super.add(items) - } - - override fun set(items: MutableList<Item>?): FastItemAdapter<Item> { - injectTheme(items) - return super.set(items) - } - - override fun set(position: Int, item: Item): FastItemAdapter<Item> { - injectTheme(item) - return super.set(position, item) - } - - override fun setNewList(items: MutableList<Item>?, retainFilter: Boolean): FastItemAdapter<Item> { - injectTheme(items) - return super.setNewList(items, retainFilter) - } - - override fun setNewList(items: MutableList<Item>?): FastItemAdapter<Item> { - injectTheme(items) - return super.setNewList(items) - } - - override fun <T, S> setSubItems(collapsible: T, subItems: MutableList<S>?): T where S : IItem<*, *>?, T : IItem<*, *>?, T : IExpandable<T, S>?, S : ISubItem<Item, T>? { - injectTheme(subItems) - return super.setSubItems(collapsible, subItems) - } - - internal fun injectTheme(items: Collection<IItem<*, *>?>?) { - items?.forEach { injectTheme(it) } - } - - internal fun injectTheme(item: IItem<*, *>?) { - if (item is ThemableIItem && item.themeEnabled) { - item.textColor = textColor - item.backgroundColor = backgroundColor - item.accentColor = accentColor - } - } -} - -interface ThemableIItemColors { - var textColor: Int? - var backgroundColor: Int? - var accentColor: Int? -} - -class ThemableIItemColorsDelegate : ThemableIItemColors { - override var textColor: Int? = null - override var backgroundColor: Int? = null - override var accentColor: Int? = null -} - -/** - * Interface that needs to be implemented by every iitem - * Holds the color values and has helper methods to inject the colors - */ -interface ThemableIItem : ThemableIItemColors { - var themeEnabled: Boolean - fun bindTextColor(vararg views: TextView?) - fun bindTextColorSecondary(vararg views: TextView?) - fun bindDividerColor(vararg views: View?) - fun bindAccentColor(vararg views: TextView?) - fun bindBackgroundColor(vararg views: View?) - fun bindBackgroundRipple(vararg views: View?) - fun bindIconColor(vararg views: ImageView?) -} - -/** - * The delegate for [ThemableIItem] - */ -class ThemableIItemDelegate : ThemableIItem, ThemableIItemColors by ThemableIItemColorsDelegate() { - override var themeEnabled: Boolean = true - - override fun bindTextColor(vararg views: TextView?) { - val color = textColor ?: return - views.forEach { it?.setTextColor(color) } - } - - override fun bindTextColorSecondary(vararg views: TextView?) { - val color = textColor?.adjustAlpha(0.8f) ?: return - views.forEach { it?.setTextColor(color) } - } - - override fun bindAccentColor(vararg views: TextView?) { - val color = accentColor ?: textColor ?: return - views.forEach { it?.setTextColor(color) } - } - - override fun bindDividerColor(vararg views: View?) { - val color = (textColor ?: accentColor)?.adjustAlpha(0.1f) ?: return - views.forEach { it?.setBackgroundColor(color) } - } - - override fun bindBackgroundColor(vararg views: View?) { - val color = backgroundColor ?: return - views.forEach { it?.setBackgroundColor(color) } - } - - override fun bindBackgroundRipple(vararg views: View?) { - val foreground = accentColor ?: textColor ?: return - val background = backgroundColor ?: return - val ripple = createSimpleRippleDrawable(foreground, background) - views.forEach { it?.background = ripple } - } - - override fun bindIconColor(vararg views: ImageView?) { - val color = accentColor ?: textColor ?: return - views.forEach { it?.drawable?.setTintList(ColorStateList.valueOf(color)) } - } -}
\ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/adapters/SectionAdapter.kt b/core/src/main/kotlin/ca/allanwang/kau/adapters/SectionAdapter.kt deleted file mode 100644 index cf7205a..0000000 --- a/core/src/main/kotlin/ca/allanwang/kau/adapters/SectionAdapter.kt +++ /dev/null @@ -1,13 +0,0 @@ -package ca.allanwang.kau.adapters - -import com.mikepenz.fastadapter.IItem -import com.mikepenz.fastadapter.adapters.HeaderAdapter - -/** - * Created by Allan Wang on 2017-06-27. - * - * Extension of [HeaderAdapter] where we can define the order - */ -class SectionAdapter<Item : IItem<*, *>>(var sectionOrder: Int = 100) : HeaderAdapter<Item>() { - override fun getOrder(): Int = sectionOrder -}
\ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/animators/BaseDelayAnimator.kt b/core/src/main/kotlin/ca/allanwang/kau/animators/BaseDelayAnimator.kt deleted file mode 100644 index c649376..0000000 --- a/core/src/main/kotlin/ca/allanwang/kau/animators/BaseDelayAnimator.kt +++ /dev/null @@ -1,45 +0,0 @@ -package ca.allanwang.kau.animators - -import android.support.v7.widget.RecyclerView -import android.view.ViewPropertyAnimator - -/** - * Created by Allan Wang on 2017-06-27. - * - * Base for delayed animators - * item delay factor by default can be 0.125f - */ -abstract class BaseDelayAnimator(val itemDelayFactor: Float) : DefaultAnimator() { - - override abstract fun addAnimationPrepare(holder: RecyclerView.ViewHolder) - - override fun addAnimation(holder: RecyclerView.ViewHolder): ViewPropertyAnimator { - return holder.itemView.animate().apply { - startDelay = Math.max(0L, (holder.adapterPosition * addDuration * itemDelayFactor).toLong()) - duration = this@BaseDelayAnimator.addDuration - interpolator = this@BaseDelayAnimator.interpolator - } - } - - - override abstract fun addAnimationCleanup(holder: RecyclerView.ViewHolder) - - override fun getAddDelay(remove: Long, move: Long, change: Long): Long = 0 - - override fun getRemoveDelay(remove: Long, move: Long, change: Long): Long = 0 - - /** - * Partial removal animation - * As of now, all it does it change the alpha - * To have it slide, add onto it in a sub class - */ - override fun removeAnimation(holder: RecyclerView.ViewHolder): ViewPropertyAnimator { - return holder.itemView.animate().apply { - duration = this@BaseDelayAnimator.removeDuration - startDelay = Math.max(0L, (holder.adapterPosition * removeDuration * itemDelayFactor).toLong()) - interpolator = this@BaseDelayAnimator.interpolator - } - } - - override abstract fun removeAnimationCleanup(holder: RecyclerView.ViewHolder) -}
\ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/animators/BaseItemAnimator.java b/core/src/main/kotlin/ca/allanwang/kau/animators/BaseItemAnimator.java deleted file mode 100644 index 69c2cf3..0000000 --- a/core/src/main/kotlin/ca/allanwang/kau/animators/BaseItemAnimator.java +++ /dev/null @@ -1,764 +0,0 @@ -package ca.allanwang.kau.animators; - -/* - * Created by Allan Wang on 2017-06-27. - * - * Based on Item Animator by {@author Mike Penz} - * Rewritten to match with the updated compat dependencies - */ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.TimeInterpolator; -import android.animation.ValueAnimator; -import android.support.annotation.NonNull; -import android.support.v4.view.ViewCompat; -import android.support.v7.widget.RecyclerView.ViewHolder; -import android.support.v7.widget.SimpleItemAnimator; -import android.view.View; -import android.view.ViewPropertyAnimator; -import android.view.animation.Interpolator; - -import java.util.ArrayList; -import java.util.List; - -/** - * This implementation of {@link android.support.v7.widget.RecyclerView.ItemAnimator} provides basic - * animations on remove, add, and move events that happen to the items in - * a RecyclerView. RecyclerView uses a DefaultItemAnimator by default. - * - * @see android.support.v7.widget.RecyclerView#setItemAnimator(android.support.v7.widget.RecyclerView.ItemAnimator) - */ -public abstract class BaseItemAnimator extends SimpleItemAnimator { - private static final boolean DEBUG = false; - - private static TimeInterpolator sDefaultInterpolator; - - private ArrayList<ViewHolder> mPendingRemovals = new ArrayList<>(); - private ArrayList<ViewHolder> mPendingAdditions = new ArrayList<>(); - private ArrayList<MoveInfo> mPendingMoves = new ArrayList<>(); - private ArrayList<ChangeInfo> mPendingChanges = new ArrayList<>(); - - ArrayList<ArrayList<ViewHolder>> mAdditionsList = new ArrayList<>(); - ArrayList<ArrayList<MoveInfo>> mMovesList = new ArrayList<>(); - ArrayList<ArrayList<ChangeInfo>> mChangesList = new ArrayList<>(); - - ArrayList<ViewHolder> mAddAnimations = new ArrayList<>(); - ArrayList<ViewHolder> mMoveAnimations = new ArrayList<>(); - ArrayList<ViewHolder> mRemoveAnimations = new ArrayList<>(); - ArrayList<ViewHolder> mChangeAnimations = new ArrayList<>(); - - public Interpolator interpolator; - - private static class MoveInfo { - public ViewHolder holder; - public int fromX, fromY, toX, toY; - - MoveInfo(ViewHolder holder, int fromX, int fromY, int toX, int toY) { - this.holder = holder; - this.fromX = fromX; - this.fromY = fromY; - this.toX = toX; - this.toY = toY; - } - } - - public static class ChangeInfo { - public ViewHolder oldHolder, newHolder; - public int fromX, fromY, toX, toY; - - private ChangeInfo(ViewHolder oldHolder, ViewHolder newHolder) { - this.oldHolder = oldHolder; - this.newHolder = newHolder; - } - - ChangeInfo(ViewHolder oldHolder, ViewHolder newHolder, - int fromX, int fromY, int toX, int toY) { - this(oldHolder, newHolder); - this.fromX = fromX; - this.fromY = fromY; - this.toX = toX; - this.toY = toY; - } - - @Override - public String toString() { - return "ChangeInfo{" - + "oldHolder=" + oldHolder - + ", newHolder=" + newHolder - + ", fromX=" + fromX - + ", fromY=" + fromY - + ", toX=" + toX - + ", toY=" + toY - + '}'; - } - } - - @Override - public void runPendingAnimations() { - boolean removalsPending = !mPendingRemovals.isEmpty(); - boolean movesPending = !mPendingMoves.isEmpty(); - boolean changesPending = !mPendingChanges.isEmpty(); - boolean additionsPending = !mPendingAdditions.isEmpty(); - if (!removalsPending && !movesPending && !additionsPending && !changesPending) { - // nothing to animate - return; - } - // First, remove stuff - for (ViewHolder holder : mPendingRemovals) { - animateRemoveImpl(holder); - } - mPendingRemovals.clear(); - // Next, move stuff - if (movesPending) { - final ArrayList<MoveInfo> moves = new ArrayList<>(); - moves.addAll(mPendingMoves); - mMovesList.add(moves); - mPendingMoves.clear(); - Runnable mover = new Runnable() { - @Override - public void run() { - for (MoveInfo moveInfo : moves) { - animateMoveImpl(moveInfo.holder, moveInfo.fromX, moveInfo.fromY, - moveInfo.toX, moveInfo.toY); - } - moves.clear(); - mMovesList.remove(moves); - } - }; - if (removalsPending) { - View view = moves.get(0).holder.itemView; - ViewCompat.postOnAnimationDelayed(view, mover, 0); - } else { - mover.run(); - } - } - // Next, change stuff, to run in parallel with move animations - if (changesPending) { - final ArrayList<ChangeInfo> changes = new ArrayList<>(); - changes.addAll(mPendingChanges); - mChangesList.add(changes); - mPendingChanges.clear(); - Runnable changer = new Runnable() { - @Override - public void run() { - for (ChangeInfo change : changes) { - animateChangeImpl(change); - } - changes.clear(); - mChangesList.remove(changes); - } - }; - if (removalsPending) { - ViewHolder holder = changes.get(0).oldHolder; - long moveDuration = movesPending ? getMoveDuration() : 0; - ViewCompat.postOnAnimationDelayed(holder.itemView, changer, getRemoveDelay(getRemoveDuration(), moveDuration, getChangeDuration())); - } else { - changer.run(); - } - } - // Next, add stuff - if (additionsPending) { - final ArrayList<ViewHolder> additions = new ArrayList<>(); - additions.addAll(mPendingAdditions); - mAdditionsList.add(additions); - mPendingAdditions.clear(); - Runnable adder = new Runnable() { - @Override - public void run() { - for (ViewHolder holder : additions) { - animateAddImpl(holder); - } - additions.clear(); - mAdditionsList.remove(additions); - } - }; - if (removalsPending || movesPending || changesPending) { - long removeDuration = removalsPending ? getRemoveDuration() : 0; - long moveDuration = movesPending ? getMoveDuration() : 0; - long changeDuration = changesPending ? getChangeDuration() : 0; - View view = additions.get(0).itemView; - ViewCompat.postOnAnimationDelayed(view, adder, getAddDelay(removeDuration, moveDuration, changeDuration)); - } else { - adder.run(); - } - } - } - - /** - * used to calculated the delay until the remove animation should start - * - * @param remove the remove duration - * @param move the move duration - * @param change the change duration - * @return the calculated delay for the remove items animation - */ - public long getRemoveDelay(long remove, long move, long change) { - return remove + Math.max(move, change); - } - - /** - * used to calculated the delay until the add animation should start - * - * @param remove the remove duration - * @param move the move duration - * @param change the change duration - * @return the calculated delay for the add items animation - */ - public long getAddDelay(long remove, long move, long change) { - return remove + Math.max(move, change); - } - - @Override - public boolean animateRemove(final ViewHolder holder) { - resetAnimation(holder); - mPendingRemovals.add(holder); - return true; - } - - private void animateRemoveImpl(final ViewHolder holder) { - final ViewPropertyAnimator animation = removeAnimation(holder); - mRemoveAnimations.add(holder); - animation.setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animator) { - dispatchRemoveStarting(holder); - } - - @Override - public void onAnimationEnd(Animator animator) { - animation.setListener(null); - removeAnimationCleanup(holder); - dispatchRemoveFinished(holder); - mRemoveAnimations.remove(holder); - dispatchFinishedWhenDone(); - } - }).start(); - } - - abstract public ViewPropertyAnimator removeAnimation(ViewHolder holder); - - abstract public void removeAnimationCleanup(ViewHolder holder); - - @Override - public boolean animateAdd(final ViewHolder holder) { - resetAnimation(holder); - addAnimationPrepare(holder); - mPendingAdditions.add(holder); - return true; - } - - void animateAddImpl(final ViewHolder holder) { - final ViewPropertyAnimator animation = addAnimation(holder); - mAddAnimations.add(holder); - animation.setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animator) { - dispatchAddStarting(holder); - } - - @Override - public void onAnimationCancel(Animator animator) { - addAnimationCleanup(holder); - } - - @Override - public void onAnimationEnd(Animator animator) { - animation.setListener(null); - dispatchAddFinished(holder); - mAddAnimations.remove(holder); - dispatchFinishedWhenDone(); - addAnimationCleanup(holder); - } - }).start(); - } - - /** - * the animation to prepare the view before the add animation is run - * - * @param holder - */ - public abstract void addAnimationPrepare(ViewHolder holder); - - /** - * the animation for adding a view - * - * @param holder - * @return - */ - public abstract ViewPropertyAnimator addAnimation(ViewHolder holder); - - /** - * the cleanup method if the animation needs to be stopped. and tro prepare for the next view - * - * @param holder - */ - abstract void addAnimationCleanup(ViewHolder holder); - - @Override - public boolean animateMove(final ViewHolder holder, int fromX, int fromY, - int toX, int toY) { - final View view = holder.itemView; - fromX += (int) holder.itemView.getTranslationX(); - fromY += (int) holder.itemView.getTranslationY(); - resetAnimation(holder); - int deltaX = toX - fromX; - int deltaY = toY - fromY; - if (deltaX == 0 && deltaY == 0) { - dispatchMoveFinished(holder); - return false; - } - if (deltaX != 0) { - view.setTranslationX(-deltaX); - } - if (deltaY != 0) { - view.setTranslationY(-deltaY); - } - mPendingMoves.add(new MoveInfo(holder, fromX, fromY, toX, toY)); - return true; - } - - void animateMoveImpl(final ViewHolder holder, int fromX, int fromY, int toX, int toY) { - final View view = holder.itemView; - final int deltaX = toX - fromX; - final int deltaY = toY - fromY; - if (deltaX != 0) { - view.animate().translationX(0); - } - if (deltaY != 0) { - view.animate().translationY(0); - } - // TODO: make EndActions end listeners instead, since end actions aren't called when - // vpas are canceled (and can't end them. why?) - // need listener functionality in VPACompat for this. Ick. - final ViewPropertyAnimator animation = view.animate(); - mMoveAnimations.add(holder); - animation.setDuration(getMoveDuration()).setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animator) { - dispatchMoveStarting(holder); - } - - @Override - public void onAnimationCancel(Animator animator) { - if (deltaX != 0) { - view.setTranslationX(0); - } - if (deltaY != 0) { - view.setTranslationY(0); - } - } - - @Override - public void onAnimationEnd(Animator animator) { - animation.setListener(null); - dispatchMoveFinished(holder); - mMoveAnimations.remove(holder); - dispatchFinishedWhenDone(); - } - }).start(); - } - - @Override - public boolean animateChange(ViewHolder oldHolder, ViewHolder newHolder, - int fromX, int fromY, int toX, int toY) { - if (oldHolder == newHolder) { - // Don't know how to run change animations when the same view holder is re-used. - // run a move animation to handle position changes. - return animateMove(oldHolder, fromX, fromY, toX, toY); - } - changeAnimation(oldHolder, newHolder, - fromX, fromY, toX, toY); - mPendingChanges.add(new ChangeInfo(oldHolder, newHolder, fromX, fromY, toX, toY)); - return true; - } - - void animateChangeImpl(final ChangeInfo changeInfo) { - final ViewHolder holder = changeInfo.oldHolder; - final View view = holder == null ? null : holder.itemView; - final ViewHolder newHolder = changeInfo.newHolder; - final View newView = newHolder != null ? newHolder.itemView : null; - if (view != null) { - final ViewPropertyAnimator oldViewAnim = changeOldAnimation(holder, changeInfo); - mChangeAnimations.add(changeInfo.oldHolder); - oldViewAnim.setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animator) { - dispatchChangeStarting(changeInfo.oldHolder, true); - } - - @Override - public void onAnimationEnd(Animator animator) { - oldViewAnim.setListener(null); - changeAnimationCleanup(holder); - view.setTranslationX(0); - view.setTranslationY(0); - dispatchChangeFinished(changeInfo.oldHolder, true); - mChangeAnimations.remove(changeInfo.oldHolder); - dispatchFinishedWhenDone(); - } - }).start(); - } - if (newView != null) { - final ViewPropertyAnimator newViewAnimation = changeNewAnimation(newHolder); - mChangeAnimations.add(changeInfo.newHolder); - newViewAnimation.setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animator) { - dispatchChangeStarting(changeInfo.newHolder, false); - } - - @Override - public void onAnimationEnd(Animator animator) { - newViewAnimation.setListener(null); - changeAnimationCleanup(newHolder); - newView.setTranslationX(0); - newView.setTranslationY(0); - dispatchChangeFinished(changeInfo.newHolder, false); - mChangeAnimations.remove(changeInfo.newHolder); - dispatchFinishedWhenDone(); - } - }).start(); - } - } - - /** - * the whole change animation if we have to cross animate two views - * - * @param oldHolder - * @param newHolder - * @param fromX - * @param fromY - * @param toX - * @param toY - */ - public void changeAnimation(ViewHolder oldHolder, ViewHolder newHolder, int fromX, int fromY, int toX, int toY) { - final float prevTranslationX = oldHolder.itemView.getTranslationX(); - final float prevTranslationY = oldHolder.itemView.getTranslationY(); - final float prevValue = oldHolder.itemView.getAlpha(); - resetAnimation(oldHolder); - int deltaX = (int) (toX - fromX - prevTranslationX); - int deltaY = (int) (toY - fromY - prevTranslationY); - // recover prev translation state after ending animation - oldHolder.itemView.setTranslationX(prevTranslationX); - oldHolder.itemView.setTranslationY(prevTranslationY); - - oldHolder.itemView.setAlpha(prevValue); - if (newHolder != null) { - // carry over translation values - resetAnimation(newHolder); - newHolder.itemView.setTranslationX(-deltaX); - newHolder.itemView.setTranslationY(-deltaY); - newHolder.itemView.setAlpha(0); - } - } - - /** - * the animation for removing the old view - * - * @param holder - * @return - */ - public abstract ViewPropertyAnimator changeOldAnimation(ViewHolder holder, ChangeInfo changeInfo); - - /** - * the animation for changing the new view - * - * @param holder - * @return - */ - public abstract ViewPropertyAnimator changeNewAnimation(ViewHolder holder); - - /** - * the cleanup method if the animation needs to be stopped. and tro prepare for the next view - * - * @param holder - */ - public abstract void changeAnimationCleanup(ViewHolder holder); - - private void endChangeAnimation(List<ChangeInfo> infoList, ViewHolder item) { - for (int i = infoList.size() - 1; i >= 0; i--) { - ChangeInfo changeInfo = infoList.get(i); - if (endChangeAnimationIfNecessary(changeInfo, item)) { - if (changeInfo.oldHolder == null && changeInfo.newHolder == null) { - infoList.remove(changeInfo); - } - } - } - } - - private void endChangeAnimationIfNecessary(ChangeInfo changeInfo) { - if (changeInfo.oldHolder != null) { - endChangeAnimationIfNecessary(changeInfo, changeInfo.oldHolder); - } - if (changeInfo.newHolder != null) { - endChangeAnimationIfNecessary(changeInfo, changeInfo.newHolder); - } - } - - private boolean endChangeAnimationIfNecessary(ChangeInfo changeInfo, ViewHolder item) { - boolean oldItem = false; - if (changeInfo.newHolder == item) { - changeInfo.newHolder = null; - } else if (changeInfo.oldHolder == item) { - changeInfo.oldHolder = null; - oldItem = true; - } else { - return false; - } - changeAnimationCleanup(item); - item.itemView.setTranslationX(0); - item.itemView.setTranslationY(0); - dispatchChangeFinished(item, oldItem); - return true; - } - - @Override - public void endAnimation(ViewHolder item) { - final View view = item.itemView; - // this will trigger end callback which should set properties to their target values. - view.animate().cancel(); - // TODO if some other animations are chained to end, how do we cancel them as well? - for (int i = mPendingMoves.size() - 1; i >= 0; i--) { - MoveInfo moveInfo = mPendingMoves.get(i); - if (moveInfo.holder == item) { - view.setTranslationY(0); - view.setTranslationX(0); - dispatchMoveFinished(item); - mPendingMoves.remove(i); - } - } - endChangeAnimation(mPendingChanges, item); - if (mPendingRemovals.remove(item)) { - removeAnimationCleanup(item); - dispatchRemoveFinished(item); - } - if (mPendingAdditions.remove(item)) { - addAnimationCleanup(item); - dispatchAddFinished(item); - } - - for (int i = mChangesList.size() - 1; i >= 0; i--) { - ArrayList<ChangeInfo> changes = mChangesList.get(i); - endChangeAnimation(changes, item); - if (changes.isEmpty()) { - mChangesList.remove(i); - } - } - for (int i = mMovesList.size() - 1; i >= 0; i--) { - ArrayList<MoveInfo> moves = mMovesList.get(i); - for (int j = moves.size() - 1; j >= 0; j--) { - MoveInfo moveInfo = moves.get(j); - if (moveInfo.holder == item) { - view.setTranslationY(0); - view.setTranslationX(0); - dispatchMoveFinished(item); - moves.remove(j); - if (moves.isEmpty()) { - mMovesList.remove(i); - } - break; - } - } - } - for (int i = mAdditionsList.size() - 1; i >= 0; i--) { - ArrayList<ViewHolder> additions = mAdditionsList.get(i); - if (additions.remove(item)) { - addAnimationCleanup(item); - dispatchAddFinished(item); - if (additions.isEmpty()) { - mAdditionsList.remove(i); - } - } - } - - // animations should be ended by the cancel above. - //noinspection PointlessBooleanExpression,ConstantConditions - if (mRemoveAnimations.remove(item) && DEBUG) { - throw new IllegalStateException("after animation is cancelled, item should not be in " - + "mRemoveAnimations list"); - } - - //noinspection PointlessBooleanExpression,ConstantConditions - if (mAddAnimations.remove(item) && DEBUG) { - throw new IllegalStateException("after animation is cancelled, item should not be in " - + "mAddAnimations list"); - } - - //noinspection PointlessBooleanExpression,ConstantConditions - if (mChangeAnimations.remove(item) && DEBUG) { - throw new IllegalStateException("after animation is cancelled, item should not be in " - + "mChangeAnimations list"); - } - - //noinspection PointlessBooleanExpression,ConstantConditions - if (mMoveAnimations.remove(item) && DEBUG) { - throw new IllegalStateException("after animation is cancelled, item should not be in " - + "mMoveAnimations list"); - } - dispatchFinishedWhenDone(); - } - - private void resetAnimation(ViewHolder holder) { - - if (sDefaultInterpolator == null) { - sDefaultInterpolator = new ValueAnimator().getInterpolator(); - } - holder.itemView.animate().setInterpolator(sDefaultInterpolator); - endAnimation(holder); - } - - @Override - public boolean isRunning() { - return (!mPendingAdditions.isEmpty() - || !mPendingChanges.isEmpty() - || !mPendingMoves.isEmpty() - || !mPendingRemovals.isEmpty() - || !mMoveAnimations.isEmpty() - || !mRemoveAnimations.isEmpty() - || !mAddAnimations.isEmpty() - || !mChangeAnimations.isEmpty() - || !mMovesList.isEmpty() - || !mAdditionsList.isEmpty() - || !mChangesList.isEmpty()); - } - - /** - * Check the state of currently pending and running animations. If there are none - * pending/running, call {@link #dispatchAnimationsFinished()} to notify any - * listeners. - */ - void dispatchFinishedWhenDone() { - if (!isRunning()) { - dispatchAnimationsFinished(); - } - } - - @Override - public void endAnimations() { - int count = mPendingMoves.size(); - for (int i = count - 1; i >= 0; i--) { - MoveInfo item = mPendingMoves.get(i); - View view = item.holder.itemView; - view.setTranslationY(0); - view.setTranslationX(0); - dispatchMoveFinished(item.holder); - mPendingMoves.remove(i); - } - count = mPendingRemovals.size(); - for (int i = count - 1; i >= 0; i--) { - ViewHolder item = mPendingRemovals.get(i); - dispatchRemoveFinished(item); - mPendingRemovals.remove(i); - } - count = mPendingAdditions.size(); - for (int i = count - 1; i >= 0; i--) { - ViewHolder item = mPendingAdditions.get(i); - addAnimationCleanup(item); - dispatchAddFinished(item); - mPendingAdditions.remove(i); - } - count = mPendingChanges.size(); - for (int i = count - 1; i >= 0; i--) { - endChangeAnimationIfNecessary(mPendingChanges.get(i)); - } - mPendingChanges.clear(); - if (!isRunning()) { - return; - } - - int listCount = mMovesList.size(); - for (int i = listCount - 1; i >= 0; i--) { - ArrayList<MoveInfo> moves = mMovesList.get(i); - count = moves.size(); - for (int j = count - 1; j >= 0; j--) { - MoveInfo moveInfo = moves.get(j); - ViewHolder item = moveInfo.holder; - View view = item.itemView; - view.setTranslationY(0); - view.setTranslationX(0); - dispatchMoveFinished(moveInfo.holder); - moves.remove(j); - if (moves.isEmpty()) { - mMovesList.remove(moves); - } - } - } - listCount = mAdditionsList.size(); - for (int i = listCount - 1; i >= 0; i--) { - ArrayList<ViewHolder> additions = mAdditionsList.get(i); - count = additions.size(); - for (int j = count - 1; j >= 0; j--) { - ViewHolder item = additions.get(j); - View view = item.itemView; - addAnimationCleanup(item); - dispatchAddFinished(item); - additions.remove(j); - if (additions.isEmpty()) { - mAdditionsList.remove(additions); - } - } - } - listCount = mChangesList.size(); - for (int i = listCount - 1; i >= 0; i--) { - ArrayList<ChangeInfo> changes = mChangesList.get(i); - count = changes.size(); - for (int j = count - 1; j >= 0; j--) { - endChangeAnimationIfNecessary(changes.get(j)); - if (changes.isEmpty()) { - mChangesList.remove(changes); - } - } - } - - cancelAll(mRemoveAnimations); - cancelAll(mMoveAnimations); - cancelAll(mAddAnimations); - cancelAll(mChangeAnimations); - - dispatchAnimationsFinished(); - } - - void cancelAll(List<ViewHolder> viewHolders) { - for (int i = viewHolders.size() - 1; i >= 0; i--) { - viewHolders.get(i).itemView.animate().cancel(); - } - } - - /** - * {@inheritDoc} - * <p> - * If the payload list is not empty, DefaultItemAnimator returns <code>true</code>. - * When this is the case: - * <ul> - * <li>If you override {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)}, both - * ViewHolder arguments will be the same instance. - * </li> - * <li> - * If you are not overriding {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)}, - * then DefaultItemAnimator will call {@link #animateMove(ViewHolder, int, int, int, int)} and - * run a move animation instead. - * </li> - * </ul> - */ - @Override - public boolean canReuseUpdatedViewHolder(@NonNull ViewHolder viewHolder, - @NonNull List<Object> payloads) { - return !payloads.isEmpty() || super.canReuseUpdatedViewHolder(viewHolder, payloads); - } -} diff --git a/core/src/main/kotlin/ca/allanwang/kau/animators/BaseSlideAlphaAnimator.kt b/core/src/main/kotlin/ca/allanwang/kau/animators/BaseSlideAlphaAnimator.kt deleted file mode 100644 index a963358..0000000 --- a/core/src/main/kotlin/ca/allanwang/kau/animators/BaseSlideAlphaAnimator.kt +++ /dev/null @@ -1,52 +0,0 @@ -package ca.allanwang.kau.animators - -import android.support.v7.widget.RecyclerView -import android.view.ViewPropertyAnimator - -/** - * Created by Allan Wang on 2017-06-27. - * - * Base for sliding animators - * item delay factor by default can be 0.125f - */ -abstract class BaseSlideAlphaAnimator(itemDelayFactor: Float) : BaseDelayAnimator(itemDelayFactor) { - - override fun addAnimation(holder: RecyclerView.ViewHolder): ViewPropertyAnimator { - return super.addAnimation(holder).apply { - translationY(0f) - translationX(0f) - alpha(1f) - } - } - - final override fun addAnimationCleanup(holder: RecyclerView.ViewHolder) { - with(holder.itemView) { - translationY = 0f - translationX = 0f - alpha = 1f - } - } - - override fun getAddDelay(remove: Long, move: Long, change: Long): Long = 0 - - override fun getRemoveDelay(remove: Long, move: Long, change: Long): Long = 0 - - /** - * Partial removal animation - * As of now, all it does it change the alpha - * To have it slide, add onto it in a sub class - */ - override fun removeAnimation(holder: RecyclerView.ViewHolder): ViewPropertyAnimator { - return super.addAnimation(holder).apply { - alpha(0f) - } - } - - override final fun removeAnimationCleanup(holder: RecyclerView.ViewHolder) { - with(holder.itemView) { - translationY = 0f - translationX = 0f - alpha = 1f - } - } -}
\ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/animators/DefaultAnimator.kt b/core/src/main/kotlin/ca/allanwang/kau/animators/DefaultAnimator.kt deleted file mode 100644 index 9aeafde..0000000 --- a/core/src/main/kotlin/ca/allanwang/kau/animators/DefaultAnimator.kt +++ /dev/null @@ -1,63 +0,0 @@ -package ca.allanwang.kau.animators - -import android.support.v7.widget.RecyclerView -import android.view.ViewPropertyAnimator - -/** - * Created by Allan Wang on 2017-06-27. - */ -open class DefaultAnimator : BaseItemAnimator() { - - override fun removeAnimation(holder: RecyclerView.ViewHolder): ViewPropertyAnimator { - return holder.itemView.animate().apply { - alpha(0f) - duration = this@DefaultAnimator.removeDuration - interpolator = this@DefaultAnimator.interpolator - } - } - - override fun removeAnimationCleanup(holder: RecyclerView.ViewHolder) { - holder.itemView.alpha = 1f - } - - override fun addAnimationPrepare(holder: RecyclerView.ViewHolder) { - holder.itemView.alpha = 0f - } - - override fun addAnimation(holder: RecyclerView.ViewHolder): ViewPropertyAnimator { - return holder.itemView.animate().apply { - alpha(1f) - duration = this@DefaultAnimator.addDuration - interpolator = this@DefaultAnimator.interpolator - } - } - - override fun addAnimationCleanup(holder: RecyclerView.ViewHolder) { - holder.itemView.alpha = 1f - } - - override fun changeOldAnimation(holder: RecyclerView.ViewHolder, changeInfo: ChangeInfo): ViewPropertyAnimator { - return holder.itemView.animate().apply { - alpha(0f) - translationX(changeInfo.toX.toFloat() - changeInfo.fromX) - translationY(changeInfo.toY.toFloat() - changeInfo.fromY) - duration = this@DefaultAnimator.changeDuration - interpolator = this@DefaultAnimator.interpolator - } - } - - override fun changeNewAnimation(holder: RecyclerView.ViewHolder): ViewPropertyAnimator { - return holder.itemView.animate().apply { - alpha(1f) - translationX(0f) - translationY(0f) - duration = this@DefaultAnimator.changeDuration - interpolator = this@DefaultAnimator.interpolator - } - } - - override fun changeAnimationCleanup(holder: RecyclerView.ViewHolder) { - holder.itemView.alpha = 1f - } - -}
\ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/animators/FadeScaleAnimator.kt b/core/src/main/kotlin/ca/allanwang/kau/animators/FadeScaleAnimator.kt deleted file mode 100644 index e968cda..0000000 --- a/core/src/main/kotlin/ca/allanwang/kau/animators/FadeScaleAnimator.kt +++ /dev/null @@ -1,51 +0,0 @@ -package ca.allanwang.kau.animators - -import android.support.v7.widget.RecyclerView -import android.view.ViewPropertyAnimator - -/** - * Created by Allan Wang on 2017-06-29. - */ -open class FadeScaleAnimator(val scaleFactor: Float = 0.7f, itemDelayFactor: Float = 0.125f) : BaseDelayAnimator(itemDelayFactor) { - - override fun addAnimationPrepare(holder: RecyclerView.ViewHolder) { - with(holder.itemView) { - scaleX = scaleFactor - scaleY = scaleFactor - alpha = 0f - } - } - - override final fun addAnimation(holder: RecyclerView.ViewHolder): ViewPropertyAnimator { - return super.addAnimation(holder).apply { - scaleX(1f) - scaleY(1f) - alpha(1f) - } - } - - - final override fun addAnimationCleanup(holder: RecyclerView.ViewHolder) { - with(holder.itemView) { - scaleX = 1f - scaleY = 1f - alpha = 1f - } - } - - override fun removeAnimation(holder: RecyclerView.ViewHolder): ViewPropertyAnimator { - return super.removeAnimation(holder).apply { - scaleX(scaleFactor) - scaleY(scaleFactor) - alpha(0f) - } - } - - override final fun removeAnimationCleanup(holder: RecyclerView.ViewHolder) { - with(holder.itemView) { - translationY = 0f - translationX = 0f - alpha = 1f - } - } -}
\ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/animators/NoAnimator.kt b/core/src/main/kotlin/ca/allanwang/kau/animators/NoAnimator.kt deleted file mode 100644 index 244287b..0000000 --- a/core/src/main/kotlin/ca/allanwang/kau/animators/NoAnimator.kt +++ /dev/null @@ -1,41 +0,0 @@ -package ca.allanwang.kau.animators - -import android.support.v7.widget.RecyclerView -import android.view.ViewPropertyAnimator - -/** - * Created by Allan Wang on 2017-06-27. - * - * Truly have no animation - */ -class NoAnimator : DefaultAnimator() { - override fun addAnimationPrepare(holder: RecyclerView.ViewHolder) {} - - override fun addAnimation(holder: RecyclerView.ViewHolder): ViewPropertyAnimator = holder.itemView.animate().apply { duration = 0 } - - override fun addAnimationCleanup(holder: RecyclerView.ViewHolder) {} - - override fun changeOldAnimation(holder: RecyclerView.ViewHolder, changeInfo: ChangeInfo): ViewPropertyAnimator = holder.itemView.animate().apply { duration = 0 } - - override fun changeNewAnimation(holder: RecyclerView.ViewHolder): ViewPropertyAnimator = holder.itemView.animate().apply { duration = 0 } - - override fun changeAnimationCleanup(holder: RecyclerView.ViewHolder) {} - - override fun changeAnimation(oldHolder: RecyclerView.ViewHolder, newHolder: RecyclerView.ViewHolder?, fromX: Int, fromY: Int, toX: Int, toY: Int) {} - - override fun getAddDelay(remove: Long, move: Long, change: Long): Long = 0 - - override fun getAddDuration(): Long = 0 - - override fun getMoveDuration(): Long = 0 - - override fun getRemoveDuration(): Long = 0 - - override fun getChangeDuration(): Long = 0 - - override fun getRemoveDelay(remove: Long, move: Long, change: Long): Long = 0 - - override fun removeAnimation(holder: RecyclerView.ViewHolder): ViewPropertyAnimator = holder.itemView.animate().apply { duration = 0 } - - override fun removeAnimationCleanup(holder: RecyclerView.ViewHolder) {} -}
\ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/animators/SlideUpExitRightAnimator.kt b/core/src/main/kotlin/ca/allanwang/kau/animators/SlideUpExitRightAnimator.kt deleted file mode 100644 index 8670493..0000000 --- a/core/src/main/kotlin/ca/allanwang/kau/animators/SlideUpExitRightAnimator.kt +++ /dev/null @@ -1,23 +0,0 @@ -package ca.allanwang.kau.animators - -import android.support.v7.widget.RecyclerView -import android.view.ViewPropertyAnimator - -/** - * Created by Allan Wang on 2017-06-27. - */ -class SlideUpExitRightAnimator(itemDelayFactor: Float = 0.125f) : BaseSlideAlphaAnimator(itemDelayFactor) { - - override fun addAnimationPrepare(holder: RecyclerView.ViewHolder) { - with(holder.itemView) { - translationY = height.toFloat() - alpha = 0f - } - } - - override fun removeAnimation(holder: RecyclerView.ViewHolder): ViewPropertyAnimator { - return super.removeAnimation(holder).apply { - translationX(holder.itemView.width.toFloat()) - } - } -}
\ 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/changelog/Changelog.kt index 302d9dc..43546ae 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/changelog/Changelog.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/changelog/Changelog.kt @@ -2,7 +2,6 @@ package ca.allanwang.kau.changelog import android.content.Context import android.content.res.XmlResourceParser -import android.os.Handler import android.support.annotation.ColorInt import android.support.annotation.LayoutRes import android.support.annotation.XmlRes @@ -20,7 +19,6 @@ import com.afollestad.materialdialogs.MaterialDialog import org.jetbrains.anko.doAsync import org.jetbrains.anko.uiThread import org.xmlpull.v1.XmlPullParser -import java.util.* /** diff --git a/core/src/main/kotlin/ca/allanwang/kau/dialogs/color/CircleView.kt b/core/src/main/kotlin/ca/allanwang/kau/dialogs/color/CircleView.kt deleted file mode 100644 index 3430b42..0000000 --- a/core/src/main/kotlin/ca/allanwang/kau/dialogs/color/CircleView.kt +++ /dev/null @@ -1,228 +0,0 @@ -package ca.allanwang.kau.dialogs.color - -import android.animation.Animator -import android.animation.AnimatorListenerAdapter -import android.animation.ValueAnimator -import android.content.Context -import android.content.res.ColorStateList -import android.graphics.Canvas -import android.graphics.Color -import android.graphics.Paint -import android.graphics.Rect -import android.graphics.drawable.Drawable -import android.graphics.drawable.RippleDrawable -import android.graphics.drawable.ShapeDrawable -import android.graphics.drawable.StateListDrawable -import android.graphics.drawable.shapes.OvalShape -import android.os.Build -import android.support.annotation.ColorInt -import android.support.annotation.ColorRes -import android.support.annotation.FloatRange -import android.support.v4.view.GravityCompat -import android.support.v4.view.ViewCompat -import android.util.AttributeSet -import android.view.Gravity -import android.widget.FrameLayout -import android.widget.Toast -import ca.allanwang.kau.utils.color -import ca.allanwang.kau.utils.getDip -import ca.allanwang.kau.utils.toColor -import ca.allanwang.kau.utils.toHSV - -/** - * Created by Allan Wang on 2017-06-10. - * - * An extension of MaterialDialog's CircleView with animation selection - * [https://github.com/afollestad/material-dialogs/blob/master/commons/src/main/java/com/afollestad/materialdialogs/color/CircleView.java] - */ -class CircleView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : FrameLayout(context, attrs, defStyleAttr) { - - private val borderWidthMicro: Float = context.getDip(1f) - private val borderWidthSmall: Float = context.getDip(3f) - private val borderWidthLarge: Float = context.getDip(5f) - private var whiteOuterBound: Float = borderWidthLarge - - private val outerPaint: Paint = Paint().apply { isAntiAlias = true } - private val whitePaint: Paint = Paint().apply { isAntiAlias = true; color = Color.WHITE } - private val innerPaint: Paint = Paint().apply { isAntiAlias = true } - private var selected: Boolean = false - var withBorder: Boolean = false - get() = field - set(value) { - if (field != value) { - field = value - invalidate() - } - } - - init { - update(Color.DKGRAY) - setWillNotDraw(false) - } - - private fun update(@ColorInt color: Int) { - innerPaint.color = color - outerPaint.color = shiftColorDown(color) - - val selector = createSelector(color) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - val states = arrayOf(intArrayOf(android.R.attr.state_pressed)) - val colors = intArrayOf(shiftColorUp(color)) - val rippleColors = ColorStateList(states, colors) - foreground = RippleDrawable(rippleColors, selector, null) - } else { - foreground = selector - } - } - - override fun setBackgroundColor(@ColorInt color: Int) { - update(color) - requestLayout() - invalidate() - } - - override fun setBackgroundResource(@ColorRes color: Int) { - setBackgroundColor(context.color(color)) - } - - - @Deprecated("") - override fun setBackground(background: Drawable) { - throw IllegalStateException("Cannot use setBackground() on CircleView.") - } - - - @Deprecated("") - override fun setBackgroundDrawable(background: Drawable) { - throw IllegalStateException("Cannot use setBackgroundDrawable() on CircleView.") - } - - - @Deprecated("") - override fun setActivated(activated: Boolean) { - throw IllegalStateException("Cannot use setActivated() on CircleView.") - } - - override fun setSelected(selected: Boolean) { - this.selected = selected - whiteOuterBound = borderWidthLarge - invalidate() - } - - fun animateSelected(selected: Boolean) { - if (this.selected == selected) return - this.selected = true // We need to draw the other bands - val range = if (selected) Pair(-borderWidthSmall, borderWidthLarge) else Pair(borderWidthLarge, -borderWidthSmall) - val animator = ValueAnimator.ofFloat(range.first, range.second) - with(animator) { - reverse() - duration = 150L - addUpdateListener { animation -> - whiteOuterBound = animation.animatedValue as Float - invalidate() - } - addListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - this@CircleView.selected = selected - } - - override fun onAnimationCancel(animation: Animator) { - this@CircleView.selected = selected - } - }) - start() - } - } - - override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { - super.onMeasure(widthMeasureSpec, widthMeasureSpec) - setMeasuredDimension(measuredWidth, measuredWidth) - } - - override fun onDraw(canvas: Canvas) { - super.onDraw(canvas) - val centerWidth = (measuredWidth / 2).toFloat() - val centerHeight = (measuredHeight / 2).toFloat() - if (withBorder) canvas.drawCircle(centerWidth, centerHeight, centerWidth, whitePaint) - if (selected) { - val whiteRadius = centerWidth - whiteOuterBound - val innerRadius = whiteRadius - borderWidthSmall - if (whiteRadius >= centerWidth) { - canvas.drawCircle(centerWidth, centerHeight, centerWidth, whitePaint) - } else { - canvas.drawCircle(centerWidth, centerHeight, if (withBorder) centerWidth - borderWidthMicro else centerWidth, outerPaint) - canvas.drawCircle(centerWidth, centerHeight, whiteRadius, whitePaint) - } - canvas.drawCircle(centerWidth, centerHeight, innerRadius, innerPaint) - } else { - canvas.drawCircle(centerWidth, centerHeight, if (withBorder) centerWidth - borderWidthMicro else centerWidth, innerPaint) - } - } - - private fun createSelector(color: Int): Drawable { - val darkerCircle = ShapeDrawable(OvalShape()) - darkerCircle.paint.color = translucentColor(shiftColorUp(color)) - val stateListDrawable = StateListDrawable() - stateListDrawable.addState(intArrayOf(android.R.attr.state_pressed), darkerCircle) - return stateListDrawable - } - - fun showHint(color: Int) { - val screenPos = IntArray(2) - val displayFrame = Rect() - getLocationOnScreen(screenPos) - getWindowVisibleDisplayFrame(displayFrame) - val context = context - val width = width - val height = height - val midy = screenPos[1] + height / 2 - var referenceX = screenPos[0] + width / 2 - if (ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_LTR) { - val screenWidth = context.resources.displayMetrics.widthPixels - referenceX = screenWidth - referenceX // mirror - } - val cheatSheet = Toast - .makeText(context, String.format("#%06X", 0xFFFFFF and color), Toast.LENGTH_SHORT) - if (midy < displayFrame.height()) { - // Show along the top; follow action buttons - cheatSheet.setGravity(Gravity.TOP or GravityCompat.END, referenceX, - screenPos[1] + height - displayFrame.top) - } else { - // Show along the bottom center - cheatSheet.setGravity(Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL, 0, height) - } - cheatSheet.show() - } - - companion object { - - @ColorInt - @JvmStatic - private fun translucentColor(color: Int): Int { - val factor = 0.7f - val alpha = Math.round(Color.alpha(color) * factor) - val red = Color.red(color) - val green = Color.green(color) - val blue = Color.blue(color) - return Color.argb(alpha, red, green, blue) - } - - @ColorInt - @JvmStatic - fun shiftColor(@ColorInt color: Int, - @FloatRange(from = 0.0, to = 2.0) by: Float): Int { - if (by == 1f) return color - val hsv = color.toHSV() - hsv[2] *= by // value component - return hsv.toColor() - } - - @ColorInt - @JvmStatic - fun shiftColorDown(@ColorInt color: Int): Int = shiftColor(color, 0.9f) - - @ColorInt - @JvmStatic - fun shiftColorUp(@ColorInt color: Int): Int = shiftColor(color, 1.1f) - } -}
\ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/dialogs/color/ColorPalette.kt b/core/src/main/kotlin/ca/allanwang/kau/dialogs/color/ColorPalette.kt deleted file mode 100644 index 22bd0d4..0000000 --- a/core/src/main/kotlin/ca/allanwang/kau/dialogs/color/ColorPalette.kt +++ /dev/null @@ -1,349 +0,0 @@ -package ca.allanwang.kau.dialogs.color - -import android.graphics.Color - -/** - * @author Aidan Follestad (afollestad) - */ -internal object ColorPalette { - - val PRIMARY_COLORS: IntArray by lazy { - colorArrayOf( - "#F44336", - "#E91E63", - "#9C27B0", - "#673AB7", - "#3F51B5", - "#2196F3", - "#03A9F4", - "#00BCD4", - "#009688", - "#4CAF50", - "#8BC34A", - "#CDDC39", - "#FFEB3B", - "#FFC107", - "#FF9800", - "#FF5722", - "#795548", - "#9E9E9E", - "#607D8B") - } - - val PRIMARY_COLORS_SUB: Array<IntArray> by lazy { - arrayOf(colorArrayOf( - "#FFEBEE", - "#FFCDD2", - "#EF9A9A", - "#E57373", - "#EF5350", - "#F44336", - "#E53935", - "#D32F2F", - "#C62828", - "#B71C1C" - ), colorArrayOf( - "#FCE4EC", - "#F8BBD0", - "#F48FB1", - "#F06292", - "#EC407A", - "#E91E63", - "#D81B60", - "#C2185B", - "#AD1457", - "#880E4F" - ), colorArrayOf( - "#F3E5F5", - "#E1BEE7", - "#CE93D8", - "#BA68C8", - "#AB47BC", - "#9C27B0", - "#8E24AA", - "#7B1FA2", - "#6A1B9A", - "#4A148C" - ), colorArrayOf( - "#EDE7F6", - "#D1C4E9", - "#B39DDB", - "#9575CD", - "#7E57C2", - "#673AB7", - "#5E35B1", - "#512DA8", - "#4527A0", - "#311B92" - ), colorArrayOf( - "#E8EAF6", - "#C5CAE9", - "#9FA8DA", - "#7986CB", - "#5C6BC0", - "#3F51B5", - "#3949AB", - "#303F9F", - "#283593", - "#1A237E" - ), colorArrayOf( - "#E3F2FD", - "#BBDEFB", - "#90CAF9", - "#64B5F6", - "#42A5F5", - "#2196F3", - "#1E88E5", - "#1976D2", - "#1565C0", - "#0D47A1" - ), colorArrayOf( - "#E1F5FE", - "#B3E5FC", - "#81D4FA", - "#4FC3F7", - "#29B6F6", - "#03A9F4", - "#039BE5", - "#0288D1", - "#0277BD", - "#01579B" - ), colorArrayOf( - "#E0F7FA", - "#B2EBF2", - "#80DEEA", - "#4DD0E1", - "#26C6DA", - "#00BCD4", - "#00ACC1", - "#0097A7", - "#00838F", - "#006064" - ), colorArrayOf( - "#E0F2F1", - "#B2DFDB", - "#80CBC4", - "#4DB6AC", - "#26A69A", - "#009688", - "#00897B", - "#00796B", - "#00695C", - "#004D40" - ), colorArrayOf( - "#E8F5E9", - "#C8E6C9", - "#A5D6A7", - "#81C784", - "#66BB6A", - "#4CAF50", - "#43A047", - "#388E3C", - "#2E7D32", - "#1B5E20" - ), colorArrayOf( - "#F1F8E9", - "#DCEDC8", - "#C5E1A5", - "#AED581", - "#9CCC65", - "#8BC34A", - "#7CB342", - "#689F38", - "#558B2F", - "#33691E" - ), colorArrayOf( - "#F9FBE7", - "#F0F4C3", - "#E6EE9C", - "#DCE775", - "#D4E157", - "#CDDC39", - "#C0CA33", - "#AFB42B", - "#9E9D24", - "#827717" - ), colorArrayOf( - "#FFFDE7", - "#FFF9C4", - "#FFF59D", - "#FFF176", - "#FFEE58", - "#FFEB3B", - "#FDD835", - "#FBC02D", - "#F9A825", - "#F57F17" - ), colorArrayOf( - "#FFF8E1", - "#FFECB3", - "#FFE082", - "#FFD54F", - "#FFCA28", - "#FFC107", - "#FFB300", - "#FFA000", - "#FF8F00", - "#FF6F00" - ), colorArrayOf( - "#FFF3E0", - "#FFE0B2", - "#FFCC80", - "#FFB74D", - "#FFA726", - "#FF9800", - "#FB8C00", - "#F57C00", - "#EF6C00", - "#E65100" - ), colorArrayOf( - "#FBE9E7", - "#FFCCBC", - "#FFAB91", - "#FF8A65", - "#FF7043", - "#FF5722", - "#F4511E", - "#E64A19", - "#D84315", - "#BF360C" - ), colorArrayOf( - "#EFEBE9", - "#D7CCC8", - "#BCAAA4", - "#A1887F", - "#8D6E63", - "#795548", - "#6D4C41", - "#5D4037", - "#4E342E", - "#3E2723" - ), colorArrayOf( - "#FAFAFA", - "#F5F5F5", - "#EEEEEE", - "#E0E0E0", - "#BDBDBD", - "#9E9E9E", - "#757575", - "#616161", - "#424242", - "#212121" - ), colorArrayOf( - "#ECEFF1", - "#CFD8DC", - "#B0BEC5", - "#90A4AE", - "#78909C", - "#607D8B", - "#546E7A", - "#455A64", - "#37474F", - "#263238")) - } - - val ACCENT_COLORS: IntArray by lazy { - colorArrayOf( - "#FF1744", - "#F50057", - "#D500F9", - "#651FFF", - "#3D5AFE", - "#2979FF", - "#00B0FF", - "#00E5FF", - "#1DE9B6", - "#00E676", - "#76FF03", - "#C6FF00", - "#FFEA00", - "#FFC400", - "#FF9100", - "#FF3D00") - } - - val ACCENT_COLORS_SUB: Array<IntArray> by lazy { - arrayOf(colorArrayOf("#FF8A80", - "#FF5252", - "#FF1744", - "#D50000" - ), colorArrayOf( - "#FF80AB", - "#FF4081", - "#F50057", - "#C51162" - ), colorArrayOf( - "#EA80FC", - "#E040FB", - "#D500F9", - "#AA00FF" - ), colorArrayOf( - "#B388FF", - "#7C4DFF", - "#651FFF", - "#6200EA" - ), colorArrayOf( - "#8C9EFF", - "#536DFE", - "#3D5AFE", - "#304FFE" - ), colorArrayOf( - "#82B1FF", - "#448AFF", - "#2979FF", - "#2962FF" - ), colorArrayOf( - "#80D8FF", - "#40C4FF", - "#00B0FF", - "#0091EA" - ), colorArrayOf( - "#84FFFF", - "#18FFFF", - "#00E5FF", - "#00B8D4" - ), colorArrayOf( - "#A7FFEB", - "#64FFDA", - "#1DE9B6", - "#00BFA5" - ), colorArrayOf( - "#B9F6CA", - "#69F0AE", - "#00E676", - "#00C853" - ), colorArrayOf( - "#CCFF90", - "#B2FF59", - "#76FF03", - "#64DD17" - ), colorArrayOf( - "#F4FF81", - "#EEFF41", - "#C6FF00", - "#AEEA00" - ), colorArrayOf( - "#FFFF8D", - "#FFFF00", - "#FFEA00", - "#FFD600" - ), colorArrayOf( - "#FFE57F", - "#FFD740", - "#FFC400", - "#FFAB00" - ), colorArrayOf( - "#FFD180", - "#FFAB40", - "#FF9100", - "#FF6D00" - ), colorArrayOf( - "#FF9E80", - "#FF6E40", - "#FF3D00", - "#DD2C00")) - } - - fun colorArrayOf(vararg colors: String) = colors.map { Color.parseColor(it) }.toIntArray() -} - diff --git a/core/src/main/kotlin/ca/allanwang/kau/dialogs/color/ColorPickerDialog.kt b/core/src/main/kotlin/ca/allanwang/kau/dialogs/color/ColorPickerDialog.kt deleted file mode 100644 index 7c57c26..0000000 --- a/core/src/main/kotlin/ca/allanwang/kau/dialogs/color/ColorPickerDialog.kt +++ /dev/null @@ -1,82 +0,0 @@ -package ca.allanwang.kau.dialogs.color - -import android.content.Context -import android.graphics.Color -import android.support.annotation.DimenRes -import android.support.annotation.StringRes -import ca.allanwang.kau.R -import ca.allanwang.kau.utils.string -import com.afollestad.materialdialogs.MaterialDialog -import com.afollestad.materialdialogs.Theme - -class ColorBuilder : ColorContract { - override var title: String? = null - override var titleRes: Int = -1 - override var allowCustom: Boolean = true - override var allowCustomAlpha: Boolean = false - override var isAccent: Boolean = false - override var defaultColor: Int = Color.BLACK - override var doneText: Int = R.string.kau_done - override var backText: Int = R.string.kau_back - override var cancelText: Int = R.string.kau_cancel - override var presetText: Int = R.string.kau_md_presets - override var customText: Int = R.string.kau_md_custom - get() = if (allowCustom) field else 0 - override var dynamicButtonColors: Boolean = true - override var circleSizeRes: Int = R.dimen.kau_color_circle_size - override var colorCallback: ((selectedColor: Int) -> Unit)? = null - override var colorsTop: IntArray? = null - override var colorsSub: Array<IntArray>? = null - override var theme: Theme? = null -} - -interface ColorContract { - var title: String? - var titleRes: Int @StringRes set - var allowCustom: Boolean - var allowCustomAlpha: Boolean - var isAccent: Boolean - var defaultColor: Int @StringRes set - var doneText: Int @StringRes set - var backText: Int @StringRes set - var cancelText: Int @StringRes set - var presetText: Int - @StringRes set - var customText: Int @StringRes set - var dynamicButtonColors: Boolean - var circleSizeRes: Int @DimenRes set - var colorCallback: ((selectedColor: Int) -> Unit)? - var colorsTop: IntArray? - var colorsSub: Array<IntArray>? - var theme: Theme? -} - -/** - * This is the extension that allows us to initialize the dialog - * Note that this returns just the dialog; you still need to call .show() to show it - */ -fun Context.colorPickerDialog(action: ColorContract.() -> Unit): MaterialDialog { - val b = ColorBuilder() - b.action() - return colorPickerDialog(b) -} - -fun Context.colorPickerDialog(contract: ColorContract): MaterialDialog { - val view = ColorPickerView(this) - val dialog = with(MaterialDialog.Builder(this)) { - title(string(contract.titleRes, contract.title) ?: string(R.string.kau_md_color_palette)) - customView(view, false) - autoDismiss(false) - positiveText(contract.doneText) - negativeText(contract.cancelText) - if (contract.allowCustom) neutralText(contract.presetText) - onPositive { dialog, _ -> contract.colorCallback?.invoke(view.selectedColor); dialog.dismiss() } - onNegative { _, _ -> view.backOrCancel() } - if (contract.allowCustom) onNeutral { _, _ -> view.toggleCustom() } - showListener { view.refreshColors() } - if (contract.theme != null) theme(contract.theme!!) - build() - } - view.bind(contract, dialog) - return dialog -}
\ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/dialogs/color/ColorPickerView.kt b/core/src/main/kotlin/ca/allanwang/kau/dialogs/color/ColorPickerView.kt deleted file mode 100644 index 20e0259..0000000 --- a/core/src/main/kotlin/ca/allanwang/kau/dialogs/color/ColorPickerView.kt +++ /dev/null @@ -1,309 +0,0 @@ -package ca.allanwang.kau.dialogs.color - -import android.content.Context -import android.graphics.Color -import android.support.annotation.ColorInt -import android.support.v4.content.res.ResourcesCompat -import android.text.Editable -import android.text.InputFilter -import android.text.TextWatcher -import android.util.AttributeSet -import android.view.View -import android.view.ViewGroup -import android.widget.* -import ca.allanwang.kau.R -import ca.allanwang.kau.utils.* -import com.afollestad.materialdialogs.DialogAction -import com.afollestad.materialdialogs.MaterialDialog -import com.afollestad.materialdialogs.color.FillGridView -import java.util.* - -/** - * Created by Allan Wang on 2017-06-08. - * - * ColorPicker component of the ColorPickerDialog - */ -internal class ColorPickerView @JvmOverloads constructor( - context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : ScrollView(context, attrs, defStyleAttr) { - var selectedColor: Int = -1 - var isInSub: Boolean = false - var isInCustom: Boolean = false - var circleSize: Int = context.dimen(R.dimen.kau_color_circle_size).toInt() - val backgroundColor = context.resolveColor(R.attr.md_background_color, - if (context.resolveColor(android.R.attr.textColorPrimary).isColorDark) Color.WHITE else 0xff424242.toInt()) - val backgroundColorTint = backgroundColor.colorToForeground() - lateinit var dialog: MaterialDialog - lateinit var builder: ColorContract - lateinit var colorsTop: IntArray - var colorsSub: Array<IntArray>? = null - var topIndex: Int = -1 - var subIndex: Int = -1 - var colorIndex: Int - get() = if (isInSub) subIndex else topIndex - set(value) { - if (isInSub) subIndex = value - else { - topIndex = value - if (colorsSub != null && colorsSub!!.size > value) { - dialog.setActionButton(DialogAction.NEGATIVE, builder.backText) - isInSub = true - invalidateGrid() - } - } - } - - - val gridView: FillGridView by bindView(R.id.md_grid) - val customFrame: LinearLayout by bindView(R.id.md_colorChooserCustomFrame) - val customColorIndicator: View by bindView(R.id.md_colorIndicator) - val hexInput: EditText by bindView(R.id.md_hexInput) - val alphaLabel: TextView by bindView(R.id.md_colorALabel) - val alphaSeekbar: SeekBar by bindView(R.id.md_colorA) - val alphaValue: TextView by bindView(R.id.md_colorAValue) - val redSeekbar: SeekBar by bindView(R.id.md_colorR) - val redValue: TextView by bindView(R.id.md_colorRValue) - val greenSeekbar: SeekBar by bindView(R.id.md_colorG) - val greenValue: TextView by bindView(R.id.md_colorGValue) - val blueSeekbar: SeekBar by bindView(R.id.md_colorB) - val blueValue: TextView by bindView(R.id.md_colorBValue) - - var customHexTextWatcher: TextWatcher? = null - var customRgbListener: SeekBar.OnSeekBarChangeListener? = null - - init { - View.inflate(context, R.layout.md_dialog_colorchooser, this) - } - - fun bind(builder: ColorContract, dialog: MaterialDialog) { - this.builder = builder - this.dialog = dialog - this.colorsTop = with(builder) { - if (colorsTop != null) colorsTop!! - else if (isAccent) ColorPalette.ACCENT_COLORS - else ColorPalette.PRIMARY_COLORS - } - this.colorsSub = with(builder) { - if (colorsTop != null) colorsSub - else if (isAccent) ColorPalette.ACCENT_COLORS_SUB - else ColorPalette.PRIMARY_COLORS_SUB - } - this.selectedColor = builder.defaultColor - if (builder.allowCustom) { - if (!builder.allowCustomAlpha) { - alphaLabel.gone() - alphaSeekbar.gone() - alphaValue.gone() - hexInput.hint = String.format("%06X", selectedColor) - hexInput.filters = arrayOf(InputFilter.LengthFilter(6)) - } else { - hexInput.hint = String.format("%08X", selectedColor) - hexInput.filters = arrayOf(InputFilter.LengthFilter(8)) - } - } - if (findColor(builder.defaultColor) || !builder.allowCustom) isInCustom = true //when toggled this will be false - toggleCustom() - } - - fun backOrCancel() { - if (isInSub) { - dialog.setActionButton(DialogAction.NEGATIVE, builder.cancelText) - //to top - isInSub = false - subIndex = -1 - invalidateGrid() - } else { - dialog.cancel() - } - } - - fun toggleCustom() { - isInCustom = !isInCustom - if (isInCustom) { - isInSub = false - if (builder.allowCustom) dialog.setActionButton(DialogAction.NEUTRAL, builder.presetText) - dialog.setActionButton(DialogAction.NEGATIVE, builder.cancelText) - customHexTextWatcher = object : TextWatcher { - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} - - override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { - try { - selectedColor = Color.parseColor("#" + s.toString()) - } catch (e: IllegalArgumentException) { - selectedColor = Color.BLACK - } - - customColorIndicator.setBackgroundColor(selectedColor) - if (alphaSeekbar.isVisible()) { - val alpha = Color.alpha(selectedColor) - alphaSeekbar.progress = alpha - alphaValue.text = String.format(Locale.CANADA, "%d", alpha) - } - redSeekbar.progress = Color.red(selectedColor) - greenSeekbar.progress = Color.green(selectedColor) - blueSeekbar.progress = Color.blue(selectedColor) - isInSub = false - topIndex = -1 - subIndex = -1 - refreshColors() - } - - override fun afterTextChanged(s: Editable?) {} - } - hexInput.setText(selectedColor.toHexString(builder.allowCustomAlpha, false)) - hexInput.addTextChangedListener(customHexTextWatcher) - customRgbListener = object : SeekBar.OnSeekBarChangeListener { - override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { - if (fromUser) { - val color = if (builder.allowCustomAlpha) - Color.argb(alphaSeekbar.progress, - redSeekbar.progress, - greenSeekbar.progress, - blueSeekbar.progress) - else Color.rgb(redSeekbar.progress, - greenSeekbar.progress, - blueSeekbar.progress) - - hexInput.setText(color.toHexString(builder.allowCustomAlpha, false)) - } - if (builder.allowCustomAlpha) alphaValue.text = alphaSeekbar.progress.toString() - redValue.text = redSeekbar.progress.toString() - greenValue.text = greenSeekbar.progress.toString() - blueValue.text = blueSeekbar.progress.toString() - } - - override fun onStartTrackingTouch(seekBar: SeekBar) {} - - override fun onStopTrackingTouch(seekBar: SeekBar) {} - } - redSeekbar.setOnSeekBarChangeListener(customRgbListener) - greenSeekbar.setOnSeekBarChangeListener(customRgbListener) - blueSeekbar.setOnSeekBarChangeListener(customRgbListener) - if (alphaSeekbar.isVisible()) - alphaSeekbar.setOnSeekBarChangeListener(customRgbListener) - hexInput.setText(selectedColor.toHexString(alphaSeekbar.isVisible(), false)) - gridView.fadeOut(onFinish = { gridView.gone() }) - customFrame.fadeIn() - } else { - findColor(selectedColor) - if (builder.allowCustom) dialog.setActionButton(DialogAction.NEUTRAL, builder.customText) - dialog.setActionButton(DialogAction.NEGATIVE, if (isInSub) builder.backText else builder.cancelText) - gridView.fadeIn(onStart = { invalidateGrid() }) - customFrame.fadeOut(onFinish = { customFrame.gone() }) - hexInput.removeTextChangedListener(customHexTextWatcher) - customHexTextWatcher = null - alphaSeekbar.setOnSeekBarChangeListener(null) - redSeekbar.setOnSeekBarChangeListener(null) - greenSeekbar.setOnSeekBarChangeListener(null) - blueSeekbar.setOnSeekBarChangeListener(null) - customRgbListener = null - } - } - - fun refreshColors() { - if (!isInCustom) findColor(selectedColor) - //Ensure that our tinted color is still visible against the background - val visibleColor = if (selectedColor.isColorVisibleOn(backgroundColor)) selectedColor else backgroundColorTint - if (builder.dynamicButtonColors) { - dialog.getActionButton(DialogAction.POSITIVE).setTextColor(visibleColor) - dialog.getActionButton(DialogAction.NEGATIVE).setTextColor(visibleColor) - dialog.getActionButton(DialogAction.NEUTRAL).setTextColor(visibleColor) - } - if (!builder.allowCustom || !isInCustom) return - if (builder.allowCustomAlpha) - alphaSeekbar.visible().tint(visibleColor) - redSeekbar.tint(visibleColor) - greenSeekbar.tint(visibleColor) - blueSeekbar.tint(visibleColor) - hexInput.tint(visibleColor) - } - - fun findColor(@ColorInt color: Int): Boolean { - topIndex = -1 - subIndex = -1 - colorsTop.forEachIndexed { - index, topColor -> - if (findSubColor(color, index)) { - topIndex = index - return true - } - if (topColor == color) { // If no sub colors exists and top color matches - topIndex = index - return true - } - } - return false - } - - fun findSubColor(@ColorInt color: Int, topIndex: Int): Boolean { - if (colorsSub == null || colorsSub!!.size <= topIndex) return false - colorsSub!![topIndex].forEachIndexed { - index, subColor -> - if (subColor == color) { - subIndex = index - return true - } - } - return false - } - - fun invalidateGrid() { - if (gridView.adapter == null) { - gridView.adapter = ColorGridAdapter() - gridView.selector = ResourcesCompat.getDrawable(resources, R.drawable.kau_transparent, null) - } else { - (gridView.adapter as BaseAdapter).notifyDataSetChanged() - } - } - - inner class ColorGridAdapter : BaseAdapter(), OnClickListener, OnLongClickListener { - override fun onClick(v: View) { - if (v.tag != null && v.tag is String) { - val tags = (v.tag as String).split(":") - if (colorIndex == tags[0].toInt()) { - colorIndex = tags[0].toInt() //Go to sub list if exists - return - } - if (colorIndex != -1) (gridView.getChildAt(colorIndex) as CircleView).animateSelected(false) - selectedColor = tags[1].toInt() - refreshColors() - val currentSub = isInSub - colorIndex = tags[0].toInt() - if (currentSub == isInSub) (gridView.getChildAt(colorIndex) as CircleView).animateSelected(true) - //Otherwise we are invalidating our grid, so there is no point in animating - } - } - - override fun onLongClick(v: View): Boolean { - if (v.tag != null && v.tag is String) { - val tag = (v.tag as String).split(":") - val color = tag[1].toInt() - (v as CircleView).showHint(color) - return true - } - return false - } - - override fun getItem(position: Int): Any = if (isInSub) colorsSub!![topIndex][position] else colorsTop[position] - - override fun getCount(): Int = if (isInSub) colorsSub!![topIndex].size else colorsTop.size - - override fun getItemId(position: Int): Long = position.toLong() - - override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { - val view: CircleView = if (convertView == null) - CircleView(context).apply { layoutParams = AbsListView.LayoutParams(circleSize, circleSize) } - else - convertView as CircleView - val color: Int = if (isInSub) colorsSub!![topIndex][position] else colorsTop[position] - return view.apply { - setBackgroundColor(color) - isSelected = colorIndex == position - tag = "$position:$color" - setOnClickListener(this@ColorGridAdapter) - setOnLongClickListener(this@ColorGridAdapter) - } - } - - } -}
\ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/iitems/CardIItem.kt b/core/src/main/kotlin/ca/allanwang/kau/iitems/CardIItem.kt deleted file mode 100644 index 3380ade..0000000 --- a/core/src/main/kotlin/ca/allanwang/kau/iitems/CardIItem.kt +++ /dev/null @@ -1,127 +0,0 @@ -package ca.allanwang.kau.iitems - -import android.graphics.Color -import android.graphics.drawable.Drawable -import android.support.v7.widget.CardView -import android.support.v7.widget.RecyclerView -import android.view.View -import android.widget.Button -import android.widget.ImageView -import android.widget.LinearLayout -import android.widget.TextView -import ca.allanwang.kau.R -import ca.allanwang.kau.adapters.ThemableIItem -import ca.allanwang.kau.adapters.ThemableIItemDelegate -import ca.allanwang.kau.utils.* -import com.mikepenz.fastadapter.FastAdapter -import com.mikepenz.fastadapter.IItem -import com.mikepenz.fastadapter.items.AbstractItem -import com.mikepenz.fastadapter.listeners.ClickEventHook -import com.mikepenz.iconics.typeface.IIcon - -/** - * Created by Allan Wang on 2017-06-28. - * - * Simple generic card item with an icon, title, description and button - * The icon and button are hidden by default unless values are given - */ -class CardIItem(val builder: Config.() -> Unit = {} -) : AbstractItem<CardIItem, CardIItem.ViewHolder>(), ThemableIItem by ThemableIItemDelegate() { - - companion object { - @JvmStatic fun bindClickEvents(fastAdapter: FastAdapter<IItem<*,*>>) { - fastAdapter.withEventHook(object : ClickEventHook<IItem<*,*>>() { - override fun onBindMany(viewHolder: RecyclerView.ViewHolder): List<View>? { - return if (viewHolder is ViewHolder) listOf(viewHolder.card, viewHolder.button) else null - } - - override fun onClick(v: View, position: Int, adapter: FastAdapter<IItem<*,*>>, item: IItem<*,*>) { - if (item !is CardIItem) return - with(item.configs) { - when (v.id) { - R.id.kau_card_container -> cardClick?.onClick(v) - R.id.kau_card_button -> buttonClick?.onClick(v) - else -> { - } - } - } - } - }) - } - } - - val configs = Config().apply { builder() } - - class Config { - var title: String? = null - var titleRes: Int = -1 - var desc: String? = null - var descRes: Int = -1 - var button: String? = null - var buttonRes: Int = -1 - var buttonClick: View.OnClickListener? = null - var cardClick: View.OnClickListener? = null - var image: Drawable? = null - var imageIIcon: IIcon? = null - var imageIIconColor: Int = Color.WHITE - var imageRes: Int = -1 - } - - - override fun getType(): Int = R.id.kau_item_card - - override fun getLayoutRes(): Int = R.layout.kau_iitem_card - - override fun bindView(holder: ViewHolder, payloads: MutableList<Any>?) { - super.bindView(holder, payloads) - with(holder.itemView.context) context@ { - with(configs) { - holder.title.text = string(titleRes, title) - holder.description.text = string(descRes, desc) - val buttonText = string(buttonRes, button) - if (buttonText != null) { - holder.bottomRow.visible() - holder.button.text = buttonText - holder.button.setOnClickListener(buttonClick) - } - holder.icon.setImageDrawable( - if (imageRes > 0) drawable(imageRes) - else if (imageIIcon != null) imageIIcon!!.toDrawable(this@context, sizeDp = 40, color = imageIIconColor) - else image - ) - holder.card.setOnClickListener(cardClick) - } - with(holder) { - bindTextColor(title) - bindTextColorSecondary(description) - bindAccentColor(button) - if (configs.imageIIcon != null) bindIconColor(icon) - bindBackgroundRipple(card) - } - } - } - - override fun unbindView(holder: ViewHolder) { - super.unbindView(holder) - with(holder) { - icon.gone().setImageDrawable(null) - title.text = null - description.text = null - bottomRow.gone() - button.setOnClickListener(null) - card.setOnClickListener(null) - } - } - - override fun getViewHolder(v: View): ViewHolder = ViewHolder(v) - - class ViewHolder(v: View) : RecyclerView.ViewHolder(v) { - val card: CardView by bindView(R.id.kau_card_container) - val icon: ImageView by bindView(R.id.kau_card_image) - val title: TextView by bindView(R.id.kau_card_title) - val description: TextView by bindView(R.id.kau_card_description) - val bottomRow: LinearLayout by bindView(R.id.kau_card_bottom_row) - val button: Button by bindView(R.id.kau_card_button) - } - -}
\ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/iitems/CutoutIItem.kt b/core/src/main/kotlin/ca/allanwang/kau/iitems/CutoutIItem.kt deleted file mode 100644 index 627e1df..0000000 --- a/core/src/main/kotlin/ca/allanwang/kau/iitems/CutoutIItem.kt +++ /dev/null @@ -1,48 +0,0 @@ -package ca.allanwang.kau.iitems - -import android.support.v7.widget.RecyclerView -import android.view.View -import ca.allanwang.kau.R -import ca.allanwang.kau.adapters.ThemableIItem -import ca.allanwang.kau.adapters.ThemableIItemDelegate -import ca.allanwang.kau.utils.bindView -import ca.allanwang.kau.views.CutoutView -import com.mikepenz.fastadapter.items.AbstractItem - -/** - * Created by Allan Wang on 2017-06-28. - * - * Just a cutout item with some defaults in [R.layout.kau_iitem_cutout] - */ -class CutoutIItem(val config: CutoutView.() -> Unit = {} -) : AbstractItem<CutoutIItem, CutoutIItem.ViewHolder>(), ThemableIItem by ThemableIItemDelegate() { - - override fun getType(): Int = R.id.kau_item_cutout - - override fun getLayoutRes(): Int = R.layout.kau_iitem_cutout - - override fun isSelectable(): Boolean = false - - override fun bindView(holder: ViewHolder, payloads: MutableList<Any>?) { - super.bindView(holder, payloads) - with(holder) { - if (accentColor != null && themeEnabled) cutout.foregroundColor = accentColor!! - cutout.config() - } - } - - override fun unbindView(holder: ViewHolder) { - super.unbindView(holder) - with(holder) { - cutout.drawable = null - cutout.text = "Text" //back to default - } - } - - override fun getViewHolder(v: View): ViewHolder = ViewHolder(v) - - class ViewHolder(v: View) : RecyclerView.ViewHolder(v) { - val cutout: CutoutView by bindView(R.id.kau_cutout) - } - -}
\ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/iitems/HeaderIItem.kt b/core/src/main/kotlin/ca/allanwang/kau/iitems/HeaderIItem.kt deleted file mode 100644 index e994781..0000000 --- a/core/src/main/kotlin/ca/allanwang/kau/iitems/HeaderIItem.kt +++ /dev/null @@ -1,49 +0,0 @@ -package ca.allanwang.kau.iitems - -import android.support.v7.widget.CardView -import android.support.v7.widget.RecyclerView -import android.view.View -import android.view.ViewGroup -import android.widget.TextView -import ca.allanwang.kau.R -import ca.allanwang.kau.adapters.ThemableIItem -import ca.allanwang.kau.adapters.ThemableIItemDelegate -import ca.allanwang.kau.utils.bindView -import ca.allanwang.kau.utils.string -import com.mikepenz.fastadapter.items.AbstractItem - -/** - * Created by Allan Wang on 2017-06-28. - * - * Simple Header with lots of padding on the top - * Contains only one text view - */ -class HeaderIItem(text: String? = null, var textRes: Int = -1 -) : AbstractItem<HeaderIItem, HeaderIItem.ViewHolder>(), ThemableIItem by ThemableIItemDelegate() { - - var text: String = text ?: "Header Placeholder" - - override fun getType(): Int = R.id.kau_item_header_big_margin_top - - override fun getLayoutRes(): Int = R.layout.kau_iitem_header - - override fun bindView(holder: ViewHolder, payloads: MutableList<Any>?) { - super.bindView(holder, payloads) - holder.text.text = holder.itemView.context.string(textRes, text) - bindTextColor(holder.text) - bindBackgroundColor(holder.container) - } - - override fun unbindView(holder: ViewHolder) { - super.unbindView(holder) - holder.text.text = null - } - - override fun getViewHolder(v: View): ViewHolder = ViewHolder(v) - - class ViewHolder(v: View) : RecyclerView.ViewHolder(v) { - val text: TextView by bindView(R.id.kau_header_text) - val container: CardView by bindView(R.id.kau_header_container) - } - -}
\ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/iitems/KauIItem.kt b/core/src/main/kotlin/ca/allanwang/kau/iitems/KauIItem.kt deleted file mode 100644 index d8567c4..0000000 --- a/core/src/main/kotlin/ca/allanwang/kau/iitems/KauIItem.kt +++ /dev/null @@ -1,24 +0,0 @@ -package ca.allanwang.kau.iitems - -import android.support.annotation.LayoutRes -import android.support.v7.widget.RecyclerView -import android.view.View -import com.mikepenz.fastadapter.IClickable -import com.mikepenz.fastadapter.IItem -import com.mikepenz.fastadapter.items.AbstractItem - -/** - * Created by Allan Wang on 2017-07-03. - * - * Kotlin implementation of the [AbstractItem] to make things shorter - * If only one iitem type extends the given [layoutRes], you may use it as the type and not worry about another id - */ -open class KauIItem<Item, VH : RecyclerView.ViewHolder>( - @param:LayoutRes private val layoutRes: Int, - private val viewHolder: (v: View) -> VH, - private val type: Int = layoutRes -) : AbstractItem<Item, VH>() where Item : IItem<*, *>, Item : IClickable<*> { - override final fun getType(): Int = type - override final fun getViewHolder(v: View): VH = viewHolder(v) - override final fun getLayoutRes(): Int = layoutRes -}
\ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/iitems/LibraryIItem.kt b/core/src/main/kotlin/ca/allanwang/kau/iitems/LibraryIItem.kt deleted file mode 100644 index aabd9e3..0000000 --- a/core/src/main/kotlin/ca/allanwang/kau/iitems/LibraryIItem.kt +++ /dev/null @@ -1,100 +0,0 @@ -package ca.allanwang.kau.iitems - -import android.os.Build -import android.support.v7.widget.CardView -import android.support.v7.widget.RecyclerView -import android.text.Html -import android.view.View -import android.widget.TextView -import ca.allanwang.kau.R -import ca.allanwang.kau.adapters.ThemableIItem -import ca.allanwang.kau.adapters.ThemableIItemDelegate -import ca.allanwang.kau.utils.bindView -import ca.allanwang.kau.utils.gone -import ca.allanwang.kau.utils.startLink -import ca.allanwang.kau.utils.visible -import ca.allanwang.kau.views.createSimpleRippleDrawable -import com.mikepenz.aboutlibraries.entity.Library -import com.mikepenz.fastadapter.FastAdapter -import com.mikepenz.fastadapter.IItem -import com.mikepenz.fastadapter.items.AbstractItem - -/** - * Created by Allan Wang on 2017-06-27. - */ -class LibraryIItem(val lib: Library -) : AbstractItem<LibraryIItem, LibraryIItem.ViewHolder>(), ThemableIItem by ThemableIItemDelegate() { - - companion object { - @JvmStatic fun bindClickEvents(fastAdapter: FastAdapter<IItem<*, *>>) { - fastAdapter.withOnClickListener { v, _, item, _ -> - if (item !is LibraryIItem) false - else { - val c = v.context - with(item.lib) { - c.startLink(libraryWebsite, repositoryLink, authorWebsite) - } - true - } - } - } - } - - override fun getType(): Int = R.id.kau_item_library - - override fun getLayoutRes(): Int = R.layout.kau_iitem_library - - override fun isSelectable(): Boolean = false - - override fun bindView(holder: ViewHolder, payloads: MutableList<Any>?) { - super.bindView(holder, payloads) - with(holder) { - name.text = lib.libraryName - creator.text = lib.author - 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) - else Html.fromHtml(lib.libraryDescription) - bottomDivider.gone() - if (lib.libraryVersion?.isNotBlank() ?: false) { - bottomDivider.visible() - version.visible().text = lib.libraryVersion - } - if (lib.license?.licenseName?.isNotBlank() ?: false) { - bottomDivider.visible() - license.visible().text = lib.license?.licenseName - } - bindTextColor(name, creator) - bindTextColorSecondary(description) - bindAccentColor(license, version) - bindDividerColor(divider, bottomDivider) - bindBackgroundRipple(card) - } - } - - override fun unbindView(holder: ViewHolder) { - super.unbindView(holder) - with(holder) { - name.text = null - creator.text = null - description.text = null - bottomDivider.gone() - version.gone().text = null - license.gone().text = null - } - } - - override fun getViewHolder(v: View): ViewHolder = ViewHolder(v) - - class ViewHolder(v: View) : RecyclerView.ViewHolder(v) { - val card: CardView by bindView(R.id.lib_item_card) - val name: TextView by bindView(R.id.lib_item_name) - val creator: TextView by bindView(R.id.lib_item_author) - val description: TextView by bindView(R.id.lib_item_description) - val version: TextView by bindView(R.id.lib_item_version) - val license: TextView by bindView(R.id.lib_item_license) - val divider: View by bindView(R.id.lib_item_top_divider) - val bottomDivider: View by bindView(R.id.lib_item_bottom_divider) - } - -}
\ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/kpref/KPrefActivity.kt b/core/src/main/kotlin/ca/allanwang/kau/kpref/KPrefActivity.kt deleted file mode 100644 index 9a9f7d4..0000000 --- a/core/src/main/kotlin/ca/allanwang/kau/kpref/KPrefActivity.kt +++ /dev/null @@ -1,137 +0,0 @@ -package ca.allanwang.kau.kpref - -import android.os.Bundle -import android.support.annotation.StringRes -import android.support.constraint.ConstraintLayout -import android.support.v7.app.AppCompatActivity -import android.support.v7.widget.RecyclerView -import android.support.v7.widget.Toolbar -import android.view.View -import android.view.animation.Animation -import android.view.animation.AnimationUtils -import android.widget.FrameLayout -import android.widget.ViewAnimator -import ca.allanwang.kau.R -import ca.allanwang.kau.kpref.items.KPrefItemCore -import ca.allanwang.kau.utils.bindView -import ca.allanwang.kau.utils.resolveColor -import ca.allanwang.kau.utils.statusBarColor -import ca.allanwang.kau.utils.string -import ca.allanwang.kau.widgets.TextSlider -import ca.allanwang.kau.views.RippleCanvas -import com.mikepenz.fastadapter.commons.adapters.FastItemAdapter - -abstract class KPrefActivity : AppCompatActivity(), KPrefActivityContract { - - val adapter: FastItemAdapter<KPrefItemCore> - @Suppress("UNCHECKED_CAST") - get() = recycler.adapter as FastItemAdapter<KPrefItemCore> - val recycler: RecyclerView - get() = prefHolder.currentView as RecyclerView - val container: ConstraintLayout by bindView(R.id.kau_container) - val bgCanvas: RippleCanvas by bindView(R.id.kau_ripple) - val toolbarCanvas: RippleCanvas by bindView(R.id.kau_toolbar_ripple) - val toolbar: Toolbar by bindView(R.id.kau_toolbar) - val toolbarTitle: TextSlider by bindView(R.id.kau_toolbar_text) - val prefHolder: ViewAnimator by bindView(R.id.kau_holder) - private lateinit var globalOptions: GlobalOptions - var animate: Boolean = true - set(value) { - field = value - toolbarTitle.animationType = if (value) TextSlider.ANIMATION_SLIDE_HORIZONTAL else TextSlider.ANIMATION_NONE - } - - private val SLIDE_IN_LEFT_ITEMS: Animation by lazy { AnimationUtils.loadAnimation(this, R.anim.kau_slide_in_left) } - private val SLIDE_IN_RIGHT_ITEMS: Animation by lazy { AnimationUtils.loadAnimation(this, R.anim.kau_slide_in_right) } - private val SLIDE_OUT_LEFT_ITEMS: Animation by lazy { AnimationUtils.loadAnimation(this, R.anim.kau_slide_out_left) } - private val SLIDE_OUT_RIGHT_ITEMS: Animation by lazy { AnimationUtils.loadAnimation(this, R.anim.kau_slide_out_right) } - - /** - * Core attribute builder that is consistent throughout all items - * Leave blank to use defaults - */ - abstract fun kPrefCoreAttributes(): CoreAttributeContract.() -> Unit - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - //setup layout - setContentView(R.layout.kau_activity_kpref) - setSupportActionBar(toolbar) - if (supportActionBar != null) - with(supportActionBar!!) { - setDisplayHomeAsUpEnabled(true) - setDisplayShowHomeEnabled(true) - toolbar.setNavigationOnClickListener { onBackPressed() } - setDisplayShowTitleEnabled(false) - } - window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN - statusBarColor = 0x30000000 - toolbarCanvas.set(resolveColor(R.attr.colorPrimary)) - bgCanvas.set(resolveColor(android.R.attr.colorBackground)) - prefHolder.animateFirstView = false - //setup prefs - val core = CoreAttributeBuilder() - val builder = kPrefCoreAttributes() - core.builder() - globalOptions = GlobalOptions(core, this) - showNextPrefs(R.string.kau_settings, onCreateKPrefs(savedInstanceState)) - } - - override fun onPostCreate(savedInstanceState: Bundle?) { - super.onPostCreate(savedInstanceState) - } - - override fun showNextPrefs(@StringRes toolbarTitleRes: Int, builder: KPrefAdapterBuilder.() -> Unit) { - val rv = RecyclerView(this).apply { - layoutParams = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT) - setKPrefAdapter(globalOptions, builder) - } - with(prefHolder) { - inAnimation = if (animate) SLIDE_IN_RIGHT_ITEMS else null - outAnimation = if (animate) SLIDE_OUT_LEFT_ITEMS else null - addView(rv) - showNext() - } - toolbarTitle.setNextText(string(toolbarTitleRes)) - } - - override fun showPrevPrefs() { - val current = prefHolder.currentView - with(prefHolder) { - inAnimation = if (animate) SLIDE_IN_LEFT_ITEMS else null - outAnimation = if (animate) SLIDE_OUT_RIGHT_ITEMS else null - showPrevious() - removeView(current) - adapter.notifyAdapterDataSetChanged() - } - toolbarTitle.setPrevText() - } - - fun reload(vararg index: Int) { - if (index.isEmpty()) adapter.notifyAdapterDataSetChanged() - else index.forEach { adapter.notifyItemChanged(it) } - } - - override fun reloadByTitle(@StringRes vararg title: Int) { - if (title.isEmpty()) return - adapter.adapterItems.forEachIndexed { index, item -> - if (title.any { item.core.titleRes == it }) - adapter.notifyItemChanged(index) - } - } - - abstract fun onCreateKPrefs(savedInstanceState: Bundle?): KPrefAdapterBuilder.() -> Unit - - override fun onBackPressed() { - if (!backPress()) super.onBackPressed() - } - - fun backPress(): Boolean { - if (!toolbarTitle.isRoot) { - showPrevPrefs() - return true - } - return false - } -} - diff --git a/core/src/main/kotlin/ca/allanwang/kau/kpref/KPrefBinder.kt b/core/src/main/kotlin/ca/allanwang/kau/kpref/KPrefBinder.kt deleted file mode 100644 index a424839..0000000 --- a/core/src/main/kotlin/ca/allanwang/kau/kpref/KPrefBinder.kt +++ /dev/null @@ -1,128 +0,0 @@ -package ca.allanwang.kau.kpref - -import android.support.annotation.StringRes -import android.support.v7.widget.LinearLayoutManager -import android.support.v7.widget.RecyclerView -import ca.allanwang.kau.R -import ca.allanwang.kau.kpref.items.* -import com.mikepenz.fastadapter.commons.adapters.FastItemAdapter -import org.jetbrains.anko.doAsync -import org.jetbrains.anko.uiThread - -/** - * Created by Allan Wang on 2017-06-08. - * - * Houses all the components that can be called externally to setup the kpref mainAdapter - */ - -/** - * Base extension that will register the layout manager and mainAdapter with the given items - * Returns FastAdapter - */ -fun RecyclerView.setKPrefAdapter(globalOptions: GlobalOptions, builder: KPrefAdapterBuilder.() -> Unit): FastItemAdapter<KPrefItemCore> { - layoutManager = LinearLayoutManager(context) - val adapter = FastItemAdapter<KPrefItemCore>() - adapter.withOnClickListener { v, _, item, _ -> item.onClick(v, v.findViewById(R.id.kau_pref_inner_content)) } - this.adapter = adapter - doAsync { - val items = KPrefAdapterBuilder(globalOptions) - builder.invoke(items) - uiThread { - adapter.add(items.list) - } - } - return adapter -} - -@DslMarker -annotation class KPrefMarker - -/** - * Contains attributes shared amongst all kpref items - */ -@KPrefMarker -interface CoreAttributeContract { - var textColor: (() -> Int)? - var accentColor: (() -> Int)? -} - -/** - * Implementation of [CoreAttributeContract] - */ -class CoreAttributeBuilder : CoreAttributeContract { - override var textColor: (() -> Int)? = null - override var accentColor: (() -> Int)? = textColor -} - -interface KPrefActivityContract { - fun showNextPrefs(@StringRes toolbarTitleRes: Int, builder: KPrefAdapterBuilder.() -> Unit) - fun showPrevPrefs() - fun reloadByTitle(@StringRes vararg title: Int) -} - - -class GlobalOptions(core: CoreAttributeContract, activity: KPrefActivityContract -) : CoreAttributeContract by core, KPrefActivityContract by activity - - -/** - * Builder for kpref items - * Contains DSLs for every possible item - * The arguments are all the mandatory values plus an optional builder housing all the possible configurations - * The mandatory values are final so they cannot be edited in the builder - */ -@KPrefMarker -class KPrefAdapterBuilder(val globalOptions: GlobalOptions) { - - @KPrefMarker - fun header(@StringRes title: Int) - = list.add(KPrefHeader(KPrefItemCore.CoreBuilder(globalOptions, title))) - - @KPrefMarker - fun checkbox(@StringRes title: Int, - getter: (() -> Boolean), - setter: ((value: Boolean) -> Unit), - builder: KPrefItemBase.BaseContract<Boolean>.() -> Unit = {}) - = list.add(KPrefCheckbox(KPrefItemBase.BaseBuilder(globalOptions, title, getter, setter) - .apply { builder() })) - - @KPrefMarker - fun colorPicker(@StringRes title: Int, - getter: (() -> Int), - setter: ((value: Int) -> Unit), - builder: KPrefColorPicker.KPrefColorContract.() -> Unit = {}) - = list.add(KPrefColorPicker(KPrefColorPicker.KPrefColorBuilder(globalOptions, title, getter, setter) - .apply { builder() })) - - @KPrefMarker - fun <T> text(@StringRes title: Int, - getter: (() -> T), - setter: ((value: T) -> Unit), - builder: KPrefText.KPrefTextContract<T>.() -> Unit = {}) - = list.add(KPrefText<T>(KPrefText.KPrefTextBuilder<T>(globalOptions, title, getter, setter) - .apply { builder() })) - - @KPrefMarker - fun subItems(@StringRes title: Int, - itemBuilder: KPrefAdapterBuilder.() -> Unit, - builder: KPrefSubItems.KPrefSubItemsContract.() -> Unit) - = list.add(KPrefSubItems(KPrefSubItems.KPrefSubItemsBuilder(globalOptions, title, itemBuilder) - .apply { builder() })) - - @KPrefMarker - fun plainText(@StringRes title: Int, - builder: KPrefItemBase.BaseContract<Unit>.() -> Unit = {}) - = list.add(KPrefPlainText(KPrefPlainText.KPrefPlainTextBuilder(globalOptions, title) - .apply { builder() })) - - @KPrefMarker - fun seekbar(@StringRes title: Int, - getter: (() -> Int), - setter: ((value: Int) -> Unit), - builder: KPrefSeekbar.KPrefSeekbarContract.() -> Unit = {}) - = list.add(KPrefSeekbar(KPrefSeekbar.KPrefSeekbarBuilder(globalOptions, title, getter, setter) - .apply { builder() })) - - @KPrefMarker - val list: MutableList<KPrefItemCore> = mutableListOf() -}
\ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefCheckbox.kt b/core/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefCheckbox.kt deleted file mode 100644 index 999a718..0000000 --- a/core/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefCheckbox.kt +++ /dev/null @@ -1,33 +0,0 @@ -package ca.allanwang.kau.kpref.items - -import android.view.View -import android.widget.CheckBox -import ca.allanwang.kau.R -import ca.allanwang.kau.kpref.KPrefMarker -import ca.allanwang.kau.utils.tint - -/** - * Created by Allan Wang on 2017-06-07. - * - * Checkbox preference - * When clicked, will toggle the preference and the apply the result to the checkbox - */ -open class KPrefCheckbox(builder: BaseContract<Boolean>) : KPrefItemBase<Boolean>(builder) { - - override fun defaultOnClick(itemView: View, innerContent: View?): Boolean { - pref = !pref - (innerContent as CheckBox).isChecked = pref - return true - } - - override fun onPostBindView(viewHolder: ViewHolder, textColor: Int?, accentColor: Int?) { - super.onPostBindView(viewHolder, textColor, accentColor) - val checkbox = viewHolder.bindInnerView<CheckBox>(R.layout.kau_preference_checkbox) - if (accentColor != null) checkbox.tint(accentColor) - checkbox.isChecked = pref - checkbox.jumpDrawablesToCurrentState() //Cancel the animation - } - - override fun getType(): Int = R.id.kau_item_pref_checkbox - -}
\ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefColorPicker.kt b/core/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefColorPicker.kt deleted file mode 100644 index 762bcf4..0000000 --- a/core/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefColorPicker.kt +++ /dev/null @@ -1,73 +0,0 @@ -package ca.allanwang.kau.kpref.items - -import android.view.View -import ca.allanwang.kau.R -import ca.allanwang.kau.dialogs.color.CircleView -import ca.allanwang.kau.dialogs.color.ColorBuilder -import ca.allanwang.kau.dialogs.color.ColorContract -import ca.allanwang.kau.dialogs.color.colorPickerDialog -import ca.allanwang.kau.kpref.CoreAttributeContract -import ca.allanwang.kau.kpref.GlobalOptions -import ca.allanwang.kau.kpref.KPrefMarker - -/** - * Created by Allan Wang on 2017-06-07. - * - * ColorPicker preference - * When a color is successfully selected in the dialog, it will be saved as an int - */ -open class KPrefColorPicker(val builder: KPrefColorContract) : KPrefItemBase<Int>(builder) { - - override fun onPostBindView(viewHolder: ViewHolder, textColor: Int?, accentColor: Int?) { - super.onPostBindView(viewHolder, textColor, accentColor) - builder.apply { - titleRes = core.titleRes - colorCallback = { - pref = it - } - } - if (builder.showPreview) { - val preview = viewHolder.bindInnerView<CircleView>(R.layout.kau_preference_color_preview) - preview.setBackgroundColor(pref) - preview.withBorder = true - builder.apply { - colorCallback = { - pref = it - if (builder.showPreview) - preview.setBackgroundColor(it) - } - } - } - } - - - override fun defaultOnClick(itemView: View, innerContent: View?): Boolean { - builder.apply { - defaultColor = pref //update color - } - itemView.context.colorPickerDialog(builder).show() - return true - } - - /** - * Extension of the base contract and [ColorContract] along with a showPreview option - */ - interface KPrefColorContract : BaseContract<Int>, ColorContract { - var showPreview: Boolean - } - - /** - * Default implementation of [KPrefColorContract] - */ - class KPrefColorBuilder(globalOptions: GlobalOptions, - override var titleRes: Int, - getter: () -> Int, - setter: (value: Int) -> Unit - ) : KPrefColorContract, BaseContract<Int> by BaseBuilder<Int>(globalOptions, titleRes, getter, setter), - ColorContract by ColorBuilder() { - override var showPreview: Boolean = true - } - - override fun getType(): Int = R.id.kau_item_pref_color_picker - -}
\ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefHeader.kt b/core/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefHeader.kt deleted file mode 100644 index 4068496..0000000 --- a/core/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefHeader.kt +++ /dev/null @@ -1,25 +0,0 @@ -package ca.allanwang.kau.kpref.items - -import android.view.View -import ca.allanwang.kau.R -import ca.allanwang.kau.kpref.KPrefMarker - -/** - * Created by Allan Wang on 2017-06-07. - * - * Header preference - * This view just holds a title and is not clickable. It is styled using the accent color - */ -open class KPrefHeader(builder: CoreContract) : KPrefItemCore(builder) { - - override fun getLayoutRes(): Int = R.layout.kau_preference_header - - override fun onPostBindView(viewHolder: ViewHolder, textColor: Int?, accentColor: Int?) { - if (accentColor != null) viewHolder.title.setTextColor(accentColor) - } - - override fun onClick(itemView: View, innerContent: View?): Boolean = true - - override fun getType() = R.id.kau_item_pref_header - -}
\ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefItemBase.kt b/core/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefItemBase.kt deleted file mode 100644 index bb0f0a3..0000000 --- a/core/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefItemBase.kt +++ /dev/null @@ -1,85 +0,0 @@ -package ca.allanwang.kau.kpref.items - -import android.support.annotation.CallSuper -import android.view.View -import ca.allanwang.kau.R -import ca.allanwang.kau.kpref.CoreAttributeContract -import ca.allanwang.kau.kpref.GlobalOptions -import ca.allanwang.kau.utils.resolveDrawable - -/** - * Created by Allan Wang on 2017-06-05. - * - * Base class for pref setters that include the Shared Preference hooks - */ -abstract class KPrefItemBase<T>(val base: BaseContract<T>) : KPrefItemCore(base) { - - open var pref: T - get() = base.getter.invoke() - set(value) { - base.setter.invoke(value) - } - - var enabled: Boolean = true - - init { - if (base.onClick == null) base.onClick = { - itemView, innerContent, _ -> - defaultOnClick(itemView, innerContent) - } - } - - abstract fun defaultOnClick(itemView: View, innerContent: View?): Boolean - - @CallSuper - override fun onPostBindView(viewHolder: ViewHolder, textColor: Int?, accentColor: Int?) { - enabled = base.enabler.invoke() - with(viewHolder) { - if (!enabled) container?.background = null - container?.alpha = if (enabled) 1.0f else 0.3f - } - } - - override final fun onClick(itemView: View, innerContent: View?): Boolean { - return if (enabled) base.onClick?.invoke(itemView, innerContent, this) ?: false - else base.onDisabledClick?.invoke(itemView, innerContent, this) ?: false - } - - override fun unbindView(holder: ViewHolder) { - super.unbindView(holder) - with(holder) { - container?.isEnabled = true - container?.background = itemView.context.resolveDrawable(android.R.attr.selectableItemBackground) - container?.alpha = 1.0f - } - } - - override final fun getLayoutRes(): Int = R.layout.kau_preference - - /** - * Extension of the core contract - * Since everything that extends the base is an actual preference, there must be a getter and setter - * The rest are optional and will have their defaults - */ - interface BaseContract<T> : CoreContract { - var enabler: () -> Boolean - var onClick: ((itemView: View, innerContent: View?, item: KPrefItemBase<T>) -> Boolean)? - var onDisabledClick: ((itemView: View, innerContent: View?, item: KPrefItemBase<T>) -> Boolean)? - val getter: () -> T - val setter: (value: T) -> Unit - } - - /** - * Default implementation of [BaseContract] - */ - class BaseBuilder<T>(globalOptions: GlobalOptions, - titleRes: Int, - override val getter: () -> T, - override val setter: (value: T) -> Unit - ) : CoreContract by CoreBuilder(globalOptions, titleRes), BaseContract<T> { - override var enabler: () -> Boolean = { true } - override var onClick: ((itemView: View, innerContent: View?, item: KPrefItemBase<T>) -> Boolean)? = null - override var onDisabledClick: ((itemView: View, innerContent: View?, item: KPrefItemBase<T>) -> Boolean)? = null - } - -}
\ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefItemCore.kt b/core/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefItemCore.kt deleted file mode 100644 index 18d1bae..0000000 --- a/core/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefItemCore.kt +++ /dev/null @@ -1,143 +0,0 @@ -package ca.allanwang.kau.kpref.items - -import android.support.annotation.CallSuper -import android.support.annotation.IdRes -import android.support.annotation.LayoutRes -import android.support.annotation.StringRes -import android.support.v7.widget.RecyclerView -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.ImageView -import android.widget.LinearLayout -import android.widget.TextView -import ca.allanwang.kau.R -import ca.allanwang.kau.adapters.ThemableIItem -import ca.allanwang.kau.adapters.ThemableIItemDelegate -import ca.allanwang.kau.kpref.GlobalOptions -import ca.allanwang.kau.kpref.KPrefMarker -import ca.allanwang.kau.utils.* -import com.mikepenz.fastadapter.items.AbstractItem -import com.mikepenz.iconics.typeface.IIcon - -/** - * Created by Allan Wang on 2017-06-05. - * - * Core class containing nothing but the view items - */ - -abstract class KPrefItemCore(val core: CoreContract) : AbstractItem<KPrefItemCore, KPrefItemCore.ViewHolder>(), - ThemableIItem by ThemableIItemDelegate() { - - override final fun getViewHolder(v: View) = ViewHolder(v) - - @CallSuper - override fun bindView(viewHolder: ViewHolder, payloads: List<Any>) { - super.bindView(viewHolder, payloads) - with(viewHolder) { - val context = itemView.context - title.text = context.string(core.titleRes) - if (core.descRes > 0) - desc?.visible()?.setText(core.descRes) - else - desc?.gone() - if (core.iicon != null) icon?.visible()?.setIcon(core.iicon, 24) - else icon?.gone() - innerFrame?.removeAllViews() - val textColor = core.globalOptions.textColor?.invoke() - if (textColor != null) { - title.setTextColor(textColor) - desc?.setTextColor(textColor) - } - val accentColor = core.globalOptions.accentColor?.invoke() - if (accentColor != null) { - icon?.drawable?.setTint(accentColor) - } - onPostBindView(this, textColor, accentColor) - } - } - - abstract fun onPostBindView(viewHolder: ViewHolder, textColor: Int?, accentColor: Int?) - - abstract fun onClick(itemView: View, innerContent: View?): Boolean - - override fun unbindView(holder: ViewHolder) { - super.unbindView(holder) - with(holder) { - title.text = null - desc?.text = null - icon?.setImageDrawable(null) - innerFrame?.removeAllViews() - lowerFrame?.removeAllViews() - } - } - - /** - * Core values for all kpref items - */ - @KPrefMarker - interface CoreContract { - val globalOptions: GlobalOptions - @get:StringRes val titleRes: Int - var descRes: Int - @StringRes get - var iicon: IIcon? - - /** - * Attempts to reload current item by identifying it with its [titleRes] - */ - fun reloadSelf() - } - - /** - * Default implementation of [CoreContract] - */ - class CoreBuilder(override val globalOptions: GlobalOptions, - override @param:StringRes val titleRes: Int) : CoreContract { - override var descRes: Int = -1 - override var iicon: IIcon? = null - - override fun reloadSelf() { - globalOptions.reloadByTitle(titleRes) - } - } - - class ViewHolder(v: View) : RecyclerView.ViewHolder(v) { - val title: TextView by bindView(R.id.kau_pref_title) - val container: ViewGroup? by bindOptionalView(R.id.kau_pref_container) - val desc: TextView? by bindOptionalView(R.id.kau_pref_desc) - val icon: ImageView? by bindOptionalView(R.id.kau_pref_icon) - val innerFrame: LinearLayout? by bindOptionalView(R.id.kau_pref_inner_frame) - val lowerFrame: LinearLayout? by bindOptionalView(R.id.kau_pref_lower_frame) - val innerContent: View? - get() = itemView.findViewById(R.id.kau_pref_inner_content) - val lowerContent: View? - get() = itemView.findViewById(R.id.kau_pref_lower_content) - - inline fun <reified T : View> bindInnerView(@LayoutRes id: Int) = bindInnerView(id) { _: T -> } - - inline fun <reified T : View> bindInnerView(@LayoutRes id: Int, onFirstBind: (T) -> Unit): T { - if (innerFrame == null) throw IllegalStateException("Cannot bind inner view when innerFrame does not exist") - if (innerContent !is T) { - innerFrame!!.removeAllViews() - LayoutInflater.from(innerFrame!!.context).inflate(id, innerFrame) - onFirstBind(innerContent as T) - } - return innerContent as T - } - - inline fun <reified T : View> bindLowerView(@LayoutRes id: Int) = bindLowerView(id) { _: T -> } - - inline fun <reified T : View> bindLowerView(@LayoutRes id: Int, onFirstBind: (T) -> Unit): T { - if (lowerFrame == null) throw IllegalStateException("Cannot bind inner view when lowerContent does not exist") - if (lowerContent !is T) { - lowerFrame!!.removeAllViews() - LayoutInflater.from(lowerFrame!!.context).inflate(id, lowerFrame) - onFirstBind(lowerContent as T) - } - return lowerContent as T - } - - operator fun get(@IdRes id: Int): View = itemView.findViewById(id) - } -}
\ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefPlainText.kt b/core/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefPlainText.kt deleted file mode 100644 index 6f7adf8..0000000 --- a/core/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefPlainText.kt +++ /dev/null @@ -1,29 +0,0 @@ -package ca.allanwang.kau.kpref.items - -import android.view.View -import ca.allanwang.kau.R -import ca.allanwang.kau.kpref.GlobalOptions - -/** - * Created by Allan Wang on 2017-06-14. - * - * Just text with the core options. Extends base preference but has an empty getter and setter - * Useful replacement of [KPrefText] when nothing is displayed on the right side, - * and when the preference is completely handled by the click - * - */ -open class KPrefPlainText(val builder: KPrefPlainTextBuilder) : KPrefItemBase<Unit>(builder) { - - override fun defaultOnClick(itemView: View, innerContent: View?): Boolean { - //nothing - return true - } - - class KPrefPlainTextBuilder( - globalOptions: GlobalOptions, - titleRes: Int - ) : BaseContract<Unit> by BaseBuilder(globalOptions, titleRes, {}, {}) - - override fun getType(): Int = R.id.kau_item_pref_plain_text - -}
\ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefSeekbar.kt b/core/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefSeekbar.kt deleted file mode 100644 index 407018f..0000000 --- a/core/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefSeekbar.kt +++ /dev/null @@ -1,109 +0,0 @@ -package ca.allanwang.kau.kpref.items - -import android.view.View -import android.widget.SeekBar -import android.widget.TextView -import ca.allanwang.kau.R -import ca.allanwang.kau.kpref.GlobalOptions -import ca.allanwang.kau.kpref.KPrefException -import ca.allanwang.kau.logging.KL -import ca.allanwang.kau.utils.tint - -/** - * Created by Allan Wang on 2017-06-07. - * - * Checkbox preference - * When clicked, will toggle the preference and the apply the result to the checkbox - */ -open class KPrefSeekbar(val builder: KPrefSeekbarContract) : KPrefItemBase<Int>(builder) { - - - override fun defaultOnClick(itemView: View, innerContent: View?): Boolean = false - - override fun onPostBindView(viewHolder: ViewHolder, textColor: Int?, accentColor: Int?) { - super.onPostBindView(viewHolder, textColor, accentColor) - val text = viewHolder.bindInnerView<TextView>(R.layout.kau_preference_seekbar_text) - if (textColor != null) text.setTextColor(textColor) - - val tvc = builder.textViewConfigs - - text.tvc() - val seekbar = viewHolder.bindLowerView<SeekBar>(R.layout.kau_preference_seekbar) { - it.max = builder.range - it.incrementProgressBy(builder.increments) - it.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { - override fun onProgressChanged(sb: SeekBar, progress: Int, fromUser: Boolean) { - text.text = builder.toText(progress.fromProgress) - } - - override fun onStartTrackingTouch(sb: SeekBar) {} - - override fun onStopTrackingTouch(sb: SeekBar) { - val trueProgress = sb.progress.fromProgress - pref = trueProgress - } - }) - } - if (accentColor != null) seekbar.tint(accentColor) - text.text = builder.toText(seekbar.progress.fromProgress) //set initial text in case no change occurs - seekbar.progress = pref.toProgress - } - - /** - * Extension of the base contract - */ - interface KPrefSeekbarContract : BaseContract<Int> { - var min: Int - var max: Int - var increments: Int - var range: Int - /** - * Once a seekbar is let go, calculates what text to show in the text view - */ - var toText: (Int) -> String - var textViewConfigs: TextView.() -> Unit - } - - /** - * Default implementation of [KPrefSeekbarContract] - */ - class KPrefSeekbarBuilder( - globalOptions: GlobalOptions, - titleRes: Int, - getter: () -> Int, - setter: (value: Int) -> Unit - ) : KPrefSeekbarContract, BaseContract<Int> by BaseBuilder(globalOptions, titleRes, getter, setter) { - - override var min: Int = 0 - set(value) { - field = value - range = -1 - } - override var max: Int = 100 - set(value) { - field = value - range = -1 - } - override var increments: Int = 1 - - override var range: Int = max - min - //value doesn't matter; setting will prompt the check - set(value) { - if (max <= min) throw KPrefException("Range min ($min) must be smaller than max ($max)") - field = max - min - } - - override var toText: (Int) -> String = { it.toString() } - - override var textViewConfigs: TextView.() -> Unit = {} - } - - val Int.toProgress: Int - get() = this - builder.min - - val Int.fromProgress: Int - get() = this + builder.min - - override fun getType(): Int = R.id.kau_item_pref_seekbar - -}
\ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefSubItems.kt b/core/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefSubItems.kt deleted file mode 100644 index f373ec4..0000000 --- a/core/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefSubItems.kt +++ /dev/null @@ -1,43 +0,0 @@ -package ca.allanwang.kau.kpref.items - -import android.view.View -import ca.allanwang.kau.R -import ca.allanwang.kau.kpref.GlobalOptions -import ca.allanwang.kau.kpref.KPrefAdapterBuilder - -/** - * Created by Allan Wang on 2017-06-14. - * - * Sub item preference - * When clicked, will navigate to a new set of preferences and add the old list to a stack - * - */ -open class KPrefSubItems(val builder: KPrefSubItemsContract) : KPrefItemCore(builder) { - - override fun onClick(itemView: View, innerContent: View?): Boolean { - builder.globalOptions.showNextPrefs(builder.titleRes, builder.itemBuilder) - return true - } - - override fun getLayoutRes(): Int = R.layout.kau_preference - - override fun onPostBindView(viewHolder: ViewHolder, textColor: Int?, accentColor: Int?) {} - /** - * Extension of the base contract with an optional text getter - */ - interface KPrefSubItemsContract : CoreContract { - val itemBuilder: KPrefAdapterBuilder.() -> Unit - } - - /** - * Default implementation of [KPrefTextContract] - */ - class KPrefSubItemsBuilder( - globalOptions: GlobalOptions, - titleRes: Int, - override val itemBuilder: KPrefAdapterBuilder.() -> Unit - ) : KPrefSubItemsContract, CoreContract by CoreBuilder(globalOptions, titleRes) - - override fun getType(): Int = R.id.kau_item_pref_sub_item - -}
\ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefText.kt b/core/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefText.kt deleted file mode 100644 index 9c44cd4..0000000 --- a/core/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefText.kt +++ /dev/null @@ -1,62 +0,0 @@ -package ca.allanwang.kau.kpref.items - -import android.view.View -import android.widget.TextView -import ca.allanwang.kau.R -import ca.allanwang.kau.kpref.GlobalOptions -import ca.allanwang.kau.utils.toast - -/** - * Created by Allan Wang on 2017-06-14. - * - * Text preference - * Holds a textview to display data on the right - * This is still a generic preference - * - */ -open class KPrefText<T>(val builder: KPrefTextContract<T>) : KPrefItemBase<T>(builder) { - - /** - * Automatically reload on set - */ - override var pref: T - get() = base.getter.invoke() - set(value) { - base.setter.invoke(value) - builder.reloadSelf() - } - - override fun defaultOnClick(itemView: View, innerContent: View?): Boolean { - itemView.context.toast("No click function set") - return true - } - - override fun onPostBindView(viewHolder: ViewHolder, textColor: Int?, accentColor: Int?) { - super.onPostBindView(viewHolder, textColor, accentColor) - val textview = viewHolder.bindInnerView<TextView>(R.layout.kau_preference_text) - if (textColor != null) textview.setTextColor(textColor) - textview.text = builder.textGetter.invoke(pref) - } - - /** - * Extension of the base contract with an optional text getter - */ - interface KPrefTextContract<T> : BaseContract<T> { - var textGetter: (T) -> String? - } - - /** - * Default implementation of [KPrefTextContract] - */ - class KPrefTextBuilder<T>( - globalOptions: GlobalOptions, - titleRes: Int, - getter: () -> T, - setter: (value: T) -> Unit - ) : KPrefTextContract<T>, BaseContract<T> by BaseBuilder<T>(globalOptions, titleRes, getter, setter) { - override var textGetter: (T) -> String? = { it?.toString() } - } - - override fun getType(): Int = R.id.kau_item_pref_text - -}
\ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/searchview/SearchItem.kt b/core/src/main/kotlin/ca/allanwang/kau/searchview/SearchItem.kt deleted file mode 100644 index 96dc789..0000000 --- a/core/src/main/kotlin/ca/allanwang/kau/searchview/SearchItem.kt +++ /dev/null @@ -1,80 +0,0 @@ -package ca.allanwang.kau.searchview - -import android.graphics.Typeface -import android.graphics.drawable.Drawable -import android.support.constraint.ConstraintLayout -import android.support.v7.widget.RecyclerView -import android.text.Spannable -import android.text.SpannableStringBuilder -import android.text.style.StyleSpan -import android.view.View -import android.widget.ImageView -import android.widget.TextView -import ca.allanwang.kau.R -import ca.allanwang.kau.iitems.KauIItem -import ca.allanwang.kau.utils.* -import com.mikepenz.google_material_typeface_library.GoogleMaterial -import com.mikepenz.iconics.typeface.IIcon - -/** - * Created by Allan Wang on 2017-06-23. - * - * A holder for each individual search item - * Contains a [key] which acts as a unique identifier (eg url) - * and a [content] which is displayed in the item - */ -class SearchItem(val key: String, - val content: String = key, - val description: String? = null, - val iicon: IIcon? = GoogleMaterial.Icon.gmd_search, - val image: Drawable? = null -) : KauIItem<SearchItem, SearchItem.ViewHolder>( - R.layout.kau_search_iitem, - { ViewHolder(it) }, - R.id.kau_item_search -) { - - companion object { - @JvmStatic var foregroundColor: Int = 0xdd000000.toInt() - @JvmStatic var backgroundColor: Int = 0xfffafafa.toInt() - } - - var styledContent: SpannableStringBuilder? = null - - /** - * Highlight the subText if it is present in the content - */ - fun withHighlights(subText: String) { - val index = content.indexOf(subText) - if (index == -1) return - styledContent = SpannableStringBuilder(content) - styledContent!!.setSpan(StyleSpan(Typeface.BOLD), index, index + subText.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) - } - - override fun bindView(holder: ViewHolder, payloads: MutableList<Any>?) { - super.bindView(holder, payloads) - holder.title.setTextColor(foregroundColor) - holder.desc.setTextColor(foregroundColor.adjustAlpha(0.6f)) - - if (image != null) holder.icon.setImageDrawable(image) - else holder.icon.setIcon(iicon, sizeDp = 18, color = foregroundColor) - - holder.container.setRippleBackground(foregroundColor, backgroundColor) - holder.title.text = styledContent ?: content - if (description?.isNotBlank() ?: false) holder.desc.visible().text = description - } - - override fun unbindView(holder: ViewHolder) { - super.unbindView(holder) - holder.title.text = null - holder.desc.gone().text = null - holder.icon.setImageDrawable(null) - } - - class ViewHolder(v: View) : RecyclerView.ViewHolder(v) { - val icon: ImageView by bindView(R.id.search_icon) - val title: TextView by bindView(R.id.search_title) - val desc: TextView by bindView(R.id.search_desc) - val container: ConstraintLayout by bindView(R.id.search_item_frame) - } -}
\ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/searchview/SearchView.kt b/core/src/main/kotlin/ca/allanwang/kau/searchview/SearchView.kt deleted file mode 100644 index c077a06..0000000 --- a/core/src/main/kotlin/ca/allanwang/kau/searchview/SearchView.kt +++ /dev/null @@ -1,412 +0,0 @@ -package ca.allanwang.kau.searchview - -import android.app.Activity -import android.content.Context -import android.content.res.ColorStateList -import android.graphics.Color -import android.support.annotation.ColorInt -import android.support.annotation.IdRes -import android.support.annotation.StringRes -import android.support.transition.AutoTransition -import android.support.v7.widget.AppCompatEditText -import android.support.v7.widget.LinearLayoutManager -import android.support.v7.widget.RecyclerView -import android.util.AttributeSet -import android.view.* -import android.widget.FrameLayout -import android.widget.ImageView -import android.widget.ProgressBar -import ca.allanwang.kau.R -import ca.allanwang.kau.animators.NoAnimator -import ca.allanwang.kau.kotlin.nonReadable -import ca.allanwang.kau.searchview.SearchView.Configs -import ca.allanwang.kau.utils.* -import ca.allanwang.kau.views.BoundedCardView -import com.jakewharton.rxbinding2.widget.RxTextView -import com.mikepenz.fastadapter.commons.adapters.FastItemAdapter -import com.mikepenz.google_material_typeface_library.GoogleMaterial -import com.mikepenz.iconics.typeface.IIcon -import io.reactivex.Observable -import io.reactivex.schedulers.Schedulers -import org.jetbrains.anko.runOnUiThread - - -/** - * Created by Allan Wang on 2017-06-23. - * - * A materialized SearchView with complete theming and observables - * This view can be added programmatically and configured using the [Configs] DSL - * It is preferred to add the view through an activity, but it can be attached to any ViewGroup - * Beware of where specifically this is added, as its view or the keyboard may affect positioning - * - * Huge thanks to @lapism for his base - * https://github.com/lapism/SearchView - */ -class SearchView @JvmOverloads constructor( - context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : FrameLayout(context, attrs, defStyleAttr) { - - /** - * Collection of all possible arguments when building the SearchView - * Everything is made as opened as possible so other components may be found in the [SearchView] - * However, these are the notable options put together an an inner class for better visibility - */ - inner class Configs { - /** - * In the searchview, foreground color accounts for all text colors and icon colors - * Various alpha levels may be used for sub texts/dividers etc - */ - var foregroundColor: Int - get() = SearchItem.foregroundColor - set(value) { - if (SearchItem.foregroundColor == value) return - SearchItem.foregroundColor = value - tintForeground(value) - } - /** - * Namely the background for the card and recycler view - */ - var backgroundColor: Int - get() = SearchItem.backgroundColor - set(value) { - if (SearchItem.backgroundColor == value) return - SearchItem.backgroundColor = value - tintBackground(value) - } - /** - * Icon for the leftmost ImageView, which typically contains the hamburger menu/back arror - */ - var navIcon: IIcon? = GoogleMaterial.Icon.gmd_arrow_back - set(value) { - field = value - iconNav.setSearchIcon(value) - if (value == null) iconNav.gone() - } - - /** - * Optional icon just to the left of the clear icon - * This is not implemented by default, but can be used for anything, such as mic or redirects - * Returns the extra imageview - * Set the iicon as null to hide the extra icon - */ - fun setExtraIcon(iicon: IIcon?, onClick: OnClickListener?): ImageView { - iconExtra.setSearchIcon(iicon) - if (iicon == null) iconClear.gone() - iconExtra.setOnClickListener(onClick) - return iconExtra - } - - /** - * Icon for the rightmost ImageView, which typically contains a close icon - */ - var clearIcon: IIcon? = GoogleMaterial.Icon.gmd_clear - set(value) { - field = value - iconClear.setSearchIcon(value) - if (value == null) iconClear.gone() - } - /** - * Duration for the circular reveal animation - */ - var revealDuration: Long = 300L - /** - * Duration for the auto transition, which is namely used to resize the recycler view - */ - var transitionDuration: Long = 100L - /** - * Defines whether the edit text and mainAdapter should clear themselves when the searchView is closed - */ - var shouldClearOnClose: Boolean = false - /** - * Callback that will be called every time the searchView opens - */ - var openListener: ((searchView: SearchView) -> Unit)? = null - /** - * Callback that will be called every time the searchView closes - */ - var closeListener: ((searchView: SearchView) -> Unit)? = null - /** - * Draw a divider between the search bar and the suggestion items - * The divider is colored based on the [foregroundColor] - */ - var withDivider: Boolean = true - set(value) { - field = value - if (value) divider.visible() else divider.invisible() - } - /** - * Hint string to be set in the searchView - */ - var hintText: String? - get() = editText.hint?.toString() - set(value) { - editText.hint = value - } - /** - * Hint string res to be set in the searchView - */ - var hintTextRes: Int - @Deprecated(level = DeprecationLevel.ERROR, message = "Non readable property") - get() = nonReadable() - @StringRes set(value) { - hintText = context.string(value) - } - /** - * StringRes for a "no results found" item - * If [results] is ever set to an empty list, it will default to - * a list with one item with this string - * - * For simplicity, kau contains [R.string.kau_no_results_found] - * which you may use - */ - var noResultsFound: Int = -1 - /** - * Text watcher configurations on init - * By default, the observable is on a separate thread, so you may directly execute background processes - * This builder acts on an observable, so you may switch threads, debounce, and do anything else that you require - */ - var textObserver: (observable: Observable<String>, searchView: SearchView) -> Unit = { _, _ -> } - /** - * Click event for suggestion items - * This event is only triggered when [key] is not blank (like in [noResultsFound] - */ - var onItemClick: (position: Int, key: String, content: String, searchView: SearchView) -> Unit = { _, _, _, _ -> } - /** - * Long click event for suggestion items - * This event is only triggered when [key] is not blank (like in [noResultsFound] - */ - var onItemLongClick: (position: Int, key: String, content: String, searchView: SearchView) -> Unit = { _, _, _, _ -> } - /** - * If a [SearchItem]'s title contains the submitted query, make that portion bold - * See [SearchItem.withHighlights] - */ - var highlightQueryText: Boolean = true - } - - /** - * Contract for mainAdapter items - * Setting results will ensure that the values are sent on the UI thread - */ - var results: List<SearchItem> - get() = adapter.adapterItems - set(value) = context.runOnUiThread { - val list = if (value.isEmpty() && configs.noResultsFound > 0) - listOf(SearchItem("", context.string(configs.noResultsFound), iicon = null)) - else value - if (configs.highlightQueryText && value.isNotEmpty()) list.forEach { it.withHighlights(editText.text.toString()) } - cardTransition() - adapter.setNewList(list) - } - - /** - * Empties the list on the UI thread - * The noResults item will not be added - */ - internal fun clearResults() = context.runOnUiThread { cardTransition(); adapter.clear() } - - val configs = Configs() - //views - private val shadow: View by bindView(R.id.search_shadow) - private val card: BoundedCardView by bindView(R.id.search_cardview) - private val iconNav: ImageView by bindView(R.id.search_nav) - private val editText: AppCompatEditText by bindView(R.id.search_edit_text) - val textEvents: Observable<String> - private val progress: ProgressBar by bindView(R.id.search_progress) - val iconExtra: ImageView by bindView(R.id.search_extra) - private val iconClear: ImageView by bindView(R.id.search_clear) - private val divider: View by bindView(R.id.search_divider) - private val recycler: RecyclerView by bindView(R.id.search_recycler) - val adapter = FastItemAdapter<SearchItem>() - var menuItem: MenuItem? = null - val isOpen: Boolean - get() = card.isVisible() - - /* - * Ripple start points and search view offset - * These are calculated every time the search view is opened, - * and can be overridden with the open listener if necessary - */ - var menuX: Int = -1 //starting x for circular reveal - var menuY: Int = -1 //reference for cardview's marginTop - var menuHalfHeight: Int = -1 //starting y for circular reveal (relative to the cardview) - - init { - View.inflate(context, R.layout.kau_search_view, this) - z = 99f - iconNav.setSearchIcon(configs.navIcon).setOnClickListener { revealClose() } - iconClear.setSearchIcon(configs.clearIcon).setOnClickListener { editText.text.clear() } - tintForeground(configs.foregroundColor) - tintBackground(configs.backgroundColor) - with(recycler) { - isNestedScrollingEnabled = false - layoutManager = LinearLayoutManager(context) - addOnScrollListener(object : RecyclerView.OnScrollListener() { - override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { - super.onScrollStateChanged(recyclerView, newState) - if (newState == RecyclerView.SCROLL_STATE_DRAGGING) hideKeyboard() - } - }) - adapter = this@SearchView.adapter - itemAnimator = NoAnimator() - } - with(adapter) { - withSelectable(true) - withOnClickListener { _, _, item, position -> - if (item.key.isNotBlank()) configs.onItemClick(position, item.key, item.content, this@SearchView); true - } - withOnLongClickListener { _, _, item, position -> - if (item.key.isNotBlank()) configs.onItemLongClick(position, item.key, item.content, this@SearchView); true - } - } - textEvents = RxTextView.textChangeEvents(editText) - .skipInitialValue() - .observeOn(Schedulers.newThread()) - .map { it.text().toString().trim() } - textEvents.filter { it.isBlank() } - .subscribe { clearResults() } - } - - internal fun ImageView.setSearchIcon(iicon: IIcon?): ImageView { - setIcon(iicon, sizeDp = 18, color = configs.foregroundColor) - return this - } - - internal fun cardTransition(builder: AutoTransition.() -> Unit = {}) { - card.transitionAuto { duration = configs.transitionDuration; builder() } - } - - fun config(config: Configs.() -> Unit) { - configs.config() - } - - /** - * Binds the SearchView to a menu item and handles everything internally - * This is assuming that SearchView has already been added to a ViewGroup - * If not, see the extension function [bindSearchView] - */ - 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) - if (menuItem!!.icon == null) menuItem!!.icon = GoogleMaterial.Icon.gmd_search.toDrawable(context, 18, menuIconColor) - card.gone() - menuItem!!.setOnMenuItemClickListener { configureCoords(it); revealOpen(); true } - shadow.setOnClickListener { revealClose() } - return this - } - - fun unBind(replacementMenuItemClickListener: MenuItem.OnMenuItemClickListener? = null) { - parentViewGroup.removeView(this) - menuItem?.setOnMenuItemClickListener(replacementMenuItemClickListener) - } - - fun configureCoords(item: MenuItem) { - val view = parentViewGroup.findViewById<View>(item.itemId) ?: return - val locations = IntArray(2) - view.getLocationOnScreen(locations) - menuX = (locations[0] + view.width / 2) - menuHalfHeight = view.height / 2 - menuY = (locations[1] + menuHalfHeight) - 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 - return false - } - }) - } - - /** - * Handle a back press event - * Returns true if back press is consumed, false otherwise - */ - fun onBackPressed(): Boolean { - if (isOpen && menuItem != null) { - revealClose() - return true - } - return false - } - - /** - * Tint foreground attributes - * This can be done publicly through [configs], which will also save the color - */ - internal fun tintForeground(@ColorInt color: Int) { - iconNav.drawable.setTint(color) - iconClear.drawable.setTint(color) - divider.setBackgroundColor(color.adjustAlpha(0.1f)) - editText.tint(color) - editText.setTextColor(ColorStateList.valueOf(color)) - } - - /** - * Tint background attributes - * This can be done publicly through [configs], which will also save the color - */ - internal fun tintBackground(@ColorInt color: Int) { - card.setCardBackgroundColor(color) - } - - fun revealOpen() { - if (isOpen) return - /** - * The y component is relative to the cardView, but it hasn't been drawn yet so its own height is 0 - * We therefore use half the menuItem height, which is a close approximation to our intended value - * The cardView matches the parent's width, so menuX is correct - */ - configs.openListener?.invoke(this) - shadow.fadeIn() - editText.showKeyboard() - card.circularReveal(menuX, menuHalfHeight, duration = configs.revealDuration) { - cardTransition() - recycler.visible() - } - } - - fun revealClose() { - if (!isOpen) return - shadow.fadeOut(duration = configs.transitionDuration) - cardTransition { - addEndListener { - card.circularHide(menuX, menuHalfHeight, duration = configs.revealDuration, - onFinish = { - configs.closeListener?.invoke(this@SearchView) - if (configs.shouldClearOnClose) editText.text.clear() - recycler.gone() - }) - } - } - recycler.gone() - editText.hideKeyboard() - } -} - -@DslMarker -annotation class KauSearch - -/** - * Helper function that binds to an activity's main view - */ -@KauSearch -fun Activity.bindSearchView(menu: Menu, @IdRes id: Int, @ColorInt menuIconColor: Int = Color.WHITE, config: SearchView.Configs.() -> Unit = {}): SearchView - = findViewById<ViewGroup>(android.R.id.content).bindSearchView(menu, id, menuIconColor, config) - -/** - * Bind searchView to a menu item; call this in [Activity.onCreateOptionsMenu] - * Be wary that if you may reinflate the menu many times (eg through [Activity.invalidateOptionsMenu]), - * it may be worthwhile to hold a reference to the searchview and only bind it if it hasn't been bound before - */ -@KauSearch -fun ViewGroup.bindSearchView(menu: Menu, @IdRes id: Int, @ColorInt menuIconColor: Int = Color.WHITE, config: SearchView.Configs.() -> Unit = {}): SearchView { - val searchView = SearchView(context) - searchView.layoutParams = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT) - addView(searchView) - searchView.bind(menu, id, menuIconColor, config) - return searchView -} - diff --git a/core/src/main/kotlin/ca/allanwang/kau/views/SimpleRippleDrawable.kt b/core/src/main/kotlin/ca/allanwang/kau/ui/SimpleRippleDrawable.kt index df842f6..30c8edd 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/views/SimpleRippleDrawable.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/ui/SimpleRippleDrawable.kt @@ -1,4 +1,4 @@ -package ca.allanwang.kau.views +package ca.allanwang.kau.ui import android.content.res.ColorStateList import android.graphics.drawable.ColorDrawable diff --git a/core/src/main/kotlin/ca/allanwang/kau/views/RippleCanvas.kt b/core/src/main/kotlin/ca/allanwang/kau/ui/views/RippleCanvas.kt index 805fb21..773490c 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/views/RippleCanvas.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/ui/views/RippleCanvas.kt @@ -1,4 +1,4 @@ -package ca.allanwang.kau.views +package ca.allanwang.kau.ui.views import android.animation.ArgbEvaluator import android.animation.ValueAnimator 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 b4752a5..48a048b 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/utils/ViewUtils.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/utils/ViewUtils.kt @@ -21,7 +21,7 @@ import android.view.inputmethod.InputMethodManager import android.widget.ImageView import android.widget.TextView import ca.allanwang.kau.logging.KL -import ca.allanwang.kau.views.createSimpleRippleDrawable +import ca.allanwang.kau.ui.createSimpleRippleDrawable import com.mikepenz.iconics.IconicsDrawable import com.mikepenz.iconics.typeface.IIcon diff --git a/core/src/main/kotlin/ca/allanwang/kau/views/BoundedCardView.kt b/core/src/main/kotlin/ca/allanwang/kau/views/BoundedCardView.kt deleted file mode 100644 index 0cb65d0..0000000 --- a/core/src/main/kotlin/ca/allanwang/kau/views/BoundedCardView.kt +++ /dev/null @@ -1,50 +0,0 @@ -package ca.allanwang.kau.views - -import android.content.Context -import android.graphics.Rect -import android.support.v7.widget.CardView -import android.util.AttributeSet -import ca.allanwang.kau.R -import ca.allanwang.kau.utils.parentViewGroup -import ca.allanwang.kau.utils.parentVisibleHeight - - -/** - * Created by Allan Wang on 2017-06-26. - * - * CardView with a limited height - * This view should be used with wrap_content as its height - * Defaults to at most the parent's visible height - */ -class BoundedCardView @JvmOverloads constructor( - context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : CardView(context, attrs, defStyleAttr) { - - /** - * Maximum height possible, defined in dp (will be converted to px) - * Defaults to parent's visible height - */ - var maxHeight: Int = -1 - /** - * Percentage of resulting max height to fill - * Negative value = fill all of maxHeight - */ - var maxHeightPercent: Float = -1.0f - - init { - if (attrs != null) { - val styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.BoundedCardView) - maxHeight = styledAttrs.getDimensionPixelSize(R.styleable.BoundedCardView_maxHeight, -1) - maxHeightPercent = styledAttrs.getFloat(R.styleable.BoundedCardView_maxHeightPercent, -1.0f) - styledAttrs.recycle() - } - } - - override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { - var maxMeasureHeight = if (maxHeight > 0) maxHeight else parentVisibleHeight - if (maxHeightPercent > 0f) maxMeasureHeight = (maxMeasureHeight * maxHeightPercent).toInt() - val trueHeightMeasureSpec = MeasureSpec.makeMeasureSpec(maxMeasureHeight, MeasureSpec.AT_MOST) - super.onMeasure(widthMeasureSpec, trueHeightMeasureSpec) - } - -}
\ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/views/CutoutView.kt b/core/src/main/kotlin/ca/allanwang/kau/views/CutoutView.kt deleted file mode 100644 index 023bdb4..0000000 --- a/core/src/main/kotlin/ca/allanwang/kau/views/CutoutView.kt +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright 2015 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ca.allanwang.kau.views - -import android.content.Context -import android.graphics.* -import android.graphics.drawable.Drawable -import android.text.TextPaint -import android.util.AttributeSet -import android.util.DisplayMetrics -import android.util.TypedValue -import android.view.View -import ca.allanwang.kau.R -import ca.allanwang.kau.utils.dimenPixelSize -import ca.allanwang.kau.utils.getFont -import ca.allanwang.kau.utils.parentVisibleHeight -import ca.allanwang.kau.utils.toBitmap - -/** - * A view which punches out some text from an opaque color block, allowing you to see through it. - */ -class CutoutView @JvmOverloads constructor( - context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : View(context, attrs, defStyleAttr) { - - companion object { - const val PHI = 1.6182f - const val TYPE_TEXT = 100 - const val TYPE_DRAWABLE = 101 - } - - private val paint: TextPaint = TextPaint(Paint.ANTI_ALIAS_FLAG) - private var bitmapScaling: Float = 1f - private var cutout: Bitmap? = null - var foregroundColor = Color.MAGENTA - var text: String? = "Text" - set(value) { - field = value - if (value != null) cutoutType = TYPE_TEXT - else if (drawable != null) cutoutType = TYPE_DRAWABLE - } - var cutoutType: Int = TYPE_TEXT - private var textSize: Float = 0f - private var cutoutY: Float = 0f - private var cutoutX: Float = 0f - var drawable: Drawable? = null - set(value) { - field = value - if (value != null) cutoutType = TYPE_DRAWABLE - else if (text != null) cutoutType = TYPE_TEXT - } - private var heightPercentage: Float = 0f - private var minHeight: Float = 0f - private val maxTextSize: Float - - init { - if (attrs != null) { - val a = context.obtainStyledAttributes(attrs, R.styleable.CutoutView, 0, 0) - if (a.hasValue(R.styleable.CutoutView_font)) - paint.typeface = context.getFont(a.getString(R.styleable.CutoutView_font)) - foregroundColor = a.getColor(R.styleable.CutoutView_foregroundColor, foregroundColor) - text = a.getString(R.styleable.CutoutView_android_text) ?: text - minHeight = a.getDimension(R.styleable.CutoutView_android_minHeight, minHeight) - heightPercentage = a.getFloat(R.styleable.CutoutView_heightPercentageToScreen, heightPercentage) - a.recycle() - } - maxTextSize = context.dimenPixelSize(R.dimen.kau_display_4_text_size).toFloat() - } - - override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { - super.onSizeChanged(w, h, oldw, oldh) - calculatePosition() - createBitmap() - } - - private fun calculatePosition() { - when (cutoutType) { - TYPE_TEXT -> calculateTextPosition() - TYPE_DRAWABLE -> calculateImagePosition() - } - } - - private fun calculateTextPosition() { - val targetWidth = width / PHI - textSize = getSingleLineTextSize(text!!, paint, targetWidth, 0f, maxTextSize, - 0.5f, resources.displayMetrics) - paint.textSize = textSize - - // measuring text is fun :] see: https://chris.banes.me/2014/03/27/measuring-text/ - cutoutX = (width - paint.measureText(text)) / 2 - val textBounds = Rect() - paint.getTextBounds(text, 0, text!!.length, textBounds) - val textHeight = textBounds.height().toFloat() - cutoutY = (height + textHeight) / 2 - } - - private fun calculateImagePosition() { - if (drawable!!.intrinsicHeight <= 0 || drawable!!.intrinsicWidth <= 0) throw IllegalArgumentException("Drawable's intrinsic size cannot be less than 0") - val targetWidth = width / PHI - val targetHeight = height / PHI - bitmapScaling = Math.min(targetHeight / drawable!!.intrinsicHeight, targetWidth / drawable!!.intrinsicWidth) - cutoutX = (width - drawable!!.intrinsicWidth * bitmapScaling) / 2 - cutoutY = (height - drawable!!.intrinsicHeight * bitmapScaling) / 2 - } - - /** - * If height percent is specified, ensure it is met - */ - override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { - val minHeight = Math.max(minHeight, heightPercentage * parentVisibleHeight) - val trueHeightMeasureSpec = if (minHeight > 0) - MeasureSpec.makeMeasureSpec(Math.max(minHeight.toInt(), measuredHeight), MeasureSpec.EXACTLY) - else heightMeasureSpec - super.onMeasure(widthMeasureSpec, trueHeightMeasureSpec) - } - - /** - * Recursive binary search to find the best size for the text. - - * Adapted from https://github.com/grantland/android-autofittextview - */ - fun getSingleLineTextSize(text: String, - paint: TextPaint, - targetWidth: Float, - low: Float, - high: Float, - precision: Float, - metrics: DisplayMetrics): Float { - val mid = (low + high) / 2.0f - - paint.textSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, mid, metrics) - val maxLineWidth = paint.measureText(text) - - return if (high - low < precision) low - else if (maxLineWidth > targetWidth) getSingleLineTextSize(text, paint, targetWidth, low, mid, precision, metrics) - else if (maxLineWidth < targetWidth) getSingleLineTextSize(text, paint, targetWidth, mid, high, precision, metrics) - else mid - } - - private fun createBitmap() { - if (!(cutout?.isRecycled ?: true)) - cutout?.recycle() - if (width == 0 || height == 0) return - cutout = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) - cutout!!.setHasAlpha(true) - val cutoutCanvas = Canvas(cutout!!) - cutoutCanvas.drawColor(foregroundColor) - paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR) - - when (cutoutType) { - TYPE_TEXT -> { - // this is the magic – Clear mode punches out the bitmap - paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR) - cutoutCanvas.drawText(text, cutoutX, cutoutY, paint) - } - TYPE_DRAWABLE -> { - cutoutCanvas.drawBitmap(drawable!!.toBitmap(bitmapScaling, Bitmap.Config.ALPHA_8), cutoutX, cutoutY, paint) - } - - } - } - - override fun onDraw(canvas: Canvas) { - canvas.drawBitmap(cutout!!, 0f, 0f, null) - } - - override fun hasOverlappingRendering(): Boolean = true - -} diff --git a/core/src/main/kotlin/ca/allanwang/kau/widgets/ElasticDragDismissFrameLayout.kt b/core/src/main/kotlin/ca/allanwang/kau/widgets/ElasticDragDismissFrameLayout.kt deleted file mode 100644 index c39a278..0000000 --- a/core/src/main/kotlin/ca/allanwang/kau/widgets/ElasticDragDismissFrameLayout.kt +++ /dev/null @@ -1,237 +0,0 @@ -/* - * Copyright 2015 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ca.allanwang.kau.widgets - -import android.app.Activity -import android.content.Context -import android.graphics.Color -import android.util.AttributeSet -import android.view.View -import android.widget.FrameLayout -import ca.allanwang.kau.R -import ca.allanwang.kau.utils.* - -/** - * A [FrameLayout] which responds to nested scrolls to create drag-dismissable layouts. - * Applies an elasticity factor to reduce movement as you approach the given dismiss distance. - * Optionally also scales down content during drag. - */ -class ElasticDragDismissFrameLayout @JvmOverloads constructor( - context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, defStyleRes: Int = 0 -) : FrameLayout(context, attrs, defStyleAttr, defStyleRes) { - - // configurable attribs - var dragDismissDistance = context.dimen(R.dimen.kau_drag_dismiss_distance).dpToPx - var dragDismissFraction = -1f - var dragDismissScale = 1f - set(value) { - field = value - shouldScale = value != 1f - } - private var shouldScale = false - var dragElacticity = 0.8f - - // state - private var totalDrag: Float = 0f - private var draggingDown = false - private var draggingUp = false - - private var callbacks: MutableList<ElasticDragDismissCallback> = mutableListOf() - - init { - if (attrs != null) { - val a = getContext().obtainStyledAttributes(attrs, R.styleable.ElasticDragDismissFrameLayout, 0, 0) - dragDismissDistance = a.getDimensionPixelSize(R.styleable.ElasticDragDismissFrameLayout_dragDismissDistance, Int.MAX_VALUE).toFloat() - dragDismissFraction = a.getFloat(R.styleable.ElasticDragDismissFrameLayout_dragDismissFraction, dragDismissFraction) - dragDismissScale = a.getFloat(R.styleable.ElasticDragDismissFrameLayout_dragDismissScale, dragDismissScale) - dragElacticity = a.getFloat(R.styleable.ElasticDragDismissFrameLayout_dragElasticity, dragElacticity) - a.recycle() - } - } - - abstract class ElasticDragDismissCallback { - - /** - * Called for each drag event. - - * @param elasticOffset Indicating the drag offset with elasticity applied i.e. may - * * exceed 1. - * * - * @param elasticOffsetPixels The elastically scaled drag distance in pixels. - * * - * @param rawOffset Value from [0, 1] indicating the raw drag offset i.e. - * * without elasticity applied. A value of 1 indicates that the - * * dismiss distance has been reached. - * * - * @param rawOffsetPixels The raw distance the user has dragged - */ - internal open fun onDrag(elasticOffset: Float, elasticOffsetPixels: Float, - rawOffset: Float, rawOffsetPixels: Float) { - } - - /** - * Called when dragging is released and has exceeded the threshold dismiss distance. - */ - internal open fun onDragDismissed() {} - - } - - override fun onStartNestedScroll(child: View, target: View, nestedScrollAxes: Int): Boolean { - return nestedScrollAxes and View.SCROLL_AXIS_VERTICAL != 0 - } - - override fun onNestedPreScroll(target: View, dx: Int, dy: Int, consumed: IntArray) { - // if we're in a drag gesture and the user reverses up the we should take those events - if (draggingDown && dy > 0 || draggingUp && dy < 0) { - dragScale(dy) - consumed[1] = dy - } - } - - override fun onNestedScroll(target: View, dxConsumed: Int, dyConsumed: Int, - dxUnconsumed: Int, dyUnconsumed: Int) { - dragScale(dyUnconsumed) - } - - override fun onStopNestedScroll(child: View) { - if (Math.abs(totalDrag) >= dragDismissDistance) { - dispatchDismissCallback() - } else { // settle back to natural position - animate() - .translationY(0f) - .scaleX(1f) - .scaleY(1f) - .setDuration(200L) - .setInterpolator(AnimHolder.fastOutSlowInInterpolator(context)) - .setListener(null) - .start() - totalDrag = 0f - draggingUp = false - draggingDown = draggingUp - dispatchDragCallback(0f, 0f, 0f, 0f) - } - } - - override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { - super.onSizeChanged(w, h, oldw, oldh) - if (dragDismissFraction > 0f) { - dragDismissDistance = h * dragDismissFraction - } - } - - fun addListener(listener: ElasticDragDismissCallback) { - callbacks.add(listener) - } - - fun removeListener(listener: ElasticDragDismissCallback) { - callbacks.remove(listener) - } - - private fun dragScale(scroll: Int) { - if (scroll == 0) return - - totalDrag += scroll.toFloat() - - // track the direction & set the pivot point for scaling - // don't double track i.e. if start dragging down and then reverse, keep tracking as - // dragging down until they reach the 'natural' position - if (scroll < 0 && !draggingUp && !draggingDown) { - draggingDown = true - if (shouldScale) pivotY = height.toFloat() - } else if (scroll > 0 && !draggingDown && !draggingUp) { - draggingUp = true - if (shouldScale) pivotY = 0f - } - // how far have we dragged relative to the distance to perform a dismiss - // (0–1 where 1 = dismiss distance). Decreasing logarithmically as we approach the limit - var dragFraction = Math.log10((1 + Math.abs(totalDrag) / dragDismissDistance).toDouble()).toFloat() - - // calculate the desired translation given the drag fraction - var dragTo = dragFraction * dragDismissDistance * dragElacticity - - if (draggingUp) { - // as we use the absolute magnitude when calculating the drag fraction, need to - // re-apply the drag direction - dragTo *= -1f - } - translationY = dragTo - - if (shouldScale) { - val scale = 1 - (1 - dragDismissScale) * dragFraction - scaleX = scale - scaleY = scale - } - - // 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 - if (draggingDown && totalDrag >= 0 || draggingUp && totalDrag <= 0) { - dragFraction = 0f - dragTo = dragFraction - totalDrag = dragTo - draggingUp = false - draggingDown = draggingUp - translationY = 0f - scaleX = 1f - scaleY = 1f - } - dispatchDragCallback(dragFraction, dragTo, - Math.min(1f, Math.abs(totalDrag) / dragDismissDistance), totalDrag) - } - - private fun dispatchDragCallback(elasticOffset: Float, elasticOffsetPixels: Float, - rawOffset: Float, rawOffsetPixels: Float) { - callbacks.forEach { - it.onDrag(elasticOffset, elasticOffsetPixels, - rawOffset, rawOffsetPixels) - } - } - - private fun dispatchDismissCallback() { - callbacks.forEach { it.onDragDismissed() } - } - - /** - * An [ElasticDragDismissCallback] which fades system chrome (i.e. status bar and - * navigation bar) whilst elastic drags are performed and - * [finishes][Activity.finishAfterTransition] the activity when drag dismissed. - */ - open class SystemChromeFader(private val activity: Activity) : ElasticDragDismissCallback() { - private val statusBarAlpha: Int = Color.alpha(activity.statusBarColor) - private val navBarAlpha: Int = Color.alpha(activity.navigationBarColor) - private val fadeNavBar: Boolean = activity.isNavBarOnBottom - - public override fun onDrag(elasticOffset: Float, elasticOffsetPixels: Float, - rawOffset: Float, rawOffsetPixels: Float) { - if (elasticOffsetPixels > 0) { - // dragging downward, fade the status bar in proportion - activity.statusBarColor = activity.statusBarColor.withAlpha(((1f - rawOffset) * statusBarAlpha).toInt()) - } else if (elasticOffsetPixels == 0f) { - // reset - activity.statusBarColor = activity.statusBarColor.withAlpha(statusBarAlpha) - activity.navigationBarColor = activity.navigationBarColor.withAlpha(navBarAlpha) - } else if (fadeNavBar) { - // dragging upward, fade the navigation bar in proportion - activity.navigationBarColor = activity.navigationBarColor.withAlpha(((1f - rawOffset) * navBarAlpha).toInt()) - } - } - - public override fun onDragDismissed() { - activity.finishAfterTransition() - } - } - -} diff --git a/core/src/main/kotlin/ca/allanwang/kau/widgets/InkPageIndicator.java b/core/src/main/kotlin/ca/allanwang/kau/widgets/InkPageIndicator.java deleted file mode 100644 index 78e915d..0000000 --- a/core/src/main/kotlin/ca/allanwang/kau/widgets/InkPageIndicator.java +++ /dev/null @@ -1,859 +0,0 @@ -/* - * Copyright 2015 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ca.allanwang.kau.widgets; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; -import android.animation.ValueAnimator; -import android.content.Context; -import android.content.res.TypedArray; -import android.database.DataSetObserver; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Path; -import android.graphics.RectF; -import android.support.annotation.ColorInt; -import android.support.v4.view.ViewPager; -import android.util.AttributeSet; -import android.util.Log; -import android.view.View; -import android.view.animation.Interpolator; - -import java.util.Arrays; - -import ca.allanwang.kau.R; -import ca.allanwang.kau.utils.AnimHolder; -import ca.allanwang.kau.utils.ColorUtilsKt; - -/** - * An ink inspired widget for indicating pages in a {@link ViewPager}. - */ -public class InkPageIndicator extends View implements ViewPager.OnPageChangeListener, - View.OnAttachStateChangeListener { - - // defaults - private static final int DEFAULT_DOT_SIZE = 8; // dp - private static final int DEFAULT_GAP = 12; // dp - private static final int DEFAULT_ANIM_DURATION = 400; // ms - private static final int DEFAULT_UNSELECTED_COLOUR = 0x80ffffff; // 50% white - private static final int DEFAULT_SELECTED_COLOUR = 0xffffffff; // 100% white - - // constants - private static final float INVALID_FRACTION = -1f; - private static final float MINIMAL_REVEAL = 0.00001f; - - // configurable attributes - private int dotDiameter; - private int gap; - private long animDuration; - private int unselectedColour; - private int selectedColour; - - public void setColour(@ColorInt int color) { - selectedColour = color; - unselectedColour = ColorUtilsKt.adjustAlpha(color, 0.5f); - selectedPaint.setColor(selectedColour); - unselectedPaint.setColor(unselectedColour); - } - - // derived from attributes - private float dotRadius; - private float halfDotRadius; - private long animHalfDuration; - private float dotTopY; - private float dotCenterY; - private float dotBottomY; - - // ViewPager - private ViewPager viewPager; - - // state - private int pageCount; - private int currentPage; - private int previousPage; - private float selectedDotX; - private boolean selectedDotInPosition; - private float[] dotCenterX; - private float[] joiningFractions; - private float retreatingJoinX1; - private float retreatingJoinX2; - private float[] dotRevealFractions; - private boolean isAttachedToWindow; - private boolean pageChanging; - - // drawing - private final Paint unselectedPaint; - private final Paint selectedPaint; - private final Path combinedUnselectedPath; - private final Path unselectedDotPath; - private final Path unselectedDotLeftPath; - private final Path unselectedDotRightPath; - private final RectF rectF; - - // animation - private ValueAnimator moveAnimation; - private AnimatorSet joiningAnimationSet; - private PendingRetreatAnimator retreatAnimation; - private PendingRevealAnimator[] revealAnimations; - private final Interpolator interpolator; - - // working values for beziers - float endX1; - float endY1; - float endX2; - float endY2; - float controlX1; - float controlY1; - float controlX2; - float controlY2; - - public InkPageIndicator(Context context) { - this(context, null, 0); - } - - public InkPageIndicator(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public InkPageIndicator(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - - final int density = (int) context.getResources().getDisplayMetrics().density; - - // Load attributes - final TypedArray a = getContext().obtainStyledAttributes( - attrs, R.styleable.InkPageIndicator, defStyle, 0); - - dotDiameter = a.getDimensionPixelSize(R.styleable.InkPageIndicator_dotDiameter, - DEFAULT_DOT_SIZE * density); - dotRadius = dotDiameter / 2; - halfDotRadius = dotRadius / 2; - gap = a.getDimensionPixelSize(R.styleable.InkPageIndicator_dotGap, - DEFAULT_GAP * density); - animDuration = (long) a.getInteger(R.styleable.InkPageIndicator_animationDuration, - DEFAULT_ANIM_DURATION); - animHalfDuration = animDuration / 2; - unselectedColour = a.getColor(R.styleable.InkPageIndicator_pageIndicatorColor, - DEFAULT_UNSELECTED_COLOUR); - selectedColour = a.getColor(R.styleable.InkPageIndicator_currentPageIndicatorColor, - DEFAULT_SELECTED_COLOUR); - - a.recycle(); - - unselectedPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - unselectedPaint.setColor(unselectedColour); - selectedPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - selectedPaint.setColor(selectedColour); - interpolator = AnimHolder.INSTANCE.getFastOutSlowInInterpolator().invoke(context); - - // create paths & rect now – reuse & rewind later - combinedUnselectedPath = new Path(); - unselectedDotPath = new Path(); - unselectedDotLeftPath = new Path(); - unselectedDotRightPath = new Path(); - rectF = new RectF(); - - addOnAttachStateChangeListener(this); - } - - public void setViewPager(ViewPager viewPager) { - this.viewPager = viewPager; - viewPager.addOnPageChangeListener(this); - setPageCount(viewPager.getAdapter().getCount()); - viewPager.getAdapter().registerDataSetObserver(new DataSetObserver() { - @Override - public void onChanged() { - setPageCount(InkPageIndicator.this.viewPager.getAdapter().getCount()); - } - }); - setCurrentPageImmediate(); - } - - @Override - public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { - if (isAttachedToWindow) { - float fraction = positionOffset; - int currentPosition = pageChanging ? previousPage : currentPage; - int leftDotPosition = position; - // when swiping from #2 to #1 ViewPager reports position as 1 and a descending offset - // need to convert this into our left-dot-based 'coordinate space' - if (currentPosition != position) { - fraction = 1f - positionOffset; - - // if user scrolls completely to next page then the position param updates to that - // new page but we're not ready to switch our 'current' page yet so adjust for that - if (fraction == 1f) { - leftDotPosition = Math.min(currentPosition, position); - } - } - setJoiningFraction(leftDotPosition, fraction); - } - } - - @Override - public void onPageSelected(int position) { - if (isAttachedToWindow) { - // this is the main event we're interested in! - setSelectedPage(position); - } else { - // when not attached, don't animate the move, just store immediately - setCurrentPageImmediate(); - } - } - - @Override - public void onPageScrollStateChanged(int state) { - // nothing to do - } - - private void setPageCount(int pages) { - pageCount = pages; - resetState(); - requestLayout(); - } - - private void calculateDotPositions(int width, int height) { - int left = getPaddingLeft(); - int top = getPaddingTop(); - int right = width - getPaddingRight(); - int bottom = height - getPaddingBottom(); - - int requiredWidth = getRequiredWidth(); - float startLeft = left + ((right - left - requiredWidth) / 2) + dotRadius; - - dotCenterX = new float[pageCount]; - for (int i = 0; i < pageCount; i++) { - dotCenterX[i] = startLeft + i * (dotDiameter + gap); - } - // todo just top aligning for now… should make this smarter - dotTopY = top; - dotCenterY = top + dotRadius; - dotBottomY = top + dotDiameter; - - setCurrentPageImmediate(); - } - - private void setCurrentPageImmediate() { - if (viewPager != null) { - currentPage = viewPager.getCurrentItem(); - } else { - currentPage = 0; - } - if (dotCenterX != null) { - selectedDotX = dotCenterX[currentPage]; - } - } - - private void resetState() { - joiningFractions = new float[pageCount - 1]; - Arrays.fill(joiningFractions, 0f); - dotRevealFractions = new float[pageCount]; - Arrays.fill(dotRevealFractions, 0f); - retreatingJoinX1 = INVALID_FRACTION; - retreatingJoinX2 = INVALID_FRACTION; - selectedDotInPosition = true; - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - - int desiredHeight = getDesiredHeight(); - int height; - switch (MeasureSpec.getMode(heightMeasureSpec)) { - case MeasureSpec.EXACTLY: - height = MeasureSpec.getSize(heightMeasureSpec); - break; - case MeasureSpec.AT_MOST: - height = Math.min(desiredHeight, MeasureSpec.getSize(heightMeasureSpec)); - break; - case MeasureSpec.UNSPECIFIED: - default: - height = desiredHeight; - break; - } - - int desiredWidth = getDesiredWidth(); - int width; - switch (MeasureSpec.getMode(widthMeasureSpec)) { - case MeasureSpec.EXACTLY: - width = MeasureSpec.getSize(widthMeasureSpec); - break; - case MeasureSpec.AT_MOST: - width = Math.min(desiredWidth, MeasureSpec.getSize(widthMeasureSpec)); - break; - case MeasureSpec.UNSPECIFIED: - default: - width = desiredWidth; - break; - } - setMeasuredDimension(width, height); - calculateDotPositions(width, height); - } - - private int getDesiredHeight() { - return getPaddingTop() + dotDiameter + getPaddingBottom(); - } - - private int getRequiredWidth() { - return pageCount * dotDiameter + (pageCount - 1) * gap; - } - - private int getDesiredWidth() { - return getPaddingLeft() + getRequiredWidth() + getPaddingRight(); - } - - @Override - public void onViewAttachedToWindow(View view) { - isAttachedToWindow = true; - } - - @Override - public void onViewDetachedFromWindow(View view) { - isAttachedToWindow = false; - } - - @Override - protected void onDraw(Canvas canvas) { - if (viewPager == null || pageCount == 0) return; - drawUnselected(canvas); - drawSelected(canvas); - } - - private void drawUnselected(Canvas canvas) { - - combinedUnselectedPath.rewind(); - - // draw any settled, revealing or joining dots - for (int page = 0; page < pageCount; page++) { - int nextXIndex = page == pageCount - 1 ? page : page + 1; - combinedUnselectedPath.op(getUnselectedPath(page, - dotCenterX[page], - dotCenterX[nextXIndex], - page == pageCount - 1 ? INVALID_FRACTION : joiningFractions[page], - dotRevealFractions[page]), Path.Op.UNION); - } - // draw any retreating joins - if (retreatingJoinX1 != INVALID_FRACTION) { - combinedUnselectedPath.op(getRetreatingJoinPath(), Path.Op.UNION); - } - canvas.drawPath(combinedUnselectedPath, unselectedPaint); - } - - /** - * Unselected dots can be in 6 states: - * <p> - * #1 At rest - * #2 Joining neighbour, still separate - * #3 Joining neighbour, combined curved - * #4 Joining neighbour, combined straight - * #5 Join retreating - * #6 Dot re-showing / revealing - * <p> - * It can also be in a combination of these states e.g. joining one neighbour while - * retreating from another. We therefore create a Path so that we can examine each - * dot pair separately and later take the union for these cases. - * <p> - * This function returns a path for the given dot **and any action to it's right** e.g. joining - * or retreating from it's neighbour - * - * @param page - * @return - */ - private Path getUnselectedPath(int page, - float centerX, - float nextCenterX, - float joiningFraction, - float dotRevealFraction) { - - unselectedDotPath.rewind(); - - if ((joiningFraction == 0f || joiningFraction == INVALID_FRACTION) - && dotRevealFraction == 0f - && !(page == currentPage && selectedDotInPosition == true)) { - - // case #1 – At rest - unselectedDotPath.addCircle(dotCenterX[page], dotCenterY, dotRadius, Path.Direction.CW); - } - - if (joiningFraction > 0f && joiningFraction <= 0.5f - && retreatingJoinX1 == INVALID_FRACTION) { - - // case #2 – Joining neighbour, still separate - - // start with the left dot - unselectedDotLeftPath.rewind(); - - // start at the bottom center - unselectedDotLeftPath.moveTo(centerX, dotBottomY); - - // semi circle to the top center - rectF.set(centerX - dotRadius, dotTopY, centerX + dotRadius, dotBottomY); - unselectedDotLeftPath.arcTo(rectF, 90, 180, true); - - // cubic to the right middle - endX1 = centerX + dotRadius + (joiningFraction * gap); - endY1 = dotCenterY; - controlX1 = centerX + halfDotRadius; - controlY1 = dotTopY; - controlX2 = endX1; - controlY2 = endY1 - halfDotRadius; - unselectedDotLeftPath.cubicTo(controlX1, controlY1, - controlX2, controlY2, - endX1, endY1); - - // cubic back to the bottom center - endX2 = centerX; - endY2 = dotBottomY; - controlX1 = endX1; - controlY1 = endY1 + halfDotRadius; - controlX2 = centerX + halfDotRadius; - controlY2 = dotBottomY; - unselectedDotLeftPath.cubicTo(controlX1, controlY1, - controlX2, controlY2, - endX2, endY2); - - unselectedDotPath.op(unselectedDotLeftPath, Path.Op.UNION); - - // now do the next dot to the right - unselectedDotRightPath.rewind(); - - // start at the bottom center - unselectedDotRightPath.moveTo(nextCenterX, dotBottomY); - - // semi circle to the top center - rectF.set(nextCenterX - dotRadius, dotTopY, nextCenterX + dotRadius, dotBottomY); - unselectedDotRightPath.arcTo(rectF, 90, -180, true); - - // cubic to the left middle - endX1 = nextCenterX - dotRadius - (joiningFraction * gap); - endY1 = dotCenterY; - controlX1 = nextCenterX - halfDotRadius; - controlY1 = dotTopY; - controlX2 = endX1; - controlY2 = endY1 - halfDotRadius; - unselectedDotRightPath.cubicTo(controlX1, controlY1, - controlX2, controlY2, - endX1, endY1); - - // cubic back to the bottom center - endX2 = nextCenterX; - endY2 = dotBottomY; - controlX1 = endX1; - controlY1 = endY1 + halfDotRadius; - controlX2 = endX2 - halfDotRadius; - controlY2 = dotBottomY; - unselectedDotRightPath.cubicTo(controlX1, controlY1, - controlX2, controlY2, - endX2, endY2); - unselectedDotPath.op(unselectedDotRightPath, Path.Op.UNION); - } - - if (joiningFraction > 0.5f && joiningFraction < 1f - && retreatingJoinX1 == INVALID_FRACTION) { - - // case #3 – Joining neighbour, combined curved - - // adjust the fraction so that it goes from 0.3 -> 1 to produce a more realistic 'join' - float adjustedFraction = (joiningFraction - 0.2f) * 1.25f; - - // start in the bottom left - unselectedDotPath.moveTo(centerX, dotBottomY); - - // semi-circle to the top left - rectF.set(centerX - dotRadius, dotTopY, centerX + dotRadius, dotBottomY); - unselectedDotPath.arcTo(rectF, 90, 180, true); - - // bezier to the middle top of the join - endX1 = centerX + dotRadius + (gap / 2); - endY1 = dotCenterY - (adjustedFraction * dotRadius); - controlX1 = endX1 - (adjustedFraction * dotRadius); - controlY1 = dotTopY; - controlX2 = endX1 - ((1 - adjustedFraction) * dotRadius); - controlY2 = endY1; - unselectedDotPath.cubicTo(controlX1, controlY1, - controlX2, controlY2, - endX1, endY1); - - // bezier to the top right of the join - endX2 = nextCenterX; - endY2 = dotTopY; - controlX1 = endX1 + ((1 - adjustedFraction) * dotRadius); - controlY1 = endY1; - controlX2 = endX1 + (adjustedFraction * dotRadius); - controlY2 = dotTopY; - unselectedDotPath.cubicTo(controlX1, controlY1, - controlX2, controlY2, - endX2, endY2); - - // semi-circle to the bottom right - rectF.set(nextCenterX - dotRadius, dotTopY, nextCenterX + dotRadius, dotBottomY); - unselectedDotPath.arcTo(rectF, 270, 180, true); - - // bezier to the middle bottom of the join - // endX1 stays the same - endY1 = dotCenterY + (adjustedFraction * dotRadius); - controlX1 = endX1 + (adjustedFraction * dotRadius); - controlY1 = dotBottomY; - controlX2 = endX1 + ((1 - adjustedFraction) * dotRadius); - controlY2 = endY1; - unselectedDotPath.cubicTo(controlX1, controlY1, - controlX2, controlY2, - endX1, endY1); - - // bezier back to the start point in the bottom left - endX2 = centerX; - endY2 = dotBottomY; - controlX1 = endX1 - ((1 - adjustedFraction) * dotRadius); - controlY1 = endY1; - controlX2 = endX1 - (adjustedFraction * dotRadius); - controlY2 = endY2; - unselectedDotPath.cubicTo(controlX1, controlY1, - controlX2, controlY2, - endX2, endY2); - } - if (joiningFraction == 1 && retreatingJoinX1 == INVALID_FRACTION) { - - // case #4 Joining neighbour, combined straight technically we could use case 3 for this - // situation as well but assume that this is an optimization rather than faffing around - // with beziers just to draw a rounded rect - rectF.set(centerX - dotRadius, dotTopY, nextCenterX + dotRadius, dotBottomY); - unselectedDotPath.addRoundRect(rectF, dotRadius, dotRadius, Path.Direction.CW); - } - - // case #5 is handled by #getRetreatingJoinPath() - // this is done separately so that we can have a single retreating path spanning - // multiple dots and therefore animate it's movement smoothly - - if (dotRevealFraction > MINIMAL_REVEAL) { - - // case #6 – previously hidden dot revealing - unselectedDotPath.addCircle(centerX, dotCenterY, dotRevealFraction * dotRadius, - Path.Direction.CW); - } - - return unselectedDotPath; - } - - private Path getRetreatingJoinPath() { - unselectedDotPath.rewind(); - rectF.set(retreatingJoinX1, dotTopY, retreatingJoinX2, dotBottomY); - unselectedDotPath.addRoundRect(rectF, dotRadius, dotRadius, Path.Direction.CW); - return unselectedDotPath; - } - - private void drawSelected(Canvas canvas) { - canvas.drawCircle(selectedDotX, dotCenterY, dotRadius, selectedPaint); - } - - private void setSelectedPage(int now) { - if (now == currentPage) return; - - pageChanging = true; - previousPage = currentPage; - currentPage = now; - final int steps = Math.abs(now - previousPage); - - if (steps > 1) { - if (now > previousPage) { - for (int i = 0; i < steps; i++) { - setJoiningFraction(previousPage + i, 1f); - } - } else { - for (int i = -1; i > -steps; i--) { - setJoiningFraction(previousPage + i, 1f); - } - } - } - - // create the anim to move the selected dot – this animator will kick off - // retreat animations when it has moved 75% of the way. - // The retreat animation in turn will kick of reveal anims when the - // retreat has passed any dots to be revealed - moveAnimation = createMoveSelectedAnimator(dotCenterX[now], previousPage, now, steps); - moveAnimation.start(); - } - - private ValueAnimator createMoveSelectedAnimator( - final float moveTo, int was, int now, int steps) { - - // create the actual move animator - ValueAnimator moveSelected = ValueAnimator.ofFloat(selectedDotX, moveTo); - - // also set up a pending retreat anim – this starts when the move is 75% complete - retreatAnimation = new PendingRetreatAnimator(was, now, steps, - now > was ? - new RightwardStartPredicate(moveTo - ((moveTo - selectedDotX) * 0.25f)) : - new LeftwardStartPredicate(moveTo + ((selectedDotX - moveTo) * 0.25f))); - retreatAnimation.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - resetState(); - pageChanging = false; - } - }); - moveSelected.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator valueAnimator) { - // todo avoid autoboxing - selectedDotX = (Float) valueAnimator.getAnimatedValue(); - retreatAnimation.startIfNecessary(selectedDotX); - postInvalidateOnAnimation(); - } - }); - moveSelected.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animation) { - // set a flag so that we continue to draw the unselected dot in the target position - // until the selected dot has finished moving into place - selectedDotInPosition = false; - } - - @Override - public void onAnimationEnd(Animator animation) { - // set a flag when anim finishes so that we don't draw both selected & unselected - // page dots - selectedDotInPosition = true; - } - }); - // 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.setInterpolator(interpolator); - return moveSelected; - } - - private void setJoiningFraction(int leftDot, float fraction) { - if (leftDot < joiningFractions.length) { - - if (leftDot == 1) { - Log.d("PageIndicator", "dot 1 fraction:\t" + fraction); - } - - joiningFractions[leftDot] = fraction; - postInvalidateOnAnimation(); - } - } - - private void clearJoiningFractions() { - Arrays.fill(joiningFractions, 0f); - postInvalidateOnAnimation(); - } - - private void setDotRevealFraction(int dot, float fraction) { - dotRevealFractions[dot] = fraction; - postInvalidateOnAnimation(); - } - - private void cancelJoiningAnimations() { - if (joiningAnimationSet != null && joiningAnimationSet.isRunning()) { - joiningAnimationSet.cancel(); - } - } - - /** - * A {@link ValueAnimator} that starts once a given predicate returns true. - */ - public abstract class PendingStartAnimator extends ValueAnimator { - - protected boolean hasStarted; - protected StartPredicate predicate; - - public PendingStartAnimator(StartPredicate predicate) { - super(); - this.predicate = predicate; - hasStarted = false; - } - - public void startIfNecessary(float currentValue) { - if (!hasStarted && predicate.shouldStart(currentValue)) { - start(); - hasStarted = true; - } - } - } - - /** - * An Animator that shows and then shrinks a retreating join between the previous and newly - * selected pages. This also sets up some pending dot reveals – to be started when the retreat - * has passed the dot to be revealed. - */ - public class PendingRetreatAnimator extends PendingStartAnimator { - - public PendingRetreatAnimator(int was, int now, int steps, StartPredicate predicate) { - super(predicate); - setDuration(animHalfDuration); - setInterpolator(interpolator); - - // work out the start/end values of the retreating join from the direction we're - // travelling in. Also look at the current selected dot position, i.e. we're moving on - // before a prior anim has finished. - final float initialX1 = now > was ? Math.min(dotCenterX[was], selectedDotX) - dotRadius - : dotCenterX[now] - dotRadius; - final float finalX1 = now > was ? dotCenterX[now] - dotRadius - : dotCenterX[now] - dotRadius; - final float initialX2 = now > was ? dotCenterX[now] + dotRadius - : Math.max(dotCenterX[was], selectedDotX) + dotRadius; - final float finalX2 = now > was ? dotCenterX[now] + dotRadius - : dotCenterX[now] + dotRadius; - - revealAnimations = new PendingRevealAnimator[steps]; - // hold on to the indexes of the dots that will be hidden by the retreat so that - // we can initialize their revealFraction's i.e. make sure they're hidden while the - // reveal animation runs - final int[] dotsToHide = new int[steps]; - if (initialX1 != finalX1) { // rightward retreat - setFloatValues(initialX1, finalX1); - // create the reveal animations that will run when the retreat passes them - for (int i = 0; i < steps; i++) { - revealAnimations[i] = new PendingRevealAnimator(was + i, - new RightwardStartPredicate(dotCenterX[was + i])); - dotsToHide[i] = was + i; - } - addUpdateListener(new AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator valueAnimator) { - // todo avoid autoboxing - retreatingJoinX1 = (Float) valueAnimator.getAnimatedValue(); - postInvalidateOnAnimation(); - // start any reveal animations if we've passed them - for (PendingRevealAnimator pendingReveal : revealAnimations) { - pendingReveal.startIfNecessary(retreatingJoinX1); - } - } - }); - } else { // (initialX2 != finalX2) leftward retreat - setFloatValues(initialX2, finalX2); - // create the reveal animations that will run when the retreat passes them - for (int i = 0; i < steps; i++) { - revealAnimations[i] = new PendingRevealAnimator(was - i, - new LeftwardStartPredicate(dotCenterX[was - i])); - dotsToHide[i] = was - i; - } - addUpdateListener(new AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator valueAnimator) { - // todo avoid autoboxing - retreatingJoinX2 = (Float) valueAnimator.getAnimatedValue(); - postInvalidateOnAnimation(); - // start any reveal animations if we've passed them - for (PendingRevealAnimator pendingReveal : revealAnimations) { - pendingReveal.startIfNecessary(retreatingJoinX2); - } - } - }); - } - - addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animation) { - cancelJoiningAnimations(); - clearJoiningFractions(); - // we need to set this so that the dots are hidden until the reveal anim runs - for (int dot : dotsToHide) { - setDotRevealFraction(dot, MINIMAL_REVEAL); - } - retreatingJoinX1 = initialX1; - retreatingJoinX2 = initialX2; - postInvalidateOnAnimation(); - } - - @Override - public void onAnimationEnd(Animator animation) { - retreatingJoinX1 = INVALID_FRACTION; - retreatingJoinX2 = INVALID_FRACTION; - postInvalidateOnAnimation(); - } - }); - } - } - - /** - * An Animator that animates a given dot's revealFraction i.e. scales it up - */ - public class PendingRevealAnimator extends PendingStartAnimator { - - private int dot; - - public PendingRevealAnimator(int dot, StartPredicate predicate) { - super(predicate); - setFloatValues(MINIMAL_REVEAL, 1f); - this.dot = dot; - setDuration(animHalfDuration); - setInterpolator(interpolator); - addUpdateListener(new AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator valueAnimator) { - // todo avoid autoboxing - setDotRevealFraction(PendingRevealAnimator.this.dot, - (Float) valueAnimator.getAnimatedValue()); - } - }); - addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - setDotRevealFraction(PendingRevealAnimator.this.dot, 0f); - postInvalidateOnAnimation(); - } - }); - } - } - - /** - * A predicate used to start an animation when a test passes - */ - public abstract class StartPredicate { - - protected float thresholdValue; - - public StartPredicate(float thresholdValue) { - this.thresholdValue = thresholdValue; - } - - abstract boolean shouldStart(float currentValue); - - } - - /** - * A predicate used to start an animation when a given value is greater than a threshold - */ - public class RightwardStartPredicate extends StartPredicate { - - public RightwardStartPredicate(float thresholdValue) { - super(thresholdValue); - } - - boolean shouldStart(float currentValue) { - return currentValue > thresholdValue; - } - } - - /** - * A predicate used to start an animation then a given value is less than a threshold - */ - public class LeftwardStartPredicate extends StartPredicate { - - public LeftwardStartPredicate(float thresholdValue) { - super(thresholdValue); - } - - boolean shouldStart(float currentValue) { - return currentValue < thresholdValue; - } - } -} diff --git a/core/src/main/kotlin/ca/allanwang/kau/widgets/TextSlider.kt b/core/src/main/kotlin/ca/allanwang/kau/widgets/TextSlider.kt deleted file mode 100644 index 528dabc..0000000 --- a/core/src/main/kotlin/ca/allanwang/kau/widgets/TextSlider.kt +++ /dev/null @@ -1,125 +0,0 @@ -package ca.allanwang.kau.widgets - -import android.content.Context -import android.graphics.Color -import android.support.v4.widget.TextViewCompat -import android.text.TextUtils -import android.util.AttributeSet -import android.view.Gravity -import android.view.animation.Animation -import android.view.animation.AnimationUtils -import android.widget.TextSwitcher -import android.widget.TextView -import ca.allanwang.kau.R -import java.util.* - -/** - * Created by Allan Wang on 2017-06-21. - * - * Text switcher with global text color and embedded sliding animations - * Also has a stack to keep track of title changes - */ -class TextSlider @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null -) : TextSwitcher(context, attrs) { - - val titleStack: Stack<CharSequence?> = Stack() - - /** - * Holds a mapping of animation types to their respective animations - */ - val animationMap = mapOf( - ANIMATION_NONE to null, - ANIMATION_SLIDE_HORIZONTAL to AnimationBundle( - R.anim.kau_slide_in_right, R.anim.kau_slide_out_left, - R.anim.kau_slide_in_left, R.anim.kau_slide_out_right), - ANIMATION_SLIDE_VERTICAL to AnimationBundle( - R.anim.kau_slide_in_bottom, R.anim.kau_slide_out_top, - R.anim.kau_slide_in_top, R.anim.kau_slide_out_bottom - ) - ) - - /** - * Holds lazy instances of the animations - */ - inner class AnimationBundle(private val nextIn: Int, private val nextOut: Int, private val prevIn: Int, private val prevOut: Int) { - val NEXT_IN: Animation by lazy { AnimationUtils.loadAnimation(context, nextIn) } - val NEXT_OUT: Animation by lazy { AnimationUtils.loadAnimation(context, nextOut) } - val PREV_IN: Animation by lazy { AnimationUtils.loadAnimation(context, prevIn) } - val PREV_OUT: Animation by lazy { AnimationUtils.loadAnimation(context, prevOut) } - } - - companion object { - const val ANIMATION_NONE = 1000 - const val ANIMATION_SLIDE_HORIZONTAL = 1001 - const val ANIMATION_SLIDE_VERTICAL = 1002 - } - - var animationType: Int = ANIMATION_SLIDE_HORIZONTAL - - var textColor: Int = Color.WHITE - get() = field - set(value) { - field = value - (getChildAt(0) as TextView).setTextColor(value) - (getChildAt(1) as TextView).setTextColor(value) - } - val isRoot: Boolean - get() = titleStack.size <= 1 - - init { - if (attrs != null) { - val styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.TextSlider) - animationType = styledAttrs.getInteger(R.styleable.TextSlider_animation_type, ANIMATION_SLIDE_HORIZONTAL) - styledAttrs.recycle() - } - } - - override fun setText(text: CharSequence?) { - if ((currentView as TextView).text == text) return - super.setText(text) - } - - override fun setCurrentText(text: CharSequence?) { - if (titleStack.isNotEmpty()) titleStack.pop() - titleStack.push(text) - super.setCurrentText(text) - } - - fun setNextText(text: CharSequence?) { - if (titleStack.isEmpty()) { - setCurrentText(text) - return - } - titleStack.push(text) - val anim = animationMap[animationType] - inAnimation = anim?.NEXT_IN - outAnimation = anim?.NEXT_OUT - setText(text) - } - - /** - * Sets the text as the previous title - * No further checks are done, so be sure to verify with [isRoot] - */ - @Throws(EmptyStackException::class) - fun setPrevText() { - titleStack.pop() - val anim = animationMap[animationType] - inAnimation = anim?.PREV_IN - outAnimation = anim?.PREV_OUT - val text = titleStack.peek() - setText(text) - } - - init { - setFactory { - TextView(context).apply { - //replica of toolbar title - gravity = Gravity.START - setSingleLine() - ellipsize = TextUtils.TruncateAt.END - TextViewCompat.setTextAppearance(this, R.style.TextAppearance_AppCompat_Title) - } - } - } -}
\ No newline at end of file |