mirror of
https://github.com/anyproto/anytype-kotlin.git
synced 2025-06-08 05:47:05 +09:00
Feature/is 142 video block (#264)
This commit is contained in:
parent
b01e5d458b
commit
5a1a2e3bab
52 changed files with 1613 additions and 166 deletions
|
@ -95,6 +95,7 @@ dependencies {
|
|||
//Compile time dependencies
|
||||
kapt applicationDependencies.daggerCompiler
|
||||
kapt applicationDependencies.glideCompiler
|
||||
kapt applicationDependencies.permissionDispCompiler
|
||||
compileOnly applicationDependencies.javaxAnnotation
|
||||
compileOnly applicationDependencies.javaxInject
|
||||
|
||||
|
@ -113,6 +114,8 @@ dependencies {
|
|||
implementation applicationDependencies.gson
|
||||
implementation applicationDependencies.rxRelay
|
||||
implementation applicationDependencies.tableView
|
||||
implementation applicationDependencies.permissionDisp
|
||||
implementation applicationDependencies.pickT
|
||||
|
||||
implementation applicationDependencies.viewModel
|
||||
implementation applicationDependencies.viewModelExtensions
|
||||
|
|
|
@ -55,6 +55,7 @@ class PageModule {
|
|||
mergeBlocks: MergeBlocks,
|
||||
splitBlock: SplitBlock,
|
||||
createPage: CreatePage,
|
||||
uploadUrl: UploadUrl,
|
||||
documentExternalEventReducer: DocumentExternalEventReducer,
|
||||
urlBuilder: UrlBuilder,
|
||||
downloadFile: DownloadFile,
|
||||
|
@ -75,6 +76,7 @@ class PageModule {
|
|||
updateLinkMarks = updateLinkMarks,
|
||||
removeLinkMark = removeLinkMark,
|
||||
mergeBlocks = mergeBlocks,
|
||||
uploadUrl = uploadUrl,
|
||||
splitBlock = splitBlock,
|
||||
documentEventReducer = documentExternalEventReducer,
|
||||
urlBuilder = urlBuilder,
|
||||
|
@ -163,6 +165,14 @@ class PageModule {
|
|||
repo = repo
|
||||
)
|
||||
|
||||
@Provides
|
||||
@PerScreen
|
||||
fun provideUploadUrl(
|
||||
repo: BlockRepository
|
||||
): UploadUrl = UploadUrl(
|
||||
repo = repo
|
||||
)
|
||||
|
||||
@Provides
|
||||
@PerScreen
|
||||
fun provideUpdateLinkMarks(): UpdateLinkMarks = UpdateLinkMarks()
|
||||
|
|
|
@ -1,11 +1,17 @@
|
|||
package com.agileburo.anytype.ui.page
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
import android.widget.LinearLayout
|
||||
import androidx.activity.addCallback
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
|
@ -25,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_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
|
||||
import com.agileburo.anytype.core_ui.widgets.toolbar.OptionToolbarWidget.OptionConfig.OPTION_TEXT_HEADER_TWO
|
||||
|
@ -32,10 +39,7 @@ import com.agileburo.anytype.core_ui.widgets.toolbar.OptionToolbarWidget.OptionC
|
|||
import com.agileburo.anytype.core_ui.widgets.toolbar.OptionToolbarWidget.OptionConfig.OPTION_TEXT_TEXT
|
||||
import com.agileburo.anytype.core_ui.widgets.toolbar.OptionToolbarWidget.OptionConfig.OPTION_TOOL_PAGE
|
||||
import com.agileburo.anytype.core_utils.common.EventWrapper
|
||||
import com.agileburo.anytype.core_utils.ext.gone
|
||||
import com.agileburo.anytype.core_utils.ext.hexColorCode
|
||||
import com.agileburo.anytype.core_utils.ext.hideSoftInput
|
||||
import com.agileburo.anytype.core_utils.ext.toast
|
||||
import com.agileburo.anytype.core_utils.ext.*
|
||||
import com.agileburo.anytype.di.common.componentManager
|
||||
import com.agileburo.anytype.domain.block.model.Block.Content.Text
|
||||
import com.agileburo.anytype.domain.common.Id
|
||||
|
@ -48,23 +52,30 @@ import com.agileburo.anytype.ui.base.NavigationFragment
|
|||
import com.agileburo.anytype.ui.page.modals.PageIconPickerFragment
|
||||
import com.agileburo.anytype.ui.page.modals.SetLinkFragment
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.hbisoft.pickit.PickiT
|
||||
import com.hbisoft.pickit.PickiTCallbacks
|
||||
import kotlinx.android.synthetic.main.fragment_page.*
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import permissions.dispatcher.*
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
const val REQUEST_FILE_CODE = 745
|
||||
|
||||
@RuntimePermissions
|
||||
open class PageFragment : NavigationFragment(R.layout.fragment_page),
|
||||
OnFragmentInteractionListener {
|
||||
OnFragmentInteractionListener, PickiTCallbacks {
|
||||
|
||||
private val vm by lazy {
|
||||
ViewModelProviders
|
||||
.of(this, factory)
|
||||
.get(PageViewModel::class.java)
|
||||
}
|
||||
private lateinit var pickiT: PickiT
|
||||
|
||||
private val pageAdapter by lazy {
|
||||
BlockAdapter(
|
||||
|
@ -93,16 +104,87 @@ open class PageFragment : NavigationFragment(R.layout.fragment_page),
|
|||
onPageClicked = vm::onPageClicked,
|
||||
onTextInputClicked = vm::onTextInputClicked,
|
||||
onDownloadFileClicked = vm::onDownloadFileClicked,
|
||||
onPageIconClicked = vm::onPageIconClicked
|
||||
onPageIconClicked = vm::onPageIconClicked,
|
||||
onAddUrlClick = vm::onAddVideoUrlClicked,
|
||||
onAddLocalVideoClick = vm::onAddLocalVideoClicked,
|
||||
strVideoError = getString(R.string.error)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
when (requestCode) {
|
||||
REQUEST_FILE_CODE -> {
|
||||
data?.data?.let {
|
||||
pickiT.getPath(it, Build.VERSION.SDK_INT)
|
||||
} ?: run {
|
||||
toast("Error while getting file")
|
||||
}
|
||||
}
|
||||
else -> toast("Unknown Request Code:$requestCode")
|
||||
}
|
||||
} else {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(
|
||||
requestCode: Int,
|
||||
permissions: Array<out String>,
|
||||
grantResults: IntArray
|
||||
) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
onRequestPermissionsResult(requestCode, grantResults)
|
||||
}
|
||||
|
||||
@NeedsPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
fun openGallery(type: String) {
|
||||
startActivityForResult(getVideoFileIntent(type), REQUEST_FILE_CODE)
|
||||
}
|
||||
|
||||
@OnShowRationale(Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
fun showRationaleForRead(request: PermissionRequest) {
|
||||
showRationaleDialog(R.string.permission_read_rationale, request)
|
||||
}
|
||||
|
||||
@OnPermissionDenied(Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
fun onReadDenied() {
|
||||
toast(getString(R.string.permission_read_denied))
|
||||
}
|
||||
|
||||
@OnNeverAskAgain(Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
fun onReadNeverAskAgain() {
|
||||
toast(getString(R.string.permission_read_never_ask_again))
|
||||
}
|
||||
|
||||
override fun PickiTonProgressUpdate(progress: Int) {
|
||||
Timber.d("PickiTonProgressUpdate progress:$progress")
|
||||
}
|
||||
|
||||
override fun PickiTonStartListener() {
|
||||
vm.onChooseVideoFileFromMedia()
|
||||
Timber.d("PickiTonStartListener")
|
||||
}
|
||||
|
||||
override fun PickiTonCompleteListener(
|
||||
path: String?,
|
||||
wasDriveFile: Boolean,
|
||||
wasUnknownProvider: Boolean,
|
||||
wasSuccessful: Boolean,
|
||||
Reason: String?
|
||||
) {
|
||||
Timber.d("PickiTonCompleteListener path:$path, wasDriveFile$wasDriveFile, " +
|
||||
"wasUnknownProvider:$wasUnknownProvider, wasSuccessful:$wasSuccessful, reason:$Reason")
|
||||
vm.onAddVideoFileClicked(filePath = path)
|
||||
}
|
||||
|
||||
@Inject
|
||||
lateinit var factory: PageViewModelFactory
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
vm.open(requireArguments().getString(ID_KEY, ID_EMPTY_VALUE))
|
||||
pickiT = PickiT(requireContext(), this)
|
||||
setupOnBackPressedDispatcher()
|
||||
}
|
||||
|
||||
|
@ -271,6 +353,12 @@ open class PageFragment : NavigationFragment(R.layout.fragment_page),
|
|||
else -> toast(NOT_IMPLEMENTED_MESSAGE)
|
||||
}
|
||||
}
|
||||
is Option.Media -> {
|
||||
when (option.type) {
|
||||
OPTION_MEDIA_VIDEO -> vm.onAddVideoBlockClicked()
|
||||
else -> toast(NOT_IMPLEMENTED_MESSAGE)
|
||||
}
|
||||
}
|
||||
is Option.Other -> vm.onOptionOtherActionClicked()
|
||||
}
|
||||
}
|
||||
|
@ -355,6 +443,9 @@ open class PageFragment : NavigationFragment(R.layout.fragment_page),
|
|||
target = command.target
|
||||
).show(childFragmentManager, null)
|
||||
}
|
||||
is PageViewModel.Command.OpenGallery -> {
|
||||
openGalleryWithPermissionCheck(command.mediaType)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -373,6 +464,7 @@ open class PageFragment : NavigationFragment(R.layout.fragment_page),
|
|||
rangeStart = state.range.first
|
||||
).show(childFragmentManager, null)
|
||||
}
|
||||
is PageViewModel.ViewState.Error -> toast(state.message)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -445,6 +537,15 @@ open class PageFragment : NavigationFragment(R.layout.fragment_page),
|
|||
hideSoftInput()
|
||||
}
|
||||
|
||||
private fun showRationaleDialog(@StringRes messageResId: Int, request: PermissionRequest) {
|
||||
AlertDialog.Builder(requireContext())
|
||||
.setPositiveButton(R.string.button_allow) { _, _ -> request.proceed() }
|
||||
.setNegativeButton(R.string.button_deny) { _, _ -> request.cancel() }
|
||||
.setCancelable(false)
|
||||
.setMessage(messageResId)
|
||||
.show()
|
||||
}
|
||||
|
||||
override fun injectDependencies() {
|
||||
componentManager().pageComponent.get().inject(this)
|
||||
}
|
||||
|
|
|
@ -91,6 +91,13 @@ Do the computation of an expensive paragraph of text on a background thread:
|
|||
<string name="button_unlink">Unlink</string>
|
||||
<string name="button_link">Link</string>
|
||||
|
||||
<string name="permission_read_rationale">Read permission is needed to load file to media block.</string>
|
||||
<string name="permission_read_denied">Read permission was denied. Please consider granting it in order to load files to media blocks!</string>
|
||||
<string name="permission_read_never_ask_again">Read permission was denied with never ask again.</string>
|
||||
<string name="button_allow">Allow</string>
|
||||
<string name="button_deny">Deny</string>
|
||||
<string name="error">Error</string>
|
||||
|
||||
<string name="page_icon">Page icon</string>
|
||||
<string name="page_icon_picker_remove_text">Remove</string>
|
||||
|
||||
|
|
|
@ -56,6 +56,7 @@ dependencies {
|
|||
implementation applicationDependencies.glide
|
||||
implementation applicationDependencies.timber
|
||||
implementation applicationDependencies.betterLinkMovement
|
||||
implementation applicationDependencies.exoPlayer
|
||||
|
||||
testImplementation unitTestDependencies.junit
|
||||
testImplementation unitTestDependencies.kotlinTest
|
||||
|
|
|
@ -25,6 +25,10 @@ import com.agileburo.anytype.core_ui.features.page.BlockViewHolder.Companion.HOL
|
|||
import com.agileburo.anytype.core_ui.features.page.BlockViewHolder.Companion.HOLDER_TASK
|
||||
import com.agileburo.anytype.core_ui.features.page.BlockViewHolder.Companion.HOLDER_TITLE
|
||||
import com.agileburo.anytype.core_ui.features.page.BlockViewHolder.Companion.HOLDER_TOGGLE
|
||||
import com.agileburo.anytype.core_ui.features.page.BlockViewHolder.Companion.HOLDER_VIDEO
|
||||
import com.agileburo.anytype.core_ui.features.page.BlockViewHolder.Companion.HOLDER_VIDEO_EMPTY
|
||||
import com.agileburo.anytype.core_ui.features.page.BlockViewHolder.Companion.HOLDER_VIDEO_ERROR
|
||||
import com.agileburo.anytype.core_ui.features.page.BlockViewHolder.Companion.HOLDER_VIDEO_UPLOAD
|
||||
import com.agileburo.anytype.core_utils.ext.typeOf
|
||||
import timber.log.Timber
|
||||
|
||||
|
@ -48,6 +52,9 @@ class BlockAdapter(
|
|||
private val onFooterClicked: () -> Unit,
|
||||
private val onPageClicked: (String) -> Unit,
|
||||
private val onTextInputClicked: () -> Unit,
|
||||
private val onAddUrlClick: (String, String) -> Unit,
|
||||
private val onAddLocalVideoClick : (String) -> Unit,
|
||||
private val strVideoError: String,
|
||||
private val onPageIconClicked: () -> Unit,
|
||||
private val onDownloadFileClicked: (String) -> Unit
|
||||
) : RecyclerView.Adapter<BlockViewHolder>() {
|
||||
|
@ -174,6 +181,42 @@ class BlockAdapter(
|
|||
)
|
||||
)
|
||||
}
|
||||
HOLDER_VIDEO -> {
|
||||
BlockViewHolder.Video(
|
||||
view = inflater.inflate(
|
||||
R.layout.item_block_video,
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
}
|
||||
HOLDER_VIDEO_EMPTY -> {
|
||||
BlockViewHolder.VideoEmpty(
|
||||
view = inflater.inflate(
|
||||
R.layout.item_block_video_empty,
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
}
|
||||
HOLDER_VIDEO_UPLOAD -> {
|
||||
BlockViewHolder.VideoUpload(
|
||||
view = inflater.inflate(
|
||||
R.layout.item_block_video_uploading,
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
}
|
||||
HOLDER_VIDEO_ERROR -> {
|
||||
BlockViewHolder.VideoError(
|
||||
view = inflater.inflate(
|
||||
R.layout.item_block_video_error,
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
}
|
||||
HOLDER_PAGE -> {
|
||||
BlockViewHolder.Page(
|
||||
view = inflater.inflate(
|
||||
|
@ -387,6 +430,22 @@ class BlockAdapter(
|
|||
onDownloadFileClicked = onDownloadFileClicked
|
||||
)
|
||||
}
|
||||
is BlockViewHolder.Video -> {
|
||||
holder.bind(
|
||||
item = blocks[position] as BlockView.Video
|
||||
)
|
||||
}
|
||||
is BlockViewHolder.VideoError -> {
|
||||
holder.bind(
|
||||
msg = strVideoError
|
||||
)
|
||||
}
|
||||
is BlockViewHolder.VideoEmpty -> {
|
||||
holder.bind(
|
||||
item = blocks[position] as BlockView.VideoEmpty,
|
||||
onAddLocalVideoClick = onAddLocalVideoClick
|
||||
)
|
||||
}
|
||||
is BlockViewHolder.Page -> {
|
||||
holder.bind(
|
||||
item = blocks[position] as BlockView.Page,
|
||||
|
|
|
@ -23,6 +23,10 @@ import com.agileburo.anytype.core_ui.features.page.BlockViewHolder.Companion.HOL
|
|||
import com.agileburo.anytype.core_ui.features.page.BlockViewHolder.Companion.HOLDER_TASK
|
||||
import com.agileburo.anytype.core_ui.features.page.BlockViewHolder.Companion.HOLDER_TITLE
|
||||
import com.agileburo.anytype.core_ui.features.page.BlockViewHolder.Companion.HOLDER_TOGGLE
|
||||
import com.agileburo.anytype.core_ui.features.page.BlockViewHolder.Companion.HOLDER_VIDEO
|
||||
import com.agileburo.anytype.core_ui.features.page.BlockViewHolder.Companion.HOLDER_VIDEO_EMPTY
|
||||
import com.agileburo.anytype.core_ui.features.page.BlockViewHolder.Companion.HOLDER_VIDEO_ERROR
|
||||
import com.agileburo.anytype.core_ui.features.page.BlockViewHolder.Companion.HOLDER_VIDEO_UPLOAD
|
||||
|
||||
/**
|
||||
* UI-models for different types of blocks.
|
||||
|
@ -275,14 +279,62 @@ sealed class BlockView : ViewType {
|
|||
*/
|
||||
data class File(
|
||||
override val id: String,
|
||||
val size: Long,
|
||||
val name: String,
|
||||
val mime: String,
|
||||
val size: Long?,
|
||||
val name: String?,
|
||||
val mime: String?,
|
||||
val url: String
|
||||
) : BlockView() {
|
||||
override fun getViewType() = HOLDER_FILE
|
||||
}
|
||||
|
||||
/**
|
||||
* UI-model for blocks containing videos, with state DONE.
|
||||
* @property id block's id
|
||||
* @property size a file's size
|
||||
* @property name a name
|
||||
* @property size file size (in bytes)
|
||||
*/
|
||||
data class Video(
|
||||
override val id: String,
|
||||
val size: Long?,
|
||||
val name: String?,
|
||||
val mime: String?,
|
||||
val hash: String?,
|
||||
val url: String
|
||||
) : BlockView() {
|
||||
override fun getViewType() = HOLDER_VIDEO
|
||||
}
|
||||
|
||||
/**
|
||||
* UI-model for blocks containing videos, with state UPLOADING.
|
||||
* @property id block's id
|
||||
*/
|
||||
data class VideoUpload(
|
||||
override val id: String
|
||||
) : BlockView() {
|
||||
override fun getViewType() = HOLDER_VIDEO_UPLOAD
|
||||
}
|
||||
|
||||
/**
|
||||
* UI-model for blocks containing videos, with state EMPTY.
|
||||
* @property id block's id
|
||||
*/
|
||||
data class VideoEmpty(
|
||||
override val id: String
|
||||
) : BlockView() {
|
||||
override fun getViewType() = HOLDER_VIDEO_EMPTY
|
||||
}
|
||||
|
||||
/**
|
||||
* UI-model for blocks containing videos, with state ERROR.
|
||||
* @property id block's id
|
||||
*/
|
||||
data class VideoError(
|
||||
override val id: String
|
||||
) : BlockView() {
|
||||
override fun getViewType() = HOLDER_VIDEO_ERROR
|
||||
}
|
||||
|
||||
/**
|
||||
* UI-model for blocks containing page links.
|
||||
* @property id block's id
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
package com.agileburo.anytype.core_ui.features.page
|
||||
|
||||
import android.graphics.Color
|
||||
import android.net.Uri
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.text.Editable
|
||||
import android.view.View
|
||||
import android.widget.TextView.BufferType
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.agileburo.anytype.core_ui.BuildConfig
|
||||
import com.agileburo.anytype.core_ui.R
|
||||
import com.agileburo.anytype.core_ui.common.*
|
||||
import com.agileburo.anytype.core_ui.extensions.color
|
||||
|
@ -26,6 +28,10 @@ import com.bumptech.glide.load.DataSource
|
|||
import com.bumptech.glide.load.engine.GlideException
|
||||
import com.bumptech.glide.request.RequestListener
|
||||
import com.bumptech.glide.request.target.Target
|
||||
import com.google.android.exoplayer2.SimpleExoPlayer
|
||||
import com.google.android.exoplayer2.source.ProgressiveMediaSource
|
||||
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory
|
||||
import com.google.android.exoplayer2.util.Util
|
||||
import kotlinx.android.synthetic.main.item_block_bookmark.view.*
|
||||
import kotlinx.android.synthetic.main.item_block_bulleted.view.*
|
||||
import kotlinx.android.synthetic.main.item_block_checkbox.view.*
|
||||
|
@ -43,6 +49,8 @@ import kotlinx.android.synthetic.main.item_block_task.view.*
|
|||
import kotlinx.android.synthetic.main.item_block_text.view.*
|
||||
import kotlinx.android.synthetic.main.item_block_title.view.*
|
||||
import kotlinx.android.synthetic.main.item_block_toggle.view.*
|
||||
import kotlinx.android.synthetic.main.item_block_video.view.*
|
||||
import kotlinx.android.synthetic.main.item_block_video_error.view.*
|
||||
import timber.log.Timber
|
||||
import android.text.format.Formatter as FileSizeFormatter
|
||||
|
||||
|
@ -617,8 +625,10 @@ sealed class BlockViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
|||
onDownloadFileClicked: (String) -> Unit
|
||||
) {
|
||||
name.text = item.name
|
||||
size.text = FileSizeFormatter.formatFileSize(itemView.context, item.size)
|
||||
when (MimeTypes.category(item.mime)) {
|
||||
item.size?.let {
|
||||
size.text = FileSizeFormatter.formatFileSize(itemView.context, it)
|
||||
}
|
||||
when (item.mime?.let { MimeTypes.category(it) }) {
|
||||
MimeTypes.Category.PDF -> icon.setImageResource(R.drawable.ic_mime_pdf)
|
||||
MimeTypes.Category.IMAGE -> icon.setImageResource(R.drawable.ic_mime_image)
|
||||
MimeTypes.Category.AUDIO -> icon.setImageResource(R.drawable.ic_mime_music)
|
||||
|
@ -628,11 +638,57 @@ 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) }
|
||||
}
|
||||
}
|
||||
|
||||
class Video(view: View) : BlockViewHolder(view) {
|
||||
|
||||
private val icon = itemView.fileIcon
|
||||
private val size = itemView.fileSize
|
||||
private val name = itemView.filename
|
||||
|
||||
fun bind(item: BlockView.Video) {
|
||||
initPlayer(item.url)
|
||||
}
|
||||
|
||||
private fun initPlayer(path: String) {
|
||||
|
||||
itemView.playerView.visibility = View.VISIBLE
|
||||
val player = SimpleExoPlayer.Builder(itemView.context).build()
|
||||
val source = DefaultDataSourceFactory(
|
||||
itemView.context,
|
||||
Util.getUserAgent(itemView.context, BuildConfig.LIBRARY_PACKAGE_NAME)
|
||||
)
|
||||
val mediaSource =
|
||||
ProgressiveMediaSource.Factory(source).createMediaSource(Uri.parse(path))
|
||||
player.playWhenReady = false
|
||||
player.seekTo(0)
|
||||
player.prepare(mediaSource, false, false)
|
||||
itemView.playerView.player = player
|
||||
}
|
||||
}
|
||||
|
||||
class VideoUpload(view: View) : BlockViewHolder(view)
|
||||
|
||||
class VideoError(view: View) : BlockViewHolder(view) {
|
||||
|
||||
fun bind(msg: String) {
|
||||
itemView.tvError.text = msg
|
||||
}
|
||||
}
|
||||
|
||||
class VideoEmpty(view: View) : BlockViewHolder(view) {
|
||||
|
||||
fun bind(item: BlockView.VideoEmpty, onAddLocalVideoClick: (String) -> Unit) {
|
||||
itemView.setOnClickListener {
|
||||
onAddLocalVideoClick(item.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Page(view: View) : BlockViewHolder(view) {
|
||||
|
||||
private val untitled = itemView.resources.getString(R.string.untitled)
|
||||
|
@ -799,6 +855,10 @@ sealed class BlockViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
|||
const val HOLDER_DIVIDER = 16
|
||||
const val HOLDER_HIGHLIGHT = 17
|
||||
const val HOLDER_FOOTER = 18
|
||||
const val HOLDER_VIDEO = 19
|
||||
const val HOLDER_VIDEO_UPLOAD = 20
|
||||
const val HOLDER_VIDEO_EMPTY = 21
|
||||
const val HOLDER_VIDEO_ERROR = 22
|
||||
|
||||
const val FOCUS_TIMEOUT_MILLIS = 16L
|
||||
}
|
||||
|
|
13
core-ui/src/main/res/drawable/ic_video.xml
Normal file
13
core-ui/src/main/res/drawable/ic_video.xml
Normal file
|
@ -0,0 +1,13 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="25dp"
|
||||
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"/>
|
||||
</vector>
|
|
@ -0,0 +1,9 @@
|
|||
<?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="8dp" />
|
||||
</shape>
|
17
core-ui/src/main/res/layout/item_block_video.xml
Normal file
17
core-ui/src/main/res/layout/item_block_video.xml
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/default_page_item_padding_start"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_marginEnd="@dimen/default_page_item_padding_end"
|
||||
android:layout_marginBottom="6dp"
|
||||
android:paddingBottom="1dp">
|
||||
|
||||
<com.google.android.exoplayer2.ui.PlayerView
|
||||
android:id="@+id/playerView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="250dp"
|
||||
android:visibility="gone" />
|
||||
|
||||
</FrameLayout>
|
54
core-ui/src/main/res/layout/item_block_video_empty.xml
Normal file
54
core-ui/src/main/res/layout/item_block_video_empty.xml
Normal file
|
@ -0,0 +1,54 @@
|
|||
<?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"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_marginEnd="@dimen/default_page_item_padding_end"
|
||||
android:layout_marginBottom="6dp"
|
||||
android:background="@drawable/rectangle_block_media_background"
|
||||
android:paddingTop="1dp"
|
||||
android:paddingBottom="1dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icVideo"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:contentDescription="@string/content_description_file_icon"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_video" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/editUrl"
|
||||
style="@style/BlockVideoStyle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="5dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:textColor="#ACA996"
|
||||
android:text="@string/hint_upload"
|
||||
android:inputType="none"
|
||||
|
||||
app:layout_constraintBottom_toBottomOf="@+id/icVideo"
|
||||
app:layout_constraintEnd_toStartOf="@+id/icMore"
|
||||
app:layout_constraintStart_toEndOf="@+id/icVideo"
|
||||
app:layout_constraintTop_toTopOf="@+id/icVideo" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icMore"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_block_more" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
51
core-ui/src/main/res/layout/item_block_video_error.xml
Normal file
51
core-ui/src/main/res/layout/item_block_video_error.xml
Normal file
|
@ -0,0 +1,51 @@
|
|||
<?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"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_marginEnd="@dimen/default_page_item_padding_end"
|
||||
android:layout_marginBottom="6dp"
|
||||
android:background="@drawable/rectangle_block_media_background"
|
||||
android:paddingTop="1dp"
|
||||
android:paddingBottom="1dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icVideo"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:contentDescription="@string/content_description_file_icon"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_video" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvError"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="5dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:textColor="@android:color/holo_red_dark"
|
||||
|
||||
app:layout_constraintBottom_toBottomOf="@+id/icVideo"
|
||||
app:layout_constraintEnd_toStartOf="@+id/icMore"
|
||||
app:layout_constraintStart_toEndOf="@+id/icVideo"
|
||||
app:layout_constraintTop_toTopOf="@+id/icVideo" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icMore"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_block_more" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
62
core-ui/src/main/res/layout/item_block_video_uploading.xml
Normal file
62
core-ui/src/main/res/layout/item_block_video_uploading.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"
|
||||
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"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_marginEnd="@dimen/default_page_item_padding_end"
|
||||
android:layout_marginBottom="6dp"
|
||||
android:background="@drawable/rectangle_block_media_background"
|
||||
android:paddingTop="1dp"
|
||||
android:paddingBottom="1dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icVideo"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:contentDescription="@string/content_description_file_icon"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_video" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/editUrl"
|
||||
style="@style/BlockVideoStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="5dp"
|
||||
android:text="Loading, please wait"
|
||||
android:textColor="#ACA996"
|
||||
|
||||
app:layout_constraintBottom_toBottomOf="@+id/icVideo"
|
||||
app:layout_constraintStart_toEndOf="@+id/icVideo"
|
||||
app:layout_constraintTop_toTopOf="@+id/icVideo" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icMore"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:visibility="invisible"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_block_more" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
style="?android:attr/progressBarStyleSmall"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/editUrl"
|
||||
app:layout_constraintStart_toEndOf="@+id/editUrl"
|
||||
app:layout_constraintTop_toTopOf="@+id/editUrl" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -119,6 +119,8 @@
|
|||
<string name="hint_header_two">Header 2</string>
|
||||
<string name="hint_header_three">Header 3</string>
|
||||
<string name="hint_checkbox">Checkbox</string>
|
||||
<string name="hint_video">Enter video URL</string>
|
||||
<string name="hint_upload">Upload a video</string>
|
||||
<string name="hint_bullet">Bulleted list item</string>
|
||||
<string name="hint_numbered_list">Numbered list item</string>
|
||||
|
||||
|
|
|
@ -177,6 +177,15 @@
|
|||
<item name="android:layout_marginBottom">3dp</item>
|
||||
</style>
|
||||
|
||||
<style name="BlockVideoStyle">
|
||||
<item name="android:textSize">15sp</item>
|
||||
<item name="android:textColor">@color/black</item>
|
||||
<item name="android:inputType">textUri</item>
|
||||
<item name="android:textColorHint">#ACA996</item>
|
||||
<item name="android:background">@null</item>
|
||||
<item name="android:fontFamily">@font/graphik_regular</item>
|
||||
</style>
|
||||
|
||||
<style name="BlockContactContentStyle">
|
||||
<item name="android:layout_width">match_parent</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
|
|
|
@ -848,7 +848,10 @@ class BlockAdapterTest {
|
|||
onPageClicked = {},
|
||||
onTextInputClicked = {},
|
||||
onDownloadFileClicked = {},
|
||||
onPageIconClicked = {}
|
||||
onPageIconClicked = {},
|
||||
onAddLocalVideoClick = {},
|
||||
onAddUrlClick = { _, _ -> },
|
||||
strVideoError = "Error"
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package com.agileburo.anytype.core_ui.features.page
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
class BlockViewTest {
|
||||
|
||||
val ID = "123"
|
||||
|
||||
@Test
|
||||
fun `should return video block with view type Empty`() {
|
||||
|
||||
val block = BlockView.VideoEmpty(id = ID)
|
||||
|
||||
assertEquals(BlockViewHolder.HOLDER_VIDEO_EMPTY, block.getViewType())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should return video block with view type Error`() {
|
||||
|
||||
val block = BlockView.VideoError(id = ID)
|
||||
|
||||
assertEquals(BlockViewHolder.HOLDER_VIDEO_ERROR, block.getViewType())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should return video block with view type Done`() {
|
||||
|
||||
val block = BlockView.Video(id = ID, hash = "", url = "", size = 0L, mime = "", name = "")
|
||||
|
||||
assertEquals(BlockViewHolder.HOLDER_VIDEO, block.getViewType())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should return video block with view type Uploading`() {
|
||||
|
||||
val block = BlockView.VideoUpload(id = ID)
|
||||
|
||||
assertEquals(BlockViewHolder.HOLDER_VIDEO_UPLOAD, block.getViewType())
|
||||
}
|
||||
}
|
|
@ -1,9 +1,11 @@
|
|||
package com.agileburo.anytype.core_utils.ext
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.res.Resources
|
||||
import android.graphics.Rect
|
||||
import android.net.Uri
|
||||
import android.os.Environment
|
||||
import android.provider.MediaStore
|
||||
import android.text.Annotation
|
||||
import android.text.Editable
|
||||
|
@ -49,6 +51,8 @@ fun Throwable.timber() = Timber.e("Get error : ${this.message}")
|
|||
const val DATE_FORMAT_MMMdYYYY = "MMM d, yyyy"
|
||||
const val KEY_ROUNDED = "key"
|
||||
const val VALUE_ROUNDED = "rounded"
|
||||
const val MIME_VIDEO_ALL = "video/*"
|
||||
const val MIME_IMAGE_ALL = "image/*"
|
||||
|
||||
fun Long.formatToDateString(pattern: String, locale: Locale): String {
|
||||
val formatter = SimpleDateFormat(pattern, locale)
|
||||
|
@ -103,4 +107,19 @@ fun Editable.removeRoundedSpans(): Editable {
|
|||
if (span.key == KEY_ROUNDED && span.value == VALUE_ROUNDED) removeSpan(span)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
fun getVideoFileIntent(mediaType: String): Intent {
|
||||
val intent =
|
||||
if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED) {
|
||||
Intent(Intent.ACTION_PICK, MediaStore.Video.Media.EXTERNAL_CONTENT_URI)
|
||||
} else {
|
||||
Intent(Intent.ACTION_PICK, MediaStore.Video.Media.INTERNAL_CONTENT_URI)
|
||||
}
|
||||
return intent.apply {
|
||||
type = mediaType
|
||||
action = Intent.ACTION_GET_CONTENT
|
||||
putExtra("return-data", true)
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
}
|
||||
}
|
|
@ -91,10 +91,9 @@ fun BlockEntity.Content.File.toDomain(): Block.Content.File {
|
|||
hash = hash,
|
||||
name = name,
|
||||
mime = mime,
|
||||
added = added,
|
||||
size = size,
|
||||
type = type.toDomain(),
|
||||
state = state.toDomain()
|
||||
type = type?.toDomain(),
|
||||
state = state?.toDomain()
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -222,10 +221,9 @@ fun Block.Content.File.toEntity(): BlockEntity.Content.File {
|
|||
hash = hash,
|
||||
name = name,
|
||||
mime = mime,
|
||||
added = added,
|
||||
size = size,
|
||||
type = type.toEntity(),
|
||||
state = state.toEntity()
|
||||
type = type?.toEntity(),
|
||||
state = state?.toEntity()
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -387,6 +385,13 @@ fun Command.SetIconName.toEntity() = CommandEntity.SetIconName(
|
|||
name = name
|
||||
)
|
||||
|
||||
fun Command.UploadVideoBlockUrl.toEntity(): CommandEntity.UploadBlock = CommandEntity.UploadBlock(
|
||||
contextId = contextId,
|
||||
blockId = blockId,
|
||||
url = url,
|
||||
filePath = filePath
|
||||
)
|
||||
|
||||
fun Position.toEntity(): PositionEntity {
|
||||
return PositionEntity.valueOf(name)
|
||||
}
|
||||
|
@ -455,6 +460,18 @@ fun EventEntity.toDomain(): Event {
|
|||
fields = Block.Fields(fields.map)
|
||||
)
|
||||
}
|
||||
is EventEntity.Command.UpdateBlockFile -> {
|
||||
Event.Command.UpdateFileBlock(
|
||||
context = context,
|
||||
id = id,
|
||||
type = type?.toDomain(),
|
||||
state = state?.toDomain(),
|
||||
size = size,
|
||||
mime = mime,
|
||||
hash = hash,
|
||||
name = name
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -470,4 +487,10 @@ fun Block.Prototype.toEntity(): BlockEntity.Prototype = when (this) {
|
|||
)
|
||||
}
|
||||
Block.Prototype.Divider -> BlockEntity.Prototype.Divider
|
||||
is Block.Prototype.File -> {
|
||||
BlockEntity.Prototype.File(
|
||||
type = BlockEntity.Content.File.Type.valueOf(this.type.name),
|
||||
state = BlockEntity.Content.File.State.valueOf(this.state.name)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -73,13 +73,12 @@ data class BlockEntity(
|
|||
}
|
||||
|
||||
data class File(
|
||||
val hash: String,
|
||||
val name: String,
|
||||
val type: Type,
|
||||
val mime: String,
|
||||
val size: Long,
|
||||
val added: Long,
|
||||
val state: State
|
||||
val hash: String? = null,
|
||||
val name: String? = null,
|
||||
val type: Type? = null,
|
||||
val mime: String? = null,
|
||||
val size: Long? = null,
|
||||
val state: State? = null
|
||||
) : Content() {
|
||||
enum class Type { NONE, FILE, IMAGE, VIDEO }
|
||||
enum class State { EMPTY, UPLOADING, DONE, ERROR }
|
||||
|
@ -106,5 +105,10 @@ data class BlockEntity(
|
|||
) : Prototype()
|
||||
|
||||
object Divider : Prototype()
|
||||
|
||||
data class File(
|
||||
val state: Content.File.State,
|
||||
val type: Content.File.Type
|
||||
) : Prototype()
|
||||
}
|
||||
}
|
|
@ -36,6 +36,13 @@ class CommandEntity {
|
|||
val isChecked: Boolean
|
||||
)
|
||||
|
||||
class UploadBlock(
|
||||
val contextId: String,
|
||||
val blockId: String,
|
||||
val url: String,
|
||||
val filePath: String
|
||||
)
|
||||
|
||||
class Create(
|
||||
val context: String,
|
||||
val target: String,
|
||||
|
|
|
@ -56,5 +56,16 @@ sealed class EventEntity {
|
|||
val target: String,
|
||||
val fields: BlockEntity.Fields
|
||||
) : Command()
|
||||
|
||||
data class UpdateBlockFile(
|
||||
override val context: String,
|
||||
val id: String,
|
||||
val type: BlockEntity.Content.File.Type? = null,
|
||||
val state: BlockEntity.Content.File.State? = null,
|
||||
val hash: String? = null,
|
||||
val name: String? = null,
|
||||
val size: Long? = null,
|
||||
val mime: String? = null
|
||||
) : Command()
|
||||
}
|
||||
}
|
|
@ -70,4 +70,8 @@ class BlockDataRepository(
|
|||
|
||||
override suspend fun setIconName(command: Command.SetIconName) =
|
||||
factory.remote.setIconName(command.toEntity())
|
||||
|
||||
override suspend fun uploadUrl(command: Command.UploadVideoBlockUrl) {
|
||||
factory.remote.uploadUrl(command.toEntity())
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@ interface BlockDataStore {
|
|||
suspend fun updateTextColor(command: CommandEntity.UpdateTextColor)
|
||||
suspend fun updateBackroundColor(command: CommandEntity.UpdateBackgroundColor)
|
||||
suspend fun updateCheckbox(command: CommandEntity.UpdateCheckbox)
|
||||
suspend fun uploadUrl(command: CommandEntity.UploadBlock)
|
||||
suspend fun dnd(command: CommandEntity.Dnd)
|
||||
suspend fun duplicate(command: CommandEntity.Duplicate): Id
|
||||
suspend fun merge(command: CommandEntity.Merge)
|
||||
|
|
|
@ -23,4 +23,5 @@ interface BlockRemote {
|
|||
suspend fun openDashboard(contextId: String, id: String)
|
||||
suspend fun closeDashboard(id: String)
|
||||
suspend fun setIconName(command: CommandEntity.SetIconName)
|
||||
suspend fun uploadUrl(command: CommandEntity.UploadBlock)
|
||||
}
|
|
@ -43,6 +43,10 @@ class BlockRemoteDataStore(private val remote: BlockRemote) : BlockDataStore {
|
|||
remote.updateCheckbox(command)
|
||||
}
|
||||
|
||||
override suspend fun uploadUrl(command: CommandEntity.UploadBlock) {
|
||||
remote.uploadUrl(command)
|
||||
}
|
||||
|
||||
override suspend fun create(command: CommandEntity.Create): String = remote.create(command)
|
||||
|
||||
override suspend fun dnd(command: CommandEntity.Dnd) {
|
||||
|
|
|
@ -23,6 +23,7 @@ ext {
|
|||
navigation_version = '2.2.0-rc01'
|
||||
|
||||
// Third party libraries
|
||||
exoplayer_version = '2.11.3'
|
||||
glide_version = '4.9.0'
|
||||
dagger_version = '2.11'
|
||||
javaxAnnotations_version = '1.0'
|
||||
|
@ -36,6 +37,8 @@ ext {
|
|||
better_link_method_version = '2.2.0'
|
||||
table_view_version = '0.8.8'
|
||||
rxbinding_version = '3.0.0'
|
||||
permission_disp_version = '4.6.0'
|
||||
pickt_version = "0.1.9"
|
||||
|
||||
// Unit Testing
|
||||
robolectric_version = '4.3.1'
|
||||
|
@ -107,6 +110,10 @@ ext {
|
|||
moshiKotlin: "com.squareup.moshi:moshi-kotlin:$moshi_version",
|
||||
tableView: "com.evrencoskun.library:tableview:$table_view_version",
|
||||
rxBinding: "com.jakewharton.rxbinding3:rxbinding:$rxbinding_version",
|
||||
exoPlayer: "com.google.android.exoplayer:exoplayer:$exoplayer_version",
|
||||
permissionDisp: "org.permissionsdispatcher:permissionsdispatcher:$permission_disp_version",
|
||||
permissionDispCompiler: "org.permissionsdispatcher:permissionsdispatcher-processor:$permission_disp_version",
|
||||
pickT: "com.github.HBiSoft:PickiT:$pickt_version",
|
||||
|
||||
crashlytics: "com.crashlytics.sdk.android:crashlytics:$crashlytics_version",
|
||||
firebaseCore: "com.google.firebase:firebase-core:$firebase_core_version"
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
package com.agileburo.anytype.domain.block.interactor
|
||||
|
||||
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
|
||||
|
||||
class UploadUrl(private val repo: BlockRepository) : BaseUseCase<Unit, UploadUrl.Params>() {
|
||||
|
||||
override suspend fun run(params: Params): Either<Throwable, Unit> = try {
|
||||
repo.uploadUrl(
|
||||
command = Command.UploadVideoBlockUrl(
|
||||
contextId = params.contextId,
|
||||
blockId = params.blockId,
|
||||
url = params.url,
|
||||
filePath = params.filePath
|
||||
)
|
||||
).let {
|
||||
Either.Right(it)
|
||||
}
|
||||
} catch (t: Throwable) {
|
||||
Either.Left(t)
|
||||
}
|
||||
|
||||
data class Params(
|
||||
val contextId: String,
|
||||
val blockId: String,
|
||||
val url: String,
|
||||
val filePath: String
|
||||
)
|
||||
}
|
|
@ -46,6 +46,7 @@ data class Block(
|
|||
fun asLink() = this as Link
|
||||
fun asDashboard() = this as Dashboard
|
||||
fun asDivider() = this as Divider
|
||||
fun asFile() = this as File
|
||||
|
||||
/**
|
||||
* Textual block.
|
||||
|
@ -159,13 +160,12 @@ data class Block(
|
|||
* @property state file state
|
||||
*/
|
||||
data class File(
|
||||
val hash: String,
|
||||
val name: String,
|
||||
val mime: String,
|
||||
val size: Long,
|
||||
val added: Long,
|
||||
val type: Type,
|
||||
val state: State
|
||||
val hash: String? = null,
|
||||
val name: String? = null,
|
||||
val mime: String? = null,
|
||||
val size: Long? = null,
|
||||
val type: Type? = null,
|
||||
val state: State? = null
|
||||
) : Content() {
|
||||
enum class Type { NONE, FILE, IMAGE, VIDEO }
|
||||
enum class State { EMPTY, UPLOADING, DONE, ERROR }
|
||||
|
@ -206,5 +206,10 @@ data class Block(
|
|||
) : Prototype()
|
||||
|
||||
object Divider : Prototype()
|
||||
|
||||
data class File(
|
||||
val type: Content.File.Type,
|
||||
val state: Content.File.State
|
||||
) : Prototype()
|
||||
}
|
||||
}
|
|
@ -128,6 +128,20 @@ sealed class Command {
|
|||
val index: Int
|
||||
)
|
||||
|
||||
/**
|
||||
* Command for updating video block url
|
||||
* @property contextId context id
|
||||
* @property blockId id of the video block
|
||||
* @property url new valid url
|
||||
* @property filePath file uri
|
||||
*/
|
||||
data class UploadVideoBlockUrl(
|
||||
val contextId: Id,
|
||||
val blockId: Id,
|
||||
val url: String,
|
||||
val filePath: String
|
||||
)
|
||||
|
||||
data class SetIconName(
|
||||
val context: Id,
|
||||
val target: Id,
|
||||
|
|
|
@ -35,5 +35,10 @@ interface BlockRepository {
|
|||
suspend fun openDashboard(contextId: String, id: String)
|
||||
suspend fun closeDashboard(id: String)
|
||||
|
||||
/**
|
||||
* Upload url for video block.
|
||||
*/
|
||||
suspend fun uploadUrl(command: Command.UploadVideoBlockUrl)
|
||||
|
||||
suspend fun setIconName(command: Command.SetIconName)
|
||||
}
|
|
@ -86,5 +86,19 @@ sealed class Event {
|
|||
val target: Id,
|
||||
val fields: Block.Fields
|
||||
) : Command()
|
||||
|
||||
/**
|
||||
* Command to update file block content
|
||||
*/
|
||||
data class UpdateFileBlock(
|
||||
override val context: String,
|
||||
val id: Id,
|
||||
val state: Block.Content.File.State? = null,
|
||||
val type: Block.Content.File.Type? = null,
|
||||
val name: String? = null,
|
||||
val hash: String? = null,
|
||||
val mime: String? = null,
|
||||
val size: Long? = null
|
||||
) : Command()
|
||||
}
|
||||
}
|
|
@ -12,16 +12,17 @@ class UrlBuilder(val config: Config) {
|
|||
/**
|
||||
* Builds image url for given [hash]
|
||||
*/
|
||||
fun image(hash: String): Url {
|
||||
return config.gateway + IMAGE_PATH + hash
|
||||
}
|
||||
fun image(hash: String?): Url = config.gateway + IMAGE_PATH + hash
|
||||
|
||||
/**
|
||||
* Builds file url for given [hash]
|
||||
*/
|
||||
fun file(hash: String): Url {
|
||||
return config.gateway + FILE_PATH + hash
|
||||
}
|
||||
fun file(hash: String?): Url = config.gateway + FILE_PATH + hash
|
||||
|
||||
/**
|
||||
* Builds video url for given [hash]
|
||||
*/
|
||||
fun video(hash: String?): Url = config.gateway + FILE_PATH + hash
|
||||
|
||||
companion object {
|
||||
const val IMAGE_PATH = "/image/"
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
package com.agileburo.anytype.domain.misc
|
||||
|
||||
import com.agileburo.anytype.domain.config.Config
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
class UrlBuilderTest {
|
||||
|
||||
private lateinit var config: Config
|
||||
private lateinit var urlBuilder: UrlBuilder
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
config = Config(home = "67889", gateway = "https://anytype.io")
|
||||
urlBuilder = UrlBuilder(config)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should return image url`() {
|
||||
val hash = "image001"
|
||||
|
||||
val expected = config.gateway + UrlBuilder.IMAGE_PATH + hash
|
||||
val actual = urlBuilder.image(hash)
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should return url with null at the end when image hash is null`() {
|
||||
val hash = null
|
||||
|
||||
val expected = config.gateway + UrlBuilder.IMAGE_PATH + null
|
||||
val actual = urlBuilder.image(hash)
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should return url without hash when image hash is empty`() {
|
||||
val hash = ""
|
||||
|
||||
val expected = config.gateway + UrlBuilder.IMAGE_PATH
|
||||
val actual = urlBuilder.image(hash)
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should return file url`() {
|
||||
val hash = "file001"
|
||||
|
||||
val expected = config.gateway + UrlBuilder.FILE_PATH + hash
|
||||
val actual = urlBuilder.file(hash)
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should return url with null at the end when file hash is null`() {
|
||||
val hash = null
|
||||
|
||||
val expected = config.gateway + UrlBuilder.FILE_PATH + null
|
||||
val actual = urlBuilder.file(hash)
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should return url without hash when file hash is empty`() {
|
||||
val hash = ""
|
||||
|
||||
val expected = config.gateway + UrlBuilder.FILE_PATH
|
||||
val actual = urlBuilder.file(hash)
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should return video url`() {
|
||||
val hash = "video001"
|
||||
|
||||
val expected = config.gateway + UrlBuilder.FILE_PATH + hash
|
||||
val actual = urlBuilder.video(hash)
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should return url with null at the end when video hash is null`() {
|
||||
val hash = null
|
||||
|
||||
val expected = config.gateway + UrlBuilder.FILE_PATH + null
|
||||
val actual = urlBuilder.video(hash)
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should return url without hash when video hash is empty`() {
|
||||
val hash = ""
|
||||
|
||||
val expected = config.gateway + UrlBuilder.FILE_PATH
|
||||
val actual = urlBuilder.video(hash)
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
}
|
|
@ -224,24 +224,27 @@ fun Block.file(): BlockEntity.Content.File = BlockEntity.Content.File(
|
|||
hash = file.hash,
|
||||
name = file.name,
|
||||
size = file.size,
|
||||
added = file.addedAt,
|
||||
mime = file.mime,
|
||||
type = when (file.type) {
|
||||
Block.Content.File.Type.File -> BlockEntity.Content.File.Type.FILE
|
||||
Block.Content.File.Type.Image -> BlockEntity.Content.File.Type.IMAGE
|
||||
Block.Content.File.Type.Video -> BlockEntity.Content.File.Type.VIDEO
|
||||
Block.Content.File.Type.None -> BlockEntity.Content.File.Type.NONE
|
||||
else -> throw IllegalStateException("Unexpected file type: $file.type")
|
||||
},
|
||||
state = when (file.state) {
|
||||
Block.Content.File.State.Done -> BlockEntity.Content.File.State.DONE
|
||||
Block.Content.File.State.Empty -> BlockEntity.Content.File.State.EMPTY
|
||||
Block.Content.File.State.Uploading -> BlockEntity.Content.File.State.UPLOADING
|
||||
Block.Content.File.State.Error -> BlockEntity.Content.File.State.ERROR
|
||||
else -> throw IllegalStateException("Unexpected file state: ${file.state}")
|
||||
}
|
||||
type = file.type.entity(),
|
||||
state = file.state.entity()
|
||||
)
|
||||
|
||||
fun Block.Content.File.Type.entity(): BlockEntity.Content.File.Type = when (this) {
|
||||
Block.Content.File.Type.File -> BlockEntity.Content.File.Type.FILE
|
||||
Block.Content.File.Type.Image -> BlockEntity.Content.File.Type.IMAGE
|
||||
Block.Content.File.Type.Video -> BlockEntity.Content.File.Type.VIDEO
|
||||
Block.Content.File.Type.None -> BlockEntity.Content.File.Type.NONE
|
||||
else -> throw IllegalStateException("Unexpected file type: $this")
|
||||
}
|
||||
|
||||
fun Block.Content.File.State.entity(): BlockEntity.Content.File.State = when (this) {
|
||||
Block.Content.File.State.Done -> BlockEntity.Content.File.State.DONE
|
||||
Block.Content.File.State.Empty -> BlockEntity.Content.File.State.EMPTY
|
||||
Block.Content.File.State.Uploading -> BlockEntity.Content.File.State.UPLOADING
|
||||
Block.Content.File.State.Error -> BlockEntity.Content.File.State.ERROR
|
||||
else -> throw IllegalStateException("Unexpected file state: $this")
|
||||
}
|
||||
|
||||
fun Block.bookmark(): BlockEntity.Content.Bookmark = BlockEntity.Content.Bookmark(
|
||||
url = bookmark.url,
|
||||
description = bookmark.description.ifEmpty { null },
|
||||
|
|
|
@ -39,6 +39,10 @@ class BlockMiddleware(
|
|||
)
|
||||
}
|
||||
|
||||
override suspend fun uploadUrl(command: CommandEntity.UploadBlock) {
|
||||
middleware.uploadMediaBlockContent(command)
|
||||
}
|
||||
|
||||
override suspend fun updateTextStyle(command: CommandEntity.UpdateStyle) {
|
||||
middleware.updateTextStyle(command)
|
||||
}
|
||||
|
|
|
@ -296,15 +296,162 @@ public class Middleware {
|
|||
service.blockSetTextBackgroundColor(request);
|
||||
}
|
||||
|
||||
public String createBlock(
|
||||
String contextId,
|
||||
String targetId,
|
||||
PositionEntity position,
|
||||
BlockEntity.Prototype prototype
|
||||
) throws Exception {
|
||||
public void uploadMediaBlockContent(CommandEntity.UploadBlock command) throws Exception {
|
||||
Block.Upload.Request request = Block.Upload.Request
|
||||
.newBuilder()
|
||||
.setFilePath(command.getFilePath())
|
||||
.setUrl(command.getUrl())
|
||||
.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;
|
||||
|
@ -325,120 +472,97 @@ public class Middleware {
|
|||
positionModel = Models.Block.Position.Inner;
|
||||
break;
|
||||
}
|
||||
return positionModel;
|
||||
}
|
||||
|
||||
Models.Block.Content.Text textBlockModel = null;
|
||||
Models.Block.Content.Page pageBlockModel = null;
|
||||
Models.Block.Content.Div dividerBlockModel = null;
|
||||
private Models.Block createBlock(Models.Block.Content.Text textBlockModel) {
|
||||
return Models.Block
|
||||
.newBuilder()
|
||||
.setText(textBlockModel)
|
||||
.build();
|
||||
}
|
||||
|
||||
if (prototype instanceof BlockEntity.Prototype.Text) {
|
||||
private Models.Block createBlock(Models.Block.Content.Page pageBlockModel) {
|
||||
return Models.Block
|
||||
.newBuilder()
|
||||
.setPage(pageBlockModel)
|
||||
.build();
|
||||
}
|
||||
|
||||
BlockEntity.Content.Text.Style style = ((BlockEntity.Prototype.Text) prototype).getStyle();
|
||||
private Models.Block createBlock(Models.Block.Content.Div dividerBlockModel) {
|
||||
return Models.Block
|
||||
.newBuilder()
|
||||
.setDiv(dividerBlockModel)
|
||||
.build();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
} else if (prototype instanceof BlockEntity.Prototype.Page) {
|
||||
pageBlockModel = Models.Block.Content.Page
|
||||
.newBuilder()
|
||||
.setStyle(Models.Block.Content.Page.Style.Empty)
|
||||
.build();
|
||||
} else if (prototype instanceof BlockEntity.Prototype.Divider) {
|
||||
dividerBlockModel = Models.Block.Content.Div
|
||||
.newBuilder()
|
||||
.setStyle(Models.Block.Content.Div.Style.Line)
|
||||
.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 = Models.Block
|
||||
.newBuilder()
|
||||
.setText(textBlockModel)
|
||||
.build();
|
||||
blockModel = createBlock(textBlockModel);
|
||||
} else if (pageBlockModel != null) {
|
||||
blockModel = Models.Block
|
||||
.newBuilder()
|
||||
.setPage(pageBlockModel)
|
||||
.build();
|
||||
blockModel = createBlock(pageBlockModel);
|
||||
} else if (dividerBlockModel != null) {
|
||||
blockModel = Models.Block
|
||||
.newBuilder()
|
||||
.setDiv(dividerBlockModel)
|
||||
.build();
|
||||
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,
|
||||
PositionEntity position,
|
||||
BlockEntity.Prototype prototype
|
||||
) throws Exception {
|
||||
|
||||
Models.Block.Position positionModel = createPosition(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);
|
||||
|
||||
Block.Create.Request request = Block.Create.Request
|
||||
.newBuilder()
|
||||
.setContextId(contextId)
|
||||
|
|
|
@ -23,7 +23,8 @@ class MiddlewareEventChannel(
|
|||
Events.Event.Message.ValueCase.BLOCKSETCHILDRENIDS,
|
||||
Events.Event.Message.ValueCase.BLOCKDELETE,
|
||||
Events.Event.Message.ValueCase.BLOCKSETLINK,
|
||||
Events.Event.Message.ValueCase.BLOCKSETFIELDS
|
||||
Events.Event.Message.ValueCase.BLOCKSETFIELDS,
|
||||
Events.Event.Message.ValueCase.BLOCKSETFILE
|
||||
)
|
||||
|
||||
override fun observeEvents(
|
||||
|
@ -111,6 +112,20 @@ class MiddlewareEventChannel(
|
|||
fields = event.blockSetFields.fields.fields()
|
||||
)
|
||||
}
|
||||
Events.Event.Message.ValueCase.BLOCKSETFILE -> {
|
||||
with(event.blockSetFile) {
|
||||
EventEntity.Command.UpdateBlockFile(
|
||||
context = context,
|
||||
id = id,
|
||||
state = if (hasState()) state.value.entity() else null,
|
||||
type = if (hasType()) type.value.entity() else null,
|
||||
name = if (hasName()) name.value else null,
|
||||
hash = if (hasHash()) hash.value else null,
|
||||
mime = if (hasMime()) mime.value else null,
|
||||
size = if (hasSize()) size.value else null
|
||||
)
|
||||
}
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
|
|
@ -252,6 +252,17 @@ public class DefaultMiddlewareService implements MiddlewareService {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Block.Upload.Response blockUpload(Block.Upload.Request request) throws Exception {
|
||||
byte[] encoded = Lib.blockUpload(request.toByteArray());
|
||||
Block.Upload.Response response = Block.Upload.Response.parseFrom(encoded);
|
||||
if (response.getError() != null && response.getError().getCode() != Block.Upload.Response.Error.Code.NULL) {
|
||||
throw new Exception(response.getError().getDescription());
|
||||
} else {
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Block.Set.Icon.Name.Response blockSetIconName(Block.Set.Icon.Name.Request request) throws Exception {
|
||||
byte[] encoded = Lib.blockSetIconName(request.toByteArray());
|
||||
|
|
|
@ -6,6 +6,7 @@ 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.
|
||||
|
@ -56,4 +57,6 @@ public interface MiddlewareService {
|
|||
BlockList.Duplicate.Response blockListDuplicate(BlockList.Duplicate.Request request) throws Exception;
|
||||
|
||||
Block.Set.Icon.Name.Response blockSetIconName(Block.Set.Icon.Name.Request request) throws Exception;
|
||||
|
||||
Block.Upload.Response blockUpload(Block.Upload.Request request) throws Exception;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,9 @@ package com.agileburo.anytype
|
|||
|
||||
import anytype.Events.Event
|
||||
import anytype.Events.Event.Message
|
||||
import anytype.model.Models
|
||||
import com.agileburo.anytype.common.MockDataFactory
|
||||
import com.agileburo.anytype.data.auth.model.BlockEntity
|
||||
import com.agileburo.anytype.data.auth.model.EventEntity
|
||||
import com.agileburo.anytype.middleware.EventProxy
|
||||
import com.agileburo.anytype.middleware.interactor.MiddlewareEventChannel
|
||||
|
@ -150,4 +152,109 @@ class MiddlewareEventChannelTest {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should return UpdateBlockFile event`() {
|
||||
|
||||
val hash = "785687346534hfjdbsjfbds"
|
||||
val name = "video1.mp4"
|
||||
val mime = "video/*"
|
||||
val size = 999111L
|
||||
val state = Models.Block.Content.File.State.Done
|
||||
val type = Models.Block.Content.File.Type.Video
|
||||
|
||||
val context = MockDataFactory.randomUuid()
|
||||
val id = MockDataFactory.randomUuid()
|
||||
|
||||
val msg = Message
|
||||
.newBuilder()
|
||||
.blockSetFileBuilder
|
||||
.setId(id)
|
||||
.setHash(Event.Block.Set.File.Hash.newBuilder().setValue(hash).build())
|
||||
.setMime(Event.Block.Set.File.Mime.newBuilder().setValue(mime).build())
|
||||
.setSize(Event.Block.Set.File.Size.newBuilder().setValue(size).build())
|
||||
.setType(Event.Block.Set.File.Type.newBuilder().setValue(type).build())
|
||||
.setState(Event.Block.Set.File.State.newBuilder().setValue(state).build())
|
||||
.setName(Event.Block.Set.File.Name.newBuilder().setValue(name).build())
|
||||
.build()
|
||||
|
||||
val message = Message
|
||||
.newBuilder()
|
||||
.setBlockSetFile(msg)
|
||||
|
||||
val event = Event
|
||||
.newBuilder()
|
||||
.setContextId(context)
|
||||
.addMessages(message)
|
||||
.build()
|
||||
|
||||
proxy.stub {
|
||||
on { flow() } doReturn flowOf(event)
|
||||
}
|
||||
|
||||
val expected = listOf(
|
||||
EventEntity.Command.UpdateBlockFile(
|
||||
context = context,
|
||||
id = id,
|
||||
hash = hash,
|
||||
mime = mime,
|
||||
size = size,
|
||||
type = BlockEntity.Content.File.Type.VIDEO,
|
||||
state = BlockEntity.Content.File.State.DONE,
|
||||
name = name
|
||||
)
|
||||
)
|
||||
|
||||
runBlocking {
|
||||
channel.observeEvents(context = context).collect { events ->
|
||||
assertEquals(
|
||||
expected = expected,
|
||||
actual = events
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should return UpdateBlockFile event with nullable values`() {
|
||||
|
||||
val context = MockDataFactory.randomUuid()
|
||||
val id = MockDataFactory.randomUuid()
|
||||
|
||||
val msg = Message
|
||||
.newBuilder()
|
||||
.blockSetFileBuilder
|
||||
.setId(id)
|
||||
.build()
|
||||
|
||||
val message = Message
|
||||
.newBuilder()
|
||||
.setBlockSetFile(msg)
|
||||
|
||||
val event = Event
|
||||
.newBuilder()
|
||||
.setContextId(context)
|
||||
.addMessages(message)
|
||||
.build()
|
||||
|
||||
proxy.stub {
|
||||
on { flow() } doReturn flowOf(event)
|
||||
}
|
||||
|
||||
val expected = listOf(
|
||||
EventEntity.Command.UpdateBlockFile(
|
||||
context = context,
|
||||
id = id
|
||||
)
|
||||
)
|
||||
|
||||
runBlocking {
|
||||
channel.observeEvents(context = context).collect { events ->
|
||||
assertEquals(
|
||||
expected = expected,
|
||||
actual = events
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -111,6 +111,12 @@ fun Block.toView(
|
|||
mime = content.mime,
|
||||
url = urlBuilder.file(content.hash)
|
||||
)
|
||||
Block.Content.File.Type.VIDEO -> content.toVideoView(
|
||||
id = id,
|
||||
urlBuilder = urlBuilder
|
||||
)
|
||||
Block.Content.File.Type.NONE ->
|
||||
throw UnsupportedOperationException("File block type None, not implemented")
|
||||
else -> TODO()
|
||||
}
|
||||
}
|
||||
|
@ -130,6 +136,22 @@ fun Block.toView(
|
|||
else -> TODO()
|
||||
}
|
||||
|
||||
fun Block.Content.File.toVideoView(id: String, urlBuilder: UrlBuilder): BlockView =
|
||||
when (this.state) {
|
||||
Block.Content.File.State.EMPTY -> BlockView.VideoEmpty(id = id)
|
||||
Block.Content.File.State.UPLOADING -> BlockView.VideoUpload(id = id)
|
||||
Block.Content.File.State.DONE -> BlockView.Video(
|
||||
id = id,
|
||||
size = size,
|
||||
name = name,
|
||||
mime = mime,
|
||||
hash = hash,
|
||||
url = urlBuilder.video(hash)
|
||||
)
|
||||
Block.Content.File.State.ERROR -> BlockView.VideoError(id = id)
|
||||
null -> throw NotImplementedError("File block state, should not be null")
|
||||
}
|
||||
|
||||
private fun mapMarks(content: Block.Content.Text): List<Markup.Mark> =
|
||||
content.marks.mapNotNull { mark ->
|
||||
when (mark.type) {
|
||||
|
|
|
@ -49,6 +49,24 @@ class DocumentExternalEventReducer : StateReducer<List<Block>, Event> {
|
|||
replacement = { block -> block.copy(fields = event.fields) },
|
||||
target = { block -> block.id == event.target }
|
||||
)
|
||||
|
||||
is Event.Command.UpdateFileBlock -> state.replace(
|
||||
replacement = { block ->
|
||||
val content = block.content<Block.Content.File>()
|
||||
block.copy(
|
||||
content = content.copy(
|
||||
hash = event.hash ?: content.hash,
|
||||
name = event.name ?: content.name,
|
||||
mime = event.mime ?: content.mime,
|
||||
size = event.size ?: content.size,
|
||||
type = event.type ?: content.type,
|
||||
state = event.state ?: content.state
|
||||
)
|
||||
)
|
||||
},
|
||||
target = { block -> block.id == event.id }
|
||||
)
|
||||
|
||||
else -> state.also { Timber.d("Ignoring event: $event") }
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@ import com.agileburo.anytype.core_ui.common.Markup
|
|||
import com.agileburo.anytype.core_ui.features.page.BlockView
|
||||
import com.agileburo.anytype.core_ui.state.ControlPanelState
|
||||
import com.agileburo.anytype.core_utils.common.EventWrapper
|
||||
import com.agileburo.anytype.core_utils.ext.MIME_VIDEO_ALL
|
||||
import com.agileburo.anytype.core_utils.ext.replace
|
||||
import com.agileburo.anytype.core_utils.ext.withLatestFrom
|
||||
import com.agileburo.anytype.core_utils.ui.ViewStateViewModel
|
||||
|
@ -37,6 +38,8 @@ import kotlinx.coroutines.flow.*
|
|||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
||||
const val EMPTY_PATH = ""
|
||||
|
||||
class PageViewModel(
|
||||
private val openPage: OpenPage,
|
||||
private val closePage: ClosePage,
|
||||
|
@ -55,6 +58,7 @@ class PageViewModel(
|
|||
private val mergeBlocks: MergeBlocks,
|
||||
private val splitBlock: SplitBlock,
|
||||
private val downloadFile: DownloadFile,
|
||||
private val uploadUrl: UploadUrl,
|
||||
private val documentExternalEventReducer: StateReducer<List<Block>, Event>,
|
||||
private val urlBuilder: UrlBuilder,
|
||||
private val emojifier: Emojifier
|
||||
|
@ -94,6 +98,11 @@ class PageViewModel(
|
|||
private val _focus: MutableLiveData<Id> = MutableLiveData()
|
||||
val focus: LiveData<Id> = _focus
|
||||
|
||||
/**
|
||||
* Open gallery and search media files for block with that id
|
||||
*/
|
||||
private var mediaBlockId = ""
|
||||
|
||||
override val navigation = MutableLiveData<EventWrapper<AppNavigation.Command>>()
|
||||
override val commands = MutableLiveData<EventWrapper<Command>>()
|
||||
|
||||
|
@ -737,6 +746,52 @@ class PageViewModel(
|
|||
)
|
||||
}
|
||||
|
||||
fun onAddVideoBlockClicked() {
|
||||
proceedWithCreatingEmptyFileBlock(
|
||||
id = focusChannel.value,
|
||||
type = Content.File.Type.VIDEO
|
||||
)
|
||||
}
|
||||
|
||||
fun onAddLocalVideoClicked(blockId: String) {
|
||||
mediaBlockId = blockId
|
||||
dispatch(Command.OpenGallery(mediaType = MIME_VIDEO_ALL))
|
||||
}
|
||||
|
||||
fun onAddImageBlockClicked() {
|
||||
proceedWithCreatingEmptyFileBlock(
|
||||
id = focusChannel.value,
|
||||
type = Content.File.Type.IMAGE
|
||||
)
|
||||
}
|
||||
|
||||
fun onAddFileBlockClicked() {
|
||||
proceedWithCreatingEmptyFileBlock(
|
||||
id = focusChannel.value,
|
||||
type = Content.File.Type.FILE
|
||||
)
|
||||
}
|
||||
|
||||
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(
|
||||
context = context,
|
||||
target = id,
|
||||
position = position,
|
||||
prototype = Prototype.File(type = type, state = state)
|
||||
)
|
||||
) { result ->
|
||||
result.either(
|
||||
fnL = { Timber.e(it, "Error while creating a block") },
|
||||
fnR = { id -> updateFocus(id) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun onCheckboxClicked(id: String) {
|
||||
val target = blocks.first { it.id == id }
|
||||
|
||||
|
@ -868,6 +923,58 @@ class PageViewModel(
|
|||
}
|
||||
}
|
||||
|
||||
fun onAddVideoUrlClicked(blockId: String, url: String) {
|
||||
//Todo add url validation
|
||||
uploadUrl.invoke(
|
||||
scope = viewModelScope,
|
||||
params = UploadUrl.Params(
|
||||
contextId = context,
|
||||
blockId = blockId,
|
||||
url = url,
|
||||
filePath = EMPTY_PATH
|
||||
)
|
||||
) { result ->
|
||||
result.either(
|
||||
fnL = { Timber.e(it, "Error while upload new url for video block") },
|
||||
fnR = { Timber.d("Upload Url Success") }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun onAddVideoFileClicked(filePath: String?) {
|
||||
if (filePath == null) {
|
||||
Timber.d("Error while getting filePath")
|
||||
return
|
||||
}
|
||||
uploadUrl.invoke(
|
||||
scope = viewModelScope,
|
||||
params = UploadUrl.Params(
|
||||
contextId = context,
|
||||
blockId = mediaBlockId,
|
||||
url = "",
|
||||
filePath = filePath
|
||||
)
|
||||
) { result ->
|
||||
result.either(
|
||||
fnL = { Timber.e(it, "Error while upload new file path for video block") },
|
||||
fnR = { Timber.d("Upload File Path Success") }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun onChooseVideoFileFromMedia() {
|
||||
try {
|
||||
val targetBlock = blocks.first { it.id == mediaBlockId }
|
||||
val targetContent = targetBlock.content as Content.File
|
||||
val newContent = targetContent.copy(state = Content.File.State.UPLOADING)
|
||||
val newBlock = targetBlock.copy(content = newContent)
|
||||
rerenderingBlocks(newBlock)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Error while update block:$mediaBlockId state to Uploading")
|
||||
stateData.value = ViewState.Error("Can't load video for this block")
|
||||
}
|
||||
}
|
||||
|
||||
fun onPageIconClicked() {
|
||||
val target = blocks.first { it.content is Content.Icon }.id
|
||||
dispatch(Command.OpenPagePicker(target))
|
||||
|
@ -880,7 +987,7 @@ class PageViewModel(
|
|||
scope = viewModelScope,
|
||||
params = DownloadFile.Params(
|
||||
url = urlBuilder.file(file.hash),
|
||||
name = file.name
|
||||
name = file.name.orEmpty()
|
||||
)
|
||||
) { result ->
|
||||
result.either(
|
||||
|
@ -917,6 +1024,10 @@ class PageViewModel(
|
|||
data class OpenPagePicker(
|
||||
val target: String
|
||||
) : Command()
|
||||
|
||||
data class OpenGallery(
|
||||
val mediaType: String
|
||||
) : Command()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -30,6 +30,7 @@ open class PageViewModelFactory(
|
|||
private val updateLinkMarks: UpdateLinkMarks,
|
||||
private val removeLinkMark: RemoveLinkMark,
|
||||
private val mergeBlocks: MergeBlocks,
|
||||
private val uploadUrl: UploadUrl,
|
||||
private val splitBlock: SplitBlock,
|
||||
private val documentEventReducer: StateReducer<List<Block>, Event>,
|
||||
private val urlBuilder: UrlBuilder,
|
||||
|
@ -55,6 +56,7 @@ open class PageViewModelFactory(
|
|||
removeLinkMark = removeLinkMark,
|
||||
mergeBlocks = mergeBlocks,
|
||||
splitBlock = splitBlock,
|
||||
uploadUrl = uploadUrl,
|
||||
createPage = createPage,
|
||||
documentExternalEventReducer = documentEventReducer,
|
||||
urlBuilder = urlBuilder,
|
||||
|
|
|
@ -37,7 +37,6 @@ object MockBlockFactory {
|
|||
hash = MockDataFactory.randomUuid(),
|
||||
name = MockDataFactory.randomString(),
|
||||
state = Block.Content.File.State.DONE,
|
||||
added = MockDataFactory.randomLong(),
|
||||
mime = MockDataFactory.randomString(),
|
||||
size = MockDataFactory.randomLong(),
|
||||
type = Block.Content.File.Type.FILE
|
||||
|
|
|
@ -0,0 +1,183 @@
|
|||
package com.agileburo.anytype.presentation.mapper
|
||||
|
||||
import MockDataFactory
|
||||
import com.agileburo.anytype.core_ui.features.page.BlockView
|
||||
import com.agileburo.anytype.domain.block.model.Block
|
||||
import com.agileburo.anytype.domain.config.Config
|
||||
import com.agileburo.anytype.domain.misc.UrlBuilder
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class MapperExtensionKtTest {
|
||||
|
||||
@Test
|
||||
fun `should return block view with type video`() {
|
||||
|
||||
val id = MockDataFactory.randomUuid()
|
||||
val urlBuilder = UrlBuilder(config = Config(home = "home", gateway = "gateway"))
|
||||
|
||||
val name = "name"
|
||||
val size = 10000L
|
||||
val mime = "video/mp4"
|
||||
val hash = "647tyhfgehf7ru"
|
||||
val state = Block.Content.File.State.DONE
|
||||
val type = Block.Content.File.Type.VIDEO
|
||||
|
||||
val block = Block.Content.File(
|
||||
name = name,
|
||||
size = size,
|
||||
mime = mime,
|
||||
hash = hash,
|
||||
state = state,
|
||||
type = type
|
||||
|
||||
)
|
||||
|
||||
val expected = BlockView.Video(
|
||||
id = id,
|
||||
name = name,
|
||||
size = size,
|
||||
mime = mime,
|
||||
hash = hash,
|
||||
url = urlBuilder.video(hash)
|
||||
)
|
||||
|
||||
val actual = block.toVideoView(id, urlBuilder)
|
||||
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should return block view with type video and empty params`() {
|
||||
|
||||
val id = MockDataFactory.randomUuid()
|
||||
val urlBuilder = UrlBuilder(config = Config(home = "home", gateway = "gateway"))
|
||||
|
||||
val state = Block.Content.File.State.DONE
|
||||
val type = Block.Content.File.Type.VIDEO
|
||||
|
||||
val block = Block.Content.File(
|
||||
name = null,
|
||||
size = null,
|
||||
mime = null,
|
||||
hash = null,
|
||||
state = state,
|
||||
type = type
|
||||
|
||||
)
|
||||
|
||||
val expected = BlockView.Video(
|
||||
id = id,
|
||||
name = null,
|
||||
size = null,
|
||||
mime = null,
|
||||
hash = null,
|
||||
url = urlBuilder.video(null)
|
||||
)
|
||||
|
||||
val actual = block.toVideoView(id, urlBuilder)
|
||||
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should return block view with type empty`() {
|
||||
|
||||
val id = MockDataFactory.randomUuid()
|
||||
val urlBuilder = UrlBuilder(config = Config(home = "home", gateway = "gateway"))
|
||||
|
||||
val state = Block.Content.File.State.EMPTY
|
||||
val type = Block.Content.File.Type.VIDEO
|
||||
|
||||
val block = Block.Content.File(
|
||||
name = null,
|
||||
size = null,
|
||||
mime = null,
|
||||
hash = null,
|
||||
state = state,
|
||||
type = type
|
||||
)
|
||||
|
||||
val expected = BlockView.VideoEmpty(
|
||||
id = id
|
||||
)
|
||||
|
||||
val actual = block.toVideoView(id, urlBuilder)
|
||||
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should return block view with type upload`() {
|
||||
|
||||
val id = MockDataFactory.randomUuid()
|
||||
val urlBuilder = UrlBuilder(config = Config(home = "home", gateway = "gateway"))
|
||||
|
||||
val state = Block.Content.File.State.UPLOADING
|
||||
val type = Block.Content.File.Type.VIDEO
|
||||
|
||||
val block = Block.Content.File(
|
||||
name = null,
|
||||
size = null,
|
||||
mime = null,
|
||||
hash = null,
|
||||
state = state,
|
||||
type = type
|
||||
)
|
||||
|
||||
val expected = BlockView.VideoUpload(
|
||||
id = id
|
||||
)
|
||||
|
||||
val actual = block.toVideoView(id, urlBuilder)
|
||||
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should return block view with type error`() {
|
||||
|
||||
val id = MockDataFactory.randomUuid()
|
||||
val urlBuilder = UrlBuilder(config = Config(home = "home", gateway = "gateway"))
|
||||
|
||||
val state = Block.Content.File.State.ERROR
|
||||
val type = Block.Content.File.Type.VIDEO
|
||||
|
||||
val block = Block.Content.File(
|
||||
name = null,
|
||||
size = null,
|
||||
mime = null,
|
||||
hash = null,
|
||||
state = state,
|
||||
type = type
|
||||
)
|
||||
|
||||
val expected = BlockView.VideoError(
|
||||
id = id
|
||||
)
|
||||
|
||||
val actual = block.toVideoView(id, urlBuilder)
|
||||
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test(expected = NotImplementedError::class)
|
||||
fun `should throw NotImplementedError when state null`() {
|
||||
|
||||
val id = MockDataFactory.randomUuid()
|
||||
val urlBuilder = UrlBuilder(config = Config(home = "home", gateway = "gateway"))
|
||||
|
||||
val type = Block.Content.File.Type.VIDEO
|
||||
|
||||
val block = Block.Content.File(
|
||||
name = null,
|
||||
size = null,
|
||||
mime = null,
|
||||
hash = null,
|
||||
state = null,
|
||||
type = type
|
||||
)
|
||||
|
||||
block.toVideoView(id, urlBuilder)
|
||||
}
|
||||
}
|
|
@ -97,6 +97,9 @@ class PageViewModelTest {
|
|||
@Mock
|
||||
lateinit var downloadFile: DownloadFile
|
||||
|
||||
@Mock
|
||||
lateinit var uploadUrl: UploadUrl
|
||||
|
||||
@Mock
|
||||
lateinit var emojifier: Emojifier
|
||||
|
||||
|
@ -2757,7 +2760,7 @@ class PageViewModelTest {
|
|||
scope = any(),
|
||||
params = eq(
|
||||
DownloadFile.Params(
|
||||
name = file.content<Block.Content.File>().name,
|
||||
name = file.content<Block.Content.File>().name.orEmpty(),
|
||||
url = builder.file(
|
||||
hash = file.content<Block.Content.File>().hash
|
||||
)
|
||||
|
@ -2845,7 +2848,8 @@ class PageViewModelTest {
|
|||
documentExternalEventReducer = DocumentExternalEventReducer(),
|
||||
urlBuilder = urlBuilder,
|
||||
downloadFile = downloadFile,
|
||||
emojifier = emojifier
|
||||
emojifier = emojifier,
|
||||
uploadUrl = uploadUrl
|
||||
)
|
||||
}
|
||||
}
|
|
@ -32,17 +32,27 @@ android {
|
|||
|
||||
dependencies {
|
||||
|
||||
def applicationDependencies = rootProject.ext.mainApplication
|
||||
|
||||
implementation 'com.github.HBiSoft:PickiT:0.1.9'
|
||||
|
||||
implementation 'com.vdurmont:emoji-java:5.1.1'
|
||||
|
||||
implementation project(':core-utils')
|
||||
implementation project(':core-ui')
|
||||
implementation project(':library-page-icon-picker-widget')
|
||||
|
||||
implementation applicationDependencies.timber
|
||||
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
implementation 'androidx.appcompat:appcompat:1.0.2'
|
||||
implementation 'androidx.core:core-ktx:1.0.2'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||
|
||||
implementation applicationDependencies.permissionDisp
|
||||
kapt applicationDependencies.permissionDispCompiler
|
||||
|
||||
testImplementation 'junit:junit:4.12'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.0'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
|
||||
|
|
|
@ -3,17 +3,23 @@
|
|||
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.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
|
||||
<application
|
||||
android:name=".SampleApp"
|
||||
android:allowBackup="true"
|
||||
android:fullBackupContent="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme"
|
||||
tools:ignore="GoogleAppIndexingWarning">
|
||||
tools:ignore="GoogleAppIndexingWarning"
|
||||
android:theme="@style/AppTheme">
|
||||
<activity android:name=".MainActivity" />
|
||||
<activity android:name=".PageIconPickerSampleActivity">
|
||||
<activity android:name=".files.LocalFileActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
package com.agileburo.anytype.sample
|
||||
|
||||
import android.app.Application
|
||||
import com.agileburo.anytype.core_utils.tools.CrashlyticsTree
|
||||
import timber.log.Timber
|
||||
|
||||
class SampleApp : Application(){
|
||||
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
setupTimber()
|
||||
}
|
||||
|
||||
private fun setupTimber() {
|
||||
if (BuildConfig.DEBUG)
|
||||
Timber.plant(Timber.DebugTree())
|
||||
else
|
||||
Timber.plant(CrashlyticsTree())
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue