From 3932c2fe87fe3310b606409cdb7a5a43355628bf Mon Sep 17 00:00:00 2001 From: Konstantin Ivanov <54908981+konstantiniiv@users.noreply.github.com> Date: Mon, 10 Mar 2025 13:46:53 +0100 Subject: [PATCH] DROID-3429 Primitives | Edit type properties, part 1 (#2138) --- app/build.gradle | 1 + .../anytype/di/common/ComponentManager.kt | 8 + .../di/feature/PrimitivesObjectTypeDI.kt | 25 +- .../anytype/di/feature/PropertiesDI.kt | 88 ++++ .../anytype/di/main/MainComponent.kt | 7 + .../primitives/EditTypePropertiesFragment.kt | 64 +++ .../ui/primitives/ObjectFieldsFragment.kt | 10 +- .../ui/primitives/ObjectTypeFieldsFragment.kt | 3 +- .../ui/primitives/ObjectTypeFragment.kt | 115 +----- app/src/main/res/navigation/graph.xml | 4 + .../anytype/core_ui/common/ComposeCommons.kt | 34 +- .../core_ui/extensions/ResExtension.kt | 3 +- .../anytype/core_ui/foundation/SearchBar.kt | 7 +- .../anytype/domain/primitives/FieldParser.kt | 36 +- .../resources/StringResourceProvider.kt | 2 + .../feature_allcontent/ui/AllContentScreen.kt | 30 +- feature-object-type/build.gradle | 1 + .../feature_object_type/fields/UiEvent.kt | 27 +- .../feature_object_type/fields/UiState.kt | 22 +- .../fields/ui/AddScreen.kt | 170 -------- .../fields/ui/EditScreen.kt | 386 ------------------ .../fields/ui/ListScreen.kt | 67 +-- .../anytype/feature_object_type/ui/Stubs.kt | 39 -- .../anytype/feature_object_type/ui/UiState.kt | 3 +- .../feature_object_type/ui/UiStateExt.kt | 24 +- .../viewmodel/ObjectTypeViewModel.kt | 258 ++---------- .../viewmodel/VmFactory.kt | 9 - .../feature_object_type/TestFieldsMappping.kt | 69 ++-- feature-properties/build.gradle | 58 +++ .../src/main/AndroidManifest.xml | 2 + .../EditSpacePropertiesViewModel.kt | 5 + .../EditTypePropertiesViewModel.kt | 22 + .../anytype/feature_properties/VMFactory.kt | 35 ++ .../add/UiEditTypePropertiesEvent.kt | 12 + .../add/UiEditTypePropertiesState.kt | 116 ++++++ .../feature_properties/add/ui/AddScreen.kt | 16 + .../edit/UiEditPropertyState.kt | 44 ++ .../edit/ui/PropertyScreen.kt | 20 + localization/src/main/res/values/strings.xml | 25 +- .../util/StringResourceProviderImpl.kt | 20 + settings.gradle | 1 + .../anytypeio/anytype/core_models/Object.kt | 4 +- 42 files changed, 750 insertions(+), 1142 deletions(-) create mode 100644 app/src/main/java/com/anytypeio/anytype/di/feature/PropertiesDI.kt create mode 100644 app/src/main/java/com/anytypeio/anytype/ui/primitives/EditTypePropertiesFragment.kt delete mode 100644 feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/fields/ui/AddScreen.kt delete mode 100644 feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/fields/ui/EditScreen.kt delete mode 100644 feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/ui/Stubs.kt create mode 100644 feature-properties/build.gradle create mode 100644 feature-properties/src/main/AndroidManifest.xml create mode 100644 feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/EditSpacePropertiesViewModel.kt create mode 100644 feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/EditTypePropertiesViewModel.kt create mode 100644 feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/VMFactory.kt create mode 100644 feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/add/UiEditTypePropertiesEvent.kt create mode 100644 feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/add/UiEditTypePropertiesState.kt create mode 100644 feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/add/ui/AddScreen.kt create mode 100644 feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/edit/UiEditPropertyState.kt create mode 100644 feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/edit/ui/PropertyScreen.kt diff --git a/app/build.gradle b/app/build.gradle index e88bf326d1..f8e6a44e9b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -177,6 +177,7 @@ dependencies { implementation project(':feature-all-content') implementation project(':feature-date') implementation project(':feature-object-type') + implementation project(':feature-properties') //Compile time dependencies ksp libs.daggerCompiler diff --git a/app/src/main/java/com/anytypeio/anytype/di/common/ComponentManager.kt b/app/src/main/java/com/anytypeio/anytype/di/common/ComponentManager.kt index 1f11c73d44..94095c1603 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/common/ComponentManager.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/common/ComponentManager.kt @@ -9,6 +9,7 @@ import com.anytypeio.anytype.di.feature.DaggerAllContentComponent import com.anytypeio.anytype.di.feature.DaggerAppPreferencesComponent import com.anytypeio.anytype.di.feature.DaggerBacklinkOrAddToObjectComponent import com.anytypeio.anytype.di.feature.DaggerDateObjectComponent +import com.anytypeio.anytype.di.feature.DaggerEditTypePropertiesComponent import com.anytypeio.anytype.di.feature.DaggerLinkToObjectComponent import com.anytypeio.anytype.di.feature.DaggerMoveToComponent import com.anytypeio.anytype.di.feature.DaggerObjectTypeComponent @@ -109,6 +110,7 @@ import com.anytypeio.anytype.feature_chats.presentation.ChatViewModel import com.anytypeio.anytype.feature_object_type.ui.ObjectTypeVmParams import com.anytypeio.anytype.feature_chats.presentation.SelectChatReactionViewModel import com.anytypeio.anytype.feature_date.viewmodel.DateObjectVmParams +import com.anytypeio.anytype.feature_properties.add.EditTypePropertiesVmParams import com.anytypeio.anytype.gallery_experience.viewmodel.GalleryInstallationViewModel import com.anytypeio.anytype.presentation.editor.EditorViewModel import com.anytypeio.anytype.presentation.history.VersionHistoryViewModel @@ -1141,6 +1143,12 @@ class ComponentManager( .create(params, findComponentDependencies()) } + val editTypePropertiesComponent = ComponentWithParams { params: EditTypePropertiesVmParams -> + DaggerEditTypePropertiesComponent + .factory() + .create(params, findComponentDependencies()) + } + class Component(private val builder: () -> T) { private var instance: T? = null diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/PrimitivesObjectTypeDI.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/PrimitivesObjectTypeDI.kt index 7fcb7d0078..0bcd0f3b0b 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/PrimitivesObjectTypeDI.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/PrimitivesObjectTypeDI.kt @@ -5,14 +5,12 @@ import com.anytypeio.anytype.analytics.base.Analytics import com.anytypeio.anytype.core_utils.di.scope.PerScreen import com.anytypeio.anytype.di.common.ComponentDependencies import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers -import com.anytypeio.anytype.domain.block.interactor.sets.CreateObjectSet import com.anytypeio.anytype.domain.block.repo.BlockRepository import com.anytypeio.anytype.domain.config.ConfigStorage import com.anytypeio.anytype.domain.config.UserSettingsRepository import com.anytypeio.anytype.domain.debugging.Logger import com.anytypeio.anytype.domain.event.interactor.EventChannel import com.anytypeio.anytype.domain.event.interactor.SpaceSyncAndP2PStatusProvider -import com.anytypeio.anytype.domain.launch.GetDefaultObjectType import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer import com.anytypeio.anytype.domain.misc.LocaleProvider import com.anytypeio.anytype.domain.misc.UrlBuilder @@ -22,15 +20,14 @@ import com.anytypeio.anytype.domain.`object`.SetObjectDetails import com.anytypeio.anytype.domain.objects.DeleteObjects import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes import com.anytypeio.anytype.domain.objects.StoreOfRelations -import com.anytypeio.anytype.domain.page.CreateObject import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.primitives.GetObjectTypeConflictingFields import com.anytypeio.anytype.domain.primitives.SetObjectTypeHeaderRecommendedFields import com.anytypeio.anytype.domain.primitives.SetObjectTypeRecommendedFields import com.anytypeio.anytype.domain.resources.StringResourceProvider import com.anytypeio.anytype.domain.search.SubscriptionEventChannel -import com.anytypeio.anytype.feature_object_type.viewmodel.ObjectTypeVMFactory import com.anytypeio.anytype.feature_object_type.ui.ObjectTypeVmParams +import com.anytypeio.anytype.feature_object_type.viewmodel.ObjectTypeVMFactory import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate import com.anytypeio.anytype.presentation.editor.cover.CoverImageHashProvider import com.anytypeio.anytype.providers.DefaultCoverImageHashProvider @@ -81,19 +78,6 @@ object ObjectTypeModule { logger = logger ) - @JvmStatic - @Provides - @PerScreen - fun createObject( - repo: BlockRepository, - getDefaultObjectType: GetDefaultObjectType, - dispatchers: AppCoroutineDispatchers, - ): CreateObject = CreateObject( - repo = repo, - getDefaultObjectType = getDefaultObjectType, - dispatchers = dispatchers - ) - @JvmStatic @Provides @PerScreen @@ -123,13 +107,6 @@ object ObjectTypeModule { dispatchers: AppCoroutineDispatchers ): GetObjectTypeConflictingFields = GetObjectTypeConflictingFields(repo, dispatchers) - @JvmStatic - @Provides - @PerScreen - fun provideCreateObjectSetUseCase( - repo: BlockRepository - ): CreateObjectSet = CreateObjectSet(repo = repo) - @JvmStatic @Provides @PerScreen diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/PropertiesDI.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/PropertiesDI.kt new file mode 100644 index 0000000000..c588f67aeb --- /dev/null +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/PropertiesDI.kt @@ -0,0 +1,88 @@ +package com.anytypeio.anytype.di.feature + +import androidx.lifecycle.ViewModelProvider +import com.anytypeio.anytype.core_utils.di.scope.PerModal +import com.anytypeio.anytype.di.common.ComponentDependencies +import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers +import com.anytypeio.anytype.domain.block.repo.BlockRepository +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.EditTypePropertiesViewModelFactory +import com.anytypeio.anytype.feature_properties.add.EditTypePropertiesVmParams +import com.anytypeio.anytype.ui.primitives.EditTypePropertiesFragment +import dagger.Binds +import dagger.BindsInstance +import dagger.Component +import dagger.Module +import dagger.Provides + +//region EDIT OBJECT TYPE PROPERTIES SCREEN +@PerModal +@Component( + modules = [ + EditTypePropertiesModule::class, + EditTypePropertiesModule.Declarations::class + ], + dependencies = [EditTypePropertiesDependencies::class] +) +interface EditTypePropertiesComponent { + @Component.Factory + interface Factory { + fun create( + @BindsInstance vmParams: EditTypePropertiesVmParams, + dependencies: EditTypePropertiesDependencies + ): EditTypePropertiesComponent + } + + fun inject(fragment: EditTypePropertiesFragment) +} + +@Module +object EditTypePropertiesModule { + + @JvmStatic + @Provides + @PerModal + fun provideTypeSetRecommendedFields( + repo: BlockRepository, + dispatchers: AppCoroutineDispatchers + ): SetObjectTypeRecommendedFields = SetObjectTypeRecommendedFields(repo, dispatchers) + + @JvmStatic + @Provides + @PerModal + fun createRelation( + repo: BlockRepository, + storeOfRelations: StoreOfRelations, + ) = CreateRelation(repo, storeOfRelations) + + @JvmStatic + @Provides + @PerModal + fun provideSetObjectDetails( + repo: BlockRepository, + dispatchers: AppCoroutineDispatchers + ): SetObjectDetails = SetObjectDetails(repo, dispatchers) + + @Module + interface Declarations { + @PerModal + @Binds + fun bindViewModelFactory( + factory: EditTypePropertiesViewModelFactory + ): ViewModelProvider.Factory + } +} + +interface EditTypePropertiesDependencies : ComponentDependencies { + fun provideStringResourceProvider(): StringResourceProvider + fun provideStoreOfRelations(): StoreOfRelations + fun provideStoreOfObjectTypes(): StoreOfObjectTypes + fun provideBlockRepository(): BlockRepository + fun provideAppCoroutineDispatchers(): AppCoroutineDispatchers +} +//endregion \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/di/main/MainComponent.kt b/app/src/main/java/com/anytypeio/anytype/di/main/MainComponent.kt index 342446f878..a49364bf25 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/main/MainComponent.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/main/MainComponent.kt @@ -3,6 +3,7 @@ package com.anytypeio.anytype.di.main import com.anytypeio.anytype.app.AndroidApplication import com.anytypeio.anytype.di.common.ComponentDependencies import com.anytypeio.anytype.di.common.ComponentDependenciesKey +import com.anytypeio.anytype.di.feature.EditTypePropertiesDependencies import com.anytypeio.anytype.di.feature.AllContentDependencies import com.anytypeio.anytype.di.feature.AppPreferencesDependencies import com.anytypeio.anytype.di.feature.BacklinkOrAddToObjectDependencies @@ -143,6 +144,7 @@ interface MainComponent : SelectChatReactionDependencies, ChatReactionDependencies, ParticipantComponentDependencies, + EditTypePropertiesDependencies, DebugDependencies { @@ -414,4 +416,9 @@ abstract class ComponentDependenciesModule { @IntoMap @ComponentDependenciesKey(DebugDependencies::class) abstract fun provideDebugDependencies(component: MainComponent): ComponentDependencies + + @Binds + @IntoMap + @ComponentDependenciesKey(EditTypePropertiesDependencies::class) + abstract fun provideEditTypePropertiesDependencies(component: MainComponent): ComponentDependencies } \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/ui/primitives/EditTypePropertiesFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/primitives/EditTypePropertiesFragment.kt new file mode 100644 index 0000000000..2eae4f407d --- /dev/null +++ b/app/src/main/java/com/anytypeio/anytype/ui/primitives/EditTypePropertiesFragment.kt @@ -0,0 +1,64 @@ +package com.anytypeio.anytype.ui.primitives + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.core.os.bundleOf +import androidx.fragment.app.viewModels +import androidx.fragment.compose.content +import com.anytypeio.anytype.core_models.Id +import com.anytypeio.anytype.core_models.primitives.SpaceId +import com.anytypeio.anytype.core_utils.ext.argString +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.add.EditTypePropertiesVmParams +import javax.inject.Inject + +class EditTypePropertiesFragment : BaseBottomSheetComposeFragment() { + + @Inject + lateinit var viewModelFactory: EditTypePropertiesViewModelFactory + private val vm by viewModels { viewModelFactory } + private val space get() = argString(ARG_SPACE) + private val typeId get() = argString(ARG_OBJECT_ID) + + @OptIn(ExperimentalMaterial3Api::class) + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ) = content { + MaterialTheme { + } + } + + override fun injectDependencies() { + val params = EditTypePropertiesVmParams( + objectTypeId = typeId, + spaceId = SpaceId(space) + + ) + componentManager().editTypePropertiesComponent.get(params).inject(this) + } + + override fun releaseDependencies() { + componentManager().editTypePropertiesComponent.release() + } + + companion object { + + fun args(objectId: Id, space: Id) = bundleOf( + ARG_OBJECT_ID to objectId, + ARG_SPACE to space + ) + + const val ARG_OBJECT_ID = "arg.primitives.edit.type.property.object.id" + const val ARG_SPACE = "arg.primitives.edit.type.property.space" + + const val DEFAULT_PADDING_TOP = 10 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/ui/primitives/ObjectFieldsFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/primitives/ObjectFieldsFragment.kt index b5857dcd1b..bb93600585 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/primitives/ObjectFieldsFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/primitives/ObjectFieldsFragment.kt @@ -302,11 +302,11 @@ class ObjectFieldsFragment : BaseBottomSheetComposeFragment(), ) } - const val ARG_CTX = "arg.document-relation.ctx" - const val ARG_SPACE = "arg.document-relation.space" - const val ARG_TARGET = "arg.document-relation.target" - const val ARG_LOCKED = "arg.document-relation.locked" - const val ARG_SET_FLOW = "arg.document-relation.set-flow" + const val ARG_CTX = "arg.primitives.properties.ctx" + const val ARG_SPACE = "arg.primitives.properties.space" + const val ARG_TARGET = "arg.primitives.properties.target" + const val ARG_LOCKED = "arg.primitives.properties.locked" + const val ARG_SET_FLOW = "arg.primitives.properties.set_flow" const val DEFAULT_PADDING_TOP = 10 } diff --git a/app/src/main/java/com/anytypeio/anytype/ui/primitives/ObjectTypeFieldsFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/primitives/ObjectTypeFieldsFragment.kt index ce5fd055fe..79ab82face 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/primitives/ObjectTypeFieldsFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/primitives/ObjectTypeFieldsFragment.kt @@ -44,9 +44,8 @@ class ObjectTypeFieldsFragment : BaseBottomSheetComposeFragment() { uiFieldsListState = vm.uiFieldsListState.collectAsStateWithLifecycle().value, uiTitleState = vm.uiTitleState.collectAsStateWithLifecycle().value, uiIconState = vm.uiIconState.collectAsStateWithLifecycle().value, - uiFieldEditOrNewState = vm.uiFieldEditOrNewState.collectAsStateWithLifecycle().value, + uiEditPropertyState = vm.uiEditPropertyScreen.collectAsStateWithLifecycle().value, uiFieldLocalInfoState = vm.uiFieldLocalInfoState.collectAsStateWithLifecycle().value, - uiAddFieldsScreenState = vm.uiAddFieldsState.collectAsStateWithLifecycle().value, fieldEvent = vm::onFieldEvent ) } diff --git a/app/src/main/java/com/anytypeio/anytype/ui/primitives/ObjectTypeFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/primitives/ObjectTypeFragment.kt index 00858a3fb7..8217d3322e 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/primitives/ObjectTypeFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/primitives/ObjectTypeFragment.kt @@ -7,7 +7,6 @@ import android.view.ViewGroup import androidx.compose.material.MaterialTheme import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.res.stringResource import androidx.core.os.bundleOf import androidx.fragment.app.setFragmentResultListener @@ -25,7 +24,6 @@ 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.subscribe -import com.anytypeio.anytype.core_utils.ext.toast import com.anytypeio.anytype.core_utils.ui.BaseComposeFragment import com.anytypeio.anytype.di.common.componentManager import com.anytypeio.anytype.feature_object_type.fields.ui.FieldsMainScreen @@ -34,14 +32,7 @@ import com.anytypeio.anytype.feature_object_type.ui.ObjectTypeVmParams import com.anytypeio.anytype.feature_object_type.ui.UiErrorState import com.anytypeio.anytype.feature_object_type.viewmodel.ObjectTypeVMFactory import com.anytypeio.anytype.feature_object_type.viewmodel.ObjectTypeViewModel -import com.anytypeio.anytype.presentation.home.OpenObjectNavigation -import com.anytypeio.anytype.ui.chats.ChatFragment -import com.anytypeio.anytype.ui.date.DateObjectFragment -import com.anytypeio.anytype.ui.editor.EditorFragment import com.anytypeio.anytype.ui.editor.EditorModalFragment -import com.anytypeio.anytype.ui.profile.ParticipantFragment -import com.anytypeio.anytype.ui.relations.RelationAddToObjectFragment -import com.anytypeio.anytype.ui.sets.ObjectSetFragment import com.anytypeio.anytype.ui.templates.EditorTemplateFragment.Companion.TYPE_TEMPLATE_EDIT import com.anytypeio.anytype.ui.types.picker.REQUEST_KEY_PICK_EMOJI import com.anytypeio.anytype.ui.types.picker.REQUEST_KEY_REMOVE_EMOJI @@ -55,7 +46,6 @@ import timber.log.Timber class ObjectTypeFragment : BaseComposeFragment() { @Inject lateinit var factory: ObjectTypeVMFactory - private val vm by viewModels { factory } private lateinit var navComposeController: NavHostController @@ -121,12 +111,18 @@ class ObjectTypeFragment : BaseComposeFragment() { navComposeController.navigate(OBJ_TYPE_FIELDS) } - is ObjectTypeCommand.OpenAddFieldScreen -> { - RelationAddToObjectFragment.new( - ctx = command.typeId, - space = command.space, - isSetOrCollection = command.isSet - ).showChildFragment() + is ObjectTypeCommand.OpenEditTypePropertiesScreen -> { + runCatching { + findNavController().navigate( + R.id.editTypePropertiesScreen, + EditTypePropertiesFragment.args( + objectId = command.typeId, + space = command.space + ) + ) + }.onFailure { + Timber.e(it, "Error while opening edit object type properties screen") + } } } } @@ -174,88 +170,12 @@ class ObjectTypeFragment : BaseComposeFragment() { uiFieldsListState = vm.uiFieldsListState.collectAsStateWithLifecycle().value, uiTitleState = vm.uiTitleState.collectAsStateWithLifecycle().value, uiIconState = vm.uiIconState.collectAsStateWithLifecycle().value, - uiFieldEditOrNewState = vm.uiFieldEditOrNewState.collectAsStateWithLifecycle().value, + uiEditPropertyState = vm.uiEditPropertyScreen.collectAsStateWithLifecycle().value, uiFieldLocalInfoState = vm.uiFieldLocalInfoState.collectAsStateWithLifecycle().value, - uiAddFieldsScreenState = vm.uiAddFieldsState.collectAsStateWithLifecycle().value, fieldEvent = vm::onFieldEvent ) } } - LaunchedEffect(Unit) { - vm.navigation.collect { nav -> - when (nav) { - is OpenObjectNavigation.OpenEditor -> { - findNavController().navigate( - R.id.objectNavigation, - EditorFragment.args( - ctx = nav.target, - space = nav.space - ) - ) - } - - is OpenObjectNavigation.OpenDataView -> { - findNavController().navigate( - R.id.dataViewNavigation, - ObjectSetFragment.args( - ctx = nav.target, - space = nav.space - ) - ) - } - - is OpenObjectNavigation.OpenParticipant -> { - runCatching { - findNavController().navigate( - R.id.participantScreen, - ParticipantFragment.args( - objectId = nav.target, - space = nav.space - ) - ) - }.onFailure { - Timber.w("Error while opening participant screen") - } - } - - is OpenObjectNavigation.OpenChat -> { - findNavController().navigate( - R.id.chatScreen, - ChatFragment.args( - ctx = nav.target, - space = nav.space - ) - ) - } - - OpenObjectNavigation.NonValidObject -> { - toast(getString(R.string.error_non_valid_object)) - } - - is OpenObjectNavigation.OpenDateObject -> { - runCatching { - findNavController().navigate( - R.id.dateObjectScreen, - DateObjectFragment.args( - objectId = nav.target, - space = nav.space - ) - ) - }.onFailure { - Timber.e(it, "Failed to navigate to date object screen") - } - } - - is OpenObjectNavigation.UnexpectedLayoutError -> { - toast(getString(R.string.error_unexpected_layout)) - } - - else -> { - // Do nothing. - } - } - } - } } @OptIn(ExperimentalMaterial3Api::class) @@ -282,15 +202,6 @@ class ObjectTypeFragment : BaseComposeFragment() { } } } - when (val state = errorStateScreen) { - UiErrorState.Hidden -> { - - } - - is UiErrorState.Show -> { - - } - } } override fun injectDependencies() { diff --git a/app/src/main/res/navigation/graph.xml b/app/src/main/res/navigation/graph.xml index 1528bb4e06..4d4c1b23dc 100644 --- a/app/src/main/res/navigation/graph.xml +++ b/app/src/main/res/navigation/graph.xml @@ -249,6 +249,10 @@ app:destination="@id/typeSetIconPickerScreen" /> + + diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/common/ComposeCommons.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/common/ComposeCommons.kt index 7d89d9ff62..5b222b9d78 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/common/ComposeCommons.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/common/ComposeCommons.kt @@ -5,10 +5,42 @@ import androidx.compose.foundation.layout.ime import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.ui.Modifier +import androidx.compose.ui.composed +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import com.anytypeio.anytype.core_ui.R @Composable fun keyboardAsState(): State { val isImeVisible = WindowInsets.ime.getBottom(LocalDensity.current) > 0 return rememberUpdatedState(isImeVisible) -} \ No newline at end of file +} + +@Composable +fun Modifier.bottomBorder( + strokeWidth: Dp = 0.5.dp, + color: Color = colorResource(R.color.shape_primary) +) = composed( + factory = { + val density = LocalDensity.current + val strokeWidthPx = density.run { strokeWidth.toPx() } + + Modifier.drawBehind { + val width = size.width + val height = size.height - strokeWidthPx / 2 + + drawLine( + color = color, + start = Offset(x = 0f, y = height), + end = Offset(x = width, y = height), + strokeWidth = strokeWidthPx + ) + } + } +) \ No newline at end of file diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/extensions/ResExtension.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/extensions/ResExtension.kt index daae3134f4..cc76110be5 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/extensions/ResExtension.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/extensions/ResExtension.kt @@ -278,8 +278,7 @@ fun ObjectLayoutView.getName(): Int? = when (this) { @StringRes fun RelationFormat.getPrettyName(): Int = when (this) { - RelationFormat.LONG_TEXT -> R.string.relation_format_long_text - RelationFormat.SHORT_TEXT -> R.string.relation_format_short_text + RelationFormat.LONG_TEXT, RelationFormat.SHORT_TEXT -> R.string.relation_format_long_text RelationFormat.NUMBER -> R.string.relation_format_number RelationFormat.STATUS -> R.string.relation_format_status RelationFormat.TAG -> R.string.relation_format_tag diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/foundation/SearchBar.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/foundation/SearchBar.kt index 1dd568cb69..69dda8e150 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/foundation/SearchBar.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/foundation/SearchBar.kt @@ -48,6 +48,7 @@ import com.anytypeio.anytype.core_ui.R @Composable fun DefaultSearchBar( modifier: Modifier = Modifier, + hint: Int = R.string.search, onQueryChanged: (String) -> Unit ) { @@ -80,7 +81,7 @@ fun DefaultSearchBar( modifier = Modifier .align(Alignment.CenterVertically) .padding( - start = 11.dp + start = 10.dp ) ) CompositionLocalProvider(value = LocalTextSelectionColors provides selectionColors) { @@ -117,9 +118,9 @@ fun DefaultSearchBar( interactionSource = interactionSource, placeholder = { Text( - text = stringResource(id = R.string.search), + text = stringResource(id = hint), style = BodyRegular.copy( - color = colorResource(id = R.color.text_tertiary) + color = colorResource(id = R.color.glyph_active) ) ) }, diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/primitives/FieldParser.kt b/domain/src/main/java/com/anytypeio/anytype/domain/primitives/FieldParser.kt index 81ec536e73..692b09da49 100644 --- a/domain/src/main/java/com/anytypeio/anytype/domain/primitives/FieldParser.kt +++ b/domain/src/main/java/com/anytypeio/anytype/domain/primitives/FieldParser.kt @@ -209,18 +209,30 @@ class FieldParserImpl @Inject constructor( storeOfRelations: StoreOfRelations ): ParsedFields { - val headerFields = storeOfRelations.getValidRelations( - ids = objType.recommendedFeaturedRelations - ) - val sidebarFields = storeOfRelations.getValidRelations( - ids = objType.recommendedRelations - ) - val hiddenFields = storeOfRelations.getValidRelations( - ids = objType.recommendedHiddenRelations - ) - val fileFields = storeOfRelations.getValidRelations( - ids = objType.recommendedFileRelations - ) + // Clean recommended IDs based on priority. + // recommendedFeaturedRelations always remain. + val featuredIds = objType.recommendedFeaturedRelations.distinct() + + // recommendedRelations: remove any ids that appear in featuredIds. + val relationsIds = objType.recommendedRelations + .filter { it !in featuredIds } + .distinct() + + // recommendedFileRelations: remove ids that are in featuredIds or relationsIds. + val fileIds = objType.recommendedFileRelations + .filter { it !in featuredIds && it !in relationsIds } + .distinct() + + // recommendedHiddenRelations: remove ids that are in featuredIds, relationsIds, or fileIds. + val hiddenIds = objType.recommendedHiddenRelations + .filter { it !in featuredIds && it !in relationsIds && it !in fileIds } + .distinct() + + // Fetch valid relations for each recommended group. + val headerFields = storeOfRelations.getValidRelations(ids = featuredIds) + val sidebarFields = storeOfRelations.getValidRelations(ids = relationsIds) + val fileFields = storeOfRelations.getValidRelations(ids = fileIds) + val hiddenFields = storeOfRelations.getValidRelations(ids = hiddenIds) // Combine IDs from all recommended relations. val existingIds = (headerFields + sidebarFields + hiddenFields + fileFields) diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/resources/StringResourceProvider.kt b/domain/src/main/java/com/anytypeio/anytype/domain/resources/StringResourceProvider.kt index c1c772f9db..678d627414 100644 --- a/domain/src/main/java/com/anytypeio/anytype/domain/resources/StringResourceProvider.kt +++ b/domain/src/main/java/com/anytypeio/anytype/domain/resources/StringResourceProvider.kt @@ -1,5 +1,6 @@ package com.anytypeio.anytype.domain.resources +import com.anytypeio.anytype.core_models.RelationFormat import com.anytypeio.anytype.core_models.RelativeDate interface StringResourceProvider { @@ -7,4 +8,5 @@ interface StringResourceProvider { fun getDeletedObjectTitle(): String fun getUntitledObjectTitle(): String fun getSetOfObjectsTitle(): String + fun getPropertiesFormatPrettyString(format: RelationFormat): String } \ 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 4ec69e7bb3..d10efcf5b8 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 @@ -52,10 +52,6 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment.Companion.CenterVertically import androidx.compose.ui.Modifier -import androidx.compose.ui.composed -import androidx.compose.ui.draw.drawBehind -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.colorResource @@ -63,11 +59,11 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource 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.common.bottomBorder import com.anytypeio.anytype.core_ui.extensions.simpleIcon import com.anytypeio.anytype.core_ui.extensions.swapList import com.anytypeio.anytype.core_ui.foundation.DefaultSearchBar @@ -89,7 +85,6 @@ 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.AllContentTab import com.anytypeio.anytype.feature_allcontent.models.UiContentItem @@ -824,29 +819,6 @@ object AllContentNavigation { const val ALL_CONTENT_MAIN = "all_content_main" } -@Composable -fun Modifier.bottomBorder( - strokeWidth: Dp = 0.5.dp, - color: Color = colorResource(R.color.shape_primary) -) = composed( - factory = { - val density = LocalDensity.current - val strokeWidthPx = density.run { strokeWidth.toPx() } - - Modifier.drawBehind { - val width = size.width - val height = size.height - strokeWidthPx / 2 - - drawLine( - color = color, - start = Offset(x = 0f, y = height), - end = Offset(x = width, y = height), - strokeWidth = strokeWidthPx - ) - } - } -) - @Composable fun SwipeToDismissListItems( item: UiContentItem.Item, diff --git a/feature-object-type/build.gradle b/feature-object-type/build.gradle index 452eeca249..46403a9eb1 100644 --- a/feature-object-type/build.gradle +++ b/feature-object-type/build.gradle @@ -28,6 +28,7 @@ dependencies { implementation project(':localization') implementation project(':presentation') implementation project(':library-emojifier') + implementation project(':feature-properties') compileOnly libs.javaxInject diff --git a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/fields/UiEvent.kt b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/fields/UiEvent.kt index 3c158075c9..c54d2a70b0 100644 --- a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/fields/UiEvent.kt +++ b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/fields/UiEvent.kt @@ -1,35 +1,13 @@ package com.anytypeio.anytype.feature_object_type.fields -import com.anytypeio.anytype.core_models.Id -import com.anytypeio.anytype.core_models.RelationFormat - sealed class FieldEvent { - data object OnFieldEditScreenDismiss : FieldEvent() - data object OnAddFieldScreenDismiss : FieldEvent() + data object OnEditPropertyScreenDismiss : FieldEvent() data class OnFieldItemClick(val item: UiFieldsListItem) : FieldEvent() - data class OnAddToHeaderFieldClick( - val item: UiAddFieldItem - ) : FieldEvent() - - data class OnAddToSidebarFieldClick( - val item: UiAddFieldItem - ) : FieldEvent() - - data class OnSaveButtonClicked( - val name: String, - val format: RelationFormat, - val limitObjectTypes: List - ) : FieldEvent() - - data object OnChangeTypeClick : FieldEvent() - data object OnLimitTypesClick : FieldEvent() - sealed class FieldItemMenu : FieldEvent() { data class OnDeleteFromTypeClick(val item: UiFieldsListItem) : FieldItemMenu() - data class OnRemoveLocalClick(val item: UiFieldsListItem) : FieldItemMenu() data class OnAddLocalToTypeClick(val item: UiFieldsListItem) : FieldItemMenu() } @@ -38,7 +16,6 @@ sealed class FieldEvent { } sealed class Section : FieldEvent() { - data object OnAddToHeaderIconClick : Section() data object OnAddToSidebarIconClick : Section() data object OnLocalInfoClick : Section() } @@ -47,6 +24,4 @@ sealed class FieldEvent { data class OnMove(val fromKey: String, val toKey: String) : DragEvent() data object OnDragEnd : DragEvent() } - - data class OnAddFieldSearchQueryChanged(val query: String) : FieldEvent() } diff --git a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/fields/UiState.kt b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/fields/UiState.kt index a2334232b0..2a5b050085 100644 --- a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/fields/UiState.kt +++ b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/fields/UiState.kt @@ -3,6 +3,7 @@ package com.anytypeio.anytype.feature_object_type.fields import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_models.Key import com.anytypeio.anytype.core_models.RelationFormat +import com.anytypeio.anytype.feature_properties.edit.UiPropertyLimitTypeItem import com.anytypeio.anytype.presentation.objects.ObjectIcon //region Top bar @@ -34,6 +35,7 @@ data class UiFieldsListState(val items: List) { } } +//todo rename to UiPropertiesListItem sealed class UiFieldsListItem { abstract val id: Id @@ -41,7 +43,7 @@ sealed class UiFieldsListItem { abstract val fieldKey: Key abstract val fieldTitle: String abstract val format: RelationFormat - abstract val limitObjectTypes: List + abstract val limitObjectTypes: List abstract val canDelete: Boolean abstract val isEditableField: Boolean @@ -50,7 +52,7 @@ sealed class UiFieldsListItem { override val fieldKey: Key, override val fieldTitle: String, override val format: RelationFormat, - override val limitObjectTypes: List = emptyList(), + override val limitObjectTypes: List = emptyList(), override val canDelete: Boolean, override val isEditableField: Boolean ) : Item() @@ -60,7 +62,7 @@ sealed class UiFieldsListItem { override val fieldKey: Key, override val fieldTitle: String, override val format: RelationFormat, - override val limitObjectTypes: List = emptyList(), + override val limitObjectTypes: List = emptyList(), override val canDelete: Boolean = false, override val isEditableField: Boolean ) : Item() @@ -186,18 +188,4 @@ sealed class UiLocalsFieldsInfoState { } //endregion -//region Add Fields screen -sealed class UiAddFieldsScreenState { - data object Hidden : UiAddFieldsScreenState() - data class Visible(val items: List, val addToHeader: Boolean) : UiAddFieldsScreenState() -} - -data class UiAddFieldItem( - val id: Id, - val fieldKey: Key, - val fieldTitle: String, - val format: RelationFormat -) -//endregion - diff --git a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/fields/ui/AddScreen.kt b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/fields/ui/AddScreen.kt deleted file mode 100644 index 580f01174c..0000000000 --- a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/fields/ui/AddScreen.kt +++ /dev/null @@ -1,170 +0,0 @@ -package com.anytypeio.anytype.feature_object_type.fields.ui - -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.safeDrawing -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.windowInsetsPadding -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.ModalBottomSheet -import androidx.compose.material3.Text -import androidx.compose.material3.rememberModalBottomSheetState -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.Companion.CenterVertically -import androidx.compose.ui.Modifier -import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.platform.rememberNestedScrollInteropConnection -import androidx.compose.ui.res.colorResource -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.dp -import com.anytypeio.anytype.core_models.RelationFormat -import com.anytypeio.anytype.core_ui.R -import com.anytypeio.anytype.core_ui.common.DefaultPreviews -import com.anytypeio.anytype.core_ui.extensions.simpleIcon -import com.anytypeio.anytype.core_ui.foundation.DefaultSearchBar -import com.anytypeio.anytype.core_ui.foundation.noRippleThrottledClickable -import com.anytypeio.anytype.core_ui.views.BodyRegular -import com.anytypeio.anytype.core_ui.widgets.dv.DragHandle -import com.anytypeio.anytype.feature_object_type.fields.FieldEvent -import com.anytypeio.anytype.feature_object_type.fields.UiAddFieldItem -import com.anytypeio.anytype.feature_object_type.fields.UiAddFieldsScreenState - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun AddFieldScreen( - state: UiAddFieldsScreenState, - fieldEvent: (FieldEvent) -> Unit -) { - val bottomSheetState = rememberModalBottomSheetState( - skipPartiallyExpanded = true - ) - - var isSearchEmpty by remember { mutableStateOf(true) } - - val lazyListState = rememberLazyListState() - - if (state is UiAddFieldsScreenState.Visible) { - ModalBottomSheet( - modifier = Modifier - .fillMaxSize() - .windowInsetsPadding(WindowInsets.safeDrawing) - .nestedScroll(rememberNestedScrollInteropConnection()), - dragHandle = { DragHandle() }, - scrimColor = colorResource(id = R.color.modal_screen_outside_background), - containerColor = colorResource(id = R.color.background_primary), - shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp), - sheetState = bottomSheetState, - onDismissRequest = { - fieldEvent(FieldEvent.OnAddFieldScreenDismiss) - }, - ) { - LazyColumn( - modifier = Modifier - .fillMaxSize(), - state = lazyListState - ) { - item { - DefaultSearchBar( - modifier = Modifier.fillMaxWidth() - .padding(horizontal = 20.dp) - ) { - isSearchEmpty = it.isEmpty() - fieldEvent(FieldEvent.OnAddFieldSearchQueryChanged(it)) - } - } - items( - count = state.items.size, - key = { index -> state.items[index].id }, - itemContent = { index -> - val item = state.items[index] - FieldItem( - modifier = commonItemModifier() - .noRippleThrottledClickable { - if (state.addToHeader) { - fieldEvent( - FieldEvent.OnAddToHeaderFieldClick( - item = item - ) - ) - } else { - fieldEvent( - FieldEvent.OnAddToSidebarFieldClick( - item = item - ) - ) - } - }, - item = item - ) - } - ) - } - } - } -} - -@OptIn(ExperimentalFoundationApi::class) -@Composable -private fun FieldItem( - modifier: Modifier, - item: UiAddFieldItem -) { - Row( - modifier = modifier, - verticalAlignment = CenterVertically - ) { - val formatIcon = item.format.simpleIcon() - if (formatIcon != null) { - Image( - modifier = Modifier - .padding(end = 10.dp) - .size(24.dp), - painter = painterResource(id = formatIcon), - contentDescription = "Relation format icon", - ) - } - Text( - modifier = Modifier - .fillMaxWidth() - .weight(1.0f) - .padding(end = 16.dp), - text = item.fieldTitle, - style = BodyRegular, - color = colorResource(id = R.color.text_primary), - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - } -} - -@DefaultPreviews -@Composable -fun PreviewAddFieldScreen() { - AddFieldScreen( - state = UiAddFieldsScreenState.Visible( - items = listOf( - UiAddFieldItem( - id = "1", - fieldKey = "key", - fieldTitle = "Title", - format = RelationFormat.LONG_TEXT - ) - ), - addToHeader = true - ), - fieldEvent = {} - ) -} \ No newline at end of file diff --git a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/fields/ui/EditScreen.kt b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/fields/ui/EditScreen.kt deleted file mode 100644 index 06564236e6..0000000000 --- a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/fields/ui/EditScreen.kt +++ /dev/null @@ -1,386 +0,0 @@ -package com.anytypeio.anytype.feature_object_type.fields.ui - -import androidx.compose.foundation.Image -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 -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.text.BasicTextField -import androidx.compose.foundation.text.KeyboardActions -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.ModalBottomSheet -import androidx.compose.material3.Text -import androidx.compose.material3.rememberModalBottomSheetState -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.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.focus.onFocusChanged -import androidx.compose.ui.graphics.SolidColor -import androidx.compose.ui.platform.LocalFocusManager -import androidx.compose.ui.platform.LocalSoftwareKeyboardController -import androidx.compose.ui.platform.SoftwareKeyboardController -import androidx.compose.ui.res.colorResource -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.input.ImeAction -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.dp -import com.anytypeio.anytype.core_models.RelationFormat -import com.anytypeio.anytype.core_ui.R -import com.anytypeio.anytype.core_ui.common.DefaultPreviews -import com.anytypeio.anytype.core_ui.extensions.getPrettyName -import com.anytypeio.anytype.core_ui.extensions.simpleIcon -import com.anytypeio.anytype.core_ui.foundation.Divider -import com.anytypeio.anytype.core_ui.foundation.noRippleThrottledClickable -import com.anytypeio.anytype.core_ui.views.BodyRegular -import com.anytypeio.anytype.core_ui.views.ButtonPrimary -import com.anytypeio.anytype.core_ui.views.ButtonSize -import com.anytypeio.anytype.core_ui.views.Caption1Regular -import com.anytypeio.anytype.core_ui.views.HeadlineHeading -import com.anytypeio.anytype.core_ui.views.Title2 -import com.anytypeio.anytype.core_ui.widgets.ListWidgetObjectIcon -import com.anytypeio.anytype.core_ui.widgets.dv.DragHandle -import com.anytypeio.anytype.feature_object_type.fields.FieldEvent -import com.anytypeio.anytype.feature_object_type.fields.UiFieldEditOrNewState -import com.anytypeio.anytype.feature_object_type.fields.UiFieldObjectItem -import com.anytypeio.anytype.feature_object_type.ui.createDummyFieldDraggableItem - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun EditFieldScreen( - modifier: Modifier, - uiFieldEditOrNewState: UiFieldEditOrNewState, - fieldEvent: (FieldEvent) -> Unit -) { - if (uiFieldEditOrNewState is UiFieldEditOrNewState.Visible) { - val bottomSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) - ModalBottomSheet( - modifier = modifier, - dragHandle = { DragHandle() }, - scrimColor = colorResource(id = R.color.modal_screen_outside_background), - containerColor = colorResource(id = R.color.background_primary), - shape = RoundedCornerShape(16.dp), - sheetState = bottomSheetState, - onDismissRequest = { fieldEvent(FieldEvent.OnFieldEditScreenDismiss) }, - ) { - EditFieldContent( - modifier = Modifier.fillMaxWidth(), - uiState = uiFieldEditOrNewState, - fieldEvent = fieldEvent - ) - } - } -} - -@Composable -private fun EditFieldContent( - modifier: Modifier, - uiState: UiFieldEditOrNewState.Visible, - fieldEvent: (FieldEvent) -> Unit -) { - - val field = uiState.item - var innerValue by remember(field.fieldTitle) { mutableStateOf(field.fieldTitle) } - val focusRequester = remember { FocusRequester() } - val keyboardController = LocalSoftwareKeyboardController.current - val isEditable = field.isEditableField - - val title = when (uiState) { - is UiFieldEditOrNewState.Visible.Edit -> stringResource(R.string.object_type_fields_edit_field) - is UiFieldEditOrNewState.Visible.New -> stringResource(R.string.object_type_fields_new_field) - is UiFieldEditOrNewState.Visible.ViewOnly -> stringResource(R.string.object_type_fields_preview_field) - } - - Column(modifier = modifier) { - // Header title - Box( - modifier = Modifier - .fillMaxWidth() - .height(44.dp), - contentAlignment = Alignment.Center - ) { - Text( - text = title, - style = Title2, - color = colorResource(id = R.color.text_primary) - ) - } - - // Name text field - NameTextField( - modifier = Modifier.fillMaxWidth(), - value = innerValue, - isEditable = isEditable, - focusRequester = focusRequester, - keyboardController = keyboardController, - onValueChange = { innerValue = it } - ) - - Spacer(modifier = Modifier.height(10.dp)) - Divider() - - // Field type section - FieldTypeSection( - format = field.format, - isEditable = isEditable, - onTypeClick = { fieldEvent(FieldEvent.OnChangeTypeClick) } - ) - Divider() - - // Limit object types (only for OBJECT format) - if (field.format == RelationFormat.OBJECT) { - LimitTypesSection( - objTypes = field.limitObjectTypes, - isEditable = isEditable, - onLimitTypesClick = { fieldEvent(FieldEvent.OnLimitTypesClick) } - ) - Divider() - } - - Spacer(modifier = Modifier.height(14.dp)) - - if (isEditable) { - ButtonPrimary( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 20.dp), - text = stringResource(R.string.object_type_fields_btn_save), - onClick = { - fieldEvent( - FieldEvent.OnSaveButtonClicked( - name = innerValue, - format = field.format, - limitObjectTypes = field.limitObjectTypes.map { it.id } - ) - ) - }, - size = ButtonSize.Large - ) - } - } -} - -@Composable -fun NameTextField( - modifier: Modifier, - value: String, - isEditable: Boolean, - focusRequester: FocusRequester, - keyboardController: SoftwareKeyboardController?, - onValueChange: (String) -> Unit -) { - val focusManager = LocalFocusManager.current - - Column(modifier = modifier) { - Text( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 20.dp), - text = stringResource(id = R.string.name), - style = Caption1Regular, - color = colorResource(id = R.color.text_secondary) - ) - - BasicTextField( - value = value, - onValueChange = onValueChange, - textStyle = HeadlineHeading.copy(color = colorResource(id = R.color.text_primary)), - singleLine = true, - enabled = isEditable, - cursorBrush = SolidColor(colorResource(id = R.color.text_primary)), - modifier = Modifier - .fillMaxWidth() - .wrapContentHeight() - .padding(start = 20.dp, top = 6.dp, end = 20.dp) - .focusRequester(focusRequester) - .onFocusChanged { /* You can handle focus changes here if needed */ }, - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), - keyboardActions = KeyboardActions { - keyboardController?.hide() - focusManager.clearFocus() - onValueChange(value) - }, - decorationBox = { innerTextField -> - if (value.isEmpty()) { - Text( - text = stringResource(id = R.string.untitled), - style = HeadlineHeading, - color = colorResource(id = R.color.text_tertiary), - modifier = Modifier.fillMaxWidth() - ) - } - innerTextField() - } - ) - } -} - -@Composable -fun FieldTypeSection( - format: RelationFormat, - isEditable: Boolean, - onTypeClick: () -> Unit -) { - val icon = format.simpleIcon() - SectionItem( - modifier = Modifier - .fillMaxWidth() - .height(52.dp), - text = stringResource(id = R.string.type) - ) - Divider() - Box( - modifier = Modifier - .fillMaxWidth() - .height(52.dp) - .padding(horizontal = 20.dp) - .noRippleThrottledClickable { if (isEditable) onTypeClick() } - ) { - if (icon != null) { - Image( - modifier = Modifier - .size(24.dp) - .align(Alignment.CenterStart), - painter = painterResource(id = icon), - contentDescription = "Relation format icon" - ) - } - Text( - modifier = Modifier - .fillMaxWidth() - .padding(start = 34.dp) - .align(Alignment.CenterStart), - text = stringResource(format.getPrettyName()), - style = BodyRegular, - color = colorResource(id = R.color.text_primary), - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - if (isEditable) { - Image( - modifier = Modifier.align(Alignment.CenterEnd), - painter = painterResource(id = R.drawable.ic_arrow_forward_24), - contentDescription = "Change field format icon" - ) - } - } -} - -@Composable -fun LimitTypesSection( - objTypes: List, - isEditable: Boolean, - onLimitTypesClick: () -> Unit -) { - val size = objTypes.size - SectionItem( - modifier = Modifier - .fillMaxWidth() - .height(52.dp), - text = stringResource(id = R.string.limit_object_types) - ) - Divider() - Box( - modifier = Modifier - .fillMaxWidth() - .height(52.dp) - .padding(horizontal = 20.dp) - .noRippleThrottledClickable { if (isEditable) onLimitTypesClick() } - ) { - if (objTypes.isNotEmpty()) { - Row(modifier = Modifier.align(Alignment.CenterStart)) { - ListWidgetObjectIcon( - modifier = Modifier.size(20.dp), - icon = objTypes.first().icon, - backgroundColor = R.color.transparent_black - ) - Text( - modifier = Modifier.padding(start = 6.dp), - text = objTypes.first().title.take(20), - style = BodyRegular, - color = colorResource(id = R.color.text_primary), - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - if (size > 1) { - Text( - text = " +${size - 1}", - style = BodyRegular, - color = colorResource(id = R.color.text_primary) - ) - } - } - } else { - Text( - modifier = Modifier - .fillMaxWidth() - .align(Alignment.CenterStart), - text = stringResource(R.string.none), - style = BodyRegular, - color = colorResource(id = R.color.text_primary), - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - } - if (isEditable) { - Image( - modifier = Modifier.align(Alignment.CenterEnd), - painter = painterResource(id = R.drawable.ic_arrow_forward_24), - contentDescription = "Change field object types icon" - ) - } - } -} - -@Composable -fun SectionItem(modifier: Modifier, text: String) { - Box(modifier = modifier) { - Text( - modifier = Modifier - .padding(start = 20.dp, bottom = 8.dp) - .align(Alignment.BottomStart), - text = text, - style = Caption1Regular, - color = colorResource(id = R.color.text_secondary) - ) - } -} - -@DefaultPreviews -@Composable -private fun MyPreview() { - EditFieldScreen( - modifier = Modifier.fillMaxWidth(), - uiFieldEditOrNewState = UiFieldEditOrNewState.Visible.Edit( - item = createDummyFieldDraggableItem() - ), - fieldEvent = {} - ) -} - -@DefaultPreviews -@Composable -private fun MyPreviewOnlyPreview() { - EditFieldScreen( - modifier = Modifier.fillMaxWidth(), - uiFieldEditOrNewState = UiFieldEditOrNewState.Visible.ViewOnly( - item = createDummyFieldDraggableItem( - isEditableField = false - ) - ), - fieldEvent = {} - ) -} \ No newline at end of file diff --git a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/fields/ui/ListScreen.kt b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/fields/ui/ListScreen.kt index 5367707496..bc88014478 100644 --- a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/fields/ui/ListScreen.kt +++ b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/fields/ui/ListScreen.kt @@ -35,24 +35,19 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment.Companion.CenterVertically import androidx.compose.ui.Modifier -import androidx.compose.ui.composed -import androidx.compose.ui.draw.drawBehind -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.rememberNestedScrollInteropConnection import androidx.compose.ui.res.colorResource 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 androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp import com.anytypeio.anytype.core_models.RelationFormat import com.anytypeio.anytype.core_ui.common.DefaultPreviews import com.anytypeio.anytype.core_ui.common.ReorderHapticFeedback import com.anytypeio.anytype.core_ui.common.ReorderHapticFeedbackType +import com.anytypeio.anytype.core_ui.common.bottomBorder import com.anytypeio.anytype.core_ui.common.rememberReorderHapticFeedback import com.anytypeio.anytype.core_ui.extensions.simpleIcon import com.anytypeio.anytype.core_ui.foundation.Dragger @@ -68,14 +63,14 @@ import com.anytypeio.anytype.feature_object_type.R import com.anytypeio.anytype.feature_object_type.fields.FieldEvent import com.anytypeio.anytype.feature_object_type.fields.FieldEvent.* import com.anytypeio.anytype.feature_object_type.fields.FieldEvent.FieldItemMenu.* -import com.anytypeio.anytype.feature_object_type.fields.UiAddFieldsScreenState -import com.anytypeio.anytype.feature_object_type.fields.UiFieldEditOrNewState import com.anytypeio.anytype.feature_object_type.fields.UiFieldsListItem import com.anytypeio.anytype.feature_object_type.fields.UiFieldsListItem.Section import com.anytypeio.anytype.feature_object_type.fields.UiFieldsListState import com.anytypeio.anytype.feature_object_type.fields.UiLocalsFieldsInfoState import com.anytypeio.anytype.feature_object_type.ui.UiIconState import com.anytypeio.anytype.feature_object_type.ui.UiTitleState +import com.anytypeio.anytype.feature_properties.edit.UiEditPropertyState +import com.anytypeio.anytype.feature_properties.edit.ui.PropertyScreen import com.anytypeio.anytype.presentation.objects.ObjectIcon import kotlinx.coroutines.delay import sh.calvin.reorderable.ReorderableItem @@ -88,9 +83,8 @@ fun FieldsMainScreen( uiFieldsListState: UiFieldsListState, uiTitleState: UiTitleState, uiIconState: UiIconState, - uiFieldEditOrNewState: UiFieldEditOrNewState, uiFieldLocalInfoState: UiLocalsFieldsInfoState, - uiAddFieldsScreenState: UiAddFieldsScreenState, + uiEditPropertyState: UiEditPropertyState, fieldEvent: (FieldEvent) -> Unit ) { @@ -198,10 +192,7 @@ fun FieldsMainScreen( item = item, reorderingState = reorderableLazyColumnState, fieldEvent = fieldEvent, - isReorderable = false, - onAddIconClick = { - fieldEvent(FieldEvent.Section.OnAddToHeaderIconClick) - } + isReorderable = false ) } is Section.Local, @@ -226,11 +217,16 @@ fun FieldsMainScreen( } ) - if (uiFieldEditOrNewState is UiFieldEditOrNewState.Visible) { - EditFieldScreen( + if (uiEditPropertyState is UiEditPropertyState.Visible) { + PropertyScreen( modifier = Modifier.fillMaxWidth(), - uiFieldEditOrNewState = uiFieldEditOrNewState, - fieldEvent = fieldEvent + uiState = uiEditPropertyState, + onDismissRequest = { fieldEvent(OnEditPropertyScreenDismiss) }, + onFormatClick = {}, + onLimitTypesClick = {}, + onSaveButtonClicked = {}, + onCreateNewButtonClicked = {}, + onPropertyNameUpdate = { } ) } @@ -241,13 +237,6 @@ fun FieldsMainScreen( fieldEvent = fieldEvent ) } - - if (uiAddFieldsScreenState is UiAddFieldsScreenState.Visible) { - AddFieldScreen( - state = uiAddFieldsScreenState, - fieldEvent = fieldEvent - ) - } } /** Returns a content type string based on the item type. **/ @@ -599,29 +588,6 @@ private fun LazyItemScope.FieldItemDraggable( } } -@Composable -fun Modifier.bottomBorder( - strokeWidth: Dp = 0.5.dp, - color: Color = colorResource(R.color.shape_primary) -) = composed( - factory = { - val density = LocalDensity.current - val strokeWidthPx = density.run { strokeWidth.toPx() } - - Modifier.drawBehind { - val width = size.width - val height = size.height - strokeWidthPx / 2 - - drawLine( - color = color, - start = Offset(x = 0f, y = height), - end = Offset(x = width, y = height), - strokeWidth = strokeWidthPx - ) - } - } -) - @Composable fun ItemDropDownMenu( item: UiFieldsListItem.Item, @@ -757,9 +723,8 @@ fun PreviewTypeFieldsMainScreen() { ) ), fieldEvent = {}, - uiFieldEditOrNewState = UiFieldEditOrNewState.Hidden, - uiFieldLocalInfoState = UiLocalsFieldsInfoState.Hidden, - uiAddFieldsScreenState = UiAddFieldsScreenState.Hidden + uiEditPropertyState = UiEditPropertyState.Hidden, + uiFieldLocalInfoState = UiLocalsFieldsInfoState.Hidden ) } diff --git a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/ui/Stubs.kt b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/ui/Stubs.kt deleted file mode 100644 index 3f93f6bf32..0000000000 --- a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/ui/Stubs.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.anytypeio.anytype.feature_object_type.ui - -import com.anytypeio.anytype.core_models.RelationFormat -import com.anytypeio.anytype.feature_object_type.fields.UiFieldObjectItem -import com.anytypeio.anytype.feature_object_type.fields.UiFieldsListItem -import com.anytypeio.anytype.presentation.objects.ObjectIcon - -fun createDummyFieldDraggableItem(isEditableField: Boolean = true): UiFieldsListItem.Item.Draggable { - return UiFieldsListItem.Item.Draggable( - id = "dummyId", - fieldKey = "dummyKey", - fieldTitle = "Field Title", - format = RelationFormat.OBJECT, - limitObjectTypes = listOf( - UiFieldObjectItem( - id = "dummyObjectId1", - key = "dummyKey1", - title = "Dummy Object Type 1", - icon = ObjectIcon.Empty.ObjectType, - ), - UiFieldObjectItem( - id = "dummyObjectId1", - key = "dummyKey1", - title = "Dummy Object Type 1", - icon = ObjectIcon.Empty.ObjectType, - - ), - UiFieldObjectItem( - id = "dummyObjectId1", - key = "dummyKey1", - title = "Dummy Object Type 1", - icon = ObjectIcon.Empty.ObjectType, - - ), - ), - isEditableField = isEditableField, - canDelete = true - ) -} \ No newline at end of file diff --git a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/ui/UiState.kt b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/ui/UiState.kt index aa54296eaf..48f23d32f4 100644 --- a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/ui/UiState.kt +++ b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/ui/UiState.kt @@ -33,8 +33,7 @@ sealed class ObjectTypeCommand { data object OpenFieldsScreen : ObjectTypeCommand() - data class OpenAddFieldScreen(val typeId: Id, val space: Id, val isSet: Boolean = false) : - ObjectTypeCommand() + data class OpenEditTypePropertiesScreen(val typeId: Id, val space: Id) : ObjectTypeCommand() } //region OBJECT TYPE HEADER (title + icon) diff --git a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/ui/UiStateExt.kt b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/ui/UiStateExt.kt index 9b9311523f..75081e6f17 100644 --- a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/ui/UiStateExt.kt +++ b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/ui/UiStateExt.kt @@ -13,11 +13,10 @@ import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes import com.anytypeio.anytype.domain.objects.StoreOfRelations import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.resources.StringResourceProvider -import com.anytypeio.anytype.feature_object_type.fields.UiAddFieldItem -import com.anytypeio.anytype.feature_object_type.fields.UiFieldObjectItem import com.anytypeio.anytype.feature_object_type.fields.UiFieldsListItem import com.anytypeio.anytype.feature_object_type.fields.UiFieldsListItem.Item import com.anytypeio.anytype.feature_object_type.fields.UiFieldsListItem.Section +import com.anytypeio.anytype.feature_properties.edit.UiPropertyLimitTypeItem import com.anytypeio.anytype.presentation.editor.cover.CoverImageHashProvider import com.anytypeio.anytype.presentation.mapper.objectIcon import com.anytypeio.anytype.presentation.relations.BasicObjectCoverWrapper @@ -56,7 +55,7 @@ fun ObjectWrapper.Basic.toTemplateView( * Extension function to safely get a name for the relation. * If the name is blank, returns a default untitled title. */ -private fun ObjectWrapper.Relation.getName(stringResourceProvider: StringResourceProvider): String = +fun ObjectWrapper.Relation.getName(stringResourceProvider: StringResourceProvider): String = if (name.isNullOrBlank()) { stringResourceProvider.getUntitledObjectTitle() } else { @@ -140,7 +139,7 @@ suspend fun buildUiFieldsList( } return buildList { - add(Section.Header(canAdd = true)) + add(Section.Header(canAdd = false)) addAll(headerItems) add(Section.SideBar(canAdd = true)) @@ -173,11 +172,11 @@ private suspend fun mapLimitObjectTypes( storeOfObjectTypes: StoreOfObjectTypes, fieldParser: FieldParser, urlBuilder: UrlBuilder -): List { +): List { return if (relation.format == RelationFormat.OBJECT && relation.relationFormatObjectTypes.isNotEmpty()) { relation.relationFormatObjectTypes.mapNotNull { key -> storeOfObjectTypes.getByKey(key)?.let { objType -> - UiFieldObjectItem( + UiPropertyLimitTypeItem( id = objType.id, key = objType.uniqueKey, title = fieldParser.getObjectName(objType), @@ -239,16 +238,3 @@ private suspend fun mapToUiFieldsLocalListItem( ) } -fun ObjectWrapper.Relation.mapToUiAddFieldListItem( - stringResourceProvider: StringResourceProvider -): UiAddFieldItem? { - val field = this - if (field.key == Relations.DESCRIPTION) return null - return UiAddFieldItem( - id = field.id, - fieldKey = field.key, - fieldTitle = field.getName(stringResourceProvider), - format = field.format - ) -} - diff --git a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/viewmodel/ObjectTypeViewModel.kt b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/viewmodel/ObjectTypeViewModel.kt index 9970b56ee0..a2fc5f0c86 100644 --- a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/viewmodel/ObjectTypeViewModel.kt +++ b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/viewmodel/ObjectTypeViewModel.kt @@ -4,14 +4,13 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.anytypeio.anytype.analytics.base.Analytics import com.anytypeio.anytype.core_models.Id -import com.anytypeio.anytype.core_models.Key import com.anytypeio.anytype.core_models.ObjectType import com.anytypeio.anytype.core_models.ObjectWrapper import com.anytypeio.anytype.core_models.Relations import com.anytypeio.anytype.core_models.permissions.ObjectPermissions import com.anytypeio.anytype.core_models.permissions.toObjectPermissionsForTypes +import com.anytypeio.anytype.core_ui.extensions.simpleIcon import com.anytypeio.anytype.domain.base.fold -import com.anytypeio.anytype.domain.block.interactor.sets.CreateObjectSet import com.anytypeio.anytype.domain.event.interactor.SpaceSyncAndP2PStatusProvider import com.anytypeio.anytype.domain.library.StoreSearchParams import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer @@ -22,17 +21,12 @@ import com.anytypeio.anytype.domain.`object`.SetObjectDetails import com.anytypeio.anytype.domain.objects.DeleteObjects import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes import com.anytypeio.anytype.domain.objects.StoreOfRelations -import com.anytypeio.anytype.domain.page.CreateObject import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.primitives.GetObjectTypeConflictingFields -import com.anytypeio.anytype.domain.primitives.SetObjectTypeHeaderRecommendedFields import com.anytypeio.anytype.domain.primitives.SetObjectTypeRecommendedFields import com.anytypeio.anytype.domain.resources.StringResourceProvider import com.anytypeio.anytype.domain.templates.CreateTemplate import com.anytypeio.anytype.feature_object_type.fields.FieldEvent -import com.anytypeio.anytype.feature_object_type.fields.UiAddFieldItem -import com.anytypeio.anytype.feature_object_type.fields.UiAddFieldsScreenState -import com.anytypeio.anytype.feature_object_type.fields.UiFieldEditOrNewState import com.anytypeio.anytype.feature_object_type.fields.UiFieldEditOrNewState.Visible.* import com.anytypeio.anytype.feature_object_type.fields.UiFieldsListItem import com.anytypeio.anytype.feature_object_type.fields.UiFieldsListState @@ -50,19 +44,15 @@ import com.anytypeio.anytype.feature_object_type.ui.UiLayoutButtonState import com.anytypeio.anytype.feature_object_type.ui.UiLayoutTypeState import com.anytypeio.anytype.feature_object_type.ui.UiLayoutTypeState.* import com.anytypeio.anytype.feature_object_type.ui.UiSyncStatusBadgeState -import com.anytypeio.anytype.feature_object_type.ui.UiTemplatesAddIconState import com.anytypeio.anytype.feature_object_type.ui.UiTemplatesButtonState import com.anytypeio.anytype.feature_object_type.ui.UiTemplatesModalListState import com.anytypeio.anytype.feature_object_type.ui.UiTitleState import com.anytypeio.anytype.feature_object_type.ui.buildUiFieldsList -import com.anytypeio.anytype.feature_object_type.ui.mapToUiAddFieldListItem import com.anytypeio.anytype.feature_object_type.ui.toTemplateView +import com.anytypeio.anytype.feature_properties.edit.UiEditPropertyState import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate import com.anytypeio.anytype.presentation.editor.cover.CoverImageHashProvider -import com.anytypeio.anytype.presentation.editor.cover.UnsplashViewModel.Companion.DEBOUNCE_DURATION import com.anytypeio.anytype.presentation.extension.sendAnalyticsScreenObjectType -import com.anytypeio.anytype.presentation.home.OpenObjectNavigation -import com.anytypeio.anytype.presentation.home.navigation import com.anytypeio.anytype.presentation.mapper.objectIcon import com.anytypeio.anytype.presentation.search.ObjectSearchConstants.defaultKeys import com.anytypeio.anytype.presentation.sync.SyncStatusWidgetState @@ -71,22 +61,14 @@ import com.anytypeio.anytype.presentation.sync.updateStatus import com.anytypeio.anytype.presentation.templates.TemplateView import kotlin.collections.map import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.FlowPreview -import kotlinx.coroutines.Job import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow 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.emptyFlow import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onCompletion -import kotlinx.coroutines.flow.take import kotlinx.coroutines.launch import timber.log.Timber @@ -105,18 +87,15 @@ class ObjectTypeViewModel( private val storeOfObjectTypes: StoreOfObjectTypes, private val storelessSubscriptionContainer: StorelessSubscriptionContainer, private val spaceSyncAndP2PStatusProvider: SpaceSyncAndP2PStatusProvider, - private val createObject: CreateObject, private val fieldParser: FieldParser, private val coverImageHashProvider: CoverImageHashProvider, private val deleteObjects: DeleteObjects, private val setObjectDetails: SetObjectDetails, - private val createObjectSet: CreateObjectSet, private val stringResourceProvider: StringResourceProvider, private val createTemplate: CreateTemplate, private val duplicateObjects: DuplicateObjects, private val getObjectTypeConflictingFields: GetObjectTypeConflictingFields, - private val objectTypeSetRecommendedFields: SetObjectTypeRecommendedFields, - private val objectTypeSetHeaderRecommendedFields: SetObjectTypeHeaderRecommendedFields + private val objectTypeSetRecommendedFields: SetObjectTypeRecommendedFields ) : ViewModel(), AnalyticSpaceHelperDelegate by analyticSpaceHelperDelegate { //region UI STATE @@ -148,13 +127,11 @@ class ObjectTypeViewModel( val uiFieldLocalInfoState = MutableStateFlow(UiLocalsFieldsInfoState.Hidden) - //fields + //properties list val uiFieldsListState = MutableStateFlow(UiFieldsListState.EMPTY) - val uiFieldEditOrNewState = - MutableStateFlow(UiFieldEditOrNewState.Hidden) - //add new field - val uiAddFieldsState = MutableStateFlow(UiAddFieldsScreenState.Hidden) + //edit property + val uiEditPropertyScreen = MutableStateFlow(UiEditPropertyState.Hidden) //error val errorState = MutableStateFlow(UiErrorState.Hidden) @@ -166,6 +143,8 @@ class ObjectTypeViewModel( private val _objectTypeConflictingFieldIds = MutableStateFlow>(emptyList()) //endregion + val commands = MutableSharedFlow() + //region INIT AND LIFE CYCLE init { Timber.d("init, vmParams: $vmParams") @@ -191,7 +170,6 @@ class ObjectTypeViewModel( //endregion //region DATA - private fun proceedWithObservingObjectType() { viewModelScope.launch { combine( @@ -387,6 +365,31 @@ class ObjectTypeViewModel( fun hideError() { errorState.value = UiErrorState.Hidden } + + fun setupUiEditPropertyScreen(item: UiFieldsListItem.Item) { + val permissions = _objectTypePermissionsState.value + if (permissions?.participantCanEdit == true && item.isEditableField) { + uiEditPropertyScreen.value = UiEditPropertyState.Visible.Edit( + id = item.id, + key = item.fieldKey, + name = item.fieldTitle, + formatName = stringResourceProvider.getPropertiesFormatPrettyString(item.format), + formatIcon = item.format.simpleIcon(), + format = item.format, + limitObjectTypes = item.limitObjectTypes + ) + } else { + uiEditPropertyScreen.value = UiEditPropertyState.Visible.View( + id = item.id, + key = item.fieldKey, + name = item.fieldTitle, + formatName = stringResourceProvider.getPropertiesFormatPrettyString(item.format), + formatIcon = item.format.simpleIcon(), + format = item.format, + limitObjectTypes = item.limitObjectTypes + ) + } + } //endregion //region Ui EVENTS - TYPES @@ -623,33 +626,17 @@ class ObjectTypeViewModel( fun onFieldEvent(event: FieldEvent) { Timber.d("onFieldEvent: $event") when (event) { - FieldEvent.OnChangeTypeClick -> TODO() - FieldEvent.OnFieldEditScreenDismiss -> { - uiFieldEditOrNewState.value = UiFieldEditOrNewState.Hidden + FieldEvent.OnEditPropertyScreenDismiss -> { + uiEditPropertyScreen.value = UiEditPropertyState.Hidden } is FieldEvent.OnFieldItemClick -> { when (event.item) { - is UiFieldsListItem.Item -> { - val permissions = _objectTypePermissionsState.value - if (permissions?.participantCanEdit == true && event.item.isEditableField) { - uiFieldEditOrNewState.value = Edit( - event.item - ) - } else { - uiFieldEditOrNewState.value = ViewOnly( - event.item - ) - } - } - + is UiFieldsListItem.Item -> setupUiEditPropertyScreen(item = event.item) else -> {} } } - FieldEvent.OnLimitTypesClick -> TODO() - is FieldEvent.OnSaveButtonClicked -> TODO() - is FieldEvent.FieldItemMenu -> proceedWithFieldItemMenuClick(event) FieldEvent.FieldLocalInfo.OnDismiss -> { uiFieldLocalInfoState.value = UiLocalsFieldsInfoState.Hidden @@ -659,12 +646,15 @@ class ObjectTypeViewModel( uiFieldLocalInfoState.value = UiLocalsFieldsInfoState.Visible } - FieldEvent.Section.OnAddToHeaderIconClick -> { - proceedWithAddFieldToHeaderScreen() - } - FieldEvent.Section.OnAddToSidebarIconClick -> { - proceedWithAddFieldToSidebarScreen() + viewModelScope.launch { + commands.emit( + ObjectTypeCommand.OpenEditTypePropertiesScreen( + typeId = vmParams.objectId, + space = vmParams.spaceId.id, + ) + ) + } } FieldEvent.DragEvent.OnDragEnd -> { @@ -706,24 +696,6 @@ class ObjectTypeViewModel( currentList.add(toIndex, item) uiFieldsListState.value = UiFieldsListState(items = currentList) } - - FieldEvent.OnAddFieldScreenDismiss -> { - hideAddNewFieldScreen() - } - - is FieldEvent.OnAddToHeaderFieldClick -> { - onAddToHeaderFieldClicked(item = event.item) - hideAddNewFieldScreen() - } - - is FieldEvent.OnAddToSidebarFieldClick -> { - onAddToSidebarFieldClicked(item = event.item) - hideAddNewFieldScreen() - } - - is FieldEvent.OnAddFieldSearchQueryChanged -> { - onQueryChanged(query = event.query) - } } } @@ -777,30 +749,6 @@ class ObjectTypeViewModel( proceedWithSetRecommendedFields(newRecommendedFields) } - is FieldEvent.FieldItemMenu.OnRemoveLocalClick -> TODO() - } - } - //endregion - - //region NAVIGATION - val commands = MutableSharedFlow() - val navigation = MutableSharedFlow() - - private fun proceedWithNavigation( - objectId: Id, - objectLayout: ObjectType.Layout? - ) { - Timber.d("proceedWithNavigation, objectId: $objectId, objectLayout: $objectLayout") - val destination = objectLayout?.navigation( - target = objectId, - space = vmParams.spaceId.id - ) - if (destination != null) { - viewModelScope.launch { - navigation.emit(destination) - } - } else { - Timber.w("No navigation destination found for object $objectId with layout $objectLayout") } } //endregion @@ -960,124 +908,6 @@ class ObjectTypeViewModel( ) } } - - private fun proceedWithSetHeaderRecommendedFields(fields: List) { - val params = SetObjectTypeHeaderRecommendedFields.Params( - objectTypeId = vmParams.objectId, - fields = fields - ) - viewModelScope.launch { - objectTypeSetHeaderRecommendedFields.async(params).fold( - onSuccess = { - Timber.d("Header recommended fields set") - }, - onFailure = { - Timber.e(it, "Error while setting header recommended fields") - } - ) - } - } - //endregion - - //region ADD NEW FIELD - private val input = MutableStateFlow("") - - @OptIn(FlowPreview::class) - private val query = input.take(1).onCompletion { - emitAll( - input.drop(1).debounce(DEBOUNCE_DURATION).distinctUntilChanged() - ) - } - - private var addFieldSearchJob: Job? = null - - /** - * Loads the available fields from type, applies filtering based on a search query, - * and then updates the UI state. - */ - private fun showAddFieldScreen(addToHeader: Boolean) { - // Collect field keys that are already present in the type fields list. - val typeFieldsKeys = - uiFieldsListState.value.items.mapNotNull { (it as? UiFieldsListItem.Item)?.fieldKey } - - addFieldSearchJob = viewModelScope.launch { - // Combine the search query flow with the list of all fields. - combine( - query, - storeOfRelations.trackChanges() - ) { queryText, _ -> - // Filter out fields by query and that already exist and are not valid. - filterFields( - allFields = storeOfRelations.getAll(), - typeKeys = typeFieldsKeys, - queryText = queryText - ) - }.collect { filteredFields -> - val items = filteredFields.mapNotNull { field -> - field.mapToUiAddFieldListItem(stringResourceProvider) - }.sortedBy { it.fieldTitle } - - uiAddFieldsState.value = UiAddFieldsScreenState.Visible( - items = items, - addToHeader = addToHeader - ) - } - } - } - - private fun filterFields( - allFields: List, - typeKeys: List, - queryText: String - ): List = allFields.filter { field -> - field.key !in typeKeys && - field.isValidToUse && - (queryText.isBlank() || field.name?.contains(queryText, ignoreCase = true) == true) - } - - private fun onQueryChanged(query: String) { - input.value = query - } - - fun hideAddNewFieldScreen() { - input.value = "" - addFieldSearchJob?.cancel() - addFieldSearchJob = null - uiAddFieldsState.value = UiAddFieldsScreenState.Hidden - } - - fun proceedWithAddFieldToHeaderScreen() { - showAddFieldScreen(addToHeader = true) - } - - fun proceedWithAddFieldToSidebarScreen() { - showAddFieldScreen(addToHeader = false) - } - - private fun updateFieldRecommendations( - currentFields: List?, - item: UiAddFieldItem, - updateAction: (List) -> Unit - ) { - val newFields = currentFields.orEmpty() + item.id - updateAction(newFields) - } - - private fun onAddToSidebarFieldClicked(item: UiAddFieldItem) { - updateFieldRecommendations( - currentFields = _objTypeState.value?.recommendedRelations, - item = item, - updateAction = ::proceedWithSetRecommendedFields - ) - } - - private fun onAddToHeaderFieldClicked(item: UiAddFieldItem) { - updateFieldRecommendations( - currentFields = _objTypeState.value?.recommendedFeaturedRelations, - item = item, - updateAction = ::proceedWithSetHeaderRecommendedFields - ) - } //endregion companion object { diff --git a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/viewmodel/VmFactory.kt b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/viewmodel/VmFactory.kt index ae2b6399a1..aa0e3e175c 100644 --- a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/viewmodel/VmFactory.kt +++ b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/viewmodel/VmFactory.kt @@ -3,7 +3,6 @@ package com.anytypeio.anytype.feature_object_type.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import com.anytypeio.anytype.analytics.base.Analytics -import com.anytypeio.anytype.domain.block.interactor.sets.CreateObjectSet import com.anytypeio.anytype.domain.event.interactor.SpaceSyncAndP2PStatusProvider import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer import com.anytypeio.anytype.domain.misc.UrlBuilder @@ -13,10 +12,8 @@ import com.anytypeio.anytype.domain.`object`.SetObjectDetails import com.anytypeio.anytype.domain.objects.DeleteObjects import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes import com.anytypeio.anytype.domain.objects.StoreOfRelations -import com.anytypeio.anytype.domain.page.CreateObject import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.primitives.GetObjectTypeConflictingFields -import com.anytypeio.anytype.domain.primitives.SetObjectTypeHeaderRecommendedFields import com.anytypeio.anytype.domain.primitives.SetObjectTypeRecommendedFields import com.anytypeio.anytype.domain.resources.StringResourceProvider import com.anytypeio.anytype.domain.templates.CreateTemplate @@ -35,18 +32,15 @@ class ObjectTypeVMFactory @Inject constructor( private val storeOfObjectTypes: StoreOfObjectTypes, private val storelessSubscriptionContainer: StorelessSubscriptionContainer, private val spaceSyncAndP2PStatusProvider: SpaceSyncAndP2PStatusProvider, - private val createObject: CreateObject, private val fieldParser: FieldParser, private val coverImageHashProvider: CoverImageHashProvider, private val deleteObjects: DeleteObjects, private val setObjectDetails: SetObjectDetails, - private val createObjectSet: CreateObjectSet, private val stringResourceProvider: StringResourceProvider, private val createTemplate: CreateTemplate, private val duplicateObjects: DuplicateObjects, private val getObjectTypeConflictingFields: GetObjectTypeConflictingFields, private val objectTypeSetRecommendedFields: SetObjectTypeRecommendedFields, - private val objectTypeSetHeaderRecommendedFields: SetObjectTypeHeaderRecommendedFields ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") @@ -61,17 +55,14 @@ class ObjectTypeVMFactory @Inject constructor( storeOfObjectTypes = storeOfObjectTypes, storelessSubscriptionContainer = storelessSubscriptionContainer, spaceSyncAndP2PStatusProvider = spaceSyncAndP2PStatusProvider, - createObject = createObject, fieldParser = fieldParser, coverImageHashProvider = coverImageHashProvider, deleteObjects = deleteObjects, setObjectDetails = setObjectDetails, - createObjectSet = createObjectSet, stringResourceProvider = stringResourceProvider, createTemplate = createTemplate, duplicateObjects = duplicateObjects, getObjectTypeConflictingFields = getObjectTypeConflictingFields, objectTypeSetRecommendedFields = objectTypeSetRecommendedFields, - objectTypeSetHeaderRecommendedFields = objectTypeSetHeaderRecommendedFields ) as T } \ No newline at end of file diff --git a/feature-object-type/src/test/java/com/anytypeio/anytype/feature_object_type/TestFieldsMappping.kt b/feature-object-type/src/test/java/com/anytypeio/anytype/feature_object_type/TestFieldsMappping.kt index bec57e3a55..ca5017ad37 100644 --- a/feature-object-type/src/test/java/com/anytypeio/anytype/feature_object_type/TestFieldsMappping.kt +++ b/feature-object-type/src/test/java/com/anytypeio/anytype/feature_object_type/TestFieldsMappping.kt @@ -193,29 +193,6 @@ class TestFieldsMappping { ) } - @Test - fun `should not filter sidebar fields by hidden`() = runTest { - - storeOfRelations.apply { - merge(allSpaceRelations) - } - - storeOfObjectTypes.apply { - merge(listOf(testObjectType, fieldAssigneeObjType2, fieldAssigneeObjType1)) - } - - val parsedFields = fieldParser.getObjectTypeParsedFields( - objectType = testObjectType, - storeOfRelations = storeOfRelations, - objectTypeConflictingFieldsIds = listOf() - ) - - assertEquals( - expected = listOf(field3, field4, field5), - actual = parsedFields.sidebar - ) - } - @Test fun `should map hidden fields`() = runTest { @@ -233,8 +210,54 @@ class TestFieldsMappping { objectTypeConflictingFieldsIds = listOf() ) + assertEquals( + expected = listOf(), + actual = parsedFields.hidden + ) + } + + @Test + fun `test filter duplicates`() = runTest { + + storeOfRelations.apply { + merge(allSpaceRelations) + } + + val testObjectType = StubObjectType( + recommendedFeaturedRelations = listOf(field1, field1, field2, field3).map { it.id }, + recommendedRelations = listOf(field1, field4, field4).map { it.id }, + recommendedFileRelations = listOf(field1, field2, field3, field4, field5, field5).map { it.id }, + recommendedHiddenRelations = listOf(field1, field2, field3, field4, field5, fieldCreatedDate, fieldCreatedDate).map { it.id }, + space = space + ) + + storeOfObjectTypes.apply { + merge(listOf(testObjectType, fieldAssigneeObjType2, fieldAssigneeObjType1)) + } + + val parsedFields = fieldParser.getObjectTypeParsedFields( + objectType = testObjectType, + storeOfRelations = storeOfRelations, + objectTypeConflictingFieldsIds = listOf() + ) + + assertEquals( + expected = listOf(field1, field2, field3), + actual = parsedFields.header + ) + + assertEquals( + expected = listOf(field4), + actual = parsedFields.sidebar + ) + assertEquals( expected = listOf(field5), + actual = parsedFields.file + ) + + assertEquals( + expected = listOf(fieldCreatedDate), actual = parsedFields.hidden ) } diff --git a/feature-properties/build.gradle b/feature-properties/build.gradle new file mode 100644 index 0000000000..de1e78ca05 --- /dev/null +++ b/feature-properties/build.gradle @@ -0,0 +1,58 @@ +plugins { + id "com.android.library" + id "kotlin-android" + alias(libs.plugins.compose.compiler) +} + +android { + + defaultConfig { + buildConfigField "boolean", "USE_NEW_WINDOW_INSET_API", "true" + buildConfigField "boolean", "USE_EDGE_TO_EDGE", "true" + } + + buildFeatures { + compose true + } + + namespace 'com.anytypeio.anytype.feature_properties' +} + +dependencies { + + implementation project(':domain') + implementation project(':core-ui') + implementation project(':analytics') + implementation project(':core-models') + implementation project(':core-utils') + implementation project(':localization') + implementation project(':presentation') + + compileOnly libs.javaxInject + + implementation libs.lifecycleViewModel + implementation libs.lifecycleRuntime + + implementation libs.appcompat + implementation libs.compose + implementation libs.fragmentCompose + implementation libs.composeFoundation + implementation libs.composeToolingPreview + implementation libs.composeMaterial3 + implementation libs.navigationCompose + + debugImplementation libs.composeTooling + + implementation libs.timber + + testImplementation project(':test:android-utils') + testImplementation project(':test:utils') + testImplementation project(":test:core-models-stub") + testImplementation libs.junit + testImplementation libs.kotlinTest + testImplementation libs.androidXTestCore + testImplementation libs.mockitoKotlin + testImplementation libs.coroutineTesting + testImplementation libs.timberJUnit + testImplementation libs.turbine +} \ No newline at end of file diff --git a/feature-properties/src/main/AndroidManifest.xml b/feature-properties/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..1d26c87a17 --- /dev/null +++ b/feature-properties/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/EditSpacePropertiesViewModel.kt b/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/EditSpacePropertiesViewModel.kt new file mode 100644 index 0000000000..714843fde2 --- /dev/null +++ b/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/EditSpacePropertiesViewModel.kt @@ -0,0 +1,5 @@ +package com.anytypeio.anytype.feature_properties + +class EditSpacePropertiesViewModel { + //todo: implement later +} \ No newline at end of file diff --git a/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/EditTypePropertiesViewModel.kt b/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/EditTypePropertiesViewModel.kt new file mode 100644 index 0000000000..0905994866 --- /dev/null +++ b/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/EditTypePropertiesViewModel.kt @@ -0,0 +1,22 @@ +package com.anytypeio.anytype.feature_properties + +import androidx.lifecycle.ViewModel +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.EditTypePropertiesVmParams + +class EditTypePropertiesViewModel( + private val vmParams: EditTypePropertiesVmParams, + private val storeOfRelations: StoreOfRelations, + private val storeOfObjectTypes: StoreOfObjectTypes, + private val stringResourceProvider: StringResourceProvider, + private val createRelation: CreateRelation, + private val setObjectDetails: SetObjectDetails, + private val setObjectTypeRecommendedFields: SetObjectTypeRecommendedFields +) : ViewModel() { +} + diff --git a/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/VMFactory.kt b/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/VMFactory.kt new file mode 100644 index 0000000000..65ece616be --- /dev/null +++ b/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/VMFactory.kt @@ -0,0 +1,35 @@ +package com.anytypeio.anytype.feature_properties + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +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.EditTypePropertiesVmParams +import javax.inject.Inject + +class EditTypePropertiesViewModelFactory @Inject constructor( + private val vmParams: EditTypePropertiesVmParams, + private val storeOfRelations: StoreOfRelations, + private val stringResourceProvider: StringResourceProvider, + private val createRelation: CreateRelation, + private val setObjectDetails: SetObjectDetails, + private val storeOfObjectTypes: StoreOfObjectTypes, + private val setObjectTypeRecommendedFields: SetObjectTypeRecommendedFields +) : ViewModelProvider.Factory { + + @Suppress("UNCHECKED_CAST") + override fun create(modelClass: Class): T = + EditTypePropertiesViewModel( + vmParams = vmParams, + storeOfRelations = storeOfRelations, + stringResourceProvider = stringResourceProvider, + createRelation = createRelation, + setObjectDetails = setObjectDetails, + storeOfObjectTypes = storeOfObjectTypes, + setObjectTypeRecommendedFields = setObjectTypeRecommendedFields + ) as T +} \ No newline at end of file diff --git a/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/add/UiEditTypePropertiesEvent.kt b/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/add/UiEditTypePropertiesEvent.kt new file mode 100644 index 0000000000..d5d326aff4 --- /dev/null +++ b/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/add/UiEditTypePropertiesEvent.kt @@ -0,0 +1,12 @@ +package com.anytypeio.anytype.feature_properties.add + +sealed class UiEditTypePropertiesEvent { + data class OnSearchQueryChanged(val query: String) : UiEditTypePropertiesEvent() + data class OnCreate(val item: UiEditTypePropertiesItem.Create) : UiEditTypePropertiesEvent() + data class OnTypeClicked(val item: UiEditTypePropertiesItem.Format) : UiEditTypePropertiesEvent() + data class OnExistingClicked(val item: UiEditTypePropertiesItem.Default) : UiEditTypePropertiesEvent() + data object OnCreateNewButtonClicked : UiEditTypePropertiesEvent() + data object OnSaveButtonClicked : UiEditTypePropertiesEvent() + data object OnEditPropertyScreenDismissed : UiEditTypePropertiesEvent() + data class OnPropertyNameUpdate(val name: String) : UiEditTypePropertiesEvent() +} \ No newline at end of file diff --git a/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/add/UiEditTypePropertiesState.kt b/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/add/UiEditTypePropertiesState.kt new file mode 100644 index 0000000000..736078b7ff --- /dev/null +++ b/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/add/UiEditTypePropertiesState.kt @@ -0,0 +1,116 @@ +package com.anytypeio.anytype.feature_properties.add + +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.RelationFormat +import com.anytypeio.anytype.core_models.Relations +import com.anytypeio.anytype.core_models.primitives.SpaceId +import com.anytypeio.anytype.domain.resources.StringResourceProvider + +data class UiEditTypePropertiesState( + val items: List +) { + companion object { + val EMPTY = UiEditTypePropertiesState(emptyList()) + + val DEFAULT_NEW_PROPERTY_FORMAT = RelationFormat.STATUS + + //This is a list of formats that are available for creating new properties + val PROPERTIES_FORMATS = listOf( + RelationFormat.LONG_TEXT, + RelationFormat.TAG, + RelationFormat.STATUS, + RelationFormat.NUMBER, + RelationFormat.DATE, + RelationFormat.FILE, + RelationFormat.OBJECT, + RelationFormat.CHECKBOX, + RelationFormat.URL, + RelationFormat.EMAIL, + RelationFormat.PHONE + ) + } +} + +sealed class UiEditTypePropertiesItem { + + abstract val id: Id + + sealed class Section : UiEditTypePropertiesItem() { + data class Types( + override val id: Id = "section_properties_types_id" + ) : Section() + + data class Existing( + override val id: Id = "section_properties_existing_id" + ) : Section() + } + + data class Format( + override val id: Id = "property_item_format_id_${format.ordinal}", + val format: RelationFormat, + val prettyName: String + ) : UiEditTypePropertiesItem() + + data class Create( + override val id: Id = ID, + val format: RelationFormat = UiEditTypePropertiesState.Companion.DEFAULT_NEW_PROPERTY_FORMAT, + val title: String + ) : UiEditTypePropertiesItem() { + companion object { + private const val ID = "create_new_property_id" + } + } + + data class Default( + override val id: Id, + val format: RelationFormat, + val propertyKey: Key, + val title: String, + ) : UiEditTypePropertiesItem() +} + +data class EditTypePropertiesVmParams( + val objectTypeId: Id, + val spaceId: SpaceId +) + +sealed class UiEditTypePropertiesErrorState { + data object Hidden : UiEditTypePropertiesErrorState() + data class Show(val reason: Reason) : UiEditTypePropertiesErrorState() + + sealed class Reason { + data class ErrorAddingProperty(val msg: String) : Reason() + data class ErrorCreatingProperty(val msg: String) : Reason() + data class ErrorUpdatingProperty(val msg: String) : Reason() + data class Other(val msg: String) : Reason() + } +} + + +//region MAPPING +fun ObjectWrapper.Relation.mapToStateItem( + stringResourceProvider: StringResourceProvider +): UiEditTypePropertiesItem.Default? { + val field = this + if (field.key == Relations.DESCRIPTION) return null + return UiEditTypePropertiesItem.Default( + id = field.id, + propertyKey = field.key, + title = field.getName(stringResourceProvider), + format = field.format + ) +} + +/** + * Extension function to safely get a name for the relation. + * If the name is blank, returns a default untitled title. + */ +fun ObjectWrapper.Relation.getName(stringResourceProvider: StringResourceProvider): String = + if (name.isNullOrBlank()) { + stringResourceProvider.getUntitledObjectTitle() + } else { + name!! + } +//endregion diff --git a/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/add/ui/AddScreen.kt b/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/add/ui/AddScreen.kt new file mode 100644 index 0000000000..72765d29f8 --- /dev/null +++ b/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/add/ui/AddScreen.kt @@ -0,0 +1,16 @@ +package com.anytypeio.anytype.feature_properties.add.ui + +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.runtime.Composable +import com.anytypeio.anytype.feature_properties.add.UiEditTypePropertiesEvent +import com.anytypeio.anytype.feature_properties.add.UiEditTypePropertiesState +import com.anytypeio.anytype.feature_properties.edit.UiEditPropertyState + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun AddFieldScreen( + state: UiEditTypePropertiesState, + uiStateEditProperty: UiEditPropertyState, + event: (UiEditTypePropertiesEvent) -> Unit +) { +} \ No newline at end of file diff --git a/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/edit/UiEditPropertyState.kt b/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/edit/UiEditPropertyState.kt new file mode 100644 index 0000000000..a88296c14f --- /dev/null +++ b/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/edit/UiEditPropertyState.kt @@ -0,0 +1,44 @@ +package com.anytypeio.anytype.feature_properties.edit + +import com.anytypeio.anytype.core_models.Id +import com.anytypeio.anytype.core_models.Key +import com.anytypeio.anytype.core_models.RelationFormat +import com.anytypeio.anytype.presentation.objects.ObjectIcon + +sealed class UiEditPropertyState { + data object Hidden : UiEditPropertyState() + sealed class Visible : UiEditPropertyState() { + + data class Edit( + val id: Id, + val key: Key, + val name: String, + val formatName: String, + val formatIcon: Int?, + val format: RelationFormat, + val limitObjectTypes: List = emptyList() + ) : Visible() + + data class New( + val name: String, + val formatName: String, + val formatIcon: Int?, + val format: RelationFormat, + val limitObjectTypes: List = emptyList() + ) : Visible() + + data class View( + val id: Id, + val key: Key, + val name: String, + val formatName: String, + val formatIcon: Int?, + val format: RelationFormat, + val limitObjectTypes: List = emptyList() + ) : Visible() + } +} + +data class UiPropertyLimitTypeItem( + val id: Id, val key: Key, val title: String, val icon: ObjectIcon +) \ No newline at end of file diff --git a/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/edit/ui/PropertyScreen.kt b/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/edit/ui/PropertyScreen.kt new file mode 100644 index 0000000000..daf8e09c86 --- /dev/null +++ b/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/edit/ui/PropertyScreen.kt @@ -0,0 +1,20 @@ +package com.anytypeio.anytype.feature_properties.edit.ui + +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.anytypeio.anytype.feature_properties.edit.UiEditPropertyState + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun PropertyScreen( + modifier: Modifier, + uiState: UiEditPropertyState.Visible, + onSaveButtonClicked: () -> Unit = {}, + onFormatClick: () -> Unit = {}, + onLimitTypesClick: () -> Unit = {}, + onCreateNewButtonClicked: () -> Unit = {}, + onDismissRequest: () -> Unit, + onPropertyNameUpdate: (String) -> Unit +) { +} \ No newline at end of file diff --git a/localization/src/main/res/values/strings.xml b/localization/src/main/res/values/strings.xml index ec29d9090d..cff0d03126 100644 --- a/localization/src/main/res/values/strings.xml +++ b/localization/src/main/res/values/strings.xml @@ -332,11 +332,11 @@ Select File & Media Checkbox - Url + URL Email - Phone + Phone number Emoji - Object + Object relation Relation Then Enter value @@ -344,7 +344,7 @@ Add a sort Apply Add a filter - Multiselect + Multi-Select Unknown number of objects Ascending Descending @@ -366,7 +366,7 @@ Unknown type Short text - Long text + Text Set new relation Deleted relation @@ -918,6 +918,7 @@ Something went wrong. Please try again. Type + Format Space info Creation date Created by @@ -1964,4 +1965,18 @@ Please provide specific details of your needs here. Edit picture Save + Add property + Properties types + Create property \'%1$s\'" + Properties types" + Existing properties" + Search or create new" + + New property + Limit objects to + + Error while creating new property + Error while updating property + Error while adding property to type + \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/util/StringResourceProviderImpl.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/util/StringResourceProviderImpl.kt index 4ee1337cbd..fe9794d551 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/util/StringResourceProviderImpl.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/util/StringResourceProviderImpl.kt @@ -1,6 +1,7 @@ package com.anytypeio.anytype.presentation.util import android.content.Context +import com.anytypeio.anytype.core_models.RelationFormat import com.anytypeio.anytype.core_models.RelativeDate import com.anytypeio.anytype.domain.resources.StringResourceProvider import com.anytypeio.anytype.presentation.R @@ -31,4 +32,23 @@ class StringResourceProviderImpl @Inject constructor(private val context: Contex override fun getSetOfObjectsTitle(): String { return context.getString(R.string.object_set_of_title) } + + override fun getPropertiesFormatPrettyString(format: RelationFormat): String { + return when (format) { + RelationFormat.LONG_TEXT, RelationFormat.SHORT_TEXT -> context.getString(R.string.relation_format_long_text) + RelationFormat.NUMBER -> context.getString(R.string.relation_format_number) + RelationFormat.STATUS -> context.getString(R.string.relation_format_status) + RelationFormat.TAG -> context.getString(R.string.relation_format_tag) + RelationFormat.DATE -> context.getString(R.string.relation_format_date) + RelationFormat.FILE -> context.getString(R.string.relation_format_file) + RelationFormat.CHECKBOX -> context.getString(R.string.relation_format_checkbox) + RelationFormat.URL -> context.getString(R.string.relation_format_url) + RelationFormat.EMAIL -> context.getString(R.string.relation_format_email) + RelationFormat.PHONE -> context.getString(R.string.relation_format_phone) + RelationFormat.EMOJI -> context.getString(R.string.relation_format_emoji) + RelationFormat.OBJECT -> context.getString(R.string.relation_format_object) + RelationFormat.RELATIONS -> context.getString(R.string.relation_format_relation) + RelationFormat.UNDEFINED -> context.getString(R.string.undefined) + } + } } \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 8e8d4d701c..fba4d1f600 100644 --- a/settings.gradle +++ b/settings.gradle @@ -67,3 +67,4 @@ include ':feature-chats' include ':feature-all-content' include ':feature-date' include ':feature-object-type' +include ':feature-properties' diff --git a/test/core-models-stub/src/main/java/com/anytypeio/anytype/core_models/Object.kt b/test/core-models-stub/src/main/java/com/anytypeio/anytype/core_models/Object.kt index dabc41910f..b0a8df6da7 100644 --- a/test/core-models-stub/src/main/java/com/anytypeio/anytype/core_models/Object.kt +++ b/test/core-models-stub/src/main/java/com/anytypeio/anytype/core_models/Object.kt @@ -96,7 +96,7 @@ fun StubObjectType( uniqueKey: String? = MockDataFactory.randomUuid(), name: String = MockDataFactory.randomString(), objectType: String = MockDataFactory.randomString(), - layout: Double = ObjectType.Layout.BASIC.code.toDouble(), + layout: Double = ObjectType.Layout.OBJECT_TYPE.code.toDouble(), smartBlockTypes: List = emptyList(), isDeleted: Boolean? = null, isArchived: Boolean? = null, @@ -109,6 +109,7 @@ fun StubObjectType( recommendedRelations: List = emptyList(), recommendedHiddenRelations: List = emptyList(), recommendedFeaturedRelations: List = emptyList(), + recommendedFileRelations: List = emptyList(), space: Id? = null ): ObjectWrapper.Type = ObjectWrapper.Type( map = mapOf( @@ -129,6 +130,7 @@ fun StubObjectType( Relations.RECOMMENDED_RELATIONS to recommendedRelations, Relations.RECOMMENDED_HIDDEN_RELATIONS to recommendedHiddenRelations, Relations.RECOMMENDED_FEATURED_RELATIONS to recommendedFeaturedRelations, + Relations.RECOMMENDED_FILE_RELATIONS to recommendedFileRelations, Relations.SPACE_ID to space ) ) \ No newline at end of file