mirror of
https://github.com/anyproto/anytype-kotlin.git
synced 2025-06-08 05:47:05 +09:00
Add bookmark block (#292)
This commit is contained in:
parent
0d1747283c
commit
ea304c62df
55 changed files with 1111 additions and 390 deletions
15
CHANGELOG.md
15
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 🚀
|
||||
|
|
|
@ -122,6 +122,13 @@ class ComponentManager(private val main: MainComponent) {
|
|||
.build()
|
||||
}
|
||||
|
||||
val createBookmarkSubComponent = Component {
|
||||
main
|
||||
.createBookmarkBuilder()
|
||||
.createBookmarkModule(CreateBookmarkModule())
|
||||
.build()
|
||||
}
|
||||
|
||||
class Component<T>(private val builder: () -> T) {
|
||||
|
||||
private var instance: T? = null
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -34,4 +34,5 @@ interface MainComponent {
|
|||
fun pageComponentBuilder(): PageSubComponent.Builder
|
||||
fun linkAddComponentBuilder(): LinkSubComponent.Builder
|
||||
fun pageIconPickerBuilder(): PageIconPickerSubComponent.Builder
|
||||
fun createBookmarkBuilder(): CreateBookmarkSubComponent.Builder
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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<ViewState> {
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
62
app/src/main/res/layout/dialog_create_bookmark.xml
Normal file
62
app/src/main/res/layout/dialog_create_bookmark.xml
Normal file
|
@ -0,0 +1,62 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/drag"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/modal_rect_margin_top"
|
||||
android:contentDescription="@string/content_description_modal_icon"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/sheet_top" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/urlInput"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:background="@null"
|
||||
android:hint="@string/hint_paste_or_type_a_url"
|
||||
android:inputType="textUri"
|
||||
android:textColor="@color/black"
|
||||
android:textColorHint="@color/hint_color"
|
||||
android:textSize="15sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/drag" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/createBookmarkButton"
|
||||
style="@style/DefaultSolidButtonStyle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/auth_default_button_height"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="@string/create_bookmark"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/divider" />
|
||||
|
||||
<View
|
||||
android:id="@+id/divider"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0.5dp"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:background="@color/divider_color"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/urlInput" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -15,5 +15,6 @@
|
|||
<color name="blue">#3F51B5</color>
|
||||
|
||||
<color name="auth_divider">#DFDDD0</color>
|
||||
<color name="hint_color">#ACA996</color>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -100,5 +100,7 @@ Do the computation of an expensive paragraph of text on a background thread:
|
|||
|
||||
<string name="page_icon">Page icon</string>
|
||||
<string name="page_icon_picker_remove_text">Remove</string>
|
||||
<string name="create_bookmark">Create bookmark</string>
|
||||
<string name="hint_paste_or_type_a_url">Paste or type a URL</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -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<BlockViewHolder>() {
|
||||
|
||||
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 -> {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
15
core-ui/src/main/res/drawable/ic_bookmark_menu.xml
Normal file
15
core-ui/src/main/res/drawable/ic_bookmark_menu.xml
Normal file
|
@ -0,0 +1,15 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M6,14.0323C7.1046,14.0323 8,13.1369 8,12.0323C8,10.9278 7.1046,10.0323 6,10.0323C4.8954,10.0323 4,10.9278 4,12.0323C4,13.1369 4.8954,14.0323 6,14.0323Z" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M12,14.0323C13.1046,14.0323 14,13.1369 14,12.0323C14,10.9278 13.1046,10.0323 12,10.0323C10.8954,10.0323 10,10.9278 10,12.0323C10,13.1369 10.8954,14.0323 12,14.0323Z" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M18,14.0323C19.1046,14.0323 20,13.1369 20,12.0323C20,10.9278 19.1046,10.0323 18,10.0323C16.8954,10.0323 16,10.9278 16,12.0323C16,13.1369 16.8954,14.0323 18,14.0323Z" />
|
||||
</vector>
|
10
core-ui/src/main/res/drawable/ic_bookmark_placeholder.xml
Normal file
10
core-ui/src/main/res/drawable/ic_bookmark_placeholder.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#DFDDD0"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M12,18L20,22V4C20,2.8954 19.1046,2 18,2H6C4.8954,2 4,2.8954 4,4V22L12,18ZM6,18.7639L12,15.7639L18,18.7639V4H6V18.7639Z" />
|
||||
</vector>
|
|
@ -3,11 +3,11 @@
|
|||
android:height="24dp"
|
||||
android:viewportWidth="25"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M20.0029,4H4.0029V20H20.0029V4ZM4.0029,2C2.8984,2 2.0029,2.8954 2.0029,4V20C2.0029,21.1046 2.8984,22 4.0029,22H20.0029C21.1075,22 22.0029,21.1046 22.0029,20V4C22.0029,2.8954 21.1075,2 20.0029,2H4.0029Z"
|
||||
android:fillColor="#DFDDD0"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M16.9717,11.9696L9.5498,7.6846V16.2546L16.9717,11.9696Z"
|
||||
android:fillColor="#DFDDD0"/>
|
||||
<path
|
||||
android:fillColor="#DFDDD0"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M20.0029,4H4.0029V20H20.0029V4ZM4.0029,2C2.8984,2 2.0029,2.8954 2.0029,4V20C2.0029,21.1046 2.8984,22 4.0029,22H20.0029C21.1075,22 22.0029,21.1046 22.0029,20V4C22.0029,2.8954 21.1075,2 20.0029,2H4.0029Z" />
|
||||
<path
|
||||
android:fillColor="#DFDDD0"
|
||||
android:pathData="M16.9717,11.9696L9.5498,7.6846V16.2546L16.9717,11.9696Z" />
|
||||
</vector>
|
||||
|
|
|
@ -4,6 +4,5 @@
|
|||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="#DFDDD0" />
|
||||
<corners
|
||||
android:radius="8dp" />
|
||||
<corners android:radius="8dp" />
|
||||
</shape>
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="#DFDDD0" />
|
||||
<corners android:radius="4dp" />
|
||||
</shape>
|
8
core-ui/src/main/res/drawable/rounded_dialog.xml
Normal file
8
core-ui/src/main/res/drawable/rounded_dialog.xml
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@android:color/white" />
|
||||
<corners
|
||||
android:topLeftRadius="10dp"
|
||||
android:topRightRadius="10dp" />
|
||||
</shape>
|
|
@ -2,10 +2,13 @@
|
|||
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:paddingStart="@dimen/default_page_item_padding_start"
|
||||
android:paddingEnd="@dimen/default_page_item_padding_end"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="6dp"
|
||||
app:cardCornerRadius="8dp">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/container"
|
||||
|
@ -34,10 +37,10 @@
|
|||
|
||||
<TextView
|
||||
android:id="@+id/bookmarkUrl"
|
||||
style="@style/BlockBookmarkUrlStyle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
style="@style/BlockBookmarkUrlStyle"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/bookmarkLogo"
|
||||
app:layout_constraintTop_toBottomOf="@+id/bookmarkDescription"
|
||||
|
@ -45,12 +48,12 @@
|
|||
|
||||
<ImageView
|
||||
android:id="@+id/bookmarkImage"
|
||||
android:layout_height="200dp"
|
||||
android:layout_width="0dp"
|
||||
app:layout_constraintWidth_default="percent"
|
||||
android:layout_height="200dp"
|
||||
android:contentDescription="@string/content_description_bookmark_image"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintWidth_default="percent"
|
||||
tools:background="@color/black" />
|
||||
|
||||
<ImageView
|
||||
|
@ -76,5 +79,15 @@
|
|||
app:layout_constraintStart_toStartOf="@+id/bookmarkImage"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/bookmarkMenu"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:src="@drawable/ic_bookmark_menu"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="6dp"
|
||||
android:background="@drawable/rectangle_bookmark_placeholder">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="14dp"
|
||||
android:layout_marginBottom="14dp"
|
||||
android:src="@drawable/ic_bookmark_placeholder"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:text="@string/add_a_web_bookmark"
|
||||
android:textColor="#ACA996"
|
||||
android:textSize="15sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/icon"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/menu"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:src="@drawable/ic_block_more"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -1,7 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/default_page_item_padding_start"
|
||||
|
@ -32,9 +31,9 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="5dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:textColor="#ACA996"
|
||||
android:text="@string/hint_upload"
|
||||
android:inputType="none"
|
||||
android:text="@string/hint_upload"
|
||||
android:textColor="#ACA996"
|
||||
|
||||
app:layout_constraintBottom_toBottomOf="@+id/icVideo"
|
||||
app:layout_constraintEnd_toStartOf="@+id/icMore"
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/default_page_item_padding_start"
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/default_page_item_padding_start"
|
||||
|
|
|
@ -127,5 +127,7 @@
|
|||
<string name="untitled">Untitled</string>
|
||||
<string name="error_while_loading_picture">Error while loading picture</string>
|
||||
<string name="block_with_a_picture">Block with a picture</string>
|
||||
<string name="tap_here_to_insert_a_bookmark_url">Tap here to insert a bookmark url</string>
|
||||
<string name="add_a_web_bookmark">Add a web bookmark</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -851,7 +851,8 @@ class BlockAdapterTest {
|
|||
onPageIconClicked = {},
|
||||
onAddLocalVideoClick = {},
|
||||
onAddUrlClick = { _, _ -> },
|
||||
strVideoError = "Error"
|
||||
strVideoError = "Error",
|
||||
onBookmarkPlaceholderClicked = {}
|
||||
)
|
||||
}
|
||||
}
|
|
@ -7,4 +7,5 @@ import androidx.lifecycle.ViewModel
|
|||
open class ViewStateViewModel<VS> : ViewModel() {
|
||||
protected val stateData = MutableLiveData<VS>()
|
||||
val state: LiveData<VS> = stateData
|
||||
fun update(update: VS) = stateData.postValue(update)
|
||||
}
|
|
@ -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),
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -84,4 +84,10 @@ class CommandEntity {
|
|||
val target: String,
|
||||
val name: String
|
||||
)
|
||||
|
||||
data class SetupBookmark(
|
||||
val context: String,
|
||||
val target: String,
|
||||
val url: String
|
||||
)
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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
|
||||
)
|
||||
}
|
|
@ -41,4 +41,6 @@ interface BlockRepository {
|
|||
suspend fun uploadUrl(command: Command.UploadVideoBlockUrl)
|
||||
|
||||
suspend fun setIconName(command: Command.SetIconName)
|
||||
|
||||
suspend fun setupBookmark(command: Command.SetupBookmark)
|
||||
}
|
|
@ -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.)
|
||||
|
|
|
@ -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<Unit, SetupBookmark.Params>() {
|
||||
|
||||
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
|
||||
)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -66,6 +66,21 @@ class DocumentExternalEventReducer : StateReducer<List<Block>, Event> {
|
|||
},
|
||||
target = { block -> block.id == event.id }
|
||||
)
|
||||
is Event.Command.BookmarkGranularChange -> state.replace(
|
||||
replacement = { block ->
|
||||
val content = block.content<Block.Content.Bookmark>()
|
||||
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") }
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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<ViewState>() {
|
||||
|
||||
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 <T : ViewModel?> create(modelClass: Class<T>): T = CreateBookmarkViewModel(
|
||||
setupBookmark = setupBookmark
|
||||
) as T
|
||||
}
|
||||
}
|
|
@ -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<Block.Content.Bookmark>().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<Block.Content.Bookmark>().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<Block.Content.Bookmark>().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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,8 +3,8 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.agileburo.anytype.sample">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
|
||||
|
@ -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">
|
||||
<activity android:name=".MainActivity" />
|
||||
<activity android:name=".files.LocalFileActivity">
|
||||
<intent-filter>
|
||||
|
|
|
@ -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() {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue