1
0
mirror of https://github.com/AllanWang/Frost-for-Facebook.git synced 2024-11-08 20:12:39 +01:00

Remove user info in debug logs

This commit is contained in:
Allan Wang 2017-06-30 10:36:38 -07:00
parent a425190308
commit 7267064d8a
20 changed files with 136 additions and 61 deletions

View File

@ -24,6 +24,12 @@ class AboutActivity : AboutActivityBase(R.string::class.java, configBuilder = {
}) { }) {
override fun getLibraries(libs: Libs): List<Library> { override fun getLibraries(libs: Libs): List<Library> {
val include = arrayOf(
"materialdialogs",
"kotterknife",
"glide",
"jsoup"
)
/* /*
* These are great libraries, but either aren't used directly or are too common to be listed * These are great libraries, but either aren't used directly or are too common to be listed
* Give more emphasis on the unique libs! * Give more emphasis on the unique libs!
@ -39,7 +45,7 @@ class AboutActivity : AboutActivityBase(R.string::class.java, configBuilder = {
"recyclerview_v7", "recyclerview_v7",
"support_v4" "support_v4"
) )
val l = libs.prepareLibraries(this, null, exclude, true, true) val l = libs.prepareLibraries(this, include, exclude, true, true)
// l.forEach { KL.d("Lib ${it.definedName}") } // l.forEach { KL.d("Lib ${it.definedName}") }
return l return l
} }

View File

@ -14,7 +14,6 @@ import android.support.v7.widget.Toolbar
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import ca.allanwang.kau.changelog.showChangelog import ca.allanwang.kau.changelog.showChangelog
import ca.allanwang.kau.logging.KL
import ca.allanwang.kau.searchview.SearchItem import ca.allanwang.kau.searchview.SearchItem
import ca.allanwang.kau.searchview.SearchView import ca.allanwang.kau.searchview.SearchView
import ca.allanwang.kau.searchview.bindSearchView import ca.allanwang.kau.searchview.bindSearchView
@ -331,10 +330,7 @@ class MainActivity : BaseActivity(), FrostWebViewSearch.SearchContract {
if (searchView == null) searchView = bindSearchView(menu, R.id.action_search, Prefs.iconColor) { if (searchView == null) searchView = bindSearchView(menu, R.id.action_search, Prefs.iconColor) {
textObserver = { textObserver = {
observable, _ -> observable, _ ->
observable.observeOn(AndroidSchedulers.mainThread()).subscribe { observable.observeOn(AndroidSchedulers.mainThread()).subscribe { hiddenSearchView?.query(it) }
L.d("Input $it")
hiddenSearchView?.query(it)
}
} }
foregroundColor = Prefs.textColor foregroundColor = Prefs.textColor
backgroundColor = Prefs.bgColor backgroundColor = Prefs.bgColor

View File

@ -12,10 +12,7 @@ import ca.allanwang.kau.utils.*
import ca.allanwang.kau.views.RippleCanvas import ca.allanwang.kau.views.RippleCanvas
import com.mikepenz.community_material_typeface_library.CommunityMaterial import com.mikepenz.community_material_typeface_library.CommunityMaterial
import com.mikepenz.google_material_typeface_library.GoogleMaterial import com.mikepenz.google_material_typeface_library.GoogleMaterial
import com.pitchedapps.frost.settings.getAppearancePrefs import com.pitchedapps.frost.settings.*
import com.pitchedapps.frost.settings.getExperimentalPrefs
import com.pitchedapps.frost.settings.getFeedPrefs
import com.pitchedapps.frost.settings.getNotificationPrefs
import com.pitchedapps.frost.utils.* import com.pitchedapps.frost.utils.*
import com.pitchedapps.frost.utils.iab.IS_FROST_PRO import com.pitchedapps.frost.utils.iab.IS_FROST_PRO
import com.pitchedapps.frost.utils.iab.openPlayProPurchase import com.pitchedapps.frost.utils.iab.openPlayProPurchase
@ -37,6 +34,11 @@ class SettingsActivity : KPrefActivity() {
iicon = GoogleMaterial.Icon.gmd_palette 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()) { subItems(R.string.newsfeed, getFeedPrefs()) {
descRes = R.string.newsfeed_desc descRes = R.string.newsfeed_desc
iicon = CommunityMaterial.Icon.cmd_newspaper iicon = CommunityMaterial.Icon.cmd_newspaper

View File

@ -15,11 +15,10 @@ class StartActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
L.d("Load cookies ${System.currentTimeMillis()}")
FbCookie.switchBackUser { FbCookie.switchBackUser {
loadFbCookiesAsync { loadFbCookiesAsync {
cookies -> cookies ->
L.d("Cookies loaded ${System.currentTimeMillis()} $cookies") L.d("Cookies loaded ${System.currentTimeMillis()}", cookies.toString())
if (cookies.isNotEmpty()) if (cookies.isNotEmpty())
launchNewTask(if (Prefs.userId != -1L) MainActivity::class.java else SelectorActivity::class.java, ArrayList(cookies)) launchNewTask(if (Prefs.userId != -1L) MainActivity::class.java else SelectorActivity::class.java, ArrayList(cookies))
else else

View File

@ -51,14 +51,14 @@ fun loadFbCookiesSync(): List<CookieModel> = (select from CookieModel::class).or
fun saveFbCookie(cookie: CookieModel, callback: (() -> Unit)? = null) { fun saveFbCookie(cookie: CookieModel, callback: (() -> Unit)? = null) {
cookie.async save { cookie.async save {
L.d("Fb cookie $cookie saved") L.d("Fb cookie saved", cookie.toString())
callback?.invoke() callback?.invoke()
} }
} }
fun removeCookie(id: Long) { fun removeCookie(id: Long) {
loadFbCookie(id)?.async?.delete({ loadFbCookie(id)?.async?.delete({
L.d("Fb cookie $id deleted") L.d("Fb cookie deleted", id.toString())
}) })
} }
@ -69,9 +69,9 @@ fun CookieModel.fetchUsername(callback: (String) -> Unit) {
result = Jsoup.connect(FbTab.PROFILE.url) result = Jsoup.connect(FbTab.PROFILE.url)
.cookie(FACEBOOK_COM, cookie) .cookie(FACEBOOK_COM, cookie)
.get().title() .get().title()
L.d("User name found: $result") L.d("Fetch username found", result)
} catch (e: Exception) { } catch (e: Exception) {
L.e("User name fetching failed: ${e.message}") L.e(e, "Fetch username failed")
} finally { } finally {
if (result.isBlank() && (name?.isNotBlank() ?: false)) { if (result.isBlank() && (name?.isNotBlank() ?: false)) {
callback(name!!) callback(name!!)

View File

@ -25,7 +25,7 @@ fun lastNotificationTime(id: Long): Long = (select from NotificationModel::class
fun saveNotificationTime(notificationModel: NotificationModel, callback: (() -> Unit)? = null) { fun saveNotificationTime(notificationModel: NotificationModel, callback: (() -> Unit)? = null) {
notificationModel.async save { notificationModel.async save {
L.d("Fb notification $notificationModel saved") L.d("Fb notification model saved", notificationModel.toString())
callback?.invoke() callback?.invoke()
} }
} }

View File

@ -26,12 +26,12 @@ object FbCookie {
callback?.invoke() callback?.invoke()
return@removeAllCookies return@removeAllCookies
} }
L.d("Setting cookie to $cookie") L.d("Setting cookie", cookie)
val cookies = cookie.split(";").map { Pair(it, SingleSubject.create<Boolean>()) } val cookies = cookie.split(";").map { Pair(it, SingleSubject.create<Boolean>()) }
cookies.forEach { (cookie, callback) -> setCookie(FB_URL_BASE, cookie, { callback.onSuccess(it) }) } cookies.forEach { (cookie, callback) -> setCookie(FB_URL_BASE, cookie, { callback.onSuccess(it) }) }
Observable.zip<Boolean, Unit>(cookies.map { (_, callback) -> callback.toObservable() }, {}).subscribeOn(AndroidSchedulers.mainThread()).subscribe({ Observable.zip<Boolean, Unit>(cookies.map { (_, callback) -> callback.toObservable() }, {}).subscribeOn(AndroidSchedulers.mainThread()).subscribe({
callback?.invoke() callback?.invoke()
L.d("Cookies set: $webCookie") L.d("Cookies set", webCookie)
flush() flush()
}) })
}) })
@ -39,7 +39,7 @@ object FbCookie {
} }
operator fun invoke() { operator fun invoke() {
L.d("User ${Prefs.userId}") L.d("FbCookie Invoke User", Prefs.userId.toString())
with(CookieManager.getInstance()) { with(CookieManager.getInstance()) {
setAcceptCookie(true) setAcceptCookie(true)
} }
@ -51,7 +51,7 @@ object FbCookie {
} }
fun save(id: Long) { fun save(id: Long) {
L.d("New cookie found for $id") L.d("New cookie found", id.toString())
Prefs.userId = id Prefs.userId = id
CookieManager.getInstance().flush() CookieManager.getInstance().flush()
val cookie = CookieModel(Prefs.userId, "", webCookie) val cookie = CookieModel(Prefs.userId, "", webCookie)
@ -74,10 +74,11 @@ object FbCookie {
fun switchUser(cookie: CookieModel?, callback: () -> Unit) { fun switchUser(cookie: CookieModel?, callback: () -> Unit) {
if (cookie == null) { if (cookie == null) {
L.d("Switching User; null cookie")
callback() callback()
return return
} }
L.d("Switching user to $cookie") L.d("Switching User", cookie.toString())
Prefs.userId = cookie.id Prefs.userId = cookie.id
setWebCookie(cookie.cookie, callback) setWebCookie(cookie.cookie, callback)
} }
@ -95,7 +96,7 @@ object FbCookie {
fun switchBackUser(callback: () -> Unit) { fun switchBackUser(callback: () -> Unit) {
if (Prefs.prevId != -1L && Prefs.prevId != Prefs.userId) { if (Prefs.prevId != -1L && Prefs.prevId != Prefs.userId) {
switchUser(Prefs.prevId) { switchUser(Prefs.prevId) {
L.d("Switched from ${Prefs.userId} to ${Prefs.prevId}") L.d("Switch back user", "${Prefs.userId} to ${Prefs.prevId}")
callback() callback()
} }
} else callback() } else callback()

View File

@ -19,7 +19,7 @@ object UsernameFetcher {
name = Jsoup.connect(FbTab.PROFILE.url) name = Jsoup.connect(FbTab.PROFILE.url)
.cookie(FACEBOOK_COM, data.cookie) .cookie(FACEBOOK_COM, data.cookie)
.get().title() .get().title()
L.d("User name found: $name") L.d("User name found", name)
} catch (e: Exception) { } catch (e: Exception) {
L.e(e, "User name fetching failed") L.e(e, "User name fetching failed")
} finally { } finally {

View File

@ -112,10 +112,6 @@ fun SettingsActivity.getAppearancePrefs(): KPrefAdapterBuilder.() -> Unit = {
descRes = R.string.rounded_icons_desc descRes = R.string.rounded_icons_desc
} }
checkbox(R.string.fancy_animations, { Prefs.animate }, { Prefs.animate = it; animate = it }) {
descRes = R.string.fancy_animations_desc
}
checkbox(R.string.tint_nav, { Prefs.tintNavBar }, { checkbox(R.string.tint_nav, { Prefs.tintNavBar }, {
Prefs.tintNavBar = it Prefs.tintNavBar = it
frostNavigationBar() frostNavigationBar()

View File

@ -0,0 +1,25 @@
package com.pitchedapps.frost.settings
import ca.allanwang.kau.kpref.KPrefAdapterBuilder
import com.pitchedapps.frost.R
import com.pitchedapps.frost.SettingsActivity
import com.pitchedapps.frost.utils.Prefs
/**
* Created by Allan Wang on 2017-06-30.
*/
fun SettingsActivity.getBehaviourPrefs(): KPrefAdapterBuilder.() -> Unit = {
checkbox(R.string.fancy_animations, { Prefs.animate }, { Prefs.animate = it; animate = it }) {
descRes = R.string.fancy_animations_desc
}
checkbox(R.string.exit_confirmation, { Prefs.exitConfirmation }, { Prefs.exitConfirmation = it }) {
descRes = R.string.exit_confirmation_desc
}
checkbox(R.string.analytics, { Prefs.analytics }, { Prefs.analytics = it }) {
descRes = R.string.analytics_desc
}
}

View File

@ -18,4 +18,8 @@ fun SettingsActivity.getExperimentalPrefs(): KPrefAdapterBuilder.() -> Unit = {
checkbox(R.string.search, { Prefs.searchBar }, { Prefs.searchBar = it; setResult(MainActivity.REQUEST_SEARCH) }) { checkbox(R.string.search, { Prefs.searchBar }, { Prefs.searchBar = it; setResult(MainActivity.REQUEST_SEARCH) }) {
descRes = R.string.search_desc descRes = R.string.search_desc
} }
checkbox(R.string.verbose_logging, { Prefs.verboseLogging }, { Prefs.verboseLogging = it }) {
descRes = R.string.verbose_logging_desc
}
} }

View File

@ -8,14 +8,36 @@ import timber.log.Timber
/** /**
* Created by Allan Wang on 2017-05-28. * Created by Allan Wang on 2017-05-28.
*
* Logging for frost
*
* To ensure privacy, the following rules are set:
*
* Debug and Error logs must not reveal person info
* Person info logs can be marked as info or verbose
*/ */
object L : TimberLogger("Frost") object L : TimberLogger("Frost") {
/**
* Helper function to separate private info
*/
fun d(tag: String, personal: String?) {
L.d(tag)
if (personal != null) L.i(personal)
}
}
internal class CrashReportingTree : Timber.Tree() { internal class CrashReportingTree : Timber.Tree() {
override fun log(priority: Int, tag: String?, message: String?, t: Throwable?) { override fun log(priority: Int, tag: String?, message: String?, t: Throwable?) {
if (priority == Log.VERBOSE || priority == Log.DEBUG) when (priority) {
return Log.VERBOSE, Log.INFO -> return
if (message != null) Crashlytics.log(priority, tag ?: "Frost", message) Log.DEBUG -> if (!Prefs.verboseLogging) return
}
if (message != null)
if (priority == Log.ERROR)
Crashlytics.log(Log.ERROR, "Frost", message)
else
Crashlytics.log(message)
if (t != null) Crashlytics.logException(t) if (t != null) Crashlytics.logException(t)
} }
} }

View File

@ -91,5 +91,9 @@ object Prefs : KPref() {
var debugPro: Boolean by kpref("debug_pro", false) var debugPro: Boolean by kpref("debug_pro", false)
var searchBar :Boolean by kpref("search_bar", false) var searchBar: Boolean by kpref("search_bar", false)
var verboseLogging: Boolean by kpref("verbose_logging", false)
var analytics: Boolean by kpref("analytics", true)
} }

View File

@ -140,8 +140,7 @@ fun Context.scheduleNotifications(minutes: Long): Boolean {
} }
fun frostAnswers(action: Answers.() -> Unit) { fun frostAnswers(action: Answers.() -> Unit) {
if (BuildConfig.DEBUG) return if (BuildConfig.DEBUG || !Prefs.analytics) return
//TODO add opt out toggle
Answers.getInstance().action() Answers.getInstance().action()
} }

