mirror of
https://github.com/TwidereProject/Twidere-Android
synced 2025-02-12 09:40:50 +01:00
trying to improve account manager
This commit is contained in:
parent
4d13123caa
commit
2f040f3aaf
@ -2,17 +2,9 @@ package org.mariotaku.twidere.model.util;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
import android.accounts.AccountManagerCallback;
|
||||
import android.accounts.AccountManagerFuture;
|
||||
import android.accounts.AuthenticatorException;
|
||||
import android.accounts.OperationCanceledException;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.mariotaku.twidere.R;
|
||||
import org.mariotaku.twidere.annotation.AccountType;
|
||||
@ -23,11 +15,8 @@ import org.mariotaku.twidere.model.AccountDetails;
|
||||
import org.mariotaku.twidere.model.UserKey;
|
||||
import org.mariotaku.twidere.model.account.cred.Credentials;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.mariotaku.twidere.TwidereConstants.ACCOUNT_AUTH_TOKEN_TYPE;
|
||||
import static org.mariotaku.twidere.TwidereConstants.ACCOUNT_TYPE;
|
||||
import static org.mariotaku.twidere.TwidereConstants.ACCOUNT_USER_DATA_ACTIVATED;
|
||||
import static org.mariotaku.twidere.TwidereConstants.ACCOUNT_USER_DATA_COLOR;
|
||||
@ -171,23 +160,6 @@ public class AccountUtils {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public static AccountManagerFuture<Account> renameAccount(AccountManager am, Account oldAccount, String newName) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
return AccountManagerSupportL.renameAccount(am, oldAccount, newName, null, null);
|
||||
}
|
||||
final Account newAccount = new Account(newName, oldAccount.type);
|
||||
if (am.addAccountExplicitly(newAccount, null, null)) {
|
||||
for (String key : ACCOUNT_USER_DATA_KEYS) {
|
||||
am.setUserData(newAccount, key, am.getUserData(oldAccount, key));
|
||||
}
|
||||
am.setAuthToken(newAccount, ACCOUNT_AUTH_TOKEN_TYPE,
|
||||
am.peekAuthToken(oldAccount, ACCOUNT_AUTH_TOKEN_TYPE));
|
||||
@SuppressWarnings("deprecation")
|
||||
final AccountManagerFuture<Boolean> booleanFuture = am.removeAccount(oldAccount, null, null);
|
||||
return new AccountFuture(newAccount, booleanFuture);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean hasAccountPermission(@NonNull AccountManager am) {
|
||||
try {
|
||||
@ -198,71 +170,6 @@ public class AccountUtils {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean hasInvalidAccount(@NonNull AccountManager am) {
|
||||
for (Account account : getAccounts(am)) {
|
||||
if (!isAccountValid(am, account)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean isAccountValid(@NonNull AccountManager am, Account account) {
|
||||
if (TextUtils.isEmpty(am.peekAuthToken(account, ACCOUNT_AUTH_TOKEN_TYPE))) return false;
|
||||
if (TextUtils.isEmpty(am.getUserData(account, ACCOUNT_USER_DATA_KEY))) return false;
|
||||
if (TextUtils.isEmpty(am.getUserData(account, ACCOUNT_USER_DATA_USER))) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static class AccountFuture implements AccountManagerFuture<Account> {
|
||||
|
||||
private final Account account;
|
||||
private final AccountManagerFuture<Boolean> booleanFuture;
|
||||
|
||||
AccountFuture(Account account, AccountManagerFuture<Boolean> booleanFuture) {
|
||||
this.account = account;
|
||||
this.booleanFuture = booleanFuture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cancel(boolean mayInterruptIfRunning) {
|
||||
return booleanFuture.cancel(mayInterruptIfRunning);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return booleanFuture.isCancelled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDone() {
|
||||
return booleanFuture.isDone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Account getResult() throws OperationCanceledException, IOException, AuthenticatorException {
|
||||
if (booleanFuture.getResult()) {
|
||||
return account;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Account getResult(long timeout, TimeUnit unit) throws OperationCanceledException, IOException, AuthenticatorException {
|
||||
if (booleanFuture.getResult(timeout, unit)) {
|
||||
return account;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static class AccountManagerSupportL {
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
static AccountManagerFuture<Account> renameAccount(AccountManager am, Account account,
|
||||
String newName,
|
||||
AccountManagerCallback<Account> callback,
|
||||
Handler handler) {
|
||||
return am.renameAccount(account, newName, callback, handler);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import android.support.v4.app.FragmentActivity
|
||||
import android.support.v7.app.AlertDialog
|
||||
import org.mariotaku.twidere.R
|
||||
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_INTENT
|
||||
import org.mariotaku.twidere.extension.model.isAccountValid
|
||||
import org.mariotaku.twidere.fragment.BaseDialogFragment
|
||||
import org.mariotaku.twidere.model.util.AccountUtils
|
||||
import org.mariotaku.twidere.util.support.removeAccountSupport
|
||||
@ -32,7 +33,7 @@ class InvalidAccountAlertActivity : FragmentActivity() {
|
||||
builder.setMessage(R.string.message_error_invalid_account)
|
||||
builder.setPositiveButton(android.R.string.ok) { dialog, which ->
|
||||
val am = AccountManager.get(context)
|
||||
AccountUtils.getAccounts(am).filter { !AccountUtils.isAccountValid(am, it) }.forEach { account ->
|
||||
AccountUtils.getAccounts(am).filter { !am.isAccountValid(it) }.forEach { account ->
|
||||
am.removeAccountSupport(account)
|
||||
}
|
||||
val intent = activity.intent.getParcelableExtra<Intent>(EXTRA_INTENT)
|
||||
|
@ -26,6 +26,7 @@ import android.widget.Toast
|
||||
import org.mariotaku.twidere.BuildConfig
|
||||
import org.mariotaku.twidere.R
|
||||
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_INTENT
|
||||
import org.mariotaku.twidere.extension.model.hasInvalidAccount
|
||||
import org.mariotaku.twidere.model.util.AccountUtils
|
||||
import org.mariotaku.twidere.util.StrictModeUtils
|
||||
import org.mariotaku.twidere.util.Utils
|
||||
@ -43,7 +44,7 @@ open class MainActivity : BaseActivity() {
|
||||
startActivity(Intent(this, IncompatibleAlertActivity::class.java))
|
||||
} else if (!AccountUtils.hasAccountPermission(am)) {
|
||||
Toast.makeText(this, R.string.message_toast_no_account_permission, Toast.LENGTH_SHORT).show()
|
||||
} else if (AccountUtils.hasInvalidAccount(am)) {
|
||||
} else if (am.hasInvalidAccount()) {
|
||||
val intent = Intent(this, InvalidAccountAlertActivity::class.java)
|
||||
intent.putExtra(EXTRA_INTENT, Intent(this, HomeActivity::class.java))
|
||||
startActivity(intent)
|
||||
|
@ -1,8 +1,13 @@
|
||||
package org.mariotaku.twidere.extension.model
|
||||
|
||||
import android.accounts.Account
|
||||
import android.accounts.AccountManager
|
||||
import android.accounts.*
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.support.annotation.RequiresApi
|
||||
import android.text.TextUtils
|
||||
import com.bluelinelabs.logansquare.LoganSquare
|
||||
import nl.komponents.kovenant.deferred
|
||||
import org.mariotaku.ktextension.HexColorFormat
|
||||
import org.mariotaku.ktextension.toHexColor
|
||||
import org.mariotaku.ktextension.toInt
|
||||
@ -17,24 +22,28 @@ import org.mariotaku.twidere.model.account.cred.BasicCredentials
|
||||
import org.mariotaku.twidere.model.account.cred.Credentials
|
||||
import org.mariotaku.twidere.model.account.cred.EmptyCredentials
|
||||
import org.mariotaku.twidere.model.account.cred.OAuthCredentials
|
||||
import org.mariotaku.twidere.model.util.AccountUtils
|
||||
import org.mariotaku.twidere.model.util.AccountUtils.ACCOUNT_USER_DATA_KEYS
|
||||
import org.mariotaku.twidere.util.ParseUtils
|
||||
import java.io.IOException
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
|
||||
fun Account.getCredentials(am: AccountManager): Credentials {
|
||||
val authToken = am.peekAuthToken(this, ACCOUNT_AUTH_TOKEN_TYPE) ?: run {
|
||||
val authToken = AccountDataQueue.peekAuthToken(am, this, ACCOUNT_AUTH_TOKEN_TYPE) ?: run {
|
||||
for (i in 0 until 5) {
|
||||
Thread.sleep(50L)
|
||||
val token = am.peekAuthToken(this, ACCOUNT_AUTH_TOKEN_TYPE)
|
||||
val token = AccountDataQueue.peekAuthToken(am, this, ACCOUNT_AUTH_TOKEN_TYPE)
|
||||
if (token != null) return@run token
|
||||
}
|
||||
throw NullPointerException("AuthToken is null for ${this}")
|
||||
}
|
||||
return@run null
|
||||
} ?: throw NullPointerException("AuthToken is null for ${this}")
|
||||
return parseCredentials(authToken, getCredentialsType(am))
|
||||
}
|
||||
|
||||
@Credentials.Type
|
||||
fun Account.getCredentialsType(am: AccountManager): String {
|
||||
return am.getUserData(this, ACCOUNT_USER_DATA_CREDS_TYPE) ?: Credentials.Type.OAUTH
|
||||
return AccountDataQueue.getUserData(am, this, ACCOUNT_USER_DATA_CREDS_TYPE) ?: Credentials.Type.OAUTH
|
||||
}
|
||||
|
||||
fun Account.getAccountKey(am: AccountManager): UserKey {
|
||||
@ -57,15 +66,15 @@ fun Account.setAccountUser(am: AccountManager, user: ParcelableUser) {
|
||||
|
||||
@android.support.annotation.ColorInt
|
||||
fun Account.getColor(am: AccountManager): Int {
|
||||
return ParseUtils.parseColor(am.getUserData(this, ACCOUNT_USER_DATA_COLOR), 0)
|
||||
return ParseUtils.parseColor(AccountDataQueue.getUserData(am, this, ACCOUNT_USER_DATA_COLOR), 0)
|
||||
}
|
||||
|
||||
fun Account.getPosition(am: AccountManager): Int {
|
||||
return am.getUserData(this, ACCOUNT_USER_DATA_POSITION).toInt(-1)
|
||||
return AccountDataQueue.getUserData(am, this, ACCOUNT_USER_DATA_POSITION).toInt(-1)
|
||||
}
|
||||
|
||||
fun Account.getAccountExtras(am: AccountManager): AccountExtras? {
|
||||
val json = am.getUserData(this, ACCOUNT_USER_DATA_EXTRAS) ?: return null
|
||||
val json = AccountDataQueue.getUserData(am, this, ACCOUNT_USER_DATA_EXTRAS) ?: return null
|
||||
when (getAccountType(am)) {
|
||||
AccountType.TWITTER -> {
|
||||
return LoganSquare.parse(json, TwitterAccountExtras::class.java)
|
||||
@ -79,11 +88,11 @@ fun Account.getAccountExtras(am: AccountManager): AccountExtras? {
|
||||
|
||||
@AccountType
|
||||
fun Account.getAccountType(am: AccountManager): String {
|
||||
return am.getUserData(this, ACCOUNT_USER_DATA_TYPE) ?: AccountType.TWITTER
|
||||
return AccountDataQueue.getUserData(am, this, ACCOUNT_USER_DATA_TYPE) ?: AccountType.TWITTER
|
||||
}
|
||||
|
||||
fun Account.isActivated(am: AccountManager): Boolean {
|
||||
return am.getUserData(this, ACCOUNT_USER_DATA_ACTIVATED)?.toBoolean() ?: true
|
||||
return AccountDataQueue.getUserData(am, this, ACCOUNT_USER_DATA_ACTIVATED)?.toBoolean() ?: true
|
||||
}
|
||||
|
||||
fun Account.setActivated(am: AccountManager, activated: Boolean) {
|
||||
@ -98,12 +107,43 @@ fun Account.setPosition(am: AccountManager, position: Int) {
|
||||
am.setUserData(this, ACCOUNT_USER_DATA_POSITION, position.toString())
|
||||
}
|
||||
|
||||
fun AccountManager.hasInvalidAccount(): Boolean {
|
||||
for (account in AccountUtils.getAccounts(this)) {
|
||||
if (!isAccountValid(account)) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun AccountManager.isAccountValid(account: Account): Boolean {
|
||||
if (TextUtils.isEmpty(AccountDataQueue.peekAuthToken(this, account, ACCOUNT_AUTH_TOKEN_TYPE))) return false
|
||||
if (TextUtils.isEmpty(AccountDataQueue.getUserData(this, account, ACCOUNT_USER_DATA_KEY))) return false
|
||||
if (TextUtils.isEmpty(AccountDataQueue.getUserData(this, account, ACCOUNT_USER_DATA_USER))) return false
|
||||
return true
|
||||
}
|
||||
|
||||
fun AccountManager.renameTwidereAccount(oldAccount: Account, newName: String): AccountManagerFuture<Account>? {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
return AccountExtensionFunctionsL.renameAccount(this, oldAccount, newName, null, null)
|
||||
}
|
||||
val newAccount = Account(newName, oldAccount.type)
|
||||
if (addAccountExplicitly(newAccount, null, null)) {
|
||||
for (key in ACCOUNT_USER_DATA_KEYS) {
|
||||
setUserData(newAccount, key, getUserData(oldAccount, key))
|
||||
}
|
||||
setAuthToken(newAccount, ACCOUNT_AUTH_TOKEN_TYPE,
|
||||
peekAuthToken(oldAccount, ACCOUNT_AUTH_TOKEN_TYPE))
|
||||
val booleanFuture = removeAccount(oldAccount, null, null)
|
||||
return AccountFuture(newAccount, booleanFuture)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun AccountManager.getNonNullUserData(account: Account, key: String): String {
|
||||
return getUserData(account, key) ?: run {
|
||||
return AccountDataQueue.getUserData(this, account, key) ?: run {
|
||||
// Rare case, assume account manager service is not running
|
||||
for (i in 0 until 5) {
|
||||
Thread.sleep(50L)
|
||||
val data = getUserData(account, key)
|
||||
val data = AccountDataQueue.getUserData(this, account, key)
|
||||
if (data != null) return data
|
||||
}
|
||||
throw NullPointerException("NonNull userData $key is null for $account")
|
||||
@ -117,4 +157,75 @@ private fun parseCredentials(authToken: String, @Credentials.Type authType: Stri
|
||||
Credentials.Type.EMPTY -> return LoganSquare.parse(authToken, EmptyCredentials::class.java)
|
||||
}
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
internal object AccountDataQueue {
|
||||
val handler = Handler(Looper.getMainLooper())
|
||||
|
||||
fun getUserData(manager: AccountManager, account: Account, key: String): String? {
|
||||
if (Thread.currentThread() == Looper.getMainLooper().thread) {
|
||||
return manager.getUserData(account, key)
|
||||
}
|
||||
val deferred = deferred<String?, Exception>()
|
||||
handler.post {
|
||||
deferred.resolve(manager.getUserData(account, key))
|
||||
}
|
||||
return deferred.promise.get()
|
||||
}
|
||||
|
||||
fun peekAuthToken(manager: AccountManager, account: Account, authTokenType: String): String? {
|
||||
if (Thread.currentThread() == Looper.getMainLooper().thread) {
|
||||
return manager.peekAuthToken(account, authTokenType)
|
||||
}
|
||||
val deferred = deferred<String?, Exception>()
|
||||
handler.post {
|
||||
deferred.resolve(manager.peekAuthToken(account, authTokenType))
|
||||
}
|
||||
return deferred.promise.get()
|
||||
}
|
||||
}
|
||||
|
||||
private object AccountExtensionFunctionsL {
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
internal fun renameAccount(am: AccountManager, account: Account,
|
||||
newName: String,
|
||||
callback: AccountManagerCallback<Account>?,
|
||||
handler: Handler?): AccountManagerFuture<Account> {
|
||||
return am.renameAccount(account, newName, callback, handler)
|
||||
}
|
||||
}
|
||||
|
||||
private class AccountFuture internal constructor(
|
||||
private val account: Account,
|
||||
private val booleanFuture: AccountManagerFuture<Boolean>
|
||||
) : AccountManagerFuture<Account> {
|
||||
|
||||
override fun cancel(mayInterruptIfRunning: Boolean): Boolean {
|
||||
return booleanFuture.cancel(mayInterruptIfRunning)
|
||||
}
|
||||
|
||||
override fun isCancelled(): Boolean {
|
||||
return booleanFuture.isCancelled
|
||||
}
|
||||
|
||||
override fun isDone(): Boolean {
|
||||
return booleanFuture.isDone
|
||||
}
|
||||
|
||||
@Throws(OperationCanceledException::class, IOException::class, AuthenticatorException::class)
|
||||
override fun getResult(): Account? {
|
||||
if (booleanFuture.result) {
|
||||
return account
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
@Throws(OperationCanceledException::class, IOException::class, AuthenticatorException::class)
|
||||
override fun getResult(timeout: Long, unit: TimeUnit): Account? {
|
||||
if (booleanFuture.getResult(timeout, unit)) {
|
||||
return account
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
@ -36,11 +36,8 @@ import android.graphics.RectF
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.support.design.widget.NavigationView
|
||||
import android.support.v4.app.LoaderManager.LoaderCallbacks
|
||||
import android.support.v4.content.AsyncTaskLoader
|
||||
import android.support.v4.content.Loader
|
||||
import android.support.v4.view.MenuItemCompat
|
||||
import android.support.v4.view.ViewPager
|
||||
@ -723,7 +720,7 @@ class AccountsDashboardFragment : BaseFragment(), LoaderCallbacks<AccountsInfo>,
|
||||
val draftsCount: Int
|
||||
)
|
||||
|
||||
class AccountsInfoLoader(context: Context) : AsyncTaskLoader<AccountsInfo>(context) {
|
||||
class AccountsInfoLoader(context: Context) : Loader<AccountsInfo>(context) {
|
||||
private var contentObserver: ContentObserver? = null
|
||||
private var accountListener: OnAccountsUpdateListener? = null
|
||||
|
||||
@ -733,10 +730,10 @@ class AccountsDashboardFragment : BaseFragment(), LoaderCallbacks<AccountsInfo>,
|
||||
firstLoad = true
|
||||
}
|
||||
|
||||
override fun loadInBackground(): AccountsInfo {
|
||||
override fun onForceLoad() {
|
||||
val accounts = AccountUtils.getAllAccountDetails(AccountManager.get(context), true)
|
||||
val draftsCount = DataStoreUtils.queryCount(context, Drafts.CONTENT_URI_UNSENT, null, null)
|
||||
return AccountsInfo(accounts, draftsCount)
|
||||
deliverResult(AccountsInfo(accounts, draftsCount))
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -15,6 +15,7 @@ import org.mariotaku.ktextension.set
|
||||
import org.mariotaku.twidere.R
|
||||
import org.mariotaku.twidere.extension.model.getAccountKey
|
||||
import org.mariotaku.twidere.extension.model.getAccountUser
|
||||
import org.mariotaku.twidere.extension.model.renameTwidereAccount
|
||||
import org.mariotaku.twidere.model.util.AccountUtils
|
||||
import org.mariotaku.twidere.preference.iface.IDialogPreference
|
||||
import org.mariotaku.twidere.util.generateAccountName
|
||||
@ -72,7 +73,7 @@ class RandomizeAccountNamePreference @JvmOverloads constructor(
|
||||
do {
|
||||
newName = UUID.randomUUID().toString()
|
||||
} while (usedNames.contains(newName))
|
||||
AccountUtils.renameAccount(am, oldAccount, newName)
|
||||
am.renameTwidereAccount(oldAccount, newName)
|
||||
usedNames.add(newName)
|
||||
}
|
||||
} else {
|
||||
@ -80,7 +81,7 @@ class RandomizeAccountNamePreference @JvmOverloads constructor(
|
||||
val accountKey = oldAccount.getAccountKey(am)
|
||||
val accountUser = oldAccount.getAccountUser(am)
|
||||
val newName = generateAccountName(accountUser.screen_name, accountKey.host)
|
||||
AccountUtils.renameAccount(am, oldAccount, newName)
|
||||
am.renameTwidereAccount(oldAccount, newName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -252,18 +252,17 @@ class StatusViewHolder(private val adapter: IStatusesAdapter<*>, itemView: View)
|
||||
quotedMediaLabel.visibility = View.GONE
|
||||
}
|
||||
|
||||
val quoteHint = if (!quoteContentAvailable) {
|
||||
quotedTextView.text = if (!quoteContentAvailable) {
|
||||
// Display 'not available' label
|
||||
context.getString(R.string.label_status_not_available)
|
||||
SpannableString.valueOf(context.getString(R.string.label_status_not_available)).apply {
|
||||
setSpan(ForegroundColorSpan(ThemeUtils.getColorFromAttribute(context,
|
||||
android.R.attr.textColorTertiary, textView.currentTextColor)), 0,
|
||||
length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
}
|
||||
} else {
|
||||
// Display 'original status' label
|
||||
context.getString(R.string.label_original_status)
|
||||
}
|
||||
quotedTextView.text = SpannableString.valueOf(quoteHint).apply {
|
||||
setSpan(ForegroundColorSpan(ThemeUtils.getColorFromAttribute(context,
|
||||
android.R.attr.textColorTertiary, textView.currentTextColor)), 0,
|
||||
length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
}
|
||||
|
||||
quotedView.drawStart(ThemeUtils.getColorFromAttribute(context, R.attr.quoteIndicatorBackgroundColor, 0))
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user