From d90cb9b61cd2e033b46f4780ad1340c5f35b7751 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Sun, 16 Jul 2017 17:26:58 -0700 Subject: Add image viewing and downloading (#63) * 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 --- .../pitchedapps/frost/activities/AboutActivity.kt | 145 +++++++ .../pitchedapps/frost/activities/BaseActivity.kt | 32 ++ .../frost/activities/FrostWebActivity.kt | 20 + .../pitchedapps/frost/activities/ImageActivity.kt | 297 +++++++++++++ .../pitchedapps/frost/activities/LoginActivity.kt | 126 ++++++ .../pitchedapps/frost/activities/MainActivity.kt | 472 +++++++++++++++++++++ .../frost/activities/SelectorActivity.kt | 47 ++ .../frost/activities/SettingsActivity.kt | 145 +++++++ .../frost/activities/WebOverlayActivity.kt | 158 +++++++ 9 files changed, 1442 insertions(+) create mode 100644 app/src/main/kotlin/com/pitchedapps/frost/activities/AboutActivity.kt create mode 100644 app/src/main/kotlin/com/pitchedapps/frost/activities/BaseActivity.kt create mode 100644 app/src/main/kotlin/com/pitchedapps/frost/activities/FrostWebActivity.kt create mode 100644 app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt create mode 100644 app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt create mode 100644 app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt create mode 100644 app/src/main/kotlin/com/pitchedapps/frost/activities/SelectorActivity.kt create mode 100644 app/src/main/kotlin/com/pitchedapps/frost/activities/SettingsActivity.kt create mode 100644 app/src/main/kotlin/com/pitchedapps/frost/activities/WebOverlayActivity.kt (limited to 'app/src/main/kotlin/com/pitchedapps/frost/activities') 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 { + 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>) { + /** + * 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(), 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?) { + 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 + + /** + * 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 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() { + + override fun removeCallback(cb: SizeReadyCallback?) {} + + override fun onResourceReady(resource: Bitmap, transition: Transition?) = 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, 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() + val progressObservable = BehaviorSubject.create()!! + val profileObservable = SingleSubject.create() + val usernameObservable = SingleSubject.create() + + // 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> { 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 { + override fun onResourceReady(resource: Drawable?, model: Any?, target: Target?, dataSource: DataSource?, isFirstResource: Boolean): Boolean { + profileObservable.onSuccess(true) + return false + } + + override fun onLoadFailed(e: GlideException?, model: Any?, target: Target?, 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()!! + var lastPosition = -1 + val headerBadgeObservable = PublishSubject.create() + 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) { + 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>, 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, 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) : 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() + 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() { + override fun onBind(viewHolder: RecyclerView.ViewHolder): View? = (viewHolder as? AccountItem.ViewHolder)?.v + + override fun onClick(v: View, position: Int, fastAdapter: FastAdapter, 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>, 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, 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 -- cgit v1.2.3