diff --git a/CHANGELOG.md b/CHANGELOG.md index 314f248793..5b12717bda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Change log for Android @Anytype app. +## Version 0.0.24 (WIP) + +### New features 🚀 + +* User can add bookmark placeholder and create bookmark from url (#140) + +### Fixes & tech 🚒 + +* Refactored block creation in `Middleware` (introduced factory to create a block from a block prototype) (#140) +* New mappers (from middleware layer entity to data layer entity) (#140) + +### Middleware ⚙️ + +* Added `blockBookmarkFetch` command (#140) + ## Version 0.0.23 ### New features 🚀 diff --git a/app/src/main/java/com/agileburo/anytype/di/common/ComponentManager.kt b/app/src/main/java/com/agileburo/anytype/di/common/ComponentManager.kt index ba37692f5f..c556e67bc2 100644 --- a/app/src/main/java/com/agileburo/anytype/di/common/ComponentManager.kt +++ b/app/src/main/java/com/agileburo/anytype/di/common/ComponentManager.kt @@ -122,6 +122,13 @@ class ComponentManager(private val main: MainComponent) { .build() } + val createBookmarkSubComponent = Component { + main + .createBookmarkBuilder() + .createBookmarkModule(CreateBookmarkModule()) + .build() + } + class Component(private val builder: () -> T) { private var instance: T? = null diff --git a/app/src/main/java/com/agileburo/anytype/di/feature/CreateBookmarkDI.kt b/app/src/main/java/com/agileburo/anytype/di/feature/CreateBookmarkDI.kt new file mode 100644 index 0000000000..49a553d1cc --- /dev/null +++ b/app/src/main/java/com/agileburo/anytype/di/feature/CreateBookmarkDI.kt @@ -0,0 +1,45 @@ +package com.agileburo.anytype.di.feature + +import com.agileburo.anytype.core_utils.di.scope.PerScreen +import com.agileburo.anytype.domain.block.repo.BlockRepository +import com.agileburo.anytype.domain.page.bookmark.SetupBookmark +import com.agileburo.anytype.presentation.page.bookmark.CreateBookmarkViewModel +import com.agileburo.anytype.ui.page.modals.CreateBookmarkFragment +import dagger.Module +import dagger.Provides +import dagger.Subcomponent + +@Subcomponent(modules = [CreateBookmarkModule::class]) +@PerScreen +interface CreateBookmarkSubComponent { + + @Subcomponent.Builder + interface Builder { + fun createBookmarkModule(module: CreateBookmarkModule): Builder + fun build(): CreateBookmarkSubComponent + } + + fun inject(fragment: CreateBookmarkFragment) +} + +@Module +class CreateBookmarkModule { + + @Provides + @PerScreen + fun provideCreateBookmarkViewModelFactory( + setupBookmark: SetupBookmark + ): CreateBookmarkViewModel.Factory { + return CreateBookmarkViewModel.Factory( + setupBookmark = setupBookmark + ) + } + + @Provides + @PerScreen + fun provideSetBookmarkUrlUseCase( + repo: BlockRepository + ): SetupBookmark = SetupBookmark( + repo = repo + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/agileburo/anytype/di/main/DataModule.kt b/app/src/main/java/com/agileburo/anytype/di/main/DataModule.kt index 5fd5c5e4c2..156642ada2 100644 --- a/app/src/main/java/com/agileburo/anytype/di/main/DataModule.kt +++ b/app/src/main/java/com/agileburo/anytype/di/main/DataModule.kt @@ -16,6 +16,8 @@ import com.agileburo.anytype.middleware.EventProxy import com.agileburo.anytype.middleware.auth.AuthMiddleware import com.agileburo.anytype.middleware.block.BlockMiddleware import com.agileburo.anytype.middleware.interactor.Middleware +import com.agileburo.anytype.middleware.interactor.MiddlewareFactory +import com.agileburo.anytype.middleware.interactor.MiddlewareMapper import com.agileburo.anytype.middleware.service.DefaultMiddlewareService import com.agileburo.anytype.middleware.service.MiddlewareService import com.agileburo.anytype.persistence.db.AnytypeDatabase @@ -149,8 +151,18 @@ class DataModule { @Provides @Singleton fun provideMiddleware( - service: MiddlewareService - ): Middleware = Middleware(service) + service: MiddlewareService, + factory: MiddlewareFactory, + mapper: MiddlewareMapper + ): Middleware = Middleware(service, factory, mapper) + + @Provides + @Singleton + fun provideMiddlewareFactory(): MiddlewareFactory = MiddlewareFactory() + + @Provides + @Singleton + fun provideMiddlewareMapper(): MiddlewareMapper = MiddlewareMapper() @Provides @Singleton diff --git a/app/src/main/java/com/agileburo/anytype/di/main/MainComponent.kt b/app/src/main/java/com/agileburo/anytype/di/main/MainComponent.kt index 4ab1c1c6ed..fe402c5c7c 100644 --- a/app/src/main/java/com/agileburo/anytype/di/main/MainComponent.kt +++ b/app/src/main/java/com/agileburo/anytype/di/main/MainComponent.kt @@ -34,4 +34,5 @@ interface MainComponent { fun pageComponentBuilder(): PageSubComponent.Builder fun linkAddComponentBuilder(): LinkSubComponent.Builder fun pageIconPickerBuilder(): PageIconPickerSubComponent.Builder + fun createBookmarkBuilder(): CreateBookmarkSubComponent.Builder } \ No newline at end of file diff --git a/app/src/main/java/com/agileburo/anytype/ui/page/PageFragment.kt b/app/src/main/java/com/agileburo/anytype/ui/page/PageFragment.kt index 775a89cade..9e0b4ec79c 100644 --- a/app/src/main/java/com/agileburo/anytype/ui/page/PageFragment.kt +++ b/app/src/main/java/com/agileburo/anytype/ui/page/PageFragment.kt @@ -31,6 +31,7 @@ import com.agileburo.anytype.core_ui.widgets.toolbar.OptionToolbarWidget.Option import com.agileburo.anytype.core_ui.widgets.toolbar.OptionToolbarWidget.OptionConfig.OPTION_LIST_BULLETED_LIST import com.agileburo.anytype.core_ui.widgets.toolbar.OptionToolbarWidget.OptionConfig.OPTION_LIST_CHECKBOX import com.agileburo.anytype.core_ui.widgets.toolbar.OptionToolbarWidget.OptionConfig.OPTION_LIST_NUMBERED_LIST +import com.agileburo.anytype.core_ui.widgets.toolbar.OptionToolbarWidget.OptionConfig.OPTION_MEDIA_BOOKMARK import com.agileburo.anytype.core_ui.widgets.toolbar.OptionToolbarWidget.OptionConfig.OPTION_MEDIA_VIDEO import com.agileburo.anytype.core_ui.widgets.toolbar.OptionToolbarWidget.OptionConfig.OPTION_TEXT_HEADER_ONE import com.agileburo.anytype.core_ui.widgets.toolbar.OptionToolbarWidget.OptionConfig.OPTION_TEXT_HEADER_THREE @@ -49,6 +50,7 @@ import com.agileburo.anytype.ext.extractMarks import com.agileburo.anytype.presentation.page.PageViewModel import com.agileburo.anytype.presentation.page.PageViewModelFactory import com.agileburo.anytype.ui.base.NavigationFragment +import com.agileburo.anytype.ui.page.modals.CreateBookmarkFragment import com.agileburo.anytype.ui.page.modals.PageIconPickerFragment import com.agileburo.anytype.ui.page.modals.SetLinkFragment import com.google.android.material.bottomsheet.BottomSheetBehavior @@ -107,7 +109,8 @@ open class PageFragment : NavigationFragment(R.layout.fragment_page), onPageIconClicked = vm::onPageIconClicked, onAddUrlClick = vm::onAddVideoUrlClicked, onAddLocalVideoClick = vm::onAddLocalVideoClicked, - strVideoError = getString(R.string.error) + strVideoError = getString(R.string.error), + onBookmarkPlaceholderClicked = vm::onBookmarkPlaceholderClicked ) } @@ -173,8 +176,10 @@ open class PageFragment : NavigationFragment(R.layout.fragment_page), wasSuccessful: Boolean, Reason: String? ) { - Timber.d("PickiTonCompleteListener path:$path, wasDriveFile$wasDriveFile, " + - "wasUnknownProvider:$wasUnknownProvider, wasSuccessful:$wasSuccessful, reason:$Reason") + Timber.d( + "PickiTonCompleteListener path:$path, wasDriveFile$wasDriveFile, " + + "wasUnknownProvider:$wasUnknownProvider, wasSuccessful:$wasSuccessful, reason:$Reason" + ) vm.onAddVideoFileClicked(filePath = path) } @@ -356,6 +361,7 @@ open class PageFragment : NavigationFragment(R.layout.fragment_page), is Option.Media -> { when (option.type) { OPTION_MEDIA_VIDEO -> vm.onAddVideoBlockClicked() + OPTION_MEDIA_BOOKMARK -> vm.onAddBookmarkClicked() else -> toast(NOT_IMPLEMENTED_MESSAGE) } } @@ -443,6 +449,12 @@ open class PageFragment : NavigationFragment(R.layout.fragment_page), target = command.target ).show(childFragmentManager, null) } + is PageViewModel.Command.OpenBookmarkSetter -> { + CreateBookmarkFragment.newInstance( + context = command.context, + target = command.target + ).show(childFragmentManager, null) + } is PageViewModel.Command.OpenGallery -> { openGalleryWithPermissionCheck(command.mediaType) } diff --git a/app/src/main/java/com/agileburo/anytype/ui/page/modals/CreateBookmarkFragment.kt b/app/src/main/java/com/agileburo/anytype/ui/page/modals/CreateBookmarkFragment.kt new file mode 100644 index 0000000000..4c7039659e --- /dev/null +++ b/app/src/main/java/com/agileburo/anytype/ui/page/modals/CreateBookmarkFragment.kt @@ -0,0 +1,108 @@ +package com.agileburo.anytype.ui.page.modals + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.os.bundleOf +import androidx.fragment.app.DialogFragment +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProviders +import androidx.lifecycle.lifecycleScope +import com.agileburo.anytype.R +import com.agileburo.anytype.core_ui.reactive.clicks +import com.agileburo.anytype.core_utils.ext.toast +import com.agileburo.anytype.core_utils.ui.BaseBottomSheetFragment +import com.agileburo.anytype.di.common.componentManager +import com.agileburo.anytype.presentation.page.bookmark.CreateBookmarkViewModel +import com.agileburo.anytype.presentation.page.bookmark.CreateBookmarkViewModel.ViewState +import kotlinx.android.synthetic.main.dialog_create_bookmark.* +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import javax.inject.Inject + +class CreateBookmarkFragment : BaseBottomSheetFragment(), Observer { + + private val target: String + get() = requireArguments() + .getString(ARG_TARGET) + ?: throw IllegalStateException(MISSING_TARGET_ERROR) + + private val context: String + get() = requireArguments() + .getString(ARG_CONTEXT) + ?: throw IllegalStateException(MISSING_CONTEXT_ERROR) + + @Inject + lateinit var factory: CreateBookmarkViewModel.Factory + + private val vm by lazy { + ViewModelProviders + .of(this, factory) + .get(CreateBookmarkViewModel::class.java) + } + + companion object { + + private const val ARG_CONTEXT = "arg.create.bookmark.context" + private const val ARG_TARGET = "arg.create.bookmark.target" + + private const val MISSING_TARGET_ERROR = "Target missing in args" + private const val MISSING_CONTEXT_ERROR = "Context missing in args" + + fun newInstance( + context: String, + target: String + ): CreateBookmarkFragment = CreateBookmarkFragment().apply { + arguments = bundleOf( + ARG_CONTEXT to context, + ARG_TARGET to target + ) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setStyle(DialogFragment.STYLE_NORMAL, R.style.DialogStyle) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? = inflater.inflate(R.layout.dialog_create_bookmark, container, false) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + createBookmarkButton + .clicks() + .onEach { + vm.onCreateBookmarkClicked( + context = context, + target = target, + url = urlInput.text.toString() + ) + } + .launchIn(lifecycleScope) + } + + override fun onActivityCreated(savedInstanceState: Bundle?) { + super.onActivityCreated(savedInstanceState) + vm.state.observe(viewLifecycleOwner, this) + } + + override fun onChanged(state: ViewState) { + if (state is ViewState.Exit) + dismiss() + else if (state is ViewState.Error) + toast(state.message) + } + + override fun injectDependencies() { + componentManager().createBookmarkSubComponent.get().inject(this) + } + + override fun releaseDependencies() { + componentManager().createBookmarkSubComponent.release() + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_create_bookmark.xml b/app/src/main/res/layout/dialog_create_bookmark.xml new file mode 100644 index 0000000000..5b947de224 --- /dev/null +++ b/app/src/main/res/layout/dialog_create_bookmark.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 33e2f37018..f73aede11c 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -15,5 +15,6 @@ #3F51B5 #DFDDD0 + #ACA996 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a6c60006d5..81b025d438 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -100,5 +100,7 @@ Do the computation of an expensive paragraph of text on a background thread: Page icon Remove + Create bookmark + Paste or type a URL diff --git a/core-ui/src/main/java/com/agileburo/anytype/core_ui/features/page/BlockAdapter.kt b/core-ui/src/main/java/com/agileburo/anytype/core_ui/features/page/BlockAdapter.kt index e370d0e528..13bc01c9ab 100644 --- a/core-ui/src/main/java/com/agileburo/anytype/core_ui/features/page/BlockAdapter.kt +++ b/core-ui/src/main/java/com/agileburo/anytype/core_ui/features/page/BlockAdapter.kt @@ -7,6 +7,7 @@ import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import com.agileburo.anytype.core_ui.R import com.agileburo.anytype.core_ui.features.page.BlockViewHolder.Companion.HOLDER_BOOKMARK +import com.agileburo.anytype.core_ui.features.page.BlockViewHolder.Companion.HOLDER_BOOKMARK_PLACEHOLDER import com.agileburo.anytype.core_ui.features.page.BlockViewHolder.Companion.HOLDER_BULLET import com.agileburo.anytype.core_ui.features.page.BlockViewHolder.Companion.HOLDER_CHECKBOX import com.agileburo.anytype.core_ui.features.page.BlockViewHolder.Companion.HOLDER_CODE_SNIPPET @@ -53,10 +54,11 @@ class BlockAdapter( private val onPageClicked: (String) -> Unit, private val onTextInputClicked: () -> Unit, private val onAddUrlClick: (String, String) -> Unit, - private val onAddLocalVideoClick : (String) -> Unit, + private val onAddLocalVideoClick: (String) -> Unit, private val strVideoError: String, private val onPageIconClicked: () -> Unit, - private val onDownloadFileClicked: (String) -> Unit + private val onDownloadFileClicked: (String) -> Unit, + private val onBookmarkPlaceholderClicked: (String) -> Unit ) : RecyclerView.Adapter() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BlockViewHolder { @@ -235,6 +237,15 @@ class BlockAdapter( ) ) } + HOLDER_BOOKMARK_PLACEHOLDER -> { + BlockViewHolder.Bookmark.Placeholder( + view = inflater.inflate( + R.layout.item_block_bookmark_placeholder, + parent, + false + ) + ) + } HOLDER_PICTURE -> { BlockViewHolder.Picture( view = inflater.inflate( @@ -454,7 +465,13 @@ class BlockAdapter( } is BlockViewHolder.Bookmark -> { holder.bind( - item = blocks[position] as BlockView.Bookmark + item = blocks[position] as BlockView.Bookmark.View + ) + } + is BlockViewHolder.Bookmark.Placeholder -> { + holder.bind( + item = blocks[position] as BlockView.Bookmark.Placeholder, + onBookmarkPlaceholderClicked = onBookmarkPlaceholderClicked ) } is BlockViewHolder.Picture -> { diff --git a/core-ui/src/main/java/com/agileburo/anytype/core_ui/features/page/BlockView.kt b/core-ui/src/main/java/com/agileburo/anytype/core_ui/features/page/BlockView.kt index 50c7ae21ee..b492ac05ab 100644 --- a/core-ui/src/main/java/com/agileburo/anytype/core_ui/features/page/BlockView.kt +++ b/core-ui/src/main/java/com/agileburo/anytype/core_ui/features/page/BlockView.kt @@ -5,6 +5,7 @@ import com.agileburo.anytype.core_ui.common.Focusable import com.agileburo.anytype.core_ui.common.Markup import com.agileburo.anytype.core_ui.common.ViewType import com.agileburo.anytype.core_ui.features.page.BlockViewHolder.Companion.HOLDER_BOOKMARK +import com.agileburo.anytype.core_ui.features.page.BlockViewHolder.Companion.HOLDER_BOOKMARK_PLACEHOLDER import com.agileburo.anytype.core_ui.features.page.BlockViewHolder.Companion.HOLDER_BULLET import com.agileburo.anytype.core_ui.features.page.BlockViewHolder.Companion.HOLDER_CHECKBOX import com.agileburo.anytype.core_ui.features.page.BlockViewHolder.Companion.HOLDER_CODE_SNIPPET @@ -366,21 +367,36 @@ sealed class BlockView : ViewType { /** * UI-model for a bookmark block * @property id block's id - * @property title website's title - * @property title website's content description - * @property url website's url - * @property faviconUrl website's favicon url - * @property imageUrl content's main image url */ - data class Bookmark( - override val id: String, - val url: String, - val title: String?, - val description: String?, - val faviconUrl: String?, - val imageUrl: String? + sealed class Bookmark( + override val id: String ) : BlockView() { - override fun getViewType() = HOLDER_BOOKMARK + + /** + * UI-model for a bookmark placeholder (used when bookmark url is not set) + */ + data class Placeholder(override val id: String) : Bookmark(id = id) { + override fun getViewType() = HOLDER_BOOKMARK_PLACEHOLDER + } + + /** + * UI-model for a bookmark view. + * @property title website's title + * @property description website's content description + * @property url website's url + * @property faviconUrl website's favicon url + * @property imageUrl content's main image url + */ + data class View( + override val id: String, + val url: String, + val title: String?, + val description: String?, + val faviconUrl: String?, + val imageUrl: String? + ) : Bookmark(id = id) { + override fun getViewType() = HOLDER_BOOKMARK + } } /** diff --git a/core-ui/src/main/java/com/agileburo/anytype/core_ui/features/page/BlockViewHolder.kt b/core-ui/src/main/java/com/agileburo/anytype/core_ui/features/page/BlockViewHolder.kt index d09bced5d6..e3209bbb02 100644 --- a/core-ui/src/main/java/com/agileburo/anytype/core_ui/features/page/BlockViewHolder.kt +++ b/core-ui/src/main/java/com/agileburo/anytype/core_ui/features/page/BlockViewHolder.kt @@ -1,8 +1,8 @@ package com.agileburo.anytype.core_ui.features.page import android.graphics.Color -import android.net.Uri import android.graphics.drawable.Drawable +import android.net.Uri import android.text.Editable import android.view.View import android.widget.TextView.BufferType @@ -638,7 +638,6 @@ sealed class BlockViewHolder(view: View) : RecyclerView.ViewHolder(view) { MimeTypes.Category.TABLE -> icon.setImageResource(R.drawable.ic_mime_table) MimeTypes.Category.PRESENTATION -> icon.setImageResource(R.drawable.ic_mime_presentation) MimeTypes.Category.OTHER -> icon.setImageResource(R.drawable.ic_mime_other) - } itemView.setOnClickListener { onDownloadFileClicked(item.id) } } @@ -741,7 +740,7 @@ sealed class BlockViewHolder(view: View) : RecyclerView.ViewHolder(view) { } } - fun bind(item: BlockView.Bookmark) { + fun bind(item: BlockView.Bookmark.View) { title.text = item.title description.text = item.description url.text = item.url @@ -759,6 +758,16 @@ sealed class BlockViewHolder(view: View) : RecyclerView.ViewHolder(view) { .into(logo) } } + + class Placeholder(view: View) : BlockViewHolder(view) { + + fun bind( + item: BlockView.Bookmark.Placeholder, + onBookmarkPlaceholderClicked: (String) -> Unit + ) { + itemView.setOnClickListener { onBookmarkPlaceholderClicked(item.id) } + } + } } class Picture(view: View) : BlockViewHolder(view) { @@ -859,6 +868,7 @@ sealed class BlockViewHolder(view: View) : RecyclerView.ViewHolder(view) { const val HOLDER_VIDEO_UPLOAD = 20 const val HOLDER_VIDEO_EMPTY = 21 const val HOLDER_VIDEO_ERROR = 22 + const val HOLDER_BOOKMARK_PLACEHOLDER = 23 const val FOCUS_TIMEOUT_MILLIS = 16L } diff --git a/core-ui/src/main/res/drawable/ic_bookmark_menu.xml b/core-ui/src/main/res/drawable/ic_bookmark_menu.xml new file mode 100644 index 0000000000..0bfd27b969 --- /dev/null +++ b/core-ui/src/main/res/drawable/ic_bookmark_menu.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/core-ui/src/main/res/drawable/ic_bookmark_placeholder.xml b/core-ui/src/main/res/drawable/ic_bookmark_placeholder.xml new file mode 100644 index 0000000000..a4daebcf21 --- /dev/null +++ b/core-ui/src/main/res/drawable/ic_bookmark_placeholder.xml @@ -0,0 +1,10 @@ + + + diff --git a/core-ui/src/main/res/drawable/ic_video.xml b/core-ui/src/main/res/drawable/ic_video.xml index d9eebd6b0b..2d56586b09 100644 --- a/core-ui/src/main/res/drawable/ic_video.xml +++ b/core-ui/src/main/res/drawable/ic_video.xml @@ -3,11 +3,11 @@ android:height="24dp" android:viewportWidth="25" android:viewportHeight="24"> - - + + diff --git a/core-ui/src/main/res/drawable/rectangle_block_media_background.xml b/core-ui/src/main/res/drawable/rectangle_block_media_background.xml index 82ffbcd1d2..ce028d52ce 100644 --- a/core-ui/src/main/res/drawable/rectangle_block_media_background.xml +++ b/core-ui/src/main/res/drawable/rectangle_block_media_background.xml @@ -4,6 +4,5 @@ - + \ No newline at end of file diff --git a/core-ui/src/main/res/drawable/rectangle_bookmark_placeholder.xml b/core-ui/src/main/res/drawable/rectangle_bookmark_placeholder.xml new file mode 100644 index 0000000000..05d4f9ddcf --- /dev/null +++ b/core-ui/src/main/res/drawable/rectangle_bookmark_placeholder.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/core-ui/src/main/res/drawable/rounded_dialog.xml b/core-ui/src/main/res/drawable/rounded_dialog.xml new file mode 100644 index 0000000000..05bec1b1b7 --- /dev/null +++ b/core-ui/src/main/res/drawable/rounded_dialog.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/core-ui/src/main/res/layout/item_block_bookmark.xml b/core-ui/src/main/res/layout/item_block_bookmark.xml index dd6fc72f96..b7822c71a7 100644 --- a/core-ui/src/main/res/layout/item_block_bookmark.xml +++ b/core-ui/src/main/res/layout/item_block_bookmark.xml @@ -2,10 +2,13 @@ + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:layout_marginTop="6dp" + android:layout_marginEnd="16dp" + android:layout_marginBottom="6dp" + app:cardCornerRadius="8dp"> + + diff --git a/core-ui/src/main/res/layout/item_block_bookmark_placeholder.xml b/core-ui/src/main/res/layout/item_block_bookmark_placeholder.xml new file mode 100644 index 0000000000..dca195b962 --- /dev/null +++ b/core-ui/src/main/res/layout/item_block_bookmark_placeholder.xml @@ -0,0 +1,46 @@ + + + + + + + + + + \ No newline at end of file diff --git a/core-ui/src/main/res/layout/item_block_video_empty.xml b/core-ui/src/main/res/layout/item_block_video_empty.xml index d3f9694006..902facf27d 100644 --- a/core-ui/src/main/res/layout/item_block_video_empty.xml +++ b/core-ui/src/main/res/layout/item_block_video_empty.xml @@ -1,7 +1,6 @@ Untitled Error while loading picture Block with a picture + Tap here to insert a bookmark url + Add a web bookmark diff --git a/core-ui/src/test/java/com/agileburo/anytype/core_ui/BlockAdapterTest.kt b/core-ui/src/test/java/com/agileburo/anytype/core_ui/BlockAdapterTest.kt index bf2fc8a40c..4e3ee29bb7 100644 --- a/core-ui/src/test/java/com/agileburo/anytype/core_ui/BlockAdapterTest.kt +++ b/core-ui/src/test/java/com/agileburo/anytype/core_ui/BlockAdapterTest.kt @@ -851,7 +851,8 @@ class BlockAdapterTest { onPageIconClicked = {}, onAddLocalVideoClick = {}, onAddUrlClick = { _, _ -> }, - strVideoError = "Error" + strVideoError = "Error", + onBookmarkPlaceholderClicked = {} ) } } \ No newline at end of file diff --git a/core-utils/src/main/java/com/agileburo/anytype/core_utils/ui/ViewStateViewModel.kt b/core-utils/src/main/java/com/agileburo/anytype/core_utils/ui/ViewStateViewModel.kt index 276dc39cae..bd3fdef74c 100644 --- a/core-utils/src/main/java/com/agileburo/anytype/core_utils/ui/ViewStateViewModel.kt +++ b/core-utils/src/main/java/com/agileburo/anytype/core_utils/ui/ViewStateViewModel.kt @@ -7,4 +7,5 @@ import androidx.lifecycle.ViewModel open class ViewStateViewModel : ViewModel() { protected val stateData = MutableLiveData() val state: LiveData = stateData + fun update(update: VS) = stateData.postValue(update) } \ No newline at end of file diff --git a/data/src/main/java/com/agileburo/anytype/data/auth/mapper/MapperExtension.kt b/data/src/main/java/com/agileburo/anytype/data/auth/mapper/MapperExtension.kt index ca15c49dd8..dda3820b01 100644 --- a/data/src/main/java/com/agileburo/anytype/data/auth/mapper/MapperExtension.kt +++ b/data/src/main/java/com/agileburo/anytype/data/auth/mapper/MapperExtension.kt @@ -392,6 +392,12 @@ fun Command.UploadVideoBlockUrl.toEntity(): CommandEntity.UploadBlock = CommandE filePath = filePath ) +fun Command.SetupBookmark.toEntity() = CommandEntity.SetupBookmark( + target = target, + context = context, + url = url +) + fun Position.toEntity(): PositionEntity { return PositionEntity.valueOf(name) } @@ -453,6 +459,17 @@ fun EventEntity.toDomain(): Event { fields = fields?.let { Block.Fields(it.map) } ) } + is EventEntity.Command.BookmarkGranularChange -> { + Event.Command.BookmarkGranularChange( + context = context, + target = target, + url = url, + title = title, + description = description, + favicon = faviconHash, + image = imageHash + ) + } is EventEntity.Command.UpdateFields -> { Event.Command.UpdateFields( context = context, @@ -486,7 +503,8 @@ fun Block.Prototype.toEntity(): BlockEntity.Prototype = when (this) { style = BlockEntity.Content.Page.Style.valueOf(this.style.name) ) } - Block.Prototype.Divider -> BlockEntity.Prototype.Divider + is Block.Prototype.Bookmark -> BlockEntity.Prototype.Bookmark + is Block.Prototype.Divider -> BlockEntity.Prototype.Divider is Block.Prototype.File -> { BlockEntity.Prototype.File( type = BlockEntity.Content.File.Type.valueOf(this.type.name), diff --git a/data/src/main/java/com/agileburo/anytype/data/auth/model/BlockEntity.kt b/data/src/main/java/com/agileburo/anytype/data/auth/model/BlockEntity.kt index 5b1b6a6a93..1eba34c185 100644 --- a/data/src/main/java/com/agileburo/anytype/data/auth/model/BlockEntity.kt +++ b/data/src/main/java/com/agileburo/anytype/data/auth/model/BlockEntity.kt @@ -85,7 +85,7 @@ data class BlockEntity( } data class Bookmark( - val url: String, + val url: String?, val title: String?, val description: String?, val image: String?, @@ -104,11 +104,12 @@ data class BlockEntity( val style: Content.Page.Style ) : Prototype() - object Divider : Prototype() - data class File( val state: Content.File.State, val type: Content.File.Type ) : Prototype() + + object Divider : Prototype() + object Bookmark : Prototype() } } \ No newline at end of file diff --git a/data/src/main/java/com/agileburo/anytype/data/auth/model/CommandEntity.kt b/data/src/main/java/com/agileburo/anytype/data/auth/model/CommandEntity.kt index f80be34429..66b849aded 100644 --- a/data/src/main/java/com/agileburo/anytype/data/auth/model/CommandEntity.kt +++ b/data/src/main/java/com/agileburo/anytype/data/auth/model/CommandEntity.kt @@ -84,4 +84,10 @@ class CommandEntity { val target: String, val name: String ) + + data class SetupBookmark( + val context: String, + val target: String, + val url: String + ) } \ No newline at end of file diff --git a/data/src/main/java/com/agileburo/anytype/data/auth/model/EventEntity.kt b/data/src/main/java/com/agileburo/anytype/data/auth/model/EventEntity.kt index f4936768e4..f10288fc55 100644 --- a/data/src/main/java/com/agileburo/anytype/data/auth/model/EventEntity.kt +++ b/data/src/main/java/com/agileburo/anytype/data/auth/model/EventEntity.kt @@ -40,6 +40,16 @@ sealed class EventEntity { val fields: BlockEntity.Fields? ) : Command() + data class BookmarkGranularChange( + override val context: String, + val target: String, + val url: String?, + val title: String?, + val description: String?, + val imageHash: String?, + val faviconHash: String? + ) : Command() + data class UpdateStructure( override val context: String, val id: String, diff --git a/data/src/main/java/com/agileburo/anytype/data/auth/repo/block/BlockDataRepository.kt b/data/src/main/java/com/agileburo/anytype/data/auth/repo/block/BlockDataRepository.kt index 9c8d6c1317..cb1ddae5fc 100644 --- a/data/src/main/java/com/agileburo/anytype/data/auth/repo/block/BlockDataRepository.kt +++ b/data/src/main/java/com/agileburo/anytype/data/auth/repo/block/BlockDataRepository.kt @@ -68,8 +68,13 @@ class BlockDataRepository( override suspend fun split(command: Command.Split) = factory.remote.split(command.toEntity()) - override suspend fun setIconName(command: Command.SetIconName) = - factory.remote.setIconName(command.toEntity()) + override suspend fun setIconName( + command: Command.SetIconName + ) = factory.remote.setIconName(command.toEntity()) + + override suspend fun setupBookmark( + command: Command.SetupBookmark + ) = factory.remote.setupBookmark(command.toEntity()) override suspend fun uploadUrl(command: Command.UploadVideoBlockUrl) { factory.remote.uploadUrl(command.toEntity()) diff --git a/data/src/main/java/com/agileburo/anytype/data/auth/repo/block/BlockDataStore.kt b/data/src/main/java/com/agileburo/anytype/data/auth/repo/block/BlockDataStore.kt index 44b31d681d..641fde7560 100644 --- a/data/src/main/java/com/agileburo/anytype/data/auth/repo/block/BlockDataStore.kt +++ b/data/src/main/java/com/agileburo/anytype/data/auth/repo/block/BlockDataStore.kt @@ -24,4 +24,5 @@ interface BlockDataStore { suspend fun openDashboard(contextId: String, id: String) suspend fun closeDashboard(id: String) suspend fun setIconName(command: CommandEntity.SetIconName) + suspend fun setupBookmark(command: CommandEntity.SetupBookmark) } \ No newline at end of file diff --git a/data/src/main/java/com/agileburo/anytype/data/auth/repo/block/BlockRemote.kt b/data/src/main/java/com/agileburo/anytype/data/auth/repo/block/BlockRemote.kt index 31511ea304..32553f1fec 100644 --- a/data/src/main/java/com/agileburo/anytype/data/auth/repo/block/BlockRemote.kt +++ b/data/src/main/java/com/agileburo/anytype/data/auth/repo/block/BlockRemote.kt @@ -24,4 +24,5 @@ interface BlockRemote { suspend fun closeDashboard(id: String) suspend fun setIconName(command: CommandEntity.SetIconName) suspend fun uploadUrl(command: CommandEntity.UploadBlock) + suspend fun setupBookmark(command: CommandEntity.SetupBookmark) } \ No newline at end of file diff --git a/data/src/main/java/com/agileburo/anytype/data/auth/repo/block/BlockRemoteDataStore.kt b/data/src/main/java/com/agileburo/anytype/data/auth/repo/block/BlockRemoteDataStore.kt index 9311e63e1a..9742165800 100644 --- a/data/src/main/java/com/agileburo/anytype/data/auth/repo/block/BlockRemoteDataStore.kt +++ b/data/src/main/java/com/agileburo/anytype/data/auth/repo/block/BlockRemoteDataStore.kt @@ -65,6 +65,11 @@ class BlockRemoteDataStore(private val remote: BlockRemote) : BlockDataStore { override suspend fun split(command: CommandEntity.Split): String = remote.split(command) - override suspend fun setIconName(command: CommandEntity.SetIconName) = - remote.setIconName(command) + override suspend fun setIconName( + command: CommandEntity.SetIconName + ) = remote.setIconName(command) + + override suspend fun setupBookmark( + command: CommandEntity.SetupBookmark + ) = remote.setupBookmark(command) } \ No newline at end of file diff --git a/domain/src/main/java/com/agileburo/anytype/domain/block/model/Block.kt b/domain/src/main/java/com/agileburo/anytype/domain/block/model/Block.kt index 92fb50c59c..1168c33e2e 100644 --- a/domain/src/main/java/com/agileburo/anytype/domain/block/model/Block.kt +++ b/domain/src/main/java/com/agileburo/anytype/domain/block/model/Block.kt @@ -179,7 +179,7 @@ data class Block( * @property favicon optional hash of bookmark's favicon */ data class Bookmark( - val url: Url, + val url: Url?, val title: String?, val description: String?, val image: Hash?, @@ -205,11 +205,12 @@ data class Block( val style: Content.Page.Style ) : Prototype() - object Divider : Prototype() - data class File( val type: Content.File.Type, val state: Content.File.State ) : Prototype() + + object Divider : Prototype() + object Bookmark : Prototype() } } \ No newline at end of file diff --git a/domain/src/main/java/com/agileburo/anytype/domain/block/model/Command.kt b/domain/src/main/java/com/agileburo/anytype/domain/block/model/Command.kt index 7c9c910f4e..ab876e010f 100644 --- a/domain/src/main/java/com/agileburo/anytype/domain/block/model/Command.kt +++ b/domain/src/main/java/com/agileburo/anytype/domain/block/model/Command.kt @@ -147,4 +147,16 @@ sealed class Command { val target: Id, val name: String ) + + /** + * Command for setting up a bookmark from [url] + * @property context id of the context + * @property target id of the target block (future bookmark block) + * @property url bookmark url + */ + data class SetupBookmark( + val context: Id, + val target: Id, + val url: String + ) } \ No newline at end of file diff --git a/domain/src/main/java/com/agileburo/anytype/domain/block/repo/BlockRepository.kt b/domain/src/main/java/com/agileburo/anytype/domain/block/repo/BlockRepository.kt index cb31167381..e9574cd17f 100644 --- a/domain/src/main/java/com/agileburo/anytype/domain/block/repo/BlockRepository.kt +++ b/domain/src/main/java/com/agileburo/anytype/domain/block/repo/BlockRepository.kt @@ -41,4 +41,6 @@ interface BlockRepository { suspend fun uploadUrl(command: Command.UploadVideoBlockUrl) suspend fun setIconName(command: Command.SetIconName) + + suspend fun setupBookmark(command: Command.SetupBookmark) } \ No newline at end of file diff --git a/domain/src/main/java/com/agileburo/anytype/domain/event/model/Event.kt b/domain/src/main/java/com/agileburo/anytype/domain/event/model/Event.kt index 6a40403f00..4fbdc25d31 100644 --- a/domain/src/main/java/com/agileburo/anytype/domain/event/model/Event.kt +++ b/domain/src/main/java/com/agileburo/anytype/domain/event/model/Event.kt @@ -2,7 +2,9 @@ package com.agileburo.anytype.domain.event.model import com.agileburo.anytype.domain.block.model.Block import com.agileburo.anytype.domain.block.model.Block.Content.Text +import com.agileburo.anytype.domain.common.Hash import com.agileburo.anytype.domain.common.Id +import com.agileburo.anytype.domain.common.Url sealed class Event { @@ -60,7 +62,7 @@ sealed class Event { * @property context update's context * @property id id of the link * @property target id of the linked block - * @property fields link's fields (considered update if not null) + * @property fields link's fields (considered updated if not null) */ data class LinkGranularChange( override val context: String, @@ -69,6 +71,26 @@ sealed class Event { val fields: Block.Fields? ) : Command() + /** + * Command to update bookmark + * @property context id of the context + * @property target id of the bookmark block + * @property url bookmark's url (considered updated if not null) + * @property title bookmark's title (considered updated if not null) + * @property description bookmark's description (considered updated if not null) + * @property image bookmark's image hash (considered updated if not null) + * @property favicon bookmark's favicon hash (considered updated if not null) + */ + data class BookmarkGranularChange( + override val context: Id, + val target: Id, + val url: Url?, + val title: String?, + val description: String?, + val image: Hash?, + val favicon: Hash? + ) : Command() + /** * Command to update a block structure. * @property context context id for this command (i.e page id, dashboard id, etc.) diff --git a/domain/src/main/java/com/agileburo/anytype/domain/page/bookmark/SetupBookmark.kt b/domain/src/main/java/com/agileburo/anytype/domain/page/bookmark/SetupBookmark.kt new file mode 100644 index 0000000000..bac6ace232 --- /dev/null +++ b/domain/src/main/java/com/agileburo/anytype/domain/page/bookmark/SetupBookmark.kt @@ -0,0 +1,41 @@ +package com.agileburo.anytype.domain.page.bookmark + +import com.agileburo.anytype.domain.base.BaseUseCase +import com.agileburo.anytype.domain.base.Either +import com.agileburo.anytype.domain.block.model.Command +import com.agileburo.anytype.domain.block.repo.BlockRepository +import com.agileburo.anytype.domain.common.Id + +/** + * Use-case for setting up (i.e. fetching) a bookmark from url. + */ +class SetupBookmark( + private val repo: BlockRepository +) : BaseUseCase() { + + override suspend fun run(params: Params) = try { + repo.setupBookmark( + command = Command.SetupBookmark( + context = params.context, + target = params.target, + url = params.url + ) + ).let { + Either.Right(it) + } + } catch (t: Throwable) { + Either.Left(t) + } + + /** + * Params for setting up a bookmark from [url] + * @property context id of the context + * @property target id of the target block (future bookmark block) + * @property url bookmark url + */ + data class Params( + val context: Id, + val target: Id, + val url: String + ) +} \ No newline at end of file diff --git a/middleware/src/main/java/com/agileburo/anytype/middleware/MapperExtension.kt b/middleware/src/main/java/com/agileburo/anytype/middleware/MapperExtension.kt index 06cae18d77..c5ed3a22b0 100644 --- a/middleware/src/main/java/com/agileburo/anytype/middleware/MapperExtension.kt +++ b/middleware/src/main/java/com/agileburo/anytype/middleware/MapperExtension.kt @@ -6,6 +6,7 @@ import anytype.model.Models.Account import anytype.model.Models.Block import com.agileburo.anytype.data.auth.model.AccountEntity import com.agileburo.anytype.data.auth.model.BlockEntity +import com.agileburo.anytype.data.auth.model.PositionEntity import com.google.protobuf.Struct import com.google.protobuf.Value @@ -246,7 +247,7 @@ fun Block.Content.File.State.entity(): BlockEntity.Content.File.State = when (th } fun Block.bookmark(): BlockEntity.Content.Bookmark = BlockEntity.Content.Bookmark( - url = bookmark.url, + url = bookmark.url.ifEmpty { null }, description = bookmark.description.ifEmpty { null }, title = bookmark.title.ifEmpty { null }, image = bookmark.imageHash.ifEmpty { null }, @@ -345,4 +346,41 @@ fun Block.Content.Text.Style.entity(): BlockEntity.Content.Text.Style = when (th Block.Content.Text.Style.Toggle -> BlockEntity.Content.Text.Style.TOGGLE Block.Content.Text.Style.Checkbox -> BlockEntity.Content.Text.Style.CHECKBOX else -> throw IllegalStateException("Unexpected text style: $this") +} + +fun BlockEntity.Content.Text.Style.toMiddleware(): Block.Content.Text.Style = when (this) { + BlockEntity.Content.Text.Style.P -> Block.Content.Text.Style.Paragraph + BlockEntity.Content.Text.Style.H1 -> Block.Content.Text.Style.Header1 + BlockEntity.Content.Text.Style.H2 -> Block.Content.Text.Style.Header2 + BlockEntity.Content.Text.Style.H3 -> Block.Content.Text.Style.Header3 + BlockEntity.Content.Text.Style.TITLE -> Block.Content.Text.Style.Title + BlockEntity.Content.Text.Style.QUOTE -> Block.Content.Text.Style.Quote + BlockEntity.Content.Text.Style.BULLET -> Block.Content.Text.Style.Marked + BlockEntity.Content.Text.Style.NUMBERED -> Block.Content.Text.Style.Numbered + BlockEntity.Content.Text.Style.TOGGLE -> Block.Content.Text.Style.Toggle + BlockEntity.Content.Text.Style.CHECKBOX -> Block.Content.Text.Style.Checkbox + else -> throw IllegalStateException("Unexpected text style: $this") +} + +fun BlockEntity.Content.File.State.toMiddleware(): Block.Content.File.State = when (this) { + BlockEntity.Content.File.State.EMPTY -> Block.Content.File.State.Empty + BlockEntity.Content.File.State.ERROR -> Block.Content.File.State.Error + BlockEntity.Content.File.State.UPLOADING -> Block.Content.File.State.Uploading + BlockEntity.Content.File.State.DONE -> Block.Content.File.State.Done +} + +fun BlockEntity.Content.File.Type.toMiddleware(): Block.Content.File.Type = when (this) { + BlockEntity.Content.File.Type.NONE -> Block.Content.File.Type.None + BlockEntity.Content.File.Type.FILE -> Block.Content.File.Type.File + BlockEntity.Content.File.Type.IMAGE -> Block.Content.File.Type.Image + BlockEntity.Content.File.Type.VIDEO -> Block.Content.File.Type.Video +} + +fun PositionEntity.toMiddleware(): Block.Position = when (this) { + PositionEntity.NONE -> Block.Position.None + PositionEntity.LEFT -> Block.Position.Left + PositionEntity.RIGHT -> Block.Position.Right + PositionEntity.TOP -> Block.Position.Top + PositionEntity.BOTTOM -> Block.Position.Bottom + PositionEntity.INNER -> Block.Position.Inner } \ No newline at end of file diff --git a/middleware/src/main/java/com/agileburo/anytype/middleware/block/BlockMiddleware.kt b/middleware/src/main/java/com/agileburo/anytype/middleware/block/BlockMiddleware.kt index 22cde3ed46..b7d6195b86 100644 --- a/middleware/src/main/java/com/agileburo/anytype/middleware/block/BlockMiddleware.kt +++ b/middleware/src/main/java/com/agileburo/anytype/middleware/block/BlockMiddleware.kt @@ -87,6 +87,11 @@ class BlockMiddleware( override suspend fun split(command: CommandEntity.Split): String = middleware.split(command) - override suspend fun setIconName(command: CommandEntity.SetIconName) = - middleware.setIconName(command) + override suspend fun setIconName( + command: CommandEntity.SetIconName + ) = middleware.setIconName(command) + + override suspend fun setupBookmark( + command: CommandEntity.SetupBookmark + ) = middleware.setupBookmark(command) } \ No newline at end of file diff --git a/middleware/src/main/java/com/agileburo/anytype/middleware/interactor/Middleware.java b/middleware/src/main/java/com/agileburo/anytype/middleware/interactor/Middleware.java index 08760918d5..a82d53c537 100644 --- a/middleware/src/main/java/com/agileburo/anytype/middleware/interactor/Middleware.java +++ b/middleware/src/main/java/com/agileburo/anytype/middleware/interactor/Middleware.java @@ -22,9 +22,17 @@ import timber.log.Timber; public class Middleware { private final MiddlewareService service; + private final MiddlewareFactory factory; + private final MiddlewareMapper mapper; - public Middleware(MiddlewareService service) { + public Middleware( + MiddlewareService service, + MiddlewareFactory factory, + MiddlewareMapper mapper + ) { this.service = service; + this.factory = factory; + this.mapper = mapper; } public ConfigEntity getConfig() throws Exception { @@ -219,46 +227,7 @@ public class Middleware { public void updateTextStyle(CommandEntity.UpdateStyle command) throws Exception { - Models.Block.Content.Text.Style style = null; - - switch (command.getStyle()) { - case P: - style = Models.Block.Content.Text.Style.Paragraph; - break; - case H1: - style = Models.Block.Content.Text.Style.Header1; - break; - case H2: - style = Models.Block.Content.Text.Style.Header2; - break; - case H3: - style = Models.Block.Content.Text.Style.Header3; - break; - case H4: - style = Models.Block.Content.Text.Style.Header4; - break; - case TITLE: - style = Models.Block.Content.Text.Style.Title; - break; - case QUOTE: - style = Models.Block.Content.Text.Style.Quote; - break; - case CODE_SNIPPET: - style = Models.Block.Content.Text.Style.Code; - break; - case BULLET: - style = Models.Block.Content.Text.Style.Marked; - break; - case CHECKBOX: - style = Models.Block.Content.Text.Style.Checkbox; - break; - case NUMBERED: - style = Models.Block.Content.Text.Style.Numbered; - break; - case TOGGLE: - style = Models.Block.Content.Text.Style.Toggle; - break; - } + Models.Block.Content.Text.Style style = mapper.toMiddleware(command.getStyle()); Block.Set.Text.Style.Request request = Block.Set.Text.Style.Request .newBuilder() @@ -304,229 +273,12 @@ public class Middleware { .setContextId(command.getContextId()) .setBlockId(command.getBlockId()) .build(); - + Timber.d("Upload video block url with the following request:\n%s", request.toString()); service.blockUpload(request); } - private Models.Block.Content.Text createTextBlock( - BlockEntity.Content.Text.Style style - ) { - - Models.Block.Content.Text textBlockModel = null; - - switch (style) { - case P: - textBlockModel = Models.Block.Content.Text - .newBuilder() - .setStyle(Models.Block.Content.Text.Style.Paragraph) - .build(); - break; - case H1: - textBlockModel = Models.Block.Content.Text - .newBuilder() - .setStyle(Models.Block.Content.Text.Style.Header1) - .build(); - break; - case H2: - textBlockModel = Models.Block.Content.Text - .newBuilder() - .setStyle(Models.Block.Content.Text.Style.Header2) - .build(); - break; - case H3: - textBlockModel = Models.Block.Content.Text - .newBuilder() - .setStyle(Models.Block.Content.Text.Style.Header3) - .build(); - break; - case H4: - throw new IllegalStateException("Unexpected prototype text style"); - case TITLE: - textBlockModel = Models.Block.Content.Text - .newBuilder() - .setStyle(Models.Block.Content.Text.Style.Title) - .build(); - break; - case QUOTE: - textBlockModel = Models.Block.Content.Text - .newBuilder() - .setStyle(Models.Block.Content.Text.Style.Quote) - .build(); - break; - case CODE_SNIPPET: - textBlockModel = Models.Block.Content.Text - .newBuilder() - .setStyle(Models.Block.Content.Text.Style.Code) - .build(); - break; - case BULLET: - textBlockModel = Models.Block.Content.Text - .newBuilder() - .setStyle(Models.Block.Content.Text.Style.Marked) - .build(); - break; - case CHECKBOX: - textBlockModel = Models.Block.Content.Text - .newBuilder() - .setStyle(Models.Block.Content.Text.Style.Checkbox) - .build(); - break; - case NUMBERED: - textBlockModel = Models.Block.Content.Text - .newBuilder() - .setStyle(Models.Block.Content.Text.Style.Numbered) - .build(); - break; - case TOGGLE: - textBlockModel = Models.Block.Content.Text - .newBuilder() - .setStyle(Models.Block.Content.Text.Style.Toggle) - .build(); - break; - } - - return textBlockModel; - } - - private Models.Block.Content.Page createPageBlock() { - return Models.Block.Content.Page - .newBuilder() - .setStyle(Models.Block.Content.Page.Style.Empty) - .build(); - } - - private Models.Block.Content.Div createDivLineBlock() { - return Models.Block.Content.Div - .newBuilder() - .setStyle(Models.Block.Content.Div.Style.Line) - .build(); - } - - private Models.Block.Content.Div createDivDotsBlock() { - return Models.Block.Content.Div - .newBuilder() - .setStyle(Models.Block.Content.Div.Style.Dots) - .build(); - } - - private Models.Block.Content.File.State getState(BlockEntity.Content.File.State state) { - switch (state) { - case EMPTY: - return Models.Block.Content.File.State.Empty; - case UPLOADING: - return Models.Block.Content.File.State.Uploading; - case DONE: - return Models.Block.Content.File.State.Done; - case ERROR: - return Models.Block.Content.File.State.Error; - default: - throw new IllegalStateException("Unexpected value: " + state); - } - } - - private Models.Block.Content.File.Type getType(BlockEntity.Content.File.Type type) { - switch (type) { - case NONE: - return Models.Block.Content.File.Type.None; - case FILE: - return Models.Block.Content.File.Type.File; - case IMAGE: - return Models.Block.Content.File.Type.Image; - case VIDEO: - return Models.Block.Content.File.Type.Video; - default: - throw new IllegalStateException("Unexpected value: " + type); - } - } - - private Models.Block.Content.File createBlock(BlockEntity.Content.File.Type type, - BlockEntity.Content.File.State state) { - return Models.Block.Content.File - .newBuilder() - .setState(getState(state)) - .setType(getType(type)) - .build(); - } - - private Models.Block.Position createPosition(PositionEntity position) { - Models.Block.Position positionModel = null; - switch (position) { - case NONE: - positionModel = Models.Block.Position.None; - break; - case TOP: - positionModel = Models.Block.Position.Top; - break; - case BOTTOM: - positionModel = Models.Block.Position.Bottom; - break; - case LEFT: - positionModel = Models.Block.Position.Left; - break; - case RIGHT: - positionModel = Models.Block.Position.Right; - break; - case INNER: - positionModel = Models.Block.Position.Inner; - break; - } - return positionModel; - } - - private Models.Block createBlock(Models.Block.Content.Text textBlockModel) { - return Models.Block - .newBuilder() - .setText(textBlockModel) - .build(); - } - - private Models.Block createBlock(Models.Block.Content.Page pageBlockModel) { - return Models.Block - .newBuilder() - .setPage(pageBlockModel) - .build(); - } - - private Models.Block createBlock(Models.Block.Content.Div dividerBlockModel) { - return Models.Block - .newBuilder() - .setDiv(dividerBlockModel) - .build(); - } - - private Models.Block createBlock(Models.Block.Content.File fileBlockModel) { - return Models.Block - .newBuilder() - .setFile(fileBlockModel) - .build(); - } - - private Models.Block createBlock(Models.Block.Content.Text textBlockModel, - Models.Block.Content.Page pageBlockModel, - Models.Block.Content.Div dividerBlockModel, - Models.Block.Content.File fileBlockModel, - BlockEntity.Prototype prototype) { - Models.Block blockModel = null; - - if (textBlockModel != null) { - blockModel = createBlock(textBlockModel); - } else if (pageBlockModel != null) { - blockModel = createBlock(pageBlockModel); - } else if (dividerBlockModel != null) { - blockModel = createBlock(dividerBlockModel); - } else if (fileBlockModel != null) { - blockModel = createBlock(fileBlockModel); - } - - if (blockModel == null) { - throw new IllegalStateException("Could not create content from the following prototype: " + prototype.toString()); - } - - return blockModel; - } - public String createBlock( String contextId, String targetId, @@ -534,41 +286,16 @@ public class Middleware { BlockEntity.Prototype prototype ) throws Exception { - Models.Block.Position positionModel = createPosition(position); + Models.Block.Position positionModel = mapper.toMiddleware(position); - Models.Block.Content.Text textBlockModel = null; - Models.Block.Content.Page pageBlockModel = null; - Models.Block.Content.Div dividerBlockModel = null; - Models.Block.Content.File fileBlockModel = null; - - if (prototype instanceof BlockEntity.Prototype.Text) { - - textBlockModel = createTextBlock(((BlockEntity.Prototype.Text) prototype).getStyle()); - - } else if (prototype instanceof BlockEntity.Prototype.Page) { - - pageBlockModel = createPageBlock(); - - } else if (prototype instanceof BlockEntity.Prototype.Divider) { - - dividerBlockModel = createDivLineBlock(); - - } else if (prototype instanceof BlockEntity.Prototype.File) { - - fileBlockModel = createBlock( - ((BlockEntity.Prototype.File) prototype).getType(), - ((BlockEntity.Prototype.File) prototype).getState()); - } - - Models.Block blockModel = createBlock(textBlockModel, pageBlockModel, - dividerBlockModel, fileBlockModel, prototype); + Models.Block model = factory.create(prototype); Block.Create.Request request = Block.Create.Request .newBuilder() .setContextId(contextId) .setTargetId(targetId) .setPosition(positionModel) - .setBlock(blockModel) + .setBlock(model) .build(); Timber.d("Creating block with the following request:\n%s", request.toString()); @@ -579,28 +306,7 @@ public class Middleware { } public void dnd(CommandEntity.Dnd command) throws Exception { - Models.Block.Position positionModel = null; - - switch (command.getPosition()) { - case NONE: - positionModel = Models.Block.Position.None; - break; - case TOP: - positionModel = Models.Block.Position.Top; - break; - case BOTTOM: - positionModel = Models.Block.Position.Bottom; - break; - case LEFT: - positionModel = Models.Block.Position.Left; - break; - case RIGHT: - positionModel = Models.Block.Position.Right; - break; - case INNER: - positionModel = Models.Block.Position.Inner; - break; - } + Models.Block.Position positionModel = mapper.toMiddleware(command.getPosition()); BlockList.Move.Request request = BlockList.Move.Request .newBuilder() @@ -681,4 +387,17 @@ public class Middleware { service.blockSetIconName(request); } + + public void setupBookmark(CommandEntity.SetupBookmark command) throws Exception { + Block.Bookmark.Fetch.Request request = Block.Bookmark.Fetch.Request + .newBuilder() + .setBlockId(command.getTarget()) + .setContextId(command.getContext()) + .setUrl(command.getUrl()) + .build(); + + Timber.d("Fetching bookmark with the following request:\n%s", request.toString()); + + service.blockBookmarkFetch(request); + } } diff --git a/middleware/src/main/java/com/agileburo/anytype/middleware/interactor/MiddlewareEventChannel.kt b/middleware/src/main/java/com/agileburo/anytype/middleware/interactor/MiddlewareEventChannel.kt index 2cc991e741..47d6ecb29d 100644 --- a/middleware/src/main/java/com/agileburo/anytype/middleware/interactor/MiddlewareEventChannel.kt +++ b/middleware/src/main/java/com/agileburo/anytype/middleware/interactor/MiddlewareEventChannel.kt @@ -23,8 +23,9 @@ class MiddlewareEventChannel( Events.Event.Message.ValueCase.BLOCKSETCHILDRENIDS, Events.Event.Message.ValueCase.BLOCKDELETE, Events.Event.Message.ValueCase.BLOCKSETLINK, + Events.Event.Message.ValueCase.BLOCKSETFILE, Events.Event.Message.ValueCase.BLOCKSETFIELDS, - Events.Event.Message.ValueCase.BLOCKSETFILE + Events.Event.Message.ValueCase.BLOCKSETBOOKMARK ) override fun observeEvents( @@ -126,6 +127,31 @@ class MiddlewareEventChannel( ) } } + Events.Event.Message.ValueCase.BLOCKSETBOOKMARK -> { + EventEntity.Command.BookmarkGranularChange( + context = context, + target = event.blockSetBookmark.id, + url = if (event.blockSetBookmark.hasUrl()) + event.blockSetBookmark.url.value + else null, + title = if (event.blockSetBookmark.hasTitle()) + event.blockSetBookmark.title.value + else + null, + description = if (event.blockSetBookmark.hasDescription()) + event.blockSetBookmark.description.value + else + null, + imageHash = if (event.blockSetBookmark.hasImageHash()) + event.blockSetBookmark.imageHash.value + else + null, + faviconHash = if (event.blockSetBookmark.hasFaviconHash()) + event.blockSetBookmark.faviconHash.value + else + null + ) + } else -> null } } diff --git a/middleware/src/main/java/com/agileburo/anytype/middleware/interactor/MiddlewareFactory.kt b/middleware/src/main/java/com/agileburo/anytype/middleware/interactor/MiddlewareFactory.kt new file mode 100644 index 0000000000..53c4077443 --- /dev/null +++ b/middleware/src/main/java/com/agileburo/anytype/middleware/interactor/MiddlewareFactory.kt @@ -0,0 +1,45 @@ +package com.agileburo.anytype.middleware.interactor + +import anytype.model.Models.Block +import com.agileburo.anytype.data.auth.model.BlockEntity +import com.agileburo.anytype.middleware.toMiddleware + +class MiddlewareFactory { + + fun create(prototype: BlockEntity.Prototype): Block { + + val builder = Block.newBuilder() + + return when (prototype) { + is BlockEntity.Prototype.Bookmark -> { + val bookmark = Block.Content.Bookmark.getDefaultInstance() + builder.setBookmark(bookmark).build() + } + is BlockEntity.Prototype.Text -> { + val text = Block.Content.Text.newBuilder().apply { + style = prototype.style.toMiddleware() + } + builder.setText(text).build() + } + is BlockEntity.Prototype.Divider -> { + val divider = Block.Content.Div.newBuilder().apply { + style = Block.Content.Div.Style.Line + } + builder.setDiv(divider).build() + } + is BlockEntity.Prototype.File -> { + val file = Block.Content.File.newBuilder().apply { + state = prototype.state.toMiddleware() + type = prototype.type.toMiddleware() + } + builder.setFile(file).build() + } + is BlockEntity.Prototype.Page -> { + val page = Block.Content.Page.newBuilder().apply { + style = Block.Content.Page.Style.Empty + } + builder.setPage(page).build() + } + } + } +} \ No newline at end of file diff --git a/middleware/src/main/java/com/agileburo/anytype/middleware/interactor/MiddlewareMapper.kt b/middleware/src/main/java/com/agileburo/anytype/middleware/interactor/MiddlewareMapper.kt new file mode 100644 index 0000000000..2555668c4e --- /dev/null +++ b/middleware/src/main/java/com/agileburo/anytype/middleware/interactor/MiddlewareMapper.kt @@ -0,0 +1,17 @@ +package com.agileburo.anytype.middleware.interactor + +import anytype.model.Models.Block +import com.agileburo.anytype.data.auth.model.BlockEntity +import com.agileburo.anytype.data.auth.model.PositionEntity +import com.agileburo.anytype.middleware.toMiddleware + +class MiddlewareMapper { + + fun toMiddleware(style: BlockEntity.Content.Text.Style): Block.Content.Text.Style { + return style.toMiddleware() + } + + fun toMiddleware(position: PositionEntity): Block.Position { + return position.toMiddleware() + } +} \ No newline at end of file diff --git a/middleware/src/main/java/com/agileburo/anytype/middleware/service/DefaultMiddlewareService.java b/middleware/src/main/java/com/agileburo/anytype/middleware/service/DefaultMiddlewareService.java index b9024f2b1b..20ebfecd8a 100644 --- a/middleware/src/main/java/com/agileburo/anytype/middleware/service/DefaultMiddlewareService.java +++ b/middleware/src/main/java/com/agileburo/anytype/middleware/service/DefaultMiddlewareService.java @@ -273,4 +273,15 @@ public class DefaultMiddlewareService implements MiddlewareService { return response; } } + + @Override + public Block.Bookmark.Fetch.Response blockBookmarkFetch(Block.Bookmark.Fetch.Request request) throws Exception { + byte[] encoded = Lib.blockBookmarkFetch(request.toByteArray()); + Block.Bookmark.Fetch.Response response = Block.Bookmark.Fetch.Response.parseFrom(encoded); + if (response.getError() != null && response.getError().getCode() != Block.Bookmark.Fetch.Response.Error.Code.NULL) { + throw new Exception(response.getError().getDescription()); + } else { + return response; + } + } } diff --git a/middleware/src/main/java/com/agileburo/anytype/middleware/service/MiddlewareService.java b/middleware/src/main/java/com/agileburo/anytype/middleware/service/MiddlewareService.java index dc9e365561..09b2762568 100644 --- a/middleware/src/main/java/com/agileburo/anytype/middleware/service/MiddlewareService.java +++ b/middleware/src/main/java/com/agileburo/anytype/middleware/service/MiddlewareService.java @@ -6,7 +6,6 @@ import anytype.Commands.Rpc.BlockList; import anytype.Commands.Rpc.Config; import anytype.Commands.Rpc.Ipfs.Image; import anytype.Commands.Rpc.Wallet; -import anytype.Events; /** * Service for interacting with the backend. @@ -58,5 +57,7 @@ public interface MiddlewareService { Block.Set.Icon.Name.Response blockSetIconName(Block.Set.Icon.Name.Request request) throws Exception; + Block.Bookmark.Fetch.Response blockBookmarkFetch(Block.Bookmark.Fetch.Request request) throws Exception; + Block.Upload.Response blockUpload(Block.Upload.Request request) throws Exception; } diff --git a/middleware/src/test/java/com/agileburo/anytype/MiddlewareTest.kt b/middleware/src/test/java/com/agileburo/anytype/MiddlewareTest.kt index ee3e70a2bc..766206e607 100644 --- a/middleware/src/test/java/com/agileburo/anytype/MiddlewareTest.kt +++ b/middleware/src/test/java/com/agileburo/anytype/MiddlewareTest.kt @@ -2,6 +2,8 @@ package com.agileburo.anytype import anytype.Commands.Rpc.Account import com.agileburo.anytype.middleware.interactor.Middleware +import com.agileburo.anytype.middleware.interactor.MiddlewareFactory +import com.agileburo.anytype.middleware.interactor.MiddlewareMapper import com.agileburo.anytype.middleware.service.MiddlewareService import com.nhaarman.mockitokotlin2.times import com.nhaarman.mockitokotlin2.verify @@ -18,10 +20,13 @@ class MiddlewareTest { private lateinit var middleware: Middleware + private val mapper = MiddlewareMapper() + private val factory = MiddlewareFactory() + @Before fun setup() { MockitoAnnotations.initMocks(this) - middleware = Middleware(service) + middleware = Middleware(service, factory, mapper) } @Test diff --git a/presentation/src/main/java/com/agileburo/anytype/presentation/page/DocumentExternalEventReducer.kt b/presentation/src/main/java/com/agileburo/anytype/presentation/page/DocumentExternalEventReducer.kt index 83fc303537..866325f51e 100644 --- a/presentation/src/main/java/com/agileburo/anytype/presentation/page/DocumentExternalEventReducer.kt +++ b/presentation/src/main/java/com/agileburo/anytype/presentation/page/DocumentExternalEventReducer.kt @@ -66,6 +66,21 @@ class DocumentExternalEventReducer : StateReducer, Event> { }, target = { block -> block.id == event.id } ) + is Event.Command.BookmarkGranularChange -> state.replace( + replacement = { block -> + val content = block.content() + block.copy( + content = content.copy( + url = event.url ?: content.url, + title = event.title ?: content.title, + description = event.description ?: content.description, + image = event.image ?: content.image, + favicon = event.favicon ?: content.favicon + ) + ) + }, + target = { block -> block.id == event.target } + ) else -> state.also { Timber.d("Ignoring event: $event") } } diff --git a/presentation/src/main/java/com/agileburo/anytype/presentation/page/PageViewModel.kt b/presentation/src/main/java/com/agileburo/anytype/presentation/page/PageViewModel.kt index ab9926502d..7e2affd9ad 100644 --- a/presentation/src/main/java/com/agileburo/anytype/presentation/page/PageViewModel.kt +++ b/presentation/src/main/java/com/agileburo/anytype/presentation/page/PageViewModel.kt @@ -310,14 +310,20 @@ class PageViewModel( is Content.Divider -> block.toView( urlBuilder = urlBuilder ) - is Content.Bookmark -> BlockView.Bookmark( - id = block.id, - url = content.url, - title = content.title, - description = content.description, - imageUrl = content.image?.let { urlBuilder.image(it) }, - faviconUrl = content.favicon?.let { urlBuilder.image(it) } - ) + is Content.Bookmark -> { + content.url?.let { url -> + BlockView.Bookmark.View( + id = block.id, + url = url, + title = content.title, + description = content.description, + imageUrl = content.image?.let { urlBuilder.image(it) }, + faviconUrl = content.favicon?.let { urlBuilder.image(it) } + ) + } ?: BlockView.Bookmark.Placeholder( + id = block.id + ) + } else -> null } } @@ -772,10 +778,12 @@ class PageViewModel( ) } - private fun proceedWithCreatingEmptyFileBlock(id: String, - type: Content.File.Type, - state: Content.File.State = Content.File.State.EMPTY, - position: Position = Position.BOTTOM) { + private fun proceedWithCreatingEmptyFileBlock( + id: String, + type: Content.File.Type, + state: Content.File.State = Content.File.State.EMPTY, + position: Position = Position.BOTTOM + ) { createBlock.invoke( scope = viewModelScope, params = CreateBlock.Params( @@ -866,6 +874,9 @@ class PageViewModel( is Content.Link -> { addNewBlockAtTheEnd() } + is Content.Bookmark -> { + addNewBlockAtTheEnd() + } else -> { Timber.d("Outside-click has been ignored.") } @@ -889,7 +900,6 @@ class PageViewModel( } fun onAddNewPageClicked() { - controlPanelInteractor.onEvent(ControlPanelMachine.Event.OnAddBlockToolbarOptionSelected) val params = CreateBlock.Params( @@ -907,6 +917,33 @@ class PageViewModel( } } + fun onAddBookmarkClicked() { + controlPanelInteractor.onEvent(ControlPanelMachine.Event.OnAddBlockToolbarOptionSelected) + + val params = CreateBlock.Params( + context = context, + position = Position.BOTTOM, + target = focusChannel.value, + prototype = Prototype.Bookmark + ) + + createBlock.invoke(scope = viewModelScope, params = params) { result -> + result.either( + fnL = { Timber.e(it, "Error while creating a bookmark with params: $params") }, + fnR = { Timber.d("Bookmark created!") } + ) + } + } + + fun onBookmarkPlaceholderClicked(target: String) { + dispatch( + command = Command.OpenBookmarkSetter( + context = context, + target = target + ) + ) + } + fun onTextInputClicked() { controlPanelInteractor.onEvent(ControlPanelMachine.Event.OnTextInputClicked) } @@ -1028,6 +1065,11 @@ class PageViewModel( data class OpenGallery( val mediaType: String ) : Command() + + data class OpenBookmarkSetter( + val target: String, + val context: String + ) : Command() } companion object { diff --git a/presentation/src/main/java/com/agileburo/anytype/presentation/page/bookmark/CreateBookmarkViewModel.kt b/presentation/src/main/java/com/agileburo/anytype/presentation/page/bookmark/CreateBookmarkViewModel.kt new file mode 100644 index 0000000000..3fff494a19 --- /dev/null +++ b/presentation/src/main/java/com/agileburo/anytype/presentation/page/bookmark/CreateBookmarkViewModel.kt @@ -0,0 +1,48 @@ +package com.agileburo.anytype.presentation.page.bookmark + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import com.agileburo.anytype.core_utils.ui.ViewStateViewModel +import com.agileburo.anytype.domain.page.bookmark.SetupBookmark +import com.agileburo.anytype.presentation.page.bookmark.CreateBookmarkViewModel.ViewState + +class CreateBookmarkViewModel( + private val setupBookmark: SetupBookmark +) : ViewStateViewModel() { + + fun onCreateBookmarkClicked( + context: String, + target: String, + url: String + ) { + setupBookmark.invoke( + scope = viewModelScope, + params = SetupBookmark.Params( + context = context, + target = target, + url = url + ) + ) { result -> + result.either( + fnL = { update(ViewState.Error(it.message ?: toString())) }, + fnR = { update(ViewState.Exit) } + ) + } + } + + sealed class ViewState { + data class Error(val message: String) : ViewState() + object Exit : ViewState() + } + + class Factory( + private val setupBookmark: SetupBookmark + ) : ViewModelProvider.Factory { + + @Suppress("UNCHECKED_CAST") + override fun create(modelClass: Class): T = CreateBookmarkViewModel( + setupBookmark = setupBookmark + ) as T + } +} \ No newline at end of file diff --git a/presentation/src/test/java/com/agileburo/anytype/presentation/page/DocumentExternalEventReducerTest.kt b/presentation/src/test/java/com/agileburo/anytype/presentation/page/DocumentExternalEventReducerTest.kt new file mode 100644 index 0000000000..ce203adb11 --- /dev/null +++ b/presentation/src/test/java/com/agileburo/anytype/presentation/page/DocumentExternalEventReducerTest.kt @@ -0,0 +1,216 @@ +package com.agileburo.anytype.presentation.page + +import MockDataFactory +import com.agileburo.anytype.domain.block.model.Block +import com.agileburo.anytype.domain.event.model.Event +import com.agileburo.anytype.domain.ext.content +import kotlinx.coroutines.runBlocking +import org.junit.Test +import kotlin.test.assertEquals + +class DocumentExternalEventReducerTest { + + private val reducer = DocumentExternalEventReducer() + + @Test + fun `should apply bookmark granular changes to the state`() { + + // SETUP + + val title = Block( + id = MockDataFactory.randomUuid(), + fields = Block.Fields.empty(), + children = emptyList(), + content = Block.Content.Text( + text = MockDataFactory.randomString(), + marks = emptyList(), + style = Block.Content.Text.Style.TITLE + ) + ) + + val bookmark = Block( + id = MockDataFactory.randomUuid(), + fields = Block.Fields.empty(), + children = emptyList(), + content = Block.Content.Bookmark( + url = null, + description = null, + title = null, + favicon = null, + image = null + ) + ) + + val page = Block( + id = MockDataFactory.randomUuid(), + fields = Block.Fields.empty(), + children = listOf(title.id, bookmark.id), + content = Block.Content.Page( + style = Block.Content.Page.Style.SET + ) + ) + + val state = listOf(page, title, bookmark) + + // TESTING + + runBlocking { + + val bookmarkUrl = MockDataFactory.randomString() + + val expected = listOf( + page, title, bookmark.copy( + content = bookmark.content().copy( + url = bookmarkUrl + ) + ) + ) + + val result = reducer.reduce( + state = state, + event = Event.Command.BookmarkGranularChange( + url = bookmarkUrl, + context = page.id, + target = bookmark.id, + title = null, + description = null, + favicon = null, + image = null + ) + ) + + assertEquals(expected = expected, actual = result) + } + + runBlocking { + + val bookmarkUrl = MockDataFactory.randomString() + val bookmarkTitle = MockDataFactory.randomString() + val bookmarkDescription = MockDataFactory.randomString() + + val expected = listOf( + page, title, bookmark.copy( + content = bookmark.content().copy( + url = bookmarkUrl, + title = bookmarkTitle, + description = bookmarkDescription + ) + ) + ) + + val result = reducer.reduce( + state = state, + event = Event.Command.BookmarkGranularChange( + url = bookmarkUrl, + context = page.id, + target = bookmark.id, + title = bookmarkTitle, + description = bookmarkDescription, + favicon = null, + image = null + ) + ) + + assertEquals(expected = expected, actual = result) + } + + runBlocking { + + val bookmarkUrl = MockDataFactory.randomString() + val bookmarkTitle = MockDataFactory.randomString() + val bookmarkDescription = MockDataFactory.randomString() + val imageHash = MockDataFactory.randomString() + val faviconHash = MockDataFactory.randomString() + + val expected = listOf( + page, title, bookmark.copy( + content = bookmark.content().copy( + url = bookmarkUrl, + title = bookmarkTitle, + description = bookmarkDescription, + image = imageHash, + favicon = faviconHash + ) + ) + ) + + val result = reducer.reduce( + state = state, + event = Event.Command.BookmarkGranularChange( + url = bookmarkUrl, + context = page.id, + target = bookmark.id, + title = bookmarkTitle, + description = bookmarkDescription, + favicon = faviconHash, + image = imageHash + ) + ) + + assertEquals(expected = expected, actual = result) + } + } + + @Test + fun `should not apply bookmark granular changes if there is not bookmark block matched by id`() { + + val title = Block( + id = MockDataFactory.randomUuid(), + fields = Block.Fields.empty(), + children = emptyList(), + content = Block.Content.Text( + text = MockDataFactory.randomString(), + marks = emptyList(), + style = Block.Content.Text.Style.TITLE + ) + ) + + val bookmark = Block( + id = MockDataFactory.randomUuid(), + fields = Block.Fields.empty(), + children = emptyList(), + content = Block.Content.Bookmark( + url = null, + description = null, + title = null, + favicon = null, + image = null + ) + ) + + val page = Block( + id = MockDataFactory.randomUuid(), + fields = Block.Fields.empty(), + children = listOf(title.id, bookmark.id), + content = Block.Content.Page( + style = Block.Content.Page.Style.SET + ) + ) + + val state = listOf(page, title, bookmark) + + // TESTING + + runBlocking { + + val bookmarkUrl = MockDataFactory.randomString() + + val expected = state.toList() + + val result = reducer.reduce( + state = state, + event = Event.Command.BookmarkGranularChange( + url = bookmarkUrl, + context = page.id, + target = MockDataFactory.randomString(), + title = null, + description = null, + favicon = null, + image = null + ) + ) + + assertEquals(expected = expected, actual = result) + } + } +} \ No newline at end of file diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index ebe750b71d..f5a8c75aa5 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -3,8 +3,8 @@ xmlns:tools="http://schemas.android.com/tools" package="com.agileburo.anytype.sample"> - - + + @@ -16,8 +16,8 @@ android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" - tools:ignore="GoogleAppIndexingWarning" - android:theme="@style/AppTheme"> + android:theme="@style/AppTheme" + tools:ignore="GoogleAppIndexingWarning"> diff --git a/sample/src/main/java/com/agileburo/anytype/sample/SampleApp.kt b/sample/src/main/java/com/agileburo/anytype/sample/SampleApp.kt index 6d9ee4ac2e..f144897117 100644 --- a/sample/src/main/java/com/agileburo/anytype/sample/SampleApp.kt +++ b/sample/src/main/java/com/agileburo/anytype/sample/SampleApp.kt @@ -4,7 +4,7 @@ import android.app.Application import com.agileburo.anytype.core_utils.tools.CrashlyticsTree import timber.log.Timber -class SampleApp : Application(){ +class SampleApp : Application() { override fun onCreate() {