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:
parent
6138ee309b
commit
5264490f3b
21 changed files with 500 additions and 241 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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 -> {
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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" />
|
||||
|
|
|
@ -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?)
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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?
|
||||
)
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
Loading…
Add table
Add a link
Reference in a new issue