From bafc1996d803862d30a2c7d0c402d30c79c4f647 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Thu, 3 Aug 2017 15:18:20 -0700 Subject: 3.2.2 - Create faq parser and update sample (#19) * Test emulator * Update readme * Update fastadapter and about listing * Make faq parser asynchronous * Modularize about panels * Add basis for faq * Test and finalize the faq panel * Update readme * Update changelog * Remove emulator for now * Update sample --- .travis.yml | 14 +- README.md | 15 +- about/README.md | 9 +- .../ca/allanwang/kau/about/AboutActivityBase.kt | 184 +++++++------------ .../ca/allanwang/kau/about/AboutPanelDelegate.kt | 198 +++++++++++++++++++++ .../ca/allanwang/kau/about/CollapsibleTextView.kt | 33 ++++ .../kotlin/ca/allanwang/kau/about/CutoutIItem.kt | 12 +- .../main/kotlin/ca/allanwang/kau/about/FaqIItem.kt | 86 +++++++++ .../kotlin/ca/allanwang/kau/about/LibraryIItem.kt | 34 ++-- about/src/main/res/layout/kau_iitem_faq.xml | 37 ++++ about/src/main/res/values/strings.xml | 1 + about/src/main/res/values/strings_about.xml | 2 +- .../ca/allanwang/kau/animators/NoAnimator.kt | 36 ++-- core/README.md | 10 +- .../kotlin/ca/allanwang/kau/xml/FaqTest.kt | 28 +-- .../kau/ui/views/CollapsibleViewDelegate.kt | 105 +++++++++++ .../main/kotlin/ca/allanwang/kau/utils/Const.kt | 7 +- .../kotlin/ca/allanwang/kau/utils/ContextUtils.kt | 9 +- core/src/main/kotlin/ca/allanwang/kau/xml/FAQ.kt | 75 ++++---- core/src/main/res/values/ids.xml | 2 + docs/Changelog.md | 8 +- gradle.properties | 4 +- .../allanwang/kau/kpref/activity/KPrefActivity.kt | 13 +- .../ca/allanwang/kau/sample/AboutActivity.kt | 2 + .../kotlin/ca/allanwang/kau/sample/KPrefSample.kt | 1 + .../kotlin/ca/allanwang/kau/sample/MainActivity.kt | 24 ++- sample/src/main/res/menu/menu_main.xml | 16 +- sample/src/main/res/xml/kau_changelog.xml | 13 +- sample/src/main/res/xml/kau_faq.xml | 14 ++ 29 files changed, 733 insertions(+), 259 deletions(-) create mode 100644 about/src/main/kotlin/ca/allanwang/kau/about/AboutPanelDelegate.kt create mode 100644 about/src/main/kotlin/ca/allanwang/kau/about/CollapsibleTextView.kt create mode 100644 about/src/main/kotlin/ca/allanwang/kau/about/FaqIItem.kt create mode 100644 about/src/main/res/layout/kau_iitem_faq.xml create mode 100644 core/src/main/kotlin/ca/allanwang/kau/ui/views/CollapsibleViewDelegate.kt create mode 100644 sample/src/main/res/xml/kau_faq.xml diff --git a/.travis.yml b/.travis.yml index 6b134ff..c2b02bf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,9 @@ jdk: android: components: - tools +# - android-21 - platform-tools + - tools - build-tools-26.0.1 - android-26 - extra-android-support @@ -12,7 +14,13 @@ android: - extra-google-m2repository licenses: - ".+" +#before_script: +#- echo no | android create avd --force -n test -t android-21 --abi armeabi-v7a +#- emulator -avd test -no-audio -no-window & +#- android-wait-for-emulator +#- adb shell input keyevent 82 & script: +- chmod +x gradlew - ./gradlew --quiet androidGitVersion - if [[ "$TRAVIS_BRANCH" == "master" ]]; then ./gradlew lintRelease publishRelease; else ./gradlew lintRelease test; fi branches: @@ -25,12 +33,12 @@ notifications: - pitchedapps:G5OB9U1vsDxy9mxt0Nt6gbFu#kau on_success: always on_failure: always -sudo: false cache: directories: - "$HOME/.m2" -before_script: -- chmod +x gradlew + - $HOME/.gradle/caches/ + - $HOME/.gradle/wrapper/ + - $HOME/.android/build-cache before_install: - openssl aes-256-cbc -K $encrypted_12e8842891a3_key -iv $encrypted_12e8842891a3_iv -in files/kau.tar.enc -out kau.tar -d diff --git a/README.md b/README.md index 9641ae2..54c3f7e 100644 --- a/README.md +++ b/README.md @@ -43,8 +43,8 @@ dependencies { compile "ca.allanwang.kau:about:$KAU" compile "ca.allanwang.kau:colorpicker:$KAU" - compile "ca.allanwang.kau:mediapicker:$KAU" compile "ca.allanwang.kau:kpref-activity:$KAU" + compile "ca.allanwang.kau:mediapicker:$KAU" compile "ca.allanwang.kau:searchview:$KAU" } @@ -53,9 +53,10 @@ dependencies { ----------- # Submodules -(linked to their respective Docs) +> linked to their respective Docs +> included dependencies are only those with exposed APIs. See [new dependency configurations](https://developer.android.com/studio/build/gradle-plugin-3-0-0-migration.html#new_configurations) -## [Core](core#readme)**** +## [Core](core#readme) * Collection of extension functions and small helper methods applicable in almost any application. * Notable features: KPrefs, Changelog XML, Kotterknife, Ripple Canvas, Delegates, Lazy Resettables, Extensions, Email Builder * Includes @@ -89,16 +90,16 @@ dependencies { * Includes `:core`, [`Material Dialogs (commons)`](https://github.com/afollestad/material-dialogs) +## [Kpref Activity](kpref-activity#readme) +* Fully programmatic implementation of a Preference Activity, backed by RecyclerViews +* Includes `:core-ui`, `:adapter`, `colorpicker` + ## [Media Picker](mediapicker#readme) * Fully functional image and video pickers, both as an overlay and as a requested activity. * Includes `:core-ui`, [`Glide`](https://github.com/bumptech/glide), [`Blurry`](https://github.com/wasabeef/Blurry) -## [Kpref Activity](kpref-activity#readme) -* Fully programmatic implementation of a Preference Activity, backed by RecyclerViews -* Includes `:core-ui`, `:adapter`, `colorpicker` - ## [SearchView](searchview#readme) * Material searchview with kotlin bindings * Includes `:core-ui`, `:adapter` diff --git a/about/README.md b/about/README.md index e7012b5..77a329d 100644 --- a/about/README.md +++ b/about/README.md @@ -11,9 +11,12 @@ and adds on the power of [About Libraries](https://github.com/mikepenz/AboutLibr This activity can be easily added by extending `AboutActivityBase`. Everything is already prepared, but you can modify the theme or other components through the config DSL or through the open functions. -There are also numerous iitem cards already prepared (in this submodule and from `:adapter`)if you wish to add that in your main view. -You may also easily launch the activity through the simple binder: +If you wish to add custom panels, you will need to implement `AboutPanelContract`. +The most common usage is with a recyclerview, so there is a simplified class `AboutPanelRecycler` that you may extend as well. +Note that the viewpager by default will keep all panels in memory, so it's best to not have too many pages with expensive interactions. + +You may easily launch the activity through the binder: ``` Activity.kauLaunchAbout(YourClass::class.java) ``` @@ -22,7 +25,7 @@ Be sure to include the activity in your Manifest and have it extend `Kau.About`, ## Proguard -Without auto detect, KAU about will retain the classes containing the lib strings by default. +Without auto detection, KAU about will retain the classes containing the lib strings by default. If you use proguard with auto detect, make sure to retain all R classes to make it possible ``` diff --git a/about/src/main/kotlin/ca/allanwang/kau/about/AboutActivityBase.kt b/about/src/main/kotlin/ca/allanwang/kau/about/AboutActivityBase.kt index 61a3bb2..442821f 100644 --- a/about/src/main/kotlin/ca/allanwang/kau/about/AboutActivityBase.kt +++ b/about/src/main/kotlin/ca/allanwang/kau/about/AboutActivityBase.kt @@ -4,8 +4,6 @@ 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 @@ -13,19 +11,15 @@ import android.view.ViewGroup import ca.allanwang.kau.adapters.FastItemThemedAdapter import ca.allanwang.kau.adapters.ThemableIItemColors import ca.allanwang.kau.adapters.ThemableIItemColorsDelegate -import ca.allanwang.kau.animators.FadeScaleAnimatorAdd -import ca.allanwang.kau.animators.KauAnimator -import ca.allanwang.kau.iitems.HeaderIItem import ca.allanwang.kau.internal.KauBaseActivity import ca.allanwang.kau.ui.widgets.ElasticDragDismissFrameLayout import ca.allanwang.kau.ui.widgets.InkPageIndicator -import ca.allanwang.kau.utils.* +import ca.allanwang.kau.utils.AnimHolder +import ca.allanwang.kau.utils.bindView +import ca.allanwang.kau.utils.dimenPixelSize 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. @@ -38,26 +32,21 @@ import java.security.InvalidParameterException * 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 = {}) : KauBaseActivity(), ViewPager.OnPageChangeListener { +abstract class AboutActivityBase(val rClass: Class<*>?, private val configBuilder: Configs.() -> Unit = {}) : KauBaseActivity(), ViewPager.OnPageChangeListener { + + private val draggableFrame: ElasticDragDismissFrameLayout by bindView(R.id.about_draggable_frame) + private val pager: ViewPager by bindView(R.id.about_pager) + private val indicator: InkPageIndicator by bindView(R.id.about_indicator) + + val currentPage + get() = pager.currentItem - 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 @@ -65,98 +54,65 @@ abstract class AboutActivityBase(val rClass: Class<*>?, val configBuilder: Confi * 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 + + val panels: List by lazy { + val defaultPanels = mutableListOf(AboutPanelMain(), AboutPanelLibs()) + if (configs.faqXmlRes != -1) defaultPanels.add(AboutPanelFaqs()) + defaultPanels + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.kau_activity_about) - pageStatus = IntArray(pageCount) - libAdapter = FastItemThemedAdapter(configs) - LibraryIItem.bindClickEvents(libAdapter) + pageStatus = IntArray(panels.size) + pageStatus[0] = 2 //the first page is instantly visible if (configs.textColor != null) indicator.setColour(configs.textColor!!) with(pager) { adapter = AboutPagerAdapter() pageMargin = dimenPixelSize(R.dimen.kau_spacing_normal) + offscreenPageLimit = panels.size - 1 addOnPageChangeListener(this@AboutActivityBase) } indicator.setViewPager(pager) draggableFrame.addListener(object : ElasticDragDismissFrameLayout.SystemChromeFader(this) { override fun onDragDismissed() { window.returnTransition = TransitionInflater.from(this@AboutActivityBase) - .inflateTransition(if (draggableFrame.translationY > 0) configs.transitionExitBottom else configs.transitionExitTop) - - libRecycler?.stopScroll() + .inflateTransition(if (draggableFrame.translationY > 0) R.transition.kau_exit_slide_bottom else R.transition.kau_exit_slide_top) + panels[currentPage].recycler?.stopScroll() finishAfterTransition() } }) + panels.forEachIndexed { index, contract -> contract.loadItems(this, index) } } - inner class Configs : ThemableIItemColors by ThemableIItemColorsDelegate() { + 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 - - var transitionExitTop: Int = R.transition.kau_exit_slide_top - var transitionExitBottom: Int = R.transition.kau_exit_slide_bottom - } - - /** - * 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!! + var libPageTitleRes: Int = R.string.kau_about_libraries_intro + var libPageTitle: String? = null + set(value) { + field = value + libPageTitleRes = -1 //reset res so we don't use our default } - }.apply { - themeEnabled = configs.cutoutForeground == null - }) - postInflateMainPage(fastAdapter) - return recycler + var faqXmlRes: Int = -1 + var faqPageTitleRes: Int = R.string.kau_about_faq_intro + var faqPageTitle: String? = null + set(value) { + field = value + faqPageTitleRes = -1 //reset res so we don't use our default + } + /** + * Whether new lines should be included + */ + var faqParseNewLine: Boolean = true } /** + * For [mainPanel] + * * Open hook called just before the main page view is returned * Feel free to add your own items to the adapter in here */ @@ -165,32 +121,23 @@ abstract class AboutActivityBase(val rClass: Class<*>?, val configBuilder: Confi } /** - * Create the lib view with the list of libraries + * For [libPanel] + * + * 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)!! + + /* + * ------------------------------------------------------------------- + * Page 3: FAQ + * ------------------------------------------------------------------- */ - 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.withMarginDecoration(16, KAU_BOTTOM) - recycler.adapter = libAdapter - recycler.itemAnimator = KauAnimator(addAnimator = FadeScaleAnimatorAdd(scaleFactor = 0.7f, itemDelayFactor = 0.2f)).apply { addDuration = 300; interpolator = AnimHolder.decelerateInterpolator(this@AboutActivityBase) } - val background = v.findViewById(R.id.kau_recycler_detached_background) - if (configs.backgroundColor != null) background.setBackgroundColor(configs.backgroundColor!!.colorToForeground()) - doAsync { - 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 inner class AboutPagerAdapter : PagerAdapter() { private val layoutInflater: LayoutInflater = LayoutInflater.from(this@AboutActivityBase) - private val views = Array(pageCount) { null } + private val views = Array(panels.size) { null } override fun instantiateItem(collection: ViewGroup, position: Int): Any { val layout = getPage(position, collection) @@ -203,7 +150,7 @@ abstract class AboutActivityBase(val rClass: Class<*>?, val configBuilder: Confi views[position] = null } - override fun getCount(): Int = pageCount + override fun getCount(): Int = panels.size override fun isViewFromObject(view: View, `object`: Any): Boolean = view === `object` @@ -211,7 +158,8 @@ abstract class AboutActivityBase(val rClass: Class<*>?, val configBuilder: Confi * 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) + if (views[position] == null) views[position] = panels[position] + .inflatePage(this@AboutActivityBase, parent, position) return views[position]!! } } @@ -222,19 +170,7 @@ abstract class AboutActivityBase(val rClass: Class<*>?, val configBuilder: Confi 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) + if (pageStatus[position] == 1) panels[position].addItems(this, position) } override fun onDestroy() { diff --git a/about/src/main/kotlin/ca/allanwang/kau/about/AboutPanelDelegate.kt b/about/src/main/kotlin/ca/allanwang/kau/about/AboutPanelDelegate.kt new file mode 100644 index 0000000..47b9ac4 --- /dev/null +++ b/about/src/main/kotlin/ca/allanwang/kau/about/AboutPanelDelegate.kt @@ -0,0 +1,198 @@ +package ca.allanwang.kau.about + +import android.support.v7.widget.RecyclerView +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import ca.allanwang.kau.adapters.FastItemThemedAdapter +import ca.allanwang.kau.animators.FadeScaleAnimatorAdd +import ca.allanwang.kau.animators.KauAnimator +import ca.allanwang.kau.animators.NoAnimatorChange +import ca.allanwang.kau.iitems.HeaderIItem +import ca.allanwang.kau.utils.* +import ca.allanwang.kau.xml.kauParseFaq +import com.mikepenz.aboutlibraries.Libs +import com.mikepenz.fastadapter.IItem +import org.jetbrains.anko.doAsync +import org.jetbrains.anko.uiThread + +/** + * Created by Allan Wang on 2017-08-02. + * + * The core logic for pages used in [AboutActivityBase] + */ +interface AboutPanelContract { + /** + * Model list to be added to [adapter] + */ + var items: List>? + /** + * The adapter, will be late initialized as it depends on configs + */ + var adapter: FastItemThemedAdapter> + /** + * Reference to the recyclerview, will be used to stop scrolling upon exit + */ + var recycler: RecyclerView? + + /** + * The base inflation method that will be called for new pages from the page adapter + * Keep in mind that when inflating, do NOT add the view to the viewgroup + * Use layoutInflater.inflate(id, parent, false) + */ + fun inflatePage(activity: AboutActivityBase, parent: ViewGroup, position: Int): View + + /** + * Convenience method called during [inflatePage] + * No return value necessary + */ + fun onInflatingPage(activity: AboutActivityBase, recycler: RecyclerView, position: Int) + + /** + * Triggers start of item loading + * Typically called with [inflatePage] + */ + fun loadItems(activity: AboutActivityBase, position: Int) + + /** + * Called when the [adapter] should take in the items + * This typically happens once the user has scroll to the page, + * so they may see a transition + * + * [AboutActivityBase.pageStatus] should be updated accordingly, + * as triggering this does not necessarily mean that the items are added + */ + fun addItems(activity: AboutActivityBase, position: Int) +} + +abstract class AboutPanelRecycler : AboutPanelContract { + + override var items: List>? = null + + override lateinit var adapter: FastItemThemedAdapter> + + override var recycler: RecyclerView? = null + + override fun onInflatingPage(activity: AboutActivityBase, recycler: RecyclerView, position: Int) { + recycler.adapter = adapter + recycler.itemAnimator = KauAnimator( + addAnimator = FadeScaleAnimatorAdd(scaleFactor = 0.7f, itemDelayFactor = 0.2f), + changeAnimator = NoAnimatorChange() + ).apply { addDuration = 300; interpolator = AnimHolder.decelerateInterpolator(recycler.context) } + } + + override fun inflatePage(activity: AboutActivityBase, parent: ViewGroup, position: Int): View { + val v = LayoutInflater.from(activity).inflate(R.layout.kau_recycler_detached_background, parent, false) + adapter = FastItemThemedAdapter(activity.configs) + recycler = v.findViewById(R.id.kau_recycler_detached) + onInflatingPage(activity, recycler!!, position) + val background = v.findViewById(R.id.kau_recycler_detached_background) + if (activity.configs.backgroundColor != null) background.setBackgroundColor(activity.configs.backgroundColor!!.colorToForeground()) + loadItems(activity, position) + return v + } + + override fun addItems(activity: AboutActivityBase, position: Int) { + if (items == null) return + activity.pageStatus[position] = 2 + postDelayed(300) { addItemsImpl(activity, position) } + } + + abstract fun addItemsImpl(activity: AboutActivityBase, position: Int) +} + +/** + * Panel delegate for the main page + * The loading is synchronous, so it is done on inflation + * All other loading and adding methods are overridden to do nothing + * There is a [AboutActivityBase.postInflateMainPage] hook that can be overridden + * to update this panel without extending the whole delegate + */ +open class AboutPanelMain : AboutPanelRecycler() { + + override fun onInflatingPage(activity: AboutActivityBase, recycler: RecyclerView, position: Int) {} + + override fun inflatePage(activity: AboutActivityBase, parent: ViewGroup, position: Int): View { + with(activity) { + adapter = FastItemThemedAdapter>(configs) + recycler = fullLinearRecycler(adapter) + adapter.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(adapter) + return recycler!! + } + } + + override fun loadItems(activity: AboutActivityBase, position: Int) {} + override fun addItems(activity: AboutActivityBase, position: Int) { + activity.pageStatus[position] = 2 + } + + override fun addItemsImpl(activity: AboutActivityBase, position: Int) {} + +} + +/** + * Panel for loading libraries + * There is a [AboutActivityBase.getLibraries] hook that can be overridden + * to customize the libraries listed + */ +open class AboutPanelLibs : AboutPanelRecycler() { + + override fun onInflatingPage(activity: AboutActivityBase, recycler: RecyclerView, position: Int) { + super.onInflatingPage(activity, recycler, position) + recycler.withMarginDecoration(16, KAU_BOTTOM) + LibraryIItem.bindEvents(adapter) + } + + override fun loadItems(activity: AboutActivityBase, position: Int) { + doAsync { + with(activity) { + items = getLibraries(if (rClass == null) Libs(activity) else Libs(this, Libs.toStringArray(rClass.fields))) + .map { LibraryIItem(it) } + if (pageStatus[position] == 1) + uiThread { addItems(activity, position) } + } + } + } + + override fun addItemsImpl(activity: AboutActivityBase, position: Int) { + with(activity.configs) { + adapter.add(HeaderIItem(text = libPageTitle, textRes = libPageTitleRes)) + .add(items) + } + } +} + +open class AboutPanelFaqs : AboutPanelRecycler() { + + override fun onInflatingPage(activity: AboutActivityBase, recycler: RecyclerView, position: Int) { + super.onInflatingPage(activity, recycler, position) + FaqIItem.bindEvents(adapter) + } + + override fun loadItems(activity: AboutActivityBase, position: Int) { + with(activity) { + kauParseFaq(configs.faqXmlRes, configs.faqParseNewLine) { + items = it.map { FaqIItem(it) } + if (pageStatus[position] == 1) + addItems(activity, position) + } + } + } + + override fun addItemsImpl(activity: AboutActivityBase, position: Int) { + with(activity.configs) { + adapter.add(HeaderIItem(text = faqPageTitle, textRes = faqPageTitleRes)) + .add(items) + } + } + +} \ No newline at end of file diff --git a/about/src/main/kotlin/ca/allanwang/kau/about/CollapsibleTextView.kt b/about/src/main/kotlin/ca/allanwang/kau/about/CollapsibleTextView.kt new file mode 100644 index 0000000..32573bc --- /dev/null +++ b/about/src/main/kotlin/ca/allanwang/kau/about/CollapsibleTextView.kt @@ -0,0 +1,33 @@ +package ca.allanwang.kau.about + +import android.content.Context +import android.content.res.Configuration +import android.util.AttributeSet +import android.widget.LinearLayout +import android.widget.TextView +import ca.allanwang.kau.ui.views.CollapsibleView +import ca.allanwang.kau.ui.views.CollapsibleViewDelegate + +/** + * Created by Allan Wang on 2017-08-02. + * + */ +class CollapsibleTextView @JvmOverloads constructor( + context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 +) : TextView(context, attrs, defStyleAttr), CollapsibleView by CollapsibleViewDelegate() { + + init { + initCollapsible(this) + } + + override fun onConfigurationChanged(newConfig: Configuration?) { + resetCollapsibleAnimation() + super.onConfigurationChanged(newConfig) + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + val result = getCollapsibleDimension() + setMeasuredDimension(result.first, result.second) + } +} \ 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 index 1ba5815..3f764d3 100644 --- a/about/src/main/kotlin/ca/allanwang/kau/about/CutoutIItem.kt +++ b/about/src/main/kotlin/ca/allanwang/kau/about/CutoutIItem.kt @@ -4,6 +4,7 @@ import android.support.v7.widget.RecyclerView import android.view.View import ca.allanwang.kau.adapters.ThemableIItem import ca.allanwang.kau.adapters.ThemableIItemDelegate +import ca.allanwang.kau.iitems.KauIItem import ca.allanwang.kau.ui.views.CutoutView import ca.allanwang.kau.utils.bindView import com.mikepenz.fastadapter.items.AbstractItem @@ -13,12 +14,9 @@ import com.mikepenz.fastadapter.items.AbstractItem * * 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 +class CutoutIItem(val config: CutoutView.() -> Unit = {}) : KauIItem( + R.layout.kau_iitem_cutout, {ViewHolder(it)}, R.id.kau_item_cutout +), ThemableIItem by ThemableIItemDelegate() { override fun isSelectable(): Boolean = false @@ -38,8 +36,6 @@ class CutoutIItem(val config: CutoutView.() -> Unit = {} } } - override fun getViewHolder(v: View): ViewHolder = ViewHolder(v) - class ViewHolder(v: View) : RecyclerView.ViewHolder(v) { val cutout: CutoutView by bindView(R.id.kau_cutout) } diff --git a/about/src/main/kotlin/ca/allanwang/kau/about/FaqIItem.kt b/about/src/main/kotlin/ca/allanwang/kau/about/FaqIItem.kt new file mode 100644 index 0000000..5e00447 --- /dev/null +++ b/about/src/main/kotlin/ca/allanwang/kau/about/FaqIItem.kt @@ -0,0 +1,86 @@ +package ca.allanwang.kau.about + +import android.support.v7.widget.RecyclerView +import android.text.method.LinkMovementMethod +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import ca.allanwang.kau.adapters.ThemableIItem +import ca.allanwang.kau.adapters.ThemableIItemDelegate +import ca.allanwang.kau.iitems.KauIItem +import ca.allanwang.kau.utils.bindView +import ca.allanwang.kau.utils.dpToPx +import ca.allanwang.kau.utils.parentViewGroup +import ca.allanwang.kau.utils.setPaddingLeft +import ca.allanwang.kau.xml.FaqItem +import com.mikepenz.fastadapter.FastAdapter +import com.mikepenz.fastadapter.IItem +import com.mikepenz.fastadapter.listeners.ClickEventHook + +/** + * Created by Allan Wang on 2017-08-02. + */ +class FaqIItem(val content: FaqItem) : KauIItem( + R.layout.kau_iitem_faq, { ViewHolder(it) }, R.id.kau_item_faq +), ThemableIItem by ThemableIItemDelegate() { + + companion object { + + + @JvmStatic fun bindEvents(fastAdapter: FastAdapter>) { + fastAdapter.withSelectable(false) + .withEventHook(object : ClickEventHook>() { + + override fun onBind(viewHolder: RecyclerView.ViewHolder): View? + = (viewHolder as? ViewHolder)?.questionContainer + + override fun onClick(v: View, position: Int, adapter: FastAdapter>, item: IItem<*, *>) { + if (item !is FaqIItem) return + item.isExpanded = !item.isExpanded + v.parentViewGroup.findViewById(R.id.faq_item_answer).setExpanded(item.isExpanded) + } + + }) + } + } + + private var isExpanded = false + + override fun bindView(holder: ViewHolder, payloads: MutableList?) { + super.bindView(holder, payloads) + with(holder) { + number.text = "${content.number}." + question.text = content.question + answer.setExpanded(isExpanded, false) + if (accentColor != null) answer.setLinkTextColor(accentColor!!) + answer.text = content.answer + answer.post { answer.setPaddingLeft(16.dpToPx + number.width) } + bindTextColor(number, question) + bindTextColorSecondary(answer) + bindDividerColor(answer) + bindBackgroundRipple(questionContainer) + } + } + + override fun unbindView(holder: ViewHolder) { + super.unbindView(holder) + with(holder) { + number.text = null + question.text = null + answer.text = null + } + } + + class ViewHolder(v: View) : RecyclerView.ViewHolder(v) { + val container: ViewGroup by bindView(R.id.faq_item) + val questionContainer: ViewGroup by bindView(R.id.faq_item_question_container) + val number: TextView by bindView(R.id.faq_item_number) + val question: TextView by bindView(R.id.faq_item_question) + val answer: CollapsibleTextView by bindView(R.id.faq_item_answer) + + init { + answer.movementMethod = LinkMovementMethod.getInstance() + } + } + +} \ 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 index 518cd54..e50460e 100644 --- a/about/src/main/kotlin/ca/allanwang/kau/about/LibraryIItem.kt +++ b/about/src/main/kotlin/ca/allanwang/kau/about/LibraryIItem.kt @@ -8,6 +8,7 @@ import android.view.View import android.widget.TextView import ca.allanwang.kau.adapters.ThemableIItem import ca.allanwang.kau.adapters.ThemableIItemDelegate +import ca.allanwang.kau.iitems.KauIItem import ca.allanwang.kau.utils.bindView import ca.allanwang.kau.utils.gone import ca.allanwang.kau.utils.startLink @@ -15,33 +16,30 @@ 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() { +class LibraryIItem(val lib: Library) : KauIItem( + R.layout.kau_iitem_library, { ViewHolder(it) }, R.id.kau_item_library +), 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) + @JvmStatic fun bindEvents(fastAdapter: FastAdapter>) { + fastAdapter.withSelectable(false) + .withOnClickListener { v, _, item, _ -> + if (item !is LibraryIItem) false + else { + val c = v.context + with(item.lib) { + c.startLink(libraryWebsite, repositoryLink, authorWebsite) + } + true + } } - 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?) { @@ -83,8 +81,6 @@ class LibraryIItem(val lib: Library } } - 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) diff --git a/about/src/main/res/layout/kau_iitem_faq.xml b/about/src/main/res/layout/kau_iitem_faq.xml new file mode 100644 index 0000000..4a0f7fc --- /dev/null +++ b/about/src/main/res/layout/kau_iitem_faq.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/about/src/main/res/values/strings.xml b/about/src/main/res/values/strings.xml index 509b56c..164c0c8 100644 --- a/about/src/main/res/values/strings.xml +++ b/about/src/main/res/values/strings.xml @@ -1,4 +1,5 @@ This app would not be possible without the following great libraries. + FAQ \ No newline at end of file diff --git a/about/src/main/res/values/strings_about.xml b/about/src/main/res/values/strings_about.xml index 8f10d31..8c3dc59 100644 --- a/about/src/main/res/values/strings_about.xml +++ b/about/src/main/res/values/strings_about.xml @@ -1,5 +1,5 @@ - 3.0 + 3.2.3 Allan Wang https://www.allanwang.ca/dev/ diff --git a/adapter/src/main/kotlin/ca/allanwang/kau/animators/NoAnimator.kt b/adapter/src/main/kotlin/ca/allanwang/kau/animators/NoAnimator.kt index 244287b..5ecc937 100644 --- a/adapter/src/main/kotlin/ca/allanwang/kau/animators/NoAnimator.kt +++ b/adapter/src/main/kotlin/ca/allanwang/kau/animators/NoAnimator.kt @@ -1,41 +1,39 @@ package ca.allanwang.kau.animators import android.support.v7.widget.RecyclerView +import android.view.View import android.view.ViewPropertyAnimator /** - * Created by Allan Wang on 2017-06-27. - * - * Truly have no animation + * Created by Allan Wang on 2017-08-02. */ -class NoAnimator : DefaultAnimator() { - override fun addAnimationPrepare(holder: RecyclerView.ViewHolder) {} +class NoAnimatorAdd(override var itemDelayFactor: Float = 0f) : KauAnimatorAdd { - override fun addAnimation(holder: RecyclerView.ViewHolder): ViewPropertyAnimator = holder.itemView.animate().apply { duration = 0 } + override fun animationPrepare(holder: RecyclerView.ViewHolder): View.() -> Unit = {} - override fun addAnimationCleanup(holder: RecyclerView.ViewHolder) {} + override fun animation(holder: RecyclerView.ViewHolder): ViewPropertyAnimator.() -> Unit = {} - override fun changeOldAnimation(holder: RecyclerView.ViewHolder, changeInfo: ChangeInfo): ViewPropertyAnimator = holder.itemView.animate().apply { duration = 0 } + override fun animationCleanup(holder: RecyclerView.ViewHolder): View.() -> Unit = { } - override fun changeNewAnimation(holder: RecyclerView.ViewHolder): ViewPropertyAnimator = holder.itemView.animate().apply { duration = 0 } + override fun getDelay(remove: Long, move: Long, change: Long): Long = 0L - override fun changeAnimationCleanup(holder: RecyclerView.ViewHolder) {} +} - override fun changeAnimation(oldHolder: RecyclerView.ViewHolder, newHolder: RecyclerView.ViewHolder?, fromX: Int, fromY: Int, toX: Int, toY: Int) {} +class NoAnimatorRemove(override var itemDelayFactor: Float = 0f) : KauAnimatorRemove { - override fun getAddDelay(remove: Long, move: Long, change: Long): Long = 0 + override fun animation(holder: RecyclerView.ViewHolder): ViewPropertyAnimator.() -> Unit = { } - override fun getAddDuration(): Long = 0 + override fun animationCleanup(holder: RecyclerView.ViewHolder): View.() -> Unit = {} - override fun getMoveDuration(): Long = 0 + override fun getDelay(remove: Long, move: Long, change: Long): Long = 0L +} - override fun getRemoveDuration(): Long = 0 +class NoAnimatorChange : KauAnimatorChange { - override fun getChangeDuration(): Long = 0 + override fun changeOldAnimation(holder: RecyclerView.ViewHolder, changeInfo: BaseItemAnimator.ChangeInfo): ViewPropertyAnimator.() -> Unit = { } - override fun getRemoveDelay(remove: Long, move: Long, change: Long): Long = 0 + override fun changeNewAnimation(holder: RecyclerView.ViewHolder): ViewPropertyAnimator.() -> Unit = { } - override fun removeAnimation(holder: RecyclerView.ViewHolder): ViewPropertyAnimator = holder.itemView.animate().apply { duration = 0 } + override fun changeAnimationCleanup(holder: RecyclerView.ViewHolder): View.() -> Unit = { alpha = 1f } - override fun removeAnimationCleanup(holder: RecyclerView.ViewHolder) {} } \ No newline at end of file diff --git a/core/README.md b/core/README.md index b952797..385c7ed 100644 --- a/core/README.md +++ b/core/README.md @@ -10,6 +10,7 @@ * [Kotterknife](#kotterknife) * [Ripple Canvas](#ripple-canvas) * [MeasureSpecDelegate](#measure-spec-delegate) +* [CollapsibleViewDelegate](#collapsible-view-delegate) * [Timber Logger](#timber-logger) * [Email Builder](#email-builder) * [Extensions](#extensions) @@ -117,7 +118,7 @@ There is another parser for a FAQ list with the following format: This is an answer ``` -Calling `kauParseFaq` will give you a `List` that you can work with. +Call `kauParseFaq` and pass a callback taking in a `List` that you can work with. By default, the questions are numbered, and the content is formatted with HTML. You may still need to add your own methods to allow interaction with certain elements such as links. @@ -150,6 +151,13 @@ If you ever have a view needing exact aspect ratios with its parent and/or itsel Implementing this in any view class unlocks its attributes, giving you three layers of view measuring to ensure exact sizing. More information can be found in the [klass file](https://github.com/AllanWang/KAU/blob/master/core/src/main/kotlin/ca/allanwang/kau/ui/views/MeasureSpecDelegate.kt) +< a name="collapsible-view-delegate"> +## Collapsible View Delegate + +A common animation is having a view that can smoothly enter and exit by changing its height. +This delegate will implement everything for you and give you the methods `expand`, `collapse`, etc. +See the [kclass file](https://github.com/AllanWang/KAU/blob/master/core/src/main/kotlin/ca/allanwang/kau/ui/views/CollapsibleViewDelegate.kt) for more details. + ## Timber Logger diff --git a/core/src/androidTest/kotlin/ca/allanwang/kau/xml/FaqTest.kt b/core/src/androidTest/kotlin/ca/allanwang/kau/xml/FaqTest.kt index 94d1330..1b185f3 100644 --- a/core/src/androidTest/kotlin/ca/allanwang/kau/xml/FaqTest.kt +++ b/core/src/androidTest/kotlin/ca/allanwang/kau/xml/FaqTest.kt @@ -17,22 +17,26 @@ class FaqTest { @Test fun simpleTest() { - val data = InstrumentationRegistry.getTargetContext().kauParseFaq(R.xml.test_faq) - assertEquals(2, data.size, "FAQ size is incorrect") - assertEquals("1. This is a question", data.first().first.toString(), "First question does not match") - assertEquals("This is an answer", data.first().second.toString(), "First answer does not match") - assertEquals("2. This is another question", data.last().first.toString(), "Second question does not match") - assertEquals("This is another answer", data.last().second.toString(), "Second answer does not match") + InstrumentationRegistry.getTargetContext().kauParseFaq(R.xml.test_faq) { + data -> + assertEquals(2, data.size, "FAQ size is incorrect") + assertEquals("1. This is a question", data.first().first.toString(), "First question does not match") + assertEquals("This is an answer", data.first().second.toString(), "First answer does not match") + assertEquals("2. This is another question", data.last().first.toString(), "Second question does not match") + assertEquals("This is another answer", data.last().second.toString(), "Second answer does not match") + } } @Test fun withoutNumbering() { - val data = InstrumentationRegistry.getTargetContext().kauParseFaq(R.xml.test_faq, false) - assertEquals(2, data.size, "FAQ size is incorrect") - assertEquals("This is a question", data.first().first.toString(), "First question does not match") - assertEquals("This is an answer", data.first().second.toString(), "First answer does not match") - assertEquals("This is another question", data.last().first.toString(), "Second question does not match") - assertEquals("This is another answer", data.last().second.toString(), "Second answer does not match") + InstrumentationRegistry.getTargetContext().kauParseFaq(R.xml.test_faq, false) { + data -> + assertEquals(2, data.size, "FAQ size is incorrect") + assertEquals("This is a question", data.first().first.toString(), "First question does not match") + assertEquals("This is an answer", data.first().second.toString(), "First answer does not match") + assertEquals("This is another question", data.last().first.toString(), "Second question does not match") + assertEquals("This is another answer", data.last().second.toString(), "Second answer does not match") + } } } \ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/ui/views/CollapsibleViewDelegate.kt b/core/src/main/kotlin/ca/allanwang/kau/ui/views/CollapsibleViewDelegate.kt new file mode 100644 index 0000000..6994ca2 --- /dev/null +++ b/core/src/main/kotlin/ca/allanwang/kau/ui/views/CollapsibleViewDelegate.kt @@ -0,0 +1,105 @@ +package ca.allanwang.kau.ui.views + +import android.animation.ValueAnimator +import android.view.View +import ca.allanwang.kau.utils.* +import java.lang.ref.WeakReference + +/** + * Created by Allan Wang on 2017-08-03. + * + * Delegation class for collapsible views + * + * Views that implement this MUST call [initCollapsible] before using any of the methods + * Additionally, you will need to call [getCollapsibleDimension] and use the response for + * [View.setMeasuredDimension] during [View.onMeasure] + * (That method is protected so we cannot access it here) + * + * With reference to ExpandableLayout + */ +interface CollapsibleView { + var expansion: Float + val state: Int + val expanded: Boolean + fun initCollapsible(view: View) + fun resetCollapsibleAnimation() + fun getCollapsibleDimension(): Pair + fun toggleExpansion() + fun toggleExpansion(animate: Boolean) + fun expand() + fun expand(animate: Boolean) + fun collapse() + fun collapse(animate: Boolean) + fun setExpanded(expand: Boolean) + fun setExpanded(expand: Boolean, animate: Boolean) +} + +class CollapsibleViewDelegate : CollapsibleView { + + private lateinit var viewRef: WeakReference + private val view + get() = viewRef.get() + private var animator: ValueAnimator? = null + + override var expansion = 0f + set(value) { + if (value == field) return + var v = value + if (v > 1) v = 1f + else if (v < 0) v = 0f + stateHolder = + if (v == 0f) KAU_COLLAPSED + else if (v == 1f) KAU_EXPANDED + else if (v - field < 0) KAU_COLLAPSING + else KAU_EXPANDING + field = v + view?.goneIf(state == KAU_COLLAPSED) + view?.requestLayout() + } + + private var stateHolder = KAU_COLLAPSED + override val state + get() = stateHolder + override val expanded + get() = stateHolder == KAU_EXPANDED || stateHolder == KAU_EXPANDING + + override fun initCollapsible(view: View) { + this.viewRef = WeakReference(view) + } + + override fun resetCollapsibleAnimation() { + animator?.cancel() + animator = null + if (stateHolder == KAU_COLLAPSING) stateHolder = KAU_COLLAPSED + else if (stateHolder == KAU_EXPANDING) stateHolder = KAU_EXPANDED + } + + override fun getCollapsibleDimension(): Pair { + val v = view ?: return Pair(0, 0) + val size = v.measuredHeight + v.goneIf(expansion == 0f && size == 0) + return Pair(v.measuredWidth, Math.round(size * expansion)) + } + + private fun animateSize(target: Float) { + resetCollapsibleAnimation() + animator = ValueAnimator.ofFloat(expansion, target).apply { + addUpdateListener { expansion = it.animatedValue as Float } + start() + } + } + + override fun toggleExpansion() = toggleExpansion(true) + override fun toggleExpansion(animate: Boolean) = setExpanded(!expanded, animate) + override fun expand() = expand(true) + override fun expand(animate: Boolean) = setExpanded(true, animate) + override fun collapse() = collapse(true) + override fun collapse(animate: Boolean) = setExpanded(false, animate) + override fun setExpanded(expand: Boolean) = setExpanded(expand, true) + override fun setExpanded(expand: Boolean, animate: Boolean) { + if (expand == expanded) return //state already matches + val target = if (expand) 1f else 0f + if (animate) animateSize(target) else expansion = target + } + +} \ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/Const.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/Const.kt index 3e90926..dad01f1 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/utils/Const.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/utils/Const.kt @@ -11,4 +11,9 @@ const val KAU_RIGHT = 4 const val KAU_BOTTOM = 8 const val KAU_HORIZONTAL = KAU_LEFT or KAU_RIGHT const val KAU_VERTICAL = KAU_TOP or KAU_BOTTOM -const val KAU_ALL = KAU_HORIZONTAL or KAU_VERTICAL \ No newline at end of file +const val KAU_ALL = KAU_HORIZONTAL or KAU_VERTICAL + +const val KAU_COLLAPSED = 0 +const val KAU_COLLAPSING = 1 +const val KAU_EXPANDING = 2 +const val KAU_EXPANDED = 3 \ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/ContextUtils.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/ContextUtils.kt index 2219b5d..20cec73 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/utils/ContextUtils.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/utils/ContextUtils.kt @@ -1,3 +1,5 @@ +@file:Suppress("NOTHING_TO_INLINE") + package ca.allanwang.kau.utils import android.annotation.SuppressLint @@ -89,9 +91,12 @@ fun Context.startLink(vararg url: String?) { } //Toast helpers -fun Context.toast(@StringRes id: Int, duration: Int = Toast.LENGTH_LONG) = toast(this.string(id), duration) +inline fun View.toast(@StringRes id: Int, duration: Int = Toast.LENGTH_LONG) = context.toast(id, duration) + +inline 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) { +inline fun View.toast(text: String, duration: Int = Toast.LENGTH_LONG) = context.toast(text, duration) +inline fun Context.toast(text: String, duration: Int = Toast.LENGTH_LONG) { Toast.makeText(this, text, duration).show() } diff --git a/core/src/main/kotlin/ca/allanwang/kau/xml/FAQ.kt b/core/src/main/kotlin/ca/allanwang/kau/xml/FAQ.kt index b39540c..07a0287 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/xml/FAQ.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/xml/FAQ.kt @@ -6,6 +6,8 @@ import android.support.annotation.XmlRes import android.text.Html import android.text.Spanned import ca.allanwang.kau.utils.use +import org.jetbrains.anko.doAsync +import org.jetbrains.anko.uiThread import org.xmlpull.v1.XmlPullParser /** @@ -13,42 +15,51 @@ import org.xmlpull.v1.XmlPullParser */ /** - * Parse an xml with two tags, Text and Text, - * and return them as a list of string pairs + * Parse an xml asynchronously with two tags, Text and Text, + * and invoke the [callback] on the ui thread */ -fun Context.kauParseFaq(@XmlRes xmlRes: Int, withNumbering: Boolean = true): List> { - val items = mutableListOf>() - resources.getXml(xmlRes).use { - parser: XmlResourceParser -> - var eventType = parser.eventType - var question: Spanned? = null - var flag = -1 //-1, 0, 1 -> invalid, question, answer - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - flag = when (parser.name) { - "question" -> 0 - "answer" -> 1 - else -> -1 - } - } else if (eventType == XmlPullParser.TEXT) { - when (flag) { - 0 -> { - var q = parser.text.replace("\n", "
") - if (withNumbering) q = "${items.size + 1}. $q" - question = Html.fromHtml(q) - flag = -1 +@Suppress("DEPRECATION") +fun Context.kauParseFaq( + @XmlRes xmlRes: Int, + /** + * If \n is used, it will automatically be converted to
+ */ + parseNewLine: Boolean = true, + callback: (items: List) -> Unit) { + doAsync { + val items = mutableListOf() + resources.getXml(xmlRes).use { + parser: XmlResourceParser -> + var eventType = parser.eventType + var question: Spanned? = null + var flag = -1 //-1, 0, 1 -> invalid, question, answer + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + flag = when (parser.name) { + "question" -> 0 + "answer" -> 1 + else -> -1 } - 1 -> { - items.add(Pair(question ?: throw IllegalArgumentException("KAU FAQ answer found without a question"), - Html.fromHtml(parser.text.replace("\n", "
")))) - question = null - flag = -1 + } else if (eventType == XmlPullParser.TEXT) { + when (flag) { + 0 -> { + question = Html.fromHtml(parser.text.replace("\n", if (parseNewLine) "
" else "")) + flag = -1 + } + 1 -> { + items.add(FaqItem(items.size + 1, + question ?: throw IllegalArgumentException("KAU FAQ answer found without a question"), + Html.fromHtml(parser.text.replace("\n", if (parseNewLine) "
" else "")))) + question = null + flag = -1 + } } } + eventType = parser.next() } - - eventType = parser.next() } + uiThread { callback(items) } } - return items -} \ No newline at end of file +} + +data class FaqItem(val number: Int, val question: Spanned, val answer: Spanned) \ No newline at end of file diff --git a/core/src/main/res/values/ids.xml b/core/src/main/res/values/ids.xml index 003e8a7..c4912e2 100644 --- a/core/src/main/res/values/ids.xml +++ b/core/src/main/res/values/ids.xml @@ -6,6 +6,8 @@ + + diff --git a/docs/Changelog.md b/docs/Changelog.md index 64d4644..e27936d 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -1,9 +1,15 @@ # Changelog +## v3.2.3 +* :about: Modularize everything +* :about: Create faq panel +* :core: Create faq parser +* :core: Create collapsible view delegate +* :mediapicker: Allow for prefetching by default for videos + ## v3.2.2 * :core: Add simple KauBaseActivity so that activities extending AppCompatActivity can have some default kau helpers implemented * :core: The permission manager will now notify you if you try to request a permission that isn\'t added to your manifest -* :core: Create faq parser * Begin writing android tests ## v3.2.1 diff --git a/gradle.properties b/gradle.properties index 5fa8125..004ae9b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -28,8 +28,8 @@ ABOUT_LIBRARIES=5.9.7 ANKO=0.10.1 BLURRY=2.1.1 CONSTRAINT_LAYOUT=1.1.0-beta1 -FAST_ADAPTER=2.6.2 -FAST_ADAPTER_COMMONS=2.6.0 +FAST_ADAPTER=2.6.3 +FAST_ADAPTER_COMMONS=2.6.3 GLIDE=4.0.0 ICONICS=2.9.0 IICON_GOOGLE=3.0.1.1 diff --git a/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/KPrefActivity.kt b/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/KPrefActivity.kt index 4324fd3..3e1596f 100644 --- a/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/KPrefActivity.kt +++ b/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/KPrefActivity.kt @@ -59,13 +59,12 @@ abstract class KPrefActivity : KauBaseActivity(), KPrefActivityContract { //setup layout setContentView(R.layout.kau_pref_activity) setSupportActionBar(toolbar) - if (supportActionBar != null) - with(supportActionBar!!) { - setDisplayHomeAsUpEnabled(true) - setDisplayShowHomeEnabled(true) - toolbar.setNavigationOnClickListener { onBackPressed() } - setDisplayShowTitleEnabled(false) - } + supportActionBar?.apply { + 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)) diff --git a/sample/src/main/kotlin/ca/allanwang/kau/sample/AboutActivity.kt b/sample/src/main/kotlin/ca/allanwang/kau/sample/AboutActivity.kt index 983544d..78f31ae 100644 --- a/sample/src/main/kotlin/ca/allanwang/kau/sample/AboutActivity.kt +++ b/sample/src/main/kotlin/ca/allanwang/kau/sample/AboutActivity.kt @@ -14,6 +14,8 @@ class AboutActivity : AboutActivityBase(R.string::class.java, { backgroundColor = 0xfffafafa.toInt() accentColor = 0xff00838F.toInt() cutoutForeground = 0xff18FFFF.toInt() + faqXmlRes = R.xml.kau_faq + faqParseNewLine = false }) { override fun postInflateMainPage(adapter: FastItemThemedAdapter>) { diff --git a/sample/src/main/kotlin/ca/allanwang/kau/sample/KPrefSample.kt b/sample/src/main/kotlin/ca/allanwang/kau/sample/KPrefSample.kt index fc4277f..9adabed 100644 --- a/sample/src/main/kotlin/ca/allanwang/kau/sample/KPrefSample.kt +++ b/sample/src/main/kotlin/ca/allanwang/kau/sample/KPrefSample.kt @@ -8,6 +8,7 @@ import ca.allanwang.kau.kpref.kpref * Created by Allan Wang on 2017-06-07. */ object KPrefSample : KPref() { + var version: Int by kpref("version", -1) var textColor: Int by kpref("TEXT_COLOR", Color.WHITE) var accentColor: Int by kpref("ACCENT_COLOR", 0xffff8900.toInt()) var bgColor: Int by kpref("BG_COLOR", 0xff303030.toInt()) diff --git a/sample/src/main/kotlin/ca/allanwang/kau/sample/MainActivity.kt b/sample/src/main/kotlin/ca/allanwang/kau/sample/MainActivity.kt index 4c9107c..62ca8a3 100644 --- a/sample/src/main/kotlin/ca/allanwang/kau/sample/MainActivity.kt +++ b/sample/src/main/kotlin/ca/allanwang/kau/sample/MainActivity.kt @@ -6,12 +6,11 @@ import android.view.Menu import android.view.MenuItem import ca.allanwang.kau.about.kauLaunchAbout import ca.allanwang.kau.email.sendEmail - -import ca.allanwang.kau.mediapicker.kauLaunchMediaPicker -import ca.allanwang.kau.mediapicker.kauOnMediaPickerResult import ca.allanwang.kau.kpref.activity.CoreAttributeContract import ca.allanwang.kau.kpref.activity.KPrefActivity import ca.allanwang.kau.kpref.activity.KPrefAdapterBuilder +import ca.allanwang.kau.mediapicker.kauLaunchMediaPicker +import ca.allanwang.kau.mediapicker.kauOnMediaPickerResult import ca.allanwang.kau.searchview.SearchItem import ca.allanwang.kau.searchview.SearchView import ca.allanwang.kau.searchview.bindSearchView @@ -20,6 +19,7 @@ import ca.allanwang.kau.utils.materialDialog import ca.allanwang.kau.utils.navigationBarColor import ca.allanwang.kau.utils.startActivity import ca.allanwang.kau.utils.toast +import ca.allanwang.kau.xml.showChangelog import com.mikepenz.google_material_typeface_library.GoogleMaterial @@ -206,7 +206,18 @@ class MainActivity : KPrefActivity() { bgCanvas.set(KPrefSample.bgColor) toolbarCanvas.set(KPrefSample.accentColor) this.navigationBarColor = KPrefSample.accentColor - + if (KPrefSample.version < BuildConfig.VERSION_CODE) { + KPrefSample.version = BuildConfig.VERSION_CODE + showChangelog(R.xml.kau_changelog, KPrefSample.textColor) { + titleColor(KPrefSample.textColor) + backgroundColor(KPrefSample.bgColor) + positiveColor(KPrefSample.accentColor) + } + } + supportActionBar?.apply { + setDisplayHomeAsUpEnabled(false) + setDisplayShowHomeEnabled(false) + } } override fun onCreateOptionsMenu(menu: Menu): Boolean { @@ -237,6 +248,11 @@ class MainActivity : KPrefActivity() { override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { + R.id.action_changelog -> showChangelog(R.xml.kau_changelog, KPrefSample.textColor) { + titleColor(KPrefSample.textColor) + backgroundColor(KPrefSample.bgColor) + positiveColor(KPrefSample.accentColor) + } R.id.action_settings -> startActivity(AnimActivity::class.java) R.id.action_email -> sendEmail(R.string.your_email, R.string.your_subject) else -> return super.onOptionsItemSelected(item) diff --git a/sample/src/main/res/menu/menu_main.xml b/sample/src/main/res/menu/menu_main.xml index 039c2c9..f8933f3 100644 --- a/sample/src/main/res/menu/menu_main.xml +++ b/sample/src/main/res/menu/menu_main.xml @@ -1,13 +1,19 @@ + tools:context="ca.allanwang.kau.sample.MainActivity"> + app:showAsAction="always" /> + + - - diff --git a/sample/src/main/res/xml/kau_changelog.xml b/sample/src/main/res/xml/kau_changelog.xml index db6a504..df6ec2b 100644 --- a/sample/src/main/res/xml/kau_changelog.xml +++ b/sample/src/main/res/xml/kau_changelog.xml @@ -6,14 +6,17 @@ --> - - - + + + + + + + + - - diff --git a/sample/src/main/res/xml/kau_faq.xml b/sample/src/main/res/xml/kau_faq.xml new file mode 100644 index 0000000..5230a31 --- /dev/null +++ b/sample/src/main/res/xml/kau_faq.xml @@ -0,0 +1,14 @@ + + + + This is a FAQ question + This is a FAQ answer + HTML tags]]> + questions and answers are automatically parsed with HTML]]> + Links + +
  • Github
  • +
  • Page
  • +
  • Play Store
  • + ]]> + \ No newline at end of file -- cgit v1.2.3