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

Editor | Feature | Note as main type (#1852)

* update proto

* add layout note

* add title note

* send default type on pageCreate

* title can be null

* fixes

* test

* drawables

* object layout screen

* footer adapter

* mapping

* get layouts use case

* fix

* update createPage use case

* default page type on dashboard

* default page type on editor

* fix tests

* notes layout logic

* fix

* hide on text change

* fix

* fix

* fix test

* fix

* fix

* fix test

* fix

* ci off

* fix

* pr fix

* fix

* fix

* fixes

* fix

* fix

* fix

* fix test

* ci off
This commit is contained in:
Konstantin Ivanov 2021-10-25 15:29:55 +03:00 committed by GitHub
parent 8a4f0e05eb
commit 8076d126f0
Signed by: github
GPG key ID: 4AEE18F83AFDEB23
42 changed files with 732 additions and 218 deletions

View file

@ -60,6 +60,7 @@ object EventsDictionary {
const val PROP_STYLE = "style"
const val PROP_TYPE = "objectType"
const val PROP_IS_DRAFT = "isDraft"
const val PROP_LAYOUT = "layout"
const val PROP_ACCOUNT_ID = "accountId"
const val PROP_RELATION_FORMAT = "relationFormat"

View file

@ -10,11 +10,12 @@ import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.config.GetConfig
import com.anytypeio.anytype.domain.config.GetDebugSettings
import com.anytypeio.anytype.domain.config.InfrastructureRepository
import com.anytypeio.anytype.domain.config.UserSettingsRepository
import com.anytypeio.anytype.domain.dashboard.interactor.*
import com.anytypeio.anytype.domain.dataview.interactor.SearchObjects
import com.anytypeio.anytype.domain.event.interactor.EventChannel
import com.anytypeio.anytype.domain.event.interactor.InterceptEvents
import com.anytypeio.anytype.domain.icon.DocumentEmojiIconProvider
import com.anytypeio.anytype.domain.launch.GetDefaultEditorType
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.domain.objects.DeleteObjects
import com.anytypeio.anytype.domain.objects.SetObjectListIsArchived
@ -61,6 +62,7 @@ object HomeDashboardModule {
getDebugSettings: GetDebugSettings,
analytics: Analytics,
searchObjects: SearchObjects,
getDefaultEditorType: GetDefaultEditorType,
urlBuilder: UrlBuilder,
setObjectListIsArchived: SetObjectListIsArchived,
deleteObjects: DeleteObjects
@ -78,7 +80,8 @@ object HomeDashboardModule {
analytics = analytics,
urlBuilder = urlBuilder,
setObjectListIsArchived = setObjectListIsArchived,
deleteObjects = deleteObjects
deleteObjects = deleteObjects,
getDefaultEditorType = getDefaultEditorType
)
@JvmStatic
@ -178,6 +181,12 @@ object HomeDashboardModule {
repo = repo
)
@JvmStatic
@Provides
@PerScreen
fun provideGetDefaultPageType(repo: UserSettingsRepository): GetDefaultEditorType =
GetDefaultEditorType(repo)
@JvmStatic
@Provides
@PerScreen

View file

@ -20,6 +20,7 @@ import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.clipboard.Clipboard
import com.anytypeio.anytype.domain.clipboard.Copy
import com.anytypeio.anytype.domain.clipboard.Paste
import com.anytypeio.anytype.domain.config.UserSettingsRepository
import com.anytypeio.anytype.domain.dataview.interactor.GetCompatibleObjectTypes
import com.anytypeio.anytype.domain.dataview.interactor.SearchObjects
import com.anytypeio.anytype.domain.dataview.interactor.SetRelationKey
@ -28,6 +29,7 @@ import com.anytypeio.anytype.domain.download.Downloader
import com.anytypeio.anytype.domain.event.interactor.EventChannel
import com.anytypeio.anytype.domain.event.interactor.InterceptEvents
import com.anytypeio.anytype.domain.icon.DocumentEmojiIconProvider
import com.anytypeio.anytype.domain.launch.GetDefaultEditorType
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.domain.objects.SetObjectIsArchived
import com.anytypeio.anytype.domain.page.*
@ -144,7 +146,8 @@ object EditorSessionModule {
updateDetail: UpdateDetail,
getCompatibleObjectTypes: GetCompatibleObjectTypes,
objectTypesProvider: ObjectTypesProvider,
searchObjects: SearchObjects
searchObjects: SearchObjects,
getDefaultEditorType: GetDefaultEditorType
): EditorViewModelFactory = EditorViewModelFactory(
openPage = openPage,
closeObject = closePage,
@ -167,7 +170,8 @@ object EditorSessionModule {
updateDetail = updateDetail,
getCompatibleObjectTypes = getCompatibleObjectTypes,
objectTypesProvider = objectTypesProvider,
searchObjects = searchObjects
searchObjects = searchObjects,
getDefaultEditorType = getDefaultEditorType
)
@JvmStatic
@ -726,4 +730,10 @@ object EditorUseCaseModule {
fun searchObjects(
repo: BlockRepository
): SearchObjects = SearchObjects(repo = repo)
@JvmStatic
@Provides
@PerScreen
fun provideGetDefaultPageType(repo: UserSettingsRepository): GetDefaultEditorType =
GetDefaultEditorType(repo)
}

View file

@ -13,11 +13,10 @@ import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.config.FlavourConfigProvider
import com.anytypeio.anytype.domain.config.UserSettingsRepository
import com.anytypeio.anytype.domain.device.PathProvider
import com.anytypeio.anytype.domain.launch.GetDefaultPageType
import com.anytypeio.anytype.domain.launch.SetDefaultPageType
import com.anytypeio.anytype.domain.launch.GetDefaultEditorType
import com.anytypeio.anytype.domain.launch.SetDefaultEditorType
import com.anytypeio.anytype.presentation.splash.SplashViewModelFactory
import com.anytypeio.anytype.ui.splash.SplashFragment
import com.squareup.wire.get
import dagger.Module
import dagger.Provides
import dagger.Subcomponent
@ -54,8 +53,8 @@ object SplashModule {
analytics: Analytics,
storeObjectTypes: StoreObjectTypes,
getLastOpenedObject: GetLastOpenedObject,
getDefaultPageType: GetDefaultPageType,
setDefaultPageType: SetDefaultPageType
getDefaultEditorType: GetDefaultEditorType,
setDefaultEditorType: SetDefaultEditorType
): SplashViewModelFactory = SplashViewModelFactory(
checkAuthorizationStatus = checkAuthorizationStatus,
launchAccount = launchAccount,
@ -63,8 +62,8 @@ object SplashModule {
analytics = analytics,
storeObjectTypes = storeObjectTypes,
getLastOpenedObject = getLastOpenedObject,
setDefaultPageType = setDefaultPageType,
getDefaultPageType = getDefaultPageType
setDefaultEditorType = setDefaultEditorType,
getDefaultEditorType = getDefaultEditorType
)
@JvmStatic
@ -123,12 +122,12 @@ object SplashModule {
@JvmStatic
@PerScreen
@Provides
fun provideGetDefaultPageType(repo: UserSettingsRepository): GetDefaultPageType =
GetDefaultPageType(repo)
fun provideGetDefaultPageType(repo: UserSettingsRepository): GetDefaultEditorType =
GetDefaultEditorType(repo)
@JvmStatic
@PerScreen
@Provides
fun provideSetDefaultPageType(repo: UserSettingsRepository): SetDefaultPageType =
SetDefaultPageType(repo)
fun provideSetDefaultPageType(repo: UserSettingsRepository): SetDefaultEditorType =
SetDefaultEditorType(repo)
}

View file

@ -3,8 +3,8 @@ package com.anytypeio.anytype.di.feature
import com.anytypeio.anytype.analytics.base.Analytics
import com.anytypeio.anytype.core_utils.di.scope.PerScreen
import com.anytypeio.anytype.domain.config.UserSettingsRepository
import com.anytypeio.anytype.domain.launch.GetDefaultPageType
import com.anytypeio.anytype.domain.launch.SetDefaultPageType
import com.anytypeio.anytype.domain.launch.GetDefaultEditorType
import com.anytypeio.anytype.domain.launch.SetDefaultEditorType
import com.anytypeio.anytype.presentation.settings.UserSettingsViewModel
import com.anytypeio.anytype.ui.settings.UserSettingsFragment
import dagger.Module
@ -30,22 +30,22 @@ object UserSettingsModule {
@JvmStatic
@PerScreen
@Provides
fun provideGetDefaultPageType(repo: UserSettingsRepository): GetDefaultPageType =
GetDefaultPageType(repo)
fun provideGetDefaultPageType(repo: UserSettingsRepository): GetDefaultEditorType =
GetDefaultEditorType(repo)
@JvmStatic
@PerScreen
@Provides
fun provideSetDefaultPageType(repo: UserSettingsRepository): SetDefaultPageType =
SetDefaultPageType(repo)
fun provideSetDefaultPageType(repo: UserSettingsRepository): SetDefaultEditorType =
SetDefaultEditorType(repo)
@JvmStatic
@Provides
@PerScreen
fun provideUserSettingsFabric(
getDefaultPageType: GetDefaultPageType,
setDefaultPageType: SetDefaultPageType,
getDefaultEditorType: GetDefaultEditorType,
setDefaultEditorType: SetDefaultEditorType,
analytics: Analytics
): UserSettingsViewModel.Factory =
UserSettingsViewModel.Factory(getDefaultPageType, setDefaultPageType, analytics)
UserSettingsViewModel.Factory(getDefaultEditorType, setDefaultEditorType, analytics)
}

View file

@ -47,6 +47,7 @@ object BlockActionToolbarFactory {
is BlockView.Description -> TODO()
is BlockView.FeaturedRelation -> TODO()
is BlockView.Unsupported -> TODO()
is BlockView.TitleNote -> TODO()
}
fun newInstance(

View file

@ -52,16 +52,15 @@ fun List<Block>.parents(selection: Iterable<Id>) : List<Id> {
/**
* Finds title block for a [Document]
* @return title block
* @throws NoSuchElementException if there was no title block in this document.
* @return title block or null if there's no title present
*/
fun Document.title(): Block {
val header = first { block ->
fun Document.title(): Block? {
val header = firstOrNull { block ->
val cnt = block.content
cnt is Content.Layout && cnt.type == Content.Layout.Type.HEADER
}
} ?: return null
val children = filter { header.children.contains(it.id) }
return children.first { child ->
return children.firstOrNull { child ->
val cnt = child.content
cnt is Content.Text && cnt.style == Content.Text.Style.TITLE
}

View file

@ -76,6 +76,7 @@ import com.anytypeio.anytype.presentation.editor.editor.mention.MentionEvent
import com.anytypeio.anytype.presentation.editor.editor.model.BlockView
import com.anytypeio.anytype.presentation.editor.editor.model.Types.HOLDER_DESCRIPTION
import com.anytypeio.anytype.presentation.editor.editor.model.Types.HOLDER_FEATURED_RELATION
import com.anytypeio.anytype.presentation.editor.editor.model.Types.HOLDER_NOTE_TITLE
import com.anytypeio.anytype.presentation.editor.editor.model.Types.HOLDER_RELATION_CHECKBOX
import com.anytypeio.anytype.presentation.editor.editor.model.Types.HOLDER_RELATION_DEFAULT
import com.anytypeio.anytype.presentation.editor.editor.model.Types.HOLDER_RELATION_FILE
@ -251,6 +252,15 @@ class BlockAdapter(
}
}
}
HOLDER_NOTE_TITLE -> {
TitleNoteHolder(
view = inflater.inflate(
R.layout.item_block_note_title,
parent,
false
)
)
}
HOLDER_HEADER_ONE -> {
HeaderOne(
view = inflater.inflate(

View file

@ -0,0 +1,6 @@
package com.anytypeio.anytype.core_ui.features.editor.holders.other
import android.view.View
import com.anytypeio.anytype.core_ui.features.editor.BlockViewHolder
class TitleNoteHolder(view: View) : BlockViewHolder(view)

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="124dp">
</FrameLayout>

View file

@ -42,10 +42,11 @@ class BlockDataRepository(
command: Command.UpdateAlignment
): Payload = factory.remote.updateAlignment(command)
override suspend fun createPage(ctx: Id?, emoji: String?, isDraft: Boolean?) = factory.remote.createPage(
override suspend fun createPage(ctx: Id?, emoji: String?, isDraft: Boolean?, type: String?) = factory.remote.createPage(
ctx = ctx,
emoji = emoji,
isDraft = isDraft
isDraft = isDraft,
type = type
)
override suspend fun closePage(id: String) {

View file

@ -26,7 +26,7 @@ interface BlockDataStore {
suspend fun move(command: Command.Move): Payload
suspend fun unlink(command: Command.Unlink): Payload
suspend fun getConfig(): Config
suspend fun createPage(ctx: Id?, emoji: String?, isDraft: Boolean?): Id
suspend fun createPage(ctx: Id?, emoji: String?, isDraft: Boolean?, type: String?): Id
suspend fun openPage(id: String): Payload
suspend fun openObjectSet(id: String): Payload
suspend fun openProfile(id: String): Payload

View file

@ -23,7 +23,7 @@ interface BlockRemote {
suspend fun updateCheckbox(command: Command.UpdateCheckbox): Payload
suspend fun move(command: Command.Move): Payload
suspend fun getConfig(): Config
suspend fun createPage(ctx: Id?, emoji: String?, isDraft: Boolean?): Id
suspend fun createPage(ctx: Id?, emoji: String?, isDraft: Boolean?, type: String?): Id
suspend fun createPage(command: Command.CreateNewDocument): String
suspend fun openPage(id: String): Payload
suspend fun openProfile(id: String): Payload

View file

@ -18,8 +18,9 @@ class BlockRemoteDataStore(private val remote: BlockRemote) : BlockDataStore {
override suspend fun createPage(
ctx: Id?,
emoji: String?,
isDraft: Boolean?
): Id = remote.createPage(ctx = ctx, emoji = emoji, isDraft = isDraft)
isDraft: Boolean?,
type: String?
): Id = remote.createPage(ctx = ctx, emoji = emoji, isDraft = isDraft, type = type)
override suspend fun openPage(id: String): Payload = remote.openPage(id)
override suspend fun openProfile(id: String): Payload = remote.openProfile(id)

View file

@ -69,7 +69,7 @@ interface BlockRepository {
suspend fun getConfig(): Config
suspend fun createPage(ctx: Id?, emoji: String?, isDraft: Boolean?): Id
suspend fun createPage(ctx: Id?, emoji: String?, isDraft: Boolean?, type: String?): Id
suspend fun openPage(id: String): Result<Payload>

View file

@ -3,9 +3,9 @@ package com.anytypeio.anytype.domain.launch
import com.anytypeio.anytype.domain.base.BaseUseCase
import com.anytypeio.anytype.domain.config.UserSettingsRepository
class GetDefaultPageType(
class GetDefaultEditorType(
private val userSettingsRepository: UserSettingsRepository
) : BaseUseCase<GetDefaultPageType.Response, Unit>() {
) : BaseUseCase<GetDefaultEditorType.Response, Unit>() {
override suspend fun run(params: Unit) = safe {
Response(userSettingsRepository.getDefaultPageType())

View file

@ -3,8 +3,8 @@ package com.anytypeio.anytype.domain.launch
import com.anytypeio.anytype.domain.base.BaseUseCase
import com.anytypeio.anytype.domain.config.UserSettingsRepository
class SetDefaultPageType(private val repo: UserSettingsRepository) :
BaseUseCase<Unit, SetDefaultPageType.Params>() {
class SetDefaultEditorType(private val repo: UserSettingsRepository) :
BaseUseCase<Unit, SetDefaultEditorType.Params>() {
override suspend fun run(params: Params) = safe {
repo.setDefaultPageType(params.type)

View file

@ -16,12 +16,20 @@ class CreatePage(
repo.createPage(
ctx = params.ctx,
emoji = null,
isDraft = params.isDraft
isDraft = params.isDraft,
type = params.type
)
}
/**
* @property [ctx] context (parent) for this new page.
* @property [type] type of created object
* @property [isDraft] should this object be in Draft state
*/
data class Params(val ctx: Id?, val isDraft: Boolean?)
data class Params(
val ctx: Id?,
val type: String?,
val emoji: String?,
val isDraft: Boolean?
)
}

View file

@ -947,8 +947,8 @@ class BlockExtensionTest {
assertEquals(expected = expected, actual = result)
}
@Test(expected = NoSuchElementException::class)
fun `should throw NoSuchElementException when title block is not present in header childs`() {
@Test
fun `should return null when title block is not present in header childs`() {
val root = MockDataFactory.randomUuid()
@ -982,10 +982,12 @@ class BlockExtensionTest {
val document = listOf(page, header, a)
val result = document.title()
assertNull(result)
}
@Test(expected = NoSuchElementException::class)
fun `should throw NoSuchElementException when header is not present`() {
@Test
fun `should return null when header is not present`() {
val root = MockDataFactory.randomUuid()
@ -1010,5 +1012,7 @@ class BlockExtensionTest {
val document = listOf(page, a)
val result = document.title()
assertNull(result)
}
}

View file

@ -23,8 +23,8 @@ class BlockMiddleware(
}
override suspend fun createPage(
ctx: Id?, emoji: String?, isDraft: Boolean?
): String = middleware.createPage(ctx = ctx, emoji = emoji, isDraft = isDraft)
ctx: Id?, emoji: String?, isDraft: Boolean?, type: String?
): String = middleware.createPage(ctx = ctx, emoji = emoji, isDraft = isDraft, type = type)
override suspend fun createPage(command: Command.CreateNewDocument): String =
middleware.createPage(command)

View file

@ -183,11 +183,12 @@ class Middleware(
}
@Throws(Exception::class)
fun createPage(ctx: Id?, emoji: String?, isDraft: Boolean?): Id {
fun createPage(ctx: Id?, emoji: String?, isDraft: Boolean?, type: String?): Id {
val details: MutableMap<String, Any> = mutableMapOf()
emoji?.let { details[iconEmojiKey] = it}
isDraft?.let { details[isDraftKey] = it }
type?.let { details[typeKey] = it }
val request = Rpc.Block.CreatePage.Request(
contextId = ctx.orEmpty(),

View file

@ -5,6 +5,8 @@ import androidx.lifecycle.viewModelScope
import com.anytypeio.anytype.analytics.base.Analytics
import com.anytypeio.anytype.analytics.base.EventsDictionary
import com.anytypeio.anytype.analytics.base.EventsDictionary.PAGE_CREATE
import com.anytypeio.anytype.analytics.base.EventsDictionary.PROP_IS_DRAFT
import com.anytypeio.anytype.analytics.base.EventsDictionary.PROP_TYPE
import com.anytypeio.anytype.analytics.base.EventsDictionary.SCREEN_DASHBOARD
import com.anytypeio.anytype.analytics.base.EventsDictionary.SCREEN_PROFILE
import com.anytypeio.anytype.analytics.base.EventsDictionary.TAB_ARCHIVE
@ -27,6 +29,7 @@ import com.anytypeio.anytype.domain.dashboard.interactor.CloseDashboard
import com.anytypeio.anytype.domain.dashboard.interactor.OpenDashboard
import com.anytypeio.anytype.domain.dataview.interactor.SearchObjects
import com.anytypeio.anytype.domain.event.interactor.InterceptEvents
import com.anytypeio.anytype.domain.launch.GetDefaultEditorType
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.domain.objects.DeleteObjects
import com.anytypeio.anytype.domain.objects.SetObjectListIsArchived
@ -58,6 +61,7 @@ class HomeDashboardViewModel(
private val getDebugSettings: GetDebugSettings,
private val analytics: Analytics,
private val searchObjects: SearchObjects,
private val getDefaultEditorType: GetDefaultEditorType,
private val urlBuilder: UrlBuilder,
private val setObjectListIsArchived: SetObjectListIsArchived,
private val deleteObjects: DeleteObjects
@ -181,25 +185,7 @@ class HomeDashboardViewModel(
}
fun onAddNewDocumentClicked() {
val startTime = System.currentTimeMillis()
createPage.invoke(viewModelScope, CreatePage.Params(ctx = null, isDraft = true)) { result ->
result.either(
fnL = { e -> Timber.e(e, "Error while creating a new page") },
fnR = { id ->
val middle = System.currentTimeMillis()
viewModelScope.sendEvent(
analytics = analytics,
startTime = startTime,
middleTime = middle,
renderTime = middle,
eventName = PAGE_CREATE,
props = Props.empty()
)
machine.onEvents(listOf(Machine.Event.OnFinishedCreatingPage))
proceedWithOpeningDocument(id)
}
)
}
proceedWithGettingDefaultPageType()
}
/**
@ -652,12 +638,54 @@ class HomeDashboardViewModel(
ObjectType.Layout.PROFILE,
ObjectType.Layout.FILE,
ObjectType.Layout.IMAGE,
ObjectType.Layout.SET
ObjectType.Layout.SET,
ObjectType.Layout.NOTE
)
}
enum class TAB { FAVOURITE, RECENT, INBOX, SETS, ARCHIVE }
//region CREATE PAGE
private fun proceedWithGettingDefaultPageType() {
viewModelScope.launch {
getDefaultEditorType.invoke(Unit).proceed(
failure = { Timber.e(it, "Error while getting default page type") },
success = { response -> proceedWithCreatePage(type = response.type) }
)
}
}
private suspend fun proceedWithCreatePage(type: String?) {
val startTime = System.currentTimeMillis()
val isDraft = true
val params = CreatePage.Params(
ctx = null,
isDraft = isDraft,
type = type,
emoji = null
)
createPage.invoke(viewModelScope, params) { result ->
result.either(
fnL = { e -> Timber.e(e, "Error while creating a new page") },
fnR = { id ->
val middle = System.currentTimeMillis()
val props = Props(mapOf(PROP_TYPE to type, PROP_IS_DRAFT to isDraft))
viewModelScope.sendEvent(
analytics = analytics,
startTime = startTime,
middleTime = middle,
renderTime = middle,
eventName = PAGE_CREATE,
props = props
)
machine.onEvents(listOf(Machine.Event.OnFinishedCreatingPage))
proceedWithOpeningDocument(id)
}
)
}
}
//endregion
enum class Mode { DEFAULT, SELECTION }
sealed class Alert {

View file

@ -10,6 +10,7 @@ import com.anytypeio.anytype.domain.config.GetDebugSettings
import com.anytypeio.anytype.domain.dashboard.interactor.*
import com.anytypeio.anytype.domain.dataview.interactor.SearchObjects
import com.anytypeio.anytype.domain.event.interactor.InterceptEvents
import com.anytypeio.anytype.domain.launch.GetDefaultEditorType
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.domain.objects.DeleteObjects
import com.anytypeio.anytype.domain.objects.SetObjectListIsArchived
@ -27,6 +28,7 @@ class HomeDashboardViewModelFactory(
private val getDebugSettings: GetDebugSettings,
private val analytics: Analytics,
private val searchObjects: SearchObjects,
private val getDefaultEditorType: GetDefaultEditorType,
private val urlBuilder: UrlBuilder,
private val setObjectListIsArchived: SetObjectListIsArchived,
private val deleteObjects: DeleteObjects
@ -47,6 +49,7 @@ class HomeDashboardViewModelFactory(
analytics = analytics,
searchObjects = searchObjects,
urlBuilder = urlBuilder,
getDefaultEditorType = getDefaultEditorType,
deleteObjects = deleteObjects,
setObjectListIsArchived = setObjectListIsArchived
) as T

View file

@ -33,7 +33,6 @@ interface Editor {
val objectTypes: Store.ObjectTypes = Store.ObjectTypes()
val textSelection: Store<Editor.TextSelection> = Store.TextSelection()
val objectRestrictions: Store.ObjectRestrictions = Store.ObjectRestrictions()
val objectIsDraft: Store.ObjectIsDraft = Store.ObjectIsDraft()
}
class Proxer(

View file

@ -43,6 +43,7 @@ import com.anytypeio.anytype.domain.dataview.interactor.SearchObjects
import com.anytypeio.anytype.domain.editor.Editor
import com.anytypeio.anytype.domain.error.Error
import com.anytypeio.anytype.domain.event.interactor.InterceptEvents
import com.anytypeio.anytype.domain.launch.GetDefaultEditorType
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.domain.objects.SetObjectIsArchived
import com.anytypeio.anytype.domain.page.*
@ -135,6 +136,7 @@ class EditorViewModel(
private val getCompatibleObjectTypes: GetCompatibleObjectTypes,
private val objectTypesProvider: ObjectTypesProvider,
private val searchObjects: SearchObjects,
private val getDefaultEditorType: GetDefaultEditorType
) : ViewStateViewModel<ViewState>(),
SupportNavigation<EventWrapper<AppNavigation.Command>>,
SupportCommand<Command>,
@ -285,8 +287,8 @@ class EditorViewModel(
orchestrator.stores.relations.update(event.relations)
orchestrator.stores.objectTypes.update(event.objectTypes)
orchestrator.stores.objectRestrictions.update(event.objectRestrictions)
orchestrator.stores.objectIsDraft.set(event.details, context)
showObjectTypesWidget()
val objectType = event.details.details[context]?.type?.firstOrNull()
proceedWithShowingObjectTypesWidget(objectType, event.blocks)
}
if (event is Event.Command.Details) {
orchestrator.stores.details.apply { update(current().process(event)) }
@ -604,6 +606,7 @@ class EditorViewModel(
}
}
//TODO need refactoring, logic must depend on Object Layouts
private fun onStartFocusing(payload: Payload) {
val event = payload.events.find { it is Event.Command.ShowObject }
if (event is Event.Command.ShowObject) {
@ -616,17 +619,27 @@ class EditorViewModel(
if (content is Content.Layout && content.type == Content.Layout.Type.HEADER) {
try {
val title = event.blocks.title()
if (title.content<Content.Text>().text.isEmpty()) {
if (title != null && title.content<Content.Text>().text.isEmpty()) {
val focus = Editor.Focus(id = title.id, cursor = Editor.Cursor.End)
viewModelScope.launch { orchestrator.stores.focus.update(focus) }
} else {
Timber.d("Skipping initial focusing. Title is not empty.")
Timber.d("Skipping initial focusing. Title is not empty or is null")
}
} catch (e: Throwable) {
Timber.e(e, "Error while initial focusing")
}
}
}
root.children.size == 2 -> {
val layout = event.details.details[root.id]?.layout
if (layout == ObjectType.Layout.NOTE.code.toDouble()) {
val block = event.blocks.firstOrNull { it.content is Content.Text }
if (block != null && block.content<Content.Text>().text.isEmpty()) {
val focus = Editor.Focus(id = block.id, cursor = Editor.Cursor.End)
viewModelScope.launch { orchestrator.stores.focus.update(focus) }
}
}
}
else -> Timber.d("Skipping initial focusing, document is not empty.")
}
}
@ -782,7 +795,6 @@ class EditorViewModel(
)
viewModelScope.launch { orchestrator.stores.views.update(new) }
viewModelScope.launch { orchestrator.proxies.changes.send(update) }
viewModelScope.launch { checkObjectIsDraft() }
}
fun onDescriptionBlockTextChanged(view: BlockView.Description) {
@ -819,6 +831,7 @@ class EditorViewModel(
viewModelScope.launch { store.update(new) }
viewModelScope.launch { orchestrator.proxies.changes.send(update) }
if (isObjectTypesWidgetVisible) hideObjectTypesWidget()
}
fun onSelectionChanged(id: String, selection: IntRange) {
@ -3476,31 +3489,6 @@ class EditorViewModel(
}
}
fun onPlusButtonPressed() {
Timber.d("onPlusButtonPressed, ")
val startTime = System.currentTimeMillis()
createPage(
scope = viewModelScope,
params = CreatePage.Params(ctx = null, isDraft = true)
) { result ->
result.either(
fnL = { Timber.e(it, "Error while creating a new page on home dashboard") },
fnR = { id ->
val middle = System.currentTimeMillis()
viewModelScope.sendEvent(
analytics = analytics,
startTime = startTime,
middleTime = middle,
renderTime = middle,
eventName = PAGE_CREATE,
props = Props.empty()
)
proceedWithOpeningPage(id)
}
)
}
}
fun onProceedWithFilePath(filePath: String?) {
Timber.d("onProceedWithFilePath, filePath:[$filePath]")
if (filePath == null) {
@ -4646,6 +4634,7 @@ class EditorViewModel(
Timber.d("onKeyPressedEvent, event:[$event]")
when (event) {
is KeyPressedEvent.OnTitleBlockEnterKeyEvent -> {
if (isObjectTypesWidgetVisible) hideObjectTypesWidget()
proceedWithTitleEnterClicked(
title = event.target,
text = event.text,
@ -5146,6 +5135,9 @@ class EditorViewModel(
//endregion
//region OBJECT TYPES WIDGET
private val isObjectTypesWidgetVisible : Boolean get() =
controlPanelViewState.value?.objectTypesToolbar?.isVisible ?: false
fun onObjectTypesWidgetItemClicked(id: Id) {
Timber.d("onObjectTypesWidgetItemClicked, id:[$id]")
controlPanelInteractor.onEvent(
@ -5175,42 +5167,57 @@ class EditorViewModel(
)
}
private fun showObjectTypesWidget() {
private fun proceedWithShowingObjectTypesWidget(objectType: String?, blocks: List<Block>) {
val restrictions = orchestrator.stores.objectRestrictions.current()
if (restrictions.contains(ObjectRestriction.TYPE_CHANGE)) {
_toasts.offer(NOT_ALLOWED_FOR_OBJECT)
Timber.d("No interaction allowed with this object type")
return
}
val isDraft = orchestrator.stores.objectIsDraft.current()
if (isDraft) {
val smartBlockType = getObjectSmartBlockType()
viewModelScope.launch {
getCompatibleObjectTypes.invoke(
GetCompatibleObjectTypes.Params(smartBlockType)
).proceed(
failure = { Timber.e(it, "Error while getting object types") },
success = { objectTypes ->
val views = listOf(ObjectTypeView.Search()) + objectTypes.toObjectTypeView()
controlPanelInteractor.onEvent(
ControlPanelMachine.Event.ObjectTypesWidgetEvent.Show(views)
)
when (objectType) {
ObjectType.NOTE_URL -> {
val root = blocks.find { it.id == context } ?: return
if (root.children.size == 2) {
val lastBlock = blocks.find { it.id == root.children.last() }
if (lastBlock != null && lastBlock.content is Content.Text) {
if (lastBlock.content<Content.Text>().text.isEmpty()) {
proceedWithGettingObjectTypesForObjectTypeWidget()
}
}
)
}
}
else -> {
val root = blocks.find { it.id == context } ?: return
if (root.children.size == 1) {
val title = blocks.title() ?: return
if (title.content<Content.Text>().text.isEmpty()) {
proceedWithGettingObjectTypesForObjectTypeWidget()
}
}
}
}
}
private suspend fun checkObjectIsDraft() {
val isDraft = orchestrator.stores.objectIsDraft.current()
if (isDraft) {
orchestrator.stores.objectIsDraft.set(state = false)
controlPanelInteractor.onEvent(
ControlPanelMachine.Event.ObjectTypesWidgetEvent.Hide
private fun proceedWithGettingObjectTypesForObjectTypeWidget() {
val smartBlockType = getObjectSmartBlockType()
val params = GetCompatibleObjectTypes.Params(smartBlockType)
viewModelScope.launch {
getCompatibleObjectTypes.invoke(params).proceed(
failure = { Timber.e(it, "Error while getting object types") },
success = { objectTypes ->
val views = listOf(ObjectTypeView.Search()) + objectTypes.toObjectTypeView()
controlPanelInteractor.onEvent(
ControlPanelMachine.Event.ObjectTypesWidgetEvent.Show(views)
)
}
)
}
}
private fun hideObjectTypesWidget() {
controlPanelInteractor.onEvent(ControlPanelMachine.Event.ObjectTypesWidgetEvent.Hide)
}
private fun getObjectSmartBlockType(): SmartBlockType {
val block = blocks.firstOrNull { it.id == context }
return if (block?.content is Content.Smart) {

View file

@ -13,6 +13,7 @@ import com.anytypeio.anytype.domain.block.interactor.UpdateLinkMarks
import com.anytypeio.anytype.domain.dataview.interactor.GetCompatibleObjectTypes
import com.anytypeio.anytype.domain.dataview.interactor.SearchObjects
import com.anytypeio.anytype.domain.event.interactor.InterceptEvents
import com.anytypeio.anytype.domain.launch.GetDefaultEditorType
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.domain.objects.SetObjectIsArchived
import com.anytypeio.anytype.domain.page.*
@ -45,7 +46,8 @@ open class EditorViewModelFactory(
private val updateDetail: UpdateDetail,
private val getCompatibleObjectTypes: GetCompatibleObjectTypes,
private val objectTypesProvider: ObjectTypesProvider,
private val searchObjects: SearchObjects
private val searchObjects: SearchObjects,
private val getDefaultEditorType: GetDefaultEditorType
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
@ -72,7 +74,8 @@ open class EditorViewModelFactory(
updateDetail = updateDetail,
getCompatibleObjectTypes = getCompatibleObjectTypes,
objectTypesProvider = objectTypesProvider,
searchObjects = searchObjects
searchObjects = searchObjects,
getDefaultEditorType = getDefaultEditorType
) as T
}
}

View file

@ -76,14 +76,5 @@ interface Store<T> {
class Relations : State<List<Relation>>(emptyList())
class ObjectTypes : State<List<ObjectType>>(emptyList())
class ObjectRestrictions : State<List<ObjectRestriction>>(emptyList())
class ObjectIsDraft : State<Boolean>(false) {
suspend fun set(state: Boolean) {
super.update(state)
}
suspend fun set(details: Block.Details, ctx: Id) {
val isDraft = details.details[ctx]?.isDraft ?: false
super.update(isDraft)
}
}
class TextSelection : State<Editor.TextSelection>(Editor.TextSelection.empty())
}

View file

@ -24,6 +24,7 @@ import com.anytypeio.anytype.presentation.editor.editor.model.Types.HOLDER_HEADE
import com.anytypeio.anytype.presentation.editor.editor.model.Types.HOLDER_HEADER_THREE
import com.anytypeio.anytype.presentation.editor.editor.model.Types.HOLDER_HEADER_TWO
import com.anytypeio.anytype.presentation.editor.editor.model.Types.HOLDER_HIGHLIGHT
import com.anytypeio.anytype.presentation.editor.editor.model.Types.HOLDER_NOTE_TITLE
import com.anytypeio.anytype.presentation.editor.editor.model.Types.HOLDER_NUMBERED
import com.anytypeio.anytype.presentation.editor.editor.model.Types.HOLDER_OBJECT_TYPE
import com.anytypeio.anytype.presentation.editor.editor.model.Types.HOLDER_PAGE
@ -540,7 +541,24 @@ sealed class BlockView : ViewType, Parcelable {
) : Title() {
override fun getViewType() = HOLDER_ARCHIVE_TITLE
}
}
/**
* UI-model for a note-layout title block.
* In fact it's just an empty space, because NOTE LAYOUT doesn't have title
* @property id block's id
*/
@Parcelize
data class TitleNote(
override val id: String
) : BlockView() {
override fun getViewType() = HOLDER_NOTE_TITLE
companion object {
const val INTERNAL_ID = "HOLDER_NOTE_TITLE"
}
}
/**

View file

@ -6,6 +6,7 @@ object Types {
const val HOLDER_PROFILE_TITLE = 35
const val HOLDER_ARCHIVE_TITLE = 36
const val HOLDER_TODO_TITLE = 48
const val HOLDER_NOTE_TITLE = 50
const val HOLDER_HEADER_ONE = 2
const val HOLDER_HEADER_TWO = 3
const val HOLDER_HEADER_THREE = 4

View file

@ -53,6 +53,9 @@ class DefaultBlockViewRenderer(
)
)
}
if (isLayoutNote(root, details)) {
result.add(BlockView.TitleNote(id = BlockView.TitleNote.INTERNAL_ID))
}
}
}
@ -472,6 +475,7 @@ class DefaultBlockViewRenderer(
relations = relations,
details = details
)
if (featured.relations.isNotEmpty()) {
result.add(featured)
}
@ -1350,4 +1354,9 @@ class DefaultBlockViewRenderer(
is Cursor.Range -> cursor.range.first
}
}
private fun isLayoutNote(root: Block, details: Block.Details): Boolean {
val layoutCode = details.details[root.id]?.layout?.toInt()
return layoutCode == ObjectType.Layout.NOTE.code
}
}

View file

@ -112,8 +112,8 @@ fun ObjectSet.render(
fun ObjectSet.title(
ctx: Id,
urlBuilder: UrlBuilder
): BlockView.Title.Basic {
val title = blocks.title()
): BlockView.Title.Basic? {
val title = blocks.title() ?: return null
val objectDetails = details[ctx]

View file

@ -4,6 +4,6 @@ import com.anytypeio.anytype.presentation.editor.editor.model.BlockView
import com.anytypeio.anytype.presentation.sets.model.Viewer
data class ObjectSetViewState(
val title: BlockView.Title.Basic,
val title: BlockView.Title.Basic?,
val viewer: Viewer
)

View file

@ -5,15 +5,15 @@ import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.anytypeio.anytype.analytics.base.Analytics
import com.anytypeio.anytype.core_models.ObjectType
import com.anytypeio.anytype.domain.launch.GetDefaultPageType
import com.anytypeio.anytype.domain.launch.SetDefaultPageType
import com.anytypeio.anytype.domain.launch.GetDefaultEditorType
import com.anytypeio.anytype.domain.launch.SetDefaultEditorType
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.launch
import timber.log.Timber
class UserSettingsViewModel(
private val getDefaultPageType: GetDefaultPageType,
private val setDefaultPageType: SetDefaultPageType,
private val getDefaultEditorType: GetDefaultEditorType,
private val setDefaultEditorType: SetDefaultEditorType,
private val analytics: Analytics
) : ViewModel() {
@ -21,7 +21,7 @@ class UserSettingsViewModel(
init {
viewModelScope.launch {
getDefaultPageType.invoke(Unit).proceed(
getDefaultEditorType.invoke(Unit).proceed(
failure = { Timber.e(it, "Error while getting user settings") },
success = { response ->
if (response.type == ObjectType.NOTE_URL) {
@ -44,7 +44,7 @@ class UserSettingsViewModel(
private fun proceedWithUpdateType(type: String) {
viewModelScope.launch {
setDefaultPageType.invoke(SetDefaultPageType.Params(type)).process(
setDefaultEditorType.invoke(SetDefaultEditorType.Params(type)).process(
failure = {
Timber.e(it, "Error while setting default object type")
commands.emit(Command.Exit)
@ -62,16 +62,16 @@ class UserSettingsViewModel(
}
class Factory(
private val getDefaultPageType: GetDefaultPageType,
private val setDefaultPageType: SetDefaultPageType,
private val getDefaultEditorType: GetDefaultEditorType,
private val setDefaultEditorType: SetDefaultEditorType,
private val analytics: Analytics
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(
modelClass: Class<T>
): T = UserSettingsViewModel(
getDefaultPageType = getDefaultPageType,
setDefaultPageType = setDefaultPageType,
getDefaultEditorType = getDefaultEditorType,
setDefaultEditorType = setDefaultEditorType,
analytics = analytics
) as T
}

View file

@ -21,8 +21,8 @@ import com.anytypeio.anytype.domain.auth.interactor.LaunchWallet
import com.anytypeio.anytype.domain.auth.model.AuthStatus
import com.anytypeio.anytype.domain.base.BaseUseCase
import com.anytypeio.anytype.domain.block.interactor.sets.StoreObjectTypes
import com.anytypeio.anytype.domain.launch.GetDefaultPageType
import com.anytypeio.anytype.domain.launch.SetDefaultPageType
import com.anytypeio.anytype.domain.launch.GetDefaultEditorType
import com.anytypeio.anytype.domain.launch.SetDefaultEditorType
import com.anytypeio.anytype.presentation.objects.SupportedLayouts
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.launch
@ -40,8 +40,8 @@ class SplashViewModel(
private val launchAccount: LaunchAccount,
private val storeObjectTypes: StoreObjectTypes,
private val getLastOpenedObject: GetLastOpenedObject,
private val getDefaultPageType: GetDefaultPageType,
private val setDefaultPageType: SetDefaultPageType
private val getDefaultEditorType: GetDefaultEditorType,
private val setDefaultEditorType: SetDefaultEditorType
) : ViewModel() {
val commands = MutableSharedFlow<Command>(replay = 0)
@ -52,7 +52,7 @@ class SplashViewModel(
private fun proceedWithUserSettings() {
viewModelScope.launch {
getDefaultPageType.invoke(Unit).process(
getDefaultEditorType.invoke(Unit).process(
failure = {
Timber.e(it, "Error while getting default page type")
checkAuthorizationStatus()
@ -77,9 +77,9 @@ class SplashViewModel(
DEFAULT_TYPE_UPDATE
}
viewModelScope.launch {
val params = SetDefaultPageType.Params(defaultType)
val params = SetDefaultEditorType.Params(defaultType)
Timber.d("Start to update Default Page Type:${params.type}")
setDefaultPageType.invoke(params).process(
setDefaultEditorType.invoke(params).process(
failure = {
Timber.e(it, "Error while setting default page type")
checkAuthorizationStatus()

View file

@ -8,8 +8,8 @@ import com.anytypeio.anytype.domain.auth.interactor.GetLastOpenedObject
import com.anytypeio.anytype.domain.auth.interactor.LaunchAccount
import com.anytypeio.anytype.domain.auth.interactor.LaunchWallet
import com.anytypeio.anytype.domain.block.interactor.sets.StoreObjectTypes
import com.anytypeio.anytype.domain.launch.GetDefaultPageType
import com.anytypeio.anytype.domain.launch.SetDefaultPageType
import com.anytypeio.anytype.domain.launch.GetDefaultEditorType
import com.anytypeio.anytype.domain.launch.SetDefaultEditorType
/**
* Created by Konstantin Ivanov
@ -23,8 +23,8 @@ class SplashViewModelFactory(
private val analytics: Analytics,
private val storeObjectTypes: StoreObjectTypes,
private val getLastOpenedObject: GetLastOpenedObject,
private val getDefaultPageType: GetDefaultPageType,
private val setDefaultPageType: SetDefaultPageType
private val getDefaultEditorType: GetDefaultEditorType,
private val setDefaultEditorType: SetDefaultEditorType
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
@ -36,7 +36,7 @@ class SplashViewModelFactory(
analytics = analytics,
storeObjectTypes = storeObjectTypes,
getLastOpenedObject = getLastOpenedObject,
getDefaultPageType = getDefaultPageType,
setDefaultPageType = setDefaultPageType
getDefaultEditorType = getDefaultEditorType,
setDefaultEditorType = setDefaultEditorType
) as T
}

View file

@ -18,6 +18,7 @@ import com.anytypeio.anytype.domain.config.GetDebugSettings
import com.anytypeio.anytype.domain.dashboard.interactor.*
import com.anytypeio.anytype.domain.dataview.interactor.SearchObjects
import com.anytypeio.anytype.domain.event.interactor.InterceptEvents
import com.anytypeio.anytype.domain.launch.GetDefaultEditorType
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.domain.objects.DeleteObjects
import com.anytypeio.anytype.domain.objects.SetObjectListIsArchived
@ -82,6 +83,9 @@ open class DashboardTestSetup {
@Mock
lateinit var objectTypesProvider: ObjectTypesProvider
@Mock
lateinit var getDefaultEditorType: GetDefaultEditorType
lateinit var vm: HomeDashboardViewModel
val builder: UrlBuilder get() = UrlBuilder(gateway)
@ -108,6 +112,7 @@ open class DashboardTestSetup {
analytics = analytics,
searchObjects = searchObjects,
urlBuilder = builder,
getDefaultEditorType = getDefaultEditorType,
setObjectListIsArchived = setObjectListIsArchived,
deleteObjects = deleteObjects
)

View file

@ -16,6 +16,7 @@ import com.anytypeio.anytype.domain.config.GetDebugSettings
import com.anytypeio.anytype.domain.dashboard.interactor.*
import com.anytypeio.anytype.domain.dataview.interactor.SearchObjects
import com.anytypeio.anytype.domain.event.interactor.InterceptEvents
import com.anytypeio.anytype.domain.launch.GetDefaultEditorType
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.domain.objects.DeleteObjects
import com.anytypeio.anytype.domain.objects.SetObjectListIsArchived
@ -83,6 +84,9 @@ class HomeDashboardViewModelTest {
@Mock
lateinit var objectTypesProvider: ObjectTypesProvider
@Mock
lateinit var getDefaultEditorType: GetDefaultEditorType
private lateinit var vm: HomeDashboardViewModel
private val config = Config(
@ -116,7 +120,8 @@ class HomeDashboardViewModelTest {
searchObjects = searchObjects,
deleteObjects = deleteObjects,
setObjectListIsArchived = setObjectListIsArchived,
urlBuilder = builder
urlBuilder = builder,
getDefaultEditorType = getDefaultEditorType
)
}
@ -305,6 +310,7 @@ class HomeDashboardViewModelTest {
fun `should start creating page when requested from UI`() {
stubObserveEvents()
stubGetDefaultObjectType(null)
vm = buildViewModel()
@ -322,6 +328,7 @@ class HomeDashboardViewModelTest {
stubGetEditorSettings()
stubCloseDashboard()
stubCreatePage(id)
stubGetDefaultObjectType(null)
vm = buildViewModel()
@ -384,4 +391,10 @@ class HomeDashboardViewModelTest {
onBlocking { invoke(any()) } doReturn Either.Right(DebugSettings(true))
}
}
private fun stubGetDefaultObjectType(type: String?) {
getDefaultEditorType.stub {
onBlocking { invoke(Unit) } doReturn Either.Right(GetDefaultEditorType.Response(type))
}
}
}

View file

@ -21,6 +21,7 @@ import com.anytypeio.anytype.domain.dataview.interactor.SearchObjects
import com.anytypeio.anytype.domain.dataview.interactor.SetRelationKey
import com.anytypeio.anytype.domain.download.DownloadFile
import com.anytypeio.anytype.domain.event.interactor.InterceptEvents
import com.anytypeio.anytype.domain.launch.GetDefaultEditorType
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.domain.objects.SetObjectIsArchived
import com.anytypeio.anytype.domain.page.*
@ -214,6 +215,9 @@ open class EditorViewModelTest {
@Mock
lateinit var objectTypesProvider: ObjectTypesProvider
@Mock
lateinit var getDefaultEditorType: GetDefaultEditorType
private lateinit var updateDetail: UpdateDetail
lateinit var vm: EditorViewModel
@ -2406,49 +2410,6 @@ open class EditorViewModelTest {
coroutineTestRule.advanceTime(EditorViewModel.TEXT_CHANGES_DEBOUNCE_DURATION)
}
@Test
fun `should proceed with creating a new page with is draft true on on-plus-button-clicked event`() {
val root = MockDataFactory.randomUuid()
val child = MockDataFactory.randomUuid()
val page = MockBlockFactory.makeOnePageWithOneTextBlock(
root = root,
child = child
)
val flow: Flow<List<Event.Command>> = flow {
delay(100)
emit(
listOf(
Event.Command.ShowObject(
root = root,
blocks = page,
context = root
)
)
)
}
stubObserveEvents(flow)
stubOpenPage()
buildViewModel()
vm.onStart(root)
coroutineTestRule.advanceTime(100)
// TESTING
vm.onPlusButtonPressed()
verify(createPage, times(1)).invoke(
scope = any(),
params = eq(CreatePage.Params(null, true)),
onResult = any()
)
}
@Test
fun `should start downloading file`() {
@ -3885,6 +3846,12 @@ open class EditorViewModelTest {
}
}
private fun stubGetDefaultObjectType(type: String?) {
getDefaultEditorType.stub {
onBlocking { invoke(Unit) } doReturn Either.Right(GetDefaultEditorType.Response(type))
}
}
fun buildViewModel(urlBuilder: UrlBuilder = builder) {
val storage = Editor.Storage()
@ -3915,6 +3882,7 @@ open class EditorViewModelTest {
createDocument = createDocument,
createNewDocument = createNewDocument,
analytics = analytics,
getDefaultEditorType = getDefaultEditorType,
orchestrator = Orchestrator(
createBlock = createBlock,
replaceBlock = replaceBlock,

View file

@ -0,0 +1,151 @@
package com.anytypeio.anytype.presentation.editor.editor
import MockDataFactory
import android.util.Log
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.anytypeio.anytype.core_models.*
import com.anytypeio.anytype.presentation.MockTypicalDocumentFactory
import com.anytypeio.anytype.presentation.editor.EditorViewModel
import com.anytypeio.anytype.presentation.editor.editor.model.BlockView
import com.anytypeio.anytype.presentation.relations.DocumentRelationView
import com.anytypeio.anytype.presentation.util.CoroutinesTestRule
import com.jraska.livedata.test
import net.lachlanmckee.timberjunit.TimberTestRule
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mockito.MockitoAnnotations
class EditorNoteLayoutTest : EditorPresentationTestSetup() {
@get:Rule
val timberTestRule: TimberTestRule = TimberTestRule.builder()
.minPriority(Log.DEBUG)
.showThread(true)
.showTimestamp(false)
.onlyLogWhenTestFails(true)
.build()
@get:Rule
val rule = InstantTaskExecutorRule()
@get:Rule
val coroutineTestRule = CoroutinesTestRule()
@Before
fun setup() {
MockitoAnnotations.openMocks(this)
}
@After
fun after() {
coroutineTestRule.advanceTime(EditorViewModel.TEXT_CHANGES_DEBOUNCE_DURATION)
}
@Test
fun `should render note title block with featured relations block`() {
val featuredBlock = Block(
id = "featuredRelations",
fields = Block.Fields.empty(),
children = emptyList(),
content = Block.Content.FeaturedRelations
)
val header = Block(
id = "header",
content = Block.Content.Layout(
type = Block.Content.Layout.Type.HEADER
),
fields = Block.Fields.empty(),
children = listOf(featuredBlock.id)
)
val page = Block(
id = root,
fields = Block.Fields(emptyMap()),
content = Block.Content.Smart(SmartBlockType.PAGE),
children = listOf(header.id)
)
val doc = listOf(page, header, featuredBlock)
val objectTypeId = "objectTypeId"
val objectTypeName = "objectTypeName"
val objectTypeDescription = "objectTypeDesc"
val r1 = MockTypicalDocumentFactory.relation("Ad")
val r2 = MockTypicalDocumentFactory.relation("De")
val r3 = MockTypicalDocumentFactory.relation("HJ")
val relationObjectType = Relation(
key = Block.Fields.TYPE_KEY,
name = "Object Type",
format = Relation.Format.OBJECT,
source = Relation.Source.DERIVED
)
val value1 = MockDataFactory.randomString()
val value2 = MockDataFactory.randomString()
val value3 = MockDataFactory.randomString()
val objectFields = Block.Fields(
mapOf(
r1.key to value1,
r2.key to value2,
r3.key to value3,
relationObjectType.key to objectTypeId,
Relations.FEATURED_RELATIONS to listOf(relationObjectType.key),
Relations.LAYOUT to ObjectType.Layout.NOTE.code.toDouble()
)
)
val objectTypeFields = Block.Fields(
mapOf(
Block.Fields.NAME_KEY to objectTypeName,
Block.Fields.DESCRIPTION_KEY to objectTypeDescription
)
)
val customDetails = Block.Details(
mapOf(
root to objectFields,
objectTypeId to objectTypeFields
)
)
stubInterceptEvents()
stubInterceptThreadStatus()
stubGetObjectTypes(objectTypes = listOf())
stubGetDefaultObjectType(null)
stubOpenDocument(
document = doc,
details = customDetails,
relations = listOf(r1, r2, r3, relationObjectType)
)
val vm = buildViewModel()
vm.onStart(root)
val expected = listOf(
BlockView.TitleNote(
id = BlockView.TitleNote.INTERNAL_ID
),
BlockView.FeaturedRelation(
id = featuredBlock.id,
relations = listOf(
DocumentRelationView.ObjectType(
relationId = relationObjectType.key,
name = objectTypeName,
value = null,
isFeatured = true,
type = objectTypeId
)
)
)
)
vm.state.test().assertValue(ViewState.Success(expected))
}
}

View file

@ -0,0 +1,253 @@
package com.anytypeio.anytype.presentation.editor.editor
import MockDataFactory
import android.util.Log
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.anytypeio.anytype.core_models.Block
import com.anytypeio.anytype.core_models.ObjectType
import com.anytypeio.anytype.core_models.SmartBlockType
import com.anytypeio.anytype.presentation.editor.EditorViewModel
import com.anytypeio.anytype.presentation.util.CoroutinesTestRule
import net.lachlanmckee.timberjunit.TimberTestRule
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mockito.MockitoAnnotations
import kotlin.test.assertFalse
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
class EditorObjectTypeChangeWidgetTest : EditorPresentationTestSetup() {
@get:Rule
val timberTestRule: TimberTestRule = TimberTestRule.builder()
.minPriority(Log.DEBUG)
.showThread(true)
.showTimestamp(false)
.onlyLogWhenTestFails(true)
.build()
@get:Rule
val rule = InstantTaskExecutorRule()
@get:Rule
val coroutineTestRule = CoroutinesTestRule()
@Before
fun setup() {
MockitoAnnotations.openMocks(this)
}
@After
fun after() {
coroutineTestRule.advanceTime(EditorViewModel.TEXT_CHANGES_DEBOUNCE_DURATION)
}
@Test
fun `should show widget on note object with one empty text block`() {
val paragraph = Block(
id = MockDataFactory.randomUuid(),
fields = Block.Fields.empty(),
children = emptyList(),
content = Block.Content.Text(
text = "",
marks = emptyList(),
style = Block.Content.Text.Style.P
)
)
val featuredBlock = Block(
id = "featuredRelations",
fields = Block.Fields.empty(),
children = emptyList(),
content = Block.Content.FeaturedRelations
)
val header = Block(
id = "header",
content = Block.Content.Layout(
type = Block.Content.Layout.Type.HEADER
),
fields = Block.Fields.empty(),
children = listOf(featuredBlock.id)
)
val page = Block(
id = root,
fields = Block.Fields(emptyMap()),
content = Block.Content.Smart(SmartBlockType.PAGE),
children = listOf(header.id, paragraph.id)
)
val doc = listOf(page, header, paragraph, featuredBlock)
val objectDetails = Block.Fields(
mapOf(
"type" to ObjectType.NOTE_URL,
"layout" to ObjectType.Layout.NOTE.code.toDouble()
)
)
val detailsList = Block.Details(details = mapOf(root to objectDetails))
stubInterceptEvents()
stubInterceptThreadStatus()
stubGetObjectTypes(objectTypes = listOf())
stubGetDefaultObjectType(type = ObjectType.NOTE_URL)
stubOpenDocument(
document = doc,
details = detailsList
)
val vm = buildViewModel()
vm.onStart(root)
val state = vm.controlPanelViewState.value
val objectTypesWidget = state?.objectTypesToolbar
assertNotNull(objectTypesWidget)
assertTrue(objectTypesWidget.isVisible)
}
@Test
fun `should not show widget on note object with one not empty text block`() {
val paragraph = Block(
id = MockDataFactory.randomUuid(),
fields = Block.Fields.empty(),
children = emptyList(),
content = Block.Content.Text(
text = "F",
marks = emptyList(),
style = Block.Content.Text.Style.P
)
)
val featuredBlock = Block(
id = "featuredRelations",
fields = Block.Fields.empty(),
children = emptyList(),
content = Block.Content.FeaturedRelations
)
val header = Block(
id = "header",
content = Block.Content.Layout(
type = Block.Content.Layout.Type.HEADER
),
fields = Block.Fields.empty(),
children = listOf(featuredBlock.id)
)
val page = Block(
id = root,
fields = Block.Fields(emptyMap()),
content = Block.Content.Smart(SmartBlockType.PAGE),
children = listOf(header.id, paragraph.id)
)
val doc = listOf(page, header, paragraph, featuredBlock)
val objectDetails = Block.Fields(
mapOf(
"type" to ObjectType.NOTE_URL,
"layout" to ObjectType.Layout.NOTE.code.toDouble()
)
)
val detailsList = Block.Details(details = mapOf(root to objectDetails))
stubInterceptEvents()
stubInterceptThreadStatus()
stubGetObjectTypes(objectTypes = listOf())
stubGetDefaultObjectType(type = ObjectType.NOTE_URL)
stubOpenDocument(
document = doc,
details = detailsList
)
val vm = buildViewModel()
vm.onStart(root)
val state = vm.controlPanelViewState.value
val objectTypesWidget = state?.objectTypesToolbar
assertNotNull(objectTypesWidget)
assertFalse(objectTypesWidget.isVisible)
}
@Test
fun `should show widget on note object layout with one empty text block and default not note type`() {
val paragraph = Block(
id = MockDataFactory.randomUuid(),
fields = Block.Fields.empty(),
children = emptyList(),
content = Block.Content.Text(
text = "",
marks = emptyList(),
style = Block.Content.Text.Style.P
)
)
val featuredBlock = Block(
id = "featuredRelations",
fields = Block.Fields.empty(),
children = emptyList(),
content = Block.Content.FeaturedRelations
)
val header = Block(
id = "header",
content = Block.Content.Layout(
type = Block.Content.Layout.Type.HEADER
),
fields = Block.Fields.empty(),
children = listOf(featuredBlock.id)
)
val page = Block(
id = root,
fields = Block.Fields(emptyMap()),
content = Block.Content.Smart(SmartBlockType.PAGE),
children = listOf(header.id, paragraph.id)
)
val doc = listOf(page, header, paragraph, featuredBlock)
val objectDetails = Block.Fields(
mapOf(
"type" to ObjectType.NOTE_URL,
"layout" to ObjectType.Layout.NOTE.code.toDouble()
)
)
val detailsList = Block.Details(details = mapOf(root to objectDetails))
stubInterceptEvents()
stubInterceptThreadStatus()
stubGetObjectTypes(objectTypes = listOf())
stubGetDefaultObjectType(type = ObjectType.PAGE_URL)
stubOpenDocument(
document = doc,
details = detailsList
)
val vm = buildViewModel()
vm.onStart(root)
val state = vm.controlPanelViewState.value
val objectTypesWidget = state?.objectTypesToolbar
assertNotNull(objectTypesWidget)
assertTrue(objectTypesWidget.isVisible)
}
}

View file

@ -20,6 +20,7 @@ import com.anytypeio.anytype.domain.dataview.interactor.SearchObjects
import com.anytypeio.anytype.domain.dataview.interactor.SetRelationKey
import com.anytypeio.anytype.domain.download.DownloadFile
import com.anytypeio.anytype.domain.event.interactor.InterceptEvents
import com.anytypeio.anytype.domain.launch.GetDefaultEditorType
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.domain.objects.SetObjectIsArchived
import com.anytypeio.anytype.domain.page.*
@ -184,6 +185,9 @@ open class EditorPresentationTestSetup {
@Mock
lateinit var searchObjects: SearchObjects
@Mock
lateinit var getDefaultEditorType: GetDefaultEditorType
private val builder: UrlBuilder get() = UrlBuilder(gateway)
private lateinit var updateDetail: UpdateDetail
@ -266,7 +270,8 @@ open class EditorPresentationTestSetup {
updateDetail = updateDetail,
getCompatibleObjectTypes = getCompatibleObjectTypes,
objectTypesProvider = objectTypesProvider,
searchObjects = searchObjects
searchObjects = searchObjects,
getDefaultEditorType = getDefaultEditorType
)
}
@ -514,4 +519,10 @@ open class EditorPresentationTestSetup {
onBlocking { invoke(any()) } doReturn Either.Right(listOf())
}
}
fun stubGetDefaultObjectType(type: String?) {
getDefaultEditorType.stub {
onBlocking { invoke(Unit) } doReturn Either.Right(GetDefaultEditorType.Response(type))
}
}
}

View file

@ -1,6 +1,5 @@
package com.anytypeio.anytype.presentation.splash
import MockDataFactory
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.anytypeio.anytype.analytics.base.Analytics
import com.anytypeio.anytype.core_models.ObjectType
@ -15,8 +14,8 @@ import com.anytypeio.anytype.domain.base.Either
import com.anytypeio.anytype.domain.block.interactor.sets.StoreObjectTypes
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.config.UserSettingsRepository
import com.anytypeio.anytype.domain.launch.GetDefaultPageType
import com.anytypeio.anytype.domain.launch.SetDefaultPageType
import com.anytypeio.anytype.domain.launch.GetDefaultEditorType
import com.anytypeio.anytype.domain.launch.SetDefaultEditorType
import com.anytypeio.anytype.presentation.util.CoroutinesTestRule
import kotlinx.coroutines.runBlocking
import org.junit.Before
@ -62,10 +61,10 @@ class SplashViewModelTest {
private lateinit var getLastOpenedObject: GetLastOpenedObject
@Mock
private lateinit var setDefaultPageType: SetDefaultPageType
private lateinit var setDefaultEditorType: SetDefaultEditorType
@Mock
private lateinit var getDefaultPageType: GetDefaultPageType
private lateinit var getDefaultEditorType: GetDefaultEditorType
lateinit var vm: SplashViewModel
@ -90,8 +89,8 @@ class SplashViewModelTest {
analytics = analytics,
storeObjectTypes = storeObjectTypes,
getLastOpenedObject = getLastOpenedObject,
setDefaultPageType = setDefaultPageType,
getDefaultPageType = getDefaultPageType
setDefaultEditorType = setDefaultEditorType,
getDefaultEditorType = getDefaultEditorType
)
}
@ -125,14 +124,14 @@ class SplashViewModelTest {
stubLaunchWallet()
stubLaunchAccount()
stubGetLastOpenedObject()
getDefaultPageType.stub {
getDefaultEditorType.stub {
onBlocking { invoke(Unit) } doReturn Either.Left(Exception("error"))
}
initViewModel()
runBlocking {
verify(getDefaultPageType, times(1)).invoke(any())
verify(getDefaultEditorType, times(1)).invoke(any())
verify(checkAuthorizationStatus, times(1)).invoke(any())
}
}
@ -151,7 +150,7 @@ class SplashViewModelTest {
initViewModel()
runBlocking {
verify(getDefaultPageType, times(1)).invoke(any())
verify(getDefaultEditorType, times(1)).invoke(any())
verify(checkAuthorizationStatus, times(1)).invoke(any())
}
}
@ -312,14 +311,14 @@ class SplashViewModelTest {
}
private fun stubGetDefaultObjectType(type: String?) {
getDefaultPageType.stub {
onBlocking { invoke(Unit) } doReturn Either.Right(GetDefaultPageType.Response(type))
getDefaultEditorType.stub {
onBlocking { invoke(Unit) } doReturn Either.Right(GetDefaultEditorType.Response(type))
}
}
private fun stubSetDefaultObjectType(type: String) {
setDefaultPageType.stub {
onBlocking { invoke(SetDefaultPageType.Params(type)) } doReturn Either.Right(Unit)
setDefaultEditorType.stub {
onBlocking { invoke(SetDefaultEditorType.Params(type)) } doReturn Either.Right(Unit)
}
}
}