aboutsummaryrefslogtreecommitdiff
path: root/app/src/main/kotlin/com/pitchedapps/frost/activities
diff options
context:
space:
mode:
authorAllan Wang <me@allanwang.ca>2017-07-16 17:26:58 -0700
committerGitHub <noreply@github.com>2017-07-16 17:26:58 -0700
commitd90cb9b61cd2e033b46f4780ad1340c5f35b7751 (patch)
tree0294ce22bacb463c9cc95de8dc5581c1bd59a108 /app/src/main/kotlin/com/pitchedapps/frost/activities
parentc3f1fc6a8b3216442a018bb04843dfa68d738918 (diff)
downloadfrost-d90cb9b61cd2e033b46f4780ad1340c5f35b7751.tar.gz
frost-d90cb9b61cd2e033b46f4780ad1340c5f35b7751.tar.bz2
frost-d90cb9b61cd2e033b46f4780ad1340c5f35b7751.zip
Add image viewing and downloading (#63)v1.3
* Commence aggressive image caching * Add glide toggle and css url parsing * Add image hook and refractor activities * Update version analytics * Implemented imageactivity but glide will not load * Create working image loader * Finalize image view * Finalize image view logic * Remove custom cache experiment
Diffstat (limited to 'app/src/main/kotlin/com/pitchedapps/frost/activities')
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/activities/AboutActivity.kt145
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/activities/BaseActivity.kt32
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/activities/FrostWebActivity.kt20
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt297
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt126
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt472
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/activities/SelectorActivity.kt47
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/activities/SettingsActivity.kt145
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/activities/WebOverlayActivity.kt158
9 files changed, 1442 insertions, 0 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
new file mode 100644
index 00000000..63ad8bae
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/AboutActivity.kt
@@ -0,0 +1,145 @@
+package com.pitchedapps.frost.activities
+
+import android.support.constraint.ConstraintLayout
+import android.support.constraint.ConstraintSet
+import android.support.v7.widget.RecyclerView
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import ca.allanwang.kau.about.AboutActivityBase
+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.utils.*
+import com.mikepenz.aboutlibraries.Libs
+import com.mikepenz.aboutlibraries.entity.Library
+import com.mikepenz.aboutlibraries.entity.License
+import com.mikepenz.community_material_typeface_library.CommunityMaterial
+import com.mikepenz.fastadapter.IItem
+import com.mikepenz.fastadapter.items.AbstractItem
+import com.mikepenz.google_material_typeface_library.GoogleMaterial
+import com.mikepenz.iconics.typeface.IIcon
+import com.pitchedapps.frost.BuildConfig
+import com.pitchedapps.frost.R
+import com.pitchedapps.frost.utils.Prefs
+
+
+/**
+ * Created by Allan Wang on 2017-06-26.
+ */
+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
+ cutoutDrawableRes = R.drawable.frost_f_256
+}) {
+
+ override fun getLibraries(libs: Libs): List<Library> {
+ val include = arrayOf(
+ "AboutLibraries",
+ "AndroidIconics",
+ "dbflow",
+ "fastadapter",
+ "glide",
+ "Jsoup",
+ "kau",
+ "kotterknife",
+ "materialdialogs",
+ "materialdrawer"
+ )
+
+ /*
+ * These are great libraries, but either aren't used directly or are too common to be listed
+ * Give more emphasis on the unique libs!
+ */
+ val exclude = arrayOf(
+ "GoogleMaterialDesignIcons",
+ "intellijannotations",
+ "MaterialDesignIconicIcons",
+ "MaterialDesignIcons",
+ "materialize",
+ "appcompat_v7",
+ "design",
+ "recyclerview_v7",
+ "support_v4"
+ )
+ val l = libs.prepareLibraries(this, include, null, false, true)
+// l.forEach { KL.d("Lib ${it.definedName}") }
+ return l
+ }
+
+ override fun postInflateMainPage(adapter: FastItemThemedAdapter<IItem<*, *>>) {
+ /**
+ * Frost may not be a library but we're conveying the same info
+ */
+ val frost = Library().apply {
+ libraryName = string(R.string.app_name)
+ author = "Pitched Apps"
+ libraryWebsite = "https://github.com/AllanWang/Frost-for-Facebook"
+ isOpenSource = true
+ libraryDescription = string(R.string.frost_description)
+ libraryVersion = BuildConfig.VERSION_NAME
+ license = License().apply {
+ licenseName = "GNU GPL v3"
+ licenseWebsite = "https://www.gnu.org/licenses/gpl-3.0.en.html"
+ }
+ }
+ adapter.add(LibraryIItem(frost)).add(AboutLinks())
+
+ }
+
+ class AboutLinks : AbstractItem<AboutLinks, AboutLinks.ViewHolder>(), ThemableIItem by ThemableIItemDelegate() {
+ override fun getViewHolder(v: View): ViewHolder = ViewHolder(v)
+
+ override fun getType(): Int = R.id.item_about_links
+
+ override fun getLayoutRes(): Int = R.layout.item_about_links
+
+ override fun bindView(holder: ViewHolder, payloads: MutableList<Any>?) {
+ super.bindView(holder, payloads)
+ with(holder) {
+ bindIconColor(*images.toTypedArray())
+ bindBackgroundColor(container)
+ }
+ }
+
+ class ViewHolder(v: View) : RecyclerView.ViewHolder(v) {
+
+ val container: ConstraintLayout by bindView(R.id.about_icons_container)
+ val images: List<ImageView>
+
+ /**
+ * There are a lot of constraints to be added to each item just to have them chained properly
+ * My as well do it programmatically
+ * Initializing the viewholder will setup the icons, scale type and background of all icons,
+ * link their click listeners and chain them together via a horizontal spread
+ */
+ init {
+ val c = itemView.context
+ val size = c.dimenPixelSize(R.dimen.kau_avatar_bounds)
+ images = arrayOf<Pair<IIcon, () -> Unit>>(
+ GoogleMaterial.Icon.gmd_star to { c.startPlayStoreLink(R.string.play_store_package_id) },
+ CommunityMaterial.Icon.cmd_reddit to { c.startLink("https://www.reddit.com/r/FrostForFacebook/") },
+ CommunityMaterial.Icon.cmd_github_circle to { c.startLink("https://github.com/AllanWang/Frost-for-Facebook") }
+ ).mapIndexed { i, (icon, onClick) ->
+ ImageView(c).apply {
+ layoutParams = ViewGroup.LayoutParams(size, size)
+ id = 109389 + i
+ setImageDrawable(icon.toDrawable(context, 32))
+ scaleType = ImageView.ScaleType.CENTER
+ background = context.resolveDrawable(android.R.attr.selectableItemBackgroundBorderless)
+ setOnClickListener({ onClick() })
+ container.addView(this)
+ }
+ }
+ val set = ConstraintSet()
+ set.clone(container)
+ set.createHorizontalChain(ConstraintSet.PARENT_ID, ConstraintSet.LEFT, ConstraintSet.PARENT_ID, ConstraintSet.RIGHT,
+ images.map { it.id }.toIntArray(), null, ConstraintSet.CHAIN_SPREAD_INSIDE)
+ set.applyTo(container)
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseActivity.kt
new file mode 100644
index 00000000..fd020af1
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseActivity.kt
@@ -0,0 +1,32 @@
+package com.pitchedapps.frost.activities
+
+import android.os.Bundle
+import android.support.v7.app.AppCompatActivity
+import com.pitchedapps.frost.R
+import com.pitchedapps.frost.utils.Prefs
+import com.pitchedapps.frost.utils.materialDialogThemed
+import com.pitchedapps.frost.utils.setFrostTheme
+
+/**
+ * Created by Allan Wang on 2017-06-12.
+ */
+open class BaseActivity : AppCompatActivity() {
+ override fun onBackPressed() {
+ if (isTaskRoot && Prefs.exitConfirmation) {
+ materialDialogThemed {
+ title(R.string.kau_exit)
+ content(R.string.kau_exit_confirmation)
+ positiveText(R.string.kau_yes)
+ negativeText(R.string.kau_no)
+ onPositive { _, _ -> super.onBackPressed() }
+ checkBoxPromptRes(R.string.kau_do_not_show_again, false, { _, b -> Prefs.exitConfirmation = !b })
+ }
+ } else super.onBackPressed()
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setFrostTheme()
+ }
+
+} \ No newline at end of file
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/FrostWebActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/FrostWebActivity.kt
new file mode 100644
index 00000000..1773471f
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/FrostWebActivity.kt
@@ -0,0 +1,20 @@
+package com.pitchedapps.frost.activities
+
+import android.os.Bundle
+import com.pitchedapps.frost.utils.Prefs
+
+
+/**
+ * Created by Allan Wang on 2017-06-19.
+ *
+ * Replica of [WebOverlayActivity] with a different base url
+ * Didn't use activity-alias because it causes issues when only one activity has the singleInstance mode
+ */
+class FrostWebActivity : WebOverlayActivity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ Prefs.prevId = Prefs.userId
+ super.onCreate(savedInstanceState)
+ }
+
+} \ No newline at end of file
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt
new file mode 100644
index 00000000..509ac2cb
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt
@@ -0,0 +1,297 @@
+package com.pitchedapps.frost.activities
+
+import android.animation.ValueAnimator
+import android.annotation.SuppressLint
+import android.content.Intent
+import android.content.res.ColorStateList
+import android.graphics.Bitmap
+import android.graphics.Color
+import android.graphics.drawable.Drawable
+import android.net.Uri
+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.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.request.target.BaseTarget
+import com.bumptech.glide.request.target.SizeReadyCallback
+import com.bumptech.glide.request.target.Target
+import com.bumptech.glide.request.transition.Transition
+import com.davemorrissey.labs.subscaleview.ImageSource
+import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
+import com.mikepenz.google_material_typeface_library.GoogleMaterial
+import com.mikepenz.iconics.typeface.IIcon
+import com.pitchedapps.frost.BuildConfig
+import com.pitchedapps.frost.R
+import com.pitchedapps.frost.utils.*
+import com.sothree.slidinguppanel.SlidingUpPanelLayout
+import org.jetbrains.anko.doAsync
+import org.jetbrains.anko.uiThread
+import timber.log.Timber
+import java.io.File
+import java.io.IOException
+import java.text.SimpleDateFormat
+import java.util.*
+
+/**
+ * Created by Allan Wang on 2017-07-15.
+ */
+class ImageActivity : AppCompatActivity() {
+
+ val progress: ProgressBar by bindView(R.id.image_progress)
+ val container: ViewGroup by bindView(R.id.image_container)
+ val panel: SlidingUpPanelLayout? by bindOptionalView(R.id.image_panel)
+ 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)
+
+ /**
+ * Reference to the temporary file path
+ * Should be nonnull if the image is successfully loaded
+ * As this is temporary, the image is deleted upon exit
+ */
+ internal var tempFilePath: String? = null
+ /**
+ * Reference to path for downloaded image
+ * Nonnull once the image is downloaded by the user
+ */
+ internal var downloadPath: String? = null
+ /**
+ * Indicator for fab's click result
+ */
+ internal var fabAction: FabStates = FabStates.NOTHING
+ set(value) {
+ if (field == value) return
+ field = value
+ value.update(fab)
+ }
+
+ val imageUrl: String
+ get() = intent.extras.getString(ARG_IMAGE_URL)
+
+ val text: String?
+ get() = intent.extras.getString(ARG_TEXT)
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(if (!text.isNullOrBlank()) R.layout.activity_image else R.layout.activity_image_textless)
+ container.setBackgroundColor(Prefs.bgColor.withMinAlpha(222))
+ caption?.setTextColor(Prefs.textColor)
+ caption?.setBackgroundColor(Prefs.bgColor.colorToForeground(0.1f).withAlpha(255))
+ 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) {
+ L.e(e, "Image load error")
+ imageCallback(null, false)
+ }
+ })
+ GlideApp.with(this).asBitmap().load(imageUrl).into(PhotoTarget(this::imageCallback))
+ }
+
+ /**
+ * Callback to add image to view
+ * [resource] is guaranteed to be nonnull when [success] is true
+ * and null when it is false
+ */
+ private fun imageCallback(resource: Bitmap?, success: Boolean) {
+ if (progress.isVisible) progress.fadeOut()
+ if (success) {
+ saveTempImage(resource!!, {
+ if (it == null) {
+ imageCallback(null, false)
+ } else {
+ photo.setImage(ImageSource.uri(it))
+ fabAction = FabStates.DOWNLOAD
+ photo.animate().alpha(1f).scaleX(1f).scaleY(1f).withEndAction { fab.show() }.start()
+ }
+ })
+ } else {
+ fabAction = FabStates.ERROR
+ fab.show()
+ }
+ }
+
+ /**
+ * Bitmap load handler
+ */
+ class PhotoTarget(val callback: (resource: Bitmap?, success: Boolean) -> Unit) : BaseTarget<Bitmap>() {
+
+ override fun removeCallback(cb: SizeReadyCallback?) {}
+
+ override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) = callback(resource, true)
+
+ override fun onLoadFailed(errorDrawable: Drawable?) = callback(null, false)
+
+ override fun getSize(cb: SizeReadyCallback) = cb.onSizeReady(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)
+
+ }
+
+ private fun saveTempImage(resource: Bitmap, callback: (uri: Uri?) -> Unit) {
+ var photoFile: File? = null
+ try {
+ photoFile = createImageFile()
+ } catch (ignored: IOException) {
+ } finally {
+ if (photoFile == null) {
+ callback(null)
+ } else {
+ tempFilePath = photoFile.absolutePath
+ Timber.d("Temp image path $tempFilePath")
+ // File created; proceed with request
+ val photoURI = FileProvider.getUriForFile(this,
+ BuildConfig.APPLICATION_ID + ".provider",
+ photoFile)
+ photoFile.outputStream().use { resource.compress(Bitmap.CompressFormat.PNG, 100, it) }
+ callback(photoURI)
+ }
+ }
+ }
+
+ @Suppress("SIMPLE_DATE_FORMAT")
+ @Throws(IOException::class)
+ private fun createImageFile(): File {
+ // Create an image file name
+ @SuppressLint("SimpleDateFormat")
+ val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())
+ val imageFileName = "Frost_" + timeStamp + "_"
+ val storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES)
+ return File.createTempFile(
+ imageFileName, /* prefix */
+ ".png", /* suffix */
+ storageDir /* directory */
+ )
+ }
+
+ internal fun downloadImage() {
+ kauRequestPermissions(PERMISSION_WRITE_EXTERNAL_STORAGE) {
+ 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)
+ downloadPath = destination.absolutePath
+ var success = true
+ try {
+ File(tempFilePath).copyTo(destination, true)
+ } catch (e: Exception) {
+ success = false
+ } finally {
+ uiThread {
+ snackbar(if (success) R.string.image_download_success else R.string.image_download_fail)
+ if (success) {
+ deleteTempFile()
+ fabAction = FabStates.SHARE
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ internal fun deleteTempFile() {
+ if (tempFilePath != null) {
+ File(tempFilePath!!).delete()
+ tempFilePath = null
+ }
+ }
+
+ override fun onDestroy() {
+ 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.textColor, val backgroundTint: Int = Prefs.accentBackgroundColor.withAlpha(255)) {
+ ERROR(GoogleMaterial.Icon.gmd_error, Color.WHITE, Color.RED) {
+ override fun onClick(activity: ImageActivity) {
+ //todo add something
+ }
+ },
+ NOTHING(GoogleMaterial.Icon.gmd_adjust) {
+ override fun onClick(activity: ImageActivity) {}
+ },
+ DOWNLOAD(GoogleMaterial.Icon.gmd_file_download) {
+ override fun onClick(activity: ImageActivity) {
+ activity.downloadImage()
+ }
+ },
+ SHARE(GoogleMaterial.Icon.gmd_share) {
+ override fun onClick(activity: ImageActivity) {
+ try {
+ val photoURI = FileProvider.getUriForFile(activity,
+ BuildConfig.APPLICATION_ID + ".provider",
+ File(activity.downloadPath))
+ val intent = Intent(Intent.ACTION_SEND).apply {
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK
+ putExtra(Intent.EXTRA_STREAM, photoURI)
+ type = "image/png"
+ }
+ activity.startActivity(intent)
+ } catch (e: Exception) {
+ L.e(e, "Image share failed");
+ activity.snackbar(R.string.image_share_failed)
+ }
+ }
+ };
+
+ /**
+ * Change the fab look
+ * If it's in view, give it some animations
+ */
+ fun update(fab: FloatingActionButton) {
+ if (!fab.isShown) {
+ fab.setIcon(iicon, color = iconColor)
+ fab.backgroundTintList = ColorStateList.valueOf(backgroundTint)
+ } else {
+ var switched = false
+ ValueAnimator.ofFloat(1.0f, 0.0f, 1.0f).apply {
+ duration = 500L
+ addUpdateListener {
+ val x = it.animatedValue as Float
+ val scale = x * 0.3f + 0.7f
+ fab.scaleX = scale
+ fab.scaleY = scale
+ fab.imageAlpha = (x * 255).toInt()
+ if (it.animatedFraction > 0.5f && !switched) {
+ switched = true
+ fab.setIcon(iicon, color = iconColor)
+ fab.backgroundTintList = ColorStateList.valueOf(backgroundTint)
+ }
+ }
+ start()
+ }
+ }
+ }
+
+ abstract fun onClick(activity: ImageActivity)
+
+} \ No newline at end of file
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt
new file mode 100644
index 00000000..e4897be5
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt
@@ -0,0 +1,126 @@
+package com.pitchedapps.frost.activities
+
+import android.graphics.drawable.Drawable
+import android.os.Bundle
+import android.os.Handler
+import android.support.v4.widget.SwipeRefreshLayout
+import android.support.v7.widget.AppCompatTextView
+import android.support.v7.widget.Toolbar
+import android.widget.ImageView
+import ca.allanwang.kau.utils.bindView
+import ca.allanwang.kau.utils.fadeIn
+import ca.allanwang.kau.utils.fadeOut
+import com.bumptech.glide.Glide
+import com.bumptech.glide.load.DataSource
+import com.bumptech.glide.load.engine.GlideException
+import com.bumptech.glide.request.RequestListener
+import com.bumptech.glide.request.target.Target
+import com.crashlytics.android.answers.LoginEvent
+import com.pitchedapps.frost.R
+import com.pitchedapps.frost.dbflow.CookieModel
+import com.pitchedapps.frost.dbflow.fetchUsername
+import com.pitchedapps.frost.dbflow.loadFbCookiesAsync
+import com.pitchedapps.frost.facebook.PROFILE_PICTURE_URL
+import com.pitchedapps.frost.utils.*
+import com.pitchedapps.frost.web.LoginWebView
+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
+
+
+/**
+ * Created by Allan Wang on 2017-06-01.
+ */
+class LoginActivity : BaseActivity() {
+
+ val toolbar: Toolbar by bindView(R.id.toolbar)
+ val web: LoginWebView by bindView(R.id.login_webview)
+ val swipeRefresh: SwipeRefreshLayout by bindView(R.id.swipe_refresh)
+ 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>()
+
+ // Helper to set and enable swipeRefresh
+ var refresh: Boolean
+ get() = swipeRefresh.isRefreshing
+ set(value) {
+ if (value) swipeRefresh.isEnabled = true
+ swipeRefresh.isRefreshing = value
+ if (!value) swipeRefresh.isEnabled = false
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_login)
+ setSupportActionBar(toolbar)
+ setTitle(R.string.kau_login)
+ setFrostColors(toolbar)
+ web.loginObservable = loginObservable
+ web.progressObservable = progressObservable
+ loginObservable.observeOn(AndroidSchedulers.mainThread()).subscribe {
+ cookie ->
+ web.fadeOut(onFinish = {
+ profile.fadeIn()
+ loadInfo(cookie)
+ })
+ }
+ progressObservable.observeOn(AndroidSchedulers.mainThread()).subscribe { refresh = it != 100 }
+ web.loadLogin()
+ }
+
+ fun loadInfo(cookie: CookieModel) {
+ refresh = true
+ Observable.zip(SingleToObservable(profileObservable), SingleToObservable(usernameObservable),
+ BiFunction<Boolean, String, Pair<Boolean, String>> { foundImage, name -> Pair(foundImage, name) })
+ .observeOn(AndroidSchedulers.mainThread()).subscribe {
+ (foundImage, name) ->
+ refresh = false
+ if (!foundImage) {
+ L.eThrow("Could not get profile photo; Invalid userId?")
+ L.i("-\t$cookie")
+ }
+ textview.text = String.format(getString(R.string.welcome), name)
+ textview.fadeIn()
+ frostAnswers { logLogin(LoginEvent().putMethod("frost_browser").putSuccess(true)) }
+ /*
+ * The user may have logged into an account that is already in the database
+ * We will let the db handle duplicates and load it now after the new account has been saved
+ */
+ loadFbCookiesAsync {
+ cookies ->
+ Handler().postDelayed({
+ launchNewTask(MainActivity::class.java, ArrayList(cookies), clearStack = true)
+ }, 1000)
+ }
+ }
+ loadProfile(cookie.id)
+ loadUsername(cookie)
+ }
+
+
+ fun loadProfile(id: Long) {
+ Glide.with(this@LoginActivity).load(PROFILE_PICTURE_URL(id)).withRoundIcon().listener(object : RequestListener<Drawable> {
+ override fun onResourceReady(resource: Drawable?, model: Any?, target: Target<Drawable>?, dataSource: DataSource?, isFirstResource: Boolean): Boolean {
+ profileObservable.onSuccess(true)
+ return false
+ }
+
+ override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Drawable>?, isFirstResource: Boolean): Boolean {
+ if (e != null) L.e(e, "Profile loading exception")
+ profileObservable.onSuccess(false)
+ return false
+ }
+ }).into(profile)
+ }
+
+ fun loadUsername(cookie: CookieModel) {
+ cookie.fetchUsername { usernameObservable.onSuccess(it) }
+ }
+} \ 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
new file mode 100644
index 00000000..ba76e594
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt
@@ -0,0 +1,472 @@
+package com.pitchedapps.frost.activities
+
+import android.app.AlarmManager
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.graphics.drawable.ColorDrawable
+import android.net.Uri
+import android.os.Build
+import android.os.Bundle
+import android.support.annotation.StringRes
+import android.support.design.widget.*
+import android.support.v4.app.ActivityOptionsCompat
+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.widget.Toolbar
+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 co.zsmb.materialdrawerkt.builders.Builder
+import co.zsmb.materialdrawerkt.builders.accountHeader
+import co.zsmb.materialdrawerkt.builders.drawer
+import co.zsmb.materialdrawerkt.draweritems.badgeable.primaryItem
+import co.zsmb.materialdrawerkt.draweritems.badgeable.secondaryItem
+import co.zsmb.materialdrawerkt.draweritems.divider
+import co.zsmb.materialdrawerkt.draweritems.profile.profile
+import co.zsmb.materialdrawerkt.draweritems.profile.profileSetting
+import com.crashlytics.android.answers.ContentViewEvent
+import com.mikepenz.google_material_typeface_library.GoogleMaterial
+import com.mikepenz.iconics.IconicsDrawable
+import com.mikepenz.materialdrawer.AccountHeader
+import com.mikepenz.materialdrawer.Drawer
+import com.pitchedapps.frost.BuildConfig
+import com.pitchedapps.frost.R
+import com.pitchedapps.frost.contracts.ActivityWebContract
+import com.pitchedapps.frost.contracts.FileChooserContract
+import com.pitchedapps.frost.contracts.FileChooserDelegate
+import com.pitchedapps.frost.dbflow.loadFbCookie
+import com.pitchedapps.frost.dbflow.loadFbTabs
+import com.pitchedapps.frost.facebook.FbCookie
+import com.pitchedapps.frost.facebook.FbCookie.switchUser
+import com.pitchedapps.frost.facebook.FbTab
+import com.pitchedapps.frost.facebook.PROFILE_PICTURE_URL
+import com.pitchedapps.frost.fragments.WebFragment
+import com.pitchedapps.frost.utils.*
+import com.pitchedapps.frost.utils.iab.validatePro
+import com.pitchedapps.frost.views.BadgedIcon
+import com.pitchedapps.frost.views.FrostViewPager
+import com.pitchedapps.frost.web.FrostWebViewSearch
+import io.reactivex.android.schedulers.AndroidSchedulers
+import io.reactivex.disposables.Disposable
+import io.reactivex.schedulers.Schedulers
+import io.reactivex.subjects.PublishSubject
+import org.jsoup.Jsoup
+import java.util.concurrent.TimeUnit
+
+class MainActivity : BaseActivity(), FrostWebViewSearch.SearchContract,
+ ActivityWebContract, FileChooserContract by FileChooserDelegate() {
+
+ lateinit var adapter: SectionsPagerAdapter
+ val toolbar: Toolbar by bindView(R.id.toolbar)
+ val viewPager: FrostViewPager by bindView(R.id.container)
+ val fab: FloatingActionButton by bindView(R.id.fab)
+ val tabs: TabLayout by bindView(R.id.tabs)
+ val appBar: AppBarLayout by bindView(R.id.appbar)
+ val coordinator: CoordinatorLayout by bindView(R.id.main_content)
+ lateinit var drawer: Drawer
+ lateinit var drawerHeader: AccountHeader
+ var webFragmentObservable = PublishSubject.create<Int>()!!
+ var lastPosition = -1
+ val headerBadgeObservable = PublishSubject.create<String>()
+ var hiddenSearchView: FrostWebViewSearch? = null
+ var firstLoadFinished = false
+ set(value) {
+ L.d("First fragment load has finished")
+ field = value
+ if (value && hiddenSearchView == null) {
+ hiddenSearchView = FrostWebViewSearch(this, this)
+ }
+ }
+ var searchView: SearchView? = null
+ override val isSearchOpened: Boolean
+ get() = searchView?.isOpen ?: false
+
+ companion object {
+ const val ACTIVITY_SETTINGS = 97
+ /*
+ * Possible responses from the SettingsActivity
+ * after the configurations have changed
+ */
+ const val REQUEST_RESTART = 90909
+ const val REQUEST_REFRESH = 80808
+ const val REQUEST_WEB_ZOOM = 50505
+ const val REQUEST_NAV = 10101
+ const val REQUEST_SEARCH = 70707
+ const val REQUEST_RESTART_APPLICATION = 60606
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ if (BuildConfig.VERSION_CODE > Prefs.versionCode) {
+ Prefs.versionCode = BuildConfig.VERSION_CODE
+ if (!BuildConfig.DEBUG) {
+ showChangelog(R.xml.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)
+ }
+ }
+ }
+ setContentView(R.layout.activity_main)
+ setSupportActionBar(toolbar)
+ adapter = SectionsPagerAdapter(supportFragmentManager, loadFbTabs())
+ viewPager.adapter = adapter
+ viewPager.offscreenPageLimit = 5
+ viewPager.addOnPageChangeListener(object : ViewPager.SimpleOnPageChangeListener() {
+ override fun onPageSelected(position: Int) {
+ super.onPageSelected(position)
+ if (lastPosition == position) return
+ if (lastPosition != -1) webFragmentObservable.onNext(-(lastPosition + 1))
+ webFragmentObservable.onNext(position)
+ lastPosition = position
+ }
+
+ override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
+ super.onPageScrolled(position, positionOffset, positionOffsetPixels)
+ val delta: Float by lazy { positionOffset * (255 - 128).toFloat() }
+ tabsForEachView {
+ tabPosition, view ->
+ view.setAllAlpha(when (tabPosition) {
+ position -> 255.0f - delta
+ position + 1 -> 128.0f + delta
+ else -> 128f
+ })
+ }
+ }
+ })
+ viewPager.post { webFragmentObservable.onNext(0); lastPosition = 0 } //trigger hook so title is set
+ setupDrawer(savedInstanceState)
+ setupTabs()
+ fab.setOnClickListener { view ->
+ Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
+ .setAction("Action", null).show()
+ }
+ setFrostColors(toolbar, themeWindow = false, headers = arrayOf(tabs, appBar), backgrounds = arrayOf(viewPager))
+ validatePro()
+ }
+
+ fun tabsForEachView(action: (position: Int, view: BadgedIcon) -> Unit) {
+ (0 until tabs.tabCount).asSequence().forEach {
+ i ->
+ action(i, tabs.getTabAt(i)!!.customView as BadgedIcon)
+ }
+ }
+
+ fun setupTabs() {
+ viewPager.addOnPageChangeListener(TabLayout.TabLayoutOnPageChangeListener(tabs))
+ tabs.addOnTabSelectedListener(object : TabLayout.ViewPagerOnTabSelectedListener(viewPager) {
+ override fun onTabReselected(tab: TabLayout.Tab) {
+ super.onTabReselected(tab)
+ currentFragment.web.scrollOrRefresh()
+ }
+
+ override fun onTabSelected(tab: TabLayout.Tab) {
+ super.onTabSelected(tab)
+ (tab.customView as BadgedIcon).badgeText = null
+ }
+ })
+ headerBadgeObservable.throttleFirst(15, TimeUnit.SECONDS).subscribeOn(Schedulers.newThread())
+ .map { Jsoup.parse(it) }
+ .filter { it.select("[data-sigil=count]").size >= 0 } //ensure headers exist
+ .map {
+ val feed = it.select("[data-sigil*=feed] [data-sigil=count]")
+ val requests = it.select("[data-sigil*=requests] [data-sigil=count]")
+ val messages = it.select("[data-sigil*=messages] [data-sigil=count]")
+ val notifications = it.select("[data-sigil*=notifications] [data-sigil=count]")
+ return@map arrayOf(feed, requests, messages, notifications).map { it?.getOrNull(0)?.ownText() }
+ }
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe {
+ (feed, requests, messages, notifications) ->
+ tabsForEachView {
+ _, view ->
+ when (view.iicon) {
+ FbTab.FEED.icon -> view.badgeText = feed
+ FbTab.FRIENDS.icon -> view.badgeText = requests
+ FbTab.MESSAGES.icon -> view.badgeText = messages
+ FbTab.NOTIFICATIONS.icon -> view.badgeText = notifications
+ }
+ }
+ }
+ adapter.pages.forEach {
+ tabs.addTab(tabs.newTab()
+ .setCustomView(BadgedIcon(this).apply {
+ iicon = it.icon
+ }))
+ }
+ }
+
+ fun setupDrawer(savedInstanceState: Bundle?) {
+ val navBg = Prefs.bgColor.withMinAlpha(200).toLong()
+ val navHeader = Prefs.headerColor.withMinAlpha(200)
+ drawer = drawer {
+ toolbar = this@MainActivity.toolbar
+ savedInstance = savedInstanceState
+ translucentStatusBar = false
+ sliderBackgroundColor = navBg
+ drawerHeader = accountHeader {
+ textColor = Prefs.iconColor.toLong()
+ backgroundDrawable = ColorDrawable(navHeader)
+ selectionSecondLineShown = false
+ paddingBelow = false
+ cookies().forEach { (id, name) ->
+ profile(name = name ?: "") {
+ iconUrl = PROFILE_PICTURE_URL(id)
+ textColor = Prefs.textColor.toLong()
+ selectedTextColor = Prefs.textColor.toLong()
+ selectedColor = 0x00000001.toLong()
+ identifier = id
+ }
+ }
+ profileSetting(nameRes = R.string.kau_logout) {
+ iicon = GoogleMaterial.Icon.gmd_exit_to_app
+ iconColor = Prefs.textColor.toLong()
+ textColor = Prefs.textColor.toLong()
+ identifier = -2L
+ }
+ profileSetting(nameRes = R.string.kau_add_account) {
+ iconDrawable = IconicsDrawable(this@MainActivity, GoogleMaterial.Icon.gmd_add).actionBar().paddingDp(5).color(Prefs.textColor)
+ textColor = Prefs.textColor.toLong()
+ identifier = -3L
+ }
+ profileSetting(nameRes = R.string.kau_manage_account) {
+ iicon = GoogleMaterial.Icon.gmd_settings
+ iconColor = Prefs.textColor.toLong()
+ textColor = Prefs.textColor.toLong()
+ identifier = -4L
+ }
+ onProfileChanged { _, profile, current ->
+ if (current) launchWebOverlay(FbTab.PROFILE.url)
+ else when (profile.identifier) {
+ -2L -> {
+ val currentCookie = loadFbCookie(Prefs.userId)
+ if (currentCookie == null) {
+ toast(R.string.account_not_found)
+ FbCookie.reset { launchLogin(cookies(), true) }
+ } else {
+ materialDialogThemed {
+ title(R.string.kau_logout)
+ content(String.format(string(R.string.kau_logout_confirm_as_x), currentCookie.name ?: Prefs.userId.toString()))
+ positiveText(R.string.kau_yes)
+ negativeText(R.string.kau_no)
+ onPositive { _, _ ->
+ FbCookie.logout(Prefs.userId) {
+ val allCookies = cookies()
+ allCookies.remove(currentCookie)
+ launchLogin(allCookies, true)
+ }
+ }
+ }
+ }
+ }
+ -3L -> launchNewTask(LoginActivity::class.java, clearStack = false)
+ -4L -> launchNewTask(SelectorActivity::class.java, cookies(), false)
+ else -> {
+ switchUser(profile.identifier, { refreshAll() })
+ tabsForEachView { _, view -> view.badgeText = null }
+ }
+ }
+ false
+ }
+ }
+ drawerHeader.setActiveProfile(Prefs.userId)
+ primaryFrostItem(FbTab.FEED_MOST_RECENT)
+ primaryFrostItem(FbTab.FEED_TOP_STORIES)
+ primaryFrostItem(FbTab.ACTIVITY_LOG)
+ divider()
+ primaryFrostItem(FbTab.PHOTOS)
+ primaryFrostItem(FbTab.GROUPS)
+ primaryFrostItem(FbTab.PAGES)
+ divider()
+ primaryFrostItem(FbTab.EVENTS)
+ primaryFrostItem(FbTab.BIRTHDAYS)
+ primaryFrostItem(FbTab.ON_THIS_DAY)
+ divider()
+ primaryFrostItem(FbTab.NOTES)
+ primaryFrostItem(FbTab.SAVED)
+ }
+ }
+
+ fun Builder.primaryFrostItem(item: FbTab) = this.primaryItem(item.titleId) {
+ iicon = item.icon
+ iconColor = Prefs.textColor.toLong()
+ textColor = Prefs.textColor.toLong()
+ selectedIconColor = Prefs.textColor.toLong()
+ selectedTextColor = Prefs.textColor.toLong()
+ selectedColor = 0x00000001.toLong()
+ identifier = item.titleId.toLong()
+ onClick { _ ->
+ frostAnswers {
+ logContentView(ContentViewEvent()
+ .putContentName(item.name)
+ .putContentType("drawer_item"))
+ }
+ launchWebOverlay(item.url)
+ false
+ }
+ }
+
+ fun Builder.secondaryFrostItem(@StringRes title: Int, onClick: () -> Unit) = this.secondaryItem(title) {
+ textColor = Prefs.textColor.toLong()
+ selectedIconColor = Prefs.textColor.toLong()
+ selectedTextColor = Prefs.textColor.toLong()
+ selectedColor = 0x00000001.toLong()
+ identifier = title.toLong()
+ onClick { _ -> onClick(); false }
+ }
+
+
+ /**
+ * Something happened where the normal search function won't work
+ * Fallback to overlay style
+ */
+ override fun searchOverlayDispose() {
+ hiddenSearchView?.dispose()
+ hiddenSearchView = null
+ searchView = null
+ //todo remove true searchview and add contract
+ }
+
+ override fun emitSearchResponse(items: List<SearchItem>) {
+ searchView?.results = items
+ }
+
+ fun refreshAll() {
+ webFragmentObservable.onNext(WebFragment.REQUEST_REFRESH)
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu): Boolean {
+ menuInflater.inflate(R.menu.menu_main, menu)
+ toolbar.tint(Prefs.iconColor)
+ setMenuIcons(menu, Prefs.iconColor,
+ R.id.action_settings to GoogleMaterial.Icon.gmd_settings,
+ R.id.action_search to GoogleMaterial.Icon.gmd_search)
+ if (Prefs.searchBar) {
+ if (firstLoadFinished && hiddenSearchView == null) hiddenSearchView = FrostWebViewSearch(this, this)
+ if (searchView == null) searchView = bindSearchView(menu, R.id.action_search, Prefs.iconColor) {
+ textObserver = {
+ observable, _ ->
+ observable.observeOn(AndroidSchedulers.mainThread()).subscribe { hiddenSearchView?.query(it) }
+ }
+ foregroundColor = Prefs.textColor
+ backgroundColor = Prefs.bgColor.withMinAlpha(200)
+ openListener = { hiddenSearchView?.pauseLoad = false }
+ closeListener = { hiddenSearchView?.pauseLoad = true }
+ onItemClick = { _, key, _, _ -> launchWebOverlay(key) }
+ }
+ } else {
+ searchOverlayDispose()
+ menu.findItem(R.id.action_search).setOnMenuItemClickListener { _ -> launchWebOverlay(FbTab.SEARCH.url); true }
+ }
+ return true
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ when (item.itemId) {
+ R.id.action_settings -> {
+ val intent = Intent(this, SettingsActivity::class.java)
+ val bundle = ActivityOptionsCompat.makeCustomAnimation(this, R.anim.kau_slide_in_right, R.anim.kau_fade_out).toBundle()
+ startActivityForResult(intent, ACTIVITY_SETTINGS, bundle)
+ }
+ else -> return super.onOptionsItemSelected(item)
+ }
+ return true
+ }
+
+ override fun openFileChooser(filePathCallback: ValueCallback<Array<Uri>>, fileChooserParams: WebChromeClient.FileChooserParams) {
+ openFileChooser(this, filePathCallback, fileChooserParams)
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ if (onActivityResultWeb(requestCode, resultCode, data)) return
+ super.onActivityResult(requestCode, resultCode, data)
+ if (requestCode == ACTIVITY_SETTINGS) {
+ when (resultCode) {
+ REQUEST_RESTART -> restart()
+ REQUEST_REFRESH -> webFragmentObservable.onNext(WebFragment.REQUEST_REFRESH)
+ REQUEST_NAV -> frostNavigationBar()
+ REQUEST_WEB_ZOOM -> webFragmentObservable.onNext(WebFragment.REQUEST_TEXT_ZOOM)
+ REQUEST_SEARCH -> invalidateOptionsMenu()
+ REQUEST_RESTART_APPLICATION -> { //completely restart application
+ L.d("Restart Application Requested")
+ val intent = packageManager.getLaunchIntentForPackage(packageName)
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)
+ val pending = PendingIntent.getActivity(this, 666, intent, PendingIntent.FLAG_CANCEL_CURRENT)
+ val alarm = getSystemService(Context.ALARM_SERVICE) as AlarmManager
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
+ alarm.setExactAndAllowWhileIdle(AlarmManager.RTC, System.currentTimeMillis() + 100, pending)
+ else
+ alarm.setExact(AlarmManager.RTC, System.currentTimeMillis() + 100, pending)
+ finish()
+ System.exit(0)
+ }
+ }
+ }
+ }
+
+ 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 { }
+ }
+
+ override fun onStart() {
+ //validate some pro features
+ if (!Prefs.pro) {
+ if (Prefs.theme == Theme.CUSTOM.ordinal) Prefs.theme = Theme.DEFAULT.ordinal
+ }
+ super.onStart()
+ }
+
+ override fun onBackPressed() {
+ if (searchView?.onBackPressed() ?: false) return
+ if (currentFragment.onBackPressed()) return
+ super.onBackPressed()
+ }
+
+ val currentFragment
+ get() = supportFragmentManager.findFragmentByTag("android:switcher:${R.id.container}:${viewPager.currentItem}") as WebFragment
+
+ inner class SectionsPagerAdapter(fm: FragmentManager, val pages: List<FbTab>) : FragmentPagerAdapter(fm) {
+
+ override fun getItem(position: Int): Fragment {
+ val fragment = WebFragment(pages[position], position)
+ //If first load hasn't occurred, add a listener
+ if (!firstLoadFinished) {
+ var disposable: Disposable? = null
+ fragment.post {
+ disposable = it.web.refreshObservable.subscribe {
+ if (!it) {
+ //Ensure first load finisher only happens once
+ if (!firstLoadFinished) firstLoadFinished = true
+ disposable?.dispose()
+ disposable = null
+ }
+ }
+ }
+ }
+ return fragment
+ }
+
+ override fun getCount() = pages.size
+
+ override fun getPageTitle(position: Int): CharSequence = getString(pages[position].titleId)
+ }
+
+}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/SelectorActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/SelectorActivity.kt
new file mode 100644
index 00000000..ff87f448
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/SelectorActivity.kt
@@ -0,0 +1,47 @@
+package com.pitchedapps.frost.activities
+
+import android.os.Bundle
+import android.support.constraint.ConstraintLayout
+import android.support.v7.widget.AppCompatTextView
+import android.support.v7.widget.GridLayoutManager
+import android.support.v7.widget.RecyclerView
+import android.view.View
+import ca.allanwang.kau.utils.bindView
+import com.mikepenz.fastadapter.FastAdapter
+import com.mikepenz.fastadapter.commons.adapters.FastItemAdapter
+import com.mikepenz.fastadapter.listeners.ClickEventHook
+import com.pitchedapps.frost.R
+import com.pitchedapps.frost.facebook.FbCookie
+import com.pitchedapps.frost.utils.cookies
+import com.pitchedapps.frost.utils.launchNewTask
+import com.pitchedapps.frost.utils.setFrostColors
+import com.pitchedapps.frost.views.AccountItem
+
+/**
+ * Created by Allan Wang on 2017-06-04.
+ */
+class SelectorActivity : BaseActivity() {
+
+ val recycler: RecyclerView by bindView(R.id.selector_recycler)
+ val adapter = FastItemAdapter<AccountItem>()
+ val text: AppCompatTextView by bindView(R.id.text_select_account)
+ val container: ConstraintLayout by bindView(R.id.container)
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_selector)
+ recycler.layoutManager = GridLayoutManager(this, 2)
+ recycler.adapter = adapter
+ adapter.add(cookies().map { AccountItem(it) })
+ adapter.add(AccountItem(null)) // add account
+ adapter.withEventHook(object : ClickEventHook<AccountItem>() {
+ override fun onBind(viewHolder: RecyclerView.ViewHolder): View? = (viewHolder as? AccountItem.ViewHolder)?.v
+
+ override fun onClick(v: View, position: Int, fastAdapter: FastAdapter<AccountItem>, item: AccountItem) {
+ if (item.cookie == null) this@SelectorActivity.launchNewTask(LoginActivity::class.java)
+ else FbCookie.switchUser(item.cookie, { launchNewTask(MainActivity::class.java, cookies()) })
+ }
+ })
+ setFrostColors(texts = arrayOf(text), backgrounds = arrayOf(container))
+ }
+} \ 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
new file mode 100644
index 00000000..b3b3bd7c
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/SettingsActivity.kt
@@ -0,0 +1,145 @@
+package com.pitchedapps.frost.activities
+
+import android.content.Intent
+import android.os.Bundle
+import android.view.Menu
+import android.view.MenuItem
+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 com.mikepenz.community_material_typeface_library.CommunityMaterial
+import com.mikepenz.google_material_typeface_library.GoogleMaterial
+import com.pitchedapps.frost.BuildConfig
+import com.pitchedapps.frost.R
+import com.pitchedapps.frost.settings.*
+import com.pitchedapps.frost.utils.*
+import com.pitchedapps.frost.utils.iab.*
+
+
+/**
+ * Created by Allan Wang on 2017-06-06.
+ */
+class SettingsActivity : KPrefActivity(), IabBroadcastReceiver.IabBroadcastListener {
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ if (!IAB.handleActivityResult(requestCode, resultCode, data)) {
+ super.onActivityResult(requestCode, resultCode, data)
+ adapter.notifyDataSetChanged()
+ }
+ }
+
+
+ override fun receivedBroadcast() {
+ L.d("IAB broadcast")
+ adapter.notifyDataSetChanged()
+ }
+
+ override fun kPrefCoreAttributes(): CoreAttributeContract.() -> Unit = {
+ textColor = { Prefs.textColor }
+ accentColor = { Prefs.accentColor }
+ }
+
+ override fun onCreateKPrefs(savedInstanceState: android.os.Bundle?): KPrefAdapterBuilder.() -> Unit = {
+ subItems(R.string.appearance, getAppearancePrefs()) {
+ descRes = R.string.appearance_desc
+ iicon = GoogleMaterial.Icon.gmd_palette
+ }
+
+ subItems(R.string.behaviour, getBehaviourPrefs()) {
+ descRes = R.string.behaviour_desc
+ iicon = GoogleMaterial.Icon.gmd_trending_up
+ }
+
+ subItems(R.string.newsfeed, getFeedPrefs()) {
+ descRes = R.string.newsfeed_desc
+ iicon = CommunityMaterial.Icon.cmd_newspaper
+ }
+
+ subItems(R.string.notifications, getNotificationPrefs()) {
+ descRes = R.string.notifications_desc
+ iicon = GoogleMaterial.Icon.gmd_notifications
+ }
+
+ subItems(R.string.experimental, getExperimentalPrefs()) {
+ descRes = R.string.experimental_desc
+ iicon = CommunityMaterial.Icon.cmd_flask_outline
+ }
+
+ plainText(R.string.restore_purchases) {
+ descRes = R.string.restore_purchases_desc
+ iicon = GoogleMaterial.Icon.gmd_refresh
+ onClick = { _, _, _ -> this@SettingsActivity.restorePurchases(); true }
+ }
+
+ plainText(R.string.about_frost) {
+ iicon = GoogleMaterial.Icon.gmd_info
+ onClick = { _, _, _ -> startActivity(AboutActivity::class.java, transition = true); true }
+ }
+
+ if (BuildConfig.DEBUG) {
+ checkbox(R.string.custom_pro, { Prefs.debugPro }, { Prefs.debugPro = it })
+ }
+ }
+
+ fun KPrefItemBase.BaseContract<*>.dependsOnPro() {
+ onDisabledClick = { _, _, _ -> openPlayProPurchase(0); true }
+ enabler = { IS_FROST_PRO }
+ }
+
+ fun shouldRestartMain() {
+ setResult(MainActivity.REQUEST_RESTART)
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ setFrostTheme(true)
+ super.onCreate(savedInstanceState)
+ animate = Prefs.animate
+ themeExterior(false)
+ }
+
+ fun themeExterior(animate: Boolean = true) {
+ if (animate) bgCanvas.fade(Prefs.bgColor)
+ else bgCanvas.set(Prefs.bgColor)
+ if (animate) toolbarCanvas.ripple(Prefs.headerColor, RippleCanvas.MIDDLE, RippleCanvas.END)
+ else toolbarCanvas.set(Prefs.headerColor)
+ frostNavigationBar()
+ }
+
+ override fun onBackPressed() {
+ if (!super.backPress())
+ finishSlideOut()
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu): Boolean {
+ menuInflater.inflate(R.menu.menu_settings, menu)
+ toolbar.tint(Prefs.iconColor)
+ toolbarTitle.textColor = Prefs.iconColor
+ toolbarTitle.invalidate()
+ setMenuIcons(menu, Prefs.iconColor,
+ R.id.action_email to GoogleMaterial.Icon.gmd_email,
+ R.id.action_changelog to GoogleMaterial.Icon.gmd_info)
+ return true
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ when (item.itemId) {
+ R.id.action_email -> materialDialogThemed {
+ title(R.string.subject)
+ items(Support.values().map { string(it.title) })
+ itemsCallback { _, _, which, _ -> Support.values()[which].sendEmail(this@SettingsActivity) }
+ }
+ R.id.action_changelog -> showChangelog(R.xml.changelog, Prefs.textColor) { theme() }
+ else -> return super.onOptionsItemSelected(item)
+ }
+ return true
+ }
+
+ override fun onDestroy() {
+ IAB.dispose()
+ super.onDestroy()
+ }
+} \ 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
new file mode 100644
index 00000000..f03c653c
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/WebOverlayActivity.kt
@@ -0,0 +1,158 @@
+package com.pitchedapps.frost.activities
+
+import android.content.Intent
+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.swipe.kauSwipeOnCreate
+import ca.allanwang.kau.swipe.kauSwipeOnDestroy
+import ca.allanwang.kau.swipe.kauSwipeOnPostCreate
+import ca.allanwang.kau.utils.*
+import com.mikepenz.community_material_typeface_library.CommunityMaterial
+import com.mikepenz.google_material_typeface_library.GoogleMaterial
+import com.pitchedapps.frost.R
+import com.pitchedapps.frost.contracts.ActivityWebContract
+import com.pitchedapps.frost.contracts.FileChooserContract
+import com.pitchedapps.frost.contracts.FileChooserDelegate
+import com.pitchedapps.frost.facebook.FbCookie
+import com.pitchedapps.frost.facebook.formattedFbUrl
+import com.pitchedapps.frost.utils.*
+import com.pitchedapps.frost.web.FrostWebView
+
+
+/**
+ * Created by Allan Wang on 2017-06-01.
+ */
+open class WebOverlayActivity : AppCompatActivity(),
+ ActivityWebContract, FileChooserContract by FileChooserDelegate() {
+
+ val toolbar: Toolbar by bindView(R.id.overlay_toolbar)
+ val frostWeb: FrostWebView by bindView(R.id.overlay_frost_webview)
+ val coordinator: CoordinatorLayout by bindView(R.id.overlay_main_content)
+
+ val urlTest: String?
+ get() = intent.extras?.getString(ARG_URL) ?: intent.dataString
+
+ open val url: String
+ get() = (intent.extras?.getString(ARG_URL) ?: intent.dataString).formattedFbUrl
+
+ val userId: Long
+ get() = intent.extras?.getLong(ARG_USER_ID, Prefs.userId) ?: Prefs.userId
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ if (urlTest == null) {
+ L.eThrow("Empty link on web overlay")
+ toast(R.string.null_url_overlay)
+ finish()
+ return
+ }
+ setContentView(R.layout.activity_web_overlay)
+ setSupportActionBar(toolbar)
+ supportActionBar?.setDisplayShowHomeEnabled(true)
+ 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))
+
+ frostWeb.setupWebview(url)
+ frostWeb.web.addTitleListener({ toolbar.title = it })
+ if (userId != Prefs.userId) FbCookie.switchUser(userId) { frostWeb.web.loadBaseUrl() }
+ else frostWeb.web.loadBaseUrl()
+ if (Showcase.firstWebOverlay) {
+ coordinator.frostSnackbar(R.string.web_overlay_swipe_hint) {
+ duration = Snackbar.LENGTH_INDEFINITE
+ setAction(R.string.kau_got_it) { _ -> this.dismiss() }
+ }
+ }
+ }
+
+ /**
+ * Manage url loadings
+ * This is usually only called when multiple listeners are added and inject the same url
+ * We will avoid reloading if the url is the same
+ */
+ override fun onNewIntent(intent: Intent) {
+ super.onNewIntent(intent)
+ val newUrl = (intent.extras?.getString(ARG_URL) ?: intent.dataString ?: return).formattedFbUrl
+ L.d("New intent")
+ if (url != newUrl) {
+ this.intent = intent
+ frostWeb.web.baseUrl = newUrl
+ frostWeb.web.loadBaseUrl()
+ }
+ }
+
+ /**
+ * Our theme for the overlay should be fully opaque
+ */
+ fun theme() {
+ val opaqueAccent = Prefs.headerColor.withAlpha(255)
+ statusBarColor = opaqueAccent.darken()
+ navigationBarColor = opaqueAccent
+ toolbar.setBackgroundColor(opaqueAccent)
+ toolbar.setTitleTextColor(Prefs.iconColor)
+ coordinator.setBackgroundColor(Prefs.bgColor.withAlpha(255))
+ toolbar.overflowIcon?.setTint(Prefs.iconColor)
+ }
+
+ override fun onPostCreate(savedInstanceState: Bundle?) {
+ super.onPostCreate(savedInstanceState)
+ kauSwipeOnPostCreate()
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ kauSwipeOnDestroy()
+ }
+
+ override fun onBackPressed() {
+ if (!frostWeb.onBackPressed()) {
+ finishSlideOut()
+ }
+ }
+
+ override fun openFileChooser(filePathCallback: ValueCallback<Array<Uri>>, fileChooserParams: WebChromeClient.FileChooserParams) {
+ openFileChooser(this, 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)
+ setMenuIcons(menu, Prefs.iconColor,
+ R.id.action_share to CommunityMaterial.Icon.cmd_share,
+ R.id.action_copy_link to GoogleMaterial.Icon.gmd_content_copy)
+ return true
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ when (item.itemId) {
+ R.id.action_copy_link -> copyToClipboard(frostWeb.web.url)
+ R.id.action_share -> shareText(frostWeb.web.url)
+ else -> return super.onOptionsItemSelected(item)
+ }
+ return true
+ }
+} \ No newline at end of file