mirror of
https://github.com/AllanWang/Frost-for-Facebook.git
synced 2024-11-08 20:12:39 +01:00
Enhancement/debug mode (#779)
* Update changelog * Improve debugger * Remove need for mapping urls * Remove excess logs * Clean up
This commit is contained in:
parent
67988a25d8
commit
fe51373f5a
@ -59,7 +59,7 @@ class AboutActivity : AboutActivityBase(null, {
|
||||
"subsamplingscaleimageview"
|
||||
)
|
||||
|
||||
val l = libs.prepareLibraries(this, include, null, false, true,true)
|
||||
val l = libs.prepareLibraries(this, include, null, false, true, true)
|
||||
// l.forEach { KL.d{"Lib ${it.definedName}"} }
|
||||
return l
|
||||
}
|
||||
@ -88,14 +88,18 @@ class AboutActivity : AboutActivityBase(null, {
|
||||
if (item is LibraryIItem) {
|
||||
val now = System.currentTimeMillis()
|
||||
if (now - lastClick > 500)
|
||||
clickCount = 0
|
||||
clickCount = 1
|
||||
else
|
||||
clickCount++
|
||||
lastClick = now
|
||||
if (clickCount == 7 && !Prefs.debugSettings) {
|
||||
Prefs.debugSettings = true
|
||||
L.d { "Debugging section enabled" }
|
||||
toast(R.string.debug_toast_enabled)
|
||||
if (clickCount == 8) {
|
||||
if (!Prefs.debugSettings) {
|
||||
Prefs.debugSettings = true
|
||||
L.d { "Debugging section enabled" }
|
||||
toast(R.string.debug_toast_enabled)
|
||||
} else {
|
||||
toast(R.string.debug_toast_already_enabled)
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
|
@ -15,10 +15,15 @@ import ca.allanwang.kau.utils.visible
|
||||
import com.mikepenz.google_material_typeface_library.GoogleMaterial
|
||||
import com.pitchedapps.frost.R
|
||||
import com.pitchedapps.frost.facebook.FbItem
|
||||
import com.pitchedapps.frost.injectors.JsActions
|
||||
import com.pitchedapps.frost.utils.L
|
||||
import com.pitchedapps.frost.utils.Prefs
|
||||
import com.pitchedapps.frost.utils.createFreshDir
|
||||
import com.pitchedapps.frost.utils.setFrostColors
|
||||
import com.pitchedapps.frost.web.DebugWebView
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
@ -33,6 +38,8 @@ class DebugActivity : KauBaseActivity() {
|
||||
|
||||
companion object {
|
||||
const val RESULT_URL = "extra_result_url"
|
||||
const val RESULT_SCREENSHOT = "extra_result_screenshot"
|
||||
const val RESULT_BODY = "extra_result_body"
|
||||
fun baseDir(context: Context) = File(context.externalCacheDir, "offline_debug")
|
||||
}
|
||||
|
||||
@ -61,13 +68,34 @@ class DebugActivity : KauBaseActivity() {
|
||||
|
||||
val parent = baseDir(this)
|
||||
parent.createFreshDir()
|
||||
val file = File(parent, "screenshot.png")
|
||||
web.getScreenshot(file) {
|
||||
val intent = Intent()
|
||||
intent.putExtra(RESULT_URL, web.url)
|
||||
setResult(Activity.RESULT_OK, intent)
|
||||
finish()
|
||||
}
|
||||
val rxScreenshot = Single.fromCallable {
|
||||
web.getScreenshot(File(parent, "screenshot.png"))
|
||||
}.subscribeOn(Schedulers.io())
|
||||
val rxBody = Single.create<String> { emitter ->
|
||||
web.evaluateJavascript(JsActions.RETURN_BODY.function) {
|
||||
emitter.onSuccess(it)
|
||||
}
|
||||
}.subscribeOn(AndroidSchedulers.mainThread())
|
||||
Single.zip(listOf(rxScreenshot, rxBody), {
|
||||
val screenshot = it[0] == true
|
||||
val body = it[1] as? String
|
||||
screenshot to body
|
||||
}).observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { (screenshot, body), err ->
|
||||
if (err != null) {
|
||||
L.e { "DebugActivity error ${err.message}" }
|
||||
setResult(Activity.RESULT_CANCELED)
|
||||
finish()
|
||||
return@subscribe
|
||||
}
|
||||
val intent = Intent()
|
||||
intent.putExtra(RESULT_URL, web.url)
|
||||
intent.putExtra(RESULT_SCREENSHOT, screenshot)
|
||||
if (body != null)
|
||||
intent.putExtra(RESULT_BODY, body)
|
||||
setResult(Activity.RESULT_OK, intent)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -92,7 +92,8 @@ class ImageActivity : KauBaseActivity() {
|
||||
|
||||
// a unique image identifier based on the id (if it exists), and its hash
|
||||
private val IMAGE_HASH: String by lazy {
|
||||
"${Math.abs(FB_IMAGE_ID_MATCHER.find(IMAGE_URL)[1]?.hashCode() ?: 0)}_${Math.abs(IMAGE_URL.hashCode())}"
|
||||
"${Math.abs(FB_IMAGE_ID_MATCHER.find(IMAGE_URL)[1]?.hashCode()
|
||||
?: 0)}_${Math.abs(IMAGE_URL.hashCode())}"
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
@ -83,32 +83,32 @@ class LoginActivity : BaseActivity() {
|
||||
usernameSubject,
|
||||
BiFunction(::Pair))
|
||||
.observeOn(AndroidSchedulers.mainThread()).subscribe { (foundImage, name) ->
|
||||
refresh = false
|
||||
if (!foundImage) {
|
||||
L.e { "Could not get profile photo; Invalid userId?" }
|
||||
L._i { cookie }
|
||||
}
|
||||
textview.text = String.format(getString(R.string.welcome), name)
|
||||
textview.fadeIn()
|
||||
frostAnswers {
|
||||
logLogin(LoginEvent()
|
||||
.putMethod("frost_browser")
|
||||
.putSuccess(true))
|
||||
}
|
||||
/*
|
||||
* The user may have logged into an account that is already in the database
|
||||
* We will let the db handle duplicates and load it now after the new account has been saved
|
||||
*/
|
||||
loadFbCookiesAsync {
|
||||
val cookies = ArrayList(it)
|
||||
Handler().postDelayed({
|
||||
if (Showcase.intro)
|
||||
launchNewTask<IntroActivity>(cookies, true)
|
||||
else
|
||||
launchNewTask<MainActivity>(cookies, true)
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
refresh = false
|
||||
if (!foundImage) {
|
||||
L.e { "Could not get profile photo; Invalid userId?" }
|
||||
L._i { cookie }
|
||||
}
|
||||
textview.text = String.format(getString(R.string.welcome), name)
|
||||
textview.fadeIn()
|
||||
frostAnswers {
|
||||
logLogin(LoginEvent()
|
||||
.putMethod("frost_browser")
|
||||
.putSuccess(true))
|
||||
}
|
||||
/*
|
||||
* The user may have logged into an account that is already in the database
|
||||
* We will let the db handle duplicates and load it now after the new account has been saved
|
||||
*/
|
||||
loadFbCookiesAsync {
|
||||
val cookies = ArrayList(it)
|
||||
Handler().postDelayed({
|
||||
if (Showcase.intro)
|
||||
launchNewTask<IntroActivity>(cookies, true)
|
||||
else
|
||||
launchNewTask<MainActivity>(cookies, true)
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
loadProfile(cookie.id)
|
||||
loadUsername(cookie)
|
||||
}
|
||||
@ -117,17 +117,17 @@ class LoginActivity : BaseActivity() {
|
||||
private fun loadProfile(id: Long) {
|
||||
profileLoader.load(PROFILE_PICTURE_URL(id))
|
||||
.transform(FrostGlide.roundCorner).listener(object : RequestListener<Drawable> {
|
||||
override fun onResourceReady(resource: Drawable?, model: Any?, target: Target<Drawable>?, dataSource: DataSource?, isFirstResource: Boolean): Boolean {
|
||||
profileSubject.onSuccess(true)
|
||||
return false
|
||||
}
|
||||
override fun onResourceReady(resource: Drawable?, model: Any?, target: Target<Drawable>?, dataSource: DataSource?, isFirstResource: Boolean): Boolean {
|
||||
profileSubject.onSuccess(true)
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Drawable>?, isFirstResource: Boolean): Boolean {
|
||||
e.logFrostAnswers("Profile loading exception")
|
||||
profileSubject.onSuccess(false)
|
||||
return false
|
||||
}
|
||||
}).into(profile)
|
||||
override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Drawable>?, isFirstResource: Boolean): Boolean {
|
||||
e.logFrostAnswers("Profile loading exception")
|
||||
profileSubject.onSuccess(false)
|
||||
return false
|
||||
}
|
||||
}).into(profile)
|
||||
}
|
||||
|
||||
private fun loadUsername(cookie: CookieModel) {
|
||||
|
@ -52,7 +52,7 @@ class SettingsActivity : KPrefActivity(), FrostBilling by IabSettings() {
|
||||
ACTIVITY_REQUEST_DEBUG -> {
|
||||
val url = data?.extras?.getString(DebugActivity.RESULT_URL)
|
||||
if (resultCode == Activity.RESULT_OK && url?.isNotBlank() == true)
|
||||
sendDebug(url)
|
||||
sendDebug(url, data.extras.getString(DebugActivity.RESULT_BODY))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ interface MainActivityContract : ActivityContract, MainFabContract {
|
||||
* Available on all threads
|
||||
*/
|
||||
fun collapseAppBar()
|
||||
|
||||
fun reloadFragment(fragment: BaseFragment)
|
||||
}
|
||||
|
||||
|
@ -15,8 +15,7 @@ interface VideoViewHolder : FrameWrapper, FrostVideoContainerContract {
|
||||
|
||||
var videoViewer: FrostVideoViewer?
|
||||
|
||||
fun showVideo(url: String)
|
||||
= showVideo(url, false)
|
||||
fun showVideo(url: String) = showVideo(url, false)
|
||||
|
||||
/**
|
||||
* Create new viewer and reuse existing one
|
||||
|
@ -29,7 +29,8 @@ class NotificationMigration2(modelClass: Class<NotificationModel>) : AlterTableM
|
||||
@Table(database = NotificationDb::class, allFields = true, primaryKeyConflict = ConflictAction.REPLACE)
|
||||
data class NotificationModel(@PrimaryKey var id: Long = -1L, var epoch: Long = -1L, var epochIm: Long = -1) : BaseModel()
|
||||
|
||||
fun lastNotificationTime(id: Long): NotificationModel = (select from NotificationModel::class where (NotificationModel_Table.id eq id)).querySingle() ?: NotificationModel(id = id)
|
||||
fun lastNotificationTime(id: Long): NotificationModel = (select from NotificationModel::class where (NotificationModel_Table.id eq id)).querySingle()
|
||||
?: NotificationModel(id = id)
|
||||
|
||||
fun saveNotificationTime(notificationModel: NotificationModel, callback: (() -> Unit)? = null) {
|
||||
notificationModel.async save {
|
||||
|
@ -9,8 +9,11 @@ import com.pitchedapps.frost.facebook.requests.zip
|
||||
import com.pitchedapps.frost.utils.createFreshDir
|
||||
import com.pitchedapps.frost.utils.createFreshFile
|
||||
import com.pitchedapps.frost.utils.frostJsoup
|
||||
import com.pitchedapps.frost.utils.unescapeHtml
|
||||
import okhttp3.Request
|
||||
import okhttp3.ResponseBody
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import org.jsoup.nodes.Entities
|
||||
import java.io.File
|
||||
@ -29,6 +32,8 @@ import java.util.zip.ZipOutputStream
|
||||
*/
|
||||
class OfflineWebsite(private val url: String,
|
||||
private val cookie: String = "",
|
||||
baseUrl: String? = null,
|
||||
private val html: String? = null,
|
||||
/**
|
||||
* Directory that holds all the files
|
||||
*/
|
||||
@ -38,7 +43,8 @@ class OfflineWebsite(private val url: String,
|
||||
/**
|
||||
* Supplied url without the queries
|
||||
*/
|
||||
val baseUrl = url.substringBefore("?").trim('/')
|
||||
private val baseUrl = (baseUrl ?: url.substringBefore("?")
|
||||
.substringBefore(".com")).trim('/')
|
||||
|
||||
private val mainFile = File(baseDir, "index.html")
|
||||
private val assetDir = File(baseDir, "assets")
|
||||
@ -50,7 +56,7 @@ class OfflineWebsite(private val url: String,
|
||||
private val L = KauLoggerExtension("Offline", com.pitchedapps.frost.utils.L)
|
||||
|
||||
init {
|
||||
if (!baseUrl.startsWith("http"))
|
||||
if (!this.baseUrl.startsWith("http"))
|
||||
throw IllegalArgumentException("Base Url must start with http")
|
||||
}
|
||||
|
||||
@ -94,7 +100,13 @@ class OfflineWebsite(private val url: String,
|
||||
|
||||
if (cancelled) return
|
||||
|
||||
val doc = frostJsoup(cookie, url)
|
||||
val doc: Document
|
||||
if (html == null || html.length < 100) {
|
||||
doc = frostJsoup(cookie, url)
|
||||
} else {
|
||||
doc = Jsoup.parse("<html>${html.unescapeHtml()}</html>")
|
||||
L.d { "Building data from supplied content of size ${html.length}" }
|
||||
}
|
||||
doc.setBaseUri(baseUrl)
|
||||
doc.outputSettings().escapeMode(Entities.EscapeMode.extended)
|
||||
if (doc.childNodeSize() == 0) {
|
||||
@ -125,8 +137,11 @@ class OfflineWebsite(private val url: String,
|
||||
progress(50)
|
||||
|
||||
downloadCss().subscribe { cssLinks, cssThrowable ->
|
||||
|
||||
if (cssThrowable != null) {
|
||||
L.e { "CSS parsing failed" }
|
||||
L.e { "CSS parsing failed: ${cssThrowable.message} $cssThrowable" }
|
||||
callback(false)
|
||||
return@subscribe
|
||||
}
|
||||
|
||||
progress(70)
|
||||
@ -291,8 +306,8 @@ class OfflineWebsite(private val url: String,
|
||||
private fun String.shorten() =
|
||||
if (length <= 10) this else substring(length - 10)
|
||||
|
||||
private fun Set<String>.clean()
|
||||
= filter(String::isNotBlank).filter { it.startsWith("http") }
|
||||
private fun Set<String>.clean(): List<String> =
|
||||
filter(String::isNotBlank).filter { it.startsWith("http") }
|
||||
|
||||
private fun reset() {
|
||||
cancelled = false
|
||||
|
@ -130,6 +130,8 @@ fun String.getAuth(): RequestAuth {
|
||||
|
||||
inline fun <T, reified R : Any, O> Array<T>.zip(crossinline mapper: (List<R>) -> O,
|
||||
crossinline caller: (T) -> R): Single<O> {
|
||||
if (isEmpty())
|
||||
return Single.just(mapper(emptyList()))
|
||||
val singles = map { Single.fromCallable { caller(it) }.subscribeOn(Schedulers.io()) }
|
||||
return Single.zip(singles) {
|
||||
val results = it.mapNotNull { it as? R }
|
||||
|
@ -76,7 +76,8 @@ class HdImageLoading : ModelLoader<HdImageMaybe, InputStream> {
|
||||
|
||||
class HdImageFetcher(private val model: HdImageMaybe) : DataFetcher<InputStream> {
|
||||
|
||||
@Volatile private var cancelled: Boolean = false
|
||||
@Volatile
|
||||
private var cancelled: Boolean = false
|
||||
private var urlCall: Call? = null
|
||||
private var inputStream: InputStream? = null
|
||||
|
||||
@ -92,7 +93,8 @@ class HdImageFetcher(private val model: HdImageMaybe) : DataFetcher<InputStream>
|
||||
if (!model.isValid) return callback.fail("Model is invalid")
|
||||
model.cookie.fbRequest(fail = { callback.fail("Invalid auth") }) {
|
||||
if (cancelled) return@fbRequest callback.fail("Cancelled")
|
||||
val url = getFullSizedImage(model.id).invoke() ?: return@fbRequest callback.fail("Null url")
|
||||
val url = getFullSizedImage(model.id).invoke()
|
||||
?: return@fbRequest callback.fail("Null url")
|
||||
if (cancelled) return@fbRequest callback.fail("Cancelled")
|
||||
if (!url.contains("png") && !url.contains("jpg")) return@fbRequest callback.fail("Invalid format")
|
||||
urlCall = Request.Builder().url(url).get().call()
|
||||
|
@ -1,6 +1,5 @@
|
||||
package com.pitchedapps.frost.fragments
|
||||
|
||||
import android.support.design.widget.FloatingActionButton
|
||||
import com.pitchedapps.frost.contracts.*
|
||||
import com.pitchedapps.frost.views.FrostRecyclerView
|
||||
import io.reactivex.disposables.Disposable
|
||||
|
@ -9,7 +9,7 @@ import android.webkit.WebView
|
||||
*/
|
||||
enum class CssHider(vararg val items: String) : InjectorContract {
|
||||
CORE("[data-sigil=m_login_upsell]", "role=progressbar"),
|
||||
// HEADER("#header", "[data-sigil=MTopBlueBarHeader]",
|
||||
// HEADER("#header", "[data-sigil=MTopBlueBarHeader]",
|
||||
// "#header-notices", "[data-sigil*=m-promo-jewel-header]"),
|
||||
ADS("article[data-xt*=sponsor]",
|
||||
"article[data-store*=sponsor]"),
|
||||
|
@ -16,6 +16,7 @@ enum class JsActions(body: String) : InjectorContract {
|
||||
LOGIN_CHECK("document.getElementById('signup-button')&&Frost.loadLogin();"),
|
||||
BASE_HREF("""document.write("<base href='$FB_URL_BASE'/>");"""),
|
||||
FETCH_BODY("""setTimeout(function(){var e=document.querySelector("main");e||(e=document.querySelector("body")),Frost.handleHtml(e.outerHTML)},1e2);"""),
|
||||
RETURN_BODY("return(document.getElementsByTagName('html')[0].innerHTML);"),
|
||||
CREATE_POST(clickBySelector("button[name=view_overview]")),
|
||||
// CREATE_MSG(clickBySelector("a[rel=dialog]")),
|
||||
/**
|
||||
@ -23,7 +24,7 @@ enum class JsActions(body: String) : InjectorContract {
|
||||
*/
|
||||
EMPTY("");
|
||||
|
||||
val function = "!function(){$body}();"
|
||||
val function = "(function(){$body})();"
|
||||
|
||||
override fun inject(webView: WebView, callback: (() -> Unit)?) =
|
||||
JsInjector(function).inject(webView, callback)
|
||||
|
@ -101,8 +101,7 @@ fun WebView.jsInject(vararg injectors: InjectorContract, callback: ((Int) -> Uni
|
||||
}
|
||||
|
||||
fun FrostWebViewClient.jsInject(vararg injectors: InjectorContract,
|
||||
callback: ((Int) -> Unit)? = null)
|
||||
= web.jsInject(*injectors, callback = callback)
|
||||
callback: ((Int) -> Unit)? = null) = web.jsInject(*injectors, callback = callback)
|
||||
|
||||
/**
|
||||
* Wrapper class to convert a function into an injector
|
||||
|
@ -22,8 +22,7 @@ class IntroFragmentTheme : BaseIntroFragment(R.layout.intro_theme) {
|
||||
val themeList
|
||||
get() = listOf(light, dark, amoled, glass)
|
||||
|
||||
override fun viewArray(): Array<Array<out View>>
|
||||
= arrayOf(arrayOf(title), arrayOf(light, dark), arrayOf(amoled, glass))
|
||||
override fun viewArray(): Array<Array<out View>> = arrayOf(arrayOf(title), arrayOf(light, dark), arrayOf(amoled, glass))
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
@ -25,8 +25,7 @@ abstract class BaseImageIntroFragment(
|
||||
val screen: Drawable by lazyResettableRegistered { imageDrawable.findDrawableByLayerId(R.id.intro_phone_screen) }
|
||||
val icon: ImageView by bindViewResettable(R.id.intro_button)
|
||||
|
||||
override fun viewArray(): Array<Array<out View>>
|
||||
= arrayOf(arrayOf(title), arrayOf(desc))
|
||||
override fun viewArray(): Array<Array<out View>> = arrayOf(arrayOf(title), arrayOf(desc))
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
title.setText(titleRes)
|
||||
|
@ -1,6 +1,5 @@
|
||||
package com.pitchedapps.frost.services
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Notification
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
@ -93,7 +92,7 @@ fun NotificationCompat.Builder.setFrostAlert(enable: Boolean, ringtone: String):
|
||||
if (enable) Notification.GROUP_ALERT_CHILDREN
|
||||
else Notification.GROUP_ALERT_SUMMARY)
|
||||
} else if (!enable) {
|
||||
setDefaults(0)
|
||||
setDefaults(0)
|
||||
} else {
|
||||
var defaults = 0
|
||||
if (Prefs.notificationVibrate) defaults = defaults or Notification.DEFAULT_VIBRATE
|
||||
|
@ -10,6 +10,7 @@ import com.pitchedapps.frost.activities.DebugActivity
|
||||
import com.pitchedapps.frost.activities.SettingsActivity
|
||||
import com.pitchedapps.frost.activities.SettingsActivity.Companion.ACTIVITY_REQUEST_DEBUG
|
||||
import com.pitchedapps.frost.debugger.OfflineWebsite
|
||||
import com.pitchedapps.frost.facebook.FB_URL_BASE
|
||||
import com.pitchedapps.frost.facebook.FbCookie
|
||||
import com.pitchedapps.frost.facebook.FbItem
|
||||
import com.pitchedapps.frost.parsers.FrostParser
|
||||
@ -90,18 +91,12 @@ private fun Context.createEmail(parser: FrostParser<*>, content: Any?) =
|
||||
|
||||
private const val ZIP_NAME = "debug"
|
||||
|
||||
fun SettingsActivity.sendDebug(urlOrig: String) {
|
||||
|
||||
val url = when {
|
||||
urlOrig.endsWith("soft=requests") -> FbItem.FRIENDS.url
|
||||
urlOrig.endsWith("soft=messages") -> FbItem.MESSAGES.url
|
||||
urlOrig.endsWith("soft=notifications") -> FbItem.NOTIFICATIONS.url
|
||||
urlOrig.endsWith("soft=search") -> "${FbItem._SEARCH.url}?q=a"
|
||||
else -> urlOrig
|
||||
}
|
||||
fun SettingsActivity.sendDebug(url: String, html: String?) {
|
||||
|
||||
val downloader = OfflineWebsite(url, FbCookie.webCookie ?: "",
|
||||
DebugActivity.baseDir(this))
|
||||
baseUrl = FB_URL_BASE,
|
||||
html = html,
|
||||
baseDir = DebugActivity.baseDir(this))
|
||||
|
||||
val md = materialDialog {
|
||||
title(R.string.parsing_data)
|
||||
|
@ -54,8 +54,10 @@ class AnimatedVectorDelegate(
|
||||
|
||||
override fun bind(view: ImageView) {
|
||||
this.view = view
|
||||
view.context.drawable(avdStart) as? AnimatedVectorDrawable ?: throw IllegalArgumentException("AnimatedVectorDelegate has a starting drawable that isn't an avd")
|
||||
view.context.drawable(avdEnd) as? AnimatedVectorDrawable ?: throw IllegalArgumentException("AnimatedVectorDelegate has an ending drawable that isn't an avd")
|
||||
view.context.drawable(avdStart) as? AnimatedVectorDrawable
|
||||
?: throw IllegalArgumentException("AnimatedVectorDelegate has a starting drawable that isn't an avd")
|
||||
view.context.drawable(avdEnd) as? AnimatedVectorDrawable
|
||||
?: throw IllegalArgumentException("AnimatedVectorDelegate has an ending drawable that isn't an avd")
|
||||
view.setImageResource(avdStart)
|
||||
if (emitOnBind) animatedVectorListener?.invoke(avd!!, false)
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ 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.apache.commons.text.StringEscapeUtils
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Element
|
||||
import java.io.File
|
||||
@ -343,3 +344,8 @@ fun File.createFreshDir(): Boolean {
|
||||
return false
|
||||
return mkdirs()
|
||||
}
|
||||
|
||||
fun String.unescapeHtml(): String =
|
||||
StringEscapeUtils.unescapeXml(this)
|
||||
.replace("\\u003C", "<")
|
||||
.replace("\\\"", "\"")
|
||||
|
@ -94,8 +94,8 @@ abstract class IabBinder : FrostBilling {
|
||||
error.logFrostAnswers("IAB error $errorCode")
|
||||
}
|
||||
|
||||
override fun onActivityResultBilling(requestCode: Int, resultCode: Int, data: Intent?): Boolean
|
||||
= bp?.handleActivityResult(requestCode, resultCode, data) ?: false
|
||||
override fun onActivityResultBilling(requestCode: Int, resultCode: Int, data: Intent?): Boolean = bp?.handleActivityResult(requestCode, resultCode, data)
|
||||
?: false
|
||||
|
||||
override fun purchasePro() {
|
||||
val bp = this.bp
|
||||
|
@ -34,16 +34,16 @@ class AccountItem(val cookie: CookieModel?) : KauIItem<AccountItem, AccountItem.
|
||||
text.text = cookie.name
|
||||
GlideApp.with(itemView).load(PROFILE_PICTURE_URL(cookie.id))
|
||||
.transform(FrostGlide.roundCorner).listener(object : RequestListener<Drawable> {
|
||||
override fun onResourceReady(resource: Drawable?, model: Any?, target: Target<Drawable>?, dataSource: DataSource?, isFirstResource: Boolean): Boolean {
|
||||
text.fadeIn()
|
||||
return false
|
||||
}
|
||||
override fun onResourceReady(resource: Drawable?, model: Any?, target: Target<Drawable>?, dataSource: DataSource?, isFirstResource: Boolean): Boolean {
|
||||
text.fadeIn()
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Drawable>?, isFirstResource: Boolean): Boolean {
|
||||
text.fadeIn()
|
||||
return false
|
||||
}
|
||||
}).into(image)
|
||||
override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Drawable>?, isFirstResource: Boolean): Boolean {
|
||||
text.fadeIn()
|
||||
return false
|
||||
}
|
||||
}).into(image)
|
||||
} else {
|
||||
text.visible()
|
||||
image.setImageDrawable(GoogleMaterial.Icon.gmd_add_circle_outline.toDrawable(itemView.context, 100, Prefs.textColor))
|
||||
|
@ -16,7 +16,6 @@ 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
|
||||
import com.pitchedapps.frost.utils.L
|
||||
import com.pitchedapps.frost.utils.Prefs
|
||||
import com.pitchedapps.frost.utils.frostDownload
|
||||
|
@ -52,8 +52,7 @@ class Keywords @JvmOverloads constructor(
|
||||
recycler.layoutManager = LinearLayoutManager(context)
|
||||
recycler.adapter = adapter
|
||||
adapter.withEventHook(object : ClickEventHook<KeywordItem>() {
|
||||
override fun onBind(viewHolder: RecyclerView.ViewHolder): View?
|
||||
= (viewHolder as? KeywordItem.ViewHolder)?.delete
|
||||
override fun onBind(viewHolder: RecyclerView.ViewHolder): View? = (viewHolder as? KeywordItem.ViewHolder)?.delete
|
||||
|
||||
override fun onClick(v: View, position: Int, fastAdapter: FastAdapter<KeywordItem>, item: KeywordItem) {
|
||||
adapter.remove(position)
|
||||
|
@ -4,6 +4,7 @@ import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Color
|
||||
import android.support.annotation.WorkerThread
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.webkit.WebView
|
||||
@ -16,8 +17,6 @@ import com.pitchedapps.frost.utils.Prefs
|
||||
import com.pitchedapps.frost.utils.createFreshFile
|
||||
import com.pitchedapps.frost.utils.iab.IS_FROST_PRO
|
||||
import com.pitchedapps.frost.utils.isFacebookUrl
|
||||
import org.jetbrains.anko.doAsync
|
||||
import org.jetbrains.anko.uiThread
|
||||
import org.jetbrains.anko.withAlpha
|
||||
import java.io.File
|
||||
|
||||
@ -45,27 +44,22 @@ class DebugWebView @JvmOverloads constructor(
|
||||
isDrawingCacheEnabled = true
|
||||
}
|
||||
|
||||
fun getScreenshot(output: File, callback: (Boolean) -> Unit) {
|
||||
@WorkerThread
|
||||
fun getScreenshot(output: File): Boolean {
|
||||
|
||||
if (!output.createFreshFile()) {
|
||||
L.e { "Failed to create ${output.absolutePath} for debug screenshot" }
|
||||
return callback(false)
|
||||
return false
|
||||
}
|
||||
doAsync {
|
||||
var valid = true
|
||||
try {
|
||||
output.outputStream().use {
|
||||
drawingCache.compress(Bitmap.CompressFormat.PNG, 100, it)
|
||||
}
|
||||
L.d { "Created screenshot at ${output.absolutePath}" }
|
||||
} catch (e: Exception) {
|
||||
L.e { "An error occurred ${e.message}" }
|
||||
valid = false
|
||||
} finally {
|
||||
uiThread {
|
||||
callback(valid)
|
||||
}
|
||||
return try {
|
||||
output.outputStream().use {
|
||||
drawingCache.compress(Bitmap.CompressFormat.PNG, 100, it)
|
||||
}
|
||||
L.d { "Created screenshot at ${output.absolutePath}" }
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
L.e { "An error occurred ${e.message}" }
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -46,7 +46,8 @@ class FrostChromeClient(web: FrostWebView) : WebChromeClient() {
|
||||
}
|
||||
|
||||
override fun onShowFileChooser(webView: WebView, filePathCallback: ValueCallback<Array<Uri>?>, fileChooserParams: FileChooserParams): Boolean {
|
||||
activity?.openFileChooser(filePathCallback, fileChooserParams) ?: webView.frostSnackbar(R.string.file_chooser_not_found)
|
||||
activity?.openFileChooser(filePathCallback, fileChooserParams)
|
||||
?: webView.frostSnackbar(R.string.file_chooser_not_found)
|
||||
return activity != null
|
||||
}
|
||||
|
||||
|
@ -46,15 +46,12 @@ val WebResourceRequest.isMedia: Boolean
|
||||
* Generic filter passthrough
|
||||
* If Resource is already nonnull, pass it, otherwise check if filter is met and override the response accordingly
|
||||
*/
|
||||
fun WebResourceResponse?.filter(request: WebResourceRequest, filter: (url: String) -> Boolean)
|
||||
= filter(request.query { filter(it) })
|
||||
fun WebResourceResponse?.filter(request: WebResourceRequest, filter: (url: String) -> Boolean) = filter(request.query { filter(it) })
|
||||
|
||||
fun WebResourceResponse?.filter(filter: Boolean): WebResourceResponse?
|
||||
= this ?: if (filter) blankResource else null
|
||||
fun WebResourceResponse?.filter(filter: Boolean): WebResourceResponse? = this
|
||||
?: if (filter) blankResource else null
|
||||
|
||||
fun WebResourceResponse?.filterCss(request: WebResourceRequest): WebResourceResponse?
|
||||
= filter(request) { it.endsWith(".css") }
|
||||
fun WebResourceResponse?.filterCss(request: WebResourceRequest): WebResourceResponse? = filter(request) { it.endsWith(".css") }
|
||||
|
||||
fun WebResourceResponse?.filterImage(request: WebResourceRequest): WebResourceResponse?
|
||||
= filter(request.isImage)
|
||||
fun WebResourceResponse?.filterImage(request: WebResourceRequest): WebResourceResponse? = filter(request.isImage)
|
||||
|
||||
|
@ -99,15 +99,11 @@ open class NestedWebView @JvmOverloads constructor(
|
||||
|
||||
final override fun hasNestedScrollingParent() = childHelper.hasNestedScrollingParent()
|
||||
|
||||
final override fun dispatchNestedScroll(dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int, dyUnconsumed: Int, offsetInWindow: IntArray?)
|
||||
= childHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow)
|
||||
final override fun dispatchNestedScroll(dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int, dyUnconsumed: Int, offsetInWindow: IntArray?) = childHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow)
|
||||
|
||||
final override fun dispatchNestedPreScroll(dx: Int, dy: Int, consumed: IntArray?, offsetInWindow: IntArray?)
|
||||
= childHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow)
|
||||
final override fun dispatchNestedPreScroll(dx: Int, dy: Int, consumed: IntArray?, offsetInWindow: IntArray?) = childHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow)
|
||||
|
||||
final override fun dispatchNestedFling(velocityX: Float, velocityY: Float, consumed: Boolean)
|
||||
= childHelper.dispatchNestedFling(velocityX, velocityY, consumed)
|
||||
final override fun dispatchNestedFling(velocityX: Float, velocityY: Float, consumed: Boolean) = childHelper.dispatchNestedFling(velocityX, velocityY, consumed)
|
||||
|
||||
final override fun dispatchNestedPreFling(velocityX: Float, velocityY: Float)
|
||||
= childHelper.dispatchNestedPreFling(velocityX, velocityY)
|
||||
final override fun dispatchNestedPreFling(velocityX: Float, velocityY: Float) = childHelper.dispatchNestedPreFling(velocityX, velocityY)
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
<resources>
|
||||
|
||||
<string name="debug_toast_enabled">Debugging section is enabled! Go back to settings.</string>
|
||||
<string name="debug_toast_already_enabled">Debugging section is already enabled. Go back to settings.</string>
|
||||
|
||||
<string name="debug_disclaimer_info">Though most private content is automatically removed in the report, some sensitive info may still be visible.
|
||||
\nPlease have a look at the debug report before sending it.
|
||||
|
@ -6,14 +6,16 @@
|
||||
<item text="" />
|
||||
-->
|
||||
|
||||
<version title="v1.8.3" />
|
||||
<item text="Add full notification channel support" />
|
||||
<item text="Fix sound spam for multiple notifications" />
|
||||
<item text="" />
|
||||
<item text="" />
|
||||
|
||||
<version title="v1.8.2" />
|
||||
<item text="Fix duplicate notification sounds" />
|
||||
<item text="Fix map redirecting to blackberry" />
|
||||
<item text="Fix event reservation" />
|
||||
<item text="" />
|
||||
<item text="" />
|
||||
<item text="" />
|
||||
<item text="" />
|
||||
|
||||
<version title="v1.8.1" />
|
||||
<item text="Theme new Facebook update" />
|
||||
|
@ -15,7 +15,7 @@ class OfflineWebsiteTest {
|
||||
fun basic() {
|
||||
val countdown = CountDownLatch(1)
|
||||
val buildPath = if (File(".").parentFile?.name == "app") "build/offline_test" else "app/build/offline_test"
|
||||
OfflineWebsite(FB_URL_BASE, COOKIE, File(buildPath))
|
||||
OfflineWebsite(FB_URL_BASE, COOKIE, baseDir = File(buildPath))
|
||||
.loadAndZip("test") {
|
||||
println("Outcome $it")
|
||||
countdown.countDown()
|
||||
|
@ -0,0 +1,16 @@
|
||||
package com.pitchedapps.frost.utils
|
||||
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
/**
|
||||
* Created by Allan Wang on 11/03/18.
|
||||
*/
|
||||
class StringEscapeUtilsTest {
|
||||
|
||||
@Test
|
||||
fun utf() {
|
||||
val escaped = "\\u003Chead> color=\\\"#3b5998\\\""
|
||||
assertEquals("<head> color=\"#3b5998\"", escaped.unescapeHtml())
|
||||
}
|
||||
}
|
@ -1,5 +1,9 @@
|
||||
# Changelog
|
||||
|
||||
## v1.8.3
|
||||
* Add full notification channel support
|
||||
* Fix sound spam for multiple notifications
|
||||
|
||||
## v1.8.2
|
||||
* Fix duplicate notification sounds
|
||||
* Fix map redirecting to blackberry
|
||||
|
Loading…
Reference in New Issue
Block a user