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

DROID-2905 Primitives | Epic | Foundation for primitives (#2098)

Co-authored-by: Evgenii Kozlov <enklave.mare.balticum@protonmail.com>
This commit is contained in:
Konstantin Ivanov 2025-02-28 20:47:43 +01:00 committed by GitHub
parent 88aa30d64b
commit 4bc1e060f3
Signed by: github
GPG key ID: B5690EEEBB952194
153 changed files with 10877 additions and 1616 deletions

View file

@ -234,6 +234,14 @@ object EventsDictionary {
const val clickDateCalendarView = "ClickDateCalendarView"
const val objectListSort = "ObjectListSort"
//ObjectType
const val screenObjectType = "ScreenType"
const val editType = "EditType"
const val changeRecommendedLayout = "ChangeRecommendedLayout"
const val changeTypeSort = "ChangeTypeSort"
const val screenTemplate = "ScreenTemplate"
const val searchBacklink = "SearchBacklink"
object SharingSpacesTypes {

View file

@ -176,6 +176,7 @@ dependencies {
implementation project(':gallery-experience')
implementation project(':feature-all-content')
implementation project(':feature-date')
implementation project(':feature-object-type')
//Compile time dependencies
ksp libs.daggerCompiler
@ -223,7 +224,7 @@ dependencies {
implementation libs.composeAccompanistNavigation
implementation libs.preference
implementation libs.activityCompose
implementation libs.composeReorderable
implementation libs.composeReorderableLegacy
implementation libs.room
implementation libs.appUpdater

View file

@ -14,6 +14,7 @@ import com.anytypeio.anytype.presentation.MockBlockContentFactory
import com.anytypeio.anytype.presentation.MockBlockFactory
import com.anytypeio.anytype.presentation.editor.cover.CoverColor
import com.anytypeio.anytype.core_models.ObjectViewDetails
import com.anytypeio.anytype.core_models.Relations
import com.anytypeio.anytype.test_utils.MockDataFactory
import com.anytypeio.anytype.test_utils.utils.checkHasText
import com.anytypeio.anytype.test_utils.utils.checkIsDisplayed
@ -85,7 +86,7 @@ class LayoutTesting : EditorTestSetup() {
root to
mapOf(
"iconEmoji" to DefaultDocumentEmojiIconProvider.DOCUMENT_SET.random(),
"layout" to ObjectType.Layout.TODO.code.toDouble()
Relations.LAYOUT to ObjectType.Layout.TODO.code.toDouble()
)
)
)
@ -135,7 +136,7 @@ class LayoutTesting : EditorTestSetup() {
root to
mapOf(
"iconEmoji" to DefaultDocumentEmojiIconProvider.DOCUMENT_SET.random(),
"layout" to ObjectType.Layout.TODO.code.toDouble()
Relations.LAYOUT to ObjectType.Layout.TODO.code.toDouble()
)
)
)
@ -180,7 +181,7 @@ class LayoutTesting : EditorTestSetup() {
root to
mapOf(
"iconEmoji" to DefaultDocumentEmojiIconProvider.DOCUMENT_SET.random(),
"layout" to ObjectType.Layout.TODO.code.toDouble(),
Relations.LAYOUT to ObjectType.Layout.TODO.code.toDouble(),
"coverType" to CoverType.COLOR.code.toDouble(),
"coverId" to CoverColor.BLUE.code,
)
@ -226,7 +227,7 @@ class LayoutTesting : EditorTestSetup() {
root to
mapOf(
"iconEmoji" to DefaultDocumentEmojiIconProvider.DOCUMENT_SET.random(),
"layout" to ObjectType.Layout.PROFILE.code.toDouble()
Relations.LAYOUT to ObjectType.Layout.PROFILE.code.toDouble()
)
)
)
@ -270,7 +271,7 @@ class LayoutTesting : EditorTestSetup() {
root to
mapOf(
"iconEmoji" to DefaultDocumentEmojiIconProvider.DOCUMENT_SET.random(),
"layout" to ObjectType.Layout.PROFILE.code.toDouble(),
Relations.LAYOUT to ObjectType.Layout.PROFILE.code.toDouble(),
"coverType" to CoverType.COLOR.code.toDouble(),
"coverId" to CoverColor.BLUE.code,
)
@ -316,7 +317,7 @@ class LayoutTesting : EditorTestSetup() {
root to
mapOf(
"iconEmoji" to DefaultDocumentEmojiIconProvider.DOCUMENT_SET.random(),
"layout" to ObjectType.Layout.BASIC.code.toDouble()
Relations.LAYOUT to ObjectType.Layout.BASIC.code.toDouble()
)
)
)
@ -360,7 +361,7 @@ class LayoutTesting : EditorTestSetup() {
root to
mapOf(
"iconEmoji" to DefaultDocumentEmojiIconProvider.DOCUMENT_SET.random(),
"layout" to ObjectType.Layout.BASIC.code.toDouble(),
Relations.LAYOUT to ObjectType.Layout.BASIC.code.toDouble(),
"coverType" to CoverType.COLOR.code.toDouble(),
"coverId" to CoverColor.BLUE.code,
)

View file

@ -14,6 +14,7 @@ import com.anytypeio.anytype.features.editor.base.EditorTestSetup
import com.anytypeio.anytype.presentation.MockBlockContentFactory.StubTextContent
import com.anytypeio.anytype.presentation.editor.cover.CoverColor
import com.anytypeio.anytype.core_models.ObjectViewDetails
import com.anytypeio.anytype.core_models.Relations
import com.anytypeio.anytype.test_utils.MockDataFactory
import com.anytypeio.anytype.test_utils.utils.checkHasText
import com.anytypeio.anytype.test_utils.utils.checkIsDisplayed
@ -182,7 +183,7 @@ class ProfileTesting : EditorTestSetup() {
root to
mapOf(
"iconImage" to "anyimage",
"layout" to ObjectType.Layout.PROFILE.code.toDouble(),
Relations.LAYOUT to ObjectType.Layout.PROFILE.code.toDouble(),
"coverType" to CoverType.COLOR.code.toDouble(),
"coverId" to CoverColor.BLUE.code,
)
@ -195,7 +196,7 @@ class ProfileTesting : EditorTestSetup() {
mapOf(
root to
mapOf(
"layout" to ObjectType.Layout.PROFILE.code.toDouble(),
Relations.LAYOUT to ObjectType.Layout.PROFILE.code.toDouble(),
"coverType" to CoverType.COLOR.code.toDouble(),
"coverId" to CoverColor.BLUE.code,
)

View file

@ -11,6 +11,7 @@ import com.anytypeio.anytype.di.feature.DaggerBacklinkOrAddToObjectComponent
import com.anytypeio.anytype.di.feature.DaggerDateObjectComponent
import com.anytypeio.anytype.di.feature.DaggerLinkToObjectComponent
import com.anytypeio.anytype.di.feature.DaggerMoveToComponent
import com.anytypeio.anytype.di.feature.DaggerObjectTypeComponent
import com.anytypeio.anytype.di.feature.DaggerSplashComponent
import com.anytypeio.anytype.di.feature.DebugSettingsModule
import com.anytypeio.anytype.di.feature.DefaultComponentParam
@ -105,6 +106,7 @@ import com.anytypeio.anytype.di.main.MainComponent
import com.anytypeio.anytype.feature_allcontent.presentation.AllContentViewModel
import com.anytypeio.anytype.feature_chats.presentation.ChatReactionViewModel
import com.anytypeio.anytype.feature_chats.presentation.ChatViewModel
import com.anytypeio.anytype.feature_object_type.ui.ObjectTypeVmParams
import com.anytypeio.anytype.feature_chats.presentation.SelectChatReactionViewModel
import com.anytypeio.anytype.feature_date.viewmodel.DateObjectVmParams
import com.anytypeio.anytype.gallery_experience.viewmodel.GalleryInstallationViewModel
@ -1133,6 +1135,12 @@ class ComponentManager(
.create(findComponentDependencies())
}
val objectTypeComponent = ComponentWithParams { params: ObjectTypeVmParams ->
DaggerObjectTypeComponent
.factory()
.create(params, findComponentDependencies())
}
class Component<T>(private val builder: () -> T) {
private var instance: T? = null

View file

@ -16,6 +16,7 @@ import com.anytypeio.anytype.domain.misc.DeepLinkResolver
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.domain.multiplayer.GetSpaceInviteLink
import com.anytypeio.anytype.domain.multiplayer.SpaceViewSubscriptionContainer
import com.anytypeio.anytype.domain.multiplayer.UserPermissionProvider
import com.anytypeio.anytype.domain.`object`.DuplicateObject
import com.anytypeio.anytype.domain.`object`.SetObjectDetails
import com.anytypeio.anytype.domain.objects.SetObjectListIsArchived
@ -23,6 +24,8 @@ import com.anytypeio.anytype.domain.page.AddBackLinkToObject
import com.anytypeio.anytype.domain.page.CloseBlock
import com.anytypeio.anytype.domain.page.OpenPage
import com.anytypeio.anytype.domain.primitives.FieldParser
import com.anytypeio.anytype.domain.relations.AddToFeaturedRelations
import com.anytypeio.anytype.domain.relations.RemoveFromFeaturedRelations
import com.anytypeio.anytype.domain.templates.CreateTemplateFromObject
import com.anytypeio.anytype.domain.widgets.CreateWidget
import com.anytypeio.anytype.domain.workspace.SpaceManager
@ -122,7 +125,10 @@ object ObjectMenuModule {
setObjectIsArchived: SetObjectListIsArchived,
fieldParser: FieldParser,
spaceViewSubscriptionContainer: SpaceViewSubscriptionContainer,
getSpaceInviteLink: GetSpaceInviteLink
getSpaceInviteLink: GetSpaceInviteLink,
addToFeaturedRelations: AddToFeaturedRelations,
removeFromFeaturedRelations: RemoveFromFeaturedRelations,
userPermissionProvider: UserPermissionProvider
): ObjectMenuViewModel.Factory = ObjectMenuViewModel.Factory(
setObjectIsArchived = setObjectIsArchived,
duplicateObject = duplicateObject,
@ -147,7 +153,10 @@ object ObjectMenuModule {
setObjectListIsFavorite = setObjectListIsFavorite,
fieldParser = fieldParser,
getSpaceInviteLink = getSpaceInviteLink,
spaceViewSubscriptionContainer = spaceViewSubscriptionContainer
spaceViewSubscriptionContainer = spaceViewSubscriptionContainer,
addToFeaturedRelations = addToFeaturedRelations,
removeFromFeaturedRelations = removeFromFeaturedRelations,
userPermissionProvider = userPermissionProvider
)
@JvmStatic
@ -214,6 +223,18 @@ object ObjectMenuModule {
repo: BlockRepository,
dispatchers: AppCoroutineDispatchers
): SetObjectListIsArchived = SetObjectListIsArchived(repo = repo, dispatchers = dispatchers)
@JvmStatic
@Provides
@PerDialog
fun addToFeaturedRelations(repo: BlockRepository): AddToFeaturedRelations =
AddToFeaturedRelations(repo)
@JvmStatic
@Provides
@PerDialog
fun removeFromFeaturedRelations(repo: BlockRepository): RemoveFromFeaturedRelations =
RemoveFromFeaturedRelations(repo)
}
@Module
@ -242,7 +263,10 @@ object ObjectSetMenuModule {
setObjectIsArchived: SetObjectListIsArchived,
fieldParser: FieldParser,
getSpaceInviteLink: GetSpaceInviteLink,
spaceViewSubscriptionContainer: SpaceViewSubscriptionContainer
spaceViewSubscriptionContainer: SpaceViewSubscriptionContainer,
addToFeaturedRelations: AddToFeaturedRelations,
removeFromFeaturedRelations: RemoveFromFeaturedRelations,
userPermissionProvider: UserPermissionProvider
): ObjectSetMenuViewModel.Factory = ObjectSetMenuViewModel.Factory(
setObjectListIsArchived = setObjectIsArchived,
addBackLinkToObject = addBackLinkToObject,
@ -263,7 +287,10 @@ object ObjectSetMenuModule {
setObjectListIsFavorite = setObjectListIsFavorite,
fieldParser = fieldParser,
getSpaceInviteLink = getSpaceInviteLink,
spaceViewSubscriptionContainer = spaceViewSubscriptionContainer
spaceViewSubscriptionContainer = spaceViewSubscriptionContainer,
addToFeaturedRelations = addToFeaturedRelations,
removeFromFeaturedRelations = removeFromFeaturedRelations,
userPermissionProvider = userPermissionProvider
)
@JvmStatic
@ -333,4 +360,16 @@ object ObjectSetMenuModule {
)
}
}
@JvmStatic
@Provides
@PerDialog
fun addToFeaturedRelations(repo: BlockRepository): AddToFeaturedRelations =
AddToFeaturedRelations(repo)
@JvmStatic
@Provides
@PerDialog
fun removeFromFeaturedRelations(repo: BlockRepository): RemoveFromFeaturedRelations =
RemoveFromFeaturedRelations(repo)
}

View file

@ -3,11 +3,16 @@ package com.anytypeio.anytype.di.feature
import com.anytypeio.anytype.analytics.base.Analytics
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.core_utils.di.scope.PerModal
import com.anytypeio.anytype.core_utils.di.scope.PerScreen
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.domain.multiplayer.UserPermissionProvider
import com.anytypeio.anytype.domain.`object`.UpdateDetail
import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes
import com.anytypeio.anytype.domain.objects.StoreOfRelations
import com.anytypeio.anytype.domain.primitives.FieldParser
import com.anytypeio.anytype.domain.primitives.SetObjectTypeRecommendedFields
import com.anytypeio.anytype.domain.relations.AddRelationToObject
import com.anytypeio.anytype.domain.relations.AddToFeaturedRelations
import com.anytypeio.anytype.domain.relations.DeleteRelationFromObject
@ -18,6 +23,7 @@ import com.anytypeio.anytype.presentation.relations.ObjectRelationListViewModelF
import com.anytypeio.anytype.presentation.relations.RelationListViewModel
import com.anytypeio.anytype.presentation.relations.providers.ObjectRelationListProvider
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.anytypeio.anytype.ui.primitives.ObjectFieldsFragment
import com.anytypeio.anytype.ui.relations.ObjectRelationListFragment
import dagger.BindsInstance
import dagger.Module
@ -37,6 +43,7 @@ interface ObjectRelationListComponent {
}
fun inject(fragment: ObjectRelationListFragment)
fun inject(fragment: ObjectFieldsFragment)
}
@Module
@ -56,9 +63,12 @@ object ObjectRelationListModule {
deleteRelationFromObject: DeleteRelationFromObject,
analytics: Analytics,
storeOfRelations: StoreOfRelations,
storeOfObjectTypes: StoreOfObjectTypes,
addRelationToObject: AddRelationToObject,
analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate,
fieldParser: FieldParser,
userPermissionProvider: UserPermissionProvider,
setObjectTypeRecommendedFields: SetObjectTypeRecommendedFields
): ObjectRelationListViewModelFactory {
return ObjectRelationListViewModelFactory(
vmParams = vmParams,
@ -72,9 +82,12 @@ object ObjectRelationListModule {
deleteRelationFromObject = deleteRelationFromObject,
analytics = analytics,
storeOfRelations = storeOfRelations,
storeOfObjectTypes = storeOfObjectTypes,
addRelationToObject = addRelationToObject,
analyticSpaceHelperDelegate = analyticSpaceHelperDelegate,
fieldParser = fieldParser,
userPermissionProvider = userPermissionProvider,
setObjectTypeRecommendedFields = setObjectTypeRecommendedFields
)
}
@ -95,4 +108,12 @@ object ObjectRelationListModule {
@PerModal
fun deleteRelationFromObject(repo: BlockRepository): DeleteRelationFromObject =
DeleteRelationFromObject(repo)
@JvmStatic
@Provides
@PerModal
fun provideTypeSetRecommendedFields(
repo: BlockRepository,
dispatchers: AppCoroutineDispatchers
): SetObjectTypeRecommendedFields = SetObjectTypeRecommendedFields(repo, dispatchers)
}

View file

@ -0,0 +1,189 @@
package com.anytypeio.anytype.di.feature
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.interactor.sets.CreateObjectSet
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.event.interactor.EventChannel
import com.anytypeio.anytype.domain.event.interactor.SpaceSyncAndP2PStatusProvider
import com.anytypeio.anytype.domain.launch.GetDefaultObjectType
import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer
import com.anytypeio.anytype.domain.misc.LocaleProvider
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.domain.multiplayer.UserPermissionProvider
import com.anytypeio.anytype.domain.`object`.DuplicateObjects
import com.anytypeio.anytype.domain.`object`.SetObjectDetails
import com.anytypeio.anytype.domain.objects.DeleteObjects
import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes
import com.anytypeio.anytype.domain.objects.StoreOfRelations
import com.anytypeio.anytype.domain.page.CreateObject
import com.anytypeio.anytype.domain.primitives.FieldParser
import com.anytypeio.anytype.domain.primitives.GetObjectTypeConflictingFields
import com.anytypeio.anytype.domain.primitives.SetObjectTypeHeaderRecommendedFields
import com.anytypeio.anytype.domain.primitives.SetObjectTypeRecommendedFields
import com.anytypeio.anytype.domain.resources.StringResourceProvider
import com.anytypeio.anytype.domain.search.SubscriptionEventChannel
import com.anytypeio.anytype.feature_object_type.viewmodel.ObjectTypeVMFactory
import com.anytypeio.anytype.feature_object_type.ui.ObjectTypeVmParams
import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate
import com.anytypeio.anytype.presentation.editor.cover.CoverImageHashProvider
import com.anytypeio.anytype.providers.DefaultCoverImageHashProvider
import com.anytypeio.anytype.ui.primitives.ObjectTypeFieldsFragment
import com.anytypeio.anytype.ui.primitives.ObjectTypeFragment
import dagger.Binds
import dagger.BindsInstance
import dagger.Component
import dagger.Module
import dagger.Provides
@Component(
dependencies = [ObjectTypeDependencies::class],
modules = [
ObjectTypeModule::class,
ObjectTypeModule.Declarations::class
]
)
@PerScreen
interface ObjectTypeComponent {
@Component.Factory
interface Factory {
fun create(
@BindsInstance vmParams: ObjectTypeVmParams,
dependencies: ObjectTypeDependencies
): ObjectTypeComponent
}
fun inject(fragment: ObjectTypeFragment)
fun inject(fragment: ObjectTypeFieldsFragment)
}
@Module
object ObjectTypeModule {
@JvmStatic
@Provides
@PerScreen
fun provideStoreLessSubscriptionContainer(
repo: BlockRepository,
channel: SubscriptionEventChannel,
dispatchers: AppCoroutineDispatchers,
logger: Logger
): StorelessSubscriptionContainer = StorelessSubscriptionContainer.Impl(
repo = repo,
channel = channel,
dispatchers = dispatchers,
logger = logger
)
@JvmStatic
@Provides
@PerScreen
fun createObject(
repo: BlockRepository,
getDefaultObjectType: GetDefaultObjectType,
dispatchers: AppCoroutineDispatchers,
): CreateObject = CreateObject(
repo = repo,
getDefaultObjectType = getDefaultObjectType,
dispatchers = dispatchers
)
@JvmStatic
@Provides
@PerScreen
fun provideUpdateDetailUseCase(
repository: BlockRepository,
dispatchers: AppCoroutineDispatchers
): SetObjectDetails = SetObjectDetails(repository, dispatchers)
@JvmStatic
@Provides
@PerScreen
fun coverHashProvider(): CoverImageHashProvider = DefaultCoverImageHashProvider()
@JvmStatic
@PerScreen
@Provides
fun getDeleteObjects(
repo: BlockRepository,
dispatchers: AppCoroutineDispatchers
): DeleteObjects = DeleteObjects(repo, dispatchers)
@JvmStatic
@PerScreen
@Provides
fun getObjectTypeConflictingFields(
repo: BlockRepository,
dispatchers: AppCoroutineDispatchers
): GetObjectTypeConflictingFields = GetObjectTypeConflictingFields(repo, dispatchers)
@JvmStatic
@Provides
@PerScreen
fun provideCreateObjectSetUseCase(
repo: BlockRepository
): CreateObjectSet = CreateObjectSet(repo = repo)
@JvmStatic
@Provides
@PerScreen
fun provideDuplicateObjectsListUseCase(
repo: BlockRepository,
dispatchers: AppCoroutineDispatchers
): DuplicateObjects = DuplicateObjects(
repo = repo,
dispatchers = dispatchers
)
@JvmStatic
@Provides
@PerScreen
fun provideTypeSetRecommendedFields(
repo: BlockRepository,
dispatchers: AppCoroutineDispatchers
): SetObjectTypeRecommendedFields = SetObjectTypeRecommendedFields(repo, dispatchers)
@JvmStatic
@Provides
@PerScreen
fun provideTypeSetHeaderRecommendedFields(
repo: BlockRepository,
dispatchers: AppCoroutineDispatchers
): SetObjectTypeHeaderRecommendedFields =
SetObjectTypeHeaderRecommendedFields(repo, dispatchers)
@Module
interface Declarations {
@PerScreen
@Binds
fun bindViewModelFactory(
factory: ObjectTypeVMFactory
): ViewModelProvider.Factory
}
}
interface ObjectTypeDependencies : ComponentDependencies {
fun blockRepository(): BlockRepository
fun analytics(): Analytics
fun urlBuilder(): UrlBuilder
fun dispatchers(): AppCoroutineDispatchers
fun storeOfObjectTypes(): StoreOfObjectTypes
fun analyticsHelper(): AnalyticSpaceHelperDelegate
fun subEventChannel(): SubscriptionEventChannel
fun logger(): Logger
fun localeProvider(): LocaleProvider
fun config(): ConfigStorage
fun userPermissionProvider(): UserPermissionProvider
fun provideStoreOfRelations(): StoreOfRelations
fun provideSpaceSyncAndP2PStatusProvider(): SpaceSyncAndP2PStatusProvider
fun provideUserSettingsRepository(): UserSettingsRepository
fun fieldParser(): FieldParser
fun provideEventChannel(): EventChannel
fun provideStringResourceProvider(): StringResourceProvider
}

View file

@ -17,6 +17,7 @@ import com.anytypeio.anytype.di.feature.MainEntrySubComponent
import com.anytypeio.anytype.di.feature.MoveToDependencies
import com.anytypeio.anytype.di.feature.ObjectSetSubComponent
import com.anytypeio.anytype.di.feature.ObjectTypeChangeSubComponent
import com.anytypeio.anytype.di.feature.ObjectTypeDependencies
import com.anytypeio.anytype.di.feature.PersonalizationSettingsSubComponent
import com.anytypeio.anytype.di.feature.SplashDependencies
import com.anytypeio.anytype.di.feature.auth.DeletedAccountDependencies
@ -138,6 +139,7 @@ interface MainComponent :
LinkToObjectDependencies,
MoveToDependencies,
DateObjectDependencies,
ObjectTypeDependencies,
SelectChatReactionDependencies,
ChatReactionDependencies,
ParticipantComponentDependencies,
@ -388,6 +390,11 @@ abstract class ComponentDependenciesModule {
@ComponentDependenciesKey(DateObjectDependencies::class)
abstract fun provideDateObjectDependencies(component: MainComponent): ComponentDependencies
@Binds
@IntoMap
@ComponentDependenciesKey(ObjectTypeDependencies::class)
abstract fun provideObjectTypeDependencies(component: MainComponent): ComponentDependencies
@Binds
@IntoMap
@ComponentDependenciesKey(SelectChatReactionDependencies::class)

View file

@ -16,7 +16,9 @@ import com.anytypeio.anytype.ui.date.DateObjectFragment
import com.anytypeio.anytype.ui.editor.EditorFragment
import com.anytypeio.anytype.ui.editor.EditorModalFragment
import com.anytypeio.anytype.ui.multiplayer.ShareSpaceFragment
import com.anytypeio.anytype.ui.primitives.ObjectTypeFieldsFragment
import com.anytypeio.anytype.ui.profile.ParticipantFragment
import com.anytypeio.anytype.ui.primitives.ObjectTypeFragment
import com.anytypeio.anytype.ui.relations.RelationCreateFromScratchForObjectFragment
import com.anytypeio.anytype.ui.relations.RelationEditFragment
import com.anytypeio.anytype.ui.search.GlobalSearchFragment
@ -357,4 +359,30 @@ class Navigator : AppNavigation {
)
)
}
override fun openObjectType(
objectId: Id,
space: Id
) {
navController?.navigate(
resId = R.id.objectTypeScreen,
args = ObjectTypeFragment.args(
objectId = objectId,
space = space
)
)
}
override fun openCurrentObjectTypeFields(
objectId: Id,
space: Id
) {
navController?.navigate(
resId = R.id.objectTypeFieldsScreen,
args = ObjectTypeFieldsFragment.args(
objectId = objectId,
space = space
)
)
}
}

View file

@ -207,11 +207,9 @@ class AllContentFragment : BaseComposeFragment(), ObjectTypeSelectionListener {
is AllContentViewModel.Command.OpenTypeEditing -> {
runCatching {
navigation().openTypeEditingScreen(
id = command.item.id,
name = command.item.name,
icon = (command.item.icon as? ObjectIcon.Basic.Emoji)?.unicode ?: "",
readOnly = command.item.readOnly
navigation().openObjectType(
objectId = command.item.id,
space = space
)
}.onFailure {
toast("Failed to open type editing screen")

View file

@ -167,7 +167,7 @@ import com.anytypeio.anytype.ui.objects.creation.ObjectTypeSelectionFragment
import com.anytypeio.anytype.ui.objects.creation.ObjectTypeUpdateFragment
import com.anytypeio.anytype.ui.objects.types.pickers.ObjectTypeSelectionListener
import com.anytypeio.anytype.ui.objects.types.pickers.ObjectTypeUpdateListener
import com.anytypeio.anytype.ui.relations.ObjectRelationListFragment
import com.anytypeio.anytype.ui.primitives.ObjectFieldsFragment
import com.anytypeio.anytype.ui.relations.RelationAddToObjectBlockFragment
import com.anytypeio.anytype.ui.relations.RelationDateValueFragment
import com.anytypeio.anytype.ui.relations.RelationTextValueFragment
@ -1074,10 +1074,10 @@ open class EditorFragment : NavigationFragment<FragmentEditorBinding>(R.layout.f
R.id.pageScreen,
R.id.objectRelationListScreen,
bundleOf(
ObjectRelationListFragment.ARG_CTX to command.ctx,
ObjectRelationListFragment.ARG_SPACE to space,
ObjectRelationListFragment.ARG_TARGET to command.target,
ObjectRelationListFragment.ARG_LOCKED to command.isLocked,
ObjectFieldsFragment.ARG_CTX to command.ctx,
ObjectFieldsFragment.ARG_SPACE to space,
ObjectFieldsFragment.ARG_TARGET to command.target,
ObjectFieldsFragment.ARG_LOCKED to command.isLocked,
)
)
}

View file

@ -25,6 +25,7 @@ import com.anytypeio.anytype.presentation.editor.layout.ObjectLayoutViewModel
import com.anytypeio.anytype.presentation.objects.ObjectLayoutView
import javax.inject.Inject
@Deprecated("epic Primitives")
class ObjectLayoutFragment : BaseBottomSheetFragment<FragmentObjectLayoutBinding>() {
private val ctx: String get() = argString(CONTEXT_ID_KEY)

View file

@ -31,13 +31,12 @@ import com.anytypeio.anytype.presentation.objects.menu.ObjectMenuViewModelBase
import com.anytypeio.anytype.ui.base.navigation
import com.anytypeio.anytype.ui.editor.cover.SelectCoverObjectFragment
import com.anytypeio.anytype.ui.editor.cover.SelectCoverObjectSetFragment
import com.anytypeio.anytype.ui.editor.layout.ObjectLayoutFragment
import com.anytypeio.anytype.ui.editor.modals.IconPickerFragmentBase
import com.anytypeio.anytype.ui.history.VersionHistoryFragment
import com.anytypeio.anytype.ui.linking.BacklinkAction
import com.anytypeio.anytype.ui.linking.BacklinkOrAddToObjectFragment
import com.anytypeio.anytype.ui.moving.OnMoveToAction
import com.anytypeio.anytype.ui.relations.ObjectRelationListFragment
import com.anytypeio.anytype.ui.primitives.ObjectFieldsFragment
import com.google.android.material.snackbar.Snackbar
import timber.log.Timber
@ -84,7 +83,7 @@ abstract class ObjectMenuBaseFragment :
override fun onStart() {
click(binding.objectDiagnostics) { vm.onDiagnosticsClicked(ctx = ctx) }
click(binding.optionHistory) { vm.onHistoryClicked(ctx = ctx, space = space) }
click(binding.optionLayout) { vm.onLayoutClicked(ctx = ctx, space = space) }
click(binding.optionDescription) { vm.onDescriptionClicked(ctx = ctx, space = space) }
click(binding.optionIcon) { vm.onIconClicked(ctx = ctx, space = space) }
click(binding.optionRelations) { vm.onRelationsClicked() }
click(binding.optionCover) { vm.onCoverClicked(ctx = ctx, space = space) }
@ -116,19 +115,22 @@ abstract class ObjectMenuBaseFragment :
private fun renderOptions(options: ObjectMenuOptionsProvider.Options) {
val iconVisibility = options.hasIcon.toVisibility()
val coverVisibility = options.hasCover.toVisibility()
val layoutVisibility = options.hasLayout.toVisibility()
val relationsVisibility = options.hasRelations.toVisibility()
val historyVisibility = options.hasHistory.toVisibility()
val objectDiagnosticsVisibility = options.hasDiagnosticsVisibility.toVisibility()
if (options.hasDescriptionShow) {
binding.optionDescription.setAction(setAsHide = false)
} else {
binding.optionDescription.setAction(setAsHide = true)
}
binding.optionIcon.visibility = iconVisibility
binding.optionCover.visibility = coverVisibility
binding.optionLayout.visibility = layoutVisibility
binding.optionRelations.visibility = relationsVisibility
binding.optionHistory.visibility = historyVisibility
binding.iconDivider.visibility = iconVisibility
binding.coverDivider.visibility = coverVisibility
binding.layoutDivider.visibility = layoutVisibility
binding.relationsDivider.visibility = relationsVisibility
binding.historyDivider.visibility = historyVisibility
binding.objectDiagnostics.visibility = objectDiagnosticsVisibility
@ -139,7 +141,6 @@ abstract class ObjectMenuBaseFragment :
when (command) {
ObjectMenuViewModelBase.Command.OpenObjectCover -> openObjectCover()
ObjectMenuViewModelBase.Command.OpenObjectIcons -> openObjectIcons()
ObjectMenuViewModelBase.Command.OpenObjectLayout -> openObjectLayout()
ObjectMenuViewModelBase.Command.OpenObjectRelations -> openObjectRelations()
ObjectMenuViewModelBase.Command.OpenSetCover -> openSetCover()
ObjectMenuViewModelBase.Command.OpenSetIcons -> openSetIcons()
@ -218,20 +219,15 @@ abstract class ObjectMenuBaseFragment :
)
}
private fun openObjectLayout() {
val fr = ObjectLayoutFragment.new(ctx = ctx, space = space)
fr.showChildFragment()
}
private fun openObjectRelations() {
findNavController().navigate(
R.id.objectRelationListScreen,
bundleOf(
ObjectRelationListFragment.ARG_CTX to ctx,
ObjectRelationListFragment.ARG_SPACE to space,
ObjectRelationListFragment.ARG_TARGET to null,
ObjectRelationListFragment.ARG_LOCKED to isLocked,
ObjectRelationListFragment.ARG_SET_FLOW to false
ObjectFieldsFragment.ARG_CTX to ctx,
ObjectFieldsFragment.ARG_SPACE to space,
ObjectFieldsFragment.ARG_TARGET to null,
ObjectFieldsFragment.ARG_LOCKED to isLocked,
ObjectFieldsFragment.ARG_SET_FLOW to false
)
)
}
@ -240,11 +236,11 @@ abstract class ObjectMenuBaseFragment :
findNavController().navigate(
R.id.objectRelationListScreen,
bundleOf(
ObjectRelationListFragment.ARG_CTX to ctx,
ObjectRelationListFragment.ARG_SPACE to space,
ObjectRelationListFragment.ARG_TARGET to null,
ObjectRelationListFragment.ARG_LOCKED to isLocked,
ObjectRelationListFragment.ARG_SET_FLOW to true
ObjectFieldsFragment.ARG_CTX to ctx,
ObjectFieldsFragment.ARG_SPACE to space,
ObjectFieldsFragment.ARG_TARGET to null,
ObjectFieldsFragment.ARG_LOCKED to isLocked,
ObjectFieldsFragment.ARG_SET_FLOW to true
)
)
}

View file

@ -0,0 +1,313 @@
package com.anytypeio.anytype.ui.primitives
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.ui.Modifier
import androidx.core.os.bundleOf
import androidx.fragment.app.viewModels
import androidx.fragment.compose.content
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.Key
import com.anytypeio.anytype.core_models.TimeInMillis
import com.anytypeio.anytype.core_models.primitives.SpaceId
import com.anytypeio.anytype.core_ui.features.fields.FieldListScreen
import com.anytypeio.anytype.core_utils.ext.arg
import com.anytypeio.anytype.core_utils.ext.argString
import com.anytypeio.anytype.core_utils.ext.argStringOrNull
import com.anytypeio.anytype.core_utils.ext.safeNavigate
import com.anytypeio.anytype.core_utils.ext.setupBottomSheetBehavior
import com.anytypeio.anytype.core_utils.ext.subscribe
import com.anytypeio.anytype.core_utils.ext.toast
import com.anytypeio.anytype.core_utils.ext.withParent
import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetComposeFragment
import com.anytypeio.anytype.di.common.componentManager
import com.anytypeio.anytype.di.feature.DefaultComponentParam
import com.anytypeio.anytype.feature_object_type.fields.ui.LocalInfoScreen
import com.anytypeio.anytype.presentation.relations.ObjectRelationListViewModelFactory
import com.anytypeio.anytype.presentation.relations.RelationListViewModel
import com.anytypeio.anytype.presentation.relations.RelationListViewModel.Command
import com.anytypeio.anytype.presentation.relations.value.tagstatus.RelationContext
import com.anytypeio.anytype.ui.base.navigation
import com.anytypeio.anytype.ui.editor.OnFragmentInteractionListener
import com.anytypeio.anytype.ui.relations.RelationDateValueFragment
import com.anytypeio.anytype.ui.relations.RelationTextValueFragment
import com.anytypeio.anytype.ui.relations.value.ObjectValueFragment
import com.anytypeio.anytype.ui.relations.value.TagOrStatusValueFragment
import javax.inject.Inject
import kotlin.getValue
import timber.log.Timber
class ObjectFieldsFragment : BaseBottomSheetComposeFragment(),
RelationTextValueFragment.TextValueEditReceiver,
RelationDateValueFragment.DateValueEditReceiver {
private val vm by viewModels<RelationListViewModel> { factory }
private val ctx: String get() = argString(ARG_CTX)
private val space: String get() = argString(ARG_SPACE)
private val target: String? get() = argStringOrNull(ARG_TARGET)
private val isSetFlow: Boolean get() = arg(ARG_SET_FLOW)
@Inject
lateinit var factory: ObjectRelationListViewModelFactory
@OptIn(ExperimentalMaterial3Api::class)
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
) =
content {
MaterialTheme {
FieldListScreen(
state = vm.views.collectAsStateWithLifecycle().value,
onRelationClicked = {
vm.onRelationClicked(
ctx = ctx,
target = target,
view = it.view
)
},
onLocalInfoIconClicked = {
vm.onShowLocalInfo()
},
onTypeIconClicked = {
vm.onTypeIconClicked()
},
onRemoveFromObjectClicked = vm::onRemoveFromObjectClicked,
onAddToTypeClicked = vm::onAddToTypeClicked
)
val showLocalFieldExplanationScreen = vm.showLocalInfo.collectAsStateWithLifecycle().value
if (showLocalFieldExplanationScreen) {
val bottomSheetState = rememberModalBottomSheetState(
skipPartiallyExpanded = true
)
LocalInfoScreen(
modifier = Modifier.fillMaxWidth(),
bottomSheetState = bottomSheetState,
onDismiss = { vm.onDismissLocalInfo() }
)
}
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupBottomSheetBehavior(DEFAULT_PADDING_TOP)
}
private fun execute(command: Command) {
when (command) {
is Command.EditTextRelationValue -> {
runCatching {
val fr = RelationTextValueFragment.new(
ctx = ctx,
relationKey = command.relationKey,
objectId = command.target,
isLocked = command.isLocked,
flow = if (isSetFlow)
RelationTextValueFragment.FLOW_DATAVIEW
else
RelationTextValueFragment.FLOW_DEFAULT,
space = space
)
fr.showChildFragment()
}.onFailure {
Timber.e(it, "Error while opening relation text value from relation list")
}
}
is Command.EditDateRelationValue -> {
val fr = RelationDateValueFragment.new(
ctx = ctx,
space = space,
relationKey = command.relationKey,
objectId = command.target,
flow = if (isSetFlow) {
RelationDateValueFragment.FLOW_SET_OR_COLLECTION
} else {
RelationDateValueFragment.FLOW_DEFAULT
},
isLocked = command.isLocked
)
fr.showChildFragment()
}
is Command.EditFileObjectRelationValue -> {
val relationContext =
if (isSetFlow) RelationContext.OBJECT_SET else RelationContext.OBJECT
findNavController().safeNavigate(
R.id.objectRelationListScreen,
R.id.objectValueScreen,
ObjectValueFragment.args(
ctx = command.ctx,
space = space,
obj = command.target,
relation = command.relationKey,
isLocked = command.isLocked,
relationContext = relationContext
)
)
}
is Command.SetRelationKey -> {
withParent<OnFragmentInteractionListener> {
onSetRelationKeyClicked(
blockId = command.blockId,
key = command.key
)
}
dismiss()
}
is Command.EditTagOrStatusRelationValue -> {
val relationContext =
if (isSetFlow) RelationContext.OBJECT_SET else RelationContext.OBJECT
val bundle = TagOrStatusValueFragment.args(
ctx = command.ctx,
space = space,
obj = command.target,
relation = command.relationKey,
isLocked = command.isLocked,
context = relationContext
)
findNavController().safeNavigate(
R.id.objectRelationListScreen,
R.id.nav_relations,
bundle
)
}
is Command.NavigateToDateObject -> {
runCatching {
navigation().openDateObject(
objectId = command.objectId,
space = space
)
}.onFailure {
Timber.e(it, "Error while opening date object from relation list")
}
}
is Command.NavigateToObjectType -> {
runCatching {
navigation().openCurrentObjectTypeFields(
objectId = command.objectTypeId,
space = space
)
}.onFailure {
Timber.e(it, "Error while opening object type fields from object fields list")
}
}
}
}
override fun onStart() {
jobs += lifecycleScope.subscribe(vm.commands) { command -> execute(command) }
jobs += lifecycleScope.subscribe(vm.toasts) { toast(it) }
super.onStart()
vm.onStartListMode(ctx)
}
override fun onStop() {
super.onStop()
vm.onStop()
}
override fun onTextValueChanged(ctx: Id, text: String, objectId: Id, relationKey: Key) {
vm.onRelationTextValueChanged(
ctx = ctx,
relationKey = relationKey,
value = text,
isValueEmpty = text.isEmpty()
)
}
override fun onNumberValueChanged(ctx: Id, number: Double?, objectId: Id, relationKey: Key) {
vm.onRelationTextValueChanged(
ctx = ctx,
relationKey = relationKey,
value = number,
isValueEmpty = number == null
)
}
override fun onDateValueChanged(
ctx: Id,
timeInSeconds: Number?,
objectId: Id,
relationKey: Key
) {
vm.onRelationTextValueChanged(
ctx = ctx,
relationKey = relationKey,
value = timeInSeconds,
isValueEmpty = timeInSeconds == null
)
}
override fun onOpenDateObject(timeInMillis: TimeInMillis) {
vm.onOpenDateObjectByTimeInMillis(timeInMillis)
}
override fun injectDependencies() {
val param = DefaultComponentParam(
ctx = ctx,
space = SpaceId(space)
)
if (isSetFlow) {
componentManager().objectSetRelationListComponent.get(param).inject(this)
} else {
componentManager().objectRelationListComponent.get(param).inject(this)
}
}
override fun releaseDependencies() {
if (isSetFlow) {
componentManager().objectSetRelationListComponent.release()
} else {
componentManager().objectRelationListComponent.release()
}
}
/**
* This screen should be started from Objects with Editor Layouts
* or from objects with Set or Collection Layouts
* @param isSetFlow - true if started from Set or Collection
*/
companion object {
fun new(
ctx: Id,
space: Id,
target: String?,
locked: Boolean = false,
isSetFlow: Boolean = false,
) = ObjectFieldsFragment().apply {
arguments = bundleOf(
ARG_CTX to ctx,
ARG_SPACE to space,
ARG_TARGET to target,
ARG_LOCKED to locked,
ARG_SET_FLOW to isSetFlow
)
}
const val ARG_CTX = "arg.document-relation.ctx"
const val ARG_SPACE = "arg.document-relation.space"
const val ARG_TARGET = "arg.document-relation.target"
const val ARG_LOCKED = "arg.document-relation.locked"
const val ARG_SET_FLOW = "arg.document-relation.set-flow"
const val DEFAULT_PADDING_TOP = 10
}
}

View file

@ -0,0 +1,93 @@
package com.anytypeio.anytype.ui.primitives
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.compose.material.MaterialTheme
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.core.os.bundleOf
import androidx.fragment.app.viewModels
import androidx.fragment.compose.content
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.primitives.SpaceId
import com.anytypeio.anytype.core_utils.ext.argString
import com.anytypeio.anytype.core_utils.ext.setupBottomSheetBehavior
import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetComposeFragment
import com.anytypeio.anytype.di.common.componentManager
import com.anytypeio.anytype.feature_object_type.fields.ui.FieldsMainScreen
import com.anytypeio.anytype.feature_object_type.ui.ObjectTypeVmParams
import com.anytypeio.anytype.feature_object_type.viewmodel.ObjectTypeVMFactory
import com.anytypeio.anytype.feature_object_type.viewmodel.ObjectTypeViewModel
import javax.inject.Inject
import kotlin.getValue
class ObjectTypeFieldsFragment : BaseBottomSheetComposeFragment() {
@Inject
lateinit var factory: ObjectTypeVMFactory
private val vm by viewModels<ObjectTypeViewModel> { factory }
private val space get() = argString(ARG_SPACE)
private val typeId get() = argString(ARG_OBJECT_ID)
@OptIn(ExperimentalMaterial3Api::class)
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
) = content {
MaterialTheme {
FieldsMainScreen(
uiFieldsListState = vm.uiFieldsListState.collectAsStateWithLifecycle().value,
uiTitleState = vm.uiTitleState.collectAsStateWithLifecycle().value,
uiIconState = vm.uiIconState.collectAsStateWithLifecycle().value,
uiFieldEditOrNewState = vm.uiFieldEditOrNewState.collectAsStateWithLifecycle().value,
uiFieldLocalInfoState = vm.uiFieldLocalInfoState.collectAsStateWithLifecycle().value,
uiAddFieldsScreenState = vm.uiAddFieldsState.collectAsStateWithLifecycle().value,
fieldEvent = vm::onFieldEvent
)
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupBottomSheetBehavior(DEFAULT_PADDING_TOP)
}
override fun onStart() {
super.onStart()
vm.onStart()
}
override fun onStop() {
super.onStop()
vm.onStop()
}
override fun injectDependencies() {
val params = ObjectTypeVmParams(
spaceId = SpaceId(space),
objectId = typeId,
withSubscriptions = false,
showHiddenFields = true
)
componentManager().objectTypeComponent.get(params).inject(this)
}
override fun releaseDependencies() {
componentManager().objectTypeComponent.release()
}
companion object {
const val ARG_SPACE = "arg.object.type.space"
const val ARG_OBJECT_ID = "arg.object.type.object_id"
fun args(space: Id, objectId: Id) = bundleOf(
ARG_SPACE to space,
ARG_OBJECT_ID to objectId
)
}
}

View file

@ -0,0 +1,331 @@
package com.anytypeio.anytype.ui.primitives
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.compose.material.MaterialTheme
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.res.stringResource
import androidx.core.os.bundleOf
import androidx.fragment.app.setFragmentResultListener
import androidx.fragment.app.viewModels
import androidx.fragment.compose.content
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.fragment.findNavController
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.views.BaseAlertDialog
import com.anytypeio.anytype.core_utils.ext.argString
import com.anytypeio.anytype.core_utils.ext.subscribe
import com.anytypeio.anytype.core_utils.ext.toast
import com.anytypeio.anytype.core_utils.ui.BaseComposeFragment
import com.anytypeio.anytype.di.common.componentManager
import com.anytypeio.anytype.feature_object_type.ui.ObjectTypeMainScreen
import com.anytypeio.anytype.feature_object_type.viewmodel.ObjectTypeVMFactory
import com.anytypeio.anytype.feature_object_type.viewmodel.ObjectTypeViewModel
import com.anytypeio.anytype.feature_object_type.ui.ObjectTypeVmParams
import com.anytypeio.anytype.feature_object_type.ui.UiErrorState
import com.anytypeio.anytype.feature_object_type.fields.ui.FieldsMainScreen
import com.anytypeio.anytype.feature_object_type.ui.ObjectTypeCommand
import com.anytypeio.anytype.presentation.home.OpenObjectNavigation
import com.anytypeio.anytype.ui.chats.ChatFragment
import com.anytypeio.anytype.ui.date.DateObjectFragment
import com.anytypeio.anytype.ui.editor.EditorFragment
import com.anytypeio.anytype.ui.editor.EditorModalFragment
import com.anytypeio.anytype.ui.profile.ParticipantFragment
import com.anytypeio.anytype.ui.relations.RelationAddToObjectFragment
import com.anytypeio.anytype.ui.sets.ObjectSetFragment
import com.anytypeio.anytype.ui.templates.EditorTemplateFragment.Companion.TYPE_TEMPLATE_EDIT
import com.anytypeio.anytype.ui.types.picker.REQUEST_KEY_PICK_EMOJI
import com.anytypeio.anytype.ui.types.picker.REQUEST_KEY_REMOVE_EMOJI
import com.anytypeio.anytype.ui.types.picker.RESULT_EMOJI_UNICODE
import com.google.accompanist.navigation.material.ExperimentalMaterialNavigationApi
import com.google.accompanist.navigation.material.rememberBottomSheetNavigator
import javax.inject.Inject
import kotlin.getValue
import timber.log.Timber
class ObjectTypeFragment : BaseComposeFragment() {
@Inject
lateinit var factory: ObjectTypeVMFactory
private val vm by viewModels<ObjectTypeViewModel> { factory }
private lateinit var navComposeController: NavHostController
private val space get() = argString(ARG_SPACE)
private val objectId get() = argString(ARG_OBJECT_ID)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setFragmentResultListener(REQUEST_KEY_PICK_EMOJI) { _, bundle ->
val res = requireNotNull(bundle.getString(RESULT_EMOJI_UNICODE))
vm.updateIcon(res)
}
setFragmentResultListener(REQUEST_KEY_REMOVE_EMOJI) { _, _ ->
vm.removeIcon()
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
) = content {
MaterialTheme {
ObjectTypeScreen()
ErrorScreen()
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
subscribe(vm.commands) { command ->
Timber.d("Received command: $command")
when (command) {
ObjectTypeCommand.Back -> {
runCatching {
findNavController().popBackStack()
}.onFailure { e ->
Timber.e(e, "Error while exiting back from object type screen")
}
}
ObjectTypeCommand.OpenEmojiPicker -> {
runCatching {
findNavController().navigate(R.id.openEmojiPicker)
}.onFailure {
Timber.w("Error while opening emoji picker")
}
}
is ObjectTypeCommand.OpenTemplate -> {
findNavController().navigate(
R.id.nav_editor_modal,
bundleOf(
EditorModalFragment.ARG_TEMPLATE_ID to command.templateId,
EditorModalFragment.ARG_TEMPLATE_TYPE_ID to command.typeId,
EditorModalFragment.ARG_TEMPLATE_TYPE_KEY to command.typeKey,
EditorModalFragment.ARG_SCREEN_TYPE to TYPE_TEMPLATE_EDIT,
EditorModalFragment.ARG_SPACE_ID to command.spaceId
)
)
}
ObjectTypeCommand.OpenFieldsScreen -> {
navComposeController.navigate(OBJ_TYPE_FIELDS)
}
is ObjectTypeCommand.OpenAddFieldScreen -> {
RelationAddToObjectFragment.new(
ctx = command.typeId,
space = command.space,
isSetOrCollection = command.isSet
).showChildFragment()
}
}
}
}
override fun onStart() {
super.onStart()
vm.onStart()
}
override fun onStop() {
super.onStop()
vm.onStop()
}
@OptIn(ExperimentalMaterialNavigationApi::class)
@Composable
fun ObjectTypeScreen() {
val bottomSheetNavigator = rememberBottomSheetNavigator()
navComposeController = rememberNavController(bottomSheetNavigator)
NavHost(
navController = navComposeController,
startDestination = OBJ_TYPE_MAIN
) {
composable(route = OBJ_TYPE_MAIN) {
ObjectTypeMainScreen(
uiSyncStatusBadgeState = vm.uiSyncStatusBadgeState.collectAsStateWithLifecycle().value,
uiSyncStatusState = vm.uiSyncStatusWidgetState.collectAsStateWithLifecycle().value,
uiTitleState = vm.uiTitleState.collectAsStateWithLifecycle().value,
uiIconState = vm.uiIconState.collectAsStateWithLifecycle().value,
uiFieldsButtonState = vm.uiFieldsButtonState.collectAsStateWithLifecycle().value,
uiLayoutButtonState = vm.uiLayoutButtonState.collectAsStateWithLifecycle().value,
uiTemplatesHeaderState = vm.uiTemplatesHeaderState.collectAsStateWithLifecycle().value,
uiTemplatesAddIconState = vm.uiTemplatesAddIconState.collectAsStateWithLifecycle().value,
uiTemplatesListState = vm.uiTemplatesListState.collectAsStateWithLifecycle().value,
uiObjectsHeaderState = vm.uiObjectsHeaderState.collectAsStateWithLifecycle().value,
uiObjectsAddIconState = vm.uiObjectsAddIconState.collectAsStateWithLifecycle().value,
uiObjectsSettingsIconState = vm.uiObjectsSettingsIconState.collectAsStateWithLifecycle().value,
uiObjectsMenuState = vm.uiMenuState.collectAsStateWithLifecycle().value,
uiObjectsListState = vm.uiObjectsListState.collectAsStateWithLifecycle().value,
uiContentState = vm.uiContentState.collectAsStateWithLifecycle().value,
uiDeleteAlertState = vm.uiAlertState.collectAsStateWithLifecycle().value,
uiEditButtonState = vm.uiEditButtonState.collectAsStateWithLifecycle().value,
uiLayoutTypeState = vm.uiTypeLayoutsState.collectAsStateWithLifecycle().value,
onTypeEvent = vm::onTypeEvent
)
}
composable(route = OBJ_TYPE_FIELDS) {
FieldsMainScreen(
uiFieldsListState = vm.uiFieldsListState.collectAsStateWithLifecycle().value,
uiTitleState = vm.uiTitleState.collectAsStateWithLifecycle().value,
uiIconState = vm.uiIconState.collectAsStateWithLifecycle().value,
uiFieldEditOrNewState = vm.uiFieldEditOrNewState.collectAsStateWithLifecycle().value,
uiFieldLocalInfoState = vm.uiFieldLocalInfoState.collectAsStateWithLifecycle().value,
uiAddFieldsScreenState = vm.uiAddFieldsState.collectAsStateWithLifecycle().value,
fieldEvent = vm::onFieldEvent
)
}
}
LaunchedEffect(Unit) {
vm.navigation.collect { nav ->
when (nav) {
is OpenObjectNavigation.OpenEditor -> {
findNavController().navigate(
R.id.objectNavigation,
EditorFragment.args(
ctx = nav.target,
space = nav.space
)
)
}
is OpenObjectNavigation.OpenDataView -> {
findNavController().navigate(
R.id.dataViewNavigation,
ObjectSetFragment.args(
ctx = nav.target,
space = nav.space
)
)
}
is OpenObjectNavigation.OpenParticipant -> {
runCatching {
findNavController().navigate(
R.id.participantScreen,
ParticipantFragment.args(
objectId = nav.target,
space = nav.space
)
)
}.onFailure {
Timber.w("Error while opening participant screen")
}
}
is OpenObjectNavigation.OpenChat -> {
findNavController().navigate(
R.id.chatScreen,
ChatFragment.args(
ctx = nav.target,
space = nav.space
)
)
}
OpenObjectNavigation.NonValidObject -> {
toast(getString(R.string.error_non_valid_object))
}
is OpenObjectNavigation.OpenDateObject -> {
runCatching {
findNavController().navigate(
R.id.dateObjectScreen,
DateObjectFragment.args(
objectId = nav.target,
space = nav.space
)
)
}.onFailure {
Timber.e(it, "Failed to navigate to date object screen")
}
}
is OpenObjectNavigation.UnexpectedLayoutError -> {
toast(getString(R.string.error_unexpected_layout))
}
else -> {
// Do nothing.
}
}
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun ErrorScreen() {
val errorStateScreen = vm.errorState.collectAsStateWithLifecycle().value
if (errorStateScreen is UiErrorState.Show) {
when (val r = errorStateScreen.reason) {
is UiErrorState.Reason.ErrorGettingObjects -> {
BaseAlertDialog(
dialogText = "${stringResource(R.string.object_type_open_type_error)}:\n${r.msg}",
buttonText = stringResource(id = R.string.membership_error_button_text_dismiss),
onButtonClick = vm::closeObject,
onDismissRequest = vm::closeObject
)
}
is UiErrorState.Reason.Other -> {
BaseAlertDialog(
dialogText = r.msg,
buttonText = stringResource(id = R.string.membership_error_button_text_dismiss),
onButtonClick = vm::hideError,
onDismissRequest = vm::hideError
)
}
}
}
when (val state = errorStateScreen) {
UiErrorState.Hidden -> {
}
is UiErrorState.Show -> {
}
}
}
override fun injectDependencies() {
val params = ObjectTypeVmParams(
spaceId = SpaceId(space),
objectId = objectId,
withSubscriptions = true,
showHiddenFields = true
)
componentManager().objectTypeComponent.get(params).inject(this)
}
override fun releaseDependencies() {
componentManager().objectTypeComponent.release()
}
override fun onApplyWindowRootInsets(view: View) {
// Skipping this, since window insets will be applied by compose code.
}
companion object {
private const val OBJ_TYPE_MAIN = "obj_type_main"
private const val OBJ_TYPE_FIELDS = "obj_fields"
const val ARG_SPACE = "arg.object.type.space"
const val ARG_OBJECT_ID = "arg.object.type.object_id"
fun args(space: Id, objectId: Id) = bundleOf(
ARG_SPACE to space,
ARG_OBJECT_ID to objectId
)
}
}

View file

@ -41,6 +41,7 @@ import com.anytypeio.anytype.ui.relations.value.TagOrStatusValueFragment
import javax.inject.Inject
import timber.log.Timber
@Deprecated("Legacy, epic Primitives, use ObjectFieldsFragment instead")
open class ObjectRelationListFragment : BaseBottomSheetFragment<FragmentRelationListBinding>(),
RelationTextValueFragment.TextValueEditReceiver,
RelationDateValueFragment.DateValueEditReceiver {
@ -194,6 +195,10 @@ open class ObjectRelationListFragment : BaseBottomSheetFragment<FragmentRelation
Timber.e(it, "Error while opening date object from relation list")
}
}
is Command.NavigateToObjectType -> {
//do nothing
}
}
}

View file

@ -28,24 +28,28 @@
android:layout_height="match_parent"
android:orientation="vertical">
<com.anytypeio.anytype.core_ui.widgets.ObjectMenuItemWidget
android:id="@+id/optionIcon"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:background="@drawable/default_ripple"
app:icon="@drawable/ic_object_menu_icon"
app:subtitle="@string/icon_description"
app:title="@string/icon" />
<com.anytypeio.anytype.core_ui.widgets.ObjectMenuItemWidget
android:id="@+id/optionIcon"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:background="@drawable/default_ripple"
app:icon="@drawable/ic_obj_settings_icon_24"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/dragger"
app:title="@string/icon" />
<View
android:id="@+id/iconDivider"
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_marginStart="72dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:background="@color/shape_primary" />
<View
android:id="@+id/iconDivider"
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:background="@color/shape_primary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/optionIcon" />
<com.anytypeio.anytype.core_ui.widgets.ObjectMenuItemWidget
android:id="@+id/optionCover"
@ -53,7 +57,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@drawable/default_ripple"
app:icon="@drawable/ic_object_menu_cover"
app:icon="@drawable/ic_obj_settings_cover_24"
app:subtitle="@string/cover_description"
app:title="@string/cover" />
@ -61,28 +65,28 @@
android:id="@+id/coverDivider"
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_marginStart="72dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:background="@color/shape_primary" />
<com.anytypeio.anytype.core_ui.widgets.ObjectMenuItemWidget
android:id="@+id/optionLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@drawable/default_ripple"
app:icon="@drawable/ic_object_menu_layout"
app:subtitle="@string/layout_description"
app:title="@string/layout" />
<com.anytypeio.anytype.core_ui.widgets.ObjectMenuDescriptionItem
android:id="@+id/optionDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@drawable/default_ripple"
app:icon="@drawable/ic_obj_settings_description_24"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/coverDivider"
app:title="@string/description" />
<View
android:id="@+id/layoutDivider"
android:id="@+id/descriptionDivider"
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_marginStart="72dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:background="@color/shape_primary" />
<com.anytypeio.anytype.core_ui.widgets.ObjectMenuItemWidget
@ -91,35 +95,35 @@
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@drawable/default_ripple"
app:icon="@drawable/ic_object_menu_relations"
app:subtitle="@string/relations_description"
app:title="@string/relations" />
app:icon="@drawable/ic_obj_settings_fields_24"
app:title="@string/fields" />
<View
android:id="@+id/relationsDivider"
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_marginStart="72dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:background="@color/shape_primary" />
<com.anytypeio.anytype.core_ui.widgets.ObjectMenuItemWidget
android:id="@+id/optionHistory"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@drawable/default_ripple"
app:icon="@drawable/ic_object_menu_history"
app:subtitle="@string/history_description"
app:title="@string/history" />
<com.anytypeio.anytype.core_ui.widgets.ObjectMenuItemWidget
android:id="@+id/optionHistory"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@drawable/default_ripple"
app:icon="@drawable/ic_obj_settings_history_24"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/relationsDivider"
app:title="@string/history" />
<View
android:id="@+id/historyDivider"
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_marginStart="72dp"
android:layout_marginTop="8dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:background="@color/shape_primary" />
<com.anytypeio.anytype.core_ui.widgets.ObjectMenuItemWidget
@ -129,15 +133,14 @@
android:layout_marginTop="8dp"
android:background="@drawable/default_ripple"
app:icon="@drawable/ic_object_menu_diagnostics"
app:subtitle="@string/object_debug"
app:title="@string/object_diagnostics" />
<View
android:id="@+id/objectDiagnosticsDivider"
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_marginStart="72dp"
android:layout_marginTop="8dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:background="@color/shape_primary" />
<com.anytypeio.anytype.core_ui.widgets.ObjectMenuItemWidget
@ -148,16 +151,15 @@
android:background="@drawable/default_ripple"
android:visibility="gone"
app:icon="@drawable/ic_object_menu_debug_goroutines"
app:subtitle="Command Debug.StackGoroutines"
app:title="Debug Goroutines"
tools:visibility="visible" />
tools:visibility="visible" />
<View
android:id="@+id/debugGoroutinesDivider"
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_marginStart="72dp"
android:layout_marginTop="8dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:background="@color/shape_primary"
android:visibility="gone" />

View file

@ -34,7 +34,7 @@
android:label="Object-Menu-Screen" />
<dialog
android:id="@+id/objectRelationListScreen"
android:name="com.anytypeio.anytype.ui.relations.ObjectRelationListFragment"
android:name="com.anytypeio.anytype.ui.primitives.ObjectFieldsFragment"
android:label="Object-Relation-List-Screen" />
<dialog
android:id="@+id/objectIconPickerScreen"
@ -81,6 +81,11 @@
android:id="@+id/actionExitToSpaceWidgets"
app:popUpTo="@+id/homeScreen"
app:popUpToInclusive="false" />
<dialog android:id="@+id/objectTypeFieldsScreen"
android:name="com.anytypeio.anytype.ui.primitives.ObjectTypeFieldsFragment"
android:label="ObjectTypeFieldsScreen" />
</navigation>
<include app:graph="@navigation/nav_editor_modal" />
@ -109,9 +114,10 @@
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
</fragment>
<dialog
android:id="@+id/objectRelationListScreen"
android:name="com.anytypeio.anytype.ui.relations.ObjectRelationListFragment"
android:name="com.anytypeio.anytype.ui.primitives.ObjectFieldsFragment"
android:label="Object-Relation-List-Screen" />
<dialog
android:id="@+id/objectSetMainMenuScreen"
@ -227,13 +233,22 @@
<fragment
android:id="@+id/dateObjectScreen"
android:name="com.anytypeio.anytype.ui.date.DateObjectFragment"
android:label="Date Object"> {
android:label="Date Object">
<action
android:id="@+id/actionExitToSpaceWidgets"
app:popUpTo="@+id/homeScreen"
app:popUpToInclusive="false" />
</fragment>
<fragment
android:id="@+id/objectTypeScreen"
android:name="com.anytypeio.anytype.ui.primitives.ObjectTypeFragment"
android:label=" Object">
<action
android:id="@+id/openEmojiPicker"
app:destination="@id/typeSetIconPickerScreen" />
</fragment>
<dialog
android:id="@+id/selectWidgetSourceScreen"
android:name="com.anytypeio.anytype.ui.widgets.SelectWidgetSourceFragment" />

View file

@ -32,7 +32,7 @@
</fragment>
<dialog
android:id="@+id/objectRelationListScreen"
android:name="com.anytypeio.anytype.ui.relations.ObjectRelationListFragment"
android:name="com.anytypeio.anytype.ui.primitives.ObjectFieldsFragment"
android:label="Object-Relation-List-Screen" />
<dialog
android:id="@+id/objectIconPickerScreen"

View file

@ -32,7 +32,7 @@
</fragment>
<dialog
android:id="@+id/objectRelationListScreen"
android:name="com.anytypeio.anytype.ui.relations.ObjectRelationListFragment"
android:name="com.anytypeio.anytype.ui.primitives.ObjectFieldsFragment"
android:label="Object-Relation-List-Screen" />
<dialog
android:id="@+id/objectIconPickerScreen"

View file

@ -653,4 +653,18 @@ sealed class Command {
val dataViewId: Id,
val viewerId: Id
)
data class ObjectTypeConflictingFields(
val spaceId: String,
val objectTypeId: String
) : Command()
data class ObjectTypeSetRecommendedHeaderFields(
val objectTypeId: String,
val fields: List<Id>
) : Command()
data class ObjectTypeSetRecommendedFields(
val objectTypeId: String,
val fields: List<Id>
) : Command()
}

View file

@ -91,10 +91,10 @@ sealed class ObjectWrapper {
val restrictions: List<ObjectRestriction>
get() = when (val value = map[Relations.RESTRICTIONS]) {
is Double -> buildList {
ObjectRestriction.values().firstOrNull { it.code == value.toInt() }
ObjectRestriction.entries.firstOrNull { it.code == value.toInt() }
}
is List<*> -> value.typeOf<Double>().mapNotNull { code ->
ObjectRestriction.values().firstOrNull { it.code == code.toInt() }
ObjectRestriction.entries.firstOrNull { it.code == code.toInt() }
}
else -> emptyList()
}
@ -168,14 +168,37 @@ sealed class ObjectWrapper {
val iconEmoji: String? by default
val isDeleted: Boolean? by default
val recommendedRelations: List<Id> get() = getValues(Relations.RECOMMENDED_RELATIONS)
val recommendedFeaturedRelations: List<Id> get() = getValues(Relations.RECOMMENDED_FEATURED_RELATIONS)
val recommendedHiddenRelations: List<Id> get() = getValues(Relations.RECOMMENDED_HIDDEN_RELATIONS)
val recommendedFileRelations: List<Id> get() = getValues(Relations.RECOMMENDED_FILE_RELATIONS)
val recommendedLayout: ObjectType.Layout?
get() = when (val value = map[Relations.RECOMMENDED_LAYOUT]) {
is Double -> ObjectType.Layout.values().singleOrNull { layout ->
is Double -> ObjectType.Layout.entries.singleOrNull { layout ->
layout.code == value.toInt()
}
else -> ObjectType.Layout.BASIC
}
val layout: ObjectType.Layout?
get() = when (val value = map[Relations.LAYOUT]) {
is Double -> ObjectType.Layout.entries.singleOrNull { layout ->
layout.code == value.toInt()
}
else -> null
}
val defaultTemplateId: Id? by default
val restrictions: List<ObjectRestriction>
get() = when (val value = map[Relations.RESTRICTIONS]) {
is Double -> buildList {
ObjectRestriction.entries.firstOrNull { it.code == value.toInt() }
}
is List<*> -> value.typeOf<Double>().mapNotNull { code ->
ObjectRestriction.entries.firstOrNull { it.code == code.toInt() }
}
else -> emptyList()
}
}
data class Relation(override val map: Struct) : ObjectWrapper() {
@ -188,7 +211,7 @@ sealed class ObjectWrapper {
get() {
val value = map[Relations.RELATION_FORMAT]
return if (value is Double) {
RelationFormat.values().firstOrNull { f ->
RelationFormat.entries.firstOrNull { f ->
f.code == value.toInt()
} ?: RelationFormat.UNDEFINED
} else {
@ -214,10 +237,10 @@ sealed class ObjectWrapper {
val restrictions: List<ObjectRestriction>
get() = when (val value = map[Relations.RESTRICTIONS]) {
is Double -> buildList {
ObjectRestriction.values().firstOrNull { it.code == value.toInt() }
ObjectRestriction.entries.firstOrNull { it.code == value.toInt() }
}
is List<*> -> value.typeOf<Double>().mapNotNull { code ->
ObjectRestriction.values().firstOrNull { it.code == code.toInt() }
ObjectRestriction.entries.firstOrNull { it.code == code.toInt() }
}
else -> emptyList()
}
@ -226,7 +249,11 @@ sealed class ObjectWrapper {
val type: List<Id> get() = getValues(Relations.TYPE)
val isValid get() = map.containsKey(Relations.RELATION_KEY) && map.containsKey(Relations.ID)
val isValid get() =
map.containsKey(Relations.RELATION_KEY) && map.containsKey(Relations.ID)
val isValidToUse get() = isValid && isDeleted != true && isArchived != true && isHidden != true
}
data class Option(override val map: Struct) : ObjectWrapper() {

View file

@ -12,7 +12,8 @@ object Relations {
const val COVER_TYPE = "coverType"
const val COVER_ID = "coverId"
const val DESCRIPTION = "description"
const val LAYOUT = "layout"
const val LAYOUT = "resolvedLayout"
const val LEGACY_LAYOUT = "layout"
const val NAME = "name"
const val ICON_EMOJI = "iconEmoji"
const val ICON_OPTION = "iconOption"
@ -69,6 +70,9 @@ object Relations {
const val RECOMMENDED_LAYOUT = "recommendedLayout"
const val RECOMMENDED_RELATIONS = "recommendedRelations"
const val RECOMMENDED_FEATURED_RELATIONS = "recommendedFeaturedRelations"
const val RECOMMENDED_HIDDEN_RELATIONS = "recommendedHiddenRelations"
const val RECOMMENDED_FILE_RELATIONS = "recommendedFileRelations"
const val DEFAULT_TEMPLATE_ID = "defaultTemplateId"
const val UNIQUE_KEY = "uniqueKey"
@ -103,59 +107,98 @@ object Relations {
"name",
"description",
"snippet",
"iconEmoji",
"iconImage",
"type",
"layout",
"layoutAlign",
"coverId",
"coverScale",
"coverType",
"coverX",
"coverY",
"createdDate",
"creator",
"lastModifiedDate",
"lastModifiedBy",
"lastOpenedDate",
"featuredRelations",
"isFavorite",
"workspaceId",
"done",
"spaceId",
"links",
"internalFlags",
"restrictions",
"addedDate",
"source",
"sourceObject",
"setOf",
"relationFormat",
"relationKey",
"relationReadonlyValue",
"relationDefaultValue",
"relationMaxCount",
"relationOptionColor",
"relationFormatObjectTypes",
"isReadonly",
"isDeleted",
"isHidden",
"spaceShareableStatus",
"isAclShared",
"isHiddenDiscovery",
"done",
"isArchived",
"templateIsBundled",
"smartblockTypes",
"targetObjectType",
"recommendedRelations",
"recommendedLayout",
"templateIsBundled",
"layout",
"layoutAlign",
"creator",
"createdDate",
"lastOpenedDate",
"lastModifiedBy",
"lastModifiedDate",
"addedDate",
"iconEmoji",
"iconImage",
"coverId",
"coverType",
"coverScale",
"coverX",
"coverY",
"fileExt",
"fileMimeType",
"sizeInBytes",
"isHidden",
"isArchived",
"isFavorite",
"isReadonly",
"relationKey",
"relationFormat",
"relationMaxCount",
"relationReadonlyValue",
"relationDefaultValue",
"relationFormatObjectTypes",
"relationOptionColor",
"sharedSpacesLimit"
"oldAnytypeID",
"spaceDashboardId",
"recommendedRelations",
"iconOption",
"widthInPixels",
"heightInPixels",
"sourceFilePath",
"fileSyncStatus",
"defaultTemplateId",
"uniqueKey",
"backlinks",
"profileOwnerIdentity",
"fileBackupStatus",
"fileId",
"fileIndexingStatus",
"origin",
"revision",
"imageKind",
"importType",
"spaceAccessType",
"spaceInviteFileCid",
"spaceInviteFileKey",
"readersLimit",
"writersLimit",
"sharedSpacesLimit",
"participantPermissions",
"participantStatus",
"latestAclHeadId",
"identity",
"globalName",
"syncDate",
"syncStatus",
"syncError",
"lastUsedDate",
"mentions",
"chatId",
"hasChat",
"timestamp",
"recommendedFeaturedRelations",
"recommendedHiddenRelations",
"recommendedFileRelations",
"layoutWidth",
"defaultViewType",
"defaultTypeId",
"resolvedLayout"
)
}

View file

@ -38,10 +38,9 @@ sealed class Response {
sealed class Set : Response() {
data class Create(
@Deprecated("legacy param")
val blockId: Id?,
val targetId: Id,
val payload: Payload
val objectId: Id,
val payload: Payload,
val details: Struct
)
}

View file

@ -14,7 +14,8 @@ object SupportedLayouts {
ObjectType.Layout.NOTE,
ObjectType.Layout.BOOKMARK,
ObjectType.Layout.AUDIO,
ObjectType.Layout.PDF
ObjectType.Layout.PDF,
ObjectType.Layout.OBJECT_TYPE,
)
val editorLayouts = listOf(
ObjectType.Layout.BASIC,

View file

@ -4,10 +4,13 @@ import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.ObjectType
import com.anytypeio.anytype.core_models.ObjectTypeIds
import com.anytypeio.anytype.core_models.ObjectView
import com.anytypeio.anytype.core_models.ObjectWrapper
import com.anytypeio.anytype.core_models.Relations
import com.anytypeio.anytype.core_models.SupportedLayouts
import com.anytypeio.anytype.core_models.getSingleValue
import com.anytypeio.anytype.core_models.restrictions.ObjectRestriction
import kotlin.collections.contains
import kotlin.collections.get
/**
* Represents a set of user permissions for a given object.
@ -52,8 +55,11 @@ data class ObjectPermissions(
val canEditRelationsList: Boolean = false,
val canEditBlocks: Boolean = false,
val canEditDetails: Boolean = false,
val editBlocks: EditBlocksPermission,
val canCreateObjectThisType: Boolean = false
val editBlocks: EditBlocksPermission = EditBlocksPermission.ReadOnly,
val canCreateObjectThisType: Boolean = false,
val canChangeRecommendedLayoutForThisType: Boolean = false,
val canCreateTemplatesForThisType: Boolean = false,
val participantCanEdit: Boolean = false,
)
/**
@ -104,7 +110,7 @@ fun ObjectView.toObjectPermissions(
return ObjectPermissions(
canArchive = participantCanEdit && !objectRestrictions.contains(ObjectRestriction.DELETE) && !isArchived,
canDelete = participantCanEdit && !objectRestrictions.contains(ObjectRestriction.DELETE),
canDelete = participantCanEdit && !objectRestrictions.contains(ObjectRestriction.DELETE),
canChangeType = canEdit &&
!isTemplateObject &&
!objectRestrictions.contains(ObjectRestriction.TYPE_CHANGE),
@ -134,7 +140,34 @@ fun ObjectView.toObjectPermissions(
canEditBlocks = (editBlocksPermission == EditBlocksPermission.Edit),
canEditDetails = canEditDetails && canEdit,
editBlocks = editBlocksPermission,
canCreateObjectThisType = !objectRestrictions.contains(ObjectRestriction.CREATE_OBJECT_OF_THIS_TYPE) && canApplyUneditableActions
canCreateObjectThisType = !objectRestrictions.contains(ObjectRestriction.CREATE_OBJECT_OF_THIS_TYPE) && canApplyUneditableActions,
participantCanEdit = participantCanEdit
)
}
fun ObjectWrapper.Type.toObjectPermissionsForTypes(
participantCanEdit: Boolean
): ObjectPermissions {
val isArchived = getSingleValue<Boolean>(Relations.IS_ARCHIVED) == true
val canEdit = !isArchived && participantCanEdit
val canEditDetails = !restrictions.contains(ObjectRestriction.DETAILS)
val canCreateTemplatesForObjectsThisType = layoutsWithTemplates.contains(recommendedLayout)
&& uniqueKey != ObjectTypeIds.TEMPLATE
val canChangeRecommendedLayoutForObjectsThisType = participantCanEdit
&& possibleToChangeLayoutLayouts.contains(recommendedLayout)
&& uniqueKey != ObjectTypeIds.TEMPLATE
return ObjectPermissions(
canDelete = participantCanEdit && !restrictions.contains(ObjectRestriction.DELETE),
canEditDetails = canEditDetails && canEdit,
canCreateTemplatesForThisType = canCreateTemplatesForObjectsThisType,
canCreateObjectThisType = !restrictions.contains(ObjectRestriction.CREATE_OBJECT_OF_THIS_TYPE) && participantCanEdit,
canChangeRecommendedLayoutForThisType = canChangeRecommendedLayoutForObjectsThisType,
participantCanEdit = canEdit
)
}
@ -185,3 +218,10 @@ private val possibleToChangeLayoutLayouts = listOf(
ObjectType.Layout.TODO,
ObjectType.Layout.NOTE
)
private val layoutsWithTemplates = listOf(
ObjectType.Layout.BASIC,
ObjectType.Layout.NOTE,
ObjectType.Layout.PROFILE,
ObjectType.Layout.TODO,
)

View file

@ -1,5 +1,6 @@
package com.anytypeio.anytype.core_models.primitives
import com.anytypeio.anytype.core_models.ObjectWrapper
import com.anytypeio.anytype.core_models.RelativeDate
sealed class Value<T> {
@ -20,4 +21,13 @@ sealed class Field<T>(open val value: Value<T>) {
data class FieldDateValue(
val timestamp: TimestampInSeconds,
val relativeDate: RelativeDate
)
data class ParsedFields(
val header: List<ObjectWrapper.Relation> = emptyList(),
val sidebar: List<ObjectWrapper.Relation> = emptyList(),
val hidden: List<ObjectWrapper.Relation> = emptyList(),
val localWithoutSystem: List<ObjectWrapper.Relation> = emptyList(),
val localSystem: List<ObjectWrapper.Relation> = emptyList(),
val file: List<ObjectWrapper.Relation> = emptyList(),
)

View file

@ -57,7 +57,7 @@ dependencies {
debugImplementation libs.composeTooling
implementation libs.coilCompose
implementation libs.composeConstraintLayout
implementation libs.composeReorderable
implementation libs.composeReorderableLegacy
testImplementation libs.fragmentTesting
testImplementation project(':test:android-utils')

View file

@ -2,6 +2,7 @@ package com.anytypeio.anytype.core_ui.common
import android.content.res.Configuration.UI_MODE_NIGHT_NO
import android.content.res.Configuration.UI_MODE_NIGHT_YES
import androidx.compose.ui.tooling.preview.Devices
import androidx.compose.ui.tooling.preview.Preview
@Preview(
@ -20,4 +21,15 @@ import androidx.compose.ui.tooling.preview.Preview
apiLevel = 34,
showSystemUi = true
)
annotation class DefaultPreviews
annotation class DefaultPreviews
@Preview(
backgroundColor = 0xFFFFFFFF,
showBackground = true,
uiMode = UI_MODE_NIGHT_NO,
name = "Light Mode",
apiLevel = 28,
showSystemUi = true,
device = Devices.NEXUS_5
)
annotation class OldDevicesPreview

View file

@ -0,0 +1,52 @@
package com.anytypeio.anytype.core_ui.common
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalView
import androidx.core.view.HapticFeedbackConstantsCompat
import androidx.core.view.ViewCompat
@Composable
fun rememberReorderHapticFeedback(): ReorderHapticFeedback {
val view = LocalView.current
val reorderHapticFeedback = remember {
object : ReorderHapticFeedback() {
override fun performHapticFeedback(type: ReorderHapticFeedbackType) {
when (type) {
ReorderHapticFeedbackType.START ->
ViewCompat.performHapticFeedback(
view,
HapticFeedbackConstantsCompat.GESTURE_START
)
ReorderHapticFeedbackType.MOVE ->
ViewCompat.performHapticFeedback(
view,
HapticFeedbackConstantsCompat.SEGMENT_FREQUENT_TICK
)
ReorderHapticFeedbackType.END ->
ViewCompat.performHapticFeedback(
view,
HapticFeedbackConstantsCompat.GESTURE_END
)
}
}
}
}
return reorderHapticFeedback
}
enum class ReorderHapticFeedbackType {
START,
MOVE,
END,
}
open class ReorderHapticFeedback {
open fun performHapticFeedback(type: ReorderHapticFeedbackType) {
// no-op
}
}

View file

@ -0,0 +1,60 @@
package com.anytypeio.anytype.core_ui.features.fields
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp
import com.anytypeio.anytype.core_ui.views.BodyCalloutRegular
import com.anytypeio.anytype.core_ui.R
@Composable
fun FieldItemDropDownMenu(
showMenu: Boolean,
onDismissRequest: () -> Unit,
onAddToCurrentTypeClick: () -> Unit,
onRemoveFromObjectClick: () -> Unit,
) {
DropdownMenu(
modifier = Modifier
.width(244.dp),
expanded = showMenu,
offset = DpOffset(x = 0.dp, y = 0.dp),
onDismissRequest = {
onDismissRequest()
},
shape = RoundedCornerShape(10.dp),
containerColor = colorResource(id = R.color.background_secondary),
) {
DropdownMenuItem(
text = {
Text(
text = stringResource(R.string.field_menu_add_to_type),
style = BodyCalloutRegular,
color = colorResource(id = R.color.text_primary)
)
},
onClick = {
onAddToCurrentTypeClick()
},
)
DropdownMenuItem(
text = {
Text(
text = stringResource(R.string.field_menu_remove_from_object),
style = BodyCalloutRegular,
color = colorResource(id = R.color.palette_system_red)
)
},
onClick = {
onRemoveFromObjectClick()
},
)
}
}

View file

@ -1,6 +1,8 @@
package com.anytypeio.anytype.core_ui.features.fields
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.border
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@ -14,6 +16,8 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.res.colorResource
@ -26,8 +30,17 @@ import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.common.DefaultPreviews
import com.anytypeio.anytype.core_ui.views.Relations1
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun FieldEmpty(modifier: Modifier = Modifier, title: String, fieldFormat: RelationFormat) {
fun FieldEmpty(
modifier: Modifier = Modifier,
title: String,
fieldFormat: RelationFormat,
isLocal: Boolean,
onFieldClick: () -> Unit,
onAddToCurrentTypeClick: () -> Unit,
onRemoveFromObjectClick: () -> Unit,
) {
val defaultModifier = modifier
.fillMaxWidth()
.border(
@ -43,7 +56,11 @@ fun FieldEmpty(modifier: Modifier = Modifier, title: String, fieldFormat: Relati
FieldVerticalEmpty(
modifier = defaultModifier,
title = title,
emptyState = emptyState
emptyState = emptyState,
isLocal = isLocal,
onFieldClick = onFieldClick,
onAddToCurrentTypeClick = onAddToCurrentTypeClick,
onRemoveFromObjectClick = onRemoveFromObjectClick
)
}
@ -52,20 +69,37 @@ fun FieldEmpty(modifier: Modifier = Modifier, title: String, fieldFormat: Relati
FieldHorizontalEmpty(
modifier = defaultModifier,
title = title,
emptyState = emptyState
emptyState = emptyState,
isLocal = isLocal,
onFieldClick = onFieldClick,
onAddToCurrentTypeClick = onAddToCurrentTypeClick,
onRemoveFromObjectClick = onRemoveFromObjectClick
)
}
}
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun FieldVerticalEmpty(
modifier: Modifier = Modifier,
title: String,
emptyState: String,
isLocal: Boolean,
onFieldClick: () -> Unit,
onAddToCurrentTypeClick: () -> Unit,
onRemoveFromObjectClick: () -> Unit,
) {
val isMenuExpanded = remember { mutableStateOf(false) }
Column(
modifier = modifier.padding(horizontal = 16.dp, vertical = 16.dp)
modifier = modifier
.combinedClickable(
onClick = { onFieldClick()},
onLongClick = {
if (isLocal) isMenuExpanded.value = true
}
)
.padding(horizontal = 16.dp, vertical = 16.dp)
) {
Text(
modifier = Modifier.fillMaxWidth(),
@ -84,21 +118,47 @@ private fun FieldVerticalEmpty(
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
FieldItemDropDownMenu(
showMenu = isMenuExpanded.value,
onDismissRequest = {
isMenuExpanded.value = false
},
onAddToCurrentTypeClick = {
isMenuExpanded.value = false
onAddToCurrentTypeClick()
},
onRemoveFromObjectClick = {
isMenuExpanded.value = false
onRemoveFromObjectClick()
}
)
}
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun FieldHorizontalEmpty(
modifier: Modifier = Modifier,
title: String,
emptyState: String,
isLocal: Boolean,
onFieldClick: () -> Unit,
onAddToCurrentTypeClick: () -> Unit,
onRemoveFromObjectClick: () -> Unit,
) {
val isMenuExpanded = remember { mutableStateOf(false) }
val configuration = LocalConfiguration.current
val screenWidth = configuration.screenWidthDp.dp
val halfScreenWidth = screenWidth / 2 - 32.dp
Row(
modifier = modifier
.combinedClickable(
onClick = onFieldClick,
onLongClick = {
if (isLocal) isMenuExpanded.value = true
}
)
.padding(horizontal = 16.dp, vertical = 16.dp)
) {
Text(
@ -116,6 +176,20 @@ private fun FieldHorizontalEmpty(
style = Relations1,
color = colorResource(id = R.color.text_tertiary)
)
FieldItemDropDownMenu(
showMenu = isMenuExpanded.value,
onDismissRequest = {
isMenuExpanded.value = false
},
onAddToCurrentTypeClick = {
isMenuExpanded.value = false
onAddToCurrentTypeClick()
},
onRemoveFromObjectClick = {
isMenuExpanded.value = false
onRemoveFromObjectClick()
}
)
}
}
@ -149,13 +223,21 @@ fun PreviewField() {
item {
FieldEmpty(
title = "Description",
fieldFormat = Relation.Format.LONG_TEXT
fieldFormat = Relation.Format.LONG_TEXT,
isLocal = true,
onFieldClick = {},
onAddToCurrentTypeClick = {},
onRemoveFromObjectClick = {}
)
}
item {
FieldEmpty(
title = "Some Number, very long long long long long fields name",
fieldFormat = Relation.Format.NUMBER
fieldFormat = Relation.Format.NUMBER,
isLocal = true,
onFieldClick = {},
onAddToCurrentTypeClick = {},
onRemoveFromObjectClick = {}
)
}
item {

View file

@ -1,7 +1,9 @@
package com.anytypeio.anytype.core_ui.features.fields
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.border
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
@ -14,6 +16,8 @@ import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.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.platform.LocalConfiguration
@ -25,13 +29,25 @@ import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.common.DefaultPreviews
import com.anytypeio.anytype.core_ui.views.Relations1
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun FieldTypeCheckbox(
modifier: Modifier = Modifier,
title: String,
isCheck: Boolean,
isLocal: Boolean,
onFieldClick: () -> Unit,
onAddToCurrentTypeClick: () -> Unit,
onRemoveFromObjectClick: () -> Unit,
) {
val isMenuExpanded = remember { mutableStateOf(false) }
val defaultModifier = modifier
.combinedClickable(
onClick = { onFieldClick()},
onLongClick = {
if (isLocal) isMenuExpanded.value = true
}
)
.fillMaxWidth()
.border(
width = 1.dp,
@ -81,6 +97,20 @@ fun FieldTypeCheckbox(
)
}
}
FieldItemDropDownMenu(
showMenu = isMenuExpanded.value,
onDismissRequest = {
isMenuExpanded.value = false
},
onAddToCurrentTypeClick = {
isMenuExpanded.value = false
onAddToCurrentTypeClick()
},
onRemoveFromObjectClick = {
isMenuExpanded.value = false
onRemoveFromObjectClick()
}
)
}
}
@ -90,6 +120,10 @@ fun FieldTypeCheckbox(
fun FieldTypeCheckboxPreview() {
FieldTypeCheckbox(
title = "Creation date",
isCheck = false
isCheck = false,
isLocal = true,
onRemoveFromObjectClick = {},
onAddToCurrentTypeClick = {},
onFieldClick = {}
)
}

View file

@ -1,6 +1,8 @@
package com.anytypeio.anytype.core_ui.features.fields
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.border
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
@ -12,6 +14,8 @@ import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.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.platform.LocalConfiguration
@ -26,13 +30,25 @@ import com.anytypeio.anytype.core_ui.extensions.getPrettyName
import com.anytypeio.anytype.core_ui.views.BodyCallout
import com.anytypeio.anytype.core_ui.views.Relations1
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun FieldTypeDate(
modifier: Modifier = Modifier,
title: String,
relativeDate: RelativeDate
relativeDate: RelativeDate,
isLocal: Boolean,
onFieldClick: () -> Unit,
onAddToCurrentTypeClick: () -> Unit,
onRemoveFromObjectClick: () -> Unit,
) {
val isMenuExpanded = remember { mutableStateOf(false) }
val defaultModifier = modifier
.combinedClickable(
onClick = onFieldClick,
onLongClick = {
if (isLocal) isMenuExpanded.value = true
}
)
.fillMaxWidth()
.border(
width = 1.dp,
@ -77,6 +93,20 @@ fun FieldTypeDate(
overflow = TextOverflow.Ellipsis
)
}
FieldItemDropDownMenu(
showMenu = isMenuExpanded.value,
onDismissRequest = {
isMenuExpanded.value = false
},
onAddToCurrentTypeClick = {
isMenuExpanded.value = false
onAddToCurrentTypeClick()
},
onRemoveFromObjectClick = {
isMenuExpanded.value = false
onRemoveFromObjectClick()
}
)
}
}
@ -90,5 +120,9 @@ fun FieldTypeDatePreview() {
initialTimeInMillis = System.currentTimeMillis(),
dayOfWeek = DayOfWeekCustom.THURSDAY
),
isLocal = true,
onRemoveFromObjectClick = {},
onAddToCurrentTypeClick = {},
onFieldClick = {}
)
}

View file

@ -1,7 +1,9 @@
package com.anytypeio.anytype.core_ui.features.fields
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@ -16,6 +18,8 @@ import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.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.layout.SubcomposeLayout
@ -34,16 +38,28 @@ import com.anytypeio.anytype.core_ui.widgets.ListWidgetObjectIcon
import com.anytypeio.anytype.presentation.relations.ObjectRelationView
import com.anytypeio.anytype.presentation.sets.model.FileView
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun FieldTypeFile(
modifier: Modifier = Modifier,
fieldObject: ObjectRelationView.File
fieldObject: ObjectRelationView.File,
isLocal: Boolean,
onFieldClick: () -> Unit,
onAddToCurrentTypeClick: () -> Unit,
onRemoveFromObjectClick: () -> Unit,
) {
val isMenuExpanded = remember { mutableStateOf(false) }
val configuration = LocalConfiguration.current
val screenWidth = configuration.screenWidthDp.dp
val halfScreenWidth = screenWidth / 2 - 32.dp
val defaultModifier = modifier
.combinedClickable(
onClick = onFieldClick,
onLongClick = {
if (isLocal) isMenuExpanded.value = true
}
)
.fillMaxWidth()
.border(
width = 1.dp,
@ -52,6 +68,7 @@ fun FieldTypeFile(
)
.padding(vertical = 16.dp)
.padding(horizontal = 16.dp)
if (fieldObject.files.size == 1) {
// If there is only one item, display the title and the item in one row.
val singleItem = fieldObject.files.first()
@ -82,6 +99,20 @@ fun FieldTypeFile(
objView = singleItem
)
}
FieldItemDropDownMenu(
showMenu = isMenuExpanded.value,
onDismissRequest = {
isMenuExpanded.value = false
},
onAddToCurrentTypeClick = {
isMenuExpanded.value = false
onAddToCurrentTypeClick()
},
onRemoveFromObjectClick = {
isMenuExpanded.value = false
onRemoveFromObjectClick()
}
)
}
} else {
Column(
@ -156,6 +187,20 @@ fun FieldTypeFile(
}
}
}
FieldItemDropDownMenu(
showMenu = isMenuExpanded.value,
onDismissRequest = {
isMenuExpanded.value = false
},
onAddToCurrentTypeClick = {
isMenuExpanded.value = false
onAddToCurrentTypeClick()
},
onRemoveFromObjectClick = {
isMenuExpanded.value = false
onRemoveFromObjectClick()
}
)
}
}
}

View file

@ -1,7 +1,9 @@
package com.anytypeio.anytype.core_ui.features.fields
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
@ -15,6 +17,8 @@ import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.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.graphics.Color
@ -35,13 +39,25 @@ import com.anytypeio.anytype.core_ui.views.Relations1
import com.anytypeio.anytype.core_ui.views.Relations2
import com.anytypeio.anytype.presentation.sets.model.TagView
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun FieldTypeMultiSelect(
modifier: Modifier = Modifier,
title: String,
tags: List<TagView>
tags: List<TagView>,
isLocal: Boolean,
onFieldClick: () -> Unit,
onAddToCurrentTypeClick: () -> Unit,
onRemoveFromObjectClick: () -> Unit,
) {
val isMenuExpanded = remember { mutableStateOf(false) }
val defaultModifier = modifier
.combinedClickable(
onClick = onFieldClick,
onLongClick = {
if (isLocal) isMenuExpanded.value = true
}
)
.fillMaxWidth()
.border(
width = 1.dp,
@ -79,6 +95,20 @@ fun FieldTypeMultiSelect(
modifier = Modifier.fillMaxWidth(),
textStyle = Relations1
)
FieldItemDropDownMenu(
showMenu = isMenuExpanded.value,
onDismissRequest = {
isMenuExpanded.value = false
},
onAddToCurrentTypeClick = {
isMenuExpanded.value = false
onAddToCurrentTypeClick()
},
onRemoveFromObjectClick = {
isMenuExpanded.value = false
onRemoveFromObjectClick()
}
)
}
}

View file

@ -1,11 +1,15 @@
package com.anytypeio.anytype.core_ui.features.fields
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.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.layout.SubcomposeLayout
@ -35,16 +39,28 @@ import com.anytypeio.anytype.presentation.sets.model.ObjectView
* (where n = total number of items minus two) immediately following its text.
* If the text of the second item is long, it is truncated so that the suffix is always visible.
*/
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun FieldTypeObject(
modifier: Modifier = Modifier,
fieldObject: ObjectRelationView.Object
fieldObject: ObjectRelationView.Object,
isLocal: Boolean,
onFieldClick: () -> Unit,
onAddToCurrentTypeClick: () -> Unit,
onRemoveFromObjectClick: () -> Unit,
) {
val isMenuExpanded = remember { mutableStateOf(false) }
val configuration = LocalConfiguration.current
val screenWidth = configuration.screenWidthDp.dp
val halfScreenWidth = screenWidth / 2 - 32.dp
val defaultModifier = modifier
.combinedClickable(
onClick = onFieldClick,
onLongClick = {
if (isLocal) isMenuExpanded.value = true
}
)
.fillMaxWidth()
.border(
width = 1.dp,
@ -83,6 +99,20 @@ fun FieldTypeObject(
objView = singleItem
)
}
FieldItemDropDownMenu(
showMenu = isMenuExpanded.value,
onDismissRequest = {
isMenuExpanded.value = false
},
onAddToCurrentTypeClick = {
isMenuExpanded.value = false
onAddToCurrentTypeClick()
},
onRemoveFromObjectClick = {
isMenuExpanded.value = false
onRemoveFromObjectClick()
}
)
}
} else {
Column(
@ -156,6 +186,20 @@ fun FieldTypeObject(
}
}
}
FieldItemDropDownMenu(
showMenu = isMenuExpanded.value,
onDismissRequest = {
isMenuExpanded.value = false
},
onAddToCurrentTypeClick = {
isMenuExpanded.value = false
onAddToCurrentTypeClick()
},
onRemoveFromObjectClick = {
isMenuExpanded.value = false
onRemoveFromObjectClick()
}
)
}
}
}

View file

@ -1,6 +1,8 @@
package com.anytypeio.anytype.core_ui.features.fields
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.border
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
@ -12,6 +14,8 @@ import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.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.platform.LocalConfiguration
@ -25,13 +29,26 @@ import com.anytypeio.anytype.core_ui.extensions.dark
import com.anytypeio.anytype.core_ui.views.Relations1
import com.anytypeio.anytype.presentation.sets.model.StatusView
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun FieldTypeSelect(
modifier: Modifier = Modifier,
title: String,
status: StatusView
status: StatusView,
isLocal: Boolean,
onFieldClick: () -> Unit,
onAddToCurrentTypeClick: () -> Unit,
onRemoveFromObjectClick: () -> Unit,
) {
val isMenuExpanded = remember { mutableStateOf(false) }
val defaultModifier = modifier
.combinedClickable(
onClick = onFieldClick,
onLongClick = {
if (isLocal) isMenuExpanded.value = true
}
)
.fillMaxWidth()
.border(
width = 1.dp,
@ -71,6 +88,20 @@ fun FieldTypeSelect(
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
FieldItemDropDownMenu(
showMenu = isMenuExpanded.value,
onDismissRequest = {
isMenuExpanded.value = false
},
onAddToCurrentTypeClick = {
isMenuExpanded.value = false
onAddToCurrentTypeClick()
},
onRemoveFromObjectClick = {
isMenuExpanded.value = false
onRemoveFromObjectClick()
}
)
}
}
@ -83,6 +114,10 @@ fun FieldTypeSelectPreview() {
id = "1",
status = "In Progress",
color = ThemeColor.TEAL.code
)
),
isLocal = true,
onRemoveFromObjectClick = {},
onAddToCurrentTypeClick = {},
onFieldClick = {}
)
}

View file

@ -1,6 +1,8 @@
package com.anytypeio.anytype.core_ui.features.fields
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.border
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
@ -9,6 +11,8 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.text.style.TextOverflow
@ -17,13 +21,26 @@ import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.common.DefaultPreviews
import com.anytypeio.anytype.core_ui.views.Relations1
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun FieldTypeText(
modifier: Modifier = Modifier,
title: String,
text: String
text: String,
isLocal: Boolean,
onFieldClick: () -> Unit,
onAddToCurrentTypeClick: () -> Unit,
onRemoveFromObjectClick: () -> Unit,
) {
val isMenuExpanded = remember { mutableStateOf(false) }
val defaultModifier = modifier
.combinedClickable(
onClick = onFieldClick,
onLongClick = {
if (isLocal) isMenuExpanded.value = true
}
)
.fillMaxWidth()
.border(
width = 1.dp,
@ -55,6 +72,20 @@ fun FieldTypeText(
maxLines = 3,
overflow = TextOverflow.Ellipsis
)
FieldItemDropDownMenu(
showMenu = isMenuExpanded.value,
onDismissRequest = {
isMenuExpanded.value = false
},
onAddToCurrentTypeClick = {
isMenuExpanded.value = false
onAddToCurrentTypeClick()
},
onRemoveFromObjectClick = {
isMenuExpanded.value = false
onRemoveFromObjectClick()
}
)
}
}
@ -65,6 +96,10 @@ fun FieldTypeTextPreview() {
title = "Description",
text = "Upon creating your profile, youll receive your very own 12 word mnemonic Recovery phrase to protect your account. This phrase is generated on-device and represents your master key generated upon signup, similar to a Bitcoin wallet. It also prevents anyone - including Anytype - from accessing your account and decrypting your data.\n" +
"\n" +
"All data you create will be stored locally (on-device) first. We use zero-knowledge encryption, meaning that your data is encrypted before it leaves your device to sync with other devices or backup nodes."
"All data you create will be stored locally (on-device) first. We use zero-knowledge encryption, meaning that your data is encrypted before it leaves your device to sync with other devices or backup nodes.",
isLocal = true,
onRemoveFromObjectClick = {},
onAddToCurrentTypeClick = {},
onFieldClick = {}
)
}

View file

@ -1,12 +1,19 @@
package com.anytypeio.anytype.core_ui.features.fields
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
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.layout.width
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
@ -14,27 +21,39 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.rememberNestedScrollInteropConnection
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.core_models.RelationFormat
import com.anytypeio.anytype.core_models.Relations
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.common.DefaultPreviews
import com.anytypeio.anytype.core_ui.features.editor.holders.relations.resRelationOrigin
import com.anytypeio.anytype.core_ui.foundation.Dragger
import com.anytypeio.anytype.core_ui.foundation.noRippleThrottledClickable
import com.anytypeio.anytype.core_ui.views.BodyCalloutMedium
import com.anytypeio.anytype.core_ui.views.Title1
import com.anytypeio.anytype.presentation.relations.ObjectRelationView
import com.anytypeio.anytype.presentation.relations.RelationListViewModel.Model
import timber.log.Timber
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun FieldListScreen(
state: List<Model>,
onRelationClicked: (Model.Item) -> Unit
onRelationClicked: (Model.Item) -> Unit,
onTypeIconClicked: () -> Unit,
onLocalInfoIconClicked: () -> Unit,
onAddToTypeClicked: (Model.Item) -> Unit,
onRemoveFromObjectClicked: (Model.Item) -> Unit
) {
LazyColumn(
modifier = Modifier
.fillMaxSize()
.background(
color = colorResource(id = R.color.widget_background),
shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp)
)
.nestedScroll(rememberNestedScrollInteropConnection())
.padding(horizontal = 16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
@ -47,7 +66,9 @@ fun FieldListScreen(
}
item {
Box(
modifier = Modifier.height(48.dp)
modifier = Modifier
.fillMaxWidth()
.height(48.dp)
) {
Text(
modifier = Modifier.align(Alignment.Center),
@ -55,6 +76,23 @@ fun FieldListScreen(
style = Title1,
color = colorResource(id = R.color.text_primary),
)
Box(
modifier = Modifier
.align(Alignment.CenterEnd)
.width(56.dp)
.height(48.dp)
.noRippleThrottledClickable {
onTypeIconClicked()
},
contentAlignment = Alignment.Center
) {
Image(
modifier = Modifier.wrapContentSize(),
painter = painterResource(R.drawable.ic_settings_24),
contentDescription = "Open object's type"
)
}
}
}
items(
@ -68,11 +106,13 @@ fun FieldListScreen(
when (field) {
is ObjectRelationView.Checkbox -> {
FieldTypeCheckbox(
modifier = Modifier.noRippleThrottledClickable {
onRelationClicked(item)
},
modifier = Modifier,
title = field.name,
isCheck = field.isChecked
isCheck = field.isChecked,
isLocal = item.isLocal,
onFieldClick = { onRelationClicked(item) },
onAddToCurrentTypeClick = { onAddToTypeClicked(item) },
onRemoveFromObjectClick = { onRemoveFromObjectClicked(item) }
)
}
@ -80,19 +120,23 @@ fun FieldListScreen(
val relativeDate = field.relativeDate
if (relativeDate != null) {
FieldTypeDate(
modifier = Modifier.noRippleThrottledClickable {
onRelationClicked(item)
},
modifier = Modifier,
title = field.name,
relativeDate = relativeDate
relativeDate = relativeDate,
isLocal = item.isLocal,
onFieldClick = { onRelationClicked(item) },
onAddToCurrentTypeClick = { onAddToTypeClicked(item) },
onRemoveFromObjectClick = { onRemoveFromObjectClicked(item) }
)
} else {
FieldEmpty(
modifier = Modifier.noRippleThrottledClickable {
onRelationClicked(item)
},
modifier = Modifier,
title = field.name,
fieldFormat = RelationFormat.DATE
fieldFormat = RelationFormat.DATE,
isLocal = item.isLocal,
onFieldClick = { onRelationClicked(item) },
onAddToCurrentTypeClick = { onAddToTypeClicked(item) },
onRemoveFromObjectClick = { onRemoveFromObjectClicked(item) }
)
}
}
@ -102,28 +146,34 @@ fun FieldListScreen(
if (field.key == Relations.ORIGIN) {
val code = textValue?.toInt() ?: -1
FieldTypeText(
modifier = Modifier.noRippleThrottledClickable {
onRelationClicked(item)
},
modifier = Modifier,
title = field.name,
text = stringResource(code.resRelationOrigin())
text = stringResource(code.resRelationOrigin()),
isLocal = item.isLocal,
onFieldClick = { onRelationClicked(item) },
onAddToCurrentTypeClick = { onAddToTypeClicked(item) },
onRemoveFromObjectClick = { onRemoveFromObjectClicked(item) }
)
} else {
if (textValue.isNullOrEmpty() == true) {
FieldEmpty(
modifier = Modifier.noRippleThrottledClickable {
onRelationClicked(item)
},
modifier = Modifier,
title = field.name,
fieldFormat = RelationFormat.LONG_TEXT
fieldFormat = RelationFormat.LONG_TEXT,
isLocal = item.isLocal,
onFieldClick = { onRelationClicked(item) },
onAddToCurrentTypeClick = { onAddToTypeClicked(item) },
onRemoveFromObjectClick = { onRemoveFromObjectClicked(item) }
)
} else {
FieldTypeText(
modifier = Modifier.noRippleThrottledClickable {
onRelationClicked(item)
},
modifier = Modifier,
title = field.name,
text = textValue
text = textValue,
isLocal = item.isLocal,
onFieldClick = { onRelationClicked(item) },
onAddToCurrentTypeClick = { onAddToTypeClicked(item) },
onRemoveFromObjectClick = { onRemoveFromObjectClicked(item) }
)
}
}
@ -132,18 +182,22 @@ fun FieldListScreen(
is ObjectRelationView.File -> {
if (field.files.isEmpty()) {
FieldEmpty(
modifier = Modifier.noRippleThrottledClickable {
onRelationClicked(item)
},
modifier = Modifier,
title = field.name,
fieldFormat = RelationFormat.FILE
fieldFormat = RelationFormat.FILE,
isLocal = item.isLocal,
onFieldClick = { onRelationClicked(item) },
onAddToCurrentTypeClick = { onAddToTypeClicked(item) },
onRemoveFromObjectClick = { onRemoveFromObjectClicked(item) }
)
} else {
FieldTypeFile(
modifier = Modifier.noRippleThrottledClickable {
onRelationClicked(item)
},
fieldObject = field
modifier = Modifier,
fieldObject = field,
isLocal = item.isLocal,
onFieldClick = { onRelationClicked(item) },
onAddToCurrentTypeClick = { onAddToTypeClicked(item) },
onRemoveFromObjectClick = { onRemoveFromObjectClicked(item) }
)
}
}
@ -151,18 +205,22 @@ fun FieldListScreen(
is ObjectRelationView.Object -> {
if (field.objects.isEmpty()) {
FieldEmpty(
modifier = Modifier.noRippleThrottledClickable {
onRelationClicked(item)
},
modifier = Modifier,
title = field.name,
fieldFormat = RelationFormat.OBJECT
fieldFormat = RelationFormat.OBJECT,
isLocal = item.isLocal,
onFieldClick = { onRelationClicked(item) },
onAddToCurrentTypeClick = { onAddToTypeClicked(item) },
onRemoveFromObjectClick = { onRemoveFromObjectClicked(item) }
)
} else {
FieldTypeObject(
modifier = Modifier.noRippleThrottledClickable {
onRelationClicked(item)
},
fieldObject = field
modifier = Modifier,
fieldObject = field,
isLocal = item.isLocal,
onFieldClick = { onRelationClicked(item) },
onAddToCurrentTypeClick = { onAddToTypeClicked(item) },
onRemoveFromObjectClick = { onRemoveFromObjectClicked(item) }
)
}
}
@ -170,19 +228,23 @@ fun FieldListScreen(
is ObjectRelationView.Status -> {
if (field.status.isEmpty()) {
FieldEmpty(
modifier = Modifier.noRippleThrottledClickable {
onRelationClicked(item)
},
modifier = Modifier,
title = field.name,
fieldFormat = RelationFormat.STATUS
fieldFormat = RelationFormat.STATUS,
isLocal = item.isLocal,
onFieldClick = { onRelationClicked(item) },
onAddToCurrentTypeClick = { onAddToTypeClicked(item) },
onRemoveFromObjectClick = { onRemoveFromObjectClicked(item) }
)
} else {
FieldTypeSelect(
modifier = Modifier.noRippleThrottledClickable {
onRelationClicked(item)
},
modifier = Modifier,
title = field.name,
status = field.status.first()
status = field.status.first(),
isLocal = item.isLocal,
onFieldClick = { onRelationClicked(item) },
onAddToCurrentTypeClick = { onAddToTypeClicked(item) },
onRemoveFromObjectClick = { onRemoveFromObjectClicked(item) }
)
}
}
@ -190,22 +252,27 @@ fun FieldListScreen(
is ObjectRelationView.Tags -> {
if (field.tags.isEmpty()) {
FieldEmpty(
modifier = Modifier.noRippleThrottledClickable {
onRelationClicked(item)
},
modifier = Modifier,
title = field.name,
fieldFormat = RelationFormat.TAG
fieldFormat = RelationFormat.TAG,
isLocal = item.isLocal,
onFieldClick = { onRelationClicked(item) },
onAddToCurrentTypeClick = { onAddToTypeClicked(item) },
onRemoveFromObjectClick = { onRemoveFromObjectClicked(item) }
)
} else {
FieldTypeMultiSelect(
modifier = Modifier.noRippleThrottledClickable {
onRelationClicked(item)
},
modifier = Modifier,
title = field.name,
tags = field.tags
tags = field.tags,
isLocal = item.isLocal,
onFieldClick = { onRelationClicked(item) },
onAddToCurrentTypeClick = { onAddToTypeClicked(item) },
onRemoveFromObjectClick = { onRemoveFromObjectClicked(item) }
)
}
}
is ObjectRelationView.Links.Backlinks,
is ObjectRelationView.Links.From,
is ObjectRelationView.ObjectType.Base,
@ -216,16 +283,14 @@ fun FieldListScreen(
}
}
Model.Section.Featured -> {
//TODO: Implement
is Model.Section.Header -> {
Section(item)
}
Model.Section.Other -> {
//TODO: Implement
is Model.Section.SideBar -> {
Section(item)
}
is Model.Section.TypeFrom -> {
//TODO: Implement
Model.Section.Local -> {
SectionLocal(onLocalInfoIconClicked)
}
}
}
@ -234,4 +299,77 @@ fun FieldListScreen(
Spacer(modifier = Modifier.height(64.dp))
}
}
}
}
@Composable
private fun SectionLocal(
onLocalInfoIconClicked: () -> Unit = {}
) {
Box(
modifier = Modifier
.fillMaxWidth()
.height(52.dp)
) {
Text(
modifier = Modifier
.padding(bottom = 7.dp, start = 20.dp)
.align(Alignment.BottomStart),
text = stringResource(id = R.string.object_type_fields_section_local_fields),
style = BodyCalloutMedium,
color = colorResource(R.color.text_primary),
)
Box(
modifier = Modifier
.align(Alignment.BottomEnd)
.height(37.dp)
.width(44.dp)
.noRippleThrottledClickable {
onLocalInfoIconClicked()
}
) {
Image(
modifier = Modifier
.padding(bottom = 9.dp, end = 20.dp)
.wrapContentSize()
.align(Alignment.BottomEnd),
painter = painterResource(R.drawable.ic_section_local_fields),
contentDescription = "Section local fields info"
)
}
}
}
@Composable
private fun Section(item: Model.Section) {
val text = when (item) {
Model.Section.Header -> stringResource(id = R.string.object_type_fields_section_header)
Model.Section.SideBar -> stringResource(id = R.string.object_type_fields_section_fields_menu)
else -> ""
}
Box(
modifier = Modifier.fillMaxWidth(),
contentAlignment = Alignment.CenterStart
) {
Text(
text = text,
style = BodyCalloutMedium,
color = colorResource(id = R.color.text_secondary),
modifier = Modifier
.padding(vertical = 17.dp)
.padding(start = 16.dp)
)
}
}
@DefaultPreviews
@Composable
fun FieldListScreenPreview() {
FieldListScreen(
state = listOf(Model.Section.Local),
onRelationClicked = {},
onLocalInfoIconClicked = {},
onTypeIconClicked = {},
onAddToTypeClicked = {},
onRemoveFromObjectClicked = {}
)
}

View file

@ -54,7 +54,11 @@ fun TagsPreview() {
color = ThemeColor.PINK.code,
)
),
title = "Tag"
title = "Tag",
isLocal = true,
onRemoveFromObjectClick = {},
onAddToCurrentTypeClick = {},
onFieldClick = {}
)
}
}
@ -76,7 +80,11 @@ fun SingleLongTagPreview() {
color = ThemeColor.RED.code,
),
),
title = "Tag"
title = "Tag",
isLocal = true,
onRemoveFromObjectClick = {},
onAddToCurrentTypeClick = {},
onFieldClick = {}
)
}
}
@ -98,7 +106,11 @@ fun SingleShortTagPreview() {
color = ThemeColor.RED.code,
),
),
title = "Tag"
title = "Tag",
isLocal = true,
onRemoveFromObjectClick = {},
onAddToCurrentTypeClick = {},
onFieldClick = {}
)
}
}
@ -125,7 +137,11 @@ fun TwoTagsFirstShortSecondLongPreview() {
color = ThemeColor.ORANGE.code,
)
),
title = "Tag"
title = "Tag",
isLocal = true,
onRemoveFromObjectClick = {},
onAddToCurrentTypeClick = {},
onFieldClick = {}
)
}
}
@ -152,7 +168,11 @@ fun TwoShortTagsPreview() {
color = ThemeColor.ORANGE.code,
)
),
title = "Tag"
title = "Tag",
isLocal = true,
onRemoveFromObjectClick = {},
onAddToCurrentTypeClick = {},
onFieldClick = {}
)
}
}
@ -184,7 +204,11 @@ fun ThreeShortTagsPreview() {
color = ThemeColor.LIME.code,
),
),
title = "Tag"
title = "Tag",
isLocal = true,
onRemoveFromObjectClick = {},
onAddToCurrentTypeClick = {},
onFieldClick = {}
)
}
}
@ -221,7 +245,11 @@ fun FourTagsWithOverflowPreview() {
color = ThemeColor.BLUE.code,
)
),
title = "Tag"
title = "Tag",
isLocal = true,
onRemoveFromObjectClick = {},
onAddToCurrentTypeClick = {},
onFieldClick = {}
)
}
}

View file

@ -21,6 +21,7 @@ import com.anytypeio.anytype.presentation.relations.ObjectRelationView
import com.anytypeio.anytype.presentation.relations.RelationListViewModel
import timber.log.Timber
@Deprecated("Use ListRelationViewHolder instead")
class DocumentRelationAdapter(
private var items: List<RelationListViewModel.Model>,
private val onRelationClicked: (RelationListViewModel.Model.Item) -> Unit,
@ -189,8 +190,8 @@ class DocumentRelationAdapter(
if (payload is GranularChange) {
if (payload.isModeChanged) {
val item = items[position]
check(item is RelationListViewModel.Model.Item)
holder.setIsRemovable(item.isRemovable)
// check(item is RelationListViewModel.Model.Item)
// holder.setIsRemovable(item.isRemovable)
} else {
super.onBindViewHolder(holder, position, payloads)
}
@ -258,7 +259,7 @@ class DocumentRelationAdapter(
if (holder is ListRelationViewHolder) {
check(item is RelationListViewModel.Model.Item)
holder.setIsFeatured(item.view.featured)
holder.setIsRemovable(item.isRemovable)
//holder.setIsRemovable(item.isRemovable)
}
}
@ -276,9 +277,9 @@ class DocumentRelationAdapter(
else -> R.layout.item_relation_list_relation_default
}
}
RelationListViewModel.Model.Section.Featured -> R.layout.item_relation_list_section
RelationListViewModel.Model.Section.Other -> R.layout.item_relation_list_section
is RelationListViewModel.Model.Section.TypeFrom -> R.layout.item_relation_list_section
// RelationListViewModel.Model.Section.Featured -> R.layout.item_relation_list_section
// RelationListViewModel.Model.Section.Other -> R.layout.item_relation_list_section
// is RelationListViewModel.Model.Section.TypeFrom -> R.layout.item_relation_list_section
else -> throw IllegalStateException("Unexpected item type: $item")
}
@ -292,18 +293,18 @@ class DocumentRelationAdapter(
class SectionViewHolder(view: View) : RecyclerView.ViewHolder(view) {
fun bind(section: RelationListViewModel.Model.Section) {
when (section) {
RelationListViewModel.Model.Section.Featured -> {
itemView.findViewById<TextView>(R.id.tvSectionName)
.setText(R.string.featured_relations)
}
RelationListViewModel.Model.Section.Other -> {
itemView.findViewById<TextView>(R.id.tvSectionName)
.setText(R.string.other_relations)
}
is RelationListViewModel.Model.Section.TypeFrom -> {
val text = itemView.resources.getString(R.string.from_type, section.typeName)
itemView.findViewById<TextView>(R.id.tvSectionName).text = text
}
// RelationListViewModel.Model.Section.Featured -> {
// itemView.findViewById<TextView>(R.id.tvSectionName)
// .setText(R.string.featured_relations)
// }
// RelationListViewModel.Model.Section.Other -> {
// itemView.findViewById<TextView>(R.id.tvSectionName)
// .setText(R.string.other_relations)
// }
// is RelationListViewModel.Model.Section.TypeFrom -> {
// val text = itemView.resources.getString(R.string.from_type, section.typeName)
// itemView.findViewById<TextView>(R.id.tvSectionName).text = text
// }
else -> throw IllegalStateException("Unexpected item type: $section")
}
}
@ -316,13 +317,14 @@ class DocumentRelationAdapter(
override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? {
val oldItem = old[oldItemPosition]
val newItem = new[newItemPosition]
return if (oldItem is RelationListViewModel.Model.Item && newItem is RelationListViewModel.Model.Item) {
if (newItem.isRemovable != oldItem.isRemovable)
GranularChange(isModeChanged = true)
else
null
} else
null
return null
// return if (oldItem is RelationListViewModel.Model.Item && newItem is RelationListViewModel.Model.Item) {
// if (newItem.isRemovable != oldItem.isRemovable)
// GranularChange(isModeChanged = true)
// else
// null
// } else
// null
}
}

View file

@ -78,16 +78,18 @@ fun Dragger(
@Composable
fun Divider(
modifier: Modifier = Modifier,
height: Dp = 0.5.dp,
paddingStart: Dp = 20.dp,
paddingEnd: Dp = 20.dp,
visible: Boolean = true
visible: Boolean = true,
color: Color = colorResource(R.color.shape_primary)
) {
Box(
modifier = modifier
.alpha(if (visible) 1f else 0f)
.padding(start = paddingStart, end = paddingEnd)
.background(color = colorResource(R.color.shape_primary))
.height(0.5.dp)
.background(color = color)
.height(height)
.fillMaxWidth()
)
}

View file

@ -0,0 +1,162 @@
package com.anytypeio.anytype.core_ui.foundation
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.selection.LocalTextSelectionColors
import androidx.compose.foundation.text.selection.TextSelectionColors
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.TextFieldDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.dp
import com.anytypeio.anytype.core_ui.common.DefaultPreviews
import com.anytypeio.anytype.core_ui.views.BodyRegular
import com.anytypeio.anytype.core_ui.R
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun DefaultSearchBar(
modifier: Modifier = Modifier,
onQueryChanged: (String) -> Unit
) {
val interactionSource = remember { MutableInteractionSource() }
val focus = LocalFocusManager.current
val focusRequester = FocusRequester()
val selectionColors = TextSelectionColors(
backgroundColor = colorResource(id = R.color.cursor_color).copy(
alpha = 0.2f
),
handleColor = colorResource(id = R.color.cursor_color),
)
var query by remember { mutableStateOf(TextFieldValue()) }
Row(
modifier = modifier
.background(
color = colorResource(id = R.color.shape_transparent),
shape = RoundedCornerShape(10.dp)
)
.height(40.dp),
verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(id = R.drawable.ic_search_18),
contentDescription = "Search icon",
modifier = Modifier
.align(Alignment.CenterVertically)
.padding(
start = 11.dp
)
)
CompositionLocalProvider(value = LocalTextSelectionColors provides selectionColors) {
BasicTextField(
value = query,
modifier = Modifier
.weight(1.0f)
.padding(start = 6.dp)
.align(Alignment.CenterVertically)
.focusRequester(focusRequester),
textStyle = BodyRegular.copy(
color = colorResource(id = R.color.text_primary)
),
onValueChange = { input ->
query = input.also {
onQueryChanged(input.text)
}
},
singleLine = true,
maxLines = 1,
keyboardActions = KeyboardActions(
onDone = {
focus.clearFocus(true)
}
),
decorationBox = @Composable { innerTextField ->
TextFieldDefaults.OutlinedTextFieldDecorationBox(
value = query.text,
innerTextField = innerTextField,
enabled = true,
singleLine = true,
visualTransformation = VisualTransformation.None,
interactionSource = interactionSource,
placeholder = {
Text(
text = stringResource(id = R.string.search),
style = BodyRegular.copy(
color = colorResource(id = R.color.text_tertiary)
)
)
},
colors = TextFieldDefaults.textFieldColors(
backgroundColor = Color.Transparent,
cursorColor = colorResource(id = R.color.cursor_color),
),
border = {},
contentPadding = PaddingValues()
)
},
cursorBrush = SolidColor(colorResource(id = R.color.palette_system_blue)),
)
}
Spacer(Modifier.width(9.dp))
AnimatedVisibility(
visible = query.text.isNotEmpty(),
enter = fadeIn(tween(100)),
exit = fadeOut(tween(100))
) {
Image(
painter = painterResource(id = R.drawable.ic_clear_18),
contentDescription = "Clear icon",
modifier = Modifier
.padding(end = 9.dp)
.noRippleClickable {
query = TextFieldValue().also {
onQueryChanged("")
}
}
)
}
}
}
@DefaultPreviews
@Composable
private fun AllContentSearchBarPreview() {
DefaultSearchBar {}
}

View file

@ -0,0 +1,137 @@
package com.anytypeio.anytype.core_ui.lists.objects
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material3.ListItem
import androidx.compose.material3.ListItemDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.anytypeio.anytype.core_models.ObjectType
import com.anytypeio.anytype.core_models.primitives.SpaceId
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.common.DefaultPreviews
import com.anytypeio.anytype.core_ui.common.ShimmerEffect
import com.anytypeio.anytype.core_ui.views.PreviewTitle2Regular
import com.anytypeio.anytype.core_ui.views.Relations3
import com.anytypeio.anytype.core_ui.widgets.ListWidgetObjectIcon
import com.anytypeio.anytype.presentation.objects.ObjectIcon
import com.anytypeio.anytype.presentation.objects.UiObjectsListItem
/**
* A reusable composable for displaying a single UiObjectsListItem.Item
*/
@Composable
fun ObjectsListItem(
item: UiObjectsListItem.Item,
modifier: Modifier = Modifier,
) {
val createdBy = item.createdBy
val typeName = item.typeName
ListItem(
colors = ListItemDefaults.colors(
containerColor = colorResource(id = R.color.background_primary),
),
modifier = modifier
.height(72.dp)
.fillMaxWidth(),
headlineContent = {
Text(
text = item.name,
style = PreviewTitle2Regular,
color = colorResource(id = R.color.text_primary),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
},
supportingContent = {
Row {
if (!typeName.isNullOrBlank()) {
Text(
text = typeName,
style = Relations3,
color = colorResource(id = R.color.text_secondary),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
if (!createdBy.isNullOrBlank()) {
Text(
text = "${stringResource(R.string.date_layout_item_created_by)}$createdBy",
style = Relations3,
color = colorResource(id = R.color.text_secondary),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}
},
leadingContent = {
ListWidgetObjectIcon(
icon = item.icon,
modifier = Modifier,
iconSize = 48.dp
)
}
)
}
@Composable
fun ListItemLoading(
modifier: Modifier
) {
ListItem(
colors = ListItemDefaults.colors(
containerColor = colorResource(id = R.color.background_primary),
),
modifier = modifier
.height(72.dp)
.fillMaxWidth(),
headlineContent = {
ShimmerEffect(
modifier = Modifier
.width(164.dp)
.height(18.dp)
)
},
supportingContent = {
ShimmerEffect(
modifier = Modifier
.width(64.dp)
.height(13.dp)
)
},
leadingContent = {
ShimmerEffect(
modifier = Modifier
.size(48.dp)
)
}
)
}
@DefaultPreviews
@Composable
fun PreviewObjectListItem() {
ObjectsListItem(
item = UiObjectsListItem.Item(
id = "123",
name = "Some name",
space = SpaceId("123"),
type = "123",
typeName = "Some type",
createdBy = "Some user",
layout = ObjectType.Layout.BASIC,
icon = ObjectIcon.Empty.Page,
isPossibleToDelete = true
)
)
}

View file

@ -0,0 +1,165 @@
package com.anytypeio.anytype.core_ui.lists.objects
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
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.rememberLazyListState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.unit.dp
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.common.DefaultPreviews
import com.anytypeio.anytype.core_ui.extensions.swapList
import com.anytypeio.anytype.core_ui.foundation.Divider
import com.anytypeio.anytype.core_ui.foundation.noRippleThrottledClickable
import com.anytypeio.anytype.core_ui.lists.objects.stubs.StubVerticalItems
import com.anytypeio.anytype.core_ui.views.ButtonSize
import com.anytypeio.anytype.core_ui.views.animations.DotsLoadingIndicator
import com.anytypeio.anytype.core_ui.views.animations.FadeAnimationSpecs
import com.anytypeio.anytype.presentation.objects.UiObjectsListItem
import kotlinx.coroutines.launch
@Composable
fun PaginatedObjectList(
state: UiObjectsListState,
uiState: UiContentState,
canPaginate: Boolean,
onLoadMore: () -> Unit,
onObjectClicked: (UiObjectsListItem.Item) -> Unit,
onMoveToBin: (UiObjectsListItem.Item) -> Unit,
) {
val items = remember { mutableStateListOf<UiObjectsListItem>() }
items.swapList(state.items)
val scope = rememberCoroutineScope()
val lazyListState = rememberLazyListState()
val canPaginateState = remember { mutableStateOf(false) }
LaunchedEffect(key1 = canPaginate) {
canPaginateState.value = canPaginate
}
val shouldStartPaging = remember {
derivedStateOf {
canPaginateState.value && (lazyListState.layoutInfo.visibleItemsInfo.lastOrNull()?.index
?: -9) >= (lazyListState.layoutInfo.totalItemsCount - 2)
}
}
LaunchedEffect(key1 = shouldStartPaging.value) {
if (shouldStartPaging.value && uiState is UiContentState.Idle) {
onLoadMore()
}
}
LazyColumn(
modifier = Modifier
.padding(top = 8.dp)
.fillMaxSize(),
state = lazyListState
) {
items(
count = items.size,
key = { index -> items[index].id },
contentType = { index ->
when (items[index]) {
is UiObjectsListItem.Loading -> "loading"
is UiObjectsListItem.Item -> "item"
}
}
) { index ->
val item = items[index]
when (item) {
is UiObjectsListItem.Item -> {
SwipeToDismissItem(
modifier = Modifier
.fillMaxWidth()
.animateItem()
.noRippleThrottledClickable {
onObjectClicked(item)
},
item = item,
onObjectClicked = onObjectClicked,
onMoveToBin = onMoveToBin
)
Divider(paddingStart = 16.dp, paddingEnd = 16.dp)
}
is UiObjectsListItem.Loading -> {
ListItemLoading(modifier = Modifier)
}
}
}
if (uiState is UiContentState.Paging) {
item {
Box(
modifier = Modifier
.fillParentMaxWidth()
.height(52.dp),
contentAlignment = Alignment.Center
) {
LoadingState()
}
}
}
item {
Spacer(modifier = Modifier.height(200.dp))
}
}
LaunchedEffect(key1 = uiState) {
if (uiState is UiContentState.Idle) {
if (uiState.scrollToTop) {
scope.launch {
lazyListState.scrollToItem(0)
}
}
}
}
}
@Composable
private fun BoxScope.LoadingState() {
val loadingAlpha by animateFloatAsState(targetValue = 1f, label = "")
DotsLoadingIndicator(
animating = true,
modifier = Modifier
.graphicsLayer { alpha = loadingAlpha }
.align(Alignment.Center),
animationSpecs = FadeAnimationSpecs(itemCount = 3),
color = colorResource(id = R.color.glyph_active),
size = ButtonSize.Small
)
}
@Composable
@DefaultPreviews
fun ObjectsListScreenPreview() {
val contentListState = UiObjectsListState(
items = StubVerticalItems
)
PaginatedObjectList(
state = contentListState,
uiState = UiContentState.Idle(scrollToTop = false),
canPaginate = true,
onObjectClicked = {},
onLoadMore = {},
onMoveToBin = {},
)
}

View file

@ -0,0 +1,27 @@
package com.anytypeio.anytype.core_ui.lists.objects
import com.anytypeio.anytype.presentation.objects.UiObjectsListItem
sealed class UiContentState {
data class Idle(val scrollToTop: Boolean = false) : UiContentState()
data object InitLoading : UiContentState()
data object Paging : UiContentState()
data object Empty : UiContentState()
}
data class UiObjectsListState(
val items: List<UiObjectsListItem>
) {
companion object {
val Empty = UiObjectsListState(items = emptyList())
val LoadingState = UiObjectsListState(
items = listOf(
UiObjectsListItem.Loading("Loading-Item-1"),
UiObjectsListItem.Loading("Loading-Item-2"),
UiObjectsListItem.Loading("Loading-Item-3"),
UiObjectsListItem.Loading("Loading-Item-4"),
)
)
}
}

View file

@ -0,0 +1,89 @@
package com.anytypeio.anytype.core_ui.lists.objects
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically
import androidx.compose.material3.SwipeToDismissBox
import androidx.compose.material3.SwipeToDismissBoxValue
import androidx.compose.material3.rememberSwipeToDismissBoxState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.foundation.DismissBackground
import com.anytypeio.anytype.core_ui.foundation.noRippleThrottledClickable
import com.anytypeio.anytype.presentation.objects.UiObjectsListItem
import kotlinx.coroutines.delay
@Composable
fun SwipeToDismissItem(
item: UiObjectsListItem.Item,
modifier: Modifier,
animationDuration: Int = 500,
onObjectClicked: (UiObjectsListItem.Item) -> Unit,
onMoveToBin: (UiObjectsListItem.Item) -> Unit,
) {
var isRemoved by remember { mutableStateOf(false) }
val dismissState = rememberSwipeToDismissBoxState(
initialValue = SwipeToDismissBoxValue.Settled,
confirmValueChange = { value ->
if (value == SwipeToDismissBoxValue.EndToStart) {
isRemoved = true
true
} else {
false
}
return@rememberSwipeToDismissBoxState true
},
positionalThreshold = { it * .5f }
)
if (dismissState.currentValue != SwipeToDismissBoxValue.Settled) {
LaunchedEffect(Unit) {
dismissState.snapTo(SwipeToDismissBoxValue.Settled)
}
}
LaunchedEffect(key1 = isRemoved) {
if (isRemoved) {
delay(animationDuration.toLong())
onMoveToBin(item)
}
}
AnimatedVisibility(
visible = !isRemoved,
exit = shrinkVertically(
animationSpec = tween(durationMillis = animationDuration),
shrinkTowards = Alignment.Top
) + fadeOut()
) {
SwipeToDismissBox(
modifier = modifier,
state = dismissState,
enableDismissFromEndToStart = item.isPossibleToDelete,
enableDismissFromStartToEnd = false,
backgroundContent = {
DismissBackground(
actionText = stringResource(R.string.move_to_bin),
dismissState = dismissState
)
},
content = {
ObjectsListItem(
modifier = Modifier
.noRippleThrottledClickable {
onObjectClicked(item)
},
item = item
)
}
)
}
}

View file

@ -0,0 +1,212 @@
package com.anytypeio.anytype.core_ui.lists.objects.menu
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment.Companion.CenterVertically
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.ColorFilter.Companion.tint
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.core_ui.R
import com.anytypeio.anytype.core_models.DVSortType
import com.anytypeio.anytype.core_ui.foundation.Divider
import com.anytypeio.anytype.core_ui.views.BodyCalloutRegular
import com.anytypeio.anytype.core_ui.views.UXBody
import com.anytypeio.anytype.presentation.objects.MenuSortsItem
import com.anytypeio.anytype.presentation.objects.ObjectsListSort
@Composable
fun ObjectsListSortingMenuContainer(
container: MenuSortsItem.Container,
sorts: List<MenuSortsItem.Sort>,
types: List<MenuSortsItem.SortType>,
sortingExpanded: Boolean,
onChangeSortExpandedState: (Boolean) -> Unit,
onSortClick: (ObjectsListSort) -> Unit
) {
SortingBox(
modifier = Modifier
.clickable {
onChangeSortExpandedState(!sortingExpanded)
},
subtitle = container.sort.title(),
isExpanded = sortingExpanded
)
Divider(
paddingStart = 0.dp,
paddingEnd = 0.dp,
color = colorResource(R.color.shape_secondary)
)
if (sortingExpanded) {
sorts.forEach { item ->
ObjectsListMenuItem(
title = item.sort.title(),
isSelected = item.sort.isSelected,
modifier = Modifier
.clickable {
onSortClick(item.sort)
}
)
Divider(
paddingStart = 0.dp,
paddingEnd = 0.dp,
color = colorResource(R.color.shape_secondary)
)
}
Divider(
height = 7.5.dp,
paddingStart = 0.dp,
paddingEnd = 0.dp,
color = colorResource(R.color.shape_secondary)
)
val size = types.size
types.forEachIndexed { index, item ->
val s = item.sort
ObjectsListMenuItem(
title = item.sortType.title(item.sort),
isSelected = item.isSelected,
modifier = Modifier
.clickable {
val updatedSort = when (val sort = item.sort) {
is ObjectsListSort.ByName -> sort.copy(sortType = item.sortType)
is ObjectsListSort.ByDateCreated -> sort.copy(sortType = item.sortType)
is ObjectsListSort.ByDateUpdated -> sort.copy(sortType = item.sortType)
is ObjectsListSort.ByDateUsed -> sort.copy(sortType = item.sortType)
}
onSortClick(updatedSort)
}
)
if (index < size - 1) {
Divider(
paddingStart = 0.dp,
paddingEnd = 0.dp,
color = colorResource(R.color.shape_secondary)
)
}
}
}
}
@Composable
private fun SortingBox(modifier: Modifier, subtitle: String, isExpanded: Boolean) {
val rotationAngle = if (isExpanded) 90f else 0f
Row(
modifier = modifier
.fillMaxWidth()
.background(colorResource(id = R.color.background_secondary)),
verticalAlignment = CenterVertically
) {
Image(
modifier = Modifier
.padding(start = 10.dp)
.size(18.dp)
.rotate(rotationAngle),
painter = painterResource(R.drawable.ic_arrow_disclosure_18),
contentDescription = "",
colorFilter = tint(colorResource(id = R.color.glyph_selected))
)
Column(
modifier = Modifier
.wrapContentHeight()
.padding(top = 11.dp, bottom = 10.dp, start = 6.dp)
) {
Text(
text = stringResource(id = R.string.all_content_sort_by),
modifier = Modifier.wrapContentSize(),
style = UXBody,
color = colorResource(id = R.color.text_primary)
)
Text(
text = subtitle,
modifier = Modifier.wrapContentSize(),
style = BodyCalloutRegular,
color = colorResource(id = R.color.text_secondary)
)
}
}
}
@Composable
fun ObjectsListMenuItem(
modifier: Modifier,
title: String,
isSelected: Boolean,
contentDescription: String? = null
) {
Row(
modifier = modifier
.fillMaxWidth()
.height(44.dp)
.background(colorResource(id = R.color.background_secondary)),
verticalAlignment = CenterVertically,
) {
Image(
modifier = Modifier
.wrapContentSize()
.padding(start = 12.dp),
painter = painterResource(R.drawable.ic_check_16),
contentDescription = contentDescription,
alpha = if (isSelected) 1f else 0f
)
Text(
text = title,
modifier = Modifier
.wrapContentSize()
.padding(start = 8.dp),
style = UXBody,
color = colorResource(id = R.color.text_primary)
)
}
}
@Composable
fun ObjectsListSort.title(): String = stringResource(
when (this) {
is ObjectsListSort.ByDateCreated -> R.string.all_content_sort_date_created
is ObjectsListSort.ByDateUpdated -> R.string.all_content_sort_date_updated
is ObjectsListSort.ByName -> R.string.all_content_sort_name
is ObjectsListSort.ByDateUsed -> R.string.all_content_sort_date_used
}
)
@Composable
fun DVSortType.title(sort: ObjectsListSort): String = when (this) {
DVSortType.ASC -> {
when (sort) {
is ObjectsListSort.ByDateCreated, is ObjectsListSort.ByDateUpdated, is ObjectsListSort.ByDateUsed -> stringResource(
id = R.string.all_content_sort_date_asc
)
is ObjectsListSort.ByName -> stringResource(id = R.string.all_content_sort_name_asc)
}
}
DVSortType.DESC -> {
when (sort) {
is ObjectsListSort.ByDateCreated,
is ObjectsListSort.ByDateUpdated,
is ObjectsListSort.ByDateUsed -> stringResource(
id = R.string.all_content_sort_date_desc
)
is ObjectsListSort.ByName -> stringResource(id = R.string.all_content_sort_name_desc)
}
}
DVSortType.CUSTOM -> ""
}

View file

@ -0,0 +1,42 @@
package com.anytypeio.anytype.core_ui.lists.objects.stubs
import com.anytypeio.anytype.core_models.ObjectType
import com.anytypeio.anytype.core_models.primitives.SpaceId
import com.anytypeio.anytype.presentation.objects.ObjectIcon
import com.anytypeio.anytype.presentation.objects.UiObjectsListItem
val StubVerticalItems = listOf(
UiObjectsListItem.Item(
id = "1",
name = "Task Object",
space = SpaceId("space1"),
type = "type1",
typeName = "Task",
createdBy = "by Joseph Wolf",
layout = ObjectType.Layout.TODO,
icon = ObjectIcon.Task(isChecked = true)
),
UiObjectsListItem.Item(
id = "2",
name = "Page Object",
space = SpaceId("space2"),
type = "type2",
typeName = "Page",
createdBy = "by Mike Long",
layout = ObjectType.Layout.BASIC,
icon = ObjectIcon.Empty.Page
),
UiObjectsListItem.Item(
id = "3",
name = "File Object",
space = SpaceId("space3"),
type = "type3",
typeName = "File",
createdBy = "by John Doe",
layout = ObjectType.Layout.FILE,
icon = ObjectIcon.File(
mime = "image/png",
fileName = "test_image.png"
)
)
)

View file

@ -32,7 +32,8 @@ fun ListWidgetObjectIcon(
icon: ObjectIcon,
modifier: Modifier,
iconSize: Dp = 48.dp,
onTaskIconClicked: (Boolean) -> Unit = {}
onTaskIconClicked: (Boolean) -> Unit = {},
backgroundColor: Int = R.color.shape_tertiary
) {
when (icon) {
is ObjectIcon.Profile.Avatar -> {
@ -46,7 +47,7 @@ fun ListWidgetObjectIcon(
DefaultProfileIconImage(icon, modifier, iconSize)
}
is ObjectIcon.Basic.Emoji -> {
EmojiIconView(icon = icon, backgroundSize = iconSize, modifier = modifier)
EmojiIconView(icon = icon, backgroundSize = iconSize, modifier = modifier, backgroundColor = backgroundColor)
}
is ObjectIcon.Basic.Image -> {
DefaultObjectImageIcon(icon.hash, modifier, iconSize, fallback = icon.emptyState)
@ -77,7 +78,8 @@ fun ListWidgetObjectIcon(
EmptyIconView(
modifier = modifier,
emptyType = icon,
backgroundSize = iconSize
backgroundSize = iconSize,
backgroundColor = backgroundColor
)
}
ObjectIcon.None -> {}

View file

@ -0,0 +1,24 @@
package com.anytypeio.anytype.core_ui.widgets
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.LinearLayout
import com.anytypeio.anytype.core_ui.databinding.WidgetObjectMenuDescriptionBinding
import com.anytypeio.anytype.core_ui.R
class ObjectMenuDescriptionItem @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : LinearLayout(context, attrs) {
val binding = WidgetObjectMenuDescriptionBinding.inflate(
LayoutInflater.from(context), this
)
fun setAction(setAsHide: Boolean) {
binding.descriptionAction.text =
if (setAsHide) context.getString(R.string.modal_hide) else context.getString(R.string.modal_show)
}
}

View file

@ -25,7 +25,6 @@ class ObjectMenuItemWidget @JvmOverloads constructor(
if (set == null) return
val attrs = context.obtainStyledAttributes(set, R.styleable.ObjectMenuItemWidget, 0, 0)
tvTitle.text = attrs.getString(R.styleable.ObjectMenuItemWidget_title)
tvSubtitle.text = attrs.getString(R.styleable.ObjectMenuItemWidget_subtitle)
ivIcon.setImageResource(attrs.getResourceId(R.styleable.ObjectMenuItemWidget_icon, -1))
attrs.recycle()
}

View file

@ -0,0 +1,72 @@
package com.anytypeio.anytype.core_ui.widgets
import androidx.compose.runtime.Composable
import com.anytypeio.anytype.core_models.ObjectType
import com.anytypeio.anytype.core_models.ObjectWrapper
import com.anytypeio.anytype.core_models.Relations
import com.anytypeio.anytype.core_models.primitives.TypeId
import com.anytypeio.anytype.core_models.primitives.TypeKey
import com.anytypeio.anytype.core_ui.common.DefaultPreviews
import com.anytypeio.anytype.presentation.editor.cover.CoverColor
import com.anytypeio.anytype.presentation.templates.TemplateObjectTypeView
import com.anytypeio.anytype.presentation.templates.TemplateView
import com.anytypeio.anytype.presentation.templates.TemplateView.Companion.DEFAULT_TEMPLATE_ID_BLANK
import com.anytypeio.anytype.presentation.widgets.TypeTemplatesWidgetUI
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@DefaultPreviews
@Composable
fun TypeTemplatesWidgetPreview() {
val items = listOf(
TemplateView.Blank(
id = DEFAULT_TEMPLATE_ID_BLANK,
targetTypeId = TypeId("page"),
targetTypeKey = TypeKey("ot-page"),
typeName = "Page",
layout = ObjectType.Layout.BASIC.code
),
TemplateView.Template(
id = "1",
name = "Template Title",
targetTypeId = TypeId("page"),
targetTypeKey = TypeKey("ot-page"),
layout = ObjectType.Layout.PROFILE,
image = null,
emoji = "📄",
coverColor = CoverColor.RED,
coverGradient = null,
coverImage = null,
isDefault = true
),
)
val state = TypeTemplatesWidgetUI.Data(
templates = items,
showWidget = true,
isEditing = true,
moreMenuItem = null,
objectTypes = listOf(
TemplateObjectTypeView.Search,
TemplateObjectTypeView.Item(
type = ObjectWrapper.Type(
map = mapOf(Relations.ID to "123", Relations.NAME to "Page"),
)
)
),
viewerId = "",
isPossibleToChangeType = true,
isPossibleToChangeTemplate = false
)
TypeTemplatesWidget(
state = state,
onDismiss = {},
editClick = {},
doneClick = {},
moreClick = {},
scope = CoroutineScope(
Dispatchers.Main
),
menuClick = {},
action = {}
)
}

View file

@ -14,7 +14,9 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
@ -71,7 +73,6 @@ import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
@ -80,19 +81,17 @@ import androidx.compose.ui.unit.sp
import coil.compose.rememberAsyncImagePainter
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.ObjectType
import com.anytypeio.anytype.core_models.ObjectWrapper
import com.anytypeio.anytype.core_models.Relations
import com.anytypeio.anytype.core_models.primitives.TypeId
import com.anytypeio.anytype.core_models.primitives.TypeKey
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.foundation.Divider
import com.anytypeio.anytype.core_ui.foundation.Dragger
import com.anytypeio.anytype.core_ui.foundation.noRippleClickable
import com.anytypeio.anytype.core_ui.foundation.noRippleThrottledClickable
import com.anytypeio.anytype.core_ui.views.AvatarTitle
import com.anytypeio.anytype.core_ui.views.BodyCalloutMedium
import com.anytypeio.anytype.core_ui.views.BodyCalloutRegular
import com.anytypeio.anytype.core_ui.views.BodyRegular
import com.anytypeio.anytype.core_ui.views.Caption1Medium
import com.anytypeio.anytype.core_ui.views.Caption2Medium
import com.anytypeio.anytype.core_ui.views.Caption2Semibold
import com.anytypeio.anytype.core_ui.views.Title1
import com.anytypeio.anytype.core_ui.views.fontInterRegular
@ -101,14 +100,15 @@ import com.anytypeio.anytype.presentation.editor.cover.CoverGradient
import com.anytypeio.anytype.presentation.templates.TemplateMenuClick
import com.anytypeio.anytype.presentation.templates.TemplateObjectTypeView
import com.anytypeio.anytype.presentation.templates.TemplateView
import com.anytypeio.anytype.presentation.templates.TemplateView.Companion.DEFAULT_TEMPLATE_ID_BLANK
import com.anytypeio.anytype.presentation.widgets.TypeTemplatesWidgetUI
import com.anytypeio.anytype.presentation.widgets.TypeTemplatesWidgetUIAction
import com.anytypeio.anytype.presentation.widgets.TypeTemplatesWidgetUIAction.TemplateClick
import com.anytypeio.anytype.presentation.widgets.TypeTemplatesWidgetUIAction.TypeClick
import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi
import com.bumptech.glide.integration.compose.GlideImage
import com.bumptech.glide.integration.compose.placeholder
import kotlin.math.roundToInt
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import timber.log.Timber
@ -414,7 +414,7 @@ private fun TemplatesList(
scrollState: LazyListState,
state: TypeTemplatesWidgetUI.Data,
action: (TypeTemplatesWidgetUIAction) -> Unit,
moreClick: (TemplateView, IntOffset) -> Unit
moreClick: (TemplateView, IntOffset) -> Unit,
) {
LazyRow(
state = scrollState,
@ -497,7 +497,10 @@ private fun TemplatesList(
}
@Composable
private fun TemplateItemContent(item: TemplateView) {
fun BoxScope.TemplateItemContent(
item: TemplateView,
showDefaultIcon: Boolean = false
) {
Column {
when (item) {
is TemplateView.Blank -> {
@ -506,63 +509,20 @@ private fun TemplateItemContent(item: TemplateView) {
}
is TemplateView.Template -> {
if (item.isCoverPresent()) {
TemplateItemCoverAndIcon(item)
if (item.layout == ObjectType.Layout.TODO) {
Spacer(modifier = Modifier.height(12.dp))
TemplateItemTodoTitle(text = item.name)
} else {
if (!item.isImageOrEmojiPresent()) {
Spacer(modifier = Modifier.height(12.dp))
} else {
Spacer(modifier = Modifier.height(6.dp))
}
TemplateItemTitle(
text = item.name,
textAlign = getProperTextAlign(item.layout)
)
when (item.layout) {
ObjectType.Layout.BASIC -> {
TemplateHeaderBasic(item)
}
} else {
if (item.layout == ObjectType.Layout.TODO) {
ObjectType.Layout.PROFILE -> {
TemplateHeaderProfile(item)
}
ObjectType.Layout.TODO -> {
TemplateHeaderTask(item)
}
else -> {
Spacer(modifier = Modifier.height(28.dp))
TemplateItemTodoTitle(text = item.name)
} else {
if (item.isImageOrEmojiPresent()) {
if (item.layout.isProfileOrParticipant()) {
Box(
modifier = Modifier
.wrapContentWidth()
.height(68.dp)
.padding(top = 28.dp)
.align(Alignment.CenterHorizontally)
) {
val modifier = Modifier.clip(CircleShape)
TemplateItemIconOrImage(item = item, modifier = modifier)
}
Spacer(modifier = Modifier.height(6.dp))
TemplateItemTitle(
text = item.name,
textAlign = getProperTextAlign(item.layout)
)
} else {
val modifier = Modifier
.padding(start = 14.dp, top = 26.dp)
TemplateItemIconOrImage(item = item, modifier = modifier)
Spacer(modifier = Modifier.height(8.dp))
TemplateItemTitle(
text = item.name, textAlign = getProperTextAlign(item.layout)
)
}
} else {
Spacer(modifier = Modifier.height(28.dp))
TemplateItemTitle(
text = item.name,
textAlign = getProperTextAlign(item.layout)
)
}
}
}
Spacer(modifier = Modifier.height(8.dp))
TemplateItemRectangles()
}
@ -581,8 +541,104 @@ private fun TemplateItemContent(item: TemplateView) {
}
}
}
if (showDefaultIcon && item.isDefault) {
Text(
modifier = Modifier
.padding(bottom = 8.dp)
.wrapContentSize()
.background(
shape = RoundedCornerShape(4.dp),
color = colorResource(R.color.shape_tertiary)
)
.padding(start = 6.dp, end = 6.dp, top = 2.dp, bottom = 2.dp)
.align(Alignment.BottomCenter),
text = stringResource(R.string.default_template_icon),
textAlign = TextAlign.Center,
color = colorResource(R.color.text_secondary),
style = Caption2Medium
)
}
}
@Composable
private fun ColumnScope.TemplateHeaderBasic(item: TemplateView.Template) {
if (item.isCoverPresent()) {
TemplateItemCoverAndIcon(item)
} else {
Spacer(modifier = Modifier.height(28.dp))
TemplateItemIconOrImage(item = item)
}
Spacer(modifier = Modifier.height(12.dp))
TemplateItemTitle(
text = item.name,
textAlign = getProperTextAlign(item.layout)
)
}
@OptIn(ExperimentalGlideComposeApi::class)
@Composable
private fun ColumnScope.TemplateHeaderProfile(item: TemplateView.Template) {
if (item.isCoverPresent()) {
TemplateItemCoverAndIcon(item)
} else {
ProfileIcon(
modifier = Modifier.align(Alignment.CenterHorizontally).padding(top = 28.dp),
item = item
)
}
Spacer(modifier = Modifier.height(12.dp))
TemplateItemTitle(
text = item.name,
textAlign = getProperTextAlign(item.layout)
)
}
@OptIn(ExperimentalGlideComposeApi::class)
@Composable
private fun ProfileIcon(
modifier: Modifier = Modifier,
item: TemplateView.Template
) {
Box(
modifier = modifier
.size(40.dp)
.background(
color = colorResource(id = R.color.shape_tertiary),
shape = CircleShape
)
) {
if (item.image != null) {
GlideImage(
model = item.image,
contentDescription = "Custom image template's icon",
modifier = Modifier
.size(40.dp)
.align(Alignment.Center),
contentScale = ContentScale.Crop
)
} else {
Text(
modifier = Modifier.align(Alignment.Center),
text = item.name.firstOrNull()?.toString() ?: "U",
style = AvatarTitle.copy(
fontSize = 24.sp,
),
color = colorResource(id = R.color.glyph_active)
)
}
}
}
@Composable
private fun ColumnScope.TemplateHeaderTask(item: TemplateView.Template) {
TemplateItemCoverColor(item = item)
TemplateItemCoverImage(item = item)
TemplateItemCoverGradient(item = item)
Spacer(modifier = Modifier.height(12.dp))
TemplateItemTodoTitle(text = item.name)
}
@OptIn(ExperimentalGlideComposeApi::class)
@Composable
private fun TemplateItemIconOrImage(
item: TemplateView.Template,
@ -591,22 +647,16 @@ private fun TemplateItemIconOrImage(
item.image?.let {
Box(
modifier = modifier
.wrapContentSize()
.border(
width = 2.dp,
color = colorResource(id = R.color.background_primary),
shape = RoundedCornerShape(2.dp)
)
.clip(RoundedCornerShape(2.dp))
.padding(start = 16.dp)
.size(40.dp)
.background(
color = colorResource(id = R.color.shape_tertiary)
color = colorResource(id = R.color.shape_tertiary),
shape = RoundedCornerShape(5.dp)
)
) {
Image(
painter = rememberAsyncImagePainter(
model = it,
error = painterResource(id = R.drawable.ic_home_widget_space)
),
GlideImage(
model = it,
failure = placeholder(painterResource(id = R.drawable.ic_home_widget_space)),
contentDescription = "Custom image template's icon",
modifier = Modifier
.size(40.dp)
@ -618,17 +668,12 @@ private fun TemplateItemIconOrImage(
item.emoji?.let {
Box(
modifier = modifier
.wrapContentSize()
.border(
width = 2.dp,
color = colorResource(id = R.color.background_primary),
shape = RoundedCornerShape(8.dp)
)
.clip(RoundedCornerShape(8.dp))
.padding(start = 16.dp)
.size(40.dp)
.background(
color = colorResource(id = R.color.shape_tertiary)
color = colorResource(id = R.color.text_tertiary),
shape = RoundedCornerShape(5.dp)
)
.padding(8.dp)
) {
Image(
painter = rememberAsyncImagePainter(
@ -645,6 +690,7 @@ private fun TemplateItemIconOrImage(
}
}
@OptIn(ExperimentalGlideComposeApi::class)
@Composable
private fun TemplateItemCoverAndIcon(item: TemplateView.Template) {
Box(
@ -658,20 +704,11 @@ private fun TemplateItemCoverAndIcon(item: TemplateView.Template) {
when (item.layout) {
ObjectType.Layout.TODO -> {}
ObjectType.Layout.PROFILE, ObjectType.Layout.PARTICIPANT -> {
Box(
modifier = Modifier
.wrapContentWidth()
.height(82.dp)
.padding(top = 50.dp)
.align(Alignment.TopCenter)
) {
val modifier = Modifier
.clip(CircleShape)
.align(Alignment.Center)
TemplateItemIconOrImage(item = item, modifier = modifier)
}
ProfileIcon(
modifier = Modifier.align(Alignment.BottomCenter),
item = item
)
}
else -> {
val modifier = Modifier
.padding(start = 14.dp, top = 44.dp)
@ -991,65 +1028,4 @@ fun ObjectTypesList(
enum class DragStates {
VISIBLE,
DISMISSED
}
@Preview
@Composable
fun ComposablePreview() {
val items = listOf(
TemplateView.Blank(
id = DEFAULT_TEMPLATE_ID_BLANK,
targetTypeId = TypeId("page"),
targetTypeKey = TypeKey("ot-page"),
typeName = "Page",
layout = ObjectType.Layout.BASIC.code
),
TemplateView.Template(
id = "1",
name = "Template 1",
targetTypeId = TypeId("page"),
targetTypeKey = TypeKey("ot-page"),
layout = ObjectType.Layout.BASIC,
image = null,
emoji = null,
coverColor = null,
coverGradient = null,
coverImage = null,
),
)
val state = TypeTemplatesWidgetUI.Data(
templates = items,
showWidget = true,
isEditing = true,
moreMenuItem = TemplateView.Template(
id = "123",
name = "Template 1",
targetTypeId = TypeId("page"),
targetTypeKey = TypeKey("ot-page"),
),
objectTypes = listOf(
TemplateObjectTypeView.Search,
TemplateObjectTypeView.Item(
type = ObjectWrapper.Type(
map = mapOf(Relations.ID to "123", Relations.NAME to "Page"),
)
)
),
viewerId = "",
isPossibleToChangeType = true,
isPossibleToChangeTemplate = false
)
TypeTemplatesWidget(
state = state,
onDismiss = {},
editClick = {},
doneClick = {},
moreClick = {},
scope = CoroutineScope(
Dispatchers.Main
),
menuClick = {},
action = {}
)
}

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M5,4H19C20.381,4 21.5,5.119 21.5,6.5V17.5L14.53,10.53C14.237,10.237 13.763,10.237 13.47,10.53L10.03,13.97C9.737,14.262 9.263,14.262 8.97,13.97L8.03,13.03C7.737,12.737 7.263,12.737 6.97,13.03L2.5,17.5V6.5C2.5,5.119 3.619,4 5,4ZM1,6.5C1,4.291 2.791,2.5 5,2.5H19C21.209,2.5 23,4.291 23,6.5V17.5C23,19.709 21.209,21.5 19,21.5H5C2.791,21.5 1,19.709 1,17.5V6.5ZM7.5,10.5C6.395,10.5 5.5,9.605 5.5,8.5C5.5,7.395 6.395,6.5 7.5,6.5C8.605,6.5 9.5,7.395 9.5,8.5C9.5,9.605 8.605,10.5 7.5,10.5Z"
android:fillColor="@color/palette_system_green"
android:fillType="evenOdd"/>
</vector>

View file

@ -0,0 +1,17 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12,12m-10.25,0a10.25,10.25 0,1 1,20.5 0a10.25,10.25 0,1 1,-20.5 0"
android:strokeWidth="1.5"
android:fillColor="@color/transparent_black"
android:strokeColor="@color/palette_system_sky"/>
<path
android:pathData="M11.25,10.063h1.5v8h-1.5z"
android:fillColor="@color/palette_system_sky"/>
<path
android:pathData="M12,6.063L12,6.063A1,1 0,0 1,13 7.063L13,7.063A1,1 0,0 1,12 8.063L12,8.063A1,1 0,0 1,11 7.063L11,7.063A1,1 0,0 1,12 6.063z"
android:fillColor="@color/palette_system_sky"/>
</vector>

View file

@ -0,0 +1,33 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M6,18.5C6,19.881 4.881,21 3.5,21C2.119,21 1,19.881 1,18.5C1,17.119 2.119,16 3.5,16C4.881,16 6,17.119 6,18.5Z"
android:fillColor="#AB50CC"/>
<path
android:pathData="M8,18.5H16"
android:strokeWidth="1.5"
android:fillColor="@color/transparent_black"
android:strokeColor="@color/palette_system_purple"
android:strokeLineCap="round"/>
<path
android:pathData="M5.75,14.839L9.75,7.911"
android:strokeWidth="1.5"
android:fillColor="@color/transparent_black"
android:strokeColor="@color/palette_system_purple"
android:strokeLineCap="round"/>
<path
android:pathData="M18.25,14.839L14.25,7.911"
android:strokeWidth="1.5"
android:fillColor="@color/transparent_black"
android:strokeColor="@color/palette_system_purple"
android:strokeLineCap="round"/>
<path
android:pathData="M23,18.5C23,19.881 21.881,21 20.5,21C19.119,21 18,19.881 18,18.5C18,17.119 19.119,16 20.5,16C21.881,16 23,17.119 23,18.5Z"
android:fillColor="@color/palette_system_purple"/>
<path
android:pathData="M14.5,4C14.5,5.381 13.381,6.5 12,6.5C10.619,6.5 9.5,5.381 9.5,4C9.5,2.619 10.619,1.5 12,1.5C13.381,1.5 14.5,2.619 14.5,4Z"
android:fillColor="@color/palette_system_purple"/>
</vector>

View file

@ -0,0 +1,23 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M2.031,12C2.031,17.523 6.508,22 12.031,22C17.554,22 22.031,17.523 22.031,12C22.031,6.477 17.554,2 12.031,2C8.125,2 4.741,4.24 3.096,7.506"
android:strokeWidth="1.5"
android:fillColor="@color/transparent_black"
android:strokeColor="@color/palette_system_pink"/>
<path
android:pathData="M12,6V11.906L15.063,15"
android:strokeWidth="1.5"
android:fillColor="@color/transparent_black"
android:strokeColor="@color/palette_system_pink"
android:strokeLineCap="round"/>
<path
android:pathData="M2.031,2.5h1.5v5.5h-1.5z"
android:fillColor="@color/palette_system_pink"/>
<path
android:pathData="M2.031,8l0,-1.5l5.5,-0l0,1.5z"
android:fillColor="@color/palette_system_pink"/>
</vector>

View file

@ -0,0 +1,20 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M9.25,7.188C8.491,7.188 7.875,8.111 7.875,9.25C7.875,10.389 8.491,11.313 9.25,11.313C10.009,11.313 10.625,10.389 10.625,9.25C10.625,8.111 10.009,7.188 9.25,7.188ZM14.75,7.188C13.991,7.188 13.375,8.111 13.375,9.25C13.375,10.389 13.991,11.313 14.75,11.313C15.509,11.313 16.125,10.389 16.125,9.25C16.125,8.111 15.509,7.188 14.75,7.188Z"
android:fillColor="@color/palette_system_amber_100"
android:fillType="evenOdd"/>
<path
android:pathData="M12,12m-10.25,0a10.25,10.25 0,1 1,20.5 0a10.25,10.25 0,1 1,-20.5 0"
android:strokeWidth="1.5"
android:fillColor="@color/transparent_black"
android:strokeColor="@color/palette_system_amber_100"/>
<path
android:pathData="M17.48,13.453C17.731,13.391 17.975,13.47 18.121,13.605C18.251,13.725 18.282,13.855 18.221,14.005C17.035,16.933 14.643,18.816 12,18.816C9.367,18.816 6.984,16.948 5.793,14.042C5.731,13.891 5.762,13.761 5.895,13.639C6.046,13.501 6.295,13.421 6.55,13.484C7.929,13.83 9.798,14.141 12.005,14.113C14.224,14.113 16.101,13.795 17.48,13.453Z"
android:strokeWidth="1.5"
android:fillColor="@color/transparent_black"
android:strokeColor="@color/palette_system_amber_100"/>
</vector>

View file

@ -0,0 +1,20 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M6,2.719L18,2.719A3.25,3.25 0,0 1,21.25 5.969L21.25,17.969A3.25,3.25 0,0 1,18 21.219L6,21.219A3.25,3.25 0,0 1,2.75 17.969L2.75,5.969A3.25,3.25 0,0 1,6 2.719z"
android:strokeWidth="1.5"
android:fillColor="@color/transparent_black"
android:strokeColor="@color/palette_system_blue"/>
<path
android:pathData="M6.75,16.5L13.25,16.5A0.75,0.75 0,0 1,14 17.25L14,17.25A0.75,0.75 0,0 1,13.25 18L6.75,18A0.75,0.75 0,0 1,6 17.25L6,17.25A0.75,0.75 0,0 1,6.75 16.5z"
android:fillColor="@color/palette_system_blue"/>
<path
android:pathData="M6.75,13.563L17.25,13.563A0.75,0.75 0,0 1,18 14.313L18,14.313A0.75,0.75 0,0 1,17.25 15.063L6.75,15.063A0.75,0.75 0,0 1,6 14.313L6,14.313A0.75,0.75 0,0 1,6.75 13.563z"
android:fillColor="@color/palette_system_blue"/>
<path
android:pathData="M8,5.969L10,5.969A2,2 0,0 1,12 7.969L12,9.969A2,2 0,0 1,10 11.969L8,11.969A2,2 0,0 1,6 9.969L6,7.969A2,2 0,0 1,8 5.969z"
android:fillColor="@color/palette_system_blue"/>
</vector>

View file

@ -0,0 +1,16 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="18dp"
android:height="18dp"
android:viewportWidth="18"
android:viewportHeight="18">
<path
android:pathData="M9,0L9,0A9,9 0,0 1,18 9L18,9A9,9 0,0 1,9 18L9,18A9,9 0,0 1,0 9L0,9A9,9 0,0 1,9 0z"
android:strokeAlpha="0.6"
android:fillColor="@color/glyph_button"
android:fillAlpha="0.6"/>
<path
android:pathData="M8.023,11.281H9.344V11.193C9.362,10.131 9.667,9.656 10.452,9.171C11.278,8.672 11.786,7.971 11.786,6.932C11.786,5.432 10.65,4.416 8.956,4.416C7.4,4.416 6.154,5.325 6.094,6.987H7.497C7.553,6.009 8.25,5.579 8.956,5.579C9.741,5.579 10.378,6.101 10.378,6.918C10.378,7.606 9.949,8.091 9.399,8.432C8.54,8.959 8.033,9.48 8.023,11.193V11.281ZM8.721,14.087C9.224,14.087 9.644,13.677 9.644,13.164C9.644,12.661 9.224,12.246 8.721,12.246C8.213,12.246 7.797,12.661 7.797,13.164C7.797,13.677 8.213,14.087 8.721,14.087Z"
android:strokeAlpha="0.6"
android:fillColor="@color/text_button_label"
android:fillAlpha="0.6"/>
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M16.5,7C16.5,7.828 15.828,8.5 15,8.5C14.172,8.5 13.5,7.828 13.5,7C13.5,6.172 14.172,5.5 15,5.5C15.828,5.5 16.5,6.172 16.5,7ZM20.25,6.25H17.906C17.573,4.956 16.398,4 15,4C13.602,4 12.427,4.956 12.094,6.25H3.75C3.336,6.25 3,6.586 3,7C3,7.414 3.336,7.75 3.75,7.75H12.094C12.427,9.044 13.602,10 15,10C16.398,10 17.573,9.044 17.906,7.75H20.25C20.664,7.75 21,7.414 21,7C21,6.586 20.664,6.25 20.25,6.25ZM7.5,17C7.5,17.828 8.172,18.5 9,18.5C9.828,18.5 10.5,17.828 10.5,17C10.5,16.172 9.828,15.5 9,15.5C8.172,15.5 7.5,16.172 7.5,17ZM6.095,17.75C6.428,19.044 7.602,20 9,20C10.398,20 11.573,19.044 11.906,17.75H20.25C20.664,17.75 21,17.414 21,17C21,16.586 20.664,16.25 20.25,16.25H11.906C11.573,14.956 10.398,14 9,14C7.602,14 6.428,14.956 6.095,16.25H3.75C3.336,16.25 3,16.586 3,17C3,17.414 3.336,17.75 3.75,17.75H6.095Z"
android:fillColor="@color/glyph_active"
android:fillType="evenOdd"/>
</vector>

View file

@ -0,0 +1,20 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M9,6.75C8.172,6.75 7.5,7.757 7.5,9C7.5,10.243 8.172,11.25 9,11.25C9.828,11.25 10.5,10.243 10.5,9C10.5,7.757 9.828,6.75 9,6.75ZM15,6.75C14.172,6.75 13.5,7.757 13.5,9C13.5,10.243 14.172,11.25 15,11.25C15.828,11.25 16.5,10.243 16.5,9C16.5,7.757 15.828,6.75 15,6.75Z"
android:fillColor="@color/glyph_active"
android:fillType="evenOdd"/>
<path
android:pathData="M12,12m-11.25,0a11.25,11.25 0,1 1,22.5 0a11.25,11.25 0,1 1,-22.5 0"
android:strokeWidth="1.5"
android:fillColor="#00000000"
android:strokeColor="@color/glyph_active"/>
<path
android:pathData="M18.178,13.464C18.426,13.4 18.668,13.476 18.816,13.611C18.949,13.733 18.984,13.869 18.924,14.025C17.652,17.342 14.975,19.503 12,19.503C9.036,19.503 6.368,17.359 5.089,14.062C5.029,13.906 5.064,13.768 5.2,13.645C5.352,13.508 5.599,13.431 5.85,13.496C7.368,13.894 9.484,14.268 12.005,14.236C14.537,14.236 16.661,13.856 18.178,13.464Z"
android:strokeWidth="1.5"
android:fillColor="#00000000"
android:strokeColor="@color/glyph_active"/>
</vector>

View file

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="12dp"
android:height="12dp"
android:viewportWidth="12"
android:viewportHeight="12">
<path
android:pathData="M3,0L9,0A3,3 0,0 1,12 3L12,9A3,3 0,0 1,9 12L3,12A3,3 0,0 1,0 9L0,3A3,3 0,0 1,3 0z"
android:fillColor="@color/glyph_active"/>
<path
android:strokeWidth="1"
android:pathData="M3,6L5.5,8.5L8.499,3"
android:fillColor="#00000000"
android:strokeColor="@color/glyph_white"/>
</vector>

View file

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<ImageView
android:id="@+id/ivIcon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginTop="14dp"
android:layout_marginBottom="14dp"
android:layout_marginStart="20dp"
android:src="@drawable/ic_obj_settings_description_24" />
<LinearLayout
android:id="@+id/textContainer"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center_vertical"
android:orientation="vertical"
android:paddingStart="12dp"
android:paddingEnd="12dp">
<TextView
android:id="@+id/tvTitle"
style="@style/TextView.ContentStyle.PreviewTitles.1.Regular"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/description"
tools:text="Icon" />
</LinearLayout>
<TextView
android:id="@+id/descriptionAction"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
style="@style/TextView.ContentStyle.PreviewTitles.1.Regular"
android:textColor="@color/text_secondary"
android:layout_marginEnd="20dp"
/>
</merge>

View file

@ -1,13 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!--typography, buttons 05.04-->
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<ImageView
android:id="@+id/ivIcon"
android:layout_width="44dp"
android:layout_height="44dp"
android:layout_marginStart="16dp"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginTop="14dp"
android:layout_marginBottom="14dp"
android:layout_marginStart="20dp"
tools:src="@drawable/ic_object_menu_icon" />
<LinearLayout
@ -22,19 +23,11 @@
<TextView
android:id="@+id/tvTitle"
style="@style/TextView.UXStyle.Titles.2.Medium"
style="@style/TextView.ContentStyle.PreviewTitles.1.Regular"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:text="Icon" />
<TextView
style="@style/TextView.UXStyle.Captions.1.Regular"
android:textColor="@color/text_secondary"
android:id="@+id/tvSubtitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:text="Emoji or image for object" />
</LinearLayout>
<ImageView
@ -42,7 +35,7 @@
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center_vertical"
android:layout_marginEnd="8dp"
android:src="@drawable/ic_object_menu_arrow_forward" />
android:layout_marginEnd="12dp"
android:src="@drawable/ic_arrow_forward_24" />
</merge>

View file

@ -41,6 +41,9 @@
<color name="dashboard_card_background">#99000000</color>
<color name="dashboard_background">#99252525</color>
<!-- Background -->
<color name="widget_background">#BF252525</color>
<!-- Colors at Figma https://www.figma.com/file/vgXV7x2v20vJajc7clYJ7a/Typography-and-Colors%2FMobile?node-id=1031%3A115 -->
<color name="palette_dark_default">#F3F2EC</color>
<color name="palette_dark_yellow">#E0D56C</color>

View file

@ -79,6 +79,9 @@
<color name="dashboard_card_background">#FFFFFF</color>
<color name="dashboard_background">#99F5F5F5</color>
<!-- Background -->
<color name="widget_background">#FFFFFF</color>
<!-- Colors at Figma https://www.figma.com/file/vgXV7x2v20vJajc7clYJ7a/Typography-and-Colors%2FMobile?node-id=1031%3A160 -->
<color name="palette_dark_default">#252525</color>

View file

@ -2,6 +2,7 @@ package com.anytypeio.anytype.data.auth.repo.block
import com.anytypeio.anytype.core_models.Block
import com.anytypeio.anytype.core_models.Command
import com.anytypeio.anytype.core_models.Command.ObjectTypeConflictingFields
import com.anytypeio.anytype.core_models.Config
import com.anytypeio.anytype.core_models.CreateBlockLinkWithObjectResult
import com.anytypeio.anytype.core_models.CreateObjectResult
@ -307,12 +308,18 @@ class BlockDataRepository(
override suspend fun createSet(
space: Id,
objectType: String?
objectType: String?,
details: Struct?
): CreateObjectSet.Response {
val result = remote.createSet(space = space, objectType = objectType)
val result = remote.createSet(
space = space,
objectType = objectType,
details = details
)
return CreateObjectSet.Response(
target = result.targetId,
payload = result.payload
target = result.objectId,
payload = result.payload,
details = result.details
)
}
@ -1101,4 +1108,16 @@ class BlockDataRepository(
override suspend fun setDeviceNetworkState(type: DeviceNetworkType) {
remote.setDeviceNetworkState(type)
}
override suspend fun objectTypeListConflictingRelations(command: ObjectTypeConflictingFields): List<Id> {
return remote.objectTypeListConflictingRelations(command)
}
override suspend fun objectTypeSetRecommendedHeaderFields(command: Command.ObjectTypeSetRecommendedHeaderFields) {
remote.objectTypeSetRecommendedHeaderFields(command)
}
override suspend fun objectTypeSetRecommendedFields(command: Command.ObjectTypeSetRecommendedFields) {
remote.objectTypeSetRecommendedFields(command)
}
}

View file

@ -2,6 +2,7 @@ package com.anytypeio.anytype.data.auth.repo.block
import com.anytypeio.anytype.core_models.Block
import com.anytypeio.anytype.core_models.Command
import com.anytypeio.anytype.core_models.Command.ObjectTypeConflictingFields
import com.anytypeio.anytype.core_models.Config
import com.anytypeio.anytype.core_models.CreateBlockLinkWithObjectResult
import com.anytypeio.anytype.core_models.CreateObjectResult
@ -106,7 +107,8 @@ interface BlockRemote {
suspend fun createSet(
space: Id,
objectType: String?
objectType: String?,
details: Struct?
): Response.Set.Create
suspend fun setDataViewViewerPosition(
@ -468,4 +470,9 @@ interface BlockRemote {
suspend fun debugAccountSelectTrace(dir: String): String
suspend fun setDeviceNetworkState(type: DeviceNetworkType)
suspend fun objectTypeListConflictingRelations(command: ObjectTypeConflictingFields): List<Id>
suspend fun objectTypeSetRecommendedHeaderFields(command: Command.ObjectTypeSetRecommendedHeaderFields)
suspend fun objectTypeSetRecommendedFields(command: Command.ObjectTypeSetRecommendedFields)
}

View file

@ -2,6 +2,7 @@ package com.anytypeio.anytype.domain.block.interactor.sets
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.core_models.Struct
import com.anytypeio.anytype.domain.base.BaseUseCase
import com.anytypeio.anytype.domain.block.repo.BlockRepository
@ -10,12 +11,17 @@ class CreateObjectSet(
) : BaseUseCase<CreateObjectSet.Response, CreateObjectSet.Params>() {
override suspend fun run(params: Params) = safe {
repo.createSet(space = params.space, objectType = params.type)
repo.createSet(
space = params.space,
objectType = params.type,
details = params.details
)
}
data class Params(
val space: Id,
val type: Id? = null
val type: Id? = null,
val details: Struct? = null
)
/**
@ -23,6 +29,7 @@ class CreateObjectSet(
*/
data class Response(
val target: Id,
val payload: Payload
val payload: Payload,
val details: Struct
)
}

View file

@ -2,6 +2,7 @@ package com.anytypeio.anytype.domain.block.repo
import com.anytypeio.anytype.core_models.Block
import com.anytypeio.anytype.core_models.Command
import com.anytypeio.anytype.core_models.Command.ObjectTypeConflictingFields
import com.anytypeio.anytype.core_models.Config
import com.anytypeio.anytype.core_models.CreateBlockLinkWithObjectResult
import com.anytypeio.anytype.core_models.CreateObjectResult
@ -159,7 +160,8 @@ interface BlockRepository {
suspend fun createSet(
space: Id,
objectType: String? = null
objectType: String? = null,
details: Struct? = null
): CreateObjectSet.Response
suspend fun addRelationToDataView(ctx: Id, dv: Id, relation: Key): Payload
@ -511,4 +513,9 @@ interface BlockRepository {
suspend fun objectDateByTimestamp(command: Command.ObjectDateByTimestamp): Struct?
suspend fun setDeviceNetworkState(type: DeviceNetworkType)
suspend fun objectTypeListConflictingRelations(command: ObjectTypeConflictingFields): List<Id>
suspend fun objectTypeSetRecommendedHeaderFields(command: Command.ObjectTypeSetRecommendedHeaderFields)
suspend fun objectTypeSetRecommendedFields(command: Command.ObjectTypeSetRecommendedFields)
}

View file

@ -8,6 +8,7 @@ import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.event.interactor.EventChannel
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.scan
@ -17,17 +18,38 @@ class ObjectWatcher @Inject constructor(
private val events: EventChannel,
private val reducer: Reducer
) {
/**
* Watches an object and returns a flow of ObjectView.
* If opening the object or observing events fails, the flow will throw an exception.
*/
fun watch(target: Id, space: SpaceId): Flow<ObjectView> = flow {
emitAll(
events.observeEvents(context = target).scan(
initial = repo.openObject(id = target, space = space),
operation = reducer
)
)
// Attempt to open the object. If it fails, throw.
val initialObjectView = try {
repo.openObject(id = target, space = space)
} catch (e: Exception) {
throw RuntimeException("Failed to open object with id=$target in space=$space", e)
}
// Start observing events. If events flow fails at any point, it will throw.
val eventFlow = events.observeEvents(context = target)
.catch { e -> throw RuntimeException("Failed to observe events for object=$target", e) }
emitAll(eventFlow.scan(initialObjectView, reducer))
}.catch { e ->
// Optional: If you want a centralized place to rethrow or log, you can do it here.
throw e
}
/**
* Stops watching an object.
* If closing the page fails, it will throw an exception.
*/
suspend fun unwatch(target: Id, space: SpaceId) {
repo.closePage(id = target, space = space)
try {
repo.closePage(id = target, space = space)
} catch (e: Exception) {
throw RuntimeException("Failed to unwatch object with id=$target in space=$space", e)
}
}
interface Reducer : (ObjectView, List<Event>) -> ObjectView

View file

@ -6,6 +6,10 @@ import com.anytypeio.anytype.core_models.ObjectWrapper
import com.anytypeio.anytype.core_models.Struct
import com.anytypeio.anytype.domain.`object`.amend
import com.anytypeio.anytype.domain.`object`.unset
import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes.TrackedEvent
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
@ -20,6 +24,13 @@ interface StoreOfObjectTypes {
suspend fun set(target: Id, data: Struct)
suspend fun remove(target: Id)
suspend fun clear()
fun trackChanges() : Flow<TrackedEvent>
sealed class TrackedEvent {
object Init : TrackedEvent()
object Change: TrackedEvent()
}
}
class DefaultStoreOfObjectTypes : StoreOfObjectTypes {
@ -27,6 +38,8 @@ class DefaultStoreOfObjectTypes : StoreOfObjectTypes {
private val mutex = Mutex()
private val store = mutableMapOf<Id, ObjectWrapper.Type>()
private val updates = MutableSharedFlow<TrackedEvent>()
override val size: Int get() = store.size
override suspend fun get(id: Id): ObjectWrapper.Type? = mutex.withLock {
@ -50,6 +63,7 @@ class DefaultStoreOfObjectTypes : StoreOfObjectTypes {
store[o.id] = current.amend(o.map)
}
}
updates.emit(TrackedEvent.Change)
}
override suspend fun amend(target: Id, diff: Map<Id, Any?>): Unit = mutex.withLock {
@ -59,6 +73,7 @@ class DefaultStoreOfObjectTypes : StoreOfObjectTypes {
} else {
store[target] = ObjectWrapper.Type(diff)
}
updates.emit(TrackedEvent.Change)
}
override suspend fun set(
@ -66,6 +81,7 @@ class DefaultStoreOfObjectTypes : StoreOfObjectTypes {
data: Map<String, Any?>
): Unit = mutex.withLock {
store[target] = ObjectWrapper.Type(data)
updates.emit(TrackedEvent.Change)
}
override suspend fun unset(
@ -76,6 +92,7 @@ class DefaultStoreOfObjectTypes : StoreOfObjectTypes {
if (current != null) {
store[target] = current.unset(keys)
}
updates.emit(TrackedEvent.Change)
}
override suspend fun remove(target: Id) : Unit = mutex.withLock {
@ -83,9 +100,14 @@ class DefaultStoreOfObjectTypes : StoreOfObjectTypes {
if (current != null) {
store.remove(target)
}
updates.emit(TrackedEvent.Change)
}
override suspend fun clear(): Unit = mutex.withLock {
store.clear()
}
override fun trackChanges(): Flow<TrackedEvent> = updates.onStart {
emit(TrackedEvent.Init)
}
}

View file

@ -132,4 +132,10 @@ class DefaultStoreOfRelations : StoreOfRelations {
override suspend fun observe(): Flow<Map<Id, ObjectWrapper.Relation>> {
return trackChanges().map { store }
}
}
suspend fun StoreOfRelations.getValidRelations(ids: List<Id>): List<ObjectWrapper.Relation> {
return ids.mapNotNull { id ->
getById(id)?.takeIf { it.isValidToUse }
}
}

View file

@ -1,10 +1,10 @@
package com.anytypeio.anytype.domain.primitives
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.Key
import com.anytypeio.anytype.core_models.MAX_SNIPPET_SIZE
import com.anytypeio.anytype.core_models.ObjectType
import com.anytypeio.anytype.core_models.ObjectTypeIds
import com.anytypeio.anytype.core_models.ObjectView
import com.anytypeio.anytype.core_models.ObjectWrapper
import com.anytypeio.anytype.core_models.Relations
import com.anytypeio.anytype.core_models.RelativeDate
@ -12,6 +12,7 @@ import com.anytypeio.anytype.core_models.SupportedLayouts
import com.anytypeio.anytype.core_models.TimeInSeconds
import com.anytypeio.anytype.core_models.primitives.Field
import com.anytypeio.anytype.core_models.primitives.FieldDateValue
import com.anytypeio.anytype.core_models.primitives.ParsedFields
import com.anytypeio.anytype.core_models.primitives.SpaceId
import com.anytypeio.anytype.core_models.primitives.TimestampInSeconds
import com.anytypeio.anytype.core_models.primitives.Value
@ -19,9 +20,12 @@ import com.anytypeio.anytype.domain.base.fold
import com.anytypeio.anytype.domain.debugging.Logger
import com.anytypeio.anytype.domain.misc.DateProvider
import com.anytypeio.anytype.domain.objects.GetDateObjectByTimestamp
import com.anytypeio.anytype.domain.objects.StoreOfRelations
import com.anytypeio.anytype.domain.objects.getValidRelations
import com.anytypeio.anytype.domain.resources.StringResourceProvider
import javax.inject.Inject
import kotlin.collections.contains
import kotlin.collections.plus
interface FieldParser {
fun toDate(any: Any?): Field.Date?
@ -31,11 +35,29 @@ interface FieldParser {
actionSuccess: suspend (ObjectWrapper.Basic) -> Unit,
actionFailure: suspend (Throwable) -> Unit
)
fun getObjectName(objectWrapper: ObjectWrapper.Basic): String
fun getObjectName(objectWrapper: ObjectWrapper.Type): String
fun getObjectTypeIdAndName(
objectWrapper: ObjectWrapper.Basic,
types: List<ObjectWrapper.Type>
): Pair<Id?, String?>
suspend fun getObjectParsedFields(
objectType: ObjectWrapper.Type,
objFieldKeys: List<Key>,
storeOfRelations: StoreOfRelations
): ParsedFields
suspend fun getObjectTypeParsedFields(
objectType: ObjectWrapper.Type,
objectTypeConflictingFieldsIds: List<Id>,
storeOfRelations: StoreOfRelations
): ParsedFields
fun isFieldEditable(relation: ObjectWrapper.Relation): Boolean
fun isFieldCanBeDeletedFromType(field: ObjectWrapper.Relation): Boolean
}
class FieldParserImpl @Inject constructor(
@ -118,13 +140,16 @@ class FieldParserImpl @Inject constructor(
val result = when (objectWrapper.layout) {
ObjectType.Layout.DATE -> {
val relativeDate = dateProvider.calculateRelativeDates(
dateInSeconds = objectWrapper.getSingleValue<Double>(Relations.TIMESTAMP)?.toLong()
dateInSeconds = objectWrapper.getSingleValue<Double>(Relations.TIMESTAMP)
?.toLong()
)
stringResourceProvider.getRelativeDateName(relativeDate)
}
ObjectType.Layout.NOTE -> {
objectWrapper.snippet?.replace("\n", " ")?.take(MAX_SNIPPET_SIZE)
}
in SupportedLayouts.fileLayouts -> {
val fileName = if (objectWrapper.name.isNullOrBlank()) {
stringResourceProvider.getUntitledObjectTitle()
@ -137,6 +162,7 @@ class FieldParserImpl @Inject constructor(
else -> "$fileName.${objectWrapper.fileExt}"
}
}
else -> {
objectWrapper.name
}
@ -148,6 +174,15 @@ class FieldParserImpl @Inject constructor(
}
}
override fun getObjectName(objectWrapper: ObjectWrapper.Type): String {
val name = objectWrapper.name
return if (name.isNullOrBlank()) {
stringResourceProvider.getUntitledObjectTitle()
} else {
name
}
}
override fun getObjectTypeIdAndName(
objectWrapper: ObjectWrapper.Basic,
types: List<ObjectWrapper.Type>
@ -164,4 +199,99 @@ class FieldParserImpl @Inject constructor(
}
}
//endregion
//region Parsed fields
// Consolidated function to build ParsedFields.
private suspend fun getParsedFields(
objType: ObjectWrapper.Type,
localFieldIds: Collection<Id>,
storeOfRelations: StoreOfRelations
): ParsedFields {
val headerFields = storeOfRelations.getValidRelations(
ids = objType.recommendedFeaturedRelations
)
val sidebarFields = storeOfRelations.getValidRelations(
ids = objType.recommendedRelations
)
val hiddenFields = storeOfRelations.getValidRelations(
ids = objType.recommendedHiddenRelations
)
val fileFields = storeOfRelations.getValidRelations(
ids = objType.recommendedFileRelations
)
// Combine IDs from all recommended relations.
val existingIds = (headerFields + sidebarFields + hiddenFields + fileFields)
.map { it.id }
.toSet()
// Filter out fields already present in the recommended groups.
val allLocalFields = storeOfRelations.getValidRelations(
ids = localFieldIds
.filter { it !in existingIds }
.toList()
)
// Partition local fields into system and non-system fields.
val (localSystemFields, localFieldsWithoutSystem) = allLocalFields.partition {
Relations.systemRelationKeys.contains(it.key)
}
return ParsedFields(
header = headerFields,
sidebar = sidebarFields,
hidden = hiddenFields,
localWithoutSystem = localFieldsWithoutSystem,
localSystem = localSystemFields,
file = fileFields
)
}
override suspend fun getObjectParsedFields(
objectType: ObjectWrapper.Type,
objFieldKeys: List<Key>,
storeOfRelations: StoreOfRelations
): ParsedFields {
val localFieldIds = storeOfRelations.getByKeys(
keys = objFieldKeys
).mapNotNull {
if (it.isValidToUse) {
it.id
} else {
null
}
}
return getParsedFields(
objType = objectType,
localFieldIds = localFieldIds,
storeOfRelations = storeOfRelations
)
}
override suspend fun getObjectTypeParsedFields(
objectType: ObjectWrapper.Type,
objectTypeConflictingFieldsIds: List<Id>,
storeOfRelations: StoreOfRelations
): ParsedFields {
return getParsedFields(
objType = objectType,
localFieldIds = objectTypeConflictingFieldsIds,
storeOfRelations = storeOfRelations
)
}
override fun isFieldEditable(relation: ObjectWrapper.Relation): Boolean {
return !(relation.isReadOnly == true ||
relation.isHidden == true ||
relation.isArchived == true ||
relation.isDeleted == true ||
Relations.systemRelationKeys.contains(relation.key))
}
override fun isFieldCanBeDeletedFromType(field: ObjectWrapper.Relation): Boolean {
return !(field.isHidden == true || Relations.systemRelationKeys.contains(field.key))
}
//endregion
}

View file

@ -0,0 +1,27 @@
package com.anytypeio.anytype.domain.primitives
import com.anytypeio.anytype.core_models.Command.ObjectTypeConflictingFields
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
import com.anytypeio.anytype.domain.base.ResultInteractor
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import javax.inject.Inject
class GetObjectTypeConflictingFields @Inject constructor(
private val repo: BlockRepository,
dispatchers: AppCoroutineDispatchers
) : ResultInteractor<GetObjectTypeConflictingFields.Params, List<Id>>(dispatchers.io) {
override suspend fun doWork(params: Params): List<Id> {
val command = ObjectTypeConflictingFields(
spaceId = params.spaceId,
objectTypeId = params.objectTypeId
)
return repo.objectTypeListConflictingRelations(command = command)
}
data class Params(
val spaceId: String,
val objectTypeId: String
)
}

View file

@ -0,0 +1,28 @@
package com.anytypeio.anytype.domain.primitives
import com.anytypeio.anytype.core_models.Command
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
import com.anytypeio.anytype.domain.base.ResultInteractor
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.primitives.SetObjectTypeHeaderRecommendedFields.Params
import javax.inject.Inject
class SetObjectTypeHeaderRecommendedFields @Inject constructor(
private val repo: BlockRepository,
dispatchers: AppCoroutineDispatchers
) : ResultInteractor<Params, Unit>(dispatchers.io) {
override suspend fun doWork(params: Params) {
val command = Command.ObjectTypeSetRecommendedHeaderFields(
objectTypeId = params.objectTypeId,
fields = params.fields
)
return repo.objectTypeSetRecommendedHeaderFields(command = command)
}
data class Params(
val objectTypeId: String,
val fields: List<Id>
)
}

View file

@ -0,0 +1,29 @@
package com.anytypeio.anytype.domain.primitives
import com.anytypeio.anytype.core_models.Command
import com.anytypeio.anytype.core_models.Command.ObjectTypeConflictingFields
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
import com.anytypeio.anytype.domain.base.ResultInteractor
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.primitives.SetObjectTypeRecommendedFields.Params
import javax.inject.Inject
class SetObjectTypeRecommendedFields @Inject constructor(
private val repo: BlockRepository,
dispatchers: AppCoroutineDispatchers
) : ResultInteractor<Params, Unit>(dispatchers.io) {
override suspend fun doWork(params: Params) {
val command = Command.ObjectTypeSetRecommendedFields(
objectTypeId = params.objectTypeId,
fields = params.fields
)
return repo.objectTypeSetRecommendedFields(command = command)
}
data class Params(
val objectTypeId: String,
val fields: List<Id>
)
}

View file

@ -6,4 +6,5 @@ interface StringResourceProvider {
fun getRelativeDateName(relativeDate: RelativeDate): String
fun getDeletedObjectTitle(): String
fun getUntitledObjectTitle(): String
fun getSetOfObjectsTitle(): String
}

View file

@ -114,8 +114,14 @@ class ObjectTypesSubscriptionManager (
Relations.DEFAULT_TEMPLATE_ID,
Relations.SPACE_ID,
Relations.UNIQUE_KEY,
Relations.RESTRICTIONS
),
Relations.RESTRICTIONS,
Relations.TARGET_SPACE_ID,
Relations.TYPE,
Relations.RECOMMENDED_RELATIONS,
Relations.RECOMMENDED_FEATURED_RELATIONS,
Relations.RECOMMENDED_HIDDEN_RELATIONS,
Relations.RECOMMENDED_FILE_RELATIONS,
),
ignoreWorkspace = true
)
}

View file

@ -12,7 +12,6 @@ import com.anytypeio.anytype.core_models.RelationFormat
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.primitives.RelationKey
import com.anytypeio.anytype.core_models.primitives.SpaceId
import com.anytypeio.anytype.core_models.restrictions.ObjectRestriction
import com.anytypeio.anytype.domain.all_content.RestoreAllContentState
@ -20,6 +19,8 @@ import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.domain.primitives.FieldParser
import com.anytypeio.anytype.feature_allcontent.presentation.AllContentViewModel.Companion.DEFAULT_INITIAL_TAB
import com.anytypeio.anytype.presentation.mapper.objectIcon
import com.anytypeio.anytype.presentation.objects.MenuSortsItem
import com.anytypeio.anytype.presentation.objects.ObjectsListSort
import com.anytypeio.anytype.presentation.objects.ObjectIcon
import com.anytypeio.anytype.presentation.objects.getDescriptionOrSnippet
import com.anytypeio.anytype.presentation.objects.getProperType
@ -41,41 +42,6 @@ sealed class AllContentMenuMode {
override val isSelected: Boolean = false
) : AllContentMenuMode()
}
sealed class AllContentSort {
abstract val relationKey: RelationKey
abstract val sortType: DVSortType
abstract val canGroupByDate: Boolean
abstract val isSelected: Boolean
data class ByName(
override val relationKey: RelationKey = RelationKey(Relations.NAME),
override val sortType: DVSortType = DVSortType.ASC,
override val canGroupByDate: Boolean = false,
override val isSelected: Boolean = false
) : AllContentSort()
data class ByDateUpdated(
override val relationKey: RelationKey = RelationKey(Relations.LAST_MODIFIED_DATE),
override val sortType: DVSortType = DVSortType.DESC,
override val canGroupByDate: Boolean = true,
override val isSelected: Boolean = false
) : AllContentSort()
data class ByDateCreated(
override val relationKey: RelationKey = RelationKey(Relations.CREATED_DATE),
override val sortType: DVSortType = DVSortType.DESC,
override val canGroupByDate: Boolean = true,
override val isSelected: Boolean = false
) : AllContentSort()
data class ByDateUsed(
override val relationKey: RelationKey = RelationKey(Relations.LAST_USED_DATE),
override val sortType: DVSortType = DVSortType.DESC,
override val canGroupByDate: Boolean = false,
override val isSelected: Boolean = false
) : AllContentSort()
}
//endregion
//region VIEW STATES
@ -190,18 +156,6 @@ sealed class UiMenuState {
val showBin: Boolean = true
) : UiMenuState()
}
sealed class MenuSortsItem {
data class Container(val sort: AllContentSort) : MenuSortsItem()
data class Sort(val sort: AllContentSort) : MenuSortsItem()
data object Spacer : MenuSortsItem()
data class SortType(
val sort: AllContentSort,
val sortType: DVSortType,
val isSelected: Boolean
) : MenuSortsItem()
}
//endregion
//region BOTTOM_MENU
@ -217,14 +171,14 @@ sealed class UiSnackbarState {
//region MAPPING
fun RestoreAllContentState.Response.Success.mapToSort(): AllContentSort {
fun RestoreAllContentState.Response.Success.mapToSort(): ObjectsListSort {
val sortType = if (isAsc) DVSortType.ASC else DVSortType.DESC
return when (activeSort) {
Relations.CREATED_DATE -> AllContentSort.ByDateCreated(sortType = sortType)
Relations.LAST_MODIFIED_DATE -> AllContentSort.ByDateUpdated(sortType = sortType)
Relations.NAME -> AllContentSort.ByName(sortType = sortType)
Relations.LAST_USED_DATE -> AllContentSort.ByDateUsed(sortType = sortType)
else -> AllContentSort.ByName(sortType = DVSortType.ASC)
Relations.CREATED_DATE -> ObjectsListSort.ByDateCreated(sortType = sortType)
Relations.LAST_MODIFIED_DATE -> ObjectsListSort.ByDateUpdated(sortType = sortType)
Relations.NAME -> ObjectsListSort.ByName(sortType = sortType)
Relations.LAST_USED_DATE -> ObjectsListSort.ByDateUsed(sortType = sortType)
else -> ObjectsListSort.ByName(sortType = DVSortType.ASC)
}
}
@ -302,12 +256,12 @@ fun ObjectWrapper.Basic.toAllContentRelation(
)
}
fun AllContentSort.toAnalyticsSortType(): Pair<String, String> {
fun ObjectsListSort.toAnalyticsSortType(): Pair<String, String> {
return when (this) {
is AllContentSort.ByName -> "Name" to sortType.toAnalyticsSortType()
is AllContentSort.ByDateUpdated -> "Updated" to sortType.toAnalyticsSortType()
is AllContentSort.ByDateCreated -> "Created" to sortType.toAnalyticsSortType()
is AllContentSort.ByDateUsed -> "Used" to sortType.toAnalyticsSortType()
is ObjectsListSort.ByName -> "Name" to sortType.toAnalyticsSortType()
is ObjectsListSort.ByDateUpdated -> "Updated" to sortType.toAnalyticsSortType()
is ObjectsListSort.ByDateCreated -> "Created" to sortType.toAnalyticsSortType()
is ObjectsListSort.ByDateUsed -> "Used" to sortType.toAnalyticsSortType()
}
}

View file

@ -7,10 +7,11 @@ import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.ObjectType
import com.anytypeio.anytype.core_models.ObjectTypeIds
import com.anytypeio.anytype.core_models.ObjectTypeUniqueKeys
import com.anytypeio.anytype.core_models.RelationFormat
import com.anytypeio.anytype.core_models.Relations
import com.anytypeio.anytype.core_models.primitives.SpaceId
import com.anytypeio.anytype.domain.library.StoreSearchParams
import com.anytypeio.anytype.presentation.objects.ObjectsListSort
import com.anytypeio.anytype.presentation.objects.toDVSort
import com.anytypeio.anytype.presentation.search.ObjectSearchConstants.defaultKeys
import com.anytypeio.anytype.presentation.search.ObjectSearchConstants.defaultKeysObjectType
import com.anytypeio.anytype.presentation.search.ObjectSearchConstants.defaultRelationKeys
@ -45,7 +46,7 @@ fun createSubscriptionParams(
spaceId: Id,
activeMode: UiTitleState,
activeTab: AllContentTab,
activeSort: AllContentSort,
activeSort: ObjectsListSort,
limitedObjectIds: List<String>,
limit: Int,
subscriptionId: String
@ -73,7 +74,7 @@ fun createSubscriptionParams(
fun AllContentTab.filtersForSubscribe(
spaces: List<Id>,
activeSort: AllContentSort,
activeSort: ObjectsListSort,
limitedObjectIds: List<Id>,
activeMode: UiTitleState
): Pair<List<DVFilter>, List<DVSort>> {
@ -240,36 +241,4 @@ private fun buildDeletedFilter(): List<DVFilter> {
value = true
)
)
}
fun AllContentSort.toDVSort(): DVSort {
return when (this) {
is AllContentSort.ByDateCreated -> DVSort(
relationKey = relationKey.key,
type = sortType,
relationFormat = RelationFormat.DATE,
includeTime = true,
)
is AllContentSort.ByDateUpdated -> DVSort(
relationKey = relationKey.key,
type = sortType,
relationFormat = RelationFormat.DATE,
includeTime = true,
)
is AllContentSort.ByName -> DVSort(
relationKey = relationKey.key,
type = sortType,
relationFormat = RelationFormat.LONG_TEXT,
includeTime = false
)
is AllContentSort.ByDateUsed -> DVSort(
relationKey = relationKey.key,
type = sortType,
relationFormat = RelationFormat.DATE,
includeTime = true,
)
}
}

View file

@ -30,9 +30,7 @@ import com.anytypeio.anytype.domain.search.SearchObjects
import com.anytypeio.anytype.domain.workspace.RemoveObjectsFromWorkspace
import com.anytypeio.anytype.feature_allcontent.models.AllContentBottomMenu
import com.anytypeio.anytype.feature_allcontent.models.AllContentMenuMode
import com.anytypeio.anytype.feature_allcontent.models.AllContentSort
import com.anytypeio.anytype.feature_allcontent.models.AllContentTab
import com.anytypeio.anytype.feature_allcontent.models.MenuSortsItem
import com.anytypeio.anytype.feature_allcontent.models.UiContentItem
import com.anytypeio.anytype.feature_allcontent.models.UiContentState
import com.anytypeio.anytype.feature_allcontent.models.UiItemsState
@ -63,6 +61,8 @@ import com.anytypeio.anytype.presentation.extension.sendAnalyticsObjectCreateEve
import com.anytypeio.anytype.presentation.home.OpenObjectNavigation
import com.anytypeio.anytype.presentation.home.navigation
import com.anytypeio.anytype.presentation.navigation.NavPanelState
import com.anytypeio.anytype.presentation.objects.MenuSortsItem
import com.anytypeio.anytype.presentation.objects.ObjectsListSort
import com.anytypeio.anytype.presentation.objects.getCreateObjectParams
import java.time.Instant
import java.time.LocalDate
@ -116,7 +116,7 @@ class AllContentViewModel(
) : ViewModel(), AnalyticSpaceHelperDelegate by analyticSpaceHelperDelegate {
private val searchResultIds = MutableStateFlow<List<Id>>(emptyList())
private val sortState = MutableStateFlow<AllContentSort>(AllContentSort.ByName())
private val sortState = MutableStateFlow<ObjectsListSort>(ObjectsListSort.ByName())
val uiTitleState = MutableStateFlow<UiTitleState>(DEFAULT_INITIAL_MODE)
val uiTabsState = MutableStateFlow<UiTabsState>(UiTabsState())
val uiMenuState = MutableStateFlow<UiMenuState>(UiMenuState.Hidden)
@ -279,7 +279,7 @@ class AllContentViewModel(
private suspend fun handleData(
objWrappers: List<ObjectWrapper.Basic>,
activeSort: AllContentSort,
activeSort: ObjectsListSort,
activeTab: AllContentTab
): List<UiContentItem> {
@ -310,7 +310,7 @@ class AllContentViewModel(
private suspend fun mapToUiContentItems(
objectWrappers: List<ObjectWrapper.Basic>,
activeSort: AllContentSort,
activeSort: ObjectsListSort,
activeTab: AllContentTab
): List<UiContentItem> {
val isOwnerOrEditor = permission.value?.isOwnerOrEditor() == true
@ -345,19 +345,19 @@ class AllContentViewModel(
)
}
val result = when (activeSort) {
is AllContentSort.ByDateCreated -> {
is ObjectsListSort.ByDateCreated -> {
groupItemsByDate(items = items, isSortByDateCreated = true, activeSort = activeSort)
}
is AllContentSort.ByDateUpdated -> {
is ObjectsListSort.ByDateUpdated -> {
groupItemsByDate(items = items, isSortByDateCreated = false, activeSort = activeSort)
}
is AllContentSort.ByName -> {
is ObjectsListSort.ByName -> {
items
}
is AllContentSort.ByDateUsed -> {
is ObjectsListSort.ByDateUsed -> {
items
}
}
@ -376,7 +376,7 @@ class AllContentViewModel(
private fun groupItemsByDate(
items: List<UiContentItem.Item>,
isSortByDateCreated: Boolean,
activeSort: AllContentSort
activeSort: ObjectsListSort
): List<UiContentItem> {
val groupedItems = mutableListOf<UiContentItem>()
@ -502,35 +502,35 @@ class AllContentViewModel(
}
}
fun AllContentTab.sorts(activeSort: AllContentSort): List<MenuSortsItem.Sort> {
fun AllContentTab.sorts(activeSort: ObjectsListSort): List<MenuSortsItem.Sort> {
return when (this) {
AllContentTab.TYPES -> {
listOf(
MenuSortsItem.Sort(
sort = AllContentSort.ByName(isSelected = activeSort is AllContentSort.ByName)
sort = ObjectsListSort.ByName(isSelected = activeSort is ObjectsListSort.ByName)
),
MenuSortsItem.Sort(
sort = AllContentSort.ByDateUsed(isSelected = activeSort is AllContentSort.ByDateUsed)
sort = ObjectsListSort.ByDateUsed(isSelected = activeSort is ObjectsListSort.ByDateUsed)
)
)
}
AllContentTab.RELATIONS -> {
listOf(
MenuSortsItem.Sort(
sort = AllContentSort.ByName(isSelected = activeSort is AllContentSort.ByName)
sort = ObjectsListSort.ByName(isSelected = activeSort is ObjectsListSort.ByName)
)
)
}
else -> {
listOf(
MenuSortsItem.Sort(
sort = AllContentSort.ByDateUpdated(isSelected = activeSort is AllContentSort.ByDateUpdated)
sort = ObjectsListSort.ByDateUpdated(isSelected = activeSort is ObjectsListSort.ByDateUpdated)
),
MenuSortsItem.Sort(
sort = AllContentSort.ByDateCreated(isSelected = activeSort is AllContentSort.ByDateCreated)
sort = ObjectsListSort.ByDateCreated(isSelected = activeSort is ObjectsListSort.ByDateCreated)
),
MenuSortsItem.Sort(
sort = AllContentSort.ByName(isSelected = activeSort is AllContentSort.ByName)
sort = ObjectsListSort.ByName(isSelected = activeSort is ObjectsListSort.ByName)
)
)
}
@ -552,12 +552,12 @@ class AllContentViewModel(
private fun AllContentTab.updateInitialState() {
return when (this) {
AllContentTab.TYPES -> {
sortState.value = AllContentSort.ByName()
sortState.value = ObjectsListSort.ByName()
userInput.value = DEFAULT_QUERY
uiTitleState.value = UiTitleState.AllContent
}
AllContentTab.RELATIONS -> {
sortState.value = AllContentSort.ByName()
sortState.value = ObjectsListSort.ByName()
userInput.value = DEFAULT_QUERY
uiTitleState.value = UiTitleState.AllContent
}
@ -598,19 +598,19 @@ class AllContentViewModel(
}
}
fun onSortClicked(sort: AllContentSort) {
fun onSortClicked(sort: ObjectsListSort) {
Timber.d("onSortClicked: $sort")
val newSort = when (sort) {
is AllContentSort.ByDateCreated -> {
is ObjectsListSort.ByDateCreated -> {
sort.copy(isSelected = true)
}
is AllContentSort.ByDateUpdated -> {
is ObjectsListSort.ByDateUpdated -> {
sort.copy(isSelected = true)
}
is AllContentSort.ByName -> {
is ObjectsListSort.ByName -> {
sort.copy(isSelected = true)
}
is AllContentSort.ByDateUsed -> {
is ObjectsListSort.ByDateUsed -> {
sort.copy(isSelected = true)
}
}
@ -628,7 +628,7 @@ class AllContentViewModel(
}
}
private fun proceedWithSortSaving(activeTab: UiTabsState, sort: AllContentSort) {
private fun proceedWithSortSaving(activeTab: UiTabsState, sort: ObjectsListSort) {
if (activeTab.selectedTab == AllContentTab.TYPES
|| activeTab.selectedTab == AllContentTab.RELATIONS
) {

View file

@ -1,109 +1,77 @@
package com.anytypeio.anytype.feature_allcontent.ui
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment.Companion.CenterVertically
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.ColorFilter.Companion.tint
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 androidx.compose.ui.unit.dp
import com.anytypeio.anytype.core_models.DVSortType
import com.anytypeio.anytype.core_ui.common.DefaultPreviews
import com.anytypeio.anytype.core_ui.views.BodyCalloutRegular
import com.anytypeio.anytype.core_ui.views.UXBody
import com.anytypeio.anytype.core_ui.foundation.Divider
import com.anytypeio.anytype.core_ui.lists.objects.menu.ObjectsListMenuItem
import com.anytypeio.anytype.core_ui.lists.objects.menu.ObjectsListSortingMenuContainer
import com.anytypeio.anytype.feature_allcontent.R
import com.anytypeio.anytype.feature_allcontent.models.AllContentMenuMode
import com.anytypeio.anytype.feature_allcontent.models.AllContentSort
import com.anytypeio.anytype.feature_allcontent.models.MenuSortsItem
import com.anytypeio.anytype.feature_allcontent.models.UiMenuState
import com.anytypeio.anytype.presentation.objects.MenuSortsItem
import com.anytypeio.anytype.presentation.objects.ObjectsListSort
@Composable
fun AllContentMenu(
uiMenuState: UiMenuState.Visible,
onModeClick: (AllContentMenuMode) -> Unit,
onSortClick: (AllContentSort) -> Unit,
onSortClick: (ObjectsListSort) -> Unit,
onBinClick: () -> Unit
) {
var sortingExpanded by remember { mutableStateOf(false) }
uiMenuState.mode.forEach { item ->
MenuItem(
ObjectsListMenuItem(
title = getModeTitle(item),
isSelected = item.isSelected,
modifier = Modifier.clickable {
onModeClick(item)
}
)
Divider(0.5.dp)
Divider(
height = 0.5.dp,
paddingStart = 0.dp,
paddingEnd = 0.dp,
color = colorResource(R.color.shape_secondary)
)
}
if (uiMenuState.mode.isNotEmpty()) {
Divider(7.5.dp)
Divider(
height = 7.5.dp,
paddingStart = 0.dp,
paddingEnd = 0.dp,
color = colorResource(R.color.shape_secondary)
)
}
SortingBox(
modifier = Modifier
.clickable {
sortingExpanded = !sortingExpanded
},
subtitle = uiMenuState.container.sort.title(),
isExpanded = sortingExpanded
ObjectsListSortingMenuContainer(
container = uiMenuState.container,
sorts = uiMenuState.sorts,
types = uiMenuState.types,
sortingExpanded = sortingExpanded,
onSortClick = onSortClick,
onChangeSortExpandedState = { sortingExpanded = it }
)
Divider(0.5.dp)
if (sortingExpanded) {
uiMenuState.sorts.forEach { item ->
MenuItem(
title = item.sort.title(),
isSelected = item.sort.isSelected,
modifier = Modifier
.clickable {
onSortClick(item.sort)
}
)
Divider(0.5.dp)
}
Divider(7.5.dp)
uiMenuState.types.forEachIndexed { index, item ->
MenuItem(
title = item.sortType.title(item.sort),
isSelected = item.isSelected,
modifier = Modifier
.clickable {
val updatedSort = when (item.sort) {
is AllContentSort.ByName -> item.sort.copy(sortType = item.sortType)
is AllContentSort.ByDateCreated -> item.sort.copy(sortType = item.sortType)
is AllContentSort.ByDateUpdated -> item.sort.copy(sortType = item.sortType)
is AllContentSort.ByDateUsed -> item.sort.copy(sortType = item.sortType)
}
onSortClick(updatedSort)
}
)
if (index < uiMenuState.types.size - 1) {
Divider(0.5.dp)
}
}
}
if (uiMenuState.showBin && !sortingExpanded) {
Divider(7.5.dp)
MenuItem(
Divider(
height = 7.5.dp,
paddingStart = 0.dp,
paddingEnd = 0.dp,
color = colorResource(R.color.shape_secondary)
)
ObjectsListMenuItem(
title = stringResource(id = R.string.all_content_view_bin),
isSelected = false,
modifier = Modifier.clickable { onBinClick() }
@ -111,83 +79,6 @@ fun AllContentMenu(
}
}
@Composable
private fun Divider(height: Dp) {
Box(
modifier = Modifier
.fillMaxWidth()
.height(height)
.background(colorResource(id = R.color.shape_secondary))
)
}
@Composable
private fun SortingBox(modifier: Modifier, subtitle: String, isExpanded: Boolean) {
val rotationAngle = if (isExpanded) 90f else 0f
Row(
modifier = modifier
.fillMaxWidth()
.background(colorResource(id = R.color.background_secondary)),
verticalAlignment = CenterVertically
) {
Image(
modifier = Modifier
.padding(start = 10.dp)
.size(18.dp)
.rotate(rotationAngle),
painter = painterResource(R.drawable.ic_arrow_disclosure_18),
contentDescription = "",
colorFilter = tint(colorResource(id = R.color.glyph_selected))
)
Column(
modifier = Modifier
.wrapContentHeight()
.padding(top = 11.dp, bottom = 10.dp, start = 6.dp)
) {
Text(
text = stringResource(id = R.string.all_content_sort_by),
modifier = Modifier.wrapContentSize(),
style = UXBody,
color = colorResource(id = R.color.text_primary)
)
Text(
text = subtitle,
modifier = Modifier.wrapContentSize(),
style = BodyCalloutRegular,
color = colorResource(id = R.color.text_secondary)
)
}
}
}
@Composable
private fun MenuItem(modifier: Modifier, title: String, isSelected: Boolean) {
Row(
modifier = modifier
.fillMaxWidth()
.height(44.dp)
.background(colorResource(id = R.color.background_secondary)),
verticalAlignment = CenterVertically,
) {
Image(
modifier = Modifier
.wrapContentSize()
.padding(start = 12.dp),
painter = painterResource(R.drawable.ic_check_16),
contentDescription = "All Content mode selected",
alpha = if (isSelected) 1f else 0f
)
Text(
text = title,
modifier = Modifier
.wrapContentSize()
.padding(start = 8.dp),
style = UXBody,
color = colorResource(id = R.color.text_primary)
)
}
}
//region RESOURCES
@Composable
private fun getModeTitle(mode: AllContentMenuMode): String = stringResource(
@ -196,43 +87,6 @@ private fun getModeTitle(mode: AllContentMenuMode): String = stringResource(
is AllContentMenuMode.Unlinked -> R.string.all_content_title_only_unlinked
}
)
@Composable
private fun AllContentSort.title(): String = stringResource(
when (this) {
is AllContentSort.ByDateCreated -> R.string.all_content_sort_date_created
is AllContentSort.ByDateUpdated -> R.string.all_content_sort_date_updated
is AllContentSort.ByName -> R.string.all_content_sort_name
is AllContentSort.ByDateUsed -> R.string.all_content_sort_date_used
}
)
@Composable
private fun DVSortType.title(sort: AllContentSort): String = when (this) {
DVSortType.ASC -> {
when (sort) {
is AllContentSort.ByDateCreated, is AllContentSort.ByDateUpdated, is AllContentSort.ByDateUsed -> stringResource(
id = R.string.all_content_sort_date_asc
)
is AllContentSort.ByName -> stringResource(id = R.string.all_content_sort_name_asc)
}
}
DVSortType.DESC -> {
when (sort) {
is AllContentSort.ByDateCreated,
is AllContentSort.ByDateUpdated,
is AllContentSort.ByDateUsed -> stringResource(
id = R.string.all_content_sort_date_desc
)
is AllContentSort.ByName -> stringResource(id = R.string.all_content_sort_name_desc)
}
}
DVSortType.CUSTOM -> ""
}
//endregion
//region PREVIEW
@ -247,28 +101,28 @@ fun AllContentMenuPreview() {
),
sorts = listOf(
MenuSortsItem.Sort(
sort = AllContentSort.ByName(isSelected = true)
sort = ObjectsListSort.ByName(isSelected = true)
),
MenuSortsItem.Sort(
AllContentSort.ByDateUpdated(isSelected = false)
ObjectsListSort.ByDateUpdated(isSelected = false)
),
MenuSortsItem.Sort(
AllContentSort.ByDateCreated(isSelected = false)
ObjectsListSort.ByDateCreated(isSelected = false)
)
),
types = listOf(
MenuSortsItem.SortType(
sortType = DVSortType.ASC,
isSelected = true,
sort = AllContentSort.ByName(isSelected = true)
sort = ObjectsListSort.ByName(isSelected = true)
),
MenuSortsItem.SortType(
sortType = DVSortType.DESC,
isSelected = false,
sort = AllContentSort.ByName(isSelected = false)
sort = ObjectsListSort.ByName(isSelected = false)
),
),
container = MenuSortsItem.Container(AllContentSort.ByName())
container = MenuSortsItem.Container(ObjectsListSort.ByName())
),
onModeClick = {},
onSortClick = {},

View file

@ -70,6 +70,7 @@ import com.anytypeio.anytype.core_models.primitives.SpaceId
import com.anytypeio.anytype.core_ui.common.DefaultPreviews
import com.anytypeio.anytype.core_ui.extensions.simpleIcon
import com.anytypeio.anytype.core_ui.extensions.swapList
import com.anytypeio.anytype.core_ui.foundation.DefaultSearchBar
import com.anytypeio.anytype.core_ui.foundation.DismissBackground
import com.anytypeio.anytype.core_ui.foundation.Divider
import com.anytypeio.anytype.core_ui.foundation.components.BottomNavigationMenu
@ -90,7 +91,6 @@ import com.anytypeio.anytype.feature_allcontent.BuildConfig
import com.anytypeio.anytype.feature_allcontent.R
import com.anytypeio.anytype.feature_allcontent.models.AllContentBottomMenu
import com.anytypeio.anytype.feature_allcontent.models.AllContentMenuMode
import com.anytypeio.anytype.feature_allcontent.models.AllContentSort
import com.anytypeio.anytype.feature_allcontent.models.AllContentTab
import com.anytypeio.anytype.feature_allcontent.models.UiContentItem
import com.anytypeio.anytype.feature_allcontent.models.UiContentState
@ -99,6 +99,7 @@ import com.anytypeio.anytype.feature_allcontent.models.UiMenuState
import com.anytypeio.anytype.feature_allcontent.models.UiSnackbarState
import com.anytypeio.anytype.feature_allcontent.models.UiTabsState
import com.anytypeio.anytype.feature_allcontent.models.UiTitleState
import com.anytypeio.anytype.presentation.objects.ObjectsListSort
import com.anytypeio.anytype.presentation.navigation.NavPanelState
import com.anytypeio.anytype.presentation.objects.ObjectIcon
import kotlinx.coroutines.CoroutineScope
@ -117,7 +118,7 @@ fun AllContentWrapperScreen(
onTabClick: (AllContentTab) -> Unit,
onQueryChanged: (String) -> Unit,
onModeClick: (AllContentMenuMode) -> Unit,
onSortClick: (AllContentSort) -> Unit,
onSortClick: (ObjectsListSort) -> Unit,
onItemClicked: (UiContentItem.Item) -> Unit,
onTypeClicked: (UiContentItem) -> Unit,
onRelationClicked: (UiContentItem) -> Unit,
@ -180,7 +181,7 @@ fun AllContentMainScreen(
onTabClick: (AllContentTab) -> Unit,
onQueryChanged: (String) -> Unit,
onModeClick: (AllContentMenuMode) -> Unit,
onSortClick: (AllContentSort) -> Unit,
onSortClick: (ObjectsListSort) -> Unit,
onItemClicked: (UiContentItem.Item) -> Unit,
onTypeClicked: (UiContentItem) -> Unit,
onRelationClicked: (UiContentItem) -> Unit,
@ -267,10 +268,14 @@ fun AllContentMainScreen(
onTabClick(tab)
}
Spacer(modifier = Modifier.size(10.dp))
AllContentSearchBar(onQueryChanged = {
isSearchEmpty = it.isEmpty()
onQueryChanged(it)
})
DefaultSearchBar(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
onQueryChanged = {
isSearchEmpty = it.isEmpty()
onQueryChanged(it)
})
Spacer(modifier = Modifier.size(10.dp))
Divider(paddingStart = 0.dp, paddingEnd = 0.dp)
}

View file

@ -1,17 +1,9 @@
package com.anytypeio.anytype.feature_allcontent.ui
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
@ -22,54 +14,38 @@ import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.selection.LocalTextSelectionColors
import androidx.compose.foundation.text.selection.TextSelectionColors
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.IconButton
import androidx.compose.material.TextFieldDefaults
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.dp
import com.anytypeio.anytype.core_models.DVSortType
import com.anytypeio.anytype.core_ui.common.DefaultPreviews
import com.anytypeio.anytype.core_ui.extensions.bouncingClickable
import com.anytypeio.anytype.core_ui.foundation.noRippleClickable
import com.anytypeio.anytype.core_ui.foundation.noRippleThrottledClickable
import com.anytypeio.anytype.core_ui.views.BodyRegular
import com.anytypeio.anytype.core_ui.views.Title1
import com.anytypeio.anytype.core_ui.views.Title2
import com.anytypeio.anytype.feature_allcontent.R
import com.anytypeio.anytype.feature_allcontent.models.AllContentMenuMode
import com.anytypeio.anytype.feature_allcontent.models.AllContentSort
import com.anytypeio.anytype.feature_allcontent.models.AllContentTab
import com.anytypeio.anytype.feature_allcontent.models.MenuSortsItem
import com.anytypeio.anytype.feature_allcontent.models.UiMenuState
import com.anytypeio.anytype.feature_allcontent.models.UiTabsState
import com.anytypeio.anytype.feature_allcontent.models.UiTitleState
import com.anytypeio.anytype.presentation.objects.MenuSortsItem
import com.anytypeio.anytype.presentation.objects.ObjectsListSort
//region AllContentTopBarContainer
@OptIn(ExperimentalMaterial3Api::class)
@ -78,7 +54,7 @@ fun AllContentTopBarContainer(
titleState: UiTitleState,
uiMenuState: UiMenuState,
onModeClick: (AllContentMenuMode) -> Unit,
onSortClick: (AllContentSort) -> Unit,
onSortClick: (ObjectsListSort) -> Unit,
onBinClick: () -> Unit,
onBackClick: () -> Unit
) {
@ -139,21 +115,21 @@ private fun AllContentTopBarContainerPreview() {
AllContentMenuMode.Unlinked()
),
container = MenuSortsItem.Container(
sort = AllContentSort.ByName(isSelected = true)
sort = ObjectsListSort.ByName(isSelected = true)
),
sorts = listOf(
MenuSortsItem.Sort(
sort = AllContentSort.ByName(isSelected = true)
sort = ObjectsListSort.ByName(isSelected = true)
),
),
types = listOf(
MenuSortsItem.SortType(
sort = AllContentSort.ByName(isSelected = true),
sort = ObjectsListSort.ByName(isSelected = true),
sortType = DVSortType.DESC,
isSelected = true
),
MenuSortsItem.SortType(
sort = AllContentSort.ByDateCreated(isSelected = false),
sort = ObjectsListSort.ByDateCreated(isSelected = false),
sortType = DVSortType.ASC,
isSelected = false
),
@ -302,122 +278,4 @@ private fun AllContentTabsPreview() {
)
}
//endregion
//region SearchBar
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun AllContentSearchBar(onQueryChanged: (String) -> Unit) {
val interactionSource = remember { MutableInteractionSource() }
val focus = LocalFocusManager.current
val focusRequester = FocusRequester()
val selectionColors = TextSelectionColors(
backgroundColor = colorResource(id = R.color.cursor_color).copy(
alpha = 0.2f
),
handleColor = colorResource(id = R.color.cursor_color),
)
var query by remember { mutableStateOf(TextFieldValue()) }
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
.background(
color = colorResource(id = R.color.shape_transparent),
shape = RoundedCornerShape(10.dp)
)
.height(40.dp),
verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(id = R.drawable.ic_search_18),
contentDescription = "Search icon",
modifier = Modifier
.align(Alignment.CenterVertically)
.padding(
start = 11.dp
)
)
CompositionLocalProvider(value = LocalTextSelectionColors provides selectionColors) {
BasicTextField(
value = query,
modifier = Modifier
.weight(1.0f)
.padding(start = 6.dp)
.align(Alignment.CenterVertically)
.focusRequester(focusRequester),
textStyle = BodyRegular.copy(
color = colorResource(id = R.color.text_primary)
),
onValueChange = { input ->
query = input.also {
onQueryChanged(input.text)
}
},
singleLine = true,
maxLines = 1,
keyboardActions = KeyboardActions(
onDone = {
focus.clearFocus(true)
}
),
decorationBox = @Composable { innerTextField ->
TextFieldDefaults.OutlinedTextFieldDecorationBox(
value = query.text,
innerTextField = innerTextField,
enabled = true,
singleLine = true,
visualTransformation = VisualTransformation.None,
interactionSource = interactionSource,
placeholder = {
Text(
text = stringResource(id = R.string.search),
style = BodyRegular.copy(
color = colorResource(id = R.color.text_tertiary)
)
)
},
colors = TextFieldDefaults.textFieldColors(
backgroundColor = Color.Transparent,
cursorColor = colorResource(id = R.color.cursor_color),
),
border = {},
contentPadding = PaddingValues()
)
},
cursorBrush = SolidColor(colorResource(id = R.color.palette_system_blue)),
)
}
Spacer(Modifier.width(9.dp))
AnimatedVisibility(
visible = query.text.isNotEmpty(),
enter = fadeIn(tween(100)),
exit = fadeOut(tween(100))
) {
Image(
painter = painterResource(id = R.drawable.ic_clear_18),
contentDescription = "Clear icon",
modifier = Modifier
.padding(end = 9.dp)
.noRippleClickable {
query = TextFieldValue().also {
onQueryChanged("")
}
}
)
}
}
}
@DefaultPreviews
@Composable
private fun AllContentSearchBarPreview() {
AllContentSearchBar() {}
}
//endregion

View file

@ -1,20 +1,9 @@
package com.anytypeio.anytype.feature_date.mapping
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.RelationListWithValueItem
import com.anytypeio.anytype.core_models.Relations
import com.anytypeio.anytype.core_models.primitives.SpaceId
import com.anytypeio.anytype.core_models.restrictions.ObjectRestriction
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.domain.objects.StoreOfRelations
import com.anytypeio.anytype.domain.primitives.FieldParser
import com.anytypeio.anytype.feature_date.viewmodel.UiFieldsItem
import com.anytypeio.anytype.feature_date.viewmodel.UiObjectsListItem
import com.anytypeio.anytype.presentation.mapper.objectIcon
import com.anytypeio.anytype.presentation.objects.getProperType
import timber.log.Timber
suspend fun List<RelationListWithValueItem>.toUiFieldsItem(
@ -52,33 +41,4 @@ suspend fun List<RelationListWithValueItem>.toUiFieldsItem(
)
}
}
}
fun ObjectWrapper.Basic.toUiObjectsListItem(
space: SpaceId,
urlBuilder: UrlBuilder,
objectTypes: List<ObjectWrapper.Type>,
fieldParser: FieldParser,
isOwnerOrEditor: Boolean
): UiObjectsListItem {
val obj = this
val typeUrl = obj.getProperType()
val isProfile = typeUrl == MarketplaceObjectTypeIds.PROFILE
val layout = obj.layout ?: ObjectType.Layout.BASIC
return UiObjectsListItem.Item(
id = obj.id,
space = space,
name = fieldParser.getObjectName(obj),
type = typeUrl,
typeName = objectTypes.firstOrNull { type ->
if (isProfile) {
type.uniqueKey == ObjectTypeUniqueKeys.PROFILE
} else {
type.id == typeUrl
}
}?.name,
layout = layout,
icon = obj.objectIcon(builder = urlBuilder),
isPossibleToDelete = isOwnerOrEditor && !restrictions.contains(ObjectRestriction.DELETE)
)
}

View file

@ -24,7 +24,6 @@ import androidx.compose.material3.SnackbarResult
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.colorResource
@ -32,18 +31,19 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_ui.foundation.components.BottomNavigationMenu
import com.anytypeio.anytype.core_ui.lists.objects.PaginatedObjectList
import com.anytypeio.anytype.core_ui.lists.objects.UiContentState
import com.anytypeio.anytype.core_ui.lists.objects.UiObjectsListState
import com.anytypeio.anytype.core_ui.syncstatus.SpaceSyncStatusScreen
import com.anytypeio.anytype.core_utils.insets.EDGE_TO_EDGE_MIN_SDK
import com.anytypeio.anytype.feature_date.R
import com.anytypeio.anytype.feature_date.ui.models.DateEvent
import com.anytypeio.anytype.feature_date.viewmodel.UiCalendarIconState
import com.anytypeio.anytype.feature_date.viewmodel.UiCalendarState
import com.anytypeio.anytype.feature_date.viewmodel.UiContentState
import com.anytypeio.anytype.feature_date.viewmodel.UiFieldsSheetState
import com.anytypeio.anytype.feature_date.viewmodel.UiFieldsState
import com.anytypeio.anytype.feature_date.viewmodel.UiHeaderState
import com.anytypeio.anytype.feature_date.viewmodel.UiNavigationWidget
import com.anytypeio.anytype.feature_date.viewmodel.UiObjectsListState
import com.anytypeio.anytype.feature_date.viewmodel.UiSnackbarState
import com.anytypeio.anytype.feature_date.viewmodel.UiSyncStatusBadgeState
import com.anytypeio.anytype.presentation.sync.SyncStatusWidgetState
@ -68,8 +68,6 @@ fun DateMainScreen(
onDateEvent: (DateEvent) -> Unit
) {
val scope = rememberCoroutineScope()
val snackBarHostState = remember { SnackbarHostState() }
val snackBarText = stringResource(R.string.all_content_snackbar_title)
@ -156,11 +154,19 @@ fun DateMainScreen(
if (uiContentState is UiContentState.Empty) {
EmptyScreen()
}
ObjectsScreen(
PaginatedObjectList(
state = uiObjectsListState,
uiState = uiContentState,
canPaginate = canPaginate,
onDateEvent = onDateEvent,
onLoadMore = {
DateEvent.ObjectsList.OnLoadMore
},
onMoveToBin = { item ->
DateEvent.ObjectsList.OnObjectMoveToBin(item)
},
onObjectClicked = { item ->
DateEvent.ObjectsList.OnObjectClicked(item)
}
)
BottomNavigationMenu(
modifier = Modifier

View file

@ -1,337 +0,0 @@
package com.anytypeio.anytype.feature_date.ui
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
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.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.Text
import androidx.compose.material3.ListItem
import androidx.compose.material3.ListItemDefaults
import androidx.compose.material3.SwipeToDismissBox
import androidx.compose.material3.SwipeToDismissBoxValue
import androidx.compose.material3.rememberSwipeToDismissBoxState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.anytypeio.anytype.core_ui.common.DefaultPreviews
import com.anytypeio.anytype.core_ui.common.ShimmerEffect
import com.anytypeio.anytype.core_ui.extensions.swapList
import com.anytypeio.anytype.core_ui.foundation.DismissBackground
import com.anytypeio.anytype.core_ui.foundation.Divider
import com.anytypeio.anytype.core_ui.foundation.noRippleThrottledClickable
import com.anytypeio.anytype.core_ui.views.ButtonSize
import com.anytypeio.anytype.core_ui.views.PreviewTitle2Regular
import com.anytypeio.anytype.core_ui.views.Relations3
import com.anytypeio.anytype.core_ui.views.animations.DotsLoadingIndicator
import com.anytypeio.anytype.core_ui.views.animations.FadeAnimationSpecs
import com.anytypeio.anytype.core_ui.widgets.ListWidgetObjectIcon
import com.anytypeio.anytype.feature_date.R
import com.anytypeio.anytype.feature_date.ui.models.DateEvent
import com.anytypeio.anytype.feature_date.ui.models.StubVerticalItems
import com.anytypeio.anytype.feature_date.viewmodel.UiContentState
import com.anytypeio.anytype.feature_date.viewmodel.UiObjectsListItem
import com.anytypeio.anytype.feature_date.viewmodel.UiObjectsListState
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@Composable
fun ObjectsScreen(
state: UiObjectsListState,
uiState: UiContentState,
canPaginate: Boolean,
onDateEvent: (DateEvent) -> Unit
) {
val items = remember { mutableStateListOf<UiObjectsListItem>() }
items.swapList(state.items)
val scope = rememberCoroutineScope()
val lazyListState = rememberLazyListState()
val canPaginateState = remember { mutableStateOf(false) }
LaunchedEffect(key1 = canPaginate) {
canPaginateState.value = canPaginate
}
val shouldStartPaging = remember {
derivedStateOf {
canPaginateState.value && (lazyListState.layoutInfo.visibleItemsInfo.lastOrNull()?.index
?: -9) >= (lazyListState.layoutInfo.totalItemsCount - 2)
}
}
LaunchedEffect(key1 = shouldStartPaging.value) {
if (shouldStartPaging.value && uiState is UiContentState.Idle) {
onDateEvent(DateEvent.ObjectsList.OnLoadMore)
}
}
LazyColumn(
modifier = Modifier
.padding(top = 8.dp)
.fillMaxSize(),
state = lazyListState
) {
items(
count = items.size,
key = { index -> items[index].id },
contentType = { index ->
when (items[index]) {
is UiObjectsListItem.Loading -> "loading"
is UiObjectsListItem.Item -> "item"
}
}
) { index ->
val item = items[index]
when (item) {
is UiObjectsListItem.Item -> {
SwipeToDismissListItems(
modifier = Modifier
.fillMaxWidth()
.animateItem()
.noRippleThrottledClickable {
onDateEvent(DateEvent.ObjectsList.OnObjectClicked(item))
},
item = item,
onDateEvent = onDateEvent
)
Divider(paddingStart = 16.dp, paddingEnd = 16.dp)
}
is UiObjectsListItem.Loading -> {
ListItemLoading(modifier = Modifier)
}
}
}
if (uiState is UiContentState.Paging) {
item {
Box(
modifier = Modifier
.fillParentMaxWidth()
.height(52.dp),
contentAlignment = Alignment.Center
) {
LoadingState()
}
}
}
item {
Spacer(modifier = Modifier.height(200.dp))
}
}
LaunchedEffect(key1 = uiState) {
if (uiState is UiContentState.Idle) {
if (uiState.scrollToTop) {
scope.launch {
lazyListState.scrollToItem(0)
}
}
}
}
}
@Composable
private fun ListItem(
modifier: Modifier,
item: UiObjectsListItem.Item
) {
val name = item.name.trim().ifBlank { stringResource(R.string.untitled) }
val createdBy = item.createdBy
val typeName = item.typeName
ListItem(
colors = ListItemDefaults.colors(
containerColor = colorResource(id = R.color.background_primary),
),
modifier = modifier
.height(72.dp)
.fillMaxWidth(),
headlineContent = {
Text(
text = name,
style = PreviewTitle2Regular,
color = colorResource(id = R.color.text_primary),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
},
supportingContent = {
Row {
if (typeName != null) {
Text(
text = typeName,
style = Relations3,
color = colorResource(id = R.color.text_secondary),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
if (!createdBy.isNullOrBlank()) {
Text(
text = "${stringResource(R.string.date_layout_item_created_by)}$createdBy",
style = Relations3,
color = colorResource(id = R.color.text_secondary),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}
},
leadingContent = {
ListWidgetObjectIcon(icon = item.icon, modifier = Modifier, iconSize = 48.dp)
}
)
}
@Composable
private fun ListItemLoading(
modifier: Modifier
) {
ListItem(
colors = ListItemDefaults.colors(
containerColor = colorResource(id = R.color.background_primary),
),
modifier = modifier
.height(72.dp)
.fillMaxWidth(),
headlineContent = {
ShimmerEffect(
modifier = Modifier
.width(164.dp)
.height(18.dp)
)
},
supportingContent = {
ShimmerEffect(
modifier = Modifier
.width(64.dp)
.height(13.dp)
)
},
leadingContent = {
ShimmerEffect(
modifier = Modifier
.size(48.dp)
)
}
)
}
@Composable
fun SwipeToDismissListItems(
item: UiObjectsListItem.Item,
modifier: Modifier,
animationDuration: Int = 500,
onDateEvent: (DateEvent) -> Unit,
) {
var isRemoved by remember { mutableStateOf(false) }
val dismissState = rememberSwipeToDismissBoxState(
initialValue = SwipeToDismissBoxValue.Settled,
confirmValueChange = { value ->
if (value == SwipeToDismissBoxValue.EndToStart) {
isRemoved = true
true
} else {
false
}
return@rememberSwipeToDismissBoxState true
},
positionalThreshold = { it * .5f }
)
if (dismissState.currentValue != SwipeToDismissBoxValue.Settled) {
LaunchedEffect(Unit) {
dismissState.snapTo(SwipeToDismissBoxValue.Settled)
}
}
LaunchedEffect(key1 = isRemoved) {
if (isRemoved) {
delay(animationDuration.toLong())
onDateEvent(DateEvent.ObjectsList.OnObjectMoveToBin(item))
}
}
AnimatedVisibility(
visible = !isRemoved,
exit = shrinkVertically(
animationSpec = tween(durationMillis = animationDuration),
shrinkTowards = Alignment.Top
) + fadeOut()
) {
SwipeToDismissBox(
modifier = modifier,
state = dismissState,
enableDismissFromEndToStart = item.isPossibleToDelete,
enableDismissFromStartToEnd = false,
backgroundContent = {
DismissBackground(
actionText = stringResource(R.string.move_to_bin),
dismissState = dismissState
)
},
content = {
ListItem(
modifier = Modifier
.noRippleThrottledClickable {
onDateEvent(DateEvent.ObjectsList.OnObjectClicked(item))
},
item = item
)
}
)
}
}
@Composable
private fun BoxScope.LoadingState() {
val loadingAlpha by animateFloatAsState(targetValue = 1f, label = "")
DotsLoadingIndicator(
animating = true,
modifier = Modifier
.graphicsLayer { alpha = loadingAlpha }
.align(Alignment.Center),
animationSpecs = FadeAnimationSpecs(itemCount = 3),
color = colorResource(id = R.color.glyph_active),
size = ButtonSize.Small
)
}
@Composable
@DefaultPreviews
fun ObjectsListScreenPreview() {
val contentListState = UiObjectsListState(
items = StubVerticalItems
)
ObjectsScreen(
state = contentListState,
uiState = UiContentState.Idle(scrollToTop = false),
canPaginate = true,
onDateEvent = {}
)
}

View file

@ -4,7 +4,7 @@ import com.anytypeio.anytype.core_models.TimeInMillis
import com.anytypeio.anytype.core_models.multiplayer.SpaceSyncAndP2PStatusState
import com.anytypeio.anytype.core_models.primitives.TimestampInSeconds
import com.anytypeio.anytype.feature_date.viewmodel.UiFieldsItem
import com.anytypeio.anytype.feature_date.viewmodel.UiObjectsListItem
import com.anytypeio.anytype.presentation.objects.UiObjectsListItem
sealed class DateEvent {

View file

@ -1,49 +1,9 @@
package com.anytypeio.anytype.feature_date.ui.models
import com.anytypeio.anytype.core_models.ObjectType
import com.anytypeio.anytype.core_models.RelationFormat
import com.anytypeio.anytype.core_models.Relations
import com.anytypeio.anytype.core_models.primitives.RelationKey
import com.anytypeio.anytype.core_models.primitives.SpaceId
import com.anytypeio.anytype.feature_date.viewmodel.UiFieldsItem
import com.anytypeio.anytype.feature_date.viewmodel.UiObjectsListItem
import com.anytypeio.anytype.presentation.objects.ObjectIcon
val StubVerticalItems = listOf(
UiObjectsListItem.Item(
id = "1",
name = "Task Object",
space = SpaceId("space1"),
type = "type1",
typeName = "Task",
createdBy = "by Joseph Wolf",
layout = ObjectType.Layout.TODO,
icon = ObjectIcon.Task(isChecked = true)
),
UiObjectsListItem.Item(
id = "2",
name = "Page Object",
space = SpaceId("space2"),
type = "type2",
typeName = "Page",
createdBy = "by Mike Long",
layout = ObjectType.Layout.BASIC,
icon = ObjectIcon.Empty.Page
),
UiObjectsListItem.Item(
id = "3",
name = "File Object",
space = SpaceId("space3"),
type = "type3",
typeName = "File",
createdBy = "by John Doe",
layout = ObjectType.Layout.FILE,
icon = ObjectIcon.File(
mime = "image/png",
fileName = "test_image.png"
)
)
)
val StubHorizontalItems = listOf(
UiFieldsItem.Settings(),

View file

@ -2,7 +2,6 @@ package com.anytypeio.anytype.feature_date.viewmodel
import com.anytypeio.anytype.core_models.DVSortType
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.ObjectType
import com.anytypeio.anytype.core_models.RelationFormat
import com.anytypeio.anytype.core_models.RelativeDate
import com.anytypeio.anytype.core_models.TimeInMillis
@ -11,7 +10,6 @@ import com.anytypeio.anytype.core_models.primitives.RelationKey
import com.anytypeio.anytype.core_models.primitives.SpaceId
import com.anytypeio.anytype.core_models.primitives.TimestampInSeconds
import com.anytypeio.anytype.feature_date.viewmodel.UiFieldsItem.Loading
import com.anytypeio.anytype.presentation.objects.ObjectIcon
data class DateObjectVmParams(
val objectId: Id,
@ -100,55 +98,12 @@ sealed class UiFieldsItem {
}
}
data class UiObjectsListState(
val items: List<UiObjectsListItem>
) {
companion object {
val Empty = UiObjectsListState(items = emptyList())
val LoadingState = UiObjectsListState(
items = listOf(
UiObjectsListItem.Loading("Loading-Item-1"),
UiObjectsListItem.Loading("Loading-Item-2"),
UiObjectsListItem.Loading("Loading-Item-3"),
UiObjectsListItem.Loading("Loading-Item-4"),
)
)
}
}
sealed class UiObjectsListItem {
abstract val id: String
data class Loading(override val id: String) : UiObjectsListItem()
data class Item(
override val id: String,
val name: String,
val space: SpaceId,
val type: String? = null,
val typeName: String? = null,
val createdBy: String? = null,
val layout: ObjectType.Layout? = null,
val icon: ObjectIcon = ObjectIcon.None,
val isPossibleToDelete: Boolean = false
) : UiObjectsListItem()
}
sealed class UiNavigationWidget {
data object Hidden : UiNavigationWidget()
data object Editor : UiNavigationWidget()
data object Viewer : UiNavigationWidget()
}
sealed class UiContentState {
data class Idle(val scrollToTop: Boolean = false) : UiContentState()
data object InitLoading : UiContentState()
data object Paging : UiContentState()
data object Empty : UiContentState()
}
sealed class UiFieldsSheetState {
data object Hidden : UiFieldsSheetState()
data class Visible(

View file

@ -14,6 +14,9 @@ import com.anytypeio.anytype.core_models.TimeInSeconds
import com.anytypeio.anytype.core_models.getSingleValue
import com.anytypeio.anytype.core_models.primitives.SpaceId
import com.anytypeio.anytype.core_models.primitives.TimestampInSeconds
import com.anytypeio.anytype.core_ui.lists.objects.UiContentState
import com.anytypeio.anytype.core_ui.lists.objects.UiContentState.Idle
import com.anytypeio.anytype.core_ui.lists.objects.UiObjectsListState
import com.anytypeio.anytype.domain.base.fold
import com.anytypeio.anytype.domain.event.interactor.SpaceSyncAndP2PStatusProvider
import com.anytypeio.anytype.domain.library.StoreSearchParams
@ -29,11 +32,9 @@ import com.anytypeio.anytype.domain.objects.StoreOfRelations
import com.anytypeio.anytype.domain.page.CreateObject
import com.anytypeio.anytype.domain.primitives.FieldParser
import com.anytypeio.anytype.domain.relations.GetObjectRelationListById
import com.anytypeio.anytype.feature_date.viewmodel.UiErrorState.Reason
import com.anytypeio.anytype.feature_date.mapping.toUiFieldsItem
import com.anytypeio.anytype.feature_date.mapping.toUiObjectsListItem
import com.anytypeio.anytype.feature_date.ui.models.DateEvent
import com.anytypeio.anytype.feature_date.viewmodel.UiContentState.*
import com.anytypeio.anytype.feature_date.viewmodel.UiErrorState.Reason
import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate
import com.anytypeio.anytype.presentation.extension.sendAnalyticsClickDateBack
import com.anytypeio.anytype.presentation.extension.sendAnalyticsClickDateCalendarView
@ -44,7 +45,9 @@ import com.anytypeio.anytype.presentation.extension.sendAnalyticsScreenDate
import com.anytypeio.anytype.presentation.extension.sendAnalyticsSwitchRelationDate
import com.anytypeio.anytype.presentation.home.OpenObjectNavigation
import com.anytypeio.anytype.presentation.home.navigation
import com.anytypeio.anytype.presentation.objects.UiObjectsListItem
import com.anytypeio.anytype.presentation.objects.getCreateObjectParams
import com.anytypeio.anytype.presentation.objects.toUiObjectsListItem
import com.anytypeio.anytype.presentation.search.GlobalSearchViewModel.Companion.DEFAULT_DEBOUNCE_DURATION
import com.anytypeio.anytype.presentation.sync.SyncStatusWidgetState
import com.anytypeio.anytype.presentation.sync.toSyncStatusWidgetState

View file

@ -0,0 +1,61 @@
plugins {
id "com.android.library"
id "kotlin-android"
alias(libs.plugins.compose.compiler)
}
android {
defaultConfig {
buildConfigField "boolean", "USE_NEW_WINDOW_INSET_API", "true"
buildConfigField "boolean", "USE_EDGE_TO_EDGE", "true"
}
buildFeatures {
compose true
}
namespace 'com.anytypeio.anytype.feature_object_type'
}
dependencies {
implementation project(':domain')
implementation project(':core-ui')
implementation project(':analytics')
implementation project(':core-models')
implementation project(':core-utils')
implementation project(':localization')
implementation project(':presentation')
implementation project(':library-emojifier')
compileOnly libs.javaxInject
implementation libs.lifecycleViewModel
implementation libs.lifecycleRuntime
implementation libs.appcompat
implementation libs.compose
implementation libs.composeFoundation
implementation libs.composeToolingPreview
implementation libs.composeMaterial3
implementation libs.composeMaterial
implementation libs.navigationCompose
implementation libs.composeReorderable
debugImplementation libs.composeTooling
implementation libs.timber
testImplementation project(':test:android-utils')
testImplementation project(':test:utils')
testImplementation project(":test:core-models-stub")
testImplementation libs.junit
testImplementation libs.kotlinTest
testImplementation libs.robolectric
testImplementation libs.androidXTestCore
testImplementation libs.mockitoKotlin
testImplementation libs.coroutineTesting
testImplementation libs.timberJUnit
testImplementation libs.turbine
}

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest/>

View file

@ -0,0 +1,52 @@
package com.anytypeio.anytype.feature_object_type.fields
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.RelationFormat
sealed class FieldEvent {
data object OnFieldEditScreenDismiss : FieldEvent()
data object OnAddFieldScreenDismiss : FieldEvent()
data class OnFieldItemClick(val item: UiFieldsListItem) : FieldEvent()
data class OnAddToHeaderFieldClick(
val item: UiAddFieldItem
) : FieldEvent()
data class OnAddToSidebarFieldClick(
val item: UiAddFieldItem
) : FieldEvent()
data class OnSaveButtonClicked(
val name: String,
val format: RelationFormat,
val limitObjectTypes: List<Id>
) : FieldEvent()
data object OnChangeTypeClick : FieldEvent()
data object OnLimitTypesClick : FieldEvent()
sealed class FieldItemMenu : FieldEvent() {
data class OnDeleteFromTypeClick(val item: UiFieldsListItem) : FieldItemMenu()
data class OnRemoveLocalClick(val item: UiFieldsListItem) : FieldItemMenu()
data class OnAddLocalToTypeClick(val item: UiFieldsListItem) : FieldItemMenu()
}
sealed class FieldLocalInfo : FieldEvent() {
data object OnDismiss : FieldLocalInfo()
}
sealed class Section : FieldEvent() {
data object OnAddToHeaderIconClick : Section()
data object OnAddToSidebarIconClick : Section()
data object OnLocalInfoClick : Section()
}
sealed class DragEvent : FieldEvent() {
data class OnMove(val fromKey: String, val toKey: String) : DragEvent()
data object OnDragEnd : DragEvent()
}
data class OnAddFieldSearchQueryChanged(val query: String) : FieldEvent()
}

Some files were not shown because too many files have changed in this diff Show more