1
0
Fork 0
mirror of https://github.com/anyproto/anytype-kotlin.git synced 2025-06-07 21:37:02 +09:00

DROID-3013 Tech | Remove old library screen (#1789)

This commit is contained in:
Konstantin Ivanov 2024-11-08 21:47:50 +01:00 committed by GitHub
parent 6d37e175d7
commit 029daff72c
Signed by: github
GPG key ID: B5690EEEBB952194
41 changed files with 22 additions and 3534 deletions

View file

@ -53,7 +53,6 @@ import com.anytypeio.anytype.di.feature.cover.UnsplashModule
import com.anytypeio.anytype.di.feature.discussions.DaggerDiscussionComponent
import com.anytypeio.anytype.di.feature.gallery.DaggerGalleryInstallationComponent
import com.anytypeio.anytype.di.feature.home.DaggerHomeScreenComponent
import com.anytypeio.anytype.di.feature.library.DaggerLibraryComponent
import com.anytypeio.anytype.di.feature.membership.DaggerMembershipComponent
import com.anytypeio.anytype.di.feature.membership.DaggerMembershipUpdateComponent
import com.anytypeio.anytype.di.feature.multiplayer.DaggerRequestJoinSpaceComponent
@ -106,7 +105,6 @@ import com.anytypeio.anytype.gallery_experience.viewmodel.GalleryInstallationVie
import com.anytypeio.anytype.presentation.common.BaseViewModel
import com.anytypeio.anytype.presentation.editor.EditorViewModel
import com.anytypeio.anytype.presentation.history.VersionHistoryViewModel
import com.anytypeio.anytype.presentation.library.LibraryViewModel
import com.anytypeio.anytype.presentation.multiplayer.RequestJoinSpaceViewModel
import com.anytypeio.anytype.presentation.multiplayer.ShareSpaceViewModel
import com.anytypeio.anytype.presentation.multiplayer.SpaceJoinRequestViewModel
@ -738,14 +736,6 @@ class ComponentManager(
.create(findComponentDependencies())
}
val libraryComponent = ComponentWithParams { (ctx, params) : Pair<Context, LibraryViewModel.Params> ->
DaggerLibraryComponent.builder()
.withContext(ctx)
.withParams(params)
.withDependencies(findComponentDependencies())
.build()
}
val backLinkOrAddToObjectComponent = ComponentWithParams { ctx: Id ->
DaggerBacklinkOrAddToObjectComponent.builder()
.withContext(ctx)

View file

@ -1,218 +0,0 @@
package com.anytypeio.anytype.di.feature.library
import android.content.Context
import androidx.lifecycle.ViewModelProvider
import com.anytypeio.anytype.analytics.base.Analytics
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.config.ConfigStorage
import com.anytypeio.anytype.domain.config.UserSettingsRepository
import com.anytypeio.anytype.domain.debugging.Logger
import com.anytypeio.anytype.domain.launch.GetDefaultObjectType
import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.domain.`object`.SetObjectDetails
import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes
import com.anytypeio.anytype.domain.page.CreateObject
import com.anytypeio.anytype.domain.search.SubscriptionEventChannel
import com.anytypeio.anytype.domain.templates.GetTemplates
import com.anytypeio.anytype.domain.workspace.AddObjectToWorkspace
import com.anytypeio.anytype.domain.workspace.RemoveObjectsFromWorkspace
import com.anytypeio.anytype.domain.workspace.SpaceManager
import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate
import com.anytypeio.anytype.presentation.library.LibraryListDelegate
import com.anytypeio.anytype.presentation.library.LibraryResourceManager
import com.anytypeio.anytype.presentation.library.LibraryViewModel
import com.anytypeio.anytype.presentation.library.delegates.LibraryRelationsDelegate
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.ui.library.LibraryFragment
import dagger.Binds
import dagger.BindsInstance
import dagger.Component
import dagger.Module
import dagger.Provides
@Component(
dependencies = [LibraryDependencies::class],
modules = [
LibraryModule::class,
LibraryModule.Declarations::class
]
)
@PerScreen
interface LibraryComponent {
@Component.Builder
interface Builder {
fun withDependencies(dependencies: LibraryDependencies): Builder
@BindsInstance
fun withContext(context: Context): Builder
@BindsInstance
fun withParams(params: LibraryViewModel.Params): Builder
fun build(): LibraryComponent
}
fun inject(fragment: LibraryFragment)
}
@Module
object LibraryModule {
@PerScreen
@Provides
@JvmStatic
fun provideMyTypesDelegate(
container: StorelessSubscriptionContainer,
spaceManager: SpaceManager,
urlBuilder: UrlBuilder,
dispatchers: AppCoroutineDispatchers
): LibraryListDelegate {
return MyTypesDelegate(
container = container,
spaceManager = spaceManager,
urlBuilder = urlBuilder,
dispatchers = dispatchers
)
}
@PerScreen
@Provides
@JvmStatic
fun provideLibTypesDelegate(
container: StorelessSubscriptionContainer,
urlBuilder: UrlBuilder,
dispatchers: AppCoroutineDispatchers
): LibraryListDelegate {
return LibraryTypesDelegate(container, urlBuilder, dispatchers)
}
@PerScreen
@Provides
@JvmStatic
fun provideMyRelationsDelegate(
container: StorelessSubscriptionContainer,
spaceManager: SpaceManager,
urlBuilder: UrlBuilder,
dispatchers: AppCoroutineDispatchers
): LibraryListDelegate {
return MyRelationsDelegate(
container = container,
spaceManager = spaceManager,
urlBuilder = urlBuilder,
dispatchers = dispatchers
)
}
@PerScreen
@Provides
@JvmStatic
fun provideLibRelationsDelegate(
container: StorelessSubscriptionContainer,
urlBuilder: UrlBuilder,
dispatchers: AppCoroutineDispatchers
): LibraryListDelegate {
return LibraryRelationsDelegate(container, urlBuilder, dispatchers)
}
@Provides
@PerScreen
@JvmStatic
fun addObjectToWorkspace(
repo: BlockRepository,
dispatchers: AppCoroutineDispatchers
): AddObjectToWorkspace = AddObjectToWorkspace(
repo = repo,
dispatchers = dispatchers
)
@Provides
@PerScreen
@JvmStatic
fun removeObjectFromWorkspace(
repo: BlockRepository,
dispatchers: AppCoroutineDispatchers
): RemoveObjectsFromWorkspace = RemoveObjectsFromWorkspace(repo, dispatchers)
@JvmStatic
@Provides
@PerScreen
fun getCreateObject(
repo: BlockRepository,
getDefaultObjectType: GetDefaultObjectType,
dispatchers: AppCoroutineDispatchers,
): CreateObject = CreateObject(
repo = repo,
getDefaultObjectType = getDefaultObjectType,
dispatchers = dispatchers
)
@JvmStatic
@Provides
@PerScreen
fun provideGetDefaultPageType(
userSettingsRepository: UserSettingsRepository,
blockRepository: BlockRepository,
dispatchers: AppCoroutineDispatchers
): GetDefaultObjectType = GetDefaultObjectType(
userSettingsRepository = userSettingsRepository,
blockRepository = blockRepository,
dispatchers = dispatchers
)
@JvmStatic
@Provides
@PerScreen
fun provideGetTemplates(
repo: BlockRepository,
spaceManager: SpaceManager,
dispatchers: AppCoroutineDispatchers
): GetTemplates = GetTemplates(
repo = repo,
spaceManager = spaceManager,
dispatchers = dispatchers
)
@Provides
@PerScreen
@JvmStatic
fun objectSetDetails(
repo: BlockRepository,
dispatchers: AppCoroutineDispatchers
): SetObjectDetails = SetObjectDetails(repo = repo, dispatchers = dispatchers)
@Module
interface Declarations {
@PerScreen
@Binds
fun bindViewModelFactory(factory: LibraryViewModel.Factory): ViewModelProvider.Factory
@PerScreen
@Binds
fun bindResourceManager(manager: LibraryResourceManager.Impl): LibraryResourceManager
}
}
interface LibraryDependencies : ComponentDependencies {
fun blockRepository(): BlockRepository
fun urlBuilder(): UrlBuilder
fun channel(): SubscriptionEventChannel
fun dispatchers(): AppCoroutineDispatchers
fun userSettingsRepository(): UserSettingsRepository
fun analytics(): Analytics
fun spaceManager(): SpaceManager
fun config(): ConfigStorage
fun logger(): Logger
fun container(): StorelessSubscriptionContainer
fun storeOfTypes (): StoreOfObjectTypes
fun analyticSpaceHelperDelegate(): AnalyticSpaceHelperDelegate
}

View file

@ -23,7 +23,6 @@ import com.anytypeio.anytype.di.feature.auth.DeletedAccountDependencies
import com.anytypeio.anytype.di.feature.discussions.DiscussionComponentDependencies
import com.anytypeio.anytype.di.feature.gallery.GalleryInstallationComponentDependencies
import com.anytypeio.anytype.di.feature.home.HomeScreenDependencies
import com.anytypeio.anytype.di.feature.library.LibraryDependencies
import com.anytypeio.anytype.di.feature.membership.MembershipComponentDependencies
import com.anytypeio.anytype.di.feature.membership.MembershipUpdateComponentDependencies
import com.anytypeio.anytype.di.feature.multiplayer.RequestJoinSpaceDependencies
@ -90,7 +89,6 @@ import javax.inject.Singleton
)
interface MainComponent :
AppearanceDependencies,
LibraryDependencies,
HomeScreenDependencies,
CollectionDependencies,
CreateObjectTypeDependencies,
@ -164,11 +162,6 @@ abstract class ComponentDependenciesModule {
@ComponentDependenciesKey(AppearanceDependencies::class)
abstract fun provideAppearanceDependencies(component: MainComponent): ComponentDependencies
@Binds
@IntoMap
@ComponentDependenciesKey(LibraryDependencies::class)
abstract fun provideLibraryDependencies(component: MainComponent): ComponentDependencies
@Binds
@IntoMap
@ComponentDependenciesKey(HomeScreenDependencies::class)

View file

@ -13,7 +13,6 @@ import com.anytypeio.anytype.ui.allcontent.AllContentFragment
import com.anytypeio.anytype.ui.auth.account.DeletedAccountFragment
import com.anytypeio.anytype.ui.editor.EditorFragment
import com.anytypeio.anytype.ui.editor.EditorModalFragment
import com.anytypeio.anytype.ui.library.LibraryFragment
import com.anytypeio.anytype.ui.relations.RelationCreateFromScratchForObjectFragment
import com.anytypeio.anytype.ui.relations.RelationEditFragment
import com.anytypeio.anytype.ui.search.GlobalSearchFragment
@ -245,13 +244,6 @@ class Navigator : AppNavigation {
})
}
override fun openLibrary(space: Id) {
navController?.navigate(
R.id.libraryFragment,
LibraryFragment.args(space)
)
}
override fun openRemoteFilesManageScreen(subscription: Id, space: Id) {
navController?.navigate(
resId = R.id.remoteStorageFragment,

View file

@ -27,11 +27,11 @@ import com.anytypeio.anytype.core_utils.ext.toast
import com.anytypeio.anytype.core_utils.insets.EDGE_TO_EDGE_MIN_SDK
import com.anytypeio.anytype.core_utils.ui.BaseComposeFragment
import com.anytypeio.anytype.di.common.componentManager
import com.anytypeio.anytype.feature_allcontent.models.AllContentTab
import com.anytypeio.anytype.feature_allcontent.presentation.AllContentViewModel
import com.anytypeio.anytype.feature_allcontent.presentation.AllContentViewModelFactory
import com.anytypeio.anytype.feature_allcontent.ui.AllContentNavigation.ALL_CONTENT_MAIN
import com.anytypeio.anytype.feature_allcontent.ui.AllContentWrapperScreen
import com.anytypeio.anytype.presentation.library.LibraryViewModel
import com.anytypeio.anytype.presentation.objects.ObjectIcon
import com.anytypeio.anytype.presentation.widgets.collection.Subscription
import com.anytypeio.anytype.ui.base.navigation
@ -65,7 +65,7 @@ class AllContentFragment : BaseComposeFragment(), ObjectTypeSelectionListener {
setFragmentResultListener(REQUEST_KEY_UNINSTALL_TYPE) { _, bundle ->
val id = requireNotNull(bundle.getString(REQUEST_UNINSTALL_TYPE_ARG_ID))
val name = requireNotNull(bundle.getString(REQUEST_UNINSTALL_TYPE_ARG_NAME))
vm.uninstallObject(id, LibraryViewModel.LibraryItem.TYPE, name)
vm.uninstallObject(id, AllContentTab.TYPES, name)
}
setFragmentResultListener(REQUEST_KEY_MODIFY_TYPE) { _, bundle ->
val id = requireNotNull(bundle.getString(REQUEST_UNINSTALL_TYPE_ARG_ID))
@ -76,7 +76,7 @@ class AllContentFragment : BaseComposeFragment(), ObjectTypeSelectionListener {
setFragmentResultListener(REQUEST_KEY_UNINSTALL_RELATION) { _, bundle ->
val id = requireNotNull(bundle.getString(REQUEST_UNINSTALL_RELATION_ARG_ID))
val name = requireNotNull(bundle.getString(REQUEST_UNINSTALL_RELATION_ARG_NAME))
vm.uninstallObject(id, LibraryViewModel.LibraryItem.RELATION, name)
vm.uninstallObject(id, AllContentTab.RELATIONS, name)
}
setFragmentResultListener(REQUEST_KEY_MODIFY_RELATION) { _, bundle ->
val id = requireNotNull(bundle.getString(REQUEST_UNINSTALL_RELATION_ARG_ID))

View file

@ -54,7 +54,6 @@ class NavigationRouter(
is AppNavigation.Command.OpenTemplates -> navigation.openTemplatesModal(
typeId = command.typeId
)
is AppNavigation.Command.OpenLibrary -> navigation.openLibrary(command.space)
is AppNavigation.Command.MigrationErrorScreen -> navigation.migrationErrorScreen()
else -> Timber.d("Nav command ignored: $command")
}

View file

@ -46,7 +46,6 @@ import com.anytypeio.anytype.core_ui.foundation.components.BottomNavigationMenu
import com.anytypeio.anytype.core_ui.foundation.noRippleClickable
import com.anytypeio.anytype.core_ui.views.UXBody
import com.anytypeio.anytype.presentation.home.InteractionMode
import com.anytypeio.anytype.presentation.profile.ProfileIconView
import com.anytypeio.anytype.presentation.widgets.DropDownMenuAction
import com.anytypeio.anytype.presentation.widgets.FromIndex
import com.anytypeio.anytype.presentation.widgets.ToIndex
@ -60,7 +59,6 @@ import com.anytypeio.anytype.ui.widgets.types.AllContentWidgetCard
import com.anytypeio.anytype.ui.widgets.types.BinWidgetCard
import com.anytypeio.anytype.ui.widgets.types.DataViewListWidgetCard
import com.anytypeio.anytype.ui.widgets.types.GalleryWidgetCard
import com.anytypeio.anytype.ui.widgets.types.LibraryWidgetCard
import com.anytypeio.anytype.ui.widgets.types.LinkWidgetCard
import com.anytypeio.anytype.ui.widgets.types.ListWidgetCard
import com.anytypeio.anytype.ui.widgets.types.SpaceChatWidgetCard
@ -87,7 +85,6 @@ fun HomeScreen(
onToggleExpandedWidgetState: (WidgetId) -> Unit,
onExitEditMode: () -> Unit,
onSearchClicked: () -> Unit,
onLibraryClicked: () -> Unit,
onCreateNewObjectClicked: () -> Unit,
onCreateNewObjectLongClicked: () -> Unit,
onBackClicked: () -> Unit,
@ -113,7 +110,6 @@ fun HomeScreen(
mode = mode,
onChangeWidgetView = onChangeWidgetView,
onEditWidgets = onEditWidgets,
onLibraryClicked = onLibraryClicked,
onSpaceWidgetClicked = onSpaceWidgetClicked,
onMove = onMove,
onObjectCheckboxClicked = onObjectCheckboxClicked,
@ -185,7 +181,6 @@ private fun WidgetList(
mode: InteractionMode,
onChangeWidgetView: (WidgetId, ViewId) -> Unit,
onEditWidgets: () -> Unit,
onLibraryClicked: () -> Unit,
onMove: (List<WidgetView>, FromIndex, ToIndex) -> Unit,
onObjectCheckboxClicked: (Id, Boolean) -> Unit,
onSpaceWidgetClicked: () -> Unit,
@ -429,15 +424,6 @@ private fun WidgetList(
onWidgetClicked = { onBundledWidgetHeaderClicked(item.id) }
)
}
is WidgetView.Library -> {
LibraryWidgetCard(
onDropDownMenuAction = { action ->
onWidgetMenuAction(item.id, action)
},
onClick = onLibraryClicked,
mode = mode
)
}
is WidgetView.Action.EditWidgets -> {
Box(
Modifier

View file

@ -35,7 +35,6 @@ import com.anytypeio.anytype.presentation.home.HomeScreenViewModel.Navigation
import com.anytypeio.anytype.presentation.widgets.DropDownMenuAction
import com.anytypeio.anytype.ui.base.navigation
import com.anytypeio.anytype.ui.gallery.GalleryInstallationFragment
import com.anytypeio.anytype.ui.library.LibraryFragment
import com.anytypeio.anytype.ui.multiplayer.RequestJoinSpaceFragment
import com.anytypeio.anytype.ui.multiplayer.ShareSpaceFragment
import com.anytypeio.anytype.ui.objects.creation.ObjectTypeSelectionFragment
@ -102,9 +101,6 @@ class HomeScreenFragment : BaseComposeFragment(),
onChangeWidgetView = vm::onChangeCurrentWidgetView,
onToggleExpandedWidgetState = vm::onToggleCollapsedWidgetState,
onSearchClicked = vm::onSearchIconClicked,
onLibraryClicked = {
vm.onLibraryClicked()
},
onCreateNewObjectClicked = throttledClick(
onClick = { vm.onCreateNewObjectClicked() }
),
@ -343,14 +339,6 @@ class HomeScreenFragment : BaseComposeFragment(),
space = destination.space
)
}
is Navigation.OpenLibrary -> runCatching {
findNavController().navigate(
R.id.libraryFragment,
args = LibraryFragment.args(destination.space)
)
}.onFailure { e ->
Timber.e(e, "Error while opening space library from widgets")
}
is Navigation.OpenAllContent -> {
runCatching {
navigation().openAllContent(space = destination.space)

View file

@ -1,243 +0,0 @@
package com.anytypeio.anytype.ui.library
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.MaterialTheme
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.core.os.bundleOf
import androidx.fragment.app.setFragmentResultListener
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import com.anytypeio.anytype.BuildConfig
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.primitives.SpaceId
import com.anytypeio.anytype.core_ui.extensions.simpleIcon
import com.anytypeio.anytype.core_utils.ext.arg
import com.anytypeio.anytype.core_utils.ext.safeNavigate
import com.anytypeio.anytype.core_utils.ext.subscribe
import com.anytypeio.anytype.core_utils.ext.toast
import com.anytypeio.anytype.core_utils.insets.EDGE_TO_EDGE_MIN_SDK
import com.anytypeio.anytype.core_utils.ui.BaseComposeFragment
import com.anytypeio.anytype.di.common.componentManager
import com.anytypeio.anytype.presentation.library.LibraryViewModel
import com.anytypeio.anytype.presentation.objects.ObjectIcon
import com.anytypeio.anytype.ui.editor.EditorFragment
import com.anytypeio.anytype.ui.relations.REQUEST_KEY_MODIFY_RELATION
import com.anytypeio.anytype.ui.relations.REQUEST_KEY_UNINSTALL_RELATION
import com.anytypeio.anytype.ui.relations.REQUEST_UNINSTALL_RELATION_ARG_ID
import com.anytypeio.anytype.ui.relations.REQUEST_UNINSTALL_RELATION_ARG_NAME
import com.anytypeio.anytype.ui.relations.RelationCreateFromScratchForObjectFragment
import com.anytypeio.anytype.ui.relations.RelationEditFragment
import com.anytypeio.anytype.ui.sets.ObjectSetFragment
import com.anytypeio.anytype.ui.settings.typography
import com.anytypeio.anytype.ui.types.create.CreateObjectTypeFragment
import com.anytypeio.anytype.ui.types.create.REQUEST_CREATE_OBJECT
import com.anytypeio.anytype.ui.types.edit.REQUEST_KEY_MODIFY_TYPE
import com.anytypeio.anytype.ui.types.edit.REQUEST_KEY_UNINSTALL_TYPE
import com.anytypeio.anytype.ui.types.edit.REQUEST_UNINSTALL_TYPE_ARG_ICON
import com.anytypeio.anytype.ui.types.edit.REQUEST_UNINSTALL_TYPE_ARG_ID
import com.anytypeio.anytype.ui.types.edit.REQUEST_UNINSTALL_TYPE_ARG_NAME
import com.anytypeio.anytype.ui.types.edit.TypeEditFragment
import javax.inject.Inject
import kotlinx.coroutines.FlowPreview
import timber.log.Timber
@Deprecated("legacy")
class LibraryFragment : BaseComposeFragment() {
@Inject
lateinit var factory: LibraryViewModel.Factory
private val vm by viewModels<LibraryViewModel> { factory }
private val space get() = arg<Id>(ARG_SPACE_ID_KEY)
@OptIn(ExperimentalAnimationApi::class)
@FlowPreview
@ExperimentalMaterialApi
@ExperimentalComposeUiApi
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View {
return ComposeView(requireContext()).apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setContent {
MaterialTheme(typography = typography) {
LibraryScreen(
configuration = LibraryConfiguration(),
viewModel = vm,
onBackPressed = {
findNavController().popBackStack()
},
onCreateObjectLongClicked = {},
onBackLongPressed = {
runCatching {
findNavController().navigate(R.id.actionExitToSpaceWidgets)
}.onFailure {
Timber.e(it, "Error while opening space switcher from library")
}
}
)
}
}
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
subscribe(vm.toasts) { toast(it) }
subscribe(vm.navigation) {
when (it) {
is LibraryViewModel.Navigation.OpenTypeCreation -> {
findNavController().safeNavigate(
R.id.libraryFragment,
R.id.openTypeCreationScreen,
CreateObjectTypeFragment.args(it.name)
)
}
is LibraryViewModel.Navigation.OpenTypeEditing -> {
findNavController().safeNavigate(
R.id.libraryFragment,
R.id.openTypeEditingScreen,
TypeEditFragment.args(
typeName = it.view.name,
id = it.view.id,
iconUnicode = (it.view.icon as? ObjectIcon.Basic.Emoji)?.unicode ?: "",
readOnly = it.view.readOnly
)
)
}
is LibraryViewModel.Navigation.OpenRelationCreation -> {
findNavController().safeNavigate(
R.id.libraryFragment,
R.id.openRelationCreationScreen,
RelationCreateFromScratchForObjectFragment.args(
ctx = "",
query = it.name,
space = space
)
)
}
is LibraryViewModel.Navigation.OpenRelationEditing -> {
findNavController().safeNavigate(
R.id.libraryFragment,
R.id.openRelationEditingScreen,
RelationEditFragment.args(
typeName = it.view.name,
id = it.view.id,
iconUnicode = it.view.format.simpleIcon() ?: 0,
readOnly = it.view.readOnly
)
)
}
is LibraryViewModel.Navigation.Back -> {
findNavController().popBackStack()
}
is LibraryViewModel.Navigation.Search -> {
findNavController().safeNavigate(
R.id.libraryFragment,
R.id.pageSearchFragment
)
}
is LibraryViewModel.Navigation.OpenEditor -> {
findNavController().safeNavigate(
R.id.libraryFragment,
R.id.objectNavigation,
EditorFragment.args(
ctx = it.id,
space = space
)
)
}
is LibraryViewModel.Navigation.ExitToVault -> {
runCatching {
findNavController().navigate(R.id.actionOpenVault)
}.onFailure { e ->
Timber.e(e, "Error while exiting to vault from space library")
}
}
is LibraryViewModel.Navigation.OpenSetOrCollection -> {
findNavController().safeNavigate(
R.id.libraryFragment,
R.id.dataViewNavigation,
bundleOf(
ObjectSetFragment.CONTEXT_ID_KEY to it.id,
ObjectSetFragment.SPACE_ID_KEY to space
)
)
}
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setFragmentResultListener(REQUEST_KEY_UNINSTALL_TYPE) { _, bundle ->
val id = requireNotNull(bundle.getString(REQUEST_UNINSTALL_TYPE_ARG_ID))
val name = requireNotNull(bundle.getString(REQUEST_UNINSTALL_TYPE_ARG_NAME))
vm.uninstallObject(id, LibraryViewModel.LibraryItem.TYPE, name)
}
setFragmentResultListener(REQUEST_KEY_MODIFY_TYPE) { _, bundle ->
val id = requireNotNull(bundle.getString(REQUEST_UNINSTALL_TYPE_ARG_ID))
val name = requireNotNull(bundle.getString(REQUEST_UNINSTALL_TYPE_ARG_NAME))
val icon = requireNotNull(bundle.getString(REQUEST_UNINSTALL_TYPE_ARG_ICON))
vm.updateObject(id, name, icon)
}
setFragmentResultListener(REQUEST_KEY_UNINSTALL_RELATION) { _, bundle ->
val id = requireNotNull(bundle.getString(REQUEST_UNINSTALL_RELATION_ARG_ID))
val name = requireNotNull(bundle.getString(REQUEST_UNINSTALL_RELATION_ARG_NAME))
vm.uninstallObject(id, LibraryViewModel.LibraryItem.RELATION, name)
}
setFragmentResultListener(REQUEST_KEY_MODIFY_RELATION) { _, bundle ->
val id = requireNotNull(bundle.getString(REQUEST_UNINSTALL_RELATION_ARG_ID))
val name = requireNotNull(bundle.getString(REQUEST_UNINSTALL_RELATION_ARG_NAME))
vm.updateObject(
id = id,
name = name,
icon = null
)
}
setFragmentResultListener(REQUEST_CREATE_OBJECT) { _, _ ->
vm.onObjectCreated()
}
}
override fun injectDependencies() {
componentManager()
.libraryComponent
.get(
Pair(
requireContext(),
LibraryViewModel.Params(
space = SpaceId(space)
)
)
)
.inject(this)
}
override fun releaseDependencies() {
componentManager().libraryComponent.release()
}
override fun onApplyWindowRootInsets(view: View) {
if (BuildConfig.USE_EDGE_TO_EDGE && Build.VERSION.SDK_INT >= EDGE_TO_EDGE_MIN_SDK) {
// Do nothing.
} else {
super.onApplyWindowRootInsets(view)
}
}
companion object {
const val ARG_SPACE_ID_KEY = "arg.library.space-id"
fun args(space: Id) = bundleOf(ARG_SPACE_ID_KEY to space)
}
}

View file

@ -1,199 +0,0 @@
@file:OptIn(ExperimentalAnimationApi::class)
package com.anytypeio.anytype.ui.library
import android.os.Build
import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.AnimatedVisibilityScope
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.core.FastOutLinearInEasing
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.TweenSpec
import androidx.compose.animation.core.VisibilityThreshold
import androidx.compose.animation.core.spring
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.ime
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.unit.IntSize
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.anytypeio.anytype.BuildConfig
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_ui.foundation.components.BottomNavigationMenu
import com.anytypeio.anytype.core_utils.insets.EDGE_TO_EDGE_MIN_SDK
import com.anytypeio.anytype.presentation.library.LibraryEvent
import com.anytypeio.anytype.presentation.library.LibraryViewModel
import com.anytypeio.anytype.ui.library.views.LibraryTabs
import com.anytypeio.anytype.ui.library.views.LibraryTabsContent
import kotlinx.coroutines.FlowPreview
@ExperimentalAnimationApi
@ExperimentalComposeUiApi
@ExperimentalMaterialApi
@FlowPreview
@Composable
fun LibraryScreen(
configuration: LibraryConfiguration,
viewModel: LibraryViewModel,
onBackPressed: () -> Unit,
onBackLongPressed: () -> Unit,
onCreateObjectLongClicked: () -> Unit
) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
val effects by viewModel.effects.collectAsStateWithLifecycle()
val pagerState = rememberPagerState(
initialPage = INITIAL_TAB,
pageCount = { 2 }
)
val modifier = Modifier
.background(color = colorResource(id = R.color.background_primary))
val screenState = remember { mutableStateOf(ScreenState.CONTENT) }
Scaffold(
bottomBar = {
Box(
modifier = modifier
.then(
if (BuildConfig.USE_EDGE_TO_EDGE && Build.VERSION.SDK_INT >= EDGE_TO_EDGE_MIN_SDK)
Modifier
.windowInsetsPadding(WindowInsets.navigationBars)
else
Modifier
)
.fillMaxWidth()
) {
Menu(
viewModel,
modifier = Modifier.align(Alignment.BottomCenter),
screenState = screenState,
onCreateObjectLongClicked = onCreateObjectLongClicked,
onBackLongClicked = onBackLongPressed
)
}
}
) { paddingValues ->
Column(
modifier = modifier
.padding(paddingValues)
.then(
if (BuildConfig.USE_EDGE_TO_EDGE && Build.VERSION.SDK_INT >= EDGE_TO_EDGE_MIN_SDK)
Modifier.windowInsetsPadding(WindowInsets.systemBars)
else
Modifier
),
) {
LibraryTabs(
modifier = modifier,
pagerState = pagerState,
configuration = configuration,
screenState = screenState,
)
LibraryTabsContent(
modifier = modifier,
pagerState = pagerState,
configuration = listOf(configuration.types, configuration.relations),
state = uiState,
vmEventStream = viewModel::eventStream,
vmAnalyticsStream = viewModel::analyticsStream,
screenState = screenState,
effects = effects
)
}
}
BackHandler {
if (screenState.value == ScreenState.SEARCH) {
screenState.value = ScreenState.CONTENT
} else {
onBackPressed.invoke()
}
}
}
@Composable
fun Menu(
viewModel: LibraryViewModel,
modifier: Modifier = Modifier,
screenState: MutableState<ScreenState>,
onCreateObjectLongClicked: () -> Unit = {},
onBackLongClicked: () -> Unit
) {
val isImeVisible = WindowInsets.ime.getBottom(LocalDensity.current) > 0
if (isImeVisible) return
BottomNavigationMenu(
modifier = modifier,
backClick = {
if (screenState.value == ScreenState.SEARCH) {
screenState.value = ScreenState.CONTENT
} else {
viewModel.eventStream(LibraryEvent.BottomMenu.Back)
}
},
backLongClick = {
onBackLongClicked()
},
searchClick = { viewModel.eventStream(LibraryEvent.BottomMenu.Search) },
addDocClick = { viewModel.eventStream(LibraryEvent.BottomMenu.CreateObject) },
addDocLongClick = onCreateObjectLongClicked,
isOwnerOrEditor = false
)
}
@Composable
fun WrapWithLibraryAnimation(
visible: Boolean,
content: @Composable AnimatedVisibilityScope.() -> Unit
) {
val animationSpecFade = TweenSpec<Float>(
easing = FastOutLinearInEasing,
durationMillis = FADE_DURATION_MILLIS
)
val animationSpecSlide = spring(
stiffness = Spring.StiffnessMediumLow,
visibilityThreshold = IntSize.VisibilityThreshold
)
AnimatedVisibility(
visible = visible,
enter = fadeIn(animationSpecFade) + expandVertically(animationSpecSlide),
exit = fadeOut(animationSpecFade) + shrinkVertically(animationSpecSlide)
) { content() }
}
enum class ScreenState {
CONTENT,
SEARCH;
fun visible() = this == CONTENT
}
private const val INITIAL_TAB = 0
private const val FADE_DURATION_MILLIS = 50

View file

@ -1,67 +0,0 @@
package com.anytypeio.anytype.ui.library
import androidx.annotation.StringRes
import androidx.compose.ui.Alignment
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.anytypeio.anytype.R
data class LibraryConfiguration(
val types: LibraryScreenConfig = LibraryScreenConfig.Types(),
val relations: LibraryScreenConfig = LibraryScreenConfig.Relations()
)
sealed class LibraryScreenConfig(
@StringRes val mainTitle: Int,
@StringRes val mainBtnTitle: Int,
@StringRes val description: Int,
val listConfig: List<LibraryListConfig>,
val index: Int,
val titleAlignment: Alignment.Horizontal,
val titlePaddingEnd: Dp,
val titlePaddingStart: Dp
) {
class Types : LibraryScreenConfig(
R.string.library_title_types,
R.string.library_button_create_type,
R.string.library_description_types,
listOf(LibraryListConfig.Types, LibraryListConfig.TypesLibrary),
0,
Alignment.End,
0.dp,
16.dp
)
class Relations : LibraryScreenConfig(
R.string.library_title_relations,
R.string.library_button_create_relation,
R.string.library_description_relations,
listOf(LibraryListConfig.Relations, LibraryListConfig.RelationsLibrary),
1,
Alignment.Start,
16.dp,
0.dp
)
}
sealed class LibraryListConfig(
@StringRes val title: Int,
val subtitleTabOffset: Dp,
) {
object Types : LibraryListConfig(
R.string.library_subtitle_types, 0.dp
)
object TypesLibrary : LibraryListConfig(
R.string.library_subtitle_library, (-14).dp
)
object Relations : LibraryListConfig(
R.string.library_subtitle_relations, 0.dp
)
object RelationsLibrary : LibraryListConfig(
R.string.library_subtitle_library, (-14).dp
)
}

View file

@ -1,21 +0,0 @@
package com.anytypeio.anytype.ui.library.styles
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
import com.anytypeio.anytype.R
import androidx.compose.ui.text.TextStyle as TextStyle
val fonts = FontFamily(
Font(R.font.inter_regular),
Font(R.font.inter_bold, weight = FontWeight.Bold),
Font(R.font.inter_medium, weight = FontWeight.Medium),
Font(R.font.inter_semibold, weight = FontWeight.SemiBold)
)
val TabTitleStyle = TextStyle(
fontFamily = fonts,
fontSize = 15.sp,
fontWeight = FontWeight.SemiBold
)

View file

@ -1,53 +0,0 @@
package com.anytypeio.anytype.ui.library.views
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.material.TabPosition
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@Composable
fun Modifier.libraryTabOffset(
currentTabPosition: TabPosition,
tabWidth: Dp
): Modifier {
val currentTabWidth = animateDpAsState(
targetValue = tabWidth,
animationSpec = tween(
durationMillis = ANIMATION_LENGTH,
easing = FastOutSlowInEasing
)
)
val targetValue = if (currentTabPosition.left == 0.dp) {
(currentTabPosition.left + currentTabPosition.right - tabWidth - TAB_OFFSET.dp)
} else {
(currentTabPosition.left)
}
val indicatorOffset = animateDpAsState(
targetValue = targetValue,
animationSpec = tween(
durationMillis = ANIMATION_LENGTH,
easing = FastOutSlowInEasing
)
)
return fillMaxWidth()
.wrapContentSize(Alignment.BottomStart)
.offset(x = indicatorOffset.value)
.height(INDICATOR_HEIGHT.dp)
.width(currentTabWidth.value + TAB_OFFSET.dp)
}
private const val TAB_OFFSET = 32
private const val INDICATOR_HEIGHT = 1
private const val ANIMATION_LENGTH = 150

View file

@ -1,142 +0,0 @@
package com.anytypeio.anytype.ui.library.views
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.pager.PagerState
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.LocalRippleConfiguration
import androidx.compose.material.RippleConfiguration
import androidx.compose.material.Tab
import androidx.compose.material.TabRow
import androidx.compose.material.TabRowDefaults
import androidx.compose.material.Text
import androidx.compose.material.ripple.RippleAlpha
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.unit.dp
import com.anytypeio.anytype.R
import com.anytypeio.anytype.ui.library.LibraryConfiguration
import com.anytypeio.anytype.ui.library.LibraryScreenConfig
import com.anytypeio.anytype.ui.library.ScreenState
import com.anytypeio.anytype.ui.library.WrapWithLibraryAnimation
import com.anytypeio.anytype.ui.library.styles.TabTitleStyle
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterialApi::class)
@Composable
fun LibraryTabs(
modifier: Modifier,
pagerState: PagerState,
configuration: LibraryConfiguration,
screenState: MutableState<ScreenState>
) = WrapWithLibraryAnimation(visible = screenState.value.visible()) {
val coroutineScope = rememberCoroutineScope()
val density = LocalDensity.current
val tabWidths = remember {
val tabWidthStateList = mutableStateListOf(0.dp, 0.dp)
tabWidthStateList
}
TabRow(
modifier = modifier,
selectedTabIndex = pagerState.currentPage,
backgroundColor = colorResource(id = R.color.background_primary),
divider = {},
indicator = { tabPositions ->
TabRowDefaults.Indicator(
modifier = Modifier
.libraryTabOffset(
currentTabPosition = tabPositions[pagerState.currentPage],
tabWidth = tabWidths[pagerState.currentPage]
),
color = colorResource(id = R.color.black)
)
},
tabs = {
CompositionLocalProvider(LocalRippleConfiguration provides LibraryRippleTheme) {
LibraryTab(
modifier = modifier,
config = configuration.types,
pagerState = pagerState,
onClick = {
coroutineScope.launch {
pagerState.animateScrollToPage(0)
}
},
onTextLayout = { tlResult ->
tabWidths[0] = with(density) { tlResult.size.width.toDp() }
}
)
LibraryTab(
modifier = modifier,
config = configuration.relations,
pagerState = pagerState,
onClick = {
coroutineScope.launch {
pagerState.animateScrollToPage(1)
}
},
onTextLayout = { tlResult ->
tabWidths[1] = with(density) { tlResult.size.width.toDp() }
}
)
}
}
)
}
@OptIn(ExperimentalMaterialApi::class)
private val LibraryRippleTheme = RippleConfiguration(color = Color.Unspecified, rippleAlpha = RippleAlpha(0f, 0f, 0f, 0f))
@Composable
fun LibraryTab(
modifier: Modifier,
config: LibraryScreenConfig,
pagerState: PagerState,
onClick: () -> Unit,
onTextLayout: (TextLayoutResult) -> Unit
) {
Tab(
modifier = modifier,
selectedContentColor = colorResource(id = R.color.glyph_selected),
unselectedContentColor = colorResource(id = R.color.glyph_active),
text = {
Column(
horizontalAlignment = config.titleAlignment,
modifier = Modifier
.fillMaxWidth()
.padding(
start = config.titlePaddingStart,
end = config.titlePaddingEnd,
)
) {
Text(
text = stringResource(id = config.mainTitle),
style = TabTitleStyle.copy(
color = if (pagerState.currentPage == config.index) {
colorResource(id = R.color.glyph_selected)
} else {
colorResource(id = R.color.glyph_active)
}
),
onTextLayout = onTextLayout::invoke
)
}
},
selected = pagerState.currentPage == config.index,
onClick = onClick,
)
}

View file

@ -1,147 +0,0 @@
package com.anytypeio.anytype.ui.library.views
import androidx.compose.animation.ExperimentalAnimationApi
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.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.PagerState
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_ui.views.ButtonPrimary
import com.anytypeio.anytype.core_ui.views.ButtonSize
import com.anytypeio.anytype.core_ui.views.HeadlineTitle
import com.anytypeio.anytype.presentation.library.LibraryAnalyticsEvent
import com.anytypeio.anytype.presentation.library.LibraryEvent
import com.anytypeio.anytype.presentation.library.LibraryScreenState
import com.anytypeio.anytype.presentation.library.LibraryViewModel
import com.anytypeio.anytype.ui.library.LibraryScreenConfig
import com.anytypeio.anytype.ui.library.ScreenState
import com.anytypeio.anytype.ui.library.WrapWithLibraryAnimation
import com.anytypeio.anytype.ui.library.views.list.LibraryListView
import kotlinx.coroutines.FlowPreview
@ExperimentalAnimationApi
@FlowPreview
@Composable
fun LibraryTabsContent(
modifier: Modifier,
pagerState: PagerState,
configuration: List<LibraryScreenConfig>,
state: LibraryScreenState,
vmEventStream: (LibraryEvent) -> Unit,
vmAnalyticsStream: (LibraryAnalyticsEvent.Ui) -> Unit,
screenState: MutableState<ScreenState>,
effects: LibraryViewModel.Effect,
) {
HorizontalPager(
modifier = modifier,
state = pagerState
) { page ->
val dataTabs = when (configuration[page]) {
is LibraryScreenConfig.Types -> {
state.types
}
is LibraryScreenConfig.Relations -> {
state.relations
}
}
TabContentScreen(
modifier = modifier,
config = configuration[page],
tabs = dataTabs,
vmEventStream = vmEventStream,
vmAnalyticsStream = vmAnalyticsStream,
screenState = screenState,
effects = effects
)
}
}
@ExperimentalAnimationApi
@FlowPreview
@Composable
fun TabContentScreen(
modifier: Modifier,
config: LibraryScreenConfig,
tabs: LibraryScreenState.Tabs,
vmEventStream: (LibraryEvent) -> Unit,
vmAnalyticsStream: (LibraryAnalyticsEvent.Ui) -> Unit,
screenState: MutableState<ScreenState>,
effects: LibraryViewModel.Effect
) {
Column(
modifier = modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Top,
) {
Header(config, vmEventStream, screenState)
LibraryListView(
libraryListConfig = config.listConfig,
tabs = tabs,
vmEventStream = vmEventStream,
vmAnalyticsStream = vmAnalyticsStream,
screenState = screenState,
effects = effects
)
}
}
@Composable
private fun Header(
config: LibraryScreenConfig,
vmEventStream: (LibraryEvent) -> Unit,
screenState: MutableState<ScreenState>
) = WrapWithLibraryAnimation(visible = screenState.value.visible()) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
text = stringResource(config.description),
style = HeadlineTitle.copy(
color = colorResource(id = R.color.text_primary)
),
textAlign = TextAlign.Center,
modifier = Modifier.padding(
top = 58.dp,
start = HeaderDefaults.HeaderPadding,
end = HeaderDefaults.HeaderPadding
)
)
Box(Modifier.height(14.dp))
ButtonPrimary(
onClick = {
when (config) {
is LibraryScreenConfig.Types -> {
vmEventStream.invoke(LibraryEvent.Type.Create())
}
is LibraryScreenConfig.Relations -> {
vmEventStream.invoke(LibraryEvent.Relation.Create())
}
}
},
modifier = Modifier.padding(bottom = 48.dp),
text = stringResource(config.mainBtnTitle),
size = ButtonSize.Medium.apply {
contentPadding = PaddingValues(28.dp, 10.dp, 28.dp, 10.dp)
}
)
}
}
private object HeaderDefaults {
val HeaderPadding = 20.dp
}

View file

@ -1,123 +0,0 @@
package com.anytypeio.anytype.ui.library.views
import androidx.compose.foundation.background
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.LocalTextStyle
import androidx.compose.material.MaterialTheme
import androidx.compose.material.TextFieldColors
import androidx.compose.material.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.takeOrElse
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.dp
import com.anytypeio.anytype.ui.library.views.LibraryTextFieldDefaults.ContentPaddingEnd
import com.anytypeio.anytype.ui.library.views.LibraryTextFieldDefaults.OffsetXSearchText
@Composable
fun LibraryTextField(
value: String,
onValueChange: (String) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
readOnly: Boolean = false,
textStyle: TextStyle = LocalTextStyle.current,
label: @Composable (() -> Unit)? = null,
placeholder: @Composable (() -> Unit)? = null,
leadingIcon: @Composable (() -> Unit)? = null,
trailingIcon: @Composable (() -> Unit)? = null,
isError: Boolean = false,
visualTransformation: VisualTransformation = VisualTransformation.None,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
keyboardActions: KeyboardActions = KeyboardActions.Default,
singleLine: Boolean = false,
maxLines: Int = Int.MAX_VALUE,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
shape: Shape = MaterialTheme.shapes.small,
colors: TextFieldColors = TextFieldDefaults.outlinedTextFieldColors()
) {
// If color is not provided via text style, use content color as a default
val textColor = textStyle.color.takeOrElse {
colors.textColor(enabled).value
}
val mergedTextStyle = textStyle.merge(TextStyle(color = textColor))
@OptIn(ExperimentalMaterialApi::class)
BasicTextField(
value = value,
modifier = if (label != null) {
modifier.semantics(mergeDescendants = true) {}
} else {
modifier
}
.background(colors.backgroundColor(enabled).value, shape)
.defaultMinSize(
minWidth = TextFieldDefaults.MinWidth,
minHeight = LibraryTextFieldDefaults.MinHeight
).offset(
x = OffsetXSearchText,
),
onValueChange = onValueChange,
enabled = enabled,
readOnly = readOnly,
textStyle = mergedTextStyle,
cursorBrush = SolidColor(colors.cursorColor(isError).value),
visualTransformation = visualTransformation,
keyboardOptions = keyboardOptions,
keyboardActions = keyboardActions,
interactionSource = interactionSource,
singleLine = singleLine,
maxLines = maxLines,
decorationBox = @Composable { innerTextField ->
TextFieldDefaults.OutlinedTextFieldDecorationBox(
value = value,
visualTransformation = visualTransformation,
innerTextField = innerTextField,
placeholder = placeholder,
label = label,
leadingIcon = leadingIcon,
trailingIcon = trailingIcon,
singleLine = singleLine,
enabled = enabled,
isError = isError,
interactionSource = interactionSource,
colors = colors,
contentPadding = PaddingValues(
start = 0.dp,
top = 0.dp,
end = ContentPaddingEnd,
bottom = 0.dp
),
border = {
TextFieldDefaults.BorderBox(
enabled,
isError,
interactionSource,
colors,
shape
)
}
)
}
)
}
@Immutable
object LibraryTextFieldDefaults {
val MinHeight = 36.dp
val ContentPaddingEnd = 16.dp
val OffsetXSearchText = (-12).dp
}

View file

@ -1,51 +0,0 @@
package com.anytypeio.anytype.ui.library.views.list
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.colorResource
import com.anytypeio.anytype.R
import com.anytypeio.anytype.presentation.library.LibraryAnalyticsEvent
import com.anytypeio.anytype.presentation.library.LibraryEvent
import com.anytypeio.anytype.presentation.library.LibraryScreenState
import com.anytypeio.anytype.presentation.library.LibraryViewModel
import com.anytypeio.anytype.ui.library.LibraryListConfig
import com.anytypeio.anytype.ui.library.ScreenState
@ExperimentalAnimationApi
@Composable
fun LibraryListView(
libraryListConfig: List<LibraryListConfig>,
tabs: LibraryScreenState.Tabs,
vmEventStream: (LibraryEvent) -> Unit,
vmAnalyticsStream: (LibraryAnalyticsEvent.Ui) -> Unit,
screenState: MutableState<ScreenState>,
effects: LibraryViewModel.Effect,
) {
val pagerState = rememberPagerState(
initialPage = INITIAL_TAB,
pageCount = { 2 }
)
val modifier = Modifier.background(
color = colorResource(id = R.color.background_primary)
)
Column {
LibraryListTabs(pagerState, libraryListConfig, modifier)
LibraryListTabsContent(
modifier = modifier,
pagerState = pagerState,
configuration = libraryListConfig,
tabs = tabs,
vmEventStream = vmEventStream,
vmAnalyticsStream = vmAnalyticsStream,
screenState = screenState,
effects = effects
)
}
}
private const val INITIAL_TAB = 0

View file

@ -1,100 +0,0 @@
package com.anytypeio.anytype.ui.library.views.list
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Text
import androidx.compose.material.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.MutableState
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.onFocusEvent
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_ui.views.UXBody
import com.anytypeio.anytype.presentation.library.LibraryEvent
import com.anytypeio.anytype.ui.library.LibraryListConfig
import com.anytypeio.anytype.ui.library.ScreenState
import com.anytypeio.anytype.ui.library.views.LibraryTextField
import com.anytypeio.anytype.ui.library.views.list.LibraryListSearchWidgetDefaults.CornerRadius
import com.anytypeio.anytype.ui.library.views.list.LibraryListSearchWidgetDefaults.Height
import com.anytypeio.anytype.ui.library.views.list.LibraryListSearchWidgetDefaults.LeadingIconOffset
import com.anytypeio.anytype.ui.library.views.list.LibraryListSearchWidgetDefaults.PaddingVertical
@Composable
fun LibraryListSearchWidget(
vmEventStream: (LibraryEvent) -> Unit,
config: LibraryListConfig,
modifier: Modifier,
animationStartState: MutableState<Boolean>,
screenState: MutableState<ScreenState>,
input: MutableState<String>
) {
LibraryTextField(
value = input.value,
onValueChange = {
input.value = it
vmEventStream.invoke(
config.toEvent(input.value)
)
},
shape = RoundedCornerShape(CornerRadius),
modifier = modifier
.padding(vertical = PaddingVertical)
.height(Height)
.onFocusEvent {
if (it.isFocused && animationStartState.value.not()) {
animationStartState.value = true
screenState.value = ScreenState.SEARCH
}
},
textStyle = UXBody,
placeholder = {
Text(
text = stringResource(id = R.string.search),
style = UXBody
)
},
colors = TextFieldDefaults.outlinedTextFieldColors(
textColor = colorResource(id = R.color.text_primary),
backgroundColor = colorResource(id = R.color.shape_transparent),
disabledBorderColor = Color.Transparent,
errorBorderColor = Color.Transparent,
focusedBorderColor = Color.Transparent,
unfocusedBorderColor = Color.Transparent,
placeholderColor = colorResource(id = R.color.glyph_active),
cursorColor = colorResource(id = R.color.orange)
),
singleLine = true,
maxLines = 1,
leadingIcon = {
Image(
painterResource(id = R.drawable.ic_search),
"",
modifier = Modifier.offset(x = LeadingIconOffset)
)
}
)
}
fun LibraryListConfig.toEvent(query: String): LibraryEvent.Query = when (this) {
LibraryListConfig.Relations -> LibraryEvent.Query.MyRelations(query)
LibraryListConfig.RelationsLibrary -> LibraryEvent.Query.LibraryRelations(query)
LibraryListConfig.Types -> LibraryEvent.Query.MyTypes(query)
LibraryListConfig.TypesLibrary -> LibraryEvent.Query.LibraryTypes(query)
}
@Immutable
private object LibraryListSearchWidgetDefaults {
val Height = 36.dp
val PaddingVertical = 6.dp
val CornerRadius = 10.dp
val LeadingIconOffset = 8.dp
}

View file

@ -1,92 +0,0 @@
package com.anytypeio.anytype.ui.library.views.list
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.pager.PagerState
import androidx.compose.material.ScrollableTabRow
import androidx.compose.material.Tab
import androidx.compose.material.Text
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.LocalRippleConfiguration
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_ui.views.HeadlineSubheading
import com.anytypeio.anytype.ui.library.LibraryListConfig
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class)
@ExperimentalAnimationApi
@Composable
fun LibraryListTabs(
pagerState: PagerState,
configuration: List<LibraryListConfig>,
modifier: Modifier,
) {
val scope = rememberCoroutineScope()
ScrollableTabRow(
selectedTabIndex = pagerState.currentPage,
backgroundColor = colorResource(id = R.color.background_primary),
indicator = {},
divider = {},
edgePadding = 0.dp,
modifier = modifier.padding(start = 4.dp),
tabs = {
CompositionLocalProvider(LocalRippleConfiguration provides null) {
configuration.forEachIndexed { index, it ->
LibraryListTab(
config = it,
pagerState = pagerState,
onClick = {
scope.launch {
pagerState.animateScrollToPage(index)
}
},
index = index,
modifier
)
}
}
}
)
}
@Composable
fun LibraryListTab(
config: LibraryListConfig,
pagerState: PagerState,
onClick: () -> Unit,
index: Int,
modifier: Modifier
) {
Tab(
selectedContentColor = colorResource(id = R.color.glyph_selected),
unselectedContentColor = colorResource(id = R.color.glyph_active),
modifier = modifier.wrapContentWidth(),
text = {
Text(
text = stringResource(id = config.title),
style = HeadlineSubheading.copy(
color = if (pagerState.currentPage == index) {
colorResource(id = R.color.glyph_selected)
} else {
colorResource(id = R.color.glyph_active)
}
),
modifier = modifier
.wrapContentWidth()
.offset(x = config.subtitleTabOffset),
)
},
selected = pagerState.currentPage == index,
onClick = onClick,
)
}

View file

@ -1,316 +0,0 @@
package com.anytypeio.anytype.ui.library.views.list
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.PagerState
import androidx.compose.material.Divider
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_ui.foundation.noRippleClickable
import com.anytypeio.anytype.core_ui.views.UXBody
import com.anytypeio.anytype.presentation.library.DependentData
import com.anytypeio.anytype.presentation.library.LibraryAnalyticsEvent
import com.anytypeio.anytype.presentation.library.LibraryEvent
import com.anytypeio.anytype.presentation.library.LibraryScreenState
import com.anytypeio.anytype.presentation.library.LibraryView
import com.anytypeio.anytype.presentation.library.LibraryViewModel
import com.anytypeio.anytype.ui.library.LibraryListConfig
import com.anytypeio.anytype.ui.library.ScreenState
import com.anytypeio.anytype.ui.library.views.list.LibraryListDefaults.SearchBarPadding
import com.anytypeio.anytype.ui.library.views.list.LibraryListDefaults.SearchCancelPaddingStart
import com.anytypeio.anytype.ui.library.views.list.LibraryListDefaults.SearchCancelPaddingTop
import com.anytypeio.anytype.ui.library.views.list.items.CreateNewRelationItem
import com.anytypeio.anytype.ui.library.views.list.items.CreateNewTypeItem
import com.anytypeio.anytype.ui.library.views.list.items.ItemDefaults
import com.anytypeio.anytype.ui.library.views.list.items.LibRelationItem
import com.anytypeio.anytype.ui.library.views.list.items.LibTypeItem
import com.anytypeio.anytype.ui.library.views.list.items.LibraryObjectEmptyItem
import com.anytypeio.anytype.ui.library.views.list.items.MyRelationItem
import com.anytypeio.anytype.ui.library.views.list.items.MyTypeItem
@ExperimentalAnimationApi
@Composable
fun LibraryListTabsContent(
modifier: Modifier,
pagerState: PagerState,
configuration: List<LibraryListConfig>,
tabs: LibraryScreenState.Tabs,
vmEventStream: (LibraryEvent) -> Unit,
vmAnalyticsStream: (LibraryAnalyticsEvent.Ui) -> Unit,
screenState: MutableState<ScreenState>,
effects: LibraryViewModel.Effect,
) {
val keyboardController = LocalSoftwareKeyboardController.current
val focusManager = LocalFocusManager.current
val animationStartState = remember {
mutableStateOf(false)
}
val input = remember { mutableStateOf(String()) }
HorizontalPager(
modifier = modifier,
state = pagerState,
userScrollEnabled = screenState.value == ScreenState.CONTENT
) { index ->
val data = when (configuration[index]) {
is LibraryListConfig.Types, is LibraryListConfig.Relations -> {
tabs.my
}
is LibraryListConfig.TypesLibrary, is LibraryListConfig.RelationsLibrary -> {
tabs.lib
}
}
val itemsListState = rememberLazyListState()
Column(
modifier = modifier,
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Top
) {
Row(
modifier = Modifier.padding(start = SearchBarPadding, end = SearchBarPadding)
) {
LaunchedEffect(key1 = effects) {
if (effects is LibraryViewModel.Effect.ObjectCreated) {
input.value = ""
vmEventStream.invoke(
configuration[index].toEvent(input.value)
)
animationStartState.value = false
keyboardController?.hide()
focusManager.clearFocus()
screenState.value = ScreenState.CONTENT
itemsListState.animateScrollToItem(0)
}
}
LibraryListSearchWidget(
vmEventStream = vmEventStream,
config = configuration[index],
modifier = Modifier.weight(1f),
screenState = screenState,
animationStartState = animationStartState,
input = input
)
SearchCancel(
modifier = Modifier
.padding(start = SearchCancelPaddingStart, top = SearchCancelPaddingTop)
.noRippleClickable {
input.value = ""
vmEventStream.invoke(
configuration[index].toEvent(input.value)
)
keyboardController?.hide()
focusManager.clearFocus()
animationStartState.value = false
screenState.value = ScreenState.CONTENT
},
visible = screenState.value.visible().not()
)
}
LibraryList(data, vmEventStream, screenState, itemsListState)
}
}
attachAnalytics(pagerState, configuration, vmAnalyticsStream)
}
@Composable
private fun attachAnalytics(
pagerState: PagerState,
configuration: List<LibraryListConfig>,
vmAnalyticsStream: (LibraryAnalyticsEvent.Ui) -> Unit
) {
LaunchedEffect(pagerState) {
snapshotFlow { pagerState.currentPage }.collect { page ->
when (configuration[page]) {
is LibraryListConfig.Types -> {
vmAnalyticsStream.invoke(LibraryAnalyticsEvent.Ui.TabView.Types)
}
is LibraryListConfig.Relations -> {
vmAnalyticsStream.invoke(LibraryAnalyticsEvent.Ui.TabView.Relations)
}
is LibraryListConfig.TypesLibrary -> {
vmAnalyticsStream.invoke(LibraryAnalyticsEvent.Ui.TabView.LibTypes)
}
is LibraryListConfig.RelationsLibrary -> {
vmAnalyticsStream.invoke(LibraryAnalyticsEvent.Ui.TabView.LibRelations)
}
}
}
}
}
@Composable
private fun SearchCancel(modifier: Modifier = Modifier, visible: Boolean = false) {
AnimatedVisibility(visible = visible) {
Text(
modifier = modifier,
text = stringResource(id = R.string.cancel),
style = UXBody.copy(
color = colorResource(id = R.color.glyph_active)
)
)
}
}
@Composable
private fun LibraryList(
data: LibraryScreenState.Tabs.TabData,
vmEventStream: (LibraryEvent) -> Unit,
screenState: MutableState<ScreenState>,
itemsListState: LazyListState,
) {
val itemModifier = Modifier
.fillMaxWidth()
.height(ItemDefaults.ITEM_HEIGHT)
LazyColumn(modifier = Modifier.fillMaxSize(), state = itemsListState) {
items(
count = data.items.size,
key = { index -> "library-item-${data.items[index].id}" },
itemContent = { ix ->
when (val item = data.items[ix]) {
is LibraryView.LibraryTypeView -> {
LibTypeItem(
name = item.name,
icon = item.icon,
installed = item.dependentData is DependentData.Model,
modifier = itemModifier,
onClick = {
vmEventStream.invoke(
LibraryEvent.ToggleInstall.Type(item)
)
}
)
}
is LibraryView.MyTypeView -> {
MyTypeItem(
name = item.name,
icon = item.icon,
readOnly = item.readOnly,
modifier = itemModifier.clickable(item.editable) {
vmEventStream.invoke(
LibraryEvent.Type.Edit(item)
)
}
)
}
is LibraryView.LibraryRelationView -> {
LibRelationItem(
modifier = itemModifier,
name = item.name,
format = item.format,
installed = item.dependentData is DependentData.Model,
onClick = {
vmEventStream.invoke(
LibraryEvent.ToggleInstall.Relation(item)
)
}
)
}
is LibraryView.MyRelationView -> {
MyRelationItem(
modifier = itemModifier.clickable(item.editable) {
vmEventStream.invoke(
LibraryEvent.Relation.Edit(item)
)
},
name = item.name,
readOnly = item.readOnly,
format = item.format
)
}
is LibraryView.CreateNewTypeView -> {
CreateNewTypeItem(
modifier = itemModifier.clickable {
vmEventStream.invoke(
LibraryEvent.Type.Create(item.name)
)
},
name = item.name
)
}
is LibraryView.CreateNewRelationView -> {
CreateNewRelationItem(
modifier = itemModifier.clickable {
vmEventStream.invoke(
LibraryEvent.Relation.Create(item.name)
)
},
name = item.name
)
}
is LibraryView.LibraryTypesPlaceholderView -> {
LibraryObjectEmptyItem(LibraryObjectTypes.TYPES.type, item.name)
}
is LibraryView.LibraryRelationsPlaceholderView -> {
LibraryObjectEmptyItem(LibraryObjectTypes.RELATIONS.type, item.name)
}
is LibraryView.UnknownView -> {
// do nothing
}
}
if (ix < data.items.lastIndex) {
LibraryDivider()
}
if (ix == data.items.lastIndex && screenState.value.visible()) {
Spacer(modifier = Modifier.height(48.dp))
}
}
)
}
}
@Composable
private fun LibraryDivider() {
Divider(
thickness = LibraryListDefaults.DividerThickness,
modifier = Modifier.padding(
start = LibraryListDefaults.DividerPadding,
end = LibraryListDefaults.DividerPadding
),
color = colorResource(id = R.color.shape_primary)
)
}
@Immutable
internal object LibraryListDefaults {
val ItemPadding = 20.dp
val DividerPadding = 20.dp
val DividerThickness = 0.5.dp
val SearchBarPadding = 20.dp
val SearchCancelPaddingStart = 8.dp
val SearchCancelPaddingTop = 12.dp
}
enum class LibraryObjectTypes(val type: String) {
TYPES("types"), RELATIONS("relations")
}

View file

@ -1,290 +0,0 @@
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.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
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.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
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.foundation.noRippleClickable
import com.anytypeio.anytype.core_ui.views.UXBody
import com.anytypeio.anytype.core_ui.widgets.ObjectIconWidget
import com.anytypeio.anytype.presentation.objects.ObjectIcon
import com.anytypeio.anytype.ui.library.views.list.LibraryListDefaults
import com.anytypeio.anytype.ui.library.views.list.items.ItemDefaults.TEXT_PADDING_START
@Composable
fun MyTypeItem(
name: String,
icon: ObjectIcon?,
readOnly: Boolean = false,
modifier: Modifier
) {
Row(
modifier.padding(
start = LibraryListDefaults.ItemPadding,
end = LibraryListDefaults.ItemPadding
),
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),
style = UXBody
)
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,
onClick: () -> Unit
) {
Row(
modifier.padding(
start = LibraryListDefaults.ItemPadding,
end = LibraryListDefaults.ItemPadding
),
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),
style = UXBody
)
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(),
modifier = Modifier.noRippleClickable(enabled = installed.not()) {
onClick()
}
)
}
}
@Composable
fun MyRelationItem(
modifier: Modifier,
name: String,
format: RelationFormat,
readOnly: Boolean = false,
) {
Row(
modifier.padding(
start = LibraryListDefaults.ItemPadding,
end = LibraryListDefaults.ItemPadding
),
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),
style = UXBody
)
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,
onClick: () -> Unit
) {
Row(
modifier.padding(
start = LibraryListDefaults.ItemPadding,
end = LibraryListDefaults.ItemPadding
),
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),
style = UXBody
)
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(),
Modifier.noRippleClickable(enabled = installed.not()) {
onClick()
}
)
}
}
@Composable
fun CreateNewTypeItem(
modifier: Modifier,
name: String,
) {
Row(
modifier.padding(
start = LibraryListDefaults.ItemPadding,
end = LibraryListDefaults.ItemPadding
),
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(id = R.drawable.ic_default_plus),
contentDescription = "",
)
Text(
text = stringResource(
id = R.string.library_create_new_type,
formatArgs = arrayOf(name)
),
color = colorResource(id = R.color.text_primary),
modifier = Modifier.padding(start = TEXT_PADDING_START),
style = UXBody
)
}
}
@Composable
fun LibraryObjectEmptyItem(objectType: String, name: String) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxWidth()
.padding(top = 48.dp)
) {
Text(
text = stringResource(
id = R.string.library_objects_empty,
formatArgs = arrayOf(objectType, name)
),
style = UXBody,
color = colorResource(id = R.color.text_primary),
textAlign = TextAlign.Center,
modifier = Modifier.padding(bottom = 9.dp)
)
Text(
text = stringResource(
id = R.string.library_objects_empty_action
),
color = colorResource(id = R.color.text_secondary),
style = UXBody,
textAlign = TextAlign.Center
)
}
}
@Composable
fun CreateNewRelationItem(
modifier: Modifier,
name: String,
) {
Row(
modifier.padding(
start = LibraryListDefaults.ItemPadding,
end = LibraryListDefaults.ItemPadding
),
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(id = R.drawable.ic_default_plus),
contentDescription = "",
)
Text(
text = stringResource(
id = R.string.library_create_new_relation,
formatArgs = arrayOf(name)
),
color = colorResource(id = R.color.text_primary),
modifier = Modifier.padding(start = TEXT_PADDING_START),
style = UXBody,
)
}
}
@Composable
fun Icon(icon: ObjectIcon?) {
icon?.let {
AndroidView(
factory = { ctx ->
ObjectIconWidget(ctx)
},
modifier = Modifier.size(20.dp),
update = {
it.setIcon(icon)
}
)
}
}
@Immutable
object ItemDefaults {
val ITEM_HEIGHT = 52.dp
val TEXT_PADDING_START = 10.dp
}

View file

@ -1,107 +0,0 @@
package com.anytypeio.anytype.ui.widgets.types
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_ui.views.HeadlineSubheading
import com.anytypeio.anytype.presentation.home.InteractionMode
import com.anytypeio.anytype.presentation.widgets.DropDownMenuAction
import com.anytypeio.anytype.ui.widgets.menu.WidgetMenu
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun LibraryWidgetCard(
mode: InteractionMode,
onClick: () -> Unit,
onDropDownMenuAction: (DropDownMenuAction) -> Unit,
) {
val haptic = LocalHapticFeedback.current
val isCardMenuExpanded = remember {
mutableStateOf(false)
}
val isHeaderMenuExpanded = remember {
mutableStateOf(false)
}
Box(
modifier = Modifier
.fillMaxWidth()
.padding(start = 20.dp, end = 20.dp, top = 6.dp, bottom = 6.dp)
.alpha(if (isCardMenuExpanded.value || isHeaderMenuExpanded.value) 0.8f else 1f)
.background(
shape = RoundedCornerShape(16.dp),
color = colorResource(id = R.color.dashboard_card_background)
)
.then(
if (mode is InteractionMode.Default)
Modifier.combinedClickable(
onClick = {
onClick()
},
onLongClick = {
isCardMenuExpanded.value = !isCardMenuExpanded.value
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
},
indication = null,
interactionSource = remember { MutableInteractionSource() }
)
else
Modifier
)
) {
Box(
Modifier
.padding(vertical = 6.dp)
.height(40.dp)
) {
Image(
painter = painterResource(id = R.drawable.ic_widget_library),
contentDescription = "Bin icon",
modifier = Modifier
.align(Alignment.CenterStart)
.padding(start = 14.dp)
)
Text(
text = stringResource(id = R.string.library),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier
.align(Alignment.CenterStart)
.padding(start = 42.dp, end = 16.dp),
style = HeadlineSubheading,
color = colorResource(id = R.color.text_primary),
)
}
WidgetMenu(
isExpanded = isCardMenuExpanded,
onDropDownMenuAction = onDropDownMenuAction,
canRemove = false,
canChangeType = false,
canChangeSource = false,
canEmptyBin = false,
canEditWidgets = mode is InteractionMode.Default,
canAddBelow = false
)
}
}

View file

@ -460,32 +460,6 @@
app:destination="@id/selectSpaceScreen"/>
</fragment>
<fragment
android:id="@+id/libraryFragment"
android:name="com.anytypeio.anytype.ui.library.LibraryFragment"
android:label="LibraryFragment">
<action
android:id="@+id/openTypeCreationScreen"
app:destination="@id/typeCreationFragment" />
<action
android:id="@+id/openTypeEditingScreen"
app:destination="@id/typeEditingFragment" />
<action
android:id="@+id/openRelationCreationScreen"
app:destination="@id/relationCreationFragment" />
<action
android:id="@+id/openRelationEditingScreen"
app:destination="@id/relationEditingFragment" />
<action
android:id="@+id/actionOpenVault"
app:destination="@id/vaultScreen"
app:popUpTo="@id/vaultScreen"
app:popUpToInclusive="true" />
<action
android:id="@+id/actionExitToSpaceWidgets"
app:popUpTo="@+id/homeScreen"
app:popUpToInclusive="false" />
</fragment>
<dialog
android:id="@+id/typeCreationFragment"
android:name="com.anytypeio.anytype.ui.types.create.CreateObjectTypeFragment"

View file

@ -18,7 +18,6 @@ import com.anytypeio.anytype.core_models.restrictions.ObjectRestriction
import com.anytypeio.anytype.domain.all_content.RestoreAllContentState
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.feature_allcontent.presentation.AllContentViewModel.Companion.DEFAULT_INITIAL_TAB
import com.anytypeio.anytype.presentation.library.DependentData
import com.anytypeio.anytype.presentation.mapper.objectIcon
import com.anytypeio.anytype.presentation.objects.ObjectIcon
import com.anytypeio.anytype.presentation.objects.getDescriptionOrSnippet
@ -144,8 +143,7 @@ sealed class UiContentItem {
val sourceObject: Id? = null,
val uniqueKey: Key? = null,
val readOnly: Boolean = true,
val editable: Boolean = true,
val dependentData: DependentData = DependentData.None
val editable: Boolean = true
) : UiContentItem()
data class Relation(

View file

@ -60,7 +60,6 @@ import com.anytypeio.anytype.presentation.extension.sendAnalyticsAllContentToBin
import com.anytypeio.anytype.presentation.extension.sendAnalyticsObjectCreateEvent
import com.anytypeio.anytype.presentation.home.OpenObjectNavigation
import com.anytypeio.anytype.presentation.home.navigation
import com.anytypeio.anytype.presentation.library.LibraryViewModel.LibraryItem
import com.anytypeio.anytype.presentation.objects.getCreateObjectParams
import java.time.Instant
import java.time.LocalDate
@ -955,7 +954,7 @@ class AllContentViewModel(
}
}
fun uninstallObject(id: Id, type: LibraryItem, name: String) {
fun uninstallObject(id: Id, tab: AllContentTab, name: String) {
viewModelScope.launch {
removeObjectsFromWorkspace.execute(
RemoveObjectsFromWorkspace.Params(listOf(id))
@ -965,13 +964,16 @@ class AllContentViewModel(
commands.emit(Command.SendToast.Error("Error while uninstalling object"))
},
onSuccess = {
when (type) {
LibraryItem.TYPE -> {
when (tab) {
AllContentTab.TYPES -> {
commands.emit(Command.SendToast.TypeRemoved(name))
}
LibraryItem.RELATION -> {
AllContentTab.RELATIONS -> {
commands.emit(Command.SendToast.RelationRemoved(name))
}
else -> {
//do nothing
}
}
}
)
@ -1014,4 +1016,8 @@ class AllContentViewModel(
val DEFAULT_INITIAL_TAB = AllContentTab.PAGES
val DEFAULT_QUERY = ""
}
enum class LibraryItem {
TYPE, RELATION
}
}

View file

@ -1775,19 +1775,6 @@ class HomeScreenViewModel(
}
}
fun onLibraryClicked() {
viewModelScope.launch {
val space = spaceManager.get()
if (space.isNotEmpty()) {
navigation(
Navigation.OpenLibrary(space)
)
} else {
Timber.w("Space is missing: ${space}")
}
}
}
fun onSearchIconClicked() {
viewModelScope.launch {
commands.emit(
@ -2127,7 +2114,6 @@ class HomeScreenViewModel(
data class OpenSet(val ctx: Id, val space: Id, val view: Id?) : Navigation()
data class ExpandWidget(val subscription: Subscription, val space: Id) : Navigation()
data object OpenSpaceSwitcher: Navigation()
data class OpenLibrary(val space: Id) : Navigation()
data class OpenAllContent(val space: Id) : Navigation()
}

View file

@ -1,51 +0,0 @@
package com.anytypeio.anytype.presentation.library
sealed class LibraryEvent {
sealed class BottomMenu : LibraryEvent() {
object Back : BottomMenu()
object Search : BottomMenu()
object CreateObject : BottomMenu()
object OpenProfile : BottomMenu()
}
sealed class Query(open val query: String) : LibraryEvent() {
class MyTypes(override val query: String) : Query(query)
class LibraryTypes(override val query: String) : Query(query)
class MyRelations(override val query: String) : Query(query)
class LibraryRelations(override val query: String) : Query(query)
}
sealed class ToggleInstall(open val item: LibraryView) : LibraryEvent() {
class Type(override val item: LibraryView) : ToggleInstall(item)
class Relation(override val item: LibraryView) : ToggleInstall(item)
}
sealed class Type : LibraryEvent() {
class Create(val name: String = "") : Type()
class Edit(val item: LibraryView.MyTypeView) : Type()
}
sealed class Relation : LibraryEvent() {
class Create(val name: String = "") : Relation()
class Edit(val item: LibraryView.MyRelationView) : Relation()
}
}
sealed class LibraryAnalyticsEvent {
sealed class Ui {
object Idle : Ui()
sealed class TabView(val type: String, val view: String) : Ui() {
object Types : TabView(type = TypeType, view = ViewYour)
object Relations : TabView(type = TypeRelation, view = ViewYour)
object LibTypes : TabView(type = TypeType, view = ViewLibrary)
object LibRelations : TabView(type = TypeRelation, view = ViewLibrary)
}
}
}
private const val TypeType = "type"
private const val TypeRelation = "relation"
private const val ViewYour = "your"
private const val ViewLibrary = "library"

View file

@ -1,30 +0,0 @@
package com.anytypeio.anytype.presentation.library
import com.anytypeio.anytype.core_models.ObjectWrapper
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.distinctUntilChanged
interface LibraryListDelegate {
val queryFlow: MutableStateFlow<String>
val itemsFlow: Flow<LibraryScreenState.Tabs.TabData>
fun itemsFlow(): Flow<List<ObjectWrapper.Basic>>
@FlowPreview
fun queryFlow() = queryFlow
.debounce(DEBOUNCE_TIMEOUT)
.distinctUntilChanged()
suspend fun unsubscribe()
}
fun List<LibraryView>.filterByQuery(query: String): List<LibraryView> {
return filter { it.name.contains(query.trim(), true) }
}
private const val DEBOUNCE_TIMEOUT = 100L

View file

@ -1,17 +0,0 @@
package com.anytypeio.anytype.presentation.library
interface QueryListenerMyTypes {
fun onQueryMyTypes(string: String)
}
interface QueryListenerMyRelations {
fun onQueryMyRelations(string: String)
}
interface QueryListenerLibTypes {
fun onQueryLibTypes(string: String)
}
interface QueryListenerLibRelations {
fun onQueryLibRelations(string: String)
}

View file

@ -1,41 +0,0 @@
package com.anytypeio.anytype.presentation.library
import android.content.Context
import android.content.res.Resources
import com.anytypeio.anytype.presentation.R
import javax.inject.Inject
interface LibraryResourceManager {
fun messageRelationAdded(name: String): String
fun messageRelationRemoved(name: String): String
fun messageTypeAdded(name: String): String
fun messageTypeRemoved(name: String): String
val errorMessage: String
class Impl @Inject constructor(
val context: Context
) : LibraryResourceManager {
private val resources: Resources = context.resources
override fun messageRelationAdded(name: String) =
resources.getString(R.string.library_relation_added, name)
override fun messageRelationRemoved(name: String) =
resources.getString(R.string.library_relation_removed, name)
override fun messageTypeAdded(name: String) =
resources.getString(R.string.library_type_added, name)
override fun messageTypeRemoved(name: String) =
resources.getString(R.string.library_type_removed, name)
override val errorMessage: String =
resources.getString(R.string.library_something_went_wrong)
}
}

View file

@ -1,27 +0,0 @@
package com.anytypeio.anytype.presentation.library
class LibraryScreenState(
val types: Tabs.Types,
val relations: Tabs.Relations
) {
sealed class Tabs(
val my: TabData,
val lib: TabData
) {
class Types(
myTypes: TabData,
libTypes: TabData
) : Tabs(myTypes, libTypes)
class Relations(
myRelations: TabData,
libRelations: TabData
) : Tabs(myRelations, libRelations)
data class TabData(
val items: List<LibraryView> = emptyList()
)
}
}

View file

@ -1,89 +0,0 @@
package com.anytypeio.anytype.presentation.library
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.Key
import com.anytypeio.anytype.core_models.RelationFormat
import com.anytypeio.anytype.presentation.objects.ObjectIcon
sealed interface DependentData {
class Model(
val item: LibraryView,
) : DependentData
object None : DependentData
}
sealed interface LibraryView {
val id: Id
val name: String
val dependentData: DependentData
class MyTypeView(
override val id: Id,
override val name: String,
val icon: ObjectIcon? = null,
val sourceObject: Id? = null,
val uniqueKey: Key? = null,
val readOnly: Boolean = true,
val editable: Boolean = true,
override val dependentData: DependentData = DependentData.None
) : LibraryView
data class LibraryTypeView(
override val id: Id,
override val name: String,
val icon: ObjectIcon? = null,
val uniqueKey: Key? = null,
override val dependentData: DependentData = DependentData.None
) : LibraryView
class MyRelationView(
override val id: Id,
override val name: String,
val format: RelationFormat,
val sourceObject: Id? = null,
val readOnly: Boolean = true,
val editable: Boolean = true,
override val dependentData: DependentData = DependentData.None
) : LibraryView
data class LibraryRelationView(
override val id: Id,
override val name: String,
val format: RelationFormat,
override val dependentData: DependentData = DependentData.None
) : LibraryView
class UnknownView(
override val id: Id,
override val name: String = "",
override val dependentData: DependentData = DependentData.None
) : LibraryView
class CreateNewTypeView(
override val id: Id = "",
override val name: String = "",
override val dependentData: DependentData = DependentData.None
) : LibraryView
class CreateNewRelationView(
override val id: Id = "",
override val name: String = "",
override val dependentData: DependentData = DependentData.None
) : LibraryView
class LibraryTypesPlaceholderView(
override val id: Id = "",
override val name: String = "",
override val dependentData: DependentData = DependentData.None,
) : LibraryView
class LibraryRelationsPlaceholderView(
override val id: Id = "",
override val name: String = "",
override val dependentData: DependentData = DependentData.None,
) : LibraryView
}

View file

@ -1,584 +0,0 @@
package com.anytypeio.anytype.presentation.library
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.anytypeio.anytype.analytics.base.Analytics
import com.anytypeio.anytype.analytics.base.EventsDictionary
import com.anytypeio.anytype.analytics.base.EventsDictionary.libraryScreenRelation
import com.anytypeio.anytype.analytics.base.EventsDictionary.libraryScreenType
import com.anytypeio.anytype.analytics.base.EventsDictionary.libraryView
import com.anytypeio.anytype.analytics.base.sendEvent
import com.anytypeio.anytype.analytics.props.Props
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.ObjectWrapper
import com.anytypeio.anytype.core_models.Relations
import com.anytypeio.anytype.core_models.primitives.SpaceId
import com.anytypeio.anytype.core_utils.ext.allUniqueBy
import com.anytypeio.anytype.core_utils.ext.orNull
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
import com.anytypeio.anytype.domain.base.fold
import com.anytypeio.anytype.domain.library.StoreSearchByIdsParams
import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.domain.`object`.SetObjectDetails
import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes
import com.anytypeio.anytype.domain.page.CreateObject
import com.anytypeio.anytype.domain.workspace.AddObjectToWorkspace
import com.anytypeio.anytype.domain.workspace.RemoveObjectsFromWorkspace
import com.anytypeio.anytype.domain.workspace.SpaceManager
import com.anytypeio.anytype.presentation.BuildConfig
import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate
import com.anytypeio.anytype.presentation.extension.sendAnalyticsObjectCreateEvent
import com.anytypeio.anytype.presentation.home.HomeScreenViewModel.Companion.HOME_SCREEN_PROFILE_OBJECT_SUBSCRIPTION
import com.anytypeio.anytype.presentation.home.OpenObjectNavigation
import com.anytypeio.anytype.presentation.home.navigation
import com.anytypeio.anytype.presentation.library.delegates.LibraryRelationsDelegate
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.NavigationViewModel
import com.anytypeio.anytype.presentation.objects.getCreateObjectParams
import com.anytypeio.anytype.presentation.profile.ProfileIconView
import com.anytypeio.anytype.presentation.profile.profileIcon
import javax.inject.Inject
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collectIndexed
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import timber.log.Timber
class LibraryViewModel(
private val params: Params,
private val myTypesDelegate: MyTypesDelegate,
private val libraryTypesDelegate: LibraryTypesDelegate,
private val myRelationsDelegate: MyRelationsDelegate,
private val libraryRelationsDelegate: LibraryRelationsDelegate,
private val addObjectToWorkspace: AddObjectToWorkspace,
private val removeObjectsFromWorkspace: RemoveObjectsFromWorkspace,
private val resourceManager: LibraryResourceManager,
private val setObjectDetails: SetObjectDetails,
private val createObject: CreateObject,
private val analytics: Analytics,
private val spaceManager: SpaceManager,
private val storelessSubscriptionContainer: StorelessSubscriptionContainer,
private val appCoroutineDispatchers: AppCoroutineDispatchers,
private val urlBuilder: UrlBuilder,
private val storeOfObjectTypes: StoreOfObjectTypes,
private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate
) : NavigationViewModel<LibraryViewModel.Navigation>(), AnalyticSpaceHelperDelegate by analyticSpaceHelperDelegate {
val icon = MutableStateFlow<ProfileIconView>(ProfileIconView.Loading)
private val uiEvents = MutableStateFlow<LibraryEvent>(LibraryEvent.Query.MyTypes(""))
private val analyticsEvents = MutableStateFlow<LibraryAnalyticsEvent.Ui>(
LibraryAnalyticsEvent.Ui.Idle
)
val effects = MutableStateFlow<Effect>(Effect.Idle)
val uiState: StateFlow<LibraryScreenState> = combine(
myTypesDelegate.itemsFlow,
libraryTypesDelegate.itemsFlow,
myRelationsDelegate.itemsFlow,
libraryRelationsDelegate.itemsFlow
) { myTypes, libTypes, 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),
initialValue = LibraryScreenState(
types = LibraryScreenState.Tabs.Types(
myTypes = LibraryScreenState.Tabs.TabData(),
libTypes = LibraryScreenState.Tabs.TabData()
),
relations = LibraryScreenState.Tabs.Relations(
myRelations = LibraryScreenState.Tabs.TabData(),
libRelations = LibraryScreenState.Tabs.TabData()
)
)
)
init {
Timber.i("LibraryViewModel, init")
proceedWithObservingProfileIcon()
viewModelScope.launch {
uiEvents.collect {
when (it) {
is LibraryEvent.Query -> proceedQueryEvent(it)
is LibraryEvent.ToggleInstall -> proceedWithToggleInstall(it.item)
is LibraryEvent.Type -> proceedWithTypeActions(it)
is LibraryEvent.Relation -> proceedWithRelationActions(it)
is LibraryEvent.BottomMenu -> proceedWithBottomMenuActions(it)
}
}
}
viewModelScope.launch {
analyticsEvents.filterIsInstance<LibraryAnalyticsEvent.Ui.TabView>()
.collectIndexed { index, it ->
val route = if (index == 0) ROUTE_OUTER else ROUTE_INNER
proceedWithViewAnalytics(it, route)
}
}
}
private fun proceedWithObservingProfileIcon() {
viewModelScope.launch {
spaceManager
.observe()
.flatMapLatest { config ->
storelessSubscriptionContainer.subscribe(
StoreSearchByIdsParams(
space = SpaceId(config.techSpace),
subscription = HOME_SCREEN_PROFILE_OBJECT_SUBSCRIPTION,
targets = listOf(config.profile),
keys = listOf(
Relations.ID,
Relations.NAME,
Relations.ICON_EMOJI,
Relations.ICON_IMAGE,
Relations.ICON_OPTION
)
)
).map { result ->
val obj = result.firstOrNull()
obj?.profileIcon(urlBuilder) ?: ProfileIconView.Placeholder(null)
}
}
.catch { Timber.e(it, "Error while observing space icon") }
.flowOn(appCoroutineDispatchers.io)
.collect { icon.value = it }
}
}
private fun proceedWithViewAnalytics(it: LibraryAnalyticsEvent.Ui.TabView, route: String) {
viewModelScope.sendEvent(
analytics = analytics,
eventName = libraryView,
props = Props(
mapOf(
"type" to it.type,
"view" to it.view,
"route" to route
)
)
)
}
private fun proceedWithBottomMenuActions(it: LibraryEvent.BottomMenu) {
when (it) {
is LibraryEvent.BottomMenu.Back -> navigate(Navigation.Back())
is LibraryEvent.BottomMenu.Search -> navigate(Navigation.Search())
is LibraryEvent.BottomMenu.CreateObject -> proceedWithCreateDoc()
is LibraryEvent.BottomMenu.OpenProfile -> navigate(Navigation.ExitToVault)
}
}
fun onCreateObjectOfTypeClicked(objType: ObjectWrapper.Type) {
proceedWithCreateDoc(objType)
}
private fun proceedWithCreateDoc(
objType: ObjectWrapper.Type? = null
) {
val startTime = System.currentTimeMillis()
viewModelScope.launch {
val params = objType?.uniqueKey.getCreateObjectParams(
space = SpaceId(spaceManager.get()),
objType?.defaultTemplateId
)
createObject.async(params).fold(
onSuccess = {
result -> proceedWithOpeningObject(result.obj)
sendAnalyticsObjectCreateEvent(
analytics = analytics,
route = EventsDictionary.Routes.allContentRoute,
startTime = startTime,
objType = objType ?: storeOfObjectTypes.getByKey(result.typeKey.key),
view = EventsDictionary.View.viewHome,
spaceParams = provideParams(spaceManager.get())
)
},
onFailure = { e -> Timber.e(e, "Error while creating a new object") }
)
}
}
private fun proceedWithOpeningObject(obj: ObjectWrapper.Basic) {
when (val navigation = obj.navigation()) {
is OpenObjectNavigation.OpenDataView -> {
navigate(Navigation.OpenSetOrCollection(navigation.target))
}
is OpenObjectNavigation.OpenEditor -> {
navigate(Navigation.OpenEditor(navigation.target))
}
is OpenObjectNavigation.OpenDiscussion -> {
sendToast("not implemented")
}
is OpenObjectNavigation.UnexpectedLayoutError -> {
sendToast("Unexpected layout: ${navigation.layout}")
}
OpenObjectNavigation.NonValidObject -> {
sendToast("Object id is missing")
}
}
}
private fun proceedQueryEvent(event: LibraryEvent.Query) {
when (event) {
is LibraryEvent.Query.MyTypes -> {
myTypesDelegate.onQueryMyTypes(event.query)
}
is LibraryEvent.Query.LibraryTypes -> {
libraryTypesDelegate.onQueryLibTypes(event.query)
}
is LibraryEvent.Query.MyRelations -> {
myRelationsDelegate.onQueryMyRelations(event.query)
}
is LibraryEvent.Query.LibraryRelations -> {
libraryRelationsDelegate.onQueryLibRelations(event.query)
}
}
}
private fun proceedWithToggleInstall(item: LibraryView) {
when (val dependentData = item.dependentData) {
is DependentData.Model -> uninstallObject(item, dependentData.item.id)
is DependentData.None -> installObject(item)
}
}
private fun proceedWithTypeActions(event: LibraryEvent.Type) {
when (event) {
is LibraryEvent.Type.Create -> {
navigate(Navigation.OpenTypeCreation(event.name))
}
is LibraryEvent.Type.Edit -> {
viewModelScope.sendEvent(
analytics = analytics,
eventName = libraryScreenType,
props = Props(
map = mapOf("objectType" to event.item.id)
)
)
navigate(Navigation.OpenTypeEditing(event.item))
}
}
}
private fun proceedWithRelationActions(event: LibraryEvent.Relation) {
when (event) {
is LibraryEvent.Relation.Create -> navigate(Navigation.OpenRelationCreation(event.name))
is LibraryEvent.Relation.Edit -> {
viewModelScope.sendEvent(
analytics = analytics,
eventName = libraryScreenRelation,
props = Props(
map = mapOf("relationKey" to event.item.id)
)
)
navigate(Navigation.OpenRelationEditing(event.item))
}
}
}
private fun installObject(item: LibraryView) {
viewModelScope.launch {
addObjectToWorkspace(
AddObjectToWorkspace.Params(
space = spaceManager.get(),
objects = listOf (item.id)
)
).proceed(
success = {
when (item) {
is LibraryView.LibraryRelationView -> {
sendToast(resourceManager.messageRelationAdded(item.name))
}
is LibraryView.LibraryTypeView -> {
sendToast(resourceManager.messageTypeAdded(item.name))
}
else -> {
Timber.e("Unsupported item type: $item")
}
}
},
failure = {
Timber.e(it, "Error while adding relation to workspace.")
sendToast(resourceManager.errorMessage)
}
)
}
}
private fun uninstallObject(item: LibraryView, id: Id) {
viewModelScope.launch {
removeObjectsFromWorkspace.execute(
RemoveObjectsFromWorkspace.Params(listOf(id))
).fold(
onFailure = {
Timber.e(it, "Error while removing relation from workspace.")
sendToast(resourceManager.errorMessage)
},
onSuccess = {
when (item) {
is LibraryView.LibraryRelationView -> {
sendToast(resourceManager.messageRelationRemoved(item.name))
}
is LibraryView.LibraryTypeView -> {
sendToast(resourceManager.messageTypeRemoved(item.name))
}
else -> {
Timber.e("Unsupported item type: $item")
}
}
}
)
}
}
fun uninstallObject(id: Id, type: LibraryItem, name: String) {
viewModelScope.launch {
removeObjectsFromWorkspace.execute(
RemoveObjectsFromWorkspace.Params(listOf(id))
).fold(
onFailure = {
Timber.e(it, "Error while uninstalling object")
sendToast(resourceManager.errorMessage)
},
onSuccess = {
val message = when (type) {
LibraryItem.TYPE -> resourceManager.messageTypeRemoved(name)
LibraryItem.RELATION -> resourceManager.messageRelationRemoved(name)
}
sendToast(message)
}
)
}
}
fun eventStream(event: LibraryEvent) {
uiEvents.value = event
}
fun analyticsStream(event: LibraryAnalyticsEvent.Ui) {
analyticsEvents.value = event
}
private fun updateInstalledValueForTypes(
libTypes: LibraryScreenState.Tabs.TabData,
myTypes: LibraryScreenState.Tabs.TabData
): LibraryScreenState.Tabs.TabData {
if (BuildConfig.DEBUG) {
assert(libTypes.items.allUniqueBy { it.id })
assert(myTypes.items.allUniqueBy { it.id })
}
val myTypeViews = myTypes
.items
.filterIsInstance<LibraryView.MyTypeView>()
return libTypes.copy(
items = libTypes.items.map { libType ->
if (libType is LibraryView.LibraryTypeView) {
with(
myTypeViews.find { it.uniqueKey == libType.uniqueKey }
) {
libType.copy(
dependentData = if (this != null) {
DependentData.Model(item = this)
} else {
DependentData.None
}
)
}
} else {
libType
}
}.distinctBy { view -> view.id }
)
}
private fun updateInstalledValueForRelations(
libRelations: LibraryScreenState.Tabs.TabData,
myRelations: LibraryScreenState.Tabs.TabData
): LibraryScreenState.Tabs.TabData {
if (BuildConfig.DEBUG) {
assert(libRelations.items.allUniqueBy { it.id })
assert(myRelations.items.allUniqueBy { it.id })
}
val updatedLibraryRelations = updateLibraryRelationItems(
libraryItems = libRelations.items,
myRelationItems = myRelations.items
)
return libRelations.copy(
items = updatedLibraryRelations.distinctBy { view -> view.id }
)
}
private fun updateLibraryRelationItems(
libraryItems: List<LibraryView>,
myRelationItems: List<LibraryView>
): List<LibraryView> {
return libraryItems.map { libraryItem ->
if (libraryItem !is LibraryView.LibraryRelationView) {
return@map libraryItem
}
val relationInstalled = myRelationItems.firstOrNull { myRelationItem ->
(myRelationItem as? LibraryView.MyRelationView)?.sourceObject == libraryItem.id
}
val dependedData = if (relationInstalled != null) {
DependentData.Model(item = relationInstalled)
} else {
DependentData.None
}
libraryItem.copy(dependentData = dependedData)
}
}
fun updateObject(id: String, name: String, icon: String?) {
viewModelScope.launch {
setObjectDetails.execute(
SetObjectDetails.Params(
ctx = id,
details = mapOf(
Relations.NAME to name,
Relations.ICON_EMOJI to icon.orNull(),
)
)
).fold(
onFailure = {
Timber.e(it, "Error while updating object details")
sendToast(resourceManager.errorMessage)
},
onSuccess = {
// do nothing
}
)
}
}
fun onObjectCreated() {
effects.value = Effect.ObjectCreated()
}
override fun onCleared() {
GlobalScope.launch {
myRelationsDelegate.unsubscribe()
libraryRelationsDelegate.unsubscribe()
myTypesDelegate.unsubscribe()
libraryTypesDelegate.unsubscribe()
}
super.onCleared()
}
class Factory @Inject constructor(
private val params: Params,
private val myTypesDelegate: MyTypesDelegate,
private val libraryTypesDelegate: LibraryTypesDelegate,
private val myRelationsDelegate: MyRelationsDelegate,
private val libraryRelationsDelegate: LibraryRelationsDelegate,
private val addObjectToWorkspace: AddObjectToWorkspace,
private val removeObjectsFromWorkspace: RemoveObjectsFromWorkspace,
private val resourceManager: LibraryResourceManager,
private val setObjectDetails: SetObjectDetails,
private val createObject: CreateObject,
private val analytics: Analytics,
private val spaceManager: SpaceManager,
private val storelessSubscriptionContainer: StorelessSubscriptionContainer,
private val appCoroutineDispatchers: AppCoroutineDispatchers,
private val urlBuilder: UrlBuilder,
private val storeOfObjectTypes: StoreOfObjectTypes,
private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return LibraryViewModel(
params = params,
myTypesDelegate = myTypesDelegate,
libraryTypesDelegate = libraryTypesDelegate,
myRelationsDelegate = myRelationsDelegate,
libraryRelationsDelegate = libraryRelationsDelegate,
addObjectToWorkspace = addObjectToWorkspace,
removeObjectsFromWorkspace = removeObjectsFromWorkspace,
resourceManager = resourceManager,
setObjectDetails = setObjectDetails,
createObject = createObject,
analytics = analytics,
spaceManager = spaceManager,
storelessSubscriptionContainer = storelessSubscriptionContainer,
appCoroutineDispatchers = appCoroutineDispatchers,
urlBuilder = urlBuilder,
storeOfObjectTypes = storeOfObjectTypes,
analyticSpaceHelperDelegate = analyticSpaceHelperDelegate
) as T
}
}
class Params(val space: SpaceId)
sealed class Navigation {
class OpenTypeCreation(
val name: String = ""
) : Navigation()
class OpenRelationCreation(
val name: String = ""
) : Navigation()
class OpenTypeEditing(
val view: LibraryView.MyTypeView
) : Navigation()
class OpenRelationEditing(
val view: LibraryView.MyRelationView
) : Navigation()
object ExitToVault: Navigation()
class Back : Navigation()
class Search : Navigation()
class OpenEditor(val id: Id) : Navigation()
class OpenSetOrCollection(val id: Id) : Navigation()
}
sealed class Effect {
class ObjectCreated : Effect()
object Idle : Effect()
}
enum class LibraryItem {
TYPE, RELATION
}
}
private const val STOP_SUBSCRIPTION_TIMEOUT = 1_000L
private const val ROUTE_INNER = "inner"
private const val ROUTE_OUTER = "outer"

View file

@ -1,74 +0,0 @@
package com.anytypeio.anytype.presentation.library.delegates
import com.anytypeio.anytype.core_models.Marketplace.MARKETPLACE_SPACE_ID
import com.anytypeio.anytype.core_models.primitives.SpaceId
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
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.library.LibraryListDelegate
import com.anytypeio.anytype.presentation.library.LibraryScreenState
import com.anytypeio.anytype.presentation.library.LibraryView
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
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
class LibraryRelationsDelegate @Inject constructor(
private val container: StorelessSubscriptionContainer,
private val urlBuilder: UrlBuilder,
private val dispatchers: AppCoroutineDispatchers
) : LibraryListDelegate, QueryListenerLibRelations {
override val queryFlow: MutableStateFlow<String> = MutableStateFlow("")
override fun onQueryLibRelations(string: String) {
queryFlow.value = string
}
override val itemsFlow: Flow<LibraryScreenState.Tabs.TabData> = combine(
itemsFlow(),
queryFlow()
) { items, query ->
LibraryScreenState.Tabs.TabData(
items
.toLibraryViews(urlBuilder)
.filterByQuery(query)
.optAddEmptyPlaceholder(query)
)
}
override fun itemsFlow() = container.subscribe(buildSearchParams())
private fun buildSearchParams(): StoreSearchParams {
return StoreSearchParams(
space = SpaceId(MARKETPLACE_SPACE_ID),
subscription = SUB_LIBRARY_RELATIONS,
keys = ObjectSearchConstants.defaultRelationKeys,
filters = buildList {
addAll(ObjectSearchConstants.filterMarketplaceRelations())
}
)
}
override suspend fun unsubscribe() = with(dispatchers.io) {
container.unsubscribe(listOf(SUB_LIBRARY_RELATIONS))
}
}
private fun List<LibraryView>.optAddEmptyPlaceholder(query: String): List<LibraryView> {
val q = query.trim()
val result = this
return if (q.isNotEmpty() && result.isEmpty()) {
listOf<LibraryView>(LibraryView.LibraryRelationsPlaceholderView(name = q))
} else {
result
}
}
private const val SUB_LIBRARY_RELATIONS = "subscription.library_relations"

View file

@ -1,73 +0,0 @@
package com.anytypeio.anytype.presentation.library.delegates
import com.anytypeio.anytype.core_models.Marketplace.MARKETPLACE_SPACE_ID
import com.anytypeio.anytype.core_models.primitives.SpaceId
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
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.library.LibraryListDelegate
import com.anytypeio.anytype.presentation.library.LibraryScreenState
import com.anytypeio.anytype.presentation.library.LibraryView
import com.anytypeio.anytype.presentation.library.QueryListenerLibTypes
import com.anytypeio.anytype.presentation.library.filterByQuery
import com.anytypeio.anytype.presentation.objects.SupportedLayouts
import com.anytypeio.anytype.presentation.objects.toLibraryViews
import com.anytypeio.anytype.presentation.search.ObjectSearchConstants
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
class LibraryTypesDelegate @Inject constructor(
private val container: StorelessSubscriptionContainer,
private val urlBuilder: UrlBuilder,
private val dispatchers: AppCoroutineDispatchers
) : LibraryListDelegate, QueryListenerLibTypes {
override val queryFlow: MutableStateFlow<String> = MutableStateFlow("")
override fun onQueryLibTypes(string: String) {
queryFlow.value = string
}
override val itemsFlow: Flow<LibraryScreenState.Tabs.TabData> = combine(
itemsFlow(),
queryFlow()
) { items, query ->
LibraryScreenState.Tabs.TabData(
items
.toLibraryViews(urlBuilder)
.filterByQuery(query)
.optAddEmptyPlaceholder(query)
)
}
override fun itemsFlow() = container.subscribe(buildSearchParams())
private fun buildSearchParams(): StoreSearchParams {
return StoreSearchParams(
space = SpaceId(MARKETPLACE_SPACE_ID),
subscription = SUB_LIBRARY_TYPES,
keys = ObjectSearchConstants.defaultKeys,
filters = ObjectSearchConstants.filterTypes()
)
}
override suspend fun unsubscribe() = with(dispatchers.io) {
container.unsubscribe(listOf(SUB_LIBRARY_TYPES))
}
}
private fun List<LibraryView>.optAddEmptyPlaceholder(query: String): List<LibraryView> {
val q = query.trim()
val result = this
return if (q.isNotEmpty() && result.isEmpty()) {
listOf(LibraryView.LibraryTypesPlaceholderView(name = q))
} else {
result
}
}
private const val SUB_LIBRARY_TYPES = "subscription.library_types"

View file

@ -1,92 +0,0 @@
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.Relations
import com.anytypeio.anytype.core_models.primitives.SpaceId
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
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.SpaceManager
import com.anytypeio.anytype.presentation.library.LibraryListDelegate
import com.anytypeio.anytype.presentation.library.LibraryScreenState
import com.anytypeio.anytype.presentation.library.LibraryView
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
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapMerge
import kotlinx.coroutines.flow.flow
class MyRelationsDelegate @Inject constructor(
private val container: StorelessSubscriptionContainer,
private val spaceManager: SpaceManager,
private val urlBuilder: UrlBuilder,
private val dispatchers: AppCoroutineDispatchers
) : LibraryListDelegate, QueryListenerMyRelations {
override val queryFlow: MutableStateFlow<String> = MutableStateFlow("")
override fun onQueryMyRelations(string: String) {
queryFlow.value = string
}
@FlowPreview
override val itemsFlow: Flow<LibraryScreenState.Tabs.TabData> = combine(
itemsFlow(),
queryFlow()
) { items, query ->
LibraryScreenState.Tabs.TabData(
items
.toLibraryViews(urlBuilder)
.filterByQuery(query)
.optAddCreateRelationView(query)
)
}
@FlowPreview
override fun itemsFlow() = flow {
emit(spaceManager.get())
}.flatMapMerge { space: Id ->
val searchParams = buildSearchParams(space)
container.subscribe(searchParams)
}
private fun buildSearchParams(space: Id): StoreSearchParams {
return StoreSearchParams(
space = SpaceId(space),
subscription = SUB_LIBRARY_MY_RELATIONS,
keys = ObjectSearchConstants.defaultRelationKeys,
filters = buildList {
addAll(ObjectSearchConstants.filterMyRelations())
}
)
}
override suspend fun unsubscribe() = with(dispatchers.io) {
container.unsubscribe(listOf(SUB_LIBRARY_MY_RELATIONS))
}
}
private fun List<LibraryView>.optAddCreateRelationView(query: String): List<LibraryView> {
val q = query.trim()
val result = this
return if (q.isNotEmpty() && result.none { it.name.lowercase() == q.lowercase() }) {
buildList {
add(LibraryView.CreateNewRelationView(name = q))
addAll(result)
}
} else {
result
}
}
private const val SUB_LIBRARY_MY_RELATIONS = "subscription.library_my_relations"

View file

@ -1,92 +0,0 @@
package com.anytypeio.anytype.presentation.library.delegates
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.Relations
import com.anytypeio.anytype.core_models.primitives.SpaceId
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
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.SpaceManager
import com.anytypeio.anytype.presentation.library.LibraryListDelegate
import com.anytypeio.anytype.presentation.library.LibraryScreenState
import com.anytypeio.anytype.presentation.library.LibraryView
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
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapMerge
import kotlinx.coroutines.flow.flow
class MyTypesDelegate @Inject constructor(
private val container: StorelessSubscriptionContainer,
private val spaceManager: SpaceManager,
private val urlBuilder: UrlBuilder,
private val dispatchers: AppCoroutineDispatchers
) : LibraryListDelegate, QueryListenerMyTypes {
override val queryFlow: MutableStateFlow<String> = MutableStateFlow("")
override fun onQueryMyTypes(string: String) {
queryFlow.value = string
}
@FlowPreview
override val itemsFlow: Flow<LibraryScreenState.Tabs.TabData> = combine(
itemsFlow(), queryFlow()
) { items, query ->
LibraryScreenState.Tabs.TabData(
items
.toLibraryViews(urlBuilder)
.filterByQuery(query)
.optAddCreateTypeView(query)
)
}
@FlowPreview
override fun itemsFlow() = flow {
emit(spaceManager.get())
}.flatMapMerge { space: Id ->
val searchParams = buildSearchParams(space = space)
container.subscribe(searchParams)
}
private fun buildSearchParams(space: Id): StoreSearchParams {
return StoreSearchParams(
space = SpaceId(space),
subscription = SUB_LIBRARY_MY_TYPES,
keys = ObjectSearchConstants.defaultKeys + listOf(
Relations.SOURCE_OBJECT,
Relations.RESTRICTIONS
),
filters = ObjectSearchConstants.filterTypes(
excludeParticipant = false
)
)
}
override suspend fun unsubscribe() = with(dispatchers.io) {
container.unsubscribe(listOf(SUB_LIBRARY_MY_TYPES))
}
}
private fun List<LibraryView>.optAddCreateTypeView(query: String): List<LibraryView> {
val q = query.trim()
val result = this
return if (q.isNotEmpty() && result.none { it.name.lowercase() == q.lowercase() }) {
buildList {
add(LibraryView.CreateNewTypeView(name = q))
addAll(result)
}
} else {
result
}
}
private const val SUB_LIBRARY_MY_TYPES = "subscription.library_my_types"

View file

@ -45,8 +45,6 @@ interface AppNavigation {
fun deletedAccountScreen(deadline: Long)
fun openLibrary(space: Id)
fun logout()
fun migrationErrorScreen()
@ -97,8 +95,6 @@ interface AppNavigation {
data class DeletedAccountScreen(val deadline: Long) : Command()
data class OpenTemplates(val typeId: Id) : Command()
data class OpenLibrary(val space: Id): Command()
}
interface Provider {

View file

@ -1,48 +1,20 @@
package com.anytypeio.anytype.presentation.objects
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.Marketplace
import com.anytypeio.anytype.core_models.MarketplaceObjectTypeIds
import com.anytypeio.anytype.core_models.ObjectType
import com.anytypeio.anytype.core_models.ObjectTypeUniqueKeys
import com.anytypeio.anytype.core_models.ObjectWrapper
import com.anytypeio.anytype.core_models.Relations
import com.anytypeio.anytype.core_models.Relations.SOURCE_OBJECT
import com.anytypeio.anytype.core_models.ext.DateParser
import com.anytypeio.anytype.core_models.restrictions.ObjectRestriction
import com.anytypeio.anytype.core_utils.ext.readableFileSize
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.presentation.extension.getProperObjectName
import com.anytypeio.anytype.presentation.library.LibraryView
import com.anytypeio.anytype.presentation.linking.LinkToItemView
import com.anytypeio.anytype.presentation.mapper.objectIcon
import com.anytypeio.anytype.presentation.navigation.DefaultObjectView
import com.anytypeio.anytype.presentation.sets.filter.CreateFilterView
import com.anytypeio.anytype.presentation.widgets.collection.CollectionView
@Deprecated("To be deleted")
fun List<ObjectWrapper.Basic>.toView(
urlBuilder: UrlBuilder,
objectTypes: List<ObjectWrapper.Type>
): List<DefaultObjectView> =
this.map { obj ->
val typeUrl = obj.getProperType()
val layout = obj.getProperLayout()
DefaultObjectView(
id = obj.id,
name = obj.getProperName(),
type = typeUrl,
typeName = getProperTypeName(
id = typeUrl,
types = objectTypes
),
description = obj.description,
layout = layout,
icon = obj.objectIcon(urlBuilder),
space = requireNotNull(obj.spaceId)
)
}
fun List<ObjectWrapper.Basic>.toViews(
urlBuilder: UrlBuilder,
objectTypes: List<ObjectWrapper.Type>
@ -79,57 +51,6 @@ fun ObjectWrapper.Basic.toView(
)
}
fun List<ObjectWrapper.Basic>.toLibraryViews(
urlBuilder: UrlBuilder
): List<LibraryView> = map { obj ->
val space = obj.getValue<Id?>(Relations.SPACE_ID)
when (obj.layout) {
ObjectType.Layout.OBJECT_TYPE -> {
if (space == Marketplace.MARKETPLACE_SPACE_ID) {
LibraryView.LibraryTypeView(
id = obj.id,
name = obj.name.orEmpty(),
icon = obj.objectIcon(urlBuilder),
uniqueKey = obj.uniqueKey
)
} else {
LibraryView.MyTypeView(
id = obj.id,
name = obj.name.orEmpty(),
icon = obj.objectIcon(urlBuilder),
sourceObject = obj.map[SOURCE_OBJECT]?.toString(),
uniqueKey = obj.uniqueKey,
readOnly = obj.restrictions.contains(ObjectRestriction.DELETE),
editable = !obj.restrictions.contains(ObjectRestriction.DETAILS)
)
}
}
ObjectType.Layout.RELATION -> {
val relation = ObjectWrapper.Relation(obj.map)
if (space == Marketplace.MARKETPLACE_SPACE_ID) {
LibraryView.LibraryRelationView(
id = obj.id,
name = obj.name.orEmpty(),
format = relation.format
)
} else {
LibraryView.MyRelationView(
id = obj.id,
name = obj.name.orEmpty(),
format = relation.format,
sourceObject = obj.map[SOURCE_OBJECT]?.toString(),
readOnly = obj.restrictions.contains(ObjectRestriction.DELETE),
editable = !obj.restrictions.contains(ObjectRestriction.DETAILS)
)
}
}
else -> LibraryView.UnknownView(
id = obj.id,
name = obj.name.orEmpty()
)
}
}
fun List<ObjectWrapper.Basic>.toLinkToView(
urlBuilder: UrlBuilder,
objectTypes: List<ObjectWrapper.Type>

View file

@ -136,11 +136,6 @@ sealed class WidgetView {
}
}
data object Library : WidgetView() {
override val id: Id get() = "id.button.library"
override val isLoading: Boolean = false
}
sealed class Action : WidgetView() {
data object EditWidgets : Action() {
override val id: Id get() = "id.action.edit-widgets"

View file

@ -4,7 +4,7 @@ import com.anytypeio.anytype.core_models.ObjectType
import com.anytypeio.anytype.core_models.ObjectWrapper
import com.anytypeio.anytype.core_models.Relations
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.presentation.objects.toView
import com.anytypeio.anytype.presentation.objects.toViews
import com.anytypeio.anytype.test_utils.MockDataFactory
import kotlin.test.assertEquals
import kotlin.test.assertNull
@ -92,7 +92,7 @@ class ObjectWrapperExtensionsKtTest {
)
val result = listOf(obj).toView(urlBuilder, objectTypes = listOf())
val result = listOf(obj).toViews(urlBuilder, objectTypes = listOf())
assertEquals(
expected = "OMr2Y",
@ -116,7 +116,10 @@ class ObjectWrapperExtensionsKtTest {
)
val result = listOf(obj).toView(urlBuilder, objectTypes = listOf())
val result = listOf(obj).toViews(
urlBuilder = urlBuilder,
objectTypes = listOf()
)
assertEquals(
expected = "LmL7R",
@ -141,7 +144,7 @@ class ObjectWrapperExtensionsKtTest {
)
val result = listOf(obj).toView(urlBuilder, objectTypes = listOf())
val result = listOf(obj).toViews(urlBuilder, objectTypes = listOf())
assertEquals(
expected = "Anytype is next-generation sof",