aboutsummaryrefslogtreecommitdiff
path: root/app/src/main/kotlin/com/pitchedapps/frost/views
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/kotlin/com/pitchedapps/frost/views')
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/views/AccountItem.kt2
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/views/FrostContentView.kt140
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/views/FrostRecyclerView.kt103
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/views/FrostWebView.kt144
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/views/Keywords.kt2
5 files changed, 389 insertions, 2 deletions
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/views/AccountItem.kt b/app/src/main/kotlin/com/pitchedapps/frost/views/AccountItem.kt
index 4b6c9e4e..2ab1d572 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/views/AccountItem.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/views/AccountItem.kt
@@ -25,7 +25,7 @@ import com.pitchedapps.frost.utils.withRoundIcon
class AccountItem(val cookie: CookieModel?) : KauIItem<AccountItem, AccountItem.ViewHolder>
(R.layout.view_account, { ViewHolder(it) }, R.id.item_account) {
- override fun bindView(viewHolder: ViewHolder, payloads: List<Any>?) {
+ override fun bindView(viewHolder: ViewHolder, payloads: MutableList<Any>) {
super.bindView(viewHolder, payloads)
with(viewHolder) {
text.invisible()
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostContentView.kt b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostContentView.kt
new file mode 100644
index 00000000..58449de3
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostContentView.kt
@@ -0,0 +1,140 @@
+package com.pitchedapps.frost.views
+
+import android.content.Context
+import android.os.Build
+import android.support.v4.widget.SwipeRefreshLayout
+import android.util.AttributeSet
+import android.view.View
+import android.widget.FrameLayout
+import android.widget.ProgressBar
+import ca.allanwang.kau.utils.*
+import com.pitchedapps.frost.R
+import com.pitchedapps.frost.contracts.FrostContentContainer
+import com.pitchedapps.frost.contracts.FrostContentCore
+import com.pitchedapps.frost.contracts.FrostContentParent
+import com.pitchedapps.frost.facebook.FbItem
+import com.pitchedapps.frost.utils.Prefs
+import com.pitchedapps.frost.web.WEB_LOAD_DELAY
+import io.reactivex.android.schedulers.AndroidSchedulers
+import io.reactivex.disposables.Disposable
+import io.reactivex.subjects.BehaviorSubject
+import io.reactivex.subjects.PublishSubject
+
+class FrostContentWeb @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, defStyleRes: Int = 0
+) : FrostContentView<FrostWebView>(context, attrs, defStyleAttr, defStyleRes) {
+
+ override val layoutRes: Int = R.layout.view_content_base_web
+
+}
+
+class FrostContentRecycler @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, defStyleRes: Int = 0
+) : FrostContentView<FrostRecyclerView>(context, attrs, defStyleAttr, defStyleRes) {
+
+ override val layoutRes: Int = R.layout.view_content_base_recycler
+
+}
+
+abstract class FrostContentView<out T> @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, defStyleRes: Int = 0
+) : FrameLayout(context, attrs, defStyleAttr, defStyleRes),
+ FrostContentParent where T : View, T : FrostContentCore {
+
+ private val refresh: SwipeRefreshLayout by bindView(R.id.content_refresh)
+ private val progress: ProgressBar by bindView(R.id.content_progress)
+ val coreView: T by bindView(R.id.content_core)
+
+ override val core: FrostContentCore
+ get() = coreView
+
+ override val progressObservable: PublishSubject<Int> = PublishSubject.create()
+ override val refreshObservable: PublishSubject<Boolean> = PublishSubject.create()
+ override val titleObservable: BehaviorSubject<String> = BehaviorSubject.create()
+
+ override lateinit var baseUrl: String
+ override var baseEnum: FbItem? = null
+
+ protected abstract val layoutRes: Int
+
+ /**
+ * Sets up everything
+ * Called by [bind]
+ */
+ protected fun init() {
+ inflate(context, layoutRes, this)
+ coreView.parent = this
+
+ // bind observables
+ progressObservable.observeOn(AndroidSchedulers.mainThread()).subscribe {
+ progress.invisibleIf(it == 100)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
+ progress.setProgress(it, true)
+ else
+ progress.progress = it
+ }
+ refreshObservable.observeOn(AndroidSchedulers.mainThread()).subscribe {
+ refresh.isRefreshing = it
+ refresh.isEnabled = true
+ }
+ refresh.setOnRefreshListener { coreView.reload(true) }
+
+ reloadThemeSelf()
+
+ }
+
+ override fun bind(container: FrostContentContainer) {
+ baseUrl = container.baseUrl
+ baseEnum = container.baseEnum
+ init()
+ core.bind(container)
+ }
+
+ override fun reloadTheme() {
+ reloadThemeSelf()
+ coreView.reloadTheme()
+ }
+
+ override fun reloadTextSize() {
+ coreView.reloadTextSize()
+ }
+
+ override fun reloadThemeSelf() {
+ progress.tint(Prefs.textColor.withAlpha(180))
+ refresh.setColorSchemeColors(Prefs.iconColor)
+ refresh.setProgressBackgroundColorSchemeColor(Prefs.headerColor.withAlpha(255))
+ }
+
+ override fun reloadTextSizeSelf() {
+ // intentionally blank
+ }
+
+ override fun destroy() {
+ titleObservable.onComplete()
+ progressObservable.onComplete()
+ refreshObservable.onComplete()
+ core.destroy()
+ }
+
+ /**
+ * Hook onto the refresh observable for one cycle
+ * Animate toggles between the fancy ripple and the basic fade
+ * The cycle only starts on the first load since there may have been another process when this is registered
+ */
+ override fun registerTransition(animate: Boolean) {
+ with(coreView) {
+ var dispose: Disposable? = null
+ var loading = false
+ dispose = refreshObservable.subscribeOn(AndroidSchedulers.mainThread()).subscribe {
+ if (it) {
+ loading = true
+ if (isVisible) fadeOut(duration = 200L)
+ } else if (loading) {
+ dispose?.dispose()
+ if (animate && Prefs.animate) circularReveal(offset = WEB_LOAD_DELAY)
+ else fadeIn(duration = 100L)
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostRecyclerView.kt b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostRecyclerView.kt
new file mode 100644
index 00000000..436f8b00
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostRecyclerView.kt
@@ -0,0 +1,103 @@
+package com.pitchedapps.frost.views
+
+import android.content.Context
+import android.support.v7.widget.RecyclerView
+import android.util.AttributeSet
+import android.view.View
+import com.pitchedapps.frost.contracts.FrostContentContainer
+import com.pitchedapps.frost.contracts.FrostContentCore
+import com.pitchedapps.frost.contracts.FrostContentParent
+import com.pitchedapps.frost.fragments.RecyclerContentContract
+import com.pitchedapps.frost.utils.L
+import java.lang.ref.WeakReference
+
+/**
+ * Created by Allan Wang on 2017-05-29.
+ *
+ */
+class FrostRecyclerView @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
+) : RecyclerView(context, attrs, defStyleAttr),
+ FrostContentCore {
+
+ override fun reload(animate: Boolean) = reloadBase(animate)
+
+ override lateinit var parent: FrostContentParent
+
+ override val currentUrl: String
+ get() = parent.baseUrl
+
+ lateinit var recyclerContract: WeakReference<RecyclerContentContract>
+
+ override fun bind(container: FrostContentContainer): View {
+ if (container !is RecyclerContentContract)
+ throw IllegalStateException("FrostRecyclerView must bind to a container that is a RecyclerContentContract")
+ this.recyclerContract = WeakReference(container)
+ container.bind(this)
+ return this
+ }
+
+ init {
+ isNestedScrollingEnabled = true
+ }
+
+ override fun reloadBase(animate: Boolean) {
+ val contract = recyclerContract.get()
+ if (contract == null) {
+ L.eThrow("Attempted to reload with invalid contract")
+ return
+ }
+ contract.reload({ parent.progressObservable.onNext(it) }) {
+ parent.progressObservable.onNext(100)
+ parent.refreshObservable.onNext(false)
+ }
+ }
+
+ override fun clearHistory() {
+ // intentionally blank
+ }
+
+ override fun destroy() {
+ // todo see if any
+ }
+
+ override fun onBackPressed() = false
+
+ /**
+ * If webview is already at the top, refresh
+ * Otherwise scroll to top
+ */
+ override fun onTabClicked() {
+ if (scrollY < 5) reloadBase(true)
+ else scrollToTop()
+ }
+
+ private fun scrollToTop() {
+ stopScroll()
+ smoothScrollToPosition(0)
+ }
+
+ override var active: Boolean = true
+ set(value) {
+ if (field == value) return
+ field = value
+ // todo
+ }
+
+ override fun reloadTheme() {
+ reloadThemeSelf()
+ }
+
+ override fun reloadThemeSelf() {
+ reload(false) // todo see if there's a better solution
+ }
+
+ override fun reloadTextSize() {
+ reloadTextSizeSelf()
+ }
+
+ override fun reloadTextSizeSelf() {
+ // todo
+ }
+
+} \ No newline at end of file
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostWebView.kt b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostWebView.kt
new file mode 100644
index 00000000..e6e1f0e2
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostWebView.kt
@@ -0,0 +1,144 @@
+package com.pitchedapps.frost.views
+
+import android.animation.ValueAnimator
+import android.annotation.SuppressLint
+import android.content.Context
+import android.graphics.Color
+import android.util.AttributeSet
+import android.view.View
+import android.view.animation.DecelerateInterpolator
+import com.pitchedapps.frost.contracts.FrostContentContainer
+import com.pitchedapps.frost.contracts.FrostContentCore
+import com.pitchedapps.frost.contracts.FrostContentParent
+import com.pitchedapps.frost.facebook.USER_AGENT_BASIC
+import com.pitchedapps.frost.fragments.WebFragment
+import com.pitchedapps.frost.utils.Prefs
+import com.pitchedapps.frost.utils.frostDownload
+import com.pitchedapps.frost.web.*
+
+/**
+ * Created by Allan Wang on 2017-05-29.
+ *
+ */
+class FrostWebView @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
+) : NestedWebView(context, attrs, defStyleAttr),
+ FrostContentCore {
+
+ override fun reload(animate: Boolean) {
+ parent.registerTransition(animate)
+ super.reload()
+ }
+
+ override lateinit var parent: FrostContentParent
+
+ internal lateinit var frostWebClient: FrostWebViewClient
+
+ override val currentUrl: String
+ get() = url ?: ""
+
+ @SuppressLint("SetJavaScriptEnabled")
+ override fun bind(container: FrostContentContainer): View {
+ with(settings) {
+ javaScriptEnabled = true
+ if (parent.baseUrl.shouldUseBasicAgent)
+ userAgentString = USER_AGENT_BASIC
+ allowFileAccess = true
+ textZoom = Prefs.webTextScaling
+ }
+ setLayerType(LAYER_TYPE_HARDWARE, null)
+ // attempt to get custom client; otherwise fallback to original
+ frostWebClient = (container as? WebFragment)?.client(this) ?: FrostWebViewClient(this)
+ webViewClient = frostWebClient
+ webChromeClient = FrostChromeClient(this)
+ addJavascriptInterface(FrostJSI(this), "Frost")
+ setBackgroundColor(Color.TRANSPARENT)
+ setDownloadListener(context::frostDownload)
+ return this
+ }
+
+
+ /**
+ * Wrapper to the main userAgentString to cache it.
+ * This decouples it from the UiThread
+ *
+ * Note that this defaults to null, but the main purpose is to
+ * check if we've set our own agent.
+ *
+ * A null value may be interpreted as the default value
+ */
+ var userAgentString: String? = null
+ set(value) {
+ field = value
+ settings.userAgentString = value
+ }
+
+ init {
+ isNestedScrollingEnabled = true
+ }
+
+ fun loadUrl(url: String?, animate: Boolean) {
+ if (url == null) return
+ parent.registerTransition(animate)
+ super.loadUrl(url)
+ }
+
+ override fun reloadBase(animate: Boolean) {
+ loadUrl(parent.baseUrl, animate)
+ }
+
+ override fun onBackPressed(): Boolean {
+ if (canGoBack()) {
+ goBack()
+ return true
+ }
+ return false
+ }
+
+ /**
+ * If webview is already at the top, refresh
+ * Otherwise scroll to top
+ */
+ override fun onTabClicked() {
+ if (scrollY < 5) reloadBase(true)
+ else scrollToTop()
+ }
+
+ private fun scrollToTop() {
+ flingScroll(0, 0) // stop fling
+ if (scrollY > 10000) {
+ scrollTo(0, 0)
+ } else {
+ ValueAnimator.ofInt(scrollY, 0).apply {
+ duration = Math.min(scrollY, 500).toLong()
+ interpolator = DecelerateInterpolator()
+ addUpdateListener { scrollY = it.animatedValue as Int }
+ start()
+ }
+ }
+ }
+
+ override var active: Boolean = true
+ set(value) {
+ if (field == value) return
+ field = value
+ // todo
+ }
+
+ override fun reloadTheme() {
+ reloadThemeSelf()
+ }
+
+ override fun reloadThemeSelf() {
+ reload(false) // todo see if there's a better solution
+ }
+
+ override fun reloadTextSize() {
+ reloadTextSizeSelf()
+ }
+
+ override fun reloadTextSizeSelf() {
+ settings.textZoom = Prefs.webTextScaling
+ }
+
+} \ No newline at end of file
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/views/Keywords.kt b/app/src/main/kotlin/com/pitchedapps/frost/views/Keywords.kt
index 25079834..9edd671b 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/views/Keywords.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/views/Keywords.kt
@@ -79,7 +79,7 @@ class KeywordItem(val keyword: String) : AbstractItem<KeywordItem, KeywordItem.V
override fun getLayoutRes(): Int = R.layout.item_keyword
- override fun bindView(holder: ViewHolder, payloads: MutableList<Any>?) {
+ override fun bindView(holder: ViewHolder, payloads: MutableList<Any>) {
super.bindView(holder, payloads)
holder.text.text = keyword
}