improve logout (#2579)

* improve logout

* fix tests

* add db migration

* delete wrongly committed file again

* improve LogoutUsecase
This commit is contained in:
Konrad Pozniak 2022-06-20 16:45:54 +02:00 committed by GitHub
parent 0574f0d096
commit f419e83c16
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 185 additions and 91 deletions

View File

@ -61,13 +61,10 @@ import com.keylesspalace.tusky.components.account.AccountActivity
import com.keylesspalace.tusky.components.announcements.AnnouncementsActivity import com.keylesspalace.tusky.components.announcements.AnnouncementsActivity
import com.keylesspalace.tusky.components.compose.ComposeActivity import com.keylesspalace.tusky.components.compose.ComposeActivity
import com.keylesspalace.tusky.components.compose.ComposeActivity.Companion.canHandleMimeType import com.keylesspalace.tusky.components.compose.ComposeActivity.Companion.canHandleMimeType
import com.keylesspalace.tusky.components.conversation.ConversationsRepository
import com.keylesspalace.tusky.components.drafts.DraftHelper
import com.keylesspalace.tusky.components.drafts.DraftsActivity import com.keylesspalace.tusky.components.drafts.DraftsActivity
import com.keylesspalace.tusky.components.login.LoginActivity import com.keylesspalace.tusky.components.login.LoginActivity
import com.keylesspalace.tusky.components.notifications.NotificationHelper import com.keylesspalace.tusky.components.notifications.NotificationHelper
import com.keylesspalace.tusky.components.notifications.disableAllNotifications import com.keylesspalace.tusky.components.notifications.disableAllNotifications
import com.keylesspalace.tusky.components.notifications.disableUnifiedPushNotificationsForAccount
import com.keylesspalace.tusky.components.notifications.enablePushNotificationsWithFallback import com.keylesspalace.tusky.components.notifications.enablePushNotificationsWithFallback
import com.keylesspalace.tusky.components.notifications.showMigrationNoticeIfNecessary import com.keylesspalace.tusky.components.notifications.showMigrationNoticeIfNecessary
import com.keylesspalace.tusky.components.preference.PreferencesActivity import com.keylesspalace.tusky.components.preference.PreferencesActivity
@ -81,11 +78,12 @@ import com.keylesspalace.tusky.interfaces.ActionButtonActivity
import com.keylesspalace.tusky.interfaces.ReselectableFragment import com.keylesspalace.tusky.interfaces.ReselectableFragment
import com.keylesspalace.tusky.pager.MainPagerAdapter import com.keylesspalace.tusky.pager.MainPagerAdapter
import com.keylesspalace.tusky.settings.PrefKeys import com.keylesspalace.tusky.settings.PrefKeys
import com.keylesspalace.tusky.usecase.LogoutUsecase
import com.keylesspalace.tusky.util.ThemeUtils import com.keylesspalace.tusky.util.ThemeUtils
import com.keylesspalace.tusky.util.deleteStaleCachedMedia import com.keylesspalace.tusky.util.deleteStaleCachedMedia
import com.keylesspalace.tusky.util.emojify import com.keylesspalace.tusky.util.emojify
import com.keylesspalace.tusky.util.hide import com.keylesspalace.tusky.util.hide
import com.keylesspalace.tusky.util.removeShortcut import com.keylesspalace.tusky.util.show
import com.keylesspalace.tusky.util.updateShortcut import com.keylesspalace.tusky.util.updateShortcut
import com.keylesspalace.tusky.util.viewBinding import com.keylesspalace.tusky.util.viewBinding
import com.keylesspalace.tusky.util.visible import com.keylesspalace.tusky.util.visible
@ -135,10 +133,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
lateinit var cacheUpdater: CacheUpdater lateinit var cacheUpdater: CacheUpdater
@Inject @Inject
lateinit var conversationRepository: ConversationsRepository lateinit var logoutUsecase: LogoutUsecase
@Inject
lateinit var draftHelper: DraftHelper
private val binding by viewBinding(ActivityMainBinding::inflate) private val binding by viewBinding(ActivityMainBinding::inflate)
@ -664,28 +659,18 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
.setTitle(R.string.action_logout) .setTitle(R.string.action_logout)
.setMessage(getString(R.string.action_logout_confirm, activeAccount.fullName)) .setMessage(getString(R.string.action_logout_confirm, activeAccount.fullName))
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int -> .setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
binding.appBar.hide()
binding.viewPager.hide()
binding.progressBar.show()
binding.bottomNav.hide()
binding.composeButton.hide()
lifecycleScope.launch { lifecycleScope.launch {
// Only disable UnifiedPush for this account -- do not call disableNotifications(), val otherAccountAvailable = logoutUsecase.logout()
// which unnecessarily disables it for all accounts and then re-enables it again at val intent = if (otherAccountAvailable) {
// the next launch
disableUnifiedPushNotificationsForAccount(this@MainActivity, activeAccount)
NotificationHelper.deleteNotificationChannelsForAccount(activeAccount, this@MainActivity)
cacheUpdater.clearForUser(activeAccount.id)
conversationRepository.deleteCacheForAccount(activeAccount.id)
draftHelper.deleteAllDraftsAndAttachmentsForAccount(activeAccount.id)
removeShortcut(this@MainActivity, activeAccount)
val newAccount = accountManager.logActiveAccountOut()
if (!NotificationHelper.areNotificationsEnabled(
this@MainActivity,
accountManager
)
) {
NotificationHelper.disablePullNotifications(this@MainActivity)
}
val intent = if (newAccount == null) {
LoginActivity.getIntent(this@MainActivity, LoginActivity.MODE_DEFAULT)
} else {
Intent(this@MainActivity, MainActivity::class.java) Intent(this@MainActivity, MainActivity::class.java)
} else {
LoginActivity.getIntent(this@MainActivity, LoginActivity.MODE_DEFAULT)
} }
startActivity(intent) startActivity(intent)
finishWithoutSlideOutAnimation() finishWithoutSlideOutAnimation()

View File

@ -9,7 +9,7 @@ import javax.inject.Inject
class CacheUpdater @Inject constructor( class CacheUpdater @Inject constructor(
eventHub: EventHub, eventHub: EventHub,
private val accountManager: AccountManager, private val accountManager: AccountManager,
private val appDatabase: AppDatabase, appDatabase: AppDatabase,
gson: Gson gson: Gson
) { ) {
@ -44,8 +44,4 @@ class CacheUpdater @Inject constructor(
fun stop() { fun stop() {
this.disposable.dispose() this.disposable.dispose()
} }
suspend fun clearForUser(accountId: Long) {
appDatabase.timelineDao().removeAll(accountId)
}
} }

View File

@ -1,30 +0,0 @@
/* Copyright 2021 Tusky Contributors
*
* This file is a part of Tusky.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky.components.conversation
import com.keylesspalace.tusky.db.AppDatabase
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class ConversationsRepository @Inject constructor(
val db: AppDatabase
) {
suspend fun deleteCacheForAccount(accountId: Long) {
db.conversationDao().deleteForAccount(accountId)
}
}

View File

@ -26,7 +26,7 @@ import androidx.paging.map
import com.keylesspalace.tusky.db.AccountManager import com.keylesspalace.tusky.db.AccountManager
import com.keylesspalace.tusky.db.AppDatabase import com.keylesspalace.tusky.db.AppDatabase
import com.keylesspalace.tusky.network.MastodonApi import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.network.TimelineCases import com.keylesspalace.tusky.usecase.TimelineCases
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.rx3.await import kotlinx.coroutines.rx3.await

View File

@ -236,7 +236,13 @@ class LoginActivity : BaseActivity(), Injectable {
domain, clientId, clientSecret, oauthRedirectUri, code, "authorization_code" domain, clientId, clientSecret, oauthRedirectUri, code, "authorization_code"
).fold( ).fold(
{ accessToken -> { accessToken ->
accountManager.addAccount(accessToken.accessToken, domain, OAUTH_SCOPES) accountManager.addAccount(
accessToken = accessToken.accessToken,
domain = domain,
clientId = clientId,
clientSecret = clientSecret,
oauthScopes = OAUTH_SCOPES
)
val intent = Intent(this, MainActivity::class.java) val intent = Intent(this, MainActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK

View File

@ -27,7 +27,7 @@ import com.keylesspalace.tusky.entity.DeletedStatus
import com.keylesspalace.tusky.entity.Poll import com.keylesspalace.tusky.entity.Poll
import com.keylesspalace.tusky.entity.Status import com.keylesspalace.tusky.entity.Status
import com.keylesspalace.tusky.network.MastodonApi import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.network.TimelineCases import com.keylesspalace.tusky.usecase.TimelineCases
import com.keylesspalace.tusky.util.RxAwareViewModel import com.keylesspalace.tusky.util.RxAwareViewModel
import com.keylesspalace.tusky.util.toViewData import com.keylesspalace.tusky.util.toViewData
import com.keylesspalace.tusky.viewdata.StatusViewData import com.keylesspalace.tusky.viewdata.StatusViewData

View File

@ -51,6 +51,10 @@ class CachedTimelineRemoteMediator(
state: PagingState<Int, TimelineStatusWithAccount> state: PagingState<Int, TimelineStatusWithAccount>
): MediatorResult { ): MediatorResult {
if (!activeAccount.isLoggedIn()) {
return MediatorResult.Success(endOfPaginationReached = true)
}
try { try {
var dbEmpty = false var dbEmpty = false

View File

@ -42,7 +42,7 @@ import com.keylesspalace.tusky.db.TimelineStatusWithAccount
import com.keylesspalace.tusky.entity.Poll import com.keylesspalace.tusky.entity.Poll
import com.keylesspalace.tusky.network.FilterModel import com.keylesspalace.tusky.network.FilterModel
import com.keylesspalace.tusky.network.MastodonApi import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.network.TimelineCases import com.keylesspalace.tusky.usecase.TimelineCases
import com.keylesspalace.tusky.viewdata.StatusViewData import com.keylesspalace.tusky.viewdata.StatusViewData
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.asExecutor import kotlinx.coroutines.asExecutor

View File

@ -34,7 +34,7 @@ import com.keylesspalace.tusky.entity.Poll
import com.keylesspalace.tusky.entity.Status import com.keylesspalace.tusky.entity.Status
import com.keylesspalace.tusky.network.FilterModel import com.keylesspalace.tusky.network.FilterModel
import com.keylesspalace.tusky.network.MastodonApi import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.network.TimelineCases import com.keylesspalace.tusky.usecase.TimelineCases
import com.keylesspalace.tusky.util.getDomain import com.keylesspalace.tusky.util.getDomain
import com.keylesspalace.tusky.util.isLessThan import com.keylesspalace.tusky.util.isLessThan
import com.keylesspalace.tusky.util.isLessThanOrEqual import com.keylesspalace.tusky.util.isLessThanOrEqual

View File

@ -39,8 +39,8 @@ import com.keylesspalace.tusky.entity.Filter
import com.keylesspalace.tusky.entity.Poll import com.keylesspalace.tusky.entity.Poll
import com.keylesspalace.tusky.network.FilterModel import com.keylesspalace.tusky.network.FilterModel
import com.keylesspalace.tusky.network.MastodonApi import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.network.TimelineCases
import com.keylesspalace.tusky.settings.PrefKeys import com.keylesspalace.tusky.settings.PrefKeys
import com.keylesspalace.tusky.usecase.TimelineCases
import com.keylesspalace.tusky.viewdata.StatusViewData import com.keylesspalace.tusky.viewdata.StatusViewData
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow

View File

@ -37,6 +37,8 @@ data class AccountEntity(
@field:PrimaryKey(autoGenerate = true) var id: Long, @field:PrimaryKey(autoGenerate = true) var id: Long,
val domain: String, val domain: String,
var accessToken: String, var accessToken: String,
var clientId: String?, // nullable for backward compatibility
var clientSecret: String?, // nullable for backward compatibility
var isActive: Boolean, var isActive: Boolean,
var accountId: String = "", var accountId: String = "",
var username: String = "", var username: String = "",
@ -81,6 +83,15 @@ data class AccountEntity(
val fullName: String val fullName: String
get() = "@$username@$domain" get() = "@$username@$domain"
fun logout() {
// deleting credentials so they cannot be used again
accessToken = ""
clientId = null
clientSecret = null
}
fun isLoggedIn() = accessToken.isNotEmpty()
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true
if (javaClass != other?.javaClass) return false if (javaClass != other?.javaClass) return false

View File

@ -54,7 +54,13 @@ class AccountManager @Inject constructor(db: AppDatabase) {
* @param accessToken the access token for the new account * @param accessToken the access token for the new account
* @param domain the domain of the accounts Mastodon instance * @param domain the domain of the accounts Mastodon instance
*/ */
fun addAccount(accessToken: String, domain: String, oauthScopes: String) { fun addAccount(
accessToken: String,
domain: String,
clientId: String,
clientSecret: String,
oauthScopes: String
) {
activeAccount?.let { activeAccount?.let {
it.isActive = false it.isActive = false
@ -66,8 +72,13 @@ class AccountManager @Inject constructor(db: AppDatabase) {
val maxAccountId = accounts.maxByOrNull { it.id }?.id ?: 0 val maxAccountId = accounts.maxByOrNull { it.id }?.id ?: 0
val newAccountId = maxAccountId + 1 val newAccountId = maxAccountId + 1
activeAccount = AccountEntity( activeAccount = AccountEntity(
id = newAccountId, domain = domain.lowercase(Locale.ROOT), id = newAccountId,
accessToken = accessToken, oauthScopes = oauthScopes, isActive = true domain = domain.lowercase(Locale.ROOT),
accessToken = accessToken,
clientId = clientId,
clientSecret = clientSecret,
oauthScopes = oauthScopes,
isActive = true
) )
} }
@ -89,11 +100,12 @@ class AccountManager @Inject constructor(db: AppDatabase) {
*/ */
fun logActiveAccountOut(): AccountEntity? { fun logActiveAccountOut(): AccountEntity? {
if (activeAccount == null) { return activeAccount?.let { account ->
return null
} else { account.logout()
accounts.remove(activeAccount!!)
accountDao.delete(activeAccount!!) accounts.remove(account)
accountDao.delete(account)
if (accounts.size > 0) { if (accounts.size > 0) {
accounts[0].isActive = true accounts[0].isActive = true
@ -103,7 +115,7 @@ class AccountManager @Inject constructor(db: AppDatabase) {
} else { } else {
activeAccount = null activeAccount = null
} }
return activeAccount activeAccount
} }
} }

View File

@ -31,7 +31,7 @@ import java.io.File;
*/ */
@Database(entities = { DraftEntity.class, AccountEntity.class, InstanceEntity.class, TimelineStatusEntity.class, @Database(entities = { DraftEntity.class, AccountEntity.class, InstanceEntity.class, TimelineStatusEntity.class,
TimelineAccountEntity.class, ConversationEntity.class TimelineAccountEntity.class, ConversationEntity.class
}, version = 38) }, version = 39)
public abstract class AppDatabase extends RoomDatabase { public abstract class AppDatabase extends RoomDatabase {
public abstract AccountDao accountDao(); public abstract AccountDao accountDao();
@ -573,4 +573,12 @@ public abstract class AppDatabase extends RoomDatabase {
database.execSQL("DELETE FROM `TimelineStatusEntity`"); database.execSQL("DELETE FROM `TimelineStatusEntity`");
} }
}; };
public static final Migration MIGRATION_38_39 = new Migration(38, 39) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE `AccountEntity` ADD COLUMN `clientId` TEXT");
database.execSQL("ALTER TABLE `AccountEntity` ADD COLUMN `clientSecret` TEXT");
}
};
} }

View File

@ -64,7 +64,8 @@ class AppModule {
AppDatabase.MIGRATION_26_27, AppDatabase.MIGRATION_27_28, AppDatabase.MIGRATION_28_29, AppDatabase.MIGRATION_26_27, AppDatabase.MIGRATION_27_28, AppDatabase.MIGRATION_28_29,
AppDatabase.MIGRATION_29_30, AppDatabase.MIGRATION_30_31, AppDatabase.MIGRATION_31_32, AppDatabase.MIGRATION_29_30, AppDatabase.MIGRATION_30_31, AppDatabase.MIGRATION_31_32,
AppDatabase.MIGRATION_32_33, AppDatabase.MIGRATION_33_34, AppDatabase.MIGRATION_34_35, AppDatabase.MIGRATION_32_33, AppDatabase.MIGRATION_33_34, AppDatabase.MIGRATION_34_35,
AppDatabase.MIGRATION_35_36, AppDatabase.MIGRATION_36_37, AppDatabase.MIGRATION_37_38 AppDatabase.MIGRATION_35_36, AppDatabase.MIGRATION_36_37, AppDatabase.MIGRATION_37_38,
AppDatabase.MIGRATION_38_39
) )
.build() .build()
} }

