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

Editor | Feature | Link to (#1746)

* move to redesign

* design fixes

* design fixes

* fixes

* fix

* move to fixes

* fixes

* fix

* ci

* link to screen

* fixes

* delete link to object use case

* link to object

* fixes

* fix

* fix

* Update check.yml
This commit is contained in:
Konstantin Ivanov 2021-08-24 17:00:48 +04:00 committed by GitHub
parent 219fbdb83a
commit 7a054b2d9d
Signed by: github
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 316 additions and 563 deletions

View file

@ -1,12 +1,12 @@
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.`object`.ObjectTypesProvider
import com.anytypeio.anytype.domain.block.interactor.CreateLinkToObject
import com.anytypeio.anytype.domain.block.interactor.sets.GetObjectTypes
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.config.GetConfig
import com.anytypeio.anytype.domain.config.GetFlavourConfig
import com.anytypeio.anytype.domain.dataview.interactor.SearchObjects
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.domain.page.navigation.GetObjectInfoWithLinks
import com.anytypeio.anytype.presentation.linking.LinkToObjectViewModelFactory
import com.anytypeio.anytype.ui.linking.LinkToObjectFragment
import dagger.Module
@ -31,41 +31,30 @@ interface LinkToObjectSubComponent {
@Module
object LinkToObjectModule {
@JvmStatic
@PerScreen
@Provides
fun provideGetPageInfoWithLinks(
repo: BlockRepository
): GetObjectInfoWithLinks = GetObjectInfoWithLinks(repo = repo)
@JvmStatic
@PerScreen
@Provides
fun provideLinkToObjectViewModelFactory(
urlBuilder: UrlBuilder,
getObjectInfoWithLinks: GetObjectInfoWithLinks,
createLinkToObject: CreateLinkToObject,
getConfig: GetConfig,
objectTypesProvider: ObjectTypesProvider
getObjectTypes: GetObjectTypes,
searchObjects: SearchObjects,
getFlavourConfig: GetFlavourConfig,
analytics: Analytics
): LinkToObjectViewModelFactory = LinkToObjectViewModelFactory(
urlBuilder = urlBuilder,
getObjectInfoWithLinks = getObjectInfoWithLinks,
createLinkToObject = createLinkToObject,
getConfig = getConfig,
objectTypesProvider = objectTypesProvider
getObjectTypes = getObjectTypes,
searchObjects = searchObjects,
analytics = analytics,
getFlavourConfig = getFlavourConfig
)
@JvmStatic
@PerScreen
@Provides
fun provideCreateLinkToObjectUseCase(
repo: BlockRepository
): CreateLinkToObject = CreateLinkToObject(repo)
fun getObjectTypes(repo: BlockRepository): GetObjectTypes = GetObjectTypes(repo = repo)
@JvmStatic
@PerScreen
@Provides
fun provideGetConfigUseCase(
repo: BlockRepository
): GetConfig = GetConfig(repo)
fun searchObjects(repo: BlockRepository): SearchObjects = SearchObjects(repo = repo)
}

View file

@ -6,15 +6,12 @@ import androidx.navigation.NavController
import androidx.navigation.navOptions
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.Position
import com.anytypeio.anytype.presentation.navigation.AppNavigation
import com.anytypeio.anytype.presentation.settings.EditorSettings
import com.anytypeio.anytype.ui.archive.ArchiveFragment
import com.anytypeio.anytype.ui.auth.Keys
import com.anytypeio.anytype.ui.auth.account.CreateAccountFragment.Companion.ARGS_CODE
import com.anytypeio.anytype.ui.editor.EditorFragment
import com.anytypeio.anytype.ui.linking.LinkToObjectFragment
import com.anytypeio.anytype.ui.moving.MoveToFragment
import com.anytypeio.anytype.ui.navigation.PageNavigationFragment
import com.anytypeio.anytype.ui.sets.CreateObjectSetFragment
import com.anytypeio.anytype.ui.sets.ObjectSetFragment
@ -168,16 +165,6 @@ class Navigator : AppNavigation {
navController?.navigate(R.id.pageNavigationFragment, bundle)
}
override fun openLinkTo(target: String, context: String, replace: Boolean, position: Position) {
val bundle = bundleOf(
LinkToObjectFragment.TARGET_ID_KEY to target,
LinkToObjectFragment.CONTEXT_ID_KEY to context,
LinkToObjectFragment.REPLACE_KEY to replace,
LinkToObjectFragment.POSITION_KEY to position.name
)
navController?.navigate(R.id.linkToFragment, bundle)
}
override fun openPageSearch() {
navController?.navigate(R.id.pageSearchFragment)
}

View file

@ -52,12 +52,6 @@ abstract class NavigationFragment(
is Command.ExitToDesktop -> navigation.exitToDesktop()
is Command.OpenDebugSettingsScreen -> navigation.openDebugSettings()
is Command.OpenPageNavigationScreen -> navigation.openPageNavigation(command.target)
is Command.OpenLinkToScreen -> navigation.openLinkTo(
command.target,
command.context,
command.replace,
command.position
)
is Command.ExitToDesktopAndOpenPage -> navigation.exitToDesktopAndOpenPage(command.pageId)
is Command.OpenPageSearch -> navigation.openPageSearch()
is Command.OpenCreateSetScreen -> navigation.openCreateSetScreen(command.ctx)

View file

@ -39,8 +39,6 @@ import androidx.transition.TransitionManager
import androidx.transition.TransitionSet
import com.anytypeio.anytype.BuildConfig
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.Block
import com.anytypeio.anytype.core_models.Block.Content.Text
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.ObjectType
import com.anytypeio.anytype.core_models.SyncStatus
@ -82,6 +80,8 @@ import com.anytypeio.anytype.ui.editor.modals.*
import com.anytypeio.anytype.ui.editor.modals.actions.BlockActionToolbarFactory
import com.anytypeio.anytype.ui.editor.sheets.ObjectMenuBaseFragment.DocumentMenuActionReceiver
import com.anytypeio.anytype.ui.editor.sheets.ObjectMenuFragment
import com.anytypeio.anytype.ui.linking.LinkToObjectFragment
import com.anytypeio.anytype.ui.linking.OnLinkToAction
import com.anytypeio.anytype.ui.moving.MoveToFragment
import com.anytypeio.anytype.ui.moving.OnMoveToAction
import com.anytypeio.anytype.ui.objects.ObjectTypeChangeFragment
@ -107,7 +107,6 @@ const val REQUEST_FILE_CODE = 745
@RuntimePermissions
open class EditorFragment : NavigationFragment(R.layout.fragment_editor),
OnFragmentInteractionListener,
AddBlockFragment.AddBlockActionReceiver,
TurnIntoActionReceiver,
SelectProgrammingLanguageReceiver,
RelationTextValueFragment.TextValueEditReceiver,
@ -116,6 +115,7 @@ open class EditorFragment : NavigationFragment(R.layout.fragment_editor),
ClipboardInterceptor,
DocCoverAction,
OnMoveToAction,
OnLinkToAction,
PickiTCallbacks {
private val ctx get() = arg<Id>(ID_KEY)
@ -302,13 +302,6 @@ open class EditorFragment : NavigationFragment(R.layout.fragment_editor),
}
}
override fun onAddObjectClicked(url: String, layout: ObjectType.Layout) {
vm.onAddNewObjectClicked(
type = url,
layout = layout
)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
@ -634,31 +627,6 @@ open class EditorFragment : NavigationFragment(R.layout.fragment_editor),
vm.onAddBookmarkUrl(target = target, url = url)
}
override fun onAddBlockClicked(block: UiBlock) {
when (block) {
UiBlock.TEXT -> vm.onAddTextBlockClicked(Text.Style.P)
UiBlock.HEADER_ONE -> vm.onAddTextBlockClicked(Text.Style.H1)
UiBlock.HEADER_TWO -> vm.onAddTextBlockClicked(Text.Style.H2)
UiBlock.HEADER_THREE -> vm.onAddTextBlockClicked(Text.Style.H3)
UiBlock.HIGHLIGHTED -> vm.onAddTextBlockClicked(Text.Style.QUOTE)
UiBlock.CHECKBOX -> vm.onAddTextBlockClicked(Text.Style.CHECKBOX)
UiBlock.BULLETED -> vm.onAddTextBlockClicked(Text.Style.BULLET)
UiBlock.NUMBERED -> vm.onAddTextBlockClicked(Text.Style.NUMBERED)
UiBlock.TOGGLE -> vm.onAddTextBlockClicked(Text.Style.TOGGLE)
UiBlock.CODE -> vm.onAddTextBlockClicked(Text.Style.CODE_SNIPPET)
UiBlock.PAGE -> vm.onAddNewPageClicked()
UiBlock.FILE -> vm.onAddFileBlockClicked(Block.Content.File.Type.FILE)
UiBlock.IMAGE -> vm.onAddFileBlockClicked(Block.Content.File.Type.IMAGE)
UiBlock.VIDEO -> vm.onAddFileBlockClicked(Block.Content.File.Type.VIDEO)
UiBlock.BOOKMARK -> vm.onAddBookmarkBlockClicked()
UiBlock.LINE_DIVIDER -> vm.onAddDividerBlockClicked(Block.Content.Divider.Style.LINE)
UiBlock.THREE_DOTS -> vm.onAddDividerBlockClicked(Block.Content.Divider.Style.DOTS)
UiBlock.LINK_TO_OBJECT -> vm.onAddLinkToObjectClicked()
UiBlock.RELATION -> vm.onAddRelationBlockClicked()
else -> toast(NOT_IMPLEMENTED_MESSAGE)
}
}
override fun onTurnIntoBlockClicked(target: String, uiBlock: UiBlock) {
vm.onTurnIntoBlockClicked(target, uiBlock)
}
@ -991,6 +959,16 @@ open class EditorFragment : NavigationFragment(R.layout.fragment_editor),
fr.show(childFragmentManager, null)
}
}
is Command.OpenLinkToScreen -> {
lifecycleScope.launch {
hideSoftInput()
delay(DEFAULT_ANIM_DURATION)
val fr = LinkToObjectFragment.new(
target = command.target
)
fr.show(childFragmentManager, null)
}
}
}
}
}
@ -1733,6 +1711,13 @@ open class EditorFragment : NavigationFragment(R.layout.fragment_editor),
)
}
override fun onLinkTo(link: Id, target: Id) {
vm.proceedWithLinkToAction(
link = link,
target = target
)
}
//------------ End of Anytype Custom Context Menu ------------
companion object {

View file

@ -27,6 +27,7 @@ import com.google.android.material.bottomsheet.BottomSheetDialog
import kotlinx.android.synthetic.main.fragment_add_block.*
import javax.inject.Inject
@Deprecated("To be deleted")
class AddBlockFragment : BaseBottomSheetFragment() {
companion object {

View file

@ -1,219 +1,194 @@
package com.anytypeio.anytype.ui.linking
import android.content.res.Resources
import android.graphics.Color
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import androidx.core.view.minusAssign
import androidx.core.view.plusAssign
import android.view.ViewGroup
import android.widget.EditText
import android.widget.FrameLayout
import androidx.core.os.bundleOf
import androidx.core.widget.doAfterTextChanged
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_ui.features.navigation.FilterView
import com.anytypeio.anytype.core_ui.features.navigation.PageNavigationAdapter
import com.anytypeio.anytype.core_ui.layout.AppBarLayoutStateChangeListener
import com.anytypeio.anytype.core_ui.layout.State
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_ui.features.navigation.DefaultObjectViewAdapter
import com.anytypeio.anytype.core_utils.ext.*
import com.anytypeio.anytype.core_utils.ui.ViewState
import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetFragment
import com.anytypeio.anytype.di.common.componentManager
import com.anytypeio.anytype.core_models.Position
import com.anytypeio.anytype.emojifier.Emojifier
import com.anytypeio.anytype.presentation.linking.LinkToObjectViewModel
import com.anytypeio.anytype.presentation.linking.LinkToObjectViewModelFactory
import com.anytypeio.anytype.presentation.navigation.PageNavigationView
import com.anytypeio.anytype.ui.base.ViewStateFragment
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.google.android.material.tabs.TabLayoutMediator
import kotlinx.android.synthetic.main.fragment_link_to_object.*
import kotlinx.android.synthetic.main.view_link_to_object_bottom.*
import kotlinx.android.synthetic.main.view_link_to_object_preview.*
import kotlinx.android.synthetic.main.view_page_navigation_open_bottom.avatarSmall
import kotlinx.android.synthetic.main.view_page_navigation_open_bottom.pageTitleSmall
import kotlinx.coroutines.flow.collect
import com.anytypeio.anytype.presentation.search.ObjectSearchView
import com.anytypeio.anytype.ui.search.ObjectSearchFragment
import com.google.android.material.bottomsheet.BottomSheetBehavior
import kotlinx.android.synthetic.main.fragment_link_to_object.progressBar
import kotlinx.android.synthetic.main.fragment_object_search.*
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
class LinkToObjectFragment :
ViewStateFragment<ViewState<PageNavigationView>>(R.layout.fragment_link_to_object) {
private val position: Position
get() {
val args = requireArguments()
val value = args.getString(POSITION_KEY)
checkNotNull(value)
return Position.valueOf(value)
}
private val replace: Boolean
get() = arguments?.getBoolean(REPLACE_KEY) ?: false
private val target: String
get() = arguments?.getString(TARGET_ID_KEY) ?: throw IllegalStateException("Missing target")
private val targetContext: String
get() = arguments?.getString(CONTEXT_ID_KEY)
?: throw IllegalStateException("Missing target")
class LinkToObjectFragment : BaseBottomSheetFragment() {
private val vm by viewModels<LinkToObjectViewModel> { factory }
@Inject
lateinit var factory: LinkToObjectViewModelFactory
private lateinit var clearSearchText: View
private lateinit var filterInputField: EditText
private val target get() = arg<Id>(ARG_TARGET)
private val moveToAdapter by lazy {
DefaultObjectViewAdapter(
onClick = vm::onObjectClicked
)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? = inflater.inflate(R.layout.fragment_object_search, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
vm.state.observe(viewLifecycleOwner, this)
vm.navigation.observe(viewLifecycleOwner, navObserver)
observeLinkingButtonState()
vm.onViewCreated()
}
private fun observeLinkingButtonState() {
lifecycleScope.launch {
vm.isLinkingDisabled.collect { isDisabled ->
if (isDisabled)
disableLinking()
else
enableLinking()
}
setupFullHeight()
setTransparent()
BottomSheetBehavior.from(sheet).apply {
state = BottomSheetBehavior.STATE_EXPANDED
isHideable = true
addBottomSheetCallback(
object : BottomSheetBehavior.BottomSheetCallback() {
override fun onSlide(bottomSheet: View, slideOffset: Float) {}
override fun onStateChanged(bottomSheet: View, newState: Int) {
if (newState == BottomSheetBehavior.STATE_HIDDEN) {
vm.onBottomSheetHidden()
}
}
}
)
}
vm.state.observe(viewLifecycleOwner, { observe(it) })
clearSearchText = searchView.findViewById(R.id.clearSearchText)
filterInputField = searchView.findViewById(R.id.filterInputField)
filterInputField.setHint(R.string.search)
initialize()
}
private fun enableLinking() {
btnLinkToThisObject.isEnabled = true
btnLinkToObjectSmall.isEnabled = true
btnLinkToThisObject.alpha = 1f
btnLinkToObjectSmall.alpha = 1f
override fun onStart() {
lifecycleScope.launch {
jobs += subscribe(vm.commands) { execute(it) }
}
super.onStart()
vm.onStart()
expand()
}
private fun disableLinking() {
btnLinkToThisObject.isEnabled = false
btnLinkToObjectSmall.isEnabled = false
btnLinkToThisObject.alpha = 0.2f
btnLinkToObjectSmall.alpha = 0.2f
}
override fun render(state: ViewState<PageNavigationView>) {
private fun observe(state: ObjectSearchView) {
when (state) {
ViewState.Init -> {
appBarLayout.addOnOffsetChangedListener(
object : AppBarLayoutStateChangeListener() {
override fun onStateChanged(state: State) {
if (state == State.COLLAPSED) {
pagePreviewSmall.visible()
} else {
pagePreviewSmall.gone()
}
}
}
)
viewPager.adapter = PageNavigationAdapter(
onClick = { vm.onLinkClicked(it, targetContext) }
) { links ->
filterContainer.plusAssign(
FilterView(requireContext()).apply {
cancelClicked = { closeFilterView() }
pageClicked = {
closeFilterView()
vm.onLinkClicked(it, targetContext)
}
bind(links)
}
)
}
TabLayoutMediator(tabLayout, viewPager) { tab, position ->
when (position) {
POSITION_FROM -> tab.text = getString(R.string.page_nav_link_from)
POSITION_TO -> tab.text = getString(R.string.page_nav_links_to)
}
}.attach()
btnLinkToThisObject.setOnClickListener {
vm.onLinkToObjectClicked(
context = targetContext,
target = target,
replace = replace,
position = position
)
}
btnLinkToObjectSmall.setOnClickListener {
vm.onLinkToObjectClicked(
context = targetContext,
target = target,
replace = replace,
position = position
)
}
vm.onStart(targetContext)
}
ViewState.Loading -> {
ObjectSearchView.Loading -> {
recyclerView.invisible()
tvScreenStateMessage.invisible()
tvScreenStateSubMessage.invisible()
progressBar.visible()
}
is ViewState.Success -> {
is ObjectSearchView.Success -> {
progressBar.invisible()
state.data.title.let { title ->
pageTitleSmall.text =
if (title.isEmpty()) getString(R.string.untitled) else title
tvPageTitle.text =
if (title.isEmpty()) getString(R.string.untitled) else title
}
if (state.data.subtitle.isNotEmpty()) {
tvPageSubtitle.visible()
tvPageSubtitle.text = state.data.subtitle
} else {
tvPageSubtitle.text = null
tvPageSubtitle.gone()
}
imageIcon.setImageDrawable(null)
avatarSmall.setImageDrawable(null)
emojiIcon.setImageDrawable(null)
state.data.image?.let { url ->
Glide
.with(imageIcon)
.load(url)
.centerInside()
.circleCrop()
.into(imageIcon)
Glide
.with(avatarSmall)
.load(url)
.centerInside()
.circleCrop()
.into(avatarSmall)
}
state.data.emoji?.let { emoji ->
try {
Glide
.with(emojiIcon)
.load(Emojifier.uri(emoji))
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(emojiIcon)
Glide
.with(avatarSmall)
.load(Emojifier.uri(emoji))
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(avatarSmall)
} catch (e: Exception) {
Timber.e(e, "Error while setting emoji icon for: $emoji")
}
}
(viewPager.adapter as? PageNavigationAdapter)?.setPageLinks(
inbound = state.data.inbound,
outbound = state.data.outbound
)
tvScreenStateMessage.invisible()
tvScreenStateSubMessage.invisible()
recyclerView.visible()
moveToAdapter.submitList(state.objects)
}
is ViewState.Error -> {
ObjectSearchView.EmptyPages -> {
progressBar.invisible()
coordinatorLayout.showSnackbar(state.error)
recyclerView.invisible()
tvScreenStateMessage.visible()
tvScreenStateMessage.text = getString(R.string.search_empty_pages)
tvScreenStateSubMessage.invisible()
}
is ObjectSearchView.NoResults -> {
progressBar.invisible()
recyclerView.invisible()
tvScreenStateMessage.visible()
tvScreenStateMessage.text = getString(R.string.search_no_results, state.searchText)
tvScreenStateSubMessage.visible()
}
is ObjectSearchView.Error -> {
progressBar.invisible()
recyclerView.invisible()
tvScreenStateMessage.visible()
tvScreenStateMessage.text = state.error
tvScreenStateSubMessage.invisible()
}
else -> Timber.d("Skipping state: $state")
}
}
private fun execute(command: LinkToObjectViewModel.Command) {
when (command) {
LinkToObjectViewModel.Command.Exit -> dismiss()
is LinkToObjectViewModel.Command.Link -> {
withParent<OnLinkToAction> {
onLinkTo(
link = command.link,
target = target
)
}
dismiss()
}
}
}
private fun closeFilterView() {
filterContainer.getChildAt(0)?.let { filterContainer.minusAssign(it) }
requireActivity().hideSoftInput()
private fun initialize() {
with(tvScreenTitle) {
text = getString(R.string.link_to)
visible()
}
recyclerView.invisible()
tvScreenStateMessage.invisible()
progressBar.invisible()
clearSearchText.setOnClickListener {
filterInputField.setText(ObjectSearchFragment.EMPTY_FILTER_TEXT)
clearSearchText.invisible()
}
filterInputField.doAfterTextChanged { newText ->
if (newText != null) {
vm.onSearchTextChanged(newText.toString())
}
if (newText.isNullOrEmpty()) {
clearSearchText.invisible()
} else {
clearSearchText.visible()
}
}
with(recyclerView) {
layoutManager = LinearLayoutManager(requireContext())
adapter = moveToAdapter
addItemDecoration(
DividerItemDecoration(context, DividerItemDecoration.VERTICAL).apply {
setDrawable(drawable(R.drawable.divider_object_search))
}
)
}
}
private fun setupFullHeight() {
val lp = (root.layoutParams as FrameLayout.LayoutParams)
lp.height =
Resources.getSystem().displayMetrics.heightPixels - requireActivity().statusBarHeight
root.layoutParams = lp
}
private fun setTransparent() {
with(root) {
background = null
(parent as? View)?.setBackgroundColor(Color.TRANSPARENT)
}
}
override fun injectDependencies() {
@ -225,11 +200,16 @@ class LinkToObjectFragment :
}
companion object {
const val TARGET_ID_KEY = "arg.link_to.target"
const val REPLACE_KEY = "arg.link_to.replace"
const val POSITION_KEY = "arg.link_to.position"
const val CONTEXT_ID_KEY = "arg.link_to.context"
const val POSITION_FROM = 0
const val POSITION_TO = 1
const val ARG_TARGET = "arg.link_to.target"
fun new(target: Id) = LinkToObjectFragment().apply {
arguments = bundleOf(
ARG_TARGET to target
)
}
}
}
interface OnLinkToAction {
fun onLinkTo(link: Id, target: Id)
}

View file

@ -358,11 +358,6 @@
android:defaultValue='""'
app:argType="string" />
</fragment>
<fragment
android:id="@+id/linkToFragment"
android:name="com.anytypeio.anytype.ui.linking.LinkToObjectFragment"
android:label="LinkToFragment"
tools:layout="@layout/fragment_link_to_object" />
<fragment
android:id="@+id/pageSearchFragment"
android:name="com.anytypeio.anytype.ui.search.ObjectSearchFragment"

View file

@ -1,3 +1,3 @@
package com.anytypeio.anytype.core_models
enum class Position { NONE, TOP, BOTTOM, LEFT, RIGHT, INNER }
enum class Position { NONE, TOP, BOTTOM, LEFT, RIGHT, INNER, REPLACE }

View file

@ -247,6 +247,7 @@
<string name="content_desc_object_icon">Object image or emoji</string>
<string name="move">Move</string>
<string name="move_to">Move to</string>
<string name="link_to">Link to</string>
<string name="mention_suggester_new_page">Create new object</string>
<string name="widget_archive_select_pages">Select pages</string>
<string name="your_search_query">Search</string>

View file

@ -207,20 +207,6 @@ class BlockDataRepository(
override suspend fun getListPages(): List<DocumentInfo> = factory.remote.getListPages()
override suspend fun linkToObject(
context: Id,
target: Id,
block: Id,
replace: Boolean,
position: Position
): Payload = factory.remote.linkToObject(
context = context,
target = target,
block = block,
replace = replace,
position = position
)
override suspend fun setRelationKey(command: Command.SetRelationKey): Payload =
factory.remote.setRelationKey(command)

View file

@ -54,14 +54,6 @@ interface BlockDataStore {
suspend fun getListPages(): List<DocumentInfo>
suspend fun linkToObject(
context: String,
target: String,
block: String,
replace: Boolean,
position: Position
): Payload
suspend fun updateDivider(command: Command.UpdateDivider): Payload
suspend fun setFields(command: Command.SetFields): Payload

View file

@ -53,14 +53,6 @@ interface BlockRemote {
suspend fun getListPages(): List<DocumentInfo>
suspend fun linkToObject(
context: String,
target: String,
block: String,
replace: Boolean,
position: Position
): Payload
suspend fun setRelationKey(command: Command.SetRelationKey): Payload
suspend fun updateDivider(command: Command.UpdateDivider): Payload

View file

@ -160,20 +160,6 @@ class BlockRemoteDataStore(private val remote: BlockRemote) : BlockDataStore {
override suspend fun getListPages(): List<DocumentInfo> = remote.getListPages()
override suspend fun linkToObject(
context: String,
target: String,
block: String,
replace: Boolean,
position: Position
): Payload = remote.linkToObject(
context = context,
target = target,
block = block,
replace = replace,
position = position
)
override suspend fun setRelationKey(command: Command.SetRelationKey): Payload =
remote.setRelationKey(command)

View file

@ -1,42 +0,0 @@
package com.anytypeio.anytype.domain.block.interactor
import com.anytypeio.anytype.domain.base.BaseUseCase
import com.anytypeio.anytype.domain.block.interactor.CreateLinkToObject.Params
import com.anytypeio.anytype.core_models.Position
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.Payload
/**
* Use-case for creating a link to existing object.
* @see [Params] for details.
*/
class CreateLinkToObject(
private val repo: BlockRepository
) : BaseUseCase<Payload, Params>() {
override suspend fun run(params: Params) = safe {
repo.linkToObject(
context = params.context,
target = params.target,
block = params.block,
replace = params.replace,
position = params.position
)
}
/**
* Params for creating a link to existing object
* @property [target] represents blocks, relative to which a link will be inserted.
* @property [context] operation's context
* @property [block] id of the block being inserted
* @property [replace] if true, a link will replace [target] block, if false, a link will be inserted below [target]
*/
data class Params(
val context: Id,
val block: Id,
val target: Id,
val replace: Boolean,
val position: Position
)
}

View file

@ -107,14 +107,6 @@ interface BlockRepository {
suspend fun getObjectInfoWithLinks(pageId: String): ObjectInfoWithLinks
suspend fun getListPages(): List<DocumentInfo>
suspend fun linkToObject(
context: Id,
target: Id,
block: Id,
replace: Boolean,
position: Position
): Payload
suspend fun updateDivider(command: Command.UpdateDivider): Payload
suspend fun setFields(command: Command.SetFields): Payload

View file

@ -189,14 +189,6 @@ class BlockMiddleware(
return middleware.setRelationKey(command)
}
override suspend fun linkToObject(
context: String,
target: String,
block: String,
replace: Boolean,
position: Position
): Payload = middleware.linkToObject(context, target, block, replace, position)
override suspend fun updateDivider(
command: Command.UpdateDivider
): Payload = middleware.updateDividerStyle(command)

View file

@ -891,41 +891,6 @@ class Middleware(
return response.objects
}
@Throws(Exception::class)
fun linkToObject(
contextId: String,
targetId: String,
blockId: String,
replace: Boolean,
positionCore: Position
): Payload {
val position: Block.Position = if (replace) {
Block.Position.Replace
} else {
positionCore.toMiddlewareModel()
}
val link = Block.Content.Link(targetBlockId = blockId)
val model = Block(link = link)
val request: Rpc.Block.Create.Request = Rpc.Block.Create.Request(
contextId = contextId,
targetId = targetId,
position = position,
block = model
)
if (BuildConfig.DEBUG) logRequest(request)
val response = service.blockCreate(request)
if (BuildConfig.DEBUG) logResponse(response)
return response.event.toPayload()
}
@Throws(Exception::class)
fun updateDividerStyle(command: Command.UpdateDivider): Payload {
val style = command.style.toMiddlewareModel()

View file

@ -204,6 +204,7 @@ fun Position.toMiddlewareModel(): MBPosition = when (this) {
Position.LEFT -> MBPosition.Left
Position.RIGHT -> MBPosition.Right
Position.INNER -> MBPosition.Inner
Position.REPLACE -> MBPosition.Replace
}
fun BlockSplitMode.toMiddlewareModel() = when (this) {

View file

@ -1827,48 +1827,6 @@ class EditorViewModel(
dispatch(Command.OpenGallery(mediaType = MIME_IMAGE_ALL))
}
fun onAddLinkToObjectClicked() {
Timber.d("onAddLinkToObjectClicked, ")
val focused = blocks.first { it.id == orchestrator.stores.focus.current().id }
val content = focused.content
val replace = content is Content.Text && content.text.isEmpty()
var position: Position = Position.BOTTOM
var target: Id = focused.id
if (!replace && focused.id == context) {
if (focused.children.isEmpty()) {
position = Position.INNER
} else {
position = Position.TOP
target = focused.children.first()
}
}
proceedWithClearingFocus()
viewModelScope.sendEvent(
analytics = analytics,
eventName = EventsDictionary.SCREEN_LINK_TO
)
navigate(
EventWrapper(
AppNavigation.Command.OpenLinkToScreen(
target = target,
context = context,
replace = replace,
position = position
)
)
)
}
fun onAddRelationBlockClicked() {
Timber.d("onAddRelationBlockClicked, ")
@ -2238,6 +2196,7 @@ class EditorViewModel(
onBlockLongPressedClicked(target, dimensions)
}
@Deprecated("To be deleted")
fun onAddBlockToolbarClicked() {
Timber.d("onAddBlockToolbarClicked, ")
@ -4691,7 +4650,8 @@ class EditorViewModel(
proceedWithMoveToButtonClicked(targetId)
}
SlashItem.Actions.LinkTo -> {
onAddLinkToObjectClicked()
onHideKeyboardClicked()
proceedWithLinkToButtonClicked(targetId)
}
}
}
@ -4920,4 +4880,39 @@ class EditorViewModel(
}
}
//endregion
//region LINK TO
private fun proceedWithLinkToButtonClicked(block: Id) {
viewModelScope.sendEvent(
analytics = analytics,
eventName = EventsDictionary.SCREEN_LINK_TO
)
dispatch(Command.OpenLinkToScreen(target = block))
}
fun proceedWithLinkToAction(link: Id, target: Id) {
val targetBlock = blocks.firstOrNull { it.id == target }
if (targetBlock != null) {
val targetContent = targetBlock.content
val position = if (targetContent is Content.Text && targetContent.text.isEmpty()) {
Position.REPLACE
} else {
Position.BOTTOM
}
viewModelScope.launch {
orchestrator.proxies.intents.send(
Intent.CRUD.Create(
context = context,
target = target,
position = position,
prototype = Prototype.Link(target = link)
)
)
}
} else {
Timber.e("Can't find target block for link")
_toasts.offer("Error while creating link")
}
}
//endregion
}

View file

@ -34,6 +34,7 @@ sealed class Command {
val context: String
) : Command()
@Deprecated("To be deleted")
data class OpenAddBlockPanel(val ctx: Id) : Command()
data class Measure(val target: Id) : Command()
@ -103,4 +104,6 @@ sealed class Command {
data class OpenChangeObjectTypeScreen(val ctx: Id, val smartBlockType: SmartBlockType): Command()
data class OpenMoveToScreen(val block: Id): Command()
data class OpenLinkToScreen(val target: Id): Command()
}

View file

@ -1,117 +1,84 @@
package com.anytypeio.anytype.presentation.linking
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.Position
import com.anytypeio.anytype.core_utils.common.EventWrapper
import com.anytypeio.anytype.core_utils.ext.timber
import com.anytypeio.anytype.core_utils.ui.ViewState
import com.anytypeio.anytype.core_utils.ui.ViewStateViewModel
import com.anytypeio.anytype.domain.`object`.ObjectTypesProvider
import com.anytypeio.anytype.domain.block.interactor.CreateLinkToObject
import com.anytypeio.anytype.domain.config.GetConfig
import com.anytypeio.anytype.analytics.base.Analytics
import com.anytypeio.anytype.core_models.*
import com.anytypeio.anytype.domain.block.interactor.sets.GetObjectTypes
import com.anytypeio.anytype.domain.config.GetFlavourConfig
import com.anytypeio.anytype.domain.dataview.interactor.SearchObjects
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.domain.page.navigation.GetObjectInfoWithLinks
import com.anytypeio.anytype.presentation.mapper.getEmojiPath
import com.anytypeio.anytype.presentation.mapper.getImagePath
import com.anytypeio.anytype.presentation.mapper.toView
import com.anytypeio.anytype.presentation.navigation.AppNavigation
import com.anytypeio.anytype.presentation.navigation.PageNavigationView
import com.anytypeio.anytype.presentation.navigation.SupportNavigation
import kotlinx.coroutines.flow.MutableStateFlow
import com.anytypeio.anytype.presentation.search.ObjectSearchViewModel
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.launch
import timber.log.Timber
class LinkToObjectViewModel(
private val urlBuilder: UrlBuilder,
private val getObjectInfoWithLinks: GetObjectInfoWithLinks,
private val createLinkToObject: CreateLinkToObject,
private val getConfig: GetConfig,
private val objectTypesProvider: ObjectTypesProvider
) : ViewStateViewModel<ViewState<PageNavigationView>>(),
SupportNavigation<EventWrapper<AppNavigation.Command>> {
urlBuilder: UrlBuilder,
searchObjects: SearchObjects,
getObjectTypes: GetObjectTypes,
analytics: Analytics,
private val getFlavourConfig: GetFlavourConfig
) : ObjectSearchViewModel(
urlBuilder = urlBuilder,
getObjectTypes = getObjectTypes,
searchObjects = searchObjects,
analytics = analytics,
getFlavourConfig = getFlavourConfig,
) {
private var pageId: String = ""
private var home: Id = ""
val commands = MutableSharedFlow<Command>(replay = 0)
val isLinkingDisabled: MutableStateFlow<Boolean> = MutableStateFlow(true)
override fun getSearchObjectsParams(): SearchObjects.Params {
override val navigation: MutableLiveData<EventWrapper<AppNavigation.Command>> =
MutableLiveData()
val filteredTypes = if (getFlavourConfig.isDataViewEnabled()) {
types.value.map { objectType -> objectType.url }
} else {
listOf(ObjectTypeConst.PAGE)
}
fun onViewCreated() {
stateData.postValue(ViewState.Init)
val filters = listOf(
DVFilter(
condition = DVFilterCondition.EQUAL,
value = false,
relationKey = Relations.IS_ARCHIVED,
operator = DVFilterOperator.AND
),
DVFilter(
relationKey = Relations.IS_HIDDEN,
condition = DVFilterCondition.NOT_EQUAL,
value = true
)
)
val sorts = listOf(
DVSort(
relationKey = Relations.LAST_OPENED_DATE,
type = DVSortType.DESC
)
)
return SearchObjects.Params(
limit = SEARCH_LIMIT,
objectTypeFilter = filteredTypes,
filters = filters,
sorts = sorts,
fulltext = EMPTY_QUERY
)
}
fun onStart(initialTarget: Id) {
override fun onObjectClicked(target: Id, layout: ObjectType.Layout?) {
viewModelScope.launch {
getConfig(Unit).proceed(
failure = { Timber.e(it, "Error while getting config") },
success = { config ->
home = config.home
proceedWithGettingDocumentLinks(initialTarget)
}
)
commands.emit(Command.Link(link = target))
}
}
private fun proceedWithGettingDocumentLinks(target: String) {
stateData.postValue(ViewState.Loading)
override fun onBottomSheetHidden() {
viewModelScope.launch {
getObjectInfoWithLinks.invoke(GetObjectInfoWithLinks.Params(pageId = target)).proceed(
failure = { error ->
error.timber()
stateData.postValue(ViewState.Error(error.message ?: "Unknown error"))
},
success = { response ->
with(response.pageInfoWithLinks) {
pageId = this.id
stateData.postValue(
ViewState.Success(
PageNavigationView(
title = documentInfo.obj.name.orEmpty(),
subtitle = documentInfo.snippet.orEmpty(),
image = documentInfo.obj.getImagePath(urlBuilder),
emoji = documentInfo.obj.getEmojiPath(),
inbound = links.inbound.map { it.toView(urlBuilder, objectTypesProvider.get()) },
outbound = links.outbound.map { it.toView(urlBuilder, objectTypesProvider.get()) }
)
)
)
}
}
)
commands.emit(Command.Exit)
}
}
fun onLinkClicked(
target: Id,
context: Id
) {
isLinkingDisabled.value = (target == context || target == home)
proceedWithGettingDocumentLinks(target)
}
fun onLinkToObjectClicked(
context: Id,
target: Id,
replace: Boolean,
position: Position
) {
viewModelScope.launch {
createLinkToObject(
CreateLinkToObject.Params(
context = context,
target = target,
block = pageId,
replace = replace,
position = position
)
).proceed(
failure = { Timber.e(it, "Error while creating link to object") },
success = { navigate(EventWrapper(AppNavigation.Command.Exit)) }
)
}
sealed class Command {
object Exit : Command()
data class Link(val link: Id) : Command()
}
}

View file

@ -2,28 +2,28 @@ package com.anytypeio.anytype.presentation.linking
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.anytypeio.anytype.domain.`object`.ObjectTypesProvider
import com.anytypeio.anytype.domain.block.interactor.CreateLinkToObject
import com.anytypeio.anytype.domain.config.GetConfig
import com.anytypeio.anytype.analytics.base.Analytics
import com.anytypeio.anytype.domain.block.interactor.sets.GetObjectTypes
import com.anytypeio.anytype.domain.config.GetFlavourConfig
import com.anytypeio.anytype.domain.dataview.interactor.SearchObjects
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.domain.page.navigation.GetObjectInfoWithLinks
class LinkToObjectViewModelFactory(
private val urlBuilder: UrlBuilder,
private val getObjectInfoWithLinks: GetObjectInfoWithLinks,
private val createLinkToObject: CreateLinkToObject,
private val getConfig: GetConfig,
private val objectTypesProvider: ObjectTypesProvider
private val getObjectTypes: GetObjectTypes,
private val searchObjects: SearchObjects,
private val analytics: Analytics,
private val getFlavourConfig: GetFlavourConfig
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return LinkToObjectViewModel(
urlBuilder = urlBuilder,
getObjectInfoWithLinks = getObjectInfoWithLinks,
createLinkToObject = createLinkToObject,
getConfig = getConfig,
objectTypesProvider = objectTypesProvider
getObjectTypes = getObjectTypes,
searchObjects = searchObjects,
analytics = analytics,
getFlavourConfig = getFlavourConfig
) as T
}
}

View file

@ -40,7 +40,6 @@ interface AppNavigation {
fun exitToDesktop()
fun openDebugSettings()
fun openPageNavigation(target: String)
fun openLinkTo(target: String, context: String, replace: Boolean, position: Position)
fun openPageSearch()
fun exitToDesktopAndOpenPage(pageId: String)
fun exitToInvitationCodeScreen()
@ -82,13 +81,6 @@ interface AppNavigation {
data class OpenPageNavigationScreen(val target: String) : Command()
data class OpenLinkToScreen(
val context: String,
val target: String,
val replace: Boolean,
val position: Position
) : Command()
data class ExitToDesktopAndOpenPage(val pageId: String) : Command()
object OpenPageSearch : Command()