From 85025da56f7a80f29ff98f90e06cb688cec502d9 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Tue, 6 Jul 2021 15:51:16 +0800 Subject: [PATCH 001/137] migrate to kotlin flow --- .../com/twidere/twiderex/db/DbListTest.kt | 14 +- .../paging/compose/LazyPagingItems.kt | 92 ++++------ .../com/twidere/twiderex/TwidereXActivity.kt | 21 +-- .../twiderex/component/TimelineComponent.kt | 2 +- .../twiderex/component/UserComponent.kt | 4 +- .../foundation/InAppNotificationScaffold.kt | 3 +- .../component/lazy/itemsGridIndexed.kt | 2 +- .../db/dao/DirectMessageConversationDao.kt | 4 +- .../com/twidere/twiderex/db/dao/DraftDao.kt | 6 +- .../com/twidere/twiderex/db/dao/ListsDao.kt | 4 +- .../com/twidere/twiderex/db/dao/SearchDao.kt | 8 +- .../com/twidere/twiderex/db/dao/StatusDao.kt | 4 +- .../com/twidere/twiderex/db/dao/UserDao.kt | 4 +- ...iveDataExtensions.kt => FlowExtensions.kt} | 24 +-- .../notification/InAppNotification.kt | 21 ++- .../mediator/paging/PagingWithGapMediator.kt | 12 +- .../twiderex/repository/AccountRepository.kt | 42 +++-- .../repository/DirectMessageRepository.kt | 7 +- .../twiderex/repository/ListsRepository.kt | 7 +- .../twiderex/repository/StatusRepository.kt | 7 +- .../twiderex/repository/UserRepository.kt | 8 +- .../twidere/twiderex/scenes/DraftListScene.kt | 2 +- .../com/twidere/twiderex/scenes/HomeScene.kt | 2 +- .../com/twidere/twiderex/scenes/MediaScene.kt | 4 +- .../twidere/twiderex/scenes/PureMediaScene.kt | 4 +- .../twidere/twiderex/scenes/StatusScene.kt | 2 +- .../twiderex/scenes/compose/ComposeScene.kt | 8 +- .../compose/ComposeSearchHashtagScene.kt | 2 +- .../scenes/compose/ComposeSearchUserScene.kt | 2 +- .../twiderex/scenes/dm/DMConversationScene.kt | 22 +-- .../scenes/dm/DMNewConversationScene.kt | 4 +- .../twiderex/scenes/home/SearchItem.kt | 2 +- .../scenes/lists/ListsAddMembersScene.kt | 2 +- .../scenes/lists/ListsTimelineScene.kt | 4 +- .../platform/MastodonListsCreateDialog.kt | 2 +- .../lists/platform/MastodonListsEditDialog.kt | 4 +- .../lists/platform/TwitterListsCreateScene.kt | 2 +- .../lists/platform/TwitterListsEditScene.kt | 4 +- .../scenes/mastodon/MastodonSignInScene.kt | 2 +- .../scenes/search/SearchInputScene.kt | 2 +- .../twiderex/scenes/search/SearchScene.kt | 2 +- .../scenes/settings/AccountManagementScene.kt | 2 +- .../settings/AccountNotificationScene.kt | 2 +- .../twiderex/scenes/settings/MiscScene.kt | 2 +- .../scenes/settings/NotificationScene.kt | 2 +- .../twiderex/scenes/settings/StorageScene.kt | 2 +- .../scenes/twitter/TwitterSigninScene.kt | 2 +- .../scenes/twitter/user/TwitterUserScene.kt | 2 +- .../twidere/twiderex/scenes/user/UserScene.kt | 2 +- .../com/twidere/twiderex/utils/Event.kt | 2 +- .../twiderex/utils/MastodonEmojiCache.kt | 10 +- .../viewmodel/ActiveAccountViewModel.kt | 13 +- .../twiderex/viewmodel/MediaViewModel.kt | 15 +- .../twiderex/viewmodel/PureMediaViewModel.kt | 4 +- .../compose/ComposeSearchUserViewModel.kt | 7 +- .../viewmodel/compose/ComposeViewModel.kt | 158 +++++++++--------- .../MastodonComposeSearchHashtagViewModel.kt | 7 +- .../twiderex/viewmodel/dm/DMEventViewModel.kt | 68 ++++---- .../dm/DMNewConversationViewModel.kt | 7 +- .../lists/ListsSearchUserViewModel.kt | 7 +- .../viewmodel/lists/ListsUserViewModel.kt | 12 +- .../viewmodel/lists/ListsViewModel.kt | 24 ++- .../mastodon/MastodonSignInViewModel.kt | 10 +- .../viewmodel/search/SearchInputViewModel.kt | 13 +- .../viewmodel/search/SearchSaveViewModel.kt | 16 +- .../settings/AccountNotificationViewModel.kt | 5 +- .../viewmodel/settings/MiscViewModel.kt | 4 +- .../settings/NotificationViewModel.kt | 5 +- .../viewmodel/settings/StorageViewModel.kt | 16 +- .../viewmodel/timeline/TimelineViewModel.kt | 4 +- .../twitter/TwitterSignInViewModel.kt | 12 +- .../twitter/user/TwitterUserViewModel.kt | 10 +- .../twiderex/viewmodel/user/UserViewModel.kt | 28 ++-- .../worker/dm/DirectMessageFetchWorker.kt | 3 +- .../db/MockDirectMessageConversationDao.kt | 6 +- .../twidere/twiderex/mock/db/MockListsDao.kt | 6 +- .../twidere/twiderex/mock/db/MockUserDao.kt | 4 +- gradle/libs.versions.toml | 4 - 78 files changed, 417 insertions(+), 453 deletions(-) rename app/src/main/kotlin/com/twidere/twiderex/extensions/{LiveDataExtensions.kt => FlowExtensions.kt} (65%) diff --git a/app/src/androidTest/java/com/twidere/twiderex/db/DbListTest.kt b/app/src/androidTest/java/com/twidere/twiderex/db/DbListTest.kt index 691c19de6..e74975c80 100644 --- a/app/src/androidTest/java/com/twidere/twiderex/db/DbListTest.kt +++ b/app/src/androidTest/java/com/twidere/twiderex/db/DbListTest.kt @@ -34,6 +34,7 @@ import com.twidere.twiderex.db.dao.ListsDao import com.twidere.twiderex.db.mapper.toDbList import com.twidere.twiderex.db.model.DbList import com.twidere.twiderex.model.MicroBlogKey +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.runBlocking import org.junit.After import org.junit.Assert @@ -116,17 +117,16 @@ class DbListTest { } @Test - fun findDbListWithListKeyWithLiveData_AutoUpdateAfterDbUpdate() { + fun findDbListWithListKeyWithFlow_AutoUpdateAfterDbUpdate() { runBlocking { - val source = listsDao.findWithListKeyWithLiveData(MicroBlogKey.twitter("0"), twitterAccountKey) + val source = listsDao.findWithListKeyWithFlow(MicroBlogKey.twitter("0"), twitterAccountKey) val observer = Observer { } - source.observeForever(observer) - Assert.assertEquals("description 0", source.value?.description) - source.value?.let { + val data = source.firstOrNull() + Assert.assertEquals("description 0", data?.description) + data?.let { listsDao.update(listOf(it.copy(description = "Update 0"))) } - Assert.assertEquals("Update 0", source.value?.description) - source.removeObserver(observer) + Assert.assertEquals("Update 0", data?.description) } } diff --git a/app/src/main/kotlin/androidx/paging/compose/LazyPagingItems.kt b/app/src/main/kotlin/androidx/paging/compose/LazyPagingItems.kt index b36e2058f..e75846d27 100644 --- a/app/src/main/kotlin/androidx/paging/compose/LazyPagingItems.kt +++ b/app/src/main/kotlin/androidx/paging/compose/LazyPagingItems.kt @@ -24,12 +24,12 @@ import android.annotation.SuppressLint import androidx.compose.foundation.lazy.LazyItemScope import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.State import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.setValue import androidx.paging.CombinedLoadStates import androidx.paging.DifferCallback @@ -63,39 +63,34 @@ public class LazyPagingItems internal constructor( private val mainDispatcher = Dispatchers.Main /** - * The number of items which can be accessed. + * Contains the latest items list snapshot collected from the [flow]. */ - var itemCount: Int by mutableStateOf(0) - private set + private var itemSnapshotList by mutableStateOf( + ItemSnapshotList(0, 0, emptyList()) + ) /** - * Set of value holders associated with the currently (composed) indexes. Once we got a new - * value from the repository we can just update the value in the state. + * The number of items which can be accessed. */ - private val activeHolders = HashSet>() + val itemCount: Int get() = itemSnapshotList.size @SuppressLint("RestrictedApi") private val differCallback: DifferCallback = object : DifferCallback { override fun onChanged(position: Int, count: Int) { if (count > 0) { - updateValueHolders(position, count) + updateItemSnapshotList() } } override fun onInserted(position: Int, count: Int) { if (count > 0) { - // we have to update all the items starting from this position as the insertion - // changes the positions for all the next items - updateValueHolders(position, pagingDataDiffer.size - position) + updateItemSnapshotList() } } override fun onRemoved(position: Int, count: Int) { if (count > 0) { - // we have to update all the items starting from this position as the removal - // changes the positions for all the next items. plus we also want to set null - // for the items which are now out of the valid bounds. - updateValueHolders(position, pagingDataDiffer.size + count - position) + updateItemSnapshotList() } } } @@ -112,12 +107,26 @@ public class LazyPagingItems internal constructor( onListPresentable: () -> Unit ): Int? { onListPresentable() - val oldSize = itemCount - updateValueHolders(0, maxOf(newList.size, oldSize)) + updateItemSnapshotList() return null } } + private fun updateItemSnapshotList() { + itemSnapshotList = pagingDataDiffer.snapshot() + } + + /** + * Returns the presented item at the specified position, notifying Paging of the item access to + * trigger any loads necessary to fulfill prefetchDistance. + * + * @see peek + */ + operator fun get(index: Int): T? { + pagingDataDiffer[index] // this registers the value load + return itemSnapshotList[index] + } + /** * Returns the state containing the item specified at [index] and notifies Paging of the item * accessed in order to trigger any loads necessary to fulfill [PagingConfig.prefetchDistance]. @@ -127,39 +136,12 @@ public class LazyPagingItems internal constructor( * placeholder or [index] is not within the correct bounds. */ @Composable + @Deprecated( + "Use get() instead. It will return you the value not wrapped into a State", + ReplaceWith("this[index]") + ) fun getAsState(index: Int): State { - require(index >= 0) { "Index can't be negative. $index was passed." } - val holder = remember(index) { - val initial = if (index < pagingDataDiffer.size) { - pagingDataDiffer[index] - } else { - null - } - ActiveItemValueHolder(index, initial) - } - DisposableEffect(index) { - activeHolders.add(holder) - onDispose { - activeHolders.remove(holder) - } - } - return holder.state - } - - private fun updateValueHolders(position: Int, count: Int) { - itemCount = pagingDataDiffer.size - if (count > 0) { - activeHolders.forEach { - if (it.index in position until position + count) { - val newValue = if (it.index < pagingDataDiffer.size) { - pagingDataDiffer[it.index] - } else { - null - } - it.state.value = newValue - } - } - } + return rememberUpdatedState(get(index)) } /** @@ -170,7 +152,7 @@ public class LazyPagingItems internal constructor( * @return The presented item at position [index], `null` if it is a placeholder */ fun peek(index: Int): T? { - return pagingDataDiffer.peek(index) + return itemSnapshotList[index] } /** @@ -178,7 +160,7 @@ public class LazyPagingItems internal constructor( * placeholders if they are enabled. */ fun snapshot(): ItemSnapshotList { - return pagingDataDiffer.snapshot() + return itemSnapshotList } /** @@ -238,10 +220,6 @@ public class LazyPagingItems internal constructor( pagingDataDiffer.collectFrom(it) } } - - private class ActiveItemValueHolder(val index: Int, initialValue: T?) { - val state = mutableStateOf(initialValue) - } } private val IncompleteLoadState = LoadState.NotLoading(false) @@ -290,7 +268,7 @@ public fun LazyListScope.items( itemContent: @Composable LazyItemScope.(value: T?) -> Unit ) { items(lazyPagingItems.itemCount, key = key) { index -> - itemContent(lazyPagingItems.getAsState(index).value) + itemContent(lazyPagingItems[index]) } } @@ -313,6 +291,6 @@ public fun LazyListScope.itemsIndexed( itemContent: @Composable LazyItemScope.(index: Int, value: T?) -> Unit ) { items(lazyPagingItems.itemCount, key = key) { index -> - itemContent(index, lazyPagingItems.getAsState(index).value) + itemContent(index, lazyPagingItems[index]) } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/TwidereXActivity.kt b/app/src/main/kotlin/com/twidere/twiderex/TwidereXActivity.kt index 82e9e6f08..a5bd1b9c7 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/TwidereXActivity.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/TwidereXActivity.kt @@ -32,12 +32,10 @@ import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue -import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.remember import androidx.core.net.ConnectivityManagerCompat import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsControllerCompat -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.lifecycleScope import androidx.lifecycle.viewmodel.compose.viewModel import com.google.accompanist.insets.ExperimentalAnimatedInsets @@ -46,6 +44,7 @@ import com.twidere.twiderex.action.LocalStatusActions import com.twidere.twiderex.action.StatusActions import com.twidere.twiderex.component.foundation.LocalInAppNotification import com.twidere.twiderex.di.assisted.ProvideAssistedFactory +import com.twidere.twiderex.extensions.observeAsState import com.twidere.twiderex.navigation.Router import com.twidere.twiderex.notification.InAppNotification import com.twidere.twiderex.preferences.PreferencesHolder @@ -62,6 +61,7 @@ import com.twidere.twiderex.utils.LocalPlatformResolver import com.twidere.twiderex.utils.PlatformResolver import com.twidere.twiderex.viewmodel.ActiveAccountViewModel import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.flow.MutableStateFlow import moe.tlaster.precompose.navigation.NavController import javax.inject.Inject @@ -71,17 +71,15 @@ class TwidereXActivity : ComponentActivity() { private val navController by lazy { NavController() } - private val isActiveNetworkMetered = MutableLiveData(false) + private val isActiveNetworkMetered = MutableStateFlow(false) private val networkCallback by lazy { object : ConnectivityManager.NetworkCallback() { override fun onCapabilitiesChanged( network: Network, networkCapabilities: NetworkCapabilities ) { - isActiveNetworkMetered.postValue( - ConnectivityManagerCompat.isActiveNetworkMetered( - connectivityManager - ) + isActiveNetworkMetered.value = ConnectivityManagerCompat.isActiveNetworkMetered( + connectivityManager ) } } @@ -108,18 +106,17 @@ class TwidereXActivity : ComponentActivity() { @OptIn(ExperimentalAnimatedInsets::class) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - isActiveNetworkMetered.postValue( - ConnectivityManagerCompat.isActiveNetworkMetered( - connectivityManager - ) + isActiveNetworkMetered.value = ConnectivityManagerCompat.isActiveNetworkMetered( + connectivityManager ) + WindowCompat.setDecorFitsSystemWindows(window, false) window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) setContent { val windowInsetsControllerCompat = remember { WindowInsetsControllerCompat(window, window.decorView) } val accountViewModel = viewModel() - val account by accountViewModel.account.observeAsState() + val account by accountViewModel.account.observeAsState(null) val isActiveNetworkMetered by isActiveNetworkMetered.observeAsState(initial = false) CompositionLocalProvider( LocalInAppNotification provides inAppNotification, diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/TimelineComponent.kt b/app/src/main/kotlin/com/twidere/twiderex/component/TimelineComponent.kt index 30f8d39c5..7e49de071 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/TimelineComponent.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/TimelineComponent.kt @@ -25,7 +25,6 @@ import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.remember import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.unit.dp @@ -34,6 +33,7 @@ import androidx.paging.compose.collectAsLazyPagingItems import com.twidere.twiderex.component.foundation.SwipeToRefreshLayout import com.twidere.twiderex.component.lazy.LazyListController import com.twidere.twiderex.component.lazy.ui.LazyUiStatusList +import com.twidere.twiderex.extensions.observeAsState import com.twidere.twiderex.extensions.refreshOrRetry import com.twidere.twiderex.viewmodel.timeline.TimelineScrollState import com.twidere.twiderex.viewmodel.timeline.TimelineViewModel diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/UserComponent.kt b/app/src/main/kotlin/com/twidere/twiderex/component/UserComponent.kt index c563821e3..123af6216 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/UserComponent.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/UserComponent.kt @@ -62,7 +62,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.runtime.key -import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope @@ -100,6 +99,7 @@ import com.twidere.twiderex.component.status.UserScreenName import com.twidere.twiderex.component.status.withAvatarClip import com.twidere.twiderex.db.model.TwitterUrlEntity import com.twidere.twiderex.di.assisted.assistedViewModel +import com.twidere.twiderex.extensions.observeAsState import com.twidere.twiderex.extensions.withElevation import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.PlatformType @@ -400,7 +400,7 @@ val maxBannerSize = 200.dp fun UserInfo( viewModel: UserViewModel, ) { - val user by viewModel.user.observeAsState() + val user by viewModel.user.observeAsState(initial = null) val navController = LocalNavController.current Box( modifier = Modifier diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/InAppNotificationScaffold.kt b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/InAppNotificationScaffold.kt index a346eb478..d67fe4ef4 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/InAppNotificationScaffold.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/InAppNotificationScaffold.kt @@ -42,7 +42,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.getValue -import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -61,7 +60,7 @@ fun ApplyNotification( snackbarHostState: SnackbarHostState ) { val inAppNotification = LocalInAppNotification.current - val notification by inAppNotification.observeAsState() + val notification by inAppNotification.observeAsState(null) val event = notification?.getContentIfNotHandled() val message = event?.getMessage() val actionMessage = event?.let { diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/lazy/itemsGridIndexed.kt b/app/src/main/kotlin/com/twidere/twiderex/component/lazy/itemsGridIndexed.kt index 41c1033ef..4eaf082b9 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/lazy/itemsGridIndexed.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/lazy/itemsGridIndexed.kt @@ -79,7 +79,7 @@ fun LazyListScope.itemsPagingGridIndexed( data.retry() } itemsGridIndexed((0 until data.itemCount).toList(), rowSize = rowSize, spacing = spacing, padding = padding) { _, index -> - itemContent.invoke(this, index, data.getAsState(index = index).value) + itemContent.invoke(this, index, data[index]) } loadState(data.loadState.append) { data.retry() diff --git a/app/src/main/kotlin/com/twidere/twiderex/db/dao/DirectMessageConversationDao.kt b/app/src/main/kotlin/com/twidere/twiderex/db/dao/DirectMessageConversationDao.kt index 2fd347400..129377068 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/db/dao/DirectMessageConversationDao.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/db/dao/DirectMessageConversationDao.kt @@ -20,7 +20,6 @@ */ package com.twidere.twiderex.db.dao -import androidx.lifecycle.LiveData import androidx.paging.PagingSource import androidx.room.Dao import androidx.room.Delete @@ -31,6 +30,7 @@ import androidx.room.Transaction import com.twidere.twiderex.db.model.DbDMConversation import com.twidere.twiderex.db.model.DbDirectMessageConversationWithMessage import com.twidere.twiderex.model.MicroBlogKey +import kotlinx.coroutines.flow.Flow import org.jetbrains.annotations.TestOnly @Dao @@ -65,7 +65,7 @@ interface DirectMessageConversationDao { ): PagingSource @Query("SELECT * FROM dm_conversation WHERE accountKey == :accountKey AND conversationKey == :conversationKey") - fun findWithConversationKeyLiveData(accountKey: MicroBlogKey, conversationKey: MicroBlogKey): LiveData + fun findWithConversationKeyFlow(accountKey: MicroBlogKey, conversationKey: MicroBlogKey): Flow @Query("SELECT * FROM dm_conversation WHERE accountKey == :accountKey AND conversationKey == :conversationKey") fun findWithConversationKey(accountKey: MicroBlogKey, conversationKey: MicroBlogKey): DbDMConversation? diff --git a/app/src/main/kotlin/com/twidere/twiderex/db/dao/DraftDao.kt b/app/src/main/kotlin/com/twidere/twiderex/db/dao/DraftDao.kt index f633061f5..de21b3351 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/db/dao/DraftDao.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/db/dao/DraftDao.kt @@ -20,13 +20,13 @@ */ package com.twidere.twiderex.db.dao -import androidx.lifecycle.LiveData import androidx.room.Dao import androidx.room.Delete import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query import com.twidere.twiderex.db.model.DbDraft +import kotlinx.coroutines.flow.Flow @Dao interface DraftDao { @@ -34,7 +34,7 @@ interface DraftDao { suspend fun insertAll(vararg draft: DbDraft) @Query("SELECT * FROM draft") - fun getAll(): LiveData> + fun getAll(): Flow> @Query("SELECT * FROM draft WHERE _id == :id") suspend fun get(id: String): DbDraft? @@ -43,5 +43,5 @@ interface DraftDao { suspend fun remove(draft: DbDraft) @Query("SELECT COUNT(*) FROM draft") - fun getDraftCount(): LiveData + fun getDraftCount(): Flow } diff --git a/app/src/main/kotlin/com/twidere/twiderex/db/dao/ListsDao.kt b/app/src/main/kotlin/com/twidere/twiderex/db/dao/ListsDao.kt index 625ee37fe..b8174d8b9 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/db/dao/ListsDao.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/db/dao/ListsDao.kt @@ -20,7 +20,6 @@ */ package com.twidere.twiderex.db.dao -import androidx.lifecycle.LiveData import androidx.paging.PagingSource import androidx.room.Dao import androidx.room.Delete @@ -31,6 +30,7 @@ import androidx.room.Transaction import androidx.room.Update import com.twidere.twiderex.db.model.DbList import com.twidere.twiderex.model.MicroBlogKey +import kotlinx.coroutines.flow.Flow @Dao interface ListsDao { @@ -41,7 +41,7 @@ interface ListsDao { suspend fun findWithListKey(listKey: MicroBlogKey, accountKey: MicroBlogKey): DbList? @Query("SELECT * FROM lists WHERE listKey == :listKey AND accountKey == :accountKey") - fun findWithListKeyWithLiveData(listKey: MicroBlogKey, accountKey: MicroBlogKey): LiveData + fun findWithListKeyWithFlow(listKey: MicroBlogKey, accountKey: MicroBlogKey): Flow @Query("SELECT * FROM lists") suspend fun findAll(): List? diff --git a/app/src/main/kotlin/com/twidere/twiderex/db/dao/SearchDao.kt b/app/src/main/kotlin/com/twidere/twiderex/db/dao/SearchDao.kt index cd8606bdf..6cc3edcb7 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/db/dao/SearchDao.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/db/dao/SearchDao.kt @@ -20,7 +20,6 @@ */ package com.twidere.twiderex.db.dao -import androidx.lifecycle.LiveData import androidx.room.Dao import androidx.room.Delete import androidx.room.Insert @@ -28,6 +27,7 @@ import androidx.room.OnConflictStrategy import androidx.room.Query import com.twidere.twiderex.db.model.DbSearch import com.twidere.twiderex.model.MicroBlogKey +import kotlinx.coroutines.flow.Flow @Dao interface SearchDao { @@ -35,13 +35,13 @@ interface SearchDao { suspend fun insertAll(search: List) @Query("SELECT * FROM search where accountKey == :accountKey ORDER BY lastActive DESC") - fun getAll(accountKey: MicroBlogKey): LiveData> + fun getAll(accountKey: MicroBlogKey): Flow> @Query("SELECT * FROM search where saved == 0 AND accountKey == :accountKey ORDER BY lastActive DESC") - fun getAllHistory(accountKey: MicroBlogKey): LiveData> + fun getAllHistory(accountKey: MicroBlogKey): Flow> @Query("SELECT * FROM search where saved == 1 AND accountKey == :accountKey ORDER BY lastActive DESC") - fun getAllSaved(accountKey: MicroBlogKey): LiveData> + fun getAllSaved(accountKey: MicroBlogKey): Flow> @Query("SELECT * FROM search WHERE content == :content AND accountKey == :accountKey") suspend fun get(content: String, accountKey: MicroBlogKey): DbSearch? diff --git a/app/src/main/kotlin/com/twidere/twiderex/db/dao/StatusDao.kt b/app/src/main/kotlin/com/twidere/twiderex/db/dao/StatusDao.kt index 83dd9b4eb..866cb2685 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/db/dao/StatusDao.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/db/dao/StatusDao.kt @@ -20,7 +20,6 @@ */ package com.twidere.twiderex.db.dao -import androidx.lifecycle.LiveData import androidx.room.Dao import androidx.room.Delete import androidx.room.Insert @@ -31,6 +30,7 @@ import androidx.room.Update import com.twidere.twiderex.db.model.DbStatusV2 import com.twidere.twiderex.db.model.DbStatusWithReference import com.twidere.twiderex.model.MicroBlogKey +import kotlinx.coroutines.flow.Flow @Dao interface StatusDao { @@ -49,7 +49,7 @@ interface StatusDao { @Transaction @Query("SELECT * FROM status WHERE statusKey == :key") - fun findWithStatusKeyWithReferenceLiveData(key: MicroBlogKey): LiveData + fun findWithStatusKeyWithReferenceFlow(key: MicroBlogKey): Flow @Update(onConflict = OnConflictStrategy.REPLACE) suspend fun update(status: List) diff --git a/app/src/main/kotlin/com/twidere/twiderex/db/dao/UserDao.kt b/app/src/main/kotlin/com/twidere/twiderex/db/dao/UserDao.kt index 8d68a768c..252452254 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/db/dao/UserDao.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/db/dao/UserDao.kt @@ -20,7 +20,6 @@ */ package com.twidere.twiderex.db.dao -import androidx.lifecycle.LiveData import androidx.room.Dao import androidx.room.Insert import androidx.room.OnConflictStrategy @@ -28,6 +27,7 @@ import androidx.room.Query import androidx.room.Update import com.twidere.twiderex.db.model.DbUser import com.twidere.twiderex.model.MicroBlogKey +import kotlinx.coroutines.flow.Flow @Dao interface UserDao { @@ -35,7 +35,7 @@ interface UserDao { suspend fun insertAll(user: List) @Query("SELECT * FROM user WHERE userKey == :userKey") - fun findWithUserKeyLiveData(userKey: MicroBlogKey): LiveData + fun findWithUserKeyFlow(userKey: MicroBlogKey): Flow @Query("SELECT * FROM user WHERE userKey == :userKey") suspend fun findWithUserKey(userKey: MicroBlogKey): DbUser? diff --git a/app/src/main/kotlin/com/twidere/twiderex/extensions/LiveDataExtensions.kt b/app/src/main/kotlin/com/twidere/twiderex/extensions/FlowExtensions.kt similarity index 65% rename from app/src/main/kotlin/com/twidere/twiderex/extensions/LiveDataExtensions.kt rename to app/src/main/kotlin/com/twidere/twiderex/extensions/FlowExtensions.kt index 65807b631..7010aaab1 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/extensions/LiveDataExtensions.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/extensions/FlowExtensions.kt @@ -20,19 +20,13 @@ */ package com.twidere.twiderex.extensions -import androidx.lifecycle.LiveData -import androidx.lifecycle.MediatorLiveData +import androidx.compose.runtime.Composable +import androidx.compose.runtime.State +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.platform.LocalLifecycleOwner +import androidx.lifecycle.flowWithLifecycle +import kotlinx.coroutines.flow.Flow -fun LiveData.combineWith( - liveData: LiveData, - block: (T?, K?) -> R -): LiveData { - val result = MediatorLiveData() - result.addSource(this) { - result.value = block(this.value, liveData.value) - } - result.addSource(liveData) { - result.value = block(this.value, liveData.value) - } - return result -} +@Composable +fun Flow.observeAsState(initial: T): State = + flowWithLifecycle(LocalLifecycleOwner.current.lifecycle).collectAsState(initial = initial) diff --git a/app/src/main/kotlin/com/twidere/twiderex/notification/InAppNotification.kt b/app/src/main/kotlin/com/twidere/twiderex/notification/InAppNotification.kt index c893c51bf..e0ccc07cd 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/notification/InAppNotification.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/notification/InAppNotification.kt @@ -24,14 +24,17 @@ import android.content.Context import androidx.annotation.StringRes import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource -import androidx.lifecycle.MutableLiveData import com.twidere.twiderex.component.navigation.INavigator +import com.twidere.twiderex.extensions.observeAsState import com.twidere.twiderex.utils.Event +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asSharedFlow interface NotificationEvent { @Composable fun getMessage(): String } + interface NotificationWithActionEvent : NotificationEvent { @Composable fun getActionMessage(): String @@ -78,16 +81,24 @@ class StringResWithActionNotificationEvent( } } -class InAppNotification : MutableLiveData>() { +class InAppNotification { + private val _source = MutableStateFlow?>(null) + private val source + get() = _source.asSharedFlow() + fun show(event: NotificationEvent) { - postValue((Event(event))) + _source.value = ((Event(event))) } fun show(message: String) { - postValue(Event(StringNotificationEvent(message))) + _source.value = Event(StringNotificationEvent(message)) } fun show(@StringRes messageId: Int) { - postValue(Event(StringResNotificationEvent(messageId = messageId))) + _source.value = Event(StringResNotificationEvent(messageId = messageId)) } + + @Composable + fun observeAsState(initial: Event? = null) = + source.observeAsState(initial = initial) } diff --git a/app/src/main/kotlin/com/twidere/twiderex/paging/mediator/paging/PagingWithGapMediator.kt b/app/src/main/kotlin/com/twidere/twiderex/paging/mediator/paging/PagingWithGapMediator.kt index 48bafdd76..771958216 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/paging/mediator/paging/PagingWithGapMediator.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/paging/mediator/paging/PagingWithGapMediator.kt @@ -20,7 +20,6 @@ */ package com.twidere.twiderex.paging.mediator.paging -import androidx.lifecycle.MutableLiveData import androidx.paging.ExperimentalPagingApi import androidx.paging.LoadType import androidx.paging.PagingState @@ -32,6 +31,8 @@ import com.twidere.twiderex.db.model.DbPagingTimelineWithStatus import com.twidere.twiderex.db.model.saveToDb import com.twidere.twiderex.model.MicroBlogKey import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.withContext @OptIn(ExperimentalPagingApi::class) @@ -39,7 +40,10 @@ abstract class PagingWithGapMediator( accountKey: MicroBlogKey, database: CacheDatabase, ) : PagingMediator(accountKey = accountKey, database = database) { - val loadingBetween = MutableLiveData(listOf()) + + private val _loadingBetween = MutableStateFlow(listOf()) + val loadingBetween + get() = _loadingBetween.asSharedFlow() override suspend fun initialize(): InitializeAction { return InitializeAction.SKIP_INITIAL_REFRESH @@ -91,7 +95,7 @@ abstract class PagingWithGapMediator( sinceStatueKey: MicroBlogKey? = null, ): MediatorResult { if (maxStatusKey != null && sinceStatueKey != null) { - loadingBetween.postValue((loadingBetween.value ?: listOf()) + maxStatusKey) + _loadingBetween.value = _loadingBetween.value + maxStatusKey } try { val max_id = withContext(Dispatchers.IO) { @@ -126,7 +130,7 @@ abstract class PagingWithGapMediator( return MediatorResult.Error(e) } finally { if (maxStatusKey != null && sinceStatueKey != null) { - loadingBetween.postValue((loadingBetween.value ?: listOf()) - maxStatusKey) + _loadingBetween.value = _loadingBetween.value - maxStatusKey } } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/repository/AccountRepository.kt b/app/src/main/kotlin/com/twidere/twiderex/repository/AccountRepository.kt index 3d9ff4a67..8be23f931 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/repository/AccountRepository.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/repository/AccountRepository.kt @@ -23,7 +23,6 @@ package com.twidere.twiderex.repository import android.accounts.Account import android.accounts.AccountManager import android.os.Build -import androidx.lifecycle.MutableLiveData import com.twidere.twiderex.model.AccountDetails import com.twidere.twiderex.model.AccountPreferences import com.twidere.twiderex.model.MicroBlogKey @@ -31,6 +30,8 @@ import com.twidere.twiderex.model.PlatformType import com.twidere.twiderex.model.cred.CredentialsType import com.twidere.twiderex.utils.fromJson import com.twidere.twiderex.utils.json +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asSharedFlow import javax.inject.Inject import javax.inject.Singleton @@ -53,16 +54,21 @@ class AccountRepository @Inject constructor( private val accountPreferencesFactory: AccountPreferences.Factory, ) { private val preferencesCache = linkedMapOf() + private val _activeAccount = + MutableStateFlow(if (hasAccount()) getCurrentAccount() else null) - val activeAccount = - MutableLiveData(if (hasAccount()) getCurrentAccount() else null) + val activeAccount + get() = _activeAccount.asSharedFlow() - val accounts = MutableLiveData( + private val _accounts = MutableStateFlow( getAccounts().map { getAccountDetails(it) } ) + val accounts + get() = _accounts.asSharedFlow() + fun getAccounts(): List { return manager.getAccountsByType(ACCOUNT_TYPE).toList() } @@ -90,7 +96,7 @@ class AccountRepository @Inject constructor( fun setCurrentAccount(detail: AccountDetails) { detail.lastActive = System.currentTimeMillis() updateAccount(detail) - activeAccount.value = detail + _activeAccount.value = detail } private fun getCurrentAccount(): AccountDetails? { @@ -102,11 +108,9 @@ class AccountRepository @Inject constructor( manager.addAccountExplicitly(detail.account, null, null) updateAccount(detail) setCurrentAccount(detail) - accounts.postValue( - getAccounts().map { - getAccountDetails(it) - } - ) + _accounts.value = getAccounts().map { + getAccountDetails(it) + } } fun getAccountDetails( @@ -153,22 +157,24 @@ class AccountRepository @Inject constructor( ACCOUNT_USER_DATA_LAST_ACTIVE, detail.lastActive.toString() ) - activeAccount.value = getCurrentAccount() - accounts.postValue( - getAccounts().map { - getAccountDetails(it) - } - ) + _activeAccount.value = getCurrentAccount() + _accounts.value = getAccounts().map { + getAccountDetails(it) + } } fun delete(detail: AccountDetails) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { manager.removeAccountExplicitly(detail.account) - accounts.value = getAccounts().map { + _accounts.value = getAccounts().map { getAccountDetails(it) } - activeAccount.value = getCurrentAccount() + _activeAccount.value = getCurrentAccount() preferencesCache.remove(detail.accountKey) } } + + fun getFirstByType(type: PlatformType): AccountDetails? { + return _accounts.value.sortedByDescending { it.lastActive }.firstOrNull { it.type == type } + } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/repository/DirectMessageRepository.kt b/app/src/main/kotlin/com/twidere/twiderex/repository/DirectMessageRepository.kt index 10fd69e05..a5b4aac2d 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/repository/DirectMessageRepository.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/repository/DirectMessageRepository.kt @@ -20,8 +20,6 @@ */ package com.twidere.twiderex.repository -import androidx.lifecycle.LiveData -import androidx.lifecycle.map import androidx.paging.PagingData import androidx.room.withTransaction import com.twidere.services.microblog.DirectMessageService @@ -49,6 +47,7 @@ import com.twidere.twiderex.paging.mediator.dm.DMConversationMediator.Companion. import com.twidere.twiderex.paging.mediator.dm.DMEventMediator import com.twidere.twiderex.paging.mediator.dm.DMEventMediator.Companion.toUi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map import java.util.UUID class DirectMessageRepository( @@ -57,9 +56,9 @@ class DirectMessageRepository( fun dmConversation( accountKey: MicroBlogKey, conversationKey: MicroBlogKey - ): LiveData { + ): Flow { return database.directMessageConversationDao() - .findWithConversationKeyLiveData( + .findWithConversationKeyFlow( accountKey = accountKey, conversationKey = conversationKey ).map { it?.toUi() } diff --git a/app/src/main/kotlin/com/twidere/twiderex/repository/ListsRepository.kt b/app/src/main/kotlin/com/twidere/twiderex/repository/ListsRepository.kt index 47e9d5803..b9ac9c69d 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/repository/ListsRepository.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/repository/ListsRepository.kt @@ -20,8 +20,6 @@ */ package com.twidere.twiderex.repository -import androidx.lifecycle.LiveData -import androidx.lifecycle.map import androidx.paging.PagingData import com.twidere.services.microblog.ListsService import com.twidere.twiderex.db.CacheDatabase @@ -34,11 +32,12 @@ import com.twidere.twiderex.model.ui.UiList.Companion.toUi import com.twidere.twiderex.paging.mediator.list.ListsMediator import com.twidere.twiderex.paging.mediator.list.ListsMediator.Companion.toUi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map class ListsRepository(private val database: CacheDatabase) { - fun findListWithListKey(account: AccountDetails, listKey: MicroBlogKey): LiveData { - return database.listsDao().findWithListKeyWithLiveData(listKey = listKey, accountKey = account.accountKey) + fun findListWithListKey(account: AccountDetails, listKey: MicroBlogKey): Flow { + return database.listsDao().findWithListKeyWithFlow(listKey = listKey, accountKey = account.accountKey) .map { it?.toUi() } diff --git a/app/src/main/kotlin/com/twidere/twiderex/repository/StatusRepository.kt b/app/src/main/kotlin/com/twidere/twiderex/repository/StatusRepository.kt index 6b8d325f5..730ea2b84 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/repository/StatusRepository.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/repository/StatusRepository.kt @@ -20,8 +20,6 @@ */ package com.twidere.twiderex.repository -import androidx.lifecycle.LiveData -import androidx.lifecycle.map import androidx.paging.ExperimentalPagingApi import androidx.paging.PagingData import androidx.room.withTransaction @@ -43,6 +41,7 @@ import com.twidere.twiderex.paging.mediator.paging.pager import com.twidere.twiderex.paging.mediator.status.MastodonStatusContextMediator import com.twidere.twiderex.paging.mediator.status.TwitterConversationMediator import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map class StatusRepository( private val database: CacheDatabase, @@ -51,8 +50,8 @@ class StatusRepository( fun loadStatus( statusKey: MicroBlogKey, accountKey: MicroBlogKey - ): LiveData { - return database.statusDao().findWithStatusKeyWithReferenceLiveData(statusKey).map { + ): Flow { + return database.statusDao().findWithStatusKeyWithReferenceFlow(statusKey).map { it?.toUi(accountKey) } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/repository/UserRepository.kt b/app/src/main/kotlin/com/twidere/twiderex/repository/UserRepository.kt index 3fa735b71..72cbe37d0 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/repository/UserRepository.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/repository/UserRepository.kt @@ -20,8 +20,6 @@ */ package com.twidere.twiderex.repository -import androidx.lifecycle.LiveData -import androidx.lifecycle.map import com.twidere.services.microblog.LookupService import com.twidere.twiderex.db.CacheDatabase import com.twidere.twiderex.db.mapper.toDbUser @@ -30,6 +28,8 @@ import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.toAmUser import com.twidere.twiderex.model.ui.UiUser import com.twidere.twiderex.model.ui.UiUser.Companion.toUi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map class UserRepository( private val database: CacheDatabase, @@ -51,8 +51,8 @@ class UserRepository( return lookupService.lookupUsersByName(name = name).map { it.toDbUser(accountKey).toUi() } } - fun getUserLiveData(userKey: MicroBlogKey): LiveData { - return database.userDao().findWithUserKeyLiveData(userKey = userKey).map { + fun getUserFlow(userKey: MicroBlogKey): Flow { + return database.userDao().findWithUserKeyFlow(userKey = userKey).map { it?.toUi() } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/DraftListScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/DraftListScene.kt index 227a2a3a4..d9f3c927e 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/DraftListScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/DraftListScene.kt @@ -34,7 +34,6 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.MoreVert import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -45,6 +44,7 @@ import com.twidere.twiderex.component.foundation.AppBar import com.twidere.twiderex.component.foundation.AppBarNavigationButton import com.twidere.twiderex.component.foundation.InAppNotificationScaffold import com.twidere.twiderex.di.assisted.assistedViewModel +import com.twidere.twiderex.extensions.observeAsState import com.twidere.twiderex.navigation.Route import com.twidere.twiderex.ui.LocalNavController import com.twidere.twiderex.ui.TwidereScene diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/HomeScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/HomeScene.kt index 9c5822505..d9b455c0e 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/HomeScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/HomeScene.kt @@ -61,7 +61,6 @@ import androidx.compose.material.icons.filled.ArrowDropDown import androidx.compose.material.rememberScaffoldState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope @@ -86,6 +85,7 @@ import com.twidere.twiderex.component.lazy.divider import com.twidere.twiderex.component.status.UserAvatar import com.twidere.twiderex.component.status.UserName import com.twidere.twiderex.component.status.UserScreenName +import com.twidere.twiderex.extensions.observeAsState import com.twidere.twiderex.extensions.withElevation import com.twidere.twiderex.model.PlatformType import com.twidere.twiderex.model.ui.UiUser diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/MediaScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/MediaScene.kt index a6c3d34f6..3ac70ed52 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/MediaScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/MediaScene.kt @@ -54,7 +54,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -94,6 +93,7 @@ import com.twidere.twiderex.component.status.UserName import com.twidere.twiderex.component.status.UserScreenName import com.twidere.twiderex.di.assisted.assistedViewModel import com.twidere.twiderex.extensions.hideControls +import com.twidere.twiderex.extensions.observeAsState import com.twidere.twiderex.extensions.setOnSystemBarsVisibilityChangeListener import com.twidere.twiderex.extensions.showControls import com.twidere.twiderex.model.MediaType @@ -117,7 +117,7 @@ fun StatusMediaScene(statusKey: MicroBlogKey, selectedIndex: Int) { val viewModel = assistedViewModel { it.create(account, statusKey) } - val status by viewModel.status.observeAsState() + val status by viewModel.status.observeAsState(null) val loading by viewModel.loading.observeAsState(initial = false) TwidereDialog( requireDarkTheme = true, diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/PureMediaScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/PureMediaScene.kt index b4e5ceeca..69b996589 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/PureMediaScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/PureMediaScene.kt @@ -41,7 +41,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -64,6 +63,7 @@ import com.twidere.twiderex.R import com.twidere.twiderex.component.foundation.InAppNotificationScaffold import com.twidere.twiderex.di.assisted.assistedViewModel import com.twidere.twiderex.extensions.hideControls +import com.twidere.twiderex.extensions.observeAsState import com.twidere.twiderex.extensions.setOnSystemBarsVisibilityChangeListener import com.twidere.twiderex.extensions.showControls import com.twidere.twiderex.model.MediaType @@ -83,7 +83,7 @@ fun PureMediaScene(belongToKey: MicroBlogKey, selectedIndex: Int) { val viewModel = assistedViewModel { it.create(belongToKey) } - val source by viewModel.source.observeAsState() + val source by viewModel.source.observeAsState(null) TwidereDialog( requireDarkTheme = true, extendViewIntoStatusBar = true, diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/StatusScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/StatusScene.kt index b8cd97cc8..92094a797 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/StatusScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/StatusScene.kt @@ -40,7 +40,6 @@ import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -61,6 +60,7 @@ import com.twidere.twiderex.component.status.StatusDivider import com.twidere.twiderex.component.status.StatusThreadStyle import com.twidere.twiderex.component.status.TimelineStatusComponent import com.twidere.twiderex.di.assisted.assistedViewModel +import com.twidere.twiderex.extensions.observeAsState import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.ui.LocalActiveAccount import com.twidere.twiderex.ui.TwidereScene diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/compose/ComposeScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/compose/ComposeScene.kt index 304227e3d..1037a0905 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/compose/ComposeScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/compose/ComposeScene.kt @@ -81,7 +81,6 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue -import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope @@ -120,6 +119,7 @@ import com.twidere.twiderex.component.status.UserName import com.twidere.twiderex.component.status.UserScreenName import com.twidere.twiderex.di.assisted.assistedViewModel import com.twidere.twiderex.extensions.icon +import com.twidere.twiderex.extensions.observeAsState import com.twidere.twiderex.extensions.stringName import com.twidere.twiderex.extensions.withElevation import com.twidere.twiderex.model.AccountDetails @@ -155,7 +155,7 @@ fun DraftComposeScene( assistedViewModel { it.create(draftId = draftId) } - val data by draftItemViewModel.draft.observeAsState() + val data by draftItemViewModel.draft.observeAsState(null) data?.let { draft -> val viewModel = assistedViewModel { @@ -191,9 +191,9 @@ private fun ComposeBody( account: AccountDetails, ) { val composeType = viewModel.composeType - val status by viewModel.status.observeAsState() + val status by viewModel.status.observeAsState(null) val images by viewModel.images.observeAsState(initial = emptyList()) - val location by viewModel.location.observeAsState() + val location by viewModel.location.observeAsState(null) val locationEnabled by viewModel.locationEnabled.observeAsState(initial = false) val navController = LocalNavController.current val textFieldValue by viewModel.textFieldValue.observeAsState(initial = TextFieldValue()) diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/compose/ComposeSearchHashtagScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/compose/ComposeSearchHashtagScene.kt index c0a6bd921..ad4293899 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/compose/ComposeSearchHashtagScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/compose/ComposeSearchHashtagScene.kt @@ -34,7 +34,6 @@ import androidx.compose.material.icons.filled.Done import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource @@ -47,6 +46,7 @@ import com.twidere.twiderex.component.foundation.AppBarNavigationButton import com.twidere.twiderex.component.foundation.InAppNotificationScaffold import com.twidere.twiderex.component.foundation.TextInput import com.twidere.twiderex.component.lazy.loadState +import com.twidere.twiderex.extensions.observeAsState import com.twidere.twiderex.extensions.viewModel import com.twidere.twiderex.ui.LocalActiveAccount import com.twidere.twiderex.ui.LocalNavController diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/compose/ComposeSearchUserScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/compose/ComposeSearchUserScene.kt index fd158a919..6ef9f9b9e 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/compose/ComposeSearchUserScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/compose/ComposeSearchUserScene.kt @@ -32,7 +32,6 @@ import androidx.compose.material.icons.filled.Done import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.Alignment import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction @@ -44,6 +43,7 @@ import com.twidere.twiderex.component.foundation.InAppNotificationScaffold import com.twidere.twiderex.component.foundation.TextInput import com.twidere.twiderex.component.lazy.loadState import com.twidere.twiderex.component.lazy.ui.LazyUiUserList +import com.twidere.twiderex.extensions.observeAsState import com.twidere.twiderex.extensions.viewModel import com.twidere.twiderex.ui.LocalActiveAccount import com.twidere.twiderex.ui.LocalNavController diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/dm/DMConversationScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/dm/DMConversationScene.kt index ad169ac26..b6fe03f43 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/dm/DMConversationScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/dm/DMConversationScene.kt @@ -58,7 +58,6 @@ import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -78,6 +77,7 @@ import com.twidere.twiderex.component.foundation.NetworkImage import com.twidere.twiderex.component.foundation.TextInput import com.twidere.twiderex.component.lazy.ui.LazyUiDMEventList import com.twidere.twiderex.di.assisted.assistedViewModel +import com.twidere.twiderex.extensions.observeAsState import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.ui.UiDMEvent import com.twidere.twiderex.ui.LocalActiveAccount @@ -99,20 +99,20 @@ fun DMConversationScene(conversationKey: MicroBlogKey) { val filePickerLauncher = rememberLauncherForActivityResult( contract = ActivityResultContracts.OpenDocument(), onResult = { - viewModel.inputImage.postValue(it) + viewModel.inputImage.value = it }, ) val source = viewModel.source.collectAsLazyPagingItems() - val conversation by viewModel.conversation.observeAsState() + val conversation by viewModel.conversation.observeAsState(null) val input by viewModel.input.observeAsState(initial = "") - val inputImage by viewModel.inputImage.observeAsState() + val inputImage by viewModel.inputImage.observeAsState(null) val listState = rememberLazyListState() val scope = rememberCoroutineScope() - val firstEventKey by viewModel.firstEventKey.observeAsState() - val pendingActionMessage by viewModel.pendingActionMessage.observeAsState() + val firstEventKey by viewModel.firstEventKey.observeAsState(null) + val pendingActionMessage by viewModel.pendingActionMessage.observeAsState(null) if (source.itemCount > 0) { source.peek(0)?.messageKey?.let { - viewModel.firstEventKey.postValue(it.toString()) + viewModel.firstEventKey.value = it.toString() } } val copyText = stringResource(id = R.string.scene_messages_action_copy_text) @@ -144,12 +144,12 @@ fun DMConversationScene(conversationKey: MicroBlogKey) { }, state = listState, onItemLongClick = { - viewModel.pendingActionMessage.postValue(it) + viewModel.pendingActionMessage.value = it } ) MessageActionComponent( pendingActionMessage = pendingActionMessage, - onDismissRequest = { viewModel.pendingActionMessage.postValue(null) }, + onDismissRequest = { viewModel.pendingActionMessage.value = null }, onCopyText = { event -> clipboardManager?.setPrimaryClip(ClipData.newPlainText(copyText, event.originText)) }, @@ -160,7 +160,7 @@ fun DMConversationScene(conversationKey: MicroBlogKey) { } Divider(modifier = Modifier.fillMaxWidth()) InputPhotoPreview(inputImage) { - viewModel.inputImage.postValue(null) + viewModel.inputImage.value = null } InputComponent( modifier = Modifier.fillMaxWidth(), @@ -169,7 +169,7 @@ fun DMConversationScene(conversationKey: MicroBlogKey) { enableSelectPhoto = inputImage == null, enableSend = input.isNotEmpty() || inputImage != null, input = input, - onValueChanged = { viewModel.input.postValue(it) }, + onValueChanged = { viewModel.input.value = it }, onSend = { viewModel.sendMessage() } ) } diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/dm/DMNewConversationScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/dm/DMNewConversationScene.kt index f9feaec0e..0c8488eb5 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/dm/DMNewConversationScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/dm/DMNewConversationScene.kt @@ -36,7 +36,6 @@ import androidx.compose.material.icons.filled.Close import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource @@ -51,6 +50,7 @@ import com.twidere.twiderex.component.foundation.InAppNotificationScaffold import com.twidere.twiderex.component.foundation.TextInput import com.twidere.twiderex.component.lazy.ui.LazyUiUserList import com.twidere.twiderex.di.assisted.assistedViewModel +import com.twidere.twiderex.extensions.observeAsState import com.twidere.twiderex.model.ui.UiUser import com.twidere.twiderex.navigation.Route import com.twidere.twiderex.ui.LocalActiveAccount @@ -90,7 +90,7 @@ fun DMNewConversationScene() { SearchInput( modifier = Modifier.fillMaxWidth(), input = keyWord, - onValueChanged = { viewModel.input.postValue(it) }, + onValueChanged = { viewModel.input.value = it }, ) Divider() } diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/SearchItem.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/SearchItem.kt index 26c1b377e..578a5e8e0 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/SearchItem.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/SearchItem.kt @@ -41,7 +41,6 @@ import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue -import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -56,6 +55,7 @@ import com.twidere.twiderex.component.navigation.LocalNavigator import com.twidere.twiderex.component.trend.MastodonTrendItem import com.twidere.twiderex.component.trend.TwitterTrendItem import com.twidere.twiderex.di.assisted.assistedViewModel +import com.twidere.twiderex.extensions.observeAsState import com.twidere.twiderex.model.PlatformType import com.twidere.twiderex.ui.LocalActiveAccount import com.twidere.twiderex.viewmodel.search.SearchInputViewModel diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/ListsAddMembersScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/ListsAddMembersScene.kt index 257c3f5d9..920039726 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/ListsAddMembersScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/ListsAddMembersScene.kt @@ -38,7 +38,6 @@ import androidx.compose.material.icons.filled.Close import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -60,6 +59,7 @@ import com.twidere.twiderex.component.foundation.TextInput import com.twidere.twiderex.component.lazy.ui.LazyUiUserList import com.twidere.twiderex.component.navigation.LocalNavigator import com.twidere.twiderex.di.assisted.assistedViewModel +import com.twidere.twiderex.extensions.observeAsState import com.twidere.twiderex.extensions.refreshOrRetry import com.twidere.twiderex.extensions.viewModel import com.twidere.twiderex.model.MicroBlogKey diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/ListsTimelineScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/ListsTimelineScene.kt index b3f3118f8..2cb6164a5 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/ListsTimelineScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/ListsTimelineScene.kt @@ -39,7 +39,6 @@ import androidx.compose.material.icons.filled.MoreVert import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue -import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -61,6 +60,7 @@ import com.twidere.twiderex.component.foundation.LoadingProgress import com.twidere.twiderex.component.foundation.SwipeToRefreshLayout import com.twidere.twiderex.component.lazy.ui.LazyUiStatusList import com.twidere.twiderex.di.assisted.assistedViewModel +import com.twidere.twiderex.extensions.observeAsState import com.twidere.twiderex.model.AccountDetails import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.PlatformType @@ -83,7 +83,7 @@ fun ListTimeLineScene( ) { it.create(account, listKey) } - val source by viewModel.source.observeAsState() + val source by viewModel.source.observeAsState(initial = null) val loading by viewModel.loading.observeAsState(initial = false) var showEditDialog by remember { mutableStateOf(false) diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/platform/MastodonListsCreateDialog.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/platform/MastodonListsCreateDialog.kt index 21d6b7031..cd98f2451 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/platform/MastodonListsCreateDialog.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/platform/MastodonListsCreateDialog.kt @@ -22,7 +22,6 @@ package com.twidere.twiderex.scenes.lists.platform import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -32,6 +31,7 @@ import com.twidere.twiderex.R import com.twidere.twiderex.component.foundation.LoadingProgress import com.twidere.twiderex.component.lists.MastodonListsModifyComponent import com.twidere.twiderex.di.assisted.assistedViewModel +import com.twidere.twiderex.extensions.observeAsState import com.twidere.twiderex.navigation.Route import com.twidere.twiderex.ui.LocalActiveAccount import com.twidere.twiderex.ui.LocalNavController diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/platform/MastodonListsEditDialog.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/platform/MastodonListsEditDialog.kt index e947e62ac..088e27d0a 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/platform/MastodonListsEditDialog.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/platform/MastodonListsEditDialog.kt @@ -22,7 +22,6 @@ package com.twidere.twiderex.scenes.lists.platform import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -32,6 +31,7 @@ import com.twidere.twiderex.R import com.twidere.twiderex.component.foundation.LoadingProgress import com.twidere.twiderex.component.lists.MastodonListsModifyComponent import com.twidere.twiderex.di.assisted.assistedViewModel +import com.twidere.twiderex.extensions.observeAsState import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.ui.LocalActiveAccount import com.twidere.twiderex.viewmodel.lists.ListsModifyViewModel @@ -51,7 +51,7 @@ fun MastodonListsEditDialog(listKey: MicroBlogKey, onDismissRequest: () -> Unit) ) { it.create(account, listKey) } - val source by listsEditViewModel.source.observeAsState() + val source by listsEditViewModel.source.observeAsState(initial = null) val loading by listsEditViewModel.loading.observeAsState(initial = false) source?.let { uiList -> if (loading) { diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/platform/TwitterListsCreateScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/platform/TwitterListsCreateScene.kt index 953af432a..95f1c7b81 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/platform/TwitterListsCreateScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/platform/TwitterListsCreateScene.kt @@ -32,7 +32,6 @@ import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Done import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -45,6 +44,7 @@ import com.twidere.twiderex.component.foundation.InAppNotificationScaffold import com.twidere.twiderex.component.foundation.LoadingProgress import com.twidere.twiderex.component.lists.TwitterListsModifyComponent import com.twidere.twiderex.di.assisted.assistedViewModel +import com.twidere.twiderex.extensions.observeAsState import com.twidere.twiderex.navigation.Route import com.twidere.twiderex.ui.LocalActiveAccount import com.twidere.twiderex.ui.LocalNavController diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/platform/TwitterListsEditScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/platform/TwitterListsEditScene.kt index ef5937caf..7bc362eea 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/platform/TwitterListsEditScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/platform/TwitterListsEditScene.kt @@ -32,7 +32,6 @@ import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Done import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.res.stringResource import androidx.compose.ui.window.Dialog import com.twidere.twiderex.R @@ -42,6 +41,7 @@ import com.twidere.twiderex.component.foundation.InAppNotificationScaffold import com.twidere.twiderex.component.foundation.LoadingProgress import com.twidere.twiderex.component.lists.TwitterListsModifyComponent import com.twidere.twiderex.di.assisted.assistedViewModel +import com.twidere.twiderex.extensions.observeAsState import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.ui.LocalActiveAccount import com.twidere.twiderex.ui.LocalNavController @@ -60,7 +60,7 @@ fun TwitterListsEditScene( it.create(account, listKey) } val loading by listsEditViewModel.loading.observeAsState(initial = false) - val source by listsEditViewModel.source.observeAsState() + val source by listsEditViewModel.source.observeAsState(null) source?.let { uiList -> TwidereScene { val name by listsEditViewModel.editName.observeAsState(uiList.title) diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/mastodon/MastodonSignInScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/mastodon/MastodonSignInScene.kt index 89c2492e8..4636263b5 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/mastodon/MastodonSignInScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/mastodon/MastodonSignInScene.kt @@ -40,7 +40,6 @@ import androidx.compose.material.icons.filled.KeyboardArrowRight import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester @@ -56,6 +55,7 @@ import com.twidere.twiderex.R import com.twidere.twiderex.component.foundation.SignInButton import com.twidere.twiderex.component.foundation.SignInScaffold import com.twidere.twiderex.di.assisted.assistedViewModel +import com.twidere.twiderex.extensions.observeAsState import com.twidere.twiderex.ui.LocalNavController import com.twidere.twiderex.utils.CustomTabSignInChannel import com.twidere.twiderex.viewmodel.mastodon.MastodonSignInViewModel diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/search/SearchInputScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/search/SearchInputScene.kt index 391d08362..33e394e04 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/search/SearchInputScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/search/SearchInputScene.kt @@ -35,7 +35,6 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.History import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -53,6 +52,7 @@ import com.twidere.twiderex.component.foundation.InAppNotificationScaffold import com.twidere.twiderex.component.foundation.TextInput import com.twidere.twiderex.component.navigation.LocalNavigator import com.twidere.twiderex.di.assisted.assistedViewModel +import com.twidere.twiderex.extensions.observeAsState import com.twidere.twiderex.ui.LocalActiveAccount import com.twidere.twiderex.ui.TwidereScene import com.twidere.twiderex.viewmodel.search.SearchInputViewModel diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/search/SearchScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/search/SearchScene.kt index 397612536..36570bda3 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/search/SearchScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/search/SearchScene.kt @@ -41,7 +41,6 @@ import androidx.compose.material.TabRowDefaults import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment @@ -61,6 +60,7 @@ import com.twidere.twiderex.component.foundation.AppBarNavigationButton import com.twidere.twiderex.component.foundation.InAppNotificationScaffold import com.twidere.twiderex.component.navigation.LocalNavigator import com.twidere.twiderex.di.assisted.assistedViewModel +import com.twidere.twiderex.extensions.observeAsState import com.twidere.twiderex.extensions.withElevation import com.twidere.twiderex.model.PlatformType import com.twidere.twiderex.scenes.search.tabs.MastodonSearchHashtagItem diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/AccountManagementScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/AccountManagementScene.kt index af975861d..0b5c19f9d 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/AccountManagementScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/AccountManagementScene.kt @@ -35,7 +35,6 @@ import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.MoreVert import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -48,6 +47,7 @@ import com.twidere.twiderex.component.foundation.InAppNotificationScaffold import com.twidere.twiderex.component.status.UserAvatar import com.twidere.twiderex.component.status.UserName import com.twidere.twiderex.component.status.UserScreenName +import com.twidere.twiderex.extensions.observeAsState import com.twidere.twiderex.navigation.Route import com.twidere.twiderex.ui.LocalActiveAccountViewModel import com.twidere.twiderex.ui.LocalNavController diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/AccountNotificationScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/AccountNotificationScene.kt index 0dad70709..b9a098f26 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/AccountNotificationScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/AccountNotificationScene.kt @@ -35,7 +35,6 @@ import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue -import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource @@ -47,6 +46,7 @@ import com.twidere.twiderex.component.foundation.InAppNotificationScaffold import com.twidere.twiderex.component.status.UserName import com.twidere.twiderex.component.status.UserScreenName import com.twidere.twiderex.di.assisted.assistedViewModel +import com.twidere.twiderex.extensions.observeAsState import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.notification.NotificationChannelSpec import com.twidere.twiderex.notification.notificationChannelId diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/MiscScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/MiscScene.kt index eeee43150..2cbd5352f 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/MiscScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/MiscScene.kt @@ -33,7 +33,6 @@ import androidx.compose.material.Text import androidx.compose.material.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -47,6 +46,7 @@ import com.twidere.twiderex.component.foundation.InAppNotificationScaffold import com.twidere.twiderex.component.lazy.ItemHeader import com.twidere.twiderex.component.navigation.LocalNavigator import com.twidere.twiderex.di.assisted.assistedViewModel +import com.twidere.twiderex.extensions.observeAsState import com.twidere.twiderex.ui.TwidereScene import com.twidere.twiderex.viewmodel.settings.MiscViewModel diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/NotificationScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/NotificationScene.kt index 1d5d2d3e1..3aa3f3aed 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/NotificationScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/NotificationScene.kt @@ -35,7 +35,6 @@ import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue -import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import com.twidere.twiderex.R @@ -48,6 +47,7 @@ import com.twidere.twiderex.component.status.UserAvatar import com.twidere.twiderex.component.status.UserName import com.twidere.twiderex.component.status.UserScreenName import com.twidere.twiderex.di.assisted.assistedViewModel +import com.twidere.twiderex.extensions.observeAsState import com.twidere.twiderex.navigation.Route import com.twidere.twiderex.ui.LocalActiveAccountViewModel import com.twidere.twiderex.ui.LocalNavController diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/StorageScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/StorageScene.kt index b33442637..19874e669 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/StorageScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/StorageScene.kt @@ -30,7 +30,6 @@ import androidx.compose.material.ListItem import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource @@ -41,6 +40,7 @@ import com.twidere.twiderex.component.foundation.AppBar import com.twidere.twiderex.component.foundation.AppBarNavigationButton import com.twidere.twiderex.component.foundation.InAppNotificationScaffold import com.twidere.twiderex.di.assisted.assistedViewModel +import com.twidere.twiderex.extensions.observeAsState import com.twidere.twiderex.ui.TwidereScene import com.twidere.twiderex.viewmodel.settings.StorageViewModel diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/twitter/TwitterSigninScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/twitter/TwitterSigninScene.kt index 39f2777ad..574022f4d 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/twitter/TwitterSigninScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/twitter/TwitterSigninScene.kt @@ -25,11 +25,11 @@ import androidx.browser.customtabs.CustomTabsIntent import androidx.compose.material.CircularProgressIndicator import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.platform.LocalContext import com.twidere.twiderex.component.foundation.SignInScaffold import com.twidere.twiderex.component.navigation.LocalNavigator import com.twidere.twiderex.di.assisted.assistedViewModel +import com.twidere.twiderex.extensions.observeAsState import com.twidere.twiderex.ui.LocalNavController import com.twidere.twiderex.utils.CustomTabSignInChannel import com.twidere.twiderex.viewmodel.twitter.TwitterSignInViewModel diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/twitter/user/TwitterUserScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/twitter/user/TwitterUserScene.kt index 8d55cfb09..5c6e938d7 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/twitter/user/TwitterUserScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/twitter/user/TwitterUserScene.kt @@ -30,13 +30,13 @@ import androidx.compose.material.icons.filled.Error import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.twidere.twiderex.component.foundation.InAppNotificationScaffold import com.twidere.twiderex.component.navigation.LocalNavigator import com.twidere.twiderex.di.assisted.assistedViewModel +import com.twidere.twiderex.extensions.observeAsState import com.twidere.twiderex.navigation.Route import com.twidere.twiderex.ui.LocalActiveAccount import com.twidere.twiderex.ui.TwidereScene diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/user/UserScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/user/UserScene.kt index fc84b2a76..45dd04c28 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/user/UserScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/user/UserScene.kt @@ -25,7 +25,6 @@ import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp @@ -36,6 +35,7 @@ import com.twidere.twiderex.component.foundation.AppBarNavigationButton import com.twidere.twiderex.component.foundation.InAppNotificationScaffold import com.twidere.twiderex.component.status.UserName import com.twidere.twiderex.di.assisted.assistedViewModel +import com.twidere.twiderex.extensions.observeAsState import com.twidere.twiderex.extensions.withElevation import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.PlatformType diff --git a/app/src/main/kotlin/com/twidere/twiderex/utils/Event.kt b/app/src/main/kotlin/com/twidere/twiderex/utils/Event.kt index 0fe2030f8..6cd3898ce 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/utils/Event.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/utils/Event.kt @@ -23,7 +23,7 @@ package com.twidere.twiderex.utils import androidx.lifecycle.Observer /** - * Used as a wrapper for data that is exposed via a LiveData that represents an event. + * Used as a wrapper for data that is exposed via a Flow that represents an event. */ open class Event(private val content: T) { diff --git a/app/src/main/kotlin/com/twidere/twiderex/utils/MastodonEmojiCache.kt b/app/src/main/kotlin/com/twidere/twiderex/utils/MastodonEmojiCache.kt index 9b4ac8521..e88d472f8 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/utils/MastodonEmojiCache.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/utils/MastodonEmojiCache.kt @@ -20,18 +20,18 @@ */ package com.twidere.twiderex.utils -import androidx.lifecycle.LiveData -import androidx.lifecycle.liveData import com.twidere.services.mastodon.MastodonService import com.twidere.twiderex.model.AccountDetails import com.twidere.twiderex.model.ui.UiEmoji import com.twidere.twiderex.model.ui.UiEmoji.Companion.toUi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow object MastodonEmojiCache { - private val items = hashMapOf>>() - fun get(account: AccountDetails): LiveData> { + private val items = hashMapOf>>() + fun get(account: AccountDetails): Flow> { return items.getOrPut(account.accountKey.host) { - liveData { + flow { account.service.let { it as MastodonService }.let { diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/ActiveAccountViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/ActiveAccountViewModel.kt index ff0d15d5c..96ce556de 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/ActiveAccountViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/ActiveAccountViewModel.kt @@ -21,7 +21,6 @@ package com.twidere.twiderex.viewmodel import androidx.lifecycle.ViewModel -import androidx.lifecycle.liveData import com.twidere.twiderex.model.AccountDetails import com.twidere.twiderex.model.PlatformType import com.twidere.twiderex.repository.AccountRepository @@ -41,15 +40,13 @@ class ActiveAccountViewModel @Inject constructor( } fun getTargetPlatformDefault(type: PlatformType): AccountDetails? { - return repository.accounts.value?.let { - it.sortedByDescending { it.lastActive }.firstOrNull { it.type == type } - } + return repository.getFirstByType(type) } - val account = liveData { - emitSource(repository.activeAccount) + val account by lazy { + repository.activeAccount } - val allAccounts = liveData { - emitSource(repository.accounts) + val allAccounts by lazy { + repository.accounts } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/MediaViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/MediaViewModel.kt index 38e435232..729767ac0 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/MediaViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/MediaViewModel.kt @@ -21,9 +21,7 @@ package com.twidere.twiderex.viewmodel import android.net.Uri -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel -import androidx.lifecycle.liveData import androidx.lifecycle.viewModelScope import androidx.work.WorkManager import com.twidere.services.microblog.LookupService @@ -36,6 +34,7 @@ import com.twidere.twiderex.utils.notify import com.twidere.twiderex.worker.DownloadMediaWorker import dagger.assisted.Assisted import dagger.assisted.AssistedInject +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch class MediaViewModel @AssistedInject constructor( @@ -63,13 +62,11 @@ class MediaViewModel @AssistedInject constructor( fun create(account: AccountDetails, statusKey: MicroBlogKey): MediaViewModel } - val loading = MutableLiveData(false) - val status = liveData { - emitSource( - repository.loadStatus( - statusKey = statusKey, - accountKey = account.accountKey, - ) + val loading = MutableStateFlow(false) + val status by lazy { + repository.loadStatus( + statusKey = statusKey, + accountKey = account.accountKey, ) } diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/PureMediaViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/PureMediaViewModel.kt index 57eaf85c6..a326512c1 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/PureMediaViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/PureMediaViewModel.kt @@ -21,11 +21,11 @@ package com.twidere.twiderex.viewmodel import androidx.lifecycle.ViewModel -import androidx.lifecycle.liveData import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.repository.MediaRepository import dagger.assisted.Assisted import dagger.assisted.AssistedInject +import kotlinx.coroutines.flow.flow class PureMediaViewModel @AssistedInject constructor( private val mediaRepository: MediaRepository, @@ -36,7 +36,7 @@ class PureMediaViewModel @AssistedInject constructor( fun create(belongKey: MicroBlogKey): PureMediaViewModel } - val source = liveData { + val source = flow { emit(mediaRepository.findMediaByBelongToKey(belongKey)) } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/compose/ComposeSearchUserViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/compose/ComposeSearchUserViewModel.kt index 4973fed5a..530c6879d 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/compose/ComposeSearchUserViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/compose/ComposeSearchUserViewModel.kt @@ -20,9 +20,7 @@ */ package com.twidere.twiderex.viewmodel.compose -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel -import androidx.lifecycle.asFlow import androidx.lifecycle.viewModelScope import androidx.paging.Pager import androidx.paging.PagingConfig @@ -32,16 +30,17 @@ import com.twidere.twiderex.defaultLoadCount import com.twidere.twiderex.model.AccountDetails import com.twidere.twiderex.paging.source.SearchUserPagingSource import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.map class ComposeSearchUserViewModel( private val account: AccountDetails, ) : ViewModel() { - val text = MutableLiveData("") + val text = MutableStateFlow("") @OptIn(FlowPreview::class) - val sourceFlow = text.asFlow().debounce(666L).map { + val sourceFlow = text.debounce(666L).map { it.takeIf { it.isNotEmpty() }?.let { Pager( config = PagingConfig( diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/compose/ComposeViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/compose/ComposeViewModel.kt index d0f2ec1cc..7adb295fc 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/compose/ComposeViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/compose/ComposeViewModel.kt @@ -32,11 +32,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextRange import androidx.compose.ui.text.input.TextFieldValue -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel -import androidx.lifecycle.liveData -import androidx.lifecycle.map -import androidx.lifecycle.switchMap import androidx.work.WorkManager import com.twidere.services.mastodon.model.Emoji import com.twidere.services.mastodon.model.Visibility @@ -44,7 +40,6 @@ import com.twidere.services.microblog.LookupService import com.twidere.twiderex.R import com.twidere.twiderex.action.ComposeAction import com.twidere.twiderex.db.model.DbDraft -import com.twidere.twiderex.extensions.combineWith import com.twidere.twiderex.extensions.getCachedLocation import com.twidere.twiderex.extensions.getTextAfterSelection import com.twidere.twiderex.extensions.getTextBeforeSelection @@ -63,6 +58,11 @@ import com.twidere.twiderex.worker.draft.SaveDraftWorker import com.twitter.twittertext.Extractor import dagger.assisted.Assisted import dagger.assisted.AssistedInject +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.emitAll +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.zip import java.util.UUID enum class ComposeType { @@ -77,7 +77,7 @@ class DraftItemViewModel @AssistedInject constructor( @Assisted private val draftId: String, ) : ViewModel() { - val draft = liveData { + val draft = flow { repository.get(draftId)?.let { emit(it) } @@ -119,7 +119,7 @@ class DraftComposeViewModel @AssistedInject constructor( init { setText(TextFieldValue(draft.content)) putImages(draft.media.map { Uri.parse(it) }) - excludedReplyUserIds.postValue(draft.excludedReplyUserIds ?: emptyList()) + excludedReplyUserIds.value = draft.excludedReplyUserIds ?: emptyList() } @dagger.assisted.AssistedFactory @@ -132,7 +132,7 @@ class DraftComposeViewModel @AssistedInject constructor( } class VoteOption { - val text = MutableLiveData("") + val text = MutableStateFlow("") fun setText(value: String) { text.value = value } @@ -162,9 +162,9 @@ enum class VoteExpired(val value: Long) { } class VoteState { - val options = MutableLiveData(arrayListOf(VoteOption(), VoteOption())) - val expired = MutableLiveData(VoteExpired.Day_1) - val multiple = MutableLiveData(false) + val options = MutableStateFlow(arrayListOf(VoteOption(), VoteOption())) + val expired = MutableStateFlow(VoteExpired.Day_1) + val multiple = MutableStateFlow(false) fun setMultiple(value: Boolean) { multiple.value = value @@ -175,7 +175,7 @@ class VoteState { } fun setOption(value: String, index: Int) { - options.value?.let { + options.value.let { it[index].setText(value) if (index == it.lastIndex && it.size < 4 && value.isNotEmpty()) { it.add(VoteOption()) @@ -222,12 +222,12 @@ open class ComposeViewModel @AssistedInject constructor( draftRepository.sourceCount } - val location = MutableLiveData() - val excludedReplyUserIds = MutableLiveData>(emptyList()) + val location = MutableStateFlow(null) + val excludedReplyUserIds = MutableStateFlow>(emptyList()) - val replyToUserName = liveData { + val replyToUserName = flow { if (account.type == PlatformType.Twitter && composeType == ComposeType.Reply && statusKey != null) { - emitSource( + emitAll( status.map { it?.let { status -> Extractor().extractMentionedScreennames( @@ -243,51 +243,47 @@ open class ComposeViewModel @AssistedInject constructor( } } - val loadingReplyUser = MutableLiveData(false) - - val replyToUser = liveData { - emitSource( - replyToUserName.switchMap { - liveData { - if (it.isNotEmpty()) { - loadingReplyUser.postValue(true) - runCatching { - userRepository.lookupUsersByName( - it, - accountKey = account.accountKey, - lookupService = account.service as LookupService, - ) - }.onFailure { - it.notify(inAppNotification) - }.onSuccess { - emit(it) - } - loadingReplyUser.postValue(false) - } - } - }, - ) - } - - val voteState = MutableLiveData(null) - val isInVoteMode = MutableLiveData(false) - val visibility = MutableLiveData(Visibility.Public) - val isImageSensitive = MutableLiveData(false) - val isContentWarningEnabled = MutableLiveData(false) - val contentWarningTextFieldValue = MutableLiveData(TextFieldValue()) - val textFieldValue = MutableLiveData(TextFieldValue()) - val images = MutableLiveData>(emptyList()) + val loadingReplyUser = MutableStateFlow(false) + + val replyToUser = replyToUserName.map { + loadingReplyUser.value = true + if (it.isNotEmpty()) { + try { + userRepository.lookupUsersByName( + it, + accountKey = account.accountKey, + lookupService = account.service as LookupService, + ) + } catch (e: Throwable) { + e.notify(inAppNotification) + emptyList() + } finally { + loadingReplyUser.value = false + } + } else { + emptyList() + } + } + + val voteState = MutableStateFlow(null) + val isInVoteMode = MutableStateFlow(false) + val visibility = MutableStateFlow(Visibility.Public) + val isImageSensitive = MutableStateFlow(false) + val isContentWarningEnabled = MutableStateFlow(false) + val contentWarningTextFieldValue = MutableStateFlow(TextFieldValue()) + val textFieldValue = MutableStateFlow(TextFieldValue()) + val images = MutableStateFlow>(emptyList()) val canSaveDraft = - textFieldValue.combineWith(images) { text, imgs -> !text?.text.isNullOrEmpty() || !imgs.isNullOrEmpty() } - val locationEnabled = MutableLiveData(false) - val enableThreadMode = MutableLiveData(composeType == ComposeType.Thread) - val status = liveData { + textFieldValue.zip(images) { text, imgs -> text.text.isNotEmpty() || !imgs.isNullOrEmpty() } + val locationEnabled = MutableStateFlow(false) + val enableThreadMode = MutableStateFlow(composeType == ComposeType.Thread) + val status = flow { statusKey?.let { statusKey -> - emitSource( + emitAll( repository.loadStatus(statusKey, accountKey = account.accountKey) .map { status -> if (status != null && - textFieldValue.value?.text.isNullOrEmpty() && + textFieldValue.value.text.isEmpty() && status.platformType == PlatformType.Mastodon && status.mastodonExtra?.mentions != null && composeType == ComposeType.Reply @@ -353,7 +349,7 @@ open class ComposeViewModel @AssistedInject constructor( } fun compose() { - textFieldValue.value?.text?.let { + textFieldValue.value.text.let { composeAction.commit( account.accountKey, account.type, @@ -363,7 +359,7 @@ open class ComposeViewModel @AssistedInject constructor( } fun saveDraft() { - textFieldValue.value?.text?.let { text -> + textFieldValue.value.text.let { text -> workManager .beginWith( SaveDraftWorker.create( @@ -377,26 +373,26 @@ open class ComposeViewModel @AssistedInject constructor( private fun buildComposeData(text: String) = ComposeData( content = text, draftId = draftId, - images = images.value?.map { it.toString() } ?: emptyList(), + images = images.value.map { it.toString() }, composeType = composeType, statusKey = statusKey, lat = location.value?.latitude, long = location.value?.longitude, excludedReplyUserIds = excludedReplyUserIds.value, - voteOptions = voteState.value?.options?.value?.map { it.text.value.toString() }, + voteOptions = voteState.value?.options?.value?.map { it.text.value }, voteExpired = voteState.value?.expired?.value, voteMultiple = voteState.value?.multiple?.value, visibility = visibility.value, isSensitive = isImageSensitive.value, - contentWarningText = contentWarningTextFieldValue.value?.text, - isThreadMode = enableThreadMode.value ?: false, + contentWarningText = contentWarningTextFieldValue.value.text, + isThreadMode = enableThreadMode.value, ) fun putImages(value: List) { - images.value?.let { + images.value.let { value + it - }?.take(imageLimit)?.let { - images.postValue(it) + }.take(imageLimit).let { + images.value = it } } @@ -410,24 +406,24 @@ open class ComposeViewModel @AssistedInject constructor( @RequiresPermission(anyOf = [permission.ACCESS_COARSE_LOCATION, permission.ACCESS_FINE_LOCATION]) fun trackingLocation() { - locationEnabled.postValue(true) + locationEnabled.value = true val criteria = Criteria() criteria.accuracy = Criteria.ACCURACY_FINE val provider = locationManager.getBestProvider(criteria, true) ?: return locationManager.requestLocationUpdates(provider, 0, 0f, this) locationManager.getCachedLocation()?.let { - location.postValue(it) + location.value = it } } fun disableLocation() { - location.postValue(null) - locationEnabled.postValue(false) + location.value = null + locationEnabled.value = false locationManager.removeUpdates(this) } override fun onLocationChanged(location: Location) { - this.location.postValue(location) + this.location.value = location } // compatibility fix for Api < 22 @@ -435,33 +431,33 @@ open class ComposeViewModel @AssistedInject constructor( } override fun onCleared() { - if (locationEnabled.value == true) { + if (locationEnabled.value) { locationManager.removeUpdates(this) } } fun removeImage(item: Uri) { - images.value?.let { + images.value.let { it - item - }?.let { - images.postValue(it) + }.let { + images.value = it } } fun excludeReplyUser(user: UiUser) { - excludedReplyUserIds.value?.let { - excludedReplyUserIds.postValue(it + user.id) + excludedReplyUserIds.value.let { + excludedReplyUserIds.value = it + user.id } } fun includeReplyUser(user: UiUser) { - excludedReplyUserIds.value?.let { - excludedReplyUserIds.postValue(it - user.id) + excludedReplyUserIds.value.let { + excludedReplyUserIds.value = it - user.id } } fun insertText(result: String) { - textFieldValue.value?.let { + textFieldValue.value.let { setText( it.copy( text = "${it.getTextBeforeSelection()}${result}${it.getTextAfterSelection()}", @@ -472,8 +468,6 @@ open class ComposeViewModel @AssistedInject constructor( } fun insertEmoji(emoji: Emoji) { - textFieldValue.value?.let { textFieldValue -> - insertText("${if (textFieldValue.selection.start != 0) " " else ""}:${emoji.shortcode}: ") - } + insertText("${if (textFieldValue.value.selection.start != 0) " " else ""}:${emoji.shortcode}: ") } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/compose/MastodonComposeSearchHashtagViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/compose/MastodonComposeSearchHashtagViewModel.kt index 9e5c044a9..8616299fd 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/compose/MastodonComposeSearchHashtagViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/compose/MastodonComposeSearchHashtagViewModel.kt @@ -20,9 +20,7 @@ */ package com.twidere.twiderex.viewmodel.compose -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel -import androidx.lifecycle.asFlow import androidx.lifecycle.viewModelScope import androidx.paging.Pager import androidx.paging.PagingConfig @@ -32,16 +30,17 @@ import com.twidere.twiderex.defaultLoadCount import com.twidere.twiderex.model.AccountDetails import com.twidere.twiderex.paging.source.MastodonSearchHashtagPagingSource import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.map class MastodonComposeSearchHashtagViewModel( private val account: AccountDetails, ) : ViewModel() { - val text = MutableLiveData("") + val text = MutableStateFlow("") @OptIn(FlowPreview::class) - val sourceFlow = text.asFlow().debounce(666L).map { + val sourceFlow = text.debounce(666L).map { it.takeIf { it.isNotEmpty() }?.let { Pager( config = PagingConfig( diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/dm/DMEventViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/dm/DMEventViewModel.kt index 1cebcdb6f..65e31ff23 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/dm/DMEventViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/dm/DMEventViewModel.kt @@ -21,9 +21,7 @@ package com.twidere.twiderex.viewmodel.dm import android.net.Uri -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel -import androidx.lifecycle.liveData import androidx.lifecycle.viewModelScope import androidx.paging.cachedIn import com.twidere.services.microblog.DirectMessageService @@ -38,6 +36,9 @@ import com.twidere.twiderex.model.ui.UiDMEvent import com.twidere.twiderex.repository.DirectMessageRepository import dagger.assisted.Assisted import dagger.assisted.AssistedInject +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.launch import java.util.UUID class DMEventViewModel @AssistedInject constructor( @@ -53,14 +54,10 @@ class DMEventViewModel @AssistedInject constructor( } val conversation by lazy { - liveData { - emitSource( - repository.dmConversation( - accountKey = account.accountKey, - conversationKey = conversationKey - ) - ) - } + repository.dmConversation( + accountKey = account.accountKey, + conversationKey = conversationKey + ) } val source by lazy { @@ -73,32 +70,37 @@ class DMEventViewModel @AssistedInject constructor( } // input - val input = MutableLiveData("") - val inputImage = MutableLiveData() - val firstEventKey = MutableLiveData(null) - val pendingActionMessage = MutableLiveData(null) + val input = MutableStateFlow("") + val inputImage = MutableStateFlow(null) + val firstEventKey = MutableStateFlow(null) + val pendingActionMessage = MutableStateFlow(null) fun sendMessage() { - if (input.value.isNullOrEmpty() && inputImage.value == null) return - conversation.value?.let { - messageAction.send( - account.type, - data = DirectMessageSendData( - text = input.value, - images = inputImage.value?.toString()?.let { uri -> listOf(uri) } ?: emptyList(), - recipientUserKey = it.recipientKey, - draftMessageKey = when (account.type) { - PlatformType.Twitter -> MicroBlogKey.twitter(UUID.randomUUID().toString()) - PlatformType.StatusNet -> TODO() - PlatformType.Fanfou -> TODO() - PlatformType.Mastodon -> MicroBlogKey.Empty - }, - conversationKey = it.conversationKey, - accountKey = account.accountKey + if (input.value.isEmpty() && inputImage.value == null) return + viewModelScope.launch { + conversation.firstOrNull()?.let { + messageAction.send( + account.type, + data = DirectMessageSendData( + text = input.value, + images = inputImage.value?.toString()?.let { uri -> listOf(uri) } + ?: emptyList(), + recipientUserKey = it.recipientKey, + draftMessageKey = when (account.type) { + PlatformType.Twitter -> MicroBlogKey.twitter( + UUID.randomUUID().toString() + ) + PlatformType.StatusNet -> TODO() + PlatformType.Fanfou -> TODO() + PlatformType.Mastodon -> MicroBlogKey.Empty + }, + conversationKey = it.conversationKey, + accountKey = account.accountKey + ) ) - ) - input.postValue("") - inputImage.postValue(null) + input.value = "" + inputImage.value = null + } } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/dm/DMNewConversationViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/dm/DMNewConversationViewModel.kt index a474fe602..fcd430593 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/dm/DMNewConversationViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/dm/DMNewConversationViewModel.kt @@ -20,9 +20,7 @@ */ package com.twidere.twiderex.viewmodel.dm -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel -import androidx.lifecycle.asFlow import androidx.lifecycle.viewModelScope import androidx.paging.Pager import androidx.paging.PagingConfig @@ -37,6 +35,7 @@ import com.twidere.twiderex.repository.DirectMessageRepository import dagger.assisted.Assisted import dagger.assisted.AssistedInject import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch @@ -51,10 +50,10 @@ class DMNewConversationViewModel @AssistedInject constructor( fun create(account: AccountDetails): DMNewConversationViewModel } - val input = MutableLiveData("") + val input = MutableStateFlow("") @OptIn(FlowPreview::class) - val sourceFlow = input.asFlow().debounce(666L).map { + val sourceFlow = input.debounce(666L).map { it.takeIf { it.isNotEmpty() }?.let { Pager( config = PagingConfig( diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/lists/ListsSearchUserViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/lists/ListsSearchUserViewModel.kt index 3fe185245..3e09eed34 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/lists/ListsSearchUserViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/lists/ListsSearchUserViewModel.kt @@ -20,9 +20,7 @@ */ package com.twidere.twiderex.viewmodel.lists -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel -import androidx.lifecycle.asFlow import androidx.lifecycle.viewModelScope import androidx.paging.Pager import androidx.paging.PagingConfig @@ -32,6 +30,7 @@ import com.twidere.twiderex.defaultLoadCount import com.twidere.twiderex.model.AccountDetails import com.twidere.twiderex.paging.source.SearchUserPagingSource import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.map @@ -39,10 +38,10 @@ class ListsSearchUserViewModel( private val account: AccountDetails, following: Boolean = false, ) : ViewModel() { - val text = MutableLiveData("") + val text = MutableStateFlow("") @OptIn(FlowPreview::class) - val sourceFlow = text.asFlow().debounce(666L).map { + val sourceFlow = text.debounce(666L).map { it.takeIf { it.isNotEmpty() }?.let { Pager( config = PagingConfig( diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/lists/ListsUserViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/lists/ListsUserViewModel.kt index 55d48a955..5a8be9139 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/lists/ListsUserViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/lists/ListsUserViewModel.kt @@ -21,7 +21,6 @@ package com.twidere.twiderex.viewmodel.lists import androidx.compose.runtime.mutableStateMapOf -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.paging.PagingData @@ -36,6 +35,7 @@ import com.twidere.twiderex.viewmodel.user.UserListViewModel import dagger.assisted.Assisted import dagger.assisted.AssistedInject import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch class ListsUserViewModel @AssistedInject constructor( @@ -89,12 +89,12 @@ class ListsAddMemberViewModel @AssistedInject constructor( fun create(account: AccountDetails, listId: String): ListsAddMemberViewModel } - val loading = MutableLiveData(false) + val loading = MutableStateFlow(false) val pendingMap = mutableStateMapOf() fun addToOrRemove(user: UiUser) { if (pendingMap[user.userKey] == null) { - loading.postValue(true) + loading.value = true loadingRequest { listsUsersRepository.addMember( listId = listId, @@ -120,15 +120,15 @@ class ListsAddMemberViewModel @AssistedInject constructor( } private fun loadingRequest(request: suspend () -> Unit) { - loading.postValue(true) + loading.value = true viewModelScope.launch { runCatching { request() }.onFailure { it.notify(inAppNotification) - loading.postValue(false) + loading.value = false }.onSuccess { - loading.postValue(false) + loading.value = false } } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/lists/ListsViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/lists/ListsViewModel.kt index 99ba73d61..353ec126f 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/lists/ListsViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/lists/ListsViewModel.kt @@ -20,9 +20,6 @@ */ package com.twidere.twiderex.viewmodel.lists -import androidx.compose.runtime.getValue -import androidx.compose.runtime.setValue -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.paging.cachedIn @@ -36,6 +33,7 @@ import com.twidere.twiderex.repository.ListsRepository import com.twidere.twiderex.utils.notify import dagger.assisted.Assisted import dagger.assisted.AssistedInject +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch @@ -68,23 +66,23 @@ class ListsViewModel @AssistedInject constructor( abstract class ListsOperatorViewModel( protected val inAppNotification: InAppNotification, ) : ViewModel() { - val modifySuccess = MutableLiveData(false) - val loading = MutableLiveData(false) + val modifySuccess = MutableStateFlow(false) + val loading = MutableStateFlow(false) protected fun loadingRequest(onResult: (success: Boolean, list: UiList?) -> Unit, request: suspend () -> UiList?) { - loading.postValue(true) + loading.value = true viewModelScope.launch { runCatching { val result = request() - modifySuccess.postValue(true) + modifySuccess.value = true onResult(true, result) }.onFailure { it.notify(inAppNotification) - modifySuccess.postValue(false) + modifySuccess.value = false onResult(false, null) - loading.postValue(false) + loading.value = false }.onSuccess { - loading.postValue(false) + loading.value = false } } } @@ -133,9 +131,9 @@ class ListsModifyViewModel @AssistedInject constructor( listsRepository.findListWithListKey(account = account, listKey = listKey) } - val editName = MutableLiveData() - var editDesc = MutableLiveData() - var editPrivate = MutableLiveData() + val editName = MutableStateFlow("") + var editDesc = MutableStateFlow("") + var editPrivate = MutableStateFlow(false) fun editList( listId: String = listKey.id, diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/mastodon/MastodonSignInViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/mastodon/MastodonSignInViewModel.kt index 1ba30c641..62dacb535 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/mastodon/MastodonSignInViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/mastodon/MastodonSignInViewModel.kt @@ -22,7 +22,6 @@ package com.twidere.twiderex.viewmodel.mastodon import android.accounts.Account import androidx.compose.ui.text.input.TextFieldValue -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.twidere.services.mastodon.MastodonOAuthService @@ -39,6 +38,7 @@ import com.twidere.twiderex.repository.ACCOUNT_TYPE import com.twidere.twiderex.repository.AccountRepository import com.twidere.twiderex.utils.json import dagger.assisted.AssistedInject +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch class MastodonSignInViewModel @AssistedInject constructor( @@ -51,8 +51,8 @@ class MastodonSignInViewModel @AssistedInject constructor( fun create(): MastodonSignInViewModel } - val loading = MutableLiveData(false) - val host = MutableLiveData(TextFieldValue()) + val loading = MutableStateFlow(false) + val host = MutableStateFlow(TextFieldValue()) fun setHost(value: TextFieldValue) { host.value = value } @@ -62,7 +62,7 @@ class MastodonSignInViewModel @AssistedInject constructor( codeProvider: suspend (url: String) -> String?, finished: (success: Boolean) -> Unit, ) = viewModelScope.launch { - loading.postValue(true) + loading.value = true runCatching { val service = MastodonOAuthService( host = "https://$host", @@ -114,7 +114,7 @@ class MastodonSignInViewModel @AssistedInject constructor( }.onFailure { inAppNotification.show(it.message.toString()) } - loading.postValue(false) + loading.value = false finished.invoke(false) } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/search/SearchInputViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/search/SearchInputViewModel.kt index 5c891a932..6c63fc312 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/search/SearchInputViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/search/SearchInputViewModel.kt @@ -20,15 +20,14 @@ */ package com.twidere.twiderex.viewmodel.search -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel -import androidx.lifecycle.liveData import androidx.lifecycle.viewModelScope import com.twidere.twiderex.db.model.DbSearch import com.twidere.twiderex.model.AccountDetails import com.twidere.twiderex.repository.SearchRepository import dagger.assisted.Assisted import dagger.assisted.AssistedInject +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch class SearchInputViewModel @AssistedInject constructor( @@ -41,15 +40,15 @@ class SearchInputViewModel @AssistedInject constructor( fun create(account: AccountDetails): SearchInputViewModel } - val source = liveData { - emitSource(repository.searchHistory(account.accountKey)) + val source by lazy { + repository.searchHistory(account.accountKey) } - val savedSource = liveData { - emitSource(repository.savedSearch(account.accountKey)) + val savedSource by lazy { + repository.savedSearch(account.accountKey) } - val expandSearch = MutableLiveData(false) + val expandSearch = MutableStateFlow(false) fun remove(item: DbSearch) = viewModelScope.launch { repository.remove(item) diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/search/SearchSaveViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/search/SearchSaveViewModel.kt index 9c5a9bf0d..7ee9cf9f7 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/search/SearchSaveViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/search/SearchSaveViewModel.kt @@ -20,13 +20,13 @@ */ package com.twidere.twiderex.viewmodel.search -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.twidere.twiderex.model.AccountDetails import com.twidere.twiderex.repository.SearchRepository import dagger.assisted.Assisted import dagger.assisted.AssistedInject +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch class SearchSaveViewModel @AssistedInject constructor( @@ -40,26 +40,26 @@ class SearchSaveViewModel @AssistedInject constructor( fun create(account: AccountDetails, content: String): SearchSaveViewModel } - val loading = MutableLiveData(false) + val loading = MutableStateFlow(false) - val isSaved = MutableLiveData(false) + val isSaved = MutableStateFlow(false) init { viewModelScope.launch { - isSaved.postValue(repository.get(content, account.accountKey)?.saved ?: false) + isSaved.value = repository.get(content, account.accountKey)?.saved ?: false } } fun save() { viewModelScope.launch { - loading.postValue(true) + loading.value = true try { repository.addOrUpgrade(content = content, accountKey = account.accountKey, saved = true) - isSaved.postValue(true) + isSaved.value = true } catch (e: Exception) { - isSaved.postValue(false) + isSaved.value = false } finally { - loading.postValue(false) + loading.value = false } } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/settings/AccountNotificationViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/settings/AccountNotificationViewModel.kt index bb7a0f90e..8a74cbbc4 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/settings/AccountNotificationViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/settings/AccountNotificationViewModel.kt @@ -21,13 +21,12 @@ package com.twidere.twiderex.viewmodel.settings import androidx.lifecycle.ViewModel -import androidx.lifecycle.asLiveData -import androidx.lifecycle.map import androidx.lifecycle.viewModelScope import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.repository.AccountRepository import dagger.assisted.Assisted import dagger.assisted.AssistedInject +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch class AccountNotificationViewModel @AssistedInject constructor( @@ -53,7 +52,7 @@ class AccountNotificationViewModel @AssistedInject constructor( } val isNotificationEnabled by lazy { - preferences.isNotificationEnabled.asLiveData() + preferences.isNotificationEnabled } fun setIsNotificationEnabled(value: Boolean) = viewModelScope.launch { diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/settings/MiscViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/settings/MiscViewModel.kt index b0e1b7beb..0719d28fb 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/settings/MiscViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/settings/MiscViewModel.kt @@ -21,12 +21,12 @@ package com.twidere.twiderex.viewmodel.settings import androidx.datastore.core.DataStore -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.twidere.twiderex.preferences.proto.MiscPreferences import dagger.assisted.AssistedInject import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch @@ -40,7 +40,7 @@ class MiscViewModel @AssistedInject constructor( } val nitter by lazy { - MutableLiveData("") + MutableStateFlow("") } init { diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/settings/NotificationViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/settings/NotificationViewModel.kt index 6ed6d5675..d02b48bbc 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/settings/NotificationViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/settings/NotificationViewModel.kt @@ -22,11 +22,10 @@ package com.twidere.twiderex.viewmodel.settings import androidx.datastore.core.DataStore import androidx.lifecycle.ViewModel -import androidx.lifecycle.asLiveData -import androidx.lifecycle.map import androidx.lifecycle.viewModelScope import com.twidere.twiderex.preferences.proto.NotificationPreferences import dagger.assisted.AssistedInject +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch class NotificationViewModel @AssistedInject constructor( @@ -36,7 +35,7 @@ class NotificationViewModel @AssistedInject constructor( interface AssistedFactory { fun create(): NotificationViewModel } - val enabled = notification.data.asLiveData().map { + val enabled = notification.data.map { it.enableNotification } diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/settings/StorageViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/settings/StorageViewModel.kt index a401d38e0..243398233 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/settings/StorageViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/settings/StorageViewModel.kt @@ -20,11 +20,11 @@ */ package com.twidere.twiderex.viewmodel.settings -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.twidere.twiderex.repository.CacheRepository import dagger.assisted.AssistedInject +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch class StorageViewModel @AssistedInject constructor( @@ -36,24 +36,24 @@ class StorageViewModel @AssistedInject constructor( fun create(): StorageViewModel } - val loading = MutableLiveData(false) + val loading = MutableStateFlow(false) fun clearDatabaseCache() = viewModelScope.launch { - loading.postValue(true) + loading.value = true repository.clearDatabaseCache() - loading.postValue(false) + loading.value = false } fun clearImageCache() = viewModelScope.launch { - loading.postValue(true) + loading.value = true repository.clearImageCache() repository.clearCacheDir() - loading.postValue(false) + loading.value = false } fun clearSearchHistory() = viewModelScope.launch { - loading.postValue(true) + loading.value = true repository.clearSearchHistory() - loading.postValue(false) + loading.value = false } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/timeline/TimelineViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/timeline/TimelineViewModel.kt index a21d9f757..b88392165 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/timeline/TimelineViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/timeline/TimelineViewModel.kt @@ -22,7 +22,6 @@ package com.twidere.twiderex.viewmodel.timeline import android.content.SharedPreferences import androidx.core.content.edit -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.paging.cachedIn @@ -31,6 +30,7 @@ import com.twidere.twiderex.extensions.toUi import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.paging.mediator.paging.PagingWithGapMediator import com.twidere.twiderex.paging.mediator.paging.pager +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.launch abstract class TimelineViewModel( @@ -41,7 +41,7 @@ abstract class TimelineViewModel( } abstract val pagingMediator: PagingWithGapMediator abstract val savedStateKey: String - val loadingBetween: MutableLiveData> + val loadingBetween: Flow> get() = pagingMediator.loadingBetween fun loadBetween( diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/twitter/TwitterSignInViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/twitter/TwitterSignInViewModel.kt index 613414922..712e58cab 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/twitter/TwitterSignInViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/twitter/TwitterSignInViewModel.kt @@ -21,7 +21,6 @@ package com.twidere.twiderex.viewmodel.twitter import android.accounts.Account -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.twidere.services.http.MicroBlogException @@ -43,6 +42,7 @@ import com.twidere.twiderex.utils.json import com.twidere.twiderex.utils.notify import dagger.assisted.Assisted import dagger.assisted.AssistedInject +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch import retrofit2.HttpException import java.io.IOException @@ -68,8 +68,8 @@ class TwitterSignInViewModel @AssistedInject constructor( ): TwitterSignInViewModel } - val success = MutableLiveData(false) - val loading = MutableLiveData(false) + val success = MutableStateFlow(false) + val loading = MutableStateFlow(false) init { viewModelScope.launch { @@ -79,7 +79,7 @@ class TwitterSignInViewModel @AssistedInject constructor( } private suspend fun beginOAuth(): Boolean { - loading.postValue(true) + loading.value = true try { val service = TwitterOAuthService(consumerKey, consumerSecret) val token = service.getOAuthToken( @@ -146,7 +146,7 @@ class TwitterSignInViewModel @AssistedInject constructor( } catch (e: HttpException) { e.message?.let { inAppNotification.show(it) } } - loading.postValue(false) + loading.value = false return false } @@ -155,6 +155,6 @@ class TwitterSignInViewModel @AssistedInject constructor( } fun cancel() { - loading.postValue(false) + loading.value = false } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/twitter/user/TwitterUserViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/twitter/user/TwitterUserViewModel.kt index 61d3deb08..9cea5da60 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/twitter/user/TwitterUserViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/twitter/user/TwitterUserViewModel.kt @@ -20,9 +20,7 @@ */ package com.twidere.twiderex.viewmodel.twitter.user -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel -import androidx.lifecycle.liveData import com.twidere.services.microblog.LookupService import com.twidere.twiderex.model.AccountDetails import com.twidere.twiderex.notification.InAppNotification @@ -30,6 +28,8 @@ import com.twidere.twiderex.repository.UserRepository import com.twidere.twiderex.utils.notify import dagger.assisted.Assisted import dagger.assisted.AssistedInject +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flow class TwitterUserViewModel @AssistedInject constructor( private val repository: UserRepository, @@ -46,9 +46,9 @@ class TwitterUserViewModel @AssistedInject constructor( ): TwitterUserViewModel } - val error = MutableLiveData(null) + val error = MutableStateFlow(null) - val user = liveData { + val user = flow { runCatching { repository.lookupUserByName( screenName, @@ -60,7 +60,7 @@ class TwitterUserViewModel @AssistedInject constructor( }.onFailure { it.notify(inAppNotification) emit(null) - error.postValue(it) + error.value = it } } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/user/UserViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/user/UserViewModel.kt index 5470e767d..5ef62593b 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/user/UserViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/user/UserViewModel.kt @@ -20,7 +20,6 @@ */ package com.twidere.twiderex.viewmodel.user -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.twidere.services.microblog.LookupService @@ -33,6 +32,7 @@ import com.twidere.twiderex.repository.UserRepository import com.twidere.twiderex.utils.notify import dagger.assisted.Assisted import dagger.assisted.AssistedInject +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch class UserViewModel @AssistedInject constructor( @@ -54,14 +54,14 @@ class UserViewModel @AssistedInject constructor( account.service as RelationshipService } - val refreshing = MutableLiveData(false) - val loadingRelationship = MutableLiveData(false) - val user = repository.getUserLiveData(userKey) - val relationship = MutableLiveData() + val refreshing = MutableStateFlow(false) + val loadingRelationship = MutableStateFlow(false) + val user = repository.getUserFlow(userKey) + val relationship = MutableStateFlow(null) val isMe = userKey == account.accountKey fun refresh() = viewModelScope.launch { - refreshing.postValue(true) + refreshing.value = true runCatching { repository.lookupUserById( userKey.id, @@ -71,42 +71,42 @@ class UserViewModel @AssistedInject constructor( }.onFailure { it.notify(inAppNotification) } - refreshing.postValue(false) + refreshing.value = false } fun follow() = viewModelScope.launch { - loadingRelationship.postValue(true) + loadingRelationship.value = true runCatching { relationshipService.follow(userKey.id) }.onSuccess { loadRelationShip() }.onFailure { - loadingRelationship.postValue(false) + loadingRelationship.value = false it.notify(inAppNotification) } } fun unfollow() = viewModelScope.launch { - loadingRelationship.postValue(true) + loadingRelationship.value = true runCatching { relationshipService.unfollow(userKey.id) }.onSuccess { loadRelationShip() }.onFailure { - loadingRelationship.postValue(false) + loadingRelationship.value = false it.notify(inAppNotification) } } private fun loadRelationShip() = viewModelScope.launch { - loadingRelationship.postValue(true) + loadingRelationship.value = true try { relationshipService.showRelationship(userKey.id).let { - relationship.postValue(it) + relationship.value = it } } catch (e: Exception) { } - loadingRelationship.postValue(false) + loadingRelationship.value = false } init { diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageFetchWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageFetchWorker.kt index e53a4333a..f7977fb5b 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageFetchWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageFetchWorker.kt @@ -43,6 +43,7 @@ import com.twidere.twiderex.repository.DirectMessageRepository import dagger.assisted.Assisted import dagger.assisted.AssistedInject import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.firstOrNull import java.util.concurrent.TimeUnit @HiltWorker @@ -63,7 +64,7 @@ class DirectMessageFetchWorker @AssistedInject constructor( override suspend fun doWork(): Result { return try { - accountRepository.activeAccount.value?.takeIf { + accountRepository.activeAccount.firstOrNull()?.takeIf { accountRepository.getAccountPreferences(it.accountKey).isNotificationEnabled.first() }?.let { account -> val result = repository.checkNewMessages( diff --git a/app/src/test/java/com/twidere/twiderex/mock/db/MockDirectMessageConversationDao.kt b/app/src/test/java/com/twidere/twiderex/mock/db/MockDirectMessageConversationDao.kt index 33069d257..d7091a843 100644 --- a/app/src/test/java/com/twidere/twiderex/mock/db/MockDirectMessageConversationDao.kt +++ b/app/src/test/java/com/twidere/twiderex/mock/db/MockDirectMessageConversationDao.kt @@ -20,7 +20,6 @@ */ package com.twidere.twiderex.mock.db -import androidx.lifecycle.LiveData import androidx.paging.PagingSource import com.twidere.services.twitter.model.User import com.twidere.twiderex.db.dao.DirectMessageConversationDao @@ -30,6 +29,7 @@ import com.twidere.twiderex.db.model.DbDMEvent import com.twidere.twiderex.db.model.DbDMEventWithAttachments import com.twidere.twiderex.db.model.DbDirectMessageConversationWithMessage import com.twidere.twiderex.model.MicroBlogKey +import kotlinx.coroutines.flow.Flow class MockDirectMessageConversationDao : DirectMessageConversationDao { val db = mutableListOf() @@ -72,10 +72,10 @@ class MockDirectMessageConversationDao : DirectMessageConversationDao { TODO("Not yet implemented") } - override fun findWithConversationKeyLiveData( + override fun findWithConversationKeyFlow( accountKey: MicroBlogKey, conversationKey: MicroBlogKey - ): LiveData { + ): Flow { TODO("Not yet implemented") } diff --git a/app/src/test/java/com/twidere/twiderex/mock/db/MockListsDao.kt b/app/src/test/java/com/twidere/twiderex/mock/db/MockListsDao.kt index 5f87e7114..b981d1ccd 100644 --- a/app/src/test/java/com/twidere/twiderex/mock/db/MockListsDao.kt +++ b/app/src/test/java/com/twidere/twiderex/mock/db/MockListsDao.kt @@ -20,12 +20,12 @@ */ package com.twidere.twiderex.mock.db -import androidx.lifecycle.LiveData import androidx.paging.PagingSource import androidx.paging.PagingState import com.twidere.twiderex.db.dao.ListsDao import com.twidere.twiderex.db.model.DbList import com.twidere.twiderex.model.MicroBlogKey +import kotlinx.coroutines.flow.Flow class MockListsDao : ListsDao { private val fakeDatabase = mutableListOf() @@ -41,10 +41,10 @@ class MockListsDao : ListsDao { } } - override fun findWithListKeyWithLiveData( + override fun findWithListKeyWithFlow( listKey: MicroBlogKey, accountKey: MicroBlogKey - ): LiveData { + ): Flow { TODO("Not yet implemented") } diff --git a/app/src/test/java/com/twidere/twiderex/mock/db/MockUserDao.kt b/app/src/test/java/com/twidere/twiderex/mock/db/MockUserDao.kt index 25243cc8e..a2c4f3346 100644 --- a/app/src/test/java/com/twidere/twiderex/mock/db/MockUserDao.kt +++ b/app/src/test/java/com/twidere/twiderex/mock/db/MockUserDao.kt @@ -20,16 +20,16 @@ */ package com.twidere.twiderex.mock.db -import androidx.lifecycle.LiveData import com.twidere.twiderex.db.dao.UserDao import com.twidere.twiderex.db.model.DbUser import com.twidere.twiderex.model.MicroBlogKey +import kotlinx.coroutines.flow.Flow class MockUserDao : UserDao { override suspend fun insertAll(user: List) { } - override fun findWithUserKeyLiveData(userKey: MicroBlogKey): LiveData { + override fun findWithUserKeyFlow(userKey: MicroBlogKey): Flow { TODO("Not yet implemented") } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4cd00d4b7..e228f52bc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -53,7 +53,6 @@ compose-animation = { module = "androidx.compose.animation:animation", version.r compose-material-base = { module = "androidx.compose.material:material", version.ref = "compose" } compose-material-icons-core = { module = "androidx.compose.material:material-icons-core", version.ref = "compose" } compose-material-icons-extended = { module = "androidx.compose.material:material-icons-extended", version.ref = "compose" } -compose-runtime_livedata = { module = "androidx.compose.runtime:runtime-livedata", version.ref = "compose" } constraintLayout = { module = "androidx.constraintlayout:constraintlayout-compose", version.ref = "constraint_layout" } # Paging @@ -63,7 +62,6 @@ paging-compose = { module = "androidx.paging:paging-compose", version.ref = "pag # Lifecycle lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle_base" } lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycle_base" } -lifecycle-livedata-ktx = { module = "androidx.lifecycle:lifecycle-livedata-ktx", version.ref = "lifecycle_base" } lifecycle-viewmodel-savedstate = { module = "androidx.lifecycle:lifecycle-viewmodel-savedstate", version.ref = "lifecycle_base" } lifecycle-common-java8 = { module = "androidx.lifecycle:lifecycle-common-java8", version.ref = "lifecycle_base" } lifecycle-viewmodel-compose_compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycle_compose" } @@ -152,7 +150,6 @@ compose = [ "compose-material-base", "compose-material-icons-core", "compose-material-icons-extended", - "compose-runtime_livedata", ] paging = [ "paging-runtime", @@ -161,7 +158,6 @@ paging = [ lifecycle = [ "lifecycle-runtime-ktx", "lifecycle-viewmodel-ktx", - "lifecycle-livedata-ktx", "lifecycle-viewmodel-savedstate", "lifecycle-common-java8", "lifecycle-viewmodel-compose_compose", From 727666b8c20853fa076a4831ca9b5846cb9ca033 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Thu, 8 Jul 2021 12:34:11 +0800 Subject: [PATCH 002/137] fix testing --- .../lists/ListsCreateViewModelTest.kt | 24 +++++++++++++++---- .../lists/ListsModifyViewModelTest.kt | 13 ++++++++-- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/app/src/test/java/com/twidere/twiderex/viewmodel/lists/ListsCreateViewModelTest.kt b/app/src/test/java/com/twidere/twiderex/viewmodel/lists/ListsCreateViewModelTest.kt index e4e709e49..799c3f102 100644 --- a/app/src/test/java/com/twidere/twiderex/viewmodel/lists/ListsCreateViewModelTest.kt +++ b/app/src/test/java/com/twidere/twiderex/viewmodel/lists/ListsCreateViewModelTest.kt @@ -29,8 +29,11 @@ import com.twidere.twiderex.notification.InAppNotification import com.twidere.twiderex.notification.NotificationEvent import com.twidere.twiderex.repository.ListsRepository import com.twidere.twiderex.viewmodel.ViewModelTestBase +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import org.junit.Assert import org.junit.Rule @@ -63,6 +66,8 @@ class ListsCreateViewModelTest : ViewModelTestBase() { private lateinit var createViewModel: ListsCreateViewModel + private val scope = CoroutineScope(Dispatchers.Main) + override fun setUp() { super.setUp() mockRepository = ListsRepository(MockCenter.mockCacheDatabase()) @@ -81,7 +86,11 @@ class ListsCreateViewModelTest : ViewModelTestBase() { whenever(mockAccount.accountKey).thenReturn(MicroBlogKey.twitter("123")) errorNotification = null mockSuccessObserver.onChanged(false) - createViewModel.loading.observeForever(mockLoadingObserver) + scope.launch { + createViewModel.loading.collect { + mockLoadingObserver.onChanged(it) + } + } } @Test @@ -104,14 +113,21 @@ class ListsCreateViewModelTest : ViewModelTestBase() { Assert.assertNotNull(errorNotification) } - private fun verifySuccessAndLoadingBefore(loadingObserver: Observer, successObserver: Observer) { + private fun verifySuccessAndLoadingBefore( + loadingObserver: Observer, + successObserver: Observer + ) { verify(loadingObserver, times(1)).onChanged(false) verify(successObserver).onChanged(false) } - private fun verifySuccessAndLoadingAfter(loadingObserver: Observer, successObserver: Observer, success: Boolean) { + private fun verifySuccessAndLoadingAfter( + loadingObserver: Observer, + successObserver: Observer, + success: Boolean + ) { verify(loadingObserver, times(1)).onChanged(true) - verify(loadingObserver, times(2)).onChanged(false) + verify(loadingObserver, times(1)).onChanged(false) verify(successObserver, if (success) times(1) else times(2)).onChanged(success) } } diff --git a/app/src/test/java/com/twidere/twiderex/viewmodel/lists/ListsModifyViewModelTest.kt b/app/src/test/java/com/twidere/twiderex/viewmodel/lists/ListsModifyViewModelTest.kt index 28df28e42..b77c4f0ca 100644 --- a/app/src/test/java/com/twidere/twiderex/viewmodel/lists/ListsModifyViewModelTest.kt +++ b/app/src/test/java/com/twidere/twiderex/viewmodel/lists/ListsModifyViewModelTest.kt @@ -29,8 +29,11 @@ import com.twidere.twiderex.notification.InAppNotification import com.twidere.twiderex.notification.NotificationEvent import com.twidere.twiderex.repository.ListsRepository import com.twidere.twiderex.viewmodel.ViewModelTestBase +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import org.junit.Assert import org.junit.Rule @@ -63,6 +66,8 @@ class ListsModifyViewModelTest : ViewModelTestBase() { private lateinit var modifyViewModel: ListsModifyViewModel + private val scope = CoroutineScope(Dispatchers.Main) + @Test fun updateList_successExpectTrue(): Unit = runBlocking(Dispatchers.Main) { verifySuccessAndLoadingBefore(mockLoadingObserver, mockSuccessObserver) @@ -176,7 +181,11 @@ class ListsModifyViewModelTest : ViewModelTestBase() { whenever(mockAccount.accountKey).thenReturn(MicroBlogKey.twitter("123")) errorNotification = null mockSuccessObserver.onChanged(false) - modifyViewModel.loading.observeForever(mockLoadingObserver) + scope.launch { + modifyViewModel.loading.collect { + mockLoadingObserver.onChanged(it) + } + } } private fun verifySuccessAndLoadingBefore(loadingObserver: Observer, successObserver: Observer) { @@ -186,7 +195,7 @@ class ListsModifyViewModelTest : ViewModelTestBase() { private fun verifySuccessAndLoadingAfter(loadingObserver: Observer, successObserver: Observer, success: Boolean) { verify(loadingObserver, times(1)).onChanged(true) - verify(loadingObserver, times(2)).onChanged(false) + verify(loadingObserver, times(1)).onChanged(false) verify(successObserver, if (success) times(1) else times(2)).onChanged(success) } } From a07d6fa73b83450672a4038e88938f75a8c8faa5 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Thu, 8 Jul 2021 12:41:43 +0800 Subject: [PATCH 003/137] add ReorderableColumn --- .../component/foundation/ReorderableColumn.kt | 224 ++++++++++++++++++ 1 file changed, 224 insertions(+) create mode 100644 app/src/main/kotlin/com/twidere/twiderex/component/foundation/ReorderableColumn.kt diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/ReorderableColumn.kt b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/ReorderableColumn.kt new file mode 100644 index 000000000..7ed51d0ec --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/ReorderableColumn.kt @@ -0,0 +1,224 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.component.foundation + +import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.Layout +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.zIndex +import kotlin.math.roundToInt + +@Composable +fun rememberReorderableColumnState( + onReorder: (oldIndex: Int, newIndex: Int) -> Unit +) = remember { + ReorderableColumnState( + onReorder = onReorder + ) +} + +@Stable +class ReorderableColumnState( + private val onReorder: (oldIndex: Int, newIndex: Int) -> Unit, +) { + private var reordering by mutableStateOf(false) + private var draggingItemIndex: Int = -1 + private var newTargetIndex by mutableStateOf(-1) + private var offsetY by mutableStateOf(0f) + private var childSizes = arrayListOf() + + private fun start(index: Int) { + draggingItemIndex = index + reordering = true + } + + private fun drag(y: Float) { + offsetY += y + if (offsetY.roundToInt() == 0) { + return + } + + val newOffset = + (childSizes.subList(0, draggingItemIndex).sumOf { it.height } + offsetY).roundToInt() + + newTargetIndex = ArrayList(childSizes) + .apply { + removeAt(draggingItemIndex) + } + .map { it.height } + .runningReduce { acc, i -> acc + i } + .let { it + newOffset } + .sortedBy { it } + .indexOf(newOffset) + .let { + if (offsetY < 0) { + it + 1 + } else { + it + } + } + } + + private fun cancel() { + reordering = false + draggingItemIndex = -1 + newTargetIndex = -1 + offsetY = 0f + } + + private fun drop() { + if (offsetY.roundToInt() == 0) { + return + } + val newOffset = + (childSizes.subList(0, draggingItemIndex).sumOf { it.height } + offsetY).roundToInt() + + val newIndex = ArrayList(childSizes) + .apply { + removeAt(draggingItemIndex) + } + .map { it.height } + .runningReduce { acc, i -> acc + i } + .let { it + newOffset } + .sortedBy { it } + .indexOf(newOffset) + .let { + if (offsetY < 0) { + it + 1 + } else { + it + } + } + + onReorder.invoke(draggingItemIndex, newIndex) + + reordering = false + draggingItemIndex = -1 + newTargetIndex = -1 + offsetY = 0f + } +} + +@Composable +fun ReorderableColumn( + modifier: Modifier = Modifier, + data: List, + state: ReorderableColumnState, + dragingContent: @Composable ((T) -> Unit)? = null, + itemContent: @Composable (T) -> Unit, +) { + Layout( + modifier = modifier + .verticalScroll(rememberScrollState()), + content = { + data.forEachIndexed { index, item -> + Box( + modifier = Modifier + .pointerInput(Unit) { + detectDragGesturesAfterLongPress( + onDragCancel = { + state.cancel() + }, + onDragEnd = { + state.drop() + }, + onDragStart = { + state.start(index) + }, + onDrag = { _, dragAmount -> + state.drag(dragAmount.y) + } + ) + } + .let { + if (state.reordering && state.draggingItemIndex == index) { + it.zIndex(0.1f) + } else { + it.zIndex(0f) + } + } + ) { + if (state.reordering && state.draggingItemIndex == index && dragingContent != null) { + dragingContent.invoke(item) + } else { + itemContent.invoke(item) + } + } + } + } + ) { measurables, constraints -> + val placeables = measurables.map { + it.measure(constraints) + } + state.childSizes.clear() + state.childSizes.addAll( + placeables.map { + IntSize( + width = it.measuredWidth, + height = it.measuredHeight + ) + } + ) + + layout( + width = placeables.maxOf { it.measuredWidth }, + height = placeables.sumOf { it.measuredHeight } + ) { + var height = 0 + placeables.forEachIndexed { index, placeable -> + if (state.reordering && index == state.newTargetIndex && index != state.draggingItemIndex && state.offsetY < 0) { + height += placeables[state.draggingItemIndex].height + } + if (state.reordering && index == state.draggingItemIndex) { + placeable.place( + 0, + (height + state.offsetY.roundToInt()).let { + if (state.newTargetIndex != -1 && index != state.newTargetIndex && state.offsetY < 0) { + it - placeables[state.newTargetIndex].height + } else { + it + } + } + ) + } else { + placeable.place(0, height) + } + if (state.reordering && index == state.newTargetIndex && index != state.draggingItemIndex && state.offsetY > 0) { + height += placeables[state.draggingItemIndex].height + } + if (!state.reordering || index != state.draggingItemIndex || state.newTargetIndex == -1 || index == state.newTargetIndex) { + height += placeable.measuredHeight + } + } + } + } +} From 9b110dc6de1aee3ea4c53d5de3d89463f37a0315 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Thu, 8 Jul 2021 12:44:10 +0800 Subject: [PATCH 004/137] fix ReorderableColumnState visibility --- .../component/foundation/ReorderableColumn.kt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/ReorderableColumn.kt b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/ReorderableColumn.kt index 7ed51d0ec..d08fecf44 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/ReorderableColumn.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/ReorderableColumn.kt @@ -50,18 +50,18 @@ fun rememberReorderableColumnState( class ReorderableColumnState( private val onReorder: (oldIndex: Int, newIndex: Int) -> Unit, ) { - private var reordering by mutableStateOf(false) - private var draggingItemIndex: Int = -1 - private var newTargetIndex by mutableStateOf(-1) - private var offsetY by mutableStateOf(0f) - private var childSizes = arrayListOf() + internal var reordering by mutableStateOf(false) + internal var draggingItemIndex: Int = -1 + internal var newTargetIndex by mutableStateOf(-1) + internal var offsetY by mutableStateOf(0f) + internal var childSizes = arrayListOf() - private fun start(index: Int) { + internal fun start(index: Int) { draggingItemIndex = index reordering = true } - private fun drag(y: Float) { + internal fun drag(y: Float) { offsetY += y if (offsetY.roundToInt() == 0) { return @@ -88,14 +88,14 @@ class ReorderableColumnState( } } - private fun cancel() { + internal fun cancel() { reordering = false draggingItemIndex = -1 newTargetIndex = -1 offsetY = 0f } - private fun drop() { + internal fun drop() { if (offsetY.roundToInt() == 0) { return } From 3dd0d91c3f2b030ca616ba793b1b92c7a83cc724 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Thu, 8 Jul 2021 16:38:36 +0800 Subject: [PATCH 005/137] make all home navigation item with scene and route support --- .../component/foundation/ReorderableColumn.kt | 5 +- .../component/navigation/Navigator.kt | 4 +- .../com/twidere/twiderex/navigation/Route.kt | 67 ++++- .../twidere/twiderex/scenes/DraftListScene.kt | 106 ++++--- .../com/twidere/twiderex/scenes/HomeScene.kt | 103 +++---- .../scenes/dm/DMConversationListScene.kt | 87 +++--- .../scenes/home/AllNotificationItem.kt | 58 +++- .../scenes/home/DMConversationListItem.kt | 57 ++++ .../scenes/home/DraftNavigationItem.kt | 51 ++++ .../twidere/twiderex/scenes/home/HomeMenus.kt | 70 +++++ .../scenes/home/HomeNavigationItem.kt | 5 + .../twiderex/scenes/home/HomeTimelineItem.kt | 93 ++++-- .../scenes/home/ListsNavigationItem.kt | 59 ++++ .../scenes/home/MastodonNotificationItem.kt | 98 ++++-- .../twidere/twiderex/scenes/home/MeItem.kt | 40 ++- .../twiderex/scenes/home/MentionItem.kt | 55 +++- .../twiderex/scenes/home/NotificationItem.kt | 64 +++- .../twiderex/scenes/home/SearchItem.kt | 284 ++++++++++-------- .../twiderex/scenes/lists/ListsScene.kt | 126 ++++---- .../twiderex/scenes/settings/LayoutScene.kt | 147 +++++++++ .../twiderex/scenes/settings/SettingsScene.kt | 11 +- .../viewmodel/settings/LayoutViewModel.kt | 41 +++ 22 files changed, 1180 insertions(+), 451 deletions(-) create mode 100644 app/src/main/kotlin/com/twidere/twiderex/scenes/home/DMConversationListItem.kt create mode 100644 app/src/main/kotlin/com/twidere/twiderex/scenes/home/DraftNavigationItem.kt create mode 100644 app/src/main/kotlin/com/twidere/twiderex/scenes/home/HomeMenus.kt create mode 100644 app/src/main/kotlin/com/twidere/twiderex/scenes/home/ListsNavigationItem.kt create mode 100644 app/src/main/kotlin/com/twidere/twiderex/scenes/settings/LayoutScene.kt create mode 100644 app/src/main/kotlin/com/twidere/twiderex/viewmodel/settings/LayoutViewModel.kt diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/ReorderableColumn.kt b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/ReorderableColumn.kt index d08fecf44..a04cf9018 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/ReorderableColumn.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/ReorderableColumn.kt @@ -22,8 +22,6 @@ package com.twidere.twiderex.component.foundation import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue @@ -137,8 +135,7 @@ fun ReorderableColumn( itemContent: @Composable (T) -> Unit, ) { Layout( - modifier = modifier - .verticalScroll(rememberScrollState()), + modifier = modifier, content = { data.forEachIndexed { index, item -> Box( diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/navigation/Navigator.kt b/app/src/main/kotlin/com/twidere/twiderex/component/navigation/Navigator.kt index a92ba5974..3b4c93fe4 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/navigation/Navigator.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/navigation/Navigator.kt @@ -109,12 +109,12 @@ class Navigator( } override fun search(keyword: String) { - navController.navigate(Route.Search(keyword), NavOptions(popUpTo = PopUpTo(Route.Home))) + navController.navigate(Route.Search.Search(keyword), NavOptions(popUpTo = PopUpTo(Route.Home))) } override fun searchInput(initial: String?) { navController.navigate( - Route.SearchInput(initial), + Route.Search.SearchInput(initial), NavOptions(popUpTo = PopUpTo(Route.Home)) ) } diff --git a/app/src/main/kotlin/com/twidere/twiderex/navigation/Route.kt b/app/src/main/kotlin/com/twidere/twiderex/navigation/Route.kt index 1cbd34d4a..dd16b9c36 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/navigation/Route.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/navigation/Route.kt @@ -47,11 +47,16 @@ import com.twidere.twiderex.scenes.compose.DraftComposeScene import com.twidere.twiderex.scenes.dm.DMConversationListScene import com.twidere.twiderex.scenes.dm.DMConversationScene import com.twidere.twiderex.scenes.dm.DMNewConversationScene +import com.twidere.twiderex.scenes.home.HomeTimelineScene +import com.twidere.twiderex.scenes.home.MastodonNotificationScene +import com.twidere.twiderex.scenes.home.MeScene +import com.twidere.twiderex.scenes.home.MentionScene import com.twidere.twiderex.scenes.lists.ListTimeLineScene import com.twidere.twiderex.scenes.lists.ListsAddMembersScene import com.twidere.twiderex.scenes.lists.ListsMembersScene import com.twidere.twiderex.scenes.lists.ListsScene import com.twidere.twiderex.scenes.lists.ListsSubscribersScene +import com.twidere.twiderex.scenes.lists.platform.MastodonListsCreateDialog import com.twidere.twiderex.scenes.lists.platform.TwitterListsCreateScene import com.twidere.twiderex.scenes.lists.platform.TwitterListsEditScene import com.twidere.twiderex.scenes.mastodon.MastodonHashtagScene @@ -64,6 +69,7 @@ import com.twidere.twiderex.scenes.settings.AccountManagementScene import com.twidere.twiderex.scenes.settings.AccountNotificationScene import com.twidere.twiderex.scenes.settings.AppearanceScene import com.twidere.twiderex.scenes.settings.DisplayScene +import com.twidere.twiderex.scenes.settings.LayoutScene import com.twidere.twiderex.scenes.settings.MiscScene import com.twidere.twiderex.scenes.settings.NotificationScene import com.twidere.twiderex.scenes.settings.SettingsScene @@ -77,6 +83,7 @@ import com.twidere.twiderex.scenes.user.UserScene import com.twidere.twiderex.twitterHosts import com.twidere.twiderex.ui.LocalActiveAccount import com.twidere.twiderex.ui.LocalActiveAccountViewModel +import com.twidere.twiderex.ui.LocalNavController import com.twidere.twiderex.ui.TwidereScene import com.twidere.twiderex.utils.LocalPlatformResolver import com.twidere.twiderex.viewmodel.compose.ComposeType @@ -96,6 +103,11 @@ const val twidereXSchema = "twiderex" object Route { const val Home = "home" + const val HomeTimeline = "HomeTimeline" + const val Notification = "Notification" + const val Mentions = "Mentions" + const val Me = "Me" + object Draft { const val List = "draft/list" fun Compose(draftId: String) = "draft/compose/$draftId" @@ -142,23 +154,26 @@ object Route { "media/pure/$belongToKey?selectedIndex=$selectedIndex" } - fun Search(keyword: String) = "search/result/${ - URLEncoder.encode( - keyword, - "UTF-8" - ) - }" - - fun SearchInput(keyword: String? = null): String { - if (keyword == null) { - return "search/input" - } - return "search/input?keyword=${ + object Search { + const val Home = "search" + fun Search(keyword: String) = "$Home/result/${ URLEncoder.encode( keyword, "UTF-8" ) }" + + fun SearchInput(keyword: String? = null): String { + if (keyword == null) { + return "$Home/input" + } + return "$Home/input?keyword=${ + URLEncoder.encode( + keyword, + "UTF-8" + ) + }" + } } fun Compose(composeType: ComposeType, statusKey: MicroBlogKey? = null) = @@ -188,6 +203,7 @@ object Route { const val AccountManagement = "settings/accountmanagement" const val Misc = "settings/misc" const val Notification = "settings/notification" + const val Layout = "settings/layout" fun AccountNotification(accountKey: MicroBlogKey) = "settings/notification/$accountKey" } @@ -207,6 +223,7 @@ object Route { object Mastodon { fun Hashtag(keyword: String) = "mastodon/hashtag/$keyword" + const val Notification = "mastodon/notification" object Compose { const val Hashtag = "mastodon/compose/hashtag" @@ -215,6 +232,7 @@ object Route { object Lists { const val Home = "lists" + const val MastodonCreateDialog = "$Home/mastodon/create" const val TwitterCreate = "$Home/twitter/create" fun TwitterEdit(listKey: MicroBlogKey) = "$Home/twitter/edit/$listKey" fun Timeline(listKey: MicroBlogKey) = "$Home/timeline/$listKey" @@ -369,6 +387,22 @@ fun RouteBuilder.route(constraints: Constraints) { HomeScene() } + authorizedScene(Route.Mastodon.Notification) { + MastodonNotificationScene() + } + + authorizedScene(Route.Me) { + MeScene() + } + + authorizedScene(Route.Mentions) { + MentionScene() + } + + authorizedScene(Route.HomeTimeline) { + HomeTimelineScene() + } + scene( Route.SignIn.General, deepLinks = listOf( @@ -642,6 +676,10 @@ fun RouteBuilder.route(constraints: Constraints) { NotificationScene() } + authorizedScene(Route.Settings.Layout) { + LayoutScene() + } + scene("settings/notification/{accountKey}") { it.path("accountKey", null)?.let { MicroBlogKey.valueOf(it) @@ -681,6 +719,11 @@ fun RouteBuilder.route(constraints: Constraints) { ListsScene() } + authorizedDialog(Route.Lists.MastodonCreateDialog) { + val navController = LocalNavController.current + MastodonListsCreateDialog(onDismissRequest = { navController.goBack() }) + } + authorizedScene(Route.Lists.TwitterCreate) { TwitterListsCreateScene() } diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/DraftListScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/DraftListScene.kt index 227a2a3a4..254470e5d 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/DraftListScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/DraftListScene.kt @@ -23,6 +23,7 @@ package com.twidere.twiderex.scenes import androidx.compose.foundation.layout.Box import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.DropdownMenu import androidx.compose.material.DropdownMenuItem import androidx.compose.material.ExperimentalMaterialApi @@ -33,6 +34,7 @@ import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.MoreVert import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.mutableStateOf @@ -44,20 +46,15 @@ import com.twidere.twiderex.R import com.twidere.twiderex.component.foundation.AppBar import com.twidere.twiderex.component.foundation.AppBarNavigationButton import com.twidere.twiderex.component.foundation.InAppNotificationScaffold +import com.twidere.twiderex.component.lazy.LazyListController import com.twidere.twiderex.di.assisted.assistedViewModel import com.twidere.twiderex.navigation.Route import com.twidere.twiderex.ui.LocalNavController import com.twidere.twiderex.ui.TwidereScene import com.twidere.twiderex.viewmodel.DraftViewModel -@OptIn(ExperimentalMaterialApi::class) @Composable fun DraftListScene() { - val viewModel = assistedViewModel { - it.create() - } - val source by viewModel.source.observeAsState(initial = emptyList()) - val navController = LocalNavController.current TwidereScene { InAppNotificationScaffold( topBar = { @@ -71,50 +68,69 @@ fun DraftListScene() { ) } ) { - LazyColumn { - items(items = source, key = { it._id.hashCode() }) { - ListItem( - text = { - Text(text = it.content) - }, - trailing = { - var expanded by remember { mutableStateOf(false) } - Box { - IconButton(onClick = { expanded = true }) { - Icon( - imageVector = Icons.Default.MoreVert, - contentDescription = stringResource( - id = R.string.accessibility_common_more - ) - ) + DraftListSceneContent() + } + } +} + +@OptIn(ExperimentalMaterialApi::class) +@Composable +fun DraftListSceneContent( + lazyListController: LazyListController? = null, +) { + val viewModel = assistedViewModel { + it.create() + } + val source by viewModel.source.observeAsState(initial = emptyList()) + val navController = LocalNavController.current + val listState = rememberLazyListState() + LaunchedEffect(lazyListController) { + lazyListController?.listState = listState + } + LazyColumn( + state = listState + ) { + items(items = source, key = { it._id.hashCode() }) { + ListItem( + text = { + Text(text = it.content) + }, + trailing = { + var expanded by remember { mutableStateOf(false) } + Box { + IconButton(onClick = { expanded = true }) { + Icon( + imageVector = Icons.Default.MoreVert, + contentDescription = stringResource( + id = R.string.accessibility_common_more + ) + ) + } + DropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false }, + ) { + DropdownMenuItem( + onClick = { + navController.navigate(Route.Draft.Compose(it._id)) } - DropdownMenu( - expanded = expanded, - onDismissRequest = { expanded = false }, - ) { - DropdownMenuItem( - onClick = { - navController.navigate(Route.Draft.Compose(it._id)) - } - ) { - Text(text = stringResource(id = R.string.scene_drafts_actions_edit_draft)) - } - DropdownMenuItem( - onClick = { - viewModel.delete(it) - } - ) { - Text( - text = stringResource(id = R.string.common_controls_actions_remove), - color = Color.Red, - ) - } + ) { + Text(text = stringResource(id = R.string.scene_drafts_actions_edit_draft)) + } + DropdownMenuItem( + onClick = { + viewModel.delete(it) } + ) { + Text( + text = stringResource(id = R.string.common_controls_actions_remove), + color = Color.Red, + ) } } - ) + } } - } + ) } } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/HomeScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/HomeScene.kt index 9c5822505..c942e2354 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/HomeScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/HomeScene.kt @@ -21,8 +21,6 @@ package com.twidere.twiderex.scenes import androidx.activity.compose.BackHandler -import androidx.annotation.DrawableRes -import androidx.annotation.StringRes import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.Crossfade import androidx.compose.animation.ExperimentalAnimationApi @@ -69,6 +67,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.rotate +import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp @@ -87,17 +86,11 @@ import com.twidere.twiderex.component.status.UserAvatar import com.twidere.twiderex.component.status.UserName import com.twidere.twiderex.component.status.UserScreenName import com.twidere.twiderex.extensions.withElevation -import com.twidere.twiderex.model.PlatformType import com.twidere.twiderex.model.ui.UiUser import com.twidere.twiderex.navigation.Route import com.twidere.twiderex.preferences.LocalAppearancePreferences import com.twidere.twiderex.preferences.proto.AppearancePreferences -import com.twidere.twiderex.scenes.home.HomeNavigationItem -import com.twidere.twiderex.scenes.home.HomeTimelineItem -import com.twidere.twiderex.scenes.home.MastodonNotificationItem -import com.twidere.twiderex.scenes.home.MeItem -import com.twidere.twiderex.scenes.home.MentionItem -import com.twidere.twiderex.scenes.home.SearchItem +import com.twidere.twiderex.scenes.home.HomeMenus import com.twidere.twiderex.ui.LocalActiveAccount import com.twidere.twiderex.ui.LocalActiveAccountViewModel import com.twidere.twiderex.ui.LocalNavController @@ -116,21 +109,8 @@ fun HomeScene() { val hideFab = LocalAppearancePreferences.current.hideFabWhenScroll val hideAppBar = LocalAppearancePreferences.current.hideAppBarWhenScroll val menus = remember(account.type) { - listOf( - HomeTimelineItem(), - ).let { - it + when (account.type) { - PlatformType.Twitter -> MentionItem() - PlatformType.StatusNet -> TODO() - PlatformType.Fanfou -> TODO() - PlatformType.Mastodon -> MastodonNotificationItem() - } - }.let { - it + listOf( - SearchItem(), - MeItem(), - ) - } + HomeMenus.values() + .filter { it.supportedPlatformType.contains(account.type) && it.showDefault } } val pagerState = rememberPagerState( maxPage = menus.lastIndex @@ -164,7 +144,7 @@ fun HomeScene() { ) { if (pagerState.currentPage == it) { scope.launch { - menus[it].lazyListController.scrollToTop() + menus[it].item.lazyListController.scrollToTop() } } scope.launch { @@ -180,9 +160,10 @@ fun HomeScene() { }, floatingActionButton = { Crossfade(pagerState.currentPage) { - menus[it].Fab() + menus[it].item.Fab() } }, + floatingActionButtonPosition = menus[pagerState.currentPage].item.floatingActionButtonPosition, enableFloatingActionButtonNestedScroll = hideFab, topBar = { HomeAppBar( @@ -203,7 +184,7 @@ fun HomeScene() { Pager( state = pagerState, ) { - menus[page].Content() + menus[page].item.Content() } } } @@ -215,14 +196,14 @@ fun HomeScene() { fun HomeAppBar( modifier: Modifier = Modifier, tabPosition: AppearancePreferences.TabPosition, - menus: List, + menus: List, pagerState: PagerState, scaffoldState: ScaffoldState, scope: CoroutineScope, ) { if (tabPosition == AppearancePreferences.TabPosition.Bottom) { AnimatedVisibility( - visible = menus[pagerState.currentPage].withAppBar, + visible = menus[pagerState.currentPage].item.withAppBar, enter = expandVertically(clip = false), exit = shrinkVertically(clip = false), modifier = modifier @@ -230,12 +211,12 @@ fun HomeAppBar( AppBar( backgroundColor = MaterialTheme.colors.surface.withElevation(), title = { - Text(text = menus[pagerState.currentPage].name()) + Text(text = menus[pagerState.currentPage].item.name()) }, navigationIcon = { MenuAvatar(scaffoldState) }, - elevation = if (menus[pagerState.currentPage].withAppBar) { + elevation = if (menus[pagerState.currentPage].item.withAppBar) { AppBarDefaults.TopAppBarElevation } else { 0.dp @@ -244,7 +225,7 @@ fun HomeAppBar( } } else { val transition = updateTransition( - targetState = menus[pagerState.currentPage].withAppBar, + targetState = menus[pagerState.currentPage].item.withAppBar, ) val elevation by transition.animateDp { if (it) { @@ -263,7 +244,7 @@ fun HomeAppBar( MenuAvatar(scaffoldState) IconTabsComponent( modifier = Modifier.weight(1f), - items = menus.map { it.icon() to it.name() }, + items = menus.map { it.item.icon() to it.item.name() }, selectedItem = pagerState.currentPage, divider = { TabRowDefaults.Divider(thickness = 0.dp) @@ -271,7 +252,7 @@ fun HomeAppBar( onItemSelected = { if (pagerState.currentPage == it) { scope.launch { - menus[it].lazyListController.scrollToTop() + menus[it].item.lazyListController.scrollToTop() } } scope.launch { @@ -321,7 +302,7 @@ private object MenuAvatarDefaults { @Composable fun HomeBottomNavigation( modifier: Modifier = Modifier, - items: List, + items: List, selectedItem: Int, onItemSelected: (Int) -> Unit, ) { @@ -339,7 +320,12 @@ fun HomeBottomNavigation( BottomNavigationItem( selectedContentColor = MaterialTheme.colors.primary, unselectedContentColor = mediumEmphasisContentContentColor, - icon = { Icon(painter = item.icon(), contentDescription = item.name()) }, + icon = { + Icon( + painter = item.item.icon(), + contentDescription = item.item.name() + ) + }, selected = selectedItem == index, onClick = { onItemSelected.invoke(index) } ) @@ -446,33 +432,16 @@ private fun HomeDrawer(scaffoldState: ScaffoldState) { exit = shrinkVertically() + fadeOut(), ) { LazyColumn { - if (account?.type == PlatformType.Twitter) { - item { - DrawerMenuItem( - onClick = { - navController.navigate(Route.Messages.Home) - }, - title = R.string.scene_messages_title, - icon = R.drawable.ic_mail, - ) - } - } - item { - DrawerMenuItem( - onClick = { - navController.navigate(Route.Draft.List) - }, - title = R.string.scene_drafts_title, - icon = R.drawable.ic_note, - ) - } - item { + items( + HomeMenus.values() + .filter { it.supportedPlatformType.contains(account?.type) && !it.showDefault } + ) { DrawerMenuItem( onClick = { - navController.navigate(Route.Lists.Home) + navController.navigate(it.item.route) }, - title = R.string.scene_lists_title, - icon = R.drawable.ic_lists, + title = it.item.name(), + icon = it.item.icon(), ) } } @@ -509,9 +478,9 @@ private fun HomeDrawer(scaffoldState: ScaffoldState) { @Composable private fun DrawerMenuItem( onClick: () -> Unit, - @StringRes title: Int, - @DrawableRes icon: Int, - @StringRes iconDescription: Int = title + title: String, + icon: Painter, + iconDescription: String = title ) { ListItem( modifier = Modifier.clickable( @@ -520,14 +489,12 @@ private fun DrawerMenuItem( } ), text = { - Text(text = stringResource(id = title)) + Text(text = title) }, icon = { Icon( - painter = painterResource(id = icon), - contentDescription = stringResource( - id = iconDescription - ) + painter = icon, + contentDescription = iconDescription ) }, ) diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/dm/DMConversationListScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/dm/DMConversationListScene.kt index d81c748c2..d46f4ea63 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/dm/DMConversationListScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/dm/DMConversationListScene.kt @@ -20,11 +20,12 @@ */ package com.twidere.twiderex.scenes.dm -import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.FloatingActionButton import androidx.compose.material.Icon import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.paging.LoadState @@ -34,6 +35,7 @@ import com.twidere.twiderex.component.foundation.AppBar import com.twidere.twiderex.component.foundation.AppBarNavigationButton import com.twidere.twiderex.component.foundation.InAppNotificationScaffold import com.twidere.twiderex.component.foundation.SwipeToRefreshLayout +import com.twidere.twiderex.component.lazy.LazyListController import com.twidere.twiderex.component.lazy.ui.LazyUiDMConversationList import com.twidere.twiderex.di.assisted.assistedViewModel import com.twidere.twiderex.navigation.Route @@ -44,14 +46,6 @@ import com.twidere.twiderex.viewmodel.dm.DMConversationViewModel @Composable fun DMConversationListScene() { - val account = LocalActiveAccount.current ?: return - val navController = LocalNavController.current - val viewModel = assistedViewModel( - account, - ) { - it.create(account) - } - val source = viewModel.source.collectAsLazyPagingItems() TwidereScene { InAppNotificationScaffold( topBar = { @@ -65,33 +59,58 @@ fun DMConversationListScene() { ) }, floatingActionButton = { - FloatingActionButton( - onClick = { - navController.navigate(Route.Messages.NewConversation) - } - ) { - Icon( - painter = painterResource(id = R.drawable.ic_add), - contentDescription = stringResource( - id = R.string.scene_lists_icons_create - ), - ) - } + DMConversationListSceneFab() }, ) { - Box { - SwipeToRefreshLayout( - refreshingState = source.loadState.refresh is LoadState.Loading, - onRefresh = { source.refresh() } - ) { - LazyUiDMConversationList( - items = source, - onItemClicked = { - navController.navigate(Route.Messages.Conversation(it.conversation.conversationKey)) - } - ) - } - } + DMConversationListSceneContent() } } } + +@Composable +fun DMConversationListSceneFab() { + val navController = LocalNavController.current + FloatingActionButton( + onClick = { + navController.navigate(Route.Messages.NewConversation) + } + ) { + Icon( + painter = painterResource(id = R.drawable.ic_add), + contentDescription = stringResource( + id = R.string.scene_lists_icons_create + ), + ) + } +} + +@Composable +fun DMConversationListSceneContent( + lazyListController: LazyListController? = null +) { + val account = LocalActiveAccount.current ?: return + val navController = LocalNavController.current + val viewModel = + assistedViewModel( + account, + ) { + it.create(account) + } + val source = viewModel.source.collectAsLazyPagingItems() + val listState = rememberLazyListState() + LaunchedEffect(lazyListController) { + lazyListController?.listState = listState + } + SwipeToRefreshLayout( + refreshingState = source.loadState.refresh is LoadState.Loading, + onRefresh = { source.refresh() } + ) { + LazyUiDMConversationList( + items = source, + state = listState, + onItemClicked = { + navController.navigate(Route.Messages.Conversation(it.conversation.conversationKey)) + } + ) + } +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/AllNotificationItem.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/AllNotificationItem.kt index 70025ef47..84d4f8a2c 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/AllNotificationItem.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/AllNotificationItem.kt @@ -20,7 +20,7 @@ */ package com.twidere.twiderex.scenes.home -import androidx.compose.material.Scaffold +import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.res.painterResource @@ -28,8 +28,13 @@ import androidx.compose.ui.res.stringResource import com.twidere.services.microblog.NotificationService import com.twidere.twiderex.R import com.twidere.twiderex.component.TimelineComponent +import com.twidere.twiderex.component.foundation.AppBar +import com.twidere.twiderex.component.foundation.AppBarNavigationButton +import com.twidere.twiderex.component.foundation.InAppNotificationScaffold +import com.twidere.twiderex.component.lazy.LazyListController import com.twidere.twiderex.di.assisted.assistedViewModel import com.twidere.twiderex.ui.LocalActiveAccount +import com.twidere.twiderex.ui.TwidereScene import com.twidere.twiderex.viewmodel.timeline.NotificationTimelineViewModel class AllNotificationItem : HomeNavigationItem() { @@ -38,6 +43,9 @@ class AllNotificationItem : HomeNavigationItem() { return stringResource(id = R.string.scene_notification_tabs_all) } + override val route: String + get() = TODO("Not yet implemented") + @Composable override fun icon(): Painter = painterResource(id = R.drawable.ic_message_circle) @@ -47,17 +55,45 @@ class AllNotificationItem : HomeNavigationItem() { if (account.service !is NotificationService) { return } - val viewModel = - assistedViewModel( - account - ) { - it.create(account = account) + AllNotificationSceneContent( + lazyListController = lazyListController, + ) + } +} + +@Composable +fun AllNotificationScene() { + TwidereScene { + InAppNotificationScaffold( + topBar = { + AppBar( + title = { + Text(text = stringResource(id = R.string.scene_notification_tabs_all)) + }, + navigationIcon = { + AppBarNavigationButton() + } + ) } - Scaffold { - TimelineComponent( - viewModel = viewModel, - lazyListController = lazyListController, - ) + ) { + AllNotificationSceneContent() } } } + +@Composable +fun AllNotificationSceneContent( + lazyListController: LazyListController? = null, +) { + val account = LocalActiveAccount.current ?: return + val viewModel = + assistedViewModel( + account + ) { + it.create(account = account) + } + TimelineComponent( + viewModel = viewModel, + lazyListController = lazyListController, + ) +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/DMConversationListItem.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/DMConversationListItem.kt new file mode 100644 index 000000000..7afcc9cac --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/DMConversationListItem.kt @@ -0,0 +1,57 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.scenes.home + +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import com.twidere.twiderex.R +import com.twidere.twiderex.navigation.Route +import com.twidere.twiderex.scenes.dm.DMConversationListSceneContent +import com.twidere.twiderex.scenes.dm.DMConversationListSceneFab + +class DMConversationListItem : HomeNavigationItem() { + @Composable + override fun name(): String { + return stringResource(id = R.string.scene_messages_title) + } + + override val route: String + get() = Route.Messages.Home + + @Composable + override fun icon(): Painter { + return painterResource(id = R.drawable.ic_mail) + } + + @Composable + override fun Fab() { + DMConversationListSceneFab() + } + + @Composable + override fun Content() { + DMConversationListSceneContent( + lazyListController + ) + } +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/DraftNavigationItem.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/DraftNavigationItem.kt new file mode 100644 index 000000000..1cb77c6c0 --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/DraftNavigationItem.kt @@ -0,0 +1,51 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.scenes.home + +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import com.twidere.twiderex.R +import com.twidere.twiderex.navigation.Route +import com.twidere.twiderex.scenes.DraftListSceneContent + +class DraftNavigationItem : HomeNavigationItem() { + @Composable + override fun name(): String { + return stringResource(id = R.string.scene_drafts_title) + } + + override val route: String + get() = Route.Draft.List + + @Composable + override fun icon(): Painter { + return painterResource(id = R.drawable.ic_note) + } + + @Composable + override fun Content() { + DraftListSceneContent( + lazyListController = lazyListController, + ) + } +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/HomeMenus.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/HomeMenus.kt new file mode 100644 index 000000000..f3a82189d --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/HomeMenus.kt @@ -0,0 +1,70 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.scenes.home + +import com.twidere.twiderex.model.PlatformType + +enum class HomeMenus( + val item: HomeNavigationItem, + val showDefault: Boolean, + val supportedPlatformType: List, +) { + HomeTimeline( + item = HomeTimelineItem(), + showDefault = true, + supportedPlatformType = PlatformType.values().toList(), + ), + MastodonNotification( + item = MastodonNotificationItem(), + showDefault = true, + supportedPlatformType = listOf(PlatformType.Mastodon), + ), + Mention( + item = MentionItem(), + showDefault = true, + supportedPlatformType = listOf(PlatformType.Twitter), + ), + Search( + item = SearchItem(), + showDefault = true, + supportedPlatformType = PlatformType.values().toList(), + ), + Me( + item = MeItem(), + showDefault = true, + supportedPlatformType = PlatformType.values().toList(), + ), + Message( + item = DMConversationListItem(), + showDefault = false, + supportedPlatformType = listOf(PlatformType.Twitter), + ), + Draft( + item = DraftNavigationItem(), + showDefault = false, + supportedPlatformType = PlatformType.values().toList(), + ), + Lists( + item = ListsNavigationItem(), + showDefault = false, + supportedPlatformType = PlatformType.values().toList(), + ) +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/HomeNavigationItem.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/HomeNavigationItem.kt index f0fb7962b..476066737 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/HomeNavigationItem.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/HomeNavigationItem.kt @@ -22,6 +22,7 @@ package com.twidere.twiderex.scenes.home import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.sizeIn +import androidx.compose.material.FabPosition import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.painter.Painter @@ -33,6 +34,8 @@ abstract class HomeNavigationItem { @Composable abstract fun name(): String + abstract val route: String + @Composable abstract fun icon(): Painter open val withAppBar = true @@ -48,6 +51,8 @@ abstract class HomeNavigationItem { Spacer(modifier = Modifier.sizeIn(minWidth = 1.dp, minHeight = 1.dp)) } + open val floatingActionButtonPosition = FabPosition.End + // offset to hide fab when scroll timeline open val fabSize = 0.dp } diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/HomeTimelineItem.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/HomeTimelineItem.kt index 5a7f811dc..07400d115 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/HomeTimelineItem.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/HomeTimelineItem.kt @@ -22,7 +22,7 @@ package com.twidere.twiderex.scenes.home import androidx.compose.material.FloatingActionButton import androidx.compose.material.Icon -import androidx.compose.material.Scaffold +import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.res.painterResource @@ -31,9 +31,15 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.twidere.twiderex.R import com.twidere.twiderex.component.TimelineComponent +import com.twidere.twiderex.component.foundation.AppBar +import com.twidere.twiderex.component.foundation.AppBarNavigationButton +import com.twidere.twiderex.component.foundation.InAppNotificationScaffold +import com.twidere.twiderex.component.lazy.LazyListController import com.twidere.twiderex.component.navigation.LocalNavigator import com.twidere.twiderex.di.assisted.assistedViewModel +import com.twidere.twiderex.navigation.Route import com.twidere.twiderex.ui.LocalActiveAccount +import com.twidere.twiderex.ui.TwidereScene import com.twidere.twiderex.viewmodel.compose.ComposeType import com.twidere.twiderex.viewmodel.timeline.HomeTimelineViewModel @@ -41,45 +47,82 @@ class HomeTimelineItem : HomeNavigationItem() { @Composable override fun name(): String = stringResource(R.string.scene_timeline_title) + override val route: String + get() = Route.HomeTimeline @Composable override fun icon(): Painter = painterResource(id = R.drawable.ic_home) @Composable override fun Content() { - val account = LocalActiveAccount.current ?: return - val viewModel = assistedViewModel( - account - ) { - it.create(account) - } - Scaffold { - TimelineComponent( - viewModel = viewModel, - lazyListController = lazyListController, - ) - } + HomeTimelineSceneContent( + lazyListController = lazyListController + ) } @Composable override fun Fab() { - val navigator = LocalNavigator.current - FloatingActionButton( - onClick = { - navigator.compose(ComposeType.New) + HomeTimelineFab() + } + + override val fabSize: Dp + get() = HomeTimeLineItemDefaults.FabSize +} + +@Composable +fun HomeTimelineScene() { + TwidereScene { + InAppNotificationScaffold( + topBar = { + AppBar( + title = { + Text(text = stringResource(id = R.string.scene_timeline_title)) + }, + navigationIcon = { + AppBarNavigationButton() + } + ) + }, + floatingActionButton = { + HomeTimelineFab() } ) { - Icon( - painter = painterResource(id = R.drawable.ic_feather), - contentDescription = stringResource( - id = R.string.accessibility_scene_home_compose - ) - ) + HomeTimelineSceneContent() } } +} - override val fabSize: Dp - get() = HomeTimeLineItemDefaults.FabSize +@Composable +private fun HomeTimelineFab() { + val navigator = LocalNavigator.current + FloatingActionButton( + onClick = { + navigator.compose(ComposeType.New) + } + ) { + Icon( + painter = painterResource(id = R.drawable.ic_feather), + contentDescription = stringResource( + id = R.string.accessibility_scene_home_compose + ) + ) + } +} + +@Composable +fun HomeTimelineSceneContent( + lazyListController: LazyListController? = null +) { + val account = LocalActiveAccount.current ?: return + val viewModel = assistedViewModel( + account + ) { + it.create(account) + } + TimelineComponent( + viewModel = viewModel, + lazyListController = lazyListController, + ) } private object HomeTimeLineItemDefaults { diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/ListsNavigationItem.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/ListsNavigationItem.kt new file mode 100644 index 000000000..f4351d511 --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/ListsNavigationItem.kt @@ -0,0 +1,59 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.scenes.home + +import androidx.compose.material.FabPosition +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import com.twidere.twiderex.R +import com.twidere.twiderex.navigation.Route +import com.twidere.twiderex.scenes.lists.ListsSceneContent +import com.twidere.twiderex.scenes.lists.ListsSceneFab + +class ListsNavigationItem : HomeNavigationItem() { + @Composable + override fun name(): String { + return stringResource(id = R.string.scene_lists_title) + } + + override val route: String + get() = Route.Lists.Home + + @Composable + override fun icon(): Painter { + return painterResource(id = R.drawable.ic_lists) + } + + @Composable + override fun Fab() { + ListsSceneFab() + } + + override val floatingActionButtonPosition: FabPosition + get() = FabPosition.Center + + @Composable + override fun Content() { + ListsSceneContent() + } +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/MastodonNotificationItem.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/MastodonNotificationItem.kt index 88d042e17..35580ca87 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/MastodonNotificationItem.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/MastodonNotificationItem.kt @@ -21,6 +21,7 @@ package com.twidere.twiderex.scenes.home import androidx.compose.material.Scaffold +import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember @@ -30,11 +31,16 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import com.twidere.services.microblog.NotificationService import com.twidere.twiderex.R +import com.twidere.twiderex.component.foundation.AppBar +import com.twidere.twiderex.component.foundation.AppBarNavigationButton +import com.twidere.twiderex.component.foundation.InAppNotificationScaffold import com.twidere.twiderex.component.foundation.Pager import com.twidere.twiderex.component.foundation.TextTabsComponent import com.twidere.twiderex.component.foundation.rememberPagerState import com.twidere.twiderex.component.lazy.LazyListController +import com.twidere.twiderex.navigation.Route import com.twidere.twiderex.ui.LocalActiveAccount +import com.twidere.twiderex.ui.TwidereScene import kotlinx.coroutines.launch class MastodonNotificationItem : HomeNavigationItem() { @@ -43,6 +49,9 @@ class MastodonNotificationItem : HomeNavigationItem() { return stringResource(id = R.string.scene_notification_title) } + override val route: String + get() = Route.Mastodon.Notification + @Composable override fun icon(): Painter { return painterResource(id = R.drawable.ic_bell) @@ -52,40 +61,71 @@ class MastodonNotificationItem : HomeNavigationItem() { @Composable override fun Content() { - val account = LocalActiveAccount.current ?: return - if (account.service !is NotificationService) { - return - } - val tabs = remember { - listOf( - AllNotificationItem(), - MentionItem(), - ) - } - val pagerState = rememberPagerState(maxPage = tabs.lastIndex) - LaunchedEffect(pagerState.currentPage) { - // FIXME: 2021/5/17 A little bit dirty - lazyListController = tabs[pagerState.currentPage].lazyListController - } - val scope = rememberCoroutineScope() - Scaffold( + MastodonNotificationSceneContent( + setLazyListController = { + lazyListController = it + } + ) + } +} + +@Composable +fun MastodonNotificationScene() { + TwidereScene { + InAppNotificationScaffold( topBar = { - TextTabsComponent( - items = tabs.map { it.name() }, - selectedItem = pagerState.currentPage, - onItemSelected = { - scope.launch { - pagerState.selectPage { - pagerState.currentPage = it - } - } + AppBar( + title = { + Text(text = stringResource(id = R.string.scene_notification_title)) }, + navigationIcon = { + AppBarNavigationButton() + } ) } ) { - Pager(state = pagerState) { - tabs[page].Content() - } + MastodonNotificationSceneContent() + } + } +} + +@Composable +fun MastodonNotificationSceneContent( + setLazyListController: ((lazyListController: LazyListController) -> Unit)? = null, +) { + val account = LocalActiveAccount.current ?: return + if (account.service !is NotificationService) { + return + } + val tabs = remember { + listOf( + AllNotificationItem(), + MentionItem(), + ) + } + val pagerState = rememberPagerState(maxPage = tabs.lastIndex) + LaunchedEffect(pagerState.currentPage) { + // FIXME: 2021/5/17 A little bit dirty + setLazyListController?.invoke(tabs[pagerState.currentPage].lazyListController) + } + val scope = rememberCoroutineScope() + Scaffold( + topBar = { + TextTabsComponent( + items = tabs.map { it.name() }, + selectedItem = pagerState.currentPage, + onItemSelected = { + scope.launch { + pagerState.selectPage { + pagerState.currentPage = it + } + } + }, + ) + } + ) { + Pager(state = pagerState) { + tabs[page].Content() } } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/MeItem.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/MeItem.kt index 0b25d8f24..9f453101a 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/MeItem.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/MeItem.kt @@ -20,19 +20,26 @@ */ package com.twidere.twiderex.scenes.home -import androidx.compose.material.Scaffold +import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import com.twidere.twiderex.R import com.twidere.twiderex.component.UserComponent +import com.twidere.twiderex.component.foundation.AppBar +import com.twidere.twiderex.component.foundation.AppBarNavigationButton +import com.twidere.twiderex.component.foundation.InAppNotificationScaffold +import com.twidere.twiderex.navigation.Route import com.twidere.twiderex.ui.LocalActiveAccount +import com.twidere.twiderex.ui.TwidereScene class MeItem : HomeNavigationItem() { @Composable override fun name(): String = stringResource(R.string.scene_profile_title) + override val route: String + get() = Route.Me @Composable override fun icon(): Painter = painterResource(id = R.drawable.ic_user) @@ -42,11 +49,34 @@ class MeItem : HomeNavigationItem() { @Composable override fun Content() { - val account = LocalActiveAccount.current - account?.toUi()?.let { user -> - Scaffold { - UserComponent(userKey = user.userKey) + MeSceneContent() + } +} + +@Composable +fun MeScene() { + TwidereScene { + InAppNotificationScaffold( + topBar = { + AppBar( + title = { + Text(text = stringResource(id = R.string.scene_profile_title)) + }, + navigationIcon = { + AppBarNavigationButton() + } + ) } + ) { + MeSceneContent() } } } + +@Composable +fun MeSceneContent() { + val account = LocalActiveAccount.current + account?.toUi()?.let { user -> + UserComponent(userKey = user.userKey) + } +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/MentionItem.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/MentionItem.kt index 068cbd29b..0b8d5e2f3 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/MentionItem.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/MentionItem.kt @@ -20,20 +20,28 @@ */ package com.twidere.twiderex.scenes.home -import androidx.compose.material.Scaffold +import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import com.twidere.twiderex.R import com.twidere.twiderex.component.TimelineComponent +import com.twidere.twiderex.component.foundation.AppBar +import com.twidere.twiderex.component.foundation.AppBarNavigationButton +import com.twidere.twiderex.component.foundation.InAppNotificationScaffold +import com.twidere.twiderex.component.lazy.LazyListController import com.twidere.twiderex.di.assisted.assistedViewModel +import com.twidere.twiderex.navigation.Route import com.twidere.twiderex.ui.LocalActiveAccount +import com.twidere.twiderex.ui.TwidereScene import com.twidere.twiderex.viewmodel.timeline.MentionsTimelineViewModel class MentionItem : HomeNavigationItem() { @Composable override fun name(): String = stringResource(R.string.scene_mentions_title) + override val route: String + get() = Route.Mentions @Composable override fun icon(): Painter = painterResource(id = R.drawable.ic_message_circle) @@ -47,11 +55,46 @@ class MentionItem : HomeNavigationItem() { ) { it.create(account) } - Scaffold { - TimelineComponent( - viewModel = viewModel, - lazyListController = lazyListController, - ) + TimelineComponent( + viewModel = viewModel, + lazyListController = lazyListController, + ) + } +} + +@Composable +fun MentionScene() { + TwidereScene { + InAppNotificationScaffold( + topBar = { + AppBar( + title = { + Text(text = stringResource(id = R.string.scene_mentions_title)) + }, + navigationIcon = { + AppBarNavigationButton() + } + ) + } + ) { + MentionSceneContent() } } } + +@Composable +fun MentionSceneContent( + lazyListController: LazyListController? = null +) { + val account = LocalActiveAccount.current ?: return + val viewModel = + assistedViewModel( + account + ) { + it.create(account) + } + TimelineComponent( + viewModel = viewModel, + lazyListController = lazyListController, + ) +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/NotificationItem.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/NotificationItem.kt index b9ad502ec..c271ca7ae 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/NotificationItem.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/NotificationItem.kt @@ -20,7 +20,7 @@ */ package com.twidere.twiderex.scenes.home -import androidx.compose.material.Scaffold +import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.res.painterResource @@ -28,31 +28,69 @@ import androidx.compose.ui.res.stringResource import com.twidere.services.microblog.NotificationService import com.twidere.twiderex.R import com.twidere.twiderex.component.TimelineComponent +import com.twidere.twiderex.component.foundation.AppBar +import com.twidere.twiderex.component.foundation.AppBarNavigationButton +import com.twidere.twiderex.component.foundation.InAppNotificationScaffold +import com.twidere.twiderex.component.lazy.LazyListController import com.twidere.twiderex.di.assisted.assistedViewModel +import com.twidere.twiderex.navigation.Route import com.twidere.twiderex.ui.LocalActiveAccount +import com.twidere.twiderex.ui.TwidereScene import com.twidere.twiderex.viewmodel.timeline.NotificationTimelineViewModel class NotificationItem : HomeNavigationItem() { @Composable - override fun name(): String = stringResource(R.string.scene_mentions_title) + override fun name(): String = stringResource(R.string.scene_notification_title) + override val route: String + get() = Route.Notification @Composable override fun icon(): Painter = painterResource(id = R.drawable.ic_message_circle) @Composable override fun Content() { - val account = LocalActiveAccount.current ?: return - if (account.service !is NotificationService) { - return - } - val viewModel = - assistedViewModel( - account - ) { - it.create(account) + NotificationContent( + lazyListController = lazyListController, + ) + } +} + +@Composable +fun NotificationScene() { + TwidereScene { + InAppNotificationScaffold( + topBar = { + AppBar( + title = { + Text(text = stringResource(id = R.string.scene_mentions_title)) + }, + navigationIcon = { + AppBarNavigationButton() + } + ) } - Scaffold { - TimelineComponent(viewModel = viewModel) + ) { + NotificationContent() } } } + +@Composable +fun NotificationContent( + lazyListController: LazyListController? = null +) { + val account = LocalActiveAccount.current ?: return + if (account.service !is NotificationService) { + return + } + val viewModel = + assistedViewModel( + account + ) { + it.create(account) + } + TimelineComponent( + viewModel = viewModel, + lazyListController = lazyListController, + ) +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/SearchItem.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/SearchItem.kt index 26c1b377e..484750db8 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/SearchItem.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/SearchItem.kt @@ -20,7 +20,6 @@ */ package com.twidere.twiderex.scenes.home -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Column @@ -52,12 +51,16 @@ import androidx.paging.compose.collectAsLazyPagingItems import androidx.paging.compose.items import com.twidere.twiderex.R import com.twidere.twiderex.component.foundation.AppBar +import com.twidere.twiderex.component.foundation.AppBarNavigationButton +import com.twidere.twiderex.component.foundation.InAppNotificationScaffold import com.twidere.twiderex.component.navigation.LocalNavigator import com.twidere.twiderex.component.trend.MastodonTrendItem import com.twidere.twiderex.component.trend.TwitterTrendItem import com.twidere.twiderex.di.assisted.assistedViewModel import com.twidere.twiderex.model.PlatformType +import com.twidere.twiderex.navigation.Route import com.twidere.twiderex.ui.LocalActiveAccount +import com.twidere.twiderex.ui.TwidereScene import com.twidere.twiderex.viewmodel.search.SearchInputViewModel import com.twidere.twiderex.viewmodel.trend.TrendViewModel @@ -65,6 +68,8 @@ class SearchItem : HomeNavigationItem() { @Composable override fun name(): String = stringResource(R.string.scene_search_title) + override val route: String + get() = Route.Search.Home @Composable override fun icon(): Painter = painterResource(id = R.drawable.ic_search) @@ -72,162 +77,187 @@ class SearchItem : HomeNavigationItem() { override val withAppBar: Boolean get() = false - @OptIn(ExperimentalFoundationApi::class, ExperimentalMaterialApi::class) @Composable override fun Content() { - val account = LocalActiveAccount.current ?: return - val viewModel = - assistedViewModel( - account - ) { - it.create(account = account) - } - val trendViewModel = assistedViewModel( - account - ) { - it.create(account = account) - } - val source by viewModel.savedSource.observeAsState(initial = emptyList()) - val trends = trendViewModel.source.collectAsLazyPagingItems() - val navigator = LocalNavigator.current - val searchCount = 3 - val expandSearch by viewModel.expandSearch.observeAsState(false) - Scaffold( + SearchSceneContent() + } +} + +@Composable +fun SearchScene() { + TwidereScene { + InAppNotificationScaffold( topBar = { AppBar( title = { - ProvideTextStyle(value = MaterialTheme.typography.body1) { - Row( - modifier = Modifier.clickable( - onClick = { - navigator.searchInput() - }, - indication = null, - interactionSource = remember { MutableInteractionSource() } - ) - ) { - CompositionLocalProvider( - LocalContentAlpha provides ContentAlpha.medium - ) { - Text( - modifier = Modifier - .weight(1F) - .align(Alignment.CenterVertically), - text = stringResource(id = R.string.scene_search_search_bar_placeholder), - ) - } - IconButton( - onClick = { - navigator.searchInput() - } - ) { - Icon( - painter = painterResource(id = R.drawable.ic_search), - contentDescription = stringResource( - id = R.string.scene_search_title - ) - ) - } - } - } + Text(text = stringResource(id = R.string.scene_search_title)) + }, + navigationIcon = { + AppBarNavigationButton() } ) } ) { - LazyColumn { - item { - if (source.isNotEmpty()) ListItem { - Text( - text = stringResource(id = R.string.scene_search_saved_search), - style = MaterialTheme.typography.button - ) - } - } - items(items = source.filterIndexed { index, _ -> index < searchCount || expandSearch }) { - ListItem( - modifier = Modifier.clickable( - onClick = { - viewModel.addOrUpgrade(it.content) - navigator.search(it.content) + SearchSceneContent() + } + } +} + +@OptIn(ExperimentalMaterialApi::class) +@Composable +fun SearchSceneContent() { + val account = LocalActiveAccount.current ?: return + val viewModel = + assistedViewModel( + account + ) { + it.create(account = account) + } + val trendViewModel = assistedViewModel( + account + ) { + it.create(account = account) + } + val source by viewModel.savedSource.observeAsState(initial = emptyList()) + val trends = trendViewModel.source.collectAsLazyPagingItems() + val navigator = LocalNavigator.current + val searchCount = 3 + val expandSearch by viewModel.expandSearch.observeAsState(false) + Scaffold( + topBar = { + AppBar( + title = { + ProvideTextStyle(value = MaterialTheme.typography.body1) { + Row( + modifier = Modifier.clickable( + onClick = { + navigator.searchInput() + }, + indication = null, + interactionSource = remember { MutableInteractionSource() } + ) + ) { + CompositionLocalProvider( + LocalContentAlpha provides ContentAlpha.medium + ) { + Text( + modifier = Modifier + .weight(1F) + .align(Alignment.CenterVertically), + text = stringResource(id = R.string.scene_search_search_bar_placeholder), + ) } - ), - trailing = { IconButton( onClick = { - viewModel.remove(it) + navigator.searchInput() } ) { Icon( - painter = painterResource(id = R.drawable.ic_trash_can), + painter = painterResource(id = R.drawable.ic_search), contentDescription = stringResource( - id = R.string.common_controls_actions_remove + id = R.string.scene_search_title ) ) } - }, - text = { - Text( - text = it.content, - style = MaterialTheme.typography.subtitle1 - ) - }, + } + } + } + ) + } + ) { + LazyColumn { + item { + if (source.isNotEmpty()) ListItem { + Text( + text = stringResource(id = R.string.scene_search_saved_search), + style = MaterialTheme.typography.button ) } - item { - if (source.size > searchCount) ListItem( - modifier = Modifier.clickable { - viewModel.expandSearch.value = !expandSearch + } + items(items = source.filterIndexed { index, _ -> index < searchCount || expandSearch }) { + ListItem( + modifier = Modifier.clickable( + onClick = { + viewModel.addOrUpgrade(it.content) + navigator.search(it.content) + } + ), + trailing = { + IconButton( + onClick = { + viewModel.remove(it) + } + ) { + Icon( + painter = painterResource(id = R.drawable.ic_trash_can), + contentDescription = stringResource( + id = R.string.common_controls_actions_remove + ) + ) } - ) { + }, + text = { Text( - text = if (expandSearch) stringResource(id = R.string.scene_search_show_less) else stringResource(id = R.string.scene_search_show_more), - style = MaterialTheme.typography.subtitle1, - color = MaterialTheme.colors.primary + text = it.content, + style = MaterialTheme.typography.subtitle1 ) + }, + ) + } + item { + if (source.size > searchCount) ListItem( + modifier = Modifier.clickable { + viewModel.expandSearch.value = !expandSearch } + ) { + Text( + text = if (expandSearch) stringResource(id = R.string.scene_search_show_less) else stringResource(id = R.string.scene_search_show_more), + style = MaterialTheme.typography.subtitle1, + color = MaterialTheme.colors.primary + ) } - if (trends.itemCount > 0) { - item { - Column { - Divider() - ListItem { - when (account.type) { - PlatformType.Twitter -> - Text( - text = stringResource(id = R.string.scene_trends_world_wide), - style = MaterialTheme.typography.button - ) - PlatformType.StatusNet -> TODO() - PlatformType.Fanfou -> TODO() - PlatformType.Mastodon -> - Text( - text = stringResource(id = R.string.scene_trends_world_wide), - style = MaterialTheme.typography.button - ) - } + } + if (trends.itemCount > 0) { + item { + Column { + Divider() + ListItem { + when (account.type) { + PlatformType.Twitter -> + Text( + text = stringResource(id = R.string.scene_trends_world_wide), + style = MaterialTheme.typography.button + ) + PlatformType.StatusNet -> TODO() + PlatformType.Fanfou -> TODO() + PlatformType.Mastodon -> + Text( + text = stringResource(id = R.string.scene_trends_world_wide), + style = MaterialTheme.typography.button + ) } } } } - items(trends) { - it?.let { trend -> - when (account.type) { - PlatformType.Twitter -> TwitterTrendItem( - trend = it, - onClick = { - viewModel.addOrUpgrade(trend.query) - navigator.search(trend.query) - } - ) - PlatformType.StatusNet -> TODO() - PlatformType.Fanfou -> TODO() - PlatformType.Mastodon -> MastodonTrendItem( - trend = it, - onClick = { - navigator.hashtag(it.query) - } - ) - } + } + items(trends) { + it?.let { trend -> + when (account.type) { + PlatformType.Twitter -> TwitterTrendItem( + trend = it, + onClick = { + viewModel.addOrUpgrade(trend.query) + navigator.search(trend.query) + } + ) + PlatformType.StatusNet -> TODO() + PlatformType.Fanfou -> TODO() + PlatformType.Mastodon -> MastodonTrendItem( + trend = it, + onClick = { + navigator.hashtag(it.query) + } + ) } } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/ListsScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/ListsScene.kt index 7601dd251..fd1146589 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/ListsScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/ListsScene.kt @@ -20,7 +20,6 @@ */ package com.twidere.twiderex.scenes.lists -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding @@ -30,10 +29,6 @@ import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource @@ -50,7 +45,6 @@ import com.twidere.twiderex.component.lazy.ui.LazyUiListsList import com.twidere.twiderex.di.assisted.assistedViewModel import com.twidere.twiderex.model.PlatformType import com.twidere.twiderex.navigation.Route -import com.twidere.twiderex.scenes.lists.platform.MastodonListsCreateDialog import com.twidere.twiderex.ui.LocalActiveAccount import com.twidere.twiderex.ui.LocalNavController import com.twidere.twiderex.ui.TwidereScene @@ -59,20 +53,6 @@ import java.util.Locale @Composable fun ListsScene() { - val navController = LocalNavController.current - val account = LocalActiveAccount.current ?: return - // if list type is all , display title of each type - val listsViewMode = assistedViewModel( - account, - ) { - it.create(account) - } - val ownerItems = listsViewMode.ownerSource.collectAsLazyPagingItems() - val subscribeItems = listsViewMode.subscribedSource.collectAsLazyPagingItems() - val sourceItems = listsViewMode.source.collectAsLazyPagingItems() - var showCreateDialog by remember { - mutableStateOf(false) - } TwidereScene { InAppNotificationScaffold( topBar = { @@ -86,55 +66,73 @@ fun ListsScene() { ) }, floatingActionButton = { - FloatingActionButton( - onClick = { - when (account.type) { - PlatformType.Twitter -> navController.navigate(Route.Lists.TwitterCreate) - PlatformType.StatusNet -> TODO() - PlatformType.Fanfou -> TODO() - PlatformType.Mastodon -> showCreateDialog = true - } - } - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.padding(ListsSceneDefaults.Fab.ContentPadding) - ) { - Icon( - painter = painterResource(id = R.drawable.ic_add), - contentDescription = stringResource( - id = R.string.scene_lists_icons_create - ), - modifier = Modifier.padding(ListsSceneDefaults.Fab.IconPadding) - ) - Text( - text = stringResource(id = R.string.scene_lists_modify_create_title) - .uppercase(Locale.getDefault()), - style = MaterialTheme.typography.button - ) - } - } + ListsSceneFab() }, floatingActionButtonPosition = FabPosition.Center ) { - Box { - SwipeToRefreshLayout( - refreshingState = ownerItems.loadState.refresh is LoadState.Loading, - onRefresh = { ownerItems.refresh() } - ) { - LazyUiListsList( - listType = account.listType, - source = sourceItems, - ownerItems = ownerItems, - subscribedItems = subscribeItems, - onItemClicked = { navController.navigate(Route.Lists.Timeline(it.listKey)) } - ) - } - if (showCreateDialog) { - MastodonListsCreateDialog(onDismissRequest = { showCreateDialog = false }) - } + ListsSceneContent() + } + } +} + +@Composable +fun ListsSceneFab() { + val account = LocalActiveAccount.current ?: return + val navController = LocalNavController.current + FloatingActionButton( + onClick = { + when (account.type) { + PlatformType.Twitter -> navController.navigate(Route.Lists.TwitterCreate) + PlatformType.StatusNet -> TODO() + PlatformType.Fanfou -> TODO() + PlatformType.Mastodon -> navController.navigate(Route.Lists.MastodonCreateDialog) } } + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.padding(ListsSceneDefaults.Fab.ContentPadding) + ) { + Icon( + painter = painterResource(id = R.drawable.ic_add), + contentDescription = stringResource( + id = R.string.scene_lists_icons_create + ), + modifier = Modifier.padding(ListsSceneDefaults.Fab.IconPadding) + ) + Text( + text = stringResource(id = R.string.scene_lists_modify_create_title) + .uppercase(Locale.getDefault()), + style = MaterialTheme.typography.button + ) + } + } +} + +@Composable +fun ListsSceneContent() { + val account = LocalActiveAccount.current ?: return + val navController = LocalNavController.current + // if list type is all , display title of each type + val listsViewMode = assistedViewModel( + account, + ) { + it.create(account) + } + val ownerItems = listsViewMode.ownerSource.collectAsLazyPagingItems() + val subscribeItems = listsViewMode.subscribedSource.collectAsLazyPagingItems() + val sourceItems = listsViewMode.source.collectAsLazyPagingItems() + SwipeToRefreshLayout( + refreshingState = ownerItems.loadState.refresh is LoadState.Loading, + onRefresh = { ownerItems.refresh() } + ) { + LazyUiListsList( + listType = account.listType, + source = sourceItems, + ownerItems = ownerItems, + subscribedItems = subscribeItems, + onItemClicked = { navController.navigate(Route.Lists.Timeline(it.listKey)) } + ) } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/LayoutScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/LayoutScene.kt new file mode 100644 index 000000000..46ca670a8 --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/LayoutScene.kt @@ -0,0 +1,147 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.scenes.settings + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.Icon +import androidx.compose.material.ListItem +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Surface +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Menu +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import com.twidere.twiderex.component.foundation.AppBar +import com.twidere.twiderex.component.foundation.AppBarNavigationButton +import com.twidere.twiderex.component.foundation.InAppNotificationScaffold +import com.twidere.twiderex.component.foundation.ReorderableColumn +import com.twidere.twiderex.component.foundation.rememberReorderableColumnState +import com.twidere.twiderex.component.lazy.ItemHeader +import com.twidere.twiderex.component.status.UserName +import com.twidere.twiderex.component.status.UserScreenName +import com.twidere.twiderex.di.assisted.assistedViewModel +import com.twidere.twiderex.scenes.home.HomeMenus +import com.twidere.twiderex.ui.LocalActiveAccount +import com.twidere.twiderex.ui.TwidereScene +import com.twidere.twiderex.viewmodel.settings.LayoutViewModel + +@OptIn(ExperimentalMaterialApi::class) +@Composable +fun LayoutScene() { + val account = LocalActiveAccount.current ?: return + val viewModel = assistedViewModel( + account + ) { + it.create( + account = account, + ) + } + val user = viewModel.user + val menus = remember(account.type) { + HomeMenus.values().filter { it.supportedPlatformType.contains(account.type) }.groupBy { + it.showDefault + }.map { + listOf( + if (it.key) { + "Tabbar actions" + } else { + "Sidebar actions" + } + ) + it.value + }.flatten() + } + TwidereScene { + InAppNotificationScaffold( + topBar = { + AppBar( + navigationIcon = { + AppBarNavigationButton() + }, + title = { + Text(text = "Layout") + } + ) + } + ) { + Column( + modifier = Modifier + .verticalScroll(rememberScrollState()), + ) { + Surface( + color = MaterialTheme.colors.primary, + ) { + ListItem( + text = { + Row { + UserName(user = user) + UserScreenName(user = user) + } + } + ) + } + ListItem( + text = { + Text(text = "Custom Layout") + }, + secondaryText = { + Text(text = "Choose and arrange up to 5 actions that will appear on the tabbar (The local and federal timelines will only be displayed in Mastodon.") + } + ) + ReorderableColumn( + data = menus, + state = rememberReorderableColumnState { _, _ -> + } + ) { + when (it) { + is String -> { + ItemHeader { + Text(text = it) + } + } + is HomeMenus -> { + ListItem( + text = { + Text(text = it.item.name()) + }, + icon = { + Icon( + it.item.icon(), + contentDescription = null, + tint = MaterialTheme.colors.primary, + ) + }, + trailing = { + Icon(Icons.Default.Menu, contentDescription = null) + } + ) + } + } + } + } + } + } +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/SettingsScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/SettingsScene.kt index 32ae1a0f0..40b96910b 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/SettingsScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/SettingsScene.kt @@ -79,12 +79,11 @@ fun SettingsScene() { painterResource(id = R.drawable.ic_triangle_square_circle), route = Route.Settings.Misc, ), - // TODO -// SettingItem( -// "Layout", -// painterResource(id = R.drawable.ic_layout_sidebar), -// route = "", -// ), + SettingItem( + "Layout", + painterResource(id = R.drawable.ic_layout_sidebar), + route = Route.Settings.Layout, + ), // SettingItem( // "Web Browser", // painterResource(id = R.drawable.ic_browser), diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/settings/LayoutViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/settings/LayoutViewModel.kt new file mode 100644 index 000000000..ddea3769f --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/settings/LayoutViewModel.kt @@ -0,0 +1,41 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.viewmodel.settings + +import androidx.lifecycle.ViewModel +import com.twidere.twiderex.model.AccountDetails +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject + +class LayoutViewModel @AssistedInject constructor( + @Assisted private val account: AccountDetails, +) : ViewModel() { + @dagger.assisted.AssistedFactory + interface AssistedFactory { + fun create( + account: AccountDetails, + ): LayoutViewModel + } + + val user by lazy { + account.toUi() + } +} From f0afa561315ca4b2d214551bb42cdd4e391a3789 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Thu, 8 Jul 2021 17:57:58 +0800 Subject: [PATCH 006/137] fix incorrect pager state being saved --- .../kotlin/com/twidere/twiderex/component/foundation/Pager.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/Pager.kt b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/Pager.kt index 5caa64974..68b76d15b 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/Pager.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/Pager.kt @@ -70,6 +70,9 @@ fun rememberPagerState( maxPage: Int = 0, ): PagerState { return rememberSaveable( + currentPage, + minPage, + maxPage, saver = PagerState.Saver(), ) { PagerState(currentPage, minPage, maxPage) From 763a5fbb9ccc4615fe4a18e56bf0eeaac3f3156a Mon Sep 17 00:00:00 2001 From: Tlaster Date: Thu, 8 Jul 2021 17:59:19 +0800 Subject: [PATCH 007/137] fix search scene route --- .../component/navigation/Navigator.kt | 4 +--- .../com/twidere/twiderex/navigation/Route.kt | 22 ++++++++++++++++++- .../scenes/search/SearchInputScene.kt | 14 ++++++++++++ .../twiderex/scenes/search/SearchScene.kt | 2 +- 4 files changed, 37 insertions(+), 5 deletions(-) diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/navigation/Navigator.kt b/app/src/main/kotlin/com/twidere/twiderex/component/navigation/Navigator.kt index 3b4c93fe4..30712f7b4 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/navigation/Navigator.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/navigation/Navigator.kt @@ -37,7 +37,6 @@ import com.twidere.twiderex.navigation.twidereXSchema import com.twidere.twiderex.viewmodel.compose.ComposeType import moe.tlaster.precompose.navigation.NavController import moe.tlaster.precompose.navigation.NavOptions -import moe.tlaster.precompose.navigation.PopUpTo val LocalNavigator = staticCompositionLocalOf { error("No Navigator") } @@ -109,13 +108,12 @@ class Navigator( } override fun search(keyword: String) { - navController.navigate(Route.Search.Search(keyword), NavOptions(popUpTo = PopUpTo(Route.Home))) + navController.navigate(Route.Search.Search(keyword)) } override fun searchInput(initial: String?) { navController.navigate( Route.Search.SearchInput(initial), - NavOptions(popUpTo = PopUpTo(Route.Home)) ) } diff --git a/app/src/main/kotlin/com/twidere/twiderex/navigation/Route.kt b/app/src/main/kotlin/com/twidere/twiderex/navigation/Route.kt index dd16b9c36..8d03a0d46 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/navigation/Route.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/navigation/Route.kt @@ -64,6 +64,10 @@ import com.twidere.twiderex.scenes.mastodon.MastodonSignInScene import com.twidere.twiderex.scenes.mastodon.MastodonWebSignInScene import com.twidere.twiderex.scenes.search.SearchInputScene import com.twidere.twiderex.scenes.search.SearchScene +import com.twidere.twiderex.scenes.search.fadeCreateTransition +import com.twidere.twiderex.scenes.search.fadeDestroyTransition +import com.twidere.twiderex.scenes.search.fadePauseTransition +import com.twidere.twiderex.scenes.search.fadeResumeTransition import com.twidere.twiderex.scenes.settings.AboutScene import com.twidere.twiderex.scenes.settings.AccountManagementScene import com.twidere.twiderex.scenes.settings.AccountNotificationScene @@ -574,8 +578,18 @@ fun RouteBuilder.route(constraints: Constraints) { } } + authorizedScene(Route.Search.Home) { + com.twidere.twiderex.scenes.home.SearchScene() + } + authorizedScene( "search/input", + navTransition = NavTransition( + createTransition = fadeCreateTransition, + destroyTransition = fadeDestroyTransition, + pauseTransition = fadePauseTransition, + resumeTransition = fadeResumeTransition, + ), ) { backStackEntry -> SearchInputScene( backStackEntry.query("keyword")?.let { URLDecoder.decode(it, "UTF-8") } @@ -586,7 +600,13 @@ fun RouteBuilder.route(constraints: Constraints) { "search/result/{keyword}", deepLinks = twitterHosts.map { "$it/search?q={keyword}" - } + "${DeepLinks.Search}/{keyword}" + } + "${DeepLinks.Search}/{keyword}", + navTransition = NavTransition( + createTransition = fadeCreateTransition, + destroyTransition = fadeDestroyTransition, + pauseTransition = fadePauseTransition, + resumeTransition = fadeResumeTransition, + ), ) { backStackEntry -> backStackEntry.path("keyword")?.takeIf { it.isNotEmpty() }?.let { SearchScene(keyword = URLDecoder.decode(it, "UTF-8")) diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/search/SearchInputScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/search/SearchInputScene.kt index 391d08362..1c8649a56 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/search/SearchInputScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/search/SearchInputScene.kt @@ -41,6 +41,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.GraphicsLayerScope import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextRange @@ -57,6 +58,19 @@ import com.twidere.twiderex.ui.LocalActiveAccount import com.twidere.twiderex.ui.TwidereScene import com.twidere.twiderex.viewmodel.search.SearchInputViewModel +val fadeCreateTransition: GraphicsLayerScope.(factor: Float) -> Unit = { factor -> + alpha = factor +} +val fadeDestroyTransition: GraphicsLayerScope.(factor: Float) -> Unit = { factor -> + alpha = factor +} +val fadePauseTransition: GraphicsLayerScope.(factor: Float) -> Unit = { factor -> + alpha = factor +} +val fadeResumeTransition: GraphicsLayerScope.(factor: Float) -> Unit = { factor -> + alpha = factor +} + @OptIn(ExperimentalFoundationApi::class, ExperimentalMaterialApi::class) @Composable fun SearchInputScene(initial: String? = null) { diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/search/SearchScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/search/SearchScene.kt index 397612536..922079b88 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/search/SearchScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/search/SearchScene.kt @@ -122,7 +122,7 @@ fun SearchScene(keyword: String) { modifier = Modifier .clickable( onClick = { - navigator.searchInput(keyword) + navigator.goBack() }, indication = null, interactionSource = remember { MutableInteractionSource() } From 4389292eb2b2a07c790bcf9f2bdcfe2a0d92a9bb Mon Sep 17 00:00:00 2001 From: Tlaster Date: Thu, 8 Jul 2021 18:51:17 +0800 Subject: [PATCH 008/137] [WIP] initial support for custom column --- .../twidere/twiderex/model/AccountDetails.kt | 8 +--- .../twiderex/model/AccountPreferences.kt | 23 ++++++++++ .../twiderex/repository/AccountRepository.kt | 40 +++++++++++++---- .../com/twidere/twiderex/scenes/HomeScene.kt | 35 ++++++++++----- .../twiderex/scenes/settings/LayoutScene.kt | 43 +++++++++++++------ .../mastodon/MastodonSignInViewModel.kt | 19 ++++---- .../settings/AccountNotificationViewModel.kt | 14 ++++-- .../viewmodel/settings/LayoutViewModel.kt | 16 +++++++ .../twitter/TwitterSignInViewModel.kt | 19 ++++---- .../twiderex/worker/NotificationWorker.kt | 2 +- .../worker/dm/DirectMessageFetchWorker.kt | 2 +- 11 files changed, 153 insertions(+), 68 deletions(-) diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/AccountDetails.kt b/app/src/main/kotlin/com/twidere/twiderex/model/AccountDetails.kt index d8791bd20..0113dc31d 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/model/AccountDetails.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/model/AccountDetails.kt @@ -24,7 +24,6 @@ import android.accounts.Account import com.twidere.services.mastodon.MastodonService import com.twidere.services.microblog.MicroBlogService import com.twidere.services.twitter.TwitterService -import com.twidere.twiderex.model.adapter.AndroidAccountSerializer import com.twidere.twiderex.model.cred.BasicCredentials import com.twidere.twiderex.model.cred.Credentials import com.twidere.twiderex.model.cred.CredentialsType @@ -33,23 +32,18 @@ import com.twidere.twiderex.model.cred.OAuth2Credentials import com.twidere.twiderex.model.cred.OAuthCredentials import com.twidere.twiderex.model.ui.UiUser import com.twidere.twiderex.utils.fromJson -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -@Serializable data class AccountDetails( - @Serializable(with = AndroidAccountSerializer::class) val account: Account, val type: PlatformType, // Note that UserKey that being used in AccountDetails is idStr@domain, not screenName@domain val accountKey: MicroBlogKey, val credentials_type: CredentialsType, - @SerialName("credentials") var credentials_json: String, - @SerialName("extras") val extras_json: String, var user: AmUser, var lastActive: Long, + val preferences: AccountPreferences, ) { val credentials: Credentials get() = when (credentials_type) { diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/AccountPreferences.kt b/app/src/main/kotlin/com/twidere/twiderex/model/AccountPreferences.kt index c737432e0..a5e174136 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/model/AccountPreferences.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/model/AccountPreferences.kt @@ -26,7 +26,9 @@ import androidx.datastore.preferences.core.PreferenceDataStoreFactory import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStoreFile +import com.twidere.twiderex.scenes.home.HomeMenus import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob @@ -40,6 +42,16 @@ class AccountPreferences( get() = dataStore.data.map { preferences -> preferences[isNotificationEnabledKey] ?: true } + val homeMenuOrder + get() = dataStore.data.map { preferences -> + if (!preferences.contains(homeMenuOrderKey) || !preferences.contains(visibleHomeMenuKey)) { + HomeMenus.values().map { it to it.showDefault }.toMap() + } else { + val order = preferences[homeMenuOrderKey].orEmpty().split(",") + val visible = preferences[visibleHomeMenuKey].orEmpty().split(",") + order.map { HomeMenus.valueOf(it) }.map { it to visible.contains(it.name) }.toMap() + } + } suspend fun setIsNotificationEnabled(value: Boolean) { dataStore.edit { @@ -47,6 +59,17 @@ class AccountPreferences( } } + private val homeMenuOrderKey = stringPreferencesKey("homeMenuOrder") + private val visibleHomeMenuKey = stringPreferencesKey("visibleHomeMenu") + suspend fun setHomeMenuOrder( + data: Map, + ) { + dataStore.edit { + it[homeMenuOrderKey] = data.keys.map { it.name }.joinToString(",") + it[visibleHomeMenuKey] = data.filter { it.value }.map { it.key.name }.joinToString(",") + } + } + class Factory( private val context: Context, ) { diff --git a/app/src/main/kotlin/com/twidere/twiderex/repository/AccountRepository.kt b/app/src/main/kotlin/com/twidere/twiderex/repository/AccountRepository.kt index 3d9ff4a67..82bdb949f 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/repository/AccountRepository.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/repository/AccountRepository.kt @@ -26,6 +26,7 @@ import android.os.Build import androidx.lifecycle.MutableLiveData import com.twidere.twiderex.model.AccountDetails import com.twidere.twiderex.model.AccountPreferences +import com.twidere.twiderex.model.AmUser import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.PlatformType import com.twidere.twiderex.model.cred.CredentialsType @@ -67,12 +68,6 @@ class AccountRepository @Inject constructor( return manager.getAccountsByType(ACCOUNT_TYPE).toList() } - fun getAccountPreferences(accountKey: MicroBlogKey): AccountPreferences { - return preferencesCache.getOrPut(accountKey) { - accountPreferencesFactory.create(accountKey) - } - } - fun hasAccount(): Boolean { return getAccounts().isNotEmpty() } @@ -98,8 +93,28 @@ class AccountRepository @Inject constructor( .map { getAccountDetails(it) }.maxByOrNull { it.lastActive } } - fun addAccount(detail: AccountDetails) { - manager.addAccountExplicitly(detail.account, null, null) + fun addAccount( + account: Account, + type: PlatformType, + accountKey: MicroBlogKey, + credentials_type: CredentialsType, + credentials_json: String, + extras_json: String, + user: AmUser, + lastActive: Long, + ) { + manager.addAccountExplicitly(account, null, null) + val detail = AccountDetails( + account = account, + type = type, + accountKey = accountKey, + credentials_type = credentials_type, + credentials_json = credentials_json, + extras_json = extras_json, + user = user, + lastActive = lastActive, + preferences = getAccountPreferences(accountKey) + ) updateAccount(detail) setCurrentAccount(detail) accounts.postValue( @@ -126,10 +141,17 @@ class AccountRepository @Inject constructor( extras_json = manager.getUserData(account, ACCOUNT_USER_DATA_EXTRAS), user = manager.getUserData(account, ACCOUNT_USER_DATA_USER).fromJson(), lastActive = manager.getUserData(account, ACCOUNT_USER_DATA_LAST_ACTIVE)?.toLongOrNull() - ?: 0 + ?: 0, + preferences = getAccountPreferences(getAccountKey(account)) ) } + private fun getAccountPreferences(accountKey: MicroBlogKey): AccountPreferences { + return preferencesCache.getOrPut(accountKey) { + accountPreferencesFactory.create(accountKey) + } + } + private fun getAccountKey(account: Account): MicroBlogKey = MicroBlogKey.valueOf(manager.getUserData(account, ACCOUNT_USER_DATA_KEY)) diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/HomeScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/HomeScene.kt index c942e2354..84d56f7b3 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/HomeScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/HomeScene.kt @@ -58,6 +58,7 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowDropDown import androidx.compose.material.rememberScaffoldState import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.mutableStateOf @@ -68,9 +69,11 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.rotate import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import androidx.lifecycle.flowWithLifecycle import com.twidere.twiderex.R import com.twidere.twiderex.component.UserMetrics import com.twidere.twiderex.component.foundation.AppBar @@ -108,9 +111,13 @@ fun HomeScene() { val hideTab = LocalAppearancePreferences.current.hideTabBarWhenScroll val hideFab = LocalAppearancePreferences.current.hideFabWhenScroll val hideAppBar = LocalAppearancePreferences.current.hideAppBarWhenScroll - val menus = remember(account.type) { - HomeMenus.values() - .filter { it.supportedPlatformType.contains(account.type) && it.showDefault } + val menuOrder by account.preferences.homeMenuOrder.flowWithLifecycle(LocalLifecycleOwner.current.lifecycle) + .collectAsState( + initial = HomeMenus.values().map { it to it.showDefault }.toMap() + ) + val menus = remember(menuOrder) { + menuOrder.filter { it.value && it.key.supportedPlatformType.contains(account.type) } + .map { it.key } } val pagerState = rememberPagerState( maxPage = menus.lastIndex @@ -342,8 +349,8 @@ private fun HomeDrawer(scaffoldState: ScaffoldState) { Column { Spacer(modifier = Modifier.height(16.dp)) - val account = LocalActiveAccount.current - val currentUser = account?.toUi() + val account = LocalActiveAccount.current ?: return + val currentUser = account.toUi() val navController = LocalNavController.current DrawerUserHeader( currentUser, @@ -354,9 +361,7 @@ private fun HomeDrawer(scaffoldState: ScaffoldState) { Spacer(modifier = Modifier.height(16.dp)) - if (currentUser != null) { - UserMetrics(user = currentUser) - } + UserMetrics(user = currentUser) Spacer(modifier = Modifier.height(24.dp)) @@ -367,7 +372,7 @@ private fun HomeDrawer(scaffoldState: ScaffoldState) { ) { val activeAccountViewModel = LocalActiveAccountViewModel.current val accounts by activeAccountViewModel.allAccounts.observeAsState(initial = emptyList()) - val allAccounts = accounts.filter { it.accountKey != account?.accountKey } + val allAccounts = accounts.filter { it.accountKey != account.accountKey } androidx.compose.animation.AnimatedVisibility( visible = showAccounts, enter = fadeIn() + expandVertically(), @@ -431,10 +436,18 @@ private fun HomeDrawer(scaffoldState: ScaffoldState) { enter = fadeIn() + expandVertically(), exit = shrinkVertically() + fadeOut(), ) { + val menuOrder by account.preferences.homeMenuOrder.flowWithLifecycle( + LocalLifecycleOwner.current.lifecycle + ).collectAsState( + initial = HomeMenus.values().map { it to it.showDefault }.toMap() + ) LazyColumn { items( - HomeMenus.values() - .filter { it.supportedPlatformType.contains(account?.type) && !it.showDefault } + menuOrder.filter { + !it.value && it.key.supportedPlatformType.contains( + account.type + ) + }.map { it.key } ) { DrawerMenuItem( onClick = { diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/LayoutScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/LayoutScene.kt index 46ca670a8..236e6a897 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/LayoutScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/LayoutScene.kt @@ -33,8 +33,12 @@ import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Menu import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalLifecycleOwner +import androidx.lifecycle.flowWithLifecycle import com.twidere.twiderex.component.foundation.AppBar import com.twidere.twiderex.component.foundation.AppBarNavigationButton import com.twidere.twiderex.component.foundation.InAppNotificationScaffold @@ -61,19 +65,19 @@ fun LayoutScene() { ) } val user = viewModel.user - val menus = remember(account.type) { - HomeMenus.values().filter { it.supportedPlatformType.contains(account.type) }.groupBy { - it.showDefault + val menuOrder by account.preferences.homeMenuOrder.flowWithLifecycle(LocalLifecycleOwner.current.lifecycle) + .collectAsState( + initial = HomeMenus.values().map { it to it.showDefault }.toMap() + ) + val menus = + menuOrder.filter { it.key.supportedPlatformType.contains(account.type) }.toList().groupBy { + it.second }.map { listOf( - if (it.key) { - "Tabbar actions" - } else { - "Sidebar actions" - } - ) + it.value + it.key + ) + it.value.map { it.first } }.flatten() - } + val menuState = rememberUpdatedState(newValue = menus) TwidereScene { InAppNotificationScaffold( topBar = { @@ -113,13 +117,24 @@ fun LayoutScene() { ) ReorderableColumn( data = menus, - state = rememberReorderableColumnState { _, _ -> + state = rememberReorderableColumnState { oldIndex, newIndex -> + viewModel.updateHomeMenu( + oldIndex, + newIndex, + menuState.value, + ) } ) { when (it) { - is String -> { + is Boolean -> { ItemHeader { - Text(text = it) + Text( + text = if (it) { + "Tabbar actions" + } else { + "Sidebar actions" + } + ) } } is HomeMenus -> { diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/mastodon/MastodonSignInViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/mastodon/MastodonSignInViewModel.kt index 1ba30c641..c5f65db06 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/mastodon/MastodonSignInViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/mastodon/MastodonSignInViewModel.kt @@ -27,7 +27,6 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.twidere.services.mastodon.MastodonOAuthService import com.twidere.twiderex.db.mapper.toDbUser -import com.twidere.twiderex.model.AccountDetails import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.PlatformType import com.twidere.twiderex.model.cred.CredentialsType @@ -95,16 +94,14 @@ class MastodonSignInViewModel @AssistedInject constructor( } } else { repository.addAccount( - AccountDetails( - account = Account(displayKey.toString(), ACCOUNT_TYPE), - type = PlatformType.Mastodon, - accountKey = internalKey, - credentials_type = CredentialsType.OAuth2, - credentials_json = credentials_json, - extras_json = "", - user = user.toDbUser(accountKey = internalKey).toAmUser(), - lastActive = System.currentTimeMillis() - ) + account = Account(displayKey.toString(), ACCOUNT_TYPE), + type = PlatformType.Mastodon, + accountKey = internalKey, + credentials_type = CredentialsType.OAuth2, + credentials_json = credentials_json, + extras_json = "", + user = user.toDbUser(accountKey = internalKey).toAmUser(), + lastActive = System.currentTimeMillis() ) } finished.invoke(true) diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/settings/AccountNotificationViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/settings/AccountNotificationViewModel.kt index bb7a0f90e..379461fc2 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/settings/AccountNotificationViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/settings/AccountNotificationViewModel.kt @@ -22,7 +22,9 @@ package com.twidere.twiderex.viewmodel.settings import androidx.lifecycle.ViewModel import androidx.lifecycle.asLiveData +import androidx.lifecycle.liveData import androidx.lifecycle.map +import androidx.lifecycle.switchMap import androidx.lifecycle.viewModelScope import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.repository.AccountRepository @@ -49,14 +51,20 @@ class AccountNotificationViewModel @AssistedInject constructor( } val preferences by lazy { - accountRepository.getAccountPreferences(accountKey) + accountRepository.accounts.map { + it.firstOrNull { it.accountKey == accountKey } + }.map { + it?.preferences + } } val isNotificationEnabled by lazy { - preferences.isNotificationEnabled.asLiveData() + preferences.switchMap { + it?.isNotificationEnabled?.asLiveData() ?: liveData { emit(false) } + } } fun setIsNotificationEnabled(value: Boolean) = viewModelScope.launch { - preferences.setIsNotificationEnabled(value) + preferences.value?.setIsNotificationEnabled(value) } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/settings/LayoutViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/settings/LayoutViewModel.kt index ddea3769f..ad9b452ca 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/settings/LayoutViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/settings/LayoutViewModel.kt @@ -21,13 +21,29 @@ package com.twidere.twiderex.viewmodel.settings import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import com.twidere.twiderex.model.AccountDetails +import com.twidere.twiderex.scenes.home.HomeMenus import dagger.assisted.Assisted import dagger.assisted.AssistedInject +import kotlinx.coroutines.launch class LayoutViewModel @AssistedInject constructor( @Assisted private val account: AccountDetails, ) : ViewModel() { + fun updateHomeMenu(oldIndex: Int, newIndex: Int, menus: List) = viewModelScope.launch { + menus.toMutableList().let { list -> + list.add(newIndex, list.removeAt(oldIndex)) + list.indexOf(false).let { index -> + list.subList(0, index).filterIsInstance() + .map { it to true } + list.subList(index, list.size) + .filterIsInstance().map { it to false } + }.toMap().let { + account.preferences.setHomeMenuOrder(it) + } + } + } + @dagger.assisted.AssistedFactory interface AssistedFactory { fun create( diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/twitter/TwitterSignInViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/twitter/TwitterSignInViewModel.kt index 613414922..8aea56b6b 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/twitter/TwitterSignInViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/twitter/TwitterSignInViewModel.kt @@ -29,7 +29,6 @@ import com.twidere.services.twitter.TwitterOAuthService import com.twidere.services.twitter.TwitterService import com.twidere.twiderex.BuildConfig import com.twidere.twiderex.db.mapper.toDbUser -import com.twidere.twiderex.model.AccountDetails import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.PlatformType import com.twidere.twiderex.model.cred.CredentialsType @@ -123,16 +122,14 @@ class TwitterSignInViewModel @AssistedInject constructor( } } else { repository.addAccount( - AccountDetails( - account = Account(displayKey.toString(), ACCOUNT_TYPE), - type = PlatformType.Twitter, - accountKey = internalKey, - credentials_type = CredentialsType.OAuth, - credentials_json = credentials_json, - extras_json = "", - user = user.toDbUser().toAmUser(), - lastActive = System.currentTimeMillis() - ) + account = Account(displayKey.toString(), ACCOUNT_TYPE), + type = PlatformType.Twitter, + accountKey = internalKey, + credentials_type = CredentialsType.OAuth, + credentials_json = credentials_json, + extras_json = "", + user = user.toDbUser().toAmUser(), + lastActive = System.currentTimeMillis() ) } return true diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/NotificationWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/NotificationWorker.kt index b11a5184c..6170869c2 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/NotificationWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/NotificationWorker.kt @@ -70,7 +70,7 @@ class NotificationWorker @AssistedInject constructor( } else { accountRepository.getAccounts().map { accountRepository.getAccountDetails(it) } .filter { - accountRepository.getAccountPreferences(it.accountKey).isNotificationEnabled.first() + it.preferences.isNotificationEnabled.first() } .map { account -> launch { diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageFetchWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageFetchWorker.kt index e53a4333a..fb3d5396e 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageFetchWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageFetchWorker.kt @@ -64,7 +64,7 @@ class DirectMessageFetchWorker @AssistedInject constructor( override suspend fun doWork(): Result { return try { accountRepository.activeAccount.value?.takeIf { - accountRepository.getAccountPreferences(it.accountKey).isNotificationEnabled.first() + it.preferences.isNotificationEnabled.first() }?.let { account -> val result = repository.checkNewMessages( accountKey = account.accountKey, From 1a3edc5b9e7dedfd2821ceed2685c341053df6be Mon Sep 17 00:00:00 2001 From: Tlaster Date: Fri, 9 Jul 2021 18:54:33 +0800 Subject: [PATCH 009/137] fix column order issue --- .../twiderex/model/AccountPreferences.kt | 19 +++++++++++++------ .../com/twidere/twiderex/scenes/HomeScene.kt | 12 ++++++------ .../twiderex/scenes/settings/LayoutScene.kt | 4 ++-- .../viewmodel/settings/LayoutViewModel.kt | 2 +- 4 files changed, 22 insertions(+), 15 deletions(-) diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/AccountPreferences.kt b/app/src/main/kotlin/com/twidere/twiderex/model/AccountPreferences.kt index a5e174136..0e11b049a 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/model/AccountPreferences.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/model/AccountPreferences.kt @@ -45,11 +45,16 @@ class AccountPreferences( val homeMenuOrder get() = dataStore.data.map { preferences -> if (!preferences.contains(homeMenuOrderKey) || !preferences.contains(visibleHomeMenuKey)) { - HomeMenus.values().map { it to it.showDefault }.toMap() + HomeMenus.values().map { it to it.showDefault } } else { - val order = preferences[homeMenuOrderKey].orEmpty().split(",") + val order = preferences[homeMenuOrderKey].orEmpty() + .split(",") + .withIndex() + .associate { HomeMenus.valueOf(it.value) to it.index } val visible = preferences[visibleHomeMenuKey].orEmpty().split(",") - order.map { HomeMenus.valueOf(it) }.map { it to visible.contains(it.name) }.toMap() + HomeMenus.values().sortedBy { + order[it] + }.map { it to visible.contains(it.name) } } } @@ -62,11 +67,13 @@ class AccountPreferences( private val homeMenuOrderKey = stringPreferencesKey("homeMenuOrder") private val visibleHomeMenuKey = stringPreferencesKey("visibleHomeMenu") suspend fun setHomeMenuOrder( - data: Map, + data: List>, ) { dataStore.edit { - it[homeMenuOrderKey] = data.keys.map { it.name }.joinToString(",") - it[visibleHomeMenuKey] = data.filter { it.value }.map { it.key.name }.joinToString(",") + it[visibleHomeMenuKey] = data.filter { it.second }.joinToString(",") { it.first.name } + } + dataStore.edit { + it[homeMenuOrderKey] = data.joinToString(",") { it.first.name } } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/HomeScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/HomeScene.kt index 84d56f7b3..4fa79b601 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/HomeScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/HomeScene.kt @@ -113,11 +113,11 @@ fun HomeScene() { val hideAppBar = LocalAppearancePreferences.current.hideAppBarWhenScroll val menuOrder by account.preferences.homeMenuOrder.flowWithLifecycle(LocalLifecycleOwner.current.lifecycle) .collectAsState( - initial = HomeMenus.values().map { it to it.showDefault }.toMap() + initial = HomeMenus.values().map { it to it.showDefault } ) val menus = remember(menuOrder) { - menuOrder.filter { it.value && it.key.supportedPlatformType.contains(account.type) } - .map { it.key } + menuOrder.filter { it.second && it.first.supportedPlatformType.contains(account.type) } + .map { it.first } } val pagerState = rememberPagerState( maxPage = menus.lastIndex @@ -439,15 +439,15 @@ private fun HomeDrawer(scaffoldState: ScaffoldState) { val menuOrder by account.preferences.homeMenuOrder.flowWithLifecycle( LocalLifecycleOwner.current.lifecycle ).collectAsState( - initial = HomeMenus.values().map { it to it.showDefault }.toMap() + initial = HomeMenus.values().map { it to it.showDefault } ) LazyColumn { items( menuOrder.filter { - !it.value && it.key.supportedPlatformType.contains( + !it.second && it.first.supportedPlatformType.contains( account.type ) - }.map { it.key } + }.map { it.first } ) { DrawerMenuItem( onClick = { diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/LayoutScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/LayoutScene.kt index 236e6a897..f05945fa0 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/LayoutScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/LayoutScene.kt @@ -67,10 +67,10 @@ fun LayoutScene() { val user = viewModel.user val menuOrder by account.preferences.homeMenuOrder.flowWithLifecycle(LocalLifecycleOwner.current.lifecycle) .collectAsState( - initial = HomeMenus.values().map { it to it.showDefault }.toMap() + initial = HomeMenus.values().map { it to it.showDefault } ) val menus = - menuOrder.filter { it.key.supportedPlatformType.contains(account.type) }.toList().groupBy { + menuOrder.filter { it.first.supportedPlatformType.contains(account.type) }.groupBy { it.second }.map { listOf( diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/settings/LayoutViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/settings/LayoutViewModel.kt index ad9b452ca..5a2f17edd 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/settings/LayoutViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/settings/LayoutViewModel.kt @@ -38,7 +38,7 @@ class LayoutViewModel @AssistedInject constructor( list.subList(0, index).filterIsInstance() .map { it to true } + list.subList(index, list.size) .filterIsInstance().map { it to false } - }.toMap().let { + }.let { account.preferences.setHomeMenuOrder(it) } } From 7e5bfe140fedd794fa3c9a0bafa82d28201a4e1d Mon Sep 17 00:00:00 2001 From: Tlaster Date: Mon, 12 Jul 2021 18:31:17 +0800 Subject: [PATCH 010/137] fix layout edge case --- .../com/twidere/twiderex/scenes/HomeScene.kt | 114 ++++++++++-------- .../twiderex/scenes/settings/LayoutScene.kt | 14 ++- .../viewmodel/settings/LayoutViewModel.kt | 4 +- 3 files changed, 83 insertions(+), 49 deletions(-) diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/HomeScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/HomeScene.kt index 4fa79b601..c449df84c 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/HomeScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/HomeScene.kt @@ -80,6 +80,7 @@ import com.twidere.twiderex.component.foundation.AppBar import com.twidere.twiderex.component.foundation.AppBarDefaults import com.twidere.twiderex.component.foundation.ApplyNotification import com.twidere.twiderex.component.foundation.IconTabsComponent +import com.twidere.twiderex.component.foundation.InAppNotificationScaffold import com.twidere.twiderex.component.foundation.NestedScrollScaffold import com.twidere.twiderex.component.foundation.Pager import com.twidere.twiderex.component.foundation.PagerState @@ -101,6 +102,7 @@ import com.twidere.twiderex.ui.TwidereScene import com.twidere.twiderex.ui.mediumEmphasisContentContentColor import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +import kotlin.math.max @OptIn(ExperimentalAnimationApi::class) @Composable @@ -120,7 +122,7 @@ fun HomeScene() { .map { it.first } } val pagerState = rememberPagerState( - maxPage = menus.lastIndex + maxPage = max(menus.lastIndex, 0) ) val scaffoldState = rememberScaffoldState() if (scaffoldState.drawerState.isOpen) { @@ -140,58 +142,76 @@ fun HomeScene() { } }, ) { - NestedScrollScaffold( - scaffoldState = scaffoldState, - enableBottomBarNestedScroll = hideTab, - bottomBar = { - if (tabPosition == AppearancePreferences.TabPosition.Bottom) { - HomeBottomNavigation( - items = menus, - selectedItem = pagerState.currentPage, - ) { - if (pagerState.currentPage == it) { - scope.launch { - menus[it].item.lazyListController.scrollToTop() + if (!menus.any()) { + InAppNotificationScaffold( + scaffoldState = scaffoldState, + topBar = { + AppBar( + backgroundColor = MaterialTheme.colors.surface.withElevation(), + navigationIcon = { + MenuAvatar(scaffoldState) + }, + ) + }, + drawerContent = { + HomeDrawer(scaffoldState = scaffoldState) + } + ) { + } + } else { + NestedScrollScaffold( + scaffoldState = scaffoldState, + enableBottomBarNestedScroll = hideTab, + bottomBar = { + if (tabPosition == AppearancePreferences.TabPosition.Bottom) { + HomeBottomNavigation( + items = menus, + selectedItem = pagerState.currentPage, + ) { + if (pagerState.currentPage == it) { + scope.launch { + menus[it].item.lazyListController.scrollToTop() + } } - } - scope.launch { - pagerState.selectPage { - pagerState.currentPage = it + scope.launch { + pagerState.selectPage { + pagerState.currentPage = it + } } } } - } - }, - drawerContent = { - HomeDrawer(scaffoldState) - }, - floatingActionButton = { - Crossfade(pagerState.currentPage) { - menus[it].item.Fab() - } - }, - floatingActionButtonPosition = menus[pagerState.currentPage].item.floatingActionButtonPosition, - enableFloatingActionButtonNestedScroll = hideFab, - topBar = { - HomeAppBar( - tabPosition = tabPosition, - menus = menus, - pagerState = pagerState, - scaffoldState = scaffoldState, - scope = scope, - ) - }, - enableTopBarNestedScroll = hideAppBar - ) { - Box( - modifier = Modifier - .fillMaxSize() - .padding(it) + }, + drawerContent = { + HomeDrawer(scaffoldState) + }, + floatingActionButton = { + Crossfade(pagerState.currentPage) { + menus[it].item.Fab() + } + }, + floatingActionButtonPosition = menus[pagerState.currentPage].item.floatingActionButtonPosition, + enableFloatingActionButtonNestedScroll = hideFab, + topBar = { + HomeAppBar( + tabPosition = tabPosition, + menus = menus, + pagerState = pagerState, + scaffoldState = scaffoldState, + scope = scope, + ) + }, + enableTopBarNestedScroll = hideAppBar ) { - Pager( - state = pagerState, + Box( + modifier = Modifier + .fillMaxSize() + .padding(it) ) { - menus[page].item.Content() + Pager( + state = pagerState, + ) { + menus[page].item.Content() + } } } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/LayoutScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/LayoutScene.kt index f05945fa0..6ee5e4f06 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/LayoutScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/LayoutScene.kt @@ -76,7 +76,19 @@ fun LayoutScene() { listOf( it.key ) + it.value.map { it.first } - }.flatten() + }.flatten().let { + if (it.firstOrNull() != true) { + listOf(true) + it + } else { + it + } + }.let { + if (!it.contains(false)) { + it + false + } else { + it + } + } val menuState = rememberUpdatedState(newValue = menus) TwidereScene { InAppNotificationScaffold( diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/settings/LayoutViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/settings/LayoutViewModel.kt index 5a2f17edd..71cd58e6d 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/settings/LayoutViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/settings/LayoutViewModel.kt @@ -27,6 +27,7 @@ import com.twidere.twiderex.scenes.home.HomeMenus import dagger.assisted.Assisted import dagger.assisted.AssistedInject import kotlinx.coroutines.launch +import kotlin.math.min class LayoutViewModel @AssistedInject constructor( @Assisted private val account: AccountDetails, @@ -34,7 +35,8 @@ class LayoutViewModel @AssistedInject constructor( fun updateHomeMenu(oldIndex: Int, newIndex: Int, menus: List) = viewModelScope.launch { menus.toMutableList().let { list -> list.add(newIndex, list.removeAt(oldIndex)) - list.indexOf(false).let { index -> + list.remove(true) + list.indexOf(false).let { min(it, 5) }.let { index -> list.subList(0, index).filterIsInstance() .map { it to true } + list.subList(index, list.size) .filterIsInstance().map { it to false } From 71dbcc8be16cd19ff7554e5a4b67bdb33490e7e8 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Tue, 13 Jul 2021 13:59:56 +0800 Subject: [PATCH 011/137] add HapticFeedback for ReorderableColumn --- .../twiderex/component/foundation/ReorderableColumn.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/ReorderableColumn.kt b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/ReorderableColumn.kt index a04cf9018..48c62ad10 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/ReorderableColumn.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/ReorderableColumn.kt @@ -20,6 +20,7 @@ */ package com.twidere.twiderex.component.foundation +import android.view.HapticFeedbackConstants import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress import androidx.compose.foundation.layout.Box import androidx.compose.runtime.Composable @@ -31,6 +32,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.Layout +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.unit.IntSize import androidx.compose.ui.zIndex import kotlin.math.roundToInt @@ -134,6 +136,7 @@ fun ReorderableColumn( dragingContent: @Composable ((T) -> Unit)? = null, itemContent: @Composable (T) -> Unit, ) { + val view = LocalView.current Layout( modifier = modifier, content = { @@ -149,6 +152,7 @@ fun ReorderableColumn( state.drop() }, onDragStart = { + view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) state.start(index) }, onDrag = { _, dragAmount -> From 7aa99f756d33e2342f181901e6bfbd1f015300c2 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Tue, 13 Jul 2021 14:00:14 +0800 Subject: [PATCH 012/137] add card for dragging item --- .../twiderex/scenes/settings/LayoutScene.kt | 75 +++++++++++-------- 1 file changed, 45 insertions(+), 30 deletions(-) diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/LayoutScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/LayoutScene.kt index 6ee5e4f06..ff57ad7a5 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/LayoutScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/LayoutScene.kt @@ -24,6 +24,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll +import androidx.compose.material.Card import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.Icon import androidx.compose.material.ListItem @@ -33,6 +34,7 @@ import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Menu import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberUpdatedState @@ -88,7 +90,7 @@ fun LayoutScene() { } else { it } - } + } as List val menuState = rememberUpdatedState(newValue = menus) TwidereScene { InAppNotificationScaffold( @@ -135,40 +137,53 @@ fun LayoutScene() { newIndex, menuState.value, ) - } - ) { - when (it) { - is Boolean -> { - ItemHeader { - Text( - text = if (it) { - "Tabbar actions" - } else { - "Sidebar actions" - } - ) - } + }, + dragingContent = { + LaunchedEffect(Unit) { } - is HomeMenus -> { - ListItem( - text = { - Text(text = it.item.name()) - }, - icon = { - Icon( - it.item.icon(), - contentDescription = null, - tint = MaterialTheme.colors.primary, - ) - }, - trailing = { - Icon(Icons.Default.Menu, contentDescription = null) - } - ) + Card { + LayoutItemContent(it = it) } } + ) { + LayoutItemContent(it) } } } } } + +@OptIn(ExperimentalMaterialApi::class) +@Composable +private fun LayoutItemContent(it: Any) { + when (it) { + is Boolean -> { + ItemHeader { + Text( + text = if (it) { + "Tabbar actions" + } else { + "Sidebar actions" + } + ) + } + } + is HomeMenus -> { + ListItem( + text = { + Text(text = it.item.name()) + }, + icon = { + Icon( + it.item.icon(), + contentDescription = null, + tint = MaterialTheme.colors.primary, + ) + }, + trailing = { + Icon(Icons.Default.Menu, contentDescription = null) + } + ) + } + } +} From a4cb59f329dc1081c3ca84bdccaeaabc704657e2 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Tue, 13 Jul 2021 14:00:37 +0800 Subject: [PATCH 013/137] update pager to resolve page change issue --- .../twiderex/component/foundation/Pager.kt | 82 ++++++++++--------- .../com/twidere/twiderex/scenes/HomeScene.kt | 3 +- .../scenes/home/MastodonNotificationItem.kt | 2 +- 3 files changed, 44 insertions(+), 43 deletions(-) diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/Pager.kt b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/Pager.kt index 68b76d15b..963655475 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/Pager.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/Pager.kt @@ -20,6 +20,7 @@ */ package com.twidere.twiderex.component.foundation +import androidx.annotation.IntRange import androidx.compose.animation.core.Animatable import androidx.compose.foundation.gestures.awaitFirstDown import androidx.compose.foundation.gestures.awaitHorizontalTouchSlopOrCancellation @@ -38,7 +39,6 @@ import androidx.compose.runtime.saveable.Saver import androidx.compose.runtime.saveable.listSaver import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue -import androidx.compose.runtime.structuralEqualityPolicy import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset @@ -65,62 +65,67 @@ import kotlin.math.withSign @Composable fun rememberPagerState( - currentPage: Int = 0, - minPage: Int = 0, - maxPage: Int = 0, + @IntRange(from = 0) pageCount: Int, + @IntRange(from = 0) initialPage: Int = 0, ): PagerState { return rememberSaveable( - currentPage, - minPage, - maxPage, saver = PagerState.Saver(), ) { - PagerState(currentPage, minPage, maxPage) + PagerState(pageCount = pageCount, currentPage = initialPage) + }.apply { + this.pageCount = pageCount } } @Stable class PagerState( - currentPage: Int = 0, - minPage: Int = 0, - maxPage: Int = 0, + @IntRange(from = 0) pageCount: Int, + @IntRange(from = 0) currentPage: Int = 0, ) { private val velocityTracker = VelocityTracker() + private var _pageCount by mutableStateOf(pageCount) + private var _currentPage by mutableStateOf(currentPage) companion object { fun Saver(): Saver = listSaver( - save = { listOf(it.currentPage, it.minPage, it.maxPage) }, + save = { listOf(it.pageCount, it.currentPage) }, restore = { PagerState( - currentPage = it[0], - minPage = it[1], - maxPage = it[2], + pageCount = it[0], + currentPage = it[1], ) } ) } - private var _minPage by mutableStateOf(minPage) - var minPage: Int - get() = _minPage - set(value) { - _minPage = value.coerceAtMost(_maxPage) - _currentPage = _currentPage.coerceIn(_minPage, _maxPage) + internal inline val firstPageIndex: Int + get() = 0 + + internal inline val lastPageIndex: Int + get() = (pageCount - 1).coerceAtLeast(0) + + @get:IntRange(from = 0) + var pageCount: Int + get() = _pageCount + set(@IntRange(from = 0) value) { + require(value >= 0) { "pageCount must be >= 0" } + _pageCount = value + currentPage = currentPage.coerceIn(firstPageIndex, lastPageIndex) + // updateLayoutPages(currentPage) } - private var _maxPage by mutableStateOf(maxPage, structuralEqualityPolicy()) - var maxPage: Int - get() = _maxPage - set(value) { - _maxPage = value.coerceAtLeast(_minPage) - _currentPage = _currentPage.coerceIn(_minPage, maxPage) + private fun Int.floorMod(other: Int): Int { + return when (other) { + 0 -> this + else -> this - this.floorDiv(other) * other } + } - private var _currentPage by mutableStateOf(currentPage.coerceIn(minPage, maxPage)) + @get:IntRange(from = 0) var currentPage: Int get() = _currentPage set(value) { - _currentPage = value.coerceIn(minPage, maxPage) + _currentPage = value.floorMod(pageCount) } enum class SelectionState { Selected, Undecided } @@ -147,14 +152,14 @@ class PagerState( get() = _currentPageOffset.value suspend fun snapToOffset(offset: Float) { - val max = if (currentPage == minPage) 0f else 1f - val min = if (currentPage == maxPage) 0f else -1f + val max = if (currentPage == firstPageIndex) 0f else 1f + val min = if (currentPage == lastPageIndex) 0f else -1f _currentPageOffset.snapTo(offset.coerceIn(min, max)) } suspend fun fling(velocity: Float) { - if (velocity < 0 && currentPage == maxPage) return - if (velocity > 0 && currentPage == minPage) return + if (velocity < 0 && currentPage == lastPageIndex) return + if (velocity > 0 && currentPage == firstPageIndex) return val currentOffset = _currentPageOffset.value when { currentOffset.sign == velocity.sign && @@ -172,9 +177,6 @@ class PagerState( } } - override fun toString(): String = "PagerState{minPage=$minPage, maxPage=$maxPage, " + - "currentPage=$currentPage, currentPageOffset=$currentPageOffset}" - fun addPosition(uptimeMillis: Long, position: Offset) { velocityTracker.addPosition(timeMillis = uptimeMillis, position = position) } @@ -205,8 +207,8 @@ fun Pager( var pageSize by remember { mutableStateOf(0) } Layout( content = { - val minPage = (state.currentPage - offscreenLimit).coerceAtLeast(state.minPage) - val maxPage = (state.currentPage + offscreenLimit).coerceAtMost(state.maxPage) + val minPage = (state.currentPage - offscreenLimit).coerceAtLeast(state.firstPageIndex) + val maxPage = (state.currentPage + offscreenLimit).coerceAtMost(state.lastPageIndex) for (page in minPage..maxPage) { val pageData = PageData(page) @@ -227,9 +229,9 @@ fun Pager( selectionState = PagerState.SelectionState.Undecided val pos = pageSize * currentPageOffset val max = - if (currentPage == minPage) 0 else pageSize * offscreenLimit + if (currentPage == firstPageIndex) 0 else pageSize * offscreenLimit val min = - if (currentPage == maxPage) 0 else -pageSize * offscreenLimit + if (currentPage == lastPageIndex) 0 else -pageSize * offscreenLimit val newPos = (pos + dragAmount).coerceIn(min.toFloat(), max.toFloat()) if (newPos != 0f) { diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/HomeScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/HomeScene.kt index c449df84c..caa45fe94 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/HomeScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/HomeScene.kt @@ -102,7 +102,6 @@ import com.twidere.twiderex.ui.TwidereScene import com.twidere.twiderex.ui.mediumEmphasisContentContentColor import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch -import kotlin.math.max @OptIn(ExperimentalAnimationApi::class) @Composable @@ -122,7 +121,7 @@ fun HomeScene() { .map { it.first } } val pagerState = rememberPagerState( - maxPage = max(menus.lastIndex, 0) + pageCount = menus.size, ) val scaffoldState = rememberScaffoldState() if (scaffoldState.drawerState.isOpen) { diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/MastodonNotificationItem.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/MastodonNotificationItem.kt index 35580ca87..c3429dd8e 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/MastodonNotificationItem.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/MastodonNotificationItem.kt @@ -103,7 +103,7 @@ fun MastodonNotificationSceneContent( MentionItem(), ) } - val pagerState = rememberPagerState(maxPage = tabs.lastIndex) + val pagerState = rememberPagerState(pageCount = tabs.size) LaunchedEffect(pagerState.currentPage) { // FIXME: 2021/5/17 A little bit dirty setLazyListController?.invoke(tabs[pagerState.currentPage].lazyListController) From 394375a6b8089c567716a96dc2ae9a368b321306 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Tue, 13 Jul 2021 14:53:05 +0800 Subject: [PATCH 014/137] remove crossfade effect for home fab --- app/src/main/kotlin/com/twidere/twiderex/scenes/HomeScene.kt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/HomeScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/HomeScene.kt index caa45fe94..1502f9795 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/HomeScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/HomeScene.kt @@ -22,7 +22,6 @@ package com.twidere.twiderex.scenes import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.Crossfade import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.core.animateDp import androidx.compose.animation.core.animateFloat @@ -184,9 +183,7 @@ fun HomeScene() { HomeDrawer(scaffoldState) }, floatingActionButton = { - Crossfade(pagerState.currentPage) { - menus[it].item.Fab() - } + menus[pagerState.currentPage].item.Fab() }, floatingActionButtonPosition = menus[pagerState.currentPage].item.floatingActionButtonPosition, enableFloatingActionButtonNestedScroll = hideFab, From c417b67bbc875653c7e040293d18f7376b5b1417 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Tue, 13 Jul 2021 16:42:31 +0800 Subject: [PATCH 015/137] add public timeline api --- .../services/mastodon/MastodonService.kt | 27 +++++++++++++++++++ .../mastodon/api/TimelineResources.kt | 11 ++++++++ 2 files changed, 38 insertions(+) diff --git a/services/src/main/java/com/twidere/services/mastodon/MastodonService.kt b/services/src/main/java/com/twidere/services/mastodon/MastodonService.kt index 8ec0f9086..6fb8f4ec5 100644 --- a/services/src/main/java/com/twidere/services/mastodon/MastodonService.kt +++ b/services/src/main/java/com/twidere/services/mastodon/MastodonService.kt @@ -407,4 +407,31 @@ class MastodonService( } override suspend fun trends(locationId: String, limit: Int?) = resources.trends(limit) + + suspend fun localTimeline( + count: Int, + since_id: String?, + max_id: String? + ): List { + return resources.publicTimeline( + since_id = since_id, + max_id = max_id, + limit = count, + local = true + ) + } + + suspend fun federatedTimeline( + count: Int, + since_id: String?, + max_id: String? + ): List { + return resources.publicTimeline( + since_id = since_id, + max_id = max_id, + limit = count, + local = false, + remote = false, + ) + } } diff --git a/services/src/main/java/com/twidere/services/mastodon/api/TimelineResources.kt b/services/src/main/java/com/twidere/services/mastodon/api/TimelineResources.kt index 8251677af..a18b95149 100644 --- a/services/src/main/java/com/twidere/services/mastodon/api/TimelineResources.kt +++ b/services/src/main/java/com/twidere/services/mastodon/api/TimelineResources.kt @@ -39,6 +39,17 @@ interface TimelineResources { @Query("local") local: Boolean? = null, ): List + @GET("/api/v1/timelines/public") + suspend fun publicTimeline( + @Query("max_id") max_id: String? = null, + @Query("since_id") since_id: String? = null, + @Query("min_id") min_id: String? = null, + @Query("limit") limit: Int? = null, + @Query("local") local: Boolean? = null, + @Query("remote") remote: Boolean? = null, + @Query("only_media") only_media: Boolean? = null, + ): List + @GET("/api/v1/accounts/{id}/statuses") suspend fun userTimeline( @Path("id") user_id: String, From 4044ef6124bea40134ae053da13d3347bac7566b Mon Sep 17 00:00:00 2001 From: Tlaster Date: Tue, 13 Jul 2021 17:32:01 +0800 Subject: [PATCH 016/137] update design --- .../twiderex/scenes/settings/LayoutScene.kt | 70 +++++++++++++++---- .../twiderex/scenes/settings/SettingsScene.kt | 15 ++-- .../viewmodel/settings/LayoutViewModel.kt | 28 +++++++- app/src/main/res/drawable/ic_add_colored.xml | 23 ++++++ .../main/res/drawable/ic_delete_colored.xml | 16 +++++ 5 files changed, 129 insertions(+), 23 deletions(-) create mode 100644 app/src/main/res/drawable/ic_add_colored.xml create mode 100644 app/src/main/res/drawable/ic_delete_colored.xml diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/LayoutScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/LayoutScene.kt index ff57ad7a5..bc285e436 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/LayoutScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/LayoutScene.kt @@ -20,27 +20,34 @@ */ package com.twidere.twiderex.scenes.settings +import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.Card +import androidx.compose.material.ContentAlpha import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.Icon +import androidx.compose.material.IconButton import androidx.compose.material.ListItem +import androidx.compose.material.LocalContentAlpha import androidx.compose.material.MaterialTheme import androidx.compose.material.Surface import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Menu import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalLifecycleOwner +import androidx.compose.ui.res.painterResource import androidx.lifecycle.flowWithLifecycle +import com.twidere.twiderex.R import com.twidere.twiderex.component.foundation.AppBar import com.twidere.twiderex.component.foundation.AppBarNavigationButton import com.twidere.twiderex.component.foundation.InAppNotificationScaffold @@ -139,14 +146,12 @@ fun LayoutScene() { ) }, dragingContent = { - LaunchedEffect(Unit) { - } Card { - LayoutItemContent(it = it) + LayoutItemContent(it = it, viewModel = viewModel, menus = menus) } } ) { - LayoutItemContent(it) + LayoutItemContent(it = it, viewModel = viewModel, menus = menus) } } } @@ -155,7 +160,14 @@ fun LayoutScene() { @OptIn(ExperimentalMaterialApi::class) @Composable -private fun LayoutItemContent(it: Any) { +private fun LayoutItemContent( + it: Any, + viewModel: LayoutViewModel, + menus: List, +) { + val current = menus.indexOf(it) + val falseIndex = menus.indexOf(false) + val visible = current < falseIndex when (it) { is Boolean -> { ItemHeader { @@ -174,14 +186,48 @@ private fun LayoutItemContent(it: Any) { Text(text = it.item.name()) }, icon = { - Icon( - it.item.icon(), - contentDescription = null, - tint = MaterialTheme.colors.primary, - ) + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + IconButton( + onClick = { + if (visible) { + viewModel.removeMenu( + current, + menus + ) + } else { + viewModel.addMenu( + current, + menus + ) + } + } + ) { + Image( + painter = painterResource( + id = if (visible) { + R.drawable.ic_delete_colored + } else { + R.drawable.ic_add_colored + } + ), + contentDescription = null, + ) + } + Icon( + it.item.icon(), + contentDescription = null, + tint = MaterialTheme.colors.primary, + ) + } }, trailing = { - Icon(Icons.Default.Menu, contentDescription = null) + CompositionLocalProvider( + LocalContentAlpha provides ContentAlpha.medium + ) { + Icon(Icons.Default.Menu, contentDescription = null) + } } ) } diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/SettingsScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/SettingsScene.kt index 40b96910b..1020a7080 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/SettingsScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/SettingsScene.kt @@ -64,6 +64,11 @@ fun SettingsScene() { painterResource(id = R.drawable.ic_template), route = Route.Settings.Display, ), + SettingItem( + "Layout", + painterResource(id = R.drawable.ic_layout_sidebar), + route = Route.Settings.Layout, + ), SettingItem( stringResource(id = R.string.scene_settings_notification_title), painterResource(id = R.drawable.ic_settings_notification), @@ -79,16 +84,6 @@ fun SettingsScene() { painterResource(id = R.drawable.ic_triangle_square_circle), route = Route.Settings.Misc, ), - SettingItem( - "Layout", - painterResource(id = R.drawable.ic_layout_sidebar), - route = Route.Settings.Layout, - ), -// SettingItem( -// "Web Browser", -// painterResource(id = R.drawable.ic_browser), -// route = "", -// ), ), stringResource(id = R.string.scene_settings_section_header_about) to listOf( SettingItem( diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/settings/LayoutViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/settings/LayoutViewModel.kt index 71cd58e6d..c97a52210 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/settings/LayoutViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/settings/LayoutViewModel.kt @@ -29,6 +29,8 @@ import dagger.assisted.AssistedInject import kotlinx.coroutines.launch import kotlin.math.min +private const val MaxMenuCount = 5 + class LayoutViewModel @AssistedInject constructor( @Assisted private val account: AccountDetails, ) : ViewModel() { @@ -36,7 +38,7 @@ class LayoutViewModel @AssistedInject constructor( menus.toMutableList().let { list -> list.add(newIndex, list.removeAt(oldIndex)) list.remove(true) - list.indexOf(false).let { min(it, 5) }.let { index -> + list.indexOf(false).let { min(it, MaxMenuCount) }.let { index -> list.subList(0, index).filterIsInstance() .map { it to true } + list.subList(index, list.size) .filterIsInstance().map { it to false } @@ -46,6 +48,30 @@ class LayoutViewModel @AssistedInject constructor( } } + fun removeMenu(current: Int, menus: List) { + val newIndex = menus.indexOf(false) + updateHomeMenu( + oldIndex = current, + newIndex = newIndex, + menus = menus, + ) + } + + fun addMenu(current: Int, menus: List) { + val newIndex = menus.indexOf(false).let { + if (it == MaxMenuCount + 1) { + it - 1 + } else { + it + } + } + updateHomeMenu( + oldIndex = current, + newIndex = newIndex, + menus = menus, + ) + } + @dagger.assisted.AssistedFactory interface AssistedFactory { fun create( diff --git a/app/src/main/res/drawable/ic_add_colored.xml b/app/src/main/res/drawable/ic_add_colored.xml new file mode 100644 index 000000000..00de90654 --- /dev/null +++ b/app/src/main/res/drawable/ic_add_colored.xml @@ -0,0 +1,23 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_delete_colored.xml b/app/src/main/res/drawable/ic_delete_colored.xml new file mode 100644 index 000000000..69f990a9a --- /dev/null +++ b/app/src/main/res/drawable/ic_delete_colored.xml @@ -0,0 +1,16 @@ + + + + From fb4dcfb1631259b71cd7574586fd090361dd78a2 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Tue, 13 Jul 2021 17:37:27 +0800 Subject: [PATCH 017/137] fix crashing when switching account --- .../com/twidere/twiderex/scenes/dm/DMConversationListScene.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/dm/DMConversationListScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/dm/DMConversationListScene.kt index d46f4ea63..450a0e5f0 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/dm/DMConversationListScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/dm/DMConversationListScene.kt @@ -30,6 +30,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.paging.LoadState import androidx.paging.compose.collectAsLazyPagingItems +import com.twidere.services.microblog.DirectMessageService import com.twidere.twiderex.R import com.twidere.twiderex.component.foundation.AppBar import com.twidere.twiderex.component.foundation.AppBarNavigationButton @@ -89,6 +90,7 @@ fun DMConversationListSceneContent( lazyListController: LazyListController? = null ) { val account = LocalActiveAccount.current ?: return + if (account.service !is DirectMessageService) return val navController = LocalNavController.current val viewModel = assistedViewModel( From fbb21e529516b65a4ee56e50c037e31529a07b10 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Tue, 13 Jul 2021 17:59:32 +0800 Subject: [PATCH 018/137] add empty column content for home scene --- .../com/twidere/twiderex/scenes/HomeScene.kt | 62 ++++++++++++++----- app/src/main/res/drawable/ic_empty_column.xml | 22 +++++++ 2 files changed, 69 insertions(+), 15 deletions(-) create mode 100644 app/src/main/res/drawable/ic_empty_column.xml diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/HomeScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/HomeScene.kt index 1502f9795..daeab2545 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/HomeScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/HomeScene.kt @@ -31,6 +31,7 @@ import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues @@ -43,11 +44,13 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.BottomNavigation import androidx.compose.material.BottomNavigationItem +import androidx.compose.material.ContentAlpha import androidx.compose.material.Divider import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.ListItem +import androidx.compose.material.LocalContentAlpha import androidx.compose.material.MaterialTheme import androidx.compose.material.ScaffoldState import androidx.compose.material.Surface @@ -57,6 +60,7 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowDropDown import androidx.compose.material.rememberScaffoldState import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState @@ -141,21 +145,7 @@ fun HomeScene() { }, ) { if (!menus.any()) { - InAppNotificationScaffold( - scaffoldState = scaffoldState, - topBar = { - AppBar( - backgroundColor = MaterialTheme.colors.surface.withElevation(), - navigationIcon = { - MenuAvatar(scaffoldState) - }, - ) - }, - drawerContent = { - HomeDrawer(scaffoldState = scaffoldState) - } - ) { - } + EmptyColumnHomeContent(scaffoldState) } else { NestedScrollScaffold( scaffoldState = scaffoldState, @@ -214,6 +204,48 @@ fun HomeScene() { } } +@Composable +private fun EmptyColumnHomeContent(scaffoldState: ScaffoldState) { + InAppNotificationScaffold( + scaffoldState = scaffoldState, + topBar = { + AppBar( + backgroundColor = MaterialTheme.colors.surface.withElevation(), + navigationIcon = { + MenuAvatar(scaffoldState) + }, + ) + }, + drawerContent = { + HomeDrawer(scaffoldState = scaffoldState) + } + ) { + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + ) { + Icon( + painter = painterResource(id = R.drawable.ic_empty_column), + contentDescription = null, + ) + Spacer(modifier = Modifier.height(EmptyColumnHomeContentDefaults.VerticalPadding)) + CompositionLocalProvider( + LocalContentAlpha provides ContentAlpha.disabled + ) { + Text( + text = "Modify the layout settings", + style = MaterialTheme.typography.h6, + ) + } + } + } +} + +private object EmptyColumnHomeContentDefaults { + val VerticalPadding = 48.dp +} + @OptIn(ExperimentalAnimationApi::class) @Composable fun HomeAppBar( diff --git a/app/src/main/res/drawable/ic_empty_column.xml b/app/src/main/res/drawable/ic_empty_column.xml new file mode 100644 index 000000000..393462ba5 --- /dev/null +++ b/app/src/main/res/drawable/ic_empty_column.xml @@ -0,0 +1,22 @@ + + + + From a8443a67bbb6f76e0422c10d849a173c096b2445 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Wed, 14 Jul 2021 12:18:17 +0800 Subject: [PATCH 019/137] add user search support for twitter --- .../kotlin/com/twidere/twiderex/scenes/compose/ComposeScene.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/compose/ComposeScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/compose/ComposeScene.kt index 304227e3d..2c8269e52 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/compose/ComposeScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/compose/ComposeScene.kt @@ -1209,7 +1209,7 @@ private fun ComposeActions( // IconButton(onClick = {}) { // Icon(painter = painterResource(id = R.drawable.ic_gif)) // } - if (account.type == PlatformType.Mastodon) { + if (account.type == PlatformType.Mastodon || account.type == PlatformType.Twitter) { IconButton( onClick = { scope.launch { From ba6d24a41b10853a076e37af97a054ae68a47a8c Mon Sep 17 00:00:00 2001 From: Tlaster Date: Wed, 14 Jul 2021 17:45:04 +0800 Subject: [PATCH 020/137] fix testing --- .../com/twidere/twiderex/db/DbListTest.kt | 6 ++-- .../com/twidere/twiderex/db/DbSearchTest.kt | 32 +++++++------------ 2 files changed, 14 insertions(+), 24 deletions(-) diff --git a/app/src/androidTest/java/com/twidere/twiderex/db/DbListTest.kt b/app/src/androidTest/java/com/twidere/twiderex/db/DbListTest.kt index e74975c80..857630a70 100644 --- a/app/src/androidTest/java/com/twidere/twiderex/db/DbListTest.kt +++ b/app/src/androidTest/java/com/twidere/twiderex/db/DbListTest.kt @@ -21,7 +21,6 @@ package com.twidere.twiderex.db import androidx.arch.core.executor.testing.InstantTaskExecutorRule -import androidx.lifecycle.Observer import androidx.paging.PagingSource import androidx.room.Room import androidx.test.core.app.ApplicationProvider @@ -32,7 +31,6 @@ import com.twidere.services.twitter.model.TwitterList import com.twidere.services.twitter.model.User import com.twidere.twiderex.db.dao.ListsDao import com.twidere.twiderex.db.mapper.toDbList -import com.twidere.twiderex.db.model.DbList import com.twidere.twiderex.model.MicroBlogKey import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.runBlocking @@ -120,12 +118,12 @@ class DbListTest { fun findDbListWithListKeyWithFlow_AutoUpdateAfterDbUpdate() { runBlocking { val source = listsDao.findWithListKeyWithFlow(MicroBlogKey.twitter("0"), twitterAccountKey) - val observer = Observer { } - val data = source.firstOrNull() + var data = source.firstOrNull() Assert.assertEquals("description 0", data?.description) data?.let { listsDao.update(listOf(it.copy(description = "Update 0"))) } + data = source.firstOrNull() Assert.assertEquals("Update 0", data?.description) } } diff --git a/app/src/androidTest/java/com/twidere/twiderex/db/DbSearchTest.kt b/app/src/androidTest/java/com/twidere/twiderex/db/DbSearchTest.kt index 4b602f90c..37d50cf86 100644 --- a/app/src/androidTest/java/com/twidere/twiderex/db/DbSearchTest.kt +++ b/app/src/androidTest/java/com/twidere/twiderex/db/DbSearchTest.kt @@ -21,13 +21,13 @@ package com.twidere.twiderex.db import androidx.arch.core.executor.testing.InstantTaskExecutorRule -import androidx.lifecycle.Observer import androidx.room.Room import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.twidere.twiderex.db.dao.SearchDao import com.twidere.twiderex.db.model.DbSearch import com.twidere.twiderex.model.MicroBlogKey +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.runBlocking import org.junit.After import org.junit.Assert @@ -92,20 +92,17 @@ class DbSearchTest { @Test fun getAll_returnResultByAccountKey() = runBlocking { val result = searchDao.getAll(twitterAccountKey) - val observer = Observer?> { } - result.observeForever(observer) - Assert.assertEquals(twitterSearchCount, result.value?.size) - result.value?.forEach { + val value = result.firstOrNull() + Assert.assertEquals(twitterSearchCount, value?.size) + value?.forEach { assert(it.content.startsWith("twitter")) } ?: assert(false) } @Test fun getAllHistory_returnResultsMatchAccountKeyAndNotSaved() = runBlocking { - val result = searchDao.getAllHistory(mastodonAccountKey) - val observer = Observer?> { } - result.observeForever(observer) - result.value?.forEach { + val result = searchDao.getAllHistory(mastodonAccountKey).firstOrNull() + result?.forEach { assert(it.content.startsWith("mastodon")) assert(!it.saved) } ?: assert(false) @@ -113,10 +110,8 @@ class DbSearchTest { @Test fun getAllSaved_returnResultsMatchAccountKeyAndSaved() = runBlocking { - val result = searchDao.getAllSaved(twitterAccountKey) - val observer = Observer?> { } - result.observeForever(observer) - result.value?.forEach { + val result = searchDao.getAllSaved(twitterAccountKey).firstOrNull() + result?.forEach { assert(it.content.startsWith("twitter")) assert(it.saved) } ?: assert(false) @@ -141,16 +136,13 @@ class DbSearchTest { @Test fun clearHistory_deleteSearchNotSaved() = runBlocking { searchDao.clear() - val twitterResult = searchDao.getAll(twitterAccountKey) - val mastodonResult = searchDao.getAll(mastodonAccountKey) - val observer = Observer?> { } - twitterResult.observeForever(observer) - mastodonResult.observeForever(observer) - twitterResult.value?.forEach { + val twitterResult = searchDao.getAll(twitterAccountKey).firstOrNull() + val mastodonResult = searchDao.getAll(mastodonAccountKey).firstOrNull() + twitterResult?.forEach { assert(it.saved) } ?: assert(false) - mastodonResult.value?.forEach { + mastodonResult?.forEach { assert(it.saved) } ?: assert(false) } From 4ed4c000ebfe830ba7e9532ced54c3211dab35cf Mon Sep 17 00:00:00 2001 From: Tlaster Date: Wed, 14 Jul 2021 18:11:11 +0800 Subject: [PATCH 021/137] fix list edit initial value --- .../twiderex/viewmodel/lists/ListsViewModel.kt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/lists/ListsViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/lists/ListsViewModel.kt index 353ec126f..dcb1e673f 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/lists/ListsViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/lists/ListsViewModel.kt @@ -34,6 +34,7 @@ import com.twidere.twiderex.utils.notify import dagger.assisted.Assisted import dagger.assisted.AssistedInject import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch @@ -131,6 +132,16 @@ class ListsModifyViewModel @AssistedInject constructor( listsRepository.findListWithListKey(account = account, listKey = listKey) } + init { + viewModelScope.launch { + source.firstOrNull()?.let { + editName.value = it.title + editDesc.value = it.descriptions + editPrivate.value = it.isPrivate + } + } + } + val editName = MutableStateFlow("") var editDesc = MutableStateFlow("") var editPrivate = MutableStateFlow(false) From 8deecfc5db54bb27e9956d868e9a026d2a483e64 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Wed, 14 Jul 2021 18:19:07 +0800 Subject: [PATCH 022/137] revert unexcepted change --- .../paging/compose/LazyPagingItems.kt | 92 ++++++++++++------- .../component/lazy/itemsGridIndexed.kt | 2 +- 2 files changed, 58 insertions(+), 36 deletions(-) diff --git a/app/src/main/kotlin/androidx/paging/compose/LazyPagingItems.kt b/app/src/main/kotlin/androidx/paging/compose/LazyPagingItems.kt index e75846d27..b36e2058f 100644 --- a/app/src/main/kotlin/androidx/paging/compose/LazyPagingItems.kt +++ b/app/src/main/kotlin/androidx/paging/compose/LazyPagingItems.kt @@ -24,12 +24,12 @@ import android.annotation.SuppressLint import androidx.compose.foundation.lazy.LazyItemScope import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.State import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.setValue import androidx.paging.CombinedLoadStates import androidx.paging.DifferCallback @@ -63,34 +63,39 @@ public class LazyPagingItems internal constructor( private val mainDispatcher = Dispatchers.Main /** - * Contains the latest items list snapshot collected from the [flow]. + * The number of items which can be accessed. */ - private var itemSnapshotList by mutableStateOf( - ItemSnapshotList(0, 0, emptyList()) - ) + var itemCount: Int by mutableStateOf(0) + private set /** - * The number of items which can be accessed. + * Set of value holders associated with the currently (composed) indexes. Once we got a new + * value from the repository we can just update the value in the state. */ - val itemCount: Int get() = itemSnapshotList.size + private val activeHolders = HashSet>() @SuppressLint("RestrictedApi") private val differCallback: DifferCallback = object : DifferCallback { override fun onChanged(position: Int, count: Int) { if (count > 0) { - updateItemSnapshotList() + updateValueHolders(position, count) } } override fun onInserted(position: Int, count: Int) { if (count > 0) { - updateItemSnapshotList() + // we have to update all the items starting from this position as the insertion + // changes the positions for all the next items + updateValueHolders(position, pagingDataDiffer.size - position) } } override fun onRemoved(position: Int, count: Int) { if (count > 0) { - updateItemSnapshotList() + // we have to update all the items starting from this position as the removal + // changes the positions for all the next items. plus we also want to set null + // for the items which are now out of the valid bounds. + updateValueHolders(position, pagingDataDiffer.size + count - position) } } } @@ -107,26 +112,12 @@ public class LazyPagingItems internal constructor( onListPresentable: () -> Unit ): Int? { onListPresentable() - updateItemSnapshotList() + val oldSize = itemCount + updateValueHolders(0, maxOf(newList.size, oldSize)) return null } } - private fun updateItemSnapshotList() { - itemSnapshotList = pagingDataDiffer.snapshot() - } - - /** - * Returns the presented item at the specified position, notifying Paging of the item access to - * trigger any loads necessary to fulfill prefetchDistance. - * - * @see peek - */ - operator fun get(index: Int): T? { - pagingDataDiffer[index] // this registers the value load - return itemSnapshotList[index] - } - /** * Returns the state containing the item specified at [index] and notifies Paging of the item * accessed in order to trigger any loads necessary to fulfill [PagingConfig.prefetchDistance]. @@ -136,12 +127,39 @@ public class LazyPagingItems internal constructor( * placeholder or [index] is not within the correct bounds. */ @Composable - @Deprecated( - "Use get() instead. It will return you the value not wrapped into a State", - ReplaceWith("this[index]") - ) fun getAsState(index: Int): State { - return rememberUpdatedState(get(index)) + require(index >= 0) { "Index can't be negative. $index was passed." } + val holder = remember(index) { + val initial = if (index < pagingDataDiffer.size) { + pagingDataDiffer[index] + } else { + null + } + ActiveItemValueHolder(index, initial) + } + DisposableEffect(index) { + activeHolders.add(holder) + onDispose { + activeHolders.remove(holder) + } + } + return holder.state + } + + private fun updateValueHolders(position: Int, count: Int) { + itemCount = pagingDataDiffer.size + if (count > 0) { + activeHolders.forEach { + if (it.index in position until position + count) { + val newValue = if (it.index < pagingDataDiffer.size) { + pagingDataDiffer[it.index] + } else { + null + } + it.state.value = newValue + } + } + } } /** @@ -152,7 +170,7 @@ public class LazyPagingItems internal constructor( * @return The presented item at position [index], `null` if it is a placeholder */ fun peek(index: Int): T? { - return itemSnapshotList[index] + return pagingDataDiffer.peek(index) } /** @@ -160,7 +178,7 @@ public class LazyPagingItems internal constructor( * placeholders if they are enabled. */ fun snapshot(): ItemSnapshotList { - return itemSnapshotList + return pagingDataDiffer.snapshot() } /** @@ -220,6 +238,10 @@ public class LazyPagingItems internal constructor( pagingDataDiffer.collectFrom(it) } } + + private class ActiveItemValueHolder(val index: Int, initialValue: T?) { + val state = mutableStateOf(initialValue) + } } private val IncompleteLoadState = LoadState.NotLoading(false) @@ -268,7 +290,7 @@ public fun LazyListScope.items( itemContent: @Composable LazyItemScope.(value: T?) -> Unit ) { items(lazyPagingItems.itemCount, key = key) { index -> - itemContent(lazyPagingItems[index]) + itemContent(lazyPagingItems.getAsState(index).value) } } @@ -291,6 +313,6 @@ public fun LazyListScope.itemsIndexed( itemContent: @Composable LazyItemScope.(index: Int, value: T?) -> Unit ) { items(lazyPagingItems.itemCount, key = key) { index -> - itemContent(index, lazyPagingItems[index]) + itemContent(index, lazyPagingItems.getAsState(index).value) } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/lazy/itemsGridIndexed.kt b/app/src/main/kotlin/com/twidere/twiderex/component/lazy/itemsGridIndexed.kt index 4eaf082b9..41c1033ef 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/lazy/itemsGridIndexed.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/lazy/itemsGridIndexed.kt @@ -79,7 +79,7 @@ fun LazyListScope.itemsPagingGridIndexed( data.retry() } itemsGridIndexed((0 until data.itemCount).toList(), rowSize = rowSize, spacing = spacing, padding = padding) { _, index -> - itemContent.invoke(this, index, data[index]) + itemContent.invoke(this, index, data.getAsState(index = index).value) } loadState(data.loadState.append) { data.retry() From 801efcba00d57fe43609f5138656d852961ac7e3 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Thu, 15 Jul 2021 11:35:42 +0800 Subject: [PATCH 023/137] [WIP] redesign precompose navigation stack --- .../foundation/navigation/BackStackEntry.kt | 39 ++++++++- .../foundation/navigation/NavHost.kt | 83 +++++++++++-------- .../foundation/navigation/RouteStack.kt | 55 ++++-------- .../navigation/RouteStackManager.kt | 11 +-- .../navigation/transition/AnimatedRoute.kt | 4 +- 5 files changed, 107 insertions(+), 85 deletions(-) diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/BackStackEntry.kt b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/BackStackEntry.kt index 4e616c6a6..399487195 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/BackStackEntry.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/BackStackEntry.kt @@ -20,6 +20,9 @@ */ package moe.tlaster.precompose.navigation +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.LifecycleRegistry import androidx.lifecycle.ViewModelStore import androidx.lifecycle.ViewModelStoreOwner import moe.tlaster.precompose.navigation.route.ComposeRoute @@ -30,10 +33,44 @@ class BackStackEntry internal constructor( val pathMap: Map, val queryString: QueryString? = null, internal val viewModel: NavControllerViewModel, -) : ViewModelStoreOwner { +) : ViewModelStoreOwner, LifecycleOwner { + private var destroyAfterTransition = false + override fun getViewModelStore(): ViewModelStore { return viewModel.get(id = id) } + + private val lifecycleRegistry by lazy { + LifecycleRegistry(this) + } + + override fun getLifecycle(): Lifecycle { + return lifecycleRegistry + } + + fun active() { + lifecycleRegistry.currentState = Lifecycle.State.RESUMED + } + + fun inActive() { + if (lifecycleRegistry.currentState.isAtLeast(Lifecycle.State.RESUMED)) { + lifecycleRegistry.currentState = Lifecycle.State.STARTED + } + if (destroyAfterTransition) { + destroy() + } + } + + fun destroy() { + if (lifecycleRegistry.currentState.isAtLeast(Lifecycle.State.RESUMED) || + lifecycleRegistry.currentState == Lifecycle.State.INITIALIZED + ) { + destroyAfterTransition = true + } else { + lifecycleRegistry.currentState = Lifecycle.State.DESTROYED + viewModelStore.clear() + } + } } inline fun BackStackEntry.path(path: String, default: T? = null): T? { diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/NavHost.kt b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/NavHost.kt index cc586deb9..49c911bf0 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/NavHost.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/NavHost.kt @@ -21,18 +21,12 @@ package moe.tlaster.precompose.navigation import androidx.activity.compose.LocalOnBackPressedDispatcherOwner -import androidx.compose.animation.Crossfade -import androidx.compose.foundation.gestures.forEachGesture -import androidx.compose.foundation.layout.Box import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveableStateHolder -import androidx.compose.ui.Modifier -import androidx.compose.ui.input.pointer.consumeAllChanges -import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner import moe.tlaster.precompose.navigation.transition.AnimatedRoute @@ -93,45 +87,62 @@ fun NavHost( navTransition = navTransition, manager = manager, ) { routeStack -> - LaunchedEffect(routeStack) { - routeStack.onActive() + // LaunchedEffect(routeStack) { + // routeStack.onActive() + // } + // DisposableEffect(routeStack) { + // onDispose { + // routeStack.onInActive() + // } + // } + LaunchedEffect(routeStack.currentEntry) { + routeStack.currentEntry?.active() } - DisposableEffect(routeStack) { + DisposableEffect(routeStack.currentEntry) { onDispose { - routeStack.onInActive() + routeStack.currentEntry?.inActive() } } - CompositionLocalProvider( - LocalLifecycleOwner provides routeStack, - ) { - stateHolder.SaveableStateProvider(routeStack.id) { + routeStack.stacks.forEach { + stateHolder.SaveableStateProvider(it.id) { CompositionLocalProvider( - LocalViewModelStoreOwner provides routeStack.scene + LocalViewModelStoreOwner provides it, + LocalLifecycleOwner provides it, ) { - routeStack.scene.route.content.invoke(routeStack.scene) - } - Crossfade(targetState = routeStack.currentDialogStack) { - it?.let { backStackEntry -> - CompositionLocalProvider( - LocalViewModelStoreOwner provides backStackEntry - ) { - Box( - modifier = Modifier - .pointerInput(Unit) { - forEachGesture { - awaitPointerEventScope { - awaitPointerEvent().changes.forEach { it.consumeAllChanges() } - } - } - } - ) { - backStackEntry.route.content.invoke(backStackEntry) - } - } - } + it.route.content.invoke(it) } } } + // stateHolder.SaveableStateProvider(routeStack.id) { + // CompositionLocalProvider( + // LocalViewModelStoreOwner provides routeStack.scene, + // LocalLifecycleOwner provides routeStack, + // ) { + // routeStack.scene.route.content.invoke(routeStack.scene) + // } + // } + // + // Crossfade(targetState = routeStack.currentDialogStack) { + // it?.let { backStackEntry -> + // CompositionLocalProvider( + // LocalViewModelStoreOwner provides backStackEntry, + // LocalLifecycleOwner provides backStackEntry, + // ) { + // Box( + // modifier = Modifier + // .pointerInput(Unit) { + // forEachGesture { + // awaitPointerEventScope { + // awaitPointerEvent().changes.forEach { it.consumeAllChanges() } + // } + // } + // } + // ) { + // backStackEntry.route.content.invoke(backStackEntry) + // } + // } + // } + // } } } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/RouteStack.kt b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/RouteStack.kt index 5eca1a6ab..b95537dea 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/RouteStack.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/RouteStack.kt @@ -23,69 +23,42 @@ package moe.tlaster.precompose.navigation import androidx.compose.runtime.Stable import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.snapshots.SnapshotStateList -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.LifecycleRegistry import moe.tlaster.precompose.navigation.transition.NavTransition @Stable internal class RouteStack( val id: Long, - val scene: BackStackEntry, - val dialogStack: SnapshotStateList = mutableStateListOf(), + val stacks: SnapshotStateList = mutableStateListOf(), val navTransition: NavTransition? = null, -) : LifecycleOwner { - private var destroyAfterTransition = false - val currentEntry: BackStackEntry - get() = if (dialogStack.any()) { - dialogStack.last() - } else { - scene - } - val currentDialogStack: BackStackEntry? - get() = dialogStack.lastOrNull() - - private val lifecycleRegistry by lazy { - LifecycleRegistry(this) - } +) { + val currentEntry: BackStackEntry? + get() = stacks.lastOrNull() val canGoBack: Boolean - get() = dialogStack.isNotEmpty() + get() = stacks.size > 1 fun goBack(): BackStackEntry { - return dialogStack.removeLast().apply { - viewModelStore.clear() + return stacks.removeLast().also { + it.destroy() } } fun onActive() { - lifecycleRegistry.currentState = Lifecycle.State.RESUMED + currentEntry?.active() } fun onInActive() { - if (lifecycleRegistry.currentState.isAtLeast(Lifecycle.State.RESUMED)) { - lifecycleRegistry.currentState = Lifecycle.State.STARTED - } - if (destroyAfterTransition) { - onDestroyed() - } + currentEntry?.inActive() } fun onDestroyed() { - if (lifecycleRegistry.currentState.isAtLeast(Lifecycle.State.RESUMED) || - lifecycleRegistry.currentState == Lifecycle.State.INITIALIZED - ) { - destroyAfterTransition = true - } else { - lifecycleRegistry.currentState = Lifecycle.State.DESTROYED - dialogStack.forEach { - it.viewModelStore.clear() - } - scene.viewModelStore.clear() + stacks.forEach { + it.destroy() } + stacks.clear() } - override fun getLifecycle(): Lifecycle { - return lifecycleRegistry + fun hasRoute(route: String): Boolean { + return stacks.any { it.route.route == route } } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/RouteStackManager.kt b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/RouteStackManager.kt index 8510b4d95..618ebdad5 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/RouteStackManager.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/RouteStackManager.kt @@ -108,7 +108,7 @@ internal class RouteStackManager( checkNotNull(matchResult) { "RouteStackManager: navigate target $path not found" } require(matchResult.route is ComposeRoute) { "RouteStackManager: navigate target $path is not ComposeRoute" } if (options != null && matchResult.route is SceneRoute && options.launchSingleTop) { - _backStacks.firstOrNull { it.scene.route.route == matchResult.route.route }?.let { + _backStacks.firstOrNull { it.hasRoute(matchResult.route.route) }?.let { _backStacks.remove(it) _backStacks.add(it) } @@ -127,19 +127,19 @@ internal class RouteStackManager( _backStacks.add( RouteStack( id = routeStackId++, - scene = entry, + stacks = mutableStateListOf(entry), navTransition = matchResult.route.navTransition, ) ) } is DialogRoute -> { - currentStack?.dialogStack?.add(entry) + currentStack?.stacks?.add(entry) } } } if (options?.popUpTo != null && matchResult.route is SceneRoute) { - val index = _backStacks.indexOfLast { it.scene.route.route == options.popUpTo.route } + val index = _backStacks.indexOfLast { it.hasRoute(options.popUpTo.route) } if (index != -1 && index != _backStacks.lastIndex) { _backStacks.removeRange( if (options.popUpTo.inclusive) index else index + 1, @@ -164,9 +164,10 @@ internal class RouteStackManager( } _backStacks.size > 1 -> { val stack = _backStacks.removeLast() + val entry = stack.currentEntry stateHolder.removeState(stack.id) stack.onDestroyed() - stack.scene + entry } else -> { null diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/transition/AnimatedRoute.kt b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/transition/AnimatedRoute.kt index 7f039ea7d..3fe4fe839 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/transition/AnimatedRoute.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/transition/AnimatedRoute.kt @@ -59,8 +59,8 @@ internal fun AnimatedRoute( .takeIf { it >= 0 || // Workaround for navOptions - targetState.lifecycle.currentState == Lifecycle.State.INITIALIZED && - previousState.lifecycle.currentState == Lifecycle.State.RESUMED + targetState.currentEntry?.lifecycle?.currentState == Lifecycle.State.INITIALIZED && + previousState.currentEntry?.lifecycle?.currentState == Lifecycle.State.RESUMED } ?: Int.MAX_VALUE val actualNavTransition = run { if (indexOfNew >= indexOfOld) targetState else previousState From d15c71e6e2565d2b52b607b2d219bf84ee213a8f Mon Sep 17 00:00:00 2001 From: itsMimao Date: Thu, 15 Jul 2021 13:58:25 +0800 Subject: [PATCH 024/137] add ui of proxy settings --- .../twiderex/component/settings/SwitchItem.kt | 4 +- .../twiderex/extensions/FlowExtensions.kt | 32 ++ .../twiderex/scenes/settings/MiscScene.kt | 286 ++++++++++++++++++ .../viewmodel/settings/MiscViewModel.kt | 107 ++++++- app/src/main/proto/MiscPreferences.proto | 11 + 5 files changed, 438 insertions(+), 2 deletions(-) create mode 100644 app/src/main/kotlin/com/twidere/twiderex/extensions/FlowExtensions.kt diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/settings/SwitchItem.kt b/app/src/main/kotlin/com/twidere/twiderex/component/settings/SwitchItem.kt index f5f6f64be..29148a38a 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/settings/SwitchItem.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/settings/SwitchItem.kt @@ -33,6 +33,7 @@ import com.twidere.twiderex.component.foundation.ColoredSwitch fun ColumnScope.switchItem( value: Boolean, onChanged: (Boolean) -> Unit, + describe: @Composable () -> Unit = {}, title: @Composable () -> Unit, ) { ListItem( @@ -47,6 +48,7 @@ fun ColumnScope.switchItem( onChanged.invoke(it) }, ) - } + }, + secondaryText = describe ) } diff --git a/app/src/main/kotlin/com/twidere/twiderex/extensions/FlowExtensions.kt b/app/src/main/kotlin/com/twidere/twiderex/extensions/FlowExtensions.kt new file mode 100644 index 000000000..7010aaab1 --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/extensions/FlowExtensions.kt @@ -0,0 +1,32 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.extensions + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.State +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.platform.LocalLifecycleOwner +import androidx.lifecycle.flowWithLifecycle +import kotlinx.coroutines.flow.Flow + +@Composable +fun Flow.observeAsState(initial: T): State = + flowWithLifecycle(LocalLifecycleOwner.current.lifecycle).collectAsState(initial = initial) diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/MiscScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/MiscScene.kt index eeee43150..a3974cc9f 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/MiscScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/MiscScene.kt @@ -20,24 +20,40 @@ */ package com.twidere.twiderex.scenes.settings +import android.annotation.SuppressLint +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.AlertDialog +import androidx.compose.material.ContentAlpha import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.ListItem +import androidx.compose.material.LocalContentAlpha import androidx.compose.material.OutlinedTextField import androidx.compose.material.Text import androidx.compose.material.TextButton +import androidx.compose.material.TextField +import androidx.compose.material.TextFieldDefaults import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import com.twidere.twiderex.R @@ -46,7 +62,11 @@ import com.twidere.twiderex.component.foundation.AppBarNavigationButton import com.twidere.twiderex.component.foundation.InAppNotificationScaffold import com.twidere.twiderex.component.lazy.ItemHeader import com.twidere.twiderex.component.navigation.LocalNavigator +import com.twidere.twiderex.component.settings.RadioItem +import com.twidere.twiderex.component.settings.switchItem import com.twidere.twiderex.di.assisted.assistedViewModel +import com.twidere.twiderex.extensions.observeAsState +import com.twidere.twiderex.preferences.proto.MiscPreferences import com.twidere.twiderex.ui.TwidereScene import com.twidere.twiderex.viewmodel.settings.MiscViewModel @@ -69,6 +89,38 @@ fun MiscScene() { ) } ) { + val showProxyTypeDialog = remember { mutableStateOf(false) } + val proxyTypeValue by viewModel.proxyType.observeAsState(initial = MiscPreferences.ProxyType.HTTP) + val showProxyInputDialog = remember { mutableStateOf(false) } + val inputTitle = remember { + mutableStateOf("") + } + val inputValue = remember { + mutableStateOf("") + } + val inputChanged = remember { + mutableStateOf<(value: String) -> Unit>({}) + } + + if (showProxyTypeDialog.value) { + ProxyTypeSelectDialog( + onDismissRequest = { showProxyTypeDialog.value = false }, + onSelect = { + viewModel.setProxyType(it.name) + }, + value = proxyTypeValue + ) + } + if (showProxyInputDialog.value) { + ProxyInputDialog( + title = inputTitle.value, + value = inputValue.value, + onValueChanged = inputChanged.value, + onDismissRequest = { + showProxyInputDialog.value = false + } + ) + } Column( modifier = Modifier .verticalScroll( @@ -76,11 +128,153 @@ fun MiscScene() { ) ) { NitterPreference(viewModel) + ProxyPreference( + viewModel = viewModel, + showProxyInputDialog = showProxyInputDialog, + showProxyTypeDialog = showProxyTypeDialog, + inputTitle = inputTitle, + inputValue = inputValue, + inputChanged = inputChanged + ) + } + } + } +} + +@Composable +fun ColumnScope.ProxyPreference( + viewModel: MiscViewModel, + showProxyInputDialog: MutableState, + showProxyTypeDialog: MutableState, + inputTitle: MutableState, + inputValue: MutableState, + inputChanged: MutableState<(value: String) -> Unit> +) { + val useProxy by viewModel.useProxy.observeAsState(false) + val proxyType by viewModel.proxyType.observeAsState(MiscPreferences.ProxyType.HTTP) + val proxyServer by viewModel.proxyServer.observeAsState("") + val proxyPort by viewModel.proxyPort.observeAsState(null) + val proxyUserName by viewModel.proxyUserName.observeAsState("") + val proxyPassword by viewModel.proxyPassword.observeAsState("") + + // todo PROXY localize + ItemHeader { + + Text(text = "Proxy settings") + } + switchItem( + value = useProxy, + onChanged = { + viewModel.setUseProxy(it) + }, + describe = { + Text(text = "Use proxy for all network requests") + } + ) { + Text(text = "Proxy") + } + ItemProxy( + enable = useProxy, + title = "Proxy type", + content = proxyTypeValue(type = proxyType), + onClick = { + showProxyTypeDialog.value = true + } + ) + + ItemProxy( + enable = useProxy, + title = "Server", + content = proxyServer, + onClick = { + inputTitle.value = "server" + inputValue.value = proxyServer + inputChanged.value = { + viewModel.setProxyServer(it) + } + showProxyInputDialog.value = true + } + ) + + ItemProxy( + enable = useProxy, + title = "Port", + content = proxyPort?.toString() ?: "", + onClick = { + inputTitle.value = "Port" + inputValue.value = proxyPort?.toString() ?: "" + inputChanged.value = { + viewModel.setProxyPort(it) + } + showProxyInputDialog.value = true + } + ) + + ItemProxy( + enable = useProxy, + title = "Username", + content = proxyUserName, + onClick = { + inputTitle.value = "UserName" + inputValue.value = proxyUserName + inputChanged.value = { + viewModel.setProxyUserName(it) + } + showProxyInputDialog.value = true + } + ) + ItemProxy( + enable = useProxy, + title = "Password", + content = proxyPassword, + onClick = { + inputTitle.value = "Password" + inputValue.value = proxyPassword + inputChanged.value = { + viewModel.setProxyPassword(it) } + showProxyInputDialog.value = true } + ) +} + +@Composable +fun proxyTypeValue(type: MiscPreferences.ProxyType): String { + return when (type) { + MiscPreferences.ProxyType.HTTP -> "HTTP" + MiscPreferences.ProxyType.REVERSE -> "Reverse" + MiscPreferences.ProxyType.UNRECOGNIZED -> "UnKnow" } } +@OptIn(ExperimentalMaterialApi::class) +@Composable +fun ItemProxy( + enable: Boolean, + title: String, + content: String, + onClick: () -> Unit, +) { + ListItem( + modifier = Modifier.clickable( + onClick = { + onClick.invoke() + }, + enabled = enable + ), + text = { + CompositionLocalProvider(LocalContentAlpha provides if (enable) ContentAlpha.high else ContentAlpha.disabled) { + Text(text = title) + } + }, + secondaryText = { + CompositionLocalProvider(LocalContentAlpha provides if (enable) ContentAlpha.medium else ContentAlpha.disabled) { + Text(text = content) + } + } + ) +} + @OptIn(ExperimentalMaterialApi::class) @Composable fun NitterPreference(viewModel: MiscViewModel) { @@ -207,3 +401,95 @@ fun NitterInformationDialog( } ) } + +@SuppressLint("UnrememberedMutableState") +@Composable +fun ProxyTypeSelectDialog( + onDismissRequest: () -> Unit, + onSelect: (value: MiscPreferences.ProxyType) -> Unit, + value: MiscPreferences.ProxyType +) { + var selected by mutableStateOf(value) + AlertDialog( + onDismissRequest = onDismissRequest, + title = { + Text(text = "Proxy type") + }, + text = { + Column { + RadioItem( + options = listOf( + MiscPreferences.ProxyType.HTTP, + MiscPreferences.ProxyType.REVERSE, + ), + value = selected, + onChanged = { + selected = it + }, + title = {}, + itemContent = { + Text( + text = proxyTypeValue(type = it) + ) + } + ) + } + }, + confirmButton = { + TextButton( + onClick = { + onSelect.invoke(selected) + onDismissRequest.invoke() + } + ) { + Text(text = stringResource(id = R.string.common_controls_actions_ok)) + } + } + ) +} + +@OptIn(ExperimentalComposeUiApi::class) +@Composable +fun ProxyInputDialog( + title: String, + value: String, + onValueChanged: (value: String) -> Unit, + onDismissRequest: () -> Unit, +) { + var input by remember { + mutableStateOf(value) + } + val focusRequester = remember { FocusRequester() } + val keyboardController = LocalSoftwareKeyboardController.current + LaunchedEffect(Unit) { + focusRequester.requestFocus() + keyboardController?.show() + } + AlertDialog( + onDismissRequest = onDismissRequest, + title = { + Text(text = title) + }, + text = { + TextField( + value = input, + onValueChange = { input = it }, + modifier = Modifier.focusRequester(focusRequester) + .fillMaxWidth(), + colors = TextFieldDefaults.textFieldColors( + backgroundColor = Color.Transparent + ), + ) + }, + confirmButton = { + TextButton( + onClick = { + onValueChanged(input) + onDismissRequest.invoke() + } + ) { + Text(text = stringResource(id = R.string.common_controls_actions_ok)) + } + } + ) +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/settings/MiscViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/settings/MiscViewModel.kt index b0e1b7beb..651028f1e 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/settings/MiscViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/settings/MiscViewModel.kt @@ -24,15 +24,19 @@ import androidx.datastore.core.DataStore import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.twidere.twiderex.notification.InAppNotification import com.twidere.twiderex.preferences.proto.MiscPreferences import dagger.assisted.AssistedInject import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch +import java.lang.NumberFormatException @OptIn(FlowPreview::class) class MiscViewModel @AssistedInject constructor( - private val miscPreferences: DataStore + private val miscPreferences: DataStore, + private val inAppNotification: InAppNotification, ) : ViewModel() { @dagger.assisted.AssistedFactory interface AssistedFactory { @@ -43,9 +47,39 @@ class MiscViewModel @AssistedInject constructor( MutableLiveData("") } + val useProxy by lazy { + MutableStateFlow(false) + } + + val proxyType by lazy { + MutableStateFlow(MiscPreferences.ProxyType.HTTP) + } + + val proxyServer by lazy { + MutableStateFlow("") + } + + val proxyPort by lazy { + MutableStateFlow(null) + } + + val proxyUserName by lazy { + MutableStateFlow("") + } + + val proxyPassword by lazy { + MutableStateFlow("") + } + init { viewModelScope.launch { nitter.value = miscPreferences.data.first().nitterInstance + useProxy.value = miscPreferences.data.first().useProxy + proxyServer.value = miscPreferences.data.first().proxyServer + proxyPort.value = miscPreferences.data.first().proxyPort + proxyUserName.value = miscPreferences.data.first().proxyUserName + proxyPassword.value = miscPreferences.data.first().proxyPassword + proxyType.value = miscPreferences.data.first().proxyType } } @@ -59,4 +93,75 @@ class MiscViewModel @AssistedInject constructor( } } } + + fun setUseProxy(value: Boolean) { + useProxy.value = value + viewModelScope.launch { + miscPreferences.updateData { + it.toBuilder() + .setUseProxy(value) + .build() + } + } + } + + fun setProxyType(value: String) { + proxyType.value = MiscPreferences.ProxyType.valueOf(value) + viewModelScope.launch { + miscPreferences.updateData { + it.toBuilder() + .setProxyType(proxyType.value) + .build() + } + } + } + + fun setProxyServer(value: String) { + proxyServer.value = value + viewModelScope.launch { + miscPreferences.updateData { + it.toBuilder() + .setProxyServer(value) + .build() + } + } + } + + fun setProxyPort(value: String) { + try { + proxyPort.value = value.toInt() + } catch (e: NumberFormatException) { + inAppNotification.show("Proxy server port must be numbers") + return + } + viewModelScope.launch { + miscPreferences.updateData { + it.toBuilder() + .setProxyPort(value.toInt()) + .build() + } + } + } + + fun setProxyUserName(value: String) { + proxyUserName.value = value + viewModelScope.launch { + miscPreferences.updateData { + it.toBuilder() + .setProxyUserName(value) + .build() + } + } + } + + fun setProxyPassword(value: String) { + proxyPassword.value = value + viewModelScope.launch { + miscPreferences.updateData { + it.toBuilder() + .setProxyPassword(value) + .build() + } + } + } } diff --git a/app/src/main/proto/MiscPreferences.proto b/app/src/main/proto/MiscPreferences.proto index 235deeabb..89772e720 100644 --- a/app/src/main/proto/MiscPreferences.proto +++ b/app/src/main/proto/MiscPreferences.proto @@ -4,5 +4,16 @@ option java_package = "com.twidere.twiderex.preferences.proto"; option java_multiple_files = true; message MiscPreferences { + enum ProxyType { + HTTP = 0; + REVERSE = 1; + } + string nitterInstance = 1; + bool useProxy = 2; + ProxyType proxyType = 3; + string proxyServer = 4; + int32 proxyPort = 5; + string proxyUserName = 6; + string proxyPassword = 7; } From a22f989401cddf731283e400c6782bece4a00721 Mon Sep 17 00:00:00 2001 From: itsMimao Date: Thu, 15 Jul 2021 16:07:44 +0800 Subject: [PATCH 025/137] add http proxy --- .../component/foundation/NetworkImage.kt | 19 ++++- .../preferences/ProvidePreferences.kt | 24 +++++- .../com/twidere/services/http/retrofit.kt | 5 +- .../services/mastodon/MastodonService.kt | 19 ++++- .../com/twidere/services/proxy/ProxyConfig.kt | 81 +++++++++++++++++++ .../twidere/services/proxy/ProxyService.kt | 31 +++++++ .../services/twitter/TwitterService.kt | 25 ++++-- 7 files changed, 192 insertions(+), 12 deletions(-) create mode 100644 services/src/main/java/com/twidere/services/proxy/ProxyConfig.kt create mode 100644 services/src/main/java/com/twidere/services/proxy/ProxyService.kt diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/NetworkImage.kt b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/NetworkImage.kt index 639c8a4b4..c5e397673 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/NetworkImage.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/NetworkImage.kt @@ -41,10 +41,12 @@ import com.google.accompanist.coil.rememberCoilPainter import com.google.accompanist.imageloading.ImageLoadState import com.google.accompanist.imageloading.LoadPainter import com.twidere.services.http.authorization.OAuth1Authorization +import com.twidere.services.proxy.ProxyConfig import com.twidere.twiderex.R import com.twidere.twiderex.model.AccountDetails import com.twidere.twiderex.model.PlatformType import com.twidere.twiderex.model.cred.OAuthCredentials +import com.twidere.twiderex.preferences.LocalProxyConfig import com.twidere.twiderex.ui.LocalActiveAccount import okhttp3.Headers import okhttp3.Request @@ -57,13 +59,14 @@ fun NetworkImage( contentScale: ContentScale = ContentScale.Crop, placeholder: @Composable (() -> Unit)? = null, ) { + val proxyConfig = LocalProxyConfig.current val painter = if (data is Painter) { data } else { rememberCoilPainter( request = data, fadeIn = true, imageLoader = TwidereImageLoader( - CoilPainterDefaults.defaultImageLoader(), + buildRealImageLoader(proxyConfig), LocalContext.current, LocalActiveAccount.current ) @@ -80,6 +83,20 @@ fun NetworkImage( ) } +@Composable +fun buildRealImageLoader(proxyConfig: ProxyConfig): ImageLoader { + return if (proxyConfig.enable && + proxyConfig.server.isNotEmpty() + ) { + CoilPainterDefaults.defaultImageLoader() + .newBuilder() + .callFactory(proxyConfig.generateProxyClientBuilder().build()) + .build() + } else { + CoilPainterDefaults.defaultImageLoader() + } +} + private class TwidereImageLoader( private val realImageLoader: ImageLoader, private val context: Context, diff --git a/app/src/main/kotlin/com/twidere/twiderex/preferences/ProvidePreferences.kt b/app/src/main/kotlin/com/twidere/twiderex/preferences/ProvidePreferences.kt index 457b6d6eb..93a5c25da 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/preferences/ProvidePreferences.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/preferences/ProvidePreferences.kt @@ -26,17 +26,22 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.getValue import androidx.datastore.core.DataStore +import com.twidere.services.proxy.ProxyConfig +import com.twidere.services.proxy.ProxyService import com.twidere.twiderex.preferences.proto.AppearancePreferences import com.twidere.twiderex.preferences.proto.DisplayPreferences +import com.twidere.twiderex.preferences.proto.MiscPreferences import com.twidere.twiderex.ui.LocalVideoPlayback +import kotlinx.coroutines.flow.map import javax.inject.Inject val LocalAppearancePreferences = compositionLocalOf { error("No AppearancePreferences") } val LocalDisplayPreferences = compositionLocalOf { error("No DisplayPreferences") } - +val LocalProxyConfig = compositionLocalOf { error("No Proxy preferences") } data class PreferencesHolder @Inject constructor( val appearancePreferences: DataStore, val displayPreferences: DataStore, + val miscPreferences: DataStore ) @Composable @@ -50,11 +55,28 @@ fun ProvidePreferences( val display by holder.displayPreferences .data .collectAsState(initial = DisplayPreferences.getDefaultInstance()) + val proxyConfig by holder.miscPreferences + .data + .map { + ProxyConfig( + enable = it.useProxy, + server = it.proxyServer, + port = it.proxyPort, + userName = it.proxyUserName, + password = it.proxyPassword, + type = when (it.proxyType) { + MiscPreferences.ProxyType.REVERSE -> ProxyConfig.Type.REVERSE + else -> ProxyConfig.Type.HTTP + } + ).apply { ProxyService.updateProxyConfig(this) } + } + .collectAsState(initial = ProxyConfig()) CompositionLocalProvider( LocalAppearancePreferences provides appearances, LocalDisplayPreferences provides display, LocalVideoPlayback provides display.autoPlayback, + LocalProxyConfig provides proxyConfig ) { content.invoke() } diff --git a/services/src/main/java/com/twidere/services/http/retrofit.kt b/services/src/main/java/com/twidere/services/http/retrofit.kt index c2d72ecb1..d9b895cf7 100644 --- a/services/src/main/java/com/twidere/services/http/retrofit.kt +++ b/services/src/main/java/com/twidere/services/http/retrofit.kt @@ -22,13 +22,13 @@ package com.twidere.services.http import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory import com.twidere.services.http.authorization.Authorization +import com.twidere.services.proxy.ProxyService import com.twidere.services.serializer.DateQueryConverterFactory import com.twidere.services.utils.DEBUG import com.twidere.services.utils.JSON import kotlinx.serialization.ExperimentalSerializationApi import okhttp3.Interceptor import okhttp3.MediaType.Companion.toMediaType -import okhttp3.OkHttpClient import okhttp3.Response import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit @@ -44,8 +44,7 @@ internal inline fun retrofit( .Builder() .baseUrl(baseUrl) .client( - OkHttpClient - .Builder() + ProxyService.proxyConfig.value.generateProxyClientBuilder() .addInterceptor(AuthorizationInterceptor(authorization)) .apply { if (DEBUG) { diff --git a/services/src/main/java/com/twidere/services/mastodon/MastodonService.kt b/services/src/main/java/com/twidere/services/mastodon/MastodonService.kt index e403a7b9f..c8eba5544 100644 --- a/services/src/main/java/com/twidere/services/mastodon/MastodonService.kt +++ b/services/src/main/java/com/twidere/services/mastodon/MastodonService.kt @@ -56,8 +56,12 @@ import com.twidere.services.microblog.model.ISearchResponse import com.twidere.services.microblog.model.IStatus import com.twidere.services.microblog.model.IUser import com.twidere.services.microblog.model.Relationship +import com.twidere.services.proxy.ProxyService import com.twidere.services.utils.await import com.twidere.services.utils.decodeJson +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch import okhttp3.MultipartBody import okhttp3.OkHttpClient import okhttp3.Request @@ -78,8 +82,19 @@ class MastodonService( DownloadMediaService, ListsService, TrendService { - private val resources by lazy { - resources ?: retrofit( + private lateinit var resources: MastodonResources + + init { + updateResources(resources) + MainScope().launch { + ProxyService.proxyConfig.collect { + updateResources(resources) + } + } + } + + private fun updateResources(resources: MastodonResources?) { + this.resources = resources ?: retrofit( "https://$host", BearerAuthorization(accessToken), { chain -> diff --git a/services/src/main/java/com/twidere/services/proxy/ProxyConfig.kt b/services/src/main/java/com/twidere/services/proxy/ProxyConfig.kt new file mode 100644 index 000000000..49533425e --- /dev/null +++ b/services/src/main/java/com/twidere/services/proxy/ProxyConfig.kt @@ -0,0 +1,81 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.services.proxy + +import okhttp3.Credentials +import okhttp3.OkHttpClient +import java.net.InetSocketAddress +import java.net.Proxy + +data class ProxyConfig( + val enable: Boolean = false, + val server: String = "", + val port: Int = 0, + val userName: String = "", + val password: String = "", + val type: Type = Type.HTTP, +) { + + enum class Type { + HTTP, + REVERSE + } + + fun generateProxyClientBuilder(): OkHttpClient.Builder { + val builder = OkHttpClient.Builder() + return if (enable) { + when (type) { + Type.HTTP -> { + if (port !in (0..65535)) { + return builder + } + val address = InetSocketAddress.createUnresolved( + server, + port + ) + builder.proxy(Proxy(Proxy.Type.HTTP, address)) + builder.proxyAuthenticator { _, response -> + val b = response.request.newBuilder() + if (response.code == 407) { + if (!userName.isNullOrEmpty() && + !password.isNullOrEmpty() + ) { + val credential = Credentials.basic( + userName, + password + ) + b.header("Proxy-Authorization", credential) + } + } + b.build() + } + builder + } + Type.REVERSE -> { + // TODO REVERSE PROXY + builder + } + } + } else { + builder + } + } +} diff --git a/services/src/main/java/com/twidere/services/proxy/ProxyService.kt b/services/src/main/java/com/twidere/services/proxy/ProxyService.kt new file mode 100644 index 000000000..d951617d5 --- /dev/null +++ b/services/src/main/java/com/twidere/services/proxy/ProxyService.kt @@ -0,0 +1,31 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.services.proxy + +import kotlinx.coroutines.flow.MutableStateFlow + +object ProxyService { + val proxyConfig = MutableStateFlow(ProxyConfig()) + + fun updateProxyConfig(proxyConfig: ProxyConfig) { + this.proxyConfig.value = proxyConfig + } +} diff --git a/services/src/main/java/com/twidere/services/twitter/TwitterService.kt b/services/src/main/java/com/twidere/services/twitter/TwitterService.kt index 3ba4017ae..fed5887d1 100644 --- a/services/src/main/java/com/twidere/services/twitter/TwitterService.kt +++ b/services/src/main/java/com/twidere/services/twitter/TwitterService.kt @@ -40,6 +40,7 @@ import com.twidere.services.microblog.model.ISearchResponse import com.twidere.services.microblog.model.IStatus import com.twidere.services.microblog.model.IUser import com.twidere.services.microblog.model.Relationship +import com.twidere.services.proxy.ProxyService import com.twidere.services.twitter.api.TwitterResources import com.twidere.services.twitter.api.UploadResources import com.twidere.services.twitter.model.Attachment @@ -67,6 +68,9 @@ import com.twidere.services.utils.Base64 import com.twidere.services.utils.await import com.twidere.services.utils.copyToInLength import com.twidere.services.utils.decodeJson +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch import okhttp3.OkHttpClient import okhttp3.Request import java.io.ByteArrayOutputStream @@ -91,8 +95,21 @@ class TwitterService( ListsService, TrendService, DirectMessageService { - private val resources by lazy { - resources ?: retrofit( + private lateinit var resources: TwitterResources + + private lateinit var uploadResources: UploadResources + + init { + updateResources(resources) + MainScope().launch { + ProxyService.proxyConfig.collect { + updateResources(resources) + } + } + } + + private fun updateResources(resources: TwitterResources? = null) { + this.resources = resources ?: retrofit( TWITTER_BASE_URL, createOAuth1Authorization(), { chain -> @@ -116,9 +133,7 @@ class TwitterService( } } ) - } - private val uploadResources by lazy { - retrofit( + this.uploadResources = retrofit( UPLOAD_TWITTER_BASE_URL, createOAuth1Authorization(), ) From 86fda8685e31fdff90b5351e38ffb7604976cf1d Mon Sep 17 00:00:00 2001 From: itsMimao Date: Thu, 15 Jul 2021 17:29:28 +0800 Subject: [PATCH 026/137] add reverse proxy --- .../com/twidere/services/proxy/ProxyConfig.kt | 34 ++-- .../services/proxy/ReverseProxyInterceptor.kt | 145 ++++++++++++++++++ .../api/proxy/ReverseProxyInterceptorTest.kt | 69 +++++++++ 3 files changed, 232 insertions(+), 16 deletions(-) create mode 100644 services/src/main/java/com/twidere/services/proxy/ReverseProxyInterceptor.kt create mode 100644 services/src/test/java/com/twidere/services/api/proxy/ReverseProxyInterceptorTest.kt diff --git a/services/src/main/java/com/twidere/services/proxy/ProxyConfig.kt b/services/src/main/java/com/twidere/services/proxy/ProxyConfig.kt index 49533425e..05c6bee79 100644 --- a/services/src/main/java/com/twidere/services/proxy/ProxyConfig.kt +++ b/services/src/main/java/com/twidere/services/proxy/ProxyConfig.kt @@ -52,30 +52,32 @@ data class ProxyConfig( port ) builder.proxy(Proxy(Proxy.Type.HTTP, address)) - builder.proxyAuthenticator { _, response -> - val b = response.request.newBuilder() - if (response.code == 407) { - if (!userName.isNullOrEmpty() && - !password.isNullOrEmpty() - ) { - val credential = Credentials.basic( - userName, - password - ) - b.header("Proxy-Authorization", credential) + .proxyAuthenticator { _, response -> + val b = response.request.newBuilder() + if (response.code == 407) { + if (userName.isNotEmpty() && + password.isNotEmpty() + ) { + val credential = Credentials.basic( + userName, + password + ) + b.header("Proxy-Authorization", credential) + } } + b.build() } - b.build() - } - builder } Type.REVERSE -> { - // TODO REVERSE PROXY - builder + builder.addInterceptor(ReverseProxyInterceptor(server, userName, password)) } } } else { builder } } + + /** + * Intercept and replace proxy patterns to real URL + */ } diff --git a/services/src/main/java/com/twidere/services/proxy/ReverseProxyInterceptor.kt b/services/src/main/java/com/twidere/services/proxy/ReverseProxyInterceptor.kt new file mode 100644 index 000000000..bb74e9f6a --- /dev/null +++ b/services/src/main/java/com/twidere/services/proxy/ReverseProxyInterceptor.kt @@ -0,0 +1,145 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.services.proxy + +import com.twidere.services.utils.Base64 +import okhttp3.Credentials +import okhttp3.HttpUrl +import okhttp3.HttpUrl.Companion.toHttpUrlOrNull +import okhttp3.Interceptor +import okhttp3.Response +import okio.IOException +import java.net.URLEncoder + +class ReverseProxyInterceptor( + private val proxyFormat: String, + private val proxyUsername: String?, + private val proxyPassword: String? +) : Interceptor { + + override fun intercept(chain: Interceptor.Chain): Response { + val request = chain.request() + val url = request.url + val builder = request.newBuilder() + val replacedUrl = ReverseProxyHandler.replaceUrl(url, proxyFormat).toHttpUrlOrNull() ?: run { + throw IOException("Invalid reverse proxy format") + } + builder.url(replacedUrl) + if (!proxyUsername.isNullOrEmpty() && !proxyPassword.isNullOrEmpty()) { + val credential = Credentials.basic( + proxyUsername, + proxyPassword + ) + builder.addHeader("Proxy-Authorization", credential) + } + return chain.proceed(builder.build()) + } +} + +object ReverseProxyHandler { + private val urlSupportedPatterns = listOf( + "[SCHEME]", "[HOST]", "[PORT]", "[AUTHORITY]", + "[PATH]", "[/PATH]", "[PATH_ENCODED]", "[QUERY]", "[?QUERY]", "[QUERY_ENCODED]", + "[FRAGMENT]", "[#FRAGMENT]", "[FRAGMENT_ENCODED]", "[URL_ENCODED]", "[URL_BASE64]" + ) + + /** + * # Supported patterns + * + * * `[SCHEME]`: E.g. `http` or `https` + * * `[HOST]`: Host address + * * `[PORT]`: Port number + * * `[AUTHORITY]`: `[HOST]`:`[PORT]` or `[HOST]` if port is default. Colon **will be** URL encoded + * * `[PATH]`: Raw path part, **without leading slash** + * * `[/PATH]`: Raw path part, **with leading slash** + * * `[PATH_ENCODED]`: Path, **will be** URL encoded again + * * `[QUERY]`: Raw query part + * * `[?QUERY]`: Raw query part, with `?` prefix + * * `[QUERY_ENCODED]`: Raw query part, **will be** URL encoded again + * * `[FRAGMENT]`: Raw fragment part + * * `[#FRAGMENT]`: Raw fragment part, with `#` prefix + * * `[FRAGMENT_ENCODED]`: Raw fragment part, **will be** URL encoded again + * * `[URL_ENCODED]`: URL Encoded `url` itself + * * `[URL_BASE64]`: Base64 Encoded `url` itself + * + * # Null values + * `[PATH]`, `[/PATH]`, `[QUERY]`, `[?QUERY]`, `[FRAGMENT]`, `[#FRAGMENT]` will be empty when + * it's null, values and base64-encoded will be string `"null"`. + * + * A valid format looks like + * + * `https://proxy.com/[SCHEME]/[AUTHORITY]/[PATH][?QUERY][#FRAGMENT]`, + * + * A request + * + * `https://example.com:8080/path?query=value#fragment` + * + * Will be transformed to + * + * `https://proxy.com/https/example.com%3A8080/path?query=value#fragment` + */ + @Suppress("KDocUnresolvedReference") + fun replaceUrl(url: HttpUrl, format: String): String { + val sb = StringBuffer() + var startIndex = 0 + while (startIndex != -1) { + val find = format.findAnyOf(urlSupportedPatterns, startIndex) ?: break + sb.append(format, startIndex, find.first) + sb.append( + when (find.second) { + "[SCHEME]" -> url.scheme + "[HOST]" -> url.host + "[PORT]" -> url.port + "[AUTHORITY]" -> url.authority() + "[PATH]" -> url.encodedPath.removePrefix("/") + "[/PATH]" -> url.encodedPath + "[PATH_ENCODED]" -> url.encodedPath.removePrefix("/").urlEncoded() + "[QUERY]" -> url.encodedQuery.orEmpty() + "[?QUERY]" -> url.encodedQuery?.prefix("?").orEmpty() + "[QUERY_ENCODED]" -> url.encodedQuery?.urlEncoded() + "[FRAGMENT]" -> url.encodedFragment.orEmpty() + "[#FRAGMENT]" -> url.encodedFragment?.prefix("#").orEmpty() + "[FRAGMENT_ENCODED]" -> url.encodedFragment?.urlEncoded() + "[URL_ENCODED]" -> url.toString().urlEncoded() + "[URL_BASE64]" -> Base64.encodeToString( + url.toString().toByteArray(Charsets.UTF_8), + Base64.URL_SAFE + ) + else -> throw AssertionError() + } + ) + startIndex = find.first + find.second.length + } + sb.append(format, startIndex, format.length) + return sb.toString() + } + + private fun HttpUrl.authority(): String { + val host = host + val port = port + if (port == HttpUrl.defaultPort(scheme)) return host + return "$host%3A$port" + } + + private fun String.urlEncoded() = URLEncoder.encode(this, "utf-8") + + private fun String.prefix(prefix: String) = prefix + this +} diff --git a/services/src/test/java/com/twidere/services/api/proxy/ReverseProxyInterceptorTest.kt b/services/src/test/java/com/twidere/services/api/proxy/ReverseProxyInterceptorTest.kt new file mode 100644 index 000000000..918ad78bd --- /dev/null +++ b/services/src/test/java/com/twidere/services/api/proxy/ReverseProxyInterceptorTest.kt @@ -0,0 +1,69 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.services.api.proxy + +import com.twidere.services.proxy.ReverseProxyHandler +import okhttp3.HttpUrl.Companion.toHttpUrlOrNull +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals + +class ReverseProxyInterceptorTest { + @Test + fun testReplaceUrl() { + val format1 = "https://proxy.com/[SCHEME]/[AUTHORITY]/[PATH][?QUERY][#FRAGMENT]" + val format2 = "https://proxy.com/[AUTHORITY]/[PATH][?QUERY][#FRAGMENT]" + val format3 = "https://proxy.com/[AUTHORITY][/PATH][?QUERY][#FRAGMENT]" + val url1 = "https://example.com:8080/path?query=value#fragment".toHttpUrlOrNull()!! + val url2 = "https://example.com:8080/path?query=value".toHttpUrlOrNull()!! + val url3 = "https://example.com:8080/path#fragment".toHttpUrlOrNull()!! + val url4 = "https://example.com:8080/path".toHttpUrlOrNull()!! + val url5 = "https://example.com/path".toHttpUrlOrNull()!! + + assertEquals( + "https://proxy.com/https/example.com%3A8080/path?query=value#fragment", + ReverseProxyHandler.replaceUrl(url1, format1) + ) + assertEquals( + "https://proxy.com/example.com%3A8080/path?query=value#fragment", + ReverseProxyHandler.replaceUrl(url1, format2) + ) + assertEquals( + "https://proxy.com/example.com%3A8080/path?query=value#fragment", + ReverseProxyHandler.replaceUrl(url1, format3) + ) + assertEquals( + "https://proxy.com/https/example.com%3A8080/path?query=value", + ReverseProxyHandler.replaceUrl(url2, format1) + ) + assertEquals( + "https://proxy.com/https/example.com%3A8080/path#fragment", + ReverseProxyHandler.replaceUrl(url3, format1) + ) + assertEquals( + "https://proxy.com/https/example.com%3A8080/path", + ReverseProxyHandler.replaceUrl(url4, format1) + ) + assertEquals( + "https://proxy.com/https/example.com/path", + ReverseProxyHandler.replaceUrl(url5, format1) + ) + } +} From 848783b574881f7c1f1cd66483270c68a33af868 Mon Sep 17 00:00:00 2001 From: itsMimao Date: Fri, 16 Jul 2021 11:28:12 +0800 Subject: [PATCH 027/137] localized strings for proxy settings --- .../twiderex/scenes/settings/MiscScene.kt | 48 ++++++++++--------- .../viewmodel/settings/MiscViewModel.kt | 3 +- app/src/main/res/values/strings.xml | 11 +++++ 3 files changed, 39 insertions(+), 23 deletions(-) diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/MiscScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/MiscScene.kt index a3974cc9f..2d73e49f0 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/MiscScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/MiscScene.kt @@ -20,7 +20,6 @@ */ package com.twidere.twiderex.scenes.settings -import android.annotation.SuppressLint import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope @@ -157,10 +156,8 @@ fun ColumnScope.ProxyPreference( val proxyUserName by viewModel.proxyUserName.observeAsState("") val proxyPassword by viewModel.proxyPassword.observeAsState("") - // todo PROXY localize ItemHeader { - - Text(text = "Proxy settings") + Text(text = stringResource(id = R.string.scene_settings_misc_proxy_title)) } switchItem( value = useProxy, @@ -168,26 +165,27 @@ fun ColumnScope.ProxyPreference( viewModel.setUseProxy(it) }, describe = { - Text(text = "Use proxy for all network requests") + Text(text = stringResource(id = R.string.scene_settings_misc_proxy_enable_description)) } ) { - Text(text = "Proxy") + Text(text = stringResource(id = R.string.scene_settings_misc_proxy_enable_title)) } ItemProxy( enable = useProxy, - title = "Proxy type", + title = stringResource(id = R.string.scene_settings_misc_proxy_type_title), content = proxyTypeValue(type = proxyType), onClick = { showProxyTypeDialog.value = true } ) + val serverTitle = stringResource(id = R.string.scene_settings_misc_proxy_server) ItemProxy( enable = useProxy, - title = "Server", + title = serverTitle, content = proxyServer, onClick = { - inputTitle.value = "server" + inputTitle.value = serverTitle inputValue.value = proxyServer inputChanged.value = { viewModel.setProxyServer(it) @@ -196,12 +194,13 @@ fun ColumnScope.ProxyPreference( } ) + val portTitle = stringResource(id = R.string.scene_settings_misc_proxy_port_title) ItemProxy( enable = useProxy, - title = "Port", + title = portTitle, content = proxyPort?.toString() ?: "", onClick = { - inputTitle.value = "Port" + inputTitle.value = portTitle inputValue.value = proxyPort?.toString() ?: "" inputChanged.value = { viewModel.setProxyPort(it) @@ -210,12 +209,13 @@ fun ColumnScope.ProxyPreference( } ) + val userNameTitle = stringResource(id = R.string.scene_settings_misc_proxy_username) ItemProxy( enable = useProxy, - title = "Username", + title = userNameTitle, content = proxyUserName, onClick = { - inputTitle.value = "UserName" + inputTitle.value = userNameTitle inputValue.value = proxyUserName inputChanged.value = { viewModel.setProxyUserName(it) @@ -223,12 +223,14 @@ fun ColumnScope.ProxyPreference( showProxyInputDialog.value = true } ) + + val passwordTitle = stringResource(id = R.string.scene_settings_misc_proxy_password) ItemProxy( enable = useProxy, - title = "Password", + title = passwordTitle, content = proxyPassword, onClick = { - inputTitle.value = "Password" + inputTitle.value = passwordTitle inputValue.value = proxyPassword inputChanged.value = { viewModel.setProxyPassword(it) @@ -241,9 +243,9 @@ fun ColumnScope.ProxyPreference( @Composable fun proxyTypeValue(type: MiscPreferences.ProxyType): String { return when (type) { - MiscPreferences.ProxyType.HTTP -> "HTTP" - MiscPreferences.ProxyType.REVERSE -> "Reverse" - MiscPreferences.ProxyType.UNRECOGNIZED -> "UnKnow" + MiscPreferences.ProxyType.HTTP -> stringResource(id = R.string.scene_settings_misc_proxy_type_http) + MiscPreferences.ProxyType.REVERSE -> stringResource(id = R.string.scene_settings_misc_proxy_type_reverse) + else -> stringResource(id = R.string.scene_settings_misc_proxy_type_http) } } @@ -402,18 +404,19 @@ fun NitterInformationDialog( ) } -@SuppressLint("UnrememberedMutableState") @Composable fun ProxyTypeSelectDialog( onDismissRequest: () -> Unit, onSelect: (value: MiscPreferences.ProxyType) -> Unit, value: MiscPreferences.ProxyType ) { - var selected by mutableStateOf(value) + var selected by remember { + mutableStateOf(value) + } AlertDialog( onDismissRequest = onDismissRequest, title = { - Text(text = "Proxy type") + Text(text = stringResource(id = R.string.scene_settings_misc_proxy_type_title)) }, text = { Column { @@ -474,7 +477,8 @@ fun ProxyInputDialog( TextField( value = input, onValueChange = { input = it }, - modifier = Modifier.focusRequester(focusRequester) + modifier = Modifier + .focusRequester(focusRequester) .fillMaxWidth(), colors = TextFieldDefaults.textFieldColors( backgroundColor = Color.Transparent diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/settings/MiscViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/settings/MiscViewModel.kt index 651028f1e..bdbcfc2db 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/settings/MiscViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/settings/MiscViewModel.kt @@ -24,6 +24,7 @@ import androidx.datastore.core.DataStore import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.twidere.twiderex.R import com.twidere.twiderex.notification.InAppNotification import com.twidere.twiderex.preferences.proto.MiscPreferences import dagger.assisted.AssistedInject @@ -131,7 +132,7 @@ class MiscViewModel @AssistedInject constructor( try { proxyPort.value = value.toInt() } catch (e: NumberFormatException) { - inAppNotification.show("Proxy server port must be numbers") + inAppNotification.show(R.string.scene_settings_misc_proxy_port_error) return } viewModelScope.launch { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7b9547808..fad5ff8a1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -316,6 +316,17 @@ Using Third-party data provider in - Twitter status threading Project URL + Proxy settings + Proxy + Use proxy for all network requests + Proxy type + HTTP + Reverse + Server + Port + Proxy server port must be numbers + Username + Password About License Next generation of Twidere for Android 5.0+. \nStill in early stage. From fd1f677478cee8b7a12126fe487254dcdee2546e Mon Sep 17 00:00:00 2001 From: Tlaster Date: Mon, 19 Jul 2021 19:24:42 +0800 Subject: [PATCH 028/137] migrate to kotlin dsl --- app/build.gradle | 229 ------------------ app/build.gradle.kts | 203 ++++++++++++++++ .../.gitignore | 0 .../build.gradle.kts | 2 +- .../main/kotlin/AssistedViewModelProcessor.kt | 0 ...ols.ksp.processing.SymbolProcessorProvider | 0 build.gradle | 42 ---- build.gradle.kts | 45 ++++ buildSrc/build.gradle.kts | 7 + buildSrc/src/main/kotlin/AndroidSdk.kt | 6 + buildSrc/src/main/kotlin/Dependencies.kt | 205 ++++++++++++++++ .../kotlin/DependencyHandlerExtensions.kt | 46 ++++ buildSrc/src/main/kotlin/Package.kt | 7 + gradle/libs.versions.toml | 202 --------------- services/build.gradle | 39 --- services/build.gradle.kts | 22 ++ settings.gradle => settings.gradle.kts | 4 +- versions.gradle | 14 -- 18 files changed, 543 insertions(+), 530 deletions(-) delete mode 100644 app/build.gradle create mode 100644 app/build.gradle.kts rename {assisted-processor => assistedProcessor}/.gitignore (100%) rename {assisted-processor => assistedProcessor}/build.gradle.kts (75%) rename {assisted-processor => assistedProcessor}/src/main/kotlin/AssistedViewModelProcessor.kt (100%) rename {assisted-processor => assistedProcessor}/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider (100%) delete mode 100644 build.gradle create mode 100644 build.gradle.kts create mode 100644 buildSrc/build.gradle.kts create mode 100644 buildSrc/src/main/kotlin/AndroidSdk.kt create mode 100644 buildSrc/src/main/kotlin/Dependencies.kt create mode 100644 buildSrc/src/main/kotlin/DependencyHandlerExtensions.kt create mode 100644 buildSrc/src/main/kotlin/Package.kt delete mode 100644 gradle/libs.versions.toml delete mode 100644 services/build.gradle create mode 100644 services/build.gradle.kts rename settings.gradle => settings.gradle.kts (76%) delete mode 100644 versions.gradle diff --git a/app/build.gradle b/app/build.gradle deleted file mode 100644 index 907aa5b0e..000000000 --- a/app/build.gradle +++ /dev/null @@ -1,229 +0,0 @@ -buildscript { - ext { - enableGoogleVariant = project.file("google-services.json").exists() - } - repositories { - google() - } - - dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlin" - classpath "com.google.dagger:hilt-android-gradle-plugin:$versions.hilt" - if (enableGoogleVariant) { - // START Non-FOSS component - classpath "com.google.gms:google-services:4.3.4" - classpath "com.google.firebase:firebase-crashlytics-gradle:2.4.1" - // END Non-FOSS component - } - } -} - -plugins { - id "com.android.application" - id "org.jetbrains.kotlin.android" - id "org.jetbrains.kotlin.kapt" - id "com.google.protobuf" version "0.8.14" - id "org.jetbrains.kotlin.plugin.serialization" version "1.5.10" - id "com.google.devtools.ksp" version "1.5.10-1.0.0-beta01" -} - -if (enableGoogleVariant) { - // START Non-FOSS component - apply plugin: "com.google.gms.google-services" - apply plugin: "com.google.firebase.crashlytics" - // END Non-FOSS component -} - -apply plugin: "dagger.hilt.android.plugin" - -android { - compileSdkVersion global.compileSdkVersion - buildToolsVersion global.buildToolsVersion - - defaultConfig { - applicationId "com.twidere.twiderex" - minSdkVersion global.minSdkVersion - targetSdkVersion global.targetSdkVersion - versionCode global.versionCode - versionName global.versionName - - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - - javaCompileOptions { - annotationProcessorOptions { - arguments += ["room.schemaLocation": "$projectDir/schemas".toString()] - } - } - def apiKeyProperties = rootProject.file("apiKey.properties") - def hasApiKeyProps = apiKeyProperties.exists() - if (hasApiKeyProps) { - Properties apiKeyProp = new Properties() - apiKeyProp.load(apiKeyProperties.newInputStream()) - buildConfigField "String", "CONSUMERKEY", apiKeyProp.get("ConsumerKey") - buildConfigField "String", "CONSUMERSECRET", apiKeyProp.get("ConsumerSecret") - } - - } - - lintOptions{ - disable "MissingTranslation" - } - - flavorDimensions "channel" - productFlavors { - if (enableGoogleVariant) { - // START Non-FOSS component - google { - dimension "channel" - } - // END Non-FOSS component - } - fdroid { - dimension "channel" - } - } - - def file = rootProject.file("signing.properties") - def hasSigningProps = file.exists() - - signingConfigs { - if (hasSigningProps) { - twidere { - Properties signingProp = new Properties() - signingProp.load(file.newInputStream()) - storeFile = rootProject.file(signingProp.get("storeFile")) - storePassword = (String) signingProp.get("storePassword") - keyAlias = (String) signingProp.get("keyAlias") - keyPassword = (String) signingProp.get("keyPassword") - } - } - } - - buildTypes { - debug { - if (hasSigningProps) { - signingConfig signingConfigs.twidere - } - } - release { - if (hasSigningProps) { - signingConfig signingConfigs.twidere - } - minifyEnabled false - proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" - } - } - sourceSets.each { - it.res.srcDirs += project.files("src/${it.name}/res-localized") - it.java.srcDirs += "src/${it.name}/kotlin" - } - sourceSets { - androidTest.assets.srcDirs += files("$projectDir/schemas".toString()) - } - compileOptions { - sourceCompatibility JavaVersion.VERSION_11 - targetCompatibility JavaVersion.VERSION_11 - } - kotlinOptions { - allWarningsAsErrors = true - freeCompilerArgs = [ - "-Xopt-in=kotlin.RequiresOptIn", - ] - jvmTarget = "11" - } - buildFeatures { - compose true - } - composeOptions { - kotlinCompilerExtensionVersion libs.versions.compose.get() - } - - packagingOptions { - exclude "META-INF/AL2.0" - exclude "META-INF/LGPL2.1" - exclude "DebugProbesKt.bin" - } -} - -protobuf { - protoc { - artifact = "com.google.protobuf:protoc:${libs.versions.protobuf.get()}" - } - generateProtoTasks { - all().each { task -> - task.builtins { - java { - option "lite" - } - } - } - } -} - -dependencies { - implementation libs.kotlinx.serialization.json - implementation libs.vectordrawable - - implementation projects.services - ksp projects.assistedProcessor - - implementation libs.bundles.compose - androidTestImplementation libs.compose.ui.test - - implementation libs.bundles.paging - implementation libs.bundles.activity - implementation libs.bundles.datastore - - implementation libs.bundles.hilt.base - kapt libs.bundles.hilt.compiler - - implementation libs.bundles.room - kapt libs.room.compiler - - implementation libs.bundles.lifecycle - - implementation libs.work.runtime.ktx - - implementation libs.startup - - implementation libs.coil - implementation libs.bundles.accompanist - - implementation libs.zoomable - implementation libs.nestedScrollView - implementation libs.swiper - implementation libs.placeholder - - implementation libs.twittertext - implementation libs.jsoup - - debugImplementation libs.leakcanary - - implementation libs.protobuf.javalite - - implementation libs.exoplayer - - implementation libs.constraintLayout - - implementation libs.exifinterface - - implementation libs.browser - - if (enableGoogleVariant) { - // START Non-FOSS component - googleImplementation platform("com.google.firebase:firebase-bom:26.1.0") - googleImplementation "com.google.firebase:firebase-analytics-ktx" - googleImplementation "com.google.firebase:firebase-crashlytics-ktx" - // END Non-FOSS component - } - - testImplementation "junit:junit:4.13.2" - androidTestImplementation libs.room.testing - testImplementation libs.bundles.mockito.test - testImplementation libs.android.test.core - androidTestImplementation libs.android.test.core - testImplementation libs.kotlinx.coroutines.test - androidTestImplementation libs.bundles.androidx.test -} - -apply from: "translate.gradle" \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 000000000..c48255246 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,203 @@ +import com.google.protobuf.gradle.builtins +import com.google.protobuf.gradle.generateProtoTasks +import com.google.protobuf.gradle.protobuf +import com.google.protobuf.gradle.protoc +import java.util.Properties + +buildscript { + repositories { + google() + } + + dependencies { + classpath("com.google.dagger:hilt-android-gradle-plugin:${Versions.hilt}") + + if (enableGoogleVariant) { + // START Non-FOSS component + classpath("com.google.gms:google-services:4.3.5") + classpath("com.google.firebase:firebase-crashlytics-gradle:2.5.2") + // END Non-FOSS component + } + } +} + +plugins { + id("com.android.application") + kotlin("android") + kotlin("kapt") + id("com.google.protobuf").version("0.8.17") + kotlin("plugin.serialization").version(Versions.kotlin) + id("com.google.devtools.ksp").version(Versions.ksp) +} + +if (enableGoogleVariant) { + // START Non-FOSS component + apply(plugin = "com.google.gms.google-services") + apply(plugin = "com.google.firebase.crashlytics") + // END Non-FOSS component +} +apply(plugin = "dagger.hilt.android.plugin") + +android { + compileSdk = AndroidSdk.compile + buildToolsVersion = AndroidSdk.buildTools + + defaultConfig { + applicationId = Package.id + minSdk = AndroidSdk.min + targetSdk = AndroidSdk.target + versionCode = Package.versionCode + versionName = Package.versionName + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + + javaCompileOptions { + annotationProcessorOptions { + argument("room.schemaLocation", "$projectDir/schemas") + } + } + val apiKeyProperties = rootProject.file("apiKey.properties") + val hasApiKeyProps = apiKeyProperties.exists() + if (hasApiKeyProps) { + val apiKeyProp = Properties() + apiKeyProp.load(apiKeyProperties.inputStream()) + buildConfigField("String", "CONSUMERKEY", apiKeyProp.getProperty("ConsumerKey")) + buildConfigField("String", "CONSUMERSECRET", apiKeyProp.getProperty("ConsumerSecret")) + } + } + + lint { + disable("MissingTranslation") + } + + flavorDimensions.add("channel") + productFlavors { + if (enableGoogleVariant) { + // START Non-FOSS component + create("google") { + dimension = "channel" + } + // END Non-FOSS component + } + create("fdroid") { + dimension = "channel" + } + } + + val file = rootProject.file("signing.properties") + val hasSigningProps = file.exists() + + signingConfigs { + if (hasSigningProps) { + create("twidere") { + val signingProp = Properties() + signingProp.load(file.inputStream()) + storeFile = rootProject.file(signingProp.getProperty("storeFile")) + storePassword = signingProp.getProperty("storePassword") + keyAlias = signingProp.getProperty("keyAlias") + keyPassword = signingProp.getProperty("keyPassword") + } + } + } + + buildTypes { + debug { + if (hasSigningProps) { + signingConfig = signingConfigs.getByName("twidere") + } + } + release { + if (hasSigningProps) { + signingConfig = signingConfigs.getByName("twidere") + } + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + sourceSets.forEach { + it.res { + srcDirs(project.files("src/${it.name}/res-localized")) + } + it.java { + srcDirs("src/${it.name}/kotlin") + } + } + sourceSets { + findByName("androidTest")?.let { + it.assets { + srcDirs(files("$projectDir/schemas")) + } + } + } + compileOptions { + sourceCompatibility = Lang.java + targetCompatibility = Lang.java + } + buildFeatures { + compose = true + } + composeOptions { + kotlinCompilerExtensionVersion = Versions.compose + } + + packagingOptions { + resources { + excludes.addAll( + listOf( + "META-INF/AL2.0", + "META-INF/LGPL2.1", + "DebugProbesKt.bin", + ) + ) + } + } +} + +protobuf { + protoc { + artifact = "com.google.protobuf:protoc:${Versions.protobuf}" + } + generateProtoTasks { + all().forEach { + it.builtins { + create("java") { + option("lite") + } + } + } + } +} + +// TODO: workaround for https://github.com/google/ksp/issues/518 +evaluationDependsOn(":assistedProcessor") + +dependencies { + android() + kotlinSerialization() + kotlinCoroutines() + implementation(projects.services) + ksp(projects.assistedProcessor) + compose() + paging() + datastore() + hilt() + accompanist() + widget() + misc() + + if (enableGoogleVariant) { + // START Non-FOSS component + val googleImplementation by configurations + googleImplementation(platform("com.google.firebase:firebase-bom:26.1.0")) + googleImplementation("com.google.firebase:firebase-analytics-ktx") + googleImplementation("com.google.firebase:firebase-crashlytics-ktx") + // END Non-FOSS component + } + + junit4() + mockito() + androidTest() +} diff --git a/assisted-processor/.gitignore b/assistedProcessor/.gitignore similarity index 100% rename from assisted-processor/.gitignore rename to assistedProcessor/.gitignore diff --git a/assisted-processor/build.gradle.kts b/assistedProcessor/build.gradle.kts similarity index 75% rename from assisted-processor/build.gradle.kts rename to assistedProcessor/build.gradle.kts index dc267cc78..17926e017 100644 --- a/assisted-processor/build.gradle.kts +++ b/assistedProcessor/build.gradle.kts @@ -8,7 +8,7 @@ repositories { } dependencies { - implementation(libs.ksp.symbol.processing.api) + kspApi() } sourceSets.main { diff --git a/assisted-processor/src/main/kotlin/AssistedViewModelProcessor.kt b/assistedProcessor/src/main/kotlin/AssistedViewModelProcessor.kt similarity index 100% rename from assisted-processor/src/main/kotlin/AssistedViewModelProcessor.kt rename to assistedProcessor/src/main/kotlin/AssistedViewModelProcessor.kt diff --git a/assisted-processor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider b/assistedProcessor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider similarity index 100% rename from assisted-processor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider rename to assistedProcessor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider diff --git a/build.gradle b/build.gradle deleted file mode 100644 index 7099fb00f..000000000 --- a/build.gradle +++ /dev/null @@ -1,42 +0,0 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. -buildscript { - apply from: "versions.gradle" - repositories { - google() - } - dependencies { - classpath 'com.android.tools.build:gradle:7.0.0-beta05' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlin" - } -} - -plugins { - id "com.diffplug.spotless" version "5.12.5" -} - -subprojects { - repositories { - google() - mavenCentral() - maven { url "https://jitpack.io" } - } - - apply plugin: "com.diffplug.spotless" - spotless { - kotlin { - target "**/*.kt" - targetExclude("$buildDir/**/*.kt") - targetExclude("bin/**/*.kt") - - ktlint(libs.versions.ktlint.get())//.userData([android: "true"]) - licenseHeaderFile rootProject.file("spotless/license.kt") - } - java { - target "**/*.java" - targetExclude("$buildDir/**/*.java") - targetExclude("bin/**/*.java") - - licenseHeaderFile rootProject.file("spotless/license.kt") - } - } -} diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 000000000..bc83d4afd --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,45 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +plugins { + id("com.diffplug.spotless").version(Versions.spotless) +} +buildscript { + repositories { + google() + } + dependencies { + classpath(kotlin("gradle-plugin", version = Versions.kotlin)) + classpath("com.android.tools.build:gradle:${Versions.agp}") + } +} + +allprojects { + configRepository() + + tasks.withType { + kotlinOptions { + jvmTarget = Lang.jvmTarget + allWarningsAsErrors = true + freeCompilerArgs = listOf( + "-Xopt-in=kotlin.RequiresOptIn", + ) + } + } + apply(plugin = "com.diffplug.spotless") + spotless { + kotlin { + target("**/*.kt") + targetExclude("$buildDir/**/*.kt", "bin/**/*.kt") + ktlint(Versions.ktlint) + // licenseHeaderFile(rootProject.file("spotless/license.kt")) + } + kotlinGradle { + target("*.gradle.kts") + ktlint(Versions.ktlint) + } + java { + target("**/*.java") + targetExclude("$buildDir/**/*.java", "bin/**/*.java") + // licenseHeaderFile(rootProject.file("spotless/license.kt")) + } + } +} diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts new file mode 100644 index 000000000..b22ed732f --- /dev/null +++ b/buildSrc/build.gradle.kts @@ -0,0 +1,7 @@ +plugins { + `kotlin-dsl` +} + +repositories { + mavenCentral() +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/AndroidSdk.kt b/buildSrc/src/main/kotlin/AndroidSdk.kt new file mode 100644 index 000000000..6bd1461cc --- /dev/null +++ b/buildSrc/src/main/kotlin/AndroidSdk.kt @@ -0,0 +1,6 @@ +object AndroidSdk { + const val min = 21 + const val compile = 31 + const val target = compile + const val buildTools = "31.0.0" +} diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt new file mode 100644 index 000000000..5551da382 --- /dev/null +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -0,0 +1,205 @@ + +import org.gradle.api.JavaVersion +import org.gradle.api.Project +import org.gradle.kotlin.dsl.DependencyHandlerScope +import org.gradle.kotlin.dsl.maven +import org.gradle.kotlin.dsl.repositories + +fun Project.configRepository() { + repositories { + google() + mavenCentral() + maven("https://jitpack.io") + } +} + +val Project.enableGoogleVariant: Boolean + get() = file("google-services.json").exists() + +object Lang { + const val jvmTarget = "11" + val java = JavaVersion.VERSION_11 +} + +object Versions { + const val kotlin = "1.5.10" + + object Kotlin { + const val lang = "1.5.10" + const val coroutines = "1.5.0" + const val serialization = "1.2.1" + } + + const val ksp = "1.5.10-1.0.0-beta02" + const val agp = "7.0.0-beta05" + const val spotless = "5.12.5" + const val ktlint = "0.41.0" + const val hilt = "2.37" + const val okhttp = "4.9.1" + const val retrofit2 = "2.9.0" + const val hson = "0.1.4" + const val compose = "1.0.0-rc02" + const val constraintLayout = "1.0.0-alpha07" + const val paging = "3.1.0-alpha02" + const val paging_compose = "1.0.0-alpha10" + const val activity = "1.3.0-rc02" + const val datastore = "1.0.0-rc01" + const val androidx_hilt = "1.0.0" + const val room = "2.4.0-alpha03" + const val lifecycle = "2.4.0-alpha02" + const val lifecycle_compose = "1.0.0-alpha07" + const val work = "2.7.0-alpha04" + const val placeholder = "0.7.0" + const val zoomable = "1.0.1" + const val swiper = "0.6.0" + const val nestedScrollView = "0.7.0" + const val startup = "1.1.0-alpha01" + const val coil = "1.3.0" + const val accompanist = "0.14.0" + const val androidx_exifinterface = "1.3.2" + const val exoplayer = "2.14.1" + const val browser = "1.3.0" + const val protobuf = "3.17.3" + const val androidx_test = "1.4.0" + const val extJUnitVersion = "1.1.3-rc01" + const val espressoVersion = "3.4.0-rc01" +} + +fun DependencyHandlerScope.kotlinCoroutines() { + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core", Versions.Kotlin.coroutines) + testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test", Versions.Kotlin.coroutines) +} + +fun DependencyHandlerScope.kotlinSerialization() { + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json", Versions.Kotlin.serialization) +} + +fun DependencyHandlerScope.retrofit() { + api("com.squareup.retrofit2:retrofit", Versions.retrofit2) + implementation("com.squareup.retrofit2:converter-scalars", Versions.retrofit2) + implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter", "0.8.0") +} + +fun DependencyHandlerScope.okhttp() { + implementation("com.squareup.okhttp3:okhttp", Versions.okhttp) + implementation("com.squareup.okhttp3:logging-interceptor", Versions.okhttp) +} + +fun DependencyHandlerScope.junit5() { + testImplementation("org.jetbrains.kotlin:kotlin-test-junit5") + testImplementation("org.junit.jupiter:junit-jupiter-api:5.7.0") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.7.0") +} + +fun DependencyHandlerScope.junit4() { + testImplementation("junit:junit:4.13.2") +} + +fun DependencyHandlerScope.hson() { + implementation("com.github.Tlaster:Hson", Versions.hson) +} + +fun DependencyHandlerScope.kspApi() { + implementation("com.google.devtools.ksp:symbol-processing-api", Versions.ksp) +} + +fun DependencyHandlerScope.compose() { + implementation("androidx.compose.ui:ui", Versions.compose) + implementation("androidx.compose.ui:ui-tooling", Versions.compose) + androidTestImplementation("androidx.compose.ui:ui-test", Versions.compose) + implementation("androidx.compose.foundation:foundation", Versions.compose) + implementation("androidx.compose.animation:animation", Versions.compose) + implementation("androidx.compose.material:material", Versions.compose) + implementation("androidx.compose.material:material-icons-core", Versions.compose) + implementation("androidx.compose.material:material-icons-extended", Versions.compose) + implementation("androidx.compose.runtime:runtime-livedata", Versions.compose) + implementation("androidx.constraintlayout:constraintlayout-compose", Versions.constraintLayout) +} + +fun DependencyHandlerScope.paging() { + implementation("androidx.paging:paging-common", Versions.paging) + // implementation("androidx.paging:paging-compose", Versions.paging_compose) +} + +fun DependencyHandlerScope.activity() { + implementation("androidx.activity:activity-ktx", Versions.activity) + implementation("androidx.activity:activity-compose", Versions.activity) +} + +fun DependencyHandlerScope.datastore() { + implementation("androidx.datastore:datastore", Versions.datastore) + implementation("androidx.datastore:datastore-preferences", Versions.datastore) +} + +fun DependencyHandlerScope.hilt() { + implementation("com.google.dagger:hilt-android", Versions.hilt) + kapt("com.google.dagger:hilt-android-compiler", Versions.hilt) + implementation("androidx.hilt:hilt-work", Versions.androidx_hilt) + kapt("androidx.hilt:hilt-compiler", Versions.androidx_hilt) +} + +fun DependencyHandlerScope.room() { + implementation("androidx.room:room-runtime", Versions.room) + implementation("androidx.room:room-ktx", Versions.room) + kapt("androidx.room:room-compiler", Versions.room) + androidTestImplementation("androidx.room:room-testing", Versions.room) +} + +fun DependencyHandlerScope.lifecycle() { + implementation("androidx.lifecycle:lifecycle-runtime-ktx", Versions.lifecycle) + implementation("androidx.lifecycle:lifecycle-viewmodel-ktx", Versions.lifecycle) + implementation("androidx.lifecycle:lifecycle-livedata-ktx", Versions.lifecycle) + // implementation("androidx.lifecycle:lifecycle-viewmodel-savedstate", Versions.lifecycle) + implementation("androidx.lifecycle:lifecycle-common-java8", Versions.lifecycle) + // implementation("androidx.lifecycle:lifecycle-viewmodel-compose", Versions.lifecycle_compose) +} + +fun DependencyHandlerScope.android() { + work() + room() + lifecycle() + activity() + implementation("androidx.startup:startup-runtime", Versions.startup) + implementation("io.coil-kt:coil-compose", Versions.coil) + implementation("androidx.vectordrawable:vectordrawable:1.1.0") + implementation("androidx.exifinterface:exifinterface", Versions.androidx_exifinterface) + implementation("com.google.android.exoplayer:exoplayer", Versions.exoplayer) + implementation("androidx.browser:browser", Versions.browser) + implementation("com.squareup.leakcanary:leakcanary-android:2.7") +} + +fun DependencyHandlerScope.accompanist() { + implementation("com.google.accompanist:accompanist-insets", Versions.accompanist) + implementation("com.google.accompanist:accompanist-pager", Versions.accompanist) + implementation("com.google.accompanist:accompanist-pager-indicators", Versions.accompanist) +} + +fun DependencyHandlerScope.work() { + implementation("androidx.work:work-runtime-ktx", Versions.work) +} + +fun DependencyHandlerScope.widget() { + implementation("com.mxalbert.zoomable:zoomable", Versions.zoomable) + implementation("com.github.Tlaster:NestedScrollView", Versions.nestedScrollView) + implementation("com.github.Tlaster:Swiper", Versions.swiper) + implementation("com.github.Tlaster:Placeholder", Versions.placeholder) +} + +fun DependencyHandlerScope.misc() { + implementation("com.twitter.twittertext:twitter-text:3.1.0") + implementation("org.jsoup:jsoup:1.13.1") + implementation("com.google.protobuf:protobuf-javalite", Versions.protobuf) +} + +fun DependencyHandlerScope.mockito() { + testImplementation("org.mockito:mockito-core:3.11.2") + testImplementation("org.mockito.kotlin:mockito-kotlin:3.2.0") +} + +fun DependencyHandlerScope.androidTest() { + androidTestImplementation("androidx.arch.core:core-testing:2.1.0") + implementation("androidx.test:core", Versions.androidx_test) + implementation("androidx.test:runner", Versions.androidx_test) + implementation("androidx.test.ext:junit", Versions.extJUnitVersion) + implementation("androidx.test.espresso:espresso-core", Versions.espressoVersion) +} diff --git a/buildSrc/src/main/kotlin/DependencyHandlerExtensions.kt b/buildSrc/src/main/kotlin/DependencyHandlerExtensions.kt new file mode 100644 index 000000000..4004b66b9 --- /dev/null +++ b/buildSrc/src/main/kotlin/DependencyHandlerExtensions.kt @@ -0,0 +1,46 @@ +import org.gradle.api.artifacts.dsl.DependencyHandler + +internal fun DependencyHandler.add( + configurationName: String, + name: String, + version: String? = null, +) = add( + configurationName, + "$name${ + if (version != null) { + ":$version" + } else { + "" + } + }" +) + +internal fun DependencyHandler.testRuntimeOnly( + name: String, + version: String? = null, +) = add("testRuntimeOnly", name, version) + +internal fun DependencyHandler.api( + name: String, + version: String? = null, +) = add("api", name, version) + +internal fun DependencyHandler.implementation( + name: String, + version: String? = null, +) = add("implementation", name, version) + +internal fun DependencyHandler.kapt( + name: String, + version: String? = null, +) = add("kapt", name, version) + +internal fun DependencyHandler.testImplementation( + name: String, + version: String? = null, +) = add("testImplementation", name, version) + +internal fun DependencyHandler.androidTestImplementation( + name: String, + version: String? = null, +) = add("androidTestImplementation", name, version) diff --git a/buildSrc/src/main/kotlin/Package.kt b/buildSrc/src/main/kotlin/Package.kt new file mode 100644 index 000000000..c9c859a22 --- /dev/null +++ b/buildSrc/src/main/kotlin/Package.kt @@ -0,0 +1,7 @@ +object Package { + const val group = "com.twidere" + const val name = "TwidereX" + const val id = "$group.twiderex" + const val versionName = "1.4.0-beta02" + const val versionCode = 48 +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml deleted file mode 100644 index 7800d8f21..000000000 --- a/gradle/libs.versions.toml +++ /dev/null @@ -1,202 +0,0 @@ -[versions] -# kotlin = "1.5.10" -ksp = "1.5.10-1.0.0-beta02" -coil = "1.3.0" -compose = "1.0.0-rc02" -androidx_activity = "1.3.0-rc02" -accompanist = "0.14.0" -constraint_layout = "1.0.0-alpha07" -retrofit2 = "2.9.0" -hilt = "2.37" -androidx_hilt = "1.0.0" -room = "2.4.0-alpha03" -lifecycle_base = "2.4.0-alpha02" -lifecycle_compose = "1.0.0-alpha07" -okhttp = "4.9.1" -kotlinx_serialization = "1.2.1" -paging_base = "3.1.0-alpha02" -paging_compose = "1.0.0-alpha10" -ktlint = "0.41.0" -protobuf = "3.17.3" -work = "2.7.0-alpha04" -datastore = "1.0.0-rc01" -exoplayer = "2.14.1" -androidx_test = "1.4.0" -extJUnitVersion = "1.1.3-rc01" -espressoVersion = "3.4.0-rc01" -startup = "1.1.0-alpha01" -kotlin-coroutines = "1.5.0" -androidx_exifinterface = "1.3.2" -placeholder = "0.7.0" -zoomable = "1.0.1" -swiper = "0.6.0" -nestedScrollView = "0.7.0" -hson = "0.1.4" -browser = "1.3.0" - -[libraries] -# androidx test -androidx-test-core = { module = "androidx.test:core", version.ref = "androidx_test" } -androidx-test-runner = { module = "androidx.test:runner", version.ref = "androidx_test" } -androidx-test-ext-junit = { module = "androidx.test.ext:junit", version.ref = "extJUnitVersion" } -androidx-test-espresso = { module = "androidx.test.espresso:espresso-core", version.ref = "espressoVersion" } - -mockito-core-test = 'org.mockito:mockito-core:3.11.2' -mockito-kotlin-test = 'org.mockito.kotlin:mockito-kotlin:3.2.0' -android-test-core = 'androidx.arch.core:core-testing:2.1.0' - -# Compose -compose-ui-base = { module = "androidx.compose.ui:ui", version.ref = "compose" } -compose-ui-test = { module = "androidx.compose.ui:ui-test", version.ref = "compose" } -compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose" } -compose-foundation = { module = "androidx.compose.foundation:foundation", version.ref = "compose" } -compose-animation = { module = "androidx.compose.animation:animation", version.ref = "compose" } -compose-material-base = { module = "androidx.compose.material:material", version.ref = "compose" } -compose-material-icons-core = { module = "androidx.compose.material:material-icons-core", version.ref = "compose" } -compose-material-icons-extended = { module = "androidx.compose.material:material-icons-extended", version.ref = "compose" } -compose-runtime_livedata = { module = "androidx.compose.runtime:runtime-livedata", version.ref = "compose" } -constraintLayout = { module = "androidx.constraintlayout:constraintlayout-compose", version.ref = "constraint_layout" } - -# Paging -paging-runtime = { module = "androidx.paging:paging-runtime-ktx", version.ref = "paging_base" } -paging-compose = { module = "androidx.paging:paging-compose", version.ref = "paging_compose" } - -# Lifecycle -lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle_base" } -lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycle_base" } -lifecycle-livedata-ktx = { module = "androidx.lifecycle:lifecycle-livedata-ktx", version.ref = "lifecycle_base" } -lifecycle-viewmodel-savedstate = { module = "androidx.lifecycle:lifecycle-viewmodel-savedstate", version.ref = "lifecycle_base" } -lifecycle-common-java8 = { module = "androidx.lifecycle:lifecycle-common-java8", version.ref = "lifecycle_base" } -lifecycle-viewmodel-compose_compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycle_compose" } - -# Room -room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" } -room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" } -room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" } -room-testing = { module = "androidx.room:room-testing", version.ref = "room" } - -# Hilt -hilt-android-base = { module = "com.google.dagger:hilt-android", version.ref = "hilt" } -hilt-work = { module = "androidx.hilt:hilt-work", version.ref = "androidx_hilt" } -hilt-android-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hilt" } -hilt-compiler = { module = "androidx.hilt:hilt-compiler", version.ref = "androidx_hilt" } - -# Accompanist -accompanist-insets = { module = "com.google.accompanist:accompanist-insets", version.ref = "accompanist" } -accompanist-pager-base = { module = "com.google.accompanist:accompanist-pager", version.ref = "accompanist" } -accompanist-pager-indicators = { module = "com.google.accompanist:accompanist-pager-indicators", version.ref = "accompanist" } - -# AndroidX Activity -activity-ktx = { module = "androidx.activity:activity-ktx", version.ref = "androidx_activity" } -activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx_activity" } - -# Androidx exifinterface -exifinterface = { module = "androidx.exifinterface:exifinterface", version.ref = "androidx_exifinterface" } - -# DataStore -datastore-main = { module = "androidx.datastore:datastore", version.ref = "datastore" } -datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastore" } - -# WorkManager -work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "work" } - -# Kotlinx Serialization -kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx_serialization" } - -# Kotlinx Coroutines -kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlin-coroutines" } -kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlin-coroutines" } - -# Retrofit -retrofit-base = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit2" } -retrofit-converter-scalars = { module = "com.squareup.retrofit2:converter-scalars", version.ref = "retrofit2" } -retrofit2-kotlinx-serialization-converter = "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:0.8.0" - -# OkHttp -okhttp-base = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" } -okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" } - -# KSP -ksp-symbol-processing-api = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" } - -coil = { module = "io.coil-kt:coil-compose", version.ref = "coil" } -zoomable = { module = "com.mxalbert.zoomable:zoomable", version.ref = "zoomable" } -nestedScrollView = { module = "com.github.Tlaster:NestedScrollView", version.ref = "nestedScrollView" } -swiper = { module = "com.github.Tlaster:Swiper", version.ref = "swiper" } -placeholder = { module = "com.github.Tlaster:Placeholder", version.ref = "placeholder" } -twittertext = "com.twitter.twittertext:twitter-text:3.1.0" -jsoup = "org.jsoup:jsoup:1.13.1" -leakcanary = "com.squareup.leakcanary:leakcanary-android:2.7" -protobuf-javalite = { module = "com.google.protobuf:protobuf-javalite", version.ref = "protobuf" } -exoplayer = { module = "com.google.android.exoplayer:exoplayer", version.ref = "exoplayer" } -startup = { module = "androidx.startup:startup-runtime", version.ref = "startup" } -vectordrawable = "androidx.vectordrawable:vectordrawable:1.1.0" -hson = { module = "com.github.Tlaster:Hson", version.ref = "hson" } -browser = { module = "androidx.browser:browser", version.ref = "browser" } - -[bundles] -androidx-test = [ - "androidx-test-core", - "androidx-test-runner", - "androidx-test-ext-junit", - "androidx-test-espresso", -] -mockito-test = [ - "mockito-core-test", - "mockito-kotlin-test" -] -compose = [ - "compose-ui-base", - "compose-ui-tooling", - "compose-foundation", - "compose-animation", - "compose-material-base", - "compose-material-icons-core", - "compose-material-icons-extended", - "compose-runtime_livedata", -] -paging = [ - "paging-runtime", - # "paging-compose", -] -lifecycle = [ - "lifecycle-runtime-ktx", - "lifecycle-viewmodel-ktx", - "lifecycle-livedata-ktx", - "lifecycle-viewmodel-savedstate", - "lifecycle-common-java8", - "lifecycle-viewmodel-compose_compose", -] -room = [ - "room-runtime", - "room-ktx", -] -hilt-base = [ - "hilt-android-base", - "hilt-work", -] -hilt-compiler = [ - "hilt-android-compiler", - "hilt-compiler", -] -accompanist = [ - "accompanist-insets", - "accompanist-pager-base", - "accompanist-pager-indicators", -] -activity = [ - "activity-ktx", - "activity-compose", -] -okhttp = [ - "okhttp-base", - "okhttp-logging", -] -retrofit = [ - "retrofit-base", - "retrofit-converter-scalars", -] -datastore = [ - "datastore-main", - "datastore-preferences" -] \ No newline at end of file diff --git a/services/build.gradle b/services/build.gradle deleted file mode 100644 index af3022be8..000000000 --- a/services/build.gradle +++ /dev/null @@ -1,39 +0,0 @@ -plugins { - id "org.jetbrains.kotlin.jvm" - id "org.jetbrains.kotlin.plugin.serialization" version "1.5.10" -} - -java { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 -} - -tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { - kotlinOptions { - allWarningsAsErrors = true - freeCompilerArgs = [ - "-Xopt-in=kotlin.RequiresOptIn", - ] - jvmTarget = "11" - useIR = true - } -} - -test { - useJUnitPlatform() -} - -dependencies { - implementation libs.kotlinx.coroutines.core - implementation libs.kotlinx.serialization.json - implementation libs.hson - - api libs.bundles.retrofit - implementation libs.retrofit2.kotlinx.serialization.converter - - implementation libs.bundles.okhttp - - testImplementation "org.jetbrains.kotlin:kotlin-test-junit5" - testImplementation "org.junit.jupiter:junit-jupiter-api:5.7.0" - testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.7.0" -} \ No newline at end of file diff --git a/services/build.gradle.kts b/services/build.gradle.kts new file mode 100644 index 000000000..e1f9b2d82 --- /dev/null +++ b/services/build.gradle.kts @@ -0,0 +1,22 @@ +plugins { + kotlin("jvm") + kotlin("plugin.serialization").version(Versions.kotlin) +} + +java { + sourceCompatibility = Lang.java + targetCompatibility = Lang.java +} + +tasks.test { + useJUnitPlatform() +} + +dependencies { + kotlinCoroutines() + kotlinSerialization() + hson() + retrofit() + okhttp() + junit5() +} diff --git a/settings.gradle b/settings.gradle.kts similarity index 76% rename from settings.gradle rename to settings.gradle.kts index 87aec09c4..a5693a1fa 100644 --- a/settings.gradle +++ b/settings.gradle.kts @@ -6,9 +6,7 @@ pluginManagement { } rootProject.name = "TwidereX" -include ":app" -include ":services" -include ":assisted-processor" +include(":app", ":services", ":assistedProcessor") enableFeaturePreview("VERSION_CATALOGS") enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") diff --git a/versions.gradle b/versions.gradle deleted file mode 100644 index 19978bd09..000000000 --- a/versions.gradle +++ /dev/null @@ -1,14 +0,0 @@ -ext { - versions = [ - kotlin: "1.5.10", - hilt : "2.37", - ] - global = [ - compileSdkVersion: 31, - buildToolsVersion: "31.0.0", - minSdkVersion : 21, - targetSdkVersion : 31, - versionCode : 48, - versionName : "1.4.0-beta02" - ] -} \ No newline at end of file From 4c68fb63cdbcc62f9016891b04944b8d66867857 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Mon, 19 Jul 2021 20:05:16 +0800 Subject: [PATCH 029/137] fix build --- buildSrc/src/main/kotlin/Dependencies.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index 5551da382..5a4881947 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -151,7 +151,7 @@ fun DependencyHandlerScope.lifecycle() { implementation("androidx.lifecycle:lifecycle-livedata-ktx", Versions.lifecycle) // implementation("androidx.lifecycle:lifecycle-viewmodel-savedstate", Versions.lifecycle) implementation("androidx.lifecycle:lifecycle-common-java8", Versions.lifecycle) - // implementation("androidx.lifecycle:lifecycle-viewmodel-compose", Versions.lifecycle_compose) + implementation("androidx.lifecycle:lifecycle-viewmodel-compose", Versions.lifecycle_compose) } fun DependencyHandlerScope.android() { @@ -197,9 +197,9 @@ fun DependencyHandlerScope.mockito() { } fun DependencyHandlerScope.androidTest() { - androidTestImplementation("androidx.arch.core:core-testing:2.1.0") - implementation("androidx.test:core", Versions.androidx_test) - implementation("androidx.test:runner", Versions.androidx_test) - implementation("androidx.test.ext:junit", Versions.extJUnitVersion) - implementation("androidx.test.espresso:espresso-core", Versions.espressoVersion) + testImplementation("androidx.arch.core:core-testing:2.1.0") + androidTestImplementation("androidx.test:core", Versions.androidx_test) + androidTestImplementation("androidx.test:runner", Versions.androidx_test) + androidTestImplementation("androidx.test.ext:junit", Versions.extJUnitVersion) + androidTestImplementation("androidx.test.espresso:espresso-core", Versions.espressoVersion) } From 0e4ab8e179003b3efb001641eb66a44417c3b69e Mon Sep 17 00:00:00 2001 From: Tlaster Date: Tue, 20 Jul 2021 11:39:40 +0800 Subject: [PATCH 030/137] fix build --- buildSrc/src/main/kotlin/Dependencies.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index 5a4881947..9a6c50fe7 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -198,6 +198,7 @@ fun DependencyHandlerScope.mockito() { fun DependencyHandlerScope.androidTest() { testImplementation("androidx.arch.core:core-testing:2.1.0") + androidTestImplementation("androidx.arch.core:core-testing:2.1.0") androidTestImplementation("androidx.test:core", Versions.androidx_test) androidTestImplementation("androidx.test:runner", Versions.androidx_test) androidTestImplementation("androidx.test.ext:junit", Versions.extJUnitVersion) From 3412b6053caef18025adcb1819a1a88a652be149 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Tue, 20 Jul 2021 13:39:13 +0800 Subject: [PATCH 031/137] fix test --- .../lists/ListsModifyViewModelTest.kt | 58 +++++++++++++------ 1 file changed, 39 insertions(+), 19 deletions(-) diff --git a/app/src/test/java/com/twidere/twiderex/viewmodel/lists/ListsModifyViewModelTest.kt b/app/src/test/java/com/twidere/twiderex/viewmodel/lists/ListsModifyViewModelTest.kt index b77c4f0ca..b6d5a2755 100644 --- a/app/src/test/java/com/twidere/twiderex/viewmodel/lists/ListsModifyViewModelTest.kt +++ b/app/src/test/java/com/twidere/twiderex/viewmodel/lists/ListsModifyViewModelTest.kt @@ -35,11 +35,13 @@ import kotlinx.coroutines.async import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext import org.junit.Assert import org.junit.Rule import org.junit.Test import org.mockito.Mock import org.mockito.kotlin.any +import org.mockito.kotlin.doReturn import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.mockito.kotlin.whenever @@ -72,7 +74,11 @@ class ListsModifyViewModelTest : ViewModelTestBase() { fun updateList_successExpectTrue(): Unit = runBlocking(Dispatchers.Main) { verifySuccessAndLoadingBefore(mockLoadingObserver, mockSuccessObserver) async { - modifyViewModel.editList(listId = "123", title = "title", private = false) { success, _ -> + modifyViewModel.editList( + listId = "123", + title = "title", + private = false + ) { success, _ -> mockSuccessObserver.onChanged(success) } }.await() @@ -84,7 +90,11 @@ class ListsModifyViewModelTest : ViewModelTestBase() { verifySuccessAndLoadingBefore(mockLoadingObserver, mockSuccessObserver) Assert.assertNull(errorNotification) async { - modifyViewModel.editList(listId = "error", title = "name", private = false) { success, _ -> + modifyViewModel.editList( + listId = "error", + title = "name", + private = false + ) { success, _ -> mockSuccessObserver.onChanged(success) } }.await() @@ -152,17 +162,18 @@ class ListsModifyViewModelTest : ViewModelTestBase() { } @Test - fun unsubscribeList_failedExpectFalseAndShowNotification(): Unit = runBlocking(Dispatchers.Main) { - verifySuccessAndLoadingBefore(mockLoadingObserver, mockSuccessObserver) - Assert.assertNull(errorNotification) - async { - modifyViewModel.unsubscribeList(MicroBlogKey.twitter("error")) { success, _ -> - mockSuccessObserver.onChanged(success) - } - }.await() - verifySuccessAndLoadingAfter(mockLoadingObserver, mockSuccessObserver, false) - Assert.assertNotNull(errorNotification) - } + fun unsubscribeList_failedExpectFalseAndShowNotification(): Unit = + runBlocking(Dispatchers.Main) { + verifySuccessAndLoadingBefore(mockLoadingObserver, mockSuccessObserver) + Assert.assertNull(errorNotification) + async { + modifyViewModel.unsubscribeList(MicroBlogKey.twitter("error")) { success, _ -> + mockSuccessObserver.onChanged(success) + } + }.await() + verifySuccessAndLoadingAfter(mockLoadingObserver, mockSuccessObserver, false) + Assert.assertNotNull(errorNotification) + } override fun setUp() { super.setUp() @@ -177,23 +188,32 @@ class ListsModifyViewModelTest : ViewModelTestBase() { errorNotification = it.getArgument(0) as NotificationEvent Unit } - whenever(mockAccount.service).thenReturn(MockCenter.mockListsService()) - whenever(mockAccount.accountKey).thenReturn(MicroBlogKey.twitter("123")) + doReturn(MockCenter.mockListsService()).whenever(mockAccount).service + doReturn(MicroBlogKey.twitter("123")).whenever(mockAccount).accountKey errorNotification = null mockSuccessObserver.onChanged(false) scope.launch { - modifyViewModel.loading.collect { - mockLoadingObserver.onChanged(it) + withContext(Dispatchers.Main) { + modifyViewModel.loading.collect { + mockLoadingObserver.onChanged(it) + } } } } - private fun verifySuccessAndLoadingBefore(loadingObserver: Observer, successObserver: Observer) { + private fun verifySuccessAndLoadingBefore( + loadingObserver: Observer, + successObserver: Observer + ) { verify(loadingObserver, times(1)).onChanged(false) verify(successObserver).onChanged(false) } - private fun verifySuccessAndLoadingAfter(loadingObserver: Observer, successObserver: Observer, success: Boolean) { + private fun verifySuccessAndLoadingAfter( + loadingObserver: Observer, + successObserver: Observer, + success: Boolean + ) { verify(loadingObserver, times(1)).onChanged(true) verify(loadingObserver, times(1)).onChanged(false) verify(successObserver, if (success) times(1) else times(2)).onChanged(success) From c9b5a06f4454ead7b2d21d60ed40d611e4774251 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Tue, 20 Jul 2021 14:58:36 +0800 Subject: [PATCH 032/137] fix test --- .../lists/ListsModifyViewModelTest.kt | 59 +++++++++++-------- 1 file changed, 33 insertions(+), 26 deletions(-) diff --git a/app/src/test/java/com/twidere/twiderex/viewmodel/lists/ListsModifyViewModelTest.kt b/app/src/test/java/com/twidere/twiderex/viewmodel/lists/ListsModifyViewModelTest.kt index b6d5a2755..8e3a20e25 100644 --- a/app/src/test/java/com/twidere/twiderex/viewmodel/lists/ListsModifyViewModelTest.kt +++ b/app/src/test/java/com/twidere/twiderex/viewmodel/lists/ListsModifyViewModelTest.kt @@ -31,20 +31,21 @@ import com.twidere.twiderex.repository.ListsRepository import com.twidere.twiderex.viewmodel.ViewModelTestBase import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.async import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.withContext import org.junit.Assert import org.junit.Rule import org.junit.Test import org.mockito.Mock import org.mockito.kotlin.any import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.mockito.kotlin.whenever +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine class ListsModifyViewModelTest : ViewModelTestBase() { @get:Rule @@ -55,8 +56,10 @@ class ListsModifyViewModelTest : ViewModelTestBase() { @Mock private lateinit var mockAppNotification: InAppNotification - @Mock - private lateinit var mockAccount: AccountDetails + private val mockAccount: AccountDetails = mock { + on { service }.doReturn(MockCenter.mockListsService()) + on { accountKey }.doReturn(MicroBlogKey.twitter("123")) + } @Mock private lateinit var mockSuccessObserver: Observer @@ -73,15 +76,16 @@ class ListsModifyViewModelTest : ViewModelTestBase() { @Test fun updateList_successExpectTrue(): Unit = runBlocking(Dispatchers.Main) { verifySuccessAndLoadingBefore(mockLoadingObserver, mockSuccessObserver) - async { + suspendCoroutine { modifyViewModel.editList( listId = "123", title = "title", private = false ) { success, _ -> mockSuccessObserver.onChanged(success) + it.resume(success) } - }.await() + } verifySuccessAndLoadingAfter(mockLoadingObserver, mockSuccessObserver, true) } @@ -89,15 +93,16 @@ class ListsModifyViewModelTest : ViewModelTestBase() { fun updateList_failedExpectFalseAndShowNotification(): Unit = runBlocking(Dispatchers.Main) { verifySuccessAndLoadingBefore(mockLoadingObserver, mockSuccessObserver) Assert.assertNull(errorNotification) - async { + suspendCoroutine { modifyViewModel.editList( listId = "error", title = "name", private = false ) { success, _ -> mockSuccessObserver.onChanged(success) + it.resume(success) } - }.await() + } verifySuccessAndLoadingAfter(mockLoadingObserver, mockSuccessObserver, false) Assert.assertNotNull(errorNotification) } @@ -105,11 +110,12 @@ class ListsModifyViewModelTest : ViewModelTestBase() { @Test fun deleteList_successExpectTrue(): Unit = runBlocking(Dispatchers.Main) { verifySuccessAndLoadingBefore(mockLoadingObserver, mockSuccessObserver) - async { + suspendCoroutine { modifyViewModel.deleteList(listId = "123", MicroBlogKey.Empty) { success, _ -> mockSuccessObserver.onChanged(success) + it.resume(success) } - }.await() + } verifySuccessAndLoadingAfter(mockLoadingObserver, mockSuccessObserver, true) } @@ -117,11 +123,12 @@ class ListsModifyViewModelTest : ViewModelTestBase() { fun deleteList_failedExpectFalseAndShowNotification(): Unit = runBlocking(Dispatchers.Main) { verifySuccessAndLoadingBefore(mockLoadingObserver, mockSuccessObserver) Assert.assertNull(errorNotification) - async { + suspendCoroutine { modifyViewModel.deleteList(listId = "error", MicroBlogKey.Empty) { success, _ -> mockSuccessObserver.onChanged(success) + it.resume(success) } - }.await() + } verifySuccessAndLoadingAfter(mockLoadingObserver, mockSuccessObserver, false) Assert.assertNotNull(errorNotification) } @@ -129,11 +136,12 @@ class ListsModifyViewModelTest : ViewModelTestBase() { @Test fun subscribeList_successExpectTrue(): Unit = runBlocking(Dispatchers.Main) { verifySuccessAndLoadingBefore(mockLoadingObserver, mockSuccessObserver) - async { + suspendCoroutine { modifyViewModel.subscribeList(MicroBlogKey.twitter("123")) { success, _ -> mockSuccessObserver.onChanged(success) + it.resume(success) } - }.await() + } verifySuccessAndLoadingAfter(mockLoadingObserver, mockSuccessObserver, true) } @@ -141,11 +149,12 @@ class ListsModifyViewModelTest : ViewModelTestBase() { fun subscribeList_failedExpectFalseAndShowNotification(): Unit = runBlocking(Dispatchers.Main) { verifySuccessAndLoadingBefore(mockLoadingObserver, mockSuccessObserver) Assert.assertNull(errorNotification) - async { + suspendCoroutine { modifyViewModel.subscribeList(MicroBlogKey.twitter("error")) { success, _ -> mockSuccessObserver.onChanged(success) + it.resume(success) } - }.await() + } verifySuccessAndLoadingAfter(mockLoadingObserver, mockSuccessObserver, false) Assert.assertNotNull(errorNotification) } @@ -153,11 +162,12 @@ class ListsModifyViewModelTest : ViewModelTestBase() { @Test fun unsubscribeList_successExpectTrue(): Unit = runBlocking(Dispatchers.Main) { verifySuccessAndLoadingBefore(mockLoadingObserver, mockSuccessObserver) - async { + suspendCoroutine { modifyViewModel.unsubscribeList(MicroBlogKey.twitter("123")) { success, _ -> mockSuccessObserver.onChanged(success) + it.resume(success) } - }.await() + } verifySuccessAndLoadingAfter(mockLoadingObserver, mockSuccessObserver, true) } @@ -166,11 +176,12 @@ class ListsModifyViewModelTest : ViewModelTestBase() { runBlocking(Dispatchers.Main) { verifySuccessAndLoadingBefore(mockLoadingObserver, mockSuccessObserver) Assert.assertNull(errorNotification) - async { + suspendCoroutine { modifyViewModel.unsubscribeList(MicroBlogKey.twitter("error")) { success, _ -> mockSuccessObserver.onChanged(success) + it.resume(success) } - }.await() + } verifySuccessAndLoadingAfter(mockLoadingObserver, mockSuccessObserver, false) Assert.assertNotNull(errorNotification) } @@ -188,15 +199,11 @@ class ListsModifyViewModelTest : ViewModelTestBase() { errorNotification = it.getArgument(0) as NotificationEvent Unit } - doReturn(MockCenter.mockListsService()).whenever(mockAccount).service - doReturn(MicroBlogKey.twitter("123")).whenever(mockAccount).accountKey errorNotification = null mockSuccessObserver.onChanged(false) scope.launch { - withContext(Dispatchers.Main) { - modifyViewModel.loading.collect { - mockLoadingObserver.onChanged(it) - } + modifyViewModel.loading.collect { + mockLoadingObserver.onChanged(it) } } } From 4b687ca43767e5483c5aa749549a3f18f34bbb17 Mon Sep 17 00:00:00 2001 From: itsMimao Date: Tue, 20 Jul 2021 15:33:16 +0800 Subject: [PATCH 033/137] refactor retrofit and okhttpclient build --- app/src/main/AndroidManifest.xml | 4 + .../kotlin/com/twidere/twiderex/TwidereApp.kt | 2 + .../component/foundation/NetworkImage.kt | 16 +- .../twiderex/di/InitializerEntryPoint.kt | 2 + .../http/TwidereHttpConfigProvider.kt | 55 +++++ .../twiderex/http/TwidereServiceFactory.kt | 80 +++++++ .../http/TwidereServiceInitializer.kt | 43 ++++ .../twidere/twiderex/model/AccountDetails.kt | 52 +++-- .../preferences/ProvidePreferences.kt | 32 +-- .../mastodon/MastodonSignInViewModel.kt | 2 + .../twitter/TwitterSignInViewModel.kt | 24 +- .../services/http/HttpClientFactory.kt | 29 +++ .../HttpConfigProvider.kt} | 12 +- .../services/http/config/HttpConfig.kt | 34 +++ .../http/config/HttpConfigClientFactory.kt | 215 ++++++++++++++++++ .../com/twidere/services/http/retrofit.kt | 75 ------ .../services/mastodon/MastodonOAuthService.kt | 11 +- .../services/mastodon/MastodonService.kt | 49 +--- .../com/twidere/services/proxy/ProxyConfig.kt | 3 +- .../services/twitter/TwitterOAuthService.kt | 9 +- .../services/twitter/TwitterService.kt | 68 ++---- 21 files changed, 579 insertions(+), 238 deletions(-) create mode 100644 app/src/main/kotlin/com/twidere/twiderex/http/TwidereHttpConfigProvider.kt create mode 100644 app/src/main/kotlin/com/twidere/twiderex/http/TwidereServiceFactory.kt create mode 100644 app/src/main/kotlin/com/twidere/twiderex/http/TwidereServiceInitializer.kt create mode 100644 services/src/main/java/com/twidere/services/http/HttpClientFactory.kt rename services/src/main/java/com/twidere/services/{proxy/ProxyService.kt => http/HttpConfigProvider.kt} (74%) create mode 100644 services/src/main/java/com/twidere/services/http/config/HttpConfig.kt create mode 100644 services/src/main/java/com/twidere/services/http/config/HttpConfigClientFactory.kt delete mode 100644 services/src/main/java/com/twidere/services/http/retrofit.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 768fec236..d5636221f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -42,6 +42,10 @@ android:name="com.twidere.twiderex.notification.NotificationInitializer" android:value="androidx.startup" tools:node="remove" /> + Unit)? = null, ) { - val proxyConfig = LocalProxyConfig.current + val httpConfig = LocalHttpConfig.current val painter = if (data is Painter) { data } else { rememberImagePainter( data = data, imageLoader = TwidereImageLoader( - buildRealImageLoader(proxyConfig), + buildRealImageLoader(httpConfig), LocalContext.current, LocalActiveAccount.current ), @@ -89,14 +89,14 @@ fun NetworkImage( } @Composable -fun buildRealImageLoader(proxyConfig: ProxyConfig): ImageLoader { - return if (proxyConfig.enable && - proxyConfig.server.isNotEmpty() +fun buildRealImageLoader(httpConfig: HttpConfig): ImageLoader { + return if (httpConfig.proxyConfig.enable && + httpConfig.proxyConfig.server.isNotEmpty() ) { LocalImageLoader.current .newBuilder() .callFactory( - proxyConfig.generateProxyClientBuilder() + httpConfig.createHttpClientBuilder() .cache(CoilUtils.createDefaultCache(LocalContext.current)) .build() ) diff --git a/app/src/main/kotlin/com/twidere/twiderex/di/InitializerEntryPoint.kt b/app/src/main/kotlin/com/twidere/twiderex/di/InitializerEntryPoint.kt index 7c17f7e71..af2b2e404 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/di/InitializerEntryPoint.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/di/InitializerEntryPoint.kt @@ -21,6 +21,7 @@ package com.twidere.twiderex.di import android.content.Context +import com.twidere.twiderex.http.TwidereServiceInitializer import com.twidere.twiderex.notification.NotificationChannelInitializer import com.twidere.twiderex.notification.NotificationInitializer import com.twidere.twiderex.worker.dm.DirectMessageInitializer @@ -45,4 +46,5 @@ interface InitializerEntryPoint { fun inject(initializer: NotificationChannelInitializer) fun inject(initializer: NotificationInitializer) fun inject(initializer: DirectMessageInitializer) + fun inject(initializer: TwidereServiceInitializer) } diff --git a/app/src/main/kotlin/com/twidere/twiderex/http/TwidereHttpConfigProvider.kt b/app/src/main/kotlin/com/twidere/twiderex/http/TwidereHttpConfigProvider.kt new file mode 100644 index 000000000..f695d5bb2 --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/http/TwidereHttpConfigProvider.kt @@ -0,0 +1,55 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.http + +import androidx.datastore.core.DataStore +import com.twidere.services.http.HttpConfigProvider +import com.twidere.services.http.config.HttpConfig +import com.twidere.services.proxy.ProxyConfig +import com.twidere.twiderex.preferences.proto.MiscPreferences +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.runBlocking +import javax.inject.Inject + +class TwidereHttpConfigProvider @Inject constructor( + private val miscPreferences: DataStore +) : HttpConfigProvider { + override fun provideConfig(): HttpConfig { + return runBlocking { + miscPreferences.data.map { + HttpConfig( + proxyConfig = ProxyConfig( + enable = it.useProxy, + server = it.proxyServer, + port = it.proxyPort, + userName = it.proxyUserName, + password = it.proxyPassword, + type = when (it.proxyType) { + MiscPreferences.ProxyType.REVERSE -> ProxyConfig.Type.REVERSE + else -> ProxyConfig.Type.HTTP + } + ) + ) + }.first() + } + } +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/http/TwidereServiceFactory.kt b/app/src/main/kotlin/com/twidere/twiderex/http/TwidereServiceFactory.kt new file mode 100644 index 000000000..a1684b440 --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/http/TwidereServiceFactory.kt @@ -0,0 +1,80 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.http + +import com.twidere.services.http.HttpClientFactory +import com.twidere.services.http.config.HttpConfigClientFactory +import com.twidere.services.mastodon.MastodonService +import com.twidere.services.microblog.MicroBlogService +import com.twidere.services.twitter.TwitterService +import com.twidere.twiderex.model.PlatformType +import com.twidere.twiderex.model.cred.Credentials +import com.twidere.twiderex.model.cred.OAuth2Credentials +import com.twidere.twiderex.model.cred.OAuthCredentials + +class TwidereServiceFactory(private val configProvider: TwidereHttpConfigProvider) { + + companion object { + private var instance: TwidereServiceFactory? = null + + fun initiate(configProvider: TwidereHttpConfigProvider) { + instance = TwidereServiceFactory(configProvider) + } + + fun createApiService(type: PlatformType, credentials: Credentials, host: String = ""): MicroBlogService { + return instance?.let { + when (type) { + PlatformType.Twitter -> { + credentials.let { + it as OAuthCredentials + }.let { + TwitterService( + consumer_key = it.consumer_key, + consumer_secret = it.consumer_secret, + access_token = it.access_token, + access_token_secret = it.access_token_secret, + httpClientFactory = createHttpClientFactory() + ) + } + } + PlatformType.StatusNet -> TODO() + PlatformType.Fanfou -> TODO() + PlatformType.Mastodon -> + credentials.let { + it as OAuth2Credentials + }.let { + MastodonService( + host, + it.access_token, + httpClientFactory = createHttpClientFactory() + ) + } + } + } ?: throw Error("Factory needs to be initiate") + } + + fun createHttpClientFactory(): HttpClientFactory { + return instance?.let { + HttpConfigClientFactory(it.configProvider) + } ?: throw Error("Factory needs to be initiate") + } + } +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/http/TwidereServiceInitializer.kt b/app/src/main/kotlin/com/twidere/twiderex/http/TwidereServiceInitializer.kt new file mode 100644 index 000000000..2f5dfe567 --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/http/TwidereServiceInitializer.kt @@ -0,0 +1,43 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.http + +import android.content.Context +import androidx.startup.Initializer +import com.twidere.twiderex.di.InitializerEntryPoint +import javax.inject.Inject + +class TwidereserviceInitializerHolder + +class TwidereServiceInitializer : Initializer { + @Inject + lateinit var configProvider: TwidereHttpConfigProvider + + override fun create(context: Context): TwidereserviceInitializerHolder { + InitializerEntryPoint.resolve(context).inject(this) + TwidereServiceFactory.initiate(configProvider) + return TwidereserviceInitializerHolder() + } + + override fun dependencies(): MutableList>> { + return mutableListOf() + } +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/AccountDetails.kt b/app/src/main/kotlin/com/twidere/twiderex/model/AccountDetails.kt index d8791bd20..3dc3763e1 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/model/AccountDetails.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/model/AccountDetails.kt @@ -21,9 +21,8 @@ package com.twidere.twiderex.model import android.accounts.Account -import com.twidere.services.mastodon.MastodonService import com.twidere.services.microblog.MicroBlogService -import com.twidere.services.twitter.TwitterService +import com.twidere.twiderex.http.TwidereServiceFactory import com.twidere.twiderex.model.adapter.AndroidAccountSerializer import com.twidere.twiderex.model.cred.BasicCredentials import com.twidere.twiderex.model.cred.Credentials @@ -61,28 +60,33 @@ data class AccountDetails( } val service by lazy { - when (type) { - PlatformType.Twitter -> { - credentials.let { - it as OAuthCredentials - }.let { - TwitterService( - consumer_key = it.consumer_key, - consumer_secret = it.consumer_secret, - access_token = it.access_token, - access_token_secret = it.access_token_secret, - ) - } - } - PlatformType.StatusNet -> TODO() - PlatformType.Fanfou -> TODO() - PlatformType.Mastodon -> - credentials.let { - it as OAuth2Credentials - }.let { - MastodonService(accountKey.host, it.access_token) - } - } + // when (type) { + // PlatformType.Twitter -> { + // credentials.let { + // it as OAuthCredentials + // }.let { + // TwitterService( + // consumer_key = it.consumer_key, + // consumer_secret = it.consumer_secret, + // access_token = it.access_token, + // access_token_secret = it.access_token_secret, + // ) + // } + // } + // PlatformType.StatusNet -> TODO() + // PlatformType.Fanfou -> TODO() + // PlatformType.Mastodon -> + // credentials.let { + // it as OAuth2Credentials + // }.let { + // MastodonService(accountKey.host, it.access_token) + // } + // } + TwidereServiceFactory.createApiService( + type = type, + credentials = credentials, + host = accountKey.host + ) } val listType: ListType diff --git a/app/src/main/kotlin/com/twidere/twiderex/preferences/ProvidePreferences.kt b/app/src/main/kotlin/com/twidere/twiderex/preferences/ProvidePreferences.kt index 93a5c25da..7fc1c68b4 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/preferences/ProvidePreferences.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/preferences/ProvidePreferences.kt @@ -26,8 +26,8 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.getValue import androidx.datastore.core.DataStore +import com.twidere.services.http.config.HttpConfig import com.twidere.services.proxy.ProxyConfig -import com.twidere.services.proxy.ProxyService import com.twidere.twiderex.preferences.proto.AppearancePreferences import com.twidere.twiderex.preferences.proto.DisplayPreferences import com.twidere.twiderex.preferences.proto.MiscPreferences @@ -37,7 +37,7 @@ import javax.inject.Inject val LocalAppearancePreferences = compositionLocalOf { error("No AppearancePreferences") } val LocalDisplayPreferences = compositionLocalOf { error("No DisplayPreferences") } -val LocalProxyConfig = compositionLocalOf { error("No Proxy preferences") } +val LocalHttpConfig = compositionLocalOf { error("No Http config preferences") } data class PreferencesHolder @Inject constructor( val appearancePreferences: DataStore, val displayPreferences: DataStore, @@ -58,25 +58,27 @@ fun ProvidePreferences( val proxyConfig by holder.miscPreferences .data .map { - ProxyConfig( - enable = it.useProxy, - server = it.proxyServer, - port = it.proxyPort, - userName = it.proxyUserName, - password = it.proxyPassword, - type = when (it.proxyType) { - MiscPreferences.ProxyType.REVERSE -> ProxyConfig.Type.REVERSE - else -> ProxyConfig.Type.HTTP - } - ).apply { ProxyService.updateProxyConfig(this) } + HttpConfig( + proxyConfig = ProxyConfig( + enable = it.useProxy, + server = it.proxyServer, + port = it.proxyPort, + userName = it.proxyUserName, + password = it.proxyPassword, + type = when (it.proxyType) { + MiscPreferences.ProxyType.REVERSE -> ProxyConfig.Type.REVERSE + else -> ProxyConfig.Type.HTTP + } + ) + ) } - .collectAsState(initial = ProxyConfig()) + .collectAsState(initial = HttpConfig()) CompositionLocalProvider( LocalAppearancePreferences provides appearances, LocalDisplayPreferences provides display, LocalVideoPlayback provides display.autoPlayback, - LocalProxyConfig provides proxyConfig + LocalHttpConfig provides proxyConfig ) { content.invoke() } diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/mastodon/MastodonSignInViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/mastodon/MastodonSignInViewModel.kt index 1ba30c641..c38136d27 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/mastodon/MastodonSignInViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/mastodon/MastodonSignInViewModel.kt @@ -27,6 +27,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.twidere.services.mastodon.MastodonOAuthService import com.twidere.twiderex.db.mapper.toDbUser +import com.twidere.twiderex.http.TwidereServiceFactory import com.twidere.twiderex.model.AccountDetails import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.PlatformType @@ -69,6 +70,7 @@ class MastodonSignInViewModel @AssistedInject constructor( client_name = "Twidere X", website = "https://github.com/TwidereProject/TwidereX-Android", redirect_uri = DeepLinks.Callback.SignIn.Mastodon, + httpClientFactory = TwidereServiceFactory.createHttpClientFactory() ) val application = service.createApplication() val target = service.getWebOAuthUrl(application) diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/twitter/TwitterSignInViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/twitter/TwitterSignInViewModel.kt index 613414922..9bc8994d1 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/twitter/TwitterSignInViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/twitter/TwitterSignInViewModel.kt @@ -29,6 +29,7 @@ import com.twidere.services.twitter.TwitterOAuthService import com.twidere.services.twitter.TwitterService import com.twidere.twiderex.BuildConfig import com.twidere.twiderex.db.mapper.toDbUser +import com.twidere.twiderex.http.TwidereServiceFactory import com.twidere.twiderex.model.AccountDetails import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.PlatformType @@ -81,7 +82,11 @@ class TwitterSignInViewModel @AssistedInject constructor( private suspend fun beginOAuth(): Boolean { loading.postValue(true) try { - val service = TwitterOAuthService(consumerKey, consumerSecret) + val service = TwitterOAuthService( + consumerKey, + consumerSecret, + TwidereServiceFactory.createHttpClientFactory() + ) val token = service.getOAuthToken( if (isBuiltInKey()) { DeepLinks.Callback.SignIn.Twitter @@ -96,12 +101,17 @@ class TwitterSignInViewModel @AssistedInject constructor( } if (!pinCode.isNullOrBlank()) { val accessToken = service.getAccessToken(pinCode, token) - val user = TwitterService( - consumer_key = consumerKey, - consumer_secret = consumerSecret, - access_token = accessToken.oauth_token, - access_token_secret = accessToken.oauth_token_secret, - ).verifyCredentials() + val user = ( + TwidereServiceFactory.createApiService( + type = PlatformType.Twitter, + credentials = OAuthCredentials( + consumer_key = consumerKey, + consumer_secret = consumerSecret, + access_token = accessToken.oauth_token, + access_token_secret = accessToken.oauth_token_secret + ), + ) as TwitterService + ).verifyCredentials() if (user != null) { val name = user.screenName val id = user.idStr diff --git a/services/src/main/java/com/twidere/services/http/HttpClientFactory.kt b/services/src/main/java/com/twidere/services/http/HttpClientFactory.kt new file mode 100644 index 000000000..f8e29b592 --- /dev/null +++ b/services/src/main/java/com/twidere/services/http/HttpClientFactory.kt @@ -0,0 +1,29 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.services.http + +import com.twidere.services.http.authorization.Authorization +import okhttp3.OkHttpClient + +interface HttpClientFactory { + fun createResources(clazz: Class, baseUrl: String, authorization: Authorization, useCache: Boolean = false): T + fun createHttpClientBuilder(): OkHttpClient.Builder +} diff --git a/services/src/main/java/com/twidere/services/proxy/ProxyService.kt b/services/src/main/java/com/twidere/services/http/HttpConfigProvider.kt similarity index 74% rename from services/src/main/java/com/twidere/services/proxy/ProxyService.kt rename to services/src/main/java/com/twidere/services/http/HttpConfigProvider.kt index d951617d5..5708c2d61 100644 --- a/services/src/main/java/com/twidere/services/proxy/ProxyService.kt +++ b/services/src/main/java/com/twidere/services/http/HttpConfigProvider.kt @@ -18,14 +18,10 @@ * You should have received a copy of the GNU General Public License * along with Twidere X. If not, see . */ -package com.twidere.services.proxy +package com.twidere.services.http -import kotlinx.coroutines.flow.MutableStateFlow +import com.twidere.services.http.config.HttpConfig -object ProxyService { - val proxyConfig = MutableStateFlow(ProxyConfig()) - - fun updateProxyConfig(proxyConfig: ProxyConfig) { - this.proxyConfig.value = proxyConfig - } +interface HttpConfigProvider { + fun provideConfig(): HttpConfig } diff --git a/services/src/main/java/com/twidere/services/http/config/HttpConfig.kt b/services/src/main/java/com/twidere/services/http/config/HttpConfig.kt new file mode 100644 index 000000000..2bc890cdf --- /dev/null +++ b/services/src/main/java/com/twidere/services/http/config/HttpConfig.kt @@ -0,0 +1,34 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.services.http.config + +import com.twidere.services.proxy.ProxyConfig +import okhttp3.OkHttpClient + +data class HttpConfig( + val proxyConfig: ProxyConfig = ProxyConfig() +) { + fun createHttpClientBuilder(): OkHttpClient.Builder { + return OkHttpClient.Builder().let { + proxyConfig.proxy(it) + } + } +} diff --git a/services/src/main/java/com/twidere/services/http/config/HttpConfigClientFactory.kt b/services/src/main/java/com/twidere/services/http/config/HttpConfigClientFactory.kt new file mode 100644 index 000000000..4358d401c --- /dev/null +++ b/services/src/main/java/com/twidere/services/http/config/HttpConfigClientFactory.kt @@ -0,0 +1,215 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.services.http.config + +import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory +import com.twidere.services.http.AuthorizationInterceptor +import com.twidere.services.http.HttpClientFactory +import com.twidere.services.http.HttpConfigProvider +import com.twidere.services.http.MicroBlogHttpException +import com.twidere.services.http.authorization.Authorization +import com.twidere.services.mastodon.api.MastodonResources +import com.twidere.services.mastodon.model.exceptions.MastodonException +import com.twidere.services.proxy.ProxyConfig +import com.twidere.services.proxy.ReverseProxyInterceptor +import com.twidere.services.serializer.DateQueryConverterFactory +import com.twidere.services.twitter.api.TwitterResources +import com.twidere.services.twitter.model.exceptions.TwitterApiException +import com.twidere.services.twitter.model.exceptions.TwitterApiExceptionV2 +import com.twidere.services.utils.DEBUG +import com.twidere.services.utils.JSON +import com.twidere.services.utils.decodeJson +import kotlinx.serialization.ExperimentalSerializationApi +import okhttp3.Credentials +import okhttp3.Interceptor +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import retrofit2.Retrofit +import retrofit2.converter.scalars.ScalarsConverterFactory +import java.net.InetSocketAddress +import java.net.Proxy + +class HttpConfigClientFactory(private val configProvider: HttpConfigProvider) : HttpClientFactory { + private val resourceCache = mutableMapOf, Pair<*, CacheIdentifier>>() + + @Suppress("UNCHECKED_CAST") + override fun createResources( + clazz: Class, + baseUrl: String, + authorization: Authorization, + useCache: Boolean + ): T { + val cache = resourceCache[clazz] + val cacheIdentifier = CacheIdentifier(configProvider.provideConfig(), baseUrl) + val result: T = if (cache != null && cache.second == cacheIdentifier && useCache) { + cache.first as T + } else { + val interceptors = when (clazz) { + TwitterResources::class.java -> Interceptor { chain -> + val response = chain.proceed(chain.request()) + if (!response.isSuccessful) { + response.body?.string()?.takeIf { + it.isNotEmpty() + }?.let { content -> + content.decodeJson().takeIf { + it.microBlogErrorMessage != null + }.let { + it ?: run { + content.decodeJson() + } + }.let { + throw it + } + } ?: throw MicroBlogHttpException(response.code) + } else { + response + } + } + MastodonResources::class.java -> Interceptor { chain -> + val response = chain.proceed(chain.request()) + if (!response.isSuccessful) { + response.body?.string()?.takeIf { + it.isNotEmpty() + }?.let { content -> + throw content.decodeJson() + } ?: throw MicroBlogHttpException(response.code) + } else { + response + } + } + else -> null + } + retrofit( + clazz, + baseUrl, + authorization, + createHttpClientBuilder(), + interceptors + ) + } + if (useCache) { + resourceCache[clazz] = Pair(result, cacheIdentifier) + } + return result + } + + override fun createHttpClientBuilder(): OkHttpClient.Builder { + val config = configProvider.provideConfig() + return proxy(OkHttpClient.Builder(), config.proxyConfig) + } + + private fun proxy(builder: OkHttpClient.Builder, proxyConfig: ProxyConfig): OkHttpClient.Builder { + return if (proxyConfig.enable) { + when (proxyConfig.type) { + ProxyConfig.Type.HTTP -> { + if (proxyConfig.port !in (0..65535)) { + return builder + } + val address = InetSocketAddress.createUnresolved( + proxyConfig.server, + proxyConfig.port + ) + builder.proxy(Proxy(Proxy.Type.HTTP, address)) + .proxyAuthenticator { _, response -> + val b = response.request.newBuilder() + if (response.code == 407) { + if (proxyConfig.userName.isNotEmpty() && + proxyConfig.password.isNotEmpty() + ) { + val credential = Credentials.basic( + proxyConfig.userName, + proxyConfig.password + ) + b.header("Proxy-Authorization", credential) + } + } + b.build() + } + } + ProxyConfig.Type.REVERSE -> { + builder.addInterceptor(ReverseProxyInterceptor(proxyConfig.server, proxyConfig.userName, proxyConfig.password)) + } + } + } else { + builder + } + } + + @OptIn(ExperimentalSerializationApi::class) + private fun retrofit( + clazz: Class, + baseUrl: String, + authorization: Authorization, + clientBuilder: OkHttpClient.Builder = OkHttpClient.Builder(), + vararg interceptors: Interceptor? + ): T { + return Retrofit + .Builder() + .baseUrl(baseUrl) + .client( + clientBuilder + .addInterceptor(AuthorizationInterceptor(authorization)) + .apply { + if (DEBUG) { + addInterceptor( + HttpLoggingInterceptor().apply { + setLevel(HttpLoggingInterceptor.Level.BODY) + } + ) + } + addInterceptor { + it.proceed( + it.request().let { request -> + request.newBuilder().url(request.url.toString().replace("%20", "+")).build() + } + ) + } + interceptors.forEach { + it?.let { addInterceptor(it) } + } + } + .build() + ) + .addConverterFactory(ScalarsConverterFactory.create()) + .addConverterFactory(JSON.asConverterFactory("application/json".toMediaType())) + .addConverterFactory(DateQueryConverterFactory()) + .build() + .create(clazz) + } + + data class CacheIdentifier( + val config: HttpConfig, + val baseUrl: String + ) { + override fun equals(other: Any?): Boolean { + if (other !is CacheIdentifier) return false + return config.toString() == other.config.toString() && + baseUrl == other.baseUrl + } + + override fun hashCode(): Int { + var result = config.hashCode() + result = 31 * result + baseUrl.hashCode() + return result + } + } +} diff --git a/services/src/main/java/com/twidere/services/http/retrofit.kt b/services/src/main/java/com/twidere/services/http/retrofit.kt deleted file mode 100644 index d9b895cf7..000000000 --- a/services/src/main/java/com/twidere/services/http/retrofit.kt +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Twidere X - * - * Copyright (C) 2020-2021 Tlaster - * - * This file is part of Twidere X. - * - * Twidere X 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. - * - * Twidere X 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 Twidere X. If not, see . - */ -package com.twidere.services.http - -import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory -import com.twidere.services.http.authorization.Authorization -import com.twidere.services.proxy.ProxyService -import com.twidere.services.serializer.DateQueryConverterFactory -import com.twidere.services.utils.DEBUG -import com.twidere.services.utils.JSON -import kotlinx.serialization.ExperimentalSerializationApi -import okhttp3.Interceptor -import okhttp3.MediaType.Companion.toMediaType -import okhttp3.Response -import okhttp3.logging.HttpLoggingInterceptor -import retrofit2.Retrofit -import retrofit2.converter.scalars.ScalarsConverterFactory - -@OptIn(ExperimentalSerializationApi::class) -internal inline fun retrofit( - baseUrl: String, - authorization: Authorization, - vararg interceptors: (chain: Interceptor.Chain) -> Response -): T { - return Retrofit - .Builder() - .baseUrl(baseUrl) - .client( - ProxyService.proxyConfig.value.generateProxyClientBuilder() - .addInterceptor(AuthorizationInterceptor(authorization)) - .apply { - if (DEBUG) { - addInterceptor( - HttpLoggingInterceptor().apply { - setLevel(HttpLoggingInterceptor.Level.BODY) - } - ) - } - addInterceptor { - it.proceed( - it.request().let { request -> - request.newBuilder().url(request.url.toString().replace("%20", "+")).build() - } - ) - } - interceptors.forEach { - addInterceptor(it) - } - } - .build() - ) - .addConverterFactory(ScalarsConverterFactory.create()) - .addConverterFactory(JSON.asConverterFactory("application/json".toMediaType())) - .addConverterFactory(DateQueryConverterFactory()) - .build() - .create(T::class.java) -} diff --git a/services/src/main/java/com/twidere/services/mastodon/MastodonOAuthService.kt b/services/src/main/java/com/twidere/services/mastodon/MastodonOAuthService.kt index 248bcd149..ff9c066a2 100644 --- a/services/src/main/java/com/twidere/services/mastodon/MastodonOAuthService.kt +++ b/services/src/main/java/com/twidere/services/mastodon/MastodonOAuthService.kt @@ -20,9 +20,9 @@ */ package com.twidere.services.mastodon +import com.twidere.services.http.HttpClientFactory import com.twidere.services.http.authorization.BearerAuthorization import com.twidere.services.http.authorization.EmptyAuthorization -import com.twidere.services.http.retrofit import com.twidere.services.mastodon.api.MastodonOAuthResources import com.twidere.services.mastodon.model.CreateApplicationResponse import com.twidere.services.mastodon.model.MastodonAuthScope @@ -32,6 +32,7 @@ class MastodonOAuthService( private val client_name: String, private val website: String? = null, private val redirect_uri: String = "urn:ietf:wg:oauth:2.0:oob", + private val httpClientFactory: HttpClientFactory, private val scopes: List = listOf( MastodonAuthScope.read, MastodonAuthScope.write, @@ -39,8 +40,9 @@ class MastodonOAuthService( MastodonAuthScope.push, ), ) { - private val resources by lazy { - retrofit( + private val resources: MastodonOAuthResources by lazy { + httpClientFactory.createResources( + MastodonOAuthResources::class.java, host, EmptyAuthorization() ) @@ -71,7 +73,8 @@ class MastodonOAuthService( ) suspend fun verifyCredentials(accessToken: String) = - retrofit( + httpClientFactory.createResources( + MastodonOAuthResources::class.java, host, BearerAuthorization(accessToken) ).verifyCredentials() diff --git a/services/src/main/java/com/twidere/services/mastodon/MastodonService.kt b/services/src/main/java/com/twidere/services/mastodon/MastodonService.kt index c8eba5544..16191dc92 100644 --- a/services/src/main/java/com/twidere/services/mastodon/MastodonService.kt +++ b/services/src/main/java/com/twidere/services/mastodon/MastodonService.kt @@ -21,9 +21,8 @@ package com.twidere.services.mastodon import com.twidere.services.http.AuthorizationInterceptor -import com.twidere.services.http.MicroBlogHttpException +import com.twidere.services.http.HttpClientFactory import com.twidere.services.http.authorization.BearerAuthorization -import com.twidere.services.http.retrofit import com.twidere.services.mastodon.api.MastodonResources import com.twidere.services.mastodon.model.Context import com.twidere.services.mastodon.model.Emoji @@ -56,14 +55,8 @@ import com.twidere.services.microblog.model.ISearchResponse import com.twidere.services.microblog.model.IStatus import com.twidere.services.microblog.model.IUser import com.twidere.services.microblog.model.Relationship -import com.twidere.services.proxy.ProxyService import com.twidere.services.utils.await -import com.twidere.services.utils.decodeJson -import kotlinx.coroutines.MainScope -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.launch import okhttp3.MultipartBody -import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.RequestBody.Companion.toRequestBody import java.io.InputStream @@ -71,7 +64,7 @@ import java.io.InputStream class MastodonService( private val host: String, private val accessToken: String, - resources: MastodonResources? = null + private val httpClientFactory: HttpClientFactory ) : MicroBlogService, TimelineService, LookupService, @@ -82,35 +75,12 @@ class MastodonService( DownloadMediaService, ListsService, TrendService { - private lateinit var resources: MastodonResources - - init { - updateResources(resources) - MainScope().launch { - ProxyService.proxyConfig.collect { - updateResources(resources) - } - } - } - - private fun updateResources(resources: MastodonResources?) { - this.resources = resources ?: retrofit( - "https://$host", - BearerAuthorization(accessToken), - { chain -> - val response = chain.proceed(chain.request()) - if (!response.isSuccessful) { - response.body?.string()?.takeIf { - it.isNotEmpty() - }?.let { content -> - throw content.decodeJson() - } ?: throw MicroBlogHttpException(response.code) - } else { - response - } - } - ) - } + private val resources: MastodonResources get() = httpClientFactory.createResources( + clazz = MastodonResources::class.java, + baseUrl = "https://$host", + authorization = BearerAuthorization(accessToken), + useCache = true + ) override suspend fun homeTimeline( count: Int, @@ -336,8 +306,7 @@ class MastodonService( suspend fun emojis(): List = resources.emojis() override suspend fun download(target: String): InputStream { - return OkHttpClient - .Builder() + return httpClientFactory.createHttpClientBuilder() .addInterceptor(AuthorizationInterceptor(BearerAuthorization(accessToken))) .build() .newCall( diff --git a/services/src/main/java/com/twidere/services/proxy/ProxyConfig.kt b/services/src/main/java/com/twidere/services/proxy/ProxyConfig.kt index 05c6bee79..56cc247f0 100644 --- a/services/src/main/java/com/twidere/services/proxy/ProxyConfig.kt +++ b/services/src/main/java/com/twidere/services/proxy/ProxyConfig.kt @@ -39,8 +39,7 @@ data class ProxyConfig( REVERSE } - fun generateProxyClientBuilder(): OkHttpClient.Builder { - val builder = OkHttpClient.Builder() + fun proxy(builder: OkHttpClient.Builder): OkHttpClient.Builder { return if (enable) { when (type) { Type.HTTP -> { diff --git a/services/src/main/java/com/twidere/services/twitter/TwitterOAuthService.kt b/services/src/main/java/com/twidere/services/twitter/TwitterOAuthService.kt index 857acc7b3..39221e357 100644 --- a/services/src/main/java/com/twidere/services/twitter/TwitterOAuthService.kt +++ b/services/src/main/java/com/twidere/services/twitter/TwitterOAuthService.kt @@ -20,8 +20,8 @@ */ package com.twidere.services.twitter +import com.twidere.services.http.HttpClientFactory import com.twidere.services.http.authorization.OAuth1Authorization -import com.twidere.services.http.retrofit import com.twidere.services.twitter.api.TwitterOAuthResources import com.twidere.services.twitter.model.AccessToken import com.twidere.services.twitter.model.OAuthToken @@ -30,11 +30,13 @@ import com.twidere.services.utils.queryString class TwitterOAuthService( private val consumerKey: String, private val consumerSecret: String, + private val httpClientFactory: HttpClientFactory, ) { suspend fun getOAuthToken( callback: String = "oob" ): OAuthToken { - return retrofit( + return httpClientFactory.createResources( + TwitterOAuthResources::class.java, TWITTER_BASE_URL, OAuth1Authorization( consumerKey, @@ -44,7 +46,8 @@ class TwitterOAuthService( } suspend fun getAccessToken(pinCode: String, token: OAuthToken): AccessToken { - return retrofit( + return httpClientFactory.createResources( + TwitterOAuthResources::class.java, TWITTER_BASE_URL, OAuth1Authorization( consumerKey, diff --git a/services/src/main/java/com/twidere/services/twitter/TwitterService.kt b/services/src/main/java/com/twidere/services/twitter/TwitterService.kt index fed5887d1..744a541d4 100644 --- a/services/src/main/java/com/twidere/services/twitter/TwitterService.kt +++ b/services/src/main/java/com/twidere/services/twitter/TwitterService.kt @@ -22,9 +22,8 @@ package com.twidere.services.twitter import com.twidere.services.http.AuthorizationInterceptor import com.twidere.services.http.Errors -import com.twidere.services.http.MicroBlogHttpException +import com.twidere.services.http.HttpClientFactory import com.twidere.services.http.authorization.OAuth1Authorization -import com.twidere.services.http.retrofit import com.twidere.services.microblog.DirectMessageService import com.twidere.services.microblog.DownloadMediaService import com.twidere.services.microblog.ListsService @@ -40,7 +39,6 @@ import com.twidere.services.microblog.model.ISearchResponse import com.twidere.services.microblog.model.IStatus import com.twidere.services.microblog.model.IUser import com.twidere.services.microblog.model.Relationship -import com.twidere.services.proxy.ProxyService import com.twidere.services.twitter.api.TwitterResources import com.twidere.services.twitter.api.UploadResources import com.twidere.services.twitter.model.Attachment @@ -67,11 +65,6 @@ import com.twidere.services.twitter.model.fields.UserFields import com.twidere.services.utils.Base64 import com.twidere.services.utils.await import com.twidere.services.utils.copyToInLength -import com.twidere.services.utils.decodeJson -import kotlinx.coroutines.MainScope -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.launch -import okhttp3.OkHttpClient import okhttp3.Request import java.io.ByteArrayOutputStream import java.io.InputStream @@ -84,7 +77,7 @@ class TwitterService( private val consumer_secret: String, private val access_token: String, private val access_token_secret: String, - resources: TwitterResources? = null + private val httpClientFactory: HttpClientFactory ) : MicroBlogService, TimelineService, LookupService, @@ -95,49 +88,21 @@ class TwitterService( ListsService, TrendService, DirectMessageService { - private lateinit var resources: TwitterResources - - private lateinit var uploadResources: UploadResources - - init { - updateResources(resources) - MainScope().launch { - ProxyService.proxyConfig.collect { - updateResources(resources) - } - } - } - - private fun updateResources(resources: TwitterResources? = null) { - this.resources = resources ?: retrofit( - TWITTER_BASE_URL, - createOAuth1Authorization(), - { chain -> - val response = chain.proceed(chain.request()) - if (!response.isSuccessful) { - response.body?.string()?.takeIf { - it.isNotEmpty() - }?.let { content -> - content.decodeJson().takeIf { - it.microBlogErrorMessage != null - }.let { - it ?: run { - content.decodeJson() - } - }.let { - throw it - } - } ?: throw MicroBlogHttpException(response.code) - } else { - response - } - } + private val resources: TwitterResources + get() = httpClientFactory.createResources( + clazz = TwitterResources::class.java, + baseUrl = TWITTER_BASE_URL, + authorization = createOAuth1Authorization(), + useCache = true, ) - this.uploadResources = retrofit( - UPLOAD_TWITTER_BASE_URL, - createOAuth1Authorization(), + + private val uploadResources: UploadResources + get() = httpClientFactory.createResources( + clazz = UploadResources::class.java, + baseUrl = UPLOAD_TWITTER_BASE_URL, + authorization = createOAuth1Authorization(), + useCache = true ) - } private fun createOAuth1Authorization() = OAuth1Authorization( consumer_key, @@ -496,8 +461,7 @@ class TwitterService( } override suspend fun download(target: String): InputStream { - return OkHttpClient - .Builder() + return httpClientFactory.createHttpClientBuilder() .addInterceptor(AuthorizationInterceptor(createOAuth1Authorization())) .build() .newCall( From 09895ed5c5c94bab6e206bb2198f5a374a61b347 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Tue, 20 Jul 2021 15:44:25 +0800 Subject: [PATCH 034/137] add localization submodule --- .gitmodules | 3 +++ localization | 1 + 2 files changed, 4 insertions(+) create mode 100644 .gitmodules create mode 160000 localization diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..e67c4932f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "localization"] + path = localization + url = git@github.com:TwidereProject/TwidereX-Localization.git diff --git a/localization b/localization new file mode 160000 index 000000000..6b979766f --- /dev/null +++ b/localization @@ -0,0 +1 @@ +Subproject commit 6b979766f1f47bcf4dc4dc966b43f023f7905e60 From 05ef95ce7177322244ee8f6d20428d262acf2697 Mon Sep 17 00:00:00 2001 From: itsMimao Date: Tue, 20 Jul 2021 16:16:05 +0800 Subject: [PATCH 035/137] add unit test for httpConfigClientFactory --- .../component/foundation/BlurImage.kt | 10 +- .../component/foundation/NetworkImage.kt | 87 ++--------------- .../com/twidere/twiderex/di/TwidereModule.kt | 3 +- .../http/TwidereNetworkImageLoader.kt | 96 +++++++++++++++++++ .../services/http/config/HttpConfig.kt | 9 +- .../http/config/HttpConfigClientFactory.kt | 2 +- .../twidere/services/nitter/NitterService.kt | 6 +- .../com/twidere/services/proxy/ProxyConfig.kt | 46 --------- .../services/HttpConfigClientFactoryTest.kt | 75 +++++++++++++++ .../services/api/common/MockServices.kt | 36 +++++-- 10 files changed, 222 insertions(+), 148 deletions(-) create mode 100644 app/src/main/kotlin/com/twidere/twiderex/http/TwidereNetworkImageLoader.kt create mode 100644 services/src/test/java/com/twidere/services/HttpConfigClientFactoryTest.kt diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/BlurImage.kt b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/BlurImage.kt index 910fe48d3..873db16b7 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/BlurImage.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/BlurImage.kt @@ -47,7 +47,6 @@ import androidx.core.graphics.drawable.toDrawable import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat import coil.ImageLoader import coil.bitmap.BitmapPool -import coil.compose.LocalImageLoader import coil.compose.rememberImagePainter import coil.memory.MemoryCache import coil.request.DefaultRequestOptions @@ -55,6 +54,8 @@ import coil.request.Disposable import coil.request.ImageRequest import coil.request.ImageResult import coil.request.SuccessResult +import com.twidere.twiderex.http.TwidereNetworkImageLoader +import com.twidere.twiderex.ui.LocalActiveAccount @Composable fun BlurImage( @@ -98,13 +99,18 @@ fun NetworkBlurImage( placeholder: @Composable (() -> Unit)? = null, ) { val context = LocalContext.current + val accountDetails = LocalActiveAccount.current val painter = rememberImagePainter( data = data, imageLoader = BlurImageLoader( context, blurRadius, bitmapScale, - LocalImageLoader.current + TwidereNetworkImageLoader( + realImageLoader = buildRealImageLoader(), + context = context, + account = accountDetails + ) ) ) NetworkImage( diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/NetworkImage.kt b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/NetworkImage.kt index 6bff59f78..c0f94ab02 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/NetworkImage.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/NetworkImage.kt @@ -20,8 +20,6 @@ */ package com.twidere.twiderex.component.foundation -import android.content.Context -import android.net.Uri import androidx.compose.foundation.Image import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -31,27 +29,15 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import coil.ImageLoader import coil.annotation.ExperimentalCoilApi -import coil.bitmap.BitmapPool import coil.compose.ImagePainter import coil.compose.LocalImageLoader import coil.compose.rememberImagePainter -import coil.memory.MemoryCache -import coil.request.DefaultRequestOptions -import coil.request.Disposable -import coil.request.ImageRequest -import coil.request.ImageResult import coil.util.CoilUtils -import com.twidere.services.http.authorization.OAuth1Authorization -import com.twidere.services.http.config.HttpConfig import com.twidere.twiderex.R -import com.twidere.twiderex.model.AccountDetails -import com.twidere.twiderex.model.PlatformType -import com.twidere.twiderex.model.cred.OAuthCredentials +import com.twidere.twiderex.http.TwidereNetworkImageLoader +import com.twidere.twiderex.http.TwidereServiceFactory import com.twidere.twiderex.preferences.LocalHttpConfig import com.twidere.twiderex.ui.LocalActiveAccount -import okhttp3.Headers -import okhttp3.Request -import java.net.URL @OptIn(ExperimentalCoilApi::class) @Composable @@ -61,14 +47,13 @@ fun NetworkImage( contentScale: ContentScale = ContentScale.Crop, placeholder: @Composable (() -> Unit)? = null, ) { - val httpConfig = LocalHttpConfig.current val painter = if (data is Painter) { data } else { rememberImagePainter( data = data, - imageLoader = TwidereImageLoader( - buildRealImageLoader(httpConfig), + imageLoader = TwidereNetworkImageLoader( + buildRealImageLoader(), LocalContext.current, LocalActiveAccount.current ), @@ -89,14 +74,16 @@ fun NetworkImage( } @Composable -fun buildRealImageLoader(httpConfig: HttpConfig): ImageLoader { +fun buildRealImageLoader(): ImageLoader { + val httpConfig = LocalHttpConfig.current return if (httpConfig.proxyConfig.enable && httpConfig.proxyConfig.server.isNotEmpty() ) { LocalImageLoader.current .newBuilder() .callFactory( - httpConfig.createHttpClientBuilder() + TwidereServiceFactory.createHttpClientFactory() + .createHttpClientBuilder() .cache(CoilUtils.createDefaultCache(LocalContext.current)) .build() ) @@ -105,61 +92,3 @@ fun buildRealImageLoader(httpConfig: HttpConfig): ImageLoader { LocalImageLoader.current } } - -private class TwidereImageLoader( - private val realImageLoader: ImageLoader, - private val context: Context, - private val account: AccountDetails? -) : ImageLoader { - private val twitterTonApiHost = "ton.twitter.com" - override val bitmapPool: BitmapPool - get() = realImageLoader.bitmapPool - override val defaults: DefaultRequestOptions - get() = realImageLoader.defaults - override val memoryCache: MemoryCache - get() = realImageLoader.memoryCache - - override fun enqueue(request: ImageRequest): Disposable { - return realImageLoader.enqueue(handleRequest(request)) - } - - override suspend fun execute(request: ImageRequest): ImageResult { - return realImageLoader.execute(handleRequest(request)) - } - - override fun newBuilder(): ImageLoader.Builder { - return ImageLoader.Builder(context) - } - - override fun shutdown() { - realImageLoader.shutdown() - } - - private fun handleRequest(request: ImageRequest): ImageRequest { - var data = request.data - // ton.twitter.com must be retrieved via an authenticated - if (data is String) data = Uri.parse(data) - return if (data is Uri && twitterTonApiHost == data.host && account?.type == PlatformType.Twitter) { - val auth = (account.credentials as OAuthCredentials).let { - OAuth1Authorization( - consumerKey = it.consumer_key, - consumerSecret = it.consumer_secret, - accessToken = it.access_token, - accessSecret = it.access_token_secret, - ) - } - request.newBuilder( - request.context - ).headers( - headers = Headers.headersOf( - "Authorization", - auth.getAuthorizationHeader(Request.Builder().url(URL(data.toString())).build()) - ) - ).build() - } else { - request.newBuilder(request.context) - .data(data) - .build() - } - } -} diff --git a/app/src/main/kotlin/com/twidere/twiderex/di/TwidereModule.kt b/app/src/main/kotlin/com/twidere/twiderex/di/TwidereModule.kt index 360af627e..787898e1a 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/di/TwidereModule.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/di/TwidereModule.kt @@ -27,6 +27,7 @@ import com.twidere.services.nitter.NitterService import com.twidere.twiderex.action.ComposeAction import com.twidere.twiderex.action.DirectMessageAction import com.twidere.twiderex.db.CacheDatabase +import com.twidere.twiderex.http.TwidereServiceFactory import com.twidere.twiderex.model.AccountPreferences import com.twidere.twiderex.notification.InAppNotification import com.twidere.twiderex.preferences.proto.MiscPreferences @@ -62,7 +63,7 @@ object TwidereModule { fun provideNitterService(preferences: DataStore): NitterService? { return runBlocking { preferences.data.first().nitterInstance.takeIf { it.isNotEmpty() } - ?.let { NitterService(it.trimEnd('/')) } + ?.let { NitterService(it.trimEnd('/'), TwidereServiceFactory.createHttpClientFactory()) } } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/http/TwidereNetworkImageLoader.kt b/app/src/main/kotlin/com/twidere/twiderex/http/TwidereNetworkImageLoader.kt new file mode 100644 index 000000000..c95b91cc0 --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/http/TwidereNetworkImageLoader.kt @@ -0,0 +1,96 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.http + +import android.content.Context +import android.net.Uri +import coil.ImageLoader +import coil.bitmap.BitmapPool +import coil.memory.MemoryCache +import coil.request.DefaultRequestOptions +import coil.request.Disposable +import coil.request.ImageRequest +import coil.request.ImageResult +import com.twidere.services.http.authorization.OAuth1Authorization +import com.twidere.twiderex.model.AccountDetails +import com.twidere.twiderex.model.PlatformType +import com.twidere.twiderex.model.cred.OAuthCredentials +import okhttp3.Headers +import okhttp3.Request +import java.net.URL + +class TwidereNetworkImageLoader( + private val realImageLoader: ImageLoader, + private val context: Context, + private val account: AccountDetails? +) : ImageLoader { + private val twitterTonApiHost = "ton.twitter.com" + override val bitmapPool: BitmapPool + get() = realImageLoader.bitmapPool + override val defaults: DefaultRequestOptions + get() = realImageLoader.defaults + override val memoryCache: MemoryCache + get() = realImageLoader.memoryCache + + override fun enqueue(request: ImageRequest): Disposable { + return realImageLoader.enqueue(handleRequest(request)) + } + + override suspend fun execute(request: ImageRequest): ImageResult { + return realImageLoader.execute(handleRequest(request)) + } + + override fun newBuilder(): ImageLoader.Builder { + return ImageLoader.Builder(context) + } + + override fun shutdown() { + realImageLoader.shutdown() + } + + private fun handleRequest(request: ImageRequest): ImageRequest { + var data = request.data + // ton.twitter.com must be retrieved via an authenticated + if (data is String) data = Uri.parse(data) + return if (data is Uri && twitterTonApiHost == data.host && account?.type == PlatformType.Twitter) { + val auth = (account.credentials as OAuthCredentials).let { + OAuth1Authorization( + consumerKey = it.consumer_key, + consumerSecret = it.consumer_secret, + accessToken = it.access_token, + accessSecret = it.access_token_secret, + ) + } + request.newBuilder( + request.context + ).headers( + headers = Headers.headersOf( + "Authorization", + auth.getAuthorizationHeader(Request.Builder().url(URL(data.toString())).build()) + ) + ).build() + } else { + request.newBuilder(request.context) + .data(data) + .build() + } + } +} diff --git a/services/src/main/java/com/twidere/services/http/config/HttpConfig.kt b/services/src/main/java/com/twidere/services/http/config/HttpConfig.kt index 2bc890cdf..52105dc47 100644 --- a/services/src/main/java/com/twidere/services/http/config/HttpConfig.kt +++ b/services/src/main/java/com/twidere/services/http/config/HttpConfig.kt @@ -21,14 +21,7 @@ package com.twidere.services.http.config import com.twidere.services.proxy.ProxyConfig -import okhttp3.OkHttpClient data class HttpConfig( val proxyConfig: ProxyConfig = ProxyConfig() -) { - fun createHttpClientBuilder(): OkHttpClient.Builder { - return OkHttpClient.Builder().let { - proxyConfig.proxy(it) - } - } -} +) diff --git a/services/src/main/java/com/twidere/services/http/config/HttpConfigClientFactory.kt b/services/src/main/java/com/twidere/services/http/config/HttpConfigClientFactory.kt index 4358d401c..fc2e64686 100644 --- a/services/src/main/java/com/twidere/services/http/config/HttpConfigClientFactory.kt +++ b/services/src/main/java/com/twidere/services/http/config/HttpConfigClientFactory.kt @@ -196,7 +196,7 @@ class HttpConfigClientFactory(private val configProvider: HttpConfigProvider) : .create(clazz) } - data class CacheIdentifier( + private data class CacheIdentifier( val config: HttpConfig, val baseUrl: String ) { diff --git a/services/src/main/java/com/twidere/services/nitter/NitterService.kt b/services/src/main/java/com/twidere/services/nitter/NitterService.kt index 219d0aec5..ccb9e9419 100644 --- a/services/src/main/java/com/twidere/services/nitter/NitterService.kt +++ b/services/src/main/java/com/twidere/services/nitter/NitterService.kt @@ -20,16 +20,17 @@ */ package com.twidere.services.nitter +import com.twidere.services.http.HttpClientFactory import com.twidere.services.http.MicroBlogHttpException import com.twidere.services.nitter.model.ConversationTimeline import com.twidere.services.nitter.model.TweetNotFound import com.twidere.services.utils.await import moe.tlaster.hson.Hson -import okhttp3.OkHttpClient import okhttp3.Request class NitterService( private val host: String, + private val httpClientFactory: HttpClientFactory, ) { suspend fun conversation( screenName: String, @@ -43,8 +44,7 @@ class NitterService( it } } - return OkHttpClient - .Builder() + return httpClientFactory.createHttpClientBuilder() .addNetworkInterceptor { it.proceed(it.request()).also { if (it.code != 200) { diff --git a/services/src/main/java/com/twidere/services/proxy/ProxyConfig.kt b/services/src/main/java/com/twidere/services/proxy/ProxyConfig.kt index 56cc247f0..4b22b1db6 100644 --- a/services/src/main/java/com/twidere/services/proxy/ProxyConfig.kt +++ b/services/src/main/java/com/twidere/services/proxy/ProxyConfig.kt @@ -20,11 +20,6 @@ */ package com.twidere.services.proxy -import okhttp3.Credentials -import okhttp3.OkHttpClient -import java.net.InetSocketAddress -import java.net.Proxy - data class ProxyConfig( val enable: Boolean = false, val server: String = "", @@ -38,45 +33,4 @@ data class ProxyConfig( HTTP, REVERSE } - - fun proxy(builder: OkHttpClient.Builder): OkHttpClient.Builder { - return if (enable) { - when (type) { - Type.HTTP -> { - if (port !in (0..65535)) { - return builder - } - val address = InetSocketAddress.createUnresolved( - server, - port - ) - builder.proxy(Proxy(Proxy.Type.HTTP, address)) - .proxyAuthenticator { _, response -> - val b = response.request.newBuilder() - if (response.code == 407) { - if (userName.isNotEmpty() && - password.isNotEmpty() - ) { - val credential = Credentials.basic( - userName, - password - ) - b.header("Proxy-Authorization", credential) - } - } - b.build() - } - } - Type.REVERSE -> { - builder.addInterceptor(ReverseProxyInterceptor(server, userName, password)) - } - } - } else { - builder - } - } - - /** - * Intercept and replace proxy patterns to real URL - */ } diff --git a/services/src/test/java/com/twidere/services/HttpConfigClientFactoryTest.kt b/services/src/test/java/com/twidere/services/HttpConfigClientFactoryTest.kt new file mode 100644 index 000000000..91b07973d --- /dev/null +++ b/services/src/test/java/com/twidere/services/HttpConfigClientFactoryTest.kt @@ -0,0 +1,75 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.services + +import com.twidere.services.http.HttpConfigProvider +import com.twidere.services.http.authorization.EmptyAuthorization +import com.twidere.services.http.config.HttpConfig +import com.twidere.services.http.config.HttpConfigClientFactory +import com.twidere.services.twitter.api.TwitterResources +import org.junit.jupiter.api.Test +import kotlin.test.assertNotSame +import kotlin.test.assertSame + +class HttpConfigClientFactoryTest { + private var config = HttpConfig() + private val configProvider = object : HttpConfigProvider { + override fun provideConfig(): HttpConfig { + return config + } + } + + @Test + fun createResourceUsingCache() { + val factory = HttpConfigClientFactory(configProvider) + val resourceOld = factory.createResources( + TwitterResources::class.java, + baseUrl = "https://www.twitter.com", + EmptyAuthorization(), + useCache = true + ) + + val resourceNew = factory.createResources( + TwitterResources::class.java, + baseUrl = "https://www.twitter.com", + EmptyAuthorization(), + useCache = true + ) + assertSame(resourceOld, resourceNew) + } + + @Test + fun createResourceNotUsingCache() { + val factory = HttpConfigClientFactory(configProvider) + val resourceOld = factory.createResources( + TwitterResources::class.java, + baseUrl = "https://www.twitter.com", + EmptyAuthorization(), + ) + + val resourceNew = factory.createResources( + TwitterResources::class.java, + baseUrl = "https://www.twitter.com", + EmptyAuthorization(), + ) + assertNotSame(resourceOld, resourceNew) + } +} diff --git a/services/src/test/java/com/twidere/services/api/common/MockServices.kt b/services/src/test/java/com/twidere/services/api/common/MockServices.kt index c14019bca..8d05a6e97 100644 --- a/services/src/test/java/com/twidere/services/api/common/MockServices.kt +++ b/services/src/test/java/com/twidere/services/api/common/MockServices.kt @@ -22,25 +22,45 @@ package com.twidere.services.api.common import com.twidere.services.api.mastodon.MastodonRequest2AssetPathConvertor import com.twidere.services.api.twitter.TwitterRequest2AssetPathConvertor +import com.twidere.services.http.HttpClientFactory +import com.twidere.services.http.authorization.Authorization import com.twidere.services.mastodon.MastodonService +import com.twidere.services.mastodon.api.MastodonResources import com.twidere.services.twitter.TwitterService +import com.twidere.services.twitter.api.TwitterResources +import okhttp3.OkHttpClient + +class MockClientFactory : HttpClientFactory { + override fun createHttpClientBuilder(): OkHttpClient.Builder { + TODO("Not yet implemented") + } + + @Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST") + override fun createResources(clazz: Class, baseUrl: String, authorization: Authorization, useCache: Boolean): T { + return when (clazz) { + MastodonResources::class.java -> mockRetrofit( + "https://test.mastodon.com/", + MastodonRequest2AssetPathConvertor() + ) + TwitterResources::class.java -> mockRetrofit( + "https://api.twitter.com/", + TwitterRequest2AssetPathConvertor() + ) + else -> throw NotImplementedError() + } as T + } +} fun mockMastodonService(): MastodonService { return MastodonService( "", "", - resources = mockRetrofit( - "https://test.mastodon.com/", - MastodonRequest2AssetPathConvertor() - ) + httpClientFactory = MockClientFactory() ) } fun mockTwitterService(): TwitterService { return TwitterService( "", "", "", "", - resources = mockRetrofit( - "https://api.twitter.com/", - TwitterRequest2AssetPathConvertor() - ) + httpClientFactory = MockClientFactory() ) } From 7ff16319adf8b5e9d91e4d9699284e5aa175b6bb Mon Sep 17 00:00:00 2001 From: Tlaster Date: Tue, 20 Jul 2021 16:39:09 +0800 Subject: [PATCH 036/137] add localization script --- app/build.gradle.kts | 68 +++++++++++++++++++ .../res/values/strings_untranslatable.xml | 4 ++ app/translate.gradle | 41 ----------- buildSrc/src/main/kotlin/Dependencies.kt | 1 - 4 files changed, 72 insertions(+), 42 deletions(-) create mode 100644 app/src/main/res/values/strings_untranslatable.xml delete mode 100644 app/translate.gradle diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c48255246..e6f0c5fe1 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -2,6 +2,8 @@ import com.google.protobuf.gradle.builtins import com.google.protobuf.gradle.generateProtoTasks import com.google.protobuf.gradle.protobuf import com.google.protobuf.gradle.protoc +import org.gradle.kotlin.dsl.support.unzipTo +import org.json.JSONObject import java.util.Properties buildscript { @@ -201,3 +203,69 @@ dependencies { mockito() androidTest() } + +tasks.create("generateTranslation") { + val localizationFolder = File(rootDir, "localization") + val appJson = File(localizationFolder, "app.json") + val target = project.file("src/main/res/values/strings.xml") + generateLocalization(appJson, target) +} + +tasks.create("generateTranslationFromZip") { + val zip = File(rootProject.buildDir, "Twidere X (translations).zip") + val unzipTarget = File(rootProject.buildDir, "translation").apply { + mkdirs() + } + unzipTo(unzipTarget, zip) + unzipTarget.listFiles()?.forEach { file -> + val source = File(file, "app.json") + val target = project.file( + "src/main/res-localized" + "/values-" + file.name.split('_') + .first() + "-r" + file.name.split('_').last() + "/strings.xml" + ) + generateLocalization(source, target) + } +} + +fun generateLocalization(appJson: File, target: File) { + val json = appJson.readText(Charsets.UTF_8) + val obj = JSONObject(json) + val result = flattenJson(obj).filter { + it.value.isNotEmpty() && it.value.isNotBlank() + } + if (result.isNotEmpty()) { + val xml = + """""" + System.lineSeparator() + + result.map { + " ${ + it.value.replace("'", "\\'").replace(System.lineSeparator(), "\\n") + }" + }.joinToString(System.lineSeparator()) + System.lineSeparator() + + "" + target.writeText(xml) + } +} + +fun flattenJson(obj: JSONObject): Map { + return obj.toMap().toList().flatMap { it -> + val (key, value) = it + when (value) { + is JSONObject -> { + flattenJson(value).map { + "${key}_${it.key}" to it.value + }.toList() + } + is Map<*, *> -> { + flattenJson(JSONObject(value)).map { + "${key}_${it.key}" to it.value + }.toList() + } + is String -> { + listOf(key to value) + } + else -> { + listOf(key to value.toString()) + } + } + }.toMap() +} diff --git a/app/src/main/res/values/strings_untranslatable.xml b/app/src/main/res/values/strings_untranslatable.xml new file mode 100644 index 000000000..23ef44dd9 --- /dev/null +++ b/app/src/main/res/values/strings_untranslatable.xml @@ -0,0 +1,4 @@ + + + Twidere X + \ No newline at end of file diff --git a/app/translate.gradle b/app/translate.gradle deleted file mode 100644 index cc02bbf3b..000000000 --- a/app/translate.gradle +++ /dev/null @@ -1,41 +0,0 @@ -import groovy.json.JsonSlurper - -task generateTranslate { - def translate = project.file('translate') - println(translate.absolutePath.toString()) - def files = translate.listFiles() - for (def file : files) { - def jsonFile = new File(file, "app.json") - if (!jsonFile.exists()) { - continue - } - def text = jsonFile.getText("UTF-8") - def json = new JsonSlurper().parseText(text) - def result = flattenMap(json).findAll { - !it.value.isEmpty() && !it.value.isAllWhitespace() - } - if (result.size() == 0) { - continue - } - def xml = """""" + System.lineSeparator() + - result.collect { - ' '+it.value.replace("'", "\\'").replace(System.lineSeparator(), "\\n")+'' - }.join(System.lineSeparator()) + System.lineSeparator() + - "" - def target = project.file('src/main/res-localized' + '/values-' + file.name.split('_').first() + '-r' + file.name.split('_').last() + "/strings.xml") - target.getParentFile().mkdirs() - target.createNewFile() - target.write(xml) - } -} - -def flattenMap(Map map) { - map.collectEntries { k, v -> - v instanceof Map ? - flattenMap(v).collectEntries { k1, v1 -> - [ "${k}_${k1}": v1 ] - } - : - [ (k): v ] - } -} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index 9a6c50fe7..2dbf6b9e5 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -1,4 +1,3 @@ - import org.gradle.api.JavaVersion import org.gradle.api.Project import org.gradle.kotlin.dsl.DependencyHandlerScope From c0123514d630148e96e00118d2a68c3df38caaf1 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Tue, 20 Jul 2021 16:44:25 +0800 Subject: [PATCH 037/137] split versions from dependencies --- app/build.gradle.kts | 6 +-- build.gradle.kts | 4 +- buildSrc/src/main/kotlin/Dependencies.kt | 50 ------------------------ buildSrc/src/main/kotlin/Versions.kt | 48 +++++++++++++++++++++++ services/build.gradle.kts | 6 +-- 5 files changed, 56 insertions(+), 58 deletions(-) create mode 100644 buildSrc/src/main/kotlin/Versions.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e6f0c5fe1..19d8b7773 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -28,7 +28,7 @@ plugins { kotlin("android") kotlin("kapt") id("com.google.protobuf").version("0.8.17") - kotlin("plugin.serialization").version(Versions.kotlin) + kotlin("plugin.serialization").version(Versions.Kotlin.lang) id("com.google.devtools.ksp").version(Versions.ksp) } @@ -135,8 +135,8 @@ android { } } compileOptions { - sourceCompatibility = Lang.java - targetCompatibility = Lang.java + sourceCompatibility = Versions.Java.java + targetCompatibility = Versions.Java.java } buildFeatures { compose = true diff --git a/build.gradle.kts b/build.gradle.kts index bc83d4afd..cb48e5761 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,7 +7,7 @@ buildscript { google() } dependencies { - classpath(kotlin("gradle-plugin", version = Versions.kotlin)) + classpath(kotlin("gradle-plugin", version = Versions.Kotlin.lang)) classpath("com.android.tools.build:gradle:${Versions.agp}") } } @@ -17,7 +17,7 @@ allprojects { tasks.withType { kotlinOptions { - jvmTarget = Lang.jvmTarget + jvmTarget = Versions.Java.jvmTarget allWarningsAsErrors = true freeCompilerArgs = listOf( "-Xopt-in=kotlin.RequiresOptIn", diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index 2dbf6b9e5..4dbb2f28e 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -1,4 +1,3 @@ -import org.gradle.api.JavaVersion import org.gradle.api.Project import org.gradle.kotlin.dsl.DependencyHandlerScope import org.gradle.kotlin.dsl.maven @@ -15,55 +14,6 @@ fun Project.configRepository() { val Project.enableGoogleVariant: Boolean get() = file("google-services.json").exists() -object Lang { - const val jvmTarget = "11" - val java = JavaVersion.VERSION_11 -} - -object Versions { - const val kotlin = "1.5.10" - - object Kotlin { - const val lang = "1.5.10" - const val coroutines = "1.5.0" - const val serialization = "1.2.1" - } - - const val ksp = "1.5.10-1.0.0-beta02" - const val agp = "7.0.0-beta05" - const val spotless = "5.12.5" - const val ktlint = "0.41.0" - const val hilt = "2.37" - const val okhttp = "4.9.1" - const val retrofit2 = "2.9.0" - const val hson = "0.1.4" - const val compose = "1.0.0-rc02" - const val constraintLayout = "1.0.0-alpha07" - const val paging = "3.1.0-alpha02" - const val paging_compose = "1.0.0-alpha10" - const val activity = "1.3.0-rc02" - const val datastore = "1.0.0-rc01" - const val androidx_hilt = "1.0.0" - const val room = "2.4.0-alpha03" - const val lifecycle = "2.4.0-alpha02" - const val lifecycle_compose = "1.0.0-alpha07" - const val work = "2.7.0-alpha04" - const val placeholder = "0.7.0" - const val zoomable = "1.0.1" - const val swiper = "0.6.0" - const val nestedScrollView = "0.7.0" - const val startup = "1.1.0-alpha01" - const val coil = "1.3.0" - const val accompanist = "0.14.0" - const val androidx_exifinterface = "1.3.2" - const val exoplayer = "2.14.1" - const val browser = "1.3.0" - const val protobuf = "3.17.3" - const val androidx_test = "1.4.0" - const val extJUnitVersion = "1.1.3-rc01" - const val espressoVersion = "3.4.0-rc01" -} - fun DependencyHandlerScope.kotlinCoroutines() { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core", Versions.Kotlin.coroutines) testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test", Versions.Kotlin.coroutines) diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt new file mode 100644 index 000000000..bd27ee8e8 --- /dev/null +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -0,0 +1,48 @@ +import org.gradle.api.JavaVersion + +object Versions { + object Kotlin { + const val lang = "1.5.10" + const val coroutines = "1.5.0" + const val serialization = "1.2.1" + } + + object Java { + const val jvmTarget = "11" + val java = JavaVersion.VERSION_11 + } + + const val ksp = "1.5.10-1.0.0-beta02" + const val agp = "7.0.0-beta05" + const val spotless = "5.12.5" + const val ktlint = "0.41.0" + const val hilt = "2.37" + const val okhttp = "4.9.1" + const val retrofit2 = "2.9.0" + const val hson = "0.1.4" + const val compose = "1.0.0-rc02" + const val constraintLayout = "1.0.0-alpha07" + const val paging = "3.1.0-alpha02" + const val paging_compose = "1.0.0-alpha10" + const val activity = "1.3.0-rc02" + const val datastore = "1.0.0-rc01" + const val androidx_hilt = "1.0.0" + const val room = "2.4.0-alpha03" + const val lifecycle = "2.4.0-alpha02" + const val lifecycle_compose = "1.0.0-alpha07" + const val work = "2.7.0-alpha04" + const val placeholder = "0.7.0" + const val zoomable = "1.0.1" + const val swiper = "0.6.0" + const val nestedScrollView = "0.7.0" + const val startup = "1.1.0-alpha01" + const val coil = "1.3.0" + const val accompanist = "0.14.0" + const val androidx_exifinterface = "1.3.2" + const val exoplayer = "2.14.1" + const val browser = "1.3.0" + const val protobuf = "3.17.3" + const val androidx_test = "1.4.0" + const val extJUnitVersion = "1.1.3-rc01" + const val espressoVersion = "3.4.0-rc01" +} diff --git a/services/build.gradle.kts b/services/build.gradle.kts index e1f9b2d82..c6ee6618b 100644 --- a/services/build.gradle.kts +++ b/services/build.gradle.kts @@ -1,11 +1,11 @@ plugins { kotlin("jvm") - kotlin("plugin.serialization").version(Versions.kotlin) + kotlin("plugin.serialization").version(Versions.Kotlin.lang) } java { - sourceCompatibility = Lang.java - targetCompatibility = Lang.java + sourceCompatibility = Versions.Java.java + targetCompatibility = Versions.Java.java } tasks.test { From 2b094effe9f58e46074b37236d3205507efc97fa Mon Sep 17 00:00:00 2001 From: Tlaster Date: Tue, 20 Jul 2021 16:44:34 +0800 Subject: [PATCH 038/137] update strings.xml --- app/src/main/res/values/strings.xml | 590 ++++++++++++++-------------- 1 file changed, 300 insertions(+), 290 deletions(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7b9547808..1f40a0c51 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,131 +1,133 @@ - Twidere X - Account Temporarily Locked - Open Twitter to unlock - Rate Limit Exceeded - Reached Twitter API usage limit - Permission Denied - Sorry, you are not authorized - Permission Denied - You have been blocked from following this account at the request of the user - Account Suspended - Twitter suspends accounts which violate the %s - Twitter Rules - No Tweets Found + %s has been unmuted Unfollow user %s? - Cancel follow request for %s? + Media will be shared after download is completed + Failed to Unfollowing + Please try again Tweet Sent - Sending tweet - Failed to Tweet - Please try again - Tweet Deleted - Failed to Delete Tweet - Please try again - Saving media - Media saved + Failed to Login + Server URL is incorrect. + Too Many Requests + Following Succeeded + Failed to Following + Please try again + %s has been reported for spam + Twitter Rules + Account Suspended + Twitter suspends accounts which violate the %s Failed to save media Please try again - Media will be shared after download is completed - Photo Saved + %s has been blocked + Cancel follow request for %s? + Failed to report %s + Please try again + %s has been reported for spam and blocked Failed to Save Photo Please try again + No Tweets Found + Saving media + Failed to Login + Connection timeout. + %s has been unblocked + %s has been muted + Permission Denied + Sorry, you are not authorized + Sending message + Failed to send message + Failed to unmute %s + Please try again + Rate Limit Exceeded + Reached Twitter API usage limit Failed to Load Please try again - Failed to Following - Please try again - Failed to Unfollowing - Please try again - Following Succeeded - Unfollowing Succeeded + Permission Denied + You have been blocked from following this account at the request of the user + Media saved + Tweet Deleted Following Request Sent - Too Many Requests - %s has been muted Failed to mute %s Please try again - %s has been unmuted - Failed to unmute %s - Please try again - %s has been blocked + Sending tweet + Failed to Delete Tweet + Please try again + Photo Saved + Account Temporarily Locked + Open Twitter to unlock Failed to block %s Please try again - %s has been unblocked - Failed to unblock %s - Please try again - %s has been reported for spam - Failed to report %s - Please try again - %s has been reported for spam and blocked + Failed to Tweet + Please try again + Unfollowing Succeeded Failed to report and block %s Please try again - Failed to Login - Server URL is incorrect. - Failed to Login - Connection timeout. - Sending message - Failed to send message - Your poll has ended - A poll you have voted in has ended + Failed to unblock %s + Please try again + Messages + Direct messages + Interactions + Interactions like mentions and retweets + Background progresses %s boosted your toot - %s just posted %s has requested to follow you - %s favourited your toot - %s followed you + Your poll has ended %s mentions you New direct message %s sent you a message + A poll you have voted in has ended + %s favourited your toot + %s followed you + %s just posted + Block %s + follower + followers + %s is not following you + Follows you + Mute %s + %s is following you + Unblock + Report and Block + Following + Pending + Report + Mute + Unmute + Block + Follow + Unfollow + Followers + Listed + Following + Load More + Photo Library Add - Remove + Cancel + Preview + Share media Edit + Open in Safari + Yes Save - OK + Remove Confirm - Yes - Cancel - Take photo Save photo + Take photo + OK Sign in - Preview - Open in Safari - Share media - Load More - Copy text - Quote - Retweet - Copy link - Share link - Delete tweet - Vote - Media %s retweeted - Closed + Media %s person %s vote - %s people + Closed %s votes + %s people Show this thread - Follow - Unfollow - Following - Pending - Mute - Unmute - Block - Unblock - Report - Report and Block - follower - followers - Follows you - %s is not following you - %s is following you - Mute %s - Block %s - Following - Followers - Listed - Photo Library - %s reply - %s replies + Share link + Delete tweet + Quote + Retweet + Copy text + Vote + Copy link %s quote %s quotes %s retweet @@ -134,229 +136,237 @@ %s likes %s member %s members - %s list - %s lists - %s tweet - %s tweets %s photo %s photos - Background progresses - Interactions - Interactions like mentions and retweets - Messages - Direct messages - Sign in - Manage accounts - Accounts - Delete account - Hello!\nSign in to Get Started. - Sign in with Twitter - Sign in with Mastodon - Sign in with Custom Twitter Key - Twitter API v2 access is required. - Authentication - Timeline - Mentions - Notification - All - Messages - [Photo] - Copy message text - Delete message for you - Find people - Search people - Send message failed - Likes - Bookmark - Trends - Trends - Worldwide - Trending Now - %d people talking - Tweet - Toot - 1 Reply - %d Replies - 1 Quote - %d Quotes - 1 Retweet - %d Retweets - 1 Like - %d Likes - Search - Search tweets or users - Saved Search - Tweets - Media - Users - Hashtag - Show more - Show less - Me - Hide reply - Permission Denied - You have been blocked from viewing this user’s profile. - All tweets - Exclude replies - Following - Followers - Listed - Compose - Reply - Quote - Reply to … - , - and - What’s happening? - Write your warning here - Others in this conversation: - Save draft? - Save draft - Replying to - Multiple choice - 5 minutes - 30 minutes - 1 hour - 6 hours - 1 day - 3 days - 7 days - Public - Unlisted - Private - Direct - Search users - Search hashtag - Drafts - Delete draft - Edit draft - Lists - MY LISTS - SUBSCRIBED - Private visibility - Create list + %s tweet + %s tweets + %s reply + %s replies + %s list + %s lists + Network Image + More + Back + Twidere X logo + Twitter Logo + Github Logo + Mastodon Logo + Telegram Logo + Play video + Close + Done + Location + Media + Retweet + Like + Reply + Retweeted + Font Size + Save + History + Add + Add image + Add mention + Open draft + Enable location + Disable location + Thread mode + Send + Load + Website + Media + Favourite + Statuses + Location + Compose + Account DropDown + Menu + Rename the list + Create a list + Private + Edit List + Name + New List + Description + Search people + Add Member + Add + Remove + All + Notification + Delete account + Accounts + Show less + Search tweets or users + Media + Tweets + Users + Hashtag + Search + Show more + Saved Search + No Members Found. + Subscribers + List Members + Delete this list + Add Members Lists Details - 1 Member %d Members 1 Subscriber + 1 Member %d Subscribers - Add Member + Delete this list: %s Edit List Rename List Delete List Follow Unfollow - List Members - Subscribers - Delete this list: %s - No Members Found. - Add Members - Delete this list - New List - Edit List - Name - Description - Private - Create a list - Rename the list - Add - Remove - Add Member - Search people - Settings - General - About - Appearance - Highlight color + Add Member + Trending Now + %d people talking + Trends + Trends - Worldwide + Sign in with Mastodon + Hello!\nSign in to Get Started. + Sign in with Twitter + Sign in with Custom Twitter Key + Twitter API v2 access is required. + Authentication + Likes + Show Notification + Accounts + Notification Pick color - Tab position - Theme - Scrolling timeline + AMOLED optimized mode Top Bottom + Highlight color Auto Light Dark - AMOLED optimized mode + Appearance Hide tab bar when scrolling - Hide app bar when scrolling Hide FAB when scrolling - Display - Preview - Text - Date Format - Media + Hide app bar when scrolling + Tab position + Theme + Scrolling timeline Thanks for using @TwidereProject! + Url previews + Absolute + Relative Use the system font size + Rounded Square Avatar Style Circle - Rounded Square - Relative - Absolute + Always Media previews - Auto playback Automatic - Always + Auto playback Off - Url previews - Notification - Show Notification - Accounts - Storage + Display + Preview + Date Format + Text + Media + License + Next generation of Twidere for Android 5.0+. \nStill in early stage. + About page background logo + About page background logo shadow + About + Ver %s + Delete all Twidere X cache. Your account credentials will not be lost. + Clear all cache Clear search history - Clear media cache Clear stored media cache. - Clear all cache - Delete all Twidere X cache. Your account credentials will not be lost. - Misc - Third-party Twitter data provider - Nitter Instance + Clear media cache + Storage + Settings + General + About + Server + Password + Port + Proxy server port must be numbers + Use proxy for all network requests + Proxy + Proxy settings + HTTP + Proxy type + Reverse + Username Alternative Twitter front-end focused on privacy. - Third party Twitter data provider - Due to the limitation of Twitter API, some data might not be able to fetch from Twitter, you can use a third-party data provider to provide these data. Twidere does not take any responsibility for them. + Nitter Instance Using Third-party data provider in - - Twitter status threading Project URL - About - License - Next generation of Twidere for Android 5.0+. \nStill in early stage. - Ver %s - About page background logo - About page background logo shadow - Network Image - Back - More - Close - Done - Twidere X logo - Twitter Logo - Mastodon Logo - Github Logo - Telegram Logo - Play video - Location - Retweeted - Media - Reply - Retweet - Like - Compose - Menu - Account DropDown - History - Save - Load - Website - Location - Statuses - Media - Favourite - Send - Enable location - Disable location - Add mention - Add image - Open draft - Thread mode - Add - Font Size + - Twitter status threading + Third party Twitter data provider + Due to the limitation of Twitter API, some data might not be able to fetch from Twitter, you can use a third-party data provider to provide these data. Twidere does not take any responsibility for them. + Third-party Twitter data provider + Misc + All tweets + Exclude replies + Hide reply + Me + Permission Denied + You have been blocked from viewing this user’s profile. + Manage accounts + Sign in + Drafts + Delete draft + Edit draft + Search users + Bookmark + Followers + Listed + Save draft + Save draft? + Reply to … + Private + Public + Unlisted + Direct + , + Write your warning here + and + What’s happening? + Quote + Compose + Reply + Others in this conversation: + Multiple choice + 1 day + 30 minutes + 7 days + 1 hour + 3 days + 5 minutes + 6 hours + Replying to + SUBSCRIBED + MY LISTS + Lists + Private visibility + Create list + Mentions + Following + Timeline + [Photo] + Search people + Find people + Send message failed + Copy message text + Delete message for you + Messages + Search hashtag + 1 Quote + %d Quotes + 1 Retweet + %d Retweets + 1 Like + %d Likes + Toot + Tweet + 1 Reply + %d Replies \ No newline at end of file From 81f4cf59adf9de5e5fe757fb3660a1b51a164e0b Mon Sep 17 00:00:00 2001 From: Tlaster Date: Tue, 20 Jul 2021 16:55:19 +0800 Subject: [PATCH 039/137] fix tasks being invoke early --- app/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 19d8b7773..1add165da 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -204,14 +204,14 @@ dependencies { androidTest() } -tasks.create("generateTranslation") { +tasks.register("generateTranslation") { val localizationFolder = File(rootDir, "localization") val appJson = File(localizationFolder, "app.json") val target = project.file("src/main/res/values/strings.xml") generateLocalization(appJson, target) } -tasks.create("generateTranslationFromZip") { +tasks.register("generateTranslationFromZip") { val zip = File(rootProject.buildDir, "Twidere X (translations).zip") val unzipTarget = File(rootProject.buildDir, "translation").apply { mkdirs() From 4ab81ce8f4c41f2e128a5cc25cf7dbee5be422f0 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Tue, 20 Jul 2021 17:12:05 +0800 Subject: [PATCH 040/137] fix spotless file header --- build.gradle.kts | 6 +++--- spotless/{license.kt => license} | 0 2 files changed, 3 insertions(+), 3 deletions(-) rename spotless/{license.kt => license} (100%) diff --git a/build.gradle.kts b/build.gradle.kts index cb48e5761..8ee741f0a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -28,9 +28,9 @@ allprojects { spotless { kotlin { target("**/*.kt") - targetExclude("$buildDir/**/*.kt", "bin/**/*.kt") + targetExclude("$buildDir/**/*.kt", "bin/**/*.kt", "buildSrc/**/*.kt") ktlint(Versions.ktlint) - // licenseHeaderFile(rootProject.file("spotless/license.kt")) + licenseHeaderFile(rootProject.file("spotless/license")) } kotlinGradle { target("*.gradle.kts") @@ -39,7 +39,7 @@ allprojects { java { target("**/*.java") targetExclude("$buildDir/**/*.java", "bin/**/*.java") - // licenseHeaderFile(rootProject.file("spotless/license.kt")) + licenseHeaderFile(rootProject.file("spotless/license")) } } } diff --git a/spotless/license.kt b/spotless/license similarity index 100% rename from spotless/license.kt rename to spotless/license From 1dc5a3f1e0b44308cbe8e7352a0e2d5a192527e5 Mon Sep 17 00:00:00 2001 From: itsMimao Date: Tue, 20 Jul 2021 17:20:00 +0800 Subject: [PATCH 041/137] remove unused code --- .../twidere/twiderex/model/AccountDetails.kt | 25 +------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/AccountDetails.kt b/app/src/main/kotlin/com/twidere/twiderex/model/AccountDetails.kt index 3dc3763e1..87cb36f64 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/model/AccountDetails.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/model/AccountDetails.kt @@ -21,7 +21,6 @@ package com.twidere.twiderex.model import android.accounts.Account -import com.twidere.services.microblog.MicroBlogService import com.twidere.twiderex.http.TwidereServiceFactory import com.twidere.twiderex.model.adapter.AndroidAccountSerializer import com.twidere.twiderex.model.cred.BasicCredentials @@ -59,29 +58,7 @@ data class AccountDetails( CredentialsType.OAuth2 -> credentials_json.fromJson() } - val service by lazy { - // when (type) { - // PlatformType.Twitter -> { - // credentials.let { - // it as OAuthCredentials - // }.let { - // TwitterService( - // consumer_key = it.consumer_key, - // consumer_secret = it.consumer_secret, - // access_token = it.access_token, - // access_token_secret = it.access_token_secret, - // ) - // } - // } - // PlatformType.StatusNet -> TODO() - // PlatformType.Fanfou -> TODO() - // PlatformType.Mastodon -> - // credentials.let { - // it as OAuth2Credentials - // }.let { - // MastodonService(accountKey.host, it.access_token) - // } - // } + val service by lazy { TwidereServiceFactory.createApiService( type = type, credentials = credentials, From de49ef3a9aca7756ff1e6834d62e0b1ad7d11858 Mon Sep 17 00:00:00 2001 From: itsMimao Date: Tue, 20 Jul 2021 18:11:02 +0800 Subject: [PATCH 042/137] add proxy for video player --- app/build.gradle | 2 +- .../component/foundation/VideoPlayer.kt | 67 +++++++++++++------ gradle/libs.versions.toml | 7 +- 3 files changed, 53 insertions(+), 23 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 907aa5b0e..47efc04cc 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -201,7 +201,7 @@ dependencies { implementation libs.protobuf.javalite - implementation libs.exoplayer + implementation libs.bundles.exoplayer implementation libs.constraintLayout diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/VideoPlayer.kt b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/VideoPlayer.kt index 2ebf72292..4cb43c642 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/VideoPlayer.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/VideoPlayer.kt @@ -51,11 +51,16 @@ import androidx.lifecycle.OnLifecycleEvent import com.google.android.exoplayer2.MediaItem import com.google.android.exoplayer2.Player import com.google.android.exoplayer2.SimpleExoPlayer +import com.google.android.exoplayer2.ext.okhttp.OkHttpDataSource +import com.google.android.exoplayer2.source.DefaultMediaSourceFactory import com.google.android.exoplayer2.source.ProgressiveMediaSource import com.google.android.exoplayer2.ui.PlayerControlView import com.google.android.exoplayer2.ui.StyledPlayerView +import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory import com.twidere.twiderex.R import com.twidere.twiderex.component.status.UserAvatarDefaults +import com.twidere.twiderex.http.TwidereServiceFactory +import com.twidere.twiderex.preferences.LocalHttpConfig import com.twidere.twiderex.preferences.proto.DisplayPreferences import com.twidere.twiderex.ui.LocalIsActiveNetworkMetered import com.twidere.twiderex.ui.LocalVideoPlayback @@ -85,35 +90,55 @@ fun VideoPlayer( var autoPlay by remember(url) { mutableStateOf(playInitial) } val context = LocalContext.current val lifecycle = LocalLifecycleOwner.current.lifecycle + val httpConfig = LocalHttpConfig.current Box { if (playInitial) { val player = remember(url) { - SimpleExoPlayer.Builder(context).build().apply { - repeatMode = Player.REPEAT_MODE_ALL - playWhenReady = autoPlay - addListener(object : Player.Listener { - override fun onPlaybackStateChanged(state: Int) { - shouldShowThumb = state != Player.STATE_READY + SimpleExoPlayer.Builder(context) + .apply { + if (httpConfig.proxyConfig.enable) { + // replace DataSource + OkHttpDataSource.Factory( + TwidereServiceFactory + .createHttpClientFactory() + .createHttpClientBuilder() + .build() + ) + .let { + DefaultDataSourceFactory(context, it) + }.let { + DefaultMediaSourceFactory(it) + }.let { + setMediaSourceFactory(it) + } } + } + .build().apply { + repeatMode = Player.REPEAT_MODE_ALL + playWhenReady = autoPlay + addListener(object : Player.Listener { + override fun onPlaybackStateChanged(state: Int) { + shouldShowThumb = state != Player.STATE_READY + } - override fun onIsPlayingChanged(isPlaying: Boolean) { - playing = isPlaying - } - }) + override fun onIsPlayingChanged(isPlaying: Boolean) { + playing = isPlaying + } + }) - setVolume(volume) - ProgressiveMediaSource.Factory( - CacheDataSourceFactory( - context, - 5 * 1024 * 1024, - ) - ).createMediaSource(MediaItem.fromUri(url)).also { - setMediaSource(it) + setVolume(volume) + ProgressiveMediaSource.Factory( + CacheDataSourceFactory( + context, + 5 * 1024 * 1024, + ) + ).createMediaSource(MediaItem.fromUri(url)).also { + setMediaSource(it) + } + prepare() + seekTo(VideoPool.get(url)) } - prepare() - seekTo(VideoPool.get(url)) - } } fun updateState() { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6dac4c3bd..b73025813 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -126,7 +126,8 @@ twittertext = "com.twitter.twittertext:twitter-text:3.1.0" jsoup = "org.jsoup:jsoup:1.13.1" leakcanary = "com.squareup.leakcanary:leakcanary-android:2.7" protobuf-javalite = { module = "com.google.protobuf:protobuf-javalite", version.ref = "protobuf" } -exoplayer = { module = "com.google.android.exoplayer:exoplayer", version.ref = "exoplayer" } +exoplayer-core = { module = "com.google.android.exoplayer:exoplayer", version.ref = "exoplayer" } +exoplayer-okhttp = { module = "com.google.android.exoplayer:extension-okhttp", version.ref = "exoplayer" } startup = { module = "androidx.startup:startup-runtime", version.ref = "startup" } vectordrawable = "androidx.vectordrawable:vectordrawable:1.1.0" hson = { module = "com.github.Tlaster:Hson", version.ref = "hson" } @@ -195,4 +196,8 @@ retrofit = [ datastore = [ "datastore-main", "datastore-preferences" +] +exoplayer = [ + "exoplayer-core", + "exoplayer-okhttp" ] \ No newline at end of file From 2d51d09fa58e66a18e5e3edf2dfaa917af986a16 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Wed, 21 Jul 2021 15:41:38 +0800 Subject: [PATCH 043/137] add localization --- .../twidere/twiderex/scenes/settings/LayoutScene.kt | 11 ++++++----- .../twidere/twiderex/scenes/settings/SettingsScene.kt | 2 +- app/src/main/res/values/strings.xml | 5 +++++ localization | 2 +- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/LayoutScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/LayoutScene.kt index bc285e436..75ed06414 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/LayoutScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/LayoutScene.kt @@ -46,6 +46,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.lifecycle.flowWithLifecycle import com.twidere.twiderex.R import com.twidere.twiderex.component.foundation.AppBar @@ -107,7 +108,7 @@ fun LayoutScene() { AppBarNavigationButton() }, title = { - Text(text = "Layout") + Text(text = stringResource(id = R.string.scene_settings_layout_title)) } ) } @@ -130,10 +131,10 @@ fun LayoutScene() { } ListItem( text = { - Text(text = "Custom Layout") + Text(text = stringResource(id = R.string.scene_settings_layout_desc_title)) }, secondaryText = { - Text(text = "Choose and arrange up to 5 actions that will appear on the tabbar (The local and federal timelines will only be displayed in Mastodon.") + Text(text = stringResource(id = R.string.scene_settings_layout_desc_content)) } ) ReorderableColumn( @@ -173,9 +174,9 @@ private fun LayoutItemContent( ItemHeader { Text( text = if (it) { - "Tabbar actions" + stringResource(id = R.string.scene_settings_layout_actions_tabbar) } else { - "Sidebar actions" + stringResource(id = R.string.scene_settings_layout_actions_drawer) } ) } diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/SettingsScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/SettingsScene.kt index 1020a7080..db9710e8d 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/SettingsScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/SettingsScene.kt @@ -65,7 +65,7 @@ fun SettingsScene() { route = Route.Settings.Display, ), SettingItem( - "Layout", + stringResource(id = R.string.scene_settings_layout_title), painterResource(id = R.drawable.ic_layout_sidebar), route = Route.Settings.Layout, ), diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1f40a0c51..6e1bdb77b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -233,6 +233,11 @@ Twitter API v2 access is required. Authentication Likes + Layout + Tabbar actions + Drawer actions + Custom Layout + Choose and arrange up to 5 actions that will appear on the tabbar (The local and federal timelines will only be displayed in Mastodon.) Show Notification Accounts Notification diff --git a/localization b/localization index 6b979766f..9e5f4ec70 160000 --- a/localization +++ b/localization @@ -1 +1 @@ -Subproject commit 6b979766f1f47bcf4dc4dc966b43f023f7905e60 +Subproject commit 9e5f4ec708d1269eca7e01a73f196efbfd571ed4 From 34060ddc9a670453e44b3cc3b55dfef1e52f69d1 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Wed, 21 Jul 2021 16:45:20 +0800 Subject: [PATCH 044/137] fix localization --- localization | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/localization b/localization index 9e5f4ec70..2d079390d 160000 --- a/localization +++ b/localization @@ -1 +1 @@ -Subproject commit 9e5f4ec708d1269eca7e01a73f196efbfd571ed4 +Subproject commit 2d079390dc20bd1a87ad5df74704953fbf69e844 From d1676aac6a53cd6dc1fff0211f97fb0d96a24d26 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Wed, 21 Jul 2021 18:27:35 +0800 Subject: [PATCH 045/137] add route generator --- app/build.gradle.kts | 3 + routeProcessor/build.gradle.kts | 16 ++ routeProcessor/src/main/kotlin/AppRoute.kt | 23 +++ .../src/main/kotlin/RouteProcessor.kt | 138 ++++++++++++++++++ ...ols.ksp.processing.SymbolProcessorProvider | 1 + settings.gradle.kts | 2 +- 6 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 routeProcessor/build.gradle.kts create mode 100644 routeProcessor/src/main/kotlin/AppRoute.kt create mode 100644 routeProcessor/src/main/kotlin/RouteProcessor.kt create mode 100644 routeProcessor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1add165da..34986909f 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -175,6 +175,7 @@ protobuf { // TODO: workaround for https://github.com/google/ksp/issues/518 evaluationDependsOn(":assistedProcessor") +evaluationDependsOn(":routeProcessor") dependencies { android() @@ -182,6 +183,8 @@ dependencies { kotlinCoroutines() implementation(projects.services) ksp(projects.assistedProcessor) + implementation(projects.routeProcessor) + ksp(projects.routeProcessor) compose() paging() datastore() diff --git a/routeProcessor/build.gradle.kts b/routeProcessor/build.gradle.kts new file mode 100644 index 000000000..17926e017 --- /dev/null +++ b/routeProcessor/build.gradle.kts @@ -0,0 +1,16 @@ +plugins { + kotlin("jvm") +} + +repositories { + mavenCentral() + google() +} + +dependencies { + kspApi() +} + +sourceSets.main { + java.srcDirs("src/main/kotlin") +} diff --git a/routeProcessor/src/main/kotlin/AppRoute.kt b/routeProcessor/src/main/kotlin/AppRoute.kt new file mode 100644 index 000000000..7d9805a3d --- /dev/null +++ b/routeProcessor/src/main/kotlin/AppRoute.kt @@ -0,0 +1,23 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.route.processor + +annotation class AppRoute \ No newline at end of file diff --git a/routeProcessor/src/main/kotlin/RouteProcessor.kt b/routeProcessor/src/main/kotlin/RouteProcessor.kt new file mode 100644 index 000000000..2ea1d227d --- /dev/null +++ b/routeProcessor/src/main/kotlin/RouteProcessor.kt @@ -0,0 +1,138 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.route.processor + +import com.google.devtools.ksp.processing.CodeGenerator +import com.google.devtools.ksp.processing.Dependencies +import com.google.devtools.ksp.processing.Resolver +import com.google.devtools.ksp.processing.SymbolProcessor +import com.google.devtools.ksp.processing.SymbolProcessorEnvironment +import com.google.devtools.ksp.processing.SymbolProcessorProvider +import com.google.devtools.ksp.symbol.KSAnnotated +import com.google.devtools.ksp.symbol.KSClassDeclaration +import com.google.devtools.ksp.symbol.KSDeclaration +import com.google.devtools.ksp.symbol.KSFunctionDeclaration +import com.google.devtools.ksp.symbol.KSNode +import com.google.devtools.ksp.symbol.KSPropertyDeclaration +import com.google.devtools.ksp.visitor.KSEmptyVisitor +import java.io.OutputStream + +class RouteProcessor( + private val codeGenerator: CodeGenerator, +) : SymbolProcessor { + override fun process(resolver: Resolver): List { + val routeSymbol = resolver + .getSymbolsWithAnnotation("com.twidere.route.processor.AppRoute") + .filterIsInstance() + routeSymbol.forEach { it.accept(RouteVisitor(), routeSymbol.toList()) } + return emptyList() + } + + inner class RouteVisitor : KSEmptyVisitor, Unit>() { + override fun defaultHandler(node: KSNode, data: List) { + if (node !is KSClassDeclaration) { + return + } + val packageName = node.packageName.asString() + val className = "${node.qualifiedName?.getShortName()}Route" + codeGenerator.createNewFile( + Dependencies( + true, + *(data.mapNotNull { it.containingFile } + listOfNotNull(node.containingFile)).toTypedArray() + ), + packageName, + className + ).use { outputStream -> + outputStream.appendLine("package $packageName") + outputStream.appendLine() + outputStream.appendLine("import java.net.URLEncoder") + outputStream.appendLine() + outputStream.appendLine("public object $className {") + + generateRoute( + node.declarations.toList(), + outputStream, + parentPath = "", + ) + + outputStream.appendLine("}") + } + } + + private fun generateRoute( + declarations: List, + outputStream: OutputStream, + parentPath: String = "", + indent: String = " ", + ) { + declarations.forEach { declaration -> + val pathName = declaration.simpleName.getShortName() + when (declaration) { + is KSClassDeclaration -> { + outputStream.appendLine("${indent}object $pathName {") + generateRoute( + declaration.declarations.toList(), + outputStream, + "$parentPath/$pathName", + indent + indent, + ) + outputStream.appendLine("$indent}") + } + is KSFunctionDeclaration -> { + val parameterStr = declaration.parameters.map { parameter -> + val name = parameter.name?.getShortName() ?: "_" + val type = + parameter.type.resolve().declaration.qualifiedName?.asString() + ?: "" + "$name: $type" + }.joinToString(", ") + val path = declaration.parameters.joinToString("/") { parameter -> + val name = parameter.name?.getShortName() ?: "_" + "{$name}" + + } + val pathWithParameter = + declaration.parameters.joinToString("/") { parameter -> + val name = parameter.name?.getShortName() ?: "_" + "\${URLEncoder.encode($name, \"UTF-8\")}" + } + + outputStream.appendLine("${indent}const val $pathName = \"$parentPath/${pathName}/$path\"") + outputStream.appendLine("${indent}fun ${pathName}($parameterStr) = \"$parentPath/${pathName}/$pathWithParameter\"") + } + is KSPropertyDeclaration -> { + outputStream.appendLine("${indent}const val $pathName = \"$parentPath/${pathName}\"") + } + } + } + } + } +} + +private fun OutputStream.appendLine(str: String = "") { + this.write("$str${System.lineSeparator()}".toByteArray()) +} + +class RouteProcessorProvider : SymbolProcessorProvider { + override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor { + return RouteProcessor(environment.codeGenerator) + } +} diff --git a/routeProcessor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider b/routeProcessor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider new file mode 100644 index 000000000..4d95f268b --- /dev/null +++ b/routeProcessor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider @@ -0,0 +1 @@ +com.twidere.route.processor.RouteProcessorProvider diff --git a/settings.gradle.kts b/settings.gradle.kts index a5693a1fa..019542bb2 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -6,7 +6,7 @@ pluginManagement { } rootProject.name = "TwidereX" -include(":app", ":services", ":assistedProcessor") +include(":app", ":services", ":assistedProcessor", ":routeProcessor") enableFeaturePreview("VERSION_CATALOGS") enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") From 85065f48b46cf1e593e2b1e7a4fba6ad61609407 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Wed, 21 Jul 2021 18:31:46 +0800 Subject: [PATCH 046/137] fix build --- routeProcessor/src/main/kotlin/AppRoute.kt | 2 +- routeProcessor/src/main/kotlin/RouteProcessor.kt | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/routeProcessor/src/main/kotlin/AppRoute.kt b/routeProcessor/src/main/kotlin/AppRoute.kt index 7d9805a3d..e189cab88 100644 --- a/routeProcessor/src/main/kotlin/AppRoute.kt +++ b/routeProcessor/src/main/kotlin/AppRoute.kt @@ -20,4 +20,4 @@ */ package com.twidere.route.processor -annotation class AppRoute \ No newline at end of file +annotation class AppRoute diff --git a/routeProcessor/src/main/kotlin/RouteProcessor.kt b/routeProcessor/src/main/kotlin/RouteProcessor.kt index 2ea1d227d..cf231bb48 100644 --- a/routeProcessor/src/main/kotlin/RouteProcessor.kt +++ b/routeProcessor/src/main/kotlin/RouteProcessor.kt @@ -107,7 +107,6 @@ class RouteProcessor( val path = declaration.parameters.joinToString("/") { parameter -> val name = parameter.name?.getShortName() ?: "_" "{$name}" - } val pathWithParameter = declaration.parameters.joinToString("/") { parameter -> @@ -115,8 +114,8 @@ class RouteProcessor( "\${URLEncoder.encode($name, \"UTF-8\")}" } - outputStream.appendLine("${indent}const val $pathName = \"$parentPath/${pathName}/$path\"") - outputStream.appendLine("${indent}fun ${pathName}($parameterStr) = \"$parentPath/${pathName}/$pathWithParameter\"") + outputStream.appendLine("${indent}const val $pathName = \"$parentPath/$pathName/$path\"") + outputStream.appendLine("${indent}fun $pathName($parameterStr) = \"$parentPath/$pathName/$pathWithParameter\"") } is KSPropertyDeclaration -> { outputStream.appendLine("${indent}const val $pathName = \"$parentPath/${pathName}\"") From e347babbb4a7b3553f7fa1ae636c6650e2506bec Mon Sep 17 00:00:00 2001 From: Tlaster Date: Wed, 21 Jul 2021 18:47:19 +0800 Subject: [PATCH 047/137] add root route --- .../com/twidere/twiderex/navigation/Root.kt | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 app/src/main/kotlin/com/twidere/twiderex/navigation/Root.kt diff --git a/app/src/main/kotlin/com/twidere/twiderex/navigation/Root.kt b/app/src/main/kotlin/com/twidere/twiderex/navigation/Root.kt new file mode 100644 index 000000000..623b35199 --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/navigation/Root.kt @@ -0,0 +1,120 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.navigation + +import com.twidere.route.processor.AppRoute +import com.twidere.twiderex.model.MicroBlogKey +import com.twidere.twiderex.viewmodel.compose.ComposeType + +@AppRoute +interface Root { + val Home: String + val HomeTimeline: String + val Notification: String + val Mentions: String + val Me: String + + interface Draft { + val List: String + fun Compose(draftId: String) + } + + interface SignIn { + val General: String + fun Twitter(consumerKey: String, consumerSecret: String) + val Mastodon: String + } + fun User(userKey: MicroBlogKey) + + interface Media { + fun Status(statusKey: MicroBlogKey, selectedIndex: Int) + fun Raw(url: String) + fun Pure(belongToKey: MicroBlogKey, selectedIndex: Int) + } + + interface Search { + val Home: String + fun Search(keyword: String) + fun SearchInput(keyword: String? = null) + } + + fun Compose(composeType: ComposeType, statusKey: MicroBlogKey? = null) + + interface Compose { + interface Search { + val User: String + } + } + + fun Following(userKey: MicroBlogKey) + fun Followers(userKey: MicroBlogKey) + + interface Settings { + val Home: String + val Appearance: String + val Display: String + val Storage: String + val About: String + val AccountManagement: String + val Misc: String + val Notification: String + val Layout: String + fun AccountNotification(accountKey: MicroBlogKey) + } + + interface DeepLink { + interface Twitter { + val User: String + val Status: String + } + fun Draft(id: String): String + fun Compose(composeType: ComposeType, statusKey: MicroBlogKey? = null) + fun Conversation(conversationKey: MicroBlogKey) + } + + fun Status(statusKey: MicroBlogKey) + + interface Mastodon { + fun Hashtag(keyword: String) + val Notification: String + + interface Compose { + val Hashtag: String + } + } + + interface Lists { + val Home: String + val MastodonCreateDialog: String + val TwitterCreate: String + fun TwitterEdit(listKey: MicroBlogKey) + fun Timeline(listKey: MicroBlogKey) + fun Members(listKey: MicroBlogKey, owned: Boolean) + fun Subscribers(listKey: MicroBlogKey) + fun AddMembers(listKey: MicroBlogKey) + } + + interface Messages { + val Home: String + fun Conversation(conversationKey: MicroBlogKey) + val NewConversation: String + } +} From f73a4f3543168c74c59a22a0a5ace7758e061bf5 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Wed, 21 Jul 2021 19:06:37 +0800 Subject: [PATCH 048/137] add query support --- .../com/twidere/twiderex/navigation/Root.kt | 6 +- .../src/main/kotlin/RouteProcessor.kt | 70 +++++++++++++++---- 2 files changed, 59 insertions(+), 17 deletions(-) diff --git a/app/src/main/kotlin/com/twidere/twiderex/navigation/Root.kt b/app/src/main/kotlin/com/twidere/twiderex/navigation/Root.kt index 623b35199..e2214d8c6 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/navigation/Root.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/navigation/Root.kt @@ -53,10 +53,10 @@ interface Root { interface Search { val Home: String fun Search(keyword: String) - fun SearchInput(keyword: String? = null) + fun SearchInput(keyword: String?) } - fun Compose(composeType: ComposeType, statusKey: MicroBlogKey? = null) + fun Compose(composeType: ComposeType, statusKey: MicroBlogKey?) interface Compose { interface Search { @@ -86,7 +86,7 @@ interface Root { val Status: String } fun Draft(id: String): String - fun Compose(composeType: ComposeType, statusKey: MicroBlogKey? = null) + fun Compose(composeType: ComposeType, statusKey: MicroBlogKey?) fun Conversation(conversationKey: MicroBlogKey) } diff --git a/routeProcessor/src/main/kotlin/RouteProcessor.kt b/routeProcessor/src/main/kotlin/RouteProcessor.kt index cf231bb48..05d837155 100644 --- a/routeProcessor/src/main/kotlin/RouteProcessor.kt +++ b/routeProcessor/src/main/kotlin/RouteProcessor.kt @@ -97,25 +97,67 @@ class RouteProcessor( outputStream.appendLine("$indent}") } is KSFunctionDeclaration -> { - val parameterStr = declaration.parameters.map { parameter -> - val name = parameter.name?.getShortName() ?: "_" - val type = - parameter.type.resolve().declaration.qualifiedName?.asString() + val parameterStr = declaration.parameters + .joinToString(", ") { parameter -> + val name = parameter.name?.getShortName() ?: "_" + val type = parameter.type.resolve() + .declaration.qualifiedName?.asString() + .let { + if (parameter.type.resolve().isMarkedNullable) { + "$it? = null" + } else { + it + } + } ?: "" - "$name: $type" - }.joinToString(", ") - val path = declaration.parameters.joinToString("/") { parameter -> - val name = parameter.name?.getShortName() ?: "_" - "{$name}" - } - val pathWithParameter = - declaration.parameters.joinToString("/") { parameter -> + "$name: $type" + } + val query = declaration.parameters + .filter { it.type.resolve().isMarkedNullable } + .joinToString("&") { parameter -> + val name = parameter.name?.getShortName() ?: "_" + "$name=\$$name" + } + .let { + if (it.isNotEmpty()) { + "?$it" + } else { + it + } + } + val path = declaration.parameters + .filter { !it.type.resolve().isMarkedNullable } + .joinToString("/") { parameter -> + val name = parameter.name?.getShortName() ?: "_" + "{$name}" + } + .let { + if (it.isNotEmpty()) { + "/$it" + } else { + it + } + } + val pathWithParameter = declaration.parameters + .filter { !it.type.resolve().isMarkedNullable } + .joinToString("/") { parameter -> val name = parameter.name?.getShortName() ?: "_" "\${URLEncoder.encode($name, \"UTF-8\")}" } + .let { + if (it.isNotEmpty()) { + "/$it" + } else { + it + } + } - outputStream.appendLine("${indent}const val $pathName = \"$parentPath/$pathName/$path\"") - outputStream.appendLine("${indent}fun $pathName($parameterStr) = \"$parentPath/$pathName/$pathWithParameter\"") + outputStream.appendLine( + "${indent}const val $pathName = \"$parentPath/$pathName$path\"" + ) + outputStream.appendLine( + "${indent}fun $pathName($parameterStr) = \"$parentPath/$pathName$pathWithParameter${query}\"" + ) } is KSPropertyDeclaration -> { outputStream.appendLine("${indent}const val $pathName = \"$parentPath/${pathName}\"") From 0b937e5c12a243f2d4c2000bc98f774774c4a9a5 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Wed, 21 Jul 2021 19:06:48 +0800 Subject: [PATCH 049/137] add retention and target for app route --- routeProcessor/src/main/kotlin/AppRoute.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/routeProcessor/src/main/kotlin/AppRoute.kt b/routeProcessor/src/main/kotlin/AppRoute.kt index e189cab88..f57ad13b9 100644 --- a/routeProcessor/src/main/kotlin/AppRoute.kt +++ b/routeProcessor/src/main/kotlin/AppRoute.kt @@ -20,4 +20,6 @@ */ package com.twidere.route.processor +@Retention(AnnotationRetention.SOURCE) +@Target(AnnotationTarget.CLASS) annotation class AppRoute From 9080d2687fcb4f15c134b48bbc06585bc846ca16 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Wed, 21 Jul 2021 19:09:16 +0800 Subject: [PATCH 050/137] fix indent --- routeProcessor/src/main/kotlin/RouteProcessor.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/routeProcessor/src/main/kotlin/RouteProcessor.kt b/routeProcessor/src/main/kotlin/RouteProcessor.kt index 05d837155..38f429ab9 100644 --- a/routeProcessor/src/main/kotlin/RouteProcessor.kt +++ b/routeProcessor/src/main/kotlin/RouteProcessor.kt @@ -35,6 +35,8 @@ import com.google.devtools.ksp.symbol.KSPropertyDeclaration import com.google.devtools.ksp.visitor.KSEmptyVisitor import java.io.OutputStream +private val StandardIndent = " " + class RouteProcessor( private val codeGenerator: CodeGenerator, ) : SymbolProcessor { @@ -81,7 +83,7 @@ class RouteProcessor( declarations: List, outputStream: OutputStream, parentPath: String = "", - indent: String = " ", + indent: String = StandardIndent, ) { declarations.forEach { declaration -> val pathName = declaration.simpleName.getShortName() @@ -92,7 +94,7 @@ class RouteProcessor( declaration.declarations.toList(), outputStream, "$parentPath/$pathName", - indent + indent, + indent + StandardIndent, ) outputStream.appendLine("$indent}") } From c4fc5fa71282593a41a7521ecf8ecf1d561c48c7 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Thu, 22 Jul 2021 14:26:38 +0800 Subject: [PATCH 051/137] update route generation --- app/build.gradle.kts | 8 + .../com/twidere/twiderex/navigation/Root.kt | 3 +- .../src/main/kotlin/RouteDefinition.kt | 151 +++++++++++++++ .../src/main/kotlin/RouteProcessor.kt | 174 +++++++----------- .../src/main/kotlin/RouteProcessorProvider.kt | 31 ++++ 5 files changed, 258 insertions(+), 109 deletions(-) create mode 100644 routeProcessor/src/main/kotlin/RouteDefinition.kt create mode 100644 routeProcessor/src/main/kotlin/RouteProcessorProvider.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 34986909f..e34dc305d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -127,6 +127,14 @@ android { srcDirs("src/${it.name}/kotlin") } } + applicationVariants.all { + val variantName = name + sourceSets { + getByName("main") { + java.srcDir(File("build/generated/ksp/$variantName/kotlin")) + } + } + } sourceSets { findByName("androidTest")?.let { it.assets { diff --git a/app/src/main/kotlin/com/twidere/twiderex/navigation/Root.kt b/app/src/main/kotlin/com/twidere/twiderex/navigation/Root.kt index e2214d8c6..4d88d2605 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/navigation/Root.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/navigation/Root.kt @@ -56,9 +56,8 @@ interface Root { fun SearchInput(keyword: String?) } - fun Compose(composeType: ComposeType, statusKey: MicroBlogKey?) - interface Compose { + fun Home(composeType: ComposeType, statusKey: MicroBlogKey?) interface Search { val User: String } diff --git a/routeProcessor/src/main/kotlin/RouteDefinition.kt b/routeProcessor/src/main/kotlin/RouteDefinition.kt new file mode 100644 index 000000000..a36195413 --- /dev/null +++ b/routeProcessor/src/main/kotlin/RouteDefinition.kt @@ -0,0 +1,151 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.route.processor + +private const val StandardIndent = " " + +internal interface RouteDefinition { + val name: String + val parent: RouteDefinition? + fun generateDefinition(): String + fun generateRoute(): String +} + +internal fun RouteDefinition.parents(): List { + val list = arrayListOf() + var p = parent + while (p != null) { + list.add(0, p) + p = p.parent + } + return list +} + +internal val RouteDefinition.parentPath + get() = parents().joinToString("/") { it.name } + +internal val RouteDefinition.indent + get() = parents().joinToString("") { StandardIndent } + +internal data class NestedRouteDefinition( + override val name: String, + override val parent: RouteDefinition? = null, + val childRoute: ArrayList = arrayListOf(), +) : RouteDefinition { + override fun generateDefinition(): String { + return "${indent}object $name {${System.lineSeparator()}" + + childRoute.joinToString(System.lineSeparator()) { it.generateDefinition() } + + System.lineSeparator() + + "$indent}" + } + + override fun generateRoute(): String { + return "${indent}object $name {${System.lineSeparator()}" + + childRoute.joinToString(System.lineSeparator()) { it.generateRoute() } + + System.lineSeparator() + + "$indent}" + } +} + +internal data class ConstRouteDefinition( + override val name: String, + override val parent: RouteDefinition? = null, +) : RouteDefinition { + override fun generateDefinition(): String { + return "${indent}const val $name = \"$parentPath/${name}\"" + } + + override fun generateRoute(): String { + return "${indent}const val $name = \"$parentPath/${name}\"" + } +} + +internal data class FunctionRouteDefinition( + override val name: String, + override val parent: RouteDefinition? = null, + val parameters: List, +) : RouteDefinition { + override fun generateDefinition(): String { + val path = parameters + .filter { !it.isNullable } + .joinToString("/") { parameter -> + "{${parameter.name}}" + } + .let { + if (it.isNotEmpty()) { + "/$it" + } else { + it + } + } + return "${indent}const val $name = \"$parentPath/$name$path\"" + } + + override fun generateRoute(): String { + val query = parameters + .filter { it.isNullable } + .joinToString("&") { parameter -> + val name = parameter.name + "$name=\$$name" + } + .let { + if (it.isNotEmpty()) { + "?$it" + } else { + it + } + } + val parameterStr = parameters + .joinToString(", ") { parameter -> + val name = parameter.name + val type = parameter.type + .let { + if (parameter.isNullable) { + "$it? = null" + } else { + it + } + } + "$name: $type" + } + val pathWithParameter = parameters + .filter { !it.isNullable } + .joinToString("/") { parameter -> + val name = parameter.name + "\${$name}" + } + .let { + if (it.isNotEmpty()) { + "/$it" + } else { + it + } + } + + return "${indent}fun $name($parameterStr) = \"$parentPath/$name$pathWithParameter${query}\"" + } +} + +internal data class RouteParameter( + val name: String, + val type: String, + val isNullable: Boolean = false, +) diff --git a/routeProcessor/src/main/kotlin/RouteProcessor.kt b/routeProcessor/src/main/kotlin/RouteProcessor.kt index 38f429ab9..8c185f4ed 100644 --- a/routeProcessor/src/main/kotlin/RouteProcessor.kt +++ b/routeProcessor/src/main/kotlin/RouteProcessor.kt @@ -24,8 +24,6 @@ import com.google.devtools.ksp.processing.CodeGenerator import com.google.devtools.ksp.processing.Dependencies import com.google.devtools.ksp.processing.Resolver import com.google.devtools.ksp.processing.SymbolProcessor -import com.google.devtools.ksp.processing.SymbolProcessorEnvironment -import com.google.devtools.ksp.processing.SymbolProcessorProvider import com.google.devtools.ksp.symbol.KSAnnotated import com.google.devtools.ksp.symbol.KSClassDeclaration import com.google.devtools.ksp.symbol.KSDeclaration @@ -35,8 +33,6 @@ import com.google.devtools.ksp.symbol.KSPropertyDeclaration import com.google.devtools.ksp.visitor.KSEmptyVisitor import java.io.OutputStream -private val StandardIndent = " " - class RouteProcessor( private val codeGenerator: CodeGenerator, ) : SymbolProcessor { @@ -53,118 +49,88 @@ class RouteProcessor( if (node !is KSClassDeclaration) { return } + + val route = generateRoute(declaration = node) + if (route !is NestedRouteDefinition) { + return + } + val packageName = node.packageName.asString() - val className = "${node.qualifiedName?.getShortName()}Route" + val routeClassName = "${node.qualifiedName?.getShortName()}Route" + val definitionClassName = "${node.qualifiedName?.getShortName()}RouteDefinition" + val dependencies = Dependencies( + true, + *(data.mapNotNull { it.containingFile } + listOfNotNull(node.containingFile)).toTypedArray() + ) + generateFile( + dependencies, + packageName, + routeClassName, + route.copy(name = routeClassName).generateRoute() + ) + generateFile( + dependencies, + packageName, + definitionClassName, + route.copy(name = definitionClassName).generateDefinition() + ) + } + + private fun generateFile( + dependencies: Dependencies, + packageName: String, + className: String, + content: String + ) { codeGenerator.createNewFile( - Dependencies( - true, - *(data.mapNotNull { it.containingFile } + listOfNotNull(node.containingFile)).toTypedArray() - ), + dependencies, packageName, className ).use { outputStream -> outputStream.appendLine("package $packageName") outputStream.appendLine() - outputStream.appendLine("import java.net.URLEncoder") - outputStream.appendLine() - outputStream.appendLine("public object $className {") - - generateRoute( - node.declarations.toList(), - outputStream, - parentPath = "", - ) - - outputStream.appendLine("}") + outputStream.appendLine(content) } } private fun generateRoute( - declarations: List, - outputStream: OutputStream, - parentPath: String = "", - indent: String = StandardIndent, - ) { - declarations.forEach { declaration -> - val pathName = declaration.simpleName.getShortName() - when (declaration) { - is KSClassDeclaration -> { - outputStream.appendLine("${indent}object $pathName {") - generateRoute( - declaration.declarations.toList(), - outputStream, - "$parentPath/$pathName", - indent + StandardIndent, - ) - outputStream.appendLine("$indent}") - } - is KSFunctionDeclaration -> { - val parameterStr = declaration.parameters - .joinToString(", ") { parameter -> - val name = parameter.name?.getShortName() ?: "_" - val type = parameter.type.resolve() - .declaration.qualifiedName?.asString() - .let { - if (parameter.type.resolve().isMarkedNullable) { - "$it? = null" - } else { - it - } - } - ?: "" - "$name: $type" - } - val query = declaration.parameters - .filter { it.type.resolve().isMarkedNullable } - .joinToString("&") { parameter -> - val name = parameter.name?.getShortName() ?: "_" - "$name=\$$name" - } - .let { - if (it.isNotEmpty()) { - "?$it" - } else { - it - } - } - val path = declaration.parameters - .filter { !it.type.resolve().isMarkedNullable } - .joinToString("/") { parameter -> - val name = parameter.name?.getShortName() ?: "_" - "{$name}" + declaration: KSDeclaration, + parent: RouteDefinition? = null + ): RouteDefinition { + val name = declaration.simpleName.getShortName() + return when (declaration) { + is KSClassDeclaration -> { + NestedRouteDefinition( + name = name, + parent = parent, + ).also { nestedRouteDefinition -> + nestedRouteDefinition.childRoute.addAll( + declaration.declarations.map { + generateRoute(it, nestedRouteDefinition) } - .let { - if (it.isNotEmpty()) { - "/$it" - } else { - it - } - } - val pathWithParameter = declaration.parameters - .filter { !it.type.resolve().isMarkedNullable } - .joinToString("/") { parameter -> - val name = parameter.name?.getShortName() ?: "_" - "\${URLEncoder.encode($name, \"UTF-8\")}" - } - .let { - if (it.isNotEmpty()) { - "/$it" - } else { - it - } - } - - outputStream.appendLine( - "${indent}const val $pathName = \"$parentPath/$pathName$path\"" ) - outputStream.appendLine( - "${indent}fun $pathName($parameterStr) = \"$parentPath/$pathName$pathWithParameter${query}\"" - ) - } - is KSPropertyDeclaration -> { - outputStream.appendLine("${indent}const val $pathName = \"$parentPath/${pathName}\"") } } + is KSPropertyDeclaration -> { + ConstRouteDefinition(name, parent) + } + is KSFunctionDeclaration -> { + FunctionRouteDefinition( + name = name, + parent = parent, + parameters = declaration.parameters.map { + val parameterName = it.name?.getShortName() ?: "_" + val parameterType = it.type.resolve() + RouteParameter( + name = parameterName, + type = parameterType.declaration.qualifiedName?.asString() + ?: "", + isNullable = parameterType.isMarkedNullable, + ) + }, + ) + } + else -> throw NotImplementedError() } } } @@ -173,9 +139,3 @@ class RouteProcessor( private fun OutputStream.appendLine(str: String = "") { this.write("$str${System.lineSeparator()}".toByteArray()) } - -class RouteProcessorProvider : SymbolProcessorProvider { - override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor { - return RouteProcessor(environment.codeGenerator) - } -} diff --git a/routeProcessor/src/main/kotlin/RouteProcessorProvider.kt b/routeProcessor/src/main/kotlin/RouteProcessorProvider.kt new file mode 100644 index 000000000..4c298284d --- /dev/null +++ b/routeProcessor/src/main/kotlin/RouteProcessorProvider.kt @@ -0,0 +1,31 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.route.processor + +import com.google.devtools.ksp.processing.SymbolProcessor +import com.google.devtools.ksp.processing.SymbolProcessorEnvironment +import com.google.devtools.ksp.processing.SymbolProcessorProvider + +class RouteProcessorProvider : SymbolProcessorProvider { + override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor { + return RouteProcessor(environment.codeGenerator) + } +} From 9a98968fb4912c95f3ec7c693b06fa0b4d1a9f05 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Thu, 22 Jul 2021 15:21:59 +0800 Subject: [PATCH 052/137] update route usage --- .../twiderex/component/UserComponent.kt | 10 +- .../component/lazy/ui/LazyUiDMEventList.kt | 4 +- .../component/navigation/Navigator.kt | 27 +- .../component/requireAuthorization.kt | 4 +- .../com/twidere/twiderex/navigation/Root.kt | 15 +- .../com/twidere/twiderex/navigation/Route.kt | 270 ++++-------------- .../twidere/twiderex/scenes/DraftListScene.kt | 4 +- .../com/twidere/twiderex/scenes/HomeScene.kt | 8 +- .../twidere/twiderex/scenes/SignInScene.kt | 8 +- .../twiderex/scenes/compose/ComposeScene.kt | 8 +- .../scenes/dm/DMConversationListScene.kt | 6 +- .../scenes/dm/DMNewConversationScene.kt | 6 +- .../scenes/home/DMConversationListItem.kt | 4 +- .../scenes/home/DraftNavigationItem.kt | 4 +- .../twiderex/scenes/home/HomeTimelineItem.kt | 4 +- .../scenes/home/ListsNavigationItem.kt | 4 +- .../scenes/home/MastodonNotificationItem.kt | 4 +- .../twidere/twiderex/scenes/home/MeItem.kt | 4 +- .../twiderex/scenes/home/MentionItem.kt | 4 +- .../twiderex/scenes/home/NotificationItem.kt | 4 +- .../twiderex/scenes/home/SearchItem.kt | 4 +- .../scenes/lists/ListsMembersScene.kt | 4 +- .../twiderex/scenes/lists/ListsScene.kt | 8 +- .../scenes/lists/ListsTimelineScene.kt | 8 +- .../platform/MastodonListsCreateDialog.kt | 4 +- .../lists/platform/TwitterListsCreateScene.kt | 6 +- .../scenes/settings/AccountManagementScene.kt | 4 +- .../scenes/settings/NotificationScene.kt | 4 +- .../twiderex/scenes/settings/SettingsScene.kt | 16 +- .../scenes/twitter/user/TwitterUserScene.kt | 4 +- .../twidere/twiderex/scenes/user/UserScene.kt | 4 +- .../twiderex/worker/compose/ComposeWorker.kt | 6 +- .../worker/dm/DirectMessageFetchWorker.kt | 4 +- .../worker/dm/DirectMessageSendWorker.kt | 4 +- 34 files changed, 164 insertions(+), 318 deletions(-) diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/UserComponent.kt b/app/src/main/kotlin/com/twidere/twiderex/component/UserComponent.kt index 123af6216..15d3fcc43 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/UserComponent.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/UserComponent.kt @@ -104,7 +104,7 @@ import com.twidere.twiderex.extensions.withElevation import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.PlatformType import com.twidere.twiderex.model.ui.UiUser -import com.twidere.twiderex.navigation.Route +import com.twidere.twiderex.navigation.RootRoute import com.twidere.twiderex.navigation.twidereXSchema import com.twidere.twiderex.ui.LocalActiveAccount import com.twidere.twiderex.ui.LocalNavController @@ -441,7 +441,7 @@ fun UserInfo( size = UserInfoDefaults.AvatarSize ) { if (user.profileImage is String) { - navController.navigate(Route.Media.Raw(user.profileImage)) + navController.navigate(RootRoute.Media.Raw(user.profileImage)) } } } @@ -702,7 +702,7 @@ private fun UserBanner( .heightIn(max = maxBannerSize) .clickable( onClick = { - navController.navigate(Route.Media.Raw(bannerUrl)) + navController.navigate(RootRoute.Media.Raw(bannerUrl)) }, indication = null, interactionSource = remember { MutableInteractionSource() }, @@ -730,7 +730,7 @@ fun UserMetrics( modifier = Modifier .weight(1f) .clickable { - navController.navigate(Route.Following(user.userKey)) + navController.navigate(RootRoute.Following(user.userKey)) }, primaryText = user.friendsCount.toString(), secondaryText = stringResource(id = R.string.common_controls_profile_dashboard_following), @@ -742,7 +742,7 @@ fun UserMetrics( modifier = Modifier .weight(1f) .clickable { - navController.navigate(Route.Followers(user.userKey)) + navController.navigate(RootRoute.Followers(user.userKey)) }, primaryText = user.followersCount.toString(), secondaryText = stringResource(id = R.string.common_controls_profile_dashboard_followers), diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/lazy/ui/LazyUiDMEventList.kt b/app/src/main/kotlin/com/twidere/twiderex/component/lazy/ui/LazyUiDMEventList.kt index 4cb9cbe7a..080eeab4c 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/lazy/ui/LazyUiDMEventList.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/lazy/ui/LazyUiDMEventList.kt @@ -80,7 +80,7 @@ import com.twidere.twiderex.component.status.UserAvatarDefaults import com.twidere.twiderex.db.model.DbDMEvent import com.twidere.twiderex.model.ui.UiDMEvent import com.twidere.twiderex.model.ui.UiMedia -import com.twidere.twiderex.navigation.Route +import com.twidere.twiderex.navigation.RootRoute import com.twidere.twiderex.preferences.proto.DisplayPreferences import com.twidere.twiderex.ui.LocalNavController import com.twidere.twiderex.ui.LocalVideoPlayback @@ -246,7 +246,7 @@ private fun MessageBody(event: UiDMEvent, onItemLongClick: (event: UiDMEvent) -> MediaMessage( media = event.media.firstOrNull(), onClick = { - navController.navigate(Route.Media.Pure(event.messageKey)) + navController.navigate(RootRoute.Media.Pure(event.messageKey)) } ) if (event.media.isNotEmpty() && event.htmlText.isNotEmpty()) Spacer(modifier = Modifier.height(MessageBodyDefaults.ContentSpacing)) diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/navigation/Navigator.kt b/app/src/main/kotlin/com/twidere/twiderex/component/navigation/Navigator.kt index 30712f7b4..a923e5790 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/navigation/Navigator.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/navigation/Navigator.kt @@ -32,7 +32,7 @@ import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.PlatformType import com.twidere.twiderex.model.ui.UiStatus import com.twidere.twiderex.model.ui.UiUser -import com.twidere.twiderex.navigation.Route +import com.twidere.twiderex.navigation.RootRoute import com.twidere.twiderex.navigation.twidereXSchema import com.twidere.twiderex.viewmodel.compose.ComposeType import moe.tlaster.precompose.navigation.NavController @@ -60,7 +60,6 @@ interface INavigator { fun openLink(it: String, deepLink: Boolean = true) {} suspend fun twitterSignInWeb(target: String): String = "" - suspend fun mastodonSignInWeb(target: String): String = "" fun searchInput(initial: String? = null) {} fun hashtag(name: String) {} fun goBack() {} @@ -71,7 +70,7 @@ class Navigator( private val context: Context, ) : INavigator { override fun user(user: UiUser, navOptions: NavOptions?) { - navController.navigate(Route.User(user.userKey), navOptions) + navController.navigate(RootRoute.User(user.userKey), navOptions) } override fun status(status: UiStatus, navOptions: NavOptions?) { @@ -93,7 +92,7 @@ class Navigator( } if (statusKey != null) { navController.navigate( - Route.Status(statusKey), + RootRoute.Status(statusKey), navOptions ) } @@ -104,16 +103,16 @@ class Navigator( selectedIndex: Int, navOptions: NavOptions? ) { - navController.navigate(Route.Media.Status(statusKey, selectedIndex), navOptions) + navController.navigate(RootRoute.Media.Status(statusKey, selectedIndex), navOptions) } override fun search(keyword: String) { - navController.navigate(Route.Search.Search(keyword)) + navController.navigate(RootRoute.Search.Result(keyword)) } override fun searchInput(initial: String?) { navController.navigate( - Route.Search.SearchInput(initial), + RootRoute.Search.Input(initial), ) } @@ -122,7 +121,7 @@ class Navigator( statusKey: MicroBlogKey?, navOptions: NavOptions? ) { - navController.navigate(Route.Compose(composeType, statusKey)) + navController.navigate(RootRoute.Compose.Home(composeType, statusKey)) } override fun openLink(it: String, deepLink: Boolean) { @@ -145,20 +144,12 @@ class Navigator( CookieManager.getInstance().removeAllCookies { } return navController.navigateForResult( - Route.SignIn.Web.Twitter(target) - ).toString() - } - - override suspend fun mastodonSignInWeb(target: String): String { - CookieManager.getInstance().removeAllCookies { - } - return navController.navigateForResult( - Route.SignIn.Web.Mastodon(target) + RootRoute.SignIn.Web.Twitter(target) ).toString() } override fun hashtag(name: String) { - navController.navigate(Route.Mastodon.Hashtag(name)) + navController.navigate(RootRoute.Mastodon.Hashtag(name)) } override fun goBack() { diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/requireAuthorization.kt b/app/src/main/kotlin/com/twidere/twiderex/component/requireAuthorization.kt index 0c629a310..6695efad1 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/requireAuthorization.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/requireAuthorization.kt @@ -22,7 +22,7 @@ package com.twidere.twiderex.component import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import com.twidere.twiderex.navigation.Route +import com.twidere.twiderex.navigation.RootRoute import com.twidere.twiderex.ui.LocalActiveAccount import com.twidere.twiderex.ui.LocalActivity import com.twidere.twiderex.ui.LocalNavController @@ -36,7 +36,7 @@ fun RequireAuthorization( val navController = LocalNavController.current val activity = LocalActivity.current LaunchedEffect(Unit) { - val result = navController.navigateForResult(Route.SignIn.Default) + val result = navController.navigateForResult(RootRoute.SignIn.General) if (result == null) { activity.finish() } diff --git a/app/src/main/kotlin/com/twidere/twiderex/navigation/Root.kt b/app/src/main/kotlin/com/twidere/twiderex/navigation/Root.kt index 4d88d2605..3124d0d22 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/navigation/Root.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/navigation/Root.kt @@ -41,23 +41,26 @@ interface Root { val General: String fun Twitter(consumerKey: String, consumerSecret: String) val Mastodon: String + interface Web { + fun Twitter(url: String) + } } fun User(userKey: MicroBlogKey) interface Media { - fun Status(statusKey: MicroBlogKey, selectedIndex: Int) + fun Status(statusKey: MicroBlogKey, selectedIndex: Int?) fun Raw(url: String) - fun Pure(belongToKey: MicroBlogKey, selectedIndex: Int) + fun Pure(belongToKey: MicroBlogKey, selectedIndex: Int?) } interface Search { val Home: String - fun Search(keyword: String) - fun SearchInput(keyword: String?) + fun Result(keyword: String) + fun Input(keyword: String?) } interface Compose { - fun Home(composeType: ComposeType, statusKey: MicroBlogKey?) + fun Home(composeType: ComposeType?, statusKey: MicroBlogKey?) interface Search { val User: String } @@ -106,7 +109,7 @@ interface Root { val TwitterCreate: String fun TwitterEdit(listKey: MicroBlogKey) fun Timeline(listKey: MicroBlogKey) - fun Members(listKey: MicroBlogKey, owned: Boolean) + fun Members(listKey: MicroBlogKey, owned: Boolean?) fun Subscribers(listKey: MicroBlogKey) fun AddMembers(listKey: MicroBlogKey) } diff --git a/app/src/main/kotlin/com/twidere/twiderex/navigation/Route.kt b/app/src/main/kotlin/com/twidere/twiderex/navigation/Route.kt index 8d03a0d46..e8f2c8009 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/navigation/Route.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/navigation/Route.kt @@ -61,7 +61,6 @@ import com.twidere.twiderex.scenes.lists.platform.TwitterListsCreateScene import com.twidere.twiderex.scenes.lists.platform.TwitterListsEditScene import com.twidere.twiderex.scenes.mastodon.MastodonHashtagScene import com.twidere.twiderex.scenes.mastodon.MastodonSignInScene -import com.twidere.twiderex.scenes.mastodon.MastodonWebSignInScene import com.twidere.twiderex.scenes.search.SearchInputScene import com.twidere.twiderex.scenes.search.SearchScene import com.twidere.twiderex.scenes.search.fadeCreateTransition @@ -99,159 +98,10 @@ import moe.tlaster.precompose.navigation.transition.NavTransition import moe.tlaster.precompose.navigation.transition.fadeScaleCreateTransition import moe.tlaster.precompose.navigation.transition.fadeScaleDestroyTransition import java.net.URLDecoder -import java.net.URLEncoder -const val initialRoute = Route.Home +const val initialRoute = RootRouteDefinition.Home const val twidereXSchema = "twiderex" -object Route { - const val Home = "home" - - const val HomeTimeline = "HomeTimeline" - const val Notification = "Notification" - const val Mentions = "Mentions" - const val Me = "Me" - - object Draft { - const val List = "draft/list" - fun Compose(draftId: String) = "draft/compose/$draftId" - } - - object SignIn { - val Default by lazy { - General - } - const val General = "signin/general" - fun Twitter(consumerKey: String, consumerSecret: String) = - "signin/twitter?consumerKey=$consumerKey&consumerSecret=$consumerSecret" - - const val Mastodon = "signin/mastodon" - - object Web { - fun Twitter(target: String) = "signin/twitter/web/${ - URLEncoder.encode( - target, - "UTF-8" - ) - }" - - fun Mastodon(target: String) = "signin/mastodon/web/${ - URLEncoder.encode( - target, - "UTF-8" - ) - }" - } - } - - fun User(userKey: MicroBlogKey) = - "user/$userKey" - - object Media { - fun Status(statusKey: MicroBlogKey, selectedIndex: Int = 0) = - "media/status/$statusKey?selectedIndex=$selectedIndex" - - fun Raw(url: String) = - "media/raw/${URLEncoder.encode(url, "UTF-8")}" - - fun Pure(belongToKey: MicroBlogKey, selectedIndex: Int = 0) = - "media/pure/$belongToKey?selectedIndex=$selectedIndex" - } - - object Search { - const val Home = "search" - fun Search(keyword: String) = "$Home/result/${ - URLEncoder.encode( - keyword, - "UTF-8" - ) - }" - - fun SearchInput(keyword: String? = null): String { - if (keyword == null) { - return "$Home/input" - } - return "$Home/input?keyword=${ - URLEncoder.encode( - keyword, - "UTF-8" - ) - }" - } - } - - fun Compose(composeType: ComposeType, statusKey: MicroBlogKey? = null) = - "compose?composeType=${composeType.name}${ - if (statusKey != null) { - "&statusKey=$statusKey" - } else { - "" - } - }" - - object Compose { - object Search { - const val User = "compose/search/user" - } - } - - fun Following(userKey: MicroBlogKey) = "following/$userKey" - fun Followers(userKey: MicroBlogKey) = "followers/$userKey" - - object Settings { - const val Home = "settings" - const val Appearance = "settings/appearance" - const val Display = "settings/display" - const val Storage = "settings/storage" - const val About = "settings/about" - const val AccountManagement = "settings/accountmanagement" - const val Misc = "settings/misc" - const val Notification = "settings/notification" - const val Layout = "settings/layout" - fun AccountNotification(accountKey: MicroBlogKey) = "settings/notification/$accountKey" - } - - object DeepLink { - object Twitter { - const val User = "deeplink/twitter/user/{screenName}" - const val Status = "deeplink/twitter/status/{statusId}" - } - fun Draft(id: String) = "$twidereXSchema://draft/compose/$id" - - fun Compose(composeType: ComposeType, statusKey: MicroBlogKey? = null) = "$twidereXSchema://${Route.Compose(composeType, statusKey)}" - - fun Conversation(conversationKey: MicroBlogKey) = "$twidereXSchema://${Messages.Conversation(conversationKey)}" - } - - fun Status(statusKey: MicroBlogKey) = "status/$statusKey" - - object Mastodon { - fun Hashtag(keyword: String) = "mastodon/hashtag/$keyword" - const val Notification = "mastodon/notification" - - object Compose { - const val Hashtag = "mastodon/compose/hashtag" - } - } - - object Lists { - const val Home = "lists" - const val MastodonCreateDialog = "$Home/mastodon/create" - const val TwitterCreate = "$Home/twitter/create" - fun TwitterEdit(listKey: MicroBlogKey) = "$Home/twitter/edit/$listKey" - fun Timeline(listKey: MicroBlogKey) = "$Home/timeline/$listKey" - fun Members(listKey: MicroBlogKey, owned: Boolean) = "$Home/members/$listKey?owned=$owned" - fun Subscribers(listKey: MicroBlogKey) = "$Home/subscribers/$listKey" - fun AddMembers(listKey: MicroBlogKey) = "$Home/members/$listKey/add" - } - - object Messages { - const val Home = "messages" - fun Conversation(conversationKey: MicroBlogKey) = "$Home/conversation/$conversationKey" - const val NewConversation = "$Home/new/conversation" - } -} - object DeepLinks { object Twitter { const val User = "$twidereXSchema://twitter/user" @@ -272,7 +122,7 @@ object DeepLinks { const val Draft = "$twidereXSchema://draft/compose/{draftId}" const val Compose = "$twidereXSchema://compose" - const val Conversation = "$twidereXSchema://${Route.Messages.Home}/conversation/{conversationKey}" + const val Conversation = "$twidereXSchema://${RootRouteDefinition.Messages.Home}/conversation/{conversationKey}" object Callback { object SignIn { @@ -385,30 +235,30 @@ fun RequirePlatformAccount( fun RouteBuilder.route(constraints: Constraints) { authorizedScene( - Route.Home, + RootRouteDefinition.Home, deepLinks = twitterHosts.map { "$it/*" } ) { HomeScene() } - authorizedScene(Route.Mastodon.Notification) { + authorizedScene(RootRouteDefinition.Mastodon.Notification) { MastodonNotificationScene() } - authorizedScene(Route.Me) { + authorizedScene(RootRouteDefinition.Me) { MeScene() } - authorizedScene(Route.Mentions) { + authorizedScene(RootRouteDefinition.Mentions) { MentionScene() } - authorizedScene(Route.HomeTimeline) { + authorizedScene(RootRouteDefinition.HomeTimeline) { HomeTimelineScene() } scene( - Route.SignIn.General, + RootRouteDefinition.SignIn.General, deepLinks = listOf( DeepLinks.SignIn ), @@ -417,37 +267,37 @@ fun RouteBuilder.route(constraints: Constraints) { } scene( - "signin/twitter", + RootRouteDefinition.SignIn.Twitter, ) { backStackEntry -> - val consumerKey = backStackEntry.query("consumerKey") - val consumerSecret = backStackEntry.query("consumerSecret") + val consumerKey = backStackEntry.path("consumerKey") + val consumerSecret = backStackEntry.path("consumerSecret") if (consumerKey != null && consumerSecret != null) { TwitterSignInScene(consumerKey = consumerKey, consumerSecret = consumerSecret) } } - scene(Route.SignIn.Mastodon) { + scene(RootRouteDefinition.SignIn.Mastodon) { MastodonSignInScene() } scene( - "signin/twitter/web/{target}", + RootRouteDefinition.SignIn.Web.Twitter, ) { backStackEntry -> backStackEntry.path("target")?.let { TwitterWebSignInScene(target = URLDecoder.decode(it, "UTF-8")) } } - scene( - "signin/mastodon/web/{target}", - ) { backStackEntry -> - backStackEntry.path("target")?.let { - MastodonWebSignInScene(target = URLDecoder.decode(it, "UTF-8")) - } - } + // scene( + // "signin/mastodon/web/{target}", + // ) { backStackEntry -> + // backStackEntry.path("target")?.let { + // MastodonWebSignInScene(target = URLDecoder.decode(it, "UTF-8")) + // } + // } authorizedScene( - Route.DeepLink.Twitter.User, + RootRouteDefinition.DeepLink.Twitter.User, deepLinks = twitterHosts.map { "$it/{screenName}" } + "${DeepLinks.Twitter.User}/{screenName}" @@ -467,7 +317,7 @@ fun RouteBuilder.route(constraints: Constraints) { } authorizedScene( - "user/{userKey}", + RootRouteDefinition.User, deepLinks = listOf( "${DeepLinks.User}/{userKey}" ) @@ -484,7 +334,7 @@ fun RouteBuilder.route(constraints: Constraints) { } authorizedScene( - "mastodon/hashtag/{keyword}", + RootRouteDefinition.Mastodon.Hashtag, deepLinks = listOf( "${DeepLinks.Mastodon.Hashtag}/{keyword}" ) @@ -495,7 +345,7 @@ fun RouteBuilder.route(constraints: Constraints) { } authorizedScene( - "status/{statusKey}", + RootRouteDefinition.Status, deepLinks = listOf( DeepLinks.Status, ) @@ -512,7 +362,7 @@ fun RouteBuilder.route(constraints: Constraints) { } authorizedScene( - Route.DeepLink.Twitter.Status, + RootRouteDefinition.DeepLink.Twitter.Status, deepLinks = twitterHosts.map { "$it/{screenName}/status/{statusId:[0-9]+}" } + listOf( @@ -537,7 +387,7 @@ fun RouteBuilder.route(constraints: Constraints) { } authorizedDialog( - "media/status/{statusKey}", + RootRouteDefinition.Media.Status, ) { backStackEntry -> backStackEntry.path("statusKey")?.let { MicroBlogKey.valueOf(it) @@ -555,7 +405,7 @@ fun RouteBuilder.route(constraints: Constraints) { } authorizedDialog( - "media/pure/{belongToKey}", + RootRouteDefinition.Media.Pure, ) { backStackEntry -> backStackEntry.path("belongToKey")?.let { MicroBlogKey.valueOf(it) @@ -569,7 +419,7 @@ fun RouteBuilder.route(constraints: Constraints) { } authorizedDialog( - "media/raw/{url}", + RootRouteDefinition.Media.Raw, ) { backStackEntry -> backStackEntry.path("url")?.let { URLDecoder.decode(it, "UTF-8") @@ -578,12 +428,12 @@ fun RouteBuilder.route(constraints: Constraints) { } } - authorizedScene(Route.Search.Home) { + authorizedScene(RootRouteDefinition.Search.Home) { com.twidere.twiderex.scenes.home.SearchScene() } authorizedScene( - "search/input", + RootRouteDefinition.Search.Input, navTransition = NavTransition( createTransition = fadeCreateTransition, destroyTransition = fadeDestroyTransition, @@ -597,7 +447,7 @@ fun RouteBuilder.route(constraints: Constraints) { } authorizedScene( - "search/result/{keyword}", + RootRouteDefinition.Search.Result, deepLinks = twitterHosts.map { "$it/search?q={keyword}" } + "${DeepLinks.Search}/{keyword}", @@ -614,7 +464,7 @@ fun RouteBuilder.route(constraints: Constraints) { } authorizedScene( - "compose", + RootRouteDefinition.Compose.Home, navTransition = NavTransition( createTransition = { translationY = constraints.maxHeight * (1 - it) @@ -649,7 +499,7 @@ fun RouteBuilder.route(constraints: Constraints) { } authorizedScene( - "followers/{userKey}", + RootRouteDefinition.Followers, ) { backStackEntry -> backStackEntry.path("userKey")?.let { MicroBlogKey.valueOf(it) @@ -659,7 +509,7 @@ fun RouteBuilder.route(constraints: Constraints) { } authorizedScene( - "following/{userKey}", + RootRouteDefinition.Following, ) { backStackEntry -> backStackEntry.path("userKey")?.let { MicroBlogKey.valueOf(it) @@ -668,39 +518,41 @@ fun RouteBuilder.route(constraints: Constraints) { } } - scene(Route.Settings.Home) { + scene(RootRouteDefinition.Settings.Home) { SettingsScene() } - scene(Route.Settings.Appearance) { + scene(RootRouteDefinition.Settings.Appearance) { AppearanceScene() } - scene(Route.Settings.Display) { + scene(RootRouteDefinition.Settings.Display) { DisplayScene() } - scene(Route.Settings.Storage) { + scene(RootRouteDefinition.Settings.Storage) { StorageScene() } - scene(Route.Settings.AccountManagement) { + scene(RootRouteDefinition.Settings.AccountManagement) { AccountManagementScene() } - scene(Route.Settings.Misc) { + scene(RootRouteDefinition.Settings.Misc) { MiscScene() } - scene(Route.Settings.Notification) { + scene(RootRouteDefinition.Settings.Notification) { NotificationScene() } - authorizedScene(Route.Settings.Layout) { + authorizedScene(RootRouteDefinition.Settings.Layout) { LayoutScene() } - scene("settings/notification/{accountKey}") { + scene( + RootRouteDefinition.Settings.AccountNotification + ) { it.path("accountKey", null)?.let { MicroBlogKey.valueOf(it) }?.let { @@ -708,16 +560,16 @@ fun RouteBuilder.route(constraints: Constraints) { } } - scene(Route.Settings.About) { + scene(RootRouteDefinition.Settings.About) { AboutScene() } - scene(Route.Draft.List) { + scene(RootRouteDefinition.Draft.List) { DraftListScene() } scene( - "draft/compose/{draftId}", + RootRouteDefinition.Draft.Compose, deepLinks = listOf( DeepLinks.Draft, ) @@ -727,29 +579,29 @@ fun RouteBuilder.route(constraints: Constraints) { } } - authorizedScene(Route.Compose.Search.User) { + authorizedScene(RootRouteDefinition.Compose.Search.User) { ComposeSearchUserScene() } - authorizedScene(Route.Mastodon.Compose.Hashtag) { + authorizedScene(RootRouteDefinition.Mastodon.Compose.Hashtag) { ComposeSearchHashtagScene() } - authorizedScene(Route.Lists.Home) { + authorizedScene(RootRouteDefinition.Lists.Home) { ListsScene() } - authorizedDialog(Route.Lists.MastodonCreateDialog) { + authorizedDialog(RootRouteDefinition.Lists.MastodonCreateDialog) { val navController = LocalNavController.current MastodonListsCreateDialog(onDismissRequest = { navController.goBack() }) } - authorizedScene(Route.Lists.TwitterCreate) { + authorizedScene(RootRouteDefinition.Lists.TwitterCreate) { TwitterListsCreateScene() } authorizedScene( - "${Route.Lists.Home}/twitter/edit/{listKey}" + RootRouteDefinition.Lists.TwitterEdit, ) { backStackEntry -> backStackEntry.path("listKey")?.let { TwitterListsEditScene(listKey = MicroBlogKey.valueOf(it)) @@ -757,7 +609,7 @@ fun RouteBuilder.route(constraints: Constraints) { } authorizedScene( - "${Route.Lists.Home}/timeline/{listKey}" + RootRouteDefinition.Lists.Timeline, ) { backStackEntry -> backStackEntry.path("listKey")?.let { ListTimeLineScene(listKey = MicroBlogKey.valueOf(it)) @@ -765,7 +617,7 @@ fun RouteBuilder.route(constraints: Constraints) { } authorizedScene( - "${Route.Lists.Home}/members/{listKey}" + RootRouteDefinition.Lists.Members, ) { backStackEntry -> backStackEntry.path("listKey")?.let { ListsMembersScene(listKey = MicroBlogKey.valueOf(it), backStackEntry.query("owned") ?: false) @@ -773,7 +625,7 @@ fun RouteBuilder.route(constraints: Constraints) { } authorizedScene( - "${Route.Lists.Home}/subscribers/{listKey}" + RootRouteDefinition.Lists.Subscribers, ) { backStackEntry -> backStackEntry.path("listKey")?.let { ListsSubscribersScene(listKey = MicroBlogKey.valueOf(it)) @@ -781,23 +633,23 @@ fun RouteBuilder.route(constraints: Constraints) { } authorizedScene( - "${Route.Lists.Home}/members/{listKey}/add" + RootRouteDefinition.Lists.AddMembers, ) { backStackEntry -> backStackEntry.path("listKey")?.let { ListsAddMembersScene(listKey = MicroBlogKey.valueOf(it)) } } - authorizedScene(Route.Messages.Home) { + authorizedScene(RootRouteDefinition.Messages.Home) { DMConversationListScene() } - authorizedScene(Route.Messages.NewConversation) { + authorizedScene(RootRouteDefinition.Messages.NewConversation) { DMNewConversationScene() } authorizedScene( - "${Route.Messages.Home}/conversation/{conversationKey}", + RootRouteDefinition.Messages.Conversation, deepLinks = listOf( DeepLinks.Conversation ) diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/DraftListScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/DraftListScene.kt index ec036cd82..494f6bb72 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/DraftListScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/DraftListScene.kt @@ -48,7 +48,7 @@ import com.twidere.twiderex.component.foundation.InAppNotificationScaffold import com.twidere.twiderex.component.lazy.LazyListController import com.twidere.twiderex.di.assisted.assistedViewModel import com.twidere.twiderex.extensions.observeAsState -import com.twidere.twiderex.navigation.Route +import com.twidere.twiderex.navigation.RootRoute import com.twidere.twiderex.ui.LocalNavController import com.twidere.twiderex.ui.TwidereScene import com.twidere.twiderex.viewmodel.DraftViewModel @@ -112,7 +112,7 @@ fun DraftListSceneContent( ) { DropdownMenuItem( onClick = { - navController.navigate(Route.Draft.Compose(it._id)) + navController.navigate(RootRoute.Draft.Compose(it._id)) } ) { Text(text = stringResource(id = R.string.scene_drafts_actions_edit_draft)) diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/HomeScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/HomeScene.kt index ccb688238..14d34b946 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/HomeScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/HomeScene.kt @@ -94,7 +94,7 @@ import com.twidere.twiderex.component.status.UserScreenName import com.twidere.twiderex.extensions.observeAsState import com.twidere.twiderex.extensions.withElevation import com.twidere.twiderex.model.ui.UiUser -import com.twidere.twiderex.navigation.Route +import com.twidere.twiderex.navigation.RootRoute import com.twidere.twiderex.preferences.LocalAppearancePreferences import com.twidere.twiderex.preferences.proto.AppearancePreferences import com.twidere.twiderex.scenes.home.HomeMenus @@ -459,7 +459,7 @@ private fun HomeDrawer(scaffoldState: ScaffoldState) { ListItem( modifier = Modifier.clickable( onClick = { - navController.navigate(Route.SignIn.Default) + navController.navigate(RootRoute.SignIn.General) } ), text = { @@ -472,7 +472,7 @@ private fun HomeDrawer(scaffoldState: ScaffoldState) { ListItem( modifier = Modifier.clickable( onClick = { - navController.navigate(Route.Settings.AccountManagement) + navController.navigate(RootRoute.Settings.AccountManagement) } ), text = { @@ -519,7 +519,7 @@ private fun HomeDrawer(scaffoldState: ScaffoldState) { onClick = { scope.launch { scaffoldState.drawerState.close() - navController.navigate(Route.Settings.Home) + navController.navigate(RootRoute.Settings.Home) } } ), diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/SignInScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/SignInScene.kt index 285efcef4..18a775dee 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/SignInScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/SignInScene.kt @@ -51,7 +51,7 @@ import com.twidere.twiderex.BuildConfig import com.twidere.twiderex.R import com.twidere.twiderex.component.foundation.SignInButton import com.twidere.twiderex.component.foundation.SignInScaffold -import com.twidere.twiderex.navigation.Route +import com.twidere.twiderex.navigation.RootRoute import com.twidere.twiderex.ui.LocalNavController import kotlinx.coroutines.launch @@ -77,7 +77,7 @@ private fun MastodonSignIn() { SignInButton( onClick = { scope.launch { - navController.navigateForResult(Route.SignIn.Mastodon) + navController.navigateForResult(RootRoute.SignIn.Mastodon) ?.let { it as Boolean }?.let { @@ -139,7 +139,7 @@ private fun TwitterSignIn() { onClick = { scope.launch { navController.navigateForResult( - Route.SignIn.Twitter( + RootRoute.SignIn.Twitter( BuildConfig.CONSUMERKEY, BuildConfig.CONSUMERSECRET, ) @@ -229,7 +229,7 @@ private fun TwitterCustomKeySignIn( onClick = { scope.launch { navController.navigateForResult( - Route.SignIn.Twitter( + RootRoute.SignIn.Twitter( apiKey, apiSecret, ) diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/compose/ComposeScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/compose/ComposeScene.kt index b0abf5242..b44794c01 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/compose/ComposeScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/compose/ComposeScene.kt @@ -128,7 +128,7 @@ import com.twidere.twiderex.model.AccountDetails import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.PlatformType import com.twidere.twiderex.model.ui.UiEmoji -import com.twidere.twiderex.navigation.Route +import com.twidere.twiderex.navigation.RootRoute import com.twidere.twiderex.ui.LocalActiveAccount import com.twidere.twiderex.ui.LocalNavController import com.twidere.twiderex.ui.Orange @@ -1219,7 +1219,7 @@ private fun ComposeActions( IconButton( onClick = { scope.launch { - val result = navController.navigateForResult(Route.Compose.Search.User) + val result = navController.navigateForResult(RootRoute.Compose.Search.User) ?.toString() if (!result.isNullOrEmpty()) { viewModel.insertText("$result ") @@ -1240,7 +1240,7 @@ private fun ComposeActions( onClick = { scope.launch { val result = - navController.navigateForResult(Route.Mastodon.Compose.Hashtag) + navController.navigateForResult(RootRoute.Mastodon.Compose.Hashtag) ?.toString() if (!result.isNullOrEmpty()) { viewModel.insertText("$result ") @@ -1295,7 +1295,7 @@ private fun ComposeActions( if (draftCount.value > 0) { IconButton( onClick = { - navController.navigate(Route.Draft.List) + navController.navigate(RootRoute.Draft.List) } ) { Box { diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/dm/DMConversationListScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/dm/DMConversationListScene.kt index 450a0e5f0..caec6dc18 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/dm/DMConversationListScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/dm/DMConversationListScene.kt @@ -39,7 +39,7 @@ import com.twidere.twiderex.component.foundation.SwipeToRefreshLayout import com.twidere.twiderex.component.lazy.LazyListController import com.twidere.twiderex.component.lazy.ui.LazyUiDMConversationList import com.twidere.twiderex.di.assisted.assistedViewModel -import com.twidere.twiderex.navigation.Route +import com.twidere.twiderex.navigation.RootRoute import com.twidere.twiderex.ui.LocalActiveAccount import com.twidere.twiderex.ui.LocalNavController import com.twidere.twiderex.ui.TwidereScene @@ -73,7 +73,7 @@ fun DMConversationListSceneFab() { val navController = LocalNavController.current FloatingActionButton( onClick = { - navController.navigate(Route.Messages.NewConversation) + navController.navigate(RootRoute.Messages.NewConversation) } ) { Icon( @@ -111,7 +111,7 @@ fun DMConversationListSceneContent( items = source, state = listState, onItemClicked = { - navController.navigate(Route.Messages.Conversation(it.conversation.conversationKey)) + navController.navigate(RootRoute.Messages.Conversation(it.conversation.conversationKey)) } ) } diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/dm/DMNewConversationScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/dm/DMNewConversationScene.kt index 0c8488eb5..b3ad7718d 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/dm/DMNewConversationScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/dm/DMNewConversationScene.kt @@ -52,7 +52,7 @@ import com.twidere.twiderex.component.lazy.ui.LazyUiUserList import com.twidere.twiderex.di.assisted.assistedViewModel import com.twidere.twiderex.extensions.observeAsState import com.twidere.twiderex.model.ui.UiUser -import com.twidere.twiderex.navigation.Route +import com.twidere.twiderex.navigation.RootRoute import com.twidere.twiderex.ui.LocalActiveAccount import com.twidere.twiderex.ui.LocalNavController import com.twidere.twiderex.ui.TwidereScene @@ -105,8 +105,8 @@ fun DMNewConversationScene() { onResult = { key -> key?.let { navController.navigate( - Route.Messages.Conversation(it), - NavOptions(popUpTo = PopUpTo(Route.Messages.Home)) + RootRoute.Messages.Conversation(it), + NavOptions(popUpTo = PopUpTo(RootRoute.Messages.Home)) ) } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/DMConversationListItem.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/DMConversationListItem.kt index 7afcc9cac..ed0888bef 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/DMConversationListItem.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/DMConversationListItem.kt @@ -25,7 +25,7 @@ import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import com.twidere.twiderex.R -import com.twidere.twiderex.navigation.Route +import com.twidere.twiderex.navigation.RootRoute import com.twidere.twiderex.scenes.dm.DMConversationListSceneContent import com.twidere.twiderex.scenes.dm.DMConversationListSceneFab @@ -36,7 +36,7 @@ class DMConversationListItem : HomeNavigationItem() { } override val route: String - get() = Route.Messages.Home + get() = RootRoute.Messages.Home @Composable override fun icon(): Painter { diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/DraftNavigationItem.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/DraftNavigationItem.kt index 1cb77c6c0..5abb0db1e 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/DraftNavigationItem.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/DraftNavigationItem.kt @@ -25,7 +25,7 @@ import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import com.twidere.twiderex.R -import com.twidere.twiderex.navigation.Route +import com.twidere.twiderex.navigation.RootRoute import com.twidere.twiderex.scenes.DraftListSceneContent class DraftNavigationItem : HomeNavigationItem() { @@ -35,7 +35,7 @@ class DraftNavigationItem : HomeNavigationItem() { } override val route: String - get() = Route.Draft.List + get() = RootRoute.Draft.List @Composable override fun icon(): Painter { diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/HomeTimelineItem.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/HomeTimelineItem.kt index 07400d115..205f05b2f 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/HomeTimelineItem.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/HomeTimelineItem.kt @@ -37,7 +37,7 @@ import com.twidere.twiderex.component.foundation.InAppNotificationScaffold import com.twidere.twiderex.component.lazy.LazyListController import com.twidere.twiderex.component.navigation.LocalNavigator import com.twidere.twiderex.di.assisted.assistedViewModel -import com.twidere.twiderex.navigation.Route +import com.twidere.twiderex.navigation.RootRoute import com.twidere.twiderex.ui.LocalActiveAccount import com.twidere.twiderex.ui.TwidereScene import com.twidere.twiderex.viewmodel.compose.ComposeType @@ -48,7 +48,7 @@ class HomeTimelineItem : HomeNavigationItem() { @Composable override fun name(): String = stringResource(R.string.scene_timeline_title) override val route: String - get() = Route.HomeTimeline + get() = RootRoute.HomeTimeline @Composable override fun icon(): Painter = painterResource(id = R.drawable.ic_home) diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/ListsNavigationItem.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/ListsNavigationItem.kt index f4351d511..07975af2c 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/ListsNavigationItem.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/ListsNavigationItem.kt @@ -26,7 +26,7 @@ import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import com.twidere.twiderex.R -import com.twidere.twiderex.navigation.Route +import com.twidere.twiderex.navigation.RootRoute import com.twidere.twiderex.scenes.lists.ListsSceneContent import com.twidere.twiderex.scenes.lists.ListsSceneFab @@ -37,7 +37,7 @@ class ListsNavigationItem : HomeNavigationItem() { } override val route: String - get() = Route.Lists.Home + get() = RootRoute.Lists.Home @Composable override fun icon(): Painter { diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/MastodonNotificationItem.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/MastodonNotificationItem.kt index c3429dd8e..17b8e69dd 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/MastodonNotificationItem.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/MastodonNotificationItem.kt @@ -38,7 +38,7 @@ import com.twidere.twiderex.component.foundation.Pager import com.twidere.twiderex.component.foundation.TextTabsComponent import com.twidere.twiderex.component.foundation.rememberPagerState import com.twidere.twiderex.component.lazy.LazyListController -import com.twidere.twiderex.navigation.Route +import com.twidere.twiderex.navigation.RootRoute import com.twidere.twiderex.ui.LocalActiveAccount import com.twidere.twiderex.ui.TwidereScene import kotlinx.coroutines.launch @@ -50,7 +50,7 @@ class MastodonNotificationItem : HomeNavigationItem() { } override val route: String - get() = Route.Mastodon.Notification + get() = RootRoute.Mastodon.Notification @Composable override fun icon(): Painter { diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/MeItem.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/MeItem.kt index 9f453101a..90ffee4fd 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/MeItem.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/MeItem.kt @@ -30,7 +30,7 @@ import com.twidere.twiderex.component.UserComponent import com.twidere.twiderex.component.foundation.AppBar import com.twidere.twiderex.component.foundation.AppBarNavigationButton import com.twidere.twiderex.component.foundation.InAppNotificationScaffold -import com.twidere.twiderex.navigation.Route +import com.twidere.twiderex.navigation.RootRoute import com.twidere.twiderex.ui.LocalActiveAccount import com.twidere.twiderex.ui.TwidereScene @@ -39,7 +39,7 @@ class MeItem : HomeNavigationItem() { @Composable override fun name(): String = stringResource(R.string.scene_profile_title) override val route: String - get() = Route.Me + get() = RootRoute.Me @Composable override fun icon(): Painter = painterResource(id = R.drawable.ic_user) diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/MentionItem.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/MentionItem.kt index 0b8d5e2f3..b7858c36d 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/MentionItem.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/MentionItem.kt @@ -32,7 +32,7 @@ import com.twidere.twiderex.component.foundation.AppBarNavigationButton import com.twidere.twiderex.component.foundation.InAppNotificationScaffold import com.twidere.twiderex.component.lazy.LazyListController import com.twidere.twiderex.di.assisted.assistedViewModel -import com.twidere.twiderex.navigation.Route +import com.twidere.twiderex.navigation.RootRoute import com.twidere.twiderex.ui.LocalActiveAccount import com.twidere.twiderex.ui.TwidereScene import com.twidere.twiderex.viewmodel.timeline.MentionsTimelineViewModel @@ -41,7 +41,7 @@ class MentionItem : HomeNavigationItem() { @Composable override fun name(): String = stringResource(R.string.scene_mentions_title) override val route: String - get() = Route.Mentions + get() = RootRoute.Mentions @Composable override fun icon(): Painter = painterResource(id = R.drawable.ic_message_circle) diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/NotificationItem.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/NotificationItem.kt index c271ca7ae..e8eea74c8 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/NotificationItem.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/NotificationItem.kt @@ -33,7 +33,7 @@ import com.twidere.twiderex.component.foundation.AppBarNavigationButton import com.twidere.twiderex.component.foundation.InAppNotificationScaffold import com.twidere.twiderex.component.lazy.LazyListController import com.twidere.twiderex.di.assisted.assistedViewModel -import com.twidere.twiderex.navigation.Route +import com.twidere.twiderex.navigation.RootRoute import com.twidere.twiderex.ui.LocalActiveAccount import com.twidere.twiderex.ui.TwidereScene import com.twidere.twiderex.viewmodel.timeline.NotificationTimelineViewModel @@ -42,7 +42,7 @@ class NotificationItem : HomeNavigationItem() { @Composable override fun name(): String = stringResource(R.string.scene_notification_title) override val route: String - get() = Route.Notification + get() = RootRoute.Notification @Composable override fun icon(): Painter = painterResource(id = R.drawable.ic_message_circle) diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/SearchItem.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/SearchItem.kt index 28924033f..a6d2236a7 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/SearchItem.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/SearchItem.kt @@ -58,7 +58,7 @@ import com.twidere.twiderex.component.trend.TwitterTrendItem import com.twidere.twiderex.di.assisted.assistedViewModel import com.twidere.twiderex.extensions.observeAsState import com.twidere.twiderex.model.PlatformType -import com.twidere.twiderex.navigation.Route +import com.twidere.twiderex.navigation.RootRoute import com.twidere.twiderex.ui.LocalActiveAccount import com.twidere.twiderex.ui.TwidereScene import com.twidere.twiderex.viewmodel.search.SearchInputViewModel @@ -69,7 +69,7 @@ class SearchItem : HomeNavigationItem() { @Composable override fun name(): String = stringResource(R.string.scene_search_title) override val route: String - get() = Route.Search.Home + get() = RootRoute.Search.Home @Composable override fun icon(): Painter = painterResource(id = R.drawable.ic_search) diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/ListsMembersScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/ListsMembersScene.kt index 9e845c4dd..bbe4c4f26 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/ListsMembersScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/ListsMembersScene.kt @@ -56,7 +56,7 @@ import com.twidere.twiderex.component.navigation.LocalNavigator import com.twidere.twiderex.di.assisted.assistedViewModel import com.twidere.twiderex.extensions.refreshOrRetry import com.twidere.twiderex.model.MicroBlogKey -import com.twidere.twiderex.navigation.Route +import com.twidere.twiderex.navigation.RootRoute import com.twidere.twiderex.ui.LocalActiveAccount import com.twidere.twiderex.ui.LocalNavController import com.twidere.twiderex.ui.TwidereScene @@ -95,7 +95,7 @@ fun ListsMembersScene( if (owned) FloatingActionButton( onClick = { scope.launch { - val result = navController.navigateForResult(Route.Lists.AddMembers(listKey = listKey)) as? List<*>? + val result = navController.navigateForResult(RootRoute.Lists.AddMembers(listKey = listKey)) as? List<*>? if (result != null && result.isNotEmpty()) source.refresh() } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/ListsScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/ListsScene.kt index fd1146589..c45a7f102 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/ListsScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/ListsScene.kt @@ -44,7 +44,7 @@ import com.twidere.twiderex.component.foundation.SwipeToRefreshLayout import com.twidere.twiderex.component.lazy.ui.LazyUiListsList import com.twidere.twiderex.di.assisted.assistedViewModel import com.twidere.twiderex.model.PlatformType -import com.twidere.twiderex.navigation.Route +import com.twidere.twiderex.navigation.RootRoute import com.twidere.twiderex.ui.LocalActiveAccount import com.twidere.twiderex.ui.LocalNavController import com.twidere.twiderex.ui.TwidereScene @@ -82,10 +82,10 @@ fun ListsSceneFab() { FloatingActionButton( onClick = { when (account.type) { - PlatformType.Twitter -> navController.navigate(Route.Lists.TwitterCreate) + PlatformType.Twitter -> navController.navigate(RootRoute.Lists.TwitterCreate) PlatformType.StatusNet -> TODO() PlatformType.Fanfou -> TODO() - PlatformType.Mastodon -> navController.navigate(Route.Lists.MastodonCreateDialog) + PlatformType.Mastodon -> navController.navigate(RootRoute.Lists.MastodonCreateDialog) } } ) { @@ -131,7 +131,7 @@ fun ListsSceneContent() { source = sourceItems, ownerItems = ownerItems, subscribedItems = subscribeItems, - onItemClicked = { navController.navigate(Route.Lists.Timeline(it.listKey)) } + onItemClicked = { navController.navigate(RootRoute.Lists.Timeline(it.listKey)) } ) } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/ListsTimelineScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/ListsTimelineScene.kt index 2cb6164a5..81dbaf055 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/ListsTimelineScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/ListsTimelineScene.kt @@ -64,7 +64,7 @@ import com.twidere.twiderex.extensions.observeAsState import com.twidere.twiderex.model.AccountDetails import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.PlatformType -import com.twidere.twiderex.navigation.Route +import com.twidere.twiderex.navigation.RootRoute import com.twidere.twiderex.scenes.lists.platform.MastodonListsEditDialog import com.twidere.twiderex.ui.LocalActiveAccount import com.twidere.twiderex.ui.LocalNavController @@ -160,7 +160,7 @@ fun ListTimeLineScene( onClick = { menuExpand = false navController.navigate( - Route.Lists.Members( + RootRoute.Lists.Members( listKey, uiList.isOwner(account.user.userId) ) @@ -175,7 +175,7 @@ fun ListTimeLineScene( onClick = { menuExpand = false navController.navigate( - Route.Lists.Subscribers( + RootRoute.Lists.Subscribers( listKey ) ) @@ -190,7 +190,7 @@ fun ListTimeLineScene( onClick = { menuExpand = false when (account.type) { - PlatformType.Twitter -> navController.navigate(Route.Lists.TwitterEdit(listKey = listKey)) + PlatformType.Twitter -> navController.navigate(RootRoute.Lists.TwitterEdit(listKey = listKey)) PlatformType.StatusNet -> TODO() PlatformType.Fanfou -> TODO() PlatformType.Mastodon -> showEditDialog = true diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/platform/MastodonListsCreateDialog.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/platform/MastodonListsCreateDialog.kt index cd98f2451..b0d3316b8 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/platform/MastodonListsCreateDialog.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/platform/MastodonListsCreateDialog.kt @@ -32,7 +32,7 @@ import com.twidere.twiderex.component.foundation.LoadingProgress import com.twidere.twiderex.component.lists.MastodonListsModifyComponent import com.twidere.twiderex.di.assisted.assistedViewModel import com.twidere.twiderex.extensions.observeAsState -import com.twidere.twiderex.navigation.Route +import com.twidere.twiderex.navigation.RootRoute import com.twidere.twiderex.ui.LocalActiveAccount import com.twidere.twiderex.ui.LocalNavController import com.twidere.twiderex.viewmodel.lists.ListsCreateViewModel @@ -59,7 +59,7 @@ fun MastodonListsCreateDialog(onDismissRequest: () -> Unit) { if (success) { list?.apply { navController.navigate( - Route.Lists.Timeline(listKey), + RootRoute.Lists.Timeline(listKey), ) } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/platform/TwitterListsCreateScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/platform/TwitterListsCreateScene.kt index 95f1c7b81..67fb115f7 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/platform/TwitterListsCreateScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/platform/TwitterListsCreateScene.kt @@ -45,7 +45,7 @@ import com.twidere.twiderex.component.foundation.LoadingProgress import com.twidere.twiderex.component.lists.TwitterListsModifyComponent import com.twidere.twiderex.di.assisted.assistedViewModel import com.twidere.twiderex.extensions.observeAsState -import com.twidere.twiderex.navigation.Route +import com.twidere.twiderex.navigation.RootRoute import com.twidere.twiderex.ui.LocalActiveAccount import com.twidere.twiderex.ui.LocalNavController import com.twidere.twiderex.ui.TwidereScene @@ -63,9 +63,9 @@ fun TwitterListsCreateScene() { it.create(account) { success, list -> if (success) list?.apply { navController.navigate( - Route.Lists.Timeline(listKey), + RootRoute.Lists.Timeline(listKey), options = NavOptions( - popUpTo = PopUpTo(Route.Lists.Home) + popUpTo = PopUpTo(RootRoute.Lists.Home) ) ) } diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/AccountManagementScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/AccountManagementScene.kt index 0b5c19f9d..9d5f92972 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/AccountManagementScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/AccountManagementScene.kt @@ -48,7 +48,7 @@ import com.twidere.twiderex.component.status.UserAvatar import com.twidere.twiderex.component.status.UserName import com.twidere.twiderex.component.status.UserScreenName import com.twidere.twiderex.extensions.observeAsState -import com.twidere.twiderex.navigation.Route +import com.twidere.twiderex.navigation.RootRoute import com.twidere.twiderex.ui.LocalActiveAccountViewModel import com.twidere.twiderex.ui.LocalNavController import com.twidere.twiderex.ui.TwidereScene @@ -70,7 +70,7 @@ fun AccountManagementScene() { val navController = LocalNavController.current IconButton( onClick = { - navController.navigate(Route.SignIn.Default) + navController.navigate(RootRoute.SignIn.General) } ) { Icon( diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/NotificationScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/NotificationScene.kt index 3aa3f3aed..ab027ba03 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/NotificationScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/NotificationScene.kt @@ -48,7 +48,7 @@ import com.twidere.twiderex.component.status.UserName import com.twidere.twiderex.component.status.UserScreenName import com.twidere.twiderex.di.assisted.assistedViewModel import com.twidere.twiderex.extensions.observeAsState -import com.twidere.twiderex.navigation.Route +import com.twidere.twiderex.navigation.RootRoute import com.twidere.twiderex.ui.LocalActiveAccountViewModel import com.twidere.twiderex.ui.LocalNavController import com.twidere.twiderex.ui.TwidereScene @@ -111,7 +111,7 @@ fun NotificationScene() { ListItem( modifier = Modifier.clickable( onClick = { - navController.navigate(Route.Settings.AccountNotification(it.accountKey)) + navController.navigate(RootRoute.Settings.AccountNotification(it.accountKey)) }, enabled = notificationEnabled, ), diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/SettingsScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/SettingsScene.kt index db9710e8d..d2e4a0c3b 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/SettingsScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/SettingsScene.kt @@ -38,7 +38,7 @@ import com.twidere.twiderex.R import com.twidere.twiderex.component.foundation.AppBar import com.twidere.twiderex.component.foundation.AppBarNavigationButton import com.twidere.twiderex.component.foundation.InAppNotificationScaffold -import com.twidere.twiderex.navigation.Route +import com.twidere.twiderex.navigation.RootRoute import com.twidere.twiderex.ui.LocalNavController import com.twidere.twiderex.ui.TwidereScene @@ -57,39 +57,39 @@ fun SettingsScene() { SettingItem( stringResource(id = R.string.scene_settings_appearance_title), painterResource(id = R.drawable.ic_shirt), - route = Route.Settings.Appearance, + route = RootRoute.Settings.Appearance, ), SettingItem( stringResource(id = R.string.scene_settings_display_title), painterResource(id = R.drawable.ic_template), - route = Route.Settings.Display, + route = RootRoute.Settings.Display, ), SettingItem( stringResource(id = R.string.scene_settings_layout_title), painterResource(id = R.drawable.ic_layout_sidebar), - route = Route.Settings.Layout, + route = RootRoute.Settings.Layout, ), SettingItem( stringResource(id = R.string.scene_settings_notification_title), painterResource(id = R.drawable.ic_settings_notification), - route = Route.Settings.Notification, + route = RootRoute.Settings.Notification, ), SettingItem( stringResource(id = R.string.scene_settings_storage_title), painterResource(id = R.drawable.ic_database), - route = Route.Settings.Storage, + route = RootRoute.Settings.Storage, ), SettingItem( stringResource(id = R.string.scene_settings_misc_title), painterResource(id = R.drawable.ic_triangle_square_circle), - route = Route.Settings.Misc, + route = RootRoute.Settings.Misc, ), ), stringResource(id = R.string.scene_settings_section_header_about) to listOf( SettingItem( stringResource(id = R.string.scene_settings_about_title), painterResource(id = R.drawable.ic_info_circle), - route = Route.Settings.About, + route = RootRoute.Settings.About, ), ) ) diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/twitter/user/TwitterUserScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/twitter/user/TwitterUserScene.kt index 5c6e938d7..7fb6e0fa6 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/twitter/user/TwitterUserScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/twitter/user/TwitterUserScene.kt @@ -37,7 +37,7 @@ import com.twidere.twiderex.component.foundation.InAppNotificationScaffold import com.twidere.twiderex.component.navigation.LocalNavigator import com.twidere.twiderex.di.assisted.assistedViewModel import com.twidere.twiderex.extensions.observeAsState -import com.twidere.twiderex.navigation.Route +import com.twidere.twiderex.navigation.RootRoute import com.twidere.twiderex.ui.LocalActiveAccount import com.twidere.twiderex.ui.TwidereScene import com.twidere.twiderex.viewmodel.twitter.user.TwitterUserViewModel @@ -61,7 +61,7 @@ fun TwitterUserScene(screenName: String) { user?.let { navigator.user( user = it, - NavOptions(popUpTo = PopUpTo(Route.DeepLink.Twitter.User, inclusive = true)) + NavOptions(popUpTo = PopUpTo(RootRoute.DeepLink.Twitter.User, inclusive = true)) ) } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/user/UserScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/user/UserScene.kt index 45dd04c28..cf8faaa1e 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/user/UserScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/user/UserScene.kt @@ -39,7 +39,7 @@ import com.twidere.twiderex.extensions.observeAsState import com.twidere.twiderex.extensions.withElevation import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.PlatformType -import com.twidere.twiderex.navigation.Route +import com.twidere.twiderex.navigation.RootRoute import com.twidere.twiderex.ui.LocalActiveAccount import com.twidere.twiderex.ui.LocalNavController import com.twidere.twiderex.ui.TwidereScene @@ -84,7 +84,7 @@ fun UserScene( it, onResult = { conversationKey -> conversationKey?.let { - navController.navigate(Route.Messages.Conversation(it)) + navController.navigate(RootRoute.Messages.Conversation(it)) } } ) diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/compose/ComposeWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/compose/ComposeWorker.kt index 3abc0cdc4..8bda4b20c 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/compose/ComposeWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/compose/ComposeWorker.kt @@ -34,7 +34,7 @@ import com.twidere.twiderex.model.ComposeData import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.toComposeData import com.twidere.twiderex.model.ui.UiStatus -import com.twidere.twiderex.navigation.Route +import com.twidere.twiderex.navigation.RootRoute import com.twidere.twiderex.notification.NotificationChannelSpec import com.twidere.twiderex.repository.AccountRepository import com.twidere.twiderex.utils.ExifScrambler @@ -103,7 +103,7 @@ abstract class ComposeWorker( applicationContext.startActivity( Intent( Intent.ACTION_VIEW, - Uri.parse(Route.DeepLink.Compose(ComposeType.Thread, status.statusKey)) + Uri.parse(RootRoute.DeepLink.Compose(ComposeType.Thread, status.statusKey)) ).apply { addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) } @@ -113,7 +113,7 @@ abstract class ComposeWorker( } catch (e: Throwable) { e.printStackTrace() val intent = - Intent(Intent.ACTION_VIEW, Uri.parse(Route.DeepLink.Draft(composeData.draftId))) + Intent(Intent.ACTION_VIEW, Uri.parse(RootRoute.DeepLink.Draft(composeData.draftId))) val pendingIntent = PendingIntent.getActivity( applicationContext, diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageFetchWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageFetchWorker.kt index f7977fb5b..a247ea430 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageFetchWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageFetchWorker.kt @@ -35,7 +35,7 @@ import com.twidere.services.microblog.LookupService import com.twidere.twiderex.R import com.twidere.twiderex.model.AccountDetails import com.twidere.twiderex.model.ui.UiDMConversationWithLatestMessage -import com.twidere.twiderex.navigation.Route +import com.twidere.twiderex.navigation.RootRoute import com.twidere.twiderex.notification.NotificationChannelSpec import com.twidere.twiderex.notification.notificationChannelId import com.twidere.twiderex.repository.AccountRepository @@ -85,7 +85,7 @@ class DirectMessageFetchWorker @AssistedInject constructor( private fun notification(account: AccountDetails, message: UiDMConversationWithLatestMessage) { val intent = - Intent(Intent.ACTION_VIEW, Uri.parse(Route.DeepLink.Conversation(message.conversation.conversationKey))) + Intent(Intent.ACTION_VIEW, Uri.parse(RootRoute.DeepLink.Conversation(message.conversation.conversationKey))) val pendingIntent = PendingIntent.getActivity( applicationContext, diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageSendWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageSendWorker.kt index 8bf8f28dd..d2c65ecab 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageSendWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageSendWorker.kt @@ -44,7 +44,7 @@ import com.twidere.twiderex.model.DirectMessageSendData import com.twidere.twiderex.model.MediaType import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.toDirectMessageSendData -import com.twidere.twiderex.navigation.Route +import com.twidere.twiderex.navigation.RootRoute import com.twidere.twiderex.notification.NotificationChannelSpec import com.twidere.twiderex.notification.notificationChannelId import com.twidere.twiderex.repository.AccountRepository @@ -101,7 +101,7 @@ abstract class DirectMessageSendWorker( .insertAll(listOf(draftEvent.message.copy(sendStatus = DbDMEvent.SendStatus.FAILED))) } val intent = - Intent(Intent.ACTION_VIEW, Uri.parse(Route.DeepLink.Conversation(sendData.conversationKey))) + Intent(Intent.ACTION_VIEW, Uri.parse(RootRoute.DeepLink.Conversation(sendData.conversationKey))) val pendingIntent = PendingIntent.getActivity( applicationContext, From 5fe8ef9d8016b05450c1d1eec8923fd2d41118ef Mon Sep 17 00:00:00 2001 From: Tlaster Date: Thu, 22 Jul 2021 15:28:48 +0800 Subject: [PATCH 053/137] update route generation --- .../com/twidere/twiderex/navigation/Root.kt | 46 +++++++++---------- .../src/main/kotlin/RouteDefinition.kt | 9 ++-- .../src/main/kotlin/RouteProcessor.kt | 1 + 3 files changed, 29 insertions(+), 27 deletions(-) diff --git a/app/src/main/kotlin/com/twidere/twiderex/navigation/Root.kt b/app/src/main/kotlin/com/twidere/twiderex/navigation/Root.kt index 3124d0d22..dcb7a1643 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/navigation/Root.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/navigation/Root.kt @@ -34,40 +34,40 @@ interface Root { interface Draft { val List: String - fun Compose(draftId: String) + fun Compose(draftId: String): String } interface SignIn { val General: String - fun Twitter(consumerKey: String, consumerSecret: String) + fun Twitter(consumerKey: String, consumerSecret: String): String val Mastodon: String interface Web { - fun Twitter(url: String) + fun Twitter(url: String): String } } - fun User(userKey: MicroBlogKey) + fun User(userKey: MicroBlogKey): String interface Media { - fun Status(statusKey: MicroBlogKey, selectedIndex: Int?) - fun Raw(url: String) - fun Pure(belongToKey: MicroBlogKey, selectedIndex: Int?) + fun Status(statusKey: MicroBlogKey, selectedIndex: Int?): String + fun Raw(url: String): String + fun Pure(belongToKey: MicroBlogKey, selectedIndex: Int?): String } interface Search { val Home: String - fun Result(keyword: String) - fun Input(keyword: String?) + fun Result(keyword: String): String + fun Input(keyword: String?): String } interface Compose { - fun Home(composeType: ComposeType?, statusKey: MicroBlogKey?) + fun Home(composeType: ComposeType?, statusKey: MicroBlogKey?): String interface Search { val User: String } } - fun Following(userKey: MicroBlogKey) - fun Followers(userKey: MicroBlogKey) + fun Following(userKey: MicroBlogKey): String + fun Followers(userKey: MicroBlogKey): String interface Settings { val Home: String @@ -79,7 +79,7 @@ interface Root { val Misc: String val Notification: String val Layout: String - fun AccountNotification(accountKey: MicroBlogKey) + fun AccountNotification(accountKey: MicroBlogKey): String } interface DeepLink { @@ -88,14 +88,14 @@ interface Root { val Status: String } fun Draft(id: String): String - fun Compose(composeType: ComposeType, statusKey: MicroBlogKey?) - fun Conversation(conversationKey: MicroBlogKey) + fun Compose(composeType: ComposeType, statusKey: MicroBlogKey?): String + fun Conversation(conversationKey: MicroBlogKey): String } - fun Status(statusKey: MicroBlogKey) + fun Status(statusKey: MicroBlogKey): String interface Mastodon { - fun Hashtag(keyword: String) + fun Hashtag(keyword: String): String val Notification: String interface Compose { @@ -107,16 +107,16 @@ interface Root { val Home: String val MastodonCreateDialog: String val TwitterCreate: String - fun TwitterEdit(listKey: MicroBlogKey) - fun Timeline(listKey: MicroBlogKey) - fun Members(listKey: MicroBlogKey, owned: Boolean?) - fun Subscribers(listKey: MicroBlogKey) - fun AddMembers(listKey: MicroBlogKey) + fun TwitterEdit(listKey: MicroBlogKey): String + fun Timeline(listKey: MicroBlogKey): String + fun Members(listKey: MicroBlogKey, owned: Boolean?): String + fun Subscribers(listKey: MicroBlogKey): String + fun AddMembers(listKey: MicroBlogKey): String } interface Messages { val Home: String - fun Conversation(conversationKey: MicroBlogKey) + fun Conversation(conversationKey: MicroBlogKey): String val NewConversation: String } } diff --git a/routeProcessor/src/main/kotlin/RouteDefinition.kt b/routeProcessor/src/main/kotlin/RouteDefinition.kt index a36195413..289ac9f79 100644 --- a/routeProcessor/src/main/kotlin/RouteDefinition.kt +++ b/routeProcessor/src/main/kotlin/RouteDefinition.kt @@ -48,6 +48,7 @@ internal val RouteDefinition.indent internal data class NestedRouteDefinition( override val name: String, override val parent: RouteDefinition? = null, + val fullName: String, val childRoute: ArrayList = arrayListOf(), ) : RouteDefinition { override fun generateDefinition(): String { @@ -58,7 +59,7 @@ internal data class NestedRouteDefinition( } override fun generateRoute(): String { - return "${indent}object $name {${System.lineSeparator()}" + + return "${indent}object $name: $fullName {${System.lineSeparator()}" + childRoute.joinToString(System.lineSeparator()) { it.generateRoute() } + System.lineSeparator() + "$indent}" @@ -74,7 +75,7 @@ internal data class ConstRouteDefinition( } override fun generateRoute(): String { - return "${indent}const val $name = \"$parentPath/${name}\"" + return "${indent}override val $name = \"$parentPath/${name}\"" } } @@ -119,7 +120,7 @@ internal data class FunctionRouteDefinition( val type = parameter.type .let { if (parameter.isNullable) { - "$it? = null" + "$it?" } else { it } @@ -140,7 +141,7 @@ internal data class FunctionRouteDefinition( } } - return "${indent}fun $name($parameterStr) = \"$parentPath/$name$pathWithParameter${query}\"" + return "${indent}override fun $name($parameterStr) = \"$parentPath/$name$pathWithParameter${query}\"" } } diff --git a/routeProcessor/src/main/kotlin/RouteProcessor.kt b/routeProcessor/src/main/kotlin/RouteProcessor.kt index 8c185f4ed..5b190bafa 100644 --- a/routeProcessor/src/main/kotlin/RouteProcessor.kt +++ b/routeProcessor/src/main/kotlin/RouteProcessor.kt @@ -103,6 +103,7 @@ class RouteProcessor( NestedRouteDefinition( name = name, parent = parent, + fullName = declaration.qualifiedName?.asString() ?: "" ).also { nestedRouteDefinition -> nestedRouteDefinition.childRoute.addAll( declaration.declarations.map { From ec1a422c2f251577344ead71e5083814ecf3096a Mon Sep 17 00:00:00 2001 From: Tlaster Date: Thu, 22 Jul 2021 16:00:29 +0800 Subject: [PATCH 054/137] fix build --- app/build.gradle.kts | 8 -------- .../component/lazy/ui/LazyUiDMEventList.kt | 2 +- routeProcessor/src/main/kotlin/RouteDefinition.kt | 14 ++++++++++++-- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e34dc305d..34986909f 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -127,14 +127,6 @@ android { srcDirs("src/${it.name}/kotlin") } } - applicationVariants.all { - val variantName = name - sourceSets { - getByName("main") { - java.srcDir(File("build/generated/ksp/$variantName/kotlin")) - } - } - } sourceSets { findByName("androidTest")?.let { it.assets { diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/lazy/ui/LazyUiDMEventList.kt b/app/src/main/kotlin/com/twidere/twiderex/component/lazy/ui/LazyUiDMEventList.kt index 080eeab4c..0f1c64d05 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/lazy/ui/LazyUiDMEventList.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/lazy/ui/LazyUiDMEventList.kt @@ -246,7 +246,7 @@ private fun MessageBody(event: UiDMEvent, onItemLongClick: (event: UiDMEvent) -> MediaMessage( media = event.media.firstOrNull(), onClick = { - navController.navigate(RootRoute.Media.Pure(event.messageKey)) + navController.navigate(RootRoute.Media.Pure(event.messageKey, null)) } ) if (event.media.isNotEmpty() && event.htmlText.isNotEmpty()) Spacer(modifier = Modifier.height(MessageBodyDefaults.ContentSpacing)) diff --git a/routeProcessor/src/main/kotlin/RouteDefinition.kt b/routeProcessor/src/main/kotlin/RouteDefinition.kt index 289ac9f79..ff9c81e10 100644 --- a/routeProcessor/src/main/kotlin/RouteDefinition.kt +++ b/routeProcessor/src/main/kotlin/RouteDefinition.kt @@ -105,7 +105,11 @@ internal data class FunctionRouteDefinition( .filter { it.isNullable } .joinToString("&") { parameter -> val name = parameter.name - "$name=\$$name" + if (parameter.type == "kotlin.String") { + "$name=${encode(name)}" + } else { + "$name=\$$name" + } } .let { if (it.isNotEmpty()) { @@ -131,7 +135,11 @@ internal data class FunctionRouteDefinition( .filter { !it.isNullable } .joinToString("/") { parameter -> val name = parameter.name - "\${$name}" + if (parameter.type == "kotlin.String") { + encode(name) + } else { + "\${$name}" + } } .let { if (it.isNotEmpty()) { @@ -143,6 +151,8 @@ internal data class FunctionRouteDefinition( return "${indent}override fun $name($parameterStr) = \"$parentPath/$name$pathWithParameter${query}\"" } + + private fun encode(value: String) = "\${java.net.URLEncoder.encode($value, \"UTF-8\")}" } internal data class RouteParameter( From d9fad67ddc4c17b8c5edcf291f4a572087d66f9c Mon Sep 17 00:00:00 2001 From: itsMimao Date: Fri, 23 Jul 2021 09:58:25 +0800 Subject: [PATCH 055/137] keep screen on when play video in MediaScene --- .../com/twidere/twiderex/component/foundation/VideoPlayer.kt | 2 ++ app/src/main/kotlin/com/twidere/twiderex/scenes/MediaScene.kt | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/VideoPlayer.kt b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/VideoPlayer.kt index 4cb43c642..1b9c9f958 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/VideoPlayer.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/VideoPlayer.kt @@ -75,6 +75,7 @@ fun VideoPlayer( customControl: PlayerControlView? = null, showControls: Boolean = customControl == null, zOrderMediaOverlay: Boolean = false, + keepScreenOn: Boolean = false, thumb: @Composable (() -> Unit)? = null, ) { var playing by remember { mutableStateOf(false) } @@ -182,6 +183,7 @@ fun VideoPlayer( StyledPlayerView(context).also { playerView -> (playerView.videoSurfaceView as? SurfaceView)?.setZOrderMediaOverlay(zOrderMediaOverlay) playerView.useController = showControls + playerView.keepScreenOn = keepScreenOn } } ) { diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/MediaScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/MediaScene.kt index b8432800c..cbdf14e37 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/MediaScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/MediaScene.kt @@ -445,7 +445,8 @@ fun MediaView( url = data.url, customControl = customControl, showControls = false, - zOrderMediaOverlay = true + zOrderMediaOverlay = true, + keepScreenOn = true ) } MediaType.other -> Unit From 59eda46bc8484c1059aff81937a2ed926f96dd91 Mon Sep 17 00:00:00 2001 From: itsMimao Date: Fri, 23 Jul 2021 11:38:54 +0800 Subject: [PATCH 056/137] fixed compose scene send button not working bug --- .../com/twidere/twiderex/scenes/compose/ComposeScene.kt | 2 +- .../twidere/twiderex/viewmodel/compose/ComposeViewModel.kt | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/compose/ComposeScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/compose/ComposeScene.kt index b0abf5242..a44e1e286 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/compose/ComposeScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/compose/ComposeScene.kt @@ -281,7 +281,7 @@ private fun ComposeBody( contentDescription = stringResource( id = if (enableThreadMode) R.string.accessibility_scene_compose_thread else R.string.accessibility_scene_compose_send ), - tint = if (textFieldValue.text.isNotEmpty()) MaterialTheme.colors.primary else LocalContentColor.current.copy(alpha = LocalContentAlpha.current) + tint = if (canSend) MaterialTheme.colors.primary else LocalContentColor.current.copy(alpha = LocalContentAlpha.current) ) } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/compose/ComposeViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/compose/ComposeViewModel.kt index 35d040d5a..717b65653 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/compose/ComposeViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/compose/ComposeViewModel.kt @@ -59,10 +59,10 @@ import com.twitter.twittertext.Extractor import dagger.assisted.Assisted import dagger.assisted.AssistedInject import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.emitAll import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.zip import java.util.UUID enum class ComposeType { @@ -273,9 +273,9 @@ open class ComposeViewModel @AssistedInject constructor( val contentWarningTextFieldValue = MutableStateFlow(TextFieldValue()) val textFieldValue = MutableStateFlow(TextFieldValue()) val images = MutableStateFlow>(emptyList()) - val canSend = textFieldValue.zip(images) { text, imgs -> text.text.isNotEmpty() || !imgs.isNullOrEmpty() } + val canSend = textFieldValue.combine(images) { text, imgs -> text.text.isNotEmpty() || !imgs.isNullOrEmpty() } val canSaveDraft = - textFieldValue.zip(images) { text, imgs -> text.text.isNotEmpty() || !imgs.isNullOrEmpty() } + textFieldValue.combine(images) { text, imgs -> text.text.isNotEmpty() || !imgs.isNullOrEmpty() } val locationEnabled = MutableStateFlow(false) val enableThreadMode = MutableStateFlow(composeType == ComposeType.Thread) val status = flow { From cdfe984172a7ed5bee6cb4c877bc96ef311c4de0 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Fri, 23 Jul 2021 14:00:51 +0800 Subject: [PATCH 057/137] add deeplink generation --- .../twidere/twiderex/db/mapper/Mastodon.kt | 6 +-- .../com/twidere/twiderex/db/mapper/Twitter.kt | 8 +-- .../twiderex/navigation/RootDeepLinks.kt | 54 +++++++++++++++++++ .../com/twidere/twiderex/navigation/Route.kt | 50 ++++------------- .../scenes/mastodon/MastodonWebSignInScene.kt | 4 +- .../service/AccountAuthenticatorService.kt | 6 +-- .../twiderex/utils/CustomTabSignInChannel.kt | 6 +-- .../mastodon/MastodonSignInViewModel.kt | 4 +- .../twitter/TwitterSignInViewModel.kt | 4 +- .../twiderex/worker/NotificationWorker.kt | 16 +++--- .../precompose/navigation/RouteParserTest.kt | 50 +++++++++++++++++ routeProcessor/src/main/kotlin/AppRoute.kt | 7 ++- .../src/main/kotlin/RouteDefinition.kt | 33 ++++++++++-- .../src/main/kotlin/RouteProcessor.kt | 46 ++++++++++++---- .../src/main/kotlin/RouteProcessorProvider.kt | 2 +- 15 files changed, 214 insertions(+), 82 deletions(-) create mode 100644 app/src/main/kotlin/com/twidere/twiderex/navigation/RootDeepLinks.kt diff --git a/app/src/main/kotlin/com/twidere/twiderex/db/mapper/Mastodon.kt b/app/src/main/kotlin/com/twidere/twiderex/db/mapper/Mastodon.kt index 27586603a..85745f167 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/db/mapper/Mastodon.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/db/mapper/Mastodon.kt @@ -50,7 +50,7 @@ import com.twidere.twiderex.model.MastodonStatusType import com.twidere.twiderex.model.MediaType import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.PlatformType -import com.twidere.twiderex.navigation.DeepLinks +import com.twidere.twiderex.navigation.RootDeepLinksRoute import org.jsoup.Jsoup import org.jsoup.nodes.Element import org.jsoup.nodes.Node @@ -411,7 +411,7 @@ private fun replaceMention(mentions: List, node: Node, accountKey: Micr if (id != null) { node.attr( "href", - DeepLinks.User + "/" + MicroBlogKey(id, accountKey.host) + RootDeepLinksRoute.User(MicroBlogKey(id, accountKey.host)) ) } } else { @@ -431,7 +431,7 @@ private fun replaceHashTag(node: Node) { ) { node.attr( "href", - DeepLinks.Mastodon.Hashtag + "/" + node.text().trimStart('#') + RootDeepLinksRoute.Mastodon.Hashtag(node.text().trimStart('#')) ) } else { node.childNodes().forEach { replaceHashTag(it) } diff --git a/app/src/main/kotlin/com/twidere/twiderex/db/mapper/Twitter.kt b/app/src/main/kotlin/com/twidere/twiderex/db/mapper/Twitter.kt index 0f501e63e..3b95a0312 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/db/mapper/Twitter.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/db/mapper/Twitter.kt @@ -53,16 +53,16 @@ import com.twidere.twiderex.model.MediaType import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.PlatformType import com.twidere.twiderex.model.ui.ListsMode -import com.twidere.twiderex.navigation.DeepLinks +import com.twidere.twiderex.navigation.RootDeepLinksRouteDefinition import com.twitter.twittertext.Autolink import java.util.UUID val autolink by lazy { Autolink().apply { setUsernameIncludeSymbol(true) - hashtagUrlBase = "${DeepLinks.Search}/%23" - cashtagUrlBase = "${DeepLinks.Search}/%24" - usernameUrlBase = "${DeepLinks.Twitter.User}/" + hashtagUrlBase = "${RootDeepLinksRouteDefinition.Search}/%23" + cashtagUrlBase = "${RootDeepLinksRouteDefinition.Search}/%24" + usernameUrlBase = "${RootDeepLinksRouteDefinition.Twitter.User}/" } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/navigation/RootDeepLinks.kt b/app/src/main/kotlin/com/twidere/twiderex/navigation/RootDeepLinks.kt new file mode 100644 index 000000000..75e44dfac --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/navigation/RootDeepLinks.kt @@ -0,0 +1,54 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.navigation + +import com.twidere.route.processor.AppRoute +import com.twidere.twiderex.model.MicroBlogKey + +@AppRoute( + prefix = "$twidereXSchema://" +) +interface RootDeepLinks { + interface Twitter { + fun User(screenName: String): String + fun Status(id: String): String + } + + interface Mastodon { + fun Hashtag(keyword: String): String + } + + fun User(userKey: MicroBlogKey): String + fun Status(statusKey: MicroBlogKey): String + fun Search(keyword: String): String + val SignIn: String + + val Draft: String + val Compose: String + val Conversation: String + + interface Callback { + interface SignIn { + val Mastodon: String + val Twitter: String + } + } +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/navigation/Route.kt b/app/src/main/kotlin/com/twidere/twiderex/navigation/Route.kt index e8f2c8009..33a136f03 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/navigation/Route.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/navigation/Route.kt @@ -102,36 +102,6 @@ import java.net.URLDecoder const val initialRoute = RootRouteDefinition.Home const val twidereXSchema = "twiderex" -object DeepLinks { - object Twitter { - const val User = "$twidereXSchema://twitter/user" - const val Status = "$twidereXSchema://twitter/status/{statusId}" - fun Status(id: String) = "$twidereXSchema://twitter/status/$id" - } - - object Mastodon { - const val Hashtag = "$twidereXSchema://mastodon/hashtag" - } - - const val User = "$twidereXSchema://user" - fun User(userKey: MicroBlogKey) = "$twidereXSchema://user/$userKey" - const val Status = "$twidereXSchema://status/{statusKey}" - fun Status(statusKey: MicroBlogKey) = "$twidereXSchema://status/$statusKey" - const val Search = "$twidereXSchema://search" - const val SignIn = "$twidereXSchema://signin" - - const val Draft = "$twidereXSchema://draft/compose/{draftId}" - const val Compose = "$twidereXSchema://compose" - const val Conversation = "$twidereXSchema://${RootRouteDefinition.Messages.Home}/conversation/{conversationKey}" - - object Callback { - object SignIn { - const val Mastodon = "$twidereXSchema://auth/callback/mastodon" - const val Twitter = "$twidereXSchema://auth/callback/twitter" - } - } -} - fun RouteBuilder.authorizedScene( route: String, deepLinks: List = emptyList(), @@ -260,7 +230,7 @@ fun RouteBuilder.route(constraints: Constraints) { scene( RootRouteDefinition.SignIn.General, deepLinks = listOf( - DeepLinks.SignIn + RootDeepLinksRouteDefinition.SignIn ), ) { SignInScene() @@ -300,7 +270,7 @@ fun RouteBuilder.route(constraints: Constraints) { RootRouteDefinition.DeepLink.Twitter.User, deepLinks = twitterHosts.map { "$it/{screenName}" - } + "${DeepLinks.Twitter.User}/{screenName}" + } + RootDeepLinksRouteDefinition.Twitter.User ) { backStackEntry -> backStackEntry.path("screenName")?.let { screenName -> val navigator = LocalNavigator.current @@ -319,7 +289,7 @@ fun RouteBuilder.route(constraints: Constraints) { authorizedScene( RootRouteDefinition.User, deepLinks = listOf( - "${DeepLinks.User}/{userKey}" + RootDeepLinksRouteDefinition.User ) ) { backStackEntry -> backStackEntry.path("userKey")?.let { @@ -336,7 +306,7 @@ fun RouteBuilder.route(constraints: Constraints) { authorizedScene( RootRouteDefinition.Mastodon.Hashtag, deepLinks = listOf( - "${DeepLinks.Mastodon.Hashtag}/{keyword}" + RootDeepLinksRouteDefinition.Mastodon.Hashtag ) ) { backStackEntry -> backStackEntry.path("keyword")?.let { @@ -347,7 +317,7 @@ fun RouteBuilder.route(constraints: Constraints) { authorizedScene( RootRouteDefinition.Status, deepLinks = listOf( - DeepLinks.Status, + RootDeepLinksRouteDefinition.Status, ) ) { backStackEntry -> backStackEntry.path("statusKey")?.let { @@ -366,7 +336,7 @@ fun RouteBuilder.route(constraints: Constraints) { deepLinks = twitterHosts.map { "$it/{screenName}/status/{statusId:[0-9]+}" } + listOf( - DeepLinks.Twitter.Status + RootDeepLinksRouteDefinition.Twitter.Status ) ) { backStackEntry -> backStackEntry.path("statusId")?.let { statusId -> @@ -450,7 +420,7 @@ fun RouteBuilder.route(constraints: Constraints) { RootRouteDefinition.Search.Result, deepLinks = twitterHosts.map { "$it/search?q={keyword}" - } + "${DeepLinks.Search}/{keyword}", + } + RootDeepLinksRouteDefinition.Search, navTransition = NavTransition( createTransition = fadeCreateTransition, destroyTransition = fadeDestroyTransition, @@ -478,7 +448,7 @@ fun RouteBuilder.route(constraints: Constraints) { resumeTransition = fadeScaleCreateTransition, ), deepLinks = listOf( - DeepLinks.Compose + RootDeepLinksRouteDefinition.Compose ) ) { backStackEntry -> val type = backStackEntry.query("composeType")?.let { @@ -571,7 +541,7 @@ fun RouteBuilder.route(constraints: Constraints) { scene( RootRouteDefinition.Draft.Compose, deepLinks = listOf( - DeepLinks.Draft, + RootDeepLinksRouteDefinition.Draft, ) ) { backStackEntry -> backStackEntry.path("draftId")?.let { @@ -651,7 +621,7 @@ fun RouteBuilder.route(constraints: Constraints) { authorizedScene( RootRouteDefinition.Messages.Conversation, deepLinks = listOf( - DeepLinks.Conversation + RootDeepLinksRouteDefinition.Conversation ) ) { backStackEntry -> backStackEntry.path("conversationKey")?.let { diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/mastodon/MastodonWebSignInScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/mastodon/MastodonWebSignInScene.kt index 072e95547..ee911b7ec 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/mastodon/MastodonWebSignInScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/mastodon/MastodonWebSignInScene.kt @@ -24,7 +24,7 @@ import android.net.Uri import androidx.compose.runtime.Composable import com.twidere.twiderex.component.foundation.InAppNotificationScaffold import com.twidere.twiderex.component.foundation.WebComponent -import com.twidere.twiderex.navigation.DeepLinks +import com.twidere.twiderex.navigation.RootDeepLinksRoute import com.twidere.twiderex.ui.LocalNavController import com.twidere.twiderex.ui.TwidereScene @@ -36,7 +36,7 @@ fun MastodonWebSignInScene(target: String) { WebComponent( url = target, onPageStarted = { _, url -> - if (url.startsWith(DeepLinks.Callback.SignIn.Mastodon)) { + if (url.startsWith(RootDeepLinksRoute.Callback.SignIn.Mastodon)) { val uri = Uri.parse(url) uri.getQueryParameter("code")?.takeIf { it.isNotEmpty() diff --git a/app/src/main/kotlin/com/twidere/twiderex/service/AccountAuthenticatorService.kt b/app/src/main/kotlin/com/twidere/twiderex/service/AccountAuthenticatorService.kt index d8f84ffe0..1de3d9f3b 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/service/AccountAuthenticatorService.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/service/AccountAuthenticatorService.kt @@ -31,7 +31,7 @@ import android.net.Uri import android.os.Bundle import android.os.IBinder import androidx.core.os.bundleOf -import com.twidere.twiderex.navigation.DeepLinks +import com.twidere.twiderex.navigation.RootDeepLinksRoute class AccountAuthenticatorService : Service() { @@ -56,7 +56,7 @@ class AccountAuthenticatorService : Service() { requiredFeatures: Array?, options: Bundle? ): Bundle { - val intent = Intent(Intent.ACTION_VIEW, Uri.parse(DeepLinks.SignIn)) + val intent = Intent(Intent.ACTION_VIEW, Uri.parse(RootDeepLinksRoute.SignIn)) intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response) return bundleOf( AccountManager.KEY_INTENT to intent, @@ -72,7 +72,7 @@ class AccountAuthenticatorService : Service() { val am = AccountManager.get(context) val authToken = am.peekAuthToken(account, authTokenType) if (authToken.isNullOrEmpty()) { - val intent = Intent(Intent.ACTION_VIEW, Uri.parse(DeepLinks.SignIn)) + val intent = Intent(Intent.ACTION_VIEW, Uri.parse(RootDeepLinksRoute.SignIn)) intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response) return bundleOf( AccountManager.KEY_INTENT to intent, diff --git a/app/src/main/kotlin/com/twidere/twiderex/utils/CustomTabSignInChannel.kt b/app/src/main/kotlin/com/twidere/twiderex/utils/CustomTabSignInChannel.kt index 6e0a79dc6..405986d03 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/utils/CustomTabSignInChannel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/utils/CustomTabSignInChannel.kt @@ -21,7 +21,7 @@ package com.twidere.twiderex.utils import android.net.Uri -import com.twidere.twiderex.navigation.DeepLinks +import com.twidere.twiderex.navigation.RootDeepLinksRoute import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.channels.Channel @@ -37,8 +37,8 @@ object CustomTabSignInChannel { } fun canHandle(uri: Uri): Boolean { - return uri.toString().startsWith(DeepLinks.Callback.SignIn.Mastodon) || - uri.toString().startsWith(DeepLinks.Callback.SignIn.Twitter) + return uri.toString().startsWith(RootDeepLinksRoute.Callback.SignIn.Mastodon) || + uri.toString().startsWith(RootDeepLinksRoute.Callback.SignIn.Twitter) } @OptIn(ExperimentalCoroutinesApi::class) diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/mastodon/MastodonSignInViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/mastodon/MastodonSignInViewModel.kt index c631e56d6..4e91467b7 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/mastodon/MastodonSignInViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/mastodon/MastodonSignInViewModel.kt @@ -32,7 +32,7 @@ import com.twidere.twiderex.model.PlatformType import com.twidere.twiderex.model.cred.CredentialsType import com.twidere.twiderex.model.cred.OAuth2Credentials import com.twidere.twiderex.model.toAmUser -import com.twidere.twiderex.navigation.DeepLinks +import com.twidere.twiderex.navigation.RootDeepLinksRoute import com.twidere.twiderex.notification.InAppNotification import com.twidere.twiderex.repository.ACCOUNT_TYPE import com.twidere.twiderex.repository.AccountRepository @@ -68,7 +68,7 @@ class MastodonSignInViewModel @AssistedInject constructor( host = "https://$host", client_name = "Twidere X", website = "https://github.com/TwidereProject/TwidereX-Android", - redirect_uri = DeepLinks.Callback.SignIn.Mastodon, + redirect_uri = RootDeepLinksRoute.Callback.SignIn.Mastodon, httpClientFactory = TwidereServiceFactory.createHttpClientFactory() ) val application = service.createApplication() diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/twitter/TwitterSignInViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/twitter/TwitterSignInViewModel.kt index bcad7cd51..785a1f105 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/twitter/TwitterSignInViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/twitter/TwitterSignInViewModel.kt @@ -34,7 +34,7 @@ import com.twidere.twiderex.model.PlatformType import com.twidere.twiderex.model.cred.CredentialsType import com.twidere.twiderex.model.cred.OAuthCredentials import com.twidere.twiderex.model.toAmUser -import com.twidere.twiderex.navigation.DeepLinks +import com.twidere.twiderex.navigation.RootDeepLinksRoute import com.twidere.twiderex.notification.InAppNotification import com.twidere.twiderex.repository.ACCOUNT_TYPE import com.twidere.twiderex.repository.AccountRepository @@ -88,7 +88,7 @@ class TwitterSignInViewModel @AssistedInject constructor( ) val token = service.getOAuthToken( if (isBuiltInKey()) { - DeepLinks.Callback.SignIn.Twitter + RootDeepLinksRoute.Callback.SignIn.Twitter } else { "oob" } diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/NotificationWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/NotificationWorker.kt index 6170869c2..83f192e37 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/NotificationWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/NotificationWorker.kt @@ -42,7 +42,7 @@ import com.twidere.twiderex.model.AccountDetails import com.twidere.twiderex.model.MastodonStatusType import com.twidere.twiderex.model.PlatformType import com.twidere.twiderex.model.ui.UiStatus -import com.twidere.twiderex.navigation.DeepLinks +import com.twidere.twiderex.navigation.RootDeepLinksRoute import com.twidere.twiderex.notification.NotificationChannelSpec import com.twidere.twiderex.notification.notificationChannelId import com.twidere.twiderex.preferences.proto.NotificationPreferences @@ -111,7 +111,7 @@ class NotificationWorker @AssistedInject constructor( status.user.displayName ), htmlContent = status.htmlText, - deepLink = DeepLinks.Twitter.Status(status.statusId), + deepLink = RootDeepLinksRoute.Twitter.Status(status.statusId), profileImage = status.user.profileImage, ) } @@ -180,7 +180,7 @@ class NotificationWorker @AssistedInject constructor( R.string.common_notification_follow, actualStatus.user.displayName ), - deepLink = DeepLinks.User(actualStatus.user.userKey), + deepLink = RootDeepLinksRoute.User(actualStatus.user.userKey), profileImage = actualStatus.user.profileImage, ) } @@ -190,7 +190,7 @@ class NotificationWorker @AssistedInject constructor( R.string.common_notification_follow_request, actualStatus.user.displayName ), - deepLink = DeepLinks.User(actualStatus.user.userKey) + deepLink = RootDeepLinksRoute.User(actualStatus.user.userKey) ) } MastodonStatusType.NotificationMention -> { @@ -200,7 +200,7 @@ class NotificationWorker @AssistedInject constructor( actualStatus.user.displayName ), htmlContent = actualStatus.htmlText, - deepLink = DeepLinks.Status(actualStatus.statusKey), + deepLink = RootDeepLinksRoute.Status(actualStatus.statusKey), profileImage = actualStatus.user.profileImage, ) } @@ -210,7 +210,7 @@ class NotificationWorker @AssistedInject constructor( R.string.common_notification_reblog, actualStatus.user.displayName ), - deepLink = DeepLinks.Status(actualStatus.statusKey), + deepLink = RootDeepLinksRoute.Status(actualStatus.statusKey), profileImage = actualStatus.user.profileImage, ) } @@ -220,7 +220,7 @@ class NotificationWorker @AssistedInject constructor( R.string.common_notification_favourite, actualStatus.user.displayName ), - deepLink = DeepLinks.Status(actualStatus.statusKey), + deepLink = RootDeepLinksRoute.Status(actualStatus.statusKey), profileImage = actualStatus.user.profileImage, ) } @@ -229,7 +229,7 @@ class NotificationWorker @AssistedInject constructor( title = applicationContext.getString( R.string.common_notification_poll, ), - deepLink = DeepLinks.Status(actualStatus.statusKey), + deepLink = RootDeepLinksRoute.Status(actualStatus.statusKey), profileImage = actualStatus.user.profileImage, ) } diff --git a/app/src/test/java/moe/tlaster/precompose/navigation/RouteParserTest.kt b/app/src/test/java/moe/tlaster/precompose/navigation/RouteParserTest.kt index 21e3a4d52..25d64b8ff 100644 --- a/app/src/test/java/moe/tlaster/precompose/navigation/RouteParserTest.kt +++ b/app/src/test/java/moe/tlaster/precompose/navigation/RouteParserTest.kt @@ -74,6 +74,56 @@ class RouteParserTest { Assert.assertEquals("1234", matchResultMultiParamsDeepLinks?.pathMap?.get("statusId")) } + @Test + fun route_MatchGenerated() { + val parser = RouteParser() + listOf( + buildRoute("/home"), + buildRoute("/user/{userId}"), + buildRoute("/twitter/{screenName}/status/{statusId}"), + buildRoute( + "/twitter/status/{statusId}", + deepLink = listOf( + "https://twitter.com/{screenName}/status/{statusId:[0-9]+}", + "twidereX:///deeplink/status/{statusId}" + ) + ) + ) + .map { route -> + RouteParser.expandOptionalVariables(route.route).let { + it + route.deepLinks.flatMap { + RouteParser.expandOptionalVariables(it) + } + } to route + } + .flatMap { it.first.map { route -> route to it.second } }.forEach { + parser.insert(it.first, it.second) + } + + Assert.assertEquals("/home", parser.find("/home")?.route?.route) + val matchResultSingleParam = parser.find("/user/123456") + Assert.assertEquals("/user/{userId}", matchResultSingleParam?.route?.route) + Assert.assertEquals("123456", matchResultSingleParam?.pathMap?.get("userId")) + + val matchResultMultiParams = parser.find("/twitter/testName/status/456") + Assert.assertEquals("/twitter/{screenName}/status/{statusId}", matchResultMultiParams?.route?.route) + Assert.assertEquals("testName", matchResultMultiParams?.pathMap?.get("screenName")) + Assert.assertEquals("456", matchResultMultiParams?.pathMap?.get("statusId")) + + val matchResultDeepLinks = parser.find("https://twitter.com/testName2/status/789") + Assert.assertEquals("/twitter/status/{statusId}", matchResultDeepLinks?.route?.route) + Assert.assertEquals("testName2", matchResultDeepLinks?.pathMap?.get("screenName")) + Assert.assertEquals("789", matchResultDeepLinks?.pathMap?.get("statusId")) + + val matchResultRegex = parser.find("https://twitter.com/testName2/status/789abc") + Assert.assertEquals(null, matchResultRegex?.route) + + val matchResultMultiParamsDeepLinks = parser.find("twidereX:///deeplink/status/1234") + Assert.assertEquals("/twitter/status/{statusId}", matchResultMultiParamsDeepLinks?.route?.route) + Assert.assertEquals(null, matchResultMultiParamsDeepLinks?.pathMap?.get("screenName")) + Assert.assertEquals("1234", matchResultMultiParamsDeepLinks?.pathMap?.get("statusId")) + } + private fun buildRoute(route: String, deepLink: List = emptyList()): SceneRoute { return SceneRoute(route = route, deepLinks = deepLink, navTransition = null, content = {}) } diff --git a/routeProcessor/src/main/kotlin/AppRoute.kt b/routeProcessor/src/main/kotlin/AppRoute.kt index f57ad13b9..c0bd8654c 100644 --- a/routeProcessor/src/main/kotlin/AppRoute.kt +++ b/routeProcessor/src/main/kotlin/AppRoute.kt @@ -22,4 +22,9 @@ package com.twidere.route.processor @Retention(AnnotationRetention.SOURCE) @Target(AnnotationTarget.CLASS) -annotation class AppRoute +annotation class AppRoute( + val prefix: String = "", + val packageName: String = "", + val routeClassName: String = "", + val definitionClassName: String = "", +) diff --git a/routeProcessor/src/main/kotlin/RouteDefinition.kt b/routeProcessor/src/main/kotlin/RouteDefinition.kt index ff9c81e10..82b8fc09f 100644 --- a/routeProcessor/src/main/kotlin/RouteDefinition.kt +++ b/routeProcessor/src/main/kotlin/RouteDefinition.kt @@ -40,14 +40,41 @@ internal fun RouteDefinition.parents(): List { } internal val RouteDefinition.parentPath - get() = parents().joinToString("/") { it.name } + get() = parents() + .joinToString("/") { it.name } internal val RouteDefinition.indent - get() = parents().joinToString("") { StandardIndent } + get() = parents() + .filter { it !is PrefixRouteDefinition } + .joinToString("") { StandardIndent } + +internal data class PrefixRouteDefinition( + val prefix: String, + val child: NestedRouteDefinition, + val routeClassName: String, + val definitionClassName: String, +) : RouteDefinition { + override val name: String + get() = prefix + override val parent: RouteDefinition? + get() = null + + init { + child.parent = this + } + + override fun generateDefinition(): String { + return child.copy(name = definitionClassName).generateDefinition() + } + + override fun generateRoute(): String { + return child.copy(name = routeClassName).generateRoute() + } +} internal data class NestedRouteDefinition( override val name: String, - override val parent: RouteDefinition? = null, + override var parent: RouteDefinition? = null, val fullName: String, val childRoute: ArrayList = arrayListOf(), ) : RouteDefinition { diff --git a/routeProcessor/src/main/kotlin/RouteProcessor.kt b/routeProcessor/src/main/kotlin/RouteProcessor.kt index 5b190bafa..324f4ab6a 100644 --- a/routeProcessor/src/main/kotlin/RouteProcessor.kt +++ b/routeProcessor/src/main/kotlin/RouteProcessor.kt @@ -22,9 +22,11 @@ package com.twidere.route.processor import com.google.devtools.ksp.processing.CodeGenerator import com.google.devtools.ksp.processing.Dependencies +import com.google.devtools.ksp.processing.KSPLogger import com.google.devtools.ksp.processing.Resolver import com.google.devtools.ksp.processing.SymbolProcessor import com.google.devtools.ksp.symbol.KSAnnotated +import com.google.devtools.ksp.symbol.KSAnnotation import com.google.devtools.ksp.symbol.KSClassDeclaration import com.google.devtools.ksp.symbol.KSDeclaration import com.google.devtools.ksp.symbol.KSFunctionDeclaration @@ -33,12 +35,16 @@ import com.google.devtools.ksp.symbol.KSPropertyDeclaration import com.google.devtools.ksp.visitor.KSEmptyVisitor import java.io.OutputStream -class RouteProcessor( +internal class RouteProcessor( private val codeGenerator: CodeGenerator, + private val logger: KSPLogger, ) : SymbolProcessor { override fun process(resolver: Resolver): List { val routeSymbol = resolver - .getSymbolsWithAnnotation("com.twidere.route.processor.AppRoute") + .getSymbolsWithAnnotation( + AppRoute::class.qualifiedName + ?: throw CloneNotSupportedException("Can not get qualifiedName for AppRoute") + ) .filterIsInstance() routeSymbol.forEach { it.accept(RouteVisitor(), routeSymbol.toList()) } return emptyList() @@ -50,14 +56,30 @@ class RouteProcessor( return } + val annotation = node.annotations + .firstOrNull { it.annotationType.resolve().declaration.qualifiedName?.asString() == AppRoute::class.qualifiedName } + ?: return + + val prefix = annotation.getStringValue(AppRoute::prefix.name) ?: "" + val packageName = annotation.getStringValue(AppRoute::packageName.name) + ?: node.packageName.asString() + val routeClassName = annotation.getStringValue(AppRoute::routeClassName.name) + ?: "${node.qualifiedName?.getShortName()}Route" + val definitionClassName = annotation.getStringValue(AppRoute::definitionClassName.name) + ?: "${node.qualifiedName?.getShortName()}RouteDefinition" + val route = generateRoute(declaration = node) - if (route !is NestedRouteDefinition) { - return - } + .takeIf { + it is NestedRouteDefinition + }?.let { + PrefixRouteDefinition( + prefix = prefix, + child = it as NestedRouteDefinition, + routeClassName = routeClassName, + definitionClassName = definitionClassName, + ) + } ?: return - val packageName = node.packageName.asString() - val routeClassName = "${node.qualifiedName?.getShortName()}Route" - val definitionClassName = "${node.qualifiedName?.getShortName()}RouteDefinition" val dependencies = Dependencies( true, *(data.mapNotNull { it.containingFile } + listOfNotNull(node.containingFile)).toTypedArray() @@ -66,13 +88,13 @@ class RouteProcessor( dependencies, packageName, routeClassName, - route.copy(name = routeClassName).generateRoute() + route.generateRoute() ) generateFile( dependencies, packageName, definitionClassName, - route.copy(name = definitionClassName).generateDefinition() + route.generateDefinition() ) } @@ -140,3 +162,7 @@ class RouteProcessor( private fun OutputStream.appendLine(str: String = "") { this.write("$str${System.lineSeparator()}".toByteArray()) } + +private fun KSAnnotation.getStringValue(name: String): String? = arguments + .firstOrNull { it.name?.asString() == name } + ?.let { it.value as? String? }.takeIf { !it.isNullOrEmpty() } diff --git a/routeProcessor/src/main/kotlin/RouteProcessorProvider.kt b/routeProcessor/src/main/kotlin/RouteProcessorProvider.kt index 4c298284d..59b73a984 100644 --- a/routeProcessor/src/main/kotlin/RouteProcessorProvider.kt +++ b/routeProcessor/src/main/kotlin/RouteProcessorProvider.kt @@ -26,6 +26,6 @@ import com.google.devtools.ksp.processing.SymbolProcessorProvider class RouteProcessorProvider : SymbolProcessorProvider { override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor { - return RouteProcessor(environment.codeGenerator) + return RouteProcessor(environment.codeGenerator, environment.logger) } } From bddb5887fac32b01751e896fd23692cdb6c6ba00 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Fri, 23 Jul 2021 14:29:18 +0800 Subject: [PATCH 058/137] fix source link display --- .../twiderex/component/status/DetailedStatusComponent.kt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/status/DetailedStatusComponent.kt b/app/src/main/kotlin/com/twidere/twiderex/component/status/DetailedStatusComponent.kt index ff5904146..8a00dcc0a 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/status/DetailedStatusComponent.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/status/DetailedStatusComponent.kt @@ -45,7 +45,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import com.twidere.twiderex.R import com.twidere.twiderex.component.FormattedTime @@ -107,10 +106,12 @@ fun DetailedStatusComponent( ) { FormattedTime(time = status.timestamp) Spacer(modifier = Modifier.width(DetailedStatusDefaults.TimestampSpacing)) - Text( - text = status.source, + HtmlText( + htmlText = status.source, maxLines = 1, - overflow = TextOverflow.Ellipsis, + linkResolver = { + ResolvedLink(null) + } ) } From bc9a1a1fa97d0de9dd3689b8145cbd0ae45a3024 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Fri, 23 Jul 2021 14:41:07 +0800 Subject: [PATCH 059/137] fix notification title i18n --- .../twiderex/scenes/settings/AccountNotificationScene.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/AccountNotificationScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/AccountNotificationScene.kt index b9a098f26..eec3d1cca 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/AccountNotificationScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/AccountNotificationScene.kt @@ -39,6 +39,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import com.twidere.twiderex.BuildConfig +import com.twidere.twiderex.R import com.twidere.twiderex.component.foundation.AppBar import com.twidere.twiderex.component.foundation.AppBarNavigationButton import com.twidere.twiderex.component.foundation.ColoredSwitch @@ -71,7 +72,7 @@ fun AccountNotificationScene( topBar = { AppBar( title = { - Text(text = "Notification") + Text(text = stringResource(id = R.string.scene_settings_notification_title)) }, navigationIcon = { AppBarNavigationButton() From 917a705f0f10384481abd861374fb1bf731060f0 Mon Sep 17 00:00:00 2001 From: itsMimao Date: Fri, 23 Jul 2021 15:14:19 +0800 Subject: [PATCH 060/137] add app notification manager interface and implement it for android --- .../com/twidere/twiderex/di/TwidereModule.kt | 8 ++ .../notification/AppNotificationManager.kt | 93 +++++++++++++++++++ .../android/AndroidNotificationManager.kt | 92 ++++++++++++++++++ 3 files changed, 193 insertions(+) create mode 100644 app/src/main/kotlin/com/twidere/twiderex/notification/AppNotificationManager.kt create mode 100644 app/src/main/kotlin/com/twidere/twiderex/notification/android/AndroidNotificationManager.kt diff --git a/app/src/main/kotlin/com/twidere/twiderex/di/TwidereModule.kt b/app/src/main/kotlin/com/twidere/twiderex/di/TwidereModule.kt index 787898e1a..3506ac772 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/di/TwidereModule.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/di/TwidereModule.kt @@ -21,6 +21,7 @@ package com.twidere.twiderex.di import android.content.Context +import androidx.core.app.NotificationManagerCompat import androidx.datastore.core.DataStore import androidx.work.WorkManager import com.twidere.services.nitter.NitterService @@ -30,6 +31,7 @@ import com.twidere.twiderex.db.CacheDatabase import com.twidere.twiderex.http.TwidereServiceFactory import com.twidere.twiderex.model.AccountPreferences import com.twidere.twiderex.notification.InAppNotification +import com.twidere.twiderex.notification.android.AndroidNotificationManager import com.twidere.twiderex.preferences.proto.MiscPreferences import com.twidere.twiderex.utils.PlatformResolver import dagger.Module @@ -76,4 +78,10 @@ object TwidereModule { @Provides fun provideAccountPreferencesFactory(@ApplicationContext context: Context): AccountPreferences.Factory = AccountPreferences.Factory(context) + + @Provides + fun provideAppNotificationManager(@ApplicationContext context: Context, notificationManagerCompat: NotificationManagerCompat) = AndroidNotificationManager( + context = context, + notificationManagerCompat = notificationManagerCompat + ) } diff --git a/app/src/main/kotlin/com/twidere/twiderex/notification/AppNotificationManager.kt b/app/src/main/kotlin/com/twidere/twiderex/notification/AppNotificationManager.kt new file mode 100644 index 000000000..a9bdbd660 --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/notification/AppNotificationManager.kt @@ -0,0 +1,93 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.notification + +import android.graphics.Bitmap +import java.util.concurrent.TimeUnit + +interface AppNotificationManager { + fun notify(notificationId: Int, appNotification: AppNotification) + + fun notifyTransient(notificationId: Int, appNotification: AppNotification, duration: Long = 5, durationTimeUnit: TimeUnit = TimeUnit.SECONDS) +} + +open class AppNotification( + val channelId: String, + val title: String, + val content: CharSequence? = null, + val largeIcon: Bitmap? = null, + val deepLink: String? = null, + val onGoing: Boolean = false, + val progress: Int = 0, + val progressMax: Int = 0, + val progressIndeterminate: Boolean = false, + val silent: Boolean = false, +) { + class Builder(private var channelId: String, private var title: String) { + private var content: CharSequence? = null + private var largeIcon: Bitmap? = null + private var deepLink: String? = null + private var onGoing: Boolean = false + private var progress: Int = 0 + private var progressMax: Int = 0 + private var progressIndeterminate: Boolean = false + private var silent: Boolean = false + + fun setContent(content: CharSequence) = this.apply { + this.content = content + } + + fun setLargeIcon(largeIcon: Bitmap?) = this.apply { + this.largeIcon = largeIcon + } + + fun setDeepLink(deepLink: String?) = this.apply { + this.deepLink = deepLink + } + + fun setOnGoing(onGoing: Boolean) = this.apply { + this.onGoing = onGoing + } + + fun setSilent(silent: Boolean) = this.apply { + this.silent = silent + } + + fun setProgress(max: Int, progress: Int, indeterminate: Boolean) = this.apply { + this.progress = progress + this.progressMax = max + this.progressIndeterminate = indeterminate + } + + fun build() = AppNotification( + title = title, + channelId = channelId, + content = content, + largeIcon = largeIcon, + deepLink = deepLink, + onGoing = onGoing, + progress = progress, + progressMax = progressMax, + progressIndeterminate = progressIndeterminate, + silent = silent + ) + } +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/notification/android/AndroidNotificationManager.kt b/app/src/main/kotlin/com/twidere/twiderex/notification/android/AndroidNotificationManager.kt new file mode 100644 index 000000000..a0278bca8 --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/notification/android/AndroidNotificationManager.kt @@ -0,0 +1,92 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.notification.android + +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Build +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import com.twidere.twiderex.R +import com.twidere.twiderex.notification.AppNotification +import com.twidere.twiderex.notification.AppNotificationManager +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import java.util.concurrent.TimeUnit +import kotlin.time.ExperimentalTime + +class AndroidNotificationManager( + private val context: Context, + private val notificationManagerCompat: NotificationManagerCompat +) : AppNotificationManager { + val scope = MainScope() + override fun notify(notificationId: Int, appNotification: AppNotification) { + val builder = NotificationCompat.Builder( + context, + appNotification.channelId + ).setSmallIcon(R.drawable.ic_notification) + .setCategory(NotificationCompat.CATEGORY_SOCIAL) + .setPriority(NotificationCompat.PRIORITY_DEFAULT) + .setAutoCancel(true) + .setContentTitle(appNotification.title) + .setOngoing(appNotification.onGoing) + .setSilent(appNotification.silent) + .setProgress(appNotification.progressMax, appNotification.progress, appNotification.progressIndeterminate) + appNotification.content?.let { + builder.setContentText(it) + .setStyle(NotificationCompat.BigTextStyle().bigText(it)) + } + appNotification.deepLink?.let { + builder.setContentIntent( + PendingIntent.getActivity( + context, + 0, + Intent(Intent.ACTION_VIEW, Uri.parse(it)), + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + PendingIntent.FLAG_IMMUTABLE + } else { + PendingIntent.FLAG_UPDATE_CURRENT + }, + ) + ) + } + appNotification.largeIcon?.let { + builder.setLargeIcon(it) + } + notificationManagerCompat.notify(notificationId, builder.build()) + } + + @OptIn(ExperimentalTime::class) + override fun notifyTransient( + notificationId: Int, + appNotification: AppNotification, + duration: Long, + durationTimeUnit: TimeUnit + ) { + scope.launch { + delay(durationTimeUnit.toMillis(duration)) + notificationManagerCompat.cancel(notificationId) + } + } +} From 64f6ff3239a15e445fbd3aec4c66a75d186dfca5 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Fri, 23 Jul 2021 16:54:47 +0800 Subject: [PATCH 061/137] add gif support for user avatar --- .../component/foundation/NetworkImage.kt | 44 ++++++++++++------- buildSrc/src/main/kotlin/Dependencies.kt | 1 + 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/NetworkImage.kt b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/NetworkImage.kt index c0f94ab02..93284077d 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/NetworkImage.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/NetworkImage.kt @@ -20,6 +20,7 @@ */ package com.twidere.twiderex.component.foundation +import android.os.Build import androidx.compose.foundation.Image import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -32,6 +33,8 @@ import coil.annotation.ExperimentalCoilApi import coil.compose.ImagePainter import coil.compose.LocalImageLoader import coil.compose.rememberImagePainter +import coil.decode.GifDecoder +import coil.decode.ImageDecoderDecoder import coil.util.CoilUtils import com.twidere.twiderex.R import com.twidere.twiderex.http.TwidereNetworkImageLoader @@ -75,20 +78,31 @@ fun NetworkImage( @Composable fun buildRealImageLoader(): ImageLoader { + val context = LocalContext.current val httpConfig = LocalHttpConfig.current - return if (httpConfig.proxyConfig.enable && - httpConfig.proxyConfig.server.isNotEmpty() - ) { - LocalImageLoader.current - .newBuilder() - .callFactory( - TwidereServiceFactory.createHttpClientFactory() - .createHttpClientBuilder() - .cache(CoilUtils.createDefaultCache(LocalContext.current)) - .build() - ) - .build() - } else { - LocalImageLoader.current - } + return ( + if (httpConfig.proxyConfig.enable && + httpConfig.proxyConfig.server.isNotEmpty() + ) { + LocalImageLoader.current + .newBuilder() + .callFactory( + TwidereServiceFactory.createHttpClientFactory() + .createHttpClientBuilder() + .cache(CoilUtils.createDefaultCache(context)) + .build() + ) + .build() + } else { + LocalImageLoader.current + } + ).newBuilder() + .componentRegistry { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + add(ImageDecoderDecoder(context)) + } else { + add(GifDecoder()) + } + } + .build() } diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index 5df646694..a333db2e5 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -108,6 +108,7 @@ fun DependencyHandlerScope.android() { activity() implementation("androidx.startup:startup-runtime", Versions.startup) implementation("io.coil-kt:coil-compose", Versions.coil) + implementation("io.coil-kt:coil-gif", Versions.coil) implementation("androidx.vectordrawable:vectordrawable:1.1.0") implementation("androidx.exifinterface:exifinterface", Versions.androidx_exifinterface) implementation("com.google.android.exoplayer:exoplayer", Versions.exoplayer) From 77b4ebab8ebb2851eeb76eb05a7e22fdf902af59 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Fri, 23 Jul 2021 16:58:28 +0800 Subject: [PATCH 062/137] add properties to html text --- .../twiderex/component/status/HtmlText.kt | 56 +++++++++++++++++-- 1 file changed, 52 insertions(+), 4 deletions(-) diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/status/HtmlText.kt b/app/src/main/kotlin/com/twidere/twiderex/component/status/HtmlText.kt index 7fea533db..bc959d3de 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/status/HtmlText.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/status/HtmlText.kt @@ -35,6 +35,7 @@ import androidx.compose.runtime.ExperimentalComposeApi import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.consumeDownChange import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.text.AnnotatedString @@ -43,10 +44,14 @@ import androidx.compose.ui.text.PlaceholderVerticalAlign import androidx.compose.ui.text.TextLayoutResult import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.ExperimentalUnitApi import androidx.compose.ui.unit.TextUnit -import androidx.compose.ui.unit.TextUnitType import com.twidere.twiderex.component.foundation.NetworkImage import com.twidere.twiderex.component.navigation.LocalNavigator import kotlinx.coroutines.coroutineScope @@ -72,6 +77,17 @@ fun HtmlText( modifier: Modifier = Modifier, htmlText: String, maxLines: Int = Int.MAX_VALUE, + color: Color = Color.Unspecified, + fontSize: TextUnit = TextUnit.Unspecified, + fontStyle: FontStyle? = null, + fontWeight: FontWeight? = null, + fontFamily: FontFamily? = null, + letterSpacing: TextUnit = TextUnit.Unspecified, + textDecoration: TextDecoration? = null, + textAlign: TextAlign? = null, + lineHeight: TextUnit = TextUnit.Unspecified, + overflow: TextOverflow = TextOverflow.Ellipsis, + softWrap: Boolean = true, textStyle: TextStyle = LocalTextStyle.current.copy(color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current)), linkStyle: TextStyle = textStyle.copy(MaterialTheme.colors.primary), linkResolver: (href: String) -> ResolvedLink = { ResolvedLink(it) }, @@ -87,6 +103,17 @@ fun HtmlText( onLinkClicked = { navigator.openLink(it) }, + color = color, + fontSize = fontSize, + fontStyle = fontStyle, + fontWeight = fontWeight, + fontFamily = fontFamily, + letterSpacing = letterSpacing, + textDecoration = textDecoration, + textAlign = textAlign, + lineHeight = lineHeight, + overflow = overflow, + softWrap = softWrap, ) } @@ -95,11 +122,22 @@ fun HtmlText( private fun RenderContent( modifier: Modifier = Modifier, htmlText: String, - maxLines: Int = Int.MAX_VALUE, textStyle: TextStyle, linkStyle: TextStyle, linkResolver: (href: String) -> ResolvedLink = { ResolvedLink(it) }, onLinkClicked: (String) -> Unit = {}, + maxLines: Int = Int.MAX_VALUE, + color: Color = Color.Unspecified, + fontSize: TextUnit = TextUnit.Unspecified, + fontStyle: FontStyle? = null, + fontWeight: FontWeight? = null, + fontFamily: FontFamily? = null, + letterSpacing: TextUnit = TextUnit.Unspecified, + textDecoration: TextDecoration? = null, + textAlign: TextAlign? = null, + lineHeight: TextUnit = TextUnit.Unspecified, + overflow: TextOverflow = TextOverflow.Clip, + softWrap: Boolean = true, ) { val value = renderContentAnnotatedString( htmlText = htmlText, @@ -133,8 +171,18 @@ private fun RenderContent( } } }, - overflow = TextOverflow.Ellipsis, maxLines = maxLines, + color = color, + fontSize = fontSize, + fontStyle = fontStyle, + fontWeight = fontWeight, + fontFamily = fontFamily, + letterSpacing = letterSpacing, + textDecoration = textDecoration, + textAlign = textAlign, + lineHeight = lineHeight, + overflow = overflow, + softWrap = softWrap, text = value, onTextLayout = { layoutResult.value = it @@ -161,7 +209,7 @@ private fun RenderContent( @Composable fun renderContentAnnotatedString( htmlText: String, - textStyle: TextStyle = MaterialTheme.typography.body1.copy(color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current), letterSpacing = TextUnit(0.25f, TextUnitType.Sp)), + textStyle: TextStyle = LocalTextStyle.current.copy(color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current)), linkStyle: TextStyle = textStyle.copy(MaterialTheme.colors.primary), linkResolver: (href: String) -> ResolvedLink, ): AnnotatedString { From ba2834410c296d3887b175a5d1bf8b58bbc7d913 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Fri, 23 Jul 2021 16:58:45 +0800 Subject: [PATCH 063/137] add emoji support for mastodon user name --- .../twiderex/component/status/UserScreenName.kt | 11 +++-------- .../kotlin/com/twidere/twiderex/db/mapper/Mastodon.kt | 5 +++-- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/status/UserScreenName.kt b/app/src/main/kotlin/com/twidere/twiderex/component/status/UserScreenName.kt index 39c5ef35c..dfdcd1385 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/status/UserScreenName.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/status/UserScreenName.kt @@ -28,7 +28,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.TextLayoutResult import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontStyle @@ -73,7 +72,6 @@ fun UserName( overflow: TextOverflow = TextOverflow.Ellipsis, softWrap: Boolean = true, maxLines: Int = 1, - onTextLayout: (TextLayoutResult) -> Unit = {}, style: TextStyle = LocalTextStyle.current ) { UserName( @@ -91,7 +89,6 @@ fun UserName( overflow = overflow, softWrap = softWrap, maxLines = maxLines, - onTextLayout = onTextLayout, style = style, ) } @@ -112,11 +109,10 @@ fun UserName( overflow: TextOverflow = TextOverflow.Ellipsis, softWrap: Boolean = true, maxLines: Int = 1, - onTextLayout: (TextLayoutResult) -> Unit = {}, style: TextStyle = LocalTextStyle.current ) { - Text( - text = userName, + HtmlText( + htmlText = userName, modifier = modifier, color = color, fontSize = fontSize, @@ -130,7 +126,6 @@ fun UserName( overflow = overflow, softWrap = softWrap, maxLines = maxLines, - onTextLayout = onTextLayout, - style = style, + textStyle = style, ) } diff --git a/app/src/main/kotlin/com/twidere/twiderex/db/mapper/Mastodon.kt b/app/src/main/kotlin/com/twidere/twiderex/db/mapper/Mastodon.kt index 27586603a..f80f325be 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/db/mapper/Mastodon.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/db/mapper/Mastodon.kt @@ -291,8 +291,9 @@ fun Account.toDbUser( return DbUser( _id = UUID.randomUUID().toString(), userId = this.id ?: throw IllegalArgumentException("mastodon user.id should not be null"), - name = displayName - ?: throw IllegalArgumentException("mastodon user.displayName should not be null"), + name = displayName?.let { + generateHtmlContentWithEmoji(it, emojis ?: emptyList()) + } ?: throw IllegalArgumentException("mastodon user.displayName should not be null"), screenName = username ?: throw IllegalArgumentException("mastodon user.username should not be null"), userKey = MicroBlogKey( From 6e5a808646f6e3a9f119ac42b2db48e242b2f417 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Fri, 23 Jul 2021 17:42:27 +0800 Subject: [PATCH 064/137] add federated timeline and local timeline --- .../com/twidere/twiderex/navigation/Root.kt | 2 + .../com/twidere/twiderex/navigation/Route.kt | 12 ++- .../{ => timeline}/HomeTimelineMediator.kt | 2 +- .../MastodonHashtagTimelineMediator.kt | 2 +- .../{ => timeline}/MentionTimelineMediator.kt | 2 +- .../NotificationTimelineMediator.kt | 2 +- .../mastodon/FederatedTimelineMediator.kt | 36 +++++++ .../mastodon/LocalTimelineMediator.kt | 36 +++++++ .../twiderex/repository/TimelineRepository.kt | 2 +- .../twidere/twiderex/scenes/home/HomeMenus.kt | 13 +++ .../home/mastodon/FederatedTimelineItem.kt | 99 +++++++++++++++++++ .../scenes/home/mastodon/LocalTimelineItem.kt | 99 +++++++++++++++++++ .../MastodonNotificationItem.kt | 5 +- .../timeline/HomeTimelineViewModel.kt | 2 +- .../timeline/MentionsTimelineViewModel.kt | 2 +- .../timeline/NotificationTimelineViewModel.kt | 2 +- .../mastodon/FederatedTimelineViewModel.kt | 51 ++++++++++ .../mastodon/LocalTimelineViewModel.kt | 51 ++++++++++ 18 files changed, 410 insertions(+), 10 deletions(-) rename app/src/main/kotlin/com/twidere/twiderex/paging/mediator/{ => timeline}/HomeTimelineMediator.kt (96%) rename app/src/main/kotlin/com/twidere/twiderex/paging/mediator/{ => timeline}/MastodonHashtagTimelineMediator.kt (96%) rename app/src/main/kotlin/com/twidere/twiderex/paging/mediator/{ => timeline}/MentionTimelineMediator.kt (97%) rename app/src/main/kotlin/com/twidere/twiderex/paging/mediator/{ => timeline}/NotificationTimelineMediator.kt (97%) create mode 100644 app/src/main/kotlin/com/twidere/twiderex/paging/mediator/timeline/mastodon/FederatedTimelineMediator.kt create mode 100644 app/src/main/kotlin/com/twidere/twiderex/paging/mediator/timeline/mastodon/LocalTimelineMediator.kt create mode 100644 app/src/main/kotlin/com/twidere/twiderex/scenes/home/mastodon/FederatedTimelineItem.kt create mode 100644 app/src/main/kotlin/com/twidere/twiderex/scenes/home/mastodon/LocalTimelineItem.kt rename app/src/main/kotlin/com/twidere/twiderex/scenes/home/{ => mastodon}/MastodonNotificationItem.kt (95%) create mode 100644 app/src/main/kotlin/com/twidere/twiderex/viewmodel/timeline/mastodon/FederatedTimelineViewModel.kt create mode 100644 app/src/main/kotlin/com/twidere/twiderex/viewmodel/timeline/mastodon/LocalTimelineViewModel.kt diff --git a/app/src/main/kotlin/com/twidere/twiderex/navigation/Root.kt b/app/src/main/kotlin/com/twidere/twiderex/navigation/Root.kt index dcb7a1643..9fd861e6f 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/navigation/Root.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/navigation/Root.kt @@ -97,6 +97,8 @@ interface Root { interface Mastodon { fun Hashtag(keyword: String): String val Notification: String + val FederatedTimeline: String + val LocalTimeline: String interface Compose { val Hashtag: String diff --git a/app/src/main/kotlin/com/twidere/twiderex/navigation/Route.kt b/app/src/main/kotlin/com/twidere/twiderex/navigation/Route.kt index 33a136f03..1e2e1cfcd 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/navigation/Route.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/navigation/Route.kt @@ -48,9 +48,11 @@ import com.twidere.twiderex.scenes.dm.DMConversationListScene import com.twidere.twiderex.scenes.dm.DMConversationScene import com.twidere.twiderex.scenes.dm.DMNewConversationScene import com.twidere.twiderex.scenes.home.HomeTimelineScene -import com.twidere.twiderex.scenes.home.MastodonNotificationScene import com.twidere.twiderex.scenes.home.MeScene import com.twidere.twiderex.scenes.home.MentionScene +import com.twidere.twiderex.scenes.home.mastodon.FederatedTimelineScene +import com.twidere.twiderex.scenes.home.mastodon.LocalTimelineScene +import com.twidere.twiderex.scenes.home.mastodon.MastodonNotificationScene import com.twidere.twiderex.scenes.lists.ListTimeLineScene import com.twidere.twiderex.scenes.lists.ListsAddMembersScene import com.twidere.twiderex.scenes.lists.ListsMembersScene @@ -215,6 +217,14 @@ fun RouteBuilder.route(constraints: Constraints) { MastodonNotificationScene() } + authorizedScene(RootRouteDefinition.Mastodon.FederatedTimeline) { + FederatedTimelineScene() + } + + authorizedScene(RootRouteDefinition.Mastodon.LocalTimeline) { + LocalTimelineScene() + } + authorizedScene(RootRouteDefinition.Me) { MeScene() } diff --git a/app/src/main/kotlin/com/twidere/twiderex/paging/mediator/HomeTimelineMediator.kt b/app/src/main/kotlin/com/twidere/twiderex/paging/mediator/timeline/HomeTimelineMediator.kt similarity index 96% rename from app/src/main/kotlin/com/twidere/twiderex/paging/mediator/HomeTimelineMediator.kt rename to app/src/main/kotlin/com/twidere/twiderex/paging/mediator/timeline/HomeTimelineMediator.kt index fbfd1bd0b..9d65da848 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/paging/mediator/HomeTimelineMediator.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/paging/mediator/timeline/HomeTimelineMediator.kt @@ -18,7 +18,7 @@ * You should have received a copy of the GNU General Public License * along with Twidere X. If not, see . */ -package com.twidere.twiderex.paging.mediator +package com.twidere.twiderex.paging.mediator.timeline import androidx.paging.ExperimentalPagingApi import com.twidere.services.microblog.TimelineService diff --git a/app/src/main/kotlin/com/twidere/twiderex/paging/mediator/MastodonHashtagTimelineMediator.kt b/app/src/main/kotlin/com/twidere/twiderex/paging/mediator/timeline/MastodonHashtagTimelineMediator.kt similarity index 96% rename from app/src/main/kotlin/com/twidere/twiderex/paging/mediator/MastodonHashtagTimelineMediator.kt rename to app/src/main/kotlin/com/twidere/twiderex/paging/mediator/timeline/MastodonHashtagTimelineMediator.kt index 126dba8a1..525e93564 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/paging/mediator/MastodonHashtagTimelineMediator.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/paging/mediator/timeline/MastodonHashtagTimelineMediator.kt @@ -18,7 +18,7 @@ * You should have received a copy of the GNU General Public License * along with Twidere X. If not, see . */ -package com.twidere.twiderex.paging.mediator +package com.twidere.twiderex.paging.mediator.timeline import com.twidere.services.mastodon.MastodonService import com.twidere.services.microblog.model.IStatus diff --git a/app/src/main/kotlin/com/twidere/twiderex/paging/mediator/MentionTimelineMediator.kt b/app/src/main/kotlin/com/twidere/twiderex/paging/mediator/timeline/MentionTimelineMediator.kt similarity index 97% rename from app/src/main/kotlin/com/twidere/twiderex/paging/mediator/MentionTimelineMediator.kt rename to app/src/main/kotlin/com/twidere/twiderex/paging/mediator/timeline/MentionTimelineMediator.kt index 7dfcc5361..a3542733d 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/paging/mediator/MentionTimelineMediator.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/paging/mediator/timeline/MentionTimelineMediator.kt @@ -18,7 +18,7 @@ * You should have received a copy of the GNU General Public License * along with Twidere X. If not, see . */ -package com.twidere.twiderex.paging.mediator +package com.twidere.twiderex.paging.mediator.timeline import com.twidere.services.microblog.TimelineService import com.twidere.services.microblog.model.IStatus diff --git a/app/src/main/kotlin/com/twidere/twiderex/paging/mediator/NotificationTimelineMediator.kt b/app/src/main/kotlin/com/twidere/twiderex/paging/mediator/timeline/NotificationTimelineMediator.kt similarity index 97% rename from app/src/main/kotlin/com/twidere/twiderex/paging/mediator/NotificationTimelineMediator.kt rename to app/src/main/kotlin/com/twidere/twiderex/paging/mediator/timeline/NotificationTimelineMediator.kt index cb6376b7d..b855d7f13 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/paging/mediator/NotificationTimelineMediator.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/paging/mediator/timeline/NotificationTimelineMediator.kt @@ -18,7 +18,7 @@ * You should have received a copy of the GNU General Public License * along with Twidere X. If not, see . */ -package com.twidere.twiderex.paging.mediator +package com.twidere.twiderex.paging.mediator.timeline import com.twidere.services.microblog.NotificationService import com.twidere.services.microblog.model.IStatus diff --git a/app/src/main/kotlin/com/twidere/twiderex/paging/mediator/timeline/mastodon/FederatedTimelineMediator.kt b/app/src/main/kotlin/com/twidere/twiderex/paging/mediator/timeline/mastodon/FederatedTimelineMediator.kt new file mode 100644 index 000000000..0580cd3d8 --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/paging/mediator/timeline/mastodon/FederatedTimelineMediator.kt @@ -0,0 +1,36 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.paging.mediator.timeline.mastodon + +import com.twidere.services.mastodon.MastodonService +import com.twidere.twiderex.db.CacheDatabase +import com.twidere.twiderex.model.MicroBlogKey +import com.twidere.twiderex.paging.mediator.paging.PagingWithGapMediator + +class FederatedTimelineMediator( + private val service: MastodonService, + accountKey: MicroBlogKey, + database: CacheDatabase, +) : PagingWithGapMediator(accountKey, database) { + override val pagingKey: String = "federated:$accountKey" + override suspend fun loadBetweenImpl(pageSize: Int, max_id: String?, since_id: String?) = + service.federatedTimeline(pageSize, max_id = max_id, since_id = since_id) +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/paging/mediator/timeline/mastodon/LocalTimelineMediator.kt b/app/src/main/kotlin/com/twidere/twiderex/paging/mediator/timeline/mastodon/LocalTimelineMediator.kt new file mode 100644 index 000000000..6fc1144f3 --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/paging/mediator/timeline/mastodon/LocalTimelineMediator.kt @@ -0,0 +1,36 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.paging.mediator.timeline.mastodon + +import com.twidere.services.mastodon.MastodonService +import com.twidere.twiderex.db.CacheDatabase +import com.twidere.twiderex.model.MicroBlogKey +import com.twidere.twiderex.paging.mediator.paging.PagingWithGapMediator + +class LocalTimelineMediator( + private val service: MastodonService, + accountKey: MicroBlogKey, + database: CacheDatabase, +) : PagingWithGapMediator(accountKey, database) { + override val pagingKey: String = "local:$accountKey" + override suspend fun loadBetweenImpl(pageSize: Int, max_id: String?, since_id: String?) = + service.localTimeline(pageSize, max_id = max_id, since_id = since_id) +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/repository/TimelineRepository.kt b/app/src/main/kotlin/com/twidere/twiderex/repository/TimelineRepository.kt index 2884e1193..f36a03f3a 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/repository/TimelineRepository.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/repository/TimelineRepository.kt @@ -29,9 +29,9 @@ import com.twidere.twiderex.extensions.toUi import com.twidere.twiderex.model.AccountDetails import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.ui.UiStatus -import com.twidere.twiderex.paging.mediator.MastodonHashtagTimelineMediator import com.twidere.twiderex.paging.mediator.list.ListsTimelineMediator import com.twidere.twiderex.paging.mediator.paging.pager +import com.twidere.twiderex.paging.mediator.timeline.MastodonHashtagTimelineMediator import com.twidere.twiderex.paging.mediator.user.UserFavouriteMediator import com.twidere.twiderex.paging.mediator.user.UserStatusMediator import kotlinx.coroutines.flow.Flow diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/HomeMenus.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/HomeMenus.kt index f3a82189d..7a3e3db16 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/HomeMenus.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/HomeMenus.kt @@ -21,6 +21,9 @@ package com.twidere.twiderex.scenes.home import com.twidere.twiderex.model.PlatformType +import com.twidere.twiderex.scenes.home.mastodon.FederatedTimelineItem +import com.twidere.twiderex.scenes.home.mastodon.LocalTimelineItem +import com.twidere.twiderex.scenes.home.mastodon.MastodonNotificationItem enum class HomeMenus( val item: HomeNavigationItem, @@ -57,6 +60,16 @@ enum class HomeMenus( showDefault = false, supportedPlatformType = listOf(PlatformType.Twitter), ), + FederatedTimeline( + item = FederatedTimelineItem(), + showDefault = false, + supportedPlatformType = listOf(PlatformType.Mastodon), + ), + LocalTimeline( + item = LocalTimelineItem(), + showDefault = false, + supportedPlatformType = listOf(PlatformType.Mastodon), + ), Draft( item = DraftNavigationItem(), showDefault = false, diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/mastodon/FederatedTimelineItem.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/mastodon/FederatedTimelineItem.kt new file mode 100644 index 000000000..e07d6e32c --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/mastodon/FederatedTimelineItem.kt @@ -0,0 +1,99 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.scenes.home.mastodon + +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.List +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.graphics.vector.rememberVectorPainter +import com.twidere.services.mastodon.MastodonService +import com.twidere.twiderex.component.TimelineComponent +import com.twidere.twiderex.component.foundation.AppBar +import com.twidere.twiderex.component.foundation.AppBarNavigationButton +import com.twidere.twiderex.component.foundation.InAppNotificationScaffold +import com.twidere.twiderex.component.lazy.LazyListController +import com.twidere.twiderex.di.assisted.assistedViewModel +import com.twidere.twiderex.navigation.RootRoute +import com.twidere.twiderex.scenes.home.HomeNavigationItem +import com.twidere.twiderex.ui.LocalActiveAccount +import com.twidere.twiderex.ui.TwidereScene +import com.twidere.twiderex.viewmodel.timeline.mastodon.FederatedTimelineViewModel + +class FederatedTimelineItem : HomeNavigationItem() { + @Composable + override fun name(): String { + return "Federated" + } + + override val route: String + get() = RootRoute.Mastodon.FederatedTimeline + + @Composable + override fun icon(): Painter { + return rememberVectorPainter(Icons.Default.List) + } + + @Composable + override fun Content() { + FederatedTimelineContent( + lazyListController = lazyListController + ) + } +} + +@Composable +fun FederatedTimelineScene() { + TwidereScene { + InAppNotificationScaffold( + topBar = { + AppBar( + navigationIcon = { + AppBarNavigationButton() + }, + title = { + Text(text = "Federated") + } + ) + } + ) { + FederatedTimelineContent() + } + } +} + +@Composable +fun FederatedTimelineContent( + lazyListController: LazyListController? = null, +) { + val account = LocalActiveAccount.current ?: return + if (account.service !is MastodonService) { + return + } + val viewModel = + assistedViewModel( + account + ) { + it.create(account = account) + } + TimelineComponent(viewModel = viewModel, lazyListController = lazyListController) +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/mastodon/LocalTimelineItem.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/mastodon/LocalTimelineItem.kt new file mode 100644 index 000000000..4fc61239b --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/mastodon/LocalTimelineItem.kt @@ -0,0 +1,99 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.scenes.home.mastodon + +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.List +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.graphics.vector.rememberVectorPainter +import com.twidere.services.mastodon.MastodonService +import com.twidere.twiderex.component.TimelineComponent +import com.twidere.twiderex.component.foundation.AppBar +import com.twidere.twiderex.component.foundation.AppBarNavigationButton +import com.twidere.twiderex.component.foundation.InAppNotificationScaffold +import com.twidere.twiderex.component.lazy.LazyListController +import com.twidere.twiderex.di.assisted.assistedViewModel +import com.twidere.twiderex.navigation.RootRoute +import com.twidere.twiderex.scenes.home.HomeNavigationItem +import com.twidere.twiderex.ui.LocalActiveAccount +import com.twidere.twiderex.ui.TwidereScene +import com.twidere.twiderex.viewmodel.timeline.mastodon.LocalTimelineViewModel + +class LocalTimelineItem : HomeNavigationItem() { + @Composable + override fun name(): String { + return "Local" + } + + override val route: String + get() = RootRoute.Mastodon.LocalTimeline + + @Composable + override fun icon(): Painter { + return rememberVectorPainter(Icons.Default.List) + } + + @Composable + override fun Content() { + LocalTimelineContent( + lazyListController = lazyListController + ) + } +} + +@Composable +fun LocalTimelineScene() { + TwidereScene { + InAppNotificationScaffold( + topBar = { + AppBar( + navigationIcon = { + AppBarNavigationButton() + }, + title = { + Text(text = "Local") + } + ) + } + ) { + LocalTimelineContent() + } + } +} + +@Composable +fun LocalTimelineContent( + lazyListController: LazyListController? = null, +) { + val account = LocalActiveAccount.current ?: return + if (account.service !is MastodonService) { + return + } + val viewModel = + assistedViewModel( + account + ) { + it.create(account = account) + } + TimelineComponent(viewModel = viewModel, lazyListController = lazyListController) +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/MastodonNotificationItem.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/mastodon/MastodonNotificationItem.kt similarity index 95% rename from app/src/main/kotlin/com/twidere/twiderex/scenes/home/MastodonNotificationItem.kt rename to app/src/main/kotlin/com/twidere/twiderex/scenes/home/mastodon/MastodonNotificationItem.kt index 17b8e69dd..25ebfe809 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/MastodonNotificationItem.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/mastodon/MastodonNotificationItem.kt @@ -18,7 +18,7 @@ * You should have received a copy of the GNU General Public License * along with Twidere X. If not, see . */ -package com.twidere.twiderex.scenes.home +package com.twidere.twiderex.scenes.home.mastodon import androidx.compose.material.Scaffold import androidx.compose.material.Text @@ -39,6 +39,9 @@ import com.twidere.twiderex.component.foundation.TextTabsComponent import com.twidere.twiderex.component.foundation.rememberPagerState import com.twidere.twiderex.component.lazy.LazyListController import com.twidere.twiderex.navigation.RootRoute +import com.twidere.twiderex.scenes.home.AllNotificationItem +import com.twidere.twiderex.scenes.home.HomeNavigationItem +import com.twidere.twiderex.scenes.home.MentionItem import com.twidere.twiderex.ui.LocalActiveAccount import com.twidere.twiderex.ui.TwidereScene import kotlinx.coroutines.launch diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/timeline/HomeTimelineViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/timeline/HomeTimelineViewModel.kt index 4ce22e078..b89573308 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/timeline/HomeTimelineViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/timeline/HomeTimelineViewModel.kt @@ -24,8 +24,8 @@ import android.content.SharedPreferences import com.twidere.services.microblog.TimelineService import com.twidere.twiderex.db.CacheDatabase import com.twidere.twiderex.model.AccountDetails -import com.twidere.twiderex.paging.mediator.HomeTimelineMediator import com.twidere.twiderex.paging.mediator.paging.PagingWithGapMediator +import com.twidere.twiderex.paging.mediator.timeline.HomeTimelineMediator import dagger.assisted.Assisted import dagger.assisted.AssistedInject diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/timeline/MentionsTimelineViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/timeline/MentionsTimelineViewModel.kt index 87f872782..bfbe4699d 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/timeline/MentionsTimelineViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/timeline/MentionsTimelineViewModel.kt @@ -24,8 +24,8 @@ import android.content.SharedPreferences import com.twidere.services.microblog.TimelineService import com.twidere.twiderex.db.CacheDatabase import com.twidere.twiderex.model.AccountDetails -import com.twidere.twiderex.paging.mediator.MentionTimelineMediator import com.twidere.twiderex.paging.mediator.paging.PagingWithGapMediator +import com.twidere.twiderex.paging.mediator.timeline.MentionTimelineMediator import com.twidere.twiderex.repository.NotificationRepository import dagger.assisted.Assisted import dagger.assisted.AssistedInject diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/timeline/NotificationTimelineViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/timeline/NotificationTimelineViewModel.kt index ad1be6939..89ab75921 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/timeline/NotificationTimelineViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/timeline/NotificationTimelineViewModel.kt @@ -24,8 +24,8 @@ import android.content.SharedPreferences import com.twidere.services.microblog.NotificationService import com.twidere.twiderex.db.CacheDatabase import com.twidere.twiderex.model.AccountDetails -import com.twidere.twiderex.paging.mediator.NotificationTimelineMediator import com.twidere.twiderex.paging.mediator.paging.PagingWithGapMediator +import com.twidere.twiderex.paging.mediator.timeline.NotificationTimelineMediator import com.twidere.twiderex.repository.NotificationRepository import dagger.assisted.Assisted import dagger.assisted.AssistedInject diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/timeline/mastodon/FederatedTimelineViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/timeline/mastodon/FederatedTimelineViewModel.kt new file mode 100644 index 000000000..8990510e0 --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/timeline/mastodon/FederatedTimelineViewModel.kt @@ -0,0 +1,51 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.viewmodel.timeline.mastodon + +import android.content.SharedPreferences +import com.twidere.services.mastodon.MastodonService +import com.twidere.twiderex.db.CacheDatabase +import com.twidere.twiderex.model.AccountDetails +import com.twidere.twiderex.paging.mediator.timeline.mastodon.FederatedTimelineMediator +import com.twidere.twiderex.viewmodel.timeline.TimelineViewModel +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject + +class FederatedTimelineViewModel @AssistedInject constructor( + preferences: SharedPreferences, + database: CacheDatabase, + @Assisted account: AccountDetails, +) : TimelineViewModel(preferences) { + @dagger.assisted.AssistedFactory + interface AssistedFactory { + fun create(account: AccountDetails): FederatedTimelineViewModel + } + + override val pagingMediator by lazy { + FederatedTimelineMediator( + account.service as MastodonService, + account.accountKey, + database, + ) + } + + override val savedStateKey = "${account.accountKey}_federated" +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/timeline/mastodon/LocalTimelineViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/timeline/mastodon/LocalTimelineViewModel.kt new file mode 100644 index 000000000..8b56dc661 --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/timeline/mastodon/LocalTimelineViewModel.kt @@ -0,0 +1,51 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.viewmodel.timeline.mastodon + +import android.content.SharedPreferences +import com.twidere.services.mastodon.MastodonService +import com.twidere.twiderex.db.CacheDatabase +import com.twidere.twiderex.model.AccountDetails +import com.twidere.twiderex.paging.mediator.timeline.mastodon.LocalTimelineMediator +import com.twidere.twiderex.viewmodel.timeline.TimelineViewModel +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject + +class LocalTimelineViewModel @AssistedInject constructor( + preferences: SharedPreferences, + database: CacheDatabase, + @Assisted account: AccountDetails, +) : TimelineViewModel(preferences) { + @dagger.assisted.AssistedFactory + interface AssistedFactory { + fun create(account: AccountDetails): LocalTimelineViewModel + } + + override val pagingMediator by lazy { + LocalTimelineMediator( + account.service as MastodonService, + account.accountKey, + database, + ) + } + + override val savedStateKey = "${account.accountKey}_local" +} From f030a938c14469fe78c0576734543ac05c5b8034 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Fri, 23 Jul 2021 22:52:00 +0800 Subject: [PATCH 065/137] upgrade jetpack dependencies --- .../paging/compose/LazyPagingItems.kt | 369 ------------------ buildSrc/src/main/kotlin/Dependencies.kt | 3 +- buildSrc/src/main/kotlin/Versions.kt | 12 +- 3 files changed, 8 insertions(+), 376 deletions(-) delete mode 100644 app/src/main/kotlin/androidx/paging/compose/LazyPagingItems.kt diff --git a/app/src/main/kotlin/androidx/paging/compose/LazyPagingItems.kt b/app/src/main/kotlin/androidx/paging/compose/LazyPagingItems.kt deleted file mode 100644 index 79b8915d7..000000000 --- a/app/src/main/kotlin/androidx/paging/compose/LazyPagingItems.kt +++ /dev/null @@ -1,369 +0,0 @@ -/* - * Twidere X - * - * Copyright (C) 2020-2021 Tlaster - * - * This file is part of Twidere X. - * - * Twidere X 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. - * - * Twidere X 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 Twidere X. If not, see . - */ -package androidx.paging.compose - -/* - * Copyright 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import android.annotation.SuppressLint -import android.os.Parcel -import android.os.Parcelable -import androidx.compose.foundation.lazy.LazyItemScope -import androidx.compose.foundation.lazy.LazyListScope -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.State -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberUpdatedState -import androidx.compose.runtime.setValue -import androidx.paging.CombinedLoadStates -import androidx.paging.DifferCallback -import androidx.paging.ItemSnapshotList -import androidx.paging.LoadState -import androidx.paging.LoadStates -import androidx.paging.NullPaddedList -import androidx.paging.PagingConfig -import androidx.paging.PagingData -import androidx.paging.PagingDataDiffer -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.collectLatest - -/** - * The class responsible for accessing the data from a [Flow] of [PagingData]. - * In order to obtain an instance of [LazyPagingItems] use the [collectAsLazyPagingItems] extension - * method of [Flow] with [PagingData]. - * This instance can be used by the [items] and [itemsIndexed] methods inside [LazyListScope] to - * display data received from the [Flow] of [PagingData]. - * - * @param T the type of value used by [PagingData]. - */ -public class LazyPagingItems internal constructor( - /** - * the [Flow] object which contains a stream of [PagingData] elements. - */ - private val flow: Flow> -) { - private val mainDispatcher = Dispatchers.Main - - /** - * Contains the latest items list snapshot collected from the [flow]. - */ - private var itemSnapshotList by mutableStateOf( - ItemSnapshotList(0, 0, emptyList()) - ) - - /** - * The number of items which can be accessed. - */ - val itemCount: Int get() = itemSnapshotList.size - - @SuppressLint("RestrictedApi") - private val differCallback: DifferCallback = object : DifferCallback { - override fun onChanged(position: Int, count: Int) { - if (count > 0) { - updateItemSnapshotList() - } - } - - override fun onInserted(position: Int, count: Int) { - if (count > 0) { - updateItemSnapshotList() - } - } - - override fun onRemoved(position: Int, count: Int) { - if (count > 0) { - updateItemSnapshotList() - } - } - } - - private val pagingDataDiffer = object : PagingDataDiffer( - differCallback = differCallback, - mainDispatcher = mainDispatcher - ) { - override suspend fun presentNewList( - previousList: NullPaddedList, - newList: NullPaddedList, - newCombinedLoadStates: CombinedLoadStates, - lastAccessedIndex: Int, - onListPresentable: () -> Unit - ): Int? { - onListPresentable() - updateItemSnapshotList() - return null - } - } - - private fun updateItemSnapshotList() { - itemSnapshotList = pagingDataDiffer.snapshot() - } - - /** - * Returns the presented item at the specified position, notifying Paging of the item access to - * trigger any loads necessary to fulfill prefetchDistance. - * - * @see peek - */ - operator fun get(index: Int): T? { - pagingDataDiffer[index] // this registers the value load - return itemSnapshotList[index] - } - - /** - * Returns the state containing the item specified at [index] and notifies Paging of the item - * accessed in order to trigger any loads necessary to fulfill [PagingConfig.prefetchDistance]. - * - * @param index the index of the item which should be returned. - * @return the state containing the item specified at [index] or null if the item is a - * placeholder or [index] is not within the correct bounds. - */ - @Composable - @Deprecated( - "Use get() instead. It will return you the value not wrapped into a State", - ReplaceWith("this[index]") - ) - fun getAsState(index: Int): State { - return rememberUpdatedState(get(index)) - } - - /** - * Returns the presented item at the specified position, without notifying Paging of the item - * access that would normally trigger page loads. - * - * @param index Index of the presented item to return, including placeholders. - * @return The presented item at position [index], `null` if it is a placeholder - */ - fun peek(index: Int): T? { - return itemSnapshotList[index] - } - - /** - * Returns a new [ItemSnapshotList] representing the currently presented items, including any - * placeholders if they are enabled. - */ - fun snapshot(): ItemSnapshotList { - return itemSnapshotList - } - - /** - * Retry any failed load requests that would result in a [LoadState.Error] update to this - * [LazyPagingItems]. - * - * Unlike [refresh], this does not invalidate [PagingSource], it only retries failed loads - * within the same generation of [PagingData]. - * - * [LoadState.Error] can be generated from two types of load requests: - * * [PagingSource.load] returning [PagingSource.LoadResult.Error] - * * [RemoteMediator.load] returning [RemoteMediator.MediatorResult.Error] - */ - fun retry() { - pagingDataDiffer.retry() - } - - /** - * Refresh the data presented by this [LazyPagingItems]. - * - * [refresh] triggers the creation of a new [PagingData] with a new instance of [PagingSource] - * to represent an updated snapshot of the backing dataset. If a [RemoteMediator] is set, - * calling [refresh] will also trigger a call to [RemoteMediator.load] with [LoadType] [REFRESH] - * to allow [RemoteMediator] to check for updates to the dataset backing [PagingSource]. - * - * Note: This API is intended for UI-driven refresh signals, such as swipe-to-refresh. - * Invalidation due repository-layer signals, such as DB-updates, should instead use - * [PagingSource.invalidate]. - * - * @see PagingSource.invalidate - */ - fun refresh() { - pagingDataDiffer.refresh() - } - - /** - * A [CombinedLoadStates] object which represents the current loading state. - */ - public var loadState: CombinedLoadStates by mutableStateOf( - CombinedLoadStates( - refresh = InitialLoadStates.refresh, - prepend = InitialLoadStates.prepend, - append = InitialLoadStates.append, - source = InitialLoadStates - ) - ) - private set - - internal suspend fun collectLoadState() { - pagingDataDiffer.loadStateFlow.collect { - loadState = it - } - } - - internal suspend fun collectPagingData() { - flow.collectLatest { - pagingDataDiffer.collectFrom(it) - } - } -} - -private val IncompleteLoadState = LoadState.NotLoading(false) -private val InitialLoadStates = LoadStates( - IncompleteLoadState, - IncompleteLoadState, - IncompleteLoadState -) - -/** - * Collects values from this [Flow] of [PagingData] and represents them inside a [LazyPagingItems] - * instance. The [LazyPagingItems] instance can be used by the [items] and [itemsIndexed] methods - * from [LazyListScope] in order to display the data obtained from a [Flow] of [PagingData]. - * - * @sample androidx.paging.compose.samples.PagingBackendSample - */ -@Composable -public fun Flow>.collectAsLazyPagingItems(): LazyPagingItems { - val lazyPagingItems = remember(this) { LazyPagingItems(this) } - - LaunchedEffect(lazyPagingItems) { - lazyPagingItems.collectPagingData() - } - LaunchedEffect(lazyPagingItems) { - lazyPagingItems.collectLoadState() - } - - return lazyPagingItems -} - -/** - * Adds the [LazyPagingItems] and their content to the scope. The range from 0 (inclusive) to - * [LazyPagingItems.itemCount] (exclusive) always represents the full range of presentable items, - * because every event from [PagingDataDiffer] will trigger a recomposition. - * - * @sample androidx.paging.compose.samples.ItemsDemo - * - * @param items the items received from a [Flow] of [PagingData]. - * @param key a factory of stable and unique keys representing the item. Using the same key - * for multiple items in the list is not allowed. Type of the key should be saveable - * via Bundle on Android. If null is passed the position in the list will represent the key. - * When you specify the key the scroll position will be maintained based on the key, which - * means if you add/remove items before the current visible item the item with the given key - * will be kept as the first visible one. - * @param itemContent the content displayed by a single item. In case the item is `null`, the - * [itemContent] method should handle the logic of displaying a placeholder instead of the main - * content displayed by an item which is not `null`. - */ -public fun LazyListScope.items( - items: LazyPagingItems, - key: ((item: T) -> Any)? = null, - itemContent: @Composable LazyItemScope.(value: T?) -> Unit -) { - items( - count = items.itemCount, - key = if (key == null) null else { index -> - val item = items.peek(index) - if (item == null) { - PagingPlaceholderKey(index) - } else { - key(item) - } - } - ) { index -> - itemContent(items[index]) - } -} - -/** - * Adds the [LazyPagingItems] and their content to the scope where the content of an item is - * aware of its local index. The range from 0 (inclusive) to [LazyPagingItems.itemCount] (exclusive) - * always represents the full range of presentable items, because every event from - * [PagingDataDiffer] will trigger a recomposition. - * - * @sample androidx.paging.compose.samples.ItemsIndexedDemo - * - * @param items the items received from a [Flow] of [PagingData]. - * @param key a factory of stable and unique keys representing the item. Using the same key - * for multiple items in the list is not allowed. Type of the key should be saveable - * via Bundle on Android. If null is passed the position in the list will represent the key. - * When you specify the key the scroll position will be maintained based on the key, which - * means if you add/remove items before the current visible item the item with the given key - * will be kept as the first visible one. - * @param itemContent the content displayed by a single item. In case the item is `null`, the - * [itemContent] method should handle the logic of displaying a placeholder instead of the main - * content displayed by an item which is not `null`. - */ -public fun LazyListScope.itemsIndexed( - items: LazyPagingItems, - key: ((index: Int, item: T) -> Any)? = null, - itemContent: @Composable LazyItemScope.(index: Int, value: T?) -> Unit -) { - items( - count = items.itemCount, - key = if (key == null) null else { index -> - val item = items.peek(index) - if (item == null) { - PagingPlaceholderKey(index) - } else { - key(index, item) - } - } - ) { index -> - itemContent(index, items[index]) - } -} - -@SuppressLint("BanParcelableUsage") -private data class PagingPlaceholderKey(private val index: Int) : Parcelable { - override fun writeToParcel(parcel: Parcel, flags: Int) { - parcel.writeInt(index) - } - - override fun describeContents(): Int { - return 0 - } - - companion object { - @Suppress("unused") - @JvmField - val CREATOR: Parcelable.Creator = - object : Parcelable.Creator { - override fun createFromParcel(parcel: Parcel) = - PagingPlaceholderKey(parcel.readInt()) - - override fun newArray(size: Int) = arrayOfNulls(size) - } - } -} diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index a333db2e5..0b1ec66ac 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -66,7 +66,7 @@ fun DependencyHandlerScope.compose() { fun DependencyHandlerScope.paging() { implementation("androidx.paging:paging-common", Versions.paging) - // implementation("androidx.paging:paging-compose", Versions.paging_compose) + implementation("androidx.paging:paging-compose", Versions.paging_compose) } fun DependencyHandlerScope.activity() { @@ -89,6 +89,7 @@ fun DependencyHandlerScope.hilt() { fun DependencyHandlerScope.room() { implementation("androidx.room:room-runtime", Versions.room) implementation("androidx.room:room-ktx", Versions.room) + implementation("androidx.room:room-paging", Versions.room) kapt("androidx.room:room-compiler", Versions.room) androidTestImplementation("androidx.room:room-testing", Versions.room) } diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index bd27ee8e8..fc28e6dfe 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -13,7 +13,7 @@ object Versions { } const val ksp = "1.5.10-1.0.0-beta02" - const val agp = "7.0.0-beta05" + const val agp = "7.0.0-rc01" const val spotless = "5.12.5" const val ktlint = "0.41.0" const val hilt = "2.37" @@ -22,20 +22,20 @@ object Versions { const val hson = "0.1.4" const val compose = "1.0.0-rc02" const val constraintLayout = "1.0.0-alpha07" - const val paging = "3.1.0-alpha02" - const val paging_compose = "1.0.0-alpha10" + const val paging = "3.1.0-alpha03" + const val paging_compose = "1.0.0-alpha12" const val activity = "1.3.0-rc02" const val datastore = "1.0.0-rc01" const val androidx_hilt = "1.0.0" - const val room = "2.4.0-alpha03" + const val room = "2.4.0-alpha04" const val lifecycle = "2.4.0-alpha02" const val lifecycle_compose = "1.0.0-alpha07" - const val work = "2.7.0-alpha04" + const val work = "2.7.0-alpha05" const val placeholder = "0.7.0" const val zoomable = "1.0.1" const val swiper = "0.6.0" const val nestedScrollView = "0.7.0" - const val startup = "1.1.0-alpha01" + const val startup = "1.1.0-rc01" const val coil = "1.3.0" const val accompanist = "0.14.0" const val androidx_exifinterface = "1.3.2" From 8acb0d075b5937413e469f73dc1afd91ad850528 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Mon, 26 Jul 2021 11:57:45 +0800 Subject: [PATCH 066/137] upgrade constraint layout --- buildSrc/src/main/kotlin/Versions.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index fc28e6dfe..909c9c175 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -21,7 +21,7 @@ object Versions { const val retrofit2 = "2.9.0" const val hson = "0.1.4" const val compose = "1.0.0-rc02" - const val constraintLayout = "1.0.0-alpha07" + const val constraintLayout = "1.0.0-beta01" const val paging = "3.1.0-alpha03" const val paging_compose = "1.0.0-alpha12" const val activity = "1.3.0-rc02" From 2108af753bd93e536b87de75bf7dd8ab91ba6e32 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Mon, 26 Jul 2021 13:23:23 +0800 Subject: [PATCH 067/137] update resources --- .../twidere/twiderex/scenes/home/HomeMenus.kt | 8 ++--- .../home/mastodon/FederatedTimelineItem.kt | 10 +++--- .../scenes/home/mastodon/LocalTimelineItem.kt | 10 +++--- app/src/main/res/drawable/ic_users.xml | 34 +++++++++++++++++++ app/src/main/res/values/strings.xml | 2 ++ localization | 2 +- 6 files changed, 51 insertions(+), 15 deletions(-) create mode 100644 app/src/main/res/drawable/ic_users.xml diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/HomeMenus.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/HomeMenus.kt index 7a3e3db16..2891e10a1 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/HomeMenus.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/HomeMenus.kt @@ -60,13 +60,13 @@ enum class HomeMenus( showDefault = false, supportedPlatformType = listOf(PlatformType.Twitter), ), - FederatedTimeline( - item = FederatedTimelineItem(), + LocalTimeline( + item = LocalTimelineItem(), showDefault = false, supportedPlatformType = listOf(PlatformType.Mastodon), ), - LocalTimeline( - item = LocalTimelineItem(), + FederatedTimeline( + item = FederatedTimelineItem(), showDefault = false, supportedPlatformType = listOf(PlatformType.Mastodon), ), diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/mastodon/FederatedTimelineItem.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/mastodon/FederatedTimelineItem.kt index e07d6e32c..f6c61aed7 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/mastodon/FederatedTimelineItem.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/mastodon/FederatedTimelineItem.kt @@ -21,12 +21,12 @@ package com.twidere.twiderex.scenes.home.mastodon import androidx.compose.material.Text -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.List import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.graphics.vector.rememberVectorPainter +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import com.twidere.services.mastodon.MastodonService +import com.twidere.twiderex.R import com.twidere.twiderex.component.TimelineComponent import com.twidere.twiderex.component.foundation.AppBar import com.twidere.twiderex.component.foundation.AppBarNavigationButton @@ -42,7 +42,7 @@ import com.twidere.twiderex.viewmodel.timeline.mastodon.FederatedTimelineViewMod class FederatedTimelineItem : HomeNavigationItem() { @Composable override fun name(): String { - return "Federated" + return stringResource(id = R.string.scene_federated_title) } override val route: String @@ -50,7 +50,7 @@ class FederatedTimelineItem : HomeNavigationItem() { @Composable override fun icon(): Painter { - return rememberVectorPainter(Icons.Default.List) + return painterResource(id = R.drawable.ic_globe) } @Composable diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/mastodon/LocalTimelineItem.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/mastodon/LocalTimelineItem.kt index 4fc61239b..119a72742 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/mastodon/LocalTimelineItem.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/mastodon/LocalTimelineItem.kt @@ -21,12 +21,12 @@ package com.twidere.twiderex.scenes.home.mastodon import androidx.compose.material.Text -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.List import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.graphics.vector.rememberVectorPainter +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import com.twidere.services.mastodon.MastodonService +import com.twidere.twiderex.R import com.twidere.twiderex.component.TimelineComponent import com.twidere.twiderex.component.foundation.AppBar import com.twidere.twiderex.component.foundation.AppBarNavigationButton @@ -42,7 +42,7 @@ import com.twidere.twiderex.viewmodel.timeline.mastodon.LocalTimelineViewModel class LocalTimelineItem : HomeNavigationItem() { @Composable override fun name(): String { - return "Local" + return stringResource(id = R.string.scene_local_title) } override val route: String @@ -50,7 +50,7 @@ class LocalTimelineItem : HomeNavigationItem() { @Composable override fun icon(): Painter { - return rememberVectorPainter(Icons.Default.List) + return painterResource(id = R.drawable.ic_users) } @Composable diff --git a/app/src/main/res/drawable/ic_users.xml b/app/src/main/res/drawable/ic_users.xml new file mode 100644 index 000000000..30967465c --- /dev/null +++ b/app/src/main/res/drawable/ic_users.xml @@ -0,0 +1,34 @@ + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6e1bdb77b..c3f2ed532 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -188,6 +188,7 @@ Name New List Description + Local Search people Add Member Add @@ -205,6 +206,7 @@ Search Show more Saved Search + Federated No Members Found. Subscribers List Members diff --git a/localization b/localization index 2d079390d..32a49017b 160000 --- a/localization +++ b/localization @@ -1 +1 @@ -Subproject commit 2d079390dc20bd1a87ad5df74704953fbf69e844 +Subproject commit 32a49017b010fe454b6829046c6394158750b84e From 7b560a00b1c6fa94bc917787c608f8f92ee72446 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Mon, 26 Jul 2021 14:57:18 +0800 Subject: [PATCH 068/137] clean up for deeplink route --- .../kotlin/com/twidere/twiderex/navigation/Root.kt | 10 ---------- .../com/twidere/twiderex/navigation/RootDeepLinks.kt | 7 ++++--- .../kotlin/com/twidere/twiderex/navigation/Route.kt | 10 ++++------ .../twiderex/scenes/twitter/user/TwitterUserScene.kt | 4 ++-- .../twidere/twiderex/worker/compose/ComposeWorker.kt | 6 +++--- .../twiderex/worker/dm/DirectMessageFetchWorker.kt | 4 ++-- .../twiderex/worker/dm/DirectMessageSendWorker.kt | 4 ++-- 7 files changed, 17 insertions(+), 28 deletions(-) diff --git a/app/src/main/kotlin/com/twidere/twiderex/navigation/Root.kt b/app/src/main/kotlin/com/twidere/twiderex/navigation/Root.kt index 9fd861e6f..8d8f2a7e0 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/navigation/Root.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/navigation/Root.kt @@ -82,16 +82,6 @@ interface Root { fun AccountNotification(accountKey: MicroBlogKey): String } - interface DeepLink { - interface Twitter { - val User: String - val Status: String - } - fun Draft(id: String): String - fun Compose(composeType: ComposeType, statusKey: MicroBlogKey?): String - fun Conversation(conversationKey: MicroBlogKey): String - } - fun Status(statusKey: MicroBlogKey): String interface Mastodon { diff --git a/app/src/main/kotlin/com/twidere/twiderex/navigation/RootDeepLinks.kt b/app/src/main/kotlin/com/twidere/twiderex/navigation/RootDeepLinks.kt index 75e44dfac..a6240d4b0 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/navigation/RootDeepLinks.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/navigation/RootDeepLinks.kt @@ -22,6 +22,7 @@ package com.twidere.twiderex.navigation import com.twidere.route.processor.AppRoute import com.twidere.twiderex.model.MicroBlogKey +import com.twidere.twiderex.viewmodel.compose.ComposeType @AppRoute( prefix = "$twidereXSchema://" @@ -41,9 +42,9 @@ interface RootDeepLinks { fun Search(keyword: String): String val SignIn: String - val Draft: String - val Compose: String - val Conversation: String + fun Draft(id: String): String + fun Compose(composeType: ComposeType, statusKey: MicroBlogKey?): String + fun Conversation(conversationKey: MicroBlogKey): String interface Callback { interface SignIn { diff --git a/app/src/main/kotlin/com/twidere/twiderex/navigation/Route.kt b/app/src/main/kotlin/com/twidere/twiderex/navigation/Route.kt index 1e2e1cfcd..1a47c55b2 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/navigation/Route.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/navigation/Route.kt @@ -277,10 +277,10 @@ fun RouteBuilder.route(constraints: Constraints) { // } authorizedScene( - RootRouteDefinition.DeepLink.Twitter.User, + RootDeepLinksRouteDefinition.Twitter.User, deepLinks = twitterHosts.map { "$it/{screenName}" - } + RootDeepLinksRouteDefinition.Twitter.User + } ) { backStackEntry -> backStackEntry.path("screenName")?.let { screenName -> val navigator = LocalNavigator.current @@ -342,12 +342,10 @@ fun RouteBuilder.route(constraints: Constraints) { } authorizedScene( - RootRouteDefinition.DeepLink.Twitter.Status, + RootDeepLinksRouteDefinition.Twitter.Status, deepLinks = twitterHosts.map { "$it/{screenName}/status/{statusId:[0-9]+}" - } + listOf( - RootDeepLinksRouteDefinition.Twitter.Status - ) + } ) { backStackEntry -> backStackEntry.path("statusId")?.let { statusId -> val navigator = LocalNavigator.current diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/twitter/user/TwitterUserScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/twitter/user/TwitterUserScene.kt index 7fb6e0fa6..9edf4e18a 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/twitter/user/TwitterUserScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/twitter/user/TwitterUserScene.kt @@ -37,7 +37,7 @@ import com.twidere.twiderex.component.foundation.InAppNotificationScaffold import com.twidere.twiderex.component.navigation.LocalNavigator import com.twidere.twiderex.di.assisted.assistedViewModel import com.twidere.twiderex.extensions.observeAsState -import com.twidere.twiderex.navigation.RootRoute +import com.twidere.twiderex.navigation.RootDeepLinksRouteDefinition import com.twidere.twiderex.ui.LocalActiveAccount import com.twidere.twiderex.ui.TwidereScene import com.twidere.twiderex.viewmodel.twitter.user.TwitterUserViewModel @@ -61,7 +61,7 @@ fun TwitterUserScene(screenName: String) { user?.let { navigator.user( user = it, - NavOptions(popUpTo = PopUpTo(RootRoute.DeepLink.Twitter.User, inclusive = true)) + NavOptions(popUpTo = PopUpTo(RootDeepLinksRouteDefinition.Twitter.User, inclusive = true)) ) } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/compose/ComposeWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/compose/ComposeWorker.kt index 8bda4b20c..805cd605b 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/compose/ComposeWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/compose/ComposeWorker.kt @@ -34,7 +34,7 @@ import com.twidere.twiderex.model.ComposeData import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.toComposeData import com.twidere.twiderex.model.ui.UiStatus -import com.twidere.twiderex.navigation.RootRoute +import com.twidere.twiderex.navigation.RootDeepLinksRoute import com.twidere.twiderex.notification.NotificationChannelSpec import com.twidere.twiderex.repository.AccountRepository import com.twidere.twiderex.utils.ExifScrambler @@ -103,7 +103,7 @@ abstract class ComposeWorker( applicationContext.startActivity( Intent( Intent.ACTION_VIEW, - Uri.parse(RootRoute.DeepLink.Compose(ComposeType.Thread, status.statusKey)) + Uri.parse(RootDeepLinksRoute.Compose(ComposeType.Thread, status.statusKey)) ).apply { addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) } @@ -113,7 +113,7 @@ abstract class ComposeWorker( } catch (e: Throwable) { e.printStackTrace() val intent = - Intent(Intent.ACTION_VIEW, Uri.parse(RootRoute.DeepLink.Draft(composeData.draftId))) + Intent(Intent.ACTION_VIEW, Uri.parse(RootDeepLinksRoute.Draft(composeData.draftId))) val pendingIntent = PendingIntent.getActivity( applicationContext, diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageFetchWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageFetchWorker.kt index a247ea430..171ca4c4f 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageFetchWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageFetchWorker.kt @@ -35,7 +35,7 @@ import com.twidere.services.microblog.LookupService import com.twidere.twiderex.R import com.twidere.twiderex.model.AccountDetails import com.twidere.twiderex.model.ui.UiDMConversationWithLatestMessage -import com.twidere.twiderex.navigation.RootRoute +import com.twidere.twiderex.navigation.RootDeepLinksRoute import com.twidere.twiderex.notification.NotificationChannelSpec import com.twidere.twiderex.notification.notificationChannelId import com.twidere.twiderex.repository.AccountRepository @@ -85,7 +85,7 @@ class DirectMessageFetchWorker @AssistedInject constructor( private fun notification(account: AccountDetails, message: UiDMConversationWithLatestMessage) { val intent = - Intent(Intent.ACTION_VIEW, Uri.parse(RootRoute.DeepLink.Conversation(message.conversation.conversationKey))) + Intent(Intent.ACTION_VIEW, Uri.parse(RootDeepLinksRoute.Conversation(message.conversation.conversationKey))) val pendingIntent = PendingIntent.getActivity( applicationContext, diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageSendWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageSendWorker.kt index d2c65ecab..2b6829cc9 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageSendWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageSendWorker.kt @@ -44,7 +44,7 @@ import com.twidere.twiderex.model.DirectMessageSendData import com.twidere.twiderex.model.MediaType import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.toDirectMessageSendData -import com.twidere.twiderex.navigation.RootRoute +import com.twidere.twiderex.navigation.RootDeepLinksRoute import com.twidere.twiderex.notification.NotificationChannelSpec import com.twidere.twiderex.notification.notificationChannelId import com.twidere.twiderex.repository.AccountRepository @@ -101,7 +101,7 @@ abstract class DirectMessageSendWorker( .insertAll(listOf(draftEvent.message.copy(sendStatus = DbDMEvent.SendStatus.FAILED))) } val intent = - Intent(Intent.ACTION_VIEW, Uri.parse(RootRoute.DeepLink.Conversation(sendData.conversationKey))) + Intent(Intent.ACTION_VIEW, Uri.parse(RootDeepLinksRoute.Conversation(sendData.conversationKey))) val pendingIntent = PendingIntent.getActivity( applicationContext, From 3430edd252f1c7426fe778307464c2399b5d5ff3 Mon Sep 17 00:00:00 2001 From: itsMimao Date: Mon, 26 Jul 2021 15:19:53 +0800 Subject: [PATCH 069/137] show AMOLED mode options when user choose auto theme --- .../com/twidere/twiderex/scenes/settings/AppearanceScene.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/AppearanceScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/AppearanceScene.kt index 6bd2845b6..701f2a570 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/AppearanceScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/settings/AppearanceScene.kt @@ -210,7 +210,7 @@ fun AppearanceScene() { ) } ) - val isLightTheme = MaterialTheme.colors.isLight + val isLightTheme = appearance.theme == AppearancePreferences.Theme.Light AnimatedVisibility(visible = !isLightTheme) { switchItem( value = appearance.isDarkModePureBlack, From 1c0055bd396ff0202a2ac85946b71ca8a2d460c1 Mon Sep 17 00:00:00 2001 From: itsMimao Date: Mon, 26 Jul 2021 15:37:06 +0800 Subject: [PATCH 070/137] replace NotificationManagerCompat to AppNotificationManager --- .../com/twidere/twiderex/di/TwidereModule.kt | 3 +- .../notification/AppNotificationManager.kt | 21 +++++++---- .../android/AndroidNotificationManager.kt | 3 +- .../twiderex/worker/NotificationWorker.kt | 36 +++--------------- .../twiderex/worker/compose/ComposeWorker.kt | 37 ++++++------------- .../worker/compose/MastodonComposeWorker.kt | 6 +-- .../worker/compose/TwitterComposeWorker.kt | 6 +-- .../worker/dm/DirectMessageFetchWorker.kt | 31 +++------------- .../worker/dm/DirectMessageSendWorker.kt | 32 +++------------- .../dm/TwitterDirectMessageSendWorker.kt | 6 +-- 10 files changed, 55 insertions(+), 126 deletions(-) diff --git a/app/src/main/kotlin/com/twidere/twiderex/di/TwidereModule.kt b/app/src/main/kotlin/com/twidere/twiderex/di/TwidereModule.kt index 3506ac772..250505a73 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/di/TwidereModule.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/di/TwidereModule.kt @@ -30,6 +30,7 @@ import com.twidere.twiderex.action.DirectMessageAction import com.twidere.twiderex.db.CacheDatabase import com.twidere.twiderex.http.TwidereServiceFactory import com.twidere.twiderex.model.AccountPreferences +import com.twidere.twiderex.notification.AppNotificationManager import com.twidere.twiderex.notification.InAppNotification import com.twidere.twiderex.notification.android.AndroidNotificationManager import com.twidere.twiderex.preferences.proto.MiscPreferences @@ -80,7 +81,7 @@ object TwidereModule { AccountPreferences.Factory(context) @Provides - fun provideAppNotificationManager(@ApplicationContext context: Context, notificationManagerCompat: NotificationManagerCompat) = AndroidNotificationManager( + fun provideAppNotificationManager(@ApplicationContext context: Context, notificationManagerCompat: NotificationManagerCompat): AppNotificationManager = AndroidNotificationManager( context = context, notificationManagerCompat = notificationManagerCompat ) diff --git a/app/src/main/kotlin/com/twidere/twiderex/notification/AppNotificationManager.kt b/app/src/main/kotlin/com/twidere/twiderex/notification/AppNotificationManager.kt index a9bdbd660..2894699df 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/notification/AppNotificationManager.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/notification/AppNotificationManager.kt @@ -31,27 +31,32 @@ interface AppNotificationManager { open class AppNotification( val channelId: String, - val title: String, + val title: CharSequence? = null, val content: CharSequence? = null, val largeIcon: Bitmap? = null, val deepLink: String? = null, - val onGoing: Boolean = false, + val ongoing: Boolean = false, val progress: Int = 0, val progressMax: Int = 0, val progressIndeterminate: Boolean = false, val silent: Boolean = false, ) { - class Builder(private var channelId: String, private var title: String) { + class Builder(private var channelId: String) { + private var title: CharSequence? = null private var content: CharSequence? = null private var largeIcon: Bitmap? = null private var deepLink: String? = null - private var onGoing: Boolean = false + private var ongoing: Boolean = false private var progress: Int = 0 private var progressMax: Int = 0 private var progressIndeterminate: Boolean = false private var silent: Boolean = false - fun setContent(content: CharSequence) = this.apply { + fun setContentTitle(title: CharSequence?) = this.apply { + this.title = title + } + + fun setContentText(content: CharSequence?) = this.apply { this.content = content } @@ -63,8 +68,8 @@ open class AppNotification( this.deepLink = deepLink } - fun setOnGoing(onGoing: Boolean) = this.apply { - this.onGoing = onGoing + fun setOngoing(ongoing: Boolean) = this.apply { + this.ongoing = ongoing } fun setSilent(silent: Boolean) = this.apply { @@ -83,7 +88,7 @@ open class AppNotification( content = content, largeIcon = largeIcon, deepLink = deepLink, - onGoing = onGoing, + ongoing = ongoing, progress = progress, progressMax = progressMax, progressIndeterminate = progressIndeterminate, diff --git a/app/src/main/kotlin/com/twidere/twiderex/notification/android/AndroidNotificationManager.kt b/app/src/main/kotlin/com/twidere/twiderex/notification/android/AndroidNotificationManager.kt index a0278bca8..546c32de6 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/notification/android/AndroidNotificationManager.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/notification/android/AndroidNotificationManager.kt @@ -50,7 +50,7 @@ class AndroidNotificationManager( .setPriority(NotificationCompat.PRIORITY_DEFAULT) .setAutoCancel(true) .setContentTitle(appNotification.title) - .setOngoing(appNotification.onGoing) + .setOngoing(appNotification.ongoing) .setSilent(appNotification.silent) .setProgress(appNotification.progressMax, appNotification.progress, appNotification.progressIndeterminate) appNotification.content?.let { @@ -84,6 +84,7 @@ class AndroidNotificationManager( duration: Long, durationTimeUnit: TimeUnit ) { + notify(notificationId, appNotification) scope.launch { delay(durationTimeUnit.toMillis(duration)) notificationManagerCompat.cancel(notificationId) diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/NotificationWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/NotificationWorker.kt index 83f192e37..6a80ef6da 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/NotificationWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/NotificationWorker.kt @@ -20,13 +20,7 @@ */ package com.twidere.twiderex.worker -import android.app.PendingIntent import android.content.Context -import android.content.Intent -import android.net.Uri -import android.os.Build -import androidx.core.app.NotificationCompat -import androidx.core.app.NotificationManagerCompat import androidx.core.graphics.drawable.toBitmap import androidx.core.text.HtmlCompat import androidx.datastore.core.DataStore @@ -43,6 +37,8 @@ import com.twidere.twiderex.model.MastodonStatusType import com.twidere.twiderex.model.PlatformType import com.twidere.twiderex.model.ui.UiStatus import com.twidere.twiderex.navigation.RootDeepLinksRoute +import com.twidere.twiderex.notification.AppNotification +import com.twidere.twiderex.notification.AppNotificationManager import com.twidere.twiderex.notification.NotificationChannelSpec import com.twidere.twiderex.notification.notificationChannelId import com.twidere.twiderex.preferences.proto.NotificationPreferences @@ -61,7 +57,7 @@ class NotificationWorker @AssistedInject constructor( @Assisted params: WorkerParameters, private val repository: NotificationRepository, private val accountRepository: AccountRepository, - private val notificationManagerCompat: NotificationManagerCompat, + private val notificationManager: AppNotificationManager, private val notificationPreferences: DataStore, ) : CoroutineWorker(appContext, params) { override suspend fun doWork(): Result = coroutineScope { @@ -91,17 +87,12 @@ class NotificationWorker @AssistedInject constructor( private suspend fun notify(account: AccountDetails, status: UiStatus) { val notificationId = "${account.accountKey}_${status.statusKey}" - val builder = NotificationCompat + val builder = AppNotification .Builder( - applicationContext, account.accountKey.notificationChannelId( NotificationChannelSpec.ContentInteractions.id ) ) - .setSmallIcon(R.drawable.ic_notification) - .setCategory(NotificationCompat.CATEGORY_SOCIAL) - .setPriority(NotificationCompat.PRIORITY_DEFAULT) - .setAutoCancel(true) val notificationData = when (status.platformType) { PlatformType.Twitter -> { @@ -132,24 +123,9 @@ class NotificationWorker @AssistedInject constructor( ) builder .setContentText(html) - .setStyle( - NotificationCompat.BigTextStyle() - .bigText(html) - ) } if (notificationData.deepLink != null) { - builder.setContentIntent( - PendingIntent.getActivity( - applicationContext, - 0, - Intent(Intent.ACTION_VIEW, Uri.parse(notificationData.deepLink)), - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - PendingIntent.FLAG_IMMUTABLE - } else { - PendingIntent.FLAG_UPDATE_CURRENT - }, - ) - ) + builder.setDeepLink(notificationData.deepLink) } if (notificationData.profileImage != null) { val result = Coil.execute( @@ -161,7 +137,7 @@ class NotificationWorker @AssistedInject constructor( builder.setLargeIcon(result.drawable.toBitmap()) } } - notificationManagerCompat.notify(notificationId.hashCode(), builder.build()) + notificationManager.notify(notificationId.hashCode(), builder.build()) } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/compose/ComposeWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/compose/ComposeWorker.kt index 8bda4b20c..553e92266 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/compose/ComposeWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/compose/ComposeWorker.kt @@ -20,12 +20,9 @@ */ package com.twidere.twiderex.worker.compose -import android.app.PendingIntent import android.content.Context import android.content.Intent import android.net.Uri -import androidx.core.app.NotificationCompat -import androidx.core.app.NotificationManagerCompat import androidx.work.CoroutineWorker import androidx.work.WorkerParameters import com.twidere.services.microblog.MicroBlogService @@ -35,6 +32,8 @@ import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.toComposeData import com.twidere.twiderex.model.ui.UiStatus import com.twidere.twiderex.navigation.RootRoute +import com.twidere.twiderex.notification.AppNotification +import com.twidere.twiderex.notification.AppNotificationManager import com.twidere.twiderex.notification.NotificationChannelSpec import com.twidere.twiderex.repository.AccountRepository import com.twidere.twiderex.utils.ExifScrambler @@ -45,18 +44,15 @@ abstract class ComposeWorker( protected val context: Context, workerParams: WorkerParameters, private val accountRepository: AccountRepository, - private val notificationManagerCompat: NotificationManagerCompat, + private val notificationManager: AppNotificationManager, ) : CoroutineWorker(context, workerParams) { override suspend fun doWork(): Result { - val builder = NotificationCompat - .Builder(applicationContext, NotificationChannelSpec.BackgroundProgresses.id) + val builder = AppNotification + .Builder(NotificationChannelSpec.BackgroundProgresses.id) .setContentTitle(applicationContext.getString(R.string.common_alerts_tweet_sending_title)) - .setSmallIcon(R.drawable.ic_notification) .setOngoing(true) .setSilent(true) - .setCategory(NotificationCompat.CATEGORY_SOCIAL) - .setPriority(NotificationCompat.PRIORITY_DEFAULT) .setProgress(100, 0, false) val composeData = inputData.toComposeData() val accountDetails = inputData.getString("accountKey")?.let { @@ -69,7 +65,7 @@ abstract class ComposeWorker( val notificationId = composeData.draftId.hashCode() @Suppress("UNCHECKED_CAST") val service = accountDetails.service as T - notificationManagerCompat.notify(notificationId, builder.build()) + notificationManager.notify(notificationId, builder.build()) return try { val exifScrambler = ExifScrambler(context) @@ -86,18 +82,17 @@ abstract class ComposeWorker( (99f * index.toFloat() / composeData.images.size.toFloat()).roundToInt(), false ) - notificationManagerCompat.notify(notificationId, builder.build()) + notificationManager.notify(notificationId, builder.build()) exifScrambler.deleteCacheFile(scramblerUri) } builder.setProgress(100, 99, false) - notificationManagerCompat.notify(notificationId, builder.build()) - // TODO insert status into database + notificationManager.notify(notificationId, builder.build()) val status = compose(service, composeData, mediaIds) builder.setOngoing(false) .setProgress(0, 0, false) .setSilent(false) .setContentTitle(applicationContext.getString(R.string.common_alerts_tweet_sent_title)) - notificationManagerCompat.notify(notificationId, builder.build()) + notificationManager.notifyTransient(notificationId, builder.build()) if (composeData.isThreadMode) { // open compose scene in thread mode applicationContext.startActivity( @@ -112,23 +107,13 @@ abstract class ComposeWorker( Result.success() } catch (e: Throwable) { e.printStackTrace() - val intent = - Intent(Intent.ACTION_VIEW, Uri.parse(RootRoute.DeepLink.Draft(composeData.draftId))) - val pendingIntent = - PendingIntent.getActivity( - applicationContext, - 0, - intent, - PendingIntent.FLAG_MUTABLE - ) builder.setOngoing(false) .setProgress(0, 0, false) .setSilent(false) - .setAutoCancel(true) .setContentTitle(applicationContext.getString(R.string.common_alerts_tweet_fail_title)) .setContentText(composeData.content) - .setContentIntent(pendingIntent) - notificationManagerCompat.notify(notificationId, builder.build()) + .setDeepLink(RootRoute.DeepLink.Draft(composeData.draftId)) + notificationManager.notify(notificationId, builder.build()) Result.failure() } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/compose/MastodonComposeWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/compose/MastodonComposeWorker.kt index 56b09d5e0..5f5c79174 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/compose/MastodonComposeWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/compose/MastodonComposeWorker.kt @@ -23,7 +23,6 @@ package com.twidere.twiderex.worker.compose import android.content.ContentResolver import android.content.Context import android.net.Uri -import androidx.core.app.NotificationManagerCompat import androidx.hilt.work.HiltWorker import androidx.work.Data import androidx.work.OneTimeWorkRequestBuilder @@ -39,6 +38,7 @@ import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.toWorkData import com.twidere.twiderex.model.ui.UiStatus import com.twidere.twiderex.model.ui.UiStatus.Companion.toUi +import com.twidere.twiderex.notification.AppNotificationManager import com.twidere.twiderex.repository.AccountRepository import com.twidere.twiderex.viewmodel.compose.ComposeType import dagger.assisted.Assisted @@ -50,10 +50,10 @@ class MastodonComposeWorker @AssistedInject constructor( @Assisted context: Context, @Assisted workerParams: WorkerParameters, accountRepository: AccountRepository, - notificationManagerCompat: NotificationManagerCompat, + notificationManager: AppNotificationManager, private val contentResolver: ContentResolver, private val cacheDatabase: CacheDatabase, -) : ComposeWorker(context, workerParams, accountRepository, notificationManagerCompat) { +) : ComposeWorker(context, workerParams, accountRepository, notificationManager) { companion object { fun create( diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/compose/TwitterComposeWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/compose/TwitterComposeWorker.kt index 956c3923d..617fe4289 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/compose/TwitterComposeWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/compose/TwitterComposeWorker.kt @@ -23,7 +23,6 @@ package com.twidere.twiderex.worker.compose import android.content.ContentResolver import android.content.Context import android.net.Uri -import androidx.core.app.NotificationManagerCompat import androidx.hilt.work.HiltWorker import androidx.work.Data import androidx.work.OneTimeWorkRequestBuilder @@ -38,6 +37,7 @@ import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.toWorkData import com.twidere.twiderex.model.ui.UiStatus import com.twidere.twiderex.model.ui.UiStatus.Companion.toUi +import com.twidere.twiderex.notification.AppNotificationManager import com.twidere.twiderex.repository.AccountRepository import com.twidere.twiderex.repository.StatusRepository import com.twidere.twiderex.viewmodel.compose.ComposeType @@ -49,7 +49,7 @@ class TwitterComposeWorker @AssistedInject constructor( @Assisted context: Context, @Assisted workerParams: WorkerParameters, accountRepository: AccountRepository, - notificationManagerCompat: NotificationManagerCompat, + notificationManager: AppNotificationManager, private val statusRepository: StatusRepository, private val contentResolver: ContentResolver, private val cacheDatabase: CacheDatabase, @@ -57,7 +57,7 @@ class TwitterComposeWorker @AssistedInject constructor( context, workerParams, accountRepository, - notificationManagerCompat + notificationManager ) { companion object { fun create( diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageFetchWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageFetchWorker.kt index a247ea430..85d5e4929 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageFetchWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageFetchWorker.kt @@ -20,12 +20,7 @@ */ package com.twidere.twiderex.worker.dm -import android.app.PendingIntent import android.content.Context -import android.content.Intent -import android.net.Uri -import androidx.core.app.NotificationCompat -import androidx.core.app.NotificationManagerCompat import androidx.hilt.work.HiltWorker import androidx.work.CoroutineWorker import androidx.work.PeriodicWorkRequestBuilder @@ -36,6 +31,8 @@ import com.twidere.twiderex.R import com.twidere.twiderex.model.AccountDetails import com.twidere.twiderex.model.ui.UiDMConversationWithLatestMessage import com.twidere.twiderex.navigation.RootRoute +import com.twidere.twiderex.notification.AppNotification +import com.twidere.twiderex.notification.AppNotificationManager import com.twidere.twiderex.notification.NotificationChannelSpec import com.twidere.twiderex.notification.notificationChannelId import com.twidere.twiderex.repository.AccountRepository @@ -52,7 +49,7 @@ class DirectMessageFetchWorker @AssistedInject constructor( @Assisted workerParams: WorkerParameters, private val repository: DirectMessageRepository, private val accountRepository: AccountRepository, - private val notificationManagerCompat: NotificationManagerCompat, + private val notificationManager: AppNotificationManager, ) : CoroutineWorker( context, workerParams @@ -84,31 +81,15 @@ class DirectMessageFetchWorker @AssistedInject constructor( } private fun notification(account: AccountDetails, message: UiDMConversationWithLatestMessage) { - val intent = - Intent(Intent.ACTION_VIEW, Uri.parse(RootRoute.DeepLink.Conversation(message.conversation.conversationKey))) - val pendingIntent = - PendingIntent.getActivity( - applicationContext, - 0, - intent, - PendingIntent.FLAG_MUTABLE - ) - val builder = NotificationCompat + val builder = AppNotification .Builder( - applicationContext, account.accountKey.notificationChannelId( NotificationChannelSpec.ContentMessages.id ) ) .setContentTitle(applicationContext.getString(R.string.common_notification_messages_title)) - .setSmallIcon(R.drawable.ic_notification) - .setCategory(NotificationCompat.CATEGORY_SOCIAL) - .setPriority(NotificationCompat.PRIORITY_DEFAULT) - .setOngoing(false) - .setSilent(false) - .setAutoCancel(true) .setContentText(applicationContext.getString(R.string.common_notification_messages_content, message.latestMessage.sender.displayName)) - .setContentIntent(pendingIntent) - notificationManagerCompat.notify(message.latestMessage.messageKey.hashCode(), builder.build()) + .setDeepLink(RootRoute.DeepLink.Conversation(message.conversation.conversationKey)) + notificationManager.notify(message.latestMessage.messageKey.hashCode(), builder.build()) } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageSendWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageSendWorker.kt index d2c65ecab..fb5744345 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageSendWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageSendWorker.kt @@ -20,14 +20,10 @@ */ package com.twidere.twiderex.worker.dm -import android.app.PendingIntent import android.content.ContentResolver import android.content.Context -import android.content.Intent import android.graphics.BitmapFactory import android.net.Uri -import androidx.core.app.NotificationCompat -import androidx.core.app.NotificationManagerCompat import androidx.room.withTransaction import androidx.work.CoroutineWorker import androidx.work.WorkerParameters @@ -45,6 +41,8 @@ import com.twidere.twiderex.model.MediaType import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.toDirectMessageSendData import com.twidere.twiderex.navigation.RootRoute +import com.twidere.twiderex.notification.AppNotification +import com.twidere.twiderex.notification.AppNotificationManager import com.twidere.twiderex.notification.NotificationChannelSpec import com.twidere.twiderex.notification.notificationChannelId import com.twidere.twiderex.repository.AccountRepository @@ -56,7 +54,7 @@ abstract class DirectMessageSendWorker( protected val cacheDatabase: CacheDatabase, protected val contentResolver: ContentResolver, private val accountRepository: AccountRepository, - private val notificationManagerCompat: NotificationManagerCompat, + private val notificationManager: AppNotificationManager, ) : CoroutineWorker( context, workerParams @@ -100,34 +98,16 @@ abstract class DirectMessageSendWorker( cacheDatabase.directMessageDao() .insertAll(listOf(draftEvent.message.copy(sendStatus = DbDMEvent.SendStatus.FAILED))) } - val intent = - Intent(Intent.ACTION_VIEW, Uri.parse(RootRoute.DeepLink.Conversation(sendData.conversationKey))) - val pendingIntent = - PendingIntent.getActivity( - applicationContext, - 0, - intent, - PendingIntent.FLAG_MUTABLE - ) - val builder = NotificationCompat + val builder = AppNotification .Builder( - applicationContext, accountDetails.accountKey.notificationChannelId( NotificationChannelSpec.ContentMessages.id ) ) - .setContentTitle(applicationContext.getString(R.string.common_alerts_failed_to_send_message_title)) - .setSmallIcon(R.drawable.ic_notification) - .setCategory(NotificationCompat.CATEGORY_SOCIAL) - .setPriority(NotificationCompat.PRIORITY_DEFAULT) - .setOngoing(false) - .setProgress(0, 0, false) - .setSilent(false) - .setAutoCancel(true) .setContentTitle(applicationContext.getString(R.string.common_alerts_failed_to_send_message_message)) .setContentText(sendData.text) - .setContentIntent(pendingIntent) - notificationManagerCompat.notify(notificationId, builder.build()) + .setDeepLink(RootRoute.DeepLink.Conversation(sendData.conversationKey)) + notificationManager.notify(notificationId, builder.build()) Result.failure() } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/dm/TwitterDirectMessageSendWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/dm/TwitterDirectMessageSendWorker.kt index 5260be733..8c1f4b396 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/dm/TwitterDirectMessageSendWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/dm/TwitterDirectMessageSendWorker.kt @@ -23,7 +23,6 @@ package com.twidere.twiderex.worker.dm import android.content.ContentResolver import android.content.Context import android.net.Uri -import androidx.core.app.NotificationManagerCompat import androidx.hilt.work.HiltWorker import androidx.work.Data import androidx.work.OneTimeWorkRequestBuilder @@ -39,6 +38,7 @@ import com.twidere.twiderex.db.model.DbUser import com.twidere.twiderex.model.DirectMessageSendData import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.toWorkData +import com.twidere.twiderex.notification.AppNotificationManager import com.twidere.twiderex.repository.AccountRepository import dagger.assisted.Assisted import dagger.assisted.AssistedInject @@ -48,7 +48,7 @@ class TwitterDirectMessageSendWorker @AssistedInject constructor( @Assisted context: Context, @Assisted workerParams: WorkerParameters, accountRepository: AccountRepository, - notificationManagerCompat: NotificationManagerCompat, + notificationManager: AppNotificationManager, contentResolver: ContentResolver, cacheDatabase: CacheDatabase, ) : DirectMessageSendWorker( @@ -57,7 +57,7 @@ class TwitterDirectMessageSendWorker @AssistedInject constructor( cacheDatabase, contentResolver, accountRepository, - notificationManagerCompat + notificationManager ) { companion object { fun create( From 39b6d3506db8f4c178d21d37000e77757aaa72e2 Mon Sep 17 00:00:00 2001 From: itsMimao Date: Mon, 26 Jul 2021 15:50:42 +0800 Subject: [PATCH 071/137] make status detail statistics to humanized count --- .../twiderex/component/status/DetailedStatusComponent.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/status/DetailedStatusComponent.kt b/app/src/main/kotlin/com/twidere/twiderex/component/status/DetailedStatusComponent.kt index 8a00dcc0a..8280206fb 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/status/DetailedStatusComponent.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/status/DetailedStatusComponent.kt @@ -48,6 +48,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.twidere.twiderex.R import com.twidere.twiderex.component.FormattedTime +import com.twidere.twiderex.extensions.humanizedCount import com.twidere.twiderex.model.PlatformType import com.twidere.twiderex.model.ui.UiStatus @@ -205,7 +206,7 @@ private fun StatusStatistics( contentDescription = contentDescription, ) Spacer(modifier = Modifier.width(StatusStatisticsDefaults.IconSpacing)) - Text(text = count.toString()) + Text(text = count.humanizedCount()) } } From f33428f09ba26dc7f5f3d3f0f9e9347fc8383bfe Mon Sep 17 00:00:00 2001 From: itsMimao Date: Mon, 26 Jul 2021 16:22:08 +0800 Subject: [PATCH 072/137] fixed deeplinks parameter missing problem --- .../main/kotlin/com/twidere/twiderex/navigation/Root.kt | 2 +- .../com/twidere/twiderex/navigation/RootDeepLinks.kt | 8 ++++++-- .../main/kotlin/com/twidere/twiderex/navigation/Route.kt | 3 +++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/app/src/main/kotlin/com/twidere/twiderex/navigation/Root.kt b/app/src/main/kotlin/com/twidere/twiderex/navigation/Root.kt index 8d8f2a7e0..5a7161fd8 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/navigation/Root.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/navigation/Root.kt @@ -42,7 +42,7 @@ interface Root { fun Twitter(consumerKey: String, consumerSecret: String): String val Mastodon: String interface Web { - fun Twitter(url: String): String + fun Twitter(target: String): String } } fun User(userKey: MicroBlogKey): String diff --git a/app/src/main/kotlin/com/twidere/twiderex/navigation/RootDeepLinks.kt b/app/src/main/kotlin/com/twidere/twiderex/navigation/RootDeepLinks.kt index a6240d4b0..878cc7e51 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/navigation/RootDeepLinks.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/navigation/RootDeepLinks.kt @@ -24,13 +24,17 @@ import com.twidere.route.processor.AppRoute import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.viewmodel.compose.ComposeType +/** + * if deeplink has the same parameters with route in Root.kt, + * make it's name the same to route parameters in Root.kt too + */ @AppRoute( prefix = "$twidereXSchema://" ) interface RootDeepLinks { interface Twitter { fun User(screenName: String): String - fun Status(id: String): String + fun Status(statusId: String): String } interface Mastodon { @@ -42,7 +46,7 @@ interface RootDeepLinks { fun Search(keyword: String): String val SignIn: String - fun Draft(id: String): String + fun Draft(draftId: String): String fun Compose(composeType: ComposeType, statusKey: MicroBlogKey?): String fun Conversation(conversationKey: MicroBlogKey): String diff --git a/app/src/main/kotlin/com/twidere/twiderex/navigation/Route.kt b/app/src/main/kotlin/com/twidere/twiderex/navigation/Route.kt index 1a47c55b2..60d59e842 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/navigation/Route.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/navigation/Route.kt @@ -205,6 +205,9 @@ fun RequirePlatformAccount( } } +/** + * Note: Path/Query key must be exactly the same as function's parameter's name in Root/RootDeepLinks + */ fun RouteBuilder.route(constraints: Constraints) { authorizedScene( RootRouteDefinition.Home, From 5e745d87029f290a82134b14badb9d98df5a2b56 Mon Sep 17 00:00:00 2001 From: huixing Date: Tue, 27 Jul 2021 07:26:20 +0800 Subject: [PATCH 073/137] Currently click search crashes because the URLEncoder.encode parameter cannot be empty --- routeProcessor/src/main/kotlin/RouteDefinition.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/routeProcessor/src/main/kotlin/RouteDefinition.kt b/routeProcessor/src/main/kotlin/RouteDefinition.kt index 82b8fc09f..d0b4d9766 100644 --- a/routeProcessor/src/main/kotlin/RouteDefinition.kt +++ b/routeProcessor/src/main/kotlin/RouteDefinition.kt @@ -133,7 +133,7 @@ internal data class FunctionRouteDefinition( .joinToString("&") { parameter -> val name = parameter.name if (parameter.type == "kotlin.String") { - "$name=${encode(name)}" + "$name=${encodeNullable(name)}" } else { "$name=\$$name" } @@ -180,6 +180,7 @@ internal data class FunctionRouteDefinition( } private fun encode(value: String) = "\${java.net.URLEncoder.encode($value, \"UTF-8\")}" + private fun encodeNullable(value: String) = "\${java.net.URLEncoder.encode(if($value == null) \"\" else $value, \"UTF-8\")}" } internal data class RouteParameter( From a47d57bf9ed61370e52cdf9a7ec7079ddc8fb01e Mon Sep 17 00:00:00 2001 From: itsMimao Date: Tue, 27 Jul 2021 10:28:23 +0800 Subject: [PATCH 074/137] remove unused code --- .../com/twidere/twiderex/notification/AppNotificationManager.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/twidere/twiderex/notification/AppNotificationManager.kt b/app/src/main/kotlin/com/twidere/twiderex/notification/AppNotificationManager.kt index 2894699df..42cb89d63 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/notification/AppNotificationManager.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/notification/AppNotificationManager.kt @@ -29,7 +29,7 @@ interface AppNotificationManager { fun notifyTransient(notificationId: Int, appNotification: AppNotification, duration: Long = 5, durationTimeUnit: TimeUnit = TimeUnit.SECONDS) } -open class AppNotification( +class AppNotification( val channelId: String, val title: CharSequence? = null, val content: CharSequence? = null, From dc973d27e0656122c8dbc879585f5ca7b7de5541 Mon Sep 17 00:00:00 2001 From: itsMimao Date: Tue, 27 Jul 2021 11:30:35 +0800 Subject: [PATCH 075/137] extract logic code out of ShareMediaWork/DownloadMediaWorker/DeleteDbStatusWorker --- .../com/twidere/twiderex/di/JobModule.kt | 56 +++++++++++++++++++ .../twiderex/jobs/common/DownloadMediaJob.kt | 56 +++++++++++++++++++ .../twiderex/jobs/common/ShareMediaJob.kt | 27 +++++++++ .../jobs/database/DeleteDbStatusJob.kt | 37 ++++++++++++ .../twiderex/worker/DownloadMediaWorker.kt | 33 ++++------- .../twiderex/worker/NotificationWorker.kt | 2 +- .../twiderex/worker/ShareMediaWorker.kt | 26 ++++++--- .../worker/database/DeleteDbStatusWorker.kt | 10 ++-- 8 files changed, 213 insertions(+), 34 deletions(-) create mode 100644 app/src/main/kotlin/com/twidere/twiderex/di/JobModule.kt create mode 100644 app/src/main/kotlin/com/twidere/twiderex/jobs/common/DownloadMediaJob.kt create mode 100644 app/src/main/kotlin/com/twidere/twiderex/jobs/common/ShareMediaJob.kt create mode 100644 app/src/main/kotlin/com/twidere/twiderex/jobs/database/DeleteDbStatusJob.kt diff --git a/app/src/main/kotlin/com/twidere/twiderex/di/JobModule.kt b/app/src/main/kotlin/com/twidere/twiderex/di/JobModule.kt new file mode 100644 index 000000000..c314b66eb --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/di/JobModule.kt @@ -0,0 +1,56 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.di + +import com.twidere.twiderex.jobs.common.DownloadMediaJob +import com.twidere.twiderex.jobs.common.ShareMediaJob +import com.twidere.twiderex.jobs.database.DeleteDbStatusJob +import com.twidere.twiderex.notification.InAppNotification +import com.twidere.twiderex.repository.AccountRepository +import com.twidere.twiderex.repository.StatusRepository +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent + +@Module +@InstallIn(SingletonComponent::class) +object JobModule { + + @Provides + fun provideShareMediaJob(): ShareMediaJob = ShareMediaJob() + + @Provides + fun provideDownloadMediaJob( + accountRepository: AccountRepository, + inAppNotification: InAppNotification + ): DownloadMediaJob = DownloadMediaJob( + accountRepository = accountRepository, + inAppNotification = inAppNotification + ) + + @Provides + fun provideDeleteDbStatusJob( + statusRepository: StatusRepository + ): DeleteDbStatusJob = DeleteDbStatusJob( + statusRepository = statusRepository + ) +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/common/DownloadMediaJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/common/DownloadMediaJob.kt new file mode 100644 index 000000000..600ead879 --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/common/DownloadMediaJob.kt @@ -0,0 +1,56 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.jobs.common + +import com.twidere.services.microblog.DownloadMediaService +import com.twidere.twiderex.R +import com.twidere.twiderex.model.MicroBlogKey +import com.twidere.twiderex.notification.InAppNotification +import com.twidere.twiderex.repository.AccountRepository +import java.io.OutputStream + +class DownloadMediaJob( + private val accountRepository: AccountRepository, + private val inAppNotification: InAppNotification, +) { + suspend fun execute( + target: String, + source: String, + accountKey: MicroBlogKey, + openOutputStream: (target: String) -> OutputStream? + ): Boolean { + val accountDetails = accountKey.let { + accountRepository.findByAccountKey(accountKey = it) + }?.let { + accountRepository.getAccountDetails(it) + } ?: return false + val service = accountDetails.service + if (service !is DownloadMediaService) { + return false + } + openOutputStream(target)?.use { + service.download(target = source).copyTo(it) + } ?: return false + + inAppNotification.show(R.string.common_controls_actions_save) + return true + } +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/common/ShareMediaJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/common/ShareMediaJob.kt new file mode 100644 index 000000000..88fc83ffe --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/common/ShareMediaJob.kt @@ -0,0 +1,27 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.jobs.common + +class ShareMediaJob { + fun execute(target: String, shareMedia: (target: String) -> Boolean): Boolean { + return shareMedia(target) + } +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/database/DeleteDbStatusJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/database/DeleteDbStatusJob.kt new file mode 100644 index 000000000..7468dd8c7 --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/database/DeleteDbStatusJob.kt @@ -0,0 +1,37 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.jobs.database + +import com.twidere.twiderex.model.MicroBlogKey +import com.twidere.twiderex.repository.StatusRepository + +class DeleteDbStatusJob( + private val statusRepository: StatusRepository +) { + suspend fun execute(statusKey: MicroBlogKey): Boolean { + try { + statusRepository.removeStatus(statusKey) + } catch (e: Exception) { + return false + } + return true + } +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/DownloadMediaWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/DownloadMediaWorker.kt index ef3af7a49..e16e70f14 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/DownloadMediaWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/DownloadMediaWorker.kt @@ -28,11 +28,8 @@ import androidx.work.CoroutineWorker import androidx.work.Data import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkerParameters -import com.twidere.services.microblog.DownloadMediaService -import com.twidere.twiderex.R +import com.twidere.twiderex.jobs.common.DownloadMediaJob import com.twidere.twiderex.model.MicroBlogKey -import com.twidere.twiderex.notification.InAppNotification -import com.twidere.twiderex.repository.AccountRepository import dagger.assisted.Assisted import dagger.assisted.AssistedInject @@ -41,8 +38,7 @@ class DownloadMediaWorker @AssistedInject constructor( @Assisted context: Context, @Assisted workerParams: WorkerParameters, private val contentResolver: ContentResolver, - private val accountRepository: AccountRepository, - private val inAppNotification: InAppNotification, + private val downloadMediaJob: DownloadMediaJob, ) : CoroutineWorker(context, workerParams) { companion object { @@ -62,24 +58,19 @@ class DownloadMediaWorker @AssistedInject constructor( } override suspend fun doWork(): Result { - val target = inputData.getString("target")?.let { Uri.parse(it) } ?: return Result.failure() + val target = inputData.getString("target") ?: return Result.failure() val source = inputData.getString("source") ?: return Result.failure() - val accountDetails = inputData.getString("accountKey")?.let { + val accountKey = inputData.getString("accountKey")?.let { MicroBlogKey.valueOf(it) - }?.let { - accountRepository.findByAccountKey(accountKey = it) - }?.let { - accountRepository.getAccountDetails(it) } ?: return Result.failure() - val service = accountDetails.service - if (service !is DownloadMediaService) { - return Result.failure() + return downloadMediaJob.execute( + target = target, + source = source, + accountKey = accountKey + ) { + contentResolver.openOutputStream(Uri.parse(it)) + }.let { + if (it) Result.success() else Result.failure() } - contentResolver.openOutputStream(target)?.use { - service.download(target = source).copyTo(it) - } ?: return Result.failure() - - inAppNotification.show(R.string.common_controls_actions_save) - return Result.success() } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/NotificationWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/NotificationWorker.kt index 83f192e37..463d1a108 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/NotificationWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/NotificationWorker.kt @@ -54,7 +54,7 @@ import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.first import kotlinx.coroutines.joinAll import kotlinx.coroutines.launch - +// TODO WORKER refactor after merge app-notification @HiltWorker class NotificationWorker @AssistedInject constructor( @Assisted appContext: Context, diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/ShareMediaWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/ShareMediaWorker.kt index 242fda25b..4589a8005 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/ShareMediaWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/ShareMediaWorker.kt @@ -29,6 +29,7 @@ import androidx.work.Data import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkerParameters import com.twidere.twiderex.extensions.shareMedia +import com.twidere.twiderex.jobs.common.ShareMediaJob import dagger.assisted.Assisted import dagger.assisted.AssistedInject @@ -36,6 +37,7 @@ import dagger.assisted.AssistedInject class ShareMediaWorker @AssistedInject constructor( @Assisted private val context: Context, @Assisted workerParams: WorkerParameters, + private val shareMediaJob: ShareMediaJob, private val contentResolver: ContentResolver, ) : CoroutineWorker(context, workerParams) { @@ -52,14 +54,22 @@ class ShareMediaWorker @AssistedInject constructor( } override suspend fun doWork(): Result { - val target = inputData.getString("target")?.let { Uri.parse(it) } ?: return Result.failure() - contentResolver.getType(target)?.let { - context.shareMedia( - uri = target, - mimeType = it, - fromOutsideOfActivity = true - ) + val target = inputData.getString("target") ?: return Result.failure() + return shareMediaJob.execute( + target + ) { + Uri.parse(it).let { uri -> + contentResolver.getType(uri)?.let { type -> + context.shareMedia( + uri = uri, + mimeType = type, + fromOutsideOfActivity = true + ) + true + } ?: false + } + }.let { + if (it) Result.success() else Result.failure() } - return Result.success() } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/database/DeleteDbStatusWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/database/DeleteDbStatusWorker.kt index a7a7541a6..54ca9f3cb 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/database/DeleteDbStatusWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/database/DeleteDbStatusWorker.kt @@ -26,8 +26,8 @@ import androidx.work.CoroutineWorker import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkerParameters import androidx.work.workDataOf +import com.twidere.twiderex.jobs.database.DeleteDbStatusJob import com.twidere.twiderex.model.MicroBlogKey -import com.twidere.twiderex.repository.StatusRepository import dagger.assisted.Assisted import dagger.assisted.AssistedInject @@ -35,7 +35,7 @@ import dagger.assisted.AssistedInject class DeleteDbStatusWorker @AssistedInject constructor( @Assisted appContext: Context, @Assisted params: WorkerParameters, - private val statusRepository: StatusRepository, + private val deleteDbStatusJob: DeleteDbStatusJob, ) : CoroutineWorker(appContext, params) { companion object { @@ -53,7 +53,9 @@ class DeleteDbStatusWorker @AssistedInject constructor( val statusKey = inputData.getString("statusKey")?.let { MicroBlogKey.valueOf(it) } ?: return Result.failure() - statusRepository.removeStatus(statusKey) - return Result.success() + return deleteDbStatusJob.execute(statusKey) + .let { + if (it) Result.success() else Result.failure() + } } } From fa7725864c081dd390226a32c03a1f426ac94d04 Mon Sep 17 00:00:00 2001 From: itsMimao Date: Tue, 27 Jul 2021 11:57:08 +0800 Subject: [PATCH 076/137] extract logic code from DeleteStatusWorker --- .../com/twidere/twiderex/di/JobModule.kt | 12 ++++ .../twiderex/extensions/WorkExtension.kt | 25 ++++++++ .../twiderex/jobs/status/DeleteStatusJob.kt | 59 +++++++++++++++++++ .../twiderex/worker/DownloadMediaWorker.kt | 5 +- .../twiderex/worker/ShareMediaWorker.kt | 5 +- .../worker/database/DeleteDbStatusWorker.kt | 5 +- .../worker/status/DeleteStatusWorker.kt | 35 +++-------- 7 files changed, 110 insertions(+), 36 deletions(-) create mode 100644 app/src/main/kotlin/com/twidere/twiderex/extensions/WorkExtension.kt create mode 100644 app/src/main/kotlin/com/twidere/twiderex/jobs/status/DeleteStatusJob.kt diff --git a/app/src/main/kotlin/com/twidere/twiderex/di/JobModule.kt b/app/src/main/kotlin/com/twidere/twiderex/di/JobModule.kt index c314b66eb..0a396452f 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/di/JobModule.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/di/JobModule.kt @@ -23,6 +23,7 @@ package com.twidere.twiderex.di import com.twidere.twiderex.jobs.common.DownloadMediaJob import com.twidere.twiderex.jobs.common.ShareMediaJob import com.twidere.twiderex.jobs.database.DeleteDbStatusJob +import com.twidere.twiderex.jobs.status.DeleteStatusJob import com.twidere.twiderex.notification.InAppNotification import com.twidere.twiderex.repository.AccountRepository import com.twidere.twiderex.repository.StatusRepository @@ -53,4 +54,15 @@ object JobModule { ): DeleteDbStatusJob = DeleteDbStatusJob( statusRepository = statusRepository ) + + @Provides + fun provideDeleteStatusJob( + accountRepository: AccountRepository, + inAppNotification: InAppNotification, + statusRepository: StatusRepository + ): DeleteStatusJob = DeleteStatusJob( + accountRepository = accountRepository, + statusRepository = statusRepository, + inAppNotification = inAppNotification + ) } diff --git a/app/src/main/kotlin/com/twidere/twiderex/extensions/WorkExtension.kt b/app/src/main/kotlin/com/twidere/twiderex/extensions/WorkExtension.kt new file mode 100644 index 000000000..8bbefe088 --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/extensions/WorkExtension.kt @@ -0,0 +1,25 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.extensions + +import androidx.work.ListenableWorker + +internal fun Boolean.toWorkResult() = if (this) ListenableWorker.Result.success() else ListenableWorker.Result.success() diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/status/DeleteStatusJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/status/DeleteStatusJob.kt new file mode 100644 index 000000000..d129913ad --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/status/DeleteStatusJob.kt @@ -0,0 +1,59 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.jobs.status + +import com.twidere.services.http.MicroBlogException +import com.twidere.services.microblog.StatusService +import com.twidere.twiderex.model.MicroBlogKey +import com.twidere.twiderex.notification.InAppNotification +import com.twidere.twiderex.repository.AccountRepository +import com.twidere.twiderex.repository.StatusRepository +import com.twidere.twiderex.utils.notify + +class DeleteStatusJob( + private val accountRepository: AccountRepository, + private val statusRepository: StatusRepository, + private val inAppNotification: InAppNotification +) { + suspend fun execute( + accountKey: MicroBlogKey, + statusKey: MicroBlogKey + ): Boolean { + val status = statusKey.let { + statusRepository.loadFromCache(it, accountKey = accountKey) + } ?: return false + val service = accountRepository.findByAccountKey(accountKey)?.let { + accountRepository.getAccountDetails(it) + }?.let { + it.service as? StatusService + } ?: return false + return try { + service.delete(status.statusId) + true + } catch (e: MicroBlogException) { + e.notify(inAppNotification) + false + } catch (e: Throwable) { + e.notify(inAppNotification) + false + } + } +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/DownloadMediaWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/DownloadMediaWorker.kt index e16e70f14..2932f174e 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/DownloadMediaWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/DownloadMediaWorker.kt @@ -28,6 +28,7 @@ import androidx.work.CoroutineWorker import androidx.work.Data import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkerParameters +import com.twidere.twiderex.extensions.toWorkResult import com.twidere.twiderex.jobs.common.DownloadMediaJob import com.twidere.twiderex.model.MicroBlogKey import dagger.assisted.Assisted @@ -69,8 +70,6 @@ class DownloadMediaWorker @AssistedInject constructor( accountKey = accountKey ) { contentResolver.openOutputStream(Uri.parse(it)) - }.let { - if (it) Result.success() else Result.failure() - } + }.toWorkResult() } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/ShareMediaWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/ShareMediaWorker.kt index 4589a8005..0c13a1c89 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/ShareMediaWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/ShareMediaWorker.kt @@ -29,6 +29,7 @@ import androidx.work.Data import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkerParameters import com.twidere.twiderex.extensions.shareMedia +import com.twidere.twiderex.extensions.toWorkResult import com.twidere.twiderex.jobs.common.ShareMediaJob import dagger.assisted.Assisted import dagger.assisted.AssistedInject @@ -68,8 +69,6 @@ class ShareMediaWorker @AssistedInject constructor( true } ?: false } - }.let { - if (it) Result.success() else Result.failure() - } + }.toWorkResult() } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/database/DeleteDbStatusWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/database/DeleteDbStatusWorker.kt index 54ca9f3cb..f3549bb90 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/database/DeleteDbStatusWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/database/DeleteDbStatusWorker.kt @@ -26,6 +26,7 @@ import androidx.work.CoroutineWorker import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkerParameters import androidx.work.workDataOf +import com.twidere.twiderex.extensions.toWorkResult import com.twidere.twiderex.jobs.database.DeleteDbStatusJob import com.twidere.twiderex.model.MicroBlogKey import dagger.assisted.Assisted @@ -54,8 +55,6 @@ class DeleteDbStatusWorker @AssistedInject constructor( MicroBlogKey.valueOf(it) } ?: return Result.failure() return deleteDbStatusJob.execute(statusKey) - .let { - if (it) Result.success() else Result.failure() - } + .toWorkResult() } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/status/DeleteStatusWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/status/DeleteStatusWorker.kt index 0201dbf2b..e0ea798bc 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/status/DeleteStatusWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/status/DeleteStatusWorker.kt @@ -26,14 +26,10 @@ import androidx.work.CoroutineWorker import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkerParameters import androidx.work.workDataOf -import com.twidere.services.http.MicroBlogException -import com.twidere.services.microblog.StatusService +import com.twidere.twiderex.extensions.toWorkResult +import com.twidere.twiderex.jobs.status.DeleteStatusJob import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.ui.UiStatus -import com.twidere.twiderex.notification.InAppNotification -import com.twidere.twiderex.repository.AccountRepository -import com.twidere.twiderex.repository.StatusRepository -import com.twidere.twiderex.utils.notify import dagger.assisted.Assisted import dagger.assisted.AssistedInject @@ -41,9 +37,7 @@ import dagger.assisted.AssistedInject class DeleteStatusWorker @AssistedInject constructor( @Assisted appContext: Context, @Assisted params: WorkerParameters, - private val accountRepository: AccountRepository, - private val statusRepository: StatusRepository, - private val inAppNotification: InAppNotification, + private val deleteStatusJob: DeleteStatusJob ) : CoroutineWorker(appContext, params) { companion object { fun create( @@ -63,25 +57,12 @@ class DeleteStatusWorker @AssistedInject constructor( val accountKey = inputData.getString("accountKey")?.let { MicroBlogKey.valueOf(it) } ?: return Result.failure() - val status = inputData.getString("statusKey")?.let { + val statusKey = inputData.getString("statusKey")?.let { MicroBlogKey.valueOf(it) - }?.let { - statusRepository.loadFromCache(it, accountKey = accountKey) } ?: return Result.failure() - val service = accountRepository.findByAccountKey(accountKey)?.let { - accountRepository.getAccountDetails(it) - }?.let { - it.service as? StatusService - } ?: return Result.failure() - return try { - service.delete(status.statusId) - Result.success() - } catch (e: MicroBlogException) { - e.notify(inAppNotification) - Result.failure() - } catch (e: Throwable) { - e.notify(inAppNotification) - Result.failure() - } + return deleteStatusJob.execute( + accountKey = accountKey, + statusKey = statusKey + ).toWorkResult() } } From b43afa585717ad5261a7018c48a6aed69fbbcb06 Mon Sep 17 00:00:00 2001 From: itsMimao Date: Tue, 27 Jul 2021 12:14:40 +0800 Subject: [PATCH 077/137] extract logic code from NotificationWorker --- .../com/twidere/twiderex/di/JobModule.kt | 17 ++ .../twiderex/jobs/common/NotificationJob.kt | 213 ++++++++++++++++++ .../twiderex/worker/NotificationWorker.kt | 188 +--------------- 3 files changed, 237 insertions(+), 181 deletions(-) create mode 100644 app/src/main/kotlin/com/twidere/twiderex/jobs/common/NotificationJob.kt diff --git a/app/src/main/kotlin/com/twidere/twiderex/di/JobModule.kt b/app/src/main/kotlin/com/twidere/twiderex/di/JobModule.kt index 0a396452f..ac35463b6 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/di/JobModule.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/di/JobModule.kt @@ -20,16 +20,21 @@ */ package com.twidere.twiderex.di +import android.content.Context import com.twidere.twiderex.jobs.common.DownloadMediaJob +import com.twidere.twiderex.jobs.common.NotificationJob import com.twidere.twiderex.jobs.common.ShareMediaJob import com.twidere.twiderex.jobs.database.DeleteDbStatusJob import com.twidere.twiderex.jobs.status.DeleteStatusJob +import com.twidere.twiderex.notification.AppNotificationManager import com.twidere.twiderex.notification.InAppNotification import com.twidere.twiderex.repository.AccountRepository +import com.twidere.twiderex.repository.NotificationRepository import com.twidere.twiderex.repository.StatusRepository import dagger.Module import dagger.Provides import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent @Module @@ -65,4 +70,16 @@ object JobModule { statusRepository = statusRepository, inAppNotification = inAppNotification ) + @Provides + fun provideNotificationJob( + @ApplicationContext context: Context, + accountRepository: AccountRepository, + repository: NotificationRepository, + notificationManager: AppNotificationManager + ): NotificationJob = NotificationJob( + applicationContext = context, + repository = repository, + accountRepository = accountRepository, + notificationManager = notificationManager + ) } diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/common/NotificationJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/common/NotificationJob.kt new file mode 100644 index 000000000..979834830 --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/common/NotificationJob.kt @@ -0,0 +1,213 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.jobs.common + +import android.content.Context +import androidx.core.graphics.drawable.toBitmap +import androidx.core.text.HtmlCompat +import coil.Coil +import coil.request.ImageRequest +import coil.request.SuccessResult +import com.twidere.twiderex.R +import com.twidere.twiderex.db.model.ReferenceType +import com.twidere.twiderex.model.AccountDetails +import com.twidere.twiderex.model.MastodonStatusType +import com.twidere.twiderex.model.PlatformType +import com.twidere.twiderex.model.ui.UiStatus +import com.twidere.twiderex.navigation.RootDeepLinksRoute +import com.twidere.twiderex.notification.AppNotification +import com.twidere.twiderex.notification.AppNotificationManager +import com.twidere.twiderex.notification.NotificationChannelSpec +import com.twidere.twiderex.notification.notificationChannelId +import com.twidere.twiderex.repository.AccountRepository +import com.twidere.twiderex.repository.NotificationRepository +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.joinAll +import kotlinx.coroutines.launch + +class NotificationJob( + private val applicationContext: Context, + private val repository: NotificationRepository, + private val accountRepository: AccountRepository, + private val notificationManager: AppNotificationManager, +) { + val scope = MainScope() + suspend fun execute(enableNotification: Boolean): Boolean { + return if (!enableNotification) { + true + } else { + accountRepository.getAccounts().map { accountRepository.getAccountDetails(it) } + .filter { + it.preferences.isNotificationEnabled.first() + } + .map { account -> + scope.launch { + val activities = try { + repository.activities(account) + } catch (e: Throwable) { + // Ignore any exception cause there's no needs ot handle it + emptyList() + } + activities.forEach { status -> + notify(account, status) + } + } + }.joinAll() + true + } + } + + private suspend fun notify(account: AccountDetails, status: UiStatus) { + val notificationId = "${account.accountKey}_${status.statusKey}" + val builder = AppNotification + .Builder( + account.accountKey.notificationChannelId( + NotificationChannelSpec.ContentInteractions.id + ) + ) + + val notificationData = when (status.platformType) { + PlatformType.Twitter -> { + NotificationData( + title = applicationContext.getString( + R.string.common_notification_mentions, + status.user.displayName + ), + htmlContent = status.htmlText, + deepLink = RootDeepLinksRoute.Twitter.Status(status.statusId), + profileImage = status.user.profileImage, + ) + } + PlatformType.StatusNet -> TODO() + PlatformType.Fanfou -> TODO() + PlatformType.Mastodon -> { + generateMastodonNotificationData( + status, + ) + } + } + if (notificationData != null) { + builder.setContentTitle(notificationData.title) + if (notificationData.htmlContent != null) { + val html = HtmlCompat.fromHtml( + notificationData.htmlContent, + HtmlCompat.FROM_HTML_MODE_COMPACT + ) + builder + .setContentText(html) + } + if (notificationData.deepLink != null) { + builder.setDeepLink(notificationData.deepLink) + } + if (notificationData.profileImage != null) { + val result = Coil.execute( + ImageRequest.Builder(applicationContext) + .data(notificationData.profileImage) + .build() + ) + if (result is SuccessResult) { + builder.setLargeIcon(result.drawable.toBitmap()) + } + } + notificationManager.notify(notificationId.hashCode(), builder.build()) + } + } + + private fun generateMastodonNotificationData( + status: UiStatus + ): NotificationData? { + val actualStatus = status.referenceStatus[ReferenceType.MastodonNotification] + if (status.mastodonExtra == null || actualStatus == null) { + return null + } + return when (status.mastodonExtra.type) { + MastodonStatusType.Status -> null + MastodonStatusType.NotificationFollow -> { + NotificationData( + title = applicationContext.getString( + R.string.common_notification_follow, + actualStatus.user.displayName + ), + deepLink = RootDeepLinksRoute.User(actualStatus.user.userKey), + profileImage = actualStatus.user.profileImage, + ) + } + MastodonStatusType.NotificationFollowRequest -> { + NotificationData( + title = applicationContext.getString( + R.string.common_notification_follow_request, + actualStatus.user.displayName + ), + deepLink = RootDeepLinksRoute.User(actualStatus.user.userKey) + ) + } + MastodonStatusType.NotificationMention -> { + NotificationData( + title = applicationContext.getString( + R.string.common_notification_mentions, + actualStatus.user.displayName + ), + htmlContent = actualStatus.htmlText, + deepLink = RootDeepLinksRoute.Status(actualStatus.statusKey), + profileImage = actualStatus.user.profileImage, + ) + } + MastodonStatusType.NotificationReblog -> { + NotificationData( + title = applicationContext.getString( + R.string.common_notification_reblog, + actualStatus.user.displayName + ), + deepLink = RootDeepLinksRoute.Status(actualStatus.statusKey), + profileImage = actualStatus.user.profileImage, + ) + } + MastodonStatusType.NotificationFavourite -> { + NotificationData( + title = applicationContext.getString( + R.string.common_notification_favourite, + actualStatus.user.displayName + ), + deepLink = RootDeepLinksRoute.Status(actualStatus.statusKey), + profileImage = actualStatus.user.profileImage, + ) + } + MastodonStatusType.NotificationPoll -> { + NotificationData( + title = applicationContext.getString( + R.string.common_notification_poll, + ), + deepLink = RootDeepLinksRoute.Status(actualStatus.statusKey), + profileImage = actualStatus.user.profileImage, + ) + } + MastodonStatusType.NotificationStatus -> null + } + } +} + +private data class NotificationData( + val title: String, + val profileImage: Any? = null, + val htmlContent: String? = null, + val deepLink: String? = null, +) diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/NotificationWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/NotificationWorker.kt index 53fcd4e03..2e3155dc8 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/NotificationWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/NotificationWorker.kt @@ -21,202 +21,28 @@ package com.twidere.twiderex.worker import android.content.Context -import androidx.core.graphics.drawable.toBitmap -import androidx.core.text.HtmlCompat import androidx.datastore.core.DataStore import androidx.hilt.work.HiltWorker import androidx.work.CoroutineWorker import androidx.work.WorkerParameters -import coil.Coil -import coil.request.ImageRequest -import coil.request.SuccessResult -import com.twidere.twiderex.R -import com.twidere.twiderex.db.model.ReferenceType -import com.twidere.twiderex.model.AccountDetails -import com.twidere.twiderex.model.MastodonStatusType -import com.twidere.twiderex.model.PlatformType -import com.twidere.twiderex.model.ui.UiStatus -import com.twidere.twiderex.navigation.RootDeepLinksRoute -import com.twidere.twiderex.notification.AppNotification -import com.twidere.twiderex.notification.AppNotificationManager -import com.twidere.twiderex.notification.NotificationChannelSpec -import com.twidere.twiderex.notification.notificationChannelId +import com.twidere.twiderex.extensions.toWorkResult +import com.twidere.twiderex.jobs.common.NotificationJob import com.twidere.twiderex.preferences.proto.NotificationPreferences -import com.twidere.twiderex.repository.AccountRepository -import com.twidere.twiderex.repository.NotificationRepository import dagger.assisted.Assisted import dagger.assisted.AssistedInject import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.first -import kotlinx.coroutines.joinAll -import kotlinx.coroutines.launch -// TODO WORKER refactor after merge app-notification + @HiltWorker class NotificationWorker @AssistedInject constructor( @Assisted appContext: Context, @Assisted params: WorkerParameters, - private val repository: NotificationRepository, - private val accountRepository: AccountRepository, - private val notificationManager: AppNotificationManager, private val notificationPreferences: DataStore, + private val notificationJob: NotificationJob + ) : CoroutineWorker(appContext, params) { override suspend fun doWork(): Result = coroutineScope { - if (!notificationPreferences.data.first().enableNotification) { - Result.success() - } else { - accountRepository.getAccounts().map { accountRepository.getAccountDetails(it) } - .filter { - it.preferences.isNotificationEnabled.first() - } - .map { account -> - launch { - val activities = try { - repository.activities(account) - } catch (e: Throwable) { - // Ignore any exception cause there's no needs ot handle it - emptyList() - } - activities.forEach { status -> - notify(account, status) - } - } - }.joinAll() - Result.success() - } - } - - private suspend fun notify(account: AccountDetails, status: UiStatus) { - val notificationId = "${account.accountKey}_${status.statusKey}" - val builder = AppNotification - .Builder( - account.accountKey.notificationChannelId( - NotificationChannelSpec.ContentInteractions.id - ) - ) - - val notificationData = when (status.platformType) { - PlatformType.Twitter -> { - NotificationData( - title = applicationContext.getString( - R.string.common_notification_mentions, - status.user.displayName - ), - htmlContent = status.htmlText, - deepLink = RootDeepLinksRoute.Twitter.Status(status.statusId), - profileImage = status.user.profileImage, - ) - } - PlatformType.StatusNet -> TODO() - PlatformType.Fanfou -> TODO() - PlatformType.Mastodon -> { - generateMastodonNotificationData( - status, - ) - } - } - if (notificationData != null) { - builder.setContentTitle(notificationData.title) - if (notificationData.htmlContent != null) { - val html = HtmlCompat.fromHtml( - notificationData.htmlContent, - HtmlCompat.FROM_HTML_MODE_COMPACT - ) - builder - .setContentText(html) - } - if (notificationData.deepLink != null) { - builder.setDeepLink(notificationData.deepLink) - } - if (notificationData.profileImage != null) { - val result = Coil.execute( - ImageRequest.Builder(applicationContext) - .data(notificationData.profileImage) - .build() - ) - if (result is SuccessResult) { - builder.setLargeIcon(result.drawable.toBitmap()) - } - } - notificationManager.notify(notificationId.hashCode(), builder.build()) - } - } - - private fun generateMastodonNotificationData( - status: UiStatus - ): NotificationData? { - val actualStatus = status.referenceStatus[ReferenceType.MastodonNotification] - if (status.mastodonExtra == null || actualStatus == null) { - return null - } - return when (status.mastodonExtra.type) { - MastodonStatusType.Status -> null - MastodonStatusType.NotificationFollow -> { - NotificationData( - title = applicationContext.getString( - R.string.common_notification_follow, - actualStatus.user.displayName - ), - deepLink = RootDeepLinksRoute.User(actualStatus.user.userKey), - profileImage = actualStatus.user.profileImage, - ) - } - MastodonStatusType.NotificationFollowRequest -> { - NotificationData( - title = applicationContext.getString( - R.string.common_notification_follow_request, - actualStatus.user.displayName - ), - deepLink = RootDeepLinksRoute.User(actualStatus.user.userKey) - ) - } - MastodonStatusType.NotificationMention -> { - NotificationData( - title = applicationContext.getString( - R.string.common_notification_mentions, - actualStatus.user.displayName - ), - htmlContent = actualStatus.htmlText, - deepLink = RootDeepLinksRoute.Status(actualStatus.statusKey), - profileImage = actualStatus.user.profileImage, - ) - } - MastodonStatusType.NotificationReblog -> { - NotificationData( - title = applicationContext.getString( - R.string.common_notification_reblog, - actualStatus.user.displayName - ), - deepLink = RootDeepLinksRoute.Status(actualStatus.statusKey), - profileImage = actualStatus.user.profileImage, - ) - } - MastodonStatusType.NotificationFavourite -> { - NotificationData( - title = applicationContext.getString( - R.string.common_notification_favourite, - actualStatus.user.displayName - ), - deepLink = RootDeepLinksRoute.Status(actualStatus.statusKey), - profileImage = actualStatus.user.profileImage, - ) - } - MastodonStatusType.NotificationPoll -> { - NotificationData( - title = applicationContext.getString( - R.string.common_notification_poll, - ), - deepLink = RootDeepLinksRoute.Status(actualStatus.statusKey), - profileImage = actualStatus.user.profileImage, - ) - } - MastodonStatusType.NotificationStatus -> null - } + notificationJob.execute(notificationPreferences.data.first().enableNotification) + .toWorkResult() } } - -private data class NotificationData( - val title: String, - val profileImage: Any? = null, - val htmlContent: String? = null, - val deepLink: String? = null, -) From 4f45088b1a630c14b3e76e5682229d3ab177aa1a Mon Sep 17 00:00:00 2001 From: itsMimao Date: Tue, 27 Jul 2021 14:48:50 +0800 Subject: [PATCH 078/137] extract logic code from StatusWorker --- .../twidere/twiderex/action/StatusActions.kt | 2 +- .../com/twidere/twiderex/di/JobModule.kt | 49 ++++++++++ .../twiderex/jobs/status/LikeStatusJob.kt | 67 +++++++++++++ .../twiderex/jobs/status/RetweetStatusJob.kt | 66 +++++++++++++ .../twidere/twiderex/jobs/status/StatusJob.kt | 97 +++++++++++++++++++ .../jobs/status/UnRetweetStatusJob.kt | 66 +++++++++++++ .../twiderex/jobs/status/UnlikeStatusJob.kt | 66 +++++++++++++ .../{worker/status => model}/StatusResult.kt | 3 +- .../twiderex/worker/status/LikeWorker.kt | 43 +------- .../twiderex/worker/status/RetweetWorker.kt | 42 +------- .../twiderex/worker/status/StatusWorker.kt | 78 ++------------- .../twiderex/worker/status/UnLikeWorker.kt | 42 +------- .../twiderex/worker/status/UnRetweetWorker.kt | 42 +------- .../worker/status/UpdateStatusWorker.kt | 1 + 14 files changed, 435 insertions(+), 229 deletions(-) create mode 100644 app/src/main/kotlin/com/twidere/twiderex/jobs/status/LikeStatusJob.kt create mode 100644 app/src/main/kotlin/com/twidere/twiderex/jobs/status/RetweetStatusJob.kt create mode 100644 app/src/main/kotlin/com/twidere/twiderex/jobs/status/StatusJob.kt create mode 100644 app/src/main/kotlin/com/twidere/twiderex/jobs/status/UnRetweetStatusJob.kt create mode 100644 app/src/main/kotlin/com/twidere/twiderex/jobs/status/UnlikeStatusJob.kt rename app/src/main/kotlin/com/twidere/twiderex/{worker/status => model}/StatusResult.kt (93%) diff --git a/app/src/main/kotlin/com/twidere/twiderex/action/StatusActions.kt b/app/src/main/kotlin/com/twidere/twiderex/action/StatusActions.kt index 013e2609a..8d91521f4 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/action/StatusActions.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/action/StatusActions.kt @@ -23,13 +23,13 @@ package com.twidere.twiderex.action import androidx.compose.runtime.compositionLocalOf import androidx.work.WorkManager import com.twidere.twiderex.model.AccountDetails +import com.twidere.twiderex.model.StatusResult import com.twidere.twiderex.model.ui.UiStatus import com.twidere.twiderex.worker.database.DeleteDbStatusWorker import com.twidere.twiderex.worker.status.DeleteStatusWorker import com.twidere.twiderex.worker.status.LikeWorker import com.twidere.twiderex.worker.status.MastodonVoteWorker import com.twidere.twiderex.worker.status.RetweetWorker -import com.twidere.twiderex.worker.status.StatusResult import com.twidere.twiderex.worker.status.StatusWorker import com.twidere.twiderex.worker.status.UnLikeWorker import com.twidere.twiderex.worker.status.UnRetweetWorker diff --git a/app/src/main/kotlin/com/twidere/twiderex/di/JobModule.kt b/app/src/main/kotlin/com/twidere/twiderex/di/JobModule.kt index ac35463b6..f1b9fd944 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/di/JobModule.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/di/JobModule.kt @@ -26,6 +26,10 @@ import com.twidere.twiderex.jobs.common.NotificationJob import com.twidere.twiderex.jobs.common.ShareMediaJob import com.twidere.twiderex.jobs.database.DeleteDbStatusJob import com.twidere.twiderex.jobs.status.DeleteStatusJob +import com.twidere.twiderex.jobs.status.LikeStatusJob +import com.twidere.twiderex.jobs.status.RetweetStatusJob +import com.twidere.twiderex.jobs.status.UnRetweetStatusJob +import com.twidere.twiderex.jobs.status.UnlikeStatusJob import com.twidere.twiderex.notification.AppNotificationManager import com.twidere.twiderex.notification.InAppNotification import com.twidere.twiderex.repository.AccountRepository @@ -70,6 +74,7 @@ object JobModule { statusRepository = statusRepository, inAppNotification = inAppNotification ) + @Provides fun provideNotificationJob( @ApplicationContext context: Context, @@ -82,4 +87,48 @@ object JobModule { accountRepository = accountRepository, notificationManager = notificationManager ) + + @Provides + fun provideLikeStatusJob( + accountRepository: AccountRepository, + statusRepository: StatusRepository, + inAppNotification: InAppNotification, + ): LikeStatusJob = LikeStatusJob( + accountRepository = accountRepository, + statusRepository = statusRepository, + inAppNotification = inAppNotification, + ) + + @Provides + fun provideRetweetStatusJob( + accountRepository: AccountRepository, + statusRepository: StatusRepository, + inAppNotification: InAppNotification, + ): RetweetStatusJob = RetweetStatusJob( + accountRepository = accountRepository, + statusRepository = statusRepository, + inAppNotification = inAppNotification, + ) + + @Provides + fun provideUnlikeStatusJob( + accountRepository: AccountRepository, + statusRepository: StatusRepository, + inAppNotification: InAppNotification, + ): UnlikeStatusJob = UnlikeStatusJob( + accountRepository = accountRepository, + statusRepository = statusRepository, + inAppNotification = inAppNotification, + ) + + @Provides + fun provideUnRetweetStatusJob( + accountRepository: AccountRepository, + statusRepository: StatusRepository, + inAppNotification: InAppNotification, + ): UnRetweetStatusJob = UnRetweetStatusJob( + accountRepository = accountRepository, + statusRepository = statusRepository, + inAppNotification = inAppNotification, + ) } diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/status/LikeStatusJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/status/LikeStatusJob.kt new file mode 100644 index 000000000..a2bb67bef --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/status/LikeStatusJob.kt @@ -0,0 +1,67 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.jobs.status + +import com.twidere.services.microblog.StatusService +import com.twidere.twiderex.db.mapper.toDbStatusWithReference +import com.twidere.twiderex.model.MicroBlogKey +import com.twidere.twiderex.model.StatusResult +import com.twidere.twiderex.model.ui.UiStatus +import com.twidere.twiderex.model.ui.UiStatus.Companion.toUi +import com.twidere.twiderex.notification.InAppNotification +import com.twidere.twiderex.repository.AccountRepository +import com.twidere.twiderex.repository.StatusRepository + +class LikeStatusJob( + accountRepository: AccountRepository, + statusRepository: StatusRepository, + inAppNotification: InAppNotification, +) : StatusJob( + accountRepository, statusRepository, inAppNotification +) { + override suspend fun doWork( + accountKey: MicroBlogKey, + service: StatusService, + status: UiStatus + ): StatusResult { + val newStatus = service.like(status.statusId) + .toDbStatusWithReference(accountKey = accountKey) + .toUi(accountKey = accountKey).let { + it.retweet ?: it + } + return StatusResult( + statusKey = newStatus.statusKey, + accountKey = accountKey, + liked = true, + retweetCount = newStatus.retweetCount, + likeCount = newStatus.likeCount, + ) + } + + override fun fallback( + accountKey: MicroBlogKey, + status: UiStatus, + ) = StatusResult( + accountKey = accountKey, + statusKey = status.statusKey, + liked = false, + ) +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/status/RetweetStatusJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/status/RetweetStatusJob.kt new file mode 100644 index 000000000..875a49daa --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/status/RetweetStatusJob.kt @@ -0,0 +1,66 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.jobs.status + +import com.twidere.services.microblog.StatusService +import com.twidere.twiderex.db.mapper.toDbStatusWithReference +import com.twidere.twiderex.model.MicroBlogKey +import com.twidere.twiderex.model.StatusResult +import com.twidere.twiderex.model.ui.UiStatus +import com.twidere.twiderex.model.ui.UiStatus.Companion.toUi +import com.twidere.twiderex.notification.InAppNotification +import com.twidere.twiderex.repository.AccountRepository +import com.twidere.twiderex.repository.StatusRepository + +class RetweetStatusJob( + accountRepository: AccountRepository, + statusRepository: StatusRepository, + inAppNotification: InAppNotification, +) : StatusJob( + accountRepository, statusRepository, inAppNotification +) { + override suspend fun doWork( + accountKey: MicroBlogKey, + service: StatusService, + status: UiStatus + ): StatusResult { + val newStatus = service.retweet(status.statusId) + .toDbStatusWithReference(accountKey = accountKey) + .toUi(accountKey = accountKey).let { + it.retweet ?: it + } + return StatusResult( + statusKey = newStatus.statusKey, + accountKey = accountKey, + retweeted = true, + retweetCount = newStatus.retweetCount, + likeCount = newStatus.likeCount, + ) + } + override fun fallback( + accountKey: MicroBlogKey, + status: UiStatus, + ) = StatusResult( + accountKey = accountKey, + statusKey = status.statusKey, + retweeted = false, + ) +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/status/StatusJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/status/StatusJob.kt new file mode 100644 index 000000000..dbe049cf6 --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/status/StatusJob.kt @@ -0,0 +1,97 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.jobs.status + +import com.twidere.services.microblog.StatusService +import com.twidere.services.twitter.TwitterErrorCodes +import com.twidere.services.twitter.model.exceptions.TwitterApiException +import com.twidere.twiderex.model.MicroBlogKey +import com.twidere.twiderex.model.StatusResult +import com.twidere.twiderex.model.ui.UiStatus +import com.twidere.twiderex.notification.InAppNotification +import com.twidere.twiderex.repository.AccountRepository +import com.twidere.twiderex.repository.StatusRepository +import com.twidere.twiderex.utils.notify + +abstract class StatusJob( + private val accountRepository: AccountRepository, + private val statusRepository: StatusRepository, + private val inAppNotification: InAppNotification, +) { + @Throws + suspend fun execute(accountKey: MicroBlogKey, statusKey: MicroBlogKey): StatusResult { + val status = statusKey.let { + statusRepository.loadFromCache(it, accountKey = accountKey) + } ?: throw Error("can't find any status matches:$statusKey") + val service = accountRepository.findByAccountKey(accountKey)?.let { + accountRepository.getAccountDetails(it) + }?.let { + it.service as? StatusService + } ?: throw Error("account service is not StatusService") + return try { + return doWork(accountKey, service, status) + } catch (e: TwitterApiException) { + e.errors?.firstOrNull()?.let { + when (it.code) { + TwitterErrorCodes.AlreadyRetweeted -> { + StatusResult( + accountKey = accountKey, + statusKey = status.statusKey, + retweeted = true, + ) + } + TwitterErrorCodes.AlreadyFavorited -> { + StatusResult( + accountKey = accountKey, + statusKey = status.statusKey, + liked = true, + ) + } + else -> { + e.notify(inAppNotification) + fallback(accountKey, status) + } + } + } ?: run { + e.notify(inAppNotification) + fallback(accountKey, status) + } + } catch (e: Throwable) { + e.notify(inAppNotification) + fallback(accountKey, status) + } + } + protected abstract suspend fun doWork( + accountKey: MicroBlogKey, + service: StatusService, + status: UiStatus, + ): StatusResult + + protected open fun fallback( + accountKey: MicroBlogKey, + status: UiStatus, + ) = StatusResult( + accountKey = accountKey, + statusKey = status.statusKey, + liked = status.liked, + retweeted = status.retweeted, + ) +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/status/UnRetweetStatusJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/status/UnRetweetStatusJob.kt new file mode 100644 index 000000000..590345697 --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/status/UnRetweetStatusJob.kt @@ -0,0 +1,66 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.jobs.status + +import com.twidere.services.microblog.StatusService +import com.twidere.twiderex.db.mapper.toDbStatusWithReference +import com.twidere.twiderex.model.MicroBlogKey +import com.twidere.twiderex.model.StatusResult +import com.twidere.twiderex.model.ui.UiStatus +import com.twidere.twiderex.model.ui.UiStatus.Companion.toUi +import com.twidere.twiderex.notification.InAppNotification +import com.twidere.twiderex.repository.AccountRepository +import com.twidere.twiderex.repository.StatusRepository + +class UnRetweetStatusJob( + accountRepository: AccountRepository, + statusRepository: StatusRepository, + inAppNotification: InAppNotification, +) : StatusJob( + accountRepository, statusRepository, inAppNotification +) { + override suspend fun doWork( + accountKey: MicroBlogKey, + service: StatusService, + status: UiStatus + ): StatusResult { + val newStatus = service.unRetweet(status.statusId) + .toDbStatusWithReference(accountKey = accountKey) + .toUi(accountKey = accountKey).let { + it.retweet ?: it + } + return StatusResult( + statusKey = newStatus.statusKey, + accountKey = accountKey, + retweeted = false, + retweetCount = newStatus.retweetCount, + likeCount = newStatus.likeCount, + ) + } + override fun fallback( + accountKey: MicroBlogKey, + status: UiStatus, + ) = StatusResult( + accountKey = accountKey, + statusKey = status.statusKey, + retweeted = true, + ) +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/status/UnlikeStatusJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/status/UnlikeStatusJob.kt new file mode 100644 index 000000000..f88b68269 --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/status/UnlikeStatusJob.kt @@ -0,0 +1,66 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.jobs.status + +import com.twidere.services.microblog.StatusService +import com.twidere.twiderex.db.mapper.toDbStatusWithReference +import com.twidere.twiderex.model.MicroBlogKey +import com.twidere.twiderex.model.StatusResult +import com.twidere.twiderex.model.ui.UiStatus +import com.twidere.twiderex.model.ui.UiStatus.Companion.toUi +import com.twidere.twiderex.notification.InAppNotification +import com.twidere.twiderex.repository.AccountRepository +import com.twidere.twiderex.repository.StatusRepository + +class UnlikeStatusJob( + accountRepository: AccountRepository, + statusRepository: StatusRepository, + inAppNotification: InAppNotification, +) : StatusJob( + accountRepository, statusRepository, inAppNotification +) { + override suspend fun doWork( + accountKey: MicroBlogKey, + service: StatusService, + status: UiStatus + ): StatusResult { + val newStatus = service.unlike(status.statusId) + .toDbStatusWithReference(accountKey = accountKey) + .toUi(accountKey = accountKey).let { + it.retweet ?: it + } + return StatusResult( + statusKey = newStatus.statusKey, + accountKey = accountKey, + liked = false, + retweetCount = newStatus.retweetCount, + likeCount = newStatus.likeCount, + ) + } + override fun fallback( + accountKey: MicroBlogKey, + status: UiStatus, + ) = StatusResult( + accountKey = accountKey, + statusKey = status.statusKey, + liked = true, + ) +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/status/StatusResult.kt b/app/src/main/kotlin/com/twidere/twiderex/model/StatusResult.kt similarity index 93% rename from app/src/main/kotlin/com/twidere/twiderex/worker/status/StatusResult.kt rename to app/src/main/kotlin/com/twidere/twiderex/model/StatusResult.kt index a7e9b267f..8e3967dfd 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/status/StatusResult.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/model/StatusResult.kt @@ -18,10 +18,9 @@ * You should have received a copy of the GNU General Public License * along with Twidere X. If not, see . */ -package com.twidere.twiderex.worker.status +package com.twidere.twiderex.model import androidx.work.workDataOf -import com.twidere.twiderex.model.MicroBlogKey data class StatusResult( val statusKey: MicroBlogKey, diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/status/LikeWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/status/LikeWorker.kt index 75768c172..4e6323eb8 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/status/LikeWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/status/LikeWorker.kt @@ -23,14 +23,7 @@ package com.twidere.twiderex.worker.status import android.content.Context import androidx.hilt.work.HiltWorker import androidx.work.WorkerParameters -import com.twidere.services.microblog.StatusService -import com.twidere.twiderex.db.mapper.toDbStatusWithReference -import com.twidere.twiderex.model.MicroBlogKey -import com.twidere.twiderex.model.ui.UiStatus -import com.twidere.twiderex.model.ui.UiStatus.Companion.toUi -import com.twidere.twiderex.notification.InAppNotification -import com.twidere.twiderex.repository.AccountRepository -import com.twidere.twiderex.repository.StatusRepository +import com.twidere.twiderex.jobs.status.LikeStatusJob import dagger.assisted.Assisted import dagger.assisted.AssistedInject @@ -38,35 +31,5 @@ import dagger.assisted.AssistedInject class LikeWorker @AssistedInject constructor( @Assisted appContext: Context, @Assisted params: WorkerParameters, - accountRepository: AccountRepository, - statusRepository: StatusRepository, - inAppNotification: InAppNotification, -) : StatusWorker(appContext, params, accountRepository, statusRepository, inAppNotification) { - override suspend fun doWork( - accountKey: MicroBlogKey, - service: StatusService, - status: UiStatus - ): StatusResult { - val newStatus = service.like(status.statusId) - .toDbStatusWithReference(accountKey = accountKey) - .toUi(accountKey = accountKey).let { - it.retweet ?: it - } - return StatusResult( - statusKey = newStatus.statusKey, - accountKey = accountKey, - liked = true, - retweetCount = newStatus.retweetCount, - likeCount = newStatus.likeCount, - ) - } - - override fun fallback( - accountKey: MicroBlogKey, - status: UiStatus, - ) = StatusResult( - accountKey = accountKey, - statusKey = status.statusKey, - liked = false, - ) -} + likeStatusJob: LikeStatusJob, +) : StatusWorker(appContext, params, likeStatusJob) diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/status/RetweetWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/status/RetweetWorker.kt index c6dadeac9..68672a115 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/status/RetweetWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/status/RetweetWorker.kt @@ -23,14 +23,7 @@ package com.twidere.twiderex.worker.status import android.content.Context import androidx.hilt.work.HiltWorker import androidx.work.WorkerParameters -import com.twidere.services.microblog.StatusService -import com.twidere.twiderex.db.mapper.toDbStatusWithReference -import com.twidere.twiderex.model.MicroBlogKey -import com.twidere.twiderex.model.ui.UiStatus -import com.twidere.twiderex.model.ui.UiStatus.Companion.toUi -import com.twidere.twiderex.notification.InAppNotification -import com.twidere.twiderex.repository.AccountRepository -import com.twidere.twiderex.repository.StatusRepository +import com.twidere.twiderex.jobs.status.RetweetStatusJob import dagger.assisted.Assisted import dagger.assisted.AssistedInject @@ -38,34 +31,5 @@ import dagger.assisted.AssistedInject class RetweetWorker @AssistedInject constructor( @Assisted appContext: Context, @Assisted params: WorkerParameters, - accountRepository: AccountRepository, - statusRepository: StatusRepository, - inAppNotification: InAppNotification, -) : StatusWorker(appContext, params, accountRepository, statusRepository, inAppNotification) { - override suspend fun doWork( - accountKey: MicroBlogKey, - service: StatusService, - status: UiStatus - ): StatusResult { - val newStatus = service.retweet(status.statusId) - .toDbStatusWithReference(accountKey = accountKey) - .toUi(accountKey = accountKey).let { - it.retweet ?: it - } - return StatusResult( - statusKey = newStatus.statusKey, - accountKey = accountKey, - retweeted = true, - retweetCount = newStatus.retweetCount, - likeCount = newStatus.likeCount, - ) - } - override fun fallback( - accountKey: MicroBlogKey, - status: UiStatus, - ) = StatusResult( - accountKey = accountKey, - statusKey = status.statusKey, - retweeted = false, - ) -} + retweetStatusJob: RetweetStatusJob, +) : StatusWorker(appContext, params, retweetStatusJob) diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/status/StatusWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/status/StatusWorker.kt index c1bf8f6a2..726c3ceab 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/status/StatusWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/status/StatusWorker.kt @@ -25,22 +25,14 @@ import androidx.work.CoroutineWorker import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkerParameters import androidx.work.workDataOf -import com.twidere.services.microblog.StatusService -import com.twidere.services.twitter.TwitterErrorCodes -import com.twidere.services.twitter.model.exceptions.TwitterApiException +import com.twidere.twiderex.jobs.status.StatusJob import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.ui.UiStatus -import com.twidere.twiderex.notification.InAppNotification -import com.twidere.twiderex.repository.AccountRepository -import com.twidere.twiderex.repository.StatusRepository -import com.twidere.twiderex.utils.notify abstract class StatusWorker( appContext: Context, params: WorkerParameters, - private val accountRepository: AccountRepository, - private val statusRepository: StatusRepository, - private val inAppNotification: InAppNotification, + private val statusJob: StatusJob, ) : CoroutineWorker(appContext, params) { companion object { inline fun create( @@ -60,70 +52,18 @@ abstract class StatusWorker( val accountKey = inputData.getString("accountKey")?.let { MicroBlogKey.valueOf(it) } ?: return Result.failure() - val status = inputData.getString("statusKey")?.let { + val statusKey = inputData.getString("statusKey")?.let { MicroBlogKey.valueOf(it) - }?.let { - statusRepository.loadFromCache(it, accountKey = accountKey) - } ?: return Result.failure() - val service = accountRepository.findByAccountKey(accountKey)?.let { - accountRepository.getAccountDetails(it) - }?.let { - it.service as? StatusService } ?: return Result.failure() return try { - val result = doWork(accountKey, service, status) - Result.success( - result.toWorkData() - ) - } catch (e: TwitterApiException) { - e.errors?.firstOrNull()?.let { - when (it.code) { - TwitterErrorCodes.AlreadyRetweeted -> { - Result.success( - StatusResult( - accountKey = accountKey, - statusKey = status.statusKey, - retweeted = true, - ).toWorkData() - ) - } - TwitterErrorCodes.AlreadyFavorited -> { - Result.success( - StatusResult( - accountKey = accountKey, - statusKey = status.statusKey, - liked = true, - ).toWorkData() - ) - } - else -> { - e.notify(inAppNotification) - Result.success(fallback(accountKey, status).toWorkData()) - } - } - } ?: run { - e.notify(inAppNotification) - Result.success(fallback(accountKey, status).toWorkData()) + statusJob.execute( + accountKey = accountKey, + statusKey = statusKey + ).let { + Result.success(it.toWorkData()) } } catch (e: Throwable) { - e.notify(inAppNotification) - Result.success(fallback(accountKey, status).toWorkData()) + Result.failure() } } - - protected open fun fallback( - accountKey: MicroBlogKey, - status: UiStatus, - ) = StatusResult( - accountKey = accountKey, - statusKey = status.statusKey, - liked = status.liked, - retweeted = status.retweeted, - ) - - protected abstract suspend fun doWork( - accountKey: MicroBlogKey, - service: StatusService, - status: UiStatus, - ): StatusResult } diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/status/UnLikeWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/status/UnLikeWorker.kt index 0263030ec..bf612f76a 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/status/UnLikeWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/status/UnLikeWorker.kt @@ -23,14 +23,7 @@ package com.twidere.twiderex.worker.status import android.content.Context import androidx.hilt.work.HiltWorker import androidx.work.WorkerParameters -import com.twidere.services.microblog.StatusService -import com.twidere.twiderex.db.mapper.toDbStatusWithReference -import com.twidere.twiderex.model.MicroBlogKey -import com.twidere.twiderex.model.ui.UiStatus -import com.twidere.twiderex.model.ui.UiStatus.Companion.toUi -import com.twidere.twiderex.notification.InAppNotification -import com.twidere.twiderex.repository.AccountRepository -import com.twidere.twiderex.repository.StatusRepository +import com.twidere.twiderex.jobs.status.UnlikeStatusJob import dagger.assisted.Assisted import dagger.assisted.AssistedInject @@ -38,34 +31,5 @@ import dagger.assisted.AssistedInject class UnLikeWorker @AssistedInject constructor( @Assisted appContext: Context, @Assisted params: WorkerParameters, - accountRepository: AccountRepository, - statusRepository: StatusRepository, - inAppNotification: InAppNotification, -) : StatusWorker(appContext, params, accountRepository, statusRepository, inAppNotification) { - override suspend fun doWork( - accountKey: MicroBlogKey, - service: StatusService, - status: UiStatus - ): StatusResult { - val newStatus = service.unlike(status.statusId) - .toDbStatusWithReference(accountKey = accountKey) - .toUi(accountKey = accountKey).let { - it.retweet ?: it - } - return StatusResult( - statusKey = newStatus.statusKey, - accountKey = accountKey, - liked = false, - retweetCount = newStatus.retweetCount, - likeCount = newStatus.likeCount, - ) - } - override fun fallback( - accountKey: MicroBlogKey, - status: UiStatus, - ) = StatusResult( - accountKey = accountKey, - statusKey = status.statusKey, - liked = true, - ) -} + unlikeStatusJob: UnlikeStatusJob, +) : StatusWorker(appContext, params, unlikeStatusJob) diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/status/UnRetweetWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/status/UnRetweetWorker.kt index 861b10dab..0ac90ff33 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/status/UnRetweetWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/status/UnRetweetWorker.kt @@ -23,14 +23,7 @@ package com.twidere.twiderex.worker.status import android.content.Context import androidx.hilt.work.HiltWorker import androidx.work.WorkerParameters -import com.twidere.services.microblog.StatusService -import com.twidere.twiderex.db.mapper.toDbStatusWithReference -import com.twidere.twiderex.model.MicroBlogKey -import com.twidere.twiderex.model.ui.UiStatus -import com.twidere.twiderex.model.ui.UiStatus.Companion.toUi -import com.twidere.twiderex.notification.InAppNotification -import com.twidere.twiderex.repository.AccountRepository -import com.twidere.twiderex.repository.StatusRepository +import com.twidere.twiderex.jobs.status.UnRetweetStatusJob import dagger.assisted.Assisted import dagger.assisted.AssistedInject @@ -38,34 +31,5 @@ import dagger.assisted.AssistedInject class UnRetweetWorker @AssistedInject constructor( @Assisted appContext: Context, @Assisted params: WorkerParameters, - accountRepository: AccountRepository, - statusRepository: StatusRepository, - inAppNotification: InAppNotification, -) : StatusWorker(appContext, params, accountRepository, statusRepository, inAppNotification) { - override suspend fun doWork( - accountKey: MicroBlogKey, - service: StatusService, - status: UiStatus - ): StatusResult { - val newStatus = service.unRetweet(status.statusId) - .toDbStatusWithReference(accountKey = accountKey) - .toUi(accountKey = accountKey).let { - it.retweet ?: it - } - return StatusResult( - statusKey = newStatus.statusKey, - accountKey = accountKey, - retweeted = false, - retweetCount = newStatus.retweetCount, - likeCount = newStatus.likeCount, - ) - } - override fun fallback( - accountKey: MicroBlogKey, - status: UiStatus, - ) = StatusResult( - accountKey = accountKey, - statusKey = status.statusKey, - retweeted = true, - ) -} + unRetweetStatusJob: UnRetweetStatusJob +) : StatusWorker(appContext, params, unRetweetStatusJob) diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/status/UpdateStatusWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/status/UpdateStatusWorker.kt index 93e992d80..49d179deb 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/status/UpdateStatusWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/status/UpdateStatusWorker.kt @@ -30,6 +30,7 @@ import androidx.work.setInputMerger import com.twidere.twiderex.extensions.getNullableBoolean import com.twidere.twiderex.extensions.getNullableLong import com.twidere.twiderex.model.MicroBlogKey +import com.twidere.twiderex.model.StatusResult import com.twidere.twiderex.repository.ReactionRepository import com.twidere.twiderex.repository.StatusRepository import dagger.assisted.Assisted From 00a2d05c09d8d3f9828882ce49e9cb5fd5921fe3 Mon Sep 17 00:00:00 2001 From: itsMimao Date: Tue, 27 Jul 2021 14:54:45 +0800 Subject: [PATCH 079/137] extract bussiness from MastodonVoteWorker --- .../com/twidere/twiderex/di/JobModule.kt | 12 +++ .../twiderex/jobs/status/MastodonVoteJob.kt | 75 +++++++++++++++++++ .../worker/status/MastodonVoteWorker.kt | 57 +++----------- 3 files changed, 96 insertions(+), 48 deletions(-) create mode 100644 app/src/main/kotlin/com/twidere/twiderex/jobs/status/MastodonVoteJob.kt diff --git a/app/src/main/kotlin/com/twidere/twiderex/di/JobModule.kt b/app/src/main/kotlin/com/twidere/twiderex/di/JobModule.kt index f1b9fd944..8668bd9f9 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/di/JobModule.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/di/JobModule.kt @@ -27,6 +27,7 @@ import com.twidere.twiderex.jobs.common.ShareMediaJob import com.twidere.twiderex.jobs.database.DeleteDbStatusJob import com.twidere.twiderex.jobs.status.DeleteStatusJob import com.twidere.twiderex.jobs.status.LikeStatusJob +import com.twidere.twiderex.jobs.status.MastodonVoteJob import com.twidere.twiderex.jobs.status.RetweetStatusJob import com.twidere.twiderex.jobs.status.UnRetweetStatusJob import com.twidere.twiderex.jobs.status.UnlikeStatusJob @@ -131,4 +132,15 @@ object JobModule { statusRepository = statusRepository, inAppNotification = inAppNotification, ) + + @Provides + fun provideMastodonVoteJob( + accountRepository: AccountRepository, + statusRepository: StatusRepository, + inAppNotification: InAppNotification, + ): MastodonVoteJob = MastodonVoteJob( + accountRepository = accountRepository, + statusRepository = statusRepository, + inAppNotification = inAppNotification, + ) } diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/status/MastodonVoteJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/status/MastodonVoteJob.kt new file mode 100644 index 000000000..12c06b28f --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/status/MastodonVoteJob.kt @@ -0,0 +1,75 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.jobs.status + +import com.twidere.services.mastodon.MastodonService +import com.twidere.twiderex.model.MicroBlogKey +import com.twidere.twiderex.model.PlatformType +import com.twidere.twiderex.notification.InAppNotification +import com.twidere.twiderex.repository.AccountRepository +import com.twidere.twiderex.repository.StatusRepository +import com.twidere.twiderex.utils.notify + +class MastodonVoteJob( + private val accountRepository: AccountRepository, + private val statusRepository: StatusRepository, + private val inAppNotification: InAppNotification, +) { + suspend fun execute(votes: List, accountKey: MicroBlogKey, statusKey: MicroBlogKey): Boolean { + val status = statusRepository.loadFromCache(statusKey, accountKey = accountKey) ?: return false + if (status.mastodonExtra?.poll == null || status.platformType != PlatformType.Mastodon) { + return true + } + val service = accountRepository.findByAccountKey(accountKey)?.let { + accountRepository.getAccountDetails(it) + }?.let { + it.service as? MastodonService + } ?: return false + + val pollId = status.mastodonExtra.poll.id ?: return false + val originPoll = status.mastodonExtra.poll + statusRepository.updateStatus(statusKey = status.statusKey) { + it.mastodonExtra = status.mastodonExtra.copy( + poll = status.mastodonExtra.poll.copy( + voted = true, + ownVotes = votes + ) + ) + } + return try { + val newPoll = service.vote(pollId, votes) + statusRepository.updateStatus(statusKey = status.statusKey) { + it.mastodonExtra = status.mastodonExtra.copy( + poll = newPoll + ) + } + true + } catch (e: Throwable) { + statusRepository.updateStatus(statusKey = status.statusKey) { + it.mastodonExtra = status.mastodonExtra.copy( + poll = originPoll + ) + } + e.notify(inAppNotification) + false + } + } +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/status/MastodonVoteWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/status/MastodonVoteWorker.kt index 6f6b1208b..c2deccf1a 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/status/MastodonVoteWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/status/MastodonVoteWorker.kt @@ -26,13 +26,9 @@ import androidx.work.CoroutineWorker import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkerParameters import androidx.work.workDataOf -import com.twidere.services.mastodon.MastodonService +import com.twidere.twiderex.extensions.toWorkResult +import com.twidere.twiderex.jobs.status.MastodonVoteJob import com.twidere.twiderex.model.MicroBlogKey -import com.twidere.twiderex.model.PlatformType -import com.twidere.twiderex.notification.InAppNotification -import com.twidere.twiderex.repository.AccountRepository -import com.twidere.twiderex.repository.StatusRepository -import com.twidere.twiderex.utils.notify import dagger.assisted.Assisted import dagger.assisted.AssistedInject @@ -40,9 +36,7 @@ import dagger.assisted.AssistedInject class MastodonVoteWorker @AssistedInject constructor( @Assisted appContext: Context, @Assisted params: WorkerParameters, - private val accountRepository: AccountRepository, - private val statusRepository: StatusRepository, - private val inAppNotification: InAppNotification, + private val mastodonVoteJob: MastodonVoteJob ) : CoroutineWorker(appContext, params) { companion object { @@ -68,46 +62,13 @@ class MastodonVoteWorker @AssistedInject constructor( val accountKey = inputData.getString("accountKey")?.let { MicroBlogKey.valueOf(it) } ?: return Result.failure() - val status = inputData.getString("statusKey")?.let { + val statusKey = inputData.getString("statusKey")?.let { MicroBlogKey.valueOf(it) - }?.let { - statusRepository.loadFromCache(it, accountKey = accountKey) } ?: return Result.failure() - if (status.mastodonExtra?.poll == null || status.platformType != PlatformType.Mastodon) { - return Result.success() - } - val service = accountRepository.findByAccountKey(accountKey)?.let { - accountRepository.getAccountDetails(it) - }?.let { - it.service as? MastodonService - } ?: return Result.failure() - - val pollId = status.mastodonExtra.poll.id ?: return Result.failure() - val originPoll = status.mastodonExtra.poll - statusRepository.updateStatus(statusKey = status.statusKey) { - it.mastodonExtra = status.mastodonExtra.copy( - poll = status.mastodonExtra.poll.copy( - voted = true, - ownVotes = votes - ) - ) - } - return try { - val newPoll = service.vote(pollId, votes) - statusRepository.updateStatus(statusKey = status.statusKey) { - it.mastodonExtra = status.mastodonExtra.copy( - poll = newPoll - ) - } - Result.success() - } catch (e: Throwable) { - statusRepository.updateStatus(statusKey = status.statusKey) { - it.mastodonExtra = status.mastodonExtra.copy( - poll = originPoll - ) - } - e.notify(inAppNotification) - Result.failure() - } + return mastodonVoteJob.execute( + votes = votes, + accountKey = accountKey, + statusKey = statusKey + ).toWorkResult() } } From 29ecd4f9e074ad253d4fb8bd843d07cbc2234bef Mon Sep 17 00:00:00 2001 From: itsMimao Date: Tue, 27 Jul 2021 15:00:31 +0800 Subject: [PATCH 080/137] extract bussiness from DraftWorkers --- .../twiderex/jobs/draft/RemoveDraftJob.kt | 36 +++++++++++++ .../twiderex/jobs/draft/SaveDraftJob.kt | 50 +++++++++++++++++++ .../worker/draft/RemoveDraftWorker.kt | 10 ++-- .../twiderex/worker/draft/SaveDraftWorker.kt | 25 ++-------- 4 files changed, 96 insertions(+), 25 deletions(-) create mode 100644 app/src/main/kotlin/com/twidere/twiderex/jobs/draft/RemoveDraftJob.kt create mode 100644 app/src/main/kotlin/com/twidere/twiderex/jobs/draft/SaveDraftJob.kt diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/draft/RemoveDraftJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/draft/RemoveDraftJob.kt new file mode 100644 index 000000000..f596a4631 --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/draft/RemoveDraftJob.kt @@ -0,0 +1,36 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.jobs.draft + +import com.twidere.twiderex.repository.DraftRepository + +class RemoveDraftJob( + private val repository: DraftRepository, +) { + suspend fun execute(draftId: String): Boolean { + return try { + repository.remove(draftId = draftId) + true + } catch (e: Throwable) { + false + } + } +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/draft/SaveDraftJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/draft/SaveDraftJob.kt new file mode 100644 index 000000000..455b734b6 --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/draft/SaveDraftJob.kt @@ -0,0 +1,50 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.jobs.draft + +import com.twidere.twiderex.model.ComposeData +import com.twidere.twiderex.notification.InAppNotification +import com.twidere.twiderex.repository.DraftRepository +import com.twidere.twiderex.utils.notify + +class SaveDraftJob( + private val repository: DraftRepository, + private val inAppNotification: InAppNotification, +) { + suspend fun execute(data: ComposeData): Boolean { + return with(data) { + try { + repository.addOrUpgrade( + content, + images, + composeType = composeType, + statusKey = statusKey, + draftId = draftId, + excludedReplyUserIds = excludedReplyUserIds, + ) + true + } catch (e: Throwable) { + e.notify(inAppNotification) + false + } + } + } +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/draft/RemoveDraftWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/draft/RemoveDraftWorker.kt index 4d41d5b70..ccc630d6f 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/draft/RemoveDraftWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/draft/RemoveDraftWorker.kt @@ -26,7 +26,8 @@ import androidx.work.CoroutineWorker import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkerParameters import androidx.work.workDataOf -import com.twidere.twiderex.repository.DraftRepository +import com.twidere.twiderex.extensions.toWorkResult +import com.twidere.twiderex.jobs.draft.RemoveDraftJob import dagger.assisted.Assisted import dagger.assisted.AssistedInject @@ -34,7 +35,7 @@ import dagger.assisted.AssistedInject class RemoveDraftWorker @AssistedInject constructor( @Assisted appContext: Context, @Assisted params: WorkerParameters, - private val repository: DraftRepository, + private val removeDraftJob: RemoveDraftJob ) : CoroutineWorker(appContext, params) { companion object { @@ -49,7 +50,8 @@ class RemoveDraftWorker @AssistedInject constructor( override suspend fun doWork(): Result { val draftId = inputData.getString("draftId") ?: return Result.failure() - repository.remove(draftId = draftId) - return Result.success() + return removeDraftJob.execute( + draftId = draftId + ).toWorkResult() } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/draft/SaveDraftWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/draft/SaveDraftWorker.kt index c6506c22c..93eb47c11 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/draft/SaveDraftWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/draft/SaveDraftWorker.kt @@ -25,12 +25,11 @@ import androidx.hilt.work.HiltWorker import androidx.work.CoroutineWorker import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkerParameters +import com.twidere.twiderex.extensions.toWorkResult +import com.twidere.twiderex.jobs.draft.SaveDraftJob import com.twidere.twiderex.model.ComposeData import com.twidere.twiderex.model.toComposeData import com.twidere.twiderex.model.toWorkData -import com.twidere.twiderex.notification.InAppNotification -import com.twidere.twiderex.repository.DraftRepository -import com.twidere.twiderex.utils.notify import dagger.assisted.Assisted import dagger.assisted.AssistedInject @@ -38,8 +37,7 @@ import dagger.assisted.AssistedInject class SaveDraftWorker @AssistedInject constructor( @Assisted context: Context, @Assisted workerParams: WorkerParameters, - private val repository: DraftRepository, - private val inAppNotification: InAppNotification, + private val saveDraftJob: SaveDraftJob ) : CoroutineWorker(context, workerParams) { companion object { @@ -50,21 +48,6 @@ class SaveDraftWorker @AssistedInject constructor( override suspend fun doWork(): Result { val data = inputData.toComposeData() - with(data) { - return try { - repository.addOrUpgrade( - content, - images, - composeType = composeType, - statusKey = statusKey, - draftId = draftId, - excludedReplyUserIds = excludedReplyUserIds, - ) - Result.success() - } catch (e: Throwable) { - e.notify(inAppNotification) - Result.failure() - } - } + return saveDraftJob.execute(data = data).toWorkResult() } } From 53a29ad6a53a800836dd9c12a356e53ad84dd0aa Mon Sep 17 00:00:00 2001 From: Tlaster Date: Tue, 27 Jul 2021 15:02:34 +0800 Subject: [PATCH 081/137] fix stack destroy transition --- .../foundation/navigation/NavHost.kt | 57 +++++-------------- .../foundation/navigation/RouteStack.kt | 8 +++ .../navigation/RouteStackManager.kt | 4 +- 3 files changed, 25 insertions(+), 44 deletions(-) diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/NavHost.kt b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/NavHost.kt index 49c911bf0..09a3fc2bc 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/NavHost.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/NavHost.kt @@ -87,20 +87,23 @@ fun NavHost( navTransition = navTransition, manager = manager, ) { routeStack -> - // LaunchedEffect(routeStack) { - // routeStack.onActive() - // } - // DisposableEffect(routeStack) { - // onDispose { - // routeStack.onInActive() - // } - // } - LaunchedEffect(routeStack.currentEntry) { - routeStack.currentEntry?.active() + LaunchedEffect(routeStack) { + routeStack.onActive() } - DisposableEffect(routeStack.currentEntry) { + DisposableEffect(routeStack) { onDispose { - routeStack.currentEntry?.inActive() + routeStack.onInActive() + } + } + val currentEntry = routeStack.currentEntry + if (currentEntry != null) { + LaunchedEffect(currentEntry) { + currentEntry.active() + } + DisposableEffect(currentEntry) { + onDispose { + currentEntry.inActive() + } } } routeStack.stacks.forEach { @@ -113,36 +116,6 @@ fun NavHost( } } } - // stateHolder.SaveableStateProvider(routeStack.id) { - // CompositionLocalProvider( - // LocalViewModelStoreOwner provides routeStack.scene, - // LocalLifecycleOwner provides routeStack, - // ) { - // routeStack.scene.route.content.invoke(routeStack.scene) - // } - // } - // - // Crossfade(targetState = routeStack.currentDialogStack) { - // it?.let { backStackEntry -> - // CompositionLocalProvider( - // LocalViewModelStoreOwner provides backStackEntry, - // LocalLifecycleOwner provides backStackEntry, - // ) { - // Box( - // modifier = Modifier - // .pointerInput(Unit) { - // forEachGesture { - // awaitPointerEventScope { - // awaitPointerEvent().changes.forEach { it.consumeAllChanges() } - // } - // } - // } - // ) { - // backStackEntry.route.content.invoke(backStackEntry) - // } - // } - // } - // } } } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/RouteStack.kt b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/RouteStack.kt index b95537dea..27d79a163 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/RouteStack.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/RouteStack.kt @@ -31,6 +31,7 @@ internal class RouteStack( val stacks: SnapshotStateList = mutableStateListOf(), val navTransition: NavTransition? = null, ) { + private var destroyAfterTransition = false val currentEntry: BackStackEntry? get() = stacks.lastOrNull() @@ -49,6 +50,13 @@ internal class RouteStack( fun onInActive() { currentEntry?.inActive() + if (destroyAfterTransition) { + onDestroyed() + } + } + + fun destroyAfterTransition() { + destroyAfterTransition = true } fun onDestroyed() { diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/RouteStackManager.kt b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/RouteStackManager.kt index 618ebdad5..8c1aa3f1a 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/RouteStackManager.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/RouteStackManager.kt @@ -108,7 +108,7 @@ internal class RouteStackManager( checkNotNull(matchResult) { "RouteStackManager: navigate target $path not found" } require(matchResult.route is ComposeRoute) { "RouteStackManager: navigate target $path is not ComposeRoute" } if (options != null && matchResult.route is SceneRoute && options.launchSingleTop) { - _backStacks.firstOrNull { it.hasRoute(matchResult.route.route) }?.let { + _backStacks.firstOrNull { it.hasRoute(matchResult.route.route) }?.let { _backStacks.remove(it) _backStacks.add(it) } @@ -166,7 +166,7 @@ internal class RouteStackManager( val stack = _backStacks.removeLast() val entry = stack.currentEntry stateHolder.removeState(stack.id) - stack.onDestroyed() + stack.destroyAfterTransition() entry } else -> { From b23da38d828000e090ae0b567c3944f3e80ff5fb Mon Sep 17 00:00:00 2001 From: itsMimao Date: Tue, 27 Jul 2021 15:39:02 +0800 Subject: [PATCH 082/137] extract bussiness from dm workers --- .../com/twidere/twiderex/di/JobModule.kt | 82 ++++++- .../jobs/dm/DirectMessageDeleteJob.kt | 53 +++++ .../twiderex/jobs/dm/DirectMessageFetchJob.kt | 78 +++++++ .../twiderex/jobs/dm/DirectMessageSendJob.kt | 213 ++++++++++++++++++ .../jobs/dm/TwitterDirectMessageSendJob.kt | 91 ++++++++ .../worker/dm/DirectMessageDeleteWorker.kt | 37 +-- .../worker/dm/DirectMessageFetchWorker.kt | 52 +---- .../worker/dm/DirectMessageSendWorker.kt | 190 +--------------- .../dm/TwitterDirectMessageSendWorker.kt | 69 +----- 9 files changed, 536 insertions(+), 329 deletions(-) create mode 100644 app/src/main/kotlin/com/twidere/twiderex/jobs/dm/DirectMessageDeleteJob.kt create mode 100644 app/src/main/kotlin/com/twidere/twiderex/jobs/dm/DirectMessageFetchJob.kt create mode 100644 app/src/main/kotlin/com/twidere/twiderex/jobs/dm/DirectMessageSendJob.kt create mode 100644 app/src/main/kotlin/com/twidere/twiderex/jobs/dm/TwitterDirectMessageSendJob.kt diff --git a/app/src/main/kotlin/com/twidere/twiderex/di/JobModule.kt b/app/src/main/kotlin/com/twidere/twiderex/di/JobModule.kt index 8668bd9f9..94f073f56 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/di/JobModule.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/di/JobModule.kt @@ -20,11 +20,18 @@ */ package com.twidere.twiderex.di +import android.content.ContentResolver import android.content.Context +import com.twidere.twiderex.db.CacheDatabase import com.twidere.twiderex.jobs.common.DownloadMediaJob import com.twidere.twiderex.jobs.common.NotificationJob import com.twidere.twiderex.jobs.common.ShareMediaJob import com.twidere.twiderex.jobs.database.DeleteDbStatusJob +import com.twidere.twiderex.jobs.dm.DirectMessageDeleteJob +import com.twidere.twiderex.jobs.dm.DirectMessageFetchJob +import com.twidere.twiderex.jobs.dm.TwitterDirectMessageSendJob +import com.twidere.twiderex.jobs.draft.RemoveDraftJob +import com.twidere.twiderex.jobs.draft.SaveDraftJob import com.twidere.twiderex.jobs.status.DeleteStatusJob import com.twidere.twiderex.jobs.status.LikeStatusJob import com.twidere.twiderex.jobs.status.MastodonVoteJob @@ -34,6 +41,8 @@ import com.twidere.twiderex.jobs.status.UnlikeStatusJob import com.twidere.twiderex.notification.AppNotificationManager import com.twidere.twiderex.notification.InAppNotification import com.twidere.twiderex.repository.AccountRepository +import com.twidere.twiderex.repository.DirectMessageRepository +import com.twidere.twiderex.repository.DraftRepository import com.twidere.twiderex.repository.NotificationRepository import com.twidere.twiderex.repository.StatusRepository import dagger.Module @@ -93,54 +102,107 @@ object JobModule { fun provideLikeStatusJob( accountRepository: AccountRepository, statusRepository: StatusRepository, - inAppNotification: InAppNotification, + inAppNotification: InAppNotification ): LikeStatusJob = LikeStatusJob( accountRepository = accountRepository, statusRepository = statusRepository, - inAppNotification = inAppNotification, + inAppNotification = inAppNotification ) @Provides fun provideRetweetStatusJob( accountRepository: AccountRepository, statusRepository: StatusRepository, - inAppNotification: InAppNotification, + inAppNotification: InAppNotification ): RetweetStatusJob = RetweetStatusJob( accountRepository = accountRepository, statusRepository = statusRepository, - inAppNotification = inAppNotification, + inAppNotification = inAppNotification ) @Provides fun provideUnlikeStatusJob( accountRepository: AccountRepository, statusRepository: StatusRepository, - inAppNotification: InAppNotification, + inAppNotification: InAppNotification ): UnlikeStatusJob = UnlikeStatusJob( accountRepository = accountRepository, statusRepository = statusRepository, - inAppNotification = inAppNotification, + inAppNotification = inAppNotification ) @Provides fun provideUnRetweetStatusJob( accountRepository: AccountRepository, statusRepository: StatusRepository, - inAppNotification: InAppNotification, + inAppNotification: InAppNotification ): UnRetweetStatusJob = UnRetweetStatusJob( accountRepository = accountRepository, statusRepository = statusRepository, - inAppNotification = inAppNotification, + inAppNotification = inAppNotification ) @Provides fun provideMastodonVoteJob( accountRepository: AccountRepository, statusRepository: StatusRepository, - inAppNotification: InAppNotification, + inAppNotification: InAppNotification ): MastodonVoteJob = MastodonVoteJob( accountRepository = accountRepository, statusRepository = statusRepository, - inAppNotification = inAppNotification, + inAppNotification = inAppNotification + ) + + @Provides + fun provideRemoveDraftJob( + repository: DraftRepository + ): RemoveDraftJob = RemoveDraftJob( + repository = repository, + ) + + @Provides + fun provideSaveDraftJob( + repository: DraftRepository, + inAppNotification: InAppNotification + ): SaveDraftJob = SaveDraftJob( + repository = repository, + inAppNotification = inAppNotification + ) + + @Provides + fun provideDirectMessageDeleteJob( + repository: DirectMessageRepository, + accountRepository: AccountRepository + ): DirectMessageDeleteJob = DirectMessageDeleteJob( + repository = repository, + accountRepository = accountRepository + ) + + @Provides + fun provideDirectMessageFetchJob( + @ApplicationContext context: Context, + repository: DirectMessageRepository, + accountRepository: AccountRepository, + notificationManager: AppNotificationManager, + ): DirectMessageFetchJob = DirectMessageFetchJob( + applicationContext = context, + repository = repository, + accountRepository = accountRepository, + notificationManager = notificationManager + ) + + @Provides + fun provideTwitterDirectMessageSendJob( + @ApplicationContext context: Context, + accountRepository: AccountRepository, + notificationManager: AppNotificationManager, + contentResolver: ContentResolver, + cacheDatabase: CacheDatabase, + ): TwitterDirectMessageSendJob = TwitterDirectMessageSendJob( + context = context, + accountRepository = accountRepository, + notificationManager = notificationManager, + contentResolver = contentResolver, + cacheDatabase = cacheDatabase ) } diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/dm/DirectMessageDeleteJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/dm/DirectMessageDeleteJob.kt new file mode 100644 index 000000000..c00fdd78f --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/dm/DirectMessageDeleteJob.kt @@ -0,0 +1,53 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.jobs.dm + +import com.twidere.services.microblog.DirectMessageService +import com.twidere.twiderex.model.DirectMessageDeleteData +import com.twidere.twiderex.model.MicroBlogKey +import com.twidere.twiderex.repository.AccountRepository +import com.twidere.twiderex.repository.DirectMessageRepository + +class DirectMessageDeleteJob( + private val repository: DirectMessageRepository, + private val accountRepository: AccountRepository, +) { + suspend fun execute(deleteData: DirectMessageDeleteData, accountKey: MicroBlogKey): Boolean { + return try { + val accountDetails = accountKey.let { + accountRepository.findByAccountKey(accountKey = it) + }?.let { + accountRepository.getAccountDetails(it) + } ?: return false + repository.deleteMessage( + accountKey = deleteData.accountKey, + conversationKey = deleteData.conversationKey, + messageId = deleteData.messageId, + messageKey = deleteData.messageKey, + service = accountDetails.service as DirectMessageService + ) + true + } catch (e: Throwable) { + e.printStackTrace() + false + } + } +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/dm/DirectMessageFetchJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/dm/DirectMessageFetchJob.kt new file mode 100644 index 000000000..2a9b4ba8b --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/dm/DirectMessageFetchJob.kt @@ -0,0 +1,78 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.jobs.dm + +import android.content.Context +import com.twidere.services.microblog.DirectMessageService +import com.twidere.services.microblog.LookupService +import com.twidere.twiderex.R +import com.twidere.twiderex.model.AccountDetails +import com.twidere.twiderex.model.ui.UiDMConversationWithLatestMessage +import com.twidere.twiderex.navigation.RootDeepLinksRoute +import com.twidere.twiderex.notification.AppNotification +import com.twidere.twiderex.notification.AppNotificationManager +import com.twidere.twiderex.notification.NotificationChannelSpec +import com.twidere.twiderex.notification.notificationChannelId +import com.twidere.twiderex.repository.AccountRepository +import com.twidere.twiderex.repository.DirectMessageRepository +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.firstOrNull + +class DirectMessageFetchJob( + private val applicationContext: Context, + private val repository: DirectMessageRepository, + private val accountRepository: AccountRepository, + private val notificationManager: AppNotificationManager, +) { + suspend fun execute(): Boolean { + return try { + accountRepository.activeAccount.firstOrNull()?.takeIf { + accountRepository.getAccountPreferences(it.accountKey).isNotificationEnabled.first() + }?.let { account -> + val result = repository.checkNewMessages( + accountKey = account.accountKey, + service = account.service as DirectMessageService, + lookupService = account.service as LookupService + ) + result.forEach { + notification(account = account, message = it) + } + } ?: throw Error() + true + } catch (e: Throwable) { + e.printStackTrace() + false + } + } + + private fun notification(account: AccountDetails, message: UiDMConversationWithLatestMessage) { + val builder = AppNotification + .Builder( + account.accountKey.notificationChannelId( + NotificationChannelSpec.ContentMessages.id + ) + ) + .setContentTitle(applicationContext.getString(R.string.common_notification_messages_title)) + .setContentText(applicationContext.getString(R.string.common_notification_messages_content, message.latestMessage.sender.displayName)) + .setDeepLink(RootDeepLinksRoute.Conversation(message.conversation.conversationKey)) + notificationManager.notify(message.latestMessage.messageKey.hashCode(), builder.build()) + } +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/dm/DirectMessageSendJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/dm/DirectMessageSendJob.kt new file mode 100644 index 000000000..0320148b9 --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/dm/DirectMessageSendJob.kt @@ -0,0 +1,213 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.jobs.dm + +import android.content.ContentResolver +import android.content.Context +import android.graphics.BitmapFactory +import android.net.Uri +import androidx.room.withTransaction +import com.twidere.services.microblog.MicroBlogService +import com.twidere.twiderex.R +import com.twidere.twiderex.db.CacheDatabase +import com.twidere.twiderex.db.model.DbDMEvent +import com.twidere.twiderex.db.model.DbDMEvent.Companion.saveToDb +import com.twidere.twiderex.db.model.DbDMEventWithAttachments +import com.twidere.twiderex.db.model.DbDMEventWithAttachments.Companion.saveToDb +import com.twidere.twiderex.db.model.DbMedia +import com.twidere.twiderex.model.AccountDetails +import com.twidere.twiderex.model.DirectMessageSendData +import com.twidere.twiderex.model.MediaType +import com.twidere.twiderex.model.MicroBlogKey +import com.twidere.twiderex.navigation.RootDeepLinksRoute +import com.twidere.twiderex.notification.AppNotification +import com.twidere.twiderex.notification.AppNotificationManager +import com.twidere.twiderex.notification.NotificationChannelSpec +import com.twidere.twiderex.notification.notificationChannelId +import com.twidere.twiderex.repository.AccountRepository +import java.util.UUID + +abstract class DirectMessageSendJob( + private val applicationContext: Context, + protected val cacheDatabase: CacheDatabase, + private val accountRepository: AccountRepository, + private val notificationManager: AppNotificationManager, + protected val contentResolver: ContentResolver, +) { + suspend fun execute(sendData: DirectMessageSendData, accountKey: MicroBlogKey): Boolean { + val accountDetails = accountKey.let { + accountRepository.findByAccountKey(accountKey = it) + }?.let { + accountRepository.getAccountDetails(it) + } ?: return false + val notificationId = sendData.draftMessageKey.hashCode() + @Suppress("UNCHECKED_CAST") + val service = try { + accountDetails.service as T + } catch (e: ClassCastException) { + return false + } + var draftEvent: DbDMEventWithAttachments? = null + return try { + val images = sendData.images.map { + Uri.parse(it) + } + draftEvent = getDraft(sendData, images, accountDetails) ?: throw IllegalArgumentException() + // val exifScrambler = ExifScrambler(context) + val mediaIds = arrayListOf() + + images.forEach { uri -> + // val scramblerUri = exifScrambler.removeExifData(uri) + // TODO FIXME 2020/6/30 Twitter DM throws bad media error after remove exif data from images + // + val id = uploadImage(uri, uri, service) + id?.let { mediaIds.add(it) } + // exifScrambler.deleteCacheFile(scramblerUri) + } + val dbEvent = sendMessage(service, sendData, mediaIds) + updateDb(draftEvent, dbEvent) + true + } catch (e: Throwable) { + e.printStackTrace() + draftEvent?.let { + cacheDatabase.directMessageDao() + .insertAll(listOf(draftEvent.message.copy(sendStatus = DbDMEvent.SendStatus.FAILED))) + } + val builder = AppNotification + .Builder( + accountDetails.accountKey.notificationChannelId( + NotificationChannelSpec.ContentMessages.id + ) + ) + .setContentTitle(applicationContext.getString(R.string.common_alerts_failed_to_send_message_message)) + .setContentText(sendData.text) + .setDeepLink(RootDeepLinksRoute.Conversation(sendData.conversationKey)) + notificationManager.notify(notificationId, builder.build()) + false + } + } + + private suspend fun updateDb(draftEvent: DbDMEventWithAttachments?, dbEvent: DbDMEventWithAttachments) { + cacheDatabase.withTransaction { + draftEvent?.let { + cacheDatabase.directMessageDao().delete( + it.message + ) + } + listOf(dbEvent).saveToDb(cacheDatabase) + } + } + + private suspend fun getDraft(sendData: DirectMessageSendData, images: List, account: AccountDetails): DbDMEventWithAttachments? { + return cacheDatabase.withTransaction { + cacheDatabase.directMessageDao().findWithMessageKey( + account.accountKey, + sendData.conversationKey, + sendData.draftMessageKey + )?.also { + cacheDatabase.directMessageDao().insertAll( + listOf(it.message.copy(sendStatus = DbDMEvent.SendStatus.PENDING)) + ) + } + } ?: saveDraft(sendData, images, account) + } + + private suspend fun saveDraft(sendData: DirectMessageSendData, images: List, account: AccountDetails): DbDMEventWithAttachments? { + return cacheDatabase.withTransaction { + val createTimeStamp = System.currentTimeMillis() + listOf( + DbDMEvent( + _id = UUID.randomUUID().toString(), + accountKey = account.accountKey, + sortId = createTimeStamp, + conversationKey = sendData.conversationKey, + messageId = sendData.draftMessageKey.id, + messageKey = sendData.draftMessageKey, + htmlText = autoLink(sendData.text ?: ""), + originText = sendData.text ?: "", + createdTimestamp = createTimeStamp, + messageType = "message_create", + senderAccountKey = account.accountKey, + recipientAccountKey = sendData.recipientUserKey, + sendStatus = DbDMEvent.SendStatus.PENDING + ) + ).saveToDb(cacheDatabase) + cacheDatabase.mediaDao().insertAll( + images.mapIndexed { index, uri -> + val imageSize = getImageSize(uri.path) + DbMedia( + _id = UUID.randomUUID().toString(), + belongToKey = sendData.draftMessageKey, + url = uri.toString(), + mediaUrl = uri.toString(), + previewUrl = uri.toString(), + type = getMediaType(uri), + width = imageSize[0], + height = imageSize[1], + altText = "", + order = index, + pageUrl = null, + ) + } + ) + cacheDatabase.directMessageDao().findWithMessageKey( + account.accountKey, + sendData.conversationKey, + sendData.draftMessageKey + ) + } + } + + private fun getMediaType(uri: Uri): MediaType { + val type = contentResolver.getType(uri) ?: "" + return when { + type.startsWith("image") -> MediaType.photo + type.startsWith("video") -> MediaType.video + else -> MediaType.other + } + } + + private fun getImageSize(path: String?): Array { + return path?.let { + val options = BitmapFactory.Options() + options.inJustDecodeBounds = true + BitmapFactory.decodeFile(it, options) + arrayOf( + options.outWidth.toLong(), + options.outHeight.toLong() + ) + } ?: arrayOf(0, 0) + } + + protected abstract suspend fun sendMessage( + service: T, + sendData: DirectMessageSendData, + mediaIds: ArrayList + ): DbDMEventWithAttachments + + protected abstract suspend fun uploadImage( + originUri: Uri, + scramblerUri: Uri, + service: T + ): String? + + protected abstract suspend fun autoLink(text: String): String +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/dm/TwitterDirectMessageSendJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/dm/TwitterDirectMessageSendJob.kt new file mode 100644 index 000000000..9b10ace18 --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/dm/TwitterDirectMessageSendJob.kt @@ -0,0 +1,91 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.jobs.dm + +import android.content.ContentResolver +import android.content.Context +import android.net.Uri +import com.twidere.services.microblog.LookupService +import com.twidere.services.twitter.TwitterService +import com.twidere.twiderex.db.CacheDatabase +import com.twidere.twiderex.db.mapper.autolink +import com.twidere.twiderex.db.mapper.toDbDirectMessage +import com.twidere.twiderex.db.mapper.toDbUser +import com.twidere.twiderex.db.model.DbDMEventWithAttachments +import com.twidere.twiderex.db.model.DbUser +import com.twidere.twiderex.model.DirectMessageSendData +import com.twidere.twiderex.model.MicroBlogKey +import com.twidere.twiderex.notification.AppNotificationManager +import com.twidere.twiderex.repository.AccountRepository + +class TwitterDirectMessageSendJob( + context: Context, + accountRepository: AccountRepository, + notificationManager: AppNotificationManager, + contentResolver: ContentResolver, + cacheDatabase: CacheDatabase, +) : DirectMessageSendJob( + context, cacheDatabase, accountRepository, notificationManager, contentResolver +) { + + override suspend fun sendMessage( + service: TwitterService, + sendData: DirectMessageSendData, + mediaIds: ArrayList + ): DbDMEventWithAttachments = service.sendDirectMessage( + recipientId = sendData.recipientUserKey.id, + text = sendData.text, + attachmentType = "media", + mediaId = mediaIds.firstOrNull() + )?.toDbDirectMessage( + accountKey = sendData.accountKey, + sender = lookUpUser(cacheDatabase, sendData.accountKey, service) + ) ?: throw Error() + + private suspend fun lookUpUser(database: CacheDatabase, userKey: MicroBlogKey, service: TwitterService): DbUser { + return database.userDao().findWithUserKey(userKey) ?: let { + val user = (service as LookupService).lookupUser(userKey.id) + .toDbUser(userKey) + database.userDao().insertAll(listOf(user)) + user + } + } + + override suspend fun uploadImage( + originUri: Uri, + scramblerUri: Uri, + service: TwitterService + ): String? { + val type = contentResolver.getType(originUri) + val size = contentResolver.openFileDescriptor(scramblerUri, "r")?.statSize + return contentResolver.openInputStream(scramblerUri)?.use { + service.uploadFile( + it, + type ?: "image/*", + size ?: it.available().toLong() + ) + } ?: throw Error() + } + + override suspend fun autoLink(text: String): String { + return autolink.autoLink(text) + } +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageDeleteWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageDeleteWorker.kt index 7112f2457..6a8553888 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageDeleteWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageDeleteWorker.kt @@ -25,13 +25,12 @@ import androidx.hilt.work.HiltWorker import androidx.work.CoroutineWorker import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkerParameters -import com.twidere.services.microblog.DirectMessageService +import com.twidere.twiderex.extensions.toWorkResult +import com.twidere.twiderex.jobs.dm.DirectMessageDeleteJob import com.twidere.twiderex.model.DirectMessageDeleteData import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.toDirectMessageDeleteData import com.twidere.twiderex.model.toWorkData -import com.twidere.twiderex.repository.AccountRepository -import com.twidere.twiderex.repository.DirectMessageRepository import dagger.assisted.Assisted import dagger.assisted.AssistedInject @@ -39,8 +38,7 @@ import dagger.assisted.AssistedInject class DirectMessageDeleteWorker @AssistedInject constructor( @Assisted context: Context, @Assisted workerParams: WorkerParameters, - private val repository: DirectMessageRepository, - private val accountRepository: AccountRepository, + private val deleteJob: DirectMessageDeleteJob ) : CoroutineWorker( context, workerParams @@ -54,26 +52,13 @@ class DirectMessageDeleteWorker @AssistedInject constructor( } override suspend fun doWork(): Result { - return try { - val deleteData = inputData.toDirectMessageDeleteData() - val accountDetails = inputData.getString("accountKey")?.let { - MicroBlogKey.valueOf(it) - }?.let { - accountRepository.findByAccountKey(accountKey = it) - }?.let { - accountRepository.getAccountDetails(it) - } ?: return Result.failure() - repository.deleteMessage( - accountKey = deleteData.accountKey, - conversationKey = deleteData.conversationKey, - messageId = deleteData.messageId, - messageKey = deleteData.messageKey, - service = accountDetails.service as DirectMessageService - ) - Result.success() - } catch (e: Throwable) { - e.printStackTrace() - Result.failure() - } + val deleteData = inputData.toDirectMessageDeleteData() + val accountKey = inputData.getString("accountKey")?.let { + MicroBlogKey.valueOf(it) + } ?: return Result.failure() + return deleteJob.execute( + deleteData = deleteData, + accountKey = accountKey + ).toWorkResult() } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageFetchWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageFetchWorker.kt index ed36e2e19..adfd31138 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageFetchWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageFetchWorker.kt @@ -25,31 +25,17 @@ import androidx.hilt.work.HiltWorker import androidx.work.CoroutineWorker import androidx.work.PeriodicWorkRequestBuilder import androidx.work.WorkerParameters -import com.twidere.services.microblog.DirectMessageService -import com.twidere.services.microblog.LookupService -import com.twidere.twiderex.R -import com.twidere.twiderex.model.AccountDetails -import com.twidere.twiderex.model.ui.UiDMConversationWithLatestMessage -import com.twidere.twiderex.navigation.RootDeepLinksRoute -import com.twidere.twiderex.notification.AppNotification -import com.twidere.twiderex.notification.AppNotificationManager -import com.twidere.twiderex.notification.NotificationChannelSpec -import com.twidere.twiderex.notification.notificationChannelId -import com.twidere.twiderex.repository.AccountRepository -import com.twidere.twiderex.repository.DirectMessageRepository +import com.twidere.twiderex.extensions.toWorkResult +import com.twidere.twiderex.jobs.dm.DirectMessageFetchJob import dagger.assisted.Assisted import dagger.assisted.AssistedInject -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.firstOrNull import java.util.concurrent.TimeUnit @HiltWorker class DirectMessageFetchWorker @AssistedInject constructor( @Assisted context: Context, @Assisted workerParams: WorkerParameters, - private val repository: DirectMessageRepository, - private val accountRepository: AccountRepository, - private val notificationManager: AppNotificationManager, + private val directMessageFetchJob: DirectMessageFetchJob ) : CoroutineWorker( context, workerParams @@ -60,36 +46,6 @@ class DirectMessageFetchWorker @AssistedInject constructor( } override suspend fun doWork(): Result { - return try { - accountRepository.activeAccount.firstOrNull()?.takeIf { - accountRepository.getAccountPreferences(it.accountKey).isNotificationEnabled.first() - }?.let { account -> - val result = repository.checkNewMessages( - accountKey = account.accountKey, - service = account.service as DirectMessageService, - lookupService = account.service as LookupService - ) - result.forEach { - notification(account = account, message = it) - } - } ?: throw Error() - Result.success() - } catch (e: Throwable) { - e.printStackTrace() - Result.failure() - } - } - - private fun notification(account: AccountDetails, message: UiDMConversationWithLatestMessage) { - val builder = AppNotification - .Builder( - account.accountKey.notificationChannelId( - NotificationChannelSpec.ContentMessages.id - ) - ) - .setContentTitle(applicationContext.getString(R.string.common_notification_messages_title)) - .setContentText(applicationContext.getString(R.string.common_notification_messages_content, message.latestMessage.sender.displayName)) - .setDeepLink(RootDeepLinksRoute.Conversation(message.conversation.conversationKey)) - notificationManager.notify(message.latestMessage.messageKey.hashCode(), builder.build()) + return directMessageFetchJob.execute().toWorkResult() } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageSendWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageSendWorker.kt index a38f6fc89..9946ac68b 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageSendWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageSendWorker.kt @@ -20,41 +20,18 @@ */ package com.twidere.twiderex.worker.dm -import android.content.ContentResolver import android.content.Context -import android.graphics.BitmapFactory -import android.net.Uri -import androidx.room.withTransaction import androidx.work.CoroutineWorker import androidx.work.WorkerParameters -import com.twidere.services.microblog.MicroBlogService -import com.twidere.twiderex.R -import com.twidere.twiderex.db.CacheDatabase -import com.twidere.twiderex.db.model.DbDMEvent -import com.twidere.twiderex.db.model.DbDMEvent.Companion.saveToDb -import com.twidere.twiderex.db.model.DbDMEventWithAttachments -import com.twidere.twiderex.db.model.DbDMEventWithAttachments.Companion.saveToDb -import com.twidere.twiderex.db.model.DbMedia -import com.twidere.twiderex.model.AccountDetails -import com.twidere.twiderex.model.DirectMessageSendData -import com.twidere.twiderex.model.MediaType +import com.twidere.twiderex.extensions.toWorkResult +import com.twidere.twiderex.jobs.dm.DirectMessageSendJob import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.toDirectMessageSendData -import com.twidere.twiderex.navigation.RootDeepLinksRoute -import com.twidere.twiderex.notification.AppNotification -import com.twidere.twiderex.notification.AppNotificationManager -import com.twidere.twiderex.notification.NotificationChannelSpec -import com.twidere.twiderex.notification.notificationChannelId -import com.twidere.twiderex.repository.AccountRepository -import java.util.UUID -abstract class DirectMessageSendWorker( - protected val context: Context, +abstract class DirectMessageSendWorker( + context: Context, workerParams: WorkerParameters, - protected val cacheDatabase: CacheDatabase, - protected val contentResolver: ContentResolver, - private val accountRepository: AccountRepository, - private val notificationManager: AppNotificationManager, + private val directMessageSendJob: DirectMessageSendJob<*>, ) : CoroutineWorker( context, workerParams @@ -62,159 +39,12 @@ abstract class DirectMessageSendWorker( override suspend fun doWork(): Result { val sendData = inputData.toDirectMessageSendData() - val accountDetails = inputData.getString("accountKey")?.let { + val accountKey = inputData.getString("accountKey")?.let { MicroBlogKey.valueOf(it) - }?.let { - accountRepository.findByAccountKey(accountKey = it) - }?.let { - accountRepository.getAccountDetails(it) } ?: return Result.failure() - val notificationId = sendData.draftMessageKey.hashCode() - @Suppress("UNCHECKED_CAST") - val service = accountDetails.service as T - var draftEvent: DbDMEventWithAttachments? = null - return try { - val images = sendData.images.map { - Uri.parse(it) - } - draftEvent = getDraft(sendData, images, accountDetails) ?: throw IllegalArgumentException() - // val exifScrambler = ExifScrambler(context) - val mediaIds = arrayListOf() - - images.forEach { uri -> - // val scramblerUri = exifScrambler.removeExifData(uri) - // TODO FIXME 2020/6/30 Twitter DM throws bad media error after remove exif data from images - // - val id = uploadImage(uri, uri, service) - id?.let { mediaIds.add(it) } - // exifScrambler.deleteCacheFile(scramblerUri) - } - val dbEvent = sendMessage(service, sendData, mediaIds) - updateDb(draftEvent, dbEvent) - Result.success() - } catch (e: Throwable) { - e.printStackTrace() - draftEvent?.let { - cacheDatabase.directMessageDao() - .insertAll(listOf(draftEvent.message.copy(sendStatus = DbDMEvent.SendStatus.FAILED))) - } - val builder = AppNotification - .Builder( - accountDetails.accountKey.notificationChannelId( - NotificationChannelSpec.ContentMessages.id - ) - ) - .setContentTitle(applicationContext.getString(R.string.common_alerts_failed_to_send_message_message)) - .setContentText(sendData.text) - .setDeepLink(RootDeepLinksRoute.Conversation(sendData.conversationKey)) - notificationManager.notify(notificationId, builder.build()) - Result.failure() - } - } - - private suspend fun updateDb(draftEvent: DbDMEventWithAttachments?, dbEvent: DbDMEventWithAttachments) { - cacheDatabase.withTransaction { - draftEvent?.let { - cacheDatabase.directMessageDao().delete( - it.message - ) - } - listOf(dbEvent).saveToDb(cacheDatabase) - } - } - - private suspend fun getDraft(sendData: DirectMessageSendData, images: List, account: AccountDetails): DbDMEventWithAttachments? { - return cacheDatabase.withTransaction { - cacheDatabase.directMessageDao().findWithMessageKey( - account.accountKey, - sendData.conversationKey, - sendData.draftMessageKey - )?.also { - cacheDatabase.directMessageDao().insertAll( - listOf(it.message.copy(sendStatus = DbDMEvent.SendStatus.PENDING)) - ) - } - } ?: saveDraft(sendData, images, account) - } - - private suspend fun saveDraft(sendData: DirectMessageSendData, images: List, account: AccountDetails): DbDMEventWithAttachments? { - return cacheDatabase.withTransaction { - val createTimeStamp = System.currentTimeMillis() - listOf( - DbDMEvent( - _id = UUID.randomUUID().toString(), - accountKey = account.accountKey, - sortId = createTimeStamp, - conversationKey = sendData.conversationKey, - messageId = sendData.draftMessageKey.id, - messageKey = sendData.draftMessageKey, - htmlText = autoLink(sendData.text ?: ""), - originText = sendData.text ?: "", - createdTimestamp = createTimeStamp, - messageType = "message_create", - senderAccountKey = account.accountKey, - recipientAccountKey = sendData.recipientUserKey, - sendStatus = DbDMEvent.SendStatus.PENDING - ) - ).saveToDb(cacheDatabase) - cacheDatabase.mediaDao().insertAll( - images.mapIndexed { index, uri -> - val imageSize = getImageSize(uri.path) - DbMedia( - _id = UUID.randomUUID().toString(), - belongToKey = sendData.draftMessageKey, - url = uri.toString(), - mediaUrl = uri.toString(), - previewUrl = uri.toString(), - type = getMediaType(uri), - width = imageSize[0], - height = imageSize[1], - altText = "", - order = index, - pageUrl = null, - ) - } - ) - cacheDatabase.directMessageDao().findWithMessageKey( - account.accountKey, - sendData.conversationKey, - sendData.draftMessageKey - ) - } + return directMessageSendJob.execute( + sendData = sendData, + accountKey = accountKey + ).toWorkResult() } - - private fun getMediaType(uri: Uri): MediaType { - val type = contentResolver.getType(uri) ?: "" - return when { - type.startsWith("image") -> MediaType.photo - type.startsWith("video") -> MediaType.video - else -> MediaType.other - } - } - - private fun getImageSize(path: String?): Array { - return path?.let { - val options = BitmapFactory.Options() - options.inJustDecodeBounds = true - BitmapFactory.decodeFile(it, options) - arrayOf( - options.outWidth.toLong(), - options.outHeight.toLong() - ) - } ?: arrayOf(0, 0) - } - - protected abstract suspend fun sendMessage( - service: T, - sendData: DirectMessageSendData, - mediaIds: ArrayList - ): DbDMEventWithAttachments - - protected abstract suspend fun uploadImage( - originUri: Uri, - scramblerUri: Uri, - service: T - ): String? - - protected abstract suspend fun autoLink(text: String): String } diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/dm/TwitterDirectMessageSendWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/dm/TwitterDirectMessageSendWorker.kt index 8c1f4b396..18a68b3ed 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/dm/TwitterDirectMessageSendWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/dm/TwitterDirectMessageSendWorker.kt @@ -20,26 +20,14 @@ */ package com.twidere.twiderex.worker.dm -import android.content.ContentResolver import android.content.Context -import android.net.Uri import androidx.hilt.work.HiltWorker import androidx.work.Data import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkerParameters -import com.twidere.services.microblog.LookupService -import com.twidere.services.twitter.TwitterService -import com.twidere.twiderex.db.CacheDatabase -import com.twidere.twiderex.db.mapper.autolink -import com.twidere.twiderex.db.mapper.toDbDirectMessage -import com.twidere.twiderex.db.mapper.toDbUser -import com.twidere.twiderex.db.model.DbDMEventWithAttachments -import com.twidere.twiderex.db.model.DbUser +import com.twidere.twiderex.jobs.dm.TwitterDirectMessageSendJob import com.twidere.twiderex.model.DirectMessageSendData -import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.toWorkData -import com.twidere.twiderex.notification.AppNotificationManager -import com.twidere.twiderex.repository.AccountRepository import dagger.assisted.Assisted import dagger.assisted.AssistedInject @@ -47,17 +35,11 @@ import dagger.assisted.AssistedInject class TwitterDirectMessageSendWorker @AssistedInject constructor( @Assisted context: Context, @Assisted workerParams: WorkerParameters, - accountRepository: AccountRepository, - notificationManager: AppNotificationManager, - contentResolver: ContentResolver, - cacheDatabase: CacheDatabase, -) : DirectMessageSendWorker( + twitterDirectMessageSendJob: TwitterDirectMessageSendJob +) : DirectMessageSendWorker( context, workerParams, - cacheDatabase, - contentResolver, - accountRepository, - notificationManager + twitterDirectMessageSendJob ) { companion object { fun create( @@ -70,47 +52,4 @@ class TwitterDirectMessageSendWorker @AssistedInject constructor( ) .build() } - - override suspend fun sendMessage( - service: TwitterService, - sendData: DirectMessageSendData, - mediaIds: ArrayList - ): DbDMEventWithAttachments = service.sendDirectMessage( - recipientId = sendData.recipientUserKey.id, - text = sendData.text, - attachmentType = "media", - mediaId = mediaIds.firstOrNull() - )?.toDbDirectMessage( - accountKey = sendData.accountKey, - sender = lookUpUser(cacheDatabase, sendData.accountKey, service) - ) ?: throw Error() - - private suspend fun lookUpUser(database: CacheDatabase, userKey: MicroBlogKey, service: TwitterService): DbUser { - return database.userDao().findWithUserKey(userKey) ?: let { - val user = (service as LookupService).lookupUser(userKey.id) - .toDbUser(userKey) - database.userDao().insertAll(listOf(user)) - user - } - } - - override suspend fun uploadImage( - originUri: Uri, - scramblerUri: Uri, - service: TwitterService - ): String? { - val type = contentResolver.getType(originUri) - val size = contentResolver.openFileDescriptor(scramblerUri, "r")?.statSize - return contentResolver.openInputStream(scramblerUri)?.use { - service.uploadFile( - it, - type ?: "image/*", - size ?: it.available().toLong() - ) - } ?: throw Error() - } - - override suspend fun autoLink(text: String): String { - return autolink.autoLink(text) - } } From d4e8e77abb132080a1256eb08e4cc61bc9062022 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Tue, 27 Jul 2021 17:21:16 +0800 Subject: [PATCH 083/137] add dialog transition --- .../foundation/navigation/NavHost.kt | 6 +- .../transition/AnimatedDialogRoute.kt | 131 ++++++++++++++++++ .../navigation/transition/AnimatedRoute.kt | 4 +- 3 files changed, 138 insertions(+), 3 deletions(-) create mode 100644 app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/transition/AnimatedDialogRoute.kt diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/NavHost.kt b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/NavHost.kt index 09a3fc2bc..237e48e0d 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/NavHost.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/NavHost.kt @@ -29,6 +29,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveableStateHolder import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner +import moe.tlaster.precompose.navigation.transition.AnimatedDialogRoute import moe.tlaster.precompose.navigation.transition.AnimatedRoute import moe.tlaster.precompose.navigation.transition.NavTransition @@ -106,7 +107,10 @@ fun NavHost( } } } - routeStack.stacks.forEach { + AnimatedDialogRoute( + stack = routeStack, + navTransition = navTransition, + ) { stateHolder.SaveableStateProvider(it.id) { CompositionLocalProvider( LocalViewModelStoreOwner provides it, diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/transition/AnimatedDialogRoute.kt b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/transition/AnimatedDialogRoute.kt new file mode 100644 index 000000000..abf2a5b8d --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/transition/AnimatedDialogRoute.kt @@ -0,0 +1,131 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package moe.tlaster.precompose.navigation.transition + +import androidx.compose.animation.core.FiniteAnimationSpec +import androidx.compose.animation.core.MutableTransitionState +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.tween +import androidx.compose.animation.core.updateTransition +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.key +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.graphicsLayer +import androidx.lifecycle.Lifecycle +import moe.tlaster.precompose.navigation.BackStackEntry +import moe.tlaster.precompose.navigation.RouteStack + +@Composable +internal fun AnimatedDialogRoute( + stack: RouteStack, + modifier: Modifier = Modifier, + animationSpec: FiniteAnimationSpec = tween(), + navTransition: NavTransition = remember { NavTransition() }, + content: @Composable (BackStackEntry) -> Unit +) { + + val items = remember { mutableStateListOf>() } + val stacks = stack.stacks + val targetState = remember(stack.stacks.size) { + stack.currentEntry + } + val transitionState = remember { MutableTransitionState(targetState) } + val targetChanged = (targetState != transitionState.targetState) + val previousState = transitionState.targetState + transitionState.targetState = targetState + val transition = updateTransition(transitionState, label = "AnimatedDialogRouteTransition") + + if (targetChanged || items.isEmpty()) { + val indexOfNew = stacks.indexOf(targetState).takeIf { it >= 0 } ?: Int.MAX_VALUE + val indexOfOld = stacks.indexOf(previousState) + .takeIf { + it >= 0 || + // Workaround for navOptions + targetState?.lifecycle?.currentState == Lifecycle.State.INITIALIZED && + previousState?.lifecycle?.currentState == Lifecycle.State.RESUMED + } ?: Int.MAX_VALUE + // Only manipulate the list when the state is changed, or in the first run. + val keys = items.map { + val type = if (indexOfNew >= indexOfOld) AnimateType.Pause else AnimateType.Destroy + it.key to type + }.toMap().run { + toMutableMap().also { + val type = if (indexOfNew >= indexOfOld) { + AnimateType.Create + } else { + AnimateType.Resume + } + if (targetState != null) { + it[targetState] = type + } + } + } + items.clear() + keys.mapTo(items) { (key, value) -> + AnimatedRouteItem(key, value) { + val factor by transition.animateFloat( + transitionSpec = { animationSpec } + ) { if (it == key) 1f else 0f } + Box( + Modifier.graphicsLayer { + when (value) { + AnimateType.Create -> navTransition.createTransition.invoke( + this, + factor + ) + AnimateType.Destroy -> navTransition.destroyTransition.invoke( + this, + factor + ) + else -> Unit + // AnimateType.Pause -> actualNavTransition.pauseTransition.invoke(this, factor) + // AnimateType.Resume -> actualNavTransition.resumeTransition.invoke(this, factor) + } + } + ) { + content(key) + } + } + }.sortByDescending { it.animateType } + } else if (transitionState.currentState == transitionState.targetState) { + // Remove all the intermediate items from the list once the animation is finished. + items.removeAll { it.animateType == AnimateType.Destroy } + } + + Box(modifier) { + for (index in items.indices) { + val item = items[index] + key(item.key) { + Box( + modifier = Modifier + .fillMaxSize() + ) { + item.content.invoke() + } + } + } + } +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/transition/AnimatedRoute.kt b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/transition/AnimatedRoute.kt index 3fe4fe839..da60842fc 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/transition/AnimatedRoute.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/transition/AnimatedRoute.kt @@ -118,14 +118,14 @@ internal fun AnimatedRoute( } } -private enum class AnimateType { +internal enum class AnimateType { Create, Destroy, Pause, Resume, } -private data class AnimatedRouteItem( +internal data class AnimatedRouteItem( val key: T, val animateType: AnimateType, val content: @Composable () -> Unit From 31646b0fe320dae973212ac3cf1cdcc20feabdb7 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Tue, 27 Jul 2021 17:32:17 +0800 Subject: [PATCH 084/137] add dialog transition --- .../foundation/navigation/NavHost.kt | 4 +- .../transition/AnimatedDialogRoute.kt | 8 ++-- .../navigation/transition/DialogTransition.kt | 41 +++++++++++++++++++ 3 files changed, 47 insertions(+), 6 deletions(-) create mode 100644 app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/transition/DialogTransition.kt diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/NavHost.kt b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/NavHost.kt index 237e48e0d..1a9ccae8a 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/NavHost.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/NavHost.kt @@ -31,6 +31,7 @@ import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner import moe.tlaster.precompose.navigation.transition.AnimatedDialogRoute import moe.tlaster.precompose.navigation.transition.AnimatedRoute +import moe.tlaster.precompose.navigation.transition.DialogTransition import moe.tlaster.precompose.navigation.transition.NavTransition /** @@ -52,6 +53,7 @@ fun NavHost( navController: NavController, initialRoute: String, navTransition: NavTransition = remember { NavTransition() }, + dialogTransition: DialogTransition = remember { DialogTransition() }, builder: RouteBuilder.() -> Unit, ) { val stateHolder = rememberSaveableStateHolder() @@ -109,7 +111,7 @@ fun NavHost( } AnimatedDialogRoute( stack = routeStack, - navTransition = navTransition, + dialogTransition = dialogTransition, ) { stateHolder.SaveableStateProvider(it.id) { CompositionLocalProvider( diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/transition/AnimatedDialogRoute.kt b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/transition/AnimatedDialogRoute.kt index abf2a5b8d..3ec5c208d 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/transition/AnimatedDialogRoute.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/transition/AnimatedDialogRoute.kt @@ -43,7 +43,7 @@ internal fun AnimatedDialogRoute( stack: RouteStack, modifier: Modifier = Modifier, animationSpec: FiniteAnimationSpec = tween(), - navTransition: NavTransition = remember { NavTransition() }, + dialogTransition: DialogTransition = remember { DialogTransition() }, content: @Composable (BackStackEntry) -> Unit ) { @@ -92,17 +92,15 @@ internal fun AnimatedDialogRoute( Box( Modifier.graphicsLayer { when (value) { - AnimateType.Create -> navTransition.createTransition.invoke( + AnimateType.Create -> dialogTransition.createTransition.invoke( this, factor ) - AnimateType.Destroy -> navTransition.destroyTransition.invoke( + AnimateType.Destroy -> dialogTransition.destroyTransition.invoke( this, factor ) else -> Unit - // AnimateType.Pause -> actualNavTransition.pauseTransition.invoke(this, factor) - // AnimateType.Resume -> actualNavTransition.resumeTransition.invoke(this, factor) } } ) { diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/transition/DialogTransition.kt b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/transition/DialogTransition.kt new file mode 100644 index 000000000..ec5a425ee --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/transition/DialogTransition.kt @@ -0,0 +1,41 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package moe.tlaster.precompose.navigation.transition + +import androidx.compose.ui.graphics.GraphicsLayerScope + +val fadeCreateTransition: GraphicsLayerScope.(factor: Float) -> Unit = { factor -> + alpha = factor +} +val fadeDestroyTransition: GraphicsLayerScope.(factor: Float) -> Unit = { factor -> + alpha = factor +} + +data class DialogTransition( + /** + * Transition the scene that about to appear for the first time, similar to activity onCreate, factor from 0.0 to 1.0 + */ + val createTransition: GraphicsLayerScope.(factor: Float) -> Unit = fadeCreateTransition, + /** + * Transition the scene that about to disappear forever, similar to activity onDestroy, factor from 1.0 to 0.0 + */ + val destroyTransition: GraphicsLayerScope.(factor: Float) -> Unit = fadeDestroyTransition, +) From bdc9cc617082ce040e97f42d51b33ab088de18ae Mon Sep 17 00:00:00 2001 From: Tlaster Date: Tue, 27 Jul 2021 17:58:36 +0800 Subject: [PATCH 085/137] convertValue now return nullable --- .../foundation/navigation/BackStackEntry.kt | 14 +++++++------- .../component/foundation/navigation/QueryString.kt | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/BackStackEntry.kt b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/BackStackEntry.kt index 399487195..2442d6629 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/BackStackEntry.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/BackStackEntry.kt @@ -82,19 +82,19 @@ inline fun BackStackEntry.query(name: String, default: T? = null): T return queryString?.query(name, default) } -inline fun BackStackEntry.queryList(name: String): List { +inline fun BackStackEntry.queryList(name: String): List { val value = queryString?.map?.get(name) ?: return emptyList() return value.map { convertValue(it) } } -inline fun convertValue(value: String): T { +inline fun convertValue(value: String): T? { return when (T::class) { - Int::class -> value.toInt() - Long::class -> value.toLong() + Int::class -> value.toIntOrNull() + Long::class -> value.toLongOrNull() String::class -> value - Boolean::class -> value.toBoolean() - Float::class -> value.toFloat() - Double::class -> value.toDouble() + Boolean::class -> value.toBooleanStrictOrNull() + Float::class -> value.toFloatOrNull() + Double::class -> value.toDoubleOrNull() else -> throw NotImplementedError() } as T } diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/QueryString.kt b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/QueryString.kt index b4a9ec981..a93ddf77d 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/QueryString.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/foundation/navigation/QueryString.kt @@ -43,7 +43,7 @@ inline fun QueryString.query(name: String, default: T? = null): T? { return convertValue(value) } -inline fun QueryString.queryList(name: String): List { +inline fun QueryString.queryList(name: String): List { val value = map[name] ?: return emptyList() return value.map { convertValue(it) } } From b62eae027004bebb9c59d28192776c364458aae3 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Tue, 27 Jul 2021 18:03:47 +0800 Subject: [PATCH 086/137] default to null for switch item describe --- .../com/twidere/twiderex/component/settings/SwitchItem.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/settings/SwitchItem.kt b/app/src/main/kotlin/com/twidere/twiderex/component/settings/SwitchItem.kt index 29148a38a..d78ab5add 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/settings/SwitchItem.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/settings/SwitchItem.kt @@ -33,7 +33,7 @@ import com.twidere.twiderex.component.foundation.ColoredSwitch fun ColumnScope.switchItem( value: Boolean, onChanged: (Boolean) -> Unit, - describe: @Composable () -> Unit = {}, + describe: @Composable (() -> Unit)? = null, title: @Composable () -> Unit, ) { ListItem( From 0cc8743770d1bcca3c33da46d7c25beae1a5df79 Mon Sep 17 00:00:00 2001 From: itsMimao Date: Tue, 27 Jul 2021 18:05:01 +0800 Subject: [PATCH 087/137] abstract android base util for jobs --- .../com/twidere/twiderex/di/JobModule.kt | 50 ++++++- .../com/twidere/twiderex/di/TwidereModule.kt | 24 +++- .../twiderex/extensions/ContextExtensions.kt | 6 +- .../twiderex/jobs/compose/ComposeJob.kt | 123 ++++++++++++++++++ .../jobs/compose/MastodonComposeJob.kt | 98 ++++++++++++++ .../jobs/compose/TwitterComposeJob.kt | 103 +++++++++++++++ .../twiderex/jobs/dm/DirectMessageSendJob.kt | 24 ++-- .../jobs/dm/TwitterDirectMessageSendJob.kt | 17 ++- .../com/twidere/twiderex/kmp/ExifScrambler.kt | 27 ++++ .../com/twidere/twiderex/kmp/FileResolver.kt | 31 +++++ .../twidere/twiderex/kmp/RemoteNavigator.kt | 29 +++++ .../android/AndroidExifScrambler.kt} | 18 +-- .../kmp/android/AndroidFileResolver.kt | 40 ++++++ .../android/AndroidNotificationManager.kt | 2 +- .../kmp/android/AndroidRemoteNavigator.kt | 56 ++++++++ .../twiderex/worker/compose/ComposeWorker.kt | 100 ++------------ .../worker/compose/MastodonComposeWorker.kt | 64 +-------- .../worker/compose/TwitterComposeWorker.kt | 76 +---------- 18 files changed, 625 insertions(+), 263 deletions(-) create mode 100644 app/src/main/kotlin/com/twidere/twiderex/jobs/compose/ComposeJob.kt create mode 100644 app/src/main/kotlin/com/twidere/twiderex/jobs/compose/MastodonComposeJob.kt create mode 100644 app/src/main/kotlin/com/twidere/twiderex/jobs/compose/TwitterComposeJob.kt create mode 100644 app/src/main/kotlin/com/twidere/twiderex/kmp/ExifScrambler.kt create mode 100644 app/src/main/kotlin/com/twidere/twiderex/kmp/FileResolver.kt create mode 100644 app/src/main/kotlin/com/twidere/twiderex/kmp/RemoteNavigator.kt rename app/src/main/kotlin/com/twidere/twiderex/{utils/ExifScrambler.kt => kmp/android/AndroidExifScrambler.kt} (88%) create mode 100644 app/src/main/kotlin/com/twidere/twiderex/kmp/android/AndroidFileResolver.kt rename app/src/main/kotlin/com/twidere/twiderex/{notification => kmp}/android/AndroidNotificationManager.kt (98%) create mode 100644 app/src/main/kotlin/com/twidere/twiderex/kmp/android/AndroidRemoteNavigator.kt diff --git a/app/src/main/kotlin/com/twidere/twiderex/di/JobModule.kt b/app/src/main/kotlin/com/twidere/twiderex/di/JobModule.kt index 94f073f56..08a19dfc4 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/di/JobModule.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/di/JobModule.kt @@ -20,12 +20,13 @@ */ package com.twidere.twiderex.di -import android.content.ContentResolver import android.content.Context import com.twidere.twiderex.db.CacheDatabase import com.twidere.twiderex.jobs.common.DownloadMediaJob import com.twidere.twiderex.jobs.common.NotificationJob import com.twidere.twiderex.jobs.common.ShareMediaJob +import com.twidere.twiderex.jobs.compose.MastodonComposeJob +import com.twidere.twiderex.jobs.compose.TwitterComposeJob import com.twidere.twiderex.jobs.database.DeleteDbStatusJob import com.twidere.twiderex.jobs.dm.DirectMessageDeleteJob import com.twidere.twiderex.jobs.dm.DirectMessageFetchJob @@ -38,6 +39,9 @@ import com.twidere.twiderex.jobs.status.MastodonVoteJob import com.twidere.twiderex.jobs.status.RetweetStatusJob import com.twidere.twiderex.jobs.status.UnRetweetStatusJob import com.twidere.twiderex.jobs.status.UnlikeStatusJob +import com.twidere.twiderex.kmp.ExifScrambler +import com.twidere.twiderex.kmp.FileResolver +import com.twidere.twiderex.kmp.RemoteNavigator import com.twidere.twiderex.notification.AppNotificationManager import com.twidere.twiderex.notification.InAppNotification import com.twidere.twiderex.repository.AccountRepository @@ -196,13 +200,53 @@ object JobModule { @ApplicationContext context: Context, accountRepository: AccountRepository, notificationManager: AppNotificationManager, - contentResolver: ContentResolver, + fileResolver: FileResolver, cacheDatabase: CacheDatabase, ): TwitterDirectMessageSendJob = TwitterDirectMessageSendJob( context = context, accountRepository = accountRepository, notificationManager = notificationManager, - contentResolver = contentResolver, + fileResolver = fileResolver, cacheDatabase = cacheDatabase ) + + @Provides + fun provideTwitterComposeJob( + @ApplicationContext context: Context, + accountRepository: AccountRepository, + notificationManager: AppNotificationManager, + fileResolver: FileResolver, + cacheDatabase: CacheDatabase, + exifScrambler: ExifScrambler, + statusRepository: StatusRepository, + remoteNavigator: RemoteNavigator + ): TwitterComposeJob = TwitterComposeJob( + context = context, + accountRepository = accountRepository, + notificationManager = notificationManager, + fileResolver = fileResolver, + cacheDatabase = cacheDatabase, + exifScrambler = exifScrambler, + statusRepository = statusRepository, + remoteNavigator = remoteNavigator + ) + + @Provides + fun provideMastodonComposeJob( + @ApplicationContext context: Context, + accountRepository: AccountRepository, + notificationManager: AppNotificationManager, + fileResolver: FileResolver, + cacheDatabase: CacheDatabase, + exifScrambler: ExifScrambler, + remoteNavigator: RemoteNavigator + ): MastodonComposeJob = MastodonComposeJob( + context = context, + accountRepository = accountRepository, + notificationManager = notificationManager, + fileResolver = fileResolver, + cacheDatabase = cacheDatabase, + exifScrambler = exifScrambler, + remoteNavigator = remoteNavigator + ) } diff --git a/app/src/main/kotlin/com/twidere/twiderex/di/TwidereModule.kt b/app/src/main/kotlin/com/twidere/twiderex/di/TwidereModule.kt index 250505a73..fd78bd461 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/di/TwidereModule.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/di/TwidereModule.kt @@ -20,6 +20,7 @@ */ package com.twidere.twiderex.di +import android.content.ContentResolver import android.content.Context import androidx.core.app.NotificationManagerCompat import androidx.datastore.core.DataStore @@ -29,10 +30,16 @@ import com.twidere.twiderex.action.ComposeAction import com.twidere.twiderex.action.DirectMessageAction import com.twidere.twiderex.db.CacheDatabase import com.twidere.twiderex.http.TwidereServiceFactory +import com.twidere.twiderex.kmp.ExifScrambler +import com.twidere.twiderex.kmp.FileResolver +import com.twidere.twiderex.kmp.RemoteNavigator +import com.twidere.twiderex.kmp.android.AndroidExifScrambler +import com.twidere.twiderex.kmp.android.AndroidFileResolver +import com.twidere.twiderex.kmp.android.AndroidNotificationManager +import com.twidere.twiderex.kmp.android.AndroidRemoteNavigator import com.twidere.twiderex.model.AccountPreferences import com.twidere.twiderex.notification.AppNotificationManager import com.twidere.twiderex.notification.InAppNotification -import com.twidere.twiderex.notification.android.AndroidNotificationManager import com.twidere.twiderex.preferences.proto.MiscPreferences import com.twidere.twiderex.utils.PlatformResolver import dagger.Module @@ -85,4 +92,19 @@ object TwidereModule { context = context, notificationManagerCompat = notificationManagerCompat ) + + @Provides + fun provideExifScrambler(@ApplicationContext context: Context): ExifScrambler = AndroidExifScrambler( + context = context + ) + + @Provides + fun provideFileResolver(contentResolver: ContentResolver): FileResolver = AndroidFileResolver( + contentResolver = contentResolver + ) + + @Provides + fun provideRemoteNavigator(@ApplicationContext context: Context): RemoteNavigator = AndroidRemoteNavigator( + context = context + ) } diff --git a/app/src/main/kotlin/com/twidere/twiderex/extensions/ContextExtensions.kt b/app/src/main/kotlin/com/twidere/twiderex/extensions/ContextExtensions.kt index 35cb7aace..c0bc2fe10 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/extensions/ContextExtensions.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/extensions/ContextExtensions.kt @@ -34,14 +34,16 @@ fun Context.checkAnySelfPermissionsGranted(vararg permissions: String): Boolean return permissions.any { ContextCompat.checkSelfPermission(this, it) == PackageManager.PERMISSION_GRANTED } } -fun Context.shareText(content: String) { +fun Context.shareText(content: String, fromOutsideOfActivity: Boolean = false) { startActivity( Intent().apply { action = Intent.ACTION_SEND putExtra(Intent.EXTRA_TEXT, content) type = "text/plain" }.let { - Intent.createChooser(it, null) + Intent.createChooser(it, null).apply { + if (fromOutsideOfActivity) addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } } ) } diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/compose/ComposeJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/compose/ComposeJob.kt new file mode 100644 index 000000000..e8b6870f0 --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/compose/ComposeJob.kt @@ -0,0 +1,123 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.jobs.compose + +import android.content.Context +import com.twidere.services.microblog.MicroBlogService +import com.twidere.twiderex.R +import com.twidere.twiderex.kmp.ExifScrambler +import com.twidere.twiderex.kmp.RemoteNavigator +import com.twidere.twiderex.model.ComposeData +import com.twidere.twiderex.model.MicroBlogKey +import com.twidere.twiderex.model.ui.UiStatus +import com.twidere.twiderex.navigation.RootDeepLinksRoute +import com.twidere.twiderex.notification.AppNotification +import com.twidere.twiderex.notification.AppNotificationManager +import com.twidere.twiderex.notification.NotificationChannelSpec +import com.twidere.twiderex.repository.AccountRepository +import com.twidere.twiderex.viewmodel.compose.ComposeType +import kotlin.math.roundToInt + +abstract class ComposeJob( + private val applicationContext: Context, + private val accountRepository: AccountRepository, + private val notificationManager: AppNotificationManager, + private val exifScrambler: ExifScrambler, + private val remoteNavigator: RemoteNavigator, +) { + suspend fun execute(composeData: ComposeData, accountKey: MicroBlogKey): Boolean { + val builder = AppNotification + .Builder(NotificationChannelSpec.BackgroundProgresses.id) + .setContentTitle(applicationContext.getString(R.string.common_alerts_tweet_sending_title)) + .setOngoing(true) + .setSilent(true) + .setProgress(100, 0, false) + val accountDetails = accountKey.let { + accountRepository.findByAccountKey(accountKey = it) + }?.let { + accountRepository.getAccountDetails(it) + } ?: return false + val notificationId = composeData.draftId.hashCode() + @Suppress("UNCHECKED_CAST") + val service = try { + accountDetails.service as T + } catch (e: ClassCastException) { + return false + } + notificationManager.notify(notificationId, builder.build()) + + return try { + val mediaIds = arrayListOf() + val images = composeData.images + images.forEachIndexed { index, uri -> + val scramblerUri = exifScrambler.removeExifData(uri) + val id = uploadImage(uri, scramblerUri, service) + id?.let { mediaIds.add(it) } + builder.setProgress( + 100, + (99f * index.toFloat() / composeData.images.size.toFloat()).roundToInt(), + false + ) + notificationManager.notify(notificationId, builder.build()) + exifScrambler.deleteCacheFile(scramblerUri) + } + builder.setProgress(100, 99, false) + notificationManager.notify(notificationId, builder.build()) + val status = compose(service, composeData, accountKey, mediaIds) + builder.setOngoing(false) + .setProgress(0, 0, false) + .setSilent(false) + .setContentTitle(applicationContext.getString(R.string.common_alerts_tweet_sent_title)) + notificationManager.notifyTransient(notificationId, builder.build()) + if (composeData.isThreadMode) { + // open compose scene in thread mode + remoteNavigator.openDeepLink( + deeplink = RootDeepLinksRoute.Compose(ComposeType.Thread, status.statusKey), + fromBackground = true + ) + } + true + } catch (e: Throwable) { + e.printStackTrace() + builder.setOngoing(false) + .setProgress(0, 0, false) + .setSilent(false) + .setContentTitle(applicationContext.getString(R.string.common_alerts_tweet_fail_title)) + .setContentText(composeData.content) + .setDeepLink(RootDeepLinksRoute.Draft(composeData.draftId)) + notificationManager.notify(notificationId, builder.build()) + false + } + } + + protected abstract suspend fun compose( + service: T, + composeData: ComposeData, + accountKey: MicroBlogKey, + mediaIds: ArrayList + ): UiStatus + + protected abstract suspend fun uploadImage( + originUri: String, + scramblerUri: String, + service: T + ): String? +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/compose/MastodonComposeJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/compose/MastodonComposeJob.kt new file mode 100644 index 000000000..28a738786 --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/compose/MastodonComposeJob.kt @@ -0,0 +1,98 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.jobs.compose + +import android.content.Context +import com.twidere.services.mastodon.MastodonService +import com.twidere.services.mastodon.model.PostPoll +import com.twidere.services.mastodon.model.PostStatus +import com.twidere.twiderex.db.CacheDatabase +import com.twidere.twiderex.db.mapper.toDbStatusWithReference +import com.twidere.twiderex.db.model.saveToDb +import com.twidere.twiderex.kmp.ExifScrambler +import com.twidere.twiderex.kmp.FileResolver +import com.twidere.twiderex.kmp.RemoteNavigator +import com.twidere.twiderex.model.ComposeData +import com.twidere.twiderex.model.MicroBlogKey +import com.twidere.twiderex.model.ui.UiStatus +import com.twidere.twiderex.model.ui.UiStatus.Companion.toUi +import com.twidere.twiderex.notification.AppNotificationManager +import com.twidere.twiderex.repository.AccountRepository +import com.twidere.twiderex.viewmodel.compose.ComposeType +import java.io.File +import java.net.URI + +class MastodonComposeJob( + context: Context, + accountRepository: AccountRepository, + notificationManager: AppNotificationManager, + exifScrambler: ExifScrambler, + remoteNavigator: RemoteNavigator, + private val fileResolver: FileResolver, + private val cacheDatabase: CacheDatabase, +) : ComposeJob( + context, + accountRepository, + notificationManager, + exifScrambler, + remoteNavigator +) { + override suspend fun compose( + service: MastodonService, + composeData: ComposeData, + accountKey: MicroBlogKey, + mediaIds: ArrayList + ): UiStatus { + val result = service.compose( + PostStatus( + status = composeData.content, + inReplyToID = if (composeData.composeType == ComposeType.Reply || composeData.composeType == ComposeType.Thread) composeData.statusKey?.id else null, + mediaIDS = mediaIds, + sensitive = composeData.isSensitive, + spoilerText = composeData.contentWarningText, + visibility = composeData.visibility, + poll = composeData.voteOptions?.let { + PostPoll( + options = composeData.voteOptions, + expiresIn = composeData.voteExpired?.value, + multiple = composeData.voteMultiple + ) + } + ) + ).toDbStatusWithReference(accountKey) + listOf(result).saveToDb(cacheDatabase) + return result.toUi(accountKey) + } + + override suspend fun uploadImage( + originUri: String, + scramblerUri: String, + service: MastodonService + ): String? { + val id = fileResolver.openInputStream(scramblerUri)?.use { input -> + service.upload( + input, + URI.create(originUri).path?.let { File(it).name }?.takeIf { it.isNotEmpty() } ?: "file" + ) + } ?: throw Error() + return id.id + } +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/compose/TwitterComposeJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/compose/TwitterComposeJob.kt new file mode 100644 index 000000000..5329ee78b --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/compose/TwitterComposeJob.kt @@ -0,0 +1,103 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.jobs.compose + +import android.content.Context +import com.twidere.services.twitter.TwitterService +import com.twidere.twiderex.db.CacheDatabase +import com.twidere.twiderex.db.mapper.toDbStatusWithReference +import com.twidere.twiderex.db.model.saveToDb +import com.twidere.twiderex.kmp.ExifScrambler +import com.twidere.twiderex.kmp.FileResolver +import com.twidere.twiderex.kmp.RemoteNavigator +import com.twidere.twiderex.model.ComposeData +import com.twidere.twiderex.model.MicroBlogKey +import com.twidere.twiderex.model.ui.UiStatus +import com.twidere.twiderex.model.ui.UiStatus.Companion.toUi +import com.twidere.twiderex.notification.AppNotificationManager +import com.twidere.twiderex.repository.AccountRepository +import com.twidere.twiderex.repository.StatusRepository +import com.twidere.twiderex.viewmodel.compose.ComposeType + +class TwitterComposeJob constructor( + context: Context, + accountRepository: AccountRepository, + notificationManager: AppNotificationManager, + exifScrambler: ExifScrambler, + remoteNavigator: RemoteNavigator, + private val statusRepository: StatusRepository, + private val fileResolver: FileResolver, + private val cacheDatabase: CacheDatabase, +) : ComposeJob( + context, + accountRepository, + notificationManager, + exifScrambler, + remoteNavigator +) { + override suspend fun compose( + service: TwitterService, + composeData: ComposeData, + accountKey: MicroBlogKey, + mediaIds: ArrayList + ): UiStatus { + val lat = composeData.lat + val long = composeData.long + val content = composeData.content.let { + if (composeData.composeType == ComposeType.Quote && composeData.statusKey != null) { + val status = statusRepository.loadFromCache( + composeData.statusKey, + accountKey = accountKey + ) + it + " ${status?.generateShareLink()}" + } else { + it + } + } + val result = service.update( + content, + media_ids = mediaIds, + in_reply_to_status_id = if (composeData.composeType == ComposeType.Reply || composeData.composeType == ComposeType.Thread) composeData.statusKey?.id else null, + repost_status_id = if (composeData.composeType == ComposeType.Quote) composeData.statusKey?.id else null, + lat = lat, + long = long, + exclude_reply_user_ids = composeData.excludedReplyUserIds + ).toDbStatusWithReference(accountKey) + listOf(result).saveToDb(cacheDatabase) + return result.toUi(accountKey) + } + + override suspend fun uploadImage( + originUri: String, + scramblerUri: String, + service: TwitterService + ): String { + val type = fileResolver.getMimeType(originUri) + val size = fileResolver.getFileSize(scramblerUri) + return fileResolver.openInputStream(scramblerUri)?.use { + service.uploadFile( + it, + type ?: "image/*", + size ?: it.available().toLong() + ) + } ?: throw Error() + } +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/dm/DirectMessageSendJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/dm/DirectMessageSendJob.kt index 0320148b9..7882a8bcb 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/jobs/dm/DirectMessageSendJob.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/dm/DirectMessageSendJob.kt @@ -20,10 +20,8 @@ */ package com.twidere.twiderex.jobs.dm -import android.content.ContentResolver import android.content.Context import android.graphics.BitmapFactory -import android.net.Uri import androidx.room.withTransaction import com.twidere.services.microblog.MicroBlogService import com.twidere.twiderex.R @@ -33,6 +31,7 @@ import com.twidere.twiderex.db.model.DbDMEvent.Companion.saveToDb import com.twidere.twiderex.db.model.DbDMEventWithAttachments import com.twidere.twiderex.db.model.DbDMEventWithAttachments.Companion.saveToDb import com.twidere.twiderex.db.model.DbMedia +import com.twidere.twiderex.kmp.FileResolver import com.twidere.twiderex.model.AccountDetails import com.twidere.twiderex.model.DirectMessageSendData import com.twidere.twiderex.model.MediaType @@ -43,6 +42,7 @@ import com.twidere.twiderex.notification.AppNotificationManager import com.twidere.twiderex.notification.NotificationChannelSpec import com.twidere.twiderex.notification.notificationChannelId import com.twidere.twiderex.repository.AccountRepository +import java.net.URI import java.util.UUID abstract class DirectMessageSendJob( @@ -50,7 +50,7 @@ abstract class DirectMessageSendJob( protected val cacheDatabase: CacheDatabase, private val accountRepository: AccountRepository, private val notificationManager: AppNotificationManager, - protected val contentResolver: ContentResolver, + protected val fileResolver: FileResolver, ) { suspend fun execute(sendData: DirectMessageSendData, accountKey: MicroBlogKey): Boolean { val accountDetails = accountKey.let { @@ -67,9 +67,7 @@ abstract class DirectMessageSendJob( } var draftEvent: DbDMEventWithAttachments? = null return try { - val images = sendData.images.map { - Uri.parse(it) - } + val images = sendData.images draftEvent = getDraft(sendData, images, accountDetails) ?: throw IllegalArgumentException() // val exifScrambler = ExifScrambler(context) val mediaIds = arrayListOf() @@ -116,7 +114,7 @@ abstract class DirectMessageSendJob( } } - private suspend fun getDraft(sendData: DirectMessageSendData, images: List, account: AccountDetails): DbDMEventWithAttachments? { + private suspend fun getDraft(sendData: DirectMessageSendData, images: List, account: AccountDetails): DbDMEventWithAttachments? { return cacheDatabase.withTransaction { cacheDatabase.directMessageDao().findWithMessageKey( account.accountKey, @@ -130,7 +128,7 @@ abstract class DirectMessageSendJob( } ?: saveDraft(sendData, images, account) } - private suspend fun saveDraft(sendData: DirectMessageSendData, images: List, account: AccountDetails): DbDMEventWithAttachments? { + private suspend fun saveDraft(sendData: DirectMessageSendData, images: List, account: AccountDetails): DbDMEventWithAttachments? { return cacheDatabase.withTransaction { val createTimeStamp = System.currentTimeMillis() listOf( @@ -152,7 +150,7 @@ abstract class DirectMessageSendJob( ).saveToDb(cacheDatabase) cacheDatabase.mediaDao().insertAll( images.mapIndexed { index, uri -> - val imageSize = getImageSize(uri.path) + val imageSize = getImageSize(URI.create(uri).path) DbMedia( _id = UUID.randomUUID().toString(), belongToKey = sendData.draftMessageKey, @@ -176,8 +174,8 @@ abstract class DirectMessageSendJob( } } - private fun getMediaType(uri: Uri): MediaType { - val type = contentResolver.getType(uri) ?: "" + private fun getMediaType(uri: String): MediaType { + val type = fileResolver.getMimeType(uri) ?: "" return when { type.startsWith("image") -> MediaType.photo type.startsWith("video") -> MediaType.video @@ -204,8 +202,8 @@ abstract class DirectMessageSendJob( ): DbDMEventWithAttachments protected abstract suspend fun uploadImage( - originUri: Uri, - scramblerUri: Uri, + originUri: String, + scramblerUri: String, service: T ): String? diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/dm/TwitterDirectMessageSendJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/dm/TwitterDirectMessageSendJob.kt index 9b10ace18..2f4856c38 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/jobs/dm/TwitterDirectMessageSendJob.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/dm/TwitterDirectMessageSendJob.kt @@ -20,9 +20,7 @@ */ package com.twidere.twiderex.jobs.dm -import android.content.ContentResolver import android.content.Context -import android.net.Uri import com.twidere.services.microblog.LookupService import com.twidere.services.twitter.TwitterService import com.twidere.twiderex.db.CacheDatabase @@ -31,6 +29,7 @@ import com.twidere.twiderex.db.mapper.toDbDirectMessage import com.twidere.twiderex.db.mapper.toDbUser import com.twidere.twiderex.db.model.DbDMEventWithAttachments import com.twidere.twiderex.db.model.DbUser +import com.twidere.twiderex.kmp.FileResolver import com.twidere.twiderex.model.DirectMessageSendData import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.notification.AppNotificationManager @@ -40,10 +39,10 @@ class TwitterDirectMessageSendJob( context: Context, accountRepository: AccountRepository, notificationManager: AppNotificationManager, - contentResolver: ContentResolver, + fileResolver: FileResolver, cacheDatabase: CacheDatabase, ) : DirectMessageSendJob( - context, cacheDatabase, accountRepository, notificationManager, contentResolver + context, cacheDatabase, accountRepository, notificationManager, fileResolver ) { override suspend fun sendMessage( @@ -70,13 +69,13 @@ class TwitterDirectMessageSendJob( } override suspend fun uploadImage( - originUri: Uri, - scramblerUri: Uri, + originUri: String, + scramblerUri: String, service: TwitterService ): String? { - val type = contentResolver.getType(originUri) - val size = contentResolver.openFileDescriptor(scramblerUri, "r")?.statSize - return contentResolver.openInputStream(scramblerUri)?.use { + val type = fileResolver.getMimeType(originUri) + val size = fileResolver.getFileSize(originUri) + return fileResolver.openInputStream(scramblerUri)?.use { service.uploadFile( it, type ?: "image/*", diff --git a/app/src/main/kotlin/com/twidere/twiderex/kmp/ExifScrambler.kt b/app/src/main/kotlin/com/twidere/twiderex/kmp/ExifScrambler.kt new file mode 100644 index 000000000..6b68a250c --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/kmp/ExifScrambler.kt @@ -0,0 +1,27 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.kmp + +interface ExifScrambler { + fun removeExifData(file: String, compress: Int = 100): String + + fun deleteCacheFile(file: String) +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/kmp/FileResolver.kt b/app/src/main/kotlin/com/twidere/twiderex/kmp/FileResolver.kt new file mode 100644 index 000000000..5ce3cf8db --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/kmp/FileResolver.kt @@ -0,0 +1,31 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.kmp + +import java.io.InputStream + +interface FileResolver { + fun getMimeType(file: String): String? + + fun getFileSize(file: String): Long? + + fun openInputStream(file: String): InputStream? +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/kmp/RemoteNavigator.kt b/app/src/main/kotlin/com/twidere/twiderex/kmp/RemoteNavigator.kt new file mode 100644 index 000000000..9a6107c14 --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/kmp/RemoteNavigator.kt @@ -0,0 +1,29 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.kmp + +interface RemoteNavigator { + fun openDeepLink(deeplink: String, fromBackground: Boolean = false) + + fun shareMedia(filePath: String, mimeType: String, fromBackground: Boolean = false) + + fun shareText(content: String, fromBackground: Boolean = false) +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/utils/ExifScrambler.kt b/app/src/main/kotlin/com/twidere/twiderex/kmp/android/AndroidExifScrambler.kt similarity index 88% rename from app/src/main/kotlin/com/twidere/twiderex/utils/ExifScrambler.kt rename to app/src/main/kotlin/com/twidere/twiderex/kmp/android/AndroidExifScrambler.kt index 336e1856c..fb9112bca 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/utils/ExifScrambler.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/kmp/android/AndroidExifScrambler.kt @@ -18,7 +18,7 @@ * You should have received a copy of the GNU General Public License * along with Twidere X. If not, see . */ -package com.twidere.twiderex.utils +package com.twidere.twiderex.kmp.android import android.content.Context import android.graphics.Bitmap @@ -26,19 +26,21 @@ import android.graphics.BitmapFactory import android.net.Uri import androidx.core.net.toUri import androidx.exifinterface.media.ExifInterface +import com.twidere.twiderex.kmp.ExifScrambler import java.io.File import java.util.UUID -class ExifScrambler(private val context: Context) { - fun removeExifData(uri: Uri, compress: Int = 100): Uri { +class AndroidExifScrambler(private val context: Context) : ExifScrambler { + override fun removeExifData(file: String, compress: Int): String { // first get input stream + val uri = Uri.parse(file) val contentResolver = context.contentResolver contentResolver.openInputStream(uri)?.use { input -> // decode to bitmap because bitmap won't store exif meta data val bitmap = try { BitmapFactory.decodeStream(input) } catch (oom: OutOfMemoryError) { - return uri + return file } // create an cache image val mimeType = contentResolver.getType(uri) ?: "" @@ -74,13 +76,13 @@ class ExifScrambler(private val context: Context) { } } } - return imageCache.toUri() + return imageCache.toUri().toString() } - return uri + return uri.toString() } - fun deleteCacheFile(uri: Uri) { - uri.path?.let { + override fun deleteCacheFile(file: String) { + Uri.parse(file).path?.let { File(it) }?.apply { if (exists()) delete() diff --git a/app/src/main/kotlin/com/twidere/twiderex/kmp/android/AndroidFileResolver.kt b/app/src/main/kotlin/com/twidere/twiderex/kmp/android/AndroidFileResolver.kt new file mode 100644 index 000000000..ea2e4d94b --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/kmp/android/AndroidFileResolver.kt @@ -0,0 +1,40 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.kmp.android + +import android.content.ContentResolver +import android.net.Uri +import com.twidere.twiderex.kmp.FileResolver +import java.io.InputStream + +class AndroidFileResolver(private val contentResolver: ContentResolver) : FileResolver { + override fun getMimeType(file: String): String? { + return contentResolver.getType(Uri.parse(file)) + } + + override fun getFileSize(file: String): Long? { + return contentResolver.openFileDescriptor(Uri.parse(file), "r")?.statSize + } + + override fun openInputStream(file: String): InputStream? { + return contentResolver.openInputStream(Uri.parse(file)) + } +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/notification/android/AndroidNotificationManager.kt b/app/src/main/kotlin/com/twidere/twiderex/kmp/android/AndroidNotificationManager.kt similarity index 98% rename from app/src/main/kotlin/com/twidere/twiderex/notification/android/AndroidNotificationManager.kt rename to app/src/main/kotlin/com/twidere/twiderex/kmp/android/AndroidNotificationManager.kt index 546c32de6..70a007fc1 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/notification/android/AndroidNotificationManager.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/kmp/android/AndroidNotificationManager.kt @@ -18,7 +18,7 @@ * You should have received a copy of the GNU General Public License * along with Twidere X. If not, see . */ -package com.twidere.twiderex.notification.android +package com.twidere.twiderex.kmp.android import android.app.PendingIntent import android.content.Context diff --git a/app/src/main/kotlin/com/twidere/twiderex/kmp/android/AndroidRemoteNavigator.kt b/app/src/main/kotlin/com/twidere/twiderex/kmp/android/AndroidRemoteNavigator.kt new file mode 100644 index 000000000..77bf60422 --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/kmp/android/AndroidRemoteNavigator.kt @@ -0,0 +1,56 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.kmp.android + +import android.content.Context +import android.content.Intent +import android.net.Uri +import com.twidere.twiderex.extensions.shareMedia +import com.twidere.twiderex.extensions.shareText +import com.twidere.twiderex.kmp.RemoteNavigator + +class AndroidRemoteNavigator(private val context: Context) : RemoteNavigator { + override fun openDeepLink(deeplink: String, fromBackground: Boolean) { + context.startActivity( + Intent( + Intent.ACTION_VIEW, + Uri.parse(deeplink) + ).apply { + if (fromBackground) addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + ) + } + + override fun shareMedia(filePath: String, mimeType: String, fromBackground: Boolean) { + context.shareMedia( + uri = Uri.parse(filePath), + mimeType = mimeType, + fromOutsideOfActivity = fromBackground + ) + } + + override fun shareText(content: String, fromBackground: Boolean) { + context.shareText( + content = content, + fromOutsideOfActivity = fromBackground + ) + } +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/compose/ComposeWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/compose/ComposeWorker.kt index dcf32f64d..0c79e718c 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/compose/ComposeWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/compose/ComposeWorker.kt @@ -21,112 +21,28 @@ package com.twidere.twiderex.worker.compose import android.content.Context -import android.content.Intent -import android.net.Uri import androidx.work.CoroutineWorker import androidx.work.WorkerParameters import com.twidere.services.microblog.MicroBlogService -import com.twidere.twiderex.R -import com.twidere.twiderex.model.ComposeData +import com.twidere.twiderex.extensions.toWorkResult +import com.twidere.twiderex.jobs.compose.ComposeJob import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.toComposeData -import com.twidere.twiderex.model.ui.UiStatus -import com.twidere.twiderex.navigation.RootDeepLinksRoute -import com.twidere.twiderex.notification.AppNotification -import com.twidere.twiderex.notification.AppNotificationManager -import com.twidere.twiderex.notification.NotificationChannelSpec -import com.twidere.twiderex.repository.AccountRepository -import com.twidere.twiderex.utils.ExifScrambler -import com.twidere.twiderex.viewmodel.compose.ComposeType -import kotlin.math.roundToInt abstract class ComposeWorker( protected val context: Context, workerParams: WorkerParameters, - private val accountRepository: AccountRepository, - private val notificationManager: AppNotificationManager, + private val composeJob: ComposeJob<*> ) : CoroutineWorker(context, workerParams) { override suspend fun doWork(): Result { - val builder = AppNotification - .Builder(NotificationChannelSpec.BackgroundProgresses.id) - .setContentTitle(applicationContext.getString(R.string.common_alerts_tweet_sending_title)) - .setOngoing(true) - .setSilent(true) - .setProgress(100, 0, false) val composeData = inputData.toComposeData() - val accountDetails = inputData.getString("accountKey")?.let { + val accountKey = inputData.getString("accountKey")?.let { MicroBlogKey.valueOf(it) - }?.let { - accountRepository.findByAccountKey(accountKey = it) - }?.let { - accountRepository.getAccountDetails(it) } ?: return Result.failure() - val notificationId = composeData.draftId.hashCode() - @Suppress("UNCHECKED_CAST") - val service = accountDetails.service as T - notificationManager.notify(notificationId, builder.build()) - - return try { - val exifScrambler = ExifScrambler(context) - val mediaIds = arrayListOf() - val images = composeData.images.map { - Uri.parse(it) - } - images.forEachIndexed { index, uri -> - val scramblerUri = exifScrambler.removeExifData(uri) - val id = uploadImage(uri, scramblerUri, service) - id?.let { mediaIds.add(it) } - builder.setProgress( - 100, - (99f * index.toFloat() / composeData.images.size.toFloat()).roundToInt(), - false - ) - notificationManager.notify(notificationId, builder.build()) - exifScrambler.deleteCacheFile(scramblerUri) - } - builder.setProgress(100, 99, false) - notificationManager.notify(notificationId, builder.build()) - val status = compose(service, composeData, mediaIds) - builder.setOngoing(false) - .setProgress(0, 0, false) - .setSilent(false) - .setContentTitle(applicationContext.getString(R.string.common_alerts_tweet_sent_title)) - notificationManager.notifyTransient(notificationId, builder.build()) - if (composeData.isThreadMode) { - // open compose scene in thread mode - applicationContext.startActivity( - Intent( - Intent.ACTION_VIEW, - Uri.parse(RootDeepLinksRoute.Compose(ComposeType.Thread, status.statusKey)) - ).apply { - addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - } - ) - } - Result.success() - } catch (e: Throwable) { - e.printStackTrace() - builder.setOngoing(false) - .setProgress(0, 0, false) - .setSilent(false) - .setContentTitle(applicationContext.getString(R.string.common_alerts_tweet_fail_title)) - .setContentText(composeData.content) - .setDeepLink(RootDeepLinksRoute.Draft(composeData.draftId)) - notificationManager.notify(notificationId, builder.build()) - Result.failure() - } + return composeJob.execute( + composeData = composeData, + accountKey = accountKey + ).toWorkResult() } - - protected abstract suspend fun compose( - service: T, - composeData: ComposeData, - mediaIds: ArrayList - ): UiStatus - - protected abstract suspend fun uploadImage( - originUri: Uri, - scramblerUri: Uri, - service: T - ): String? } diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/compose/MastodonComposeWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/compose/MastodonComposeWorker.kt index 5f5c79174..e4e7506b0 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/compose/MastodonComposeWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/compose/MastodonComposeWorker.kt @@ -20,40 +20,25 @@ */ package com.twidere.twiderex.worker.compose -import android.content.ContentResolver import android.content.Context -import android.net.Uri import androidx.hilt.work.HiltWorker import androidx.work.Data import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkerParameters import com.twidere.services.mastodon.MastodonService -import com.twidere.services.mastodon.model.PostPoll -import com.twidere.services.mastodon.model.PostStatus -import com.twidere.twiderex.db.CacheDatabase -import com.twidere.twiderex.db.mapper.toDbStatusWithReference -import com.twidere.twiderex.db.model.saveToDb +import com.twidere.twiderex.jobs.compose.MastodonComposeJob import com.twidere.twiderex.model.ComposeData import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.toWorkData -import com.twidere.twiderex.model.ui.UiStatus -import com.twidere.twiderex.model.ui.UiStatus.Companion.toUi -import com.twidere.twiderex.notification.AppNotificationManager -import com.twidere.twiderex.repository.AccountRepository -import com.twidere.twiderex.viewmodel.compose.ComposeType import dagger.assisted.Assisted import dagger.assisted.AssistedInject -import java.io.File @HiltWorker class MastodonComposeWorker @AssistedInject constructor( @Assisted context: Context, @Assisted workerParams: WorkerParameters, - accountRepository: AccountRepository, - notificationManager: AppNotificationManager, - private val contentResolver: ContentResolver, - private val cacheDatabase: CacheDatabase, -) : ComposeWorker(context, workerParams, accountRepository, notificationManager) { + mastodonComposeJob: MastodonComposeJob +) : ComposeWorker(context, workerParams, mastodonComposeJob) { companion object { fun create( @@ -68,47 +53,4 @@ class MastodonComposeWorker @AssistedInject constructor( ) .build() } - - override suspend fun compose( - service: MastodonService, - composeData: ComposeData, - mediaIds: ArrayList - ): UiStatus { - val accountKey = inputData.getString("accountKey")?.let { - MicroBlogKey.valueOf(it) - } ?: throw Error() - val result = service.compose( - PostStatus( - status = composeData.content, - inReplyToID = if (composeData.composeType == ComposeType.Reply || composeData.composeType == ComposeType.Thread) composeData.statusKey?.id else null, - mediaIDS = mediaIds, - sensitive = composeData.isSensitive, - spoilerText = composeData.contentWarningText, - visibility = composeData.visibility, - poll = composeData.voteOptions?.let { - PostPoll( - options = composeData.voteOptions, - expiresIn = composeData.voteExpired?.value, - multiple = composeData.voteMultiple - ) - } - ) - ).toDbStatusWithReference(accountKey) - listOf(result).saveToDb(cacheDatabase) - return result.toUi(accountKey) - } - - override suspend fun uploadImage( - originUri: Uri, - scramblerUri: Uri, - service: MastodonService - ): String? { - val id = contentResolver.openInputStream(scramblerUri)?.use { input -> - service.upload( - input, - originUri.path?.let { File(it).name }?.takeIf { it.isNotEmpty() } ?: "file" - ) - } ?: throw Error() - return id.id - } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/compose/TwitterComposeWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/compose/TwitterComposeWorker.kt index 617fe4289..6a4ebf967 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/compose/TwitterComposeWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/compose/TwitterComposeWorker.kt @@ -20,27 +20,16 @@ */ package com.twidere.twiderex.worker.compose -import android.content.ContentResolver import android.content.Context -import android.net.Uri import androidx.hilt.work.HiltWorker import androidx.work.Data import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkerParameters -import androidx.work.hasKeyWithValueOfType import com.twidere.services.twitter.TwitterService -import com.twidere.twiderex.db.CacheDatabase -import com.twidere.twiderex.db.mapper.toDbStatusWithReference -import com.twidere.twiderex.db.model.saveToDb +import com.twidere.twiderex.jobs.compose.TwitterComposeJob import com.twidere.twiderex.model.ComposeData import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.toWorkData -import com.twidere.twiderex.model.ui.UiStatus -import com.twidere.twiderex.model.ui.UiStatus.Companion.toUi -import com.twidere.twiderex.notification.AppNotificationManager -import com.twidere.twiderex.repository.AccountRepository -import com.twidere.twiderex.repository.StatusRepository -import com.twidere.twiderex.viewmodel.compose.ComposeType import dagger.assisted.Assisted import dagger.assisted.AssistedInject @@ -48,16 +37,11 @@ import dagger.assisted.AssistedInject class TwitterComposeWorker @AssistedInject constructor( @Assisted context: Context, @Assisted workerParams: WorkerParameters, - accountRepository: AccountRepository, - notificationManager: AppNotificationManager, - private val statusRepository: StatusRepository, - private val contentResolver: ContentResolver, - private val cacheDatabase: CacheDatabase, + twitterComposeJob: TwitterComposeJob, ) : ComposeWorker( context, workerParams, - accountRepository, - notificationManager + twitterComposeJob ) { companion object { fun create( @@ -74,58 +58,4 @@ class TwitterComposeWorker @AssistedInject constructor( ) .build() } - - override suspend fun compose( - service: TwitterService, - composeData: ComposeData, - mediaIds: ArrayList - ): UiStatus { - val accountKey = inputData.getString("accountKey")?.let { - MicroBlogKey.valueOf(it) - } ?: throw Error() - val lat = inputData.takeIf { - it.hasKeyWithValueOfType("lat") - }?.getDouble("lat", 0.0) - val long = inputData.takeIf { - it.hasKeyWithValueOfType("long") - }?.getDouble("long", 0.0) - val content = composeData.content.let { - if (composeData.composeType == ComposeType.Quote && composeData.statusKey != null) { - val status = statusRepository.loadFromCache( - composeData.statusKey, - accountKey = accountKey - ) - it + " ${status?.generateShareLink()}" - } else { - it - } - } - val result = service.update( - content, - media_ids = mediaIds, - in_reply_to_status_id = if (composeData.composeType == ComposeType.Reply || composeData.composeType == ComposeType.Thread) composeData.statusKey?.id else null, - repost_status_id = if (composeData.composeType == ComposeType.Quote) composeData.statusKey?.id else null, - lat = lat, - long = long, - exclude_reply_user_ids = composeData.excludedReplyUserIds - ).toDbStatusWithReference(accountKey) - listOf(result).saveToDb(cacheDatabase) - return result.toUi(accountKey) - } - - override suspend fun uploadImage( - originUri: Uri, - scramblerUri: Uri, - service: TwitterService - ): String { - val type = contentResolver.getType(originUri) - val size = contentResolver.openFileDescriptor(scramblerUri, "r")?.statSize - return contentResolver.openInputStream(scramblerUri)?.use { - service.uploadFile( - it, - type ?: "image/*", - size ?: it.available().toLong() - ) - } ?: throw Error() - } } From 89ad2be9a3ac8a86741382238575d45e02f019b8 Mon Sep 17 00:00:00 2001 From: itsMimao Date: Tue, 27 Jul 2021 18:18:57 +0800 Subject: [PATCH 088/137] fixed toWorkResult always return success --- .../kotlin/com/twidere/twiderex/extensions/WorkExtension.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/twidere/twiderex/extensions/WorkExtension.kt b/app/src/main/kotlin/com/twidere/twiderex/extensions/WorkExtension.kt index 8bbefe088..4aee73e55 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/extensions/WorkExtension.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/extensions/WorkExtension.kt @@ -22,4 +22,4 @@ package com.twidere.twiderex.extensions import androidx.work.ListenableWorker -internal fun Boolean.toWorkResult() = if (this) ListenableWorker.Result.success() else ListenableWorker.Result.success() +internal fun Boolean.toWorkResult() = if (this) ListenableWorker.Result.success() else ListenableWorker.Result.failure() From 95a9a7dfdf501999685d2fceb6800cc8fd148054 Mon Sep 17 00:00:00 2001 From: itsMimao Date: Wed, 28 Jul 2021 11:02:44 +0800 Subject: [PATCH 089/137] fixed compose thread not working issue fixed hashtag/mention click crash issue --- .../com/twidere/twiderex/db/mapper/Twitter.kt | 13 ++++++++++--- .../twidere/twiderex/navigation/RootDeepLinks.kt | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/app/src/main/kotlin/com/twidere/twiderex/db/mapper/Twitter.kt b/app/src/main/kotlin/com/twidere/twiderex/db/mapper/Twitter.kt index 3b95a0312..2dc60e39b 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/db/mapper/Twitter.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/db/mapper/Twitter.kt @@ -60,12 +60,19 @@ import java.util.UUID val autolink by lazy { Autolink().apply { setUsernameIncludeSymbol(true) - hashtagUrlBase = "${RootDeepLinksRouteDefinition.Search}/%23" - cashtagUrlBase = "${RootDeepLinksRouteDefinition.Search}/%24" - usernameUrlBase = "${RootDeepLinksRouteDefinition.Twitter.User}/" + hashtagUrlBase = "${generateDeepLinkBase(RootDeepLinksRouteDefinition.Search)}/%23" + cashtagUrlBase = "${generateDeepLinkBase(RootDeepLinksRouteDefinition.Search)}/%24" + usernameUrlBase = "${generateDeepLinkBase(RootDeepLinksRouteDefinition.Twitter.User)}/" } } +private fun generateDeepLinkBase(deeplink: String): String { + return deeplink.substring( + 0, + deeplink.indexOf("/{") + ) +} + fun StatusV2.toDbPagingTimeline( accountKey: MicroBlogKey, pagingKey: String, diff --git a/app/src/main/kotlin/com/twidere/twiderex/navigation/RootDeepLinks.kt b/app/src/main/kotlin/com/twidere/twiderex/navigation/RootDeepLinks.kt index 878cc7e51..d54ee5b31 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/navigation/RootDeepLinks.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/navigation/RootDeepLinks.kt @@ -47,7 +47,7 @@ interface RootDeepLinks { val SignIn: String fun Draft(draftId: String): String - fun Compose(composeType: ComposeType, statusKey: MicroBlogKey?): String + fun Compose(composeType: ComposeType?, statusKey: MicroBlogKey?): String fun Conversation(conversationKey: MicroBlogKey): String interface Callback { From dddf529e458e5b0774127ab6458119dc0d11941e Mon Sep 17 00:00:00 2001 From: itsMimao Date: Wed, 28 Jul 2021 11:39:14 +0800 Subject: [PATCH 090/137] fixed dm conversation click media crash --- .../com/twidere/twiderex/component/lazy/ui/LazyUiDMEventList.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/lazy/ui/LazyUiDMEventList.kt b/app/src/main/kotlin/com/twidere/twiderex/component/lazy/ui/LazyUiDMEventList.kt index 0f1c64d05..5d5a833fa 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/lazy/ui/LazyUiDMEventList.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/lazy/ui/LazyUiDMEventList.kt @@ -246,7 +246,7 @@ private fun MessageBody(event: UiDMEvent, onItemLongClick: (event: UiDMEvent) -> MediaMessage( media = event.media.firstOrNull(), onClick = { - navController.navigate(RootRoute.Media.Pure(event.messageKey, null)) + navController.navigate(RootRoute.Media.Pure(event.messageKey, 0)) } ) if (event.media.isNotEmpty() && event.htmlText.isNotEmpty()) Spacer(modifier = Modifier.height(MessageBodyDefaults.ContentSpacing)) From a2b5218c0bbf36a721fb8aa5f804cbc79293936d Mon Sep 17 00:00:00 2001 From: itsMimao Date: Wed, 28 Jul 2021 12:11:24 +0800 Subject: [PATCH 091/137] fixed reply compose scene mention progress runing forever issuse --- .../com/twidere/twiderex/viewmodel/compose/ComposeViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/compose/ComposeViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/compose/ComposeViewModel.kt index 717b65653..63d88c205 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/compose/ComposeViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/compose/ComposeViewModel.kt @@ -246,8 +246,8 @@ open class ComposeViewModel @AssistedInject constructor( val loadingReplyUser = MutableStateFlow(false) val replyToUser = replyToUserName.map { - loadingReplyUser.value = true if (it.isNotEmpty()) { + loadingReplyUser.value = true try { userRepository.lookupUsersByName( it, From dce4374cb912eaf14e7ce60a7caecaac5a4119a7 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Wed, 28 Jul 2021 12:17:55 +0800 Subject: [PATCH 092/137] fix route schema generation --- .../twiderex/navigation/RootDeepLinks.kt | 2 +- routeProcessor/src/main/kotlin/AppRoute.kt | 2 +- .../src/main/kotlin/RouteDefinition.kt | 23 ++++++++++--------- .../src/main/kotlin/RouteProcessor.kt | 4 ++-- 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/app/src/main/kotlin/com/twidere/twiderex/navigation/RootDeepLinks.kt b/app/src/main/kotlin/com/twidere/twiderex/navigation/RootDeepLinks.kt index 878cc7e51..75942faac 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/navigation/RootDeepLinks.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/navigation/RootDeepLinks.kt @@ -29,7 +29,7 @@ import com.twidere.twiderex.viewmodel.compose.ComposeType * make it's name the same to route parameters in Root.kt too */ @AppRoute( - prefix = "$twidereXSchema://" + schema = twidereXSchema ) interface RootDeepLinks { interface Twitter { diff --git a/routeProcessor/src/main/kotlin/AppRoute.kt b/routeProcessor/src/main/kotlin/AppRoute.kt index c0bd8654c..6f3adf195 100644 --- a/routeProcessor/src/main/kotlin/AppRoute.kt +++ b/routeProcessor/src/main/kotlin/AppRoute.kt @@ -23,7 +23,7 @@ package com.twidere.route.processor @Retention(AnnotationRetention.SOURCE) @Target(AnnotationTarget.CLASS) annotation class AppRoute( - val prefix: String = "", + val schema: String = "", val packageName: String = "", val routeClassName: String = "", val definitionClassName: String = "", diff --git a/routeProcessor/src/main/kotlin/RouteDefinition.kt b/routeProcessor/src/main/kotlin/RouteDefinition.kt index 82b8fc09f..0f3c78aca 100644 --- a/routeProcessor/src/main/kotlin/RouteDefinition.kt +++ b/routeProcessor/src/main/kotlin/RouteDefinition.kt @@ -21,6 +21,7 @@ package com.twidere.route.processor private const val StandardIndent = " " +private const val RouteDivider = "/" internal interface RouteDefinition { val name: String @@ -41,7 +42,7 @@ internal fun RouteDefinition.parents(): List { internal val RouteDefinition.parentPath get() = parents() - .joinToString("/") { it.name } + .joinToString(RouteDivider) { it.name } internal val RouteDefinition.indent get() = parents() @@ -49,13 +50,13 @@ internal val RouteDefinition.indent .joinToString("") { StandardIndent } internal data class PrefixRouteDefinition( - val prefix: String, + val schema: String, val child: NestedRouteDefinition, val routeClassName: String, val definitionClassName: String, ) : RouteDefinition { override val name: String - get() = prefix + get() = if (schema.isEmpty()) "" else "$schema:$RouteDivider" override val parent: RouteDefinition? get() = null @@ -98,11 +99,11 @@ internal data class ConstRouteDefinition( override val parent: RouteDefinition? = null, ) : RouteDefinition { override fun generateDefinition(): String { - return "${indent}const val $name = \"$parentPath/${name}\"" + return "${indent}const val $name = \"$parentPath$RouteDivider${name}\"" } override fun generateRoute(): String { - return "${indent}override val $name = \"$parentPath/${name}\"" + return "${indent}override val $name = \"$parentPath$RouteDivider${name}\"" } } @@ -114,17 +115,17 @@ internal data class FunctionRouteDefinition( override fun generateDefinition(): String { val path = parameters .filter { !it.isNullable } - .joinToString("/") { parameter -> + .joinToString(RouteDivider) { parameter -> "{${parameter.name}}" } .let { if (it.isNotEmpty()) { - "/$it" + "$RouteDivider$it" } else { it } } - return "${indent}const val $name = \"$parentPath/$name$path\"" + return "${indent}const val $name = \"$parentPath$RouteDivider$name$path\"" } override fun generateRoute(): String { @@ -160,7 +161,7 @@ internal data class FunctionRouteDefinition( } val pathWithParameter = parameters .filter { !it.isNullable } - .joinToString("/") { parameter -> + .joinToString(RouteDivider) { parameter -> val name = parameter.name if (parameter.type == "kotlin.String") { encode(name) @@ -170,13 +171,13 @@ internal data class FunctionRouteDefinition( } .let { if (it.isNotEmpty()) { - "/$it" + "$RouteDivider$it" } else { it } } - return "${indent}override fun $name($parameterStr) = \"$parentPath/$name$pathWithParameter${query}\"" + return "${indent}override fun $name($parameterStr) = \"$parentPath$RouteDivider$name$pathWithParameter${query}\"" } private fun encode(value: String) = "\${java.net.URLEncoder.encode($value, \"UTF-8\")}" diff --git a/routeProcessor/src/main/kotlin/RouteProcessor.kt b/routeProcessor/src/main/kotlin/RouteProcessor.kt index 324f4ab6a..e22afe441 100644 --- a/routeProcessor/src/main/kotlin/RouteProcessor.kt +++ b/routeProcessor/src/main/kotlin/RouteProcessor.kt @@ -60,7 +60,7 @@ internal class RouteProcessor( .firstOrNull { it.annotationType.resolve().declaration.qualifiedName?.asString() == AppRoute::class.qualifiedName } ?: return - val prefix = annotation.getStringValue(AppRoute::prefix.name) ?: "" + val schema = annotation.getStringValue(AppRoute::schema.name) ?: "" val packageName = annotation.getStringValue(AppRoute::packageName.name) ?: node.packageName.asString() val routeClassName = annotation.getStringValue(AppRoute::routeClassName.name) @@ -73,7 +73,7 @@ internal class RouteProcessor( it is NestedRouteDefinition }?.let { PrefixRouteDefinition( - prefix = prefix, + schema = schema, child = it as NestedRouteDefinition, routeClassName = routeClassName, definitionClassName = definitionClassName, From e87ccf1a3c11fc2a021feb4d479375fddfd5438d Mon Sep 17 00:00:00 2001 From: Tlaster Date: Wed, 28 Jul 2021 13:29:19 +0800 Subject: [PATCH 093/137] update login logo --- .../kotlin/com/twidere/twiderex/component/LoginLogo.kt | 8 +------- .../ic_login_logo.xml} | 0 2 files changed, 1 insertion(+), 7 deletions(-) rename app/src/main/res/{drawable/ic_login_logo_dark.xml => drawable-night/ic_login_logo.xml} (100%) diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/LoginLogo.kt b/app/src/main/kotlin/com/twidere/twiderex/component/LoginLogo.kt index 1f2b43407..234fec77b 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/LoginLogo.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/LoginLogo.kt @@ -27,21 +27,15 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import com.twidere.twiderex.R -import com.twidere.twiderex.extensions.isDarkTheme @Composable fun LoginLogo( modifier: Modifier = Modifier, ) { - val resource = if (isDarkTheme()) { - painterResource(id = R.drawable.ic_login_logo_dark) - } else { - painterResource(id = R.drawable.ic_login_logo) - } Image( modifier = modifier, contentScale = ContentScale.FillWidth, - painter = resource, + painter = painterResource(id = R.drawable.ic_login_logo), contentDescription = stringResource(id = R.string.accessibility_common_logo_twidere) ) } diff --git a/app/src/main/res/drawable/ic_login_logo_dark.xml b/app/src/main/res/drawable-night/ic_login_logo.xml similarity index 100% rename from app/src/main/res/drawable/ic_login_logo_dark.xml rename to app/src/main/res/drawable-night/ic_login_logo.xml From aaf8f50b4d29ed27bbff273f6e0344a941818671 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Wed, 28 Jul 2021 13:29:39 +0800 Subject: [PATCH 094/137] add splash --- .../com/twidere/twiderex/TwidereXActivity.kt | 126 +++++++++++++----- .../preferences/ProvidePreferences.kt | 14 +- 2 files changed, 107 insertions(+), 33 deletions(-) diff --git a/app/src/main/kotlin/com/twidere/twiderex/TwidereXActivity.kt b/app/src/main/kotlin/com/twidere/twiderex/TwidereXActivity.kt index a5bd1b9c7..c2f51c77e 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/TwidereXActivity.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/TwidereXActivity.kt @@ -30,15 +30,36 @@ import android.os.Bundle import android.view.WindowManager import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.foundation.Image +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Scaffold +import androidx.compose.material.darkColors +import androidx.compose.material.lightColors +import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.core.net.ConnectivityManagerCompat import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsControllerCompat import androidx.lifecycle.lifecycleScope import androidx.lifecycle.viewmodel.compose.viewModel -import com.google.accompanist.insets.ExperimentalAnimatedInsets import com.google.accompanist.insets.ProvideWindowInsets import com.twidere.twiderex.action.LocalStatusActions import com.twidere.twiderex.action.StatusActions @@ -103,7 +124,7 @@ class TwidereXActivity : ComponentActivity() { @Inject lateinit var platformResolver: PlatformResolver - @OptIn(ExperimentalAnimatedInsets::class) + @OptIn(ExperimentalAnimationApi::class) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) isActiveNetworkMetered.value = ConnectivityManagerCompat.isActiveNetworkMetered( @@ -113,43 +134,84 @@ class TwidereXActivity : ComponentActivity() { WindowCompat.setDecorFitsSystemWindows(window, false) window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) setContent { - val windowInsetsControllerCompat = - remember { WindowInsetsControllerCompat(window, window.decorView) } - val accountViewModel = viewModel() - val account by accountViewModel.account.observeAsState(null) - val isActiveNetworkMetered by isActiveNetworkMetered.observeAsState(initial = false) - CompositionLocalProvider( - LocalInAppNotification provides inAppNotification, - LocalWindow provides window, - LocalWindowInsetsController provides windowInsetsControllerCompat, - LocalActiveAccount provides account, - LocalApplication provides application, - LocalStatusActions provides statusActions, - LocalActivity provides this, - LocalActiveAccountViewModel provides accountViewModel, - LocalIsActiveNetworkMetered provides isActiveNetworkMetered, - LocalPlatformResolver provides platformResolver, + var showSplash by rememberSaveable { mutableStateOf(true) } + LaunchedEffect(Unit) { + preferencesHolder.warmup() + showSplash = false + } + App() + AnimatedVisibility( + visible = showSplash, + enter = fadeIn(), + exit = fadeOut(), ) { - ProvidePreferences( - preferencesHolder, + Splash() + } + } + intent.data?.let { + onDeeplink(it) + } + } + + @Composable + private fun Splash() { + MaterialTheme( + colors = if (isSystemInDarkTheme()) { + darkColors() + } else { + lightColors() + } + ) { + Scaffold { + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, ) { - ProvideAssistedFactory( - viewModelHolder.factory, + Image( + painter = painterResource(id = R.drawable.ic_login_logo), + contentDescription = stringResource(id = R.string.accessibility_common_logo_twidere) + ) + } + } + } + } + + @Composable + private fun App() { + val windowInsetsControllerCompat = + remember { WindowInsetsControllerCompat(window, window.decorView) } + val accountViewModel = viewModel() + val account by accountViewModel.account.observeAsState(null) + val isActiveNetworkMetered by isActiveNetworkMetered.observeAsState(initial = false) + CompositionLocalProvider( + LocalInAppNotification provides inAppNotification, + LocalWindow provides window, + LocalWindowInsetsController provides windowInsetsControllerCompat, + LocalActiveAccount provides account, + LocalApplication provides application, + LocalStatusActions provides statusActions, + LocalActivity provides this, + LocalActiveAccountViewModel provides accountViewModel, + LocalIsActiveNetworkMetered provides isActiveNetworkMetered, + LocalPlatformResolver provides platformResolver, + ) { + ProvidePreferences( + preferencesHolder, + ) { + ProvideAssistedFactory( + viewModelHolder.factory, + ) { + ProvideWindowInsets( + windowInsetsAnimationsEnabled = true ) { - ProvideWindowInsets( - windowInsetsAnimationsEnabled = true - ) { - Router( - navController = navController - ) - } + Router( + navController = navController + ) } } } } - intent.data?.let { - onDeeplink(it) - } } private fun onDeeplink(it: Uri) { diff --git a/app/src/main/kotlin/com/twidere/twiderex/preferences/ProvidePreferences.kt b/app/src/main/kotlin/com/twidere/twiderex/preferences/ProvidePreferences.kt index 7fc1c68b4..975f58c52 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/preferences/ProvidePreferences.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/preferences/ProvidePreferences.kt @@ -32,6 +32,10 @@ import com.twidere.twiderex.preferences.proto.AppearancePreferences import com.twidere.twiderex.preferences.proto.DisplayPreferences import com.twidere.twiderex.preferences.proto.MiscPreferences import com.twidere.twiderex.ui.LocalVideoPlayback +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.map import javax.inject.Inject @@ -42,7 +46,15 @@ data class PreferencesHolder @Inject constructor( val appearancePreferences: DataStore, val displayPreferences: DataStore, val miscPreferences: DataStore -) +) { + suspend fun warmup() = coroutineScope { + awaitAll( + async { appearancePreferences.data.firstOrNull() }, + async { displayPreferences.data.firstOrNull() }, + async { miscPreferences.data.firstOrNull() }, + ) + } +} @Composable fun ProvidePreferences( From 58af5376772cbf9ac9f401780f892bc524a6ed2c Mon Sep 17 00:00:00 2001 From: Tlaster Date: Wed, 28 Jul 2021 15:58:56 +0800 Subject: [PATCH 095/137] improve url parsing for mastodon host --- .../twiderex/viewmodel/mastodon/MastodonSignInViewModel.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/mastodon/MastodonSignInViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/mastodon/MastodonSignInViewModel.kt index 4e91467b7..4ebd29af4 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/mastodon/MastodonSignInViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/mastodon/MastodonSignInViewModel.kt @@ -40,6 +40,7 @@ import com.twidere.twiderex.utils.json import dagger.assisted.AssistedInject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch +import java.net.URI class MastodonSignInViewModel @AssistedInject constructor( private val repository: AccountRepository, @@ -63,9 +64,12 @@ class MastodonSignInViewModel @AssistedInject constructor( finished: (success: Boolean) -> Unit, ) = viewModelScope.launch { loading.value = true + val realHost = runCatching { + URI.create(host) + }.getOrNull()?.takeIf { !it.scheme.isNullOrEmpty() }?.toString() ?: "https://$host" runCatching { val service = MastodonOAuthService( - host = "https://$host", + host = realHost, client_name = "Twidere X", website = "https://github.com/TwidereProject/TwidereX-Android", redirect_uri = RootDeepLinksRoute.Callback.SignIn.Mastodon, From 5ab78c799a3b0fa322d935c73f716c0326d13e04 Mon Sep 17 00:00:00 2001 From: itsMimao Date: Wed, 28 Jul 2021 17:39:36 +0800 Subject: [PATCH 096/137] Jobs throws error instead of bool to present failed --- .../com/twidere/twiderex/di/JobModule.kt | 14 ++++++-- .../twiderex/extensions/WorkExtension.kt | 25 --------------- .../twiderex/jobs/common/DownloadMediaJob.kt | 16 ++++------ .../twiderex/jobs/common/NotificationJob.kt | 12 +++---- .../twiderex/jobs/common/ShareMediaJob.kt | 18 +++++++++-- .../twiderex/jobs/compose/ComposeJob.kt | 15 +++------ .../jobs/database/DeleteDbStatusJob.kt | 9 ++---- .../jobs/dm/DirectMessageDeleteJob.kt | 32 ++++++++----------- .../twiderex/jobs/dm/DirectMessageFetchJob.kt | 30 +++++++---------- .../twiderex/jobs/dm/DirectMessageSendJob.kt | 16 ++++------ .../twiderex/jobs/draft/RemoveDraftJob.kt | 9 ++---- .../twiderex/jobs/draft/SaveDraftJob.kt | 6 ++-- .../twiderex/jobs/status/DeleteStatusJob.kt | 15 +++------ .../twiderex/jobs/status/MastodonVoteJob.kt | 15 ++++----- .../twidere/twiderex/jobs/status/StatusJob.kt | 1 - .../com/twidere/twiderex/kmp/FileResolver.kt | 3 ++ .../kmp/android/AndroidFileResolver.kt | 5 +++ .../twiderex/worker/DownloadMediaWorker.kt | 21 ++++++------ .../twiderex/worker/NotificationWorker.kt | 13 +++++--- .../twiderex/worker/ShareMediaWorker.kt | 25 ++++++--------- .../twiderex/worker/compose/ComposeWorker.kt | 15 ++++++--- .../worker/database/DeleteDbStatusWorker.kt | 10 ++++-- .../worker/dm/DirectMessageDeleteWorker.kt | 15 ++++++--- .../worker/dm/DirectMessageFetchWorker.kt | 8 +++-- .../worker/dm/DirectMessageSendWorker.kt | 15 ++++++--- .../worker/draft/RemoveDraftWorker.kt | 13 +++++--- .../twiderex/worker/draft/SaveDraftWorker.kt | 9 ++++-- .../worker/status/DeleteStatusWorker.kt | 15 ++++++--- .../worker/status/MastodonVoteWorker.kt | 17 ++++++---- 29 files changed, 208 insertions(+), 209 deletions(-) delete mode 100644 app/src/main/kotlin/com/twidere/twiderex/extensions/WorkExtension.kt diff --git a/app/src/main/kotlin/com/twidere/twiderex/di/JobModule.kt b/app/src/main/kotlin/com/twidere/twiderex/di/JobModule.kt index 08a19dfc4..86883f273 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/di/JobModule.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/di/JobModule.kt @@ -60,15 +60,23 @@ import dagger.hilt.components.SingletonComponent object JobModule { @Provides - fun provideShareMediaJob(): ShareMediaJob = ShareMediaJob() + fun provideShareMediaJob( + fileResolver: FileResolver, + remoteNavigator: RemoteNavigator + ): ShareMediaJob = ShareMediaJob( + fileResolver = fileResolver, + remoteNavigator = remoteNavigator + ) @Provides fun provideDownloadMediaJob( accountRepository: AccountRepository, - inAppNotification: InAppNotification + inAppNotification: InAppNotification, + fileResolver: FileResolver, ): DownloadMediaJob = DownloadMediaJob( accountRepository = accountRepository, - inAppNotification = inAppNotification + inAppNotification = inAppNotification, + fileResolver = fileResolver ) @Provides diff --git a/app/src/main/kotlin/com/twidere/twiderex/extensions/WorkExtension.kt b/app/src/main/kotlin/com/twidere/twiderex/extensions/WorkExtension.kt deleted file mode 100644 index 4aee73e55..000000000 --- a/app/src/main/kotlin/com/twidere/twiderex/extensions/WorkExtension.kt +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Twidere X - * - * Copyright (C) 2020-2021 Tlaster - * - * This file is part of Twidere X. - * - * Twidere X 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. - * - * Twidere X 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 Twidere X. If not, see . - */ -package com.twidere.twiderex.extensions - -import androidx.work.ListenableWorker - -internal fun Boolean.toWorkResult() = if (this) ListenableWorker.Result.success() else ListenableWorker.Result.failure() diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/common/DownloadMediaJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/common/DownloadMediaJob.kt index 600ead879..ef7cf3729 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/jobs/common/DownloadMediaJob.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/common/DownloadMediaJob.kt @@ -22,35 +22,33 @@ package com.twidere.twiderex.jobs.common import com.twidere.services.microblog.DownloadMediaService import com.twidere.twiderex.R +import com.twidere.twiderex.kmp.FileResolver import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.notification.InAppNotification import com.twidere.twiderex.repository.AccountRepository -import java.io.OutputStream class DownloadMediaJob( private val accountRepository: AccountRepository, private val inAppNotification: InAppNotification, + private val fileResolver: FileResolver, ) { suspend fun execute( target: String, source: String, accountKey: MicroBlogKey, - openOutputStream: (target: String) -> OutputStream? - ): Boolean { + ) { val accountDetails = accountKey.let { accountRepository.findByAccountKey(accountKey = it) }?.let { accountRepository.getAccountDetails(it) - } ?: return false + } ?: throw Error("Can't find any account matches:$$accountKey") val service = accountDetails.service if (service !is DownloadMediaService) { - return false + throw Error("Service must be DownloadMediaService") } - openOutputStream(target)?.use { + fileResolver.openOutputStream(target)?.use { service.download(target = source).copyTo(it) - } ?: return false - + } ?: throw Error("Download failed") inAppNotification.show(R.string.common_controls_actions_save) - return true } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/common/NotificationJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/common/NotificationJob.kt index 979834830..cf2ce227c 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/jobs/common/NotificationJob.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/common/NotificationJob.kt @@ -39,7 +39,7 @@ import com.twidere.twiderex.notification.NotificationChannelSpec import com.twidere.twiderex.notification.notificationChannelId import com.twidere.twiderex.repository.AccountRepository import com.twidere.twiderex.repository.NotificationRepository -import kotlinx.coroutines.MainScope +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.first import kotlinx.coroutines.joinAll import kotlinx.coroutines.launch @@ -50,17 +50,14 @@ class NotificationJob( private val accountRepository: AccountRepository, private val notificationManager: AppNotificationManager, ) { - val scope = MainScope() - suspend fun execute(enableNotification: Boolean): Boolean { - return if (!enableNotification) { - true - } else { + suspend fun execute(enableNotification: Boolean) = coroutineScope { + if (!enableNotification) { accountRepository.getAccounts().map { accountRepository.getAccountDetails(it) } .filter { it.preferences.isNotificationEnabled.first() } .map { account -> - scope.launch { + launch { val activities = try { repository.activities(account) } catch (e: Throwable) { @@ -72,7 +69,6 @@ class NotificationJob( } } }.joinAll() - true } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/common/ShareMediaJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/common/ShareMediaJob.kt index 88fc83ffe..50efd726b 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/jobs/common/ShareMediaJob.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/common/ShareMediaJob.kt @@ -20,8 +20,20 @@ */ package com.twidere.twiderex.jobs.common -class ShareMediaJob { - fun execute(target: String, shareMedia: (target: String) -> Boolean): Boolean { - return shareMedia(target) +import com.twidere.twiderex.kmp.FileResolver +import com.twidere.twiderex.kmp.RemoteNavigator + +class ShareMediaJob( + private val fileResolver: FileResolver, + private val remoteNavigator: RemoteNavigator +) { + fun execute(target: String) { + fileResolver.getMimeType(target)?.let { type -> + remoteNavigator.shareMedia( + filePath = target, + mimeType = type, + ) + true + } ?: throw Error("Unresolved file:$target") } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/compose/ComposeJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/compose/ComposeJob.kt index e8b6870f0..ceb4ede09 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/jobs/compose/ComposeJob.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/compose/ComposeJob.kt @@ -43,7 +43,7 @@ abstract class ComposeJob( private val exifScrambler: ExifScrambler, private val remoteNavigator: RemoteNavigator, ) { - suspend fun execute(composeData: ComposeData, accountKey: MicroBlogKey): Boolean { + suspend fun execute(composeData: ComposeData, accountKey: MicroBlogKey) { val builder = AppNotification .Builder(NotificationChannelSpec.BackgroundProgresses.id) .setContentTitle(applicationContext.getString(R.string.common_alerts_tweet_sending_title)) @@ -54,17 +54,13 @@ abstract class ComposeJob( accountRepository.findByAccountKey(accountKey = it) }?.let { accountRepository.getAccountDetails(it) - } ?: return false + } ?: throw Error("Can't find any account matches:$$accountKey") val notificationId = composeData.draftId.hashCode() @Suppress("UNCHECKED_CAST") - val service = try { - accountDetails.service as T - } catch (e: ClassCastException) { - return false - } + val service = accountDetails.service as T notificationManager.notify(notificationId, builder.build()) - return try { + try { val mediaIds = arrayListOf() val images = composeData.images images.forEachIndexed { index, uri -> @@ -94,7 +90,6 @@ abstract class ComposeJob( fromBackground = true ) } - true } catch (e: Throwable) { e.printStackTrace() builder.setOngoing(false) @@ -104,7 +99,7 @@ abstract class ComposeJob( .setContentText(composeData.content) .setDeepLink(RootDeepLinksRoute.Draft(composeData.draftId)) notificationManager.notify(notificationId, builder.build()) - false + throw e } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/database/DeleteDbStatusJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/database/DeleteDbStatusJob.kt index 7468dd8c7..66a1cce13 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/jobs/database/DeleteDbStatusJob.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/database/DeleteDbStatusJob.kt @@ -26,12 +26,7 @@ import com.twidere.twiderex.repository.StatusRepository class DeleteDbStatusJob( private val statusRepository: StatusRepository ) { - suspend fun execute(statusKey: MicroBlogKey): Boolean { - try { - statusRepository.removeStatus(statusKey) - } catch (e: Exception) { - return false - } - return true + suspend fun execute(statusKey: MicroBlogKey) { + statusRepository.removeStatus(statusKey) } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/dm/DirectMessageDeleteJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/dm/DirectMessageDeleteJob.kt index c00fdd78f..d4dbc834e 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/jobs/dm/DirectMessageDeleteJob.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/dm/DirectMessageDeleteJob.kt @@ -30,24 +30,18 @@ class DirectMessageDeleteJob( private val repository: DirectMessageRepository, private val accountRepository: AccountRepository, ) { - suspend fun execute(deleteData: DirectMessageDeleteData, accountKey: MicroBlogKey): Boolean { - return try { - val accountDetails = accountKey.let { - accountRepository.findByAccountKey(accountKey = it) - }?.let { - accountRepository.getAccountDetails(it) - } ?: return false - repository.deleteMessage( - accountKey = deleteData.accountKey, - conversationKey = deleteData.conversationKey, - messageId = deleteData.messageId, - messageKey = deleteData.messageKey, - service = accountDetails.service as DirectMessageService - ) - true - } catch (e: Throwable) { - e.printStackTrace() - false - } + suspend fun execute(deleteData: DirectMessageDeleteData, accountKey: MicroBlogKey) { + val accountDetails = accountKey.let { + accountRepository.findByAccountKey(accountKey = it) + }?.let { + accountRepository.getAccountDetails(it) + } ?: throw Error("Can't find any account matches:$$accountKey") + repository.deleteMessage( + accountKey = deleteData.accountKey, + conversationKey = deleteData.conversationKey, + messageId = deleteData.messageId, + messageKey = deleteData.messageKey, + service = accountDetails.service as DirectMessageService + ) } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/dm/DirectMessageFetchJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/dm/DirectMessageFetchJob.kt index 2a9b4ba8b..55206df52 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/jobs/dm/DirectMessageFetchJob.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/dm/DirectMessageFetchJob.kt @@ -42,24 +42,18 @@ class DirectMessageFetchJob( private val accountRepository: AccountRepository, private val notificationManager: AppNotificationManager, ) { - suspend fun execute(): Boolean { - return try { - accountRepository.activeAccount.firstOrNull()?.takeIf { - accountRepository.getAccountPreferences(it.accountKey).isNotificationEnabled.first() - }?.let { account -> - val result = repository.checkNewMessages( - accountKey = account.accountKey, - service = account.service as DirectMessageService, - lookupService = account.service as LookupService - ) - result.forEach { - notification(account = account, message = it) - } - } ?: throw Error() - true - } catch (e: Throwable) { - e.printStackTrace() - false + suspend fun execute() { + accountRepository.activeAccount.firstOrNull()?.takeIf { + accountRepository.getAccountPreferences(it.accountKey).isNotificationEnabled.first() + }?.let { account -> + val result = repository.checkNewMessages( + accountKey = account.accountKey, + service = account.service as DirectMessageService, + lookupService = account.service as LookupService + ) + result.forEach { + notification(account = account, message = it) + } } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/dm/DirectMessageSendJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/dm/DirectMessageSendJob.kt index 7882a8bcb..adad5e9cf 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/jobs/dm/DirectMessageSendJob.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/dm/DirectMessageSendJob.kt @@ -52,21 +52,18 @@ abstract class DirectMessageSendJob( private val notificationManager: AppNotificationManager, protected val fileResolver: FileResolver, ) { - suspend fun execute(sendData: DirectMessageSendData, accountKey: MicroBlogKey): Boolean { + suspend fun execute(sendData: DirectMessageSendData, accountKey: MicroBlogKey) { val accountDetails = accountKey.let { accountRepository.findByAccountKey(accountKey = it) }?.let { accountRepository.getAccountDetails(it) - } ?: return false + } ?: throw Error("can't find any account matches:$accountKey") val notificationId = sendData.draftMessageKey.hashCode() @Suppress("UNCHECKED_CAST") - val service = try { - accountDetails.service as T - } catch (e: ClassCastException) { - return false - } + val service = accountDetails.service as T + var draftEvent: DbDMEventWithAttachments? = null - return try { + try { val images = sendData.images draftEvent = getDraft(sendData, images, accountDetails) ?: throw IllegalArgumentException() // val exifScrambler = ExifScrambler(context) @@ -82,7 +79,6 @@ abstract class DirectMessageSendJob( } val dbEvent = sendMessage(service, sendData, mediaIds) updateDb(draftEvent, dbEvent) - true } catch (e: Throwable) { e.printStackTrace() draftEvent?.let { @@ -99,7 +95,7 @@ abstract class DirectMessageSendJob( .setContentText(sendData.text) .setDeepLink(RootDeepLinksRoute.Conversation(sendData.conversationKey)) notificationManager.notify(notificationId, builder.build()) - false + throw e } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/draft/RemoveDraftJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/draft/RemoveDraftJob.kt index f596a4631..b2f675cd6 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/jobs/draft/RemoveDraftJob.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/draft/RemoveDraftJob.kt @@ -25,12 +25,7 @@ import com.twidere.twiderex.repository.DraftRepository class RemoveDraftJob( private val repository: DraftRepository, ) { - suspend fun execute(draftId: String): Boolean { - return try { - repository.remove(draftId = draftId) - true - } catch (e: Throwable) { - false - } + suspend fun execute(draftId: String) { + repository.remove(draftId = draftId) } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/draft/SaveDraftJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/draft/SaveDraftJob.kt index 455b734b6..6f044dced 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/jobs/draft/SaveDraftJob.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/draft/SaveDraftJob.kt @@ -29,8 +29,8 @@ class SaveDraftJob( private val repository: DraftRepository, private val inAppNotification: InAppNotification, ) { - suspend fun execute(data: ComposeData): Boolean { - return with(data) { + suspend fun execute(data: ComposeData) { + with(data) { try { repository.addOrUpgrade( content, @@ -43,7 +43,7 @@ class SaveDraftJob( true } catch (e: Throwable) { e.notify(inAppNotification) - false + throw e } } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/status/DeleteStatusJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/status/DeleteStatusJob.kt index d129913ad..3ffa66a87 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/jobs/status/DeleteStatusJob.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/status/DeleteStatusJob.kt @@ -20,7 +20,6 @@ */ package com.twidere.twiderex.jobs.status -import com.twidere.services.http.MicroBlogException import com.twidere.services.microblog.StatusService import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.notification.InAppNotification @@ -36,24 +35,20 @@ class DeleteStatusJob( suspend fun execute( accountKey: MicroBlogKey, statusKey: MicroBlogKey - ): Boolean { + ) { val status = statusKey.let { statusRepository.loadFromCache(it, accountKey = accountKey) - } ?: return false + } ?: throw Error("Can't find any status matches:$statusKey") val service = accountRepository.findByAccountKey(accountKey)?.let { accountRepository.getAccountDetails(it) }?.let { it.service as? StatusService - } ?: return false - return try { + } ?: throw Error() + try { service.delete(status.statusId) - true - } catch (e: MicroBlogException) { - e.notify(inAppNotification) - false } catch (e: Throwable) { e.notify(inAppNotification) - false + throw e } } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/status/MastodonVoteJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/status/MastodonVoteJob.kt index 12c06b28f..9d44f0b98 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/jobs/status/MastodonVoteJob.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/status/MastodonVoteJob.kt @@ -33,18 +33,18 @@ class MastodonVoteJob( private val statusRepository: StatusRepository, private val inAppNotification: InAppNotification, ) { - suspend fun execute(votes: List, accountKey: MicroBlogKey, statusKey: MicroBlogKey): Boolean { - val status = statusRepository.loadFromCache(statusKey, accountKey = accountKey) ?: return false + suspend fun execute(votes: List, accountKey: MicroBlogKey, statusKey: MicroBlogKey) { + val status = statusRepository.loadFromCache(statusKey, accountKey = accountKey) ?: throw Error("Can't find any status matches:$statusKey") if (status.mastodonExtra?.poll == null || status.platformType != PlatformType.Mastodon) { - return true + throw Error() } val service = accountRepository.findByAccountKey(accountKey)?.let { accountRepository.getAccountDetails(it) }?.let { it.service as? MastodonService - } ?: return false + } ?: throw Error() - val pollId = status.mastodonExtra.poll.id ?: return false + val pollId = status.mastodonExtra.poll.id ?: throw Error("Poll id is null") val originPoll = status.mastodonExtra.poll statusRepository.updateStatus(statusKey = status.statusKey) { it.mastodonExtra = status.mastodonExtra.copy( @@ -54,14 +54,13 @@ class MastodonVoteJob( ) ) } - return try { + try { val newPoll = service.vote(pollId, votes) statusRepository.updateStatus(statusKey = status.statusKey) { it.mastodonExtra = status.mastodonExtra.copy( poll = newPoll ) } - true } catch (e: Throwable) { statusRepository.updateStatus(statusKey = status.statusKey) { it.mastodonExtra = status.mastodonExtra.copy( @@ -69,7 +68,7 @@ class MastodonVoteJob( ) } e.notify(inAppNotification) - false + throw e } } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/status/StatusJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/status/StatusJob.kt index dbe049cf6..63eaeb4eb 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/jobs/status/StatusJob.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/status/StatusJob.kt @@ -36,7 +36,6 @@ abstract class StatusJob( private val statusRepository: StatusRepository, private val inAppNotification: InAppNotification, ) { - @Throws suspend fun execute(accountKey: MicroBlogKey, statusKey: MicroBlogKey): StatusResult { val status = statusKey.let { statusRepository.loadFromCache(it, accountKey = accountKey) diff --git a/app/src/main/kotlin/com/twidere/twiderex/kmp/FileResolver.kt b/app/src/main/kotlin/com/twidere/twiderex/kmp/FileResolver.kt index 5ce3cf8db..4df89a1ba 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/kmp/FileResolver.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/kmp/FileResolver.kt @@ -21,6 +21,7 @@ package com.twidere.twiderex.kmp import java.io.InputStream +import java.io.OutputStream interface FileResolver { fun getMimeType(file: String): String? @@ -28,4 +29,6 @@ interface FileResolver { fun getFileSize(file: String): Long? fun openInputStream(file: String): InputStream? + + fun openOutputStream(file: String): OutputStream? } diff --git a/app/src/main/kotlin/com/twidere/twiderex/kmp/android/AndroidFileResolver.kt b/app/src/main/kotlin/com/twidere/twiderex/kmp/android/AndroidFileResolver.kt index ea2e4d94b..0cfc7e6a1 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/kmp/android/AndroidFileResolver.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/kmp/android/AndroidFileResolver.kt @@ -24,6 +24,7 @@ import android.content.ContentResolver import android.net.Uri import com.twidere.twiderex.kmp.FileResolver import java.io.InputStream +import java.io.OutputStream class AndroidFileResolver(private val contentResolver: ContentResolver) : FileResolver { override fun getMimeType(file: String): String? { @@ -37,4 +38,8 @@ class AndroidFileResolver(private val contentResolver: ContentResolver) : FileRe override fun openInputStream(file: String): InputStream? { return contentResolver.openInputStream(Uri.parse(file)) } + + override fun openOutputStream(file: String): OutputStream? { + return contentResolver.openOutputStream(Uri.parse(file)) + } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/DownloadMediaWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/DownloadMediaWorker.kt index 2932f174e..e49de1745 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/DownloadMediaWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/DownloadMediaWorker.kt @@ -20,7 +20,6 @@ */ package com.twidere.twiderex.worker -import android.content.ContentResolver import android.content.Context import android.net.Uri import androidx.hilt.work.HiltWorker @@ -28,7 +27,6 @@ import androidx.work.CoroutineWorker import androidx.work.Data import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkerParameters -import com.twidere.twiderex.extensions.toWorkResult import com.twidere.twiderex.jobs.common.DownloadMediaJob import com.twidere.twiderex.model.MicroBlogKey import dagger.assisted.Assisted @@ -38,7 +36,6 @@ import dagger.assisted.AssistedInject class DownloadMediaWorker @AssistedInject constructor( @Assisted context: Context, @Assisted workerParams: WorkerParameters, - private val contentResolver: ContentResolver, private val downloadMediaJob: DownloadMediaJob, ) : CoroutineWorker(context, workerParams) { @@ -64,12 +61,16 @@ class DownloadMediaWorker @AssistedInject constructor( val accountKey = inputData.getString("accountKey")?.let { MicroBlogKey.valueOf(it) } ?: return Result.failure() - return downloadMediaJob.execute( - target = target, - source = source, - accountKey = accountKey - ) { - contentResolver.openOutputStream(Uri.parse(it)) - }.toWorkResult() + return try { + downloadMediaJob.execute( + target = target, + source = source, + accountKey = accountKey + ) + Result.success() + } catch (e: Throwable) { + e.printStackTrace() + Result.failure() + } } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/NotificationWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/NotificationWorker.kt index 2e3155dc8..cdc1f91a9 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/NotificationWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/NotificationWorker.kt @@ -25,12 +25,10 @@ import androidx.datastore.core.DataStore import androidx.hilt.work.HiltWorker import androidx.work.CoroutineWorker import androidx.work.WorkerParameters -import com.twidere.twiderex.extensions.toWorkResult import com.twidere.twiderex.jobs.common.NotificationJob import com.twidere.twiderex.preferences.proto.NotificationPreferences import dagger.assisted.Assisted import dagger.assisted.AssistedInject -import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.first @HiltWorker @@ -41,8 +39,13 @@ class NotificationWorker @AssistedInject constructor( private val notificationJob: NotificationJob ) : CoroutineWorker(appContext, params) { - override suspend fun doWork(): Result = coroutineScope { - notificationJob.execute(notificationPreferences.data.first().enableNotification) - .toWorkResult() + override suspend fun doWork(): Result { + return try { + notificationJob.execute(notificationPreferences.data.first().enableNotification) + Result.success() + } catch (e: Throwable) { + e.printStackTrace() + Result.failure() + } } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/ShareMediaWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/ShareMediaWorker.kt index 0c13a1c89..ddfea7fde 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/ShareMediaWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/ShareMediaWorker.kt @@ -28,8 +28,6 @@ import androidx.work.CoroutineWorker import androidx.work.Data import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkerParameters -import com.twidere.twiderex.extensions.shareMedia -import com.twidere.twiderex.extensions.toWorkResult import com.twidere.twiderex.jobs.common.ShareMediaJob import dagger.assisted.Assisted import dagger.assisted.AssistedInject @@ -56,19 +54,14 @@ class ShareMediaWorker @AssistedInject constructor( override suspend fun doWork(): Result { val target = inputData.getString("target") ?: return Result.failure() - return shareMediaJob.execute( - target - ) { - Uri.parse(it).let { uri -> - contentResolver.getType(uri)?.let { type -> - context.shareMedia( - uri = uri, - mimeType = type, - fromOutsideOfActivity = true - ) - true - } ?: false - } - }.toWorkResult() + return try { + shareMediaJob.execute( + target + ) + Result.success() + } catch (e: Throwable) { + e.printStackTrace() + Result.failure() + } } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/compose/ComposeWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/compose/ComposeWorker.kt index 0c79e718c..ed58a5d60 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/compose/ComposeWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/compose/ComposeWorker.kt @@ -24,7 +24,6 @@ import android.content.Context import androidx.work.CoroutineWorker import androidx.work.WorkerParameters import com.twidere.services.microblog.MicroBlogService -import com.twidere.twiderex.extensions.toWorkResult import com.twidere.twiderex.jobs.compose.ComposeJob import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.toComposeData @@ -40,9 +39,15 @@ abstract class ComposeWorker( val accountKey = inputData.getString("accountKey")?.let { MicroBlogKey.valueOf(it) } ?: return Result.failure() - return composeJob.execute( - composeData = composeData, - accountKey = accountKey - ).toWorkResult() + return try { + composeJob.execute( + composeData = composeData, + accountKey = accountKey + ) + Result.success() + } catch (e: Throwable) { + e.printStackTrace() + Result.failure() + } } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/database/DeleteDbStatusWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/database/DeleteDbStatusWorker.kt index f3549bb90..062d7f450 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/database/DeleteDbStatusWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/database/DeleteDbStatusWorker.kt @@ -26,7 +26,6 @@ import androidx.work.CoroutineWorker import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkerParameters import androidx.work.workDataOf -import com.twidere.twiderex.extensions.toWorkResult import com.twidere.twiderex.jobs.database.DeleteDbStatusJob import com.twidere.twiderex.model.MicroBlogKey import dagger.assisted.Assisted @@ -54,7 +53,12 @@ class DeleteDbStatusWorker @AssistedInject constructor( val statusKey = inputData.getString("statusKey")?.let { MicroBlogKey.valueOf(it) } ?: return Result.failure() - return deleteDbStatusJob.execute(statusKey) - .toWorkResult() + return try { + deleteDbStatusJob.execute(statusKey) + Result.success() + } catch (e: Throwable) { + e.printStackTrace() + Result.failure() + } } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageDeleteWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageDeleteWorker.kt index 6a8553888..20993a446 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageDeleteWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageDeleteWorker.kt @@ -25,7 +25,6 @@ import androidx.hilt.work.HiltWorker import androidx.work.CoroutineWorker import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkerParameters -import com.twidere.twiderex.extensions.toWorkResult import com.twidere.twiderex.jobs.dm.DirectMessageDeleteJob import com.twidere.twiderex.model.DirectMessageDeleteData import com.twidere.twiderex.model.MicroBlogKey @@ -56,9 +55,15 @@ class DirectMessageDeleteWorker @AssistedInject constructor( val accountKey = inputData.getString("accountKey")?.let { MicroBlogKey.valueOf(it) } ?: return Result.failure() - return deleteJob.execute( - deleteData = deleteData, - accountKey = accountKey - ).toWorkResult() + return try { + deleteJob.execute( + deleteData = deleteData, + accountKey = accountKey + ) + Result.success() + } catch (e: Throwable) { + e.printStackTrace() + Result.failure() + } } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageFetchWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageFetchWorker.kt index adfd31138..fc2205c9a 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageFetchWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageFetchWorker.kt @@ -25,7 +25,6 @@ import androidx.hilt.work.HiltWorker import androidx.work.CoroutineWorker import androidx.work.PeriodicWorkRequestBuilder import androidx.work.WorkerParameters -import com.twidere.twiderex.extensions.toWorkResult import com.twidere.twiderex.jobs.dm.DirectMessageFetchJob import dagger.assisted.Assisted import dagger.assisted.AssistedInject @@ -46,6 +45,11 @@ class DirectMessageFetchWorker @AssistedInject constructor( } override suspend fun doWork(): Result { - return directMessageFetchJob.execute().toWorkResult() + try { + directMessageFetchJob.execute() + } catch (e: Throwable) { + // no need to handle this error + } + return Result.success() } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageSendWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageSendWorker.kt index 9946ac68b..4519f2653 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageSendWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageSendWorker.kt @@ -23,7 +23,6 @@ package com.twidere.twiderex.worker.dm import android.content.Context import androidx.work.CoroutineWorker import androidx.work.WorkerParameters -import com.twidere.twiderex.extensions.toWorkResult import com.twidere.twiderex.jobs.dm.DirectMessageSendJob import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.toDirectMessageSendData @@ -42,9 +41,15 @@ abstract class DirectMessageSendWorker( val accountKey = inputData.getString("accountKey")?.let { MicroBlogKey.valueOf(it) } ?: return Result.failure() - return directMessageSendJob.execute( - sendData = sendData, - accountKey = accountKey - ).toWorkResult() + return try { + directMessageSendJob.execute( + sendData = sendData, + accountKey = accountKey + ) + Result.success() + } catch (e: Throwable) { + e.printStackTrace() + Result.failure() + } } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/draft/RemoveDraftWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/draft/RemoveDraftWorker.kt index ccc630d6f..ac1933fdf 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/draft/RemoveDraftWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/draft/RemoveDraftWorker.kt @@ -26,7 +26,6 @@ import androidx.work.CoroutineWorker import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkerParameters import androidx.work.workDataOf -import com.twidere.twiderex.extensions.toWorkResult import com.twidere.twiderex.jobs.draft.RemoveDraftJob import dagger.assisted.Assisted import dagger.assisted.AssistedInject @@ -50,8 +49,14 @@ class RemoveDraftWorker @AssistedInject constructor( override suspend fun doWork(): Result { val draftId = inputData.getString("draftId") ?: return Result.failure() - return removeDraftJob.execute( - draftId = draftId - ).toWorkResult() + return try { + removeDraftJob.execute( + draftId = draftId + ) + Result.success() + } catch (e: Throwable) { + e.printStackTrace() + Result.failure() + } } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/draft/SaveDraftWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/draft/SaveDraftWorker.kt index 93eb47c11..8b210e10a 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/draft/SaveDraftWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/draft/SaveDraftWorker.kt @@ -25,7 +25,6 @@ import androidx.hilt.work.HiltWorker import androidx.work.CoroutineWorker import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkerParameters -import com.twidere.twiderex.extensions.toWorkResult import com.twidere.twiderex.jobs.draft.SaveDraftJob import com.twidere.twiderex.model.ComposeData import com.twidere.twiderex.model.toComposeData @@ -48,6 +47,12 @@ class SaveDraftWorker @AssistedInject constructor( override suspend fun doWork(): Result { val data = inputData.toComposeData() - return saveDraftJob.execute(data = data).toWorkResult() + return try { + saveDraftJob.execute(data = data) + Result.success() + } catch (e: Throwable) { + e.printStackTrace() + Result.failure() + } } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/status/DeleteStatusWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/status/DeleteStatusWorker.kt index e0ea798bc..065c4ac1f 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/status/DeleteStatusWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/status/DeleteStatusWorker.kt @@ -26,7 +26,6 @@ import androidx.work.CoroutineWorker import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkerParameters import androidx.work.workDataOf -import com.twidere.twiderex.extensions.toWorkResult import com.twidere.twiderex.jobs.status.DeleteStatusJob import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.ui.UiStatus @@ -60,9 +59,15 @@ class DeleteStatusWorker @AssistedInject constructor( val statusKey = inputData.getString("statusKey")?.let { MicroBlogKey.valueOf(it) } ?: return Result.failure() - return deleteStatusJob.execute( - accountKey = accountKey, - statusKey = statusKey - ).toWorkResult() + return try { + deleteStatusJob.execute( + accountKey = accountKey, + statusKey = statusKey + ) + Result.success() + } catch (e: Throwable) { + e.printStackTrace() + Result.failure() + } } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/status/MastodonVoteWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/status/MastodonVoteWorker.kt index c2deccf1a..ae5351043 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/status/MastodonVoteWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/status/MastodonVoteWorker.kt @@ -26,7 +26,6 @@ import androidx.work.CoroutineWorker import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkerParameters import androidx.work.workDataOf -import com.twidere.twiderex.extensions.toWorkResult import com.twidere.twiderex.jobs.status.MastodonVoteJob import com.twidere.twiderex.model.MicroBlogKey import dagger.assisted.Assisted @@ -65,10 +64,16 @@ class MastodonVoteWorker @AssistedInject constructor( val statusKey = inputData.getString("statusKey")?.let { MicroBlogKey.valueOf(it) } ?: return Result.failure() - return mastodonVoteJob.execute( - votes = votes, - accountKey = accountKey, - statusKey = statusKey - ).toWorkResult() + return try { + mastodonVoteJob.execute( + votes = votes, + accountKey = accountKey, + statusKey = statusKey + ) + Result.success() + } catch (e: Throwable) { + e.printStackTrace() + Result.failure() + } } } From 4ad5304a8de88883f1a6358cdffea15cadfa05a3 Mon Sep 17 00:00:00 2001 From: itsMimao Date: Wed, 28 Jul 2021 17:51:04 +0800 Subject: [PATCH 097/137] fixed share media failed issue --- .../kotlin/com/twidere/twiderex/jobs/common/ShareMediaJob.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/common/ShareMediaJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/common/ShareMediaJob.kt index 50efd726b..96af576a9 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/jobs/common/ShareMediaJob.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/common/ShareMediaJob.kt @@ -32,6 +32,7 @@ class ShareMediaJob( remoteNavigator.shareMedia( filePath = target, mimeType = type, + fromBackground = true ) true } ?: throw Error("Unresolved file:$target") From fd267c417f94b8129e34e2a901170e840d255e43 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Thu, 29 Jul 2021 07:13:43 +0800 Subject: [PATCH 098/137] update compose to stable --- buildSrc/src/main/kotlin/Versions.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 909c9c175..c2a02a776 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -13,18 +13,18 @@ object Versions { } const val ksp = "1.5.10-1.0.0-beta02" - const val agp = "7.0.0-rc01" + const val agp = "7.0.0" const val spotless = "5.12.5" const val ktlint = "0.41.0" const val hilt = "2.37" const val okhttp = "4.9.1" const val retrofit2 = "2.9.0" const val hson = "0.1.4" - const val compose = "1.0.0-rc02" + const val compose = "1.0.0" const val constraintLayout = "1.0.0-beta01" const val paging = "3.1.0-alpha03" const val paging_compose = "1.0.0-alpha12" - const val activity = "1.3.0-rc02" + const val activity = "1.3.0" const val datastore = "1.0.0-rc01" const val androidx_hilt = "1.0.0" const val room = "2.4.0-alpha04" @@ -37,7 +37,7 @@ object Versions { const val nestedScrollView = "0.7.0" const val startup = "1.1.0-rc01" const val coil = "1.3.0" - const val accompanist = "0.14.0" + const val accompanist = "0.15.0" const val androidx_exifinterface = "1.3.2" const val exoplayer = "2.14.1" const val browser = "1.3.0" From 2bf9970c435fb7285abaa5a64b54b7d3f3e53381 Mon Sep 17 00:00:00 2001 From: itsMimao Date: Thu, 29 Jul 2021 12:02:43 +0800 Subject: [PATCH 099/137] fixed compose replyToUser keeps emit issue --- .../twiderex/viewmodel/compose/ComposeViewModel.kt | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/compose/ComposeViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/compose/ComposeViewModel.kt index 63d88c205..3a052b304 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/compose/ComposeViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/compose/ComposeViewModel.kt @@ -33,6 +33,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextRange import androidx.compose.ui.text.input.TextFieldValue import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import androidx.work.WorkManager import com.twidere.services.mastodon.model.Emoji import com.twidere.services.mastodon.model.Visibility @@ -59,10 +60,12 @@ import com.twitter.twittertext.Extractor import dagger.assisted.Assisted import dagger.assisted.AssistedInject import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.emitAll import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn import java.util.UUID enum class ComposeType { @@ -263,7 +266,14 @@ open class ComposeViewModel @AssistedInject constructor( } else { emptyList() } - } + // return a stateFlow to emit latest state + // WhileSubscribed will unsubscribe upstream flow when there is no subscribers + // official suggest use 5s as stop timeout + }.stateIn( + viewModelScope, + SharingStarted.WhileSubscribed(5000), + emptyList() + ) val voteState = MutableStateFlow(null) val isInVoteMode = MutableStateFlow(false) From 435048f5378c2d773ad497e582f695e57e1c640a Mon Sep 17 00:00:00 2001 From: itsMimao Date: Thu, 29 Jul 2021 16:20:22 +0800 Subject: [PATCH 100/137] fixed paging source test stuck issue --- .../androidTest/java/com/twidere/twiderex/db/DbDMEventTest.kt | 3 ++- app/src/androidTest/java/com/twidere/twiderex/db/DbListTest.kt | 3 ++- .../androidTest/java/com/twidere/twiderex/db/DbTrendTest.kt | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/src/androidTest/java/com/twidere/twiderex/db/DbDMEventTest.kt b/app/src/androidTest/java/com/twidere/twiderex/db/DbDMEventTest.kt index 9f364bff5..dab27abc7 100644 --- a/app/src/androidTest/java/com/twidere/twiderex/db/DbDMEventTest.kt +++ b/app/src/androidTest/java/com/twidere/twiderex/db/DbDMEventTest.kt @@ -50,6 +50,7 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import java.util.UUID +import java.util.concurrent.Executors @RunWith(AndroidJUnit4::class) class DbDMEventTest { @@ -64,7 +65,7 @@ class DbDMEventTest { @Before fun setUp() { cacheDatabase = Room.inMemoryDatabaseBuilder(ApplicationProvider.getApplicationContext(), CacheDatabase::class.java) - .build() + .setTransactionExecutor(Executors.newSingleThreadExecutor()).build() runBlocking { for (i in 0 until conversationCount) { generateDirectMessage( diff --git a/app/src/androidTest/java/com/twidere/twiderex/db/DbListTest.kt b/app/src/androidTest/java/com/twidere/twiderex/db/DbListTest.kt index 857630a70..795326b83 100644 --- a/app/src/androidTest/java/com/twidere/twiderex/db/DbListTest.kt +++ b/app/src/androidTest/java/com/twidere/twiderex/db/DbListTest.kt @@ -40,6 +40,7 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith +import java.util.concurrent.Executors @RunWith(AndroidJUnit4::class) class DbListTest { @@ -56,7 +57,7 @@ class DbListTest { @Before fun setUp() { cacheDatabase = Room.inMemoryDatabaseBuilder(ApplicationProvider.getApplicationContext(), CacheDatabase::class.java) - .build() + .setTransactionExecutor(Executors.newSingleThreadExecutor()).build() listsDao = cacheDatabase.listsDao() for (i in 0 until twitterCount) { val ownerId = if (i % 2 == 0) twitterAccountKey.id else "789" diff --git a/app/src/androidTest/java/com/twidere/twiderex/db/DbTrendTest.kt b/app/src/androidTest/java/com/twidere/twiderex/db/DbTrendTest.kt index 0661e6335..49c706c17 100644 --- a/app/src/androidTest/java/com/twidere/twiderex/db/DbTrendTest.kt +++ b/app/src/androidTest/java/com/twidere/twiderex/db/DbTrendTest.kt @@ -37,6 +37,7 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith +import java.util.concurrent.Executors typealias TwitterTrend = com.twidere.services.twitter.model.Trend typealias MastodonTrend = com.twidere.services.mastodon.model.Trend @@ -57,7 +58,7 @@ class DbTrendTest { @Before fun setUp() { cacheDatabase = Room.inMemoryDatabaseBuilder(ApplicationProvider.getApplicationContext(), CacheDatabase::class.java) - .build() + .setTransactionExecutor(Executors.newSingleThreadExecutor()).build() for (i in 0 until twitterTrendCount) { trends.add( TwitterTrend( From b08c1b480ffc091ab61237fe783d7ab6b3920f97 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Thu, 29 Jul 2021 16:27:35 +0800 Subject: [PATCH 101/137] upgrade package versions --- buildSrc/src/main/kotlin/Versions.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index c2a02a776..2672e3895 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -4,7 +4,7 @@ object Versions { object Kotlin { const val lang = "1.5.10" const val coroutines = "1.5.0" - const val serialization = "1.2.1" + const val serialization = "1.2.2" } object Java { @@ -12,9 +12,9 @@ object Versions { val java = JavaVersion.VERSION_11 } - const val ksp = "1.5.10-1.0.0-beta02" + const val ksp = "${Kotlin.lang}-1.0.0-beta02" const val agp = "7.0.0" - const val spotless = "5.12.5" + const val spotless = "5.14.2" const val ktlint = "0.41.0" const val hilt = "2.37" const val okhttp = "4.9.1" @@ -39,7 +39,7 @@ object Versions { const val coil = "1.3.0" const val accompanist = "0.15.0" const val androidx_exifinterface = "1.3.2" - const val exoplayer = "2.14.1" + const val exoplayer = "2.14.2" const val browser = "1.3.0" const val protobuf = "3.17.3" const val androidx_test = "1.4.0" From 4e1c46e966463df811665ab3b55ce2ec402f2e4d Mon Sep 17 00:00:00 2001 From: Tlaster Date: Thu, 29 Jul 2021 16:29:40 +0800 Subject: [PATCH 102/137] sync localization from crowdin --- .../res-localized/values-ar-rSA/strings.xml | 54 +- .../res-localized/values-ca-rES/strings.xml | 492 +++++++-------- .../res-localized/values-de-rDE/strings.xml | 364 +++++------ .../res-localized/values-en-rUS/strings.xml | 540 ++++++++-------- .../res-localized/values-es-rES/strings.xml | 550 +++++++++------- .../res-localized/values-fr-rFR/strings.xml | 2 +- .../res-localized/values-it-rIT/strings.xml | 80 +-- .../res-localized/values-ja-rJP/strings.xml | 461 +++++++------- .../res-localized/values-ko-rKR/strings.xml | 580 ++++++++--------- .../res-localized/values-pt-rBR/strings.xml | 570 +++++++++-------- .../res-localized/values-si-rLK/strings.xml | 24 +- .../res-localized/values-tr-rTR/strings.xml | 74 +-- .../res-localized/values-vi-rVN/strings.xml | 540 ++++++++-------- .../res-localized/values-zh-rCN/strings.xml | 596 +++++++++--------- .../res-localized/values-zh-rTW/strings.xml | 364 +++++------ 15 files changed, 2712 insertions(+), 2579 deletions(-) diff --git a/app/src/main/res-localized/values-ar-rSA/strings.xml b/app/src/main/res-localized/values-ar-rSA/strings.xml index 93afbc8e8..73588b0c1 100644 --- a/app/src/main/res-localized/values-ar-rSA/strings.xml +++ b/app/src/main/res-localized/values-ar-rSA/strings.xml @@ -1,41 +1,41 @@ - الحساب مقفل مؤقتاً - إفتح تويتر لفك القفل - تم إجتياز الحد المسموح به - وصلت إلى حد استخدام واجهة برمجة تطبيقات تويتر - تم رفض الإذن - عذراً، غير مصرح لك - تم رفض الإذن - تم حظرك من متابعة هذا الحساب بناء على طلب المستخدم - الحساب معلق - تويتر يعلق الحسابات التي تخالف %s - قواعد تويتر - لم يتم العثور على تغريدات إلغاء متابعة المستخدم %s؟ - إلغاء طلب متابعة %s؟ + الرجاء المحاولة مرة أخرى تم إرسال التغريدة - يتم إرسال التغريدة - تعذر إرسال التغريدة - الرجاء المحاولة مرة أخرى - تم حذف التغريدة - تعذر حذف التغريدة - الرجاء المحاولة مرة أخرى - يتم حفظ الوسائط - تم حفظ الوسائط + الرجاء المحاولة مرة أخرى + قواعد تويتر + الحساب معلق + تويتر يعلق الحسابات التي تخالف %s تعذر تحميل الوسائط الرجاء المحاولة مرة أخرى - تم حفظ الصورة + إلغاء طلب متابعة %s؟ + الرجاء المحاولة مرة أخرى تعذر حفظ الصورة الرجاء المحاولة مرة أخرى + لم يتم العثور على تغريدات + يتم حفظ الوسائط + تم رفض الإذن + عذراً، غير مصرح لك + الرجاء المحاولة مرة أخرى + تم إجتياز الحد المسموح به + وصلت إلى حد استخدام واجهة برمجة تطبيقات تويتر فشل التحميل الرجاء المحاولة مرة أخرى - الرجاء المحاولة مرة أخرى - الرجاء المحاولة مرة أخرى + تم رفض الإذن + تم حظرك من متابعة هذا الحساب بناء على طلب المستخدم + تم حفظ الوسائط + تم حذف التغريدة الرجاء المحاولة مرة أخرى - الرجاء المحاولة مرة أخرى + يتم إرسال التغريدة + تعذر حذف التغريدة + الرجاء المحاولة مرة أخرى + تم حفظ الصورة + الحساب مقفل مؤقتاً + إفتح تويتر لفك القفل الرجاء المحاولة مرة أخرى - الرجاء المحاولة مرة أخرى - الرجاء المحاولة مرة أخرى + تعذر إرسال التغريدة + الرجاء المحاولة مرة أخرى الرجاء المحاولة مرة أخرى + الرجاء المحاولة مرة أخرى تم رفض الإذن \ No newline at end of file diff --git a/app/src/main/res-localized/values-ca-rES/strings.xml b/app/src/main/res-localized/values-ca-rES/strings.xml index b2b0377a8..8b73eafcd 100644 --- a/app/src/main/res-localized/values-ca-rES/strings.xml +++ b/app/src/main/res-localized/values-ca-rES/strings.xml @@ -1,121 +1,122 @@ - Compte blocat temporalment - Obre Twitter per desblocar - Taxa d\'ús excedida - S\'ha arribat al límit d\'ús de la API de Twitter - Permís denegat - Ho sento, no estàs autoritzat - Permís denegat - Has estat blocat de seguir aquest compte a petició de l\'usuari - Compte suspès - Twitter suspèn comptes que violen %s - Normes de Twitter - No s\'han trobat piulades + %s ha estat silenciat Deixar de seguir l\'usuari %s? - Canceŀlar la petició de seguiment a %s? + Error al deixar de seguir + Si us plau, torna-ho a provar Piulada enviada - S\'està enviant la piulada - Error al piular - Si us plau, torna-ho a provar - Piulada esborrada - Error al esborrar piulada - Si us plau, torna-ho a provar - S\'està desant el mitjà - S\'ha desat el mitjà + No s\'ha pogut entrar + L\'adreça URL del servidor és incorrecta. + Massa soŀlicituds + Seguiment realitzat + Error al seguir + Si us plau, torna-ho a provar + %s ha estat denunciat per publicitat abusiva + Normes de Twitter + Compte suspès + Twitter suspèn comptes que violen %s No s\'ha pogut desar el mitjà Si us plau, torna-ho a provar - Imatge desada + %s ha estat blocat + Canceŀlar la petició de seguiment a %s? + Error al denunciar %s + Si us plau, torna-ho a provar + %s ha estat denunciat per publicitat abusiva i blocat Error al desar la imatge Si us plau, torna-ho a provar + No s\'han trobat piulades + S\'està desant el mitjà + No s\'ha pogut entrar + S\'ha esgotat el temps d\'espera. + %s ha estat desblocat + %s ha estat silenciat + Permís denegat + Ho sento, no estàs autoritzat + Error al silenciar %s + Si us plau, torna-ho a provar + Taxa d\'ús excedida + S\'ha arribat al límit d\'ús de la API de Twitter Error al carregar Si us plau, torna-ho a provar - Error al seguir - Si us plau, torna-ho a provar - Error al deixar de seguir - Si us plau, torna-ho a provar - Seguiment realitzat - S\'ha deixat de seguir + Permís denegat + Has estat blocat de seguir aquest compte a petició de l\'usuari + S\'ha desat el mitjà + Piulada esborrada Petició de seguiment enviada - Massa soŀlicituds - %s ha estat silenciat Error al silenciar %s Si us plau, torna-ho a provar - %s ha estat silenciat - Error al silenciar %s - Si us plau, torna-ho a provar - %s ha estat blocat + S\'està enviant la piulada + Error al esborrar piulada + Si us plau, torna-ho a provar + Imatge desada + Compte blocat temporalment + Obre Twitter per desblocar Error al blocar %s Si us plau, torna-ho a provar - %s ha estat desblocat - Error al desblocar %s - Si us plau, torna-ho a provar - %s ha estat denunciat per publicitat abusiva - Error al denunciar %s - Si us plau, torna-ho a provar - %s ha estat denunciat per publicitat abusiva i blocat + Error al piular + Si us plau, torna-ho a provar + S\'ha deixat de seguir Error al denunciar i blocar %s Si us plau, torna-ho a provar - No s\'ha pogut entrar - L\'adreça URL del servidor és incorrecta. - No s\'ha pogut entrar - S\'ha esgotat el temps d\'espera. + Error al desblocar %s + Si us plau, torna-ho a provar + Missatges + Interaccions + Interaccions com mencions i repiulades + %s han impulsat el vostre tut La vostra enquesta ha finalitzar + %s t\'ha mencionat Una votació en que has participat ha finalitzat - %s han impulsat el vostre tut %s han impulsat el vostre tut %s et segueix - %s t\'ha mencionat + Bloca %s + seguidor + seguidors + %s no et segueix + Et segueix + Silencia %s + %s et segueix + Desbloca + Denuncia i bloca + Seguint + Pendents + Denuncia + Silencia + Deixar de silenciar + Bloca + Segueix + Deixa de seguir + Seguidors + Llistat + Seguint + Carrega\'n més + Galeria d\'imatges Afegir - Elimina + Canceŀla + Previsualitza Edita + Obrir en Safari + Desa - D\'acord + Elimina Confirma - - Canceŀla - Fes una foto Desa la foto + Fes una foto + D\'acord Iniciar sessió - Previsualitza - Obrir en Safari - Carrega\'n més - Copia el text - Cita - Repiular - Copia l\'enllaç - Comparteix l\'enllaç - Esborra la piulada - Votació - Multimèdia %s repiulades - Tancada + Multimèdia %s persona %s vots - %s persones + Tancada %s vots - Segueix - Deixa de seguir - Seguint - Pendents - Silencia - Deixar de silenciar - Bloca - Desbloca - Denuncia - Denuncia i bloca - seguidor - seguidors - Et segueix - %s no et segueix - %s et segueix - Silencia %s - Bloca %s - Seguint - Seguidors - Llistat - Galeria d\'imatges - %s resposta - %s respostes + %s persones + Comparteix l\'enllaç + Esborra la piulada + Cita + Repiular + Copia el text + Votació + Copia l\'enllaç %s cita %s cites %s repiulada @@ -124,197 +125,196 @@ %s agradaments %s membre %s membres - %s llista - %s llistes - %s piulada - %s piulades %s foto %s fotos - Interaccions - Interaccions com mencions i repiulades - Missatges - Iniciar sessió - Gestiona els comptes - Comptes - Esborra el compte - Hola!\nInicia sessió per començar. - Inicia sessió amb Twitter - Inicia sessió amb Mastodon - Inicia sessió amb clau de Twitter personalitzada - Es requerix accés amb l\'API v2 de Twitter. - Autenticació - Línia de temps - Mencions - Notificació + %s piulada + %s piulades + %s resposta + %s respostes + %s llista + %s llistes + Imatge de xarxa + Més + Torna + Logo de Twidere X + Logo de Twitter + Logo de Github + Logo de Mastodon + Logo de Telegram + Reprodueix el vídeo + Tanca + Fet + Ubicació + Multimèdia + Repiular + M\'agrada + Resposta + Repiulades + Mida de la lletra + Desa + Historial + Afegir + Afegeix imatge + Afegeix menció + Obre esborrany + Habilita la ubicació + Desactivar la ubicació + Enviar + Carrega + Lloc web + Multimèdia + Favorit + Estats + Ubicació + Redacta + Desplegable del compte + Menú + Canvia el nom de la llista + Crea una llista + Privada + Edita la llista + Nom + Nova llista + Descripció + Cerca persones + Afegeix membre + Afegir + Elimina Tot - Missatges - Cerca persones - M\'agrada - Marcador - Tendències - Piulada - 1 resposta - %d respostes - 1 cita - %d cites - 1 repiulada - %d repiulades - 1 M\'agrada - %d M\'agrada - Cerca + Notificació + Esborra el compte + Comptes Cerca piulades o usuaris - Cerca desada - Piulades Multimèdia + Piulades Usuaris Etiqueta - Jo - Amaga la resposta - Permís denegat - Has estat blocat pel propietari d\'aquest perfil. - Totes les piulades - Amaga les respostes - Seguint - Seguidors - Llistat - Redacta - Resposta - Cita - Resposta a … - , - i - Què està passant? - Escriviu la vostra advertència aquí - Altres en aquesta conversa: - Desar l\'esborrany? - Desa l\'esborrany - Responent a - Elecció múltiple - 5 minuts - 30 minuts - 1 hora - 6 hores - 1 dia - 3 dies - 7 dies - Pública - Cap llista - Privada - Directe - Cerca usuaris - Cerca etiquetes - Esborranys - Suprimeix l\'esborrany - Edita l\'esborrany - Llistes - LES MEVES LLISTES - SUBSCRIBRIT - Visibilitat privada - Crea llista + Cerca + Cerca desada + No s\'han trobat membres. + Subscriptors + Mostra els membres de la llista + Afegir membres Detalls de la llista - 1 Membre %d Membres 1 subscriptor + 1 Membre %d subscriptors - Afegeix membre + Suprimeix aquesta llista: %s Edita la llista Canvia el nom de llista Suprimeix llista Segueix Deixa de seguir - Mostra els membres de la llista - Subscriptors - Suprimeix aquesta llista: %s - No s\'han trobat membres. - Afegir membres - Nova llista - Edita la llista - Nom - Descripció - Privada - Crea una llista - Canvia el nom de la llista - Afegir - Elimina - Afegeix membre - Cerca persones - Configuració - General - Quant a - Aparença - Color de realçament + Afegeix membre + Tendències + Inicia sessió amb Mastodon + Hola!\nInicia sessió per començar. + Inicia sessió amb Twitter + Inicia sessió amb clau de Twitter personalitzada + Es requerix accés amb l\'API v2 de Twitter. + Autenticació + M\'agrada + Comptes + Notificació Tria un color - Posició de la pestanya - Tema - Línia de temps Dalt Baix + Color de realçament Automàtic Clar Fosc + Aparença Amaga la barra de pestanyes al desplaçar-te Amaga la barra de l\'aplicació al desplaçar-te - Visualització - Previsualitza - Text - Format de la data - Multimèdia + Posició de la pestanya + Tema + Línia de temps Gràcies per utilitzar @TwidereProject! + Previsualització d\'Url + Absoluta + Relativa Empra la mida de la lletra del sistema + Quadrat arrodonit Estil d\'avatar Cercle - Quadrat arrodonit - Relativa - Absoluta + Sempre Previsualitzacions dels mitjans - Reproducció automàtica Automàtic - Sempre + Reproducció automàtica Desactivada - Previsualització d\'Url - Notificació - Comptes - URL del projecte - Quant a + Visualització + Previsualitza + Format de la data + Text + Multimèdia Llicència La següent generació de Twidere per Android 5.0. \nEncara en una fase inicial. - Versió %s Quan al logo del fons de la pàgina Quant al ombrejat del logo del fons de la pàgina - Imatge de xarxa - Torna - Més - Tanca - Fet - Logo de Twidere X - Logo de Twitter - Logo de Mastodon - Logo de Github - Logo de Telegram - Reprodueix el vídeo - Ubicació - Repiulades - Multimèdia - Resposta - Repiular - M\'agrada - Redacta - Menú - Desplegable del compte - Historial - Desa - Carrega - Lloc web - Ubicació - Estats - Multimèdia - Favorit - Enviar - Habilita la ubicació - Desactivar la ubicació - Afegeix menció - Afegeix imatge - Obre esborrany - Afegir - Mida de la lletra + Quant a + Versió %s + Configuració + General + Quant a + URL del projecte + Totes les piulades + Amaga les respostes + Amaga la resposta + Jo + Permís denegat + Has estat blocat pel propietari d\'aquest perfil. + Gestiona els comptes + Iniciar sessió + Esborranys + Suprimeix l\'esborrany + Edita l\'esborrany + Cerca usuaris + Marcador + Seguidors + Llistat + Desa l\'esborrany + Desar l\'esborrany? + Resposta a … + Privada + Pública + Cap llista + Directe + , + Escriviu la vostra advertència aquí + i + Què està passant? + Cita + Redacta + Resposta + Altres en aquesta conversa: + Elecció múltiple + 1 dia + 30 minuts + 7 dies + 1 hora + 3 dies + 5 minuts + 6 hores + Responent a + SUBSCRIBRIT + LES MEVES LLISTES + Llistes + Visibilitat privada + Crea llista + Mencions + Seguint + Línia de temps + Cerca persones + Missatges + Cerca etiquetes + 1 cita + %d cites + 1 repiulada + %d repiulades + 1 M\'agrada + %d M\'agrada + Piulada + 1 resposta + %d respostes \ No newline at end of file diff --git a/app/src/main/res-localized/values-de-rDE/strings.xml b/app/src/main/res-localized/values-de-rDE/strings.xml index 22ae35e9c..09d62cdb8 100644 --- a/app/src/main/res-localized/values-de-rDE/strings.xml +++ b/app/src/main/res-localized/values-de-rDE/strings.xml @@ -1,230 +1,230 @@ - Konto vorübergehend gesperrt - Öffne Twitter zum Entsperren - Kappungsgrenze überschritten - Nutzungsgrenze der Twitter-API erreicht - Zugriff verweigert - Entschuldigung, Sie sind nicht berechtigt - Zugriff verweigert - Dieser Nutzer hat dich blockiert, du kannst ihm nicht folgen - Konto gesperrt - Twitter sperrt Konten die gegen %s verstoßen - Twitter-Regeln - Keine Tweets gefunden + %s ist nicht länger stumm geschaltet Benutzer %s entfolgen? - Follow Anfrage für %s abbrechen? + Fehler beim Entfolgen + Bitte versuche es erneut Tweet gesendet - Tweet fehlgeschlagen - Bitte versuche es erneut - Tweet gelöscht - Tweet konnte nicht gelöscht werden - Bitte versuche es erneut + Zu viele Anfragen + Folgen erfolgreich + Fehler beim Folgen + Bitte versuche es erneut + %s wurde wegen Spam gemeldet + Twitter-Regeln + Konto gesperrt + Twitter sperrt Konten die gegen %s verstoßen Bitte versuche es erneut - Foto gespeichert + %s wurde blockiert + Follow Anfrage für %s abbrechen? + Fehler beim melden von %s + Bitte versuche es erneut + %s wurde wegen Spam gemeldet und blockiert Foto konnte nicht gespeichert werden Bitte versuche es erneut + Keine Tweets gefunden + %s wurde freigegeben + %s wurde stumm geschaltet + Zugriff verweigert + Entschuldigung, Sie sind nicht berechtigt + Stummschaltung von %s konnte nicht aufgehoben werden + Bitte versuche es erneut + Kappungsgrenze überschritten + Nutzungsgrenze der Twitter-API erreicht Fehler beim Laden Bitte versuche es erneut - Fehler beim Folgen - Bitte versuche es erneut - Fehler beim Entfolgen - Bitte versuche es erneut - Folgen erfolgreich - Entfolgen erfolgreich + Zugriff verweigert + Dieser Nutzer hat dich blockiert, du kannst ihm nicht folgen + Tweet gelöscht Anfrage zum Folgen gesendet - Zu viele Anfragen - %s wurde stumm geschaltet Fehler beim Stummschalten von %s Bitte versuche es erneut - %s ist nicht länger stumm geschaltet - Stummschaltung von %s konnte nicht aufgehoben werden - Bitte versuche es erneut - %s wurde blockiert + Tweet konnte nicht gelöscht werden + Bitte versuche es erneut + Foto gespeichert + Konto vorübergehend gesperrt + Öffne Twitter zum Entsperren Fehler beim Blockieren von %s Bitte versuche es erneut - %s wurde freigegeben - Fehler beim Freigeben von %s - Bitte versuche es erneut - %s wurde wegen Spam gemeldet - Fehler beim melden von %s - Bitte versuche es erneut - %s wurde wegen Spam gemeldet und blockiert + Tweet fehlgeschlagen + Bitte versuche es erneut + Entfolgen erfolgreich Fehler beim melden und Sperren von %s Bitte versuche es erneut + Fehler beim Freigeben von %s + Bitte versuche es erneut + Nachrichten + %s blockieren + folgender + folgende + %s folgt dir nicht + Folgt dir + %s stummschalten + %s folgt dir + Blockierung aufheben + Melden und blockieren + Folgen + Ausstehend + Melden + Stumm schalten + Stummschaltung aufheben + Blockieren + Folgen + Entfolgen + Follower + Listet + Folgen + Mehr laden + Fo­to­ga­le­rie Hinzufügen - Entfernen + Abbrechen + Vorschau Bearbeiten + In Safari öffnen Speichern - Ok + Entfernen Bestätigen - Abbrechen - Foto aufnehmen Foto speichern + Foto aufnehmen + Ok Einloggen - Vorschau - In Safari öffnen - Mehr laden - Text kopieren + %s retweeted + Medien + Link teilen + Tweet löschen Zitieren Retweet + Text kopieren Link kopieren - Link teilen - Tweet löschen - Medien - %s retweeted - Folgen - Entfolgen - Folgen - Ausstehend - Stumm schalten - Stummschaltung aufheben - Blockieren - Blockierung aufheben - Melden - Melden und blockieren - folgender - folgende - Folgt dir - %s folgt dir nicht - %s folgt dir - %s stummschalten - %s blockieren - Folgen - Follower - Listet - Fo­to­ga­le­rie - Nachrichten - Einloggen - Konten verwalten - Konten + Netzwerkbild + Mehr + Zurück + Twidere X Logo + Twitter logo + GitHub logo + Mastodon logo + Video abspielen + Schließen + Fertig + Standort + Medien + Retweet + Gefällt mir + Antworten + Retweetet + Schriftgröße + Speichern + Verlauf + Hinzufügen + Bild hinzufügen + Entwurf öffnen + Standortzugriff aktivieren + Standortzugriff deaktivieren + Senden + Laden + Webseite + Medien + Favoriten + Status + Standort + Erstellen + Konto DropDown-Liste + Menü + Liste bearbeiten + Mitglied hinzufügen + Hinzufügen + Entfernen Konto löschen - Hallo!\nMelden Sie sich an, um loszulegen. - Mit Twitter anmelden - Mit Mastodon anmelden - Mit benutzerdefiniertem Twitter-Schlüssel anmelden - Twitter API v2 Zugriff ist erforderlich. - Authentifizierung - Timeline - Erwähnungen - Nachrichten - Likes - Lesezeichen - Trends - Tweet - 1 Antwort - %d Antworten - 1 Zitat - %d Zitate - 1 Retweet - %d Retweets - 1 Like - %d Likes - Suche + Konten Suche Tweets oder Benutzer - Gespeicherte Suche - Tweets Medien + Tweets Benutzer - Ich - Antwort ausblenden - Zugriff verweigert - Du wurdest vom Profil dieses Benutzers blockiert. - Folgen - Follower - Listet - Erstellen - Antworten - Zitieren - Antworten auf … - , - und - Was ist passiert? - Andere in dieser Konversation: - Entwurf speichern? - Entwurf speichern - Antworten auf - Entwürfe - Entwurf löschen - Entwurf bearbeiten - Listen + Suche + Gespeicherte Suche + Keine Mitglieder gefunden. + Mitglieder hinzufügen Listendetails - 1 Mitglied %d Mitglieder 1 Abonnent + 1 Mitglied %d Abonnenten - Mitglied hinzufügen Liste bearbeiten Folgen Entfolgen - Keine Mitglieder gefunden. - Mitglieder hinzufügen - Liste bearbeiten - Hinzufügen - Entfernen - Mitglied hinzufügen - Einstellungen - Allgemein - Über - Aussehen - Farbe hervorheben + Mitglied hinzufügen + Trends + Mit Mastodon anmelden + Hallo!\nMelden Sie sich an, um loszulegen. + Mit Twitter anmelden + Mit benutzerdefiniertem Twitter-Schlüssel anmelden + Twitter API v2 Zugriff ist erforderlich. + Authentifizierung + Likes + Konten Farbe wählen - Tab position - Design Oben Unten + Farbe hervorheben Automatisch - Anzeige - Vorschau - Text - Datumsformat - Medien + Aussehen + Tab position + Design Vielen Dank für die Verwendung von @TwidereProject! + Absolut + Relativ Die Systemschriftgröße verwenden + Abgerundetes Quadrat Avatar Stil Kreis - Abgerundetes Quadrat - Relativ - Absolut + Immer Medien Vorschau - Automatische Wiedergabe Automatisch - Immer + Automatische Wiedergabe Aus - Konten - Über + Anzeige + Vorschau + Datumsformat + Text + Medien Lizenz - Netzwerkbild - Zurück - Mehr - Schließen - Fertig - Twidere X Logo - Twitter logo - Mastodon logo - GitHub logo - Video abspielen - Standort - Retweetet - Medien - Antworten - Retweet - Gefällt mir - Erstellen - Menü - Konto DropDown-Liste - Verlauf - Speichern - Laden - Webseite - Standort - Status - Medien - Favoriten - Senden - Standortzugriff aktivieren - Standortzugriff deaktivieren - Bild hinzufügen - Entwurf öffnen - Hinzufügen - Schriftgröße + Über + Einstellungen + Allgemein + Über + Antwort ausblenden + Ich + Zugriff verweigert + Du wurdest vom Profil dieses Benutzers blockiert. + Konten verwalten + Einloggen + Entwürfe + Entwurf löschen + Entwurf bearbeiten + Lesezeichen + Follower + Listet + Entwurf speichern + Entwurf speichern? + Antworten auf … + , + und + Was ist passiert? + Zitieren + Erstellen + Antworten + Andere in dieser Konversation: + Antworten auf + Listen + Erwähnungen + Folgen + Timeline + Nachrichten + 1 Zitat + %d Zitate + 1 Retweet + %d Retweets + 1 Like + %d Likes + Tweet + 1 Antwort + %d Antworten \ No newline at end of file diff --git a/app/src/main/res-localized/values-en-rUS/strings.xml b/app/src/main/res-localized/values-en-rUS/strings.xml index b02a96804..b01dc5140 100644 --- a/app/src/main/res-localized/values-en-rUS/strings.xml +++ b/app/src/main/res-localized/values-en-rUS/strings.xml @@ -1,124 +1,126 @@ - Account Temporarily Locked - Open Twitter to unlock - Rate Limit Exceeded - Reached Twitter API usage limit - Permission Denied - Sorry, you are not authorized - Permission Denied - You have been blocked from following this account at the request of the user - Account Suspended - Twitter suspends accounts which violate the %s - Twitter Rules - No Tweets Found + %s has been unmuted Unfollow user %s? - Cancel follow request for %s? + Failed to Unfollowing + Please try again Tweet Sent - Sending tweet - Failed to Tweet - Please try again - Tweet Deleted - Failed to Delete Tweet - Please try again - Saving media - Media saved + Failed to Login + Server URL is incorrect. + Too Many Requests + Following Succeeded + Failed to Following + Please try again + %s has been reported for spam + Twitter Rules + Account Suspended + Twitter suspends accounts which violate the %s Failed to save media Please try again - Photo Saved + %s has been blocked + Cancel follow request for %s? + Failed to report %s + Please try again + %s has been reported for spam and blocked Failed to Save Photo Please try again + No Tweets Found + Saving media + Failed to Login + Connection timeout. + %s has been unblocked + %s has been muted + Permission Denied + Sorry, you are not authorized + Failed to unmute %s + Please try again + Rate Limit Exceeded + Reached Twitter API usage limit Failed to Load Please try again - Failed to Following - Please try again - Failed to Unfollowing - Please try again - Following Succeeded - Unfollowing Succeeded + Permission Denied + You have been blocked from following this account at the request of the user + Media saved + Tweet Deleted Following Request Sent - Too Many Requests - %s has been muted Failed to mute %s Please try again - %s has been unmuted - Failed to unmute %s - Please try again - %s has been blocked + Sending tweet + Failed to Delete Tweet + Please try again + Photo Saved + Account Temporarily Locked + Open Twitter to unlock Failed to block %s Please try again - %s has been unblocked - Failed to unblock %s - Please try again - %s has been reported for spam - Failed to report %s - Please try again - %s has been reported for spam and blocked + Failed to Tweet + Please try again + Unfollowing Succeeded Failed to report and block %s Please try again - Failed to Login - Server URL is incorrect. - Failed to Login - Connection timeout. - Your poll has ended - A poll you have voted in has ended + Failed to unblock %s + Please try again + Messages + Interactions + Interactions like mentions and retweets + Background progresses %s boosted your toot - %s just posted %s has requested to follow you + Your poll has ended + %s mentions you + A poll you have voted in has ended %s favourited your toot %s followed you - %s mentions you + %s just posted + Block %s + follower + followers + %s is not following you + Follows you + Mute %s + %s is following you + Unblock + Report and Block + Following + Pending + Report + Mute + Unmute + Block + Follow + Unfollow + Followers + Listed + Following + Load More + Photo Library Add - Remove + Cancel + Preview Edit + Open in Safari + Yes Save - OK + Remove Confirm - Yes - Cancel - Take photo Save photo + Take photo + OK Sign in - Preview - Open in Safari - Load More - Copy text - Quote - Retweet - Copy link - Share link - Delete tweet - Vote - Media %s retweeted - Closed + Media %s person %s vote - %s people + Closed %s votes + %s people Show this thread - Follow - Unfollow - Following - Pending - Mute - Unmute - Block - Unblock - Report - Report and Block - follower - followers - Follows you - %s is not following you - %s is following you - Mute %s - Block %s - Following - Followers - Listed - Photo Library - %s reply - %s replies + Share link + Delete tweet + Quote + Retweet + Copy text + Vote + Copy link %s quote %s quotes %s retweet @@ -127,221 +129,219 @@ %s likes %s member %s members - %s list - %s lists - %s tweet - %s tweets %s photo %s photos - Background progresses - Interactions - Interactions like mentions and retweets - Messages - Sign in - Manage accounts - Accounts + %s tweet + %s tweets + %s reply + %s replies + %s list + %s lists + Network Image + More + Back + Twidere X logo + Twitter Logo + Github Logo + Mastodon Logo + Telegram Logo + Play video + Close + Done + Location + Media + Retweet + Like + Reply + Retweeted + Font Size + Save + History + Add + Add image + Add mention + Open draft + Enable location + Disable location + Thread mode + Send + Load + Website + Media + Favourite + Statuses + Location + Compose + Account DropDown + Menu + Rename the list + Create a list + Private + Edit List + Name + New List + Description + Search people + Add Member + Add + Remove + All + Notification Delete account - Hello!\nSign in to Get Started. - Sign in with Twitter - Sign in with Mastodon - Sign in with Custom Twitter Key - Twitter API v2 access is required. - Authentication - Timeline - Mentions - Notification - All - Messages - Search people - Likes - Bookmark - Trends - Trends - Worldwide - Trending Now - %d people talking - Tweet - Toot - 1 Reply - %d Replies - 1 Quote - %d Quotes - 1 Retweet - %d Retweets - 1 Like - %d Likes - Search + Accounts + Show less Search tweets or users - Saved Search - Tweets Media + Tweets Users Hashtag + Search Show more - Show less - Me - Hide reply - Permission Denied - You have been blocked from viewing this user’s profile. - All tweets - Exclude replies - Following - Followers - Listed - Compose - Reply - Quote - Reply to … - , - and - What’s happening? - Write your warning here - Others in this conversation: - Save draft? - Save draft - Replying to - Multiple choice - 5 minutes - 30 minutes - 1 hour - 6 hours - 1 day - 3 days - 7 days - Public - Unlisted - Private - Direct - Search users - Search hashtag - Drafts - Delete draft - Edit draft - Lists - MY LISTS - SUBSCRIBED - Private visibility - Create list + Saved Search + No Members Found. + Subscribers + List Members + Add Members Lists Details - 1 Member %d Members 1 Subscriber + 1 Member %d Subscribers - Add Member + Delete this list: %s Edit List Rename List Delete List Follow Unfollow - List Members - Subscribers - Delete this list: %s - No Members Found. - Add Members - New List - Edit List - Name - Description - Private - Create a list - Rename the list - Add - Remove - Add Member - Search people - Settings - General - About - Appearance - Highlight color + Add Member + Trending Now + %d people talking + Trends + Trends - Worldwide + Sign in with Mastodon + Hello!\nSign in to Get Started. + Sign in with Twitter + Sign in with Custom Twitter Key + Twitter API v2 access is required. + Authentication + Likes + Accounts + Notification Pick color - Tab position - Theme - Scrolling timeline + AMOLED optimized mode Top Bottom + Highlight color Auto Light Dark - AMOLED optimized mode + Appearance Hide tab bar when scrolling - Hide app bar when scrolling Hide FAB when scrolling - Display - Preview - Text - Date Format - Media + Hide app bar when scrolling + Tab position + Theme + Scrolling timeline Thanks for using @TwidereProject! + Url previews + Absolute + Relative Use the system font size + Rounded Square Avatar Style Circle - Rounded Square - Relative - Absolute + Always Media previews - Auto playback Automatic - Always + Auto playback Off - Url previews - Notification - Accounts - Storage + Display + Preview + Date Format + Text + Media + License + Next generation of Twidere for Android 5.0+. \nStill in early stage. + About page background logo + About page background logo shadow + About + Ver %s + Delete all Twidere X cache. Your account credentials will not be lost. + Clear all cache Clear search history - Clear media cache Clear stored media cache. - Clear all cache - Delete all Twidere X cache. Your account credentials will not be lost. - Misc - Third-party Twitter data provider - Nitter Instance + Clear media cache + Storage + Settings + General + About Alternative Twitter front-end focused on privacy. - Third party Twitter data provider - Due to the limitation of Twitter API, some data might not be able to fetch from Twitter, you can use a third-party data provider to provide these data. Twidere does not take any responsibility for them. + Nitter Instance Using Third-party data provider in - - Twitter status threading Project URL - About - License - Next generation of Twidere for Android 5.0+. \nStill in early stage. - Ver %s - About page background logo - About page background logo shadow - Network Image - Back - More - Close - Done - Twidere X logo - Twitter Logo - Mastodon Logo - Github Logo - Telegram Logo - Play video - Location - Retweeted - Media - Reply - Retweet - Like - Compose - Menu - Account DropDown - History - Save - Load - Website - Location - Statuses - Media - Favourite - Send - Enable location - Disable location - Add mention - Add image - Open draft - Thread mode - Add - Font Size + - Twitter status threading + Third party Twitter data provider + Due to the limitation of Twitter API, some data might not be able to fetch from Twitter, you can use a third-party data provider to provide these data. Twidere does not take any responsibility for them. + Third-party Twitter data provider + Misc + All tweets + Exclude replies + Hide reply + Me + Permission Denied + You have been blocked from viewing this user’s profile. + Manage accounts + Sign in + Drafts + Delete draft + Edit draft + Search users + Bookmark + Followers + Listed + Save draft + Save draft? + Reply to … + Private + Public + Unlisted + Direct + , + Write your warning here + and + What’s happening? + Quote + Compose + Reply + Others in this conversation: + Multiple choice + 1 day + 30 minutes + 7 days + 1 hour + 3 days + 5 minutes + 6 hours + Replying to + SUBSCRIBED + MY LISTS + Lists + Private visibility + Create list + Mentions + Following + Timeline + Search people + Messages + Search hashtag + 1 Quote + %d Quotes + 1 Retweet + %d Retweets + 1 Like + %d Likes + Toot + Tweet + 1 Reply + %d Replies \ No newline at end of file diff --git a/app/src/main/res-localized/values-es-rES/strings.xml b/app/src/main/res-localized/values-es-rES/strings.xml index 0dcef39e2..a068e8331 100644 --- a/app/src/main/res-localized/values-es-rES/strings.xml +++ b/app/src/main/res-localized/values-es-rES/strings.xml @@ -1,118 +1,133 @@ - Cuenta bloqueada temporalmente - Abre Twitter para desbloquear - Límite de transferencia excedido - Límite de uso de la API de Twitter alcanzado - Permiso denegado - Lo sentimos, no estás autorizado - Permiso denegado - Se le ha bloqueado el acceso a esta cuenta a petición del usuario - Cuenta suspendida - Twitter suspende cuentas que violan %s - Reglas de Twitter - No se encontraron Tweets + %s ha sido desmuteado ¿Dejar de seguir al usuario %s? - ¿Cancelar la solicitud de seguimiento para %s? + Los medios serán compartidos una vez completada la descarga + Error al dejar de seguir + Por favor, inténtalo de nuevo Tweet enviado - Enviando tweet - No se pudo enviar el Tweet - Por favor, inténtelo de nuevo - Tweet eliminado - Error al eliminar el Tweet - Por favor, inténtalo de nuevo - Guardando multimedia - Archivo multimedia guardado + Error al iniciar sesión + La URL del servidor es incorrecta. + Demasiadas solicitudes + Seguido con éxito + Error al seguir + Por favor, inténtalo de nuevo + %s ha sido reportado por spam + Reglas de Twitter + Cuenta suspendida + Twitter suspende cuentas que violan %s Error al guardar archivo multimedia Por favor, inténtelo de nuevo - Foto guardada + %s ha sido bloqueado + ¿Cancelar la solicitud de seguimiento para %s? + Error al reportar %s + Por favor, inténtelo de nuevo + %s ha sido reportado por spam y bloqueado Error al guardar la foto Por favor, inténtalo de nuevo + No se encontraron Tweets + Guardando multimedia + Error al iniciar sesión + Tiempo límite de conexión. + %s ha sido desbloqueado + %s ha sido silenciado + Permiso denegado + Lo sentimos, no estás autorizado + Enviando mensaje + No se pudo enviar el mensaje + Error al desmutear %s + Por favor, inténtelo de nuevo + Límite de transferencia excedido + Límite de uso de la API de Twitter alcanzado Error al cargar Por favor, inténtalo de nuevo - Error al seguir - Por favor, inténtalo de nuevo - Error al dejar de seguir - Por favor, inténtalo de nuevo - Seguido con éxito - Dejar de seguir con éxito + Permiso denegado + Se le ha bloqueado el acceso a esta cuenta a petición del usuario + Archivo multimedia guardado + Tweet eliminado Solicitud de seguimiento enviada - Demasiadas solicitudes - %s ha sido silenciado Error al silenciar %s Por favor, inténtalo de nuevo - %s ha sido desmuteado - Error al desmutear %s - Por favor, inténtelo de nuevo - %s ha sido bloqueado + Enviando tweet + Error al eliminar el Tweet + Por favor, inténtalo de nuevo + Foto guardada + Cuenta bloqueada temporalmente + Abre Twitter para desbloquear Error al bloquear %s Por favor, inténtelo de nuevo - %s ha sido desbloqueado - Error al desbloquear %s - Por favor, inténtelo de nuevo - %s ha sido reportado por spam - Error al reportar %s - Por favor, inténtelo de nuevo - %s ha sido reportado por spam y bloqueado + No se pudo enviar el Tweet + Por favor, inténtelo de nuevo + Dejar de seguir con éxito Error al reportar y bloquear a %s Por favor, inténtelo de nuevo - Tu encuesta ha finalizado - Una encuesta en la que has votado ha finalizado + Error al desbloquear %s + Por favor, inténtelo de nuevo + Mensajes + Mensajes directos + Interacciones + Interacciones como menciones y retweets + Progresos en segundo plano %s ha potenciado tu toot - %s acaba de publicar %s ha solicitado seguirte + Tu encuesta ha finalizado + %s te ha mencionado + Nuevo mensaje directo + %s te ha enviado un mensaje + Una encuesta en la que has votado ha finalizado %s ha marcado como favorito tu toot %s te ha seguido + %s acaba de publicar + Bloquear a %s + seguidor + seguidores + %s no te sigue + Te sigue + Silenciar %s + %s te sigue + Desbloquear + Reportar y Bloquear + Siguiendo + Pendiente + Reportar + Silenciar + No Silenciar + Bloquear + Seguir + Dejar de seguir + Seguidores + Listado + Siguiendo + Cargar más + Galería de fotos Añadir - Eliminar + Cancelar + Vista previa + Compartir medios Editar + Abrir en Safari + Guardar - OK + Eliminar Confirmar - - Cancelar - Hacer una foto Guardar foto + Hacer una foto + OK Iniciar sesión - Vista previa - Abrir en Safari - Cargar más - Copiar texto - Cita - Retwittear - Copiar enlace - Compartir enlace - Eliminar tweet - Votar - Multimedia %s ha retwitteado - Cerrado + Multimedia %s persona %s voto - %s personas + Cerrado %s votos - Seguir - Dejar de seguir - Siguiendo - Pendiente - Silenciar - No Silenciar - Bloquear - Desbloquear - Reportar - Reportar y Bloquear - seguidor - seguidores - Te sigue - %s no te sigue - %s te sigue - Silenciar %s - Bloquear a %s - Siguiendo - Seguidores - Listado - Galería de fotos - %s respuesta - %s respuestas + %s personas + Mostrar este hilo de conversación + Compartir enlace + Eliminar tweet + Cita + Retwittear + Copiar texto + Votar + Copiar enlace %s cita %s citas %s retweet @@ -121,197 +136,242 @@ %s me gusta %s miembro %s miembros - %s lista - %s listas - %s tweet - %s tweets %s foto %s fotos - Progresos en segundo plano - Mensajes - Iniciar sesión - Administrar cuentas - Cuentas - Eliminar cuenta - ¡Hola!\nInicie sesión para empezar. - Iniciar sesión con Twitter - Iniciar sesión con Mastodon - Iniciar sesión con una clave personalizada de Twitter - Se requiere acceso a la API v2 de Twitter. - Autentificación - Línea temporal - Menciones - Notificación + %s tweet + %s tweets + %s respuesta + %s respuestas + %s lista + %s listas + Imagen de red + Más + Volver + Logo de Twidere X + Logo de Twitter + Logo de Github + Logo de Mastodon + Logo de Telegram + Reproducir vídeo + Cerrar + Completado + Ubicación + Multimedia + Retwittear + Me gusta + Responder + Retwitteados + Tamaño de fuente + Guardar + Historial + Añadir + Añadir imagen + Añadir mención + Abrir borrador + Activar ubicación + Desactivar la localización + Modo de hilo + Enviar + Cargar + Página web + Multimedia + Favorito + Estados + Ubicación + Crear + Desplegable de la cuenta + Menú + Renombrar la lista + Crear una lista + Privado + Editar lista + Nombre + Nueva lista + Descripción + Buscar personas + Añadir Miembro + Añadir + Eliminar Todo - Mensajes - Buscar personas - Me gusta - Marcador - Tendencias - Tweet - 1 Respuesta - %d Respuestas - 1 Cita - %d Citas - 1 Retweet - %d Retweets - 1 Me gusta - %d Me gusta - Buscar + Notificación + Eliminar cuenta + Cuentas + Mostrar menos Buscar tweets o usuarios - Búsqueda guardada - Tweets Multimedia + Tweets Usuarios Hashtag - Yo - Ocultar respuesta - Permiso denegado - Se le ha bloqueado para ver el perfil de este usuario. - Todos los tweets - Excluir respuestas - Siguiendo - Seguidores - Listado - Crear - Responder - Cita - Responder a … - , - y - ¿Qué está pasando? - Escribe tu advertencia aquí - Usuarios en esta conversación: - ¿Guardar borrador? - Guardar borrador - Responder a - Selección múltiple - 5 minutos - 30 minutos - 1 hora - 6 horas - 1 día - 3 días - 7 días - Público - No listado - Privado - Directo - Buscar usuarios - Buscar hashtags - Borradores - Eliminar borrador - Editar borrador - Listas - MIS LISTAS - SUSCRITO - Visibilidad privada - Crear lista + Buscar + Mostrar más + Búsqueda guardada + No se han encontrado miembros. + Suscriptores + Lista de miembros + Borrar esta lista + Agregar Miembros Detalles de la lista - 1 Miembro %d Miembros 1 Suscriptor + 1 Miembro %d Suscriptores - Añadir Miembro + Eliminar esta lista: %s Editar lista Renombrar lista Eliminar lista Seguir Dejar de seguir - Lista de miembros - Suscriptores - Eliminar esta lista: %s - No se han encontrado miembros. - Agregar Miembros - Nueva lista - Editar lista - Nombre - Descripción - Privado - Crear una lista - Renombrar la lista - Añadir - Eliminar - Añadir Miembro - Buscar personas - Ajustes - General - Acerca de - Apariencia - Color destacado + Añadir Miembro + Tendencias en este momento + %d personas hablando + Tendencias + Tendencias - Mundial + Iniciar sesión con Mastodon + ¡Hola!\nInicie sesión para empezar. + Iniciar sesión con Twitter + Iniciar sesión con una clave personalizada de Twitter + Se requiere acceso a la API v2 de Twitter. + Autentificación + Me gusta + Diseño + Acciones de la barra de pestañas + Acciones del cajón + Diseño personalizado + Escoja y ordene hasta 5 acciones que aparecerán en la barra de pestañas (Las líneas de tiempo locales y federadas solo se mostrarán en Mastodon.) + Mostrar notificación + Cuentas + Notificación Elige un color - Posición de la pestaña - Tema + Modo optimizado para AMOLED Arriba Abajo + Color destacado Automático Claro Oscuro - Pantalla - Vista previa - Texto - Formato de fecha - Multimedia + Apariencia + Ocultar barra de pestañas al desplazar + Ocultar botón de acción flotante al desplazar + Ocultar barra de aplicación al desplazar + Posición de la pestaña + Tema + Línea de tiempo desplazable ¡Gracias por usar @TwidereProject! + URL de previsualización + Absoluto + Relativo Utilice el tamaño de fuente del sistema + Cuadrado redondeado Estilo del avatar Círculo - Cuadrado redondeado - Relativo - Absoluto + Siempre Vista previa de contenido multimedia - Reproducción automática en segundo plano Automático - Siempre + Reproducción automática en segundo plano Desactivado - URL de previsualización - Notificación - Cuentas - Almacenamiento - Limpiar historial de búsqueda - Limpiar caché multimedia - Limpiar caché de archivos multimedia. - Limpiar toda la caché - Borrar toda la caché de Twidere X. Las credenciales de tu cuenta no se perderán. - Acerca de + Pantalla + Vista previa + Formato de fecha + Texto + Multimedia Licencia La próxima generación de Twidere para Android 5.0+. \nTodavía en fase temprana. - Ver %s Acerca del logo de fondo de la página Acerca de la sombra del logo del fondo de la página - Imagen de red - Volver - Más - Cerrar - Completado - Logo de Twidere X - Logo de Twitter - Logo de Mastodon - Logo de Github - Logo de Telegram - Reproducir vídeo - Ubicación - Retwitteados - Multimedia - Responder - Retwittear - Me gusta - Crear - Menú - Desplegable de la cuenta - Historial - Guardar - Cargar - Página web - Ubicación - Estados - Multimedia - Favorito - Enviar - Activar ubicación - Desactivar la localización - Añadir imagen - Abrir borrador - Añadir - Tamaño de fuente + Acerca de + Ver %s + Borrar toda la caché de Twidere X. Las credenciales de tu cuenta no se perderán. + Limpiar toda la caché + Limpiar historial de búsqueda + Limpiar caché de archivos multimedia. + Limpiar caché multimedia + Almacenamiento + Ajustes + General + Acerca de + Servidor + Contraseña + Puerto + El puerto del servidor proxy debe tener números + Usar proxy para todas las peticiones de red + Proxy + Ajustes del proxy + HTTP + Tipo de proxy + Inverso + Nombre de usuario + Front-end alternativo de Twitter enfocado en la privacidad. + Instancia de Nitter + Usando proveedor de datos de terceros en + URL del proyecto + - Estado de hilos de Twitter + Proveedor de datos de terceros de Twitter + Debido a limitaciones de la API de Twitter, algunos datos podrían no poder obtenerse desde Twitter. Puedes usar un proveedor de datos de terceros para proporcionar estos datos en estos casos. Twidere no asume ninguna responsabilidad por estos. + Proveedor de datos de terceros de Twitter + Varios + Todos los tweets + Excluir respuestas + Ocultar respuesta + Yo + Permiso denegado + Se le ha bloqueado para ver el perfil de este usuario. + Administrar cuentas + Iniciar sesión + Borradores + Eliminar borrador + Editar borrador + Buscar usuarios + Marcador + Seguidores + Listado + Guardar borrador + ¿Guardar borrador? + Responder a … + Privado + Público + No listado + Directo + , + Escribe tu advertencia aquí + y + ¿Qué está pasando? + Cita + Crear + Responder + Usuarios en esta conversación: + Selección múltiple + 1 día + 30 minutos + 7 días + 1 hora + 3 días + 5 minutos + 6 horas + Responder a + SUSCRITO + MIS LISTAS + Listas + Visibilidad privada + Crear lista + Menciones + Siguiendo + Línea temporal + [Photo] + Buscar personas + Encontrar personas + Error al enviar mensaje + Copiar texto del mensaje + Eliminar mensaje para ti + Mensajes + Buscar hashtags + 1 Cita + %d Citas + 1 Retweet + %d Retweets + 1 Me gusta + %d Me gusta + Toot + Tweet + 1 Respuesta + %d Respuestas \ No newline at end of file diff --git a/app/src/main/res-localized/values-fr-rFR/strings.xml b/app/src/main/res-localized/values-fr-rFR/strings.xml index 73afce2c6..7a58d9d62 100644 --- a/app/src/main/res-localized/values-fr-rFR/strings.xml +++ b/app/src/main/res-localized/values-fr-rFR/strings.xml @@ -1,7 +1,7 @@ %s a été bloqué(e) + %s a été débloqué(e) Échec du blocage de %s Merci de réessayer - %s a été débloqué(e) Merci de réessayer \ No newline at end of file diff --git a/app/src/main/res-localized/values-it-rIT/strings.xml b/app/src/main/res-localized/values-it-rIT/strings.xml index bbffeb8ee..2caf917fb 100644 --- a/app/src/main/res-localized/values-it-rIT/strings.xml +++ b/app/src/main/res-localized/values-it-rIT/strings.xml @@ -1,65 +1,65 @@ - Account Temporaneamente Bloccato - Apri Twitter per sbloccare Smettere di seguire l\'utente %s? - Annullare la richiesta di seguire %s? + Si prega di riprovare Tweet Inviato - Tweet non riuscito - Si prega di riprovare - Si prega di riprovare + Si prega di riprovare Si prega di riprovare - Foto Salvata + Annullare la richiesta di seguire %s? + Si prega di riprovare Salvataggio foto non riuscito Si prega di riprovare + Si prega di riprovare Si prega di riprovare - Si prega di riprovare - Si prega di riprovare Si prega di riprovare - Si prega di riprovare + Si prega di riprovare + Foto Salvata + Account Temporaneamente Bloccato + Apri Twitter per sbloccare Si prega di riprovare - Si prega di riprovare - Si prega di riprovare + Tweet non riuscito + Si prega di riprovare Si prega di riprovare + Si prega di riprovare + Segui + Smetti di seguire + Carica Altro Aggiungi - Rimuovi + Annulla + Anteprima Modifica + Apri in Safari Salva - OK + Rimuovi Conferma - Annulla - Scatta foto Salva foto - Anteprima - Apri in Safari - Carica Altro - Cita + Scatta foto + OK Contenuti - Segui - Smetti di seguire - Gestisci account + Cita + Contenuti + Rispondi + Salva + Aggiungi + Contenuti + Componi + Aggiungi + Rimuovi Cerca tweet o utenti - Ricerca Salvata - Tweet Contenuti + Tweet Utenti - Io - Nascondi risposta - Componi - Rispondi - Cita + Ricerca Salvata Segui Smetti di seguire - Aggiungi - Rimuovi - Anteprima - Contenuti Sempre Off + Anteprima + Contenuti Licenza - Contenuti - Rispondi - Componi - Salva - Contenuti - Aggiungi + Nascondi risposta + Io + Gestisci account + Cita + Componi + Rispondi \ No newline at end of file diff --git a/app/src/main/res-localized/values-ja-rJP/strings.xml b/app/src/main/res-localized/values-ja-rJP/strings.xml index 5da029927..3d1cd0cc7 100644 --- a/app/src/main/res-localized/values-ja-rJP/strings.xml +++ b/app/src/main/res-localized/values-ja-rJP/strings.xml @@ -1,287 +1,294 @@ - アカウントは一時的にロックされています - Twitter を開いてロックを解除する - レート制限を超えました - Twitter APIの使用制限に達しました - 権限がありません - 権限がありません - 権限がありません - ユーザーのリクエストにより、このアカウントのフォローをブロックされています - アカウントは凍結されています - Twitterは%sに違反したアカウントを凍結します - Twitterルール - ツイートが見つかりませんでした + %s はミュート解除されました %s のフォローを解除しますか? - %s のフォローリクエストをキャンセルしますか? + フォロー解除に失敗しました + もう一度やり直してください ツイートを送信しました - ツイートを送信中 - ツイートに失敗しました - もう一度やり直してください - ツイートを削除しました - ツイートの削除に失敗しました - もう一度やり直してください - メディアの保存 - メディアを保存しました + ログインに失敗しました + サーバー URL が正しくありません。 + リクエストが多すぎます + フォローに成功しました + フォローに失敗しました + もう一度やり直してください + %s はスパムとして報告されました + Twitterルール + アカウントは凍結されています + Twitterは%sに違反したアカウントを凍結します メディアを保存できませんでした もう一度やり直してください - 写真を保存しました + %s はブロックされました + %s のフォローリクエストをキャンセルしますか? + %s の報告に失敗 + もう一度やり直してください + %s はスパムとして報告されブロックされました 写真の保存に失敗しました もう一度やり直してください + ツイートが見つかりませんでした + メディアの保存 + ログインに失敗しました + %s はブロック解除されました + %s はミュートされました + 権限がありません + 権限がありません + %s のミュート解除に失敗 + もう一度やり直してください + レート制限を超えました + Twitter APIの使用制限に達しました 読み込みに失敗しました もう一度やり直してください - フォローに失敗しました - もう一度やり直してください - フォロー解除に失敗しました - もう一度やり直してください - フォローに成功しました - フォロー解除に成功しました + 権限がありません + ユーザーのリクエストにより、このアカウントのフォローをブロックされています + メディアを保存しました + ツイートを削除しました フォローリクエストが送信されました - リクエストが多すぎます - %s はミュートされました %s のミュートに失敗 もう一度やり直してください - %s はミュート解除されました - %s のミュート解除に失敗 - もう一度やり直してください - %s はブロックされました + ツイートを送信中 + ツイートの削除に失敗しました + もう一度やり直してください + 写真を保存しました + アカウントは一時的にロックされています + Twitter を開いてロックを解除する %s のブロックに失敗 もう一度やり直してください - %s はブロック解除されました - %s のブロック解除に失敗 - もう一度やり直してください - %s はスパムとして報告されました - %s の報告に失敗 - もう一度やり直してください - %s はスパムとして報告されブロックされました + ツイートに失敗しました + もう一度やり直してください + フォロー解除に成功しました %s の報告とブロックに失敗 もう一度やり直してください - ログインに失敗しました - サーバー URL が正しくありません。 - ログインに失敗しました + %s のブロック解除に失敗 + もう一度やり直してください + メッセージ + %s さんがトゥートをブーストしました アンケートは終了しました 投票したアンケートが締め切られました - %s さんがトゥートをブーストしました %s さんがトゥートをお気に入りにしました - 追加 - 削除 - 編集 - 保存 - OK - 確認 - はい - キャンセル - 写真を撮影 - 写真を保存 - サインイン - プレビュー - Safari で開く - メディアを共有 - さらに読み込む - テキストをコピー - 引用 - リンクをコピー - リンクを共有 - ツイートを削除 - メディア - %s がリツイート - フォロー - フォロー解除 + %s をブロック + フォロワー + フォロワー + %sさんはあなたをフォローしていません + あなたをフォローしています + %s をミュート + %sさんはあなたをフォローしています + ブロック解除 + 報告とブロック フォロー中 保留中 + 報告 ミュート ミュート解除 ブロック - ブロック解除 - 報告 - 報告とブロック - フォロワー - フォロワー - あなたをフォローしています - %sさんはあなたをフォローしていません - %sさんはあなたをフォローしています - %s をミュート - %s をブロック - フォロー中 + フォロー + フォロー解除 フォロワー リスト + フォロー中 + さらに読み込む フォトライブラリ - メッセージ - サインイン - アカウントを管理 - アカウント - アカウントの削除 - こんにちは!\nサインインしてはじめましょう。 - Twitter でサインイン - Mastodonでサインイン - カスタム Twitter キーでサインイン - Twitter API v2 へのアクセスが必要です。 - 認証 - タイムライン - メンション + 追加 + キャンセル + プレビュー + メディアを共有 + 編集 + Safari で開く + はい + 保存 + 削除 + 確認 + 写真を保存 + 写真を撮影 + OK + サインイン + %s がリツイート + メディア + リンクを共有 + ツイートを削除 + 引用 + テキストをコピー + リンクをコピー + もっと見る + 戻る + Twidere X ロゴ + Twitter ロゴ + GitHub ロゴ + Mastodon ロゴ + 動画の再生 + 閉じる + 完了 + 位置情報 + メディア + 返信 + フォントサイズ + 保存 + 履歴 + 追加 + 画像を追加 + 下書きを開く + 位置情報を有効化 + 位置情報を無効化 + 送信 + 読み込み + ウェブサイト + メディア + お気に入り + 投稿 + 位置情報 + 新規作成 + アカウント ドロップダウン + メニュー + プライベート + リストを編集 + 名前 + 新規リスト + 説明 + ユーザーを検索 + メンバーを追加 + 追加 + 削除 通知 - メッセージ - メッセージをコピー - あなたへのメッセージを削除 - ユーザーを探す - ユーザーを検索 - いいね - ブックマーク - トレンド - トレンド - 全世界 - ツイート - トゥート - 1 返信 - %d 返信 - 1 引用 - %d 引用 - 1 リツイート - %d リツイート - 1 いいね - %d いいね - 検索 + アカウントの削除 + アカウント + 一部を表示 ツイートまたはユーザーを検索 - 保存した検索 - ツイート メディア + ツイート ユーザー ハッシュタグ - - 返信を非表示 - 権限がありません - このユーザーのプロフィールの閲覧をブロックされています。 - すべてのツイート - リプライを除外 - フォロー中 - フォロワー - リスト - 新規作成 - 返信 - 引用 - 返信 ... - , - - いまどうしてる? - ここに警告を書いてください - この会話の他の人: - 下書きを保存しますか? - 下書きを保存 - 返信先 - 複数選択 - 公開 - 未収載 - フォロワー限定 - ダイレクト - ハッシュタグを検索 - 下書き - 下書きを削除 - 下書きを編集 - リスト - 自分のリスト - フォロー済み + 検索 + もっと表示 + 保存した検索 + メンバーが見つかりません。 + リストのメンバー + このリストを削除 + メンバーを追加 リストの詳細 - 1 メンバー %d メンバー 1 フォロワー + 1 メンバー %d フォロワー - メンバーを追加 + このリストを削除: %s リストを編集 リストを削除 フォロー フォロー解除 - リストのメンバー - このリストを削除: %s - メンバーが見つかりません。 - メンバーを追加 - このリストを削除 - 新規リスト - リストを編集 - 名前 - 説明 - プライベート - 追加 - 削除 - メンバーを追加 - ユーザーを検索 - 設定 - 一般 - 情報 - 外観 - ハイライト色 + メンバーを追加 + トレンド + トレンド - 全世界 + Mastodonでサインイン + こんにちは!\nサインインしてはじめましょう。 + Twitter でサインイン + カスタム Twitter キーでサインイン + Twitter API v2 へのアクセスが必要です。 + 認証 + いいね + 通知を表示 + アカウント + 通知 色を選択 - タブの位置 - テーマ - タイムラインのスクロール + AMOLED 最適化モード + ハイライト色 自動 ライト ダーク - AMOLED 最適化モード + 外観 スクロール時にタブバーを隠す - スクロール時にアプリバーを隠す スクロール時にフローティングアクションボタンを隠す - 表示 - プレビュー - テキスト - 日付フォーマット - メディア + スクロール時にアプリバーを隠す + タブの位置 + テーマ + タイムラインのスクロール @TwidereProject をご利用いただきありがとうございます! + URL プレビュー + 絶対 + 相対 システムのフォントサイズを使用 + 角丸の四角 アバターのスタイル 円形 - 角丸の四角 - 相対 - 絶対 + 常に メディアのプレビュー - 自動再生 自動 - 常に + 自動再生 オフ - URL プレビュー - 通知 - 通知を表示 - アカウント - ストレージ - 検索履歴を削除 - メディアのキャッシュを削除 - 保存されたメディアのキャッシュを削除します。 - すべてのキャッシュを削除 - すべての Twidere X のキャッシュを削除します。アカウントの情報は失われません。 - その他 - 情報 + 表示 + プレビュー + 日付フォーマット + テキスト + メディア ライセンス Android 5.0+ 用の次世代の Twidere。 \nまだ開発段階です。 - Ver %s 情報ページの背景のロゴ 情報ページの背景のロゴの影 - 戻る - もっと見る - 閉じる - 完了 - Twidere X ロゴ - Twitter ロゴ - Mastodon ロゴ - GitHub ロゴ - 動画の再生 - 位置情報 - メディア - 返信 - 新規作成 - メニュー - アカウント ドロップダウン - 履歴 - 保存 - 読み込み - ウェブサイト - 位置情報 - 投稿 - メディア - お気に入り - 送信 - 位置情報を有効化 - 位置情報を無効化 - 画像を追加 - 下書きを開く - 追加 - フォントサイズ + 情報 + Ver %s + すべての Twidere X のキャッシュを削除します。アカウントの情報は失われません。 + すべてのキャッシュを削除 + 検索履歴を削除 + 保存されたメディアのキャッシュを削除します。 + メディアのキャッシュを削除 + ストレージ + 設定 + 一般 + 情報 + その他 + すべてのツイート + リプライを除外 + 返信を非表示 + + 権限がありません + このユーザーのプロフィールの閲覧をブロックされています。 + アカウントを管理 + サインイン + 下書き + 下書きを削除 + 下書きを編集 + ブックマーク + フォロワー + リスト + 下書きを保存 + 下書きを保存しますか? + 返信 ... + フォロワー限定 + 公開 + 未収載 + ダイレクト + , + ここに警告を書いてください + + いまどうしてる? + 引用 + 新規作成 + 返信 + この会話の他の人: + 複数選択 + 1 日 + 30 分 + 1 時間 + 5 分 + 6 時間 + 返信先 + フォロー済み + 自分のリスト + リスト + メンション + フォロー中 + タイムライン + ユーザーを検索 + ユーザーを探す + メッセージをコピー + あなたへのメッセージを削除 + メッセージ + ハッシュタグを検索 + 1 引用 + %d 引用 + 1 リツイート + %d リツイート + 1 いいね + %d いいね + トゥート + ツイート + 1 返信 + %d 返信 \ No newline at end of file diff --git a/app/src/main/res-localized/values-ko-rKR/strings.xml b/app/src/main/res-localized/values-ko-rKR/strings.xml index 31f5ee171..738d65df1 100644 --- a/app/src/main/res-localized/values-ko-rKR/strings.xml +++ b/app/src/main/res-localized/values-ko-rKR/strings.xml @@ -1,128 +1,133 @@ - 계정이 일시적으로 잠겼습니다. - 트위터를 열어 잠금 풀기 - 한도를 넘어서 제한이 걸렸습니다. - 트위터 API 사용량 제한이 걸렸습니다. - 권한 거부됨 - 안타깝지만 권한이 없습니다. - 권한 거부됨 - 이 사용자의 요청으로 이 계정을 팔로우하는 것이 차단되어 있습니다. - 계정 정지됨 - 트위터는 %s 규정을 어기면 계정을 정지시킵니다. - 트위터 규정 - 트윗을 찾을 수 없습니다. + %s를 뮤트하지 않습니다. %s를 그만 팔로우할까요? - %s의 팔로우 요청을 취소할까요? + 다운로드 뒤 미디어가 공유됩니다 + 팔로우를 끊지 못했습니다. + 다시 해보세요. 트윗 보냄 - 트윗을 보냅니다 - 트윗이 올라가지 않았습니다. - 다시 해보세요. - 트윗을 지웠습니다. - 트윗을 지우지 못했습니다. - 다시 해보세요. - 저장하기 - 저장했습니다 + 로그인 실패 + 서버 주소가 잘못됐습니다. + 요청이 몰렸습니다. + 팔로우했습니다. + 팔로우하지 못했습니다. + 다시 해보세요. + %s는 스팸으로 신고됐습니다. + 트위터 규정 + 계정 정지됨 + 트위터는 %s 규정을 어기면 계정을 정지시킵니다. 저장에 실패했습니다 다시 해보세요. - 이미지를 저장했습니다. + %s를 차단했습니다. + %s의 팔로우 요청을 취소할까요? + %s를 신고하지 못했습니다. + 다시 해보세요. + %s는 스팸으로 신고받아 차단되었습니다. 이미지를 저장하지 못했습니다. 다시 해보세요. + 트윗을 찾을 수 없습니다. + 저장하기 + 로그인 실패 + 연결 시간이 초과되었습니다. + %s의 차단을 풉니다. + %s를 뮤트했습니다. + 권한 거부됨 + 안타깝지만 권한이 없습니다. + 메시지를 보내는 중 + 메시지를 보내지 못했습니다 + %s의 뮤트를 풀지 못했습니다. + 다시 해보세요. + 한도를 넘어서 제한이 걸렸습니다. + 트위터 API 사용량 제한이 걸렸습니다. 불러오지 못했습니다. 다시 해보세요. - 팔로우하지 못했습니다. - 다시 해보세요. - 팔로우를 끊지 못했습니다. - 다시 해보세요. - 팔로우했습니다. - 팔로우를 끊었습니다. + 권한 거부됨 + 이 사용자의 요청으로 이 계정을 팔로우하는 것이 차단되어 있습니다. + 저장했습니다 + 트윗을 지웠습니다. 팔로우 요청을 보냈습니다. - 요청이 몰렸습니다. - %s를 뮤트했습니다. %s를 뮤트하지 못했습니다. 다시 해보세요. - %s를 뮤트하지 않습니다. - %s의 뮤트를 풀지 못했습니다. - 다시 해보세요. - %s를 차단했습니다. + 트윗을 보냅니다 + 트윗을 지우지 못했습니다. + 다시 해보세요. + 이미지를 저장했습니다. + 계정이 일시적으로 잠겼습니다. + 트위터를 열어 잠금 풀기 %s를 차단하지 못했습니다. 다시 해보세요. - %s의 차단을 풉니다. - %s 차단을 풀지 못했습니다. - 다시 해보세요. - %s는 스팸으로 신고됐습니다. - %s를 신고하지 못했습니다. - 다시 해보세요. - %s는 스팸으로 신고받아 차단되었습니다. + 트윗이 올라가지 않았습니다. + 다시 해보세요. + 팔로우를 끊었습니다. %s를 신고하고 차단하지 못했습니다. 다시 해보세요. - 로그인 실패 - 서버 주소가 잘못됐습니다. - 로그인 실패 - 연결 시간이 초과되었습니다. - 메시지를 보내는 중 - 메시지를 보내지 못했습니다 - 투표가 끝났습니다 - 투표한 것의 결과가 나왔습니다 + %s 차단을 풀지 못했습니다. + 다시 해보세요. + 메시지 + 쪽지 + 알림 + 답글과 리트윗 같은 알림 + 백그라운드에서 작동 %s가 내 툿을 부스트 했습니다 - %s가 글을 올렸습니다 %s가 팔로 요청을 보냈습니다 - %s가 내 툿에 좋아요를 눌렀습니다 - %s가 나를 팔로합니다. + 투표가 끝났습니다 %s가 언급했습니다. 새 쪽지 %s님이 메시지를 보냈습니다 + 투표한 것의 결과가 나왔습니다 + %s가 내 툿에 좋아요를 눌렀습니다 + %s가 나를 팔로합니다. + %s가 글을 올렸습니다 + %s 차단하기 + 팔로워 + 팔로워 + %s가 날 팔로우하지 않음 + 나를 팔로우하고 있음 + %s 숨기기 + %s가 날 팔로우하고 있음 + 차단 풀기 + 신고하고 차단하기 + 팔로우 중 + 대기중 + 신고하기 + 숨기기 + 숨기기 풀기 + 차단하기 + 팔로우하기 + 팔로우 끊기 + 팔로워 + 담긴 리스트 + 팔로우하는 계정 + 더 불러오기 + 갤러리 새로 만들기 - 지우기 + 돌아가기 + 미리보기 + 미디어 공유하기 고치기 + 사파리에서 보기 + 저장 - 확인 + 지우기 확인 - - 돌아가기 - 사진 찍기 사진 저장하기 + 사진 찍기 + 확인 로그인 - 미리보기 - 사파리에서 보기 - 더 불러오기 - 글 복사하기 - 인용 - 리트윗 - 링크 복사하기 - 링크 공유하기 - 트윗 지우기 - 투표 - 미디어 %s가 리트윗함 - 닫힘 + 미디어 %s명 %s표 - %s명 + 닫힘 %s표 + %s명 이 타래 보기 - 팔로우하기 - 팔로우 끊기 - 팔로우 중 - 대기중 - 숨기기 - 숨기기 풀기 - 차단하기 - 차단 풀기 - 신고하기 - 신고하고 차단하기 - 팔로워 - 팔로워 - 나를 팔로우하고 있음 - %s가 날 팔로우하지 않음 - %s가 날 팔로우하고 있음 - %s 숨기기 - %s 차단하기 - 팔로우하는 계정 - 팔로워 - 담긴 리스트 - 갤러리 - %s개 답글 - %s개 답글 + 링크 공유하기 + 트윗 지우기 + 인용 + 리트윗 + 글 복사하기 + 투표 + 링크 복사하기 %s개 인용 %s개 인용 %s개 리트윗 @@ -131,229 +136,242 @@ %s개 좋아요 %s명의 멤버 %s명의 멤버 - %s개 리스트 - %s개 리스트 - %s개 트윗 - %s개 트윗 사진 %s장 사진 %s장 - 백그라운드에서 작동 - 소통 - 답글과 리트윗 같은 소통 - 메시지 - 쪽지 - 로그인 - 계정 관리 - 계정 + %s개 트윗 + %s개 트윗 + %s개 답글 + %s개 답글 + %s개 리스트 + %s개 리스트 + 네트워크 이미지 + 더 보기 + 뒤로 + 트위데레 X 로고 + 트위터 로고 + 깃허브 로고 + 마스토돈 로고 + 텔레그램 로고 + 영상 보기 + 닫기 + 닫기 + 위치 + 미디어 + 리트윗 + 좋아요 + 답글 + 리트윗함 + 글꼴 크기 + 저장 + 기록 + 새로 만들기 + 사진 넣기 + 답글 달기 + 임시 보관함 열기 + 위치 켜기 + 위치 끄기 + 타래 모드 + 보내기 + 불러오기 + 웹사이트 + 미디어 + 좋아요 + 상태 + 위치 + 쓰기 + 계정 목록 + 메뉴 + 리스트 이름 바꾸기 + 리스트 만들기 + 비공개 + 리스트 고치기 + 이름 + 새 리스트 + 설명 + 사람 찾기 + 멤버 추가 + 새로 만들기 + 지우기 + 모두 + 알림 계정 지우기 - 안녕하세요!\n로그인해서 시작하세요. - 트위터로 로그인하기 - Mastodon으로 로그인하기 - 트위터 커스텀 키로 로그인하기 - 트위터 API v2 접근 권한이 있어야 합니다. - 인증 - 타임라인 - 답글 - 알림 - 모두 - 메시지 - [사진] - 메시지 글자 복사 - 메시지 지우기 - 사람 찾기 - 사람 찾기 - 메시지를 보내지 못했습니다 - 좋아요 - 즐겨찾기 - 트렌드 - 실시간 트렌드 - 전세계 - 실시간 트렌드 - %d 명이 언급함 - 트윗 - - 답글 한 개 - %d개 답글 - 한 번 인용 - %d개 인용 - 1 리트윗 - %d 리트윗 - 1 좋아요 - %d 좋아요 - 찾기 + 계정 + 덜 보기 트윗 또는 사용자 찾기 - 검색 기록 - 트윗 미디어 + 트윗 사용자 해시태그 + 찾기 더 보기 - 덜 보기 - - 답글 숨기기 - 권한 거부됨 - 이 사용자에게 차단당해 프로필을 볼 수 없습니다. - 모든 트윗 - 답글 빼기 - 내가 팔로우하는 계정 - 나를 팔로우하는 계정 - 담긴 리스트 - 쓰기 - 답글 - 인용 - …에 답글 - , - 그리고 - 무슨 일이 일어나고 있나요? - 여기에 주의사항을 적어주세요 - 이 대화에 있는 다른 사람 - 임시 저장할까요? - 임시 저장하기 - 답글 달기 - 여러 개 고르기 - 5분 - 30분 - 1시간 - 6시간 - 하루 - 사흘 - 일주일 - 공개 - 리스트 되지 않음 - 비공개 - 직접 - 사용자 찾기 - 해시태그 찾기 - 임시 보관함 - 지우기 - 다시 쓰기 - 리스트 - 내가 만든 리스트 - 구독함 - 잠근 계정 보기 - 리스트 만들기 + 검색 기록 + 멤버가 없습니다. + 구독자 + 리스트 멤버 + 리스트 지우기 + 멤버 추가 리스트 정보 - 1 멤버 멤버 %d명 구독자 1 + 1 멤버 구독자 %d명 - 멤버 추가 + %s 리스트를 지웁니다. 리스트 고치기 이름 바꾸기 리스트 지우기 팔로우하기 팔로우 끊기 - 리스트 멤버 - 구독자 - %s 리스트를 지웁니다. - 멤버가 없습니다. - 멤버 추가 - 리스트 지우기 - 새 리스트 - 리스트 고치기 - 이름 - 설명 - 비공개 - 리스트 만들기 - 리스트 이름 바꾸기 - 새로 만들기 - 지우기 - 멤버 추가 - 사람 찾기 - 설정 - 일반 - 정보 - 모양 - 강조색 + 멤버 추가 + 실시간 트렌드 + %d 명이 언급함 + 트렌드 + 실시간 트렌드 - 전세계 + Mastodon으로 로그인하기 + 안녕하세요!\n로그인해서 시작하세요. + 트위터로 로그인하기 + 트위터 커스텀 키로 로그인하기 + 트위터 API v2 접근 권한이 있어야 합니다. + 인증 + 좋아요 + 화면 구성 + 탭바 요소 + 서랍 요소 + 사용자 지정 화면 구성 + 탭바에 표시할 요소 5가지를 고르고 배치하세요. (로컬과 연합 타임라인은 마스토돈에서만 보입니다.) + 알림 보이기 + 계정 + 알림 색 고르기 - 탭 위치 - 테마 - 타임라인을 스크롤할 때 + AMOLED 최적화 모드 맨 아래 + 강조색 자동 밝게 어둡게 - AMOLED 최적화 모드 + 모양 탭 바 숨기기 - 제목 표시줄 숨기기 글쓰기 버튼 숨기기 - 보기 - 미리보기 - 글자 - 날짜 형식 - 미디어 + 제목 표시줄 숨기기 + 탭 위치 + 테마 + 타임라인을 스크롤할 때 @TwidereProject를 써주셔서 고맙습니다! + 링크 미리보기 + 절대 시간 + 상대적 시간 (~분 전) 시스템 글꼴 크기 적용 + 둥근 네모 프로필 이미지의 모양 둥글게 - 둥근 네모 - 상대적 시간 (~분 전) - 절대 시간 + 언제나 미디어 미리보기 - 자동 재생 자동 - 언제나 + 자동 재생 끄기 - 링크 미리보기 - 알림 - 알림 보이기 - 계정 - 저장소 + 보기 + 미리보기 + 날짜 형식 + 글자 + 미디어 + 라이선스 + 안드로이드 5.0 이상을 위한 트위데레의 다음 버전입니다. \n아직 베타 단계입니다. + 페이지 바탕 로고에 대하여 + 페이지 바탕 로고의 그림자에 대하여 + 정보 + %s 버전 + 트위데레X의 모든 캐시를 지웁니다. 계정은 지워지지 않습니다. + 모든 캐시 지우기 검색 기록 지우기 - 미디어 캐시 지우기 저장된 미디어 미리보기 파일을 지웁니다. - 모든 캐시 지우기 - 트위데레X의 모든 캐시를 지웁니다. 계정은 지워지지 않습니다. - 잡동사니 - 제3의 트위터 정보 제공자 - Nitter 인스턴스 + 미디어 캐시 지우기 + 저장소 + 설정 + 일반 + 정보 + 서버 + 비밀번호 + 포트 + 프록시 서버의 포트는 숫자여야 합니다. + 모든 네트워크 연결에 프록시 쓰기 + 프록시 + 프록시 설정 + HTTP + 프록시 유형 + 반대로 + 사용자 이름 개인정보보호에 초점을 둔 트위터 프론트엔드 대체 기능 - 제3의 트위터 정보 제공자 - 트위터 API의 제약 때문에 일부 정보는 트위터에서 불러올 수 없습니다. 이에 따라 제3의 정보 제공자에게서 정보를 받아올 수 있습니다. 트위데레는 이에 관해 어떠한 책임도 지지 않습니다. + Nitter 인스턴스 다음의 제3 트위터 정보 제공자를 사용 중 - 트윗 타래 프로젝트 소개 보기 - 정보 - 라이선스 - 안드로이드 5.0 이상을 위한 트위데레의 다음 버전입니다. \n아직 베타 단계입니다. - %s 버전 - 페이지 바탕 로고에 대하여 - 페이지 바탕 로고의 그림자에 대하여 - 네트워크 이미지 - 뒤로 - 더 보기 - 닫기 - 닫기 - 트위데레 X 로고 - 트위터 로고 - 마스토돈 로고 - 깃허브 로고 - 텔레그램 로고 - 영상 보기 - 위치 - 리트윗함 - 미디어 - 답글 - 리트윗 - 좋아요 - 쓰기 - 메뉴 - 계정 목록 - 기록 - 저장 - 불러오기 - 웹사이트 - 위치 - 상태 - 미디어 - 좋아요 - 보내기 - 위치 켜기 - 위치 끄기 - 답글 달기 - 사진 넣기 - 임시 보관함 열기 - 타래 모드 - 새로 만들기 - 글꼴 크기 + 트윗 타래 + 제3의 트위터 정보 제공자 + 트위터 API의 제약 때문에 일부 정보는 트위터에서 불러올 수 없습니다. 이에 따라 제3의 정보 제공자에게서 정보를 받아올 수 있습니다. 트위데레는 이에 관해 어떠한 책임도 지지 않습니다. + 제3의 트위터 정보 제공자 + 잡동사니 + 모든 트윗 + 답글 빼기 + 답글 숨기기 + + 권한 거부됨 + 이 사용자에게 차단당해 프로필을 볼 수 없습니다. + 계정 관리 + 로그인 + 임시 보관함 + 지우기 + 다시 쓰기 + 사용자 찾기 + 즐겨찾기 + 나를 팔로우하는 계정 + 담긴 리스트 + 임시 저장하기 + 임시 저장할까요? + …에 답글 + 비공개 + 공개 + 리스트 되지 않음 + 직접 + , + 여기에 주의사항을 적어주세요 + 그리고 + 무슨 일이 일어나고 있나요? + 인용 + 쓰기 + 답글 + 이 대화에 있는 다른 사람 + 여러 개 고르기 + 하루 + 30분 + 일주일 + 1시간 + 사흘 + 5분 + 6시간 + 답글 달기 + 구독함 + 내가 만든 리스트 + 리스트 + 잠근 계정 보기 + 리스트 만들기 + 답글 + 내가 팔로우하는 계정 + 타임라인 + [사진] + 사람 찾기 + 사람 찾기 + 메시지를 보내지 못했습니다 + 메시지 글자 복사 + 메시지 지우기 + 메시지 + 해시태그 찾기 + 한 번 인용 + %d개 인용 + 1 리트윗 + %d 리트윗 + 1 좋아요 + %d 좋아요 + + 트윗 + 답글 한 개 + %d개 답글 \ No newline at end of file diff --git a/app/src/main/res-localized/values-pt-rBR/strings.xml b/app/src/main/res-localized/values-pt-rBR/strings.xml index 994a40d5f..ffc4e0e9a 100644 --- a/app/src/main/res-localized/values-pt-rBR/strings.xml +++ b/app/src/main/res-localized/values-pt-rBR/strings.xml @@ -1,124 +1,133 @@ - Conta Temporariamente Bloqueada - Abra o Twitter para desbloquear - Limite de Acesso Excedido - Atingiu o limite de uso da API do Twitter - Permissão Negada - Desculpe, você não está autorizado - Permissão Negada - Você foi impedido de seguir esta conta a pedido do usuário - Conta Suspensa - O Twitter suspende contas que violam as %s - Regras do Twitter - Nenhum Tweet Encontrado + %s foi dessilenciado Deixar de seguir o usuário %s? - Cancelar pedido para seguir %s? + A mídia será compartilhada depois que o download for concluído + Falha ao Deixar de Seguir + Por favor, tente novamente Tweet Enviado - Enviando tweet - Tweet Falhou - Por favor, tente novamente - Tweet Excluído - Falha ao Excluir Tweet - Por favor, tente novamente - Salvando mídia - Mídia salva + Falha ao Fazer Login + A URL do servidor está incorreta. + Muitas solicitações + Seguiu com Sucesso + Falha ao Seguir + Por favor, tente novamente + %s foi reportado por spam + Regras do Twitter + Conta Suspensa + O Twitter suspende contas que violam as %s Falha ao salvar mídia Por favor, tente novamente - Foto Salva + %s foi bloqueado + Cancelar pedido para seguir %s? + Falha ao reportar %s + Por favor, tente novamente + %s foi reportado por spam e bloqueado Falha ao Salvar Foto Por favor, tente novamente + Nenhum Tweet Encontrado + Salvando mídia + Falha ao Fazer Login + Tempo limite de conexão. + %s foi desbloqueado + %s foi silenciado + Permissão Negada + Desculpe, você não está autorizado + Enviando mensagem + Falha ao enviar mensagem + Falha ao dessilenciar %s + Por favor, tente novamente + Limite de Acesso Excedido + Atingiu o limite de uso da API do Twitter Falha ao Carregar Por favor, tente novamente - Falha ao Seguir - Por favor, tente novamente - Falha ao Deixar de Seguir - Por favor, tente novamente - Seguiu com Sucesso - Deixou de Seguir com Sucesso + Permissão Negada + Você foi impedido de seguir esta conta a pedido do usuário + Mídia salva + Tweet Excluído Pedido para Seguir Enviado - Muitas solicitações - %s foi silenciado Falha ao silenciar %s Por favor, tente novamente - %s foi dessilenciado - Falha ao dessilenciar %s - Por favor, tente novamente - %s foi bloqueado + Enviando tweet + Falha ao Excluir Tweet + Por favor, tente novamente + Foto Salva + Conta Temporariamente Bloqueada + Abra o Twitter para desbloquear Falha ao bloquear %s Por favor, tente novamente - %s foi desbloqueado - Falha ao desbloquear %s - Por favor, tente novamente - %s foi reportado por spam - Falha ao reportar %s - Por favor, tente novamente - %s foi reportado por spam e bloqueado + Tweet Falhou + Por favor, tente novamente + Deixou de Seguir com Sucesso Falha ao reportar e bloquear %s Por favor, tente novamente - Falha ao Fazer Login - A URL do servidor está incorreta. - Falha ao Fazer Login - Tempo limite de conexão. - Sua enquete terminou - Uma enquete em que você votou terminou + Falha ao desbloquear %s + Por favor, tente novamente + Mensagens + Mensagens diretas + Interações + Interações como menções e retweets + Progressos em segundo plano %s impulsionou seu toot - %s acabou de postar %s pediu para segui-lo + Sua enquete terminou + %s mencionou você + Nova mensagem direta + %s te enviou uma mensagem + Uma enquete em que você votou terminou %s favoritou seu toot %s seguiu você - %s mencionou você + %s acabou de postar + Bloquear %s + seguidor + seguidores + %s não está seguindo você + Segue você + Silenciar %s + %s está seguindo você + Desbloquear + Reportar e Bloquear + Seguindo + Pendente + Reportar + Silenciar + Dessilenciar + Bloquear + Seguir + Deixar de seguir + Seguidores + Listado + Seguindo + Carregar Mais + Biblioteca de Fotos Adicionar - Remover + Cancelar + Pré-visualização + Compartilhar mídia Editar + Abrir no Safari + Sim Salvar - OK + Remover Confirmar - Sim - Cancelar - Tirar foto Salvar foto + Tirar foto + OK Entrar - Pré-visualização - Abrir no Safari - Carregar Mais - Copiar texto - Citação - Retweet - Copiar link - Compartilhar link - Excluir tweet - Votar - Mídia %s retweetado - Fechado + Mídia %s pessoa %s voto - %s pessoas + Fechado %s votos + %s pessoas Mostrar esta thread - Seguir - Deixar de seguir - Seguindo - Pendente - Silenciar - Dessilenciar - Bloquear - Desbloquear - Reportar - Reportar e Bloquear - seguidor - seguidores - Segue você - %s não está seguindo você - %s está seguindo você - Silenciar %s - Bloquear %s - Seguindo - Seguidores - Listado - Biblioteca de Fotos - %s resposta - %s respostas + Compartilhar link + Excluir tweet + Citação + Retweet + Copiar texto + Votar + Copiar link %s citação %s citações %s retweet @@ -127,221 +136,242 @@ %s curtidas %s membro %s membros - %s lista - %s listas - %s tweet - %s tweets %s foto %s fotos - Progressos em segundo plano - Interações - Interações como menções e retweets - Mensagens - Entrar - Gerenciar contas - Contas + %s tweet + %s tweets + %s resposta + %s respostas + %s lista + %s listas + Imagem da Rede + Mais + Voltar + Logo do Twidere X + Logo do Twitter + Logo do GitHub + Logo do Mastodon + Logo do Telegram + Reproduzir vídeo + Fechar + Concluído + Localização + Mídia + Retweet + Curtir + Responder + Retweetado + Tamanho da Fonte + Salvar + Histórico + Adicionar + Adicionar imagem + Adicionar menção + Rascunho aberto + Ativar Localização + Desativar Localização + Modo thread + Enviar + Carregar + Site + Mídia + Favorito + Status + Localização + Compor + Abaixar Conta + Menu + Renomear a lista + Criar uma lista + Privado + Editar Lista + Nome + Nova Lista + Descrição + Buscar pessoas + Adicionar Membro + Adicionar + Remover + Tudo + Notificação Excluir conta - Olá!\nFaça Login para Começar. - Entrar com o Twitter - Fazer Login com Mastodon - Fazer Login com uma Chave Personalizada do Twitter - O acesso à API v2 do Twitter é necessário. - Autenticação - Linha do Tempo - Menções - Notificação - Tudo - Mensagens - Buscar pessoas - Curtidas - Favorito - Trends - Trends - Mundial - Tendências Agora - %d pessoas falando - Tweet - Toot - 1 Resposta - %d Respostas - 1 Citação - %d Citações - 1 Retweet - %d Retweets - 1 Curtida - %d Curtidas - Buscar + Contas + Mostrar menos Buscar tweets ou usuários - Busca Salva - Tweets Mídia + Tweets Usuários Hashtag + Buscar Mostrar mais - Mostrar menos - Eu - Ocultar resposta - Permissão Negada - Você foi impedido de visualizar o perfil deste usuário. - Todos os tweets - Excluir respostas - Seguindo - Seguidores - Listado - Compor - Responder - Citação - Resposta para … - , - e - O que está acontecendo? - Escreva seu aviso aqui - Outros nesta conversa: - Salvar rascunho? - Salvar rascunho - Em resposta a - Múltipla escolha - 5 minutos - 30 minutos - 1 hora - 6 horas - 1 dia - 3 dias - 7 dias - Público - Não listado - Privado - Direto - Buscar usuários - Buscar hashtag - Rascunhos - Excluir rascunho - Editar rascunho - Listas - MINHAS LISTAS - INSCRITO - Visibilidade privada - Criar lista + Busca Salva + Nenhum Membro Encontrado. + Inscritos + Listar Membros + Excluir esta lista + Adicionar Membros Detalhes das Listas - 1 Membro %d Membros 1 Inscrito + 1 Membro %d Inscritos - Adicionar Membro + Excluir esta lista: %s Editar Lista Renomear Lista Excluir Lista Seguir Deixar de seguir - Listar Membros - Inscritos - Excluir esta lista: %s - Nenhum Membro Encontrado. - Adicionar Membros - Nova Lista - Editar Lista - Nome - Descrição - Privado - Criar uma lista - Renomear a lista - Adicionar - Remover - Adicionar Membro - Buscar pessoas - Configurações - Geral - Sobre - Aparência - Cor de destaque + Adicionar Membro + Tendências Agora + %d pessoas falando + Trends + Trends - Mundial + Fazer Login com Mastodon + Olá!\nFaça Login para Começar. + Entrar com o Twitter + Fazer Login com uma Chave Personalizada do Twitter + O acesso à API v2 do Twitter é necessário. + Autenticação + Curtidas + Layout + Ações da barra de abas + Ações do menu + Layout Personalizado + Escolha e organize até 5 ações que aparecerão na barra de abas (os cronogramas locais e federais serão exibidos apenas no Mastodon.) + Mostrar Notificação + Contas + Notificação Escolher cor - Posição da aba - Tema - Rolar linha do tempo + Modo otimizado para AMOLED Superior Inferior + Cor de destaque Automático Claro Escuro - Modo otimizado para AMOLED + Aparência Ocultar barra de aba ao rolar - Ocultar barra do aplicativo ao rolar Ocultar FAB ao rolar - Visualização - Pré-visualização - Texto - Formato da Data - Mídia + Ocultar barra do aplicativo ao rolar + Posição da aba + Tema + Rolar linha do tempo Obrigado por usar o @TwidereProject! + Pré-visualizações do Url + Absoluto + Relativo Usar o tamanho da fonte do sistema + Quadrado Arredondado Estilo do Avatar Círculo - Quadrado Arredondado - Relativo - Absoluto + Sempre Pré-visualizações de mídia - Reprodução automática Automático - Sempre + Reprodução automática Desligado - Pré-visualizações do Url - Notificação - Contas - Armazenamento + Visualização + Pré-visualização + Formato da Data + Texto + Mídia + Licença + Próxima geração do Twidere para Android 5.0+. \nAinda na fase inicial. + Sobre o logo da página de fundo + Sobre a sombra do logo da página de fundo + Sobre + Ver %s + Exclua todo o cache do Twidere X. Suas credenciais de conta não serão perdidas. + Limpar todo o cache Limpar histórico de busca - Limpar cache de mídia Limpar cache de mídia armazenado. - Limpar todo o cache - Exclua todo o cache do Twidere X. Suas credenciais de conta não serão perdidas. - Miscelânea - Provedor de dados do Twitter de terceiros - Instância Nitter + Limpar cache de mídia + Armazenamento + Configurações + Geral + Sobre + Servidor + Senha + Porta + A porta do servidor proxy deve ser números + Usar proxy para todas as solicitações de rede + Proxy + Configurações de proxy + HTTP + Tipo de proxy + Inverso + Nome de usuário Front-end alternativo do Twitter focado em privacidade. - Provedor de dados do Twitter de terceiros - Devido à limitação da API do Twitter, alguns dados podem não ser capazes de ser obtidos do Twitter, você pode usar um provedor de dados de terceiros para fornecer esses dados. O Twidere não se responsabiliza por eles. + Instância Nitter Usando o provedor de dados de terceiros em - - Threading de status do Twitter URL do Projeto - Sobre - Licença - Próxima geração do Twidere para Android 5.0+. \nAinda na fase inicial. - Ver %s - Sobre o logo da página de fundo - Sobre a sombra do logo da página de fundo - Imagem da Rede - Voltar - Mais - Fechar - Concluído - Logo do Twidere X - Logo do Twitter - Logo do Mastodon - Logo do GitHub - Logo do Telegram - Reproduzir vídeo - Localização - Retweetado - Mídia - Responder - Retweet - Curtir - Compor - Menu - Abaixar Conta - Histórico - Salvar - Carregar - Site - Localização - Status - Mídia - Favorito - Enviar - Ativar Localização - Desativar Localização - Adicionar menção - Adicionar imagem - Rascunho aberto - Modo thread - Adicionar - Tamanho da Fonte + - Threading de status do Twitter + Provedor de dados do Twitter de terceiros + Devido à limitação da API do Twitter, alguns dados podem não ser capazes de ser obtidos do Twitter, você pode usar um provedor de dados de terceiros para fornecer esses dados. O Twidere não se responsabiliza por eles. + Provedor de dados do Twitter de terceiros + Miscelânea + Todos os tweets + Excluir respostas + Ocultar resposta + Eu + Permissão Negada + Você foi impedido de visualizar o perfil deste usuário. + Gerenciar contas + Entrar + Rascunhos + Excluir rascunho + Editar rascunho + Buscar usuários + Favorito + Seguidores + Listado + Salvar rascunho + Salvar rascunho? + Resposta para … + Privado + Público + Não listado + Direto + , + Escreva seu aviso aqui + e + O que está acontecendo? + Citação + Compor + Responder + Outros nesta conversa: + Múltipla escolha + 1 dia + 30 minutos + 7 dias + 1 hora + 3 dias + 5 minutos + 6 horas + Em resposta a + INSCRITO + MINHAS LISTAS + Listas + Visibilidade privada + Criar lista + Menções + Seguindo + Linha do Tempo + [Foto] + Buscar pessoas + Encontrar pessoas + Falha ao enviar mensagem + Copiar texto da mensagem + Excluir mensagem para você + Mensagens + Buscar hashtag + 1 Citação + %d Citações + 1 Retweet + %d Retweets + 1 Curtida + %d Curtidas + Toot + Tweet + 1 Resposta + %d Respostas \ No newline at end of file diff --git a/app/src/main/res-localized/values-si-rLK/strings.xml b/app/src/main/res-localized/values-si-rLK/strings.xml index 2156c38cc..802670e21 100644 --- a/app/src/main/res-localized/values-si-rLK/strings.xml +++ b/app/src/main/res-localized/values-si-rLK/strings.xml @@ -1,22 +1,22 @@ එකතු කරන්න - ඉවත් කරන්න + අවලංගු කරන්න සංස්කරණය සුරකින්න + ඉවත් කරන්න හරි - අවලංගු කරන්න මාධ්‍යය - සොයන්න - ටුවිට්ස් - මාධ්‍යය - පරිශිලකයන් - , - එකතු කරන්න - ඉවත් කරන්න - සැකසුම් - මාධ්‍යය මාධ්‍යය සුරකින්න - මාධ්‍යය එකතු කරන්න + මාධ්‍යය + එකතු කරන්න + ඉවත් කරන්න + මාධ්‍යය + ටුවිට්ස් + පරිශිලකයන් + සොයන්න + මාධ්‍යය + සැකසුම් + , \ No newline at end of file diff --git a/app/src/main/res-localized/values-tr-rTR/strings.xml b/app/src/main/res-localized/values-tr-rTR/strings.xml index a94b0d757..7be0690c4 100644 --- a/app/src/main/res-localized/values-tr-rTR/strings.xml +++ b/app/src/main/res-localized/values-tr-rTR/strings.xml @@ -1,57 +1,57 @@ - Hesap Geçici Olarak Kilitlendi - Kilidi açmak için Twitter\'ı açın - Twitter API kullanım sınırına ulaşıldı - İzin Reddedildi - Üzgünüm, yetkiniz yok - İzin Reddedildi - Kullanıcının isteği üzerine bu hesabı takip etmeniz engellendi - Hesap Askıya Alındı - Twitter, %s ihlal eden hesapları askıya alır - Twitter Kuralları - Tweet Bulunamadı + %s sessizden çıkarıldı %s adlı kullanıcı takipten çıkılsın mı? - %s takip isteği iptal edilsin mi? + Takip Bırakılamadı + Lütfen tekrar deneyin Tweet Gönderildi - Tweet gönderiliyor - Tweet gönderilemedi - Lütfen tekrar deneyin - Tweet Silindi - Tweet Silinemedi - Lütfen tekrar deneyin - Medya kaydediliyor - Medya kaydedildi + Çok Fazla İstek + Takip Edildi + Takip Edilemedi + Lütfen tekrar deneyin + Twitter Kuralları + Hesap Askıya Alındı + Twitter, %s ihlal eden hesapları askıya alır Medya kaydedilemedi Lütfen tekrar deneyin - Fotoğraf Kaydedildi + %s engellendi + %s takip isteği iptal edilsin mi? + Lütfen tekrar deneyin Fotoğrafı kaydetme başarısız Lütfen tekrar deneyin + Tweet Bulunamadı + Medya kaydediliyor + %s engeli kaldırıldı + %s sessize alındı + İzin Reddedildi + Üzgünüm, yetkiniz yok + %s sessizden çıkarılamadı + Lütfen tekrar deneyin + Twitter API kullanım sınırına ulaşıldı Yükleme Başarısız Lütfen tekrar deneyin - Takip Edilemedi - Lütfen tekrar deneyin - Takip Bırakılamadı - Lütfen tekrar deneyin - Takip Edildi - Takip Bırakıldı + İzin Reddedildi + Kullanıcının isteği üzerine bu hesabı takip etmeniz engellendi + Medya kaydedildi + Tweet Silindi Takip Talebi Gönderildi - Çok Fazla İstek - %s sessize alındı %s susturulamadı Lütfen tekrar deneyin - %s sessizden çıkarıldı - %s sessizden çıkarılamadı - Lütfen tekrar deneyin - %s engellendi + Tweet gönderiliyor + Tweet Silinemedi + Lütfen tekrar deneyin + Fotoğraf Kaydedildi + Hesap Geçici Olarak Kilitlendi + Kilidi açmak için Twitter\'ı açın %s engellenemedi Lütfen tekrar deneyin - %s engeli kaldırıldı + Tweet gönderilemedi + Lütfen tekrar deneyin + Takip Bırakıldı + Lütfen tekrar deneyin %s engeli kaldırılamadı Lütfen tekrar deneyin - Lütfen tekrar deneyin - Lütfen tekrar deneyin takipçiler - Sizi takip ediyor %s sizi takip etmiyor + Sizi takip ediyor İzin Reddedildi \ No newline at end of file diff --git a/app/src/main/res-localized/values-vi-rVN/strings.xml b/app/src/main/res-localized/values-vi-rVN/strings.xml index 5e8d52825..574cc74be 100644 --- a/app/src/main/res-localized/values-vi-rVN/strings.xml +++ b/app/src/main/res-localized/values-vi-rVN/strings.xml @@ -1,124 +1,126 @@ - Tài khoản bị khoá tạm thời - Mở Twitter để mở khoá - Vượt quá giới hạn truy cập - Đã đạt đến giới hạn sử dụng API Twitter - Quyền bị từ chối - Xin lỗi, bạn không được uỷ quyền - Quyền bị từ chối - Bạn đã bị chặn theo dõi tài khoản này theo yêu cầu của người dùng - Tài khoản bị tạm ngừng hoạt động - Twitter tạm ngừng hoạt động các tài khoản vi phạm %s - Nội quy của Twitter - Không tìm thấy Tweet nào + %s đã được bỏ tắt tiếng Bỏ theo dõi người dùng %s? - Hủy yêu cầu theo dõi cho %s? + Bỏ theo dõi thất bại + Vui lòng thử lại Đã gửi Tweet - Đang gửi Tweet - Tweet thất bại - Vui lòng thử lại - Đã xoá Tweet - Xoá Tweet thất bại - Vui lòng thử lại - Đang lưu phương tiện - Đã lưu phương tiện + Đăng nhập thất bại + URL máy chủ không đúng. + Quá nhiều yêu cầu + Theo dõi thành công + Theo dõi thất bại + Vui lòng thử lại + %s đã bị báo cáo vì spam + Nội quy của Twitter + Tài khoản bị tạm ngừng hoạt động + Twitter tạm ngừng hoạt động các tài khoản vi phạm %s Lưu phương tiện thất bại Vui lòng thử lại - Đã lưu ảnh + %s đã bị chặn + Hủy yêu cầu theo dõi cho %s? + Báo cáo %s thất bại + Vui lòng thử lại + %s đã bị báo cáo vì spam và đã bị chặn Lưu ảnh thất bại Vui lòng thử lại + Không tìm thấy Tweet nào + Đang lưu phương tiện + Đăng nhập thất bại + Hết thời gian chờ kết nối. + %s đã được bỏ chặn + %s đã bị tắt tiếng + Quyền bị từ chối + Xin lỗi, bạn không được uỷ quyền + Bỏ tắt tiếng %s thất bại + Vui lòng thử lại + Vượt quá giới hạn truy cập + Đã đạt đến giới hạn sử dụng API Twitter Tải thất bại Vui lòng thử lại - Theo dõi thất bại - Vui lòng thử lại - Bỏ theo dõi thất bại - Vui lòng thử lại - Theo dõi thành công - Bỏ theo dõi thành công + Quyền bị từ chối + Bạn đã bị chặn theo dõi tài khoản này theo yêu cầu của người dùng + Đã lưu phương tiện + Đã xoá Tweet Đã gửi yêu cầu theo dõi - Quá nhiều yêu cầu - %s đã bị tắt tiếng Tắt tiếng %s thất bại Vui lòng thử lại - %s đã được bỏ tắt tiếng - Bỏ tắt tiếng %s thất bại - Vui lòng thử lại - %s đã bị chặn + Đang gửi Tweet + Xoá Tweet thất bại + Vui lòng thử lại + Đã lưu ảnh + Tài khoản bị khoá tạm thời + Mở Twitter để mở khoá Chặn %s thất bại Vui lòng thử lại - %s đã được bỏ chặn - Bỏ chặn %s thất bại - Vui lòng thử lại - %s đã bị báo cáo vì spam - Báo cáo %s thất bại - Vui lòng thử lại - %s đã bị báo cáo vì spam và đã bị chặn + Tweet thất bại + Vui lòng thử lại + Bỏ theo dõi thành công Báo cáo và chặn %s thất bại Vui lòng thử lại - Đăng nhập thất bại - URL máy chủ không đúng. - Đăng nhập thất bại - Hết thời gian chờ kết nối. - Cuộc bình chọn của bạn đã kết thúc - Một cuộc bình chọn mà bạn đã bình chọn trong đó đã kết thúc + Bỏ chặn %s thất bại + Vui lòng thử lại + Tin nhắn + Tương tác + Các sự tương tác như nhắc đến và retweet + Tiến trình trong nền %s đã tăng cường toot của bạn - %s vừa mới đăng %s đã yêu cầu theo dõi bạn + Cuộc bình chọn của bạn đã kết thúc + %s nhắc đến bạn + Một cuộc bình chọn mà bạn đã bình chọn trong đó đã kết thúc %s đã yêu thích toot của bạn %s đã theo dõi bạn - %s nhắc đến bạn + %s vừa mới đăng + Chặn %s + người theo dõi + người theo dõi + %s đang không theo dõi bạn + Theo dõi bạn + Tắt tiếng %s + %s đang theo dõi bạn + Bỏ chặn + Báo cáo và chặn + Đang theo dõi + Đang chờ + Báo cáo + Tắt tiếng + Bỏ tắt tiếng + Chặn + Theo dõi + Bỏ theo dõi + Người theo dõi + Đã liệt kê + Đang theo dõi + Tải thêm + Thư viện ảnh Thêm - Xoá + Huỷ + Xem trước Sửa + Mở trong Safari + Lưu - OK + Xoá Xác nhận - - Huỷ - Chụp ảnh Lưu ảnh + Chụp ảnh + OK Đăng nhập - Xem trước - Mở trong Safari - Tải thêm - Sao chép văn bản - Trích dẫn - Retweet - Sao chép liên kết - Chia sẻ liên kết - Xoá tweet - Bình chọn - Phương tiện %s đã retweet - Đã đóng + Phương tiện %s người %s bình chọn - %s người + Đã đóng %s bình chọn + %s người Hiện chủ đề này - Theo dõi - Bỏ theo dõi - Đang theo dõi - Đang chờ - Tắt tiếng - Bỏ tắt tiếng - Chặn - Bỏ chặn - Báo cáo - Báo cáo và chặn - người theo dõi - người theo dõi - Theo dõi bạn - %s đang không theo dõi bạn - %s đang theo dõi bạn - Tắt tiếng %s - Chặn %s - Đang theo dõi - Người theo dõi - Đã liệt kê - Thư viện ảnh - %s câu trả lời - %s câu trả lời + Chia sẻ liên kết + Xoá tweet + Trích dẫn + Retweet + Sao chép văn bản + Bình chọn + Sao chép liên kết %s lời trích dẫn %s lời trích dẫn %s retweet @@ -127,221 +129,219 @@ %s lượt thích %s thành viên %s thành viên - %s danh sách - %s danh sách - %s tweet - %s tweet %s ảnh %s ảnh - Tiến trình trong nền - Tương tác - Các sự tương tác như nhắc đến và retweet - Tin nhắn - Đăng nhập - Quản lý tài khoản - Tài khoản + %s tweet + %s tweet + %s câu trả lời + %s câu trả lời + %s danh sách + %s danh sách + Hình ảnh mạng + Thêm + Quay lại + Logo Twidere X + Logo Twitter + Logo Github + Logo Mastodon + Logo Telegram + Phát video + Đóng + Xong + Vị trí + Phương tiện + Retweet + Thích + Trả lời + Đã retweet + Cỡ chữ + Lưu + Lịch sử + Thêm + Thêm hình ảnh + Thêm nhắc đến + Mở bản nháp + Bật vị trí + Tắt vị trí + Chế độ chủ đề + Gửi + Tải + Trang web + Phương tiện + Yêu thích + Trạng thái + Vị trí + Soạn + Mục tài khoản kiểu thả xuống + Menu + Đổi tên danh sách + Tạo danh sách + Riêng tư + Sửa danh sách + Tên + Danh sách mới + Mô tả + Tìm kiếm người + Thêm thành viên + Thêm + Xoá + Tất cả + Thông báo Xoá tài khoản - Xin chào!\nHãy đăng nhập để bắt đầu. - Đăng nhập bằng Twitter - Đăng nhập bằng Mastodon - Đăng nhập bằng Mã khoá Twitter tuỳ chỉnh - Yêu cầu quyền truy cập API Twitter v2. - Xác thực - Dòng thời gian - Nhắc đến - Thông báo - Tất cả - Tin nhắn - Tìm kiếm người - Lượt thích - Dấu trang - Xu hướng - Xu hướng - Toàn cầu - Đang là xu hướng - %d người đang nói chuyện - Tweet - Toot - 1 câu trả lời - %d câu trả lời - 1 lời trích dẫn - %d lời trích dẫn - 1 Retweet - %d Retweet - 1 Lượt thích - %d Lượt thích - Tìm kiếm + Tài khoản + Hiện ít hơn Tìm kiếm tweet hoặc người dùng - Tìm kiếm đã lưu - Tweet Phương tiện + Tweet Người dùng Hashtag + Tìm kiếm Hiện thêm - Hiện ít hơn - Tôi - Ẩn câu trả lời - Quyền bị từ chối - Bạn đã bị chặn xem hồ sơ của người dùng này. - Tất cả tweet - Loại trừ câu trả lời - Đang theo dõi - Người theo dõi - Đã liệt kê - Soạn - Trả lời - Trích dẫn - Trả lời … - , - - Cái gì đang xảy ra? - Viết cảnh báo của bạn ở đây - Những người khác trong cuộc hội thoại này: - Lưu bản nháp? - Lưu bản nháp - Đang trả lời - Nhiều lựa chọn - 5 phút - 30 phút - 1 tiếng - 6 tiếng - 1 ngày - 3 ngày - 7 ngày - Công khai - Không được liệt kê - Riêng tư - Trực tiếp - Tìm kiếm người dùng - Tìm kiếm hashtag - Nháp - Xóa bản nháp - Sửa bản nháp - Danh sách - DANH SÁCH CỦA TÔI - ĐÃ ĐĂNG KÝ - Tầm nhìn riêng tư - Tạo danh sách + Tìm kiếm đã lưu + Không tìm thấy thành viên nào. + Người đăng kí + Liệt kê các thành viên + Thêm thành viên Chi tiết danh sách - 1 thành viên %d thành viên 1 người đăng kí + 1 thành viên %d người đăng kí - Thêm thành viên + Xóa danh sách này: %s Sửa danh sách Đổi tên danh sách Xoá danh sách Theo dõi Bỏ theo dõi - Liệt kê các thành viên - Người đăng kí - Xóa danh sách này: %s - Không tìm thấy thành viên nào. - Thêm thành viên - Danh sách mới - Sửa danh sách - Tên - Mô tả - Riêng tư - Tạo danh sách - Đổi tên danh sách - Thêm - Xoá - Thêm thành viên - Tìm kiếm người - Cài đặt - Cài đặt chung - Giới thiệu - Ngoại hình - Màu sắc đánh dấu + Thêm thành viên + Đang là xu hướng + %d người đang nói chuyện + Xu hướng + Xu hướng - Toàn cầu + Đăng nhập bằng Mastodon + Xin chào!\nHãy đăng nhập để bắt đầu. + Đăng nhập bằng Twitter + Đăng nhập bằng Mã khoá Twitter tuỳ chỉnh + Yêu cầu quyền truy cập API Twitter v2. + Xác thực + Lượt thích + Tài khoản + Thông báo Chọn màu - Vị trí tab - Chủ đề - Dòng thời gian cuộn + Chế độ tối ưu hoá cho AMOLED Trên cùng Dưới cùng + Màu sắc đánh dấu Tự động Sáng Tối - Chế độ tối ưu hoá cho AMOLED + Ngoại hình Ẩn thanh tab khi cuộn - Ẩn thanh ứng dụng khi cuộn Ẩn FAB khi cuộn - Hiển thị - Xem trước - Văn bản - Định dạng ngày - Phương tiện + Ẩn thanh ứng dụng khi cuộn + Vị trí tab + Chủ đề + Dòng thời gian cuộn Cảm ơn vì đã sử dụng @TwidereProject! + Xem trước URL + Tuyệt đối + Tương đối Sử dụng cỡ chữ hệ thống + Hình vuông bo tròn Phong cách ảnh đại diện Vòng tròn - Hình vuông bo tròn - Tương đối - Tuyệt đối + Luôn luôn Xem trước phương tiện - Tự động phát Tự động - Luôn luôn + Tự động phát Tắt - Xem trước URL - Thông báo - Tài khoản - Bộ nhớ + Hiển thị + Xem trước + Định dạng ngày + Văn bản + Phương tiện + Giấy phép + Twidere thế hệ mới cho Android 5.0+. \nVẫn đang trong giai đoạn đầu. + Logo nền của trang Giới thiệu + Cái bóng của logo nền của trang Giới thiệu + Giới thiệu + Phiên bản %s + Xoá tất cả bộ nhớ đệm của Twidere X. Thông tin tài khoản của bạn sẽ không bị mất. + Xóa tất cả bộ nhớ đệm Xóa lịch sử tìm kiếm - Xoá bộ nhớ đệm phương tiện Xoá bộ nhớ đệm phương tiện đã lưu trữ. - Xóa tất cả bộ nhớ đệm - Xoá tất cả bộ nhớ đệm của Twidere X. Thông tin tài khoản của bạn sẽ không bị mất. - Khác - Nhà cung cấp dữ liệu Twitter bên thứ ba - Bản sao Nitter + Xoá bộ nhớ đệm phương tiện + Bộ nhớ + Cài đặt + Cài đặt chung + Giới thiệu Frontend thay thế cho Twitter tập trung vào sự riêng tư. - Nhà cung cấp dữ liệu Twitter bên thứ ba - Vì giới hạn của API Twitter, một số dữ liệu có thể không được lấy từ Twitter, bạn có thể sử dụng một nhà cung cấp dữ liệu bên thứ ba để cung cấp dữ liệu đó. Twidere không chịu trách nhiệm cho chúng. + Bản sao Nitter Sử dụng nhà cung cấp dữ liệu bên thứ ba trong - - Chia chủ đề trạng thái Twitter URL dự án - Giới thiệu - Giấy phép - Twidere thế hệ mới cho Android 5.0+. \nVẫn đang trong giai đoạn đầu. - Phiên bản %s - Logo nền của trang Giới thiệu - Cái bóng của logo nền của trang Giới thiệu - Hình ảnh mạng - Quay lại - Thêm - Đóng - Xong - Logo Twidere X - Logo Twitter - Logo Mastodon - Logo Github - Logo Telegram - Phát video - Vị trí - Đã retweet - Phương tiện - Trả lời - Retweet - Thích - Soạn - Menu - Mục tài khoản kiểu thả xuống - Lịch sử - Lưu - Tải - Trang web - Vị trí - Trạng thái - Phương tiện - Yêu thích - Gửi - Bật vị trí - Tắt vị trí - Thêm nhắc đến - Thêm hình ảnh - Mở bản nháp - Chế độ chủ đề - Thêm - Cỡ chữ + - Chia chủ đề trạng thái Twitter + Nhà cung cấp dữ liệu Twitter bên thứ ba + Vì giới hạn của API Twitter, một số dữ liệu có thể không được lấy từ Twitter, bạn có thể sử dụng một nhà cung cấp dữ liệu bên thứ ba để cung cấp dữ liệu đó. Twidere không chịu trách nhiệm cho chúng. + Nhà cung cấp dữ liệu Twitter bên thứ ba + Khác + Tất cả tweet + Loại trừ câu trả lời + Ẩn câu trả lời + Tôi + Quyền bị từ chối + Bạn đã bị chặn xem hồ sơ của người dùng này. + Quản lý tài khoản + Đăng nhập + Nháp + Xóa bản nháp + Sửa bản nháp + Tìm kiếm người dùng + Dấu trang + Người theo dõi + Đã liệt kê + Lưu bản nháp + Lưu bản nháp? + Trả lời … + Riêng tư + Công khai + Không được liệt kê + Trực tiếp + , + Viết cảnh báo của bạn ở đây + + Cái gì đang xảy ra? + Trích dẫn + Soạn + Trả lời + Những người khác trong cuộc hội thoại này: + Nhiều lựa chọn + 1 ngày + 30 phút + 7 ngày + 1 tiếng + 3 ngày + 5 phút + 6 tiếng + Đang trả lời + ĐÃ ĐĂNG KÝ + DANH SÁCH CỦA TÔI + Danh sách + Tầm nhìn riêng tư + Tạo danh sách + Nhắc đến + Đang theo dõi + Dòng thời gian + Tìm kiếm người + Tin nhắn + Tìm kiếm hashtag + 1 lời trích dẫn + %d lời trích dẫn + 1 Retweet + %d Retweet + 1 Lượt thích + %d Lượt thích + Toot + Tweet + 1 câu trả lời + %d câu trả lời \ No newline at end of file diff --git a/app/src/main/res-localized/values-zh-rCN/strings.xml b/app/src/main/res-localized/values-zh-rCN/strings.xml index 944f12ebb..52b6a33b4 100644 --- a/app/src/main/res-localized/values-zh-rCN/strings.xml +++ b/app/src/main/res-localized/values-zh-rCN/strings.xml @@ -1,130 +1,133 @@ - 帐户临时锁定 - 打开 Twitter 以解锁 - 超出用量限制 - 已超过 Twitter API 用量限制 - 权限已被拒绝 - 对不起,您没有被授权 - 权限已被拒绝 - 您已被阻止关注此帐户 - 账号已被冻结 - Twitter 会冻结违反 %s 的账号 - Twitter 规则 - 未找到推文 + %s 已被解除静音 是否取消关注用户 %s? - 取消关注 %s 的请求? + 下载完成后,媒体将被共享 + 取消关注失败 + 请重试 推文已发送 - 正在发送推文 - 推文发送失败 - 请重试 - 推文已删除 - 删除推文失败 - 请重试 - 正在保存媒体 - 媒体已保存 + 登录失败 + 服务器地址不正确 + 请求次数过多 + 关注成功 + 关注失败 + 请重试 + %s 已被举报为垃圾信息用户 + Twitter 规则 + 账号已被冻结 + Twitter 会冻结违反 %s 的账号 保存媒体失败 请重试 - 下载完成后,媒体将被共享 - 照片已保存 + %s 已被屏蔽 + 取消关注 %s 的请求? + 举报 %s 为垃圾信息用户失败 + 请重试 + %s 已被举报为垃圾信息用户并被屏蔽 保存照片失败 请重试 + 未找到推文 + 正在保存媒体 + 登录失败 + 连接超时 + %s 已被解除屏蔽 + %s 已被静音 + 权限已被拒绝 + 对不起,您没有被授权 + 发送消息 + 消息发送失败 + 解除静音 %s 失败 + 请重试 + 超出用量限制 + 已超过 Twitter API 用量限制 加载失败 请重试 - 关注失败 - 请重试 - 取消关注失败 - 请重试 - 关注成功 - 取消关注成功 + 权限已被拒绝 + 您已被阻止关注此帐户 + 媒体已保存 + 推文已删除 已发送关注请求 - 请求次数过多 - %s 已被静音 静音 %s 失败 请重试 - %s 已被解除静音 - 解除静音 %s 失败 - 请重试 - %s 已被屏蔽 + 正在发送推文 + 删除推文失败 + 请重试 + 照片已保存 + 帐户临时锁定 + 打开 Twitter 以解锁 屏蔽 %s 失败 请重试 - %s 已被解除屏蔽 - 解除屏蔽 %s 失败 - 请重试 - %s 已被举报为垃圾信息用户 - 举报 %s 为垃圾信息用户失败 - 请重试 - %s 已被举报为垃圾信息用户并被屏蔽 + 推文发送失败 + 请重试 + 取消关注成功 屏蔽并举报 %s 为垃圾信息用户失败 请重试 - 登录失败 - 服务器地址不正确 - 登录失败 - 连接超时 - 发送消息 - 消息发送失败 - 您的投票已经结束 - 一个你参加过的投票已经结束 + 解除屏蔽 %s 失败 + 请重试 + 私信 + 私信 + 互动 + 互动,例如提及和转发等信息 + 后台操作 %s 转嘟了你的嘟文 - %s 刚刚发布 %s 向你发送了关注请求 - %s 收藏了你的嘟文 - %s 关注了你 + 您的投票已经结束 %s 提及了你 新私信 %s 给你发送了一条消息 + 一个你参加过的投票已经结束 + %s 收藏了你的嘟文 + %s 关注了你 + %s 刚刚发布 + 屏蔽 %s + 关注者 + 关注者 + %s 尚未关注你 + 关注了你 + 静音 %s + %s 正在关注你 + 解除屏蔽 + 举报并屏蔽 + 已关注 + 等待中 + 举报 + 静音 + 解除静音 + 屏蔽 + 关注 + 取消关注 + 关注者 + 列表中 + 正在关注 + 加载更多 + 照片库 添加 - 删除 + 取消 + 预览 + 共享媒体 编辑 + 在 Safari 中打开 + 确定 保存 - 好的 + 删除 确认 - 确定 - 取消 - 拍摄照片 保存照片 + 拍摄照片 + 好的 登录 - 预览 - 在 Safari 中打开 - 共享媒体 - 加载更多 - 复制文本 - 引用 - 转推 - 复制链接 - 分享链接 - 删除推文 - 投票 - 媒体 %s 转推了 - 已关闭 + 媒体 %s 人 %s 票 - %s 人 + 已关闭 %s 票 + %s 人 显示此主题帖 - 关注 - 取消关注 - 已关注 - 等待中 - 静音 - 解除静音 - 屏蔽 - 解除屏蔽 - 举报 - 举报并屏蔽 - 关注者 - 关注者 - 关注了你 - %s 尚未关注你 - %s 正在关注你 - 静音 %s - 屏蔽 %s - 正在关注 - 关注者 - 列表中 - 照片库 - %s 回复 - %s 回复 + 分享链接 + 删除推文 + 引用 + 转推 + 复制文本 + 投票 + 复制链接 %s 引用 %s 引用 %s 转推 @@ -133,229 +136,244 @@ %s 喜欢 %s 名成员 %s 名成员 - %s 列表 - %s 列表 - %s 推文 - %s 推文 %s 图片 %s 图片 - 后台操作 - 互动 - 互动,例如提及和转发等信息 - 私信 - 私信 - 登录 - 管理帐号 - 帐号 - 删除帐号 - 你好!\n登录后即可开始使用。 - 使用 Twitter 登录 - 使用 Mastodon 登录 - 使用自定义 Twitter 口令登录 - 需要开启 Twitter API v2 访问权限。 - 身份认证 - 时间线 - 提及 - 通知 - 全部 - 私信 - [照片] - 复制消息文本 - 删除您的消息 - 查找人 - 搜索用户 - 发送消息失败 - 喜欢 - 书签 - 趋势 - 趋势 - 全球范围 - 现在的趋势 - %d 人正讨论 - 推文 - 嘟文 - 1 条回复 - %d 条回复 - 1 引用 - %d 引用 - 1 转推 - %d 转推 - 1 喜欢 - %d 喜欢 - 搜索 - 搜索推文或用户 - 已保存的搜索 - 推文 - 媒体 - 用户 - 标签 - 显示更多 - 显示更少 - 我的 - 隐藏回复 - 权限已被拒绝 - 您已被阻止查看此用户的个人资料。 - 所有推文 - 不包括回复 - 正在关注 - 关注者 - 列表中 - 撰写 - 回复 - 引用 - 回复给 … - - - 发生了什么? - 折叠部分的警告消息 - 此对话中的其他人: - 要保存草稿吗? - 保存草稿 - 回复给 - 多选 - 5 分钟 - 30 分钟 - 1 小时 - 6 小时 - 1 天 - 3 天 - 7 天 - 公开 - 不公开 - 仅关注者 - 私信 - 搜索用户 - 搜索标签 - 草稿 - 删除草稿 - 编辑草稿 - 列表 - 我的列表 - 已订阅 - 私有可见性 - 创建列表 + %s 推文 + %s 推文 + %s 回复 + %s 回复 + %s 列表 + %s 列表 + 网络图像 + 更多 + 返回 + Twidere X 徽标 + Twitter 徽标 + Github 徽标 + Mastodon 徽标 + Telegram 群组 + 播放视频 + 关闭 + 完成 + 地理位置 + 媒体 + 转推 + 喜欢 + 回复 + 转推了 + 字体大小 + 保存 + 历史 + 添加 + 添加图片 + 添加提及 + 打开草稿 + 启用位置信息 + 禁用位置信息 + 会话模式 + 发送 + 加载 + 站点 + 媒体 + 收藏 + 推文 + 地理位置 + 撰写 + 账户下拉列表 + 菜单 + 重命名列表 + 创建列表 + 仅关注者 + 编辑列表 + 名称 + 新建列表 + 描述 + 本站 + 搜索用户 + 添加成员 + 添加 + 删除 + 全部 + 通知 + 删除帐号 + 帐号 + 显示更少 + 搜索推文或用户 + 媒体 + 推文 + 用户 + 标签 + 搜索 + 显示更多 + 已保存的搜索 + 跨站 + 没有找到成员。 + 订阅者 + 列表成员 + 删除此列表 + 添加成员 列表详情 - 1 名成员 %d 名成员 1 名订阅者 + 1 名成员 %d 名订阅者 - 添加成员 + 删除此列表: %s 编辑列表 重命名列表 删除列表 关注 取消关注 - 列表成员 - 订阅者 - 删除此列表: %s - 没有找到成员。 - 添加成员 - 删除此列表 - 新建列表 - 编辑列表 - 名称 - 描述 - 仅关注者 - 创建列表 - 重命名列表 - 添加 - 删除 - 添加成员 - 搜索用户 - 设置 - 通用 - 关于 - 外观 - 高亮颜色 + 添加成员 + 现在的趋势 + %d 人正讨论 + 趋势 + 趋势 - 全球范围 + 使用 Mastodon 登录 + 你好!\n登录后即可开始使用。 + 使用 Twitter 登录 + 使用自定义 Twitter 口令登录 + 需要开启 Twitter API v2 访问权限。 + 身份认证 + 喜欢 + 布局 + 标签栏操作 + 抽屉操作 + 自定义布局 + 选择并安排最多5个将出现在标签栏上的动作(地方和联邦时间将只显示在Mastodon) + 显示通知 + 帐号 + 通知 选择颜色 - 标签位置 - 主题 - 滚动时间线 + AMOLED 优化模式 顶端 底端 + 高亮颜色 自动 亮色 深色 - AMOLED 优化模式 + 外观 滚动时隐藏标签栏 - 滚动时隐藏应用栏 滚动时隐藏悬浮操作按钮 - 显示 - 预览 - 文本 - 时间格式 - 媒体 + 滚动时隐藏应用栏 + 标签位置 + 主题 + 滚动时间线 感谢您使用 @TwidereProject ! + 网址预览 + 绝对的 + 相对的 使用系统字体大小 + 圆角矩形 头像样式 圆形 - 圆角矩形 - 相对的 - 绝对的 + 总是 媒体预览 - 自动播放 自动 - 总是 + 自动播放 关闭 - 网址预览 - 通知 - 显示通知 - 帐号 - 存储空间 + 显示 + 预览 + 时间格式 + 文本 + 媒体 + 开源协议 + 适用于 Android 5.0+ 的下一代Twidere。 \n仍处于早期阶段。 + 关于页面背景徽标 + 关于页面背景徽标阴影 + 关于 + 版本 %s + 删除所有 Twidere X 缓存。您的帐户凭据不会丢失。 + 清除所有缓存 清除搜索记录 - 清除媒体缓存 清除存储的媒体缓存。 - 清除所有缓存 - 删除所有 Twidere X 缓存。您的帐户凭据不会丢失。 - 其它 - 第三方 Twitter 数据提供方 - Nitter 实例 + 清除媒体缓存 + 存储空间 + 设置 + 通用 + 关于 + 服务器 + 密码 + 端口 + 代理服务器端口必须是数字 + 为所有网络请求使用代理 + 代理 + 代理设置 + HTTP + 代理类型 + 反向代理 + 用户名 着力于隐私的 Twitter 前端替换选择 - 第三方 Twitter 数据提供商 - 由于 Twitter API 的限制,某些数据可能无法从 Twitter 获取。 您可以使用第三方数据提供商提供这些数据。 Twidere 不对此负责。 + Nitter 实例 使用第三方数据提供商用于 - - Twitter 会话 项目地址 - 关于 - 开源协议 - 适用于 Android 5.0+ 的下一代Twidere。 \n仍处于早期阶段。 - 版本 %s - 关于页面背景徽标 - 关于页面背景徽标阴影 - 网络图像 - 返回 - 更多 - 关闭 - 完成 - Twidere X 徽标 - Twitter 徽标 - Mastodon 徽标 - Github 徽标 - Telegram 群组 - 播放视频 - 地理位置 - 转推了 - 媒体 - 回复 - 转推 - 喜欢 - 撰写 - 菜单 - 账户下拉列表 - 历史 - 保存 - 加载 - 站点 - 地理位置 - 推文 - 媒体 - 收藏 - 发送 - 启用位置信息 - 禁用位置信息 - 添加提及 - 添加图片 - 打开草稿 - 会话模式 - 添加 - 字体大小 + - Twitter 会话 + 第三方 Twitter 数据提供商 + 由于 Twitter API 的限制,某些数据可能无法从 Twitter 获取。 您可以使用第三方数据提供商提供这些数据。 Twidere 不对此负责。 + 第三方 Twitter 数据提供方 + 其它 + 所有推文 + 不包括回复 + 隐藏回复 + 我的 + 权限已被拒绝 + 您已被阻止查看此用户的个人资料。 + 管理帐号 + 登录 + 草稿 + 删除草稿 + 编辑草稿 + 搜索用户 + 书签 + 关注者 + 列表中 + 保存草稿 + 要保存草稿吗? + 回复给 … + 仅关注者 + 公开 + 不公开 + 私信 + + 折叠部分的警告消息 + + 发生了什么? + 引用 + 撰写 + 回复 + 此对话中的其他人: + 多选 + 1 天 + 30 分钟 + 7 天 + 1 小时 + 3 天 + 5 分钟 + 6 小时 + 回复给 + 已订阅 + 我的列表 + 列表 + 私有可见性 + 创建列表 + 提及 + 正在关注 + 时间线 + [照片] + 搜索用户 + 查找人 + 发送消息失败 + 复制消息文本 + 删除您的消息 + 私信 + 搜索标签 + 1 引用 + %d 引用 + 1 转推 + %d 转推 + 1 喜欢 + %d 喜欢 + 嘟文 + 推文 + 1 条回复 + %d 条回复 \ No newline at end of file diff --git a/app/src/main/res-localized/values-zh-rTW/strings.xml b/app/src/main/res-localized/values-zh-rTW/strings.xml index a3445efb3..e7f289cb7 100644 --- a/app/src/main/res-localized/values-zh-rTW/strings.xml +++ b/app/src/main/res-localized/values-zh-rTW/strings.xml @@ -1,232 +1,232 @@ - 帳號暫時被鎖 - 開啟推特以解鎖 - 超過限制 - 達到推特API使用量限制 - 權限不足 - 抱歉,您並未被授權 - 權限不足 - 您已經被禁止請求跟隨該用戶 - 帳號已被停用 - 推特停用違反 %s 的帳號 - Twitter 規則 - 無推文 + %s 已解除靜音 取消追隨使用者 %s ? - 取消 %s 的追隨請求? + 取消跟隨失敗 + 請再試一次 您的推文已發送 - 推文失敗 - 請再試一次 - 推文已刪除 - 刪除推文失敗 - 請再試一次 + 過多請求次數 + 跟隨成功 + 跟隨失敗 + 請再試一次 + %s 已被回報為垃圾訊息 + Twitter 規則 + 帳號已被停用 + 推特停用違反 %s 的帳號 請再試一次 - 照片已儲存 + %s已經被封鎖 + 取消 %s 的追隨請求? + 檢舉 %s 失敗 + 請再試一次 + %s 已被檢舉為垃圾訊息並封鎖 儲存照片失敗 請再試一次 + 無推文 + %s 已經解鎖。 + %s 已靜音 + 權限不足 + 抱歉,您並未被授權 + %s 解除靜音失敗 + 請再試一次 + 超過限制 + 達到推特API使用量限制 載入失敗 請再試一次 - 跟隨失敗 - 請再試一次 - 取消跟隨失敗 - 請再試一次 - 跟隨成功 - 取消跟隨成功 + 權限不足 + 您已經被禁止請求跟隨該用戶 + 推文已刪除 跟隨請求已發送 - 過多請求次數 - %s 已靜音 %s 靜音失敗 請再試一次 - %s 已解除靜音 - %s 解除靜音失敗 - 請再試一次 - %s已經被封鎖 + 刪除推文失敗 + 請再試一次 + 照片已儲存 + 帳號暫時被鎖 + 開啟推特以解鎖 封鎖%s失敗 請再試一次 - %s 已經解鎖。 - %s 解鎖失敗 - 請再試一次 - %s 已被回報為垃圾訊息 - 檢舉 %s 失敗 - 請再試一次 - %s 已被檢舉為垃圾訊息並封鎖 + 推文失敗 + 請再試一次 + 取消跟隨成功 %s 檢舉及封鎖失敗 請再試一次 + %s 解鎖失敗 + 請再試一次 + 訊息 + 封鎖 %s + 跟隨者 + 跟隨者 + %s 未跟隨你 + 跟隨 + 靜音 %s + %s 跟隨你 + 解除封鎖 + 檢舉和封鎖 + 跟隨 + 待審核 + 檢舉 + 靜音 + 取消靜音 + 封鎖 + 跟隨 + 取消跟隨 + 跟隨者 + 列表 + 跟隨 + 載入更多 + 照片庫 新增 - 移除 + 取消 + 預覽 編輯 + 在 Safari 中打開 儲存 - 確定 + 移除 確認 - 取消 - 拍照 儲存照片 + 拍照 + 確定 登入 - 預覽 - 在 Safari 中打開 - 載入更多 - 複製文字 + %s 已轉推 + 媒體 + 分享連結 + 刪除推文 引用 轉推 + 複製文字 複製連結 - 分享連結 - 刪除推文 - 媒體 - %s 已轉推 - 跟隨 - 取消跟隨 - 跟隨 - 待審核 - 靜音 - 取消靜音 - 封鎖 - 解除封鎖 - 檢舉 - 檢舉和封鎖 - 跟隨者 - 跟隨者 - 跟隨 - %s 未跟隨你 - %s 跟隨你 - 靜音 %s - 封鎖 %s - 跟隨 - 跟隨者 - 列表 - 照片庫 - 訊息 - 登入 - 管理帳號 - 帳號 + 網路圖片 + 更多 + 返回 + Twidere X logo + Twitter Logo + Github Logo + Mastodon Logo + 播放影片 + 關閉 + 完成 + 位置 + 媒體 + 轉推 + 喜歡 + 回覆 + 已轉推 + 字體大小 + 儲存 + 歷史紀錄 + 新增 + 新增圖片 + 開啟草稿 + 啟用定位功能 + 啟用定位功能 + 傳送 + 載入 + 網站 + 媒體 + 最愛 + 狀態 + 位置 + 撰寫 + 帳號下拉列表 + 選單 + 編輯列表 + 新增成員 + 新增 + 移除 刪除帳號 - 哈囉!\n登入並開始使用。 - 登入Twitter - 登入Mastodon - 以客製的Twitter金鑰登入 - 需要存取 Twitter API v2 - 驗證 - 時間軸 - 提及 - 訊息 - 喜歡 - 書籤 - 趨勢 - Tweet - 1 回覆 - %d 則回覆 - 1 引用 - %d 則引文 - 1 轉推 - %d 轉推 - 1 喜歡 - %d 個喜歡 - 搜尋 + 帳號 搜尋推文或是使用者 - 儲存搜尋 - 推文 媒體 + 推文 使用者 - - 隱藏回覆 - 權限不足 - 您已被禁止觀看此使用者的個人資料 - 跟隨 - 跟隨者 - 列表 - 撰寫 - 回覆 - 引用 - 回覆至 … - - - 發生了什麼事? - 在這對話中的其他成員: - 儲存草稿? - 儲存草稿 - 正在回覆至 - 草稿 - 刪除草稿 - 編輯草稿 - 列表 + 搜尋 + 儲存搜尋 + 無會員 + 增加成員 列出細節 - 1 位成員 %d 個成員 1訂閱者 + 1 位成員 %d 位訂閱者 - 新增成員 編輯列表 跟隨 取消跟隨 - 無會員 - 增加成員 - 編輯列表 - 新增 - 移除 - 新增成員 - 設定 - 一般 - 關於 - 外觀設定 - 高亮度顏色 + 新增成員 + 趨勢 + 登入Mastodon + 哈囉!\n登入並開始使用。 + 登入Twitter + 以客製的Twitter金鑰登入 + 需要存取 Twitter API v2 + 驗證 + 喜歡 + 帳號 選擇顏色 - 分頁位置 - 佈景主題 上方 下方 + 高亮度顏色 自動 淺色 深色 - 顯示 - 預覽 - 文字 - 日期格式 - 媒體 + 外觀設定 + 分頁位置 + 佈景主題 感謝使用 @TwidereProject! + 絕對 + 相對 使用系統字體大小 + 圓角矩形 大頭貼樣式 圓形 - 圓角矩形 - 相對 - 絕對 + 一直顯示 媒體預覽 - 自動播放 自動 - 一直顯示 + 自動播放 關閉 - 帳號 - 關於 + 顯示 + 預覽 + 日期格式 + 文字 + 媒體 授權協議 - 網路圖片 - 返回 - 更多 - 關閉 - 完成 - Twidere X logo - Twitter Logo - Mastodon Logo - Github Logo - 播放影片 - 位置 - 已轉推 - 媒體 - 回覆 - 轉推 - 喜歡 - 撰寫 - 選單 - 帳號下拉列表 - 歷史紀錄 - 儲存 - 載入 - 網站 - 位置 - 狀態 - 媒體 - 最愛 - 傳送 - 啟用定位功能 - 啟用定位功能 - 新增圖片 - 開啟草稿 - 新增 - 字體大小 + 關於 + 設定 + 一般 + 關於 + 隱藏回覆 + + 權限不足 + 您已被禁止觀看此使用者的個人資料 + 管理帳號 + 登入 + 草稿 + 刪除草稿 + 編輯草稿 + 書籤 + 跟隨者 + 列表 + 儲存草稿 + 儲存草稿? + 回覆至 … + + + 發生了什麼事? + 引用 + 撰寫 + 回覆 + 在這對話中的其他成員: + 正在回覆至 + 列表 + 提及 + 跟隨 + 時間軸 + 訊息 + 1 引用 + %d 則引文 + 1 轉推 + %d 轉推 + 1 喜歡 + %d 個喜歡 + Tweet + 1 回覆 + %d 則回覆 \ No newline at end of file From a431be246534b71accbcbb74dd89d4ae44f18b82 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Thu, 29 Jul 2021 18:01:12 +0800 Subject: [PATCH 103/137] version 1.5.0-beta01 --- buildSrc/src/main/kotlin/Package.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/buildSrc/src/main/kotlin/Package.kt b/buildSrc/src/main/kotlin/Package.kt index c9c859a22..f3951f1e2 100644 --- a/buildSrc/src/main/kotlin/Package.kt +++ b/buildSrc/src/main/kotlin/Package.kt @@ -2,6 +2,6 @@ object Package { const val group = "com.twidere" const val name = "TwidereX" const val id = "$group.twiderex" - const val versionName = "1.4.0-beta02" - const val versionCode = 48 + const val versionName = "1.5.0-beta01" + const val versionCode = 50 } From 6cbc5a3c3a9d158de97d066d272bd28deb2a3d9f Mon Sep 17 00:00:00 2001 From: Tlaster Date: Fri, 30 Jul 2021 09:57:46 +0800 Subject: [PATCH 104/137] fix leakcanary --- buildSrc/src/main/kotlin/Dependencies.kt | 2 +- buildSrc/src/main/kotlin/DependencyHandlerExtensions.kt | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index 0b1ec66ac..a9334dac5 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -115,7 +115,7 @@ fun DependencyHandlerScope.android() { implementation("com.google.android.exoplayer:exoplayer", Versions.exoplayer) implementation("com.google.android.exoplayer:extension-okhttp", Versions.exoplayer) implementation("androidx.browser:browser", Versions.browser) - implementation("com.squareup.leakcanary:leakcanary-android:2.7") + debugImplementation("com.squareup.leakcanary:leakcanary-android:2.7") } fun DependencyHandlerScope.accompanist() { diff --git a/buildSrc/src/main/kotlin/DependencyHandlerExtensions.kt b/buildSrc/src/main/kotlin/DependencyHandlerExtensions.kt index 4004b66b9..90fe9dad1 100644 --- a/buildSrc/src/main/kotlin/DependencyHandlerExtensions.kt +++ b/buildSrc/src/main/kotlin/DependencyHandlerExtensions.kt @@ -30,6 +30,11 @@ internal fun DependencyHandler.implementation( version: String? = null, ) = add("implementation", name, version) +internal fun DependencyHandler.debugImplementation( + name: String, + version: String? = null, +) = add("debugImplementation", name, version) + internal fun DependencyHandler.kapt( name: String, version: String? = null, From f865e2980ece6fcdac3f8df11e63ae0c68362101 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Fri, 30 Jul 2021 10:47:26 +0800 Subject: [PATCH 105/137] version 1.5.0-beta02 --- buildSrc/src/main/kotlin/Package.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/buildSrc/src/main/kotlin/Package.kt b/buildSrc/src/main/kotlin/Package.kt index f3951f1e2..7ae5302a9 100644 --- a/buildSrc/src/main/kotlin/Package.kt +++ b/buildSrc/src/main/kotlin/Package.kt @@ -2,6 +2,6 @@ object Package { const val group = "com.twidere" const val name = "TwidereX" const val id = "$group.twiderex" - const val versionName = "1.5.0-beta01" - const val versionCode = 50 + const val versionName = "1.5.0-beta02" + const val versionCode = 51 } From faa84c8ba8755540be77986316f5d26aaf2b79a9 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Fri, 30 Jul 2021 13:46:57 +0800 Subject: [PATCH 106/137] fix json decode exception handling --- .../http/config/HttpConfigClientFactory.kt | 24 ++++++++++++------- .../java/com/twidere/services/utils/Json.kt | 4 +++- .../java/com/twidere/services/JsonTest.kt | 13 ++++++++++ 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/services/src/main/java/com/twidere/services/http/config/HttpConfigClientFactory.kt b/services/src/main/java/com/twidere/services/http/config/HttpConfigClientFactory.kt index fc2e64686..66a65d3a4 100644 --- a/services/src/main/java/com/twidere/services/http/config/HttpConfigClientFactory.kt +++ b/services/src/main/java/com/twidere/services/http/config/HttpConfigClientFactory.kt @@ -36,8 +36,8 @@ import com.twidere.services.twitter.model.exceptions.TwitterApiException import com.twidere.services.twitter.model.exceptions.TwitterApiExceptionV2 import com.twidere.services.utils.DEBUG import com.twidere.services.utils.JSON -import com.twidere.services.utils.decodeJson import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.decodeFromString import okhttp3.Credentials import okhttp3.Interceptor import okhttp3.MediaType.Companion.toMediaType @@ -70,15 +70,17 @@ class HttpConfigClientFactory(private val configProvider: HttpConfigProvider) : response.body?.string()?.takeIf { it.isNotEmpty() }?.let { content -> - content.decodeJson().takeIf { - it.microBlogErrorMessage != null - }.let { - it ?: run { - content.decodeJson() - } + runCatching { + JSON.decodeFromString(content) + }.getOrNull()?.takeIf { + !it.microBlogErrorMessage.isNullOrEmpty() }.let { + it ?: runCatching { + JSON.decodeFromString(content) + }.getOrNull() + }?.let { throw it - } + } ?: throw MicroBlogHttpException(response.code) } ?: throw MicroBlogHttpException(response.code) } else { response @@ -90,7 +92,11 @@ class HttpConfigClientFactory(private val configProvider: HttpConfigProvider) : response.body?.string()?.takeIf { it.isNotEmpty() }?.let { content -> - throw content.decodeJson() + runCatching { + JSON.decodeFromString(content) + }.getOrNull()?.let { + throw it + } ?: throw MicroBlogHttpException(response.code) } ?: throw MicroBlogHttpException(response.code) } else { response diff --git a/services/src/main/java/com/twidere/services/utils/Json.kt b/services/src/main/java/com/twidere/services/utils/Json.kt index f15f60ca0..47b5b6144 100644 --- a/services/src/main/java/com/twidere/services/utils/Json.kt +++ b/services/src/main/java/com/twidere/services/utils/Json.kt @@ -42,6 +42,8 @@ internal inline fun String.decodeJson(): T { return runCatching { JSON.parseToJsonElement(this) }.getOrNull()?.let { - JSON.decodeFromJsonElement(it) + runCatching { + JSON.decodeFromJsonElement(it) + }.getOrNull() } ?: throw MicroBlogJsonException(this) } diff --git a/services/src/test/java/com/twidere/services/JsonTest.kt b/services/src/test/java/com/twidere/services/JsonTest.kt index 8562dbc9b..23dffc3e6 100644 --- a/services/src/test/java/com/twidere/services/JsonTest.kt +++ b/services/src/test/java/com/twidere/services/JsonTest.kt @@ -22,6 +22,7 @@ package com.twidere.services import com.twidere.services.http.MicroBlogException import com.twidere.services.utils.decodeJson +import kotlinx.serialization.Serializable import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows @@ -39,4 +40,16 @@ class JsonTest { "".decodeJson() } } + + @Test + fun decodeMismatchType() { + assertThrows { + "{\"a\": { }}".decodeJson() + } + } } + +@Serializable +data class DataClass( + val a: List, +) From 33ec39b6fdc9010259971327575b6643cb3d0b40 Mon Sep 17 00:00:00 2001 From: itsMimao Date: Fri, 30 Jul 2021 15:12:00 +0800 Subject: [PATCH 107/137] fixed crashes caused by missing splits --- app/src/main/kotlin/com/twidere/twiderex/TwidereApp.kt | 6 ++++++ buildSrc/src/main/kotlin/Dependencies.kt | 1 + buildSrc/src/main/kotlin/Versions.kt | 1 + 3 files changed, 8 insertions(+) diff --git a/app/src/main/kotlin/com/twidere/twiderex/TwidereApp.kt b/app/src/main/kotlin/com/twidere/twiderex/TwidereApp.kt index d884d5808..27cd899d1 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/TwidereApp.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/TwidereApp.kt @@ -24,6 +24,7 @@ import android.app.Application import androidx.hilt.work.HiltWorkerFactory import androidx.startup.AppInitializer import androidx.work.Configuration +import com.google.android.play.core.missingsplits.MissingSplitsManagerFactory import com.twidere.twiderex.http.TwidereServiceInitializer import com.twidere.twiderex.notification.NotificationInitializer import com.twidere.twiderex.worker.dm.DirectMessageInitializer @@ -42,6 +43,11 @@ class TwidereApp : Application(), Configuration.Provider { override fun onCreate() { super.onCreate() + // Note:Installs with missing splits are now blocked on devices which have Play Protect active or run on Android 10. + // But there are still some custom roms allows missing splits which causes resources not found exception + if (MissingSplitsManagerFactory.create(this).disableAppIfMissingRequiredSplits()) { + return + } // manually setup NotificationInitializer since it require HiltWorkerFactory AppInitializer.getInstance(this) .apply { diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index a9334dac5..22ba7e973 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -115,6 +115,7 @@ fun DependencyHandlerScope.android() { implementation("com.google.android.exoplayer:exoplayer", Versions.exoplayer) implementation("com.google.android.exoplayer:extension-okhttp", Versions.exoplayer) implementation("androidx.browser:browser", Versions.browser) + implementation("com.google.android.play:core-ktx", Versions.google_play) debugImplementation("com.squareup.leakcanary:leakcanary-android:2.7") } diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 2672e3895..d17a994b5 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -41,6 +41,7 @@ object Versions { const val androidx_exifinterface = "1.3.2" const val exoplayer = "2.14.2" const val browser = "1.3.0" + const val google_play = "1.8.1" const val protobuf = "3.17.3" const val androidx_test = "1.4.0" const val extJUnitVersion = "1.1.3-rc01" From c1bfcc5821dbb416dcf1482ba4b9ef6a60d4c566 Mon Sep 17 00:00:00 2001 From: itsMimao Date: Fri, 30 Jul 2021 15:43:19 +0800 Subject: [PATCH 108/137] implement missing splits check only for channel google --- app/build.gradle.kts | 1 + .../MissingSplitsCheckerImpl.kt | 29 +++++++++++++++++++ .../MissingSplitsCheckerImpl.kt | 29 +++++++++++++++++++ .../kotlin/com/twidere/twiderex/TwidereApp.kt | 8 +++-- buildSrc/src/main/kotlin/Dependencies.kt | 1 - buildSrc/src/main/kotlin/Versions.kt | 1 - 6 files changed, 65 insertions(+), 4 deletions(-) create mode 100644 app/src/fdroid/kotlin/com.twidere.twiderex/MissingSplitsCheckerImpl.kt create mode 100644 app/src/google/kotlin/com.twidere.twiderex/MissingSplitsCheckerImpl.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 34986909f..1293ebfa7 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -200,6 +200,7 @@ dependencies { googleImplementation("com.google.firebase:firebase-analytics-ktx") googleImplementation("com.google.firebase:firebase-crashlytics-ktx") // END Non-FOSS component + googleImplementation("com.google.android.play:core-ktx:1.8.1") } junit4() diff --git a/app/src/fdroid/kotlin/com.twidere.twiderex/MissingSplitsCheckerImpl.kt b/app/src/fdroid/kotlin/com.twidere.twiderex/MissingSplitsCheckerImpl.kt new file mode 100644 index 000000000..e3d5cb336 --- /dev/null +++ b/app/src/fdroid/kotlin/com.twidere.twiderex/MissingSplitsCheckerImpl.kt @@ -0,0 +1,29 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex + +import android.content.Context + +class MissingSplitsCheckerImpl : TwidereApp.MissingSplitsChecker { + override fun requiredSplits(context: Context): Boolean { + return false + } +} diff --git a/app/src/google/kotlin/com.twidere.twiderex/MissingSplitsCheckerImpl.kt b/app/src/google/kotlin/com.twidere.twiderex/MissingSplitsCheckerImpl.kt new file mode 100644 index 000000000..72dc225a6 --- /dev/null +++ b/app/src/google/kotlin/com.twidere.twiderex/MissingSplitsCheckerImpl.kt @@ -0,0 +1,29 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex +import android.content.Context +import com.google.android.play.core.missingsplits.MissingSplitsManagerFactory + +class MissingSplitsCheckerImpl : TwidereApp.MissingSplitsChecker { + override fun requiredSplits(context: Context): Boolean { + return MissingSplitsManagerFactory.create(context).disableAppIfMissingRequiredSplits() + } +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/TwidereApp.kt b/app/src/main/kotlin/com/twidere/twiderex/TwidereApp.kt index 27cd899d1..9fefbffda 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/TwidereApp.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/TwidereApp.kt @@ -21,10 +21,10 @@ package com.twidere.twiderex import android.app.Application +import android.content.Context import androidx.hilt.work.HiltWorkerFactory import androidx.startup.AppInitializer import androidx.work.Configuration -import com.google.android.play.core.missingsplits.MissingSplitsManagerFactory import com.twidere.twiderex.http.TwidereServiceInitializer import com.twidere.twiderex.notification.NotificationInitializer import com.twidere.twiderex.worker.dm.DirectMessageInitializer @@ -45,7 +45,7 @@ class TwidereApp : Application(), Configuration.Provider { super.onCreate() // Note:Installs with missing splits are now blocked on devices which have Play Protect active or run on Android 10. // But there are still some custom roms allows missing splits which causes resources not found exception - if (MissingSplitsManagerFactory.create(this).disableAppIfMissingRequiredSplits()) { + if (MissingSplitsCheckerImpl().requiredSplits(this)) { return } // manually setup NotificationInitializer since it require HiltWorkerFactory @@ -56,4 +56,8 @@ class TwidereApp : Application(), Configuration.Provider { initializeComponent(TwidereServiceInitializer::class.java) } } + + interface MissingSplitsChecker { + fun requiredSplits(context: Context): Boolean + } } diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index 22ba7e973..a9334dac5 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -115,7 +115,6 @@ fun DependencyHandlerScope.android() { implementation("com.google.android.exoplayer:exoplayer", Versions.exoplayer) implementation("com.google.android.exoplayer:extension-okhttp", Versions.exoplayer) implementation("androidx.browser:browser", Versions.browser) - implementation("com.google.android.play:core-ktx", Versions.google_play) debugImplementation("com.squareup.leakcanary:leakcanary-android:2.7") } diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index d17a994b5..2672e3895 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -41,7 +41,6 @@ object Versions { const val androidx_exifinterface = "1.3.2" const val exoplayer = "2.14.2" const val browser = "1.3.0" - const val google_play = "1.8.1" const val protobuf = "3.17.3" const val androidx_test = "1.4.0" const val extJUnitVersion = "1.1.3-rc01" From 820f6795decc214fc25cadbe665f4ae223a82c6a Mon Sep 17 00:00:00 2001 From: itsMimao Date: Fri, 30 Jul 2021 15:48:03 +0800 Subject: [PATCH 109/137] move google play dependency in Non-FOSS component --- app/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1293ebfa7..3be76d667 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -199,8 +199,8 @@ dependencies { googleImplementation(platform("com.google.firebase:firebase-bom:26.1.0")) googleImplementation("com.google.firebase:firebase-analytics-ktx") googleImplementation("com.google.firebase:firebase-crashlytics-ktx") - // END Non-FOSS component googleImplementation("com.google.android.play:core-ktx:1.8.1") + // END Non-FOSS component } junit4() From 28606c24ebaa08f2c4eee7d986f85165f3b7abc1 Mon Sep 17 00:00:00 2001 From: itsMimao Date: Fri, 30 Jul 2021 16:15:22 +0800 Subject: [PATCH 110/137] disable signin when twitter custom apikey or api secret is empty --- app/src/main/kotlin/com/twidere/twiderex/scenes/SignInScene.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/SignInScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/SignInScene.kt index 18a775dee..6f0168d30 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/SignInScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/SignInScene.kt @@ -241,7 +241,8 @@ private fun TwitterCustomKeySignIn( } } } - } + }, + enabled = apiKey.isNotEmpty() && apiSecret.isNotEmpty() ) { Text(text = stringResource(id = R.string.scene_drawer_sign_in)) } From dfed09cdf3639fec7dfe0d0960d8555c6efc4b53 Mon Sep 17 00:00:00 2001 From: itsMimao Date: Tue, 3 Aug 2021 16:16:50 +0800 Subject: [PATCH 111/137] redesign directory and move transform code from job datas --- .../dm/DirectMessageRepositoryTest.kt | 2 +- .../twidere/twiderex/action/ComposeAction.kt | 4 +- .../twiderex/action/DirectMessageAction.kt | 6 +- .../twidere/twiderex/action/StatusActions.kt | 2 +- .../twiderex/component/UserComponent.kt | 2 +- .../component/lazy/ui/LazyUiListsList.kt | 2 +- .../component/navigation/Navigator.kt | 4 +- .../status/DetailedStatusComponent.kt | 2 +- .../twiderex/component/status/MastodonPoll.kt | 2 +- .../component/status/StatusActions.kt | 2 +- .../component/status/StatusMediaComponent.kt | 4 +- .../twiderex/component/status/StatusText.kt | 2 +- .../status/TimelineStatusComponent.kt | 4 +- .../twiderex/component/status/UserAvatar.kt | 2 +- .../twidere/twiderex/db/mapper/Mastodon.kt | 6 +- .../com/twidere/twiderex/db/mapper/Twitter.kt | 4 +- .../com/twidere/twiderex/db/model/DbMedia.kt | 2 +- .../com/twidere/twiderex/db/model/DbStatus.kt | 4 +- .../com/twidere/twiderex/db/model/DbUser.kt | 2 +- .../db/model/converter/MediaTypeConverter.kt | 2 +- .../converter/NotificationTypeConverter.kt | 2 +- .../model/converter/PlatformTypeConverter.kt | 2 +- .../http/TwidereNetworkImageLoader.kt | 2 +- .../twiderex/http/TwidereServiceFactory.kt | 2 +- .../twiderex/jobs/common/NotificationJob.kt | 4 +- .../twiderex/jobs/compose/ComposeJob.kt | 2 +- .../jobs/compose/MastodonComposeJob.kt | 2 +- .../jobs/compose/TwitterComposeJob.kt | 2 +- .../jobs/dm/DirectMessageDeleteJob.kt | 2 +- .../twiderex/jobs/dm/DirectMessageSendJob.kt | 4 +- .../jobs/dm/TwitterDirectMessageSendJob.kt | 2 +- .../twiderex/jobs/draft/SaveDraftJob.kt | 2 +- .../twiderex/jobs/status/LikeStatusJob.kt | 2 +- .../twiderex/jobs/status/MastodonVoteJob.kt | 2 +- .../twiderex/jobs/status/RetweetStatusJob.kt | 2 +- .../twidere/twiderex/jobs/status/StatusJob.kt | 2 +- .../jobs/status/UnRetweetStatusJob.kt | 2 +- .../twiderex/jobs/status/UnlikeStatusJob.kt | 2 +- .../twidere/twiderex/model/AccountDetails.kt | 2 + .../twiderex/model/{ => enums}/ListType.kt | 2 +- .../model/{ => enums}/MastodonStatusType.kt | 2 +- .../twiderex/model/{ => enums}/MediaType.kt | 2 +- .../model/{ => enums}/PlatformType.kt | 2 +- .../twidere/twiderex/model/job/ComposeData.kt | 45 +++++++++++++ .../{ => job}/DirectMessageDeleteData.kt | 19 +----- .../model/{ => job}/DirectMessageSendData.kt | 23 +------ .../twiderex/model/{ => job}/StatusResult.kt | 15 +---- .../WorkDataTransform.kt} | 63 +++++++++++++------ .../com/twidere/twiderex/model/ui/UiMedia.kt | 2 +- .../com/twidere/twiderex/model/ui/UiStatus.kt | 4 +- .../com/twidere/twiderex/model/ui/UiUser.kt | 2 +- .../com/twidere/twiderex/navigation/Route.kt | 2 +- .../mediator/user/UserFavouriteMediator.kt | 2 +- .../twiderex/repository/AccountRepository.kt | 2 +- .../repository/DirectMessageRepository.kt | 2 +- .../twiderex/repository/StatusRepository.kt | 2 +- .../com/twidere/twiderex/scenes/MediaScene.kt | 2 +- .../twidere/twiderex/scenes/PureMediaScene.kt | 2 +- .../twiderex/scenes/compose/ComposeScene.kt | 2 +- .../twidere/twiderex/scenes/home/HomeMenus.kt | 2 +- .../twiderex/scenes/home/SearchItem.kt | 2 +- .../scenes/lists/ListsAddMembersScene.kt | 2 +- .../twiderex/scenes/lists/ListsScene.kt | 2 +- .../scenes/lists/ListsTimelineScene.kt | 2 +- .../twiderex/scenes/search/SearchScene.kt | 2 +- .../twidere/twiderex/scenes/user/UserScene.kt | 2 +- .../twiderex/utils/PlatformResolver.kt | 2 +- .../viewmodel/ActiveAccountViewModel.kt | 2 +- .../viewmodel/compose/ComposeViewModel.kt | 4 +- .../twiderex/viewmodel/dm/DMEventViewModel.kt | 6 +- .../mastodon/MastodonSignInViewModel.kt | 2 +- .../twitter/TwitterSignInViewModel.kt | 2 +- .../twiderex/worker/compose/ComposeWorker.kt | 2 +- .../worker/compose/MastodonComposeWorker.kt | 4 +- .../worker/compose/TwitterComposeWorker.kt | 4 +- .../worker/dm/DirectMessageDeleteWorker.kt | 6 +- .../worker/dm/DirectMessageSendWorker.kt | 2 +- .../dm/TwitterDirectMessageSendWorker.kt | 4 +- .../twiderex/worker/draft/SaveDraftWorker.kt | 6 +- .../twiderex/worker/status/StatusWorker.kt | 1 + .../worker/status/UpdateStatusWorker.kt | 3 +- 81 files changed, 198 insertions(+), 165 deletions(-) rename app/src/main/kotlin/com/twidere/twiderex/model/{ => enums}/ListType.kt (95%) rename app/src/main/kotlin/com/twidere/twiderex/model/{ => enums}/MastodonStatusType.kt (96%) rename app/src/main/kotlin/com/twidere/twiderex/model/{ => enums}/MediaType.kt (95%) rename app/src/main/kotlin/com/twidere/twiderex/model/{ => enums}/PlatformType.kt (95%) create mode 100644 app/src/main/kotlin/com/twidere/twiderex/model/job/ComposeData.kt rename app/src/main/kotlin/com/twidere/twiderex/model/{ => job}/DirectMessageDeleteData.kt (59%) rename app/src/main/kotlin/com/twidere/twiderex/model/{ => job}/DirectMessageSendData.kt (53%) rename app/src/main/kotlin/com/twidere/twiderex/model/{ => job}/StatusResult.kt (74%) rename app/src/main/kotlin/com/twidere/twiderex/model/{ComposeData.kt => transform/WorkDataTransform.kt} (58%) diff --git a/app/src/androidTest/java/com/twidere/twiderex/repository/dm/DirectMessageRepositoryTest.kt b/app/src/androidTest/java/com/twidere/twiderex/repository/dm/DirectMessageRepositoryTest.kt index 41944f76a..24883e57b 100644 --- a/app/src/androidTest/java/com/twidere/twiderex/repository/dm/DirectMessageRepositoryTest.kt +++ b/app/src/androidTest/java/com/twidere/twiderex/repository/dm/DirectMessageRepositoryTest.kt @@ -30,7 +30,7 @@ import com.twidere.twiderex.db.mapper.toDbUser import com.twidere.twiderex.mock.MockDirectMessageService import com.twidere.twiderex.mock.MockLookUpService import com.twidere.twiderex.model.MicroBlogKey -import com.twidere.twiderex.model.PlatformType +import com.twidere.twiderex.model.enums.PlatformType import com.twidere.twiderex.model.ui.UiUser.Companion.toUi import com.twidere.twiderex.repository.DirectMessageRepository import kotlinx.coroutines.runBlocking diff --git a/app/src/main/kotlin/com/twidere/twiderex/action/ComposeAction.kt b/app/src/main/kotlin/com/twidere/twiderex/action/ComposeAction.kt index 150826b08..fa671a1a7 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/action/ComposeAction.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/action/ComposeAction.kt @@ -21,9 +21,9 @@ package com.twidere.twiderex.action import androidx.work.WorkManager -import com.twidere.twiderex.model.ComposeData import com.twidere.twiderex.model.MicroBlogKey -import com.twidere.twiderex.model.PlatformType +import com.twidere.twiderex.model.enums.PlatformType +import com.twidere.twiderex.model.job.ComposeData import com.twidere.twiderex.worker.compose.MastodonComposeWorker import com.twidere.twiderex.worker.compose.TwitterComposeWorker import com.twidere.twiderex.worker.draft.RemoveDraftWorker diff --git a/app/src/main/kotlin/com/twidere/twiderex/action/DirectMessageAction.kt b/app/src/main/kotlin/com/twidere/twiderex/action/DirectMessageAction.kt index 2e2d22ea4..fc66e0d0e 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/action/DirectMessageAction.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/action/DirectMessageAction.kt @@ -21,9 +21,9 @@ package com.twidere.twiderex.action import androidx.work.WorkManager -import com.twidere.twiderex.model.DirectMessageDeleteData -import com.twidere.twiderex.model.DirectMessageSendData -import com.twidere.twiderex.model.PlatformType +import com.twidere.twiderex.model.enums.PlatformType +import com.twidere.twiderex.model.job.DirectMessageDeleteData +import com.twidere.twiderex.model.job.DirectMessageSendData import com.twidere.twiderex.worker.dm.DirectMessageDeleteWorker import com.twidere.twiderex.worker.dm.TwitterDirectMessageSendWorker diff --git a/app/src/main/kotlin/com/twidere/twiderex/action/StatusActions.kt b/app/src/main/kotlin/com/twidere/twiderex/action/StatusActions.kt index 8d91521f4..bc3290400 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/action/StatusActions.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/action/StatusActions.kt @@ -23,7 +23,7 @@ package com.twidere.twiderex.action import androidx.compose.runtime.compositionLocalOf import androidx.work.WorkManager import com.twidere.twiderex.model.AccountDetails -import com.twidere.twiderex.model.StatusResult +import com.twidere.twiderex.model.job.StatusResult import com.twidere.twiderex.model.ui.UiStatus import com.twidere.twiderex.worker.database.DeleteDbStatusWorker import com.twidere.twiderex.worker.status.DeleteStatusWorker diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/UserComponent.kt b/app/src/main/kotlin/com/twidere/twiderex/component/UserComponent.kt index 15d3fcc43..c051e066b 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/UserComponent.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/UserComponent.kt @@ -102,7 +102,7 @@ import com.twidere.twiderex.di.assisted.assistedViewModel import com.twidere.twiderex.extensions.observeAsState import com.twidere.twiderex.extensions.withElevation import com.twidere.twiderex.model.MicroBlogKey -import com.twidere.twiderex.model.PlatformType +import com.twidere.twiderex.model.enums.PlatformType import com.twidere.twiderex.model.ui.UiUser import com.twidere.twiderex.navigation.RootRoute import com.twidere.twiderex.navigation.twidereXSchema diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/lazy/ui/LazyUiListsList.kt b/app/src/main/kotlin/com/twidere/twiderex/component/lazy/ui/LazyUiListsList.kt index 8ecc22a55..0492763d6 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/lazy/ui/LazyUiListsList.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/lazy/ui/LazyUiListsList.kt @@ -54,7 +54,7 @@ import androidx.paging.compose.items import com.twidere.twiderex.R import com.twidere.twiderex.component.lazy.loadState import com.twidere.twiderex.component.status.StatusDivider -import com.twidere.twiderex.model.ListType +import com.twidere.twiderex.model.enums.ListType import com.twidere.twiderex.model.ui.UiList import moe.tlaster.placeholder.TextPlaceHolder import java.util.Locale diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/navigation/Navigator.kt b/app/src/main/kotlin/com/twidere/twiderex/component/navigation/Navigator.kt index a923e5790..f387081f8 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/navigation/Navigator.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/navigation/Navigator.kt @@ -27,9 +27,9 @@ import android.net.Uri import android.webkit.CookieManager import androidx.compose.runtime.staticCompositionLocalOf import com.twidere.twiderex.db.model.ReferenceType -import com.twidere.twiderex.model.MastodonStatusType import com.twidere.twiderex.model.MicroBlogKey -import com.twidere.twiderex.model.PlatformType +import com.twidere.twiderex.model.enums.MastodonStatusType +import com.twidere.twiderex.model.enums.PlatformType import com.twidere.twiderex.model.ui.UiStatus import com.twidere.twiderex.model.ui.UiUser import com.twidere.twiderex.navigation.RootRoute diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/status/DetailedStatusComponent.kt b/app/src/main/kotlin/com/twidere/twiderex/component/status/DetailedStatusComponent.kt index 8280206fb..a6af522de 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/status/DetailedStatusComponent.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/status/DetailedStatusComponent.kt @@ -49,7 +49,7 @@ import androidx.compose.ui.unit.dp import com.twidere.twiderex.R import com.twidere.twiderex.component.FormattedTime import com.twidere.twiderex.extensions.humanizedCount -import com.twidere.twiderex.model.PlatformType +import com.twidere.twiderex.model.enums.PlatformType import com.twidere.twiderex.model.ui.UiStatus @Composable diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/status/MastodonPoll.kt b/app/src/main/kotlin/com/twidere/twiderex/component/status/MastodonPoll.kt index a84c46076..547b5e3fa 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/status/MastodonPoll.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/status/MastodonPoll.kt @@ -71,7 +71,7 @@ import com.twidere.services.mastodon.model.Poll import com.twidere.twiderex.R import com.twidere.twiderex.action.LocalStatusActions import com.twidere.twiderex.extensions.humanizedTimestamp -import com.twidere.twiderex.model.PlatformType +import com.twidere.twiderex.model.enums.PlatformType import com.twidere.twiderex.model.ui.UiStatus import com.twidere.twiderex.ui.LocalActiveAccount import kotlin.math.max diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/status/StatusActions.kt b/app/src/main/kotlin/com/twidere/twiderex/component/status/StatusActions.kt index 1521214fd..c15ed1928 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/status/StatusActions.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/status/StatusActions.kt @@ -64,7 +64,7 @@ import com.twidere.twiderex.action.LocalStatusActions import com.twidere.twiderex.component.navigation.LocalNavigator import com.twidere.twiderex.extensions.humanizedCount import com.twidere.twiderex.extensions.shareText -import com.twidere.twiderex.model.PlatformType +import com.twidere.twiderex.model.enums.PlatformType import com.twidere.twiderex.model.ui.UiStatus import com.twidere.twiderex.ui.LocalActiveAccount import com.twidere.twiderex.viewmodel.compose.ComposeType diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/status/StatusMediaComponent.kt b/app/src/main/kotlin/com/twidere/twiderex/component/status/StatusMediaComponent.kt index 7f5e13dcd..570ea06ed 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/status/StatusMediaComponent.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/status/StatusMediaComponent.kt @@ -59,8 +59,8 @@ import com.twidere.twiderex.component.foundation.NetworkBlurImage import com.twidere.twiderex.component.foundation.NetworkImage import com.twidere.twiderex.component.foundation.VideoPlayer import com.twidere.twiderex.component.navigation.LocalNavigator -import com.twidere.twiderex.model.MediaType -import com.twidere.twiderex.model.PlatformType +import com.twidere.twiderex.model.enums.MediaType +import com.twidere.twiderex.model.enums.PlatformType import com.twidere.twiderex.model.ui.UiMedia import com.twidere.twiderex.model.ui.UiStatus import com.twidere.twiderex.ui.TwidereTheme diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/status/StatusText.kt b/app/src/main/kotlin/com/twidere/twiderex/component/status/StatusText.kt index 08f3d0ee8..01f0ab87f 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/status/StatusText.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/status/StatusText.kt @@ -45,7 +45,7 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp import com.twidere.twiderex.R -import com.twidere.twiderex.model.PlatformType +import com.twidere.twiderex.model.enums.PlatformType import com.twidere.twiderex.model.ui.UiStatus @OptIn(ExperimentalAnimationApi::class) diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/status/TimelineStatusComponent.kt b/app/src/main/kotlin/com/twidere/twiderex/component/status/TimelineStatusComponent.kt index 770f78c18..c78783180 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/status/TimelineStatusComponent.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/status/TimelineStatusComponent.kt @@ -71,8 +71,8 @@ import com.twidere.twiderex.component.navigation.LocalNavigator import com.twidere.twiderex.db.model.DbMastodonStatusExtra import com.twidere.twiderex.db.model.DbPreviewCard import com.twidere.twiderex.extensions.icon -import com.twidere.twiderex.model.MastodonStatusType -import com.twidere.twiderex.model.PlatformType +import com.twidere.twiderex.model.enums.MastodonStatusType +import com.twidere.twiderex.model.enums.PlatformType import com.twidere.twiderex.model.ui.UiStatus import com.twidere.twiderex.preferences.LocalDisplayPreferences import com.twidere.twiderex.ui.LocalActiveAccount diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/status/UserAvatar.kt b/app/src/main/kotlin/com/twidere/twiderex/component/status/UserAvatar.kt index c73a9a466..d4b3cbb14 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/status/UserAvatar.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/status/UserAvatar.kt @@ -37,7 +37,7 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.twidere.twiderex.R import com.twidere.twiderex.component.navigation.LocalNavigator -import com.twidere.twiderex.model.PlatformType +import com.twidere.twiderex.model.enums.PlatformType import com.twidere.twiderex.model.ui.UiUser import com.twidere.twiderex.preferences.LocalDisplayPreferences import com.twidere.twiderex.preferences.proto.DisplayPreferences diff --git a/app/src/main/kotlin/com/twidere/twiderex/db/mapper/Mastodon.kt b/app/src/main/kotlin/com/twidere/twiderex/db/mapper/Mastodon.kt index 461fae6e0..b37747945 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/db/mapper/Mastodon.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/db/mapper/Mastodon.kt @@ -46,10 +46,10 @@ import com.twidere.twiderex.db.model.DbTrendWithHistory import com.twidere.twiderex.db.model.DbUser import com.twidere.twiderex.db.model.ReferenceType import com.twidere.twiderex.db.model.toDbStatusReference -import com.twidere.twiderex.model.MastodonStatusType -import com.twidere.twiderex.model.MediaType import com.twidere.twiderex.model.MicroBlogKey -import com.twidere.twiderex.model.PlatformType +import com.twidere.twiderex.model.enums.MastodonStatusType +import com.twidere.twiderex.model.enums.MediaType +import com.twidere.twiderex.model.enums.PlatformType import com.twidere.twiderex.navigation.RootDeepLinksRoute import org.jsoup.Jsoup import org.jsoup.nodes.Element diff --git a/app/src/main/kotlin/com/twidere/twiderex/db/mapper/Twitter.kt b/app/src/main/kotlin/com/twidere/twiderex/db/mapper/Twitter.kt index 2dc60e39b..9302a22c4 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/db/mapper/Twitter.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/db/mapper/Twitter.kt @@ -49,9 +49,9 @@ import com.twidere.twiderex.db.model.DbUser import com.twidere.twiderex.db.model.ReferenceType import com.twidere.twiderex.db.model.TwitterUrlEntity import com.twidere.twiderex.db.model.toDbStatusReference -import com.twidere.twiderex.model.MediaType import com.twidere.twiderex.model.MicroBlogKey -import com.twidere.twiderex.model.PlatformType +import com.twidere.twiderex.model.enums.MediaType +import com.twidere.twiderex.model.enums.PlatformType import com.twidere.twiderex.model.ui.ListsMode import com.twidere.twiderex.navigation.RootDeepLinksRouteDefinition import com.twitter.twittertext.Autolink diff --git a/app/src/main/kotlin/com/twidere/twiderex/db/model/DbMedia.kt b/app/src/main/kotlin/com/twidere/twiderex/db/model/DbMedia.kt index 7bcdaca7b..e6b857d73 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/db/model/DbMedia.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/db/model/DbMedia.kt @@ -23,8 +23,8 @@ package com.twidere.twiderex.db.model import androidx.room.Entity import androidx.room.Index import androidx.room.PrimaryKey -import com.twidere.twiderex.model.MediaType import com.twidere.twiderex.model.MicroBlogKey +import com.twidere.twiderex.model.enums.MediaType @Entity( tableName = "media", diff --git a/app/src/main/kotlin/com/twidere/twiderex/db/model/DbStatus.kt b/app/src/main/kotlin/com/twidere/twiderex/db/model/DbStatus.kt index 9fc015789..51f13ffc7 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/db/model/DbStatus.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/db/model/DbStatus.kt @@ -33,9 +33,9 @@ import com.twidere.services.mastodon.model.Poll import com.twidere.services.mastodon.model.Visibility import com.twidere.services.twitter.model.ReplySettings import com.twidere.twiderex.db.CacheDatabase -import com.twidere.twiderex.model.MastodonStatusType import com.twidere.twiderex.model.MicroBlogKey -import com.twidere.twiderex.model.PlatformType +import com.twidere.twiderex.model.enums.MastodonStatusType +import com.twidere.twiderex.model.enums.PlatformType import kotlinx.serialization.Serializable @Entity( diff --git a/app/src/main/kotlin/com/twidere/twiderex/db/model/DbUser.kt b/app/src/main/kotlin/com/twidere/twiderex/db/model/DbUser.kt index 513079244..f26a948c4 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/db/model/DbUser.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/db/model/DbUser.kt @@ -25,7 +25,7 @@ import androidx.room.Entity import androidx.room.Index import androidx.room.PrimaryKey import com.twidere.twiderex.model.MicroBlogKey -import com.twidere.twiderex.model.PlatformType +import com.twidere.twiderex.model.enums.PlatformType import kotlinx.serialization.Serializable @Entity( diff --git a/app/src/main/kotlin/com/twidere/twiderex/db/model/converter/MediaTypeConverter.kt b/app/src/main/kotlin/com/twidere/twiderex/db/model/converter/MediaTypeConverter.kt index 1518288cd..fbb10fd21 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/db/model/converter/MediaTypeConverter.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/db/model/converter/MediaTypeConverter.kt @@ -21,7 +21,7 @@ package com.twidere.twiderex.db.model.converter import androidx.room.TypeConverter -import com.twidere.twiderex.model.MediaType +import com.twidere.twiderex.model.enums.MediaType class MediaTypeConverter { @TypeConverter diff --git a/app/src/main/kotlin/com/twidere/twiderex/db/model/converter/NotificationTypeConverter.kt b/app/src/main/kotlin/com/twidere/twiderex/db/model/converter/NotificationTypeConverter.kt index bbcca4a38..8e018b098 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/db/model/converter/NotificationTypeConverter.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/db/model/converter/NotificationTypeConverter.kt @@ -21,7 +21,7 @@ package com.twidere.twiderex.db.model.converter import androidx.room.TypeConverter -import com.twidere.twiderex.model.MastodonStatusType +import com.twidere.twiderex.model.enums.MastodonStatusType class NotificationTypeConverter { @TypeConverter diff --git a/app/src/main/kotlin/com/twidere/twiderex/db/model/converter/PlatformTypeConverter.kt b/app/src/main/kotlin/com/twidere/twiderex/db/model/converter/PlatformTypeConverter.kt index e4eb54d2d..77fd7cd6f 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/db/model/converter/PlatformTypeConverter.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/db/model/converter/PlatformTypeConverter.kt @@ -21,7 +21,7 @@ package com.twidere.twiderex.db.model.converter import androidx.room.TypeConverter -import com.twidere.twiderex.model.PlatformType +import com.twidere.twiderex.model.enums.PlatformType class PlatformTypeConverter { @TypeConverter diff --git a/app/src/main/kotlin/com/twidere/twiderex/http/TwidereNetworkImageLoader.kt b/app/src/main/kotlin/com/twidere/twiderex/http/TwidereNetworkImageLoader.kt index c95b91cc0..b7b885732 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/http/TwidereNetworkImageLoader.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/http/TwidereNetworkImageLoader.kt @@ -31,8 +31,8 @@ import coil.request.ImageRequest import coil.request.ImageResult import com.twidere.services.http.authorization.OAuth1Authorization import com.twidere.twiderex.model.AccountDetails -import com.twidere.twiderex.model.PlatformType import com.twidere.twiderex.model.cred.OAuthCredentials +import com.twidere.twiderex.model.enums.PlatformType import okhttp3.Headers import okhttp3.Request import java.net.URL diff --git a/app/src/main/kotlin/com/twidere/twiderex/http/TwidereServiceFactory.kt b/app/src/main/kotlin/com/twidere/twiderex/http/TwidereServiceFactory.kt index a1684b440..dcebf8e12 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/http/TwidereServiceFactory.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/http/TwidereServiceFactory.kt @@ -25,10 +25,10 @@ import com.twidere.services.http.config.HttpConfigClientFactory import com.twidere.services.mastodon.MastodonService import com.twidere.services.microblog.MicroBlogService import com.twidere.services.twitter.TwitterService -import com.twidere.twiderex.model.PlatformType import com.twidere.twiderex.model.cred.Credentials import com.twidere.twiderex.model.cred.OAuth2Credentials import com.twidere.twiderex.model.cred.OAuthCredentials +import com.twidere.twiderex.model.enums.PlatformType class TwidereServiceFactory(private val configProvider: TwidereHttpConfigProvider) { diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/common/NotificationJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/common/NotificationJob.kt index cf2ce227c..983756c4a 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/jobs/common/NotificationJob.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/common/NotificationJob.kt @@ -29,8 +29,8 @@ import coil.request.SuccessResult import com.twidere.twiderex.R import com.twidere.twiderex.db.model.ReferenceType import com.twidere.twiderex.model.AccountDetails -import com.twidere.twiderex.model.MastodonStatusType -import com.twidere.twiderex.model.PlatformType +import com.twidere.twiderex.model.enums.MastodonStatusType +import com.twidere.twiderex.model.enums.PlatformType import com.twidere.twiderex.model.ui.UiStatus import com.twidere.twiderex.navigation.RootDeepLinksRoute import com.twidere.twiderex.notification.AppNotification diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/compose/ComposeJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/compose/ComposeJob.kt index ceb4ede09..4fe75382b 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/jobs/compose/ComposeJob.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/compose/ComposeJob.kt @@ -25,8 +25,8 @@ import com.twidere.services.microblog.MicroBlogService import com.twidere.twiderex.R import com.twidere.twiderex.kmp.ExifScrambler import com.twidere.twiderex.kmp.RemoteNavigator -import com.twidere.twiderex.model.ComposeData import com.twidere.twiderex.model.MicroBlogKey +import com.twidere.twiderex.model.job.ComposeData import com.twidere.twiderex.model.ui.UiStatus import com.twidere.twiderex.navigation.RootDeepLinksRoute import com.twidere.twiderex.notification.AppNotification diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/compose/MastodonComposeJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/compose/MastodonComposeJob.kt index 28a738786..42cdd72b5 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/jobs/compose/MastodonComposeJob.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/compose/MastodonComposeJob.kt @@ -30,8 +30,8 @@ import com.twidere.twiderex.db.model.saveToDb import com.twidere.twiderex.kmp.ExifScrambler import com.twidere.twiderex.kmp.FileResolver import com.twidere.twiderex.kmp.RemoteNavigator -import com.twidere.twiderex.model.ComposeData import com.twidere.twiderex.model.MicroBlogKey +import com.twidere.twiderex.model.job.ComposeData import com.twidere.twiderex.model.ui.UiStatus import com.twidere.twiderex.model.ui.UiStatus.Companion.toUi import com.twidere.twiderex.notification.AppNotificationManager diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/compose/TwitterComposeJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/compose/TwitterComposeJob.kt index 5329ee78b..67c4baf6e 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/jobs/compose/TwitterComposeJob.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/compose/TwitterComposeJob.kt @@ -28,8 +28,8 @@ import com.twidere.twiderex.db.model.saveToDb import com.twidere.twiderex.kmp.ExifScrambler import com.twidere.twiderex.kmp.FileResolver import com.twidere.twiderex.kmp.RemoteNavigator -import com.twidere.twiderex.model.ComposeData import com.twidere.twiderex.model.MicroBlogKey +import com.twidere.twiderex.model.job.ComposeData import com.twidere.twiderex.model.ui.UiStatus import com.twidere.twiderex.model.ui.UiStatus.Companion.toUi import com.twidere.twiderex.notification.AppNotificationManager diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/dm/DirectMessageDeleteJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/dm/DirectMessageDeleteJob.kt index d4dbc834e..21ff36fb7 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/jobs/dm/DirectMessageDeleteJob.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/dm/DirectMessageDeleteJob.kt @@ -21,8 +21,8 @@ package com.twidere.twiderex.jobs.dm import com.twidere.services.microblog.DirectMessageService -import com.twidere.twiderex.model.DirectMessageDeleteData import com.twidere.twiderex.model.MicroBlogKey +import com.twidere.twiderex.model.job.DirectMessageDeleteData import com.twidere.twiderex.repository.AccountRepository import com.twidere.twiderex.repository.DirectMessageRepository diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/dm/DirectMessageSendJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/dm/DirectMessageSendJob.kt index adad5e9cf..244d773c3 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/jobs/dm/DirectMessageSendJob.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/dm/DirectMessageSendJob.kt @@ -33,9 +33,9 @@ import com.twidere.twiderex.db.model.DbDMEventWithAttachments.Companion.saveToDb import com.twidere.twiderex.db.model.DbMedia import com.twidere.twiderex.kmp.FileResolver import com.twidere.twiderex.model.AccountDetails -import com.twidere.twiderex.model.DirectMessageSendData -import com.twidere.twiderex.model.MediaType import com.twidere.twiderex.model.MicroBlogKey +import com.twidere.twiderex.model.enums.MediaType +import com.twidere.twiderex.model.job.DirectMessageSendData import com.twidere.twiderex.navigation.RootDeepLinksRoute import com.twidere.twiderex.notification.AppNotification import com.twidere.twiderex.notification.AppNotificationManager diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/dm/TwitterDirectMessageSendJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/dm/TwitterDirectMessageSendJob.kt index 2f4856c38..4b1b4615a 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/jobs/dm/TwitterDirectMessageSendJob.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/dm/TwitterDirectMessageSendJob.kt @@ -30,8 +30,8 @@ import com.twidere.twiderex.db.mapper.toDbUser import com.twidere.twiderex.db.model.DbDMEventWithAttachments import com.twidere.twiderex.db.model.DbUser import com.twidere.twiderex.kmp.FileResolver -import com.twidere.twiderex.model.DirectMessageSendData import com.twidere.twiderex.model.MicroBlogKey +import com.twidere.twiderex.model.job.DirectMessageSendData import com.twidere.twiderex.notification.AppNotificationManager import com.twidere.twiderex.repository.AccountRepository diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/draft/SaveDraftJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/draft/SaveDraftJob.kt index 6f044dced..8b6342178 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/jobs/draft/SaveDraftJob.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/draft/SaveDraftJob.kt @@ -20,7 +20,7 @@ */ package com.twidere.twiderex.jobs.draft -import com.twidere.twiderex.model.ComposeData +import com.twidere.twiderex.model.job.ComposeData import com.twidere.twiderex.notification.InAppNotification import com.twidere.twiderex.repository.DraftRepository import com.twidere.twiderex.utils.notify diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/status/LikeStatusJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/status/LikeStatusJob.kt index a2bb67bef..f75636165 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/jobs/status/LikeStatusJob.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/status/LikeStatusJob.kt @@ -23,7 +23,7 @@ package com.twidere.twiderex.jobs.status import com.twidere.services.microblog.StatusService import com.twidere.twiderex.db.mapper.toDbStatusWithReference import com.twidere.twiderex.model.MicroBlogKey -import com.twidere.twiderex.model.StatusResult +import com.twidere.twiderex.model.job.StatusResult import com.twidere.twiderex.model.ui.UiStatus import com.twidere.twiderex.model.ui.UiStatus.Companion.toUi import com.twidere.twiderex.notification.InAppNotification diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/status/MastodonVoteJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/status/MastodonVoteJob.kt index 9d44f0b98..cdc425031 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/jobs/status/MastodonVoteJob.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/status/MastodonVoteJob.kt @@ -22,7 +22,7 @@ package com.twidere.twiderex.jobs.status import com.twidere.services.mastodon.MastodonService import com.twidere.twiderex.model.MicroBlogKey -import com.twidere.twiderex.model.PlatformType +import com.twidere.twiderex.model.enums.PlatformType import com.twidere.twiderex.notification.InAppNotification import com.twidere.twiderex.repository.AccountRepository import com.twidere.twiderex.repository.StatusRepository diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/status/RetweetStatusJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/status/RetweetStatusJob.kt index 875a49daa..1bcbd788c 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/jobs/status/RetweetStatusJob.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/status/RetweetStatusJob.kt @@ -23,7 +23,7 @@ package com.twidere.twiderex.jobs.status import com.twidere.services.microblog.StatusService import com.twidere.twiderex.db.mapper.toDbStatusWithReference import com.twidere.twiderex.model.MicroBlogKey -import com.twidere.twiderex.model.StatusResult +import com.twidere.twiderex.model.job.StatusResult import com.twidere.twiderex.model.ui.UiStatus import com.twidere.twiderex.model.ui.UiStatus.Companion.toUi import com.twidere.twiderex.notification.InAppNotification diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/status/StatusJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/status/StatusJob.kt index 63eaeb4eb..85ef159b6 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/jobs/status/StatusJob.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/status/StatusJob.kt @@ -24,7 +24,7 @@ import com.twidere.services.microblog.StatusService import com.twidere.services.twitter.TwitterErrorCodes import com.twidere.services.twitter.model.exceptions.TwitterApiException import com.twidere.twiderex.model.MicroBlogKey -import com.twidere.twiderex.model.StatusResult +import com.twidere.twiderex.model.job.StatusResult import com.twidere.twiderex.model.ui.UiStatus import com.twidere.twiderex.notification.InAppNotification import com.twidere.twiderex.repository.AccountRepository diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/status/UnRetweetStatusJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/status/UnRetweetStatusJob.kt index 590345697..b1e0381c4 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/jobs/status/UnRetweetStatusJob.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/status/UnRetweetStatusJob.kt @@ -23,7 +23,7 @@ package com.twidere.twiderex.jobs.status import com.twidere.services.microblog.StatusService import com.twidere.twiderex.db.mapper.toDbStatusWithReference import com.twidere.twiderex.model.MicroBlogKey -import com.twidere.twiderex.model.StatusResult +import com.twidere.twiderex.model.job.StatusResult import com.twidere.twiderex.model.ui.UiStatus import com.twidere.twiderex.model.ui.UiStatus.Companion.toUi import com.twidere.twiderex.notification.InAppNotification diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/status/UnlikeStatusJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/status/UnlikeStatusJob.kt index f88b68269..f41ae1902 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/jobs/status/UnlikeStatusJob.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/status/UnlikeStatusJob.kt @@ -23,7 +23,7 @@ package com.twidere.twiderex.jobs.status import com.twidere.services.microblog.StatusService import com.twidere.twiderex.db.mapper.toDbStatusWithReference import com.twidere.twiderex.model.MicroBlogKey -import com.twidere.twiderex.model.StatusResult +import com.twidere.twiderex.model.job.StatusResult import com.twidere.twiderex.model.ui.UiStatus import com.twidere.twiderex.model.ui.UiStatus.Companion.toUi import com.twidere.twiderex.notification.InAppNotification diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/AccountDetails.kt b/app/src/main/kotlin/com/twidere/twiderex/model/AccountDetails.kt index d3ed155bd..a2106f17d 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/model/AccountDetails.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/model/AccountDetails.kt @@ -28,6 +28,8 @@ import com.twidere.twiderex.model.cred.CredentialsType import com.twidere.twiderex.model.cred.EmptyCredentials import com.twidere.twiderex.model.cred.OAuth2Credentials import com.twidere.twiderex.model.cred.OAuthCredentials +import com.twidere.twiderex.model.enums.ListType +import com.twidere.twiderex.model.enums.PlatformType import com.twidere.twiderex.model.ui.UiUser import com.twidere.twiderex.utils.fromJson diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/ListType.kt b/app/src/main/kotlin/com/twidere/twiderex/model/enums/ListType.kt similarity index 95% rename from app/src/main/kotlin/com/twidere/twiderex/model/ListType.kt rename to app/src/main/kotlin/com/twidere/twiderex/model/enums/ListType.kt index 39385b66e..5199dfb82 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/model/ListType.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/model/enums/ListType.kt @@ -18,7 +18,7 @@ * You should have received a copy of the GNU General Public License * along with Twidere X. If not, see . */ -package com.twidere.twiderex.model +package com.twidere.twiderex.model.enums enum class ListType { All, // both owned and subscribed diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/MastodonStatusType.kt b/app/src/main/kotlin/com/twidere/twiderex/model/enums/MastodonStatusType.kt similarity index 96% rename from app/src/main/kotlin/com/twidere/twiderex/model/MastodonStatusType.kt rename to app/src/main/kotlin/com/twidere/twiderex/model/enums/MastodonStatusType.kt index dd63133fd..26f7197fb 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/model/MastodonStatusType.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/model/enums/MastodonStatusType.kt @@ -18,7 +18,7 @@ * You should have received a copy of the GNU General Public License * along with Twidere X. If not, see . */ -package com.twidere.twiderex.model +package com.twidere.twiderex.model.enums import kotlinx.serialization.Serializable diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/MediaType.kt b/app/src/main/kotlin/com/twidere/twiderex/model/enums/MediaType.kt similarity index 95% rename from app/src/main/kotlin/com/twidere/twiderex/model/MediaType.kt rename to app/src/main/kotlin/com/twidere/twiderex/model/enums/MediaType.kt index b47cce3bc..27315e2dc 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/model/MediaType.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/model/enums/MediaType.kt @@ -18,7 +18,7 @@ * You should have received a copy of the GNU General Public License * along with Twidere X. If not, see . */ -package com.twidere.twiderex.model +package com.twidere.twiderex.model.enums enum class MediaType { photo, diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/PlatformType.kt b/app/src/main/kotlin/com/twidere/twiderex/model/enums/PlatformType.kt similarity index 95% rename from app/src/main/kotlin/com/twidere/twiderex/model/PlatformType.kt rename to app/src/main/kotlin/com/twidere/twiderex/model/enums/PlatformType.kt index a17bb1d19..fedc795ba 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/model/PlatformType.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/model/enums/PlatformType.kt @@ -18,7 +18,7 @@ * You should have received a copy of the GNU General Public License * along with Twidere X. If not, see . */ -package com.twidere.twiderex.model +package com.twidere.twiderex.model.enums enum class PlatformType { Twitter, diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/job/ComposeData.kt b/app/src/main/kotlin/com/twidere/twiderex/model/job/ComposeData.kt new file mode 100644 index 000000000..d0f537c0b --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/model/job/ComposeData.kt @@ -0,0 +1,45 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.model.job + +import com.twidere.services.mastodon.model.Visibility +import com.twidere.twiderex.model.MicroBlogKey +import com.twidere.twiderex.viewmodel.compose.ComposeType +import com.twidere.twiderex.viewmodel.compose.VoteExpired +import java.util.UUID + +data class ComposeData( + val content: String, + val images: List, + val composeType: ComposeType, + val statusKey: MicroBlogKey? = null, + val lat: Double? = null, + val long: Double? = null, + val draftId: String = UUID.randomUUID().toString(), + val excludedReplyUserIds: List? = null, + val voteOptions: List? = null, + val voteExpired: VoteExpired? = null, + val voteMultiple: Boolean? = null, + val visibility: Visibility? = null, + val isSensitive: Boolean? = null, + val contentWarningText: String? = null, + val isThreadMode: Boolean = false +) diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/DirectMessageDeleteData.kt b/app/src/main/kotlin/com/twidere/twiderex/model/job/DirectMessageDeleteData.kt similarity index 59% rename from app/src/main/kotlin/com/twidere/twiderex/model/DirectMessageDeleteData.kt rename to app/src/main/kotlin/com/twidere/twiderex/model/job/DirectMessageDeleteData.kt index bdd2787c2..1a7c22a6e 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/model/DirectMessageDeleteData.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/model/job/DirectMessageDeleteData.kt @@ -18,10 +18,9 @@ * You should have received a copy of the GNU General Public License * along with Twidere X. If not, see . */ -package com.twidere.twiderex.model +package com.twidere.twiderex.model.job -import androidx.work.Data -import androidx.work.workDataOf +import com.twidere.twiderex.model.MicroBlogKey data class DirectMessageDeleteData( val messageId: String, @@ -29,17 +28,3 @@ data class DirectMessageDeleteData( val messageKey: MicroBlogKey, val accountKey: MicroBlogKey, ) - -fun DirectMessageDeleteData.toWorkData() = workDataOf( - "accountKey" to accountKey.toString(), - "conversationKey" to conversationKey.toString(), - "messageKey" to messageKey.toString(), - "messageId" to messageId, -) - -fun Data.toDirectMessageDeleteData() = DirectMessageDeleteData( - messageId = getString("messageId") ?: "", - accountKey = MicroBlogKey.valueOf(getString("accountKey") ?: ""), - conversationKey = MicroBlogKey.valueOf(getString("conversationKey") ?: ""), - messageKey = MicroBlogKey.valueOf(getString("messageKey") ?: ""), -) diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/DirectMessageSendData.kt b/app/src/main/kotlin/com/twidere/twiderex/model/job/DirectMessageSendData.kt similarity index 53% rename from app/src/main/kotlin/com/twidere/twiderex/model/DirectMessageSendData.kt rename to app/src/main/kotlin/com/twidere/twiderex/model/job/DirectMessageSendData.kt index d20682539..a2f36d5bf 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/model/DirectMessageSendData.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/model/job/DirectMessageSendData.kt @@ -18,10 +18,9 @@ * You should have received a copy of the GNU General Public License * along with Twidere X. If not, see . */ -package com.twidere.twiderex.model +package com.twidere.twiderex.model.job -import androidx.work.Data -import androidx.work.workDataOf +import com.twidere.twiderex.model.MicroBlogKey data class DirectMessageSendData( val text: String?, @@ -31,21 +30,3 @@ data class DirectMessageSendData( val accountKey: MicroBlogKey, val draftMessageKey: MicroBlogKey, ) - -fun DirectMessageSendData.toWorkData() = workDataOf( - "text" to text, - "images" to images.toTypedArray(), - "recipientUserKey" to recipientUserKey.toString(), - "dratMessageKey" to draftMessageKey.toString(), - "conversationKey" to conversationKey.toString(), - "accountKey" to accountKey.toString(), -) - -fun Data.toDirectMessageSendData() = DirectMessageSendData( - text = getString("text") ?: "", - images = getStringArray("images")?.toList() ?: emptyList(), - recipientUserKey = MicroBlogKey.valueOf(getString("recipientUserKey") ?: ""), - draftMessageKey = MicroBlogKey.valueOf(getString("dratMessageKey") ?: ""), - conversationKey = MicroBlogKey.valueOf(getString("conversationKey") ?: ""), - accountKey = MicroBlogKey.valueOf(getString("accountKey") ?: ""), -) diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/StatusResult.kt b/app/src/main/kotlin/com/twidere/twiderex/model/job/StatusResult.kt similarity index 74% rename from app/src/main/kotlin/com/twidere/twiderex/model/StatusResult.kt rename to app/src/main/kotlin/com/twidere/twiderex/model/job/StatusResult.kt index 8e3967dfd..f27c2f7e5 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/model/StatusResult.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/model/job/StatusResult.kt @@ -18,9 +18,9 @@ * You should have received a copy of the GNU General Public License * along with Twidere X. If not, see . */ -package com.twidere.twiderex.model +package com.twidere.twiderex.model.job -import androidx.work.workDataOf +import com.twidere.twiderex.model.MicroBlogKey data class StatusResult( val statusKey: MicroBlogKey, @@ -29,13 +29,4 @@ data class StatusResult( val liked: Boolean? = null, val retweetCount: Long? = null, val likeCount: Long? = null, -) { - fun toWorkData() = workDataOf( - "statusKey" to statusKey.toString(), - "accountKey" to accountKey.toString(), - "liked" to liked, - "retweeted" to retweeted, - "retweetCount" to retweetCount, - "likeCount" to likeCount, - ) -} +) diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/ComposeData.kt b/app/src/main/kotlin/com/twidere/twiderex/model/transform/WorkDataTransform.kt similarity index 58% rename from app/src/main/kotlin/com/twidere/twiderex/model/ComposeData.kt rename to app/src/main/kotlin/com/twidere/twiderex/model/transform/WorkDataTransform.kt index 4127fffde..b46a1df0c 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/model/ComposeData.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/model/transform/WorkDataTransform.kt @@ -18,33 +18,28 @@ * You should have received a copy of the GNU General Public License * along with Twidere X. If not, see . */ -package com.twidere.twiderex.model +package com.twidere.twiderex.model.transform import androidx.work.Data import androidx.work.workDataOf import com.twidere.services.mastodon.model.Visibility import com.twidere.twiderex.extensions.getNullableBoolean import com.twidere.twiderex.extensions.getNullableDouble +import com.twidere.twiderex.model.MicroBlogKey +import com.twidere.twiderex.model.job.ComposeData +import com.twidere.twiderex.model.job.DirectMessageDeleteData +import com.twidere.twiderex.model.job.DirectMessageSendData +import com.twidere.twiderex.model.job.StatusResult import com.twidere.twiderex.viewmodel.compose.ComposeType import com.twidere.twiderex.viewmodel.compose.VoteExpired -import java.util.UUID -data class ComposeData( - val content: String, - val images: List, - val composeType: ComposeType, - val statusKey: MicroBlogKey? = null, - val lat: Double? = null, - val long: Double? = null, - val draftId: String = UUID.randomUUID().toString(), - val excludedReplyUserIds: List? = null, - val voteOptions: List? = null, - val voteExpired: VoteExpired? = null, - val voteMultiple: Boolean? = null, - val visibility: Visibility? = null, - val isSensitive: Boolean? = null, - val contentWarningText: String? = null, - val isThreadMode: Boolean = false +fun StatusResult.toWorkData() = workDataOf( + "statusKey" to statusKey.toString(), + "accountKey" to accountKey.toString(), + "liked" to liked, + "retweeted" to retweeted, + "retweetCount" to retweetCount, + "likeCount" to likeCount, ) fun ComposeData.toWorkData() = workDataOf( @@ -82,3 +77,35 @@ fun Data.toComposeData() = ComposeData( contentWarningText = getString("contentWarningText"), isThreadMode = getBoolean("isThreadMode", false), ) + +fun DirectMessageDeleteData.toWorkData() = workDataOf( + "accountKey" to accountKey.toString(), + "conversationKey" to conversationKey.toString(), + "messageKey" to messageKey.toString(), + "messageId" to messageId, +) + +fun Data.toDirectMessageDeleteData() = DirectMessageDeleteData( + messageId = getString("messageId") ?: "", + accountKey = MicroBlogKey.valueOf(getString("accountKey") ?: ""), + conversationKey = MicroBlogKey.valueOf(getString("conversationKey") ?: ""), + messageKey = MicroBlogKey.valueOf(getString("messageKey") ?: ""), +) + +fun DirectMessageSendData.toWorkData() = workDataOf( + "text" to text, + "images" to images.toTypedArray(), + "recipientUserKey" to recipientUserKey.toString(), + "dratMessageKey" to draftMessageKey.toString(), + "conversationKey" to conversationKey.toString(), + "accountKey" to accountKey.toString(), +) + +fun Data.toDirectMessageSendData() = DirectMessageSendData( + text = getString("text") ?: "", + images = getStringArray("images")?.toList() ?: emptyList(), + recipientUserKey = MicroBlogKey.valueOf(getString("recipientUserKey") ?: ""), + draftMessageKey = MicroBlogKey.valueOf(getString("dratMessageKey") ?: ""), + conversationKey = MicroBlogKey.valueOf(getString("conversationKey") ?: ""), + accountKey = MicroBlogKey.valueOf(getString("accountKey") ?: ""), +) diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiMedia.kt b/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiMedia.kt index 768bd07db..4b4d25423 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiMedia.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiMedia.kt @@ -26,7 +26,7 @@ import androidx.compose.runtime.Immutable import androidx.compose.ui.res.painterResource import com.twidere.twiderex.R import com.twidere.twiderex.db.model.DbMedia -import com.twidere.twiderex.model.MediaType +import com.twidere.twiderex.model.enums.MediaType @Immutable data class UiMedia( diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiStatus.kt b/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiStatus.kt index d94035447..c86a64505 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiStatus.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiStatus.kt @@ -31,9 +31,9 @@ import com.twidere.twiderex.db.model.DbStatusWithMediaAndUser import com.twidere.twiderex.db.model.DbStatusWithReference import com.twidere.twiderex.db.model.DbTwitterStatusExtra import com.twidere.twiderex.db.model.ReferenceType -import com.twidere.twiderex.model.MastodonStatusType import com.twidere.twiderex.model.MicroBlogKey -import com.twidere.twiderex.model.PlatformType +import com.twidere.twiderex.model.enums.MastodonStatusType +import com.twidere.twiderex.model.enums.PlatformType import com.twidere.twiderex.model.ui.UiMedia.Companion.toUi import com.twidere.twiderex.model.ui.UiUrlEntity.Companion.toUi import com.twidere.twiderex.model.ui.UiUser.Companion.toUi diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiUser.kt b/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiUser.kt index 6a2f22793..35f92f9c3 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiUser.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiUser.kt @@ -29,7 +29,7 @@ import com.twidere.twiderex.db.model.DbTwitterUserExtra import com.twidere.twiderex.db.model.DbUser import com.twidere.twiderex.model.AccountDetails import com.twidere.twiderex.model.MicroBlogKey -import com.twidere.twiderex.model.PlatformType +import com.twidere.twiderex.model.enums.PlatformType import com.twidere.twiderex.ui.LocalActiveAccount @Immutable diff --git a/app/src/main/kotlin/com/twidere/twiderex/navigation/Route.kt b/app/src/main/kotlin/com/twidere/twiderex/navigation/Route.kt index 60d59e842..bc2293903 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/navigation/Route.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/navigation/Route.kt @@ -32,7 +32,7 @@ import androidx.compose.ui.unit.Constraints import com.twidere.twiderex.component.RequireAuthorization import com.twidere.twiderex.component.navigation.LocalNavigator import com.twidere.twiderex.model.MicroBlogKey -import com.twidere.twiderex.model.PlatformType +import com.twidere.twiderex.model.enums.PlatformType import com.twidere.twiderex.scenes.DraftListScene import com.twidere.twiderex.scenes.HomeScene import com.twidere.twiderex.scenes.PureMediaScene diff --git a/app/src/main/kotlin/com/twidere/twiderex/paging/mediator/user/UserFavouriteMediator.kt b/app/src/main/kotlin/com/twidere/twiderex/paging/mediator/user/UserFavouriteMediator.kt index dcd55ecbd..0cc277532 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/paging/mediator/user/UserFavouriteMediator.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/paging/mediator/user/UserFavouriteMediator.kt @@ -29,7 +29,7 @@ import com.twidere.twiderex.db.model.DbPagingTimelineWithStatus import com.twidere.twiderex.db.model.UserTimelineType import com.twidere.twiderex.db.model.pagingKey import com.twidere.twiderex.model.MicroBlogKey -import com.twidere.twiderex.model.PlatformType +import com.twidere.twiderex.model.enums.PlatformType import com.twidere.twiderex.paging.mediator.paging.CursorWithCustomOrderPagination import com.twidere.twiderex.paging.mediator.paging.CursorWithCustomOrderPagingMediator import com.twidere.twiderex.paging.mediator.paging.CursorWithCustomOrderPagingResult diff --git a/app/src/main/kotlin/com/twidere/twiderex/repository/AccountRepository.kt b/app/src/main/kotlin/com/twidere/twiderex/repository/AccountRepository.kt index c3eddabbd..5ffc04aaf 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/repository/AccountRepository.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/repository/AccountRepository.kt @@ -27,8 +27,8 @@ import com.twidere.twiderex.model.AccountDetails import com.twidere.twiderex.model.AccountPreferences import com.twidere.twiderex.model.AmUser import com.twidere.twiderex.model.MicroBlogKey -import com.twidere.twiderex.model.PlatformType import com.twidere.twiderex.model.cred.CredentialsType +import com.twidere.twiderex.model.enums.PlatformType import com.twidere.twiderex.utils.fromJson import com.twidere.twiderex.utils.json import kotlinx.coroutines.flow.MutableStateFlow diff --git a/app/src/main/kotlin/com/twidere/twiderex/repository/DirectMessageRepository.kt b/app/src/main/kotlin/com/twidere/twiderex/repository/DirectMessageRepository.kt index a5b4aac2d..a0fe9bb4c 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/repository/DirectMessageRepository.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/repository/DirectMessageRepository.kt @@ -35,7 +35,7 @@ import com.twidere.twiderex.db.model.DbDMConversation.Companion.saveToDb import com.twidere.twiderex.db.model.DbDMEventWithAttachments.Companion.saveToDb import com.twidere.twiderex.db.model.DbUser import com.twidere.twiderex.model.MicroBlogKey -import com.twidere.twiderex.model.PlatformType +import com.twidere.twiderex.model.enums.PlatformType import com.twidere.twiderex.model.ui.UiDMConversation import com.twidere.twiderex.model.ui.UiDMConversation.Companion.toUi import com.twidere.twiderex.model.ui.UiDMConversationWithLatestMessage diff --git a/app/src/main/kotlin/com/twidere/twiderex/repository/StatusRepository.kt b/app/src/main/kotlin/com/twidere/twiderex/repository/StatusRepository.kt index 730ea2b84..ad9788c22 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/repository/StatusRepository.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/repository/StatusRepository.kt @@ -34,7 +34,7 @@ import com.twidere.twiderex.db.model.saveToDb import com.twidere.twiderex.extensions.toUi import com.twidere.twiderex.model.AccountDetails import com.twidere.twiderex.model.MicroBlogKey -import com.twidere.twiderex.model.PlatformType +import com.twidere.twiderex.model.enums.PlatformType import com.twidere.twiderex.model.ui.UiStatus import com.twidere.twiderex.model.ui.UiStatus.Companion.toUi import com.twidere.twiderex.paging.mediator.paging.pager diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/MediaScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/MediaScene.kt index cbdf14e37..90e05382a 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/MediaScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/MediaScene.kt @@ -96,8 +96,8 @@ import com.twidere.twiderex.extensions.hideControls import com.twidere.twiderex.extensions.observeAsState import com.twidere.twiderex.extensions.setOnSystemBarsVisibilityChangeListener import com.twidere.twiderex.extensions.showControls -import com.twidere.twiderex.model.MediaType import com.twidere.twiderex.model.MicroBlogKey +import com.twidere.twiderex.model.enums.MediaType import com.twidere.twiderex.model.ui.UiMedia import com.twidere.twiderex.model.ui.UiStatus import com.twidere.twiderex.preferences.proto.DisplayPreferences diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/PureMediaScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/PureMediaScene.kt index 69b996589..f2a4a6804 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/PureMediaScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/PureMediaScene.kt @@ -66,8 +66,8 @@ import com.twidere.twiderex.extensions.hideControls import com.twidere.twiderex.extensions.observeAsState import com.twidere.twiderex.extensions.setOnSystemBarsVisibilityChangeListener import com.twidere.twiderex.extensions.showControls -import com.twidere.twiderex.model.MediaType import com.twidere.twiderex.model.MicroBlogKey +import com.twidere.twiderex.model.enums.MediaType import com.twidere.twiderex.preferences.proto.DisplayPreferences import com.twidere.twiderex.ui.LocalNavController import com.twidere.twiderex.ui.LocalVideoPlayback diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/compose/ComposeScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/compose/ComposeScene.kt index 74ed12217..b71302ed2 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/compose/ComposeScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/compose/ComposeScene.kt @@ -126,7 +126,7 @@ import com.twidere.twiderex.extensions.stringName import com.twidere.twiderex.extensions.withElevation import com.twidere.twiderex.model.AccountDetails import com.twidere.twiderex.model.MicroBlogKey -import com.twidere.twiderex.model.PlatformType +import com.twidere.twiderex.model.enums.PlatformType import com.twidere.twiderex.model.ui.UiEmoji import com.twidere.twiderex.navigation.RootRoute import com.twidere.twiderex.ui.LocalActiveAccount diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/HomeMenus.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/HomeMenus.kt index 2891e10a1..9d0a9cf5b 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/HomeMenus.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/HomeMenus.kt @@ -20,7 +20,7 @@ */ package com.twidere.twiderex.scenes.home -import com.twidere.twiderex.model.PlatformType +import com.twidere.twiderex.model.enums.PlatformType import com.twidere.twiderex.scenes.home.mastodon.FederatedTimelineItem import com.twidere.twiderex.scenes.home.mastodon.LocalTimelineItem import com.twidere.twiderex.scenes.home.mastodon.MastodonNotificationItem diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/SearchItem.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/SearchItem.kt index a6d2236a7..0392f21a6 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/home/SearchItem.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/home/SearchItem.kt @@ -57,7 +57,7 @@ import com.twidere.twiderex.component.trend.MastodonTrendItem import com.twidere.twiderex.component.trend.TwitterTrendItem import com.twidere.twiderex.di.assisted.assistedViewModel import com.twidere.twiderex.extensions.observeAsState -import com.twidere.twiderex.model.PlatformType +import com.twidere.twiderex.model.enums.PlatformType import com.twidere.twiderex.navigation.RootRoute import com.twidere.twiderex.ui.LocalActiveAccount import com.twidere.twiderex.ui.TwidereScene diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/ListsAddMembersScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/ListsAddMembersScene.kt index 920039726..71dd62931 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/ListsAddMembersScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/ListsAddMembersScene.kt @@ -63,7 +63,7 @@ import com.twidere.twiderex.extensions.observeAsState import com.twidere.twiderex.extensions.refreshOrRetry import com.twidere.twiderex.extensions.viewModel import com.twidere.twiderex.model.MicroBlogKey -import com.twidere.twiderex.model.PlatformType +import com.twidere.twiderex.model.enums.PlatformType import com.twidere.twiderex.model.ui.UiUser import com.twidere.twiderex.ui.LocalActiveAccount import com.twidere.twiderex.ui.LocalNavController diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/ListsScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/ListsScene.kt index c45a7f102..48d6f0f92 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/ListsScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/ListsScene.kt @@ -43,7 +43,7 @@ import com.twidere.twiderex.component.foundation.InAppNotificationScaffold import com.twidere.twiderex.component.foundation.SwipeToRefreshLayout import com.twidere.twiderex.component.lazy.ui.LazyUiListsList import com.twidere.twiderex.di.assisted.assistedViewModel -import com.twidere.twiderex.model.PlatformType +import com.twidere.twiderex.model.enums.PlatformType import com.twidere.twiderex.navigation.RootRoute import com.twidere.twiderex.ui.LocalActiveAccount import com.twidere.twiderex.ui.LocalNavController diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/ListsTimelineScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/ListsTimelineScene.kt index 81dbaf055..3c975a342 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/ListsTimelineScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/lists/ListsTimelineScene.kt @@ -63,7 +63,7 @@ import com.twidere.twiderex.di.assisted.assistedViewModel import com.twidere.twiderex.extensions.observeAsState import com.twidere.twiderex.model.AccountDetails import com.twidere.twiderex.model.MicroBlogKey -import com.twidere.twiderex.model.PlatformType +import com.twidere.twiderex.model.enums.PlatformType import com.twidere.twiderex.navigation.RootRoute import com.twidere.twiderex.scenes.lists.platform.MastodonListsEditDialog import com.twidere.twiderex.ui.LocalActiveAccount diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/search/SearchScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/search/SearchScene.kt index 336ff935b..fe6b5e7ab 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/search/SearchScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/search/SearchScene.kt @@ -62,7 +62,7 @@ import com.twidere.twiderex.component.navigation.LocalNavigator import com.twidere.twiderex.di.assisted.assistedViewModel import com.twidere.twiderex.extensions.observeAsState import com.twidere.twiderex.extensions.withElevation -import com.twidere.twiderex.model.PlatformType +import com.twidere.twiderex.model.enums.PlatformType import com.twidere.twiderex.scenes.search.tabs.MastodonSearchHashtagItem import com.twidere.twiderex.scenes.search.tabs.SearchTweetsItem import com.twidere.twiderex.scenes.search.tabs.SearchUserItem diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/user/UserScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/user/UserScene.kt index cf8faaa1e..5ccdf1357 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/user/UserScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/user/UserScene.kt @@ -38,7 +38,7 @@ import com.twidere.twiderex.di.assisted.assistedViewModel import com.twidere.twiderex.extensions.observeAsState import com.twidere.twiderex.extensions.withElevation import com.twidere.twiderex.model.MicroBlogKey -import com.twidere.twiderex.model.PlatformType +import com.twidere.twiderex.model.enums.PlatformType import com.twidere.twiderex.navigation.RootRoute import com.twidere.twiderex.ui.LocalActiveAccount import com.twidere.twiderex.ui.LocalNavController diff --git a/app/src/main/kotlin/com/twidere/twiderex/utils/PlatformResolver.kt b/app/src/main/kotlin/com/twidere/twiderex/utils/PlatformResolver.kt index 218b7ceea..d32a78e55 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/utils/PlatformResolver.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/utils/PlatformResolver.kt @@ -23,7 +23,7 @@ package com.twidere.twiderex.utils import androidx.compose.runtime.staticCompositionLocalOf import com.twidere.twiderex.db.CacheDatabase import com.twidere.twiderex.model.MicroBlogKey -import com.twidere.twiderex.model.PlatformType +import com.twidere.twiderex.model.enums.PlatformType import javax.inject.Inject class PlatformResolver @Inject constructor( diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/ActiveAccountViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/ActiveAccountViewModel.kt index 96ce556de..f948faa52 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/ActiveAccountViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/ActiveAccountViewModel.kt @@ -22,7 +22,7 @@ package com.twidere.twiderex.viewmodel import androidx.lifecycle.ViewModel import com.twidere.twiderex.model.AccountDetails -import com.twidere.twiderex.model.PlatformType +import com.twidere.twiderex.model.enums.PlatformType import com.twidere.twiderex.repository.AccountRepository import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/compose/ComposeViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/compose/ComposeViewModel.kt index 3a052b304..acb5cbf95 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/compose/ComposeViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/compose/ComposeViewModel.kt @@ -45,9 +45,9 @@ import com.twidere.twiderex.extensions.getCachedLocation import com.twidere.twiderex.extensions.getTextAfterSelection import com.twidere.twiderex.extensions.getTextBeforeSelection import com.twidere.twiderex.model.AccountDetails -import com.twidere.twiderex.model.ComposeData import com.twidere.twiderex.model.MicroBlogKey -import com.twidere.twiderex.model.PlatformType +import com.twidere.twiderex.model.enums.PlatformType +import com.twidere.twiderex.model.job.ComposeData import com.twidere.twiderex.model.ui.UiUser import com.twidere.twiderex.notification.InAppNotification import com.twidere.twiderex.repository.DraftRepository diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/dm/DMEventViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/dm/DMEventViewModel.kt index 65e31ff23..4da2aa555 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/dm/DMEventViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/dm/DMEventViewModel.kt @@ -28,10 +28,10 @@ import com.twidere.services.microblog.DirectMessageService import com.twidere.services.microblog.LookupService import com.twidere.twiderex.action.DirectMessageAction import com.twidere.twiderex.model.AccountDetails -import com.twidere.twiderex.model.DirectMessageDeleteData -import com.twidere.twiderex.model.DirectMessageSendData import com.twidere.twiderex.model.MicroBlogKey -import com.twidere.twiderex.model.PlatformType +import com.twidere.twiderex.model.enums.PlatformType +import com.twidere.twiderex.model.job.DirectMessageDeleteData +import com.twidere.twiderex.model.job.DirectMessageSendData import com.twidere.twiderex.model.ui.UiDMEvent import com.twidere.twiderex.repository.DirectMessageRepository import dagger.assisted.Assisted diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/mastodon/MastodonSignInViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/mastodon/MastodonSignInViewModel.kt index 4ebd29af4..95baeaa4d 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/mastodon/MastodonSignInViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/mastodon/MastodonSignInViewModel.kt @@ -28,9 +28,9 @@ import com.twidere.services.mastodon.MastodonOAuthService import com.twidere.twiderex.db.mapper.toDbUser import com.twidere.twiderex.http.TwidereServiceFactory import com.twidere.twiderex.model.MicroBlogKey -import com.twidere.twiderex.model.PlatformType import com.twidere.twiderex.model.cred.CredentialsType import com.twidere.twiderex.model.cred.OAuth2Credentials +import com.twidere.twiderex.model.enums.PlatformType import com.twidere.twiderex.model.toAmUser import com.twidere.twiderex.navigation.RootDeepLinksRoute import com.twidere.twiderex.notification.InAppNotification diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/twitter/TwitterSignInViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/twitter/TwitterSignInViewModel.kt index 785a1f105..35949ba57 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/twitter/TwitterSignInViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/twitter/TwitterSignInViewModel.kt @@ -30,9 +30,9 @@ import com.twidere.twiderex.BuildConfig import com.twidere.twiderex.db.mapper.toDbUser import com.twidere.twiderex.http.TwidereServiceFactory import com.twidere.twiderex.model.MicroBlogKey -import com.twidere.twiderex.model.PlatformType import com.twidere.twiderex.model.cred.CredentialsType import com.twidere.twiderex.model.cred.OAuthCredentials +import com.twidere.twiderex.model.enums.PlatformType import com.twidere.twiderex.model.toAmUser import com.twidere.twiderex.navigation.RootDeepLinksRoute import com.twidere.twiderex.notification.InAppNotification diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/compose/ComposeWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/compose/ComposeWorker.kt index ed58a5d60..87e2df8ac 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/compose/ComposeWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/compose/ComposeWorker.kt @@ -26,7 +26,7 @@ import androidx.work.WorkerParameters import com.twidere.services.microblog.MicroBlogService import com.twidere.twiderex.jobs.compose.ComposeJob import com.twidere.twiderex.model.MicroBlogKey -import com.twidere.twiderex.model.toComposeData +import com.twidere.twiderex.model.transform.toComposeData abstract class ComposeWorker( protected val context: Context, diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/compose/MastodonComposeWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/compose/MastodonComposeWorker.kt index e4e7506b0..121c47144 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/compose/MastodonComposeWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/compose/MastodonComposeWorker.kt @@ -27,9 +27,9 @@ import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkerParameters import com.twidere.services.mastodon.MastodonService import com.twidere.twiderex.jobs.compose.MastodonComposeJob -import com.twidere.twiderex.model.ComposeData import com.twidere.twiderex.model.MicroBlogKey -import com.twidere.twiderex.model.toWorkData +import com.twidere.twiderex.model.job.ComposeData +import com.twidere.twiderex.model.transform.toWorkData import dagger.assisted.Assisted import dagger.assisted.AssistedInject diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/compose/TwitterComposeWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/compose/TwitterComposeWorker.kt index 6a4ebf967..7672c6e42 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/compose/TwitterComposeWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/compose/TwitterComposeWorker.kt @@ -27,9 +27,9 @@ import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkerParameters import com.twidere.services.twitter.TwitterService import com.twidere.twiderex.jobs.compose.TwitterComposeJob -import com.twidere.twiderex.model.ComposeData import com.twidere.twiderex.model.MicroBlogKey -import com.twidere.twiderex.model.toWorkData +import com.twidere.twiderex.model.job.ComposeData +import com.twidere.twiderex.model.transform.toWorkData import dagger.assisted.Assisted import dagger.assisted.AssistedInject diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageDeleteWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageDeleteWorker.kt index 20993a446..6871ce915 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageDeleteWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageDeleteWorker.kt @@ -26,10 +26,10 @@ import androidx.work.CoroutineWorker import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkerParameters import com.twidere.twiderex.jobs.dm.DirectMessageDeleteJob -import com.twidere.twiderex.model.DirectMessageDeleteData import com.twidere.twiderex.model.MicroBlogKey -import com.twidere.twiderex.model.toDirectMessageDeleteData -import com.twidere.twiderex.model.toWorkData +import com.twidere.twiderex.model.job.DirectMessageDeleteData +import com.twidere.twiderex.model.transform.toDirectMessageDeleteData +import com.twidere.twiderex.model.transform.toWorkData import dagger.assisted.Assisted import dagger.assisted.AssistedInject diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageSendWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageSendWorker.kt index 4519f2653..81f88e3ed 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageSendWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/dm/DirectMessageSendWorker.kt @@ -25,7 +25,7 @@ import androidx.work.CoroutineWorker import androidx.work.WorkerParameters import com.twidere.twiderex.jobs.dm.DirectMessageSendJob import com.twidere.twiderex.model.MicroBlogKey -import com.twidere.twiderex.model.toDirectMessageSendData +import com.twidere.twiderex.model.transform.toDirectMessageSendData abstract class DirectMessageSendWorker( context: Context, diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/dm/TwitterDirectMessageSendWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/dm/TwitterDirectMessageSendWorker.kt index 18a68b3ed..b8f72dd07 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/dm/TwitterDirectMessageSendWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/dm/TwitterDirectMessageSendWorker.kt @@ -26,8 +26,8 @@ import androidx.work.Data import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkerParameters import com.twidere.twiderex.jobs.dm.TwitterDirectMessageSendJob -import com.twidere.twiderex.model.DirectMessageSendData -import com.twidere.twiderex.model.toWorkData +import com.twidere.twiderex.model.job.DirectMessageSendData +import com.twidere.twiderex.model.transform.toWorkData import dagger.assisted.Assisted import dagger.assisted.AssistedInject diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/draft/SaveDraftWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/draft/SaveDraftWorker.kt index 8b210e10a..3903e5fcc 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/draft/SaveDraftWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/draft/SaveDraftWorker.kt @@ -26,9 +26,9 @@ import androidx.work.CoroutineWorker import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkerParameters import com.twidere.twiderex.jobs.draft.SaveDraftJob -import com.twidere.twiderex.model.ComposeData -import com.twidere.twiderex.model.toComposeData -import com.twidere.twiderex.model.toWorkData +import com.twidere.twiderex.model.job.ComposeData +import com.twidere.twiderex.model.transform.toComposeData +import com.twidere.twiderex.model.transform.toWorkData import dagger.assisted.Assisted import dagger.assisted.AssistedInject diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/status/StatusWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/status/StatusWorker.kt index 726c3ceab..457e4d059 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/status/StatusWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/status/StatusWorker.kt @@ -27,6 +27,7 @@ import androidx.work.WorkerParameters import androidx.work.workDataOf import com.twidere.twiderex.jobs.status.StatusJob import com.twidere.twiderex.model.MicroBlogKey +import com.twidere.twiderex.model.transform.toWorkData import com.twidere.twiderex.model.ui.UiStatus abstract class StatusWorker( diff --git a/app/src/main/kotlin/com/twidere/twiderex/worker/status/UpdateStatusWorker.kt b/app/src/main/kotlin/com/twidere/twiderex/worker/status/UpdateStatusWorker.kt index 49d179deb..83b497750 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/worker/status/UpdateStatusWorker.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/worker/status/UpdateStatusWorker.kt @@ -30,7 +30,8 @@ import androidx.work.setInputMerger import com.twidere.twiderex.extensions.getNullableBoolean import com.twidere.twiderex.extensions.getNullableLong import com.twidere.twiderex.model.MicroBlogKey -import com.twidere.twiderex.model.StatusResult +import com.twidere.twiderex.model.job.StatusResult +import com.twidere.twiderex.model.transform.toWorkData import com.twidere.twiderex.repository.ReactionRepository import com.twidere.twiderex.repository.StatusRepository import dagger.assisted.Assisted From 0ff9c6dfc6d489cd60583299b5d44b8abe4beadd Mon Sep 17 00:00:00 2001 From: itsMimao Date: Tue, 3 Aug 2021 16:31:24 +0800 Subject: [PATCH 112/137] move transfrom code from UiConversation/Event/Emoji --- .../transform/DbDmConversationTransform.kt | 65 +++++++++++++++++++ .../model/transform/DbEmojiTransform.kt | 40 ++++++++++++ .../twiderex/model/ui/UiDMConversation.kt | 27 +------- .../twidere/twiderex/model/ui/UiDMEvent.kt | 24 ------- .../ui/{UiEmoji.kt => UiEmojiCategory.kt} | 20 +++--- .../mediator/dm/DMConversationMediator.kt | 2 +- .../paging/mediator/dm/DMEventMediator.kt | 2 +- .../repository/DirectMessageRepository.kt | 3 +- .../twiderex/scenes/compose/ComposeScene.kt | 4 +- .../twiderex/utils/MastodonEmojiCache.kt | 8 +-- .../viewmodel/compose/ComposeViewModel.kt | 4 +- 11 files changed, 128 insertions(+), 71 deletions(-) create mode 100644 app/src/main/kotlin/com/twidere/twiderex/model/transform/DbDmConversationTransform.kt create mode 100644 app/src/main/kotlin/com/twidere/twiderex/model/transform/DbEmojiTransform.kt rename app/src/main/kotlin/com/twidere/twiderex/model/ui/{UiEmoji.kt => UiEmojiCategory.kt} (75%) diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/transform/DbDmConversationTransform.kt b/app/src/main/kotlin/com/twidere/twiderex/model/transform/DbDmConversationTransform.kt new file mode 100644 index 000000000..0785c4d2b --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/model/transform/DbDmConversationTransform.kt @@ -0,0 +1,65 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.model.transform + +import com.twidere.twiderex.db.model.DbDMConversation +import com.twidere.twiderex.db.model.DbDMEventWithAttachments +import com.twidere.twiderex.db.model.DbDirectMessageConversationWithMessage +import com.twidere.twiderex.model.ui.UiDMConversation +import com.twidere.twiderex.model.ui.UiDMConversationWithLatestMessage +import com.twidere.twiderex.model.ui.UiDMEvent +import com.twidere.twiderex.model.ui.UiMedia.Companion.toUi +import com.twidere.twiderex.model.ui.UiUrlEntity.Companion.toUi +import com.twidere.twiderex.model.ui.UiUser.Companion.toUi + +fun DbDMConversation.toUi() = UiDMConversation( + accountKey = accountKey, + conversationId = conversationId, + conversationKey = conversationKey, + conversationAvatar = conversationAvatar, + conversationName = conversationName, + conversationSubName = conversationSubName, + conversationType = conversationType, + recipientKey = recipientKey, +) + +fun DbDirectMessageConversationWithMessage.toUi() = UiDMConversationWithLatestMessage( + conversation = conversation.toUi(), + latestMessage = latestMessage.toUi() +) + +fun DbDMEventWithAttachments.toUi() = UiDMEvent( + accountKey = message.accountKey, + sortId = message.sortId, + conversationKey = message.conversationKey, + messageId = message.messageId, + messageKey = message.messageKey, + htmlText = message.htmlText, + originText = message.originText, + createdTimestamp = message.createdTimestamp, + messageType = message.messageType, + senderAccountKey = message.senderAccountKey, + recipientAccountKey = message.recipientAccountKey, + sendStatus = message.sendStatus, + media = media.toUi(), + urlEntity = urlEntity.toUi(), + sender = sender.toUi() +) diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/transform/DbEmojiTransform.kt b/app/src/main/kotlin/com/twidere/twiderex/model/transform/DbEmojiTransform.kt new file mode 100644 index 000000000..c886ebcf1 --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/model/transform/DbEmojiTransform.kt @@ -0,0 +1,40 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.model.transform + +import com.twidere.services.mastodon.model.Emoji +import com.twidere.twiderex.model.ui.UiEmoji +import com.twidere.twiderex.model.ui.UiEmojiCategory + +fun List.toUi(): List = groupBy({ it.category }, { it }).map { + UiEmojiCategory( + if (it.key.isNullOrEmpty()) null else it.key, + it.value.map { emoji -> + UiEmoji( + shortcode = emoji.shortcode, + url = emoji.url, + staticURL = emoji.staticURL, + visibleInPicker = emoji.visibleInPicker, + category = emoji.category + ) + } + ) +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiDMConversation.kt b/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiDMConversation.kt index 0f258da98..2fe028df2 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiDMConversation.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiDMConversation.kt @@ -21,10 +21,7 @@ package com.twidere.twiderex.model.ui import com.twidere.twiderex.db.model.DbDMConversation -import com.twidere.twiderex.db.model.DbDirectMessageConversationWithMessage import com.twidere.twiderex.model.MicroBlogKey -import com.twidere.twiderex.model.ui.UiDMConversation.Companion.toUi -import com.twidere.twiderex.model.ui.UiDMEvent.Companion.toUi data class UiDMConversation( val accountKey: MicroBlogKey, @@ -36,29 +33,9 @@ data class UiDMConversation( val conversationSubName: String, val conversationType: DbDMConversation.Type, val recipientKey: MicroBlogKey, -) { - companion object { - fun DbDMConversation.toUi() = UiDMConversation( - accountKey = accountKey, - conversationId = conversationId, - conversationKey = conversationKey, - conversationAvatar = conversationAvatar, - conversationName = conversationName, - conversationSubName = conversationSubName, - conversationType = conversationType, - recipientKey = recipientKey, - ) - } -} +) data class UiDMConversationWithLatestMessage( val conversation: UiDMConversation, val latestMessage: UiDMEvent -) { - companion object { - fun DbDirectMessageConversationWithMessage.toUi() = UiDMConversationWithLatestMessage( - conversation = conversation.toUi(), - latestMessage = latestMessage.toUi() - ) - } -} +) diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiDMEvent.kt b/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiDMEvent.kt index 13a084532..4972e7372 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiDMEvent.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiDMEvent.kt @@ -21,11 +21,7 @@ package com.twidere.twiderex.model.ui import com.twidere.twiderex.db.model.DbDMEvent -import com.twidere.twiderex.db.model.DbDMEventWithAttachments import com.twidere.twiderex.model.MicroBlogKey -import com.twidere.twiderex.model.ui.UiMedia.Companion.toUi -import com.twidere.twiderex.model.ui.UiUrlEntity.Companion.toUi -import com.twidere.twiderex.model.ui.UiUser.Companion.toUi data class UiDMEvent( val accountKey: MicroBlogKey, @@ -48,24 +44,4 @@ data class UiDMEvent( ) { val isInCome: Boolean get() = recipientAccountKey == accountKey - - companion object { - fun DbDMEventWithAttachments.toUi() = UiDMEvent( - accountKey = message.accountKey, - sortId = message.sortId, - conversationKey = message.conversationKey, - messageId = message.messageId, - messageKey = message.messageKey, - htmlText = message.htmlText, - originText = message.originText, - createdTimestamp = message.createdTimestamp, - messageType = message.messageType, - senderAccountKey = message.senderAccountKey, - recipientAccountKey = message.recipientAccountKey, - sendStatus = message.sendStatus, - media = media.toUi(), - urlEntity = urlEntity.toUi(), - sender = sender.toUi() - ) - } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiEmoji.kt b/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiEmojiCategory.kt similarity index 75% rename from app/src/main/kotlin/com/twidere/twiderex/model/ui/UiEmoji.kt rename to app/src/main/kotlin/com/twidere/twiderex/model/ui/UiEmojiCategory.kt index 7c157a0f6..d041c6290 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiEmoji.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiEmojiCategory.kt @@ -20,15 +20,15 @@ */ package com.twidere.twiderex.model.ui -import com.twidere.services.mastodon.model.Emoji +data class UiEmojiCategory( + val category: String?, + val emoji: List +) data class UiEmoji( - val category: String?, - val emoji: List -) { - companion object { - fun List.toUi(): List = groupBy({ it.category }, { it }).map { - UiEmoji(if (it.key.isNullOrEmpty()) null else it.key, it.value) - } - } -} + val shortcode: String? = null, + val url: String? = null, + val staticURL: String? = null, + val visibleInPicker: Boolean? = null, + val category: String? = null +) diff --git a/app/src/main/kotlin/com/twidere/twiderex/paging/mediator/dm/DMConversationMediator.kt b/app/src/main/kotlin/com/twidere/twiderex/paging/mediator/dm/DMConversationMediator.kt index eacca6408..8bea147a7 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/paging/mediator/dm/DMConversationMediator.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/paging/mediator/dm/DMConversationMediator.kt @@ -31,8 +31,8 @@ import com.twidere.twiderex.db.CacheDatabase import com.twidere.twiderex.db.model.DbDirectMessageConversationWithMessage import com.twidere.twiderex.defaultLoadCount import com.twidere.twiderex.model.MicroBlogKey +import com.twidere.twiderex.model.transform.toUi import com.twidere.twiderex.model.ui.UiDMConversationWithLatestMessage -import com.twidere.twiderex.model.ui.UiDMConversationWithLatestMessage.Companion.toUi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map diff --git a/app/src/main/kotlin/com/twidere/twiderex/paging/mediator/dm/DMEventMediator.kt b/app/src/main/kotlin/com/twidere/twiderex/paging/mediator/dm/DMEventMediator.kt index 5e021a4d2..53b01a4cf 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/paging/mediator/dm/DMEventMediator.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/paging/mediator/dm/DMEventMediator.kt @@ -31,8 +31,8 @@ import com.twidere.twiderex.db.CacheDatabase import com.twidere.twiderex.db.model.DbDMEventWithAttachments import com.twidere.twiderex.defaultLoadCount import com.twidere.twiderex.model.MicroBlogKey +import com.twidere.twiderex.model.transform.toUi import com.twidere.twiderex.model.ui.UiDMEvent -import com.twidere.twiderex.model.ui.UiDMEvent.Companion.toUi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map diff --git a/app/src/main/kotlin/com/twidere/twiderex/repository/DirectMessageRepository.kt b/app/src/main/kotlin/com/twidere/twiderex/repository/DirectMessageRepository.kt index a0fe9bb4c..d46743908 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/repository/DirectMessageRepository.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/repository/DirectMessageRepository.kt @@ -36,10 +36,9 @@ import com.twidere.twiderex.db.model.DbDMEventWithAttachments.Companion.saveToDb import com.twidere.twiderex.db.model.DbUser import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.enums.PlatformType +import com.twidere.twiderex.model.transform.toUi import com.twidere.twiderex.model.ui.UiDMConversation -import com.twidere.twiderex.model.ui.UiDMConversation.Companion.toUi import com.twidere.twiderex.model.ui.UiDMConversationWithLatestMessage -import com.twidere.twiderex.model.ui.UiDMConversationWithLatestMessage.Companion.toUi import com.twidere.twiderex.model.ui.UiDMEvent import com.twidere.twiderex.model.ui.UiUser import com.twidere.twiderex.paging.mediator.dm.DMConversationMediator diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/compose/ComposeScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/compose/ComposeScene.kt index b71302ed2..08cd300c1 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/compose/ComposeScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/compose/ComposeScene.kt @@ -127,7 +127,7 @@ import com.twidere.twiderex.extensions.withElevation import com.twidere.twiderex.model.AccountDetails import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.enums.PlatformType -import com.twidere.twiderex.model.ui.UiEmoji +import com.twidere.twiderex.model.ui.UiEmojiCategory import com.twidere.twiderex.navigation.RootRoute import com.twidere.twiderex.ui.LocalActiveAccount import com.twidere.twiderex.ui.LocalNavController @@ -523,7 +523,7 @@ fun EmojiPanel( @ExperimentalFoundationApi @Composable private fun EmojiList( - items: List, + items: List, viewModel: ComposeViewModel ) { BoxWithConstraints(modifier = Modifier.padding(EmojiListDefaults.ContentPadding)) { diff --git a/app/src/main/kotlin/com/twidere/twiderex/utils/MastodonEmojiCache.kt b/app/src/main/kotlin/com/twidere/twiderex/utils/MastodonEmojiCache.kt index e88d472f8..32e13af4e 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/utils/MastodonEmojiCache.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/utils/MastodonEmojiCache.kt @@ -22,14 +22,14 @@ package com.twidere.twiderex.utils import com.twidere.services.mastodon.MastodonService import com.twidere.twiderex.model.AccountDetails -import com.twidere.twiderex.model.ui.UiEmoji -import com.twidere.twiderex.model.ui.UiEmoji.Companion.toUi +import com.twidere.twiderex.model.transform.toUi +import com.twidere.twiderex.model.ui.UiEmojiCategory import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow object MastodonEmojiCache { - private val items = hashMapOf>>() - fun get(account: AccountDetails): Flow> { + private val items = hashMapOf>>() + fun get(account: AccountDetails): Flow> { return items.getOrPut(account.accountKey.host) { flow { account.service.let { diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/compose/ComposeViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/compose/ComposeViewModel.kt index acb5cbf95..818f43a3f 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/compose/ComposeViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/compose/ComposeViewModel.kt @@ -35,7 +35,6 @@ import androidx.compose.ui.text.input.TextFieldValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.work.WorkManager -import com.twidere.services.mastodon.model.Emoji import com.twidere.services.mastodon.model.Visibility import com.twidere.services.microblog.LookupService import com.twidere.twiderex.R @@ -48,6 +47,7 @@ import com.twidere.twiderex.model.AccountDetails import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.enums.PlatformType import com.twidere.twiderex.model.job.ComposeData +import com.twidere.twiderex.model.ui.UiEmoji import com.twidere.twiderex.model.ui.UiUser import com.twidere.twiderex.notification.InAppNotification import com.twidere.twiderex.repository.DraftRepository @@ -478,7 +478,7 @@ open class ComposeViewModel @AssistedInject constructor( } } - fun insertEmoji(emoji: Emoji) { + fun insertEmoji(emoji: UiEmoji) { insertText("${if (textFieldValue.value.selection.start != 0) " " else ""}:${emoji.shortcode}: ") } } From ce3cf41dc7d9917b2a7731083945278ad496813d Mon Sep 17 00:00:00 2001 From: itsMimao Date: Tue, 3 Aug 2021 16:56:47 +0800 Subject: [PATCH 113/137] move transform code from ui models --- .../component/status/UserScreenName.kt | 3 +- ...ransform.kt => DmConversationTransform.kt} | 2 - ...{DbEmojiTransform.kt => EmojiTransform.kt} | 0 .../twiderex/model/transform/ListTransform.kt | 38 ++++++++++++++++ .../model/transform/MediaTransform.kt | 37 ++++++++++++++++ .../model/transform/TrendTransform.kt | 44 +++++++++++++++++++ .../model/transform/UrlEntityTransform.kt | 29 ++++++++++++ .../com/twidere/twiderex/model/ui/UiList.kt | 15 ------- .../com/twidere/twiderex/model/ui/UiMedia.kt | 14 ------ .../com/twidere/twiderex/model/ui/UiStatus.kt | 3 +- .../com/twidere/twiderex/model/ui/UiTrend.kt | 27 +----------- .../twidere/twiderex/model/ui/UiUrlEntity.kt | 10 +---- .../com/twidere/twiderex/model/ui/UiUser.kt | 11 +---- .../paging/mediator/list/ListsMediator.kt | 2 +- .../paging/mediator/trend/TrendMediator.kt | 2 +- .../twiderex/repository/ListsRepository.kt | 2 +- .../twiderex/repository/MediaRepository.kt | 2 +- .../twiderex/repository/TrendRepository.kt | 1 - .../scenes/compose/ComposeSearchUserScene.kt | 2 +- .../viewmodel/compose/ComposeViewModel.kt | 2 +- 20 files changed, 161 insertions(+), 85 deletions(-) rename app/src/main/kotlin/com/twidere/twiderex/model/transform/{DbDmConversationTransform.kt => DmConversationTransform.kt} (95%) rename app/src/main/kotlin/com/twidere/twiderex/model/transform/{DbEmojiTransform.kt => EmojiTransform.kt} (100%) create mode 100644 app/src/main/kotlin/com/twidere/twiderex/model/transform/ListTransform.kt create mode 100644 app/src/main/kotlin/com/twidere/twiderex/model/transform/MediaTransform.kt create mode 100644 app/src/main/kotlin/com/twidere/twiderex/model/transform/TrendTransform.kt create mode 100644 app/src/main/kotlin/com/twidere/twiderex/model/transform/UrlEntityTransform.kt diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/status/UserScreenName.kt b/app/src/main/kotlin/com/twidere/twiderex/component/status/UserScreenName.kt index dfdcd1385..58ddfc8b7 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/status/UserScreenName.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/status/UserScreenName.kt @@ -37,10 +37,11 @@ import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.TextUnit import com.twidere.twiderex.model.ui.UiUser +import com.twidere.twiderex.ui.LocalActiveAccount @Composable fun UserScreenName(user: UiUser) { - UserScreenName(name = user.displayScreenName) + UserScreenName(name = user.getDisplayScreenName(LocalActiveAccount.current?.accountKey?.host)) } @Composable diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/transform/DbDmConversationTransform.kt b/app/src/main/kotlin/com/twidere/twiderex/model/transform/DmConversationTransform.kt similarity index 95% rename from app/src/main/kotlin/com/twidere/twiderex/model/transform/DbDmConversationTransform.kt rename to app/src/main/kotlin/com/twidere/twiderex/model/transform/DmConversationTransform.kt index 0785c4d2b..85e67bf71 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/model/transform/DbDmConversationTransform.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/model/transform/DmConversationTransform.kt @@ -26,8 +26,6 @@ import com.twidere.twiderex.db.model.DbDirectMessageConversationWithMessage import com.twidere.twiderex.model.ui.UiDMConversation import com.twidere.twiderex.model.ui.UiDMConversationWithLatestMessage import com.twidere.twiderex.model.ui.UiDMEvent -import com.twidere.twiderex.model.ui.UiMedia.Companion.toUi -import com.twidere.twiderex.model.ui.UiUrlEntity.Companion.toUi import com.twidere.twiderex.model.ui.UiUser.Companion.toUi fun DbDMConversation.toUi() = UiDMConversation( diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/transform/DbEmojiTransform.kt b/app/src/main/kotlin/com/twidere/twiderex/model/transform/EmojiTransform.kt similarity index 100% rename from app/src/main/kotlin/com/twidere/twiderex/model/transform/DbEmojiTransform.kt rename to app/src/main/kotlin/com/twidere/twiderex/model/transform/EmojiTransform.kt diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/transform/ListTransform.kt b/app/src/main/kotlin/com/twidere/twiderex/model/transform/ListTransform.kt new file mode 100644 index 000000000..7ff1f131f --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/model/transform/ListTransform.kt @@ -0,0 +1,38 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.model.transform + +import com.twidere.twiderex.db.model.DbList +import com.twidere.twiderex.model.ui.UiList + +fun DbList.toUi() = + UiList( + id = listId, + ownerId = ownerId, + listKey = listKey, + accountKey = accountKey, + title = title, + descriptions = description, + mode = mode, + replyPolicy = replyPolicy, + isFollowed = isFollowed, + allowToSubscribe = allowToSubscribe, + ) diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/transform/MediaTransform.kt b/app/src/main/kotlin/com/twidere/twiderex/model/transform/MediaTransform.kt new file mode 100644 index 000000000..acae8388e --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/model/transform/MediaTransform.kt @@ -0,0 +1,37 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.model.transform + +import com.twidere.twiderex.db.model.DbMedia +import com.twidere.twiderex.model.ui.UiMedia + +fun List.toUi() = sortedBy { it.order }.map { + UiMedia( + url = it.url, + mediaUrl = it.mediaUrl, + previewUrl = it.previewUrl, + type = it.type, + width = it.width, + height = it.height, + pageUrl = it.pageUrl, + altText = it.altText, + ) +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/transform/TrendTransform.kt b/app/src/main/kotlin/com/twidere/twiderex/model/transform/TrendTransform.kt new file mode 100644 index 000000000..485c4ad42 --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/model/transform/TrendTransform.kt @@ -0,0 +1,44 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.model.transform + +import com.twidere.twiderex.db.model.DbTrendHistory +import com.twidere.twiderex.db.model.DbTrendWithHistory +import com.twidere.twiderex.model.ui.UiTrend +import com.twidere.twiderex.model.ui.UiTrendHistory + +fun DbTrendWithHistory.toUi() = UiTrend( + trendKey = trend.trendKey, + displayName = trend.displayName, + url = trend.url, + query = trend.query, + volume = trend.volume, + history = history.map { + it.toUi() + } +) + +fun DbTrendHistory.toUi() = UiTrendHistory( + trendKey = trendKey, + day = day, + uses = uses, + accounts = accounts +) diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/transform/UrlEntityTransform.kt b/app/src/main/kotlin/com/twidere/twiderex/model/transform/UrlEntityTransform.kt new file mode 100644 index 000000000..3850f8324 --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/model/transform/UrlEntityTransform.kt @@ -0,0 +1,29 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.model.transform + +import com.twidere.twiderex.db.model.DbUrlEntity +import com.twidere.twiderex.model.ui.UiUrlEntity + +fun DbUrlEntity.toUi() = UiUrlEntity( + url, expandedUrl, displayUrl, title, description, image +) +fun List.toUi() = map { it.toUi() } diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiList.kt b/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiList.kt index 5aa46526f..9d523c4a6 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiList.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiList.kt @@ -20,7 +20,6 @@ */ package com.twidere.twiderex.model.ui import androidx.compose.runtime.Immutable -import com.twidere.twiderex.db.model.DbList import com.twidere.twiderex.model.MicroBlogKey @Immutable @@ -58,20 +57,6 @@ data class UiList( isFollowed = isFollowed, allowToSubscribe = true, ) - - fun DbList.toUi() = - UiList( - id = listId, - ownerId = ownerId, - listKey = listKey, - accountKey = accountKey, - title = title, - descriptions = description, - mode = mode, - replyPolicy = replyPolicy, - isFollowed = isFollowed, - allowToSubscribe = allowToSubscribe, - ) } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiMedia.kt b/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiMedia.kt index 4b4d25423..0c959c218 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiMedia.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiMedia.kt @@ -25,7 +25,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.ui.res.painterResource import com.twidere.twiderex.R -import com.twidere.twiderex.db.model.DbMedia import com.twidere.twiderex.model.enums.MediaType @Immutable @@ -73,18 +72,5 @@ data class UiMedia( altText = "", ), ) - - fun List.toUi() = sortedBy { it.order }.map { - UiMedia( - url = it.url, - mediaUrl = it.mediaUrl, - previewUrl = it.previewUrl, - type = it.type, - width = it.width, - height = it.height, - pageUrl = it.pageUrl, - altText = it.altText, - ) - } } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiStatus.kt b/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiStatus.kt index c86a64505..edfb37ef9 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiStatus.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiStatus.kt @@ -34,8 +34,7 @@ import com.twidere.twiderex.db.model.ReferenceType import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.enums.MastodonStatusType import com.twidere.twiderex.model.enums.PlatformType -import com.twidere.twiderex.model.ui.UiMedia.Companion.toUi -import com.twidere.twiderex.model.ui.UiUrlEntity.Companion.toUi +import com.twidere.twiderex.model.transform.toUi import com.twidere.twiderex.model.ui.UiUser.Companion.toUi @Immutable diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiTrend.kt b/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiTrend.kt index 765e58d4d..9e96f4cb5 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiTrend.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiTrend.kt @@ -20,10 +20,7 @@ */ package com.twidere.twiderex.model.ui -import com.twidere.twiderex.db.model.DbTrendHistory -import com.twidere.twiderex.db.model.DbTrendWithHistory import com.twidere.twiderex.model.MicroBlogKey -import com.twidere.twiderex.model.ui.UiTrendHistory.Companion.toUi data class UiTrend( val trendKey: MicroBlogKey, @@ -39,19 +36,6 @@ data class UiTrend( val dailyUses: Long get() = if (sortedHistory.isNotEmpty()) sortedHistory[0].uses else 0L - - companion object { - fun DbTrendWithHistory.toUi() = UiTrend( - trendKey = trend.trendKey, - displayName = trend.displayName, - url = trend.url, - query = trend.query, - volume = trend.volume, - history = history.map { - it.toUi() - } - ) - } } data class UiTrendHistory( @@ -59,13 +43,4 @@ data class UiTrendHistory( val day: Long, val uses: Long, val accounts: Long -) { - companion object { - fun DbTrendHistory.toUi() = UiTrendHistory( - trendKey = trendKey, - day = day, - uses = uses, - accounts = accounts - ) - } -} +) diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiUrlEntity.kt b/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiUrlEntity.kt index ce2d30398..aad89523d 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiUrlEntity.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiUrlEntity.kt @@ -21,7 +21,6 @@ package com.twidere.twiderex.model.ui import androidx.compose.runtime.Immutable -import com.twidere.twiderex.db.model.DbUrlEntity @Immutable data class UiUrlEntity( @@ -31,11 +30,4 @@ data class UiUrlEntity( val title: String?, val description: String?, val image: String?, -) { - companion object { - fun DbUrlEntity.toUi() = UiUrlEntity( - url, expandedUrl, displayUrl, title, description, image - ) - fun List.toUi() = map { it.toUi() } - } -} +) diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiUser.kt b/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiUser.kt index 35f92f9c3..697b7a24c 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiUser.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiUser.kt @@ -27,10 +27,8 @@ import com.twidere.twiderex.R import com.twidere.twiderex.db.model.DbMastodonUserExtra import com.twidere.twiderex.db.model.DbTwitterUserExtra import com.twidere.twiderex.db.model.DbUser -import com.twidere.twiderex.model.AccountDetails import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.enums.PlatformType -import com.twidere.twiderex.ui.LocalActiveAccount @Immutable data class UiUser( @@ -57,14 +55,9 @@ data class UiUser( ) { val displayName get() = name.takeUnless { it.isEmpty() } ?: screenName - val displayScreenName: String - @Composable - get() { - return LocalActiveAccount.current?.let { getDisplayScreenName(it) } ?: "@$screenName" - } - fun getDisplayScreenName(accountDetails: AccountDetails): String { - return if (accountDetails.accountKey.host.let { it != acct.host }) { + fun getDisplayScreenName(host: String?): String { + return if (host != null && host != acct.host) { "@$screenName@${acct.host}" } else { "@$screenName" diff --git a/app/src/main/kotlin/com/twidere/twiderex/paging/mediator/list/ListsMediator.kt b/app/src/main/kotlin/com/twidere/twiderex/paging/mediator/list/ListsMediator.kt index e3b8e9adc..9657e8c64 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/paging/mediator/list/ListsMediator.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/paging/mediator/list/ListsMediator.kt @@ -35,8 +35,8 @@ import com.twidere.twiderex.db.mapper.toDbList import com.twidere.twiderex.db.model.DbList import com.twidere.twiderex.defaultLoadCount import com.twidere.twiderex.model.MicroBlogKey +import com.twidere.twiderex.model.transform.toUi import com.twidere.twiderex.model.ui.UiList -import com.twidere.twiderex.model.ui.UiList.Companion.toUi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map diff --git a/app/src/main/kotlin/com/twidere/twiderex/paging/mediator/trend/TrendMediator.kt b/app/src/main/kotlin/com/twidere/twiderex/paging/mediator/trend/TrendMediator.kt index 215a73f70..70e26db02 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/paging/mediator/trend/TrendMediator.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/paging/mediator/trend/TrendMediator.kt @@ -36,8 +36,8 @@ import com.twidere.twiderex.db.model.DbTrendWithHistory import com.twidere.twiderex.db.model.saveToDb import com.twidere.twiderex.defaultLoadCount import com.twidere.twiderex.model.MicroBlogKey +import com.twidere.twiderex.model.transform.toUi import com.twidere.twiderex.model.ui.UiTrend -import com.twidere.twiderex.model.ui.UiTrend.Companion.toUi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map diff --git a/app/src/main/kotlin/com/twidere/twiderex/repository/ListsRepository.kt b/app/src/main/kotlin/com/twidere/twiderex/repository/ListsRepository.kt index b9ac9c69d..902cb62fd 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/repository/ListsRepository.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/repository/ListsRepository.kt @@ -26,9 +26,9 @@ import com.twidere.twiderex.db.CacheDatabase import com.twidere.twiderex.db.mapper.toDbList import com.twidere.twiderex.model.AccountDetails import com.twidere.twiderex.model.MicroBlogKey +import com.twidere.twiderex.model.transform.toUi import com.twidere.twiderex.model.ui.ListsMode import com.twidere.twiderex.model.ui.UiList -import com.twidere.twiderex.model.ui.UiList.Companion.toUi import com.twidere.twiderex.paging.mediator.list.ListsMediator import com.twidere.twiderex.paging.mediator.list.ListsMediator.Companion.toUi import kotlinx.coroutines.flow.Flow diff --git a/app/src/main/kotlin/com/twidere/twiderex/repository/MediaRepository.kt b/app/src/main/kotlin/com/twidere/twiderex/repository/MediaRepository.kt index 8c1538a0e..000574a11 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/repository/MediaRepository.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/repository/MediaRepository.kt @@ -22,7 +22,7 @@ package com.twidere.twiderex.repository import com.twidere.twiderex.db.CacheDatabase import com.twidere.twiderex.model.MicroBlogKey -import com.twidere.twiderex.model.ui.UiMedia.Companion.toUi +import com.twidere.twiderex.model.transform.toUi class MediaRepository(private val database: CacheDatabase) { diff --git a/app/src/main/kotlin/com/twidere/twiderex/repository/TrendRepository.kt b/app/src/main/kotlin/com/twidere/twiderex/repository/TrendRepository.kt index 377c1d17b..fe0f0f09c 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/repository/TrendRepository.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/repository/TrendRepository.kt @@ -25,7 +25,6 @@ import com.twidere.services.microblog.TrendService import com.twidere.twiderex.db.CacheDatabase import com.twidere.twiderex.model.AccountDetails import com.twidere.twiderex.model.ui.UiTrend -import com.twidere.twiderex.model.ui.UiTrend.Companion.toUi import com.twidere.twiderex.paging.mediator.trend.TrendMediator import com.twidere.twiderex.paging.mediator.trend.TrendMediator.Companion.toUi import kotlinx.coroutines.flow.Flow diff --git a/app/src/main/kotlin/com/twidere/twiderex/scenes/compose/ComposeSearchUserScene.kt b/app/src/main/kotlin/com/twidere/twiderex/scenes/compose/ComposeSearchUserScene.kt index 18e441e70..8df5d5ca8 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/scenes/compose/ComposeSearchUserScene.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/scenes/compose/ComposeSearchUserScene.kt @@ -115,7 +115,7 @@ fun ComposeSearchUserScene() { LazyUiUserList( items = source, onItemClicked = { - val displayName = it.getDisplayScreenName(accountDetails = account) + val displayName = it.getDisplayScreenName(account.accountKey.host) navController.goBackWith(displayName) }, header = { diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/compose/ComposeViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/compose/ComposeViewModel.kt index 818f43a3f..ee6e0e451 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/compose/ComposeViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/compose/ComposeViewModel.kt @@ -303,7 +303,7 @@ open class ComposeViewModel @AssistedInject constructor( status.mastodonExtra.mentions.mapNotNull { it.acct } .filter { it != account.user.screenName }.map { "@$it" }.let { if (status.user.userKey != account.user.userKey) { - listOf(status.user.getDisplayScreenName(account)) + it + listOf(status.user.getDisplayScreenName(account.accountKey.host)) + it } else { it } From 86f798320aacea3f1f44d0aa1324f96622c3b3c0 Mon Sep 17 00:00:00 2001 From: itsMimao Date: Tue, 3 Aug 2021 18:53:29 +0800 Subject: [PATCH 114/137] redesign UiUser and DbUser --- .../19.json | 1089 +++++++++++++++++ .../twiderex/component/UserComponent.kt | 8 +- .../com/twidere/twiderex/db/CacheDatabase.kt | 2 +- .../twidere/twiderex/db/mapper/Mastodon.kt | 14 +- .../com/twidere/twiderex/db/mapper/Twitter.kt | 42 +- .../com/twidere/twiderex/db/model/Alias.kt | 25 + .../com/twidere/twiderex/db/model/DbUser.kt | 3 +- .../com/twidere/twiderex/model/ui/UiUser.kt | 50 +- .../model/ui/mastodon/MastodonUserExtra.kt | 36 + .../model/ui/twitter/TwitterUserExtra.kt | 29 + 10 files changed, 1264 insertions(+), 34 deletions(-) create mode 100644 app/schemas/com.twidere.twiderex.db.CacheDatabase/19.json create mode 100644 app/src/main/kotlin/com/twidere/twiderex/db/model/Alias.kt create mode 100644 app/src/main/kotlin/com/twidere/twiderex/model/ui/mastodon/MastodonUserExtra.kt create mode 100644 app/src/main/kotlin/com/twidere/twiderex/model/ui/twitter/TwitterUserExtra.kt diff --git a/app/schemas/com.twidere.twiderex.db.CacheDatabase/19.json b/app/schemas/com.twidere.twiderex.db.CacheDatabase/19.json new file mode 100644 index 000000000..f9c6d8760 --- /dev/null +++ b/app/schemas/com.twidere.twiderex.db.CacheDatabase/19.json @@ -0,0 +1,1089 @@ +{ + "formatVersion": 1, + "database": { + "version": 19, + "identityHash": "7304de2dc43bca1acdd86fcbf0c78ab2", + "entities": [ + { + "tableName": "status", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` TEXT NOT NULL, `statusId` TEXT NOT NULL, `statusKey` TEXT NOT NULL, `htmlText` TEXT NOT NULL, `rawText` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `retweetCount` INTEGER NOT NULL, `likeCount` INTEGER NOT NULL, `replyCount` INTEGER NOT NULL, `placeString` TEXT, `source` TEXT NOT NULL, `hasMedia` INTEGER NOT NULL, `userKey` TEXT NOT NULL, `lang` TEXT, `is_possibly_sensitive` INTEGER NOT NULL, `platformType` TEXT NOT NULL, `mastodonExtra` TEXT, `twitterExtra` TEXT, `previewCard` TEXT, `inReplyToUserId` TEXT, `inReplyToStatusId` TEXT, PRIMARY KEY(`_id`))", + "fields": [ + { + "fieldPath": "_id", + "columnName": "_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "statusId", + "columnName": "statusId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "statusKey", + "columnName": "statusKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "htmlText", + "columnName": "htmlText", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "rawText", + "columnName": "rawText", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "retweetCount", + "columnName": "retweetCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "likeCount", + "columnName": "likeCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "replyCount", + "columnName": "replyCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "placeString", + "columnName": "placeString", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "source", + "columnName": "source", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasMedia", + "columnName": "hasMedia", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userKey", + "columnName": "userKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lang", + "columnName": "lang", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "is_possibly_sensitive", + "columnName": "is_possibly_sensitive", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "platformType", + "columnName": "platformType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mastodonExtra", + "columnName": "mastodonExtra", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "twitterExtra", + "columnName": "twitterExtra", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "previewCard", + "columnName": "previewCard", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "inReplyToUserId", + "columnName": "inReplyToUserId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "inReplyToStatusId", + "columnName": "inReplyToStatusId", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "_id" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_status_statusKey", + "unique": true, + "columnNames": [ + "statusKey" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_status_statusKey` ON `${TABLE_NAME}` (`statusKey`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "media", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` TEXT NOT NULL, `belongToKey` TEXT NOT NULL, `url` TEXT, `mediaUrl` TEXT, `previewUrl` TEXT, `type` TEXT NOT NULL, `width` INTEGER NOT NULL, `height` INTEGER NOT NULL, `pageUrl` TEXT, `altText` TEXT NOT NULL, `order` INTEGER NOT NULL, PRIMARY KEY(`_id`))", + "fields": [ + { + "fieldPath": "_id", + "columnName": "_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "belongToKey", + "columnName": "belongToKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "mediaUrl", + "columnName": "mediaUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "previewUrl", + "columnName": "previewUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "width", + "columnName": "width", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "height", + "columnName": "height", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "pageUrl", + "columnName": "pageUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "altText", + "columnName": "altText", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "order", + "columnName": "order", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "_id" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_media_belongToKey_order", + "unique": true, + "columnNames": [ + "belongToKey", + "order" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_media_belongToKey_order` ON `${TABLE_NAME}` (`belongToKey`, `order`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "user", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` TEXT NOT NULL, `userId` TEXT NOT NULL, `name` TEXT NOT NULL, `userKey` TEXT NOT NULL, `acct` TEXT NOT NULL, `screenName` TEXT NOT NULL, `profileImage` TEXT NOT NULL, `profileBackgroundImage` TEXT, `followersCount` INTEGER NOT NULL, `friendsCount` INTEGER NOT NULL, `listedCount` INTEGER NOT NULL, `htmlDesc` TEXT NOT NULL, `rawDesc` TEXT NOT NULL, `website` TEXT, `location` TEXT, `verified` INTEGER NOT NULL, `isProtected` INTEGER NOT NULL, `platformType` TEXT NOT NULL, `statusesCount` INTEGER NOT NULL, `extra` TEXT NOT NULL, PRIMARY KEY(`_id`))", + "fields": [ + { + "fieldPath": "_id", + "columnName": "_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userId", + "columnName": "userId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userKey", + "columnName": "userKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "acct", + "columnName": "acct", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "screenName", + "columnName": "screenName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "profileImage", + "columnName": "profileImage", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "profileBackgroundImage", + "columnName": "profileBackgroundImage", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "followersCount", + "columnName": "followersCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "friendsCount", + "columnName": "friendsCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "listedCount", + "columnName": "listedCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "htmlDesc", + "columnName": "htmlDesc", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "rawDesc", + "columnName": "rawDesc", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "website", + "columnName": "website", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "location", + "columnName": "location", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "verified", + "columnName": "verified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isProtected", + "columnName": "isProtected", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "platformType", + "columnName": "platformType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "statusesCount", + "columnName": "statusesCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "extra", + "columnName": "extra", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "_id" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_user_userKey", + "unique": true, + "columnNames": [ + "userKey" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_user_userKey` ON `${TABLE_NAME}` (`userKey`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "status_reactions", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` TEXT NOT NULL, `statusKey` TEXT NOT NULL, `accountKey` TEXT NOT NULL, `liked` INTEGER NOT NULL, `retweeted` INTEGER NOT NULL, PRIMARY KEY(`_id`))", + "fields": [ + { + "fieldPath": "_id", + "columnName": "_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "statusKey", + "columnName": "statusKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "accountKey", + "columnName": "accountKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "liked", + "columnName": "liked", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "retweeted", + "columnName": "retweeted", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "_id" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_status_reactions_statusKey_accountKey", + "unique": true, + "columnNames": [ + "statusKey", + "accountKey" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_status_reactions_statusKey_accountKey` ON `${TABLE_NAME}` (`statusKey`, `accountKey`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "paging_timeline", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` TEXT NOT NULL, `accountKey` TEXT NOT NULL, `pagingKey` TEXT NOT NULL, `statusKey` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `sortId` INTEGER NOT NULL, `isGap` INTEGER NOT NULL, PRIMARY KEY(`_id`))", + "fields": [ + { + "fieldPath": "_id", + "columnName": "_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "accountKey", + "columnName": "accountKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pagingKey", + "columnName": "pagingKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "statusKey", + "columnName": "statusKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sortId", + "columnName": "sortId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isGap", + "columnName": "isGap", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "_id" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_paging_timeline_accountKey_statusKey_pagingKey", + "unique": true, + "columnNames": [ + "accountKey", + "statusKey", + "pagingKey" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_paging_timeline_accountKey_statusKey_pagingKey` ON `${TABLE_NAME}` (`accountKey`, `statusKey`, `pagingKey`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "url_entity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` TEXT NOT NULL, `statusKey` TEXT NOT NULL, `url` TEXT NOT NULL, `expandedUrl` TEXT NOT NULL, `displayUrl` TEXT NOT NULL, `title` TEXT, `description` TEXT, `image` TEXT, PRIMARY KEY(`_id`))", + "fields": [ + { + "fieldPath": "_id", + "columnName": "_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "statusKey", + "columnName": "statusKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "expandedUrl", + "columnName": "expandedUrl", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "displayUrl", + "columnName": "displayUrl", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "image", + "columnName": "image", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "_id" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_url_entity_statusKey_url", + "unique": true, + "columnNames": [ + "statusKey", + "url" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_url_entity_statusKey_url` ON `${TABLE_NAME}` (`statusKey`, `url`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "status_reference", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` TEXT NOT NULL, `referenceType` TEXT NOT NULL, `statusKey` TEXT NOT NULL, `referenceStatusKey` TEXT NOT NULL, PRIMARY KEY(`_id`))", + "fields": [ + { + "fieldPath": "_id", + "columnName": "_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "referenceType", + "columnName": "referenceType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "statusKey", + "columnName": "statusKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "referenceStatusKey", + "columnName": "referenceStatusKey", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "_id" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_status_reference_referenceType_statusKey_referenceStatusKey", + "unique": true, + "columnNames": [ + "referenceType", + "statusKey", + "referenceStatusKey" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_status_reference_referenceType_statusKey_referenceStatusKey` ON `${TABLE_NAME}` (`referenceType`, `statusKey`, `referenceStatusKey`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "lists", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` TEXT NOT NULL, `listId` TEXT NOT NULL, `ownerId` TEXT NOT NULL, `accountKey` TEXT NOT NULL, `listKey` TEXT NOT NULL, `title` TEXT NOT NULL, `description` TEXT NOT NULL, `mode` TEXT NOT NULL, `replyPolicy` TEXT NOT NULL, `isFollowed` INTEGER NOT NULL, `allowToSubscribe` INTEGER NOT NULL, PRIMARY KEY(`_id`))", + "fields": [ + { + "fieldPath": "_id", + "columnName": "_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "listId", + "columnName": "listId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "ownerId", + "columnName": "ownerId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "accountKey", + "columnName": "accountKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "listKey", + "columnName": "listKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mode", + "columnName": "mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "replyPolicy", + "columnName": "replyPolicy", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isFollowed", + "columnName": "isFollowed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "allowToSubscribe", + "columnName": "allowToSubscribe", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "_id" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_lists_accountKey_listKey", + "unique": true, + "columnNames": [ + "accountKey", + "listKey" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_lists_accountKey_listKey` ON `${TABLE_NAME}` (`accountKey`, `listKey`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "notification_cursor", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` TEXT NOT NULL, `accountKey` TEXT NOT NULL, `type` TEXT NOT NULL, `value` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, PRIMARY KEY(`_id`))", + "fields": [ + { + "fieldPath": "_id", + "columnName": "_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "accountKey", + "columnName": "accountKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "_id" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_notification_cursor_accountKey_type", + "unique": true, + "columnNames": [ + "accountKey", + "type" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_notification_cursor_accountKey_type` ON `${TABLE_NAME}` (`accountKey`, `type`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "trends", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` TEXT NOT NULL, `trendKey` TEXT NOT NULL, `accountKey` TEXT NOT NULL, `displayName` TEXT NOT NULL, `url` TEXT NOT NULL, `query` TEXT NOT NULL, `volume` INTEGER NOT NULL, PRIMARY KEY(`_id`))", + "fields": [ + { + "fieldPath": "_id", + "columnName": "_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "trendKey", + "columnName": "trendKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "accountKey", + "columnName": "accountKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "displayName", + "columnName": "displayName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "query", + "columnName": "query", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "volume", + "columnName": "volume", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "_id" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_trends_trendKey_url", + "unique": true, + "columnNames": [ + "trendKey", + "url" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_trends_trendKey_url` ON `${TABLE_NAME}` (`trendKey`, `url`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "trend_histories", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` TEXT NOT NULL, `trendKey` TEXT NOT NULL, `day` INTEGER NOT NULL, `uses` INTEGER NOT NULL, `accounts` INTEGER NOT NULL, `accountKey` TEXT NOT NULL, PRIMARY KEY(`_id`))", + "fields": [ + { + "fieldPath": "_id", + "columnName": "_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "trendKey", + "columnName": "trendKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "day", + "columnName": "day", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "uses", + "columnName": "uses", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "accounts", + "columnName": "accounts", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "accountKey", + "columnName": "accountKey", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "_id" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_trend_histories_trendKey_day", + "unique": true, + "columnNames": [ + "trendKey", + "day" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_trend_histories_trendKey_day` ON `${TABLE_NAME}` (`trendKey`, `day`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "dm_conversation", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` TEXT NOT NULL, `accountKey` TEXT NOT NULL, `conversationId` TEXT NOT NULL, `conversationKey` TEXT NOT NULL, `conversationAvatar` TEXT NOT NULL, `conversationName` TEXT NOT NULL, `conversationSubName` TEXT NOT NULL, `conversationType` TEXT NOT NULL, `recipientKey` TEXT NOT NULL, PRIMARY KEY(`_id`))", + "fields": [ + { + "fieldPath": "_id", + "columnName": "_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "accountKey", + "columnName": "accountKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conversationId", + "columnName": "conversationId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conversationKey", + "columnName": "conversationKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conversationAvatar", + "columnName": "conversationAvatar", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conversationName", + "columnName": "conversationName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conversationSubName", + "columnName": "conversationSubName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conversationType", + "columnName": "conversationType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "recipientKey", + "columnName": "recipientKey", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "_id" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_dm_conversation_accountKey_conversationKey", + "unique": true, + "columnNames": [ + "accountKey", + "conversationKey" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_dm_conversation_accountKey_conversationKey` ON `${TABLE_NAME}` (`accountKey`, `conversationKey`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "dm_event", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` TEXT NOT NULL, `accountKey` TEXT NOT NULL, `sortId` INTEGER NOT NULL, `conversationKey` TEXT NOT NULL, `messageId` TEXT NOT NULL, `messageKey` TEXT NOT NULL, `htmlText` TEXT NOT NULL, `originText` TEXT NOT NULL, `createdTimestamp` INTEGER NOT NULL, `messageType` TEXT NOT NULL, `senderAccountKey` TEXT NOT NULL, `recipientAccountKey` TEXT NOT NULL, `sendStatus` TEXT NOT NULL, PRIMARY KEY(`_id`))", + "fields": [ + { + "fieldPath": "_id", + "columnName": "_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "accountKey", + "columnName": "accountKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sortId", + "columnName": "sortId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "conversationKey", + "columnName": "conversationKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "messageId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "messageKey", + "columnName": "messageKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "htmlText", + "columnName": "htmlText", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "originText", + "columnName": "originText", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdTimestamp", + "columnName": "createdTimestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageType", + "columnName": "messageType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderAccountKey", + "columnName": "senderAccountKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "recipientAccountKey", + "columnName": "recipientAccountKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sendStatus", + "columnName": "sendStatus", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "_id" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_dm_event_accountKey_conversationKey_messageKey", + "unique": true, + "columnNames": [ + "accountKey", + "conversationKey", + "messageKey" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_dm_event_accountKey_conversationKey_messageKey` ON `${TABLE_NAME}` (`accountKey`, `conversationKey`, `messageKey`)" + } + ], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '7304de2dc43bca1acdd86fcbf0c78ab2')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/UserComponent.kt b/app/src/main/kotlin/com/twidere/twiderex/component/UserComponent.kt index c051e066b..cc751d324 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/UserComponent.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/UserComponent.kt @@ -97,12 +97,12 @@ import com.twidere.twiderex.component.status.UserAvatar import com.twidere.twiderex.component.status.UserName import com.twidere.twiderex.component.status.UserScreenName import com.twidere.twiderex.component.status.withAvatarClip -import com.twidere.twiderex.db.model.TwitterUrlEntity import com.twidere.twiderex.di.assisted.assistedViewModel import com.twidere.twiderex.extensions.observeAsState import com.twidere.twiderex.extensions.withElevation import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.enums.PlatformType +import com.twidere.twiderex.model.ui.UiUrlEntity import com.twidere.twiderex.model.ui.UiUser import com.twidere.twiderex.navigation.RootRoute import com.twidere.twiderex.navigation.twidereXSchema @@ -558,7 +558,7 @@ fun MastodonUserField(user: UiUser) { if (user.platformType != PlatformType.Mastodon || user.mastodonExtra == null) { return } - user.mastodonExtra.fields.forEachIndexed { index, field -> + user.mastodonExtra?.fields?.forEachIndexed { index, field -> Row( modifier = Modifier .fillMaxWidth() @@ -576,7 +576,7 @@ fun MastodonUserField(user: UiUser) { ) } } - if (index != user.mastodonExtra.fields.lastIndex) { + if (index != user.mastodonExtra?.fields?.lastIndex) { Spacer(modifier = Modifier.height(MastodonUserFieldDefaults.ItemSpacing)) } } @@ -780,7 +780,7 @@ fun MetricsItem( fun UserDescText( modifier: Modifier = Modifier, htmlDesc: String, - url: List, + url: List, ) { key( htmlDesc, diff --git a/app/src/main/kotlin/com/twidere/twiderex/db/CacheDatabase.kt b/app/src/main/kotlin/com/twidere/twiderex/db/CacheDatabase.kt index 1232b94c1..a7a95f5a9 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/db/CacheDatabase.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/db/CacheDatabase.kt @@ -76,7 +76,7 @@ import javax.inject.Singleton DbDMConversation::class, DbDMEvent::class ], - version = 18, + version = 19, ) @TypeConverters( MicroBlogKeyConverter::class, diff --git a/app/src/main/kotlin/com/twidere/twiderex/db/mapper/Mastodon.kt b/app/src/main/kotlin/com/twidere/twiderex/db/mapper/Mastodon.kt index b37747945..611e5032f 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/db/mapper/Mastodon.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/db/mapper/Mastodon.kt @@ -51,6 +51,8 @@ import com.twidere.twiderex.model.enums.MastodonStatusType import com.twidere.twiderex.model.enums.MediaType import com.twidere.twiderex.model.enums.PlatformType import com.twidere.twiderex.navigation.RootDeepLinksRoute +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json import org.jsoup.Jsoup import org.jsoup.nodes.Element import org.jsoup.nodes.Node @@ -327,11 +329,13 @@ fun Account.toDbUser( } ?: throw IllegalArgumentException("mastodon user.acct should not be null"), platformType = PlatformType.Mastodon, statusesCount = statusesCount ?: 0L, - mastodonExtra = DbMastodonUserExtra( - fields = fields ?: emptyList(), - bot = bot ?: false, - locked = locked ?: false, - emoji = emojis ?: emptyList(), + extra = Json.encodeToString( + DbMastodonUserExtra( + fields = fields ?: emptyList(), + bot = bot ?: false, + locked = locked ?: false, + emoji = emojis ?: emptyList(), + ) ) ) } diff --git a/app/src/main/kotlin/com/twidere/twiderex/db/mapper/Twitter.kt b/app/src/main/kotlin/com/twidere/twiderex/db/mapper/Twitter.kt index 9302a22c4..9b1e89506 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/db/mapper/Twitter.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/db/mapper/Twitter.kt @@ -55,6 +55,8 @@ import com.twidere.twiderex.model.enums.PlatformType import com.twidere.twiderex.model.ui.ListsMode import com.twidere.twiderex.navigation.RootDeepLinksRouteDefinition import com.twitter.twittertext.Autolink +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json import java.util.UUID val autolink by lazy { @@ -439,15 +441,17 @@ fun User.toDbUser(): DbUser { platformType = PlatformType.Twitter, acct = MicroBlogKey.twitter(screenName ?: ""), statusesCount = statusesCount ?: 0, - twitterExtra = DbTwitterUserExtra( - pinned_tweet_id = null, - url = entities?.description?.urls?.map { - TwitterUrlEntity( - url = it.url ?: "", - expandedUrl = it.expandedURL ?: "", - displayUrl = it.displayURL ?: "", - ) - } ?: emptyList() + extra = Json.encodeToString( + DbTwitterUserExtra( + pinned_tweet_id = null, + url = entities?.description?.urls?.map { + TwitterUrlEntity( + url = it.url ?: "", + expandedUrl = it.expandedURL ?: "", + displayUrl = it.displayURL ?: "", + ) + } ?: emptyList() + ) ) ) } @@ -477,15 +481,17 @@ fun UserV2.toDbUser(): DbUser { acct = MicroBlogKey.twitter(username ?: ""), platformType = PlatformType.Twitter, statusesCount = publicMetrics?.tweetCount ?: 0, - twitterExtra = DbTwitterUserExtra( - pinned_tweet_id = pinnedTweetID, - url = entities?.description?.urls?.map { - TwitterUrlEntity( - url = it.url ?: "", - expandedUrl = it.expandedURL ?: "", - displayUrl = it.displayURL ?: "", - ) - } ?: emptyList() + extra = Json.encodeToString( + DbTwitterUserExtra( + pinned_tweet_id = pinnedTweetID, + url = entities?.description?.urls?.map { + TwitterUrlEntity( + url = it.url ?: "", + expandedUrl = it.expandedURL ?: "", + displayUrl = it.displayURL ?: "", + ) + } ?: emptyList() + ) ) ) } diff --git a/app/src/main/kotlin/com/twidere/twiderex/db/model/Alias.kt b/app/src/main/kotlin/com/twidere/twiderex/db/model/Alias.kt new file mode 100644 index 000000000..4ea627f62 --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/db/model/Alias.kt @@ -0,0 +1,25 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.db.model + +typealias Html = String +typealias Json = String +typealias Timestamp = Long diff --git a/app/src/main/kotlin/com/twidere/twiderex/db/model/DbUser.kt b/app/src/main/kotlin/com/twidere/twiderex/db/model/DbUser.kt index f26a948c4..a2a3eaed6 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/db/model/DbUser.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/db/model/DbUser.kt @@ -56,8 +56,7 @@ data class DbUser( val isProtected: Boolean, val platformType: PlatformType, val statusesCount: Long, - val twitterExtra: DbTwitterUserExtra? = null, - val mastodonExtra: DbMastodonUserExtra? = null, + val extra: Json ) @Immutable diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiUser.kt b/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiUser.kt index 697b7a24c..a0b983bc0 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiUser.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiUser.kt @@ -29,6 +29,12 @@ import com.twidere.twiderex.db.model.DbTwitterUserExtra import com.twidere.twiderex.db.model.DbUser import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.enums.PlatformType +import com.twidere.twiderex.model.transform.toUi +import com.twidere.twiderex.model.ui.mastodon.Field +import com.twidere.twiderex.model.ui.mastodon.MastodonUserExtra +import com.twidere.twiderex.model.ui.twitter.TwitterUserExtra +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json @Immutable data class UiUser( @@ -50,8 +56,7 @@ data class UiUser( val verified: Boolean, val protected: Boolean, val platformType: PlatformType, - val twitterExtra: DbTwitterUserExtra? = null, - val mastodonExtra: DbMastodonUserExtra? = null, + val extra: UserExtra? = null ) { val displayName get() = name.takeUnless { it.isEmpty() } ?: screenName @@ -63,6 +68,11 @@ data class UiUser( "@$screenName" } } + val twitterExtra: TwitterUserExtra? + get() = if (extra is TwitterUserExtra) extra else null + + val mastodonExtra: MastodonUserExtra? + get() = if (extra is MastodonUserExtra) extra else null companion object { @Composable @@ -106,9 +116,41 @@ data class UiUser( protected = isProtected, userKey = userKey, platformType = platformType, - twitterExtra = twitterExtra, - mastodonExtra = mastodonExtra, + extra = when (platformType) { + PlatformType.Twitter -> Json.decodeFromString(extra).let { + TwitterUserExtra( + pinned_tweet_id = it.pinned_tweet_id, + url = it.url.map { url -> + UiUrlEntity( + url = url.displayUrl, + expandedUrl = url.expandedUrl, + displayUrl = url.displayUrl, + title = null, + description = null, + image = null + ) + } + ) + } + PlatformType.StatusNet -> TODO() + PlatformType.Fanfou -> TODO() + PlatformType.Mastodon -> Json.decodeFromString(extra).let { + MastodonUserExtra( + emoji = it.emoji.toUi(), + bot = it.bot, + locked = it.locked, + fields = it.fields.map { + Field( + it.name, + it.value + ) + } + ) + } + }, acct = acct, ) } } + +interface UserExtra diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/ui/mastodon/MastodonUserExtra.kt b/app/src/main/kotlin/com/twidere/twiderex/model/ui/mastodon/MastodonUserExtra.kt new file mode 100644 index 000000000..b5d00ada8 --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/model/ui/mastodon/MastodonUserExtra.kt @@ -0,0 +1,36 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.model.ui.mastodon + +import com.twidere.twiderex.model.ui.UiEmojiCategory +import com.twidere.twiderex.model.ui.UserExtra + +data class MastodonUserExtra( + val fields: List, + val emoji: List, + val bot: Boolean, + val locked: Boolean, +) : UserExtra + +data class Field( + val name: String?, + val value: String? +) diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/ui/twitter/TwitterUserExtra.kt b/app/src/main/kotlin/com/twidere/twiderex/model/ui/twitter/TwitterUserExtra.kt new file mode 100644 index 000000000..8a51144da --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/model/ui/twitter/TwitterUserExtra.kt @@ -0,0 +1,29 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.model.ui.twitter + +import com.twidere.twiderex.model.ui.UiUrlEntity +import com.twidere.twiderex.model.ui.UserExtra + +data class TwitterUserExtra( + val pinned_tweet_id: String?, + val url: List, +) : UserExtra From c268d62e840f4a3aa9b7da83f8a0709dd806c2a4 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Wed, 4 Aug 2021 12:49:02 +0800 Subject: [PATCH 115/137] fix null pointer in lists modify view model --- .../twidere/twiderex/viewmodel/lists/ListsViewModel.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/lists/ListsViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/lists/ListsViewModel.kt index dcb1e673f..87355f54b 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/lists/ListsViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/lists/ListsViewModel.kt @@ -128,6 +128,10 @@ class ListsModifyViewModel @AssistedInject constructor( fun create(account: AccountDetails, listKey: MicroBlogKey): ListsModifyViewModel } + val editName = MutableStateFlow("") + var editDesc = MutableStateFlow("") + var editPrivate = MutableStateFlow(false) + val source by lazy { listsRepository.findListWithListKey(account = account, listKey = listKey) } @@ -142,10 +146,6 @@ class ListsModifyViewModel @AssistedInject constructor( } } - val editName = MutableStateFlow("") - var editDesc = MutableStateFlow("") - var editPrivate = MutableStateFlow(false) - fun editList( listId: String = listKey.id, title: String, From f5ae242eff4e9d90d92c10bafa6c4856e6e9abdb Mon Sep 17 00:00:00 2001 From: Tlaster Date: Wed, 4 Aug 2021 12:56:09 +0800 Subject: [PATCH 116/137] bump db version --- .../19.json | 1095 +++++++++++++++++ .../com/twidere/twiderex/db/CacheDatabase.kt | 2 +- 2 files changed, 1096 insertions(+), 1 deletion(-) create mode 100644 app/schemas/com.twidere.twiderex.db.CacheDatabase/19.json diff --git a/app/schemas/com.twidere.twiderex.db.CacheDatabase/19.json b/app/schemas/com.twidere.twiderex.db.CacheDatabase/19.json new file mode 100644 index 000000000..e043d1e18 --- /dev/null +++ b/app/schemas/com.twidere.twiderex.db.CacheDatabase/19.json @@ -0,0 +1,1095 @@ +{ + "formatVersion": 1, + "database": { + "version": 19, + "identityHash": "95078733d7caff478bf283cc54997675", + "entities": [ + { + "tableName": "status", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` TEXT NOT NULL, `statusId` TEXT NOT NULL, `statusKey` TEXT NOT NULL, `htmlText` TEXT NOT NULL, `rawText` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `retweetCount` INTEGER NOT NULL, `likeCount` INTEGER NOT NULL, `replyCount` INTEGER NOT NULL, `placeString` TEXT, `source` TEXT NOT NULL, `hasMedia` INTEGER NOT NULL, `userKey` TEXT NOT NULL, `lang` TEXT, `is_possibly_sensitive` INTEGER NOT NULL, `platformType` TEXT NOT NULL, `mastodonExtra` TEXT, `twitterExtra` TEXT, `previewCard` TEXT, `inReplyToUserId` TEXT, `inReplyToStatusId` TEXT, PRIMARY KEY(`_id`))", + "fields": [ + { + "fieldPath": "_id", + "columnName": "_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "statusId", + "columnName": "statusId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "statusKey", + "columnName": "statusKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "htmlText", + "columnName": "htmlText", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "rawText", + "columnName": "rawText", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "retweetCount", + "columnName": "retweetCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "likeCount", + "columnName": "likeCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "replyCount", + "columnName": "replyCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "placeString", + "columnName": "placeString", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "source", + "columnName": "source", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasMedia", + "columnName": "hasMedia", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userKey", + "columnName": "userKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lang", + "columnName": "lang", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "is_possibly_sensitive", + "columnName": "is_possibly_sensitive", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "platformType", + "columnName": "platformType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mastodonExtra", + "columnName": "mastodonExtra", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "twitterExtra", + "columnName": "twitterExtra", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "previewCard", + "columnName": "previewCard", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "inReplyToUserId", + "columnName": "inReplyToUserId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "inReplyToStatusId", + "columnName": "inReplyToStatusId", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "_id" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_status_statusKey", + "unique": true, + "columnNames": [ + "statusKey" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_status_statusKey` ON `${TABLE_NAME}` (`statusKey`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "media", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` TEXT NOT NULL, `belongToKey` TEXT NOT NULL, `url` TEXT, `mediaUrl` TEXT, `previewUrl` TEXT, `type` TEXT NOT NULL, `width` INTEGER NOT NULL, `height` INTEGER NOT NULL, `pageUrl` TEXT, `altText` TEXT NOT NULL, `order` INTEGER NOT NULL, PRIMARY KEY(`_id`))", + "fields": [ + { + "fieldPath": "_id", + "columnName": "_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "belongToKey", + "columnName": "belongToKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "mediaUrl", + "columnName": "mediaUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "previewUrl", + "columnName": "previewUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "width", + "columnName": "width", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "height", + "columnName": "height", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "pageUrl", + "columnName": "pageUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "altText", + "columnName": "altText", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "order", + "columnName": "order", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "_id" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_media_belongToKey_order", + "unique": true, + "columnNames": [ + "belongToKey", + "order" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_media_belongToKey_order` ON `${TABLE_NAME}` (`belongToKey`, `order`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "user", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` TEXT NOT NULL, `userId` TEXT NOT NULL, `name` TEXT NOT NULL, `userKey` TEXT NOT NULL, `acct` TEXT NOT NULL, `screenName` TEXT NOT NULL, `profileImage` TEXT NOT NULL, `profileBackgroundImage` TEXT, `followersCount` INTEGER NOT NULL, `friendsCount` INTEGER NOT NULL, `listedCount` INTEGER NOT NULL, `htmlDesc` TEXT NOT NULL, `rawDesc` TEXT NOT NULL, `website` TEXT, `location` TEXT, `verified` INTEGER NOT NULL, `isProtected` INTEGER NOT NULL, `platformType` TEXT NOT NULL, `statusesCount` INTEGER NOT NULL, `twitterExtra` TEXT, `mastodonExtra` TEXT, PRIMARY KEY(`_id`))", + "fields": [ + { + "fieldPath": "_id", + "columnName": "_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userId", + "columnName": "userId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userKey", + "columnName": "userKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "acct", + "columnName": "acct", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "screenName", + "columnName": "screenName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "profileImage", + "columnName": "profileImage", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "profileBackgroundImage", + "columnName": "profileBackgroundImage", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "followersCount", + "columnName": "followersCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "friendsCount", + "columnName": "friendsCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "listedCount", + "columnName": "listedCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "htmlDesc", + "columnName": "htmlDesc", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "rawDesc", + "columnName": "rawDesc", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "website", + "columnName": "website", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "location", + "columnName": "location", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "verified", + "columnName": "verified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isProtected", + "columnName": "isProtected", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "platformType", + "columnName": "platformType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "statusesCount", + "columnName": "statusesCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "twitterExtra", + "columnName": "twitterExtra", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "mastodonExtra", + "columnName": "mastodonExtra", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "_id" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_user_userKey", + "unique": true, + "columnNames": [ + "userKey" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_user_userKey` ON `${TABLE_NAME}` (`userKey`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "status_reactions", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` TEXT NOT NULL, `statusKey` TEXT NOT NULL, `accountKey` TEXT NOT NULL, `liked` INTEGER NOT NULL, `retweeted` INTEGER NOT NULL, PRIMARY KEY(`_id`))", + "fields": [ + { + "fieldPath": "_id", + "columnName": "_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "statusKey", + "columnName": "statusKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "accountKey", + "columnName": "accountKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "liked", + "columnName": "liked", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "retweeted", + "columnName": "retweeted", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "_id" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_status_reactions_statusKey_accountKey", + "unique": true, + "columnNames": [ + "statusKey", + "accountKey" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_status_reactions_statusKey_accountKey` ON `${TABLE_NAME}` (`statusKey`, `accountKey`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "paging_timeline", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` TEXT NOT NULL, `accountKey` TEXT NOT NULL, `pagingKey` TEXT NOT NULL, `statusKey` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `sortId` INTEGER NOT NULL, `isGap` INTEGER NOT NULL, PRIMARY KEY(`_id`))", + "fields": [ + { + "fieldPath": "_id", + "columnName": "_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "accountKey", + "columnName": "accountKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pagingKey", + "columnName": "pagingKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "statusKey", + "columnName": "statusKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sortId", + "columnName": "sortId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isGap", + "columnName": "isGap", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "_id" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_paging_timeline_accountKey_statusKey_pagingKey", + "unique": true, + "columnNames": [ + "accountKey", + "statusKey", + "pagingKey" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_paging_timeline_accountKey_statusKey_pagingKey` ON `${TABLE_NAME}` (`accountKey`, `statusKey`, `pagingKey`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "url_entity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` TEXT NOT NULL, `statusKey` TEXT NOT NULL, `url` TEXT NOT NULL, `expandedUrl` TEXT NOT NULL, `displayUrl` TEXT NOT NULL, `title` TEXT, `description` TEXT, `image` TEXT, PRIMARY KEY(`_id`))", + "fields": [ + { + "fieldPath": "_id", + "columnName": "_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "statusKey", + "columnName": "statusKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "expandedUrl", + "columnName": "expandedUrl", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "displayUrl", + "columnName": "displayUrl", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "image", + "columnName": "image", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "_id" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_url_entity_statusKey_url", + "unique": true, + "columnNames": [ + "statusKey", + "url" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_url_entity_statusKey_url` ON `${TABLE_NAME}` (`statusKey`, `url`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "status_reference", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` TEXT NOT NULL, `referenceType` TEXT NOT NULL, `statusKey` TEXT NOT NULL, `referenceStatusKey` TEXT NOT NULL, PRIMARY KEY(`_id`))", + "fields": [ + { + "fieldPath": "_id", + "columnName": "_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "referenceType", + "columnName": "referenceType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "statusKey", + "columnName": "statusKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "referenceStatusKey", + "columnName": "referenceStatusKey", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "_id" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_status_reference_referenceType_statusKey_referenceStatusKey", + "unique": true, + "columnNames": [ + "referenceType", + "statusKey", + "referenceStatusKey" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_status_reference_referenceType_statusKey_referenceStatusKey` ON `${TABLE_NAME}` (`referenceType`, `statusKey`, `referenceStatusKey`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "lists", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` TEXT NOT NULL, `listId` TEXT NOT NULL, `ownerId` TEXT NOT NULL, `accountKey` TEXT NOT NULL, `listKey` TEXT NOT NULL, `title` TEXT NOT NULL, `description` TEXT NOT NULL, `mode` TEXT NOT NULL, `replyPolicy` TEXT NOT NULL, `isFollowed` INTEGER NOT NULL, `allowToSubscribe` INTEGER NOT NULL, PRIMARY KEY(`_id`))", + "fields": [ + { + "fieldPath": "_id", + "columnName": "_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "listId", + "columnName": "listId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "ownerId", + "columnName": "ownerId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "accountKey", + "columnName": "accountKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "listKey", + "columnName": "listKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mode", + "columnName": "mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "replyPolicy", + "columnName": "replyPolicy", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isFollowed", + "columnName": "isFollowed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "allowToSubscribe", + "columnName": "allowToSubscribe", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "_id" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_lists_accountKey_listKey", + "unique": true, + "columnNames": [ + "accountKey", + "listKey" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_lists_accountKey_listKey` ON `${TABLE_NAME}` (`accountKey`, `listKey`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "notification_cursor", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` TEXT NOT NULL, `accountKey` TEXT NOT NULL, `type` TEXT NOT NULL, `value` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, PRIMARY KEY(`_id`))", + "fields": [ + { + "fieldPath": "_id", + "columnName": "_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "accountKey", + "columnName": "accountKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "_id" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_notification_cursor_accountKey_type", + "unique": true, + "columnNames": [ + "accountKey", + "type" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_notification_cursor_accountKey_type` ON `${TABLE_NAME}` (`accountKey`, `type`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "trends", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` TEXT NOT NULL, `trendKey` TEXT NOT NULL, `accountKey` TEXT NOT NULL, `displayName` TEXT NOT NULL, `url` TEXT NOT NULL, `query` TEXT NOT NULL, `volume` INTEGER NOT NULL, PRIMARY KEY(`_id`))", + "fields": [ + { + "fieldPath": "_id", + "columnName": "_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "trendKey", + "columnName": "trendKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "accountKey", + "columnName": "accountKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "displayName", + "columnName": "displayName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "query", + "columnName": "query", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "volume", + "columnName": "volume", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "_id" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_trends_trendKey_url", + "unique": true, + "columnNames": [ + "trendKey", + "url" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_trends_trendKey_url` ON `${TABLE_NAME}` (`trendKey`, `url`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "trend_histories", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` TEXT NOT NULL, `trendKey` TEXT NOT NULL, `day` INTEGER NOT NULL, `uses` INTEGER NOT NULL, `accounts` INTEGER NOT NULL, `accountKey` TEXT NOT NULL, PRIMARY KEY(`_id`))", + "fields": [ + { + "fieldPath": "_id", + "columnName": "_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "trendKey", + "columnName": "trendKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "day", + "columnName": "day", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "uses", + "columnName": "uses", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "accounts", + "columnName": "accounts", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "accountKey", + "columnName": "accountKey", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "_id" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_trend_histories_trendKey_day", + "unique": true, + "columnNames": [ + "trendKey", + "day" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_trend_histories_trendKey_day` ON `${TABLE_NAME}` (`trendKey`, `day`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "dm_conversation", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` TEXT NOT NULL, `accountKey` TEXT NOT NULL, `conversationId` TEXT NOT NULL, `conversationKey` TEXT NOT NULL, `conversationAvatar` TEXT NOT NULL, `conversationName` TEXT NOT NULL, `conversationSubName` TEXT NOT NULL, `conversationType` TEXT NOT NULL, `recipientKey` TEXT NOT NULL, PRIMARY KEY(`_id`))", + "fields": [ + { + "fieldPath": "_id", + "columnName": "_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "accountKey", + "columnName": "accountKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conversationId", + "columnName": "conversationId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conversationKey", + "columnName": "conversationKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conversationAvatar", + "columnName": "conversationAvatar", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conversationName", + "columnName": "conversationName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conversationSubName", + "columnName": "conversationSubName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conversationType", + "columnName": "conversationType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "recipientKey", + "columnName": "recipientKey", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "_id" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_dm_conversation_accountKey_conversationKey", + "unique": true, + "columnNames": [ + "accountKey", + "conversationKey" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_dm_conversation_accountKey_conversationKey` ON `${TABLE_NAME}` (`accountKey`, `conversationKey`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "dm_event", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` TEXT NOT NULL, `accountKey` TEXT NOT NULL, `sortId` INTEGER NOT NULL, `conversationKey` TEXT NOT NULL, `messageId` TEXT NOT NULL, `messageKey` TEXT NOT NULL, `htmlText` TEXT NOT NULL, `originText` TEXT NOT NULL, `createdTimestamp` INTEGER NOT NULL, `messageType` TEXT NOT NULL, `senderAccountKey` TEXT NOT NULL, `recipientAccountKey` TEXT NOT NULL, `sendStatus` TEXT NOT NULL, PRIMARY KEY(`_id`))", + "fields": [ + { + "fieldPath": "_id", + "columnName": "_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "accountKey", + "columnName": "accountKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sortId", + "columnName": "sortId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "conversationKey", + "columnName": "conversationKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "messageId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "messageKey", + "columnName": "messageKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "htmlText", + "columnName": "htmlText", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "originText", + "columnName": "originText", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdTimestamp", + "columnName": "createdTimestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageType", + "columnName": "messageType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderAccountKey", + "columnName": "senderAccountKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "recipientAccountKey", + "columnName": "recipientAccountKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sendStatus", + "columnName": "sendStatus", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "_id" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_dm_event_accountKey_conversationKey_messageKey", + "unique": true, + "columnNames": [ + "accountKey", + "conversationKey", + "messageKey" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_dm_event_accountKey_conversationKey_messageKey` ON `${TABLE_NAME}` (`accountKey`, `conversationKey`, `messageKey`)" + } + ], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '95078733d7caff478bf283cc54997675')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/twidere/twiderex/db/CacheDatabase.kt b/app/src/main/kotlin/com/twidere/twiderex/db/CacheDatabase.kt index 1232b94c1..a7a95f5a9 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/db/CacheDatabase.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/db/CacheDatabase.kt @@ -76,7 +76,7 @@ import javax.inject.Singleton DbDMConversation::class, DbDMEvent::class ], - version = 18, + version = 19, ) @TypeConverters( MicroBlogKeyConverter::class, From 01168a2b3bf1b03fd9bc1f94be8bd541907f171c Mon Sep 17 00:00:00 2001 From: Tlaster Date: Wed, 4 Aug 2021 14:28:56 +0800 Subject: [PATCH 117/137] fix status rtl --- .../twiderex/component/status/TimelineStatusComponent.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/status/TimelineStatusComponent.kt b/app/src/main/kotlin/com/twidere/twiderex/component/status/TimelineStatusComponent.kt index 770f78c18..6a7efbacd 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/status/TimelineStatusComponent.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/status/TimelineStatusComponent.kt @@ -519,8 +519,14 @@ fun statusConstraintSets() = ConstraintSet { val footerRef = createRefFor(StatusContentDefaults.Ref.Footer) val lineUpRef = createRefFor(StatusContentDefaults.Ref.LineUp) val lineDownRef = createRefFor(StatusContentDefaults.Ref.LineDown) + constrain(statusHeaderRef) { + top.linkTo(parent.top) + start.linkTo(parent.start) + end.linkTo(parent.end) + } constrain(avatarRef) { top.linkTo(statusHeaderRef.bottom) + start.linkTo(parent.start) } constrain(contentRef) { start.linkTo(avatarRef.end, margin = StatusContentDefaults.AvatarSpacing) From f9bec9890c18742e19c077d34fccb1c0ef3bdbf1 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Wed, 4 Aug 2021 14:33:49 +0800 Subject: [PATCH 118/137] fix status header --- .../twidere/twiderex/component/status/TimelineStatusComponent.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/status/TimelineStatusComponent.kt b/app/src/main/kotlin/com/twidere/twiderex/component/status/TimelineStatusComponent.kt index 6a7efbacd..4f0527c46 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/status/TimelineStatusComponent.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/status/TimelineStatusComponent.kt @@ -522,7 +522,6 @@ fun statusConstraintSets() = ConstraintSet { constrain(statusHeaderRef) { top.linkTo(parent.top) start.linkTo(parent.start) - end.linkTo(parent.end) } constrain(avatarRef) { top.linkTo(statusHeaderRef.bottom) From 8e1f2c54cd2883a972f401a6ea59c64f11a0c9e2 Mon Sep 17 00:00:00 2001 From: itsMimao Date: Wed, 4 Aug 2021 14:35:01 +0800 Subject: [PATCH 119/137] redesign extra for UiStatus --- .../dm/DirectMessageRepositoryTest.kt | 2 +- .../twiderex/component/UserComponent.kt | 4 +- .../twidere/twiderex/db/mapper/Mastodon.kt | 48 +++--- .../com/twidere/twiderex/db/mapper/Twitter.kt | 23 ++- .../com/twidere/twiderex/db/model/DbStatus.kt | 13 +- .../twiderex/extensions/PagingExtensions.kt | 2 +- .../jobs/compose/MastodonComposeJob.kt | 2 +- .../jobs/compose/TwitterComposeJob.kt | 2 +- .../twiderex/jobs/status/LikeStatusJob.kt | 2 +- .../twiderex/jobs/status/RetweetStatusJob.kt | 2 +- .../jobs/status/UnRetweetStatusJob.kt | 2 +- .../twiderex/jobs/status/UnlikeStatusJob.kt | 2 +- .../{MastodonStatusType.kt => Mastodon.kt} | 8 + .../twidere/twiderex/model/enums/Twitter.kt | 30 ++++ .../transform/DmConversationTransform.kt | 1 - .../model/transform/StatusTransform.kt | 150 ++++++++++++++++++ .../twiderex/model/transform/UserTransform.kt | 87 ++++++++++ .../com/twidere/twiderex/model/ui/UiStatus.kt | 121 ++------------ .../com/twidere/twiderex/model/ui/UiUser.kt | 67 +------- .../model/ui/mastodon/MastodonStatusExtra.kt | 40 +++++ .../model/ui/twitter/TwitterStatusExtra.kt | 29 ++++ .../mediator/list/ListsUserPagingMediator.kt | 2 +- .../paging/source/FollowersPagingSource.kt | 2 +- .../paging/source/FollowingPagingSource.kt | 2 +- .../paging/source/SearchUserPagingSource.kt | 2 +- .../paging/source/UserPagingSource.kt | 2 +- .../repository/NotificationRepository.kt | 2 +- .../twiderex/repository/StatusRepository.kt | 2 +- .../twiderex/repository/UserRepository.kt | 2 +- .../viewmodel/search/SearchTweetsViewModel.kt | 2 +- .../search/TwitterSearchMediaViewModel.kt | 2 +- .../user/UserMediaTimelineViewModel.kt | 2 +- .../repository/ListsUsersRepositoryTest.kt | 2 +- 33 files changed, 429 insertions(+), 232 deletions(-) rename app/src/main/kotlin/com/twidere/twiderex/model/enums/{MastodonStatusType.kt => Mastodon.kt} (91%) create mode 100644 app/src/main/kotlin/com/twidere/twiderex/model/enums/Twitter.kt create mode 100644 app/src/main/kotlin/com/twidere/twiderex/model/transform/StatusTransform.kt create mode 100644 app/src/main/kotlin/com/twidere/twiderex/model/transform/UserTransform.kt create mode 100644 app/src/main/kotlin/com/twidere/twiderex/model/ui/mastodon/MastodonStatusExtra.kt create mode 100644 app/src/main/kotlin/com/twidere/twiderex/model/ui/twitter/TwitterStatusExtra.kt diff --git a/app/src/androidTest/java/com/twidere/twiderex/repository/dm/DirectMessageRepositoryTest.kt b/app/src/androidTest/java/com/twidere/twiderex/repository/dm/DirectMessageRepositoryTest.kt index 24883e57b..8fb3d77a7 100644 --- a/app/src/androidTest/java/com/twidere/twiderex/repository/dm/DirectMessageRepositoryTest.kt +++ b/app/src/androidTest/java/com/twidere/twiderex/repository/dm/DirectMessageRepositoryTest.kt @@ -31,7 +31,7 @@ import com.twidere.twiderex.mock.MockDirectMessageService import com.twidere.twiderex.mock.MockLookUpService import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.enums.PlatformType -import com.twidere.twiderex.model.ui.UiUser.Companion.toUi +import com.twidere.twiderex.model.transform.toUi import com.twidere.twiderex.repository.DirectMessageRepository import kotlinx.coroutines.runBlocking import org.junit.After diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/UserComponent.kt b/app/src/main/kotlin/com/twidere/twiderex/component/UserComponent.kt index cc751d324..163cb1098 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/UserComponent.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/UserComponent.kt @@ -558,7 +558,7 @@ fun MastodonUserField(user: UiUser) { if (user.platformType != PlatformType.Mastodon || user.mastodonExtra == null) { return } - user.mastodonExtra?.fields?.forEachIndexed { index, field -> + user.mastodonExtra.fields.forEachIndexed { index, field -> Row( modifier = Modifier .fillMaxWidth() @@ -576,7 +576,7 @@ fun MastodonUserField(user: UiUser) { ) } } - if (index != user.mastodonExtra?.fields?.lastIndex) { + if (index != user.mastodonExtra.fields.lastIndex) { Spacer(modifier = Modifier.height(MastodonUserFieldDefaults.ItemSpacing)) } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/db/mapper/Mastodon.kt b/app/src/main/kotlin/com/twidere/twiderex/db/mapper/Mastodon.kt index 611e5032f..dc4f83b26 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/db/mapper/Mastodon.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/db/mapper/Mastodon.kt @@ -48,6 +48,7 @@ import com.twidere.twiderex.db.model.ReferenceType import com.twidere.twiderex.db.model.toDbStatusReference import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.enums.MastodonStatusType +import com.twidere.twiderex.model.enums.MastodonVisibility import com.twidere.twiderex.model.enums.MediaType import com.twidere.twiderex.model.enums.PlatformType import com.twidere.twiderex.navigation.RootDeepLinksRoute @@ -105,15 +106,17 @@ fun Notification.toDbStatusWithReference( lang = null, is_possibly_sensitive = false, platformType = PlatformType.Mastodon, - mastodonExtra = DbMastodonStatusExtra( - type = this.type.toDbType(), - emoji = emptyList(), - visibility = Visibility.Public, - sensitive = false, - spoilerText = null, - poll = null, - card = null, - mentions = null, + extra = Json.encodeToString( + DbMastodonStatusExtra( + type = this.type.toDbType(), + emoji = emptyList(), + visibility = MastodonVisibility.Public, + sensitive = false, + spoilerText = null, + poll = null, + card = null, + mentions = null, + ) ), inReplyToStatusId = null, inReplyToUserId = null, @@ -222,15 +225,17 @@ private fun Status.toDbStatusWithMediaAndUser( ), is_possibly_sensitive = sensitive ?: false, platformType = PlatformType.Mastodon, - mastodonExtra = DbMastodonStatusExtra( - type = MastodonStatusType.Status, - emoji = emojis ?: emptyList(), - visibility = visibility ?: Visibility.Public, - sensitive = sensitive ?: false, - spoilerText = spoilerText?.takeIf { it.isNotEmpty() }, - poll = poll, - card = card, - mentions = mentions, + extra = Json.encodeToString( + DbMastodonStatusExtra( + type = MastodonStatusType.Status, + emoji = emojis ?: emptyList(), + visibility = visibility.toDbEnums(), + sensitive = sensitive ?: false, + spoilerText = spoilerText?.takeIf { it.isNotEmpty() }, + poll = poll, + card = card, + mentions = mentions, + ) ), previewCard = card?.url?.let { url -> DbPreviewCard( @@ -442,3 +447,10 @@ private fun replaceHashTag(node: Node) { node.childNodes().forEach { replaceHashTag(it) } } } + +private fun Visibility?.toDbEnums() = when (this) { + Visibility.Unlisted -> MastodonVisibility.Unlisted + Visibility.Private -> MastodonVisibility.Private + Visibility.Direct -> MastodonVisibility.Direct + Visibility.Public, null -> MastodonVisibility.Public +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/db/mapper/Twitter.kt b/app/src/main/kotlin/com/twidere/twiderex/db/mapper/Twitter.kt index 9b1e89506..b59955358 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/db/mapper/Twitter.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/db/mapper/Twitter.kt @@ -52,6 +52,7 @@ import com.twidere.twiderex.db.model.toDbStatusReference import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.enums.MediaType import com.twidere.twiderex.model.enums.PlatformType +import com.twidere.twiderex.model.enums.TwitterReplySettings import com.twidere.twiderex.model.ui.ListsMode import com.twidere.twiderex.navigation.RootDeepLinksRouteDefinition import com.twitter.twittertext.Autolink @@ -227,10 +228,11 @@ private fun StatusV2.toDbStatusWithMediaAndUser( id ?: throw IllegalArgumentException("Status.idStr should not be null") ), platformType = PlatformType.Twitter, - mastodonExtra = null, - twitterExtra = DbTwitterStatusExtra( - reply_settings = replySettings ?: ReplySettings.Everyone, - quoteCount = publicMetrics?.quoteCount + extra = Json.encodeToString( + DbTwitterStatusExtra( + reply_settings = replySettings.toDbEnums(), + quoteCount = publicMetrics?.quoteCount + ) ), previewCard = entities?.urls?.firstOrNull() ?.takeUnless { url -> @@ -339,9 +341,10 @@ private fun Status.toDbStatusWithMediaAndUser( idStr ?: throw IllegalArgumentException("Status.idStr should not be null") ), platformType = PlatformType.Twitter, - mastodonExtra = null, - twitterExtra = DbTwitterStatusExtra( - reply_settings = ReplySettings.Everyone, + extra = Json.encodeToString( + DbTwitterStatusExtra( + reply_settings = TwitterReplySettings.Everyone, + ) ), previewCard = entities?.urls?.firstOrNull() ?.takeUnless { url -> quotedStatus?.idStr?.let { id -> url.expandedURL?.endsWith(id) == true } == true } @@ -613,3 +616,9 @@ fun DirectMessageEvent.toDbDirectMessage(accountKey: MicroBlogKey, sender: DbUse sender = sender ) } + +private fun ReplySettings?.toDbEnums() = when (this) { + ReplySettings.MentionedUsers -> TwitterReplySettings.MentionedUsers + ReplySettings.FollowingUsers -> TwitterReplySettings.FollowingUsers + ReplySettings.Everyone, null -> TwitterReplySettings.Everyone +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/db/model/DbStatus.kt b/app/src/main/kotlin/com/twidere/twiderex/db/model/DbStatus.kt index 51f13ffc7..552db62b1 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/db/model/DbStatus.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/db/model/DbStatus.kt @@ -30,12 +30,12 @@ import com.twidere.services.mastodon.model.Card import com.twidere.services.mastodon.model.Emoji import com.twidere.services.mastodon.model.Mention import com.twidere.services.mastodon.model.Poll -import com.twidere.services.mastodon.model.Visibility -import com.twidere.services.twitter.model.ReplySettings import com.twidere.twiderex.db.CacheDatabase import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.enums.MastodonStatusType +import com.twidere.twiderex.model.enums.MastodonVisibility import com.twidere.twiderex.model.enums.PlatformType +import com.twidere.twiderex.model.enums.TwitterReplySettings import kotlinx.serialization.Serializable @Entity( @@ -66,11 +66,10 @@ data class DbStatusV2( val lang: String?, val is_possibly_sensitive: Boolean, val platformType: PlatformType, - var mastodonExtra: DbMastodonStatusExtra? = null, - val twitterExtra: DbTwitterStatusExtra? = null, val previewCard: DbPreviewCard? = null, val inReplyToUserId: String? = null, - val inReplyToStatusId: String? = null + val inReplyToStatusId: String? = null, + val extra: Json ) @Immutable @@ -86,7 +85,7 @@ data class DbPreviewCard( @Immutable @Serializable data class DbTwitterStatusExtra( - val reply_settings: ReplySettings, + val reply_settings: TwitterReplySettings, val quoteCount: Long? = null, ) @@ -95,7 +94,7 @@ data class DbTwitterStatusExtra( data class DbMastodonStatusExtra( val type: MastodonStatusType, val emoji: List, - val visibility: Visibility, + val visibility: MastodonVisibility, val sensitive: Boolean, val spoilerText: String?, val poll: Poll?, diff --git a/app/src/main/kotlin/com/twidere/twiderex/extensions/PagingExtensions.kt b/app/src/main/kotlin/com/twidere/twiderex/extensions/PagingExtensions.kt index cdef9f589..ec14e0e8a 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/extensions/PagingExtensions.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/extensions/PagingExtensions.kt @@ -24,7 +24,7 @@ import androidx.paging.Pager import androidx.paging.map import com.twidere.twiderex.db.model.DbPagingTimelineWithStatus import com.twidere.twiderex.model.MicroBlogKey -import com.twidere.twiderex.model.ui.UiStatus.Companion.toUi +import com.twidere.twiderex.model.transform.toUi import kotlinx.coroutines.flow.map fun Pager.toUi(accountKey: MicroBlogKey) = diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/compose/MastodonComposeJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/compose/MastodonComposeJob.kt index 42cdd72b5..64abd8887 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/jobs/compose/MastodonComposeJob.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/compose/MastodonComposeJob.kt @@ -32,8 +32,8 @@ import com.twidere.twiderex.kmp.FileResolver import com.twidere.twiderex.kmp.RemoteNavigator import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.job.ComposeData +import com.twidere.twiderex.model.transform.toUi import com.twidere.twiderex.model.ui.UiStatus -import com.twidere.twiderex.model.ui.UiStatus.Companion.toUi import com.twidere.twiderex.notification.AppNotificationManager import com.twidere.twiderex.repository.AccountRepository import com.twidere.twiderex.viewmodel.compose.ComposeType diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/compose/TwitterComposeJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/compose/TwitterComposeJob.kt index 67c4baf6e..b629de39e 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/jobs/compose/TwitterComposeJob.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/compose/TwitterComposeJob.kt @@ -30,8 +30,8 @@ import com.twidere.twiderex.kmp.FileResolver import com.twidere.twiderex.kmp.RemoteNavigator import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.job.ComposeData +import com.twidere.twiderex.model.transform.toUi import com.twidere.twiderex.model.ui.UiStatus -import com.twidere.twiderex.model.ui.UiStatus.Companion.toUi import com.twidere.twiderex.notification.AppNotificationManager import com.twidere.twiderex.repository.AccountRepository import com.twidere.twiderex.repository.StatusRepository diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/status/LikeStatusJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/status/LikeStatusJob.kt index f75636165..7ffda9744 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/jobs/status/LikeStatusJob.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/status/LikeStatusJob.kt @@ -24,8 +24,8 @@ import com.twidere.services.microblog.StatusService import com.twidere.twiderex.db.mapper.toDbStatusWithReference import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.job.StatusResult +import com.twidere.twiderex.model.transform.toUi import com.twidere.twiderex.model.ui.UiStatus -import com.twidere.twiderex.model.ui.UiStatus.Companion.toUi import com.twidere.twiderex.notification.InAppNotification import com.twidere.twiderex.repository.AccountRepository import com.twidere.twiderex.repository.StatusRepository diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/status/RetweetStatusJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/status/RetweetStatusJob.kt index 1bcbd788c..fc258fa42 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/jobs/status/RetweetStatusJob.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/status/RetweetStatusJob.kt @@ -24,8 +24,8 @@ import com.twidere.services.microblog.StatusService import com.twidere.twiderex.db.mapper.toDbStatusWithReference import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.job.StatusResult +import com.twidere.twiderex.model.transform.toUi import com.twidere.twiderex.model.ui.UiStatus -import com.twidere.twiderex.model.ui.UiStatus.Companion.toUi import com.twidere.twiderex.notification.InAppNotification import com.twidere.twiderex.repository.AccountRepository import com.twidere.twiderex.repository.StatusRepository diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/status/UnRetweetStatusJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/status/UnRetweetStatusJob.kt index b1e0381c4..87b8532dd 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/jobs/status/UnRetweetStatusJob.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/status/UnRetweetStatusJob.kt @@ -24,8 +24,8 @@ import com.twidere.services.microblog.StatusService import com.twidere.twiderex.db.mapper.toDbStatusWithReference import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.job.StatusResult +import com.twidere.twiderex.model.transform.toUi import com.twidere.twiderex.model.ui.UiStatus -import com.twidere.twiderex.model.ui.UiStatus.Companion.toUi import com.twidere.twiderex.notification.InAppNotification import com.twidere.twiderex.repository.AccountRepository import com.twidere.twiderex.repository.StatusRepository diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/status/UnlikeStatusJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/status/UnlikeStatusJob.kt index f41ae1902..17d470d85 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/jobs/status/UnlikeStatusJob.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/status/UnlikeStatusJob.kt @@ -24,8 +24,8 @@ import com.twidere.services.microblog.StatusService import com.twidere.twiderex.db.mapper.toDbStatusWithReference import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.job.StatusResult +import com.twidere.twiderex.model.transform.toUi import com.twidere.twiderex.model.ui.UiStatus -import com.twidere.twiderex.model.ui.UiStatus.Companion.toUi import com.twidere.twiderex.notification.InAppNotification import com.twidere.twiderex.repository.AccountRepository import com.twidere.twiderex.repository.StatusRepository diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/enums/MastodonStatusType.kt b/app/src/main/kotlin/com/twidere/twiderex/model/enums/Mastodon.kt similarity index 91% rename from app/src/main/kotlin/com/twidere/twiderex/model/enums/MastodonStatusType.kt rename to app/src/main/kotlin/com/twidere/twiderex/model/enums/Mastodon.kt index 26f7197fb..0741f8c5d 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/model/enums/MastodonStatusType.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/model/enums/Mastodon.kt @@ -33,3 +33,11 @@ enum class MastodonStatusType { NotificationPoll, NotificationStatus, } + +@Serializable +enum class MastodonVisibility { + Public, + Unlisted, + Private, + Direct; +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/enums/Twitter.kt b/app/src/main/kotlin/com/twidere/twiderex/model/enums/Twitter.kt new file mode 100644 index 000000000..dca216f91 --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/model/enums/Twitter.kt @@ -0,0 +1,30 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.model.enums + +import kotlinx.serialization.Serializable + +@Serializable +enum class TwitterReplySettings { + Everyone, + MentionedUsers, + FollowingUsers, +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/transform/DmConversationTransform.kt b/app/src/main/kotlin/com/twidere/twiderex/model/transform/DmConversationTransform.kt index 85e67bf71..b851f1cc1 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/model/transform/DmConversationTransform.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/model/transform/DmConversationTransform.kt @@ -26,7 +26,6 @@ import com.twidere.twiderex.db.model.DbDirectMessageConversationWithMessage import com.twidere.twiderex.model.ui.UiDMConversation import com.twidere.twiderex.model.ui.UiDMConversationWithLatestMessage import com.twidere.twiderex.model.ui.UiDMEvent -import com.twidere.twiderex.model.ui.UiUser.Companion.toUi fun DbDMConversation.toUi() = UiDMConversation( accountKey = accountKey, diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/transform/StatusTransform.kt b/app/src/main/kotlin/com/twidere/twiderex/model/transform/StatusTransform.kt new file mode 100644 index 000000000..3a62dfb35 --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/model/transform/StatusTransform.kt @@ -0,0 +1,150 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.model.transform + +import com.twidere.services.mastodon.model.Mention +import com.twidere.twiderex.db.model.DbMastodonStatusExtra +import com.twidere.twiderex.db.model.DbPagingTimelineWithStatus +import com.twidere.twiderex.db.model.DbStatusReaction +import com.twidere.twiderex.db.model.DbStatusV2 +import com.twidere.twiderex.db.model.DbStatusWithMediaAndUser +import com.twidere.twiderex.db.model.DbStatusWithReference +import com.twidere.twiderex.db.model.DbTwitterStatusExtra +import com.twidere.twiderex.db.model.ReferenceType +import com.twidere.twiderex.model.MicroBlogKey +import com.twidere.twiderex.model.enums.PlatformType +import com.twidere.twiderex.model.ui.UiMedia +import com.twidere.twiderex.model.ui.UiStatus +import com.twidere.twiderex.model.ui.UiUrlEntity +import com.twidere.twiderex.model.ui.UiUser +import com.twidere.twiderex.model.ui.mastodon.MastodonMention +import com.twidere.twiderex.model.ui.mastodon.MastodonStatusExtra +import com.twidere.twiderex.model.ui.twitter.TwitterStatusExtra +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json + +fun DbStatusV2.toUi( + user: UiUser, + media: List, + url: List, + reaction: DbStatusReaction?, + isGap: Boolean, + referenceStatus: Map = emptyMap(), +) = UiStatus( + statusId = statusId, + htmlText = htmlText, + timestamp = timestamp, + retweetCount = retweetCount, + likeCount = likeCount, + replyCount = replyCount, + retweeted = reaction?.retweeted ?: false, + liked = reaction?.liked ?: false, + placeString = placeString, + hasMedia = hasMedia, + user = user, + media = media, + isGap = isGap, + source = source, + url = url, + statusKey = statusKey, + rawText = rawText, + platformType = platformType, + extra = when (platformType) { + PlatformType.Twitter -> Json.decodeFromString(extra).toUi() + PlatformType.StatusNet -> TODO() + PlatformType.Fanfou -> TODO() + PlatformType.Mastodon -> Json.decodeFromString(extra).toUi() + }, + referenceStatus = referenceStatus, + linkPreview = previewCard, + inReplyToStatusId = inReplyToStatusId, + inReplyToUserId = inReplyToStatusId +) + +fun DbStatusWithMediaAndUser.toUi( + accountKey: MicroBlogKey, +): UiStatus { + val reaction = reactions.firstOrNull { it.accountKey == accountKey } + return data.toUi( + user = user.toUi(), + media = media.toUi(), + url = url.toUi(), + isGap = false, + reaction = reaction + ) +} + +fun DbStatusWithReference.toUi( + accountKey: MicroBlogKey, +) = with(status) { + val reaction = reactions.firstOrNull { it.accountKey == accountKey } + data.toUi( + user = user.toUi(), + media = media.toUi(), + isGap = false, + url = url.toUi(), + reaction = reaction, + referenceStatus = references.map { + it.reference.referenceType to it.status.toUi( + accountKey = accountKey + ) + }.toMap() + ) +} + +fun DbPagingTimelineWithStatus.toUi( + accountKey: MicroBlogKey, +) = with(status.status) { + val reaction = reactions.firstOrNull { it.accountKey == accountKey } + data.toUi( + user = user.toUi(), + media = media.toUi(), + isGap = timeline.isGap, + url = url.toUi(), + reaction = reaction, + referenceStatus = status.references.map { + it.reference.referenceType to it.status.toUi( + accountKey = accountKey + ) + }.toMap() + ) +} + +fun DbTwitterStatusExtra.toUi() = TwitterStatusExtra( + reply_settings = reply_settings, + quoteCount = quoteCount +) + +fun DbMastodonStatusExtra.toUi() = MastodonStatusExtra( + type = type, + emoji = emoji.toUi(), + visibility = visibility, + mentions = mentions?.toUi() +) + +fun List.toUi() = map { + MastodonMention( + id = it.id, + username = it.username, + url = it.url, + acct = it.acct + ) +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/transform/UserTransform.kt b/app/src/main/kotlin/com/twidere/twiderex/model/transform/UserTransform.kt new file mode 100644 index 000000000..67cb91bff --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/model/transform/UserTransform.kt @@ -0,0 +1,87 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.model.transform + +import com.twidere.twiderex.db.model.DbMastodonUserExtra +import com.twidere.twiderex.db.model.DbTwitterUserExtra +import com.twidere.twiderex.db.model.DbUser +import com.twidere.twiderex.model.enums.PlatformType +import com.twidere.twiderex.model.ui.UiUrlEntity +import com.twidere.twiderex.model.ui.UiUser +import com.twidere.twiderex.model.ui.mastodon.Field +import com.twidere.twiderex.model.ui.mastodon.MastodonUserExtra +import com.twidere.twiderex.model.ui.twitter.TwitterUserExtra +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json + +fun DbUser.toUi() = + UiUser( + id = userId, + name = name, + screenName = screenName, + profileImage = profileImage, + profileBackgroundImage = profileBackgroundImage, + followersCount = followersCount, + friendsCount = friendsCount, + listedCount = listedCount, + statusesCount = statusesCount, + rawDesc = rawDesc, + htmlDesc = htmlDesc, + website = website, + location = location, + verified = verified, + protected = isProtected, + userKey = userKey, + platformType = platformType, + extra = when (platformType) { + PlatformType.Twitter -> Json.decodeFromString(extra).toUi() + PlatformType.StatusNet -> TODO() + PlatformType.Fanfou -> TODO() + PlatformType.Mastodon -> Json.decodeFromString(extra).toUi() + }, + acct = acct, + ) + +fun DbTwitterUserExtra.toUi() = TwitterUserExtra( + pinned_tweet_id = pinned_tweet_id, + url = url.map { url -> + UiUrlEntity( + url = url.displayUrl, + expandedUrl = url.expandedUrl, + displayUrl = url.displayUrl, + title = null, + description = null, + image = null + ) + } +) + +fun DbMastodonUserExtra.toUi() = MastodonUserExtra( + emoji = emoji.toUi(), + bot = bot, + locked = locked, + fields = fields.map { field -> + Field( + field.name, + field.value + ) + } +) diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiStatus.kt b/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiStatus.kt index edfb37ef9..598bcd07b 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiStatus.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiStatus.kt @@ -24,18 +24,13 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.ui.res.stringResource import com.twidere.twiderex.R -import com.twidere.twiderex.db.model.DbMastodonStatusExtra -import com.twidere.twiderex.db.model.DbPagingTimelineWithStatus import com.twidere.twiderex.db.model.DbPreviewCard -import com.twidere.twiderex.db.model.DbStatusWithMediaAndUser -import com.twidere.twiderex.db.model.DbStatusWithReference -import com.twidere.twiderex.db.model.DbTwitterStatusExtra import com.twidere.twiderex.db.model.ReferenceType import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.enums.MastodonStatusType import com.twidere.twiderex.model.enums.PlatformType -import com.twidere.twiderex.model.transform.toUi -import com.twidere.twiderex.model.ui.UiUser.Companion.toUi +import com.twidere.twiderex.model.ui.mastodon.MastodonStatusExtra +import com.twidere.twiderex.model.ui.twitter.TwitterStatusExtra @Immutable data class UiStatus( @@ -57,13 +52,16 @@ data class UiStatus( val isGap: Boolean, val url: List, val platformType: PlatformType, - val mastodonExtra: DbMastodonStatusExtra? = null, - val twitterExtra: DbTwitterStatusExtra? = null, val linkPreview: DbPreviewCard? = null, val referenceStatus: Map = emptyMap(), val inReplyToUserId: String? = null, val inReplyToStatusId: String? = null, + val extra: StatusExtra? = null ) { + val mastodonExtra: MastodonStatusExtra? = if (extra is MastodonStatusExtra) extra else null + + val twitterExtra: TwitterStatusExtra? = if (extra is TwitterStatusExtra) extra else null + val retweet: UiStatus? by lazy { if (platformType == PlatformType.Mastodon && mastodonExtra != null && mastodonExtra.type != MastodonStatusType.Status) { referenceStatus[ReferenceType.MastodonNotification] @@ -118,108 +116,7 @@ data class UiStatus( rawText = "", platformType = PlatformType.Twitter, ) - - fun DbStatusWithMediaAndUser.toUi( - accountKey: MicroBlogKey, - ): UiStatus { - val reaction = reactions.firstOrNull { it.accountKey == accountKey } - return UiStatus( - statusId = data.statusId, - htmlText = data.htmlText, - timestamp = data.timestamp, - retweetCount = data.retweetCount, - likeCount = data.likeCount, - replyCount = data.replyCount, - retweeted = reaction?.retweeted ?: false, - liked = reaction?.liked ?: false, - placeString = data.placeString, - hasMedia = data.hasMedia, - user = user.toUi(), - media = media.toUi(), - isGap = false, - source = data.source, - url = url.toUi(), - statusKey = data.statusKey, - rawText = data.rawText, - platformType = data.platformType, - mastodonExtra = data.mastodonExtra, - twitterExtra = data.twitterExtra, - linkPreview = data.previewCard, - inReplyToStatusId = data.inReplyToStatusId, - inReplyToUserId = data.inReplyToStatusId - ) - } - - fun DbStatusWithReference.toUi( - accountKey: MicroBlogKey, - ) = with(status) { - val reaction = reactions.firstOrNull { it.accountKey == accountKey } - UiStatus( - statusId = data.statusId, - htmlText = data.htmlText, - timestamp = data.timestamp, - retweetCount = data.retweetCount, - likeCount = data.likeCount, - replyCount = data.replyCount, - retweeted = reaction?.retweeted ?: false, - liked = reaction?.liked ?: false, - placeString = data.placeString, - hasMedia = data.hasMedia, - user = user.toUi(), - media = media.toUi(), - isGap = false, - source = data.source, - url = url.toUi(), - statusKey = data.statusKey, - rawText = data.rawText, - platformType = data.platformType, - mastodonExtra = data.mastodonExtra, - twitterExtra = data.twitterExtra, - referenceStatus = references.map { - it.reference.referenceType to it.status.toUi( - accountKey = accountKey - ) - }.toMap(), - linkPreview = data.previewCard, - inReplyToUserId = data.inReplyToUserId, - inReplyToStatusId = data.inReplyToStatusId - ) - } - - fun DbPagingTimelineWithStatus.toUi( - accountKey: MicroBlogKey, - ) = with(status.status) { - val reaction = reactions.firstOrNull { it.accountKey == accountKey } - UiStatus( - statusId = data.statusId, - htmlText = data.htmlText, - timestamp = data.timestamp, - retweetCount = data.retweetCount, - likeCount = data.likeCount, - replyCount = data.replyCount, - retweeted = reaction?.retweeted ?: false, - liked = reaction?.liked ?: false, - placeString = data.placeString, - hasMedia = data.hasMedia, - user = user.toUi(), - media = media.toUi(), - isGap = timeline.isGap, - source = data.source, - url = url.toUi(), - statusKey = data.statusKey, - rawText = data.rawText, - platformType = data.platformType, - mastodonExtra = data.mastodonExtra, - twitterExtra = data.twitterExtra, - referenceStatus = status.references.map { - it.reference.referenceType to it.status.toUi( - accountKey = accountKey - ) - }.toMap(), - linkPreview = data.previewCard, - inReplyToUserId = data.inReplyToUserId, - inReplyToStatusId = data.inReplyToStatusId - ) - } } } + +interface StatusExtra diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiUser.kt b/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiUser.kt index a0b983bc0..1af82c72f 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiUser.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiUser.kt @@ -24,17 +24,10 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.ui.res.painterResource import com.twidere.twiderex.R -import com.twidere.twiderex.db.model.DbMastodonUserExtra -import com.twidere.twiderex.db.model.DbTwitterUserExtra -import com.twidere.twiderex.db.model.DbUser import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.enums.PlatformType -import com.twidere.twiderex.model.transform.toUi -import com.twidere.twiderex.model.ui.mastodon.Field import com.twidere.twiderex.model.ui.mastodon.MastodonUserExtra import com.twidere.twiderex.model.ui.twitter.TwitterUserExtra -import kotlinx.serialization.decodeFromString -import kotlinx.serialization.json.Json @Immutable data class UiUser( @@ -68,11 +61,9 @@ data class UiUser( "@$screenName" } } - val twitterExtra: TwitterUserExtra? - get() = if (extra is TwitterUserExtra) extra else null + val twitterExtra: TwitterUserExtra? = if (extra is TwitterUserExtra) extra else null - val mastodonExtra: MastodonUserExtra? - get() = if (extra is MastodonUserExtra) extra else null + val mastodonExtra: MastodonUserExtra? = if (extra is MastodonUserExtra) extra else null companion object { @Composable @@ -96,60 +87,6 @@ data class UiUser( platformType = PlatformType.Twitter, acct = MicroBlogKey.twitter("TwidereProject") ) - - fun DbUser.toUi() = - UiUser( - id = userId, - name = name, - screenName = screenName, - profileImage = profileImage, - profileBackgroundImage = profileBackgroundImage, - followersCount = followersCount, - friendsCount = friendsCount, - listedCount = listedCount, - statusesCount = statusesCount, - rawDesc = rawDesc, - htmlDesc = htmlDesc, - website = website, - location = location, - verified = verified, - protected = isProtected, - userKey = userKey, - platformType = platformType, - extra = when (platformType) { - PlatformType.Twitter -> Json.decodeFromString(extra).let { - TwitterUserExtra( - pinned_tweet_id = it.pinned_tweet_id, - url = it.url.map { url -> - UiUrlEntity( - url = url.displayUrl, - expandedUrl = url.expandedUrl, - displayUrl = url.displayUrl, - title = null, - description = null, - image = null - ) - } - ) - } - PlatformType.StatusNet -> TODO() - PlatformType.Fanfou -> TODO() - PlatformType.Mastodon -> Json.decodeFromString(extra).let { - MastodonUserExtra( - emoji = it.emoji.toUi(), - bot = it.bot, - locked = it.locked, - fields = it.fields.map { - Field( - it.name, - it.value - ) - } - ) - } - }, - acct = acct, - ) } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/ui/mastodon/MastodonStatusExtra.kt b/app/src/main/kotlin/com/twidere/twiderex/model/ui/mastodon/MastodonStatusExtra.kt new file mode 100644 index 000000000..dd2707373 --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/model/ui/mastodon/MastodonStatusExtra.kt @@ -0,0 +1,40 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.model.ui.mastodon + +import com.twidere.twiderex.model.enums.MastodonStatusType +import com.twidere.twiderex.model.enums.MastodonVisibility +import com.twidere.twiderex.model.ui.StatusExtra +import com.twidere.twiderex.model.ui.UiEmojiCategory + +data class MastodonStatusExtra( + val type: MastodonStatusType, + val emoji: List, + val visibility: MastodonVisibility, + val mentions: List?, +) : StatusExtra + +data class MastodonMention( + val id: String? = null, + val username: String? = null, + val url: String? = null, + val acct: String? = null +) diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/ui/twitter/TwitterStatusExtra.kt b/app/src/main/kotlin/com/twidere/twiderex/model/ui/twitter/TwitterStatusExtra.kt new file mode 100644 index 000000000..90f294f64 --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/model/ui/twitter/TwitterStatusExtra.kt @@ -0,0 +1,29 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.model.ui.twitter + +import com.twidere.twiderex.model.enums.TwitterReplySettings +import com.twidere.twiderex.model.ui.StatusExtra + +data class TwitterStatusExtra( + val reply_settings: TwitterReplySettings, + val quoteCount: Long? = null, +) : StatusExtra diff --git a/app/src/main/kotlin/com/twidere/twiderex/paging/mediator/list/ListsUserPagingMediator.kt b/app/src/main/kotlin/com/twidere/twiderex/paging/mediator/list/ListsUserPagingMediator.kt index 18e290006..629353a6c 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/paging/mediator/list/ListsUserPagingMediator.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/paging/mediator/list/ListsUserPagingMediator.kt @@ -28,8 +28,8 @@ import com.twidere.services.microblog.model.IPaging import com.twidere.services.microblog.model.IUser import com.twidere.twiderex.db.mapper.toDbUser import com.twidere.twiderex.model.MicroBlogKey +import com.twidere.twiderex.model.transform.toUi import com.twidere.twiderex.model.ui.UiUser -import com.twidere.twiderex.model.ui.UiUser.Companion.toUi import com.twidere.twiderex.paging.crud.MemoryCachePagingMediator import com.twidere.twiderex.paging.crud.PagingMemoryCache diff --git a/app/src/main/kotlin/com/twidere/twiderex/paging/source/FollowersPagingSource.kt b/app/src/main/kotlin/com/twidere/twiderex/paging/source/FollowersPagingSource.kt index 792c3e11e..14277c844 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/paging/source/FollowersPagingSource.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/paging/source/FollowersPagingSource.kt @@ -26,8 +26,8 @@ import com.twidere.services.microblog.RelationshipService import com.twidere.services.microblog.model.IPaging import com.twidere.twiderex.db.mapper.toDbUser import com.twidere.twiderex.model.MicroBlogKey +import com.twidere.twiderex.model.transform.toUi import com.twidere.twiderex.model.ui.UiUser -import com.twidere.twiderex.model.ui.UiUser.Companion.toUi class FollowersPagingSource( private val userKey: MicroBlogKey, diff --git a/app/src/main/kotlin/com/twidere/twiderex/paging/source/FollowingPagingSource.kt b/app/src/main/kotlin/com/twidere/twiderex/paging/source/FollowingPagingSource.kt index d98ea91b9..c6b31390b 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/paging/source/FollowingPagingSource.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/paging/source/FollowingPagingSource.kt @@ -26,8 +26,8 @@ import com.twidere.services.microblog.RelationshipService import com.twidere.services.microblog.model.IPaging import com.twidere.twiderex.db.mapper.toDbUser import com.twidere.twiderex.model.MicroBlogKey +import com.twidere.twiderex.model.transform.toUi import com.twidere.twiderex.model.ui.UiUser -import com.twidere.twiderex.model.ui.UiUser.Companion.toUi class FollowingPagingSource( private val userKey: MicroBlogKey, diff --git a/app/src/main/kotlin/com/twidere/twiderex/paging/source/SearchUserPagingSource.kt b/app/src/main/kotlin/com/twidere/twiderex/paging/source/SearchUserPagingSource.kt index 8b22299e2..58b71b1ad 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/paging/source/SearchUserPagingSource.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/paging/source/SearchUserPagingSource.kt @@ -26,8 +26,8 @@ import com.twidere.services.microblog.SearchService import com.twidere.twiderex.db.mapper.toDbUser import com.twidere.twiderex.defaultLoadCount import com.twidere.twiderex.model.MicroBlogKey +import com.twidere.twiderex.model.transform.toUi import com.twidere.twiderex.model.ui.UiUser -import com.twidere.twiderex.model.ui.UiUser.Companion.toUi class SearchUserPagingSource( private val accountKey: MicroBlogKey, diff --git a/app/src/main/kotlin/com/twidere/twiderex/paging/source/UserPagingSource.kt b/app/src/main/kotlin/com/twidere/twiderex/paging/source/UserPagingSource.kt index 0fc7b19e5..db4a7a96b 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/paging/source/UserPagingSource.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/paging/source/UserPagingSource.kt @@ -26,8 +26,8 @@ import com.twidere.services.microblog.model.IPaging import com.twidere.services.microblog.model.IUser import com.twidere.twiderex.db.mapper.toDbUser import com.twidere.twiderex.model.MicroBlogKey +import com.twidere.twiderex.model.transform.toUi import com.twidere.twiderex.model.ui.UiUser -import com.twidere.twiderex.model.ui.UiUser.Companion.toUi abstract class UserPagingSource( protected val userKey: MicroBlogKey, diff --git a/app/src/main/kotlin/com/twidere/twiderex/repository/NotificationRepository.kt b/app/src/main/kotlin/com/twidere/twiderex/repository/NotificationRepository.kt index bed314459..ab2708500 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/repository/NotificationRepository.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/repository/NotificationRepository.kt @@ -28,8 +28,8 @@ import com.twidere.twiderex.db.model.DbNotificationCursor import com.twidere.twiderex.db.model.NotificationCursorType import com.twidere.twiderex.model.AccountDetails import com.twidere.twiderex.model.MicroBlogKey +import com.twidere.twiderex.model.transform.toUi import com.twidere.twiderex.model.ui.UiStatus -import com.twidere.twiderex.model.ui.UiStatus.Companion.toUi import java.util.UUID class NotificationRepository( diff --git a/app/src/main/kotlin/com/twidere/twiderex/repository/StatusRepository.kt b/app/src/main/kotlin/com/twidere/twiderex/repository/StatusRepository.kt index ad9788c22..c2db0bb16 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/repository/StatusRepository.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/repository/StatusRepository.kt @@ -35,8 +35,8 @@ import com.twidere.twiderex.extensions.toUi import com.twidere.twiderex.model.AccountDetails import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.enums.PlatformType +import com.twidere.twiderex.model.transform.toUi import com.twidere.twiderex.model.ui.UiStatus -import com.twidere.twiderex.model.ui.UiStatus.Companion.toUi import com.twidere.twiderex.paging.mediator.paging.pager import com.twidere.twiderex.paging.mediator.status.MastodonStatusContextMediator import com.twidere.twiderex.paging.mediator.status.TwitterConversationMediator diff --git a/app/src/main/kotlin/com/twidere/twiderex/repository/UserRepository.kt b/app/src/main/kotlin/com/twidere/twiderex/repository/UserRepository.kt index 72cbe37d0..e308a6982 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/repository/UserRepository.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/repository/UserRepository.kt @@ -26,8 +26,8 @@ import com.twidere.twiderex.db.mapper.toDbUser import com.twidere.twiderex.db.model.DbUser import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.toAmUser +import com.twidere.twiderex.model.transform.toUi import com.twidere.twiderex.model.ui.UiUser -import com.twidere.twiderex.model.ui.UiUser.Companion.toUi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/search/SearchTweetsViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/search/SearchTweetsViewModel.kt index 884a9f35e..d21019bfb 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/search/SearchTweetsViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/search/SearchTweetsViewModel.kt @@ -27,7 +27,7 @@ import androidx.paging.map import com.twidere.services.microblog.SearchService import com.twidere.twiderex.db.CacheDatabase import com.twidere.twiderex.model.AccountDetails -import com.twidere.twiderex.model.ui.UiStatus.Companion.toUi +import com.twidere.twiderex.model.transform.toUi import com.twidere.twiderex.paging.mediator.paging.pager import com.twidere.twiderex.paging.mediator.search.SearchStatusMediator import dagger.assisted.Assisted diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/twitter/search/TwitterSearchMediaViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/twitter/search/TwitterSearchMediaViewModel.kt index 7f56e40d6..31913339d 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/twitter/search/TwitterSearchMediaViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/twitter/search/TwitterSearchMediaViewModel.kt @@ -28,7 +28,7 @@ import androidx.paging.map import com.twidere.services.twitter.TwitterService import com.twidere.twiderex.db.CacheDatabase import com.twidere.twiderex.model.AccountDetails -import com.twidere.twiderex.model.ui.UiStatus.Companion.toUi +import com.twidere.twiderex.model.transform.toUi import com.twidere.twiderex.paging.mediator.paging.pager import com.twidere.twiderex.paging.mediator.search.SearchMediaMediator import dagger.assisted.Assisted diff --git a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/user/UserMediaTimelineViewModel.kt b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/user/UserMediaTimelineViewModel.kt index 7a170de31..38f68d0bb 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/viewmodel/user/UserMediaTimelineViewModel.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/viewmodel/user/UserMediaTimelineViewModel.kt @@ -31,9 +31,9 @@ import com.twidere.services.microblog.TimelineService import com.twidere.twiderex.db.CacheDatabase import com.twidere.twiderex.model.AccountDetails import com.twidere.twiderex.model.MicroBlogKey +import com.twidere.twiderex.model.transform.toUi import com.twidere.twiderex.model.ui.UiMedia import com.twidere.twiderex.model.ui.UiStatus -import com.twidere.twiderex.model.ui.UiStatus.Companion.toUi import com.twidere.twiderex.paging.mediator.paging.PagingMediator import com.twidere.twiderex.paging.mediator.paging.pager import com.twidere.twiderex.paging.mediator.user.UserMediaMediator diff --git a/app/src/test/java/com/twidere/twiderex/repository/ListsUsersRepositoryTest.kt b/app/src/test/java/com/twidere/twiderex/repository/ListsUsersRepositoryTest.kt index a448fc16c..dc345f02a 100644 --- a/app/src/test/java/com/twidere/twiderex/repository/ListsUsersRepositoryTest.kt +++ b/app/src/test/java/com/twidere/twiderex/repository/ListsUsersRepositoryTest.kt @@ -25,8 +25,8 @@ import com.twidere.twiderex.db.mapper.toDbUser import com.twidere.twiderex.mock.MockCenter import com.twidere.twiderex.model.AccountDetails import com.twidere.twiderex.model.MicroBlogKey +import com.twidere.twiderex.model.transform.toUi import com.twidere.twiderex.model.ui.UiUser -import com.twidere.twiderex.model.ui.UiUser.Companion.toUi import com.twidere.twiderex.paging.crud.PagingMemoryCache import kotlinx.coroutines.runBlocking import org.junit.After From f4e3ddcd8d53de30b6786d5fc9d0aa9106868900 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Wed, 4 Aug 2021 15:47:46 +0800 Subject: [PATCH 120/137] sync localization from crowdin --- .../res-localized/values-es-rES/strings.xml | 2 + .../res-localized/values-pt-rBR/strings.xml | 2 + .../res-localized/values-tr-rTR/strings.xml | 234 ++++++++++++++++++ 3 files changed, 238 insertions(+) diff --git a/app/src/main/res-localized/values-es-rES/strings.xml b/app/src/main/res-localized/values-es-rES/strings.xml index a068e8331..b3ac19745 100644 --- a/app/src/main/res-localized/values-es-rES/strings.xml +++ b/app/src/main/res-localized/values-es-rES/strings.xml @@ -188,6 +188,7 @@ Nombre Nueva lista Descripción + Local Buscar personas Añadir Miembro Añadir @@ -205,6 +206,7 @@ Buscar Mostrar más Búsqueda guardada + Federada No se han encontrado miembros. Suscriptores Lista de miembros diff --git a/app/src/main/res-localized/values-pt-rBR/strings.xml b/app/src/main/res-localized/values-pt-rBR/strings.xml index ffc4e0e9a..2d5957648 100644 --- a/app/src/main/res-localized/values-pt-rBR/strings.xml +++ b/app/src/main/res-localized/values-pt-rBR/strings.xml @@ -188,6 +188,7 @@ Nome Nova Lista Descrição + Local Buscar pessoas Adicionar Membro Adicionar @@ -205,6 +206,7 @@ Buscar Mostrar mais Busca Salva + Federado Nenhum Membro Encontrado. Inscritos Listar Membros diff --git a/app/src/main/res-localized/values-tr-rTR/strings.xml b/app/src/main/res-localized/values-tr-rTR/strings.xml index 7be0690c4..4956a5bf9 100644 --- a/app/src/main/res-localized/values-tr-rTR/strings.xml +++ b/app/src/main/res-localized/values-tr-rTR/strings.xml @@ -1,13 +1,17 @@ %s sessizden çıkarıldı %s adlı kullanıcı takipten çıkılsın mı? + İndirme tamamlandıktan sonra medya paylaşılacak Takip Bırakılamadı Lütfen tekrar deneyin Tweet Gönderildi + Giriş yapılmadı + Sunuru URLsi hatalı. Çok Fazla İstek Takip Edildi Takip Edilemedi Lütfen tekrar deneyin + %s spam için şikayet edildi Twitter Kuralları Hesap Askıya Alındı Twitter, %s ihlal eden hesapları askıya alır @@ -15,15 +19,21 @@ Lütfen tekrar deneyin %s engellendi %s takip isteği iptal edilsin mi? + %s Şikayet edilemedi Lütfen tekrar deneyin + %s spam için şikayet edildi ve engellendi Fotoğrafı kaydetme başarısız Lütfen tekrar deneyin Tweet Bulunamadı Medya kaydediliyor + Giriş yapılmadı + Bağlantı zaman aşımına uğradı. %s engeli kaldırıldı %s sessize alındı İzin Reddedildi Üzgünüm, yetkiniz yok + Mesaj gönderiliyor + Mesaj gönderilemedi %s sessizden çıkarılamadı Lütfen tekrar deneyin Twitter API kullanım sınırına ulaşıldı @@ -47,11 +57,235 @@ Tweet gönderilemedi Lütfen tekrar deneyin Takip Bırakıldı + %s bildirilemedi ve engellenemedi Lütfen tekrar deneyin %s engeli kaldırılamadı Lütfen tekrar deneyin + Mesajlar + Direkt mesajlar + Etkileşimler + Etiketler ve retweetler gibi etkileşimler + Arkaplan ilerleyişleri + %s sana takip isteği gönderdi + Anketiniz sona erdi + %s seni etiketledi + Yeni direkt mesaj + %s sana mesaj gönderdi + Oy verdiğiniz anket sona erdi + %s seni takip etti + %s gönderi paylaştı + %s Engelle + takipçi takipçiler %s sizi takip etmiyor Sizi takip ediyor + %s Sustur + %s seni takip ediyor + Engeli kaldır + Şikayet et ve engelle + Takip ediliyor + Beklemede + Şikayet et + Sustur + Sessizden çıkar + Engelle + Takip et + Takibi bırak + Takipçiler + Sıralanan + Takip ediliyor + Daha fazla yükle + Fotoğraf kitaplığı + Ekle + İptal + Önizleme + Medyayı paylaş + Düzenle + Safari\'de Aç + Evet + Kaydet + Kaldır + Onayla + Fotoğrafı kaydet + Fotoğraf çek + Giriş yap + %s retweetledi + Medya + %s kişi + %s oy + Kapandı + %s oylar + %s kişi + Bu konuyu göster + Linki paylaş + Tweeti sil + Alıntıla + Metni kopyala + Oy ver + Linki kopyala + %s alıntı + %s alıntılar + %s retweetler + %s beğeni + %s beğeniler + %s üye + %s üyeler + %s fotoğraf + %s fotoğraflar + %s tweetler + %s yanıt + %s yanıtlar + Daha fazla + Geri + Video oynat + Kapat + Bitti + Konum + Medya + Yanıt + Kaydet + Ekle + Medya + Konum + Listeyi yeniden adlandır + Bir liste oluştur + Gizli + Liste düzenle + İsim + Yeni liste + Açıklama + Yerel + Üye ekle + Ekle + Kaldır + Hepsi + Bildirim + Hesabı sil + Hesaplar + Daha az göster + Tweetler veya kullanıcılar ara + Medya + Tweetler + Kullanıcılar + Ara + Daha fazla göster + Kaydedilmiş arama + Üyeler bulunmadı. + Aboneler + Üyeleri listele + Bu listeyi sil + Üye ekle + Liste ayrıntıları + %d üyeler + 1 Abone + 1 üye + %d Aboneler + Bu listeyi sil: %s + Liste düzenle + Listeyi yeniden adlandır + Listeyi sil + Takip et + Takibi bırak + Üye ekle + Mastodon ile giriş yap + Merhaba!\nBaşlamak için giriş yap. + Twitter ile giriş yap + Özel Twitter Anahtarı ile giriş yap + Twitter API v2 erişimi gereklidir. + Yetkilendirme + Düzen + Sekme çubuğu eylemleri + Çekmece eylemleri + Özel düzen + Bildirim göster + Hesaplar + Bildirim + Kaydırırken sekme çubuğunu gizle + Kaydırırken uygulama çubuğunu gizle + @TwidereProject kullandığınız için teşekkürler! + Url önizleme + Sistem yazı tipi boyutunu kullan + Yuvarlatılmış kare + Avatar tarzı + Daire + Daima + Medya önizleme + Otomatik + Otomatik oynatma + Kapalı + Görünüm + Önizleme + Tarih biçimi + Metin + Medya + Lisans + Android 5.0+ için yeni nesil Twidere. \nHala erken aşamada. + Sayfa arka plan logosu hakkında + Hakkında + Sür %s + Tüm Twidere X önbelleğini sil. Hesap kimliğiniz kaybolmaz. + Tüm önbelleği temizle + Arama geçmişini temizle + Saklı medya önbelleğini temizle. + Medya önbelleğini temizle + Depolama + Sunucu + Şifre + Proxy server bağlantısı numaralar olmalı + Tüm ağ istekleri için proxy kullan + Proxy + Proxy ayarları + Proxy türü + Ters + Kullanıcı adı + Proje URLsi + Üçüncü parti Twitter veri sağlayıcı + Twitter API kısıtlaması nedeniyle, bazı veriler Twitter\'dan alınmayabilir, bu verileri sağlamak için üçüncü taraf bir veri sağlayıcısı kullanabilirsiniz. Twidere onlar için herhangi bir sorumluluk kabul etmez. + Üçüncü parti Twitter veri sağlayıcı + Tüm tweetler + Yanıtları çıkart + Yanıtı gizle + Ben İzin Reddedildi + Bu kullanıcının profilini görüntülemeniz engellendi. + Hesapları yönet + Giriş yap + Taslaklar + Taslağı sil + Taslağı düzenle + Kullanıcıları ara + Takipçiler + Sıralanan + Taslağı kaydet + Taslağı kaydet? + Gizli + Herkese açık + Liste dışı + Direkt + Uyarınızı buraya yazın + Ne oluyor? + Alıntı + Yanıt + Çoklu seçim + 1 gün + 30 dakika + 7 gün + 1 saat + 3 gün + 5 dakika + 6 saat + Yanıt veriliyor + ABONE OLUNDU + LİSTELERİM + Listeler + Özel görünürlük + Liste oluştur + Etiketler + Takip ediliyor + Zaman tüneli + İnsan bul + Mesaj metnini kopyala + Senin için mesajı sil + Mesajlar + Hashtag ara \ No newline at end of file From 2cb0253c065c4d4c49c40adb103facd0e2d254eb Mon Sep 17 00:00:00 2001 From: itsMimao Date: Wed, 4 Aug 2021 16:01:50 +0800 Subject: [PATCH 121/137] redesigned UiStatus --- .../19.json | 24 ++-- .../component/navigation/Navigator.kt | 2 +- .../status/DetailedStatusComponent.kt | 16 +-- .../twiderex/component/status/MastodonPoll.kt | 44 +++---- .../component/status/StatusActions.kt | 6 +- .../component/status/StatusMediaComponent.kt | 2 +- .../twiderex/component/status/StatusText.kt | 8 +- .../status/TimelineStatusComponent.kt | 16 +-- .../twiderex/db/dao/StatusReferenceDao.kt | 2 +- .../twidere/twiderex/db/mapper/Mastodon.kt | 2 +- .../com/twidere/twiderex/db/mapper/Twitter.kt | 2 +- .../com/twidere/twiderex/db/model/DbStatus.kt | 17 ++- .../twiderex/db/model/DbStatusReference.kt | 8 +- .../twiderex/extensions/MastodonExtensions.kt | 22 ++-- .../twiderex/jobs/common/NotificationJob.kt | 2 +- .../jobs/compose/MastodonComposeJob.kt | 9 +- .../twiderex/jobs/status/LikeStatusJob.kt | 4 +- .../twiderex/jobs/status/MastodonVoteJob.kt | 37 +++--- .../twiderex/jobs/status/RetweetStatusJob.kt | 4 +- .../jobs/status/UnRetweetStatusJob.kt | 4 +- .../twiderex/jobs/status/UnlikeStatusJob.kt | 4 +- .../twiderex/model/enums/ReferenceType.kt | 31 +++++ .../twidere/twiderex/model/job/ComposeData.kt | 4 +- .../model/transform/StatusTransform.kt | 109 +++++++++++++----- .../model/transform/WorkDataTransform.kt | 4 +- .../com/twidere/twiderex/model/ui/UiCard.kt | 29 +++++ .../com/twidere/twiderex/model/ui/UiGeo.kt | 27 +++++ .../com/twidere/twiderex/model/ui/UiPoll.kt | 38 ++++++ .../com/twidere/twiderex/model/ui/UiStatus.kt | 31 +++-- .../paging/mediator/user/UserMediaMediator.kt | 2 +- .../twiderex/scenes/compose/ComposeScene.kt | 6 +- .../viewmodel/compose/ComposeViewModel.kt | 6 +- 32 files changed, 361 insertions(+), 161 deletions(-) create mode 100644 app/src/main/kotlin/com/twidere/twiderex/model/enums/ReferenceType.kt create mode 100644 app/src/main/kotlin/com/twidere/twiderex/model/ui/UiCard.kt create mode 100644 app/src/main/kotlin/com/twidere/twiderex/model/ui/UiGeo.kt create mode 100644 app/src/main/kotlin/com/twidere/twiderex/model/ui/UiPoll.kt diff --git a/app/schemas/com.twidere.twiderex.db.CacheDatabase/19.json b/app/schemas/com.twidere.twiderex.db.CacheDatabase/19.json index f9c6d8760..8d70ac2ae 100644 --- a/app/schemas/com.twidere.twiderex.db.CacheDatabase/19.json +++ b/app/schemas/com.twidere.twiderex.db.CacheDatabase/19.json @@ -2,11 +2,11 @@ "formatVersion": 1, "database": { "version": 19, - "identityHash": "7304de2dc43bca1acdd86fcbf0c78ab2", + "identityHash": "a371ddef3fc92cdd0d0914cd1df452a2", "entities": [ { "tableName": "status", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` TEXT NOT NULL, `statusId` TEXT NOT NULL, `statusKey` TEXT NOT NULL, `htmlText` TEXT NOT NULL, `rawText` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `retweetCount` INTEGER NOT NULL, `likeCount` INTEGER NOT NULL, `replyCount` INTEGER NOT NULL, `placeString` TEXT, `source` TEXT NOT NULL, `hasMedia` INTEGER NOT NULL, `userKey` TEXT NOT NULL, `lang` TEXT, `is_possibly_sensitive` INTEGER NOT NULL, `platformType` TEXT NOT NULL, `mastodonExtra` TEXT, `twitterExtra` TEXT, `previewCard` TEXT, `inReplyToUserId` TEXT, `inReplyToStatusId` TEXT, PRIMARY KEY(`_id`))", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` TEXT NOT NULL, `statusId` TEXT NOT NULL, `statusKey` TEXT NOT NULL, `htmlText` TEXT NOT NULL, `rawText` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `retweetCount` INTEGER NOT NULL, `likeCount` INTEGER NOT NULL, `replyCount` INTEGER NOT NULL, `placeString` TEXT, `source` TEXT NOT NULL, `hasMedia` INTEGER NOT NULL, `userKey` TEXT NOT NULL, `lang` TEXT, `is_possibly_sensitive` INTEGER NOT NULL, `platformType` TEXT NOT NULL, `previewCard` TEXT, `inReplyToUserId` TEXT, `inReplyToStatusId` TEXT, `extra` TEXT NOT NULL, PRIMARY KEY(`_id`))", "fields": [ { "fieldPath": "_id", @@ -104,18 +104,6 @@ "affinity": "TEXT", "notNull": true }, - { - "fieldPath": "mastodonExtra", - "columnName": "mastodonExtra", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "twitterExtra", - "columnName": "twitterExtra", - "affinity": "TEXT", - "notNull": false - }, { "fieldPath": "previewCard", "columnName": "previewCard", @@ -133,6 +121,12 @@ "columnName": "inReplyToStatusId", "affinity": "TEXT", "notNull": false + }, + { + "fieldPath": "extra", + "columnName": "extra", + "affinity": "TEXT", + "notNull": true } ], "primaryKey": { @@ -1083,7 +1077,7 @@ "views": [], "setupQueries": [ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '7304de2dc43bca1acdd86fcbf0c78ab2')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'a371ddef3fc92cdd0d0914cd1df452a2')" ] } } \ No newline at end of file diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/navigation/Navigator.kt b/app/src/main/kotlin/com/twidere/twiderex/component/navigation/Navigator.kt index f387081f8..cde7e3520 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/navigation/Navigator.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/navigation/Navigator.kt @@ -26,10 +26,10 @@ import android.content.Intent.ACTION_VIEW import android.net.Uri import android.webkit.CookieManager import androidx.compose.runtime.staticCompositionLocalOf -import com.twidere.twiderex.db.model.ReferenceType import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.enums.MastodonStatusType import com.twidere.twiderex.model.enums.PlatformType +import com.twidere.twiderex.model.enums.ReferenceType import com.twidere.twiderex.model.ui.UiStatus import com.twidere.twiderex.model.ui.UiUser import com.twidere.twiderex.navigation.RootRoute diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/status/DetailedStatusComponent.kt b/app/src/main/kotlin/com/twidere/twiderex/component/status/DetailedStatusComponent.kt index a6af522de..7814d1a7d 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/status/DetailedStatusComponent.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/status/DetailedStatusComponent.kt @@ -84,7 +84,7 @@ fun DetailedStatusComponent( CompositionLocalProvider( LocalContentAlpha provides ContentAlpha.disabled ) { - if (!status.placeString.isNullOrEmpty()) { + if (status.geo.name.isNotEmpty()) { Row( modifier = Modifier .align(Alignment.CenterHorizontally) @@ -96,7 +96,7 @@ fun DetailedStatusComponent( id = R.string.accessibility_common_status_location ) ) - Text(text = status.placeString) + Text(text = status.geo.name) } Spacer(modifier = Modifier.height(DetailedStatusDefaults.ContentSpacing)) } @@ -123,20 +123,20 @@ fun DetailedStatusComponent( horizontalArrangement = Arrangement.Center, ) { StatusStatistics( - count = status.replyCount, + count = status.metrics.reply, icon = painterResource(id = R.drawable.ic_corner_up_left), contentDescription = stringResource( id = R.string.scene_status_reply_mutiple, - status.replyCount, + status.metrics.reply, ), ) Spacer(modifier = Modifier.width(DetailedStatusDefaults.StatusStatisticsSpacing)) StatusStatistics( - count = status.retweetCount, + count = status.metrics.retweet, icon = painterResource(id = R.drawable.ic_repeat), contentDescription = stringResource( id = R.string.scene_status_retweet_mutiple, - status.retweetCount, + status.metrics.retweet, ), ) if (status.platformType == PlatformType.Twitter) { @@ -149,11 +149,11 @@ fun DetailedStatusComponent( } Spacer(modifier = Modifier.width(DetailedStatusDefaults.StatusStatisticsSpacing)) StatusStatistics( - count = status.likeCount, + count = status.metrics.like, icon = painterResource(id = R.drawable.ic_heart), contentDescription = stringResource( id = R.string.scene_status_like_multiple, - status.likeCount, + status.metrics.like, ), ) } diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/status/MastodonPoll.kt b/app/src/main/kotlin/com/twidere/twiderex/component/status/MastodonPoll.kt index 547b5e3fa..8ca946483 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/status/MastodonPoll.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/status/MastodonPoll.kt @@ -66,39 +66,39 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import com.twidere.services.mastodon.model.Option -import com.twidere.services.mastodon.model.Poll import com.twidere.twiderex.R import com.twidere.twiderex.action.LocalStatusActions import com.twidere.twiderex.extensions.humanizedTimestamp import com.twidere.twiderex.model.enums.PlatformType +import com.twidere.twiderex.model.ui.Option +import com.twidere.twiderex.model.ui.UiPoll import com.twidere.twiderex.model.ui.UiStatus import com.twidere.twiderex.ui.LocalActiveAccount import kotlin.math.max -private val Poll.canVote: Boolean - get() = voted != true && - !(expired ?: false) && - expiresAt?.time?.let { it > System.currentTimeMillis() } ?: true // some instance allows expires time == null +private val UiPoll.canVote: Boolean + get() = voted && + !expired && + expiresAt?.let { it > System.currentTimeMillis() } ?: true // some instance allows expires time == null @Composable fun MastodonPoll(status: UiStatus) { val account = LocalActiveAccount.current ?: return - if (status.platformType != PlatformType.Mastodon || status.mastodonExtra?.poll == null) { + if (status.platformType != PlatformType.Mastodon || status.poll == null) { return } val voteState = remember { mutableStateListOf() } - status.mastodonExtra.poll.options?.forEachIndexed { index, option -> + status.poll.options.forEachIndexed { index, option -> MastodonPollOption( option, index, - status.mastodonExtra.poll, + status.poll, voted = voteState.contains(index), onVote = { - if (status.mastodonExtra.poll.multiple == true) { + if (status.poll.multiple) { if (voteState.contains(index)) { voteState.remove(index) } else { @@ -114,7 +114,7 @@ fun MastodonPoll(status: UiStatus) { } } ) - if (index != status.mastodonExtra.poll.options?.lastIndex) { + if (index != status.poll.options.lastIndex) { Spacer(modifier = Modifier.height(MastodonPollDefaults.OptionSpacing)) } } @@ -125,7 +125,7 @@ fun MastodonPoll(status: UiStatus) { modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically ) { - val countText = status.mastodonExtra.poll.votersCount?.let { + val countText = status.poll.votersCount?.let { if (it > 1) { stringResource( id = R.string.common_controls_status_poll_total_people, @@ -137,7 +137,7 @@ fun MastodonPoll(status: UiStatus) { it, ) } - } ?: status.mastodonExtra.poll.votesCount?.let { + } ?: status.poll.votesCount?.let { if (it > 1) { stringResource( id = R.string.common_controls_status_poll_total_votes, @@ -161,15 +161,15 @@ fun MastodonPoll(status: UiStatus) { Text(text = countText) } Spacer(modifier = Modifier.width(MastodonPollDefaults.VoteTimeSpacing)) - if (status.mastodonExtra.poll.expired == true) { + if (status.poll.expired) { Text(text = stringResource(id = R.string.common_controls_status_poll_expired)) } else { - Text(text = status.mastodonExtra.poll.expiresAt?.time?.humanizedTimestamp() ?: "") + Text(text = status.poll.expiresAt?.humanizedTimestamp() ?: "") } } } - if (status.mastodonExtra.poll.canVote) { + if (status.poll.canVote) { val statusActions = LocalStatusActions.current TextButton( onClick = { @@ -195,21 +195,21 @@ object MastodonPollDefaults { fun MastodonPollOption( option: Option, index: Int, - poll: Poll, + poll: UiPoll, voted: Boolean, onVote: (voted: Boolean) -> Unit = {}, ) { - val transition = updateTransition(targetState = option.votesCount) + val transition = updateTransition(targetState = option.count) val progress by transition.animateFloat { - (it ?: 0).toFloat() / max((poll.votesCount ?: 0), 1).toFloat() + it.toFloat() / max((poll.votesCount ?: 0), 1).toFloat() } - val color = if (poll.expired == true) { + val color = if (poll.expired) { MaterialTheme.colors.onBackground } else { MaterialTheme.colors.primary } CompositionLocalProvider( - *if (poll.expired == true) { + *if (poll.expired) { arrayOf(LocalContentAlpha provides ContentAlpha.medium) } else { emptyArray() @@ -327,7 +327,7 @@ fun MastodonPollOption( Spacer(modifier = Modifier.width(MastodonPollOptionDefaults.IconSpacing)) Text( modifier = Modifier.weight(1f), - text = option.title ?: "", + text = option.text, style = MaterialTheme.typography.body2 ) Spacer(modifier = Modifier.width(MastodonPollOptionDefaults.IconSpacing)) diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/status/StatusActions.kt b/app/src/main/kotlin/com/twidere/twiderex/component/status/StatusActions.kt index c15ed1928..752fb1c79 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/status/StatusActions.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/status/StatusActions.kt @@ -89,7 +89,7 @@ fun ReplyButton( modifier = modifier, icon = icon, color = LocalContentColor.current, - count = data.replyCount, + count = data.metrics.reply, contentDescription = contentDescription, onClick = { action.invoke() @@ -136,7 +136,7 @@ fun LikeButton( StatusActionButtonWithNumbers( modifier = modifier, icon = icon, - count = data.likeCount, + count = data.metrics.like, color = color, contentDescription = contentDescription, onClick = { @@ -217,7 +217,7 @@ fun RetweetButton( if (withNumber) { StatusActionButtonWithNumbers( icon = icon, - count = data.retweetCount, + count = data.metrics.retweet, color = color, contentDescription = contentDescription, onClick = { diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/status/StatusMediaComponent.kt b/app/src/main/kotlin/com/twidere/twiderex/component/status/StatusMediaComponent.kt index 570ea06ed..f5213351a 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/status/StatusMediaComponent.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/status/StatusMediaComponent.kt @@ -81,7 +81,7 @@ fun StatusMediaComponent( navigator.media(statusKey = status.statusKey, selectedIndex = index) } var sensitive by rememberSaveable(status.statusKey.toString()) { - mutableStateOf(status.mastodonExtra?.sensitive ?: false) + mutableStateOf(status.sensitive) } val aspectRatio = when (media.size) { diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/status/StatusText.kt b/app/src/main/kotlin/com/twidere/twiderex/component/status/StatusText.kt index 01f0ab87f..85770faa2 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/status/StatusText.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/status/StatusText.kt @@ -56,12 +56,12 @@ fun ColumnScope.StatusText( showMastodonPoll: Boolean = true ) { val expandable = status.platformType == PlatformType.Mastodon && - status.mastodonExtra?.spoilerText != null + status.spoilerText != null var expanded by rememberSaveable { mutableStateOf(!expandable) } - if (expandable && status.mastodonExtra?.spoilerText != null) { - Text(text = status.mastodonExtra.spoilerText) + if (expandable && status.spoilerText != null) { + Text(text = status.spoilerText) Spacer(modifier = Modifier.height(StatusTextDefaults.Mastodon.SpoilerSpacing)) Row( modifier = Modifier @@ -92,7 +92,7 @@ fun ColumnScope.StatusText( }, ) - if (showMastodonPoll && status.platformType == PlatformType.Mastodon && status.mastodonExtra?.poll != null) { + if (showMastodonPoll && status.platformType == PlatformType.Mastodon && status.poll != null) { Spacer(modifier = Modifier.height(StatusTextDefaults.Mastodon.PollSpacing)) MastodonPoll(status) } diff --git a/app/src/main/kotlin/com/twidere/twiderex/component/status/TimelineStatusComponent.kt b/app/src/main/kotlin/com/twidere/twiderex/component/status/TimelineStatusComponent.kt index c78783180..6c5d92bfe 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/component/status/TimelineStatusComponent.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/component/status/TimelineStatusComponent.kt @@ -68,12 +68,12 @@ import androidx.constraintlayout.compose.Dimension import com.twidere.twiderex.R import com.twidere.twiderex.component.HumanizedTime import com.twidere.twiderex.component.navigation.LocalNavigator -import com.twidere.twiderex.db.model.DbMastodonStatusExtra -import com.twidere.twiderex.db.model.DbPreviewCard import com.twidere.twiderex.extensions.icon import com.twidere.twiderex.model.enums.MastodonStatusType import com.twidere.twiderex.model.enums.PlatformType +import com.twidere.twiderex.model.ui.UiCard import com.twidere.twiderex.model.ui.UiStatus +import com.twidere.twiderex.model.ui.mastodon.MastodonStatusExtra import com.twidere.twiderex.preferences.LocalDisplayPreferences import com.twidere.twiderex.ui.LocalActiveAccount @@ -240,7 +240,7 @@ private object StatusHeaderDefaults { @Composable private fun MastodonStatusHeader( - mastodonExtra: DbMastodonStatusExtra, + mastodonExtra: MastodonStatusExtra, data: UiStatus ) { when (mastodonExtra.type) { @@ -594,13 +594,13 @@ fun ColumnScope.StatusBody( StatusBodyMedia(status) if (LocalDisplayPreferences.current.urlPreview && !status.media.any()) { - status.linkPreview?.let { + status.card?.let { Spacer(modifier = Modifier.height(StatusBodyDefaults.LinkPreviewSpacing)) StatusLinkPreview(it) } } - if (!status.placeString.isNullOrEmpty() && type == StatusContentType.Normal) { + if (status.geo.name.isNotEmpty() && type == StatusContentType.Normal) { Spacer(modifier = Modifier.height(StatusBodyDefaults.PlaceSpacing)) CompositionLocalProvider( LocalContentAlpha provides ContentAlpha.disabled @@ -614,7 +614,7 @@ fun ColumnScope.StatusBody( contentDescription = stringResource(id = R.string.accessibility_common_status_location) ) Box(modifier = Modifier.width(StatusBodyDefaults.PlaceSpacing)) - Text(text = status.placeString) + Text(text = status.geo.name) } } } @@ -648,7 +648,7 @@ object StatusBodyDefaults { } @Composable -private fun StatusLinkPreview(card: DbPreviewCard) { +private fun StatusLinkPreview(card: UiCard) { val navigator = LocalNavigator.current LinkPreview( modifier = Modifier @@ -659,7 +659,7 @@ private fun StatusLinkPreview(card: DbPreviewCard) { link = card.displayLink ?: card.link, title = card.title?.trim(), image = card.image, - desc = card.desc?.trim(), + desc = card.description?.trim(), maxLines = 5, ) } diff --git a/app/src/main/kotlin/com/twidere/twiderex/db/dao/StatusReferenceDao.kt b/app/src/main/kotlin/com/twidere/twiderex/db/dao/StatusReferenceDao.kt index c7f18570e..fdb793754 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/db/dao/StatusReferenceDao.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/db/dao/StatusReferenceDao.kt @@ -27,8 +27,8 @@ import androidx.room.Query import androidx.room.Transaction import com.twidere.twiderex.db.model.DbStatusReference import com.twidere.twiderex.db.model.DbStatusReferenceWithStatus -import com.twidere.twiderex.db.model.ReferenceType import com.twidere.twiderex.model.MicroBlogKey +import com.twidere.twiderex.model.enums.ReferenceType @Dao interface StatusReferenceDao { diff --git a/app/src/main/kotlin/com/twidere/twiderex/db/mapper/Mastodon.kt b/app/src/main/kotlin/com/twidere/twiderex/db/mapper/Mastodon.kt index dc4f83b26..184b90bc7 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/db/mapper/Mastodon.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/db/mapper/Mastodon.kt @@ -44,13 +44,13 @@ import com.twidere.twiderex.db.model.DbTrend import com.twidere.twiderex.db.model.DbTrendHistory import com.twidere.twiderex.db.model.DbTrendWithHistory import com.twidere.twiderex.db.model.DbUser -import com.twidere.twiderex.db.model.ReferenceType import com.twidere.twiderex.db.model.toDbStatusReference import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.enums.MastodonStatusType import com.twidere.twiderex.model.enums.MastodonVisibility import com.twidere.twiderex.model.enums.MediaType import com.twidere.twiderex.model.enums.PlatformType +import com.twidere.twiderex.model.enums.ReferenceType import com.twidere.twiderex.navigation.RootDeepLinksRoute import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json diff --git a/app/src/main/kotlin/com/twidere/twiderex/db/mapper/Twitter.kt b/app/src/main/kotlin/com/twidere/twiderex/db/mapper/Twitter.kt index b59955358..82e7c7313 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/db/mapper/Twitter.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/db/mapper/Twitter.kt @@ -46,12 +46,12 @@ import com.twidere.twiderex.db.model.DbTwitterStatusExtra import com.twidere.twiderex.db.model.DbTwitterUserExtra import com.twidere.twiderex.db.model.DbUrlEntity import com.twidere.twiderex.db.model.DbUser -import com.twidere.twiderex.db.model.ReferenceType import com.twidere.twiderex.db.model.TwitterUrlEntity import com.twidere.twiderex.db.model.toDbStatusReference import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.enums.MediaType import com.twidere.twiderex.model.enums.PlatformType +import com.twidere.twiderex.model.enums.ReferenceType import com.twidere.twiderex.model.enums.TwitterReplySettings import com.twidere.twiderex.model.ui.ListsMode import com.twidere.twiderex.navigation.RootDeepLinksRouteDefinition diff --git a/app/src/main/kotlin/com/twidere/twiderex/db/model/DbStatus.kt b/app/src/main/kotlin/com/twidere/twiderex/db/model/DbStatus.kt index 552db62b1..579dc9e86 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/db/model/DbStatus.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/db/model/DbStatus.kt @@ -37,7 +37,10 @@ import com.twidere.twiderex.model.enums.MastodonVisibility import com.twidere.twiderex.model.enums.PlatformType import com.twidere.twiderex.model.enums.TwitterReplySettings import kotlinx.serialization.Serializable +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString +private typealias KTJson = kotlinx.serialization.json.Json @Entity( tableName = "status", indices = [Index(value = ["statusKey"], unique = true)], @@ -69,8 +72,14 @@ data class DbStatusV2( val previewCard: DbPreviewCard? = null, val inReplyToUserId: String? = null, val inReplyToStatusId: String? = null, - val extra: Json -) + var extra: Json +) { + inline fun updateExtra(update: (T) -> T) { + extra = KTJson.encodeToString(update.invoke(KTJson.decodeFromString(extra))) + } +} + +interface DbStatusExtra @Immutable @Serializable @@ -87,7 +96,7 @@ data class DbPreviewCard( data class DbTwitterStatusExtra( val reply_settings: TwitterReplySettings, val quoteCount: Long? = null, -) +) : DbStatusExtra @Immutable @Serializable @@ -100,7 +109,7 @@ data class DbMastodonStatusExtra( val poll: Poll?, val card: Card?, val mentions: List?, -) +) : DbStatusExtra data class DbStatusWithMediaAndUser( @Embedded diff --git a/app/src/main/kotlin/com/twidere/twiderex/db/model/DbStatusReference.kt b/app/src/main/kotlin/com/twidere/twiderex/db/model/DbStatusReference.kt index e25333e68..818ba7433 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/db/model/DbStatusReference.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/db/model/DbStatusReference.kt @@ -27,6 +27,7 @@ import androidx.room.PrimaryKey import androidx.room.Relation import com.twidere.twiderex.db.CacheDatabase import com.twidere.twiderex.model.MicroBlogKey +import com.twidere.twiderex.model.enums.ReferenceType import java.util.UUID @Entity( @@ -83,13 +84,6 @@ fun DbStatusWithMediaAndUser?.toDbStatusReference( } } -enum class ReferenceType { - Retweet, - Reply, - Quote, - MastodonNotification, -} - data class DbStatusWithReference( @Embedded val status: DbStatusWithMediaAndUser, diff --git a/app/src/main/kotlin/com/twidere/twiderex/extensions/MastodonExtensions.kt b/app/src/main/kotlin/com/twidere/twiderex/extensions/MastodonExtensions.kt index 7c2d76e75..77f224893 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/extensions/MastodonExtensions.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/extensions/MastodonExtensions.kt @@ -24,25 +24,25 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import com.twidere.services.mastodon.model.Visibility import com.twidere.twiderex.R +import com.twidere.twiderex.model.enums.MastodonVisibility @Composable -fun Visibility.icon(): Painter { +fun MastodonVisibility.icon(): Painter { return when (this) { - Visibility.Public -> painterResource(id = R.drawable.ic_globe) - Visibility.Unlisted -> painterResource(id = R.drawable.ic_lock_open) - Visibility.Private -> painterResource(id = R.drawable.ic_lock) - Visibility.Direct -> painterResource(id = R.drawable.ic_mail) + MastodonVisibility.Public -> painterResource(id = R.drawable.ic_globe) + MastodonVisibility.Unlisted -> painterResource(id = R.drawable.ic_lock_open) + MastodonVisibility.Private -> painterResource(id = R.drawable.ic_lock) + MastodonVisibility.Direct -> painterResource(id = R.drawable.ic_mail) } } @Composable -fun Visibility.stringName(): String { +fun MastodonVisibility.stringName(): String { return when (this) { - Visibility.Public -> stringResource(id = R.string.scene_compose_visibility_public) - Visibility.Unlisted -> stringResource(id = R.string.scene_compose_visibility_unlisted) - Visibility.Private -> stringResource(id = R.string.scene_compose_visibility_private) - Visibility.Direct -> stringResource(id = R.string.scene_compose_visibility_direct) + MastodonVisibility.Public -> stringResource(id = R.string.scene_compose_visibility_public) + MastodonVisibility.Unlisted -> stringResource(id = R.string.scene_compose_visibility_unlisted) + MastodonVisibility.Private -> stringResource(id = R.string.scene_compose_visibility_private) + MastodonVisibility.Direct -> stringResource(id = R.string.scene_compose_visibility_direct) } } diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/common/NotificationJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/common/NotificationJob.kt index 983756c4a..55c036a0e 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/jobs/common/NotificationJob.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/common/NotificationJob.kt @@ -27,10 +27,10 @@ import coil.Coil import coil.request.ImageRequest import coil.request.SuccessResult import com.twidere.twiderex.R -import com.twidere.twiderex.db.model.ReferenceType import com.twidere.twiderex.model.AccountDetails import com.twidere.twiderex.model.enums.MastodonStatusType import com.twidere.twiderex.model.enums.PlatformType +import com.twidere.twiderex.model.enums.ReferenceType import com.twidere.twiderex.model.ui.UiStatus import com.twidere.twiderex.navigation.RootDeepLinksRoute import com.twidere.twiderex.notification.AppNotification diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/compose/MastodonComposeJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/compose/MastodonComposeJob.kt index 64abd8887..65b49d11b 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/jobs/compose/MastodonComposeJob.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/compose/MastodonComposeJob.kt @@ -24,6 +24,7 @@ import android.content.Context import com.twidere.services.mastodon.MastodonService import com.twidere.services.mastodon.model.PostPoll import com.twidere.services.mastodon.model.PostStatus +import com.twidere.services.mastodon.model.Visibility import com.twidere.twiderex.db.CacheDatabase import com.twidere.twiderex.db.mapper.toDbStatusWithReference import com.twidere.twiderex.db.model.saveToDb @@ -31,6 +32,7 @@ import com.twidere.twiderex.kmp.ExifScrambler import com.twidere.twiderex.kmp.FileResolver import com.twidere.twiderex.kmp.RemoteNavigator import com.twidere.twiderex.model.MicroBlogKey +import com.twidere.twiderex.model.enums.MastodonVisibility import com.twidere.twiderex.model.job.ComposeData import com.twidere.twiderex.model.transform.toUi import com.twidere.twiderex.model.ui.UiStatus @@ -68,7 +70,12 @@ class MastodonComposeJob( mediaIDS = mediaIds, sensitive = composeData.isSensitive, spoilerText = composeData.contentWarningText, - visibility = composeData.visibility, + visibility = when (composeData.visibility) { + MastodonVisibility.Public, null -> Visibility.Public + MastodonVisibility.Unlisted -> Visibility.Unlisted + MastodonVisibility.Private -> Visibility.Private + MastodonVisibility.Direct -> Visibility.Direct + }, poll = composeData.voteOptions?.let { PostPoll( options = composeData.voteOptions, diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/status/LikeStatusJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/status/LikeStatusJob.kt index 7ffda9744..0adb1dfa4 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/jobs/status/LikeStatusJob.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/status/LikeStatusJob.kt @@ -51,8 +51,8 @@ class LikeStatusJob( statusKey = newStatus.statusKey, accountKey = accountKey, liked = true, - retweetCount = newStatus.retweetCount, - likeCount = newStatus.likeCount, + retweetCount = newStatus.metrics.retweet, + likeCount = newStatus.metrics.like, ) } diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/status/MastodonVoteJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/status/MastodonVoteJob.kt index cdc425031..2ea3e38c9 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/jobs/status/MastodonVoteJob.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/status/MastodonVoteJob.kt @@ -21,6 +21,8 @@ package com.twidere.twiderex.jobs.status import com.twidere.services.mastodon.MastodonService +import com.twidere.services.mastodon.model.Poll +import com.twidere.twiderex.db.model.DbMastodonStatusExtra import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.enums.PlatformType import com.twidere.twiderex.notification.InAppNotification @@ -35,7 +37,7 @@ class MastodonVoteJob( ) { suspend fun execute(votes: List, accountKey: MicroBlogKey, statusKey: MicroBlogKey) { val status = statusRepository.loadFromCache(statusKey, accountKey = accountKey) ?: throw Error("Can't find any status matches:$statusKey") - if (status.mastodonExtra?.poll == null || status.platformType != PlatformType.Mastodon) { + if (status.poll == null || status.platformType != PlatformType.Mastodon) { throw Error() } val service = accountRepository.findByAccountKey(accountKey)?.let { @@ -44,28 +46,35 @@ class MastodonVoteJob( it.service as? MastodonService } ?: throw Error() - val pollId = status.mastodonExtra.poll.id ?: throw Error("Poll id is null") - val originPoll = status.mastodonExtra.poll + val pollId = status.poll.id + var originPoll: Poll? = null statusRepository.updateStatus(statusKey = status.statusKey) { - it.mastodonExtra = status.mastodonExtra.copy( - poll = status.mastodonExtra.poll.copy( - voted = true, - ownVotes = votes + it.updateExtra { extra -> + originPoll = extra.poll + extra.copy( + poll = extra.poll?.copy( + voted = true, + ownVotes = votes + ) ) - ) + } } try { val newPoll = service.vote(pollId, votes) statusRepository.updateStatus(statusKey = status.statusKey) { - it.mastodonExtra = status.mastodonExtra.copy( - poll = newPoll - ) + it.updateExtra { extra -> + extra.copy( + poll = newPoll + ) + } } } catch (e: Throwable) { statusRepository.updateStatus(statusKey = status.statusKey) { - it.mastodonExtra = status.mastodonExtra.copy( - poll = originPoll - ) + it.updateExtra { extra -> + extra.copy( + poll = originPoll + ) + } } e.notify(inAppNotification) throw e diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/status/RetweetStatusJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/status/RetweetStatusJob.kt index fc258fa42..fcc25f2dc 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/jobs/status/RetweetStatusJob.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/status/RetweetStatusJob.kt @@ -51,8 +51,8 @@ class RetweetStatusJob( statusKey = newStatus.statusKey, accountKey = accountKey, retweeted = true, - retweetCount = newStatus.retweetCount, - likeCount = newStatus.likeCount, + retweetCount = newStatus.metrics.retweet, + likeCount = newStatus.metrics.like, ) } override fun fallback( diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/status/UnRetweetStatusJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/status/UnRetweetStatusJob.kt index 87b8532dd..d8f5e35e1 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/jobs/status/UnRetweetStatusJob.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/status/UnRetweetStatusJob.kt @@ -51,8 +51,8 @@ class UnRetweetStatusJob( statusKey = newStatus.statusKey, accountKey = accountKey, retweeted = false, - retweetCount = newStatus.retweetCount, - likeCount = newStatus.likeCount, + retweetCount = newStatus.metrics.retweet, + likeCount = newStatus.metrics.like, ) } override fun fallback( diff --git a/app/src/main/kotlin/com/twidere/twiderex/jobs/status/UnlikeStatusJob.kt b/app/src/main/kotlin/com/twidere/twiderex/jobs/status/UnlikeStatusJob.kt index 17d470d85..18835bd3e 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/jobs/status/UnlikeStatusJob.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/jobs/status/UnlikeStatusJob.kt @@ -51,8 +51,8 @@ class UnlikeStatusJob( statusKey = newStatus.statusKey, accountKey = accountKey, liked = false, - retweetCount = newStatus.retweetCount, - likeCount = newStatus.likeCount, + retweetCount = newStatus.metrics.retweet, + likeCount = newStatus.metrics.like, ) } override fun fallback( diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/enums/ReferenceType.kt b/app/src/main/kotlin/com/twidere/twiderex/model/enums/ReferenceType.kt new file mode 100644 index 000000000..8e10b05d3 --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/model/enums/ReferenceType.kt @@ -0,0 +1,31 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.model.enums + +import kotlinx.serialization.Serializable + +@Serializable +enum class ReferenceType { + Retweet, + Reply, + Quote, + MastodonNotification, +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/job/ComposeData.kt b/app/src/main/kotlin/com/twidere/twiderex/model/job/ComposeData.kt index d0f537c0b..9b5fd745a 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/model/job/ComposeData.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/model/job/ComposeData.kt @@ -20,8 +20,8 @@ */ package com.twidere.twiderex.model.job -import com.twidere.services.mastodon.model.Visibility import com.twidere.twiderex.model.MicroBlogKey +import com.twidere.twiderex.model.enums.MastodonVisibility import com.twidere.twiderex.viewmodel.compose.ComposeType import com.twidere.twiderex.viewmodel.compose.VoteExpired import java.util.UUID @@ -38,7 +38,7 @@ data class ComposeData( val voteOptions: List? = null, val voteExpired: VoteExpired? = null, val voteMultiple: Boolean? = null, - val visibility: Visibility? = null, + val visibility: MastodonVisibility? = null, val isSensitive: Boolean? = null, val contentWarningText: String? = null, val isThreadMode: Boolean = false diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/transform/StatusTransform.kt b/app/src/main/kotlin/com/twidere/twiderex/model/transform/StatusTransform.kt index 3a62dfb35..e1cfc0691 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/model/transform/StatusTransform.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/model/transform/StatusTransform.kt @@ -21,17 +21,24 @@ package com.twidere.twiderex.model.transform import com.twidere.services.mastodon.model.Mention +import com.twidere.services.mastodon.model.Poll import com.twidere.twiderex.db.model.DbMastodonStatusExtra import com.twidere.twiderex.db.model.DbPagingTimelineWithStatus +import com.twidere.twiderex.db.model.DbPreviewCard import com.twidere.twiderex.db.model.DbStatusReaction import com.twidere.twiderex.db.model.DbStatusV2 import com.twidere.twiderex.db.model.DbStatusWithMediaAndUser import com.twidere.twiderex.db.model.DbStatusWithReference import com.twidere.twiderex.db.model.DbTwitterStatusExtra -import com.twidere.twiderex.db.model.ReferenceType import com.twidere.twiderex.model.MicroBlogKey import com.twidere.twiderex.model.enums.PlatformType +import com.twidere.twiderex.model.enums.ReferenceType +import com.twidere.twiderex.model.ui.Option +import com.twidere.twiderex.model.ui.StatusMetrics +import com.twidere.twiderex.model.ui.UiCard +import com.twidere.twiderex.model.ui.UiGeo import com.twidere.twiderex.model.ui.UiMedia +import com.twidere.twiderex.model.ui.UiPoll import com.twidere.twiderex.model.ui.UiStatus import com.twidere.twiderex.model.ui.UiUrlEntity import com.twidere.twiderex.model.ui.UiUser @@ -48,36 +55,55 @@ fun DbStatusV2.toUi( reaction: DbStatusReaction?, isGap: Boolean, referenceStatus: Map = emptyMap(), -) = UiStatus( - statusId = statusId, - htmlText = htmlText, - timestamp = timestamp, - retweetCount = retweetCount, - likeCount = likeCount, - replyCount = replyCount, - retweeted = reaction?.retweeted ?: false, - liked = reaction?.liked ?: false, - placeString = placeString, - hasMedia = hasMedia, - user = user, - media = media, - isGap = isGap, - source = source, - url = url, - statusKey = statusKey, - rawText = rawText, - platformType = platformType, - extra = when (platformType) { +): UiStatus { + var poll: UiPoll? = null + var sensitive = false + var spoilerText: String? = null + val extra = when (platformType) { PlatformType.Twitter -> Json.decodeFromString(extra).toUi() PlatformType.StatusNet -> TODO() PlatformType.Fanfou -> TODO() - PlatformType.Mastodon -> Json.decodeFromString(extra).toUi() - }, - referenceStatus = referenceStatus, - linkPreview = previewCard, - inReplyToStatusId = inReplyToStatusId, - inReplyToUserId = inReplyToStatusId -) + PlatformType.Mastodon -> Json.decodeFromString(extra).apply { + poll = this.poll?.toUi() + sensitive = this.sensitive + spoilerText = this.spoilerText + }.toUi() + } + return UiStatus( + statusId = statusId, + htmlText = htmlText, + timestamp = timestamp, + metrics = StatusMetrics( + retweet = retweetCount, + like = likeCount, + reply = replyCount, + ), + retweeted = reaction?.retweeted ?: false, + liked = reaction?.liked ?: false, + geo = UiGeo( + name = placeString ?: "", + lat = null, + long = null + ), + hasMedia = hasMedia, + user = user, + media = media, + isGap = isGap, + source = source, + url = url, + statusKey = statusKey, + rawText = rawText, + platformType = platformType, + extra = extra, + referenceStatus = referenceStatus, + card = previewCard?.toUi(), + poll = poll, + inReplyToStatusId = inReplyToStatusId, + inReplyToUserId = inReplyToStatusId, + sensitive = sensitive, + spoilerText = spoilerText + ) +} fun DbStatusWithMediaAndUser.toUi( accountKey: MicroBlogKey, @@ -148,3 +174,30 @@ fun List.toUi() = map { acct = it.acct ) } + +fun DbPreviewCard.toUi() = UiCard( + link = link, + displayLink = displayLink, + title = title, + description = desc, + image = image +) + +fun Poll.toUi() = id?.let { + UiPoll( + id = it, + options = options?.map { option -> + Option( + text = option.title ?: "", + count = option.votesCount ?: 0 + ) + } ?: emptyList(), + expired = expired ?: false, + expiresAt = expiresAt?.time, + multiple = multiple ?: false, + voted = voted ?: false, + votersCount = votersCount, + ownVotes = ownVotes, + votesCount = votesCount + ) +} diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/transform/WorkDataTransform.kt b/app/src/main/kotlin/com/twidere/twiderex/model/transform/WorkDataTransform.kt index b46a1df0c..1694ade18 100644 --- a/app/src/main/kotlin/com/twidere/twiderex/model/transform/WorkDataTransform.kt +++ b/app/src/main/kotlin/com/twidere/twiderex/model/transform/WorkDataTransform.kt @@ -22,10 +22,10 @@ package com.twidere.twiderex.model.transform import androidx.work.Data import androidx.work.workDataOf -import com.twidere.services.mastodon.model.Visibility import com.twidere.twiderex.extensions.getNullableBoolean import com.twidere.twiderex.extensions.getNullableDouble import com.twidere.twiderex.model.MicroBlogKey +import com.twidere.twiderex.model.enums.MastodonVisibility import com.twidere.twiderex.model.job.ComposeData import com.twidere.twiderex.model.job.DirectMessageDeleteData import com.twidere.twiderex.model.job.DirectMessageSendData @@ -72,7 +72,7 @@ fun Data.toComposeData() = ComposeData( voteOptions = getStringArray("voteOptions")?.toList(), voteExpired = getString("voteExpired")?.let { VoteExpired.valueOf(it) }, voteMultiple = getNullableBoolean("voteMultiple"), - visibility = getString("visibility")?.let { Visibility.valueOf(it) }, + visibility = getString("visibility")?.let { MastodonVisibility.valueOf(it) }, isSensitive = getNullableBoolean("isSensitive"), contentWarningText = getString("contentWarningText"), isThreadMode = getBoolean("isThreadMode", false), diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiCard.kt b/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiCard.kt new file mode 100644 index 000000000..271444244 --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiCard.kt @@ -0,0 +1,29 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.model.ui + +data class UiCard( + val link: String, + val displayLink: String?, + val title: String?, + val description: String?, + val image: String?, +) diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiGeo.kt b/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiGeo.kt new file mode 100644 index 000000000..d336f2a8e --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiGeo.kt @@ -0,0 +1,27 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.model.ui + +data class UiGeo( + val name: String, + val lat: Long? = null, + val long: Long? = null, +) diff --git a/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiPoll.kt b/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiPoll.kt new file mode 100644 index 000000000..883245053 --- /dev/null +++ b/app/src/main/kotlin/com/twidere/twiderex/model/ui/UiPoll.kt @@ -0,0 +1,38 @@ +/* + * Twidere X + * + * Copyright (C) 2020-2021 Tlaster + * + * This file is part of Twidere X. + * + * Twidere X 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. + * + * Twidere X 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 Twidere X. If not, see . + */ +package com.twidere.twiderex.model.ui + +data class UiPoll( + val id: String, + val options: List