From db262e95779e0a17275bdb94be2b0ac12819178e Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Mon, 11 Dec 2017 17:52:24 -0500 Subject: Feature/tab customization (#522) * Add initial tab customizing view * Add rest of content for now * Delete project file backups * Stash * Support full tab customization * Test activity animations * Update kau and fix sound uri * Try catch download, resolves #523 --- .../kotlin/com/pitchedapps/frost/StartActivity.kt | 2 + .../pitchedapps/frost/activities/BaseActivity.kt | 2 + .../pitchedapps/frost/activities/MainActivity.kt | 3 +- .../frost/activities/SettingsActivity.kt | 34 ++++-- .../frost/activities/TabCustomizerActivity.kt | 131 +++++++++++++++++++++ .../com/pitchedapps/frost/dbflow/CookiesDb.kt | 6 +- .../com/pitchedapps/frost/dbflow/FbTabsDb.kt | 19 ++- .../com/pitchedapps/frost/facebook/FbItem.kt | 7 +- .../com/pitchedapps/frost/iitems/TabIItem.kt | 52 ++++++++ .../com/pitchedapps/frost/injectors/CssHider.kt | 5 +- .../com/pitchedapps/frost/injectors/JsAssets.kt | 10 +- .../com/pitchedapps/frost/injectors/JsInjector.kt | 23 +++- .../com/pitchedapps/frost/settings/Appearance.kt | 17 +-- .../kotlin/com/pitchedapps/frost/settings/Debug.kt | 2 +- .../com/pitchedapps/frost/settings/Experimental.kt | 3 +- .../kotlin/com/pitchedapps/frost/settings/Feed.kt | 4 +- .../pitchedapps/frost/settings/Notifications.kt | 12 +- .../com/pitchedapps/frost/utils/Downloader.kt | 19 +-- .../kotlin/com/pitchedapps/frost/utils/Utils.kt | 12 ++ 19 files changed, 304 insertions(+), 59 deletions(-) create mode 100644 app/src/main/kotlin/com/pitchedapps/frost/activities/TabCustomizerActivity.kt create mode 100644 app/src/main/kotlin/com/pitchedapps/frost/iitems/TabIItem.kt (limited to 'app/src/main/kotlin/com/pitchedapps') diff --git a/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt index 8c70f5f2..9888c377 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt @@ -1,10 +1,12 @@ package com.pitchedapps.frost +import android.content.Context import android.os.Bundle import ca.allanwang.kau.internal.KauBaseActivity import com.pitchedapps.frost.activities.LoginActivity import com.pitchedapps.frost.activities.MainActivity import com.pitchedapps.frost.activities.SelectorActivity +import com.pitchedapps.frost.activities.TabCustomizerActivity import com.pitchedapps.frost.dbflow.loadFbCookiesAsync import com.pitchedapps.frost.facebook.FbCookie import com.pitchedapps.frost.utils.L diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseActivity.kt index 97afd480..6ab65399 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseActivity.kt @@ -2,6 +2,7 @@ package com.pitchedapps.frost.activities import android.content.res.Configuration import android.os.Bundle +import android.transition.Fade import ca.allanwang.kau.internal.KauBaseActivity import ca.allanwang.kau.searchview.SearchViewHolder import com.pitchedapps.frost.contracts.VideoViewHolder @@ -28,6 +29,7 @@ abstract class BaseActivity : KauBaseActivity() { super.onCreate(savedInstanceState) if (this !is WebOverlayActivityBase) setFrostTheme() } + // // private var networkDisposable: Disposable? = null // private var networkConsumer: ((Connectivity) -> Unit)? = null diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt index 57cda44a..1ba7f4c3 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt @@ -49,6 +49,7 @@ import com.pitchedapps.frost.contracts.ActivityWebContract import com.pitchedapps.frost.contracts.FileChooserContract import com.pitchedapps.frost.contracts.FileChooserDelegate import com.pitchedapps.frost.contracts.VideoViewHolder +import com.pitchedapps.frost.dbflow.TAB_COUNT import com.pitchedapps.frost.dbflow.loadFbCookie import com.pitchedapps.frost.dbflow.loadFbTabs import com.pitchedapps.frost.enums.MainActivityLayout @@ -134,7 +135,7 @@ class MainActivity : BaseActivity(), setSupportActionBar(toolbar) adapter = SectionsPagerAdapter(supportFragmentManager, loadFbTabs()) viewPager.adapter = adapter - viewPager.offscreenPageLimit = 5 + viewPager.offscreenPageLimit = TAB_COUNT viewPager.addOnPageChangeListener(object : ViewPager.SimpleOnPageChangeListener() { override fun onPageSelected(position: Int) { super.onPageSelected(position) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/SettingsActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/SettingsActivity.kt index 293be694..f17ccf20 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/SettingsActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/SettingsActivity.kt @@ -37,10 +37,16 @@ class SettingsActivity : KPrefActivity(), FrostBilling by IabSettings() { private const val REQUEST_RINGTONE = 0b10111 shl 5 const val REQUEST_NOTIFICATION_RINGTONE = REQUEST_RINGTONE or 1 const val REQUEST_MESSAGE_RINGTONE = REQUEST_RINGTONE or 2 + const val ACTIVITY_REQUEST_TABS = 29 } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { if (fetchRingtone(requestCode, resultCode, data)) return + if (requestCode == ACTIVITY_REQUEST_TABS) { + if (resultCode == Activity.RESULT_OK) + shouldRestartMain() + return + } if (!onActivityResultBilling(requestCode, resultCode, data)) super.onActivityResult(requestCode, resultCode, data) reloadList() @@ -52,14 +58,22 @@ class SettingsActivity : KPrefActivity(), FrostBilling by IabSettings() { */ private fun fetchRingtone(requestCode: Int, resultCode: Int, data: Intent?): Boolean { if (requestCode and REQUEST_RINGTONE != REQUEST_RINGTONE || resultCode != Activity.RESULT_OK) return false - val uri: String = data?.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI)?.toString() ?: "" + val uri = data?.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI) + val uriString: String = uri?.toString() ?: "" + if (uri != null) { + try { + grantUriPermission("com.android.systemui", uri, Intent.FLAG_GRANT_READ_URI_PERMISSION) + } catch (e: Exception) { + L.e(e, "grantUriPermission") + } + } when (requestCode) { REQUEST_NOTIFICATION_RINGTONE -> { - Prefs.notificationRingtone = uri + Prefs.notificationRingtone = uriString reloadByTitle(R.string.notification_ringtone) } REQUEST_MESSAGE_RINGTONE -> { - Prefs.messageRingtone = uri + Prefs.messageRingtone = uriString reloadByTitle(R.string.message_ringtone) } } @@ -106,24 +120,28 @@ class SettingsActivity : KPrefActivity(), FrostBilling by IabSettings() { descRes = R.string.get_pro_desc iicon = GoogleMaterial.Icon.gmd_star visible = { !IS_FROST_PRO } - onClick = { _, _, _ -> restorePurchases(); true } + onClick = { restorePurchases() } } plainText(R.string.about_frost) { descRes = R.string.about_frost_desc iicon = GoogleMaterial.Icon.gmd_info - onClick = { _, _, _ -> startActivityForResult(AboutActivity::class.java, 9, true); true } + onClick = { + startActivityForResult(AboutActivity::class.java, 9, bundleBuilder = { + withSceneTransitionAnimation(this@SettingsActivity) + }) + } } plainText(R.string.help_translate) { descRes = R.string.help_translate_desc iicon = GoogleMaterial.Icon.gmd_translate - onClick = { _, _, _ -> startLink(R.string.translation_url); true } + onClick = { startLink(R.string.translation_url) } } plainText(R.string.replay_intro) { iicon = GoogleMaterial.Icon.gmd_replay - onClick = { _, _, _ -> launchIntroActivity(cookies()); true } + onClick = { launchIntroActivity(cookies()) } } subItems(R.string.debug_frost, getDebugPrefs()) { @@ -138,7 +156,7 @@ class SettingsActivity : KPrefActivity(), FrostBilling by IabSettings() { } fun KPrefItemBase.BaseContract<*>.dependsOnPro() { - onDisabledClick = { _, _, _ -> purchasePro(); true } + onDisabledClick = { purchasePro() } enabler = { IS_FROST_PRO } } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/TabCustomizerActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/TabCustomizerActivity.kt new file mode 100644 index 00000000..bac352af --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/TabCustomizerActivity.kt @@ -0,0 +1,131 @@ +package com.pitchedapps.frost.activities + +import android.app.Activity +import android.content.res.ColorStateList +import android.os.Bundle +import android.support.design.widget.FloatingActionButton +import android.support.v7.widget.GridLayoutManager +import android.support.v7.widget.RecyclerView +import android.support.v7.widget.helper.ItemTouchHelper +import android.view.View +import android.view.animation.AnimationUtils +import android.widget.TextView +import ca.allanwang.kau.kotlin.lazyContext +import ca.allanwang.kau.utils.bindView +import ca.allanwang.kau.utils.scaleXY +import ca.allanwang.kau.utils.setIcon +import ca.allanwang.kau.utils.withAlpha +import com.mikepenz.fastadapter.commons.adapters.FastItemAdapter +import com.mikepenz.fastadapter_extensions.drag.ItemTouchCallback +import com.mikepenz.fastadapter_extensions.drag.SimpleDragCallback +import com.mikepenz.google_material_typeface_library.GoogleMaterial +import com.pitchedapps.frost.R +import com.pitchedapps.frost.dbflow.TAB_COUNT +import com.pitchedapps.frost.dbflow.loadFbTabs +import com.pitchedapps.frost.dbflow.save +import com.pitchedapps.frost.facebook.FbItem +import com.pitchedapps.frost.iitems.TabIItem +import com.pitchedapps.frost.utils.Prefs +import com.pitchedapps.frost.utils.setFrostColors +import java.util.* + +/** + * Created by Allan Wang on 26/11/17. + */ +class TabCustomizerActivity : BaseActivity() { + + val toolbar: View by bindView(R.id.pseudo_toolbar) + val recycler: RecyclerView by bindView(R.id.tab_recycler) + val instructions: TextView by bindView(R.id.instructions) + val divider: View by bindView(R.id.divider) + val adapter = FastItemAdapter() + val fabCancel: FloatingActionButton by bindView(R.id.fab_cancel) + val fabSave: FloatingActionButton by bindView(R.id.fab_save) + + private val wobble = lazyContext { AnimationUtils.loadAnimation(it, R.anim.rotate_delta) } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_tab_customizer) + + toolbar.setBackgroundColor(Prefs.headerColor) + + recycler.layoutManager = GridLayoutManager(this, TAB_COUNT, GridLayoutManager.VERTICAL, false) + recycler.adapter = adapter + recycler.setHasFixedSize(true) + + divider.setBackgroundColor(Prefs.textColor.withAlpha(30)) + instructions.setTextColor(Prefs.textColor) + + val tabs = loadFbTabs().toMutableList() + val remaining = FbItem.values().toMutableList() + remaining.removeAll(tabs) + tabs.addAll(remaining) + + adapter.add(tabs.map(::TabIItem)) + bindSwapper(adapter, recycler) + + adapter.withOnClickListener { view, _, _, _ -> view.wobble(); true } + + setResult(Activity.RESULT_CANCELED) + + fabSave.setIcon(GoogleMaterial.Icon.gmd_check, Prefs.iconColor) + fabSave.backgroundTintList = ColorStateList.valueOf(Prefs.accentColor) + fabSave.setOnClickListener { + adapter.adapterItems.subList(0, TAB_COUNT).map(TabIItem::item).save() + setResult(Activity.RESULT_OK) + finish() + } + fabCancel.setIcon(GoogleMaterial.Icon.gmd_close, Prefs.iconColor) + fabCancel.backgroundTintList = ColorStateList.valueOf(Prefs.accentColor) + fabCancel.setOnClickListener { finish() } + setFrostColors(themeWindow = true) + } + + private fun View.wobble() = startAnimation(wobble(context)) + + private fun bindSwapper(adapter: FastItemAdapter<*>, recycler: RecyclerView) { + val dragCallback = TabDragCallback(SimpleDragCallback.ALL, swapper(adapter)) + ItemTouchHelper(dragCallback).attachToRecyclerView(recycler) + } + + private fun swapper(adapter: FastItemAdapter<*>) = object : ItemTouchCallback { + override fun itemTouchOnMove(oldPosition: Int, newPosition: Int): Boolean { + Collections.swap(adapter.adapterItems, oldPosition, newPosition) + adapter.notifyAdapterDataSetChanged() + return true + } + + override fun itemTouchDropped(oldPosition: Int, newPosition: Int) = Unit + } + + + private class TabDragCallback( + directions: Int, itemTouchCallback: ItemTouchCallback + ) : SimpleDragCallback(directions, itemTouchCallback) { + + private var draggingView: TabIItem.ViewHolder? = null + + override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) { + super.onSelectedChanged(viewHolder, actionState) + when (actionState) { + ItemTouchHelper.ACTION_STATE_DRAG -> { + (viewHolder as? TabIItem.ViewHolder)?.apply { + draggingView = this + itemView.animate().scaleXY(1.3f) + text.animate().alpha(0f) + } + } + ItemTouchHelper.ACTION_STATE_IDLE -> { + draggingView?.apply { + itemView.animate().scaleXY(1f) + text.animate().alpha(1f) + } + draggingView = null + } + } + } + + } + +} diff --git a/app/src/main/kotlin/com/pitchedapps/frost/dbflow/CookiesDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/dbflow/CookiesDb.kt index ff46856c..762dd4c1 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/dbflow/CookiesDb.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/dbflow/CookiesDb.kt @@ -52,10 +52,10 @@ fun loadFbCookiesAsync(callback: (cookies: List) -> Unit) { fun loadFbCookiesSync(): List = (select from CookieModel::class).orderBy(CookieModel_Table.name, true).queryList() -fun saveFbCookie(cookie: CookieModel, callback: (() -> Unit)? = null) { +inline fun saveFbCookie(cookie: CookieModel, crossinline callback: (() -> Unit) = {}) { cookie.async save { L.d("Fb cookie saved", cookie.toString()) - callback?.invoke() + callback() } } @@ -65,7 +65,7 @@ fun removeCookie(id: Long) { } } -fun CookieModel.fetchUsername(callback: (String) -> Unit) { +inline fun CookieModel.fetchUsername(crossinline callback: (String) -> Unit) { ReactiveNetwork.checkInternetConnectivity().subscribeOn(Schedulers.io()).subscribe { yes, _ -> if (!yes) return@subscribe callback("") var result = "" diff --git a/app/src/main/kotlin/com/pitchedapps/frost/dbflow/FbTabsDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/dbflow/FbTabsDb.kt index 7fc56af0..18f0e2e8 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/dbflow/FbTabsDb.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/dbflow/FbTabsDb.kt @@ -6,13 +6,16 @@ import com.pitchedapps.frost.utils.L import com.raizlabs.android.dbflow.annotation.Database import com.raizlabs.android.dbflow.annotation.PrimaryKey import com.raizlabs.android.dbflow.annotation.Table +import com.raizlabs.android.dbflow.kotlinextensions.database +import com.raizlabs.android.dbflow.kotlinextensions.fastSave import com.raizlabs.android.dbflow.kotlinextensions.from -import com.raizlabs.android.dbflow.sql.language.SQLite +import com.raizlabs.android.dbflow.kotlinextensions.select import com.raizlabs.android.dbflow.structure.BaseModel /** * Created by Allan Wang on 2017-05-30. */ +const val TAB_COUNT = 4 @Database(version = FbTabsDb.VERSION) object FbTabsDb { @@ -23,13 +26,17 @@ object FbTabsDb { @Table(database = FbTabsDb::class, allFields = true) data class FbTabModel(@PrimaryKey var position: Int = -1, var tab: FbItem = FbItem.FEED) : BaseModel() +/** + * Load tabs synchronously + * Note that tab length should never be a big number anyways + */ fun loadFbTabs(): List { - val tabs: List? = SQLite.select().from(FbTabModel::class).orderBy(FbTabModel_Table.position, true).queryList() - if (tabs?.isNotEmpty() ?: false) return tabs!!.map { it.tab } - L.d("No tabs; loading default") + val tabs: List? = (select from (FbTabModel::class)).orderBy(FbTabModel_Table.position, true).queryList() + if (tabs?.size == TAB_COUNT) return tabs.map(FbTabModel::tab) + L.d("No tabs (${tabs?.size}); loading default") return defaultTabs() } -fun List.saveAsync() { - mapIndexed { index, fbTab -> FbTabModel(index, fbTab) }.replace(FbTabsDb.NAME) +fun List.save() { + database().beginTransactionAsync(mapIndexed(::FbTabModel).fastSave().build()).execute() } \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbItem.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbItem.kt index 2f478d44..27479691 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbItem.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbItem.kt @@ -10,7 +10,12 @@ import com.pitchedapps.frost.web.FrostWebViewClient import com.pitchedapps.frost.web.FrostWebViewClientMenu import com.pitchedapps.frost.web.FrostWebViewCore -enum class FbItem(@StringRes val titleId: Int, val icon: IIcon, relativeUrl: String, val webClient: ((webCore: FrostWebViewCore) -> FrostWebViewClient)? = null) { +enum class FbItem( + @StringRes val titleId: Int, + val icon: IIcon, + relativeUrl: String, + val webClient: ((webCore: FrostWebViewCore) -> FrostWebViewClient)? = null +) { ACTIVITY_LOG(R.string.activity_log, GoogleMaterial.Icon.gmd_list, "me/allactivity"), BIRTHDAYS(R.string.birthdays, GoogleMaterial.Icon.gmd_cake, "events/birthdays"), CHAT(R.string.chat, GoogleMaterial.Icon.gmd_chat, "buddylist"), diff --git a/app/src/main/kotlin/com/pitchedapps/frost/iitems/TabIItem.kt b/app/src/main/kotlin/com/pitchedapps/frost/iitems/TabIItem.kt new file mode 100644 index 00000000..73d80538 --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/iitems/TabIItem.kt @@ -0,0 +1,52 @@ +package com.pitchedapps.frost.iitems + +import android.graphics.Color +import android.view.View +import android.widget.ImageView +import android.widget.TextView +import ca.allanwang.kau.iitems.KauIItem +import ca.allanwang.kau.utils.* +import com.mikepenz.fastadapter.FastAdapter +import com.mikepenz.fastadapter.IItem +import com.mikepenz.fastadapter_extensions.drag.IDraggable +import com.pitchedapps.frost.R +import com.pitchedapps.frost.facebook.FbItem +import com.pitchedapps.frost.utils.L +import com.pitchedapps.frost.utils.Prefs + +/** + * Created by Allan Wang on 26/11/17. + */ +class TabIItem(val item: FbItem) : KauIItem( + R.layout.iitem_tab_preview, + { ViewHolder(it) } +), IDraggable> { + + override fun withIsDraggable(draggable: Boolean): TabIItem = this + + override fun isDraggable() = true + + class ViewHolder(itemView: View) : FastAdapter.ViewHolder(itemView) { + + val image: ImageView by bindView(R.id.image) + val text: TextView by bindView(R.id.text) + + override fun bindView(item: TabIItem, payloads: MutableList) { + val isInToolbar = adapterPosition < 4 + val color = if (isInToolbar) Prefs.iconColor else Prefs.textColor + image.setIcon(item.item.icon, 20, color) + if (isInToolbar) + text.invisible() + else { + text.visible().setText(item.item.titleId) + text.setTextColor(color.withAlpha(200)) + } + } + + override fun unbindView(item: TabIItem) { + image.setImageDrawable(null) + text.visible().text = null + } + + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/injectors/CssHider.kt b/app/src/main/kotlin/com/pitchedapps/frost/injectors/CssHider.kt index 5b26ebac..de268360 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/injectors/CssHider.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/injectors/CssHider.kt @@ -21,7 +21,10 @@ enum class CssHider(vararg val items: String) : InjectorContract { NON_RECENT("article:not([data-store*=actor_name])") ; - val injector: JsInjector by lazy { JsBuilder().css("${items.joinToString(separator = ",")}{display:none!important}").build() } + val injector: JsInjector by lazy { + JsBuilder().css("${items.joinToString(separator = ",")}{display:none !important}") + .single(name).build() + } override fun inject(webView: WebView, callback: ((String) -> Unit)?) { injector.inject(webView, callback) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsAssets.kt b/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsAssets.kt index 0e46225c..beb60293 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsAssets.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsAssets.kt @@ -19,7 +19,7 @@ enum class JsAssets : InjectorContract { var injector = lazyContext { try { val content = it.assets.open("js/$file").bufferedReader().use { it.readText() } - JsBuilder().js(singleInjector(content)).build() + JsBuilder().js(content).single(name).build() } catch (e: FileNotFoundException) { L.e(e, "JsAssets file not found") JsInjector(JsActions.EMPTY.function) @@ -30,12 +30,4 @@ enum class JsAssets : InjectorContract { injector(webView.context).inject(webView, callback) } - private fun singleInjector(content: String) = StringBuilder().apply { - val name = "_frost_$name" - append("if (!window.hasOwnProperty(\"$name\")) {") - append("console.log(\"Registering $name\");") - append("window.$name = true;") - append(content) - append("}") - }.toString() } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsInjector.kt b/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsInjector.kt index a29ff55e..4fed2db9 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsInjector.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsInjector.kt @@ -7,11 +7,14 @@ import io.reactivex.Observable import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.subjects.SingleSubject import org.apache.commons.text.StringEscapeUtils +import java.util.* class JsBuilder { private val css = StringBuilder() private val js = StringBuilder() + private var tag: String? = null + fun css(css: String): JsBuilder { this.css.append(StringEscapeUtils.escapeEcmaScript(css)) return this @@ -22,6 +25,11 @@ class JsBuilder { return this } + fun single(tag: String): JsBuilder { + this.tag = tag + return this + } + fun build() = JsInjector(toString()) override fun toString(): String { @@ -32,8 +40,19 @@ class JsBuilder { } if (js.isNotBlank()) builder.append(js) - return builder.append("}()").toString() + var content = builder.append("}()").toString() + if (tag != null) content = singleInjector(tag!!, content) + return content } + + private fun singleInjector(tag: String, content: String) = StringBuilder().apply { + val name = "_frost_${tag.toLowerCase(Locale.CANADA)}" + append("if (!window.hasOwnProperty(\"$name\")) {") + append("console.log(\"Registering $name\");") + append("window.$name = true;") + append(content) + append("}") + }.toString() } /** @@ -72,4 +91,4 @@ class JsInjector(val function: String) : InjectorContract { override fun inject(webView: WebView, callback: ((String) -> Unit)?) { webView.evaluateJavascript(function, { value -> callback?.invoke(value) }) } -} +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/settings/Appearance.kt b/app/src/main/kotlin/com/pitchedapps/frost/settings/Appearance.kt index 382b2dad..2c229830 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/settings/Appearance.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/settings/Appearance.kt @@ -23,7 +23,7 @@ fun SettingsActivity.getAppearancePrefs(): KPrefAdapterBuilder.() -> Unit = { header(R.string.theme_customization) text(R.string.theme, { Prefs.theme }, { Prefs.theme = it }) { - onClick = { _, _, item -> + onClick = { materialDialogThemed { title(R.string.theme) items(Theme.values() @@ -46,7 +46,6 @@ fun SettingsActivity.getAppearancePrefs(): KPrefAdapterBuilder.() -> Unit = { true } } - true } textGetter = { string(Theme(it).textRes) @@ -55,12 +54,12 @@ fun SettingsActivity.getAppearancePrefs(): KPrefAdapterBuilder.() -> Unit = { fun KPrefColorPicker.KPrefColorContract.dependsOnCustom() { enabler = { Prefs.isCustomTheme } - onDisabledClick = { _, _, _ -> frostSnackbar(R.string.requires_custom_theme); true } + onDisabledClick = { frostSnackbar(R.string.requires_custom_theme) } allowCustom = true } fun invalidateCustomTheme() { - CssAssets.CUSTOM.injector = null + CssAssets.CUSTOM.injector.invalidate() } colorPicker(R.string.text_color, { Prefs.customTextColor }, { @@ -119,9 +118,9 @@ fun SettingsActivity.getAppearancePrefs(): KPrefAdapterBuilder.() -> Unit = { text(R.string.main_activity_layout, { Prefs.mainActivityLayoutType }, { Prefs.mainActivityLayoutType = it }) { textGetter = { string(Prefs.mainActivityLayout.titleRes) } - onClick = { _, _, item -> + onClick = { materialDialogThemed { - title(R.string.set_main_activity_layout) + title(R.string.main_activity_layout_desc) items(MainActivityLayout.values.map { string(it.titleRes) }) itemsCallbackSingleChoice(item.pref) { _, _, which, _ -> if (item.pref != which) { @@ -132,10 +131,14 @@ fun SettingsActivity.getAppearancePrefs(): KPrefAdapterBuilder.() -> Unit = { true } } - true } } + plainText(R.string.main_tabs) { + descRes = R.string.main_tabs_desc + onClick = { launchTabCustomizerActivity() } + } + checkbox(R.string.rounded_icons, { Prefs.showRoundedIcons }, { Prefs.showRoundedIcons = it setFrostResult(MainActivity.REQUEST_REFRESH) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/settings/Debug.kt b/app/src/main/kotlin/com/pitchedapps/frost/settings/Debug.kt index fc008765..60397158 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/settings/Debug.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/settings/Debug.kt @@ -36,7 +36,7 @@ fun SettingsActivity.getDebugPrefs(): KPrefAdapterBuilder.() -> Unit = { Debugger.values().forEach { plainText(it.data.titleId) { iicon = it.data.icon - onClick = { itemView, _, _ -> it.debug(itemView.context); true } + onClick = { it.debug(itemView.context) } } } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/settings/Experimental.kt b/app/src/main/kotlin/com/pitchedapps/frost/settings/Experimental.kt index aad2fe9a..ed011af9 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/settings/Experimental.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/settings/Experimental.kt @@ -39,10 +39,9 @@ fun SettingsActivity.getExperimentalPrefs(): KPrefAdapterBuilder.() -> Unit = { plainText(R.string.restart_frost) { descRes = R.string.restart_frost_desc - onClick = { _, _, _ -> + onClick = { setFrostResult(MainActivity.REQUEST_RESTART_APPLICATION) finish() - true } } } \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/settings/Feed.kt b/app/src/main/kotlin/com/pitchedapps/frost/settings/Feed.kt index e3a0872a..2a0f913c 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/settings/Feed.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/settings/Feed.kt @@ -7,7 +7,6 @@ import com.pitchedapps.frost.activities.MainActivity import com.pitchedapps.frost.activities.SettingsActivity import com.pitchedapps.frost.enums.FeedSort import com.pitchedapps.frost.utils.Prefs -import com.pitchedapps.frost.utils.launchWebOverlay import com.pitchedapps.frost.utils.materialDialogThemed /** @@ -17,7 +16,7 @@ fun SettingsActivity.getFeedPrefs(): KPrefAdapterBuilder.() -> Unit = { text(R.string.newsfeed_sort, { Prefs.feedSort }, { Prefs.feedSort = it }) { descRes = R.string.newsfeed_sort_desc - onClick = { _, _, item -> + onClick = { materialDialogThemed { title(R.string.newsfeed_sort) items(FeedSort.values().map { string(it.textRes) }) @@ -29,7 +28,6 @@ fun SettingsActivity.getFeedPrefs(): KPrefAdapterBuilder.() -> Unit = { true }) } - true } textGetter = { string(FeedSort(it).textRes) } } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/settings/Notifications.kt b/app/src/main/kotlin/com/pitchedapps/frost/settings/Notifications.kt index f46517ac..4bd41802 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/settings/Notifications.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/settings/Notifications.kt @@ -25,7 +25,7 @@ fun SettingsActivity.getNotificationPrefs(): KPrefAdapterBuilder.() -> Unit = { text(R.string.notification_frequency, { Prefs.notificationFreq }, { Prefs.notificationFreq = it }) { val options = longArrayOf(-1, 15, 30, 60, 120, 180, 300, 1440, 2880) val texts = options.map { if (it <= 0) string(R.string.no_notifications) else minuteToText(it) } - onClick = { _, _, item -> + onClick = { materialDialogThemed { title(R.string.notification_frequency) items(texts) @@ -35,14 +35,13 @@ fun SettingsActivity.getNotificationPrefs(): KPrefAdapterBuilder.() -> Unit = { true }) } - true } textGetter = { minuteToText(it) } } plainText(R.string.notification_keywords) { descRes = R.string.notification_keywords_desc - onClick = { _, _, _ -> + onClick = { val keywordView = Keywords(this@getNotificationPrefs) materialDialogThemed { title(R.string.notification_keywords) @@ -50,7 +49,6 @@ fun SettingsActivity.getNotificationPrefs(): KPrefAdapterBuilder.() -> Unit = { dismissListener { keywordView.save() } positiveText(R.string.kau_done) } - true } } @@ -76,7 +74,7 @@ fun SettingsActivity.getNotificationPrefs(): KPrefAdapterBuilder.() -> Unit = { else RingtoneManager.getRingtone(this@getNotificationPrefs, Uri.parse(it)) ?.getTitle(this@getNotificationPrefs) ?: "---" //todo figure out why this happens } - onClick = { _, _, item -> + onClick = { val intent = Intent(RingtoneManager.ACTION_RINGTONE_PICKER).apply { putExtra(RingtoneManager.EXTRA_RINGTONE_TITLE, string(R.string.select_ringtone)) putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, false) @@ -86,7 +84,6 @@ fun SettingsActivity.getNotificationPrefs(): KPrefAdapterBuilder.() -> Unit = { putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, Uri.parse(item.pref)) } startActivityForResult(intent, code) - true } } @@ -104,10 +101,9 @@ fun SettingsActivity.getNotificationPrefs(): KPrefAdapterBuilder.() -> Unit = { plainText(R.string.notification_fetch_now) { descRes = R.string.notification_fetch_now_desc - onClick = { _, _, _ -> + onClick = { val text = if (fetchNotifications()) R.string.notification_fetch_success else R.string.notification_fetch_fail frostSnackbar(text) - true } } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/Downloader.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/Downloader.kt index e6db8eee..2c638dfd 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/utils/Downloader.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Downloader.kt @@ -9,14 +9,12 @@ import android.webkit.URLUtil import ca.allanwang.kau.permissions.PERMISSION_WRITE_EXTERNAL_STORAGE import ca.allanwang.kau.permissions.kauRequestPermissions import ca.allanwang.kau.utils.isAppEnabled +import ca.allanwang.kau.utils.showAppInfo import ca.allanwang.kau.utils.string +import ca.allanwang.kau.utils.toast import com.pitchedapps.frost.R import com.pitchedapps.frost.dbflow.loadFbCookie import com.pitchedapps.frost.facebook.USER_AGENT_BASIC -import android.support.v4.content.ContextCompat.startActivity -import android.content.Intent -import android.content.ActivityNotFoundException -import ca.allanwang.kau.utils.showAppInfo /** @@ -40,8 +38,10 @@ fun Context.frostDownload(uri: Uri?, contentLength: Long = 0L) { uri ?: return L.d("Received download request", "Download $uri") - if (uri.scheme != "http" && uri.scheme != "https") - return L.e("Invalid download attempt", uri.toString()) + if (uri.scheme != "http" && uri.scheme != "https") { + toast(R.string.error_invalid_download) + return L.e(string(R.string.error_invalid_download), uri.toString()) + } if (!isAppEnabled(DOWNLOAD_MANAGER_PACKAGE)) { materialDialogThemed { title(R.string.no_download_manager) @@ -66,7 +66,12 @@ fun Context.frostDownload(uri: Uri?, request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "Frost/$title") val dm = getSystemService(DOWNLOAD_SERVICE) as DownloadManager - dm.enqueue(request) + try { + dm.enqueue(request) + } catch (e: Exception) { + toast(R.string.error_generic) + L.e(e, "Download") + } } } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt index d8fa2ce9..ccc4033c 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt @@ -10,6 +10,7 @@ import android.net.Uri import android.support.annotation.StringRes import android.support.design.internal.SnackbarContentLayout import android.support.design.widget.Snackbar +import android.support.v4.app.ActivityOptionsCompat import android.support.v7.widget.Toolbar import android.view.View import android.widget.FrameLayout @@ -80,13 +81,24 @@ fun Context.launchWebOverlay(url: String, clazz: Class) = launchNewTask(IntroActivity::class.java, cookieList, true) -- cgit v1.2.3