aboutsummaryrefslogtreecommitdiff
path: root/about/src/main/kotlin/ca/allanwang/kau/about/AboutActivityBase.kt
blob: 19fb4c4ad868e5c1406b5f2358038b7f2f1156d8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
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.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.ui.widgets.ElasticDragDismissFrameLayout
import ca.allanwang.kau.ui.widgets.InkPageIndicator
import ca.allanwang.kau.utils.*
import com.mikepenz.aboutlibraries.Libs
import com.mikepenz.aboutlibraries.entity.Library
import com.mikepenz.fastadapter.IItem
import org.jetbrains.anko.doAsync
import org.jetbrains.anko.uiThread
import java.security.InvalidParameterException

/**
 * Created by Allan Wang on 2017-06-28.
 *
 * Floating About Activity Panel for your app
 * This contains all the necessary layouts, and can be extended and configured using the [configBuilder]
 * The [rClass] is necessary to generate the list of libraries used in your app, and should point to your app's
 * R.string::class.java
 * If you don't need auto detect, you can pass null instead
 * Note that for the auto detection to work, the R fields must be excluded from Proguard
 * Manual lib listings and other extra modifications can be done so by overriding the open functions
 */
abstract class AboutActivityBase(val rClass: Class<*>?, val configBuilder: Configs.() -> Unit = {}) : AppCompatActivity(), ViewPager.OnPageChangeListener {

