Feature | Create a bookmark from pasted url (#2074)
This commit is contained in:
parent
4d806ab010
commit
03df82390d
|
@ -99,6 +99,7 @@ object EventsDictionary {
|
|||
const val BLOCK_DOWNLOAD_FILE = "DownloadFile"
|
||||
const val BLOCK_UPLOAD = "BlockUpload"
|
||||
const val BLOCK_SETUP_BOOKMARK = "BlockBookmarkFetch"
|
||||
const val BLOCK_CREATE_BOOKMARK = "BlockBookmarkCreateAndFetch"
|
||||
const val BLOCK_PASTE = "BlockPaste"
|
||||
const val BLOCK_COPY = "BlockCopy"
|
||||
const val BLOCK_DIVIDER_UPDATE = "BlockListSetDivStyle"
|
||||
|
|
|
@ -6,7 +6,6 @@ import androidx.fragment.app.testing.launchFragmentInContainer
|
|||
import com.anytypeio.anytype.R
|
||||
import com.anytypeio.anytype.analytics.base.Analytics
|
||||
import com.anytypeio.anytype.core_models.*
|
||||
import com.anytypeio.anytype.core_utils.tools.Counter
|
||||
import com.anytypeio.anytype.domain.`object`.ObjectTypesProvider
|
||||
import com.anytypeio.anytype.domain.`object`.UpdateDetail
|
||||
import com.anytypeio.anytype.domain.base.Either
|
||||
|
@ -32,6 +31,7 @@ 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.*
|
||||
import com.anytypeio.anytype.domain.page.bookmark.CreateBookmark
|
||||
import com.anytypeio.anytype.domain.page.bookmark.SetupBookmark
|
||||
import com.anytypeio.anytype.domain.sets.FindObjectSetForType
|
||||
import com.anytypeio.anytype.domain.status.InterceptThreadStatus
|
||||
|
@ -49,6 +49,7 @@ import com.anytypeio.anytype.presentation.editor.editor.pattern.DefaultPatternMa
|
|||
import com.anytypeio.anytype.presentation.editor.render.DefaultBlockViewRenderer
|
||||
import com.anytypeio.anytype.presentation.editor.selection.SelectionStateHolder
|
||||
import com.anytypeio.anytype.presentation.editor.toggle.ToggleStateHolder
|
||||
import com.anytypeio.anytype.presentation.util.CopyFileToCacheDirectory
|
||||
import com.anytypeio.anytype.presentation.util.Dispatcher
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
|
@ -73,6 +74,7 @@ open class EditorTestSetup {
|
|||
lateinit var updateAlignment: UpdateAlignment
|
||||
lateinit var replaceBlock: ReplaceBlock
|
||||
lateinit var setupBookmark: SetupBookmark
|
||||
lateinit var createBookmark: CreateBookmark
|
||||
lateinit var uploadBlock: UploadBlock
|
||||
lateinit var splitBlock: SplitBlock
|
||||
lateinit var createPage: CreatePage
|
||||
|
@ -82,6 +84,9 @@ open class EditorTestSetup {
|
|||
lateinit var updateDetail: UpdateDetail
|
||||
lateinit var getCompatibleObjectTypes: GetCompatibleObjectTypes
|
||||
|
||||
@Mock
|
||||
lateinit var copyFileToCacheDirectory: CopyFileToCacheDirectory
|
||||
|
||||
@Mock
|
||||
lateinit var openPage: OpenPage
|
||||
@Mock
|
||||
|
@ -232,6 +237,7 @@ open class EditorTestSetup {
|
|||
getDefaultEditorType = GetDefaultEditorType(userSettingsRepository)
|
||||
createObjectSet = CreateObjectSet(repo)
|
||||
findObjectSetForType = FindObjectSetForType(repo)
|
||||
createBookmark = CreateBookmark(repo)
|
||||
|
||||
TestEditorFragment.testViewModelFactory = EditorViewModelFactory(
|
||||
openPage = openPage,
|
||||
|
@ -247,7 +253,6 @@ open class EditorTestSetup {
|
|||
urlBuilder = urlBuilder,
|
||||
renderer = DefaultBlockViewRenderer(
|
||||
urlBuilder = urlBuilder,
|
||||
counter = Counter.Default(),
|
||||
toggleStateHolder = ToggleStateHolder.Default(),
|
||||
coverImageHashProvider = coverImageHashProvider
|
||||
),
|
||||
|
@ -290,7 +295,8 @@ open class EditorTestSetup {
|
|||
turnIntoDocument = turnIntoDocument,
|
||||
turnIntoStyle = turnIntoStyle,
|
||||
updateBlocksMark = updateBlocksMark,
|
||||
setObjectType = setObjectType
|
||||
setObjectType = setObjectType,
|
||||
createBookmark = createBookmark
|
||||
),
|
||||
createNewDocument = createNewDocument,
|
||||
interceptThreadStatus = interceptThreadStatus,
|
||||
|
@ -303,7 +309,8 @@ open class EditorTestSetup {
|
|||
searchObjects = getSearchObjects,
|
||||
getDefaultEditorType = getDefaultEditorType,
|
||||
createObjectSet = createObjectSet,
|
||||
findObjectSetForType = findObjectSetForType
|
||||
findObjectSetForType = findObjectSetForType,
|
||||
copyFileToCacheDirectory = copyFileToCacheDirectory
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ import com.anytypeio.anytype.presentation.relations.providers.ObjectDetailProvid
|
|||
import com.anytypeio.anytype.presentation.sets.ObjectSet
|
||||
import com.anytypeio.anytype.presentation.sets.ObjectSetSession
|
||||
import com.anytypeio.anytype.presentation.sets.RelationValueDVViewModel
|
||||
import com.anytypeio.anytype.presentation.util.CopyFileToCacheDirectory
|
||||
import com.anytypeio.anytype.presentation.util.Dispatcher
|
||||
import com.anytypeio.anytype.ui.relations.RelationValueBaseFragment
|
||||
import com.anytypeio.anytype.utils.*
|
||||
|
@ -52,6 +53,9 @@ class DisplayRelationObjectValueTest {
|
|||
@Mock
|
||||
lateinit var gateway: Gateway
|
||||
|
||||
@Mock
|
||||
lateinit var copyFileToCacheDirectory: CopyFileToCacheDirectory
|
||||
|
||||
private lateinit var addRelationOption: AddDataViewRelationOption
|
||||
private lateinit var removeTagFromDataViewRecord: RemoveTagFromDataViewRecord
|
||||
private lateinit var removeStatusFromDataViewRecord: RemoveStatusFromDataViewRecord
|
||||
|
@ -95,9 +99,9 @@ class DisplayRelationObjectValueTest {
|
|||
removeTagFromRecord = removeTagFromDataViewRecord,
|
||||
removeStatusFromDataViewRecord = removeStatusFromDataViewRecord,
|
||||
urlBuilder = urlBuilder,
|
||||
dispatcher = dispatcher,
|
||||
updateDataViewRecord = updateDataViewRecord,
|
||||
addFileToRecord = addFileToRecord
|
||||
addFileToRecord = addFileToRecord,
|
||||
copyFileToCache = copyFileToCacheDirectory
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ import com.anytypeio.anytype.presentation.relations.providers.ObjectDetailProvid
|
|||
import com.anytypeio.anytype.presentation.sets.ObjectSet
|
||||
import com.anytypeio.anytype.presentation.sets.ObjectSetSession
|
||||
import com.anytypeio.anytype.presentation.sets.RelationValueDVViewModel
|
||||
import com.anytypeio.anytype.presentation.util.CopyFileToCacheDirectory
|
||||
import com.anytypeio.anytype.presentation.util.Dispatcher
|
||||
import com.anytypeio.anytype.ui.relations.RelationValueBaseFragment
|
||||
import com.anytypeio.anytype.utils.CoroutinesTestRule
|
||||
|
@ -55,6 +56,9 @@ class DisplayRelationStatusValueTest {
|
|||
@Mock
|
||||
lateinit var gateway: Gateway
|
||||
|
||||
@Mock
|
||||
lateinit var copyFileToCacheDirectory: CopyFileToCacheDirectory
|
||||
|
||||
private lateinit var addRelationOption: AddDataViewRelationOption
|
||||
private lateinit var removeTagFromDataViewRecord: RemoveTagFromDataViewRecord
|
||||
private lateinit var removeStatusFromDataViewRecord: RemoveStatusFromDataViewRecord
|
||||
|
@ -98,9 +102,9 @@ class DisplayRelationStatusValueTest {
|
|||
removeTagFromRecord = removeTagFromDataViewRecord,
|
||||
removeStatusFromDataViewRecord = removeStatusFromDataViewRecord,
|
||||
urlBuilder = urlBuilder,
|
||||
dispatcher = dispatcher,
|
||||
updateDataViewRecord = updateDataViewRecord,
|
||||
addFileToRecord = addFileToRecord
|
||||
addFileToRecord = addFileToRecord,
|
||||
copyFileToCache = copyFileToCacheDirectory
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ import com.anytypeio.anytype.presentation.relations.providers.ObjectDetailProvid
|
|||
import com.anytypeio.anytype.presentation.sets.ObjectSet
|
||||
import com.anytypeio.anytype.presentation.sets.ObjectSetSession
|
||||
import com.anytypeio.anytype.presentation.sets.RelationValueDVViewModel
|
||||
import com.anytypeio.anytype.presentation.util.CopyFileToCacheDirectory
|
||||
import com.anytypeio.anytype.presentation.util.Dispatcher
|
||||
import com.anytypeio.anytype.ui.relations.RelationValueBaseFragment
|
||||
import com.anytypeio.anytype.utils.CoroutinesTestRule
|
||||
|
@ -55,6 +56,9 @@ class DisplayRelationTagValueTest {
|
|||
@Mock
|
||||
lateinit var dispatcher: Dispatcher<Payload>
|
||||
|
||||
@Mock
|
||||
lateinit var copyFileToCacheDirectory: CopyFileToCacheDirectory
|
||||
|
||||
private lateinit var addRelationOption: AddDataViewRelationOption
|
||||
private lateinit var removeTagFromDataViewRecord: RemoveTagFromDataViewRecord
|
||||
private lateinit var removeStatusFromDataViewRecord: RemoveStatusFromDataViewRecord
|
||||
|
@ -98,9 +102,9 @@ class DisplayRelationTagValueTest {
|
|||
removeTagFromRecord = removeTagFromDataViewRecord,
|
||||
removeStatusFromDataViewRecord = removeStatusFromDataViewRecord,
|
||||
urlBuilder = urlBuilder,
|
||||
dispatcher = dispatcher,
|
||||
updateDataViewRecord = updateDataViewRecord,
|
||||
addFileToRecord = addFileToRecord
|
||||
addFileToRecord = addFileToRecord,
|
||||
copyFileToCache = copyFileToCacheDirectory
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ import com.anytypeio.anytype.presentation.relations.providers.ObjectDetailProvid
|
|||
import com.anytypeio.anytype.presentation.sets.ObjectSet
|
||||
import com.anytypeio.anytype.presentation.sets.ObjectSetSession
|
||||
import com.anytypeio.anytype.presentation.sets.RelationValueDVViewModel
|
||||
import com.anytypeio.anytype.presentation.util.CopyFileToCacheDirectory
|
||||
import com.anytypeio.anytype.presentation.util.Dispatcher
|
||||
import com.anytypeio.anytype.ui.relations.RelationValueBaseFragment
|
||||
import com.anytypeio.anytype.utils.*
|
||||
|
@ -51,6 +52,9 @@ class EditRelationTagValueTest {
|
|||
@Mock
|
||||
lateinit var dispatcher: Dispatcher<Payload>
|
||||
|
||||
@Mock
|
||||
lateinit var copyFileToCacheDirectory: CopyFileToCacheDirectory
|
||||
|
||||
private lateinit var addRelationOption: AddDataViewRelationOption
|
||||
private lateinit var removeTagFromDataViewRecord: RemoveTagFromDataViewRecord
|
||||
private lateinit var removeStatusFromDataViewRecord: RemoveStatusFromDataViewRecord
|
||||
|
@ -94,9 +98,9 @@ class EditRelationTagValueTest {
|
|||
removeTagFromRecord = removeTagFromDataViewRecord,
|
||||
removeStatusFromDataViewRecord = removeStatusFromDataViewRecord,
|
||||
urlBuilder = urlBuilder,
|
||||
dispatcher = dispatcher,
|
||||
updateDataViewRecord = updateDataViewRecord,
|
||||
addFileToRecord = addFileToRecord
|
||||
addFileToRecord = addFileToRecord,
|
||||
copyFileToCache = copyFileToCacheDirectory
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ 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.*
|
||||
import com.anytypeio.anytype.domain.page.bookmark.CreateBookmark
|
||||
import com.anytypeio.anytype.domain.page.bookmark.SetupBookmark
|
||||
import com.anytypeio.anytype.domain.relations.AddFileToObject
|
||||
import com.anytypeio.anytype.domain.sets.FindObjectSetForType
|
||||
|
@ -237,6 +238,7 @@ object EditorSessionModule {
|
|||
updateFields: UpdateFields,
|
||||
updateAlignment: UpdateAlignment,
|
||||
setupBookmark: SetupBookmark,
|
||||
createBookmark: CreateBookmark,
|
||||
turnIntoDocument: TurnIntoDocument,
|
||||
setObjectType: SetObjectType,
|
||||
matcher: DefaultPatternMatcher,
|
||||
|
@ -277,6 +279,7 @@ object EditorSessionModule {
|
|||
updateText = updateText,
|
||||
updateAlignment = updateAlignment,
|
||||
setupBookmark = setupBookmark,
|
||||
createBookmark = createBookmark,
|
||||
move = move,
|
||||
paste = paste,
|
||||
copy = copy,
|
||||
|
@ -589,6 +592,15 @@ object EditorUseCaseModule {
|
|||
repo = repo
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@PerScreen
|
||||
fun provideCreateBookmarkUseCase(
|
||||
repo: BlockRepository
|
||||
): CreateBookmark = CreateBookmark(
|
||||
repo = repo
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@PerScreen
|
||||
|
|
|
@ -49,6 +49,7 @@ import com.anytypeio.anytype.R
|
|||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.Position
|
||||
import com.anytypeio.anytype.core_models.SyncStatus
|
||||
import com.anytypeio.anytype.core_models.Url
|
||||
import com.anytypeio.anytype.core_models.ext.getFirstLinkOrObjectMarkupParam
|
||||
import com.anytypeio.anytype.core_models.ext.getSubstring
|
||||
import com.anytypeio.anytype.core_ui.extensions.addTextFromSelectedStart
|
||||
|
@ -119,7 +120,6 @@ import kotlinx.coroutines.flow.*
|
|||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.util.ArrayList
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.abs
|
||||
|
||||
|
@ -1645,6 +1645,10 @@ open class EditorFragment : NavigationFragment(R.layout.fragment_editor),
|
|||
}
|
||||
}
|
||||
|
||||
override fun onUrlPasted(url: Url) {
|
||||
vm.onUrlPasted(url)
|
||||
}
|
||||
|
||||
private fun hideKeyboard() {
|
||||
Timber.d("Hiding keyboard")
|
||||
hideSoftInput()
|
||||
|
|
|
@ -279,6 +279,20 @@ sealed class Command {
|
|||
val url: String
|
||||
)
|
||||
|
||||
/**
|
||||
* Command for creating a bookmark block from [url]
|
||||
* @property context id of the context
|
||||
* @property target id of the target block (future bookmark block)
|
||||
* @property url bookmark url
|
||||
* @property [position] position relative to [target] block
|
||||
*/
|
||||
data class CreateBookmark(
|
||||
val context: Id,
|
||||
val target: Id,
|
||||
val url: String,
|
||||
val position: Position
|
||||
)
|
||||
|
||||
/**
|
||||
* Command for undoing latest changes in document
|
||||
* @property context id of the context
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
package com.anytypeio.anytype.core_ui.tools
|
||||
|
||||
import com.anytypeio.anytype.core_models.Url
|
||||
|
||||
interface ClipboardInterceptor {
|
||||
|
||||
fun onClipboardAction(action: Action)
|
||||
fun onUrlPasted(url: Url)
|
||||
|
||||
sealed class Action {
|
||||
data class Copy(val selection: IntRange) : Action()
|
||||
|
|
|
@ -2,6 +2,7 @@ package com.anytypeio.anytype.core_ui.widgets.text
|
|||
|
||||
import android.R.id.copy
|
||||
import android.R.id.paste
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.text.InputType
|
||||
|
@ -9,6 +10,7 @@ import android.text.Spanned
|
|||
import android.text.TextWatcher
|
||||
import android.text.util.Linkify
|
||||
import android.util.AttributeSet
|
||||
import android.util.Patterns
|
||||
import android.view.DragEvent
|
||||
import android.view.KeyEvent
|
||||
import android.view.MotionEvent
|
||||
|
@ -16,6 +18,7 @@ import android.view.inputmethod.EditorInfo
|
|||
import android.view.inputmethod.InputMethodManager
|
||||
import androidx.appcompat.widget.AppCompatEditText
|
||||
import androidx.core.graphics.withTranslation
|
||||
import com.anytypeio.anytype.core_models.Url
|
||||
import com.anytypeio.anytype.core_ui.R
|
||||
import com.anytypeio.anytype.core_ui.features.editor.EditorTouchProcessor
|
||||
import com.anytypeio.anytype.core_ui.tools.*
|
||||
|
@ -204,11 +207,24 @@ class TextInputWidget : AppCompatEditText {
|
|||
when (id) {
|
||||
paste -> {
|
||||
if (clipboardInterceptor != null) {
|
||||
clipboardInterceptor?.onClipboardAction(
|
||||
ClipboardInterceptor.Action.Paste(
|
||||
selection = selectionStart..selectionEnd
|
||||
if (editableText.isNotEmpty()) {
|
||||
clipboardInterceptor?.onClipboardAction(
|
||||
ClipboardInterceptor.Action.Paste(
|
||||
selection = selectionStart..selectionEnd
|
||||
)
|
||||
)
|
||||
)
|
||||
} else {
|
||||
val url = parseUrlFromPastedText()
|
||||
if (url == null) {
|
||||
clipboardInterceptor?.onClipboardAction(
|
||||
ClipboardInterceptor.Action.Paste(
|
||||
selection = selectionStart..selectionEnd
|
||||
)
|
||||
)
|
||||
} else {
|
||||
clipboardInterceptor?.onUrlPasted(url = url)
|
||||
}
|
||||
}
|
||||
consumed = true
|
||||
}
|
||||
}
|
||||
|
@ -231,6 +247,22 @@ class TextInputWidget : AppCompatEditText {
|
|||
}
|
||||
}
|
||||
|
||||
private fun parseUrlFromPastedText(): Url? {
|
||||
val mng = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
val clip = mng.primaryClip
|
||||
return if (clip != null && clip.itemCount > 0) {
|
||||
val txt = clip.getItemAt(0).text
|
||||
val matcher = Patterns.WEB_URL.matcher(txt)
|
||||
if (matcher.matches()) {
|
||||
txt.toString()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDraw(canvas: Canvas?) {
|
||||
// need to draw bg first so that text can be on top during super.onDraw()
|
||||
if (text is Spanned && layout != null) {
|
||||
|
|
|
@ -11,6 +11,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
|
|||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.Url
|
||||
import com.anytypeio.anytype.core_ui.features.editor.BlockAdapter
|
||||
import com.anytypeio.anytype.core_ui.features.editor.BlockViewDiffUtil
|
||||
import com.anytypeio.anytype.core_ui.features.editor.BlockViewDiffUtil.Companion.BACKGROUND_COLOR_CHANGED
|
||||
|
@ -70,6 +71,7 @@ class BlockAdapterTest {
|
|||
|
||||
private val clipboardInterceptor : ClipboardInterceptor = object: ClipboardInterceptor {
|
||||
override fun onClipboardAction(action: ClipboardInterceptor.Action) {}
|
||||
override fun onUrlPasted(url: Url) {}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -7,6 +7,7 @@ import android.text.Spannable
|
|||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import com.anytypeio.anytype.core_models.Url
|
||||
import com.anytypeio.anytype.core_ui.common.Span
|
||||
import com.anytypeio.anytype.core_ui.features.editor.BlockAdapter
|
||||
import com.anytypeio.anytype.core_ui.features.editor.BlockViewDiffUtil
|
||||
|
@ -35,6 +36,7 @@ class HeaderBlockTest {
|
|||
private val context: Context = ApplicationProvider.getApplicationContext()
|
||||
private val clipboardInterceptor: ClipboardInterceptor = object : ClipboardInterceptor {
|
||||
override fun onClipboardAction(action: ClipboardInterceptor.Action) = Unit
|
||||
override fun onUrlPasted(url: Url) {}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -8,9 +8,9 @@ import android.widget.TextView
|
|||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import com.anytypeio.anytype.core_models.Url
|
||||
import com.anytypeio.anytype.core_ui.common.Span
|
||||
import com.anytypeio.anytype.core_ui.features.editor.BlockAdapter
|
||||
import com.anytypeio.anytype.core_ui.features.editor.BlockViewHolder
|
||||
import com.anytypeio.anytype.core_ui.features.editor.EditorDragAndDropListener
|
||||
import com.anytypeio.anytype.core_ui.features.editor.holders.text.Highlight
|
||||
import com.anytypeio.anytype.core_ui.tools.ClipboardInterceptor
|
||||
|
@ -33,6 +33,7 @@ class HighlightingBlockTest {
|
|||
|
||||
private val clipboardInterceptor: ClipboardInterceptor = object : ClipboardInterceptor {
|
||||
override fun onClipboardAction(action: ClipboardInterceptor.Action) = Unit
|
||||
override fun onUrlPasted(url: Url) {}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -6,6 +6,7 @@ import android.text.Editable
|
|||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import com.anytypeio.anytype.core_models.Url
|
||||
import com.anytypeio.anytype.core_ui.MockDataFactory
|
||||
import com.anytypeio.anytype.core_ui.features.editor.holders.text.*
|
||||
import com.anytypeio.anytype.core_ui.tools.ClipboardInterceptor
|
||||
|
@ -34,6 +35,7 @@ class BlockAdapterCursorBindingTest {
|
|||
|
||||
private val clipboardInterceptor: ClipboardInterceptor = object : ClipboardInterceptor {
|
||||
override fun onClipboardAction(action: ClipboardInterceptor.Action) {}
|
||||
override fun onUrlPasted(url: Url) {}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -3,14 +3,16 @@ package com.anytypeio.anytype.core_ui.features.editor
|
|||
import android.content.Context
|
||||
import android.text.Editable
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import com.anytypeio.anytype.core_models.Url
|
||||
import com.anytypeio.anytype.core_ui.tools.ClipboardInterceptor
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.BlockView
|
||||
import java.util.*
|
||||
|
||||
open class BlockAdapterTestSetup {
|
||||
|
||||
val clipboardInterceptor: ClipboardInterceptor = object: ClipboardInterceptor {
|
||||
private val clipboardInterceptor: ClipboardInterceptor = object: ClipboardInterceptor {
|
||||
override fun onClipboardAction(action: ClipboardInterceptor.Action) {}
|
||||
override fun onUrlPasted(url: Url) {}
|
||||
}
|
||||
|
||||
val context: Context = ApplicationProvider.getApplicationContext()
|
||||
|
|
|
@ -171,6 +171,10 @@ class BlockDataRepository(
|
|||
command: Command.SetupBookmark
|
||||
): Payload = factory.remote.setupBookmark(command)
|
||||
|
||||
override suspend fun createBookmark(
|
||||
command: Command.CreateBookmark
|
||||
): Payload = factory.remote.createBookmark(command)
|
||||
|
||||
override suspend fun uploadBlock(command: Command.UploadBlock): Payload =
|
||||
factory.remote.uploadBlock(command)
|
||||
|
||||
|
|
|
@ -40,6 +40,7 @@ interface BlockDataStore {
|
|||
suspend fun removeDocumentCover(ctx: String): Payload
|
||||
suspend fun removeDocumentIcon(ctx: Id): Payload
|
||||
suspend fun setupBookmark(command: Command.SetupBookmark): Payload
|
||||
suspend fun createBookmark(command: Command.CreateBookmark): Payload
|
||||
suspend fun undo(command: Command.Undo): Payload
|
||||
suspend fun redo(command: Command.Redo): Payload
|
||||
suspend fun turnIntoDocument(command: Command.TurnIntoDocument): List<Id>
|
||||
|
|
|
@ -40,6 +40,7 @@ interface BlockRemote {
|
|||
suspend fun removeDocumentIcon(ctx: Id): Payload
|
||||
suspend fun uploadBlock(command: Command.UploadBlock): Payload
|
||||
suspend fun setupBookmark(command: Command.SetupBookmark) : Payload
|
||||
suspend fun createBookmark(command: Command.CreateBookmark): Payload
|
||||
suspend fun undo(command: Command.Undo) : Payload
|
||||
suspend fun redo(command: Command.Redo) : Payload
|
||||
suspend fun turnIntoDocument(command: Command.TurnIntoDocument): List<Id>
|
||||
|
|
|
@ -129,6 +129,10 @@ class BlockRemoteDataStore(private val remote: BlockRemote) : BlockDataStore {
|
|||
command: Command.SetupBookmark
|
||||
): Payload = remote.setupBookmark(command)
|
||||
|
||||
override suspend fun createBookmark(
|
||||
command: Command.CreateBookmark
|
||||
): Payload = remote.createBookmark(command)
|
||||
|
||||
override suspend fun undo(command: Command.Undo) = remote.undo(command)
|
||||
|
||||
override suspend fun redo(command: Command.Redo) = remote.redo(command)
|
||||
|
|
|
@ -95,6 +95,7 @@ interface BlockRepository {
|
|||
suspend fun removeDocumentIcon(ctx: Id): Payload
|
||||
|
||||
suspend fun setupBookmark(command: Command.SetupBookmark): Payload
|
||||
suspend fun createBookmark(command: Command.CreateBookmark): Payload
|
||||
|
||||
suspend fun undo(command: Command.Undo): Undo.Result
|
||||
suspend fun redo(command: Command.Redo): Redo.Result
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
package com.anytypeio.anytype.domain.page.bookmark
|
||||
|
||||
import com.anytypeio.anytype.core_models.Command
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.Payload
|
||||
import com.anytypeio.anytype.core_models.Position
|
||||
import com.anytypeio.anytype.domain.base.BaseUseCase
|
||||
import com.anytypeio.anytype.domain.base.Either
|
||||
import com.anytypeio.anytype.domain.block.repo.BlockRepository
|
||||
|
||||
/**
|
||||
* Use-case for creating a bookmark block from url.
|
||||
*/
|
||||
class CreateBookmark(
|
||||
private val repo: BlockRepository
|
||||
) : BaseUseCase<Payload, CreateBookmark.Params>() {
|
||||
|
||||
override suspend fun run(params: Params) = try {
|
||||
repo.createBookmark(
|
||||
command = Command.CreateBookmark(
|
||||
context = params.context,
|
||||
target = params.target,
|
||||
url = params.url,
|
||||
position = params.position
|
||||
)
|
||||
).let {
|
||||
Either.Right(it)
|
||||
}
|
||||
} catch (t: Throwable) {
|
||||
Either.Left(t)
|
||||
}
|
||||
|
||||
/**
|
||||
* Params for creating a bookmark block from [url]
|
||||
* @property context id of the context
|
||||
* @property target id of the target block (future bookmark block)
|
||||
* @property url bookmark url
|
||||
* @property [position] positon relative to [target] block
|
||||
*/
|
||||
data class Params(
|
||||
val context: Id,
|
||||
val target: Id,
|
||||
val url: String,
|
||||
val position: Position
|
||||
)
|
||||
}
|
|
@ -145,6 +145,10 @@ class BlockMiddleware(
|
|||
command: Command.SetupBookmark
|
||||
): Payload = middleware.setupBookmark(command)
|
||||
|
||||
override suspend fun createBookmark(
|
||||
command: Command.CreateBookmark
|
||||
): Payload = middleware.createAndSetupBookmark(command)
|
||||
|
||||
override suspend fun undo(
|
||||
command: Command.Undo
|
||||
): Payload = middleware.undo(command)
|
||||
|
|
|
@ -728,6 +728,21 @@ class Middleware(
|
|||
return response.event.toPayload()
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun createAndSetupBookmark(command: Command.CreateBookmark): Payload {
|
||||
val request: Rpc.Block.Bookmark.CreateAndFetch.Request = Rpc.Block.Bookmark.CreateAndFetch.Request(
|
||||
contextId = command.context,
|
||||
targetId = command.target,
|
||||
url = command.url,
|
||||
position = command.position.toMiddlewareModel()
|
||||
)
|
||||
if (BuildConfig.DEBUG) logRequest(request)
|
||||
val response = service.blockBookmarkCreateAndFetch(request)
|
||||
if (BuildConfig.DEBUG) logResponse(response)
|
||||
|
||||
return response.event.toPayload()
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun undo(command: Command.Undo): Payload {
|
||||
val request = Rpc.Block.Undo.Request(contextId = command.context)
|
||||
|
|
|
@ -84,6 +84,9 @@ interface MiddlewareService {
|
|||
@Throws(Exception::class)
|
||||
fun blockBookmarkFetch(request: Block.Bookmark.Fetch.Request): Block.Bookmark.Fetch.Response
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun blockBookmarkCreateAndFetch(request: Block.Bookmark.CreateAndFetch.Request): Block.Bookmark.CreateAndFetch.Response
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun blockUpload(request: Block.Upload.Request): Block.Upload.Response
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package com.anytypeio.anytype.middleware.service
|
||||
|
||||
import anytype.Rpc.*
|
||||
import anytype.Rpc.Config
|
||||
import com.anytypeio.anytype.data.auth.exception.BackwardCompatilityNotSupportedException
|
||||
import com.anytypeio.anytype.data.auth.exception.NotFoundObjectException
|
||||
import com.anytypeio.anytype.data.auth.exception.UndoRedoExhaustedException
|
||||
|
@ -314,6 +313,19 @@ class MiddlewareServiceImplementation : MiddlewareService {
|
|||
}
|
||||
}
|
||||
|
||||
override fun blockBookmarkCreateAndFetch(request: Block.Bookmark.CreateAndFetch.Request): Block.Bookmark.CreateAndFetch.Response {
|
||||
val encoded = Service.blockBookmarkCreateAndFetch(
|
||||
Block.Bookmark.CreateAndFetch.Request.ADAPTER.encode(request)
|
||||
)
|
||||
val response = Block.Bookmark.CreateAndFetch.Response.ADAPTER.decode(encoded)
|
||||
val error = response.error
|
||||
if (error != null && error.code != Block.Bookmark.CreateAndFetch.Response.Error.Code.NULL) {
|
||||
throw Exception(error.description)
|
||||
} else {
|
||||
return response
|
||||
}
|
||||
}
|
||||
|
||||
override fun blockUpload(request: Block.Upload.Request): Block.Upload.Response {
|
||||
val encoded = Service.blockUpload(Block.Upload.Request.ADAPTER.encode(request))
|
||||
val response = Block.Upload.Response.ADAPTER.decode(encoded)
|
||||
|
|
|
@ -3132,6 +3132,22 @@ class EditorViewModel(
|
|||
}
|
||||
}
|
||||
|
||||
fun onUrlPasted(url: Url) {
|
||||
val focus = orchestrator.stores.focus.current()
|
||||
if (!focus.isEmpty) {
|
||||
viewModelScope.launch {
|
||||
orchestrator.proxies.intents.send(
|
||||
Intent.Bookmark.CreateBookmark(
|
||||
context = context,
|
||||
target = focus.id,
|
||||
position = Position.TOP,
|
||||
url = url
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onClickListener(clicked: ListenerType) {
|
||||
Timber.d("onClickListener, clicked:[$clicked]")
|
||||
if (mode is EditorMode.Styling) {
|
||||
|
|
|
@ -180,12 +180,17 @@ sealed class Intent {
|
|||
}
|
||||
|
||||
sealed class Bookmark : Intent() {
|
||||
|
||||
class SetupBookmark(
|
||||
val context: Id,
|
||||
val target: Id,
|
||||
val url: String
|
||||
) : Bookmark()
|
||||
class CreateBookmark(
|
||||
val context: Id,
|
||||
val target: Id,
|
||||
val url: String,
|
||||
val position: Position
|
||||
) : Bookmark()
|
||||
}
|
||||
|
||||
sealed class Divider : Intent() {
|
||||
|
|
|
@ -4,6 +4,7 @@ import com.anytypeio.anytype.analytics.base.Analytics
|
|||
import com.anytypeio.anytype.analytics.base.EventsDictionary.BLOCK_BACKGROUND_COLOR
|
||||
import com.anytypeio.anytype.analytics.base.EventsDictionary.BLOCK_COPY
|
||||
import com.anytypeio.anytype.analytics.base.EventsDictionary.BLOCK_CREATE
|
||||
import com.anytypeio.anytype.analytics.base.EventsDictionary.BLOCK_CREATE_BOOKMARK
|
||||
import com.anytypeio.anytype.analytics.base.EventsDictionary.BLOCK_DIVIDER_UPDATE
|
||||
import com.anytypeio.anytype.analytics.base.EventsDictionary.BLOCK_DOWNLOAD_FILE
|
||||
import com.anytypeio.anytype.analytics.base.EventsDictionary.BLOCK_DUPLICATE
|
||||
|
@ -42,6 +43,7 @@ import com.anytypeio.anytype.domain.editor.Editor.Focus
|
|||
import com.anytypeio.anytype.domain.page.Redo
|
||||
import com.anytypeio.anytype.domain.page.Undo
|
||||
import com.anytypeio.anytype.domain.page.UpdateTitle
|
||||
import com.anytypeio.anytype.domain.page.bookmark.CreateBookmark
|
||||
import com.anytypeio.anytype.domain.page.bookmark.SetupBookmark
|
||||
import com.anytypeio.anytype.presentation.editor.Editor
|
||||
import com.anytypeio.anytype.presentation.extension.getAnalyticsEvent
|
||||
|
@ -68,6 +70,7 @@ class Orchestrator(
|
|||
private val updateAlignment: UpdateAlignment,
|
||||
private val uploadBlock: UploadBlock,
|
||||
private val setupBookmark: SetupBookmark,
|
||||
private val createBookmark: CreateBookmark,
|
||||
private val turnIntoDocument: TurnIntoDocument,
|
||||
private val updateFields: UpdateFields,
|
||||
private val move: Move,
|
||||
|
@ -664,6 +667,32 @@ class Orchestrator(
|
|||
}
|
||||
)
|
||||
}
|
||||
is Intent.Bookmark.CreateBookmark -> {
|
||||
val startTime = System.currentTimeMillis()
|
||||
createBookmark(
|
||||
params = CreateBookmark.Params(
|
||||
context = intent.context,
|
||||
target = intent.target,
|
||||
url = intent.url,
|
||||
position = intent.position
|
||||
)
|
||||
).proceed(
|
||||
failure = { throwable ->
|
||||
proxies.errors.send(throwable)
|
||||
},
|
||||
success = { payload ->
|
||||
val event = EventAnalytics.Anytype(
|
||||
name = BLOCK_CREATE_BOOKMARK,
|
||||
props = Props.empty(),
|
||||
duration = EventAnalytics.Duration(
|
||||
start = startTime,
|
||||
middleware = System.currentTimeMillis()
|
||||
)
|
||||
)
|
||||
defaultPayloadWithEvent(Pair(payload, event))
|
||||
}
|
||||
)
|
||||
}
|
||||
is Intent.Clipboard.Paste -> {
|
||||
val startTime = System.currentTimeMillis()
|
||||
paste(
|
||||
|
|
|
@ -25,6 +25,7 @@ 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.*
|
||||
import com.anytypeio.anytype.domain.page.bookmark.CreateBookmark
|
||||
import com.anytypeio.anytype.domain.page.bookmark.SetupBookmark
|
||||
import com.anytypeio.anytype.domain.sets.FindObjectSetForType
|
||||
import com.anytypeio.anytype.domain.status.InterceptThreadStatus
|
||||
|
@ -166,6 +167,9 @@ open class EditorViewModelTest {
|
|||
@Mock
|
||||
lateinit var setupBookmark: SetupBookmark
|
||||
|
||||
@Mock
|
||||
lateinit var createBookmark: CreateBookmark
|
||||
|
||||
@Mock
|
||||
lateinit var createDocument: CreateDocument
|
||||
|
||||
|
@ -3964,6 +3968,7 @@ open class EditorViewModelTest {
|
|||
),
|
||||
updateAlignment = updateAlignment,
|
||||
setupBookmark = setupBookmark,
|
||||
createBookmark = createBookmark,
|
||||
paste = paste,
|
||||
copy = copy,
|
||||
move = move,
|
||||
|
@ -3973,7 +3978,7 @@ open class EditorViewModelTest {
|
|||
setRelationKey = setRelationKey,
|
||||
turnIntoStyle = turnIntoStyle,
|
||||
updateBlocksMark = updateBlocksMark,
|
||||
setObjectType = setObjectType
|
||||
setObjectType = setObjectType,
|
||||
),
|
||||
dispatcher = Dispatcher.Default(),
|
||||
detailModificationManager = InternalDetailModificationManager(storage.details),
|
||||
|
|
|
@ -24,6 +24,7 @@ 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.*
|
||||
import com.anytypeio.anytype.domain.page.bookmark.CreateBookmark
|
||||
import com.anytypeio.anytype.domain.page.bookmark.SetupBookmark
|
||||
import com.anytypeio.anytype.domain.sets.FindObjectSetForType
|
||||
import com.anytypeio.anytype.domain.status.InterceptThreadStatus
|
||||
|
@ -139,6 +140,9 @@ open class EditorPresentationTestSetup {
|
|||
@Mock
|
||||
lateinit var setupBookmark: SetupBookmark
|
||||
|
||||
@Mock
|
||||
lateinit var createBookmark: CreateBookmark
|
||||
|
||||
@Mock
|
||||
lateinit var createDocument: CreateDocument
|
||||
|
||||
|
@ -242,6 +246,7 @@ open class EditorPresentationTestSetup {
|
|||
),
|
||||
updateAlignment = updateAlignment,
|
||||
setupBookmark = setupBookmark,
|
||||
createBookmark = createBookmark,
|
||||
paste = paste,
|
||||
copy = copy,
|
||||
move = move,
|
||||
|
|
Loading…
Reference in New Issue
Block a user