diff options
Diffstat (limited to 'app/src/main/kotlin')
27 files changed, 191 insertions, 172 deletions
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt index 12cb955d..c2b3cc0f 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt @@ -1,7 +1,7 @@ package com.pitchedapps.frost import android.os.Bundle -import android.support.v7.app.AppCompatActivity +import ca.allanwang.kau.internal.KauBaseActivity import com.pitchedapps.frost.activities.LoginActivity import com.pitchedapps.frost.activities.MainActivity import com.pitchedapps.frost.activities.SelectorActivity @@ -14,7 +14,7 @@ import com.pitchedapps.frost.utils.launchNewTask /** * Created by Allan Wang on 2017-05-28. */ -class StartActivity : AppCompatActivity() { +class StartActivity : KauBaseActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) 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 80d2aa48..a1717de1 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/AboutActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/AboutActivity.kt @@ -1,8 +1,10 @@ package com.pitchedapps.frost.activities +import android.os.Bundle import android.support.constraint.ConstraintLayout import android.support.constraint.ConstraintSet import android.support.v7.widget.RecyclerView +import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ImageView @@ -11,6 +13,8 @@ import ca.allanwang.kau.about.LibraryIItem import ca.allanwang.kau.adapters.FastItemThemedAdapter import ca.allanwang.kau.adapters.ThemableIItem import ca.allanwang.kau.adapters.ThemableIItemDelegate +import ca.allanwang.kau.animators.FadeScaleAnimatorAdd +import ca.allanwang.kau.animators.KauAnimator import ca.allanwang.kau.utils.* import com.mikepenz.aboutlibraries.Libs import com.mikepenz.aboutlibraries.entity.Library @@ -23,6 +27,9 @@ import com.mikepenz.iconics.typeface.IIcon import com.pitchedapps.frost.BuildConfig import com.pitchedapps.frost.R import com.pitchedapps.frost.utils.Prefs +import org.jetbrains.anko.doAsync +import org.jetbrains.anko.uiThread +import java.security.InvalidParameterException /** @@ -34,6 +41,8 @@ class AboutActivity : AboutActivityBase(null, { backgroundColor = Prefs.bgColor.withMinAlpha(200) cutoutForeground = if (0xff3b5998.toInt().isColorVisibleOn(Prefs.bgColor)) 0xff3b5998.toInt() else Prefs.accentColor cutoutDrawableRes = R.drawable.frost_f_256 + faqXmlRes = R.xml.frost_faq + faqParseNewLine = false }) { override fun getLibraries(libs: Libs): List<Library> { diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseActivity.kt index 6806bf24..77a20d04 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseActivity.kt @@ -1,18 +1,16 @@ package com.pitchedapps.frost.activities -import android.content.Intent import android.os.Bundle -import android.support.v7.app.AppCompatActivity +import ca.allanwang.kau.internal.KauBaseActivity import com.pitchedapps.frost.R import com.pitchedapps.frost.utils.Prefs import com.pitchedapps.frost.utils.materialDialogThemed import com.pitchedapps.frost.utils.setFrostTheme -import org.jetbrains.anko.contentView /** * Created by Allan Wang on 2017-06-12. */ -open class BaseActivity : AppCompatActivity() { +abstract class BaseActivity : KauBaseActivity() { override fun onBackPressed() { if (isTaskRoot && Prefs.exitConfirmation) { materialDialogThemed { 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 2e4ae410..e419c21c 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt @@ -10,14 +10,13 @@ import android.os.Bundle import android.os.Environment import android.support.design.widget.FloatingActionButton import android.support.v4.content.FileProvider -import android.support.v7.app.AppCompatActivity import android.view.View import android.view.ViewGroup import android.widget.ProgressBar import android.widget.TextView import ca.allanwang.kau.email.sendEmail +import ca.allanwang.kau.internal.KauBaseActivity import ca.allanwang.kau.permissions.PERMISSION_WRITE_EXTERNAL_STORAGE -import ca.allanwang.kau.permissions.kauOnRequestPermissionsResult import ca.allanwang.kau.permissions.kauRequestPermissions import ca.allanwang.kau.utils.* import com.bumptech.glide.Glide @@ -44,7 +43,7 @@ import java.util.* /** * Created by Allan Wang on 2017-07-15. */ -class ImageActivity : AppCompatActivity() { +class ImageActivity : KauBaseActivity() { val progress: ProgressBar by bindView(R.id.image_progress) val container: ViewGroup by bindView(R.id.image_container) @@ -230,11 +229,6 @@ class ImageActivity : AppCompatActivity() { deleteTempFile() super.onDestroy() } - - override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults) - kauOnRequestPermissionsResult(permissions, grantResults) - } } internal enum class FabStates(val iicon: IIcon, val iconColor: Int = Prefs.iconColor, val backgroundTint: Int = Prefs.iconBackgroundColor.withAlpha(255)) { diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/ImagePickerActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/ImagePickerActivity.kt deleted file mode 100644 index f18d358e..00000000 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/ImagePickerActivity.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.pitchedapps.frost.activities - -import android.content.res.ColorStateList -import android.os.Bundle -import ca.allanwang.kau.imagepicker.ImagePickerActivityBase -import ca.allanwang.kau.imagepicker.ImagePickerActivityOverlayBase -import com.pitchedapps.frost.utils.Prefs - -/** - * Created by Allan Wang on 2017-07-23. - */ -class ImagePickerActivity : ImagePickerActivityOverlayBase()
\ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/IntroActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/IntroActivity.kt index 28b8f466..2321a936 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/IntroActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/IntroActivity.kt @@ -8,12 +8,12 @@ import android.support.v4.app.Fragment import android.support.v4.app.FragmentManager import android.support.v4.app.FragmentPagerAdapter import android.support.v4.view.ViewPager -import android.support.v7.app.AppCompatActivity import android.view.View import android.view.WindowManager import android.widget.Button import android.widget.ImageButton import android.widget.ImageView +import ca.allanwang.kau.internal.KauBaseActivity import ca.allanwang.kau.ui.views.RippleCanvas import ca.allanwang.kau.ui.widgets.InkPageIndicator import ca.allanwang.kau.utils.* @@ -28,8 +28,11 @@ import org.jetbrains.anko.find /** * Created by Allan Wang on 2017-07-25. + * + * A beautiful intro activity + * Phone showcases are drawn via layers */ -class IntroActivity : AppCompatActivity(), ViewPager.PageTransformer, ViewPager.OnPageChangeListener { +class IntroActivity : KauBaseActivity(), ViewPager.PageTransformer, ViewPager.OnPageChangeListener { val ripple: RippleCanvas by bindView(R.id.intro_ripple) val viewpager: ViewPager by bindView(R.id.intro_viewpager) @@ -63,6 +66,7 @@ class IntroActivity : AppCompatActivity(), ViewPager.PageTransformer, ViewPager. if (barHasNext) viewpager.setCurrentItem(viewpager.currentItem + 1, true) else finish(next.x + next.pivotX, next.y + next.pivotY) } + skip.setOnClickListener { finish() } ripple.set(Prefs.bgColor) theme() } @@ -102,18 +106,19 @@ class IntroActivity : AppCompatActivity(), ViewPager.PageTransformer, ViewPager. ripple.ripple(blue, x, y, 600) { postDelayed(1000) { finish() } } - arrayOf(skip, indicator, next, fragments.last().view!!.find<View>(R.id.intro_title), fragments.last().view!!.find<View>(R.id.intro_desc)).forEach { - it.animate().alpha(0f).setDuration(600).start() + arrayOf(skip, indicator, next, fragments.last().view?.find<View>(R.id.intro_title), fragments.last().view?.find<View>(R.id.intro_desc)).forEach { + it?.animate()?.alpha(0f)?.setDuration(600)?.start() } if (Prefs.textColor != Color.WHITE) { - val f = fragments.last().view!!.find<ImageView>(R.id.intro_image).drawable - ValueAnimator.ofFloat(0f, 1f).apply { - addUpdateListener { - f.setTint(Prefs.textColor.blendWith(Color.WHITE, it.animatedValue as Float)) + val f = fragments.last().view?.find<ImageView>(R.id.intro_image)?.drawable + if (f != null) + ValueAnimator.ofFloat(0f, 1f).apply { + addUpdateListener { + f.setTint(Prefs.textColor.blendWith(Color.WHITE, it.animatedValue as Float)) + } + duration = 600 + start() } - duration = 600 - start() - } } if (Prefs.headerColor != blue) { ValueAnimator.ofFloat(0f, 1f).apply { 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 a6396b1b..df1228bd 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt @@ -23,12 +23,11 @@ import android.view.Menu import android.view.MenuItem import android.webkit.ValueCallback import android.webkit.WebChromeClient -import ca.allanwang.kau.changelog.showChangelog -import ca.allanwang.kau.permissions.kauOnRequestPermissionsResult import ca.allanwang.kau.searchview.SearchItem import ca.allanwang.kau.searchview.SearchView import ca.allanwang.kau.searchview.bindSearchView import ca.allanwang.kau.utils.* +import ca.allanwang.kau.xml.showChangelog import co.zsmb.materialdrawerkt.builders.Builder import co.zsmb.materialdrawerkt.builders.accountHeader import co.zsmb.materialdrawerkt.builders.drawer @@ -57,6 +56,7 @@ import com.pitchedapps.frost.fragments.WebFragment import com.pitchedapps.frost.utils.* import com.pitchedapps.frost.utils.iab.FrostBilling import com.pitchedapps.frost.utils.iab.IABMain +import com.pitchedapps.frost.utils.iab.IS_FROST_PRO import com.pitchedapps.frost.views.BadgedIcon import com.pitchedapps.frost.views.FrostViewPager import com.pitchedapps.frost.web.SearchWebView @@ -117,12 +117,11 @@ class MainActivity : BaseActivity(), SearchWebView.SearchContract, Prefs.versionCode = BuildConfig.VERSION_CODE if (!BuildConfig.DEBUG) { showChangelog(R.xml.frost_changelog, Prefs.textColor) { theme() } - frostAnswersCustom("Version") { - putCustomAttribute("Version code", BuildConfig.VERSION_CODE) - putCustomAttribute("Version name", BuildConfig.VERSION_NAME) - putCustomAttribute("Build type", BuildConfig.BUILD_TYPE) - putCustomAttribute("Frost id", Prefs.frostId) - } + frostAnswersCustom("Version", + "Version code" to BuildConfig.VERSION_CODE, + "Version name" to BuildConfig.VERSION_NAME, + "Build type" to BuildConfig.BUILD_TYPE, + "Frost id" to Prefs.frostId) } } setContentView(R.layout.activity_main) @@ -388,6 +387,7 @@ class MainActivity : BaseActivity(), SearchWebView.SearchContract, return true } + @SuppressLint("RestrictedApi") override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { R.id.action_settings -> { @@ -402,7 +402,7 @@ class MainActivity : BaseActivity(), SearchWebView.SearchContract, } override fun openFileChooser(filePathCallback: ValueCallback<Array<Uri>>, fileChooserParams: WebChromeClient.FileChooserParams) { - openImagePicker(filePathCallback, fileChooserParams) + openMediaPicker(filePathCallback, fileChooserParams) } @SuppressLint("NewApi") @@ -435,11 +435,6 @@ class MainActivity : BaseActivity(), SearchWebView.SearchContract, } } - override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults) - kauOnRequestPermissionsResult(permissions, grantResults) - } - override fun onResume() { super.onResume() FbCookie.switchBackUser { } @@ -447,7 +442,7 @@ class MainActivity : BaseActivity(), SearchWebView.SearchContract, override fun onStart() { //validate some pro features - if (!Prefs.pro) { + if (!IS_FROST_PRO) { if (Prefs.theme == Theme.CUSTOM.ordinal) Prefs.theme = Theme.DEFAULT.ordinal } super.onStart() diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/MediaPickerActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/MediaPickerActivity.kt new file mode 100644 index 00000000..0d041e7a --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/MediaPickerActivity.kt @@ -0,0 +1,10 @@ +package com.pitchedapps.frost.activities + +import ca.allanwang.kau.mediapicker.MediaPickerActivityOverlayBase +import ca.allanwang.kau.mediapicker.MediaType + +/** + * Created by Allan Wang on 2017-07-23. + */ +class ImagePickerActivity : MediaPickerActivityOverlayBase(MediaType.IMAGE) +class VideoPickerActivity : MediaPickerActivityOverlayBase(MediaType.VIDEO)
\ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/SettingsActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/SettingsActivity.kt index 8455bf1e..7cbbe4df 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/SettingsActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/SettingsActivity.kt @@ -1,18 +1,22 @@ package com.pitchedapps.frost.activities +import android.annotation.SuppressLint import android.app.Activity import android.content.Intent import android.os.Bundle import android.view.Menu import android.view.MenuItem import ca.allanwang.kau.about.kauLaunchAbout -import ca.allanwang.kau.changelog.showChangelog import ca.allanwang.kau.kpref.activity.CoreAttributeContract import ca.allanwang.kau.kpref.activity.KPrefActivity import ca.allanwang.kau.kpref.activity.KPrefAdapterBuilder import ca.allanwang.kau.kpref.activity.items.KPrefItemBase import ca.allanwang.kau.ui.views.RippleCanvas -import ca.allanwang.kau.utils.* +import ca.allanwang.kau.utils.finishSlideOut +import ca.allanwang.kau.utils.setMenuIcons +import ca.allanwang.kau.utils.string +import ca.allanwang.kau.utils.tint +import ca.allanwang.kau.xml.showChangelog import com.mikepenz.community_material_typeface_library.CommunityMaterial import com.mikepenz.google_material_typeface_library.GoogleMaterial import com.pitchedapps.frost.BuildConfig @@ -75,13 +79,14 @@ class SettingsActivity : KPrefActivity(), FrostBilling by IABSettings() { } plainText(R.string.about_frost) { + descRes = R.string.about_frost_desc iicon = GoogleMaterial.Icon.gmd_info onClick = { _, _, _ -> kauLaunchAbout(AboutActivity::class.java); true } } plainText(R.string.replay_intro) { iicon = GoogleMaterial.Icon.gmd_replay - onClick = {_,_,_-> launchIntroActivity(cookies()); true} + onClick = { _, _, _ -> launchIntroActivity(cookies()); true } } if (BuildConfig.DEBUG) { @@ -98,6 +103,7 @@ class SettingsActivity : KPrefActivity(), FrostBilling by IABSettings() { setFrostResult(MainActivity.REQUEST_RESTART) } + @SuppressLint("MissingSuperCall") override fun onCreate(savedInstanceState: Bundle?) { setFrostTheme(true) super.onCreate(savedInstanceState) 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 13d72ffe..7b612166 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/WebOverlayActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/WebOverlayActivity.kt @@ -5,13 +5,12 @@ import android.net.Uri import android.os.Bundle import android.support.design.widget.CoordinatorLayout import android.support.design.widget.Snackbar -import android.support.v7.app.AppCompatActivity import android.support.v7.widget.Toolbar import android.view.Menu import android.view.MenuItem import android.webkit.ValueCallback import android.webkit.WebChromeClient -import ca.allanwang.kau.permissions.kauOnRequestPermissionsResult +import ca.allanwang.kau.internal.KauBaseActivity import ca.allanwang.kau.swipe.kauSwipeOnCreate import ca.allanwang.kau.swipe.kauSwipeOnDestroy import ca.allanwang.kau.swipe.kauSwipeOnPostCreate @@ -31,7 +30,7 @@ import com.pitchedapps.frost.web.FrostWebView /** * Created by Allan Wang on 2017-06-01. */ -open class WebOverlayActivity : AppCompatActivity(), +open class WebOverlayActivity : KauBaseActivity(), ActivityWebContract, FileChooserContract by FileChooserDelegate() { val toolbar: Toolbar by bindView(R.id.overlay_toolbar) @@ -126,18 +125,13 @@ open class WebOverlayActivity : AppCompatActivity(), } override fun openFileChooser(filePathCallback: ValueCallback<Array<Uri>>, fileChooserParams: WebChromeClient.FileChooserParams) { - openImagePicker(filePathCallback, fileChooserParams) + openMediaPicker(filePathCallback, fileChooserParams) } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { if (onActivityResultWeb(requestCode, resultCode, data)) return } - override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults) - kauOnRequestPermissionsResult(permissions, grantResults) - } - override fun onCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.menu_web, menu) toolbar.tint(Prefs.iconColor) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/contracts/FileChooser.kt b/app/src/main/kotlin/com/pitchedapps/frost/contracts/FileChooser.kt index bd31d6ce..fd8a3677 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/contracts/FileChooser.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/contracts/FileChooser.kt @@ -5,16 +5,18 @@ import android.content.Intent import android.net.Uri import android.webkit.ValueCallback import android.webkit.WebChromeClient -import ca.allanwang.kau.imagepicker.kauLaunchImagePicker -import ca.allanwang.kau.imagepicker.kauOnImagePickerResult +import ca.allanwang.kau.mediapicker.MediaPickerActivityOverlayBase +import ca.allanwang.kau.mediapicker.MediaType +import ca.allanwang.kau.mediapicker.kauLaunchMediaPicker +import ca.allanwang.kau.mediapicker.kauOnMediaPickerResult import com.pitchedapps.frost.activities.ImagePickerActivity +import com.pitchedapps.frost.activities.VideoPickerActivity import com.pitchedapps.frost.utils.L -import java.io.File /** * Created by Allan Wang on 2017-07-04. */ -const val IMAGE_CHOOSER_REQUEST = 67 +const val MEDIA_CHOOSER_RESULT = 67 interface FileChooserActivityContract { fun openFileChooser(filePathCallback: ValueCallback<Array<Uri>>, fileChooserParams: WebChromeClient.FileChooserParams) @@ -22,7 +24,7 @@ interface FileChooserActivityContract { interface FileChooserContract { var filePathCallback: ValueCallback<Array<Uri>>? - fun Activity.openImagePicker(filePathCallback: ValueCallback<Array<Uri>>, fileChooserParams: WebChromeClient.FileChooserParams) + fun Activity.openMediaPicker(filePathCallback: ValueCallback<Array<Uri>>, fileChooserParams: WebChromeClient.FileChooserParams) fun Activity.onActivityResultWeb(requestCode: Int, resultCode: Int, intent: Intent?): Boolean } @@ -30,16 +32,17 @@ class FileChooserDelegate : FileChooserContract { override var filePathCallback: ValueCallback<Array<Uri>>? = null - override fun Activity.openImagePicker(filePathCallback: ValueCallback<Array<Uri>>, fileChooserParams: WebChromeClient.FileChooserParams) { + override fun Activity.openMediaPicker(filePathCallback: ValueCallback<Array<Uri>>, fileChooserParams: WebChromeClient.FileChooserParams) { this@FileChooserDelegate.filePathCallback = filePathCallback - kauLaunchImagePicker(ImagePickerActivity::class.java, IMAGE_CHOOSER_REQUEST) + val isVideo = fileChooserParams.acceptTypes.firstOrNull() == "video/*" + kauLaunchMediaPicker(if (isVideo) VideoPickerActivity::class.java else ImagePickerActivity::class.java, MEDIA_CHOOSER_RESULT) } override fun Activity.onActivityResultWeb(requestCode: Int, resultCode: Int, intent: Intent?): Boolean { L.d("FileChooser On activity results web $requestCode") - if (requestCode != IMAGE_CHOOSER_REQUEST) return false - val results = kauOnImagePickerResult(resultCode, intent).map { it.uri }.toTypedArray() - L.d("FileChooser result ${results.contentToString()}") + if (requestCode != MEDIA_CHOOSER_RESULT) return false + val results = kauOnMediaPickerResult(resultCode, intent).map { it.uri }.toTypedArray() + L.i("FileChooser result ${results.contentToString()}") //proper path content://com.android.providers.media.documents/document/image%3A36341 L.d("FileChooser Callback received; ${filePathCallback != null}") filePathCallback?.onReceiveValue(results) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbUrlFormatter.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbUrlFormatter.kt index 622be067..7cd93d14 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbUrlFormatter.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbUrlFormatter.kt @@ -1,5 +1,7 @@ package com.pitchedapps.frost.facebook +import com.pitchedapps.frost.utils.L + /** * Created by Allan Wang on 2017-07-07. * @@ -13,22 +15,27 @@ class FbUrlFormatter(url: String) { val cleaned: String init { - var cleanedUrl = url - discardable.forEach { cleanedUrl = cleanedUrl.replace(it, "", true) } - val changed = cleanedUrl != url //note that discardables strip away the first ? - decoder.forEach { (k, v) -> cleanedUrl = cleanedUrl.replace(k, v, true) } - val qm = cleanedUrl.indexOf(if (changed) "&" else "?") - if (qm > -1) { - cleanedUrl.substring(qm + 1).split("&").forEach { - val p = it.split("=") - queries.put(p[0], p.elementAtOrNull(1) ?: "") + if (url.isNullOrBlank()) cleaned = "" + else { + var cleanedUrl = url + discardable.forEach { cleanedUrl = cleanedUrl.replace(it, "", true) } + val changed = cleanedUrl != url //note that discardables strip away the first ? + decoder.forEach { (k, v) -> cleanedUrl = cleanedUrl.replace(k, v, true) } + val qm = cleanedUrl.indexOf(if (changed) "&" else "?") + if (qm > -1) { + cleanedUrl.substring(qm + 1).split("&").forEach { + val p = it.split("=") + queries.put(p[0], p.elementAtOrNull(1) ?: "") + } + cleanedUrl = cleanedUrl.substring(0, qm) } - cleanedUrl = cleanedUrl.substring(0, qm) + discardableQueries.forEach { queries.remove(it) } + if (cleanedUrl.startsWith("#!/")) cleanedUrl = cleanedUrl.substring(2) + if (cleanedUrl.startsWith("/")) cleanedUrl = FB_URL_BASE + cleanedUrl.substring(1) + cleanedUrl = cleanedUrl.replaceFirst(".facebook.com//", ".facebook.com/") //sometimes we are given a bad url + L.v("Formatted url from $url to $cleanedUrl") + cleaned = cleanedUrl } - discardableQueries.forEach { queries.remove(it) } - if (cleanedUrl.startsWith("#!/")) cleanedUrl = cleanedUrl.substring(2) - if (cleanedUrl.startsWith("/")) cleanedUrl = FB_URL_BASE + cleanedUrl.substring(1) - cleaned = cleanedUrl } override fun toString(): String { 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 ac979c85..0992a9cb 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/injectors/CssAssets.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/injectors/CssAssets.kt @@ -3,7 +3,6 @@ package com.pitchedapps.frost.injectors import android.graphics.Color import android.webkit.WebView import ca.allanwang.kau.utils.* -import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.Prefs /** diff --git a/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsActions.kt b/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsActions.kt index de270948..fae1846b 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsActions.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsActions.kt @@ -15,7 +15,9 @@ enum class JsActions(body: String) : InjectorContract { */ LOGIN_CHECK("document.getElementById('signup-button')&&Frost.loadLogin();"), BASE_HREF("document.write(\"<base href='$FB_URL_BASE'/>\");"), - GET_MESSAGES("setTimeout(function(){Frost.handleHtml(document.getElementById('threadlist_rows').outerHtml)},1000)"), + /** + * Used as a pseudoinjector for maybe functions + */ EMPTY(""); val function = "!function(){$body}();" diff --git a/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsAssets.kt b/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsAssets.kt index b22abd70..d2201c52 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsAssets.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsAssets.kt @@ -8,7 +8,7 @@ import android.webkit.WebView * The enum name must match the css file name */ enum class JsAssets : InjectorContract { - MENU, CLICK_A, CONTEXT_A, HEADER_BADGES, SEARCH, TEXTAREA_LISTENER + MENU, CLICK_A, CONTEXT_A, HEADER_BADGES, SEARCH, TEXTAREA_LISTENER, NOTIF_MSG ; var file = "${name.toLowerCase()}.min.js" 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 05497904..3ddad869 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt @@ -14,10 +14,13 @@ import com.pitchedapps.frost.dbflow.loadFbCookiesSync import com.pitchedapps.frost.facebook.FACEBOOK_COM import com.pitchedapps.frost.facebook.FbTab import com.pitchedapps.frost.facebook.USER_AGENT_BASIC +import com.pitchedapps.frost.facebook.formattedFbUrl import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.Prefs import com.pitchedapps.frost.utils.frostAnswersCustom +import com.pitchedapps.frost.web.MessageWebView import org.jetbrains.anko.doAsync +import org.jetbrains.anko.uiThread import org.jsoup.Jsoup import org.jsoup.nodes.Element import java.util.concurrent.Future @@ -32,6 +35,8 @@ class NotificationService : JobService() { var future: Future<Unit>? = null + val startTime = System.currentTimeMillis() + companion object { val epochMatcher: Regex by lazy { Regex(":([0-9]*?),") } val notifIdMatcher: Regex by lazy { Regex("notif_id\":([0-9]*?),") } @@ -40,38 +45,47 @@ class NotificationService : JobService() { } override fun onStopJob(params: JobParameters?): Boolean { + val time = System.currentTimeMillis() - startTime + L.d("Notification service has finished abruptly in $time ms") + frostAnswersCustom("NotificationTime", + "Type" to "Service force stop", + "IM Included" to Prefs.notificationsInstantMessages, + "Duration" to time) future?.cancel(true) future = null return false } + fun finish(params: JobParameters?) { + val time = System.currentTimeMillis() - startTime + L.d("Notification service has finished in $time ms") + frostAnswersCustom("NotificationTime", + "Type" to "Service", + "IM Included" to Prefs.notificationsInstantMessages, + "Duration" to time) + jobFinished(params, false) + future?.cancel(true) + future = null + } + override fun onStartJob(params: JobParameters?): Boolean { future = doAsync { if (Prefs.notificationAllAccounts) { val cookies = loadFbCookiesSync() cookies.forEach { fetchGeneralNotifications(it) } -// if (Prefs.notificationsInstantMessages) { -// Prefs.prevId = Prefs.userId -// uiThread { -// val messageWebView = MessageWebView(this@NotificationService, params) -// cookies.forEach { messageWebView.request(it) } -// } -// return@doAsync -// } } else { val currentCookie = loadFbCookie(Prefs.userId) if (currentCookie != null) { fetchGeneralNotifications(currentCookie) -// if (Prefs.notificationsInstantMessages) { -// uiThread { MessageWebView(this@NotificationService, params).request(currentCookie) } -// return@doAsync -// } } } - L.d("Finished notifications") - jobFinished(params, false) - future = null + L.d("Finished main notifications") + if (Prefs.notificationsInstantMessages) { + val currentCookie = loadFbCookie(Prefs.userId) + if (currentCookie != null) + uiThread { MessageWebView(this@NotificationService, params, currentCookie) } + } else finish(params) } return true } @@ -104,10 +118,7 @@ class NotificationService : JobService() { } if (newLatestEpoch != prevLatestEpoch) prevNotifTime.copy(epoch = newLatestEpoch).save() L.d("Notif new latest epoch ${lastNotificationTime(data.id).epoch}") - frostAnswersCustom("Notifications") { - putCustomAttribute("Type", "General") - putCustomAttribute("Count", notifCount) - } + frostAnswersCustom("Notifications", "Type" to "General", "Count" to notifCount) summaryNotification(data.id, notifCount) } @@ -132,12 +143,6 @@ class NotificationService : JobService() { val doc = Jsoup.parseBodyFragment(content) val unreadNotifications = (doc.getElementById("threadlist_rows") ?: return L.eThrow("Notification messages not found")).getElementsByClass("aclb") var notifCount = 0 - L.d("IM notif count ${unreadNotifications.size}") - unreadNotifications.forEach { - with(it) { - L.d("notif ${id()} ${className()}") - } - } val prevNotifTime = lastNotificationTime(data.id) val prevLatestEpoch = prevNotifTime.epochIm L.v("Notif Prev Latest Im Epoch $prevLatestEpoch") @@ -154,10 +159,7 @@ class NotificationService : JobService() { } if (newLatestEpoch != prevLatestEpoch) prevNotifTime.copy(epochIm = newLatestEpoch).save() L.d("Notif new latest im epoch ${lastNotificationTime(data.id).epochIm}") - frostAnswersCustom("Notifications") { - putCustomAttribute("Type", "Message") - putCustomAttribute("Count", notifCount) - } + frostAnswersCustom("Notifications", "Type" to "Message", "Count" to notifCount) summaryNotification(data.id, notifCount) } @@ -173,7 +175,8 @@ class NotificationService : JobService() { //fetch convo pic val p = element.select("i.img[style*=url]") val pUrl = profMatcher.find(p.attr("style"))?.groups?.get(1)?.value ?: "" - return NotificationContent(data, notifId.toInt(), a.attr("href"), a.text(), text, epoch, pUrl) + L.v("url ${a.attr("href")}") + return NotificationContent(data, notifId.toInt(), a.attr("href"), a.text(), text, epoch, pUrl.formattedFbUrl) } private fun Context.debugNotification(text: String) { 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 9c9754bb..e37afc33 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/settings/Appearance.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/settings/Appearance.kt @@ -41,7 +41,7 @@ fun SettingsActivity.getAppearancePrefs(): KPrefAdapterBuilder.() -> Unit = { setFrostTheme(true) themeExterior() invalidateOptionsMenu() - frostAnswersCustom("Theme") { putCustomAttribute("Count", text.toString()) } + frostAnswersCustom("Theme", "Count" to text) } true } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/settings/Experimental.kt b/app/src/main/kotlin/com/pitchedapps/frost/settings/Experimental.kt index 05a852ee..594cbe01 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/settings/Experimental.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/settings/Experimental.kt @@ -22,7 +22,9 @@ fun SettingsActivity.getExperimentalPrefs(): KPrefAdapterBuilder.() -> Unit = { // Experimental content starts here ------------------ - + checkbox(R.string.notification_messages, { Prefs.notificationsInstantMessages }, { Prefs.notificationsInstantMessages = it }) { + descRes = R.string.notification_messages_desc + } // Experimental content ends here -------------------- 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 a20e755f..b053b9dd 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt @@ -97,7 +97,8 @@ object Prefs : KPref() { var notificationAllAccounts: Boolean by kpref("notification_all_accounts", true) - var notificationsInstantMessages: Boolean by kpref("notification_im", true) + //todo remove from experimental once stabilized + var notificationsInstantMessages: Boolean by kpref("notification_im", Showcase.experimentalDefault) var notificationVibrate: Boolean by kpref("notification_vibrate", true) 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 40e16f20..cc3ea52e 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt @@ -116,9 +116,14 @@ fun frostAnswers(action: Answers.() -> Unit) { Answers.getInstance().action() } -fun frostAnswersCustom(name: String, action: CustomEvent.() -> Unit = {}) { +fun frostAnswersCustom(name: String, vararg events: Pair<String, Any>) { frostAnswers { - logCustom(CustomEvent("Frost $name").apply { action() }) + logCustom(CustomEvent("Frost $name").apply { + events.forEach { (key, value) -> + if (value is Number) putCustomAttribute(key, value) + else putCustomAttribute(key, value.toString()) + } + }) } } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/WebContextMenu.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/WebContextMenu.kt index ea66030f..3918a993 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/utils/WebContextMenu.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/WebContextMenu.kt @@ -41,6 +41,7 @@ class WebContext(val unformattedUrl: String, val text: String?) { } enum class WebContextType(val textId: Int, val onClick: (c: Context, wc: WebContext) -> Unit) { + OPEN_LINK(R.string.open_link, { c, wc -> c.launchWebOverlay(wc.unformattedUrl) }), COPY_LINK(R.string.copy_link, { c, wc -> c.copyToClipboard(wc.url) }), COPY_TEXT(R.string.copy_text, { c, wc -> if (wc.text != null) c.copyToClipboard(wc.text) else c.toast(R.string.no_text) }), SHARE_LINK(R.string.share_link, { c, wc -> c.shareText(wc.url) }), 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 b3992ff4..bad7f8fd 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 @@ -15,6 +15,10 @@ import com.pitchedapps.frost.utils.frostAnswers */ private const val FROST_PRO = "frost_pro" +/** + * Implemented pro checker with a hook for debug builds + * Use this when checking if the pro feature is enabled + */ val IS_FROST_PRO: Boolean get() = (BuildConfig.DEBUG && Prefs.debugPro) || Prefs.pro diff --git a/app/src/main/kotlin/com/pitchedapps/frost/views/BadgedIcon.kt b/app/src/main/kotlin/com/pitchedapps/frost/views/BadgedIcon.kt index df468715..8ae54ef3 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/views/BadgedIcon.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/views/BadgedIcon.kt @@ -1,8 +1,6 @@ package com.pitchedapps.frost.views import android.content.Context -import android.graphics.Color -import android.graphics.drawable.ColorDrawable import android.graphics.drawable.GradientDrawable import android.support.constraint.ConstraintLayout import android.util.AttributeSet 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 b8ba0d1d..343674d5 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClients.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClients.kt @@ -1,10 +1,9 @@ package com.pitchedapps.frost.web import android.net.Uri -import android.webkit.ConsoleMessage -import android.webkit.ValueCallback -import android.webkit.WebChromeClient -import android.webkit.WebView +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.contracts.ActivityWebContract import com.pitchedapps.frost.utils.L @@ -33,6 +32,7 @@ class FrostChromeClient(webCore: FrostWebViewCore) : WebChromeClient() { val progressObservable: Subject<Int> = webCore.progressObservable val titleObservable: BehaviorSubject<String> = webCore.titleObservable val activityContract = (webCore.context as? ActivityWebContract) + val context = webCore.context!! companion object { val consoleBlacklist = setOf( @@ -62,4 +62,12 @@ class FrostChromeClient(webCore: FrostWebViewCore) : WebChromeClient() { return activityContract != null } + override fun onGeolocationPermissionsShowPrompt(origin: String, callback: GeolocationPermissions.Callback) { + L.d("Requesting geolocation") + context.kauRequestPermissions(PERMISSION_ACCESS_FINE_LOCATION) { + granted, _ -> + L.d("Geolocation response received; ${if (granted) "granted" else "denied"}") + 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 6ec1aec5..1679d7a3 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebView.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebView.kt @@ -16,6 +16,7 @@ import ca.allanwang.kau.utils.withAlpha 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 io.reactivex.android.schedulers.AndroidSchedulers @@ -61,7 +62,7 @@ class FrostWebView @JvmOverloads constructor( baseEnum = enum with(settings) { javaScriptEnabled = true - if (url.contains("com/message")) + if (url.contains("/message")) userAgentString = USER_AGENT_BASIC allowFileAccess = true textZoom = Prefs.webTextScaling 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 cb5125c4..9f7dd916 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt @@ -1,6 +1,5 @@ package com.pitchedapps.frost.web -import android.content.ActivityNotFoundException import android.content.Context import android.content.Intent import android.graphics.Bitmap @@ -17,6 +16,7 @@ import com.pitchedapps.frost.facebook.FB_URL_BASE import com.pitchedapps.frost.facebook.FbCookie import com.pitchedapps.frost.injectors.* import com.pitchedapps.frost.utils.* +import com.pitchedapps.frost.utils.iab.IS_FROST_PRO import io.reactivex.subjects.Subject /** @@ -71,8 +71,8 @@ open class FrostWebViewClient(val webCore: FrostWebViewCore) : BaseWebViewClient } view.jsInject( CssAssets.ROUND_ICONS.maybe(Prefs.showRoundedIcons), - CssHider.PEOPLE_YOU_MAY_KNOW.maybe(!Prefs.showSuggestedFriends && Prefs.pro), - CssHider.ADS.maybe(!Prefs.showFacebookAds && Prefs.pro) + CssHider.PEOPLE_YOU_MAY_KNOW.maybe(!Prefs.showSuggestedFriends && IS_FROST_PRO), + CssHider.ADS.maybe(!Prefs.showFacebookAds && IS_FROST_PRO) ) onPageFinishedActions(url) } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/MessageWebView.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/MessageWebView.kt index 0f3a12b6..53fa0657 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/MessageWebView.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/MessageWebView.kt @@ -6,23 +6,24 @@ import android.webkit.JavascriptInterface import android.webkit.WebView import ca.allanwang.kau.utils.gone import com.pitchedapps.frost.dbflow.CookieModel -import com.pitchedapps.frost.facebook.FbCookie import com.pitchedapps.frost.facebook.FbTab import com.pitchedapps.frost.facebook.USER_AGENT_BASIC -import com.pitchedapps.frost.injectors.JsActions +import com.pitchedapps.frost.injectors.JsAssets import com.pitchedapps.frost.services.NotificationService import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.frostAnswersCustom import org.jetbrains.anko.doAsync -import org.jetbrains.anko.runOnUiThread -@SuppressLint("ViewConstructor") /** * Created by Allan Wang on 2017-07-17. * * Bare boned headless view made solely to extract conversation info */ -class MessageWebView(val service: NotificationService, val params: JobParameters?) : WebView(service) { +@SuppressLint("ViewConstructor") +class MessageWebView(val service: NotificationService, val params: JobParameters?, val cookie: CookieModel) : WebView(service) { + + private val startTime = System.currentTimeMillis() + private var isCancelled = false init { gone() @@ -33,48 +34,33 @@ class MessageWebView(val service: NotificationService, val params: JobParameters private fun setupWebview() { settings.javaScriptEnabled = true settings.userAgentString = USER_AGENT_BASIC - webViewClient = HeadlessWebViewClient("MessageNotifs", JsActions.GET_MESSAGES) + webViewClient = HeadlessWebViewClient("MessageNotifs", JsAssets.NOTIF_MSG) webChromeClient = QuietChromeClient() addJavascriptInterface(MessageJSI(), "Frost") + loadUrl(FbTab.MESSAGES.url) } - private val startTime = System.currentTimeMillis() - private val endTime: Long by lazy { System.currentTimeMillis() } - private var inProgress = false - private val pendingRequests: MutableList<CookieModel> = mutableListOf() - private lateinit var data: CookieModel - - fun request(data: CookieModel) { - pendingRequests.add(data) - if (inProgress) return - inProgress = true - load(data) + fun finish() { + if (isCancelled) return + isCancelled = true + post { destroy() } + service.finish(params) } - private fun load(data: CookieModel) { - L.d("Notif retrieving messages", data.toString()) - this.data = data - FbCookie.setWebCookie(data.cookie) { context.runOnUiThread { L.d("Notif messages load"); loadUrl(FbTab.MESSAGES.url) } } + override fun destroy() { + L.d("MessageWebView destroyed") + super.destroy() } inner class MessageJSI { @JavascriptInterface fun handleHtml(html: String) { - L.d("Notif messages received", data.toString()) - doAsync { service.fetchMessageNotifications(data, html) } - pendingRequests.remove(data) - if (pendingRequests.isEmpty()) { - val time = endTime - startTime - L.d("Notif messages finished $time") - frostAnswersCustom("Notifications") { - putCustomAttribute("Message retrieval duration", time) - } - post { destroy() } - service.jobFinished(params, false) - service.future = null - } else { - load(pendingRequests.first()) - } + if (isCancelled) return + if (html.length < 10) return finish() + val time = System.currentTimeMillis() - startTime + L.d("Notif messages fetched in $time ms") + frostAnswersCustom("NotificationTime", "Type" to "IM Headless", "Duration" to time) + doAsync { service.fetchMessageNotifications(cookie, html); finish() } } } |