1
0
Fork 0
mirror of https://github.com/anyproto/anytype-kotlin.git synced 2025-06-08 05:47:05 +09:00

DROID-2916 App | Tech | Prepare staging release (#1666)

Co-authored-by: Konstantin Ivanov <54908981+konstantiniiv@users.noreply.github.com>
This commit is contained in:
Evgenii Kozlov 2024-10-22 12:17:11 +02:00
parent 14af7b40c1
commit b78e3e4d0d
203 changed files with 2606 additions and 1726 deletions

View file

@ -15,12 +15,13 @@ import com.anytypeio.anytype.core_models.ext.DateParser
import com.anytypeio.anytype.core_models.primitives.RelationKey
import com.anytypeio.anytype.core_models.primitives.SpaceId
import com.anytypeio.anytype.core_models.restrictions.ObjectRestriction
import com.anytypeio.anytype.domain.all_content.RestoreAllContentState
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.feature_allcontent.presentation.AllContentViewModel.Companion.DEFAULT_INITIAL_SORT
import com.anytypeio.anytype.feature_allcontent.presentation.AllContentViewModel.Companion.DEFAULT_INITIAL_TAB
import com.anytypeio.anytype.presentation.library.DependentData
import com.anytypeio.anytype.presentation.mapper.objectIcon
import com.anytypeio.anytype.presentation.objects.ObjectIcon
import com.anytypeio.anytype.presentation.objects.getDescriptionOrSnippet
import com.anytypeio.anytype.presentation.objects.getProperName
import com.anytypeio.anytype.presentation.objects.getProperType
@ -104,6 +105,11 @@ sealed class UiContentState {
) : UiContentState()
}
sealed class UiItemsState{
data object Empty : UiItemsState()
data class Content(val items: List<UiContentItem>) : UiItemsState()
}
// ITEMS
sealed class UiContentItem {
abstract val id: String
@ -151,6 +157,18 @@ sealed class UiContentItem {
val editable: Boolean = true,
) : UiContentItem()
data object NewRelation : UiContentItem() {
override val id: String = "NewRelation"
}
data object NewType : UiContentItem() {
override val id: String = "NewType"
}
data object UnlinkedDescription : UiContentItem() {
override val id: String = "UnlinkedDescription"
}
companion object {
const val TODAY_ID = "TodayId"
const val YESTERDAY_ID = "YesterdayId"
@ -188,13 +206,27 @@ sealed class MenuSortsItem {
}
//endregion
//region BOTTOM_MENU
data class AllContentBottomMenu(val isOwnerOrEditor: Boolean = true)
//endregion
//region SNACKBAR
sealed class UiSnackbarState {
data object Hidden : UiSnackbarState()
data class Visible(val message: String, val objId: Id) : UiSnackbarState()
}
//endregion
//region MAPPING
fun Key?.mapRelationKeyToSort(): AllContentSort {
return when (this) {
Relations.CREATED_DATE -> AllContentSort.ByDateCreated()
Relations.LAST_MODIFIED_DATE -> AllContentSort.ByDateUpdated()
Relations.NAME -> AllContentSort.ByName()
else -> DEFAULT_INITIAL_SORT
fun RestoreAllContentState.Response.Success.mapToSort(): AllContentSort {
val sortType = if (isAsc) DVSortType.ASC else DVSortType.DESC
return when (activeSort) {
Relations.CREATED_DATE -> AllContentSort.ByDateCreated(sortType = sortType)
Relations.LAST_MODIFIED_DATE -> AllContentSort.ByDateUpdated(sortType = sortType)
Relations.NAME -> AllContentSort.ByName(sortType = sortType)
Relations.LAST_USED_DATE -> AllContentSort.ByDateUsed(sortType = sortType)
else -> AllContentSort.ByName(sortType = DVSortType.ASC)
}
}
@ -216,12 +248,12 @@ fun ObjectWrapper.Basic.toAllContentItem(
val obj = this
val typeUrl = obj.getProperType()
val isProfile = typeUrl == MarketplaceObjectTypeIds.PROFILE
val layout = layout ?: ObjectType.Layout.BASIC
val layout = obj.layout ?: ObjectType.Layout.BASIC
return UiContentItem.Item(
id = obj.id,
space = space,
name = obj.getProperName(),
description = obj.description,
description = getDescriptionOrSnippet(),
type = typeUrl,
typeName = objectTypes.firstOrNull { type ->
if (isProfile) {

View file

@ -9,6 +9,7 @@ import com.anytypeio.anytype.core_models.ObjectTypeIds
import com.anytypeio.anytype.core_models.ObjectTypeUniqueKeys
import com.anytypeio.anytype.core_models.RelationFormat
import com.anytypeio.anytype.core_models.Relations
import com.anytypeio.anytype.core_models.primitives.SpaceId
import com.anytypeio.anytype.domain.library.StoreSearchParams
import com.anytypeio.anytype.presentation.search.ObjectSearchConstants.defaultKeys
import com.anytypeio.anytype.presentation.search.ObjectSearchConstants.defaultKeysObjectType
@ -61,6 +62,7 @@ fun createSubscriptionParams(
activeMode = activeMode
)
return StoreSearchParams(
space = SpaceId(spaceId),
filters = filters,
sorts = sorts,
keys = keys,

View file

@ -27,18 +27,21 @@ import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes
import com.anytypeio.anytype.domain.page.CreateObject
import com.anytypeio.anytype.domain.search.SearchObjects
import com.anytypeio.anytype.domain.workspace.RemoveObjectsFromWorkspace
import com.anytypeio.anytype.feature_allcontent.models.AllContentBottomMenu
import com.anytypeio.anytype.feature_allcontent.models.AllContentMenuMode
import com.anytypeio.anytype.feature_allcontent.models.AllContentSort
import com.anytypeio.anytype.feature_allcontent.models.AllContentTab
import com.anytypeio.anytype.feature_allcontent.models.MenuSortsItem
import com.anytypeio.anytype.feature_allcontent.models.UiContentItem
import com.anytypeio.anytype.feature_allcontent.models.UiContentState
import com.anytypeio.anytype.feature_allcontent.models.UiItemsState
import com.anytypeio.anytype.feature_allcontent.models.UiMenuState
import com.anytypeio.anytype.feature_allcontent.models.UiSnackbarState
import com.anytypeio.anytype.feature_allcontent.models.UiTabsState
import com.anytypeio.anytype.feature_allcontent.models.UiTitleState
import com.anytypeio.anytype.feature_allcontent.models.createSubscriptionParams
import com.anytypeio.anytype.feature_allcontent.models.filtersForSearch
import com.anytypeio.anytype.feature_allcontent.models.mapRelationKeyToSort
import com.anytypeio.anytype.feature_allcontent.models.mapToSort
import com.anytypeio.anytype.feature_allcontent.models.toAnalyticsModeType
import com.anytypeio.anytype.feature_allcontent.models.toAnalyticsSortType
import com.anytypeio.anytype.feature_allcontent.models.toAnalyticsTabType
@ -109,12 +112,14 @@ class AllContentViewModel(
) : ViewModel(), AnalyticSpaceHelperDelegate by analyticSpaceHelperDelegate {
private val searchResultIds = MutableStateFlow<List<Id>>(emptyList())
private val sortState = MutableStateFlow<AllContentSort>(DEFAULT_INITIAL_SORT)
private val sortState = MutableStateFlow<AllContentSort>(AllContentSort.ByName())
val uiTitleState = MutableStateFlow<UiTitleState>(DEFAULT_INITIAL_MODE)
val uiTabsState = MutableStateFlow<UiTabsState>(UiTabsState())
val uiMenuState = MutableStateFlow<UiMenuState>(UiMenuState.Hidden)
val uiItemsState = MutableStateFlow<List<UiContentItem>>(emptyList())
val uiItemsState = MutableStateFlow<UiItemsState>(UiItemsState.Empty)
val uiContentState = MutableStateFlow<UiContentState>(UiContentState.Idle())
val uiBottomMenu = MutableStateFlow<AllContentBottomMenu>(AllContentBottomMenu())
val uiSnackbarState = MutableStateFlow<UiSnackbarState>(UiSnackbarState.Hidden)
val commands = MutableSharedFlow<Command>()
@ -155,6 +160,8 @@ class AllContentViewModel(
userPermissionProvider
.observe(space = vmParams.spaceId)
.collect {
uiBottomMenu.value =
AllContentBottomMenu(isOwnerOrEditor = it?.isOwnerOrEditor() == true)
permission.value = it
}
}
@ -167,9 +174,14 @@ class AllContentViewModel(
val initialParams = restoreAllContentState.run(
RestoreAllContentState.Params(vmParams.spaceId)
)
if (!initialParams.activeSort.isNullOrEmpty()) {
sortState.value = initialParams.activeSort.mapRelationKeyToSort()
restartSubscription.value++
when (initialParams) {
RestoreAllContentState.Response.Empty -> {
//do nothing
}
is RestoreAllContentState.Response.Success -> {
sortState.value = initialParams.mapToSort()
restartSubscription.value++
}
}
}.onFailure { e ->
Timber.e(e, "Error restoring state")
@ -196,7 +208,7 @@ class AllContentViewModel(
success = { searchResults ->
Timber.d("Search objects by query:[$query], size: : ${searchResults.size}")
if (searchResults.isEmpty()) {
uiItemsState.value = emptyList()
uiItemsState.value = UiItemsState.Empty
uiContentState.value = UiContentState.Empty
} else {
searchResultIds.value = searchResults.map { it.id }
@ -218,7 +230,12 @@ class AllContentViewModel(
restartSubscription.flatMapLatest {
loadData()
}.collectLatest { items ->
uiItemsState.value = items
if (items.isEmpty()) {
uiItemsState.value = UiItemsState.Empty
uiContentState.value = UiContentState.Empty
} else {
uiItemsState.value = UiItemsState.Content(items)
}
}
}
}
@ -290,20 +307,25 @@ class AllContentViewModel(
activeSort: AllContentSort,
activeTab: AllContentTab
): List<UiContentItem> {
val isOwnerOrEditor = permission.value?.isOwnerOrEditor() == true
return when (activeTab) {
AllContentTab.TYPES -> {
val items = objectWrappers.toUiContentTypes(
urlBuilder = urlBuilder,
isOwnerOrEditor = permission.value?.isOwnerOrEditor() == true
isOwnerOrEditor = isOwnerOrEditor
)
items
buildList {
if (isOwnerOrEditor) add(UiContentItem.NewType)
addAll(items)
}
}
AllContentTab.RELATIONS -> {
val items = objectWrappers.toUiContentRelations(
isOwnerOrEditor = permission.value?.isOwnerOrEditor() == true
)
items
val items = objectWrappers.toUiContentRelations(isOwnerOrEditor = isOwnerOrEditor)
buildList {
if (isOwnerOrEditor) add(UiContentItem.NewRelation)
addAll(items)
}
}
else -> {
@ -311,7 +333,7 @@ class AllContentViewModel(
space = vmParams.spaceId,
urlBuilder = urlBuilder,
objectTypes = storeOfObjectTypes.getAll(),
isOwnerOrEditor = permission.value?.isOwnerOrEditor() == true
isOwnerOrEditor = isOwnerOrEditor
)
val result = when (activeSort) {
is AllContentSort.ByDateCreated -> {
@ -330,7 +352,14 @@ class AllContentViewModel(
items
}
}
result
if (uiTitleState.value == UiTitleState.OnlyUnlinked) {
buildList {
add(UiContentItem.UnlinkedDescription)
addAll(result)
}
} else {
result
}
}
}
}
@ -422,6 +451,7 @@ class AllContentViewModel(
spaces = listOf(vmParams.spaceId.id)
)
return SearchObjects.Params(
space = vmParams.spaceId,
filters = filters,
keys = listOf(Relations.ID),
fulltext = activeQuery
@ -484,14 +514,14 @@ class AllContentViewModel(
}
else -> {
listOf(
MenuSortsItem.Sort(
sort = AllContentSort.ByName(isSelected = activeSort is AllContentSort.ByName)
),
MenuSortsItem.Sort(
sort = AllContentSort.ByDateUpdated(isSelected = activeSort is AllContentSort.ByDateUpdated)
),
MenuSortsItem.Sort(
sort = AllContentSort.ByDateCreated(isSelected = activeSort is AllContentSort.ByDateCreated)
),
MenuSortsItem.Sort(
sort = AllContentSort.ByName(isSelected = activeSort is AllContentSort.ByName)
)
)
}
@ -531,7 +561,7 @@ class AllContentViewModel(
tab.updateInitialState()
shouldScrollToTopItems = true
resetLimit()
uiItemsState.value = emptyList()
uiItemsState.value = UiItemsState.Empty
uiTabsState.value = uiTabsState.value.copy(selectedTab = tab)
restartSubscription.value++
viewModelScope.launch {
@ -545,7 +575,7 @@ class AllContentViewModel(
fun onAllContentModeClicked(mode: AllContentMenuMode) {
Timber.d("onAllContentModeClicked: $mode")
shouldScrollToTopItems = true
uiItemsState.value = emptyList()
uiItemsState.value = UiItemsState.Empty
uiTitleState.value = when (mode) {
is AllContentMenuMode.AllContent -> UiTitleState.AllContent
is AllContentMenuMode.Unlinked -> UiTitleState.OnlyUnlinked
@ -576,7 +606,7 @@ class AllContentViewModel(
}
}
shouldScrollToTopItems = true
uiItemsState.value = emptyList()
uiItemsState.value = UiItemsState.Empty
sortState.value = newSort
proceedWithSortSaving(uiTabsState.value, newSort)
restartSubscription.value++
@ -712,7 +742,10 @@ class AllContentViewModel(
objType: ObjectWrapper.Type? = null
) {
val startTime = System.currentTimeMillis()
val params = objType?.uniqueKey.getCreateObjectParams(objType?.defaultTemplateId)
val params = objType?.uniqueKey.getCreateObjectParams(
space = vmParams.spaceId,
objType?.defaultTemplateId
)
viewModelScope.launch {
createObject.async(params).fold(
onSuccess = { result ->
@ -733,10 +766,22 @@ class AllContentViewModel(
}
}
fun onTypeClicked(item: UiContentItem.Type) {
fun onTypeClicked(item: UiContentItem) {
Timber.d("onTypeClicked: $item")
viewModelScope.launch {
commands.emit(Command.OpenTypeEditing(item))
when (item) {
UiContentItem.NewType -> {
viewModelScope.launch {
commands.emit(Command.OpenTypeCreation)
}
}
is UiContentItem.Type -> {
viewModelScope.launch {
commands.emit(Command.OpenTypeEditing(item))
}
}
else -> {
//do nothing
}
}
viewModelScope.sendEvent(
analytics = analytics,
@ -744,24 +789,38 @@ class AllContentViewModel(
)
}
fun onRelationClicked(item: UiContentItem.Relation) {
fun onRelationClicked(item: UiContentItem) {
Timber.d("onRelationClicked: $item")
viewModelScope.launch {
commands.emit(
Command.OpenRelationEditing(
typeName = item.name,
id = item.id,
iconUnicode = item.format.simpleIcon() ?: 0,
readOnly = item.readOnly
)
)
}
viewModelScope.launch {
viewModelScope.sendEvent(
analytics = analytics,
eventName = libraryScreenRelation
)
when (item) {
UiContentItem.NewRelation -> {
viewModelScope.launch {
commands.emit(
Command.OpenRelationCreation(
space = vmParams.spaceId.id
)
)
}
}
is UiContentItem.Relation -> {
viewModelScope.launch {
commands.emit(
Command.OpenRelationEditing(
typeName = item.name,
id = item.id,
iconUnicode = item.format.simpleIcon() ?: 0,
readOnly = item.readOnly
)
)
}
}
else -> {
//do nothing
}
}
viewModelScope.sendEvent(
analytics = analytics,
eventName = libraryScreenRelation
)
}
fun onStart() {
@ -782,7 +841,7 @@ class AllContentViewModel(
viewModelScope.launch {
userInput.value = DEFAULT_QUERY
searchResultIds.value = emptyList()
uiItemsState.value = emptyList()
uiItemsState.value = UiItemsState.Empty
uiContentState.value = UiContentState.Empty
}
}
@ -796,7 +855,11 @@ class AllContentViewModel(
setObjectListIsArchived.async(params).fold(
onSuccess = { ids ->
Timber.d("Successfully archived object: $ids")
commands.emit(Command.SendToast.ObjectArchived(item.name))
val name = item.name
uiSnackbarState.value = UiSnackbarState.Visible(
message = name.take(10),
objId = item.id
)
},
onFailure = { e ->
Timber.e(e, "Error while archiving object")
@ -806,6 +869,29 @@ class AllContentViewModel(
}
}
fun proceedWithUndoMoveToBin(objectId: Id) {
val params = SetObjectListIsArchived.Params(
targets = listOf(objectId),
isArchived = false
)
viewModelScope.launch {
setObjectListIsArchived.async(params).fold(
onSuccess = { ids ->
Timber.d("Successfully archived object: $ids")
uiSnackbarState.value = UiSnackbarState.Hidden
},
onFailure = { e ->
Timber.e(e, "Error while un-archiving object")
commands.emit(Command.SendToast.Error("Error while un-archiving object"))
}
)
}
}
fun proceedWithDismissSnackbar() {
uiSnackbarState.value = UiSnackbarState.Hidden
}
/**
* Updates the limit for the number of items fetched and triggers data reload.
*/
@ -819,7 +905,7 @@ class AllContentViewModel(
override fun onCleared() {
super.onCleared()
uiItemsState.value = emptyList()
uiItemsState.value = UiItemsState.Empty
resetLimit()
}
@ -893,14 +979,14 @@ class AllContentViewModel(
data class ObjectArchived(val name: String) : SendToast()
}
data class OpenTypeEditing(val item: UiContentItem.Type) : Command()
data class OpenTypeCreation(val name: String): Command()
data object OpenTypeCreation: Command()
data class OpenRelationEditing(
val typeName: String,
val id: Id,
val iconUnicode: Int,
val readOnly: Boolean
) : Command()
data class OpenRelationCreation(val id: Id, val name: String, val space: Id): Command()
data class OpenRelationCreation(val space: Id): Command()
data object OpenGlobalSearch : Command()
data object ExitToVault : Command()
data object Back : Command()
@ -913,7 +999,6 @@ class AllContentViewModel(
const val DEFAULT_SEARCH_LIMIT = 100
val DEFAULT_INITIAL_MODE = UiTitleState.AllContent
val DEFAULT_INITIAL_TAB = AllContentTab.PAGES
val DEFAULT_INITIAL_SORT = AllContentSort.ByName()
val DEFAULT_QUERY = ""
}
}

View file

@ -132,9 +132,10 @@ private fun SortingBox(modifier: Modifier, subtitle: String, isExpanded: Boolean
) {
Image(
modifier = Modifier
.size(32.dp)
.padding(start = 10.dp)
.size(18.dp)
.rotate(rotationAngle),
painter = painterResource(R.drawable.ic_menu_arrow_right),
painter = painterResource(R.drawable.ic_arrow_disclosure_18),
contentDescription = "",
colorFilter = tint(colorResource(id = R.color.glyph_selected))
)

View file

@ -8,6 +8,7 @@ import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically
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.BoxScope
import androidx.compose.foundation.layout.Column
@ -26,11 +27,15 @@ 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.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.LazyItemScope
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.ListItem
import androidx.compose.material3.ListItemDefaults
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarDuration
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult
import androidx.compose.material3.SwipeToDismissBox
import androidx.compose.material3.SwipeToDismissBoxValue
import androidx.compose.material3.Text
@ -39,6 +44,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
@ -59,14 +65,17 @@ 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.dp
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.primitives.SpaceId
import com.anytypeio.anytype.core_ui.common.DefaultPreviews
import com.anytypeio.anytype.core_ui.extensions.simpleIcon
import com.anytypeio.anytype.core_ui.extensions.swapList
import com.anytypeio.anytype.core_ui.foundation.DismissBackground
import com.anytypeio.anytype.core_ui.foundation.Divider
import com.anytypeio.anytype.core_ui.foundation.components.BottomNavigationMenu
import com.anytypeio.anytype.core_ui.foundation.noRippleClickable
import com.anytypeio.anytype.core_ui.foundation.noRippleThrottledClickable
import com.anytypeio.anytype.core_ui.views.BodyRegular
import com.anytypeio.anytype.core_ui.views.ButtonSize
import com.anytypeio.anytype.core_ui.views.Caption1Regular
import com.anytypeio.anytype.core_ui.views.PreviewTitle1Medium
@ -79,15 +88,19 @@ import com.anytypeio.anytype.core_ui.widgets.ListWidgetObjectIcon
import com.anytypeio.anytype.core_utils.insets.EDGE_TO_EDGE_MIN_SDK
import com.anytypeio.anytype.feature_allcontent.BuildConfig
import com.anytypeio.anytype.feature_allcontent.R
import com.anytypeio.anytype.feature_allcontent.models.AllContentBottomMenu
import com.anytypeio.anytype.feature_allcontent.models.AllContentMenuMode
import com.anytypeio.anytype.feature_allcontent.models.AllContentSort
import com.anytypeio.anytype.feature_allcontent.models.AllContentTab
import com.anytypeio.anytype.feature_allcontent.models.UiContentItem
import com.anytypeio.anytype.feature_allcontent.models.UiContentState
import com.anytypeio.anytype.feature_allcontent.models.UiItemsState
import com.anytypeio.anytype.feature_allcontent.models.UiMenuState
import com.anytypeio.anytype.feature_allcontent.models.UiSnackbarState
import com.anytypeio.anytype.feature_allcontent.models.UiTabsState
import com.anytypeio.anytype.feature_allcontent.models.UiTitleState
import com.anytypeio.anytype.presentation.objects.ObjectIcon
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@ -97,49 +110,34 @@ fun AllContentWrapperScreen(
uiTitleState: UiTitleState,
uiTabsState: UiTabsState,
uiMenuState: UiMenuState,
uiItemsState: List<UiContentItem>,
uiSnackbarState: UiSnackbarState,
uiItemsState: UiItemsState,
uiBottomMenu: AllContentBottomMenu,
onTabClick: (AllContentTab) -> Unit,
onQueryChanged: (String) -> Unit,
onModeClick: (AllContentMenuMode) -> Unit,
onSortClick: (AllContentSort) -> Unit,
onItemClicked: (UiContentItem.Item) -> Unit,
onTypeClicked: (UiContentItem.Type) -> Unit,
onRelationClicked: (UiContentItem.Relation) -> Unit,
onTypeClicked: (UiContentItem) -> Unit,
onRelationClicked: (UiContentItem) -> Unit,
onBinClick: () -> Unit,
canPaginate: Boolean,
onUpdateLimitSearch: () -> Unit,
uiContentState: UiContentState,
onHomeClicked: () -> Unit,
onGlobalSearchClicked: () -> Unit,
onAddDocClicked: () -> Unit,
onCreateObjectLongClicked: () -> Unit,
onBackClicked: () -> Unit,
onBackLongClicked: () -> Unit,
moveToBin: (UiContentItem.Item) -> Unit
moveToBin: (UiContentItem.Item) -> Unit,
undoMoveToBin: (Id) -> Unit,
onDismissSnackbar: () -> Unit
) {
val lazyListState = rememberLazyListState()
val canPaginateState = remember { mutableStateOf(false) }
LaunchedEffect(key1 = canPaginate) {
canPaginateState.value = canPaginate
}
val shouldStartPaging = remember {
derivedStateOf {
canPaginateState.value && (lazyListState.layoutInfo.visibleItemsInfo.lastOrNull()?.index
?: -9) >= (lazyListState.layoutInfo.totalItemsCount - 2)
}
}
LaunchedEffect(key1 = shouldStartPaging.value) {
if (shouldStartPaging.value && uiContentState is UiContentState.Idle) {
onUpdateLimitSearch()
}
}
AllContentMainScreen(
uiTitleState = uiTitleState,
uiTabsState = uiTabsState,
uiSnackbarState = uiSnackbarState,
onTabClick = onTabClick,
onQueryChanged = onQueryChanged,
uiMenuState = uiMenuState,
@ -148,46 +146,71 @@ fun AllContentWrapperScreen(
onItemClicked = onItemClicked,
onBinClick = onBinClick,
uiItemsState = uiItemsState,
lazyListState = lazyListState,
uiContentState = uiContentState,
onTypeClicked = onTypeClicked,
onHomeClicked = onHomeClicked,
onGlobalSearchClicked = onGlobalSearchClicked,
onAddDocClicked = onAddDocClicked,
onCreateObjectLongClicked = onCreateObjectLongClicked,
onBackClicked = onBackClicked,
onBackLongClicked = onBackLongClicked,
moveToBin = moveToBin,
onRelationClicked = onRelationClicked
onRelationClicked = onRelationClicked,
uiBottomMenu = uiBottomMenu,
undoMoveToBin = undoMoveToBin,
onDismissSnackbar = onDismissSnackbar,
canPaginate = canPaginate,
onUpdateLimitSearch = onUpdateLimitSearch
)
}
@Composable
fun AllContentMainScreen(
uiItemsState: List<UiContentItem>,
uiItemsState: UiItemsState,
uiTitleState: UiTitleState,
uiTabsState: UiTabsState,
uiMenuState: UiMenuState,
uiSnackbarState: UiSnackbarState,
uiBottomMenu: AllContentBottomMenu,
onTabClick: (AllContentTab) -> Unit,
onQueryChanged: (String) -> Unit,
onModeClick: (AllContentMenuMode) -> Unit,
onSortClick: (AllContentSort) -> Unit,
onItemClicked: (UiContentItem.Item) -> Unit,
onTypeClicked: (UiContentItem.Type) -> Unit,
onRelationClicked: (UiContentItem.Relation) -> Unit,
onTypeClicked: (UiContentItem) -> Unit,
onRelationClicked: (UiContentItem) -> Unit,
onBinClick: () -> Unit,
lazyListState: LazyListState,
uiContentState: UiContentState,
onHomeClicked: () -> Unit,
onGlobalSearchClicked: () -> Unit,
onAddDocClicked: () -> Unit,
onCreateObjectLongClicked: () -> Unit,
onBackClicked: () -> Unit,
onBackLongClicked: () -> Unit,
moveToBin: (UiContentItem.Item) -> Unit
moveToBin: (UiContentItem.Item) -> Unit,
undoMoveToBin: (Id) -> Unit,
onDismissSnackbar: () -> Unit,
canPaginate: Boolean,
onUpdateLimitSearch: () -> Unit,
) {
var isSearchEmpty by remember { mutableStateOf(true) }
val snackBarHostState = remember { SnackbarHostState() }
val snackBarText = stringResource(R.string.all_content_snackbar_title)
val undoText = stringResource(R.string.undo)
LaunchedEffect(key1 = uiSnackbarState) {
if (uiSnackbarState is UiSnackbarState.Visible) {
ShowMoveToBinSnackbar(
message = "'${uiSnackbarState.message}' $snackBarText",
undo = undoText,
scope = this,
snackBarHostState = snackBarHostState,
objectId = uiSnackbarState.objId,
undoMoveToBin = undoMoveToBin,
onDismissSnackbar = onDismissSnackbar
)
}
}
Scaffold(
modifier = Modifier
@ -207,12 +230,12 @@ fun AllContentMainScreen(
) {
BottomMenu(
modifier = Modifier.align(Alignment.BottomCenter),
onHomeClicked = onHomeClicked,
onGlobalSearchClicked = onGlobalSearchClicked,
onAddDocClicked = onAddDocClicked,
onCreateObjectLongClicked = onCreateObjectLongClicked,
onBackClicked = onBackClicked,
onBackLongClicked = onBackLongClicked
onBackLongClicked = onBackLongClicked,
uiBottomMenu = uiBottomMenu
)
}
},
@ -262,8 +285,8 @@ fun AllContentMainScreen(
modifier = contentModifier,
contentAlignment = Alignment.Center
) {
when {
uiItemsState.isEmpty() -> {
when (uiItemsState) {
UiItemsState.Empty -> {
when (uiContentState) {
is UiContentState.Error -> {
ErrorState(uiContentState.message)
@ -284,27 +307,31 @@ fun AllContentMainScreen(
}
}
else -> {
is UiItemsState.Content -> {
ContentItems(
uiItemsState = uiItemsState,
onItemClicked = onItemClicked,
onTypeClicked = onTypeClicked,
uiContentState = uiContentState,
lazyListState = lazyListState,
moveToBin = moveToBin,
onRelationClicked = onRelationClicked
onRelationClicked = onRelationClicked,
canPaginate = canPaginate,
onUpdateLimitSearch = onUpdateLimitSearch
)
}
}
}
},
snackbarHost = {
SnackbarHost(hostState = snackBarHostState)
}
)
}
@Composable
fun BottomMenu(
uiBottomMenu: AllContentBottomMenu,
modifier: Modifier = Modifier,
onHomeClicked: () -> Unit,
onGlobalSearchClicked: () -> Unit,
onAddDocClicked: () -> Unit,
onCreateObjectLongClicked: () -> Unit,
@ -317,42 +344,69 @@ fun BottomMenu(
modifier = modifier,
backClick = onBackClicked,
backLongClick = onBackLongClicked,
onProfileClicked = onHomeClicked,
searchClick = onGlobalSearchClicked,
addDocClick = onAddDocClicked,
onCreateObjectLongClicked = onCreateObjectLongClicked
addDocLongClick = onCreateObjectLongClicked,
isOwnerOrEditor = uiBottomMenu.isOwnerOrEditor
)
}
@Composable
private fun ContentItems(
uiItemsState: List<UiContentItem>,
uiItemsState: UiItemsState.Content,
onItemClicked: (UiContentItem.Item) -> Unit,
onTypeClicked: (UiContentItem.Type) -> Unit,
onRelationClicked: (UiContentItem.Relation) -> Unit,
onTypeClicked: (UiContentItem) -> Unit,
onRelationClicked: (UiContentItem) -> Unit,
uiContentState: UiContentState,
lazyListState: LazyListState,
moveToBin: (UiContentItem.Item) -> Unit
canPaginate: Boolean,
moveToBin: (UiContentItem.Item) -> Unit,
onUpdateLimitSearch: () -> Unit
) {
val items = remember { mutableStateListOf<UiContentItem>() }
items.swapList(uiItemsState.items)
val scope = rememberCoroutineScope()
val lazyListState = rememberLazyListState()
val canPaginateState = remember { mutableStateOf(false) }
LaunchedEffect(key1 = canPaginate) {
canPaginateState.value = canPaginate
}
val shouldStartPaging = remember {
derivedStateOf {
canPaginateState.value && (lazyListState.layoutInfo.visibleItemsInfo.lastOrNull()?.index
?: -9) >= (lazyListState.layoutInfo.totalItemsCount - 2)
}
}
LaunchedEffect(key1 = shouldStartPaging.value) {
if (shouldStartPaging.value && uiContentState is UiContentState.Idle) {
onUpdateLimitSearch()
}
}
LazyColumn(
modifier = Modifier.fillMaxSize(),
state = lazyListState
) {
items(
count = uiItemsState.size,
key = { index -> uiItemsState[index].id },
count = items.size,
key = { index -> items[index].id },
contentType = { index ->
when (uiItemsState[index]) {
when (items[index]) {
is UiContentItem.Group -> "group"
is UiContentItem.Item -> "item"
is UiContentItem.Type -> "type"
is UiContentItem.Relation -> "relation"
UiContentItem.NewRelation -> "new_relation"
UiContentItem.NewType -> "new_type"
UiContentItem.UnlinkedDescription -> "unlinked_description"
}
}
) { index ->
when (val item = uiItemsState[index]) {
when (val item = items[index]) {
is UiContentItem.Group -> {
Box(
modifier = Modifier
@ -412,6 +466,29 @@ private fun ContentItems(
item = item
)
}
UiContentItem.NewRelation -> {
AddItem(
modifier = Modifier
.clickable { onRelationClicked(item) },
text = stringResource(id = R.string.all_content_new_relation)
)
Divider(paddingStart = 16.dp, paddingEnd = 16.dp)
}
UiContentItem.NewType -> {
AddItem(
modifier = Modifier
.clickable { onTypeClicked(item) },
text = stringResource(id = R.string.all_content_new_type)
)
Divider(paddingStart = 16.dp, paddingEnd = 16.dp)
}
UiContentItem.UnlinkedDescription -> {
UnlinkedDescription()
Divider(paddingStart = 16.dp, paddingEnd = 16.dp)
}
}
}
if (uiContentState is UiContentState.Paging) {
@ -442,6 +519,48 @@ private fun ContentItems(
}
}
@Composable
private fun LazyItemScope.UnlinkedDescription() {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
.height(64.dp)
.animateItem(),
contentAlignment = Alignment.CenterStart
) {
Text(
text = stringResource(id = R.string.all_content_unlinked_description),
style = Caption1Regular,
color = colorResource(id = R.color.text_secondary),
)
}
}
@Composable
private fun LazyItemScope.AddItem(modifier: Modifier, text: String) {
Box(
modifier = modifier
.fillMaxWidth()
.height(52.dp)
.padding(horizontal = 16.dp)
.animateItem(),
contentAlignment = Alignment.CenterStart
) {
Image(
painter = painterResource(id = R.drawable.ic_default_plus),
contentDescription = text,
modifier = Modifier.size(24.dp)
)
Text(
modifier = Modifier.padding(start = 34.dp),
text = text,
style = BodyRegular,
color = colorResource(id = R.color.text_secondary),
)
}
}
@Composable
private fun BoxScope.LoadingState() {
val loadingAlpha by animateFloatAsState(targetValue = 1f, label = "")
@ -468,7 +587,7 @@ fun PreviewLoadingState() {
@Composable
fun PreviewMainScreen() {
AllContentMainScreen(
uiItemsState = emptyList(),
uiItemsState = UiItemsState.Empty,
uiTitleState = UiTitleState.AllContent,
uiTabsState = UiTabsState(
tabs = listOf(
@ -484,17 +603,21 @@ fun PreviewMainScreen() {
onSortClick = {},
onItemClicked = {},
onBinClick = {},
lazyListState = rememberLazyListState(),
uiContentState = UiContentState.Error("Error message"),
onTypeClicked = {},
onHomeClicked = {},
onGlobalSearchClicked = {},
onAddDocClicked = {},
onCreateObjectLongClicked = {},
onBackClicked = {},
moveToBin = {},
onBackLongClicked = {},
onRelationClicked = {}
onRelationClicked = {},
uiBottomMenu = AllContentBottomMenu(isOwnerOrEditor = false),
uiSnackbarState = UiSnackbarState.Hidden,
undoMoveToBin = {},
onDismissSnackbar = {},
canPaginate = true,
onUpdateLimitSearch = {}
)
}
@ -614,7 +737,10 @@ private fun Relation(
@Composable
private fun ErrorState(message: String) {
Column {
Column(
modifier = Modifier
.windowInsetsPadding(WindowInsets.ime)
) {
Text(
modifier = Modifier
.fillMaxWidth()
@ -644,7 +770,10 @@ private fun EmptyState(isSearchEmpty: Boolean) {
} else {
stringResource(R.string.allContent_empty_state_title) to stringResource(R.string.allContent_empty_state_description)
}
Column {
Column(
modifier = Modifier
.windowInsetsPadding(WindowInsets.ime)
) {
Text(
modifier = Modifier
.fillMaxWidth(),
@ -712,6 +841,7 @@ fun SwipeToDismissListItems(
) {
var isRemoved by remember { mutableStateOf(false) }
val dismissState = rememberSwipeToDismissBoxState(
initialValue = SwipeToDismissBoxValue.Settled,
confirmValueChange = { value ->
if (value == SwipeToDismissBoxValue.EndToStart) {
isRemoved = true
@ -723,6 +853,12 @@ fun SwipeToDismissListItems(
positionalThreshold = { it * .5f }
)
if (dismissState.currentValue != SwipeToDismissBoxValue.Settled) {
LaunchedEffect(Unit) {
dismissState.snapTo(SwipeToDismissBoxValue.Settled)
}
}
LaunchedEffect(key1 = isRemoved) {
if (isRemoved) {
delay(animationDuration.toLong())
@ -752,6 +888,35 @@ fun SwipeToDismissListItems(
}
}
private fun ShowMoveToBinSnackbar(
objectId: Id,
message: String,
undo: String,
scope: CoroutineScope,
snackBarHostState: SnackbarHostState,
undoMoveToBin: (Id) -> Unit,
onDismissSnackbar: () -> Unit
) {
scope.launch {
val result = snackBarHostState
.showSnackbar(
message = message,
actionLabel = undo,
duration = SnackbarDuration.Short,
withDismissAction = true
)
when (result) {
SnackbarResult.ActionPerformed -> {
undoMoveToBin(objectId)
}
SnackbarResult.Dismissed -> {
onDismissSnackbar()
}
}
}
}
@DefaultPreviews
@Composable
fun MtSwipeToDismissListItems() {

View file

@ -101,14 +101,8 @@ fun AllContentTopBarContainer(
) {
AllContentMenu(
uiMenuState = uiMenuState,
onModeClick = {
onModeClick(it)
isMenuExpanded = false
},
onSortClick = {
onSortClick(it)
isMenuExpanded = false
},
onModeClick = onModeClick,
onSortClick = onSortClick,
onBinClick = onBinClick
)
}