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

Collection | Enhancement | Default type and template for View, part 2 (#409)

This commit is contained in:
Konstantin Ivanov 2023-09-29 15:38:12 +03:00 committed by GitHub
parent 6a1b17b66e
commit 30068e3f45
Signed by: github
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 247 additions and 153 deletions

View file

@ -96,7 +96,9 @@ import com.anytypeio.anytype.ui.editor.sheets.ObjectMenuBaseFragment
import com.anytypeio.anytype.ui.objects.BaseObjectTypeChangeFragment
import com.anytypeio.anytype.ui.objects.types.pickers.DataViewSelectSourceFragment
import com.anytypeio.anytype.ui.objects.types.pickers.EmptyDataViewSelectSourceFragment
import com.anytypeio.anytype.ui.objects.types.pickers.ObjectSelectTypeFragment
import com.anytypeio.anytype.ui.objects.types.pickers.OnDataViewSelectSourceAction
import com.anytypeio.anytype.ui.objects.types.pickers.OnObjectSelectTypeAction
import com.anytypeio.anytype.ui.relations.RelationDateValueFragment
import com.anytypeio.anytype.ui.relations.RelationDateValueFragment.DateValueEditReceiver
import com.anytypeio.anytype.ui.relations.RelationTextValueFragment
@ -121,7 +123,8 @@ open class ObjectSetFragment :
NavigationFragment<FragmentObjectSetBinding>(R.layout.fragment_object_set),
TextValueEditReceiver,
DateValueEditReceiver,
OnDataViewSelectSourceAction {
OnDataViewSelectSourceAction,
OnObjectSelectTypeAction {
// Controls
@ -1029,6 +1032,13 @@ open class ObjectSetFragment :
val fr = ManageViewerFragment.new(ctx = command.ctx, dv = command.dataview)
fr.showChildFragment(EMPTY_TAG)
}
is ObjectSetCommand.Modal.OpenSelectTypeScreen -> {
val fr = ObjectSelectTypeFragment.newInstance(
excludeTypes = command.excludedTypes
)
fr.showChildFragment()
}
}
}
@ -1217,6 +1227,14 @@ open class ObjectSetFragment :
inflater, container, false
)
override fun onProceedWithUpdateType(id: Id) {
vm.onNewTypeForViewerClicked(id)
}
override fun onProceedWithDraftUpdateType(id: Id) {
// Do nothing
}
private fun observeSelectingTemplate() {
val navController = findNavController()
val navBackStackEntry = navController.getBackStackEntry(R.id.objectSetScreen)

View file

@ -3,5 +3,5 @@ package com.anytypeio.anytype.core_models
data class CreateObjectResult(
val id: Id,
val event: Payload,
val details: Struct?
val details: Struct
)

View file

@ -64,12 +64,16 @@ import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.em
import androidx.compose.ui.unit.sp
import coil.compose.rememberAsyncImagePainter
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.ObjectType
@ -84,7 +88,7 @@ import com.anytypeio.anytype.core_ui.views.BodyCalloutRegular
import com.anytypeio.anytype.core_ui.views.Caption1Medium
import com.anytypeio.anytype.core_ui.views.Caption2Semibold
import com.anytypeio.anytype.core_ui.views.ModalTitle
import com.anytypeio.anytype.core_ui.views.TitleInter15
import com.anytypeio.anytype.core_ui.views.fontInterRegular
import com.anytypeio.anytype.emojifier.Emojifier
import com.anytypeio.anytype.presentation.editor.cover.CoverGradient
import com.anytypeio.anytype.presentation.templates.TemplateMenuClick
@ -157,6 +161,7 @@ fun TypeTemplatesWidget(
var currentClickedMoreButtonCoordinates: IntOffset by remember {
mutableStateOf(IntOffset(0, 0))
}
val showPlusButton = remember { mutableStateOf(false) }
AnimatedVisibility(
visible = currentState.showWidget,
@ -242,18 +247,29 @@ fun TypeTemplatesWidget(
color = colorResource(R.color.text_primary)
)
}
// Box(modifier = Modifier.align(Alignment.CenterEnd)) {
// Image(
// modifier = Modifier.padding(
// start = 16.dp,
// top = 12.dp,
// bottom = 12.dp,
// end = 16.dp
// ),
// painter = painterResource(id = R.drawable.ic_default_plus),
// contentDescription = null
// )
// }
if (showPlusButton.value) {
Box(
modifier = Modifier.align(Alignment.CenterEnd)
.noRippleThrottledClickable {
val templates = (currentState as? TypeTemplatesWidgetUI.Data)?.templates
val newTemplate = templates?.firstOrNull { it is TemplateView.New }
if (newTemplate != null) {
action(TemplateClick(newTemplate))
}
}
) {
Image(
modifier = Modifier.padding(
start = 16.dp,
top = 12.dp,
bottom = 12.dp,
end = 16.dp
),
painter = painterResource(id = R.drawable.ic_default_plus),
contentDescription = null
)
}
}
}
val itemsScroll = rememberLazyListState()
if ((currentState as? TypeTemplatesWidgetUI.Data)?.isPossibleToChangeType == true) {
@ -286,7 +302,8 @@ fun TypeTemplatesWidget(
moreClick(template)
},
action = action,
scrollState = itemsScroll
scrollState = itemsScroll,
showPlusButton = { showPlusButton.value = it }
)
if ((currentState as TypeTemplatesWidgetUI.Data).moreMenuItem != null
&& itemsScroll.isScrollInProgress
@ -305,11 +322,15 @@ fun TypeTemplatesWidget(
currentCoordinates = currentClickedMoreButtonCoordinates,
menuClick = menuClick
)
is TemplateView.Template -> MoreMenu(
itemId = templateView.id,
currentCoordinates = currentClickedMoreButtonCoordinates,
menuClick = menuClick
)
is TemplateView.Template -> {
val withDefaultForView = currentState is TypeTemplatesWidgetUI.Data.DefaultObject
MoreMenu(
itemId = templateView.id,
currentCoordinates = currentClickedMoreButtonCoordinates,
menuClick = menuClick,
withDefaultForView = withDefaultForView
)
}
is TemplateView.New -> Unit
}
}
@ -320,7 +341,8 @@ fun TypeTemplatesWidget(
private fun MoreMenu(
itemId: Id,
currentCoordinates: IntOffset,
menuClick: (TemplateMenuClick) -> Unit
menuClick: (TemplateMenuClick) -> Unit,
withDefaultForView: Boolean
) {
val moreButtonXCoordinatesDp = with(LocalDensity.current) { currentCoordinates.x.toDp() }
val offsetX = if (moreButtonXCoordinatesDp > 244.dp) {
@ -344,11 +366,13 @@ private fun MoreMenu(
shape = RoundedCornerShape(size = 10.dp)
)
) {
MenuItem(
click = { menuClick(TemplateMenuClick.Default(itemId)) },
text = stringResource(id = R.string.templates_menu_default_for_view)
)
Divider()
if (withDefaultForView) {
MenuItem(
click = { menuClick(TemplateMenuClick.Default(itemId)) },
text = stringResource(id = R.string.templates_menu_default_for_view)
)
Divider()
}
MenuItem(
click = { menuClick(TemplateMenuClick.Edit(itemId)) },
text = stringResource(id = R.string.templates_menu_edit)
@ -422,85 +446,104 @@ private fun TemplatesList(
scrollState: LazyListState,
state: TypeTemplatesWidgetUI.Data,
action: (TypeTemplatesWidgetUIAction) -> Unit,
moreClick: (TemplateView, IntOffset) -> Unit
moreClick: (TemplateView, IntOffset) -> Unit,
showPlusButton: (Boolean) -> Unit
) {
LazyRow(
state = scrollState,
modifier = Modifier
.wrapContentHeight()
.fillMaxWidth(),
contentPadding = PaddingValues(start = 20.dp, end = 20.dp),
horizontalArrangement = Arrangement.spacedBy(5.dp)
)
{
itemsIndexed(
items = state.templates,
itemContent = { index, item ->
Box(
modifier =
Modifier
.height(232.dp)
.width(127.dp),
contentAlignment = Alignment.BottomStart
) {
val borderWidth: Dp
val borderColor: Color
if (item.isDefault) {
borderWidth = 2.dp
borderColor = colorResource(id = R.color.palette_system_amber_50)
} else {
borderWidth = 1.dp
borderColor = colorResource(id = R.color.shape_primary)
}
if (state.templates.isEmpty()) {
showPlusButton.invoke(false)
Box(
modifier = Modifier.fillMaxWidth().wrapContentHeight(),
contentAlignment = Alignment.Center) {
Text(
modifier = Modifier
.wrapContentSize()
.padding(top = 111.dp, bottom = 111.dp),
text = stringResource(id = R.string.title_templates_not_allowed),
style = BodyCalloutRegular,
color = colorResource(id = R.color.text_secondary)
)
}
} else {
showPlusButton.invoke(true)
LazyRow(
state = scrollState,
modifier = Modifier
.wrapContentHeight()
.fillMaxWidth(),
contentPadding = PaddingValues(start = 20.dp, end = 20.dp),
horizontalArrangement = Arrangement.spacedBy(5.dp)
)
{
itemsIndexed(
items = state.templates,
itemContent = { index, item ->
Box(
modifier = Modifier
.border(
width = borderWidth,
color = borderColor,
shape = RoundedCornerShape(size = 16.dp)
)
.height(224.dp)
.width(120.dp)
.clickable {
action(TemplateClick(item))
}
modifier =
Modifier
.height(232.dp)
.width(127.dp),
contentAlignment = Alignment.BottomStart
) {
TemplateItemContent(item)
}
val showMoreButton = (item is TemplateView.Template && state.isEditing) || (state is TypeTemplatesWidgetUI.Data.DefaultObject && item is TemplateView.Blank && state.isEditing)
AnimatedVisibility(
visible = showMoreButton,
enter = fadeIn(),
exit = fadeOut(),
modifier = Modifier
.align(Alignment.TopEnd)
.padding(1.dp)
) {
var currentCoordinates: IntOffset by remember {
mutableStateOf(IntOffset(0, 0))
val borderWidth: Dp
val borderColor: Color
if (item.isDefault) {
borderWidth = 2.dp
borderColor = colorResource(id = R.color.palette_system_amber_50)
} else {
borderWidth = 1.dp
borderColor = colorResource(id = R.color.shape_primary)
}
Image(
Box(
modifier = Modifier
.width(28.dp)
.height(28.dp)
.clickable { moreClick(item, currentCoordinates) }
.onGloballyPositioned { coordinates ->
if (coordinates.isAttached) {
with(coordinates.positionInRoot()) {
currentCoordinates = IntOffset(x.toInt(), y.toInt())
.border(
width = borderWidth,
color = borderColor,
shape = RoundedCornerShape(size = 16.dp)
)
.height(224.dp)
.width(120.dp)
.clickable {
action(TemplateClick(item))
}
) {
TemplateItemContent(item)
}
val showMoreButton =
(item is TemplateView.Template && state.isEditing) || (state is TypeTemplatesWidgetUI.Data.DefaultObject && item is TemplateView.Blank && state.isEditing)
AnimatedVisibility(
visible = showMoreButton,
enter = fadeIn(),
exit = fadeOut(),
modifier = Modifier
.align(Alignment.TopEnd)
.padding(1.dp)
) {
var currentCoordinates: IntOffset by remember {
mutableStateOf(IntOffset(0, 0))
}
Image(
modifier = Modifier
.width(28.dp)
.height(28.dp)
.clickable { moreClick(item, currentCoordinates) }
.onGloballyPositioned { coordinates ->
if (coordinates.isAttached) {
with(coordinates.positionInRoot()) {
currentCoordinates = IntOffset(x.toInt(), y.toInt())
}
} else {
currentCoordinates = IntOffset(0, 0)
}
} else {
currentCoordinates = IntOffset(0, 0)
}
},
painter = painterResource(id = R.drawable.ic_edit_temlate),
contentDescription = "Edit template button"
)
},
painter = painterResource(id = R.drawable.ic_edit_temlate),
contentDescription = "Edit template button"
)
}
}
}
}
)
)
}
}
}
@ -780,7 +823,7 @@ private fun TemplateItemTitle(text: String, textAlign: TextAlign = TextAlign.Sta
end = 16.dp
),
text = text.ifBlank { stringResource(id = R.string.untitled) },
style = TitleInter15.copy(
style = TemplateTitleStyle.copy(
color = colorResource(id = R.color.text_primary)
),
maxLines = 2,
@ -788,6 +831,14 @@ private fun TemplateItemTitle(text: String, textAlign: TextAlign = TextAlign.Sta
)
}
val TemplateTitleStyle = TextStyle(
fontFamily = fontInterRegular,
fontWeight = FontWeight.W600,
fontSize = 11.sp,
lineHeight = 14.sp,
letterSpacing = (-0.006).em
)
@Composable
private fun TemplateItemTodoTitle(text: String) {
Row {

View file

@ -662,6 +662,7 @@
<string name="create">create</string>
<string name="default_object">Default object</string>
<string name="default_template">Default template</string>
<string name="title_templates_not_allowed">This type doesnt support templates</string>
<string name="layout">Layout</string>
<string name="num_applied">%1$d applied</string>
<string name="view_list">List</string>

View file

@ -37,7 +37,8 @@ class CreateDataViewObject(
val result = repo.createObject(command)
Result(
objectId = result.id,
objectType = params.type
objectType = params.type,
struct = result.details
)
}
is Params.SetByRelation -> {
@ -53,7 +54,8 @@ class CreateDataViewObject(
val result = repo.createObject(command)
Result(
objectId = result.id,
objectType = params.type
objectType = params.type,
struct = result.details
)
}
is Params.Collection -> {
@ -69,7 +71,8 @@ class CreateDataViewObject(
val result = repo.createObject(command)
Result(
objectId = result.id,
objectType = params.type
objectType = params.type,
struct = result.details
)
}
}
@ -168,7 +171,8 @@ class CreateDataViewObject(
data class Result(
val objectId : Id,
val objectType: Id?
val objectType: Id?,
val struct: Struct?
)
companion object {

View file

@ -206,7 +206,7 @@ class CreateObjectAsMentionOrLinkTest {
onBlocking { createObject(any()) } doReturn CreateObjectResult(
id = "",
event = Payload(context = "", events = listOf()),
details = null
details = emptyMap()
)
}
}

View file

@ -250,7 +250,7 @@ class CreateObjectTest {
onBlocking { createObject(any()) } doReturn CreateObjectResult(
id = "",
event = Payload(context = "", events = listOf()),
details = null
details = emptyMap()
)
}
}

View file

@ -712,7 +712,7 @@ fun Rpc.Object.Create.Response.toCoreModel(): CreateObjectResult {
return CreateObjectResult(
id = objectId,
event = event.toPayload(),
details = details
details = details.orEmpty()
)
}

View file

@ -89,6 +89,10 @@ sealed class ObjectSetCommand {
val ctx: Id,
val relation: Key
) : Modal()
data class OpenSelectTypeScreen(
val excludedTypes: List<Id>
) : Modal()
}
sealed class Intent : ObjectSetCommand() {

View file

@ -1052,7 +1052,7 @@ class ObjectSetViewModel(
createDataViewObject.async(params).fold(
onFailure = { Timber.e(it, "Error while creating new record") },
onSuccess = { result ->
proceedWithNewDataViewObject(params, result.objectId)
proceedWithNewDataViewObject(result)
action?.invoke(result)
sendAnalyticsObjectCreateEvent(
startTime = startTime,
@ -1063,27 +1063,14 @@ class ObjectSetViewModel(
}
}
private suspend fun proceedWithNewDataViewObject(params: CreateDataViewObject.Params, newObject: Id) {
when (params) {
is CreateDataViewObject.Params.Collection -> {
proceedWithOpeningObject(newObject)
}
is CreateDataViewObject.Params.SetByRelation -> {
proceedWithOpeningObject(newObject)
}
is CreateDataViewObject.Params.SetByType -> {
if (params.type == ObjectTypeIds.NOTE) {
proceedWithOpeningObject(newObject)
} else {
dispatch(
ObjectSetCommand.Modal.SetNameForCreatedObject(
ctx = context,
target = newObject
)
)
}
}
}
private suspend fun proceedWithNewDataViewObject(
response: CreateDataViewObject.Result,
) {
val obj = ObjectWrapper.Basic(response.struct.orEmpty())
proceedWithOpeningObject(
target = response.objectId,
layout = obj.layout
)
}
fun onViewerCustomizeButtonClicked() {
@ -1227,16 +1214,19 @@ class ObjectSetViewModel(
//region NAVIGATION
private suspend fun proceedWithOpeningObject(target: Id) {
private suspend fun proceedWithOpeningObject(target: Id, layout: ObjectType.Layout? = null) {
isCustomizeViewPanelVisible.value = false
jobs += viewModelScope.launch {
val navigateCommand = when (layout) {
ObjectType.Layout.SET,
ObjectType.Layout.COLLECTION -> AppNavigation.Command.OpenSetOrCollection(target = target)
else -> AppNavigation.Command.OpenObject(id = target)
}
closeBlock.async(context).fold(
onSuccess = {
navigate(EventWrapper(AppNavigation.Command.OpenObject(id = target)))
},
onSuccess = { navigate(EventWrapper(navigateCommand)) },
onFailure = {
Timber.e(it, "Error while closing object set: $context")
navigate(EventWrapper(AppNavigation.Command.OpenObject(id = target)))
navigate(EventWrapper(navigateCommand))
}
)
}
@ -1536,7 +1526,14 @@ class ObjectSetViewModel(
}
}
// region TYPES AND TEMPLATES WIDGET
//region TYPES AND TEMPLATES WIDGET
fun onNewTypeForViewerClicked(typeId: Id) {
viewModelScope.launch {
val type = storeOfObjectTypes.get(typeId)
selectedTypeFlow.value = type
}
}
private fun showTypeTemplatesWidgetForObjectCreation() {
val isPossibleToChangeType = stateReducer.state.value.dataViewState()?.isChangingDefaultTypeAvailable()
showTypeTemplatesWidget(
@ -1607,7 +1604,13 @@ class ObjectSetViewModel(
is TypeTemplatesWidgetUI.Init -> Unit
}
}
TypeTemplatesWidgetUIAction.TypeClick.Search -> TODO()
TypeTemplatesWidgetUIAction.TypeClick.Search -> {
_commands.emit(
ObjectSetCommand.Modal.OpenSelectTypeScreen(
excludedTypes = emptyList()
)
)
}
is TypeTemplatesWidgetUIAction.TemplateClick -> {
when (uiState) {
is TypeTemplatesWidgetUI.Data.CreateObject ->
@ -1664,12 +1667,22 @@ class ObjectSetViewModel(
is TemplateView.Blank -> {
proceedWithUpdateViewer(
viewerId = getWidgetViewerId()
) { it.copy(defaultTemplate = templateView.id) }
) {
it.copy(
defaultTemplate = templateView.id,
defaultObjectType = templateView.typeId
)
}
}
is TemplateView.Template -> {
proceedWithUpdateViewer(
viewerId = getWidgetViewerId()
) { it.copy(defaultTemplate = templateView.id) }
) {
it.copy(
defaultTemplate = templateView.id,
defaultObjectType = templateView.typeId
)
}
}
is TemplateView.New -> {
proceedWithCreatingTemplate(
@ -1698,11 +1711,14 @@ class ObjectSetViewModel(
Pair(selectedTypeId, types)
}
}.map { (selectedTypeId, types) ->
types.map { type ->
TemplateObjectTypeView.Item(
type = ObjectWrapper.Type(type.map),
isDefault = type.id == selectedTypeId.id
)
buildList {
add(TemplateObjectTypeView.Search)
addAll( types.map { type ->
TemplateObjectTypeView.Item(
type = ObjectWrapper.Type(type.map),
isDefault = type.id == selectedTypeId.id
)
})
}
}.collectLatest { types ->
typeTemplatesWidgetState.value =

View file

@ -22,7 +22,6 @@ import org.junit.After
import org.junit.Before
import org.junit.Test
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.any
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.times
import org.mockito.kotlin.verifyBlocking
@ -72,7 +71,8 @@ class ObjectCreateTest : ObjectSetViewModelTestSetup() {
val newObjectId = "objNewNote-${RandomString.make()}"
val result = CreateDataViewObject.Result(
objectId = newObjectId,
objectType = ObjectTypeIds.NOTE
objectType = ObjectTypeIds.NOTE,
struct = null
)
doReturn(Resultat.success(result)).`when`(createDataViewObject).async(
CreateDataViewObject.Params.SetByType(
@ -106,7 +106,7 @@ class ObjectCreateTest : ObjectSetViewModelTestSetup() {
}
@Test
fun `Should create and Set Name for Not-Note Object when clicking on New button in Set by Type`() = runTest {
fun `Should create and open Not-Note Object when clicking on New button in Set by Type`() = runTest {
mockObjectSet = MockSet(context = root, setOfValue = ObjectTypeIds.PAGE)
@ -130,7 +130,8 @@ class ObjectCreateTest : ObjectSetViewModelTestSetup() {
val newObjectId = "objNewPage-${RandomString.make()}"
val result = CreateDataViewObject.Result(
objectId = newObjectId,
objectType = ObjectTypeIds.PAGE
objectType = ObjectTypeIds.PAGE,
struct = null
)
doReturn(Resultat.success(result)).`when`(createDataViewObject).async(
CreateDataViewObject.Params.SetByType(
@ -149,8 +150,6 @@ class ObjectCreateTest : ObjectSetViewModelTestSetup() {
viewModel.proceedWithDataViewObjectCreate()
assertIs<ObjectSetCommand.Modal.SetNameForCreatedObject>(commandFlow.awaitItem())
advanceUntilIdle()
verifyBlocking(createDataViewObject, times(1)) {
@ -162,8 +161,6 @@ class ObjectCreateTest : ObjectSetViewModelTestSetup() {
)
)
}
verifyNoInteractions(closeBlock)
}
@Test
@ -211,7 +208,8 @@ class ObjectCreateTest : ObjectSetViewModelTestSetup() {
val newObjectId = "objNew-${RandomString.make()}"
val result = CreateDataViewObject.Result(
objectId = newObjectId,
objectType = ObjectTypeIds.NOTE
objectType = ObjectTypeIds.NOTE,
struct = null
)
doReturn(Resultat.success(result)).`when`(createDataViewObject).async(
CreateDataViewObject.Params.SetByRelation(
@ -289,7 +287,8 @@ class ObjectCreateTest : ObjectSetViewModelTestSetup() {
val newObjectId = "objNew-${RandomString.make()}"
val result = CreateDataViewObject.Result(
objectId = newObjectId,
objectType = ObjectTypeIds.NOTE
objectType = ObjectTypeIds.NOTE,
struct = null
)
doReturn(Resultat.success(result)).`when`(createDataViewObject).async(
CreateDataViewObject.Params.Collection(

View file

@ -409,7 +409,8 @@ open class ObjectSetViewModelTestSetup {
Resultat.success(
CreateDataViewObject.Result(
objectId = objectId,
objectType = objectType
objectType = objectType,
struct = null
)
)
)