Compare commits
29 Commits
main
...
droid-2121
Author | SHA1 | Date | |
---|---|---|---|
|
3975cebe0e | ||
|
3ed09113fd | ||
|
cbcc02656c | ||
|
50cdb9493b | ||
|
63e9a1dc2b | ||
|
61026f38f3 | ||
|
0985632e4c | ||
|
0a9b134a55 | ||
|
cdd4085161 | ||
|
f17d8fe601 | ||
|
9219d007d3 | ||
|
159db9a85d | ||
|
de7d2b3d42 | ||
|
e101d11d49 | ||
|
aaecfea468 | ||
|
907fbfb4c3 | ||
|
005fca7c24 | ||
|
2c80d0a250 | ||
|
f96156f10f | ||
|
49c51d76e7 | ||
|
b796c9ebb5 | ||
|
d82bba1647 | ||
|
87b2268091 | ||
|
ff6b552445 | ||
|
b9d82662b8 | ||
|
53e7127df8 | ||
|
bc2a6e0db1 | ||
|
3f9b00b35d | ||
|
153e0a47f8 |
|
@ -102,6 +102,7 @@ import com.anytypeio.anytype.di.feature.widgets.SelectWidgetSourceModule
|
|||
import com.anytypeio.anytype.di.feature.widgets.SelectWidgetTypeModule
|
||||
import com.anytypeio.anytype.di.main.MainComponent
|
||||
import com.anytypeio.anytype.presentation.objects.SelectObjectTypeViewModel
|
||||
import com.anytypeio.anytype.presentation.relations.option.OptionViewModel
|
||||
import com.anytypeio.anytype.presentation.relations.value.tagstatus.TagStatusViewModel
|
||||
import com.anytypeio.anytype.ui.relations.RelationEditParameters
|
||||
import com.anytypeio.anytype.ui.types.edit.TypeEditParameters
|
||||
|
@ -949,13 +950,20 @@ class ComponentManager(
|
|||
.create(findComponentDependencies())
|
||||
}
|
||||
|
||||
val tagStatusObjectComponent = ComponentWithParams { params: TagStatusViewModel.Params ->
|
||||
val tagStatusObjectComponent = ComponentWithParams { params: TagStatusViewModel.ViewModelParams ->
|
||||
editorComponent.get(params.ctx)
|
||||
.tagStatusObjectComponent()
|
||||
.params(params)
|
||||
.build()
|
||||
}
|
||||
|
||||
val optionObjectComponent = ComponentWithParams { params: OptionViewModel.Params ->
|
||||
editorComponent.get(params.ctx)
|
||||
.optionObjectComponent()
|
||||
.params(params)
|
||||
.build()
|
||||
}
|
||||
|
||||
class Component<T>(private val builder: () -> T) {
|
||||
|
||||
private var instance: T? = null
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
package com.anytypeio.anytype.di.feature
|
||||
|
||||
import com.anytypeio.anytype.analytics.base.Analytics
|
||||
import com.anytypeio.anytype.core_models.Payload
|
||||
import com.anytypeio.anytype.core_utils.di.scope.PerModal
|
||||
import com.anytypeio.anytype.domain.block.repo.BlockRepository
|
||||
import com.anytypeio.anytype.domain.`object`.SetObjectDetails
|
||||
import com.anytypeio.anytype.domain.relations.CreateRelationOption
|
||||
import com.anytypeio.anytype.domain.workspace.SpaceManager
|
||||
import com.anytypeio.anytype.presentation.relations.option.OptionViewModel
|
||||
import com.anytypeio.anytype.presentation.relations.option.OptionViewModelFactory
|
||||
import com.anytypeio.anytype.presentation.relations.providers.ObjectRelationProvider
|
||||
import com.anytypeio.anytype.presentation.relations.providers.ObjectValueProvider
|
||||
import com.anytypeio.anytype.presentation.util.Dispatcher
|
||||
import com.anytypeio.anytype.ui.relations.value.OptionFragment
|
||||
import dagger.BindsInstance
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.Subcomponent
|
||||
import javax.inject.Named
|
||||
|
||||
@PerModal
|
||||
@Subcomponent(
|
||||
modules = [OptionObjectModule::class]
|
||||
)
|
||||
interface OptionObjectComponent {
|
||||
|
||||
@Subcomponent.Builder
|
||||
interface Builder {
|
||||
@BindsInstance
|
||||
fun params(params: OptionViewModel.Params): Builder
|
||||
fun build(): OptionObjectComponent
|
||||
}
|
||||
|
||||
fun inject(fragment: OptionFragment)
|
||||
}
|
||||
|
||||
@Module
|
||||
object OptionObjectModule {
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@PerModal
|
||||
fun createRelationOption(
|
||||
repo: BlockRepository
|
||||
): CreateRelationOption = CreateRelationOption(repo = repo)
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@PerModal
|
||||
fun provideFactory(
|
||||
params: OptionViewModel.Params,
|
||||
@Named(ObjectRelationProvider.INTRINSIC_PROVIDER_TYPE) values: ObjectValueProvider,
|
||||
setObjectDetails: SetObjectDetails,
|
||||
dispatcher: Dispatcher<Payload>,
|
||||
spaceManager: SpaceManager,
|
||||
analytics: Analytics,
|
||||
createOption: CreateRelationOption
|
||||
): OptionViewModelFactory = OptionViewModelFactory(
|
||||
params = params,
|
||||
values = values,
|
||||
createOption = createOption,
|
||||
setObjectDetails = setObjectDetails,
|
||||
dispatcher = dispatcher,
|
||||
spaceManager = spaceManager,
|
||||
analytics = analytics,
|
||||
)
|
||||
}
|
|
@ -3,14 +3,17 @@ package com.anytypeio.anytype.di.feature
|
|||
import com.anytypeio.anytype.analytics.base.Analytics
|
||||
import com.anytypeio.anytype.core_models.Payload
|
||||
import com.anytypeio.anytype.core_utils.di.scope.PerModal
|
||||
import com.anytypeio.anytype.domain.misc.UrlBuilder
|
||||
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
|
||||
import com.anytypeio.anytype.domain.block.repo.BlockRepository
|
||||
import com.anytypeio.anytype.domain.debugging.Logger
|
||||
import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer
|
||||
import com.anytypeio.anytype.domain.`object`.UpdateDetail
|
||||
import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes
|
||||
import com.anytypeio.anytype.domain.objects.options.GetOptions
|
||||
import com.anytypeio.anytype.domain.search.SubscriptionEventChannel
|
||||
import com.anytypeio.anytype.domain.workspace.SpaceManager
|
||||
import com.anytypeio.anytype.presentation.editor.Editor
|
||||
import com.anytypeio.anytype.presentation.relations.providers.ObjectRelationProvider
|
||||
import com.anytypeio.anytype.presentation.relations.providers.ObjectValueProvider
|
||||
import com.anytypeio.anytype.presentation.relations.value.tagstatus.SUB_MY_OPTIONS
|
||||
import com.anytypeio.anytype.presentation.relations.value.tagstatus.TagStatusViewModel
|
||||
import com.anytypeio.anytype.presentation.relations.value.tagstatus.TagStatusViewModelFactory
|
||||
import com.anytypeio.anytype.presentation.util.Dispatcher
|
||||
|
@ -30,7 +33,7 @@ interface TagStatusObjectComponent {
|
|||
@Subcomponent.Builder
|
||||
interface Builder {
|
||||
@BindsInstance
|
||||
fun params(params: TagStatusViewModel.Params): Builder
|
||||
fun params(params: TagStatusViewModel.ViewModelParams): Builder
|
||||
fun build(): TagStatusObjectComponent
|
||||
}
|
||||
|
||||
|
@ -40,6 +43,22 @@ interface TagStatusObjectComponent {
|
|||
@Module
|
||||
object TagStatusObjectModule {
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@PerModal
|
||||
@Named(SUB_MY_OPTIONS)
|
||||
fun provideStoreLessSubscriptionContainer(
|
||||
repo: BlockRepository,
|
||||
channel: SubscriptionEventChannel,
|
||||
dispatchers: AppCoroutineDispatchers,
|
||||
logger: Logger
|
||||
): StorelessSubscriptionContainer = StorelessSubscriptionContainer.Impl(
|
||||
repo = repo,
|
||||
channel = channel,
|
||||
dispatchers = dispatchers,
|
||||
logger = logger
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@PerModal
|
||||
|
@ -47,25 +66,21 @@ object TagStatusObjectModule {
|
|||
@Named(ObjectRelationProvider.INTRINSIC_PROVIDER_TYPE) relations: ObjectRelationProvider,
|
||||
@Named(ObjectRelationProvider.INTRINSIC_PROVIDER_TYPE) values: ObjectValueProvider,
|
||||
storage: Editor.Storage,
|
||||
storeOfObjectTypes: StoreOfObjectTypes,
|
||||
urlBuilder: UrlBuilder,
|
||||
setObjectDetails: UpdateDetail,
|
||||
dispatcher: Dispatcher<Payload>,
|
||||
analytics: Analytics,
|
||||
getOptions: GetOptions,
|
||||
spaceManager: SpaceManager,
|
||||
params: TagStatusViewModel.Params
|
||||
params: TagStatusViewModel.ViewModelParams,
|
||||
@Named(SUB_MY_OPTIONS) subscription: StorelessSubscriptionContainer
|
||||
): TagStatusViewModelFactory = TagStatusViewModelFactory(
|
||||
params = params,
|
||||
values = values,
|
||||
storage = storage,
|
||||
relations = relations,
|
||||
storeOfObjectTypes = storeOfObjectTypes,
|
||||
urlBuilder = urlBuilder,
|
||||
setObjectDetails = setObjectDetails,
|
||||
dispatcher = dispatcher,
|
||||
analytics = analytics,
|
||||
getOptions = getOptions,
|
||||
spaceManager = spaceManager
|
||||
spaceManager = spaceManager,
|
||||
subscription = subscription
|
||||
)
|
||||
}
|
|
@ -0,0 +1,145 @@
|
|||
package com.anytypeio.anytype.ui.relations.value
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.compose.ui.platform.ViewCompositionStrategy
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.anytypeio.anytype.R
|
||||
import com.anytypeio.anytype.core_models.Key
|
||||
import com.anytypeio.anytype.core_ui.relations.OptionWidget
|
||||
import com.anytypeio.anytype.core_utils.ext.argString
|
||||
import com.anytypeio.anytype.core_utils.ext.argStringOrNull
|
||||
import com.anytypeio.anytype.core_utils.ext.subscribe
|
||||
import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetComposeFragment
|
||||
import com.anytypeio.anytype.di.common.componentManager
|
||||
import com.anytypeio.anytype.presentation.relations.option.OptionViewModel
|
||||
import com.anytypeio.anytype.presentation.relations.option.OptionViewModelFactory
|
||||
import com.anytypeio.anytype.presentation.relations.value.tagstatus.RelationContext
|
||||
import com.anytypeio.anytype.ui.settings.typography
|
||||
import javax.inject.Inject
|
||||
|
||||
class OptionFragment : BaseBottomSheetComposeFragment() {
|
||||
|
||||
@Inject
|
||||
lateinit var factory: OptionViewModelFactory
|
||||
private val vm by viewModels<OptionViewModel> { factory }
|
||||
|
||||
private val ctx get() = argString(CTX_KEY)
|
||||
private val objectId get() = argString(OBJECT_ID_KEY)
|
||||
private val relationKey get() = argString(RELATION_KEY)
|
||||
private val optionId get() = argStringOrNull(OPTION_ID_KEY)
|
||||
private val color get() = argStringOrNull(COLOR_KEY)
|
||||
private val text get() = argStringOrNull(TEXT_KEY)
|
||||
private val relationContext get() = requireArguments().getSerializable(RELATION_CONTEXT_KEY) as RelationContext
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
return ComposeView(requireContext()).apply {
|
||||
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
|
||||
setContent {
|
||||
MaterialTheme(
|
||||
typography = typography,
|
||||
shapes = MaterialTheme.shapes.copy(medium = RoundedCornerShape(10.dp)),
|
||||
colors = MaterialTheme.colors.copy(
|
||||
surface = colorResource(id = R.color.context_menu_background)
|
||||
)
|
||||
) {
|
||||
OptionWidget(
|
||||
state = vm.viewState.collectAsStateWithLifecycle().value,
|
||||
onButtonClicked = { vm.onButtonClick() },
|
||||
onTextChanged = { vm.updateName(it) },
|
||||
onColorChanged = { vm.updateColor(it) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
skipCollapsed()
|
||||
expand()
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
jobs += lifecycleScope.subscribe(vm.command) { observeCommand(it) }
|
||||
}
|
||||
|
||||
private fun observeCommand(command: OptionViewModel.Command) {
|
||||
when (command) {
|
||||
is OptionViewModel.Command.Dismiss -> dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
override fun injectDependencies() {
|
||||
val params = OptionViewModel.Params(
|
||||
ctx = ctx,
|
||||
relationKey = relationKey,
|
||||
optionId = optionId,
|
||||
color = color,
|
||||
name = text,
|
||||
objectId = objectId
|
||||
)
|
||||
inject(params)
|
||||
}
|
||||
|
||||
override fun releaseDependencies() {
|
||||
componentManager().optionObjectComponent.release()
|
||||
}
|
||||
|
||||
private fun inject(params: OptionViewModel.Params) {
|
||||
when (relationContext) {
|
||||
RelationContext.OBJECT -> {
|
||||
componentManager()
|
||||
.optionObjectComponent.get(params)
|
||||
.inject(this)
|
||||
}
|
||||
|
||||
RelationContext.OBJECT_SET -> TODO()
|
||||
RelationContext.DATA_VIEW -> TODO()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val CTX_KEY = "arg.option.ctx"
|
||||
const val OBJECT_ID_KEY = "arg.option.object_id"
|
||||
const val RELATION_KEY = "arg.option.relation_key"
|
||||
const val OPTION_ID_KEY = "arg.option.option_id"
|
||||
const val COLOR_KEY = "arg.option.color"
|
||||
const val TEXT_KEY = "arg.option.text"
|
||||
const val RELATION_CONTEXT_KEY = "arg.option.relation-context"
|
||||
|
||||
fun args(
|
||||
ctx: String,
|
||||
objectId: String,
|
||||
relationKey: Key,
|
||||
optionId: String?,
|
||||
color: String?,
|
||||
text: String?,
|
||||
relationContext: RelationContext
|
||||
) = bundleOf(
|
||||
CTX_KEY to ctx,
|
||||
OBJECT_ID_KEY to objectId,
|
||||
RELATION_KEY to relationKey,
|
||||
OPTION_ID_KEY to optionId,
|
||||
COLOR_KEY to color,
|
||||
TEXT_KEY to text,
|
||||
RELATION_CONTEXT_KEY to relationContext
|
||||
)
|
||||
}
|
||||
}
|
|
@ -13,14 +13,18 @@ import androidx.compose.ui.unit.dp
|
|||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.anytypeio.anytype.R
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.Key
|
||||
import com.anytypeio.anytype.core_ui.relations.RelationsValueScreen
|
||||
import com.anytypeio.anytype.core_utils.ext.argBoolean
|
||||
import com.anytypeio.anytype.core_utils.ext.argString
|
||||
import com.anytypeio.anytype.core_utils.ext.subscribe
|
||||
import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetComposeFragment
|
||||
import com.anytypeio.anytype.di.common.componentManager
|
||||
import com.anytypeio.anytype.presentation.relations.value.tagstatus.Command
|
||||
import com.anytypeio.anytype.presentation.relations.value.tagstatus.RelationContext
|
||||
import com.anytypeio.anytype.presentation.relations.value.tagstatus.TagStatusViewModel
|
||||
import com.anytypeio.anytype.presentation.relations.value.tagstatus.TagStatusViewModelFactory
|
||||
|
@ -65,10 +69,33 @@ class TagStatusFragment : BaseBottomSheetComposeFragment() {
|
|||
override fun onStart() {
|
||||
super.onStart()
|
||||
vm.onStart()
|
||||
jobs += lifecycleScope.subscribe(vm.commands) { observeCommands(it) }
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
vm.onStop()
|
||||
}
|
||||
|
||||
private fun observeCommands(command: Command) {
|
||||
when (command) {
|
||||
is Command.OpenOptionScreen -> {
|
||||
val arg = OptionFragment.args(
|
||||
ctx = command.ctx,
|
||||
objectId = command.objectId,
|
||||
relationKey = command.relationKey,
|
||||
optionId = command.optionId,
|
||||
color = command.color,
|
||||
text = command.text,
|
||||
relationContext = relationContext
|
||||
)
|
||||
findNavController().navigate(R.id.optionScreen, arg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun injectDependencies() {
|
||||
val params = TagStatusViewModel.Params(
|
||||
val params = TagStatusViewModel.ViewModelParams(
|
||||
ctx = ctx,
|
||||
objectId = objectId,
|
||||
relationKey = relationKey,
|
||||
|
|
|
@ -72,6 +72,7 @@
|
|||
|
||||
<include app:graph="@navigation/nav_editor_modal" />
|
||||
<include app:graph="@navigation/nav_templates_modal" />
|
||||
<include app:graph="@navigation/nav_relations" />
|
||||
|
||||
<navigation
|
||||
android:id="@+id/dataViewNavigation"
|
||||
|
|
17
app/src/main/res/navigation/nav_relations.xml
Normal file
17
app/src/main/res/navigation/nav_relations.xml
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/nav_relations"
|
||||
app:startDestination="@id/tagStatusScreen">
|
||||
|
||||
<dialog
|
||||
android:id="@+id/tagStatusScreen"
|
||||
android:name="com.anytypeio.anytype.ui.relations.value.TagStatusFragment"
|
||||
android:label="Tag status screen" />
|
||||
|
||||
<dialog
|
||||
android:id="@+id/optionScreen"
|
||||
android:name="com.anytypeio.anytype.ui.relations.value.OptionFragment"
|
||||
android:label="Option create or edit screen" />
|
||||
|
||||
</navigation>
|
|
@ -33,7 +33,7 @@ fun ItemMenu(
|
|||
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 11.dp),
|
||||
onClick = {
|
||||
isMenuExpanded.value = false
|
||||
action(TagStatusAction.Edit(item.optionId))
|
||||
action(TagStatusAction.Edit(item))
|
||||
},
|
||||
) {
|
||||
Text(
|
||||
|
|
|
@ -0,0 +1,296 @@
|
|||
package com.anytypeio.anytype.core_ui.relations
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
import androidx.compose.ui.platform.SoftwareKeyboardController
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Devices
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.anytypeio.anytype.core_models.ThemeColor
|
||||
import com.anytypeio.anytype.core_ui.R
|
||||
import com.anytypeio.anytype.core_ui.extensions.dark
|
||||
import com.anytypeio.anytype.core_ui.foundation.Divider
|
||||
import com.anytypeio.anytype.core_ui.foundation.Dragger
|
||||
import com.anytypeio.anytype.core_ui.foundation.noRippleClickable
|
||||
import com.anytypeio.anytype.core_ui.views.ButtonPrimary
|
||||
import com.anytypeio.anytype.core_ui.views.ButtonSize
|
||||
import com.anytypeio.anytype.core_ui.views.Caption1Regular
|
||||
import com.anytypeio.anytype.core_ui.views.Title1
|
||||
import com.anytypeio.anytype.presentation.relations.option.OptionScreenViewState
|
||||
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
@Composable
|
||||
fun OptionWidget(
|
||||
state: OptionScreenViewState,
|
||||
onButtonClicked: () -> Unit,
|
||||
onTextChanged: (String) -> Unit,
|
||||
onColorChanged: (ThemeColor) -> Unit
|
||||
) {
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight(),
|
||||
contentAlignment = Alignment.TopCenter,
|
||||
) {
|
||||
|
||||
val currentState by rememberUpdatedState(state)
|
||||
val keyboardController = LocalSoftwareKeyboardController.current
|
||||
|
||||
OptionWidgetContent(
|
||||
state = currentState,
|
||||
onButtonClicked = onButtonClicked,
|
||||
focusRequester = focusRequester,
|
||||
keyboardController = keyboardController,
|
||||
onTextChanged = onTextChanged,
|
||||
onColorChanged = onColorChanged
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
@Composable
|
||||
fun OptionWidgetContent(
|
||||
state: OptionScreenViewState,
|
||||
onButtonClicked: () -> Unit,
|
||||
focusRequester: FocusRequester,
|
||||
keyboardController: SoftwareKeyboardController?,
|
||||
onTextChanged: (String) -> Unit,
|
||||
onColorChanged: (ThemeColor) -> Unit
|
||||
) {
|
||||
var selectedColor by remember { mutableStateOf(state.color) }
|
||||
var editableText by remember { mutableStateOf(state.text) }
|
||||
|
||||
val (title, buttonText) = getTexts(state)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(bottom = 20.dp, start = 20.dp, end = 20.dp)
|
||||
) {
|
||||
Header(text = title)
|
||||
TextInput(
|
||||
initialValue = state.text,
|
||||
color = state.color,
|
||||
onTextChanged = {
|
||||
editableText = it
|
||||
onTextChanged(it)
|
||||
},
|
||||
focusRequester = focusRequester,
|
||||
keyboardController = keyboardController
|
||||
)
|
||||
Divider(paddingEnd = 0.dp, paddingStart = 0.dp)
|
||||
Spacer(modifier = Modifier.height(26.dp))
|
||||
Text(
|
||||
text = stringResource(id = R.string.color),
|
||||
style = Caption1Regular,
|
||||
color = colorResource(id = R.color.text_secondary)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
CirclesContainer(selectedColor = selectedColor) { newColor ->
|
||||
selectedColor = newColor
|
||||
onColorChanged(newColor)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(115.dp))
|
||||
ButtonPrimary(
|
||||
text = buttonText,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.imePadding(),
|
||||
onClick = { onButtonClicked() },
|
||||
size = ButtonSize.Large
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun CirclesContainer(selectedColor: ThemeColor, action: (ThemeColor) -> Unit) {
|
||||
FlowRow(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight(),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
maxItemsInEachRow = 5
|
||||
) {
|
||||
val itemModifier = Modifier
|
||||
.weight(1f)
|
||||
.fillMaxWidth()
|
||||
.clip(CircleShape)
|
||||
.aspectRatio(1f)
|
||||
.align(Alignment.CenterVertically)
|
||||
ThemeColor.values().drop(1).forEach { color ->
|
||||
Box(
|
||||
itemModifier
|
||||
.noRippleClickable { action(color) }
|
||||
.background(
|
||||
color = dark(color = color)
|
||||
)
|
||||
) {
|
||||
if (selectedColor == color) {
|
||||
Image(
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
painter = painterResource(id = R.drawable.ic_tick_24),
|
||||
contentDescription = "option selected"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
@Composable
|
||||
private fun TextInput(
|
||||
initialValue: String,
|
||||
color: ThemeColor,
|
||||
focusRequester: FocusRequester,
|
||||
keyboardController: SoftwareKeyboardController?,
|
||||
onTextChanged: (String) -> Unit
|
||||
) {
|
||||
var innerValue by remember { mutableStateOf(initialValue) }
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
if (initialValue.isEmpty()) {
|
||||
LaunchedEffect(Unit) {
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
}
|
||||
|
||||
BasicTextField(
|
||||
value = innerValue,
|
||||
onValueChange = {
|
||||
innerValue = it
|
||||
onTextChanged(it)
|
||||
},
|
||||
textStyle = Title1.copy(color = dark(color = color)),
|
||||
singleLine = true,
|
||||
enabled = true,
|
||||
cursorBrush = SolidColor(dark(color = color)),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.padding(start = 0.dp, top = 2.dp)
|
||||
.focusRequester(focusRequester),
|
||||
keyboardOptions = KeyboardOptions(
|
||||
imeAction = ImeAction.Done
|
||||
),
|
||||
keyboardActions = KeyboardActions {
|
||||
keyboardController?.hide()
|
||||
focusManager.clearFocus()
|
||||
},
|
||||
decorationBox = { innerTextField ->
|
||||
if (innerValue.isEmpty()) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.hint_enter_name),
|
||||
style = Title1,
|
||||
color = colorResource(id = R.color.text_tertiary),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
)
|
||||
}
|
||||
innerTextField()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Header(text: String) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 6.dp)
|
||||
) {
|
||||
Dragger(modifier = Modifier.align(Alignment.Center))
|
||||
}
|
||||
|
||||
// Main content box
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(48.dp)
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.align(Alignment.Center)
|
||||
.padding(horizontal = 74.dp),
|
||||
text = text,
|
||||
style = Title1.copy(),
|
||||
color = colorResource(R.color.text_primary),
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun getTexts(state: OptionScreenViewState): Pair<String, String> {
|
||||
return when (state) {
|
||||
is OptionScreenViewState.Create -> {
|
||||
stringResource(id = R.string.option_widget_create) to stringResource(id = R.string.create)
|
||||
}
|
||||
|
||||
is OptionScreenViewState.Edit -> {
|
||||
stringResource(id = R.string.option_widget_edit) to stringResource(id = R.string.apply)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, backgroundColor = 0xFFFFFFFF, device = Devices.PIXEL_4_XL)
|
||||
@Composable
|
||||
fun PreviewOptionWidget() {
|
||||
OptionWidget(
|
||||
state = OptionScreenViewState.Create(
|
||||
text = "Urgent",
|
||||
color = ThemeColor.BLUE,
|
||||
),
|
||||
onButtonClicked = {},
|
||||
onTextChanged = {},
|
||||
onColorChanged = {}
|
||||
)
|
||||
}
|
10
core-ui/src/main/res/drawable/ic_tick_24.xml
Normal file
10
core-ui/src/main/res/drawable/ic_tick_24.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M18.633,6.403L11.131,18.191L5.47,12.53L6.53,11.47L10.869,15.809L17.367,5.597L18.633,6.403Z"
|
||||
android:fillColor="@color/text_white"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
|
@ -4,4 +4,5 @@
|
|||
<item>MIIEqDCCA5CgAwIBAgIJA071MA0GCSqGSIb3DQEBBAUAMIGUMQsww...</item>
|
||||
</string-array>
|
||||
<string name="msg_query_cannot_be_changed">This query can be changed on desktop only</string>
|
||||
<string name="hint_enter_name">Enter name...</string>
|
||||
</resources>
|
||||
|
|
|
@ -1251,4 +1251,7 @@
|
|||
|
||||
<string name="relation_value_create_new">Create \"%1$s\"</string>
|
||||
|
||||
<string name="option_widget_create">Create option</string>
|
||||
<string name="option_widget_edit">Edit option</string>
|
||||
|
||||
</resources>
|
|
@ -0,0 +1,189 @@
|
|||
package com.anytypeio.anytype.presentation.relations.option
|
||||
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.anytypeio.anytype.analytics.base.Analytics
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.Key
|
||||
import com.anytypeio.anytype.core_models.ObjectWrapper
|
||||
import com.anytypeio.anytype.core_models.Payload
|
||||
import com.anytypeio.anytype.core_models.Relations
|
||||
import com.anytypeio.anytype.core_models.ThemeColor
|
||||
import com.anytypeio.anytype.core_utils.ext.typeOf
|
||||
import com.anytypeio.anytype.domain.base.fold
|
||||
import com.anytypeio.anytype.domain.`object`.SetObjectDetails
|
||||
import com.anytypeio.anytype.domain.relations.CreateRelationOption
|
||||
import com.anytypeio.anytype.domain.workspace.SpaceManager
|
||||
import com.anytypeio.anytype.presentation.common.BaseViewModel
|
||||
import com.anytypeio.anytype.presentation.extension.sendAnalyticsRelationValueEvent
|
||||
import com.anytypeio.anytype.presentation.relations.providers.ObjectValueProvider
|
||||
import com.anytypeio.anytype.presentation.util.Dispatcher
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
||||
class OptionViewModel(
|
||||
private val vmParams: Params,
|
||||
private val values: ObjectValueProvider,
|
||||
private val createOption: CreateRelationOption,
|
||||
private val setObjectDetails: SetObjectDetails,
|
||||
private val dispatcher: Dispatcher<Payload>,
|
||||
private val spaceManager: SpaceManager,
|
||||
private val analytics: Analytics
|
||||
) : BaseViewModel() {
|
||||
|
||||
val command = MutableSharedFlow<Command>(replay = 0)
|
||||
val viewState: MutableStateFlow<OptionScreenViewState> =
|
||||
if (vmParams.optionId == null) {
|
||||
val color = if (vmParams.color != null) {
|
||||
ThemeColor.fromCode(vmParams.color)
|
||||
} else {
|
||||
ThemeColor.values().drop(1).random()
|
||||
}
|
||||
MutableStateFlow(
|
||||
OptionScreenViewState.Create(
|
||||
text = vmParams.name.orEmpty(),
|
||||
color = color
|
||||
)
|
||||
)
|
||||
} else {
|
||||
val color = if (vmParams.color != null) {
|
||||
ThemeColor.fromCode(vmParams.color)
|
||||
} else {
|
||||
ThemeColor.values().filter { it != ThemeColor.DEFAULT }.random()
|
||||
}
|
||||
MutableStateFlow(
|
||||
OptionScreenViewState.Edit(
|
||||
optionId = vmParams.optionId,
|
||||
text = vmParams.name.orEmpty(),
|
||||
color = color
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun updateName(name: String) {
|
||||
viewState.value = when (val state = viewState.value) {
|
||||
is OptionScreenViewState.Create -> state.copy(text = name)
|
||||
is OptionScreenViewState.Edit -> state.copy(text = name)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateColor(color: ThemeColor) {
|
||||
viewState.value = when (val state = viewState.value) {
|
||||
is OptionScreenViewState.Create -> state.copy(color = color)
|
||||
is OptionScreenViewState.Edit -> state.copy(color = color)
|
||||
}
|
||||
}
|
||||
|
||||
fun onButtonClick() {
|
||||
when (viewState.value) {
|
||||
is OptionScreenViewState.Create -> createOption()
|
||||
is OptionScreenViewState.Edit -> updateOption()
|
||||
}
|
||||
}
|
||||
|
||||
private fun createOption() {
|
||||
viewModelScope.launch {
|
||||
val params = CreateRelationOption.Params(
|
||||
space = spaceManager.get(),
|
||||
relation = vmParams.relationKey,
|
||||
name = viewState.value.text,
|
||||
color = viewState.value.color.code
|
||||
)
|
||||
if (params.name.isEmpty()) {
|
||||
return@launch
|
||||
}
|
||||
createOption.invoke(params).proceed(
|
||||
success = { option ->
|
||||
proceedWithAddingTagToObject(
|
||||
ctx = vmParams.ctx,
|
||||
objectId = vmParams.objectId,
|
||||
relationKey = vmParams.relationKey,
|
||||
option = option
|
||||
)
|
||||
},
|
||||
failure = { Timber.e(it, "Error while creating option") }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateOption() {
|
||||
val optionId = vmParams.optionId ?: return
|
||||
viewModelScope.launch {
|
||||
val params = SetObjectDetails.Params(
|
||||
ctx = optionId,
|
||||
details = mapOf(
|
||||
Relations.NAME to viewState.value.text,
|
||||
Relations.RELATION_OPTION_COLOR to viewState.value.color.code
|
||||
)
|
||||
)
|
||||
setObjectDetails.execute(params).fold(
|
||||
onFailure = { Timber.e(it, "Error while updating option") },
|
||||
onSuccess = {
|
||||
dispatcher.send(it)
|
||||
viewModelScope.sendAnalyticsRelationValueEvent(analytics)
|
||||
command.emit(Command.Dismiss)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun proceedWithAddingTagToObject(
|
||||
ctx: Id,
|
||||
objectId: Id,
|
||||
relationKey: Key,
|
||||
option: ObjectWrapper.Option
|
||||
) {
|
||||
Timber.d("Adding option to object with id: $objectId")
|
||||
val obj = values.get(target = objectId, ctx = ctx)
|
||||
val result = mutableListOf<Id>()
|
||||
val value = obj[relationKey]
|
||||
if (value is List<*>) {
|
||||
result.addAll(value.typeOf())
|
||||
} else if (value is Id) {
|
||||
result.add(value)
|
||||
}
|
||||
result.add(option.id)
|
||||
val params = SetObjectDetails.Params(
|
||||
ctx = objectId,
|
||||
details = mapOf(relationKey to result)
|
||||
)
|
||||
setObjectDetails.execute(params).fold(
|
||||
onFailure = { Timber.e(it, "Error while adding tag to object") },
|
||||
onSuccess = {
|
||||
dispatcher.send(it)
|
||||
viewModelScope.sendAnalyticsRelationValueEvent(analytics)
|
||||
command.emit(Command.Dismiss)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
data class Params(
|
||||
val ctx: Id,
|
||||
val relationKey: Key,
|
||||
val objectId: Id,
|
||||
val optionId: Id?,
|
||||
val name: String?,
|
||||
val color: String?
|
||||
)
|
||||
|
||||
sealed class Command {
|
||||
object Dismiss : Command()
|
||||
}
|
||||
}
|
||||
|
||||
sealed class OptionScreenViewState {
|
||||
abstract val text: String
|
||||
abstract val color: ThemeColor
|
||||
|
||||
data class Edit(
|
||||
val optionId: Id,
|
||||
override val text: String,
|
||||
override val color: ThemeColor
|
||||
) : OptionScreenViewState()
|
||||
|
||||
data class Create(
|
||||
override val text: String,
|
||||
override val color: ThemeColor
|
||||
) : OptionScreenViewState()
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package com.anytypeio.anytype.presentation.relations.option
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.anytypeio.anytype.analytics.base.Analytics
|
||||
import com.anytypeio.anytype.core_models.Payload
|
||||
import com.anytypeio.anytype.domain.`object`.SetObjectDetails
|
||||
import com.anytypeio.anytype.domain.relations.CreateRelationOption
|
||||
import com.anytypeio.anytype.domain.workspace.SpaceManager
|
||||
import com.anytypeio.anytype.presentation.relations.providers.ObjectValueProvider
|
||||
import com.anytypeio.anytype.presentation.util.Dispatcher
|
||||
import javax.inject.Inject
|
||||
|
||||
class OptionViewModelFactory @Inject constructor(
|
||||
private val params: OptionViewModel.Params,
|
||||
private val values: ObjectValueProvider,
|
||||
private val createOption: CreateRelationOption,
|
||||
private val setObjectDetails: SetObjectDetails,
|
||||
private val dispatcher: Dispatcher<Payload>,
|
||||
private val spaceManager: SpaceManager,
|
||||
private val analytics: Analytics,
|
||||
) : ViewModelProvider.Factory {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel> create(
|
||||
modelClass: Class<T>
|
||||
) = OptionViewModel(
|
||||
vmParams = params,
|
||||
values = values,
|
||||
createOption = createOption,
|
||||
setObjectDetails = setObjectDetails,
|
||||
dispatcher = dispatcher,
|
||||
spaceManager = spaceManager,
|
||||
analytics = analytics,
|
||||
) as T
|
||||
}
|
|
@ -3,25 +3,27 @@ package com.anytypeio.anytype.presentation.relations.value.tagstatus
|
|||
import androidx.lifecycle.viewModelScope
|
||||
import com.anytypeio.anytype.analytics.base.Analytics
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.Key
|
||||
import com.anytypeio.anytype.core_models.ObjectWrapper
|
||||
import com.anytypeio.anytype.core_models.Payload
|
||||
import com.anytypeio.anytype.core_models.Relation
|
||||
import com.anytypeio.anytype.core_models.Struct
|
||||
import com.anytypeio.anytype.core_models.ThemeColor
|
||||
import com.anytypeio.anytype.core_models.restrictions.ObjectRestriction
|
||||
import com.anytypeio.anytype.core_utils.ext.cancel
|
||||
import com.anytypeio.anytype.core_utils.ext.typeOf
|
||||
import com.anytypeio.anytype.domain.misc.UrlBuilder
|
||||
import com.anytypeio.anytype.domain.library.StoreSearchParams
|
||||
import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer
|
||||
import com.anytypeio.anytype.domain.`object`.UpdateDetail
|
||||
import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes
|
||||
import com.anytypeio.anytype.domain.objects.options.GetOptions
|
||||
import com.anytypeio.anytype.domain.workspace.SpaceManager
|
||||
import com.anytypeio.anytype.presentation.common.BaseViewModel
|
||||
import com.anytypeio.anytype.presentation.editor.Editor
|
||||
import com.anytypeio.anytype.presentation.extension.sendAnalyticsRelationValueEvent
|
||||
import com.anytypeio.anytype.presentation.relations.providers.ObjectRelationProvider
|
||||
import com.anytypeio.anytype.presentation.relations.providers.ObjectValueProvider
|
||||
import com.anytypeio.anytype.presentation.search.ObjectSearchConstants
|
||||
import com.anytypeio.anytype.presentation.sets.filterIdsById
|
||||
import com.anytypeio.anytype.presentation.util.Dispatcher
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.collect
|
||||
|
@ -31,49 +33,64 @@ import kotlinx.coroutines.launch
|
|||
import timber.log.Timber
|
||||
|
||||
class TagStatusViewModel(
|
||||
private val params: Params,
|
||||
private val viewModelParams: ViewModelParams,
|
||||
private val relations: ObjectRelationProvider,
|
||||
private val values: ObjectValueProvider,
|
||||
private val storage: Editor.Storage,
|
||||
private val storeOfObjectTypes: StoreOfObjectTypes,
|
||||
private val urlBuilder: UrlBuilder,
|
||||
private val dispatcher: Dispatcher<Payload>,
|
||||
private val setObjectDetails: UpdateDetail,
|
||||
private val analytics: Analytics,
|
||||
private val getOptions: GetOptions,
|
||||
private val spaceManager: SpaceManager
|
||||
private val spaceManager: SpaceManager,
|
||||
private val subscription: StorelessSubscriptionContainer
|
||||
) : BaseViewModel() {
|
||||
|
||||
val viewState = MutableStateFlow<TagStatusViewState>(TagStatusViewState.Loading)
|
||||
private val query = MutableSharedFlow<String>()
|
||||
private val query = MutableSharedFlow<String>(replay = 0)
|
||||
private var isRelationNotEditable = false
|
||||
val commands = MutableSharedFlow<Command>(replay = 0)
|
||||
private val jobs = mutableListOf<Job>()
|
||||
|
||||
fun onStart() {
|
||||
val obj = storage.details.current().details[params.objectId]
|
||||
isRelationNotEditable = params.isLocked || storage.objectRestrictions.current()
|
||||
.contains(ObjectRestriction.RELATIONS)
|
||||
Timber.d("TagStatusViewModel onStart, params: $params, obj: $obj, isRelationNotEditable: $isRelationNotEditable")
|
||||
viewModelScope.launch {
|
||||
Timber.d("onStart, params: $viewModelParams")
|
||||
jobs += viewModelScope.launch {
|
||||
val relation = relations.get(relation = viewModelParams.relationKey)
|
||||
val spaces = listOf(spaceManager.get())
|
||||
val searchParams = StoreSearchParams(
|
||||
subscription = SUB_MY_OPTIONS,
|
||||
keys = ObjectSearchConstants.keysRelationOptions,
|
||||
filters = ObjectSearchConstants.filterRelationOptions(
|
||||
relationKey = viewModelParams.relationKey,
|
||||
spaces = spaces
|
||||
)
|
||||
)
|
||||
combine(
|
||||
relations.observe(
|
||||
relation = params.relationKey
|
||||
),
|
||||
values.subscribe(
|
||||
ctx = params.ctx,
|
||||
target = params.objectId
|
||||
ctx = viewModelParams.ctx,
|
||||
target = viewModelParams.objectId
|
||||
),
|
||||
query.onStart { emit("") }
|
||||
) { relation, record, query ->
|
||||
query.onStart { emit("") },
|
||||
subscription.subscribe(searchParams)
|
||||
) { record, query, options ->
|
||||
setupIsRelationNotEditable(relation)
|
||||
getAllOptions(
|
||||
initViewState(
|
||||
relation = relation,
|
||||
record = record,
|
||||
options = options
|
||||
.map { ObjectWrapper.Option(map = it.map) }
|
||||
.filter { it.name?.contains(query, true) == true },
|
||||
query = query
|
||||
)
|
||||
}.collect()
|
||||
}
|
||||
}
|
||||
|
||||
fun onStop() {
|
||||
viewModelScope.launch {
|
||||
subscription.unsubscribe(listOf(SUB_MY_OPTIONS))
|
||||
}
|
||||
jobs.cancel()
|
||||
}
|
||||
|
||||
fun onQueryChanged(input: String) {
|
||||
viewModelScope.launch {
|
||||
query.emit(input)
|
||||
|
@ -96,10 +113,36 @@ class TagStatusViewModel(
|
|||
viewState.value = currentState.copy(showItemMenu = action.item)
|
||||
}
|
||||
}
|
||||
TagStatusAction.Plus -> TODO()
|
||||
|
||||
TagStatusAction.Plus -> emitCommand(
|
||||
Command.OpenOptionScreen(
|
||||
color = ThemeColor.values().drop(1).random().code,
|
||||
relationKey = viewModelParams.relationKey,
|
||||
ctx = viewModelParams.ctx,
|
||||
objectId = viewModelParams.objectId
|
||||
)
|
||||
)
|
||||
is TagStatusAction.Delete -> TODO()
|
||||
is TagStatusAction.Duplicate -> TODO()
|
||||
is TagStatusAction.Edit -> TODO()
|
||||
is TagStatusAction.Edit -> {
|
||||
val item = action.item
|
||||
emitCommand(
|
||||
Command.OpenOptionScreen(
|
||||
optionId = item.optionId,
|
||||
color = item.color.code,
|
||||
text = item.name,
|
||||
relationKey = viewModelParams.relationKey,
|
||||
ctx = viewModelParams.ctx,
|
||||
objectId = viewModelParams.objectId
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun emitCommand(command: Command) {
|
||||
viewModelScope.launch {
|
||||
commands.emit(command)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -119,35 +162,27 @@ class TagStatusViewModel(
|
|||
addTag(item.optionId)
|
||||
}
|
||||
}
|
||||
is RelationsListItem.CreateItem.Status -> TODO()
|
||||
is RelationsListItem.CreateItem.Tag -> TODO()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getAllOptions(
|
||||
relation: ObjectWrapper.Relation,
|
||||
record: Struct,
|
||||
query: String
|
||||
) {
|
||||
val params = GetOptions.Params(
|
||||
space = spaceManager.get(),
|
||||
relation = relation.key,
|
||||
fulltext = query
|
||||
)
|
||||
getOptions(params).proceed(
|
||||
success = { options ->
|
||||
Timber.d("TagStatusViewModel getAllOptions, options: ${options.size}")
|
||||
initViewState(
|
||||
relation = relation,
|
||||
record = record,
|
||||
options = options,
|
||||
query = query
|
||||
is RelationsListItem.CreateItem.Status -> {
|
||||
emitCommand(
|
||||
Command.OpenOptionScreen(
|
||||
text = item.text,
|
||||
relationKey = viewModelParams.relationKey,
|
||||
ctx = viewModelParams.ctx,
|
||||
objectId = viewModelParams.objectId
|
||||
)
|
||||
)
|
||||
},
|
||||
failure = {
|
||||
Timber.e(it, "Error while getting options by id")
|
||||
}
|
||||
)
|
||||
is RelationsListItem.CreateItem.Tag -> {
|
||||
emitCommand(
|
||||
Command.OpenOptionScreen(
|
||||
text = item.text,
|
||||
relationKey = viewModelParams.relationKey,
|
||||
ctx = viewModelParams.ctx,
|
||||
objectId = viewModelParams.objectId
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun initViewState(
|
||||
|
@ -159,7 +194,7 @@ class TagStatusViewModel(
|
|||
val result = mutableListOf<RelationsListItem>()
|
||||
when (relation.format) {
|
||||
Relation.Format.STATUS -> {
|
||||
val ids: List<Id> = when (val value = record[params.relationKey]) {
|
||||
val ids: List<Id> = when (val value = record[viewModelParams.relationKey]) {
|
||||
is Id -> listOf(value)
|
||||
is List<*> -> value.typeOf()
|
||||
else -> emptyList()
|
||||
|
@ -172,7 +207,7 @@ class TagStatusViewModel(
|
|||
)
|
||||
}
|
||||
Relation.Format.TAG -> {
|
||||
val ids: List<Id> = when (val value = record[params.relationKey]) {
|
||||
val ids: List<Id> = when (val value = record[viewModelParams.relationKey]) {
|
||||
is Id -> listOf(value)
|
||||
is List<*> -> value.typeOf()
|
||||
else -> emptyList()
|
||||
|
@ -208,9 +243,9 @@ class TagStatusViewModel(
|
|||
|
||||
private fun addTag(tag: Id) {
|
||||
viewModelScope.launch {
|
||||
val obj = values.get(ctx = params.ctx, target = params.objectId)
|
||||
val obj = values.get(ctx = viewModelParams.ctx, target = viewModelParams.objectId)
|
||||
val result = mutableListOf<Id>()
|
||||
val value = obj[params.relationKey]
|
||||
val value = obj[viewModelParams.relationKey]
|
||||
if (value is List<*>) {
|
||||
result.addAll(value.typeOf())
|
||||
} else if (value is Id) {
|
||||
|
@ -219,8 +254,8 @@ class TagStatusViewModel(
|
|||
result.add(tag)
|
||||
setObjectDetails(
|
||||
UpdateDetail.Params(
|
||||
target = params.objectId,
|
||||
key = params.relationKey,
|
||||
target = viewModelParams.objectId,
|
||||
key = viewModelParams.relationKey,
|
||||
value = result
|
||||
)
|
||||
).process(
|
||||
|
@ -235,12 +270,12 @@ class TagStatusViewModel(
|
|||
|
||||
private fun removeTag(tag: Id) {
|
||||
viewModelScope.launch {
|
||||
val obj = values.get(ctx = params.ctx, target = params.objectId)
|
||||
val remaining = obj[params.relationKey].filterIdsById(tag)
|
||||
val obj = values.get(ctx = viewModelParams.ctx, target = viewModelParams.objectId)
|
||||
val remaining = obj[viewModelParams.relationKey].filterIdsById(tag)
|
||||
setObjectDetails(
|
||||
UpdateDetail.Params(
|
||||
target = params.objectId,
|
||||
key = params.relationKey,
|
||||
target = viewModelParams.objectId,
|
||||
key = viewModelParams.relationKey,
|
||||
value = remaining
|
||||
)
|
||||
).process(
|
||||
|
@ -256,8 +291,8 @@ class TagStatusViewModel(
|
|||
viewModelScope.launch {
|
||||
setObjectDetails(
|
||||
UpdateDetail.Params(
|
||||
target = params.objectId,
|
||||
key = params.relationKey,
|
||||
target = viewModelParams.objectId,
|
||||
key = viewModelParams.relationKey,
|
||||
value = listOf(status)
|
||||
)
|
||||
).process(
|
||||
|
@ -274,8 +309,8 @@ class TagStatusViewModel(
|
|||
viewModelScope.launch {
|
||||
setObjectDetails(
|
||||
UpdateDetail.Params(
|
||||
target = params.objectId,
|
||||
key = params.relationKey,
|
||||
target = viewModelParams.objectId,
|
||||
key = viewModelParams.relationKey,
|
||||
value = null
|
||||
)
|
||||
).process(
|
||||
|
@ -322,7 +357,7 @@ class TagStatusViewModel(
|
|||
}
|
||||
|
||||
private fun setupIsRelationNotEditable(relation: ObjectWrapper.Relation) {
|
||||
isRelationNotEditable = params.isLocked
|
||||
isRelationNotEditable = viewModelParams.isLocked
|
||||
|| storage.objectRestrictions.current().contains(ObjectRestriction.RELATIONS)
|
||||
|| relation.isReadonlyValue
|
||||
|| relation.isHidden == true
|
||||
|
@ -331,15 +366,33 @@ class TagStatusViewModel(
|
|||
|| !relation.isValid
|
||||
}
|
||||
|
||||
data class Params(
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
viewModelScope.launch {
|
||||
subscription.unsubscribe(listOf(SUB_MY_OPTIONS))
|
||||
}
|
||||
}
|
||||
|
||||
data class ViewModelParams(
|
||||
val ctx: Id,
|
||||
val objectId: Id,
|
||||
val relationKey: Id,
|
||||
val relationKey: Key,
|
||||
val isLocked: Boolean,
|
||||
val relationContext: RelationContext
|
||||
)
|
||||
}
|
||||
|
||||
sealed class Command {
|
||||
data class OpenOptionScreen(
|
||||
val ctx: Id,
|
||||
val objectId: Id,
|
||||
val relationKey: Key,
|
||||
val optionId: Id? = null,
|
||||
val color: String? = null,
|
||||
val text: String? = null,
|
||||
) : Command()
|
||||
}
|
||||
|
||||
sealed class TagStatusViewState {
|
||||
|
||||
object Loading : TagStatusViewState()
|
||||
|
@ -359,32 +412,36 @@ sealed class TagStatusAction {
|
|||
data class LongClick(val item: RelationsListItem.Item) : TagStatusAction()
|
||||
object Clear : TagStatusAction()
|
||||
object Plus : TagStatusAction()
|
||||
data class Edit(val optionId: Id) : TagStatusAction()
|
||||
data class Edit(val item: RelationsListItem.Item) : TagStatusAction()
|
||||
data class Delete(val optionId: Id) : TagStatusAction()
|
||||
data class Duplicate(val optionId: Id) : TagStatusAction()
|
||||
}
|
||||
|
||||
enum class RelationContext{ OBJECT, OBJECT_SET, DATA_VIEW }
|
||||
enum class RelationContext { OBJECT, OBJECT_SET, DATA_VIEW }
|
||||
|
||||
sealed class RelationsListItem {
|
||||
|
||||
sealed class Item : RelationsListItem() {
|
||||
|
||||
abstract val optionId: Id
|
||||
abstract val name: String
|
||||
abstract val color: ThemeColor
|
||||
abstract val isSelected: Boolean
|
||||
|
||||
data class Tag(
|
||||
override val optionId: Id,
|
||||
val name: String,
|
||||
val color: ThemeColor,
|
||||
val isSelected: Boolean,
|
||||
override val name: String,
|
||||
override val color: ThemeColor,
|
||||
override val isSelected: Boolean,
|
||||
val number: Int = Int.MAX_VALUE,
|
||||
val showMenu: Boolean = false
|
||||
) : Item()
|
||||
|
||||
data class Status(
|
||||
override val optionId: Id,
|
||||
val name: String,
|
||||
val color: ThemeColor,
|
||||
val isSelected: Boolean
|
||||
override val name: String,
|
||||
override val color: ThemeColor,
|
||||
override val isSelected: Boolean
|
||||
) : Item()
|
||||
}
|
||||
|
||||
|
@ -394,4 +451,6 @@ sealed class RelationsListItem {
|
|||
class Tag(text: String) : CreateItem(text)
|
||||
class Status(text: String) : CreateItem(text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const val SUB_MY_OPTIONS = "subscription.relation_options"
|
||||
|
|
|
@ -4,10 +4,8 @@ import androidx.lifecycle.ViewModel
|
|||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.anytypeio.anytype.analytics.base.Analytics
|
||||
import com.anytypeio.anytype.core_models.Payload
|
||||
import com.anytypeio.anytype.domain.misc.UrlBuilder
|
||||
import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer
|
||||
import com.anytypeio.anytype.domain.`object`.UpdateDetail
|
||||
import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes
|
||||
import com.anytypeio.anytype.domain.objects.options.GetOptions
|
||||
import com.anytypeio.anytype.domain.workspace.SpaceManager
|
||||
import com.anytypeio.anytype.presentation.editor.Editor
|
||||
import com.anytypeio.anytype.presentation.relations.providers.ObjectRelationProvider
|
||||
|
@ -16,32 +14,28 @@ import com.anytypeio.anytype.presentation.util.Dispatcher
|
|||
import javax.inject.Inject
|
||||
|
||||
class TagStatusViewModelFactory @Inject constructor(
|
||||
private val params: TagStatusViewModel.Params,
|
||||
private val params: TagStatusViewModel.ViewModelParams,
|
||||
private val relations: ObjectRelationProvider,
|
||||
private val values: ObjectValueProvider,
|
||||
private val storage: Editor.Storage,
|
||||
private val storeOfObjectTypes: StoreOfObjectTypes,
|
||||
private val urlBuilder: UrlBuilder,
|
||||
private val dispatcher: Dispatcher<Payload>,
|
||||
private val setObjectDetails: UpdateDetail,
|
||||
private val analytics: Analytics,
|
||||
private val getOptions: GetOptions,
|
||||
private val spaceManager: SpaceManager
|
||||
private val spaceManager: SpaceManager,
|
||||
private val subscription: StorelessSubscriptionContainer
|
||||
) : ViewModelProvider.Factory {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel> create(
|
||||
modelClass: Class<T>
|
||||
) = TagStatusViewModel(
|
||||
params = params,
|
||||
viewModelParams = params,
|
||||
relations = relations,
|
||||
values = values,
|
||||
storage = storage,
|
||||
storeOfObjectTypes = storeOfObjectTypes,
|
||||
dispatcher = dispatcher,
|
||||
setObjectDetails = setObjectDetails,
|
||||
urlBuilder = urlBuilder,
|
||||
analytics = analytics,
|
||||
getOptions = getOptions,
|
||||
spaceManager = spaceManager,
|
||||
subscription = subscription
|
||||
) as T
|
||||
}
|
|
@ -939,4 +939,45 @@ object ObjectSearchConstants {
|
|||
value = types
|
||||
)
|
||||
)
|
||||
|
||||
fun filterRelationOptions(relationKey: Key, spaces: List<Id>) : List<DVFilter> = listOf(
|
||||
DVFilter(
|
||||
relation = Relations.RELATION_KEY,
|
||||
condition = DVFilterCondition.EQUAL,
|
||||
value = relationKey
|
||||
),
|
||||
DVFilter(
|
||||
relation = Relations.LAYOUT,
|
||||
condition = DVFilterCondition.EQUAL,
|
||||
value = ObjectType.Layout.RELATION_OPTION.code.toDouble()
|
||||
),
|
||||
DVFilter(
|
||||
relation = Relations.IS_ARCHIVED,
|
||||
condition = DVFilterCondition.NOT_EQUAL,
|
||||
value = true
|
||||
),
|
||||
DVFilter(
|
||||
relation = Relations.IS_DELETED,
|
||||
condition = DVFilterCondition.NOT_EQUAL,
|
||||
value = true
|
||||
),
|
||||
DVFilter(
|
||||
relation = Relations.IS_HIDDEN,
|
||||
condition = DVFilterCondition.NOT_EQUAL,
|
||||
value = true
|
||||
),
|
||||
DVFilter(
|
||||
relation = Relations.SPACE_ID,
|
||||
condition = DVFilterCondition.IN,
|
||||
value = spaces
|
||||
)
|
||||
)
|
||||
|
||||
val keysRelationOptions = listOf(
|
||||
Relations.ID,
|
||||
Relations.SPACE_ID,
|
||||
Relations.NAME,
|
||||
Relations.RELATION_OPTION_COLOR,
|
||||
Relations.RELATION_KEY
|
||||
)
|
||||
}
|
Loading…
Reference in New Issue
Block a user