fix account switching (#4636)
closes #4631 closes #4629 and other weirdness introduced in Tusky 26.1. I did a lot of testing on 2 physical devices and multiple emulators. It definitely is better than before, but probably still not perfect.
This commit is contained in:
parent
74d479c3dc
commit
31e4f08966
|
@ -42,6 +42,7 @@
|
|||
android:name=".MainActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden|screenLayout|smallestScreenSize"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTask"
|
||||
android:theme="@style/SplashTheme">
|
||||
|
||||
<intent-filter>
|
||||
|
@ -106,7 +107,6 @@
|
|||
<activity
|
||||
android:name=".components.compose.ComposeActivity"
|
||||
android:theme="@style/TuskyDialogActivityTheme"
|
||||
android:excludeFromRecents="true"
|
||||
android:windowSoftInputMode="stateVisible|adjustResize" />
|
||||
<activity
|
||||
android:name=".components.viewthread.ViewThreadActivity"
|
||||
|
|
|
@ -176,6 +176,8 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
|
|||
|
||||
private val binding by viewBinding(ActivityMainBinding::inflate)
|
||||
|
||||
private lateinit var activeAccount: AccountEntity
|
||||
|
||||
private lateinit var header: AccountHeaderView
|
||||
|
||||
private var onTabSelectedListener: OnTabSelectedListener? = null
|
||||
|
@ -209,7 +211,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
|
|||
super.onCreate(savedInstanceState)
|
||||
|
||||
// will be redirected to LoginActivity by BaseActivity
|
||||
val activeAccount = accountManager.activeAccount ?: return
|
||||
activeAccount = accountManager.activeAccount ?: return
|
||||
|
||||
if (explodeAnimationWasRequested()) {
|
||||
overrideActivityTransitionCompat(
|
||||
|
@ -224,7 +226,12 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
|
|||
// check for savedInstanceState in order to not handle intent events more than once
|
||||
if (intent != null && savedInstanceState == null) {
|
||||
showNotificationTab = handleIntent(intent, activeAccount)
|
||||
if (isFinishing) {
|
||||
// handleIntent() finished this activity and started a new one - no need to continue initialization
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
window.statusBarColor = Color.TRANSPARENT // don't draw a status bar, the DrawerLayout and the MaterialDrawerLayout have their own
|
||||
setContentView(binding.root)
|
||||
|
||||
|
@ -258,10 +265,10 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
|
|||
setupDrawer(
|
||||
savedInstanceState,
|
||||
addSearchButton = hideTopToolbar,
|
||||
addTrendingTagsButton = !accountManager.activeAccount!!.tabPreferences.hasTab(
|
||||
addTrendingTagsButton = !activeAccount.tabPreferences.hasTab(
|
||||
TRENDING_TAGS
|
||||
),
|
||||
addTrendingStatusesButton = !accountManager.activeAccount!!.tabPreferences.hasTab(
|
||||
addTrendingStatusesButton = !activeAccount.tabPreferences.hasTab(
|
||||
TRENDING_STATUSES
|
||||
)
|
||||
)
|
||||
|
@ -352,7 +359,6 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
|
|||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
val activeAccount = accountManager.activeAccount ?: return
|
||||
val showNotificationTab = handleIntent(intent, activeAccount)
|
||||
if (showNotificationTab) {
|
||||
val tabs = activeAccount.tabPreferences
|
||||
|
@ -394,8 +400,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
|
|||
}
|
||||
val accountRequested = tuskyAccountId != -1L
|
||||
if (accountRequested && tuskyAccountId != activeAccount.id) {
|
||||
accountManager.setActiveAccount(tuskyAccountId)
|
||||
changeAccount(tuskyAccountId, intent, withAnimation = false)
|
||||
changeAccount(tuskyAccountId, intent)
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -451,11 +456,9 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
|
|||
|
||||
// TODO a bit cumbersome (also for resetting)
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
accountManager.activeAccount?.let {
|
||||
if (it.hasDirectMessageBadge != showBadge) {
|
||||
it.hasDirectMessageBadge = showBadge
|
||||
accountManager.saveAccount(it)
|
||||
}
|
||||
if (activeAccount.hasDirectMessageBadge != showBadge) {
|
||||
activeAccount.hasDirectMessageBadge = showBadge
|
||||
accountManager.saveAccount(activeAccount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -562,7 +565,6 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
|
|||
private fun forwardToComposeActivity(intent: Intent) {
|
||||
val composeOptions =
|
||||
intent.getParcelableExtraCompat<ComposeActivity.ComposeOptions>(COMPOSE_OPTIONS)
|
||||
|
||||
val composeIntent = if (composeOptions != null) {
|
||||
ComposeActivity.startIntent(this, composeOptions)
|
||||
} else {
|
||||
|
@ -570,7 +572,6 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
|
|||
action = intent.action
|
||||
type = intent.type
|
||||
putExtras(intent)
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
}
|
||||
}
|
||||
startActivity(composeIntent)
|
||||
|
@ -831,11 +832,9 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
|
|||
0 -> {
|
||||
Log.d(TAG, "Creating \"Load more\" gap")
|
||||
lifecycleScope.launch {
|
||||
accountManager.activeAccount?.let {
|
||||
developerToolsUseCase.createLoadMoreGap(
|
||||
it.id
|
||||
)
|
||||
}
|
||||
developerToolsUseCase.createLoadMoreGap(
|
||||
activeAccount.id
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -864,7 +863,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
|
|||
// Save the previous tab so it can be restored later
|
||||
val previousTab = tabAdapter.tabs.getOrNull(binding.viewPager.currentItem)
|
||||
|
||||
val tabs = accountManager.activeAccount!!.tabPreferences
|
||||
val tabs = activeAccount.tabPreferences
|
||||
|
||||
// Detach any existing mediator before changing tab contents and attaching a new mediator
|
||||
tabLayoutMediator?.detach()
|
||||
|
@ -883,7 +882,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
|
|||
}
|
||||
if (tabs[position].id == DIRECT) {
|
||||
val badge = tab.orCreateBadge
|
||||
badge.isVisible = accountManager.activeAccount?.hasDirectMessageBadge ?: false
|
||||
badge.isVisible = activeAccount.hasDirectMessageBadge
|
||||
badge.backgroundColor = MaterialColors.getColor(binding.mainDrawer, materialR.attr.colorPrimary)
|
||||
directMessageTab = tab
|
||||
}
|
||||
|
@ -921,11 +920,9 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
|
|||
if (tab == directMessageTab) {
|
||||
tab.badge?.isVisible = false
|
||||
|
||||
accountManager.activeAccount?.let {
|
||||
if (it.hasDirectMessageBadge) {
|
||||
it.hasDirectMessageBadge = false
|
||||
accountManager.saveAccount(it)
|
||||
}
|
||||
if (activeAccount.hasDirectMessageBadge) {
|
||||
activeAccount.hasDirectMessageBadge = false
|
||||
accountManager.saveAccount(activeAccount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -971,10 +968,8 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
|
|||
}
|
||||
|
||||
private fun handleProfileClick(profile: IProfile, current: Boolean): Boolean {
|
||||
val activeAccount = accountManager.activeAccount
|
||||
|
||||
// open profile when active image was clicked
|
||||
if (current && activeAccount != null) {
|
||||
if (current) {
|
||||
val intent = AccountActivity.getIntent(this, activeAccount.accountId)
|
||||
startActivityWithSlideInAnimation(intent)
|
||||
return false
|
||||
|
@ -994,49 +989,43 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
|
|||
private fun changeAccount(
|
||||
newSelectedId: Long,
|
||||
forward: Intent?,
|
||||
withAnimation: Boolean = true
|
||||
) {
|
||||
cacheUpdater.stop()
|
||||
accountManager.setActiveAccount(newSelectedId)
|
||||
val intent = Intent(this, MainActivity::class.java)
|
||||
if (withAnimation) {
|
||||
intent.putExtra(OPEN_WITH_EXPLODE_ANIMATION, true)
|
||||
}
|
||||
if (forward != null) {
|
||||
intent.type = forward.type
|
||||
intent.action = forward.action
|
||||
intent.putExtras(forward)
|
||||
}
|
||||
startActivity(intent)
|
||||
finish()
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
private fun logout() {
|
||||
accountManager.activeAccount?.let { activeAccount ->
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle(R.string.action_logout)
|
||||
.setMessage(getString(R.string.action_logout_confirm, activeAccount.fullName))
|
||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
|
||||
binding.appBar.hide()
|
||||
binding.viewPager.hide()
|
||||
binding.progressBar.show()
|
||||
binding.bottomNav.hide()
|
||||
binding.composeButton.hide()
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle(R.string.action_logout)
|
||||
.setMessage(getString(R.string.action_logout_confirm, activeAccount.fullName))
|
||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
|
||||
binding.appBar.hide()
|
||||
binding.viewPager.hide()
|
||||
binding.progressBar.show()
|
||||
binding.bottomNav.hide()
|
||||
binding.composeButton.hide()
|
||||
|
||||
lifecycleScope.launch {
|
||||
val otherAccountAvailable = logoutUsecase.logout()
|
||||
val intent = if (otherAccountAvailable) {
|
||||
Intent(this@MainActivity, MainActivity::class.java)
|
||||
} else {
|
||||
LoginActivity.getIntent(this@MainActivity, LoginActivity.MODE_DEFAULT)
|
||||
}
|
||||
startActivity(intent)
|
||||
finish()
|
||||
lifecycleScope.launch {
|
||||
val otherAccountAvailable = logoutUsecase.logout(activeAccount)
|
||||
val intent = if (otherAccountAvailable) {
|
||||
Intent(this@MainActivity, MainActivity::class.java)
|
||||
} else {
|
||||
LoginActivity.getIntent(this@MainActivity, LoginActivity.MODE_DEFAULT)
|
||||
}
|
||||
startActivity(intent)
|
||||
finish()
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun fetchUserInfo() = lifecycleScope.launch {
|
||||
|
@ -1058,11 +1047,8 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
|
|||
|
||||
loadDrawerAvatar(me.avatar, false)
|
||||
|
||||
accountManager.updateActiveAccount(me)
|
||||
NotificationHelper.createNotificationChannelsForAccount(
|
||||
accountManager.activeAccount!!,
|
||||
this
|
||||
)
|
||||
accountManager.updateAccount(activeAccount, me)
|
||||
NotificationHelper.createNotificationChannelsForAccount(activeAccount, this)
|
||||
|
||||
// Setup push notifications
|
||||
showMigrationNoticeIfNecessary(
|
||||
|
@ -1217,9 +1203,9 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
|
|||
}
|
||||
header.clear()
|
||||
header.profiles = profiles
|
||||
header.setActiveProfile(accountManager.activeAccount!!.id)
|
||||
header.setActiveProfile(activeAccount.id)
|
||||
binding.mainToolbar.subtitle = if (accountManager.shouldDisplaySelfUsername()) {
|
||||
accountManager.activeAccount!!.fullName
|
||||
activeAccount.fullName
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
|
|
@ -221,9 +221,9 @@ class ComposeActivity :
|
|||
}
|
||||
}
|
||||
} else if (result == CropImage.CancelledResult) {
|
||||
Log.w("ComposeActivity", "Edit image cancelled by user")
|
||||
Log.w(TAG, "Edit image cancelled by user")
|
||||
} else {
|
||||
Log.w("ComposeActivity", "Edit image failed: " + result.error)
|
||||
Log.w(TAG, "Edit image failed: " + result.error)
|
||||
displayTransientMessage(R.string.error_image_edit_failed)
|
||||
}
|
||||
viewModel.cropImageItemOld = null
|
||||
|
@ -940,7 +940,7 @@ class ComposeActivity :
|
|||
val errorMessage =
|
||||
getString(
|
||||
R.string.error_no_custom_emojis,
|
||||
accountManager.activeAccount!!.domain
|
||||
activeAccount
|
||||
)
|
||||
displayTransientMessage(errorMessage)
|
||||
} else {
|
||||
|
|
|
@ -303,11 +303,10 @@ class LoginActivity : BaseActivity() {
|
|||
oauthScopes = OAUTH_SCOPES,
|
||||
newAccount = newAccount
|
||||
)
|
||||
|
||||
finishAffinity()
|
||||
val intent = Intent(this, MainActivity::class.java)
|
||||
intent.putExtra(MainActivity.OPEN_WITH_EXPLODE_ANIMATION, true)
|
||||
startActivity(intent)
|
||||
finishAffinity()
|
||||
}, { e ->
|
||||
setLoading(false)
|
||||
binding.domainTextInputLayout.error =
|
||||
|
|
|
@ -106,7 +106,7 @@ class AccountManager @Inject constructor(
|
|||
}
|
||||
|
||||
activeAccount = newAccountEntity
|
||||
updateActiveAccount(newAccount)
|
||||
updateAccount(newAccountEntity, newAccount)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -122,49 +122,45 @@ class AccountManager @Inject constructor(
|
|||
}
|
||||
|
||||
/**
|
||||
* Logs the current account out by deleting all data of the account.
|
||||
* Logs an account out by deleting all its data.
|
||||
* @return the new active account, or null if no other account was found
|
||||
*/
|
||||
fun logActiveAccountOut(): AccountEntity? {
|
||||
return activeAccount?.let { account ->
|
||||
fun logout(account: AccountEntity): AccountEntity? {
|
||||
account.logout()
|
||||
|
||||
account.logout()
|
||||
accounts.remove(account)
|
||||
accountDao.delete(account)
|
||||
|
||||
accounts.remove(account)
|
||||
accountDao.delete(account)
|
||||
|
||||
if (accounts.size > 0) {
|
||||
accounts[0].isActive = true
|
||||
activeAccount = accounts[0]
|
||||
Log.d(TAG, "logActiveAccountOut: saving account with id " + accounts[0].id)
|
||||
accountDao.insertOrReplace(accounts[0])
|
||||
} else {
|
||||
activeAccount = null
|
||||
}
|
||||
activeAccount
|
||||
if (accounts.size > 0) {
|
||||
accounts[0].isActive = true
|
||||
activeAccount = accounts[0]
|
||||
Log.d(TAG, "logActiveAccountOut: saving account with id " + accounts[0].id)
|
||||
accountDao.insertOrReplace(accounts[0])
|
||||
} else {
|
||||
activeAccount = null
|
||||
}
|
||||
return activeAccount
|
||||
}
|
||||
|
||||
/**
|
||||
* updates the current account with new information from the mastodon api
|
||||
* and saves it in the database
|
||||
* @param account the [Account] object returned from the api
|
||||
* Updates an account with new information from the Mastodon api
|
||||
* and saves it in the database.
|
||||
* @param accountEntity the [AccountEntity] to update
|
||||
* @param account the [Account] object which the newest data from the api
|
||||
*/
|
||||
fun updateActiveAccount(account: Account) {
|
||||
activeAccount?.let {
|
||||
it.accountId = account.id
|
||||
it.username = account.username
|
||||
it.displayName = account.name
|
||||
it.profilePictureUrl = account.avatar
|
||||
it.defaultPostPrivacy = account.source?.privacy ?: Status.Visibility.PUBLIC
|
||||
it.defaultPostLanguage = account.source?.language.orEmpty()
|
||||
it.defaultMediaSensitivity = account.source?.sensitive ?: false
|
||||
it.emojis = account.emojis
|
||||
it.locked = account.locked
|
||||
fun updateAccount(accountEntity: AccountEntity, account: Account) {
|
||||
accountEntity.accountId = account.id
|
||||
accountEntity.username = account.username
|
||||
accountEntity.displayName = account.name
|
||||
accountEntity.profilePictureUrl = account.avatar
|
||||
accountEntity.defaultPostPrivacy = account.source?.privacy ?: Status.Visibility.PUBLIC
|
||||
accountEntity.defaultPostLanguage = account.source?.language.orEmpty()
|
||||
accountEntity.defaultMediaSensitivity = account.source?.sensitive ?: false
|
||||
accountEntity.emojis = account.emojis
|
||||
accountEntity.locked = account.locked
|
||||
|
||||
Log.d(TAG, "updateActiveAccount: saving account with id " + it.id)
|
||||
accountDao.insertOrReplace(it)
|
||||
}
|
||||
Log.d(TAG, "updateAccount: saving account with id " + accountEntity.id)
|
||||
accountDao.insertOrReplace(accountEntity)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -38,9 +38,7 @@ inline fun <reified T> apiForAccount(
|
|||
)
|
||||
.removeHeader(MastodonApi.DOMAIN_HEADER)
|
||||
.build()
|
||||
}
|
||||
|
||||
if (account != null && request.url.host == account.domain) {
|
||||
} else if (account != null && request.url.host == account.domain) {
|
||||
request = request.newBuilder()
|
||||
.header("Authorization", "Bearer ${account.accessToken}")
|
||||
.build()
|
||||
|
|
|
@ -6,6 +6,7 @@ import com.keylesspalace.tusky.components.systemnotifications.NotificationHelper
|
|||
import com.keylesspalace.tusky.components.systemnotifications.disableUnifiedPushNotificationsForAccount
|
||||
import com.keylesspalace.tusky.db.AccountManager
|
||||
import com.keylesspalace.tusky.db.DatabaseCleaner
|
||||
import com.keylesspalace.tusky.db.entity.AccountEntity
|
||||
import com.keylesspalace.tusky.network.MastodonApi
|
||||
import com.keylesspalace.tusky.util.ShareShortcutHelper
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
|
@ -24,44 +25,40 @@ class LogoutUsecase @Inject constructor(
|
|||
* Logs the current account out and clears all caches associated with it
|
||||
* @return true if the user is logged in with other accounts, false if it was the only one
|
||||
*/
|
||||
suspend fun logout(): Boolean {
|
||||
accountManager.activeAccount?.let { activeAccount ->
|
||||
|
||||
// invalidate the oauth token, if we have the client id & secret
|
||||
// (could be missing if user logged in with a previous version of Tusky)
|
||||
val clientId = activeAccount.clientId
|
||||
val clientSecret = activeAccount.clientSecret
|
||||
if (clientId != null && clientSecret != null) {
|
||||
api.revokeOAuthToken(
|
||||
clientId = clientId,
|
||||
clientSecret = clientSecret,
|
||||
token = activeAccount.accessToken
|
||||
)
|
||||
}
|
||||
|
||||
// disable push notifications
|
||||
disableUnifiedPushNotificationsForAccount(context, activeAccount)
|
||||
|
||||
// disable pull notifications
|
||||
if (!NotificationHelper.areNotificationsEnabled(context, accountManager)) {
|
||||
NotificationHelper.disablePullNotifications(context)
|
||||
}
|
||||
|
||||
// clear notification channels
|
||||
NotificationHelper.deleteNotificationChannelsForAccount(activeAccount, context)
|
||||
|
||||
// remove account from local AccountManager
|
||||
val otherAccountAvailable = accountManager.logActiveAccountOut() != null
|
||||
|
||||
// clear the database - this could trigger network calls so do it last when all tokens are gone
|
||||
databaseCleaner.cleanupEverything(activeAccount.id)
|
||||
draftHelper.deleteAllDraftsAndAttachmentsForAccount(activeAccount.id)
|
||||
|
||||
// remove shortcut associated with the account
|
||||
shareShortcutHelper.removeShortcut(activeAccount)
|
||||
|
||||
return otherAccountAvailable
|
||||
suspend fun logout(account: AccountEntity): Boolean {
|
||||
// invalidate the oauth token, if we have the client id & secret
|
||||
// (could be missing if user logged in with a previous version of Tusky)
|
||||
val clientId = account.clientId
|
||||
val clientSecret = account.clientSecret
|
||||
if (clientId != null && clientSecret != null) {
|
||||
api.revokeOAuthToken(
|
||||
clientId = clientId,
|
||||
clientSecret = clientSecret,
|
||||
token = account.accessToken
|
||||
)
|
||||
}
|
||||
return false
|
||||
|
||||
// disable push notifications
|
||||
disableUnifiedPushNotificationsForAccount(context, account)
|
||||
|
||||
// disable pull notifications
|
||||
if (!NotificationHelper.areNotificationsEnabled(context, accountManager)) {
|
||||
NotificationHelper.disablePullNotifications(context)
|
||||
}
|
||||
|
||||
// clear notification channels
|
||||
NotificationHelper.deleteNotificationChannelsForAccount(account, context)
|
||||
|
||||
// remove account from local AccountManager
|
||||
val otherAccountAvailable = accountManager.logout(account) != null
|
||||
|
||||
// clear the database - this could trigger network calls so do it last when all tokens are gone
|
||||
databaseCleaner.cleanupEverything(account.id)
|
||||
draftHelper.deleteAllDraftsAndAttachmentsForAccount(account.id)
|
||||
|
||||
// remove shortcut associated with the account
|
||||
shareShortcutHelper.removeShortcut(account)
|
||||
|
||||
return otherAccountAvailable
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ import org.junit.Test
|
|||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.moshi.MoshiConverterFactory
|
||||
|
||||
class RetrofitApiTest {
|
||||
class ApiFactoryTest {
|
||||
|
||||
private val mockWebServer = MockWebServer()
|
||||
private val okHttpClient = OkHttpClient.Builder().build()
|
||||
|
@ -68,7 +68,7 @@ class RetrofitApiTest {
|
|||
|
||||
val account = AccountEntity(
|
||||
id = 1,
|
||||
domain = "test.domain",
|
||||
domain = mockWebServer.hostName,
|
||||
accessToken = "fakeToken",
|
||||
clientId = "fakeId",
|
||||
clientSecret = "fakeSecret",
|
Loading…
Reference in New Issue