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

DROID-3429 Primitives | Edit type properties, part 2 (#2143)

This commit is contained in:
Konstantin Ivanov 2025-03-10 16:27:03 +01:00 committed by GitHub
parent ec9cdd1c60
commit 17a981cf35
Signed by: github
GPG key ID: B5690EEEBB952194
2 changed files with 402 additions and 1 deletions

View file

@ -2,20 +2,33 @@ package com.anytypeio.anytype.ui.primitives
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import androidx.core.os.bundleOf
import androidx.fragment.app.viewModels
import androidx.fragment.compose.content
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.primitives.SpaceId
import com.anytypeio.anytype.core_ui.views.BaseAlertDialog
import com.anytypeio.anytype.core_utils.ext.argString
import com.anytypeio.anytype.core_utils.ext.setupBottomSheetBehavior
import com.anytypeio.anytype.core_utils.ext.subscribe
import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetComposeFragment
import com.anytypeio.anytype.di.common.componentManager
import com.anytypeio.anytype.feature_properties.EditTypePropertiesViewModel
import com.anytypeio.anytype.feature_properties.EditTypePropertiesViewModelFactory
import com.anytypeio.anytype.feature_properties.EditTypePropertiesViewModel
import com.anytypeio.anytype.feature_properties.EditTypePropertiesViewModel.EditTypePropertiesCommand
import com.anytypeio.anytype.feature_properties.add.EditTypePropertiesVmParams
import com.anytypeio.anytype.feature_properties.add.UiEditTypePropertiesErrorState
import com.anytypeio.anytype.feature_properties.add.ui.AddFieldScreen
import javax.inject.Inject
class EditTypePropertiesFragment : BaseBottomSheetComposeFragment() {
@ -33,6 +46,75 @@ class EditTypePropertiesFragment : BaseBottomSheetComposeFragment() {
savedInstanceState: Bundle?
) = content {
MaterialTheme {
AddFieldScreen(
state = vm.uiState.collectAsStateWithLifecycle().value,
uiStateEditProperty = vm.uiPropertyEditState.collectAsStateWithLifecycle().value,
event = vm::onEvent
)
ErrorScreen()
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun ErrorScreen() {
val errorStateScreen = vm.errorState.collectAsStateWithLifecycle().value
if (errorStateScreen is UiEditTypePropertiesErrorState.Show) {
when (val r = errorStateScreen.reason) {
is UiEditTypePropertiesErrorState.Reason.ErrorAddingProperty -> {
BaseAlertDialog(
dialogText = stringResource(id = R.string.add_property_error_add),
buttonText = stringResource(id = R.string.membership_error_button_text_dismiss),
onButtonClick = vm::hideError,
onDismissRequest = vm::hideError
)
}
is UiEditTypePropertiesErrorState.Reason.ErrorCreatingProperty -> {
BaseAlertDialog(
dialogText = stringResource(id = R.string.add_property_error_create_new),
buttonText = stringResource(id = R.string.membership_error_button_text_dismiss),
onButtonClick = vm::hideError,
onDismissRequest = vm::hideError
)
}
is UiEditTypePropertiesErrorState.Reason.ErrorUpdatingProperty -> {
BaseAlertDialog(
dialogText = stringResource(id = R.string.add_property_error_update),
buttonText = stringResource(id = R.string.membership_error_button_text_dismiss),
onButtonClick = vm::hideError,
onDismissRequest = vm::hideError
)
}
is UiEditTypePropertiesErrorState.Reason.Other -> {
BaseAlertDialog(
dialogText = r.msg,
buttonText = stringResource(id = R.string.membership_error_button_text_dismiss),
onButtonClick = vm::hideError,
onDismissRequest = vm::hideError
)
}
}
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupBottomSheetBehavior(DEFAULT_PADDING_TOP)
}
override fun onStart() {
super.onStart()
jobs += lifecycleScope.subscribe(vm.commands) { command -> execute(command) }
}
private fun execute(command: EditTypePropertiesCommand) {
when (command) {
is EditTypePropertiesCommand.Exit -> {
findNavController().popBackStack()
}
}
}

View file

@ -1,13 +1,41 @@
package com.anytypeio.anytype.feature_properties
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.Key
import com.anytypeio.anytype.core_models.ObjectWrapper
import com.anytypeio.anytype.core_models.Relations
import com.anytypeio.anytype.core_ui.extensions.simpleIcon
import com.anytypeio.anytype.domain.base.fold
import com.anytypeio.anytype.domain.`object`.SetObjectDetails
import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes
import com.anytypeio.anytype.domain.objects.StoreOfRelations
import com.anytypeio.anytype.domain.primitives.SetObjectTypeRecommendedFields
import com.anytypeio.anytype.domain.relations.CreateRelation
import com.anytypeio.anytype.domain.resources.StringResourceProvider
import com.anytypeio.anytype.feature_properties.add.UiEditTypePropertiesEvent
import com.anytypeio.anytype.feature_properties.add.EditTypePropertiesVmParams
import com.anytypeio.anytype.feature_properties.add.UiEditTypePropertiesErrorState
import com.anytypeio.anytype.feature_properties.add.UiEditTypePropertiesItem
import com.anytypeio.anytype.feature_properties.add.UiEditTypePropertiesState
import com.anytypeio.anytype.feature_properties.add.mapToStateItem
import com.anytypeio.anytype.feature_properties.edit.UiEditPropertyState
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.launch
import timber.log.Timber
class EditTypePropertiesViewModel(
private val vmParams: EditTypePropertiesVmParams,
@ -18,5 +46,296 @@ class EditTypePropertiesViewModel(
private val setObjectDetails: SetObjectDetails,
private val setObjectTypeRecommendedFields: SetObjectTypeRecommendedFields
) : ViewModel() {
private val _uiState = MutableStateFlow(UiEditTypePropertiesState.Companion.EMPTY)
val uiState = _uiState.asStateFlow()
private val _errorState =
MutableStateFlow<UiEditTypePropertiesErrorState>(UiEditTypePropertiesErrorState.Hidden)
val errorState = _errorState.asStateFlow()
val uiPropertyEditState =
MutableStateFlow<UiEditPropertyState>(UiEditPropertyState.Hidden)
private val _commands = MutableSharedFlow<EditTypePropertiesCommand>()
val commands = _commands.asSharedFlow()
private val input = MutableStateFlow("")
@OptIn(FlowPreview::class)
private val query = input.take(1).onCompletion {
emitAll(
input.drop(1).debounce(DEBOUNCE_DURATION).distinctUntilChanged()
)
}
//region Init
init {
setupAddNewPropertiesState()
}
private fun setupAddNewPropertiesState() {
viewModelScope.launch {
combine(
storeOfObjectTypes.trackChanges(),
storeOfRelations.trackChanges(),
query
) { _, _, queryText ->
val objType = storeOfObjectTypes.get(id = vmParams.objectTypeId)
if (objType != null) {
val typeKeys =
objType.recommendedRelations + objType.recommendedFeaturedRelations + objType.recommendedFileRelations + objType.recommendedHiddenRelations
queryText to filterProperties(
allProperties = storeOfRelations.getAll(),
typeKeys = typeKeys,
queryText = queryText
)
} else {
Timber.w("Object type:[${vmParams.objectTypeId}] not found in the store")
queryText to emptyList()
}
}.catch {
Timber.e(it, "Error while filtering properties")
_errorState.value = UiEditTypePropertiesErrorState.Show(
UiEditTypePropertiesErrorState.Reason.Other("Error while filtering properties")
)
}
.collect { (queryText, filteredProperties) ->
val sortedExistingItems = filteredProperties.mapNotNull { field ->
field.mapToStateItem(
stringResourceProvider = stringResourceProvider
)
}.sortedBy { it.title }
setUiState(
queryText = queryText,
sortedExistingProperties = sortedExistingItems
)
}
}
}
//endregion
//region Ui State
private fun setUiState(
queryText: String,
sortedExistingProperties: List<UiEditTypePropertiesItem>
) {
val items = buildList {
if (queryText.isNotEmpty()) {
add(UiEditTypePropertiesItem.Create(title = queryText))
val propertiesFormatItems = filterPropertiesFormats(queryText)
if (propertiesFormatItems.isNotEmpty()) {
add(UiEditTypePropertiesItem.Section.Types())
addAll(propertiesFormatItems)
}
if (sortedExistingProperties.isNotEmpty()) {
add(UiEditTypePropertiesItem.Section.Existing())
addAll(sortedExistingProperties)
}
} else {
val propertiesFormatItems = filterPropertiesFormats(queryText)
if (propertiesFormatItems.isNotEmpty()) {
add(UiEditTypePropertiesItem.Section.Types())
addAll(propertiesFormatItems)
}
if (sortedExistingProperties.isNotEmpty()) {
add(UiEditTypePropertiesItem.Section.Existing())
addAll(sortedExistingProperties)
}
}
}
_uiState.value = UiEditTypePropertiesState(items = items)
}
private fun filterPropertiesFormats(query: String): List<UiEditTypePropertiesItem.Format> {
return if (query.isNotEmpty()) {
UiEditTypePropertiesState.Companion.PROPERTIES_FORMATS.map { format ->
UiEditTypePropertiesItem.Format(
format = format,
prettyName = stringResourceProvider.getPropertiesFormatPrettyString(format)
)
}.filter { it.prettyName.contains(query, ignoreCase = true) }
} else {
UiEditTypePropertiesState.Companion.PROPERTIES_FORMATS.map { format ->
UiEditTypePropertiesItem.Format(
format = format,
prettyName = stringResourceProvider.getPropertiesFormatPrettyString(format)
)
}
}
}
private fun filterProperties(
allProperties: List<ObjectWrapper.Relation>,
typeKeys: List<Key>,
queryText: String
): List<ObjectWrapper.Relation> = allProperties.filter { field ->
field.key !in typeKeys &&
field.isValidToUse &&
(queryText.isBlank() || field.name?.contains(queryText, ignoreCase = true) == true)
}
fun hideError() {
_errorState.value = UiEditTypePropertiesErrorState.Hidden
}
//endregion
//region Ui Events
fun onEvent(event: UiEditTypePropertiesEvent) {
when (event) {
is UiEditTypePropertiesEvent.OnCreate -> {
val format = event.item.format
uiPropertyEditState.value = UiEditPropertyState.Visible.New(
name = event.item.title,
formatName = stringResourceProvider.getPropertiesFormatPrettyString(format),
formatIcon = format.simpleIcon(),
format = format,
)
}
is UiEditTypePropertiesEvent.OnExistingClicked -> {
viewModelScope.launch {
val objType = storeOfObjectTypes.get(vmParams.objectTypeId)
if (objType != null) {
proceedWithSetRecommendedProperties(
properties = objType.recommendedRelations + event.item.id
)
}
}
}
is UiEditTypePropertiesEvent.OnSearchQueryChanged -> {
input.value = event.query
}
is UiEditTypePropertiesEvent.OnTypeClicked -> {
val format = event.item.format
uiPropertyEditState.value = UiEditPropertyState.Visible.New(
name = "",
formatName = stringResourceProvider.getPropertiesFormatPrettyString(format),
formatIcon = format.simpleIcon(),
format = format,
)
}
UiEditTypePropertiesEvent.OnEditPropertyScreenDismissed -> {
uiPropertyEditState.value = UiEditPropertyState.Hidden
}
UiEditTypePropertiesEvent.OnCreateNewButtonClicked -> {
proceedWithCreatingRelation()
}
UiEditTypePropertiesEvent.OnSaveButtonClicked -> {
proceedWithUpdatingRelation()
}
is UiEditTypePropertiesEvent.OnPropertyNameUpdate -> {
val state = uiPropertyEditState.value as? UiEditPropertyState.Visible ?: return
uiPropertyEditState.value = when (state) {
is UiEditPropertyState.Visible.Edit -> state.copy(name = event.name)
is UiEditPropertyState.Visible.New -> state.copy(name = event.name)
is UiEditPropertyState.Visible.View -> state
}
}
}
}
//endregion
//region Use Cases
private fun proceedWithUpdatingRelation() {
val state = uiPropertyEditState.value as? UiEditPropertyState.Visible.Edit ?: return
viewModelScope.launch {
val params = SetObjectDetails.Params(
ctx = state.id,
details = mapOf(
Relations.NAME to state.name,
Relations.RELATION_FORMAT to state.format
)
)
setObjectDetails.async(params).fold(
onSuccess = {
Timber.d("Relation updated: $it")
},
onFailure = { error ->
Timber.e(error, "Failed to update relation")
_errorState.value = UiEditTypePropertiesErrorState.Show(
UiEditTypePropertiesErrorState.Reason.ErrorUpdatingProperty(error.message ?: "")
)
}
)
}
}
private fun proceedWithCreatingRelation() {
viewModelScope.launch {
val state = uiPropertyEditState.value as? UiEditPropertyState.Visible ?: return@launch
val (name, format) = when (state) {
is UiEditPropertyState.Visible.Edit -> state.name to state.format
is UiEditPropertyState.Visible.View -> state.name to state.format
is UiEditPropertyState.Visible.New -> state.name to state.format
}
val params = CreateRelation.Params(
space = vmParams.spaceId.id,
format = format,
name = name,
limitObjectTypes = emptyList(),
prefilled = emptyMap()
)
createRelation(params).process(
success = { relation ->
Timber.d("Relation created: $relation")
val objType = storeOfObjectTypes.get(vmParams.objectTypeId)
if (objType != null) {
proceedWithSetRecommendedProperties(
properties = objType.recommendedRelations + listOf(relation.id)
)
}
uiPropertyEditState.value = UiEditPropertyState.Hidden
_commands.emit(EditTypePropertiesCommand.Exit)
},
failure = { error ->
Timber.e(error, "Failed to create relation")
_errorState.value = UiEditTypePropertiesErrorState.Show(
UiEditTypePropertiesErrorState.Reason.ErrorCreatingProperty(error.message ?: "")
)
}
)
}
}
private fun proceedWithSetRecommendedProperties(properties: List<Id>) {
val params = SetObjectTypeRecommendedFields.Params(
objectTypeId = vmParams.objectTypeId,
fields = properties
)
viewModelScope.launch {
setObjectTypeRecommendedFields.async(params).fold(
onSuccess = {
Timber.d("Recommended fields set")
_commands.emit(EditTypePropertiesCommand.Exit)
},
onFailure = { error ->
Timber.e(error, "Error while setting recommended fields")
_errorState.value = UiEditTypePropertiesErrorState.Show(
UiEditTypePropertiesErrorState.Reason.ErrorAddingProperty(error.message ?: "")
)
}
)
}
}
//endregion
//region Commands
sealed class EditTypePropertiesCommand {
data object Exit : EditTypePropertiesCommand()
}
//endregion
companion object {
private const val DEBOUNCE_DURATION = 300L
}
}