diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/widgets/SelectWidgetTypeDI.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/widgets/SelectWidgetTypeDI.kt index 014a36e4bb..1a1e2c3339 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/widgets/SelectWidgetTypeDI.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/widgets/SelectWidgetTypeDI.kt @@ -1,6 +1,11 @@ package com.anytypeio.anytype.di.feature.widgets +import com.anytypeio.anytype.core_models.Payload import com.anytypeio.anytype.core_utils.di.scope.PerModal +import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers +import com.anytypeio.anytype.domain.block.repo.BlockRepository +import com.anytypeio.anytype.domain.widgets.UpdateWidget +import com.anytypeio.anytype.presentation.util.Dispatcher import com.anytypeio.anytype.presentation.widgets.SelectWidgetTypeViewModel import com.anytypeio.anytype.ui.widgets.SelectWidgetTypeFragment import dagger.Module @@ -29,7 +34,17 @@ object SelectWidgetTypeModule { @PerModal @Provides fun factory( + dispatcher: Dispatcher, + updateWidget: UpdateWidget, + appCoroutineDispatchers: AppCoroutineDispatchers ): SelectWidgetTypeViewModel.Factory = SelectWidgetTypeViewModel.Factory( - // TODO + dispatcher = dispatcher, + updateWidget = updateWidget, + appCoroutineDispatchers = appCoroutineDispatchers ) + + @JvmStatic + @PerModal + @Provides + fun updateWidget(repo: BlockRepository) = UpdateWidget(repo = repo) } \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/ui/home/HomeScreen.kt b/app/src/main/java/com/anytypeio/anytype/ui/home/HomeScreen.kt index ee7404e3db..805afde9c4 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/home/HomeScreen.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/home/HomeScreen.kt @@ -24,6 +24,7 @@ import androidx.compose.material.DropdownMenuItem import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment @@ -37,7 +38,6 @@ import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.presentation.widgets.TreePath import com.anytypeio.anytype.presentation.widgets.WidgetView -@OptIn(ExperimentalFoundationApi::class) @Composable fun HomeScreen( widgets: List, @@ -45,9 +45,7 @@ fun HomeScreen( onCreateWidget: () -> Unit, onEditWidgets: () -> Unit, onRefresh: () -> Unit, - onDeleteWidget: (Id) -> Unit, - onChangeWidgetSource: (Id) -> Unit, - onChangeWidgetType: (Id) -> Unit + onWidgetMenuAction: (Id, DropDownMenuAction) -> Unit ) { LazyColumn( modifier = Modifier @@ -62,14 +60,17 @@ fun HomeScreen( TreeWidgetCard( item = item, onExpand = onExpand, - onChangeWidgetSource = onChangeWidgetSource, - onChangeWidgetType = onChangeWidgetType, - onDeleteWidget = onDeleteWidget + onDropDownMenuAction = { action -> + onWidgetMenuAction(item.id, action) + } ) } is WidgetView.Link -> { LinkWidgetCard( - item = item + item = item, + onDropDownMenuAction = { action -> + onWidgetMenuAction(item.id, action) + } ) } is WidgetView.Action.CreateWidget -> { @@ -105,8 +106,15 @@ fun HomeScreen( } } +@OptIn(ExperimentalFoundationApi::class) @Composable -private fun LinkWidgetCard(item: WidgetView.Link) { +private fun LinkWidgetCard( + item: WidgetView.Link, + onDropDownMenuAction: (DropDownMenuAction) -> Unit +) { + val isDropDownMenuExpanded = remember { + mutableStateOf(false) + } Card( modifier = Modifier .fillMaxWidth() @@ -114,7 +122,16 @@ private fun LinkWidgetCard(item: WidgetView.Link) { .padding(start = 20.dp, end = 20.dp, top = 6.dp, bottom = 6.dp), shape = RoundedCornerShape(16.dp) ) { - Box(Modifier.fillMaxHeight()) { + Box( + Modifier + .fillMaxHeight() + .combinedClickable( + onClick = {}, + onLongClick = { + isDropDownMenuExpanded.value = !isDropDownMenuExpanded.value + } + ) + ) { Text( text = item.obj.name.orEmpty().trim(), style = MaterialTheme.typography.h6, @@ -123,6 +140,10 @@ private fun LinkWidgetCard(item: WidgetView.Link) { .padding(start = 16.dp) ) } + WidgetMenu( + isExpanded = isDropDownMenuExpanded, + onDropDownMenuAction = onDropDownMenuAction + ) } } @@ -131,9 +152,7 @@ private fun LinkWidgetCard(item: WidgetView.Link) { private fun TreeWidgetCard( item: WidgetView.Tree, onExpand: (TreePath) -> Unit, - onChangeWidgetSource: (Id) -> Unit, - onChangeWidgetType: (Id) -> Unit, - onDeleteWidget: (Id) -> Unit + onDropDownMenuAction: (DropDownMenuAction) -> Unit ) { val isDropDownMenuExpanded = remember { mutableStateOf(false) @@ -150,8 +169,7 @@ private fun TreeWidgetCard( .combinedClickable( onClick = {}, onLongClick = { - isDropDownMenuExpanded.value = - !isDropDownMenuExpanded.value + isDropDownMenuExpanded.value = !isDropDownMenuExpanded.value } ) ) { @@ -209,52 +227,47 @@ private fun TreeWidgetCard( } } WidgetMenu( - expanded = isDropDownMenuExpanded.value, - onDismiss = { - isDropDownMenuExpanded.value = false - }, - onChangeSourceClicked = { - isDropDownMenuExpanded.value = false - onChangeWidgetSource(item.id) - }, - onChangeTypeClicked = { - isDropDownMenuExpanded.value = false - onChangeWidgetType(item.id) - }, - onRemoveWidgetClicked = { - isDropDownMenuExpanded.value = false - onDeleteWidget(item.id) - } + isExpanded = isDropDownMenuExpanded, + onDropDownMenuAction = onDropDownMenuAction ) } } @Composable private fun WidgetMenu( - expanded: Boolean, - onDismiss: () -> Unit, - onChangeTypeClicked: () -> Unit, - onRemoveWidgetClicked: () -> Unit, - onChangeSourceClicked: () -> Unit + isExpanded: MutableState, + onDropDownMenuAction: (DropDownMenuAction) -> Unit ) { DropdownMenu( - expanded = expanded, - onDismissRequest = onDismiss + expanded = isExpanded.value, + onDismissRequest = { isExpanded.value = false } ) { DropdownMenuItem( - onClick = onChangeSourceClicked + onClick = { + onDropDownMenuAction(DropDownMenuAction.ChangeWidgetSource).also { + isExpanded.value = false + } + } ) { Text(stringResource(R.string.widget_change_source)) } Divider(thickness = 0.5.dp) DropdownMenuItem( - onClick = onChangeTypeClicked + onClick = { + onDropDownMenuAction(DropDownMenuAction.ChangeWidgetType).also { + isExpanded.value = false + } + } ) { Text(stringResource(R.string.widget_change_type)) } Divider(thickness = 0.5.dp) DropdownMenuItem( - onClick = onRemoveWidgetClicked + onClick = { + onDropDownMenuAction(DropDownMenuAction.RemoveWidget).also { + isExpanded.value = false + } + } ) { Text(stringResource(id = R.string.remove_widget)) } @@ -298,3 +311,10 @@ fun WidgetActionButton( Text(text = label) } } + +sealed class DropDownMenuAction { + object ChangeWidgetType : DropDownMenuAction() + object ChangeWidgetSource : DropDownMenuAction() + object RemoveWidget : DropDownMenuAction() + object EditWidgets : DropDownMenuAction() +} \ No newline at end of file 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 e769f19cce..832fb0e9a0 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 @@ -4,17 +4,27 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.compose.material.MaterialTheme import androidx.compose.runtime.collectAsState import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.fragment.app.viewModels +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import androidx.navigation.findNavController +import androidx.navigation.fragment.findNavController import com.anytypeio.anytype.R +import com.anytypeio.anytype.core_models.Id 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.presentation.home.Command import com.anytypeio.anytype.presentation.home.HomeScreenViewModel +import com.anytypeio.anytype.ui.settings.typography +import com.anytypeio.anytype.ui.widgets.SelectWidgetTypeFragment import javax.inject.Inject +import kotlinx.coroutines.launch class HomeScreenFragment : BaseComposeFragment() { @@ -30,28 +40,62 @@ class HomeScreenFragment : BaseComposeFragment() { ): View = ComposeView(requireContext()).apply { setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { - HomeScreen( - widgets = vm.views.collectAsState().value, - onExpand = { path -> vm.onExpand(path) }, - onCreateWidget = { - findNavController().navigate(R.id.selectWidgetSourceScreen) - }, - onDeleteWidget = vm::onDeleteWidgetClicked, - onEditWidgets = { context.toast("Coming soon") }, - onRefresh = vm::onRefresh, - onChangeWidgetSource = { - toast("TODO") - }, - onChangeWidgetType = { - toast("TODO") - } - ) + MaterialTheme(typography = typography) { + HomeScreen( + widgets = vm.views.collectAsState().value, + onExpand = { path -> vm.onExpand(path) }, + onCreateWidget = { + findNavController().navigate(R.id.selectWidgetSourceScreen) + }, + onEditWidgets = { context.toast("Coming soon") }, + onRefresh = vm::onRefresh, + onWidgetMenuAction = { widget: Id, action: DropDownMenuAction -> + when(action) { + DropDownMenuAction.ChangeWidgetSource -> { + toast("TODO") + } + DropDownMenuAction.ChangeWidgetType -> { + vm.onChangeWidgetTypeClicked(widget) + } + DropDownMenuAction.EditWidgets -> { + toast("TODO") + } + DropDownMenuAction.RemoveWidget -> { + vm.onDeleteWidgetClicked(widget) + } + } + } + ) + } } } - override fun onStart() { - super.onStart() - vm.onStart() + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + viewLifecycleOwner.lifecycleScope.launch { + viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { + vm.commands.collect { command -> proceed(command) } + } + } + } + + private fun proceed(command: Command) { + when(command) { + is Command.SelectWidgetSource -> { + findNavController().navigate(R.id.selectWidgetSourceScreen) + } + is Command.SelectWidgetType -> { + findNavController().navigate( + R.id.selectWidgetTypeScreen, + args = SelectWidgetTypeFragment.args( + ctx = command.ctx, + widget = command.widget, + source = command.source, + type = command.type + ) + ) + } + } } override fun injectDependencies() { diff --git a/app/src/main/java/com/anytypeio/anytype/ui/widgets/SelectWidgetTypeFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/widgets/SelectWidgetTypeFragment.kt index b2885b6388..7e5fc47121 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/widgets/SelectWidgetTypeFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/widgets/SelectWidgetTypeFragment.kt @@ -4,16 +4,32 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.compose.material.MaterialTheme +import androidx.compose.runtime.collectAsState import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.core.os.bundleOf import androidx.fragment.app.viewModels +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import com.anytypeio.anytype.core_models.Id +import com.anytypeio.anytype.core_utils.ext.arg +import com.anytypeio.anytype.core_utils.ext.argString import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetComposeFragment import com.anytypeio.anytype.di.common.componentManager import com.anytypeio.anytype.presentation.widgets.SelectWidgetTypeViewModel +import com.anytypeio.anytype.ui.settings.typography import javax.inject.Inject +import kotlinx.coroutines.launch class SelectWidgetTypeFragment : BaseBottomSheetComposeFragment() { + private val ctx: Id get() = argString(CTX_KEY) + private val widget: Id get() = argString(WIDGET_ID_KEY) + private val source: Id get() = argString(WIDGET_SOURCE_KEY) + private val currentType: Int get() = arg(WIDGET_TYPE_KEY) + private val vm by viewModels { factory } @Inject @@ -26,13 +42,38 @@ class SelectWidgetTypeFragment : BaseBottomSheetComposeFragment() { ): View = ComposeView(requireContext()).apply { setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { - SelectWidgetTypeScreen( - views = emptyList(), - onViewClicked = {} - ) + MaterialTheme(typography = typography) { + SelectWidgetTypeScreen( + views = vm.views.collectAsState().value, + onViewClicked = { view -> + vm.onWidgetTypeClicked( + ctx = ctx, + view = view, + widget = widget, + source = source + ) + } + ) + } } } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + viewLifecycleOwner.lifecycleScope.launch { + viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { + vm.isDismissed.collect { isDismissed -> + if (isDismissed) dismiss() + } + } + } + } + + override fun onStart() { + super.onStart() + vm.onStart(currentType = currentType) + } + override fun injectDependencies() { componentManager().selectWidgetTypeSubcomponent.get().inject(this) } @@ -40,4 +81,23 @@ class SelectWidgetTypeFragment : BaseBottomSheetComposeFragment() { override fun releaseDependencies() { componentManager().selectWidgetTypeSubcomponent.release() } + + companion object { + private const val CTX_KEY = "arg.select-widget-type.ctx" + private const val WIDGET_ID_KEY = "arg.select-widget-type.widget-id" + private const val WIDGET_TYPE_KEY = "arg.select-widget-type.widget-type" + private const val WIDGET_SOURCE_KEY = "arg.select-widget-type.widget-source" + + fun args( + ctx: Id, + widget: Id, + source: Id, + type: Int + ) = bundleOf( + CTX_KEY to ctx, + WIDGET_ID_KEY to widget, + WIDGET_SOURCE_KEY to source, + WIDGET_TYPE_KEY to type + ) + } } \ 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 038576b124..ec0307c003 100644 --- a/app/src/main/res/navigation/graph.xml +++ b/app/src/main/res/navigation/graph.xml @@ -156,6 +156,11 @@ android:name="com.anytypeio.anytype.ui.widgets.SelectWidgetSourceFragment"> + + + \ No newline at end of file +typealias DVRecord = Map + +typealias Widget = Block.Content.Widget +typealias WidgetLayout = Block.Content.Widget.Layout \ No newline at end of file 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 990129211c..b1971e074e 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 @@ -711,6 +711,18 @@ class BlockDataRepository( source = source ) + override suspend fun updateWidget( + ctx: Id, + target: Id, + source: Id, + type: Block.Content.Widget.Layout + ): Payload = remote.updateWidget( + ctx = ctx, + target = target, + source = source, + type = type + ) + override suspend fun addDataViewFilter(command: Command.AddFilter): Payload { return remote.addDataViewFilter(command = command) } diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataStore.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataStore.kt index a9a9a06ad1..9c376a8000 100644 --- a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataStore.kt +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataStore.kt @@ -308,6 +308,13 @@ interface BlockDataStore { suspend fun createWidget(ctx: Id, source: Id): Payload + suspend fun updateWidget( + ctx: Id, + target: Id, + source: Id, + type: Block.Content.Widget.Layout + ): Payload + suspend fun addDataViewFilter(command: Command.AddFilter): Payload suspend fun removeDataViewFilter(command: Command.RemoveFilter): Payload suspend fun replaceDataViewFilter(command: Command.ReplaceFilter): Payload 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 ac7c68e30c..00365ea5f8 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 @@ -308,6 +308,13 @@ interface BlockRemote { suspend fun createWidget(ctx: Id, source: Id): Payload + suspend fun updateWidget( + ctx: Id, + target: Id, + source: Id, + type: Block.Content.Widget.Layout + ): Payload + suspend fun addDataViewFilter(command: Command.AddFilter): Payload suspend fun removeDataViewFilter(command: Command.RemoveFilter): Payload suspend fun replaceDataViewFilter(command: Command.ReplaceFilter): Payload diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemoteDataStore.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemoteDataStore.kt index 740cbedbab..d4d4a4748a 100644 --- a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemoteDataStore.kt +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemoteDataStore.kt @@ -640,6 +640,18 @@ class BlockRemoteDataStore(private val remote: BlockRemote) : BlockDataStore { source = source ) + override suspend fun updateWidget( + ctx: Id, + target: Id, + source: Id, + type: Block.Content.Widget.Layout + ): Payload = remote.updateWidget( + ctx = ctx, + target = target, + source = source, + type = type + ) + override suspend fun addDataViewFilter(command: Command.AddFilter): Payload { return remote.addDataViewFilter(command) } 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 0e3bd7df59..1108fa6779 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 @@ -361,6 +361,13 @@ interface BlockRepository { source: Id ): Payload + suspend fun updateWidget( + ctx: Id, + target: Id, + source: Id, + type: Block.Content.Widget.Layout + ): Payload + suspend fun addDataViewFilter(command: Command.AddFilter): Payload suspend fun removeDataViewFilter(command: Command.RemoveFilter): Payload suspend fun replaceDataViewFilter(command: Command.ReplaceFilter): Payload diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/widgets/UpdateWidget.kt b/domain/src/main/java/com/anytypeio/anytype/domain/widgets/UpdateWidget.kt new file mode 100644 index 0000000000..e489b75efe --- /dev/null +++ b/domain/src/main/java/com/anytypeio/anytype/domain/widgets/UpdateWidget.kt @@ -0,0 +1,35 @@ +package com.anytypeio.anytype.domain.widgets + +import com.anytypeio.anytype.core_models.Block +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 + +/** + * Use-case for updating widget block contained in widgets. + */ +class UpdateWidget( + private val repo: BlockRepository +) : ResultatInteractor() { + + override suspend fun execute(params: Params): Payload = repo.updateWidget( + ctx = params.ctx, + source = params.source, + type = params.type, + target = params.target + ) + + /** + * [ctx] context of widgets — id of object containing widgets + * [target] widget block to update + * [source] source for the given widget block + * [type] layout type to update + */ + data class Params( + val ctx: Id, + val target: Id, + val source: Id, + val type: Block.Content.Widget.Layout + ) +} \ No newline at end of file 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 e92a96165d..153f25ca8d 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 @@ -679,6 +679,18 @@ class BlockMiddleware( source = source ) + override suspend fun updateWidget( + ctx: Id, + target: Id, + source: Id, + type: Block.Content.Widget.Layout + ): Payload = middleware.updateWidget( + ctx = ctx, + target = target, + source = source, + type = type + ) + override suspend fun addDataViewFilter(command: Command.AddFilter): Payload { return middleware.addDataViewFilter(command) } 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 b788dbfa45..afe4b7a577 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 @@ -29,12 +29,14 @@ import com.anytypeio.anytype.core_models.Response import com.anytypeio.anytype.core_models.SearchResult import com.anytypeio.anytype.core_models.Struct import com.anytypeio.anytype.core_models.Url +import com.anytypeio.anytype.core_models.WidgetLayout import com.anytypeio.anytype.middleware.BuildConfig import com.anytypeio.anytype.middleware.auth.toAccountSetup import com.anytypeio.anytype.middleware.const.Constants import com.anytypeio.anytype.middleware.mappers.MDVFilter import com.anytypeio.anytype.middleware.mappers.MRelationFormat import com.anytypeio.anytype.middleware.mappers.core +import com.anytypeio.anytype.middleware.mappers.mw import com.anytypeio.anytype.middleware.mappers.parse import com.anytypeio.anytype.middleware.mappers.toCore import com.anytypeio.anytype.middleware.mappers.toCoreModel @@ -1860,6 +1862,30 @@ class Middleware( return response.event.toPayload() } + @Throws(Exception::class) + fun updateWidget( + ctx: Id, + target: Id, + source: Id, + type: WidgetLayout + ): Payload { + val request = Rpc.Block.CreateWidget.Request( + contextId = ctx, + targetId = target, + widgetLayout = type.mw(), + position = Block.Position.Replace, + block = Block( + link = Block.Content.Link( + targetBlockId = source + ) + ) + ) + if (BuildConfig.DEBUG) logRequest(request) + val response = service.blockCreateWidget(request) + if (BuildConfig.DEBUG) logResponse(response) + return response.event.toPayload() + } + @Throws(Exception::class) fun addDataViewFilter( command: Command.AddFilter diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/ToMiddlewareModelMappers.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/ToMiddlewareModelMappers.kt index a84afb3b37..ccc4b2ca2b 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/ToMiddlewareModelMappers.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/ToMiddlewareModelMappers.kt @@ -470,4 +470,9 @@ fun List.toMiddlewareModel(): List = map { InternalFlags.ShouldSelectTemplate -> InternalFlag(InternalFlag.Value.editorSelectTemplate) InternalFlags.ShouldSelectType -> InternalFlag(InternalFlag.Value.editorSelectType) } +} + +fun Block.Content.Widget.Layout.mw() : MWidgetLayout = when(this) { + Block.Content.Widget.Layout.TREE -> MWidgetLayout.Tree + Block.Content.Widget.Layout.LINK -> MWidgetLayout.Link } \ No newline at end of file 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 4bc6f0ffe7..e6b73c84be 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 @@ -30,6 +30,7 @@ import com.anytypeio.anytype.presentation.widgets.WidgetDispatchEvent import com.anytypeio.anytype.presentation.widgets.WidgetView import com.anytypeio.anytype.presentation.widgets.parseWidgets import javax.inject.Inject +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flatMapLatest @@ -54,6 +55,7 @@ class HomeScreenViewModel( ) : BaseViewModel(), Reducer { val views = MutableStateFlow>(actions) + val commands = MutableSharedFlow() private val objectViewState = MutableStateFlow(ObjectViewState.Idle) private val widgets = MutableStateFlow>(emptyList()) @@ -242,10 +244,6 @@ class HomeScreenViewModel( proceedWithOpeningWidgetObject(widgetObject = configStorage.get().widgets) } - fun onStart() { - Timber.d("onStart") - } - fun onExpand(path: TreePath) { expanded.onExpand(linkPath = path) } @@ -254,6 +252,27 @@ class HomeScreenViewModel( proceedWithDeletingWidget(widget) } + fun onChangeWidgetTypeClicked(widget: Id) { + val curr = widgets.value.find { it.id == widget } + if (curr != null) { + viewModelScope.launch { + commands.emit( + Command.SelectWidgetType( + ctx = configStorage.get().widgets, + widget = widget, + source = curr.source.id, + type = when(curr) { + is Widget.Link -> Command.SelectWidgetType.TYPE_LINK + is Widget.Tree -> Command.SelectWidgetType.TYPE_TREE + } + ) + ) + } + } else { + sendToast("Widget missing. Please try again later") + } + } + // TODO move to a separate reducer inject into this VM's constructor override fun reduce(state: ObjectView, event: Payload): ObjectView { var curr = state @@ -330,4 +349,21 @@ sealed class ObjectViewState { object Loading : ObjectViewState() data class Success(val obj: ObjectView) : ObjectViewState() data class Failure(val e: Throwable) : ObjectViewState() +} + +sealed class Command { + object SelectWidgetSource : Command() + data class SelectWidgetType( + val ctx: Id, + val widget: Id, + val source: Id, + val type: Int + ) : Command() { + companion object { + const val UNKNOWN_TYPE = -1 + const val TYPE_TREE = 0 + const val TYPE_LINK = 1 + const val TYPE_LIST = 2 + } + } } \ No newline at end of file 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 9a4c3922db..7506ab6e23 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 @@ -2,19 +2,87 @@ package com.anytypeio.anytype.presentation.widgets import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import com.anytypeio.anytype.core_models.Id +import com.anytypeio.anytype.core_models.Payload +import com.anytypeio.anytype.core_models.WidgetLayout +import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers +import com.anytypeio.anytype.domain.base.fold +import com.anytypeio.anytype.domain.widgets.UpdateWidget import com.anytypeio.anytype.presentation.common.BaseViewModel +import com.anytypeio.anytype.presentation.util.Dispatcher +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.launch +import timber.log.Timber -class SelectWidgetTypeViewModel : BaseViewModel() { +class SelectWidgetTypeViewModel( + private val appCoroutineDispatchers: AppCoroutineDispatchers, + private val dispatcher: Dispatcher, + private val updateWidget: UpdateWidget +) : BaseViewModel() { + + val views = MutableStateFlow( + listOf( + WidgetTypeView.Tree(isSelected = false), + WidgetTypeView.Link(isSelected = false) + ) + ) + + val isDismissed = MutableStateFlow(false) + + fun onStart(currentType: Int) { + views.value = views.value.map { view -> view.setIsSelected(currentType) } + } + + fun onWidgetTypeClicked( + ctx: Id, + widget: Id, + source: Id, + view: WidgetTypeView + ) { + if (!view.isSelected) { + viewModelScope.launch { + updateWidget( + UpdateWidget.Params( + ctx = ctx, + target = widget, + source = source, + type = when (view) { + is WidgetTypeView.Link -> WidgetLayout.LINK + is WidgetTypeView.Tree -> WidgetLayout.TREE + is WidgetTypeView.List -> TODO() + } + ) + ).flowOn(appCoroutineDispatchers.io).collect { result -> + result.fold( + onFailure = { + Timber.e(it, "Error while updating widget type") + }, + onSuccess = { + dispatcher.send(it).also { + isDismissed.value = true + } + } + ) + } + } + } + } class Factory( + private val appCoroutineDispatchers: AppCoroutineDispatchers, + private val dispatcher: Dispatcher, + private val updateWidget: UpdateWidget ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class): T { return SelectWidgetTypeViewModel( - + appCoroutineDispatchers = appCoroutineDispatchers, + dispatcher = dispatcher, + updateWidget = updateWidget ) as T } } - } \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/WidgetTypeView.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/WidgetTypeView.kt index e0d2d6f108..2b507fbafa 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/WidgetTypeView.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/WidgetTypeView.kt @@ -1,8 +1,19 @@ package com.anytypeio.anytype.presentation.widgets +import com.anytypeio.anytype.presentation.home.Command.SelectWidgetType.Companion.TYPE_LINK +import com.anytypeio.anytype.presentation.home.Command.SelectWidgetType.Companion.TYPE_LIST +import com.anytypeio.anytype.presentation.home.Command.SelectWidgetType.Companion.TYPE_TREE + sealed class WidgetTypeView { abstract val isSelected: Boolean + data class List(override val isSelected: Boolean) : WidgetTypeView() - data class Tree(override val isSelected: Boolean): WidgetTypeView() - data class Link(override val isSelected: Boolean): WidgetTypeView() + data class Tree(override val isSelected: Boolean) : WidgetTypeView() + data class Link(override val isSelected: Boolean) : WidgetTypeView() + + fun setIsSelected(type: Int): WidgetTypeView = when (this) { + is Link -> copy(isSelected = type == TYPE_LINK) + is List -> copy(isSelected = type == TYPE_LIST) + is Tree -> copy(isSelected = type == TYPE_TREE) + } } \ No newline at end of file