diff --git a/app/build.gradle b/app/build.gradle index 62529e10d..591ed9ba5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -54,7 +54,8 @@ android { disable 'TrustAllX509TrustManager', 'UnusedResources', 'ContentDescription', - 'RtlSymmetry' + 'RtlSymmetry', + 'MissingTranslation' xmlReport false textReport true diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5acf19d6d..136a467f0 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -145,11 +145,6 @@ android:enabled="true" android:label="@string/frost_notifications" android:permission="android.permission.BIND_JOB_SERVICE" /> - cleanedUrl = cleanedUrl.replace(k, v, true) } - if (cleanedUrl != url && !cleanedUrl.contains("?")) cleanedUrl = cleanedUrl.replaceFirst("&", "?") + cleaned = clean(url) + } + + fun clean(url: String): String { + if (url.isBlank()) return "" + var cleanedUrl = url + discardable.forEach { cleanedUrl = cleanedUrl.replace(it, "", true) } + val changed = cleanedUrl != url + converter.forEach { (k, v) -> cleanedUrl = cleanedUrl.replace(k, v, true) } + try { 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("=") - queries.put(p[0], p.elementAtOrNull(1) ?: "") - } - cleanedUrl = cleanedUrl.substring(0, qm) - } - discardableQueries.forEach { queries.remove(it) } - //final cleanup - misc.forEach { (k, v) -> cleanedUrl = cleanedUrl.replace(k, v, true) } - if (cleanedUrl.startsWith("#!")) cleanedUrl = cleanedUrl.substring(2) - if (cleanedUrl.startsWith("/")) cleanedUrl = FB_URL_BASE + cleanedUrl.substring(1) - cleanedUrl = cleanedUrl.replaceFirst(".facebook.com//", ".facebook.com/") //sometimes we are given a bad url - L.v(null, "Formatted url from $url to $cleanedUrl") - cleaned = cleanedUrl + } catch (e: Exception) { + L.e(e, "Failed url formatting") + return url } + if (changed && !cleanedUrl.contains("?")) //ensure we aren't missing '?' + cleanedUrl = cleanedUrl.replaceFirst("&", "?") + val qm = cleanedUrl.indexOf("?") + if (qm > -1) { + cleanedUrl.substring(qm + 1).split("&").forEach { + val p = it.split("=") + queries.put(p[0], p.elementAtOrNull(1) ?: "") + } + cleanedUrl = cleanedUrl.substring(0, qm) + } + discardableQueries.forEach { queries.remove(it) } + //final cleanup + misc.forEach { (k, v) -> cleanedUrl = cleanedUrl.replace(k, v, true) } + if (cleanedUrl.startsWith("#!")) cleanedUrl = cleanedUrl.substring(2) + if (cleanedUrl.startsWith("/")) cleanedUrl = FB_URL_BASE + cleanedUrl.substring(1) + cleanedUrl = cleanedUrl.replaceFirst(".facebook.com//", ".facebook.com/") //sometimes we are given a bad url + L.v(null, "Formatted url from $url to $cleanedUrl") + return cleanedUrl } override fun toString(): String { @@ -76,6 +85,10 @@ class FbUrlFormatter(url: String) { * Items here are explicitly removed from the url * Taken from FaceSlim * https://github.com/indywidualny/FaceSlim/blob/master/app/src/main/java/org/indywidualni/fblite/util/Miscellany.java + * + * Note: Typically, in this case, the redirect url should have all the necessary queries + * I am unsure how Facebook reacts in all cases, so the ones after the redirect are appended on afterwards + * That shouldn't break anything */ val discardable = arrayOf( "http://lm.facebook.com/l.php?u=", diff --git a/app/src/main/kotlin/com/pitchedapps/frost/services/DownloadService.kt b/app/src/main/kotlin/com/pitchedapps/frost/services/DownloadService.kt index fda5ebf58..520d750f2 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/services/DownloadService.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/services/DownloadService.kt @@ -1,5 +1,6 @@ package com.pitchedapps.frost.services +import android.annotation.SuppressLint import android.app.IntentService import android.app.Notification import android.app.PendingIntent @@ -26,11 +27,14 @@ import java.io.File /** * Created by Allan Wang on 2017-08-08. * + * Not in use + * * Background file downloader * All we are given is a link and a mime type * * With reference to the OkHttp3 sample */ +@SuppressLint("Registered") class DownloadService : IntentService("FrostVideoDownloader") { companion object { diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/Animator.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/Animator.kt deleted file mode 100644 index da852e6e3..000000000 --- a/app/src/main/kotlin/com/pitchedapps/frost/utils/Animator.kt +++ /dev/null @@ -1,70 +0,0 @@ -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/Downloader.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/Downloader.kt index 3e1e1ddea..e6db8eee2 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/utils/Downloader.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Downloader.kt @@ -8,10 +8,15 @@ import android.os.Environment import android.webkit.URLUtil import ca.allanwang.kau.permissions.PERMISSION_WRITE_EXTERNAL_STORAGE import ca.allanwang.kau.permissions.kauRequestPermissions +import ca.allanwang.kau.utils.isAppEnabled 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 +import android.support.v4.content.ContextCompat.startActivity +import android.content.Intent +import android.content.ActivityNotFoundException +import ca.allanwang.kau.utils.showAppInfo /** @@ -37,6 +42,16 @@ fun Context.frostDownload(uri: Uri?, L.d("Received download request", "Download $uri") if (uri.scheme != "http" && uri.scheme != "https") return L.e("Invalid download attempt", uri.toString()) + if (!isAppEnabled(DOWNLOAD_MANAGER_PACKAGE)) { + materialDialogThemed { + title(R.string.no_download_manager) + content(R.string.no_download_manager_desc) + positiveText(R.string.kau_yes) + onPositive { _, _ -> showAppInfo(DOWNLOAD_MANAGER_PACKAGE) } + negativeText(R.string.kau_no) + } + return + } kauRequestPermissions(PERMISSION_WRITE_EXTERNAL_STORAGE) { granted, _ -> if (!granted) return@kauRequestPermissions val request = DownloadManager.Request(uri) @@ -53,4 +68,6 @@ fun Context.frostDownload(uri: Uri?, val dm = getSystemService(DOWNLOAD_SERVICE) as DownloadManager dm.enqueue(request) } -} \ No newline at end of file +} + +private const val DOWNLOAD_MANAGER_PACKAGE = "com.android.providers.downloads" \ 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 22c77f5f8..c644499ea 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt @@ -202,7 +202,7 @@ inline val String?.isFacebookUrl get() = this != null && this.contains(FACEBOOK_COM) inline val String?.isVideoUrl - get() = this != null && this.startsWith(VIDEO_REDIRECT) + get() = this != null && (this.startsWith(VIDEO_REDIRECT) || this.startsWith("https://video-")) fun Context.frostChangelog() = showChangelog(R.xml.frost_changelog, Prefs.textColor) { theme() 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 639dc9baa..9d5e199a0 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostVideoView.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostVideoView.kt @@ -8,12 +8,12 @@ import android.util.AttributeSet import android.view.GestureDetector import android.view.MotionEvent import android.view.View +import ca.allanwang.kau.ui.ProgressAnimator 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. @@ -83,7 +83,7 @@ class FrostVideoView @JvmOverloads constructor( ProgressAnimator.ofFloat { duration = ANIMATION_DURATION interpolator = AnimHolder.fastOutSlowInInterpolator(context) - withAnimatorInv { viewerContract.onExpand(it) } + withAnimator { viewerContract.onExpand(1f - it) } withAnimator(origScale, scale) { scaleXY = it } withAnimator(origX, tX) { translationX = it } withAnimator(origY, tY) { translationY = it } 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 ca88a23e0..e8f9fee9f 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostUrlOverlayValidator.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostUrlOverlayValidator.kt @@ -3,11 +3,13 @@ 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.VideoViewHolder 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.* +import org.jetbrains.anko.runOnUiThread /** * Created by Allan Wang on 2017-08-15. @@ -18,6 +20,9 @@ import com.pitchedapps.frost.utils.* * This helper method will collect all known cases and launch the overlay accordingly * Returns {@code true} (default) if action is consumed, {@code false} otherwise * + * Note that this is not always called on the main thread! + * UI related methods should always be posted or they may not be properly executed. + * * 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, * as we have no need of sending a new intent to the same activity @@ -26,7 +31,7 @@ fun FrostWebViewCore.requestWebOverlay(url: String): Boolean { if (url == "#") return false if (url.isVideoUrl && context is VideoViewHolder) { L.i("Found video", url) - (context as VideoViewHolder).showVideo(url) + context.runOnUiThread { (context as VideoViewHolder).showVideo(url) } return true } if (!Prefs.overlayEnabled) return false diff --git a/app/src/main/res/values/strings_errors.xml b/app/src/main/res/values/strings_errors.xml index cf1a48cb1..884286527 100644 --- a/app/src/main/res/values/strings_errors.xml +++ b/app/src/main/res/values/strings_errors.xml @@ -4,4 +4,6 @@ The url could not be loaded properly. Would you like to send it for debugging? Invalid Share Url You have shared a block of text that is not a url. The text has been copied to your clipboard, so you may share it manually yourself. + No Download Manager + The download manager is not enabled. Would you like to enable it to allow downloads? \ No newline at end of file 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 79cde1377..62b7cac2b 100644 --- a/app/src/test/kotlin/com/pitchedapps/frost/facebook/FbUrlTest.kt +++ b/app/src/test/kotlin/com/pitchedapps/frost/facebook/FbUrlTest.kt @@ -9,7 +9,8 @@ import kotlin.test.assertEquals */ class FbUrlTest { - fun assertFbFormat(expected: String, url: String) { + @Suppress("NOTHING_TO_INLINE") + inline fun assertFbFormat(expected: String, url: String) { val fbUrl = FbUrlFormatter(url) assertEquals(expected, fbUrl.toString(), "FbUrl Mismatch:\n${fbUrl.toLogList().joinToString("\n\t")}") } @@ -52,9 +53,8 @@ class FbUrlTest { 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" + val expected = "https://video-yyz1-1.xx.fbcdn.net/v/t42.1790-2/2349078999904_n.mp4?efg=eyJ87J9&oh=f5777784&oe=56FD4&source=media_collage&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 diff --git a/gradle.properties b/gradle.properties index 03b000afb..d5013a4d5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -17,7 +17,7 @@ MIN_SDK=21 TARGET_SDK=26 BUILD_TOOLS=26.0.2 -KAU=1b7368f +KAU=1bc48a6 KOTLIN=1.1.51 COMMONS_TEXT=1.1