View File

@ -38,7 +38,7 @@ val IS_FROST_PRO: Boolean
get() = (BuildConfig.DEBUG && Prefs.debugPro) || (IAB.helper?.queryInventory()?.getSkuDetails(FROST_PRO) != null) get() = (BuildConfig.DEBUG && Prefs.debugPro) || (IAB.helper?.queryInventory()?.getSkuDetails(FROST_PRO) != null)
private fun Context.checkFromPlay(): Boolean { private fun Context.checkFromPlay(): Boolean {
val isPlay = isFromGooglePlay val isPlay = isFromGooglePlay || BuildConfig.DEBUG
if (!isPlay) materialDialogThemed { if (!isPlay) materialDialogThemed {
title(R.string.uh_oh) title(R.string.uh_oh)
content(R.string.play_store_not_found) content(R.string.play_store_not_found)
@ -63,7 +63,7 @@ fun Activity.openPlayPurchase(key: String, code: Int) {
L.e("IAB error: ${res.message}") L.e("IAB error: ${res.message}")
playStoreErrorDialog() playStoreErrorDialog()
} else if (inv == null) { } else if (inv == null) {
playStoreErrorDialog() playStoreErrorDialog("Empty inventory")
} else { } else {
val donation = inv.getSkuDetails(key) val donation = inv.getSkuDetails(key)
if (donation != null) { if (donation != null) {
@ -73,23 +73,23 @@ fun Activity.openPlayPurchase(key: String, code: Int) {
title(R.string.play_thank_you) title(R.string.play_thank_you)
content(R.string.play_purchased_pro) content(R.string.play_purchased_pro)
positiveText(R.string.kau_ok) positiveText(R.string.kau_ok)
} else playStoreErrorDialog() } else playStoreErrorDialog("Result: ${result.message}")
frostAnswers { frostAnswers {
logPurchase(PurchaseEvent() logPurchase(PurchaseEvent()
.putItemId(key) .putItemId(key)
.putSuccess(result.isSuccess)) .putSuccess(result.isSuccess))
} }
} ?: playStoreErrorDialog() } ?: playStoreErrorDialog("Launch Purchase Flow")
} }
} }
} }
} }
private fun Context.playStoreErrorDialog() { private fun Context.playStoreErrorDialog(s: String = "Play Store Error") {
materialDialogThemed { materialDialogThemed {
title(R.string.uh_oh) title(R.string.uh_oh)
content(R.string.play_store_billing_error) content(R.string.play_store_billing_error)
positiveText(R.string.kau_ok) positiveText(R.string.kau_ok)
} }
L.eThrow("Play Store Error") L.e(Throwable(s), "Play Store Error")
} }

View File

@ -80,20 +80,9 @@ open class FrostWebViewClient(val webCore: FrostWebViewCore) : WebViewClient() {
L.d("Emit $flag") L.d("Emit $flag")
} }
override fun shouldOverrideKeyEvent(view: WebView, event: KeyEvent): Boolean {
L.d("Key event ${event.keyCode}")
return super.shouldOverrideKeyEvent(view, event)
}
override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest?): Boolean { override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest?): Boolean {
L.d("Url Loading ${request?.url?.path}") L.i("Url Loading ${request?.url?.path}")
return super.shouldOverrideUrlLoading(view, request) return super.shouldOverrideUrlLoading(view, request)
} }
override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest?): WebResourceResponse? {
if (request == null || !(request.url.host?.contains(FACEBOOK_COM) ?: false)) return super.shouldInterceptRequest(view, request)
L.v("Url intercept ${request.url.path}")
return super.shouldInterceptRequest(view, request)
}
} }

View File

@ -69,9 +69,10 @@ class FrostWebViewSearch(context: Context, val contract: SearchContract) : WebVi
.subscribe { .subscribe {
content: List<Pair<List<String>, String>> -> content: List<Pair<List<String>, String>> ->
saveResultFrame(content) saveResultFrame(content)
L.d("Search element count ${content.size}")
contract.emitSearchResponse(content.map { contract.emitSearchResponse(content.map {
(texts, href) -> (texts, href) ->
L.d("Search element $texts $href") L.i("Search element $texts $href")
SearchItem(href, texts[0], texts.getOrNull(1)) SearchItem(href, texts[0], texts.getOrNull(1))
}) })
} }
@ -96,10 +97,8 @@ class FrostWebViewSearch(context: Context, val contract: SearchContract) : WebVi
* Sets the input to have our given text, then dispatches the input event so the webpage recognizes it * Sets the input to have our given text, then dispatches the input event so the webpage recognizes it
*/ */
fun query(input: String) { fun query(input: String) {
L.d("Searching attempt for $input") L.d("Searching attempt", input)
JsBuilder().js("var e=document.getElementById('main-search-input');if(e){e.value='$input';var n=new Event('input',{bubbles:!0,cancelable:!0});e.dispatchEvent(n)}else console.log('Input field not found')").build().inject(this) { JsBuilder().js("var e=document.getElementById('main-search-input');if(e){e.value='$input';var n=new Event('input',{bubbles:!0,cancelable:!0});e.dispatchEvent(n)}else console.log('Input field not found')").build().inject(this)
L.d("Searching for $input")
}
} }
/** /**
@ -130,7 +129,7 @@ class FrostWebViewSearch(context: Context, val contract: SearchContract) : WebVi
L.d("Search loaded successfully") L.d("Search loaded successfully")
} }
1 -> { //something is not found in the search view; this is effectively useless 1 -> { //something is not found in the search view; this is effectively useless
L.d("Search subject error; reverting to full overlay") L.eThrow("Search subject error; reverting to full overlay")
searchSubject.onComplete() searchSubject.onComplete()
contract.searchOverlayDispose() contract.searchOverlayDispose()
} }

View File

@ -6,7 +6,6 @@ import android.util.AttributeSet
import android.view.View import android.view.View
import android.webkit.* import android.webkit.*
import ca.allanwang.kau.utils.fadeIn import ca.allanwang.kau.utils.fadeIn
import ca.allanwang.kau.utils.snackbar
import com.pitchedapps.frost.R import com.pitchedapps.frost.R
import com.pitchedapps.frost.dbflow.CookieModel import com.pitchedapps.frost.dbflow.CookieModel
import com.pitchedapps.frost.facebook.FACEBOOK_COM import com.pitchedapps.frost.facebook.FACEBOOK_COM
@ -44,7 +43,7 @@ class LoginWebView @JvmOverloads constructor(
cookieObservable.filter { (_, cookie) -> cookie?.contains(userMatcher) ?: false } cookieObservable.filter { (_, cookie) -> cookie?.contains(userMatcher) ?: false }
.subscribe { .subscribe {
(url, cookie) -> (url, cookie) ->
L.d("Checking cookie for $url\n\t$cookie") L.d("Checking cookie for login", "$url\n\t$cookie")
val id = userMatcher.find(cookie!!)?.groups?.get(1)?.value val id = userMatcher.find(cookie!!)?.groups?.get(1)?.value
if (id != null) { if (id != null) {
try { try {

View File

@ -53,4 +53,28 @@
<string name="play_store_billing_error">Something went wrong. Please try again later.</string> <string name="play_store_billing_error">Something went wrong. Please try again later.</string>
<string name="play_thank_you">Thank you!</string> <string name="play_thank_you">Thank you!</string>
<string name="play_purchased_pro">Thank you for your support! Enjoy the pro version.</string> <string name="play_purchased_pro">Thank you for your support! Enjoy the pro version.</string>
<string name="define_dbflow"></string>
<!-- Author section -->
<string name="library_dbflow_author">Raizlabs</string>
<string name="library_dbflow_authorWebsite">https://www.raizlabs.com/</string>
<!-- Library section -->
<string name="library_dbflow_libraryName">DbFlow</string>
<string name="library_dbflow_libraryDescription">
<![CDATA[
A robust, powerful, and very simple ORM android database library with <b>annotation processing</b>.
<br/><br/>
The library is built on speed, performance, and approachability. It not only eliminates most boiler-plate code for dealing with databases, but also provides a powerful and simple API to manage interactions.
Let DBFlow make SQL code <i>flow</i> like a steady stream so you can focus on writing amazing apps.
]]>
A robust, powerful, and very simple ORM android database library with annotation processing.</string>
<string name="library_dbflow_libraryWebsite">https://github.com/Raizlabs/DBFlow</string>
<string name="library_dbflow_libraryVersion">4.0.4</string>
<!-- OpenSource section -->
<string name="library_dbflow_isOpenSource">true</string>
<string name="library_dbflow_repositoryLink">https://github.com/Raizlabs/DBFlow</string>
<!-- ClassPath for autoDetect section -->
<string name="library_dbflow_classPath">com.raizlabs.android.dbflow</string>
<!-- License section -->
<string name="library_dbflow_licenseId">mit</string>
</resources> </resources>

View File

@ -7,6 +7,14 @@
<string name="newsfeed">News Feed</string> <string name="newsfeed">News Feed</string>
<string name="newsfeed_desc">Define what items appear in the newsfeed</string> <string name="newsfeed_desc">Define what items appear in the newsfeed</string>
<string name="behaviour">Behaviour</string>
<string name="behaviour_desc">Define how the app interacts in certain settings</string>
<string name="exit_confirmation">Exit Confirmation</string>
<string name="exit_confirmation_desc">Show confirmation dialog before exiting the app</string>
<string name="analytics">Analytics</string>
<string name="analytics_desc">Enable anonymous analytics and crash reports to help improve the app. No personal information is ever exposed.</string>
<!--themes--> <!--themes-->
<string name="theme">Theme</string> <string name="theme">Theme</string>
<string name="text_color">Text Color</string> <string name="text_color">Text Color</string>
@ -44,5 +52,7 @@
<string name="experimental_disclaimer_info">Experimental features may be unstable and may never make it to production. Use at your own risk, send feedback, and feel free to disable them if they don\'t work well.</string> <string name="experimental_disclaimer_info">Experimental features may be unstable and may never make it to production. Use at your own risk, send feedback, and feel free to disable them if they don\'t work well.</string>
<string name="search">Search Type</string> <string name="search">Search Type</string>
<string name="search_desc">Enable the search bar instead of a search overlay</string> <string name="search_desc">Enable the search bar instead of a search overlay</string>
<string name="verbose_logging">Verbose Logging</string>
<string name="verbose_logging_desc">Enable verbose logging to help with crash reports. Logging will only be sent once an error is encountered, so repeat the issue to notify the dev.\nAnalytics must also be enabled under the behaviour settings.</string>
</resources> </resources>