mirror of
https://github.com/anyproto/anytype-kotlin.git
synced 2025-06-08 05:47:05 +09:00
DROID-1431 Editor | Enhancement | Blank template (#242)
This commit is contained in:
parent
9c3801ee13
commit
315f95624d
28 changed files with 661 additions and 131 deletions
|
@ -92,6 +92,7 @@ import com.anytypeio.anytype.di.feature.settings.DaggerFilesStorageComponent
|
|||
import com.anytypeio.anytype.di.feature.settings.LogoutWarningModule
|
||||
import com.anytypeio.anytype.di.feature.settings.MainSettingsModule
|
||||
import com.anytypeio.anytype.di.feature.settings.ProfileModule
|
||||
import com.anytypeio.anytype.di.feature.templates.DaggerTemplateBlankComponent
|
||||
import com.anytypeio.anytype.di.feature.types.DaggerTypeCreationComponent
|
||||
import com.anytypeio.anytype.di.feature.types.DaggerTypeEditComponent
|
||||
import com.anytypeio.anytype.di.feature.types.DaggerTypeIconPickComponent
|
||||
|
@ -815,6 +816,10 @@ class ComponentManager(
|
|||
.build()
|
||||
}
|
||||
|
||||
val templateBlankComponent = Component {
|
||||
DaggerTemplateBlankComponent.factory().create(findComponentDependencies())
|
||||
}
|
||||
|
||||
// Settings
|
||||
|
||||
val aboutAppComponent = Component {
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
package com.anytypeio.anytype.di.feature.templates
|
||||
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.anytypeio.anytype.di.common.ComponentDependencies
|
||||
import com.anytypeio.anytype.domain.misc.UrlBuilder
|
||||
import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes
|
||||
import com.anytypeio.anytype.domain.objects.StoreOfRelations
|
||||
import com.anytypeio.anytype.presentation.editor.cover.CoverImageHashProvider
|
||||
import com.anytypeio.anytype.presentation.editor.render.DefaultBlockViewRenderer
|
||||
import com.anytypeio.anytype.presentation.editor.toggle.ToggleStateHolder
|
||||
import com.anytypeio.anytype.presentation.templates.TemplateBlankViewModelFactory
|
||||
import com.anytypeio.anytype.providers.DefaultCoverImageHashProvider
|
||||
import com.anytypeio.anytype.ui.templates.TemplateBlankFragment
|
||||
import dagger.Binds
|
||||
import dagger.Component
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import javax.inject.Scope
|
||||
|
||||
@Component(
|
||||
modules = [TemplateBlankModule::class],
|
||||
dependencies = [TemplateBlankDependencies::class]
|
||||
)
|
||||
@TemplateBlankScope
|
||||
interface TemplateBlankComponent {
|
||||
|
||||
@Component.Factory
|
||||
interface Factory {
|
||||
fun create(dependencies: TemplateBlankDependencies): TemplateBlankComponent
|
||||
}
|
||||
|
||||
fun inject(fragment: TemplateBlankFragment)
|
||||
}
|
||||
|
||||
@Module
|
||||
object TemplateBlankModule {
|
||||
|
||||
@JvmStatic
|
||||
@TemplateBlankScope
|
||||
@Provides
|
||||
fun provideToggleHolder(): ToggleStateHolder = ToggleStateHolder.Default()
|
||||
|
||||
@JvmStatic
|
||||
@TemplateBlankScope
|
||||
@Provides
|
||||
fun provideCoverImageHashProvider(): CoverImageHashProvider = DefaultCoverImageHashProvider()
|
||||
|
||||
@JvmStatic
|
||||
@TemplateBlankScope
|
||||
@Provides
|
||||
fun provideViewModelFactory(
|
||||
renderer: DefaultBlockViewRenderer
|
||||
): ViewModelProvider.Factory = TemplateBlankViewModelFactory(
|
||||
renderer = renderer
|
||||
)
|
||||
|
||||
@Module
|
||||
interface Declarations {
|
||||
|
||||
@TemplateBlankScope
|
||||
@Binds
|
||||
fun bindRenderer(
|
||||
defaultRenderer: DefaultBlockViewRenderer
|
||||
): DefaultBlockViewRenderer
|
||||
}
|
||||
}
|
||||
|
||||
interface TemplateBlankDependencies : ComponentDependencies {
|
||||
fun urlBuilder(): UrlBuilder
|
||||
fun storeOfRelations(): StoreOfRelations
|
||||
fun storeOfObjectTypes(): StoreOfObjectTypes
|
||||
}
|
||||
|
||||
@Scope
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class TemplateBlankScope
|
|
@ -5,13 +5,13 @@ import com.anytypeio.anytype.core_utils.di.scope.PerScreen
|
|||
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
|
||||
import com.anytypeio.anytype.domain.block.repo.BlockRepository
|
||||
import com.anytypeio.anytype.domain.templates.ApplyTemplate
|
||||
import com.anytypeio.anytype.domain.templates.GetTemplates
|
||||
import com.anytypeio.anytype.presentation.templates.TemplateSelectViewModel
|
||||
import com.anytypeio.anytype.ui.templates.TemplateSelectFragment
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.Subcomponent
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
||||
@Subcomponent(
|
||||
modules = [TemplateSelectModule::class, TemplateSelectModule.Bindings::class]
|
||||
|
@ -38,6 +38,15 @@ object TemplateSelectModule {
|
|||
dispatchers = dispatchers
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@PerScreen
|
||||
fun getTemplates(repo: BlockRepository, dispatchers: AppCoroutineDispatchers): GetTemplates =
|
||||
GetTemplates(
|
||||
repo = repo,
|
||||
dispatchers = dispatchers
|
||||
)
|
||||
|
||||
@Module
|
||||
interface Bindings {
|
||||
@PerScreen
|
||||
|
|
|
@ -37,6 +37,7 @@ import com.anytypeio.anytype.di.feature.settings.FilesStorageDependencies
|
|||
import com.anytypeio.anytype.di.feature.settings.LogoutWarningSubComponent
|
||||
import com.anytypeio.anytype.di.feature.settings.MainSettingsSubComponent
|
||||
import com.anytypeio.anytype.di.feature.settings.ProfileSubComponent
|
||||
import com.anytypeio.anytype.di.feature.templates.TemplateBlankDependencies
|
||||
import com.anytypeio.anytype.di.feature.templates.TemplateSelectSubComponent
|
||||
import com.anytypeio.anytype.di.feature.templates.TemplateSubComponent
|
||||
import com.anytypeio.anytype.di.feature.types.TypeCreationDependencies
|
||||
|
@ -93,7 +94,8 @@ interface MainComponent :
|
|||
OnboardingSoulCreationDependencies,
|
||||
OnboardingLoginSetupDependencies,
|
||||
AboutAppDependencies,
|
||||
OnboardingSoulCreationAnimDependencies {
|
||||
OnboardingSoulCreationAnimDependencies,
|
||||
TemplateBlankDependencies {
|
||||
|
||||
fun inject(app: AndroidApplication)
|
||||
|
||||
|
@ -245,5 +247,8 @@ private abstract class ComponentDependenciesModule private constructor() {
|
|||
@ComponentDependenciesKey(OnboardingSoulCreationAnimDependencies::class)
|
||||
abstract fun provideOnboardingSoulCreationAnimDependencies(component: MainComponent): ComponentDependencies
|
||||
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ComponentDependenciesKey(TemplateBlankDependencies::class)
|
||||
abstract fun provideTemplateBlankDependencies(component: MainComponent): ComponentDependencies
|
||||
}
|
|
@ -268,13 +268,11 @@ class Navigator : AppNavigation {
|
|||
override fun openTemplates(
|
||||
ctx: Id,
|
||||
type: String,
|
||||
templates: List<Id>
|
||||
) {
|
||||
navController?.navigate(
|
||||
R.id.templateSelectScreen,
|
||||
bundleOf(
|
||||
resId = R.id.templateSelectScreen,
|
||||
args = bundleOf(
|
||||
TemplateSelectFragment.CTX_KEY to ctx,
|
||||
TemplateSelectFragment.TEMPLATE_IDS_KEY to templates,
|
||||
TemplateSelectFragment.OBJECT_TYPE_KEY to type
|
||||
)
|
||||
)
|
||||
|
|
|
@ -69,8 +69,7 @@ class NavigationRouter(
|
|||
|
||||
is AppNavigation.Command.OpenTemplates -> navigation.openTemplates(
|
||||
ctx = command.ctx,
|
||||
type = command.type,
|
||||
templates = command.templates
|
||||
type = command.type
|
||||
)
|
||||
|
||||
is AppNavigation.Command.OpenLibrary -> navigation.openLibrary()
|
||||
|
|
|
@ -455,7 +455,7 @@ open class EditorFragment : NavigationFragment<FragmentEditorBinding>(R.layout.f
|
|||
val behavior = BottomSheetBehavior.from(binding.typeHasTemplateToolbar)
|
||||
when (state) {
|
||||
is SelectTemplateViewState.Active -> {
|
||||
binding.typeHasTemplateToolbar.count = state.count
|
||||
binding.typeHasTemplateToolbar.setText(state.count, state.typeName)
|
||||
behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
behavior.addBottomSheetCallback(onHideBottomSheetCallback)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,143 @@
|
|||
package com.anytypeio.anytype.ui.templates
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.anytypeio.anytype.R
|
||||
import com.anytypeio.anytype.core_models.Url
|
||||
import com.anytypeio.anytype.core_ui.features.editor.BlockAdapter
|
||||
import com.anytypeio.anytype.core_ui.features.editor.DragAndDropAdapterDelegate
|
||||
import com.anytypeio.anytype.core_ui.tools.ClipboardInterceptor
|
||||
import com.anytypeio.anytype.core_utils.ext.arg
|
||||
import com.anytypeio.anytype.core_utils.ext.argInt
|
||||
import com.anytypeio.anytype.core_utils.ext.subscribe
|
||||
import com.anytypeio.anytype.core_utils.ui.BaseFragment
|
||||
import com.anytypeio.anytype.databinding.FragmentTemplateBinding
|
||||
import com.anytypeio.anytype.di.common.componentManager
|
||||
import com.anytypeio.anytype.presentation.templates.TemplateBlankViewModel
|
||||
import com.anytypeio.anytype.presentation.templates.TemplateBlankViewModelFactory
|
||||
import java.util.LinkedList
|
||||
import javax.inject.Inject
|
||||
import timber.log.Timber
|
||||
|
||||
class TemplateBlankFragment : BaseFragment<FragmentTemplateBinding>(R.layout.fragment_template),
|
||||
ClipboardInterceptor {
|
||||
|
||||
@Inject
|
||||
lateinit var factory: TemplateBlankViewModelFactory
|
||||
|
||||
val vm by viewModels<TemplateBlankViewModel> { factory }
|
||||
|
||||
private val typeId: String get() = arg(OBJECT_TYPE_ID_KEY)
|
||||
private val typeName: String get() = arg(OBJECT_TYPE_NAME_KEY)
|
||||
private val layout: Int get() = argInt(OBJECT_LAYOUT_KEY)
|
||||
|
||||
private val templateAdapter by lazy {
|
||||
BlockAdapter(
|
||||
restore = LinkedList(),
|
||||
initialBlock = mutableListOf(),
|
||||
onTextChanged = { _, _ -> },
|
||||
onTextBlockTextChanged = {},
|
||||
onDescriptionChanged = { },
|
||||
onTitleBlockTextChanged = { _, _ -> },
|
||||
onSelectionChanged = { _, _ -> },
|
||||
onCheckboxClicked = {},
|
||||
onTitleCheckboxClicked = {},
|
||||
onFocusChanged = { _, _ -> },
|
||||
onSplitLineEnterClicked = { _, _, _ -> },
|
||||
onSplitDescription = { _, _, _ -> },
|
||||
onEmptyBlockBackspaceClicked = {},
|
||||
onNonEmptyBlockBackspaceClicked = { _, _ -> },
|
||||
onTextInputClicked = { },
|
||||
onPageIconClicked = {},
|
||||
onCoverClicked = { },
|
||||
onTogglePlaceholderClicked = { },
|
||||
onToggleClicked = {},
|
||||
onTitleTextInputClicked = {},
|
||||
onClickListener = {},
|
||||
clipboardInterceptor = object : ClipboardInterceptor {
|
||||
override fun onClipboardAction(action: ClipboardInterceptor.Action) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun onBookmarkPasted(url: Url) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
},
|
||||
onMentionEvent = {},
|
||||
onSlashEvent = {},
|
||||
onBackPressedCallback = { false },
|
||||
onKeyPressedEvent = {},
|
||||
onDragAndDropTrigger = { _, _ -> false },
|
||||
onDragListener = { _, _ -> false },
|
||||
lifecycle = lifecycle,
|
||||
dragAndDropSelector = DragAndDropAdapterDelegate(),
|
||||
onCellSelectionChanged = { _, _ -> }
|
||||
)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
binding.templateRecycler.apply {
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
adapter = templateAdapter
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
with(lifecycleScope) {
|
||||
jobs += subscribe(vm.state) {
|
||||
Timber.d("TemplateBlankFragment: $it")
|
||||
templateAdapter.updateWithDiffUtil(it)
|
||||
}
|
||||
}
|
||||
super.onStart()
|
||||
vm.onStart(typeId, typeName, layout)
|
||||
}
|
||||
|
||||
override fun injectDependencies() {
|
||||
componentManager().templateBlankComponent.get().inject(this)
|
||||
}
|
||||
|
||||
override fun releaseDependencies() {
|
||||
componentManager().templateBlankComponent.release()
|
||||
}
|
||||
|
||||
override fun inflateBinding(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?
|
||||
): FragmentTemplateBinding = FragmentTemplateBinding.inflate(
|
||||
inflater, container, false
|
||||
)
|
||||
|
||||
override fun onClipboardAction(action: ClipboardInterceptor.Action) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
override fun onBookmarkPasted(url: Url) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun new(
|
||||
typeId: String,
|
||||
typeName: String,
|
||||
layout: Int
|
||||
) = TemplateBlankFragment().apply {
|
||||
arguments = bundleOf(
|
||||
OBJECT_TYPE_ID_KEY to typeId,
|
||||
OBJECT_TYPE_NAME_KEY to typeName,
|
||||
OBJECT_LAYOUT_KEY to layout
|
||||
)
|
||||
}
|
||||
|
||||
const val OBJECT_TYPE_ID_KEY = "arg.template.object_type_id"
|
||||
const val OBJECT_TYPE_NAME_KEY = "arg.template.object_type"
|
||||
const val OBJECT_LAYOUT_KEY = "arg.template.object_layout"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package com.anytypeio.anytype.ui.templates
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
import com.anytypeio.anytype.presentation.templates.TemplateSelectViewModel
|
||||
|
||||
class TemplateSelectAdapter(
|
||||
private var items: List<TemplateSelectViewModel.TemplateView>,
|
||||
fragment: Fragment
|
||||
) : FragmentStateAdapter(fragment) {
|
||||
|
||||
fun update(newItems: List<TemplateSelectViewModel.TemplateView>) {
|
||||
items = newItems
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = items.size
|
||||
|
||||
override fun createFragment(position: Int): Fragment {
|
||||
return when (val templateView = items[position]) {
|
||||
is TemplateSelectViewModel.TemplateView.Blank -> TemplateBlankFragment.new(
|
||||
typeId = templateView.typeId,
|
||||
typeName = templateView.typeName,
|
||||
layout = templateView.layout
|
||||
)
|
||||
is TemplateSelectViewModel.TemplateView.Template -> TemplateFragment.new(
|
||||
templateView.id
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,58 +4,92 @@ import android.os.Bundle
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
import com.anytypeio.anytype.R
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_ui.reactive.clicks
|
||||
import com.anytypeio.anytype.core_utils.ext.arg
|
||||
import com.anytypeio.anytype.core_utils.ext.argBoolean
|
||||
import com.anytypeio.anytype.core_utils.ext.subscribe
|
||||
import com.anytypeio.anytype.core_utils.ui.BaseFragment
|
||||
import com.anytypeio.anytype.databinding.FragmentTemplateSelectBinding
|
||||
import com.anytypeio.anytype.di.common.componentManager
|
||||
import com.anytypeio.anytype.presentation.templates.TemplateSelectViewModel
|
||||
import com.anytypeio.anytype.ui.base.NavigationFragment
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
class TemplateSelectFragment :
|
||||
NavigationFragment<FragmentTemplateSelectBinding>(R.layout.fragment_template_select) {
|
||||
BaseFragment<FragmentTemplateSelectBinding>(R.layout.fragment_template_select) {
|
||||
|
||||
private val vm by viewModels<TemplateSelectViewModel> { factory }
|
||||
|
||||
@Inject
|
||||
lateinit var factory: TemplateSelectViewModel.Factory
|
||||
|
||||
private val ids: List<Id> get() = arg(TEMPLATE_IDS_KEY)
|
||||
private val type: Id get() = arg(OBJECT_TYPE_KEY)
|
||||
private val ctx: Id get() = arg(CTX_KEY)
|
||||
private val withoutBlankTemplate: Boolean get() = argBoolean(WITH_BLANK_TEMPLATE_KEY)
|
||||
|
||||
private lateinit var templatesAdapter: TemplateSelectAdapter
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
binding.templateViewPager.adapter = Adapter(ids, this)
|
||||
TabLayoutMediator(binding.tabs, binding.templateViewPager) { _, _ -> }.attach()
|
||||
vm.navigation.observe(viewLifecycleOwner, navObserver)
|
||||
|
||||
setupViewPagerAndTabs()
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
launch { setupTemplateHeaderMessage() }
|
||||
launch { setupCancelClicks() }
|
||||
launch { setupUseTemplateClicks() }
|
||||
launch { setupDismissStatusObserver() }
|
||||
setupClickEventHandlers()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupViewPagerAndTabs() {
|
||||
templatesAdapter = TemplateSelectAdapter(mutableListOf(), this)
|
||||
binding.templateViewPager.adapter = templatesAdapter
|
||||
TabLayoutMediator(binding.tabs, binding.templateViewPager) { _, _ -> }.attach()
|
||||
}
|
||||
|
||||
private suspend fun setupClickEventHandlers() {
|
||||
setupUseTemplateClicks()
|
||||
setupCancelClicks()
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
jobs += lifecycleScope.subscribe(vm.viewState) { render(it) }
|
||||
jobs += lifecycleScope.subscribe(vm.isDismissed) { if (it) exit() }
|
||||
super.onStart()
|
||||
vm.onStart(type = type, withoutBlankTemplate = withoutBlankTemplate)
|
||||
}
|
||||
|
||||
private fun render(viewState: TemplateSelectViewModel.ViewState) {
|
||||
when (viewState) {
|
||||
TemplateSelectViewModel.ViewState.ErrorGettingType -> TODO()
|
||||
TemplateSelectViewModel.ViewState.Init -> {
|
||||
binding.tvTemplateCountOrTutorial.text = null
|
||||
binding.btnCancel.isEnabled = true
|
||||
binding.btnUseTemplate.isEnabled = false
|
||||
}
|
||||
|
||||
is TemplateSelectViewModel.ViewState.Success -> {
|
||||
binding.tvTemplateCountOrTutorial.text = getString(
|
||||
R.string.this_type_has_templates,
|
||||
viewState.objectTypeName,
|
||||
viewState.templates.size
|
||||
)
|
||||
binding.btnUseTemplate.isEnabled = true
|
||||
templatesAdapter.update(viewState.templates)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun setupUseTemplateClicks() {
|
||||
binding.btnUseTemplate.clicks().collect {
|
||||
vm.onUseTemplate(
|
||||
template = ids[binding.templateViewPager.currentItem],
|
||||
vm.onUseTemplateButtonPressed(
|
||||
currentItem = binding.templateViewPager.currentItem,
|
||||
ctx = ctx
|
||||
)
|
||||
}
|
||||
|
@ -65,22 +99,10 @@ class TemplateSelectFragment :
|
|||
binding.btnCancel.clicks().collect { exit() }
|
||||
}
|
||||
|
||||
private suspend fun setupDismissStatusObserver() {
|
||||
vm.isDismissed.collect { isDismissed -> if (isDismissed) exit() }
|
||||
}
|
||||
|
||||
private fun exit() {
|
||||
findNavController().popBackStack()
|
||||
}
|
||||
|
||||
private suspend fun setupTemplateHeaderMessage() {
|
||||
binding.tvTemplateCountOrTutorial.text = getString(
|
||||
R.string.this_type_has_templates, ids.size
|
||||
)
|
||||
delay(USE_SWIPE_TO_CHOOSE_MSG_DELAY)
|
||||
binding.tvTemplateCountOrTutorial.setText(R.string.swipe_to_choose)
|
||||
}
|
||||
|
||||
override fun injectDependencies() {
|
||||
componentManager().templateSelectComponent.get().inject(this)
|
||||
}
|
||||
|
@ -97,19 +119,8 @@ class TemplateSelectFragment :
|
|||
)
|
||||
|
||||
companion object {
|
||||
const val TEMPLATE_IDS_KEY = "arg.template.ids"
|
||||
const val WITH_BLANK_TEMPLATE_KEY = "arg.template.with_empty_template"
|
||||
const val OBJECT_TYPE_KEY = "arg.template.object_type"
|
||||
const val CTX_KEY = "arg.template.ctx"
|
||||
|
||||
private const val USE_SWIPE_TO_CHOOSE_MSG_DELAY = 2000L
|
||||
}
|
||||
|
||||
internal class Adapter(
|
||||
private val ids: List<Id>, fragment: Fragment
|
||||
) : FragmentStateAdapter(fragment) {
|
||||
override fun getItemCount(): Int = ids.size
|
||||
override fun createFragment(position: Int): Fragment {
|
||||
return TemplateFragment.new(ids[position])
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--typography, buttons 05.04-->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
|
@ -22,8 +21,9 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_marginTop="11dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="11dp"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:singleLine="true"
|
||||
|
@ -51,7 +51,7 @@
|
|||
android:layout_marginStart="20dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/without_template" />
|
||||
android:text="@string/cancel" />
|
||||
|
||||
<com.anytypeio.anytype.core_ui.views.ButtonPrimaryLarge
|
||||
android:id="@+id/btnUseTemplate"
|
||||
|
|
|
@ -154,6 +154,13 @@ sealed class ObjectWrapper {
|
|||
val iconEmoji: String? by default
|
||||
val isDeleted: Boolean? by default
|
||||
val recommendedRelations: List<Id> get() = getValues(Relations.RECOMMENDED_RELATIONS)
|
||||
val recommendedLayout: ObjectType.Layout?
|
||||
get() = when (val value = map[Relations.RECOMMENDED_LAYOUT]) {
|
||||
is Double -> ObjectType.Layout.values().singleOrNull { layout ->
|
||||
layout.code == value.toInt()
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
data class Relation(override val map: Struct) : ObjectWrapper() {
|
||||
|
|
|
@ -64,6 +64,9 @@ sealed class Title(view: View) : BlockViewHolder(view), TextHolder {
|
|||
focus(item.isFocused)
|
||||
}
|
||||
content.pauseTextWatchers {
|
||||
if (item.hint != null) {
|
||||
content.hint = item.hint
|
||||
}
|
||||
content.setText(item.text, TextView.BufferType.EDITABLE)
|
||||
}
|
||||
cover?.setOnClickListener { onCoverClicked() }
|
||||
|
|
|
@ -16,9 +16,11 @@ class TypeHasTemplateToolbarWidget @JvmOverloads constructor(
|
|||
LayoutInflater.from(context), this, true
|
||||
)
|
||||
|
||||
var count: Int = 0
|
||||
set(value) {
|
||||
field = value
|
||||
binding.tvTitle.text = resources.getString(R.string.this_type_has_templates, value)
|
||||
}
|
||||
fun setText(count: Int, typeName: String) {
|
||||
binding.tvTitle.text = context.getString(
|
||||
R.string.this_type_has_templates,
|
||||
typeName,
|
||||
count
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,46 +1,57 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--typography, buttons 05.04-->
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<View
|
||||
android:id="@+id/dragger"
|
||||
android:layout_width="@dimen/default_dragger_width"
|
||||
android:layout_height="@dimen/default_dragger_height"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="6dp"
|
||||
android:background="@drawable/dragger" />
|
||||
android:background="@drawable/dragger"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvTitle"
|
||||
style="@style/TextView.UXStyle.Captions.1.Medium"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginTop="24dp"
|
||||
tools:text="This type has 2 templates" />
|
||||
android:layout_marginEnd="20dp"
|
||||
app:layout_constraintEnd_toStartOf="@+id/btnShow"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/dragger"
|
||||
tools:text="This type has 2 templates " />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvSubtitle"
|
||||
style="@style/TextView.UXStyle.Body"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginTop="45dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:text="@string/swipe_down_to_skip"
|
||||
android:textColor="@color/text_secondary" />
|
||||
android:textColor="@color/text_secondary"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/btnShow"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/tvTitle" />
|
||||
|
||||
<com.anytypeio.anytype.core_ui.views.ButtonSecondaryMedium
|
||||
android:id="@+id/btnShow"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical|end"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:text="@string/show" />
|
||||
android:text="@string/show"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</FrameLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -553,7 +553,7 @@
|
|||
<string name="swipe_down_to_skip">Swipe down to skip</string>
|
||||
<string name="show">Show</string>
|
||||
|
||||
<string name="this_type_has_templates">This type has %1$d templates</string>
|
||||
<string name="this_type_has_templates">Type \"%1$s\" has %2$d templates</string>
|
||||
|
||||
<string name="item_relation_create_from_scratch_title">Type</string>
|
||||
<string name="btn_restore">Restore</string>
|
||||
|
|
|
@ -31,16 +31,12 @@ class CreateObject(
|
|||
null
|
||||
}
|
||||
|
||||
val template = objectTemplates?.singleOrNull()?.id
|
||||
|
||||
val internalFlags = buildList {
|
||||
if (objectTemplates != null && objectTemplates.size > 1) {
|
||||
if (!objectTemplates.isNullOrEmpty()) {
|
||||
add(InternalFlags.ShouldSelectType)
|
||||
add(InternalFlags.ShouldSelectTemplate)
|
||||
} else {
|
||||
if (template == null) {
|
||||
add(InternalFlags.ShouldSelectType)
|
||||
}
|
||||
add(InternalFlags.ShouldSelectType)
|
||||
}
|
||||
add(InternalFlags.ShouldEmptyDelete)
|
||||
}
|
||||
|
@ -50,7 +46,7 @@ class CreateObject(
|
|||
}
|
||||
|
||||
val command = Command.CreateObject(
|
||||
template = template,
|
||||
template = null,
|
||||
prefilled = prefilled,
|
||||
internalFlags = internalFlags
|
||||
)
|
||||
|
@ -60,7 +56,7 @@ class CreateObject(
|
|||
return Result(
|
||||
objectId = result.id,
|
||||
event = result.event,
|
||||
appliedTemplate = template,
|
||||
appliedTemplate = null,
|
||||
type = type
|
||||
)
|
||||
}
|
||||
|
|
|
@ -51,7 +51,8 @@ class ObjectTypesSubscriptionManager (
|
|||
Relations.DESCRIPTION,
|
||||
Relations.ICON_EMOJI,
|
||||
Relations.SOURCE_OBJECT,
|
||||
Relations.IS_READ_ONLY
|
||||
Relations.IS_READ_ONLY,
|
||||
Relations.RECOMMENDED_LAYOUT
|
||||
),
|
||||
ignoreWorkspace = true
|
||||
)
|
||||
|
|
|
@ -125,8 +125,10 @@ class CreateObjectTest {
|
|||
verifyBlocking(getTemplates, times(1)) { run(GetTemplates.Params(defaultType)) }
|
||||
val commands = Command.CreateObject(
|
||||
prefilled = buildMap { put(Relations.TYPE, defaultType) },
|
||||
template = templateBook,
|
||||
template = null,
|
||||
internalFlags = listOf(
|
||||
InternalFlags.ShouldSelectType,
|
||||
InternalFlags.ShouldSelectTemplate,
|
||||
InternalFlags.ShouldEmptyDelete
|
||||
)
|
||||
)
|
||||
|
@ -181,8 +183,10 @@ class CreateObjectTest {
|
|||
verifyBlocking(getTemplates, times(1)) { run(GetTemplates.Params(type)) }
|
||||
val commands = Command.CreateObject(
|
||||
prefilled = buildMap { put(Relations.TYPE, type) },
|
||||
template = template,
|
||||
template = null,
|
||||
internalFlags = listOf(
|
||||
InternalFlags.ShouldSelectType,
|
||||
InternalFlags.ShouldSelectTemplate,
|
||||
InternalFlags.ShouldEmptyDelete
|
||||
)
|
||||
)
|
||||
|
|
|
@ -311,7 +311,8 @@ class EditorViewModel(
|
|||
when (state) {
|
||||
is SelectTemplateState.Available -> {
|
||||
SelectTemplateViewState.Active(
|
||||
count = state.templates.size
|
||||
count = state.templates.size,
|
||||
typeName = state.typeName
|
||||
)
|
||||
}
|
||||
else -> SelectTemplateViewState.Idle
|
||||
|
@ -415,7 +416,6 @@ class EditorViewModel(
|
|||
EventWrapper(
|
||||
AppNavigation.Command.OpenTemplates(
|
||||
type = state.type,
|
||||
templates = state.templates,
|
||||
ctx = context
|
||||
)
|
||||
)
|
||||
|
@ -6188,12 +6188,18 @@ class EditorViewModel(
|
|||
|
||||
private fun proceedWithTemplateSelection(typeId: Id) {
|
||||
viewModelScope.launch {
|
||||
onEvent(
|
||||
SelectTemplateEvent.OnStart(
|
||||
ctx = context,
|
||||
type = typeId
|
||||
val objType = storeOfObjectTypes.get(typeId)
|
||||
if (objType != null) {
|
||||
onEvent(
|
||||
SelectTemplateEvent.OnStart(
|
||||
ctx = context,
|
||||
type = typeId,
|
||||
typeName = objType.name.orEmpty()
|
||||
)
|
||||
)
|
||||
)
|
||||
} else {
|
||||
Timber.e("Error while getting object type from storeOfObjectTypes by id: $typeId")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -639,6 +639,7 @@ sealed class BlockView : ViewType {
|
|||
abstract var coverGradient: String?
|
||||
override val color: ThemeColor = ThemeColor.DEFAULT
|
||||
abstract override val background: ThemeColor
|
||||
abstract val hint: String?
|
||||
|
||||
val hasCover get() = coverColor != null || coverImage != null || coverGradient != null
|
||||
|
||||
|
@ -660,7 +661,8 @@ sealed class BlockView : ViewType {
|
|||
override val image: String? = null,
|
||||
override val mode: Mode = Mode.EDIT,
|
||||
override var cursor: Int? = null,
|
||||
override val searchFields: List<Searchable.Field> = emptyList()
|
||||
override val searchFields: List<Searchable.Field> = emptyList(),
|
||||
override val hint: String? = null
|
||||
) : Title(), Searchable {
|
||||
override fun getViewType() = HOLDER_TITLE
|
||||
}
|
||||
|
@ -683,7 +685,8 @@ sealed class BlockView : ViewType {
|
|||
override val image: String? = null,
|
||||
override val mode: Mode = Mode.EDIT,
|
||||
override var cursor: Int? = null,
|
||||
override val searchFields: List<Searchable.Field> = emptyList()
|
||||
override val searchFields: List<Searchable.Field> = emptyList(),
|
||||
override val hint: String? = null
|
||||
) : Title(), Searchable {
|
||||
override fun getViewType() = HOLDER_PROFILE_TITLE
|
||||
}
|
||||
|
@ -707,6 +710,7 @@ sealed class BlockView : ViewType {
|
|||
override var cursor: Int? = null,
|
||||
override val searchFields: List<Searchable.Field> = emptyList(),
|
||||
var isChecked: Boolean = false,
|
||||
override val hint: String? = null
|
||||
) : Title(), Searchable {
|
||||
override fun getViewType() = HOLDER_TODO_TITLE
|
||||
}
|
||||
|
@ -728,7 +732,8 @@ sealed class BlockView : ViewType {
|
|||
override val background: ThemeColor = ThemeColor.DEFAULT,
|
||||
override val color: ThemeColor = ThemeColor.DEFAULT,
|
||||
override val mode: Mode = Mode.READ,
|
||||
override var cursor: Int? = null
|
||||
override var cursor: Int? = null,
|
||||
override val hint: String? = null
|
||||
) : Title() {
|
||||
override fun getViewType() = HOLDER_ARCHIVE_TITLE
|
||||
}
|
||||
|
|
|
@ -27,21 +27,11 @@ class DefaultEditorTemplateDelegate(
|
|||
try {
|
||||
val templates = getTemplates.run(GetTemplates.Params(event.type))
|
||||
if (templates.isNotEmpty()) {
|
||||
if (templates.size == 1) {
|
||||
// No need to choose template if there is only one available template.
|
||||
applyTemplate.run(
|
||||
ApplyTemplate.Params(
|
||||
ctx = event.ctx,
|
||||
template = templates.first().id
|
||||
)
|
||||
)
|
||||
SelectTemplateState.Idle
|
||||
} else {
|
||||
SelectTemplateState.Available(
|
||||
templates = templates.map { it.id },
|
||||
type = event.type
|
||||
)
|
||||
}
|
||||
SelectTemplateState.Available(
|
||||
templates = templates.map { it.id },
|
||||
type = event.type,
|
||||
typeName = event.typeName
|
||||
)
|
||||
} else {
|
||||
SelectTemplateState.Idle
|
||||
}
|
||||
|
@ -77,7 +67,8 @@ sealed class SelectTemplateState {
|
|||
*/
|
||||
data class Available(
|
||||
val type: Id,
|
||||
val templates: List<Id>
|
||||
val templates: List<Id>,
|
||||
val typeName: String
|
||||
) : SelectTemplateState()
|
||||
|
||||
/**
|
||||
|
@ -94,7 +85,7 @@ sealed class SelectTemplateState {
|
|||
}
|
||||
|
||||
sealed class SelectTemplateEvent {
|
||||
data class OnStart(val ctx: Id, val type: Id) : SelectTemplateEvent()
|
||||
data class OnStart(val ctx: Id, val type: Id, val typeName: String) : SelectTemplateEvent()
|
||||
object OnSkipped : SelectTemplateEvent()
|
||||
object OnAccepted : SelectTemplateEvent()
|
||||
}
|
|
@ -2,5 +2,5 @@ package com.anytypeio.anytype.presentation.editor.template
|
|||
|
||||
sealed class SelectTemplateViewState {
|
||||
object Idle : SelectTemplateViewState()
|
||||
data class Active(val count: Int) : SelectTemplateViewState()
|
||||
data class Active(val count: Int, val typeName: String) : SelectTemplateViewState()
|
||||
}
|
|
@ -52,8 +52,7 @@ interface AppNavigation {
|
|||
|
||||
fun openTemplates(
|
||||
ctx: Id,
|
||||
type: String,
|
||||
templates: List<Id>
|
||||
type: String
|
||||
)
|
||||
|
||||
fun openLibrary()
|
||||
|
@ -117,8 +116,7 @@ interface AppNavigation {
|
|||
|
||||
data class OpenTemplates(
|
||||
val ctx: Id,
|
||||
val type: String,
|
||||
val templates: List<Id>
|
||||
val type: String
|
||||
) : Command()
|
||||
|
||||
object OpenLibrary: Command()
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
package com.anytypeio.anytype.presentation.templates
|
||||
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.anytypeio.anytype.core_models.Block
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.Relations
|
||||
import com.anytypeio.anytype.core_models.ext.asMap
|
||||
import com.anytypeio.anytype.presentation.common.BaseViewModel
|
||||
import com.anytypeio.anytype.presentation.editor.Editor
|
||||
import com.anytypeio.anytype.presentation.editor.EditorViewModel
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.BlockView
|
||||
import com.anytypeio.anytype.presentation.editor.render.BlockViewRenderer
|
||||
import com.anytypeio.anytype.presentation.editor.render.DefaultBlockViewRenderer
|
||||
import com.anytypeio.anytype.presentation.templates.TemplateConstants.BLANK_ROOT_ID
|
||||
import com.anytypeio.anytype.presentation.templates.TemplateConstants.BLANK_TITLE
|
||||
import com.anytypeio.anytype.presentation.templates.TemplateConstants.HEADER_ID
|
||||
import com.anytypeio.anytype.presentation.templates.TemplateConstants.TITLE_ID
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
||||
class TemplateBlankViewModel(
|
||||
private val renderer: DefaultBlockViewRenderer,
|
||||
) : BaseViewModel(), BlockViewRenderer by renderer {
|
||||
|
||||
val state = MutableStateFlow<List<BlockView>>(emptyList())
|
||||
|
||||
fun onStart(typeId: Id, typeName: String, layout: Int) {
|
||||
Timber.d("onStart, typeId: $typeId, typeName: $typeName, layout: $layout")
|
||||
val blockTitle = Block(
|
||||
id = TITLE_ID,
|
||||
content = Block.Content.Text(
|
||||
text = "",
|
||||
style = Block.Content.Text.Style.TITLE,
|
||||
marks = emptyList()
|
||||
),
|
||||
children = emptyList(),
|
||||
fields = Block.Fields.empty(),
|
||||
)
|
||||
val featuredRelationsBlock = Block(
|
||||
id = Relations.FEATURED_RELATIONS,
|
||||
content = Block.Content.FeaturedRelations,
|
||||
children = emptyList(),
|
||||
fields = Block.Fields.empty(),
|
||||
)
|
||||
val headerBlock = Block(
|
||||
id = HEADER_ID,
|
||||
content = Block.Content.Layout(
|
||||
type = Block.Content.Layout.Type.HEADER
|
||||
),
|
||||
children = listOf(blockTitle.id, featuredRelationsBlock.id),
|
||||
fields = Block.Fields.empty(),
|
||||
)
|
||||
val rootBlock = Block(
|
||||
id = BLANK_ROOT_ID,
|
||||
content = Block.Content.Smart,
|
||||
children = listOf(headerBlock.id),
|
||||
fields = Block.Fields.empty(),
|
||||
)
|
||||
val featuredRelations = listOf(Relations.TYPE)
|
||||
val page = listOf(rootBlock, headerBlock, blockTitle, featuredRelationsBlock)
|
||||
val objectDetails = Block.Fields(
|
||||
mapOf(
|
||||
Relations.LAYOUT to layout,
|
||||
Relations.TYPE to typeId,
|
||||
Relations.FEATURED_RELATIONS to featuredRelations
|
||||
)
|
||||
)
|
||||
|
||||
val typeDetails = Block.Fields(
|
||||
mapOf(
|
||||
Relations.ID to typeId,
|
||||
Relations.NAME to typeName
|
||||
)
|
||||
)
|
||||
|
||||
val customDetails =
|
||||
Block.Details(mapOf(BLANK_ROOT_ID to objectDetails, typeId to typeDetails))
|
||||
|
||||
viewModelScope.launch {
|
||||
val blockViews = page.asMap().render(
|
||||
mode = Editor.Mode.Read,
|
||||
root = page.first(),
|
||||
focus = com.anytypeio.anytype.domain.editor.Editor.Focus.empty(),
|
||||
anchor = page.first().id,
|
||||
indent = EditorViewModel.INITIAL_INDENT,
|
||||
details = customDetails,
|
||||
relationLinks = emptyList(),
|
||||
restrictions = emptyList(),
|
||||
selection = emptySet()
|
||||
)
|
||||
state.value = blockViews.map {
|
||||
when (it) {
|
||||
is BlockView.Title.Basic -> it.copy(hint = BLANK_TITLE)
|
||||
is BlockView.Title.Profile -> it.copy(hint = BLANK_TITLE)
|
||||
is BlockView.Title.Todo -> it.copy(hint = BLANK_TITLE)
|
||||
else -> it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package com.anytypeio.anytype.presentation.templates
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.anytypeio.anytype.presentation.editor.render.DefaultBlockViewRenderer
|
||||
import javax.inject.Inject
|
||||
|
||||
class TemplateBlankViewModelFactory @Inject constructor(
|
||||
private val renderer: DefaultBlockViewRenderer,
|
||||
) : ViewModelProvider.Factory {
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return TemplateBlankViewModel(
|
||||
renderer = renderer
|
||||
) as T
|
||||
}
|
||||
}
|
|
@ -5,47 +5,148 @@ import androidx.lifecycle.ViewModel
|
|||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.ObjectWrapper
|
||||
import com.anytypeio.anytype.core_utils.common.EventWrapper
|
||||
import com.anytypeio.anytype.domain.base.fold
|
||||
import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes
|
||||
import com.anytypeio.anytype.domain.templates.ApplyTemplate
|
||||
import com.anytypeio.anytype.domain.templates.GetTemplates
|
||||
import com.anytypeio.anytype.presentation.common.BaseViewModel
|
||||
import com.anytypeio.anytype.presentation.navigation.AppNavigation
|
||||
import com.anytypeio.anytype.presentation.navigation.SupportNavigation
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import timber.log.Timber
|
||||
|
||||
class TemplateSelectViewModel(
|
||||
private val storeOfObjectTypes: StoreOfObjectTypes,
|
||||
private val getTemplates: GetTemplates,
|
||||
private val applyTemplate: ApplyTemplate
|
||||
) : BaseViewModel(), SupportNavigation<EventWrapper<AppNavigation.Command>> {
|
||||
|
||||
val isDismissed = MutableSharedFlow<Boolean>(replay = 0)
|
||||
val isDismissed = MutableStateFlow(false)
|
||||
|
||||
override val navigation: MutableLiveData<EventWrapper<AppNavigation.Command>> = MutableLiveData()
|
||||
private val _viewState = MutableStateFlow<ViewState>(ViewState.Init)
|
||||
val viewState: StateFlow<ViewState> = _viewState
|
||||
|
||||
fun onUseTemplate(ctx: Id, template: Id) {
|
||||
override val navigation: MutableLiveData<EventWrapper<AppNavigation.Command>> =
|
||||
MutableLiveData()
|
||||
|
||||
fun onStart(type: Id, withoutBlankTemplate: Boolean) {
|
||||
viewModelScope.launch {
|
||||
val result = applyTemplate.execute(
|
||||
ApplyTemplate.Params(
|
||||
ctx = ctx,
|
||||
template = template
|
||||
val objType = storeOfObjectTypes.get(type)
|
||||
if (objType != null) {
|
||||
Timber.d("onStart, Object type $objType")
|
||||
proceedWithGettingTemplates(objType, withoutBlankTemplate)
|
||||
} else {
|
||||
Timber.e("onStart, Object type $type not found")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun proceedWithGettingTemplates(
|
||||
objType: ObjectWrapper.Type, withoutBlankTemplate: Boolean
|
||||
) {
|
||||
val params = GetTemplates.Params(objType.id)
|
||||
viewModelScope.launch {
|
||||
getTemplates.async(params)
|
||||
.fold(
|
||||
onSuccess = { buildTemplateViews(objType, it, withoutBlankTemplate) },
|
||||
onFailure = { Timber.e(it, "Error while getting templates") })
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun buildTemplateViews(
|
||||
objType: ObjectWrapper.Type,
|
||||
templates: List<ObjectWrapper.Basic>,
|
||||
withoutBlankTemplate: Boolean
|
||||
) {
|
||||
val templateViews = buildList {
|
||||
if (!withoutBlankTemplate) add(
|
||||
TemplateView.Blank(
|
||||
typeId = objType.id,
|
||||
typeName = objType.name.orEmpty(),
|
||||
layout = objType.recommendedLayout?.code ?: 0
|
||||
)
|
||||
)
|
||||
if (result.isFailure) {
|
||||
sendToast("Something went wrong. Please, try again later.")
|
||||
addAll(templates.map { TemplateView.Template(it.id) })
|
||||
}
|
||||
_viewState.emit(
|
||||
ViewState.Success(
|
||||
objectTypeName = objType.name.orEmpty(),
|
||||
templates = templateViews,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun onUseTemplateButtonPressed(ctx: Id, currentItem: Int) {
|
||||
when (val state = _viewState.value) {
|
||||
is ViewState.Success -> {
|
||||
when (val template = state.templates[currentItem]) {
|
||||
is TemplateView.Blank -> {
|
||||
isDismissed.value = true
|
||||
}
|
||||
is TemplateView.Template -> {
|
||||
proceedWithApplyingTemplate(ctx, template)
|
||||
}
|
||||
}
|
||||
}
|
||||
isDismissed.emit(true)
|
||||
else -> {
|
||||
Timber.e("onUseTemplate: unexpected state $state")
|
||||
isDismissed.value = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun proceedWithApplyingTemplate(ctx: Id, template: TemplateView.Template) {
|
||||
val params = ApplyTemplate.Params(ctx = ctx, template = template.id)
|
||||
viewModelScope.launch {
|
||||
applyTemplate.async(params).fold(
|
||||
onSuccess = {
|
||||
isDismissed.value = true
|
||||
Timber.d("Template ${template.id} applied successfully")
|
||||
},
|
||||
onFailure = {
|
||||
isDismissed.value = true
|
||||
Timber.e(it, "Error while applying template")
|
||||
sendToast("Something went wrong. Please, try again later.")
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class Factory @Inject constructor(
|
||||
private val applyTemplate: ApplyTemplate
|
||||
private val applyTemplate: ApplyTemplate,
|
||||
private val getTemplates: GetTemplates,
|
||||
private val storeOfObjectTypes: StoreOfObjectTypes
|
||||
) : ViewModelProvider.Factory {
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return TemplateSelectViewModel(
|
||||
applyTemplate = applyTemplate
|
||||
applyTemplate = applyTemplate,
|
||||
getTemplates = getTemplates,
|
||||
storeOfObjectTypes = storeOfObjectTypes
|
||||
) as T
|
||||
}
|
||||
}
|
||||
|
||||
sealed class ViewState {
|
||||
data class Success(
|
||||
val objectTypeName: String, val templates: List<TemplateView>
|
||||
) : ViewState()
|
||||
|
||||
object Init : ViewState()
|
||||
object ErrorGettingType : ViewState()
|
||||
}
|
||||
|
||||
sealed class TemplateView {
|
||||
data class Blank(
|
||||
val typeId: Id, val typeName: String, val layout: Int
|
||||
) : TemplateView()
|
||||
|
||||
data class Template(val id: Id) : TemplateView()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package com.anytypeio.anytype.presentation.templates
|
||||
|
||||
object TemplateConstants {
|
||||
const val BLANK_TITLE = "Blank template"
|
||||
const val BLANK_ROOT_ID = "blank_template_root_id"
|
||||
const val HEADER_ID = "header"
|
||||
const val TITLE_ID = "blockTitle"
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue