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

Feature/render file and picture blocks (#272)

This commit is contained in:
Evgenii Kozlov 2020-02-28 18:57:52 +03:00 committed by GitHub
parent a6b0c064c9
commit 5fb40987f2
Signed by: github
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 290 additions and 73 deletions

View file

@ -4,10 +4,7 @@ import android.app.Application
import com.agileburo.anytype.BuildConfig
import com.agileburo.anytype.core_utils.tools.CrashlyticsTree
import com.agileburo.anytype.di.common.ComponentManager
import com.agileburo.anytype.di.main.ContextModule
import com.agileburo.anytype.di.main.DaggerMainComponent
import com.agileburo.anytype.di.main.DataModule
import com.agileburo.anytype.di.main.MainComponent
import com.agileburo.anytype.di.main.*
import com.facebook.stetho.Stetho
import timber.log.Timber
@ -18,6 +15,8 @@ class AndroidApplication : Application() {
.builder()
.contextModule(ContextModule(this))
.dataModule(DataModule())
.configModule(ConfigModule())
.utilModule(UtilModule())
.build()
}

View file

@ -5,6 +5,7 @@ import com.agileburo.anytype.domain.block.interactor.*
import com.agileburo.anytype.domain.block.repo.BlockRepository
import com.agileburo.anytype.domain.event.interactor.EventChannel
import com.agileburo.anytype.domain.event.interactor.InterceptEvents
import com.agileburo.anytype.domain.misc.UrlBuilder
import com.agileburo.anytype.domain.page.ClosePage
import com.agileburo.anytype.domain.page.CreatePage
import com.agileburo.anytype.domain.page.OpenPage
@ -51,7 +52,8 @@ class PageModule {
mergeBlocks: MergeBlocks,
splitBlock: SplitBlock,
createPage: CreatePage,
documentExternalEventReducer: DocumentExternalEventReducer
documentExternalEventReducer: DocumentExternalEventReducer,
urlBuilder: UrlBuilder
): PageViewModelFactory = PageViewModelFactory(
openPage = openPage,
closePage = closePage,
@ -69,7 +71,8 @@ class PageModule {
removeLinkMark = removeLinkMark,
mergeBlocks = mergeBlocks,
splitBlock = splitBlock,
documentEventReducer = documentExternalEventReducer
documentEventReducer = documentExternalEventReducer,
urlBuilder = urlBuilder
)
@Provides

View file

@ -0,0 +1,25 @@
package com.agileburo.anytype.di.main
import com.agileburo.anytype.data.auth.repo.config.Configuration
import com.agileburo.anytype.data.auth.repo.config.Configurator
import com.agileburo.anytype.domain.config.Config
import com.agileburo.anytype.middleware.config.DefaultConfigurator
import dagger.Module
import dagger.Provides
import javax.inject.Singleton
@Module
class ConfigModule {
@Provides
@Singleton
fun provideApplicationConfig(configurator: Configurator): Config {
return Configuration(configurator).init()
}
@Provides
@Singleton
fun provideConfigurator(): Configurator {
return DefaultConfigurator()
}
}

View file

@ -10,7 +10,9 @@ import javax.inject.Singleton
ContextModule::class,
DataModule::class,
EventModule::class,
ImageModule::class
ImageModule::class,
ConfigModule::class,
UtilModule::class
]
)
interface MainComponent {

View file

@ -0,0 +1,17 @@
package com.agileburo.anytype.di.main
import com.agileburo.anytype.domain.config.Config
import com.agileburo.anytype.domain.misc.UrlBuilder
import dagger.Module
import dagger.Provides
import javax.inject.Singleton
@Module
class UtilModule {
@Provides
@Singleton
fun provideUrlBuilder(config: Config): UrlBuilder {
return UrlBuilder(config)
}
}

View file

@ -1,17 +0,0 @@
name: CI
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Run a one-line script
run: echo Hello, world!
- name: Run a multi-line script
run: |
echo Add other actions to build,
echo test, and deploy your project.

View file

@ -28,6 +28,15 @@ android {
includeAndroidResources = true
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
}
dependencies {

View file

@ -269,12 +269,14 @@ sealed class BlockView : ViewType {
* UI-model for blocks containing files.
* @property id block's id
* @property size a file's size
* @property filename a filename
* @property name a name
* @property size file size (in bytes)
*/
data class File(
override val id: String,
val size: String,
val filename: String
val size: Long,
val name: String,
val mime: String
) : BlockView() {
override fun getViewType() = HOLDER_FILE
}
@ -330,9 +332,11 @@ sealed class BlockView : ViewType {
/**
* UI-model for a picture block
* @property id block's id
* @property url url of the image file
*/
data class Picture(
override val id: String
override val id: String,
val url: String
) : BlockView() {
override fun getViewType() = HOLDER_PICTURE
}

View file

@ -15,8 +15,10 @@ import com.agileburo.anytype.core_ui.features.page.BlockViewDiffUtil.Payload
import com.agileburo.anytype.core_ui.tools.DefaultSpannableFactory
import com.agileburo.anytype.core_ui.tools.DefaultTextWatcher
import com.agileburo.anytype.core_ui.widgets.text.TextInputWidget
import com.agileburo.anytype.core_utils.const.MimeTypes
import com.agileburo.anytype.core_utils.text.BackspaceKeyDetector
import com.agileburo.anytype.core_utils.text.DefaultEnterKeyDetector
import com.bumptech.glide.Glide
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.*
@ -29,11 +31,13 @@ import kotlinx.android.synthetic.main.item_block_header_two.view.*
import kotlinx.android.synthetic.main.item_block_highlight.view.*
import kotlinx.android.synthetic.main.item_block_numbered.view.*
import kotlinx.android.synthetic.main.item_block_page.view.*
import kotlinx.android.synthetic.main.item_block_picture.view.*
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 timber.log.Timber
import android.text.format.Formatter as FileSizeFormatter
/**
* Viewholder for rendering different type of blocks (i.e its UI-models).
@ -593,9 +597,14 @@ sealed class BlockViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private val name = itemView.filename
fun bind(item: BlockView.File) {
name.text = item.filename
size.text = item.size
// TODO set file icon.
name.text = item.name
size.text = FileSizeFormatter.formatFileSize(itemView.context, item.size)
when (MimeTypes.category(item.mime)) {
MimeTypes.Category.PDF -> icon.setImageResource(R.drawable.ic_mime_pdf)
else -> {
// TODO add images when they are ready.
}
}
}
}
@ -636,8 +645,10 @@ sealed class BlockViewHolder(view: View) : RecyclerView.ViewHolder(view) {
class Picture(view: View) : BlockViewHolder(view) {
private val image = itemView.image
fun bind(item: BlockView.Picture) {
// TODO
Glide.with(image).load(item.url).into(image)
}
}

View file

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="18dp"
android:height="18dp"
android:viewportWidth="18"
android:viewportHeight="18">
<path
android:fillColor="#F55522"
android:pathData="M2,0L16,0A2,2 0,0 1,18 2L18,16A2,2 0,0 1,16 18L2,18A2,2 0,0 1,0 16L0,2A2,2 0,0 1,2 0z" />
<path
android:fillColor="#ffffff"
android:fillType="evenOdd"
android:pathData="M7.4534,3.8617C7.6352,3.4063 8.0107,3.0303 8.5857,3.0303C8.8285,3.0303 9.0634,3.0921 9.2671,3.233C9.4686,3.3723 9.6067,3.566 9.6972,3.7768C9.8712,4.1818 9.8937,4.7038 9.8366,5.2607C9.7661,5.9497 9.5622,6.7915 9.2424,7.7391C9.4333,8.0453 9.6544,8.3492 9.9069,8.6422C10.3059,9.1053 10.7248,9.5157 11.1395,9.8664C11.932,9.7651 12.6314,9.7428 13.2012,9.8003C13.7158,9.8523 14.193,9.9754 14.5301,10.2254C14.7067,10.3563 14.8566,10.5312 14.9377,10.7559C15.0193,10.9821 15.0161,11.2172 14.9551,11.4415C14.8886,11.6859 14.751,11.8953 14.5434,12.042C14.3427,12.1839 14.1103,12.2436 13.8856,12.2569C13.4481,12.2827 12.943,12.1389 12.4408,11.9088C11.9429,11.6806 11.3987,11.3448 10.8491,10.918C10.345,10.9975 9.7963,11.1099 9.2116,11.2576C8.5914,11.4142 7.9884,11.6005 7.4246,11.8051C6.9412,12.6415 6.4386,13.355 5.9559,13.8919C5.5347,14.3602 5.0914,14.7364 4.654,14.9151C4.4298,15.0067 4.1792,15.0578 3.923,15.0149C3.6567,14.9704 3.4308,14.8318 3.258,14.6258C3.0783,14.4115 2.9868,14.1544 3.0015,13.8779C3.0155,13.6156 3.1225,13.3779 3.2596,13.1754C3.529,12.7776 3.9927,12.4015 4.5365,12.0618C5.138,11.6861 5.8933,11.3209 6.7328,10.9965C7.022,10.4719 7.3054,9.8962 7.5718,9.2816C7.7907,8.7766 7.9823,8.2957 8.1463,7.8435C7.6903,7.0045 7.421,6.1587 7.3265,5.4257C7.2521,4.8491 7.2801,4.296 7.4534,3.8617ZM8.5979,6.4181C8.7233,5.9429 8.8048,5.5202 8.8419,5.1588C8.893,4.66 8.8517,4.3422 8.7784,4.1715C8.7453,4.0944 8.7138,4.0661 8.6984,4.0555C8.6852,4.0464 8.6558,4.0303 8.5857,4.0303C8.5345,4.0303 8.4598,4.0381 8.3822,4.2325C8.2961,4.448 8.2555,4.8107 8.3183,5.2978C8.3623,5.6394 8.4545,6.0198 8.5979,6.4181ZM8.8192,8.8863C8.7161,9.1453 8.6061,9.4099 8.4893,9.6793C8.3635,9.9696 8.2334,10.2529 8.1001,10.528C8.385,10.4421 8.6746,10.3618 8.9667,10.288C9.2806,10.2088 9.5873,10.1385 9.8854,10.0773C9.6347,9.8333 9.388,9.572 9.1493,9.295C9.0337,9.1608 8.9237,9.0244 8.8192,8.8863ZM5.7853,12.5071C5.5235,12.6399 5.2824,12.7749 5.0663,12.9099C4.5628,13.2244 4.2398,13.5113 4.0876,13.7361C4.0139,13.8449 4.0013,13.9084 4.0001,13.9311C4.0001,13.9315 4.0001,13.932 4,13.9324C3.9995,13.9408 3.9987,13.9528 4.0242,13.9832C4.058,14.0235 4.0774,14.0269 4.088,14.0286C4.1088,14.0321 4.1668,14.0339 4.2758,13.9894C4.5049,13.8958 4.8265,13.6522 5.2123,13.2232C5.3981,13.0165 5.5903,12.7766 5.7853,12.5071ZM12.4129,10.7701C12.5664,10.8578 12.7151,10.9345 12.8574,10.9997C13.2975,11.2014 13.6268,11.2704 13.8267,11.2586C13.9207,11.253 13.9578,11.2314 13.9662,11.2254C13.9663,11.2253 13.9665,11.2253 13.9666,11.2252C13.9692,11.2236 13.9798,11.2169 13.9901,11.179C14.0062,11.1199 13.9981,11.0979 13.9971,11.0954L13.997,11.0952C13.9959,11.092 13.9869,11.0676 13.9346,11.0287C13.8138,10.9392 13.5527,10.8409 13.1007,10.7952C12.8961,10.7746 12.666,10.766 12.4129,10.7701Z" />
</vector>

View file

@ -1,18 +1,18 @@
<?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"
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:orientation="horizontal">
android:orientation="horizontal"
android:paddingStart="@dimen/default_page_item_padding_start"
android:paddingEnd="@dimen/default_page_item_padding_end">
<ImageView
android:layout_height="24dp"
android:layout_marginTop="3dp"
android:id="@+id/fileIcon"
android:layout_width="24dp"
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_marginTop="3dp"
android:layout_marginBottom="3dp"
android:contentDescription="@string/content_description_file_icon"
app:layout_constraintBottom_toBottomOf="parent"
@ -23,20 +23,21 @@
<TextView
android:id="@+id/filename"
style="@style/BlockFileFilenameContentStyle"
android:layout_width="0dp"
android:layout_marginStart="6dp"
android:layout_marginEnd="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/fileIcon"
app:layout_constraintEnd_toStartOf="@+id/fileSize"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@+id/fileIcon"
app:layout_constraintTop_toTopOf="parent"
tools:text="punkt_berlin.pdf" />
tools:text="W. J. T. Mitchell — There Are No Visual Media.pdf" />
<TextView
android:id="@+id/fileSize"
style="@style/BlockFileFileSizeContentStyle"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/fileMenuButton"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@+id/filename"
app:layout_constraintTop_toTopOf="parent"
tools:text="2.1 MB" />

View file

@ -1,19 +1,13 @@
<?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="64dp"
android:layout_height="match_parent"
android:background="@drawable/rectangle_debug"
android:paddingStart="@dimen/default_page_item_padding_start"
android:paddingEnd="@dimen/default_page_item_padding_end"
android:orientation="vertical">
<TextView
<ImageView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:fontFamily="monospace"
android:gravity="center"
android:text="picture"
android:textColor="@color/orange"
android:textSize="12sp" />
android:layout_height="match_parent" />
</FrameLayout>

View file

@ -0,0 +1,20 @@
package com.agileburo.anytype.core_utils.const
object MimeTypes {
private const val PDF = "application/pdf"
private val IMAGES = listOf(
"image/jpeg"
)
enum class Category {
IMAGE, PDF, PICTURE, DOC, AUDIO, VIDEO, ZIP, OTHER
}
fun category(mime: String): Category = when {
mime == PDF -> Category.PDF
IMAGES.contains(mime) -> Category.IMAGE
else -> Category.OTHER
}
}

View file

@ -0,0 +1,7 @@
package com.agileburo.anytype.data.auth.repo.config
import com.agileburo.anytype.data.auth.mapper.toDomain
class Configuration(private val configurator: Configurator) {
fun init() = configurator.configure().toDomain()
}

View file

@ -0,0 +1,7 @@
package com.agileburo.anytype.data.auth.repo.config
import com.agileburo.anytype.data.auth.model.ConfigEntity
interface Configurator {
fun configure(): ConfigEntity
}

View file

@ -140,6 +140,15 @@ data class Block(
enum class Type { PAGE, DATA_VIEW, DASHBOARD, ARCHIVE }
}
/**
* File block.
* @property hash file hash
* @property name filename
* @property mime mime type
* @property size file size (in bytes)
* @property type file type
* @property state file state
*/
data class File(
val hash: String,
val name: String,

View file

@ -23,13 +23,19 @@ fun Map<String, List<Block>>.asRender(anchor: String): List<Block> {
val children = getValue(anchor)
val result = mutableListOf<Block>()
children.forEach { child ->
if (child.content is Content.Text || child.content is Content.Image
|| child.content is Content.Link || child.content is Content.Divider
) {
result.add(child)
result.addAll(asRender(child.id))
} else if (child.content is Content.Layout)
result.addAll(asRender(child.id))
when (child.content) {
is Content.Text,
is Content.Image,
is Content.Link,
is Content.Divider,
is Content.File -> {
result.add(child)
result.addAll(asRender(child.id))
}
is Content.Layout -> {
result.addAll(asRender(child.id))
}
}
}
return result
}

View file

@ -0,0 +1,30 @@
package com.agileburo.anytype.domain.misc
import com.agileburo.anytype.domain.common.Url
import com.agileburo.anytype.domain.config.Config
/**
* Helper class for building urls for files and images
* @property config configuration properties
*/
class UrlBuilder(val config: Config) {
/**
* Builds image url for given [hash]
*/
fun image(hash: String): Url {
return config.gateway + IMAGE_PATH + hash
}
/**
* Builds file url for given [hash]
*/
fun file(hash: String): Url {
return config.gateway + FILE_PATH + hash
}
companion object {
const val IMAGE_PATH = "/image/"
const val FILE_PATH = "/file/"
}
}

View file

@ -0,0 +1,48 @@
package com.agileburo.anytype.middleware.config
import anytype.Commands.Rpc.Config
import com.agileburo.anytype.data.auth.model.ConfigEntity
import com.agileburo.anytype.data.auth.repo.config.Configurator
import lib.Lib
/**
* Obtains middleware configuration data.
*/
class DefaultConfigurator : Configurator {
override fun configure() = get()
private val builder: () -> ConfigEntity = {
fetchConfig().let { response ->
ConfigEntity(
home = response.homeBlockId,
gateway = response.gatewayUrl
)
}
}
private var instance: ConfigEntity? = null
fun get() = instance ?: builder().also { instance = it }
fun new() = builder().also { instance = it }
fun release() {
instance = null
}
private fun fetchConfig(): Config.Get.Response {
val request = Config.Get.Request.newBuilder().build()
val encoded = Lib.configGet(request.toByteArray())
val response = Config.Get.Response.parseFrom(encoded)
return parseResponse(response)
}
private fun parseResponse(response: Config.Get.Response): Config.Get.Response {
return if (response.error != null && response.error.code != Config.Get.Response.Error.Code.NULL) {
throw Exception(response.error.description)
} else {
response
}
}
}

View file

@ -5,11 +5,13 @@ import com.agileburo.anytype.core_ui.features.page.BlockView
import com.agileburo.anytype.domain.block.model.Block
import com.agileburo.anytype.domain.block.model.Block.Content.Text.Style
import com.agileburo.anytype.domain.dashboard.model.HomeDashboard
import com.agileburo.anytype.domain.misc.UrlBuilder
import com.agileburo.anytype.presentation.desktop.DashboardView
fun Block.toView(
focused: Boolean = false,
numbers: Map<String, Int> = emptyMap()
numbers: Map<String, Int> = emptyMap(),
urlBuilder: UrlBuilder
): BlockView = when (val content = this.content) {
is Block.Content.Text -> {
when (content.style) {
@ -86,10 +88,20 @@ fun Block.toView(
)
}
}
is Block.Content.Image -> {
BlockView.Picture(
id = id
)
is Block.Content.File -> {
when (content.type) {
Block.Content.File.Type.IMAGE -> BlockView.Picture(
id = id,
url = urlBuilder.image(content.hash)
)
Block.Content.File.Type.FILE -> BlockView.File(
id = id,
size = content.size,
name = content.name,
mime = content.mime
)
else -> TODO()
}
}
is Block.Content.Link -> {
BlockView.Page(

View file

@ -19,6 +19,7 @@ import com.agileburo.anytype.domain.common.Id
import com.agileburo.anytype.domain.event.interactor.InterceptEvents
import com.agileburo.anytype.domain.event.model.Event
import com.agileburo.anytype.domain.ext.*
import com.agileburo.anytype.domain.misc.UrlBuilder
import com.agileburo.anytype.domain.page.ClosePage
import com.agileburo.anytype.domain.page.CreatePage
import com.agileburo.anytype.domain.page.OpenPage
@ -50,7 +51,8 @@ class PageViewModel(
private val removeLinkMark: RemoveLinkMark,
private val mergeBlocks: MergeBlocks,
private val splitBlock: SplitBlock,
private val documentExternalEventReducer: StateReducer<List<Block>, Event>
private val documentExternalEventReducer: StateReducer<List<Block>, Event>,
private val urlBuilder: UrlBuilder
) : ViewStateViewModel<PageViewModel.ViewState>(),
SupportNavigation<EventWrapper<AppNavigation.Command>>,
StateReducer<List<Block>, Event> by documentExternalEventReducer {
@ -258,14 +260,26 @@ class PageViewModel(
is Content.Text -> {
block.toView(
focused = block.id == focus,
numbers = numbers
numbers = numbers,
urlBuilder = urlBuilder
)
}
is Content.Image -> {
block.toView()
block.toView(
urlBuilder = urlBuilder
)
}
is Content.Link -> block.toView()
is Content.Divider -> block.toView()
is Content.File -> {
block.toView(
urlBuilder = urlBuilder
)
}
is Content.Link -> block.toView(
urlBuilder = urlBuilder
)
is Content.Divider -> block.toView(
urlBuilder = urlBuilder
)
else -> null
}
}

View file

@ -6,6 +6,7 @@ import com.agileburo.anytype.domain.block.interactor.*
import com.agileburo.anytype.domain.block.model.Block
import com.agileburo.anytype.domain.event.interactor.InterceptEvents
import com.agileburo.anytype.domain.event.model.Event
import com.agileburo.anytype.domain.misc.UrlBuilder
import com.agileburo.anytype.domain.page.ClosePage
import com.agileburo.anytype.domain.page.CreatePage
import com.agileburo.anytype.domain.page.OpenPage
@ -28,7 +29,8 @@ open class PageViewModelFactory(
private val removeLinkMark: RemoveLinkMark,
private val mergeBlocks: MergeBlocks,
private val splitBlock: SplitBlock,
private val documentEventReducer: StateReducer<List<Block>, Event>
private val documentEventReducer: StateReducer<List<Block>, Event>,
private val urlBuilder: UrlBuilder
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
@ -50,7 +52,8 @@ open class PageViewModelFactory(
mergeBlocks = mergeBlocks,
splitBlock = splitBlock,
createPage = createPage,
documentExternalEventReducer = documentEventReducer
documentExternalEventReducer = documentEventReducer,
urlBuilder = urlBuilder
) as T
}
}