    val draggableFrame: ElasticDragDismissFrameLayout by bindView(R.id.about_draggable_frame)
    val pager: ViewPager by bindView(R.id.about_pager)
    val indicator: InkPageIndicator by bindView(R.id.about_indicator)
    /**
     * Holds some common configurations that may be added directly from the constructor
     * Applied lazily since it needs the context to fetch resources
     */
    val configs: Configs by lazy { Configs().apply { configBuilder() } }
    /**
     * Number of pages in the adapter
     * Defaults to just the main view and lib view
     */
    open val pageCount: Int = 2
    /**
     * Page position for the libs
     * This is generated automatically if [inflateLibPage] is called
     */
    private var libPage: Int = -2
    /**
     * Holds that status of each page
     * 0 means nothing has happened
     * 1 means this page has been in view at least once
     * The rest is up to you
     */
    lateinit var pageStatus: IntArray
    /**
     * Holds the lib items once they are fetched asynchronously
     */
    var libItems: List<LibraryIItem>? = null
    /**
     * Holds the adapter for the library page; this is generated later because it uses the config colors
     */
    lateinit var libAdapter: FastItemThemedAdapter<IItem<*, *>>
    /**
     * Global reference of the library recycler
     * This is set by default through [inflateLibPage] and is used to stop scrolling
     * When the draggable frame exits
     * It is not required, hence its nullability
     */
    private var libRecycler: RecyclerView? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.kau_activity_about)
        pageStatus = IntArray(pageCount)
        libAdapter = FastItemThemedAdapter(configs)
        LibraryIItem.bindClickEvents(libAdapter)
        if (configs.textColor != null) indicator.setColour(configs.textColor!!)
        with(pager) {
            adapter = AboutPagerAdapter()
            pageMargin = dimenPixelSize(R.dimen.kau_spacing_normal)
            addOnPageChangeListener(this@AboutActivityBase)
        }
        indicator.setViewPager(pager)
        draggableFrame.addListener(object : ElasticDragDismissFrameLayout.SystemChromeFader(this) {
            override fun onDragDismissed() {
                window.returnTransition = TransitionInflater.from(this@AboutActivityBase)
                        .inflateTransition(if (draggableFrame.translationY > 0) configs.transitionExitBottom else configs.transitionExitTop)

                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

        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<Library> = libs.prepareLibraries(this, null, null, true, true)!!

    /**
     * Gets the view associated with the given page position
     * Keep in mind that when inflating, do NOT add the view to the viewgroup
     * Use layoutInflater.inflate(id, parent, false)
     */
    open fun getPage(position: Int, layoutInflater: LayoutInflater, parent: ViewGroup): View {
        return when (position) {
            0 -> inflateMainPage(layoutInflater, parent, position)
            pageCount - 1 -> inflateLibPage(layoutInflater, parent, position)
            else -> throw InvalidParameterException()
        }
    }

    /**
     * Create the main view with the cutout
     */
    open fun inflateMainPage(layoutInflater: LayoutInflater, parent: ViewGroup, position: Int): View {
        val fastAdapter = FastItemThemedAdapter<IItem<*, *>>(configs)
        val recycler = fullLinearRecycler(fastAdapter)
        fastAdapter.add(CutoutIItem {
            with(configs) {
                text = string(cutoutTextRes, cutoutText)
                drawable = drawable(cutoutDrawableRes, cutoutDrawable)
                if (configs.cutoutForeground != null) foregroundColor = configs.cutoutForeground!!
            }
        }.apply {
            themeEnabled = configs.cutoutForeground == null
        })
        postInflateMainPage(fastAdapter)
        return recycler
    }

    /**
     * Open hook called just before the main page view is returned
     * Feel free to add your own items to the adapter in here
     */
    open fun postInflateMainPage(adapter: FastItemThemedAdapter<IItem<*, *>>) {

    }

    /**
     * Create the lib view with the list of libraries
     */
    open fun inflateLibPage(layoutInflater: LayoutInflater, parent: ViewGroup, position: Int): View {
        libPage = position
        val v = layoutInflater.inflate(R.layout.kau_recycler_detached_background, parent, false)
        val recycler = v.findViewById<RecyclerView>(R.id.kau_recycler_detached)
        libRecycler = recycler
        recycler.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<View>(R.id.kau_recycler_detached_background)
        if (configs.backgroundColor != null) background.setBackgroundColor(configs.backgroundColor!!.colorToForeground())
        doAsync {
            libItems = getLibraries(
                    if (rClass == null) Libs(this@AboutActivityBase) else Libs(this@AboutActivityBase, Libs.toStringArray(rClass.fields))
            ).map { LibraryIItem(it) }
            if (libPage >= 0 && pageStatus[libPage] == 1)
                uiThread { addLibItems() }
        }
        return v
    }

    inner class AboutPagerAdapter : PagerAdapter() {

        private val layoutInflater: LayoutInflater = LayoutInflater.from(this@AboutActivityBase)
        private val views = Array<View?>(pageCount) { null }

        override fun instantiateItem(collection: ViewGroup, position: Int): Any {
            val layout = getPage(position, collection)
            collection.addView(layout)
            return layout
        }

        override fun destroyItem(collection: ViewGroup, position: Int, view: Any) {
            collection.removeView(view as View)
            views[position] = null
        }

        override fun getCount(): Int = pageCount

        override fun isViewFromObject(view: View, `object`: Any): Boolean = view === `object`

        /**
         * Only get page if view does not exist
         */
        private fun getPage(position: Int, parent: ViewGroup): View {
            if (views[position] == null) views[position] = getPage(position, layoutInflater, parent)
            return views[position]!!
        }
    }

    override fun onPageScrollStateChanged(state: Int) {}

    override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {}

    override fun onPageSelected(position: Int) {
        if (pageStatus[position] == 0) pageStatus[position] = 1 // mark as seen if previously null
        if (position == libPage && libItems != null && pageStatus[position] == 1) {
            pageStatus[position] = 2            //add libs and mark as such
            postDelayed(300) { addLibItems() }  //delay so that the animations occur once the page is fully switched
        }
    }

    /**
     * Function that is called when the view is ready to add the lib items
     * Feel free to add your own items here
     */
    open fun addLibItems() {
        libAdapter.add(HeaderIItem(text = configs.libPageTitle, textRes = configs.libPageTitleRes))
                .add(libItems)
    }

    override fun onDestroy() {
        AnimHolder.decelerateInterpolator.invalidate() //clear the reference to the interpolators we've used
        super.onDestroy()
    }
}