From c84dad4e6ffc2061c916ae79bff1d06d026c0a5c Mon Sep 17 00:00:00 2001 From: Konstantin Ivanov <54908981+konstantiniiv@users.noreply.github.com> Date: Thu, 5 Dec 2024 12:28:57 +0100 Subject: [PATCH] DROID-2794 Date as an object | Slash menu, select date (#1870) --- .../features/editor/base/EditorTestSetup.kt | 2 +- .../anytypeio/anytype/di/feature/EditorDI.kt | 19 -- .../features/editor/EditorDatePicker.kt | 49 ++- .../editor/slash/holders/ActionMenuHolder.kt | 5 + .../layout/item_slash_widget_object_type.xml | 63 ++-- .../anytype/core_utils/const/SlashConst.kt | 2 + .../domain/page/CreateBlockLinkWithObject.kt | 2 - .../page/CreateBlockLinkWithObjectTest.kt | 15 +- .../presentation/editor/EditorViewModel.kt | 308 ++++++++++++------ .../editor/editor/slash/SlashExtensions.kt | 5 +- .../editor/editor/slash/SlashItem.kt | 8 + .../editor/model/EditorDatePicker.kt | 25 +- .../editor/EditorSlashWidgetClicksTest.kt | 1 + .../editor/EditorSlashWidgetFilterTest.kt | 2 + 14 files changed, 329 insertions(+), 177 deletions(-) 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 3ec9cae52a..a7bdc61e6a 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 @@ -347,7 +347,7 @@ open class EditorTestSetup { setupBookmark = SetupBookmark(repo) updateAlignment = UpdateAlignment(repo) uploadBlock = UploadBlock(repo) - createBlockLinkWithObject = CreateBlockLinkWithObject(repo, getTemplates, dispatchers) + createBlockLinkWithObject = CreateBlockLinkWithObject(repo, dispatchers) setRelationKey = SetRelationKey(repo) turnIntoDocument = TurnIntoDocument(repo) updateFields = UpdateFields(repo) 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 3a8d8f6d9a..a6c2a3244b 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 @@ -96,7 +96,6 @@ import com.anytypeio.anytype.domain.table.MoveTableColumn import com.anytypeio.anytype.domain.table.MoveTableRow import com.anytypeio.anytype.domain.table.SetTableRowHeader import com.anytypeio.anytype.domain.templates.ApplyTemplate -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.workspace.FileLimitsEventChannel @@ -722,12 +721,10 @@ object EditorUseCaseModule { @PerScreen fun provideCreateObjectUseCase( repo: BlockRepository, - getTemplates: GetTemplates, dispatchers: AppCoroutineDispatchers ): CreateBlockLinkWithObject = CreateBlockLinkWithObject( repo = repo, - getTemplates = getTemplates, dispatchers = dispatchers ) @@ -736,8 +733,6 @@ object EditorUseCaseModule { @PerScreen fun provideCreateObjectAsMentionOrLink( repo: BlockRepository, - getDefaultObjectType: GetDefaultObjectType, - getTemplates: GetTemplates, dispatchers: AppCoroutineDispatchers, spaceManager: SpaceManager ): CreateObjectAsMentionOrLink = CreateObjectAsMentionOrLink( @@ -1012,20 +1007,6 @@ object EditorUseCaseModule { repo: BlockRepository ): SetDocumentImageIcon = SetDocumentImageIcon(repo) - @JvmStatic - @Provides - @PerScreen - fun getTemplates( - repo: BlockRepository, - spaceManager: SpaceManager, - dispatchers: AppCoroutineDispatchers - ): GetTemplates = - GetTemplates( - repo = repo, - spaceManager = spaceManager, - dispatchers = dispatchers - ) - @JvmStatic @Provides @PerScreen 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 index 254eb57ce2..31b345fdb1 100644 --- 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 @@ -13,9 +13,14 @@ 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.EditorDatePickerState.Visible import com.anytypeio.anytype.presentation.editor.model.OnEditorDatePickerEvent +import com.anytypeio.anytype.presentation.editor.model.OnEditorDatePickerEvent.OnDateSelected +import com.anytypeio.anytype.presentation.editor.model.OnEditorDatePickerEvent.OnTodayClick +import com.anytypeio.anytype.presentation.editor.model.OnEditorDatePickerEvent.OnTomorrowClick import com.anytypeio.anytype.presentation.sets.DateValueView + @OptIn(ExperimentalMaterial3Api::class) @Composable fun EditorDatePicker( @@ -24,7 +29,7 @@ fun EditorDatePicker( onEvent: (OnEditorDatePickerEvent) -> Unit ) { - if (uiState !is EditorDatePickerState.Visible) return + if (uiState !is Visible) return val bottomSheetState = rememberModalBottomSheetState( skipPartiallyExpanded = true @@ -47,16 +52,40 @@ fun EditorDatePicker( DatePickerContent( state = DateValueView(timeInMillis = null), showHeader = false, - onDateSelected = { - onEvent( - OnEditorDatePickerEvent.OnDateSelected( - timeInMillis = it - ) - ) - }, - onTodayClicked = { onEvent(OnEditorDatePickerEvent.OnTodayClick) }, - onTomorrowClicked = { onEvent(OnEditorDatePickerEvent.OnTomorrowClick) } + onDateSelected = { onEvent(getDateSelectedEvent(uiState, it)) }, + onTodayClicked = { onEvent(getTodayEvent(uiState)) }, + onTomorrowClicked = { onEvent(getTomorrowEvent(uiState)) } ) }, ) +} + +private fun getTodayEvent(uiState: Visible): OnTodayClick { + return when (uiState) { + is Visible.Link -> OnTodayClick.Link(uiState.targetId) + is Visible.Mention -> OnTodayClick.Mention(uiState.targetId) + } +} + +private fun getTomorrowEvent(uiState: Visible): OnTomorrowClick { + return when (uiState) { + is Visible.Link -> OnTomorrowClick.Link(uiState.targetId) + is Visible.Mention -> OnTomorrowClick.Mention(uiState.targetId) + } +} + +private fun getDateSelectedEvent( + uiState: Visible, + timeInMillis: Long? +): OnDateSelected { + return when (uiState) { + is Visible.Link -> OnDateSelected.Link( + timeInMillis = timeInMillis, + targetId = uiState.targetId + ) + is Visible.Mention -> OnDateSelected.Mention( + timeInMillis = timeInMillis, + targetId = uiState.targetId + ) + } } \ No newline at end of file diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/slash/holders/ActionMenuHolder.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/slash/holders/ActionMenuHolder.kt index e6e7aaa61d..1bd354144d 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/slash/holders/ActionMenuHolder.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/slash/holders/ActionMenuHolder.kt @@ -50,6 +50,11 @@ class ActionMenuHolder(val binding: ItemSlashWidgetStyleBinding) : RecyclerView. ivIcon.setImageResource(R.drawable.ic_slash_actions_link_to) tvSubtitle.setText(R.string.slash_widget_actions_link_to_subtitle) } + SlashItem.Actions.SelectDate -> { + tvTitle.setText(R.string.select_date) + ivIcon.setImageResource(R.drawable.ic_obj_date_24) + tvSubtitle.gone() + } } } } \ No newline at end of file diff --git a/core-ui/src/main/res/layout/item_slash_widget_object_type.xml b/core-ui/src/main/res/layout/item_slash_widget_object_type.xml index 9001056006..bbe4fc127e 100644 --- a/core-ui/src/main/res/layout/item_slash_widget_object_type.xml +++ b/core-ui/src/main/res/layout/item_slash_widget_object_type.xml @@ -2,10 +2,10 @@ + android:layout_height="56dp" + android:paddingStart="@dimen/dp_20" + android:paddingEnd="@dimen/dp_20"> - + android:layout_marginEnd="@dimen/dp_16" + android:layout_gravity="center_vertical" + android:orientation="vertical"> - + + + + + android:background="@drawable/divider_relations" /> \ No newline at end of file diff --git a/core-utils/src/main/java/com/anytypeio/anytype/core_utils/const/SlashConst.kt b/core-utils/src/main/java/com/anytypeio/anytype/core_utils/const/SlashConst.kt index 4238d18738..c359c957cf 100644 --- a/core-utils/src/main/java/com/anytypeio/anytype/core_utils/const/SlashConst.kt +++ b/core-utils/src/main/java/com/anytypeio/anytype/core_utils/const/SlashConst.kt @@ -24,6 +24,8 @@ object SlashConst { const val SLASH_ACTION_COPY = "Copy" const val SLASH_ACTION_PASTE = "Paste" const val SLASH_ACTION_LINK_TO = "Link to object" + const val SLASH_ACTION_SELECT_DATE = "Select date" + const val SLASH_ACTION_SELECT_DATE_ABBREVIATION = "date" const val SLASH_ACTION_MOVE = "Move" const val SLASH_ACTION_MOVE_TO = "Move to" const val SLASH_ACTION_CLEAN_STYLE = "Clean style" diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/page/CreateBlockLinkWithObject.kt b/domain/src/main/java/com/anytypeio/anytype/domain/page/CreateBlockLinkWithObject.kt index a540b58678..e88a4538ca 100644 --- a/domain/src/main/java/com/anytypeio/anytype/domain/page/CreateBlockLinkWithObject.kt +++ b/domain/src/main/java/com/anytypeio/anytype/domain/page/CreateBlockLinkWithObject.kt @@ -6,7 +6,6 @@ import com.anytypeio.anytype.core_models.primitives.TypeKey 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.templates.GetTemplates /** * UseCase for creating a new Object with block linked to this Object @@ -14,7 +13,6 @@ import com.anytypeio.anytype.domain.templates.GetTemplates class CreateBlockLinkWithObject( private val repo: BlockRepository, - private val getTemplates: GetTemplates, dispatchers: AppCoroutineDispatchers ) : ResultInteractor(dispatchers.io) { diff --git a/domain/src/test/java/com/anytypeio/anytype/domain/page/CreateBlockLinkWithObjectTest.kt b/domain/src/test/java/com/anytypeio/anytype/domain/page/CreateBlockLinkWithObjectTest.kt index bcadb147c5..4d302fa365 100644 --- a/domain/src/test/java/com/anytypeio/anytype/domain/page/CreateBlockLinkWithObjectTest.kt +++ b/domain/src/test/java/com/anytypeio/anytype/domain/page/CreateBlockLinkWithObjectTest.kt @@ -4,14 +4,11 @@ import com.anytypeio.anytype.core_models.Command import com.anytypeio.anytype.core_models.CoroutineTestRule import com.anytypeio.anytype.core_models.CreateBlockLinkWithObjectResult import com.anytypeio.anytype.core_models.InternalFlags -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.Relations import com.anytypeio.anytype.core_models.primitives.TypeId import com.anytypeio.anytype.core_models.primitives.TypeKey import com.anytypeio.anytype.domain.block.repo.BlockRepository -import com.anytypeio.anytype.domain.templates.GetTemplates import com.anytypeio.anytype.domain.util.dispatchers import com.anytypeio.anytype.test_utils.MockDataFactory import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -39,14 +36,11 @@ class CreateBlockLinkWithObjectTest { @Mock lateinit var repo: BlockRepository - @Mock - lateinit var getTemplates: GetTemplates - lateinit var createBlockLinkWithObject: CreateBlockLinkWithObject @Before fun setup() { - createBlockLinkWithObject = CreateBlockLinkWithObject(repo, getTemplates, dispatchers) + createBlockLinkWithObject = CreateBlockLinkWithObject(repo, dispatchers) } @Test @@ -60,7 +54,6 @@ class CreateBlockLinkWithObjectTest { val typeId = MockDataFactory.randomString() val type = MockDataFactory.randomString() stubCreateBlockLinkWithObject() - givenGetTemplates(listOf()) //TESTING val params = CreateBlockLinkWithObject.Params( @@ -166,12 +159,6 @@ class CreateBlockLinkWithObjectTest { verifyBlocking(repo, times(1)) { createBlockLinkWithObject(commands) } } - private fun givenGetTemplates(objects: List = listOf()) { - getTemplates.stub { - onBlocking { run(any()) } doReturn objects - } - } - private fun stubCreateBlockLinkWithObject() { repo.stub { onBlocking { createBlockLinkWithObject(any()) } doReturn CreateBlockLinkWithObjectResult( 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 61168173d3..cee9c5deb8 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 @@ -250,6 +250,12 @@ 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.core_models.SupportedLayouts +import com.anytypeio.anytype.presentation.editor.ControlPanelMachine.Event.SAM.* +import com.anytypeio.anytype.presentation.editor.editor.Intent.Clipboard.* +import com.anytypeio.anytype.presentation.editor.model.OnEditorDatePickerEvent.OnDatePickerDismiss +import com.anytypeio.anytype.presentation.editor.model.OnEditorDatePickerEvent.OnDateSelected +import com.anytypeio.anytype.presentation.editor.model.OnEditorDatePickerEvent.OnTodayClick +import com.anytypeio.anytype.presentation.editor.model.OnEditorDatePickerEvent.OnTomorrowClick import com.anytypeio.anytype.presentation.objects.getCreateObjectParams import com.anytypeio.anytype.presentation.objects.getObjectTypeViewsForSBPage import com.anytypeio.anytype.presentation.objects.getProperType @@ -3273,6 +3279,16 @@ class EditorViewModel( ) } } + ObjectType.Layout.DATE -> { + navigate( + EventWrapper( + OpenDateObject( + objectId = target, + space = vmParams.space.id + ) + ) + ) + } else -> { sendToast("Cannot open object with layout: ${wrapper.layout}") } @@ -5392,7 +5408,7 @@ class EditorViewModel( } SlashItem.Actions.Copy -> { val block = blocks.first { it.id == targetId } - val intent = Intent.Clipboard.Copy( + val intent = Copy( context = context, range = null, blocks = listOf(block) @@ -5404,7 +5420,7 @@ class EditorViewModel( SlashItem.Actions.Paste -> { viewModelScope.launch { orchestrator.proxies.intents.send( - Intent.Clipboard.Paste( + Paste( context = context, focus = targetId, range = IntRange(slashStartIndex, slashStartIndex), @@ -5431,7 +5447,7 @@ class EditorViewModel( orchestrator.stores.views.update(updated) renderCommand.send(Unit) controlPanelInteractor.onEvent( - ControlPanelMachine.Event.SAM.OnQuickStart( + OnQuickStart( currentSelection().size ) ) @@ -5449,6 +5465,11 @@ class EditorViewModel( onHideKeyboardClicked() proceedWithLinkToButtonClicked(block = targetId, position = slashStartIndex) } + SlashItem.Actions.SelectDate -> { + mentionDatePicker.value = EditorDatePickerState.Visible.Link( + targetId = targetId + ) + } } } @@ -6156,100 +6177,12 @@ class EditorViewModel( 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) + val targetId = orchestrator.stores.focus.current().targetOrNull() + if (targetId == null) { + Timber.e("Error while getting targetId from focus") + return } - 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 - ) - 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.") - } - ) + mentionDatePicker.value = EditorDatePickerState.Visible.Mention(targetId = targetId) } } @@ -7678,6 +7611,189 @@ class EditorViewModel( } //endregion + //region CALENDAR + private enum class EditorCalendarDateShortcuts { + TODAY, + TOMORROW + } + + private enum class EditorCalendarActionType { + MENTION, + LINK + } + + fun onEditorDatePickerEvent(event: OnEditorDatePickerEvent) { + Timber.d("onEditorDatePickerEvent, event:[$event]") + + when (event) { + is OnDateSelected.Mention -> { + handleDatePickerDismiss() + dispatch(Command.ShowKeyboard) + handleDateSelected( + timeInMillis = event.timeInMillis, + actionType = EditorCalendarActionType.MENTION, + targetId = event.targetId + ) + } + is OnDateSelected.Link -> { + cutSlashFilter(targetId = event.targetId) + controlPanelInteractor.onEvent(ControlPanelMachine.Event.Slash.OnStop) + handleDatePickerDismiss() + handleDateSelected( + timeInMillis = event.timeInMillis, + actionType = EditorCalendarActionType.LINK, + targetId = event.targetId + ) + proceedWithClearingFocus() + } + is OnTodayClick.Mention -> { + handleDatePickerDismiss() + dispatch(Command.ShowKeyboard) + handlePredefinedDateClick( + predefinedDate = EditorCalendarDateShortcuts.TODAY, + actionType = EditorCalendarActionType.MENTION, + targetId = event.targetId + ) + } + is OnTodayClick.Link -> { + handleDatePickerDismiss() + cutSlashFilter(targetId = event.targetId) + controlPanelInteractor.onEvent(ControlPanelMachine.Event.Slash.OnStop) + handlePredefinedDateClick( + predefinedDate = EditorCalendarDateShortcuts.TODAY, + actionType = EditorCalendarActionType.LINK, + targetId = event.targetId + ) + proceedWithClearingFocus() + } + is OnTomorrowClick.Mention -> { + handleDatePickerDismiss() + dispatch(Command.ShowKeyboard) + handlePredefinedDateClick( + predefinedDate = EditorCalendarDateShortcuts.TOMORROW, + actionType = EditorCalendarActionType.MENTION, + targetId = event.targetId + ) + } + is OnTomorrowClick.Link -> { + handleDatePickerDismiss() + cutSlashFilter(targetId = event.targetId) + controlPanelInteractor.onEvent(ControlPanelMachine.Event.Slash.OnStop) + handlePredefinedDateClick( + predefinedDate = EditorCalendarDateShortcuts.TOMORROW, + actionType = EditorCalendarActionType.LINK, + targetId = event.targetId + ) + proceedWithClearingFocus() + } + is OnDatePickerDismiss -> handleDatePickerDismiss() + } + } + + private fun handleDateSelected( + timeInMillis: Long?, + actionType: EditorCalendarActionType, + targetId: Id + ) { + if (timeInMillis == null) { + sendToast("Selected time is invalid.") + Timber.w("OnDateSelected received null timeInMillis") + return + } + + val adjustedTimestamp = dateProvider.adjustFromStartOfDayInUserTimeZoneToUTC(timeInMillis) + handleTimestamp(adjustedTimestamp, actionType, targetId) + } + + private fun handlePredefinedDateClick( + predefinedDate: EditorCalendarDateShortcuts, + actionType: EditorCalendarActionType, + targetId: Id + ) { + val timestamp = when (predefinedDate) { + EditorCalendarDateShortcuts.TODAY -> dateProvider.getTimestampForTodayAtStartOfDay() + EditorCalendarDateShortcuts.TOMORROW -> dateProvider.getTimestampForTomorrowAtStartOfDay() + } + handleTimestamp(timestamp, actionType, targetId) + } + + private fun handleTimestamp(timestamp: Long, actionType: EditorCalendarActionType, targetId: Id) { + 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) { + when (actionType) { + EditorCalendarActionType.MENTION -> onCreateMentionInText( + id = id, + name = name, + mentionTrigger = mentionFilter.value + ) + EditorCalendarActionType.LINK -> onCreateDateLink( + linkId = id, + targetId = targetId + ) + } + } else { + sendToast("Error while creating ${actionType.name.lowercase()}, 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 + ) + 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.") + } + ) + } + } + + private fun onCreateDateLink(linkId: String, targetId: Id) { + Timber.d("Link created with id: $linkId, targetId: $targetId") + val targetBlock = blocks.firstOrNull { it.id == targetId } + if (targetBlock != null) { + val targetContent = targetBlock.content + val position = if (targetContent is Content.Text && targetContent.text.isEmpty()) { + Position.REPLACE + } else { + Position.BOTTOM + } + viewModelScope.launch{ + orchestrator.proxies.intents.send( + Intent.CRUD.Create( + context = context, + target = targetId, + position = position, + prototype = Prototype.Link(target = linkId), + onSuccess = {} + ) + ) + } + } else { + Timber.e("Can't find target block for link") + sendToast("Error while creating link") + } + } + //endregion + data class Params( val ctx: Id, val space: SpaceId diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/slash/SlashExtensions.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/slash/SlashExtensions.kt index 4bc12665c7..ea7cb45b55 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/slash/SlashExtensions.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/slash/SlashExtensions.kt @@ -132,7 +132,8 @@ object SlashExtensions { fun getSlashWidgetObjectTypeItems(objectTypes: List): List = listOf( SlashItem.Subheader.ObjectTypeWithBlack, - SlashItem.Actions.LinkTo + SlashItem.Actions.LinkTo, + SlashItem.Actions.SelectDate ) + objectTypes.toSlashItemView() fun getSlashWidgetRelationItems(relations: List): List = @@ -202,7 +203,7 @@ object SlashExtensions { ) val filteredObjects = filterObjectTypes( filter = filter, - items = listOf(SlashItem.Actions.LinkTo) + objectTypes + items = listOf(SlashItem.Actions.LinkTo, SlashItem.Actions.SelectDate) + objectTypes ) val filteredRelations = filterRelations( filter = filter, diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/slash/SlashItem.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/slash/SlashItem.kt index daea13f0ce..ffd610706c 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/slash/SlashItem.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/slash/SlashItem.kt @@ -3,6 +3,7 @@ package com.anytypeio.anytype.presentation.editor.editor.slash import com.anytypeio.anytype.core_utils.const.SlashConst import com.anytypeio.anytype.core_utils.const.SlashConst.SLASH_OTHER_TOC_ABBREVIATION import com.anytypeio.anytype.core_models.ThemeColor +import com.anytypeio.anytype.core_utils.const.SlashConst.SLASH_ACTION_SELECT_DATE_ABBREVIATION import com.anytypeio.anytype.presentation.objects.ObjectTypeView sealed class SlashWidgetState { @@ -395,6 +396,13 @@ sealed class SlashItem { override fun getSearchName(): String = SlashConst.SLASH_ACTION_LINK_TO override fun getAbbreviation(): List? = null } + + object SelectDate : Actions() { + override fun getSearchName(): String = SlashConst.SLASH_ACTION_SELECT_DATE + override fun getAbbreviation(): List? = listOf( + SLASH_ACTION_SELECT_DATE_ABBREVIATION + ) + } } //endregion 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 index f8ecacd152..ff6b9f5c1e 100644 --- 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 @@ -1,13 +1,30 @@ package com.anytypeio.anytype.presentation.editor.model +import com.anytypeio.anytype.core_models.Id + sealed class EditorDatePickerState { data object Hidden : EditorDatePickerState() - data object Visible : EditorDatePickerState() + sealed class Visible(val targetId: Id) : EditorDatePickerState() { + class Mention(targetId: Id) : Visible(targetId) + class Link(targetId: Id) : Visible(targetId) + } } sealed class OnEditorDatePickerEvent { + sealed class OnDateSelected : OnEditorDatePickerEvent() { + data class Mention(val timeInMillis: Long?, val targetId: Id) : OnDateSelected() + data class Link(val timeInMillis: Long?, val targetId: Id) : OnDateSelected() + } + object OnDatePickerDismiss : OnEditorDatePickerEvent() - data class OnDateSelected(val timeInMillis: Long?) : OnEditorDatePickerEvent() - object OnTodayClick : OnEditorDatePickerEvent() - object OnTomorrowClick : OnEditorDatePickerEvent() + + sealed class OnTodayClick : OnEditorDatePickerEvent() { + data class Mention(val targetId: Id) : OnTodayClick() + data class Link(val targetId: Id) : OnTodayClick() + } + + sealed class OnTomorrowClick : OnEditorDatePickerEvent() { + data class Mention(val targetId: Id) : OnTomorrowClick() + data class Link(val targetId: Id) : OnTomorrowClick() + } } \ No newline at end of file diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorSlashWidgetClicksTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorSlashWidgetClicksTest.kt index c1f3ed0de8..5a3b0b6943 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorSlashWidgetClicksTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorSlashWidgetClicksTest.kt @@ -360,6 +360,7 @@ class EditorSlashWidgetClicksTest: EditorPresentationTestSetup() { val expectedObjectItems = listOf( SlashItem.Subheader.ObjectTypeWithBlack, SlashItem.Actions.LinkTo, + SlashItem.Actions.SelectDate, SlashItem.ObjectType( objectTypeView = ObjectTypeView( id = type1.id, diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorSlashWidgetFilterTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorSlashWidgetFilterTest.kt index 7a9cfc98cd..d209e1c6e5 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorSlashWidgetFilterTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorSlashWidgetFilterTest.kt @@ -478,6 +478,7 @@ class EditorSlashWidgetFilterTest : EditorPresentationTestSetup() { val expectedItems = listOf( SlashItem.Subheader.Actions, SlashItem.Actions.LinkTo, + SlashItem.Actions.SelectDate, SlashItem.ObjectType( objectTypeView = ObjectTypeView( id = type1.id, @@ -1561,6 +1562,7 @@ class EditorSlashWidgetFilterTest : EditorPresentationTestSetup() { val expectedItems = listOf( SlashItem.Subheader.Actions, SlashItem.Actions.LinkTo, + SlashItem.Actions.SelectDate, SlashItem.ObjectType( objectTypeView = ObjectTypeView( id = type1.id,