From 266569a14f3b1dea9002de58cef40b4149384ef6 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Thu, 1 Jun 2017 17:45:42 -0700 Subject: add progressbar and remove tab indicator --- .../kotlin/com/pitchedapps/frost/MainActivity.kt | 35 ++++- .../com/pitchedapps/frost/WebOverlayActivity.kt | 29 +++- .../com/pitchedapps/frost/events/WebEvent.kt | 4 +- .../com/pitchedapps/frost/fragments/WebFragment.kt | 55 ++----- .../com/pitchedapps/frost/utils/IIconUtils.kt | 12 +- .../com/pitchedapps/frost/utils/Interfaces.kt | 2 +- .../com/pitchedapps/frost/views/AnimUtils.kt | 107 +++++++++++++ .../com/pitchedapps/frost/views/ViewUtils.kt | 18 ++- .../com/pitchedapps/frost/web/FrostChromeClient.kt | 16 +- .../com/pitchedapps/frost/web/FrostWebEnums.kt | 13 ++ .../com/pitchedapps/frost/web/FrostWebView.kt | 167 ++++++--------------- .../pitchedapps/frost/web/FrostWebViewClient.kt | 45 ++++-- .../com/pitchedapps/frost/web/FrostWebViewCore.kt | 137 +++++++++++++++++ .../com/pitchedapps/frost/web/SwipeRefreshBase.kt | 30 ---- 14 files changed, 443 insertions(+), 227 deletions(-) create mode 100644 app/src/main/kotlin/com/pitchedapps/frost/views/AnimUtils.kt create mode 100644 app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebEnums.kt create mode 100644 app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewCore.kt delete mode 100644 app/src/main/kotlin/com/pitchedapps/frost/web/SwipeRefreshBase.kt (limited to 'app/src/main/kotlin/com') diff --git a/app/src/main/kotlin/com/pitchedapps/frost/MainActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/MainActivity.kt index 2cf8cd5b..c167dfdb 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/MainActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/MainActivity.kt @@ -11,6 +11,7 @@ import android.support.v7.app.AppCompatActivity import android.support.v7.widget.Toolbar import android.view.Menu import android.view.MenuItem +import android.view.ViewTreeObserver import butterknife.ButterKnife import com.mikepenz.materialdrawer.Drawer import com.pitchedapps.frost.dbflow.loadFbTabs @@ -18,9 +19,7 @@ import com.pitchedapps.frost.dbflow.saveAsync import com.pitchedapps.frost.facebook.FbTab import com.pitchedapps.frost.fragments.BaseFragment import com.pitchedapps.frost.fragments.WebFragment -import com.pitchedapps.frost.utils.Changelog -import com.pitchedapps.frost.utils.bindView -import com.pitchedapps.frost.utils.toDrawable +import com.pitchedapps.frost.utils.* class MainActivity : AppCompatActivity() { @@ -33,6 +32,8 @@ class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) +// SwipeBackHelper.onCreate(this) +// SwipeBackHelper.getCurrentPage(this).setSwipeBackEnable(false) setContentView(R.layout.activity_main) ButterKnife.bind(this) setSupportActionBar(toolbar) @@ -40,6 +41,20 @@ class MainActivity : AppCompatActivity() { adapter = SectionsPagerAdapter(supportFragmentManager, loadFbTabs()) viewPager.adapter = adapter viewPager.offscreenPageLimit = 5 + viewPager.addOnPageChangeListener(object : ViewPager.SimpleOnPageChangeListener() { + override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) { + super.onPageScrolled(position, positionOffset, positionOffsetPixels) + val delta: Float by lazy { positionOffset * (255 - 128).toFloat() } + (0 until tabs.tabCount).asSequence().forEach { + i -> + tabs.getTabAt(i)?.icon?.alpha = when (i) { + position -> (255.0 - delta).toInt() + position + 1 -> (128.0 + delta).toInt() + else -> 128 + } + } + } + }) setupTabs() fab.setOnClickListener { view -> Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) @@ -47,6 +62,17 @@ class MainActivity : AppCompatActivity() { } } + + override fun onPostCreate(savedInstanceState: Bundle?) { + super.onPostCreate(savedInstanceState) +// SwipeBackHelper.onPostCreate(this) + } + + override fun onDestroy() { + super.onDestroy() +// SwipeBackHelper.onDestroy(this) + } + fun setupTabs() { viewPager.addOnPageChangeListener(TabLayout.TabLayoutOnPageChangeListener(tabs)) @@ -72,8 +98,7 @@ class MainActivity : AppCompatActivity() { // finish() } R.id.action_changelog -> Changelog.show(this) - R.id.action_call -> { - } + R.id.action_call -> WebOverlayActivity.newInstance(this, "https://www.google.ca") R.id.action_db -> adapter.pages.saveAsync(this) R.id.action_restart -> { finish(); diff --git a/app/src/main/kotlin/com/pitchedapps/frost/WebOverlayActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/WebOverlayActivity.kt index 25c20594..fafcf7ae 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/WebOverlayActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/WebOverlayActivity.kt @@ -3,12 +3,17 @@ package com.pitchedapps.frost import android.content.Context import android.content.Intent import android.os.Bundle +import android.support.v4.app.ActivityOptionsCompat +import android.support.v4.content.ContextCompat import android.support.v4.widget.SwipeRefreshLayout import android.support.v7.app.AppCompatActivity import android.support.v7.widget.Toolbar import butterknife.ButterKnife +import com.jude.swipbackhelper.SwipeBackHelper import com.pitchedapps.frost.utils.bindView import com.pitchedapps.frost.web.FrostWebView +import com.pitchedapps.frost.web.FrostWebViewCore + /** * Created by Allan Wang on 2017-06-01. @@ -16,15 +21,15 @@ import com.pitchedapps.frost.web.FrostWebView class WebOverlayActivity : AppCompatActivity() { val toolbar: Toolbar by bindView(R.id.toolbar) - val refresh: SwipeRefreshLayout by bindView(R.id.swipe_refresh) - val web: FrostWebView by bindView(R.id.frost_webview) + val frostWeb:FrostWebView by bindView(R.id.frost_webview) companion object { private const val ARG_URL = "arg_url" fun newInstance(context: Context, url: String) { val intent = Intent(context, WebOverlayActivity::class.java) intent.putExtra(ARG_URL, url) - context.startActivity(intent) + val bundle = ActivityOptionsCompat.makeCustomAnimation(context, R.anim.slide_in_right, R.anim.slide_out_right).toBundle() + ContextCompat.startActivity(context, intent, bundle) } } @@ -33,5 +38,23 @@ class WebOverlayActivity : AppCompatActivity() { setContentView(R.layout.activity_web_overlay) ButterKnife.bind(this) setSupportActionBar(toolbar) + frostWeb.baseUrl = intent.extras.getString(ARG_URL) + SwipeBackHelper.onCreate(this) + SwipeBackHelper.getCurrentPage(this) + .setSwipeBackEnable(true) + .setSwipeSensitivity(0.5f) + .setSwipeRelateEnable(true) + .setSwipeRelateOffset(300) + } + + override fun onPostCreate(savedInstanceState: Bundle?) { + super.onPostCreate(savedInstanceState) + SwipeBackHelper.onPostCreate(this) } + + override fun onDestroy() { + super.onDestroy() + SwipeBackHelper.onDestroy(this) + } + } \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/events/WebEvent.kt b/app/src/main/kotlin/com/pitchedapps/frost/events/WebEvent.kt index 64c91955..a2eb6907 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/events/WebEvent.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/events/WebEvent.kt @@ -1,6 +1,6 @@ package com.pitchedapps.frost.events -import com.pitchedapps.frost.web.FrostWebView +import com.pitchedapps.frost.web.FrostWebViewCore /** * Created by Allan Wang on 2017-05-31. @@ -12,7 +12,7 @@ class WebEvent(val key: Int, val urlMatch: String? = null) { const val REFRESH_BASE = 1 } - fun execute(webView: FrostWebView) { + fun execute(webView: FrostWebViewCore) { if (urlMatch != null && !webView.url.contains(urlMatch)) return when (key) { REFRESH -> webView.reload() diff --git a/app/src/main/kotlin/com/pitchedapps/frost/fragments/WebFragment.kt b/app/src/main/kotlin/com/pitchedapps/frost/fragments/WebFragment.kt index f55579e0..21d019e7 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/fragments/WebFragment.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/fragments/WebFragment.kt @@ -5,34 +5,26 @@ import android.support.v4.widget.SwipeRefreshLayout import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import butterknife.ButterKnife -import butterknife.Unbinder -import com.pitchedapps.frost.R -import com.pitchedapps.frost.utils.bindView import com.pitchedapps.frost.utils.putString import com.pitchedapps.frost.web.FrostWebView -import com.pitchedapps.frost.web.WebStatus +import com.pitchedapps.frost.web.FrostWebViewCore /** * Created by Allan Wang on 2017-05-29. */ -class WebFragment : BaseFragment(), SwipeRefreshLayout.OnRefreshListener { - - override fun onRefresh() { - web.reload() - } +class WebFragment : BaseFragment() { companion object { private const val ARG_URL = "arg_url" fun newInstance(position: Int, url: String) = BaseFragment.newInstance(WebFragment(), position).putString(ARG_URL, url) } - val refresh: SwipeRefreshLayout by bindView(R.id.swipe_refresh) - val web: FrostWebView by bindView(R.id.frost_webview) + val refresh: SwipeRefreshLayout by lazy { frostWebView.refresh } + val web: FrostWebViewCore by lazy { frostWebView.web } lateinit var url: String - private lateinit var unbinder: Unbinder + lateinit private var frostWebView: FrostWebView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -41,39 +33,10 @@ class WebFragment : BaseFragment(), SwipeRefreshLayout.OnRefreshListener { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { super.onCreateView(inflater, container, savedInstanceState) - val view = inflater.inflate(R.layout.swipe_webview, container, false) - unbinder = ButterKnife.bind(this, view) - return view - } - - override fun onViewCreated(view: View?, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - web.baseUrl = url - web.observable.subscribe { - t: WebStatus -> - when (t) { - WebStatus.LOADED, WebStatus.ERROR -> refresh.isRefreshing = false - WebStatus.LOADING -> refresh.isRefreshing = true - } - } - refresh.setOnRefreshListener(this) -// refresh.shouldSwipe = { -// L.e("Y ${web.scrollY}") -// SwipeRefreshBase.shouldScroll(web) -// } - web.loadUrl(url) + frostWebView = FrostWebView(context) + frostWebView.baseUrl = url + return frostWebView } - override fun onDestroyView() { - super.onDestroyView() - unbinder.unbind() - } - - override fun onBackPressed(): Boolean { - if (web.canGoBack()) { - web.goBack() - return true - } - return false - } + override fun onBackPressed() = frostWebView.onBackPressed() } \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/IIconUtils.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/IIconUtils.kt index cc645d93..2e0a44c1 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/utils/IIconUtils.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/IIconUtils.kt @@ -1,13 +1,19 @@ package com.pitchedapps.frost.utils import android.content.Context +import android.content.res.ColorStateList +import android.graphics.Color import android.graphics.drawable.Drawable -import android.support.annotation.ColorRes +import android.support.annotation.ColorInt import com.mikepenz.iconics.IconicsDrawable import com.mikepenz.iconics.typeface.IIcon /** * Created by Allan Wang on 2017-05-29. */ -fun IIcon.toDrawable(c: Context, sizeDp: Int = 24, @ColorRes color: Int = android.R.color.white): Drawable - = IconicsDrawable(c).icon(this).colorRes(color).sizeDp(sizeDp) \ No newline at end of file +fun IIcon.toDrawable(c: Context, sizeDp: Int = 24, @ColorInt color: Int = Color.WHITE): Drawable { + val state = ColorStateList.valueOf(color) + val icon = IconicsDrawable(c).icon(this).sizeDp(sizeDp) + icon.setTintList(state) + return icon +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/Interfaces.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/Interfaces.kt index 79904c6e..1e42f59f 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/utils/Interfaces.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Interfaces.kt @@ -6,7 +6,7 @@ import io.reactivex.subjects.Subject * Created by Allan Wang on 2017-05-29. */ interface ObservableContainer { - val observable: Subject + val progressObservable: Subject } interface KeyPairObservable : ObservableContainer> \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/views/AnimUtils.kt b/app/src/main/kotlin/com/pitchedapps/frost/views/AnimUtils.kt new file mode 100644 index 00000000..a2a0dcff --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/views/AnimUtils.kt @@ -0,0 +1,107 @@ +package com.pitchedapps.frost.views + +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.view.View +import android.view.ViewAnimationUtils +import android.view.animation.Animation +import android.view.animation.AnimationUtils +import android.view.animation.DecelerateInterpolator + + +/** + * Created by Allan Wang on 2017-06-01. + */ + +fun View.rootCircularReveal(x: Int = 0, y: Int = 0, duration: Long = 500L, onStart: (() -> Unit)? = null, onFinish: (() -> Unit)? = null) { + this.addOnLayoutChangeListener(object : View.OnLayoutChangeListener { + override fun onLayoutChange(v: View, left: Int, top: Int, right: Int, bottom: Int, oldLeft: Int, oldTop: Int, + oldRight: Int, oldBottom: Int) { + v.removeOnLayoutChangeListener(this) + var x2 = x + var y2 = y + if (x2 > right) x2 = 0 + if (y2 > bottom) y2 = 0 + val radius = Math.hypot(Math.max(x2, right - x2).toDouble(), Math.max(y2, bottom - y2).toDouble()).toInt() + val reveal = ViewAnimationUtils.createCircularReveal(v, x2, y2, 0f, radius.toFloat()) + reveal.interpolator = DecelerateInterpolator(1f) + reveal.duration = duration + reveal.addListener(object : AnimatorListenerAdapter() { + override fun onAnimationStart(animation: Animator?) { + visibility = View.VISIBLE + onStart?.invoke() + } + + override fun onAnimationEnd(animation: Animator?) = onFinish?.invoke() ?: Unit + override fun onAnimationCancel(animation: Animator?) = onFinish?.invoke() ?: Unit + }) + reveal.start() + } + }) +} + +fun View.circularReveal(x: Int = 0, y: Int = 0, offset: Long = 0L, radius: Float = -1.0f, duration: Long = 500L, onStart: (() -> Unit)? = null, onFinish: (() -> Unit)? = null) { + if (!isAttachedToWindow) { + visibility = View.VISIBLE + return + } + var r = radius + if (r < 0.0f) { + r = Math.max(Math.hypot(x.toDouble(), y.toDouble()), Math.hypot((width - x.toDouble()), (height - y.toDouble()))).toFloat() + } + val anim = ViewAnimationUtils.createCircularReveal(this, x, y, 0f, r).setDuration(duration) + anim.startDelay = offset + anim.addListener(object : AnimatorListenerAdapter() { + override fun onAnimationStart(animation: Animator?) { + visibility = View.VISIBLE + onStart?.invoke() + } + + override fun onAnimationEnd(animation: Animator?) = onFinish?.invoke() ?: Unit + override fun onAnimationCancel(animation: Animator?) = onFinish?.invoke() ?: Unit + }) + anim.start() +} + +fun View.fadeIn(offset: Long = 0L, duration: Long = 200L, onStart: (() -> Unit)? = null, onFinish: (() -> Unit)? = null) { + if (!isAttachedToWindow) { + visibility = View.VISIBLE + return + } + if (isAttachedToWindow) { + val anim = AnimationUtils.loadAnimation(context, android.R.anim.fade_in) + anim.startOffset = offset + anim.duration = duration + anim.setAnimationListener(object : Animation.AnimationListener { + override fun onAnimationRepeat(animation: Animation?) {} + override fun onAnimationEnd(animation: Animation?) = onFinish?.invoke() ?: Unit + override fun onAnimationStart(animation: Animation?) { + visibility = View.VISIBLE + onStart?.invoke() + } + }) + startAnimation(anim) + } +} + +fun View.fadeOut(offset: Long = 0L, duration: Long = 200L, onStart: (() -> Unit)? = null, onFinish: (() -> Unit)? = null) { + if (!isAttachedToWindow) { + visibility = View.INVISIBLE + return + } + val anim = AnimationUtils.loadAnimation(context, android.R.anim.fade_out) + anim.startOffset = offset + anim.duration = duration + anim.setAnimationListener(object : Animation.AnimationListener { + override fun onAnimationRepeat(animation: Animation?) {} + override fun onAnimationEnd(animation: Animation?) { + visibility = View.INVISIBLE + onFinish?.invoke() + } + + override fun onAnimationStart(animation: Animation?) { + onStart?.invoke() + } + }) + startAnimation(anim) +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/views/ViewUtils.kt b/app/src/main/kotlin/com/pitchedapps/frost/views/ViewUtils.kt index 513287ef..01a49f7e 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/views/ViewUtils.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/views/ViewUtils.kt @@ -1,14 +1,30 @@ package com.pitchedapps.frost.views +import android.content.res.ColorStateList +import android.graphics.PorterDuff +import android.support.annotation.ColorInt +import android.support.annotation.ColorRes +import android.support.v4.content.ContextCompat import android.view.View import android.view.ViewGroup +import android.widget.ProgressBar + /** * Created by Allan Wang on 2017-05-31. */ fun View.matchParent() { - with (layoutParams) { + with(layoutParams) { height = ViewGroup.LayoutParams.MATCH_PARENT width = ViewGroup.LayoutParams.MATCH_PARENT } +} + +fun ProgressBar.tintRes(@ColorRes id: Int) = tint(ContextCompat.getColor(context, id)) + +fun ProgressBar.tint(@ColorInt color: Int) { + val sl = ColorStateList.valueOf(color) + progressTintList = sl + secondaryProgressTintList = sl + indeterminateTintList = sl } \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClient.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClient.kt index 8367255a..4cd90d7b 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClient.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClient.kt @@ -2,14 +2,28 @@ package com.pitchedapps.frost.web import android.webkit.ConsoleMessage import android.webkit.WebChromeClient +import android.webkit.WebView import com.pitchedapps.frost.utils.L +import io.reactivex.subjects.Subject /** * Created by Allan Wang on 2017-05-31. */ -class FrostChromeClient:WebChromeClient() { +class FrostChromeClient(val progressObservable: Subject) : WebChromeClient() { override fun onConsoleMessage(consoleMessage: ConsoleMessage): Boolean { L.d("Console ${consoleMessage.lineNumber()}: ${consoleMessage.message()}") return super.onConsoleMessage(consoleMessage) } + + override fun onReceivedTitle(view: WebView?, title: String?) { + super.onReceivedTitle(view, title) + L.d("Title $title") + } + + override fun onProgressChanged(view: WebView, newProgress: Int) { + super.onProgressChanged(view, newProgress) + progressObservable.onNext(newProgress) + } + + } \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebEnums.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebEnums.kt new file mode 100644 index 00000000..17e44785 --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebEnums.kt @@ -0,0 +1,13 @@ +package com.pitchedapps.frost.web + +enum class FrostWebEnums { + LOADING, LOADED, ERROR +} + +enum class FrostWebOverlay(val match: String) { + STORY("story.php?story_fbid"); + + companion object { + val values = values() + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebView.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebView.kt index de7860da..a9cf0559 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebView.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebView.kt @@ -1,141 +1,62 @@ package com.pitchedapps.frost.web -import android.annotation.SuppressLint import android.content.Context -import android.support.v4.view.MotionEventCompat -import android.support.v4.view.NestedScrollingChild -import android.support.v4.view.NestedScrollingChildHelper -import android.support.v4.view.ViewCompat +import android.os.Build +import android.support.v4.widget.SwipeRefreshLayout import android.util.AttributeSet -import android.view.MotionEvent import android.view.View -import android.webkit.WebView -import com.pitchedapps.frost.events.WebEvent -import com.pitchedapps.frost.utils.ObservableContainer -import io.reactivex.subjects.BehaviorSubject -import io.reactivex.subjects.Subject -import org.greenrobot.eventbus.EventBus -import org.greenrobot.eventbus.Subscribe - -enum class WebStatus { - LOADING, LOADED, ERROR -} +import android.widget.FrameLayout +import android.widget.ProgressBar +import butterknife.ButterKnife +import com.pitchedapps.frost.R +import com.pitchedapps.frost.utils.L +import com.pitchedapps.frost.utils.bindView +import io.reactivex.android.schedulers.AndroidSchedulers /** - * Created by Allan Wang on 2017-05-29. - * - * Courtesy of takahirom - * - * https://github.com/takahirom/webview-in-coordinatorlayout/blob/master/app/src/main/java/com/github/takahirom/webview_in_coodinator_layout/NestedWebView.java + * Created by Allan Wang on 2017-06-01. */ -class FrostWebView @JvmOverloads constructor( - context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : WebView(context, attrs, defStyleAttr), NestedScrollingChild, ObservableContainer { - - private val childHelper = NestedScrollingChildHelper(this) - private var lastY: Int = 0 - private val scrollOffset = IntArray(2) - private val scrollConsumed = IntArray(2) - private var nestedOffsetY: Int = 0 - override val observable: Subject //TODO see if we need this - var baseUrl: String? = null +class FrostWebView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, defStyleRes: Int = 0) + : FrameLayout(context, attrs, defStyleAttr, defStyleRes), SwipeRefreshLayout.OnRefreshListener { + var baseUrl: String? + get() = web.baseUrl + set(value) { + web.baseUrl = value + if (value != null) web.loadUrl(value) + } + val refresh: SwipeRefreshLayout by bindView(R.id.swipe_refresh) + val web: FrostWebViewCore by bindView(R.id.frost_webview_core) + val progress: ProgressBar by bindView(R.id.progressBar) init { - isNestedScrollingEnabled = true - observable = BehaviorSubject.create() - setupWebview() - } - - @SuppressLint("SetJavaScriptEnabled") - fun setupWebview() { - settings.javaScriptEnabled = true - settings.domStorageEnabled = true - setLayerType(View.LAYER_TYPE_HARDWARE, null) - setWebViewClient(FrostWebViewClient(observable)) - setWebChromeClient(FrostChromeClient()) - } - - override fun loadUrl(url: String?) { - if (url != null) - super.loadUrl(url) - } - - override fun onTouchEvent(ev: MotionEvent): Boolean { - val event = MotionEvent.obtain(ev) - val action = MotionEventCompat.getActionMasked(event) - if (action == MotionEvent.ACTION_DOWN) - nestedOffsetY = 0 - val eventY = event.y.toInt() - event.offsetLocation(0f, nestedOffsetY.toFloat()) - val returnValue: Boolean - when (action) { - MotionEvent.ACTION_MOVE -> { - var deltaY = lastY - eventY - // NestedPreScroll - if (dispatchNestedPreScroll(0, deltaY, scrollConsumed, scrollOffset)) { - deltaY -= scrollConsumed[1] - event.offsetLocation(0f, -scrollOffset[1].toFloat()) - nestedOffsetY += scrollOffset[1] - } - lastY = eventY - scrollOffset[1] - returnValue = super.onTouchEvent(event) - // NestedScroll - if (dispatchNestedScroll(0, scrollOffset[1], 0, deltaY, scrollOffset)) { - event.offsetLocation(0f, scrollOffset[1].toFloat()) - nestedOffsetY += scrollOffset[1] - lastY -= scrollOffset[1] - } - } - MotionEvent.ACTION_DOWN -> { - returnValue = super.onTouchEvent(event) - lastY = eventY - startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL) - } - MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { - returnValue = super.onTouchEvent(event) - stopNestedScroll() - } - else -> return false + inflate(getContext(), R.layout.swipe_webview, this) + ButterKnife.bind(this) + web.progressObservable.observeOn(AndroidSchedulers.mainThread()).subscribe { + val loaded = it == 100 + refresh.isRefreshing = !loaded + progress.visibility = if (loaded) View.INVISIBLE else View.VISIBLE + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) progress.setProgress(it, true) + else progress.progress = it } - return returnValue - } + refresh.setOnRefreshListener(this) + addOnAttachStateChangeListener(object : OnAttachStateChangeListener { + override fun onViewDetachedFromWindow(v: View) { + web.visibility = View.VISIBLE + } - override fun onAttachedToWindow() { - super.onAttachedToWindow() - EventBus.getDefault().register(this); + override fun onViewAttachedToWindow(v: View) {} + }) } - override fun onDetachedFromWindow() { - EventBus.getDefault().unregister(this) - super.onDetachedFromWindow() + override fun onRefresh() { + web.reload() } - @Subscribe - fun webEvent(event: WebEvent) = event.execute(this) - - // Nested Scroll implements - override fun setNestedScrollingEnabled(enabled: Boolean) { - childHelper.isNestedScrollingEnabled = enabled + fun onBackPressed(): Boolean { + if (web.canGoBack()) { + web.goBack() + return true + } + return false } - - override fun isNestedScrollingEnabled() = childHelper.isNestedScrollingEnabled - - override fun startNestedScroll(axes: Int) = childHelper.startNestedScroll(axes) - - override fun stopNestedScroll() = childHelper.stopNestedScroll() - - override fun hasNestedScrollingParent() = childHelper.hasNestedScrollingParent() - - override fun dispatchNestedScroll(dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int, dyUnconsumed: Int, offsetInWindow: IntArray?) - = childHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow) - - override fun dispatchNestedPreScroll(dx: Int, dy: Int, consumed: IntArray?, offsetInWindow: IntArray?) - = childHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow) - - override fun dispatchNestedFling(velocityX: Float, velocityY: Float, consumed: Boolean) - = childHelper.dispatchNestedFling(velocityX, velocityY, consumed) - - override fun dispatchNestedPreFling(velocityX: Float, velocityY: Float) - = childHelper.dispatchNestedPreFling(velocityX, velocityY) - } \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClient.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClient.kt index 379bb22d..143c9f5a 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClient.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClient.kt @@ -1,18 +1,20 @@ package com.pitchedapps.frost.web import android.graphics.Bitmap -import android.view.View +import android.view.KeyEvent import android.webkit.* import com.pitchedapps.frost.facebook.FACEBOOK_COM import com.pitchedapps.frost.facebook.FbCookie import com.pitchedapps.frost.injectors.CssAssets import com.pitchedapps.frost.utils.L +import com.pitchedapps.frost.views.circularReveal +import com.pitchedapps.frost.views.fadeOut import io.reactivex.subjects.Subject /** * Created by Allan Wang on 2017-05-31. */ -class FrostWebViewClient(val observable: Subject) : WebViewClient() { +class FrostWebViewClient : WebViewClient() { companion object { //Collections of jewels mapped with url match -> id @@ -23,31 +25,50 @@ class FrostWebViewClient(val observable: Subject) : WebViewClient() { } } - override fun onReceivedError(view: WebView?, request: WebResourceRequest?, error: WebResourceError?) { - super.onReceivedError(view, request, error) - observable.onNext(WebStatus.ERROR) - L.e("FWV Error ${request}") - } - override fun onPageStarted(view: WebView, url: String, favicon: Bitmap?) { super.onPageStarted(view, url, favicon) - observable.onNext(WebStatus.LOADING) L.d("FWV Loading $url") if (!url.contains(FACEBOOK_COM)) return if (url.contains("logout.php")) FbCookie.logout() - view.visibility = View.INVISIBLE + view.fadeOut(duration = 200L) } override fun onPageFinished(view: WebView, url: String) { super.onPageFinished(view, url) if (!url.contains(FACEBOOK_COM)) return - observable.onNext(WebStatus.LOADED) FbCookie.checkUserId(url, CookieManager.getInstance().getCookie(url)) CssAssets.BASE.inject(view, { - view.visibility = View.VISIBLE + view.circularReveal(offset = 150L) }) } + override fun shouldOverrideKeyEvent(view: WebView, event: KeyEvent): Boolean { + L.d("Key event ${event.keyCode}") + return super.shouldOverrideKeyEvent(view, event) + } + + override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean { + L.d("Hi") + L.d("Url Loading ${request.url?.path}") + return super.shouldOverrideUrlLoading(view, request) + } + + override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? { + if (!request.url.host.contains(FACEBOOK_COM)) return super.shouldInterceptRequest(view, request) + L.d("Url intercept ${request.url.path}") + return super.shouldInterceptRequest(view, request) + } + + override fun onLoadResource(view: WebView, url: String) { + if (!url.contains(FACEBOOK_COM)) return super.onLoadResource(view, url) + L.d("Resource $url") + FrostWebOverlay.values.forEach { + if (url.contains(it.match)) + L.d("Loaded $it") + } + super.onLoadResource(view, url) + } + fun logout() { } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewCore.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewCore.kt new file mode 100644 index 00000000..03659908 --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewCore.kt @@ -0,0 +1,137 @@ +package com.pitchedapps.frost.web + +import android.annotation.SuppressLint +import android.content.Context +import android.support.v4.view.MotionEventCompat +import android.support.v4.view.NestedScrollingChild +import android.support.v4.view.NestedScrollingChildHelper +import android.support.v4.view.ViewCompat +import android.util.AttributeSet +import android.view.MotionEvent +import android.view.View +import android.webkit.WebView +import com.pitchedapps.frost.events.WebEvent +import com.pitchedapps.frost.utils.ObservableContainer +import io.reactivex.subjects.BehaviorSubject +import io.reactivex.subjects.Subject +import org.greenrobot.eventbus.EventBus +import org.greenrobot.eventbus.Subscribe + +/** + * Created by Allan Wang on 2017-05-29. + * + * Courtesy of takahirom + * + * https://github.com/takahirom/webview-in-coordinatorlayout/blob/master/app/src/main/java/com/github/takahirom/webview_in_coodinator_layout/NestedWebView.java + */ +class FrostWebViewCore @JvmOverloads constructor( + context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 +) : WebView(context, attrs, defStyleAttr), NestedScrollingChild, ObservableContainer { + + private val childHelper = NestedScrollingChildHelper(this) + private var lastY: Int = 0 + private val scrollOffset = IntArray(2) + private val scrollConsumed = IntArray(2) + private var nestedOffsetY: Int = 0 + override val progressObservable: Subject //TODO see if we need this + var baseUrl: String? = null + + init { + isNestedScrollingEnabled = true + progressObservable = BehaviorSubject.create() + setupWebview() + } + + @SuppressLint("SetJavaScriptEnabled") + fun setupWebview() { + settings.javaScriptEnabled = true + settings.domStorageEnabled = true + setLayerType(View.LAYER_TYPE_HARDWARE, null) + setWebViewClient(FrostWebViewClient()) + setWebChromeClient(FrostChromeClient(progressObservable)) + } + + override fun loadUrl(url: String?) { + if (url != null) + super.loadUrl(url) + } + + override fun onTouchEvent(ev: MotionEvent): Boolean { + val event = MotionEvent.obtain(ev) + val action = MotionEventCompat.getActionMasked(event) + if (action == MotionEvent.ACTION_DOWN) + nestedOffsetY = 0 + val eventY = event.y.toInt() + event.offsetLocation(0f, nestedOffsetY.toFloat()) + val returnValue: Boolean + when (action) { + MotionEvent.ACTION_MOVE -> { + var deltaY = lastY - eventY + // NestedPreScroll + if (dispatchNestedPreScroll(0, deltaY, scrollConsumed, scrollOffset)) { + deltaY -= scrollConsumed[1] + event.offsetLocation(0f, -scrollOffset[1].toFloat()) + nestedOffsetY += scrollOffset[1] + } + lastY = eventY - scrollOffset[1] + returnValue = super.onTouchEvent(event) + // NestedScroll + if (dispatchNestedScroll(0, scrollOffset[1], 0, deltaY, scrollOffset)) { + event.offsetLocation(0f, scrollOffset[1].toFloat()) + nestedOffsetY += scrollOffset[1] + lastY -= scrollOffset[1] + } + } + MotionEvent.ACTION_DOWN -> { + returnValue = super.onTouchEvent(event) + lastY = eventY + startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL) + } + MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { + returnValue = super.onTouchEvent(event) + stopNestedScroll() + } + else -> return false + } + return returnValue + } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + EventBus.getDefault().register(this); + } + + override fun onDetachedFromWindow() { + EventBus.getDefault().unregister(this) + super.onDetachedFromWindow() + } + + @Subscribe + fun webEvent(event: WebEvent) = event.execute(this) + + // Nested Scroll implements + override fun setNestedScrollingEnabled(enabled: Boolean) { + childHelper.isNestedScrollingEnabled = enabled + } + + override fun isNestedScrollingEnabled() = childHelper.isNestedScrollingEnabled + + override fun startNestedScroll(axes: Int) = childHelper.startNestedScroll(axes) + + override fun stopNestedScroll() = childHelper.stopNestedScroll() + + override fun hasNestedScrollingParent() = childHelper.hasNestedScrollingParent() + + override fun dispatchNestedScroll(dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int, dyUnconsumed: Int, offsetInWindow: IntArray?) + = childHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow) + + override fun dispatchNestedPreScroll(dx: Int, dy: Int, consumed: IntArray?, offsetInWindow: IntArray?) + = childHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow) + + override fun dispatchNestedFling(velocityX: Float, velocityY: Float, consumed: Boolean) + = childHelper.dispatchNestedFling(velocityX, velocityY, consumed) + + override fun dispatchNestedPreFling(velocityX: Float, velocityY: Float) + = childHelper.dispatchNestedPreFling(velocityX, velocityY) + +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/SwipeRefreshBase.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/SwipeRefreshBase.kt deleted file mode 100644 index 0e303e49..00000000 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/SwipeRefreshBase.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.pitchedapps.frost.web - -import android.content.Context -import android.support.v4.widget.SwipeRefreshLayout -import android.util.AttributeSet -import android.view.MotionEvent -import android.webkit.WebView -import com.pitchedapps.frost.utils.Utils - - -/** - * Created by Allan Wang on 2017-05-28. - */ -class SwipeRefreshBase @JvmOverloads constructor( - context: Context?, attrs: AttributeSet? = null -) : SwipeRefreshLayout(context, attrs) { - - lateinit var shouldSwipe: (ev: MotionEvent) -> Boolean - - companion object { - private val SCROLL_BUFFER by lazy { Utils.dpToPx(5) } - fun shouldScroll(webview: WebView) = webview.scrollY <= SCROLL_BUFFER - } - -// override fun onInterceptTouchEvent(ev: MotionEvent):Boolean { -// val b = shouldSwipe.invoke(ev) && super.onInterceptTouchEvent(ev) -// L.e("Should swipe $b") -// return b -// } -} \ No newline at end of file -- cgit v1.2.3