diff options
author | Allan Wang <me@allanwang.ca> | 2017-11-12 02:48:36 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-11-12 02:48:36 -0500 |
commit | 2b51bc4bfa86863ed14b550fe3281840587ab038 (patch) | |
tree | 0fd7276e326ed0901b1af980d07d57f3bbb7d7eb /app | |
parent | ec7fdc2521463d0a773bb9d0be454f3b2a60eee3 (diff) | |
download | frost-2b51bc4bfa86863ed14b550fe3281840587ab038.tar.gz frost-2b51bc4bfa86863ed14b550fe3281840587ab038.tar.bz2 frost-2b51bc4bfa86863ed14b550fe3281840587ab038.zip |
enhancement/video-player (#480)v1.6.3
* Add toolbar visibility toggle and draw it over viewer
* Set contract bindings once available
* Fix video url param error and prepare progressanimator
* Add gif support and better transitions
* Interface a lot of things
* Reorder back press
* Clean up files and fix selector
* Add gif support
* Redraw bounds when necessary
Diffstat (limited to 'app')
22 files changed, 322 insertions, 165 deletions
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5ebb2118..5acf19d6 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -42,12 +42,14 @@ android:theme="@style/FrostTheme" /> <activity android:name=".activities.WebOverlayActivity" + android:configChanges="orientation|screenSize|locale" android:hardwareAccelerated="true" android:label="@string/frost_name" android:launchMode="singleTop" android:theme="@style/FrostTheme.Overlay.Slide" /> <activity android:name=".activities.WebOverlayBasicActivity" + android:configChanges="orientation|screenSize|locale" android:hardwareAccelerated="true" android:label="@string/frost_web" android:launchMode="singleTop" @@ -55,6 +57,7 @@ <activity android:name=".activities.FrostWebActivity" android:autoRemoveFromRecents="true" + android:configChanges="orientation|screenSize|locale" android:exported="true" android:hardwareAccelerated="true" android:label="@string/frost_web" @@ -70,6 +73,7 @@ android:autoVerify="true" tools:ignore="UnusedAttribute"> <action android:name="android.intent.action.VIEW" /> + <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> @@ -135,8 +139,6 @@ <activity android:name=".activities.ImageActivity" android:theme="@style/FrostTheme.Transparent" /> - <activity android:name=".activities.VideoActivity" - android:theme="@style/FrostTheme.Video" /> <service android:name=".services.NotificationService" diff --git a/app/src/main/assets/js/media.js b/app/src/main/assets/js/media.js index 852a1e8c..f1a5ac3c 100644 --- a/app/src/main/assets/js/media.js +++ b/app/src/main/assets/js/media.js @@ -9,7 +9,7 @@ if (!window.hasOwnProperty('frost_media')) { * Commonality; check for valid target */ var element = e.target || e.srcElement; - if (!element.hasAttribute("data-sigil") || !element.getAttribute("data-sigil").includes("playInlineVideo")) return; + if (!element.hasAttribute("data-sigil") || !element.getAttribute("data-sigil").toLowerCase().includes("inlinevideo")) return; console.log("Found inline video"); element = element.parentNode; if (!element.hasAttribute("data-store")) return; @@ -20,8 +20,8 @@ if (!window.hasOwnProperty('frost_media')) { return; } if (!dataStore.src) return; - console.log("Inline video", dataStore.src); - if (typeof Frost !== 'undefined') Frost.loadVideo(dataStore.src); + console.log("Inline video " + dataStore.src); + if (typeof Frost !== 'undefined') Frost.loadVideo(dataStore.src, dataStore.animatedGifVideo); e.stopPropagation(); e.preventDefault(); return; diff --git a/app/src/main/assets/js/media.min.js b/app/src/main/assets/js/media.min.js index c965f515..767b8a36 100644 --- a/app/src/main/assets/js/media.min.js +++ b/app/src/main/assets/js/media.min.js @@ -1,20 +1,20 @@ if(!window.hasOwnProperty("frost_media")){ console.log("Registering frost_media"), window.frost_media=!0 -;var _frostMediaClick=function(t){ -var e=t.target||t.srcElement -;if(e.hasAttribute("data-sigil")&&e.getAttribute("data-sigil").includes("playInlineVideo")&&(console.log("Found inline video"), -e=e.parentNode, -e.hasAttribute("data-store"))){ +;var _frostMediaClick=function(e){ +var t=e.target||e.srcElement +;if(t.hasAttribute("data-sigil")&&t.getAttribute("data-sigil").toLowerCase().includes("inlinevideo")&&(console.log("Found inline video"), +t=t.parentNode, +t.hasAttribute("data-store"))){ var i ;try{ -i=JSON.parse(e.getAttribute("data-store")) -}catch(t){ +i=JSON.parse(t.getAttribute("data-store")) +}catch(e){ return } -i.src&&(console.log("Inline video",i.src),"undefined"!=typeof Frost&&Frost.loadVideo(i.src), -t.stopPropagation(), -t.preventDefault()) +i.src&&(console.log("Inline video "+i.src),"undefined"!=typeof Frost&&Frost.loadVideo(i.src,i.animatedGifVideo), +e.stopPropagation(), +e.preventDefault()) } } ;document.addEventListener("click",_frostMediaClick,!0) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseActivity.kt index ddb42d72..ab08981f 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseActivity.kt @@ -1,10 +1,12 @@ package com.pitchedapps.frost.activities +import android.content.res.Configuration import android.os.Bundle import ca.allanwang.kau.internal.KauBaseActivity import com.github.pwittchen.reactivenetwork.library.rx2.Connectivity import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork import com.pitchedapps.frost.R +import com.pitchedapps.frost.contracts.VideoViewerContract import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.Prefs import com.pitchedapps.frost.utils.materialDialogThemed @@ -18,7 +20,10 @@ import io.reactivex.schedulers.Schedulers */ abstract class BaseActivity : KauBaseActivity() { override fun onBackPressed() { - if (isTaskRoot && Prefs.exitConfirmation) { + if (this is MainActivity && searchView?.onBackPressed() == true) return + if (this is VideoViewerContract && videoOnBackPress()) return + if (this is MainActivity && currentFragment.onBackPressed()) return + if (this !is WebOverlayActivityBase && isTaskRoot && Prefs.exitConfirmation) { materialDialogThemed { title(R.string.kau_exit) content(R.string.kau_exit_confirmation) @@ -27,12 +32,14 @@ abstract class BaseActivity : KauBaseActivity() { onPositive { _, _ -> super.onBackPressed() } checkBoxPromptRes(R.string.kau_do_not_show_again, false, { _, b -> Prefs.exitConfirmation = !b }) } - } else super.onBackPressed() + return + } + super.onBackPressed() } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setFrostTheme() + if (this !is WebOverlayActivityBase) setFrostTheme() } private var networkDisposable: Disposable? = null @@ -47,8 +54,7 @@ abstract class BaseActivity : KauBaseActivity() { networkDisposable = ReactiveNetwork.observeNetworkConnectivity(applicationContext) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe { - connectivity: Connectivity -> + .subscribe { connectivity: Connectivity -> connectivity.apply { L.d("Network connectivity changed: isAvailable: $isAvailable isRoaming: $isRoaming") consumer(connectivity) @@ -64,12 +70,23 @@ abstract class BaseActivity : KauBaseActivity() { override fun onResume() { super.onResume() - disposeNetworkConnectivity() - observeNetworkConnectivity() +// disposeNetworkConnectivity() +// observeNetworkConnectivity() } override fun onPause() { super.onPause() - disposeNetworkConnectivity() +// disposeNetworkConnectivity() + } + + + override fun onStop() { + if (this is VideoViewerContract) videoOnStop() + super.onStop() + } + + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + if (this is VideoViewerContract) videoViewer?.updateLocation() } }
\ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt index 2f4bd2e1..d4c30547 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt @@ -5,7 +5,6 @@ import android.app.AlarmManager import android.app.PendingIntent import android.content.Context import android.content.Intent -import android.content.res.Configuration import android.graphics.PointF import android.graphics.drawable.ColorDrawable import android.net.Uri @@ -48,6 +47,7 @@ import com.pitchedapps.frost.R import com.pitchedapps.frost.contracts.ActivityWebContract import com.pitchedapps.frost.contracts.FileChooserContract import com.pitchedapps.frost.contracts.FileChooserDelegate +import com.pitchedapps.frost.contracts.VideoViewerContract import com.pitchedapps.frost.dbflow.loadFbCookie import com.pitchedapps.frost.dbflow.loadFbTabs import com.pitchedapps.frost.enums.MainActivityLayout @@ -63,7 +63,6 @@ import com.pitchedapps.frost.utils.iab.FrostBilling import com.pitchedapps.frost.utils.iab.IS_FROST_PRO import com.pitchedapps.frost.utils.iab.IabMain import com.pitchedapps.frost.views.BadgedIcon -import com.pitchedapps.frost.views.FrostVideoContainerContract import com.pitchedapps.frost.views.FrostVideoViewer import com.pitchedapps.frost.views.FrostViewPager import io.reactivex.android.schedulers.AndroidSchedulers @@ -77,18 +76,18 @@ import java.util.concurrent.TimeUnit class MainActivity : BaseActivity(), ActivityWebContract, FileChooserContract by FileChooserDelegate(), - FrostVideoContainerContract, + VideoViewerContract, FrostBilling by IabMain() { lateinit var adapter: SectionsPagerAdapter - val frameWrapper: FrameLayout by bindView(R.id.frame_wrapper) + override val frameWrapper: FrameLayout by bindView(R.id.frame_wrapper) val toolbar: Toolbar by bindView(R.id.toolbar) val viewPager: FrostViewPager by bindView(R.id.container) val fab: FloatingActionButton by bindView(R.id.fab) val tabs: TabLayout by bindView(R.id.tabs) val appBar: AppBarLayout by bindView(R.id.appbar) val coordinator: CoordinatorLayout by bindView(R.id.main_content) - var videoViewer: FrostVideoViewer? = null + override var videoViewer: FrostVideoViewer? = null lateinit var drawer: Drawer lateinit var drawerHeader: AccountHeader var webFragmentObservable = PublishSubject.create<Int>()!! @@ -130,8 +129,7 @@ class MainActivity : BaseActivity(), "Frost id" to Prefs.frostId) } } - setContentView(R.layout.activity_frame_wrapper) - frameWrapper.inflate(Prefs.mainActivityLayout.layoutRes, true) + setFrameContentView(Prefs.mainActivityLayout.layoutRes) setSupportActionBar(toolbar) adapter = SectionsPagerAdapter(supportFragmentManager, loadFbTabs()) viewPager.adapter = adapter @@ -147,7 +145,7 @@ class MainActivity : BaseActivity(), override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) { super.onPageScrolled(position, positionOffset, positionOffsetPixels) - val delta: Float by lazy { positionOffset * (255 - 128).toFloat() } + val delta = positionOffset * (255 - 128).toFloat() tabsForEachView { tabPosition, view -> view.setAllAlpha(when (tabPosition) { position -> 255.0f - delta @@ -169,14 +167,6 @@ class MainActivity : BaseActivity(), onCreateBilling() } - fun showVideo(url: String) { - if (videoViewer != null) { - videoViewer?.setVideo(url) - } else { - videoViewer = FrostVideoViewer.showVideo(url, this) - } - } - fun tabsForEachView(action: (position: Int, view: BadgedIcon) -> Unit) { (0 until tabs.tabCount).asSequence().forEach { i -> action(i, tabs.getTabAt(i)!!.customView as BadgedIcon) @@ -312,7 +302,7 @@ class MainActivity : BaseActivity(), } } - fun Builder.primaryFrostItem(item: FbItem) = this.primaryItem(item.titleId) { + private fun Builder.primaryFrostItem(item: FbItem) = this.primaryItem(item.titleId) { iicon = item.icon iconColor = Prefs.textColor.toLong() textColor = Prefs.textColor.toLong() @@ -331,7 +321,7 @@ class MainActivity : BaseActivity(), } } - fun Builder.secondaryFrostItem(@StringRes title: Int, onClick: () -> Unit) = this.secondaryItem(title) { + private fun Builder.secondaryFrostItem(@StringRes title: Int, onClick: () -> Unit) = this.secondaryItem(title) { textColor = Prefs.textColor.toLong() selectedIconColor = Prefs.textColor.toLong() selectedTextColor = Prefs.textColor.toLong() @@ -436,23 +426,11 @@ class MainActivity : BaseActivity(), super.onStart() } - override fun onStop() { - videoViewer?.pause() - super.onStop() - } - override fun onDestroy() { onDestroyBilling() super.onDestroy() } - override fun onBackPressed() { - if (videoViewer?.onBackPressed() == true) return - if (searchView?.onBackPressed() == true) return - if (currentFragment.onBackPressed()) return - super.onBackPressed() - } - inline val currentFragment get() = supportFragmentManager.findFragmentByTag("android:switcher:${R.id.container}:${viewPager.currentItem}") as WebFragment @@ -489,16 +467,4 @@ class MainActivity : BaseActivity(), else PointF(0f, 0f) - override val videoContainer: FrameLayout - get() = frameWrapper - - override fun onVideoFinished() { - L.d("Video view released") - videoViewer = null - } - - override fun onConfigurationChanged(newConfig: Configuration) { - super.onConfigurationChanged(newConfig) - videoViewer?.updateLocation() - } } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/VideoActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/VideoActivity.kt deleted file mode 100644 index 5943c73c..00000000 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/VideoActivity.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.pitchedapps.frost.activities - -import android.os.Bundle -import android.view.ViewGroup -import ca.allanwang.kau.internal.KauBaseActivity -import ca.allanwang.kau.utils.bindView -import com.pitchedapps.frost.R -import com.pitchedapps.frost.utils.L -import com.pitchedapps.frost.views.FrostVideoView - -/** - * Created by Allan Wang on 2017-06-01. - */ -class VideoActivity : KauBaseActivity() { - - val container: ViewGroup by bindView(R.id.video_container) - val video: FrostVideoView by bindView(R.id.video) - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.view_video) - container.setOnTouchListener { _, event -> - val y = video.shouldParentAcceptTouch(event) - L.d("Video SPAT $y") - y - } - } - - override fun onStop() { - video.pause() - super.onStop() - } -}
\ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/WebOverlayActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/WebOverlayActivity.kt index c2556563..e20cfbf2 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/WebOverlayActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/WebOverlayActivity.kt @@ -1,6 +1,7 @@ package com.pitchedapps.frost.activities import android.content.Intent +import android.graphics.PointF import android.net.Uri import android.os.Bundle import android.support.design.widget.CoordinatorLayout @@ -10,6 +11,7 @@ import android.view.Menu import android.view.MenuItem import android.webkit.ValueCallback import android.webkit.WebChromeClient +import android.widget.FrameLayout import ca.allanwang.kau.internal.KauBaseActivity import ca.allanwang.kau.swipe.kauSwipeOnCreate import ca.allanwang.kau.swipe.kauSwipeOnDestroy @@ -20,9 +22,11 @@ import com.pitchedapps.frost.R import com.pitchedapps.frost.contracts.ActivityWebContract import com.pitchedapps.frost.contracts.FileChooserContract import com.pitchedapps.frost.contracts.FileChooserDelegate +import com.pitchedapps.frost.contracts.VideoViewerContract import com.pitchedapps.frost.enums.OverlayContext import com.pitchedapps.frost.facebook.* import com.pitchedapps.frost.utils.* +import com.pitchedapps.frost.views.FrostVideoViewer import com.pitchedapps.frost.web.FrostWebView import io.reactivex.disposables.Disposable import okhttp3.HttpUrl @@ -95,9 +99,10 @@ class WebOverlayBasicActivity : WebOverlayActivityBase(true) */ class WebOverlayActivity : WebOverlayActivityBase(false) -open class WebOverlayActivityBase(private val forceBasicAgent: Boolean) : KauBaseActivity(), - ActivityWebContract, FileChooserContract by FileChooserDelegate() { +open class WebOverlayActivityBase(private val forceBasicAgent: Boolean) : BaseActivity(), + ActivityWebContract, VideoViewerContract, FileChooserContract by FileChooserDelegate() { + override val frameWrapper: FrameLayout by bindView(R.id.frame_wrapper) val toolbar: Toolbar by bindView(R.id.overlay_toolbar) val frostWeb: FrostWebView by bindView(R.id.overlay_frost_webview) val coordinator: CoordinatorLayout by bindView(R.id.overlay_main_content) @@ -122,7 +127,7 @@ open class WebOverlayActivityBase(private val forceBasicAgent: Boolean) : KauBas finish() return } - setContentView(R.layout.activity_web_overlay) + setFrameContentView(R.layout.activity_web_overlay) setSupportActionBar(toolbar) supportActionBar?.setDisplayShowHomeEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true) @@ -219,4 +224,13 @@ open class WebOverlayActivityBase(private val forceBasicAgent: Boolean) : KauBas } return true } + + /* + * ---------------------------------------------------- + * Video Contract + * ---------------------------------------------------- + */ + override var videoViewer: FrostVideoViewer? = null + override val lowerVideoPadding: PointF = PointF(0f, 0f) + }
\ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/contracts/VideoViewerContract.kt b/app/src/main/kotlin/com/pitchedapps/frost/contracts/VideoViewerContract.kt new file mode 100644 index 00000000..2e6ad04f --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/contracts/VideoViewerContract.kt @@ -0,0 +1,54 @@ +package com.pitchedapps.frost.contracts + +import android.app.Activity +import android.widget.FrameLayout +import ca.allanwang.kau.utils.inflate +import com.pitchedapps.frost.R +import com.pitchedapps.frost.utils.L +import com.pitchedapps.frost.views.FrostVideoContainerContract +import com.pitchedapps.frost.views.FrostVideoViewer + +/** + * Created by Allan Wang on 2017-11-10. + */ +interface VideoViewerContract : FrameWrapper, FrostVideoContainerContract { + + var videoViewer: FrostVideoViewer? + + fun showVideo(url: String) + = showVideo(url, false) + + /** + * Create new viewer and reuse existing one + * The url will be formatted upon loading + */ + fun showVideo(url: String, repeat: Boolean) { + if (videoViewer != null) + videoViewer?.setVideo(url, repeat) + else + videoViewer = FrostVideoViewer.showVideo(url, repeat, this) + } + + fun videoOnStop() = videoViewer?.pause() + + fun videoOnBackPress() = videoViewer?.onBackPressed() ?: false + + override val videoContainer: FrameLayout + get() = frameWrapper + + override fun onVideoFinished() { + L.d("Video view released") + videoViewer = null + } +} + +interface FrameWrapper { + + val frameWrapper: FrameLayout + + fun Activity.setFrameContentView(layoutRes: Int) { + setContentView(R.layout.activity_frame_wrapper) + frameWrapper.inflate(layoutRes, true) + } + +} diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbConst.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbConst.kt index 65fae493..64f0b652 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbConst.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbConst.kt @@ -5,7 +5,7 @@ package com.pitchedapps.frost.facebook */ const val HTTPS_FACEBOOK_COM = "https://facebook.com" const val FACEBOOK_COM = "facebook.com" -const val FB_URL_BASE = "https://m.facebook.com/" +const val FB_URL_BASE = "https://m.$FACEBOOK_COM/" fun PROFILE_PICTURE_URL(id: Long) = "https://graph.facebook.com/$id/picture?type=large" const val USER_AGENT_FULL = "Mozilla/5.0 (Linux; Android 4.4.2; en-us; SAMSUNG SM-G900T Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Version/1.6 Chrome/28.0.1500.94 Mobile Safari/537.36" diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbUrlFormatter.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbUrlFormatter.kt index 22fc275f..0e74bb59 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbUrlFormatter.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbUrlFormatter.kt @@ -31,7 +31,7 @@ class FbUrlFormatter(url: String) { var cleanedUrl = url discardable.forEach { cleanedUrl = cleanedUrl.replace(it, "", true) } converter.forEach { (k, v) -> cleanedUrl = cleanedUrl.replace(k, v, true) } - if (cleanedUrl != url) cleanedUrl = cleanedUrl.replaceFirst("&", "?") + if (cleanedUrl != url && !cleanedUrl.contains("?")) cleanedUrl = cleanedUrl.replaceFirst("&", "?") cleanedUrl = URLDecoder.decode(cleanedUrl, StandardCharsets.UTF_8.name()) val qm = cleanedUrl.indexOf("?") if (qm > -1) { @@ -70,6 +70,8 @@ class FbUrlFormatter(url: String) { } companion object { + + const val VIDEO_REDIRECT = "/video_redirect/?src=" /** * Items here are explicitly removed from the url * Taken from FaceSlim @@ -82,7 +84,7 @@ class FbUrlFormatter(url: String) { "https://m.facebook.com/l.php?u=", "http://touch.facebook.com/l.php?u=", "https://touch.facebook.com/l.php?u=", - "/video_redirect/?src=" + VIDEO_REDIRECT ) val misc = arrayOf("&" to "&") diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/Animator.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/Animator.kt new file mode 100644 index 00000000..da852e6e --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Animator.kt @@ -0,0 +1,70 @@ +package com.pitchedapps.frost.utils + +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.animation.ValueAnimator +import android.view.animation.Interpolator + +/** + * Created by Allan Wang on 2017-11-10. + */ +class ProgressAnimator private constructor(private vararg val values: Float) { + + companion object { + inline fun ofFloat(crossinline builder: ProgressAnimator.() -> Unit) = ofFloat(0f, 1f) { builder() } + + fun ofFloat(vararg values: Float, builder: ProgressAnimator.() -> Unit) = ProgressAnimator(*values).apply { + builder() + build() + } + } + + private val animators: MutableList<(Float) -> Unit> = mutableListOf() + private val startActions: MutableList<() -> Unit> = mutableListOf() + private val endActions: MutableList<() -> Unit> = mutableListOf() + + var duration: Long = -1L + var interpolator: Interpolator? = null + + /** + * Add more changes to the [ValueAnimator] before running + */ + var extraConfigs: ValueAnimator.() -> Unit = {} + + fun withAnimator(from: Float, to: Float, animator: (Float) -> Unit) = animators.add { + val range = to - from + animator(range * it + from) + } + + fun withAnimator(animator: (Float) -> Unit) = animators.add(animator) + + fun withAnimatorInv(animator: (Float) -> Unit) = animators.add { animator(1f - it) } + + fun withStartAction(action: () -> Unit) = startActions.add(action) + + fun withEndAction(action: () -> Unit) = endActions.add(action) + + fun build() { + ValueAnimator.ofFloat(*values).apply { + if (this@ProgressAnimator.duration > 0L) + duration = this@ProgressAnimator.duration + if (this@ProgressAnimator.interpolator != null) + interpolator = this@ProgressAnimator.interpolator + addUpdateListener { + val progress = it.animatedValue as Float + animators.forEach { it(progress) } + } + addListener(object : AnimatorListenerAdapter() { + override fun onAnimationStart(animation: Animator?) { + startActions.forEach { it() } + } + + override fun onAnimationEnd(animation: Animator?) { + endActions.forEach { it() } + } + }) + extraConfigs() + start() + } + } +}
\ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt index 6d6c5381..22c77f5f 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt @@ -30,6 +30,7 @@ import com.pitchedapps.frost.R import com.pitchedapps.frost.activities.* import com.pitchedapps.frost.dbflow.CookieModel import com.pitchedapps.frost.facebook.* +import com.pitchedapps.frost.facebook.FbUrlFormatter.Companion.VIDEO_REDIRECT import com.pitchedapps.frost.utils.iab.IS_FROST_PRO import org.jsoup.Jsoup import org.jsoup.nodes.Element @@ -200,6 +201,9 @@ fun Context.resolveActivityForUri(uri: Uri): Boolean { inline val String?.isFacebookUrl get() = this != null && this.contains(FACEBOOK_COM) +inline val String?.isVideoUrl + get() = this != null && this.startsWith(VIDEO_REDIRECT) + fun Context.frostChangelog() = showChangelog(R.xml.frost_changelog, Prefs.textColor) { theme() if (System.currentTimeMillis() - Prefs.installDate > 2592000000) { //show after 1 month diff --git a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostVideoView.kt b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostVideoView.kt index c5508a4d..639dc9ba 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostVideoView.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostVideoView.kt @@ -8,10 +8,12 @@ import android.util.AttributeSet import android.view.GestureDetector import android.view.MotionEvent import android.view.View +import ca.allanwang.kau.utils.AnimHolder import ca.allanwang.kau.utils.dpToPx import ca.allanwang.kau.utils.scaleXY import com.devbrackets.android.exomedia.ui.widget.VideoView import com.pitchedapps.frost.utils.L +import com.pitchedapps.frost.utils.ProgressAnimator /** * Created by Allan Wang on 2017-10-13. @@ -29,10 +31,10 @@ class FrostVideoView @JvmOverloads constructor( private inline val v get() = videoViewImpl - var backgroundView: View? = null var onFinishedListener: () -> Unit = {} - lateinit var viewerContract: FrostVideoViewerContract + private lateinit var viewerContract: FrostVideoViewerContract lateinit var containerContract: FrostVideoContainerContract + var repeat: Boolean = false private val videoDimensions = PointF(0f, 0f) @@ -47,8 +49,8 @@ class FrostVideoView @JvmOverloads constructor( private val SWIPE_TO_CLOSE_HORIZONTAL_THRESHOLD = 2f.dpToPx private val SWIPE_TO_CLOSE_VERTICAL_THRESHOLD = 5f.dpToPx private val SWIPE_TO_CLOSE_OFFSET_THRESHOLD = 75f.dpToPx - val ANIMATION_DURATION = 300L - private val FAST_ANIMATION_DURATION = 100L + const val ANIMATION_DURATION = 200L + private const val FAST_ANIMATION_DURATION = 100L } private var videoBounds = RectF() @@ -59,19 +61,32 @@ class FrostVideoView @JvmOverloads constructor( if (videoDimensions.x <= 0f || videoDimensions.y <= 0f) return L.d("Attempted to toggle video expansion when points have not been finalized") field = value + val origX = translationX + val origY = translationY + val origScale = scaleX if (field) { - animate().scaleXY(1f).translationX(0f).translationY(0f).setDuration(ANIMATION_DURATION).withStartAction { - backgroundView?.animate()?.alpha(1f)?.setDuration(ANIMATION_DURATION) - viewerContract.onFade(1f, ANIMATION_DURATION) - }.withEndAction { - if (!isPlaying) showControls() + ProgressAnimator.ofFloat { + duration = ANIMATION_DURATION + interpolator = AnimHolder.fastOutSlowInInterpolator(context) + withAnimator { viewerContract.onExpand(it) } + withAnimator(origScale, 1f) { scaleXY = it } + withAnimator(origX, 0f) { translationX = it } + withAnimator(origY, 0f) { translationY = it } + withEndAction { + if (!isPlaying) showControls() + else viewerContract.onControlsHidden() + } } } else { hideControls() val (scale, tX, tY) = mapBounds() - animate().scaleXY(scale).translationX(tX).translationY(tY).setDuration(ANIMATION_DURATION).withStartAction { - backgroundView?.animate()?.alpha(0f)?.setDuration(ANIMATION_DURATION) - viewerContract.onFade(0f, ANIMATION_DURATION) + ProgressAnimator.ofFloat { + duration = ANIMATION_DURATION + interpolator = AnimHolder.fastOutSlowInInterpolator(context) + withAnimatorInv { viewerContract.onExpand(it) } + withAnimator(origScale, scale) { scaleXY = it } + withAnimator(origX, tX) { translationX = it } + withAnimator(origY, tY) { translationY = it } } } } @@ -110,16 +125,28 @@ class FrostVideoView @JvmOverloads constructor( if (isExpanded) showControls() } setOnCompletionListener { - viewerContract.onVideoComplete() + if (repeat) restart() + else viewerContract.onVideoComplete() } setOnTouchListener(FrameTouchListener(context)) v.setOnTouchListener(VideoTouchListener(context)) setOnVideoSizedChangedListener { intrinsicWidth, intrinsicHeight -> val ratio = Math.min(width.toFloat() / intrinsicWidth, height.toFloat() / intrinsicHeight.toFloat()) + /** + * Only remap if not expanded and if dimensions have changed + */ + val shouldRemap = !isExpanded + && (videoDimensions.x != ratio * intrinsicWidth || videoDimensions.y != ratio * intrinsicHeight) videoDimensions.set(ratio * intrinsicWidth, ratio * intrinsicHeight) + if (shouldRemap) updateLocation() } } + fun setViewerContract(contract: FrostVideoViewerContract) { + this.viewerContract = contract + videoControls?.setVisibilityListener(viewerContract) + } + fun jumpToStart() { pause() v.seekTo(0) @@ -136,7 +163,7 @@ class FrostVideoView @JvmOverloads constructor( override fun restart(): Boolean { videoUri ?: return false - if (videoViewImpl.restart() && isExpanded) { + if (videoViewImpl.restart() && isExpanded && !repeat) { videoControls?.showLoading(true) return true } @@ -163,9 +190,11 @@ class FrostVideoView @JvmOverloads constructor( fun destroy() { stopPlayback() if (alpha > 0f) - animate().alpha(0f).setDuration(FAST_ANIMATION_DURATION).withEndAction { onFinishedListener() }.withStartAction { - viewerContract.onFade(0f, FAST_ANIMATION_DURATION) - }.start() + ProgressAnimator.ofFloat(alpha, 0f) { + duration = FAST_ANIMATION_DURATION + withAnimator { alpha = it } + withEndAction { onFinishedListener() } + } else onFinishedListener() } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostVideoViewer.kt b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostVideoViewer.kt index bf4df8fe..3a773288 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostVideoViewer.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostVideoViewer.kt @@ -13,6 +13,7 @@ import android.view.ViewTreeObserver import android.widget.FrameLayout import android.widget.ImageView import ca.allanwang.kau.utils.* +import com.devbrackets.android.exomedia.listener.VideoControlsVisibilityListener import com.mikepenz.google_material_typeface_library.GoogleMaterial import com.pitchedapps.frost.R import com.pitchedapps.frost.facebook.formattedFbUrl @@ -35,17 +36,21 @@ class FrostVideoViewer @JvmOverloads constructor( companion object { /** + * Matches VideoControls.CONTROL_VISIBILITY_ANIMATION_LENGTH + */ + private const val CONTROL_ANIMATION_DURATION = 300L + + /** * Simplified binding to add video to layout, and remove it when finished * This is under the assumption that the container allows for overlays, * such as a FrameLayout */ - fun showVideo(url: String, contract: FrostVideoContainerContract): FrostVideoViewer { + fun showVideo(url: String, repeat: Boolean, contract: FrostVideoContainerContract): FrostVideoViewer { val container = contract.videoContainer val videoViewer = FrostVideoViewer(container.context) container.addView(videoViewer) videoViewer.bringToFront() - L.d("Create video view", url) - videoViewer.setVideo(url) + videoViewer.setVideo(url, repeat) videoViewer.video.containerContract = contract videoViewer.video.onFinishedListener = { container.removeView(videoViewer); contract.onVideoFinished() } return videoViewer @@ -56,11 +61,9 @@ class FrostVideoViewer @JvmOverloads constructor( inflate(R.layout.view_video, true) alpha = 0f background.setBackgroundColor(if (Prefs.bgColor.isColorDark) Prefs.bgColor.withMinAlpha(200) else Color.BLACK) - video.backgroundView = background - video.viewerContract = this + video.setViewerContract(this) video.pause() toolbar.inflateMenu(R.menu.menu_video) - toolbar.setBackgroundColor(Prefs.headerColor) context.setMenuIcons(toolbar.menu, Prefs.iconColor, R.id.action_pip to GoogleMaterial.Icon.gmd_picture_in_picture_alt, R.id.action_download to GoogleMaterial.Icon.gmd_file_download @@ -77,12 +80,14 @@ class FrostVideoViewer @JvmOverloads constructor( video.restart() restarter.fadeOut { restarter.gone() } } -// toolbar.setOnTouchListener { _, event -> video.shouldParentAcceptTouch(event) } } - fun setVideo(url: String) { + fun setVideo(url: String, repeat: Boolean = false) { + val formattedUrl = url.formattedFbUrl + L.d("Load video view; repeat: $repeat", url) animate().alpha(1f).setDuration(FrostVideoView.ANIMATION_DURATION).start() - video.setVideoURI(Uri.parse(url.formattedFbUrl)) + video.setVideoURI(Uri.parse(formattedUrl)) + video.repeat = repeat } /** @@ -106,10 +111,9 @@ class FrostVideoViewer @JvmOverloads constructor( * ------------------------------------------------------------- */ - override fun onFade(alpha: Float, duration: Long) { - toolbar.visible().animate().alpha(alpha).setDuration(duration).withEndAction { - if (alpha == 0f) toolbar.gone() - } + override fun onExpand(progress: Float) { + toolbar.goneIf(progress == 0f).alpha = progress + background.alpha = progress } override fun onSingleTapConfirmed(event: MotionEvent): Boolean { @@ -134,11 +138,26 @@ class FrostVideoViewer @JvmOverloads constructor( }) } + override fun onControlsShown() { + if (video.isExpanded) + toolbar.fadeIn(duration = CONTROL_ANIMATION_DURATION, onStart = { toolbar.visible() }) + } + + override fun onControlsHidden() { + if (!toolbar.isGone) + toolbar.fadeOut(duration = CONTROL_ANIMATION_DURATION) { toolbar.gone() } + } + } -interface FrostVideoViewerContract { +interface FrostVideoViewerContract : VideoControlsVisibilityListener { fun onSingleTapConfirmed(event: MotionEvent): Boolean - fun onFade(alpha: Float, duration: Long) + /** + * Process of expansion + * 1f represents an expanded view, 0f represents a minimized view + */ + fun onExpand(progress: Float) + fun onVideoComplete() } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt index 4700c894..71ceb4ca 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt @@ -4,6 +4,7 @@ import android.content.Context import android.support.v4.widget.SwipeRefreshLayout import android.webkit.JavascriptInterface import com.pitchedapps.frost.activities.MainActivity +import com.pitchedapps.frost.contracts.VideoViewerContract import com.pitchedapps.frost.dbflow.CookieModel import com.pitchedapps.frost.facebook.FbCookie import com.pitchedapps.frost.utils.* @@ -36,9 +37,10 @@ class FrostJSI(val webView: FrostWebViewCore) { = if (url == null) false else webView.requestWebOverlay(url) @JavascriptInterface - fun loadVideo(url: String?) { + fun loadVideo(url: String?, isGif: Boolean) { if (url != null) - webView.post { activity?.showVideo(url) } + webView.post { (context as? VideoViewerContract)?.showVideo(url, isGif) + ?: L.d("Could not load video; contract not implemented") } } @JavascriptInterface diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostUrlOverlayValidator.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostUrlOverlayValidator.kt index ac5cde29..334ef51b 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostUrlOverlayValidator.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostUrlOverlayValidator.kt @@ -3,13 +3,12 @@ package com.pitchedapps.frost.web import com.pitchedapps.frost.activities.WebOverlayActivity import com.pitchedapps.frost.activities.WebOverlayActivityBase import com.pitchedapps.frost.activities.WebOverlayBasicActivity +import com.pitchedapps.frost.contracts.VideoViewerContract import com.pitchedapps.frost.facebook.FB_URL_BASE import com.pitchedapps.frost.facebook.FbItem import com.pitchedapps.frost.facebook.USER_AGENT_BASIC import com.pitchedapps.frost.facebook.formattedFbUrl -import com.pitchedapps.frost.utils.L -import com.pitchedapps.frost.utils.isFacebookUrl -import com.pitchedapps.frost.utils.launchWebOverlay +import com.pitchedapps.frost.utils.* /** * Created by Allan Wang on 2017-08-15. @@ -26,6 +25,12 @@ import com.pitchedapps.frost.utils.launchWebOverlay */ fun FrostWebViewCore.requestWebOverlay(url: String): Boolean { if (url == "#") return false + if (url.isVideoUrl && context is VideoViewerContract) { + L.i("Found video", url) + (context as VideoViewerContract).showVideo(url) + return true + } + if (!Prefs.overlayEnabled) return false if (context is WebOverlayActivityBase) { L.v("Check web request from overlay", url) //already overlay; manage user agent @@ -73,7 +78,8 @@ fun FrostWebViewCore.requestWebOverlay(url: String): Boolean { val messageWhitelist = setOf(FbItem.MESSAGES, FbItem.CHAT, FbItem.FEED_MOST_RECENT, FbItem.FEED_TOP_STORIES).map { it.url }.toSet() val String.shouldUseBasicAgent - get() = (messageWhitelist.any { contains(it) }) || this == FB_URL_BASE + get() = !contains("story.php") //we will use basic agent for anything that isn't a comment section +// get() = (messageWhitelist.any { contains(it) }) || this == FB_URL_BASE /** * The following components should never be launched in a new overlay diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt index e3803134..c8c7e2e7 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt @@ -106,11 +106,11 @@ open class FrostWebViewClient(val webCore: FrostWebViewCore) : BaseWebViewClient injectBackgroundColor() webCore.jsInject( JsActions.LOGIN_CHECK, - JsAssets.CLICK_A.maybe(Prefs.overlayEnabled), + JsAssets.CLICK_A, JsAssets.TEXTAREA_LISTENER, CssHider.ADS.maybe(!Prefs.showFacebookAds && IS_FROST_PRO), JsAssets.CONTEXT_A, - JsAssets.MEDIA.maybe(webCore.baseEnum != null), + JsAssets.MEDIA, JsAssets.HEADER_BADGES.maybe(webCore.baseEnum != null) ) } diff --git a/app/src/main/res/layout/activity_frame_wrapper.xml b/app/src/main/res/layout/activity_frame_wrapper.xml index 585789ef..e51d3391 100644 --- a/app/src/main/res/layout/activity_frame_wrapper.xml +++ b/app/src/main/res/layout/activity_frame_wrapper.xml @@ -2,4 +2,5 @@ <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/frame_wrapper" android:layout_width="match_parent" - android:layout_height="match_parent" />
\ No newline at end of file + android:layout_height="match_parent" + android:fitsSystemWindows="true" />
\ No newline at end of file diff --git a/app/src/main/res/layout/activity_selector.xml b/app/src/main/res/layout/activity_selector.xml index a5f90aab..d9625731 100644 --- a/app/src/main/res/layout/activity_selector.xml +++ b/app/src/main/res/layout/activity_selector.xml @@ -2,9 +2,9 @@ <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/container" android:layout_width="match_parent" android:layout_height="match_parent" - android:id="@+id/container" android:paddingBottom="@dimen/kau_activity_vertical_margin" android:paddingEnd="@dimen/kau_activity_horizontal_margin" android:paddingStart="@dimen/kau_activity_horizontal_margin" @@ -17,7 +17,6 @@ android:layout_gravity="center_horizontal" android:layout_marginTop="@dimen/kau_activity_vertical_margin" android:text="@string/select_facebook_account" - android:textColor="@android:color/white" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" @@ -32,21 +31,10 @@ app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" - app:layout_constraintStart_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/text_select_account" app:layout_constraintVertical_bias="0.3" tools:layout_editor_absoluteX="0dp" tools:layout_editor_absoluteY="0dp" /> - <!--<android.support.v7.widget.AppCompatTextView--> - <!--android:layout_width="wrap_content"--> - <!--android:layout_height="wrap_content"--> - <!--android:layout_gravity="center_horizontal"--> - <!--android:text="@string/select_facebook_account"--> - <!--app:layout_constraintEnd_toEndOf="parent"--> - <!--app:layout_constraintHorizontal_bias="0.5"--> - <!--app:layout_constraintStart_toStartOf="parent"--> - <!--app:layout_constraintTop_toBottomOf="@userId/selector_recycler"--> - <!--tools:layout_editor_absoluteX="8dp"--> - <!--tools:layout_editor_absoluteY="0dp" />--> </android.support.constraint.ConstraintLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/view_video.xml b/app/src/main/res/layout/view_video.xml index b8226ff2..ca256477 100644 --- a/app/src/main/res/layout/view_video.xml +++ b/app/src/main/res/layout/view_video.xml @@ -14,16 +14,10 @@ android:layout_height="match_parent" android:clickable="false" /> - <android.support.v7.widget.Toolbar - android:id="@+id/video_toolbar" - android:layout_width="match_parent" - android:layout_height="?attr/actionBarSize" /> - <com.pitchedapps.frost.views.FrostVideoView android:id="@+id/video" android:layout_width="match_parent" android:layout_height="match_parent" - android:layout_marginTop="?attr/actionBarSize" android:background="@android:color/transparent" android:theme="@style/FrostTheme.Video" app:useDefaultControls="true" @@ -38,5 +32,10 @@ </com.pitchedapps.frost.views.FrostVideoView> + <android.support.v7.widget.Toolbar + android:id="@+id/video_toolbar" + android:layout_width="match_parent" + android:background="@drawable/exomedia_default_controls_interactive_background" + android:layout_height="?attr/actionBarSize" /> </merge>
\ No newline at end of file diff --git a/app/src/main/res/xml/frost_changelog.xml b/app/src/main/res/xml/frost_changelog.xml index 65d577e0..6f1dc7a3 100644 --- a/app/src/main/res/xml/frost_changelog.xml +++ b/app/src/main/res/xml/frost_changelog.xml @@ -5,6 +5,14 @@ <version title="v" /> <item text="" /> --> + + <version title="v1.6.3" /> + <item text="Allow for truly full screen videos" /> + <item text="Support pip video everywhere" /> + <item text="Support gifs" /> + <item text="" /> + <item text="" /> + <item text="" /> <version title="v1.6.2" /> <item text="Fix search update from Facebook" /> diff --git a/app/src/test/kotlin/com/pitchedapps/frost/facebook/FbUrlTest.kt b/app/src/test/kotlin/com/pitchedapps/frost/facebook/FbUrlTest.kt index 5ac484b5..79cde137 100644 --- a/app/src/test/kotlin/com/pitchedapps/frost/facebook/FbUrlTest.kt +++ b/app/src/test/kotlin/com/pitchedapps/frost/facebook/FbUrlTest.kt @@ -48,4 +48,13 @@ class FbUrlTest { assertFbFormat("${FB_URL_BASE}relative", "$FB_URL_BASE/relative") } + @Test + fun video() { + //note that the video numbers have been changed to maintain privacy + val url = "/video_redirect/?src=https%3A%2F%2Fvideo-yyz1-1.xx.fbcdn.net%2Fv%2Ft42.1790-2%2F2349078999904_n.mp4%3Fefg%3DeyJ87J9%26oh%3Df5777784%26oe%3D56FD4&source=media_collage&id=1735049&refid=8&_ft_=qid.6484464%3Amf_story_key.-43172431214%3Atop_level_post_id.102773&__tn__=FEH-R" + val expected = "https://video-yyz1-1.xx.fbcdn.net/v/t42.1790-2/2349078999904_n.mp4?efg=eyJ87J9&oh=f5777784&oe=56FD4?source&id=1735049&_ft_=qid.6484464:mf_story_key.-43172431214:top_level_post_id.102773&__tn__=FEH-R" + assertFbFormat(expected, url) + } + + }
\ No newline at end of file |