aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAllan Wang <me@allanwang.ca>2017-10-24 23:29:55 -0400
committerGitHub <noreply@github.com>2017-10-24 23:29:55 -0400
commitc2ca9066c6fd760bd6ef5d2f8f0530a89bfa7b66 (patch)
treea54665bb873b650b8f6f03b76cd59456ef79e296
parent64dbf74b7a44a25f41ed7ff2ebfa11db0bc91769 (diff)
downloadfrost-c2ca9066c6fd760bd6ef5d2f8f0530a89bfa7b66.tar.gz
frost-c2ca9066c6fd760bd6ef5d2f8f0530a89bfa7b66.tar.bz2
frost-c2ca9066c6fd760bd6ef5d2f8f0530a89bfa7b66.zip
WIP: Feature/pip video 2 (#405)
* Add dependency * Test new video view * Add initial video bindings * Implement drag to dismiss * Begin initial integration * Fix typo * Fix up url formatter * Update changelog * Create first fully integrated video build * Update translations * Update translations 2
-rw-r--r--README.md1
-rw-r--r--app/build.gradle2
-rw-r--r--app/src/main/AndroidManifest.xml2
-rw-r--r--app/src/main/assets/js/click-debugger.js13
-rw-r--r--app/src/main/assets/js/click-debugger.min.js6
-rw-r--r--app/src/main/assets/js/click_a.js6
-rw-r--r--app/src/main/assets/js/click_a.min.js12
-rw-r--r--app/src/main/assets/js/media.js31
-rw-r--r--app/src/main/assets/js/media.min.js21
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt23
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/activities/VideoActivity.kt33
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/facebook/FbUrlFormatter.kt44
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/injectors/JsAssets.kt2
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/utils/Downloader.kt27
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/views/FrostVideoView.kt234
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/views/FrostVideoViewer.kt121
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt7
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/web/FrostUrlOverlayValidator.kt11
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt1
-rw-r--r--app/src/main/res/layout/view_video.xml53
-rw-r--r--app/src/main/res/menu/menu_video.xml15
-rw-r--r--app/src/main/res/values-de/strings_download.xml1
-rw-r--r--app/src/main/res/values-es/strings_download.xml1
-rw-r--r--app/src/main/res/values-fr/strings_download.xml1
-rw-r--r--app/src/main/res/values/strings.xml3
-rw-r--r--app/src/main/res/values/strings_download.xml1
-rw-r--r--app/src/main/res/values/styles.xml7
-rw-r--r--app/src/main/res/xml/frost_changelog.xml22
-rw-r--r--app/src/test/kotlin/com/pitchedapps/frost/facebook/FbUrlTest.kt15
-rw-r--r--docs/Changelog.md7
-rw-r--r--gradle.properties3
31 files changed, 649 insertions, 77 deletions
diff --git a/README.md b/README.md
index 16c2e8d3..10d25641 100644
--- a/README.md
+++ b/README.md
@@ -36,6 +36,7 @@ If you would like to contribute, please visit [here](https://crwd.in/frost-for-f
Special thanks to the following awesome people for translating significant portions of Frost!
* [Vincent Kulak](https://github.com/VonOx) [FR]
+* [Jean-Philippe Gravel](https://crowdin.com/profile/wokija) [FR]
* [Jahir Fiquitiva](https://jahirfiquitiva.me/) [ES]
* [Nefi Salazar](https://plus.google.com/u/0/105547968033551087431) [ES]
* [Bushido1992](https://forum.xda-developers.com/member.php?u=5179246) [DE]
diff --git a/app/build.gradle b/app/build.gradle
index e7b83ddd..2a474cd3 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -157,6 +157,8 @@ dependencies {
implementation "org.apache.commons:commons-text:${COMMONS_TEXT}"
+ implementation "com.devbrackets.android:exomedia:${EXOMEDIA}"
+
//noinspection GradleDependency
releaseImplementation "com.squareup.leakcanary:leakcanary-android-no-op:${LEAK_CANARY}"
//noinspection GradleDependency
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 6ce65cea..2a23ea02 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -135,6 +135,8 @@
<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/click-debugger.js b/app/src/main/assets/js/click-debugger.js
new file mode 100644
index 00000000..702a62c9
--- /dev/null
+++ b/app/src/main/assets/js/click-debugger.js
@@ -0,0 +1,13 @@
+//for desktop only
+var _frostAContext = function(e) {
+ /*
+ * Commonality; check for valid target
+ */
+ var element = e.target || e.currentTarget || e.srcElement;
+ if (!element) return;
+ console.log("Clicked element:");
+ console.log(element.tagName);
+ console.log(element.className);
+}
+
+document.addEventListener('contextmenu', _frostAContext, true);
diff --git a/app/src/main/assets/js/click-debugger.min.js b/app/src/main/assets/js/click-debugger.min.js
new file mode 100644
index 00000000..0f986b07
--- /dev/null
+++ b/app/src/main/assets/js/click-debugger.min.js
@@ -0,0 +1,6 @@
+var _frostAContext=function(e){
+var t=e.target||e.currentTarget||e.srcElement
+;t&&(console.log("Clicked element:"),
+console.log(t.tagName),console.log(t.className))
+}
+;document.addEventListener("contextmenu",_frostAContext,!0); \ No newline at end of file
diff --git a/app/src/main/assets/js/click_a.js b/app/src/main/assets/js/click_a.js
index 24a08f56..2f5d759f 100644
--- a/app/src/main/assets/js/click_a.js
+++ b/app/src/main/assets/js/click_a.js
@@ -12,6 +12,7 @@ if (!window.hasOwnProperty('frost_click_a')) {
* Commonality; check for valid target
*/
var element = e.target || e.srcElement;
+
if (element.tagName !== 'A') element = element.parentNode;
//Notifications is two layers under
if (element.tagName !== 'A') element = element.parentNode;
@@ -41,11 +42,14 @@ if (!window.hasOwnProperty('frost_click_a')) {
document.addEventListener('click', _frostAClick, true);
+ var clickTimeout;
+
document.addEventListener('touchstart', function _frostStart(e) {
- setTimeout(_frostPreventClick, 400);
+ clickTimeout = setTimeout(_frostPreventClick, 400);
}, true);
document.addEventListener('touchend', function _frostEnd(e) {
prevented = false;
+ clearTimeout(clickTimeout);
}, true);
}
diff --git a/app/src/main/assets/js/click_a.min.js b/app/src/main/assets/js/click_a.min.js
index 2033fd31..a0df6912 100644
--- a/app/src/main/assets/js/click_a.min.js
+++ b/app/src/main/assets/js/click_a.min.js
@@ -5,16 +5,18 @@ window.frost_click_a=!0
var t=e.target||e.srcElement
;if("A"!==t.tagName&&(t=t.parentNode),"A"!==t.tagName&&(t=t.parentNode),
"A"===t.tagName&&!prevented){
-var n=t.getAttribute("href")
-;console.log("Click Intercept",n),"undefined"!=typeof Frost&&Frost.loadUrl(n)&&(e.stopPropagation(),
+var o=t.getAttribute("href")
+;console.log("Click Intercept",o),"undefined"!=typeof Frost&&Frost.loadUrl(o)&&(e.stopPropagation(),
e.preventDefault())
}
},_frostPreventClick=function(){
console.log("Click prevented"),prevented=!0
}
-;document.addEventListener("click",_frostAClick,!0),document.addEventListener("touchstart",function(e){
-setTimeout(_frostPreventClick,400)
+;document.addEventListener("click",_frostAClick,!0)
+;var clickTimeout
+;document.addEventListener("touchstart",function(e){
+clickTimeout=setTimeout(_frostPreventClick,400)
},!0),document.addEventListener("touchend",function(e){
-prevented=!1
+prevented=!1,clearTimeout(clickTimeout)
},!0)
} \ No newline at end of file
diff --git a/app/src/main/assets/js/media.js b/app/src/main/assets/js/media.js
new file mode 100644
index 00000000..852a1e8c
--- /dev/null
+++ b/app/src/main/assets/js/media.js
@@ -0,0 +1,31 @@
+// we will media events
+if (!window.hasOwnProperty('frost_media')) {
+ console.log('Registering frost_media');
+ window.frost_media = true;
+
+ var _frostMediaClick = function(e) {
+
+ /*
+ * Commonality; check for valid target
+ */
+ var element = e.target || e.srcElement;
+ if (!element.hasAttribute("data-sigil") || !element.getAttribute("data-sigil").includes("playInlineVideo")) return;
+ console.log("Found inline video");
+ element = element.parentNode;
+ if (!element.hasAttribute("data-store")) return;
+ var dataStore;
+ try {
+ dataStore = JSON.parse(element.getAttribute("data-store"));
+ } catch (e) {
+ return;
+ }
+ if (!dataStore.src) return;
+ console.log("Inline video", dataStore.src);
+ if (typeof Frost !== 'undefined') Frost.loadVideo(dataStore.src);
+ e.stopPropagation();
+ e.preventDefault();
+ return;
+ }
+
+ document.addEventListener('click', _frostMediaClick, true);
+}
diff --git a/app/src/main/assets/js/media.min.js b/app/src/main/assets/js/media.min.js
new file mode 100644
index 00000000..c965f515
--- /dev/null
+++ b/app/src/main/assets/js/media.min.js
@@ -0,0 +1,21 @@
+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 i
+;try{
+i=JSON.parse(e.getAttribute("data-store"))
+}catch(t){
+return
+}
+i.src&&(console.log("Inline video",i.src),"undefined"!=typeof Frost&&Frost.loadVideo(i.src),
+t.stopPropagation(),
+t.preventDefault())
+}
+}
+;document.addEventListener("click",_frostMediaClick,!0)
+} \ 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 1a96601d..b6232272 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt
@@ -59,6 +59,7 @@ import com.pitchedapps.frost.utils.iab.FrostBilling
import com.pitchedapps.frost.utils.iab.IabMain
import com.pitchedapps.frost.utils.iab.IS_FROST_PRO
import com.pitchedapps.frost.views.BadgedIcon
+import com.pitchedapps.frost.views.FrostVideoViewer
import com.pitchedapps.frost.views.FrostViewPager
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
@@ -80,6 +81,7 @@ class MainActivity : BaseActivity(),
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
lateinit var drawer: Drawer
lateinit var drawerHeader: AccountHeader
var webFragmentObservable = PublishSubject.create<Int>()!!
@@ -157,9 +159,18 @@ class MainActivity : BaseActivity(),
setFrostColors(toolbar, themeWindow = false, headers = arrayOf(appBar), backgrounds = arrayOf(viewPager))
tabs.setBackgroundColor(Prefs.mainActivityLayout.backgroundColor())
onCreateBilling()
-// setNetworkObserver { connectivity ->
-// shouldLoadImages = !connectivity.isRoaming
-// }
+ }
+
+ fun showVideo(url: String) {
+ if (videoViewer != null) {
+ videoViewer?.setVideo(url)
+ } else {
+ val viewer = FrostVideoViewer.showVideo(coordinator.parentViewGroup, url) {
+ L.d("Video view released")
+ videoViewer = null
+ }
+ videoViewer = viewer
+ }
}
fun tabsForEachView(action: (position: Int, view: BadgedIcon) -> Unit) {
@@ -420,12 +431,18 @@ 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()
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/VideoActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/VideoActivity.kt
new file mode 100644
index 00000000..5943c73c
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/VideoActivity.kt
@@ -0,0 +1,33 @@
+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/facebook/FbUrlFormatter.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbUrlFormatter.kt
index 4879e68b..3298ff15 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbUrlFormatter.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbUrlFormatter.kt
@@ -1,6 +1,8 @@
package com.pitchedapps.frost.facebook
import com.pitchedapps.frost.utils.L
+import java.net.URLDecoder
+import java.nio.charset.StandardCharsets
/**
* Created by Allan Wang on 2017-07-07.
@@ -8,12 +10,12 @@ import com.pitchedapps.frost.utils.L
* Custom url builder so we can easily test it without the Android framework
*/
inline val String.formattedFbUrl: String
- get() = FbUrlFormatter(this, false).toString()
+ get() = FbUrlFormatter(this).toString()
inline val String.formattedFbUrlCss: String
- get() = FbUrlFormatter(this, true).toString()
+ get() = FbUrlFormatter(this).toString()
-class FbUrlFormatter(url: String, css: Boolean = false) {
+class FbUrlFormatter(url: String) {
private val queries = mutableMapOf<String, String>()
private val cleaned: String
@@ -23,19 +25,17 @@ class FbUrlFormatter(url: String, css: Boolean = false) {
* The order is very important:
* 1. Wrapper links (discardables) are stripped away, resulting in the actual link
* 2. CSS encoding is converted to normal encoding
- * 3. Query portions are separated from the cleaned url
- * 4. The cleaned url is decoded. Queries are kept as is!
+ * 3. Url is completely decoded
+ * 4. Url is split into sections
*/
init {
if (url.isBlank()) cleaned = ""
else {
var cleanedUrl = url
discardable.forEach { cleanedUrl = cleanedUrl.replace(it, "", true) }
- val changed = cleanedUrl != url //note that discardables strip away the first '?'
converter.forEach { (k, v) -> cleanedUrl = cleanedUrl.replace(k, v, true) }
- //must decode for css
- if (css) decoder.forEach { (k, v) -> cleanedUrl = cleanedUrl.replace(k, v, true) }
- val qm = cleanedUrl.indexOf(if (changed) "&" else "?")
+ cleanedUrl = URLDecoder.decode(cleanedUrl, StandardCharsets.UTF_8.name())
+ val qm = cleanedUrl.indexOf("?")
if (qm > -1) {
cleanedUrl.substring(qm + 1).split("&").forEach {
val p = it.split("=")
@@ -43,8 +43,6 @@ class FbUrlFormatter(url: String, css: Boolean = false) {
}
cleanedUrl = cleanedUrl.substring(0, qm)
}
- //only decode non query portion of the url
- if (!css) decoder.forEach { (k, v) -> cleanedUrl = cleanedUrl.replace(k, v, true) }
discardableQueries.forEach { queries.remove(it) }
//final cleanup
misc.forEach { (k, v) -> cleanedUrl = cleanedUrl.replace(k, v, true) }
@@ -89,32 +87,10 @@ class FbUrlFormatter(url: String, css: Boolean = false) {
"/video_redirect/?src="
)
- val misc = listOf(
- "&amp;" to "&"
- )
+ val misc = arrayOf("&amp;" to "&")
val discardableQueries = arrayOf("ref", "refid")
- val decoder = listOf(
- "%3C" to "<", "%3E" to ">", "%23" to "#", "%25" to "%",
- "%7B" to "{", "%7D" to "}", "%7C" to "|", "%5C" to "\\",
- "%5E" to "^", "%7E" to "~", "%5B" to "[", "%5D" to "]",
- "%60" to "`", "%3B" to ";", "%2F" to "/", "%3F" to "?",
- "%3A" to ":", "%40" to "@", "%3D" to "=", "%26" to "&",
- "%24" to "$", "%2B" to "+", "%22" to "\"", "%2C" to ",",
- "%20" to " "
- )
-
- val cssDecoder = listOf(
- "\\3C " to "<", "\\3E " to ">", "\\23 " to "#", "\\25 " to "%",
- "\\7B " to "{", "\\7D " to "}", "\\7C " to "|", "\\5C " to "\\",
- "\\5E " to "^", "\\7E " to "~", "\\5B " to "[", "\\5D " to "]",
- "\\60 " to "`", "\\3B " to ";", "\\2F " to "/", "\\3F " to "?",
- "\\3A " to ":", "\\40 " to "@", "\\3D " to "=", "\\26 " to "&",
- "\\24 " to "$", "\\2B " to "+", "\\22 " to "\"", "\\2C " to ",",
- "%20" to " "
- )
-
val converter = listOf(
"\\3C " to "%3C", "\\3E " to "%3E", "\\23 " to "%23", "\\25 " to "%25",
"\\7B " to "%7B", "\\7D " to "%7D", "\\7C " to "%7C", "\\5C " to "%5C",
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsAssets.kt b/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsAssets.kt
index b4ce05a5..8e30346b 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsAssets.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsAssets.kt
@@ -11,7 +11,7 @@ import java.util.*
* The enum name must match the css file name
*/
enum class JsAssets : InjectorContract {
- MENU, MENU_DEBUG, CLICK_A, CONTEXT_A, HEADER_BADGES, SEARCH, TEXTAREA_LISTENER, NOTIF_MSG
+ MENU, MENU_DEBUG, CLICK_A, CONTEXT_A, MEDIA, HEADER_BADGES, SEARCH, TEXTAREA_LISTENER, NOTIF_MSG
;
var file = "${name.toLowerCase(Locale.CANADA)}.min.js"
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/Downloader.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/Downloader.kt
index 53cede18..3e1e1dde 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/utils/Downloader.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Downloader.kt
@@ -11,6 +11,7 @@ import ca.allanwang.kau.permissions.kauRequestPermissions
import ca.allanwang.kau.utils.string
import com.pitchedapps.frost.R
import com.pitchedapps.frost.dbflow.loadFbCookie
+import com.pitchedapps.frost.facebook.USER_AGENT_BASIC
/**
@@ -18,23 +19,37 @@ import com.pitchedapps.frost.dbflow.loadFbCookie
*
* With reference to <a href="https://stackoverflow.com/questions/33434532/android-webview-download-files-like-browsers-do">Stack Overflow</a>
*/
-fun Context.frostDownload(url: String, userAgent: String, contentDisposition: String, mimeType: String, contentLength: Long) {
- L.d("Received download request", "Download $url")
- val uri = Uri.parse(url) ?: return
+fun Context.frostDownload(url: String?,
+ userAgent: String = USER_AGENT_BASIC,
+ contentDisposition: String? = null,
+ mimeType: String? = null,
+ contentLength: Long = 0L) {
+ url ?: return
+ frostDownload(Uri.parse(url), userAgent, contentDisposition, mimeType, contentLength)
+}
+
+fun Context.frostDownload(uri: Uri?,
+ userAgent: String = USER_AGENT_BASIC,
+ contentDisposition: String? = null,
+ mimeType: String? = null,
+ contentLength: Long = 0L) {
+ uri ?: return
+ L.d("Received download request", "Download $uri")
if (uri.scheme != "http" && uri.scheme != "https")
- return L.e("Invalid download attempt", url)
+ return L.e("Invalid download attempt", uri.toString())
kauRequestPermissions(PERMISSION_WRITE_EXTERNAL_STORAGE) { granted, _ ->
if (!granted) return@kauRequestPermissions
val request = DownloadManager.Request(uri)
request.setMimeType(mimeType)
val cookie = loadFbCookie(Prefs.userId) ?: return@kauRequestPermissions
+ val title = URLUtil.guessFileName(uri.toString(), contentDisposition, mimeType)
request.addRequestHeader("cookie", cookie.cookie)
request.addRequestHeader("User-Agent", userAgent)
request.setDescription(string(R.string.downloading))
- request.setTitle(URLUtil.guessFileName(url, contentDisposition, mimeType))
+ request.setTitle(title)
request.allowScanningByMediaScanner()
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
- request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "Frost/" + URLUtil.guessFileName(url, contentDisposition, mimeType))
+ request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "Frost/$title")
val dm = getSystemService(DOWNLOAD_SERVICE) as DownloadManager
dm.enqueue(request)
}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostVideoView.kt b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostVideoView.kt
new file mode 100644
index 00000000..eaa4e698
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostVideoView.kt
@@ -0,0 +1,234 @@
+package com.pitchedapps.frost.views
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.graphics.PointF
+import android.util.AttributeSet
+import android.view.GestureDetector
+import android.view.MotionEvent
+import android.view.View
+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
+
+/**
+ * Created by Allan Wang on 2017-10-13.
+ *
+ * VideoView with scalability
+ * Parent must have layout with both height & width as match_parent
+ */
+class FrostVideoView @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
+) : VideoView(context, attrs, defStyleAttr) {
+
+ /**
+ * Shortcut for actual video view
+ */
+ private inline val v
+ get() = videoViewImpl
+
+ var backgroundView: View? = null
+ var onFinishedListener: () -> Unit = {}
+ var viewerContract: FrostVideoViewerContract? = null
+
+ private val videoDimensions = PointF(0f, 0f)
+
+ companion object {
+
+ /**
+ * Padding between minimized video and the parent borders
+ * Note that this is double the actual padding
+ * as we are calculating then dividing by 2
+ */
+ private val MINIMIZED_PADDING = 10.dpToPx
+ 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
+ }
+
+ private var upperMinimizedX = 0f
+ private var upperMinimizedY = 0f
+
+ var isExpanded: Boolean = true
+ set(value) {
+ if (field == value) return
+ if (videoDimensions.x <= 0f || videoDimensions.y <= 0f)
+ return L.d("Attempted to toggle video expansion when points have not been finalized")
+ field = value
+ 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)
+ }
+ } else {
+ hideControls()
+ val height = height
+ val width = width
+ val scale = Math.min(height / 4f / videoDimensions.y, width / 2.3f / videoDimensions.x)
+ val desiredHeight = scale * videoDimensions.y
+ val desiredWidth = scale * videoDimensions.x
+ val translationX = (width - MINIMIZED_PADDING - desiredWidth) / 2
+ val translationY = (height - MINIMIZED_PADDING - desiredHeight) / 2
+ upperMinimizedX = width - desiredWidth - MINIMIZED_PADDING
+ upperMinimizedY = height - desiredHeight - MINIMIZED_PADDING
+ animate().scaleXY(scale).translationX(translationX).translationY(translationY).setDuration(ANIMATION_DURATION).withStartAction {
+ backgroundView?.animate()?.alpha(0f)?.setDuration(ANIMATION_DURATION)
+ viewerContract?.onFade(0f, ANIMATION_DURATION)
+ }
+ }
+ }
+
+ init {
+ setOnPreparedListener {
+ start()
+ showControls()
+ }
+ setOnCompletionListener {
+ viewerContract?.onVideoComplete()
+ }
+ setOnTouchListener(FrameTouchListener(context))
+ v.setOnTouchListener(VideoTouchListener(context))
+ setOnVideoSizedChangedListener { intrinsicWidth, intrinsicHeight ->
+ val ratio = Math.min(width.toFloat() / intrinsicWidth, height.toFloat() / intrinsicHeight.toFloat())
+ videoDimensions.set(ratio * intrinsicWidth, ratio * intrinsicHeight)
+ }
+ }
+
+ fun jumpToStart() {
+ pause()
+ videoControls?.hide()
+ v.seekTo(0)
+ videoControls?.finishLoading()
+ }
+
+ private fun hideControls() {
+ if (videoControls?.isVisible == true)
+ videoControls?.hide()
+ }
+
+ private fun toggleControls() {
+ if (videoControls?.isVisible == true)
+ hideControls()
+ else
+ showControls()
+ }
+
+ fun shouldParentAcceptTouch(ev: MotionEvent): Boolean {
+ if (isExpanded) return true
+ return ev.x >= upperMinimizedX && ev.y >= upperMinimizedY
+ }
+
+ fun destroy() {
+ stopPlayback()
+ if (alpha > 0f)
+ animate().alpha(0f).setDuration(FAST_ANIMATION_DURATION).withEndAction { onFinishedListener() }.withStartAction {
+ viewerContract?.onFade(0f, FAST_ANIMATION_DURATION)
+ }.start()
+ else
+ onFinishedListener()
+ }
+
+ private fun onHorizontalSwipe(offset: Float) {
+ val alpha = Math.max((1f - Math.abs(offset / SWIPE_TO_CLOSE_OFFSET_THRESHOLD)) * 0.5f + 0.5f, 0f)
+ this.alpha = alpha
+ }
+
+ /*
+ * -------------------------------------------------------------------
+ * Touch Listeners
+ * -------------------------------------------------------------------
+ */
+
+ private inner class FrameTouchListener(context: Context) : GestureDetector.SimpleOnGestureListener(), View.OnTouchListener {
+
+ private val gestureDetector: GestureDetector = GestureDetector(context, this)
+
+ @SuppressLint("ClickableViewAccessibility")
+ override fun onTouch(view: View, event: MotionEvent): Boolean {
+ if (!isExpanded) return false
+ gestureDetector.onTouchEvent(event)
+ return true
+ }
+
+ override fun onSingleTapConfirmed(event: MotionEvent): Boolean {
+ if (viewerContract?.onSingleTapConfirmed(event) != true)
+ toggleControls()
+ return true
+ }
+
+ override fun onDoubleTap(e: MotionEvent): Boolean {
+ isExpanded = !isExpanded
+ return true
+ }
+ }
+
+ /**
+ * Monitors the view click events to show and hide the video controls if they have been specified.
+ */
+ private inner class VideoTouchListener(context: Context) : GestureDetector.SimpleOnGestureListener(), View.OnTouchListener {
+
+ private val gestureDetector: GestureDetector = GestureDetector(context, this)
+ private val downLoc = PointF()
+ private var baseSwipeX = -1f
+ private var baseTranslateX = -1f
+ private var checkForDismiss = true
+ private var onSwipe = false
+
+ @SuppressLint("ClickableViewAccessibility")
+ override fun onTouch(view: View, event: MotionEvent): Boolean {
+ gestureDetector.onTouchEvent(event)
+ when (event.actionMasked) {
+ MotionEvent.ACTION_DOWN -> {
+ checkForDismiss = !isExpanded
+ onSwipe = false
+ downLoc.x = event.rawX
+ downLoc.y = event.rawY
+ }
+ MotionEvent.ACTION_MOVE -> {
+ if (onSwipe) {
+ val dx = baseSwipeX - event.rawX
+ translationX = baseTranslateX - dx
+ onHorizontalSwipe(dx)
+ } else if (checkForDismiss) {
+ if (Math.abs(event.rawY - downLoc.y) > SWIPE_TO_CLOSE_VERTICAL_THRESHOLD)
+ checkForDismiss = false
+ else if (Math.abs(event.rawX - downLoc.x) > SWIPE_TO_CLOSE_HORIZONTAL_THRESHOLD) {
+ onSwipe = true
+ baseSwipeX = event.rawX
+ baseTranslateX = translationX
+ }
+ }
+ }
+ MotionEvent.ACTION_UP -> {
+ if (onSwipe) {
+ if (Math.abs(baseSwipeX - event.rawX) > SWIPE_TO_CLOSE_OFFSET_THRESHOLD)
+ destroy()
+ else
+ animate().translationX(baseTranslateX).setDuration(FAST_ANIMATION_DURATION).withStartAction {
+ animate().alpha(1f)
+ }
+ }
+ }
+ }
+ return true
+ }
+
+ override fun onSingleTapConfirmed(event: MotionEvent): Boolean {
+ if (viewerContract?.onSingleTapConfirmed(event) == true) return true
+ if (!isExpanded) {
+ isExpanded = true
+ return true
+ }
+ toggleControls()
+ return true
+ }
+
+ override fun onDoubleTap(e: MotionEvent): Boolean {
+ isExpanded = !isExpanded
+ return true
+ }
+ }
+} \ No newline at end of file
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostVideoViewer.kt b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostVideoViewer.kt
new file mode 100644
index 00000000..0f7d49e8
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostVideoViewer.kt
@@ -0,0 +1,121 @@
+package com.pitchedapps.frost.views
+
+import android.content.Context
+import android.graphics.Color
+import android.net.Uri
+import android.support.constraint.ConstraintLayout
+import android.support.v7.widget.Toolbar
+import android.util.AttributeSet
+import android.view.MotionEvent
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import ca.allanwang.kau.utils.*
+import com.mikepenz.google_material_typeface_library.GoogleMaterial
+import com.pitchedapps.frost.R
+import com.pitchedapps.frost.facebook.formattedFbUrl
+import com.pitchedapps.frost.utils.L
+import com.pitchedapps.frost.utils.Prefs
+import com.pitchedapps.frost.utils.frostDownload
+
+/**
+ * Created by Allan Wang on 2017-10-13.
+ */
+class FrostVideoViewer @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
+) : ConstraintLayout(context, attrs, defStyleAttr), FrostVideoViewerContract {
+
+ override fun onFade(alpha: Float, duration: Long) {
+ toolbar.animate().alpha(alpha).setDuration(duration)
+ }
+
+ override fun onSingleTapConfirmed(event: MotionEvent): Boolean {
+ if (restarter.isVisible) {
+ restarter.performClick()
+ return true
+ }
+ return false
+ }
+
+ override fun onVideoComplete() {
+ video.jumpToStart()
+ restarter.fadeIn()
+ }
+
+ val container: ViewGroup by bindView(R.id.video_container)
+ val toolbar: Toolbar by bindView(R.id.video_toolbar)
+ val background: View by bindView(R.id.video_background)
+ val video: FrostVideoView by bindView(R.id.video)
+ val restarter: ImageView by bindView(R.id.video_restart)
+
+
+ companion object {
+ /**
+ * 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
+ */
+ inline fun showVideo(container: ViewGroup, url: String, crossinline onFinish: () -> Unit): FrostVideoViewer {
+ val videoViewer = FrostVideoViewer(container.context)
+ container.addView(videoViewer)
+ videoViewer.bringToFront()
+ L.d("Create video view", url)
+ videoViewer.setVideo(url)
+ videoViewer.video.onFinishedListener = { container.removeView(videoViewer); onFinish() }
+ return videoViewer
+ }
+ }
+
+ init {
+ 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.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
+ )
+ toolbar.setOnMenuItemClickListener {
+ when (it.itemId) {
+ R.id.action_pip -> video.isExpanded = false
+ R.id.action_download -> context.frostDownload(video.videoUri)
+ }
+ true
+ }
+ restarter.gone().setIcon(GoogleMaterial.Icon.gmd_replay, 64)
+ restarter.setOnClickListener {
+ video.restart()
+ restarter.fadeOut { restarter.gone() }
+ }
+ }
+
+ fun setVideo(url: String) {
+ animate().alpha(1f).setDuration(FrostVideoView.ANIMATION_DURATION).start()
+ video.setVideoURI(Uri.parse(url.formattedFbUrl))
+ }
+
+ /**
+ * Handle back presses
+ * returns true if consumed, false otherwise
+ */
+ fun onBackPressed(): Boolean {
+ if (video.isExpanded) {
+ video.isExpanded = false
+ return true
+ }
+ return false
+ }
+
+ fun pause() = video.pause()
+
+}
+
+interface FrostVideoViewerContract {
+ fun onSingleTapConfirmed(event: MotionEvent): Boolean
+ fun onFade(alpha: Float, duration: Long)
+ fun onVideoComplete()
+} \ No newline at end of file
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 93d5c773..07703dde 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt
@@ -6,6 +6,7 @@ import android.webkit.JavascriptInterface
import com.pitchedapps.frost.activities.MainActivity
import com.pitchedapps.frost.dbflow.CookieModel
import com.pitchedapps.frost.facebook.FbCookie
+import com.pitchedapps.frost.facebook.formattedFbUrl
import com.pitchedapps.frost.utils.*
import io.reactivex.subjects.Subject
@@ -36,6 +37,12 @@ class FrostJSI(val webView: FrostWebViewCore) {
= if (url == null) false else webView.requestWebOverlay(url)
@JavascriptInterface
+ fun loadVideo(url: String?) {
+ if (url != null)
+ webView.post { activity?.showVideo(url) }
+ }
+
+ @JavascriptInterface
fun reloadBaseUrl(animate: Boolean) {
L.d("FrostJSI reload")
webView.post {
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 2d9915be..bf53c7eb 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostUrlOverlayValidator.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostUrlOverlayValidator.kt
@@ -1,8 +1,10 @@
package com.pitchedapps.frost.web
-import com.pitchedapps.frost.activities.WebOverlayBasicActivity
+import com.pitchedapps.frost.activities.MainActivity
import com.pitchedapps.frost.activities.WebOverlayActivity
import com.pitchedapps.frost.activities.WebOverlayActivityBase
+import com.pitchedapps.frost.activities.WebOverlayBasicActivity
+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
@@ -17,7 +19,7 @@ import com.pitchedapps.frost.utils.launchWebOverlay
* cannot be resolved on a new window and must instead
* by loaded in the current page
* This helper method will collect all known cases and launch the overlay accordingly
- * Returns {@code true} (default) if overlay is launched, {@code false} otherwise
+ * Returns {@code true} (default) if action is consumed, {@code false} otherwise
*
* If the request already comes from an instance of [WebOverlayActivity], we will then judge
* whether the user agent string should be changed. All propagated results will return false,
@@ -25,7 +27,6 @@ import com.pitchedapps.frost.utils.launchWebOverlay
*/
fun FrostWebViewCore.requestWebOverlay(url: String): Boolean {
if (url == "#") return false
-
if (context is WebOverlayActivityBase) {
L.v("Check web request from overlay", url)
//already overlay; manage user agent
@@ -70,10 +71,10 @@ fun FrostWebViewCore.requestWebOverlay(url: String): Boolean {
/**
* If the url contains any one of the whitelist segments, switch to the chat overlay
*/
-val messageWhitelist = setOf(FbItem.MESSAGES.url, FbItem.CHAT.url)
+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) })
+ 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 3275b2a6..e3803134 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt
@@ -110,6 +110,7 @@ open class FrostWebViewClient(val webCore: FrostWebViewCore) : BaseWebViewClient
JsAssets.TEXTAREA_LISTENER,
CssHider.ADS.maybe(!Prefs.showFacebookAds && IS_FROST_PRO),
JsAssets.CONTEXT_A,
+ JsAssets.MEDIA.maybe(webCore.baseEnum != null),
JsAssets.HEADER_BADGES.maybe(webCore.baseEnum != null)
)
}
diff --git a/app/src/main/res/layout/view_video.xml b/app/src/main/res/layout/view_video.xml
new file mode 100644
index 00000000..e8782459
--- /dev/null
+++ b/app/src/main/res/layout/view_video.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/video_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@android:color/transparent"
+ android:clickable="false"
+ android:theme="@style/FrostTheme.Video">
+
+ <View
+ android:id="@+id/video_background"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:clickable="false"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
+
+ <android.support.v7.widget.Toolbar
+ android:id="@+id/video_toolbar"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:minHeight="?attr/actionBarSize"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
+
+ <com.pitchedapps.frost.views.FrostVideoView
+ android:id="@+id/video"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:background="@android:color/transparent"
+ android:theme="@style/FrostTheme.Video"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/video_toolbar"
+ app:useDefaultControls="true"
+ app:useTextureViewBacking="true">
+
+ <ImageView
+ android:id="@+id/video_restart"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerInParent="true"
+ android:foregroundGravity="center" />
+
+ </com.pitchedapps.frost.views.FrostVideoView>
+
+
+</android.support.constraint.ConstraintLayout> \ No newline at end of file
diff --git a/app/src/main/res/menu/menu_video.xml b/app/src/main/res/menu/menu_video.xml
new file mode 100644
index 00000000..955a03ee
--- /dev/null
+++ b/app/src/main/res/menu/menu_video.xml
@@ -0,0 +1,15 @@
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+
+ <item
+ android:id="@+id/action_pip"
+ android:orderInCategory="100"
+ android:title="@string/pip"
+ app:showAsAction="always" />
+ <item
+ android:id="@+id/action_download"
+ android:orderInCategory="200"
+ android:title="@string/download"
+ app:showAsAction="ifRoom" />
+</menu>
+
diff --git a/app/src/main/res/values-de/strings_download.xml b/app/src/main/res/values-de/strings_download.xml
index 77c1b68c..862049f5 100644
--- a/app/src/main/res/values-de/strings_download.xml
+++ b/app/src/main/res/values-de/strings_download.xml
@@ -2,6 +2,7 @@
<!--Generated by crowdin.com-->
<resources>
<string name="pick_image">Foto auswählen</string>
+ <string name="download">Downloaden</string>
<string name="downloading">Downloade…</string>
<string name="image_download_success">Foto heruntergeladen</string>
<string name="image_download_fail">Fehler beim Download des Fotos</string>
diff --git a/app/src/main/res/values-es/strings_download.xml b/app/src/main/res/values-es/strings_download.xml
index c15378f4..4890b870 100644
--- a/app/src/main/res/values-es/strings_download.xml
+++ b/app/src/main/res/values-es/strings_download.xml
@@ -2,6 +2,7 @@
<!--Generated by crowdin.com-->
<resources>
<string name="pick_image">Seleccionar imagen</string>
+ <string name="download">Descargar</string>
<string name="downloading">Descargando…</string>
<string name="image_download_success">Imagen descargada</string>
<string name="image_download_fail">Descarga de imagen fallida</string>
diff --git a/app/src/main/res/values-fr/strings_download.xml b/app/src/main/res/values-fr/strings_download.xml
index 3480ad1f..146d71e7 100644
--- a/app/src/main/res/values-fr/strings_download.xml
+++ b/app/src/main/res/values-fr/strings_download.xml
@@ -2,6 +2,7 @@
<!--Generated by crowdin.com-->
<resources>
<string name="pick_image">Sélectionner une image</string>
+ <string name="download">Télécharger</string>
<string name="downloading">Téléchargement…</string>
<string name="image_download_success">Image téléchargée</string>
<string name="image_download_fail">Échec du téléchargement de l\'image</string>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 82aa82f1..4c9b7285 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -59,4 +59,7 @@
<string name="top_bar">Top Bar</string>
<string name="bottom_bar">Bottom Bar</string>
+
+ <string name="pip" translatable="false">PIP</string>
+
</resources>
diff --git a/app/src/main/res/values/strings_download.xml b/app/src/main/res/values/strings_download.xml
index ef166508..c0cb8cd4 100644
--- a/app/src/main/res/values/strings_download.xml
+++ b/app/src/main/res/values/strings_download.xml
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pick_image">Pick Image</string>
+ <string name="download">Download</string>
<string name="downloading">Downloading…</string>
<string name="image_download_success">Image downloaded</string>
<string name="image_download_fail">Image failed to download</string>
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index b91710d2..ab893b3a 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -41,6 +41,13 @@
<item name="android:windowAnimationStyle">@style/KauFadeInFadeOut</item>
</style>
+ <style name="FrostTheme.Video" parent="FrostTheme.Overlay.Fade">
+ <item name="android:windowBackground">@android:color/transparent</item>
+ <item name="android:backgroundDimEnabled">false</item>
+ <item name="android:colorBackgroundCacheHint">@null</item>
+ <item name="android:windowIsTranslucent">true</item>
+ </style>
+
<style name="FrostTheme.Settings" parent="FrostTheme">
<item name="android:windowAnimationStyle">@style/KauSlideInFadeOut</item>
</style>
diff --git a/app/src/main/res/xml/frost_changelog.xml b/app/src/main/res/xml/frost_changelog.xml
index b46889c2..5cc8a2c8 100644
--- a/app/src/main/res/xml/frost_changelog.xml
+++ b/app/src/main/res/xml/frost_changelog.xml
@@ -5,14 +5,22 @@
<version title="v" />
<item text="" />
-->
-
- <version title="Translations are opened!" />
- <item text="If you want to have Frost in your language, please consider helping translate it. Link is in settings." />
-
- <version title="v1.5.9" />
+
+ <version title="v1.6.0" />
+ <item text="Add Spanish translations" />
+ <item text="Add French translations" />
+ <item text="Add German translations" />
+ <item text="Check permissions before attempting upload or download" />
+ <item text="Add pip video support" />
+ <item text="Add video downloader" />
+ <item text="Fix bugs with parsing url queries" />
+ <item text="" />
+ <item text="" />
+
+ <version title="v1.5.9" />
<item text="Add notification support for Android O" />
-
- <version title="v1.5.8" />
+
+ <version title="v1.5.8" />
<item text="Fix theme for newer comments layout" />
<item text="Revert media picker to use system default" />
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 3a87a697..57589b5e 100644
--- a/app/src/test/kotlin/com/pitchedapps/frost/facebook/FbUrlTest.kt
+++ b/app/src/test/kotlin/com/pitchedapps/frost/facebook/FbUrlTest.kt
@@ -1,6 +1,9 @@
package com.pitchedapps.frost.facebook
+import okhttp3.HttpUrl
+import okio.Utf8
import org.junit.Test
+import java.net.URLDecoder
import kotlin.test.assertEquals
@@ -27,11 +30,6 @@ class FbUrlTest {
}
@Test
- fun redirect() {
- assertFbFormat("${FB_URL_BASE}relative/?asdf=1234&hjkl=7890", "https://touch.facebook.com/l.php?u=${FB_URL_BASE}relative/&asdf=1234&hjkl=7890")
- }
-
- @Test
fun discard() {
val prefix = "$FB_URL_BASE?test=1234"
val suffix = "&apple=notorange"
@@ -43,11 +41,4 @@ class FbUrlTest {
assertFbFormat("${FB_URL_BASE}relative", "$FB_URL_BASE/relative")
}
- @Test
- fun css() {
- val expected = "https://test.com?efg=hi&oh=bye&oe=apple%3Fornot"
- val orig = "https\\3a //test.com?efg=hi&oh=bye&oe=apple\\3F ornot"
- assertFbFormat(expected, orig)
- }
-
} \ No newline at end of file
diff --git a/docs/Changelog.md b/docs/Changelog.md
index 16b94b80..86fb33b2 100644
--- a/docs/Changelog.md
+++ b/docs/Changelog.md
@@ -1,7 +1,10 @@
# Changelog
-## Translations are opened!
-* If you want to have Frost in your language, please consider helping translate it. Link is in settings.
+## v1.6.0
+* Add Spanish translations
+* Add French translations
+* Add German translations
+* Check permissions before attempting upload or download
## v1.5.9
* Add notification support for Android O
diff --git a/gradle.properties b/gradle.properties
index 0202da09..e1d1a99f 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -17,12 +17,13 @@ MIN_SDK=21
TARGET_SDK=26
BUILD_TOOLS=26.0.2
-KAU=9270686
+KAU=a0587e8
KOTLIN=1.1.51
COMMONS_TEXT=1.1
CRASHLYTICS=2.7.1
DBFLOW=4.0.5
+EXOMEDIA=4.0.3
IAB=1.0.44
IICON_COMMUNITY=1.9.32.2
IICON_MATERIAL=2.2.0.3