aboutsummaryrefslogtreecommitdiff
path: root/app/src/main/kotlin/com
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/kotlin/com')
-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.kt38
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt1
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt1
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/activities/MediaPickerActivity.kt28
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/activities/WebOverlayActivity.kt15
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/injectors/CssAssets.kt8
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/injectors/JsInjector.kt3
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/settings/Appearance.kt13
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/settings/Notifications.kt4
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt24
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/utils/Theme.kt71
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt37
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IABBinder.kt4
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/views/FrostViewPager.kt7
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClients.kt5
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt10
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/web/FrostRequestInterceptor.kt44
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/web/FrostUrlOverlayValidator.kt48
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt24
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt15
21 files changed, 272 insertions, 130 deletions
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 670e8669..b7bacbc2 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/activities/AboutActivity.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/AboutActivity.kt
@@ -33,7 +33,7 @@ class AboutActivity : AboutActivityBase(null, {
textColor = Prefs.textColor
accentColor = Prefs.accentColor
backgroundColor = Prefs.bgColor.withMinAlpha(200)
- cutoutForeground = if (0xff3b5998.toInt().isColorVisibleOn(Prefs.bgColor)) 0xff3b5998.toInt() else Prefs.accentColor
+ cutoutForeground = Prefs.accentColor
cutoutDrawableRes = R.drawable.frost_f_256
faqPageTitleRes = R.string.faq_title
faqXmlRes = R.xml.frost_faq
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 31479d54..9a5f3c6e 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt
@@ -15,6 +15,7 @@ import android.widget.ProgressBar
import android.widget.TextView
import ca.allanwang.kau.email.sendEmail
import ca.allanwang.kau.internal.KauBaseActivity
+import ca.allanwang.kau.mediapicker.scanMedia
import ca.allanwang.kau.permissions.PERMISSION_WRITE_EXTERNAL_STORAGE
import ca.allanwang.kau.permissions.kauRequestPermissions
import ca.allanwang.kau.utils.*
@@ -47,6 +48,7 @@ class ImageActivity : KauBaseActivity() {
val photo: SubsamplingScaleImageView by bindView(R.id.image_photo)
val caption: TextView? by bindOptionalView(R.id.image_text)
val fab: FloatingActionButton by bindView(R.id.image_fab)
+ var errorRef: Throwable? = null
/**
* Reference to the temporary file path
@@ -85,22 +87,22 @@ class ImageActivity : KauBaseActivity() {
caption?.text = text
progress.tint(Prefs.accentColor)
panel?.addPanelSlideListener(object : SlidingUpPanelLayout.SimplePanelSlideListener() {
-
override fun onPanelSlide(panel: View, slideOffset: Float) {
if (slideOffset == 0f && !fab.isShown) fab.show()
else if (slideOffset != 0f && fab.isShown) fab.hide()
caption?.alpha = slideOffset / 2 + 0.5f
}
-
})
fab.setOnClickListener { fabAction.onClick(this) }
photo.setOnImageEventListener(object : SubsamplingScaleImageView.DefaultOnImageEventListener() {
override fun onImageLoadError(e: Exception?) {
+ errorRef = e
e.logFrostAnswers("Image load error")
imageCallback(null, false)
}
})
Glide.with(this).asBitmap().load(imageUrl).into(PhotoTarget(this::imageCallback))
+ setFrostColors(themeWindow = false)
}
/**
@@ -145,7 +147,8 @@ class ImageActivity : KauBaseActivity() {
var photoFile: File? = null
try {
photoFile = createPrivateMediaFile(".png")
- } catch (ignored: IOException) {
+ } catch (e: IOException) {
+ errorRef = e
} finally {
if (photoFile == null) {
callback(null)
@@ -173,14 +176,15 @@ class ImageActivity : KauBaseActivity() {
var success = true
try {
File(tempFilePath).copyTo(destination, true)
- scanFile(destination)
+ scanMedia(destination)
} catch (e: Exception) {
+ errorRef = e
success = false
} finally {
L.d("Download image async finished: $success")
uiThread {
val text = if (success) R.string.image_download_success else R.string.image_download_fail
- snackbar(text)
+ frostSnackbar(text)
if (success) fabAction = FabStates.SHARE
}
}
@@ -189,17 +193,6 @@ class ImageActivity : KauBaseActivity() {
}
}
- /**
- * See <a href="https://developer.android.com/training/camera/photobasics.html#TaskGallery">Docs</a>
- */
- internal fun scanFile(file: File) {
- if (!file.exists()) return
- val mediaScanIntent = Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE)
- val contentUri = Uri.fromFile(file)
- mediaScanIntent.data = contentUri
- this.sendBroadcast(mediaScanIntent)
- }
-
internal fun deleteTempFile() {
if (tempFilePath != null) {
File(tempFilePath!!).delete()
@@ -213,7 +206,7 @@ class ImageActivity : KauBaseActivity() {
}
}
-internal enum class FabStates(val iicon: IIcon, val iconColor: Int = Prefs.iconColor, val backgroundTint: Int = Prefs.iconBackgroundColor.withAlpha(255)) {
+internal enum class FabStates(val iicon: IIcon, val iconColor: Int = Prefs.iconColor, val backgroundTint: Int = Int.MAX_VALUE) {
ERROR(GoogleMaterial.Icon.gmd_error, Color.WHITE, Color.RED) {
override fun onClick(activity: ImageActivity) {
activity.materialDialogThemed {
@@ -221,8 +214,11 @@ internal enum class FabStates(val iicon: IIcon, val iconColor: Int = Prefs.iconC
content(R.string.bad_image_overlay)
positiveText(R.string.kau_yes)
onPositive { _, _ ->
+ if (activity.errorRef != null)
+ L.e(activity.errorRef, "ImageActivity error report")
activity.sendEmail(R.string.dev_email, R.string.debug_image_link_subject) {
addItem("Url", activity.imageUrl)
+ addItem("Message", activity.errorRef?.message ?: "Null")
}
}
negativeText(R.string.kau_no)
@@ -248,8 +244,9 @@ internal enum class FabStates(val iicon: IIcon, val iconColor: Int = Prefs.iconC
}
activity.startActivity(intent)
} catch (e: Exception) {
+ activity.errorRef = e
e.logFrostAnswers("Image share failed")
- activity.snackbar(R.string.image_share_failed)
+ activity.frostSnackbar(R.string.image_share_failed)
}
}
};
@@ -259,14 +256,15 @@ internal enum class FabStates(val iicon: IIcon, val iconColor: Int = Prefs.iconC
* If it's in view, give it some animations
*/
fun update(fab: FloatingActionButton) {
+ val tint = if (backgroundTint != Int.MAX_VALUE) backgroundTint else Prefs.accentColor
if (fab.isHidden) {
fab.setIcon(iicon, color = iconColor)
- fab.backgroundTintList = ColorStateList.valueOf(backgroundTint)
+ fab.backgroundTintList = ColorStateList.valueOf(tint)
fab.show()
} else {
fab.fadeScaleTransition {
setIcon(iicon, color = iconColor)
- backgroundTintList = ColorStateList.valueOf(backgroundTint)
+ backgroundTintList = ColorStateList.valueOf(tint)
}
}
}
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 67f07635..6b1f2c5c 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt
@@ -1,6 +1,5 @@
package com.pitchedapps.frost.activities
-import android.content.Intent
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.os.Handler
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 759be983..45488c94 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt
@@ -368,6 +368,7 @@ class MainActivity : BaseActivity(), SearchWebView.SearchContract,
if (firstLoadFinished && hiddenSearchView == null) hiddenSearchView = SearchWebView(this, this)
if (searchView == null) searchView = bindSearchView(menu, R.id.action_search, Prefs.iconColor) {
textCallback = { query, _ -> runOnUiThread { hiddenSearchView?.query(query) } }
+ searchCallback = { query, _ -> launchWebOverlay("${FbItem.SEARCH.url}/?q=$query"); true }
foregroundColor = Prefs.textColor
backgroundColor = Prefs.bgColor.withMinAlpha(200)
openListener = { hiddenSearchView?.pauseLoad = false }
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/MediaPickerActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/MediaPickerActivity.kt
index 0d041e7a..162baf20 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/activities/MediaPickerActivity.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/MediaPickerActivity.kt
@@ -1,10 +1,30 @@
package com.pitchedapps.frost.activities
-import ca.allanwang.kau.mediapicker.MediaPickerActivityOverlayBase
-import ca.allanwang.kau.mediapicker.MediaType
+import android.content.Context
+import android.net.Uri
+import android.support.v4.content.FileProvider
+import ca.allanwang.kau.mediapicker.*
+import ca.allanwang.kau.utils.colorToBackground
+import com.pitchedapps.frost.BuildConfig
+import com.pitchedapps.frost.utils.Prefs
+import java.io.File
/**
* Created by Allan Wang on 2017-07-23.
*/
-class ImagePickerActivity : MediaPickerActivityOverlayBase(MediaType.IMAGE)
-class VideoPickerActivity : MediaPickerActivityOverlayBase(MediaType.VIDEO) \ No newline at end of file
+private fun actions(): List<MediaAction> {
+ val color = Prefs.accentColorForWhite
+ return listOf(object : MediaActionCamera(color) {
+
+ override fun createFile(context: Context): File
+ = createMediaFile("Frost", ".jpg")
+
+ override fun createUri(context: Context, file: File): Uri
+ = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", file)
+
+ }, MediaActionGallery(color = color.colorToBackground(0.1f)))
+}
+
+class ImagePickerActivity : MediaPickerActivityOverlayBase(MediaType.IMAGE, actions())
+
+class VideoPickerActivity : MediaPickerActivityOverlayBase(MediaType.VIDEO, actions()) \ No newline at end of file
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/WebOverlayActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/WebOverlayActivity.kt
index 7b612166..51aedae3 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/activities/WebOverlayActivity.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/WebOverlayActivity.kt
@@ -60,10 +60,7 @@ open class WebOverlayActivity : KauBaseActivity(),
supportActionBar?.setDisplayHomeAsUpEnabled(true)
toolbar.navigationIcon = GoogleMaterial.Icon.gmd_close.toDrawable(this, 16, Prefs.iconColor)
toolbar.setNavigationOnClickListener { finishSlideOut() }
- kauSwipeOnCreate {
- if (!Prefs.overlayFullScreenSwipe) edgeSize = 20.dpToPx
- transitionSystemBars = false
- }
+
setFrostColors(toolbar, themeWindow = false)
coordinator.setBackgroundColor(Prefs.bgColor.withAlpha(255))
@@ -77,6 +74,11 @@ open class WebOverlayActivity : KauBaseActivity(),
setAction(R.string.kau_got_it) { _ -> this.dismiss() }
}
}
+
+ kauSwipeOnCreate {
+ if (!Prefs.overlayFullScreenSwipe) edgeSize = 20.dpToPx
+ transitionSystemBars = false
+ }
}
/**
@@ -108,11 +110,6 @@ open class WebOverlayActivity : KauBaseActivity(),
toolbar.overflowIcon?.setTint(Prefs.iconColor)
}
- override fun onPostCreate(savedInstanceState: Bundle?) {
- super.onPostCreate(savedInstanceState)
- kauSwipeOnPostCreate()
- }
-
override fun onDestroy() {
super.onDestroy()
kauSwipeOnDestroy()
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/injectors/CssAssets.kt b/app/src/main/kotlin/com/pitchedapps/frost/injectors/CssAssets.kt
index 733bc151..cf935360 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/injectors/CssAssets.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/injectors/CssAssets.kt
@@ -25,21 +25,19 @@ enum class CssAssets(val folder: String = "themes") : InjectorContract {
try {
var content = webView.context.assets.open("css/$folder/$file").bufferedReader().use { it.readText() }
if (this == CUSTOM) {
- var bbt = Prefs.bgColor
val bt: String
- if (Color.alpha(bbt) == 255) {
- bbt = bbt.adjustAlpha(0.2f).colorToForeground(0.35f)
+ if (Color.alpha(Prefs.bgColor) == 255) {
bt = Prefs.bgColor.toRgbaString()
} else {
- bbt = bbt.adjustAlpha(0.05f).colorToForeground(0.5f)
bt = "transparent"
}
content = content
.replace("\$T\$", Prefs.textColor.toRgbaString())
.replace("\$TT\$", Prefs.textColor.colorToBackground(0.05f).toRgbaString())
+ .replace("\$A\$", Prefs.accentColor.toRgbaString())
.replace("\$B\$", Prefs.bgColor.toRgbaString())
.replace("\$BT\$", bt)
- .replace("\$BBT\$", bbt.toRgbaString())
+ .replace("\$BBT\$", Prefs.bgColor.withAlpha(51).colorToForeground(0.35f).toRgbaString())
.replace("\$O\$", Prefs.bgColor.withAlpha(255).toRgbaString())
.replace("\$OO\$", Prefs.bgColor.colorToForeground(0.35f).withAlpha(255).toRgbaString())
.replace("\$D\$", Prefs.textColor.adjustAlpha(0.3f).toRgbaString())
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 0387bb99..9fafbf88 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsInjector.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsInjector.kt
@@ -5,13 +5,14 @@ import com.pitchedapps.frost.web.FrostWebViewClient
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.subjects.SingleSubject
+import org.apache.commons.text.StringEscapeUtils
class JsBuilder {
private val css = StringBuilder()
private val js = StringBuilder()
fun css(css: String): JsBuilder {
- this.css.append(css)
+ this.css.append(StringEscapeUtils.escapeEcmaScript(css))
return this
}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/settings/Appearance.kt b/app/src/main/kotlin/com/pitchedapps/frost/settings/Appearance.kt
index e37afc33..7dff5b16 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/settings/Appearance.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/settings/Appearance.kt
@@ -55,7 +55,7 @@ fun SettingsActivity.getAppearancePrefs(): KPrefAdapterBuilder.() -> Unit = {
fun KPrefColorPicker.KPrefColorContract.dependsOnCustom() {
enabler = { Prefs.isCustomTheme }
- onDisabledClick = { itemView, _, _ -> itemView.frostSnackbar(R.string.requires_custom_theme); true }
+ onDisabledClick = { itemView, _, _ -> frostSnackbar(R.string.requires_custom_theme); true }
allowCustom = true
}
@@ -73,6 +73,17 @@ fun SettingsActivity.getAppearancePrefs(): KPrefAdapterBuilder.() -> Unit = {
allowCustomAlpha = false
}
+ colorPicker(R.string.accent_color, { Prefs.customAccentColor }, {
+ Prefs.customAccentColor = it
+ reload()
+ invalidateCustomTheme()
+ shouldRestartMain()
+ }) {
+ dependsOnCustom()
+ allowCustomAlpha = false
+ }
+
+
colorPicker(R.string.background_color, { Prefs.customBackgroundColor }, {
Prefs.customBackgroundColor = it
bgCanvas.ripple(it, duration = 500L)
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/settings/Notifications.kt b/app/src/main/kotlin/com/pitchedapps/frost/settings/Notifications.kt
index b1e5015f..8fcb3594 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/settings/Notifications.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/settings/Notifications.kt
@@ -2,12 +2,12 @@ package com.pitchedapps.frost.settings
import ca.allanwang.kau.kpref.activity.KPrefAdapterBuilder
import ca.allanwang.kau.utils.minuteToText
-import ca.allanwang.kau.utils.snackbar
import com.pitchedapps.frost.R
import com.pitchedapps.frost.activities.SettingsActivity
import com.pitchedapps.frost.services.fetchNotifications
import com.pitchedapps.frost.services.scheduleNotifications
import com.pitchedapps.frost.utils.Prefs
+import com.pitchedapps.frost.utils.frostSnackbar
import com.pitchedapps.frost.utils.materialDialogThemed
import com.pitchedapps.frost.views.Keywords
@@ -70,7 +70,7 @@ fun SettingsActivity.getNotificationPrefs(): KPrefAdapterBuilder.() -> Unit = {
onClick = {
_, _, _ ->
val text = if (fetchNotifications()) R.string.notification_fetch_success else R.string.notification_fetch_fail
- snackbar(text)
+ frostSnackbar(text)
true
}
}
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 9b8064a4..34257da8 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt
@@ -1,10 +1,10 @@
package com.pitchedapps.frost.utils
+import android.graphics.Color
import ca.allanwang.kau.kotlin.lazyResettable
import ca.allanwang.kau.kpref.KPref
import ca.allanwang.kau.kpref.StringSet
import ca.allanwang.kau.kpref.kpref
-import ca.allanwang.kau.utils.colorToForeground
import ca.allanwang.kau.utils.isColorVisibleOn
import com.pitchedapps.frost.facebook.FeedSort
import com.pitchedapps.frost.injectors.InjectorContract
@@ -26,6 +26,8 @@ object Prefs : KPref() {
var customTextColor: Int by kpref("color_text", 0xffeceff1.toInt())
+ var customAccentColor: Int by kpref("color_accent", 0xff0288d1.toInt())
+
var customBackgroundColor: Int by kpref("color_bg", 0xff212121.toInt())
var customHeaderColor: Int by kpref("color_header", 0xff01579b.toInt())
@@ -49,6 +51,14 @@ object Prefs : KPref() {
val textColor: Int
get() = t.textColor
+ val accentColor: Int
+ get() = t.accentColor
+
+ val accentColorForWhite: Int
+ get() = if (accentColor.isColorVisibleOn(Color.WHITE)) accentColor
+ else if (textColor.isColorVisibleOn(Color.WHITE)) textColor
+ else FACEBOOK_BLUE
+
val bgColor: Int
get() = t.bgColor
@@ -58,18 +68,6 @@ object Prefs : KPref() {
val iconColor: Int
get() = t.iconColor
- /**
- * Ensures that the color is visible against the background
- */
- val accentColor: Int
- get() = if (headerColor.isColorVisibleOn(bgColor, 100)) headerColor else textColor
-
- /**
- * Ensures that the color is visible against the background
- */
- val iconBackgroundColor: Int
- get() = if (headerColor.isColorVisibleOn(bgColor)) headerColor else headerColor.colorToForeground(0.2f)
-
val themeInjector: InjectorContract
get() = t.injector
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/Theme.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/Theme.kt
index cb265149..5cbb051d 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/utils/Theme.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Theme.kt
@@ -10,20 +10,71 @@ import com.pitchedapps.frost.injectors.JsActions
/**
* Created by Allan Wang on 2017-06-14.
*/
-enum class Theme(@StringRes val textRes: Int, val injector: InjectorContract,
- private val textColorGetter: () -> Int, private val backgroundColorGetter: () -> Int,
- private val headerColorGetter: () -> Int, private val iconColorGetter: () -> Int) {
- DEFAULT(R.string.kau_default, JsActions.EMPTY, { 0xde000000.toInt() }, { 0xfffafafa.toInt() }, { 0xff3b5998.toInt() }, { Color.WHITE }),
- LIGHT(R.string.kau_light, CssAssets.MATERIAL_LIGHT, { 0xde000000.toInt() }, { 0xfffafafa.toInt() }, { 0xff3b5998.toInt() }, { Color.WHITE }),
- DARK(R.string.kau_dark, CssAssets.MATERIAL_DARK, { Color.WHITE }, { 0xff303030.toInt() }, { 0xff2e4b86.toInt() }, { Color.WHITE }),
- AMOLED(R.string.kau_amoled, CssAssets.MATERIAL_AMOLED, { Color.WHITE }, { Color.BLACK }, { Color.BLACK }, { Color.WHITE }),
- GLASS(R.string.kau_glass, CssAssets.MATERIAL_GLASS, { Color.WHITE }, { 0x80000000.toInt() }, { 0xb3000000.toInt() }, { Color.WHITE }),
- CUSTOM(R.string.kau_custom, CssAssets.CUSTOM, { Prefs.customTextColor }, { Prefs.customBackgroundColor }, { Prefs.customHeaderColor }, { Prefs.customIconColor })
- ;
+const val FACEBOOK_BLUE = 0xff3b5998.toInt()
+const val BLUE_LIGHT = 0xff5d86dd.toInt()
+
+enum class Theme(@StringRes val textRes: Int,
+ val injector: InjectorContract,
+ private val textColorGetter: () -> Int,
+ private val accentColorGetter: () -> Int,
+ private val backgroundColorGetter: () -> Int,
+ private val headerColorGetter: () -> Int,
+ private val iconColorGetter: () -> Int) {
+
+ DEFAULT(R.string.kau_default,
+ JsActions.EMPTY,
+ { 0xde000000.toInt() },
+ { FACEBOOK_BLUE },
+ { 0xfffafafa.toInt() },
+ { FACEBOOK_BLUE },
+ { Color.WHITE }),
+
+ LIGHT(R.string.kau_light,
+ CssAssets.MATERIAL_LIGHT,
+ { 0xde000000.toInt() },
+ { FACEBOOK_BLUE },
+ { 0xfffafafa.toInt() },
+ { FACEBOOK_BLUE },
+ { Color.WHITE }),
+
+ DARK(R.string.kau_dark,
+ CssAssets.MATERIAL_DARK,
+ { Color.WHITE },
+ { BLUE_LIGHT },
+ { 0xff303030.toInt() },
+ { 0xff2e4b86.toInt() },
+ { Color.WHITE }),
+
+ AMOLED(R.string.kau_amoled,
+ CssAssets.MATERIAL_AMOLED,
+ { Color.WHITE },
+ { BLUE_LIGHT },
+ { Color.BLACK },
+ { Color.BLACK },
+ { Color.WHITE }),
+
+ GLASS(R.string.kau_glass,
+ CssAssets.MATERIAL_GLASS,
+ { Color.WHITE },
+ { BLUE_LIGHT },
+ { 0x80000000.toInt() },
+ { 0xb3000000.toInt() },
+ { Color.WHITE }),
+
+ CUSTOM(R.string.kau_custom,
+ CssAssets.CUSTOM,
+ { Prefs.customTextColor },
+ { Prefs.customAccentColor },
+ { Prefs.customBackgroundColor },
+ { Prefs.customHeaderColor },
+ { Prefs.customIconColor });
val textColor: Int
get() = textColorGetter()
+ val accentColor: Int
+ get() = accentColorGetter()
+
val bgColor: Int
get() = backgroundColorGetter()
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 e79816f3..469a3951 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt
@@ -13,6 +13,8 @@ import android.support.v7.widget.Toolbar
import android.view.View
import android.widget.FrameLayout
import android.widget.TextView
+import ca.allanwang.kau.mediapicker.createMediaFile
+import ca.allanwang.kau.mediapicker.createPrivateMediaFile
import ca.allanwang.kau.utils.*
import com.afollestad.materialdialogs.MaterialDialog
import com.bumptech.glide.RequestBuilder
@@ -54,6 +56,11 @@ fun Activity.cookies(): ArrayList<CookieModel> {
return intent?.extras?.getParcelableArrayList<CookieModel>(EXTRA_COOKIES) ?: arrayListOf()
}
+/**
+ * Launches the given url in a new overlay (if it already isn't in an overlay)
+ * Note that most requests may need to first check if the url can be launched as an overlay
+ * See [requestWebOverlay] to verify the launch
+ */
fun Context.launchWebOverlay(url: String) {
val argUrl = url.formattedFbUrl
L.v("Launch received", url)
@@ -141,17 +148,20 @@ fun Throwable?.logFrostAnswers(text: String) {
frostAnswersCustom("Errors", "text" to text, "message" to (this?.message ?: "NA"))
}
-fun View.frostSnackbar(@StringRes text: Int, builder: Snackbar.() -> Unit = {}) {
- Snackbar.make(this, text, Snackbar.LENGTH_LONG).apply {
- builder()
- //hacky workaround, but it has proper checks and shouldn't crash
- ((view as? FrameLayout)?.getChildAt(0) as? SnackbarContentLayout)?.apply {
- messageView.setTextColor(Prefs.textColor)
- actionView.setTextColor(Prefs.accentColor)
- //only set if previous text colors are set
- view.setBackgroundColor(Prefs.bgColor.withAlpha(255).colorToForeground(0.1f))
- }
- show()
+fun Activity.frostSnackbar(@StringRes text: Int, builder: Snackbar.() -> Unit = {})
+ = snackbar(text, Snackbar.LENGTH_LONG, frostSnackbar(builder))
+
+fun View.frostSnackbar(@StringRes text: Int, builder: Snackbar.() -> Unit = {})
+ = snackbar(text, Snackbar.LENGTH_LONG, frostSnackbar(builder))
+
+private inline fun frostSnackbar(crossinline builder: Snackbar.() -> Unit): Snackbar.() -> Unit = {
+ builder()
+ //hacky workaround, but it has proper checks and shouldn't crash
+ ((view as? FrameLayout)?.getChildAt(0) as? SnackbarContentLayout)?.apply {
+ messageView.setTextColor(Prefs.textColor)
+ actionView.setTextColor(Prefs.accentColor)
+ //only set if previous text colors are set
+ view.setBackgroundColor(Prefs.bgColor.withAlpha(255).colorToForeground(0.1f))
}
}
@@ -172,10 +182,13 @@ fun Context.createPrivateMediaFile(extension: String) = createPrivateMediaFile("
* @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
+ if (uri.toString().isFacebookUrl && !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
}
+inline val String?.isFacebookUrl
+ get() = this != null && this.contains(FACEBOOK_COM)
+
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IABBinder.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IABBinder.kt
index 7f6e8a6d..8aa3bcde 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IABBinder.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IABBinder.kt
@@ -110,7 +110,7 @@ abstract class IABBinder : FrostBilling {
}
val a = activity ?: return
- if (!BillingProcessor.isIabServiceAvailable(a) || !bp.isOneTimePurchaseSupported)
+ if (!BillingProcessor.isIabServiceAvailable(a) || !bp.isInitialized || !bp.isOneTimePurchaseSupported)
a.playStorePurchaseUnsupported()
else
bp.purchase(a, FROST_PRO)
@@ -127,7 +127,7 @@ class IABSettings : IABBinder() {
override fun onBillingError(errorCode: Int, error: Throwable?) {
super.onBillingError(errorCode, error)
- activity?.playStoreGenericError(null)
+ L.e("Billing error $errorCode ${error?.message}")
}
/**
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 d692c7aa..91673b15 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostViewPager.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostViewPager.kt
@@ -15,7 +15,12 @@ import com.pitchedapps.frost.utils.Prefs
class FrostViewPager @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : ViewPager(context, attrs) {
var enableSwipe = true
- override fun onInterceptTouchEvent(ev: MotionEvent?) = Prefs.viewpagerSwipe && enableSwipe && super.onInterceptTouchEvent(ev)
+ override fun onInterceptTouchEvent(ev: MotionEvent?) =
+ try {
+ Prefs.viewpagerSwipe && enableSwipe && super.onInterceptTouchEvent(ev)
+ } catch(e: IllegalArgumentException) {
+ true
+ }
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(ev: MotionEvent?): Boolean = Prefs.viewpagerSwipe && enableSwipe && super.onTouchEvent(ev)
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 61711092..386c5339 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClients.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClients.kt
@@ -4,9 +4,10 @@ import android.net.Uri
import android.webkit.*
import ca.allanwang.kau.permissions.PERMISSION_ACCESS_FINE_LOCATION
import ca.allanwang.kau.permissions.kauRequestPermissions
-import ca.allanwang.kau.utils.snackbar
+import com.pitchedapps.frost.R
import com.pitchedapps.frost.contracts.ActivityWebContract
import com.pitchedapps.frost.utils.L
+import com.pitchedapps.frost.utils.frostSnackbar
import io.reactivex.subjects.BehaviorSubject
import io.reactivex.subjects.Subject
@@ -52,7 +53,7 @@ class FrostChromeClient(webCore: FrostWebViewCore) : WebChromeClient() {
}
override fun onShowFileChooser(webView: WebView, filePathCallback: ValueCallback<Array<Uri>>, fileChooserParams: FileChooserParams): Boolean {
- activityContract?.openFileChooser(filePathCallback, fileChooserParams) ?: webView.snackbar("File chooser not found")
+ activityContract?.openFileChooser(filePathCallback, fileChooserParams) ?: webView.frostSnackbar(R.string.file_chooser_not_found)
return activityContract != null
}
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 f24a7a51..2abc9b25 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt
@@ -25,10 +25,14 @@ class FrostJSI(val webView: FrostWebViewCore) {
val cookies: ArrayList<CookieModel>
get() = activity?.cookies() ?: arrayListOf()
+ /**
+ * Attempts to load the url in an overlay
+ * Returns {@code true} if successful, meaning the event is consumed,
+ * or {@code false} otherwise, meaning the event should be propagated
+ */
@JavascriptInterface
- fun loadUrl(url: String) {
- context.launchWebOverlay(url)
- }
+ fun loadUrl(url: String?): Boolean
+ = if (url == null) false else context.requestWebOverlay(url)
@JavascriptInterface
fun reloadBaseUrl(animate: Boolean) {
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostRequestInterceptor.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostRequestInterceptor.kt
index 1a907f7f..2dfdda89 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostRequestInterceptor.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostRequestInterceptor.kt
@@ -3,6 +3,8 @@ package com.pitchedapps.frost.web
import android.webkit.WebResourceRequest
import android.webkit.WebResourceResponse
import android.webkit.WebView
+import ca.allanwang.kau.kotlin.LazyContext
+import ca.allanwang.kau.kotlin.lazyContext
import ca.allanwang.kau.utils.use
import com.pitchedapps.frost.utils.L
import com.pitchedapps.frost.utils.Prefs
@@ -19,41 +21,37 @@ import java.io.ByteArrayInputStream
private val blankResource: WebResourceResponse by lazy { WebResourceResponse("text/plain", "utf-8", ByteArrayInputStream("".toByteArray())) }
//these hosts will redirect to a blank resource
-private val blacklistHost: Set<String> by lazy {
- setOf(
- "edge-chat.facebook.com"
- )
-}
+private val blacklistHost: Set<String> =
+ setOf(
+ "edge-chat.facebook.com"
+ )
//these hosts will return null and skip logging
-private val whitelistHost: Set<String> by lazy {
- setOf(
- "static.xx.fbcdn.net",
- "m.facebook.com",
- "touch.facebook.com"
- )
-}
+private val whitelistHost: Set<String> =
+ setOf(
+ "static.xx.fbcdn.net",
+ "m.facebook.com",
+ "touch.facebook.com"
+ )
//these hosts will skip ad inspection
//this list does not have to include anything from the two above
-private val adWhitelistHost: Set<String> by lazy {
- setOf(
- "scontent-sea1-1.xx.fbcdn.net"
- )
-}
+private val adWhitelistHost: Set<String> =
+ setOf(
+ "scontent-sea1-1.xx.fbcdn.net"
+ )
-private var adblock: Set<String>? = null
+private val adblock: LazyContext<Set<String>> = lazyContext {
+ it.assets.open("adblock.txt").bufferedReader().use { it.readLines().toSet() }
+}
-fun shouldFrostInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? {
+fun WebView.shouldFrostInterceptRequest(request: WebResourceRequest): WebResourceResponse? {
val httpUrl = HttpUrl.parse(request.url?.toString() ?: return null) ?: return null
val host = httpUrl.host()
val url = httpUrl.toString()
if (blacklistHost.contains(host)) return blankResource
if (whitelistHost.contains(host)) return null
- if (!adWhitelistHost.contains(host)) {
- if (adblock == null) adblock = view.context.assets.open("adblock.txt").bufferedReader().use { it.readLines().toSet() }
- if (adblock?.any { url.contains(it) } ?: false) return blankResource
- }
+ if (!adWhitelistHost.contains(host) && adblock(context).any { url.contains(it) }) return blankResource
if (!shouldLoadImages && !Prefs.loadMediaOnMeteredNetwork && request.isMedia) return blankResource
L.v("Intercept Request", "$host $url")
return null
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostUrlOverlayValidator.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostUrlOverlayValidator.kt
new file mode 100644
index 00000000..0a1878b3
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostUrlOverlayValidator.kt
@@ -0,0 +1,48 @@
+package com.pitchedapps.frost.web
+
+import android.content.Context
+import com.pitchedapps.frost.facebook.formattedFbUrl
+import com.pitchedapps.frost.utils.L
+import com.pitchedapps.frost.utils.isFacebookUrl
+import com.pitchedapps.frost.utils.launchWebOverlay
+
+/**
+ * Created by Allan Wang on 2017-08-15.
+ *
+ * Due to the nature of facebook href's, many links
+ * cannot be resolved on a new window and must instead
+ * by loaded in the current page
+ * This helper method will collect all known cases and launch the overlay accordingly
+ * Returns {@code true} (default) if overlay is launcher, {@code false} otherwise
+ */
+fun Context.requestWebOverlay(url: String): Boolean {
+ if (url == "#") return false
+ /*
+ * Non facebook urls can be loaded
+ */
+ if (!url.formattedFbUrl.isFacebookUrl) {
+ 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)
+ launchWebOverlay(url)
+ return true
+}
+
+/**
+ * The following components should never be launched in a new overlay
+ */
+val overlayBlacklist = setOf("messages/?pageNum", "photoset_token") \ No newline at end of file
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 5f679c65..b1466ee8 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt
@@ -11,7 +11,6 @@ import com.pitchedapps.frost.activities.LoginActivity
import com.pitchedapps.frost.activities.MainActivity
import com.pitchedapps.frost.activities.SelectorActivity
import com.pitchedapps.frost.activities.WebOverlayActivity
-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.facebook.FbItem
@@ -34,7 +33,7 @@ import org.jetbrains.anko.withAlpha
open class BaseWebViewClient : WebViewClient() {
override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse?
- = shouldFrostInterceptRequest(view, request)
+ = view.shouldFrostInterceptRequest(request)
}
@@ -51,7 +50,7 @@ open class FrostWebViewClient(val webCore: FrostWebViewCore) : BaseWebViewClient
if (url == null) return
L.i("FWV Loading", url)
refreshObservable.onNext(true)
- if (!url.contains(FACEBOOK_COM)) return
+ if (!url.isFacebookUrl) return
if (url.contains("logout.php")) FbCookie.logout(Prefs.userId, { launchLogin(view.context) })
else if (url.contains("login.php")) FbCookie.reset({ launchLogin(view.context) })
}
@@ -71,18 +70,19 @@ open class FrostWebViewClient(val webCore: FrostWebViewCore) : BaseWebViewClient
override fun onPageCommitVisible(view: WebView, url: String?) {
super.onPageCommitVisible(view, url)
injectBackgroundColor()
- view.jsInject(
- CssAssets.ROUND_ICONS.maybe(Prefs.showRoundedIcons),
- CssHider.HEADER,
- CssHider.PEOPLE_YOU_MAY_KNOW.maybe(!Prefs.showSuggestedFriends && IS_FROST_PRO),
- Prefs.themeInjector,
- CssHider.NON_RECENT.maybe(webCore.url?.contains("?sk=h_chr") ?: false))
+ if (url.isFacebookUrl)
+ view.jsInject(
+ CssAssets.ROUND_ICONS.maybe(Prefs.showRoundedIcons),
+ CssHider.HEADER,
+ CssHider.PEOPLE_YOU_MAY_KNOW.maybe(!Prefs.showSuggestedFriends && IS_FROST_PRO),
+ Prefs.themeInjector,
+ CssHider.NON_RECENT.maybe(webCore.url?.contains("?sk=h_chr") ?: false))
}
override fun onPageFinished(view: WebView, url: String?) {
url ?: return
L.i("Page finished", url)
- if (!url.contains(FACEBOOK_COM)) {
+ if (!url.isFacebookUrl) {
refreshObservable.onNext(false)
return
}
@@ -124,9 +124,7 @@ open class FrostWebViewClient(val webCore: FrostWebViewCore) : BaseWebViewClient
*/
private fun launchRequest(request: WebResourceRequest): Boolean {
L.d("Launching Url", request.url?.toString() ?: "null")
- if (webCore.context is WebOverlayActivity) return false
- webCore.context.launchWebOverlay(request.url.toString())
- return true
+ return webCore.context !is WebOverlayActivity && webCore.context.requestWebOverlay(request.url.toString())
}
private fun launchImage(url: String, text: String? = null): Boolean {
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 aea25337..dd8e2390 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt
@@ -8,13 +8,13 @@ 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.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.isFacebookUrl
import org.jetbrains.anko.doAsync
import org.jetbrains.anko.uiThread
@@ -55,17 +55,18 @@ class LoginWebView @JvmOverloads constructor(
override fun onPageFinished(view: WebView, url: String?) {
super.onPageFinished(view, url)
- 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 = WEB_LOAD_DELAY) })
+ if (url.isFacebookUrl)
+ view.jsInject(CssHider.HEADER,
+ CssHider.CORE,
+ Prefs.themeInjector,
+ callback = { if (!view.isVisible) view.fadeIn(offset = WEB_LOAD_DELAY) })
+ else if (!view.isVisible) view.fadeIn()
}
fun checkForLogin(url: String?, onFound: (id: Long, cookie: String) -> Unit) {
doAsync {
- if (url == null || !url.contains(FACEBOOK_COM)) return@doAsync
+ 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