improve logout (#2579)
* improve logout * fix tests * add db migration * delete wrongly committed file again * improve LogoutUsecase
This commit is contained in:
parent
0574f0d096
commit
f419e83c16
|
@ -61,13 +61,10 @@ import com.keylesspalace.tusky.components.account.AccountActivity
|
|||
import com.keylesspalace.tusky.components.announcements.AnnouncementsActivity
|
||||
import com.keylesspalace.tusky.components.compose.ComposeActivity
|
||||
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.login.LoginActivity
|
||||
import com.keylesspalace.tusky.components.notifications.NotificationHelper
|
||||
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.showMigrationNoticeIfNecessary
|
||||
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.pager.MainPagerAdapter
|
||||
import com.keylesspalace.tusky.settings.PrefKeys
|
||||
import com.keylesspalace.tusky.usecase.LogoutUsecase
|
||||
import com.keylesspalace.tusky.util.ThemeUtils
|
||||
import com.keylesspalace.tusky.util.deleteStaleCachedMedia
|
||||
import com.keylesspalace.tusky.util.emojify
|
||||
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.viewBinding
|
||||
import com.keylesspalace.tusky.util.visible
|
||||
|
@ -135,10 +133,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
|
|||
lateinit var cacheUpdater: CacheUpdater
|
||||
|
||||
@Inject
|
||||
lateinit var conversationRepository: ConversationsRepository
|
||||
|
||||
@Inject
|
||||
lateinit var draftHelper: DraftHelper
|
||||
lateinit var logoutUsecase: LogoutUsecase
|
||||
|
||||
private val binding by viewBinding(ActivityMainBinding::inflate)
|
||||
|
||||
|
@ -664,28 +659,18 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
|
|||
.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 {
|
||||
// Only disable UnifiedPush for this account -- do not call disableNotifications(),
|
||||
// which unnecessarily disables it for all accounts and then re-enables it again at
|
||||
// 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 {
|
||||
val otherAccountAvailable = logoutUsecase.logout()
|
||||
val intent = if (otherAccountAvailable) {
|
||||
Intent(this@MainActivity, MainActivity::class.java)
|
||||
} else {
|
||||
LoginActivity.getIntent(this@MainActivity, LoginActivity.MODE_DEFAULT)
|
||||
}
|
||||
startActivity(intent)
|
||||
finishWithoutSlideOutAnimation()
|
||||
|
|
|
@ -9,7 +9,7 @@ import javax.inject.Inject
|
|||
class CacheUpdater @Inject constructor(
|
||||
eventHub: EventHub,
|
||||
private val accountManager: AccountManager,
|
||||
private val appDatabase: AppDatabase,
|
||||
appDatabase: AppDatabase,
|
||||
gson: Gson
|
||||
) {
|
||||
|
||||
|
@ -44,8 +44,4 @@ class CacheUpdater @Inject constructor(
|
|||
fun stop() {
|
||||
this.disposable.dispose()
|
||||
}
|
||||
|
||||
suspend fun clearForUser(accountId: Long) {
|
||||
appDatabase.timelineDao().removeAll(accountId)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -26,7 +26,7 @@ import androidx.paging.map
|
|||
import com.keylesspalace.tusky.db.AccountManager
|
||||
import com.keylesspalace.tusky.db.AppDatabase
|
||||
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.launch
|
||||
import kotlinx.coroutines.rx3.await
|
||||
|
|
|
@ -236,7 +236,13 @@ class LoginActivity : BaseActivity(), Injectable {
|
|||
domain, clientId, clientSecret, oauthRedirectUri, code, "authorization_code"
|
||||
).fold(
|
||||
{ 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)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
|
|
|
@ -27,7 +27,7 @@ import com.keylesspalace.tusky.entity.DeletedStatus
|
|||
import com.keylesspalace.tusky.entity.Poll
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
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.toViewData
|
||||
import com.keylesspalace.tusky.viewdata.StatusViewData
|
||||
|
|
|
@ -51,6 +51,10 @@ class CachedTimelineRemoteMediator(
|
|||
state: PagingState<Int, TimelineStatusWithAccount>
|
||||
): MediatorResult {
|
||||
|
||||
if (!activeAccount.isLoggedIn()) {
|
||||
return MediatorResult.Success(endOfPaginationReached = true)
|
||||
}
|
||||
|
||||
try {
|
||||
var dbEmpty = false
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ import com.keylesspalace.tusky.db.TimelineStatusWithAccount
|
|||
import com.keylesspalace.tusky.entity.Poll
|
||||
import com.keylesspalace.tusky.network.FilterModel
|
||||
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 kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.asExecutor
|
||||
|
|
|
@ -34,7 +34,7 @@ import com.keylesspalace.tusky.entity.Poll
|
|||
import com.keylesspalace.tusky.entity.Status
|
||||
import com.keylesspalace.tusky.network.FilterModel
|
||||
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.isLessThan
|
||||
import com.keylesspalace.tusky.util.isLessThanOrEqual
|
||||
|
|
|
@ -39,8 +39,8 @@ import com.keylesspalace.tusky.entity.Filter
|
|||
import com.keylesspalace.tusky.entity.Poll
|
||||
import com.keylesspalace.tusky.network.FilterModel
|
||||
import com.keylesspalace.tusky.network.MastodonApi
|
||||
import com.keylesspalace.tusky.network.TimelineCases
|
||||
import com.keylesspalace.tusky.settings.PrefKeys
|
||||
import com.keylesspalace.tusky.usecase.TimelineCases
|
||||
import com.keylesspalace.tusky.viewdata.StatusViewData
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
|
|
@ -37,6 +37,8 @@ data class AccountEntity(
|
|||
@field:PrimaryKey(autoGenerate = true) var id: Long,
|
||||
val domain: String,
|
||||
var accessToken: String,
|
||||
var clientId: String?, // nullable for backward compatibility
|
||||
var clientSecret: String?, // nullable for backward compatibility
|
||||
var isActive: Boolean,
|
||||
var accountId: String = "",
|
||||
var username: String = "",
|
||||
|
@ -81,6 +83,15 @@ data class AccountEntity(
|
|||
val fullName: String
|
||||
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 {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
|
|
@ -54,7 +54,13 @@ class AccountManager @Inject constructor(db: AppDatabase) {
|
|||
* @param accessToken the access token for the new account
|
||||
* @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 {
|
||||
it.isActive = false
|
||||
|
@ -66,8 +72,13 @@ class AccountManager @Inject constructor(db: AppDatabase) {
|
|||
val maxAccountId = accounts.maxByOrNull { it.id }?.id ?: 0
|
||||
val newAccountId = maxAccountId + 1
|
||||
activeAccount = AccountEntity(
|
||||
id = newAccountId, domain = domain.lowercase(Locale.ROOT),
|
||||
accessToken = accessToken, oauthScopes = oauthScopes, isActive = true
|
||||
id = newAccountId,
|
||||
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? {
|
||||
|
||||
if (activeAccount == null) {
|
||||
return null
|
||||
} else {
|
||||
accounts.remove(activeAccount!!)
|
||||
accountDao.delete(activeAccount!!)
|
||||
return activeAccount?.let { account ->
|
||||
|
||||
account.logout()
|
||||
|
||||
accounts.remove(account)
|
||||
accountDao.delete(account)
|
||||
|
||||
if (accounts.size > 0) {
|
||||
accounts[0].isActive = true
|
||||
|
@ -103,7 +115,7 @@ class AccountManager @Inject constructor(db: AppDatabase) {
|
|||
} else {
|
||||
activeAccount = null
|
||||
}
|
||||
return activeAccount
|
||||
activeAccount
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ import java.io.File;
|
|||
*/
|
||||
@Database(entities = { DraftEntity.class, AccountEntity.class, InstanceEntity.class, TimelineStatusEntity.class,
|
||||
TimelineAccountEntity.class, ConversationEntity.class
|
||||
}, version = 38)
|
||||
}, version = 39)
|
||||
public abstract class AppDatabase extends RoomDatabase {
|
||||
|
||||
public abstract AccountDao accountDao();
|
||||
|
@ -573,4 +573,12 @@ public abstract class AppDatabase extends RoomDatabase {
|
|||
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");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -64,7 +64,8 @@ class AppModule {
|
|||
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_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()
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ import com.keylesspalace.tusky.di.Injectable;
|
|||
import com.keylesspalace.tusky.entity.Attachment;
|
||||
import com.keylesspalace.tusky.entity.Status;
|
||||
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.StatusParsingHelper;
|
||||
import com.keylesspalace.tusky.view.MuteAccountDialog;
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
package com.keylesspalace.tusky.network;
|
||||
|
||||
import android.util.Log;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.keylesspalace.tusky.db.AccountEntity;
|
||||
|
@ -22,22 +23,20 @@ import com.keylesspalace.tusky.db.AccountManager;
|
|||
|
||||
import java.io.IOException;
|
||||
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.Interceptor;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.*;
|
||||
|
||||
/**
|
||||
* Created by charlag on 31/10/17.
|
||||
*/
|
||||
|
||||
public final class InstanceSwitchAuthInterceptor implements Interceptor {
|
||||
private AccountManager accountManager;
|
||||
private final AccountManager accountManager;
|
||||
|
||||
public InstanceSwitchAuthInterceptor(AccountManager accountManager) {
|
||||
this.accountManager = accountManager;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
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.removeHeader(MastodonApi.DOMAIN_HEADER);
|
||||
} else if (currentAccount != null) {
|
||||
//use domain of current account
|
||||
builder.url(swapHost(originalRequest.url(), currentAccount.getDomain()))
|
||||
.header("Authorization",
|
||||
String.format("Bearer %s", currentAccount.getAccessToken()));
|
||||
String accessToken = currentAccount.getAccessToken();
|
||||
if (!accessToken.isEmpty()) {
|
||||
//use domain of current account
|
||||
builder.url(swapHost(originalRequest.url(), currentAccount.getDomain()))
|
||||
.header("Authorization",
|
||||
String.format("Bearer %s", currentAccount.getAccessToken()));
|
||||
}
|
||||
}
|
||||
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);
|
||||
|
||||
} else {
|
||||
|
|
|
@ -459,6 +459,14 @@ interface MastodonApi {
|
|||
@Field("grant_type") grantType: String
|
||||
): 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")
|
||||
suspend fun getLists(): NetworkResult<List<MastoList>>
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -13,7 +13,7 @@
|
|||
* 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.network
|
||||
package com.keylesspalace.tusky.usecase
|
||||
|
||||
import android.util.Log
|
||||
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.Poll
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
import com.keylesspalace.tusky.network.MastodonApi
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
import io.reactivex.rxjava3.kotlin.addTo
|
|
@ -13,6 +13,7 @@
|
|||
tools:context="com.keylesspalace.tusky.MainActivity">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:elevation="@dimen/actionbar_elevation"
|
||||
|
@ -75,6 +76,13 @@
|
|||
|
||||
<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>
|
||||
|
||||
<com.mikepenz.materialdrawer.widget.MaterialDrawerSliderView
|
||||
|
|
|
@ -68,6 +68,8 @@ class ComposeActivityTest {
|
|||
id = 1,
|
||||
domain = instanceDomain,
|
||||
accessToken = "token",
|
||||
clientId = "id",
|
||||
clientSecret = "secret",
|
||||
isActive = true,
|
||||
accountId = "1",
|
||||
username = "username",
|
||||
|
|
|
@ -46,6 +46,8 @@ class CachedTimelineRemoteMediatorTest {
|
|||
id = 1,
|
||||
domain = "mastodon.example",
|
||||
accessToken = "token",
|
||||
clientId = "id",
|
||||
clientSecret = "secret",
|
||||
isActive = true
|
||||
)
|
||||
}
|
||||
|
|
|
@ -38,6 +38,8 @@ class NetworkTimelineRemoteMediatorTest {
|
|||
id = 1,
|
||||
domain = "mastodon.example",
|
||||
accessToken = "token",
|
||||
clientId = "id",
|
||||
clientSecret = "secret",
|
||||
isActive = true
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue