From 81996038462de1be86643e95d262933c4b96c551 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Sat, 8 Jul 2017 20:25:23 -0700 Subject: Move components to separate modules --- .../ca/allanwang/kau/about/AboutActivityBase.kt | 248 +++++++++++++++++++++ .../kotlin/ca/allanwang/kau/about/CutoutIItem.kt | 48 ++++ .../kotlin/ca/allanwang/kau/about/LibraryIItem.kt | 99 ++++++++ 3 files changed, 395 insertions(+) create mode 100644 about/src/main/kotlin/ca/allanwang/kau/about/AboutActivityBase.kt create mode 100644 about/src/main/kotlin/ca/allanwang/kau/about/CutoutIItem.kt create mode 100644 about/src/main/kotlin/ca/allanwang/kau/about/LibraryIItem.kt (limited to 'about/src/main/kotlin') diff --git a/about/src/main/kotlin/ca/allanwang/kau/about/AboutActivityBase.kt b/about/src/main/kotlin/ca/allanwang/kau/about/AboutActivityBase.kt new file mode 100644 index 0000000..4751a09 --- /dev/null +++ b/about/src/main/kotlin/ca/allanwang/kau/about/AboutActivityBase.kt @@ -0,0 +1,248 @@ +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.ui.widgets.ElasticDragDismissFrameLayout +import ca.allanwang.kau.ui.widgets.InkPageIndicator +import com.mikepenz.aboutlibraries.Libs +import com.mikepenz.aboutlibraries.entity.Library +import com.mikepenz.fastadapter.IItem +import org.jetbrains.anko.doAsync +import org.jetbrains.anko.uiThread +import java.security.InvalidParameterException + +/** + * Created by Allan Wang on 2017-06-28. + * + * Floating About Activity Panel for your app + * This contains all the necessary layouts, and can be extended and configured using the [configBuilder] + * The [rClass] is necessary to generate the list of libraries used in your app, and should point to your app's + * R.string::class.java + * If you don't need auto detect, you can pass null instead + * Note that for the auto detection to work, the R fields must be excluded from Proguard + * Manual lib listings and other extra modifications can be done so by overriding the open functions + */ +abstract class AboutActivityBase(val rClass: Class<*>?, val configBuilder: Configs.() -> Unit = {}) : AppCompatActivity(), ViewPager.OnPageChangeListener { + + val draggableFrame: ElasticDragDismissFrameLayout by bindView(R.id.about_draggable_frame) + val pager: ViewPager by bindView(R.id.about_pager) + val indicator: InkPageIndicator by bindView(R.id.about_indicator) + /** + * Holds some common configurations that may be added directly from the constructor + * Applied lazily since it needs the context to fetch resources + */ + val configs: Configs by lazy { Configs().apply { configBuilder() } } + /** + * Number of pages in the adapter + * Defaults to just the main view and lib view + */ + open val pageCount: Int = 2 + /** + * Page position for the libs + * This is generated automatically if [inflateLibPage] is called + */ + private var libPage: Int = -2 + /** + * Holds that status of each page + * 0 means nothing has happened + * 1 means this page has been in view at least once + * The rest is up to you + */ + lateinit var pageStatus: IntArray + /** + * Holds the lib items once they are fetched asynchronously + */ + var libItems: List? = null + /** + * Holds the adapter for the library page; this is generated later because it uses the config colors + */ + lateinit var libAdapter: FastItemThemedAdapter> + /** + * Global reference of the library recycler + * This is set by default through [inflateLibPage] and is used to stop scrolling + * When the draggable frame exits + * It is not required, hence its nullability + */ + private var libRecycler: RecyclerView? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.kau_activity_about) + pageStatus = IntArray(pageCount) + libAdapter = FastItemThemedAdapter(configs) + LibraryIItem.bindClickEvents(libAdapter) + if (configs.textColor != null) indicator.setColour(configs.textColor!!) + with(pager) { + adapter = AboutPagerAdapter() + pageMargin = dimenPixelSize(R.dimen.kau_spacing_normal) + addOnPageChangeListener(this@AboutActivityBase) + } + indicator.setViewPager(pager) + draggableFrame.addListener(object : ElasticDragDismissFrameLayout.SystemChromeFader(this) { + override fun onDragDismissed() { + // if we drag dismiss downward then the default reversal of the enter + // transition would slide content upward which looks weird. So reverse it. + if (draggableFrame.translationY > 0) { + window.returnTransition = TransitionInflater.from(this@AboutActivityBase) + .inflateTransition(configs.transitionExitReversed) + } + libRecycler?.stopScroll() + finishAfterTransition() + } + }) + } + + inner class Configs : ThemableIItemColors by ThemableIItemColorsDelegate() { + var cutoutTextRes: Int = -1 + var cutoutText: String? = null + var cutoutDrawableRes: Int = -1 + var cutoutDrawable: Drawable? = null + var cutoutForeground: Int? = null + var libPageTitleRes: Int = -1 + var libPageTitle: String? = string(R.string.kau_about_libraries_intro) //This is in the string by default since it's lower priority + /** + * Transition to be called if the view is dragged down + */ + var transitionExitReversed: Int = R.transition.kau_about_return_downward + } + + /** + * Method to fetch the library list + * This is fetched asynchronously and you may override it to customize the list + */ + open fun getLibraries(libs: Libs): List = 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>(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>) { + + } + + /** + * 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(R.id.kau_recycler_detached) + libRecycler = recycler + recycler.adapter = libAdapter + recycler.itemAnimator = FadeScaleAnimator(itemDelayFactor = 0.2f).apply { addDuration = 300; interpolator = AnimHolder.decelerateInterpolator(this@AboutActivityBase) } + val background = v.findViewById(R.id.kau_recycler_detached_background) + if (configs.backgroundColor != null) background.setBackgroundColor(configs.backgroundColor!!.colorToForeground()) + doAsync { + libItems = getLibraries( + if (rClass == null) Libs(this@AboutActivityBase) else Libs(this@AboutActivityBase, Libs.toStringArray(rClass.fields)) + ).map { LibraryIItem(it) } + if (libPage >= 0 && pageStatus[libPage] == 1) + uiThread { addLibItems() } + } + return v + } + + inner class AboutPagerAdapter : PagerAdapter() { + + private val layoutInflater: LayoutInflater = LayoutInflater.from(this@AboutActivityBase) + private val views = Array(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/about/src/main/kotlin/ca/allanwang/kau/about/CutoutIItem.kt b/about/src/main/kotlin/ca/allanwang/kau/about/CutoutIItem.kt new file mode 100644 index 0000000..34e8641 --- /dev/null +++ b/about/src/main/kotlin/ca/allanwang/kau/about/CutoutIItem.kt @@ -0,0 +1,48 @@ +package ca.allanwang.kau.about + +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.ui.views.CutoutView +import ca.allanwang.kau.utils.bindView +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(), 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?) { + 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/about/src/main/kotlin/ca/allanwang/kau/about/LibraryIItem.kt b/about/src/main/kotlin/ca/allanwang/kau/about/LibraryIItem.kt new file mode 100644 index 0000000..1b832a2 --- /dev/null +++ b/about/src/main/kotlin/ca/allanwang/kau/about/LibraryIItem.kt @@ -0,0 +1,99 @@ +package ca.allanwang.kau.about + +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 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(), ThemableIItem by ThemableIItemDelegate() { + + companion object { + @JvmStatic fun bindClickEvents(fastAdapter: FastAdapter>) { + 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?) { + 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 -- cgit v1.2.3