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

DROID-417 Set | Empty source state (#2583)

* DROID-417 legacy

* DROID-417 icon text widget refactoring

* DROID-417 update icon widget

* DROID-417 relation holder source

* DROID-417 render source as featured relation

* DROID-417 blockDataViewSetSource command

* DROID-417 blockDataViewSetSource command, data + middle layer

* DROID-417 use case + di

* DROID-417 source set + render no source state

* DROID-417 type change screen, update title

* DROID-417 clicks + command

* DROID-417 add event updateStructure and addBlock to set events reducer

* DROID-417 add clicks on set source relations

* DROID-417 start select source screen

* DROID-417 header + divider visibility

* DROID-417 icon replace

* DROID-417 button new update

* DROID-417 move featured relation subscription to onStart

* DROID-417 fix tests

* DROID-417 design + clicks

* DROID-417 code style

* DROID-417 holder source

* DROID-417 add extension mapping to object view

* DROID-417 add is selected state for object type view

* DROID-417 sort types by selected

* DROID-417 pr fix

* DROID-417 rename view

* DROID-417 pr fix

* DROID-417 do not map empty objects

* DROID-417 ci

* DROID-417 ci

* DROID-417 ci off

Co-authored-by: konstantiniiv <ki@anytype.io>
This commit is contained in:
Konstantin Ivanov 2022-09-07 22:38:49 +02:00 committed by GitHub
parent e4363820b0
commit a2a979a5ce
Signed by: github
GPG key ID: 4AEE18F83AFDEB23
53 changed files with 517 additions and 401 deletions

View file

@ -19,6 +19,7 @@ import com.anytypeio.anytype.domain.block.interactor.UpdateText
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.config.Gateway
import com.anytypeio.anytype.domain.cover.SetDocCoverImage
import com.anytypeio.anytype.domain.dataview.SetDataViewSource
import com.anytypeio.anytype.domain.dataview.interactor.AddNewRelationToDataView
import com.anytypeio.anytype.domain.dataview.interactor.CreateDataViewRecord
import com.anytypeio.anytype.domain.dataview.interactor.SetActiveViewer
@ -64,6 +65,7 @@ abstract class TestObjectSetSetup {
private lateinit var interceptThreadStatus: InterceptThreadStatus
private lateinit var setDocCoverImage: SetDocCoverImage
private lateinit var downloadUnsplashImage: DownloadUnsplashImage
private lateinit var setDataViewSource: SetDataViewSource
lateinit var urlBuilder: UrlBuilder
@ -123,6 +125,7 @@ abstract class TestObjectSetSetup {
open fun setup() {
MockitoAnnotations.openMocks(this)
setDataViewSource = SetDataViewSource(repo)
addDataViewRelation = AddNewRelationToDataView(repo)
updateText = UpdateText(repo)
openObjectSet = OpenObjectSet(repo, auth)
@ -144,6 +147,7 @@ abstract class TestObjectSetSetup {
)
)
TestObjectSetFragment.testVmFactory = ObjectSetViewModelFactory(
openObjectSet = openObjectSet,
closeBlock = closeBlock,
@ -165,7 +169,8 @@ abstract class TestObjectSetSetup {
setDocCoverImage = setDocCoverImage,
delegator = delegator,
getTemplates = getTemplates,
createNewObject = createNewObject
createNewObject = createNewObject,
setDataViewSource = setDataViewSource
)
}

View file

@ -21,6 +21,7 @@ import com.anytypeio.anytype.domain.block.interactor.UpdateText
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.config.UserSettingsRepository
import com.anytypeio.anytype.domain.cover.SetDocCoverImage
import com.anytypeio.anytype.domain.dataview.SetDataViewSource
import com.anytypeio.anytype.domain.dataview.interactor.AddNewRelationToDataView
import com.anytypeio.anytype.domain.dataview.interactor.CreateDataViewRecord
import com.anytypeio.anytype.domain.search.SearchObjects
@ -89,8 +90,8 @@ interface ObjectSetSubComponent {
fun objectSetSettingsComponent(): ObjectSetSettingsSubComponent.Builder
fun viewerCardSizeSelectComponent(): ViewerCardSizeSelectSubcomponent.Builder
fun viewerImagePreviewSelectComponent(): ViewerImagePreviewSelectSubcomponent.Builder
fun relationAddToDataViewComponent() : RelationAddToDataViewSubComponent.Builder
fun relationCreateFromScratchForDataViewComponent() : RelationCreateFromScratchForDataViewSubComponent.Builder
fun relationAddToDataViewComponent(): RelationAddToDataViewSubComponent.Builder
fun relationCreateFromScratchForDataViewComponent(): RelationCreateFromScratchForDataViewSubComponent.Builder
fun dataviewViewerActionComponent(): DataViewViewerActionSubComponent.Builder
fun selectSortRelationComponent(): SelectSortRelationSubComponent.Builder
fun selectFilterRelationComponent(): SelectFilterRelationSubComponent.Builder
@ -101,10 +102,10 @@ interface ObjectSetSubComponent {
fun relationTextValueComponent(): RelationTextValueSubComponent.Builder
fun relationDateValueComponent(): RelationDataValueSubComponent.Builder
fun objectSetMenuComponent() : ObjectSetMenuComponent.Builder
fun objectSetIconPickerComponent() : ObjectSetIconPickerComponent.Builder
fun objectSetCoverComponent() : SelectCoverObjectSetSubComponent.Builder
fun objectUnsplashComponent() : UnsplashSubComponent.Builder
fun objectSetMenuComponent(): ObjectSetMenuComponent.Builder
fun objectSetIconPickerComponent(): ObjectSetIconPickerComponent.Builder
fun objectSetCoverComponent(): SelectCoverObjectSetSubComponent.Builder
fun objectUnsplashComponent(): UnsplashSubComponent.Builder
}
@Module
@ -141,7 +142,8 @@ object ObjectSetModule {
downloadUnsplashImage: DownloadUnsplashImage,
setDocCoverImage: SetDocCoverImage,
getTemplates: GetTemplates,
createNewObject: CreateNewObject
createNewObject: CreateNewObject,
setDataViewSource: SetDataViewSource
): ObjectSetViewModelFactory = ObjectSetViewModelFactory(
openObjectSet = openObjectSet,
closeBlock = closeBlock,
@ -163,7 +165,8 @@ object ObjectSetModule {
downloadUnsplashImage = downloadUnsplashImage,
setDocCoverImage = setDocCoverImage,
getTemplates = getTemplates,
createNewObject = createNewObject
createNewObject = createNewObject,
setDataViewSource = setDataViewSource
)
@JvmStatic
@ -173,12 +176,19 @@ object ObjectSetModule {
getDefaultEditorType: GetDefaultEditorType,
getTemplates: GetTemplates,
createPage: CreatePage,
) : CreateNewObject = CreateNewObject(
): CreateNewObject = CreateNewObject(
getDefaultEditorType,
getTemplates,
createPage
)
@JvmStatic
@Provides
@PerScreen
fun provideSetDataViewSource(
repo: BlockRepository
): SetDataViewSource = SetDataViewSource(repo)
@JvmStatic
@Provides
@PerScreen
@ -259,7 +269,7 @@ object ObjectSetModule {
@PerScreen
fun provideInterceptThreadStatus(
channel: ThreadStatusChannel
) : InterceptThreadStatus = InterceptThreadStatus(
): InterceptThreadStatus = InterceptThreadStatus(
channel = channel
)
@ -295,7 +305,7 @@ object ObjectSetModule {
@JvmStatic
@Provides
@PerScreen
fun provideDelegator() : Delegator<Action> = Delegator.Default()
fun provideDelegator(): Delegator<Action> = Delegator.Default()
@JvmStatic
@Provides
@ -307,7 +317,7 @@ object ObjectSetModule {
@PerScreen
fun provideDataViewObjectRelationProvider(
state: StateFlow<ObjectSet>
) : ObjectRelationProvider = DataViewObjectRelationProvider(state)
): ObjectRelationProvider = DataViewObjectRelationProvider(state)
@JvmStatic
@Provides
@ -315,14 +325,14 @@ object ObjectSetModule {
fun provideDataViewObjectValueProvider(
state: StateFlow<ObjectSet>,
session: ObjectSetSession
) : ObjectValueProvider = DataViewObjectValueProvider(state, session)
): ObjectValueProvider = DataViewObjectValueProvider(state, session)
@JvmStatic
@Provides
@PerScreen
fun provideObjectTypeProvider(
state: StateFlow<ObjectSet>,
) : ObjectTypeProvider = object : ObjectTypeProvider {
): ObjectTypeProvider = object : ObjectTypeProvider {
override fun provide(): List<ObjectType> = state.value.objectTypes
}
@ -331,7 +341,7 @@ object ObjectSetModule {
@PerScreen
fun provideObjectDetailProvider(
state: StateFlow<ObjectSet>,
) : ObjectDetailProvider = object : ObjectDetailProvider {
): ObjectDetailProvider = object : ObjectDetailProvider {
override fun provide(): Map<Id, Block.Fields> = state.value.details
}
@ -340,7 +350,7 @@ object ObjectSetModule {
@PerScreen
fun provideUpdateDetailUseCase(
repository: BlockRepository
) : UpdateDetail = UpdateDetail(repository)
): UpdateDetail = UpdateDetail(repository)
@JvmStatic
@Provides

View file

@ -10,6 +10,7 @@ import androidx.fragment.app.setFragmentResult
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.Id
import com.anytypeio.anytype.core_models.SmartBlockType
import com.anytypeio.anytype.core_ui.features.objects.ObjectTypeVerticalAdapter
@ -37,6 +38,11 @@ class ObjectTypeChangeFragment :
private val isDraft: Boolean get() = argOrNull<Boolean>(OBJECT_IS_DRAFT_KEY) ?: false
private val isSetSource: Boolean get() = argOrNull<Boolean>(ARG_IS_SET_SOURCE) ?: false
private val selectedSources : List<Id>
get() = argOrNull<List<Id>>(ARG_SOURCES) ?: emptyList()
@Inject
lateinit var factory: ObjectTypeChangeViewModelFactory
@ -51,6 +57,9 @@ class ObjectTypeChangeFragment :
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if (isSetSource) {
binding.tvTitle.text = getString(R.string.select_source)
}
binding.recycler.apply {
adapter = objectTypeAdapter
layoutManager = LinearLayoutManager(context)
@ -83,7 +92,8 @@ class ObjectTypeChangeFragment :
vm.onStart(
smartBlockType = smartBlockType,
excludedTypes = excludedTypes,
isDraft = isDraft
isDraft = isDraft,
selectedSources = selectedSources
)
}
@ -105,6 +115,8 @@ class ObjectTypeChangeFragment :
companion object {
const val ARG_SMART_BLOCK_TYPE = "arg.object-type.smart-block-type"
const val ARG_EXCLUDED_TYPES = "arg.object-type.excluded-types"
const val ARG_IS_SET_SOURCE = "arg.object-type.is-set-source"
const val ARG_SOURCES = "arg.object-type.sources"
const val OBJECT_TYPE_URL_KEY = "object-type-url.key"
const val OBJECT_TYPE_NAME_KEY = "object-type-name.key"
const val OBJECT_TYPE_REQUEST_KEY = "object-type.request"

View file

@ -23,6 +23,7 @@ import androidx.core.view.WindowInsetsAnimationCompat.Callback.DISPATCH_MODE_STO
import androidx.core.view.children
import androidx.core.view.marginBottom
import androidx.core.view.updatePadding
import androidx.fragment.app.setFragmentResultListener
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
@ -49,6 +50,7 @@ import com.anytypeio.anytype.core_utils.ext.drawable
import com.anytypeio.anytype.core_utils.ext.gone
import com.anytypeio.anytype.core_utils.ext.hideKeyboard
import com.anytypeio.anytype.core_utils.ext.hideSoftInput
import com.anytypeio.anytype.core_utils.ext.invisible
import com.anytypeio.anytype.core_utils.ext.subscribe
import com.anytypeio.anytype.core_utils.ext.syncFocusWithImeVisibility
import com.anytypeio.anytype.core_utils.ext.syncTranslationWithImeVisibility
@ -69,6 +71,7 @@ import com.anytypeio.anytype.ui.base.NavigationFragment
import com.anytypeio.anytype.ui.editor.cover.SelectCoverObjectSetFragment
import com.anytypeio.anytype.ui.editor.modals.IconPickerFragmentBase
import com.anytypeio.anytype.ui.editor.sheets.ObjectMenuBaseFragment
import com.anytypeio.anytype.ui.objects.ObjectTypeChangeFragment
import com.anytypeio.anytype.ui.relations.RelationDateValueFragment
import com.anytypeio.anytype.ui.relations.RelationDateValueFragment.DateValueEditReceiver
import com.anytypeio.anytype.ui.relations.RelationTextValueFragment
@ -111,7 +114,7 @@ open class ObjectSetFragment :
private val topToolbarStatusText: TextView
get() = binding.topToolbar.root.findViewById(R.id.tvStatus)
private val addNewButton: ImageView
private val addNewButton: TextView
get() = binding.dataViewHeader.root.findViewById(R.id.addNewButton)
private val customizeViewButton: ImageView
@ -242,6 +245,14 @@ open class ObjectSetFragment :
binding.listView.onTaskCheckboxClicked = { id ->
vm.onTaskCheckboxClicked(id)
}
setFragmentResultListener(ObjectTypeChangeFragment.OBJECT_TYPE_REQUEST_KEY) { _, bundle ->
val source = bundle.getString(ObjectTypeChangeFragment.OBJECT_TYPE_URL_KEY)
if (source != null) {
vm.onDataViewSourcePicked(source = source)
} else {
toast("Error while setting Set source. Source is empty")
}
}
}
private fun setupWindowInsetAnimation() {
@ -313,19 +324,6 @@ open class ObjectSetFragment :
vm.navigation.observe(viewLifecycleOwner, navObserver)
lifecycleScope.subscribe(vm.toasts.stream()) { toast(it) }
lifecycleScope.subscribe(vm.status) { setStatus(it) }
lifecycleScope.subscribe(vm.featured) { featured ->
if (featured != null) {
featuredRelations.visible()
featuredRelations.set(
item = featured,
click = {},
isObjectSet = true
)
} else {
featuredRelations.clear()
featuredRelations.gone()
}
}
lifecycleScope.subscribe(vm.isCustomizeViewPanelVisible) { isCustomizeViewPanelVisible ->
if (isCustomizeViewPanelVisible) showBottomPanel() else hideBottomPanel()
}
@ -348,6 +346,7 @@ open class ObjectSetFragment :
when (viewer) {
is Viewer.GridView -> {
with(binding) {
dataViewHeader.root.visible()
unsupportedViewError.gone()
unsupportedViewError.text = null
galleryView.clear()
@ -362,6 +361,7 @@ open class ObjectSetFragment :
viewerGridHeaderAdapter.submitList(emptyList())
viewerGridAdapter.submitList(emptyList())
with(binding) {
dataViewHeader.root.visible()
unsupportedViewError.gone()
unsupportedViewError.text = null
listView.gone()
@ -377,6 +377,7 @@ open class ObjectSetFragment :
viewerGridHeaderAdapter.submitList(emptyList())
viewerGridAdapter.submitList(emptyList())
with(binding) {
dataViewHeader.root.visible()
unsupportedViewError.gone()
unsupportedViewError.text = null
galleryView.gone()
@ -389,6 +390,7 @@ open class ObjectSetFragment :
viewerGridHeaderAdapter.submitList(emptyList())
viewerGridAdapter.submitList(emptyList())
with(binding) {
dataViewHeader.root.visible()
galleryView.gone()
galleryView.clear()
listView.gone()
@ -401,6 +403,7 @@ open class ObjectSetFragment :
viewerGridHeaderAdapter.submitList(emptyList())
viewerGridAdapter.submitList(emptyList())
with(binding) {
dataViewHeader.root.invisible()
galleryView.gone()
galleryView.clear()
listView.gone()
@ -612,8 +615,8 @@ open class ObjectSetFragment :
}
is ObjectSetCommand.Modal.SetNameForCreatedRecord -> {
findNavController().navigate(
R.id.setNameForNewRecordScreen,
bundleOf(SetObjectSetRecordNameFragment.CONTEXT_KEY to command.ctx)
R.id.setNameForNewRecordScreen,
bundleOf(SetObjectSetRecordNameFragment.CONTEXT_KEY to command.ctx)
)
}
is ObjectSetCommand.Intent.MailTo -> {
@ -681,6 +684,17 @@ open class ObjectSetFragment :
)
)
}
is ObjectSetCommand.Modal.OpenSelectSourceScreen -> {
findNavController()
.navigate(
R.id.objectTypeChangeScreen,
bundleOf(
ObjectTypeChangeFragment.ARG_SMART_BLOCK_TYPE to command.smartBlockType,
ObjectTypeChangeFragment.ARG_IS_SET_SOURCE to true,
ObjectTypeChangeFragment.ARG_SOURCES to command.sources
)
)
}
}
}
@ -756,6 +770,19 @@ open class ObjectSetFragment :
binding.dvProgressBar.hide()
}
}
jobs += lifecycleScope.subscribe(vm.featured) { featured ->
if (featured != null) {
featuredRelations.visible()
featuredRelations.set(
item = featured,
click = vm::onClickListener,
isObjectSet = true
)
} else {
featuredRelations.clear()
featuredRelations.gone()
}
}
vm.onStart(ctx)
}

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="12dp"
android:height="12dp"
android:viewportWidth="12"
android:viewportHeight="12">
<path
android:pathData="M5.999,0C5.644,0 5.356,0.288 5.356,0.643V5.356H0.643C0.288,5.356 0,5.644 0,5.999C0,6.354 0.288,6.642 0.643,6.642H5.356V11.357C5.356,11.712 5.644,12 5.999,12C6.354,12 6.642,11.712 6.642,11.357V6.642H11.357C11.712,6.642 12,6.354 12,5.999C12,5.644 11.712,5.356 11.357,5.356H6.642V0.643C6.642,0.288 6.354,0 5.999,0Z"
android:fillColor="#ffffff"
android:fillType="evenOdd"/>
</vector>

View file

@ -43,11 +43,11 @@
android:layout_width="0dp"
android:layout_height="@dimen/data_view_divider_height"
android:background="@color/shape_primary"
android:visibility="invisible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/dataViewHeader" />
<include
android:id="@+id/gridContainer"
layout="@layout/item_viewer_container"

View file

@ -14,6 +14,7 @@
android:background="@drawable/dragger" />
<TextView
android:id="@+id/tvTitle"
style="@style/ModalTitleStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"

View file

@ -3,53 +3,62 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/dataViewToolbar"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="56dp">
android:layout_height="56dp"
android:orientation="horizontal">
<FrameLayout
android:id="@+id/switchViewContainer"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:layout_weight="1">
<TextView
android:id="@+id/tvCurrentViewerName"
android:layout_width="wrap_content"
android:layout_weight="01"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="20dp"
android:layout_weight="01"
android:drawablePadding="2dp"
android:fontFamily="@font/inter_semibold"
android:gravity="center_vertical"
android:layout_gravity="center_vertical"
android:maxLines="1"
android:singleLine="true"
android:textColor="@color/text_primary"
android:textSize="17sp"
android:singleLine="true"
android:maxLines="1"
app:drawableEndCompat="@drawable/ic_arrow_expand_dv_viewer"
tools:text="All Pages" />
</FrameLayout>
<ImageView
android:background="?attr/selectableItemBackgroundBorderless"
android:id="@+id/customizeViewButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="24dp"
android:layout_marginStart="24dp"
android:layout_gravity="center_vertical"
android:layout_marginStart="24dp"
android:layout_marginEnd="16dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/content_description_customize_view_button"
android:src="@drawable/ic_dv_customize_view" />
<ImageView
android:background="?attr/selectableItemBackgroundBorderless"
<TextView
android:id="@+id/addNewButton"
android:layout_gravity="center_vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:src="@drawable/ic_add_new_object_to_set" />
android:layout_gravity="center_vertical"
android:layout_marginEnd="20dp"
android:background="@drawable/bg_set_new_button"
android:drawableStart="@drawable/ic_plus_white_12"
android:drawablePadding="4dp"
android:fontFamily="@font/inter_medium"
android:paddingStart="8dp"
android:paddingTop="5dp"
android:paddingEnd="8dp"
android:paddingBottom="5dp"
android:text="New"
android:textColor="@color/white"
android:textSize="12sp" />
</LinearLayout>

View file

@ -29,7 +29,16 @@
android:layout_height="@dimen/default_toolbar_height"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toBottomOf="@+id/objectHeader" />
motion:layout_constraintTop_toBottomOf="@+id/objectHeader"
motion:visibilityMode="ignore" />
<Constraint
android:id="@+id/controlDivider2"
android:layout_width="0dp"
android:layout_height="@dimen/data_view_divider_height"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toBottomOf="@+id/dataViewHeader"
motion:visibilityMode="ignore" />
<Constraint
android:id="@+id/paginatorToolbar"
android:layout_width="0dp"
@ -85,7 +94,16 @@
android:layout_height="@dimen/default_toolbar_height"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toBottomOf="@+id/topToolbar" />
motion:layout_constraintTop_toBottomOf="@+id/topToolbar"
motion:visibilityMode="ignore"/>
<Constraint
android:id="@+id/controlDivider2"
android:layout_width="0dp"
android:layout_height="@dimen/data_view_divider_height"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toBottomOf="@+id/dataViewHeader"
motion:visibilityMode="ignore"/>
<Constraint
android:id="@+id/paginatorToolbar"
android:layout_width="0dp"

View file

@ -15,6 +15,11 @@ class ObjectTypeHolder(
) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: ObjectTypeView.Item) = with(binding) {
if (item.isSelected) {
icSelected.visible()
} else {
icSelected.gone()
}
ivIcon.setIcon(
emoji = item.emoji,
image = null,

View file

@ -1,77 +0,0 @@
package com.anytypeio.anytype.core_ui.features.relations
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.databinding.ItemFeaturedRelationDefaultBinding
import com.anytypeio.anytype.core_ui.databinding.ItemFeaturedRelationStatusBinding
import com.anytypeio.anytype.core_ui.databinding.ItemFeaturedRelationTagsBinding
import com.anytypeio.anytype.core_ui.features.relations.holders.FeaturedRelationViewHolder
import com.anytypeio.anytype.presentation.relations.DocumentRelationView
class FeaturedRelationAdapter(
private var items: List<DocumentRelationView> = emptyList()
): RecyclerView.Adapter<FeaturedRelationViewHolder>() {
override fun onCreateViewHolder(
parent: ViewGroup, viewType: Int
): FeaturedRelationViewHolder {
val inflater = LayoutInflater.from(parent.context)
return when(viewType) {
R.layout.item_featured_relation_default -> {
FeaturedRelationViewHolder.Default(
binding = ItemFeaturedRelationDefaultBinding.inflate(
inflater, parent, false
)
)
}
R.layout.item_featured_relation_tags -> {
FeaturedRelationViewHolder.Tags(
binding = ItemFeaturedRelationTagsBinding.inflate(
inflater, parent, false
)
)
}
R.layout.item_featured_relation_status -> {
FeaturedRelationViewHolder.Status(
binding = ItemFeaturedRelationStatusBinding.inflate(
inflater, parent, false
)
)
}
else -> throw IllegalStateException("Unexpected view type: $viewType")
}
}
override fun onBindViewHolder(holder: FeaturedRelationViewHolder, position: Int) {
when(holder) {
is FeaturedRelationViewHolder.Default -> {
holder.bind(items[position])
}
is FeaturedRelationViewHolder.Tags -> {
holder.bind(items[position] as DocumentRelationView.Tags)
}
is FeaturedRelationViewHolder.Status -> {
holder.bind(items[position] as DocumentRelationView.Status)
}
}
}
override fun getItemCount(): Int = items.size
override fun getItemViewType(position: Int): Int = when(items[position]) {
is DocumentRelationView.Default -> R.layout.item_featured_relation_default
is DocumentRelationView.Tags -> R.layout.item_featured_relation_tags
is DocumentRelationView.Status -> R.layout.item_featured_relation_status
is DocumentRelationView.Checkbox -> TODO()
is DocumentRelationView.File -> TODO()
is DocumentRelationView.Object -> TODO()
is DocumentRelationView.ObjectType -> R.layout.item_featured_relation_default
}
fun update(items: List<DocumentRelationView>) {
this.items = items
notifyDataSetChanged()
}
}

View file

@ -1,83 +0,0 @@
package com.anytypeio.anytype.core_ui.features.relations.holders
import android.view.View
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.databinding.ItemFeaturedRelationDefaultBinding
import com.anytypeio.anytype.core_ui.databinding.ItemFeaturedRelationStatusBinding
import com.anytypeio.anytype.core_ui.databinding.ItemFeaturedRelationTagsBinding
import com.anytypeio.anytype.core_ui.extensions.color
import com.anytypeio.anytype.core_ui.extensions.dark
import com.anytypeio.anytype.core_ui.extensions.veryLight
import com.anytypeio.anytype.core_utils.ext.setDrawableColor
import com.anytypeio.anytype.core_models.ThemeColor
import com.anytypeio.anytype.presentation.relations.DocumentRelationView
import com.anytypeio.anytype.presentation.sets.model.StatusView
import com.anytypeio.anytype.presentation.sets.model.TagView
sealed class FeaturedRelationViewHolder(view: View) : RecyclerView.ViewHolder(view) {
class Default(
val binding: ItemFeaturedRelationDefaultBinding
) : FeaturedRelationViewHolder(binding.root) {
fun bind(item: DocumentRelationView) {
binding.tvFeaturedRelationValue.text = item.value
}
}
class Tags(
val binding: ItemFeaturedRelationTagsBinding
) : FeaturedRelationViewHolder(binding.root) {
private val container = binding.featuredRelationTagContainer
fun bind(item: DocumentRelationView.Tags) {
// TODO optimize create and delete only diff between views
container.removeAllViews()
item.tags.forEach { tag ->
container.addView(createTagView(tag))
}
}
private fun createTagView(tag: TagView): View {
val context = itemView.context
val resources = context.resources
val defaultTextColor = context.color(R.color.default_filter_tag_text_color)
val defaultBackgroundColor = context.color(R.color.default_filter_tag_background_color)
val themeColor = ThemeColor.values().find { it.code == tag.color } ?: ThemeColor.DEFAULT
return TextView(context).apply {
text = tag.tag
setBackgroundResource(R.drawable.rect_dv_cell_tag_item)
background.setDrawableColor(resources.veryLight(themeColor, defaultBackgroundColor))
setTextColor(resources.dark(themeColor, defaultTextColor))
}
}
}
class Status(
val binding: ItemFeaturedRelationStatusBinding
) : FeaturedRelationViewHolder(binding.root) {
private val container = binding.featuredRelationStatusContainer
fun bind(item: DocumentRelationView.Status) {
// TODO optimize create and delete only diff between views
container.removeAllViews()
item.status.forEach { status ->
container.addView(createStatusView(status))
}
}
private fun createStatusView(status: StatusView): View {
val resources = itemView.context.resources
val color = ThemeColor.values().find { it.code == status.color } ?: ThemeColor.DEFAULT
val defaultTextColor = itemView.context.color(R.color.default_filter_tag_text_color)
return TextView(itemView.context).apply {
text = status.status
setTextColor(resources.dark(color, defaultTextColor))
}
}
}
}

View file

@ -16,8 +16,10 @@ import com.anytypeio.anytype.core_ui.menu.ObjectTypePopupMenu
import com.anytypeio.anytype.core_utils.ext.dimen
import com.anytypeio.anytype.core_utils.ext.setDrawableColor
import com.anytypeio.anytype.core_models.ThemeColor
import com.anytypeio.anytype.core_ui.extensions.color
import com.anytypeio.anytype.presentation.editor.editor.listener.ListenerType
import com.anytypeio.anytype.presentation.editor.editor.model.BlockView
import com.anytypeio.anytype.presentation.objects.ObjectIcon
import com.anytypeio.anytype.presentation.relations.DocumentRelationView
import com.anytypeio.anytype.presentation.sets.model.ObjectView
@ -249,6 +251,50 @@ class FeaturedRelationGroupWidget : ConstraintLayout {
addView(view)
ids.add(view.id)
}
is DocumentRelationView.Source -> {
relation.sources.forEach { obj ->
val view = ObjectIconTextWidget(context).apply {
id = generateViewId()
when (obj) {
is ObjectView.Default -> {
setTextColor(context.color(R.color.text_secondary))
setTextSize(context.dimen(R.dimen.featured_relations_text_size))
setup(
name = obj.name,
icon = obj.icon
)
}
is ObjectView.Deleted -> {
setTextColor(context.color(R.color.glyph_active))
setTextSize(context.dimen(R.dimen.featured_relations_text_size))
setup(
name = context.getString(R.string.deleted),
icon = ObjectIcon.None
)
}
}
}
view.setOnClickListener {
click(
ListenerType.Relation.SetSource(sources = relation.sources)
)
}
addView(view)
ids.add(view.id)
}
if (relation.sources.isEmpty()) {
val placeholder =
buildPlaceholderView(resources.getString(R.string.source)).apply {
setOnClickListener {
click(
ListenerType.Relation.SetSource(sources = emptyList())
)
}
}
addView(placeholder)
ids.add(placeholder.id)
}
}
}
if (index != item.relations.lastIndex) {

View file

@ -4,14 +4,15 @@ import android.content.Context
import android.util.AttributeSet
import android.util.TypedValue
import android.view.LayoutInflater
import android.widget.FrameLayout
import androidx.constraintlayout.widget.ConstraintLayout
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.databinding.WidgetObjectIconTextBinding
import com.anytypeio.anytype.core_utils.ext.gone
import com.anytypeio.anytype.presentation.objects.ObjectIcon
class ObjectIconTextWidget @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {
) : ConstraintLayout(context, attrs, defStyleAttr) {
val binding = WidgetObjectIconTextBinding.inflate(
LayoutInflater.from(context), this, true
@ -22,23 +23,33 @@ class ObjectIconTextWidget @JvmOverloads constructor(
}
private fun setupAttributeValues(set: AttributeSet?) {
if (set == null) return
val attrs = context.obtainStyledAttributes(set, R.styleable.ObjectIconTextWidget, 0, 0)
val nameTextSize = attrs.getDimensionPixelSize(R.styleable.ObjectIconTextWidget_nameTextSize, 0)
if (nameTextSize > 0) {
binding.objectName.setTextSize(TypedValue.COMPLEX_UNIT_PX, nameTextSize.toFloat())
setTextSize(nameTextSize.toFloat())
}
val nameTextColor = attrs.getColor(R.styleable.ObjectIconTextWidget_nameTextColor, 0)
binding.objectName.setTextColor(nameTextColor)
setTextColor(nameTextColor)
attrs.recycle()
}
fun setTextSize(textSize: Float) {
binding.objectName.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
}
fun setTextColor(textColor: Int) {
binding.objectName.setTextColor(textColor)
}
fun setup(name: String?, icon: ObjectIcon) {
binding.objectName.text = name
binding.objectIcon.setIcon(icon)
if (icon is ObjectIcon.None) {
binding.objectIcon.gone()
}
}
}

View file

@ -0,0 +1,8 @@
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="@color/amber_100" />
<corners android:radius="@dimen/dp_6" />
</shape>
</item>
</layer-list>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="14dp"
android:height="14dp"
android:viewportWidth="14"
android:viewportHeight="14">
<path
android:pathData="M13.633,1.403L6.131,13.191L0.47,7.53L1.53,6.47L5.869,10.809L12.367,0.597L13.633,1.403Z"
android:fillColor="@color/glyph_selected"
android:fillType="evenOdd"/>
</vector>

View file

@ -4,19 +4,7 @@
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M4,6L20,6A1,1 0,0 1,21 7L21,7A1,1 0,0 1,20 8L4,8A1,1 0,0 1,3 7L3,7A1,1 0,0 1,4 6z"
android:fillColor="@color/glyph_active" />
<path
android:pathData="M17.25,7C17.25,8.1046 16.3546,9 15.25,9C14.1454,9 13.25,8.1046 13.25,7C13.25,5.8954 14.1454,5 15.25,5C16.3546,5 17.25,5.8954 17.25,7Z"
android:strokeWidth="2"
android:fillColor="@color/background_primary"
android:strokeColor="@color/glyph_active" />
<path
android:pathData="M4,16L20,16A1,1 0,0 1,21 17L21,17A1,1 0,0 1,20 18L4,18A1,1 0,0 1,3 17L3,17A1,1 0,0 1,4 16z"
android:fillColor="@color/glyph_active" />
<path
android:pathData="M9.25,17m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"
android:strokeWidth="2"
android:fillColor="@color/background_primary"
android:strokeColor="@color/glyph_active" />
android:fillColor="@color/glyph_active"
android:fillType="evenOdd"
android:pathData="M16.5,7C16.5,7.828 15.828,8.5 15,8.5C14.172,8.5 13.5,7.828 13.5,7C13.5,6.172 14.172,5.5 15,5.5C15.828,5.5 16.5,6.172 16.5,7ZM20.25,6.25H17.906C17.573,4.956 16.398,4 15,4C13.602,4 12.427,4.956 12.094,6.25H3.75C3.336,6.25 3,6.586 3,7C3,7.414 3.336,7.75 3.75,7.75H12.094C12.427,9.044 13.602,10 15,10C16.398,10 17.573,9.044 17.906,7.75H20.25C20.664,7.75 21,7.414 21,7C21,6.586 20.664,6.25 20.25,6.25ZM7.5,17C7.5,17.828 8.172,18.5 9,18.5C9.828,18.5 10.5,17.828 10.5,17C10.5,16.172 9.828,15.5 9,15.5C8.172,15.5 7.5,16.172 7.5,17ZM6.095,17.75C6.428,19.044 7.602,20 9,20C10.398,20 11.573,19.044 11.906,17.75H20.25C20.664,17.75 21,17.414 21,17C21,16.586 20.664,16.25 20.25,16.25H11.906C11.573,14.956 10.398,14 9,14C7.602,14 6.428,14.956 6.095,16.25H3.75C3.336,16.25 3,16.586 3,17C3,17.414 3.336,17.75 3.75,17.75H6.095Z" />
</vector>

View file

@ -1,12 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="17dp"
android:viewportWidth="16"
android:viewportHeight="17">
<path
android:fillColor="#FFFFFF"
android:pathData="M7,0.75h2v16h-2z" />
<path
android:fillColor="#FFFFFF"
android:pathData="M16,7.75l-0,2l-16,0l-0,-2z" />
</vector>

View file

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="match_parent">
<TextView
android:layout_gravity="center_vertical"
tools:text="Anytype"
android:id="@+id/tvFeaturedRelationValue"
android:textColor="@color/text_secondary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</FrameLayout>

View file

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/featuredRelationStatusContainer"
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="match_parent"/>

View file

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/featuredRelationTagContainer"
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="match_parent"/>

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.anytypeio.anytype.core_ui.widgets.ObjectIconWidget
android:id="@+id/ivIcon"
@ -26,13 +26,15 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:hint="@string/untitled"
android:textColorHint="@color/text_tertiary"
app:layout_constraintBottom_toTopOf="@+id/tvSubtitle"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintEnd_toStartOf="@+id/ic_selected"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/ivIcon"
app:layout_constraintTop_toTopOf="@+id/ivIcon"
app:layout_goneMarginEnd="0dp"
tools:text="File" />
<TextView
@ -41,14 +43,16 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:ellipsize="end"
android:maxLines="1"
android:singleLine="true"
app:layout_constraintBottom_toBottomOf="@+id/ivIcon"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintEnd_toStartOf="@+id/ic_selected"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/ivIcon"
app:layout_constraintTop_toBottomOf="@+id/tvTitle"
app:layout_goneMarginEnd="0dp"
tools:text="Just start writing with a plain text" />
<View
@ -61,4 +65,14 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<ImageView
android:id="@+id/ic_selected"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_check_black_14"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -7,19 +7,26 @@
<com.anytypeio.anytype.core_ui.widgets.ObjectIconWidget
android:id="@+id/objectIcon"
style="@style/ObjectIcon15Style"
android:layout_width="18dp"
android:layout_height="18dp"
style="@style/ObjectIcon15Style"
android:layout_gravity="center_vertical" />
android:layout_gravity="center_vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
<TextView
android:id="@+id/objectName"
style="@style/GridCellDateTextStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:gravity="center_vertical"
android:layout_marginStart="4dp"
android:textColor="@color/black"
app:layout_goneMarginStart="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/objectIcon"
app:layout_constraintTop_toTopOf="parent"
tools:text="Feature" />
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -180,6 +180,7 @@
<color name="dashboard_tab_color">#CC0066C3</color>
<color name="amber_80">#FFC532</color>
<color name="amber_100">#FFB522</color>
<color name="table_row_header_background">#1A50491C</color>
<color name="object_loading_state_gradient_start_color">@color/shape_tertiary</color>
<color name="object_loading_state_gradient_end_color">#F1F0ED</color>

View file

@ -301,4 +301,6 @@
<dimen name="block_text_markup_underline_height">0.5dp</dimen>
<dimen name="featured_relations_text_size">13sp</dimen>
</resources>

View file

@ -443,6 +443,8 @@
<string name="select_tags">Select tags</string>
<string name="select_status">Select status</string>
<string name="select_objects">Select objects</string>
<string name="source">Source</string>
<string name="select_source">Select source</string>
<string name="objects">Objects</string>
<string name="web_pages">Web pages</string>
<string name="select_files">Select files</string>

View file

@ -629,4 +629,12 @@ class BlockDataRepository(
override suspend fun objectToSet(ctx: Id, source: List<String>): Id {
return remote.objectToSet(ctx, source)
}
override suspend fun blockDataViewSetSource(
ctx: Id,
block: Id,
sources: List<Id>
): Payload {
return remote.blockDataViewSetSource(ctx, block, sources)
}
}

View file

@ -255,4 +255,6 @@ interface BlockDataStore {
suspend fun fillTableRow(ctx: String, targetIds: List<String>): Payload
suspend fun objectToSet(ctx: Id, source: List<String>): Id
suspend fun blockDataViewSetSource(ctx: Id, block: Id, sources: List<String>): Payload
}

View file

@ -254,4 +254,6 @@ interface BlockRemote {
suspend fun fillTableRow(ctx: String, targetIds: List<String>): Payload
suspend fun objectToSet(ctx: Id, source: List<String>): Id
suspend fun blockDataViewSetSource(ctx: Id, block: Id, sources: List<String>): Payload
}

View file

@ -545,4 +545,12 @@ class BlockRemoteDataStore(private val remote: BlockRemote) : BlockDataStore {
override suspend fun objectToSet(ctx: Id, source: List<String>): Id {
return remote.objectToSet(ctx, source)
}
override suspend fun blockDataViewSetSource(
ctx: Id,
block: Id,
sources: List<Id>
): Payload {
return remote.blockDataViewSetSource(ctx, block, sources)
}
}

View file

@ -318,4 +318,6 @@ interface BlockRepository {
suspend fun fillTableRow(ctx: String, targetIds: List<String>): Payload
suspend fun objectToSet(ctx: Id, source: List<String>): Id
suspend fun blockDataViewSetSource(ctx: Id, block: Id, sources: List<String>): Payload
}

View file

@ -0,0 +1,28 @@
package com.anytypeio.anytype.domain.dataview
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.domain.base.BaseUseCase
import com.anytypeio.anytype.domain.base.Either
import com.anytypeio.anytype.domain.block.repo.BlockRepository
class SetDataViewSource(private val repo: BlockRepository) :
BaseUseCase<Payload, SetDataViewSource.Params>() {
override suspend fun run(params: Params): Either<Throwable, Payload> = safe {
repo.blockDataViewSetSource(
ctx = params.ctx,
block = DEFAULT_DATA_VIEW_BLOCK_ID,
sources = params.sources
)
}
companion object {
const val DEFAULT_DATA_VIEW_BLOCK_ID = "dataview"
}
data class Params(
val ctx: Id,
val sources: List<Id>
)
}

View file

@ -584,4 +584,12 @@ class BlockMiddleware(
override suspend fun objectToSet(ctx: Id, source: List<String>): Id {
return middleware.objectToSet(ctx, source)
}
override suspend fun blockDataViewSetSource(
ctx: Id,
block: Id,
sources: List<Id>
): Payload {
return middleware.blockDataViewSetSource(ctx, block, sources)
}
}

View file

@ -1595,7 +1595,8 @@ class Middleware(
if (BuildConfig.DEBUG) logRequest(request)
val response = service.objectShow(request)
if (BuildConfig.DEBUG) logResponse(response)
return response.objectView?.toPayload() ?: throw IllegalStateException("Object view was null")
return response.objectView?.toPayload()
?: throw IllegalStateException("Object view was null")
}
@Throws(Exception::class)
@ -1722,17 +1723,34 @@ class Middleware(
}
@Throws(Exception::class)
fun objectToSet(ctx: String, source: List<String>) : String {
fun objectToSet(ctx: String, source: List<String>): String {
val request = Rpc.Object.ToSet.Request(
contextId = ctx,
source = source
)
if (BuildConfig.DEBUG) logRequest(request)
val response = service.objectToSet(request)
if(BuildConfig.DEBUG) logResponse(response)
if (BuildConfig.DEBUG) logResponse(response)
return response.setId
}
@Throws(Exception::class)
fun blockDataViewSetSource(
ctx: Id,
blockId: Id,
sources: List<Id>
): Payload {
val request = Rpc.BlockDataview.SetSource.Request(
contextId = ctx,
blockId = blockId,
source = sources
)
if (BuildConfig.DEBUG) logRequest(request)
val response = service.blockDataViewSetSource(request)
if (BuildConfig.DEBUG) logResponse(response)
return response.event.toPayload()
}
private fun logRequest(any: Any) {
val message = "===> " + any::class.java.canonicalName + ":" + "\n" + any.toString()
Timber.d(message)

View file

@ -277,6 +277,9 @@ interface MiddlewareService {
@Throws(Exception::class)
fun blockDataViewRelationDelete(request: Rpc.BlockDataview.Relation.Delete.Request): Rpc.BlockDataview.Relation.Delete.Response
@Throws(Exception::class)
fun blockDataViewSetSource(request: Rpc.BlockDataview.SetSource.Request): Rpc.BlockDataview.SetSource.Response
//endregion
//region TEXT BLOCK commands

View file

@ -1103,4 +1103,16 @@ class MiddlewareServiceImplementation : MiddlewareService {
return response
}
}
override fun blockDataViewSetSource(request: Rpc.BlockDataview.SetSource.Request): Rpc.BlockDataview.SetSource.Response {
val encoded =
Service.blockDataviewSetSource(Rpc.BlockDataview.SetSource.Request.ADAPTER.encode(request))
val response = Rpc.BlockDataview.SetSource.Response.ADAPTER.decode(encoded)
val error = response.error
if (error != null && error.code != Rpc.BlockDataview.SetSource.Response.Error.Code.NULL) {
throw Exception(error.description)
} else {
return response
}
}
}

View file

@ -4,6 +4,7 @@ import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.presentation.editor.editor.BlockDimensions
import com.anytypeio.anytype.presentation.editor.editor.model.BlockView
import com.anytypeio.anytype.presentation.relations.DocumentRelationView
import com.anytypeio.anytype.presentation.sets.model.ObjectView
sealed interface ListenerType {
@ -65,6 +66,7 @@ sealed interface ListenerType {
data class ChangeObjectType(val type: String) : Relation()
data class ObjectTypeOpenSet(val type: String) : Relation()
data class Featured(val relation: DocumentRelationView) : Relation()
data class SetSource(val sources: List<ObjectView>) : Relation()
}
data class TableOfContentsItem(val target: Id, val item: Id) : ListenerType

View file

@ -47,6 +47,7 @@ import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_RELATION_FILE
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_RELATION_OBJECT
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_RELATION_PLACEHOLDER
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_RELATION_SOURCE
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_RELATION_STATUS
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_RELATION_TAGS
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_TABLE
@ -1196,6 +1197,7 @@ sealed class BlockView : ViewType {
is DocumentRelationView.Object -> HOLDER_RELATION_OBJECT
is DocumentRelationView.File -> HOLDER_RELATION_FILE
is DocumentRelationView.ObjectType -> HOLDER_OBJECT_TYPE
is DocumentRelationView.Source -> HOLDER_RELATION_SOURCE
}
}
}

View file

@ -53,6 +53,7 @@ object Types {
const val HOLDER_RELATION_OBJECT = 43
const val HOLDER_RELATION_FILE = 44
const val HOLDER_RELATION_CHECKBOX = 45
const val HOLDER_RELATION_SOURCE = 58
const val HOLDER_DESCRIPTION = 46
const val HOLDER_FEATURED_RELATION = 47

View file

@ -760,14 +760,16 @@ fun Relation.Format.toView() = when (this) {
Relation.Format.RELATIONS -> ColumnView.Format.RELATIONS
}
fun List<ObjectType>.toObjectTypeView(): List<ObjectTypeView.Item> = map { oType ->
ObjectTypeView.Item(
id = oType.url,
name = oType.name,
emoji = oType.emoji,
description = oType.description
)
}
fun List<ObjectType>.toObjectTypeView(selectedSources: List<Id> = emptyList()): List<ObjectTypeView.Item> =
map { oType ->
ObjectTypeView.Item(
id = oType.url,
name = oType.name,
emoji = oType.emoji,
description = oType.description,
isSelected = selectedSources.contains(oType.url)
)
}
fun List<ObjectType.Layout>.toView(): List<ObjectLayoutView> = map { layout ->
when (layout) {

View file

@ -32,7 +32,8 @@ class ObjectTypeChangeViewModel(
fun onStart(
smartBlockType: SmartBlockType,
excludedTypes: List<Id> = emptyList(),
isDraft: Boolean
isDraft: Boolean,
selectedSources: List<Id>
) {
viewModelScope.launch {
getCompatibleObjectTypes.invoke(
@ -44,17 +45,25 @@ class ObjectTypeChangeViewModel(
failure = { Timber.e(it, "Error while getting object types") },
success = { types ->
if (excludedTypes.isEmpty()) {
setViews(objectTypes = types)
setViews(
objectTypes = types,
selectedSources = selectedSources
)
} else {
setViews(objectTypes = types.filter { t -> !excludedTypes.contains(t.url) })
setViews(
objectTypes = types.filter { !excludedTypes.contains(it.url) },
selectedSources = selectedSources
)
}
}
)
}
}
private fun setViews(objectTypes: List<ObjectType>) {
views.value = objectTypes.toObjectTypeView()
private fun setViews(objectTypes: List<ObjectType>, selectedSources: List<Id>) {
views.value = objectTypes
.toObjectTypeView(selectedSources = selectedSources)
.sortedBy { !selectedSources.contains(it.id) }
}
fun onQueryChanged(input: String) {

View file

@ -9,7 +9,8 @@ sealed class ObjectTypeView {
val id: String,
val name: String,
val description: String?,
val emoji: String?
val emoji: String?,
val isSelected: Boolean = false
) : ObjectTypeView()
object Search : ObjectTypeView()

View file

@ -15,6 +15,7 @@ import com.anytypeio.anytype.presentation.sets.model.FileView
import com.anytypeio.anytype.presentation.sets.model.ObjectView
import com.anytypeio.anytype.presentation.sets.model.StatusView
import com.anytypeio.anytype.presentation.sets.model.TagView
import com.anytypeio.anytype.presentation.sets.toObjectView
import timber.log.Timber
fun ObjectWrapper.Basic.values(
@ -261,22 +262,7 @@ fun ObjectWrapper.Basic.objects(
ids.forEach { id ->
val wrapper = ObjectWrapper.Basic(details[id]?.map ?: return@forEach)
if (wrapper.isDeleted == true) {
result.add(ObjectView.Deleted(id = id))
} else {
result.add(
ObjectView.Default(
id = id,
name = wrapper.getProperName(),
icon = ObjectIcon.from(
obj = wrapper,
layout = wrapper.layout,
builder = urlBuilder
),
types = wrapper.type
)
)
}
result.add(wrapper.toObjectView(urlBuilder))
}
return result
}

View file

@ -58,6 +58,14 @@ sealed class DocumentRelationView : DefaultObjectDiffIdentifier {
val objects: List<ObjectView>
) : DocumentRelationView()
data class Source(
override val relationId: Id,
override val name: String,
override val value: String? = null,
override val isFeatured: Boolean = false,
val sources: List<ObjectView>
) : DocumentRelationView()
data class File(
override val relationId: Id,
override val name: String,

View file

@ -59,6 +59,7 @@ import com.anytypeio.anytype.presentation.sets.model.StatusView
import com.anytypeio.anytype.presentation.sets.model.TagView
import com.anytypeio.anytype.presentation.sets.model.Viewer
import com.anytypeio.anytype.presentation.sets.model.ViewerTabView
import com.anytypeio.anytype.presentation.sets.toObjectView
import timber.log.Timber
@ -517,21 +518,8 @@ fun Relation.toObjects(
val list = arrayListOf<ObjectView>()
ids.forEach { id ->
val wrapper = ObjectWrapper.Basic(details[id]?.map ?: emptyMap())
if (wrapper.isDeleted == true) {
list.add(ObjectView.Deleted(id = id))
} else {
list.add(
ObjectView.Default(
id = id,
name = wrapper.getProperName(),
icon = ObjectIcon.from(
obj = wrapper,
layout = wrapper.layout,
builder = urlBuilder
),
types = wrapper.type
)
)
if (!wrapper.isEmpty()) {
list.add(wrapper.toObjectView(urlBuilder))
}
}
list

View file

@ -1,6 +1,7 @@
package com.anytypeio.anytype.presentation.sets
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.SmartBlockType
sealed class ObjectSetCommand {
@ -77,6 +78,11 @@ sealed class ObjectSetCommand {
data class OpenCoverActionMenu(
val ctx: Id
) : Modal()
data class OpenSelectSourceScreen(
val smartBlockType: SmartBlockType,
val sources: List<Id>
) : Modal()
}
sealed class Intent : ObjectSetCommand() {

View file

@ -3,9 +3,12 @@ package com.anytypeio.anytype.presentation.sets
import com.anytypeio.anytype.core_models.*
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.presentation.editor.editor.model.BlockView
import com.anytypeio.anytype.presentation.objects.ObjectIcon
import com.anytypeio.anytype.presentation.objects.getProperName
import com.anytypeio.anytype.presentation.relations.DocumentRelationView
import com.anytypeio.anytype.presentation.relations.ObjectSetConfig.ID_KEY
import com.anytypeio.anytype.presentation.relations.view
import com.anytypeio.anytype.presentation.sets.model.ObjectView
import com.anytypeio.anytype.presentation.sets.model.SimpleRelationView
fun ObjectSet.updateRecord(
@ -27,7 +30,7 @@ fun ObjectSet.updateRecord(
fun ObjectSet.featuredRelations(
ctx: Id,
urlBuilder: UrlBuilder
) : BlockView.FeaturedRelation? {
): BlockView.FeaturedRelation? {
val block = blocks.find { it.content is Block.Content.FeaturedRelations }
if (block != null) {
val views = mutableListOf<DocumentRelationView>()
@ -72,6 +75,22 @@ private fun mapFeaturedRelations(
null
}
}
Relations.SET_OF -> {
val objectSet = ObjectWrapper.Basic(details.details[ctx]?.map.orEmpty())
val sources = mutableListOf<ObjectView>()
objectSet.setOf.forEach { objectTypeId ->
val wrapper = ObjectWrapper.Basic(details.details[objectTypeId]?.map.orEmpty())
if (!wrapper.isEmpty()) {
sources.add(wrapper.toObjectView(urlBuilder = urlBuilder))
}
}
DocumentRelationView.Source(
relationId = id,
name = Relations.RELATION_NAME_EMPTY,
isFeatured = true,
sources = sources
)
}
else -> {
val relation = relations.firstOrNull { it.key == id }
relation?.view(
@ -121,4 +140,18 @@ fun DV.getRelation(relationKey: Id): Relation? = relations.firstOrNull { it.key
fun DV.isRelationReadOnly(relationKey: Id): Boolean {
val relation = getRelation(relationKey)
return relation != null && relation.isReadOnly
}
fun ObjectWrapper.Basic.toObjectView(urlBuilder: UrlBuilder): ObjectView = when (isDeleted) {
true -> ObjectView.Deleted(id)
else -> ObjectView.Default(
id = id,
name = getProperName(),
icon = ObjectIcon.from(
obj = this,
layout = layout,
builder = urlBuilder
),
types = type
)
}

View file

@ -5,6 +5,7 @@ import com.anytypeio.anytype.core_models.Event
import com.anytypeio.anytype.core_models.Event.Command
import com.anytypeio.anytype.core_models.ext.amend
import com.anytypeio.anytype.core_models.ext.unset
import com.anytypeio.anytype.core_utils.ext.replace
import com.anytypeio.anytype.presentation.extension.updateFields
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.MutableSharedFlow
@ -176,6 +177,19 @@ class ObjectSetReducer {
)
)
}
is Command.UpdateStructure -> {
state.copy(
blocks = state.blocks.replace(
replacement = { target -> target.copy(children = event.children) },
target = { block -> block.id == event.id }
)
)
}
is Command.AddBlock -> {
state.copy(
blocks = state.blocks + event.blocks
)
}
else -> {
Timber.d("Ignoring event: $event")
state.copy()

View file

@ -19,6 +19,7 @@ import com.anytypeio.anytype.core_models.ObjectWrapper
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.core_models.Relation
import com.anytypeio.anytype.core_models.Relations
import com.anytypeio.anytype.core_models.SmartBlockType
import com.anytypeio.anytype.core_models.SyncStatus
import com.anytypeio.anytype.core_models.ext.content
import com.anytypeio.anytype.core_models.ext.title
@ -27,6 +28,7 @@ import com.anytypeio.anytype.core_utils.common.EventWrapper
import com.anytypeio.anytype.domain.base.Result
import com.anytypeio.anytype.domain.block.interactor.UpdateText
import com.anytypeio.anytype.domain.cover.SetDocCoverImage
import com.anytypeio.anytype.domain.dataview.SetDataViewSource
import com.anytypeio.anytype.domain.dataview.interactor.AddNewRelationToDataView
import com.anytypeio.anytype.domain.dataview.interactor.CreateDataViewRecord
import com.anytypeio.anytype.domain.dataview.interactor.SetActiveViewer
@ -44,6 +46,7 @@ import com.anytypeio.anytype.domain.unsplash.DownloadUnsplashImage
import com.anytypeio.anytype.presentation.common.Action
import com.anytypeio.anytype.presentation.common.Delegator
import com.anytypeio.anytype.presentation.editor.editor.Proxy
import com.anytypeio.anytype.presentation.editor.editor.listener.ListenerType
import com.anytypeio.anytype.presentation.editor.editor.model.BlockView
import com.anytypeio.anytype.presentation.editor.model.TextUpdate
import com.anytypeio.anytype.presentation.extension.sendAnalyticsObjectCreateEvent
@ -100,7 +103,8 @@ class ObjectSetViewModel(
private val session: ObjectSetSession,
private val analytics: Analytics,
private val getTemplates: GetTemplates,
private val createNewObject: CreateNewObject
private val createNewObject: CreateNewObject,
private val setDataViewSource: SetDataViewSource
) : ViewModel(), SupportNavigation<EventWrapper<AppNavigation.Command>> {
val status = MutableStateFlow(SyncStatus.UNKNOWN)
@ -1028,7 +1032,12 @@ class ObjectSetViewModel(
error.value = DATA_VIEW_NOT_FOUND_ERROR
}
} else {
error.value = OBJECT_SET_HAS_EMPTY_SOURCE_ERROR
dispatch(
ObjectSetCommand.Modal.OpenSelectSourceScreen(
sources = emptyList(),
smartBlockType = SmartBlockType.PAGE
)
)
}
}
@ -1195,11 +1204,39 @@ class ObjectSetViewModel(
}
}
fun onClickListener(clicked: ListenerType) {
Timber.d("onClickListener, clicked:[$clicked]")
when (clicked) {
is ListenerType.Relation.SetSource -> {
val sources = clicked.sources.map { it.id }
dispatch(
ObjectSetCommand.Modal.OpenSelectSourceScreen(
sources = sources,
smartBlockType = SmartBlockType.PAGE
)
)
}
else -> {}
}
}
fun onDataViewSourcePicked(source: Id) {
viewModelScope.launch {
val params = SetDataViewSource.Params(
ctx = context,
sources = listOf(source)
)
setDataViewSource(params).proceed(
failure = { e -> Timber.e(e, "Error while setting Set source") },
success = { payload -> defaultPayloadConsumer(payload) }
)
}
}
companion object {
const val TITLE_CHANNEL_DISPATCH_DELAY = 300L
const val NOT_ALLOWED = "Not allowed for this set"
const val NOT_ALLOWED_CELL = "Not allowed for this cell"
const val OBJECT_TYPE_UNKNOWN = "Can't open object, object type unknown"
const val DATA_VIEW_HAS_NO_VIEW_MSG = "Data view has no view."
const val DATA_VIEW_NOT_FOUND_ERROR =
"Content missing for this set. Please, try again later."

View file

@ -6,6 +6,7 @@ import com.anytypeio.anytype.analytics.base.Analytics
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.domain.block.interactor.UpdateText
import com.anytypeio.anytype.domain.cover.SetDocCoverImage
import com.anytypeio.anytype.domain.dataview.SetDataViewSource
import com.anytypeio.anytype.domain.dataview.interactor.AddNewRelationToDataView
import com.anytypeio.anytype.domain.dataview.interactor.CreateDataViewRecord
import com.anytypeio.anytype.domain.dataview.interactor.SetActiveViewer
@ -44,7 +45,8 @@ class ObjectSetViewModelFactory(
private val session: ObjectSetSession,
private val analytics: Analytics,
private val getTemplates: GetTemplates,
private val createNewObject: CreateNewObject
private val createNewObject: CreateNewObject,
private val setDataViewSource: SetDataViewSource
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
@ -69,7 +71,8 @@ class ObjectSetViewModelFactory(
session = session,
analytics = analytics,
getTemplates = getTemplates,
createNewObject = createNewObject
createNewObject = createNewObject,
setDataViewSource = setDataViewSource
) as T
}
}

View file

@ -223,40 +223,14 @@ fun Map<String, Any?>.buildObjectViews(
val value = this.getOrDefault(columnKey, null)
if (value is Id) {
val wrapper = ObjectWrapper.Basic(details[value]?.map ?: emptyMap())
if (wrapper.isDeleted == true) {
objects.add(ObjectView.Deleted(id = value))
} else {
objects.add(
ObjectView.Default(
id = value,
name = wrapper.getProperName(),
icon = ObjectIcon.from(
obj = wrapper,
layout = wrapper.layout,
builder = builder
),
types = wrapper.type
)
)
if (!wrapper.isEmpty()) {
objects.add(wrapper.toObjectView(urlBuilder = builder))
}
} else if (value is List<*>) {
value.typeOf<Id>().forEach { id ->
val wrapper = ObjectWrapper.Basic(details[id]?.map ?: emptyMap())
if (wrapper.isDeleted == true) {
objects.add(ObjectView.Deleted(id = id))
} else {
objects.add(
ObjectView.Default(
id = id,
name = wrapper.getProperName(),
icon = ObjectIcon.from(
obj = wrapper,
layout = wrapper.layout,
builder = builder
),
types = wrapper.type
)
)
if (!wrapper.isEmpty()) {
objects.add(wrapper.toObjectView(urlBuilder = builder))
}
}
}

View file

@ -15,6 +15,7 @@ import com.anytypeio.anytype.domain.base.Result
import com.anytypeio.anytype.domain.block.interactor.UpdateText
import com.anytypeio.anytype.domain.config.Gateway
import com.anytypeio.anytype.domain.cover.SetDocCoverImage
import com.anytypeio.anytype.domain.dataview.SetDataViewSource
import com.anytypeio.anytype.domain.dataview.interactor.AddNewRelationToDataView
import com.anytypeio.anytype.domain.dataview.interactor.CreateDataViewRecord
import com.anytypeio.anytype.domain.dataview.interactor.SetActiveViewer
@ -96,6 +97,9 @@ open class ObjectSetViewModelTestSetup {
@Mock
lateinit var createNewObject: CreateNewObject
@Mock
lateinit var setDataViewSource: SetDataViewSource
val dispatcher = Dispatcher.Default<Payload>()
val delegator = Delegator.Default<Action>()
val reducer = ObjectSetReducer()
@ -126,7 +130,8 @@ open class ObjectSetViewModelTestSetup {
downloadUnsplashImage = downloadUnsplashImage,
setDocCoverImage = setDocCoverImage,
getTemplates = getTemplates,
createNewObject = createNewObject
createNewObject = createNewObject,
setDataViewSource = setDataViewSource
)
fun stubInterceptEvents(

View file

@ -45,36 +45,6 @@ class ObjectSetZeroDataViewTest : ObjectSetViewModelTestSetup() {
MockitoAnnotations.openMocks(this)
}
@Test
fun `should show empty-source error`() {
// SETUP
stubInterceptEvents()
stubOpenObjectSet(
doc = listOf(
header,
title
)
)
val vm = givenViewModel()
// TESTING
assertEquals(
expected = null,
actual = vm.error.value
)
vm.onStart(root)
assertEquals(
expected = ObjectSetViewModel.OBJECT_SET_HAS_EMPTY_SOURCE_ERROR,
actual = vm.error.value
)
}
@Test
fun `should show no-content error because of missing dv`() {