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:
parent
a425190308
commit
7267064d8a
@ -24,6 +24,12 @@ class AboutActivity : AboutActivityBase(R.string::class.java, configBuilder = {
|
||||
}) {
|
||||
|
||||
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
|
||||
* Give more emphasis on the unique libs!
|
||||
@ -39,7 +45,7 @@ class AboutActivity : AboutActivityBase(R.string::class.java, configBuilder = {
|
||||
"recyclerview_v7",
|
||||
"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}") }
|
||||
return l
|
||||
}
|
||||
|
@ -14,7 +14,6 @@ import android.support.v7.widget.Toolbar
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import ca.allanwang.kau.changelog.showChangelog
|
||||
import ca.allanwang.kau.logging.KL
|
||||
import ca.allanwang.kau.searchview.SearchItem
|
||||
import ca.allanwang.kau.searchview.SearchView
|
||||
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) {
|
||||
textObserver = {
|
||||
observable, _ ->
|
||||
observable.observeOn(AndroidSchedulers.mainThread()).subscribe {
|
||||
L.d("Input $it")
|
||||
hiddenSearchView?.query(it)
|
||||
}
|
||||
observable.observeOn(AndroidSchedulers.mainThread()).subscribe { hiddenSearchView?.query(it) }
|
||||
}
|
||||
foregroundColor = Prefs.textColor
|
||||
backgroundColor = Prefs.bgColor
|
||||
|
@ -12,10 +12,7 @@ import ca.allanwang.kau.utils.*
|
||||
import ca.allanwang.kau.views.RippleCanvas
|
||||
import com.mikepenz.community_material_typeface_library.CommunityMaterial
|
||||
import com.mikepenz.google_material_typeface_library.GoogleMaterial
|
||||
import com.pitchedapps.frost.settings.getAppearancePrefs
|
||||
import com.pitchedapps.frost.settings.getExperimentalPrefs
|
||||
import com.pitchedapps.frost.settings.getFeedPrefs
|
||||
import com.pitchedapps.frost.settings.getNotificationPrefs
|
||||
import com.pitchedapps.frost.settings.*
|
||||
import com.pitchedapps.frost.utils.*
|
||||
import com.pitchedapps.frost.utils.iab.IS_FROST_PRO
|
||||
import com.pitchedapps.frost.utils.iab.openPlayProPurchase
|
||||
@ -37,6 +34,11 @@ class SettingsActivity : KPrefActivity() {
|
||||
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
|
||||
|
@ -15,11 +15,10 @@ class StartActivity : AppCompatActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
L.d("Load cookies ${System.currentTimeMillis()}")
|
||||
FbCookie.switchBackUser {
|
||||
loadFbCookiesAsync {
|
||||
cookies ->
|
||||
L.d("Cookies loaded ${System.currentTimeMillis()} $cookies")
|
||||
L.d("Cookies loaded ${System.currentTimeMillis()}", cookies.toString())
|
||||
if (cookies.isNotEmpty())
|
||||
launchNewTask(if (Prefs.userId != -1L) MainActivity::class.java else SelectorActivity::class.java, ArrayList(cookies))
|
||||
else
|
||||
|
@ -51,14 +51,14 @@ fun loadFbCookiesSync(): List<CookieModel> = (select from CookieModel::class).or
|
||||
|
||||
fun saveFbCookie(cookie: CookieModel, callback: (() -> Unit)? = null) {
|
||||
cookie.async save {
|
||||
L.d("Fb cookie $cookie saved")
|
||||
L.d("Fb cookie saved", cookie.toString())
|
||||
callback?.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
fun removeCookie(id: Long) {
|
||||
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)
|
||||
.cookie(FACEBOOK_COM, cookie)
|
||||
.get().title()
|
||||
L.d("User name found: $result")
|
||||
L.d("Fetch username found", result)
|
||||
} catch (e: Exception) {
|
||||
L.e("User name fetching failed: ${e.message}")
|
||||
L.e(e, "Fetch username failed")
|
||||
} finally {
|
||||
if (result.isBlank() && (name?.isNotBlank() ?: false)) {
|
||||
callback(name!!)
|
||||
|
@ -25,7 +25,7 @@ fun lastNotificationTime(id: Long): Long = (select from NotificationModel::class
|
||||
|
||||
fun saveNotificationTime(notificationModel: NotificationModel, callback: (() -> Unit)? = null) {
|
||||
notificationModel.async save {
|
||||
L.d("Fb notification $notificationModel saved")
|
||||
L.d("Fb notification model saved", notificationModel.toString())
|
||||
callback?.invoke()
|
||||
}
|
||||
}
|
@ -26,12 +26,12 @@ object FbCookie {
|
||||
callback?.invoke()
|
||||
return@removeAllCookies
|
||||
}
|
||||
L.d("Setting cookie to $cookie")
|
||||
L.d("Setting cookie", cookie)
|
||||
val cookies = cookie.split(";").map { Pair(it, SingleSubject.create<Boolean>()) }
|
||||
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({
|
||||
callback?.invoke()
|
||||
L.d("Cookies set: $webCookie")
|
||||
L.d("Cookies set", webCookie)
|
||||
flush()
|
||||
})
|
||||
})
|
||||
@ -39,7 +39,7 @@ object FbCookie {
|
||||
}
|
||||
|
||||
operator fun invoke() {
|
||||
L.d("User ${Prefs.userId}")
|
||||
L.d("FbCookie Invoke User", Prefs.userId.toString())
|
||||
with(CookieManager.getInstance()) {
|
||||
setAcceptCookie(true)
|
||||
}
|
||||
@ -51,7 +51,7 @@ object FbCookie {
|
||||
}
|
||||
|
||||
fun save(id: Long) {
|
||||
L.d("New cookie found for $id")
|
||||
L.d("New cookie found", id.toString())
|
||||
Prefs.userId = id
|
||||
CookieManager.getInstance().flush()
|
||||
val cookie = CookieModel(Prefs.userId, "", webCookie)
|
||||
@ -74,10 +74,11 @@ object FbCookie {
|
||||
|
||||
fun switchUser(cookie: CookieModel?, callback: () -> Unit) {
|
||||
if (cookie == null) {
|
||||
L.d("Switching User; null cookie")
|
||||
callback()
|
||||
return
|
||||
}
|
||||
L.d("Switching user to $cookie")
|
||||
L.d("Switching User", cookie.toString())
|
||||
Prefs.userId = cookie.id
|
||||
setWebCookie(cookie.cookie, callback)
|
||||
}
|
||||
@ -95,7 +96,7 @@ object FbCookie {
|
||||
fun switchBackUser(callback: () -> Unit) {
|
||||
if (Prefs.prevId != -1L && Prefs.prevId != Prefs.userId) {
|
||||
switchUser(Prefs.prevId) {
|
||||
L.d("Switched from ${Prefs.userId} to ${Prefs.prevId}")
|
||||
L.d("Switch back user", "${Prefs.userId} to ${Prefs.prevId}")
|
||||
callback()
|
||||
}
|
||||
} else callback()
|
||||
|
@ -19,7 +19,7 @@ object UsernameFetcher {
|
||||
name = Jsoup.connect(FbTab.PROFILE.url)
|
||||
.cookie(FACEBOOK_COM, data.cookie)
|
||||
.get().title()
|
||||
L.d("User name found: $name")
|
||||
L.d("User name found", name)
|
||||
} catch (e: Exception) {
|
||||
L.e(e, "User name fetching failed")
|
||||
} finally {
|
||||
|
@ -112,10 +112,6 @@ fun SettingsActivity.getAppearancePrefs(): KPrefAdapterBuilder.() -> Unit = {
|
||||
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 }, {
|
||||
Prefs.tintNavBar = it
|
||||
frostNavigationBar()
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
}
|
@ -18,4 +18,8 @@ fun SettingsActivity.getExperimentalPrefs(): KPrefAdapterBuilder.() -> Unit = {
|
||||
checkbox(R.string.search, { Prefs.searchBar }, { Prefs.searchBar = it; setResult(MainActivity.REQUEST_SEARCH) }) {
|
||||
descRes = R.string.search_desc
|
||||
}
|
||||
|
||||
checkbox(R.string.verbose_logging, { Prefs.verboseLogging }, { Prefs.verboseLogging = it }) {
|
||||
descRes = R.string.verbose_logging_desc
|
||||
}
|
||||
}
|
@ -8,14 +8,36 @@ import timber.log.Timber
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
override fun log(priority: Int, tag: String?, message: String?, t: Throwable?) {
|
||||
if (priority == Log.VERBOSE || priority == Log.DEBUG)
|
||||
return
|
||||
if (message != null) Crashlytics.log(priority, tag ?: "Frost", message)
|
||||
when (priority) {
|
||||
Log.VERBOSE, Log.INFO -> return
|
||||
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)
|
||||
}
|
||||
}
|
@ -91,5 +91,9 @@ object Prefs : KPref() {
|
||||
|
||||
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)
|
||||
}
|
||||
|
@ -140,8 +140,7 @@ fun Context.scheduleNotifications(minutes: Long): Boolean {
|
||||
}
|
||||
|
||||
fun frostAnswers(action: Answers.() -> Unit) {
|
||||
if (BuildConfig.DEBUG) return
|
||||
//TODO add opt out toggle
|
||||
if (BuildConfig.DEBUG || !Prefs.analytics) return
|
||||
Answers.getInstance().action()
|
||||
}
|
||||
|
||||
|
@ -38,7 +38,7 @@ val IS_FROST_PRO: Boolean
|
||||
get() = (BuildConfig.DEBUG && Prefs.debugPro) || (IAB.helper?.queryInventory()?.getSkuDetails(FROST_PRO) != null)
|
||||
|
||||
private fun Context.checkFromPlay(): Boolean {
|
||||
val isPlay = isFromGooglePlay
|
||||
val isPlay = isFromGooglePlay || BuildConfig.DEBUG
|
||||
if (!isPlay) materialDialogThemed {
|
||||
title(R.string.uh_oh)
|
||||
content(R.string.play_store_not_found)
|
||||
@ -63,7 +63,7 @@ fun Activity.openPlayPurchase(key: String, code: Int) {
|
||||
L.e("IAB error: ${res.message}")
|
||||
playStoreErrorDialog()
|
||||
} else if (inv == null) {
|
||||
playStoreErrorDialog()
|
||||
playStoreErrorDialog("Empty inventory")
|
||||
} else {
|
||||
val donation = inv.getSkuDetails(key)
|
||||
if (donation != null) {
|
||||
@ -73,23 +73,23 @@ fun Activity.openPlayPurchase(key: String, code: Int) {
|
||||
title(R.string.play_thank_you)
|
||||
content(R.string.play_purchased_pro)
|
||||
positiveText(R.string.kau_ok)
|
||||
} else playStoreErrorDialog()
|
||||
} else playStoreErrorDialog("Result: ${result.message}")
|
||||
frostAnswers {
|
||||
logPurchase(PurchaseEvent()
|
||||
.putItemId(key)
|
||||
.putSuccess(result.isSuccess))
|
||||
}
|
||||
} ?: playStoreErrorDialog()
|
||||
} ?: playStoreErrorDialog("Launch Purchase Flow")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Context.playStoreErrorDialog() {
|
||||
private fun Context.playStoreErrorDialog(s: String = "Play Store Error") {
|
||||
materialDialogThemed {
|
||||
title(R.string.uh_oh)
|
||||
content(R.string.play_store_billing_error)
|
||||
positiveText(R.string.kau_ok)
|
||||
}
|
||||
L.eThrow("Play Store Error")
|
||||
L.e(Throwable(s), "Play Store Error")
|
||||
}
|
@ -80,20 +80,9 @@ open class FrostWebViewClient(val webCore: FrostWebViewCore) : WebViewClient() {
|
||||
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 {
|
||||
L.d("Url Loading ${request?.url?.path}")
|
||||
L.i("Url Loading ${request?.url?.path}")
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
@ -69,9 +69,10 @@ class FrostWebViewSearch(context: Context, val contract: SearchContract) : WebVi
|
||||
.subscribe {
|
||||
content: List<Pair<List<String>, String>> ->
|
||||
saveResultFrame(content)
|
||||
L.d("Search element count ${content.size}")
|
||||
contract.emitSearchResponse(content.map {
|
||||
(texts, href) ->
|
||||
L.d("Search element $texts $href")
|
||||
L.i("Search element $texts $href")
|
||||
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
|
||||
*/
|
||||
fun query(input: String) {
|
||||
L.d("Searching attempt for $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) {
|
||||
L.d("Searching 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)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -130,7 +129,7 @@ class FrostWebViewSearch(context: Context, val contract: SearchContract) : WebVi
|
||||
L.d("Search loaded successfully")
|
||||
}
|
||||
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()
|
||||
contract.searchOverlayDispose()
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.webkit.*
|
||||
import ca.allanwang.kau.utils.fadeIn
|
||||
import ca.allanwang.kau.utils.snackbar
|
||||
import com.pitchedapps.frost.R
|
||||
import com.pitchedapps.frost.dbflow.CookieModel
|
||||
import com.pitchedapps.frost.facebook.FACEBOOK_COM
|
||||
@ -44,7 +43,7 @@ class LoginWebView @JvmOverloads constructor(
|
||||
cookieObservable.filter { (_, cookie) -> cookie?.contains(userMatcher) ?: false }
|
||||
.subscribe {
|
||||
(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
|
||||
if (id != null) {
|
||||
try {
|
||||
|
@ -53,4 +53,28 @@
|
||||
<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_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>
|
||||
|
@ -7,6 +7,14 @@
|
||||
<string name="newsfeed">News Feed</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-->
|
||||
<string name="theme">Theme</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="search">Search Type</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>
|
Loading…
Reference in New Issue
Block a user