From ca8721b725765c7b129c9e354416d0fc8e3b642a Mon Sep 17 00:00:00 2001 From: Konstantin Ivanov <54908981+konstantiniiv@users.noreply.github.com> Date: Wed, 4 Dec 2024 16:06:16 +0100 Subject: [PATCH] DROID-2793 Date as an Object | Epic (#1782) Co-authored-by: Evgenii Kozlov Co-authored-by: Evgenii Kozlov --- .../analytics/base/EventsDictionary.kt | 1 + app/build.gradle | 1 + .../features/editor/base/EditorTestSetup.kt | 20 +- .../relations/ObjectRelationListTest.kt | 7 +- .../features/sets/dv/TestObjectSetSetup.kt | 20 +- .../sets/filter/CreateSelectedFilterTest.kt | 7 +- .../features/sets/filter/FilterListTest.kt | 7 +- .../sets/filter/ModifyInputValueFilterTest.kt | 7 +- .../sets/filter/ModifyStatusFilterTest.kt | 7 +- .../sets/filter/ModifyTagFilterTest.kt | 7 +- .../anytype/di/common/ComponentManager.kt | 9 +- .../anytype/di/feature/AllContentDI.kt | 13 +- .../di/feature/BacklinkOrAddToObjectDI.kt | 4 + .../anytype/di/feature/DateObjectDI.kt | 173 ++++ .../anytypeio/anytype/di/feature/EditorDI.kt | 27 +- .../anytype/di/feature/LinkToObjectDI.kt | 10 +- .../anytype/di/feature/LinkToObjectOrWebDi.kt | 7 +- .../anytypeio/anytype/di/feature/MoveToDi.kt | 8 +- .../anytype/di/feature/ObjectMenuDI.kt | 14 +- .../di/feature/ObjectRelationListDI.kt | 7 +- .../anytype/di/feature/ObjectSetDI.kt | 7 +- .../anytype/di/feature/ObjectValueDI.kt | 19 +- .../anytype/di/feature/ViewerFilter.kt | 9 +- .../feature/search/GlobalSearchComponent.kt | 2 + .../anytype/di/feature/sets/CreateFilter.kt | 7 +- .../anytype/di/feature/sets/ModifyFilter.kt | 7 +- .../di/feature/templates/TemplateBlankDI.kt | 4 + .../feature/widgets/SelectWidgetSourceDI.kt | 8 +- .../anytype/di/main/MainComponent.kt | 9 +- .../anytypeio/anytype/di/main/UtilModule.kt | 31 +- .../anytypeio/anytype/navigation/Navigator.kt | 13 + .../ui/allcontent/AllContentFragment.kt | 10 + .../anytype/ui/base/NavigationRouter.kt | 4 + .../anytype/ui/date/DateObjectFragment.kt | 263 ++++++ .../anytype/ui/editor/EditorFragment.kt | 11 + .../anytype/ui/home/HomeScreenFragment.kt | 10 + .../anytypeio/anytype/ui/main/MainActivity.kt | 19 +- .../ui/settings/RemoteFilesManageFragment.kt | 3 + .../anytype/ui/sharing/SharingFragment.kt | 2 - .../anytype/ui/vault/VaultFragment.kt | 12 +- .../ui/widgets/collection/CollectionDI.kt | 8 +- .../widgets/collection/CollectionFragment.kt | 10 + .../ui/widgets/types/DataViewWidget.kt | 43 +- .../anytype/ui/widgets/types/LinkWidget.kt | 10 +- .../anytype/ui/widgets/types/ListWidget.kt | 4 +- .../anytype/ui/widgets/types/TreeWidget.kt | 13 +- app/src/main/res/layout/fragment_editor.xml | 5 + app/src/main/res/navigation/graph.xml | 10 + .../anytypeio/anytype/core_models/Block.kt | 6 - .../anytypeio/anytype/core_models/Command.kt | 4 + .../anytype/core_models/Constants.kt | 19 +- .../anytype/core_models/RelationLink.kt | 7 + .../anytype/core_models/Relations.kt | 2 + .../anytype/core_models/RelativeDate.kt | 8 + .../anytype/core_models}/SupportedLayouts.kt | 16 +- .../exceptions/SpaceLimitReachedException.kt | 3 - .../anytype/core_models/history/Version.kt | 4 +- .../core_models/primitives/FieldValues.kt | 23 + .../core_models/primitives/Primitives.kt | 2 +- .../anytype/core_ui/common/ComposePreview.kt | 8 +- .../anytype/core_ui/common/Markup.kt | 23 + .../anytype/core_ui/common/ShimmerEffect.kt | 66 ++ .../core_ui/extensions/ResExtension.kt | 18 + .../dataview/holders/DVGridCellDateHolder.kt | 9 +- .../dataview/modals/FilterDateViewHolder.kt | 7 +- .../core_ui/features/editor/BlockAdapter.kt | 10 + .../features/editor/BlockViewDiffUtil.kt | 7 + .../features/editor/EditorDatePicker.kt | 62 ++ .../relations/ListRelationViewHolder.kt | 24 + .../relations/RelationBlockViewHolder.kt | 37 + .../editor/slash/SlashRelationsAdapter.kt | 20 + .../history/VersionHistoryMainScreen.kt | 8 +- .../features/navigation/PageLinksAdapter.kt | 26 +- .../relations/DocumentRelationAdapter.kt | 24 + .../features/sets/CreateFilterAdapter.kt | 6 +- .../core_ui/relations/DatePickerContent.kt | 23 +- .../anytype/core_ui/syncstatus/StatusBadge.kt | 167 ++++ .../core_ui/widgets/ObjectIconCompose.kt | 1 + .../core_ui/widgets/ObjectIconWidget.kt | 22 +- .../widgets/RelationValueListWidget.kt | 22 + .../widgets/dv/GalleryViewContentWidget.kt | 8 +- .../dv/ListViewItemRelationGroupWidget.kt | 8 +- .../core_ui/widgets/toolbar/MentionToolbar.kt | 16 +- .../widgets/toolbar/adapter/MentionAdapter.kt | 88 +- .../src/main/res/drawable/ic_burger_24.xml | 10 + .../src/main/res/drawable/ic_calendar_24.xml | 31 + .../src/main/res/drawable/ic_mention_24.xml | 9 + .../src/main/res/drawable/ic_obj_date_20.xml | 14 + .../src/main/res/drawable/ic_obj_date_24.xml | 14 + .../src/main/res/layout/item_list_object.xml | 62 +- .../res/layout/item_list_object_small.xml | 46 +- .../res/layout/item_mention_section_date.xml | 18 + .../layout/item_mention_section_objects.xml | 18 + .../item_relation_list_relation_date.xml | 56 ++ .../res/layout/item_search_new_object.xml | 5 +- .../main/res/layout/widget_mention_menu.xml | 4 +- core-ui/src/main/res/values/colors.xml | 4 +- core-ui/src/main/res/values/dimens.xml | 11 +- core-ui/src/main/res/values/styles.xml | 4 +- .../features/dv/CellViewDiffUtilTest.kt | 6 +- .../core_utils/ext/AndroidExtension.kt | 35 - .../anytype/core_utils/ext/DateUtils.kt | 62 -- .../auth/repo/block/BlockDataRepository.kt | 9 + .../data/auth/repo/block/BlockRemote.kt | 5 + device/build.gradle | 2 + .../providers/AppDefaultDateFormatProvider.kt | 3 +- .../device/providers/DateProviderImpl.kt | 102 ++- .../anytypeio/anytype/DateProviderImplTest.kt | 54 +- .../domain/block/repo/BlockRepository.kt | 5 + .../anytype/domain/misc/DateProvider.kt | 13 +- .../objects/GetDateObjectByTimestamp.kt | 26 + .../anytype/domain/primitives/FieldParser.kt | 123 +++ .../domain/primitives/FieldValuesParser.kt | 64 ++ .../relations/GetObjectRelationListById.kt | 27 + .../domain/primitives/FieldDateTest.kt | 105 +++ .../models/AllContentModels.kt | 17 +- .../presentation/AllContentViewModel.kt | 33 +- .../AllContentViewModelFactory.kt | 7 +- feature-date/build.gradle | 60 ++ feature-date/src/main/AndroidManifest.xml | 2 + .../mapping/DateObjectModelsExt.kt | 81 ++ .../anytype/feature_date/ui/CalendarScreen.kt | 49 + .../anytype/feature_date/ui/FieldsScreen.kt | 199 ++++ .../feature_date/ui/FieldsSheetScreen.kt | 320 +++++++ .../anytype/feature_date/ui/HeaderScreen.kt | 143 +++ .../anytype/feature_date/ui/MainScreen.kt | 177 ++++ .../anytype/feature_date/ui/ObjectsScreen.kt | 258 ++++++ .../anytype/feature_date/ui/OtherScreen.kt | 40 + .../feature_date/ui/TopToolbarScreen.kt | 106 +++ .../feature_date/ui/models/DateEvent.kt | 54 ++ .../feature_date/ui/models/PreviewStubs.kt | 86 ++ .../feature_date/viewmodel/DateModels.kt | 179 ++++ .../viewmodel/DateObjectCommand.kt | 19 + .../viewmodel/DateObjectVMFactory.kt | 58 ++ .../viewmodel/DateObjectViewModel.kt | 863 ++++++++++++++++++ .../feature_date/viewmodel/SearchParams.kt | 134 +++ libs/build.gradle | 2 +- localization/src/main/res/values/strings.xml | 9 + .../middleware/block/BlockMiddleware.kt | 9 + .../middleware/interactor/Middleware.kt | 25 + .../middleware/mappers/ToCoreModelMappers.kt | 15 +- .../middleware/service/MiddlewareService.kt | 6 + .../MiddlewareServiceImplementation.kt | 26 + .../editor/ControlPanelMachine.kt | 3 +- .../presentation/editor/EditorViewModel.kt | 201 +++- .../editor/EditorViewModelFactory.kt | 13 +- .../presentation/editor/editor/Markup.kt | 6 + .../editor/control/ControlPanelState.kt | 3 +- .../editor/editor/ext/ContentTextExt.kt | 18 +- .../editor/editor/mention/MentionExt.kt | 7 + .../editor/editor/model/BlockView.kt | 2 + .../editor/editor/model/types/Types.kt | 1 + .../editor/model/EditorDatePicker.kt | 13 + .../editor/render/DefaultBlockViewRenderer.kt | 82 +- .../presentation/extension/FilterExtension.kt | 10 +- .../presentation/extension/MapExtension.kt | 50 - .../presentation/extension/MarkupExtension.kt | 1 + .../history/VersionHistoryViewModel.kt | 6 +- .../presentation/home/HomeScreenViewModel.kt | 39 +- .../linking/BackLinkOrAddToObjectViewModel.kt | 8 +- .../linking/LinkToObjectOrWebViewModel.kt | 12 +- .../linking/LinkToObjectViewModel.kt | 9 +- .../linking/LinkToObjectViewModelFactory.kt | 19 +- .../presentation/mapper/MapperExtension.kt | 5 +- .../presentation/mapper/ObjectIconMapper.kt | 7 +- .../presentation/moving/MoveToViewModel.kt | 9 +- .../moving/MoveToViewModelFactory.kt | 7 +- .../presentation/navigation/AppNavigation.kt | 9 + .../presentation/navigation/ObjectView.kt | 4 + .../presentation/objects/ObjectIcon.kt | 1 + .../objects/ObjectTypeChangeViewModel.kt | 1 + .../objects/ObjectTypeExtensions.kt | 6 +- .../objects/ObjectWrapperExtensions.kt | 52 +- .../objects/ObjectWrapperMapper.kt | 52 +- .../objects/SelectObjectTypeViewModel.kt | 2 +- .../menu/ObjectMenuOptionsProviderImpl.kt | 2 +- .../objects/menu/ObjectMenuViewModel.kt | 17 +- .../objects/menu/ObjectMenuViewModelBase.kt | 7 +- .../objects/menu/ObjectSetMenuViewModel.kt | 13 +- .../ObjectRelationListViewModelFactory.kt | 7 +- .../relations/ObjectRelationView.kt | 13 + .../relations/ObjectSetRenderMapper.kt | 57 +- .../relations/RelationExtensions.kt | 38 +- .../relations/RelationListViewModel.kt | 13 +- .../relations/RelationObjectExtensions.kt | 41 +- .../model/DefaultObjectRelationValueView.kt | 4 +- .../value/object/ObjectValueViewModel.kt | 14 +- .../object/ObjectValueViewModelFactory.kt | 7 +- .../search/GlobalSearchViewModel.kt | 27 +- .../search/ObjectSearchConstants.kt | 14 +- .../search/ObjectSearchViewModel.kt | 8 +- .../search/ObjectSearchViewModelFactory.kt | 31 - .../presentation/sets/GalleryViewMapper.kt | 27 +- .../presentation/sets/ListViewMapper.kt | 23 +- .../presentation/sets/ObjectSetExtension.kt | 24 +- .../presentation/sets/ObjectSetViewModel.kt | 18 +- .../sets/ObjectSetViewModelFactory.kt | 7 +- .../sets/RelationDateValueViewModel.kt | 4 +- .../presentation/sets/SetsExtension.kt | 36 +- .../sets/filter/CreateFilterView.kt | 4 +- .../sets/filter/FilterViewModel.kt | 45 +- .../sets/filter/ViewerFilterViewModel.kt | 13 +- .../presentation/sets/model/CellView.kt | 4 + .../presentation/sets/model/FilterView.kt | 4 +- .../presentation/splash/SplashViewModel.kt | 2 +- .../presentation/vault/VaultViewModel.kt | 9 + .../widgets/DataViewListWidgetContainer.kt | 65 +- .../widgets/LinkWidgetContainer.kt | 8 +- .../widgets/ListWidgetContainer.kt | 3 + .../widgets/SelectWidgetSourceViewModel.kt | 17 +- .../widgets/SelectWidgetTypeViewModel.kt | 158 ++-- .../widgets/TreeWidgetContainer.kt | 22 +- .../anytype/presentation/widgets/Widget.kt | 22 +- .../presentation/widgets/WidgetConfig.kt | 2 +- .../presentation/widgets/WidgetView.kt | 28 +- .../widgets/collection/CollectionViewModel.kt | 47 +- .../collections/CollectionAddRelationTest.kt | 7 +- .../CollectionCreateAndAddObjectTest.kt | 19 +- .../CollectionDataViewUpdateTest.kt | 2 + .../collections/ObjectCreateTest.kt | 2 + .../ObjectStateCollectionViewTest.kt | 7 +- .../collections/ObjectStateSetViewTest.kt | 2 + .../editor/DefaultBlockViewRendererTest.kt | 16 +- .../editor/EditorViewModelTest.kt | 20 +- .../editor/editor/EditorBackButtonTest.kt | 7 +- .../editor/EditorBackspaceDeleteTest.kt | 3 - .../editor/editor/EditorBlockActionsTest.kt | 1 - .../editor/editor/EditorCheckboxTest.kt | 6 +- .../editor/editor/EditorCreateBlockTest.kt | 2 - .../editor/EditorEmptySpaceInteractionTest.kt | 5 +- .../editor/editor/EditorErrorMessageTest.kt | 6 +- .../editor/EditorEventSubscriptionTest.kt | 5 +- .../editor/editor/EditorFocusTest.kt | 6 +- .../editor/editor/EditorLatexBlockTest.kt | 1 - .../editor/editor/EditorListBlockTest.kt | 2 - .../editor/editor/EditorLockPageTest.kt | 9 +- .../editor/editor/EditorMentionTest.kt | 3 +- .../editor/editor/EditorMergeTest.kt | 5 +- .../editor/editor/EditorNoteLayoutTest.kt | 5 +- .../editor/EditorPresentationTestSetup.kt | 28 +- .../editor/editor/EditorRelationBlockTest.kt | 2 - .../editor/EditorSlashWidgetActionsTest.kt | 5 +- .../editor/EditorSlashWidgetColorTest.kt | 5 +- .../editor/EditorSlashWidgetCutFilterTest.kt | 5 +- .../editor/EditorSlashWidgetMarksTest.kt | 5 +- .../editor/EditorSlashWidgetObjectTypeTest.kt | 11 +- .../editor/EditorSlashWidgetShowHideTest.kt | 5 +- .../editor/EditorSlashWidgetStyleTypeTest.kt | 5 +- .../editor/editor/EditorSplitTest.kt | 5 +- .../editor/editor/EditorTextUpdateTest.kt | 5 +- .../editor/editor/EditorTitleAddBlockTest.kt | 5 +- .../editor/editor/EditorTitleTest.kt | 7 +- .../table/EditorTableRowsColumnsDeleteTest.kt | 5 +- .../editor/table/TableBlockRendererTest.kt | 8 +- .../extension/MapExtensionKtTest.kt | 39 - .../presentation/history/StubVersionModels.kt | 4 +- .../history/VersionHistoryViewModelTest.kt | 34 +- .../home/HomeScreenViewModelTest.kt | 75 +- .../presentation/home/ParseWidgetTest.kt | 2 + .../home/TreeWidgetContainerTest.kt | 46 +- .../linking/LinkToObjectOrWebViewModelTest.kt | 7 +- .../mapper/ObjectWrapperExtensionsKtTest.kt | 28 +- .../objects/CreateDVObjectTest.kt | 14 +- .../presentation/sets/TagAndStatusTests.kt | 20 +- .../sets/ViewerDefaultObjectTypeTest.kt | 2 + ...ilterViewModelInputFieldValueCreateTest.kt | 7 +- ...ilterViewModelInputFieldValueModifyTest.kt | 7 +- .../sets/main/ObjectSetCellTest.kt | 2 + .../main/ObjectSetConvertToCollectionTest.kt | 2 + .../main/ObjectSetDataViewObjectCreateTest.kt | 5 +- .../sets/main/ObjectSetHeaderTest.kt | 2 + .../sets/main/ObjectSetInitializationTest.kt | 2 + .../sets/main/ObjectSetNavigationTest.kt | 5 +- .../sets/main/ObjectSetRestrictionsTest.kt | 2 + .../sets/main/ObjectSetViewModelTestSetup.kt | 20 +- .../sets/main/ObjectSetZeroDataViewTest.kt | 2 + .../sets/main/ObjectSetZeroViewTest.kt | 2 + .../sets/main/SetByRelationTest.kt | 2 + .../splash/SplashViewModelTest.kt | 9 + .../CollectionViewerTypeAndTemplateTest.kt | 2 + .../SetByRelationViewerTypeAndTemplateTest.kt | 2 + .../SetByTypeViewerTypeAndTemplateTest.kt | 2 + .../types/ObjectTypeChangeViewModelTest.kt | 3 +- settings.gradle | 4 +- 284 files changed, 6589 insertions(+), 1211 deletions(-) create mode 100644 app/src/main/java/com/anytypeio/anytype/di/feature/DateObjectDI.kt create mode 100644 app/src/main/java/com/anytypeio/anytype/ui/date/DateObjectFragment.kt create mode 100644 core-models/src/main/java/com/anytypeio/anytype/core_models/RelativeDate.kt rename {presentation/src/main/java/com/anytypeio/anytype/presentation/objects => core-models/src/main/java/com/anytypeio/anytype/core_models}/SupportedLayouts.kt (84%) delete mode 100644 core-models/src/main/java/com/anytypeio/anytype/core_models/exceptions/SpaceLimitReachedException.kt create mode 100644 core-models/src/main/java/com/anytypeio/anytype/core_models/primitives/FieldValues.kt create mode 100644 core-ui/src/main/java/com/anytypeio/anytype/core_ui/common/ShimmerEffect.kt create mode 100644 core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/EditorDatePicker.kt create mode 100644 core-ui/src/main/java/com/anytypeio/anytype/core_ui/syncstatus/StatusBadge.kt create mode 100644 core-ui/src/main/res/drawable/ic_burger_24.xml create mode 100644 core-ui/src/main/res/drawable/ic_calendar_24.xml create mode 100644 core-ui/src/main/res/drawable/ic_mention_24.xml create mode 100644 core-ui/src/main/res/drawable/ic_obj_date_20.xml create mode 100644 core-ui/src/main/res/drawable/ic_obj_date_24.xml create mode 100644 core-ui/src/main/res/layout/item_mention_section_date.xml create mode 100644 core-ui/src/main/res/layout/item_mention_section_objects.xml create mode 100644 core-ui/src/main/res/layout/item_relation_list_relation_date.xml create mode 100644 domain/src/main/java/com/anytypeio/anytype/domain/objects/GetDateObjectByTimestamp.kt create mode 100644 domain/src/main/java/com/anytypeio/anytype/domain/primitives/FieldParser.kt create mode 100644 domain/src/main/java/com/anytypeio/anytype/domain/primitives/FieldValuesParser.kt create mode 100644 domain/src/main/java/com/anytypeio/anytype/domain/relations/GetObjectRelationListById.kt create mode 100644 domain/src/test/java/com/anytypeio/anytype/domain/primitives/FieldDateTest.kt create mode 100644 feature-date/build.gradle create mode 100644 feature-date/src/main/AndroidManifest.xml create mode 100644 feature-date/src/main/java/com/anytypeio/anytype/feature_date/mapping/DateObjectModelsExt.kt create mode 100644 feature-date/src/main/java/com/anytypeio/anytype/feature_date/ui/CalendarScreen.kt create mode 100644 feature-date/src/main/java/com/anytypeio/anytype/feature_date/ui/FieldsScreen.kt create mode 100644 feature-date/src/main/java/com/anytypeio/anytype/feature_date/ui/FieldsSheetScreen.kt create mode 100644 feature-date/src/main/java/com/anytypeio/anytype/feature_date/ui/HeaderScreen.kt create mode 100644 feature-date/src/main/java/com/anytypeio/anytype/feature_date/ui/MainScreen.kt create mode 100644 feature-date/src/main/java/com/anytypeio/anytype/feature_date/ui/ObjectsScreen.kt create mode 100644 feature-date/src/main/java/com/anytypeio/anytype/feature_date/ui/OtherScreen.kt create mode 100644 feature-date/src/main/java/com/anytypeio/anytype/feature_date/ui/TopToolbarScreen.kt create mode 100644 feature-date/src/main/java/com/anytypeio/anytype/feature_date/ui/models/DateEvent.kt create mode 100644 feature-date/src/main/java/com/anytypeio/anytype/feature_date/ui/models/PreviewStubs.kt create mode 100644 feature-date/src/main/java/com/anytypeio/anytype/feature_date/viewmodel/DateModels.kt create mode 100644 feature-date/src/main/java/com/anytypeio/anytype/feature_date/viewmodel/DateObjectCommand.kt create mode 100644 feature-date/src/main/java/com/anytypeio/anytype/feature_date/viewmodel/DateObjectVMFactory.kt create mode 100644 feature-date/src/main/java/com/anytypeio/anytype/feature_date/viewmodel/DateObjectViewModel.kt create mode 100644 feature-date/src/main/java/com/anytypeio/anytype/feature_date/viewmodel/SearchParams.kt create mode 100644 presentation/src/main/java/com/anytypeio/anytype/presentation/editor/model/EditorDatePicker.kt delete mode 100644 presentation/src/main/java/com/anytypeio/anytype/presentation/extension/MapExtension.kt delete mode 100644 presentation/src/main/java/com/anytypeio/anytype/presentation/search/ObjectSearchViewModelFactory.kt delete mode 100644 presentation/src/test/java/com/anytypeio/anytype/presentation/extension/MapExtensionKtTest.kt diff --git a/analytics/src/main/java/com/anytypeio/anytype/analytics/base/EventsDictionary.kt b/analytics/src/main/java/com/anytypeio/anytype/analytics/base/EventsDictionary.kt index fa5b4825cb..02bdaff46f 100644 --- a/analytics/src/main/java/com/anytypeio/anytype/analytics/base/EventsDictionary.kt +++ b/analytics/src/main/java/com/anytypeio/anytype/analytics/base/EventsDictionary.kt @@ -289,6 +289,7 @@ object EventsDictionary { const val searchMenu = "MenuSearch" const val objCreateSet = "Set" const val objCreateHome = "Home" + const val objDate = "Date" const val objCreateCollection = "Collection" const val allContentRoute = "Library" const val objCreateMention = "Mention" diff --git a/app/build.gradle b/app/build.gradle index ca45c9059e..4b70054c3f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -176,6 +176,7 @@ dependencies { implementation project(':feature-discussions') implementation project(':gallery-experience') implementation project(':feature-all-content') + implementation project(':feature-date') //Compile time dependencies ksp libs.daggerCompiler diff --git a/app/src/androidTest/java/com/anytypeio/anytype/features/editor/base/EditorTestSetup.kt b/app/src/androidTest/java/com/anytypeio/anytype/features/editor/base/EditorTestSetup.kt index 95f518c06f..3ec9cae52a 100644 --- a/app/src/androidTest/java/com/anytypeio/anytype/features/editor/base/EditorTestSetup.kt +++ b/app/src/androidTest/java/com/anytypeio/anytype/features/editor/base/EditorTestSetup.kt @@ -61,6 +61,7 @@ import com.anytypeio.anytype.domain.event.interactor.SpaceSyncAndP2PStatusProvid import com.anytypeio.anytype.domain.icon.SetDocumentImageIcon import com.anytypeio.anytype.domain.launch.GetDefaultObjectType import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer +import com.anytypeio.anytype.domain.misc.DateProvider import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.multiplayer.UserPermissionProvider import com.anytypeio.anytype.domain.networkmode.GetNetworkMode @@ -69,6 +70,7 @@ import com.anytypeio.anytype.domain.`object`.ConvertObjectToSet import com.anytypeio.anytype.domain.`object`.UpdateDetail import com.anytypeio.anytype.domain.objects.DefaultStoreOfObjectTypes import com.anytypeio.anytype.domain.objects.DefaultStoreOfRelations +import com.anytypeio.anytype.domain.objects.GetDateObjectByTimestamp import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes import com.anytypeio.anytype.domain.objects.StoreOfRelations import com.anytypeio.anytype.domain.page.CloseBlock @@ -80,6 +82,7 @@ import com.anytypeio.anytype.domain.page.Redo import com.anytypeio.anytype.domain.page.Undo import com.anytypeio.anytype.domain.page.bookmark.CreateBookmarkBlock import com.anytypeio.anytype.domain.page.bookmark.SetupBookmark +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.relations.AddRelationToObject import com.anytypeio.anytype.domain.relations.SetRelationKey import com.anytypeio.anytype.domain.search.SearchObjects @@ -148,6 +151,9 @@ open class EditorTestSetup { @Mock lateinit var documentFileShareDownloader: DocumentFileShareDownloader + @Mock + lateinit var dateProvider: DateProvider + @Mock lateinit var openPage: OpenPage @@ -276,6 +282,9 @@ open class EditorTestSetup { @Mock lateinit var fileLimitsEventChannel: FileLimitsEventChannel + @Mock + lateinit var getDateObjectByTimestamp: GetDateObjectByTimestamp + @Mock lateinit var spaceManager: SpaceManager @@ -294,6 +303,9 @@ open class EditorTestSetup { @Mock lateinit var spaceSyncAndP2PStatusProvider: SpaceSyncAndP2PStatusProvider + @Mock + lateinit var fieldParser: FieldParser + lateinit var interceptFileLimitEvents: InterceptFileLimitEvents lateinit var addRelationToObject: AddRelationToObject @@ -410,7 +422,8 @@ open class EditorTestSetup { toggleStateHolder = ToggleStateHolder.Default(), coverImageHashProvider = coverImageHashProvider, storeOfRelations = storeOfRelations, - storeOfObjectTypes = storeOfObjectTypes + storeOfObjectTypes = storeOfObjectTypes, + fieldParser = fieldParser ), orchestrator = Orchestrator( createBlock = createBlock, @@ -496,7 +509,10 @@ open class EditorTestSetup { syncStatusProvider = spaceSyncAndP2PStatusProvider, analyticSpaceHelperDelegate = analyticSpaceHelperDelegate, clearLastOpenedObject = clearLastOpenedObject, - getNetworkMode = getNetworkMode + getNetworkMode = getNetworkMode, + fieldParser = fieldParser, + dateProvider = dateProvider, + getDateObjectByTimestamp = getDateObjectByTimestamp, ) } diff --git a/app/src/androidTest/java/com/anytypeio/anytype/features/relations/ObjectRelationListTest.kt b/app/src/androidTest/java/com/anytypeio/anytype/features/relations/ObjectRelationListTest.kt index 11244a9650..d638cfd1be 100644 --- a/app/src/androidTest/java/com/anytypeio/anytype/features/relations/ObjectRelationListTest.kt +++ b/app/src/androidTest/java/com/anytypeio/anytype/features/relations/ObjectRelationListTest.kt @@ -24,6 +24,7 @@ import com.anytypeio.anytype.domain.config.Gateway import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.`object`.UpdateDetail import com.anytypeio.anytype.domain.objects.StoreOfRelations +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.relations.AddRelationToObject import com.anytypeio.anytype.domain.relations.AddToFeaturedRelations import com.anytypeio.anytype.domain.relations.DeleteRelationFromObject @@ -105,6 +106,9 @@ class ObjectRelationListTest { lateinit var urlBuilder: UrlBuilder + @Mock + lateinit var fieldParser: FieldParser + @Before fun setup() { MockitoAnnotations.openMocks(this) @@ -126,7 +130,8 @@ class ObjectRelationListTest { storeOfRelations = storeOfRelations, addRelationToObject = addRelationToObject, spaceManager = spaceManager, - analyticSpaceHelperDelegate = analyticSpaceHelperDelegate + analyticSpaceHelperDelegate = analyticSpaceHelperDelegate, + fieldParser = fieldParser ) } diff --git a/app/src/androidTest/java/com/anytypeio/anytype/features/sets/dv/TestObjectSetSetup.kt b/app/src/androidTest/java/com/anytypeio/anytype/features/sets/dv/TestObjectSetSetup.kt index 0d5d3abcde..193e8fe1b6 100644 --- a/app/src/androidTest/java/com/anytypeio/anytype/features/sets/dv/TestObjectSetSetup.kt +++ b/app/src/androidTest/java/com/anytypeio/anytype/features/sets/dv/TestObjectSetSetup.kt @@ -48,6 +48,7 @@ import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes import com.anytypeio.anytype.domain.objects.StoreOfRelations import com.anytypeio.anytype.domain.page.CloseBlock import com.anytypeio.anytype.domain.page.CreateObject +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.search.CancelSearchSubscription import com.anytypeio.anytype.domain.search.DataViewSubscriptionContainer import com.anytypeio.anytype.domain.search.SubscriptionEventChannel @@ -57,6 +58,7 @@ import com.anytypeio.anytype.domain.templates.CreateTemplate import com.anytypeio.anytype.domain.templates.GetTemplates import com.anytypeio.anytype.domain.unsplash.DownloadUnsplashImage import com.anytypeio.anytype.domain.unsplash.UnsplashRepository +import com.anytypeio.anytype.domain.vault.ObserveVaultSettings import com.anytypeio.anytype.domain.workspace.SpaceManager import com.anytypeio.anytype.emojifier.data.DefaultDocumentEmojiIconProvider import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate @@ -79,12 +81,12 @@ import java.util.Locale import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope import org.mockito.Mock import org.mockito.Mockito import org.mockito.MockitoAnnotations import org.mockito.kotlin.any import org.mockito.kotlin.doReturn -import org.mockito.kotlin.mock import org.mockito.kotlin.stub abstract class TestObjectSetSetup { @@ -231,7 +233,18 @@ abstract class TestObjectSetSetup { @Mock lateinit var localeProvider : LocaleProvider - private val dateProvider = DateProviderImpl(ZoneId.systemDefault(), localeProvider) + @Mock + lateinit var observeVaultSettings: ObserveVaultSettings + + @Mock + lateinit var fieldParser: FieldParser + + private val dateProvider = DateProviderImpl( + defaultZoneId = ZoneId.systemDefault(), + localeProvider = localeProvider, + vaultSettings = observeVaultSettings, + scope = TestScope() + ) open fun setup() { MockitoAnnotations.openMocks(this) @@ -317,7 +330,8 @@ abstract class TestObjectSetSetup { permissions = permissions, analyticSpaceHelperDelegate = analyticSpaceHelperDelegate, spaceSyncAndP2PStatusProvider = spaceSyncAndP2PStatusProvider, - clearLastOpenedObject = clearLastOpenedObject + clearLastOpenedObject = clearLastOpenedObject, + fieldParser = fieldParser ) Mockito.`when`(localeProvider.locale()).thenReturn(Locale.getDefault()) diff --git a/app/src/androidTest/java/com/anytypeio/anytype/features/sets/filter/CreateSelectedFilterTest.kt b/app/src/androidTest/java/com/anytypeio/anytype/features/sets/filter/CreateSelectedFilterTest.kt index 813b83e60f..96ebae7829 100644 --- a/app/src/androidTest/java/com/anytypeio/anytype/features/sets/filter/CreateSelectedFilterTest.kt +++ b/app/src/androidTest/java/com/anytypeio/anytype/features/sets/filter/CreateSelectedFilterTest.kt @@ -25,6 +25,7 @@ import com.anytypeio.anytype.domain.objects.ObjectStore import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes import com.anytypeio.anytype.domain.objects.StoreOfRelations import com.anytypeio.anytype.domain.objects.options.GetOptions +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.search.SearchObjects import com.anytypeio.anytype.domain.workspace.SpaceManager import com.anytypeio.anytype.presentation.sets.ObjectSetDatabase @@ -73,6 +74,9 @@ class CreateSelectedFilterTest { @Mock lateinit var dispatchers: AppCoroutineDispatchers + @Mock + lateinit var fieldParser: FieldParser + private lateinit var updateDataViewViewer: UpdateDataViewViewer private lateinit var searchObjects: SearchObjects private lateinit var getOptions: GetOptions @@ -104,7 +108,8 @@ class CreateSelectedFilterTest { storeOfRelations = storeOfRelations, objectSetDatabase = db, getOptions = getOptions, - spaceManager = spaceManager + spaceManager = spaceManager, + fieldParser = fieldParser ) } diff --git a/app/src/androidTest/java/com/anytypeio/anytype/features/sets/filter/FilterListTest.kt b/app/src/androidTest/java/com/anytypeio/anytype/features/sets/filter/FilterListTest.kt index d535d8b4e4..ed7f5c784e 100644 --- a/app/src/androidTest/java/com/anytypeio/anytype/features/sets/filter/FilterListTest.kt +++ b/app/src/androidTest/java/com/anytypeio/anytype/features/sets/filter/FilterListTest.kt @@ -21,6 +21,7 @@ import com.anytypeio.anytype.domain.dataview.interactor.UpdateDataViewViewer import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.objects.DefaultObjectStore import com.anytypeio.anytype.domain.objects.DefaultStoreOfRelations +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.search.SearchObjects import com.anytypeio.anytype.presentation.relations.ObjectSetConfig import com.anytypeio.anytype.presentation.sets.ObjectSetDatabase @@ -64,6 +65,9 @@ class FilterListTest { @Mock lateinit var dispatchers: AppCoroutineDispatchers + @Mock + lateinit var fieldParser: FieldParser + lateinit var updateDataViewViewer: UpdateDataViewViewer lateinit var searchObjects: SearchObjects lateinit var urlBuilder: UrlBuilder @@ -88,7 +92,8 @@ class FilterListTest { state = state, analytics = analytics, storeOfRelations = storeOfRelations, - objectSetDatabase = db + objectSetDatabase = db, + fieldParser = fieldParser ) } diff --git a/app/src/androidTest/java/com/anytypeio/anytype/features/sets/filter/ModifyInputValueFilterTest.kt b/app/src/androidTest/java/com/anytypeio/anytype/features/sets/filter/ModifyInputValueFilterTest.kt index e43474d80d..39d8ded654 100644 --- a/app/src/androidTest/java/com/anytypeio/anytype/features/sets/filter/ModifyInputValueFilterTest.kt +++ b/app/src/androidTest/java/com/anytypeio/anytype/features/sets/filter/ModifyInputValueFilterTest.kt @@ -33,6 +33,7 @@ import com.anytypeio.anytype.domain.objects.ObjectStore import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes import com.anytypeio.anytype.domain.objects.StoreOfRelations import com.anytypeio.anytype.domain.objects.options.GetOptions +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.search.SearchObjects import com.anytypeio.anytype.domain.workspace.SpaceManager import com.anytypeio.anytype.presentation.sets.ObjectSetDatabase @@ -78,6 +79,9 @@ class ModifyInputValueFilterTest { @Mock lateinit var spaceManager: SpaceManager + @Mock + lateinit var fieldParser: FieldParser + private lateinit var updateDataViewViewer: UpdateDataViewViewer private lateinit var searchObjects: SearchObjects private lateinit var getOptions: GetOptions @@ -108,7 +112,8 @@ class ModifyInputValueFilterTest { storeOfRelations = storeOfRelations, objectSetDatabase = db, getOptions = getOptions, - spaceManager = spaceManager + spaceManager = spaceManager, + fieldParser = fieldParser ) } diff --git a/app/src/androidTest/java/com/anytypeio/anytype/features/sets/filter/ModifyStatusFilterTest.kt b/app/src/androidTest/java/com/anytypeio/anytype/features/sets/filter/ModifyStatusFilterTest.kt index bc577e1ca6..006f507e5b 100644 --- a/app/src/androidTest/java/com/anytypeio/anytype/features/sets/filter/ModifyStatusFilterTest.kt +++ b/app/src/androidTest/java/com/anytypeio/anytype/features/sets/filter/ModifyStatusFilterTest.kt @@ -31,6 +31,7 @@ import com.anytypeio.anytype.domain.objects.ObjectStore import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes import com.anytypeio.anytype.domain.objects.StoreOfRelations import com.anytypeio.anytype.domain.objects.options.GetOptions +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.search.SearchObjects import com.anytypeio.anytype.domain.workspace.SpaceManager import com.anytypeio.anytype.presentation.sets.ObjectSetDatabase @@ -74,6 +75,9 @@ class ModifyStatusFilterTest { @Mock lateinit var analytics: Analytics + @Mock + lateinit var fieldParser: FieldParser + @Mock lateinit var spaceManager: SpaceManager @@ -108,7 +112,8 @@ class ModifyStatusFilterTest { analytics = analytics, storeOfRelations = storeOfRelations, objectSetDatabase = db, - spaceManager = spaceManager + spaceManager = spaceManager, + fieldParser = fieldParser ) } diff --git a/app/src/androidTest/java/com/anytypeio/anytype/features/sets/filter/ModifyTagFilterTest.kt b/app/src/androidTest/java/com/anytypeio/anytype/features/sets/filter/ModifyTagFilterTest.kt index bf01326f99..9c6a64505a 100644 --- a/app/src/androidTest/java/com/anytypeio/anytype/features/sets/filter/ModifyTagFilterTest.kt +++ b/app/src/androidTest/java/com/anytypeio/anytype/features/sets/filter/ModifyTagFilterTest.kt @@ -31,6 +31,7 @@ import com.anytypeio.anytype.domain.objects.ObjectStore import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes import com.anytypeio.anytype.domain.objects.StoreOfRelations import com.anytypeio.anytype.domain.objects.options.GetOptions +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.search.SearchObjects import com.anytypeio.anytype.domain.workspace.SpaceManager import com.anytypeio.anytype.presentation.sets.ObjectSetDatabase @@ -76,6 +77,9 @@ class ModifyTagFilterTest { @Mock lateinit var spaceManager: SpaceManager + @Mock + lateinit var fieldParser: FieldParser + private lateinit var updateDataViewViewer: UpdateDataViewViewer private lateinit var searchObjects: SearchObjects private lateinit var getOptions: GetOptions @@ -107,7 +111,8 @@ class ModifyTagFilterTest { storeOfRelations = storeOfRelations, objectSetDatabase = db, getOptions = getOptions, - spaceManager = spaceManager + spaceManager = spaceManager, + fieldParser = fieldParser ) } 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 2bcb53beea..d371bd56db 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 @@ -8,6 +8,7 @@ import com.anytypeio.anytype.di.feature.CreateObjectModule 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.DaggerLinkToObjectComponent import com.anytypeio.anytype.di.feature.DaggerMoveToComponent import com.anytypeio.anytype.di.feature.DaggerSplashComponent @@ -100,9 +101,9 @@ import com.anytypeio.anytype.di.feature.widgets.DaggerSelectWidgetSourceComponen import com.anytypeio.anytype.di.feature.widgets.DaggerSelectWidgetTypeComponent import com.anytypeio.anytype.di.main.MainComponent import com.anytypeio.anytype.feature_allcontent.presentation.AllContentViewModel +import com.anytypeio.anytype.feature_date.viewmodel.DateObjectVmParams import com.anytypeio.anytype.feature_discussions.presentation.DiscussionViewModel import com.anytypeio.anytype.gallery_experience.viewmodel.GalleryInstallationViewModel -import com.anytypeio.anytype.presentation.common.BaseViewModel import com.anytypeio.anytype.presentation.editor.EditorViewModel import com.anytypeio.anytype.presentation.history.VersionHistoryViewModel import com.anytypeio.anytype.presentation.linking.LinkToObjectOrWebViewModel @@ -337,6 +338,12 @@ class ComponentManager( .create(params, findComponentDependencies()) } + val dateObjectComponent = ComponentWithParams { params: DateObjectVmParams -> + DaggerDateObjectComponent + .factory() + .create(params, findComponentDependencies()) + } + val objectSetComponent = ComponentMapWithParam { param: DefaultComponentParam -> main.objectSetComponentBuilder() .module(ObjectSetModule) diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/AllContentDI.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/AllContentDI.kt index 5296eea54b..5880e9ad59 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/AllContentDI.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/AllContentDI.kt @@ -8,7 +8,6 @@ import com.anytypeio.anytype.domain.all_content.RestoreAllContentState import com.anytypeio.anytype.domain.all_content.UpdateAllContentState import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers 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.launch.GetDefaultObjectType @@ -20,10 +19,10 @@ import com.anytypeio.anytype.domain.`object`.SetObjectDetails import com.anytypeio.anytype.domain.objects.SetObjectListIsArchived import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes import com.anytypeio.anytype.domain.page.CreateObject +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.search.SearchObjects import com.anytypeio.anytype.domain.search.SubscriptionEventChannel import com.anytypeio.anytype.domain.workspace.RemoveObjectsFromWorkspace -import com.anytypeio.anytype.domain.workspace.SpaceManager import com.anytypeio.anytype.feature_allcontent.presentation.AllContentViewModel import com.anytypeio.anytype.feature_allcontent.presentation.AllContentViewModelFactory import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate @@ -100,8 +99,7 @@ object AllContentModule { fun createObject( repo: BlockRepository, getDefaultObjectType: GetDefaultObjectType, - dispatchers: AppCoroutineDispatchers, - spaceManager: SpaceManager, + dispatchers: AppCoroutineDispatchers ): CreateObject = CreateObject( repo = repo, getDefaultObjectType = getDefaultObjectType, @@ -114,9 +112,7 @@ object AllContentModule { fun provideGetDefaultPageType( userSettingsRepository: UserSettingsRepository, blockRepository: BlockRepository, - dispatchers: AppCoroutineDispatchers, - spaceManager: SpaceManager, - configStorage: ConfigStorage + dispatchers: AppCoroutineDispatchers ): GetDefaultObjectType = GetDefaultObjectType( userSettingsRepository = userSettingsRepository, blockRepository = blockRepository, @@ -169,8 +165,7 @@ interface AllContentDependencies : ComponentDependencies { fun subEventChannel(): SubscriptionEventChannel fun logger(): Logger fun localeProvider(): LocaleProvider - fun spaceManager(): SpaceManager - fun config(): ConfigStorage fun userPermissionProvider(): UserPermissionProvider fun searchObjects(): SearchObjects + fun fieldsProvider(): FieldParser } \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/BacklinkOrAddToObjectDI.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/BacklinkOrAddToObjectDI.kt index fa544a3401..34fc32115f 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/BacklinkOrAddToObjectDI.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/BacklinkOrAddToObjectDI.kt @@ -8,7 +8,9 @@ import com.anytypeio.anytype.domain.auth.repo.AuthRepository import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers import com.anytypeio.anytype.domain.block.interactor.sets.GetObjectTypes import com.anytypeio.anytype.domain.block.repo.BlockRepository +import com.anytypeio.anytype.domain.misc.DateProvider import com.anytypeio.anytype.domain.misc.UrlBuilder +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.search.SearchObjects import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate import com.anytypeio.anytype.presentation.linking.BackLinkOrAddToObjectViewModelFactory @@ -73,4 +75,6 @@ interface BacklinkOrAddToObjectDependencies : ComponentDependencies { fun analytics(): Analytics fun analyticSpaceHelper(): AnalyticSpaceHelperDelegate fun searchObjects(): SearchObjects + fun dateProvider(): DateProvider + fun fieldParser(): FieldParser } \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/DateObjectDI.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/DateObjectDI.kt new file mode 100644 index 0000000000..5dda2a77be --- /dev/null +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/DateObjectDI.kt @@ -0,0 +1,173 @@ +package com.anytypeio.anytype.di.feature + +import androidx.lifecycle.ViewModelProvider +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.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.SpaceSyncAndP2PStatusProvider +import com.anytypeio.anytype.domain.launch.GetDefaultObjectType +import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer +import com.anytypeio.anytype.domain.misc.DateProvider +import com.anytypeio.anytype.domain.misc.LocaleProvider +import com.anytypeio.anytype.domain.misc.UrlBuilder +import com.anytypeio.anytype.domain.multiplayer.UserPermissionProvider +import com.anytypeio.anytype.domain.`object`.GetObject +import com.anytypeio.anytype.domain.`object`.SetObjectDetails +import com.anytypeio.anytype.domain.objects.GetDateObjectByTimestamp +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.relations.GetObjectRelationListById +import com.anytypeio.anytype.domain.search.SearchObjects +import com.anytypeio.anytype.domain.search.SubscriptionEventChannel +import com.anytypeio.anytype.feature_date.viewmodel.DateObjectVMFactory +import com.anytypeio.anytype.feature_date.viewmodel.DateObjectVmParams +import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate +import com.anytypeio.anytype.ui.date.DateObjectFragment +import dagger.Binds +import dagger.BindsInstance +import dagger.Component +import dagger.Module +import dagger.Provides + +@Component( + dependencies = [DateObjectDependencies::class], + modules = [ + DateObjectModule::class, + DateObjectModule.Declarations::class + ] +) +@PerScreen +interface DateObjectComponent { + @Component.Factory + interface Factory { + fun create( + @BindsInstance vmParams: DateObjectVmParams, + dependencies: DateObjectDependencies + ): DateObjectComponent + } + + fun inject(fragment: DateObjectFragment) +} + +@Module +object DateObjectModule { + + @JvmStatic + @Provides + @PerScreen + fun getObject( + repo: BlockRepository, + dispatchers: AppCoroutineDispatchers + ): GetObject = GetObject( + repo = repo, + dispatchers = dispatchers + ) + + @JvmStatic + @Provides + @PerScreen + fun provideStoreLessSubscriptionContainer( + repo: BlockRepository, + channel: SubscriptionEventChannel, + dispatchers: AppCoroutineDispatchers, + logger: Logger + ): StorelessSubscriptionContainer = StorelessSubscriptionContainer.Impl( + repo = repo, + channel = channel, + dispatchers = dispatchers, + logger = logger + ) + + @JvmStatic + @Provides + @PerScreen + fun searchObjects( + repo: BlockRepository + ): SearchObjects = SearchObjects(repo = repo) + + @JvmStatic + @Provides + @PerScreen + fun createObject( + repo: BlockRepository, + getDefaultObjectType: GetDefaultObjectType, + dispatchers: AppCoroutineDispatchers, + ): CreateObject = CreateObject( + repo = repo, + getDefaultObjectType = getDefaultObjectType, + dispatchers = dispatchers + ) + + @JvmStatic + @Provides + @PerScreen + fun provideGetDefaultPageType( + userSettingsRepository: UserSettingsRepository, + blockRepository: BlockRepository, + dispatchers: AppCoroutineDispatchers + ): GetDefaultObjectType = GetDefaultObjectType( + userSettingsRepository = userSettingsRepository, + blockRepository = blockRepository, + dispatchers = dispatchers + ) + + @JvmStatic + @Provides + @PerScreen + fun provideRelationListWithValue( + repository: BlockRepository, + dispatchers: AppCoroutineDispatchers + ): GetObjectRelationListById = GetObjectRelationListById(repository, dispatchers) + + @JvmStatic + @Provides + @PerScreen + fun provideUpdateDetailUseCase( + repository: BlockRepository, + dispatchers: AppCoroutineDispatchers + ): SetObjectDetails = SetObjectDetails(repository, dispatchers) + + @JvmStatic + @Provides + @PerScreen + fun provideDateByTimestamp( + repository: BlockRepository, + dispatchers: AppCoroutineDispatchers + ): GetDateObjectByTimestamp = GetDateObjectByTimestamp(repository, dispatchers) + + @Module + interface Declarations { + @PerScreen + @Binds + fun bindViewModelFactory( + factory: DateObjectVMFactory + ): ViewModelProvider.Factory + + } +} + +interface DateObjectDependencies : ComponentDependencies { + fun blockRepository(): BlockRepository + fun analytics(): Analytics + fun urlBuilder(): UrlBuilder + fun dispatchers(): AppCoroutineDispatchers + fun storeOfObjectTypes(): StoreOfObjectTypes + fun analyticsHelper(): AnalyticSpaceHelperDelegate + fun subEventChannel(): SubscriptionEventChannel + fun logger(): Logger + fun localeProvider(): LocaleProvider + fun config(): ConfigStorage + fun userPermissionProvider(): UserPermissionProvider + fun provideStoreOfRelations(): StoreOfRelations + fun provideDateProvider(): DateProvider + fun provideSpaceSyncAndP2PStatusProvider(): SpaceSyncAndP2PStatusProvider + fun provideUserSettingsRepository(): UserSettingsRepository + fun fieldParser(): FieldParser +} \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/EditorDI.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/EditorDI.kt index 547f12b8cb..3a8d8f6d9a 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/EditorDI.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/EditorDI.kt @@ -55,6 +55,7 @@ import com.anytypeio.anytype.domain.event.interactor.SpaceSyncAndP2PStatusProvid import com.anytypeio.anytype.domain.icon.SetDocumentImageIcon import com.anytypeio.anytype.domain.launch.GetDefaultObjectType import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer +import com.anytypeio.anytype.domain.misc.DateProvider import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.multiplayer.UserPermissionProvider import com.anytypeio.anytype.domain.networkmode.GetNetworkMode @@ -62,6 +63,7 @@ import com.anytypeio.anytype.domain.`object`.ConvertObjectToCollection import com.anytypeio.anytype.domain.`object`.ConvertObjectToSet import com.anytypeio.anytype.domain.`object`.DuplicateObject import com.anytypeio.anytype.domain.`object`.UpdateDetail +import com.anytypeio.anytype.domain.objects.GetDateObjectByTimestamp import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes import com.anytypeio.anytype.domain.objects.StoreOfRelations import com.anytypeio.anytype.domain.objects.options.GetOptions @@ -75,6 +77,7 @@ import com.anytypeio.anytype.domain.page.Undo import com.anytypeio.anytype.domain.page.UpdateTitle import com.anytypeio.anytype.domain.page.bookmark.CreateBookmarkBlock import com.anytypeio.anytype.domain.page.bookmark.SetupBookmark +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.relations.AddFileToObject import com.anytypeio.anytype.domain.relations.AddRelationToObject import com.anytypeio.anytype.domain.relations.SetRelationKey @@ -287,7 +290,10 @@ object EditorSessionModule { analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate, syncStatusProvider: SpaceSyncAndP2PStatusProvider, getNetworkMode: GetNetworkMode, - clearLastOpenedObject: ClearLastOpenedObject + clearLastOpenedObject: ClearLastOpenedObject, + fieldParser: FieldParser, + dateProvider: DateProvider, + getDateObjectByTimestamp: GetDateObjectByTimestamp ): EditorViewModelFactory = EditorViewModelFactory( params = params, permissions = permissions, @@ -333,7 +339,10 @@ object EditorSessionModule { getNetworkMode = getNetworkMode, analyticSpaceHelperDelegate = analyticSpaceHelperDelegate, clearLastOpenedObject = clearLastOpenedObject, - syncStatusProvider = syncStatusProvider + syncStatusProvider = syncStatusProvider, + fieldParser = fieldParser, + dateProvider = dateProvider, + getDateObjectByTimestamp = getDateObjectByTimestamp ) @JvmStatic @@ -374,13 +383,15 @@ object EditorSessionModule { toggleStateHolder: ToggleStateHolder, coverImageHashProvider: CoverImageHashProvider, storeOfRelations: StoreOfRelations, - storeOfObjectTypes: StoreOfObjectTypes + storeOfObjectTypes: StoreOfObjectTypes, + fieldParser: FieldParser ): DefaultBlockViewRenderer = DefaultBlockViewRenderer( urlBuilder = urlBuilder, toggleStateHolder = toggleStateHolder, coverImageHashProvider = coverImageHashProvider, storeOfRelations = storeOfRelations, - storeOfObjectTypes = storeOfObjectTypes + storeOfObjectTypes = storeOfObjectTypes, + fieldParser = fieldParser ) @JvmStatic @@ -1160,4 +1171,12 @@ object EditorUseCaseModule { getDefaultObjectType = getDefaultObjectType, dispatchers = dispatchers ) + + @JvmStatic + @Provides + @PerScreen + fun provideDateByTimestamp( + repository: BlockRepository, + dispatchers: AppCoroutineDispatchers + ): GetDateObjectByTimestamp = GetDateObjectByTimestamp(repository, dispatchers) } \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/LinkToObjectDI.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/LinkToObjectDI.kt index 2bd6244783..d4fc9b6f85 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/LinkToObjectDI.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/LinkToObjectDI.kt @@ -4,7 +4,9 @@ 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.block.interactor.sets.GetObjectTypes +import com.anytypeio.anytype.domain.misc.DateProvider import com.anytypeio.anytype.domain.misc.UrlBuilder +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.search.SearchObjects import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate import com.anytypeio.anytype.presentation.linking.LinkToObjectViewModelFactory @@ -40,6 +42,8 @@ interface LinkToObjectDependencies: ComponentDependencies { fun searchObjects(): SearchObjects fun analytics(): Analytics fun analyticSpaceHelperDelegate(): AnalyticSpaceHelperDelegate + fun dateProvider(): DateProvider + fun fieldParser(): FieldParser } @@ -55,13 +59,15 @@ object LinkToObjectModule { getObjectTypes: GetObjectTypes, searchObjects: SearchObjects, analytics: Analytics, - analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate + analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate, + fieldParser: FieldParser ): LinkToObjectViewModelFactory = LinkToObjectViewModelFactory( vmParams = vmParams, urlBuilder = urlBuilder, getObjectTypes = getObjectTypes, searchObjects = searchObjects, analytics = analytics, - analyticSpaceHelperDelegate = analyticSpaceHelperDelegate + analyticSpaceHelperDelegate = analyticSpaceHelperDelegate, + fieldParser = fieldParser ) } \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/LinkToObjectOrWebDi.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/LinkToObjectOrWebDi.kt index 6957eef725..586fd220e4 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/LinkToObjectOrWebDi.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/LinkToObjectOrWebDi.kt @@ -5,6 +5,7 @@ import com.anytypeio.anytype.core_utils.di.scope.PerModal import com.anytypeio.anytype.core_utils.tools.UrlValidator import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.search.SearchObjects import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate import com.anytypeio.anytype.presentation.editor.Editor @@ -47,7 +48,8 @@ object LinkToObjectOrWebModule { analytics: Analytics, stores: Editor.Storage, urlValidator: UrlValidator, - analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate + analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate, + fieldParser: FieldParser ): LinkToObjectOrWebViewModelFactory = LinkToObjectOrWebViewModelFactory( vmParams = vmParams, urlBuilder = urlBuilder, @@ -56,6 +58,7 @@ object LinkToObjectOrWebModule { analytics = analytics, stores = stores, urlValidator = urlValidator, - analyticSpaceHelperDelegate = analyticSpaceHelperDelegate + analyticSpaceHelperDelegate = analyticSpaceHelperDelegate, + fieldParser = fieldParser ) } \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/MoveToDi.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/MoveToDi.kt index 496a837f66..5677ca809a 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/MoveToDi.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/MoveToDi.kt @@ -5,6 +5,7 @@ import com.anytypeio.anytype.core_utils.di.scope.PerScreen import com.anytypeio.anytype.di.common.ComponentDependencies import com.anytypeio.anytype.domain.block.interactor.sets.GetObjectTypes import com.anytypeio.anytype.domain.misc.UrlBuilder +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.search.SearchObjects import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate import com.anytypeio.anytype.presentation.moving.MoveToViewModel @@ -40,6 +41,7 @@ interface MoveToDependencies : ComponentDependencies { fun searchObjects(): SearchObjects fun analytics(): Analytics fun analyticSpaceHelperDelegate(): AnalyticSpaceHelperDelegate + fun fieldParser(): FieldParser } @Module @@ -54,13 +56,15 @@ object MoveToModule { getObjectTypes: GetObjectTypes, searchObjects: SearchObjects, analytics: Analytics, - analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate + analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate, + fieldParser: FieldParser ): MoveToViewModelFactory = MoveToViewModelFactory( vmParams = vmParams, urlBuilder = urlBuilder, getObjectTypes = getObjectTypes, searchObjects = searchObjects, analytics = analytics, - analyticSpaceHelperDelegate = analyticSpaceHelperDelegate + analyticSpaceHelperDelegate = analyticSpaceHelperDelegate, + fieldParser = fieldParser ) } \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/ObjectMenuDI.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/ObjectMenuDI.kt index b235f72048..97a214e9b1 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/ObjectMenuDI.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/ObjectMenuDI.kt @@ -12,6 +12,7 @@ import com.anytypeio.anytype.domain.block.repo.BlockRepository import com.anytypeio.anytype.domain.collections.AddObjectToCollection import com.anytypeio.anytype.domain.config.UserSettingsRepository import com.anytypeio.anytype.domain.dashboard.interactor.SetObjectListIsFavorite +import com.anytypeio.anytype.domain.misc.DateProvider import com.anytypeio.anytype.domain.misc.DeepLinkResolver import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.`object`.DuplicateObject @@ -20,6 +21,7 @@ import com.anytypeio.anytype.domain.objects.SetObjectListIsArchived import com.anytypeio.anytype.domain.page.AddBackLinkToObject import com.anytypeio.anytype.domain.page.CloseBlock import com.anytypeio.anytype.domain.page.OpenPage +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.templates.CreateTemplateFromObject import com.anytypeio.anytype.domain.widgets.CreateWidget import com.anytypeio.anytype.domain.workspace.SpaceManager @@ -116,7 +118,8 @@ object ObjectMenuModule { analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate, payloadDelegator: PayloadDelegator, setObjectListIsFavorite: SetObjectListIsFavorite, - setObjectIsArchived: SetObjectListIsArchived + setObjectIsArchived: SetObjectListIsArchived, + fieldParser: FieldParser ): ObjectMenuViewModel.Factory = ObjectMenuViewModel.Factory( setObjectIsArchived = setObjectIsArchived, duplicateObject = duplicateObject, @@ -138,7 +141,8 @@ object ObjectMenuModule { deepLinkResolver = deepLinkResolver, analyticSpaceHelperDelegate = analyticSpaceHelperDelegate, payloadDelegator = payloadDelegator, - setObjectListIsFavorite = setObjectListIsFavorite + setObjectListIsFavorite = setObjectListIsFavorite, + fieldParser = fieldParser ) @JvmStatic @@ -231,7 +235,8 @@ object ObjectSetMenuModule { analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate, payloadDelegator: PayloadDelegator, setObjectListIsFavorite: SetObjectListIsFavorite, - setObjectIsArchived: SetObjectListIsArchived + setObjectIsArchived: SetObjectListIsArchived, + fieldParser: FieldParser, ): ObjectSetMenuViewModel.Factory = ObjectSetMenuViewModel.Factory( setObjectListIsArchived = setObjectIsArchived, addBackLinkToObject = addBackLinkToObject, @@ -249,7 +254,8 @@ object ObjectSetMenuModule { deepLinkResolver = deepLinkResolver, analyticSpaceHelperDelegate = analyticSpaceHelperDelegate, payloadDelegator = payloadDelegator, - setObjectListIsFavorite = setObjectListIsFavorite + setObjectListIsFavorite = setObjectListIsFavorite, + fieldParser = fieldParser ) @JvmStatic diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/ObjectRelationListDI.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/ObjectRelationListDI.kt index d106ffdff2..10bc04b48c 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/ObjectRelationListDI.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/ObjectRelationListDI.kt @@ -7,6 +7,7 @@ import com.anytypeio.anytype.domain.block.repo.BlockRepository import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.`object`.UpdateDetail import com.anytypeio.anytype.domain.objects.StoreOfRelations +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.relations.AddRelationToObject import com.anytypeio.anytype.domain.relations.AddToFeaturedRelations import com.anytypeio.anytype.domain.relations.DeleteRelationFromObject @@ -53,7 +54,8 @@ object ObjectRelationListModule { storeOfRelations: StoreOfRelations, addRelationToObject: AddRelationToObject, analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate, - spaceManager: SpaceManager + spaceManager: SpaceManager, + fieldParser: FieldParser ): ObjectRelationListViewModelFactory { return ObjectRelationListViewModelFactory( lockedStateProvider = lockedStateProvider, @@ -68,7 +70,8 @@ object ObjectRelationListModule { storeOfRelations = storeOfRelations, addRelationToObject = addRelationToObject, analyticSpaceHelperDelegate = analyticSpaceHelperDelegate, - spaceManager = spaceManager + spaceManager = spaceManager, + fieldParser = fieldParser ) } diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/ObjectSetDI.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/ObjectSetDI.kt index 8fdeefd213..79c7fa2554 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/ObjectSetDI.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/ObjectSetDI.kt @@ -50,6 +50,7 @@ import com.anytypeio.anytype.domain.objects.StoreOfRelations import com.anytypeio.anytype.domain.objects.options.GetOptions import com.anytypeio.anytype.domain.page.CloseBlock import com.anytypeio.anytype.domain.page.CreateObject +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.relations.AddFileToObject import com.anytypeio.anytype.domain.relations.AddRelationToObject import com.anytypeio.anytype.domain.relations.DeleteRelationFromDataView @@ -232,7 +233,8 @@ object ObjectSetModule { permissions: UserPermissionProvider, clearLastOpenedObject: ClearLastOpenedObject, analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate, - spaceSyncAndP2PStatusProvider: SpaceSyncAndP2PStatusProvider + spaceSyncAndP2PStatusProvider: SpaceSyncAndP2PStatusProvider, + fieldParser: FieldParser ): ObjectSetViewModelFactory = ObjectSetViewModelFactory( params = params, openObjectSet = openObjectSet, @@ -274,7 +276,8 @@ object ObjectSetModule { permissions = permissions, analyticSpaceHelperDelegate = analyticSpaceHelperDelegate, spaceSyncAndP2PStatusProvider = spaceSyncAndP2PStatusProvider, - clearLastOpenedObject = clearLastOpenedObject + clearLastOpenedObject = clearLastOpenedObject, + fieldParser = fieldParser ) @JvmStatic diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/ObjectValueDI.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/ObjectValueDI.kt index 4609a85dd4..b180cafea9 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/ObjectValueDI.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/ObjectValueDI.kt @@ -11,6 +11,7 @@ import com.anytypeio.anytype.domain.`object`.UpdateDetail import com.anytypeio.anytype.domain.objects.SetObjectListIsArchived 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.search.SearchObjects import com.anytypeio.anytype.domain.workspace.SpaceManager import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate @@ -71,7 +72,8 @@ object ObjectValueObjectModule { objectListIsArchived: SetObjectListIsArchived, duplicateObject: DuplicateObject, analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate, - storeOfRelations: StoreOfRelations + storeOfRelations: StoreOfRelations, + fieldParser: FieldParser, ): ObjectValueViewModelFactory = ObjectValueViewModelFactory( params = params, values = values, @@ -86,7 +88,8 @@ object ObjectValueObjectModule { objectListIsArchived = objectListIsArchived, duplicateObject = duplicateObject, analyticSpaceHelperDelegate = analyticSpaceHelperDelegate, - storeOfRelations = storeOfRelations + storeOfRelations = storeOfRelations, + fieldParser = fieldParser ) } //endregion @@ -128,7 +131,8 @@ object ObjectValueSetModule { objectListIsArchived: SetObjectListIsArchived, duplicateObject: DuplicateObject, analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate, - storeOfRelations: StoreOfRelations + storeOfRelations: StoreOfRelations, + fieldParser: FieldParser ): ObjectValueViewModelFactory = ObjectValueViewModelFactory( params = params, values = values, @@ -143,7 +147,8 @@ object ObjectValueSetModule { objectListIsArchived = objectListIsArchived, duplicateObject = duplicateObject, analyticSpaceHelperDelegate = analyticSpaceHelperDelegate, - storeOfRelations = storeOfRelations + storeOfRelations = storeOfRelations, + fieldParser = fieldParser ) } //endregion @@ -185,7 +190,8 @@ object ObjectValueDataViewModule { objectListIsArchived: SetObjectListIsArchived, duplicateObject: DuplicateObject, analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate, - storeOfRelations: StoreOfRelations + storeOfRelations: StoreOfRelations, + fieldParser: FieldParser ): ObjectValueViewModelFactory = ObjectValueViewModelFactory( params = params, values = values, @@ -200,7 +206,8 @@ object ObjectValueDataViewModule { objectListIsArchived = objectListIsArchived, duplicateObject = duplicateObject, analyticSpaceHelperDelegate = analyticSpaceHelperDelegate, - storeOfRelations = storeOfRelations + storeOfRelations = storeOfRelations, + fieldParser = fieldParser ) } //endregion \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/ViewerFilter.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/ViewerFilter.kt index 36048a3ae6..8943cf5d09 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/ViewerFilter.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/ViewerFilter.kt @@ -5,6 +5,7 @@ import com.anytypeio.anytype.core_models.Payload import com.anytypeio.anytype.domain.dataview.interactor.UpdateDataViewViewer import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.objects.StoreOfRelations +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.presentation.sets.ObjectSetDatabase import com.anytypeio.anytype.presentation.sets.filter.ViewerFilterViewModel import com.anytypeio.anytype.presentation.sets.state.ObjectState @@ -41,7 +42,8 @@ object ViewerFilterModule { urlBuilder: UrlBuilder, analytics: Analytics, storeOfRelations: StoreOfRelations, - db: ObjectSetDatabase + db: ObjectSetDatabase, + fieldParser: FieldParser ): ViewerFilterViewModel.Factory = ViewerFilterViewModel.Factory( state = state, dispatcher = dispatcher, @@ -49,10 +51,11 @@ object ViewerFilterModule { urlBuilder = urlBuilder, analytics = analytics, storeOfRelations = storeOfRelations, - objectSetDatabase = db + objectSetDatabase = db, + fieldParser = fieldParser ) } @Scope -@kotlin.annotation.Retention(AnnotationRetention.RUNTIME) +@Retention(AnnotationRetention.RUNTIME) annotation class ViewerFilterByScope \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/search/GlobalSearchComponent.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/search/GlobalSearchComponent.kt index 519bd38d67..65929c6dd7 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/search/GlobalSearchComponent.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/search/GlobalSearchComponent.kt @@ -10,6 +10,7 @@ import com.anytypeio.anytype.domain.config.UserSettingsRepository import com.anytypeio.anytype.domain.misc.UrlBuilder 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.presentation.analytics.AnalyticSpaceHelperDelegate import com.anytypeio.anytype.presentation.search.GlobalSearchViewModel import com.anytypeio.anytype.ui.search.GlobalSearchFragment @@ -58,4 +59,5 @@ interface GlobalSearchDependencies : ComponentDependencies { fun analytics(): Analytics fun analyticsHelper(): AnalyticSpaceHelperDelegate fun userSettingsRepository(): UserSettingsRepository + fun fieldParser(): FieldParser } \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/sets/CreateFilter.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/sets/CreateFilter.kt index 0081f65998..ae1d8b453f 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/sets/CreateFilter.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/sets/CreateFilter.kt @@ -8,6 +8,7 @@ import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes import com.anytypeio.anytype.domain.objects.StoreOfRelations import com.anytypeio.anytype.domain.objects.options.GetOptions +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.search.SearchObjects import com.anytypeio.anytype.domain.workspace.SpaceManager import com.anytypeio.anytype.presentation.sets.ObjectSetDatabase @@ -52,7 +53,8 @@ object CreateFilterModule { objectSetDatabase: ObjectSetDatabase, analytics: Analytics, getOptions: GetOptions, - spaceManager: SpaceManager + spaceManager: SpaceManager, + fieldParser: FieldParser ): FilterViewModel.Factory = FilterViewModel.Factory( objectState = state, dispatcher = dispatcher, @@ -64,6 +66,7 @@ object CreateFilterModule { objectSetDatabase = objectSetDatabase, analytics = analytics, getOptions = getOptions, - spaceManager = spaceManager + spaceManager = spaceManager, + fieldParser = fieldParser ) } diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/sets/ModifyFilter.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/sets/ModifyFilter.kt index 749e666825..e59025290c 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/sets/ModifyFilter.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/sets/ModifyFilter.kt @@ -8,6 +8,7 @@ import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes import com.anytypeio.anytype.domain.objects.StoreOfRelations import com.anytypeio.anytype.domain.objects.options.GetOptions +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.search.SearchObjects import com.anytypeio.anytype.domain.workspace.SpaceManager import com.anytypeio.anytype.presentation.sets.ObjectSetDatabase @@ -52,7 +53,8 @@ object ModifyFilterModule { objectSetDatabase: ObjectSetDatabase, getOptions: GetOptions, storeOfRelations: StoreOfRelations, - spaceManager: SpaceManager + spaceManager: SpaceManager, + fieldParser: FieldParser ): FilterViewModel.Factory = FilterViewModel.Factory( objectState = state, dispatcher = dispatcher, @@ -64,6 +66,7 @@ object ModifyFilterModule { storeOfRelations = storeOfRelations, analytics = analytics, getOptions = getOptions, - spaceManager = spaceManager + spaceManager = spaceManager, + fieldParser = fieldParser ) } diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/templates/TemplateBlankDI.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/templates/TemplateBlankDI.kt index 116c2011b6..e3bad88164 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/templates/TemplateBlankDI.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/templates/TemplateBlankDI.kt @@ -2,9 +2,11 @@ package com.anytypeio.anytype.di.feature.templates import androidx.lifecycle.ViewModelProvider import com.anytypeio.anytype.di.common.ComponentDependencies +import com.anytypeio.anytype.domain.misc.DateProvider import com.anytypeio.anytype.domain.misc.UrlBuilder 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.presentation.editor.cover.CoverImageHashProvider import com.anytypeio.anytype.presentation.editor.render.DefaultBlockViewRenderer import com.anytypeio.anytype.presentation.editor.toggle.ToggleStateHolder @@ -69,6 +71,8 @@ interface TemplateBlankDependencies : ComponentDependencies { fun urlBuilder(): UrlBuilder fun storeOfRelations(): StoreOfRelations fun storeOfObjectTypes(): StoreOfObjectTypes + fun dateProvider(): DateProvider + fun fieldsProvider(): FieldParser } @Scope diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/widgets/SelectWidgetSourceDI.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/widgets/SelectWidgetSourceDI.kt index d2a8040ab0..469fb594a1 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/widgets/SelectWidgetSourceDI.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/widgets/SelectWidgetSourceDI.kt @@ -8,6 +8,7 @@ import com.anytypeio.anytype.domain.block.interactor.sets.GetObjectTypes import com.anytypeio.anytype.domain.block.repo.BlockRepository import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.search.SearchObjects import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate import com.anytypeio.anytype.presentation.search.ObjectSearchViewModel @@ -48,6 +49,7 @@ interface SelectWidgetSourceDependencies : ComponentDependencies { fun storeOfObjectTypes(): StoreOfObjectTypes fun analyticsHelper(): AnalyticSpaceHelperDelegate fun searchObjects(): SearchObjects + fun fieldParser(): FieldParser } @Module @@ -63,7 +65,8 @@ object SelectWidgetSourceModule { searchObjects: SearchObjects, getObjectTypes: GetObjectTypes, dispatcher: Dispatcher, - analyticsHelper: AnalyticSpaceHelperDelegate + analyticsHelper: AnalyticSpaceHelperDelegate, + fieldParser: FieldParser ): SelectWidgetSourceViewModel.Factory = SelectWidgetSourceViewModel.Factory( vmParams = vmParams, urlBuilder = urlBuilder, @@ -71,6 +74,7 @@ object SelectWidgetSourceModule { analytics = analytics, getObjectTypes = getObjectTypes, dispatcher = dispatcher, - analyticSpaceHelperDelegate = analyticsHelper + analyticSpaceHelperDelegate = analyticsHelper, + fieldParser = fieldParser ) } \ 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 dc7eb98592..9772a29834 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 @@ -8,6 +8,7 @@ import com.anytypeio.anytype.di.feature.AppPreferencesDependencies import com.anytypeio.anytype.di.feature.BacklinkOrAddToObjectDependencies import com.anytypeio.anytype.di.feature.CreateBookmarkSubComponent import com.anytypeio.anytype.di.feature.CreateObjectSubComponent +import com.anytypeio.anytype.di.feature.DateObjectDependencies import com.anytypeio.anytype.di.feature.DebugSettingsSubComponent import com.anytypeio.anytype.di.feature.EditorSubComponent import com.anytypeio.anytype.di.feature.KeychainPhraseSubComponent @@ -133,7 +134,8 @@ interface MainComponent : SelectWidgetSourceDependencies, SelectWidgetTypeDependencies, LinkToObjectDependencies, - MoveToDependencies + MoveToDependencies, + DateObjectDependencies { fun inject(app: AndroidApplication) @@ -379,4 +381,9 @@ abstract class ComponentDependenciesModule { @IntoMap @ComponentDependenciesKey(MoveToDependencies::class) abstract fun provideMoveToDependencies(component: MainComponent): ComponentDependencies + + @Binds + @IntoMap + @ComponentDependenciesKey(DateObjectDependencies::class) + abstract fun provideDateObjectDependencies(component: MainComponent): ComponentDependencies } \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/di/main/UtilModule.kt b/app/src/main/java/com/anytypeio/anytype/di/main/UtilModule.kt index d62a7691dd..a6b091a683 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/main/UtilModule.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/main/UtilModule.kt @@ -14,6 +14,7 @@ import com.anytypeio.anytype.core_utils.tools.FeatureToggles import com.anytypeio.anytype.core_utils.tools.ThreadInfo import com.anytypeio.anytype.core_utils.tools.UrlValidator import com.anytypeio.anytype.device.providers.DateProviderImpl +import com.anytypeio.anytype.di.main.ConfigModule.DEFAULT_APP_COROUTINE_SCOPE import com.anytypeio.anytype.domain.config.Gateway import com.anytypeio.anytype.domain.debugging.DebugConfig import com.anytypeio.anytype.domain.debugging.Logger @@ -21,6 +22,9 @@ import com.anytypeio.anytype.domain.misc.DateProvider import com.anytypeio.anytype.domain.misc.DateTypeNameProvider import com.anytypeio.anytype.domain.misc.LocaleProvider import com.anytypeio.anytype.domain.misc.UrlBuilder +import com.anytypeio.anytype.domain.primitives.FieldParser +import com.anytypeio.anytype.domain.primitives.FieldParserImpl +import com.anytypeio.anytype.domain.vault.ObserveVaultSettings import com.anytypeio.anytype.middleware.interactor.MiddlewareProtobufLogger import com.anytypeio.anytype.middleware.interactor.ProtobufConverterProvider import com.anytypeio.anytype.other.BasicLogger @@ -30,7 +34,9 @@ import dagger.Binds import dagger.Module import dagger.Provides import java.time.ZoneId +import javax.inject.Named import javax.inject.Singleton +import kotlinx.coroutines.CoroutineScope @Module(includes = [UtilModule.Bindings::class]) object UtilModule { @@ -57,12 +63,23 @@ object UtilModule { @Provides @Singleton fun provideDateProvider( - localeProvider: LocaleProvider - ): DateProvider = - DateProviderImpl( - defaultZoneId = ZoneId.systemDefault(), - localeProvider = localeProvider - ) + localeProvider: LocaleProvider, + observeVaultSettings: ObserveVaultSettings, + @Named(DEFAULT_APP_COROUTINE_SCOPE) scope: CoroutineScope + ): DateProvider = DateProviderImpl( + defaultZoneId = ZoneId.systemDefault(), + localeProvider = localeProvider, + vaultSettings = observeVaultSettings, + scope = scope + ) + + @JvmStatic + @Provides + @Singleton + fun provideFieldsProvider( + dateProvider: DateProvider, + logger: Logger + ): FieldParser = FieldParserImpl(dateProvider, logger) @Module interface Bindings { @@ -97,6 +114,6 @@ object UtilModule { @Binds @Singleton - fun bindDateTypeNameProvider(provider: DefaultDateTypeNameProvider) : DateTypeNameProvider + fun bindDateTypeNameProvider(provider: DefaultDateTypeNameProvider): DateTypeNameProvider } } \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/navigation/Navigator.kt b/app/src/main/java/com/anytypeio/anytype/navigation/Navigator.kt index a8a26d6592..0c67246dc6 100644 --- a/app/src/main/java/com/anytypeio/anytype/navigation/Navigator.kt +++ b/app/src/main/java/com/anytypeio/anytype/navigation/Navigator.kt @@ -11,6 +11,7 @@ import com.anytypeio.anytype.presentation.navigation.AppNavigation import com.anytypeio.anytype.presentation.widgets.collection.Subscription import com.anytypeio.anytype.ui.allcontent.AllContentFragment import com.anytypeio.anytype.ui.auth.account.DeletedAccountFragment +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.relations.RelationCreateFromScratchForObjectFragment @@ -318,4 +319,16 @@ class Navigator : AppNavigation { ) ) } + + override fun openDateObject( + objectId: Id, + space: Id + ) { + navController?.navigate( + resId = R.id.dateObjectScreen, + args = DateObjectFragment.args( + objectId = objectId, + space = space) + ) + } } diff --git a/app/src/main/java/com/anytypeio/anytype/ui/allcontent/AllContentFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/allcontent/AllContentFragment.kt index 16eebbd36e..c37a9eea71 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/allcontent/AllContentFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/allcontent/AllContentFragment.kt @@ -259,6 +259,16 @@ class AllContentFragment : BaseComposeFragment(), ObjectTypeSelectionListener { Timber.e(it, "Failed to open relation editing screen from all content") } } + is AllContentViewModel.Command.NavigateToDateObject -> { + runCatching { + navigation().openDateObject( + objectId = command.objectId, + space = command.space + ) + }.onFailure { e -> + Timber.e(e, "Error while opening date object from All Objects screen") + } + } } } } diff --git a/app/src/main/java/com/anytypeio/anytype/ui/base/NavigationRouter.kt b/app/src/main/java/com/anytypeio/anytype/ui/base/NavigationRouter.kt index 4827609e40..91ab54b950 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/base/NavigationRouter.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/base/NavigationRouter.kt @@ -55,6 +55,10 @@ class NavigationRouter( typeId = command.typeId ) is AppNavigation.Command.MigrationErrorScreen -> navigation.migrationErrorScreen() + is AppNavigation.Command.OpenDateObject -> navigation.openDateObject( + objectId = command.objectId, + space = command.space + ) else -> Timber.d("Nav command ignored: $command") } } catch (e: Exception) { diff --git a/app/src/main/java/com/anytypeio/anytype/ui/date/DateObjectFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/date/DateObjectFragment.kt new file mode 100644 index 0000000000..69e68f6f18 --- /dev/null +++ b/app/src/main/java/com/anytypeio/anytype/ui/date/DateObjectFragment.kt @@ -0,0 +1,263 @@ +package com.anytypeio.anytype.ui.date + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.compose.material.MaterialTheme +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +import androidx.core.os.bundleOf +import androidx.fragment.app.viewModels +import androidx.fragment.compose.content +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import androidx.navigation.fragment.findNavController +import androidx.navigation.navOptions +import com.anytypeio.anytype.R +import com.anytypeio.anytype.core_models.Id +import com.anytypeio.anytype.core_models.ObjectWrapper +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_date.viewmodel.UiErrorState +import com.anytypeio.anytype.feature_date.viewmodel.DateObjectViewModel +import com.anytypeio.anytype.feature_date.viewmodel.DateObjectVMFactory +import com.anytypeio.anytype.feature_date.ui.DateMainScreen +import com.anytypeio.anytype.feature_date.viewmodel.DateObjectCommand +import com.anytypeio.anytype.feature_date.viewmodel.DateObjectVmParams +import com.anytypeio.anytype.ui.base.navigation +import com.anytypeio.anytype.ui.objects.creation.ObjectTypeSelectionFragment +import com.anytypeio.anytype.ui.objects.types.pickers.ObjectTypeSelectionListener +import com.anytypeio.anytype.ui.search.GlobalSearchFragment +import com.google.accompanist.navigation.material.ExperimentalMaterialNavigationApi +import com.google.accompanist.navigation.material.rememberBottomSheetNavigator +import javax.inject.Inject +import timber.log.Timber + +class DateObjectFragment : BaseComposeFragment(), ObjectTypeSelectionListener { + @Inject + lateinit var factory: DateObjectVMFactory + + private val vm by viewModels { factory } + private lateinit var navComposeController: NavHostController + + private val space get() = argString(ARG_SPACE) + private val objectId get() = argString(ARG_OBJECT_ID) + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ) = content { + MaterialTheme { + DateLayoutScreenWrapper() + ErrorScreen() + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + subscribe(vm.effects) { effect -> + Timber.d("Received date effect: $effect") + when (effect) { + DateObjectCommand.Back -> { + runCatching { + findNavController().popBackStack() + }.onFailure { e -> + Timber.e(e, "Error while exiting back from all content") + } + } + DateObjectCommand.ExitToVault -> { + runCatching { + findNavController().navigate(R.id.actionOpenVault) + }.onFailure { e -> + Timber.e(e, "Error while exiting to vault from all content") + } + } + is DateObjectCommand.NavigateToEditor -> { + runCatching { + navigation().openDocument( + target = effect.id, + space = effect.space.id + ) + }.onFailure { + toast("Failed to open document") + Timber.e(it, "Failed to open document from all content") + } + } + is DateObjectCommand.NavigateToSetOrCollection -> { + runCatching { + navigation().openObjectSet( + target = effect.id, + space = effect.space.id + ) + }.onFailure { + toast("Failed to open object set") + Timber.e(it, "Failed to open object set from all content") + } + } + is DateObjectCommand.OpenChat -> { + runCatching { + navigation().openChat( + target = effect.target, + space = effect.space.id + ) + }.onFailure { + Timber.e(it, "Failed to open a chat from all content") + } + } + DateObjectCommand.OpenGlobalSearch -> { + runCatching { + findNavController().navigate( + resId = R.id.globalSearchScreen, + args = GlobalSearchFragment.args( + space = space + ) + ) + }.onFailure { e -> + Timber.e(e, "Error while opening global search screen from all content") + } + } + is DateObjectCommand.NavigateToDateObject -> { + runCatching { + findNavController().navigate( + resId = R.id.dateObjectScreen, + args = args( + objectId = effect.objectId, + space = effect.space.id + ), + navOptions = navOptions { + launchSingleTop = true + } + ) + }.onFailure { + Timber.e(it, "Failed to navigate to date object") + } + } + + DateObjectCommand.ExitToSpaceWidgets -> { + runCatching { + findNavController().navigate(R.id.actionExitToSpaceWidgets) + }.onFailure { + Timber.e(it, "Error while opening space switcher from all-content screen") + } + } + is DateObjectCommand.SendToast.UnexpectedLayout -> { + toast("Unexpected layout") + } + DateObjectCommand.TypeSelectionScreen -> { + val dialog = ObjectTypeSelectionFragment.new(space = space) + dialog.show(childFragmentManager, null) + } + } + } + } + + override fun onSelectObjectType(objType: ObjectWrapper.Type) { + vm.onCreateObjectOfTypeClicked(objType = objType) + } + + override fun onStart() { + super.onStart() + vm.onStart() + } + + override fun onStop() { + super.onStop() + vm.onStop() + } + + @OptIn(ExperimentalMaterialNavigationApi::class) + @Composable + fun DateLayoutScreenWrapper() { + val bottomSheetNavigator = rememberBottomSheetNavigator() + navComposeController = rememberNavController(bottomSheetNavigator) + NavHost( + navController = navComposeController, + startDestination = DATE_MAIN + ) { + composable(route = DATE_MAIN) { + DateMainScreen( + uiCalendarIconState = vm.uiCalendarIconState.collectAsStateWithLifecycle().value, + uiSyncStatusBadgeState = vm.uiSyncStatusBadgeState.collectAsStateWithLifecycle().value, + uiHeaderState = vm.uiHeaderState.collectAsStateWithLifecycle().value, + uiFieldsState = vm.uiFieldsState.collectAsStateWithLifecycle().value, + uiObjectsListState = vm.uiObjectsListState.collectAsStateWithLifecycle().value, + uiNavigationWidget = vm.uiNavigationWidget.collectAsStateWithLifecycle().value, + uiFieldsSheetState = vm.uiFieldsSheetState.collectAsStateWithLifecycle().value, + uiContentState = vm.uiContentState.collectAsStateWithLifecycle().value, + canPaginate = vm.canPaginate.collectAsStateWithLifecycle().value, + uiCalendarState = vm.uiCalendarState.collectAsStateWithLifecycle().value, + uiSyncStatusState = vm.uiSyncStatusWidgetState.collectAsStateWithLifecycle().value, + onDateEvent = vm::onDateEvent + ) + } + } + } + + @OptIn(ExperimentalMaterial3Api::class) + @Composable + private fun ErrorScreen() { + val errorStateScreen = vm.errorState.collectAsStateWithLifecycle() + when (val state = errorStateScreen.value) { + UiErrorState.Hidden -> { + // Do nothing + } + is UiErrorState.Show -> { + val message = when (val r = state.reason) { + is UiErrorState.Reason.YearOutOfRange -> + stringResource( + id = R.string.date_layout_alert_date_out_of_range, + r.min, + r.max + ) + is UiErrorState.Reason.ErrorGettingFields -> r.msg + is UiErrorState.Reason.ErrorGettingObjects -> r.msg + is UiErrorState.Reason.Other -> r.msg + } + BaseAlertDialog( + dialogText = message, + buttonText = stringResource(id = R.string.membership_error_button_text_dismiss), + onButtonClick = vm::hideError, + onDismissRequest = vm::hideError + ) + } + } + } + + override fun injectDependencies() { + val params = DateObjectVmParams( + spaceId = SpaceId(space), + objectId = objectId + ) + componentManager().dateObjectComponent.get(params).inject(this) + } + + override fun releaseDependencies() { + componentManager().dateObjectComponent.release() + } + + override fun onApplyWindowRootInsets(view: View) { + // Do nothing. TODO add ime padding. + } + + companion object DateLayoutNavigation { + private const val DATE_MAIN = "date_main" + const val ARG_SPACE = "arg.date.object.space" + const val ARG_OBJECT_ID = "arg.date.object.object_id" + + fun args(space: Id, objectId: Id) = bundleOf( + ARG_SPACE to space, + ARG_OBJECT_ID to objectId + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/ui/editor/EditorFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/editor/EditorFragment.kt index ad1e9f6c1f..a245529218 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/editor/EditorFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/editor/EditorFragment.kt @@ -60,6 +60,7 @@ import com.anytypeio.anytype.core_ui.extensions.color import com.anytypeio.anytype.core_ui.extensions.cursorYBottomCoordinate import com.anytypeio.anytype.core_ui.features.editor.BlockAdapter import com.anytypeio.anytype.core_ui.features.editor.DragAndDropAdapterDelegate +import com.anytypeio.anytype.core_ui.features.editor.EditorDatePicker import com.anytypeio.anytype.core_ui.features.editor.scrollandmove.DefaultScrollAndMoveTargetDescriptor import com.anytypeio.anytype.core_ui.features.editor.scrollandmove.ScrollAndMoveStateListener import com.anytypeio.anytype.core_ui.features.editor.scrollandmove.ScrollAndMoveTargetHighlighter @@ -753,6 +754,16 @@ open class EditorFragment : NavigationFragment(R.layout.f } } + binding.editorDatePicker.apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + setContent { + EditorDatePicker( + uiState = vm.mentionDatePicker.collectAsStateWithLifecycle().value, + onEvent = vm::onEditorDatePickerEvent + ) + } + } + BottomSheetBehavior.from(binding.styleToolbarMain).state = BottomSheetBehavior.STATE_HIDDEN BottomSheetBehavior.from(binding.styleToolbarOther).state = BottomSheetBehavior.STATE_HIDDEN BottomSheetBehavior.from(binding.styleToolbarColors).state = diff --git a/app/src/main/java/com/anytypeio/anytype/ui/home/HomeScreenFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/home/HomeScreenFragment.kt index b66182dbea..71128ad84f 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/home/HomeScreenFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/home/HomeScreenFragment.kt @@ -520,6 +520,16 @@ class HomeScreenFragment : BaseComposeFragment(), is Navigation.OpenSpaceSwitcher -> { findNavController().navigate(R.id.actionOpenSpaceSwitcher) } + is Navigation.OpenDateObject -> { + runCatching { + navigation().openDateObject( + objectId = destination.ctx, + space = destination.space + ) + }.onFailure { e -> + Timber.e(e, "Error while opening date object from widgets") + } + } } } diff --git a/app/src/main/java/com/anytypeio/anytype/ui/main/MainActivity.kt b/app/src/main/java/com/anytypeio/anytype/ui/main/MainActivity.kt index 0aade3a6a7..602ee61251 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/main/MainActivity.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/main/MainActivity.kt @@ -16,6 +16,7 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import androidx.navigation.NavOptions +import androidx.navigation.NavOptions.* import androidx.navigation.findNavController import com.anytypeio.anytype.BuildConfig import com.anytypeio.anytype.R @@ -34,7 +35,6 @@ import com.anytypeio.anytype.core_utils.ext.toast import com.anytypeio.anytype.core_utils.insets.EDGE_TO_EDGE_MIN_SDK import com.anytypeio.anytype.core_utils.tools.FeatureToggles import com.anytypeio.anytype.di.common.componentManager -import com.anytypeio.anytype.di.feature.discussions.DiscussionFragment import com.anytypeio.anytype.domain.base.BaseUseCase import com.anytypeio.anytype.domain.theme.GetTheme import com.anytypeio.anytype.middleware.discovery.MDNSProvider @@ -50,6 +50,7 @@ import com.anytypeio.anytype.presentation.notifications.NotificationAction import com.anytypeio.anytype.presentation.notifications.NotificationCommand import com.anytypeio.anytype.presentation.wallpaper.WallpaperColor import com.anytypeio.anytype.presentation.wallpaper.WallpaperView +import com.anytypeio.anytype.ui.date.DateObjectFragment import com.anytypeio.anytype.ui.editor.CreateObjectFragment import com.anytypeio.anytype.ui.editor.EditorFragment import com.anytypeio.anytype.ui.gallery.GalleryInstallationFragment @@ -232,6 +233,22 @@ class MainActivity : AppCompatActivity(R.layout.activity_main), AppNavigation.Pr OpenObjectNavigation.NonValidObject -> { toast(getString(R.string.error_non_valid_object)) } + is OpenObjectNavigation.OpenDataObject -> { + runCatching { + findNavController(R.id.fragment).navigate( + R.id.dateObjectScreen, + args = DateObjectFragment.args( + objectId = dest.target, + space = dest.space + ), + navOptions = Builder() + .setPopUpTo(R.id.homeScreen, true) + .build() + ) + }.onFailure { + Timber.e(it, "Error while date object navigation") + } + } } } is Command.Deeplink.DeepLinkToObjectNotWorking -> { diff --git a/app/src/main/java/com/anytypeio/anytype/ui/settings/RemoteFilesManageFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/settings/RemoteFilesManageFragment.kt index 4deab6d891..c70d88a8c0 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/settings/RemoteFilesManageFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/settings/RemoteFilesManageFragment.kt @@ -104,6 +104,9 @@ class RemoteFilesManageFragment : BaseBottomSheetComposeFragment() { is CollectionViewModel.Command.ExitToSpaceWidgets -> { // Do nothing } + is CollectionViewModel.Command.OpenDateObject -> { + // Do nothing + } } } diff --git a/app/src/main/java/com/anytypeio/anytype/ui/sharing/SharingFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/sharing/SharingFragment.kt index 7801578839..c381478cfa 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/sharing/SharingFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/sharing/SharingFragment.kt @@ -17,7 +17,6 @@ import androidx.navigation.fragment.findNavController import com.anytypeio.anytype.R import com.anytypeio.anytype.core_utils.ext.arg import com.anytypeio.anytype.core_utils.ext.argStringList -import com.anytypeio.anytype.core_utils.ext.getFormattedDateTime import com.anytypeio.anytype.core_utils.ext.toast import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetComposeFragment import com.anytypeio.anytype.di.common.componentManager @@ -25,7 +24,6 @@ import com.anytypeio.anytype.presentation.home.OpenObjectNavigation import com.anytypeio.anytype.presentation.sharing.AddToAnytypeViewModel import com.anytypeio.anytype.ui.editor.EditorFragment import com.anytypeio.anytype.ui.settings.typography -import java.util.Locale import javax.inject.Inject import kotlinx.coroutines.flow.map diff --git a/app/src/main/java/com/anytypeio/anytype/ui/vault/VaultFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/vault/VaultFragment.kt index 21eccabdef..214be25b22 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/vault/VaultFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/vault/VaultFragment.kt @@ -30,7 +30,6 @@ import com.anytypeio.anytype.ui.gallery.GalleryInstallationFragment import com.anytypeio.anytype.ui.home.HomeScreenFragment import com.anytypeio.anytype.ui.multiplayer.RequestJoinSpaceFragment import com.anytypeio.anytype.ui.payments.MembershipFragment -import com.anytypeio.anytype.ui.settings.ProfileSettingsFragment import com.anytypeio.anytype.ui.settings.typography import javax.inject.Inject import timber.log.Timber @@ -168,6 +167,17 @@ class VaultFragment : BaseComposeFragment() { space = destination.space ) } + is Navigation.OpenDateObject -> { + runCatching { + findNavController().navigate(R.id.actionOpenSpaceFromVault) + navigation().openDateObject( + objectId = destination.ctx, + space = destination.space + ) + }.onFailure { e -> + Timber.e(e, "Error while opening date object from widgets") + } + } } } diff --git a/app/src/main/java/com/anytypeio/anytype/ui/widgets/collection/CollectionDI.kt b/app/src/main/java/com/anytypeio/anytype/ui/widgets/collection/CollectionDI.kt index 0c1bb1d3b6..c1d99cf855 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/widgets/collection/CollectionDI.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/widgets/collection/CollectionDI.kt @@ -28,6 +28,7 @@ import com.anytypeio.anytype.domain.objects.ObjectStore import com.anytypeio.anytype.domain.objects.SetObjectListIsArchived import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes import com.anytypeio.anytype.domain.page.CreateObject +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.search.ObjectSearchSubscriptionContainer import com.anytypeio.anytype.domain.search.SubscriptionEventChannel import com.anytypeio.anytype.domain.templates.GetTemplates @@ -141,8 +142,7 @@ object CollectionModule { fun getCreateObject( repo: BlockRepository, getDefaultObjectType: GetDefaultObjectType, - dispatchers: AppCoroutineDispatchers, - spaceManager: SpaceManager + dispatchers: AppCoroutineDispatchers ): CreateObject = CreateObject( repo = repo, getDefaultObjectType = getDefaultObjectType, @@ -155,8 +155,7 @@ object CollectionModule { fun provideGetDefaultPageType( userSettingsRepository: UserSettingsRepository, blockRepository: BlockRepository, - dispatchers: AppCoroutineDispatchers, - spaceManager: SpaceManager, + dispatchers: AppCoroutineDispatchers ): GetDefaultObjectType = GetDefaultObjectType( userSettingsRepository = userSettingsRepository, blockRepository = blockRepository, @@ -209,4 +208,5 @@ interface CollectionDependencies : ComponentDependencies { fun dateTypeNameProvider(): DateTypeNameProvider fun analyticsHelperDelegate(): AnalyticSpaceHelperDelegate fun provideUserPermissionProvider(): UserPermissionProvider + fun fieldParser(): FieldParser } \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/ui/widgets/collection/CollectionFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/widgets/collection/CollectionFragment.kt index 955119746a..8667a55ebc 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/widgets/collection/CollectionFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/widgets/collection/CollectionFragment.kt @@ -121,6 +121,16 @@ class CollectionFragment : BaseComposeFragment(), ObjectTypeSelectionListener { Timber.e(it, "Error while opening space switcher from full-screen widget") } } + is Command.OpenDateObject -> { + runCatching { + navigation().openDateObject( + objectId = command.target, + space = command.space + ) + }.onFailure { e -> + Timber.e(e, "Error while opening date object from Collection screen") + } + } } } diff --git a/app/src/main/java/com/anytypeio/anytype/ui/widgets/types/DataViewWidget.kt b/app/src/main/java/com/anytypeio/anytype/ui/widgets/types/DataViewWidget.kt index 12d5939991..73d30d35a3 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/widgets/types/DataViewWidget.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/widgets/types/DataViewWidget.kt @@ -51,13 +51,11 @@ import com.anytypeio.anytype.presentation.editor.cover.CoverGradient import com.anytypeio.anytype.presentation.editor.cover.CoverView import com.anytypeio.anytype.presentation.home.InteractionMode import com.anytypeio.anytype.presentation.objects.ObjectIcon -import com.anytypeio.anytype.presentation.objects.getProperName import com.anytypeio.anytype.presentation.widgets.DropDownMenuAction import com.anytypeio.anytype.presentation.widgets.ViewId import com.anytypeio.anytype.presentation.widgets.Widget import com.anytypeio.anytype.presentation.widgets.WidgetId import com.anytypeio.anytype.presentation.widgets.WidgetView -import com.anytypeio.anytype.presentation.widgets.getWidgetObjectName import com.anytypeio.anytype.ui.widgets.menu.WidgetMenu import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi import com.bumptech.glide.integration.compose.GlideImage @@ -104,13 +102,12 @@ fun DataViewListWidgetCard( .padding(horizontal = 0.dp, vertical = 6.dp) ) { WidgetHeader( - title = when (val source = item.source) { - is Widget.Source.Default -> { - source.obj.getWidgetObjectName() ?: stringResource(id = R.string.untitled) + title = when (val name = item.name) { + is WidgetView.Name.Default -> { + name.prettyPrintName ?: stringResource(id = R.string.untitled) } - - is Widget.Source.Bundled -> { - stringResource(id = source.res()) + is WidgetView.Name.Bundled -> { + stringResource(id = name.source.res()) } }, isCardMenuExpanded = isCardMenuExpanded, @@ -238,13 +235,12 @@ fun GalleryWidgetCard( .padding(horizontal = 0.dp, vertical = 6.dp) ) { WidgetHeader( - title = when (val source = item.source) { - is Widget.Source.Default -> { - source.obj.getWidgetObjectName() ?: stringResource(id = R.string.untitled) + title = when (val source = item.name) { + is WidgetView.Name.Default -> { + source.prettyPrintName ?: stringResource(id = R.string.untitled) } - - is Widget.Source.Bundled -> { - stringResource(id = source.res()) + is WidgetView.Name.Bundled -> { + stringResource(id = source.source.res()) } }, isCardMenuExpanded = isCardMenuExpanded, @@ -548,8 +544,14 @@ private fun GalleryWidgetItemCard( .padding(start = 12.dp, top = 9.dp), onTaskIconClicked = {} ) + val prettyPrintName = item.name.prettyPrintName + val name = if (prettyPrintName.isNullOrEmpty()) { + stringResource(id = R.string.untitled) + } else { + prettyPrintName + } Text( - text = item.obj.getProperName().ifEmpty { stringResource(id = R.string.untitled) }, + text = name, maxLines = 2, overflow = TextOverflow.Ellipsis, style = Caption1Medium, @@ -564,8 +566,14 @@ private fun GalleryWidgetItemCard( ) } } else { + val prettyPrintName = item.name.prettyPrintName + val name = if (prettyPrintName.isNullOrEmpty()) { + stringResource(id = R.string.untitled) + } else { + prettyPrintName + } Text( - text = item.obj.getProperName().ifEmpty { stringResource(id = R.string.untitled) }, + text = name, maxLines = 2, overflow = TextOverflow.Ellipsis, style = Caption1Medium, @@ -661,6 +669,9 @@ fun GalleryWidgetItemCardPreview() { map = mapOf( Relations.NAME to "Stephen Bann" ) + ), + name = WidgetView.Name.Default( + prettyPrintName = "Stephen Bann" ) ), onItemClicked = {} diff --git a/app/src/main/java/com/anytypeio/anytype/ui/widgets/types/LinkWidget.kt b/app/src/main/java/com/anytypeio/anytype/ui/widgets/types/LinkWidget.kt index 7411704f3d..2f73754f16 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/widgets/types/LinkWidget.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/widgets/types/LinkWidget.kt @@ -33,7 +33,6 @@ import com.anytypeio.anytype.core_ui.views.HeadlineSubheading import com.anytypeio.anytype.presentation.widgets.DropDownMenuAction import com.anytypeio.anytype.presentation.widgets.Widget import com.anytypeio.anytype.presentation.widgets.WidgetView -import com.anytypeio.anytype.presentation.widgets.getWidgetObjectName import com.anytypeio.anytype.ui.widgets.menu.WidgetMenu @OptIn(ExperimentalFoundationApi::class) @@ -89,12 +88,13 @@ fun LinkWidgetCard( .fillMaxWidth() .height(40.dp) ) { + Text( - text = when (val source = item.source) { - is Widget.Source.Default -> { - source.obj.getWidgetObjectName() ?: stringResource(id = R.string.untitled) + text = when(val name = item.name) { + is WidgetView.Name.Bundled -> stringResource(id = name.source.res()) + is WidgetView.Name.Default -> { + name.prettyPrintName ?: stringResource(id = R.string.untitled) } - is Widget.Source.Bundled -> { stringResource(id = source.res()) } }, maxLines = 1, overflow = TextOverflow.Ellipsis, diff --git a/app/src/main/java/com/anytypeio/anytype/ui/widgets/types/ListWidget.kt b/app/src/main/java/com/anytypeio/anytype/ui/widgets/types/ListWidget.kt index e6d190ce5f..206ed15137 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/widgets/types/ListWidget.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/widgets/types/ListWidget.kt @@ -33,7 +33,6 @@ import com.anytypeio.anytype.presentation.widgets.Widget import com.anytypeio.anytype.presentation.widgets.WidgetId import com.anytypeio.anytype.presentation.widgets.WidgetView import com.anytypeio.anytype.presentation.widgets.WidgetView.ListOfObjects.Type -import com.anytypeio.anytype.presentation.widgets.getWidgetObjectName import com.anytypeio.anytype.ui.widgets.menu.WidgetMenu @Composable @@ -168,8 +167,7 @@ fun CompactListWidgetList( } ) Text( - text = element.obj.getWidgetObjectName() - ?: stringResource(id = R.string.untitled), + text = element.name.prettyPrintName ?: stringResource(id = R.string.untitled), modifier = Modifier .padding(start = 8.dp) .fillMaxWidth(), diff --git a/app/src/main/java/com/anytypeio/anytype/ui/widgets/types/TreeWidget.kt b/app/src/main/java/com/anytypeio/anytype/ui/widgets/types/TreeWidget.kt index d0ff1f258c..8fdd6dfbe8 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/widgets/types/TreeWidget.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/widgets/types/TreeWidget.kt @@ -55,7 +55,6 @@ import com.anytypeio.anytype.presentation.widgets.TreePath import com.anytypeio.anytype.presentation.widgets.Widget import com.anytypeio.anytype.presentation.widgets.WidgetId import com.anytypeio.anytype.presentation.widgets.WidgetView -import com.anytypeio.anytype.presentation.widgets.getWidgetObjectName import com.anytypeio.anytype.ui.widgets.menu.WidgetMenu @Composable @@ -102,11 +101,11 @@ fun TreeWidgetCard( ) ) { WidgetHeader( - title = when (val source = item.source) { - is Widget.Source.Default -> { - source.obj.getWidgetObjectName() ?: stringResource(id = R.string.untitled) + title = when (val name = item.name) { + is WidgetView.Name.Default -> { + name.prettyPrintName ?: stringResource(id = R.string.untitled) } - is Widget.Source.Bundled -> { stringResource(id = source.res()) } + is WidgetView.Name.Bundled -> { stringResource(id = name.source.res()) } }, isCardMenuExpanded = isCardMenuExpanded, isHeaderMenuExpanded = isHeaderMenuExpanded, @@ -223,12 +222,12 @@ private fun TreeWidgetTreeItems( icon = element.objectIcon, modifier = Modifier.align(Alignment.CenterVertically).padding(start = 8.dp, end = 4.dp), onTaskIconClicked = { isChecked -> - onObjectCheckboxClicked(element.obj.id, isChecked) + onObjectCheckboxClicked(element.id, isChecked) } ) } Text( - text = element.obj.getWidgetObjectName() ?: stringResource(id = R.string.untitled), + text = element.name.prettyPrintName ?: stringResource(id = R.string.untitled), modifier = Modifier .padding(start = 8.dp) .fillMaxWidth(), diff --git a/app/src/main/res/layout/fragment_editor.xml b/app/src/main/res/layout/fragment_editor.xml index 0e591ff635..627be24bab 100644 --- a/app/src/main/res/layout/fragment_editor.xml +++ b/app/src/main/res/layout/fragment_editor.xml @@ -317,4 +317,9 @@ android:layout_height="wrap_content" android:layout_gravity="bottom|center_horizontal" /> + \ No newline at end of file diff --git a/app/src/main/res/navigation/graph.xml b/app/src/main/res/navigation/graph.xml index 42df6e45a6..5d1339a5fa 100644 --- a/app/src/main/res/navigation/graph.xml +++ b/app/src/main/res/navigation/graph.xml @@ -205,6 +205,16 @@ app:popUpToInclusive="false" /> + { + + + diff --git a/core-models/src/main/java/com/anytypeio/anytype/core_models/Block.kt b/core-models/src/main/java/com/anytypeio/anytype/core_models/Block.kt index 46de401252..8cca843a89 100644 --- a/core-models/src/main/java/com/anytypeio/anytype/core_models/Block.kt +++ b/core-models/src/main/java/com/anytypeio/anytype/core_models/Block.kt @@ -31,7 +31,6 @@ data class Block( val featuredRelations: List? by default val name: String? by default val iconEmoji: String? by default - val iconOption: Double? by default val coverId: Id? by default val coverType: Double? by default val iconImage: Id? get() = map.getSingleValue(Relations.ICON_IMAGE) @@ -42,7 +41,6 @@ data class Block( val done: Boolean? by default val lang: String? by default val fileExt: String? by default - val fileMimeType: String? by default val type: List get() = when (val value = map[TYPE_KEY]) { is String -> listOf(value) @@ -60,10 +58,6 @@ data class Block( else -> null } - - val analyticsContext: String? by default - val analyticsOriginalId: String? by default - companion object { fun empty(): Fields = Fields(emptyMap()) const val NAME_KEY = "name" diff --git a/core-models/src/main/java/com/anytypeio/anytype/core_models/Command.kt b/core-models/src/main/java/com/anytypeio/anytype/core_models/Command.kt index 960a0bafeb..15128a7a18 100644 --- a/core-models/src/main/java/com/anytypeio/anytype/core_models/Command.kt +++ b/core-models/src/main/java/com/anytypeio/anytype/core_models/Command.kt @@ -480,6 +480,10 @@ sealed class Command { data class DeleteRelationOptions(val optionIds: List) + data class RelationListWithValue(val space: SpaceId, val value: Any?) + + data class ObjectDateByTimestamp(val space: SpaceId, val timestamp: Long) + data class SendJoinSpaceRequest( val space: SpaceId, val network: Id?, diff --git a/core-models/src/main/java/com/anytypeio/anytype/core_models/Constants.kt b/core-models/src/main/java/com/anytypeio/anytype/core_models/Constants.kt index 4d39e378eb..9fc2070bf5 100644 --- a/core-models/src/main/java/com/anytypeio/anytype/core_models/Constants.kt +++ b/core-models/src/main/java/com/anytypeio/anytype/core_models/Constants.kt @@ -1,5 +1,7 @@ package com.anytypeio.anytype.core_models +import java.text.DateFormat + const val NO_VALUE = "" const val EMPTY_QUERY = "" @@ -16,4 +18,19 @@ const val DEFAULT_RELATIVE_DATES = true /** * The default value for showing the introduce vault. */ -const val DEFAULT_SHOW_INTRODUCE_VAULT = true \ No newline at end of file +const val DEFAULT_SHOW_INTRODUCE_VAULT = true + +/** + * The default value for showing the time + */ +const val DEFAULT_TIME_STYLE = DateFormat.DEFAULT + +/** + * The date range for the date picker. + */ +val DATE_PICKER_YEAR_RANGE = IntRange(0, 3000) + +/** + * The maximum size of a snippet. + */ +const val MAX_SNIPPET_SIZE = 30 \ No newline at end of file diff --git a/core-models/src/main/java/com/anytypeio/anytype/core_models/RelationLink.kt b/core-models/src/main/java/com/anytypeio/anytype/core_models/RelationLink.kt index 93897be1d1..09c1ce83ca 100644 --- a/core-models/src/main/java/com/anytypeio/anytype/core_models/RelationLink.kt +++ b/core-models/src/main/java/com/anytypeio/anytype/core_models/RelationLink.kt @@ -1,3 +1,10 @@ package com.anytypeio.anytype.core_models +import com.anytypeio.anytype.core_models.primitives.RelationKey + data class RelationLink(val key: Key, val format: RelationFormat) + +data class RelationListWithValueItem( + val key: RelationKey, + val counter: Long +) diff --git a/core-models/src/main/java/com/anytypeio/anytype/core_models/Relations.kt b/core-models/src/main/java/com/anytypeio/anytype/core_models/Relations.kt index e628c389af..5d989ee0ea 100644 --- a/core-models/src/main/java/com/anytypeio/anytype/core_models/Relations.kt +++ b/core-models/src/main/java/com/anytypeio/anytype/core_models/Relations.kt @@ -56,6 +56,8 @@ object Relations { const val SYNC_DATE = "syncDate" const val SYNC_STATUS = "syncStatus" const val IS_HIDDEN_DISCOVERY = "isHiddenDiscovery" + const val MENTIONS = "mentions" + const val TIMESTAMP = "timestamp" const val PAGE_COVER = "pageCover" diff --git a/core-models/src/main/java/com/anytypeio/anytype/core_models/RelativeDate.kt b/core-models/src/main/java/com/anytypeio/anytype/core_models/RelativeDate.kt new file mode 100644 index 0000000000..ea6345128c --- /dev/null +++ b/core-models/src/main/java/com/anytypeio/anytype/core_models/RelativeDate.kt @@ -0,0 +1,8 @@ +package com.anytypeio.anytype.core_models + +sealed class RelativeDate { + data object Today : RelativeDate() + data object Tomorrow : RelativeDate() + data object Yesterday : RelativeDate() + data class Other(val formattedDate: String, val formattedTime: String) : RelativeDate() +} \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/SupportedLayouts.kt b/core-models/src/main/java/com/anytypeio/anytype/core_models/SupportedLayouts.kt similarity index 84% rename from presentation/src/main/java/com/anytypeio/anytype/presentation/objects/SupportedLayouts.kt rename to core-models/src/main/java/com/anytypeio/anytype/core_models/SupportedLayouts.kt index b66b1a695c..bd443d83ab 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/SupportedLayouts.kt +++ b/core-models/src/main/java/com/anytypeio/anytype/core_models/SupportedLayouts.kt @@ -1,6 +1,4 @@ -package com.anytypeio.anytype.presentation.objects - -import com.anytypeio.anytype.core_models.ObjectType +package com.anytypeio.anytype.core_models object SupportedLayouts { val layouts = listOf( @@ -56,16 +54,26 @@ object SupportedLayouts { ObjectType.Layout.BOOKMARK ) + val dateLayouts = listOf( + ObjectType.Layout.DATE + ) + val addAsLinkToLayouts = editorLayouts + listOf( ObjectType.Layout.COLLECTION ) - val globalSearchLayouts = createObjectLayouts + fileLayouts + val globalSearchLayouts = createObjectLayouts + fileLayouts + dateLayouts + + val widgetsLayouts = layouts + dateLayouts fun isSupported(layout: ObjectType.Layout?) : Boolean { return layouts.contains(layout) } + fun isSupportedForWidgets(layout: ObjectType.Layout?) : Boolean { + return widgetsLayouts.contains(layout) + } + fun isEditorOrFileLayout(layout: ObjectType.Layout?) : Boolean { return editorLayouts.contains(layout) || fileLayouts.contains(layout) } diff --git a/core-models/src/main/java/com/anytypeio/anytype/core_models/exceptions/SpaceLimitReachedException.kt b/core-models/src/main/java/com/anytypeio/anytype/core_models/exceptions/SpaceLimitReachedException.kt deleted file mode 100644 index d30448aaf8..0000000000 --- a/core-models/src/main/java/com/anytypeio/anytype/core_models/exceptions/SpaceLimitReachedException.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.anytypeio.anytype.core_models.exceptions - -class SpaceLimitReachedException : Exception() \ No newline at end of file diff --git a/core-models/src/main/java/com/anytypeio/anytype/core_models/history/Version.kt b/core-models/src/main/java/com/anytypeio/anytype/core_models/history/Version.kt index 3923e27517..59eb82e378 100644 --- a/core-models/src/main/java/com/anytypeio/anytype/core_models/history/Version.kt +++ b/core-models/src/main/java/com/anytypeio/anytype/core_models/history/Version.kt @@ -4,14 +4,14 @@ import com.anytypeio.anytype.core_models.Event import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_models.ObjectView import com.anytypeio.anytype.core_models.Payload -import com.anytypeio.anytype.core_models.primitives.TimeInSeconds +import com.anytypeio.anytype.core_models.primitives.TimestampInSeconds data class Version( val id: Id, val previousIds: List, val spaceMember: Id, val spaceMemberName: String, - val timestamp: TimeInSeconds, + val timestamp: TimestampInSeconds, val groupId: Long ) diff --git a/core-models/src/main/java/com/anytypeio/anytype/core_models/primitives/FieldValues.kt b/core-models/src/main/java/com/anytypeio/anytype/core_models/primitives/FieldValues.kt new file mode 100644 index 0000000000..4219b6832e --- /dev/null +++ b/core-models/src/main/java/com/anytypeio/anytype/core_models/primitives/FieldValues.kt @@ -0,0 +1,23 @@ +package com.anytypeio.anytype.core_models.primitives + +import com.anytypeio.anytype.core_models.RelativeDate + +sealed class Value { + data class Single(val single: T) : Value() + data class Multiple(val multiple: List) : Value() +} + +sealed class Field(open val value: Value) { + data class Text(override val value: Value) : Field(value) + data class Date(override val value: Value.Single) : Field(value) { + val timestamp: TimestampInSeconds + get() = value.single.timestamp + val relativeDate: RelativeDate + get() = value.single.relativeDate + } +} + +data class FieldDateValue( + val timestamp: TimestampInSeconds, + val relativeDate: RelativeDate +) \ No newline at end of file diff --git a/core-models/src/main/java/com/anytypeio/anytype/core_models/primitives/Primitives.kt b/core-models/src/main/java/com/anytypeio/anytype/core_models/primitives/Primitives.kt index efed7c5803..dd3a8c1fff 100644 --- a/core-models/src/main/java/com/anytypeio/anytype/core_models/primitives/Primitives.kt +++ b/core-models/src/main/java/com/anytypeio/anytype/core_models/primitives/Primitives.kt @@ -22,7 +22,7 @@ value class RelationId(val id: String) value class RelationKey(val key: String) @JvmInline -value class TimeInSeconds(val time: Long) { +value class TimestampInSeconds(val time: Long) { val inMillis get() = time * 1000 } diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/common/ComposePreview.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/common/ComposePreview.kt index b3a00a11fd..49d2f588e0 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/common/ComposePreview.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/common/ComposePreview.kt @@ -8,12 +8,16 @@ import androidx.compose.ui.tooling.preview.Preview backgroundColor = 0xFFFFFFFF, showBackground = true, uiMode = UI_MODE_NIGHT_NO, - name = "Light Mode" + name = "Light Mode", + apiLevel = 34, + showSystemUi = true ) @Preview( backgroundColor = 0x000000, showBackground = true, uiMode = UI_MODE_NIGHT_YES, - name = "Dark Mode" + name = "Dark Mode", + apiLevel = 34, + showSystemUi = true ) annotation class DefaultPreviews \ No newline at end of file diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/common/Markup.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/common/Markup.kt index cdf7635543..ae92cd113f 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/common/Markup.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/common/Markup.kt @@ -469,6 +469,29 @@ fun Editable.proceedWithSettingMentionSpan( ) if (!mark.isArchived) setClickableSpan(click, mark) } + + is Markup.Mark.Mention.Date -> { + val placeholder = + ContextCompat.getDrawable( + context, + R.drawable.ic_obj_date_20 + ) + setSpan( + MentionSpan( + onImageResourceReady = onImageReady, + context = context, + imageSize = mentionImageSize, + imagePadding = mentionImagePadding, + param = mark.param, + placeholder = placeholder, + isArchived = false + ), + mark.from, + mark.to, + Markup.MENTION_SPANNABLE_FLAG + ) + setClickableSpan(click, mark) + } } } diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/common/ShimmerEffect.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/common/ShimmerEffect.kt new file mode 100644 index 0000000000..a26da7c00a --- /dev/null +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/common/ShimmerEffect.kt @@ -0,0 +1,66 @@ +package com.anytypeio.anytype.core_ui.common + +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.tween +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Spacer +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.res.colorResource +import com.anytypeio.anytype.core_ui.R + +@Composable +fun ShimmerEffect( + modifier: Modifier, + widthOfShadowBrush: Int = 500, + angleOfAxisY: Float = 270f, + durationMillis: Int = 1000, +) { + + val colorStart = colorResource(id = R.color.background_secondary) + val colorEnd = colorResource(id = R.color.shape_secondary) + + val shimmerColors = listOf( + colorStart, + colorEnd, + colorStart + ) + + val infiniteTransition = rememberInfiniteTransition(label = "") + + val translateAnimation = infiniteTransition.animateFloat( + initialValue = 0f, + targetValue = (durationMillis + widthOfShadowBrush).toFloat(), + animationSpec = infiniteRepeatable( + animation = tween( + durationMillis = durationMillis, + easing = LinearEasing, + ), + repeatMode = RepeatMode.Restart, + ), + label = "Shimmer loading animation", + ) + + val brush = Brush.linearGradient( + colors = shimmerColors, + start = Offset(x = translateAnimation.value - widthOfShadowBrush, y = 0.0f), + end = Offset(x = translateAnimation.value, y = angleOfAxisY), + ) + + Box( + modifier = modifier + ) { + Spacer( + modifier = Modifier + .matchParentSize() + .background(brush) + ) + } +} 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 c778b6b732..f771839968 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 @@ -1,6 +1,7 @@ package com.anytypeio.anytype.core_ui.extensions import android.content.Context +import android.content.res.Resources import android.graphics.drawable.Drawable import android.webkit.MimeTypeMap import androidx.annotation.ColorRes @@ -9,6 +10,7 @@ import androidx.annotation.StringRes import androidx.core.content.ContextCompat import com.anytypeio.anytype.core_models.DVSortType import com.anytypeio.anytype.core_models.RelationFormat +import com.anytypeio.anytype.core_models.RelativeDate import com.anytypeio.anytype.core_ui.R import com.anytypeio.anytype.core_utils.const.MimeTypes import com.anytypeio.anytype.presentation.objects.ObjectLayoutView @@ -291,4 +293,20 @@ fun RelationFormat.getPrettyName(): Int = when (this) { RelationFormat.OBJECT -> R.string.relation_format_object RelationFormat.RELATIONS -> R.string.relation_format_relation RelationFormat.UNDEFINED -> R.string.undefined +} + +fun RelativeDate.getPrettyName( + isTimeIncluded: Boolean = false, + resources: Resources +): String = when (this) { + is RelativeDate.Other -> { + if (isTimeIncluded) { + "$formattedDate $formattedTime" + } else { + formattedDate + } + } + is RelativeDate.Today -> resources.getString(R.string.today) + is RelativeDate.Tomorrow -> resources.getString(R.string.tomorrow) + is RelativeDate.Yesterday -> resources.getString(R.string.yesterday) } \ No newline at end of file diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/dataview/holders/DVGridCellDateHolder.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/dataview/holders/DVGridCellDateHolder.kt index 2245a93b34..8dbc328ed6 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/dataview/holders/DVGridCellDateHolder.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/dataview/holders/DVGridCellDateHolder.kt @@ -4,15 +4,16 @@ import android.view.View import android.widget.TextView import androidx.recyclerview.widget.RecyclerView import com.anytypeio.anytype.core_ui.R -import com.anytypeio.anytype.core_utils.ext.formatTimestamp +import com.anytypeio.anytype.core_ui.extensions.getPrettyName import com.anytypeio.anytype.presentation.sets.model.CellView class DVGridCellDateHolder(view: View) : RecyclerView.ViewHolder(view) { fun bind(cell: CellView.Date) { - itemView.findViewById(R.id.tvText).text = cell.timeInSecs?.formatTimestamp( - isMillis = false, - format = cell.dateFormat + val prettyDate = cell.relativeDate?.getPrettyName( + resources = itemView.resources, + isTimeIncluded = cell.isTimeIncluded ) + itemView.findViewById(R.id.tvText).text = prettyDate } } \ No newline at end of file diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/dataview/modals/FilterDateViewHolder.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/dataview/modals/FilterDateViewHolder.kt index f69e90cdda..f36baffe6b 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/dataview/modals/FilterDateViewHolder.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/dataview/modals/FilterDateViewHolder.kt @@ -5,8 +5,8 @@ import android.widget.TextView import com.anytypeio.anytype.core_models.DVFilterQuickOption import com.anytypeio.anytype.core_ui.R import com.anytypeio.anytype.core_ui.databinding.ItemDvViewerFilterDateBinding +import com.anytypeio.anytype.core_ui.extensions.getPrettyName import com.anytypeio.anytype.core_ui.widgets.RelationFormatIconWidget -import com.anytypeio.anytype.core_utils.ext.formatTimestamp import com.anytypeio.anytype.core_utils.ext.invisible import com.anytypeio.anytype.core_utils.ext.visible import com.anytypeio.anytype.presentation.extension.hasValue @@ -44,7 +44,10 @@ class FilterDateViewHolder(val binding: ItemDvViewerFilterDateBinding) : R.string.dates_days_from, value ) - DVFilterQuickOption.EXACT_DATE -> item.filterValue.value?.formatTimestamp(isMillis = false) + DVFilterQuickOption.EXACT_DATE -> { + val relativeDate = item.relativeDate + relativeDate?.getPrettyName(resources = resources) + } else -> item.quickOption.toName() } } else { diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/BlockAdapter.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/BlockAdapter.kt index fe26340fb7..4d865876b2 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/BlockAdapter.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/BlockAdapter.kt @@ -170,6 +170,7 @@ import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_PICTURE_UPLOAD import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_PROFILE_TITLE import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_RELATION_CHECKBOX +import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_RELATION_DATE import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_RELATION_DEFAULT import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_RELATION_DELETED import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_RELATION_FILE @@ -733,6 +734,10 @@ class BlockAdapter( val binding = ItemBlockRelationDefaultBinding.inflate(inflater, parent, false) RelationBlockViewHolder.Default(binding).setup(this) } + HOLDER_RELATION_DATE -> { + val binding = ItemBlockRelationDefaultBinding.inflate(inflater, parent, false) + RelationBlockViewHolder.Date(binding).setup(this) + } HOLDER_RELATION_PLACEHOLDER -> { RelationBlockViewHolder.Placeholder( ItemBlockRelationPlaceholderBinding.inflate(inflater, parent, false) @@ -1542,6 +1547,11 @@ class BlockAdapter( holder.bind(item = item.view) holder.bindHolder(item) } + is RelationBlockViewHolder.Date -> { + val item = (blocks[position] as BlockView.Relation.Related) + holder.bind(item = item.view) + holder.bindHolder(item) + } is RelationBlockViewHolder.Status -> { val item = (blocks[position] as BlockView.Relation.Related) holder.bind(item = item.view) diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/BlockViewDiffUtil.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/BlockViewDiffUtil.kt index 718a99015e..f3251dbe4a 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/BlockViewDiffUtil.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/BlockViewDiffUtil.kt @@ -229,6 +229,13 @@ class BlockViewDiffUtil( changes.add(RELATION_VALUE_CHANGED) } } + + newRelationView is ObjectRelationView.Date && oldRelationView is ObjectRelationView.Date -> { + if (newRelationView.relativeDate != oldRelationView.relativeDate + || newRelationView.isTimeIncluded != oldRelationView.isTimeIncluded) { + changes.add(RELATION_VALUE_CHANGED) + } + } } } diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/EditorDatePicker.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/EditorDatePicker.kt new file mode 100644 index 0000000000..254eb57ce2 --- /dev/null +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/EditorDatePicker.kt @@ -0,0 +1,62 @@ +package com.anytypeio.anytype.core_ui.features.editor + +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.unit.dp +import com.anytypeio.anytype.core_ui.R +import com.anytypeio.anytype.core_ui.relations.DatePickerContent +import com.anytypeio.anytype.presentation.editor.model.EditorDatePickerState +import com.anytypeio.anytype.presentation.editor.model.OnEditorDatePickerEvent +import com.anytypeio.anytype.presentation.sets.DateValueView + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun EditorDatePicker( + modifier: Modifier = Modifier, + uiState: EditorDatePickerState, + onEvent: (OnEditorDatePickerEvent) -> Unit +) { + + if (uiState !is EditorDatePickerState.Visible) return + + val bottomSheetState = rememberModalBottomSheetState( + skipPartiallyExpanded = true + ) + val keyboardController = LocalSoftwareKeyboardController.current + + LaunchedEffect(uiState) { + keyboardController?.hide() + } + + ModalBottomSheet( + modifier = modifier, + dragHandle = null, + scrimColor = colorResource(id = R.color.modal_screen_outside_background), + containerColor = colorResource(id = R.color.background_secondary), + shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp), + sheetState = bottomSheetState, + onDismissRequest = { onEvent(OnEditorDatePickerEvent.OnDatePickerDismiss) }, + content = { + DatePickerContent( + state = DateValueView(timeInMillis = null), + showHeader = false, + onDateSelected = { + onEvent( + OnEditorDatePickerEvent.OnDateSelected( + timeInMillis = it + ) + ) + }, + onTodayClicked = { onEvent(OnEditorDatePickerEvent.OnTodayClick) }, + onTomorrowClicked = { onEvent(OnEditorDatePickerEvent.OnTomorrowClick) } + ) + }, + ) +} \ No newline at end of file diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/holders/relations/ListRelationViewHolder.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/holders/relations/ListRelationViewHolder.kt index 499866544a..cd6c4f898b 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/holders/relations/ListRelationViewHolder.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/holders/relations/ListRelationViewHolder.kt @@ -8,12 +8,14 @@ import com.anytypeio.anytype.core_models.Relations import com.anytypeio.anytype.core_models.ThemeColor import com.anytypeio.anytype.core_ui.R import com.anytypeio.anytype.core_ui.databinding.ItemRelationListRelationCheckboxBinding +import com.anytypeio.anytype.core_ui.databinding.ItemRelationListRelationDateBinding import com.anytypeio.anytype.core_ui.databinding.ItemRelationListRelationDefaultBinding import com.anytypeio.anytype.core_ui.databinding.ItemRelationListRelationFileBinding import com.anytypeio.anytype.core_ui.databinding.ItemRelationListRelationObjectBinding import com.anytypeio.anytype.core_ui.databinding.ItemRelationListRelationStatusBinding import com.anytypeio.anytype.core_ui.databinding.ItemRelationListRelationTagBinding import com.anytypeio.anytype.core_ui.extensions.dark +import com.anytypeio.anytype.core_ui.extensions.getPrettyName import com.anytypeio.anytype.core_ui.extensions.setBlockBackgroundColor import com.anytypeio.anytype.core_ui.widgets.GridCellFileItem import com.anytypeio.anytype.core_ui.widgets.RelationObjectItem @@ -61,6 +63,28 @@ sealed class ListRelationViewHolder( } } + class Date(binding: ItemRelationListRelationDateBinding) : + ListRelationViewHolder(binding.root) { + + private val tvTitle = binding.content.tvRelationTitle + private val tvValue = binding.content.tvRelationValue + + fun bind(item: ObjectRelationView) { + tvTitle.text = item.name + val item = item as? ObjectRelationView.Date ?: return + val relativeDate = item.relativeDate + if (relativeDate != null) { + tvValue.text = relativeDate.getPrettyName( + resources = itemView.resources + ) + } else { + tvValue.text = null + tvValue.setHint(R.string.enter_date) + } + setLockIcon(tvTitle, item) + } + } + class Checkbox(binding: ItemRelationListRelationCheckboxBinding) : ListRelationViewHolder(binding.root) { diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/holders/relations/RelationBlockViewHolder.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/holders/relations/RelationBlockViewHolder.kt index ad6f25037d..5334e1fa93 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/holders/relations/RelationBlockViewHolder.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/holders/relations/RelationBlockViewHolder.kt @@ -30,6 +30,7 @@ import com.anytypeio.anytype.core_utils.ext.visible import com.anytypeio.anytype.core_models.ThemeColor import com.anytypeio.anytype.core_ui.databinding.ItemBlockRelationDeletedBinding import com.anytypeio.anytype.core_ui.extensions.clearDrawable +import com.anytypeio.anytype.core_ui.extensions.getPrettyName import com.anytypeio.anytype.core_ui.extensions.setDrawable import com.anytypeio.anytype.core_ui.features.editor.BlockViewDiffUtil import com.anytypeio.anytype.core_utils.ext.readableFileSize @@ -473,6 +474,42 @@ sealed class RelationBlockViewHolder( } } + class Date(binding: ItemBlockRelationDefaultBinding) : + RelationBlockViewHolder(binding.root) { + + private val tvTitle = binding.tvRelationTitle + private val tvValue = binding.tvRelationValue + override val content = binding.content + override val selected = binding.selected + override val relationName: TextView = tvTitle + override val decoratableContainer: EditorDecorationContainer = binding.decorationContainer + + fun bind(item: ObjectRelationView) { + applyRelationName(item.name, item.readOnly) + applyRelationValue(item) + } + + override fun indentize(item: BlockView.Indentable) { + indent(item, itemView) + } + + override fun applyDecorations(decorations: List) { + super.applyContentDecorations(itemView, decorations) + } + + override fun applyRelationValue(item: ObjectRelationView) { + val item = item as? ObjectRelationView.Date ?: return + val relativeDate = item.relativeDate + if (relativeDate != null) { + tvValue.text = relativeDate.getPrettyName( + resources = itemView.resources + ) + } else { + tvValue.text = null + tvValue.setHint(R.string.enter_date) + } + } + } private fun TextView.setReadOnly(isReadOnly: Boolean) { if (isReadOnly) { diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/slash/SlashRelationsAdapter.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/slash/SlashRelationsAdapter.kt index 224e8c93f9..7dee364553 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/slash/SlashRelationsAdapter.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/slash/SlashRelationsAdapter.kt @@ -9,6 +9,7 @@ import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import com.anytypeio.anytype.core_ui.R import com.anytypeio.anytype.core_ui.databinding.ItemRelationListRelationCheckboxBinding +import com.anytypeio.anytype.core_ui.databinding.ItemRelationListRelationDateBinding import com.anytypeio.anytype.core_ui.databinding.ItemRelationListRelationDefaultBinding import com.anytypeio.anytype.core_ui.databinding.ItemRelationListRelationFileBinding import com.anytypeio.anytype.core_ui.databinding.ItemRelationListRelationObjectBinding @@ -47,6 +48,18 @@ class SlashRelationsAdapter( } } } + R.layout.item_relation_list_relation_date -> { + val binding = + ItemRelationListRelationDateBinding.inflate(inflater, parent, false) + ListRelationViewHolder.Date(binding).apply { + updateHeight(binding.root) + updatePadding(binding.root, defaultPadding) + binding.divider.visible() + itemView.setOnClickListener { + onItemClicked(bindingAdapterPosition) + } + } + } R.layout.item_relation_list_relation_checkbox -> { val binding = ItemRelationListRelationCheckboxBinding.inflate(inflater, parent, false) @@ -196,6 +209,12 @@ class SlashRelationsAdapter( check(view is ObjectRelationView.Default) holder.bind(view) } + is ListRelationViewHolder.Date -> { + check(item is SlashRelationView.Item) + val view = item.view + check(view is ObjectRelationView.Date) + holder.bind(view) + } is RelationsSubheaderMenuHolder -> { check(item is SlashRelationView.Section) holder.bind(item) @@ -216,6 +235,7 @@ class SlashRelationsAdapter( is ObjectRelationView.Status -> R.layout.item_relation_list_relation_status is ObjectRelationView.Tags -> R.layout.item_relation_list_relation_tag is ObjectRelationView.File -> R.layout.item_relation_list_relation_file + is ObjectRelationView.Date -> R.layout.item_relation_list_relation_date else -> R.layout.item_relation_list_relation_default } } diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/history/VersionHistoryMainScreen.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/history/VersionHistoryMainScreen.kt index 18a59e5adb..1e2548e5e4 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/history/VersionHistoryMainScreen.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/history/VersionHistoryMainScreen.kt @@ -44,7 +44,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.anytypeio.anytype.core_models.primitives.TimeInSeconds +import com.anytypeio.anytype.core_models.primitives.TimestampInSeconds import com.anytypeio.anytype.core_ui.R import com.anytypeio.anytype.core_ui.foundation.Dragger import com.anytypeio.anytype.core_ui.foundation.Header @@ -367,7 +367,7 @@ private fun SpaceListScreenPreview() { spaceMemberName = "John Doe", icon = ObjectIcon.Profile.Avatar("A"), spaceMember = "1", - timeStamp = TimeInSeconds(23423423L), + timeStamp = TimestampInSeconds(23423423L), versions = emptyList(), dateFormatted = "Today", @@ -378,7 +378,7 @@ private fun SpaceListScreenPreview() { spaceMemberName = "Alice Doe", icon = ObjectIcon.Profile.Avatar("B"), spaceMember = "1", - timeStamp = TimeInSeconds(23423423L), + timeStamp = TimestampInSeconds(23423423L), versions = emptyList(), dateFormatted = "Today", ), @@ -388,7 +388,7 @@ private fun SpaceListScreenPreview() { spaceMemberName = "Bob Doe", icon = ObjectIcon.Profile.Avatar("C"), spaceMember = "1", - timeStamp = TimeInSeconds(23423423L), + timeStamp = TimestampInSeconds(23423423L), versions = emptyList(), dateFormatted = "Today", ), diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/navigation/PageLinksAdapter.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/navigation/PageLinksAdapter.kt index fe44ad6993..6da48269b3 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/navigation/PageLinksAdapter.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/navigation/PageLinksAdapter.kt @@ -11,10 +11,13 @@ import com.anytypeio.anytype.core_ui.R import com.anytypeio.anytype.core_ui.databinding.ItemListObjectBinding import com.anytypeio.anytype.core_ui.databinding.ItemSearchNewObjectBinding import com.anytypeio.anytype.core_ui.widgets.ObjectIconWidget +import com.anytypeio.anytype.core_utils.ext.gone +import com.anytypeio.anytype.core_utils.ext.visible import com.anytypeio.anytype.core_utils.ui.setOnThrottleClickListener import com.anytypeio.anytype.presentation.navigation.DefaultObjectView import com.anytypeio.anytype.presentation.navigation.DefaultSearchItem import com.anytypeio.anytype.presentation.navigation.NewObject +import com.anytypeio.anytype.presentation.objects.ObjectIcon import com.anytypeio.anytype.presentation.search.ObjectSearchSection import com.anytypeio.anytype.presentation.widgets.source.BundledWidgetSourceView @@ -85,7 +88,7 @@ class DefaultObjectViewAdapter( holder.title.setText(R.string.widget_source_anytype_library) } ObjectSearchSection.SelectWidgetSource.FromMyObjects -> { - holder.title.setText(R.string.objects) + holder.title.setText(R.string.your_objects) } ObjectSearchSection.SelectWidgetSource.DefaultLists -> { holder.title.setText(R.string.widget_source_default_lists) @@ -129,9 +132,20 @@ class ObjectItemViewHolder(view: View) : ObjectViewHolder(view) { fun bind(link: DefaultObjectView) { title.text = link.name - subtitle.text = link.typeName + if (link.typeName != null) { + subtitle.visible() + subtitle.text = link.typeName + } else { + subtitle.gone() + } icon.setIcon(link.icon) } + + fun bindSelectDateItem() { + title.setText(R.string.select_date) + subtitle.gone() + icon.setIcon(ObjectIcon.Empty.Date) + } } private fun inflate( @@ -168,7 +182,7 @@ class BundledWidgetSourceHolder( BundledWidgetSourceView.Favorites -> { with(binding) { tvTitle.setText(R.string.favorites) - tvSubtitle.text = "" + tvSubtitle.gone() ivIcon.setImageDrawable( drawable = binding.root.context.resources.getDrawable( R.drawable.ic_widget_bundled_source_favorites, @@ -181,6 +195,7 @@ class BundledWidgetSourceHolder( BundledWidgetSourceView.Recent -> { with(binding) { tvTitle.setText(R.string.recent) + tvSubtitle.gone() ivIcon.setImageDrawable( drawable = binding.root.context.resources.getDrawable( R.drawable.ic_widget_bundled_source_recently_edited, @@ -193,6 +208,7 @@ class BundledWidgetSourceHolder( BundledWidgetSourceView.RecentLocal -> { with(binding) { tvTitle.setText(R.string.recently_opened) + tvSubtitle.visible() tvSubtitle.setText(R.string.on_this_device) ivIcon.setImageDrawable( drawable = binding.root.context.resources.getDrawable( @@ -206,7 +222,7 @@ class BundledWidgetSourceHolder( BundledWidgetSourceView.Sets -> { with(binding) { tvTitle.setText(R.string.sets) - tvSubtitle.text = "" + tvSubtitle.gone() ivIcon.setImageDrawable( drawable = binding.root.context.resources.getDrawable( R.drawable.ic_widget_bundled_source_sets, @@ -218,7 +234,7 @@ class BundledWidgetSourceHolder( BundledWidgetSourceView.Collections -> { with(binding) { tvTitle.setText(R.string.collections) - tvSubtitle.text = "" + tvSubtitle.gone() ivIcon.setImageDrawable( drawable = binding.root.context.resources.getDrawable( R.drawable.ic_widget_bundled_source_collection, diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/relations/DocumentRelationAdapter.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/relations/DocumentRelationAdapter.kt index 407e68e4ca..a5f60473d5 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/relations/DocumentRelationAdapter.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/relations/DocumentRelationAdapter.kt @@ -8,6 +8,7 @@ import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import com.anytypeio.anytype.core_ui.R import com.anytypeio.anytype.core_ui.databinding.ItemRelationListRelationCheckboxBinding +import com.anytypeio.anytype.core_ui.databinding.ItemRelationListRelationDateBinding import com.anytypeio.anytype.core_ui.databinding.ItemRelationListRelationDefaultBinding import com.anytypeio.anytype.core_ui.databinding.ItemRelationListRelationFileBinding import com.anytypeio.anytype.core_ui.databinding.ItemRelationListRelationObjectBinding @@ -131,6 +132,22 @@ class DocumentRelationAdapter( R.layout.item_relation_list_section -> { SectionViewHolder(view = inflater.inflate(viewType, parent, false)) } + R.layout.item_relation_list_relation_date -> { + val binding = + ItemRelationListRelationDateBinding.inflate(inflater, parent, false) + ListRelationViewHolder.Date(binding = binding).apply { + binding.featuredRelationCheckbox.visible() + itemView.setOnClickListener { + relationClicked(bindingAdapterPosition) + } + binding.featuredRelationCheckbox.setOnClickListener { + checkboxClicked(bindingAdapterPosition) + } + binding.ivActionDelete.setOnClickListener { + deleteClicked(bindingAdapterPosition) + } + } + } else -> throw IllegalStateException("Unexpected view type: $viewType") } } @@ -224,6 +241,12 @@ class DocumentRelationAdapter( check(view is ObjectRelationView.Default) holder.bind(view) } + is ListRelationViewHolder.Date -> { + check(item is RelationListViewModel.Model.Item) + val view = item.view + check(view is ObjectRelationView.Date) + holder.bind(view) + } is SectionViewHolder -> { check(item is RelationListViewModel.Model.Section) holder.bind(item) @@ -249,6 +272,7 @@ class DocumentRelationAdapter( is ObjectRelationView.Status -> R.layout.item_relation_list_relation_status is ObjectRelationView.Tags -> R.layout.item_relation_list_relation_tag is ObjectRelationView.File -> R.layout.item_relation_list_relation_file + is ObjectRelationView.Date -> R.layout.item_relation_list_relation_date else -> R.layout.item_relation_list_relation_default } } diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/sets/CreateFilterAdapter.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/sets/CreateFilterAdapter.kt index 27678600f2..e87a6291a1 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/sets/CreateFilterAdapter.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/sets/CreateFilterAdapter.kt @@ -13,8 +13,8 @@ import com.anytypeio.anytype.core_ui.databinding.ItemCreateFilterObjectBinding import com.anytypeio.anytype.core_ui.databinding.ItemCreateFilterStatusBinding import com.anytypeio.anytype.core_ui.databinding.ItemCreateFilterTagBinding import com.anytypeio.anytype.core_ui.extensions.dark +import com.anytypeio.anytype.core_ui.extensions.getPrettyName import com.anytypeio.anytype.core_ui.extensions.light -import com.anytypeio.anytype.core_utils.ext.formatTimestamp import com.anytypeio.anytype.core_utils.ext.invisible import com.anytypeio.anytype.core_utils.ext.setDrawableColor import com.anytypeio.anytype.core_utils.ext.visible @@ -175,7 +175,9 @@ class CreateFilterAdapter( when (item.type) { DVFilterQuickOption.EXACT_DATE -> { tvDate.visible() - tvDate.text = item.value.formatTimestamp(isMillis = false) + tvDate.text = item.relativeDate?.getPrettyName( + resources = itemView.resources + ) } DVFilterQuickOption.DAYS_AGO, DVFilterQuickOption.DAYS_AHEAD -> { tvDate.visible() diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/relations/DatePickerContent.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/relations/DatePickerContent.kt index 78aad08564..5ac6eedd64 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/relations/DatePickerContent.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/relations/DatePickerContent.kt @@ -29,6 +29,7 @@ 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 com.anytypeio.anytype.core_models.DATE_PICKER_YEAR_RANGE import com.anytypeio.anytype.core_ui.R import com.anytypeio.anytype.core_ui.foundation.Divider import com.anytypeio.anytype.core_ui.foundation.Dragger @@ -37,14 +38,13 @@ import com.anytypeio.anytype.core_ui.views.Title1 import com.anytypeio.anytype.core_ui.views.UXBody import com.anytypeio.anytype.presentation.sets.DateValueView -val DATE_RANGE = IntRange(0, 3000) - @OptIn(ExperimentalMaterial3Api::class) @Composable fun DatePickerContent( state: DateValueView, + showHeader: Boolean = true, onDateSelected: (Long?) -> Unit, - onClear: () -> Unit, + onClear: () -> Unit = {}, onTodayClicked: () -> Unit, onTomorrowClicked: () -> Unit ) { @@ -56,7 +56,7 @@ fun DatePickerContent( return state.isEditable } }, - yearRange = DATE_RANGE + yearRange = DATE_PICKER_YEAR_RANGE ) val isFirstLoad = remember { mutableStateOf(true) } @@ -78,7 +78,11 @@ fun DatePickerContent( shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp) ) ) { - Header(state = state, onClear = onClear) + CalendarDragger() + + if (showHeader) { + Header(state = state, onClear = onClear) + } val todayContentColor = if (state.isEditable) { colorResource(id = R.color.glyph_accent) @@ -144,17 +148,18 @@ fun DatePickerContent( } @Composable -private fun Header(state: DateValueView, onClear: () -> Unit) { - - // Dragger at the top, centered +private fun CalendarDragger(modifier: Modifier = Modifier) { Box( - modifier = Modifier + modifier = modifier .fillMaxWidth() .padding(vertical = 6.dp) ) { Dragger(modifier = Modifier.align(Alignment.Center)) } +} +@Composable +private fun Header(state: DateValueView, onClear: () -> Unit) { // Main content box Box( modifier = Modifier diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/syncstatus/StatusBadge.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/syncstatus/StatusBadge.kt new file mode 100644 index 0000000000..dee8066c76 --- /dev/null +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/syncstatus/StatusBadge.kt @@ -0,0 +1,167 @@ +package com.anytypeio.anytype.core_ui.syncstatus + +import androidx.compose.animation.core.FastOutSlowInEasing +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.tween +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp +import com.anytypeio.anytype.core_models.multiplayer.SpaceSyncAndP2PStatusState +import com.anytypeio.anytype.core_models.multiplayer.SpaceSyncError +import com.anytypeio.anytype.core_models.multiplayer.SpaceSyncStatus +import com.anytypeio.anytype.core_models.multiplayer.SpaceSyncUpdate +import com.anytypeio.anytype.core_ui.R +import androidx.compose.runtime.getValue +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.colorResource +import com.anytypeio.anytype.core_models.multiplayer.P2PStatusUpdate +import com.anytypeio.anytype.core_models.multiplayer.SpaceSyncNetwork +import com.anytypeio.anytype.core_ui.common.DefaultPreviews + +@Composable +fun StatusBadge( + status: SpaceSyncAndP2PStatusState?, + modifier: Modifier = Modifier.size(20.dp) +) { + when (status) { + is SpaceSyncAndP2PStatusState.Error -> { + Image( + painter = painterResource(R.drawable.ic_sync_error_10), + contentDescription = null, + modifier = modifier + ) + } + + SpaceSyncAndP2PStatusState.Init -> {} + is SpaceSyncAndP2PStatusState.Success -> { + when (val spaceSyncUpdate = status.spaceSyncUpdate) { + SpaceSyncUpdate.Initial -> {} + is SpaceSyncUpdate.Update -> { + if (spaceSyncUpdate.error != SpaceSyncError.NULL) { + Image( + painter = painterResource(R.drawable.ic_sync_error_10), + contentDescription = null, + modifier = modifier + ) + } else { + when (spaceSyncUpdate.status) { + SpaceSyncStatus.SYNCED -> { + Image( + painter = painterResource(R.drawable.ic_synced_10), + contentDescription = "Synced", + modifier = modifier.size(8.dp), + contentScale = ContentScale.Inside + ) + } + + SpaceSyncStatus.SYNCING -> { + PulsatingCircle( + color = colorResource(R.color.palette_system_green), // Replace with your color resource + modifier = modifier.size(24.dp) + ) + } + + SpaceSyncStatus.ERROR -> { + Image( + painter = painterResource(R.drawable.ic_sync_error_10), + contentDescription = "Sync Error", + modifier = modifier + ) + } + + SpaceSyncStatus.OFFLINE -> { + Image( + painter = painterResource(R.drawable.ic_sync_grey_10), + contentDescription = "Offline", + modifier = modifier + ) + } + + SpaceSyncStatus.NETWORK_UPDATE_NEEDED -> { + Image( + painter = painterResource(R.drawable.ic_sync_slow_10), + contentDescription = "Network Update Needed", + modifier = modifier + ) + } + } + } + } + } + } + + null -> {} + } +} + +@Composable +fun PulsatingCircle( + color: Color, + modifier: Modifier = Modifier.size(20.dp) +) { + val infiniteTransition = rememberInfiniteTransition(label = "sync dot animation") + + val scale by infiniteTransition.animateFloat( + initialValue = 0.0f, + targetValue = 1f, + animationSpec = infiniteRepeatable( + animation = tween(600, easing = FastOutSlowInEasing), + repeatMode = RepeatMode.Reverse + ), label = "sync dot animation" + ) + + val density = LocalDensity.current + val centerRadius = with(density) { 4.dp.toPx() } + + // Outer circles' base radii + val middleRadius = centerRadius + with(density) { 3.dp.toPx() } // 7 dp radius + val outerRadius = centerRadius + with(density) { 6.dp.toPx() } // 10 dp radius + + Canvas(modifier = modifier) { + + // Apply scaling to the outer circles + drawCircle( + color = color.copy(alpha = 0.2f), + radius = outerRadius * scale, + center = center + ) + drawCircle( + color = color.copy(alpha = 0.4f), + radius = middleRadius * scale, + center = center + ) + + // Static center circle + drawCircle( + color = color, + radius = centerRadius, + center = center + ) + } +} + +@DefaultPreviews +@Composable +fun StatusBadgePreview() { + StatusBadge( + status = SpaceSyncAndP2PStatusState.Success( + spaceSyncUpdate = SpaceSyncUpdate.Update( + id = "1", + status = SpaceSyncStatus.SYNCED, + network = SpaceSyncNetwork.ANYTYPE, + error = SpaceSyncError.NULL, + syncingObjectsCounter = 2 + ), + p2PStatusUpdate = P2PStatusUpdate.Initial + ) + ) +} \ No newline at end of file diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/ObjectIconCompose.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/ObjectIconCompose.kt index 8217ccc7db..0362dc318f 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/ObjectIconCompose.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/ObjectIconCompose.kt @@ -201,5 +201,6 @@ fun imageAsset(emptyType: ObjectIcon.Empty): Int { ObjectIcon.Empty.List -> R.drawable.ic_empty_state_list ObjectIcon.Empty.ObjectType -> R.drawable.ic_empty_state_type ObjectIcon.Empty.Page -> R.drawable.ic_empty_state_page + ObjectIcon.Empty.Date -> R.drawable.ic_obj_date_24 } } \ No newline at end of file diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/ObjectIconWidget.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/ObjectIconWidget.kt index 206ad64144..2ade1ca5e8 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/ObjectIconWidget.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/ObjectIconWidget.kt @@ -31,7 +31,7 @@ class ObjectIconWidget @JvmOverloads constructor( ) : FrameLayout(context, attrs, defStyleAttr) { companion object { - const val DEFAULT_SIZE = 24 + const val DEFAULT_SIZE = 28 } val binding = WidgetObjectIconBinding.inflate( @@ -362,12 +362,13 @@ class ObjectIconWidget @JvmOverloads constructor( } private fun ObjectIcon.Empty.setEmptyIcon() { - val drawable = when (this) { - ObjectIcon.Empty.Bookmark -> R.drawable.ic_empty_state_link - ObjectIcon.Empty.Discussion -> R.drawable.ic_empty_state_chat - ObjectIcon.Empty.List -> R.drawable.ic_empty_state_list - ObjectIcon.Empty.ObjectType -> R.drawable.ic_empty_state_type - ObjectIcon.Empty.Page -> R.drawable.ic_empty_state_page + val (drawable, containerBackground) = when (this) { + ObjectIcon.Empty.Bookmark -> R.drawable.ic_empty_state_link to true + ObjectIcon.Empty.Discussion -> R.drawable.ic_empty_state_chat to true + ObjectIcon.Empty.List -> R.drawable.ic_empty_state_list to true + ObjectIcon.Empty.ObjectType -> R.drawable.ic_empty_state_type to true + ObjectIcon.Empty.Page -> R.drawable.ic_empty_state_page to true + ObjectIcon.Empty.Date -> R.drawable.ic_obj_date_24 to false } val icon = context.drawable(drawable) with(binding) { @@ -377,7 +378,12 @@ class ObjectIconWidget @JvmOverloads constructor( ivImage.invisible() ivBookmark.setImageDrawable(null) ivBookmark.gone() - emojiContainer.visible() + if (containerBackground) { + emojiContainer.visible() + } else { + emojiContainer.visible() + emojiContainer.setBackgroundResource(0) + } } } diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/RelationValueListWidget.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/RelationValueListWidget.kt index c77318a2bb..a493997295 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/RelationValueListWidget.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/RelationValueListWidget.kt @@ -11,6 +11,7 @@ import com.anytypeio.anytype.core_models.ThemeColor import com.anytypeio.anytype.core_ui.R import com.anytypeio.anytype.core_ui.databinding.RelationValueListBinding import com.anytypeio.anytype.core_ui.extensions.dark +import com.anytypeio.anytype.core_ui.extensions.getPrettyName import com.anytypeio.anytype.core_ui.extensions.light import com.anytypeio.anytype.core_utils.ext.gone import com.anytypeio.anytype.core_utils.ext.setDrawableColor @@ -64,6 +65,7 @@ class RelationValueListWidget @JvmOverloads constructor( is ObjectRelationView.ObjectType -> setObjectTypeRelation(relation) is ObjectRelationView.Links -> setLinksRelation(relation) is ObjectRelationView.Source -> setSourceRelation(relation, clickListener) + is ObjectRelationView.Date -> setDateRelation(relation) } if (!isLast) dot.visible() } @@ -83,6 +85,26 @@ class RelationValueListWidget @JvmOverloads constructor( } //endregion + //region DATE + private fun setDateRelation(relation: ObjectRelationView.Date) { + val relativeDate = relation.relativeDate + if (relativeDate != null) { + val formattedDate = relativeDate.getPrettyName( + resources = resources + ) + setupSingleTextItem( + name = formattedDate, + textColor = textColorPrimary + ) + } else { + setupSingleTextItem( + name = relation.name, + textColor =textColorSecondary + ) + } + } + //endregion + //region OBJECTS private fun setObjectRelation(relation: ObjectRelationView.Object) { when { diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/dv/GalleryViewContentWidget.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/dv/GalleryViewContentWidget.kt index e3fefbf44f..73b1042ddb 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/dv/GalleryViewContentWidget.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/dv/GalleryViewContentWidget.kt @@ -17,8 +17,8 @@ import androidx.core.view.updateLayoutParams import com.anytypeio.anytype.core_models.ThemeColor import com.anytypeio.anytype.core_ui.R import com.anytypeio.anytype.core_ui.extensions.dark +import com.anytypeio.anytype.core_ui.extensions.getPrettyName import com.anytypeio.anytype.core_ui.extensions.light -import com.anytypeio.anytype.core_utils.ext.formatTimestamp import com.anytypeio.anytype.core_utils.ext.setDrawableColor import com.anytypeio.anytype.emojifier.Emojifier import com.anytypeio.anytype.presentation.objects.ObjectIcon @@ -158,9 +158,9 @@ class GalleryViewContentWidget @JvmOverloads constructor( isSingleLine = true maxLines = 1 ellipsize = TextUtils.TruncateAt.END - text = relation.timeInMillis?.formatTimestamp( - isMillis = true, - format = relation.dateFormat + text = relation.relativeDate?.getPrettyName( + isTimeIncluded = relation.isTimeIncluded, + resources = resources ) } addView(view) diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/dv/ListViewItemRelationGroupWidget.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/dv/ListViewItemRelationGroupWidget.kt index db0b912731..78d560deef 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/dv/ListViewItemRelationGroupWidget.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/dv/ListViewItemRelationGroupWidget.kt @@ -10,10 +10,10 @@ import androidx.constraintlayout.widget.ConstraintLayout import com.anytypeio.anytype.core_models.ThemeColor import com.anytypeio.anytype.core_ui.R import com.anytypeio.anytype.core_ui.extensions.dark +import com.anytypeio.anytype.core_ui.extensions.getPrettyName import com.anytypeio.anytype.core_ui.widgets.ListViewRelationObjectValueView import com.anytypeio.anytype.core_ui.widgets.ListViewRelationTagValueView import com.anytypeio.anytype.core_utils.ext.dimen -import com.anytypeio.anytype.core_utils.ext.formatTimestamp import com.anytypeio.anytype.presentation.objects.ObjectIcon import com.anytypeio.anytype.presentation.relations.model.DefaultObjectRelationValueView import com.anytypeio.anytype.presentation.sets.model.ObjectView @@ -60,9 +60,9 @@ class ListViewItemRelationGroupWidget @JvmOverloads constructor( ids.add(view.id) } is DefaultObjectRelationValueView.Date -> { - val value = relation.timeInMillis?.formatTimestamp( - isMillis = true, - format = relation.dateFormat + val value = relation.relativeDate?.getPrettyName( + isTimeIncluded = relation.isTimeIncluded, + resources = resources ) if (value != null) { val view = createView(value = value) diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/toolbar/MentionToolbar.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/toolbar/MentionToolbar.kt index 16a94da3e5..335f580544 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/toolbar/MentionToolbar.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/toolbar/MentionToolbar.kt @@ -8,7 +8,7 @@ import com.anytypeio.anytype.core_ui.R import com.anytypeio.anytype.core_ui.databinding.WidgetMentionMenuBinding import com.anytypeio.anytype.core_ui.widgets.toolbar.adapter.MentionAdapter import com.anytypeio.anytype.core_utils.ui.NpaLinearLayoutManager -import com.anytypeio.anytype.presentation.navigation.DefaultObjectView +import com.anytypeio.anytype.presentation.navigation.DefaultSearchItem class MentionToolbar @JvmOverloads constructor( context: Context, @@ -16,11 +16,10 @@ class MentionToolbar @JvmOverloads constructor( defStyleAttr: Int = 0 ) : ConstraintLayout(context, attrs, defStyleAttr) { - private var mentionClick: ((DefaultObjectView, String, Int) -> Unit)? = null + private var mentionClick: ((DefaultSearchItem, String, Int) -> Unit)? = null private var newPageClick: ((String) -> Unit)? = null private val mentionAdapter by lazy { MentionAdapter( - data = arrayListOf(), onClicked = { objectView, filter, pos -> mentionClick?.invoke(objectView, filter, pos) }, @@ -39,7 +38,7 @@ class MentionToolbar @JvmOverloads constructor( } fun setupClicks( - mentionClick: (DefaultObjectView, String, Int) -> Unit, + mentionClick: (DefaultSearchItem, String, Int) -> Unit, newPageClick: (String) -> Unit ) { this.mentionClick = mentionClick @@ -54,8 +53,13 @@ class MentionToolbar @JvmOverloads constructor( } } - fun addItems(items: List) { - mentionAdapter.setData(items) + fun addItems(items: List) { + mentionAdapter.submitList(items) + if (items.isNotEmpty()) { + handler.post { + binding.recyclerView.scrollToPosition(0) + } + } } fun updateFilter(filter: String) { diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/toolbar/adapter/MentionAdapter.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/toolbar/adapter/MentionAdapter.kt index f86ff722fe..a096069cb0 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/toolbar/adapter/MentionAdapter.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/toolbar/adapter/MentionAdapter.kt @@ -2,43 +2,40 @@ package com.anytypeio.anytype.core_ui.widgets.toolbar.adapter import android.view.LayoutInflater import android.view.ViewGroup +import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import com.anytypeio.anytype.core_ui.R import com.anytypeio.anytype.core_ui.databinding.ItemMentionNewPageBinding +import com.anytypeio.anytype.core_ui.databinding.ItemMentionSectionDateBinding +import com.anytypeio.anytype.core_ui.databinding.ItemMentionSectionObjectsBinding +import com.anytypeio.anytype.core_ui.features.navigation.DefaultObjectViewAdapter.Differ import com.anytypeio.anytype.core_ui.features.navigation.DefaultObjectViewAdapter.ObjectItemViewHolder +import com.anytypeio.anytype.core_ui.features.navigation.DefaultObjectViewAdapter.ObjectViewHolder import com.anytypeio.anytype.presentation.editor.editor.mention.MentionConst.MENTION_PREFIX import com.anytypeio.anytype.presentation.navigation.DefaultObjectView +import com.anytypeio.anytype.presentation.navigation.DefaultSearchItem +import com.anytypeio.anytype.presentation.navigation.NewObject +import com.anytypeio.anytype.presentation.navigation.SectionDates +import com.anytypeio.anytype.presentation.navigation.SectionObjects +import com.anytypeio.anytype.presentation.navigation.SelectDateItem class MentionAdapter( - private var data: ArrayList, private var mentionFilter: String = "", - private val onClicked: (DefaultObjectView, String, Int) -> Unit, - private val newClicked: (String) -> Unit -) : RecyclerView.Adapter() { - - fun setData(mentions: List) { - if (mentions.isEmpty()) { - data.clear() - notifyDataSetChanged() - } else { - data.clear() - data.addAll(mentions) - notifyDataSetChanged() - } - } + private val onClicked: (DefaultSearchItem, String, Int) -> Unit, + private val newClicked: (String) -> Unit, + private val onCurrentListChanged: (Int, Int) -> Unit = { prevSize, newSize -> }, +) : ListAdapter(Differ) { fun updateFilter(filter: String) { mentionFilter = filter } fun clear() { + submitList(emptyList()) mentionFilter = "" - val size = data.size - data.clear() - notifyItemRangeRemoved(0, size + 1) } - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ObjectViewHolder { val inflater = LayoutInflater.from(parent.context) return when (viewType) { TYPE_NEW_PAGE -> NewPageViewHolder( @@ -48,38 +45,59 @@ class MentionAdapter( newClicked(mentionFilter) } } - TYPE_MENTION -> + TYPE_MENTION, TYPE_SELECT_DATE -> ObjectItemViewHolder( view = inflater.inflate(R.layout.item_list_object_small, parent, false) ).apply { itemView.setOnClickListener { val pos = bindingAdapterPosition if (pos != RecyclerView.NO_POSITION) { - onClicked(data[pos], mentionFilter, pos) + val item = getItem(pos) + onClicked(item, mentionFilter, pos) } } } + TYPE_SECTION_DATES -> SectionDatesViewHolder( + binding = ItemMentionSectionDateBinding.inflate(inflater, parent, false) + ) + TYPE_SECTION_OBJECTS -> SectionObjectsViewHolder( + binding = ItemMentionSectionObjectsBinding.inflate(inflater, parent, false) + ) else -> throw IllegalStateException("Unexpected view type: $viewType") } } - override fun getItemCount(): Int = data.size + 1 - - override fun getItemViewType(position: Int): Int { - return if (position > data.lastIndex) TYPE_NEW_PAGE else TYPE_MENTION + override fun onCurrentListChanged( + previousList: MutableList, + currentList: MutableList + ) { + super.onCurrentListChanged(previousList, currentList) + onCurrentListChanged(previousList.size, currentList.size) } - override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { - if (holder is ObjectItemViewHolder) { - holder.bind(data[position]) + override fun getItemViewType(position: Int): Int = when (val item = getItem(position)) { + is DefaultObjectView -> TYPE_MENTION + is SectionDates -> TYPE_SECTION_DATES + is SectionObjects -> TYPE_SECTION_OBJECTS + is NewObject -> TYPE_NEW_PAGE + is SelectDateItem -> TYPE_SELECT_DATE + else -> throw IllegalStateException("Unexpected item type: ${item.javaClass.name}") + } + + override fun onBindViewHolder(holder: ObjectViewHolder, position: Int) { + val item = getItem(position) + if (holder is ObjectItemViewHolder && item is DefaultObjectView) { + holder.bind(item) } if (holder is NewPageViewHolder) { holder.bind(filter = mentionFilter.removePrefix(MENTION_PREFIX)) } + if (holder is ObjectItemViewHolder && item is SelectDateItem) { + holder.bindSelectDateItem() + } } - class NewPageViewHolder(binding: ItemMentionNewPageBinding) : - RecyclerView.ViewHolder(binding.root) { + ObjectViewHolder(binding.root) { private val tvTitle = binding.text @@ -92,11 +110,19 @@ class MentionAdapter( "${res.getString(R.string.mention_suggester_create_object)} \"$filter\"" } } - } + class SectionDatesViewHolder(binding: ItemMentionSectionDateBinding) : + ObjectViewHolder(binding.root) + + class SectionObjectsViewHolder(binding: ItemMentionSectionObjectsBinding) : + ObjectViewHolder(binding.root) + companion object { const val TYPE_NEW_PAGE = 1 const val TYPE_MENTION = 2 + const val TYPE_SECTION_DATES = 3 + const val TYPE_SECTION_OBJECTS = 4 + const val TYPE_SELECT_DATE = 5 } } \ No newline at end of file diff --git a/core-ui/src/main/res/drawable/ic_burger_24.xml b/core-ui/src/main/res/drawable/ic_burger_24.xml new file mode 100644 index 0000000000..2ace21e065 --- /dev/null +++ b/core-ui/src/main/res/drawable/ic_burger_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/core-ui/src/main/res/drawable/ic_calendar_24.xml b/core-ui/src/main/res/drawable/ic_calendar_24.xml new file mode 100644 index 0000000000..36dd225246 --- /dev/null +++ b/core-ui/src/main/res/drawable/ic_calendar_24.xml @@ -0,0 +1,31 @@ + + + + + + + + + + diff --git a/core-ui/src/main/res/drawable/ic_mention_24.xml b/core-ui/src/main/res/drawable/ic_mention_24.xml new file mode 100644 index 0000000000..bc798be0df --- /dev/null +++ b/core-ui/src/main/res/drawable/ic_mention_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/core-ui/src/main/res/drawable/ic_obj_date_20.xml b/core-ui/src/main/res/drawable/ic_obj_date_20.xml new file mode 100644 index 0000000000..65a6a8e758 --- /dev/null +++ b/core-ui/src/main/res/drawable/ic_obj_date_20.xml @@ -0,0 +1,14 @@ + + + + diff --git a/core-ui/src/main/res/drawable/ic_obj_date_24.xml b/core-ui/src/main/res/drawable/ic_obj_date_24.xml new file mode 100644 index 0000000000..fe832a874a --- /dev/null +++ b/core-ui/src/main/res/drawable/ic_obj_date_24.xml @@ -0,0 +1,14 @@ + + + + diff --git a/core-ui/src/main/res/layout/item_list_object.xml b/core-ui/src/main/res/layout/item_list_object.xml index 5d42bf0716..d860e33550 100644 --- a/core-ui/src/main/res/layout/item_list_object.xml +++ b/core-ui/src/main/res/layout/item_list_object.xml @@ -3,57 +3,61 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="wrap_content"> + android:layout_height="@dimen/dp_72"> - - + android:orientation="vertical" + android:layout_gravity="center_vertical" + android:layout_marginStart="76dp" + android:layout_marginEnd="@dimen/dp_16"> + + + + + - + android:layout_gravity="center_vertical" + android:orientation="vertical"> - + + + + + + + + + \ No newline at end of file diff --git a/core-ui/src/main/res/layout/item_mention_section_objects.xml b/core-ui/src/main/res/layout/item_mention_section_objects.xml new file mode 100644 index 0000000000..f76106cf70 --- /dev/null +++ b/core-ui/src/main/res/layout/item_mention_section_objects.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/core-ui/src/main/res/layout/item_relation_list_relation_date.xml b/core-ui/src/main/res/layout/item_relation_list_relation_date.xml new file mode 100644 index 0000000000..5e73513cb9 --- /dev/null +++ b/core-ui/src/main/res/layout/item_relation_list_relation_date.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/core-ui/src/main/res/layout/item_search_new_object.xml b/core-ui/src/main/res/layout/item_search_new_object.xml index 702ea079c2..11bb696b3d 100644 --- a/core-ui/src/main/res/layout/item_search_new_object.xml +++ b/core-ui/src/main/res/layout/item_search_new_object.xml @@ -15,9 +15,8 @@ android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginStart="76dp" - android:fontFamily="@font/inter_medium" + style="@style/TextView.ContentStyle.PreviewTitles.2.Medium" android:text="@string/new_object" - android:textColor="@color/text_primary" - android:textSize="15sp" /> + android:textColor="@color/text_primary" /> \ No newline at end of file diff --git a/core-ui/src/main/res/layout/widget_mention_menu.xml b/core-ui/src/main/res/layout/widget_mention_menu.xml index 96a1aa2edf..a3ce2293b4 100644 --- a/core-ui/src/main/res/layout/widget_mention_menu.xml +++ b/core-ui/src/main/res/layout/widget_mention_menu.xml @@ -23,9 +23,9 @@ android:id="@+id/recyclerView" android:layout_width="0dp" android:layout_height="0dp" - android:layout_marginStart="20dp" + android:layout_marginStart="16dp" android:layout_marginTop="4dp" - android:layout_marginEnd="20dp" + android:layout_marginEnd="16dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" diff --git a/core-ui/src/main/res/values/colors.xml b/core-ui/src/main/res/values/colors.xml index d7ac493327..665116b11c 100644 --- a/core-ui/src/main/res/values/colors.xml +++ b/core-ui/src/main/res/values/colors.xml @@ -52,7 +52,7 @@ #FFFFFF #FFFFFF - #000000 + #26000000 #FFFFFF #144F4F4F #0A4F4F4F @@ -304,4 +304,6 @@ #99FFFFFF #66000000 + #26000000 + diff --git a/core-ui/src/main/res/values/dimens.xml b/core-ui/src/main/res/values/dimens.xml index ea934c381a..5880304c71 100644 --- a/core-ui/src/main/res/values/dimens.xml +++ b/core-ui/src/main/res/values/dimens.xml @@ -75,6 +75,7 @@ 19sp 56dp + 36dp 4dp 5dp 1dp @@ -104,17 +105,17 @@ 48dp 48dp - 20dp + 16dp 10dp 10dp 13dp 80dp 20dp 34dp - 80dp - 20dp - 24dp - 24dp + 16dp + 16dp + 28dp + 22dp 28sp 2dp 1dp diff --git a/core-ui/src/main/res/values/styles.xml b/core-ui/src/main/res/values/styles.xml index 4d77ba5fd8..8aa284b3a6 100644 --- a/core-ui/src/main/res/values/styles.xml +++ b/core-ui/src/main/res/values/styles.xml @@ -507,11 +507,11 @@ - - diff --git a/core-ui/src/test/java/com/anytypeio/anytype/core_ui/features/dv/CellViewDiffUtilTest.kt b/core-ui/src/test/java/com/anytypeio/anytype/core_ui/features/dv/CellViewDiffUtilTest.kt index 0f769f2854..47e26d9af6 100644 --- a/core-ui/src/test/java/com/anytypeio/anytype/core_ui/features/dv/CellViewDiffUtilTest.kt +++ b/core-ui/src/test/java/com/anytypeio/anytype/core_ui/features/dv/CellViewDiffUtilTest.kt @@ -104,7 +104,8 @@ class CellViewDiffUtilTest { id = MockDataFactory.randomString(), relationKey = MockDataFactory.randomString(), timeInSecs = MockDataFactory.randomLong(), - dateFormat = MockDataFactory.randomString() + dateFormat = MockDataFactory.randomString(), + relativeDate = null ) val newCell = oldCell.copy( @@ -127,7 +128,8 @@ class CellViewDiffUtilTest { val oldCell = CellView.Date( id = MockDataFactory.randomString(), relationKey = MockDataFactory.randomString(), - dateFormat = MockDataFactory.randomString() + dateFormat = MockDataFactory.randomString(), + relativeDate = null ) val newCell = oldCell.copy() diff --git a/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/AndroidExtension.kt b/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/AndroidExtension.kt index 19cfddbe38..5a0d8ae8f5 100644 --- a/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/AndroidExtension.kt +++ b/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/AndroidExtension.kt @@ -50,9 +50,6 @@ import com.anytypeio.anytype.presentation.util.downloader.UriFileProvider import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import java.io.File -import java.text.DateFormat -import java.text.SimpleDateFormat -import java.util.* import timber.log.Timber fun Context.dimen(res: Int): Float { @@ -125,11 +122,6 @@ fun Throwable.timber() = Timber.e("Get error : ${this.message}") const val VALUE_ROUNDED = "rounded" -fun Long.formatToDateString(pattern: String, locale: Locale): String { - val formatter = SimpleDateFormat(pattern, locale) - return formatter.format(Date(this)) -} - fun View.show() { this.visibility = View.VISIBLE } @@ -478,31 +470,4 @@ fun BaseBottomSheetComposeFragment.setupBottomSheetBehavior(paddingTop: Int) { state = BottomSheetBehavior.STATE_EXPANDED skipCollapsed = true } -} - -fun getLocalizedDateTimePattern(locale: Locale): String { - // Get DateFormat instances for both date and time - val dateFormat = DateFormat.getDateInstance(DateFormat.SHORT, locale) - val timeFormat = DateFormat.getTimeInstance(DateFormat.SHORT, locale) - - // Convert to SimpleDateFormat to extract the pattern - val datePattern = (dateFormat as? SimpleDateFormat)?.toPattern() - val timePattern = (timeFormat as? SimpleDateFormat)?.toPattern() - - // Combine date and time patterns - return "$datePattern $timePattern" -} - -fun getFormattedDateTime(locale: Locale): String { - // Get the current date and time - val currentDate = Date() - - // Get the localized pattern - val localizedPattern = getLocalizedDateTimePattern(locale) - - // Create a formatter with the localized pattern - val simpleDateFormat = SimpleDateFormat(localizedPattern, locale) - - // Format the current date and time - return simpleDateFormat.format(currentDate) } \ No newline at end of file diff --git a/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/DateUtils.kt b/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/DateUtils.kt index c7307c6cc6..6164ac1bae 100644 --- a/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/DateUtils.kt +++ b/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/DateUtils.kt @@ -1,79 +1,17 @@ package com.anytypeio.anytype.core_utils.ext -import com.anytypeio.anytype.core_utils.const.DateConst.DEFAULT_DATE_FORMAT import java.text.SimpleDateFormat import java.util.* -fun Calendar.isSameDay(compare: Calendar): Boolean = - this.get(Calendar.YEAR) == compare.get(Calendar.YEAR) - && this.get(Calendar.MONTH) == compare.get(Calendar.MONTH) - && this.get(Calendar.DAY_OF_MONTH) == compare.get(Calendar.DAY_OF_MONTH) - fun Calendar.timeInSeconds() = this.timeInMillis / 1000 -fun getTodayTimeUnit(): Calendar = Calendar.getInstance() -fun getTomorrowTimeUnit(): Calendar = Calendar.getInstance().apply { add(Calendar.DATE, 1) } -fun getYesterdayTimeUnit(): Calendar = Calendar.getInstance().apply { add(Calendar.DATE, -1) } -fun getWeekAheadTimeUnit(): Calendar = - Calendar.getInstance().apply { add(Calendar.WEEK_OF_YEAR, 1) } - -fun getWeekAgoTimeUnit(): Calendar = Calendar.getInstance().apply { add(Calendar.WEEK_OF_YEAR, -1) } -fun getMonthAheadTimeUnit(): Calendar = Calendar.getInstance().apply { add(Calendar.MONTH, 1) } -fun getMonthAgoTimeUnit(): Calendar = Calendar.getInstance().apply { add(Calendar.MONTH, -1) } - fun Long.toTimeSeconds(): Double = (this / 1000).toDouble() -fun Long.toTimeSecondsLong(): Long = (this / 1000) - -fun Long.formatTimestamp(isMillis: Boolean, format: String? = null): String { - val filterTime = Calendar.getInstance() - if (isMillis) { - filterTime.timeInMillis = this - } else { - filterTime.timeInMillis = this * 1000 - } - - val today = getTodayTimeUnit() - val tomorrow = getTomorrowTimeUnit() - val yesterday = getYesterdayTimeUnit() - val weekAgo = getWeekAgoTimeUnit() - val weekForward = getWeekAheadTimeUnit() - val monthAgo = getMonthAgoTimeUnit() - val monthForward = getMonthAheadTimeUnit() - - val isToday = filterTime.isSameDay(today) - if (isToday) return TODAY - val isTomorrow = filterTime.isSameDay(tomorrow) - if (isTomorrow) return TOMORROW - val isYesterday = filterTime.isSameDay(yesterday) - if (isYesterday) return YESTERDAY - val isWeekAgo = filterTime.isSameDay(weekAgo) - if (isWeekAgo) return LAST_WEEK - val isWeekAhead = filterTime.isSameDay(weekForward) - if (isWeekAhead) return NEXT_WEEK - val isMonthAgo = filterTime.isSameDay(monthAgo) - if (isMonthAgo) return MONTH_AGO - val isMonthAhead = filterTime.isSameDay(monthForward) - if (isMonthAhead) return MONTH_AHEAD - val isExactDay = !isToday && !isTomorrow && !isYesterday && !isWeekAgo && !isWeekAhead - && !isMonthAgo && !isMonthAhead - return if (isExactDay) { - val simpleDateFormat = if (format != null) { - SimpleDateFormat(format, Locale.getDefault()) - } else { - SimpleDateFormat(DEFAULT_DATE_FORMAT, Locale.getDefault()) - } - simpleDateFormat.format(filterTime.time) - } else { - "" - } -} fun Long.formatTimeInMillis(pattern: String): String { val simpleDateFormat = SimpleDateFormat(pattern, Locale.getDefault()) return simpleDateFormat.format(this) } -const val NO_DATE = "No date" const val TODAY = "Today" const val TOMORROW = "Tomorrow" const val YESTERDAY = "Yesterday" diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataRepository.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataRepository.kt index 70260ec1e5..6f94f3f64b 100644 --- a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataRepository.kt +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataRepository.kt @@ -20,6 +20,7 @@ import com.anytypeio.anytype.core_models.ObjectWrapper import com.anytypeio.anytype.core_models.Payload import com.anytypeio.anytype.core_models.Position import com.anytypeio.anytype.core_models.RelationFormat +import com.anytypeio.anytype.core_models.RelationListWithValueItem import com.anytypeio.anytype.core_models.Response import com.anytypeio.anytype.core_models.SearchResult import com.anytypeio.anytype.core_models.Struct @@ -1085,7 +1086,15 @@ class BlockDataRepository( return remote.unsubscribeChat(chat) } + override suspend fun objectRelationListWithValue(command: Command.RelationListWithValue): List { + return remote.objectRelationListWithValue(command) + } + override suspend fun debugAccountSelectTrace(dir: String): String { return remote.debugAccountSelectTrace(dir) } + + override suspend fun objectDateByTimestamp(command: Command.ObjectDateByTimestamp): Struct? { + return remote.objectDateByTimestamp(command) + } } \ No newline at end of file diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemote.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemote.kt index 498e3650fd..0020ff2efa 100644 --- a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemote.kt +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemote.kt @@ -20,6 +20,7 @@ import com.anytypeio.anytype.core_models.ObjectWrapper import com.anytypeio.anytype.core_models.Payload import com.anytypeio.anytype.core_models.Position import com.anytypeio.anytype.core_models.RelationFormat +import com.anytypeio.anytype.core_models.RelationListWithValueItem import com.anytypeio.anytype.core_models.Response import com.anytypeio.anytype.core_models.SearchResult import com.anytypeio.anytype.core_models.Struct @@ -209,6 +210,8 @@ interface BlockRemote { details: Struct ): Payload + suspend fun objectDateByTimestamp(command: Command.ObjectDateByTimestamp): Struct? + suspend fun updateBlocksMark(command: Command.UpdateBlocksMark): Payload suspend fun addRelationToBlock(command: Command.AddRelationToBlock): Payload @@ -459,5 +462,7 @@ interface BlockRemote { //endregion + suspend fun objectRelationListWithValue(command: Command.RelationListWithValue): List + suspend fun debugAccountSelectTrace(dir: String): String } \ No newline at end of file diff --git a/device/build.gradle b/device/build.gradle index dcfef8b92c..0ce221cd09 100644 --- a/device/build.gradle +++ b/device/build.gradle @@ -21,6 +21,8 @@ dependencies { testImplementation libs.androidXTestCore testImplementation libs.robolectric testImplementation libs.mockitoKotlin + testImplementation libs.coroutineTesting + testImplementation libs.turbine compileOnly libs.javaxInject } diff --git a/device/src/main/java/com/anytypeio/anytype/device/providers/AppDefaultDateFormatProvider.kt b/device/src/main/java/com/anytypeio/anytype/device/providers/AppDefaultDateFormatProvider.kt index 7677886408..aa9cb34d54 100644 --- a/device/src/main/java/com/anytypeio/anytype/device/providers/AppDefaultDateFormatProvider.kt +++ b/device/src/main/java/com/anytypeio/anytype/device/providers/AppDefaultDateFormatProvider.kt @@ -1,5 +1,6 @@ package com.anytypeio.anytype.device.providers +import com.anytypeio.anytype.core_models.DEFAULT_TIME_STYLE import com.anytypeio.anytype.core_models.FALLBACK_DATE_PATTERN import com.anytypeio.anytype.domain.misc.LocaleProvider import java.text.DateFormat @@ -33,7 +34,7 @@ class AppDefaultDateFormatProviderImpl @Inject constructor( override fun provide(): String { return try { val locale = localeProvider.locale() - val dateFormat = DateFormat.getDateInstance(DateFormat.SHORT, locale) + val dateFormat = DateFormat.getDateInstance(DEFAULT_TIME_STYLE, locale) if (dateFormat is SimpleDateFormat) { dateFormat.toPattern() } else { diff --git a/device/src/main/java/com/anytypeio/anytype/device/providers/DateProviderImpl.kt b/device/src/main/java/com/anytypeio/anytype/device/providers/DateProviderImpl.kt index 4937837884..a18347572d 100644 --- a/device/src/main/java/com/anytypeio/anytype/device/providers/DateProviderImpl.kt +++ b/device/src/main/java/com/anytypeio/anytype/device/providers/DateProviderImpl.kt @@ -1,11 +1,14 @@ package com.anytypeio.anytype.device.providers import android.text.format.DateUtils +import com.anytypeio.anytype.core_models.FALLBACK_DATE_PATTERN +import com.anytypeio.anytype.core_models.RelativeDate import com.anytypeio.anytype.core_models.TimeInMillis import com.anytypeio.anytype.core_models.TimeInSeconds import com.anytypeio.anytype.domain.misc.DateProvider import com.anytypeio.anytype.domain.misc.DateType import com.anytypeio.anytype.domain.misc.LocaleProvider +import com.anytypeio.anytype.domain.vault.ObserveVaultSettings import java.text.DateFormat import java.text.SimpleDateFormat import java.time.Instant @@ -15,14 +18,30 @@ import java.time.ZoneId import java.time.ZoneOffset import java.time.temporal.ChronoUnit import java.util.Date +import java.util.concurrent.TimeUnit import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.launch import timber.log.Timber class DateProviderImpl @Inject constructor( private val defaultZoneId: ZoneId, - private val localeProvider: LocaleProvider + private val localeProvider: LocaleProvider, + private val vaultSettings: ObserveVaultSettings, + scope: CoroutineScope ) : DateProvider { + private val defaultDateFormat = MutableStateFlow(FALLBACK_DATE_PATTERN) + + init { + scope.launch { + vaultSettings.flow().collect { settings -> + defaultDateFormat.value = settings.dateFormat + } + } + } + override fun calculateDateType(date: TimeInSeconds): DateType { val dateInstant = Instant.ofEpochSecond(date) val givenDate = dateInstant.atZone(defaultZoneId).toLocalDate() @@ -88,12 +107,13 @@ class DateProviderImpl @Inject constructor( return weekAgoWithZeroTime.atStartOfDay(defaultZoneId).toEpochSecond() } - override fun getRelativeTimeSpanString(date: TimeInSeconds): CharSequence = DateUtils.getRelativeTimeSpanString( - date, - System.currentTimeMillis(), - DateUtils.DAY_IN_MILLIS, - DateUtils.FORMAT_ABBREV_RELATIVE - ) + override fun getRelativeTimeSpanString(date: TimeInSeconds): CharSequence = + DateUtils.getRelativeTimeSpanString( + date, + System.currentTimeMillis(), + DateUtils.DAY_IN_MILLIS, + DateUtils.FORMAT_ABBREV_RELATIVE + ) override fun adjustToStartOfDayInUserTimeZone(timestamp: TimeInSeconds): TimeInMillis { val instant = Instant.ofEpochSecond(timestamp) @@ -102,19 +122,21 @@ class DateProviderImpl @Inject constructor( return (startOfDay.toEpochSecond(ZoneOffset.UTC) * 1000) } - override fun adjustFromStartOfDayInUserTimeZoneToUTC(timestamp: TimeInMillis, zoneId: ZoneId): TimeInSeconds { - // Convert the timestamp to an Instan - val instant = Instant.ofEpochSecond(timestamp) + override fun adjustFromStartOfDayInUserTimeZoneToUTC( + timeInMillis: TimeInMillis + ): TimeInSeconds { + // Convert the timestamp to an Instant + val instant = Instant.ofEpochMilli(timeInMillis) // Convert the Instant to a ZonedDateTime in UTC val utcDateTime = instant.atZone(ZoneOffset.UTC) // Convert the UTC ZonedDateTime to the local time zone - val localDateTime = utcDateTime.withZoneSameInstant(zoneId) + val localDateTime = utcDateTime.withZoneSameInstant(defaultZoneId) // Get the local date and the start of the day in the local time zone val localDate = localDateTime.toLocalDate() - val startOfDay = localDate.atStartOfDay(zoneId) + val startOfDay = localDate.atStartOfDay(defaultZoneId) // Check if the UTC timestamp is at the boundary of the day in the local time zone return when { @@ -122,10 +144,12 @@ class DateProviderImpl @Inject constructor( // If the UTC timestamp is after the start of the day in the local time zone, return the start of the next day startOfDay.plusDays(1).toEpochSecond() } + utcDateTime.toLocalDate().isBefore(startOfDay.toLocalDate()) -> { // If the UTC timestamp is before the start of the day in the local time zone, return the start of the previous day startOfDay.minusDays(1).toEpochSecond() } + else -> { // Otherwise, return the start of the day startOfDay.toEpochSecond() @@ -139,20 +163,21 @@ class DateProviderImpl @Inject constructor( val formatter = SimpleDateFormat(pattern, locale) return formatter.format(Date(timestamp)) } catch (e: Exception) { - Timber.e(e,"Error formatting timestamp to date string") + Timber.e(e, "Error formatting timestamp to date string") return "" } } override fun formatTimestampToDateAndTime( timestamp: TimeInMillis, - dateStyle: Int, timeStyle: Int ): Pair { + Timber.d("Formatting timestamp [$timestamp] to date and time") return try { val locale = localeProvider.locale() - val datePattern = (DateFormat.getDateInstance(dateStyle, locale) as SimpleDateFormat).toPattern() - val timePattern = (DateFormat.getTimeInstance(timeStyle, locale) as SimpleDateFormat).toPattern() + val datePattern = defaultDateFormat.value + val timePattern = + (DateFormat.getTimeInstance(timeStyle, locale) as SimpleDateFormat).toPattern() val dateFormatter = SimpleDateFormat(datePattern, locale) val timeFormatter = SimpleDateFormat(timePattern, locale) val date = Date(timestamp) @@ -174,6 +199,51 @@ class DateProviderImpl @Inject constructor( return truncatedDateTime1 == truncatedDateTime2 } + + override fun calculateRelativeDates(dateInSeconds: TimeInSeconds?): RelativeDate? { + + if (dateInSeconds == null || dateInSeconds == 0L) return null + val zoneId = defaultZoneId + val dateInstant = Instant.ofEpochSecond(dateInSeconds) + val givenDate = dateInstant.atZone(zoneId).toLocalDate() + val today = LocalDate.now(zoneId) + + val daysDifference = ChronoUnit.DAYS.between(today, givenDate) + + return when (daysDifference) { + 0L -> RelativeDate.Today + 1L -> RelativeDate.Tomorrow + -1L -> RelativeDate.Yesterday + else -> { + val timestampMillis = TimeUnit.SECONDS.toMillis(dateInSeconds) + + val (dateString, timeString) = formatTimestampToDateAndTime(timestampMillis) + RelativeDate.Other( + formattedDate = dateString, + formattedTime = timeString + ) + } + } + } + + override fun getLocalDateOfTime(epochMilli: Long): LocalDate { + val instant = Instant.ofEpochMilli(epochMilli) + return instant.atZone(defaultZoneId).toLocalDate() + } + + override fun isTimestampWithinYearRange(timeStampInMillis: Long, yearRange: IntRange): Boolean { + // Convert the timestamp in milliseconds to an Instant object + val instant = Instant.ofEpochMilli(timeStampInMillis) + + // Convert the Instant to a LocalDate object in default time zone + val date = instant.atZone(defaultZoneId).toLocalDate() + + // Extract the year from the LocalDate object + val year = date.year + + // Check if the year is within the desired range + return year in yearRange.first()..yearRange.last() + } } diff --git a/device/src/test/java/com/anytypeio/anytype/DateProviderImplTest.kt b/device/src/test/java/com/anytypeio/anytype/DateProviderImplTest.kt index ddd6457ed5..5c90a59e0a 100644 --- a/device/src/test/java/com/anytypeio/anytype/DateProviderImplTest.kt +++ b/device/src/test/java/com/anytypeio/anytype/DateProviderImplTest.kt @@ -3,8 +3,11 @@ package com.anytypeio.anytype import com.anytypeio.anytype.device.providers.DateProviderImpl import com.anytypeio.anytype.domain.misc.DateProvider import com.anytypeio.anytype.domain.misc.LocaleProvider +import com.anytypeio.anytype.domain.vault.ObserveVaultSettings import java.time.ZoneId import java.util.Locale +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.runTest import org.junit.Assert.* import org.junit.Before import org.junit.Test @@ -14,20 +17,25 @@ import org.mockito.MockitoAnnotations class DateProviderImplTest { + private val dispatcher = StandardTestDispatcher(name = "Default test dispatcher") + @Mock lateinit var localeProvider: LocaleProvider + @Mock + lateinit var observeVaultSettings: ObserveVaultSettings + lateinit var dateProviderImpl: DateProvider @Before fun setUp() { MockitoAnnotations.openMocks(this) - dateProviderImpl = DateProviderImpl(ZoneId.systemDefault(), localeProvider) Mockito.`when`(localeProvider.locale()).thenReturn(Locale.getDefault()) + Mockito.`when`(localeProvider.language()).thenReturn("en") } @Test - fun adjustToStartOfDayInUserTimeZoneWithPastStart() { + fun adjustToStartOfDayInUserTimeZoneWithPastStart() = runTest(dispatcher) { val timeStamp = 1720828800L // Saturday, 13 July 2024 00:00:00 @@ -61,15 +69,23 @@ class DateProviderImplTest { Triple(timeStamp, ZoneId.of("GMT-12"), 1720872000L) ) tests.forEach { (utcTimestamp, zoneId, expected) -> + dateProviderImpl = DateProviderImpl( + defaultZoneId = zoneId, + localeProvider = localeProvider, + vaultSettings = observeVaultSettings, + scope = backgroundScope + ) val startOfDayInLocalZone = - dateProviderImpl.adjustFromStartOfDayInUserTimeZoneToUTC(utcTimestamp, zoneId) + dateProviderImpl.adjustFromStartOfDayInUserTimeZoneToUTC( + timeInMillis = utcTimestamp * 1000 + ) assertEquals(expected, startOfDayInLocalZone) } } @Test - fun adjustToStartOfDayInUserTimeZoneWithPastMidday() { + fun adjustToStartOfDayInUserTimeZoneWithPastMidday() = runTest(dispatcher) { val timeStamp = 1720888800L // Saturday, 13 July 2024 16:40:00 val tests = listOf( @@ -101,16 +117,23 @@ class DateProviderImplTest { Triple(timeStamp, ZoneId.of("GMT+12"), 1720785600L), Triple(timeStamp, ZoneId.of("GMT-12"), 1720872000L) ) + // TODO fix tests tests.forEach { (utcTimestamp, zoneId, expected) -> + dateProviderImpl = DateProviderImpl( + defaultZoneId = zoneId, + localeProvider = localeProvider, + vaultSettings = observeVaultSettings, + scope = backgroundScope + ) val startOfDayInLocalZone = - dateProviderImpl.adjustFromStartOfDayInUserTimeZoneToUTC(utcTimestamp, zoneId) + dateProviderImpl.adjustFromStartOfDayInUserTimeZoneToUTC(utcTimestamp * 1000) assertEquals(expected, startOfDayInLocalZone) } } @Test - fun adjustToStartOfDayInUserTimeZoneWithPastEnd() { + fun adjustToStartOfDayInUserTimeZoneWithPastEnd() = runTest(dispatcher) { val timeStamp = 1720915199L // Saturday, 13 July 2024 23:59:59 val tests = listOf( @@ -143,15 +166,21 @@ class DateProviderImplTest { Triple(timeStamp, ZoneId.of("GMT-12"), 1720872000L) ) tests.forEach { (utcTimestamp, zoneId, expected) -> + dateProviderImpl = DateProviderImpl( + defaultZoneId = zoneId, + localeProvider = localeProvider, + vaultSettings = observeVaultSettings, + scope = backgroundScope + ) val startOfDayInLocalZone = - dateProviderImpl.adjustFromStartOfDayInUserTimeZoneToUTC(utcTimestamp, zoneId) + dateProviderImpl.adjustFromStartOfDayInUserTimeZoneToUTC(utcTimestamp * 1000) assertEquals(expected, startOfDayInLocalZone) } } @Test - fun adjustToStartOfDayInUserTimeZoneWithFuture() { + fun adjustToStartOfDayInUserTimeZoneWithFuture() = runTest(dispatcher) { val timeStamp = 3720915199 // Saturday, 29 November 2087 03:33:19 val tests = listOf( @@ -181,9 +210,16 @@ class DateProviderImplTest { Triple(timeStamp, ZoneId.of("GMT-11"), 3720942000L), Triple(timeStamp, ZoneId.of("GMT-12"), 3720945600L) ) + // TODO fix tests tests.forEach { (utcTimestamp, zoneId, expected) -> + dateProviderImpl = DateProviderImpl( + defaultZoneId = zoneId, + localeProvider = localeProvider, + vaultSettings = observeVaultSettings, + scope = backgroundScope + ) val startOfDayInLocalZone = - dateProviderImpl.adjustFromStartOfDayInUserTimeZoneToUTC(utcTimestamp, zoneId) + dateProviderImpl.adjustFromStartOfDayInUserTimeZoneToUTC(utcTimestamp * 1000) assertEquals(expected, startOfDayInLocalZone) } diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/block/repo/BlockRepository.kt b/domain/src/main/java/com/anytypeio/anytype/domain/block/repo/BlockRepository.kt index b794e7a7c4..9a7514da70 100644 --- a/domain/src/main/java/com/anytypeio/anytype/domain/block/repo/BlockRepository.kt +++ b/domain/src/main/java/com/anytypeio/anytype/domain/block/repo/BlockRepository.kt @@ -20,6 +20,7 @@ import com.anytypeio.anytype.core_models.ObjectWrapper import com.anytypeio.anytype.core_models.Payload import com.anytypeio.anytype.core_models.Position import com.anytypeio.anytype.core_models.RelationFormat +import com.anytypeio.anytype.core_models.RelationListWithValueItem import com.anytypeio.anytype.core_models.Response import com.anytypeio.anytype.core_models.SearchResult import com.anytypeio.anytype.core_models.Struct @@ -502,5 +503,9 @@ interface BlockRepository { //endregion + suspend fun objectRelationListWithValue(command: Command.RelationListWithValue): List + suspend fun debugAccountSelectTrace(dir: String): String + + suspend fun objectDateByTimestamp(command: Command.ObjectDateByTimestamp): Struct? } \ No newline at end of file diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/misc/DateProvider.kt b/domain/src/main/java/com/anytypeio/anytype/domain/misc/DateProvider.kt index 239401a049..a1e499d1e5 100644 --- a/domain/src/main/java/com/anytypeio/anytype/domain/misc/DateProvider.kt +++ b/domain/src/main/java/com/anytypeio/anytype/domain/misc/DateProvider.kt @@ -1,9 +1,10 @@ package com.anytypeio.anytype.domain.misc +import com.anytypeio.anytype.core_models.DEFAULT_TIME_STYLE +import com.anytypeio.anytype.core_models.RelativeDate import com.anytypeio.anytype.core_models.TimeInMillis import com.anytypeio.anytype.core_models.TimeInSeconds -import java.text.DateFormat -import java.time.ZoneId +import java.time.LocalDate /** @@ -19,14 +20,16 @@ interface DateProvider { fun getTimestampForWeekAheadAtStartOfDay(): TimeInSeconds fun getTimestampForWeekAgoAtStartOfDay(): TimeInSeconds fun adjustToStartOfDayInUserTimeZone(timestamp: TimeInSeconds): TimeInMillis - fun adjustFromStartOfDayInUserTimeZoneToUTC(timestamp: TimeInMillis, zoneId: ZoneId): TimeInSeconds + fun adjustFromStartOfDayInUserTimeZoneToUTC(timeInMillis: TimeInMillis): TimeInSeconds fun formatToDateString(timestamp: Long, pattern: String): String fun formatTimestampToDateAndTime( timestamp: TimeInMillis, - dateStyle: Int = DateFormat.MEDIUM, - timeStyle: Int = DateFormat.SHORT + timeStyle: Int = DEFAULT_TIME_STYLE ): Pair + fun calculateRelativeDates(dateInSeconds: TimeInSeconds?): RelativeDate? fun isSameMinute(timestamp1: Long, timestamp2: Long): Boolean + fun getLocalDateOfTime(epochMilli: Long): LocalDate + fun isTimestampWithinYearRange(timeStampInMillis: Long, yearRange: IntRange): Boolean } interface DateTypeNameProvider { diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/objects/GetDateObjectByTimestamp.kt b/domain/src/main/java/com/anytypeio/anytype/domain/objects/GetDateObjectByTimestamp.kt new file mode 100644 index 0000000000..69f8684d91 --- /dev/null +++ b/domain/src/main/java/com/anytypeio/anytype/domain/objects/GetDateObjectByTimestamp.kt @@ -0,0 +1,26 @@ +package com.anytypeio.anytype.domain.objects + +import com.anytypeio.anytype.core_models.Command +import com.anytypeio.anytype.core_models.Struct +import com.anytypeio.anytype.core_models.primitives.SpaceId +import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers +import com.anytypeio.anytype.domain.base.ResultInteractor +import com.anytypeio.anytype.domain.block.repo.BlockRepository +import com.anytypeio.anytype.domain.objects.GetDateObjectByTimestamp.Params +import javax.inject.Inject + +class GetDateObjectByTimestamp @Inject constructor( + private val repo: BlockRepository, + dispatchers: AppCoroutineDispatchers +) : ResultInteractor(dispatchers.io) { + + override suspend fun doWork(params: Params): Struct? { + val command = Command.ObjectDateByTimestamp( + space = params.space, + timestamp = params.timestamp + ) + return repo.objectDateByTimestamp(command) + } + + data class Params(val space: SpaceId, val timestamp: Long) +} \ No newline at end of file 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 new file mode 100644 index 0000000000..9a67695967 --- /dev/null +++ b/domain/src/main/java/com/anytypeio/anytype/domain/primitives/FieldParser.kt @@ -0,0 +1,123 @@ +package com.anytypeio.anytype.domain.primitives + +import com.anytypeio.anytype.core_models.MAX_SNIPPET_SIZE +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.SupportedLayouts +import com.anytypeio.anytype.core_models.primitives.Field +import com.anytypeio.anytype.core_models.primitives.FieldDateValue +import com.anytypeio.anytype.core_models.primitives.TimestampInSeconds +import com.anytypeio.anytype.core_models.primitives.Value +import com.anytypeio.anytype.domain.debugging.Logger +import com.anytypeio.anytype.domain.misc.DateProvider +import javax.inject.Inject +import kotlin.collections.contains + +interface FieldParser { + fun toDate(any: Any?): Field.Date? + fun getObjectName(objectWrapper: ObjectWrapper.Basic): String + fun getObjectTypeName( + objectWrapper: ObjectWrapper.Basic, + types: List + ): String? +} + +class FieldParserImpl @Inject constructor( + private val dateProvider: DateProvider, + private val logger: Logger +) : FieldParser { + + //region Date field + override fun toDate( + any: Any? + ): Field.Date? { + return when (val value = FieldDateParser.parse(any)) { + is Value.Single -> { + calculateFieldDate(value = value) + } + + else -> { + return null + } + } + } + + private fun calculateFieldDate(value: Value.Single?): Field.Date? { + val dateInSeconds = value?.single ?: return null + val relativeDate = dateProvider.calculateRelativeDates(dateInSeconds) + if (relativeDate == null) { + return null + } + return Field.Date( + value = Value.Single( + FieldDateValue( + timestamp = TimestampInSeconds(time = dateInSeconds), + relativeDate = relativeDate + ) + ) + ) + } + //endregion + + //region ObjectWrapper.Basic fields + override fun getObjectName(objectWrapper: ObjectWrapper.Basic): String { + return objectWrapper.getProperObjectName().orEmpty() + } + + override fun getObjectTypeName( + objectWrapper: ObjectWrapper.Basic, + types: List + ): String? { + return when (objectWrapper.layout) { + ObjectType.Layout.DATE -> { + return null + } + else -> { + types.find { it.id == objectWrapper.id }?.name + } + } + } + + private fun ObjectWrapper.Basic.getProperObjectName(): String? { + return when (layout) { + ObjectType.Layout.DATE -> { + getProperDateName() + } + + ObjectType.Layout.NOTE -> { + snippet?.replace("\n", " ")?.take(MAX_SNIPPET_SIZE) + } + + in SupportedLayouts.fileLayouts -> { + val fileName = if (name.isNullOrBlank()) "Untitled" else name.orEmpty() + if (fileExt.isNullOrBlank()) { + fileName + } else { + if (fileName.endsWith(".$fileExt")) { + fileName + } else { + "$fileName.$fileExt" + } + } + } + + else -> { + name + } + } + } + + private fun ObjectWrapper.Basic.getProperDateName(): String { + val timestampInSeconds = getSingleValue(Relations.TIMESTAMP)?.toLong() + if (timestampInSeconds != null) { + val (formattedDate, _) = dateProvider.formatTimestampToDateAndTime( + timestamp = timestampInSeconds * 1000, + ) + return formattedDate + } else { + return "" + } + } + //endregion +} \ No newline at end of file diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/primitives/FieldValuesParser.kt b/domain/src/main/java/com/anytypeio/anytype/domain/primitives/FieldValuesParser.kt new file mode 100644 index 0000000000..ce18b852c3 --- /dev/null +++ b/domain/src/main/java/com/anytypeio/anytype/domain/primitives/FieldValuesParser.kt @@ -0,0 +1,64 @@ +package com.anytypeio.anytype.domain.primitives + +import com.anytypeio.anytype.core_models.primitives.Value +import com.anytypeio.anytype.core_models.primitives.Value.Single + +/** + * Object responsible for parsing various input types into a [Value.Single] containing a [Long] timestamp. + * + * The `FieldDateParser` provides a flexible way to handle different types of inputs that represent dates or timestamps. + * It attempts to parse the input into a `Long` value, which can then be used for further date calculations or formatting. + * + * **Supported Input Types:** + * - **String**: Parses numeric strings into `Long`. + * - **Number**: Converts numeric types directly into `Long`. + * - **List**: Iterates over a list to find and return the **first** valid `Long` value. + * + * If the input cannot be parsed into a `Long`, the parser returns `null`. + */ +object FieldDateParser { + + /** + * Parses the input [value] into a [Value.Single] containing a [Long], if possible. + * + * ### Supported Input Types: + * - **String**: Attempts to parse the string into a `Long`. Returns a [Value.Single] if successful. + * - **Number**: Converts the number to a `Long` and wraps it in a [Value.Single]. + * - **List<\*>**: Iterates over the list to find the **first** valid `Long` value. + * - The list can contain elements of type `String`, `Number`, or others. + * - Invalid entries are skipped. + * - If a valid `Long` is found, it is returned as a [Value.Single]. + * + * ### Behavior: + * - Returns `null` if the input is `null` or cannot be parsed into a `Long`. + * - For floating-point numbers, the decimal part is truncated when converting to `Long`. + * + * @param value The input value to parse, which can be of any type (`Any?`). + * @return A [Value.Single] containing the parsed `Long` value, or `null` if parsing fails. + * + * ### Examples: + * ```kotlin + * FieldDateParser.parse("1627814400") // Returns Value.Single(1627814400L) + * FieldDateParser.parse(1627814400L) // Returns Value.Single(1627814400L) + * FieldDateParser.parse(listOf("invalid", "1627814400")) // Returns Value.Single(1627814400L) + * FieldDateParser.parse(null) // Returns null + * FieldDateParser.parse("invalid_number") // Returns null + * ``` + */ + fun parse(value: Any?): Single? = when (value) { + is String -> value.toLongOrNull()?.let(Value::Single) + is Number -> Single(value.toLong()) + is List<*> -> value + .asSequence() + .mapNotNull { + when (it) { + is String -> it.toLongOrNull() + is Number -> it.toLong() + else -> null + } + } + .firstOrNull() + ?.let(Value::Single) + else -> null + } +} \ No newline at end of file diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/relations/GetObjectRelationListById.kt b/domain/src/main/java/com/anytypeio/anytype/domain/relations/GetObjectRelationListById.kt new file mode 100644 index 0000000000..7d6fd9e35a --- /dev/null +++ b/domain/src/main/java/com/anytypeio/anytype/domain/relations/GetObjectRelationListById.kt @@ -0,0 +1,27 @@ +package com.anytypeio.anytype.domain.relations + +import com.anytypeio.anytype.core_models.Command +import com.anytypeio.anytype.core_models.Id +import com.anytypeio.anytype.core_models.RelationListWithValueItem +import com.anytypeio.anytype.core_models.primitives.SpaceId +import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers +import com.anytypeio.anytype.domain.base.ResultInteractor +import com.anytypeio.anytype.domain.block.repo.BlockRepository +import com.anytypeio.anytype.domain.relations.GetObjectRelationListById.Params +import javax.inject.Inject + +class GetObjectRelationListById @Inject constructor( + private val repo: BlockRepository, + dispatchers: AppCoroutineDispatchers +) : ResultInteractor>(dispatchers.io) { + + override suspend fun doWork(params: Params): List { + val command = Command.RelationListWithValue( + space = params.space, + value = params.value + ) + return repo.objectRelationListWithValue(command) + } + + data class Params(val space: SpaceId, val value: Id) +} \ No newline at end of file diff --git a/domain/src/test/java/com/anytypeio/anytype/domain/primitives/FieldDateTest.kt b/domain/src/test/java/com/anytypeio/anytype/domain/primitives/FieldDateTest.kt new file mode 100644 index 0000000000..c2df4176a2 --- /dev/null +++ b/domain/src/test/java/com/anytypeio/anytype/domain/primitives/FieldDateTest.kt @@ -0,0 +1,105 @@ +package com.anytypeio.anytype.domain.primitives + +import com.anytypeio.anytype.core_models.primitives.Value +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class FieldDateTest { + + @Test + fun `parse should return Single when input is valid String number`() { + val input = "1627814400" + val expected = Value.Single(1627814400L) + + val result = FieldDateParser.parse(input) + + assertEquals(expected, result) + } + + @Test + fun `parse should return null when input is invalid String`() { + val input = "invalid_number" + + val result = FieldDateParser.parse(input) + + assertNull(result) + } + + @Test + fun `parse should return Single when input is Number`() { + val input = 1627814400L + val expected = Value.Single(1627814400L) + + val result = FieldDateParser.parse(input) + + assertEquals(expected, result) + } + + @Test + fun `parse should return Single of first valid value when input is List`() { + val input = listOf("invalid_number", "1627814400", 1627900800L) + val expected = Value.Single(1627814400L) + + val result = FieldDateParser.parse(input) + + assertEquals(expected, result) + } + + @Test + fun `parse should return Single when input is List with one valid value`() { + val input = listOf("1627814400") + val expected = Value.Single(1627814400L) + + val result = FieldDateParser.parse(input) + + assertEquals(expected, result) + } + + @Test + fun `parse should return null when List contains no valid entries`() { + val input = listOf("invalid_number", null, "another_invalid") + + val result = FieldDateParser.parse(input) + + assertNull(result) + } + + @Test + fun `parse should return Single when input is List of Numbers and Strings`() { + val input = listOf(1627900800L, "1627814400") + val expected = Value.Single(1627900800L) + + val result = FieldDateParser.parse(input) + + assertEquals(expected, result) + } + + @Test + fun `parse should return null when input is null`() { + val input = null + + val result = FieldDateParser.parse(input) + + assertNull(result) + } + + @Test + fun `parse should return null when input is unsupported type`() { + val input = mapOf("key" to "value") + + val result = FieldDateParser.parse(input) + + assertNull(result) + } + + @Test + fun `parse should handle floating-point numbers in List by converting to Long`() { + val input = listOf(1627814400.99, "invalid_number") + val expected = Value.Single(1627814400L) + + val result = FieldDateParser.parse(input) + + assertEquals(expected, result) + } +} \ No newline at end of file diff --git a/feature-all-content/src/main/java/com/anytypeio/anytype/feature_allcontent/models/AllContentModels.kt b/feature-all-content/src/main/java/com/anytypeio/anytype/feature_allcontent/models/AllContentModels.kt index 39a739f29c..4d4f390644 100644 --- a/feature-all-content/src/main/java/com/anytypeio/anytype/feature_allcontent/models/AllContentModels.kt +++ b/feature-all-content/src/main/java/com/anytypeio/anytype/feature_allcontent/models/AllContentModels.kt @@ -17,11 +17,11 @@ import com.anytypeio.anytype.core_models.primitives.SpaceId import com.anytypeio.anytype.core_models.restrictions.ObjectRestriction import com.anytypeio.anytype.domain.all_content.RestoreAllContentState import com.anytypeio.anytype.domain.misc.UrlBuilder +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.feature_allcontent.presentation.AllContentViewModel.Companion.DEFAULT_INITIAL_TAB import com.anytypeio.anytype.presentation.mapper.objectIcon import com.anytypeio.anytype.presentation.objects.ObjectIcon import com.anytypeio.anytype.presentation.objects.getDescriptionOrSnippet -import com.anytypeio.anytype.presentation.objects.getProperName import com.anytypeio.anytype.presentation.objects.getProperType //region STATE @@ -228,20 +228,12 @@ fun RestoreAllContentState.Response.Success.mapToSort(): AllContentSort { } } -fun List.toUiContentItems( - space: SpaceId, - urlBuilder: UrlBuilder, - objectTypes: List, - isOwnerOrEditor: Boolean -): List { - return map { it.toAllContentItem(space, urlBuilder, objectTypes, isOwnerOrEditor) } -} - fun ObjectWrapper.Basic.toAllContentItem( space: SpaceId, urlBuilder: UrlBuilder, objectTypes: List, - isOwnerOrEditor: Boolean + isOwnerOrEditor: Boolean, + fieldParser: FieldParser ): UiContentItem.Item { val obj = this val typeUrl = obj.getProperType() @@ -250,7 +242,7 @@ fun ObjectWrapper.Basic.toAllContentItem( return UiContentItem.Item( id = obj.id, space = space, - name = obj.getProperName(), + name = fieldParser.getObjectName(obj), description = getDescriptionOrSnippet(), type = typeUrl, typeName = objectTypes.firstOrNull { type -> @@ -280,7 +272,6 @@ fun ObjectWrapper.Basic.toAllContentType( isOwnerOrEditor: Boolean ): UiContentItem.Type { val obj = this - val layout = layout ?: ObjectType.Layout.BASIC return UiContentItem.Type( id = obj.id, name = obj.name.orEmpty(), diff --git a/feature-all-content/src/main/java/com/anytypeio/anytype/feature_allcontent/presentation/AllContentViewModel.kt b/feature-all-content/src/main/java/com/anytypeio/anytype/feature_allcontent/presentation/AllContentViewModel.kt index f2d6ff26fb..b7263fce2f 100644 --- a/feature-all-content/src/main/java/com/anytypeio/anytype/feature_allcontent/presentation/AllContentViewModel.kt +++ b/feature-all-content/src/main/java/com/anytypeio/anytype/feature_allcontent/presentation/AllContentViewModel.kt @@ -14,7 +14,6 @@ import com.anytypeio.anytype.core_models.Relations import com.anytypeio.anytype.core_models.primitives.SpaceId import com.anytypeio.anytype.core_ui.extensions.simpleIcon import com.anytypeio.anytype.core_utils.ext.orNull -import com.anytypeio.anytype.core_utils.ext.toast import com.anytypeio.anytype.domain.all_content.RestoreAllContentState import com.anytypeio.anytype.domain.all_content.UpdateAllContentState import com.anytypeio.anytype.domain.base.fold @@ -26,6 +25,7 @@ import com.anytypeio.anytype.domain.`object`.SetObjectDetails import com.anytypeio.anytype.domain.objects.SetObjectListIsArchived import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes import com.anytypeio.anytype.domain.page.CreateObject +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.search.SearchObjects import com.anytypeio.anytype.domain.workspace.RemoveObjectsFromWorkspace import com.anytypeio.anytype.feature_allcontent.models.AllContentBottomMenu @@ -43,12 +43,14 @@ import com.anytypeio.anytype.feature_allcontent.models.UiTitleState import com.anytypeio.anytype.feature_allcontent.models.createSubscriptionParams import com.anytypeio.anytype.feature_allcontent.models.filtersForSearch import com.anytypeio.anytype.feature_allcontent.models.mapToSort +import com.anytypeio.anytype.feature_allcontent.models.toAllContentItem import com.anytypeio.anytype.feature_allcontent.models.toAnalyticsModeType import com.anytypeio.anytype.feature_allcontent.models.toAnalyticsSortType import com.anytypeio.anytype.feature_allcontent.models.toAnalyticsTabType -import com.anytypeio.anytype.feature_allcontent.models.toUiContentItems import com.anytypeio.anytype.feature_allcontent.models.toUiContentRelations import com.anytypeio.anytype.feature_allcontent.models.toUiContentTypes +import com.anytypeio.anytype.feature_allcontent.presentation.AllContentViewModel.Command.* +import com.anytypeio.anytype.feature_allcontent.presentation.AllContentViewModel.Command.SendToast.* import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate import com.anytypeio.anytype.presentation.extension.sendAnalyticsAllContentChangeMode import com.anytypeio.anytype.presentation.extension.sendAnalyticsAllContentChangeSort @@ -108,7 +110,8 @@ class AllContentViewModel( private val setObjectListIsArchived: SetObjectListIsArchived, private val setObjectDetails: SetObjectDetails, private val removeObjectsFromWorkspace: RemoveObjectsFromWorkspace, - private val userPermissionProvider: UserPermissionProvider + private val userPermissionProvider: UserPermissionProvider, + private val fieldParser: FieldParser ) : ViewModel(), AnalyticSpaceHelperDelegate by analyticSpaceHelperDelegate { private val searchResultIds = MutableStateFlow>(emptyList()) @@ -329,12 +332,15 @@ class AllContentViewModel( } else -> { - val items = objectWrappers.toUiContentItems( - space = vmParams.spaceId, - urlBuilder = urlBuilder, - objectTypes = storeOfObjectTypes.getAll(), - isOwnerOrEditor = isOwnerOrEditor - ) + val items = objectWrappers.map { obj -> + obj.toAllContentItem( + space = vmParams.spaceId, + urlBuilder = urlBuilder, + isOwnerOrEditor = isOwnerOrEditor, + fieldParser = fieldParser, + objectTypes = storeOfObjectTypes.getAll() + ) + } val result = when (activeSort) { is AllContentSort.ByDateCreated -> { groupItemsByDate(items = items, isSortByDateCreated = true, activeSort = activeSort) @@ -715,6 +721,14 @@ class AllContentViewModel( OpenObjectNavigation.NonValidObject -> { Timber.e("Object id is missing") } + is OpenObjectNavigation.OpenDataObject -> { + commands.emit( + NavigateToEditor( + id = navigation.target, + space = navigation.space + ) + ) + } } } } @@ -986,6 +1000,7 @@ class AllContentViewModel( data class NavigateToEditor(val id: Id, val space: Id) : Command() data class NavigateToSetOrCollection(val id: Id, val space: Id) : Command() data class NavigateToBin(val space: Id) : Command() + data class NavigateToDateObject(val objectId: Id, val space: Id) : Command() sealed class SendToast: Command() { data class Error(val message: String) : SendToast() data class RelationRemoved(val name: String) : SendToast() diff --git a/feature-all-content/src/main/java/com/anytypeio/anytype/feature_allcontent/presentation/AllContentViewModelFactory.kt b/feature-all-content/src/main/java/com/anytypeio/anytype/feature_allcontent/presentation/AllContentViewModelFactory.kt index 98ea554e30..820295a6cc 100644 --- a/feature-all-content/src/main/java/com/anytypeio/anytype/feature_allcontent/presentation/AllContentViewModelFactory.kt +++ b/feature-all-content/src/main/java/com/anytypeio/anytype/feature_allcontent/presentation/AllContentViewModelFactory.kt @@ -13,6 +13,7 @@ import com.anytypeio.anytype.domain.`object`.SetObjectDetails import com.anytypeio.anytype.domain.objects.SetObjectListIsArchived import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes import com.anytypeio.anytype.domain.page.CreateObject +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.search.SearchObjects import com.anytypeio.anytype.domain.workspace.RemoveObjectsFromWorkspace import com.anytypeio.anytype.feature_allcontent.presentation.AllContentViewModel.VmParams @@ -34,7 +35,8 @@ class AllContentViewModelFactory @Inject constructor( private val setObjectListIsArchived: SetObjectListIsArchived, private val setObjectDetails: SetObjectDetails, private val removeObjectsFromWorkspace: RemoveObjectsFromWorkspace, - private val userPermissionProvider: UserPermissionProvider + private val userPermissionProvider: UserPermissionProvider, + private val fieldParser: FieldParser ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class): T = @@ -53,6 +55,7 @@ class AllContentViewModelFactory @Inject constructor( setObjectListIsArchived = setObjectListIsArchived, setObjectDetails = setObjectDetails, removeObjectsFromWorkspace = removeObjectsFromWorkspace, - userPermissionProvider = userPermissionProvider + userPermissionProvider = userPermissionProvider, + fieldParser = fieldParser ) as T } \ No newline at end of file diff --git a/feature-date/build.gradle b/feature-date/build.gradle new file mode 100644 index 0000000000..a6230fba55 --- /dev/null +++ b/feature-date/build.gradle @@ -0,0 +1,60 @@ +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_date' +} + +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') + implementation project(':library-emojifier') + + compileOnly libs.javaxInject + + implementation libs.lifecycleViewModel + implementation libs.lifecycleRuntime + + implementation libs.appcompat + implementation libs.compose + implementation libs.composeFoundation + implementation libs.composeToolingPreview + implementation libs.composeMaterial3 + implementation libs.composeMaterial + 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.robolectric + 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-date/src/main/AndroidManifest.xml b/feature-date/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..1d26c87a17 --- /dev/null +++ b/feature-date/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/feature-date/src/main/java/com/anytypeio/anytype/feature_date/mapping/DateObjectModelsExt.kt b/feature-date/src/main/java/com/anytypeio/anytype/feature_date/mapping/DateObjectModelsExt.kt new file mode 100644 index 0000000000..1fb1536f4c --- /dev/null +++ b/feature-date/src/main/java/com/anytypeio/anytype/feature_date/mapping/DateObjectModelsExt.kt @@ -0,0 +1,81 @@ +package com.anytypeio.anytype.feature_date.mapping + +import com.anytypeio.anytype.core_models.MarketplaceObjectTypeIds +import com.anytypeio.anytype.core_models.ObjectType +import com.anytypeio.anytype.core_models.ObjectTypeUniqueKeys +import com.anytypeio.anytype.core_models.ObjectWrapper +import com.anytypeio.anytype.core_models.RelationListWithValueItem +import com.anytypeio.anytype.core_models.Relations +import com.anytypeio.anytype.core_models.primitives.SpaceId +import com.anytypeio.anytype.domain.misc.UrlBuilder +import com.anytypeio.anytype.domain.objects.StoreOfRelations +import com.anytypeio.anytype.domain.primitives.FieldParser +import com.anytypeio.anytype.feature_date.viewmodel.UiFieldsItem +import com.anytypeio.anytype.feature_date.viewmodel.UiObjectsListItem +import com.anytypeio.anytype.presentation.mapper.objectIcon +import com.anytypeio.anytype.presentation.objects.getProperType +import timber.log.Timber + +suspend fun List.toUiFieldsItem( + storeOfRelations: StoreOfRelations +): List { + return this + .sortedByDescending { it.key.key == Relations.MENTIONS } + .mapNotNull { item -> + val relation = storeOfRelations.getByKey(item.key.key) + if (relation == null) { + Timber.e("Relation ${item.key.key} not found in the relation store") + return@mapNotNull null + } + if (relation.key == Relations.LINKS || relation.key == Relations.BACKLINKS) { + Timber.w("Relation ${item.key.key} is LINKS or BACKLINKS") + return@mapNotNull null + } + if (relation.key != Relations.MENTIONS && relation.isHidden == true) { + Timber.w("Relation ${item.key.key} is hidden") + return@mapNotNull null + } + if (relation.key == Relations.MENTIONS) { + UiFieldsItem.Item.Mention( + id = item.key.key, + key = item.key, + title = relation.name.orEmpty(), + relationFormat = relation.format + ) + } else { + UiFieldsItem.Item.Default( + id = item.key.key, + key = item.key, + title = relation.name.orEmpty(), + relationFormat = relation.format + ) + } + } +} + +fun ObjectWrapper.Basic.toUiObjectsListItem( + space: SpaceId, + urlBuilder: UrlBuilder, + objectTypes: List, + fieldParser: FieldParser +): UiObjectsListItem { + val obj = this + val typeUrl = obj.getProperType() + val isProfile = typeUrl == MarketplaceObjectTypeIds.PROFILE + val layout = obj.layout ?: ObjectType.Layout.BASIC + return UiObjectsListItem.Item( + id = obj.id, + space = space, + name = fieldParser.getObjectName(obj), + type = typeUrl, + typeName = objectTypes.firstOrNull { type -> + if (isProfile) { + type.uniqueKey == ObjectTypeUniqueKeys.PROFILE + } else { + type.id == typeUrl + } + }?.name, + layout = layout, + icon = obj.objectIcon(builder = urlBuilder) + ) +} \ No newline at end of file diff --git a/feature-date/src/main/java/com/anytypeio/anytype/feature_date/ui/CalendarScreen.kt b/feature-date/src/main/java/com/anytypeio/anytype/feature_date/ui/CalendarScreen.kt new file mode 100644 index 0000000000..bd1517aef7 --- /dev/null +++ b/feature-date/src/main/java/com/anytypeio/anytype/feature_date/ui/CalendarScreen.kt @@ -0,0 +1,49 @@ +package com.anytypeio.anytype.feature_date.ui + +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.unit.dp +import com.anytypeio.anytype.core_ui.relations.DatePickerContent +import com.anytypeio.anytype.feature_date.R +import com.anytypeio.anytype.feature_date.viewmodel.UiCalendarState +import com.anytypeio.anytype.feature_date.ui.models.DateEvent +import com.anytypeio.anytype.presentation.sets.DateValueView + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun CalendarScreen( + uiState: UiCalendarState, + onDateEvent: (DateEvent) -> Unit +) { + + val bottomSheetState = rememberModalBottomSheetState( + skipPartiallyExpanded = true + ) + ModalBottomSheet( + dragHandle = null, + scrimColor = colorResource(id = R.color.modal_screen_outside_background), + containerColor = colorResource(id = R.color.background_secondary), + shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp), + sheetState = bottomSheetState, + onDismissRequest = { onDateEvent(DateEvent.Calendar.OnCalendarDismiss) }, + content = { + when (uiState) { + is UiCalendarState.Calendar -> { + DatePickerContent( + state = DateValueView(timeInMillis = uiState.timeInMillis), + showHeader = false, + onDateSelected = { onDateEvent(DateEvent.Calendar.OnCalendarDateSelected(it)) }, + onTodayClicked = { onDateEvent(DateEvent.Calendar.OnTodayClick) }, + onTomorrowClicked = { onDateEvent(DateEvent.Calendar.OnTomorrowClick) } + ) + } + + UiCalendarState.Hidden -> {} + } + }, + ) +} \ No newline at end of file diff --git a/feature-date/src/main/java/com/anytypeio/anytype/feature_date/ui/FieldsScreen.kt b/feature-date/src/main/java/com/anytypeio/anytype/feature_date/ui/FieldsScreen.kt new file mode 100644 index 0000000000..34a9987226 --- /dev/null +++ b/feature-date/src/main/java/com/anytypeio/anytype/feature_date/ui/FieldsScreen.kt @@ -0,0 +1,199 @@ +package com.anytypeio.anytype.feature_date.ui + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +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.width +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.anytypeio.anytype.core_models.primitives.RelationKey +import com.anytypeio.anytype.core_ui.common.DefaultPreviews +import com.anytypeio.anytype.core_ui.common.ShimmerEffect +import com.anytypeio.anytype.core_ui.extensions.swapList +import com.anytypeio.anytype.core_ui.foundation.noRippleThrottledClickable +import com.anytypeio.anytype.core_ui.views.PreviewTitle2Medium +import com.anytypeio.anytype.feature_date.R +import com.anytypeio.anytype.feature_date.ui.models.DateEvent +import com.anytypeio.anytype.feature_date.ui.models.StubHorizontalItems +import com.anytypeio.anytype.feature_date.viewmodel.UiFieldsItem +import com.anytypeio.anytype.feature_date.viewmodel.UiFieldsState + +@Composable +fun FieldsScreen( + uiState: UiFieldsState, + onDateEvent: (DateEvent) -> Unit +) { + + val lazyFieldsListState = rememberLazyListState() + + val items = remember { mutableStateListOf() } + items.swapList(uiState.items) + + // Effect to scroll to the selected item when needToScrollTo and selectedRelationKey are set + LaunchedEffect(uiState.needToScrollTo, uiState.selectedRelationKey) { + if (uiState.needToScrollTo && uiState.selectedRelationKey != null) { + val relationKey = uiState.selectedRelationKey + val index = items.indexOfFirst { it.id == relationKey.key } + if (index != -1) { + lazyFieldsListState.animateScrollToItem(index) + onDateEvent(DateEvent.FieldsList.OnScrolledToItemDismiss) + } + } + } + + LazyRow( + state = lazyFieldsListState, + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp), + contentPadding = PaddingValues(start = 16.dp, end = 16.dp, top = 24.dp) + ) { + items( + count = items.size, + key = { items[it].id }, + contentType = { index -> + when (items[index]) { + is UiFieldsItem.Settings -> "settings" + is UiFieldsItem.Item -> "item" + is UiFieldsItem.Loading -> "loading" + } + } + ) { + val item = items[it] + val background = if (uiState.selectedRelationKey?.key == item.id) { + colorResource(R.color.shape_secondary) + } else { + Color.Transparent + } + Box( + modifier = Modifier + .height(40.dp) + .wrapContentWidth() + .background( + color = background, + shape = RoundedCornerShape(size = 10.dp) + ) + .border( + width = 1.dp, + color = colorResource(R.color.shape_primary), + shape = RoundedCornerShape(size = 10.dp) + ) + .noRippleThrottledClickable { + onDateEvent(DateEvent.FieldsList.OnFieldClick(item)) + } + ) { + when (item) { + is UiFieldsItem.Settings -> { + Image( + modifier = Modifier + .padding(horizontal = 8.dp) + .size(24.dp) + .align(Alignment.Center), + painter = painterResource(R.drawable.ic_burger_24), + contentDescription = "List of date relations" + ) + } + + is UiFieldsItem.Item.Mention -> { + Row( + modifier = Modifier + .fillParentMaxHeight() + .wrapContentWidth() + .padding(horizontal = 12.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Image( + modifier = Modifier + .padding(end = 6.dp) + .size(24.dp), + painter = painterResource(R.drawable.ic_mention_24), + contentDescription = "Mentioned in" + ) + Text( + modifier = Modifier + .wrapContentSize(), + text = stringResource(R.string.date_layout_mentioned_in), + color = colorResource(R.color.text_primary), + style = PreviewTitle2Medium + ) + } + } + + is UiFieldsItem.Item.Default -> { + Text( + modifier = Modifier + .padding(horizontal = 12.dp) + .wrapContentSize() + .align(Alignment.Center), + text = item.title, + color = colorResource(R.color.text_primary), + style = PreviewTitle2Medium + ) + } + + is UiFieldsItem.Loading.Item -> { + ShimmerEffect( + modifier = Modifier + .padding(8.dp) + .align(Alignment.Center) + .width(88.dp) + .height(20.dp) + ) + } + + is UiFieldsItem.Loading.Settings -> { + ShimmerEffect( + modifier = Modifier + .padding(8.dp) + .align(Alignment.Center) + .size(24.dp) + ) + } + } + } + } + } +} + +@Composable +@DefaultPreviews +fun FieldsScreenPreview() { + FieldsScreen( + uiState = UiFieldsState( + items = StubHorizontalItems, + selectedRelationKey = RelationKey("1") + ), + onDateEvent = {} + ) +} + +@Composable +@DefaultPreviews +fun LoadingPreview() { + FieldsScreen( + uiState = UiFieldsState.LoadingState, + onDateEvent = {} + ) +} \ No newline at end of file diff --git a/feature-date/src/main/java/com/anytypeio/anytype/feature_date/ui/FieldsSheetScreen.kt b/feature-date/src/main/java/com/anytypeio/anytype/feature_date/ui/FieldsSheetScreen.kt new file mode 100644 index 0000000000..d63531cb00 --- /dev/null +++ b/feature-date/src/main/java/com/anytypeio/anytype/feature_date/ui/FieldsSheetScreen.kt @@ -0,0 +1,320 @@ +package com.anytypeio.anytype.feature_date.ui + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.PaddingValues +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.width +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.selection.LocalTextSelectionColors +import androidx.compose.foundation.text.selection.TextSelectionColors +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.TextFieldDefaults +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.CompositionLocalProvider +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.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import com.anytypeio.anytype.core_ui.common.DefaultPreviews +import com.anytypeio.anytype.core_ui.foundation.Divider +import com.anytypeio.anytype.core_ui.foundation.Dragger +import com.anytypeio.anytype.core_ui.foundation.noRippleClickable +import com.anytypeio.anytype.core_ui.foundation.noRippleThrottledClickable +import com.anytypeio.anytype.core_ui.views.BodyRegular +import com.anytypeio.anytype.feature_date.R +import com.anytypeio.anytype.feature_date.viewmodel.UiFieldsSheetState +import com.anytypeio.anytype.feature_date.viewmodel.UiFieldsItem +import com.anytypeio.anytype.feature_date.ui.models.DateEvent +import com.anytypeio.anytype.feature_date.ui.models.StubHorizontalItems + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun FieldsSheetScreen( + uiState: UiFieldsSheetState, + onDateEvent: (DateEvent) -> Unit +) { + + val bottomSheetState = rememberModalBottomSheetState( + skipPartiallyExpanded = true + ) + ModalBottomSheet( + dragHandle = { + Column { + Spacer(modifier = Modifier.height(6.dp)) + Dragger() + Spacer(modifier = Modifier.height(6.dp)) + } + }, + scrimColor = colorResource(id = R.color.modal_screen_outside_background), + containerColor = colorResource(id = R.color.background_secondary), + shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp), + sheetState = bottomSheetState, + onDismissRequest = { + onDateEvent(DateEvent.FieldsSheet.OnSheetDismiss) + }, + content = { + when (uiState) { + is UiFieldsSheetState.Visible -> { + DateObjectSheetScreen( + uiSheetState = uiState, + onDateEvent = onDateEvent + ) + } + + UiFieldsSheetState.Hidden -> {} + } + }, + ) +} + + +@Composable +private fun ColumnScope.DateObjectSheetScreen( + uiSheetState: UiFieldsSheetState.Visible, + onDateEvent: (DateEvent) -> Unit +) { + val listState = rememberLazyListState() + Spacer(Modifier.height(10.dp)) + SearchBar(onDateEvent = onDateEvent) + Spacer(Modifier.height(10.dp)) + Divider(paddingStart = 0.dp, paddingEnd = 0.dp) + LazyColumn( + modifier = Modifier.fillMaxWidth(), + state = listState + ) { + items( + count = uiSheetState.items.size, + key = { index -> uiSheetState.items[index].id }, + itemContent = { index -> + when (val item = uiSheetState.items[index]) { + is UiFieldsItem.Item.Default -> { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp) + .height(52.dp) + .noRippleThrottledClickable { + onDateEvent(DateEvent.FieldsSheet.OnFieldClick(item)) + }, + contentAlignment = Alignment.CenterStart, + ) { + Text( + modifier = Modifier.fillMaxWidth(), + text = item.title, + maxLines = 1, + color = colorResource(R.color.text_primary), + style = BodyRegular, + textAlign = TextAlign.Start + ) + } + Divider() + } + + is UiFieldsItem.Item.Mention -> { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp) + .height(52.dp) + .noRippleThrottledClickable { + onDateEvent(DateEvent.FieldsSheet.OnFieldClick(item)) + }, + contentAlignment = Alignment.CenterStart, + ) { + Row( + modifier = Modifier + .fillParentMaxHeight() + .wrapContentWidth(), + verticalAlignment = Alignment.CenterVertically, + ) { + Image( + modifier = Modifier + .padding(end = 6.dp) + .size(24.dp), + painter = painterResource(R.drawable.ic_mention_24), + contentDescription = "Mentioned in" + ) + Text( + modifier = Modifier + .wrapContentSize(), + text = stringResource(R.string.date_layout_mentioned_in), + color = colorResource(R.color.text_primary), + style = BodyRegular + ) + } + } + Divider() + } + + else -> { + //do nothing + } + } + } + ) + } + Spacer(Modifier.height(64.dp)) +} + +//region SearchBar +@OptIn(ExperimentalMaterialApi::class) +@Composable +private fun SearchBar(onDateEvent: (DateEvent) -> Unit) { + + val interactionSource = remember { MutableInteractionSource() } + val focus = LocalFocusManager.current + val focusRequester = FocusRequester() + + val selectionColors = TextSelectionColors( + backgroundColor = colorResource(id = R.color.cursor_color).copy( + alpha = 0.2f + ), + handleColor = colorResource(id = R.color.cursor_color), + ) + + var query by remember { mutableStateOf(TextFieldValue()) } + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp) + .background( + color = colorResource(id = R.color.shape_transparent), + shape = RoundedCornerShape(10.dp) + ) + .height(40.dp), + verticalAlignment = Alignment.CenterVertically + + ) { + Image( + painter = painterResource(id = R.drawable.ic_search_18), + contentDescription = "Search icon", + modifier = Modifier + .align(Alignment.CenterVertically) + .padding( + start = 11.dp + ) + ) + CompositionLocalProvider(value = LocalTextSelectionColors provides selectionColors) { + + BasicTextField( + value = query, + modifier = Modifier + .weight(1.0f) + .padding(start = 6.dp) + .align(Alignment.CenterVertically) + .focusRequester(focusRequester), + textStyle = BodyRegular.copy( + color = colorResource(id = R.color.text_primary) + ), + onValueChange = { input -> + query = input.also { + onDateEvent(DateEvent.FieldsSheet.OnSearchQueryChanged(input.text)) + } + }, + singleLine = true, + maxLines = 1, + keyboardActions = KeyboardActions( + onDone = { + focus.clearFocus(true) + } + ), + decorationBox = @Composable { innerTextField -> + TextFieldDefaults.OutlinedTextFieldDecorationBox( + value = query.text, + innerTextField = innerTextField, + enabled = true, + singleLine = true, + visualTransformation = VisualTransformation.None, + interactionSource = interactionSource, + placeholder = { + Text( + text = stringResource(id = R.string.search), + style = BodyRegular.copy( + color = colorResource(id = R.color.text_tertiary) + ) + ) + }, + colors = TextFieldDefaults.textFieldColors( + backgroundColor = Color.Transparent, + cursorColor = colorResource(id = R.color.cursor_color), + ), + border = {}, + contentPadding = PaddingValues() + ) + }, + cursorBrush = SolidColor(colorResource(id = R.color.palette_system_blue)), + ) + } + Spacer(Modifier.width(9.dp)) + AnimatedVisibility( + visible = query.text.isNotEmpty(), + enter = fadeIn(tween(100)), + exit = fadeOut(tween(100)) + ) { + Image( + painter = painterResource(id = R.drawable.ic_clear_18), + contentDescription = "Clear icon", + modifier = Modifier + .padding(end = 9.dp) + .noRippleClickable { + query = TextFieldValue().also { + onDateEvent(DateEvent.FieldsSheet.OnSearchQueryChanged("")) + } + } + ) + } + } +} + +@DefaultPreviews +@Composable +private fun SearchBarPreview() { + Column { + DateObjectSheetScreen( + uiSheetState = UiFieldsSheetState.Visible( + items = StubHorizontalItems + ), + onDateEvent = {} + ) + } +} +//endregion \ No newline at end of file diff --git a/feature-date/src/main/java/com/anytypeio/anytype/feature_date/ui/HeaderScreen.kt b/feature-date/src/main/java/com/anytypeio/anytype/feature_date/ui/HeaderScreen.kt new file mode 100644 index 0000000000..70e933b4e8 --- /dev/null +++ b/feature-date/src/main/java/com/anytypeio/anytype/feature_date/ui/HeaderScreen.kt @@ -0,0 +1,143 @@ +package com.anytypeio.anytype.feature_date.ui + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +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.width +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.rotate +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import com.anytypeio.anytype.core_ui.common.DefaultPreviews +import com.anytypeio.anytype.core_ui.common.ShimmerEffect +import com.anytypeio.anytype.core_ui.foundation.noRippleThrottledClickable +import com.anytypeio.anytype.core_ui.views.HeadlineTitle +import com.anytypeio.anytype.feature_date.R +import com.anytypeio.anytype.feature_date.viewmodel.UiHeaderState +import com.anytypeio.anytype.feature_date.ui.models.DateEvent + +@Composable +fun HeaderScreen( + modifier: Modifier, + uiState: UiHeaderState, + onDateEvent: (DateEvent) -> Unit +) { + Row( + modifier = modifier + ) { + Image( + modifier = Modifier + .height(48.dp) + .width(52.dp) + .rotate(180f) + .noRippleThrottledClickable { + onDateEvent(DateEvent.Header.OnPreviousClick) + }, + contentDescription = "Previous day", + painter = painterResource(id = R.drawable.ic_arrow_disclosure_18), + contentScale = ContentScale.None + ) + when (uiState) { + is UiHeaderState.Content -> { + Text( + textAlign = TextAlign.Center, + text = uiState.title, + modifier = Modifier + .fillMaxWidth() + .weight(1f) + .align(Alignment.CenterVertically), + style = HeadlineTitle, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + color = colorResource(id = R.color.text_primary) + ) + } + + UiHeaderState.Loading -> { + Box( + modifier = Modifier + .fillMaxWidth() + .weight(1f) + .align(Alignment.CenterVertically), + contentAlignment = Alignment.Center + ) { + ShimmerEffect( + modifier = Modifier + .width(200.dp) + .height(30.dp) + ) + } + } + + UiHeaderState.Empty -> { + Box( + modifier = Modifier + .fillMaxWidth() + .weight(1f) + .align(Alignment.CenterVertically), + contentAlignment = Alignment.Center + ) { + Spacer( + modifier = Modifier + .width(200.dp) + .height(30.dp) + ) + } + } + } + Image( + modifier = Modifier + .height(48.dp) + .width(52.dp) + .noRippleThrottledClickable { + onDateEvent(DateEvent.Header.OnNextClick) + }, + contentDescription = "Next day", + painter = painterResource(id = R.drawable.ic_arrow_disclosure_18), + contentScale = ContentScale.None + ) + } +} + +@Composable +@DefaultPreviews +fun DateLayoutHeaderEmptyPreview() { + val state = UiHeaderState.Empty + HeaderScreen( + modifier = Modifier + .fillMaxWidth() + .height(48.dp), uiState = state + ) {} +} + +@Composable +@DefaultPreviews +fun DateLayoutHeaderLoadingPreview() { + val state = UiHeaderState.Loading + HeaderScreen( + Modifier + .fillMaxWidth() + .height(48.dp), state + ) {} +} + +@Composable +@DefaultPreviews +fun DateLayoutHeaderPreview() { + val state = UiHeaderState.Content("Tue, 12 Oct") + HeaderScreen( + Modifier + .fillMaxWidth() + .height(48.dp), state + ) {} +} \ No newline at end of file diff --git a/feature-date/src/main/java/com/anytypeio/anytype/feature_date/ui/MainScreen.kt b/feature-date/src/main/java/com/anytypeio/anytype/feature_date/ui/MainScreen.kt new file mode 100644 index 0000000000..de4aab9f2f --- /dev/null +++ b/feature-date/src/main/java/com/anytypeio/anytype/feature_date/ui/MainScreen.kt @@ -0,0 +1,177 @@ +package com.anytypeio.anytype.feature_date.ui + +import android.os.Build +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBars +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.statusBars +import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.foundation.layout.windowInsetsTopHeight +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.unit.dp +import com.anytypeio.anytype.core_ui.foundation.components.BottomNavigationMenu +import com.anytypeio.anytype.core_ui.syncstatus.SpaceSyncStatusScreen +import com.anytypeio.anytype.core_utils.insets.EDGE_TO_EDGE_MIN_SDK +import com.anytypeio.anytype.feature_date.R +import com.anytypeio.anytype.feature_date.ui.models.DateEvent +import com.anytypeio.anytype.feature_date.viewmodel.UiCalendarIconState +import com.anytypeio.anytype.feature_date.viewmodel.UiCalendarState +import com.anytypeio.anytype.feature_date.viewmodel.UiContentState +import com.anytypeio.anytype.feature_date.viewmodel.UiFieldsSheetState +import com.anytypeio.anytype.feature_date.viewmodel.UiFieldsState +import com.anytypeio.anytype.feature_date.viewmodel.UiHeaderState +import com.anytypeio.anytype.feature_date.viewmodel.UiNavigationWidget +import com.anytypeio.anytype.feature_date.viewmodel.UiObjectsListState +import com.anytypeio.anytype.feature_date.viewmodel.UiSyncStatusBadgeState +import com.anytypeio.anytype.feature_date.viewmodel.UiSyncStatusWidgetState + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun DateMainScreen( + uiCalendarIconState: UiCalendarIconState, + uiSyncStatusBadgeState: UiSyncStatusBadgeState, + uiHeaderState: UiHeaderState, + uiFieldsState: UiFieldsState, + uiObjectsListState: UiObjectsListState, + uiNavigationWidget: UiNavigationWidget, + uiFieldsSheetState: UiFieldsSheetState, + uiSyncStatusState: UiSyncStatusWidgetState, + uiCalendarState: UiCalendarState, + uiContentState: UiContentState, + canPaginate: Boolean, + onDateEvent: (DateEvent) -> Unit +) { + + val scope = rememberCoroutineScope() + + Scaffold( + modifier = Modifier.fillMaxSize(), + containerColor = colorResource(id = R.color.background_primary), + contentColor = colorResource(id = R.color.background_primary), + topBar = { + Column( + modifier = Modifier + .fillMaxWidth() + .background(color = colorResource(id = R.color.background_primary)) + ) { + if (Build.VERSION.SDK_INT >= EDGE_TO_EDGE_MIN_SDK) { + Spacer( + modifier = Modifier.windowInsetsTopHeight( + WindowInsets.statusBars + ) + ) + } + TopToolbarScreen( + modifier = Modifier + .fillMaxWidth() + .height(48.dp), + uiCalendarIconState = uiCalendarIconState, + uiSyncStatusBadgeState = uiSyncStatusBadgeState, + onDateEvent = onDateEvent + ) + Spacer( + modifier = Modifier.height(24.dp) + ) + HeaderScreen( + modifier = Modifier + .fillMaxWidth() + .height(48.dp), + uiState = uiHeaderState, + onDateEvent = onDateEvent + ) + FieldsScreen( + uiState = uiFieldsState, + onDateEvent = onDateEvent + ) + Spacer( + modifier = Modifier.height(8.dp) + ) + } + }, + content = { paddingValues -> + val contentModifier = + if (Build.VERSION.SDK_INT >= EDGE_TO_EDGE_MIN_SDK) + Modifier + .windowInsetsPadding(WindowInsets.navigationBars) + .fillMaxSize() + .padding(top = paddingValues.calculateTopPadding()) + else + Modifier + .fillMaxSize() + .padding(paddingValues) + Box( + modifier = contentModifier, + contentAlignment = Alignment.TopCenter + ) { + if (uiContentState is UiContentState.Empty) { + EmptyScreen() + } + ObjectsScreen( + state = uiObjectsListState, + uiState = uiContentState, + canPaginate = canPaginate, + onDateEvent = onDateEvent, + ) + BottomNavigationMenu( + modifier = Modifier + .align(Alignment.BottomCenter) + .padding(bottom = 16.dp), + backClick = { + onDateEvent(DateEvent.NavigationWidget.OnBackClick) + }, + backLongClick = { + onDateEvent(DateEvent.NavigationWidget.OnBackLongClick) + }, + searchClick = { + onDateEvent(DateEvent.NavigationWidget.OnGlobalSearchClick) + }, + addDocClick = { + onDateEvent(DateEvent.NavigationWidget.OnAddDocClick) + }, + addDocLongClick = { + onDateEvent(DateEvent.NavigationWidget.OnAddDocLongClick) + }, + isOwnerOrEditor = uiNavigationWidget is UiNavigationWidget.Editor + ) + } + } + ) + if (uiSyncStatusState is UiSyncStatusWidgetState.Visible) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.BottomCenter + ) { + SpaceSyncStatusScreen( + uiState = uiSyncStatusState.status, + onDismiss = { onDateEvent(DateEvent.SyncStatusWidget.OnSyncStatusDismiss) }, + scope = scope, + onUpdateAppClick = {} + ) + } + } + if (uiFieldsSheetState is UiFieldsSheetState.Visible) { + FieldsSheetScreen( + uiState = uiFieldsSheetState, + onDateEvent = onDateEvent + ) + } + if (uiCalendarState is UiCalendarState.Calendar) { + CalendarScreen( + uiState = uiCalendarState, + onDateEvent = onDateEvent + ) + } +} \ No newline at end of file diff --git a/feature-date/src/main/java/com/anytypeio/anytype/feature_date/ui/ObjectsScreen.kt b/feature-date/src/main/java/com/anytypeio/anytype/feature_date/ui/ObjectsScreen.kt new file mode 100644 index 0000000000..9ff10a7fa8 --- /dev/null +++ b/feature-date/src/main/java/com/anytypeio/anytype/feature_date/ui/ObjectsScreen.kt @@ -0,0 +1,258 @@ +package com.anytypeio.anytype.feature_date.ui + +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +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.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material.Text +import androidx.compose.material3.ListItem +import androidx.compose.material3.ListItemDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import com.anytypeio.anytype.core_ui.common.DefaultPreviews +import com.anytypeio.anytype.core_ui.common.ShimmerEffect +import com.anytypeio.anytype.core_ui.extensions.swapList +import com.anytypeio.anytype.core_ui.foundation.noRippleThrottledClickable +import com.anytypeio.anytype.core_ui.views.ButtonSize +import com.anytypeio.anytype.core_ui.views.PreviewTitle2Regular +import com.anytypeio.anytype.core_ui.views.Relations3 +import com.anytypeio.anytype.core_ui.views.animations.DotsLoadingIndicator +import com.anytypeio.anytype.core_ui.views.animations.FadeAnimationSpecs +import com.anytypeio.anytype.core_ui.widgets.ListWidgetObjectIcon +import com.anytypeio.anytype.feature_date.R +import com.anytypeio.anytype.feature_date.ui.models.DateEvent +import com.anytypeio.anytype.feature_date.ui.models.StubVerticalItems +import com.anytypeio.anytype.feature_date.viewmodel.UiContentState +import com.anytypeio.anytype.feature_date.viewmodel.UiObjectsListItem +import com.anytypeio.anytype.feature_date.viewmodel.UiObjectsListState +import kotlinx.coroutines.launch + +@Composable +fun ObjectsScreen( + state: UiObjectsListState, + uiState: UiContentState, + canPaginate: Boolean, + onDateEvent: (DateEvent) -> Unit +) { + val items = remember { mutableStateListOf() } + items.swapList(state.items) + + val scope = rememberCoroutineScope() + + val lazyListState = rememberLazyListState() + + val canPaginateState = remember { mutableStateOf(false) } + LaunchedEffect(key1 = canPaginate) { + canPaginateState.value = canPaginate + } + + val shouldStartPaging = remember { + derivedStateOf { + canPaginateState.value && (lazyListState.layoutInfo.visibleItemsInfo.lastOrNull()?.index + ?: -9) >= (lazyListState.layoutInfo.totalItemsCount - 2) + } + } + + LaunchedEffect(key1 = shouldStartPaging.value) { + if (shouldStartPaging.value && uiState is UiContentState.Idle) { + onDateEvent(DateEvent.ObjectsList.OnLoadMore) + } + } + + LazyColumn( + modifier = Modifier + .padding(top = 8.dp) + .fillMaxSize(), + state = lazyListState + ) { + items( + count = items.size, + key = { index -> items[index].id }, + contentType = { index -> + when (items[index]) { + is UiObjectsListItem.Loading -> "loading" + is UiObjectsListItem.Item -> "item" + } + } + ) { index -> + val item = items[index] + when (item) { + is UiObjectsListItem.Item -> { + ListItem( + modifier = Modifier + .noRippleThrottledClickable { + onDateEvent(DateEvent.ObjectsList.OnObjectClicked(item)) + }, + item = item + ) + } + + is UiObjectsListItem.Loading -> { + ListItemLoading(modifier = Modifier) + } + } + } + if (uiState is UiContentState.Paging) { + item { + Box( + modifier = Modifier + .fillParentMaxWidth() + .height(52.dp), + contentAlignment = Alignment.Center + ) { + LoadingState() + } + } + } + item { + Spacer(modifier = Modifier.height(200.dp)) + } + } + + LaunchedEffect(key1 = uiState) { + if (uiState is UiContentState.Idle) { + if (uiState.scrollToTop) { + scope.launch { + lazyListState.scrollToItem(0) + } + } + } + } +} + +@Composable +private fun ListItem( + modifier: Modifier, + item: UiObjectsListItem.Item +) { + val name = item.name.trim().ifBlank { stringResource(R.string.untitled) } + val createdBy = item.createdBy + val typeName = item.typeName + ListItem( + colors = ListItemDefaults.colors( + containerColor = colorResource(id = R.color.background_primary), + ), + modifier = modifier + .height(72.dp) + .fillMaxWidth(), + headlineContent = { + Text( + text = name, + style = PreviewTitle2Regular, + color = colorResource(id = R.color.text_primary), + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + }, + supportingContent = { + Row { + if (typeName != null) { + Text( + text = typeName, + style = Relations3, + color = colorResource(id = R.color.text_secondary), + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } + if (!createdBy.isNullOrBlank()) { + Text( + text = "${stringResource(R.string.date_layout_item_created_by)} • $createdBy", + style = Relations3, + color = colorResource(id = R.color.text_secondary), + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } + } + }, + leadingContent = { + ListWidgetObjectIcon(icon = item.icon, modifier = Modifier, iconSize = 48.dp) + } + ) +} + + +@Composable +private fun ListItemLoading( + modifier: Modifier +) { + ListItem( + colors = ListItemDefaults.colors( + containerColor = colorResource(id = R.color.background_primary), + ), + modifier = modifier + .height(72.dp) + .fillMaxWidth(), + headlineContent = { + ShimmerEffect( + modifier = Modifier + .width(164.dp) + .height(18.dp) + ) + }, + supportingContent = { + ShimmerEffect( + modifier = Modifier + .width(64.dp) + .height(13.dp) + ) + }, + leadingContent = { + ShimmerEffect( + modifier = Modifier + .size(48.dp) + ) + } + ) +} + +@Composable +private fun BoxScope.LoadingState() { + val loadingAlpha by animateFloatAsState(targetValue = 1f, label = "") + DotsLoadingIndicator( + animating = true, + modifier = Modifier + .graphicsLayer { alpha = loadingAlpha } + .align(Alignment.Center), + animationSpecs = FadeAnimationSpecs(itemCount = 3), + color = colorResource(id = R.color.glyph_active), + size = ButtonSize.Small + ) +} + +@Composable +@DefaultPreviews +fun ObjectsListScreenPreview() { + val contentListState = UiObjectsListState( + items = StubVerticalItems + ) + ObjectsScreen( + state = contentListState, + uiState = UiContentState.Idle(scrollToTop = false), + canPaginate = true, + onDateEvent = {} + ) +} \ No newline at end of file diff --git a/feature-date/src/main/java/com/anytypeio/anytype/feature_date/ui/OtherScreen.kt b/feature-date/src/main/java/com/anytypeio/anytype/feature_date/ui/OtherScreen.kt new file mode 100644 index 0000000000..921a1da7eb --- /dev/null +++ b/feature-date/src/main/java/com/anytypeio/anytype/feature_date/ui/OtherScreen.kt @@ -0,0 +1,40 @@ +package com.anytypeio.anytype.feature_date.ui + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.ime +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import com.anytypeio.anytype.core_ui.views.UXBody +import com.anytypeio.anytype.feature_date.R + +@Composable +fun EmptyScreen() { + val title = stringResource(R.string.date_layout_empty_items) + Box( + modifier = Modifier + .windowInsetsPadding(WindowInsets.ime) + .fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Text( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 32.dp), + text = title, + color = colorResource(id = R.color.text_primary), + style = UXBody, + textAlign = TextAlign.Center + ) + } +} \ No newline at end of file diff --git a/feature-date/src/main/java/com/anytypeio/anytype/feature_date/ui/TopToolbarScreen.kt b/feature-date/src/main/java/com/anytypeio/anytype/feature_date/ui/TopToolbarScreen.kt new file mode 100644 index 0000000000..f893644d16 --- /dev/null +++ b/feature-date/src/main/java/com/anytypeio/anytype/feature_date/ui/TopToolbarScreen.kt @@ -0,0 +1,106 @@ +package com.anytypeio.anytype.feature_date.ui + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp +import com.anytypeio.anytype.core_models.multiplayer.P2PStatusUpdate +import com.anytypeio.anytype.core_models.multiplayer.SpaceSyncAndP2PStatusState +import com.anytypeio.anytype.core_models.multiplayer.SpaceSyncError +import com.anytypeio.anytype.core_models.multiplayer.SpaceSyncNetwork +import com.anytypeio.anytype.core_models.multiplayer.SpaceSyncStatus +import com.anytypeio.anytype.core_models.multiplayer.SpaceSyncUpdate +import com.anytypeio.anytype.core_models.primitives.TimestampInSeconds +import com.anytypeio.anytype.core_ui.common.DefaultPreviews +import com.anytypeio.anytype.core_ui.foundation.noRippleThrottledClickable +import com.anytypeio.anytype.core_ui.syncstatus.StatusBadge +import com.anytypeio.anytype.feature_date.R +import com.anytypeio.anytype.feature_date.ui.models.DateEvent +import com.anytypeio.anytype.feature_date.viewmodel.UiCalendarIconState +import com.anytypeio.anytype.feature_date.viewmodel.UiSyncStatusBadgeState + +@Composable +fun TopToolbarScreen( + modifier: Modifier, + uiCalendarIconState: UiCalendarIconState, + uiSyncStatusBadgeState: UiSyncStatusBadgeState, + onDateEvent: (DateEvent) -> Unit +) { + Box( + modifier = modifier + .fillMaxWidth() + .height(48.dp) + ) { + if (uiSyncStatusBadgeState is UiSyncStatusBadgeState.Visible) { + val s = uiSyncStatusBadgeState.status + + Box( + modifier = Modifier + .size(48.dp) + .noRippleThrottledClickable { + onDateEvent( + DateEvent.TopToolbar.OnSyncStatusClick( + status = uiSyncStatusBadgeState.status + ) + ) + }, + ) { + StatusBadge( + status = uiSyncStatusBadgeState.status, + modifier = Modifier + .size(20.dp) + .align(Alignment.Center) + ) + } + } + if (uiCalendarIconState is UiCalendarIconState.Visible) { + Image( + modifier = Modifier + .size(48.dp) + .align(Alignment.CenterEnd) + .noRippleThrottledClickable { + onDateEvent( + DateEvent.TopToolbar.OnCalendarClick( + timestampInSeconds = uiCalendarIconState.timestampInSeconds + ) + ) + }, + contentDescription = null, + painter = painterResource(id = R.drawable.ic_calendar_24), + contentScale = ContentScale.None + ) + } + } +} + +@Composable +@DefaultPreviews +fun TopToolbarPreview() { + val spaceSyncUpdate = SpaceSyncUpdate.Update( + id = "1", + status = SpaceSyncStatus.SYNCING, + network = SpaceSyncNetwork.ANYTYPE, + error = SpaceSyncError.NULL, + syncingObjectsCounter = 2 + ) + TopToolbarScreen( + modifier = Modifier.fillMaxWidth(), + uiCalendarIconState = UiCalendarIconState.Visible( + timestampInSeconds = TimestampInSeconds(3232L) + ), + uiSyncStatusBadgeState = UiSyncStatusBadgeState.Visible( + status = SpaceSyncAndP2PStatusState.Success( + spaceSyncUpdate = spaceSyncUpdate, + p2PStatusUpdate = P2PStatusUpdate.Initial + ) + ), + onDateEvent = {} + ) +} \ No newline at end of file diff --git a/feature-date/src/main/java/com/anytypeio/anytype/feature_date/ui/models/DateEvent.kt b/feature-date/src/main/java/com/anytypeio/anytype/feature_date/ui/models/DateEvent.kt new file mode 100644 index 0000000000..6eb1967080 --- /dev/null +++ b/feature-date/src/main/java/com/anytypeio/anytype/feature_date/ui/models/DateEvent.kt @@ -0,0 +1,54 @@ +package com.anytypeio.anytype.feature_date.ui.models + +import com.anytypeio.anytype.core_models.multiplayer.SpaceSyncAndP2PStatusState +import com.anytypeio.anytype.core_models.primitives.TimestampInSeconds +import com.anytypeio.anytype.feature_date.viewmodel.UiFieldsItem +import com.anytypeio.anytype.feature_date.viewmodel.UiObjectsListItem + +sealed class DateEvent { + + sealed class TopToolbar : DateEvent() { + data class OnSyncStatusClick(val status: SpaceSyncAndP2PStatusState) : TopToolbar() + data class OnCalendarClick(val timestampInSeconds: TimestampInSeconds) : TopToolbar() + } + + sealed class Header : DateEvent() { + data object OnNextClick : Header() + data object OnPreviousClick : Header() + } + + sealed class FieldsSheet : DateEvent() { + data object OnSheetDismiss : FieldsSheet() + data class OnFieldClick(val item: UiFieldsItem) : FieldsSheet() + data class OnSearchQueryChanged(val query: String) : FieldsSheet() + } + + sealed class Calendar : DateEvent() { + data object OnCalendarDismiss : Calendar() + data class OnCalendarDateSelected(val timeInMillis: Long?) : Calendar() + data object OnTodayClick : Calendar() + data object OnTomorrowClick : Calendar() + } + + sealed class NavigationWidget : DateEvent() { + data object OnGlobalSearchClick : NavigationWidget() + data object OnAddDocClick : NavigationWidget() + data object OnAddDocLongClick : NavigationWidget() + data object OnBackClick : NavigationWidget() + data object OnBackLongClick : NavigationWidget() + } + + sealed class ObjectsList : DateEvent() { + data class OnObjectClicked(val item: UiObjectsListItem) : ObjectsList() + data object OnLoadMore : ObjectsList() + } + + sealed class FieldsList : DateEvent() { + data class OnFieldClick(val item: UiFieldsItem) : FieldsList() + data object OnScrolledToItemDismiss : FieldsList() + } + + sealed class SyncStatusWidget : DateEvent() { + data object OnSyncStatusDismiss : SyncStatusWidget() + } +} \ No newline at end of file diff --git a/feature-date/src/main/java/com/anytypeio/anytype/feature_date/ui/models/PreviewStubs.kt b/feature-date/src/main/java/com/anytypeio/anytype/feature_date/ui/models/PreviewStubs.kt new file mode 100644 index 0000000000..a476db0b83 --- /dev/null +++ b/feature-date/src/main/java/com/anytypeio/anytype/feature_date/ui/models/PreviewStubs.kt @@ -0,0 +1,86 @@ +package com.anytypeio.anytype.feature_date.ui.models + +import com.anytypeio.anytype.core_models.ObjectType +import com.anytypeio.anytype.core_models.RelationFormat +import com.anytypeio.anytype.core_models.Relations +import com.anytypeio.anytype.core_models.primitives.RelationKey +import com.anytypeio.anytype.core_models.primitives.SpaceId +import com.anytypeio.anytype.feature_date.viewmodel.UiFieldsItem +import com.anytypeio.anytype.feature_date.viewmodel.UiObjectsListItem +import com.anytypeio.anytype.presentation.objects.ObjectIcon + +val StubVerticalItems = listOf( + UiObjectsListItem.Item( + id = "1", + name = "Task Object", + space = SpaceId("space1"), + type = "type1", + typeName = "Task", + createdBy = "by Joseph Wolf", + layout = ObjectType.Layout.TODO, + icon = ObjectIcon.Task(isChecked = true) + ), + UiObjectsListItem.Item( + id = "2", + name = "Page Object", + space = SpaceId("space2"), + type = "type2", + typeName = "Page", + createdBy = "by Mike Long", + layout = ObjectType.Layout.BASIC, + icon = ObjectIcon.Empty.Page + ), + UiObjectsListItem.Item( + id = "3", + name = "File Object", + space = SpaceId("space3"), + type = "type3", + typeName = "File", + createdBy = "by John Doe", + layout = ObjectType.Layout.FILE, + icon = ObjectIcon.File( + mime = "image/png", + fileName = "test_image.png" + ) + ) +) + +val StubHorizontalItems = listOf( + UiFieldsItem.Settings(), + UiFieldsItem.Item.Mention( + id = "Item 54", + title = "Mentionssssss", + key = RelationKey(key = Relations.MENTIONS), + relationFormat = RelationFormat.DATE + ), + UiFieldsItem.Item.Default( + "Item 1", + title = "Title1", + key = RelationKey("key1"), + relationFormat = RelationFormat.DATE + ), + UiFieldsItem.Item.Default( + "Item 2", + title = "Title2", + key = RelationKey("key2"), + relationFormat = RelationFormat.DATE + ), + UiFieldsItem.Item.Default( + "Item 3", + title = "Title3", + key = RelationKey("key3"), + relationFormat = RelationFormat.DATE + ), + UiFieldsItem.Item.Default( + "Item 4", + title = "Title4", + key = RelationKey("key4"), + relationFormat = RelationFormat.DATE + ), + UiFieldsItem.Item.Default( + "Item 5", + title = "Title5", + key = RelationKey("key5"), + relationFormat = RelationFormat.DATE + ), +) \ No newline at end of file diff --git a/feature-date/src/main/java/com/anytypeio/anytype/feature_date/viewmodel/DateModels.kt b/feature-date/src/main/java/com/anytypeio/anytype/feature_date/viewmodel/DateModels.kt new file mode 100644 index 0000000000..b4e8964802 --- /dev/null +++ b/feature-date/src/main/java/com/anytypeio/anytype/feature_date/viewmodel/DateModels.kt @@ -0,0 +1,179 @@ +package com.anytypeio.anytype.feature_date.viewmodel + +import com.anytypeio.anytype.core_models.DVSortType +import com.anytypeio.anytype.core_models.Id +import com.anytypeio.anytype.core_models.ObjectType +import com.anytypeio.anytype.core_models.RelationFormat +import com.anytypeio.anytype.core_models.TimeInMillis +import com.anytypeio.anytype.core_models.multiplayer.SpaceSyncAndP2PStatusState +import com.anytypeio.anytype.core_models.primitives.RelationKey +import com.anytypeio.anytype.core_models.primitives.SpaceId +import com.anytypeio.anytype.core_models.primitives.TimestampInSeconds +import com.anytypeio.anytype.feature_date.viewmodel.UiFieldsItem.Loading +import com.anytypeio.anytype.presentation.objects.ObjectIcon +import com.anytypeio.anytype.presentation.sync.SyncStatusWidgetState + +data class DateObjectVmParams( + val objectId: Id, + val spaceId: SpaceId +) + +data class ActiveField( + val key: RelationKey, + val format: RelationFormat, + val sort: DVSortType = DVSortType.DESC +) + +sealed class UiHeaderState { + + data object Empty : UiHeaderState() + data object Loading : UiHeaderState() + data class Content( + val title: String + ) : UiHeaderState() +} + +sealed class UiCalendarIconState { + data object Hidden : UiCalendarIconState() + data class Visible(val timestampInSeconds: TimestampInSeconds) : UiCalendarIconState() +} + +sealed class UiSyncStatusBadgeState { + data object Hidden : UiSyncStatusBadgeState() + data class Visible(val status: SpaceSyncAndP2PStatusState) : UiSyncStatusBadgeState() +} + +sealed class UiSyncStatusWidgetState { + data object Hidden : UiSyncStatusWidgetState() + data class Visible(val status: SyncStatusWidgetState) : UiSyncStatusWidgetState() +} + +data class UiFieldsState( + val items: List, + val selectedRelationKey: RelationKey? = null, + val needToScrollTo: Boolean = false +) { + companion object { + + val Empty = UiFieldsState(items = emptyList()) + + val LoadingState = + UiFieldsState( + items = listOf( + UiFieldsItem.Loading.Settings("Loading-Settings"), + UiFieldsItem.Loading.Item("Loading-Item-1"), + UiFieldsItem.Loading.Item("Loading-Item-2"), + UiFieldsItem.Loading.Item("Loading-Item-3"), + UiFieldsItem.Loading.Item("Loading-Item-4") + ), + ) + } +} + +sealed class UiFieldsItem { + + abstract val id: String + + sealed class Loading(override val id: String) : UiFieldsItem() { + data class Item(override val id: String) : Loading(id) + data class Settings(override val id: String) : Loading(id) + } + + data class Settings( + override val id: String = "UiHorizontalListItem-Settings-Id" + ) : UiFieldsItem() + + sealed class Item : UiFieldsItem() { + abstract val key: RelationKey + abstract val relationFormat: RelationFormat + abstract val title: String + + data class Default( + override val id: String, + override val key: RelationKey, + override val relationFormat: RelationFormat, + override val title: String + ) : Item() + + data class Mention( + override val id: String, + override val key: RelationKey, + override val relationFormat: RelationFormat, + override val title: String + ) : Item() + } +} + +data class UiObjectsListState( + val items: List +) { + companion object { + + val Empty = UiObjectsListState(items = emptyList()) + val LoadingState = UiObjectsListState( + items = listOf( + UiObjectsListItem.Loading("Loading-Item-1"), + UiObjectsListItem.Loading("Loading-Item-2"), + UiObjectsListItem.Loading("Loading-Item-3"), + UiObjectsListItem.Loading("Loading-Item-4"), + ) + ) + } +} + +sealed class UiObjectsListItem { + + abstract val id: String + + data class Loading(override val id: String) : UiObjectsListItem() + + data class Item( + override val id: String, + val name: String, + val space: SpaceId, + val type: String? = null, + val typeName: String? = null, + val createdBy: String? = null, + val layout: ObjectType.Layout? = null, + val icon: ObjectIcon = ObjectIcon.None + ) : UiObjectsListItem() +} + +sealed class UiNavigationWidget { + data object Hidden : UiNavigationWidget() + data object Editor : UiNavigationWidget() + data object Viewer : UiNavigationWidget() +} + +sealed class UiContentState { + data class Idle(val scrollToTop: Boolean = false) : UiContentState() + data object InitLoading : UiContentState() + data object Paging : UiContentState() + data object Empty : UiContentState() +} + +sealed class UiFieldsSheetState { + data object Hidden : UiFieldsSheetState() + data class Visible( + val items: List + ) : UiFieldsSheetState() +} + +sealed class UiCalendarState { + data object Hidden : UiCalendarState() + data class Calendar( + val timeInMillis: TimeInMillis? + ) : UiCalendarState() +} + +sealed class UiErrorState { + data object Hidden : UiErrorState() + data class Show(val reason: Reason) : UiErrorState() + + sealed class Reason { + data class YearOutOfRange(val min: Int, val max: Int) : Reason() + data class ErrorGettingFields(val msg: String) : Reason() + data class ErrorGettingObjects(val msg: String) : Reason() + data class Other(val msg: String) : Reason() + } +} diff --git a/feature-date/src/main/java/com/anytypeio/anytype/feature_date/viewmodel/DateObjectCommand.kt b/feature-date/src/main/java/com/anytypeio/anytype/feature_date/viewmodel/DateObjectCommand.kt new file mode 100644 index 0000000000..37c403413a --- /dev/null +++ b/feature-date/src/main/java/com/anytypeio/anytype/feature_date/viewmodel/DateObjectCommand.kt @@ -0,0 +1,19 @@ +package com.anytypeio.anytype.feature_date.viewmodel + +import com.anytypeio.anytype.core_models.Id +import com.anytypeio.anytype.core_models.primitives.SpaceId + +sealed class DateObjectCommand { + data class OpenChat(val target: Id, val space: SpaceId) : DateObjectCommand() + data class NavigateToEditor(val id: Id, val space: SpaceId) : DateObjectCommand() + data class NavigateToSetOrCollection(val id: Id, val space: SpaceId) : DateObjectCommand() + data class NavigateToDateObject(val objectId: Id, val space: SpaceId) : DateObjectCommand() + data object TypeSelectionScreen : DateObjectCommand() + data object ExitToSpaceWidgets : DateObjectCommand() + sealed class SendToast : DateObjectCommand() { + data class UnexpectedLayout(val layout: String) : SendToast() + } + data object OpenGlobalSearch : DateObjectCommand() + data object ExitToVault : DateObjectCommand() + data object Back : DateObjectCommand() +} \ No newline at end of file diff --git a/feature-date/src/main/java/com/anytypeio/anytype/feature_date/viewmodel/DateObjectVMFactory.kt b/feature-date/src/main/java/com/anytypeio/anytype/feature_date/viewmodel/DateObjectVMFactory.kt new file mode 100644 index 0000000000..5534b4b7f9 --- /dev/null +++ b/feature-date/src/main/java/com/anytypeio/anytype/feature_date/viewmodel/DateObjectVMFactory.kt @@ -0,0 +1,58 @@ +package com.anytypeio.anytype.feature_date.viewmodel + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import com.anytypeio.anytype.analytics.base.Analytics +import com.anytypeio.anytype.domain.event.interactor.SpaceSyncAndP2PStatusProvider +import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer +import com.anytypeio.anytype.domain.misc.DateProvider +import com.anytypeio.anytype.domain.misc.UrlBuilder +import com.anytypeio.anytype.domain.multiplayer.UserPermissionProvider +import com.anytypeio.anytype.domain.`object`.GetObject +import com.anytypeio.anytype.domain.objects.GetDateObjectByTimestamp +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.relations.GetObjectRelationListById +import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate +import javax.inject.Inject + +class DateObjectVMFactory @Inject constructor( + private val vmParams: DateObjectVmParams, + private val getObject: GetObject, + private val analytics: Analytics, + private val urlBuilder: UrlBuilder, + private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate, + private val userPermissionProvider: UserPermissionProvider, + private val getObjectRelationListById: GetObjectRelationListById, + private val storeOfRelations: StoreOfRelations, + private val storeOfObjectTypes: StoreOfObjectTypes, + private val storelessSubscriptionContainer: StorelessSubscriptionContainer, + private val getDateObjectByTimestamp: GetDateObjectByTimestamp, + private val dateProvider: DateProvider, + private val spaceSyncAndP2PStatusProvider: SpaceSyncAndP2PStatusProvider, + private val createObject: CreateObject, + private val fieldParser: FieldParser +) : ViewModelProvider.Factory { + + @Suppress("UNCHECKED_CAST") + override fun create(modelClass: Class): T = + DateObjectViewModel( + vmParams = vmParams, + getObject = getObject, + analytics = analytics, + urlBuilder = urlBuilder, + analyticSpaceHelperDelegate = analyticSpaceHelperDelegate, + userPermissionProvider = userPermissionProvider, + getObjectRelationListById = getObjectRelationListById, + storeOfRelations = storeOfRelations, + storeOfObjectTypes = storeOfObjectTypes, + storelessSubscriptionContainer = storelessSubscriptionContainer, + getDateObjectByTimestamp = getDateObjectByTimestamp, + dateProvider = dateProvider, + spaceSyncAndP2PStatusProvider = spaceSyncAndP2PStatusProvider, + createObject = createObject, + fieldParser = fieldParser + ) as T +} \ No newline at end of file diff --git a/feature-date/src/main/java/com/anytypeio/anytype/feature_date/viewmodel/DateObjectViewModel.kt b/feature-date/src/main/java/com/anytypeio/anytype/feature_date/viewmodel/DateObjectViewModel.kt new file mode 100644 index 0000000000..ed071c90b2 --- /dev/null +++ b/feature-date/src/main/java/com/anytypeio/anytype/feature_date/viewmodel/DateObjectViewModel.kt @@ -0,0 +1,863 @@ +package com.anytypeio.anytype.feature_date.viewmodel + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.anytypeio.anytype.analytics.base.Analytics +import com.anytypeio.anytype.analytics.base.EventsDictionary +import com.anytypeio.anytype.core_models.DATE_PICKER_YEAR_RANGE +import com.anytypeio.anytype.core_models.DVSortType +import com.anytypeio.anytype.core_models.Id +import com.anytypeio.anytype.core_models.ObjectWrapper +import com.anytypeio.anytype.core_models.Relations +import com.anytypeio.anytype.core_models.Struct +import com.anytypeio.anytype.core_models.TimeInSeconds +import com.anytypeio.anytype.core_models.getSingleValue +import com.anytypeio.anytype.core_models.primitives.SpaceId +import com.anytypeio.anytype.core_models.primitives.TimestampInSeconds +import com.anytypeio.anytype.domain.base.fold +import com.anytypeio.anytype.domain.event.interactor.SpaceSyncAndP2PStatusProvider +import com.anytypeio.anytype.domain.library.StoreSearchParams +import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer +import com.anytypeio.anytype.domain.misc.DateProvider +import com.anytypeio.anytype.domain.misc.UrlBuilder +import com.anytypeio.anytype.domain.multiplayer.UserPermissionProvider +import com.anytypeio.anytype.domain.`object`.GetObject +import com.anytypeio.anytype.domain.objects.GetDateObjectByTimestamp +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.relations.GetObjectRelationListById +import com.anytypeio.anytype.feature_date.viewmodel.UiErrorState.Reason +import com.anytypeio.anytype.feature_date.mapping.toUiFieldsItem +import com.anytypeio.anytype.feature_date.mapping.toUiObjectsListItem +import com.anytypeio.anytype.feature_date.ui.models.DateEvent +import com.anytypeio.anytype.feature_date.viewmodel.UiContentState.* +import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate +import com.anytypeio.anytype.presentation.extension.sendAnalyticsAllContentScreen +import com.anytypeio.anytype.presentation.extension.sendAnalyticsObjectCreateEvent +import com.anytypeio.anytype.presentation.home.OpenObjectNavigation +import com.anytypeio.anytype.presentation.home.navigation +import com.anytypeio.anytype.presentation.objects.getCreateObjectParams +import com.anytypeio.anytype.presentation.search.GlobalSearchViewModel.Companion.DEFAULT_DEBOUNCE_DURATION +import com.anytypeio.anytype.presentation.search.ObjectSearchConstants.defaultKeys +import com.anytypeio.anytype.presentation.sync.toSyncStatusWidgetState +import kotlin.collections.map +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.collectLatest +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.filterNotNull +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onCompletion +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.take +import kotlinx.coroutines.launch +import timber.log.Timber + +/** + * ViewState: @see [UiContentState] + * Factory: @see [DateObjectVMFactory] + * Screen: @see [com.anytypeio.anytype.feature_date.ui.DateMainScreen] + * Models: @see [UiObjectsListState] + */ +class DateObjectViewModel( + private val vmParams: DateObjectVmParams, + private val getObject: GetObject, + private val analytics: Analytics, + private val urlBuilder: UrlBuilder, + private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate, + private val userPermissionProvider: UserPermissionProvider, + private val getObjectRelationListById: GetObjectRelationListById, + private val storeOfRelations: StoreOfRelations, + private val storeOfObjectTypes: StoreOfObjectTypes, + private val storelessSubscriptionContainer: StorelessSubscriptionContainer, + private val getDateObjectByTimestamp: GetDateObjectByTimestamp, + private val dateProvider: DateProvider, + private val spaceSyncAndP2PStatusProvider: SpaceSyncAndP2PStatusProvider, + private val createObject: CreateObject, + private val fieldParser: FieldParser +) : ViewModel(), AnalyticSpaceHelperDelegate by analyticSpaceHelperDelegate { + + val uiCalendarIconState = MutableStateFlow(UiCalendarIconState.Hidden) + val uiSyncStatusBadgeState = + MutableStateFlow(UiSyncStatusBadgeState.Hidden) + val uiHeaderState = MutableStateFlow(UiHeaderState.Empty) + val uiNavigationWidget = MutableStateFlow(UiNavigationWidget.Hidden) + val uiFieldsState = MutableStateFlow(UiFieldsState.Empty) + val uiFieldsSheetState = MutableStateFlow(UiFieldsSheetState.Hidden) + val uiObjectsListState = MutableStateFlow(UiObjectsListState.Empty) + val uiContentState = MutableStateFlow(UiContentState.Idle()) + val uiCalendarState = MutableStateFlow(UiCalendarState.Hidden) + val uiSyncStatusWidgetState = + MutableStateFlow(UiSyncStatusWidgetState.Hidden) + + val effects = MutableSharedFlow() + val errorState = MutableStateFlow(UiErrorState.Hidden) + + private val _dateId = MutableStateFlow(null) + private val _dateTimestamp = MutableStateFlow(null) + private val _activeField = MutableStateFlow(null) + + /** + * Paging and subscription limit. If true, we can paginate after reaching bottom items. + * Could be true only after the first subscription results (if results size == limit) + */ + val canPaginate = MutableStateFlow(false) + private var _itemsLimit = DEFAULT_SEARCH_LIMIT + private val restartSubscription = MutableStateFlow(0L) + + /** + * Search query + */ + private val userInput = MutableStateFlow("") + + @OptIn(FlowPreview::class) + private val searchQuery = userInput + .take(1) + .onCompletion { + emitAll(userInput.drop(1).debounce(DEFAULT_DEBOUNCE_DURATION).distinctUntilChanged()) + } + + private var shouldScrollToTopItems = false + + private val permission = MutableStateFlow(userPermissionProvider.get(vmParams.spaceId)) + + init { + Timber.d("Init DateObjectViewModel, date object id: [${vmParams.objectId}], space: [${vmParams.spaceId}]") + uiHeaderState.value = UiHeaderState.Loading + uiFieldsState.value = UiFieldsState.LoadingState + uiObjectsListState.value = UiObjectsListState.LoadingState + proceedWithObservingPermissions() + proceedWithGettingDateObject() + proceedWithGettingDateObjectRelationList() + proceedWithObservingSyncStatus() + setupSearchStateFlow() + _dateId.value = vmParams.objectId + } + + fun onStart() { + Timber.d("onStart") + setupUiStateFlow() + viewModelScope.launch { + sendAnalyticsAllContentScreen( + analytics = analytics + ) + } + } + + fun onStop() { + unsubscribe() + resetLimit() + canPaginate.value = false + uiObjectsListState.value = UiObjectsListState.Empty + uiContentState.value = UiContentState.Empty + } + + override fun onCleared() { + Timber.d("onCleared") + super.onCleared() + uiContentState.value = UiContentState.Empty + uiHeaderState.value = UiHeaderState.Empty + uiCalendarIconState.value = UiCalendarIconState.Hidden + uiSyncStatusBadgeState.value = UiSyncStatusBadgeState.Hidden + uiFieldsState.value = UiFieldsState.Empty + uiObjectsListState.value = UiObjectsListState.Empty + uiFieldsSheetState.value = UiFieldsSheetState.Hidden + resetLimit() + } + + private fun proceedWithReopenDateObjectByTimestamp(timestamp: TimeInSeconds) { + proceedWithGettingDateByTimestamp( + timestamp = timestamp + ) { dateObject -> + val id = dateObject?.getSingleValue(Relations.ID) + if (id != null) { + reopenDateObject(id) + } else { + Timber.e("GettingDateByTimestamp error, object has no id") + } + } + } + + private fun reopenDateObject(dateObjectId: Id) { + Timber.d("Reopen date object: $dateObjectId") + canPaginate.value = false + resetLimit() + shouldScrollToTopItems = true + uiFieldsState.value = UiFieldsState.Empty + uiObjectsListState.value = UiObjectsListState.Empty + uiFieldsSheetState.value = UiFieldsSheetState.Hidden + _activeField.value = null + _dateId.value = dateObjectId + } + + private fun setupSearchStateFlow() { + viewModelScope.launch { + searchQuery.collectLatest { query -> + if (uiFieldsSheetState.value is UiFieldsSheetState.Hidden) return@collectLatest + val items = uiFieldsState.value.items + if (items.isEmpty()) return@collectLatest + val filteredItems = if (query.isBlank()) { + items + } else { + items.filterIsInstance() + .filter { it.title.contains(query, ignoreCase = true) } + } + uiFieldsSheetState.value = UiFieldsSheetState.Visible( + items = filteredItems + ) + } + } + } + + //region Initialization + private fun proceedWithObservingPermissions() { + viewModelScope.launch { + userPermissionProvider + .observe(space = vmParams.spaceId) + .collect { result -> + uiNavigationWidget.value = if (result?.isOwnerOrEditor() == true) { + UiNavigationWidget.Editor + } else { + UiNavigationWidget.Viewer + } + permission.value = result + } + } + } + + private fun proceedWithObservingSyncStatus() { + viewModelScope.launch { + spaceSyncAndP2PStatusProvider + .observe() + .catch { + Timber.e(it, "Error while observing sync status") + } + .collect { syncAndP2pState -> + Timber.d("Sync status: $syncAndP2pState") + uiSyncStatusBadgeState.value = UiSyncStatusBadgeState.Visible(syncAndP2pState) + val state = uiSyncStatusWidgetState.value + uiSyncStatusWidgetState.value = when (state) { + UiSyncStatusWidgetState.Hidden -> UiSyncStatusWidgetState.Hidden + is UiSyncStatusWidgetState.Visible -> state.copy( + status = syncAndP2pState.toSyncStatusWidgetState() + ) + } + } + } + } + + private fun proceedWithGettingDateObjectRelationList() { + viewModelScope.launch { + _dateId + .filterNotNull() + .collect { id -> + val params = GetObjectRelationListById.Params( + space = vmParams.spaceId, + value = id + ) + Timber.d("Start RelationListWithValue with params: $params") + getObjectRelationListById.async(params).fold( + onSuccess = { result -> + Timber.d("RelationListWithValue Success: $result") + val items = + result.toUiFieldsItem(storeOfRelations = storeOfRelations) + initFieldsState(items) + }, + onFailure = { e -> + Timber.e(e, "RelationListWithValue Error") + errorState.value = UiErrorState.Show( + Reason.ErrorGettingFields( + msg = e.message ?: "Error getting fields" + ) + ) + } + ) + } + } + } + + private fun proceedWithGettingDateObject() { + viewModelScope.launch { + _dateId + .filterNotNull() + .collect { id -> + val params = GetObject.Params( + target = id, + space = vmParams.spaceId + ) + Timber.d("Start GetObject with params: $params") + getObject.async(params).fold( + onSuccess = { obj -> + Timber.d("GetObject Success, obj:[$obj]") + val timestampInSeconds = + obj.details[id]?.getSingleValue( + Relations.TIMESTAMP + )?.toLong() + if (timestampInSeconds != null) { + _dateTimestamp.value = timestampInSeconds + val (formattedDate, _) = dateProvider.formatTimestampToDateAndTime( + timestamp = timestampInSeconds * 1000, + ) + uiCalendarIconState.value = UiCalendarIconState.Visible( + timestampInSeconds = TimestampInSeconds(timestampInSeconds) + ) + uiHeaderState.value = UiHeaderState.Content( + title = formattedDate + ) + } + }, + onFailure = { e -> Timber.e(e, "GetObject Error") } + ) + } + } + } + + private fun proceedWithGettingDateByTimestamp(timestamp: Long, action: (Struct?) -> Unit) { + val params = GetDateObjectByTimestamp.Params( + space = vmParams.spaceId, + timestamp = timestamp + ) + Timber.d("Start ObjectDateByTimestamp with params: [$params]") + viewModelScope.launch { + getDateObjectByTimestamp.async(params).fold( + onSuccess = { dateObject -> + Timber.d("ObjectDateByTimestamp Success, dateObject: [$dateObject]") + action(dateObject) + }, + onFailure = { e -> Timber.e(e, "ObjectDateByTimestamp Error") } + ) + } + } + //endregion + + //region Subscription + private fun subscriptionId() = "date_object_subscription_${vmParams.spaceId}" + + @OptIn(ExperimentalCoroutinesApi::class) + private fun setupUiStateFlow() { + viewModelScope.launch { + combine( + _dateId.filterNotNull(), + _dateTimestamp.filterNotNull(), + _activeField.filterNotNull(), + restartSubscription + ) { dateId, timestamp, activeField, _ -> + createSearchParams( + dateId = dateId, + timestamp = timestamp, + space = vmParams.spaceId, + itemsLimit = _itemsLimit, + field = activeField + ) + } + .flatMapLatest { searchParams -> + loadData(searchParams) + } + .catch { + errorState.value = UiErrorState.Show( + Reason.Other(it.message ?: "Error getting data") + ) + } + .collect { items -> + uiObjectsListState.value = UiObjectsListState(items) + } + } + } + + private fun loadData( + searchParams: StoreSearchParams + ): Flow> { + + return storelessSubscriptionContainer.subscribe(searchParams) + .onStart { + uiContentState.value = if (_itemsLimit == DEFAULT_SEARCH_LIMIT) { + UiContentState.InitLoading + } else { + UiContentState.Paging + } + Timber.d("Restart subscription: with params: $searchParams") + } + .map { objWrappers -> + handleData(objWrappers) + }.catch { e -> + Timber.e("Error loading data: $e") + errorState.value = UiErrorState.Show( + Reason.ErrorGettingObjects( + e.message ?: "Error getting objects" + ) + ) + } + } + + private suspend fun handleData( + objWrappers: List + ): List { + + canPaginate.value = objWrappers.size == _itemsLimit + + val items = objWrappers.map { + it.toUiObjectsListItem( + space = vmParams.spaceId, + urlBuilder = urlBuilder, + objectTypes = storeOfObjectTypes.getAll(), + fieldParser = fieldParser + ) + } + uiContentState.value = if (items.isEmpty()) { + UiContentState.Empty + } else { + UiContentState.Idle(scrollToTop = shouldScrollToTopItems).also { + shouldScrollToTopItems = false + } + } + return items + } + + private fun createSearchParams( + dateId: Id, + timestamp: TimeInSeconds, + field: ActiveField, + space: SpaceId, + itemsLimit: Int + ): StoreSearchParams { + val (filters, sorts) = filtersAndSortsForSearch( + spaces = listOf(space.id), + field = field, + timestamp = timestamp, + dateId = dateId + ) + return StoreSearchParams( + space = space, + filters = filters, + sorts = sorts, + keys = defaultKeys, + limit = itemsLimit, + subscription = subscriptionId() + ) + } + + /** + * Updates the limit for the number of items fetched and triggers data reload. + */ + fun updateLimit() { + Timber.d("Update limit, canPaginate: ${canPaginate.value} uiContentState: ${uiContentState.value}") + if (canPaginate.value && uiContentState.value is UiContentState.Idle) { + _itemsLimit += DEFAULT_SEARCH_LIMIT + restartSubscription.value++ + } + } + + private fun resetLimit() { + Timber.d("Reset limit") + _itemsLimit = DEFAULT_SEARCH_LIMIT + } + + private fun unsubscribe() { + viewModelScope.launch { + storelessSubscriptionContainer.unsubscribe(listOf(subscriptionId())) + } + } + //endregion + + //region Ui Actions + private fun onFieldsEvent(item: UiFieldsItem, needToScroll: Boolean = false) { + when (item) { + is UiFieldsItem.Item -> { + if (_activeField.value?.key == item.key) { + val value = _activeField.value + val activeSort = _activeField.value?.sort ?: DEFAULT_SORT_TYPE + _activeField.value = value?.copy( + sort = if (activeSort == DVSortType.ASC) { + DVSortType.DESC + } else { + DVSortType.ASC + } + ) + shouldScrollToTopItems = true + resetLimit() + canPaginate.value = false + uiContentState.value = Idle() + uiObjectsListState.value = UiObjectsListState.Empty + restartSubscription.value++ + updateHorizontalListState(selectedItem = item, needToScroll = needToScroll) + } else { + shouldScrollToTopItems = true + resetLimit() + canPaginate.value = false + uiContentState.value = Idle() + _activeField.value = ActiveField( + key = item.key, + format = item.relationFormat + ) + restartSubscription.value++ + updateHorizontalListState(selectedItem = item, needToScroll = needToScroll) + } + } + + is UiFieldsItem.Settings -> { + val items = uiFieldsState.value.items + uiFieldsSheetState.value = UiFieldsSheetState.Visible( + items = items.filterIsInstance() + ) + } + + else -> {} + } + } + + private fun proceedWithReopeningDate(offset: Int) { + val timestamp = _dateTimestamp.value + if (timestamp == null) { + Timber.w("Error getting timestamp") + return + } + + val newTimestamp = timestamp + offset + + val isValid = dateProvider.isTimestampWithinYearRange( + timeStampInMillis = newTimestamp * 1000, + yearRange = DATE_PICKER_YEAR_RANGE + ) + + if (isValid) { + proceedWithReopenDateObjectByTimestamp( + timestamp = newTimestamp + ) + } else { + showDateOutOfRangeError() + } + } + + private fun proceedWithCreateDoc( + objType: ObjectWrapper.Type? = null + ) { + val startTime = System.currentTimeMillis() + val params = objType?.uniqueKey.getCreateObjectParams( + space = vmParams.spaceId, + objType?.defaultTemplateId + ) + viewModelScope.launch { + createObject.async(params).fold( + onSuccess = { result -> + proceedWithNavigation( + navigation = result.obj.navigation() + ) + sendAnalyticsObjectCreateEvent( + analytics = analytics, + route = EventsDictionary.Routes.objDate, + startTime = startTime, + objType = objType ?: storeOfObjectTypes.getByKey(result.typeKey.key), + view = EventsDictionary.View.viewHome, + spaceParams = provideParams(space = vmParams.spaceId.id) + ) + }, + onFailure = { e -> Timber.e(e, "Error while creating a new object") } + ) + } + } + + private fun proceedWithNavigation(navigation: OpenObjectNavigation) { + viewModelScope.launch { + when (navigation) { + is OpenObjectNavigation.OpenDataView -> { + effects.emit( + DateObjectCommand.NavigateToSetOrCollection( + id = navigation.target, + space = SpaceId(navigation.space) + ) + ) + } + + is OpenObjectNavigation.OpenEditor -> { + effects.emit( + DateObjectCommand.NavigateToEditor( + id = navigation.target, + space = SpaceId(navigation.space) + ) + ) + } + + is OpenObjectNavigation.UnexpectedLayoutError -> { + Timber.e("Unexpected layout: ${navigation.layout}") + effects.emit(DateObjectCommand.SendToast.UnexpectedLayout(navigation.layout?.name.orEmpty())) + } + + is OpenObjectNavigation.OpenDiscussion -> { + effects.emit( + DateObjectCommand.OpenChat( + target = navigation.target, + space = SpaceId(navigation.space) + ) + ) + } + + OpenObjectNavigation.NonValidObject -> { + Timber.e("Object id is missing") + } + + is OpenObjectNavigation.OpenDataObject -> { + effects.emit( + DateObjectCommand.NavigateToEditor( + id = navigation.target, + space = SpaceId(navigation.space) + ) + ) + } + } + } + } + + private fun onItemClicked(item: UiObjectsListItem) { + Timber.d("onItemClicked: ${item.id}") + when (item) { + is UiObjectsListItem.Item -> { + val layout = item.layout ?: return + proceedWithNavigation( + navigation = layout.navigation( + target = item.id, + space = vmParams.spaceId.id + ) + ) + viewModelScope.launch { + //sendAnalyticsAllContentResult(analytics = analytics) + } + } + + is UiObjectsListItem.Loading -> { + Timber.d("Loading item clicked") + } + } + } + + fun onCreateObjectOfTypeClicked(objType: ObjectWrapper.Type) { + proceedWithCreateDoc(objType) + } + + fun onDateEvent(event: DateEvent) { + when (event) { + is DateEvent.Calendar -> onCalendarEvent(event) + is DateEvent.TopToolbar -> onTopToolbarEvent(event) + is DateEvent.Header -> onHeaderEvent(event) + is DateEvent.FieldsSheet -> onFieldsSheetEvent(event) + is DateEvent.FieldsList -> onFieldsListEvent(event) + is DateEvent.NavigationWidget -> onNavigationWidgetEvent(event) + is DateEvent.ObjectsList -> onObjectsListEvent(event) + is DateEvent.SyncStatusWidget -> onSyncStatusWidgetEvent(event) + } + } + + private fun onFieldsListEvent(event: DateEvent.FieldsList) { + when (event) { + DateEvent.FieldsList.OnScrolledToItemDismiss -> { + uiFieldsState.value = uiFieldsState.value.copy( + needToScrollTo = false + ) + } + is DateEvent.FieldsList.OnFieldClick -> onFieldsEvent(event.item) + } + } + + private fun onSyncStatusWidgetEvent(event: DateEvent.SyncStatusWidget) { + when (event) { + DateEvent.SyncStatusWidget.OnSyncStatusDismiss -> { + uiSyncStatusWidgetState.value = UiSyncStatusWidgetState.Hidden + } + } + } + + private fun onObjectsListEvent(event: DateEvent.ObjectsList) { + when (event) { + DateEvent.ObjectsList.OnLoadMore -> updateLimit() + is DateEvent.ObjectsList.OnObjectClicked -> onItemClicked(event.item) + } + } + + private fun onTopToolbarEvent(event: DateEvent.TopToolbar) { + when (event) { + is DateEvent.TopToolbar.OnCalendarClick -> { + val timestampInSeconds = event.timestampInSeconds + val timeInMillis = dateProvider.adjustToStartOfDayInUserTimeZone( + timestamp = timestampInSeconds.time + ) + val isValid = dateProvider.isTimestampWithinYearRange( + timeStampInMillis = timeInMillis, + yearRange = DATE_PICKER_YEAR_RANGE + ) + if (isValid) { + uiCalendarState.value = UiCalendarState.Calendar( + timeInMillis = timeInMillis + ) + } else { + showDateOutOfRangeError() + } + } + + is DateEvent.TopToolbar.OnSyncStatusClick -> { + uiSyncStatusWidgetState.value = + UiSyncStatusWidgetState.Visible( + status = event.status.toSyncStatusWidgetState() + ) + } + } + } + + private fun onHeaderEvent(event: DateEvent.Header) { + when (event) { + DateEvent.Header.OnNextClick -> proceedWithReopeningDate(offset = SECONDS_IN_DAY) + DateEvent.Header.OnPreviousClick -> proceedWithReopeningDate(offset = -SECONDS_IN_DAY) + } + } + + private fun onCalendarEvent(event: DateEvent.Calendar) { + when (event) { + is DateEvent.Calendar.OnCalendarDateSelected -> { + uiCalendarState.value = UiCalendarState.Hidden + val timeInMillis = event.timeInMillis + Timber.d("Selected date in millis: [$timeInMillis]") + if (timeInMillis == null) return + proceedWithReopenDateObjectByTimestamp( + timestamp = dateProvider.adjustFromStartOfDayInUserTimeZoneToUTC( + timeInMillis = timeInMillis + ) + ) + } + + DateEvent.Calendar.OnCalendarDismiss -> { + uiCalendarState.value = UiCalendarState.Hidden + } + + DateEvent.Calendar.OnTodayClick -> { + uiCalendarState.value = UiCalendarState.Hidden + proceedWithReopenDateObjectByTimestamp( + timestamp = dateProvider.getTimestampForTodayAtStartOfDay() + ) + } + + DateEvent.Calendar.OnTomorrowClick -> { + uiCalendarState.value = UiCalendarState.Hidden + proceedWithReopenDateObjectByTimestamp( + timestamp = dateProvider.getTimestampForTomorrowAtStartOfDay() + ) + } + } + } + + private fun onFieldsSheetEvent(event: DateEvent.FieldsSheet) { + when (event) { + is DateEvent.FieldsSheet.OnFieldClick -> { + uiFieldsSheetState.value = UiFieldsSheetState.Hidden + onFieldsEvent(event.item, needToScroll = true) + } + + is DateEvent.FieldsSheet.OnSearchQueryChanged -> { + Timber.d("Search query: ${event.query}") + userInput.value = event.query + } + + DateEvent.FieldsSheet.OnSheetDismiss -> { + uiFieldsSheetState.value = UiFieldsSheetState.Hidden + } + } + } + + private fun onNavigationWidgetEvent(event: DateEvent.NavigationWidget) { + when (event) { + DateEvent.NavigationWidget.OnAddDocClick -> { + proceedWithCreateDoc() + } + + DateEvent.NavigationWidget.OnAddDocLongClick -> { + viewModelScope.launch { + effects.emit(DateObjectCommand.TypeSelectionScreen) + } + } + + DateEvent.NavigationWidget.OnBackClick -> { + viewModelScope.launch { + effects.emit(DateObjectCommand.Back) + } + } + + DateEvent.NavigationWidget.OnBackLongClick -> { + viewModelScope.launch { + effects.emit(DateObjectCommand.ExitToSpaceWidgets) + } + } + + DateEvent.NavigationWidget.OnGlobalSearchClick -> { + viewModelScope.launch { + effects.emit(DateObjectCommand.OpenGlobalSearch) + } + } + } + } + //endregion + + //region Ui State + private fun initFieldsState(relations: List) { + val relation = relations.getOrNull(0) + if (relation == null) { + Timber.e("Error getting relation") + return + } + _activeField.value = ActiveField( + key = relation.key, + format = relation.relationFormat + ) + restartSubscription.value++ + uiFieldsState.value = UiFieldsState( + items = buildList { + add(UiFieldsItem.Settings()) + addAll(relations) + }, + selectedRelationKey = _activeField.value?.key + ) + if (relations.isEmpty()) { + uiContentState.value = UiContentState.Empty + uiObjectsListState.value = UiObjectsListState.Empty + } + } + + private fun updateHorizontalListState(selectedItem: UiFieldsItem.Item, needToScroll: Boolean = false) { + uiFieldsState.value = uiFieldsState.value.copy( + selectedRelationKey = selectedItem.key, + needToScrollTo = needToScroll + ) + } + + fun hideError() { + errorState.value = UiErrorState.Hidden + } + + fun showDateOutOfRangeError() { + viewModelScope.launch { + errorState.emit( + UiErrorState.Show( + Reason.YearOutOfRange( + min = DATE_PICKER_YEAR_RANGE.first, + max = DATE_PICKER_YEAR_RANGE.last + ) + ) + ) + } + } + //endregion + + companion object { + //INITIAL STATE + const val SECONDS_IN_DAY = 86400 + const val DEFAULT_SEARCH_LIMIT = 25 + val DEFAULT_SORT_TYPE = DVSortType.DESC + } +} \ No newline at end of file diff --git a/feature-date/src/main/java/com/anytypeio/anytype/feature_date/viewmodel/SearchParams.kt b/feature-date/src/main/java/com/anytypeio/anytype/feature_date/viewmodel/SearchParams.kt new file mode 100644 index 0000000000..d76706f06c --- /dev/null +++ b/feature-date/src/main/java/com/anytypeio/anytype/feature_date/viewmodel/SearchParams.kt @@ -0,0 +1,134 @@ +package com.anytypeio.anytype.feature_date.viewmodel + +import com.anytypeio.anytype.core_models.DVFilter +import com.anytypeio.anytype.core_models.DVFilterCondition +import com.anytypeio.anytype.core_models.DVSort +import com.anytypeio.anytype.core_models.Id +import com.anytypeio.anytype.core_models.ObjectType +import com.anytypeio.anytype.core_models.ObjectTypeUniqueKeys +import com.anytypeio.anytype.core_models.Relation +import com.anytypeio.anytype.core_models.RelationFormat +import com.anytypeio.anytype.core_models.Relations +import com.anytypeio.anytype.core_models.TimeInSeconds + +fun filtersAndSortsForSearch( + dateId: Id, + field: ActiveField, + timestamp: TimeInSeconds, + spaces: List +): Pair, List> { + val filters = buildList { + addAll(buildDeletedFilter()) + add(buildSpaceIdFilter(spaces)) + add(buildTemplateFilter()) + add( + buildFieldFilter( + dateObjectId = dateId, + field = field, + timestamp = timestamp + ) + ) + add(buildLayoutFilter()) + } + return filters to buildSorts(field) +} + +private fun buildSorts( + field: ActiveField, +): List { + return listOf( + DVSort( + relationKey = field.key.key, + type = field.sort, + relationFormat = RelationFormat.DATE + ) + ) +} + +private fun buildFieldFilter( + dateObjectId: Id, + field: ActiveField, + timestamp: TimeInSeconds +): DVFilter { + val fieldKey = field.key.key + return when (field.format) { + Relation.Format.DATE -> { + DVFilter( + relation = fieldKey, + condition = DVFilterCondition.EQUAL, + value = timestamp.toDouble(), + relationFormat = RelationFormat.DATE + ) + } + else -> { + DVFilter( + relation = fieldKey, + condition = DVFilterCondition.IN, + value = dateObjectId + ) + } + } +} + +private fun buildTemplateFilter(): DVFilter = DVFilter( + relation = Relations.TYPE_UNIQUE_KEY, + condition = DVFilterCondition.NOT_EQUAL, + value = ObjectTypeUniqueKeys.TEMPLATE +) + +private fun buildSpaceIdFilter(spaces: List): DVFilter = DVFilter( + relation = Relations.SPACE_ID, + condition = DVFilterCondition.IN, + value = spaces +) + +private fun buildLayoutFilter(): DVFilter = DVFilter( + relation = Relations.LAYOUT, + condition = DVFilterCondition.IN, + value = SUPPORTED_DATE_OBJECT_LAYOUTS.map { it.code.toDouble() } +) + +private fun buildDeletedFilter(): List { + return listOf( + DVFilter( + relation = Relations.IS_ARCHIVED, + condition = DVFilterCondition.NOT_EQUAL, + value = true + ), + DVFilter( + relation = Relations.IS_HIDDEN, + condition = DVFilterCondition.NOT_EQUAL, + value = true + ), + DVFilter( + relation = Relations.IS_DELETED, + condition = DVFilterCondition.NOT_EQUAL, + value = true + ), + DVFilter( + relation = Relations.IS_HIDDEN_DISCOVERY, + condition = DVFilterCondition.NOT_EQUAL, + value = true + ) + ) +} + +private val SUPPORTED_DATE_OBJECT_LAYOUTS = listOf( + ObjectType.Layout.SET, + ObjectType.Layout.COLLECTION, + + ObjectType.Layout.TODO, + ObjectType.Layout.NOTE, + ObjectType.Layout.BASIC, + ObjectType.Layout.PROFILE, + + ObjectType.Layout.PARTICIPANT, + ObjectType.Layout.BOOKMARK, + ObjectType.Layout.DATE, + + ObjectType.Layout.FILE, + ObjectType.Layout.IMAGE, + ObjectType.Layout.VIDEO, + ObjectType.Layout.AUDIO, + ObjectType.Layout.PDF +) \ No newline at end of file diff --git a/libs/build.gradle b/libs/build.gradle index 3b44a88010..9d0bc9b11f 100644 --- a/libs/build.gradle +++ b/libs/build.gradle @@ -9,7 +9,7 @@ afterEvaluate { artifact(file('lib.aar')) groupId 'io.anyproto' artifactId 'anytype-heart-android' - version = '9e8c87fd4c10497a7f2b7c7b6c169f646d975980' + version = '0.99' } } } diff --git a/localization/src/main/res/values/strings.xml b/localization/src/main/res/values/strings.xml index bc82489f79..72982ac0e9 100644 --- a/localization/src/main/res/values/strings.xml +++ b/localization/src/main/res/values/strings.xml @@ -288,6 +288,8 @@ Linked to Remove link Create new object + Dates + Objects Create object Search Syncing… @@ -400,11 +402,13 @@ Select tags Select status Select objects + Select date No value Source Query Select query Objects + Your objects Web pages Select files U @@ -1823,6 +1827,11 @@ Please provide specific details of your needs here. There is no messages yet.\nBe the first to start a discussion. Write a message... + Mentioned in + There is nothing here for this date yet + The selected date is out of the valid range (years %1$d to %2$d). Please select a date within this range. + by + Object File Media diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/block/BlockMiddleware.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/block/BlockMiddleware.kt index e977a12fb0..e17a98c863 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/block/BlockMiddleware.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/block/BlockMiddleware.kt @@ -21,6 +21,7 @@ import com.anytypeio.anytype.core_models.ObjectWrapper import com.anytypeio.anytype.core_models.Payload import com.anytypeio.anytype.core_models.Position import com.anytypeio.anytype.core_models.RelationFormat +import com.anytypeio.anytype.core_models.RelationListWithValueItem import com.anytypeio.anytype.core_models.Response import com.anytypeio.anytype.core_models.SearchResult import com.anytypeio.anytype.core_models.Struct @@ -1062,7 +1063,15 @@ class BlockMiddleware( return middleware.dataViewSetActiveView(command) } + override suspend fun objectRelationListWithValue(command: Command.RelationListWithValue): List { + return middleware.objectRelationListWithValue(command) + } + override suspend fun debugAccountSelectTrace(dir: String): String { return middleware.debugAccountSelectTrace(dir) } + + override suspend fun objectDateByTimestamp(command: Command.ObjectDateByTimestamp): Struct? { + return middleware.objectDateByTimestamp(command) + } } \ No newline at end of file diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/Middleware.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/Middleware.kt index f799bd421a..a3c3c8ee24 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/Middleware.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/Middleware.kt @@ -26,6 +26,7 @@ import com.anytypeio.anytype.core_models.ObjectWrapper import com.anytypeio.anytype.core_models.Payload import com.anytypeio.anytype.core_models.Position import com.anytypeio.anytype.core_models.RelationFormat +import com.anytypeio.anytype.core_models.RelationListWithValueItem import com.anytypeio.anytype.core_models.Relations import com.anytypeio.anytype.core_models.Response import com.anytypeio.anytype.core_models.SearchResult @@ -1159,6 +1160,18 @@ class Middleware @Inject constructor( return response.event.toPayload() } + @Throws(Exception::class) + fun objectRelationListWithValue(command: Command.RelationListWithValue): List { + val request = Rpc.Relation.ListWithValue.Request( + spaceId = command.space.id, + value_ = command.value + ) + logRequestIfDebug(request) + val (response, time) = measureTimedValue { service.objectRelationListWithValue(request) } + logResponseIfDebug(response, time) + return response.list.map { it.toCoreModel() } + } + @Throws(Exception::class) fun objectRelationRemoveFeatured( ctx: Id, @@ -2828,6 +2841,18 @@ class Middleware @Inject constructor( return response.path } + @Throws(Exception::class) + fun objectDateByTimestamp(command: Command.ObjectDateByTimestamp): Struct? { + val request = Rpc.Object.DateByTimestamp.Request( + timestamp = command.timestamp, + spaceId = command.space.id + ) + logRequestIfDebug(request) + val (response, time) = measureTimedValue { service.objectDateByTimestamp(request) } + logResponseIfDebug(response, time) + return response.details + } + private fun logRequestIfDebug(request: Any) { if (BuildConfig.DEBUG) { logger.logRequest(request).also { diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/ToCoreModelMappers.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/ToCoreModelMappers.kt index 28c3a83873..6b4f7aa13a 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/ToCoreModelMappers.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/ToCoreModelMappers.kt @@ -3,7 +3,6 @@ package com.anytypeio.anytype.middleware.mappers import anytype.ResponseEvent import anytype.Rpc import anytype.model.Account -import anytype.model.Import import anytype.model.NameserviceNameType import anytype.model.ParticipantPermissions import anytype.model.Restrictions @@ -44,7 +43,9 @@ import com.anytypeio.anytype.core_models.Process import com.anytypeio.anytype.core_models.Relation import com.anytypeio.anytype.core_models.RelationFormat import com.anytypeio.anytype.core_models.RelationLink +import com.anytypeio.anytype.core_models.RelationListWithValueItem import com.anytypeio.anytype.core_models.SpaceUsage +import com.anytypeio.anytype.core_models.TimeInSeconds import com.anytypeio.anytype.core_models.chats.Chat import com.anytypeio.anytype.core_models.history.DiffVersionResponse import com.anytypeio.anytype.core_models.history.ShowVersionResponse @@ -60,8 +61,9 @@ import com.anytypeio.anytype.core_models.multiplayer.SpaceMemberPermissions import com.anytypeio.anytype.core_models.multiplayer.SpaceSyncError import com.anytypeio.anytype.core_models.multiplayer.SpaceSyncNetwork import com.anytypeio.anytype.core_models.multiplayer.SpaceSyncStatus +import com.anytypeio.anytype.core_models.primitives.RelationKey import com.anytypeio.anytype.core_models.primitives.SpaceId -import com.anytypeio.anytype.core_models.primitives.TimeInSeconds +import com.anytypeio.anytype.core_models.primitives.TimestampInSeconds import com.anytypeio.anytype.core_models.restrictions.DataViewRestriction import com.anytypeio.anytype.core_models.restrictions.DataViewRestrictions import com.anytypeio.anytype.core_models.restrictions.ObjectRestriction @@ -1150,7 +1152,7 @@ fun Rpc.History.Version.toCoreModel(): Version { previousIds = previousIds, spaceMember = authorId, spaceMemberName = authorName, - timestamp = TimeInSeconds(time), + timestamp = TimestampInSeconds(time), groupId = groupId ) } @@ -1170,4 +1172,11 @@ fun Rpc.History.DiffVersions.Response.toCoreModel( historyEvents = historyEvents.mapNotNull { it.toCoreModels(context) }, objectView = objectView?.toCore() ) +} + +fun Rpc.Relation.ListWithValue.Response.ResponseItem.toCoreModel(): RelationListWithValueItem { + return RelationListWithValueItem( + key = RelationKey(key = relationKey), + counter = counter + ) } \ No newline at end of file diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareService.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareService.kt index 563577dd27..0415066db3 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareService.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareService.kt @@ -156,6 +156,9 @@ interface MiddlewareService { @Throws(Exception::class) fun createTemplateFromObject(request: Rpc.Template.CreateFromObject.Request): Rpc.Template.CreateFromObject.Response + @Throws(Exception::class) + fun objectDateByTimestamp(request: Rpc.Object.DateByTimestamp.Request): Rpc.Object.DateByTimestamp.Response + //endregion //region OBJECT'S RELATIONS command @@ -175,6 +178,9 @@ interface MiddlewareService { @Throws(Exception::class) fun objectRelationListAvailable(request: Rpc.ObjectRelation.ListAvailable.Request): Rpc.ObjectRelation.ListAvailable.Response + @Throws(Exception::class) + fun objectRelationListWithValue(request: Rpc.Relation.ListWithValue.Request): Rpc.Relation.ListWithValue.Response + // @Throws(Exception::class) // fun objectRelationOptionAdd(request: Rpc.ObjectRelationOption.Add.Request): Rpc.ObjectRelationOption.Add.Response diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareServiceImplementation.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareServiceImplementation.kt index 71497b9d7b..12ef1b2be2 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareServiceImplementation.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareServiceImplementation.kt @@ -961,6 +961,19 @@ class MiddlewareServiceImplementation @Inject constructor( } } + override fun objectRelationListWithValue(request: Rpc.Relation.ListWithValue.Request): Rpc.Relation.ListWithValue.Response { + val encoded = Service.relationListWithValue( + Rpc.Relation.ListWithValue.Request.ADAPTER.encode(request) + ) + val response = Rpc.Relation.ListWithValue.Response.ADAPTER.decode(encoded) + val error = response.error + if (error != null && error.code != Rpc.Relation.ListWithValue.Response.Error.Code.NULL) { + throw Exception(error.description) + } else { + return response + } + } + override fun objectRelationRemoveFeatured(request: Rpc.ObjectRelation.RemoveFeatured.Request): Rpc.ObjectRelation.RemoveFeatured.Response { val encoded = Service.objectRelationRemoveFeatured( Rpc.ObjectRelation.RemoveFeatured.Request.ADAPTER.encode(request) @@ -2424,4 +2437,17 @@ class MiddlewareServiceImplementation @Inject constructor( return response } } + + override fun objectDateByTimestamp(request: Rpc.Object.DateByTimestamp.Request): Rpc.Object.DateByTimestamp.Response { + val encoded = Service.objectDateByTimestamp( + Rpc.Object.DateByTimestamp.Request.ADAPTER.encode(request) + ) + val response = Rpc.Object.DateByTimestamp.Response.ADAPTER.decode(encoded) + val error = response.error + if (error != null && error.code != Rpc.Object.DateByTimestamp.Response.Error.Code.NULL) { + throw Exception(error.description) + } else { + return response + } + } } \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/ControlPanelMachine.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/ControlPanelMachine.kt index 2984deb3fb..b575ef36f0 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/ControlPanelMachine.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/ControlPanelMachine.kt @@ -20,6 +20,7 @@ import com.anytypeio.anytype.presentation.editor.editor.styling.getSupportedMark import com.anytypeio.anytype.presentation.editor.editor.table.SimpleTableWidgetItem import com.anytypeio.anytype.presentation.extension.style import com.anytypeio.anytype.presentation.navigation.DefaultObjectView +import com.anytypeio.anytype.presentation.navigation.DefaultSearchItem import com.anytypeio.anytype.presentation.objects.ObjectTypeView import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.Channel @@ -215,7 +216,7 @@ sealed class ControlPanelMachine { sealed class Mentions : Event() { data class OnStart(val cursorCoordinate: Int, val mentionFrom: Int) : Mentions() data class OnQuery(val text: String) : Mentions() - data class OnResult(val mentions: List, val text: String) : + data class OnResult(val mentions: List, val text: String) : Mentions() object OnMentionClicked : Mentions() diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModel.kt index 368efca553..61168173d3 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModel.kt @@ -48,6 +48,7 @@ import com.anytypeio.anytype.core_models.ext.sortByType import com.anytypeio.anytype.core_models.ext.supportNesting import com.anytypeio.anytype.core_models.ext.title import com.anytypeio.anytype.core_models.ext.updateTextContent +import com.anytypeio.anytype.core_models.getSingleValue import com.anytypeio.anytype.core_models.multiplayer.SpaceSyncAndP2PStatusState import com.anytypeio.anytype.core_models.primitives.SpaceId import com.anytypeio.anytype.core_models.primitives.TypeId @@ -59,7 +60,6 @@ import com.anytypeio.anytype.core_utils.ext.cancel import com.anytypeio.anytype.core_utils.ext.isEndLineClick import com.anytypeio.anytype.core_utils.ext.replace import com.anytypeio.anytype.core_utils.ext.switchToLatestFrom -import com.anytypeio.anytype.core_utils.ext.toast import com.anytypeio.anytype.core_utils.ext.withLatestFrom import com.anytypeio.anytype.core_utils.tools.FeatureToggles import com.anytypeio.anytype.core_utils.tools.toPrettyString @@ -84,12 +84,14 @@ import com.anytypeio.anytype.domain.icon.SetImageIcon import com.anytypeio.anytype.domain.launch.GetDefaultObjectType import com.anytypeio.anytype.domain.library.StoreSearchByIdsParams import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer +import com.anytypeio.anytype.domain.misc.DateProvider import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.multiplayer.UserPermissionProvider import com.anytypeio.anytype.domain.networkmode.GetNetworkMode import com.anytypeio.anytype.domain.`object`.ConvertObjectToCollection import com.anytypeio.anytype.domain.`object`.ConvertObjectToSet import com.anytypeio.anytype.domain.`object`.UpdateDetail +import com.anytypeio.anytype.domain.objects.GetDateObjectByTimestamp import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes import com.anytypeio.anytype.domain.objects.StoreOfRelations import com.anytypeio.anytype.domain.page.CloseBlock @@ -97,6 +99,7 @@ import com.anytypeio.anytype.domain.page.CreateBlockLinkWithObject import com.anytypeio.anytype.domain.page.CreateObject import com.anytypeio.anytype.domain.page.CreateObjectAsMentionOrLink import com.anytypeio.anytype.domain.page.OpenPage +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.relations.AddRelationToObject import com.anytypeio.anytype.domain.search.SearchObjects import com.anytypeio.anytype.domain.sets.FindObjectSetForType @@ -181,7 +184,9 @@ import com.anytypeio.anytype.presentation.editor.editor.table.EditorTableEvent import com.anytypeio.anytype.presentation.editor.editor.table.SimpleTableWidgetItem import com.anytypeio.anytype.presentation.editor.editor.toCoreModel import com.anytypeio.anytype.presentation.editor.editor.updateText +import com.anytypeio.anytype.presentation.editor.model.EditorDatePickerState import com.anytypeio.anytype.presentation.editor.model.EditorFooter +import com.anytypeio.anytype.presentation.editor.model.OnEditorDatePickerEvent import com.anytypeio.anytype.presentation.editor.model.TextUpdate import com.anytypeio.anytype.presentation.editor.picker.PickerListener import com.anytypeio.anytype.presentation.editor.render.BlockViewRenderer @@ -234,14 +239,19 @@ import com.anytypeio.anytype.presentation.home.navigation import com.anytypeio.anytype.presentation.mapper.mark import com.anytypeio.anytype.presentation.mapper.style import com.anytypeio.anytype.presentation.navigation.AppNavigation +import com.anytypeio.anytype.presentation.navigation.AppNavigation.Command.* import com.anytypeio.anytype.presentation.navigation.DefaultObjectView +import com.anytypeio.anytype.presentation.navigation.DefaultSearchItem +import com.anytypeio.anytype.presentation.navigation.NewObject +import com.anytypeio.anytype.presentation.navigation.SectionDates +import com.anytypeio.anytype.presentation.navigation.SectionObjects +import com.anytypeio.anytype.presentation.navigation.SelectDateItem import com.anytypeio.anytype.presentation.navigation.SupportNavigation import com.anytypeio.anytype.presentation.objects.ObjectIcon import com.anytypeio.anytype.presentation.objects.ObjectTypeView -import com.anytypeio.anytype.presentation.objects.SupportedLayouts +import com.anytypeio.anytype.core_models.SupportedLayouts import com.anytypeio.anytype.presentation.objects.getCreateObjectParams import com.anytypeio.anytype.presentation.objects.getObjectTypeViewsForSBPage -import com.anytypeio.anytype.presentation.objects.getProperName import com.anytypeio.anytype.presentation.objects.getProperType import com.anytypeio.anytype.presentation.objects.isTemplatesAllowed import com.anytypeio.anytype.presentation.objects.toViews @@ -331,7 +341,10 @@ class EditorViewModel( private val getNetworkMode: GetNetworkMode, private val clearLastOpenedObject: ClearLastOpenedObject, private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate, - private val spaceSyncAndP2PStatusProvider: SpaceSyncAndP2PStatusProvider + private val spaceSyncAndP2PStatusProvider: SpaceSyncAndP2PStatusProvider, + private val fieldParser : FieldParser, + private val dateProvider: DateProvider, + private val getDateObjectByTimestamp: GetDateObjectByTimestamp ) : ViewStateViewModel(), PickerListener, SupportNavigation>, @@ -413,6 +426,11 @@ class EditorViewModel( val permission = MutableStateFlow(permissions.get(vmParams.space)) + /** + * Mention date picker + */ + val mentionDatePicker = MutableStateFlow(EditorDatePickerState.Hidden) + init { Timber.i("EditorViewModel, init") proceedWithObservingPermissions() @@ -4340,7 +4358,7 @@ class EditorViewModel( orchestrator.proxies.intents.send( Media.DownloadFile( url = url, - name = obj.getProperName(), + name = fieldParser.getObjectName(obj), type = null ) ) @@ -4465,6 +4483,16 @@ class EditorViewModel( OpenObjectNavigation.NonValidObject -> { sendToast("Object id is missing") } + is OpenObjectNavigation.OpenDataObject -> { + navigate( + EventWrapper( + OpenDateObject( + objectId = navigation.target, + space = navigation.space + ) + ) + ) + } } } @@ -5247,7 +5275,8 @@ class EditorViewModel( details = details, values = objectDetails, urlBuilder = urlBuilder, - featured = objectWrapper.featuredRelations + featured = objectWrapper.featuredRelations, + fieldParser = fieldParser ) } @@ -5268,7 +5297,8 @@ class EditorViewModel( context = ctx, details = details, values = objectDetails, - urlBuilder = urlBuilder + urlBuilder = urlBuilder, + fieldParser = fieldParser ) } @@ -6114,15 +6144,113 @@ class EditorViewModel( } } - fun onMentionSuggestClick(mention: DefaultObjectView, mentionTrigger: String, pos: Int) { + fun onMentionSuggestClick(mention: DefaultSearchItem, mentionTrigger: String, pos: Int) { Timber.d("onMentionSuggestClick, mention:[$mention] mentionTrigger:[$mentionTrigger]") - viewModelScope.sendAnalyticsSearchResultEvent( - analytics = analytics, - pos = pos, - length = mentionTrigger.length - 1, - spaceParams = provideParams(vmParams.space.id) + if (mention is DefaultObjectView) { + viewModelScope.sendAnalyticsSearchResultEvent( + analytics = analytics, + pos = pos, + length = mentionTrigger.length - 1, + spaceParams = provideParams(vmParams.space.id) + ) + onCreateMentionInText(id = mention.id, name = mention.name, mentionTrigger = mentionTrigger) + } + if (mention is SelectDateItem) { + mentionDatePicker.value = EditorDatePickerState.Visible + } + } + + private enum class EditorCalendarDateShortcuts { + TODAY, + TOMORROW + } + + fun onEditorDatePickerEvent(event: OnEditorDatePickerEvent) { + Timber.d("onEditorDatePickerEvent, event:[$event]") + + when (event) { + is OnEditorDatePickerEvent.OnDateSelected -> { + handleDatePickerDismiss() + dispatch(Command.ShowKeyboard) + handleDateSelected(event.timeInMillis) + } + is OnEditorDatePickerEvent.OnDatePickerDismiss -> { + dispatch(Command.ShowKeyboard) + handleDatePickerDismiss() + } + OnEditorDatePickerEvent.OnTodayClick -> { + handleDatePickerDismiss() + dispatch(Command.ShowKeyboard) + handlePredefinedDateClick(editorCalendarDateShortcuts = EditorCalendarDateShortcuts.TODAY) + } + OnEditorDatePickerEvent.OnTomorrowClick -> { + handleDatePickerDismiss() + dispatch(Command.ShowKeyboard) + handlePredefinedDateClick(editorCalendarDateShortcuts = EditorCalendarDateShortcuts.TOMORROW) + } + } + } + + private fun handleDateSelected(timeInMillis: Long?) { + if (timeInMillis == null) { + sendToast("Selected time is invalid.") + Timber.w("OnDateSelected received null timeInMillis") + return + } + + val adjustedTimestamp = dateProvider.adjustFromStartOfDayInUserTimeZoneToUTC(timeInMillis) + handleTimestamp(adjustedTimestamp) + } + + private fun handlePredefinedDateClick(editorCalendarDateShortcuts: EditorCalendarDateShortcuts) { + val timestamp = when (editorCalendarDateShortcuts) { + EditorCalendarDateShortcuts.TODAY -> dateProvider.getTimestampForTodayAtStartOfDay() + EditorCalendarDateShortcuts.TOMORROW -> dateProvider.getTimestampForTomorrowAtStartOfDay() + } + handleTimestamp(timestamp) + } + + private fun handleTimestamp(timestamp: Long) { + proceedWithGettingDateByTimestamp(timestamp) { dateObject -> + Timber.d("handleTimestamp, dateObject:[$dateObject]") + val id = dateObject?.getSingleValue(Relations.ID) + val name = dateObject?.getSingleValue(Relations.NAME) + + if (id != null && name != null) { + onCreateMentionInText( + id = id, + name = name, + mentionTrigger = mentionFilter.value + ) + } else { + sendToast("Error while creating mention, date object is null") + Timber.e("Date object missing ID or name.") + } + } + } + + private fun handleDatePickerDismiss() { + mentionDatePicker.value = EditorDatePickerState.Hidden + } + + private fun proceedWithGettingDateByTimestamp(timestamp: Long, action: (Struct?) -> Unit) { + val params = GetDateObjectByTimestamp.Params( + space = vmParams.space, + timestamp = timestamp ) - onCreateMentionInText(id = mention.id, name = mention.name, mentionTrigger = mentionTrigger) + Timber.d("Start ObjectDateByTimestamp with params: [$params]") + viewModelScope.launch { + getDateObjectByTimestamp.async(params).fold( + onSuccess = { dateObject -> + Timber.d("ObjectDateByTimestamp Success, dateObject: [$dateObject]") + action(dateObject) + }, + onFailure = { e -> + Timber.e(e, "ObjectDateByTimestamp Error") + sendToast("Failed to retrieve date object.") + } + ) + } } fun onCreateMentionInText(id: Id, name: String, mentionTrigger: String) { @@ -6192,7 +6320,12 @@ class EditorViewModel( fun onMentionClicked(target: String) { if (isObjectTemplate()) return - proceedWithOpeningObjectByLayout(target) + val details = orchestrator.stores.details.current() + val objectDetails = details.details[target]?.map ?: return + if (objectDetails.isEmpty()) return + val obj = ObjectWrapper.Basic(objectDetails) + proceedWithClearingFocus() + proceedWithOpeningObject(obj) } private suspend fun onMentionFilter(filter: String) { @@ -6218,16 +6351,14 @@ class EditorViewModel( val objects = result .toViews( urlBuilder = urlBuilder, - objectTypes = storeOfObjectTypes.getAll() + objectTypes = storeOfObjectTypes.getAll(), + fieldParser = fieldParser ) - .filter { - SupportedLayouts.layouts.contains(it.layout) - && it.type != ObjectTypeIds.TEMPLATE - } + controlPanelInteractor.onEvent( ControlPanelMachine.Event.Mentions.OnResult( - objects, - filter + mentions = createSectionedList(objects), + text = filter ) ) }, @@ -6237,6 +6368,32 @@ class EditorViewModel( } } + fun createSectionedList(items: List): List { + + val (dateItems, otherItems) = items.partition { it.layout == ObjectType.Layout.DATE } + + val sectionedList = mutableListOf() + + if (dateItems.isNotEmpty()) { + sectionedList.add(SectionDates) + dateItems.forEach { item -> + sectionedList.add(item) + } + sectionedList.add(SelectDateItem) + } + + if (otherItems.isNotEmpty()) { + sectionedList.add(SectionObjects) + otherItems.forEach { item -> + sectionedList.add(item) + } + } + + sectionedList.add(NewObject) + + return sectionedList + } + fun onDragAndDrop( dragged: Id, target: Id, diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModelFactory.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModelFactory.kt index 4e1407d0a3..1f53e99297 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModelFactory.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModelFactory.kt @@ -20,12 +20,14 @@ import com.anytypeio.anytype.domain.event.interactor.SpaceSyncAndP2PStatusProvid import com.anytypeio.anytype.domain.icon.SetDocumentImageIcon import com.anytypeio.anytype.domain.launch.GetDefaultObjectType import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer +import com.anytypeio.anytype.domain.misc.DateProvider import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.multiplayer.UserPermissionProvider import com.anytypeio.anytype.domain.networkmode.GetNetworkMode import com.anytypeio.anytype.domain.`object`.ConvertObjectToCollection import com.anytypeio.anytype.domain.`object`.ConvertObjectToSet import com.anytypeio.anytype.domain.`object`.UpdateDetail +import com.anytypeio.anytype.domain.objects.GetDateObjectByTimestamp import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes import com.anytypeio.anytype.domain.objects.StoreOfRelations import com.anytypeio.anytype.domain.page.CloseBlock @@ -33,6 +35,7 @@ import com.anytypeio.anytype.domain.page.CreateBlockLinkWithObject import com.anytypeio.anytype.domain.page.CreateObject import com.anytypeio.anytype.domain.page.CreateObjectAsMentionOrLink import com.anytypeio.anytype.domain.page.OpenPage +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.relations.AddRelationToObject import com.anytypeio.anytype.domain.search.SearchObjects import com.anytypeio.anytype.domain.sets.FindObjectSetForType @@ -97,7 +100,10 @@ open class EditorViewModelFactory @Inject constructor( private val getNetworkMode: GetNetworkMode, private val clearLastOpenedObject: ClearLastOpenedObject, private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate, - private val syncStatusProvider: SpaceSyncAndP2PStatusProvider + private val syncStatusProvider: SpaceSyncAndP2PStatusProvider, + private val fieldParser : FieldParser, + private val dateProvider: DateProvider, + private val getDateObjectByTimestamp: GetDateObjectByTimestamp ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") @@ -147,7 +153,10 @@ open class EditorViewModelFactory @Inject constructor( spaceSyncAndP2PStatusProvider = syncStatusProvider, getNetworkMode = getNetworkMode, analyticSpaceHelperDelegate = analyticSpaceHelperDelegate, - clearLastOpenedObject = clearLastOpenedObject + clearLastOpenedObject = clearLastOpenedObject, + fieldParser = fieldParser, + dateProvider = dateProvider, + getDateObjectByTimestamp = getDateObjectByTimestamp ) as T } } \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/Markup.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/Markup.kt index 2f905edcd0..1fee6c106a 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/Markup.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/Markup.kt @@ -103,6 +103,12 @@ interface Markup { override val param: String ) : Mention() + data class Date( + override val from: Int, + override val to: Int, + override val param: String, + ) : Mention() + sealed class Profile : Mention() { abstract val isArchived: Boolean data class WithImage( diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/control/ControlPanelState.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/control/ControlPanelState.kt index 474eeafea8..39290bbbfe 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/control/ControlPanelState.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/control/ControlPanelState.kt @@ -8,6 +8,7 @@ import com.anytypeio.anytype.presentation.editor.editor.styling.StyleToolbarStat import com.anytypeio.anytype.presentation.editor.editor.table.SimpleTableWidgetItem import com.anytypeio.anytype.presentation.editor.markup.MarkupStyleDescriptor import com.anytypeio.anytype.presentation.navigation.DefaultObjectView +import com.anytypeio.anytype.presentation.navigation.DefaultSearchItem import com.anytypeio.anytype.presentation.objects.ObjectTypeView /** @@ -207,7 +208,7 @@ data class ControlPanelState( val mentionFilter: String?, val cursorCoordinate: Int?, val updateList: Boolean = false, - val mentions: List = emptyList() + val mentions: List = emptyList() ) : Toolbar() { companion object { fun reset(): MentionToolbar = MentionToolbar( diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/ext/ContentTextExt.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/ext/ContentTextExt.kt index 4d9c00f39a..307ae3670b 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/ext/ContentTextExt.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/ext/ContentTextExt.kt @@ -1,17 +1,21 @@ package com.anytypeio.anytype.presentation.editor.editor.ext import com.anytypeio.anytype.core_models.Block +import com.anytypeio.anytype.core_models.Id +import com.anytypeio.anytype.core_models.MAX_SNIPPET_SIZE +import com.anytypeio.anytype.core_models.ObjectType import com.anytypeio.anytype.core_models.Relations import com.anytypeio.anytype.core_models.ext.replaceRangeWithWord +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.presentation.editor.editor.Markup import com.anytypeio.anytype.presentation.editor.editor.Markup.Companion.NON_EXISTENT_OBJECT_MENTION_NAME -import com.anytypeio.anytype.presentation.extension.getProperObjectName import com.anytypeio.anytype.presentation.extension.shift import timber.log.Timber fun Block.Content.Text.getTextAndMarks( details: Block.Details, - marks: List + marks: List, + fieldParser: FieldParser ): Pair> { if (details.details.isEmpty() || marks.none { it is Markup.Mark.Mention } @@ -51,3 +55,13 @@ fun Block.Content.Text.getTextAndMarks( return Pair(updatedText, updatedMarks) } +private fun Map.getProperObjectName(id: Id?): String? { + if (id == null) return null + val layoutCode = this[id]?.layout?.toInt() + return if (layoutCode == ObjectType.Layout.NOTE.code) { + this[id]?.snippet?.replace("\n", " ")?.take(MAX_SNIPPET_SIZE) + } else { + this[id]?.name + } +} + diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/mention/MentionExt.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/mention/MentionExt.kt index c07efc83ac..73f3dde45e 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/mention/MentionExt.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/mention/MentionExt.kt @@ -72,6 +72,13 @@ fun Block.Content.Text.Mark.createMentionMarkup( urlBuilder = urlBuilder, isArchived = isArchived ) + ObjectType.Layout.DATE -> { + Markup.Mark.Mention.Date( + from = range.first, + to = range.last, + param = p, + ) + } ObjectType.Layout.PROFILE, ObjectType.Layout.PARTICIPANT -> { if (image.isNullOrBlank()) { Markup.Mark.Mention.Profile.WithInitials( diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/model/BlockView.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/model/BlockView.kt index f0d7c1096c..0dff6a24f5 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/model/BlockView.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/model/BlockView.kt @@ -53,6 +53,7 @@ import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_PICTURE_UPLOAD import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_PROFILE_TITLE import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_RELATION_CHECKBOX +import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_RELATION_DATE import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_RELATION_DEFAULT import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_RELATION_DELETED import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_RELATION_FILE @@ -1300,6 +1301,7 @@ sealed class BlockView : ViewType { is ObjectRelationView.Source -> HOLDER_RELATION_SOURCE is ObjectRelationView.ObjectType.Deleted -> HOLDER_OBJECT_TYPE_DELETED is ObjectRelationView.Links -> HOLDER_RELATION_OBJECT + is ObjectRelationView.Date -> HOLDER_RELATION_DATE } } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/model/types/Types.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/model/types/Types.kt index e6afe2b975..d34e1ecaa7 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/model/types/Types.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/model/types/Types.kt @@ -57,6 +57,7 @@ object Types { const val HOLDER_RELATION_FILE = 44 const val HOLDER_RELATION_CHECKBOX = 45 const val HOLDER_RELATION_SOURCE = 58 + const val HOLDER_RELATION_DATE = 59 const val HOLDER_RELATION_DELETED = 160 const val HOLDER_DESCRIPTION = 46 diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/model/EditorDatePicker.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/model/EditorDatePicker.kt new file mode 100644 index 0000000000..f8ecacd152 --- /dev/null +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/model/EditorDatePicker.kt @@ -0,0 +1,13 @@ +package com.anytypeio.anytype.presentation.editor.model + +sealed class EditorDatePickerState { + data object Hidden : EditorDatePickerState() + data object Visible : EditorDatePickerState() +} + +sealed class OnEditorDatePickerEvent { + object OnDatePickerDismiss : OnEditorDatePickerEvent() + data class OnDateSelected(val timeInMillis: Long?) : OnEditorDatePickerEvent() + object OnTodayClick : OnEditorDatePickerEvent() + object OnTomorrowClick : OnEditorDatePickerEvent() +} \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/render/DefaultBlockViewRenderer.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/render/DefaultBlockViewRenderer.kt index c41224be9f..f019deabd4 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/render/DefaultBlockViewRenderer.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/render/DefaultBlockViewRenderer.kt @@ -18,6 +18,7 @@ import com.anytypeio.anytype.domain.editor.Editor.Focus import com.anytypeio.anytype.domain.misc.UrlBuilder 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.presentation.editor.Editor import com.anytypeio.anytype.presentation.editor.cover.CoverImageHashProvider import com.anytypeio.anytype.presentation.editor.editor.ext.getTextAndMarks @@ -25,7 +26,6 @@ import com.anytypeio.anytype.presentation.editor.editor.model.Alignment import com.anytypeio.anytype.presentation.editor.editor.model.BlockView import com.anytypeio.anytype.presentation.editor.editor.model.BlockView.Appearance.InEditor import com.anytypeio.anytype.presentation.editor.toggle.ToggleStateHolder -import com.anytypeio.anytype.presentation.extension.getProperObjectName import com.anytypeio.anytype.presentation.mapper.marks import com.anytypeio.anytype.presentation.mapper.objectIcon import com.anytypeio.anytype.presentation.mapper.toFileView @@ -50,7 +50,8 @@ class DefaultBlockViewRenderer @Inject constructor( private val toggleStateHolder: ToggleStateHolder, private val coverImageHashProvider: CoverImageHashProvider, private val storeOfRelations: StoreOfRelations, - private val storeOfObjectTypes: StoreOfObjectTypes + private val storeOfObjectTypes: StoreOfObjectTypes, + private val fieldParser: FieldParser ) : BlockViewRenderer, ToggleStateHolder by toggleStateHolder { override suspend fun Map>.render( @@ -686,7 +687,8 @@ class DefaultBlockViewRenderer @Inject constructor( indent = indent, details = details, urlBuilder = urlBuilder, - schema = blockDecorationScheme + schema = blockDecorationScheme, + fieldParser = fieldParser, ) ) } @@ -696,7 +698,8 @@ class DefaultBlockViewRenderer @Inject constructor( val featured = featured( ctx = root.id, block = block, - details = details + details = details, + fieldParser = fieldParser, ) if (featured.relations.isNotEmpty()) { @@ -798,7 +801,8 @@ class DefaultBlockViewRenderer @Inject constructor( val marks = content.marks(details = details, urlBuilder = urlBuilder) val (normalizedText, normalizedMarks) = content.getTextAndMarks( details = details, - marks = marks + marks = marks, + fieldParser = fieldParser ) val isFocused = resolveIsFocused(focus, block) @@ -859,7 +863,8 @@ class DefaultBlockViewRenderer @Inject constructor( val marks = content.marks(details = details, urlBuilder = urlBuilder) val (normalizedText, normalizedMarks) = content.getTextAndMarks( details = details, - marks = marks + marks = marks, + fieldParser = fieldParser ) return BlockView.Text.Header.Three( mode = if (mode == EditorMode.Edit) BlockView.Mode.EDIT else BlockView.Mode.READ, @@ -894,7 +899,8 @@ class DefaultBlockViewRenderer @Inject constructor( val marks = content.marks(details = details, urlBuilder = urlBuilder) val (normalizedText, normalizedMarks) = content.getTextAndMarks( details = details, - marks = marks + marks = marks, + fieldParser = fieldParser ) return BlockView.Text.Header.Two( mode = if (mode == EditorMode.Edit) BlockView.Mode.EDIT else BlockView.Mode.READ, @@ -929,7 +935,8 @@ class DefaultBlockViewRenderer @Inject constructor( val marks = content.marks(details = details, urlBuilder = urlBuilder) val (normalizedText, normalizedMarks) = content.getTextAndMarks( details = details, - marks = marks + marks = marks, + fieldParser = fieldParser ) return BlockView.Text.Header.One( mode = if (mode == EditorMode.Edit) BlockView.Mode.EDIT else BlockView.Mode.READ, @@ -964,7 +971,8 @@ class DefaultBlockViewRenderer @Inject constructor( val marks = content.marks(details = details, urlBuilder = urlBuilder) val (normalizedText, normalizedMarks) = content.getTextAndMarks( details = details, - marks = marks + marks = marks, + fieldParser = fieldParser ) return BlockView.Text.Checkbox( mode = if (mode == EditorMode.Edit) BlockView.Mode.EDIT else BlockView.Mode.READ, @@ -999,7 +1007,8 @@ class DefaultBlockViewRenderer @Inject constructor( val marks = content.marks(details = details, urlBuilder = urlBuilder) val (normalizedText, normalizedMarks) = content.getTextAndMarks( details = details, - marks = marks + marks = marks, + fieldParser = fieldParser ) return BlockView.Text.Bulleted( mode = if (mode == EditorMode.Edit) BlockView.Mode.EDIT else BlockView.Mode.READ, @@ -1058,7 +1067,8 @@ class DefaultBlockViewRenderer @Inject constructor( val marks = content.marks(details = details, urlBuilder = urlBuilder) val (normalizedText, normalizedMarks) = content.getTextAndMarks( details = details, - marks = marks + marks = marks, + fieldParser = fieldParser ) val current = listOf( BlockView.Decoration( @@ -1100,7 +1110,8 @@ class DefaultBlockViewRenderer @Inject constructor( val marks = content.marks(details = details, urlBuilder = urlBuilder) val (normalizedText, normalizedMarks) = content.getTextAndMarks( details = details, - marks = marks + marks = marks, + fieldParser = fieldParser ) val iconImage = content.iconImage val iconEmoji = content.iconEmoji @@ -1153,7 +1164,8 @@ class DefaultBlockViewRenderer @Inject constructor( val marks = content.marks(details = details, urlBuilder = urlBuilder) val (normalizedText, normalizedMarks) = content.getTextAndMarks( details = details, - marks = marks + marks = marks, + fieldParser = fieldParser ) return BlockView.Text.Toggle( mode = if (mode == EditorMode.Edit) BlockView.Mode.EDIT else BlockView.Mode.READ, @@ -1190,7 +1202,8 @@ class DefaultBlockViewRenderer @Inject constructor( val marks = content.marks(details = details, urlBuilder = urlBuilder) val (normalizedText, normalizedMarks) = content.getTextAndMarks( details = details, - marks = marks + marks = marks, + fieldParser = fieldParser ) return BlockView.Text.Numbered( mode = if (mode == EditorMode.Edit) BlockView.Mode.EDIT else BlockView.Mode.READ, @@ -1673,7 +1686,7 @@ class DefaultBlockViewRenderer @Inject constructor( ObjectIcon.None } - val name = obj.getProperObjectName()?.trim() + val name = fieldParser.getObjectName(obj) val description = when (inEditorAppearance.description) { InEditor.Description.NONE -> null @@ -1851,7 +1864,7 @@ class DefaultBlockViewRenderer @Inject constructor( isEmpty = true, emoji = null, image = null, - text = obj.getProperObjectName()?.trim(), + text = fieldParser.getObjectName(obj), indent = indent, isSelected = checkIfSelected( mode = mode, @@ -2065,7 +2078,8 @@ class DefaultBlockViewRenderer @Inject constructor( indent: Int, details: Block.Details, urlBuilder: UrlBuilder, - schema: NestedDecorationData + schema: NestedDecorationData, + fieldParser: FieldParser ): BlockView.Relation { val relationKey = content.key if (relationKey.isNullOrEmpty()) { @@ -2081,24 +2095,16 @@ class DefaultBlockViewRenderer @Inject constructor( val view = relation.view( details = details.details, values = details.details[ctx]?.map ?: emptyMap(), - urlBuilder = urlBuilder + urlBuilder = urlBuilder, + fieldParser = fieldParser + ) + return BlockView.Relation.Related( + id = block.id, + view = view, + indent = indent, + background = block.parseThemeBackgroundColor(), + decorations = schema.toBlockViewDecoration(block) ) - return if (view != null) { - BlockView.Relation.Related( - id = block.id, - view = view, - indent = indent, - background = block.parseThemeBackgroundColor(), - decorations = schema.toBlockViewDecoration(block) - ) - } else { - BlockView.Relation.Deleted( - id = block.id, - indent = indent, - decorations = schema.toBlockViewDecoration(block), - background = block.parseThemeBackgroundColor() - ) - } } else { return BlockView.Relation.Deleted( id = block.id, @@ -2113,7 +2119,8 @@ class DefaultBlockViewRenderer @Inject constructor( private suspend fun featured( ctx: Id, block: Block, - details: Block.Details + details: Block.Details, + fieldParser: FieldParser, ): BlockView.FeaturedRelation { val map = details.details[ctx]?.map ?: emptyMap() val obj = ObjectWrapper.Basic(map) @@ -2122,6 +2129,7 @@ class DefaultBlockViewRenderer @Inject constructor( ctx = ctx, keys = featuredKeys, details = details, + fieldParser = fieldParser ).sortedByDescending { it.key == Relations.TYPE || it.key == Relations.GLOBAL_NAME || it.key == Relations.IDENTITY } return BlockView.FeaturedRelation( @@ -2159,6 +2167,7 @@ class DefaultBlockViewRenderer @Inject constructor( ctx: Id, keys: List, details: Block.Details, + fieldParser: FieldParser ): List = keys.mapNotNull { key -> when (key) { Relations.DESCRIPTION -> null @@ -2188,7 +2197,8 @@ class DefaultBlockViewRenderer @Inject constructor( details = details.details, values = details.details[ctx]?.map ?: emptyMap(), urlBuilder = urlBuilder, - isFeatured = true + isFeatured = true, + fieldParser = fieldParser ) } } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/extension/FilterExtension.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/extension/FilterExtension.kt index c90a79f475..ca09cc625e 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/extension/FilterExtension.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/extension/FilterExtension.kt @@ -1,14 +1,12 @@ package com.anytypeio.anytype.presentation.extension -import com.anytypeio.anytype.core_models.Block import com.anytypeio.anytype.core_models.DVFilter import com.anytypeio.anytype.core_models.DVFilterCondition import com.anytypeio.anytype.core_models.DVSort -import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.objects.ObjectStore import com.anytypeio.anytype.domain.objects.StoreOfRelations -import com.anytypeio.anytype.domain.search.DataViewState +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.presentation.relations.toView import com.anytypeio.anytype.presentation.sets.filter.CreateFilterView import com.anytypeio.anytype.presentation.sets.filter.ViewerFilterViewModel @@ -62,7 +60,8 @@ suspend fun List.toView( storeOfRelations: StoreOfRelations, storeOfObjects: ObjectStore, screenState: ViewerFilterViewModel.ScreenState, - urlBuilder: UrlBuilder + urlBuilder: UrlBuilder, + fieldParser: FieldParser ): List = mapNotNull { filter -> val relation = storeOfRelations.getByKey(filter.relation) if (relation != null) { @@ -70,7 +69,8 @@ suspend fun List.toView( relation = relation, isInEditMode = screenState == ViewerFilterViewModel.ScreenState.EDIT, urlBuilder = urlBuilder, - store = storeOfObjects + store = storeOfObjects, + fieldParser = fieldParser ) } else { Timber.w("Could not found relation: ${filter.relation} for filter: $filter") diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/extension/MapExtension.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/extension/MapExtension.kt deleted file mode 100644 index b7204fcf1b..0000000000 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/extension/MapExtension.kt +++ /dev/null @@ -1,50 +0,0 @@ -package com.anytypeio.anytype.presentation.extension - -import com.anytypeio.anytype.core_models.Block -import com.anytypeio.anytype.core_models.Id -import com.anytypeio.anytype.core_models.ObjectType -import com.anytypeio.anytype.core_models.ObjectWrapper -import com.anytypeio.anytype.presentation.objects.SupportedLayouts - -const val MAX_SNIPPET_SIZE = 30 - -fun Map.updateFields(update: Map): Map { - val result = this.toMutableMap() - for ((key, value) in update) { - result[key] = value - } - return result -} - -fun Map.getProperObjectName(id: Id?): String? { - if (id == null) return null - val layoutCode = this[id]?.layout?.toInt() - return if (layoutCode == ObjectType.Layout.NOTE.code) { - this[id]?.snippet?.replace("\n", " ")?.take(MAX_SNIPPET_SIZE) - } else { - this[id]?.name - } -} - -fun ObjectWrapper.Basic.getProperObjectName(): String? { - return when (layout) { - ObjectType.Layout.NOTE -> { - snippet?.replace("\n", " ")?.take(MAX_SNIPPET_SIZE) - } - in SupportedLayouts.fileLayouts -> { - val fileName = if (name.isNullOrBlank()) "Untitled" else name.orEmpty() - if (fileExt.isNullOrBlank()) { - fileName - } else { - if (fileName.endsWith(".$fileExt")) { - fileName - } else { - "$fileName.$fileExt" - } - } - } - else -> { - name - } - } -} diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/extension/MarkupExtension.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/extension/MarkupExtension.kt index 06b39d128a..c4bfc37987 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/extension/MarkupExtension.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/extension/MarkupExtension.kt @@ -205,5 +205,6 @@ private fun Markup.Mark.updateRanges(start: Int, length: Int): Markup.Mark { is Markup.Mark.Mention.Task.Unchecked -> copy(from = newFrom, to = newTo) is Markup.Mark.Mention.Profile.WithImage -> copy(from = newFrom, to = newTo) is Markup.Mark.Mention.Profile.WithInitials -> copy(from = newFrom, to = newTo) + is Markup.Mark.Mention.Date -> copy(from = newFrom, to = newTo) } } \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/history/VersionHistoryViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/history/VersionHistoryViewModel.kt index b54b1da75d..5f041d1f6f 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/history/VersionHistoryViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/history/VersionHistoryViewModel.kt @@ -14,7 +14,7 @@ import com.anytypeio.anytype.core_models.history.Version import com.anytypeio.anytype.core_models.isDataView import com.anytypeio.anytype.core_models.primitives.RelationKey import com.anytypeio.anytype.core_models.primitives.SpaceId -import com.anytypeio.anytype.core_models.primitives.TimeInSeconds +import com.anytypeio.anytype.core_models.primitives.TimestampInSeconds import com.anytypeio.anytype.domain.base.fold import com.anytypeio.anytype.domain.editor.Editor import com.anytypeio.anytype.domain.history.GetVersions @@ -419,7 +419,7 @@ class VersionHistoryViewModel( return groupedBySpaceMember } - private fun getGroupTitle(timestamp: TimeInSeconds): GroupTitle { + private fun getGroupTitle(timestamp: TimestampInSeconds): GroupTitle { val dateInstant = Instant.ofEpochSecond(timestamp.time) val givenDate = dateInstant.atZone(ZoneId.systemDefault()).toLocalDate() val currentDate = LocalDate.now() @@ -693,7 +693,7 @@ data class VersionHistoryGroup( val spaceMemberName: String, val dateFormatted: String, val timeFormatted: String, - val timeStamp: TimeInSeconds, + val timeStamp: TimestampInSeconds, val icon: ObjectIcon?, val versions: List ) diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/home/HomeScreenViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/home/HomeScreenViewModel.kt index 9587bb1f3e..0a29ed2021 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/home/HomeScreenViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/home/HomeScreenViewModel.kt @@ -87,7 +87,7 @@ import com.anytypeio.anytype.presentation.extension.sendSelectHomeTabEvent import com.anytypeio.anytype.presentation.home.Command.ChangeWidgetType.Companion.UNDEFINED_LAYOUT_CODE import com.anytypeio.anytype.presentation.navigation.DeepLinkToObjectDelegate import com.anytypeio.anytype.presentation.navigation.NavigationViewModel -import com.anytypeio.anytype.presentation.objects.SupportedLayouts +import com.anytypeio.anytype.core_models.SupportedLayouts import com.anytypeio.anytype.presentation.objects.getCreateObjectParams import com.anytypeio.anytype.presentation.search.Subscriptions import com.anytypeio.anytype.presentation.sets.prefillNewObjectDetails @@ -633,15 +633,24 @@ class HomeScreenViewModel( .withLatestFrom(spaceManager.observe()) { dispatch, config -> when (dispatch) { is WidgetDispatchEvent.SourcePicked.Default -> { - commands.emit( - Command.SelectWidgetType( + if (dispatch.sourceLayout == ObjectType.Layout.DATE.code) { + proceedWithCreatingWidget( ctx = config.widgets, source = dispatch.source, - layout = dispatch.sourceLayout, - target = dispatch.target, - isInEditMode = isInEditMode() + type = Command.ChangeWidgetType.TYPE_LINK, + target = dispatch.target ) - ) + } else { + commands.emit( + Command.SelectWidgetType( + ctx = config.widgets, + source = dispatch.source, + layout = dispatch.sourceLayout, + target = dispatch.target, + isInEditMode = isInEditMode() + ) + ) + } } is WidgetDispatchEvent.SourcePicked.Bundled -> { commands.emit( @@ -1406,6 +1415,14 @@ class HomeScreenViewModel( OpenObjectNavigation.NonValidObject -> { sendToast("Object id is missing") } + is OpenObjectNavigation.OpenDataObject -> { + navigate( + destination = Navigation.OpenDateObject( + ctx = navigation.target, + space = navigation.space + ) + ) + } } } @@ -2120,6 +2137,7 @@ class HomeScreenViewModel( data class ExpandWidget(val subscription: Subscription, val space: Id) : Navigation() data object OpenSpaceSwitcher: Navigation() data class OpenAllContent(val space: Id) : Navigation() + data class OpenDateObject(val ctx: Id, val space: Id) : Navigation() } class Factory @Inject constructor( @@ -2346,6 +2364,7 @@ sealed class OpenObjectNavigation { data class UnexpectedLayoutError(val layout: ObjectType.Layout?): OpenObjectNavigation() data object NonValidObject: OpenObjectNavigation() data class OpenDiscussion(val target: Id, val space: Id): OpenObjectNavigation() + data class OpenDataObject(val target: Id, val space: Id): OpenObjectNavigation() } fun ObjectWrapper.Basic.navigation() : OpenObjectNavigation { @@ -2394,6 +2413,12 @@ fun ObjectWrapper.Basic.navigation() : OpenObjectNavigation { space = requireNotNull(spaceId) ) } + ObjectType.Layout.DATE -> { + OpenObjectNavigation.OpenDataObject( + target = id, + space = requireNotNull(spaceId) + ) + } else -> { OpenObjectNavigation.UnexpectedLayoutError(layout) } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/linking/BackLinkOrAddToObjectViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/linking/BackLinkOrAddToObjectViewModel.kt index 6ace3f037c..f83f447f94 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/linking/BackLinkOrAddToObjectViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/linking/BackLinkOrAddToObjectViewModel.kt @@ -7,7 +7,9 @@ import com.anytypeio.anytype.core_models.ObjectType import com.anytypeio.anytype.core_models.ObjectWrapper import com.anytypeio.anytype.domain.base.Resultat import com.anytypeio.anytype.domain.block.interactor.sets.GetObjectTypes +import com.anytypeio.anytype.domain.misc.DateProvider import com.anytypeio.anytype.domain.misc.UrlBuilder +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.search.SearchObjects import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate import com.anytypeio.anytype.presentation.navigation.DefaultObjectView @@ -24,14 +26,16 @@ class BackLinkOrAddToObjectViewModel( searchObjects: SearchObjects, getObjectTypes: GetObjectTypes, analytics: Analytics, - analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate + analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate, + fieldParser: FieldParser ) : ObjectSearchViewModel( vmParams = vmParams, urlBuilder = urlBuilder, getObjectTypes = getObjectTypes, searchObjects = searchObjects, analytics = analytics, - analyticSpaceHelperDelegate = analyticSpaceHelperDelegate + analyticSpaceHelperDelegate = analyticSpaceHelperDelegate, + fieldParser = fieldParser ) { private val _commands = MutableSharedFlow(replay = 0) diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/linking/LinkToObjectOrWebViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/linking/LinkToObjectOrWebViewModel.kt index 8d503d8bd9..47954f9924 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/linking/LinkToObjectOrWebViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/linking/LinkToObjectOrWebViewModel.kt @@ -12,12 +12,13 @@ import com.anytypeio.anytype.core_models.primitives.SpaceId import com.anytypeio.anytype.core_utils.tools.UrlValidator import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.search.SearchObjects import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate import com.anytypeio.anytype.presentation.editor.Editor import com.anytypeio.anytype.presentation.extension.sendAnalyticsSearchResultEvent import com.anytypeio.anytype.presentation.objects.ObjectIcon -import com.anytypeio.anytype.presentation.objects.SupportedLayouts +import com.anytypeio.anytype.core_models.SupportedLayouts import com.anytypeio.anytype.presentation.objects.toLinkToObjectView import com.anytypeio.anytype.presentation.objects.toLinkToView import com.anytypeio.anytype.presentation.search.ObjectSearchConstants @@ -40,7 +41,8 @@ class LinkToObjectOrWebViewModel( private val analytics: Analytics, private val stores: Editor.Storage, private val urlValidator: UrlValidator, - private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate + private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate, + private val fieldParser: FieldParser ) : ViewModel(), AnalyticSpaceHelperDelegate by analyticSpaceHelperDelegate { val viewState = MutableStateFlow(ViewState.Init) @@ -109,7 +111,8 @@ class LinkToObjectOrWebViewModel( LinkToItemView.Subheading.LinkedTo, obj.toLinkToObjectView( urlBuilder = urlBuilder, - objectTypes = storeOfObjectTypes.getAll() + objectTypes = storeOfObjectTypes.getAll(), + fieldParser = fieldParser ), LinkToItemView.Subheading.Actions, LinkToItemView.RemoveLink @@ -129,7 +132,8 @@ class LinkToObjectOrWebViewModel( val objectViews = filteredSearchResponse.toLinkToView( urlBuilder = urlBuilder, - objectTypes = storeOfObjectTypes.getAll() + objectTypes = storeOfObjectTypes.getAll(), + fieldParser = fieldParser ) val views = mutableListOf() if (clipboardUrl != null && userInput.value.isBlank()) { diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/linking/LinkToObjectViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/linking/LinkToObjectViewModel.kt index 59d9130ed9..73d9d27fa1 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/linking/LinkToObjectViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/linking/LinkToObjectViewModel.kt @@ -8,10 +8,11 @@ import com.anytypeio.anytype.core_models.ObjectWrapper import com.anytypeio.anytype.domain.base.Resultat import com.anytypeio.anytype.domain.block.interactor.sets.GetObjectTypes import com.anytypeio.anytype.domain.misc.UrlBuilder +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.search.SearchObjects import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate import com.anytypeio.anytype.presentation.navigation.DefaultObjectView -import com.anytypeio.anytype.presentation.objects.SupportedLayouts +import com.anytypeio.anytype.core_models.SupportedLayouts import com.anytypeio.anytype.presentation.search.ObjectSearchConstants import com.anytypeio.anytype.presentation.search.ObjectSearchViewModel import kotlinx.coroutines.flow.MutableSharedFlow @@ -23,14 +24,16 @@ class LinkToObjectViewModel( searchObjects: SearchObjects, getObjectTypes: GetObjectTypes, analytics: Analytics, - analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate + analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate, + fieldParser: FieldParser ) : ObjectSearchViewModel( vmParams = vmParams, urlBuilder = urlBuilder, getObjectTypes = getObjectTypes, searchObjects = searchObjects, analytics = analytics, - analyticSpaceHelperDelegate = analyticSpaceHelperDelegate + analyticSpaceHelperDelegate = analyticSpaceHelperDelegate, + fieldParser = fieldParser ) { val commands = MutableSharedFlow(replay = 0) diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/linking/LinkToObjectViewModelFactory.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/linking/LinkToObjectViewModelFactory.kt index 36b7dc6f42..e7c87c7ad8 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/linking/LinkToObjectViewModelFactory.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/linking/LinkToObjectViewModelFactory.kt @@ -7,6 +7,7 @@ import com.anytypeio.anytype.core_utils.tools.UrlValidator import com.anytypeio.anytype.domain.block.interactor.sets.GetObjectTypes import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.search.SearchObjects import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate import com.anytypeio.anytype.presentation.editor.Editor @@ -19,7 +20,8 @@ class LinkToObjectViewModelFactory( private val getObjectTypes: GetObjectTypes, private val searchObjects: SearchObjects, private val analytics: Analytics, - private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate + private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate, + private val fieldParser: FieldParser ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") @@ -30,7 +32,8 @@ class LinkToObjectViewModelFactory( getObjectTypes = getObjectTypes, searchObjects = searchObjects, analytics = analytics, - analyticSpaceHelperDelegate = analyticSpaceHelperDelegate + analyticSpaceHelperDelegate = analyticSpaceHelperDelegate, + fieldParser = fieldParser ) as T } } @@ -43,7 +46,8 @@ class LinkToObjectOrWebViewModelFactory( private val analytics: Analytics, private val stores: Editor.Storage, private val urlValidator: UrlValidator, - private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate + private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate, + private val fieldParser: FieldParser ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") @@ -56,7 +60,8 @@ class LinkToObjectOrWebViewModelFactory( analytics = analytics, stores = stores, urlValidator = urlValidator, - analyticSpaceHelperDelegate = analyticSpaceHelperDelegate + analyticSpaceHelperDelegate = analyticSpaceHelperDelegate, + fieldParser = fieldParser ) as T } } @@ -67,7 +72,8 @@ class BackLinkOrAddToObjectViewModelFactory @Inject constructor( private val getObjectTypes: GetObjectTypes, private val searchObjects: SearchObjects, private val analytics: Analytics, - private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate + private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate, + private val fieldParser: FieldParser ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") @@ -78,7 +84,8 @@ class BackLinkOrAddToObjectViewModelFactory @Inject constructor( getObjectTypes = getObjectTypes, searchObjects = searchObjects, analytics = analytics, - analyticSpaceHelperDelegate = analyticSpaceHelperDelegate + analyticSpaceHelperDelegate = analyticSpaceHelperDelegate, + fieldParser = fieldParser ) as T } } \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/mapper/MapperExtension.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/mapper/MapperExtension.kt index 82459d89e7..a71848b517 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/mapper/MapperExtension.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/mapper/MapperExtension.kt @@ -13,6 +13,7 @@ import com.anytypeio.anytype.core_models.ThemeColor import com.anytypeio.anytype.domain.config.DebugSettings import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.objects.ObjectStore +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.presentation.editor.editor.Markup import com.anytypeio.anytype.presentation.editor.editor.mention.createMentionMarkup import com.anytypeio.anytype.presentation.editor.editor.model.Alignment @@ -571,6 +572,7 @@ suspend fun List.toGridRecordRows( columns: List, builder: UrlBuilder, store: ObjectStore, + fieldParser: FieldParser ): List { val rows = mutableListOf() forEach { id -> @@ -580,7 +582,8 @@ suspend fun List.toGridRecordRows( showIcon = showIcon, obj = record, store = store, - builder = builder + builder = builder, + fieldParser = fieldParser ) rows.add(row) } else { diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/mapper/ObjectIconMapper.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/mapper/ObjectIconMapper.kt index 448c97c9b5..ec4792e5de 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/mapper/ObjectIconMapper.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/mapper/ObjectIconMapper.kt @@ -5,7 +5,7 @@ import com.anytypeio.anytype.core_models.ObjectWrapper import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.presentation.objects.ObjectIcon import com.anytypeio.anytype.presentation.objects.ObjectIcon.Basic -import com.anytypeio.anytype.presentation.objects.SupportedLayouts +import com.anytypeio.anytype.core_models.SupportedLayouts fun ObjectWrapper.Basic.objectIcon(builder: UrlBuilder): ObjectIcon { @@ -33,7 +33,7 @@ fun ObjectWrapper.Basic.objectIcon(builder: UrlBuilder): ObjectIcon { } if (layout == ObjectType.Layout.TODO) { - return taskIcon(isChecked = done ?: false) + return taskIcon(isChecked = done == true) } return layout.emptyType() @@ -48,6 +48,7 @@ fun ObjectType.Layout?.emptyType(): ObjectIcon.Empty { ObjectType.Layout.OBJECT_TYPE -> ObjectIcon.Empty.ObjectType ObjectType.Layout.BOOKMARK -> ObjectIcon.Empty.Bookmark ObjectType.Layout.CHAT, ObjectType.Layout.CHAT_DERIVED -> ObjectIcon.Empty.Discussion + ObjectType.Layout.DATE -> ObjectIcon.Empty.Date else -> ObjectIcon.Empty.Page } } @@ -83,6 +84,8 @@ fun ObjectType.Layout.icon( builder = builder ) + ObjectType.Layout.DATE -> emptyType() + else -> null } } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/moving/MoveToViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/moving/MoveToViewModel.kt index b65d899527..2fc778ad6d 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/moving/MoveToViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/moving/MoveToViewModel.kt @@ -13,11 +13,12 @@ import com.anytypeio.anytype.domain.base.getOrDefault import com.anytypeio.anytype.domain.base.getOrThrow import com.anytypeio.anytype.domain.block.interactor.sets.GetObjectTypes import com.anytypeio.anytype.domain.misc.UrlBuilder +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.search.SearchObjects import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate import com.anytypeio.anytype.presentation.extension.sendAnalyticsSearchResultEvent import com.anytypeio.anytype.presentation.navigation.DefaultObjectView -import com.anytypeio.anytype.presentation.objects.SupportedLayouts +import com.anytypeio.anytype.core_models.SupportedLayouts import com.anytypeio.anytype.presentation.objects.toViews import com.anytypeio.anytype.presentation.search.ObjectSearchConstants import kotlinx.coroutines.flow.MutableStateFlow @@ -39,7 +40,8 @@ class MoveToViewModel( private val searchObjects: SearchObjects, private val getObjectTypes: GetObjectTypes, private val analytics: Analytics, - private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate + private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate, + private val fieldParser: FieldParser ) : ViewModel(), TextInputDialogBottomBehaviorApplier.OnDialogCancelListener, AnalyticSpaceHelperDelegate by analyticSpaceHelperDelegate { @@ -64,7 +66,8 @@ class MoveToViewModel( Resultat.success( listOfObjects.getOrThrow().toViews( urlBuilder = urlBuilder, - objectTypes = listOfTypes.getOrThrow() + objectTypes = listOfTypes.getOrThrow(), + fieldParser = fieldParser ) ) } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/moving/MoveToViewModelFactory.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/moving/MoveToViewModelFactory.kt index ef2116f929..bedd008059 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/moving/MoveToViewModelFactory.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/moving/MoveToViewModelFactory.kt @@ -5,6 +5,7 @@ import androidx.lifecycle.ViewModelProvider import com.anytypeio.anytype.analytics.base.Analytics import com.anytypeio.anytype.domain.block.interactor.sets.GetObjectTypes import com.anytypeio.anytype.domain.misc.UrlBuilder +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.search.SearchObjects import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate @@ -14,7 +15,8 @@ class MoveToViewModelFactory( private val getObjectTypes: GetObjectTypes, private val searchObjects: SearchObjects, private val analytics: Analytics, - private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate + private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate, + private val fieldParser: FieldParser ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") @@ -25,7 +27,8 @@ class MoveToViewModelFactory( getObjectTypes = getObjectTypes, searchObjects = searchObjects, analytics = analytics, - analyticSpaceHelperDelegate = analyticSpaceHelperDelegate + analyticSpaceHelperDelegate = analyticSpaceHelperDelegate, + fieldParser = fieldParser ) as T } } \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/navigation/AppNavigation.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/navigation/AppNavigation.kt index f62610b87d..6a89e7fc7d 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/navigation/AppNavigation.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/navigation/AppNavigation.kt @@ -31,6 +31,10 @@ interface AppNavigation { templateTypeKey: Key, space: Id ) + fun openDateObject( + objectId: Id, + space: Id + ) fun launchDocument(target: String, space: Id) fun launchCollections(subscription: Subscription, space: Id) @@ -88,6 +92,11 @@ interface AppNavigation { val isPopUpToDashboard: Boolean = false ) : Command() + data class OpenDateObject( + val objectId: Id, + val space: Id + ) : Command() + data class LaunchObjectSet(val target: Id, val space: Id) : Command() object OpenUpdateAppScreen : Command() diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/navigation/ObjectView.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/navigation/ObjectView.kt index 903e79f878..3e688242ce 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/navigation/ObjectView.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/navigation/ObjectView.kt @@ -8,6 +8,10 @@ interface DefaultSearchItem data object NewObject: DefaultSearchItem +data object SectionDates: DefaultSearchItem +data object SelectDateItem: DefaultSearchItem +data object SectionObjects: DefaultSearchItem + data class DefaultObjectView( val id: Id, val space: Id, diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/ObjectIcon.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/ObjectIcon.kt index bee2f209bd..7ca9f10552 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/ObjectIcon.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/ObjectIcon.kt @@ -15,6 +15,7 @@ sealed class ObjectIcon { data object Bookmark : Empty() data object Discussion : Empty() data object ObjectType : Empty() + data object Date : Empty() } sealed class Basic : ObjectIcon() { diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/ObjectTypeChangeViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/ObjectTypeChangeViewModel.kt index 9fe1501745..5a0b418350 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/ObjectTypeChangeViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/ObjectTypeChangeViewModel.kt @@ -10,6 +10,7 @@ import com.anytypeio.anytype.core_models.MarketplaceObjectTypeIds.MARKETPLACE_OB 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.SupportedLayouts import com.anytypeio.anytype.core_models.ext.mapToObjectWrapperType import com.anytypeio.anytype.core_models.primitives.SpaceId import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/ObjectTypeExtensions.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/ObjectTypeExtensions.kt index 1a012c1bed..2094af382d 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/ObjectTypeExtensions.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/ObjectTypeExtensions.kt @@ -14,9 +14,9 @@ import com.anytypeio.anytype.core_models.primitives.TypeKey import com.anytypeio.anytype.core_models.restrictions.DataViewRestriction import com.anytypeio.anytype.domain.page.CreateObject import com.anytypeio.anytype.presentation.mapper.toObjectTypeView -import com.anytypeio.anytype.presentation.objects.SupportedLayouts.editorLayouts -import com.anytypeio.anytype.presentation.objects.SupportedLayouts.fileLayouts -import com.anytypeio.anytype.presentation.objects.SupportedLayouts.systemLayouts +import com.anytypeio.anytype.core_models.SupportedLayouts.editorLayouts +import com.anytypeio.anytype.core_models.SupportedLayouts.fileLayouts +import com.anytypeio.anytype.core_models.SupportedLayouts.systemLayouts import com.anytypeio.anytype.presentation.sets.state.ObjectState /** diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/ObjectWrapperExtensions.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/ObjectWrapperExtensions.kt index cc089c7d5e..85153e7f84 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/ObjectWrapperExtensions.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/ObjectWrapperExtensions.kt @@ -1,18 +1,16 @@ package com.anytypeio.anytype.presentation.objects -import com.anytypeio.anytype.core_models.Block import com.anytypeio.anytype.core_models.DVViewerRelation import com.anytypeio.anytype.core_models.Id +import com.anytypeio.anytype.core_models.MAX_SNIPPET_SIZE import com.anytypeio.anytype.core_models.ObjectType 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.ext.DateParser -import com.anytypeio.anytype.core_utils.const.DateConst import com.anytypeio.anytype.core_utils.ext.typeOf import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.objects.ObjectStore -import com.anytypeio.anytype.presentation.extension.MAX_SNIPPET_SIZE +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.presentation.number.NumberParser import com.anytypeio.anytype.presentation.relations.model.DefaultObjectRelationValueView import com.anytypeio.anytype.presentation.sets.model.FileView @@ -26,7 +24,8 @@ suspend fun ObjectWrapper.Basic.values( relations: List, settings: List, urlBuilder: UrlBuilder, - storeOfObjects: ObjectStore + storeOfObjects: ObjectStore, + fieldParser: FieldParser ): List { val values = mutableListOf() relations.forEach { relation -> @@ -121,24 +120,8 @@ suspend fun ObjectWrapper.Basic.values( } RelationFormat.DATE -> { val setting = settings.find { it.key == relation.key } - val format: String - val dateFormat: String - if (setting != null) { - dateFormat = setting.dateFormat?.format ?: DateConst.DEFAULT_DATE_FORMAT - format = if (setting.isDateIncludeTime == true) { - if (setting.timeFormat == Block.Content.DataView.TimeFormat.H12) { - dateFormat + DateConst.DATE_FORMAT_SPACE + DateConst.TIME_H12 - } else { - dateFormat + DateConst.DATE_FORMAT_SPACE + DateConst.TIME_H24 - } - } else { - dateFormat - } - } else { - format = DateConst.DEFAULT_DATE_FORMAT - } - val time = map.getOrDefault(key = relation.key, null) - val value = if (time == null) { + val fieldDate = fieldParser.toDate(any = map.getOrDefault(relation.key, null)) + val value = if (fieldDate == null) { DefaultObjectRelationValueView.Empty( objectId = id, relationKey = relation.key, @@ -147,8 +130,9 @@ suspend fun ObjectWrapper.Basic.values( DefaultObjectRelationValueView.Date( objectId = id, relationKey = relation.key, - timeInMillis = DateParser.parseInMillis(time), - dateFormat = format + timeInMillis = fieldDate.timestamp.inMillis, + isTimeIncluded = setting?.isDateIncludeTime == true, + relativeDate = fieldDate.relativeDate ) } values.add(value) @@ -215,7 +199,8 @@ suspend fun ObjectWrapper.Basic.values( val objects = objects( relation = relation.key, urlBuilder = urlBuilder, - storeOfObjects = storeOfObjects + storeOfObjects = storeOfObjects, + fieldParser = fieldParser ) val value = if (objects.isEmpty()) { DefaultObjectRelationValueView.Empty( @@ -244,13 +229,15 @@ suspend fun ObjectWrapper.Basic.relationsFilteredByHiddenAndDescription( relations: List, settings: List, urlBuilder: UrlBuilder, - storeOfObjects: ObjectStore + storeOfObjects: ObjectStore, + fieldParser: FieldParser ): List { return values( relations = relations.filter { it.isHidden != true && it.key != Relations.DESCRIPTION }, settings = settings, urlBuilder = urlBuilder, - storeOfObjects = storeOfObjects + storeOfObjects = storeOfObjects, + fieldParser = fieldParser ) } @@ -340,7 +327,8 @@ suspend fun ObjectWrapper.Basic.files( suspend fun ObjectWrapper.Basic.objects( relation: Id, urlBuilder: UrlBuilder, - storeOfObjects: ObjectStore + storeOfObjects: ObjectStore, + fieldParser: FieldParser ) : List { val result = mutableListOf() @@ -352,16 +340,12 @@ suspend fun ObjectWrapper.Basic.objects( ids.forEach { id -> val wrapper = storeOfObjects.get(id) ?: return@forEach if (wrapper.isValid) { - result.add(wrapper.toObjectView(urlBuilder)) + result.add(wrapper.toObjectView(urlBuilder, fieldParser)) } } return result } -fun ObjectWrapper.File.getProperName(): String { - return "${name.orEmpty()}.$fileExt" -} - fun ObjectWrapper.Basic.getDescriptionOrSnippet(): String? { return when (layout) { ObjectType.Layout.NOTE -> description diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/ObjectWrapperMapper.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/ObjectWrapperMapper.kt index 910b22338a..e29162f7c1 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/ObjectWrapperMapper.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/ObjectWrapperMapper.kt @@ -8,7 +8,7 @@ import com.anytypeio.anytype.core_models.ObjectWrapper import com.anytypeio.anytype.core_models.ext.DateParser import com.anytypeio.anytype.core_utils.ext.readableFileSize import com.anytypeio.anytype.domain.misc.UrlBuilder -import com.anytypeio.anytype.presentation.extension.getProperObjectName +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.presentation.linking.LinkToItemView import com.anytypeio.anytype.presentation.mapper.objectIcon import com.anytypeio.anytype.presentation.navigation.DefaultObjectView @@ -17,50 +17,46 @@ import com.anytypeio.anytype.presentation.widgets.collection.CollectionView fun List.toViews( urlBuilder: UrlBuilder, - objectTypes: List + objectTypes: List, + fieldParser: FieldParser ): List = map { obj -> - obj.toView(urlBuilder, objectTypes) + obj.toView(urlBuilder, objectTypes, fieldParser = fieldParser) } fun ObjectWrapper.Basic.toView( urlBuilder: UrlBuilder, - objectTypes: List + objectTypes: List, + fieldParser: FieldParser ): DefaultObjectView { val obj = this val typeUrl = obj.getProperType() - val isProfile = typeUrl == MarketplaceObjectTypeIds.PROFILE val layout = obj.getProperLayout() return DefaultObjectView( id = obj.id, - name = obj.getProperName(), + name = fieldParser.getObjectName(obj), description = obj.description, type = typeUrl, - typeName = objectTypes.firstOrNull { type -> - if (isProfile) { - type.uniqueKey == ObjectTypeUniqueKeys.PROFILE - } else { - type.id == typeUrl - } - }?.name, + typeName = fieldParser.getObjectTypeName(obj, objectTypes), layout = layout, icon = obj.objectIcon(urlBuilder), lastModifiedDate = DateParser.parseInMillis(obj.lastModifiedDate) ?: 0L, lastOpenedDate = DateParser.parseInMillis(obj.lastOpenedDate) ?: 0L, - isFavorite = obj.isFavorite ?: false, + isFavorite = obj.isFavorite == true, space = requireNotNull(obj.spaceId) ) } fun List.toLinkToView( urlBuilder: UrlBuilder, - objectTypes: List + objectTypes: List, + fieldParser: FieldParser, ): List = this.mapIndexed { index, obj -> val typeUrl = obj.getProperType() val layout = obj.getProperLayout() LinkToItemView.Object( id = obj.id, - title = obj.getProperName(), + title = fieldParser.getObjectName(obj), subtitle = getProperTypeName(id = typeUrl, types = objectTypes), type = typeUrl, layout = layout, @@ -71,13 +67,14 @@ fun List.toLinkToView( fun ObjectWrapper.Basic.toLinkToObjectView( urlBuilder: UrlBuilder, - objectTypes: List + objectTypes: List, + fieldParser: FieldParser, ): LinkToItemView.LinkedTo.Object { val typeUrl = this.getProperType() val layout = this.getProperLayout() return LinkToItemView.LinkedTo.Object( id = this.id, - title = this.getProperName(), + title = fieldParser.getObjectName(this), subtitle = getProperTypeName(id = typeUrl, types = objectTypes), type = typeUrl, layout = layout, @@ -88,7 +85,8 @@ fun ObjectWrapper.Basic.toLinkToObjectView( fun List.toCreateFilterObjectView( ids: List<*>? = null, urlBuilder: UrlBuilder, - objectTypes: List + objectTypes: List, + fieldParser: FieldParser ): List = this.map { obj -> CreateFilterView.Object( @@ -97,7 +95,7 @@ fun List.toCreateFilterObjectView( id = obj.getProperType(), types = objectTypes ), - name = obj.getProperName(), + name = fieldParser.getObjectName(obj), icon = obj.objectIcon(urlBuilder), isSelected = ids?.contains(obj.id) ?: false ) @@ -109,15 +107,11 @@ fun ObjectWrapper.Basic.getProperType() = type.firstOrNull() private fun getProperTypeName(id: Id?, types: List) = types.find { it.id == id }?.name.orEmpty() -fun ObjectWrapper.Basic.getProperName(): String { - return getProperObjectName().orEmpty() -} - -fun ObjectWrapper.Basic.mapFileObjectToView(): CollectionView.ObjectView { - val fileIcon = getFileObjectIcon() +fun ObjectWrapper.Basic.mapFileObjectToView(fieldParser: FieldParser): CollectionView.ObjectView { + val fileIcon = getFileObjectIcon(fieldParser) val defaultObjectView = DefaultObjectView( id = id, - name = getProperName(), + name = fieldParser.getObjectName(this), description = sizeInBytes?.toLong()?.readableFileSize().orEmpty(), layout = layout, icon = fileIcon, @@ -126,12 +120,12 @@ fun ObjectWrapper.Basic.mapFileObjectToView(): CollectionView.ObjectView { return CollectionView.ObjectView(defaultObjectView) } -private fun ObjectWrapper.Basic.getFileObjectIcon(): ObjectIcon { +private fun ObjectWrapper.Basic.getFileObjectIcon(fieldParser: FieldParser): ObjectIcon { return when (layout) { ObjectType.Layout.FILE, ObjectType.Layout.IMAGE -> ObjectIcon.File( mime = fileMimeType, - fileName = getProperName(), + fileName = fieldParser.getObjectName(this), extensions = fileExt ) diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/SelectObjectTypeViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/SelectObjectTypeViewModel.kt index 41e3959aed..2f4a9580d2 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/SelectObjectTypeViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/SelectObjectTypeViewModel.kt @@ -9,11 +9,11 @@ import com.anytypeio.anytype.core_models.EMPTY_QUERY import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_models.Key import com.anytypeio.anytype.core_models.ObjectOrigin -import com.anytypeio.anytype.core_models.ObjectTypeIds import com.anytypeio.anytype.core_models.ObjectTypeIds.DEFAULT_OBJECT_TYPE import com.anytypeio.anytype.core_models.ObjectTypeUniqueKeys import com.anytypeio.anytype.core_models.ObjectWrapper import com.anytypeio.anytype.core_models.Relations +import com.anytypeio.anytype.core_models.SupportedLayouts import com.anytypeio.anytype.core_models.Url import com.anytypeio.anytype.core_models.ext.mapToObjectWrapperType import com.anytypeio.anytype.core_models.primitives.SpaceId diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/menu/ObjectMenuOptionsProviderImpl.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/menu/ObjectMenuOptionsProviderImpl.kt index 1d2252bda7..7208f66084 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/menu/ObjectMenuOptionsProviderImpl.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/menu/ObjectMenuOptionsProviderImpl.kt @@ -6,7 +6,7 @@ import com.anytypeio.anytype.core_models.ObjectType import com.anytypeio.anytype.core_models.ObjectWrapper import com.anytypeio.anytype.core_models.restrictions.ObjectRestriction import com.anytypeio.anytype.core_utils.tools.FeatureToggles -import com.anytypeio.anytype.presentation.objects.SupportedLayouts +import com.anytypeio.anytype.core_models.SupportedLayouts import com.anytypeio.anytype.presentation.objects.menu.ObjectMenuOptionsProvider.Options import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/menu/ObjectMenuViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/menu/ObjectMenuViewModel.kt index a8b34e62ec..04b94f8b4e 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/menu/ObjectMenuViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/menu/ObjectMenuViewModel.kt @@ -24,6 +24,7 @@ import com.anytypeio.anytype.domain.`object`.DuplicateObject import com.anytypeio.anytype.domain.`object`.SetObjectDetails import com.anytypeio.anytype.domain.objects.SetObjectListIsArchived import com.anytypeio.anytype.domain.page.AddBackLinkToObject +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.templates.CreateTemplateFromObject import com.anytypeio.anytype.domain.widgets.CreateWidget import com.anytypeio.anytype.domain.workspace.SpaceManager @@ -35,8 +36,8 @@ import com.anytypeio.anytype.presentation.editor.Editor import com.anytypeio.anytype.presentation.extension.sendAnalyticsCreateTemplateEvent import com.anytypeio.anytype.presentation.extension.sendAnalyticsDefaultTemplateEvent import com.anytypeio.anytype.presentation.objects.ObjectAction -import com.anytypeio.anytype.presentation.objects.SupportedLayouts.fileLayouts -import com.anytypeio.anytype.presentation.objects.SupportedLayouts.systemLayouts +import com.anytypeio.anytype.core_models.SupportedLayouts.fileLayouts +import com.anytypeio.anytype.core_models.SupportedLayouts.systemLayouts import com.anytypeio.anytype.presentation.objects.isTemplatesAllowed import com.anytypeio.anytype.presentation.util.Dispatcher import com.anytypeio.anytype.presentation.util.downloader.DebugGoroutinesShareDownloader @@ -67,7 +68,8 @@ class ObjectMenuViewModel( private val deepLinkResolver: DeepLinkResolver, private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate, private val setObjectListIsFavorite: SetObjectListIsFavorite, - private val setObjectIsArchived: SetObjectListIsArchived + private val setObjectIsArchived: SetObjectListIsArchived, + private val fieldParser: FieldParser ) : ObjectMenuViewModelBase( setObjectIsArchived = setObjectIsArchived, addBackLinkToObject = addBackLinkToObject, @@ -83,7 +85,8 @@ class ObjectMenuViewModel( spaceManager = spaceManager, analyticSpaceHelperDelegate = analyticSpaceHelperDelegate, payloadDelegator = payloadDelegator, - setObjectListIsFavorite = setObjectListIsFavorite + setObjectListIsFavorite = setObjectListIsFavorite, + fieldParser = fieldParser ) { init { @@ -475,7 +478,8 @@ class ObjectMenuViewModel( private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate, private val payloadDelegator: PayloadDelegator, private val setObjectListIsFavorite: SetObjectListIsFavorite, - private val setObjectIsArchived: SetObjectListIsArchived + private val setObjectIsArchived: SetObjectListIsArchived, + private val fieldParser: FieldParser ) : ViewModelProvider.Factory { override fun create(modelClass: Class): T { return ObjectMenuViewModel( @@ -499,7 +503,8 @@ class ObjectMenuViewModel( deepLinkResolver = deepLinkResolver, analyticSpaceHelperDelegate = analyticSpaceHelperDelegate, payloadDelegator = payloadDelegator, - setObjectListIsFavorite = setObjectListIsFavorite + setObjectListIsFavorite = setObjectListIsFavorite, + fieldParser = fieldParser ) as T } } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/menu/ObjectMenuViewModelBase.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/menu/ObjectMenuViewModelBase.kt index 5f9b18302f..3d3a99236a 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/menu/ObjectMenuViewModelBase.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/menu/ObjectMenuViewModelBase.kt @@ -19,6 +19,7 @@ import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.`object`.DuplicateObject import com.anytypeio.anytype.domain.objects.SetObjectListIsArchived import com.anytypeio.anytype.domain.page.AddBackLinkToObject +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.widgets.CreateWidget import com.anytypeio.anytype.domain.workspace.SpaceManager import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate @@ -35,7 +36,6 @@ import com.anytypeio.anytype.presentation.extension.sendAnalyticsRemoveFromFavor import com.anytypeio.anytype.presentation.mapper.objectIcon import com.anytypeio.anytype.presentation.objects.ObjectAction import com.anytypeio.anytype.presentation.objects.ObjectIcon -import com.anytypeio.anytype.presentation.objects.getProperName import com.anytypeio.anytype.presentation.util.Dispatcher import com.anytypeio.anytype.presentation.util.downloader.DebugGoroutinesShareDownloader import com.anytypeio.anytype.presentation.util.downloader.MiddlewareShareDownloader @@ -61,7 +61,8 @@ abstract class ObjectMenuViewModelBase( private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate, private val payloadDelegator: PayloadDelegator, private val setObjectListIsFavorite: SetObjectListIsFavorite, - private val setObjectIsArchived: SetObjectListIsArchived + private val setObjectIsArchived: SetObjectListIsArchived, + private val fieldParser: FieldParser ) : BaseViewModel(), AnalyticSpaceHelperDelegate by analyticSpaceHelperDelegate { protected val jobs = mutableListOf() @@ -301,7 +302,7 @@ abstract class ObjectMenuViewModelBase( Command.OpenSnackbar( id = addTo, currentObjectName = fromName, - targetObjectName = obj.getProperName(), + targetObjectName = fieldParser.getObjectName(obj), icon = obj.objectIcon(urlBuilder), space = space ) diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/menu/ObjectSetMenuViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/menu/ObjectSetMenuViewModel.kt index 58c339a917..b8833dc4f0 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/menu/ObjectSetMenuViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/menu/ObjectSetMenuViewModel.kt @@ -16,6 +16,7 @@ import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.`object`.DuplicateObject import com.anytypeio.anytype.domain.objects.SetObjectListIsArchived import com.anytypeio.anytype.domain.page.AddBackLinkToObject +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.widgets.CreateWidget import com.anytypeio.anytype.domain.workspace.SpaceManager import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate @@ -49,7 +50,8 @@ class ObjectSetMenuViewModel( private val debugGoroutinesShareDownloader: DebugGoroutinesShareDownloader, private val deepLinkResolver: DeepLinkResolver, private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate, - setObjectListIsFavorite: SetObjectListIsFavorite + setObjectListIsFavorite: SetObjectListIsFavorite, + fieldParser: FieldParser ) : ObjectMenuViewModelBase( setObjectIsArchived = setObjectIsArchived, addBackLinkToObject = addBackLinkToObject, @@ -65,7 +67,8 @@ class ObjectSetMenuViewModel( spaceManager = spaceManager, analyticSpaceHelperDelegate = analyticSpaceHelperDelegate, payloadDelegator = payloadDelegator, - setObjectListIsFavorite = setObjectListIsFavorite + setObjectListIsFavorite = setObjectListIsFavorite, + fieldParser = fieldParser ) { init { @@ -90,7 +93,8 @@ class ObjectSetMenuViewModel( private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate, private val payloadDelegator: PayloadDelegator, private val setObjectListIsFavorite: SetObjectListIsFavorite, - private val setObjectListIsArchived: SetObjectListIsArchived + private val setObjectListIsArchived: SetObjectListIsArchived, + private val fieldParser: FieldParser ) : ViewModelProvider.Factory { override fun create(modelClass: Class): T { return ObjectSetMenuViewModel( @@ -110,7 +114,8 @@ class ObjectSetMenuViewModel( deepLinkResolver = deepLinkResolver, analyticSpaceHelperDelegate = analyticSpaceHelperDelegate, payloadDelegator = payloadDelegator, - setObjectListIsFavorite = setObjectListIsFavorite + setObjectListIsFavorite = setObjectListIsFavorite, + fieldParser = fieldParser ) as T } } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/ObjectRelationListViewModelFactory.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/ObjectRelationListViewModelFactory.kt index fc19718362..04d0ea50b1 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/ObjectRelationListViewModelFactory.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/ObjectRelationListViewModelFactory.kt @@ -7,6 +7,7 @@ import com.anytypeio.anytype.core_models.Payload import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.`object`.UpdateDetail import com.anytypeio.anytype.domain.objects.StoreOfRelations +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.relations.AddRelationToObject import com.anytypeio.anytype.domain.relations.AddToFeaturedRelations import com.anytypeio.anytype.domain.relations.DeleteRelationFromObject @@ -30,7 +31,8 @@ class ObjectRelationListViewModelFactory( private val storeOfRelations: StoreOfRelations, private val addRelationToObject: AddRelationToObject, private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate, - private val spaceManager: SpaceManager + private val spaceManager: SpaceManager, + private val fieldParser: FieldParser ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") @@ -48,7 +50,8 @@ class ObjectRelationListViewModelFactory( storeOfRelations = storeOfRelations, addRelationToObject = addRelationToObject, analyticSpaceHelperDelegate = analyticSpaceHelperDelegate, - spaceManager = spaceManager + spaceManager = spaceManager, + fieldParser = fieldParser ) as T } } \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/ObjectRelationView.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/ObjectRelationView.kt index 6786b4f4f1..b29a2cd4d1 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/ObjectRelationView.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/ObjectRelationView.kt @@ -3,6 +3,7 @@ package com.anytypeio.anytype.presentation.relations import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_models.Key import com.anytypeio.anytype.core_models.Relation +import com.anytypeio.anytype.core_models.RelativeDate import com.anytypeio.anytype.core_utils.diff.DefaultObjectDiffIdentifier import com.anytypeio.anytype.presentation.sets.model.FileView import com.anytypeio.anytype.presentation.sets.model.ObjectView @@ -21,6 +22,18 @@ sealed class ObjectRelationView : DefaultObjectDiffIdentifier { override val identifier: String get() = id + data class Date( + override val id: Id, + override val key: Key, + override val name: String, + override val value: String? = null, + override val featured: Boolean = false, + override val system: Boolean, + override val readOnly: Boolean = false, + val relativeDate: RelativeDate?, + val isTimeIncluded: Boolean = false + ) : ObjectRelationView() + data class Default( override val id: Id, override val key: Key, diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/ObjectSetRenderMapper.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/ObjectSetRenderMapper.kt index 38f5cd561f..ee4c87b5f8 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/ObjectSetRenderMapper.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/ObjectSetRenderMapper.kt @@ -27,10 +27,11 @@ import com.anytypeio.anytype.core_utils.ext.TOMORROW import com.anytypeio.anytype.core_utils.ext.YESTERDAY import com.anytypeio.anytype.core_utils.ext.orNull import com.anytypeio.anytype.core_utils.ext.typeOf +import com.anytypeio.anytype.domain.misc.DateProvider import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.objects.ObjectStore import com.anytypeio.anytype.domain.objects.StoreOfRelations -import com.anytypeio.anytype.domain.search.DataViewState +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.presentation.editor.cover.CoverImageHashProvider import com.anytypeio.anytype.presentation.editor.editor.model.BlockView import com.anytypeio.anytype.presentation.extension.isValueRequired @@ -45,8 +46,6 @@ import com.anytypeio.anytype.presentation.mapper.toTextView import com.anytypeio.anytype.presentation.mapper.toView import com.anytypeio.anytype.presentation.mapper.toViewerColumns import com.anytypeio.anytype.presentation.number.NumberParser -import com.anytypeio.anytype.presentation.objects.ObjectIcon -import com.anytypeio.anytype.presentation.objects.getProperName import com.anytypeio.anytype.presentation.sets.buildGalleryViews import com.anytypeio.anytype.presentation.sets.buildListViews import com.anytypeio.anytype.presentation.sets.dataViewState @@ -83,7 +82,8 @@ suspend fun DVViewer.render( dataViewRelations: List, store: ObjectStore, objectOrderIds: List = emptyList(), - storeOfRelations: StoreOfRelations + storeOfRelations: StoreOfRelations, + fieldParser: FieldParser ): Viewer { return when (type) { DVViewerType.GRID -> { @@ -92,7 +92,8 @@ suspend fun DVViewer.render( objects = objects, builder = builder, store = store, - objectOrderIds = objectOrderIds + objectOrderIds = objectOrderIds, + fieldParser = fieldParser ) } DVViewerType.GALLERY -> { @@ -105,7 +106,8 @@ suspend fun DVViewer.render( urlBuilder = builder, objectStore = store, objectOrderIds = objectOrderIds, - storeOfRelations = storeOfRelations + storeOfRelations = storeOfRelations, + fieldParser = fieldParser ), title = name, largeCards = cardSize == DVViewerCardSize.LARGE @@ -124,7 +126,8 @@ suspend fun DVViewer.render( relations = visibleRelations, urlBuilder = builder, store = store, - objectOrderIds = objectOrderIds + objectOrderIds = objectOrderIds, + fieldParser = fieldParser ), title = name ) @@ -136,7 +139,8 @@ suspend fun DVViewer.render( objects = objects, builder = builder, store = store, - objectOrderIds = objectOrderIds + objectOrderIds = objectOrderIds, + fieldParser = fieldParser ) } else { Viewer.Unsupported( @@ -162,7 +166,8 @@ private suspend fun DVViewer.buildGridView( objects: List, builder: UrlBuilder, store: ObjectStore, - objectOrderIds: List + objectOrderIds: List, + fieldParser: FieldParser ): Viewer { val vmap = viewerRelations.associateBy { it.key } val visibleRelations = dataViewRelations.filter { relation -> @@ -179,7 +184,8 @@ private suspend fun DVViewer.buildGridView( showIcon = !hideIcon, columns = columns, builder = builder, - store = store + store = store, + fieldParser = fieldParser ) ) } @@ -281,17 +287,20 @@ fun List.toCreateFilterStatusView( fun ObjectWrapper.Relation.toCreateFilterDateView( quickOption: DVFilterQuickOption?, condition: DVFilterCondition, - value: Long + value: Long, + fieldParser: FieldParser ) = quickOptionOrderMap.getOrDefault(condition, quickOptionDefaultOrder) .map { val isSelected = quickOption.isSelected(it, value) + val fieldDate = fieldParser.toDate(any = value) CreateFilterView.Date( id = key, description = it.toName(), type = it, condition = condition, value = if (isSelected) value else CreateFilterView.Date.NO_VALUE, - isSelected = isSelected + isSelected = isSelected, + relativeDate = fieldDate?.relativeDate ) } @@ -466,7 +475,8 @@ suspend fun ObjectWrapper.Relation.toStatus( suspend fun ObjectWrapper.Relation.toObjects( value: Any?, store: ObjectStore, - urlBuilder: UrlBuilder + urlBuilder: UrlBuilder, + fieldParser: FieldParser ) : List { val ids = value.values() return buildList { @@ -478,7 +488,7 @@ suspend fun ObjectWrapper.Relation.toObjects( true -> ObjectView.Deleted(id) else -> ObjectView.Default( id = id, - name = wrapper.getProperName(), + name = fieldParser.getObjectName(wrapper), icon = wrapper.objectIcon(urlBuilder), types = type, isRelation = wrapper.layout == ObjectType.Layout.RELATION @@ -494,7 +504,8 @@ suspend fun DVFilter.toView( store: ObjectStore, relation: ObjectWrapper.Relation, isInEditMode: Boolean, - urlBuilder: UrlBuilder + urlBuilder: UrlBuilder, + fieldParser: FieldParser ): FilterView.Expression = when (relation.format) { Relation.Format.SHORT_TEXT -> { FilterView.Expression.TextShort( @@ -575,6 +586,7 @@ suspend fun DVFilter.toView( ) } Relation.Format.DATE -> { + val fieldDate = fieldParser.toDate(any = value) FilterView.Expression.Date( id = id, relation = this.relation, @@ -582,10 +594,11 @@ suspend fun DVFilter.toView( operator = operator.toView(), condition = condition.toDateView(), quickOption = quickOption, - filterValue = FilterValue.Date(DateParser.parse(value)), + filterValue = FilterValue.Date(fieldDate?.timestamp?.time), format = relation.format.toView(), isValueRequired = condition.isValueRequired(), - isInEditMode = isInEditMode + isInEditMode = isInEditMode, + relativeDate = fieldDate?.relativeDate ) } Relation.Format.STATUS -> { @@ -634,7 +647,8 @@ suspend fun DVFilter.toView( relation.toObjects( value = value, store = store, - urlBuilder = urlBuilder + urlBuilder = urlBuilder, + fieldParser = fieldParser ) ), format = relation.format.toView(), @@ -660,9 +674,9 @@ suspend fun DVFilter.toView( suspend fun ObjectWrapper.Relation.toFilterValue( value: Any?, - details: Map, urlBuilder: UrlBuilder, - store: ObjectStore + store: ObjectStore, + fieldParser: FieldParser ): FilterValue = when (this.format) { Relation.Format.SHORT_TEXT -> FilterValue.TextShort(toText(value)) Relation.Format.LONG_TEXT -> FilterValue.Text(toText(value)) @@ -687,7 +701,8 @@ suspend fun ObjectWrapper.Relation.toFilterValue( val obj = toObjects( value = value, store = store, - urlBuilder = urlBuilder + urlBuilder = urlBuilder, + fieldParser = fieldParser ) FilterValue.Object(obj) } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/RelationExtensions.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/RelationExtensions.kt index 4ce9750dae..a5d0803190 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/RelationExtensions.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/RelationExtensions.kt @@ -2,31 +2,30 @@ package com.anytypeio.anytype.presentation.relations import com.anytypeio.anytype.core_models.* import com.anytypeio.anytype.core_models.Relations.NUMBER_DEFAULT_VALUE -import com.anytypeio.anytype.core_models.ext.DateParser import com.anytypeio.anytype.core_utils.const.DateConst import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.objects.StoreOfRelations +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.presentation.extension.hasValue import com.anytypeio.anytype.presentation.number.NumberParser import com.anytypeio.anytype.presentation.sets.* import com.anytypeio.anytype.presentation.sets.model.ColumnView import com.anytypeio.anytype.presentation.sets.model.Viewer -import java.sql.Date -import java.text.SimpleDateFormat import java.util.* -import java.util.concurrent.TimeUnit fun List.views( details: Block.Details, values: Map, urlBuilder: UrlBuilder, - featured: List = emptyList() + featured: List = emptyList(), + fieldParser: FieldParser ): List = mapNotNull { relation -> relation.view( details = details.details, values = values, urlBuilder = urlBuilder, - isFeatured = featured.contains(relation.key) + isFeatured = featured.contains(relation.key), + fieldParser = fieldParser ) } @@ -36,7 +35,8 @@ fun ObjectWrapper.Relation.view( details: Map, values: Map, urlBuilder: UrlBuilder, - isFeatured: Boolean = false + isFeatured: Boolean = false, + fieldParser: FieldParser ): ObjectRelationView { val relation = this return when (relation.format) { @@ -44,7 +44,8 @@ fun ObjectWrapper.Relation.view( val objects = values.buildRelationValueObjectViews( relationKey = relation.key, details = details, - builder = urlBuilder + builder = urlBuilder, + fieldParser = fieldParser ) ObjectRelationView.Object( id = relation.id, @@ -72,27 +73,15 @@ fun ObjectWrapper.Relation.view( ) } RelationFormat.DATE -> { - //TODO In DataView Relation Date uses DateFormat and TimeFormat - // so SimpleDateFormat can be different from what we have here - // see {SetsExtension:buildGridRow()} - val format = SimpleDateFormat(DateConst.DEFAULT_DATE_FORMAT, Locale.getDefault()) - val value = values[relation.key] - - val timeInSec = DateParser.parse(value) - val formattedDate = if (timeInSec != null) { - format.format(Date(TimeUnit.SECONDS.toMillis(timeInSec))) - } else { - null - } - ObjectRelationView.Default( + val fieldDate = fieldParser.toDate(any = values[relation.key]) + ObjectRelationView.Date( id = relation.id, key = relation.key, name = relation.name.orEmpty(), - value = formattedDate, featured = isFeatured, readOnly = relation.isReadonlyValue, - format = relation.format, - system = relation.key.isSystemKey() + system = relation.key.isSystemKey(), + relativeDate = fieldDate?.relativeDate ) } RelationFormat.STATUS -> { @@ -295,4 +284,5 @@ fun ObjectRelationView.getRelationFormat(): RelationFormat = when (this) { is ObjectRelationView.ObjectType.Base -> RelationFormat.OBJECT is ObjectRelationView.ObjectType.Deleted -> RelationFormat.OBJECT is ObjectRelationView.Source -> RelationFormat.OBJECT + is ObjectRelationView.Date -> RelationFormat.DATE } \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/RelationListViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/RelationListViewModel.kt index a0f741b0b8..d5aed81d1f 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/RelationListViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/RelationListViewModel.kt @@ -19,6 +19,7 @@ import com.anytypeio.anytype.core_utils.diff.DefaultObjectDiffIdentifier import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.`object`.UpdateDetail import com.anytypeio.anytype.domain.objects.StoreOfRelations +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.relations.AddRelationToObject import com.anytypeio.anytype.domain.relations.AddToFeaturedRelations import com.anytypeio.anytype.domain.relations.DeleteRelationFromObject @@ -54,7 +55,8 @@ class RelationListViewModel( private val storeOfRelations: StoreOfRelations, private val addRelationToObject: AddRelationToObject, private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate, - private val spaceManager: SpaceManager + private val spaceManager: SpaceManager, + private val fieldParser: FieldParser ) : BaseViewModel(), AnalyticSpaceHelperDelegate by analyticSpaceHelperDelegate { val isEditMode = MutableStateFlow(false) @@ -137,7 +139,8 @@ class RelationListViewModel( details = details, values = objectDetails, urlBuilder = urlBuilder, - featured = objectWrapper.featuredRelations + featured = objectWrapper.featuredRelations, + fieldParser = fieldParser, ).map { view -> Model.Item( view = view, @@ -161,7 +164,8 @@ class RelationListViewModel( context = ctx, details = details, values = objectDetails, - urlBuilder = urlBuilder + urlBuilder = urlBuilder, + fieldParser = fieldParser ).map { view -> Model.Item( view = view, @@ -490,7 +494,8 @@ class RelationListViewModel( views.value = relations.views( details = details, values = values, - urlBuilder = urlBuilder + urlBuilder = urlBuilder, + fieldParser = fieldParser ).map { Model.Item(it) } } } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/RelationObjectExtensions.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/RelationObjectExtensions.kt index 64b7b1d00b..8e4f8ee56c 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/RelationObjectExtensions.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/RelationObjectExtensions.kt @@ -3,39 +3,34 @@ package com.anytypeio.anytype.presentation.relations import com.anytypeio.anytype.core_models.Block 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.RelationFormat import com.anytypeio.anytype.core_models.Relations -import com.anytypeio.anytype.core_models.ext.DateParser import com.anytypeio.anytype.core_models.ext.mapToObjectWrapperType -import com.anytypeio.anytype.core_models.getSingleValue -import com.anytypeio.anytype.core_utils.const.DateConst import com.anytypeio.anytype.core_utils.ext.typeOf import com.anytypeio.anytype.domain.misc.UrlBuilder +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.presentation.number.NumberParser import com.anytypeio.anytype.presentation.sets.buildFileViews import com.anytypeio.anytype.presentation.sets.buildRelationValueObjectViews import com.anytypeio.anytype.presentation.sets.model.StatusView import com.anytypeio.anytype.presentation.sets.model.TagView -import java.sql.Date -import java.text.SimpleDateFormat -import java.util.* -import java.util.concurrent.TimeUnit fun List.views( context: Id, details: Block.Details, values: Map, urlBuilder: UrlBuilder, - featured: List = emptyList() + featured: List = emptyList(), + fieldParser: FieldParser ): List = mapNotNull { relation -> relation.view( context = context, details = details, values = values, urlBuilder = urlBuilder, - isFeatured = featured.contains(relation.key) + isFeatured = featured.contains(relation.key), + fieldParser = fieldParser ) } @@ -44,7 +39,8 @@ fun ObjectWrapper.Relation.view( details: Block.Details, values: Map, urlBuilder: UrlBuilder, - isFeatured: Boolean = false + isFeatured: Boolean = false, + fieldParser: FieldParser ): ObjectRelationView? { val relation = this val relationFormat = relation.relationFormat @@ -56,7 +52,8 @@ fun ObjectWrapper.Relation.view( val objects = values.buildRelationValueObjectViews( relationKey = relation.key, details = details.details, - builder = urlBuilder + builder = urlBuilder, + fieldParser = fieldParser ) ObjectRelationView.Object( id = relation.id, @@ -84,27 +81,15 @@ fun ObjectWrapper.Relation.view( ) } RelationFormat.DATE -> { - //TODO In DataView Relation Date uses DateFormat and TimeFormat - // so SimpleDateFormat can be different from what we have here - // see {SetsExtension:buildGridRow()} - val format = SimpleDateFormat(DateConst.DEFAULT_DATE_FORMAT, Locale.getDefault()) - val value = values[relation.key] - - val timeInSec = DateParser.parse(value) - val formattedDate = if (timeInSec != null) { - format.format(Date(TimeUnit.SECONDS.toMillis(timeInSec))) - } else { - null - } - ObjectRelationView.Default( + val fieldDate = fieldParser.toDate(any = values[relation.key]) + ObjectRelationView.Date( id = relation.id, key = relation.key, name = relation.name.orEmpty(), - value = formattedDate, featured = isFeatured, - format = relationFormat, readOnly = relation.isReadonlyValue, - system = relation.key.isSystemKey() + system = relation.key.isSystemKey(), + relativeDate = fieldDate?.relativeDate ) } RelationFormat.STATUS -> { diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/model/DefaultObjectRelationValueView.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/model/DefaultObjectRelationValueView.kt index 48db03dd96..a2f4924388 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/model/DefaultObjectRelationValueView.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/model/DefaultObjectRelationValueView.kt @@ -1,6 +1,7 @@ package com.anytypeio.anytype.presentation.relations.model import com.anytypeio.anytype.core_models.Id +import com.anytypeio.anytype.core_models.RelativeDate import com.anytypeio.anytype.presentation.sets.model.FileView import com.anytypeio.anytype.presentation.sets.model.ObjectView import com.anytypeio.anytype.presentation.sets.model.StatusView @@ -51,7 +52,8 @@ sealed class DefaultObjectRelationValueView { override val objectId: Id, override val relationKey: Id, val timeInMillis: Long? = null, - val dateFormat: String, + val relativeDate: RelativeDate?, + val isTimeIncluded: Boolean, ) : DefaultObjectRelationValueView() data class Tag( diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/value/object/ObjectValueViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/value/object/ObjectValueViewModel.kt index 23d78b1da6..740f3e260a 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/value/object/ObjectValueViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/value/object/ObjectValueViewModel.kt @@ -20,13 +20,14 @@ import com.anytypeio.anytype.domain.`object`.UpdateDetail import com.anytypeio.anytype.domain.objects.SetObjectListIsArchived 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.search.SearchObjects import com.anytypeio.anytype.domain.workspace.SpaceManager import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate import com.anytypeio.anytype.presentation.common.BaseViewModel import com.anytypeio.anytype.presentation.extension.sendAnalyticsRelationEvent import com.anytypeio.anytype.presentation.navigation.DefaultObjectView -import com.anytypeio.anytype.presentation.objects.SupportedLayouts.isEditorOrFileLayout +import com.anytypeio.anytype.core_models.SupportedLayouts.isEditorOrFileLayout import com.anytypeio.anytype.presentation.objects.toView import com.anytypeio.anytype.presentation.relations.providers.ObjectRelationProvider import com.anytypeio.anytype.presentation.relations.providers.ObjectValueProvider @@ -58,7 +59,8 @@ class ObjectValueViewModel( private val objectListIsArchived: SetObjectListIsArchived, private val duplicateObject: DuplicateObject, private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate, - private val storeOfRelations: StoreOfRelations + private val storeOfRelations: StoreOfRelations, + private val fieldParser: FieldParser ) : BaseViewModel(), AnalyticSpaceHelperDelegate by analyticSpaceHelperDelegate { val viewState = MutableStateFlow(ObjectValueViewState.Loading()) @@ -181,7 +183,7 @@ class ObjectValueViewModel( objects: List, query: String = "" ) { - val views = mapObjects(ids, objects, query) + val views = mapObjects(ids, objects, query, fieldParser = fieldParser) viewState.value = if (views.isNotEmpty()) { ObjectValueViewState.Content( isEditableRelation = isEditableRelation, @@ -217,7 +219,8 @@ class ObjectValueViewModel( private suspend fun mapObjects( ids: List, objects: List, - query: String + query: String, + fieldParser: FieldParser ): List = objects.mapNotNull { obj -> if (!obj.isValid) return@mapNotNull null if (query.isNotBlank() && obj.name?.contains(query, true) == false) return@mapNotNull null @@ -227,7 +230,8 @@ class ObjectValueViewModel( ObjectValueItem.Object( view = obj.toView( urlBuilder = urlBuilder, - objectTypes = storeOfObjectTypes.getAll() + objectTypes = storeOfObjectTypes.getAll(), + fieldParser = fieldParser ), isSelected = isSelected, number = number, diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/value/object/ObjectValueViewModelFactory.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/value/object/ObjectValueViewModelFactory.kt index e349caece2..0388849841 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/value/object/ObjectValueViewModelFactory.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/value/object/ObjectValueViewModelFactory.kt @@ -10,6 +10,7 @@ import com.anytypeio.anytype.domain.`object`.UpdateDetail import com.anytypeio.anytype.domain.objects.SetObjectListIsArchived 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.search.SearchObjects import com.anytypeio.anytype.domain.workspace.SpaceManager import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate @@ -32,7 +33,8 @@ class ObjectValueViewModelFactory @Inject constructor( private val objectListIsArchived: SetObjectListIsArchived, private val duplicateObject: DuplicateObject, private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate, - private val storeOfRelations: StoreOfRelations + private val storeOfRelations: StoreOfRelations, + private val fieldParser: FieldParser ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create( @@ -51,6 +53,7 @@ class ObjectValueViewModelFactory @Inject constructor( objectListIsArchived = objectListIsArchived, duplicateObject = duplicateObject, analyticSpaceHelperDelegate = analyticSpaceHelperDelegate, - storeOfRelations = storeOfRelations + storeOfRelations = storeOfRelations, + fieldParser = fieldParser ) as T } \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/search/GlobalSearchViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/search/GlobalSearchViewModel.kt index 1d8052bedd..4625a3f6c1 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/search/GlobalSearchViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/search/GlobalSearchViewModel.kt @@ -34,6 +34,7 @@ import com.anytypeio.anytype.domain.base.fold import com.anytypeio.anytype.domain.misc.UrlBuilder 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.search.RestoreGlobalSearchHistory import com.anytypeio.anytype.domain.search.SearchWithMeta import com.anytypeio.anytype.domain.search.UpdateGlobalSearchHistory @@ -45,7 +46,6 @@ 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.objects.ObjectIcon -import com.anytypeio.anytype.presentation.objects.getProperName import com.anytypeio.anytype.presentation.search.ObjectSearchConstants.filterObjectsByIds import com.anytypeio.anytype.presentation.search.ObjectSearchConstants.filterSearchObjects import javax.inject.Inject @@ -75,7 +75,8 @@ class GlobalSearchViewModel @Inject constructor( private val analytics: Analytics, private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate, private val restoreGlobalSearchHistory: RestoreGlobalSearchHistory, - private val updateGlobalSearchHistory: UpdateGlobalSearchHistory + private val updateGlobalSearchHistory: UpdateGlobalSearchHistory, + private val fieldParser: FieldParser ) : BaseViewModel(), AnalyticSpaceHelperDelegate by analyticSpaceHelperDelegate { private val userInput = MutableStateFlow("") @@ -146,7 +147,8 @@ class GlobalSearchViewModel @Inject constructor( val relatedGlobalSearchItemView = result.firstOrNull()?.view( storeOfRelations = storeOfRelations, storeOfObjectTypes = storeOfObjectTypes, - urlBuilder = urlBuilder + urlBuilder = urlBuilder, + fieldParser = fieldParser ) if (relatedGlobalSearchItemView != null) { mode.value = Mode.Related(target = relatedGlobalSearchItemView) @@ -248,7 +250,8 @@ class GlobalSearchViewModel @Inject constructor( it.view( storeOfRelations = storeOfRelations, storeOfObjectTypes = storeOfObjectTypes, - urlBuilder = urlBuilder + urlBuilder = urlBuilder, + fieldParser = fieldParser ) }, isLoading = false @@ -330,7 +333,8 @@ class GlobalSearchViewModel @Inject constructor( it.view( storeOfRelations = storeOfRelations, storeOfObjectTypes = storeOfObjectTypes, - urlBuilder = urlBuilder + urlBuilder = urlBuilder, + fieldParser = fieldParser ) }, isLoading = false @@ -416,7 +420,8 @@ class GlobalSearchViewModel @Inject constructor( private val analytics: Analytics, private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate, private val restoreGlobalSearchHistory: RestoreGlobalSearchHistory, - private val updateGlobalSearchHistory: UpdateGlobalSearchHistory + private val updateGlobalSearchHistory: UpdateGlobalSearchHistory, + private val fieldParser: FieldParser ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class): T { @@ -429,7 +434,8 @@ class GlobalSearchViewModel @Inject constructor( analytics = analytics, analyticSpaceHelperDelegate = analyticSpaceHelperDelegate, restoreGlobalSearchHistory = restoreGlobalSearchHistory, - updateGlobalSearchHistory = updateGlobalSearchHistory + updateGlobalSearchHistory = updateGlobalSearchHistory, + fieldParser = fieldParser ) as T } } @@ -544,7 +550,8 @@ data class GlobalSearchItemView( suspend fun Command.SearchWithMeta.Result.view( storeOfObjectTypes: StoreOfObjectTypes, storeOfRelations: StoreOfRelations, - urlBuilder: UrlBuilder + urlBuilder: UrlBuilder, + fieldParser: FieldParser ) : GlobalSearchItemView? { if (wrapper.spaceId == null) return null if (wrapper.layout == null) return null @@ -557,7 +564,7 @@ suspend fun Command.SearchWithMeta.Result.view( backlinks = wrapper.backlinks, space = SpaceId(requireNotNull(wrapper.spaceId)), layout = requireNotNull(wrapper.layout), - title = wrapper.getProperName(), + title = fieldParser.getObjectName(wrapper), type = if (type != null) { storeOfObjectTypes.get(type)?.name.orEmpty() } else { @@ -612,7 +619,7 @@ suspend fun Command.SearchWithMeta.Result.view( } FILE -> GlobalSearchItemView.Meta.Default( name = relation.name.orEmpty(), - value = dep.getProperName(), + value = fieldParser.getObjectName(dep), highlights = emptyList() ) OBJECT -> { diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/search/ObjectSearchConstants.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/search/ObjectSearchConstants.kt index 9525aff4b8..15e4c2a603 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/search/ObjectSearchConstants.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/search/ObjectSearchConstants.kt @@ -7,16 +7,15 @@ import com.anytypeio.anytype.core_models.DVSort import com.anytypeio.anytype.core_models.DVSortType import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_models.Key -import com.anytypeio.anytype.core_models.Marketplace.MARKETPLACE_SPACE_ID import com.anytypeio.anytype.core_models.ObjectType import com.anytypeio.anytype.core_models.ObjectTypeUniqueKeys import com.anytypeio.anytype.core_models.RelationFormat import com.anytypeio.anytype.core_models.Relations import com.anytypeio.anytype.core_models.primitives.SpaceId import com.anytypeio.anytype.core_models.primitives.TypeKey -import com.anytypeio.anytype.core_models.restrictions.ObjectRestriction import com.anytypeio.anytype.domain.library.StoreSearchParams -import com.anytypeio.anytype.presentation.objects.SupportedLayouts +import com.anytypeio.anytype.core_models.SupportedLayouts +import com.anytypeio.anytype.core_models.SupportedLayouts.globalSearchLayouts /** * This class contains all filters and sorts for different use cases using Rpc.Object.Search command @@ -64,7 +63,7 @@ object ObjectSearchConstants { DVFilter( relation = Relations.LAYOUT, condition = DVFilterCondition.IN, - value = SupportedLayouts.globalSearchLayouts.map { it.code.toDouble() } + value = globalSearchLayouts.map { it.code.toDouble() } ) ) } @@ -112,7 +111,7 @@ object ObjectSearchConstants { DVFilter( relation = Relations.LAYOUT, condition = DVFilterCondition.IN, - value = (SupportedLayouts.createObjectLayouts + SupportedLayouts.fileLayouts).map { it.code.toDouble() } + value = globalSearchLayouts.map { it.code.toDouble() } ), DVFilter( relation = Relations.ID, @@ -123,7 +122,7 @@ object ObjectSearchConstants { val sortLinkTo = listOf( DVSort( - relationKey = Relations.LAST_MODIFIED_DATE, + relationKey = Relations.LAST_OPENED_DATE, type = DVSortType.DESC, includeTime = true, relationFormat = RelationFormat.DATE @@ -683,7 +682,8 @@ object ObjectSearchConstants { Relations.LINKS, Relations.BACKLINKS, Relations.LAST_USED_DATE, - Relations.DESCRIPTION + Relations.DESCRIPTION, + Relations.TIMESTAMP ) val defaultOptionKeys = listOf( diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/search/ObjectSearchViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/search/ObjectSearchViewModel.kt index 8aefd26955..adb216d28a 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/search/ObjectSearchViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/search/ObjectSearchViewModel.kt @@ -16,7 +16,9 @@ import com.anytypeio.anytype.domain.base.Resultat import com.anytypeio.anytype.domain.base.fold import com.anytypeio.anytype.domain.base.getOrThrow import com.anytypeio.anytype.domain.block.interactor.sets.GetObjectTypes +import com.anytypeio.anytype.domain.misc.DateProvider import com.anytypeio.anytype.domain.misc.UrlBuilder +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.search.SearchObjects import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate import com.anytypeio.anytype.presentation.extension.sendAnalyticsSearchResultEvent @@ -44,7 +46,8 @@ open class ObjectSearchViewModel( private val searchObjects: SearchObjects, private val getObjectTypes: GetObjectTypes, private val analytics: Analytics, - private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate + private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate, + private val fieldParser: FieldParser ) : ViewStateViewModel(), SupportNavigation>, TextInputDialogBottomBehaviorApplier.OnDialogCancelListener, @@ -75,7 +78,8 @@ open class ObjectSearchViewModel( Resultat.success( listOfObjects.getOrThrow().toViews( urlBuilder = urlBuilder, - objectTypes = listOfTypes.getOrThrow() + objectTypes = listOfTypes.getOrThrow(), + fieldParser = fieldParser ) ) } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/search/ObjectSearchViewModelFactory.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/search/ObjectSearchViewModelFactory.kt deleted file mode 100644 index b072586aaf..0000000000 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/search/ObjectSearchViewModelFactory.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.anytypeio.anytype.presentation.search - -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import com.anytypeio.anytype.analytics.base.Analytics -import com.anytypeio.anytype.domain.block.interactor.sets.GetObjectTypes -import com.anytypeio.anytype.domain.misc.UrlBuilder -import com.anytypeio.anytype.domain.search.SearchObjects -import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate - -class ObjectSearchViewModelFactory( - private val vmParams: ObjectSearchViewModel.VmParams, - private val urlBuilder: UrlBuilder, - private val getObjectTypes: GetObjectTypes, - private val searchObjects: SearchObjects, - private val analytics: Analytics, - private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate -) : ViewModelProvider.Factory { - - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T { - return ObjectSearchViewModel( - vmParams = vmParams, - urlBuilder = urlBuilder, - getObjectTypes = getObjectTypes, - searchObjects = searchObjects, - analytics = analytics, - analyticSpaceHelperDelegate = analyticSpaceHelperDelegate - ) as T - } -} \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/GalleryViewMapper.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/GalleryViewMapper.kt index 1a64f97265..f3ecd2c3cc 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/GalleryViewMapper.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/GalleryViewMapper.kt @@ -12,10 +12,10 @@ import com.anytypeio.anytype.core_utils.ext.typeOf import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.objects.ObjectStore import com.anytypeio.anytype.domain.objects.StoreOfRelations +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.presentation.editor.cover.CoverImageHashProvider import com.anytypeio.anytype.presentation.editor.cover.CoverView import com.anytypeio.anytype.presentation.mapper.objectIcon -import com.anytypeio.anytype.presentation.objects.getProperName import com.anytypeio.anytype.presentation.objects.setTypeRelationIconsAsNone import com.anytypeio.anytype.presentation.objects.values import com.anytypeio.anytype.presentation.relations.BasicObjectCoverWrapper @@ -31,7 +31,8 @@ suspend fun DVViewer.buildGalleryViews( urlBuilder: UrlBuilder, objectStore: ObjectStore, objectOrderIds: List, - storeOfRelations: StoreOfRelations + storeOfRelations: StoreOfRelations, + fieldParser: FieldParser ): List { val filteredRelations = viewerRelations.mapNotNull { setting -> @@ -58,7 +59,8 @@ suspend fun DVViewer.buildGalleryViews( store = objectStore, filteredRelations = filteredRelations, isLargeSize = true, - storeOfRelations = storeOfRelations + storeOfRelations = storeOfRelations, + fieldParser = fieldParser ) } else { obj.mapToDefaultItem( @@ -66,7 +68,8 @@ suspend fun DVViewer.buildGalleryViews( urlBuilder = urlBuilder, viewerRelations = viewerRelations, store = objectStore, - filteredRelations = filteredRelations + filteredRelations = filteredRelations, + fieldParser = fieldParser ) } } @@ -78,7 +81,8 @@ private suspend fun ObjectWrapper.Basic.mapToDefaultItem( urlBuilder: UrlBuilder, viewerRelations: List, store: ObjectStore, - filteredRelations: List + filteredRelations: List, + fieldParser: FieldParser ): Viewer.GalleryView.Item { val obj = this return Viewer.GalleryView.Item.Default( @@ -87,10 +91,11 @@ private suspend fun ObjectWrapper.Basic.mapToDefaultItem( relations = filteredRelations, urlBuilder = urlBuilder, settings = viewerRelations, - storeOfObjects = store + storeOfObjects = store, + fieldParser = fieldParser ).setTypeRelationIconsAsNone(), hideIcon = hideIcon, - name = obj.getProperName(), + name = fieldParser.getObjectName(obj), icon = obj.objectIcon(urlBuilder) ) } @@ -102,7 +107,8 @@ private suspend fun ObjectWrapper.Basic.mapToCoverItem( store: ObjectStore, filteredRelations: List, isLargeSize: Boolean, - storeOfRelations: StoreOfRelations + storeOfRelations: StoreOfRelations, + fieldParser: FieldParser ): Viewer.GalleryView.Item { val obj = this @@ -123,10 +129,11 @@ private suspend fun ObjectWrapper.Basic.mapToCoverItem( relations = filteredRelations, urlBuilder = urlBuilder, settings = dvViewer.viewerRelations, - storeOfObjects = store + storeOfObjects = store, + fieldParser = fieldParser ).setTypeRelationIconsAsNone(), hideIcon = dvViewer.hideIcon, - name = obj.getProperName(), + name = fieldParser.getObjectName(obj), icon = obj.objectIcon(urlBuilder), cover = cover, fitImage = dvViewer.coverFit, diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/ListViewMapper.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/ListViewMapper.kt index 8aa838d88a..ab4d6e8049 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/ListViewMapper.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/ListViewMapper.kt @@ -7,9 +7,8 @@ import com.anytypeio.anytype.core_models.ObjectWrapper import com.anytypeio.anytype.core_models.Relations import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.objects.ObjectStore +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.presentation.mapper.objectIcon -import com.anytypeio.anytype.presentation.objects.ObjectIcon -import com.anytypeio.anytype.presentation.objects.getProperName import com.anytypeio.anytype.presentation.objects.relationsFilteredByHiddenAndDescription import com.anytypeio.anytype.presentation.objects.setTypeRelationIconsAsNone import com.anytypeio.anytype.presentation.sets.model.Viewer @@ -19,7 +18,8 @@ suspend fun DVViewer.buildListViews( relations: List, urlBuilder: UrlBuilder, store: ObjectStore, - objectOrderIds: List + objectOrderIds: List, + fieldParser: FieldParser ): List { val items = objects.mapNotNull { id -> val obj = store.get(id) @@ -37,9 +37,10 @@ suspend fun DVViewer.buildListViews( relations = relations, urlBuilder = urlBuilder, settings = viewerRelations, - storeOfObjects = store + storeOfObjects = store, + fieldParser = fieldParser ).setTypeRelationIconsAsNone(), - name = obj.getProperName(), + name = fieldParser.getObjectName(obj), icon = obj.objectIcon(urlBuilder), description = description, hideIcon = hideIcon @@ -57,10 +58,11 @@ suspend fun DVViewer.buildListViews( relations = relations, urlBuilder = urlBuilder, settings = viewerRelations, - storeOfObjects = store + storeOfObjects = store, + fieldParser = fieldParser ).setTypeRelationIconsAsNone(), - name = obj.getProperName(), - done = obj.done ?: false, + name = fieldParser.getObjectName(obj), + done = obj.done == true, description = description ) } @@ -76,9 +78,10 @@ suspend fun DVViewer.buildListViews( relations = relations, urlBuilder = urlBuilder, settings = viewerRelations, - storeOfObjects = store + storeOfObjects = store, + fieldParser = fieldParser ).setTypeRelationIconsAsNone(), - name = obj.getProperName(), + name = fieldParser.getObjectName(obj), icon = obj.objectIcon(urlBuilder), description = description, hideIcon = hideIcon diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/ObjectSetExtension.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/ObjectSetExtension.kt index d3b3295cba..e6d62f4d02 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/ObjectSetExtension.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/ObjectSetExtension.kt @@ -39,10 +39,10 @@ import com.anytypeio.anytype.domain.misc.DateProvider import com.anytypeio.anytype.domain.misc.UrlBuilder 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.presentation.editor.cover.CoverImageHashProvider import com.anytypeio.anytype.presentation.editor.editor.model.BlockView import com.anytypeio.anytype.presentation.mapper.objectIcon -import com.anytypeio.anytype.presentation.objects.getProperName import com.anytypeio.anytype.presentation.relations.BasicObjectCoverWrapper import com.anytypeio.anytype.presentation.relations.ObjectRelationView import com.anytypeio.anytype.presentation.relations.ObjectSetConfig.ID_KEY @@ -64,7 +64,8 @@ import timber.log.Timber fun ObjectState.DataView.featuredRelations( ctx: Id, urlBuilder: UrlBuilder, - relations: List + relations: List, + fieldParser: FieldParser ): BlockView.FeaturedRelation? { val block = blocks.find { it.content is Block.Content.FeaturedRelations } if (block != null) { @@ -76,7 +77,8 @@ fun ObjectState.DataView.featuredRelations( keys = ids, details = Block.Details(details), relations = relations, - urlBuilder = urlBuilder + urlBuilder = urlBuilder, + fieldParser = fieldParser ) ) return BlockView.FeaturedRelation( @@ -127,7 +129,8 @@ private fun ObjectState.DataView.mapFeaturedRelations( keys: List, details: Block.Details, relations: List, - urlBuilder: UrlBuilder + urlBuilder: UrlBuilder, + fieldParser: FieldParser ): List = keys.mapNotNull { key -> when (key) { Relations.DESCRIPTION -> null @@ -174,7 +177,7 @@ private fun ObjectState.DataView.mapFeaturedRelations( val isReadOnly = wrapper?.relationReadonlyValue == true val sources = if (isValid && !isDeleted) { - listOf(wrapper.toObjectViewDefault(urlBuilder = urlBuilder)) + listOf(wrapper.toObjectViewDefault(urlBuilder = urlBuilder, fieldParser = fieldParser)) } else { emptyList() } @@ -203,7 +206,8 @@ private fun ObjectState.DataView.mapFeaturedRelations( details = details.details, values = details.details[ctx]?.map ?: emptyMap(), urlBuilder = urlBuilder, - isFeatured = true + isFeatured = true, + fieldParser = fieldParser ) } } @@ -283,15 +287,15 @@ fun List.updateFormatForSubscription(relationLinks: List fun List.filterHiddenRelations(): List = filter { !it.isHidden } -fun ObjectWrapper.Basic.toObjectView(urlBuilder: UrlBuilder): ObjectView = when (isDeleted) { +fun ObjectWrapper.Basic.toObjectView(urlBuilder: UrlBuilder, fieldParser: FieldParser): ObjectView = when (isDeleted) { true -> ObjectView.Deleted(id) - else -> toObjectViewDefault(urlBuilder) + else -> toObjectViewDefault(urlBuilder, fieldParser) } -fun ObjectWrapper.Basic.toObjectViewDefault(urlBuilder: UrlBuilder): ObjectView.Default { +fun ObjectWrapper.Basic.toObjectViewDefault(urlBuilder: UrlBuilder, fieldParser: FieldParser): ObjectView.Default { return ObjectView.Default( id = id, - name = getProperName(), + name = fieldParser.getObjectName(this), icon = this.objectIcon(builder = urlBuilder), types = type, isRelation = layout == ObjectType.Layout.RELATION diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/ObjectSetViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/ObjectSetViewModel.kt index 3485ca297a..86c95e189c 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/ObjectSetViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/ObjectSetViewModel.kt @@ -41,7 +41,6 @@ import com.anytypeio.anytype.domain.dataview.interactor.CreateDataViewObject import com.anytypeio.anytype.domain.error.Error import com.anytypeio.anytype.domain.event.interactor.InterceptEvents import com.anytypeio.anytype.domain.event.interactor.SpaceSyncAndP2PStatusProvider -import com.anytypeio.anytype.domain.library.StoreSearchByIdsParams import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer import com.anytypeio.anytype.domain.misc.DateProvider import com.anytypeio.anytype.domain.misc.UrlBuilder @@ -55,6 +54,7 @@ import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes import com.anytypeio.anytype.domain.objects.StoreOfRelations import com.anytypeio.anytype.domain.page.CloseBlock import com.anytypeio.anytype.domain.page.CreateObject +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.search.DataViewState import com.anytypeio.anytype.domain.search.DataViewSubscriptionContainer import com.anytypeio.anytype.domain.sets.OpenObjectSet @@ -77,12 +77,10 @@ import com.anytypeio.anytype.presentation.home.HomeScreenViewModel.Companion.HOM import com.anytypeio.anytype.presentation.mapper.toTemplateObjectTypeViewItems import com.anytypeio.anytype.presentation.navigation.AppNavigation import com.anytypeio.anytype.presentation.navigation.SupportNavigation -import com.anytypeio.anytype.presentation.objects.SupportedLayouts +import com.anytypeio.anytype.core_models.SupportedLayouts import com.anytypeio.anytype.presentation.objects.getCreateObjectParams import com.anytypeio.anytype.presentation.objects.isCreateObjectAllowed import com.anytypeio.anytype.presentation.objects.isTemplatesAllowed -import com.anytypeio.anytype.presentation.profile.ProfileIconView -import com.anytypeio.anytype.presentation.profile.profileIcon import com.anytypeio.anytype.presentation.relations.ObjectRelationView import com.anytypeio.anytype.presentation.relations.ObjectSetConfig.DEFAULT_LIMIT import com.anytypeio.anytype.presentation.relations.RelationListViewModel @@ -126,11 +124,9 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.consumeAsFlow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.emptyFlow -import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.onEach @@ -179,6 +175,7 @@ class ObjectSetViewModel( private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate, private val spaceSyncAndP2PStatusProvider: SpaceSyncAndP2PStatusProvider, private val clearLastOpenedObject: ClearLastOpenedObject, + private val fieldParser: FieldParser ) : ViewModel(), SupportNavigation>, ViewerDelegate by viewerDelegate, AnalyticSpaceHelperDelegate by analyticSpaceHelperDelegate @@ -248,7 +245,8 @@ class ObjectSetViewModel( featured.value = state.featuredRelations( ctx = vmParams.ctx, urlBuilder = urlBuilder, - relations = storeOfRelations.getAll() + relations = storeOfRelations.getAll(), + fieldParser = fieldParser ) _header.value = state.header( ctx = vmParams.ctx, @@ -689,7 +687,8 @@ class ObjectSetViewModel( objects = dataViewState.objects, dataViewRelations = relations, store = objectStore, - storeOfRelations = storeOfRelations + storeOfRelations = storeOfRelations, + fieldParser = fieldParser ) when { @@ -741,7 +740,8 @@ class ObjectSetViewModel( dataViewRelations = relations, store = objectStore, objectOrderIds = objectOrderIds, - storeOfRelations = storeOfRelations + storeOfRelations = storeOfRelations, + fieldParser = fieldParser ) } } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/ObjectSetViewModelFactory.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/ObjectSetViewModelFactory.kt index b95126aae0..f40dd6ddf5 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/ObjectSetViewModelFactory.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/ObjectSetViewModelFactory.kt @@ -26,6 +26,7 @@ import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes import com.anytypeio.anytype.domain.objects.StoreOfRelations import com.anytypeio.anytype.domain.page.CloseBlock import com.anytypeio.anytype.domain.page.CreateObject +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.search.DataViewSubscriptionContainer import com.anytypeio.anytype.domain.sets.OpenObjectSet import com.anytypeio.anytype.domain.sets.SetQueryToObjectSet @@ -83,7 +84,8 @@ class ObjectSetViewModelFactory( private val dateProvider: DateProvider, private val spaceSyncAndP2PStatusProvider: SpaceSyncAndP2PStatusProvider, private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate, - private val clearLastOpenedObject: ClearLastOpenedObject + private val clearLastOpenedObject: ClearLastOpenedObject, + private val fieldParser: FieldParser, ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class): T { @@ -128,7 +130,8 @@ class ObjectSetViewModelFactory( dateProvider = dateProvider, analyticSpaceHelperDelegate = analyticSpaceHelperDelegate, spaceSyncAndP2PStatusProvider = spaceSyncAndP2PStatusProvider, - clearLastOpenedObject = clearLastOpenedObject + clearLastOpenedObject = clearLastOpenedObject, + fieldParser = fieldParser ) as T } } \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/RelationDateValueViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/RelationDateValueViewModel.kt index a08984c88e..52b022213c 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/RelationDateValueViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/RelationDateValueViewModel.kt @@ -12,7 +12,6 @@ import com.anytypeio.anytype.core_utils.ext.cancel import com.anytypeio.anytype.domain.misc.DateProvider import com.anytypeio.anytype.presentation.relations.providers.ObjectRelationProvider import com.anytypeio.anytype.presentation.relations.providers.ObjectValueProvider -import java.time.ZoneId import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow @@ -89,8 +88,7 @@ class RelationDateValueViewModel( fun onDateSelected(selectedDate: TimeInMillis?) { if (selectedDate != null) { val properDate = dateProvider.adjustFromStartOfDayInUserTimeZoneToUTC( - timestamp = (selectedDate / 1000), - zoneId = ZoneId.systemDefault() + timeInMillis = selectedDate ) viewModelScope.launch { commands.emit( diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/SetsExtension.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/SetsExtension.kt index ad6159b264..dde43665cf 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/SetsExtension.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/SetsExtension.kt @@ -7,14 +7,13 @@ import com.anytypeio.anytype.core_models.ObjectWrapper import com.anytypeio.anytype.core_models.Relation import com.anytypeio.anytype.core_models.Relations import com.anytypeio.anytype.core_models.Struct -import com.anytypeio.anytype.core_models.ext.DateParser import com.anytypeio.anytype.core_utils.ext.typeOf import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.objects.ObjectStore import com.anytypeio.anytype.presentation.mapper.objectIcon import com.anytypeio.anytype.presentation.number.NumberParser import com.anytypeio.anytype.presentation.objects.ObjectIcon -import com.anytypeio.anytype.presentation.objects.getProperName +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.presentation.relations.getDateRelationFormat import com.anytypeio.anytype.presentation.sets.model.CellView import com.anytypeio.anytype.presentation.sets.model.ColumnView @@ -30,11 +29,12 @@ suspend fun List.buildGridRow( showIcon: Boolean, obj: ObjectWrapper.Basic, builder: UrlBuilder, - store: ObjectStore + store: ObjectStore, + fieldParser: FieldParser ): Viewer.GridView.Row { val type = obj.type.firstOrNull() - val name = obj.getProperName() + val name = fieldParser.getObjectName(obj) val emoji = obj.iconEmoji val image = obj.iconImage val done = obj.done @@ -59,7 +59,8 @@ suspend fun List.buildGridRow( columnKey = column.key, builder = builder, store = store, - withIcon = false + withIcon = false, + fieldParser = fieldParser ) cells.add( CellView.Object( @@ -93,12 +94,14 @@ suspend fun List.buildGridRow( } ColumnView.Format.DATE -> { - val value = obj.getValue(column.key) + val fieldDate = fieldParser.toDate(any = obj.getValue(column.key)) CellView.Date( id = obj.id, relationKey = column.key, - timeInSecs = DateParser.parse(value), - dateFormat = column.getDateRelationFormat() + timeInSecs = fieldDate?.timestamp?.time, + dateFormat = column.getDateRelationFormat(), + relativeDate = fieldDate?.relativeDate, + isTimeIncluded = column.isDateIncludeTime == true ) } @@ -166,7 +169,8 @@ suspend fun List.buildGridRow( val objects = obj.map.buildObjectViews( columnKey = column.key, builder = builder, - store = store + store = store, + fieldParser = fieldParser ) CellView.Object( id = obj.id, @@ -287,20 +291,21 @@ private fun ObjectWrapper.File.toView() : FileView { fun Struct.buildRelationValueObjectViews( relationKey: Id, details: Map, - builder: UrlBuilder + builder: UrlBuilder, + fieldParser: FieldParser ): List { val objects = mutableListOf() val value = this.getOrDefault(relationKey, null) if (value is Id) { val wrapper = ObjectWrapper.Basic(details[value]?.map.orEmpty()) if (wrapper.isValid) { - objects.add(wrapper.toObjectView(urlBuilder = builder)) + objects.add(wrapper.toObjectView(urlBuilder = builder, fieldParser = fieldParser)) } } else if (value is List<*>) { value.typeOf().forEach { id -> val wrapper = ObjectWrapper.Basic(details[id]?.map.orEmpty()) if (wrapper.isValid) { - objects.add(wrapper.toObjectView(urlBuilder = builder)) + objects.add(wrapper.toObjectView(urlBuilder = builder, fieldParser = fieldParser)) } } } @@ -311,7 +316,8 @@ suspend fun Struct.buildObjectViews( columnKey: Id, store: ObjectStore, builder: UrlBuilder, - withIcon: Boolean = true + withIcon: Boolean = true, + fieldParser: FieldParser ): List { val objects = mutableListOf() val value = this.getOrDefault(columnKey, null) @@ -329,7 +335,7 @@ suspend fun Struct.buildObjectViews( objects.add( ObjectView.Default( id = value, - name = wrapper.getProperName(), + name = fieldParser.getObjectName(wrapper), icon = icon, types = wrapper.type ) @@ -353,7 +359,7 @@ suspend fun Struct.buildObjectViews( objects.add( ObjectView.Default( id = id, - name = wrapper.getProperName(), + name = fieldParser.getObjectName(wrapper), icon = icon, types = wrapper.type ) diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/filter/CreateFilterView.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/filter/CreateFilterView.kt index 80bd5c798e..28f0650150 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/filter/CreateFilterView.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/filter/CreateFilterView.kt @@ -3,6 +3,7 @@ package com.anytypeio.anytype.presentation.sets.filter import com.anytypeio.anytype.core_models.DVFilterCondition import com.anytypeio.anytype.core_models.DVFilterQuickOption import com.anytypeio.anytype.core_models.Id +import com.anytypeio.anytype.core_models.RelativeDate import com.anytypeio.anytype.presentation.objects.ObjectIcon sealed class CreateFilterView { @@ -36,7 +37,8 @@ sealed class CreateFilterView { val type: DVFilterQuickOption, val condition: DVFilterCondition, val value: Long, - override val isSelected: Boolean + override val isSelected: Boolean, + val relativeDate: RelativeDate? ) : CreateFilterView() { override val text: String get() = description diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/filter/FilterViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/filter/FilterViewModel.kt index 6675f62b5e..cb8cec5554 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/filter/FilterViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/filter/FilterViewModel.kt @@ -24,6 +24,7 @@ import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes import com.anytypeio.anytype.domain.objects.StoreOfRelations import com.anytypeio.anytype.domain.objects.options.GetOptions +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.search.SearchObjects import com.anytypeio.anytype.domain.workspace.SpaceManager import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate @@ -35,7 +36,7 @@ import com.anytypeio.anytype.presentation.extension.logEvent import com.anytypeio.anytype.presentation.extension.toConditionView import com.anytypeio.anytype.presentation.extension.type import com.anytypeio.anytype.presentation.mapper.toDomain -import com.anytypeio.anytype.presentation.objects.SupportedLayouts +import com.anytypeio.anytype.core_models.SupportedLayouts import com.anytypeio.anytype.presentation.objects.toCreateFilterObjectView import com.anytypeio.anytype.presentation.relations.FilterInputValueParser import com.anytypeio.anytype.presentation.relations.toCreateFilterCheckboxView @@ -73,7 +74,8 @@ open class FilterViewModel( private val objectSetDatabase: ObjectSetDatabase, private val analytics: Analytics, private val getOptions: GetOptions, - private val spaceManager: SpaceManager + private val spaceManager: SpaceManager, + private val fieldParser: FieldParser ) : ViewModel() { val commands = MutableSharedFlow() @@ -96,7 +98,8 @@ open class FilterViewModel( setValueStates( condition = condition?.condition, index = filterIndex, - viewerId = viewerId + viewerId = viewerId, + fieldParser = fieldParser ) } } @@ -133,7 +136,8 @@ open class FilterViewModel( setValueStates( condition = conditionState.value?.condition, index = filterIndex, - viewerId = viewerId + viewerId = viewerId, + fieldParser = fieldParser ) } else { Timber.e("Couldn't find relation in StoreOfRelations by relationKey:[$relationKey]") @@ -226,7 +230,8 @@ open class FilterViewModel( private suspend fun setValueStates( viewerId: Id, condition: Viewer.Filter.Condition?, - index: Int? + index: Int?, + fieldParser: FieldParser ) { if (condition == null || !condition.hasValue()) { filterValueState.value = null @@ -242,28 +247,30 @@ open class FilterViewModel( if (index == null) { filterValueState.value = relation.toFilterValue( value = null, - details = state.details, urlBuilder = urlBuilder, - store = objectSetDatabase.store + store = objectSetDatabase.store, + fieldParser = fieldParser ) proceedWithFilterValueList( relation = relation, filter = null, - condition = condition + condition = condition, + fieldParser = fieldParser ) } else { val filter = viewer.filters[index] check(filter.relation == relation.key) { "Incorrect filter state" } filterValueState.value = relation.toFilterValue( value = filter.value, - details = state.details, urlBuilder = urlBuilder, - store = objectSetDatabase.store + store = objectSetDatabase.store, + fieldParser = fieldParser ) proceedWithFilterValueList( relation = relation, filter = filter, condition = condition, + fieldParser = fieldParser ) } } @@ -272,14 +279,16 @@ open class FilterViewModel( private suspend fun proceedWithFilterValueList( relation: ObjectWrapper.Relation, filter: DVFilter?, - condition: Viewer.Filter.Condition + condition: Viewer.Filter.Condition, + fieldParser: FieldParser ): Unit = when (relation.format) { Relation.Format.DATE -> { val value = (filter?.value as? Double)?.toLong() ?: 0L filterValueListState.value = relation.toCreateFilterDateView( quickOption = filter?.quickOption, condition = condition.toDomain(), - value = value + value = value, + fieldParser = fieldParser ) } Relation.Format.TAG -> { @@ -398,7 +407,8 @@ open class FilterViewModel( filterValueListState.value = objects.toCreateFilterObjectView( ids = ids, urlBuilder = urlBuilder, - objectTypes = objectTypes + objectTypes = objectTypes, + fieldParser = fieldParser ).also { optionCountState.value = it.count { view -> view.isSelected } } @@ -432,7 +442,8 @@ open class FilterViewModel( filterValueListState.value = objects.toCreateFilterObjectView( ids = ids, urlBuilder = urlBuilder, - objectTypes = objectTypes + objectTypes = objectTypes, + fieldParser = fieldParser ).also { optionCountState.value = it.count { view -> view.isSelected } } @@ -925,7 +936,8 @@ open class FilterViewModel( private val objectSetDatabase: ObjectSetDatabase, private val getOptions: GetOptions, private val analytics: Analytics, - private val spaceManager: SpaceManager + private val spaceManager: SpaceManager, + private val fieldParser: FieldParser ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class): T { @@ -940,7 +952,8 @@ open class FilterViewModel( objectSetDatabase = objectSetDatabase, getOptions = getOptions, analytics = analytics, - spaceManager = spaceManager + spaceManager = spaceManager, + fieldParser = fieldParser ) as T } } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/filter/ViewerFilterViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/filter/ViewerFilterViewModel.kt index e7b627c4f2..c982c206e8 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/filter/ViewerFilterViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/filter/ViewerFilterViewModel.kt @@ -9,6 +9,7 @@ import com.anytypeio.anytype.domain.base.fold import com.anytypeio.anytype.domain.dataview.interactor.UpdateDataViewViewer import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.objects.StoreOfRelations +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.search.DataViewState import com.anytypeio.anytype.presentation.common.BaseListViewModel import com.anytypeio.anytype.presentation.extension.ObjectStateAnalyticsEvent @@ -34,7 +35,8 @@ class ViewerFilterViewModel( private val urlBuilder: UrlBuilder, private val storeOfRelations: StoreOfRelations, private val db: ObjectSetDatabase, - private val analytics: Analytics + private val analytics: Analytics, + private val fieldParser: FieldParser ) : BaseListViewModel() { val screenState = MutableStateFlow(ScreenState.LIST) @@ -66,7 +68,8 @@ class ViewerFilterViewModel( storeOfRelations = storeOfRelations, storeOfObjects = db.store, screenState = screenState.value, - urlBuilder = urlBuilder + urlBuilder = urlBuilder, + fieldParser = fieldParser ) } } @@ -217,7 +220,8 @@ class ViewerFilterViewModel( private val urlBuilder: UrlBuilder, private val analytics: Analytics, private val storeOfRelations: StoreOfRelations, - private val objectSetDatabase: ObjectSetDatabase + private val objectSetDatabase: ObjectSetDatabase, + private val fieldParser: FieldParser ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class): T { @@ -228,7 +232,8 @@ class ViewerFilterViewModel( urlBuilder = urlBuilder, analytics = analytics, storeOfRelations = storeOfRelations, - db = objectSetDatabase + db = objectSetDatabase, + fieldParser = fieldParser ) as T } } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/model/CellView.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/model/CellView.kt index d9d8b788ef..85bc8025dc 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/model/CellView.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/model/CellView.kt @@ -1,5 +1,7 @@ package com.anytypeio.anytype.presentation.sets.model +import com.anytypeio.anytype.core_models.RelativeDate + sealed class CellView { abstract val relationKey: String @@ -28,6 +30,8 @@ sealed class CellView { override val relationKey: String, val timeInSecs: Long? = null, override val dateFormat: String, + val relativeDate: RelativeDate?, + val isTimeIncluded: Boolean = false ) : CellView(), DateFormat data class Number( diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/model/FilterView.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/model/FilterView.kt index b8d14b6369..d3adbe6845 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/model/FilterView.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/model/FilterView.kt @@ -3,6 +3,7 @@ package com.anytypeio.anytype.presentation.sets.model import com.anytypeio.anytype.core_models.DVFilterQuickOption import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_models.Key +import com.anytypeio.anytype.core_models.RelativeDate import com.anytypeio.anytype.core_utils.ui.ViewType sealed class FilterView : ViewType { @@ -141,7 +142,8 @@ sealed class FilterView : ViewType { override val filterValue: FilterValue.Date, override val format: ColumnView.Format, override val isValueRequired: Boolean, - override val isInEditMode: Boolean + override val isInEditMode: Boolean, + val relativeDate: RelativeDate? ) : Expression(), ViewType { override fun getViewType(): Int = HOLDER_DATE } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/splash/SplashViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/splash/SplashViewModel.kt index ec7242ad48..2480309680 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/splash/SplashViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/splash/SplashViewModel.kt @@ -35,7 +35,7 @@ import com.anytypeio.anytype.domain.workspace.SpaceManager import com.anytypeio.anytype.presentation.BuildConfig import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate import com.anytypeio.anytype.presentation.extension.sendAnalyticsObjectCreateEvent -import com.anytypeio.anytype.presentation.objects.SupportedLayouts +import com.anytypeio.anytype.core_models.SupportedLayouts import com.anytypeio.anytype.presentation.search.ObjectSearchConstants import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/vault/VaultViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/vault/VaultViewModel.kt index 6f38b29a05..401e4882f0 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/vault/VaultViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/vault/VaultViewModel.kt @@ -281,6 +281,14 @@ class VaultViewModel( OpenObjectNavigation.NonValidObject -> { sendToast("Object id is missing") } + is OpenObjectNavigation.OpenDataObject -> { + navigate( + Navigation.OpenDateObject( + ctx = navigation.target, + space = navigation.space + ) + ) + } } } @@ -344,6 +352,7 @@ class VaultViewModel( data class OpenChat(val ctx: Id, val space: Id) : Navigation() data class OpenObject(val ctx: Id, val space: Id) : Navigation() data class OpenSet(val ctx: Id, val space: Id, val view: Id?) : Navigation() + data class OpenDateObject(val ctx: Id, val space: Id) : Navigation() } companion object { diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/DataViewListWidgetContainer.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/DataViewListWidgetContainer.kt index 91d08879a1..d3a193f0dd 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/DataViewListWidgetContainer.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/DataViewListWidgetContainer.kt @@ -66,7 +66,13 @@ class DataViewListWidgetContainer( elements = emptyList(), isExpanded = !isCollapsed, isCompact = widget.isCompact, - isLoading = true + isLoading = true, + name = when(val source = widget.source) { + is Widget.Source.Bundled -> WidgetView.Name.Bundled(source = source) + is Widget.Source.Default -> WidgetView.Name.Default( + prettyPrintName = source.obj.getWidgetObjectName() + ) + } ) } is Widget.View -> { @@ -76,7 +82,10 @@ class DataViewListWidgetContainer( tabs = emptyList(), elements = emptyList(), isExpanded = !isCollapsed, - isLoading = true + isLoading = true, + name = WidgetView.Name.Default( + prettyPrintName = widget.source.obj.getWidgetObjectName() + ) ) } is Widget.Link, is Widget.Tree -> { @@ -112,7 +121,13 @@ class DataViewListWidgetContainer( tabs = emptyList(), elements = emptyList(), isExpanded = false, - isCompact = isCompact + isCompact = isCompact, + name = when(val source = widget.source) { + is Widget.Source.Bundled -> WidgetView.Name.Bundled(source = source) + is Widget.Source.Default -> WidgetView.Name.Default( + prettyPrintName = source.obj.getWidgetObjectName() + ) + } ) ) } @@ -123,7 +138,10 @@ class DataViewListWidgetContainer( source = widget.source, tabs = emptyList(), elements = emptyList(), - isExpanded = false + isExpanded = false, + name = WidgetView.Name.Default( + prettyPrintName = source.obj.getWidgetObjectName() + ) ) ) } @@ -241,12 +259,21 @@ class DataViewListWidgetContainer( ) } else { null - } + }, + name = WidgetView.Name.Default( + prettyPrintName = obj.getWidgetObjectName() + ) ) }, isExpanded = true, showIcon = withIcon, - showCover = withCover + showCover = withCover, + name = when(val source = widget.source) { + is Widget.Source.Bundled -> WidgetView.Name.Bundled(source = source) + is Widget.Source.Default -> WidgetView.Name.Default( + prettyPrintName = source.obj.getWidgetObjectName() + ) + } ) } } @@ -270,11 +297,20 @@ class DataViewListWidgetContainer( elements = objects.map { obj -> WidgetView.SetOfObjects.Element( obj = obj, - objectIcon = obj.objectIcon(builder = urlBuilder) + objectIcon = obj.objectIcon(builder = urlBuilder), + name = WidgetView.Name.Default( + prettyPrintName = obj.getWidgetObjectName() + ) ) }, isExpanded = true, - isCompact = isCompact + isCompact = isCompact, + name = when(val source = widget.source) { + is Widget.Source.Bundled -> WidgetView.Name.Bundled(source = source) + is Widget.Source.Default -> WidgetView.Name.Default( + prettyPrintName = source.obj.getWidgetObjectName() + ) + } ) } } @@ -287,7 +323,13 @@ class DataViewListWidgetContainer( tabs = emptyList(), elements = emptyList(), isExpanded = true, - isCompact = (widget as? Widget.List)?.isCompact ?: false + isCompact = (widget as? Widget.List)?.isCompact ?: false, + name = when(val source = widget.source) { + is Widget.Source.Bundled -> WidgetView.Name.Bundled(source = source) + is Widget.Source.Default -> WidgetView.Name.Default( + prettyPrintName = source.obj.getWidgetObjectName() + ) + } ) is Widget.View -> WidgetView.Gallery( id = widget.id, @@ -295,7 +337,10 @@ class DataViewListWidgetContainer( tabs = emptyList(), elements = emptyList(), isExpanded = true, - view = null + view = null, + name = WidgetView.Name.Default( + prettyPrintName = widget.source.obj.getWidgetObjectName() + ) ) is Widget.Link, is Widget.Tree -> { throw IllegalStateException("Incompatible widget type.") diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/LinkWidgetContainer.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/LinkWidgetContainer.kt index 4efe96fa22..9e1e250ef8 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/LinkWidgetContainer.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/LinkWidgetContainer.kt @@ -9,7 +9,13 @@ class LinkWidgetContainer( override val view: Flow = flowOf( WidgetView.Link( id = widget.id, - source = widget.source + source = widget.source, + name = when(val source = widget.source) { + is Widget.Source.Bundled -> WidgetView.Name.Bundled(source = source) + is Widget.Source.Default -> WidgetView.Name.Default( + prettyPrintName = source.obj.getWidgetObjectName() + ) + } ) ) } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/ListWidgetContainer.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/ListWidgetContainer.kt index b9c7f7957a..58668afc4c 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/ListWidgetContainer.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/ListWidgetContainer.kt @@ -153,6 +153,9 @@ class ListWidgetContainer( obj = obj, objectIcon = obj.objectIcon( builder = urlBuilder + ), + name = WidgetView.Name.Default( + prettyPrintName = obj.getWidgetObjectName() ) ) }, diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/SelectWidgetSourceViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/SelectWidgetSourceViewModel.kt index fb752b05b3..9fd995ebc9 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/SelectWidgetSourceViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/SelectWidgetSourceViewModel.kt @@ -5,11 +5,13 @@ import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import com.anytypeio.anytype.analytics.base.Analytics import com.anytypeio.anytype.core_models.Id +import com.anytypeio.anytype.core_models.ObjectType import com.anytypeio.anytype.domain.base.Resultat import com.anytypeio.anytype.domain.base.fold import com.anytypeio.anytype.domain.base.getOrDefault import com.anytypeio.anytype.domain.block.interactor.sets.GetObjectTypes import com.anytypeio.anytype.domain.misc.UrlBuilder +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.search.SearchObjects import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate import com.anytypeio.anytype.presentation.extension.sendChangeWidgetSourceEvent @@ -34,14 +36,16 @@ class SelectWidgetSourceViewModel( private val getObjectTypes: GetObjectTypes, private val analytics: Analytics, private val dispatcher: Dispatcher, - private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate + private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate, + fieldParser: FieldParser ) : ObjectSearchViewModel( vmParams = vmParams, urlBuilder = urlBuilder, searchObjects = searchObjects, getObjectTypes = getObjectTypes, analytics = analytics, - analyticSpaceHelperDelegate = analyticSpaceHelperDelegate + analyticSpaceHelperDelegate = analyticSpaceHelperDelegate, + fieldParser = fieldParser ) { val isDismissed = MutableStateFlow(false) @@ -204,6 +208,9 @@ class SelectWidgetSourceViewModel( isInEditMode = curr.isInEditMode ) } + if (view.layout == ObjectType.Layout.DATE) { + isDismissed.value = true + } } } is Config.ExistingWidget -> { @@ -259,7 +266,8 @@ class SelectWidgetSourceViewModel( private val getObjectTypes: GetObjectTypes, private val analytics: Analytics, private val dispatcher: Dispatcher, - private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate + private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate, + private val fieldParser: FieldParser ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") @@ -271,7 +279,8 @@ class SelectWidgetSourceViewModel( analytics = analytics, getObjectTypes = getObjectTypes, dispatcher = dispatcher, - analyticSpaceHelperDelegate = analyticSpaceHelperDelegate + analyticSpaceHelperDelegate = analyticSpaceHelperDelegate, + fieldParser = fieldParser ) as T } } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/SelectWidgetTypeViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/SelectWidgetTypeViewModel.kt index b4b5ee1944..0a95aaae26 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/SelectWidgetTypeViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/SelectWidgetTypeViewModel.kt @@ -43,74 +43,117 @@ class SelectWidgetTypeViewModel( sourceLayout: Int, source: Id ) { - Timber.d("onStart for existing widget") - if (BundledWidgetSourceIds.ids.contains(source)) { - if (source == BundledWidgetSourceIds.FAVORITE - || source == BundledWidgetSourceIds.RECENT - || source == BundledWidgetSourceIds.RECENT_LOCAL - ) { - views.value = listOf( - WidgetTypeView.CompactList().setIsSelected(currentType), - WidgetTypeView.List().setIsSelected(currentType), - WidgetTypeView.Tree().setIsSelected(currentType), + Timber.d("onStartForExistingWidget, currentType:[$currentType], sourceLayout:[$sourceLayout], source:[$source]") + + views.value = when { + // Check if the source is a bundled widget + BundledWidgetSourceIds.ids.contains(source) -> { + // Determine if the source requires additional widget types + val supportTreeLayout = source in setOf( + BundledWidgetSourceIds.FAVORITE, + BundledWidgetSourceIds.RECENT, + BundledWidgetSourceIds.RECENT_LOCAL ) - } else { - views.value = listOf( - WidgetTypeView.CompactList().setIsSelected(currentType), - WidgetTypeView.List().setIsSelected(currentType) + + // Base widget types for bundled sources + val defaultWidgetTypes = listOf( + WidgetTypeView.CompactList(), + WidgetTypeView.List() ) + + // Add Tree widget type if it's an extended source + val widgetList = if (supportTreeLayout) { + defaultWidgetTypes + WidgetTypeView.Tree() + } else { + defaultWidgetTypes + } + + // Set the selected state + widgetList.map { it.setIsSelected(currentType) } } - } else { - val objectLayout = ObjectType.Layout.entries.find { it.code == sourceLayout } - if (objectLayout.isDataView()) { - views.value = listOf( - WidgetTypeView.View().setIsSelected(currentType), - WidgetTypeView.Link().setIsSelected(currentType) - ) - } else if (objectLayout == ObjectType.Layout.PARTICIPANT) { - views.value = listOf( - WidgetTypeView.Link().setIsSelected(currentType) - ) - } else { - views.value = views.value.map { view -> view.setIsSelected(currentType) } + + // Handle non-bundled widget sources + else -> { + // Find the corresponding ObjectType.Layout based on sourceLayout + val objectLayout = ObjectType.Layout.entries.find { it.code == sourceLayout } + + when { + // If the layout is a data view, provide View and Link widgets + objectLayout?.isDataView() == true -> listOf( + WidgetTypeView.View(), + WidgetTypeView.Link() + ) + + // If the layout is PARTICIPANT, provide only the Link widget + objectLayout == ObjectType.Layout.PARTICIPANT -> listOf( + WidgetTypeView.Link() + ) + + // If the layout is DATE, provide only the Link widget + objectLayout == ObjectType.Layout.DATE -> listOf( + WidgetTypeView.Link() + ) + + // For other layouts, update existing views with the selected state + else -> views.value + }.map { it.setIsSelected(currentType) } } } } fun onStartForNewWidget(layout: Int, source: Id) { - Timber.d("onStart for new widget") - if (BundledWidgetSourceIds.ids.contains(source)) { - if (source == BundledWidgetSourceIds.FAVORITE - || source == BundledWidgetSourceIds.RECENT - || source == BundledWidgetSourceIds.RECENT_LOCAL - ) { - views.value = listOf( - WidgetTypeView.CompactList(isSelected = false), - WidgetTypeView.List(isSelected = false), - WidgetTypeView.Tree(isSelected = false), + Timber.d("onStartForNewWidget, layout:[$layout], source:[$source]") + + views.value = when { + // Check if the source is a bundled widget + BundledWidgetSourceIds.ids.contains(source) -> { + // Determine if the source requires additional widget types + val supportTreeLayout = source in setOf( + BundledWidgetSourceIds.FAVORITE, + BundledWidgetSourceIds.RECENT, + BundledWidgetSourceIds.RECENT_LOCAL ) - } else { - views.value = listOf( + + // Base widget types for bundled sources + val baseWidgets = listOf( WidgetTypeView.CompactList(isSelected = false), - WidgetTypeView.List(isSelected = false), + WidgetTypeView.List(isSelected = false) ) + + // Add Tree widget type if it's an extended source + val widgetList = if (supportTreeLayout) { + baseWidgets + WidgetTypeView.Tree(isSelected = false) + } else { + baseWidgets + } + + widgetList } - } else { - val objectLayout = ObjectType.Layout.entries.find { it.code == layout } - if (objectLayout.isDataView()) { - views.value = listOf( - WidgetTypeView.View(isSelected = false), - WidgetTypeView.Link(isSelected = false) - ) - } else if (objectLayout == ObjectType.Layout.PARTICIPANT) { - views.value = listOf( - WidgetTypeView.Link(isSelected = true) - ) - } else { - views.value = listOf( - WidgetTypeView.Tree(isSelected = false), - WidgetTypeView.Link(isSelected = false) - ) + + // Handle non-bundled widget sources + else -> { + // Find the corresponding ObjectType.Layout based on layout + val objectLayout = ObjectType.Layout.entries.find { it.code == layout } + + when { + // If the layout is a data view, provide View and Link widgets + objectLayout?.isDataView() == true -> listOf( + WidgetTypeView.View(isSelected = false), + WidgetTypeView.Link(isSelected = false) + ) + + // If the layout is PARTICIPANT or DATE, provide only the Link widget (selected) + objectLayout == ObjectType.Layout.PARTICIPANT || + objectLayout == ObjectType.Layout.DATE -> listOf( + WidgetTypeView.Link(isSelected = true) + ) + + // For other layouts, provide Tree and Link widgets (not selected) + else -> listOf( + WidgetTypeView.Tree(isSelected = false), + WidgetTypeView.Link(isSelected = false) + ) + } } } } @@ -159,6 +202,8 @@ class SelectWidgetTypeViewModel( ) } } + } else { + isDismissed.value = true } } @@ -170,6 +215,7 @@ class SelectWidgetTypeViewModel( target: Id?, view: WidgetTypeView ) { + Timber.d("onWidgetTypeClicked, source:[$source], target:[$target], view:[$view]") if (!view.isSelected) { viewModelScope.launch { widgetDispatcher.send( diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/TreeWidgetContainer.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/TreeWidgetContainer.kt index 006ba57448..41842c2504 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/TreeWidgetContainer.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/TreeWidgetContainer.kt @@ -52,7 +52,13 @@ class TreeWidgetContainer( source = widget.source, isExpanded = !isCollapsed, elements = emptyList(), - isLoading = true + isLoading = true, + name = when(val source = widget.source) { + is Widget.Source.Bundled -> WidgetView.Name.Bundled(source = source) + is Widget.Source.Default -> WidgetView.Name.Default( + prettyPrintName = source.obj.getWidgetObjectName() + ) + } ) if (isCollapsed) { emit(loadingStateView) @@ -113,7 +119,8 @@ class TreeWidgetContainer( path = widget.id + SEPARATOR + widget.source.id + SEPARATOR, data = data, rootLimit = rootLevelLimit - ) + ), + name = WidgetView.Name.Bundled(source = source) ) } } @@ -152,6 +159,9 @@ class TreeWidgetContainer( path = widget.id + SEPARATOR + widget.source.id + SEPARATOR, data = data, rootLimit = WidgetConfig.NO_LIMIT + ), + name = WidgetView.Name.Default( + prettyPrintName = source.obj.getWidgetObjectName() ) ) } @@ -260,6 +270,8 @@ class TreeWidgetContainer( val isExpandable = level < MAX_INDENT add( WidgetView.Tree.Element( + id = obj.id, + obj = obj, elementIcon = resolveObjectIcon( obj = obj, isExpandable = isExpandable, @@ -270,8 +282,10 @@ class TreeWidgetContainer( builder = urlBuilder ), indent = level, - obj = obj, - path = path + link + path = path + link, + name = WidgetView.Name.Default( + prettyPrintName = obj.getWidgetObjectName() + ) ) ) if (isExpandable && expanded.contains(currentLinkPath)) { diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/Widget.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/Widget.kt index 1ec6c6786b..3cfb3ef2ac 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/Widget.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/Widget.kt @@ -7,7 +7,7 @@ import com.anytypeio.anytype.core_models.ObjectWrapper import com.anytypeio.anytype.core_models.Relations import com.anytypeio.anytype.core_models.Struct import com.anytypeio.anytype.core_models.ext.asMap -import com.anytypeio.anytype.presentation.objects.SupportedLayouts +import com.anytypeio.anytype.core_models.SupportedLayouts.isSupportedForWidgets sealed class Widget { @@ -61,7 +61,9 @@ sealed class Widget { abstract val id: Id abstract val type: Id? - data class Default(val obj: ObjectWrapper.Basic) : Source() { + data class Default( + val obj: ObjectWrapper.Basic + ) : Source() { override val id: Id = obj.id override val type: Id? = obj.type.firstOrNull() } @@ -95,15 +97,9 @@ sealed class Widget { } } -fun Widget.hasValidLayout() : Boolean { - return when (val widgetSource = source) { - is Widget.Source.Default -> { - widgetSource.obj.layout != null && SupportedLayouts.layouts.contains(widgetSource.obj.layout) - } - is Widget.Source.Bundled -> { - true - } - } +fun Widget.hasValidLayout(): Boolean = when (val widgetSource = source) { + is Widget.Source.Default -> isSupportedForWidgets(widgetSource.obj.layout) + is Widget.Source.Bundled -> true } fun List.parseActiveViews() : WidgetToActiveView { @@ -139,9 +135,7 @@ fun List.parseWidgets( val source = if (BundledWidgetSourceIds.ids.contains(target)) { target.bundled() } else { - Widget.Source.Default( - ObjectWrapper.Basic(raw) - ) + Widget.Source.Default(ObjectWrapper.Basic(raw)) } val hasValidSource = when(source) { is Widget.Source.Bundled -> true diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/WidgetConfig.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/WidgetConfig.kt index 730f150318..2a816e435a 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/WidgetConfig.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/WidgetConfig.kt @@ -2,7 +2,7 @@ package com.anytypeio.anytype.presentation.widgets import com.anytypeio.anytype.core_models.ObjectTypeIds import com.anytypeio.anytype.core_models.ObjectWrapper -import com.anytypeio.anytype.presentation.objects.SupportedLayouts +import com.anytypeio.anytype.core_models.SupportedLayouts object WidgetConfig { val excludedTypes = listOf( diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/WidgetView.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/WidgetView.kt index 15d9f4558a..2f91916d63 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/WidgetView.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/WidgetView.kt @@ -12,9 +12,15 @@ import com.anytypeio.anytype.presentation.spaces.SpaceIconView sealed class WidgetView { + sealed interface Name { + data class Bundled(val source: Widget.Source.Bundled): Name + data class Default(val prettyPrintName: String?): Name + } + interface Element { val objectIcon: ObjectIcon val obj: ObjectWrapper.Basic + val name: Name.Default } abstract val id: Id @@ -23,17 +29,23 @@ sealed class WidgetView { data class Tree( override val id: Id, override val isLoading: Boolean = false, + val name: Name, val source: Widget.Source, val elements: List = emptyList(), val isExpanded: Boolean = false, val isEditable: Boolean = true ) : WidgetView(), Draggable { + /** + * @property [obj] is deprecated + */ data class Element( + val id: Id, + val obj: ObjectWrapper.Basic, val elementIcon: ElementIcon, val objectIcon: ObjectIcon = ObjectIcon.None, val indent: Indent, - val obj: ObjectWrapper.Basic, - val path: String + val path: String, + val name: Name.Default ) sealed class ElementIcon { @@ -47,6 +59,7 @@ sealed class WidgetView { data class Link( override val id: Id, override val isLoading: Boolean = false, + val name: Name, val source: Widget.Source, ) : WidgetView(), Draggable @@ -57,8 +70,9 @@ sealed class WidgetView { val tabs: List, val elements: List, val isExpanded: Boolean, - val isCompact: Boolean = false - ) : WidgetView(), Draggable { + val isCompact: Boolean = false, + val name: Name + ) : WidgetView(), Draggable { data class Tab( val id: Id, val name: String, @@ -67,6 +81,7 @@ sealed class WidgetView { data class Element( override val objectIcon: ObjectIcon, override val obj: ObjectWrapper.Basic, + override val name: Name.Default, val cover: CoverView? = null ) : WidgetView.Element } @@ -75,6 +90,7 @@ sealed class WidgetView { override val id: Id, override val isLoading: Boolean = false, val view: Id? = null, + val name: Name, val source: Widget.Source, val tabs: List, val elements: List, @@ -94,7 +110,8 @@ sealed class WidgetView { ) : WidgetView(), Draggable { data class Element( override val objectIcon: ObjectIcon, - override val obj: ObjectWrapper.Basic + override val obj: ObjectWrapper.Basic, + override val name: Name.Default ) : WidgetView.Element sealed class Type { data object Recent : Type() @@ -155,6 +172,7 @@ sealed class DropDownMenuAction { data object EmptyBin: DropDownMenuAction() } +// TODO extend to support date object name or consider creating another extension function fun ObjectWrapper.Basic.getWidgetObjectName(): String? { return if (layout == ObjectType.Layout.NOTE) { snippet?.trim()?.ifEmpty { null } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/collection/CollectionViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/collection/CollectionViewModel.kt index 330b183fe3..d7aa7375c8 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/collection/CollectionViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/collection/CollectionViewModel.kt @@ -39,6 +39,7 @@ import com.anytypeio.anytype.domain.objects.DeleteObjects import com.anytypeio.anytype.domain.objects.SetObjectListIsArchived import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes import com.anytypeio.anytype.domain.page.CreateObject +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.spaces.GetSpaceView import com.anytypeio.anytype.domain.workspace.SpaceManager import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate @@ -50,7 +51,6 @@ import com.anytypeio.anytype.presentation.home.navigation import com.anytypeio.anytype.presentation.navigation.DefaultObjectView import com.anytypeio.anytype.presentation.objects.ObjectAction import com.anytypeio.anytype.presentation.objects.getCreateObjectParams -import com.anytypeio.anytype.presentation.objects.getProperName import com.anytypeio.anytype.presentation.objects.mapFileObjectToView import com.anytypeio.anytype.presentation.objects.toViews import com.anytypeio.anytype.presentation.search.ObjectSearchConstants @@ -58,6 +58,7 @@ import com.anytypeio.anytype.presentation.search.Subscriptions import com.anytypeio.anytype.presentation.util.Dispatcher import com.anytypeio.anytype.presentation.widgets.collection.CollectionView.FavoritesView import com.anytypeio.anytype.presentation.widgets.collection.CollectionView.ObjectView +import com.anytypeio.anytype.presentation.widgets.collection.CollectionViewModel.Command.* import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -107,7 +108,8 @@ class CollectionViewModel( private val getSpaceView: GetSpaceView, private val dateTypeNameProvider: DateTypeNameProvider, private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate, - private val userPermissionProvider: UserPermissionProvider + private val userPermissionProvider: UserPermissionProvider, + private val fieldParser: FieldParser ) : ViewModel(), Reducer, AnalyticSpaceHelperDelegate by analyticSpaceHelperDelegate { val payloads: Flow @@ -273,17 +275,18 @@ class CollectionViewModel( ), sorts = subscription.sorts, limit = subscription.limit - ) + ), + fieldParser = fieldParser ) } else { - subscriptionFlow(buildSearchParams()) + subscriptionFlow(buildSearchParams(), fieldParser) } } Subscription.Files -> { filesSubscriptionFlow() } else -> { - subscriptionFlow(buildSearchParams()) + subscriptionFlow(buildSearchParams(), fieldParser) } } .map { update -> preserveSelectedState(update) } @@ -311,7 +314,8 @@ class CollectionViewModel( @OptIn(FlowPreview::class) private suspend fun subscriptionFlow( - params: StoreSearchParams + params: StoreSearchParams, + fieldParser: FieldParser ) = combine( container.subscribe(params).map { results -> results.distinctBy { it.id } }, @@ -320,11 +324,12 @@ class CollectionViewModel( ) { objects, query, types -> val filteredResults = objects.filter { obj -> - obj.getProperName().contains(query, ignoreCase = true) + val name = fieldParser.getObjectName(obj) + name.contains(query, ignoreCase = true) } val views = filteredResults - .toViews(urlBuilder = urlBuilder, objectTypes = types) + .toViews(urlBuilder = urlBuilder, objectTypes = types, fieldParser = fieldParser) .map { ObjectView(it) } .tryAddSections() @@ -404,9 +409,10 @@ class CollectionViewModel( details = favoritesObj.details ) return objs.toOrder(favs).filter { obj -> - obj.getProperName().lowercase().contains(query.lowercase(), true) + val name = fieldParser.getObjectName(obj) + name.lowercase().contains(query.lowercase(), true) } - .toViews(urlBuilder, types) + .toViews(urlBuilder, types, fieldParser) .map { FavoritesView(it, favs[it.id]?.blockId ?: "") } } @@ -912,6 +918,14 @@ class CollectionViewModel( OpenObjectNavigation.NonValidObject -> { toasts.emit("Object id is missing") } + is OpenObjectNavigation.OpenDataObject -> { + commands.emit( + OpenDateObject( + target = navigation.target, + space = navigation.space + ) + ) + } } } @@ -937,8 +951,10 @@ class CollectionViewModel( query: String ): List { return objects - .filter { it.getProperName().contains(query, true) } - .map { it.mapFileObjectToView() } + .filter { + val name = fieldParser.getObjectName(it) + name.contains(query, true) } + .map { it.mapFileObjectToView(fieldParser) } .tryAddSections() } @@ -972,7 +988,8 @@ class CollectionViewModel( private val getSpaceView: GetSpaceView, private val dateTypeNameProvider: DateTypeNameProvider, private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate, - private val userPermissionProvider: UserPermissionProvider + private val userPermissionProvider: UserPermissionProvider, + private val fieldParser: FieldParser ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") @@ -1000,7 +1017,8 @@ class CollectionViewModel( dateTypeNameProvider = dateTypeNameProvider, analyticSpaceHelperDelegate = analyticSpaceHelperDelegate, userPermissionProvider = userPermissionProvider, - vmParams = vmParams + vmParams = vmParams, + fieldParser = fieldParser ) as T } } @@ -1011,6 +1029,7 @@ class CollectionViewModel( data class OpenCollection(val subscription: Subscription, val space: Id) : Command() data class LaunchObjectSet(val target: Id, val space: Id) : Command() data class OpenChat(val target: Id, val space: Id) : Command() + data class OpenDateObject(val target: Id, val space: Id) : Command() data object ToDesktop : Command() data class ToSearch(val space: Id) : Command() diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/collections/CollectionAddRelationTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/collections/CollectionAddRelationTest.kt index 4df9b2278b..27285313df 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/collections/CollectionAddRelationTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/collections/CollectionAddRelationTest.kt @@ -13,8 +13,8 @@ import com.anytypeio.anytype.core_models.Relations import com.anytypeio.anytype.core_models.Relations.ID import com.anytypeio.anytype.core_models.Relations.LAYOUT import com.anytypeio.anytype.core_models.StubRelationObject +import com.anytypeio.anytype.domain.primitives.FieldParserImpl import com.anytypeio.anytype.presentation.objects.ObjectIcon -import com.anytypeio.anytype.presentation.objects.getProperName import com.anytypeio.anytype.presentation.relations.model.DefaultObjectRelationValueView import com.anytypeio.anytype.presentation.sets.DataViewViewState import com.anytypeio.anytype.presentation.sets.ObjectSetViewModel @@ -48,6 +48,7 @@ class CollectionAddRelationTest : ObjectSetViewModelTestSetup() { @Before fun setup() { MockitoAnnotations.openMocks(this) + fieldParser = FieldParserImpl(dateProvider, logger) objectCollection = MockCollection(context = root, space = defaultSpace) viewModel = givenViewModel() stubNetworkMode() @@ -132,7 +133,7 @@ class CollectionAddRelationTest : ObjectSetViewModelTestSetup() { items = listOf( Viewer.ListView.Item.Default( objectId = objectCollection.obj1.id, - name = objectCollection.obj1.getProperName(), + name = objectCollection.obj1.name.orEmpty(), description = objectCollection.obj1.description, hideIcon = false, icon = ObjectIcon.Empty.Page, @@ -153,7 +154,7 @@ class CollectionAddRelationTest : ObjectSetViewModelTestSetup() { ), Viewer.ListView.Item.Default( objectId = objectCollection.obj2.id, - name = objectCollection.obj2.getProperName(), + name = objectCollection.obj2.name.orEmpty(), description = objectCollection.obj2.description, hideIcon = false, icon = ObjectIcon.Empty.Page, diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/collections/CollectionCreateAndAddObjectTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/collections/CollectionCreateAndAddObjectTest.kt index fde16a6380..bec490f5e7 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/collections/CollectionCreateAndAddObjectTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/collections/CollectionCreateAndAddObjectTest.kt @@ -1,37 +1,24 @@ package com.anytypeio.anytype.presentation.collections import android.util.Log -import app.cash.turbine.test -import com.anytypeio.anytype.core_models.Command -import com.anytypeio.anytype.core_models.InternalFlags import com.anytypeio.anytype.core_models.StubConfig import com.anytypeio.anytype.core_models.primitives.SpaceId -import com.anytypeio.anytype.core_models.primitives.TypeKey import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers import com.anytypeio.anytype.domain.dataview.interactor.CreateDataViewObject -import com.anytypeio.anytype.domain.misc.DateProvider import com.anytypeio.anytype.domain.objects.DefaultStoreOfObjectTypes +import com.anytypeio.anytype.domain.primitives.FieldParserImpl import com.anytypeio.anytype.domain.search.DataViewSubscriptionContainer import com.anytypeio.anytype.domain.workspace.SpaceManager -import com.anytypeio.anytype.presentation.sets.DataViewViewState import com.anytypeio.anytype.presentation.sets.ObjectSetViewModel import com.anytypeio.anytype.presentation.sets.main.ObjectSetViewModelTestSetup import com.anytypeio.anytype.presentation.sets.subscription.DefaultDataViewSubscription -import com.anytypeio.anytype.test_utils.MockDataFactory -import kotlin.test.assertIs import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.advanceUntilIdle -import kotlinx.coroutines.test.runTest import net.lachlanmckee.timberjunit.TimberTestRule import org.junit.After import org.junit.Before import org.junit.Rule -import org.junit.Test -import org.mockito.Mock import org.mockito.MockitoAnnotations import org.mockito.kotlin.mock -import org.mockito.kotlin.times -import org.mockito.kotlin.verifyBlocking @OptIn(ExperimentalCoroutinesApi::class) class CollectionCreateAndAddObjectTest: ObjectSetViewModelTestSetup() { @@ -51,6 +38,7 @@ class CollectionCreateAndAddObjectTest: ObjectSetViewModelTestSetup() { fun setup() { MockitoAnnotations.openMocks(this) mockObjectCollection = MockCollection(context = root, space = defaultSpace) + fieldParser = FieldParserImpl(dateProvider, logger) repo = mock(verboseLogging = true) dispatchers = AppCoroutineDispatchers( io = rule.dispatcher, @@ -119,7 +107,8 @@ class CollectionCreateAndAddObjectTest: ObjectSetViewModelTestSetup() { permissions = permissions, analyticSpaceHelperDelegate = analyticSpaceHelperDelegate, spaceSyncAndP2PStatusProvider = spaceSyncAndP2PStatusProvider, - clearLastOpenedObject = clearLastOpenedObject + clearLastOpenedObject = clearLastOpenedObject, + fieldParser = fieldParser ) stubNetworkMode() stubObservePermissions() diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/collections/CollectionDataViewUpdateTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/collections/CollectionDataViewUpdateTest.kt index ccde77853c..9bef84b8d2 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/collections/CollectionDataViewUpdateTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/collections/CollectionDataViewUpdateTest.kt @@ -7,6 +7,7 @@ import com.anytypeio.anytype.core_models.DVSortType import com.anytypeio.anytype.core_models.Event import com.anytypeio.anytype.core_models.Payload import com.anytypeio.anytype.core_models.primitives.SpaceId +import com.anytypeio.anytype.domain.primitives.FieldParserImpl import com.anytypeio.anytype.presentation.sets.ObjectSetViewModel import com.anytypeio.anytype.presentation.sets.main.ObjectSetViewModelTestSetup import com.anytypeio.anytype.presentation.sets.state.ObjectState @@ -29,6 +30,7 @@ class CollectionDataViewUpdateTest : ObjectSetViewModelTestSetup() { @Before fun setup() { MockitoAnnotations.openMocks(this) + fieldParser = FieldParserImpl(dateProvider, logger) viewModel = givenViewModel() objectCollection = MockCollection(context = root, space = defaultSpace) stubGetDefaultPageType() diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/collections/ObjectCreateTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/collections/ObjectCreateTest.kt index aa676e2410..5d935be2c9 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/collections/ObjectCreateTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/collections/ObjectCreateTest.kt @@ -10,6 +10,7 @@ import com.anytypeio.anytype.core_models.primitives.TypeKey import com.anytypeio.anytype.domain.base.Resultat import com.anytypeio.anytype.domain.dataview.interactor.CreateDataViewObject import com.anytypeio.anytype.domain.page.CloseBlock +import com.anytypeio.anytype.domain.primitives.FieldParserImpl import com.anytypeio.anytype.presentation.sets.ObjectSetViewModel import com.anytypeio.anytype.presentation.sets.main.ObjectSetViewModelTestSetup import com.anytypeio.anytype.presentation.sets.state.ObjectState @@ -36,6 +37,7 @@ class ObjectCreateTest : ObjectSetViewModelTestSetup() { @Before fun setup() { MockitoAnnotations.openMocks(this) + fieldParser = FieldParserImpl(dateProvider, logger) viewModel = givenViewModel() stubNetworkMode() stubCreateDataViewObject() diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/collections/ObjectStateCollectionViewTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/collections/ObjectStateCollectionViewTest.kt index e720095d1b..6b48194ba1 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/collections/ObjectStateCollectionViewTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/collections/ObjectStateCollectionViewTest.kt @@ -1,18 +1,16 @@ package com.anytypeio.anytype.presentation.collections -import app.cash.turbine.testIn -import app.cash.turbine.turbineScope import app.cash.turbine.turbineScope import com.anytypeio.anytype.core_models.DV import com.anytypeio.anytype.core_models.ObjectType import com.anytypeio.anytype.core_models.ObjectTypeIds import com.anytypeio.anytype.core_models.Relations import com.anytypeio.anytype.core_models.StubObject -import com.anytypeio.anytype.core_models.primitives.SpaceId import com.anytypeio.anytype.core_models.primitives.TypeKey import com.anytypeio.anytype.core_models.restrictions.DataViewRestriction import com.anytypeio.anytype.core_models.restrictions.DataViewRestrictions -import com.anytypeio.anytype.presentation.objects.SupportedLayouts +import com.anytypeio.anytype.core_models.SupportedLayouts +import com.anytypeio.anytype.domain.primitives.FieldParserImpl import com.anytypeio.anytype.presentation.sets.DataViewViewState import com.anytypeio.anytype.presentation.sets.ObjectSetViewModel import com.anytypeio.anytype.presentation.sets.SetOrCollectionHeaderState @@ -42,6 +40,7 @@ class ObjectStateCollectionViewTest : ObjectSetViewModelTestSetup() { @Before fun setup() { MockitoAnnotations.openMocks(this) + fieldParser = FieldParserImpl(dateProvider, logger) viewModel = givenViewModel() mockObjectCollection = MockCollection(context = root, space = defaultSpace) stubGetDefaultPageType() diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/collections/ObjectStateSetViewTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/collections/ObjectStateSetViewTest.kt index 7a365c67de..173e3d5e74 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/collections/ObjectStateSetViewTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/collections/ObjectStateSetViewTest.kt @@ -10,6 +10,7 @@ import com.anytypeio.anytype.core_models.ObjectWrapper import com.anytypeio.anytype.core_models.Relations import com.anytypeio.anytype.core_models.StubObject import com.anytypeio.anytype.core_models.primitives.SpaceId +import com.anytypeio.anytype.domain.primitives.FieldParserImpl import com.anytypeio.anytype.presentation.relations.ObjectSetConfig import com.anytypeio.anytype.presentation.search.ObjectSearchConstants import com.anytypeio.anytype.presentation.sets.DataViewViewState @@ -41,6 +42,7 @@ class ObjectStateSetViewTest : ObjectSetViewModelTestSetup() { @Before fun setup() { MockitoAnnotations.openMocks(this) + fieldParser = FieldParserImpl(dateProvider, logger) viewModel = givenViewModel() mockObjectSet = MockSet(context = root, space = defaultSpace) stubNetworkMode() diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/DefaultBlockViewRendererTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/DefaultBlockViewRendererTest.kt index 86678c7050..43f4d57b8c 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/DefaultBlockViewRendererTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/DefaultBlockViewRendererTest.kt @@ -18,11 +18,15 @@ import com.anytypeio.anytype.core_models.ext.parseThemeTextColor import com.anytypeio.anytype.core_models.restrictions.ObjectRestriction import com.anytypeio.anytype.core_utils.const.DetailsKeys import com.anytypeio.anytype.domain.config.Gateway +import com.anytypeio.anytype.domain.debugging.Logger import com.anytypeio.anytype.domain.editor.Editor +import com.anytypeio.anytype.domain.misc.DateProvider import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.objects.DefaultStoreOfObjectTypes import com.anytypeio.anytype.domain.objects.DefaultStoreOfRelations import com.anytypeio.anytype.domain.objects.StoreOfRelations +import com.anytypeio.anytype.domain.primitives.FieldParser +import com.anytypeio.anytype.domain.primitives.FieldParserImpl import com.anytypeio.anytype.emojifier.data.DefaultDocumentEmojiIconProvider import com.anytypeio.anytype.presentation.MockBlockContentFactory.StubLinkContent import com.anytypeio.anytype.presentation.MockBlockFactory.link @@ -95,6 +99,14 @@ class DefaultBlockViewRendererTest { @Mock lateinit var coverImageHashProvider: CoverImageHashProvider + @Mock + lateinit var dateProvider: DateProvider + + @Mock + lateinit var logger: Logger + + lateinit var fieldParser: FieldParser + private lateinit var renderer: DefaultBlockViewRenderer private lateinit var wrapper: BlockViewRenderWrapper @@ -106,12 +118,14 @@ class DefaultBlockViewRendererTest { @Before fun setup() { MockitoAnnotations.openMocks(this) + fieldParser = FieldParserImpl(dateProvider, logger) renderer = DefaultBlockViewRenderer( urlBuilder = UrlBuilder(gateway), toggleStateHolder = toggleStateHolder, coverImageHashProvider = coverImageHashProvider, storeOfRelations = storeOfRelations, - storeOfObjectTypes = storeOfObjectTypes + storeOfObjectTypes = storeOfObjectTypes, + fieldParser = fieldParser ) } diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/EditorViewModelTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/EditorViewModelTest.kt index 9544d57c82..d438e6e30e 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/EditorViewModelTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/EditorViewModelTest.kt @@ -64,6 +64,7 @@ import com.anytypeio.anytype.domain.event.interactor.SpaceSyncAndP2PStatusProvid import com.anytypeio.anytype.domain.icon.SetDocumentImageIcon import com.anytypeio.anytype.domain.launch.GetDefaultObjectType import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer +import com.anytypeio.anytype.domain.misc.DateProvider import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.multiplayer.UserPermissionProvider import com.anytypeio.anytype.domain.networkmode.GetNetworkMode @@ -73,6 +74,7 @@ import com.anytypeio.anytype.domain.`object`.SetObjectInternalFlags import com.anytypeio.anytype.domain.`object`.UpdateDetail import com.anytypeio.anytype.domain.objects.DefaultStoreOfObjectTypes import com.anytypeio.anytype.domain.objects.DefaultStoreOfRelations +import com.anytypeio.anytype.domain.objects.GetDateObjectByTimestamp import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes import com.anytypeio.anytype.domain.objects.StoreOfRelations import com.anytypeio.anytype.domain.page.CloseBlock @@ -85,6 +87,7 @@ import com.anytypeio.anytype.domain.page.Undo import com.anytypeio.anytype.domain.page.UpdateTitle import com.anytypeio.anytype.domain.page.bookmark.CreateBookmarkBlock import com.anytypeio.anytype.domain.page.bookmark.SetupBookmark +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.relations.AddRelationToObject import com.anytypeio.anytype.domain.relations.SetRelationKey import com.anytypeio.anytype.domain.search.SearchObjects @@ -323,6 +326,9 @@ open class EditorViewModelTest { @Mock lateinit var fillTableRow: FillTableRow + @Mock + lateinit var getDateObjectByTimestamp: GetDateObjectByTimestamp + @Mock lateinit var templatesContainer: ObjectTypeTemplatesContainer @@ -364,6 +370,9 @@ open class EditorViewModelTest { @Mock lateinit var spaceSyncAndP2PStatusProvider: SpaceSyncAndP2PStatusProvider + @Mock + lateinit var fieldParser: FieldParser + lateinit var vm: EditorViewModel lateinit var orchestrator: Orchestrator @@ -3848,6 +3857,9 @@ open class EditorViewModelTest { lateinit var addRelationToObject: AddRelationToObject + @Mock + lateinit var dateProvider: DateProvider + fun givenViewModel(urlBuilder: UrlBuilder = builder) { val storage = Editor.Storage() @@ -3929,7 +3941,8 @@ open class EditorViewModelTest { toggleStateHolder = ToggleStateHolder.Default(), coverImageHashProvider = coverImageHashProvider, storeOfRelations = storeOfRelations, - storeOfObjectTypes = storeOfObjectTypes + storeOfObjectTypes = storeOfObjectTypes, + fieldParser = fieldParser ), orchestrator = orchestrator, analytics = analytics, @@ -3968,7 +3981,10 @@ open class EditorViewModelTest { analyticSpaceHelperDelegate = analyticSpaceHelperDelegate, spaceSyncAndP2PStatusProvider = spaceSyncAndP2PStatusProvider, clearLastOpenedObject = clearLastOpenedObject, - getNetworkMode = getNetworkMode + getNetworkMode = getNetworkMode, + fieldParser = fieldParser, + dateProvider = dateProvider, + getDateObjectByTimestamp = getDateObjectByTimestamp ) } diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorBackButtonTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorBackButtonTest.kt index 6ba565be3a..5b21a920d5 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorBackButtonTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorBackButtonTest.kt @@ -30,12 +30,7 @@ class EditorBackButtonTest : EditorPresentationTestSetup() { @Before fun setup() { MockitoAnnotations.openMocks(this) - stubSpaceManager() - stubClosePage() - stubGetNetworkMode() - stubFileLimitEvents() - stubInterceptEvents() - stubAnalyticSpaceHelperDelegate() + proceedWithDefaultBeforeTestStubbing() } @Test diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorBackspaceDeleteTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorBackspaceDeleteTest.kt index 172b59ec7c..f1808d6a90 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorBackspaceDeleteTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorBackspaceDeleteTest.kt @@ -63,9 +63,6 @@ class EditorBackspaceDeleteTest : EditorPresentationTestSetup() { fun setup() { MockitoAnnotations.openMocks(this) proceedWithDefaultBeforeTestStubbing() - stubInterceptEvents() - stubInterceptThreadStatus() - stubClosePage() } @Test diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorBlockActionsTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorBlockActionsTest.kt index 1785b01412..c1e2ac6bcb 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorBlockActionsTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorBlockActionsTest.kt @@ -30,7 +30,6 @@ class EditorBlockActionsTest : EditorPresentationTestSetup() { fun setup() { MockitoAnnotations.openMocks(this) proceedWithDefaultBeforeTestStubbing() - stubInterceptEvents() } @Test diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorCheckboxTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorCheckboxTest.kt index 4c5f406725..a386f207d1 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorCheckboxTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorCheckboxTest.kt @@ -35,11 +35,7 @@ class EditorCheckboxTest : EditorPresentationTestSetup() { @Before fun setup() { MockitoAnnotations.openMocks(this) - stubSpaceManager() - stubGetNetworkMode() - stubFileLimitEvents() - stubInterceptEvents() - stubAnalyticSpaceHelperDelegate() + proceedWithDefaultBeforeTestStubbing() } @Test diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorCreateBlockTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorCreateBlockTest.kt index 521a9dcda8..0e80bc4a5a 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorCreateBlockTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorCreateBlockTest.kt @@ -34,8 +34,6 @@ class EditorCreateBlockTest : EditorPresentationTestSetup() { fun setup() { MockitoAnnotations.openMocks(this) proceedWithDefaultBeforeTestStubbing() - stubInterceptEvents() - stubInterceptThreadStatus() } val title = StubTitle() diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorEmptySpaceInteractionTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorEmptySpaceInteractionTest.kt index 4d57806f1c..05fabcf7c6 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorEmptySpaceInteractionTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorEmptySpaceInteractionTest.kt @@ -58,10 +58,7 @@ class EditorEmptySpaceInteractionTest : EditorPresentationTestSetup() { @Before fun setup() { MockitoAnnotations.openMocks(this) - stubSpaceManager() - stubGetNetworkMode() - stubFileLimitEvents() - stubAnalyticSpaceHelperDelegate() + proceedWithDefaultBeforeTestStubbing() } @Test diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorErrorMessageTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorErrorMessageTest.kt index 2159c31956..7bbcd44983 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorErrorMessageTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorErrorMessageTest.kt @@ -30,11 +30,7 @@ class EditorErrorMessageTest : EditorPresentationTestSetup() { @Before fun setup() { MockitoAnnotations.openMocks(this) - stubSpaceManager() - stubGetNetworkMode() - stubFileLimitEvents() - stubInterceptEvents() - stubAnalyticSpaceHelperDelegate() + proceedWithDefaultBeforeTestStubbing() } @Test diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorEventSubscriptionTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorEventSubscriptionTest.kt index 0ad3de526d..a8d36a4fc7 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorEventSubscriptionTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorEventSubscriptionTest.kt @@ -28,10 +28,7 @@ class EditorEventSubscriptionTest : EditorPresentationTestSetup() { @Before fun setup() { MockitoAnnotations.openMocks(this) - stubSpaceManager() - stubFileLimitEvents() - stubGetNetworkMode() - stubInterceptEvents() + proceedWithDefaultBeforeTestStubbing() stubAnalyticSpaceHelperDelegate() } diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorFocusTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorFocusTest.kt index b88f92f252..50ade054e1 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorFocusTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorFocusTest.kt @@ -61,11 +61,7 @@ class EditorFocusTest : EditorPresentationTestSetup() { @Before fun setup() { MockitoAnnotations.openMocks(this) - stubSpaceManager() - stubGetNetworkMode() - stubFileLimitEvents() - stubInterceptEvents() - stubAnalyticSpaceHelperDelegate() + proceedWithDefaultBeforeTestStubbing() } @Test diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorLatexBlockTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorLatexBlockTest.kt index 3f7aaba206..9124aca52c 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorLatexBlockTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorLatexBlockTest.kt @@ -29,7 +29,6 @@ class EditorLatexBlockTest : EditorPresentationTestSetup() { fun setup() { MockitoAnnotations.openMocks(this) proceedWithDefaultBeforeTestStubbing() - stubInterceptEvents() } val title = Block( diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorListBlockTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorListBlockTest.kt index 7f4bce499b..941263b434 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorListBlockTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorListBlockTest.kt @@ -48,8 +48,6 @@ class EditorListBlockTest : EditorPresentationTestSetup() { fun setup() { MockitoAnnotations.openMocks(this) proceedWithDefaultBeforeTestStubbing() - stubInterceptThreadStatus() - stubInterceptEvents() } @Test diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorLockPageTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorLockPageTest.kt index 5a42fd237a..99ed9b2561 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorLockPageTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorLockPageTest.kt @@ -273,9 +273,6 @@ class EditorLockPageTest : EditorPresentationTestSetup() { link ) - stubInterceptEvents() - stubInterceptThreadStatus() - stubClosePage() stubOpenDocument( document = page, details = Block.Details( @@ -313,7 +310,8 @@ class EditorLockPageTest : EditorPresentationTestSetup() { BlockView.Decoration( background = link.parseThemeBackgroundColor() ) - ) + ), + text = "" ) ) @@ -392,7 +390,8 @@ class EditorLockPageTest : EditorPresentationTestSetup() { target to Block.Fields( mapOf( Relations.ID to target, - Relations.LAYOUT to ObjectType.Layout.BASIC.code.toDouble() + Relations.LAYOUT to ObjectType.Layout.BASIC.code.toDouble(), + Relations.SPACE_ID to defaultSpace ) ) ) diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorMentionTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorMentionTest.kt index e36cfb27a9..b7188540dc 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorMentionTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorMentionTest.kt @@ -13,6 +13,7 @@ import com.anytypeio.anytype.domain.base.Result import com.anytypeio.anytype.domain.base.Resultat import com.anytypeio.anytype.domain.event.interactor.InterceptEvents import com.anytypeio.anytype.domain.icon.DocumentEmojiIconProvider +import com.anytypeio.anytype.domain.primitives.FieldParserImpl import com.anytypeio.anytype.presentation.editor.EditorViewModel import com.anytypeio.anytype.presentation.editor.editor.control.ControlPanelState import com.anytypeio.anytype.presentation.editor.editor.mention.MentionConst.MENTION_TITLE_EMPTY @@ -87,8 +88,6 @@ class EditorMentionTest : EditorPresentationTestSetup() { fun setup() { MockitoAnnotations.openMocks(this) proceedWithDefaultBeforeTestStubbing() - stubInterceptThreadStatus() - stubInterceptEvents() } @Test diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorMergeTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorMergeTest.kt index ddbf74d956..0aca5e0ea9 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorMergeTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorMergeTest.kt @@ -36,10 +36,7 @@ class EditorMergeTest : EditorPresentationTestSetup() { @Before fun setup() { MockitoAnnotations.openMocks(this) - stubSpaceManager() - stubGetNetworkMode() - stubFileLimitEvents() - stubAnalyticSpaceHelperDelegate() + proceedWithDefaultBeforeTestStubbing() } @Test diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorNoteLayoutTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorNoteLayoutTest.kt index 70acb725a0..c7329ee5ce 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorNoteLayoutTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorNoteLayoutTest.kt @@ -48,10 +48,7 @@ class EditorNoteLayoutTest : EditorPresentationTestSetup() { @Before fun setup() { MockitoAnnotations.openMocks(this) - stubSpaceManager() - stubGetNetworkMode() - stubFileLimitEvents() - stubAnalyticSpaceHelperDelegate() + proceedWithDefaultBeforeTestStubbing() } @OptIn(ExperimentalCoroutinesApi::class) diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorPresentationTestSetup.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorPresentationTestSetup.kt index 62a392dc99..94425a43f8 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorPresentationTestSetup.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorPresentationTestSetup.kt @@ -50,12 +50,14 @@ import com.anytypeio.anytype.domain.clipboard.Copy import com.anytypeio.anytype.domain.clipboard.Paste import com.anytypeio.anytype.domain.config.Gateway import com.anytypeio.anytype.domain.cover.SetDocCoverImage +import com.anytypeio.anytype.domain.debugging.Logger import com.anytypeio.anytype.domain.download.DownloadFile import com.anytypeio.anytype.domain.event.interactor.InterceptEvents import com.anytypeio.anytype.domain.event.interactor.SpaceSyncAndP2PStatusProvider import com.anytypeio.anytype.domain.icon.SetDocumentImageIcon import com.anytypeio.anytype.domain.launch.GetDefaultObjectType import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer +import com.anytypeio.anytype.domain.misc.DateProvider import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.multiplayer.UserPermissionProvider import com.anytypeio.anytype.domain.networkmode.GetNetworkMode @@ -65,6 +67,7 @@ import com.anytypeio.anytype.domain.`object`.SetObjectInternalFlags import com.anytypeio.anytype.domain.`object`.UpdateDetail import com.anytypeio.anytype.domain.objects.DefaultStoreOfObjectTypes import com.anytypeio.anytype.domain.objects.DefaultStoreOfRelations +import com.anytypeio.anytype.domain.objects.GetDateObjectByTimestamp import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes import com.anytypeio.anytype.domain.objects.StoreOfRelations import com.anytypeio.anytype.domain.page.CloseBlock @@ -76,6 +79,8 @@ import com.anytypeio.anytype.domain.page.Redo import com.anytypeio.anytype.domain.page.Undo import com.anytypeio.anytype.domain.page.bookmark.CreateBookmarkBlock import com.anytypeio.anytype.domain.page.bookmark.SetupBookmark +import com.anytypeio.anytype.domain.primitives.FieldParser +import com.anytypeio.anytype.domain.primitives.FieldParserImpl import com.anytypeio.anytype.domain.relations.AddRelationToObject import com.anytypeio.anytype.domain.relations.SetRelationKey import com.anytypeio.anytype.domain.search.SearchObjects @@ -214,6 +219,9 @@ open class EditorPresentationTestSetup { @Mock lateinit var copy: Copy + @Mock + lateinit var getDateObjectByTimestamp: GetDateObjectByTimestamp + @Mock lateinit var undo: Undo @@ -238,6 +246,8 @@ open class EditorPresentationTestSetup { @Mock lateinit var move: MoveOld + lateinit var fieldParser: FieldParser + @Mock lateinit var turnIntoDocument: TurnIntoDocument @@ -373,6 +383,12 @@ open class EditorPresentationTestSetup { @Mock lateinit var spaceSyncAndP2PStatusProvider: SpaceSyncAndP2PStatusProvider + @Mock + lateinit var dateProvider: DateProvider + + @Mock + lateinit var logger: Logger + var permissions: UserPermissionProvider = UserPermissionProviderStub() open fun buildViewModel(urlBuilder: UrlBuilder = builder): EditorViewModel { @@ -460,7 +476,8 @@ open class EditorPresentationTestSetup { toggleStateHolder = ToggleStateHolder.Default(), coverImageHashProvider = coverImageHashProvider, storeOfRelations = storeOfRelations, - storeOfObjectTypes = storeOfObjectTypes + storeOfObjectTypes = storeOfObjectTypes, + fieldParser = fieldParser ), orchestrator = orchestrator, analytics = analytics, @@ -499,7 +516,10 @@ open class EditorPresentationTestSetup { analyticSpaceHelperDelegate = analyticSpaceHelperDelegate, clearLastOpenedObject = clearLastOpenedObject, spaceSyncAndP2PStatusProvider = spaceSyncAndP2PStatusProvider, - getNetworkMode = getNetworkMode + getNetworkMode = getNetworkMode, + fieldParser = fieldParser, + dateProvider = dateProvider, + getDateObjectByTimestamp = getDateObjectByTimestamp ) } @@ -861,10 +881,14 @@ open class EditorPresentationTestSetup { } fun proceedWithDefaultBeforeTestStubbing() { + fieldParser = FieldParserImpl(dateProvider, logger) stubAnalyticSpaceHelperDelegate() stubSpaceManager() stubUserPermission() stubGetNetworkMode() stubFileLimitEvents() + stubInterceptEvents() + stubClosePage() + stubInterceptThreadStatus() } } \ No newline at end of file diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorRelationBlockTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorRelationBlockTest.kt index 44e994d1b4..65dcb6fc54 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorRelationBlockTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorRelationBlockTest.kt @@ -47,8 +47,6 @@ class EditorRelationBlockTest : EditorPresentationTestSetup() { fun setup() { MockitoAnnotations.openMocks(this) proceedWithDefaultBeforeTestStubbing() - stubInterceptEvents() - stubInterceptThreadStatus() } @Test diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorSlashWidgetActionsTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorSlashWidgetActionsTest.kt index f16d614267..88dfb0a964 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorSlashWidgetActionsTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorSlashWidgetActionsTest.kt @@ -42,14 +42,11 @@ class EditorSlashWidgetActionsTest : EditorPresentationTestSetup() { @Before fun setup() { MockitoAnnotations.openMocks(this) - stubSpaceManager() - stubGetNetworkMode() - stubFileLimitEvents() + proceedWithDefaultBeforeTestStubbing() stubUpdateText() stubDuplicateBlock("", emptyList()) stubCopy() stubPaste() - stubAnalyticSpaceHelperDelegate() } @OptIn(ExperimentalCoroutinesApi::class) diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorSlashWidgetColorTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorSlashWidgetColorTest.kt index 3fa9a71bc6..ab5e06f8d1 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorSlashWidgetColorTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorSlashWidgetColorTest.kt @@ -46,11 +46,8 @@ class EditorSlashWidgetColorTest : EditorPresentationTestSetup() { @Before fun setup() { MockitoAnnotations.openMocks(this) - stubSpaceManager() - stubGetNetworkMode() - stubFileLimitEvents() + proceedWithDefaultBeforeTestStubbing() stubUpdateText() - stubAnalyticSpaceHelperDelegate() } @OptIn(ExperimentalCoroutinesApi::class) diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorSlashWidgetCutFilterTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorSlashWidgetCutFilterTest.kt index 0e0f99ef19..cea2a6d3ff 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorSlashWidgetCutFilterTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorSlashWidgetCutFilterTest.kt @@ -39,11 +39,8 @@ class EditorSlashWidgetCutFilterTest : EditorPresentationTestSetup() { @Before fun setup() { MockitoAnnotations.openMocks(this) - stubSpaceManager() - stubGetNetworkMode() - stubFileLimitEvents() + proceedWithDefaultBeforeTestStubbing() stubGetObjectTypes(emptyList()) - stubAnalyticSpaceHelperDelegate() } @OptIn(ExperimentalCoroutinesApi::class) diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorSlashWidgetMarksTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorSlashWidgetMarksTest.kt index 6a11708e52..d69c328b96 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorSlashWidgetMarksTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorSlashWidgetMarksTest.kt @@ -48,10 +48,7 @@ class EditorSlashWidgetMarksTest : EditorPresentationTestSetup() { @Before fun setup() { MockitoAnnotations.openMocks(this) - stubSpaceManager() - stubGetNetworkMode() - stubFileLimitEvents() - stubAnalyticSpaceHelperDelegate() + proceedWithDefaultBeforeTestStubbing() } @After diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorSlashWidgetObjectTypeTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorSlashWidgetObjectTypeTest.kt index 8369676ae6..b9e54d27c0 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorSlashWidgetObjectTypeTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorSlashWidgetObjectTypeTest.kt @@ -16,7 +16,6 @@ import com.anytypeio.anytype.presentation.editor.editor.model.types.Types import com.anytypeio.anytype.presentation.editor.editor.slash.SlashEvent import com.anytypeio.anytype.presentation.editor.editor.slash.SlashItem import com.anytypeio.anytype.presentation.objects.ObjectTypeView -import com.anytypeio.anytype.presentation.objects.getProperName import com.anytypeio.anytype.presentation.util.DefaultCoroutineTestRule import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.advanceUntilIdle @@ -43,11 +42,7 @@ class EditorSlashWidgetObjectTypeTest : EditorPresentationTestSetup() { @Before fun setup() { MockitoAnnotations.openMocks(this) - stubSpaceManager(space = space) - stubGetNetworkMode() - stubFileLimitEvents() - stubClosePage() - stubAnalyticSpaceHelperDelegate() + proceedWithDefaultBeforeTestStubbing() } @OptIn(ExperimentalCoroutinesApi::class) @@ -99,7 +94,7 @@ class EditorSlashWidgetObjectTypeTest : EditorPresentationTestSetup() { SlashItem.ObjectType( objectTypeView = ObjectTypeView( id = type2.id, - name = type2.getProperName(), + name = type2.name.orEmpty(), description = type2.description, emoji = type2.iconEmoji, key = type2.getValue(Relations.UNIQUE_KEY)!! @@ -167,7 +162,7 @@ class EditorSlashWidgetObjectTypeTest : EditorPresentationTestSetup() { SlashItem.ObjectType( objectTypeView = ObjectTypeView( id = type2.id, - name = type2.getProperName(), + name = type2.name.orEmpty(), description = type2.description, emoji = type2.iconEmoji, key = type2.getValue(Relations.UNIQUE_KEY)!! diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorSlashWidgetShowHideTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorSlashWidgetShowHideTest.kt index 7e0b2236f2..e782ec12a3 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorSlashWidgetShowHideTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorSlashWidgetShowHideTest.kt @@ -37,10 +37,7 @@ class EditorSlashWidgetShowHideTest : EditorPresentationTestSetup() { @Before fun setup() { MockitoAnnotations.openMocks(this) - stubSpaceManager() - stubGetNetworkMode() - stubFileLimitEvents() - stubAnalyticSpaceHelperDelegate() + proceedWithDefaultBeforeTestStubbing() } @Test diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorSlashWidgetStyleTypeTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorSlashWidgetStyleTypeTest.kt index bf1f595dd1..1762754151 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorSlashWidgetStyleTypeTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorSlashWidgetStyleTypeTest.kt @@ -46,10 +46,7 @@ class EditorSlashWidgetStyleTypeTest : EditorPresentationTestSetup() { @Before fun setup() { MockitoAnnotations.openMocks(this) - stubSpaceManager() - stubGetNetworkMode() - stubFileLimitEvents() - stubAnalyticSpaceHelperDelegate() + proceedWithDefaultBeforeTestStubbing() } @OptIn(ExperimentalCoroutinesApi::class) diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorSplitTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorSplitTest.kt index a0c813e932..02b85b40eb 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorSplitTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorSplitTest.kt @@ -48,10 +48,7 @@ class EditorSplitTest : EditorPresentationTestSetup() { @Before fun setup() { MockitoAnnotations.openMocks(this) - stubSpaceManager() - stubGetNetworkMode() - stubFileLimitEvents() - stubAnalyticSpaceHelperDelegate() + proceedWithDefaultBeforeTestStubbing() } @After diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorTextUpdateTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorTextUpdateTest.kt index 48d6b97e49..c94e0f9e4c 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorTextUpdateTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorTextUpdateTest.kt @@ -35,10 +35,7 @@ class EditorTextUpdateTest : EditorPresentationTestSetup() { @Before fun setup() { MockitoAnnotations.openMocks(this) - stubSpaceManager() - stubGetNetworkMode() - stubFileLimitEvents() - stubAnalyticSpaceHelperDelegate() + proceedWithDefaultBeforeTestStubbing() } val title = Block( diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorTitleAddBlockTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorTitleAddBlockTest.kt index 52beb18100..f4e6c87922 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorTitleAddBlockTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorTitleAddBlockTest.kt @@ -32,11 +32,8 @@ class EditorTitleAddBlockTest : EditorPresentationTestSetup() { @Before fun setup() { MockitoAnnotations.openMocks(this) - stubSpaceManager() - stubGetNetworkMode() - stubFileLimitEvents() + proceedWithDefaultBeforeTestStubbing() stubCreateBlock(root) - stubAnalyticSpaceHelperDelegate() } val title = Block( diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorTitleTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorTitleTest.kt index 9852b309fa..67b1ebbff3 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorTitleTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorTitleTest.kt @@ -54,15 +54,10 @@ class EditorTitleTest : EditorPresentationTestSetup() { @Before fun setup() { MockitoAnnotations.openMocks(this) - stubSpaceManager() - stubGetNetworkMode() - stubUserPermission() - stubInterceptEvents() - stubFileLimitEvents() + proceedWithDefaultBeforeTestStubbing() stubCopy() stubSplitBlock() stubCreateBlock(root) - stubAnalyticSpaceHelperDelegate() } @After diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/table/EditorTableRowsColumnsDeleteTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/table/EditorTableRowsColumnsDeleteTest.kt index a806a3c100..0476dacd1a 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/table/EditorTableRowsColumnsDeleteTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/table/EditorTableRowsColumnsDeleteTest.kt @@ -37,10 +37,7 @@ class EditorTableRowsColumnsDeleteTest : EditorPresentationTestSetup() { @Before fun setup() { MockitoAnnotations.openMocks(this) - stubSpaceManager() - stubGetNetworkMode() - stubFileLimitEvents() - stubAnalyticSpaceHelperDelegate() + proceedWithDefaultBeforeTestStubbing() } private var tableId = MockDataFactory.randomUuid() diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/table/TableBlockRendererTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/table/TableBlockRendererTest.kt index b7b28018ba..428188bc71 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/table/TableBlockRendererTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/table/TableBlockRendererTest.kt @@ -22,7 +22,7 @@ import com.anytypeio.anytype.domain.editor.Editor import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.objects.DefaultStoreOfObjectTypes import com.anytypeio.anytype.domain.objects.DefaultStoreOfRelations -import com.anytypeio.anytype.presentation.BuildConfig +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.presentation.editor.cover.CoverImageHashProvider import com.anytypeio.anytype.presentation.editor.editor.model.BlockView import com.anytypeio.anytype.presentation.editor.render.BlockViewRenderer @@ -90,6 +90,9 @@ class TableBlockRendererTest { private val storeOfObjectTypes = DefaultStoreOfObjectTypes() + @Mock + lateinit var fieldParser: FieldParser + @Before fun setup() { MockitoAnnotations.openMocks(this) @@ -98,7 +101,8 @@ class TableBlockRendererTest { toggleStateHolder = toggleStateHolder, coverImageHashProvider = coverImageHashProvider, storeOfRelations = storeOfRelations, - storeOfObjectTypes = storeOfObjectTypes + storeOfObjectTypes = storeOfObjectTypes, + fieldParser = fieldParser ) } diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/extension/MapExtensionKtTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/extension/MapExtensionKtTest.kt deleted file mode 100644 index 0d06c4f125..0000000000 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/extension/MapExtensionKtTest.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.anytypeio.anytype.presentation.extension - -import com.anytypeio.anytype.core_models.Block -import org.junit.Test -import kotlin.test.assertEquals - -class MapExtensionKtTest { - - @Test - fun `should return updated map`() { - - val map = mapOf( - "id1" to Block.Fields(mapOf("name" to "Name1", "size" to 111)), - "id2" to Block.Fields(mapOf("name" to "Name2", "size" to 222)), - "id3" to Block.Fields(mapOf("name" to "Name3", "size" to 333)), - "id4" to Block.Fields(mapOf("name" to "Name4", "size" to 444)), - "id5" to Block.Fields(mapOf("name" to "Name5", "size" to 555)) - ) - - val update = mapOf( - "id2" to Block.Fields(mapOf("name" to "Name9", "size" to 234)), - "id4" to Block.Fields(mapOf("name" to "Name4", "size" to 874)), - "id6" to Block.Fields(mapOf("name" to "Name6", "size" to 675)) - ) - - val result = map.updateFields(update) - - val expected = mapOf( - "id1" to Block.Fields(mapOf("name" to "Name1", "size" to 111)), - "id2" to Block.Fields(mapOf("name" to "Name9", "size" to 234)), - "id3" to Block.Fields(mapOf("name" to "Name3", "size" to 333)), - "id4" to Block.Fields(mapOf("name" to "Name4", "size" to 874)), - "id5" to Block.Fields(mapOf("name" to "Name5", "size" to 555)), - "id6" to Block.Fields(mapOf("name" to "Name6", "size" to 675)) - ) - - assertEquals(expected = expected, actual = result) - } -} \ No newline at end of file diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/history/StubVersionModels.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/history/StubVersionModels.kt index c1ea31311c..41596e2e13 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/history/StubVersionModels.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/history/StubVersionModels.kt @@ -2,7 +2,7 @@ package com.anytypeio.anytype.presentation.history import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_models.history.Version -import com.anytypeio.anytype.core_models.primitives.TimeInSeconds +import com.anytypeio.anytype.core_models.primitives.TimestampInSeconds import kotlin.random.Random import net.bytebuddy.utility.RandomString @@ -11,7 +11,7 @@ fun StubVersion( previousIds: List = emptyList(), authorId: Id = "authorId - ${RandomString.make()}", authorName: String = "", - timestamp: TimeInSeconds, + timestamp: TimestampInSeconds, groupId: Long = Random(100).nextLong() ): Version { return Version( diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/history/VersionHistoryViewModelTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/history/VersionHistoryViewModelTest.kt index 7b3cff5447..ba1fabb240 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/history/VersionHistoryViewModelTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/history/VersionHistoryViewModelTest.kt @@ -7,7 +7,7 @@ import com.anytypeio.anytype.core_models.history.Version import com.anytypeio.anytype.core_models.multiplayer.ParticipantStatus import com.anytypeio.anytype.core_models.multiplayer.SpaceMemberPermissions import com.anytypeio.anytype.core_models.primitives.SpaceId -import com.anytypeio.anytype.core_models.primitives.TimeInSeconds +import com.anytypeio.anytype.core_models.primitives.TimestampInSeconds import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers import com.anytypeio.anytype.domain.base.Either import com.anytypeio.anytype.domain.base.Resultat @@ -162,23 +162,23 @@ class VersionHistoryViewModelTest { * Fri Dec 31 2021 23:59:57 GMT+0100 1640991597 User2 */ - private val timestamp0 = TimeInSeconds(1640991603L) - private val timestamp1 = TimeInSeconds(1640991602L) - private val timestamp2 = TimeInSeconds(1640991601L) - private val timestamp3 = TimeInSeconds(1640991600L) - private val timestamp4 = TimeInSeconds(1640991599L) - private val timestamp5 = TimeInSeconds(1640991598L) - private val timestamp6 = TimeInSeconds(1640991597L) + private val timestamp0 = TimestampInSeconds(1640991603L) + private val timestamp1 = TimestampInSeconds(1640991602L) + private val timestamp2 = TimestampInSeconds(1640991601L) + private val timestamp3 = TimestampInSeconds(1640991600L) + private val timestamp4 = TimestampInSeconds(1640991599L) + private val timestamp5 = TimestampInSeconds(1640991598L) + private val timestamp6 = TimestampInSeconds(1640991597L) - private val timestamp7 = TimeInSeconds(1641078000L) - private val timestamp8 = TimeInSeconds(1641078001L) - private val timestamp9 = TimeInSeconds(1641078003L) - private val timestamp10 = TimeInSeconds(1641078004L) - private val timestamp11 = TimeInSeconds(1641078059L) - private val timestamp12 = TimeInSeconds(1641078060L) - private val timestamp13 = TimeInSeconds(1641078061L) - private val timestamp14 = TimeInSeconds(1641078062L) - private val timestamp15 = TimeInSeconds(1641078120L) + private val timestamp7 = TimestampInSeconds(1641078000L) + private val timestamp8 = TimestampInSeconds(1641078001L) + private val timestamp9 = TimestampInSeconds(1641078003L) + private val timestamp10 = TimestampInSeconds(1641078004L) + private val timestamp11 = TimestampInSeconds(1641078059L) + private val timestamp12 = TimestampInSeconds(1641078060L) + private val timestamp13 = TimestampInSeconds(1641078061L) + private val timestamp14 = TimestampInSeconds(1641078062L) + private val timestamp15 = TimestampInSeconds(1641078120L) private val versions = listOf( StubVersion( diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/home/HomeScreenViewModelTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/home/HomeScreenViewModelTest.kt index a07871b78d..e4cd23b065 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/home/HomeScreenViewModelTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/home/HomeScreenViewModelTest.kt @@ -92,6 +92,7 @@ import com.anytypeio.anytype.presentation.widgets.WidgetConfig import com.anytypeio.anytype.presentation.widgets.WidgetDispatchEvent import com.anytypeio.anytype.presentation.widgets.WidgetSessionStateHolder import com.anytypeio.anytype.presentation.widgets.WidgetView +import com.anytypeio.anytype.presentation.widgets.getWidgetObjectName import com.anytypeio.anytype.test_utils.MockDataFactory import kotlin.test.assertEquals import kotlin.test.assertTrue @@ -506,7 +507,10 @@ class HomeScreenViewModelTest { id = widgetBlock.id, source = Widget.Source.Default(sourceObject), elements = emptyList(), - isExpanded = true + isExpanded = true, + name = WidgetView.Name.Default( + prettyPrintName = sourceObject.getWidgetObjectName() + ) ) ) addAll(HomeScreenViewModel.actions) @@ -615,21 +619,32 @@ class HomeScreenViewModelTest { add( WidgetView.Tree( id = widgetBlock.id, + name = WidgetView.Name.Default( + prettyPrintName = sourceObject.getWidgetObjectName() + ), source = Widget.Source.Default(sourceObject), elements = listOf( WidgetView.Tree.Element( + id = firstLink.id, elementIcon = WidgetView.Tree.ElementIcon.Leaf, obj = firstLink, objectIcon = ObjectIcon.Empty.Page, indent = 0, - path = widgetBlock.id + "/" + sourceObject.id + "/" + firstLink.id + path = widgetBlock.id + "/" + sourceObject.id + "/" + firstLink.id, + name = WidgetView.Name.Default( + prettyPrintName = firstLink.getWidgetObjectName() + ) ), WidgetView.Tree.Element( + id = secondLink.id, elementIcon = WidgetView.Tree.ElementIcon.Leaf, obj = secondLink, objectIcon = ObjectIcon.Empty.Page, indent = 0, - path = widgetBlock.id + "/" + sourceObject.id + "/" + secondLink.id + path = widgetBlock.id + "/" + sourceObject.id + "/" + secondLink.id, + name = WidgetView.Name.Default( + prettyPrintName = secondLink.getWidgetObjectName() + ) ) ), isExpanded = true @@ -734,6 +749,9 @@ class HomeScreenViewModelTest { add( WidgetView.SetOfObjects( id = widgetBlock.id, + name = WidgetView.Name.Default( + prettyPrintName = sourceObject.getWidgetObjectName() + ), source = Widget.Source.Default(sourceObject), elements = emptyList(), isExpanded = true, @@ -840,6 +858,9 @@ class HomeScreenViewModelTest { add( WidgetView.SetOfObjects( id = widgetBlock.id, + name = WidgetView.Name.Default( + prettyPrintName = sourceObject.getWidgetObjectName() + ), source = Widget.Source.Default(sourceObject), elements = emptyList(), isExpanded = true, @@ -1073,20 +1094,31 @@ class HomeScreenViewModelTest { WidgetView.Tree( id = favoriteWidgetBlock.id, source = Widget.Source.Bundled.Favorites, + name = WidgetView.Name.Bundled( + Widget.Source.Bundled.Favorites, + ), elements = listOf( WidgetView.Tree.Element( + id = firstLink.id, elementIcon = WidgetView.Tree.ElementIcon.Leaf, obj = firstLink, objectIcon = ObjectIcon.Empty.Page, indent = 0, - path = favoriteWidgetBlock.id + "/" + favoriteSource.id + "/" + firstLink.id + path = favoriteWidgetBlock.id + "/" + favoriteSource.id + "/" + firstLink.id, + name = WidgetView.Name.Default( + prettyPrintName = firstLink.getWidgetObjectName() + ) ), WidgetView.Tree.Element( + id = secondLink.id, elementIcon = WidgetView.Tree.ElementIcon.Leaf, obj = secondLink, objectIcon = ObjectIcon.Empty.Page, indent = 0, - path = favoriteWidgetBlock.id + "/" + favoriteSource.id + "/" + secondLink.id + path = favoriteWidgetBlock.id + "/" + favoriteSource.id + "/" + secondLink.id, + name = WidgetView.Name.Default( + prettyPrintName = secondLink.getWidgetObjectName() + ) ) ), isExpanded = true @@ -1095,21 +1127,32 @@ class HomeScreenViewModelTest { add( WidgetView.Tree( id = recentWidgetBlock.id, + name = WidgetView.Name.Bundled( + Widget.Source.Bundled.Recent, + ), source = Widget.Source.Bundled.Recent, elements = listOf( WidgetView.Tree.Element( + id = firstLink.id, elementIcon = WidgetView.Tree.ElementIcon.Leaf, obj = firstLink, objectIcon = ObjectIcon.Empty.Page, indent = 0, - path = recentWidgetBlock.id + "/" + recentSource.id + "/" + firstLink.id + path = recentWidgetBlock.id + "/" + recentSource.id + "/" + firstLink.id, + name = WidgetView.Name.Default( + prettyPrintName = firstLink.getWidgetObjectName() + ) ), WidgetView.Tree.Element( + id = secondLink.id, elementIcon = WidgetView.Tree.ElementIcon.Leaf, obj = secondLink, objectIcon = ObjectIcon.Empty.Page, indent = 0, - path = recentWidgetBlock.id + "/" + recentSource.id + "/" + secondLink.id + path = recentWidgetBlock.id + "/" + recentSource.id + "/" + secondLink.id, + name = WidgetView.Name.Default( + prettyPrintName = secondLink.getWidgetObjectName() + ) ) ), isExpanded = true @@ -1119,20 +1162,31 @@ class HomeScreenViewModelTest { WidgetView.Tree( id = setsWidgetBlock.id, source = Widget.Source.Bundled.Sets, + name = WidgetView.Name.Bundled( + Widget.Source.Bundled.Sets, + ), elements = listOf( WidgetView.Tree.Element( + id = firstLink.id, elementIcon = WidgetView.Tree.ElementIcon.Leaf, obj = firstLink, objectIcon = ObjectIcon.Empty.Page, indent = 0, - path = setsWidgetBlock.id + "/" + setsSource.id + "/" + firstLink.id + path = setsWidgetBlock.id + "/" + setsSource.id + "/" + firstLink.id, + name = WidgetView.Name.Default( + prettyPrintName = firstLink.getWidgetObjectName() + ) ), WidgetView.Tree.Element( + id = secondLink.id, elementIcon = WidgetView.Tree.ElementIcon.Leaf, obj = secondLink, objectIcon = ObjectIcon.Empty.Page, indent = 0, - path = setsWidgetBlock.id + "/" + setsSource.id + "/" + secondLink.id + path = setsWidgetBlock.id + "/" + setsSource.id + "/" + secondLink.id, + name = WidgetView.Name.Default( + prettyPrintName = secondLink.getWidgetObjectName() + ) ) ), isExpanded = true @@ -1223,6 +1277,9 @@ class HomeScreenViewModelTest { WidgetView.Link( id = widgetBlock.id, source = Widget.Source.Default(sourceObject), + name = WidgetView.Name.Default( + sourceObject.getWidgetObjectName() + ) ) ) addAll(HomeScreenViewModel.actions) diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/home/ParseWidgetTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/home/ParseWidgetTest.kt index 17a3a16f35..4d6d339ae7 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/home/ParseWidgetTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/home/ParseWidgetTest.kt @@ -12,6 +12,8 @@ import org.junit.Test class ParseWidgetTest { + + @Test fun `should hide widgets with archived source`() { diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/home/TreeWidgetContainerTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/home/TreeWidgetContainerTest.kt index b59d3caf46..2fa14cd57c 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/home/TreeWidgetContainerTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/home/TreeWidgetContainerTest.kt @@ -21,6 +21,7 @@ import com.anytypeio.anytype.presentation.widgets.TreePath import com.anytypeio.anytype.presentation.widgets.TreeWidgetContainer import com.anytypeio.anytype.presentation.widgets.Widget import com.anytypeio.anytype.presentation.widgets.WidgetView +import com.anytypeio.anytype.presentation.widgets.getWidgetObjectName import com.anytypeio.anytype.test_utils.MockDataFactory import kotlin.test.assertEquals import kotlinx.coroutines.delay @@ -45,9 +46,6 @@ class TreeWidgetContainerTest { @Mock lateinit var gateway: Gateway - @Mock - lateinit var spaceGradientProvider: SpaceGradientProvider - @Mock lateinit var storelessSubscriptionContainer: StorelessSubscriptionContainer @@ -301,27 +299,42 @@ class TreeWidgetContainerTest { expected = WidgetView.Tree( id = widget.id, source = widget.source, + name = WidgetView.Name.Default( + (widget.source as Widget.Source.Default).obj.getWidgetObjectName() + ), elements = listOf( WidgetView.Tree.Element( + id = sourceLinks[0].id, indent = 0, obj = sourceLinks[0], path = widget.id + "/" + widget.source.id + "/" + sourceLinks[0].id, elementIcon = WidgetView.Tree.ElementIcon.Branch(isExpanded = false), objectIcon = ObjectIcon.Empty.Page, + name = WidgetView.Name.Default( + prettyPrintName = sourceLinks[0].getWidgetObjectName() + ) ), WidgetView.Tree.Element( indent = 0, + id = sourceLinks[1].id, obj = sourceLinks[1], path = widget.id + "/" + widget.source.id + "/" + sourceLinks[1].id, elementIcon = WidgetView.Tree.ElementIcon.Leaf, objectIcon = ObjectIcon.Empty.Page, + name = WidgetView.Name.Default( + prettyPrintName = sourceLinks[1].getWidgetObjectName() + ) ), WidgetView.Tree.Element( indent = 0, + id = sourceLinks[2].id, obj = sourceLinks[2], path = widget.id + "/" + widget.source.id + "/" + sourceLinks[2].id, elementIcon = WidgetView.Tree.ElementIcon.Leaf, objectIcon = ObjectIcon.Empty.Page, + name = WidgetView.Name.Default( + prettyPrintName = sourceLinks[2].getWidgetObjectName() + ) ) ), isExpanded = true @@ -335,48 +348,75 @@ class TreeWidgetContainerTest { expected = WidgetView.Tree( id = widget.id, source = widget.source, + name = WidgetView.Name.Default( + (widget.source as Widget.Source.Default).obj.getWidgetObjectName() + ), elements = listOf( WidgetView.Tree.Element( indent = 0, + id = sourceLinks[0].id, obj = sourceLinks[0], path = widget.id + "/" + widget.source.id + "/" + sourceLinks[0].id, elementIcon = WidgetView.Tree.ElementIcon.Branch(isExpanded = true), objectIcon = ObjectIcon.Empty.Page, + name = WidgetView.Name.Default( + prettyPrintName = sourceLinks[0].getWidgetObjectName() + ) ), WidgetView.Tree.Element( indent = 1, + id = linkA1.id, obj = linkA1, path = widget.id + "/" + widget.source.id + "/" + sourceLinks[0].id + "/" + linkA1.id, elementIcon = WidgetView.Tree.ElementIcon.Leaf, objectIcon = ObjectIcon.Empty.Page, + name = WidgetView.Name.Default( + prettyPrintName = linkA1.getWidgetObjectName() + ) ), WidgetView.Tree.Element( indent = 1, + id = linkA2.id, obj = linkA2, path = widget.id + "/" + widget.source.id + "/" + sourceLinks[0].id + "/" + linkA2.id, elementIcon = WidgetView.Tree.ElementIcon.Leaf, objectIcon = ObjectIcon.Empty.Page, + name = WidgetView.Name.Default( + prettyPrintName = linkA2.getWidgetObjectName() + ) ), WidgetView.Tree.Element( indent = 1, + id = linkA3.id, obj = linkA3, path = widget.id + "/" + widget.source.id + "/" + sourceLinks[0].id + "/" + linkA3.id, elementIcon = WidgetView.Tree.ElementIcon.Leaf, objectIcon = ObjectIcon.Empty.Page, + name = WidgetView.Name.Default( + prettyPrintName = linkA3.getWidgetObjectName() + ) ), WidgetView.Tree.Element( indent = 0, + id = sourceLinks[1].id, obj = sourceLinks[1], path = widget.id + "/" + widget.source.id + "/" + sourceLinks[1].id, elementIcon = WidgetView.Tree.ElementIcon.Leaf, objectIcon = ObjectIcon.Empty.Page, + name = WidgetView.Name.Default( + prettyPrintName = sourceLinks[1].getWidgetObjectName() + ) ), WidgetView.Tree.Element( indent = 0, + id = sourceLinks[2].id, obj = sourceLinks[2], path = widget.id + "/" + widget.source.id + "/" + sourceLinks[2].id, elementIcon = WidgetView.Tree.ElementIcon.Leaf, objectIcon = ObjectIcon.Empty.Page, + name = WidgetView.Name.Default( + prettyPrintName = sourceLinks[2].getWidgetObjectName() + ) ) ), isExpanded = true diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/linking/LinkToObjectOrWebViewModelTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/linking/LinkToObjectOrWebViewModelTest.kt index b8a96b640c..542b9f36a5 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/linking/LinkToObjectOrWebViewModelTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/linking/LinkToObjectOrWebViewModelTest.kt @@ -12,6 +12,7 @@ import com.anytypeio.anytype.domain.config.Gateway import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.objects.DefaultStoreOfObjectTypes import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.search.SearchObjects import com.anytypeio.anytype.domain.workspace.SpaceManager import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate @@ -302,6 +303,9 @@ open class LinkToObjectOrWebViewModelTest { } } + @Mock + lateinit var fieldParser: FieldParser + private fun givenViewModel() = LinkToObjectOrWebViewModel( searchObjects = searchObjects, analytics = analytics, @@ -310,7 +314,8 @@ open class LinkToObjectOrWebViewModelTest { urlValidator = urlValidator, storeOfObjectTypes = storeOfObjectTypes, analyticSpaceHelperDelegate = analyticSpaceHelperDelegate, - vmParams = vmParams + vmParams = vmParams, + fieldParser = fieldParser ) fun stubSpaceManager() { diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/mapper/ObjectWrapperExtensionsKtTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/mapper/ObjectWrapperExtensionsKtTest.kt index d2d2a45ab2..a9aedd6561 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/mapper/ObjectWrapperExtensionsKtTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/mapper/ObjectWrapperExtensionsKtTest.kt @@ -3,7 +3,11 @@ package com.anytypeio.anytype.presentation.mapper 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.domain.debugging.Logger +import com.anytypeio.anytype.domain.misc.DateProvider import com.anytypeio.anytype.domain.misc.UrlBuilder +import com.anytypeio.anytype.domain.primitives.FieldParser +import com.anytypeio.anytype.domain.primitives.FieldParserImpl import com.anytypeio.anytype.presentation.objects.toViews import com.anytypeio.anytype.test_utils.MockDataFactory import kotlin.test.assertEquals @@ -20,11 +24,20 @@ class ObjectWrapperExtensionsKtTest { @Mock lateinit var urlBuilder: UrlBuilder + @Mock + lateinit var dateProvider: DateProvider + + @Mock + lateinit var logger: Logger + + lateinit var fieldParser: FieldParser + val URL = "anytype.io/" @Before fun before() { MockitoAnnotations.openMocks(this) + fieldParser = FieldParserImpl(dateProvider, logger) } @Test @@ -92,7 +105,11 @@ class ObjectWrapperExtensionsKtTest { ) - val result = listOf(obj).toViews(urlBuilder, objectTypes = listOf()) + val result = listOf(obj).toViews( + urlBuilder = urlBuilder, + objectTypes = listOf(), + fieldParser + ) assertEquals( expected = "OMr2Y", @@ -118,7 +135,8 @@ class ObjectWrapperExtensionsKtTest { val result = listOf(obj).toViews( urlBuilder = urlBuilder, - objectTypes = listOf() + objectTypes = listOf(), + fieldParser ) assertEquals( @@ -144,7 +162,11 @@ class ObjectWrapperExtensionsKtTest { ) - val result = listOf(obj).toViews(urlBuilder, objectTypes = listOf()) + val result = listOf(obj).toViews( + urlBuilder = urlBuilder, + objectTypes = listOf(), + fieldParser + ) assertEquals( expected = "Anytype is next-generation sof", diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/objects/CreateDVObjectTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/objects/CreateDVObjectTest.kt index 5121511b58..dc4465ef15 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/objects/CreateDVObjectTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/objects/CreateDVObjectTest.kt @@ -15,6 +15,7 @@ import com.anytypeio.anytype.core_models.StubRelationObject import com.anytypeio.anytype.core_models.ext.DAYS_IN_MONTH import com.anytypeio.anytype.core_models.ext.DAYS_IN_WEEK import com.anytypeio.anytype.core_models.ext.SECONDS_IN_DAY +import com.anytypeio.anytype.domain.primitives.FieldParserImpl import com.anytypeio.anytype.presentation.sets.main.ObjectSetViewModelTestSetup import com.anytypeio.anytype.presentation.sets.prefillNewObjectDetails import com.anytypeio.anytype.presentation.sets.resolveSetByRelationPrefilledObjectData @@ -22,11 +23,13 @@ import com.anytypeio.anytype.presentation.util.CoroutinesTestRule import com.anytypeio.anytype.test_utils.MockDataFactory import kotlin.test.assertEquals import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest import net.bytebuddy.utility.RandomString import org.junit.Before import org.junit.Rule import org.junit.Test +import org.mockito.MockitoAnnotations import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock import org.mockito.kotlin.stub @@ -48,13 +51,16 @@ class CreateDVObjectTest : ObjectSetViewModelTestSetup() { val coroutineTestRule = CoroutinesTestRule() @Before - fun setup() = runTest { - //MockitoAnnotations.openMocks(this) - dateProvider = mock(verboseLogging = true) - storeOfRelations.merge(listOf(filterDate)) + fun setup() { + MockitoAnnotations.openMocks(this) + fieldParser = FieldParserImpl(dateProvider, logger) + dateProvider.stub { on { getCurrentTimestampInSeconds() } doReturn timestamp } + runBlocking{ + storeOfRelations.merge(listOf(filterDate)) + } } @Test diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/TagAndStatusTests.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/TagAndStatusTests.kt index 17fc2a76f5..bd73879582 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/TagAndStatusTests.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/TagAndStatusTests.kt @@ -7,8 +7,12 @@ import com.anytypeio.anytype.core_models.RelationFormat import com.anytypeio.anytype.core_models.Relations import com.anytypeio.anytype.core_models.StubRelationOptionObject import com.anytypeio.anytype.domain.config.Gateway +import com.anytypeio.anytype.domain.debugging.Logger +import com.anytypeio.anytype.domain.misc.DateProvider import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.objects.DefaultObjectStore +import com.anytypeio.anytype.domain.primitives.FieldParser +import com.anytypeio.anytype.domain.primitives.FieldParserImpl import com.anytypeio.anytype.presentation.mapper.toViewerColumns import com.anytypeio.anytype.presentation.objects.ObjectIcon import com.anytypeio.anytype.presentation.relations.ObjectSetConfig @@ -32,9 +36,19 @@ class TagAndStatusTests { @Mock lateinit var gateway: Gateway + lateinit var fieldParser: FieldParser + + @Mock + lateinit var dateProvider: DateProvider + + @Mock + lateinit var logger: Logger + + @Before fun setup() { MockitoAnnotations.openMocks(this) + fieldParser = FieldParserImpl(dateProvider, logger) } @Test @@ -131,7 +145,8 @@ class TagAndStatusTests { obj = ObjectWrapper.Basic(records), builder = UrlBuilder(gateway), showIcon = false, - store = store + store = store, + fieldParser = fieldParser ) val expected = Viewer.GridView.Row( @@ -265,7 +280,8 @@ class TagAndStatusTests { obj = ObjectWrapper.Basic(records), builder = UrlBuilder(gateway), showIcon = false, - store = store + store = store, + fieldParser = fieldParser ) val expected = Viewer.GridView.Row( diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/ViewerDefaultObjectTypeTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/ViewerDefaultObjectTypeTest.kt index 2317969f10..144dd6bb96 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/ViewerDefaultObjectTypeTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/ViewerDefaultObjectTypeTest.kt @@ -4,6 +4,7 @@ import com.anytypeio.anytype.core_models.ObjectType import com.anytypeio.anytype.core_models.Relations import com.anytypeio.anytype.core_models.StubDataView import com.anytypeio.anytype.core_models.primitives.SpaceId +import com.anytypeio.anytype.domain.primitives.FieldParserImpl import com.anytypeio.anytype.presentation.collections.MockCollection import com.anytypeio.anytype.presentation.collections.MockSet import com.anytypeio.anytype.presentation.sets.main.ObjectSetViewModelTestSetup @@ -62,6 +63,7 @@ class ViewerDefaultObjectTypeTest : ObjectSetViewModelTestSetup() { @Before fun setup() { closable = MockitoAnnotations.openMocks(this) + fieldParser = FieldParserImpl(dateProvider, logger) viewModel = givenViewModel() stubNetworkMode() stubObservePermissions() diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/filter/FilterViewModelInputFieldValueCreateTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/filter/FilterViewModelInputFieldValueCreateTest.kt index c640a2aca9..4b7f556a98 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/filter/FilterViewModelInputFieldValueCreateTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/filter/FilterViewModelInputFieldValueCreateTest.kt @@ -17,6 +17,7 @@ import com.anytypeio.anytype.domain.objects.DefaultStoreOfRelations import com.anytypeio.anytype.domain.objects.ObjectStore import com.anytypeio.anytype.domain.objects.StoreOfRelations import com.anytypeio.anytype.domain.objects.options.GetOptions +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.search.SearchObjects import com.anytypeio.anytype.domain.workspace.SpaceManager import com.anytypeio.anytype.presentation.mapper.toDomain @@ -66,6 +67,9 @@ class FilterViewModelInputFieldValueCreateTest { @Mock lateinit var spaceManager: SpaceManager + @Mock + lateinit var fieldParser: FieldParser + private lateinit var viewModel: FilterViewModel private lateinit var urlBuilder: UrlBuilder private val root = MockDataFactory.randomUuid() @@ -191,7 +195,8 @@ class FilterViewModelInputFieldValueCreateTest { storeOfRelations = storeOfRelations, objectSetDatabase = db, getOptions = getOptions, - spaceManager = spaceManager + spaceManager = spaceManager, + fieldParser = fieldParser ) } diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/filter/FilterViewModelInputFieldValueModifyTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/filter/FilterViewModelInputFieldValueModifyTest.kt index 8e1055ff9f..55e9d11fb7 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/filter/FilterViewModelInputFieldValueModifyTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/filter/FilterViewModelInputFieldValueModifyTest.kt @@ -18,6 +18,7 @@ import com.anytypeio.anytype.domain.objects.ObjectStore import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes import com.anytypeio.anytype.domain.objects.StoreOfRelations import com.anytypeio.anytype.domain.objects.options.GetOptions +import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.search.SearchObjects import com.anytypeio.anytype.domain.workspace.SpaceManager import com.anytypeio.anytype.presentation.mapper.toDomain @@ -68,6 +69,9 @@ class FilterViewModelInputFieldValueModifyTest { @Mock lateinit var spaceManager: SpaceManager + @Mock + lateinit var fieldParser: FieldParser + private lateinit var viewModel: FilterViewModel private lateinit var urlBuilder: UrlBuilder private val root = MockDataFactory.randomUuid() @@ -198,7 +202,8 @@ class FilterViewModelInputFieldValueModifyTest { storeOfRelations = storeOfRelations, objectSetDatabase = db, getOptions = getOptions, - spaceManager = spaceManager + spaceManager = spaceManager, + fieldParser = fieldParser ) } diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/ObjectSetCellTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/ObjectSetCellTest.kt index c0561b58d0..6bc80cb11a 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/ObjectSetCellTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/ObjectSetCellTest.kt @@ -1,6 +1,7 @@ package com.anytypeio.anytype.presentation.sets.main import app.cash.turbine.test +import com.anytypeio.anytype.domain.primitives.FieldParserImpl import com.anytypeio.anytype.presentation.collections.MockSet import com.anytypeio.anytype.presentation.sets.DataViewViewState import com.anytypeio.anytype.presentation.sets.ObjectSetCommand @@ -23,6 +24,7 @@ class ObjectSetCellTest : ObjectSetViewModelTestSetup() { @Before fun setup() { MockitoAnnotations.openMocks(this) + fieldParser = FieldParserImpl(dateProvider, logger) viewModel = givenViewModel() mockObjectSet = MockSet(context = root, space = defaultSpace) stubObservePermissions() diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/ObjectSetConvertToCollectionTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/ObjectSetConvertToCollectionTest.kt index 0632063c3b..4003303066 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/ObjectSetConvertToCollectionTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/ObjectSetConvertToCollectionTest.kt @@ -4,6 +4,7 @@ import app.cash.turbine.turbineScope import com.anytypeio.anytype.core_models.Event import com.anytypeio.anytype.core_models.Payload import com.anytypeio.anytype.core_models.primitives.SpaceId +import com.anytypeio.anytype.domain.primitives.FieldParserImpl import com.anytypeio.anytype.presentation.collections.MockSet import com.anytypeio.anytype.presentation.relations.ObjectSetConfig import com.anytypeio.anytype.presentation.search.ObjectSearchConstants @@ -31,6 +32,7 @@ class ObjectSetConvertToCollectionTest : ObjectSetViewModelTestSetup() { @Before fun setup() { MockitoAnnotations.openMocks(this) + fieldParser = FieldParserImpl(dateProvider, logger) viewModel = givenViewModel() mockObjectSet = MockSet(context = root, space = defaultSpace) stubGetDefaultPageType() diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/ObjectSetDataViewObjectCreateTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/ObjectSetDataViewObjectCreateTest.kt index 056b6e0a43..59bac32539 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/ObjectSetDataViewObjectCreateTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/ObjectSetDataViewObjectCreateTest.kt @@ -5,7 +5,6 @@ import com.anytypeio.anytype.core_models.Block import com.anytypeio.anytype.core_models.ObjectType import com.anytypeio.anytype.core_models.ObjectTypeIds import com.anytypeio.anytype.core_models.Relations -import com.anytypeio.anytype.core_models.primitives.SpaceId import com.anytypeio.anytype.core_models.primitives.TypeKey import com.anytypeio.anytype.core_models.restrictions.DataViewRestriction import com.anytypeio.anytype.core_models.restrictions.DataViewRestrictions @@ -13,7 +12,8 @@ import com.anytypeio.anytype.domain.base.Resultat import com.anytypeio.anytype.domain.dataview.interactor.CreateDataViewObject import com.anytypeio.anytype.presentation.collections.MockCollection import com.anytypeio.anytype.presentation.collections.MockSet -import com.anytypeio.anytype.presentation.objects.SupportedLayouts +import com.anytypeio.anytype.core_models.SupportedLayouts +import com.anytypeio.anytype.domain.primitives.FieldParserImpl import com.anytypeio.anytype.presentation.sets.DataViewViewState import com.anytypeio.anytype.presentation.sets.ObjectSetViewModel import kotlin.test.assertFalse @@ -44,6 +44,7 @@ class ObjectSetDataViewObjectCreateTest : ObjectSetViewModelTestSetup() { @Before fun setup() { MockitoAnnotations.openMocks(this) + fieldParser = FieldParserImpl(dateProvider, logger) viewModel = givenViewModel() mockObjectSet = MockSet(context = root, setOfValue = setOfId, setOfKey = setOfKey, space = defaultSpace) diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/ObjectSetHeaderTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/ObjectSetHeaderTest.kt index 0a559e9d55..bd3e795bec 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/ObjectSetHeaderTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/ObjectSetHeaderTest.kt @@ -2,6 +2,7 @@ package com.anytypeio.anytype.presentation.sets.main import app.cash.turbine.test import com.anytypeio.anytype.core_models.primitives.SpaceId +import com.anytypeio.anytype.domain.primitives.FieldParserImpl import com.anytypeio.anytype.presentation.collections.MockSet import com.anytypeio.anytype.presentation.sets.DataViewViewState import com.anytypeio.anytype.presentation.sets.ObjectSetViewModel @@ -22,6 +23,7 @@ class ObjectSetHeaderTest : ObjectSetViewModelTestSetup() { @Before fun setup() { MockitoAnnotations.openMocks(this) + fieldParser = FieldParserImpl(dateProvider, logger) viewModel = givenViewModel() mockObjectSet = MockSet(context = root, space = defaultSpace) stubNetworkMode() diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/ObjectSetInitializationTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/ObjectSetInitializationTest.kt index b644ccc35a..4b3db40e05 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/ObjectSetInitializationTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/ObjectSetInitializationTest.kt @@ -1,6 +1,7 @@ package com.anytypeio.anytype.presentation.sets.main import com.anytypeio.anytype.core_models.primitives.SpaceId +import com.anytypeio.anytype.domain.primitives.FieldParserImpl import com.anytypeio.anytype.presentation.collections.MockSet import com.anytypeio.anytype.presentation.sets.ObjectSetViewModel import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -23,6 +24,7 @@ class ObjectSetInitializationTest : ObjectSetViewModelTestSetup() { @Before fun setup() { MockitoAnnotations.openMocks(this) + fieldParser = FieldParserImpl(dateProvider, logger) viewModel = givenViewModel() mockObjectSet = MockSet(context = root, space = defaultSpace) stubNetworkMode() diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/ObjectSetNavigationTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/ObjectSetNavigationTest.kt index 5ff01a6e78..2ab235ca00 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/ObjectSetNavigationTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/ObjectSetNavigationTest.kt @@ -8,7 +8,8 @@ import com.anytypeio.anytype.core_models.primitives.SpaceId import com.anytypeio.anytype.domain.page.CloseBlock import com.anytypeio.anytype.presentation.collections.MockSet import com.anytypeio.anytype.presentation.objects.ObjectIcon -import com.anytypeio.anytype.presentation.objects.SupportedLayouts +import com.anytypeio.anytype.core_models.SupportedLayouts +import com.anytypeio.anytype.domain.primitives.FieldParserImpl import com.anytypeio.anytype.presentation.sets.DataViewViewState import com.anytypeio.anytype.presentation.sets.ObjectSetCommand import com.anytypeio.anytype.presentation.sets.ObjectSetViewModel @@ -25,7 +26,6 @@ import org.junit.After import org.junit.Before import org.junit.Test import org.mockito.MockitoAnnotations -import org.mockito.kotlin.doReturn import org.mockito.kotlin.times import org.mockito.kotlin.verifyBlocking import org.mockito.kotlin.verifyNoInteractions @@ -40,6 +40,7 @@ class ObjectSetNavigationTest : ObjectSetViewModelTestSetup() { @Before fun setup() { closable = MockitoAnnotations.openMocks(this) + fieldParser = FieldParserImpl(dateProvider, logger) viewModel = givenViewModel() mockObjectSet = MockSet(context = root, space = defaultSpace) stubNetworkMode() diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/ObjectSetRestrictionsTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/ObjectSetRestrictionsTest.kt index 340d8abde7..5076a3de98 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/ObjectSetRestrictionsTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/ObjectSetRestrictionsTest.kt @@ -4,6 +4,7 @@ import app.cash.turbine.test import com.anytypeio.anytype.core_models.primitives.SpaceId import com.anytypeio.anytype.core_models.restrictions.DataViewRestriction import com.anytypeio.anytype.core_models.restrictions.DataViewRestrictions +import com.anytypeio.anytype.domain.primitives.FieldParserImpl import com.anytypeio.anytype.presentation.collections.MockSet import com.anytypeio.anytype.presentation.sets.DataViewViewState import com.anytypeio.anytype.presentation.sets.ObjectSetViewModel @@ -25,6 +26,7 @@ class ObjectSetRestrictionsTest : ObjectSetViewModelTestSetup() { @Before fun setup() { MockitoAnnotations.openMocks(this) + fieldParser = FieldParserImpl(dateProvider, logger) viewModel = givenViewModel() mockObjectSet = MockSet(context = root, space = defaultSpace) givenNetworkNodeMocked() diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/ObjectSetViewModelTestSetup.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/ObjectSetViewModelTestSetup.kt index 739d907071..65ee4782a3 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/ObjectSetViewModelTestSetup.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/ObjectSetViewModelTestSetup.kt @@ -34,6 +34,7 @@ import com.anytypeio.anytype.domain.config.Gateway import com.anytypeio.anytype.domain.cover.SetDocCoverImage import com.anytypeio.anytype.domain.dataview.interactor.CreateDataViewObject import com.anytypeio.anytype.domain.dataview.interactor.UpdateDataViewViewer +import com.anytypeio.anytype.domain.debugging.Logger import com.anytypeio.anytype.domain.event.interactor.InterceptEvents import com.anytypeio.anytype.domain.event.interactor.SpaceSyncAndP2PStatusProvider import com.anytypeio.anytype.domain.launch.GetDefaultObjectType @@ -56,6 +57,8 @@ import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes import com.anytypeio.anytype.domain.objects.StoreOfRelations import com.anytypeio.anytype.domain.page.CloseBlock import com.anytypeio.anytype.domain.page.CreateObject +import com.anytypeio.anytype.domain.primitives.FieldParser +import com.anytypeio.anytype.domain.primitives.FieldParserImpl import com.anytypeio.anytype.domain.search.CancelSearchSubscription import com.anytypeio.anytype.domain.search.DataViewSubscriptionContainer import com.anytypeio.anytype.domain.search.SubscriptionEventChannel @@ -215,6 +218,11 @@ open class ObjectSetViewModelTestSetup { @Mock lateinit var localeProvider : LocaleProvider + lateinit var fieldParser: FieldParser + + @Mock + lateinit var logger: Logger + @Mock lateinit var dateProvider: DateProvider @@ -264,7 +272,7 @@ open class ObjectSetViewModelTestSetup { ) dataViewSubscription = DefaultDataViewSubscription(dataViewSubscriptionContainer) storeOfObjectTypes = DefaultStoreOfObjectTypes() - Mockito.`when`(localeProvider.locale()).thenReturn(Locale.getDefault()) + stubLocalProvider() return ObjectSetViewModel( openObjectSet = openObjectSet, closeBlock = closeBlock, @@ -309,7 +317,8 @@ open class ObjectSetViewModelTestSetup { permissions = permissions, analyticSpaceHelperDelegate = analyticSpaceHelperDelegate, spaceSyncAndP2PStatusProvider = spaceSyncAndP2PStatusProvider, - clearLastOpenedObject = clearLastOpenedObject + clearLastOpenedObject = clearLastOpenedObject, + fieldParser = fieldParser ) } @@ -565,4 +574,11 @@ open class ObjectSetViewModelTestSetup { Mockito.`when`(analyticSpaceHelperDelegate.provideParams(defaultSpace)) .thenReturn(AnalyticSpaceHelperDelegate.Params.EMPTY) } + + fun stubLocalProvider() { + localeProvider.stub { + on { locale() } doReturn Locale.getDefault() + on { language() } doReturn "en" + } + } } \ No newline at end of file diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/ObjectSetZeroDataViewTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/ObjectSetZeroDataViewTest.kt index 2665c08658..9e8f4ed50f 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/ObjectSetZeroDataViewTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/ObjectSetZeroDataViewTest.kt @@ -1,6 +1,7 @@ package com.anytypeio.anytype.presentation.sets.main import com.anytypeio.anytype.core_models.Block +import com.anytypeio.anytype.domain.primitives.FieldParserImpl import com.anytypeio.anytype.presentation.sets.ObjectSetViewModel import com.anytypeio.anytype.test_utils.MockDataFactory import kotlin.test.assertEquals @@ -33,6 +34,7 @@ class ObjectSetZeroDataViewTest : ObjectSetViewModelTestSetup() { @Before fun setup() { MockitoAnnotations.openMocks(this) + fieldParser = FieldParserImpl(dateProvider, logger) } @Test diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/ObjectSetZeroViewTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/ObjectSetZeroViewTest.kt index dbe2dc5370..ed08b28cd5 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/ObjectSetZeroViewTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/ObjectSetZeroViewTest.kt @@ -3,6 +3,7 @@ package com.anytypeio.anytype.presentation.sets.main import com.anytypeio.anytype.core_models.Block import com.anytypeio.anytype.core_models.DV import com.anytypeio.anytype.core_models.Relations +import com.anytypeio.anytype.domain.primitives.FieldParserImpl import com.anytypeio.anytype.presentation.sets.ObjectSetViewModel import com.anytypeio.anytype.test_utils.MockDataFactory import org.junit.Before @@ -44,6 +45,7 @@ class ObjectSetZeroViewTest : ObjectSetViewModelTestSetup() { @Before fun setup() { MockitoAnnotations.openMocks(this) + fieldParser = FieldParserImpl(dateProvider, logger) } @Test diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/SetByRelationTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/SetByRelationTest.kt index b6111cbf49..d5105bb6ca 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/SetByRelationTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/SetByRelationTest.kt @@ -5,6 +5,7 @@ import app.cash.turbine.turbineScope import com.anytypeio.anytype.core_models.primitives.SpaceId import com.anytypeio.anytype.core_models.primitives.TypeKey import com.anytypeio.anytype.domain.dataview.interactor.CreateDataViewObject +import com.anytypeio.anytype.domain.primitives.FieldParserImpl import com.anytypeio.anytype.presentation.collections.MockSet import com.anytypeio.anytype.presentation.sets.DataViewViewState import com.anytypeio.anytype.presentation.sets.ObjectSetViewModel @@ -33,6 +34,7 @@ class SetByRelationTest : ObjectSetViewModelTestSetup() { @Before fun setup() { closable = MockitoAnnotations.openMocks(this) + fieldParser = FieldParserImpl(dateProvider, logger) viewModel = givenViewModel() mockObjectSet = MockSet(context = root, setOfValue = setOfId, setOfKey = setOfKey, space = defaultSpace) stubNetworkMode() diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/splash/SplashViewModelTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/splash/SplashViewModelTest.kt index 2dc0d2ee04..aea9d3bdbc 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/splash/SplashViewModelTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/splash/SplashViewModelTest.kt @@ -20,6 +20,7 @@ import com.anytypeio.anytype.domain.subscriptions.GlobalSubscriptionManager import com.anytypeio.anytype.domain.workspace.SpaceManager import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate import com.anytypeio.anytype.presentation.util.CoroutinesTestRule +import java.util.Locale import kotlinx.coroutines.runBlocking import org.junit.Before import org.junit.Rule @@ -100,6 +101,7 @@ class SplashViewModelTest { } private fun initViewModel() { + stubLocalProvider() vm = SplashViewModel( checkAuthorizationStatus = checkAuthorizationStatus, launchAccount = launchAccount, @@ -216,4 +218,11 @@ class SplashViewModelTest { Mockito.`when`(analyticSpaceHelperDelegate.provideParams("")) .thenReturn(AnalyticSpaceHelperDelegate.Params.EMPTY) } + + fun stubLocalProvider() { + localeProvider.stub { + on { locale() } doReturn Locale.getDefault() + on { language() } doReturn "en" + } + } } \ No newline at end of file diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/templates/CollectionViewerTypeAndTemplateTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/templates/CollectionViewerTypeAndTemplateTest.kt index bfec2ee130..186015f54e 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/templates/CollectionViewerTypeAndTemplateTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/templates/CollectionViewerTypeAndTemplateTest.kt @@ -13,6 +13,7 @@ import com.anytypeio.anytype.core_models.StubRelationObject import com.anytypeio.anytype.core_models.primitives.TypeId import com.anytypeio.anytype.core_models.primitives.TypeKey import com.anytypeio.anytype.domain.dataview.interactor.CreateDataViewObject +import com.anytypeio.anytype.domain.primitives.FieldParserImpl import com.anytypeio.anytype.presentation.sets.DataViewViewState import com.anytypeio.anytype.presentation.sets.ObjectSetViewModel import com.anytypeio.anytype.presentation.sets.main.ObjectSetViewModelTestSetup @@ -75,6 +76,7 @@ class CollectionViewerTypeAndTemplateTest: ObjectSetViewModelTestSetup() { @Before fun setup() { closable = MockitoAnnotations.openMocks(this) + fieldParser = FieldParserImpl(dateProvider, logger) givenNetworkNodeMocked() viewModel = givenViewModel() stubAddObjectToCollection() diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/templates/SetByRelationViewerTypeAndTemplateTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/templates/SetByRelationViewerTypeAndTemplateTest.kt index 29a2cbc58f..cafe300e5e 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/templates/SetByRelationViewerTypeAndTemplateTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/templates/SetByRelationViewerTypeAndTemplateTest.kt @@ -12,6 +12,7 @@ import com.anytypeio.anytype.core_models.StubObject import com.anytypeio.anytype.core_models.StubRelationLink import com.anytypeio.anytype.core_models.primitives.TypeKey import com.anytypeio.anytype.domain.dataview.interactor.CreateDataViewObject +import com.anytypeio.anytype.domain.primitives.FieldParserImpl import com.anytypeio.anytype.presentation.sets.DataViewViewState import com.anytypeio.anytype.presentation.sets.ObjectSetViewModel import com.anytypeio.anytype.presentation.sets.main.ObjectSetViewModelTestSetup @@ -83,6 +84,7 @@ class SetByRelationViewerTypeAndTemplateTest : ObjectSetViewModelTestSetup() { @Before fun setup() { closable = MockitoAnnotations.openMocks(this) + fieldParser = FieldParserImpl(dateProvider, logger) viewModel = givenViewModel() stubNetworkMode() stubObservePermissions() diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/templates/SetByTypeViewerTypeAndTemplateTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/templates/SetByTypeViewerTypeAndTemplateTest.kt index 927f51a70a..2df0490c05 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/templates/SetByTypeViewerTypeAndTemplateTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/templates/SetByTypeViewerTypeAndTemplateTest.kt @@ -13,6 +13,7 @@ import com.anytypeio.anytype.core_models.StubRelationLink import com.anytypeio.anytype.core_models.StubRelationObject import com.anytypeio.anytype.core_models.primitives.TypeKey import com.anytypeio.anytype.domain.dataview.interactor.CreateDataViewObject +import com.anytypeio.anytype.domain.primitives.FieldParserImpl import com.anytypeio.anytype.presentation.sets.DataViewViewState import com.anytypeio.anytype.presentation.sets.ObjectSetViewModel import com.anytypeio.anytype.presentation.sets.main.ObjectSetViewModelTestSetup @@ -63,6 +64,7 @@ class SetByTypeViewerTypeAndTemplateTest : ObjectSetViewModelTestSetup() { @Before fun setup() { closable = MockitoAnnotations.openMocks(this) + fieldParser = FieldParserImpl(dateProvider, logger) getNetworkMode.stub { onBlocking { run(any()) diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/types/ObjectTypeChangeViewModelTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/types/ObjectTypeChangeViewModelTest.kt index 7668a1be50..16ff90d7ec 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/types/ObjectTypeChangeViewModelTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/types/ObjectTypeChangeViewModelTest.kt @@ -7,7 +7,6 @@ import com.anytypeio.anytype.core_models.DVFilterCondition import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_models.Marketplace.MARKETPLACE_SPACE_ID import com.anytypeio.anytype.core_models.MarketplaceObjectTypeIds -import com.anytypeio.anytype.core_models.ObjectType import com.anytypeio.anytype.core_models.ObjectTypeIds import com.anytypeio.anytype.core_models.Relations import com.anytypeio.anytype.core_models.StubObjectType @@ -23,7 +22,7 @@ import com.anytypeio.anytype.domain.spaces.AddObjectTypeToSpace import com.anytypeio.anytype.domain.workspace.SpaceManager import com.anytypeio.anytype.presentation.objects.ObjectTypeChangeViewModel import com.anytypeio.anytype.presentation.objects.ObjectTypeView -import com.anytypeio.anytype.presentation.objects.SupportedLayouts +import com.anytypeio.anytype.core_models.SupportedLayouts import com.anytypeio.anytype.presentation.search.ObjectSearchConstants import com.anytypeio.anytype.presentation.util.CoroutinesTestRule import com.anytypeio.anytype.test_utils.MockDataFactory diff --git a/settings.gradle b/settings.gradle index 268288b102..afc75da1cf 100644 --- a/settings.gradle +++ b/settings.gradle @@ -57,7 +57,8 @@ include ':app', ':test:android-utils', ':test:utils', ':test:core-models-stub', - ':libs' + ':libs', + ':feature-date' include ':ui-settings' include ':crash-reporting' @@ -66,3 +67,4 @@ include ':payments' include ':gallery-experience' include ':feature-discussions' include ':feature-all-content' +include ':feature-date'