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

DROID-741 Widgets | Enhancement | Allow deleting widgets (#2902)

This commit is contained in:
Evgenii Kozlov 2023-02-08 12:08:31 +01:00 committed by GitHub
parent ee4c810bf6
commit 1309af7752
Signed by: github
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 141 additions and 19 deletions

View file

@ -19,6 +19,7 @@ import com.anytypeio.anytype.domain.objects.ObjectStore
import com.anytypeio.anytype.domain.search.ObjectSearchSubscriptionContainer
import com.anytypeio.anytype.domain.search.SubscriptionEventChannel
import com.anytypeio.anytype.domain.widgets.CreateWidget
import com.anytypeio.anytype.domain.widgets.DeleteWidget
import com.anytypeio.anytype.domain.workspace.WorkspaceManager
import com.anytypeio.anytype.presentation.home.HomeScreenViewModel
import com.anytypeio.anytype.presentation.util.Dispatcher
@ -71,11 +72,18 @@ object HomeScreenModule {
@Provides
@PerScreen
fun createWidget(
repo: BlockRepository,
dispatchers: AppCoroutineDispatchers
repo: BlockRepository
): CreateWidget = CreateWidget(
repo = repo,
dispatchers = dispatchers
)
@JvmStatic
@Provides
@PerScreen
fun deleteWidget(
repo: BlockRepository,
): DeleteWidget = DeleteWidget(
repo = repo
)
@JvmStatic

View file

@ -1,5 +1,6 @@
package com.anytypeio.anytype.ui.home
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@ -9,6 +10,7 @@ import androidx.compose.foundation.layout.fillMaxHeight
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.items
@ -22,10 +24,12 @@ 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.graphics.Color
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.presentation.widgets.TreePath
import com.anytypeio.anytype.presentation.widgets.WidgetView
import timber.log.Timber
@ -36,7 +40,8 @@ fun HomeScreen(
onExpand: (TreePath) -> Unit,
onCreateWidget: () -> Unit,
onEditWidgets: () -> Unit,
onRefresh: () -> Unit
onRefresh: () -> Unit,
onDeleteWidget: (Id) -> Unit
) {
LazyColumn(
modifier = Modifier
@ -55,6 +60,9 @@ fun HomeScreen(
.padding(start = 20.dp, end = 20.dp, top = 6.dp, bottom = 6.dp),
shape = RoundedCornerShape(16.dp)
) {
Circle {
onDeleteWidget(item.id)
}
Column(Modifier.padding(16.dp)) {
TreeWidgetHeader(item)
item.elements.forEach { element ->
@ -73,7 +81,7 @@ fun HomeScreen(
.width(20.dp)
.height(20.dp)
) {
when(val icon = element.icon) {
when (val icon = element.icon) {
is WidgetView.Tree.Icon.Branch -> {
Text(
text = ">",
@ -122,6 +130,9 @@ fun HomeScreen(
)
}
}
Circle {
onDeleteWidget(item.id)
}
}
is WidgetView.Action.CreateWidget -> {
Box(Modifier.fillMaxWidth()) {
@ -168,7 +179,9 @@ private fun TreeWidgetHeader(item: WidgetView.Tree) {
Spacer(modifier = Modifier.width(8.dp))
Text(
text = ">",
modifier = Modifier.align(Alignment.CenterVertically).rotate(90f)
modifier = Modifier
.align(Alignment.CenterVertically)
.rotate(90f)
)
}
}
@ -190,4 +203,25 @@ fun WidgetActionButton(
) {
Text(text = label)
}
}
}
@Composable
fun Circle(
onClick: () -> Unit
) {
Box(
Modifier
.height(24.dp)
.width(24.dp)) {
Canvas(
modifier = Modifier
.size(24.dp)
.align(Alignment.CenterEnd)
.padding(4.dp)
.clickable { onClick() },
onDraw = {
drawCircle(color = Color.Red)
}
)
}
}

View file

@ -36,6 +36,7 @@ class HomeScreenFragment : BaseComposeFragment() {
onCreateWidget = {
findNavController().navigate(R.id.selectWidgetSourceScreen)
},
onDeleteWidget = vm::onDeleteWidgetClicked,
onEditWidgets = { context.toast("Coming soon") },
onRefresh = vm::onRefresh
)

View file

@ -1,15 +1,15 @@
package com.anytypeio.anytype.domain.block.interactor
import com.anytypeio.anytype.domain.base.BaseUseCase
import com.anytypeio.anytype.domain.base.Either
import com.anytypeio.anytype.core_models.Command
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.domain.base.BaseUseCase
import com.anytypeio.anytype.domain.base.Either
import com.anytypeio.anytype.domain.block.repo.BlockRepository
/**
* Use-case for unlinking blocks from its context.
* Unlinking is a remplacement for delete operations.
* Unlinking is a replacement for delete operations.
*/
open class UnlinkBlocks(private val repo: BlockRepository) :
BaseUseCase<Payload, UnlinkBlocks.Params>() {

View file

@ -2,12 +2,10 @@ package com.anytypeio.anytype.domain.widgets
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
import com.anytypeio.anytype.domain.base.ResultatInteractor
import com.anytypeio.anytype.domain.block.repo.BlockRepository
class CreateWidget(
private val dispatchers: AppCoroutineDispatchers,
private val repo: BlockRepository
) : ResultatInteractor<CreateWidget.Params, Payload>() {

View file

@ -0,0 +1,29 @@
package com.anytypeio.anytype.domain.widgets
import com.anytypeio.anytype.core_models.Command
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.domain.base.ResultatInteractor
import com.anytypeio.anytype.domain.block.repo.BlockRepository
class DeleteWidget(
private val repo: BlockRepository
) : ResultatInteractor<DeleteWidget.Params, Payload>() {
override suspend fun execute(params: Params) = repo.unlink(
command = Command.Unlink(
context = params.ctx,
targets = params.targets
)
)
/**
* Params for deleting widget blocks from widget object
* @property [ctx] id of the widget object
* @property [targets] ids of widget blocks to delete from widget object
*/
data class Params(
val ctx: Id,
val targets: List<Id>
)
}

View file

@ -17,6 +17,7 @@ import com.anytypeio.anytype.domain.misc.Reducer
import com.anytypeio.anytype.domain.`object`.OpenObject
import com.anytypeio.anytype.domain.search.ObjectSearchSubscriptionContainer
import com.anytypeio.anytype.domain.widgets.CreateWidget
import com.anytypeio.anytype.domain.widgets.DeleteWidget
import com.anytypeio.anytype.presentation.common.BaseViewModel
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.anytypeio.anytype.presentation.widgets.LinkWidgetContainer
@ -44,6 +45,7 @@ class HomeScreenViewModel(
private val configStorage: ConfigStorage,
private val openObject: OpenObject,
private val createWidget: CreateWidget,
private val deleteWidget: DeleteWidget,
private val objectSearchSubscriptionContainer: ObjectSearchSubscriptionContainer,
private val appCoroutineDispatchers: AppCoroutineDispatchers,
private val widgetEventDispatcher: Dispatcher<WidgetDispatchEvent>,
@ -84,6 +86,7 @@ class HomeScreenViewModel(
}
}
}.map { state ->
Timber.d("Emitting new state: ${state::class.java.simpleName}")
when (state) {
is ObjectViewState.Failure -> {
emptyList()
@ -102,6 +105,7 @@ class HomeScreenViewModel(
}
}
}.collect {
Timber.d("Emitting list of widgets: ${it.size}")
widgets.value = it
}
}
@ -121,16 +125,22 @@ class HomeScreenViewModel(
}
}
}.collect {
Timber.d("Emitting list of containers: ${it.size}")
containers.value = it
}
}
viewModelScope.launch {
containers.flatMapLatest {
combine(
it.map { m -> m.view }
) { array ->
array.toList()
containers.flatMapLatest { list ->
Timber.d("Receiving list of containers: ${list.size}")
if (list.isNotEmpty()) {
combine(
list.map { m -> m.view }
) { array ->
array.toList()
}
} else {
flowOf(emptyList())
}
}.flowOn(appCoroutineDispatchers.io).collect {
Timber.d("Views update: $it")
@ -183,7 +193,7 @@ class HomeScreenViewModel(
ctx = config.widgets,
source = source
)
).collect { status ->
).flowOn(appCoroutineDispatchers.io).collect { status ->
Timber.d("Status while creating widget: $status")
when (status) {
is Resultat.Failure -> {
@ -201,6 +211,32 @@ class HomeScreenViewModel(
}
}
private fun proceedWithDeletingWidget(widget: Id) {
viewModelScope.launch {
val config = configStorage.get()
deleteWidget(
DeleteWidget.Params(
ctx = config.widgets,
targets = listOf(widget)
)
).flowOn(appCoroutineDispatchers.io).collect { status ->
Timber.d("Status while deleting widget: $status")
when (status) {
is Resultat.Failure -> {
sendToast("Error while deleting widget: ${status.exception}")
Timber.e(status.exception, "Error while deleting widget")
}
is Resultat.Loading -> {
// Do nothing?
}
is Resultat.Success -> {
objectPayloadDispatcher.send(status.value)
}
}
}
}
}
@Deprecated("For debugging only")
fun onRefresh() {
proceedWithOpeningWidgetObject(widgetObject = configStorage.get().widgets)
@ -214,6 +250,10 @@ class HomeScreenViewModel(
expanded.onExpand(linkPath = path)
}
fun onDeleteWidgetClicked(widget: Id) {
proceedWithDeletingWidget(widget)
}
// TODO move to a separate reducer inject into this VM's constructor
override fun reduce(state: ObjectView, event: Payload): ObjectView {
var curr = state
@ -222,6 +262,11 @@ class HomeScreenViewModel(
is Event.Command.AddBlock -> {
curr = curr.copy(blocks = curr.blocks + e.blocks)
}
is Event.Command.DeleteBlock -> {
curr = curr.copy(
blocks = curr.blocks.filter { !e.targets.contains(it.id) }
)
}
is Event.Command.UpdateStructure -> {
curr = curr.copy(
blocks = curr.blocks.replace(
@ -247,6 +292,7 @@ class HomeScreenViewModel(
private val configStorage: ConfigStorage,
private val openObject: OpenObject,
private val createWidget: CreateWidget,
private val deleteWidget: DeleteWidget,
private val objectSearchSubscriptionContainer: ObjectSearchSubscriptionContainer,
private val appCoroutineDispatchers: AppCoroutineDispatchers,
private val widgetEventDispatcher: Dispatcher<WidgetDispatchEvent>,
@ -258,6 +304,7 @@ class HomeScreenViewModel(
configStorage = configStorage,
openObject = openObject,
createWidget = createWidget,
deleteWidget = deleteWidget,
objectSearchSubscriptionContainer = objectSearchSubscriptionContainer,
appCoroutineDispatchers = appCoroutineDispatchers,
widgetEventDispatcher = widgetEventDispatcher,

View file

@ -21,6 +21,7 @@ import com.anytypeio.anytype.domain.event.interactor.InterceptEvents
import com.anytypeio.anytype.domain.`object`.OpenObject
import com.anytypeio.anytype.domain.search.ObjectSearchSubscriptionContainer
import com.anytypeio.anytype.domain.widgets.CreateWidget
import com.anytypeio.anytype.domain.widgets.DeleteWidget
import com.anytypeio.anytype.presentation.util.DefaultCoroutineTestRule
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.anytypeio.anytype.presentation.widgets.TreeWidgetContainer
@ -54,6 +55,9 @@ class HomeViewModelTest {
@Mock
lateinit var createWidget: CreateWidget
@Mock
lateinit var deleteWidget: DeleteWidget
@Mock
lateinit var interceptEvents: InterceptEvents
@ -318,6 +322,7 @@ class HomeViewModelTest {
configStorage = configStorage,
interceptEvents = interceptEvents,
createWidget = createWidget,
deleteWidget = deleteWidget,
objectPayloadDispatcher = objectPayloadDispatcher,
widgetEventDispatcher = widgetEventDispatcher,
openObject = openObject,