/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package com.pitchedapps.frost.views
import android.content.Context
import android.os.Build
import android.util.AttributeSet
import android.view.View
import android.widget.FrameLayout
import android.widget.ProgressBar
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import ca.allanwang.kau.utils.bindView
import ca.allanwang.kau.utils.circularReveal
import ca.allanwang.kau.utils.fadeIn
import ca.allanwang.kau.utils.fadeOut
import ca.allanwang.kau.utils.invisibleIf
import ca.allanwang.kau.utils.isVisible
import ca.allanwang.kau.utils.tint
import ca.allanwang.kau.utils.withAlpha
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.facebook.WEB_LOAD_DELAY
import com.pitchedapps.frost.utils.L
import com.pitchedapps.frost.utils.Prefs
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable
import io.reactivex.rxkotlin.addTo
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(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(context, attrs, defStyleAttr, defStyleRes) {
override val layoutRes: Int = R.layout.view_content_base_recycler
}
abstract class FrostContentView @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 = PublishSubject.create()
override val refreshObservable: PublishSubject = PublishSubject.create()
override val titleObservable: BehaviorSubject = BehaviorSubject.create()
private val compositeDisposable = CompositeDisposable()
override lateinit var baseUrl: String
override var baseEnum: FbItem? = null
protected abstract val layoutRes: Int
override var swipeEnabled = true
set(value) {
if (field == value)
return
field = value
refresh.post { refresh.isEnabled = value }
}
/**
* 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
}.addTo(compositeDisposable)
refreshObservable
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
refresh.isRefreshing = it
refresh.isEnabled = true
}.addTo(compositeDisposable)
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()
compositeDisposable.dispose()
}
private var dispose: Disposable? = null
private var transitionStart: Long = -1
/**
* 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(urlChanged: Boolean, animate: Boolean): Boolean {
if (!urlChanged && dispose != null) {
L.v { "Consuming url load" }
return false // still in progress; do not bother with load
}
L.v { "Registered transition" }
with(coreView) {
var loading = dispose != null
dispose?.dispose()
dispose = refreshObservable
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
if (it) {
loading = true
transitionStart = System.currentTimeMillis()
clearAnimation()
if (isVisible)
fadeOut(duration = 200L)
} else if (loading) {
loading = false
if (animate && Prefs.animate) circularReveal(offset = WEB_LOAD_DELAY)
else fadeIn(duration = 200L, offset = WEB_LOAD_DELAY)
L.v { "Transition loaded in ${System.currentTimeMillis() - transitionStart} ms" }
dispose?.dispose()
dispose = null
}
}
}
return true
}
}