diff options
Diffstat (limited to 'library/src/main/kotlin/ca/allanwang')
67 files changed, 0 insertions, 7553 deletions
diff --git a/library/src/main/kotlin/ca/allanwang/kau/about/AboutActivityBase.kt b/library/src/main/kotlin/ca/allanwang/kau/about/AboutActivityBase.kt deleted file mode 100644 index 32e8745..0000000 --- a/library/src/main/kotlin/ca/allanwang/kau/about/AboutActivityBase.kt +++ /dev/null @@ -1,236 +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 - * 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<*, *>> - - 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) - } - 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) - 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(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/library/src/main/kotlin/ca/allanwang/kau/adapters/ChainedAdapters.kt b/library/src/main/kotlin/ca/allanwang/kau/adapters/ChainedAdapters.kt deleted file mode 100644 index e1c5c18..0000000 --- a/library/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/library/src/main/kotlin/ca/allanwang/kau/adapters/FastItemThemedAdapter.kt b/library/src/main/kotlin/ca/allanwang/kau/adapters/FastItemThemedAdapter.kt deleted file mode 100644 index 66fec4b..0000000 --- a/library/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/library/src/main/kotlin/ca/allanwang/kau/adapters/SectionAdapter.kt b/library/src/main/kotlin/ca/allanwang/kau/adapters/SectionAdapter.kt deleted file mode 100644 index cf7205a..0000000 --- a/library/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/library/src/main/kotlin/ca/allanwang/kau/animators/BaseDelayAnimator.kt b/library/src/main/kotlin/ca/allanwang/kau/animators/BaseDelayAnimator.kt deleted file mode 100644 index c649376..0000000 --- a/library/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/library/src/main/kotlin/ca/allanwang/kau/animators/BaseItemAnimator.java b/library/src/main/kotlin/ca/allanwang/kau/animators/BaseItemAnimator.java deleted file mode 100644 index 69c2cf3..0000000 --- a/library/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/library/src/main/kotlin/ca/allanwang/kau/animators/BaseSlideAlphaAnimator.kt b/library/src/main/kotlin/ca/allanwang/kau/animators/BaseSlideAlphaAnimator.kt deleted file mode 100644 index a963358..0000000 --- a/library/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/library/src/main/kotlin/ca/allanwang/kau/animators/DefaultAnimator.kt b/library/src/main/kotlin/ca/allanwang/kau/animators/DefaultAnimator.kt deleted file mode 100644 index 9aeafde..0000000 --- a/library/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/library/src/main/kotlin/ca/allanwang/kau/animators/FadeScaleAnimator.kt b/library/src/main/kotlin/ca/allanwang/kau/animators/FadeScaleAnimator.kt deleted file mode 100644 index e968cda..0000000 --- a/library/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/library/src/main/kotlin/ca/allanwang/kau/animators/NoAnimator.kt b/library/src/main/kotlin/ca/allanwang/kau/animators/NoAnimator.kt deleted file mode 100644 index 244287b..0000000 --- a/library/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/library/src/main/kotlin/ca/allanwang/kau/animators/SlideUpExitRightAnimator.kt b/library/src/main/kotlin/ca/allanwang/kau/animators/SlideUpExitRightAnimator.kt deleted file mode 100644 index 8670493..0000000 --- a/library/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/library/src/main/kotlin/ca/allanwang/kau/changelog/Changelog.kt b/library/src/main/kotlin/ca/allanwang/kau/changelog/Changelog.kt deleted file mode 100644 index 302d9dc..0000000 --- a/library/src/main/kotlin/ca/allanwang/kau/changelog/Changelog.kt +++ /dev/null @@ -1,105 +0,0 @@ -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 -import android.support.v7.widget.RecyclerView -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.TextView -import ca.allanwang.kau.R -import ca.allanwang.kau.utils.bindOptionalView -import ca.allanwang.kau.utils.bindView -import ca.allanwang.kau.utils.materialDialog -import ca.allanwang.kau.utils.use -import com.afollestad.materialdialogs.MaterialDialog -import org.jetbrains.anko.doAsync -import org.jetbrains.anko.uiThread -import org.xmlpull.v1.XmlPullParser -import java.util.* - - -/** - * Created by Allan Wang on 2017-05-28. - */ - -fun Context.showChangelog(@XmlRes xmlRes: Int, @ColorInt textColor: Int? = null, customize: MaterialDialog.Builder.() -> Unit = {}) { - doAsync { - val items = parse(this@showChangelog, xmlRes) - uiThread { - materialDialog { - title(R.string.kau_changelog) - positiveText(R.string.kau_great) - adapter(ChangelogAdapter(items, textColor), null) - customize() - } - } - } -} - -/** - * Internals of the changelog dialog - * Contains an mainAdapter for each item, as well as the tags to parse - */ -internal class ChangelogAdapter(val items: List<Pair<String, ChangelogType>>, @ColorInt val textColor: Int? = null) : RecyclerView.Adapter<ChangelogAdapter.ChangelogVH>() { - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ChangelogVH(LayoutInflater.from(parent.context) - .inflate(items[viewType].second.layout, parent, false)) - - override fun onBindViewHolder(holder: ChangelogVH, position: Int) { - holder.text.text = items[position].first - if (textColor != null) { - holder.text.setTextColor(textColor) - holder.bullet?.setTextColor(textColor) - } - } - - override fun getItemId(position: Int) = position.toLong() - - override fun getItemViewType(position: Int) = position - - override fun getItemCount() = items.size - - internal class ChangelogVH(itemView: View) : RecyclerView.ViewHolder(itemView) { - val text: TextView by bindView(R.id.kau_changelog_text) - val bullet: TextView? by bindOptionalView(R.id.kau_changelog_bullet) - } -} - -internal fun parse(context: Context, @XmlRes xmlRes: Int): List<Pair<String, ChangelogType>> { - val items = mutableListOf<Pair<String, ChangelogType>>() - context.resources.getXml(xmlRes).use { - parser: XmlResourceParser -> - var eventType = parser.eventType - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) - ChangelogType.values.any { it.add(parser, items) } - eventType = parser.next() - } - } - return items -} - -internal enum class ChangelogType(val tag: String, val attr: String, @LayoutRes val layout: Int) { - TITLE("version", "title", R.layout.kau_changelog_title), - ITEM("item", "text", R.layout.kau_changelog_content); - - companion object { - @JvmStatic val values = values() - } - - /** - * Returns true if tag matches; false otherwise - */ - fun add(parser: XmlResourceParser, list: MutableList<Pair<String, ChangelogType>>): Boolean { - if (parser.name != tag) return false - if (parser.getAttributeValue(null, attr).isNotBlank()) - list.add(Pair(parser.getAttributeValue(null, attr), this)) - return true - } -} - diff --git a/library/src/main/kotlin/ca/allanwang/kau/dialogs/color/CircleView.kt b/library/src/main/kotlin/ca/allanwang/kau/dialogs/color/CircleView.kt deleted file mode 100644 index 3430b42..0000000 --- a/library/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/library/src/main/kotlin/ca/allanwang/kau/dialogs/color/ColorPalette.kt b/library/src/main/kotlin/ca/allanwang/kau/dialogs/color/ColorPalette.kt deleted file mode 100644 index 22bd0d4..0000000 --- a/library/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/library/src/main/kotlin/ca/allanwang/kau/dialogs/color/ColorPickerDialog.kt b/library/src/main/kotlin/ca/allanwang/kau/dialogs/color/ColorPickerDialog.kt deleted file mode 100644 index 7c57c26..0000000 --- a/library/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/library/src/main/kotlin/ca/allanwang/kau/dialogs/color/ColorPickerView.kt b/library/src/main/kotlin/ca/allanwang/kau/dialogs/color/ColorPickerView.kt deleted file mode 100644 index da864c9..0000000 --- a/library/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/library/src/main/kotlin/ca/allanwang/kau/email/EmailBuilder.kt b/library/src/main/kotlin/ca/allanwang/kau/email/EmailBuilder.kt deleted file mode 100644 index b03a620..0000000 --- a/library/src/main/kotlin/ca/allanwang/kau/email/EmailBuilder.kt +++ /dev/null @@ -1,92 +0,0 @@ -package ca.allanwang.kau.email - -import android.app.Activity -import android.content.Context -import android.content.Intent -import android.content.pm.PackageManager -import android.net.Uri -import android.os.Build -import android.support.annotation.StringRes -import android.util.DisplayMetrics -import ca.allanwang.kau.R -import ca.allanwang.kau.logging.KL -import ca.allanwang.kau.utils.installerPackageName -import ca.allanwang.kau.utils.isAppInstalled -import ca.allanwang.kau.utils.string - - -/** - * Created by Allan Wang on 2017-06-20. - */ -class EmailBuilder(val email: String, val subject: String) { - var message: String = "Write here." - var deviceDetails: Boolean = true - var appInfo: Boolean = true - var footer: String? = null - private val pairs: MutableMap<String, String> = mutableMapOf() - private val packages: MutableList<Package> = mutableListOf() - - fun checkPackage(packageName: String, appName: String) = packages.add(Package(packageName, appName)) - - fun addItem(key: String, value: String) = pairs.put(key, value) - - data class Package(val packageName: String, val appName: String) - - fun execute(context: Context) { - val intent = Intent(Intent.ACTION_VIEW, Uri.parse("mailto:$email")) - intent.putExtra(Intent.EXTRA_SUBJECT, subject) - val emailBuilder = StringBuilder() - emailBuilder.append(message).append("\n\n") - if (deviceDetails) { - val deviceItems = mutableMapOf( - "OS Version" to "${System.getProperty("os.version")} (${Build.VERSION.INCREMENTAL})", - "OS API Level" to Build.DEVICE, - "Manufacturer" to Build.MANUFACTURER, - "Model (and Product)" to "${Build.MODEL} (${Build.PRODUCT})", - "Package Installer" to (context.installerPackageName ?: "None") - ) - if (context is Activity) { - val metric = DisplayMetrics() - context.windowManager.defaultDisplay.getMetrics(metric) - deviceItems.put("Screen Dimensions", "${metric.widthPixels} x ${metric.heightPixels}") - } - deviceItems.forEach { (k, v) -> emailBuilder.append("$k: $v\n") } - } - if (appInfo) { - try { - val appInfo = context.packageManager.getPackageInfo(context.packageName, 0) - emailBuilder.append("\nApp: ").append(context.packageName) - .append("\nApp Version Name: ").append(appInfo.versionName) - .append("\nApp Version Code: ").append(appInfo.versionCode).append("\n") - } catch (e: PackageManager.NameNotFoundException) { - KL.e("EmailBuilder packageInfo not found") - } - } - - if (packages.isNotEmpty()) emailBuilder.append("\n") - packages.forEach { - if (context.isAppInstalled(it.packageName)) - emailBuilder.append(String.format("\n%s is installed", it.appName)) - } - - if (pairs.isNotEmpty()) emailBuilder.append("\n") - pairs.forEach { (k, v) -> emailBuilder.append("$k: $v\n") } - - if (footer != null) - emailBuilder.append("\n").append(footer) - - intent.putExtra(Intent.EXTRA_TEXT, emailBuilder.toString()) - context.startActivity(Intent.createChooser(intent, context.resources.getString(R.string.kau_send_via))) - } -} - -fun Context.sendEmail(@StringRes emailId: Int, @StringRes subjectId: Int, builder: EmailBuilder.() -> Unit = {}) - = sendEmail(string(emailId), string(subjectId), builder) - - -fun Context.sendEmail(email: String, subject: String, builder: EmailBuilder.() -> Unit = {}) { - EmailBuilder(email, subject).apply { - builder() - execute(this@sendEmail) - } -}
\ No newline at end of file diff --git a/library/src/main/kotlin/ca/allanwang/kau/iitems/CardIItem.kt b/library/src/main/kotlin/ca/allanwang/kau/iitems/CardIItem.kt deleted file mode 100644 index 3380ade..0000000 --- a/library/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/library/src/main/kotlin/ca/allanwang/kau/iitems/CutoutIItem.kt b/library/src/main/kotlin/ca/allanwang/kau/iitems/CutoutIItem.kt deleted file mode 100644 index 627e1df..0000000 --- a/library/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/library/src/main/kotlin/ca/allanwang/kau/iitems/HeaderIItem.kt b/library/src/main/kotlin/ca/allanwang/kau/iitems/HeaderIItem.kt deleted file mode 100644 index e994781..0000000 --- a/library/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/library/src/main/kotlin/ca/allanwang/kau/iitems/KauIItem.kt b/library/src/main/kotlin/ca/allanwang/kau/iitems/KauIItem.kt deleted file mode 100644 index 00b165c..0000000 --- a/library/src/main/kotlin/ca/allanwang/kau/iitems/KauIItem.kt +++ /dev/null @@ -1,23 +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 - */ -open class KauIItem<Item, VH : RecyclerView.ViewHolder>( - private val type: Int, - @param:LayoutRes private val layoutRes: Int, - private val viewHolder: (v: View) -> VH -) : 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/library/src/main/kotlin/ca/allanwang/kau/iitems/LibraryIItem.kt b/library/src/main/kotlin/ca/allanwang/kau/iitems/LibraryIItem.kt deleted file mode 100644 index aabd9e3..0000000 --- a/library/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/library/src/main/kotlin/ca/allanwang/kau/kotlin/LazyContext.kt b/library/src/main/kotlin/ca/allanwang/kau/kotlin/LazyContext.kt deleted file mode 100644 index 8b59539..0000000 --- a/library/src/main/kotlin/ca/allanwang/kau/kotlin/LazyContext.kt +++ /dev/null @@ -1,50 +0,0 @@ -package ca.allanwang.kau.kotlin - -import android.content.Context -import android.support.annotation.AnimatorRes -import android.support.annotation.InterpolatorRes -import android.view.animation.Animation -import android.view.animation.AnimationUtils -import android.view.animation.Interpolator - -/** - * Created by Allan Wang on 2017-05-30. - * - * Lazy retrieval of context based items - * Items are retrieved using delegateName[context] - * - */ - -fun lazyInterpolator(@InterpolatorRes id: Int) = lazyContext<Interpolator> { AnimationUtils.loadInterpolator(it, id) } -fun lazyAnimation(@AnimatorRes id: Int) = lazyContext<Animation> { AnimationUtils.loadAnimation(it, id) } - -fun <T : Any> lazyContext(initializer: (context: Context) -> T): LazyContext<T> = LazyContext<T>(initializer) - -class LazyContext<out T : Any>(private val initializer: (context: Context) -> T, lock: Any? = null) { - @Volatile private var _value: Any = UNINITIALIZED - private val lock = lock ?: this - - fun invalidate() { - _value = UNINITIALIZED - } - - operator fun invoke(context: Context): T { - val _v1 = _value - if (_v1 !== UNINITIALIZED) - @Suppress("UNCHECKED_CAST") - return _v1 as T - - return synchronized(lock) { - val _v2 = _value - if (_v2 !== UNINITIALIZED) { - @Suppress("UNCHECKED_CAST") - _v2 as T - } else { - val typedValue = initializer(context) - _value = typedValue - typedValue - } - } - } - -}
\ No newline at end of file diff --git a/library/src/main/kotlin/ca/allanwang/kau/kotlin/LazyResettable.kt b/library/src/main/kotlin/ca/allanwang/kau/kotlin/LazyResettable.kt deleted file mode 100644 index f8947f3..0000000 --- a/library/src/main/kotlin/ca/allanwang/kau/kotlin/LazyResettable.kt +++ /dev/null @@ -1,51 +0,0 @@ -package ca.allanwang.kau.kotlin - -import java.io.Serializable -import kotlin.reflect.KProperty - -/** - * Created by Allan Wang on 2017-05-30. - * - * Lazy delegate that can be invalidated if needed - * https://stackoverflow.com/a/37294840/4407321 - */ -internal object UNINITIALIZED - -fun <T : Any> lazyResettable(initializer: () -> T): LazyResettable<T> = LazyResettable<T>(initializer) - -class LazyResettable<T : Any>(private val initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable { - @Volatile private var _value: Any = UNINITIALIZED - private val lock = lock ?: this - - fun invalidate() { - _value = UNINITIALIZED - } - - override val value: T - get() { - val _v1 = _value - if (_v1 !== UNINITIALIZED) - @Suppress("UNCHECKED_CAST") - return _v1 as T - - return synchronized(lock) { - val _v2 = _value - if (_v2 !== UNINITIALIZED) { - @Suppress("UNCHECKED_CAST") - _v2 as T - } else { - val typedValue = initializer() - _value = typedValue - typedValue - } - } - } - - override fun isInitialized(): Boolean = _value !== UNINITIALIZED - - override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet." - - operator fun setValue(any: Any, property: KProperty<*>, t: T) { - _value = t - } -}
\ No newline at end of file diff --git a/library/src/main/kotlin/ca/allanwang/kau/kotlin/NonReadablePropertyException.kt b/library/src/main/kotlin/ca/allanwang/kau/kotlin/NonReadablePropertyException.kt deleted file mode 100644 index f3add48..0000000 --- a/library/src/main/kotlin/ca/allanwang/kau/kotlin/NonReadablePropertyException.kt +++ /dev/null @@ -1,12 +0,0 @@ -package ca.allanwang.kau.kotlin - -/** - * Created by Allan Wang on 2017-06-24. - * - * Credits to @zsmb13 - * - * https://github.com/zsmb13/MaterialDrawerKt/blob/master/library/src/main/java/co/zsmb/materialdrawerkt/NonReadablePropertyException.kt - */ -class NonReadablePropertyException : Exception() - -fun nonReadable(): Nothing = throw NonReadablePropertyException()
\ No newline at end of file diff --git a/library/src/main/kotlin/ca/allanwang/kau/kpref/KPref.kt b/library/src/main/kotlin/ca/allanwang/kau/kpref/KPref.kt deleted file mode 100644 index 7fd8955..0000000 --- a/library/src/main/kotlin/ca/allanwang/kau/kpref/KPref.kt +++ /dev/null @@ -1,35 +0,0 @@ -package ca.allanwang.kau.kpref - -import android.content.Context -import android.content.SharedPreferences - -/** - * Created by Allan Wang on 2017-06-07. - */ -open class KPref { - - lateinit private var c: Context - lateinit internal var PREFERENCE_NAME: String - private var initialized = false - - fun initialize(c: Context, preferenceName: String) { - if (initialized) throw KPrefException("KPref object $preferenceName has already been initialized; please only do so once") - initialized = true - this.c = c.applicationContext - PREFERENCE_NAME = preferenceName - } - - internal val sp: SharedPreferences by lazy { - if (!initialized) throw KPrefException("KPref object has not yet been initialized; please initialize it with a context and preference name") - c.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE) - } - - internal val prefMap: MutableMap<String, KPrefDelegate<*>> = mutableMapOf() - - fun reset() { - prefMap.values.forEach { it.invalidate() } - } - - operator fun get(key: String): KPrefDelegate<*>? = prefMap[key] - -}
\ No newline at end of file diff --git a/library/src/main/kotlin/ca/allanwang/kau/kpref/KPrefActivity.kt b/library/src/main/kotlin/ca/allanwang/kau/kpref/KPrefActivity.kt deleted file mode 100644 index 9a9f7d4..0000000 --- a/library/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/library/src/main/kotlin/ca/allanwang/kau/kpref/KPrefBinder.kt b/library/src/main/kotlin/ca/allanwang/kau/kpref/KPrefBinder.kt deleted file mode 100644 index 7f42d2a..0000000 --- a/library/src/main/kotlin/ca/allanwang/kau/kpref/KPrefBinder.kt +++ /dev/null @@ -1,120 +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(internal 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 - internal val list: MutableList<KPrefItemCore> = mutableListOf() -}
\ No newline at end of file diff --git a/library/src/main/kotlin/ca/allanwang/kau/kpref/KPrefDelegate.kt b/library/src/main/kotlin/ca/allanwang/kau/kpref/KPrefDelegate.kt deleted file mode 100644 index 4d57ff1..0000000 --- a/library/src/main/kotlin/ca/allanwang/kau/kpref/KPrefDelegate.kt +++ /dev/null @@ -1,89 +0,0 @@ -package ca.allanwang.kau.kpref - -/** - * Created by Allan Wang on 2017-06-07. - */ -private object UNINITIALIZED - -fun KPref.kpref(key: String, fallback: Boolean, postSetter: (value: Boolean) -> Unit = {}): KPrefDelegate<Boolean> = KPrefDelegate(key, fallback, this, postSetter) -fun KPref.kpref(key: String, fallback: Double, postSetter: (value: Float) -> Unit = {}): KPrefDelegate<Float> = KPrefDelegate(key, fallback.toFloat(), this, postSetter) -fun KPref.kpref(key: String, fallback: Float, postSetter: (value: Float) -> Unit = {}): KPrefDelegate<Float> = KPrefDelegate(key, fallback, this, postSetter) -fun KPref.kpref(key: String, fallback: Int, postSetter: (value: Int) -> Unit = {}): KPrefDelegate<Int> = KPrefDelegate(key, fallback, this, postSetter) -fun KPref.kpref(key: String, fallback: Long, postSetter: (value: Long) -> Unit = {}): KPrefDelegate<Long> = KPrefDelegate(key, fallback, this, postSetter) -fun KPref.kpref(key: String, fallback: Set<String>, postSetter: (value: Set<String>) -> Unit = {}): KPrefDelegate<StringSet> = KPrefDelegate(key, StringSet(fallback), this, postSetter) -fun KPref.kpref(key: String, fallback: String, postSetter: (value: String) -> Unit = {}): KPrefDelegate<String> = KPrefDelegate(key, fallback, this, postSetter) - -class StringSet(set: Collection<String>) : LinkedHashSet<String>(set) - -/** - * Implementation of a kpref data item - * Contains a unique key for the shared preference as well as a nonnull fallback item - * Also contains an optional mutable postSetter that will be called every time a new value is given - */ -class KPrefDelegate<T : Any> internal constructor(private val key: String, private val fallback: T, private val pref: KPref, var postSetter: (value: T) -> Unit = {}, lock: Any? = null) : Lazy<T>, java.io.Serializable { - - @Volatile private var _value: Any = UNINITIALIZED - private val lock = lock ?: this - - init { - if (pref.prefMap.containsKey(key)) - throw KPrefException("$key is already used elsewhere in preference ${pref.PREFERENCE_NAME}") - pref.prefMap.put(key, this@KPrefDelegate) - } - - fun invalidate() { - _value = UNINITIALIZED - } - - override val value: T - get() { - val _v1 = _value - if (_v1 !== UNINITIALIZED) - @Suppress("UNCHECKED_CAST") - return _v1 as T - - return synchronized(lock) { - val _v2 = _value - if (_v2 !== UNINITIALIZED) { - @Suppress("UNCHECKED_CAST") - _v2 as T - } else { - _value = when (fallback) { - is Boolean -> pref.sp.getBoolean(key, fallback) - is Float -> pref.sp.getFloat(key, fallback) - is Int -> pref.sp.getInt(key, fallback) - is Long -> pref.sp.getLong(key, fallback) - is StringSet -> StringSet(pref.sp.getStringSet(key, fallback)) - is String -> pref.sp.getString(key, fallback) - else -> throw KPrefException(fallback) - } - @Suppress("UNCHECKED_CAST") - _value as T - } - } - } - - override fun isInitialized(): Boolean = _value !== UNINITIALIZED - - override fun toString(): String = if (isInitialized()) value.toString() else "Lazy kPref $key not initialized yet." - - operator fun setValue(any: Any, property: kotlin.reflect.KProperty<*>, t: T) { - _value = t - val editor = pref.sp.edit() - when (t) { - is Boolean -> editor.putBoolean(key, t) - is Float -> editor.putFloat(key, t) - is Int -> editor.putInt(key, t) - is Long -> editor.putLong(key, t) - is StringSet -> editor.putStringSet(key, t) - is String -> editor.putString(key, t) - else -> throw KPrefException(t) - } - editor.apply() - postSetter.invoke(t) - } -} - -class KPrefException(message: String) : IllegalAccessException(message) { - constructor(element: Any?) : this("Invalid type in pref cache: ${element?.javaClass?.simpleName ?: "null"}") -}
\ No newline at end of file diff --git a/library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefCheckbox.kt b/library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefCheckbox.kt deleted file mode 100644 index 22cc927..0000000 --- a/library/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 - */ -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/library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefColorPicker.kt b/library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefColorPicker.kt deleted file mode 100644 index c573939..0000000 --- a/library/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 - */ -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/library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefHeader.kt b/library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefHeader.kt deleted file mode 100644 index fa8efff..0000000 --- a/library/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 - */ -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/library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefItemBase.kt b/library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefItemBase.kt deleted file mode 100644 index bb0f0a3..0000000 --- a/library/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/library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefItemCore.kt b/library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefItemCore.kt deleted file mode 100644 index 5f684ba..0000000 --- a/library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefItemCore.kt +++ /dev/null @@ -1,126 +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() - } - } - - /** - * 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 innerContent: View? - get() = itemView.findViewById(R.id.kau_pref_inner_content) - - inline fun <reified T : View> bindInnerView(@LayoutRes id: Int): 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) - } - return innerContent as T - } - - inline fun <reified T : View> getInnerView() = innerContent as T - - operator fun get(@IdRes id: Int): View = itemView.findViewById(id) - } -}
\ No newline at end of file diff --git a/library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefPlainText.kt b/library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefPlainText.kt deleted file mode 100644 index a782430..0000000 --- a/library/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 - * - */ -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/library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefSubItems.kt b/library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefSubItems.kt deleted file mode 100644 index 51625ab..0000000 --- a/library/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 - * - */ -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/library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefText.kt b/library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefText.kt deleted file mode 100644 index 8662b6a..0000000 --- a/library/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 - * - */ -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/library/src/main/kotlin/ca/allanwang/kau/logging/KL.kt b/library/src/main/kotlin/ca/allanwang/kau/logging/KL.kt deleted file mode 100644 index 4fa3360..0000000 --- a/library/src/main/kotlin/ca/allanwang/kau/logging/KL.kt +++ /dev/null @@ -1,6 +0,0 @@ -package ca.allanwang.kau.logging - -/** - * Created by Allan Wang on 2017-06-19. - */ -object KL : TimberLogger("KAU")
\ No newline at end of file diff --git a/library/src/main/kotlin/ca/allanwang/kau/logging/TimberLogger.kt b/library/src/main/kotlin/ca/allanwang/kau/logging/TimberLogger.kt deleted file mode 100644 index 5969fd5..0000000 --- a/library/src/main/kotlin/ca/allanwang/kau/logging/TimberLogger.kt +++ /dev/null @@ -1,19 +0,0 @@ -package ca.allanwang.kau.logging - -import timber.log.Timber - - -/** - * Created by Allan Wang on 2017-05-28. - * - * Timber extension that will embed the tag as part of the message for each log item - */ -open class TimberLogger(tag: String) { - internal val TAG = "$tag: %s" - fun e(s: String) = Timber.e(TAG, s) - fun e(t: Throwable, s: String = "error") = Timber.e(t, TAG, s) - fun d(s: String) = Timber.d(TAG, s) - fun i(s: String) = Timber.i(TAG, s) - fun v(s: String) = Timber.v(TAG, s) - fun eThrow(s: String) = e(Throwable(s)) -}
\ No newline at end of file diff --git a/library/src/main/kotlin/ca/allanwang/kau/permissions/PermissionManager.kt b/library/src/main/kotlin/ca/allanwang/kau/permissions/PermissionManager.kt deleted file mode 100644 index 6f93c9f..0000000 --- a/library/src/main/kotlin/ca/allanwang/kau/permissions/PermissionManager.kt +++ /dev/null @@ -1,58 +0,0 @@ -package ca.allanwang.kau.permissions - -import android.app.Activity -import android.content.Context -import android.support.v4.app.ActivityCompat -import ca.allanwang.kau.logging.KL -import ca.allanwang.kau.utils.KauException -import ca.allanwang.kau.utils.buildIsMarshmallowAndUp -import ca.allanwang.kau.utils.hasPermission -import java.lang.ref.WeakReference - -/** - * Created by Allan Wang on 2017-07-03. - */ -internal object PermissionManager { - - var requestInProgress = false - val pendingResults: MutableList<WeakReference<PermissionResult>> by lazy { mutableListOf<WeakReference<PermissionResult>>() } - - operator fun invoke(context: Context, permissions: Array<out String>, callback: (granted: Boolean, deniedPerm: String?) -> Unit) { - KL.d("Requesting permissions: ${permissions.contentToString()}") - if (!buildIsMarshmallowAndUp) return callback(true, null) - val missingPermissions = permissions.filter { !context.hasPermission(it) } - if (missingPermissions.isEmpty()) return callback(true, null) - pendingResults.add(WeakReference(PermissionResult(permissions, callback = callback))) - if (!requestInProgress) { - requestInProgress = true - requestPermissions(context, missingPermissions.toTypedArray()) - } else KL.d("Request is postponed since another one is still in progress; did you remember to override onRequestPermissionsResult?") - } - - @Synchronized internal fun requestPermissions(context: Context, permissions: Array<out String>) { - val activity = (context as? Activity) ?: throw KauException("Context is not an instance of an activity; cannot request permissions") - ActivityCompat.requestPermissions(activity, permissions, 1) - } - - fun onRequestPermissionsResult(context: Context, permissions: Array<out String>, grantResults: IntArray) { - val count = Math.min(permissions.size, grantResults.size) - val iter = pendingResults.iterator() - while (iter.hasNext()) { - val action = iter.next().get() - if ((0 until count).any { action?.onResult(permissions[it], grantResults[it]) ?: true }) - iter.remove() - } - if (pendingResults.isEmpty()) - requestInProgress = false - else { - val action = pendingResults.map { it.get() }.firstOrNull { it != null } - if (action == null) { //actions have been unlinked from their weak references - pendingResults.clear() - requestInProgress = false - return - } - requestPermissions(context, action.permissions.toTypedArray()) - } - } - -}
\ No newline at end of file diff --git a/library/src/main/kotlin/ca/allanwang/kau/permissions/PermissionResult.kt b/library/src/main/kotlin/ca/allanwang/kau/permissions/PermissionResult.kt deleted file mode 100644 index 14bfdff..0000000 --- a/library/src/main/kotlin/ca/allanwang/kau/permissions/PermissionResult.kt +++ /dev/null @@ -1,26 +0,0 @@ -package ca.allanwang.kau.permissions - -import android.content.pm.PackageManager - -/** - * Created by Allan Wang on 2017-07-03. - */ -class PermissionResult(permissions: Array<out String>, val callback: (granted: Boolean, deniedPerm: String?) -> Unit) { - val permissions = mutableSetOf(*permissions) - - /** - * Called from the manager whenever a permission has changed - * Returns true if result is completed, false otherwise - */ - fun onResult(permission: String, result: Int): Boolean { - if (result != PackageManager.PERMISSION_GRANTED) { - callback(false, permission) - permissions.clear() - return true - } - permissions.remove(permission) - if (permissions.isNotEmpty()) return false - callback(true, null) - return true - } -}
\ No newline at end of file diff --git a/library/src/main/kotlin/ca/allanwang/kau/permissions/Permissions.kt b/library/src/main/kotlin/ca/allanwang/kau/permissions/Permissions.kt deleted file mode 100644 index fd43102..0000000 --- a/library/src/main/kotlin/ca/allanwang/kau/permissions/Permissions.kt +++ /dev/null @@ -1,68 +0,0 @@ -package ca.allanwang.kau.permissions - -import android.Manifest -import android.app.Activity -import android.content.Context - - -/** - * Created by Allan Wang on 2017-07-02. - * - * Bindings for the permission manager - */ - -/** - * Hook that should be added inside all [Activity.onRequestPermissionsResult] so that the Permission manager can handle the responses - */ -fun Activity.kauOnRequestPermissionsResult(permissions: Array<out String>, grantResults: IntArray) - = PermissionManager.onRequestPermissionsResult(this, permissions, grantResults) - -/** - * Request a permission with a callback - * In reality, an activity is needed to fulfill the request, but a context is enough if those permissions are already granted - * To be safe, you may want to check that the context can be casted successfully first - * The [callback] returns [granted], which is true if all permissions are granted - * [deniedPerm] is the first denied permission, if granted is false - */ -fun Context.kauRequestPermissions(vararg permissions: String, callback: (granted: Boolean, deniedPerm: String?) -> Unit) - = PermissionManager(this, permissions, callback) - -/** - * See http://developer.android.com/guide/topics/security/permissions.html#normal-dangerous for a - * list of 'dangerous' permissions that require a permission request on API 23. - */ -const val PERMISSION_READ_CALENDAR = Manifest.permission.READ_CALENDAR - -const val PERMISSION_WRITE_CALENDAR = Manifest.permission.WRITE_CALENDAR - -const val PERMISSION_CAMERA = Manifest.permission.CAMERA - -const val PERMISSION_READ_CONTACTS = Manifest.permission.READ_CONTACTS -const val PERMISSION_WRITE_CONTACTS = Manifest.permission.WRITE_CONTACTS -const val PERMISSION_GET_ACCOUNTS = Manifest.permission.GET_ACCOUNTS - -const val PERMISSION_ACCESS_FINE_LOCATION = Manifest.permission.ACCESS_FINE_LOCATION -const val PERMISSION_ACCESS_COARSE_LOCATION = Manifest.permission.ACCESS_COARSE_LOCATION - -const val PERMISSION_RECORD_AUDIO = Manifest.permission.RECORD_AUDIO - -const val PERMISSION_READ_PHONE_STATE = Manifest.permission.READ_PHONE_STATE -const val PERMISSION_CALL_PHONE = Manifest.permission.CALL_PHONE -const val PERMISSION_READ_CALL_LOG = Manifest.permission.READ_CALL_LOG -const val PERMISSION_WRITE_CALL_LOG = Manifest.permission.WRITE_CALL_LOG -const val PERMISSION_ADD_VOICEMAIL = Manifest.permission.ADD_VOICEMAIL -const val PERMISSION_USE_SIP = Manifest.permission.USE_SIP -const val PERMISSION_PROCESS_OUTGOING_CALLS = Manifest.permission.PROCESS_OUTGOING_CALLS - -const val PERMISSION_BODY_SENSORS = Manifest.permission.BODY_SENSORS - -const val PERMISSION_SEND_SMS = Manifest.permission.SEND_SMS -const val PERMISSION_RECEIVE_SMS = Manifest.permission.RECEIVE_SMS -const val PERMISSION_READ_SMS = Manifest.permission.READ_SMS -const val PERMISSION_RECEIVE_WAP_PUSH = Manifest.permission.RECEIVE_WAP_PUSH -const val PERMISSION_RECEIVE_MMS = Manifest.permission.RECEIVE_MMS - -const val PERMISSION_READ_EXTERNAL_STORAGE = Manifest.permission.READ_EXTERNAL_STORAGE -const val PERMISSION_WRITE_EXTERNAL_STORAGE = Manifest.permission.WRITE_EXTERNAL_STORAGE - -const val PERMISSION_SYSTEM_ALERT_WINDOW = Manifest.permission.SYSTEM_ALERT_WINDOW
\ No newline at end of file diff --git a/library/src/main/kotlin/ca/allanwang/kau/searchview/SearchItem.kt b/library/src/main/kotlin/ca/allanwang/kau/searchview/SearchItem.kt deleted file mode 100644 index ac8ec2e..0000000 --- a/library/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.id.kau_item_search, - R.layout.kau_search_iitem, - {ViewHolder(it)} -) { - - 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/library/src/main/kotlin/ca/allanwang/kau/searchview/SearchView.kt b/library/src/main/kotlin/ca/allanwang/kau/searchview/SearchView.kt deleted file mode 100644 index c077a06..0000000 --- a/library/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/library/src/main/kotlin/ca/allanwang/kau/utils/ActivityUtils.kt b/library/src/main/kotlin/ca/allanwang/kau/utils/ActivityUtils.kt deleted file mode 100644 index ec51bfd..0000000 --- a/library/src/main/kotlin/ca/allanwang/kau/utils/ActivityUtils.kt +++ /dev/null @@ -1,71 +0,0 @@ -package ca.allanwang.kau.utils - -import android.app.Activity -import android.content.Intent -import android.graphics.Color -import android.support.annotation.ColorInt -import android.support.annotation.StringRes -import android.support.design.widget.Snackbar -import android.support.v4.app.Fragment -import android.view.Menu -import ca.allanwang.kau.R -import com.mikepenz.iconics.typeface.IIcon -import org.jetbrains.anko.contentView -import org.jetbrains.anko.withArguments - -/** - * Created by Allan Wang on 2017-06-21. - */ - -/** - * Restarts an activity from itself without animations - * Keeps its existing extra bundles and has a builder to accept other parameters - */ -fun Activity.restart(builder: Intent.() -> Unit = {}) { - val i = Intent(this, this::class.java) - i.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION) - i.putExtras(intent.extras) - i.builder() - startActivity(i) - overridePendingTransition(0, 0) //No transitions - finish() - overridePendingTransition(0, 0) -} - -fun Activity.finishSlideOut() { - finish() - overridePendingTransition(R.anim.kau_fade_in, R.anim.kau_slide_out_right_top) -} - -var Activity.navigationBarColor: Int - get() = if (buildIsLollipopAndUp) window.navigationBarColor else Color.BLACK - set(value) { - if (buildIsLollipopAndUp) window.navigationBarColor = value - } - -var Activity.statusBarColor: Int - get() = if (buildIsLollipopAndUp) window.statusBarColor else Color.BLACK - set(value) { - if (buildIsLollipopAndUp) window.statusBarColor = value - } - -/** - * Themes the base menu icons and adds iicons programmatically based on ids - * - * Call in [Activity.onCreateOptionsMenu] - */ -fun Activity.setMenuIcons(menu: Menu, @ColorInt color: Int = Color.WHITE, vararg iicons: Pair<Int, IIcon>) { - iicons.forEach { (id, iicon) -> - menu.findItem(id).icon = iicon.toDrawable(this, sizeDp = 18, color = color) - } -} - -fun Activity.hideKeyboard() = currentFocus.hideKeyboard() - -fun Activity.showKeyboard() = currentFocus.showKeyboard() - -fun Activity.snackbar(text: String, duration: Int = Snackbar.LENGTH_LONG, builder: Snackbar.() -> Unit = {}) - = contentView!!.snackbar(text, duration, builder) - -fun Activity.snackbar(@StringRes textId: Int, duration: Int = Snackbar.LENGTH_LONG, builder: Snackbar.() -> Unit = {}) - = contentView!!.snackbar(textId, duration, builder)
\ No newline at end of file diff --git a/library/src/main/kotlin/ca/allanwang/kau/utils/AnimHolder.kt b/library/src/main/kotlin/ca/allanwang/kau/utils/AnimHolder.kt deleted file mode 100644 index 3db8b9c..0000000 --- a/library/src/main/kotlin/ca/allanwang/kau/utils/AnimHolder.kt +++ /dev/null @@ -1,15 +0,0 @@ -package ca.allanwang.kau.utils - -import ca.allanwang.kau.kotlin.lazyInterpolator - -/** - * Created by Allan Wang on 2017-06-28. - * - * Holder for a bunch of common animators/interpolators used throughout this library - */ -object AnimHolder { - - val fastOutSlowInInterpolator = lazyInterpolator(android.R.interpolator.fast_out_linear_in) - val decelerateInterpolator = lazyInterpolator(android.R.interpolator.decelerate_cubic) - -}
\ No newline at end of file diff --git a/library/src/main/kotlin/ca/allanwang/kau/utils/AnimUtils.kt b/library/src/main/kotlin/ca/allanwang/kau/utils/AnimUtils.kt deleted file mode 100644 index 86b049e..0000000 --- a/library/src/main/kotlin/ca/allanwang/kau/utils/AnimUtils.kt +++ /dev/null @@ -1,153 +0,0 @@ -package ca.allanwang.kau.utils - -import android.animation.Animator -import android.animation.AnimatorListenerAdapter -import android.content.Context -import android.support.annotation.StringRes -import android.view.View -import android.view.ViewAnimationUtils -import android.view.animation.Animation -import android.view.animation.AnimationUtils -import android.view.animation.DecelerateInterpolator -import android.view.animation.Interpolator -import android.widget.TextView -import ca.allanwang.kau.kotlin.lazyContext - -/** - * Created by Allan Wang on 2017-06-01. - * - * Animation extension @KauUtils functions for Views - */ -@KauUtils fun View.rootCircularReveal(x: Int = 0, y: Int = 0, duration: Long = 500L, onStart: (() -> Unit)? = null, onFinish: (() -> Unit)? = null) { - this.addOnLayoutChangeListener(object : View.OnLayoutChangeListener { - override @KauUtils fun onLayoutChange(v: View, left: Int, top: Int, right: Int, bottom: Int, oldLeft: Int, oldTop: Int, - oldRight: Int, oldBottom: Int) { - v.removeOnLayoutChangeListener(this) - var x2 = x - var y2 = y - if (x2 > right) x2 = 0 - if (y2 > bottom) y2 = 0 - val radius = Math.hypot(Math.max(x2, right - x2).toDouble(), Math.max(y2, bottom - y2).toDouble()).toInt() - val reveal = ViewAnimationUtils.createCircularReveal(v, x2, y2, 0f, radius.toFloat()) - reveal.interpolator = DecelerateInterpolator(1f) - reveal.duration = duration - reveal.addListener(object : AnimatorListenerAdapter() { - override @KauUtils fun onAnimationStart(animation: Animator?) { - visible() - onStart?.invoke() - } - - override @KauUtils fun onAnimationEnd(animation: Animator?) = onFinish?.invoke() ?: Unit - override @KauUtils fun onAnimationCancel(animation: Animator?) = onFinish?.invoke() ?: Unit - }) - reveal.start() - } - }) -} - -@KauUtils fun View.circularReveal(x: Int = 0, y: Int = 0, offset: Long = 0L, radius: Float = -1.0f, duration: Long = 500L, onStart: (() -> Unit)? = null, onFinish: (() -> Unit)? = null) { - if (!isAttachedToWindow) { - onStart?.invoke() - visible() - onFinish?.invoke() - return - } - var r = radius - if (r < 0.0f) { - r = Math.max(Math.hypot(x.toDouble(), y.toDouble()), Math.hypot((width - x.toDouble()), (height - y.toDouble()))).toFloat() - } - val anim = ViewAnimationUtils.createCircularReveal(this, x, y, 0f, r).setDuration(duration) - anim.startDelay = offset - anim.addListener(object : AnimatorListenerAdapter() { - override @KauUtils fun onAnimationStart(animation: Animator?) { - visible() - onStart?.invoke() - } - - override @KauUtils fun onAnimationEnd(animation: Animator?) = onFinish?.invoke() ?: Unit - override @KauUtils fun onAnimationCancel(animation: Animator?) = onFinish?.invoke() ?: Unit - }) - anim.start() -} - -@KauUtils fun View.circularHide(x: Int = 0, y: Int = 0, offset: Long = 0L, radius: Float = -1.0f, duration: Long = 500L, onStart: (() -> Unit)? = null, onFinish: (() -> Unit)? = null) { - if (!isAttachedToWindow) { - onStart?.invoke() - invisible() - onFinish?.invoke() - return - } - var r = radius - if (r < 0.0f) { - r = Math.max(Math.hypot(x.toDouble(), y.toDouble()), Math.hypot((width - x.toDouble()), (height - y.toDouble()))).toFloat() - } - val anim = ViewAnimationUtils.createCircularReveal(this, x, y, r, 0f).setDuration(duration) - anim.startDelay = offset - anim.addListener(object : AnimatorListenerAdapter() { - override @KauUtils fun onAnimationStart(animation: Animator?) = onStart?.invoke() ?: Unit - - override @KauUtils fun onAnimationEnd(animation: Animator?) { - invisible() - onFinish?.invoke() ?: Unit - } - - override @KauUtils fun onAnimationCancel(animation: Animator?) = onFinish?.invoke() ?: Unit - }) - anim.start() -} - -@KauUtils fun View.fadeIn(offset: Long = 0L, duration: Long = 200L, onStart: (() -> Unit)? = null, onFinish: (() -> Unit)? = null) { - if (!isAttachedToWindow) { - onStart?.invoke() - visible() - onFinish?.invoke() - return - } - if (isAttachedToWindow) { - val anim = AnimationUtils.loadAnimation(context, android.R.anim.fade_in) - anim.startOffset = offset - anim.duration = duration - anim.setAnimationListener(object : Animation.AnimationListener { - override @KauUtils fun onAnimationRepeat(animation: Animation?) {} - override @KauUtils fun onAnimationEnd(animation: Animation?) = onFinish?.invoke() ?: Unit - override @KauUtils fun onAnimationStart(animation: Animation?) { - visible() - onStart?.invoke() - } - }) - startAnimation(anim) - } -} - -@KauUtils fun View.fadeOut(offset: Long = 0L, duration: Long = 200L, onStart: (() -> Unit)? = null, onFinish: (() -> Unit)? = null) { - if (!isAttachedToWindow) { - onStart?.invoke() - invisible() - onFinish?.invoke() - return - } - val anim = AnimationUtils.loadAnimation(context, android.R.anim.fade_out) - anim.startOffset = offset - anim.duration = duration - anim.setAnimationListener(object : Animation.AnimationListener { - override @KauUtils fun onAnimationRepeat(animation: Animation?) {} - override @KauUtils fun onAnimationEnd(animation: Animation?) { - invisible() - onFinish?.invoke() - } - - override @KauUtils fun onAnimationStart(animation: Animation?) { - onStart?.invoke() - } - }) - startAnimation(anim) -} - -@KauUtils fun TextView.setTextWithFade(text: String, duration: Long = 200, onFinish: (() -> Unit)? = null) { - fadeOut(duration = duration, onFinish = { - setText(text) - fadeIn(duration = duration, onFinish = onFinish) - }) -} - -@KauUtils fun TextView.setTextWithFade(@StringRes textId: Int, duration: Long = 200, onFinish: (() -> Unit)? = null) = setTextWithFade(context.getString(textId), duration, onFinish)
\ No newline at end of file diff --git a/library/src/main/kotlin/ca/allanwang/kau/utils/ColorUtils.kt b/library/src/main/kotlin/ca/allanwang/kau/utils/ColorUtils.kt deleted file mode 100644 index 8590d6f..0000000 --- a/library/src/main/kotlin/ca/allanwang/kau/utils/ColorUtils.kt +++ /dev/null @@ -1,235 +0,0 @@ -package ca.allanwang.kau.utils - -import android.content.Context -import android.content.res.ColorStateList -import android.graphics.Color -import android.graphics.PorterDuff -import android.graphics.drawable.Drawable -import android.os.Build -import android.support.annotation.ColorInt -import android.support.annotation.FloatRange -import android.support.annotation.IntRange -import android.support.v4.content.ContextCompat -import android.support.v4.graphics.drawable.DrawableCompat -import android.support.v7.widget.AppCompatEditText -import android.support.v7.widget.Toolbar -import android.widget.* -import com.afollestad.materialdialogs.R - -/** - * Created by Allan Wang on 2017-06-08. - */ -fun Int.isColorDark(): Boolean - = (0.299 * Color.red(this) + 0.587 * Color.green(this) + 0.114 * Color.blue(this)) / 255.0 < 0.5 - -fun Int.toHexString(withAlpha: Boolean = false, withHexPrefix: Boolean = true): String { - val hex = if (withAlpha) String.format("#%08X", this) - else String.format("#%06X", 0xFFFFFF and this) - return if (withHexPrefix) hex else hex.substring(1) -} - -fun Int.toRgbaString(): String = "rgba(${Color.red(this)}, ${Color.green(this)}, ${Color.blue(this)}, ${(Color.alpha(this) / 255f).round(3)})" - -fun Int.toHSV(): FloatArray { - val hsv = FloatArray(3) - Color.colorToHSV(this, hsv) - return hsv -} - -fun FloatArray.toColor(): Int = Color.HSVToColor(this) - -fun Int.isColorVisibleOn(@ColorInt color: Int, @IntRange(from = 0L, to = 255L) delta: Int = 25, - @IntRange(from = 0L, to = 255L) minAlpha: Int = 50): Boolean = - if (Color.alpha(this) < minAlpha) false - else !(Math.abs(Color.red(this) - Color.red(color)) < delta - && Math.abs(Color.green(this) - Color.green(color)) < delta - && Math.abs(Color.blue(this) - Color.blue(color)) < delta) - - -@ColorInt -fun Context.getDisabledColor(): Int { - val primaryColor = resolveColor(android.R.attr.textColorPrimary) - val disabledColor = if (primaryColor.isColorDark()) Color.BLACK else Color.WHITE - return disabledColor.adjustAlpha(0.3f) -} - -@ColorInt -fun Int.adjustAlpha(factor: Float): Int { - val alpha = Math.round(Color.alpha(this) * factor) - return Color.argb(alpha, Color.red(this), Color.green(this), Color.blue(this)) -} - -val Int.isColorTransparent: Boolean - get() = Color.alpha(this) != 255 - -@ColorInt -fun Int.withAlpha(@IntRange(from = 0L, to = 255L) alpha: Int): Int - = Color.argb(alpha, Color.red(this), Color.green(this), Color.blue(this)) - -@ColorInt -fun Int.withMinAlpha(@IntRange(from = 0L, to = 255L) alpha: Int): Int - = Color.argb(Math.max(alpha, Color.alpha(this)), Color.red(this), Color.green(this), Color.blue(this)) - -@ColorInt -fun Int.lighten(@FloatRange(from = 0.0, to = 1.0) factor: Float = 0.1f): Int { - val (red, green, blue) = intArrayOf(Color.red(this), Color.green(this), Color.blue(this)) - .map { (it * (1f - factor) + 255f * factor).toInt() } - return Color.argb(Color.alpha(this), red, green, blue) -} - -@ColorInt -fun Int.darken(@FloatRange(from = 0.0, to = 1.0) factor: Float = 0.1f): Int { - val (red, green, blue) = intArrayOf(Color.red(this), Color.green(this), Color.blue(this)) - .map { (it * (1f - factor)).toInt() } - return Color.argb(Color.alpha(this), red, green, blue) -} - -@ColorInt -fun Int.colorToBackground(@FloatRange(from = 0.0, to = 1.0) factor: Float = 0.1f): Int - = if (isColorDark()) darken(factor) else lighten(factor) - -@ColorInt -fun Int.colorToForeground(@FloatRange(from = 0.0, to = 1.0) factor: Float = 0.1f): Int - = if (isColorDark()) lighten(factor) else darken(factor) - -@Throws(IllegalArgumentException::class) -fun String.toColor(): Int { - val toParse: String - if (startsWith("#") && length == 4) - toParse = "#${this[1]}${this[1]}${this[2]}${this[2]}${this[3]}${this[3]}" - else - toParse = this - return Color.parseColor(toParse) -} - -//Get ColorStateList -fun Context.colorStateList(@ColorInt color: Int): ColorStateList { - val disabledColor = color.adjustAlpha(0.3f) - return ColorStateList(arrayOf(intArrayOf(android.R.attr.state_enabled, -android.R.attr.state_checked), - intArrayOf(android.R.attr.state_enabled, android.R.attr.state_checked), - intArrayOf(-android.R.attr.state_enabled, -android.R.attr.state_checked), - intArrayOf(-android.R.attr.state_enabled, android.R.attr.state_checked)), - intArrayOf(color.adjustAlpha(0.8f), color, disabledColor, disabledColor)) -} - -/* - * Tint Helpers - * Kotlin tint bindings that start with 'tint' so it doesn't conflict with existing methods - * Largely based on MDTintHelper - * https://github.com/afollestad/material-dialogs/blob/master/core/src/main/java/com/afollestad/materialdialogs/internal/MDTintHelper.java - */ -fun RadioButton.tint(colors: ColorStateList) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - buttonTintList = colors - } else { - val radioDrawable = context.drawable(R.drawable.abc_btn_radio_material) - val d = DrawableCompat.wrap(radioDrawable) - DrawableCompat.setTintList(d, colors) - buttonDrawable = d - } -} - -fun RadioButton.tint(@ColorInt color: Int) = tint(context.colorStateList(color)) - -fun CheckBox.tint(colors: ColorStateList) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - buttonTintList = colors - } else { - val checkDrawable = context.drawable(R.drawable.abc_btn_check_material) - val drawable = DrawableCompat.wrap(checkDrawable) - DrawableCompat.setTintList(drawable, colors) - buttonDrawable = drawable - } -} - -fun CheckBox.tint(@ColorInt color: Int) = tint(context.colorStateList(color)) - -fun SeekBar.tint(@ColorInt color: Int) { - val s1 = ColorStateList.valueOf(color) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - thumbTintList = s1 - progressTintList = s1 - } else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.GINGERBREAD_MR1) { - val progressDrawable = DrawableCompat.wrap(progressDrawable) - this.progressDrawable = progressDrawable - DrawableCompat.setTintList(progressDrawable, s1) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - val thumbDrawable = DrawableCompat.wrap(thumb) - DrawableCompat.setTintList(thumbDrawable, s1) - thumb = thumbDrawable - } - } else { - val mode: PorterDuff.Mode = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) - PorterDuff.Mode.MULTIPLY else PorterDuff.Mode.SRC_IN - indeterminateDrawable?.setColorFilter(color, mode) - progressDrawable?.setColorFilter(color, mode) - } -} - -fun ProgressBar.tint(@ColorInt color: Int, skipIndeterminate: Boolean = false) { - val sl = ColorStateList.valueOf(color) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - progressTintList = sl - secondaryProgressTintList = sl - if (!skipIndeterminate) indeterminateTintList = sl - } else { - val mode: PorterDuff.Mode = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) - PorterDuff.Mode.MULTIPLY else PorterDuff.Mode.SRC_IN - indeterminateDrawable?.setColorFilter(color, mode) - progressDrawable?.setColorFilter(color, mode) - } -} - -fun Context.textColorStateList(@ColorInt color: Int): ColorStateList { - val states = arrayOf( - intArrayOf(-android.R.attr.state_enabled), - intArrayOf(-android.R.attr.state_pressed, -android.R.attr.state_focused), - intArrayOf() - ) - val colors = intArrayOf( - resolveColor(R.attr.colorControlNormal), - resolveColor(R.attr.colorControlNormal), - color - ) - return ColorStateList(states, colors) -} - -fun EditText.tint(@ColorInt color: Int) { - val editTextColorStateList = context.textColorStateList(color) - if (this is AppCompatEditText) { - supportBackgroundTintList = editTextColorStateList - } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - backgroundTintList = editTextColorStateList - } - tintCursor(color) -} - -fun EditText.tintCursor(@ColorInt color: Int) { - try { - val fCursorDrawableRes = TextView::class.java.getDeclaredField("mCursorDrawableRes") - fCursorDrawableRes.isAccessible = true - val mCursorDrawableRes = fCursorDrawableRes.getInt(this) - val fEditor = TextView::class.java.getDeclaredField("mEditor") - fEditor.isAccessible = true - val editor = fEditor.get(this) - val clazz = editor.javaClass - val fCursorDrawable = clazz.getDeclaredField("mCursorDrawable") - fCursorDrawable.isAccessible = true - val drawables: Array<Drawable> = Array(2, { - val drawable = ContextCompat.getDrawable(context, mCursorDrawableRes) - drawable.setColorFilter(color, PorterDuff.Mode.SRC_IN) - drawable - }) - fCursorDrawable.set(editor, drawables) - } catch (e: Exception) { - e.printStackTrace() - } -} - -fun Toolbar.tint(@ColorInt color: Int, tintTitle: Boolean = true) { - if (tintTitle) { - setTitleTextColor(color) - setSubtitleTextColor(color) - } - (0 until childCount).asSequence().forEach { (getChildAt(it) as? ImageButton)?.setColorFilter(color) } -}
\ No newline at end of file diff --git a/library/src/main/kotlin/ca/allanwang/kau/utils/Const.kt b/library/src/main/kotlin/ca/allanwang/kau/utils/Const.kt deleted file mode 100644 index 944caa4..0000000 --- a/library/src/main/kotlin/ca/allanwang/kau/utils/Const.kt +++ /dev/null @@ -1,6 +0,0 @@ -package ca.allanwang.kau.utils - -/** - * Created by Allan Wang on 2017-06-08. - */ -const val ANDROID_NAMESPACE = "http://schemas.android.com/apk/res/android"
\ No newline at end of file diff --git a/library/src/main/kotlin/ca/allanwang/kau/utils/ContextUtils.kt b/library/src/main/kotlin/ca/allanwang/kau/utils/ContextUtils.kt deleted file mode 100644 index 21021e2..0000000 --- a/library/src/main/kotlin/ca/allanwang/kau/utils/ContextUtils.kt +++ /dev/null @@ -1,165 +0,0 @@ -package ca.allanwang.kau.utils - -import android.app.Activity -import android.app.ActivityOptions -import android.content.Context -import android.content.Intent -import android.content.pm.PackageManager -import android.graphics.drawable.Drawable -import android.net.ConnectivityManager -import android.net.Uri -import android.os.Bundle -import android.support.annotation.* -import android.support.v4.app.ActivityOptionsCompat -import android.support.v4.content.ContextCompat -import android.util.TypedValue -import android.view.View -import android.widget.Toast -import ca.allanwang.kau.R -import ca.allanwang.kau.logging.KL -import com.afollestad.materialdialogs.MaterialDialog - - -/** - * Created by Allan Wang on 2017-06-03. - */ -fun Context.startActivity( - clazz: Class<out Activity>, - clearStack: Boolean = false, - transition: Boolean = false, - bundle: Bundle? = null, - intentBuilder: Intent.() -> Unit = {}) { - val intent = (Intent(this, clazz)) - if (clearStack) intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK) - intent.intentBuilder() - val fullBundle = if (transition && this is Activity) ActivityOptions.makeSceneTransitionAnimation(this).toBundle() else Bundle() - if (transition && this !is Activity) KL.d("Cannot make scene transition when context is not an instance of an Activity") - if (bundle != null) fullBundle.putAll(bundle) - ContextCompat.startActivity(this, intent, if (fullBundle.isEmpty) null else fullBundle) - if (this is Activity && clearStack) finish() -} - -/** - * Bring in activity from the right - */ -fun Context.startActivitySlideIn(clazz: Class<out Activity>, clearStack: Boolean = false, intentBuilder: Intent.() -> Unit = {}, bundleBuilder: Bundle.() -> Unit = {}) { - val bundle = ActivityOptionsCompat.makeCustomAnimation(this, R.anim.kau_slide_in_right, R.anim.kau_fade_out).toBundle() - bundle.bundleBuilder() - startActivity(clazz, clearStack, intentBuilder = intentBuilder, bundle = bundle) -} - -/** - * Bring in activity from behind while pushing the current activity to the right - * This replicates the exit animation of a sliding activity, but is a forward creation - * For the animation to work, the previous activity should not be in the stack (otherwise you wouldn't need this in the first place) - * Consequently, the stack will be cleared by default - */ -fun Context.startActivitySlideOut(clazz: Class<out Activity>, clearStack: Boolean = true, intentBuilder: Intent.() -> Unit = {}, bundleBuilder: Bundle.() -> Unit = {}) { - val bundle = ActivityOptionsCompat.makeCustomAnimation(this, R.anim.kau_fade_in, R.anim.kau_slide_out_right_top).toBundle() - bundle.bundleBuilder() - startActivity(clazz, clearStack, intentBuilder = intentBuilder, bundle = bundle) -} - -fun Context.startPlayStoreLink(@StringRes packageIdRes: Int) = startPlayStoreLink(string(packageIdRes)) - -fun Context.startPlayStoreLink(packageId: String) { - startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=$packageId"))) -} - -/** - * Starts a url - * If given a series of links, will open the first one that isn't null - */ -fun Context.startLink(vararg url: String?) { - val link = url.firstOrNull { !it.isNullOrBlank() } ?: return - val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(link)) - startActivity(browserIntent) -} - -//Toast helpers -fun Context.toast(@StringRes id: Int, duration: Int = Toast.LENGTH_LONG) = toast(this.string(id), duration) - -fun Context.toast(text: String, duration: Int = Toast.LENGTH_LONG) { - Toast.makeText(this, text, duration).show() -} - -//Resource retrievers -fun Context.string(@StringRes id: Int): String = getString(id) - -fun Context.string(@StringRes id: Int, fallback: String?): String? = if (id > 0) string(id) else fallback -fun Context.string(holder: StringHolder?): String? = holder?.getString(this) -fun Context.color(@ColorRes id: Int): Int = ContextCompat.getColor(this, id) -fun Context.integer(@IntegerRes id: Int): Int = resources.getInteger(id) -fun Context.dimen(@DimenRes id: Int): Float = resources.getDimension(id) -fun Context.dimenPixelSize(@DimenRes id: Int): Int = resources.getDimensionPixelSize(id) -fun Context.drawable(@DrawableRes id: Int): Drawable = ContextCompat.getDrawable(this, id) -fun Context.drawable(@DrawableRes id: Int, fallback: Drawable?): Drawable? = if (id > 0) drawable(id) else fallback - -//Attr retrievers -fun Context.resolveColor(@AttrRes attr: Int, fallback: Int = 0): Int { - val a = theme.obtainStyledAttributes(intArrayOf(attr)) - try { - return a.getColor(0, fallback) - } finally { - a.recycle() - } -} - -fun Context.resolveDrawable(@AttrRes attr: Int): Drawable? { - val a = theme.obtainStyledAttributes(intArrayOf(attr)) - try { - return a.getDrawable(0) - } finally { - a.recycle() - } -} - -fun Context.resolveBoolean(@AttrRes attr: Int, fallback: Boolean = false): Boolean { - val a = theme.obtainStyledAttributes(intArrayOf(attr)) - try { - return a.getBoolean(0, fallback) - } finally { - a.recycle() - } -} - -fun Context.resolveString(@AttrRes attr: Int, fallback: String = ""): String { - val v = TypedValue() - return if (theme.resolveAttribute(attr, v, true)) v.string.toString() else fallback -} - -/** - * Wrapper function for the MaterialDialog adapterBuilder - * There is no need to call build() or show() as those are done by default - */ -inline fun Context.materialDialog(action: MaterialDialog.Builder.() -> Unit): MaterialDialog { - val builder = MaterialDialog.Builder(this) - builder.action() - return builder.show() -} - -inline val Context.isNetworkAvailable: Boolean - get() { - val connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager - val activeNetworkInfo = connectivityManager.activeNetworkInfo - return activeNetworkInfo != null && activeNetworkInfo.isConnectedOrConnecting - } - -fun Context.getDip(value: Float): Float = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value, resources.displayMetrics) - -inline val Context.isRtl: Boolean - get() = resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL - -/** - * Determine if the navigation bar will be on the bottom of the screen, based on logic in - * PhoneWindowManager. - */ -inline val Context.isNavBarOnBottom: Boolean - get() { - val cfg = resources.configuration - val dm = resources.displayMetrics - val canMove = dm.widthPixels != dm.heightPixels && cfg.smallestScreenWidthDp < 600 - return !canMove || dm.widthPixels < dm.heightPixels - } - -fun Context.hasPermission(permissions: String) = !buildIsMarshmallowAndUp || ContextCompat.checkSelfPermission(this, permissions) == PackageManager.PERMISSION_GRANTED
\ No newline at end of file diff --git a/library/src/main/kotlin/ca/allanwang/kau/utils/Either.kt b/library/src/main/kotlin/ca/allanwang/kau/utils/Either.kt deleted file mode 100644 index dab5810..0000000 --- a/library/src/main/kotlin/ca/allanwang/kau/utils/Either.kt +++ /dev/null @@ -1,32 +0,0 @@ -package ca.allanwang.kau.utils - -/** - * Created by Allan Wang on 2017-06-17. - * - * Courtesy of adelnizamutdinov - * - * https://github.com/adelnizamutdinov/kotlin-either - */ -@Suppress("unused") -sealed class Either<out L, out R> - -data class Left<out T>(val value: T) : Either<T, Nothing>() -data class Right<out T>(val value: T) : Either<Nothing, T>() - -inline fun <L, R, T> Either<L, R>.fold(left: (L) -> T, right: (R) -> T): T = - when (this) { - is Left -> left(value) - is Right -> right(value) - } - -inline fun <L, R, T> Either<L, R>.flatMap(f: (R) -> Either<L, T>): Either<L, T> = - fold({ this as Left }, f) - -inline fun <L, R, T> Either<L, R>.map(f: (R) -> T): Either<L, T> = - flatMap { Right(f(it)) } - -val <T> Either<T, *>.isLeft: Boolean - get() = this is Left<T> - -val <T> Either<*, T>.isRight: Boolean - get() = this is Right<T>
\ No newline at end of file diff --git a/library/src/main/kotlin/ca/allanwang/kau/utils/FontUtils.kt b/library/src/main/kotlin/ca/allanwang/kau/utils/FontUtils.kt deleted file mode 100644 index 3fc509d..0000000 --- a/library/src/main/kotlin/ca/allanwang/kau/utils/FontUtils.kt +++ /dev/null @@ -1,29 +0,0 @@ -package ca.allanwang.kau.utils - -import android.content.Context -import android.graphics.Typeface - -/** - * Created by Allan Wang on 2017-06-28. - */ -object FontUtils { - - val sTypefaceCache: MutableMap<String, Typeface> = mutableMapOf() - - fun get(context: Context, font: String): Typeface { - synchronized(sTypefaceCache) { - if (!sTypefaceCache.containsKey(font)) { - val tf = Typeface.createFromAsset( - context.applicationContext.assets, "fonts/$font.ttf") - sTypefaceCache.put(font, tf) - } - return sTypefaceCache.get(font) ?: throw IllegalArgumentException("Font error; typeface does not exist at assets/fonts$font.ttf") - } - } - - fun getName(typeface: Typeface): String? = sTypefaceCache.entries.firstOrNull { it.value == typeface }?.key - -} - -fun Context.getFont(font: String) = FontUtils.get(this, font) -fun Context.getFontName(typeface: Typeface) = FontUtils.getName(typeface)
\ No newline at end of file diff --git a/library/src/main/kotlin/ca/allanwang/kau/utils/FragmentUtils.kt b/library/src/main/kotlin/ca/allanwang/kau/utils/FragmentUtils.kt deleted file mode 100644 index acc71f2..0000000 --- a/library/src/main/kotlin/ca/allanwang/kau/utils/FragmentUtils.kt +++ /dev/null @@ -1,12 +0,0 @@ -package ca.allanwang.kau.utils - -import android.support.v4.app.Fragment -import org.jetbrains.anko.bundleOf - -/** - * Created by Allan Wang on 2017-07-02. - */ -fun <T : Fragment> T.withArguments(vararg params: Pair<String, Any>): T { - arguments = bundleOf(*params) - return this -}
\ No newline at end of file diff --git a/library/src/main/kotlin/ca/allanwang/kau/utils/IIconUtils.kt b/library/src/main/kotlin/ca/allanwang/kau/utils/IIconUtils.kt deleted file mode 100644 index 03a1605..0000000 --- a/library/src/main/kotlin/ca/allanwang/kau/utils/IIconUtils.kt +++ /dev/null @@ -1,20 +0,0 @@ -package ca.allanwang.kau.utils - -import android.content.Context -import android.content.res.ColorStateList -import android.graphics.Color -import android.graphics.drawable.Drawable -import android.support.annotation.ColorInt -import com.mikepenz.iconics.IconicsDrawable -import com.mikepenz.iconics.typeface.IIcon - -/** - * Created by Allan Wang on 2017-05-29. - */ -@KauUtils fun IIcon.toDrawable(c: Context, sizeDp: Int = 24, @ColorInt color: Int = Color.WHITE, builder: IconicsDrawable.() -> Unit = {}): Drawable { - val state = ColorStateList.valueOf(color) - val icon = IconicsDrawable(c).icon(this).sizeDp(sizeDp) - icon.setTintList(state) - icon.builder() - return icon -}
\ No newline at end of file diff --git a/library/src/main/kotlin/ca/allanwang/kau/utils/Kotterknife.kt b/library/src/main/kotlin/ca/allanwang/kau/utils/Kotterknife.kt deleted file mode 100644 index 247bbc7..0000000 --- a/library/src/main/kotlin/ca/allanwang/kau/utils/Kotterknife.kt +++ /dev/null @@ -1,166 +0,0 @@ -package ca.allanwang.kau.utils - -/** - * Created by Allan Wang on 2017-05-29. - * - * Courtesy of Jake Wharton - * - * https://github.com/JakeWharton/kotterknife/blob/master/src/main/kotlin/kotterknife/ButterKnife.kt - */ -import android.app.Activity -import android.app.Dialog -import android.app.DialogFragment -import android.app.Fragment -import android.support.v7.widget.RecyclerView.ViewHolder -import android.view.View -import kotlin.properties.ReadOnlyProperty -import kotlin.reflect.KProperty -import android.support.v4.app.DialogFragment as SupportDialogFragment -import android.support.v4.app.Fragment as SupportFragment - -fun <V : View> View.bindView(id: Int) - : ReadOnlyProperty<View, V> = required(id, viewFinder) - -fun <V : View> Activity.bindView(id: Int) - : ReadOnlyProperty<Activity, V> = required(id, viewFinder) - -fun <V : View> Dialog.bindView(id: Int) - : ReadOnlyProperty<Dialog, V> = required(id, viewFinder) - -fun <V : View> DialogFragment.bindView(id: Int) - : ReadOnlyProperty<DialogFragment, V> = required(id, viewFinder) - -fun <V : View> android.support.v4.app.DialogFragment.bindView(id: Int) - : ReadOnlyProperty<android.support.v4.app.DialogFragment, V> = required(id, viewFinder) - -fun <V : View> Fragment.bindView(id: Int) - : ReadOnlyProperty<Fragment, V> = required(id, viewFinder) - -fun <V : View> android.support.v4.app.Fragment.bindView(id: Int) - : ReadOnlyProperty<android.support.v4.app.Fragment, V> = required(id, viewFinder) - -fun <V : View> ViewHolder.bindView(id: Int) - : ReadOnlyProperty<ViewHolder, V> = required(id, viewFinder) - -fun <V : View> View.bindOptionalView(id: Int) - : ReadOnlyProperty<View, V?> = optional(id, viewFinder) - -fun <V : View> Activity.bindOptionalView(id: Int) - : ReadOnlyProperty<Activity, V?> = optional(id, viewFinder) - -fun <V : View> Dialog.bindOptionalView(id: Int) - : ReadOnlyProperty<Dialog, V?> = optional(id, viewFinder) - -fun <V : View> DialogFragment.bindOptionalView(id: Int) - : ReadOnlyProperty<DialogFragment, V?> = optional(id, viewFinder) - -fun <V : View> android.support.v4.app.DialogFragment.bindOptionalView(id: Int) - : ReadOnlyProperty<android.support.v4.app.DialogFragment, V?> = optional(id, viewFinder) - -fun <V : View> Fragment.bindOptionalView(id: Int) - : ReadOnlyProperty<Fragment, V?> = optional(id, viewFinder) - -fun <V : View> android.support.v4.app.Fragment.bindOptionalView(id: Int) - : ReadOnlyProperty<android.support.v4.app.Fragment, V?> = optional(id, viewFinder) - -fun <V : View> ViewHolder.bindOptionalView(id: Int) - : ReadOnlyProperty<ViewHolder, V?> = optional(id, viewFinder) - -fun <V : View> View.bindViews(vararg ids: Int) - : ReadOnlyProperty<View, List<V>> = required(ids, viewFinder) - -fun <V : View> Activity.bindViews(vararg ids: Int) - : ReadOnlyProperty<Activity, List<V>> = required(ids, viewFinder) - -fun <V : View> Dialog.bindViews(vararg ids: Int) - : ReadOnlyProperty<Dialog, List<V>> = required(ids, viewFinder) - -fun <V : View> DialogFragment.bindViews(vararg ids: Int) - : ReadOnlyProperty<DialogFragment, List<V>> = required(ids, viewFinder) - -fun <V : View> android.support.v4.app.DialogFragment.bindViews(vararg ids: Int) - : ReadOnlyProperty<android.support.v4.app.DialogFragment, List<V>> = required(ids, viewFinder) - -fun <V : View> Fragment.bindViews(vararg ids: Int) - : ReadOnlyProperty<Fragment, List<V>> = required(ids, viewFinder) - -fun <V : View> android.support.v4.app.Fragment.bindViews(vararg ids: Int) - : ReadOnlyProperty<android.support.v4.app.Fragment, List<V>> = required(ids, viewFinder) - -fun <V : View> ViewHolder.bindViews(vararg ids: Int) - : ReadOnlyProperty<ViewHolder, List<V>> = required(ids, viewFinder) - -fun <V : View> View.bindOptionalViews(vararg ids: Int) - : ReadOnlyProperty<View, List<V>> = optional(ids, viewFinder) - -fun <V : View> Activity.bindOptionalViews(vararg ids: Int) - : ReadOnlyProperty<Activity, List<V>> = optional(ids, viewFinder) - -fun <V : View> Dialog.bindOptionalViews(vararg ids: Int) - : ReadOnlyProperty<Dialog, List<V>> = optional(ids, viewFinder) - -fun <V : View> DialogFragment.bindOptionalViews(vararg ids: Int) - : ReadOnlyProperty<DialogFragment, List<V>> = optional(ids, viewFinder) - -fun <V : View> android.support.v4.app.DialogFragment.bindOptionalViews(vararg ids: Int) - : ReadOnlyProperty<android.support.v4.app.DialogFragment, List<V>> = optional(ids, viewFinder) - -fun <V : View> Fragment.bindOptionalViews(vararg ids: Int) - : ReadOnlyProperty<Fragment, List<V>> = optional(ids, viewFinder) - -fun <V : View> android.support.v4.app.Fragment.bindOptionalViews(vararg ids: Int) - : ReadOnlyProperty<android.support.v4.app.Fragment, List<V>> = optional(ids, viewFinder) - -fun <V : View> ViewHolder.bindOptionalViews(vararg ids: Int) - : ReadOnlyProperty<ViewHolder, List<V>> = optional(ids, viewFinder) - -private val View.viewFinder: View.(Int) -> View? - get() = { findViewById(it) } -private val Activity.viewFinder: Activity.(Int) -> View? - get() = { findViewById(it) } -private val Dialog.viewFinder: Dialog.(Int) -> View? - get() = { findViewById(it) } -private val DialogFragment.viewFinder: DialogFragment.(Int) -> View? - get() = { dialog.findViewById(it) } -private val android.support.v4.app.DialogFragment.viewFinder: android.support.v4.app.DialogFragment.(Int) -> View? - get() = { dialog.findViewById(it) } -private val Fragment.viewFinder: Fragment.(Int) -> View? - get() = { view.findViewById(it) } -private val android.support.v4.app.Fragment.viewFinder: android.support.v4.app.Fragment.(Int) -> View? - get() = { view!!.findViewById(it) } -private val ViewHolder.viewFinder: ViewHolder.(Int) -> View? - get() = { itemView.findViewById(it) } - -private fun viewNotFound(id: Int, desc: KProperty<*>): Nothing = - throw IllegalStateException("View ID $id for '${desc.name}' not found.") - -@Suppress("UNCHECKED_CAST") -private fun <T, V : View> required(id: Int, finder: T.(Int) -> View?) - = Lazy { t: T, desc -> (t.finder(id) as V?)?.apply { } ?: viewNotFound(id, desc) } - -@Suppress("UNCHECKED_CAST") -private fun <T, V : View> optional(id: Int, finder: T.(Int) -> View?) - = Lazy { t: T, _ -> t.finder(id) as V? } - -@Suppress("UNCHECKED_CAST") -private fun <T, V : View> required(ids: IntArray, finder: T.(Int) -> View?) - = Lazy { t: T, desc -> ids.map { t.finder(it) as V? ?: viewNotFound(it, desc) } } - -@Suppress("UNCHECKED_CAST") -private fun <T, V : View> optional(ids: IntArray, finder: T.(Int) -> View?) - = Lazy { t: T, _ -> ids.map { t.finder(it) as V? }.filterNotNull() } - -// Like Kotlin's lazy delegate but the initializer gets the target and metadata passed to it -private class Lazy<T, V>(private val initializer: (T, KProperty<*>) -> V) : ReadOnlyProperty<T, V> { - private object EMPTY - - private var value: Any? = EMPTY - - override fun getValue(thisRef: T, property: KProperty<*>): V { - if (value == EMPTY) { - value = initializer(thisRef, property) - } - @Suppress("UNCHECKED_CAST") - return value as V - } -}
\ No newline at end of file diff --git a/library/src/main/kotlin/ca/allanwang/kau/utils/PackageUtils.kt b/library/src/main/kotlin/ca/allanwang/kau/utils/PackageUtils.kt deleted file mode 100644 index 837c209..0000000 --- a/library/src/main/kotlin/ca/allanwang/kau/utils/PackageUtils.kt +++ /dev/null @@ -1,48 +0,0 @@ -package ca.allanwang.kau.utils - -import android.content.Context -import android.content.pm.PackageManager -import android.os.Build -import android.support.annotation.RequiresApi - -/** - * Created by Allan Wang on 2017-06-23. - */ - -/** - * Checks if a given package is installed - * @param packageName packageId - * @return true if installed with activity, false otherwise - */ -@KauUtils fun Context.isAppInstalled(packageName: String): Boolean { - val pm = packageManager - var installed: Boolean - try { - pm.getPackageInfo(packageName, PackageManager.GET_ACTIVITIES) - installed = true - } catch (e: PackageManager.NameNotFoundException) { - installed = false - } - return installed -} - -val buildIsLollipopAndUp: Boolean - get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP - -val buildIsMarshmallowAndUp: Boolean - get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M - -val buildIsNougatAndUp: Boolean - get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N - -const val INSTALLER_GOOGLE_PLAY_VENDING = "com.android.vending" -const val INSTALLER_GOOGLE_PLAY_FEEDBACK = "com.google.android.feedback" - -val Context.installerPackageName: String? - get() = packageManager.getInstallerPackageName(packageName) - -val Context.isFromGooglePlay: Boolean - get() { - val installer = installerPackageName - return arrayOf(INSTALLER_GOOGLE_PLAY_FEEDBACK, INSTALLER_GOOGLE_PLAY_VENDING).any { it == installer } - }
\ No newline at end of file diff --git a/library/src/main/kotlin/ca/allanwang/kau/utils/StringHolder.kt b/library/src/main/kotlin/ca/allanwang/kau/utils/StringHolder.kt deleted file mode 100644 index e70a2d1..0000000 --- a/library/src/main/kotlin/ca/allanwang/kau/utils/StringHolder.kt +++ /dev/null @@ -1,22 +0,0 @@ -package ca.allanwang.kau.utils - -import android.content.Context -import android.support.annotation.StringRes - -/** - * Created by Allan Wang on 2017-06-08. - */ -class StringHolder { - var text: String? = null - var textRes: Int = 0 - - constructor(@StringRes textRes: Int) { - this.textRes = textRes - } - - constructor(text: String) { - this.text = text - } - - fun getString(context: Context) = context.string(textRes, text) -}
\ No newline at end of file diff --git a/library/src/main/kotlin/ca/allanwang/kau/utils/TransitionUtils.kt b/library/src/main/kotlin/ca/allanwang/kau/utils/TransitionUtils.kt deleted file mode 100644 index 9e668d0..0000000 --- a/library/src/main/kotlin/ca/allanwang/kau/utils/TransitionUtils.kt +++ /dev/null @@ -1,19 +0,0 @@ -package ca.allanwang.kau.utils - -import android.support.transition.Transition -import android.support.transition.TransitionSet - -/** - * Created by Allan Wang on 2017-06-24. - */ -class TransitionEndListener(val onEnd: (transition: Transition) -> Unit) : Transition.TransitionListener { - override fun onTransitionEnd(transition: Transition) = onEnd(transition) - override fun onTransitionResume(transition: Transition) {} - override fun onTransitionPause(transition: Transition) {} - override fun onTransitionCancel(transition: Transition) {} - override fun onTransitionStart(transition: Transition) {} -} - -@KauUtils fun TransitionSet.addEndListener(onEnd: (transition: Transition) -> Unit) { - addListener(TransitionEndListener(onEnd)) -}
\ No newline at end of file diff --git a/library/src/main/kotlin/ca/allanwang/kau/utils/Utils.kt b/library/src/main/kotlin/ca/allanwang/kau/utils/Utils.kt deleted file mode 100644 index 84794f9..0000000 --- a/library/src/main/kotlin/ca/allanwang/kau/utils/Utils.kt +++ /dev/null @@ -1,111 +0,0 @@ -package ca.allanwang.kau.utils - -import android.content.Context -import android.content.res.Resources -import android.graphics.Bitmap -import android.graphics.Canvas -import android.graphics.drawable.BitmapDrawable -import android.graphics.drawable.Drawable -import android.os.Handler -import android.os.Looper -import android.support.annotation.IntRange -import ca.allanwang.kau.R -import ca.allanwang.kau.logging.KL -import java.math.RoundingMode -import java.text.DecimalFormat - - -/** - * Created by Allan Wang on 2017-05-28. - */ - -/** - * Markers to isolate respective extension @KauUtils functions to their extended class - * Avoids having a whole bunch of methods for nested calls - */ -@DslMarker -annotation class KauUtils - -@KauUtils val Int.dpToPx: Int - get() = (this * Resources.getSystem().displayMetrics.density).toInt() - -@KauUtils val Int.pxToDp: Int - get() = (this / Resources.getSystem().displayMetrics.density).toInt() - -/** - * Log whether current state is in the main thread - */ -@KauUtils fun checkThread(id: Int) { - val status = if (Looper.myLooper() == Looper.getMainLooper()) "is" else "is not" - KL.d("$id $status in the main thread") -} - -/** - * Converts minute value to string - * Whole hours and days will be converted as such, otherwise it will default to x minutes - */ -@KauUtils fun Context.minuteToText(minutes: Long): String = with(minutes) { - if (this < 0L) string(R.string.kau_none) - else if (this == 60L) string(R.string.kau_one_hour) - else if (this == 1440L) string(R.string.kau_one_day) - else if (this % 1440L == 0L) String.format(string(R.string.kau_x_days), this / 1440L) - else if (this % 60L == 0L) String.format(string(R.string.kau_x_hours), this / 60L) - else String.format(string(R.string.kau_x_minutes), this) -} - -@KauUtils fun Number.round(@IntRange(from = 1L) decimalCount: Int): String { - val expression = StringBuilder().append("#.") - (1..decimalCount).forEach { expression.append("#") } - val formatter = DecimalFormat(expression.toString()) - formatter.roundingMode = RoundingMode.HALF_UP - return formatter.format(this) -} - -/** - * Extracts the bitmap of a drawable, and applies a scale if given - * For solid colors, a 1 x 1 pixel will be generated - */ -@KauUtils fun Drawable.toBitmap(scaling: Float = 1f, config: Bitmap.Config = Bitmap.Config.ARGB_8888): Bitmap { - if (this is BitmapDrawable && bitmap != null) { - if (scaling == 1f) return bitmap - val width = (bitmap.width * scaling).toInt() - val height = (bitmap.height * scaling).toInt() - return Bitmap.createScaledBitmap(bitmap, width, height, false) - } - val bitmap = if (intrinsicWidth <= 0 || intrinsicHeight <= 0) - Bitmap.createBitmap(1, 1, config) - else - Bitmap.createBitmap((intrinsicWidth * scaling).toInt(), (intrinsicHeight * scaling).toInt(), config) - val canvas = Canvas(bitmap) - setBounds(0, 0, canvas.width, canvas.height) - draw(canvas) - return bitmap -} - -/** - * Use block for autocloseables - */ -inline fun <T : AutoCloseable, R> T.use(block: (T) -> R): R { - var closed = false - try { - return block(this) - } catch (e: Exception) { - closed = true - try { - close() - } catch (closeException: Exception) { - e.addSuppressed(closeException) - } - throw e - } finally { - if (!closed) { - close() - } - } -} - -fun postDelayed(delay: Long, action: () -> Unit) { - Handler().postDelayed(action, delay) -} - -class KauException(message: String) : RuntimeException(message)
\ No newline at end of file diff --git a/library/src/main/kotlin/ca/allanwang/kau/utils/ViewUtils.kt b/library/src/main/kotlin/ca/allanwang/kau/utils/ViewUtils.kt deleted file mode 100644 index b4752a5..0000000 --- a/library/src/main/kotlin/ca/allanwang/kau/utils/ViewUtils.kt +++ /dev/null @@ -1,126 +0,0 @@ -package ca.allanwang.kau.utils - -import android.content.Context -import android.graphics.Color -import android.graphics.Outline -import android.graphics.Rect -import android.support.annotation.ColorInt -import android.support.annotation.StringRes -import android.support.annotation.TransitionRes -import android.support.design.widget.Snackbar -import android.support.transition.AutoTransition -import android.support.transition.Transition -import android.support.transition.TransitionInflater -import android.support.transition.TransitionManager -import android.support.v7.widget.LinearLayoutManager -import android.support.v7.widget.RecyclerView -import android.view.View -import android.view.ViewGroup -import android.view.ViewOutlineProvider -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 com.mikepenz.iconics.IconicsDrawable -import com.mikepenz.iconics.typeface.IIcon - - -/** - * Created by Allan Wang on 2017-05-31. - */ -@KauUtils fun <T : View> T.visible(): T { - visibility = View.VISIBLE - return this -} - -@KauUtils fun <T : View> T.invisible(): T { - visibility = View.INVISIBLE - return this -} - -@KauUtils fun <T : View> T.gone(): T { - visibility = View.GONE - return this -} - -@KauUtils fun View.isVisible(): Boolean = visibility == View.VISIBLE -@KauUtils fun View.isInvisible(): Boolean = visibility == View.INVISIBLE -@KauUtils fun View.isGone(): Boolean = visibility == View.GONE - -fun View.snackbar(text: String, duration: Int = Snackbar.LENGTH_LONG, builder: Snackbar.() -> Unit = {}): Snackbar { - val snackbar = Snackbar.make(this, text, duration) - snackbar.builder() - snackbar.show() - return snackbar -} - -fun View.snackbar(@StringRes textId: Int, duration: Int = Snackbar.LENGTH_LONG, builder: Snackbar.() -> Unit = {}) - = snackbar(context.string(textId), duration, builder) - -@KauUtils fun TextView.setTextIfValid(@StringRes id: Int) { - if (id > 0) text = context.string(id) -} - -@KauUtils fun ImageView.setIcon(icon: IIcon?, sizeDp: Int = 24, @ColorInt color: Int = Color.WHITE, builder: IconicsDrawable.() -> Unit = {}) { - if (icon == null) return - setImageDrawable(icon.toDrawable(context, sizeDp = sizeDp, color = color, builder = builder)) -} - -@KauUtils fun View.hideKeyboard() { - clearFocus() - (context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager).hideSoftInputFromWindow(windowToken, 0) -} - -@KauUtils fun View.showKeyboard() { - requestFocus() - (context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager).showSoftInput(this, InputMethodManager.SHOW_IMPLICIT) -} - -@KauUtils fun ViewGroup.transitionAuto(builder: AutoTransition.() -> Unit = {}) { - val transition = AutoTransition() - transition.builder() - TransitionManager.beginDelayedTransition(this, transition) -} - -@KauUtils fun ViewGroup.transitionDelayed(@TransitionRes id: Int, builder: Transition.() -> Unit = {}) { - val transition = TransitionInflater.from(context).inflateTransition(id) - transition.builder() - TransitionManager.beginDelayedTransition(this, transition) -} - -@KauUtils fun View.setRippleBackground(@ColorInt foregroundColor: Int, @ColorInt backgroundColor: Int) { - background = createSimpleRippleDrawable(foregroundColor, backgroundColor) -} - -@KauUtils val View.parentViewGroup: ViewGroup - get() = parent as ViewGroup - -@KauUtils val View.parentVisibleHeight: Int - get() { - val r = Rect() - parentViewGroup.getWindowVisibleDisplayFrame(r) - return r.height() - } - -val CIRCULAR_OUTLINE: ViewOutlineProvider = object : ViewOutlineProvider() { - override fun getOutline(view: View, outline: Outline) { - KL.d("CIRCULAR OUTLINE") - outline.setOval(view.paddingLeft, - view.paddingTop, - view.width - view.paddingRight, - view.height - view.paddingBottom) - } -} - -/** - * Generates a recycler view with match parent and a linearlayoutmanager, since it's so commonly used - */ -fun Context.fullLinearRecycler(rvAdapter: RecyclerView.Adapter<*>? = null, configs: RecyclerView.() -> Unit = {}): RecyclerView { - return RecyclerView(this).apply { - layoutManager = LinearLayoutManager(this@fullLinearRecycler) - layoutParams = RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.MATCH_PARENT) - if (rvAdapter != null) adapter = rvAdapter - configs() - } -}
\ No newline at end of file diff --git a/library/src/main/kotlin/ca/allanwang/kau/views/BoundedCardView.kt b/library/src/main/kotlin/ca/allanwang/kau/views/BoundedCardView.kt deleted file mode 100644 index 0cb65d0..0000000 --- a/library/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/library/src/main/kotlin/ca/allanwang/kau/views/CutoutView.kt b/library/src/main/kotlin/ca/allanwang/kau/views/CutoutView.kt deleted file mode 100644 index 023bdb4..0000000 --- a/library/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/library/src/main/kotlin/ca/allanwang/kau/views/RippleCanvas.kt b/library/src/main/kotlin/ca/allanwang/kau/views/RippleCanvas.kt deleted file mode 100644 index 805fb21..0000000 --- a/library/src/main/kotlin/ca/allanwang/kau/views/RippleCanvas.kt +++ /dev/null @@ -1,140 +0,0 @@ -package ca.allanwang.kau.views - -import android.animation.ArgbEvaluator -import android.animation.ValueAnimator -import android.content.Context -import android.graphics.Canvas -import android.graphics.Color -import android.graphics.Paint -import android.util.AttributeSet -import android.view.View -import ca.allanwang.kau.utils.adjustAlpha - -/** - * Created by Allan Wang on 2016-11-17. - * - * - * Canvas drawn ripples that keep the previous color - * Extends to view dimensions - * Supports multiple ripples from varying locations - */ -class RippleCanvas @JvmOverloads constructor( - context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : View(context, attrs, defStyleAttr) { - private val paint: Paint = Paint() - private var baseColor = Color.TRANSPARENT - private val ripples: MutableList<Ripple> = mutableListOf() - - init { - paint.isAntiAlias = true - paint.style = Paint.Style.FILL - } - - /** - * Drawing the ripples involves having access to the next layer if it exists, - * and using its values to decide on the current color. - * If the next layer requests a fade, we will adjust the alpha of our current layer before drawing. - * Otherwise we will just draw the color as intended - */ - override fun onDraw(canvas: Canvas) { - val itr = ripples.listIterator() - if (!itr.hasNext()) return canvas.drawColor(baseColor) - var next = itr.next() - canvas.drawColor(colorToDraw(baseColor, next.fade, next.radius, next.maxRadius)) - var last = false - while (!last) { - val current = next - if (itr.hasNext()) next = itr.next() - else last = true - //We may fade any layer except for the last one - paint.color = colorToDraw(current.color, next.fade && !last, next.radius, next.maxRadius) - canvas.drawCircle(current.x, current.y, current.radius, paint) - if (current.radius == current.maxRadius) { - if (!last) { - itr.previous() - itr.remove() - itr.next() - } else { - itr.remove() - } - baseColor = current.color - } - } - } - - /** - * Given our current color and next layer's radius & max, - * we will decide on the alpha of our current layer - */ - internal fun colorToDraw(color: Int, fade: Boolean, current: Float, goal: Float): Int { - if (!fade || (current / goal <= FADE_PIVOT)) return color - val factor = (goal - current) / (goal - FADE_PIVOT * goal) - return color.adjustAlpha(factor) - } - - /** - * Creates a ripple effect from the given starting values - * [fade] will gradually transition previous ripples to a transparent color so the resulting background is what we want - * this is typically only necessary if the ripple color has transparency - */ - fun ripple(color: Int, startX: Float = 0f, startY: Float = 0f, duration: Long = 600L, fade: Boolean = Color.alpha(color) != 255) { - val w = width.toFloat() - val h = height.toFloat() - val x = when (startX) { - MIDDLE -> w / 2 - END -> w - else -> startX - } - val y = when (startY) { - MIDDLE -> h / 2 - END -> h - else -> startY - } - val maxRadius = Math.hypot(Math.max(x, w - x).toDouble(), Math.max(y, h - y).toDouble()).toFloat() - val ripple = Ripple(color, x, y, 0f, maxRadius, fade) - ripples.add(ripple) - val animator = ValueAnimator.ofFloat(0f, maxRadius) - animator.duration = duration - animator.addUpdateListener { animation -> - ripple.radius = animation.animatedValue as Float - invalidate() - } - animator.start() - } - - /** - * Sets a color directly; clears ripple queue if it exists - */ - fun set(color: Int) { - baseColor = color - ripples.clear() - invalidate() - } - - /** - * Sets a color directly but with a transition - */ - fun fade(color: Int, duration: Long = 300L) { - ripples.clear() - val animator = ValueAnimator.ofObject(ArgbEvaluator(), baseColor, color) - animator.duration = duration - animator.addUpdateListener { animation -> - baseColor = animation.animatedValue as Int - invalidate() - } - animator.start() - } - - internal class Ripple(val color: Int, - val x: Float, - val y: Float, - var radius: Float, - val maxRadius: Float, - val fade: Boolean) - - companion object { - const val MIDDLE = -1.0f - const val END = -2.0f - const val FADE_PIVOT = 0.5f - } -} diff --git a/library/src/main/kotlin/ca/allanwang/kau/views/SimpleRippleDrawable.kt b/library/src/main/kotlin/ca/allanwang/kau/views/SimpleRippleDrawable.kt deleted file mode 100644 index df842f6..0000000 --- a/library/src/main/kotlin/ca/allanwang/kau/views/SimpleRippleDrawable.kt +++ /dev/null @@ -1,19 +0,0 @@ -package ca.allanwang.kau.views - -import android.content.res.ColorStateList -import android.graphics.drawable.ColorDrawable -import android.graphics.drawable.RippleDrawable -import android.support.annotation.ColorInt -import ca.allanwang.kau.utils.adjustAlpha - -/** - * Created by Allan Wang on 2017-06-24. - * - * Tries to mimic a standard ripple, given the foreground and background colors - */ -fun createSimpleRippleDrawable(@ColorInt foregroundColor: Int, @ColorInt backgroundColor: Int): RippleDrawable { - val states = ColorStateList(arrayOf(intArrayOf()), intArrayOf(foregroundColor)) - val content = ColorDrawable(backgroundColor) - val mask = ColorDrawable(foregroundColor.adjustAlpha(0.16f)) - return RippleDrawable(states, content, mask) -}
\ No newline at end of file diff --git a/library/src/main/kotlin/ca/allanwang/kau/widgets/ElasticDragDismissFrameLayout.kt b/library/src/main/kotlin/ca/allanwang/kau/widgets/ElasticDragDismissFrameLayout.kt deleted file mode 100644 index 38c99c3..0000000 --- a/library/src/main/kotlin/ca/allanwang/kau/widgets/ElasticDragDismissFrameLayout.kt +++ /dev/null @@ -1,234 +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 - private var dragDismissDistance = Float.MAX_VALUE - private var dragDismissFraction = -1f - private var dragDismissScale = 1f - private var shouldScale = false - private 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) - shouldScale = dragDismissScale != 1f - 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/library/src/main/kotlin/ca/allanwang/kau/widgets/InkPageIndicator.java b/library/src/main/kotlin/ca/allanwang/kau/widgets/InkPageIndicator.java deleted file mode 100644 index 78e915d..0000000 --- a/library/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/library/src/main/kotlin/ca/allanwang/kau/widgets/TextSlider.kt b/library/src/main/kotlin/ca/allanwang/kau/widgets/TextSlider.kt deleted file mode 100644 index 528dabc..0000000 --- a/library/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 |