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

DROID-3484 Primitives | Create type screen, part 1 (#2229)

This commit is contained in:
Konstantin Ivanov 2025-04-04 09:30:16 +02:00 committed by GitHub
parent 6138ee309b
commit 5264490f3b
Signed by: github
GPG key ID: B5690EEEBB952194
21 changed files with 500 additions and 241 deletions

View file

@ -8,6 +8,7 @@ import com.anytypeio.anytype.di.feature.CreateObjectModule
import com.anytypeio.anytype.di.feature.DaggerAllContentComponent
import com.anytypeio.anytype.di.feature.DaggerAppPreferencesComponent
import com.anytypeio.anytype.di.feature.DaggerBacklinkOrAddToObjectComponent
import com.anytypeio.anytype.di.feature.DaggerCreateObjectTypeComponent
import com.anytypeio.anytype.di.feature.DaggerDateObjectComponent
import com.anytypeio.anytype.di.feature.DaggerEditTypePropertiesComponent
import com.anytypeio.anytype.di.feature.DaggerLinkToObjectComponent
@ -106,6 +107,7 @@ import com.anytypeio.anytype.feature_chats.presentation.ChatViewModel
import com.anytypeio.anytype.feature_object_type.ui.ObjectTypeVmParams
import com.anytypeio.anytype.feature_chats.presentation.SelectChatReactionViewModel
import com.anytypeio.anytype.feature_date.viewmodel.DateObjectVmParams
import com.anytypeio.anytype.feature_object_type.viewmodel.CreateTypeVmParams
import com.anytypeio.anytype.feature_properties.add.EditTypePropertiesVmParams
import com.anytypeio.anytype.gallery_experience.viewmodel.GalleryInstallationViewModel
import com.anytypeio.anytype.presentation.editor.EditorViewModel
@ -1113,6 +1115,12 @@ class ComponentManager(
.create(params, findComponentDependencies())
}
val createObjectTypeComponent = ComponentWithParams { params: CreateTypeVmParams ->
DaggerCreateObjectTypeComponent
.factory()
.create(params, findComponentDependencies())
}
class Component<T>(private val builder: () -> T) {
private var instance: T? = null

View file

@ -26,11 +26,15 @@ import com.anytypeio.anytype.domain.primitives.SetObjectTypeHeaderRecommendedFie
import com.anytypeio.anytype.domain.primitives.SetObjectTypeRecommendedFields
import com.anytypeio.anytype.domain.resources.StringResourceProvider
import com.anytypeio.anytype.domain.search.SubscriptionEventChannel
import com.anytypeio.anytype.domain.types.CreateObjectType
import com.anytypeio.anytype.feature_object_type.ui.ObjectTypeVmParams
import com.anytypeio.anytype.feature_object_type.viewmodel.CreateObjectTypeVMFactory
import com.anytypeio.anytype.feature_object_type.viewmodel.CreateTypeVmParams
import com.anytypeio.anytype.feature_object_type.viewmodel.ObjectTypeVMFactory
import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate
import com.anytypeio.anytype.presentation.editor.cover.CoverImageHashProvider
import com.anytypeio.anytype.providers.DefaultCoverImageHashProvider
import com.anytypeio.anytype.ui.primitives.CreateTypeFragment
import com.anytypeio.anytype.ui.primitives.ObjectTypeFieldsFragment
import com.anytypeio.anytype.ui.primitives.ObjectTypeFragment
import dagger.Binds
@ -163,4 +167,56 @@ interface ObjectTypeDependencies : ComponentDependencies {
fun fieldParser(): FieldParser
fun provideEventChannel(): EventChannel
fun provideStringResourceProvider(): StringResourceProvider
}
}
//region Create Type Screen
@Component(
dependencies = [CreateObjectTypeDependencies::class],
modules = [
CreateObjectTypeModule::class,
CreateObjectTypeModule.Declarations::class
]
)
@PerScreen
interface CreateObjectTypeComponent {
@Component.Factory
interface Factory {
fun create(
@BindsInstance vmParams: CreateTypeVmParams,
dependencies: CreateObjectTypeDependencies
)
: CreateObjectTypeComponent
}
fun inject(fragment: CreateTypeFragment)
}
@Module
object CreateObjectTypeModule {
@Provides
@PerScreen
@JvmStatic
fun createObjectType(
repo: BlockRepository,
dispatchers: AppCoroutineDispatchers
): CreateObjectType = CreateObjectType(repo, dispatchers)
@Module
interface Declarations {
@PerScreen
@Binds
fun bindViewModelFactory(
factory: CreateObjectTypeVMFactory
): ViewModelProvider.Factory
}
}
interface CreateObjectTypeDependencies : ComponentDependencies {
fun stringResourceProvider(): StringResourceProvider
fun blockRepository(): BlockRepository
fun analyticsHelper(): AnalyticSpaceHelperDelegate
fun analytics(): Analytics
fun dispatchers(): AppCoroutineDispatchers
}
//endregion

View file

@ -8,6 +8,7 @@ import com.anytypeio.anytype.di.feature.AppPreferencesDependencies
import com.anytypeio.anytype.di.feature.BacklinkOrAddToObjectDependencies
import com.anytypeio.anytype.di.feature.CreateBookmarkSubComponent
import com.anytypeio.anytype.di.feature.CreateObjectSubComponent
import com.anytypeio.anytype.di.feature.CreateObjectTypeDependencies
import com.anytypeio.anytype.di.feature.DateObjectDependencies
import com.anytypeio.anytype.di.feature.DebugSettingsSubComponent
import com.anytypeio.anytype.di.feature.EditTypePropertiesDependencies
@ -137,7 +138,8 @@ interface MainComponent :
ChatReactionDependencies,
ParticipantComponentDependencies,
EditTypePropertiesDependencies,
DebugDependencies
DebugDependencies,
CreateObjectTypeDependencies
{
fun inject(app: AndroidApplication)
@ -393,4 +395,9 @@ abstract class ComponentDependenciesModule {
@IntoMap
@ComponentDependenciesKey(EditTypePropertiesDependencies::class)
abstract fun provideEditTypePropertiesDependencies(component: MainComponent): ComponentDependencies
@Binds
@IntoMap
@ComponentDependenciesKey(CreateObjectTypeDependencies::class)
abstract fun provideCreateObjectTypeDependencies(component: MainComponent): ComponentDependencies
}

View file

@ -16,6 +16,7 @@ import com.anytypeio.anytype.ui.date.DateObjectFragment
import com.anytypeio.anytype.ui.editor.EditorFragment
import com.anytypeio.anytype.ui.editor.EditorModalFragment
import com.anytypeio.anytype.ui.multiplayer.ShareSpaceFragment
import com.anytypeio.anytype.ui.primitives.CreateTypeFragment
import com.anytypeio.anytype.ui.primitives.ObjectTypeFieldsFragment
import com.anytypeio.anytype.ui.profile.ParticipantFragment
import com.anytypeio.anytype.ui.primitives.ObjectTypeFragment
@ -294,6 +295,16 @@ class Navigator : AppNavigation {
)
}
override fun openCreateObjectTypeScreen(spaceId: Id) {
val args = CreateTypeFragment.args(
spaceId = spaceId
)
navController?.navigate(
resId = R.id.createObjectTypeScreen,
args = args
)
}
override fun openDateObject(
objectId: Id,
space: Id

View file

@ -186,7 +186,12 @@ class AllContentFragment : BaseComposeFragment(), ObjectTypeSelectionListener {
}
is AllContentViewModel.Command.OpenTypeCreation -> {
//todo: implement new screen logic
runCatching {
navigation().openCreateObjectTypeScreen(spaceId = command.space)
}.onFailure {
toast("Failed to open type creation screen")
Timber.e(it, "Failed to open type creation screen from all content")
}
}
is AllContentViewModel.Command.OpenRelationCreation -> {

View file

@ -0,0 +1,128 @@
package com.anytypeio.anytype.ui.primitives
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import androidx.core.os.bundleOf
import androidx.fragment.app.viewModels
import androidx.fragment.compose.content
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavOptions
import androidx.navigation.fragment.findNavController
import androidx.navigation.navOptions
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.Id
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.feature_object_type.ui.UiIconsPickerState
import com.anytypeio.anytype.feature_object_type.ui.create.SetTypeTitlesAndIconScreen
import com.anytypeio.anytype.feature_object_type.ui.icons.ChangeIconScreen
import com.anytypeio.anytype.feature_object_type.viewmodel.CreateObjectTypeVMFactory
import com.anytypeio.anytype.feature_object_type.viewmodel.CreateObjectTypeViewModel
import com.anytypeio.anytype.feature_object_type.viewmodel.CreateTypeCommand
import com.anytypeio.anytype.feature_object_type.viewmodel.CreateTypeVmParams
import com.anytypeio.anytype.ui.base.navigation
import javax.inject.Inject
import kotlin.getValue
import timber.log.Timber
class CreateTypeFragment: BaseBottomSheetComposeFragment() {
@Inject
lateinit var factory: CreateObjectTypeVMFactory
private val vm by viewModels<CreateObjectTypeViewModel> { factory }
private val space get() = argString(ARG_SPACE_ID)
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
) = content {
MaterialTheme {
SetTypeTitlesAndIconScreen(
uiState = vm.uiState.collectAsStateWithLifecycle().value,
onDismiss = vm::onDismiss,
onTitleChanged = vm::onTypeTitleChanged,
onPluralChanged = vm::onTypePluralChanged,
onIconClicked = vm::onIconClicked,
onButtonClicked = vm::onButtonClicked
)
IconsPickerScreen()
LaunchedEffect(Unit) {
vm.commands.collect{ command ->
when (command) {
CreateTypeCommand.Dismiss -> {
findNavController().popBackStack()
}
is CreateTypeCommand.NavigateToObjectType -> {
runCatching {
findNavController().navigate(
resId = R.id.objectTypeScreen,
args = ObjectTypeFragment.args(
objectId = command.id,
space = command.space
),
navOptions = navOptions {
popUpTo(R.id.createObjectTypeScreen) {
inclusive = true
}
}
)
}.onFailure {
Timber.e(it, "Failed to open object type object from create type Screen")
}
}
}
}
}
}
}
@Composable
private fun IconsPickerScreen() {
val uiState = vm.uiIconsPickerScreen.collectAsStateWithLifecycle().value
if (uiState is UiIconsPickerState.Visible) {
ChangeIconScreen(
modifier = Modifier.fillMaxWidth(),
onDismissRequest = vm::onDismissIconPicker,
onIconClicked = { name, color ->
vm.onNewIconPicked(
iconName = name,
color = color
)
},
onRemoveIconClicked = vm::onRemoveIcon
)
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
override fun injectDependencies() {
val vmParams = CreateTypeVmParams(spaceId = space)
componentManager().createObjectTypeComponent.get(vmParams).inject(this)
}
override fun releaseDependencies() {
componentManager().createObjectTypeComponent.release()
}
companion object {
const val ARG_SPACE_ID = "arg.create_type.space_id"
fun args(spaceId: Id) = bundleOf(
ARG_SPACE_ID to spaceId
)
}
}

View file

@ -691,6 +691,10 @@
android:id="@+id/multiplayerFeatureDialog"
android:name="com.anytypeio.anytype.ui.multiplayer.IntroduceSpaceSharingFragment"/>
<dialog
android:id="@+id/createObjectTypeScreen"
android:name="com.anytypeio.anytype.ui.primitives.CreateTypeFragment"/>
<action
android:id="@+id/actionOpenSpaceFromVault"
app:destination="@id/homeScreen" />

View file

@ -480,6 +480,12 @@ sealed class Command {
val shouldApplyEmptyUseCase: Boolean
)
data class CreateObjectType(
val details: Struct,
val spaceId: Id,
val internalFlags: List<InternalFlags>
)
data class AddObjectToSpace(val space: Id, val objectId: Id)
data class ApplyTemplate(val objectId: Id, val template: Id?)

View file

@ -609,15 +609,9 @@ class BlockDataRepository(
prefilled = prefilled
)
override suspend fun createType(
space: Id,
name: String,
emojiUnicode: String?,
): Struct? = remote.createType(
space = space,
name = name,
emojiUnicode = emojiUnicode
)
override suspend fun createType(command: Command.CreateObjectType): String {
return remote.createType(command)
}
override suspend fun createRelationOption(
space: Id,

View file

@ -260,10 +260,8 @@ interface BlockRemote {
): ObjectWrapper.Relation
suspend fun createType(
space: Id,
name: String,
emojiUnicode: String?
): Struct?
command: Command.CreateObjectType
): String
suspend fun createRelationOption(
space: Id,

View file

@ -314,10 +314,8 @@ interface BlockRepository {
) : ObjectWrapper.Relation
suspend fun createType(
space: Id,
name: String,
emojiUnicode: String?
): Struct?
command: Command.CreateObjectType
): String
suspend fun createRelationOption(
space: Id,

View file

@ -1,8 +1,8 @@
package com.anytypeio.anytype.domain.types
import com.anytypeio.anytype.core_models.Command
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.ObjectWrapper
import com.anytypeio.anytype.core_models.ext.mapToObjectWrapperType
import com.anytypeio.anytype.core_models.Relations
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
import com.anytypeio.anytype.domain.base.ResultInteractor
import com.anytypeio.anytype.domain.block.repo.BlockRepository
@ -10,19 +10,26 @@ import com.anytypeio.anytype.domain.block.repo.BlockRepository
class CreateObjectType(
private val repo: BlockRepository,
dispatchers: AppCoroutineDispatchers
) : ResultInteractor<CreateObjectType.Params, ObjectWrapper.Type?>(dispatchers.io) {
override suspend fun doWork(params: Params): ObjectWrapper.Type? {
val result = repo.createType(
space = params.space,
name = params.name,
emojiUnicode = params.emojiUnicode
) : ResultInteractor<CreateObjectType.Params, String>(dispatchers.io) {
override suspend fun doWork(params: Params): String {
val command = Command.CreateObjectType(
details = mapOf(
Relations.NAME to params.name,
Relations.ICON_NAME to params.iconName,
Relations.PLURAL_NAME to params.pluralName,
Relations.ICON_OPTION to params.iconColor
),
spaceId = params.space,
internalFlags = listOf()
)
return result?.mapToObjectWrapperType()
return repo.createType(command)
}
class Params(
val space: Id,
val name: String,
val emojiUnicode: String? = null
val iconName: String,
val pluralName: String,
val iconColor: Double?
)
}

View file

@ -4,12 +4,9 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.anytypeio.anytype.analytics.base.Analytics
import com.anytypeio.anytype.analytics.base.EventsDictionary
import com.anytypeio.anytype.analytics.base.EventsDictionary.libraryCreateType
import com.anytypeio.anytype.analytics.base.EventsDictionary.libraryScreenRelation
import com.anytypeio.anytype.analytics.base.EventsDictionary.libraryScreenType
import com.anytypeio.anytype.analytics.base.EventsPropertiesKey
import com.anytypeio.anytype.analytics.base.sendEvent
import com.anytypeio.anytype.analytics.props.Props
import com.anytypeio.anytype.core_models.DVSortType
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.ObjectWrapper
@ -838,39 +835,7 @@ class AllContentViewModel(
when (item) {
UiContentItem.NewType -> {
viewModelScope.launch {
createObjectType.execute(
CreateObjectType.Params(
space = vmParams.spaceId.id,
name = ""
)
).fold(
onSuccess = { newType ->
val spaceParams = provideParams(vmParams.spaceId.id)
viewModelScope.sendEvent(
analytics = analytics,
eventName = libraryCreateType,
props = Props(
mapOf(
EventsPropertiesKey.permissions to spaceParams.permission,
EventsPropertiesKey.spaceType to spaceParams.spaceType
)
)
)
if (newType == null) {
Timber.e("Error while creating type")
return@fold
}
commands.emit(
NavigateToObjectType(
id = newType.id,
space = vmParams.spaceId.id
)
)
},
onFailure = {
Timber.e(it, "Error while creating type")
}
)
commands.emit(OpenTypeCreation(space = vmParams.spaceId.id))
}
}
is UiContentItem.Type -> {
@ -1086,7 +1051,7 @@ class AllContentViewModel(
data class ObjectArchived(val name: String) : SendToast()
}
data class OpenTypeEditing(val item: UiContentItem.Type) : Command()
data object OpenTypeCreation: Command()
data class OpenTypeCreation(val space: Id): Command()
data class OpenShareScreen(val space: SpaceId): Command()
data class OpenRelationEditing(
val typeName: String,

View file

@ -0,0 +1,20 @@
package com.anytypeio.anytype.feature_object_type.ui.create
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SetTypeTitlesAndIconScreen(
uiState: UiTypeSetupTitleAndIconState,
modifier: Modifier = Modifier,
onTitleChanged: (String) -> Unit,
onPluralChanged: (String) -> Unit,
onIconClicked: () -> Unit,
onDismiss: () -> Unit,
onButtonClicked: () -> Unit
) {
//next PR
}

View file

@ -0,0 +1,20 @@
package com.anytypeio.anytype.feature_object_type.ui.create
import com.anytypeio.anytype.presentation.objects.ObjectIcon
sealed class UiTypeSetupTitleAndIconState {
abstract val icon: ObjectIcon.TypeIcon.Default
data class CreateNewType(
override val icon: ObjectIcon.TypeIcon.Default,
val initialTitle: String = "",
val initialPlural: String = ""
) : UiTypeSetupTitleAndIconState()
data class EditType(
override val icon: ObjectIcon.TypeIcon.Default,
val initialTitle: String = "",
val initialPlural: String = ""
) : UiTypeSetupTitleAndIconState()
}

View file

@ -0,0 +1,190 @@
package com.anytypeio.anytype.feature_object_type.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.anytypeio.anytype.analytics.base.Analytics
import com.anytypeio.anytype.analytics.base.EventsDictionary.libraryCreateType
import com.anytypeio.anytype.analytics.base.EventsPropertiesKey
import com.anytypeio.anytype.analytics.base.sendEvent
import com.anytypeio.anytype.analytics.props.Props
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.domain.base.fold
import com.anytypeio.anytype.domain.types.CreateObjectType
import com.anytypeio.anytype.feature_object_type.ui.UiIconsPickerState
import com.anytypeio.anytype.feature_object_type.ui.create.UiTypeSetupTitleAndIconState
import com.anytypeio.anytype.feature_object_type.viewmodel.CreateTypeCommand.NavigateToObjectType
import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate
import com.anytypeio.anytype.presentation.objects.ObjectIcon
import com.anytypeio.anytype.presentation.objects.custom_icon.CustomIconColor
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import timber.log.Timber
class CreateObjectTypeViewModel(
private val vmParams: CreateTypeVmParams,
private val createObjectType: CreateObjectType,
private val analytics: Analytics,
private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate
) : ViewModel(), AnalyticSpaceHelperDelegate by analyticSpaceHelperDelegate {
private val INIT =
when (vmParams.mode) {
MODE.CREATE -> UiTypeSetupTitleAndIconState.CreateNewType(
icon = ObjectIcon.TypeIcon.Default.DEFAULT
)
MODE.EDIT -> UiTypeSetupTitleAndIconState.EditType(
icon = ObjectIcon.TypeIcon.Default.DEFAULT
)
}
private val _uiState = MutableStateFlow<UiTypeSetupTitleAndIconState>(INIT)
val uiState = _uiState.asStateFlow()
//icons picker screen
val uiIconsPickerScreen = MutableStateFlow<UiIconsPickerState>(UiIconsPickerState.Hidden)
val commands = MutableSharedFlow<CreateTypeCommand>(replay = 0)
private val typeTitle = MutableStateFlow("")
private val typePlural = MutableStateFlow("")
init {
Timber.d("CreateObjectTypeViewModel initialized")
}
fun onTypeTitleChanged(title: String) {
typeTitle.value = title
}
fun onTypePluralChanged(plural: String) {
typePlural.value = plural
}
fun onDismiss() {
viewModelScope.launch {
commands.emit(CreateTypeCommand.Dismiss)
}
}
fun onRemoveIcon() {
val defaultIcon = ObjectIcon.TypeIcon.Default.DEFAULT
_uiState.value = when (val currentState = _uiState.value) {
is UiTypeSetupTitleAndIconState.CreateNewType -> currentState.copy(icon = defaultIcon)
is UiTypeSetupTitleAndIconState.EditType -> currentState.copy(icon = defaultIcon)
}
uiIconsPickerScreen.value = UiIconsPickerState.Hidden
}
fun onNewIconPicked(iconName: String, color: CustomIconColor?) {
val newIcon = ObjectIcon.TypeIcon.Default(
rawValue = iconName,
color = color ?: CustomIconColor.DEFAULT
)
_uiState.value = when (val currentState = _uiState.value) {
is UiTypeSetupTitleAndIconState.CreateNewType -> currentState.copy(icon = newIcon)
is UiTypeSetupTitleAndIconState.EditType -> currentState.copy(icon = newIcon)
}
uiIconsPickerScreen.value = UiIconsPickerState.Hidden
}
fun onIconClicked() {
uiIconsPickerScreen.value = UiIconsPickerState.Visible
}
fun onDismissIconPicker() {
uiIconsPickerScreen.value = UiIconsPickerState.Hidden
}
fun onButtonClicked() {
when (vmParams.mode) {
MODE.CREATE -> createNewType()
MODE.EDIT -> updateType()
}
}
private fun createNewType() {
val icon = _uiState.value.icon
val name = typeTitle.value
val plural = typePlural.value
Timber.d("Creating new type with name: $name, plural: $plural, icon: $icon")
viewModelScope.launch {
createObjectType.execute(
CreateObjectType.Params(
space = vmParams.spaceId,
name = name,
pluralName = plural,
iconName = icon.rawValue,
iconColor = icon.color.iconOption.toDouble()
)
).fold(
onSuccess = { objTypeId ->
val spaceParams = provideParams(vmParams.spaceId)
viewModelScope.sendEvent(
analytics = analytics,
eventName = libraryCreateType,
props = Props(
mapOf(
EventsPropertiesKey.permissions to spaceParams.permission,
EventsPropertiesKey.spaceType to spaceParams.spaceType
)
)
)
commands.emit(
NavigateToObjectType(
id = objTypeId,
space = vmParams.spaceId
)
)
},
onFailure = {
Timber.e(it, "Error while creating type")
}
)
}
}
private fun updateType() {
val icon = _uiState.value.icon
val name = typeTitle.value
val plural = typePlural.value
viewModelScope.launch {
}
}
}
data class CreateTypeVmParams(
val spaceId: Id,
val mode: MODE = MODE.CREATE
)
enum class MODE {
CREATE,
EDIT
}
sealed class CreateTypeCommand {
data object Dismiss : CreateTypeCommand()
data class NavigateToObjectType(val id: Id, val space: Id) : CreateTypeCommand()
}
class CreateObjectTypeVMFactory @Inject constructor(
private val vmParams: CreateTypeVmParams,
private val createObjectType: CreateObjectType,
private val analytics: Analytics,
private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T = CreateObjectTypeViewModel(
vmParams = vmParams,
createObjectType = createObjectType,
analytics = analytics,
analyticSpaceHelperDelegate = analyticSpaceHelperDelegate
) as T
}

View file

@ -2008,6 +2008,11 @@ Please provide specific details of your needs here.</string>
<string name="object_type_icon_change_title_search_hint">Search..."</string>
<string name="space_invite_link_copied">Space invite link copied!</string>
<string name="object_type_create_new_title">Create new type</string>
<string name="object_type_rename_title">Rename type</string>
<string name="object_type_create_plural_title">Type list name</string>
<string name="object_type_create_title_hint">Puzzle</string>
<string name="object_type_create_plural_title_hint">Puzzles</string>
<string name="object_conflict_screen_title">Resolve layout conflict</string>
<string name="object_conflict_screen_description">This layout differs from the type\'s default. Reset to match?</string>
<string name="object_conflict_screen_action_button">Reset to default</string>

View file

@ -688,14 +688,8 @@ class BlockMiddleware(
)
override suspend fun createType(
space: Id,
name: String,
emojiUnicode: String?
): Struct? = middleware.objectCreateObjectType(
space = space,
name = name,
emojiUnicode = emojiUnicode
)
command: Command.CreateObjectType
): String = middleware.objectCreateObjectType(command)
override suspend fun createRelationOption(
space: Id,

View file

@ -986,23 +986,17 @@ class Middleware @Inject constructor(
@Throws(Exception::class)
fun objectCreateObjectType(
space: Id,
name: String,
emojiUnicode: String?
): Struct {
command: Command.CreateObjectType
): String {
val request = Rpc.Object.CreateObjectType.Request(
details = buildMap {
put(Relations.NAME, name)
emojiUnicode?.let {
put(Relations.ICON_EMOJI, it)
}
},
spaceId = space
details = command.details,
spaceId = command.spaceId,
internalFlags = command.internalFlags.toMiddlewareModel()
)
logRequestIfDebug(request)
val (response, time) = measureTimedValue { service.objectCreateObjectType(request) }
logResponseIfDebug(response)
return response.details ?: throw IllegalStateException("Null object type struct")
logResponseIfDebug(response, time)
return response.objectId
}
@Throws(Exception::class)

View file

@ -72,6 +72,8 @@ interface AppNavigation {
fun openAllContent(space: Id)
fun openRelationCreationScreen(id: Id, name: String, space: Id)
fun openCreateObjectTypeScreen(spaceId: Id)
sealed class Command {
data object Exit : Command()

View file

@ -1,153 +0,0 @@
package com.anytypeio.anytype.presentation.types
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.anytypeio.anytype.analytics.base.Analytics
import com.anytypeio.anytype.analytics.base.EventsDictionary.libraryCreateType
import com.anytypeio.anytype.analytics.base.EventsPropertiesKey
import com.anytypeio.anytype.analytics.base.sendEvent
import com.anytypeio.anytype.analytics.props.Props
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.ObjectType
import com.anytypeio.anytype.core_models.ObjectWrapper
import com.anytypeio.anytype.core_models.Relations
import com.anytypeio.anytype.core_utils.ext.orNull
import com.anytypeio.anytype.domain.base.fold
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.domain.types.CreateObjectType
import com.anytypeio.anytype.domain.workspace.SpaceManager
import com.anytypeio.anytype.emojifier.data.EmojiProvider
import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate
import com.anytypeio.anytype.presentation.mapper.objectIcon
import com.anytypeio.anytype.presentation.navigation.NavigationViewModel
import com.anytypeio.anytype.presentation.objects.ObjectIcon
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import timber.log.Timber
@Deprecated("Before Primitives, to delete")
class CreateObjectTypeViewModel(
private val createObjectType: CreateObjectType,
private val urlBuilder: UrlBuilder,
private val emojiProvider: EmojiProvider,
private val analytics: Analytics,
private val spaceManager: SpaceManager,
private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate
) : NavigationViewModel<CreateObjectTypeViewModel.Navigation>(),
AnalyticSpaceHelperDelegate by analyticSpaceHelperDelegate {
private val unicodeIconFlow = MutableStateFlow("")
val uiState: StateFlow<TypeCreationIconState> = unicodeIconFlow.map { icon ->
val objectIcon = icon.orNull()?.let {
ObjectWrapper.Basic(
mapOf(
Relations.ICON_EMOJI to icon,
Relations.LAYOUT to ObjectType.Layout.OBJECT_TYPE.code.toDouble()
)
).objectIcon(urlBuilder)
}
TypeCreationIconState(
emojiUnicode = icon,
objectIcon = objectIcon ?: ObjectIcon.None
)
}.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(STOP_SUBSCRIPTION_TIMEOUT),
TypeCreationIconState()
)
fun createType(name: String) {
if (name.isEmpty()) {
sendToast("Name should not be empty")
return
}
viewModelScope.launch {
createObjectType.execute(
CreateObjectType.Params(
space = spaceManager.get(),
name = name,
emojiUnicode = uiState.value.emojiUnicode.orNull()
)
).fold(
onSuccess = {
val spaceParams = provideParams(spaceManager.get())
viewModelScope.sendEvent(
analytics = analytics,
eventName = libraryCreateType,
props = Props(
mapOf(
EventsPropertiesKey.permissions to spaceParams.permission,
EventsPropertiesKey.spaceType to spaceParams.spaceType
)
)
)
navigate(Navigation.BackWithCreatedType)
},
onFailure = {
Timber.e(it, "Error while creating type $name")
sendToast("Something went wrong. Please, try again later.")
}
)
}
}
fun openEmojiPicker() {
navigate(Navigation.SelectEmoji(unicodeIconFlow.value.isNotEmpty()))
}
fun setEmoji(emojiUnicode: String) {
unicodeIconFlow.value = emojiUnicode
}
fun removeEmoji() {
unicodeIconFlow.value = ""
}
fun onPreparedString(preparedName: Id) {
if (preparedName.isNotEmpty()) {
// todo: not random but random
unicodeIconFlow.value = emojiProvider.emojis.random().random()
}
}
sealed class Navigation {
object BackWithCreatedType: Navigation()
class SelectEmoji(val showRemove: Boolean) : Navigation()
}
class Factory @Inject constructor(
private val createObjectType: CreateObjectType,
private val urlBuilder: UrlBuilder,
private val emojiProvider: EmojiProvider,
private val analytics: Analytics,
private val spaceManager: SpaceManager,
private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return CreateObjectTypeViewModel(
createObjectType = createObjectType,
urlBuilder = urlBuilder,
emojiProvider = emojiProvider,
analytics = analytics,
spaceManager = spaceManager,
analyticSpaceHelperDelegate = analyticSpaceHelperDelegate
) as T
}
}
}
data class TypeCreationIconState(
val emojiUnicode: String? = null,
val objectIcon: ObjectIcon = ObjectIcon.None
)
private const val STOP_SUBSCRIPTION_TIMEOUT = 1_000L