View File

@ -56,7 +56,7 @@ import com.keylesspalace.tusky.di.Injectable;
import com.keylesspalace.tusky.entity.Attachment; import com.keylesspalace.tusky.entity.Attachment;
import com.keylesspalace.tusky.entity.Status; import com.keylesspalace.tusky.entity.Status;
import com.keylesspalace.tusky.network.MastodonApi; import com.keylesspalace.tusky.network.MastodonApi;
import com.keylesspalace.tusky.network.TimelineCases; import com.keylesspalace.tusky.usecase.TimelineCases;
import com.keylesspalace.tusky.util.LinkHelper; import com.keylesspalace.tusky.util.LinkHelper;
import com.keylesspalace.tusky.util.StatusParsingHelper; import com.keylesspalace.tusky.util.StatusParsingHelper;
import com.keylesspalace.tusky.view.MuteAccountDialog; import com.keylesspalace.tusky.view.MuteAccountDialog;

View File

@ -15,6 +15,7 @@
package com.keylesspalace.tusky.network; package com.keylesspalace.tusky.network;
import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import com.keylesspalace.tusky.db.AccountEntity; import com.keylesspalace.tusky.db.AccountEntity;
@ -22,22 +23,20 @@ import com.keylesspalace.tusky.db.AccountManager;
import java.io.IOException; import java.io.IOException;
import okhttp3.HttpUrl; import okhttp3.*;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
/** /**
* Created by charlag on 31/10/17. * Created by charlag on 31/10/17.
*/ */
public final class InstanceSwitchAuthInterceptor implements Interceptor { public final class InstanceSwitchAuthInterceptor implements Interceptor {
private AccountManager accountManager; private final AccountManager accountManager;
public InstanceSwitchAuthInterceptor(AccountManager accountManager) { public InstanceSwitchAuthInterceptor(AccountManager accountManager) {
this.accountManager = accountManager; this.accountManager = accountManager;
} }
@NonNull
@Override @Override
public Response intercept(@NonNull Chain chain) throws IOException { public Response intercept(@NonNull Chain chain) throws IOException {
@ -55,13 +54,26 @@ public final class InstanceSwitchAuthInterceptor implements Interceptor {
builder.url(swapHost(originalRequest.url(), instanceHeader)); builder.url(swapHost(originalRequest.url(), instanceHeader));
builder.removeHeader(MastodonApi.DOMAIN_HEADER); builder.removeHeader(MastodonApi.DOMAIN_HEADER);
} else if (currentAccount != null) { } else if (currentAccount != null) {
String accessToken = currentAccount.getAccessToken();
if (!accessToken.isEmpty()) {
//use domain of current account //use domain of current account
builder.url(swapHost(originalRequest.url(), currentAccount.getDomain())) builder.url(swapHost(originalRequest.url(), currentAccount.getDomain()))
.header("Authorization", .header("Authorization",
String.format("Bearer %s", currentAccount.getAccessToken())); String.format("Bearer %s", currentAccount.getAccessToken()));
} }
}
Request newRequest = builder.build(); Request newRequest = builder.build();
if (MastodonApi.PLACEHOLDER_DOMAIN.equals(newRequest.url().host())) {
Log.w("ISAInterceptor", "no user logged in or no domain header specified - can't make request to " + newRequest.url());
return new Response.Builder()
.code(400)
.message("Bad Request")
.protocol(Protocol.HTTP_2)
.body(ResponseBody.create("", MediaType.parse("text/plain")))
.request(chain.request())
.build();
}
return chain.proceed(newRequest); return chain.proceed(newRequest);
} else { } else {

View File

@ -459,6 +459,14 @@ interface MastodonApi {
@Field("grant_type") grantType: String @Field("grant_type") grantType: String
): NetworkResult<AccessToken> ): NetworkResult<AccessToken>
@FormUrlEncoded
@POST("oauth/revoke")
suspend fun revokeOAuthToken(
@Field("client_id") clientId: String,
@Field("client_secret") clientSecret: String,
@Field("token") token: String
): NetworkResult<Unit>
@GET("/api/v1/lists") @GET("/api/v1/lists")
suspend fun getLists(): NetworkResult<List<MastoList>> suspend fun getLists(): NetworkResult<List<MastoList>>

View File

@ -0,0 +1,66 @@
package com.keylesspalace.tusky.usecase
import android.content.Context
import com.keylesspalace.tusky.components.drafts.DraftHelper
import com.keylesspalace.tusky.components.notifications.NotificationHelper
import com.keylesspalace.tusky.components.notifications.disableUnifiedPushNotificationsForAccount
import com.keylesspalace.tusky.db.AccountManager
import com.keylesspalace.tusky.db.AppDatabase
import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.util.removeShortcut
import javax.inject.Inject
class LogoutUsecase @Inject constructor(
private val context: Context,
private val api: MastodonApi,
private val db: AppDatabase,
private val accountManager: AccountManager,
private val draftHelper: DraftHelper
) {
/**
* 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
db.timelineDao().removeAll(activeAccount.id)
db.conversationDao().deleteForAccount(activeAccount.id)
draftHelper.deleteAllDraftsAndAttachmentsForAccount(activeAccount.id)
// remove shortcut associated with the account
removeShortcut(context, activeAccount)
return otherAccountAvailable
}
return false
}
}

View File

@ -13,7 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not, * You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky.network package com.keylesspalace.tusky.usecase
import android.util.Log import android.util.Log
import com.keylesspalace.tusky.appstore.BlockEvent import com.keylesspalace.tusky.appstore.BlockEvent
@ -29,6 +29,7 @@ import com.keylesspalace.tusky.appstore.StatusDeletedEvent
import com.keylesspalace.tusky.entity.DeletedStatus import com.keylesspalace.tusky.entity.DeletedStatus
import com.keylesspalace.tusky.entity.Poll import com.keylesspalace.tusky.entity.Poll
import com.keylesspalace.tusky.entity.Status import com.keylesspalace.tusky.entity.Status
import com.keylesspalace.tusky.network.MastodonApi
import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.addTo import io.reactivex.rxjava3.kotlin.addTo

View File

@ -13,6 +13,7 @@
tools:context="com.keylesspalace.tusky.MainActivity"> tools:context="com.keylesspalace.tusky.MainActivity">
<com.google.android.material.appbar.AppBarLayout <com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:elevation="@dimen/actionbar_elevation" android:elevation="@dimen/actionbar_elevation"
@ -75,6 +76,13 @@
<include layout="@layout/item_status_bottom_sheet" /> <include layout="@layout/item_status_bottom_sheet" />
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
android:layout_gravity="center" />
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.mikepenz.materialdrawer.widget.MaterialDrawerSliderView <com.mikepenz.materialdrawer.widget.MaterialDrawerSliderView

View File

@ -68,6 +68,8 @@ class ComposeActivityTest {
id = 1, id = 1,
domain = instanceDomain, domain = instanceDomain,
accessToken = "token", accessToken = "token",
clientId = "id",
clientSecret = "secret",
isActive = true, isActive = true,
accountId = "1", accountId = "1",
username = "username", username = "username",

View File

@ -46,6 +46,8 @@ class CachedTimelineRemoteMediatorTest {
id = 1, id = 1,
domain = "mastodon.example", domain = "mastodon.example",
accessToken = "token", accessToken = "token",
clientId = "id",
clientSecret = "secret",
isActive = true isActive = true
) )
} }

View File

@ -38,6 +38,8 @@ class NetworkTimelineRemoteMediatorTest {
id = 1, id = 1,
domain = "mastodon.example", domain = "mastodon.example",
accessToken = "token", accessToken = "token",
clientId = "id",
clientSecret = "secret",
isActive = true isActive = true
) )
} }