aboutsummaryrefslogtreecommitdiff
path: root/app/src/main/kotlin/com/pitchedapps
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/kotlin/com/pitchedapps')
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt2
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/activities/BaseMainActivity.kt10
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/contracts/ActivityContract.kt1
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/contracts/FrostContentContract.kt5
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/facebook/FbConst.kt3
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/facebook/FbRegex.kt20
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/facebook/FbRequest.kt98
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/utils/Downloader.kt2
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt2
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt21
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/views/FrostContentView.kt11
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt3
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/web/FrostUrlOverlayValidator.kt38
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt12
14 files changed, 179 insertions, 49 deletions
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt b/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt
index 2c313ffe..fa2bdf8a 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt
@@ -27,6 +27,7 @@ import com.pitchedapps.frost.utils.Showcase
import com.raizlabs.android.dbflow.config.DatabaseConfig
import com.raizlabs.android.dbflow.config.FlowConfig
import com.raizlabs.android.dbflow.config.FlowManager
+import com.raizlabs.android.dbflow.runtime.ContentResolverNotifier
import io.fabric.sdk.android.Fabric
import java.util.*
import kotlin.reflect.KClass
@@ -46,6 +47,7 @@ class FrostApp : Application() {
private fun FlowConfig.Builder.withDatabase(name: String, klass: KClass<*>) =
addDatabaseConfig(DatabaseConfig.builder(klass.java)
.databaseName(name)
+ .modelNotifier(ContentResolverNotifier("${BuildConfig.APPLICATION_ID}.dbflow.provider"))
.build())
override fun onCreate() {
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseMainActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseMainActivity.kt
index 389ff88e..4a9cbb55 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseMainActivity.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseMainActivity.kt
@@ -87,8 +87,8 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract,
val appBar: AppBarLayout by bindView(R.id.appbar)
val coordinator: CoordinatorLayout by bindView(R.id.main_content)
override var videoViewer: FrostVideoViewer? = null
- lateinit var drawer: Drawer
- lateinit var drawerHeader: AccountHeader
+ private lateinit var drawer: Drawer
+ private lateinit var drawerHeader: AccountHeader
override var searchView: SearchView? = null
private val searchViewCache = mutableMapOf<String, List<SearchItem>>()
@@ -96,11 +96,13 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract,
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (BuildConfig.VERSION_CODE > Prefs.versionCode) {
+ Prefs.prevVersionCode = Prefs.versionCode
Prefs.versionCode = BuildConfig.VERSION_CODE
if (!BuildConfig.DEBUG) {
frostChangelog()
frostAnswersCustom("Version",
"Version code" to BuildConfig.VERSION_CODE,
+ "Prev version code" to Prefs.prevVersionCode,
"Version name" to BuildConfig.VERSION_NAME,
"Build type" to BuildConfig.BUILD_TYPE,
"Frost id" to Prefs.frostId)
@@ -346,6 +348,10 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract,
super.onDestroy()
}
+ override fun collapseAppBar() {
+ appBar.setExpanded(false)
+ }
+
override fun backConsumer(): Boolean {
if (currentFragment.onBackPressed()) return true
if (Prefs.exitConfirmation) {
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/contracts/ActivityContract.kt b/app/src/main/kotlin/com/pitchedapps/frost/contracts/ActivityContract.kt
index f51c4e53..e46a4bfb 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/contracts/ActivityContract.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/contracts/ActivityContract.kt
@@ -11,4 +11,5 @@ interface MainActivityContract : ActivityContract {
val fragmentSubject: PublishSubject<Int>
fun setTitle(res: Int)
fun setTitle(text: CharSequence)
+ fun collapseAppBar()
} \ No newline at end of file
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/contracts/FrostContentContract.kt b/app/src/main/kotlin/com/pitchedapps/frost/contracts/FrostContentContract.kt
index 681636c4..117a1d36 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/contracts/FrostContentContract.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/contracts/FrostContentContract.kt
@@ -54,6 +54,11 @@ interface FrostContentParent : DynamicUiContract {
var baseEnum: FbItem?
/**
+ * Toggle state for allowing swipes
+ */
+ var swipeEnabled: Boolean
+
+ /**
* Binds the container to self
* this will also handle all future bindings
* Must be called by container!
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 43bc5724..d98241f1 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbConst.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbConst.kt
@@ -6,8 +6,9 @@ package com.pitchedapps.frost.facebook
const val FACEBOOK_COM = "facebook.com"
const val HTTPS_FACEBOOK_COM = "https://$FACEBOOK_COM"
-const val FB_URL_BASE = "https://m.$FACEBOOK_COM/"
+const val FB_URL_BASE = "https://touch.$FACEBOOK_COM/"
fun PROFILE_PICTURE_URL(id: Long) = "https://graph.facebook.com/$id/picture?type=large"
+const val FB_LOGIN_URL = "${FB_URL_BASE}login"
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"
const val USER_AGENT_BASIC = "Mozilla/5.0 (BB10; Kbd) AppleWebKit/537.10+ (KHTML, like Gecko) Version/10.1.0.4633 Mobile Safari/537.10+"
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbRegex.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbRegex.kt
new file mode 100644
index 00000000..39e8c467
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbRegex.kt
@@ -0,0 +1,20 @@
+package com.pitchedapps.frost.facebook
+
+/**
+ * Created by Allan Wang on 21/12/17.
+ *
+ * Collection of regex matchers
+ * Input text must be properly unescaped
+ *
+ * See [StringEscapeUtils]
+ */
+
+/**
+ * Matches the fb_dtsg component of a page containing it as a hidden value
+ */
+val FB_DTSG_MATCHER: Regex by lazy { Regex("name=\"fb_dtsg\" value=\"(.*?)\"") }
+
+/**
+ * Matches user id from cookie
+ */
+val FB_USER_MATCHER: Regex by lazy { Regex("c_user=([0-9]*);") } \ No newline at end of file
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbRequest.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbRequest.kt
new file mode 100644
index 00000000..428043a0
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbRequest.kt
@@ -0,0 +1,98 @@
+package com.pitchedapps.frost.facebook
+
+import com.pitchedapps.frost.BuildConfig
+import com.pitchedapps.frost.utils.L
+import io.reactivex.Single
+import io.reactivex.schedulers.Schedulers
+import okhttp3.*
+import okhttp3.logging.HttpLoggingInterceptor
+import org.apache.commons.text.StringEscapeUtils
+
+/**
+ * Created by Allan Wang on 21/12/17.
+ */
+data class RequestAuth(val userId: Long = -1, val cookie: String = "", val fb_dtsg: String = "")
+
+private val client: OkHttpClient by lazy {
+ val builder = OkHttpClient.Builder()
+ if (BuildConfig.DEBUG)
+ builder.addInterceptor(HttpLoggingInterceptor()
+ .setLevel(HttpLoggingInterceptor.Level.BASIC))
+ builder.build()
+}
+
+private fun List<Pair<String, Any?>>.toForm(): RequestBody {
+ val builder = FormBody.Builder()
+ forEach { (key, value) ->
+ val v = value?.toString() ?: ""
+ builder.add(key, v)
+ }
+ return builder.build()
+}
+
+private fun String.requestBuilder() = Request.Builder()
+ .header("Cookie", this)
+ .header("User-Agent", USER_AGENT_BASIC)
+ .cacheControl(CacheControl.FORCE_NETWORK)
+
+private fun Request.Builder.call() = client.newCall(build())
+
+
+fun Pair<Long, String>.getAuth(): RequestAuth? {
+ val (userId, cookie) = this
+ val call = cookie.requestBuilder()
+ .url(FB_URL_BASE)
+ .get()
+ .call()
+ call.execute().body()?.charStream()?.useLines {
+ it.forEach {
+ val text = StringEscapeUtils.unescapeEcmaScript(it)
+ val result = FB_DTSG_MATCHER.find(text)
+ val fb_dtsg = result?.groupValues?.get(1)
+ if (fb_dtsg != null) {
+ L.d(null, "fb_dtsg for $userId: $fb_dtsg")
+ return RequestAuth(userId, cookie, fb_dtsg)
+ }
+ }
+ }
+
+ return null
+}
+
+fun RequestAuth.markNotificationRead(notifId: Long): Call {
+
+ val body = listOf(
+ "click_type" to "notification_click",
+ "id" to notifId,
+ "target_id" to "null",
+ "m_sess" to null,
+ "fb_dtsg" to fb_dtsg,
+ "__dyn" to null,
+ "__req" to null,
+ "__ajax__" to null,
+ "__user" to userId
+ )
+
+ return cookie.requestBuilder()
+ .url("${FB_URL_BASE}a/jewel_notifications_log.php")
+ .post(body.toForm())
+ .call()
+}
+
+private inline fun <T, reified R : Any, O> zip(data: Array<T>,
+ crossinline mapper: (List<R>) -> O,
+ crossinline caller: (T) -> R): Single<O> {
+ val singles = data.map { Single.fromCallable { caller(it) }.subscribeOn(Schedulers.io()) }
+ return Single.zip(singles) {
+ val results = it.mapNotNull { it as? R }
+ mapper(results)
+ }
+}
+
+fun RequestAuth.markNotificationsRead(vararg notifId: Long) = zip<Long, Boolean, Int>(notifId.toTypedArray(),
+ { it.count { it } }) {
+ val response = markNotificationRead(it).execute()
+ val buffer = CharArray(20)
+ response.body()?.charStream()?.read(buffer) ?: return@zip false
+ !buffer.toString().contains("error")
+}
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 2c638dfd..aab79e00 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/utils/Downloader.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Downloader.kt
@@ -58,7 +58,7 @@ fun Context.frostDownload(uri: 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("Cookie", cookie.cookie)
request.addRequestHeader("User-Agent", userAgent)
request.setDescription(string(R.string.downloading))
request.setTitle(title)
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt
index 94bf0016..cc5ee733 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt
@@ -43,6 +43,8 @@ object Prefs : KPref() {
var versionCode: Int by kpref("version_code", -1)
+ var prevVersionCode: Int by kpref("prev_version_code", -1)
+
var installDate: Long by kpref("install_date", -1L)
var identifier: Int by kpref("identifier", -1)
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 5a83c3f3..0ca068b5 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt
@@ -229,12 +229,23 @@ inline val String?.isVideoUrl
/**
* [true] if url can be displayed in a different webview
*/
-inline val String?.isIndependent
- get() = this == null || (startsWith("http") && !isFacebookUrl)
- || dependentSet.all { !contains(it) }
+inline val String?.isIndependent: Boolean
+ get() {
+ if (this == null || length < 5) return false // ignore short queries
+ if (this[0] == '#' && !contains('/')) return false // ignore element values
+ if (startsWith("http") && !isFacebookUrl) return true // ignore non facebook urls
+ if (dependentSet.any { contains(it) }) return false // ignore known dependent segments
+ return true
+ }
val dependentSet = setOf(
- "photoset_token", "direct_action_execute"
+ "photoset_token", "direct_action_execute", "messages/?pageNum", "sharer.php",
+ /*
+ * Facebook messages have the following cases for the tid query
+ * mid* or id* for newer threads, which can be launched in new windows
+ * or a hash for old threads, which must be loaded on old threads
+ */
+ "messages/read/?tid=id", "messages/read/?tid=mid"
)
inline val String?.isExplicitIntent
@@ -254,6 +265,8 @@ inline fun Context.sendFrostEmail(@StringRes subjectId: Int, crossinline builder
inline fun Context.sendFrostEmail(subjectId: String, crossinline builder: EmailBuilder.() -> Unit)
= sendEmail(string(R.string.dev_email), subjectId) {
builder()
+
+ addItem("Prev version", Prefs.prevVersionCode.toString())
val proTag = if (IS_FROST_PRO) "TY" else "FP"
addItem("Random Frost ID", "${Prefs.frostId}-$proTag")
}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostContentView.kt b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostContentView.kt
index 58449de3..809b6090 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostContentView.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostContentView.kt
@@ -12,6 +12,7 @@ import com.pitchedapps.frost.R
import com.pitchedapps.frost.contracts.FrostContentContainer
import com.pitchedapps.frost.contracts.FrostContentCore
import com.pitchedapps.frost.contracts.FrostContentParent
+import com.pitchedapps.frost.contracts.MainActivityContract
import com.pitchedapps.frost.facebook.FbItem
import com.pitchedapps.frost.utils.Prefs
import com.pitchedapps.frost.web.WEB_LOAD_DELAY
@@ -57,6 +58,16 @@ abstract class FrostContentView<out T> @JvmOverloads constructor(
protected abstract val layoutRes: Int
+ override var swipeEnabled: Boolean
+ get() = refresh.isEnabled
+ set(value) {
+ refresh.isEnabled = value
+ if (!value) {
+ // locked onto an input field; ensure content is visible
+ (context as? MainActivityContract)?.collapseAppBar()
+ }
+ }
+
/**
* Sets up everything
* Called by [bind]
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 e8135f5b..b567801b 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt
@@ -1,6 +1,5 @@
package com.pitchedapps.frost.web
-import android.support.v4.widget.SwipeRefreshLayout
import android.webkit.JavascriptInterface
import com.pitchedapps.frost.activities.MainActivity
import com.pitchedapps.frost.contracts.VideoViewHolder
@@ -68,7 +67,7 @@ class FrostJSI(val web: FrostWebView) {
*/
@JavascriptInterface
fun disableSwipeRefresh(disable: Boolean) {
- web.post { (web.parent as? SwipeRefreshLayout)?.isEnabled = !disable }
+ web.post { web.parent.swipeEnabled = !disable }
}
@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 9255b5bb..253d4801 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostUrlOverlayValidator.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostUrlOverlayValidator.kt
@@ -29,15 +29,16 @@ import org.jetbrains.anko.runOnUiThread
* as we have no need of sending a new intent to the same activity
*/
fun FrostWebView.requestWebOverlay(url: String): Boolean {
- if (url == "#" || !url.isIndependent) {
- L.i("Forbid overlay switch", url)
- return false
- }
+ val context = context // finalize reference
if (url.isVideoUrl && context is VideoViewHolder) {
L.i("Found video", url)
- context.runOnUiThread { (context as VideoViewHolder).showVideo(url) }
+ context.runOnUiThread { context.showVideo(url) }
return true
}
+ if (!url.isIndependent) {
+ L.i("Forbid overlay switch", url)
+ return false
+ }
if (!Prefs.overlayEnabled) return false
if (context is WebOverlayActivityBase) {
L.v("Check web request from overlay", url)
@@ -55,26 +56,6 @@ fun FrostWebView.requestWebOverlay(url: String): Boolean {
L.i("return false switch")
return false
}
- /*
- * Non facebook urls can be loaded
- */
- if (!url.formattedFbUrl.isFacebookUrl) {
- context.launchWebOverlay(url)
- L.d("Request web overlay is not a facebook url", url)
- return true
- }
- /*
- * Check blacklist
- */
- if (overlayBlacklist.any { url.contains(it) }) return false
- /*
- * Facebook messages have the following cases for the tid query
- * mid* or id* for newer threads, which can be launched in new windows
- * or a hash for old threads, which must be loaded on old threads
- */
- if (url.contains("/messages/read/?tid=")) {
- if (!url.contains("?tid=id") && !url.contains("?tid=mid")) return false
- }
L.v("Request web overlay passed", url)
context.launchWebOverlay(url)
return true
@@ -87,9 +68,4 @@ val messageWhitelist = setOf(FbItem.MESSAGES, FbItem.CHAT, FbItem.FEED_MOST_RECE
val String.shouldUseBasicAgent
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
- */
-val overlayBlacklist = setOf("messages/?pageNum", "photoset_token", "sharer.php") \ No newline at end of file
+// get() = (messageWhitelist.any { contains(it) }) || this == FB_URL_BASE \ No newline at end of file
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt
index 73d2476c..3a10ed32 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt
@@ -9,8 +9,9 @@ import android.webkit.*
import ca.allanwang.kau.utils.fadeIn
import ca.allanwang.kau.utils.isVisible
import com.pitchedapps.frost.dbflow.CookieModel
-import com.pitchedapps.frost.facebook.FB_URL_BASE
+import com.pitchedapps.frost.facebook.FB_USER_MATCHER
import com.pitchedapps.frost.facebook.FbCookie
+import com.pitchedapps.frost.facebook.FB_LOGIN_URL
import com.pitchedapps.frost.injectors.CssHider
import com.pitchedapps.frost.injectors.jsInject
import com.pitchedapps.frost.utils.L
@@ -26,11 +27,6 @@ class LoginWebView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : WebView(context, attrs, defStyleAttr) {
- companion object {
- const val LOGIN_URL = "${FB_URL_BASE}login"
- private val userMatcher: Regex = Regex("c_user=([0-9]*);")
- }
-
private lateinit var loginCallback: (CookieModel) -> Unit
private lateinit var progressCallback: (Int) -> Unit
@@ -50,7 +46,7 @@ class LoginWebView @JvmOverloads constructor(
this.progressCallback = progressCallback
this.loginCallback = loginCallback
L.d("Begin loading login")
- loadUrl(LOGIN_URL)
+ loadUrl(FB_LOGIN_URL)
}
private inner class LoginClient : BaseWebViewClient() {
@@ -66,7 +62,7 @@ class LoginWebView @JvmOverloads constructor(
if (!url.isFacebookUrl) return@doAsync
val cookie = CookieManager.getInstance().getCookie(url) ?: return@doAsync
L.d("Checking cookie for login", cookie)
- val id = userMatcher.find(cookie)?.groups?.get(1)?.value?.toLong() ?: return@doAsync
+ val id = FB_USER_MATCHER.find(cookie)?.groupValues?.get(1)?.toLong() ?: return@doAsync
uiThread { onFound(id, cookie) }
}
}