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

DROID-2794 Date as an object | Slash menu, select date (#1870)

This commit is contained in:
Konstantin Ivanov 2024-12-05 12:28:57 +01:00 committed by GitHub
parent 9060dedb86
commit c84dad4e6f
Signed by: github
GPG key ID: B5690EEEBB952194
14 changed files with 329 additions and 177 deletions

View file

@ -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<String>(Relations.ID)
val name = dateObject?.getSingleValue<String>(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<String>(Relations.ID)
val name = dateObject?.getSingleValue<String>(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

View file

@ -132,7 +132,8 @@ object SlashExtensions {
fun getSlashWidgetObjectTypeItems(objectTypes: List<ObjectTypeView>): List<SlashItem> =
listOf(
SlashItem.Subheader.ObjectTypeWithBlack,
SlashItem.Actions.LinkTo
SlashItem.Actions.LinkTo,
SlashItem.Actions.SelectDate
) + objectTypes.toSlashItemView()
fun getSlashWidgetRelationItems(relations: List<SlashRelationView>): List<SlashRelationView> =
@ -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,

View file

@ -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<String>? = null
}
object SelectDate : Actions() {
override fun getSearchName(): String = SlashConst.SLASH_ACTION_SELECT_DATE
override fun getAbbreviation(): List<String>? = listOf(
SLASH_ACTION_SELECT_DATE_ABBREVIATION
)
}
}
//endregion

View file

@ -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()
}
}

View file

@ -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,

View file

@ -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,