aboutsummaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/build.gradle4
-rw-r--r--app/src/main/AndroidManifest.xml5
-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
-rw-r--r--app/src/main/res/layout/view_content_recycler.xml1
-rw-r--r--app/src/main/res/layout/view_content_web.xml1
-rw-r--r--app/src/main/res/layout/view_main_toolbar.xml2
-rw-r--r--app/src/main/res/xml/frost_changelog.xml19
-rw-r--r--app/src/test/kotlin/com/pitchedapps/frost/facebook/FbRequestTest.kt62
-rw-r--r--app/src/test/kotlin/com/pitchedapps/frost/internal/Internal.kt28
-rw-r--r--app/src/test/kotlin/com/pitchedapps/frost/utils/UrlTests.kt6
23 files changed, 299 insertions, 57 deletions
diff --git a/app/build.gradle b/app/build.gradle
index 7fa3fe71..e09740dd 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -136,6 +136,9 @@ dependencies {
androidTestImplementation("com.android.support.test:runner:${TEST_RUNNER}") {
exclude group: 'com.android.support', module: 'support-annotations'
}
+
+ implementation "com.android.support:exifinterface:${ANDROID_SUPPORT_LIBS}"
+
androidTestImplementation "org.jetbrains.kotlin:kotlin-test-junit:${KOTLIN}"
androidTestImplementation "com.android.support.test:rules:${TEST_RULE}"
testImplementation "org.jetbrains.kotlin:kotlin-test-junit:${KOTLIN}"
@@ -186,6 +189,7 @@ dependencies {
implementation "org.jsoup:jsoup:${JSOUP}"
implementation "com.squareup.okhttp3:okhttp:${OKHTTP}"
+ implementation "com.squareup.okhttp3:logging-interceptor:${OKHTTP}"
implementation "com.anjlab.android.iab.v3:library:${IAB}"
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index f1877bc3..1839a122 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -166,6 +166,11 @@
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
+ <provider
+ android:name="com.raizlabs.android.dbflow.runtime.StubContentProvider"
+ android:authorities="${applicationId}.dbflow.provider"
+ android:exported="false" />
+
<meta-data
android:name="io.fabric.ApiKey"
android:value="18b3c223b96b7e7fc1fac372e36b8f4d49a193c7" />
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) }
}
}
diff --git a/app/src/main/res/layout/view_content_recycler.xml b/app/src/main/res/layout/view_content_recycler.xml
index 511cfe5d..811df9a0 100644
--- a/app/src/main/res/layout/view_content_recycler.xml
+++ b/app/src/main/res/layout/view_content_recycler.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<com.pitchedapps.frost.views.FrostContentRecycler xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/frost_content_recycler"
android:layout_width="match_parent"
android:layout_height="match_parent" /> \ No newline at end of file
diff --git a/app/src/main/res/layout/view_content_web.xml b/app/src/main/res/layout/view_content_web.xml
index 1a51b136..32f97891 100644
--- a/app/src/main/res/layout/view_content_web.xml
+++ b/app/src/main/res/layout/view_content_web.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<com.pitchedapps.frost.views.FrostContentWeb xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/frost_content_web"
android:layout_width="match_parent"
android:layout_height="match_parent" /> \ No newline at end of file
diff --git a/app/src/main/res/layout/view_main_toolbar.xml b/app/src/main/res/layout/view_main_toolbar.xml
index b0a4de7c..b4572af3 100644
--- a/app/src/main/res/layout/view_main_toolbar.xml
+++ b/app/src/main/res/layout/view_main_toolbar.xml
@@ -4,5 +4,5 @@
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
- app:layout_scrollFlags="scroll|enterAlways"
+ app:layout_scrollFlags="scroll|enterAlways|snap"
app:popupTheme="@style/AppTheme.PopupOverlay" /> \ No newline at end of file
diff --git a/app/src/main/res/xml/frost_changelog.xml b/app/src/main/res/xml/frost_changelog.xml
index 7c61b789..fe35fcdc 100644
--- a/app/src/main/res/xml/frost_changelog.xml
+++ b/app/src/main/res/xml/frost_changelog.xml
@@ -6,15 +6,20 @@
<item text="" />
-->
+
+ <version title="v1.7.0" />
+ <item text="Fix launching messages in new overlay" />
+ <item text="Fix some errors in launching pages" />
+ <item text="Redid base design to prepare for native views" />
+ <item text="Automatically bring toolbar up when keyboard is shown" />
+ <item text="" />
+
<version title="v1.6.8" />
<item text="Fully customize your tabs! Check out settings > appearance > main activity tabs" />
<item text="Optimize scripts" />
<item text="Add more theme components" />
<item text="Fixed issue with deleting posts" />
<item text="Automatically close video player if url cannot be loaded" />
- <item text="" />
- <item text="" />
- <item text="" />
<version title="v1.6.7" />
<item text="Fix icons in tablets" />
@@ -22,16 +27,16 @@
<item text="Add Galician translations" />
<item text="Remove generic translations" />
- <version title="v1.6.5" />
+ <version title="v1.6.5" />
<item text="Allow for truly full screen videos" />
<item text="Support pip video everywhere" />
<item text="Support gifs (auto loop)" />
<item text="Add Italian translations" />
<item text="Add Korean translations" />
<item text="Add Vietnamese translations" />
- <item text="Fix bad date param for videos and images" />
- <item text="Allow for explicit intent launches" />
- <item text="Updated all dependencies" />
+ <item text="Fix bad date param for videos and images" />
+ <item text="Allow for explicit intent launches" />
+ <item text="Updated all dependencies" />
<version title="v1.6.2" />
<item text="Fix search update from Facebook" />
diff --git a/app/src/test/kotlin/com/pitchedapps/frost/facebook/FbRequestTest.kt b/app/src/test/kotlin/com/pitchedapps/frost/facebook/FbRequestTest.kt
new file mode 100644
index 00000000..a521ceda
--- /dev/null
+++ b/app/src/test/kotlin/com/pitchedapps/frost/facebook/FbRequestTest.kt
@@ -0,0 +1,62 @@
+package com.pitchedapps.frost.facebook
+
+import com.pitchedapps.frost.internal.COOKIE
+import com.pitchedapps.frost.internal.FB_DTSG
+import com.pitchedapps.frost.internal.USER_ID
+import org.junit.Assume
+import org.junit.BeforeClass
+import org.junit.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertNotNull
+
+/**
+ * Created by Allan Wang on 21/12/17.
+ */
+class FbRequestTest {
+
+ companion object {
+ @BeforeClass
+ @JvmStatic
+ fun before() {
+ Assume.assumeTrue(COOKIE.isNotEmpty())
+ }
+
+ val AUTH: RequestAuth by lazy { RequestAuth(USER_ID, COOKIE, FB_DTSG) }
+ }
+
+ @Test
+ fun userIdRegex() {
+ val id = 12349876L
+ val cookie = "wd=1366x615; c_user=$id; act=1234%2F12; m_pixel_ratio=1; presence=hello; x-referer=asdfasdf"
+ assertEquals(id, FB_USER_MATCHER.find(cookie)?.groupValues?.get(1)?.toLong())
+ }
+
+ @Test
+ fun fbDtsgRegex() {
+ val fb_dtsg = "readme"
+ val input = "data-sigil=\"mbasic_inline_feed_composer\">\u003Cinput type=\"hidden\" name=\"fb_dtsg\" value=\"$fb_dtsg\" autocomplete=\"off\" \\/>\u003Cinput type=\"hidden\" name=\"privacyx\" value=\"12345\""
+ assertEquals(fb_dtsg, FB_DTSG_MATCHER.find(input)?.groupValues?.get(1))
+ }
+
+ @Test
+ fun auth() {
+ val auth = (USER_ID to COOKIE).getAuth()
+ assertNotNull(auth)
+ assertEquals(USER_ID, auth!!.userId)
+ assertEquals(COOKIE, auth.cookie)
+ println("Test auth: priv $FB_DTSG, test ${auth.fb_dtsg}")
+ }
+
+ @Test
+ fun markNotification() {
+ val notifId = 1513544657695779
+
+ val out = AUTH.markNotificationRead(notifId)
+ .execute().body()?.string() ?: ""
+ println(out)
+
+ assertFalse(out.contains("error"))
+ }
+
+} \ No newline at end of file
diff --git a/app/src/test/kotlin/com/pitchedapps/frost/internal/Internal.kt b/app/src/test/kotlin/com/pitchedapps/frost/internal/Internal.kt
new file mode 100644
index 00000000..91eb968d
--- /dev/null
+++ b/app/src/test/kotlin/com/pitchedapps/frost/internal/Internal.kt
@@ -0,0 +1,28 @@
+package com.pitchedapps.frost.internal
+
+import com.pitchedapps.frost.facebook.FB_USER_MATCHER
+import java.io.File
+import java.io.FileInputStream
+import java.util.*
+
+/**
+ * Created by Allan Wang on 21/12/17.
+ */
+
+private const val FILE = "priv.properties"
+
+val PROPS: Properties by lazy {
+ val props = Properties()
+ val file = File(FILE)
+ if (!file.exists()) {
+ println("$FILE not found")
+ return@lazy props
+ }
+ println("Found properties at ${file.absolutePath}")
+ FileInputStream(file).use { props.load(it) }
+ props
+}
+
+val COOKIE: String by lazy { PROPS.getProperty("COOKIE") ?: "" }
+val FB_DTSG: String by lazy { PROPS.getProperty("FB_DTSG") ?: "" }
+val USER_ID: Long by lazy { FB_USER_MATCHER.find(COOKIE)?.groupValues?.get(1)?.toLong() ?: -1 }
diff --git a/app/src/test/kotlin/com/pitchedapps/frost/utils/UrlTests.kt b/app/src/test/kotlin/com/pitchedapps/frost/utils/UrlTests.kt
index 4062cf98..5dbcad65 100644
--- a/app/src/test/kotlin/com/pitchedapps/frost/utils/UrlTests.kt
+++ b/app/src/test/kotlin/com/pitchedapps/frost/utils/UrlTests.kt
@@ -17,6 +17,12 @@ class UrlTests {
assertTrue(GOOGLE.isIndependent, "google")
assertTrue(FACEBOOK_COM.isIndependent, "facebook")
assertFalse("#!/photos/viewer/?photoset_token=pcb.1234".isIndependent, "photo")
+ assertFalse("#test-id".isIndependent, "id")
+ assertFalse("#".isIndependent, "#")
+ assertFalse("#!".isIndependent, "#!")
+ assertFalse("#!/".isIndependent, "#!/")
+ assertTrue("/this/is/valid".isIndependent, "url segments")
+ assertTrue("#!/facebook/segment".isIndependent, "facebook segments")
}
@Test