From 8878d0760bf26c462ac6d02baeb7bf684bf7be62 Mon Sep 17 00:00:00 2001 From: Konstantin Ivanov <54908981+konstantiniiv@users.noreply.github.com> Date: Tue, 1 Oct 2024 21:02:58 +0200 Subject: [PATCH] DROID-2853 All content | Enhancement | Menu, cache, empty states (#1613) --- .../ui/allcontent/AllContentFragment.kt | 22 ++- .../history/VersionHistoryMainScreen.kt | 1 - .../data/auth/repo/UserSettingsCache.kt | 3 + .../auth/repo/UserSettingsDataRepository.kt | 8 ++ .../all_content/RestoreAllContentState.kt | 5 +- .../all_content/UpdateAllContentState.kt | 8 +- .../domain/config/UserSettingsRepository.kt | 3 + .../models/AllContentModels.kt | 10 +- .../models/AllContentSearchParams.kt | 2 - .../presentation/AllContentViewModel.kt | 33 ++++- .../feature_allcontent/ui/AllContentMenu.kt | 49 +++++-- .../feature_allcontent/ui/AllContentScreen.kt | 126 +++++++++++++----- .../ui/AllContentTopToolbar.kt | 15 ++- localization/src/main/res/values/strings.xml | 8 +- .../repo/DefaultUserSettingsCache.kt | 35 +++++ persistence/src/main/proto/preferences.proto | 5 + .../presentation/home/HomeScreenViewModel.kt | 18 ++- .../home/HomeScreenViewModelTest.kt | 46 ++----- 18 files changed, 287 insertions(+), 110 deletions(-) diff --git a/app/src/main/java/com/anytypeio/anytype/ui/allcontent/AllContentFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/allcontent/AllContentFragment.kt index d71948bf61..27e5ed5b82 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/allcontent/AllContentFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/allcontent/AllContentFragment.kt @@ -27,9 +27,11 @@ import com.anytypeio.anytype.feature_allcontent.presentation.AllContentViewModel import com.anytypeio.anytype.feature_allcontent.presentation.AllContentViewModelFactory import com.anytypeio.anytype.feature_allcontent.ui.AllContentWrapperScreen import com.anytypeio.anytype.feature_allcontent.ui.AllContentNavigation.ALL_CONTENT_MAIN +import com.anytypeio.anytype.presentation.widgets.collection.Subscription import com.anytypeio.anytype.ui.base.navigation import com.anytypeio.anytype.ui.settings.typography import javax.inject.Inject +import timber.log.Timber class AllContentFragment : BaseComposeFragment() { @@ -62,6 +64,9 @@ class AllContentFragment : BaseComposeFragment() { target = command.id, space = command.space ) + }.onFailure { + toast("Failed to open document") + Timber.e(it, "Failed to open document") } } is AllContentViewModel.Command.NavigateToSetOrCollection -> { @@ -70,11 +75,25 @@ class AllContentFragment : BaseComposeFragment() { target = command.id, space = command.space, ) + }.onFailure { + toast("Failed to open object set") + Timber.e(it, "Failed to open object set") } } is AllContentViewModel.Command.SendToast -> { toast(command.message) } + is AllContentViewModel.Command.NavigateToBin -> { + runCatching { + navigation().launchCollections( + subscription = Subscription.Bin, + space = command.space + ) + }.onFailure { + toast("Failed to open bin") + Timber.e(it, "Failed to open bin") + } + } } } } @@ -96,7 +115,8 @@ class AllContentFragment : BaseComposeFragment() { uiMenuState = vm.uiMenu.collectAsStateWithLifecycle().value, onSortClick = vm::onSortClicked, onModeClick = vm::onAllContentModeClicked, - onItemClicked = vm::onItemClicked + onItemClicked = vm::onItemClicked, + onBinClick = vm::onViewBinClicked ) } } diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/history/VersionHistoryMainScreen.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/history/VersionHistoryMainScreen.kt index 59faefcd2a..3c03b0cc31 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/history/VersionHistoryMainScreen.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/history/VersionHistoryMainScreen.kt @@ -11,7 +11,6 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/UserSettingsCache.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/UserSettingsCache.kt index 838c16113f..a655835d3f 100644 --- a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/UserSettingsCache.kt +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/UserSettingsCache.kt @@ -42,4 +42,7 @@ interface UserSettingsCache { suspend fun getWidgetSession() : WidgetSession suspend fun saveWidgetSession(session: WidgetSession) suspend fun clear() + + suspend fun getAllContentSort(space: SpaceId): Id + suspend fun setAllContentSort(space: SpaceId, sort: Id) } \ No newline at end of file diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/UserSettingsDataRepository.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/UserSettingsDataRepository.kt index b9f57d730a..4ef16141ca 100644 --- a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/UserSettingsDataRepository.kt +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/UserSettingsDataRepository.kt @@ -105,4 +105,12 @@ class UserSettingsDataRepository(private val cache: UserSettingsCache) : UserSet order = order ) } + + override suspend fun getAllContentSort(space: SpaceId): Id { + return cache.getAllContentSort(space) + } + + override suspend fun setAllContentSort(space: SpaceId, sort: Id) { + cache.setAllContentSort(space, sort) + } } \ No newline at end of file diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/all_content/RestoreAllContentState.kt b/domain/src/main/java/com/anytypeio/anytype/domain/all_content/RestoreAllContentState.kt index 20df11fd12..1dcd392dae 100644 --- a/domain/src/main/java/com/anytypeio/anytype/domain/all_content/RestoreAllContentState.kt +++ b/domain/src/main/java/com/anytypeio/anytype/domain/all_content/RestoreAllContentState.kt @@ -1,6 +1,5 @@ package com.anytypeio.anytype.domain.all_content -import com.anytypeio.anytype.core_models.DVSort import com.anytypeio.anytype.core_models.primitives.SpaceId import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers import com.anytypeio.anytype.domain.base.ResultInteractor @@ -15,8 +14,8 @@ class RestoreAllContentState @Inject constructor( ) { override suspend fun doWork(params: Params): Response { - //todo: implement - return Response(activeSort = null) + val sort = settings.getAllContentSort(params.spaceId) + return Response(activeSort = sort) } data class Params( diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/all_content/UpdateAllContentState.kt b/domain/src/main/java/com/anytypeio/anytype/domain/all_content/UpdateAllContentState.kt index 6671201de2..8ee62d8a2d 100644 --- a/domain/src/main/java/com/anytypeio/anytype/domain/all_content/UpdateAllContentState.kt +++ b/domain/src/main/java/com/anytypeio/anytype/domain/all_content/UpdateAllContentState.kt @@ -13,12 +13,14 @@ class UpdateAllContentState @Inject constructor( ) : ResultInteractor(dispatchers.io) { override suspend fun doWork(params: Params) { - //todo: implement + settings.setAllContentSort( + space = params.spaceId, + sort = params.sort + ) } data class Params( val spaceId: SpaceId, - val query: String, - val relatedObjectId: Id? + val sort: Id ) } \ No newline at end of file diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/config/UserSettingsRepository.kt b/domain/src/main/java/com/anytypeio/anytype/domain/config/UserSettingsRepository.kt index 2ce431160c..dc83d7545f 100644 --- a/domain/src/main/java/com/anytypeio/anytype/domain/config/UserSettingsRepository.kt +++ b/domain/src/main/java/com/anytypeio/anytype/domain/config/UserSettingsRepository.kt @@ -45,5 +45,8 @@ interface UserSettingsRepository { suspend fun getWidgetSession() : WidgetSession suspend fun saveWidgetSession(session: WidgetSession) + suspend fun getAllContentSort(space: SpaceId): Id + suspend fun setAllContentSort(space: SpaceId, sort: Id) + suspend fun clear() } \ No newline at end of file diff --git a/feature-all-content/src/main/java/com/anytypeio/anytype/feature_allcontent/models/AllContentModels.kt b/feature-all-content/src/main/java/com/anytypeio/anytype/feature_allcontent/models/AllContentModels.kt index 6ac75a1e89..885080c361 100644 --- a/feature-all-content/src/main/java/com/anytypeio/anytype/feature_allcontent/models/AllContentModels.kt +++ b/feature-all-content/src/main/java/com/anytypeio/anytype/feature_allcontent/models/AllContentModels.kt @@ -31,7 +31,7 @@ sealed class AllContentState { @Immutable enum class AllContentTab { - PAGES, LISTS, MEDIA, BOOKMARKS, FILES, TYPES, RELATIONS + PAGES, LISTS, MEDIA, BOOKMARKS, FILES, TYPES } sealed class AllContentMode { @@ -164,7 +164,8 @@ data class UiMenuState( val mode: List, val container: MenuSortsItem.Container, val sorts: List, - val types: List + val types: List, + val showBin: Boolean = true ) { companion object { fun empty(): UiMenuState { @@ -208,7 +209,8 @@ fun AllContentMode.view(): UiTitleState { fun Key?.mapRelationKeyToSort(): AllContentSort { return when (this) { Relations.CREATED_DATE -> AllContentSort.ByDateCreated() - Relations.LAST_OPENED_DATE -> AllContentSort.ByDateUpdated() + Relations.LAST_MODIFIED_DATE -> AllContentSort.ByDateUpdated() + Relations.NAME -> AllContentSort.ByName() else -> DEFAULT_INITIAL_SORT } } @@ -249,7 +251,7 @@ fun ObjectWrapper.Basic.toAllContentItem( layout = layout, builder = urlBuilder ), - lastModifiedDate = DateParser.parseInMillis(obj.lastModifiedDate) ?: 0L, + lastModifiedDate = DateParser.parse(obj.getValue(Relations.LAST_MODIFIED_DATE)) ?: 0L, createdDate = DateParser.parse(obj.getValue(Relations.CREATED_DATE)) ?: 0L ) } diff --git a/feature-all-content/src/main/java/com/anytypeio/anytype/feature_allcontent/models/AllContentSearchParams.kt b/feature-all-content/src/main/java/com/anytypeio/anytype/feature_allcontent/models/AllContentSearchParams.kt index f8eefa84ac..ce880473e5 100644 --- a/feature-all-content/src/main/java/com/anytypeio/anytype/feature_allcontent/models/AllContentSearchParams.kt +++ b/feature-all-content/src/main/java/com/anytypeio/anytype/feature_allcontent/models/AllContentSearchParams.kt @@ -118,7 +118,6 @@ fun AllContentTab.filtersForSubscribe( } AllContentTab.TYPES -> TODO() - AllContentTab.RELATIONS -> TODO() } } @@ -144,7 +143,6 @@ fun AllContentTab.filtersForSearch( } AllContentTab.TYPES -> TODO() - AllContentTab.RELATIONS -> TODO() } } diff --git a/feature-all-content/src/main/java/com/anytypeio/anytype/feature_allcontent/presentation/AllContentViewModel.kt b/feature-all-content/src/main/java/com/anytypeio/anytype/feature_allcontent/presentation/AllContentViewModel.kt index eb08f9561a..87cd30f7a2 100644 --- a/feature-all-content/src/main/java/com/anytypeio/anytype/feature_allcontent/presentation/AllContentViewModel.kt +++ b/feature-all-content/src/main/java/com/anytypeio/anytype/feature_allcontent/presentation/AllContentViewModel.kt @@ -10,6 +10,7 @@ import com.anytypeio.anytype.core_models.Relations import com.anytypeio.anytype.core_models.primitives.SpaceId import com.anytypeio.anytype.domain.all_content.RestoreAllContentState import com.anytypeio.anytype.domain.all_content.UpdateAllContentState +import com.anytypeio.anytype.domain.base.fold import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer import com.anytypeio.anytype.domain.misc.LocaleProvider import com.anytypeio.anytype.domain.misc.UrlBuilder @@ -40,7 +41,6 @@ import java.time.LocalDate import java.time.ZoneId import java.time.format.TextStyle import java.time.temporal.ChronoUnit -import javax.inject.Named import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.delay @@ -159,7 +159,7 @@ class AllContentViewModel( val initialParams = restoreAllContentState.run( RestoreAllContentState.Params(vmParams.spaceId) ) - if (initialParams.activeSort != null) { + if (!initialParams.activeSort.isNullOrEmpty()) { _sortState.value = initialParams.activeSort.mapRelationKeyToSort() } }.onFailure { e -> @@ -418,7 +418,7 @@ class AllContentViewModel( fun onTabClicked(tab: AllContentTab) { Timber.d("onTabClicked: $tab") - if (tab == AllContentTab.TYPES || tab == AllContentTab.RELATIONS) { + if (tab == AllContentTab.TYPES) { viewModelScope.launch { _commands.emit(Command.SendToast("Not implemented yet")) } @@ -438,6 +438,24 @@ class AllContentViewModel( fun onSortClicked(sort: AllContentSort) { Timber.d("onSortClicked: $sort") _sortState.value = sort + proceedWithSortSaving(sort) + } + + private fun proceedWithSortSaving(sort: AllContentSort) { + viewModelScope.launch { + val params = UpdateAllContentState.Params( + spaceId = vmParams.spaceId, + sort = sort.relationKey.key + ) + updateAllContentState.async(params).fold( + onSuccess = { + Timber.d("Sort updated") + }, + onFailure = { + Timber.e(it, "Error updating sort") + } + ) + } } fun onFilterChanged(filter: String) { @@ -450,6 +468,12 @@ class AllContentViewModel( _limitState.value = limit } + fun onViewBinClicked() { + viewModelScope.launch { + _commands.emit(Command.NavigateToBin(vmParams.spaceId.id)) + } + } + fun onItemClicked(item: UiContentItem.Item) { Timber.d("onItemClicked: ${item.id}") val layout = item.layout ?: return @@ -506,6 +530,7 @@ class AllContentViewModel( sealed class Command { data class NavigateToEditor(val id: Id, val space: Id) : Command() data class NavigateToSetOrCollection(val id: Id, val space: Id) : Command() + data class NavigateToBin(val space: Id) : Command() data class SendToast(val message: String) : Command() } @@ -516,7 +541,7 @@ class AllContentViewModel( //INITIAL STATE const val DEFAULT_SEARCH_LIMIT = 50 val DEFAULT_INITIAL_TAB = AllContentTab.PAGES - val DEFAULT_INITIAL_SORT = AllContentSort.ByDateCreated() + val DEFAULT_INITIAL_SORT = AllContentSort.ByName() val DEFAULT_INITIAL_MODE = AllContentMode.AllContent val DEFAULT_QUERY = "" } diff --git a/feature-all-content/src/main/java/com/anytypeio/anytype/feature_allcontent/ui/AllContentMenu.kt b/feature-all-content/src/main/java/com/anytypeio/anytype/feature_allcontent/ui/AllContentMenu.kt index 61470ddf72..0e5fe57297 100644 --- a/feature-all-content/src/main/java/com/anytypeio/anytype/feature_allcontent/ui/AllContentMenu.kt +++ b/feature-all-content/src/main/java/com/anytypeio/anytype/feature_allcontent/ui/AllContentMenu.kt @@ -3,9 +3,9 @@ package com.anytypeio.anytype.feature_allcontent.ui import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding @@ -20,10 +20,12 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment.Companion.CenterVertically import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.rotate import androidx.compose.ui.graphics.ColorFilter.Companion.tint import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.anytypeio.anytype.core_models.DVSortType import com.anytypeio.anytype.core_ui.common.DefaultPreviews @@ -39,7 +41,8 @@ import com.anytypeio.anytype.feature_allcontent.models.UiMenuState fun AllContentMenu( uiMenuState: UiMenuState, onModeClick: (AllContentMenuMode) -> Unit, - onSortClick: (AllContentSort) -> Unit + onSortClick: (AllContentSort) -> Unit, + onBinClick: () -> Unit ) { var sortingExpanded by remember { mutableStateOf(false) } @@ -51,15 +54,18 @@ fun AllContentMenu( onModeClick(item) } ) + Divider(0.5.dp) } - Spacer(modifier = Modifier.height(8.dp)) + Divider(7.5.dp) SortingBox( modifier = Modifier .clickable { sortingExpanded = !sortingExpanded }, - subtitle = uiMenuState.container.sort.title() + subtitle = uiMenuState.container.sort.title(), + isExpanded = sortingExpanded ) + Divider(0.5.dp) if (sortingExpanded) { uiMenuState.sorts.forEach { item -> MenuItem( @@ -70,8 +76,10 @@ fun AllContentMenu( onSortClick(item.sort) } ) + Divider(0.5.dp) } - uiMenuState.types.forEach { item -> + Divider(7.5.dp) + uiMenuState.types.forEachIndexed { index, item -> MenuItem( title = item.sortType.title(item.sort), isSelected = item.isSelected, @@ -85,12 +93,34 @@ fun AllContentMenu( onSortClick(updatedSort) } ) + if (index < uiMenuState.types.size - 1) { + Divider(0.5.dp) + } } } + if (uiMenuState.showBin && !sortingExpanded) { + Divider(7.5.dp) + MenuItem( + title = stringResource(id = R.string.all_content_view_bin), + isSelected = false, + modifier = Modifier.clickable { onBinClick() } + ) + } } @Composable -private fun SortingBox(modifier: Modifier, subtitle: String) { +private fun Divider(height: Dp) { + Box( + modifier = Modifier + .fillMaxWidth() + .height(height) + .background(colorResource(id = R.color.shape_tertiary)) + ) +} + +@Composable +private fun SortingBox(modifier: Modifier, subtitle: String, isExpanded: Boolean) { + val rotationAngle = if (isExpanded) 90f else 0f Row( modifier = modifier .fillMaxWidth() @@ -98,7 +128,9 @@ private fun SortingBox(modifier: Modifier, subtitle: String) { verticalAlignment = CenterVertically ) { Image( - modifier = Modifier.size(32.dp), + modifier = Modifier + .size(32.dp) + .rotate(rotationAngle), painter = painterResource(R.drawable.ic_menu_arrow_right), contentDescription = "", colorFilter = tint(colorResource(id = R.color.glyph_selected)) @@ -232,7 +264,8 @@ fun AllContentMenuPreview() { container = MenuSortsItem.Container(AllContentSort.ByName()) ), onModeClick = {}, - onSortClick = {} + onSortClick = {}, + onBinClick = {} ) } //endregion \ No newline at end of file diff --git a/feature-all-content/src/main/java/com/anytypeio/anytype/feature_allcontent/ui/AllContentScreen.kt b/feature-all-content/src/main/java/com/anytypeio/anytype/feature_allcontent/ui/AllContentScreen.kt index 1b22b0fdbc..41be83cba9 100644 --- a/feature-all-content/src/main/java/com/anytypeio/anytype/feature_allcontent/ui/AllContentScreen.kt +++ b/feature-all-content/src/main/java/com/anytypeio/anytype/feature_allcontent/ui/AllContentScreen.kt @@ -20,15 +20,13 @@ import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.material3.FabPosition import androidx.compose.material3.Scaffold import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState 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.Alignment.Companion.CenterVertically import androidx.compose.ui.Modifier @@ -42,12 +40,12 @@ import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import com.anytypeio.anytype.core_models.DVSortType import com.anytypeio.anytype.core_ui.common.DefaultPreviews import com.anytypeio.anytype.core_ui.foundation.Divider import com.anytypeio.anytype.core_ui.views.ButtonSize @@ -90,44 +88,43 @@ fun AllContentWrapperScreen( onQueryChanged: (String) -> Unit, onModeClick: (AllContentMenuMode) -> Unit, onSortClick: (AllContentSort) -> Unit, - onItemClicked: (UiContentItem.Item) -> Unit + onItemClicked: (UiContentItem.Item) -> Unit, + onBinClick: () -> Unit ) { - val objects = remember { mutableStateOf>(emptyList()) } - if (uiState is UiContentState.Content) { - objects.value = uiState.items - } AllContentMainScreen( uiTitleState = uiTitleState, uiTabsState = uiTabsState, uiMenuButtonViewState = uiMenuButtonViewState, onTabClick = onTabClick, - objects = objects, - isLoading = uiState is UiContentState.Loading, onQueryChanged = onQueryChanged, uiMenuState = uiMenuState, onModeClick = onModeClick, onSortClick = onSortClick, - onItemClicked = onItemClicked + onItemClicked = onItemClicked, + onBinClick = onBinClick, + uiState = uiState ) } @Composable fun AllContentMainScreen( + uiState: UiContentState, uiTitleState: UiTitleState, uiTabsState: UiTabsState, uiMenuButtonViewState: MenuButtonViewState, uiMenuState: UiMenuState, - objects: MutableState>, onTabClick: (AllContentTab) -> Unit, onQueryChanged: (String) -> Unit, onModeClick: (AllContentMenuMode) -> Unit, onSortClick: (AllContentSort) -> Unit, - isLoading: Boolean, - onItemClicked: (UiContentItem.Item) -> Unit + onItemClicked: (UiContentItem.Item) -> Unit, + onBinClick: () -> Unit ) { val modifier = Modifier .background(color = colorResource(id = R.color.background_primary)) + var isSearchEmpty by remember { mutableStateOf(true) } + Scaffold( modifier = modifier .fillMaxSize(), @@ -148,6 +145,7 @@ fun AllContentMainScreen( uiMenuState = uiMenuState, onSortClick = onSortClick, onModeClick = onModeClick, + onBinClick = onBinClick ) } @@ -157,7 +155,10 @@ fun AllContentMainScreen( } } Spacer(modifier = Modifier.size(10.dp)) - AllContentSearchBar(onQueryChanged) + AllContentSearchBar(onQueryChanged = { + isSearchEmpty = it.isEmpty() + onQueryChanged(it) + }) Spacer(modifier = Modifier.size(10.dp)) Divider(paddingStart = 0.dp, paddingEnd = 0.dp) } @@ -173,16 +174,35 @@ fun AllContentMainScreen( Modifier .fillMaxSize() .padding(paddingValues) - if (isLoading) { - Box(modifier = contentModifier) { - LoadingState() + + when (uiState) { + is UiContentState.Content -> { + if (uiState.items.isEmpty()) { + Box(modifier = contentModifier, contentAlignment = Alignment.Center) { + EmptyState(isSearchEmpty = isSearchEmpty) + } + } else { + ContentItems( + modifier = contentModifier, + items = uiState.items, + onItemClicked = onItemClicked + ) + } + } + is UiContentState.Error -> { + Box( + modifier = contentModifier, + contentAlignment = Alignment.Center + ) { + ErrorState(uiState.message) + } + } + UiContentState.Hidden -> {} + UiContentState.Loading -> { + Box(modifier = contentModifier) { + LoadingState() + } } - } else { - ContentItems( - modifier = contentModifier, - items = objects.value, - onItemClicked = onItemClicked - ) } } ) @@ -367,14 +387,54 @@ fun AllContentItemIcon( @Composable private fun BoxScope.ErrorState(message: String) { - Text( - modifier = Modifier - .wrapContentSize() - .align(Alignment.Center), - text = "Error : message", - color = colorResource(id = R.color.palette_system_red), - style = UXBody - ) + Column { + Text( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 32.dp), + text = stringResource(id = R.string.all_content_error_title), + color = colorResource(id = R.color.text_primary), + style = UXBody, + textAlign = TextAlign.Center + ) + Text( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 32.dp), + text = message, + color = colorResource(id = R.color.palette_system_red), + style = UXBody, + textAlign = TextAlign.Center, + maxLines = 3 + ) + } +} + +@Composable +private fun EmptyState(isSearchEmpty: Boolean) { + val (title, description) = if (!isSearchEmpty) { + stringResource(R.string.all_content_no_results_title) to stringResource(R.string.all_content_no_results_description) + } else { + stringResource(R.string.allContent_empty_state_title) to stringResource(R.string.allContent_empty_state_description) + } + Column { + Text( + modifier = Modifier + .fillMaxWidth(), + text = title, + color = colorResource(id = R.color.text_primary), + style = UXBody, + textAlign = TextAlign.Center + ) + Text( + modifier = Modifier + .fillMaxWidth(), + text = description, + color = colorResource(id = R.color.text_secondary), + style = UXBody, + textAlign = TextAlign.Center + ) + } } @Composable diff --git a/feature-all-content/src/main/java/com/anytypeio/anytype/feature_allcontent/ui/AllContentTopToolbar.kt b/feature-all-content/src/main/java/com/anytypeio/anytype/feature_allcontent/ui/AllContentTopToolbar.kt index 6119acc627..24b9cf5952 100644 --- a/feature-all-content/src/main/java/com/anytypeio/anytype/feature_allcontent/ui/AllContentTopToolbar.kt +++ b/feature-all-content/src/main/java/com/anytypeio/anytype/feature_allcontent/ui/AllContentTopToolbar.kt @@ -77,7 +77,8 @@ fun AllContentTopBarContainer( menuButtonState: MenuButtonViewState, uiMenuState: UiMenuState, onModeClick: (AllContentMenuMode) -> Unit, - onSortClick: (AllContentSort) -> Unit + onSortClick: (AllContentSort) -> Unit, + onBinClick: () -> Unit ) { var isMenuExpanded by remember { mutableStateOf(false) } @@ -96,7 +97,7 @@ fun AllContentTopBarContainer( onDismissRequest = { isMenuExpanded = false }, shape = RoundedCornerShape(size = 16.dp), containerColor = colorResource(id = R.color.background_primary), - shadowElevation = 20.dp, + shadowElevation = 5.dp ) { AllContentMenu( uiMenuState = uiMenuState, @@ -107,7 +108,8 @@ fun AllContentTopBarContainer( onSortClick = { onSortClick(it) isMenuExpanded = false - } + }, + onBinClick = onBinClick ) } }, @@ -150,7 +152,8 @@ private fun AllContentTopBarContainerPreview() { ) ), onModeClick = {}, - onSortClick = {} + onSortClick = {}, + onBinClick = {} ) } //endregion @@ -266,7 +269,6 @@ private fun getTabText(tab: AllContentTab): String { AllContentTab.MEDIA -> stringResource(id = R.string.all_content_title_tab_media) AllContentTab.BOOKMARKS -> stringResource(id = R.string.all_content_title_tab_bookmarks) AllContentTab.TYPES -> stringResource(id = R.string.all_content_title_tab_objetc_types) - AllContentTab.RELATIONS -> stringResource(id = R.string.all_content_title_tab_relations) AllContentTab.LISTS -> stringResource(id = R.string.all_content_title_tab_lists) } } @@ -281,8 +283,7 @@ private fun AllContentTabsPreview() { AllContentTab.FILES, AllContentTab.MEDIA, AllContentTab.BOOKMARKS, - AllContentTab.TYPES, - AllContentTab.RELATIONS + AllContentTab.TYPES ), selectedTab = AllContentTab.MEDIA ), diff --git a/localization/src/main/res/values/strings.xml b/localization/src/main/res/values/strings.xml index 6d3ffbfb4b..0c70d38eb2 100644 --- a/localization/src/main/res/values/strings.xml +++ b/localization/src/main/res/values/strings.xml @@ -1760,7 +1760,6 @@ Please provide specific details of your needs here. Bookmarks Files Object Types - Relations Sort by Z → A @@ -1783,5 +1782,12 @@ Please provide specific details of your needs here. Welcome to the Vault + It’s empty here. + Create your first objects to get started. + + No results found. + Try searching with different keywords. + + Something went wrong. \ No newline at end of file diff --git a/persistence/src/main/java/com/anytypeio/anytype/persistence/repo/DefaultUserSettingsCache.kt b/persistence/src/main/java/com/anytypeio/anytype/persistence/repo/DefaultUserSettingsCache.kt index 8d6285f985..861cb44538 100644 --- a/persistence/src/main/java/com/anytypeio/anytype/persistence/repo/DefaultUserSettingsCache.kt +++ b/persistence/src/main/java/com/anytypeio/anytype/persistence/repo/DefaultUserSettingsCache.kt @@ -15,6 +15,7 @@ import com.anytypeio.anytype.core_models.primitives.SpaceId import com.anytypeio.anytype.core_models.primitives.TypeId import com.anytypeio.anytype.core_models.settings.VaultSettings import com.anytypeio.anytype.data.auth.repo.UserSettingsCache +import com.anytypeio.anytype.persistence.AllContentSettings import com.anytypeio.anytype.persistence.GlobalSearchHistoryProto import com.anytypeio.anytype.persistence.SpacePreference import com.anytypeio.anytype.persistence.SpacePreferences @@ -434,6 +435,40 @@ class DefaultUserSettingsCache( } } + override suspend fun getAllContentSort(space: SpaceId): Id { + return context.spacePrefsStore + .data + .map { preferences -> + preferences + .preferences[space.id] + ?.allContent + ?.sortKey + .orEmpty() + } + .first() + } + + override suspend fun setAllContentSort(space: SpaceId, sort: Id) { + context.spacePrefsStore.updateData { existingPreferences -> + val givenSpacePreference = existingPreferences + .preferences + .getOrDefault( + key = space.id, + defaultValue = SpacePreference() + ) + val updated = givenSpacePreference.copy( + allContent = AllContentSettings( + sortKey = sort + ) + ) + val result = buildMap { + putAll(existingPreferences.preferences) + put(key = space.id, updated) + } + SpacePreferences(preferences = result) + } + } + companion object { const val CURRENT_SPACE_KEY = "prefs.user_settings.current_space" const val DEFAULT_OBJECT_TYPE_ID_KEY = "prefs.user_settings.default_object_type.id" diff --git a/persistence/src/main/proto/preferences.proto b/persistence/src/main/proto/preferences.proto index b5318784cf..3522e898a6 100644 --- a/persistence/src/main/proto/preferences.proto +++ b/persistence/src/main/proto/preferences.proto @@ -22,9 +22,14 @@ message SpacePreference { repeated string pinnedObjectTypeIds = 2; optional string lastOpenedObject = 3; optional GlobalSearchHistoryProto globalSearchHistory = 5; + optional AllContentSettings allContent = 6; } message GlobalSearchHistoryProto { optional string lastSearchQuery = 1; optional string lastSearchRelatedObjectId = 2; +} + +message AllContentSettings { + optional string sortKey = 1; } \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/home/HomeScreenViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/home/HomeScreenViewModel.kt index 09c64f13d8..bb772617a9 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/home/HomeScreenViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/home/HomeScreenViewModel.kt @@ -395,11 +395,7 @@ class HomeScreenViewModel( spaceWidgetView.map { view -> listOf(view) } } }.combine(hasEditAccess) { widgets, hasEditAccess -> - if (hasEditAccess) { - widgets + listOf(WidgetView.Library, bin) + actions - } else { - widgets - } + buildListOfWidgets(hasEditAccess, widgets) } .catch { Timber.e(it, "Error while rendering widgets") @@ -410,6 +406,18 @@ class HomeScreenViewModel( } } + private fun buildListOfWidgets( + hasEditAccess: Boolean, + widgets: List + ): List { + return buildList { + addAll(widgets) + if (hasEditAccess) { + addAll(actions) + } + } + } + private fun proceedWithWidgetContainerPipeline() { viewModelScope.launch { widgets.filterNotNull().map { widgets -> diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/home/HomeScreenViewModelTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/home/HomeScreenViewModelTest.kt index cb20940584..d2fb288a5c 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/home/HomeScreenViewModelTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/home/HomeScreenViewModelTest.kt @@ -300,7 +300,7 @@ class HomeScreenViewModelTest { } @Test - fun `should emit bin, library and actions and space view if there is no block`() = runTest { + fun `should emit actions and space view if there is no block`() = runTest { // SETUP @@ -314,8 +314,6 @@ class HomeScreenViewModelTest { blocks = listOf(smartBlock) ) - val binWidget = WidgetView.Bin(id = Subscriptions.SUBSCRIPTION_ARCHIVED) - val events: Flow> = emptyFlow() stubConfig() @@ -351,8 +349,6 @@ class HomeScreenViewModelTest { actual = secondTimeState, expected = buildList { add(defaultSpaceWidgetView) - add(WidgetView.Library) - add(binWidget) addAll(HomeScreenViewModel.actions) } ) @@ -360,7 +356,7 @@ class HomeScreenViewModelTest { } @Test - fun `should emit only bin and actions when home screen has no associated widgets except the default ones`() = + fun `should emit only actions when home screen has no associated widgets except the default ones`() = runTest { // SETUP @@ -404,8 +400,6 @@ class HomeScreenViewModelTest { assertEquals( expected = buildList { add(defaultSpaceWidgetView) - add(WidgetView.Library) - add(binWidget) addAll(HomeScreenViewModel.actions) }, actual = secondTimeItem @@ -421,7 +415,7 @@ class HomeScreenViewModelTest { } @Test - fun `should emit tree-widget with empty elements and bin when source has no links`() = runTest { + fun `should emit tree-widget with empty elements when source has no links`() = runTest { // SETUP @@ -458,8 +452,6 @@ class HomeScreenViewModelTest { ) ) - val binWidget = WidgetView.Bin(id = Subscriptions.SUBSCRIPTION_ARCHIVED) - stubConfig() stubInterceptEvents(events = emptyFlow()) stubOpenWidgetObject(givenObjectView) @@ -505,8 +497,6 @@ class HomeScreenViewModelTest { isExpanded = true ) ) - add(WidgetView.Library) - add(binWidget) addAll(HomeScreenViewModel.actions) }, actual = secondTimeState @@ -522,7 +512,7 @@ class HomeScreenViewModelTest { } @Test - fun `should emit tree-widget with 2 elements, library and bin`() = runTest { + fun `should emit tree-widget with 2 elements`() = runTest { // SETUP @@ -568,8 +558,6 @@ class HomeScreenViewModelTest { ) ) - val binWidget = WidgetView.Bin(id = Subscriptions.SUBSCRIPTION_ARCHIVED) - stubConfig() stubInterceptEvents(events = emptyFlow()) stubOpenWidgetObject(givenObjectView) @@ -633,8 +621,6 @@ class HomeScreenViewModelTest { isExpanded = true ) ) - add(WidgetView.Library) - add(binWidget) addAll(HomeScreenViewModel.actions) }, actual = secondTimeState @@ -643,7 +629,7 @@ class HomeScreenViewModelTest { } @Test - fun `should emit list without elements, library and bin`() = runTest { + fun `should emit list without elements`() = runTest { // SETUP @@ -689,8 +675,6 @@ class HomeScreenViewModelTest { ) ) - val binWidget = WidgetView.Bin(id = Subscriptions.SUBSCRIPTION_ARCHIVED) - stubConfig() stubInterceptEvents(events = emptyFlow()) stubOpenWidgetObject(givenObjectView) @@ -741,8 +725,6 @@ class HomeScreenViewModelTest { tabs = emptyList() ) ) - add(WidgetView.Library) - add(binWidget) addAll(HomeScreenViewModel.actions) }, actual = secondTimeState @@ -751,7 +733,7 @@ class HomeScreenViewModelTest { } @Test - fun `should emit compact list without elements, library and bin`() = runTest { + fun `should emit compact list without elements`() = runTest { // SETUP @@ -797,8 +779,6 @@ class HomeScreenViewModelTest { ) ) - val binWidget = WidgetView.Bin(id = Subscriptions.SUBSCRIPTION_ARCHIVED) - stubConfig() stubInterceptEvents(events = emptyFlow()) stubOpenWidgetObject(givenObjectView) @@ -849,8 +829,6 @@ class HomeScreenViewModelTest { tabs = emptyList() ) ) - add(WidgetView.Library) - add(binWidget) addAll(HomeScreenViewModel.actions) }, actual = secondTimeState @@ -859,7 +837,7 @@ class HomeScreenViewModelTest { } @Test - fun `should emit three bundled widgets with tree layout, each having 2 elements, library and bin`() = + fun `should emit three bundled widgets with tree layout, each having 2 elements`() = runTest { // SETUP @@ -924,8 +902,6 @@ class HomeScreenViewModelTest { ) ) - val binWidget = WidgetView.Bin(id = Subscriptions.SUBSCRIPTION_ARCHIVED) - stubConfig() stubInterceptEvents(events = emptyFlow()) stubOpenWidgetObject(givenObjectView) @@ -1142,8 +1118,6 @@ class HomeScreenViewModelTest { isExpanded = true ) ) - add(WidgetView.Library) - add(binWidget) addAll(HomeScreenViewModel.actions) }, actual = secondTimeState @@ -1152,7 +1126,7 @@ class HomeScreenViewModelTest { } @Test - fun `should emit link-widget, library, bin and actions`() = runTest { + fun `should emit link-widget and actions`() = runTest { // SETUP @@ -1229,8 +1203,6 @@ class HomeScreenViewModelTest { source = Widget.Source.Default(sourceObject), ) ) - add(WidgetView.Library) - add(binWidget) addAll(HomeScreenViewModel.actions) }, actual = secondTimeState @@ -1987,8 +1959,6 @@ class HomeScreenViewModelTest { actual = secondTimeState, expected = buildList { add(defaultSpaceWidgetView) - add(WidgetView.Library) - add(binWidget) addAll(HomeScreenViewModel.actions) } )