From 7b373339f0a58278d72f9fb8c77421053acca0a6 Mon Sep 17 00:00:00 2001 From: Allan Quatermain <122483431+quaterma1n@users.noreply.github.com> Date: Thu, 2 Feb 2023 13:47:37 +0300 Subject: [PATCH] DROID-824 Library | Enhancement | Added reactive changes on library support (#2883) DROID-824 Library | Enhancement | Added reactive changes on library support --- .../anytype/di/feature/library/LibraryDI.kt | 44 ++-- .../ui/library/views/LibraryTabsContent.kt | 8 +- .../views/list/LibraryListSearchWidget.kt | 6 +- .../views/list/LibraryListTabsContent.kt | 79 +++++--- .../ui/library/views/list/items/Items.kt | 191 ++++++++++++------ .../main/res/drawable/ic_type_installed.xml | 14 ++ .../res/drawable/ic_type_not_installed.xml | 17 ++ .../core_ui/extensions/ResExtension.kt | 16 ++ .../widgets/RelationFormatIconWidget.kt | 20 +- domain/build.gradle | 2 + ...rySearchParams.kt => StoreSearchParams.kt} | 2 +- .../library/StorelessSubscriptionContainer.kt | 105 ++++++++++ .../library/processors/EventAddProcessor.kt | 26 +++ .../library/processors/EventAmendProcessor.kt | 35 ++++ .../processors/EventPositionProcessor.kt | 25 +++ .../processors/EventRemoveProcessor.kt | 18 ++ .../library/processors/EventSetProcessor.kt | 33 +++ .../library/processors/EventUnsetProcessor.kt | 24 +++ .../processors/SubscriptionEventProcessor.kt | 11 + .../presentation/library/LibraryInteractor.kt | 42 ---- .../library/LibraryListDelegate.kt | 3 +- .../presentation/library/LibraryViewModel.kt | 52 ++++- .../delegates/LibraryRelationsDelegate.kt | 23 ++- .../library/delegates/LibraryTypesDelegate.kt | 31 ++- .../library/delegates/MyRelationsDelegate.kt | 29 ++- .../library/delegates/MyTypesDelegate.kt | 36 +++- .../presentation/navigation/ObjectView.kt | 52 ++++- .../objects/ObjectWrapperMapper.kt | 74 +++++-- .../search/ObjectSearchConstants.kt | 5 - 29 files changed, 792 insertions(+), 231 deletions(-) create mode 100644 app/src/main/res/drawable/ic_type_installed.xml create mode 100644 app/src/main/res/drawable/ic_type_not_installed.xml rename domain/src/main/java/com/anytypeio/anytype/domain/library/{LibrarySearchParams.kt => StoreSearchParams.kt} (94%) create mode 100644 domain/src/main/java/com/anytypeio/anytype/domain/library/StorelessSubscriptionContainer.kt create mode 100644 domain/src/main/java/com/anytypeio/anytype/domain/library/processors/EventAddProcessor.kt create mode 100644 domain/src/main/java/com/anytypeio/anytype/domain/library/processors/EventAmendProcessor.kt create mode 100644 domain/src/main/java/com/anytypeio/anytype/domain/library/processors/EventPositionProcessor.kt create mode 100644 domain/src/main/java/com/anytypeio/anytype/domain/library/processors/EventRemoveProcessor.kt create mode 100644 domain/src/main/java/com/anytypeio/anytype/domain/library/processors/EventSetProcessor.kt create mode 100644 domain/src/main/java/com/anytypeio/anytype/domain/library/processors/EventUnsetProcessor.kt create mode 100644 domain/src/main/java/com/anytypeio/anytype/domain/library/processors/SubscriptionEventProcessor.kt delete mode 100644 presentation/src/main/java/com/anytypeio/anytype/presentation/library/LibraryInteractor.kt diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/library/LibraryDI.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/library/LibraryDI.kt index 92311eb545..374972b838 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/library/LibraryDI.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/library/LibraryDI.kt @@ -3,10 +3,12 @@ package com.anytypeio.anytype.di.feature.library import androidx.lifecycle.ViewModelProvider import com.anytypeio.anytype.core_utils.di.scope.PerScreen import com.anytypeio.anytype.di.common.ComponentDependencies +import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers import com.anytypeio.anytype.domain.block.repo.BlockRepository +import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer import com.anytypeio.anytype.domain.misc.UrlBuilder +import com.anytypeio.anytype.domain.search.SubscriptionEventChannel import com.anytypeio.anytype.domain.workspace.WorkspaceManager -import com.anytypeio.anytype.presentation.library.LibraryInteractor import com.anytypeio.anytype.presentation.library.LibraryListDelegate import com.anytypeio.anytype.presentation.library.LibraryViewModel import com.anytypeio.anytype.presentation.library.delegates.LibraryRelationsDelegate @@ -18,6 +20,7 @@ import dagger.Binds import dagger.Component import dagger.Module import dagger.Provides +import kotlinx.coroutines.Dispatchers @Component( dependencies = [LibraryDependencies::class], @@ -43,33 +46,49 @@ object LibraryModule { @PerScreen @Provides fun provideMyTypesDelegate( - interactor: LibraryInteractor, - workspaceManager: WorkspaceManager + container: StorelessSubscriptionContainer, + workspaceManager: WorkspaceManager, + urlBuilder: UrlBuilder ): LibraryListDelegate { - return MyTypesDelegate(interactor, workspaceManager) + return MyTypesDelegate(container, workspaceManager, urlBuilder) } @PerScreen @Provides - fun provideLibTypesDelegate(interactor: LibraryInteractor): LibraryListDelegate { - return LibraryTypesDelegate(interactor) + fun provideLibTypesDelegate( + container: StorelessSubscriptionContainer, + urlBuilder: UrlBuilder + ): LibraryListDelegate { + return LibraryTypesDelegate(container, urlBuilder) } @PerScreen @Provides fun provideMyRelationsDelegate( - interactor: LibraryInteractor, - workspaceManager: WorkspaceManager + container: StorelessSubscriptionContainer, + workspaceManager: WorkspaceManager, + urlBuilder: UrlBuilder ): LibraryListDelegate { - return MyRelationsDelegate(interactor, workspaceManager) + return MyRelationsDelegate(container, workspaceManager, urlBuilder) } @PerScreen @Provides - fun provideLibRelationsDelegate(interactor: LibraryInteractor): LibraryListDelegate { - return LibraryRelationsDelegate(interactor) + fun provideLibRelationsDelegate( + container: StorelessSubscriptionContainer, + urlBuilder: UrlBuilder + ): LibraryListDelegate { + return LibraryRelationsDelegate(container, urlBuilder) } + @PerScreen + @Provides + fun provideAppCoroutineDispatchers() : AppCoroutineDispatchers = AppCoroutineDispatchers( + io = Dispatchers.IO, + computation = Dispatchers.Default, + main = Dispatchers.Main + ) + @Module interface Declarations { @@ -79,7 +98,7 @@ object LibraryModule { @PerScreen @Binds - fun bindInteractor(interactor: LibraryInteractor.Impl): LibraryInteractor + fun bindContainer(container: StorelessSubscriptionContainer.Impl): StorelessSubscriptionContainer } @@ -89,4 +108,5 @@ interface LibraryDependencies : ComponentDependencies { fun blockRepository(): BlockRepository fun workspaceManager(): WorkspaceManager fun urlBuilder(): UrlBuilder + fun channel(): SubscriptionEventChannel } \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/ui/library/views/LibraryTabsContent.kt b/app/src/main/java/com/anytypeio/anytype/ui/library/views/LibraryTabsContent.kt index 6666fb9f0a..cff5b5cecc 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/library/views/LibraryTabsContent.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/library/views/LibraryTabsContent.kt @@ -76,7 +76,7 @@ fun TabContentScreen( verticalArrangement = Arrangement.Top, ) { Text( - color = colorResource(id = R.color.black), + color = colorResource(id = R.color.text_primary), text = stringResource(config.description), style = MaterialTheme.typography.h1, textAlign = TextAlign.Center, @@ -85,7 +85,9 @@ fun TabContentScreen( Box(Modifier.height(18.dp)) Button( onClick = { /*TODO*/ }, - colors = ButtonDefaults.buttonColors(backgroundColor = colorResource(id = R.color.black)), + colors = ButtonDefaults.buttonColors( + backgroundColor = colorResource(id = R.color.glyph_selected) + ), shape = RoundedCornerShape(10.dp), contentPadding = PaddingValues( 28.dp, 10.dp, 28.dp, 10.dp @@ -93,7 +95,7 @@ fun TabContentScreen( content = { Text( text = stringResource(config.mainBtnTitle), - color = colorResource(id = R.color.library_action_btn_text_color), + color = colorResource(id = R.color.text_white), fontSize = 17.sp, fontWeight = FontWeight.SemiBold ) diff --git a/app/src/main/java/com/anytypeio/anytype/ui/library/views/list/LibraryListSearchWidget.kt b/app/src/main/java/com/anytypeio/anytype/ui/library/views/list/LibraryListSearchWidget.kt index a068bbbe5e..049d33de45 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/library/views/list/LibraryListSearchWidget.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/library/views/list/LibraryListSearchWidget.kt @@ -52,13 +52,13 @@ fun LibraryListSearchWidget( }, colors = TextFieldDefaults.outlinedTextFieldColors( textColor = colorResource(id = R.color.text_primary), - backgroundColor = colorResource(id = R.color.light_grayish), + backgroundColor = colorResource(id = R.color.shape_transparent), disabledBorderColor = Color.Transparent, errorBorderColor = Color.Transparent, focusedBorderColor = Color.Transparent, unfocusedBorderColor = Color.Transparent, - placeholderColor = colorResource(id = R.color.text_tertiary), - cursorColor = colorResource(id = R.color.black) + placeholderColor = colorResource(id = R.color.glyph_active), + cursorColor = colorResource(id = R.color.text_primary) ), singleLine = true, maxLines = 1, diff --git a/app/src/main/java/com/anytypeio/anytype/ui/library/views/list/LibraryListTabsContent.kt b/app/src/main/java/com/anytypeio/anytype/ui/library/views/list/LibraryListTabsContent.kt index 50f3b7a61e..fbde9cfb5a 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/library/views/list/LibraryListTabsContent.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/library/views/list/LibraryListTabsContent.kt @@ -1,7 +1,6 @@ package com.anytypeio.anytype.ui.library.views.list import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxHeight @@ -10,17 +9,15 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material.Divider -import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.res.colorResource import androidx.compose.ui.unit.dp -import com.anytypeio.anytype.core_models.ObjectTypeIds.OBJECT_TYPE as MY_TYPE -import com.anytypeio.anytype.core_models.ObjectTypeIds.RELATION as MY_RELATION -import com.anytypeio.anytype.core_models.MarketplaceObjectTypeIds.OBJECT_TYPE as LIB_TYPE -import com.anytypeio.anytype.core_models.MarketplaceObjectTypeIds.RELATION as LIB_RELATION +import com.anytypeio.anytype.R import com.anytypeio.anytype.presentation.library.LibraryEvent import com.anytypeio.anytype.presentation.library.LibraryScreenState +import com.anytypeio.anytype.presentation.navigation.LibraryView import com.anytypeio.anytype.ui.library.LibraryListConfig import com.anytypeio.anytype.ui.library.views.list.items.ItemDefaults import com.anytypeio.anytype.ui.library.views.list.items.LibRelationItem @@ -64,29 +61,59 @@ fun LibraryListTabsContent( .fillMaxHeight(), contentPadding = PaddingValues(start = 16.dp, end = 16.dp) ) { - items(data.items.size) { index -> - val item = data.items[index] - when (item.type) { - LIB_TYPE -> { - LibTypeItem(modifier = itemModifier, item = item) + + items( + count = data.items.size, + key = { index -> + data.items[index].id + }, + itemContent = { ix -> + when (val item = data.items[ix]) { + is LibraryView.LibraryTypeView -> { + LibTypeItem( + name = item.name, + icon = item.icon, + installed = item.installed, + modifier = itemModifier + ) + } + is LibraryView.MyTypeView -> { + MyTypeItem( + name = item.name, + icon = item.icon, + readOnly = item.readOnly, + modifier = itemModifier + ) + } + is LibraryView.LibraryRelationView -> { + LibRelationItem( + modifier = itemModifier, + name = item.name, + format = item.format, + installed = item.installed + ) + } + is LibraryView.MyRelationView -> { + MyRelationItem( + modifier = itemModifier, + name = item.name, + readOnly = item.readOnly, + format = item.format + ) + } + is LibraryView.UnknownView -> { + // do nothing + } } - MY_TYPE -> { - MyTypeItem(modifier = itemModifier, item = item) - } - LIB_RELATION -> { - LibRelationItem(modifier = itemModifier, item = item) - } - MY_RELATION -> { - MyRelationItem(modifier = itemModifier, item = item) - } - else -> { - Timber.d("Unknown item type: ${item.type}") + if (ix < data.items.lastIndex) { + Divider( + thickness = 1.dp, + modifier = Modifier.padding(start = 4.dp, end = 4.dp), + color = colorResource(id = R.color.shape_primary) + ) } } - if (index < data.items.size.minus(1)) { - Divider(thickness = 1.dp, modifier = Modifier.padding(start = 4.dp, end = 4.dp)) - } - } + ) } } } diff --git a/app/src/main/java/com/anytypeio/anytype/ui/library/views/list/items/Items.kt b/app/src/main/java/com/anytypeio/anytype/ui/library/views/list/items/Items.kt index 82fda0d5d0..954dbb6798 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/library/views/list/items/Items.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/library/views/list/items/Items.kt @@ -1,92 +1,161 @@ package com.anytypeio.anytype.ui.library.views.list.items +import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.padding import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView -import com.anytypeio.anytype.core_models.ObjectWrapper +import com.anytypeio.anytype.R import com.anytypeio.anytype.core_models.RelationFormat +import com.anytypeio.anytype.core_ui.extensions.simpleIcon import com.anytypeio.anytype.core_ui.widgets.ObjectIconWidget -import com.anytypeio.anytype.core_ui.widgets.RelationFormatIconWidget -import com.anytypeio.anytype.presentation.navigation.LibraryView import com.anytypeio.anytype.presentation.objects.ObjectIcon import com.anytypeio.anytype.ui.library.views.list.items.ItemDefaults.TEXT_PADDING_START @Composable -fun MyTypeItem(item: LibraryView, modifier: Modifier) { +fun MyTypeItem( + name: String, + icon: ObjectIcon?, + readOnly: Boolean = false, + modifier: Modifier +) { Row( modifier, horizontalArrangement = Arrangement.Start, verticalAlignment = Alignment.CenterVertically ) { - Icon(icon = item.icon) + Icon(icon = icon) Text( - text = item.name, + text = name, + color = colorResource(id = R.color.text_primary), modifier = Modifier .padding(start = TEXT_PADDING_START) ) - } -} - -@Composable -fun LibTypeItem(item: LibraryView, modifier: Modifier) { - Row( - modifier, - horizontalArrangement = Arrangement.Start, - verticalAlignment = Alignment.CenterVertically - ) { - Icon(icon = item.icon) - Text( - text = item.name, - modifier = Modifier - .padding(start = TEXT_PADDING_START) - ) - } -} - -@Composable -fun MyRelationItem(item: LibraryView, modifier: Modifier) { - Row( - modifier, - horizontalArrangement = Arrangement.Start, - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = item.name, - modifier = Modifier - .padding(start = TEXT_PADDING_START) - ) - } -} - -@Composable -fun LibRelationItem(item: LibraryView, modifier: Modifier) { - Row( - modifier, - horizontalArrangement = Arrangement.Start, - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = item.name, - modifier = Modifier - .padding(start = TEXT_PADDING_START) - ) - } -} - -@Composable -fun Icon(icon: ObjectIcon) { - AndroidView(factory = { ctx -> - ObjectIconWidget(ctx).apply { - setIcon(icon) + Spacer(modifier = Modifier.weight(1f)) + if (readOnly) { + Image( + painter = painterResource(id = R.drawable.ic_object_locked), + contentDescription = "", + ) } - }) + } +} + +@Composable +fun LibTypeItem( + name: String, + icon: ObjectIcon?, + installed: Boolean = false, + modifier: Modifier, +) { + Row( + modifier, + horizontalArrangement = Arrangement.Start, + verticalAlignment = Alignment.CenterVertically + ) { + Icon(icon = icon) + Text( + text = name, + color = colorResource(id = R.color.text_primary), + modifier = Modifier + .padding(start = TEXT_PADDING_START) + ) + Spacer(modifier = Modifier.weight(1f)) + val installedImageRes = if (installed) { + R.drawable.ic_type_installed + } else { + R.drawable.ic_type_not_installed + } + Image( + painter = painterResource(id = installedImageRes), + contentDescription = installedImageRes.toString(), + ) + } +} + +@Composable +fun MyRelationItem( + modifier: Modifier, + name: String, + format: RelationFormat, + readOnly: Boolean = false, +) { + Row( + modifier, + horizontalArrangement = Arrangement.Start, + verticalAlignment = Alignment.CenterVertically + ) { + format.simpleIcon()?.let { + Image(painter = painterResource(id = it), contentDescription = "") + } + Text( + text = name, + color = colorResource(id = R.color.text_primary), + modifier = Modifier + .padding(start = TEXT_PADDING_START) + ) + Spacer(modifier = Modifier.weight(1f)) + if (readOnly) { + Image( + painter = painterResource(id = R.drawable.ic_object_locked), + contentDescription = "" + ) + } + } +} + +@Composable +fun LibRelationItem( + modifier: Modifier, + name: String, + format: RelationFormat, + installed: Boolean +) { + Row( + modifier, + horizontalArrangement = Arrangement.Start, + verticalAlignment = Alignment.CenterVertically + ) { + format.simpleIcon()?.let { + Image(painter = painterResource(id = it), contentDescription = "") + } + Text( + text = name, + color = colorResource(id = R.color.text_primary), + modifier = Modifier + .padding(start = TEXT_PADDING_START) + ) + Spacer(modifier = Modifier.weight(1f)) + val installedImageRes = if (installed) { + R.drawable.ic_type_installed + } else { + R.drawable.ic_type_not_installed + } + Image( + painter = painterResource(id = installedImageRes), + contentDescription = installedImageRes.toString(), + ) + } +} + +@Composable +fun Icon(icon: ObjectIcon?) { + icon?.let { + AndroidView(factory = { ctx -> + ObjectIconWidget(ctx).apply { + setIcon(it) + } + }) + } } diff --git a/app/src/main/res/drawable/ic_type_installed.xml b/app/src/main/res/drawable/ic_type_installed.xml new file mode 100644 index 0000000000..d92865f651 --- /dev/null +++ b/app/src/main/res/drawable/ic_type_installed.xml @@ -0,0 +1,14 @@ + + + + diff --git a/app/src/main/res/drawable/ic_type_not_installed.xml b/app/src/main/res/drawable/ic_type_not_installed.xml new file mode 100644 index 0000000000..7c29d96357 --- /dev/null +++ b/app/src/main/res/drawable/ic_type_not_installed.xml @@ -0,0 +1,17 @@ + + + + + diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/extensions/ResExtension.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/extensions/ResExtension.kt index 2c21a6233c..c35cb6ef74 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/extensions/ResExtension.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/extensions/ResExtension.kt @@ -221,6 +221,22 @@ fun RelationFormat.icon(isMedium: Boolean = false): Int = when (this) { } } +fun RelationFormat.simpleIcon(): Int? = when (this) { + RelationFormat.SHORT_TEXT -> R.drawable.ic_relation_format_text_small + RelationFormat.LONG_TEXT -> R.drawable.ic_relation_format_text_small + RelationFormat.NUMBER -> R.drawable.ic_relation_format_number_small + RelationFormat.STATUS -> R.drawable.ic_relation_format_status_small + RelationFormat.TAG -> R.drawable.ic_relation_format_tag_small + RelationFormat.DATE -> R.drawable.ic_relation_format_date_small + RelationFormat.FILE -> R.drawable.ic_relation_format_attachment_small + RelationFormat.CHECKBOX -> R.drawable.ic_relation_format_checkbox_small + RelationFormat.URL -> R.drawable.ic_relation_format_url_small + RelationFormat.EMAIL -> R.drawable.ic_relation_format_email_small + RelationFormat.PHONE -> R.drawable.ic_relation_format_phone_number_small + RelationFormat.OBJECT -> R.drawable.ic_relation_format_object_small + else -> null +} + fun DVSortType.text(format: RelationFormat): Int = when (format) { RelationFormat.TAG, RelationFormat.STATUS -> { if (this == DVSortType.ASC) diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/RelationFormatIconWidget.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/RelationFormatIconWidget.kt index b3a128c036..772e2e1d1f 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/RelationFormatIconWidget.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/RelationFormatIconWidget.kt @@ -5,6 +5,7 @@ import android.util.AttributeSet import androidx.appcompat.widget.AppCompatImageView import com.anytypeio.anytype.core_models.RelationFormat import com.anytypeio.anytype.core_ui.R +import com.anytypeio.anytype.core_ui.extensions.simpleIcon import com.anytypeio.anytype.presentation.sets.model.ColumnView import timber.log.Timber @@ -13,21 +14,10 @@ class RelationFormatIconWidget @JvmOverloads constructor( attrs: AttributeSet? = null ) : AppCompatImageView(context, attrs) { fun bind(format: RelationFormat) { - when (format) { - RelationFormat.SHORT_TEXT -> setImageResource(R.drawable.ic_relation_format_text_small) - RelationFormat.LONG_TEXT -> setImageResource(R.drawable.ic_relation_format_text_small) - RelationFormat.NUMBER -> setImageResource(R.drawable.ic_relation_format_number_small) - RelationFormat.STATUS -> setImageResource(R.drawable.ic_relation_format_status_small) - RelationFormat.TAG -> setImageResource(R.drawable.ic_relation_format_tag_small) - RelationFormat.DATE -> setImageResource(R.drawable.ic_relation_format_date_small) - RelationFormat.FILE -> setImageResource(R.drawable.ic_relation_format_attachment_small) - RelationFormat.CHECKBOX -> setImageResource(R.drawable.ic_relation_format_checkbox_small) - RelationFormat.URL -> setImageResource(R.drawable.ic_relation_format_url_small) - RelationFormat.EMAIL -> setImageResource(R.drawable.ic_relation_format_email_small) - RelationFormat.PHONE -> setImageResource(R.drawable.ic_relation_format_phone_number_small) - RelationFormat.OBJECT -> setImageResource(R.drawable.ic_relation_format_object_small) - else -> Timber.d("Unexpected format: $format") - } + format.simpleIcon()?.let { + setImageResource(it) + return + } ?: Timber.e("Unexpected format: $format") } fun bind(format: ColumnView.Format) { when (format) { diff --git a/domain/build.gradle b/domain/build.gradle index f4d43154c1..c3ebd0b4b3 100644 --- a/domain/build.gradle +++ b/domain/build.gradle @@ -10,6 +10,8 @@ dependencies { implementation libs.kotlin implementation libs.coroutines + compileOnly libs.javaxInject + testImplementation project(":test:utils") testImplementation libs.kotlinTest testImplementation libs.turbine diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/library/LibrarySearchParams.kt b/domain/src/main/java/com/anytypeio/anytype/domain/library/StoreSearchParams.kt similarity index 94% rename from domain/src/main/java/com/anytypeio/anytype/domain/library/LibrarySearchParams.kt rename to domain/src/main/java/com/anytypeio/anytype/domain/library/StoreSearchParams.kt index 39b50d5f27..3a540c96bc 100644 --- a/domain/src/main/java/com/anytypeio/anytype/domain/library/LibrarySearchParams.kt +++ b/domain/src/main/java/com/anytypeio/anytype/domain/library/StoreSearchParams.kt @@ -4,7 +4,7 @@ import com.anytypeio.anytype.core_models.DVFilter import com.anytypeio.anytype.core_models.DVSort import com.anytypeio.anytype.core_models.Id -class LibrarySearchParams( +class StoreSearchParams( val subscription: Id, val sorts: List = emptyList(), val filters: List = emptyList(), diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/library/StorelessSubscriptionContainer.kt b/domain/src/main/java/com/anytypeio/anytype/domain/library/StorelessSubscriptionContainer.kt new file mode 100644 index 0000000000..f5572e8c19 --- /dev/null +++ b/domain/src/main/java/com/anytypeio/anytype/domain/library/StorelessSubscriptionContainer.kt @@ -0,0 +1,105 @@ +package com.anytypeio.anytype.domain.library + +import com.anytypeio.anytype.core_models.Id +import com.anytypeio.anytype.core_models.ObjectWrapper +import com.anytypeio.anytype.core_models.SubscriptionEvent +import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers +import com.anytypeio.anytype.domain.block.repo.BlockRepository +import com.anytypeio.anytype.domain.library.processors.EventAddProcessor +import com.anytypeio.anytype.domain.library.processors.EventAmendProcessor +import com.anytypeio.anytype.domain.library.processors.EventPositionProcessor +import com.anytypeio.anytype.domain.library.processors.EventRemoveProcessor +import com.anytypeio.anytype.domain.library.processors.EventSetProcessor +import com.anytypeio.anytype.domain.library.processors.EventUnsetProcessor +import com.anytypeio.anytype.domain.search.SubscriptionEventChannel +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emitAll +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.scan + +interface StorelessSubscriptionContainer { + + fun subscribe(searchParams: StoreSearchParams): Flow> + + class Impl @Inject constructor( + private val repo: BlockRepository, + private val channel: SubscriptionEventChannel, + private val dispatchers: AppCoroutineDispatchers + ) : StorelessSubscriptionContainer { + + private val addEventProcessor by lazy { EventAddProcessor() } + private val unsetEventProcessor by lazy { EventUnsetProcessor() } + private val removeEventProcessor by lazy { EventRemoveProcessor() } + private val setEventProcessor by lazy { EventSetProcessor() } + private val amendEventProcessor by lazy { EventAmendProcessor() } + private val positionEventProcessor by lazy { EventPositionProcessor() } + + private fun subscribe(subscriptions: List) = channel.subscribe(subscriptions) + + override fun subscribe(searchParams: StoreSearchParams): Flow> = + flow { + with(searchParams) { + + val initial = repo.searchObjectsWithSubscription( + subscription = subscription, + sorts = sorts, + filters = filters, + offset = offset, + limit = limit, + keys = keys, + afterId = null, + beforeId = null, + source = source, + ignoreWorkspace = null, + noDepSubscription = null + ).results.map { SubscriptionObject(it.id, it) }.toMutableList() + + val objectsFlow = + subscribe( + listOf(searchParams.subscription) + ).scan(initial) { dataItems, payload -> + var result = dataItems + payload.forEach { event -> + when (event) { + is SubscriptionEvent.Add -> { + result = addEventProcessor.process(event, result) + } + is SubscriptionEvent.Amend -> { + result = amendEventProcessor.process(event, result) + } + is SubscriptionEvent.Position -> { + result = positionEventProcessor.process(event, result) + } + is SubscriptionEvent.Remove -> { + result = removeEventProcessor.process(event, result) + } + is SubscriptionEvent.Set -> { + result = setEventProcessor.process(event, result) + } + is SubscriptionEvent.Unset -> { + result = unsetEventProcessor.process(event, result) + } + else -> { + // do nothing + } + } + } + result + }.map { + it.mapNotNull { item -> item.objectWrapper } + } + + emitAll(objectsFlow) + } + }.flowOn(dispatchers.io) + } + +} + +data class SubscriptionObject( + val id: Id, + val objectWrapper: ObjectWrapper.Basic? = null +) \ No newline at end of file diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/library/processors/EventAddProcessor.kt b/domain/src/main/java/com/anytypeio/anytype/domain/library/processors/EventAddProcessor.kt new file mode 100644 index 0000000000..5b9ff84c31 --- /dev/null +++ b/domain/src/main/java/com/anytypeio/anytype/domain/library/processors/EventAddProcessor.kt @@ -0,0 +1,26 @@ +package com.anytypeio.anytype.domain.library.processors + +import com.anytypeio.anytype.core_models.SubscriptionEvent +import com.anytypeio.anytype.domain.library.SubscriptionObject + +class EventAddProcessor : SubscriptionEventProcessor { + + override fun process( + event: SubscriptionEvent.Add, + dataItems: MutableList + ): MutableList = with(dataItems) { + val afterId = event.afterId + if (afterId != null) { + val afterIdx = indexOfFirst { afterId == it.id } + if (afterIdx != -1) { + add(afterIdx.inc(), SubscriptionObject(event.target)) + } else { + add(0, SubscriptionObject(event.target)) + } + } else { + add(0, SubscriptionObject(event.target)) + } + return this + } + +} \ No newline at end of file diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/library/processors/EventAmendProcessor.kt b/domain/src/main/java/com/anytypeio/anytype/domain/library/processors/EventAmendProcessor.kt new file mode 100644 index 0000000000..f46a602875 --- /dev/null +++ b/domain/src/main/java/com/anytypeio/anytype/domain/library/processors/EventAmendProcessor.kt @@ -0,0 +1,35 @@ +package com.anytypeio.anytype.domain.library.processors + +import com.anytypeio.anytype.core_models.ObjectWrapper +import com.anytypeio.anytype.core_models.SubscriptionEvent +import com.anytypeio.anytype.domain.library.SubscriptionObject +import com.anytypeio.anytype.domain.`object`.amend + +class EventAmendProcessor: SubscriptionEventProcessor { + + override fun process( + event: SubscriptionEvent.Amend, + dataItems: MutableList + ): MutableList = with(dataItems) { + val item = find { it.id == event.target } + if (item?.objectWrapper != null) { + set( + indexOf(item), + SubscriptionObject( + id = item.id, + objectWrapper = item.objectWrapper.amend(event.diff) + ) + ) + } else { + set( + indexOf(item), + SubscriptionObject( + id = item?.id ?: event.target, + objectWrapper = ObjectWrapper.Basic(event.diff) + ) + ) + } + return this + } + +} \ No newline at end of file diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/library/processors/EventPositionProcessor.kt b/domain/src/main/java/com/anytypeio/anytype/domain/library/processors/EventPositionProcessor.kt new file mode 100644 index 0000000000..ecfaab2d48 --- /dev/null +++ b/domain/src/main/java/com/anytypeio/anytype/domain/library/processors/EventPositionProcessor.kt @@ -0,0 +1,25 @@ +package com.anytypeio.anytype.domain.library.processors + +import com.anytypeio.anytype.core_models.SubscriptionEvent +import com.anytypeio.anytype.domain.library.SubscriptionObject + +class EventPositionProcessor: SubscriptionEventProcessor { + + override fun process( + event: SubscriptionEvent.Position, + dataItems: MutableList + ): MutableList = with(dataItems) { + val itemToMove = find { it.id == event.target } + if (itemToMove != null) { + remove(itemToMove) + val afterIdx = indexOfFirst { event.afterId == it.id } + if (afterIdx != -1) { + add(afterIdx.inc(), itemToMove) + } else { + add(0, itemToMove) + } + } + return this + } + +} \ No newline at end of file diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/library/processors/EventRemoveProcessor.kt b/domain/src/main/java/com/anytypeio/anytype/domain/library/processors/EventRemoveProcessor.kt new file mode 100644 index 0000000000..a674f65138 --- /dev/null +++ b/domain/src/main/java/com/anytypeio/anytype/domain/library/processors/EventRemoveProcessor.kt @@ -0,0 +1,18 @@ +package com.anytypeio.anytype.domain.library.processors + +import com.anytypeio.anytype.core_models.SubscriptionEvent +import com.anytypeio.anytype.domain.library.SubscriptionObject + +class EventRemoveProcessor : SubscriptionEventProcessor { + + override fun process( + event: SubscriptionEvent.Remove, + dataItems: MutableList + ): MutableList = with(dataItems) { + retainAll { + it.id != event.target + } + return this + } + +} \ No newline at end of file diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/library/processors/EventSetProcessor.kt b/domain/src/main/java/com/anytypeio/anytype/domain/library/processors/EventSetProcessor.kt new file mode 100644 index 0000000000..142728b8fe --- /dev/null +++ b/domain/src/main/java/com/anytypeio/anytype/domain/library/processors/EventSetProcessor.kt @@ -0,0 +1,33 @@ +package com.anytypeio.anytype.domain.library.processors + +import com.anytypeio.anytype.core_models.SubscriptionEvent +import com.anytypeio.anytype.domain.library.SubscriptionObject + +class EventSetProcessor : SubscriptionEventProcessor { + + override fun process( + event: SubscriptionEvent.Set, + dataItems: MutableList + ): MutableList = with(dataItems) { + val indexOfItem = indexOfFirst { it.id == event.target } + if (indexOfItem != -1) { + set( + indexOfItem, + SubscriptionObject( + event.target, + com.anytypeio.anytype.core_models.ObjectWrapper.Basic(event.data) + ) + ) + } else { + add( + 0, + SubscriptionObject( + event.target, + com.anytypeio.anytype.core_models.ObjectWrapper.Basic(event.data) + ) + ) + } + return this + } + +} \ No newline at end of file diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/library/processors/EventUnsetProcessor.kt b/domain/src/main/java/com/anytypeio/anytype/domain/library/processors/EventUnsetProcessor.kt new file mode 100644 index 0000000000..362238bbdc --- /dev/null +++ b/domain/src/main/java/com/anytypeio/anytype/domain/library/processors/EventUnsetProcessor.kt @@ -0,0 +1,24 @@ +package com.anytypeio.anytype.domain.library.processors + +import com.anytypeio.anytype.core_models.SubscriptionEvent +import com.anytypeio.anytype.domain.library.SubscriptionObject +import com.anytypeio.anytype.domain.`object`.unset + +class EventUnsetProcessor : SubscriptionEventProcessor { + + override fun process( + event: SubscriptionEvent.Unset, + dataItems: MutableList + ): MutableList = with(dataItems) { + val item = find { it.id == event.target }?.let { + SubscriptionObject(it.id, it.objectWrapper.apply { + this?.unset(event.keys) + }) + } + if (item != null) { + set(indexOf(item), item) + } + return this + } + +} \ No newline at end of file diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/library/processors/SubscriptionEventProcessor.kt b/domain/src/main/java/com/anytypeio/anytype/domain/library/processors/SubscriptionEventProcessor.kt new file mode 100644 index 0000000000..7446908182 --- /dev/null +++ b/domain/src/main/java/com/anytypeio/anytype/domain/library/processors/SubscriptionEventProcessor.kt @@ -0,0 +1,11 @@ +package com.anytypeio.anytype.domain.library.processors + +import com.anytypeio.anytype.core_models.SubscriptionEvent +import com.anytypeio.anytype.domain.library.SubscriptionObject + +interface SubscriptionEventProcessor { + fun process( + event: T, + dataItems: MutableList + ): MutableList +} \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/library/LibraryInteractor.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/library/LibraryInteractor.kt deleted file mode 100644 index 9305956d84..0000000000 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/library/LibraryInteractor.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.anytypeio.anytype.presentation.library - -import com.anytypeio.anytype.domain.block.repo.BlockRepository -import com.anytypeio.anytype.domain.library.LibrarySearchParams -import com.anytypeio.anytype.domain.misc.UrlBuilder -import com.anytypeio.anytype.presentation.navigation.LibraryView -import com.anytypeio.anytype.presentation.objects.toLibraryViews -import javax.inject.Inject -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flow - -interface LibraryInteractor { - - fun subscribe(searchParams: LibrarySearchParams): Flow> - - class Impl @Inject constructor( - private val repo: BlockRepository, - private val urlBuilder: UrlBuilder, - ) : LibraryInteractor { - override fun subscribe(searchParams: LibrarySearchParams): Flow> = - flow { - - with(searchParams) { - val initial = repo.searchObjectsWithSubscription( - subscription = subscription, - sorts = sorts, - filters = filters, - offset = offset, - limit = limit, - keys = keys, - afterId = null, - beforeId = null, - source = source, - ignoreWorkspace = null, - noDepSubscription = null - ).results.toLibraryViews(urlBuilder = urlBuilder) - - emit(initial) - } - } - } -} \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/library/LibraryListDelegate.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/library/LibraryListDelegate.kt index 9273283134..1b71373010 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/library/LibraryListDelegate.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/library/LibraryListDelegate.kt @@ -1,5 +1,6 @@ package com.anytypeio.anytype.presentation.library +import com.anytypeio.anytype.core_models.ObjectWrapper import com.anytypeio.anytype.presentation.navigation.LibraryView import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.Flow @@ -12,7 +13,7 @@ interface LibraryListDelegate { val queryFlow: MutableStateFlow val itemsFlow: Flow - fun itemsFlow(): Flow> + fun itemsFlow(): Flow> @FlowPreview fun queryFlow() = queryFlow diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/library/LibraryViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/library/LibraryViewModel.kt index 582c5539c9..104d4a487d 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/library/LibraryViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/library/LibraryViewModel.kt @@ -7,6 +7,7 @@ import com.anytypeio.anytype.presentation.library.delegates.LibraryRelationsDele import com.anytypeio.anytype.presentation.library.delegates.LibraryTypesDelegate import com.anytypeio.anytype.presentation.library.delegates.MyRelationsDelegate import com.anytypeio.anytype.presentation.library.delegates.MyTypesDelegate +import com.anytypeio.anytype.presentation.navigation.LibraryView import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted @@ -55,10 +56,21 @@ class LibraryViewModel( myRelationsDelegate.itemsFlow, libraryRelationsDelegate.itemsFlow ) { myTypes, libTypes, myRel, libRel -> - LibraryScreenState( - types = LibraryScreenState.Tabs.Types(myTypes, libTypes), - relations = LibraryScreenState.Tabs.Relations(myRel, libRel) + + val libTypesItems = updateInstalledValueForTypes( + libTypes, + myTypes ) + val libRelItems = updateInstalledValueForRelations( + libRel, + myRel + ) + + LibraryScreenState( + types = LibraryScreenState.Tabs.Types(myTypes, libTypesItems), + relations = LibraryScreenState.Tabs.Relations(myRel, libRelItems) + ) + }.stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(STOP_SUBSCRIPTION_TIMEOUT), @@ -74,6 +86,40 @@ class LibraryViewModel( ) ) + private fun updateInstalledValueForTypes( + libTypes: LibraryScreenState.Tabs.TabData, + myTypes: LibraryScreenState.Tabs.TabData + ): LibraryScreenState.Tabs.TabData { + return libTypes.copy( + items = libTypes.items.map { libType -> + if (libType is LibraryView.LibraryTypeView) { + libType.copy(installed = myTypes.items.find { myType -> + (myType as LibraryView.MyTypeView).sourceObject == libType.id + } != null) + } else { + libType + } + } + ) + } + + private fun updateInstalledValueForRelations( + libRelations: LibraryScreenState.Tabs.TabData, + myRelations: LibraryScreenState.Tabs.TabData + ): LibraryScreenState.Tabs.TabData { + return libRelations.copy( + items = libRelations.items.map { libRelation -> + if (libRelation is LibraryView.LibraryRelationView) { + libRelation.copy(installed = myRelations.items.find { myType -> + (myType as LibraryView.MyRelationView).sourceObject == libRelation.id + } != null) + } else { + libRelation + } + } + ) + } + class Factory @Inject constructor( private val myTypesDelegate: MyTypesDelegate, private val libraryTypesDelegate: LibraryTypesDelegate, diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/library/delegates/LibraryRelationsDelegate.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/library/delegates/LibraryRelationsDelegate.kt index 15927cab6c..d1a9cfdfb2 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/library/delegates/LibraryRelationsDelegate.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/library/delegates/LibraryRelationsDelegate.kt @@ -4,13 +4,15 @@ import com.anytypeio.anytype.core_models.DVFilter import com.anytypeio.anytype.core_models.DVFilterCondition import com.anytypeio.anytype.core_models.Marketplace import com.anytypeio.anytype.core_models.Relations -import com.anytypeio.anytype.domain.library.LibrarySearchParams +import com.anytypeio.anytype.domain.library.StoreSearchParams +import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer +import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.presentation.dashboard.DEFAULT_KEYS -import com.anytypeio.anytype.presentation.library.LibraryInteractor import com.anytypeio.anytype.presentation.library.LibraryListDelegate import com.anytypeio.anytype.presentation.library.LibraryScreenState import com.anytypeio.anytype.presentation.library.QueryListenerLibRelations import com.anytypeio.anytype.presentation.library.filterByQuery +import com.anytypeio.anytype.presentation.objects.toLibraryViews import com.anytypeio.anytype.presentation.search.ObjectSearchConstants import javax.inject.Inject import kotlinx.coroutines.flow.Flow @@ -18,7 +20,8 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine class LibraryRelationsDelegate @Inject constructor( - private val interactor: LibraryInteractor + private val container: StorelessSubscriptionContainer, + private val urlBuilder: UrlBuilder ) : LibraryListDelegate, QueryListenerLibRelations { override val queryFlow: MutableStateFlow = MutableStateFlow("") @@ -31,15 +34,19 @@ class LibraryRelationsDelegate @Inject constructor( itemsFlow(), queryFlow() ) { items, query -> - LibraryScreenState.Tabs.TabData(items.filterByQuery(query)) + LibraryScreenState.Tabs.TabData( + items + .toLibraryViews(urlBuilder) + .filterByQuery(query) + ) } - override fun itemsFlow() = interactor.subscribe(buildSearchParams()) + override fun itemsFlow() = container.subscribe(buildSearchParams()) - private fun buildSearchParams(): LibrarySearchParams { - return LibrarySearchParams( + private fun buildSearchParams(): StoreSearchParams { + return StoreSearchParams( subscription = SUB_LIBRARY_RELATIONS, - keys = DEFAULT_KEYS, + keys = DEFAULT_KEYS + listOf(Relations.RELATION_FORMAT), filters = buildList { addAll(ObjectSearchConstants.filterMarketplaceRelations()) add( diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/library/delegates/LibraryTypesDelegate.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/library/delegates/LibraryTypesDelegate.kt index e0dbe31d1a..0c801bd478 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/library/delegates/LibraryTypesDelegate.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/library/delegates/LibraryTypesDelegate.kt @@ -3,14 +3,17 @@ package com.anytypeio.anytype.presentation.library.delegates import com.anytypeio.anytype.core_models.DVFilter import com.anytypeio.anytype.core_models.DVFilterCondition import com.anytypeio.anytype.core_models.Marketplace.MARKETPLACE_ID +import com.anytypeio.anytype.core_models.MarketplaceObjectTypeIds import com.anytypeio.anytype.core_models.Relations -import com.anytypeio.anytype.domain.library.LibrarySearchParams +import com.anytypeio.anytype.domain.library.StoreSearchParams +import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer +import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.presentation.dashboard.DEFAULT_KEYS -import com.anytypeio.anytype.presentation.library.LibraryInteractor import com.anytypeio.anytype.presentation.library.LibraryListDelegate import com.anytypeio.anytype.presentation.library.LibraryScreenState import com.anytypeio.anytype.presentation.library.QueryListenerLibTypes import com.anytypeio.anytype.presentation.library.filterByQuery +import com.anytypeio.anytype.presentation.objects.toLibraryViews import com.anytypeio.anytype.presentation.search.ObjectSearchConstants import javax.inject.Inject import kotlinx.coroutines.flow.Flow @@ -18,7 +21,8 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine class LibraryTypesDelegate @Inject constructor( - private val interactor: LibraryInteractor + private val container: StorelessSubscriptionContainer, + private val urlBuilder: UrlBuilder ) : LibraryListDelegate, QueryListenerLibTypes { override val queryFlow: MutableStateFlow = MutableStateFlow("") @@ -31,17 +35,21 @@ class LibraryTypesDelegate @Inject constructor( itemsFlow(), queryFlow() ) { items, query -> - LibraryScreenState.Tabs.TabData(items.filterByQuery(query)) + LibraryScreenState.Tabs.TabData( + items + .toLibraryViews(urlBuilder) + .filterByQuery(query) + ) } - override fun itemsFlow() = interactor.subscribe(buildSearchParams()) + override fun itemsFlow() = container.subscribe(buildSearchParams()) - private fun buildSearchParams(): LibrarySearchParams { - return LibrarySearchParams( + private fun buildSearchParams(): StoreSearchParams { + return StoreSearchParams( subscription = SUB_LIBRARY_TYPES, keys = DEFAULT_KEYS, filters = buildList { - ObjectSearchConstants.filterTypes() + addAll(ObjectSearchConstants.filterTypes()) add( DVFilter( relation = Relations.WORKSPACE_ID, @@ -49,6 +57,13 @@ class LibraryTypesDelegate @Inject constructor( value = MARKETPLACE_ID ) ) + add( + DVFilter( + relation = Relations.TYPE, + condition = DVFilterCondition.EQUAL, + value = MarketplaceObjectTypeIds.OBJECT_TYPE + ) + ) } ) } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/library/delegates/MyRelationsDelegate.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/library/delegates/MyRelationsDelegate.kt index 277c2ed7e2..4cf1ae4d69 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/library/delegates/MyRelationsDelegate.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/library/delegates/MyRelationsDelegate.kt @@ -4,14 +4,16 @@ import com.anytypeio.anytype.core_models.DVFilter import com.anytypeio.anytype.core_models.DVFilterCondition import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_models.Relations -import com.anytypeio.anytype.domain.library.LibrarySearchParams +import com.anytypeio.anytype.domain.library.StoreSearchParams +import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer +import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.workspace.WorkspaceManager import com.anytypeio.anytype.presentation.dashboard.DEFAULT_KEYS -import com.anytypeio.anytype.presentation.library.LibraryInteractor import com.anytypeio.anytype.presentation.library.LibraryListDelegate import com.anytypeio.anytype.presentation.library.LibraryScreenState import com.anytypeio.anytype.presentation.library.QueryListenerMyRelations import com.anytypeio.anytype.presentation.library.filterByQuery +import com.anytypeio.anytype.presentation.objects.toLibraryViews import com.anytypeio.anytype.presentation.search.ObjectSearchConstants import javax.inject.Inject import kotlinx.coroutines.FlowPreview @@ -22,8 +24,9 @@ import kotlinx.coroutines.flow.flatMapMerge import kotlinx.coroutines.flow.flow class MyRelationsDelegate @Inject constructor( - private val interactor: LibraryInteractor, - private val workspaceManager: WorkspaceManager + private val container: StorelessSubscriptionContainer, + private val workspaceManager: WorkspaceManager, + private val urlBuilder: UrlBuilder ) : LibraryListDelegate, QueryListenerMyRelations { override val queryFlow: MutableStateFlow = MutableStateFlow("") @@ -37,7 +40,11 @@ class MyRelationsDelegate @Inject constructor( itemsFlow(), queryFlow() ) { items, query -> - LibraryScreenState.Tabs.TabData(items.filterByQuery(query)) + LibraryScreenState.Tabs.TabData( + items + .toLibraryViews(urlBuilder) + .filterByQuery(query) + ) } @FlowPreview @@ -45,13 +52,17 @@ class MyRelationsDelegate @Inject constructor( emit(workspaceManager.getCurrentWorkspace()) }.flatMapMerge { val searchParams = buildSearchParams(it) - interactor.subscribe(searchParams) + container.subscribe(searchParams) } - private fun buildSearchParams(workspaceId: Id): LibrarySearchParams { - return LibrarySearchParams( + private fun buildSearchParams(workspaceId: Id): StoreSearchParams { + return StoreSearchParams( subscription = SUB_LIBRARY_MY_RELATIONS, - keys = DEFAULT_KEYS, + keys = DEFAULT_KEYS + listOf( + Relations.SOURCE_OBJECT, + Relations.RELATION_FORMAT, + Relations.RELATION_READ_ONLY_VALUE + ), filters = buildList { addAll(ObjectSearchConstants.filterMyRelations()) add( diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/library/delegates/MyTypesDelegate.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/library/delegates/MyTypesDelegate.kt index 0cccdfaa1a..47443eacf5 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/library/delegates/MyTypesDelegate.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/library/delegates/MyTypesDelegate.kt @@ -3,15 +3,18 @@ package com.anytypeio.anytype.presentation.library.delegates import com.anytypeio.anytype.core_models.DVFilter import com.anytypeio.anytype.core_models.DVFilterCondition import com.anytypeio.anytype.core_models.Id +import com.anytypeio.anytype.core_models.ObjectTypeIds import com.anytypeio.anytype.core_models.Relations -import com.anytypeio.anytype.domain.library.LibrarySearchParams +import com.anytypeio.anytype.domain.library.StoreSearchParams +import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer +import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.workspace.WorkspaceManager import com.anytypeio.anytype.presentation.dashboard.DEFAULT_KEYS -import com.anytypeio.anytype.presentation.library.LibraryInteractor import com.anytypeio.anytype.presentation.library.LibraryListDelegate import com.anytypeio.anytype.presentation.library.LibraryScreenState import com.anytypeio.anytype.presentation.library.QueryListenerMyTypes import com.anytypeio.anytype.presentation.library.filterByQuery +import com.anytypeio.anytype.presentation.objects.toLibraryViews import com.anytypeio.anytype.presentation.search.ObjectSearchConstants import javax.inject.Inject import kotlinx.coroutines.FlowPreview @@ -22,8 +25,9 @@ import kotlinx.coroutines.flow.flatMapMerge import kotlinx.coroutines.flow.flow class MyTypesDelegate @Inject constructor( - private val interactor: LibraryInteractor, - private val workspaceManager: WorkspaceManager + private val container: StorelessSubscriptionContainer, + private val workspaceManager: WorkspaceManager, + private val urlBuilder: UrlBuilder ) : LibraryListDelegate, QueryListenerMyTypes { override val queryFlow: MutableStateFlow = MutableStateFlow("") @@ -36,7 +40,11 @@ class MyTypesDelegate @Inject constructor( override val itemsFlow: Flow = combine( itemsFlow(), queryFlow() ) { items, query -> - LibraryScreenState.Tabs.TabData(items.filterByQuery(query)) + LibraryScreenState.Tabs.TabData( + items + .toLibraryViews(urlBuilder) + .filterByQuery(query) + ) } @FlowPreview @@ -44,13 +52,16 @@ class MyTypesDelegate @Inject constructor( emit(workspaceManager.getCurrentWorkspace()) }.flatMapMerge { val searchParams = buildSearchParams(it) - interactor.subscribe(searchParams) + container.subscribe(searchParams) } - private fun buildSearchParams(workspaceId: Id): LibrarySearchParams { - return LibrarySearchParams( + private fun buildSearchParams(workspaceId: Id): StoreSearchParams { + return StoreSearchParams( subscription = SUB_LIBRARY_MY_TYPES, - keys = DEFAULT_KEYS, + keys = DEFAULT_KEYS + listOf( + Relations.SOURCE_OBJECT, + Relations.RELATION_READ_ONLY_VALUE + ), filters = buildList { addAll(ObjectSearchConstants.filterTypes()) add( @@ -60,6 +71,13 @@ class MyTypesDelegate @Inject constructor( value = workspaceId ) ) + add( + DVFilter( + relation = Relations.TYPE, + condition = DVFilterCondition.EQUAL, + value = ObjectTypeIds.OBJECT_TYPE + ) + ) } ) } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/navigation/ObjectView.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/navigation/ObjectView.kt index 7afc29ddf0..383156aec2 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/navigation/ObjectView.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/navigation/ObjectView.kt @@ -2,7 +2,7 @@ package com.anytypeio.anytype.presentation.navigation import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_models.ObjectType -import com.anytypeio.anytype.core_models.Struct +import com.anytypeio.anytype.core_models.RelationFormat import com.anytypeio.anytype.presentation.objects.ObjectIcon interface DefaultSearchItem @@ -14,7 +14,7 @@ data class DefaultObjectView( val typeName: String? = null, val layout: ObjectType.Layout? = null, val icon: ObjectIcon = ObjectIcon.None -): DefaultSearchItem +) : DefaultSearchItem data class ObjectView( val id: String, @@ -31,11 +31,43 @@ fun List.filterBy(text: String): List = if (text.isNotEmpty()) this.filter { it.isContainsText(text) } else this -data class LibraryView( - val id: Id, - val name: String, - val type: String? = null, - val typeName: String? = null, - val layout: ObjectType.Layout? = null, - val icon: ObjectIcon = ObjectIcon.None -) \ No newline at end of file +sealed interface LibraryView { + val id: Id + val name: String + + class MyTypeView( + override val id: Id, + override val name: String, + val icon: ObjectIcon? = null, + val sourceObject: Id? = null, + val readOnly: Boolean = false + ) : LibraryView + + data class LibraryTypeView( + override val id: Id, + override val name: String, + val icon: ObjectIcon? = null, + val installed: Boolean = false, + ) : LibraryView + + class MyRelationView( + override val id: Id, + override val name: String, + val format: RelationFormat, + val sourceObject: Id? = null, + val readOnly: Boolean = false + ) : LibraryView + + data class LibraryRelationView( + override val id: Id, + override val name: String, + val format: RelationFormat, + val installed: Boolean = false, + ) : LibraryView + + class UnknownView( + override val id: Id = "", + override val name: String = "", + ) : LibraryView + +} \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/ObjectWrapperMapper.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/ObjectWrapperMapper.kt index 2fe6ed7330..3912a903b3 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/ObjectWrapperMapper.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/ObjectWrapperMapper.kt @@ -1,14 +1,18 @@ package com.anytypeio.anytype.presentation.objects import com.anytypeio.anytype.core_models.Id +import com.anytypeio.anytype.core_models.MarketplaceObjectTypeIds import com.anytypeio.anytype.core_models.ObjectType +import com.anytypeio.anytype.core_models.ObjectTypeIds import com.anytypeio.anytype.core_models.ObjectWrapper +import com.anytypeio.anytype.core_models.Relations.SOURCE_OBJECT import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.presentation.linking.LinkToItemView import com.anytypeio.anytype.presentation.navigation.DefaultObjectView import com.anytypeio.anytype.presentation.navigation.LibraryView import com.anytypeio.anytype.presentation.relations.RelationValueView import com.anytypeio.anytype.presentation.sets.filter.CreateFilterView +import timber.log.Timber @Deprecated("To be deleted") fun List.toView( @@ -22,7 +26,10 @@ fun List.toView( id = obj.id, name = obj.getProperName(), type = typeUrl, - typeName = getProperTypeName(id = typeUrl, types = objectTypes), + typeName = getProperTypeName( + id = typeUrl, + types = objectTypes + ), layout = layout, icon = ObjectIcon.from( obj = obj, @@ -55,19 +62,55 @@ fun List.toViews( fun List.toLibraryViews( urlBuilder: UrlBuilder, ): List = map { obj -> - val typeUrl = obj.getProperType() - val layout = obj.getProperLayout() - LibraryView( - id = obj.id, - name = obj.getProperName(), - type = typeUrl, - layout = layout, - icon = ObjectIcon.from( - obj = obj, - layout = layout, - builder = urlBuilder - ), - ) + when (obj.getProperType()) { + MarketplaceObjectTypeIds.OBJECT_TYPE -> { + LibraryView.LibraryTypeView( + id = obj.id, + name = obj.name ?: "", + icon = ObjectIcon.from( + obj = obj, + layout = obj.getProperLayout(), + builder = urlBuilder + ), + installed = false, + ) + } + ObjectTypeIds.OBJECT_TYPE -> { + LibraryView.MyTypeView( + id = obj.id, + name = obj.name ?: "", + icon = ObjectIcon.from( + obj = obj, + layout = obj.getProperLayout(), + builder = urlBuilder + ), + sourceObject = obj.map[SOURCE_OBJECT]?.toString(), + readOnly = obj.relationReadonlyValue ?: false + ) + } + ObjectTypeIds.RELATION -> { + val relation = ObjectWrapper.Relation(obj.map) + LibraryView.MyRelationView( + id = obj.id, + name = obj.name ?: "", + format = relation.format, + sourceObject = obj.map[SOURCE_OBJECT]?.toString(), + readOnly = relation.isReadonlyValue + ) + } + MarketplaceObjectTypeIds.RELATION -> { + val relation = ObjectWrapper.Relation(obj.map) + LibraryView.LibraryRelationView( + id = obj.id, + name = obj.name ?: "", + format = relation.format + ) + } + else -> { + Timber.e("Unknown type: ${obj.getProperType()}") + LibraryView.UnknownView() + } + } } fun List.toLinkToView( @@ -198,7 +241,8 @@ private fun ObjectWrapper.Basic.getProperType() = type.firstOrNull() private fun ObjectWrapper.Basic.getProperFileExt() = fileExt.orEmpty() private fun ObjectWrapper.Basic.getProperFileMime() = fileMimeType.orEmpty() -private fun getProperTypeName(id: Id?, types: List) = types.find { it.id == id }?.name.orEmpty() +private fun getProperTypeName(id: Id?, types: List) = + types.find { it.id == id }?.name.orEmpty() private fun ObjectWrapper.Basic.getProperFileImage(urlBuilder: UrlBuilder): String? = iconImage?.let { if (it.isBlank()) null else urlBuilder.thumbnail(it) } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/search/ObjectSearchConstants.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/search/ObjectSearchConstants.kt index dedeb322a0..de4d202e15 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/search/ObjectSearchConstants.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/search/ObjectSearchConstants.kt @@ -594,11 +594,6 @@ object ObjectSearchConstants { ) fun filterTypes() : List = listOf( - DVFilter( - relation = Relations.TYPE, - condition = DVFilterCondition.EQUAL, - value = OBJECT_TYPE - ), DVFilter( relation = Relations.IS_ARCHIVED, condition = DVFilterCondition.NOT_EQUAL,