aboutsummaryrefslogtreecommitdiff
path: root/app/src/main/kotlin/com
diff options
context:
space:
mode:
authorAllan Wang <me@allanwang.ca>2017-08-07 14:56:48 -0700
committerGitHub <noreply@github.com>2017-08-07 14:56:48 -0700
commitab7ec131b62ac1567e983c846c921bd3ada11dd4 (patch)
tree1e9e7db2151ba531f438a2ac9c4fc960c913dc46 /app/src/main/kotlin/com
parent7746e63373c905faa6d7e45e45fffc48d3ffff85 (diff)
downloadfrost-ab7ec131b62ac1567e983c846c921bd3ada11dd4.tar.gz
frost-ab7ec131b62ac1567e983c846c921bd3ada11dd4.tar.bz2
frost-ab7ec131b62ac1567e983c846c921bd3ada11dd4.zip
Fix/2FA (#115)
* Create basis for downloading videos * Resolve some download errors and allow video to be opened in external apps * Remove url checks for loging * Update readme with build links * Allow for all apks to build * Fix travis apk uploads * Fix null mapping * Fix some notation * Add commit message to test builds * Remove faulty commit from test release * Add intent overriding to login web client * Add resource logging * Add intent verification without url check * Simplify login activity * Check start activity for result * Add check before resolving intent * Fix wrong index * Temporary fix for 2FA login with U2F (#116) * Clean up and add comments
Diffstat (limited to 'app/src/main/kotlin/com')
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt2
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/activities/AboutActivity.kt2
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt18
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt19
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt1
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/injectors/JsInjector.kt1
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt8
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/services/NotificationReceiver.kt34
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt4
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/utils/Downloader.kt163
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt26
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/views/AccountItem.kt9
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/views/FrostViewPager.kt2
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClients.kt2
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebView.kt12
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt8
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewCore.kt2
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt78
18 files changed, 296 insertions, 95 deletions
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt
index c2b3cc0f..5de07b7a 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt
@@ -21,7 +21,7 @@ class StartActivity : KauBaseActivity() {
FbCookie.switchBackUser {
loadFbCookiesAsync {
cookies ->
- L.d("Cookies loaded ${System.currentTimeMillis()}", cookies.toString())
+ L.d("Cookies loaded at time ${System.currentTimeMillis()}", cookies.toString())
if (cookies.isNotEmpty())
launchNewTask(if (Prefs.userId != -1L) MainActivity::class.java else SelectorActivity::class.java, ArrayList(cookies))
else
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/AboutActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/AboutActivity.kt
index a1717de1..fbcd12cc 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/activities/AboutActivity.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/AboutActivity.kt
@@ -73,7 +73,7 @@ class AboutActivity : AboutActivityBase(null, {
* Frost may not be a library but we're conveying the same info
*/
val frost = Library().apply {
- libraryName = string(R.string.app_name)
+ libraryName = string(R.string.frost_name)
author = "Pitched Apps"
libraryWebsite = "https://github.com/AllanWang/Frost-for-Facebook"
isOpenSource = true
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt
index e419c21c..6a39b269 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt
@@ -148,7 +148,7 @@ class ImageActivity : KauBaseActivity() {
private fun saveTempImage(resource: Bitmap, callback: (uri: Uri?) -> Unit) {
var photoFile: File? = null
try {
- photoFile = createImageFile()
+ photoFile = createPrivateMediaFile(".png")
} catch (ignored: IOException) {
} finally {
if (photoFile == null) {
@@ -166,27 +166,13 @@ class ImageActivity : KauBaseActivity() {
}
}
- @Throws(IOException::class)
- private fun createImageFile(): File {
- // Create an image file name
- val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())
- val imageFileName = "Frost_" + timeStamp + "_"
- val storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES)
- return File.createTempFile(imageFileName, ".png", storageDir)
- }
-
internal fun downloadImage() {
kauRequestPermissions(PERMISSION_WRITE_EXTERNAL_STORAGE) {
granted, _ ->
L.d("Download image callback granted: $granted")
if (granted) {
doAsync {
- val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())
- val imageFileName = "Frost_" + timeStamp + "_"
- val storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
- val frostDir = File(storageDir, "Frost")
- if (!frostDir.exists()) frostDir.mkdirs()
- val destination = File.createTempFile(imageFileName, ".png", frostDir)
+ val destination = createMediaFile(".png")
downloadPath = destination.absolutePath
var success = true
try {
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt
index 8503145e..47c286fa 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt
@@ -1,5 +1,6 @@
package com.pitchedapps.frost.activities
+import android.content.Intent
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.os.Handler
@@ -27,7 +28,6 @@ import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.functions.BiFunction
import io.reactivex.internal.operators.single.SingleToObservable
-import io.reactivex.subjects.BehaviorSubject
import io.reactivex.subjects.SingleSubject
@@ -42,8 +42,6 @@ class LoginActivity : BaseActivity() {
val textview: AppCompatTextView by bindView(R.id.textview)
val profile: ImageView by bindView(R.id.profile)
- val loginObservable = SingleSubject.create<CookieModel>()
- val progressObservable = BehaviorSubject.create<Int>()!!
val profileObservable = SingleSubject.create<Boolean>()
val usernameObservable = SingleSubject.create<String>()
@@ -62,17 +60,14 @@ class LoginActivity : BaseActivity() {
setSupportActionBar(toolbar)
setTitle(R.string.kau_login)
setFrostColors(toolbar)
- web.loginObservable = loginObservable
- web.progressObservable = progressObservable
- loginObservable.observeOn(AndroidSchedulers.mainThread()).subscribe {
+ web.loadLogin({ refresh = it != 100 }) {
cookie ->
+ L.d("Login found")
web.fadeOut(onFinish = {
profile.fadeIn()
loadInfo(cookie)
})
}
- progressObservable.observeOn(AndroidSchedulers.mainThread()).subscribe { refresh = it != 100 }
- web.loadLogin()
}
fun loadInfo(cookie: CookieModel) {
@@ -124,4 +119,12 @@ class LoginActivity : BaseActivity() {
fun loadUsername(cookie: CookieModel) {
cookie.fetchUsername { usernameObservable.onSuccess(it) }
}
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ if (requestCode == 999) {
+ L.d("Result found for activity with result $resultCode")
+ L.d("Intent data ${data?.extras.toString()}")
+ } else
+ super.onActivityResult(requestCode, resultCode, data)
+ }
} \ No newline at end of file
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt
index df1228bd..e8148b55 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt
@@ -350,7 +350,6 @@ class MainActivity : BaseActivity(), SearchWebView.SearchContract,
hiddenSearchView?.dispose()
hiddenSearchView = null
searchView = null
- //todo remove true searchview and add contract
}
override fun emitSearchResponse(items: List<SearchItem>) {
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsInjector.kt b/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsInjector.kt
index 7e9fdcad..0387bb99 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsInjector.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsInjector.kt
@@ -52,6 +52,7 @@ interface InjectorContract {
*/
fun WebView.jsInject(vararg injectors: InjectorContract, callback: ((Array<String>) -> Unit) = {}) {
val validInjectors = injectors.filter { it != JsActions.EMPTY }
+ if (validInjectors.isEmpty()) return callback(emptyArray())
val observables = Array(validInjectors.size, { SingleSubject.create<String>() })
Observable.zip<String, Array<String>>(observables.map { it.toObservable() }, { it.map { it.toString() }.toTypedArray() }).subscribeOn(AndroidSchedulers.mainThread()).subscribe({
callback(it)
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt b/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt
index d9b91225..2453d3b0 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt
@@ -22,7 +22,7 @@ import com.pitchedapps.frost.R
import com.pitchedapps.frost.activities.FrostWebActivity
import com.pitchedapps.frost.dbflow.CookieModel
import com.pitchedapps.frost.dbflow.fetchUsername
-import com.pitchedapps.frost.facebook.FB_URL_BASE
+import com.pitchedapps.frost.facebook.formattedFbUrl
import com.pitchedapps.frost.utils.ARG_USER_ID
import com.pitchedapps.frost.utils.L
import com.pitchedapps.frost.utils.Prefs
@@ -40,7 +40,7 @@ val Context.frostNotification: NotificationCompat.Builder
}
@Suppress("DEPRECATION")
-//The update feature is for Android O and seems to still be in beta
+ //The update feature is for Android O and seems to still be in beta
fun Notification.frostConfig() = apply {
if (Prefs.notificationVibrate) defaults = defaults or Notification.DEFAULT_VIBRATE
if (Prefs.notificationSound) defaults = defaults or Notification.DEFAULT_SOUND
@@ -86,12 +86,12 @@ data class NotificationContent(val data: CookieModel,
}
} else {
val intent = Intent(context, FrostWebActivity::class.java)
- intent.data = Uri.parse("${FB_URL_BASE}$href")
+ intent.data = Uri.parse(href.formattedFbUrl)
intent.putExtra(ARG_USER_ID, data.id)
val group = "frost_${data.id}"
val pendingIntent = PendingIntent.getActivity(context, 0, intent, 0)
val notifBuilder = context.frostNotification
- .setContentTitle(title ?: context.string(R.string.app_name))
+ .setContentTitle(title ?: context.string(R.string.frost_name))
.setContentText(text)
.setContentIntent(pendingIntent)
.setCategory(Notification.CATEGORY_SOCIAL)
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationReceiver.kt b/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationReceiver.kt
new file mode 100644
index 00000000..c903ff72
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationReceiver.kt
@@ -0,0 +1,34 @@
+package com.pitchedapps.frost.services
+
+import android.app.PendingIntent
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.support.v4.app.NotificationManagerCompat
+import com.pitchedapps.frost.utils.L
+
+/**
+ * Created by Allan Wang on 2017-08-04.
+ *
+ * Cancels a notification
+ */
+private const val NOTIF_TAG_TO_CANCEL = "notif_tag_to_cancel"
+private const val NOTIF_ID_TO_CANCEL = "notif_id_to_cancel"
+
+class NotificationReceiver : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ L.d("NotificationReceiver triggered")
+ val notifTag = intent.getStringExtra(NOTIF_TAG_TO_CANCEL)
+ val notifId = intent.getIntExtra(NOTIF_ID_TO_CANCEL, -1)
+ if (notifId != -1) {
+ L.d("NotificationReceiver: Cancelling $notifTag $notifId")
+ NotificationManagerCompat.from(context).cancel(notifTag, notifId)
+ }
+ }
+}
+
+fun Context.getNotificationPendingCancelIntent(tag: String?, notifId: Int): PendingIntent {
+ val cancelIntent = Intent(this, NotificationReceiver::class.java)
+ .putExtra(NOTIF_TAG_TO_CANCEL, tag).putExtra(NOTIF_ID_TO_CANCEL, notifId)
+ return PendingIntent.getBroadcast(this, 0, cancelIntent, 0)
+} \ No newline at end of file
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt b/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt
index 3ddad869..fe7758cc 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt
@@ -182,7 +182,7 @@ class NotificationService : JobService() {
private fun Context.debugNotification(text: String) {
if (!BuildConfig.DEBUG) return
val notifBuilder = frostNotification
- .setContentTitle(string(R.string.app_name))
+ .setContentTitle(string(R.string.frost_name))
.setContentText(text)
NotificationManagerCompat.from(this).notify(999, notifBuilder.build().frostConfig())
}
@@ -190,7 +190,7 @@ class NotificationService : JobService() {
fun summaryNotification(userId: Long, count: Int) {
if (count <= 1) return
val notifBuilder = frostNotification
- .setContentTitle(string(R.string.app_name))
+ .setContentTitle(string(R.string.frost_name))
.setContentText("$count notifications")
.setGroup("frost_$userId")
.setGroupSummary(true)
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/Downloader.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/Downloader.kt
new file mode 100644
index 00000000..35f69bca
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Downloader.kt
@@ -0,0 +1,163 @@
+package com.pitchedapps.frost.utils
+
+import android.app.Notification
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.support.v4.app.NotificationCompat
+import android.support.v4.app.NotificationManagerCompat
+import android.support.v4.content.FileProvider
+import ca.allanwang.kau.permissions.PERMISSION_WRITE_EXTERNAL_STORAGE
+import ca.allanwang.kau.permissions.kauRequestPermissions
+import ca.allanwang.kau.utils.copyFromInputStream
+import ca.allanwang.kau.utils.string
+import com.pitchedapps.frost.BuildConfig
+import com.pitchedapps.frost.R
+import com.pitchedapps.frost.services.frostConfig
+import com.pitchedapps.frost.services.frostNotification
+import com.pitchedapps.frost.services.getNotificationPendingCancelIntent
+import okhttp3.MediaType
+import okhttp3.OkHttpClient
+import okhttp3.Request
+import okhttp3.ResponseBody
+import okio.*
+import org.jetbrains.anko.AnkoAsyncContext
+import org.jetbrains.anko.doAsync
+import java.io.File
+import java.io.IOException
+import java.lang.ref.WeakReference
+
+/**
+ * Created by Allan Wang on 2017-08-04.
+ *
+ * With reference to the <a href="https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/Progress.java">OkHttp3 sample</a>
+ */
+fun Context.frostDownload(url: String) {
+ L.d("Received download request", "Download $url")
+ val type = if (url.contains("video")) DownloadType.VIDEO
+ else return L.d("Download request does not match any type")
+ kauRequestPermissions(PERMISSION_WRITE_EXTERNAL_STORAGE) {
+ granted, _ ->
+ if (granted) doAsync { frostDownloadImpl(url, type) }
+ }
+}
+
+private const val MAX_PROGRESS = 1000
+//private val DOWNLOAD_GROUP: String? = null
+private const val DOWNLOAD_GROUP = "frost_downloads"
+
+private enum class DownloadType(val downloadingRes: Int, val downloadedRes: Int) {
+ VIDEO(R.string.downloading_video, R.string.downloaded_video),
+ FILE(R.string.downloading_file, R.string.downloaded_file);
+
+ fun getPendingIntent(context: Context, file: File): PendingIntent {
+ val uri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", file)
+ val type = context.contentResolver.getType(uri)
+ L.d("DownloadType: retrieved pending intent - $uri $type")
+ val intent = Intent(Intent.ACTION_VIEW, uri)
+ .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ .setDataAndType(uri, type)
+ return PendingIntent.getActivity(context, 0, intent, 0)
+ }
+}
+
+private fun AnkoAsyncContext<Context>.frostDownloadImpl(url: String, type: DownloadType) {
+ L.d("Starting download request")
+ val notifId = Math.abs(url.hashCode() + System.currentTimeMillis().toInt())
+ var notifBuilderAttempt: NotificationCompat.Builder? = null
+ weakRef.get()?.apply {
+ notifBuilderAttempt = frostNotification
+ .setContentTitle(string(type.downloadingRes))
+ .setCategory(Notification.CATEGORY_PROGRESS)
+ .setWhen(System.currentTimeMillis())
+ .setProgress(MAX_PROGRESS, 0, false)
+ .setOngoing(true)
+ .addAction(R.drawable.ic_action_cancel, string(R.string.kau_cancel), getNotificationPendingCancelIntent(DOWNLOAD_GROUP, notifId))
+ .setGroup(DOWNLOAD_GROUP)
+ }
+ val notifBuilder = notifBuilderAttempt ?: return
+ notifBuilder.show(weakRef, notifId)
+
+ val request: Request = Request.Builder()
+ .url(url)
+ .tag(url)
+ .build()
+
+ var client: OkHttpClient? = null
+ client = OkHttpClient.Builder()
+ .addNetworkInterceptor {
+ chain ->
+ val original = chain.proceed(chain.request())
+ return@addNetworkInterceptor original.newBuilder().body(ProgressResponseBody(original.body()!!) {
+ bytesRead, contentLength, done ->
+ //cancel request if context reference is now invalid
+ if (weakRef.get() == null) {
+ client?.cancel(url)
+ }
+ val ctx = weakRef.get() ?: return@ProgressResponseBody client?.cancel(url) ?: Unit
+ val percentage = bytesRead.toFloat() / contentLength.toFloat() * MAX_PROGRESS
+ L.v("Download request progress: $percentage")
+ notifBuilder.setProgress(MAX_PROGRESS, percentage.toInt(), false)
+ if (done) {
+ notifBuilder.setFinished(ctx, type)
+ L.d("Download request finished")
+ }
+ notifBuilder.show(weakRef, notifId)
+ }).build()
+ }
+ .build()
+ client.newCall(request).execute().use {
+ response ->
+ if (!response.isSuccessful) throw IOException("Unexpected code $response")
+ val stream = response.body()?.byteStream()
+ if (stream != null) {
+ val destination = createMediaFile(".mp4")
+ destination.copyFromInputStream(stream)
+ weakRef.get()?.apply {
+ notifBuilder.setContentIntent(type.getPendingIntent(this, destination))
+ notifBuilder.show(weakRef, notifId)
+ }
+ }
+ }
+}
+
+private fun NotificationCompat.Builder.setFinished(context: Context, type: DownloadType)
+ = setContentTitle(context.string(type.downloadedRes))
+ .setProgress(0, 0, false).setOngoing(false).setAutoCancel(true)
+ .apply { mActions.clear() }
+
+private fun OkHttpClient.cancel(url: String) {
+ val call = dispatcher().runningCalls().firstOrNull { it.request().tag() == url }
+ if (call != null && !call.isCanceled) call.cancel()
+}
+
+private fun NotificationCompat.Builder.show(weakRef: WeakReference<Context>, notifId: Int) {
+ val c = weakRef.get() ?: return
+ NotificationManagerCompat.from(c).notify(DOWNLOAD_GROUP, notifId, build().frostConfig())
+}
+
+private class ProgressResponseBody(
+ val responseBody: ResponseBody,
+ val listener: (bytesRead: Long, contentLength: Long, done: Boolean) -> Unit) : ResponseBody() {
+
+ private val bufferedSource: BufferedSource by lazy { Okio.buffer(source(responseBody.source())) }
+
+ override fun contentLength(): Long = responseBody.contentLength()
+
+ override fun contentType(): MediaType? = responseBody.contentType()
+
+ override fun source(): BufferedSource = bufferedSource
+
+ private fun source(source: Source): Source = object : ForwardingSource(source) {
+
+ private var totalBytesRead = 0L
+
+ override fun read(sink: Buffer?, byteCount: Long): Long {
+ val bytesRead = super.read(sink, byteCount)
+ // read() returns the number of bytes read, or -1 if this source is exhausted.
+ totalBytesRead += if (bytesRead != -1L) bytesRead else 0
+ listener(totalBytesRead, responseBody.contentLength(), bytesRead == -1L)
+ return bytesRead
+ }
+ }
+} \ 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 cc3ea52e..496a6b5b 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt
@@ -2,8 +2,10 @@ package com.pitchedapps.frost.utils
import android.app.Activity
import android.content.Context
+import android.content.Intent
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
+import android.net.Uri
import android.support.annotation.StringRes
import android.support.design.internal.SnackbarContentLayout
import android.support.design.widget.Snackbar
@@ -22,8 +24,11 @@ import com.pitchedapps.frost.BuildConfig
import com.pitchedapps.frost.R
import com.pitchedapps.frost.activities.*
import com.pitchedapps.frost.dbflow.CookieModel
+import com.pitchedapps.frost.facebook.FACEBOOK_COM
import com.pitchedapps.frost.facebook.FbTab
import com.pitchedapps.frost.facebook.formattedFbUrl
+import java.io.IOException
+import java.util.*
/**
* Created by Allan Wang on 2017-06-03.
@@ -145,4 +150,23 @@ fun Activity.frostNavigationBar() {
navigationBarColor = if (Prefs.tintNavBar) Prefs.headerColor else Color.BLACK
}
-fun <T> RequestBuilder<T>.withRoundIcon() = apply(RequestOptions().transform(CircleCrop()))!! \ No newline at end of file
+fun <T> RequestBuilder<T>.withRoundIcon() = apply(RequestOptions().transform(CircleCrop()))!!
+
+@Throws(IOException::class)
+fun createMediaFile(extension: String) = createMediaFile("Frost", extension)
+
+@Throws(IOException::class)
+fun Context.createPrivateMediaFile(extension: String) = createPrivateMediaFile("Frost", extension)
+
+/**
+ * Tries to send the uri to the proper activity via an intent
+ * @returns {@code true} if activity is resolved, {@code false} otherwise
+ */
+fun Context.resolveActivityForUri(uri: Uri): Boolean {
+ if (uri.toString().contains(FACEBOOK_COM) && !uri.toString().contains("intent:")) return false //ignore response as we will be triggering ourself
+ val intent = Intent(Intent.ACTION_VIEW, uri)
+ if (intent.resolveActivity(packageManager) == null) return false
+ startActivity(intent)
+ return true
+}
+
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/views/AccountItem.kt b/app/src/main/kotlin/com/pitchedapps/frost/views/AccountItem.kt
index dc5ac6ac..4b6c9e4e 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/views/AccountItem.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/views/AccountItem.kt
@@ -6,9 +6,7 @@ import android.support.v7.widget.RecyclerView
import android.view.View
import android.widget.ImageView
import ca.allanwang.kau.iitems.KauIItem
-import ca.allanwang.kau.utils.bindView
-import ca.allanwang.kau.utils.fadeIn
-import ca.allanwang.kau.utils.toDrawable
+import ca.allanwang.kau.utils.*
import com.bumptech.glide.Glide
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException
@@ -30,7 +28,7 @@ class AccountItem(val cookie: CookieModel?) : KauIItem<AccountItem, AccountItem.
override fun bindView(viewHolder: ViewHolder, payloads: List<Any>?) {
super.bindView(viewHolder, payloads)
with(viewHolder) {
- text.visibility = View.INVISIBLE
+ text.invisible()
text.setTextColor(Prefs.textColor)
if (cookie != null) {
text.text = cookie.name
@@ -46,10 +44,9 @@ class AccountItem(val cookie: CookieModel?) : KauIItem<AccountItem, AccountItem.
}
}).into(image)
} else {
- text.visibility = View.VISIBLE
+ text.visible()
image.setImageDrawable(GoogleMaterial.Icon.gmd_add_circle_outline.toDrawable(itemView.context, 100, Prefs.textColor))
text.text = itemView.context.getString(R.string.kau_add_account)
- //todo add plus image
}
}
}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostViewPager.kt b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostViewPager.kt
index 367495b5..d692c7aa 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostViewPager.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostViewPager.kt
@@ -1,5 +1,6 @@
package com.pitchedapps.frost.views
+import android.annotation.SuppressLint
import android.content.Context
import android.support.v4.view.ViewPager
import android.util.AttributeSet
@@ -16,5 +17,6 @@ class FrostViewPager @JvmOverloads constructor(context: Context, attrs: Attribut
override fun onInterceptTouchEvent(ev: MotionEvent?) = Prefs.viewpagerSwipe && enableSwipe && super.onInterceptTouchEvent(ev)
+ @SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(ev: MotionEvent?): Boolean = Prefs.viewpagerSwipe && enableSwipe && super.onTouchEvent(ev)
} \ No newline at end of file
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClients.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClients.kt
index 343674d5..6bc27256 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClients.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClients.kt
@@ -70,4 +70,6 @@ class FrostChromeClient(webCore: FrostWebViewCore) : WebChromeClient() {
callback(origin, granted, true)
}
}
+
+
} \ No newline at end of file
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebView.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebView.kt
index 1679d7a3..79ca1fdf 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebView.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebView.kt
@@ -9,15 +9,12 @@ import android.util.AttributeSet
import android.view.View
import android.widget.FrameLayout
import android.widget.ProgressBar
-import ca.allanwang.kau.utils.bindView
-import ca.allanwang.kau.utils.tint
-import ca.allanwang.kau.utils.visible
-import ca.allanwang.kau.utils.withAlpha
+import ca.allanwang.kau.utils.*
import com.pitchedapps.frost.R
import com.pitchedapps.frost.facebook.FbTab
import com.pitchedapps.frost.facebook.USER_AGENT_BASIC
-import com.pitchedapps.frost.utils.L
import com.pitchedapps.frost.utils.Prefs
+import com.pitchedapps.frost.utils.frostDownload
import io.reactivex.android.schedulers.AndroidSchedulers
/**
@@ -37,7 +34,7 @@ class FrostWebView @JvmOverloads constructor(
refresh.setColorSchemeColors(Prefs.iconColor)
refresh.setProgressBackgroundColorSchemeColor(Prefs.headerColor.withAlpha(255))
web.progressObservable.observeOn(AndroidSchedulers.mainThread()).subscribe {
- progress.visibility = if (it == 100) View.INVISIBLE else View.VISIBLE
+ progress.invisibleIf(it == 100)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) progress.setProgress(it, true)
else progress.progress = it
}
@@ -62,7 +59,7 @@ class FrostWebView @JvmOverloads constructor(
baseEnum = enum
with(settings) {
javaScriptEnabled = true
- if (url.contains("/message"))
+ if (url.contains("facebook.com/message"))
userAgentString = USER_AGENT_BASIC
allowFileAccess = true
textZoom = Prefs.webTextScaling
@@ -73,6 +70,7 @@ class FrostWebView @JvmOverloads constructor(
webChromeClient = FrostChromeClient(this)
addJavascriptInterface(FrostJSI(this), "Frost")
setBackgroundColor(Color.TRANSPARENT)
+ setDownloadListener { downloadUrl, _, _, _, _ -> context.frostDownload(downloadUrl) }
}
}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt
index 9f7dd916..94bff3c3 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt
@@ -132,13 +132,7 @@ open class FrostWebViewClient(val webCore: FrostWebViewCore) : BaseWebViewClient
if (path.startsWith("/composer/")) return launchRequest(request)
if (request.url.toString().contains("scontent-sea1-1.xx.fbcdn.net") && (path.endsWith(".jpg") || path.endsWith(".png")))
return launchImage(request)
- if (!request.url.toString().contains(FACEBOOK_COM)) {
- val intent = Intent(Intent.ACTION_VIEW, request.url)
- if (intent.resolveActivity(view.context.packageManager) != null) {
- view.context.startActivity(Intent(Intent.ACTION_VIEW, request.url))
- return true
- }
- }
+ if (view.context.resolveActivityForUri(request.url)) return true
return super.shouldOverrideUrlLoading(view, request)
}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewCore.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewCore.kt
index d96fba55..d8edc15c 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewCore.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewCore.kt
@@ -1,6 +1,7 @@
package com.pitchedapps.frost.web
import android.animation.ValueAnimator
+import android.annotation.SuppressLint
import android.content.Context
import android.support.v4.view.NestedScrollingChild
import android.support.v4.view.NestedScrollingChildHelper
@@ -94,6 +95,7 @@ class FrostWebViewCore @JvmOverloads constructor(
*
* https://github.com/takahirom/webview-in-coordinatorlayout/blob/master/app/src/main/java/com/github/takahirom/webview_in_coodinator_layout/NestedWebView.java
*/
+ @SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(ev: MotionEvent): Boolean {
val event = MotionEvent.obtain(ev)
val action = event.action
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 b178f66c..31be4450 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt
@@ -4,23 +4,19 @@ import android.annotation.SuppressLint
import android.content.Context
import android.util.AttributeSet
import android.view.View
-import android.webkit.ConsoleMessage
-import android.webkit.CookieManager
-import android.webkit.WebChromeClient
-import android.webkit.WebView
+import android.webkit.*
import ca.allanwang.kau.utils.fadeIn
-import com.pitchedapps.frost.R
+import ca.allanwang.kau.utils.isVisible
import com.pitchedapps.frost.dbflow.CookieModel
import com.pitchedapps.frost.facebook.FACEBOOK_COM
+import com.pitchedapps.frost.facebook.FB_URL_BASE
import com.pitchedapps.frost.facebook.FbCookie
import com.pitchedapps.frost.injectors.CssHider
import com.pitchedapps.frost.injectors.jsInject
import com.pitchedapps.frost.utils.L
import com.pitchedapps.frost.utils.Prefs
-import com.pitchedapps.frost.utils.frostSnackbar
-import io.reactivex.subjects.PublishSubject
-import io.reactivex.subjects.SingleSubject
-import io.reactivex.subjects.Subject
+import org.jetbrains.anko.doAsync
+import org.jetbrains.anko.uiThread
/**
* Created by Allan Wang on 2017-05-29.
@@ -30,27 +26,15 @@ class LoginWebView @JvmOverloads constructor(
) : WebView(context, attrs, defStyleAttr) {
companion object {
- const val LOGIN_URL = "https://touch.facebook.com/login"
- private val userMatcher: Regex by lazy { Regex("c_user=([0-9]*);") }
+ const val LOGIN_URL = "${FB_URL_BASE}login"
+ private val userMatcher: Regex = Regex("c_user=([0-9]*);")
}
- val cookieObservable = PublishSubject.create<Pair<String, String?>>()
- lateinit var loginObservable: SingleSubject<CookieModel>
- lateinit var progressObservable: Subject<Int>
+ private lateinit var loginCallback: (CookieModel) -> Unit
+ private lateinit var progressCallback: (Int) -> Unit
init {
- FbCookie.reset({
- cookieObservable.filter { (_, cookie) -> cookie?.contains(userMatcher) ?: false }
- .subscribe {
- (url, cookie) ->
- L.d("Checking cookie for login", "$url\n\t$cookie")
- val id = userMatcher.find(cookie!!)?.groups?.get(1)?.value!!
- FbCookie.save(id.toLong())
- cookieObservable.onComplete()
- loginObservable.onSuccess(CookieModel(id.toLong(), "", cookie))
- }
- setupWebview()
- })
+ FbCookie.reset { setupWebview() }
}
@SuppressLint("SetJavaScriptEnabled")
@@ -61,27 +45,39 @@ class LoginWebView @JvmOverloads constructor(
webChromeClient = LoginChromeClient()
}
- fun loadLogin() {
+ fun loadLogin(progressCallback: (Int) -> Unit, loginCallback: (CookieModel) -> Unit) {
+ this.progressCallback = progressCallback
+ this.loginCallback = loginCallback
loadUrl(LOGIN_URL)
}
-
- inner class LoginClient : BaseWebViewClient() {
+ private inner class LoginClient : BaseWebViewClient() {
override fun onPageFinished(view: WebView, url: String?) {
super.onPageFinished(view, url)
- if (url == null || !url.contains(FACEBOOK_COM)) {
- view.frostSnackbar(R.string.no_longer_facebook)
- loadLogin()
- return
+ val containsFacebook = url?.contains(FACEBOOK_COM) ?: false
+ checkForLogin(url) { id, cookie -> loginCallback(CookieModel(id, "", cookie)) }
+ view.jsInject(CssHider.HEADER.maybe(containsFacebook),
+ CssHider.CORE.maybe(containsFacebook),
+ Prefs.themeInjector.maybe(containsFacebook),
+ callback = { if (!view.isVisible) view.fadeIn(offset = 150L) })
+ }
+
+ fun checkForLogin(url: String?, onFound: (id: Long, cookie: String) -> Unit) {
+ doAsync {
+ if (url == null || !url.contains(FACEBOOK_COM)) 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
+ uiThread { onFound(id, cookie) }
}
- cookieObservable.onNext(Pair(url, CookieManager.getInstance().getCookie(url)))
- view.jsInject(CssHider.HEADER, CssHider.CORE,
- Prefs.themeInjector,
- callback = {
- if (view.visibility != View.VISIBLE)
- view.fadeIn(offset = 150L)
- })
+ }
+
+ override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
+ //For now, we will ignore all attempts to launch external apps during login
+ if (request.url == null || request.url.scheme == "intent" || request.url.scheme == "android-app")
+ return true
+ return super.shouldOverrideUrlLoading(view, request)
}
}
@@ -93,7 +89,7 @@ class LoginWebView @JvmOverloads constructor(
override fun onProgressChanged(view: WebView, newProgress: Int) {
super.onProgressChanged(view, newProgress)
- progressObservable.onNext(newProgress)
+ progressCallback(newProgress)
}
}
} \ No newline at end of file