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

Update coroutines library (#1171)

* update lib

* ci

* fixes

* Update workflow.yml
This commit is contained in:
Konstantin Ivanov 2020-12-11 17:00:49 +03:00 committed by GitHub
parent 3a5eb40665
commit bd87851956
Signed by: github
GPG key ID: 4AEE18F83AFDEB23
143 changed files with 103 additions and 11824 deletions

View file

@ -29,7 +29,7 @@ dependencies {
def app = rootProject.ext.mainApplication
def analytics = rootProject.ext.analytics
implementation app.kotlin
implementation app.coroutines
implementation app.coroutinesAndroid
implementation app.timber
implementation analytics.amplitude
}

View file

@ -106,7 +106,7 @@ dependencies {
//Application dependencies
implementation applicationDependencies.kotlin
implementation applicationDependencies.coroutines
implementation applicationDependencies.coroutinesAndroid
implementation applicationDependencies.fragment
implementation applicationDependencies.navigation
implementation applicationDependencies.navigationUi
@ -118,7 +118,6 @@ dependencies {
implementation applicationDependencies.dagger
implementation applicationDependencies.timber
implementation applicationDependencies.gson
implementation applicationDependencies.rxRelay
implementation applicationDependencies.tableView
implementation applicationDependencies.permissionDisp
implementation applicationDependencies.pickT

View file

@ -2,30 +2,19 @@ package com.anytypeio.anytype.ui.database.kanban
import android.os.Bundle
import android.view.View
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.LinearLayoutManager
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_ui.layout.SpacingItemDecoration
import com.anytypeio.anytype.core_utils.ext.disposedBy
import com.anytypeio.anytype.core_utils.ext.px
import com.anytypeio.anytype.core_utils.ext.toast
import com.anytypeio.anytype.core_utils.ui.ViewState
import com.anytypeio.anytype.presentation.databaseview.KanbanBoardViewModel
import com.anytypeio.anytype.ui.database.kanban.adapter.KanbanColumnAdapter
import com.anytypeio.anytype.ui.database.kanban.helpers.KanbanBoardCallback
import com.anytypeio.anytype.ui.database.kanban.helpers.KanbanBoardListener
import io.reactivex.disposables.CompositeDisposable
import kotlinx.android.synthetic.main.fragment_kanban.*
const val COLUMN_WIDTH = 1.46 // На эту величину делим ширину экрана
class KanbanBoardFragment : Fragment(R.layout.fragment_kanban) {
private val subscriptions by lazy { CompositeDisposable() }
private val vm by lazy {
ViewModelProviders.of(this).get(KanbanBoardViewModel::class.java)
}
@ -59,64 +48,59 @@ class KanbanBoardFragment : Fragment(R.layout.fragment_kanban) {
}
private fun startObservingViewModel() {
vm.observeKanbanBoard().subscribe { state ->
when (state) {
is ViewState.Success -> {
state.data.forEachIndexed { index, kanbanColumnView ->
val header =
View.inflate(
requireContext(), R.layout.item_kanban_column_header, null
)
.apply {
findViewById<TextView>(R.id.columnName).apply {
text = kanbanColumnView.name
(layoutParams as ConstraintLayout.LayoutParams).run {
if (index != 0) this.marginStart = 10.px
}
}
}
var startSpace = 0
val space = 10.px
if (index != 0) {
startSpace = 10.px
}
mBoardView.addColumn(
KanbanColumnAdapter(
kanbanColumnView.rows
),
header,
header,
true,
LinearLayoutManager(requireContext())
)
.apply {
addItemDecoration(
SpacingItemDecoration(
spacingStart = startSpace,
spacingTop = space,
spacingEnd = space,
spacingBottom = space
)
)
}
}
}
is ViewState.Error -> {
requireActivity().toast(state.error)
}
is ViewState.Loading -> {
TODO()
}
}
}.disposedBy(subscriptions)
}
override fun onDestroyView() {
super.onDestroyView()
subscriptions.clear()
// vm.observeKanbanBoard().subscribe { state ->
// when (state) {
// is ViewState.Success -> {
// state.data.forEachIndexed { index, kanbanColumnView ->
// val header =
// View.inflate(
// requireContext(), R.layout.item_kanban_column_header, null
// )
// .apply {
// findViewById<TextView>(R.id.columnName).apply {
// text = kanbanColumnView.name
// (layoutParams as ConstraintLayout.LayoutParams).run {
// if (index != 0) this.marginStart = 10.px
// }
// }
//
// }
// var startSpace = 0
// val space = 10.px
// if (index != 0) {
// startSpace = 10.px
// }
//
// mBoardView.addColumn(
// KanbanColumnAdapter(
// kanbanColumnView.rows
// ),
// header,
// header,
// true,
// LinearLayoutManager(requireContext())
// )
// .apply {
// addItemDecoration(
// SpacingItemDecoration(
// spacingStart = startSpace,
// spacingTop = space,
// spacingEnd = space,
// spacingBottom = space
// )
// )
// }
// }
// }
//
// is ViewState.Error -> {
// requireActivity().toast(state.error)
// }
//
// is ViewState.Loading -> {
// TODO()
// }
// }
// }
}
}

View file

@ -52,7 +52,7 @@ dependencies {
implementation project(':data')
implementation applicationDependencies.kotlin
implementation applicationDependencies.coroutines
implementation applicationDependencies.coroutinesAndroid
implementation applicationDependencies.timber
testImplementation unitTestDependencies.junit

View file

@ -50,7 +50,7 @@ dependencies {
implementation applicationDependencies.appcompat
implementation applicationDependencies.kotlin
implementation applicationDependencies.coroutines
implementation applicationDependencies.coroutinesAndroid
implementation applicationDependencies.androidxCore
implementation applicationDependencies.design

View file

@ -32,7 +32,7 @@ dependencies {
implementation applicationDependencies.appcompat
implementation applicationDependencies.kotlin
implementation applicationDependencies.coroutines
implementation applicationDependencies.coroutinesAndroid
implementation applicationDependencies.dagger
implementation applicationDependencies.timber
@ -40,9 +40,6 @@ dependencies {
implementation applicationDependencies.constraintLayout
implementation applicationDependencies.rxjava2
implementation applicationDependencies.rxAndroid
testImplementation unitTestDependencies.junit
testImplementation unitTestDependencies.kotlinTest
testImplementation unitTestDependencies.mockito

View file

@ -1,43 +0,0 @@
package com.anytypeio.anytype.core_utils.ext
import io.reactivex.Scheduler
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import io.reactivex.schedulers.TestScheduler
/**
* Created by Konstantin Ivanov
* email : ki@agileburo.com
* on 02.04.2019.
*/
interface BaseSchedulerProvider {
fun io(): Scheduler
fun computation(): Scheduler
fun ui(): Scheduler
}
class SchedulerProvider : BaseSchedulerProvider {
override fun computation() = Schedulers.computation()
override fun ui() = AndroidSchedulers.mainThread()
override fun io() = Schedulers.io()
}
class TrampolineSchedulerProvider : BaseSchedulerProvider {
override fun computation() = Schedulers.trampoline()
override fun ui() = Schedulers.trampoline()
override fun io() = Schedulers.trampoline()
}
class TestSchedulerProvider(private val scheduler: TestScheduler) :
BaseSchedulerProvider {
override fun computation() = scheduler
override fun ui() = scheduler
override fun io() = scheduler
}
fun Disposable.disposedBy(subscriptions: CompositeDisposable) {
subscriptions.add(this)
}

View file

@ -7,7 +7,7 @@ allprojects {
ext {
// Kotlin
kotlin_coroutines_version = '1.3.9'
kotlin_coroutines_version = '1.4.2'
kotlinx_serialization_json_version = '1.0.0'
// AndroidX
@ -39,13 +39,9 @@ ext {
javaxInject_version = '1'
retrofit_version = '2.3.0'
okhttp_logging_interceptor_version = '3.8.1'
rxjava2_version = '2.1.1'
moshi_version = '1.8.0'
gson_version = '2.8.6'
rxrelay_version = '2.1.0'
better_link_method_version = '2.2.0'
table_view_version = '0.8.9.2'
rxbinding_version = '3.0.0'
permission_disp_version = '4.6.0'
pickt_version = "0.1.11"
zxing_version = "4.1.0"
@ -56,7 +52,7 @@ ext {
junit_version = '4.12'
mockito_version = '1.4.0'
kluent_version = '1.14'
coroutine_testing_version = '1.3.2'
coroutine_testing_version = '1.4.2'
live_data_testing_version = '1.1.0'
mockito_kotlin_version = '2.2.0'
mockito_android_version = '2.25.0'
@ -94,6 +90,7 @@ ext {
mainApplication = [
kotlin: "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version",
coroutines: "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version",
coroutinesAndroid: "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version",
androidxCore: "androidx.core:core-ktx:$androidx_core_version",
fragment: "androidx.fragment:fragment-ktx:$fragment_version",
navigation: "androidx.navigation:navigation-fragment-ktx:$navigation_version",
@ -120,15 +117,10 @@ ext {
javaxAnnotation: "javax.annotation:jsr250-api:$javaxAnnotations_version",
javaxInject: "javax.inject:javax.inject:$javaxInject_version",
gson: "com.google.code.gson:gson:$gson_version",
rxRelay: "com.jakewharton.rxrelay2:rxrelay:$rxrelay_version",
retrofit: "com.squareup.retrofit2:converter-gson:$retrofit_version",
okhttpLoggingInterceptor: "com.squareup.okhttp3:logging-interceptor:$okhttp_logging_interceptor_version",
timber: "com.jakewharton.timber:timber:$timber_version",
rxjava2: "io.reactivex.rxjava2:rxjava:$rxjava2_version",
rxAndroid: "io.reactivex.rxjava2:rxandroid:$rxjava2_version",
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",

View file

@ -46,7 +46,7 @@ dependencies {
implementation project(':data')
implementation applicationDependencies.kotlin
implementation applicationDependencies.coroutines
implementation applicationDependencies.coroutinesAndroid
implementation applicationDependencies.androidxCore
implementation applicationDependencies.timber

View file

@ -1 +0,0 @@
/build

View file

@ -1,70 +0,0 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
android {
def config = rootProject.extensions.getByName("ext")
compileSdkVersion config["compile_sdk"]
defaultConfig {
minSdkVersion config["min_sdk"]
targetSdkVersion config["target_sdk"]
testInstrumentationRunner config["test_runner"]
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
buildConfigField "String", "TEST_JSON", '"marks.json"'
}
debug {
buildConfigField "String", "TEST_JSON", '"marks.json"'
}
}
sourceSets { main { assets.srcDirs = ['src/main/assets', 'src/main/assets/'] } }
}
dependencies {
implementation project(':core-utils')
def applicationDependencies = rootProject.ext.mainApplication
def unitTestDependencies = rootProject.ext.unitTesting
def acceptenceTesting = rootProject.ext.acceptanceTesting
kapt applicationDependencies.daggerCompiler
compileOnly applicationDependencies.javaxInject
implementation applicationDependencies.kotlin
implementation applicationDependencies.viewModel
implementation applicationDependencies.viewModelExtensions
implementation applicationDependencies.appcompat
implementation applicationDependencies.design
implementation applicationDependencies.constraintLayout
implementation applicationDependencies.recyclerView
implementation applicationDependencies.rxjava2
implementation applicationDependencies.rxRelay
implementation applicationDependencies.rxAndroid
implementation applicationDependencies.dagger
implementation applicationDependencies.timber
implementation applicationDependencies.gson
implementation applicationDependencies.betterLinkMovement
implementation applicationDependencies.glide
kapt applicationDependencies.glideCompiler
testImplementation unitTestDependencies.junit
testImplementation unitTestDependencies.kotlinTest
testImplementation unitTestDependencies.mockito
androidTestImplementation acceptenceTesting.espressoCore
androidTestImplementation acceptenceTesting.androidJUnit
androidTestImplementation acceptenceTesting.testRules
}

View file

@ -1,21 +0,0 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View file

@ -1,251 +0,0 @@
package com.agileburo.anytype.feature_editor
import android.graphics.Typeface
import android.text.SpannableString
import android.text.style.StrikethroughSpan
import android.text.style.StyleSpan
import androidx.test.runner.AndroidJUnit4
import com.agileburo.anytype.feature_editor.domain.*
import com.agileburo.anytype.feature_editor.factory.AndroidDataFactory
import com.agileburo.anytype.feature_editor.presentation.mapper.BlockModelMapper
import com.agileburo.anytype.feature_editor.presentation.model.BlockView
import junit.framework.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
/**
* Created by Konstantin Ivanov
* email : ki@agileburo.com
* on 2019-05-15.
*/
class BlockModelMapperTest {
lateinit var blockMapper: BlockModelMapper
val BOLD = "BOLD"
val ITALIC = "ITALIC"
val STRIKE = "STRIKE_THROUGH"
val spannableText = SpannableString("This can be simply solved by using Spannable String").apply {
setSpan(StyleSpan(Typeface.BOLD), 0, 3, 0)
setSpan(StyleSpan(Typeface.BOLD), 19, 24, 0)
setSpan(StyleSpan(Typeface.ITALIC), 12, 17, 0)
setSpan(StrikethroughSpan(), 27, 33, 0)
}
val blockView = BlockView.ParagraphView(
id = "2321",
text = spannableText
)
@Before
fun init() {
blockMapper = BlockModelMapper()
}
@Test
fun testShouldBeTheSameMarks() {
val block = blockMapper.mapToModel(blockView)
val content = block.content as Content.Text
assertEquals(4, content.marks.size)
assertEquals(3, content.marks[0].end)
assertEquals(BOLD, content.marks[0].type.name)
assertEquals(24, content.marks[1].end)
assertEquals(BOLD, content.marks[1].type.name)
assertEquals(12, content.marks[2].start)
assertEquals(ITALIC, content.marks[2].type.name)
assertEquals(33, content.marks[3].end)
assertEquals(STRIKE, content.marks[3].type.name)
}
@Test
fun paragraphViewConvertedCorrectly() {
val view = BlockView.ParagraphView(
id = AndroidDataFactory.randomString(),
text = SpannableString(AndroidDataFactory.randomString())
)
val block = blockMapper.mapToModel(view)
val content = block.content as Content.Text
assertEquals(view.text.toString(), content.text)
assertEquals(view.id, view.id)
assertEquals(block.blockType, BlockType.Editable)
assertEquals(block.contentType, ContentType.P)
}
@Test
fun bulletViewConvertedCorrectly() {
val view = BlockView.BulletView(
id = AndroidDataFactory.randomString(),
text = SpannableString(AndroidDataFactory.randomString())
)
val block = blockMapper.mapToModel(view)
val content = block.content as Content.Text
assertEquals(view.text.toString(), content.text)
assertEquals(view.id, view.id)
assertEquals(block.blockType, BlockType.Editable)
assertEquals(block.contentType, ContentType.UL)
}
@Test
fun quoteViewConvertedCorrectly() {
val view = BlockView.QuoteView(
id = AndroidDataFactory.randomString(),
text = SpannableString(AndroidDataFactory.randomString())
)
val block = blockMapper.mapToModel(view)
val content = block.content as Content.Text
assertEquals(view.text.toString(), content.text)
assertEquals(view.id, view.id)
assertEquals(block.blockType, BlockType.Editable)
assertEquals(block.contentType, ContentType.Quote)
}
@Test
fun checkboxViewConvertedCorrectly() {
val view = BlockView.CheckboxView(
id = AndroidDataFactory.randomString(),
text = SpannableString(AndroidDataFactory.randomString()),
isChecked = AndroidDataFactory.randomBoolean()
)
val block = blockMapper.mapToModel(view)
val content = block.content as Content.Text
assertEquals(view.text.toString(), content.text)
assertEquals(view.id, view.id)
assertEquals(block.blockType, BlockType.Editable)
assertEquals(block.contentType, ContentType.Check)
assertEquals(content.param.checked, view.isChecked)
}
@Test
fun numberedListItemConvertedCorrectly() {
val view = BlockView.NumberListItemView(
id = AndroidDataFactory.randomString(),
text = SpannableString(AndroidDataFactory.randomString()),
number = AndroidDataFactory.randomInt()
)
val block = blockMapper.mapToModel(view)
val content = block.content as Content.Text
assertEquals(view.text.toString(), content.text)
assertEquals(view.id, view.id)
assertEquals(block.blockType, BlockType.Editable)
assertEquals(block.contentType, ContentType.NumberedList)
assertEquals(content.param.number, view.number)
}
@Test
fun codeSnippetViewConvertedCorrectly() {
val view = BlockView.CodeSnippetView(
id = AndroidDataFactory.randomString(),
text = SpannableString(AndroidDataFactory.randomString())
)
val block = blockMapper.mapToModel(view)
val content = block.content as Content.Text
assertEquals(view.text.toString(), content.text)
assertEquals(view.id, view.id)
assertEquals(block.blockType, BlockType.Editable)
assertEquals(block.contentType, ContentType.Code)
}
@Test
fun headerOneViewConvertedCorrectly() {
val headerOneView = BlockView.HeaderView(
id = AndroidDataFactory.randomString(),
text = SpannableString(AndroidDataFactory.randomString()),
type = BlockView.HeaderView.HeaderType.ONE
)
val headerOneBlock = blockMapper.mapToModel(headerOneView)
val content = headerOneBlock.content as Content.Text
assertEquals(headerOneView.text.toString(), content.text)
assertEquals(headerOneView.id, headerOneView.id)
assertEquals(headerOneBlock.blockType, BlockType.Editable)
assertEquals(headerOneBlock.contentType, ContentType.H1)
}
@Test
fun headerTwoViewConvertedCorrectly() {
val headerTwoView = BlockView.HeaderView(
id = AndroidDataFactory.randomString(),
text = SpannableString(AndroidDataFactory.randomString()),
type = BlockView.HeaderView.HeaderType.TWO
)
val headerTwoBlock = blockMapper.mapToModel(headerTwoView)
val content = headerTwoBlock.content as Content.Text
assertEquals(headerTwoView.text.toString(), content.text)
assertEquals(headerTwoView.id, headerTwoView.id)
assertEquals(headerTwoBlock.blockType, BlockType.Editable)
assertEquals(headerTwoBlock.contentType, ContentType.H2)
}
@Test
fun headerThreeViewConvertedCorrectly() {
val headerThreeView = BlockView.HeaderView(
id = AndroidDataFactory.randomString(),
text = SpannableString(AndroidDataFactory.randomString()),
type = BlockView.HeaderView.HeaderType.THREE
)
val headerThreeBlock = blockMapper.mapToModel(headerThreeView)
val content = headerThreeBlock.content as Content.Text
assertEquals(headerThreeView.text.toString(), content.text)
assertEquals(headerThreeView.id, headerThreeView.id)
assertEquals(headerThreeBlock.blockType, BlockType.Editable)
assertEquals(headerThreeBlock.contentType, ContentType.H3)
}
@Test
fun headerFourViewConvertedCorrectly() {
val headerFourView = BlockView.HeaderView(
id = AndroidDataFactory.randomString(),
text = SpannableString(AndroidDataFactory.randomString()),
type = BlockView.HeaderView.HeaderType.FOUR
)
val headerFourBlock = blockMapper.mapToModel(headerFourView)
val content = headerFourBlock.content as Content.Text
assertEquals(headerFourView.text.toString(), content.text)
assertEquals(headerFourView.id, headerFourView.id)
assertEquals(headerFourBlock.blockType, BlockType.Editable)
assertEquals(headerFourBlock.contentType, ContentType.H4)
}
}

View file

@ -1,42 +0,0 @@
package com.agileburo.anytype.feature_editor
import android.text.SpannableString
import androidx.recyclerview.widget.DiffUtil
import com.agileburo.anytype.feature_editor.factory.AndroidDataFactory
import com.agileburo.anytype.feature_editor.presentation.model.BlockView
import com.agileburo.anytype.feature_editor.presentation.util.BlockViewDiffUtil
import org.junit.Assert
import org.junit.Before
import org.junit.Test
class BlockViewDiffUtilTest {
private lateinit var diffUtil : DiffUtil.Callback
@Before
fun init() {
val firstBlock = BlockView.ParagraphView(
id = AndroidDataFactory.randomString(),
text = SpannableString(AndroidDataFactory.randomString())
)
val second = firstBlock.copy()
diffUtil = BlockViewDiffUtil(listOf(firstBlock), listOf(second))
}
@Test
fun blocksShouldBeTheSame() {
val result = diffUtil.areItemsTheSame(0, 0)
assert(result)
}
@Test
fun blockShouldBeEquals() {
val result = diffUtil.areContentsTheSame(0,0)
Assert.assertTrue(result)
}
}

View file

@ -1,48 +0,0 @@
package com.agileburo.anytype.feature_editor.factory
import java.util.*
import java.util.concurrent.ThreadLocalRandom
object AndroidDataFactory {
fun randomUuid(): String {
return UUID.randomUUID().toString()
}
fun randomString(): String {
return randomUuid()
}
fun randomInt(): Int {
return ThreadLocalRandom.current().nextInt(0, 1000 + 1)
}
fun randomInt(min : Int = 0, max: Int): Int {
return ThreadLocalRandom.current().nextInt(min, max)
}
fun randomLong(): Long {
return randomInt().toLong()
}
fun randomFloat(): Float {
return randomInt().toFloat()
}
fun randomDouble(): Double {
return randomInt().toDouble()
}
fun randomBoolean(): Boolean {
return Math.random() < 0.5
}
fun makeStringList(count: Int): List<String> {
val items = mutableListOf<String>()
repeat(count) {
items.add(randomUuid())
}
return items
}
}

View file

@ -1,2 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.agileburo.anytype.feature_editor" />

View file

@ -1,78 +0,0 @@
{
"blocks": [
{
"id": "c0301f2b-b532-55e1-92ba-2b2fc4af237e",
"parentId": "",
"type": 3,
"contentType": 1,
"content": {
"text": "Первый",
"marks": []
},
"width": 0,
"children": []
},
{
"id": "fdresaf2b-b532-5345-92ba-2b2fc4af237e",
"parentId": "",
"type": 3,
"contentType": 1,
"content": {
"text": "Третий",
"marks": []
},
"width": 0,
"children": []
},
{
"id": "ytewty6q-b532-5345-92ba-2b2fc4af237e",
"parentId": "",
"type": 3,
"contentType": 1,
"content": {
"text": "Пятый",
"marks": []
},
"width": 0,
"children": []
},
{
"id": "6ytewty6q-b532-5345-92ba-2b2fc4af237e",
"parentId": "",
"type": 3,
"contentType": 1,
"content": {
"text": "Шестой",
"marks": []
},
"width": 0,
"children": []
},
{
"id": "7ytewty6q-b532-5345-92ba-2b2fc4af237e",
"parentId": "",
"type": 3,
"contentType": 1,
"content": {
"text": "Седьмой",
"marks": []
},
"width": 0,
"children": []
},
{
"id": "8ytewty6q-b532-5345-92ba-2b2fc4af237e",
"parentId": "",
"type": 3,
"contentType": 1,
"content": {
"text": "Восьмой",
"marks": []
},
"width": 0,
"children": []
}
]
}

View file

@ -1,42 +0,0 @@
{
"blocks": [
{
"id": "c0301f2b-b532-55e1-92ba-2b2fc4af237e",
"parentId": "",
"type": 3,
"contentType": 1,
"content": {
"text": "Первый",
"marks": []
},
"width": 0,
"children": []
},
{
"id": "fdresaf2b-b532-5345-92ba-2b2fc4af237e",
"parentId": "",
"type": 3,
"contentType": 1,
"content": {
"text": "Третий",
"marks": []
},
"width": 0,
"children": []
},
{
"id": "ytewty6q-b532-5345-92ba-2b2fc4af237e",
"parentId": "",
"type": 3,
"contentType": 11,
"content": {
"text": "Пятый",
"marks": []
},
"width": 0,
"children": []
}
]
}

View file

@ -1,365 +0,0 @@
{
"blocks": [
{
"id": "0",
"parentId": "",
"type": 3,
"contentType": 3,
"content": {"text":"Разработка AnyType","marks":[]},
"width": 0,
"children": []
},
{
"id":"1",
"parentId":"",
"type":6,
"contentType":0,
"content":{
"original":{
"key":"TACT7msSX8vkkFBhanRYT2wkA77hjfowo7WGyrrL9SEeEVEixy3LpDwp8j46",
"type":"image/jpeg",
"name":"1280px-Francesco_Salviati_005.jpg",
"hash":"QmXzbG8L8aeZ3VUnMJDJzVCKNTf1MMrGPdUsgsR2F3f3vu",
"size":311430,
"time":1558123043583
},
"size":{
"width":770,
"div":0.73
}
},
"width":0,
"children":[
]
},
{
"id":"2",
"parentId":"",
"type": 3,
"contentType": 9,
"content":{"text":"First toggle","marks":[]},
"width": 0,
"children":[
{
"id":"3",
"parentId":"2",
"type": 3,
"contentType": 1,
"content":{"text":"First toggle first paragraph","marks":[]},
"width": 0,
"children": []
},
{
"id":"4",
"parentId":"2",
"type": 3,
"contentType": 9,
"content":{"text":"Second toggle","marks":[]},
"width": 0,
"children": [
{
"id":"5",
"parentId":"4",
"type": 3,
"contentType": 1,
"content":{"text":"Second toggle first paragraph","marks":[]},
"width": 0,
"children": []
},
{
"id":"6",
"parentId":"4",
"type": 3,
"contentType": 1,
"content":{"text":"Second toggle second paragraph","marks":[]},
"width": 0,
"children": []
},
{
"id":"7",
"parentId":"4",
"type": 3,
"contentType": 9,
"content":{"text":"Third toggle","marks":[]},
"width": 0,
"children": [
{
"id":"8",
"parentId":"7",
"type": 3,
"contentType": 1,
"content":{"text":"Third toggle first paragraph","marks":[]},
"width": 0,
"children": []
},
{
"id":"9",
"parentId":"7",
"type": 3,
"contentType": 1,
"content":{"text":"Third toggle second paragraph","marks":[]},
"width": 0,
"children": []
}
]
}
]
}
]
},
{
"id": "10",
"parentId": "",
"type": 3,
"contentType": 1,
"content": {
"text": "Так говорила в июле 1805 года известная Анна Павловна Шерер, фрейлина и приближенная императрицы Марии Феодоровны, встречая важного и чиновного князя Василия, первого приехавшего на ее вечер. Анна Павловна кашляла несколько дней, у нее был грипп,как она говорила (грипп был тогда новое слово, употреблявшееся только редкими). В записочках, разосланных утрoом с красным лакеем, было написано без различия во всех:",
"marks": [
{
"type": "i",
"start": 240,
"end": 246
},
{
"type": "i",
"start": 264,
"end": 269
},
{
"type": "b",
"start": 340,
"end": 374
},
{
"type": "i",
"start": 150,
"end": 157
},
{
"type": "i",
"start": 40,
"end": 59
},
{
"type": "i",
"start": 97,
"end": 113
},
{
"type": "i",
"start": 192,
"end": 205
},
{
"type": "b",
"start": 167,
"end": 178
},
{
"type": "b",
"start": 125,
"end": 130
},
{
"type": "s",
"start": 254,
"end": 259
},
{
"type": "i",
"start": 397,
"end": 402
},
{
"type": "kbd",
"start": 328,
"end": 338
},
{
"type": "a",
"start": 316,
"end": 323,
"param": "https://yandex.ru/"
}
]
},
"width": 0,
"children": []
},
{
"id": "11",
"parentId": "",
"type": 3,
"contentType": 1,
"content": {
"text": "«Si vous n'avez rien de mieux à faire, Monsieur le comte (или mon prince), et si la perspective de passer la soirée chez une pauvre malade ne vous effraye pas trop, je serai charmée de vous voir chez moi entre 7 et 10 heures. Annette Scherer» ",
"marks": [
{
"type": "s",
"start": 0,
"end": 242
}
]
},
"width": 0,
"children": []
},
{
"id": "12",
"parentId": "",
"type": 3,
"contentType": 1,
"content": {
"text": "Dieu, quelle virulente sortie! — отвечал, нисколько не смутясь такою встречей, вошедший князь, в придворном, шитом мундире, в чулках, башмаках и звездах, с светлым выражением плоского лица.",
"marks": [
{
"type": "a",
"param": "https://ilibrary.ru/text/11/p.1/index.html#fn4",
"start": 0,
"end": 30
},
{
"type": "b",
"start": 33,
"end": 40
}
]
},
"width": 0,
"children": []
},
{
"id": "13",
"parentId": "",
"type": 3,
"contentType": 1,
"content": {
"text": "Жена узнала, что муж был в связи с бывшею в их доме француженкою-гувернанткой, и объявила мужу, что не может жить с ним в одном доме. ",
"marks": []
},
"children": []
},
{
"id": "14",
"parentId": "",
"type": 3,
"contentType": 10,
"content": {
"text": "На третий день после ссоры князь Степан Аркадьич Облонский — Стива, как его звали в свете, — в обычный час, то есть в восемь часов утра, проснулся не в спальне жены, а в своем кабинете, на сафьянном диване.",
"marks": []
},
"children": []
},
{
"id": "15",
"parentId": "",
"type": 3,
"contentType": 2,
"content": {
"text": "Он повернул свое полное, выхоленное тело на пружинах дивана, как бы желая опять заснуть надолго, с другой стороны крепко обнял подушку и прижался к ней щекой; но вдруг вскочил, сел на диван и открыл глаза.",
"marks": []
},
"children": []
},
{
"id": "16",
"parentId": "",
"type": 3,
"contentType": 1,
"content": {
"text": "«Да, да, как это было? — думал он, вспоминая сон.",
"marks": []
},
"children": []
},
{
"id": "17",
"parentId": "",
"type": 3,
"contentType": 1,
"content": {
"text": "Первый",
"marks": []
},
"width": 0,
"children": []
},
{
"id": "18",
"parentId": "",
"type": 3,
"contentType": 1,
"content": {
"text": "Третий",
"marks": []
},
"width": 0,
"children": []
},
{
"id": "19",
"parentId": "",
"type": 3,
"contentType": 11,
"content": {
"text": "Пятый",
"marks": []
},
"width": 0,
"children": []
},
{
"id": "20",
"parentId": "",
"type": 7,
"contentType": 0,
"content": {
"id": "12D3KooWRUkB3qUaxiz1VCGP3Uoy4jv8Nx7kCKdMSuhmXqwsjMJ6"
},
"width": 0,
"children": []
},
{
"id":"21",
"parentId":"",
"type":4,
"contentType":0,
"content":"",
"width":0,
"children":[
]
},
{
"id": "22",
"parentId": "",
"type": 9,
"contentType": 0,
"content": {
"bookMark": {
"type": "profile",
"url": "https://rateyourmusic.com/artist/the_tallest_man_on_earth",
"title": "The Tallest Man on Earth discography",
"description": "The Tallest Man on Earth discography and songs: Music profile for The Tallest Man on Earth, born 30 April 1983. Genres: Contemporary Folk, Singer/Songwriter, Indie Folk. Albums include The Wild Hunt, Shallow Grave, and There's No Leaving Now.",
"site": "RateYourMusic",
"icon": "",
"images": [
{
"url": "https://e.snmc.io/i/600/s/a9eec69d93bfd25142741293d34de3f8/4095112",
"secure": "",
"type": "",
"width": 0,
"height": 0
}
],
"audios": [],
"videos": []
}
},
"width": 0,
"children": []
}
]
}

View file

@ -1,161 +0,0 @@
{
"blocks": [
{
"id": "1cb83d95-7913-5c2e-99e8-1272110ab38f",
"parentId": "",
"type": 3,
"contentType": 1,
"content": {
"text": "Все счастливые семьи похожи друг на друга, каждая несчастливая семья несчастлива по-своему.",
"marks": []
},
"children": []
},
{
"id": "02ca4410-2cff-5978-81e5-09dc76dd003c",
"parentId": "",
"type": 3,
"contentType": 4,
"content": {
"text": "Все смешалось в доме Облонских.",
"marks": []
},
"children": []
},
{
"id": "116271cc-c6e3-5c58-8598-9342567b9a66",
"parentId": "",
"type": 3,
"contentType": 5,
"content": {
"text": "Жена узнала, что муж был в связи с бывшею в их доме француженкою-гувернанткой, и объявила мужу, что не может жить с ним в одном доме. ",
"marks": []
},
"children": []
},
{
"id": "567980cc-fght-wedr-5679-9342567b9a76",
"parentId": "",
"type": 3,
"contentType": 11,
"content": {
"text": "Положение это продолжалось уже третий день и мучительно чувствовалось и самими супругами, и всеми членами семьи, и домочадцами.",
"marks": []
},
"children": []
},
{
"id": "93f8810e-590f-5962-a662-2c1fe17e5cbe",
"parentId": "",
"type": 1,
"contentType": 0,
"content": {},
"children": [
{
"id": "22a8f9f5-b042-5532-bb5d-cbda07f579e0",
"parentId": "93f8810e-590f-5962-a662-2c1fe17e5cbe",
"type": 3,
"contentType": 8,
"content": {
"text": "Все члены семьи и домочадцы чувствовали, что нет смысла в их сожительстве и что на каждом постоялом дворе случайно сошедшиеся люди более связаны между собой, чем они, члены семьи и домочадцы Облонских.",
"marks": []
},
"children": [
{
"id": "1983",
"parentId": "92152a56-40d3-53ec-a1fa-ec4e4d92b0ec",
"type": 3,
"contentType": 7,
"content": {
"text": "Жена не выходила из своих комнат, мужа третий день не было дома.",
"marks": []
},
"children": []
}
]
},
{
"id": "92152a56-40d3-53ec-a1fa-ec4e4d92b0ec",
"parentId": "93f8810e-590f-5962-a662-2c1fe17e5cbe",
"type": 3,
"contentType": 6,
"content": {
"text": "Дети бегали по всему дому, как потерянные; англичанка поссорилась с экономкой и написала записку приятельнице, прося приискать ей новое место; повар ушел вчера со двора, во время самого обеда; черная кухарка и кучер просили расчета.",
"marks": [],
"number": 1
},
"children": []
}
]
},
{
"id": "21fe093a-5f74-583b-9c17-ee798e1bfc7e",
"parentId": "",
"type": 3,
"contentType": 10,
"content": {
"text": "На третий день после ссоры князь Степан Аркадьич Облонский — Стива, как его звали в свете, — в обычный час, то есть в восемь часов утра, проснулся не в спальне жены, а в своем кабинете, на сафьянном диване.",
"marks": []
},
"children": []
},
{
"id": "f0c40441-2a3f-5150-bad3-e0d43c72a997",
"parentId": "",
"type": 3,
"contentType": 2,
"content": {
"text": "Он повернул свое полное, выхоленное тело на пружинах дивана, как бы желая опять заснуть надолго, с другой стороны крепко обнял подушку и прижался к ней щекой; но вдруг вскочил, сел на диван и открыл глаза.",
"marks": []
},
"children": []
},
{
"id": "01f2a219-94ba-5bd6-a48e-5b9af61e1a10",
"parentId": "",
"type": 3,
"contentType": 1,
"content": {
"text": "«Да, да, как это было? — думал он, вспоминая сон.",
"marks": []
},
"children": []
},
{
"id": "c0301f2b-b532-55e1-92ba-2b2fc4af237e",
"parentId": "",
"type": 3,
"contentType": 1,
"content": {
"text": "Первый",
"marks": []
},
"width": 0,
"children": []
},
{
"id": "fdresaf2b-b532-5345-92ba-2b2fc4af237e",
"parentId": "",
"type": 3,
"contentType": 1,
"content": {
"text": "Третий",
"marks": []
},
"width": 0,
"children": []
},
{
"id": "ytewty6q-b532-5345-92ba-2b2fc4af237e",
"parentId": "",
"type": 3,
"contentType": 11,
"content": {
"text": "Пятый",
"marks": []
},
"width": 0,
"children": []
}
]
}

View file

@ -1,89 +0,0 @@
package com.agileburo.anytype.feature_editor
import android.content.Context
import com.agileburo.anytype.core_utils.di.scope.PerFeature
import com.agileburo.anytype.core_utils.ext.BaseSchedulerProvider
import com.agileburo.anytype.feature_editor.data.*
import com.agileburo.anytype.feature_editor.data.datasource.BlockDataSource
import com.agileburo.anytype.feature_editor.data.datasource.IPFSDataSourceImpl
import com.agileburo.anytype.feature_editor.data.parser.ContentModelParser
import com.agileburo.anytype.feature_editor.domain.EditorInteractor
import com.agileburo.anytype.feature_editor.domain.EditorInteractorImpl
import com.agileburo.anytype.feature_editor.presentation.converter.BlockContentTypeConverter
import com.agileburo.anytype.feature_editor.presentation.converter.BlockContentTypeConverterImpl
import com.agileburo.anytype.feature_editor.presentation.mvvm.EditorViewModelFactory
import com.agileburo.anytype.feature_editor.ui.EditorFragment
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import dagger.Module
import dagger.Provides
import dagger.Subcomponent
@PerFeature
@Subcomponent(modules = [EditorModule::class])
interface EditorComponent {
fun inject(fragment: EditorFragment)
}
@Module
@PerFeature
class EditorModule {
@Provides
@PerFeature
fun provideGson(): Gson = GsonBuilder().create()
@Provides
@PerFeature
fun provideBlockConverter(
contentConverter: ContentConverter,
contentModelParser : ContentModelParser
): BlockConverter =
BlockConverterImpl(contentConverter = contentConverter, contentModelParser = contentModelParser)
@Provides
@PerFeature
fun provideRepo(blockConverter: BlockConverter, dataSource: BlockDataSource): EditorRepo =
EditorRepoImpl(dataSource = dataSource, blockConverter = blockConverter)
@Provides
@PerFeature
fun provideInteractor(repo: EditorRepo): EditorInteractor = EditorInteractorImpl(repo = repo)
@Provides
@PerFeature
fun provideDataSource(context: Context, gson: Gson): BlockDataSource =
IPFSDataSourceImpl(context = context, gson = gson)
@Provides
@PerFeature
fun provideFactory(
interactor: EditorInteractor,
contentTypeConverter: BlockContentTypeConverter,
baseSchedulerProvider: BaseSchedulerProvider
): EditorViewModelFactory =
EditorViewModelFactory(interactor, contentTypeConverter, baseSchedulerProvider)
@Provides
@PerFeature
fun provideBlockContentConverter(markConverter: MarkConverter): ContentConverter =
ContentConverterImpl(markConverter = markConverter)
@Provides
@PerFeature
fun provideMarkConverter(): MarkConverter = MarkConverterImpl()
@Provides
@PerFeature
fun provideContentModelParser(gson : Gson) : ContentModelParser {
return ContentModelParser(gson)
}
@Provides
@PerFeature
fun provideContentTypeConverter(): BlockContentTypeConverter =
BlockContentTypeConverterImpl()
}

View file

@ -1,14 +0,0 @@
package com.agileburo.anytype.feature_editor
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable
/**
* Created by Konstantin Ivanov
* email : ki@agileburo.com
* on 21.03.2019.
*/
fun Disposable.disposedBy(subscriptions: CompositeDisposable) {
subscriptions.add(this)
}

View file

@ -1,198 +0,0 @@
package com.agileburo.anytype.feature_editor.data
import com.agileburo.anytype.feature_editor.data.converter.toBlockType
import com.agileburo.anytype.feature_editor.data.converter.toContentType
import com.agileburo.anytype.feature_editor.data.parser.ContentModelParser
import com.agileburo.anytype.feature_editor.domain.Block
import com.agileburo.anytype.feature_editor.domain.BlockType
import com.agileburo.anytype.feature_editor.domain.Content
import com.agileburo.anytype.feature_editor.domain.ContentType
/**
* Created by Konstantin Ivanov
* email : ki@agileburo.com
* on 20.03.2019.
*/
interface BlockConverter {
fun modelToDomain(model: BlockModel): Block
fun modelTreeToDomainTree(model: BlockModel): Block
}
class BlockConverterImpl(
private val contentConverter: ContentConverter,
private val contentModelParser: ContentModelParser
) : BlockConverter {
override fun modelTreeToDomainTree(model: BlockModel): Block {
val type = model.type.toBlockType()
when (type) {
is BlockType.Editable -> {
val parsed = contentModelParser.parse(model.content, type)
val content = contentConverter.modelToDomain(parsed as ContentModel.Text)
return Block(
id = model.id,
content = content,
parentId = model.parentId,
contentType = model.contentType.toContentType(),
blockType = model.type.toBlockType(),
children = model.children.map { modelTreeToDomainTree(it) }.toMutableList()
)
}
is BlockType.Page -> {
val parsed = contentModelParser.parse(model.content, type)
val content = contentConverter.modelToDomain(parsed as ContentModel.Page)
return Block(
id = model.id,
content = content,
parentId = model.parentId,
contentType = model.contentType.toContentType(),
blockType = model.type.toBlockType(),
children = model.children.map { modelTreeToDomainTree(it) }.toMutableList()
)
}
is BlockType.BookMark -> {
val parsed = contentModelParser.parse(model.content, type)
val content = contentConverter.modelToDomain(parsed as ContentModel.Bookmark)
return Block(
id = model.id,
content = content,
parentId = model.parentId,
contentType = model.contentType.toContentType(),
blockType = model.type.toBlockType(),
children = model.children.map { modelTreeToDomainTree(it) }.toMutableList()
)
}
is BlockType.Divider -> {
return Block(
id = model.id,
parentId = model.parentId,
contentType = ContentType.None,
content = Content.Empty,
blockType = BlockType.Divider,
children = model.children.map { modelTreeToDomainTree(it) }.toMutableList()
)
}
is BlockType.Image -> {
val parsed = contentModelParser.parse(model.content, type) as ContentModel.Image
val content = contentConverter.modelToDomain(parsed)
return Block(
id = model.id,
parentId = model.parentId,
content = content,
contentType = ContentType.None,
blockType = BlockType.Image,
children = model.children.map { modelTreeToDomainTree(it) }.toMutableList()
)
}
else -> TODO()
}
}
override fun modelToDomain(model: BlockModel): Block {
val type = model.type.toBlockType()
when (type) {
is BlockType.Editable -> {
val parsed = contentModelParser.parse(model.content, type)
val content = contentConverter.modelToDomain(parsed as ContentModel.Text)
return Block(
id = model.id,
content = content,
parentId = model.parentId,
contentType = model.contentType.toContentType(),
blockType = model.type.toBlockType()
)
}
is BlockType.Page -> {
val parsed = contentModelParser.parse(model.content, type)
val content = contentConverter.modelToDomain(parsed as ContentModel.Page)
return Block(
id = model.id,
content = content,
parentId = model.parentId,
contentType = model.contentType.toContentType(),
blockType = model.type.toBlockType()
)
}
is BlockType.BookMark -> {
val parsed = contentModelParser.parse(model.content, type)
val content = contentConverter.modelToDomain(parsed as ContentModel.Bookmark)
return Block(
id = model.id,
content = content,
parentId = model.parentId,
contentType = model.contentType.toContentType(),
blockType = model.type.toBlockType()
)
}
is BlockType.Divider -> {
return Block(
id = model.id,
parentId = model.parentId,
contentType = ContentType.None,
content = Content.Empty,
blockType = BlockType.Divider
)
}
is BlockType.Image -> {
val parsed = contentModelParser.parse(model.content, type) as ContentModel.Image
val content = contentConverter.modelToDomain(parsed)
return Block(
id = model.id,
parentId = model.parentId,
content = content,
contentType = ContentType.None,
blockType = BlockType.Image
)
}
else -> TODO()
}
}
}

View file

@ -1,17 +0,0 @@
package com.agileburo.anytype.feature_editor.data
import com.google.gson.JsonElement
/**
* Created by Konstantin Ivanov
* email : ki@agileburo.com
* on 20.03.2019.
*/
data class BlockModel(
val id: String,
val parentId: String = "",
val content: JsonElement,
val contentType: Int,
val type: Int,
val children: List<BlockModel>
)

View file

@ -1,65 +0,0 @@
package com.agileburo.anytype.feature_editor.data
import com.agileburo.anytype.feature_editor.domain.Content
import com.agileburo.anytype.feature_editor.domain.ContentParam
/**
* Created by Konstantin Ivanov
* email : ki@agileburo.com
* on 25.03.2019.
*/
interface ContentConverter {
fun modelToDomain(model: ContentModel.Text): Content.Text
fun domainToModel(domain: Content.Text): ContentModel.Text
fun modelToDomain(model : ContentModel.Page) : Content.Page
fun modelToDomain(model : ContentModel.Bookmark) : Content.Bookmark
fun modelToDomain(model : ContentModel.Image) : Content.Picture
}
class ContentConverterImpl(private val markConverter: MarkConverter) : ContentConverter {
override fun modelToDomain(model: ContentModel.Text) =
Content.Text(
text = model.text,
marks = model.marks.map { markConverter.modelToDomain(it) },
param = ContentParam(
mutableMapOf(
"number" to (model.number ?: 0),
"checked" to (model.checked ?: false)
)
)
)
//TODO add marks convert!
override fun domainToModel(domain: Content.Text) =
ContentModel.Text(
text = domain.text.toString(),
marks = domain.marks.map { markConverter.domainToModel(it) },
number = domain.param.number,
checked = domain.param.checked
)
override fun modelToDomain(model: ContentModel.Page): Content.Page {
return Content.Page(model.id)
}
override fun modelToDomain(model: ContentModel.Bookmark): Content.Bookmark {
return Content.Bookmark(
type = model.bookMark.type,
description = model.bookMark.description,
title = model.bookMark.title,
url = model.bookMark.url,
site = model.bookMark.site,
icon = model.bookMark.icon,
images = model.bookMark.images.map { image -> Content.Bookmark.Image(image.url) }
)
}
override fun modelToDomain(model: ContentModel.Image): Content.Picture {
// TODO remove hard-coded
return Content.Picture(
url = "https://c.wallhere.com/photos/6c/31/vintage_typewriters_books_glasses_wood_paper_pine_cones-727921.jpg!d",
type = Content.Picture.Type.ORIGINAL
)
}
}

View file

@ -1,49 +0,0 @@
package com.agileburo.anytype.feature_editor.data
/**
* Created by Konstantin Ivanov
* email : ki@agileburo.com
* on 25.03.2019.
*/
sealed class ContentModel {
data class Text(
val text: String = "",
val marks: List<MarkModel> = emptyList(),
val number : Int? = null,
val checked : Boolean? = null
) : ContentModel()
data class Page(
val id : String
) : ContentModel()
data class Bookmark(
val bookMark : BookmarkModel
) : ContentModel()
data class BookmarkModel(
val type : String,
val url : String,
val title : String,
val description : String,
val site : String,
val icon : String,
val images : List<ImageModel>
)
data class Image(
val original : OriginalImageModel
) : ContentModel()
data class OriginalImageModel(
val key : String,
val type : String,
val name : String,
val size : Int,
val time : Long
)
data class ImageModel(val url : String)
}

View file

@ -1,44 +0,0 @@
package com.agileburo.anytype.feature_editor.data
import com.agileburo.anytype.feature_editor.data.datasource.BlockDataSource
import com.agileburo.anytype.feature_editor.domain.Block
import io.reactivex.Single
import javax.inject.Inject
interface EditorRepo {
fun getBlocks(): Single<List<Block>>
fun saveState(list: List<Block>)
}
class EditorRepoImpl @Inject constructor(
private val dataSource: BlockDataSource,
private val blockConverter: BlockConverter
) : EditorRepo {
override fun getBlocks(): Single<List<Block>> {
return dataSource.getBlocks().map { it.map(blockConverter::modelTreeToDomainTree) }
}
override fun saveState(list: List<Block>) {
wrap(list)
}
// TODO перевести в отдельный маппер, пусть репозиторий отвечает толька за CRUD-операции
private fun unwrap(blocks: List<BlockModel>): List<Block> {
val result = mutableListOf<Block>()
if (blocks.isEmpty()) return result
blocks.forEach {
result.add(blockConverter.modelToDomain(it))
if (it.children.isNotEmpty()) {
result.addAll(unwrap(it.children))
}
}
return result
}
//TODO предполагаем, что понадобится перевод листа блоков в дерево для отдачи беку
private fun wrap(list: List<Block>): List<BlockModel> {
return emptyList()
}
}

View file

@ -1,38 +0,0 @@
package com.agileburo.anytype.feature_editor.data
import android.net.MacAddress
import com.agileburo.anytype.feature_editor.domain.Mark
/**
* Created by Konstantin Ivanov
* email : ki@agileburo.com
* on 03.04.2019.
*/
interface MarkConverter {
fun modelToDomain(model: MarkModel): Mark
fun domainToModel(domain: Mark): MarkModel
}
class MarkConverterImpl : MarkConverter {
override fun modelToDomain(model: MarkModel): Mark =
Mark(type = getType(model.type),
start = model.start,
end = model.end,
param = model.param)
override fun domainToModel(domain: Mark): MarkModel {
throw UnsupportedOperationException("not implemented")
}
private fun getType(type: String) =
when (type) {
"b" -> Mark.MarkType.BOLD
"i" -> Mark.MarkType.ITALIC
"s" -> Mark.MarkType.STRIKE_THROUGH
"kbd" -> Mark.MarkType.CODE
"a" -> Mark.MarkType.HYPERTEXT
else -> Mark.MarkType.UNDEFINED
}
}

View file

@ -1,11 +0,0 @@
package com.agileburo.anytype.feature_editor.data
/**
* Created by Konstantin Ivanov
* email : ki@agileburo.com
* on 03.04.2019.
*/
data class MarkModel(val type: String = "",
val start: Int = 0,
val end: Int = 0,
val param: String = "")

View file

@ -1,33 +0,0 @@
package com.agileburo.anytype.feature_editor.data.converter
import com.agileburo.anytype.feature_editor.domain.BlockType
import com.agileburo.anytype.feature_editor.domain.BlockTypes
fun Int.toBlockType(): BlockType =
when (this) {
BlockTypes.HORIZONTAL_GRID -> BlockType.HrGrid
BlockTypes.VERTICAL_GRID -> BlockType.VrGrid
BlockTypes.EDITABLE -> BlockType.Editable
BlockTypes.DIVIDER -> BlockType.Divider
BlockTypes.VIDEO -> BlockType.Video
BlockTypes.IMAGE -> BlockType.Image
BlockTypes.PAGE -> BlockType.Page
BlockTypes.NEW_PAGE -> BlockType.NewPage
BlockTypes.BOOKMARK -> BlockType.BookMark
BlockTypes.FILE -> BlockType.File
else -> throw IllegalStateException("Unexpected block type code: $this")
}
fun BlockType.toNumericalCode() : Int =
when(this) {
BlockType.HrGrid -> BlockTypes.HORIZONTAL_GRID
BlockType.VrGrid -> BlockTypes.VERTICAL_GRID
BlockType.Editable -> BlockTypes.EDITABLE
BlockType.Divider -> BlockTypes.DIVIDER
BlockType.Video -> BlockTypes.VIDEO
BlockType.Image -> BlockTypes.IMAGE
BlockType.Page -> BlockTypes.PAGE
BlockType.NewPage -> BlockTypes.NEW_PAGE
BlockType.BookMark -> BlockTypes.BOOKMARK
BlockType.File -> BlockTypes.FILE
}

View file

@ -1,37 +0,0 @@
package com.agileburo.anytype.feature_editor.data.converter
import com.agileburo.anytype.feature_editor.domain.ContentType
import com.agileburo.anytype.feature_editor.domain.ContentTypes
fun Int.toContentType(): ContentType =
when (this) {
ContentTypes.UNDEFINED -> ContentType.None
ContentTypes.PARAGRAPH -> ContentType.P
ContentTypes.CODE_SNIPPET -> ContentType.Code
ContentTypes.HEADER_ONE -> ContentType.H1
ContentTypes.HEADER_TWO -> ContentType.H2
ContentTypes.HEADER_THREE -> ContentType.H3
ContentTypes.NUMBERED_LIST -> ContentType.NumberedList
ContentTypes.BULLET_LIST_ITEM -> ContentType.UL
ContentTypes.QUOTE -> ContentType.Quote
ContentTypes.TOGGLE -> ContentType.Toggle
ContentTypes.CHECKBOX -> ContentType.Check
ContentTypes.HEADER_FOUR -> ContentType.H4
else -> throw IllegalStateException("Unexpected content type code: $this")
}
fun ContentType.toNumericalCode(): Int =
when (this) {
ContentType.None -> ContentTypes.UNDEFINED
ContentType.P -> ContentTypes.PARAGRAPH
ContentType.Code -> ContentTypes.CODE_SNIPPET
ContentType.H1 -> ContentTypes.HEADER_ONE
ContentType.H2 -> ContentTypes.HEADER_TWO
ContentType.H3 -> ContentTypes.HEADER_THREE
ContentType.NumberedList -> ContentTypes.NUMBERED_LIST
ContentType.UL -> ContentTypes.BULLET_LIST_ITEM
ContentType.Quote -> ContentTypes.QUOTE
ContentType.Toggle -> ContentTypes.TOGGLE
ContentType.Check -> ContentTypes.CHECKBOX
ContentType.H4 -> ContentTypes.HEADER_FOUR
}

View file

@ -1,42 +0,0 @@
package com.agileburo.anytype.feature_editor.data.datasource
import android.content.Context
import com.agileburo.anytype.feature_editor.BuildConfig
import com.agileburo.anytype.feature_editor.data.BlockModel
import com.google.gson.Gson
import io.reactivex.Single
import javax.inject.Inject
/**
* Created by Konstantin Ivanov
* email : ki@agileburo.com
* on 20.03.2019.
*/
interface BlockDataSource {
fun getBlocks(): Single<List<BlockModel>>
}
class IPFSDataSourceImpl @Inject constructor(
private val context: Context,
private val gson: Gson
) : BlockDataSource {
override fun getBlocks(): Single<List<BlockModel>> {
return Single.create<List<BlockModel>> { emitter ->
try {
val json = context.assets.open(BuildConfig.TEST_JSON).bufferedReader().use {
it.readText()
}
val ipfsResponse = gson.fromJson<IpfsResponse>(json, IpfsResponse::class.java)
emitter.onSuccess(ipfsResponse.blocks)
} catch (e: Exception) {
emitter.onError(e)
}
}
}
}
data class IpfsResponse(val blocks: List<BlockModel> = emptyList())

View file

@ -1,28 +0,0 @@
package com.agileburo.anytype.feature_editor.data.parser
import com.agileburo.anytype.feature_editor.data.ContentModel
import com.agileburo.anytype.feature_editor.domain.BlockType
import com.google.gson.Gson
import com.google.gson.JsonElement
class ContentModelParser(val gson : Gson) {
fun parse(json : JsonElement, blockType : BlockType) : ContentModel {
return when(blockType) {
BlockType.Editable -> {
gson.fromJson(json, ContentModel.Text::class.java)
}
BlockType.Page -> {
gson.fromJson(json, ContentModel.Page::class.java)
}
BlockType.BookMark -> {
gson.fromJson(json, ContentModel.Bookmark::class.java)
}
BlockType.Image -> {
gson.fromJson(json, ContentModel.Image::class.java)
}
else -> TODO()
}
}
}

View file

@ -1,496 +0,0 @@
package com.agileburo.anytype.feature_editor.domain
import com.agileburo.anytype.feature_editor.presentation.mapper.BlockViewMapper
import com.agileburo.anytype.feature_editor.presentation.model.BlockView
import java.util.*
/**
* Created by Konstantin Ivanov
* email : ki@agileburo.com
* on 14.03.2019.
*/
sealed class BlockType {
object HrGrid : BlockType()
object VrGrid : BlockType()
object Editable : BlockType()
object Divider : BlockType()
object Video : BlockType()
object Image : BlockType()
object Page : BlockType()
object NewPage : BlockType()
object BookMark : BlockType()
object File : BlockType()
}
sealed class ContentType {
object None : ContentType()
object P : ContentType()
object Code : ContentType()
object H1 : ContentType()
object H2 : ContentType()
object H3 : ContentType()
object NumberedList : ContentType()
object UL : ContentType()
object Quote : ContentType()
object Toggle : ContentType()
object Check : ContentType()
object H4 : ContentType()
}
typealias Document = MutableList<Block>
data class Block(
val id: String,
val parentId: String,
val contentType: ContentType,
val blockType: BlockType,
val content: Content,
val children : MutableList<Block> = mutableListOf(),
val state : State = State.expanded(false)
) {
fun isNumberedList() = contentType == ContentType.NumberedList
fun setNumber(number: Int) {
if (content is Content.Text) content.param.number = number
}
fun isConsumer(): Boolean = when (contentType) {
ContentType.Toggle, ContentType.Check, ContentType.NumberedList, ContentType.UL -> true
else -> false
}
fun hasParent() = parentId.isNotEmpty()
fun isList() : Boolean {
return when(contentType) {
ContentType.NumberedList, ContentType.UL, ContentType.Check -> true
else -> false
}
}
fun isCheckbox() = contentType == ContentType.Check
fun isToggle() = contentType == ContentType.Toggle
data class State(val map : MutableMap<String, Any> = mutableMapOf()) {
var expanded : Boolean by map
var focused : Boolean by map
companion object {
fun expanded(expanded : Boolean = false) = State(mutableMapOf("expanded" to expanded, "focused" to false))
fun focused(focused : Boolean = false) = State(mutableMapOf("focused" to focused))
}
}
companion object {
fun new(parentId: String, contentType : ContentType) : Block {
return Block(
id = UUID.randomUUID().toString(),
parentId = parentId,
content = Content.Text(
marks = emptyList(),
text = "",
param = ContentParam.empty()
),
blockType = BlockType.Editable,
contentType = contentType,
state = State.focused(true)
)
}
}
}
/**
* Flattens document, so that as result we get a flattened list.
* @return list, in which all blocks do not have children (parent-child relations are indicated only via parentId)
*/
fun Document.flat() : List<Block> {
val result = mutableListOf<Block>()
forEach { block ->
result.add(block.copy(children = mutableListOf()))
if (block.children.isNotEmpty()) result.addAll(block.children.flat())
}
return result
}
/**
* This method allows to transform a flattened list of block into a graph-like structured document.
*/
fun Document.graph() : Document {
val map = this.associateBy { block -> block.id }.toMutableMap()
forEach { block ->
if (block.parentId.isNotEmpty()) {
map[block.parentId]?.let { parent ->
parent.children.add(block)
map[block.parentId] = parent
map[block.id] = block
}
}
}
return map.values.filter { block -> block.parentId.isEmpty() }.toMutableList()
}
/**
* Search inside a flattened block list.
*/
fun Document.flatSearch(id : String) : Block? {
return flat().find { block -> block.id == id }?.copy()
}
/**
* Search inside a graph-like structured document.
* @param id id of the block to search
* @return returns a copy instance of the block, or null if it is not present inside the document
*/
fun Document.search(id : String) : Block? {
forEach { block ->
if (block.id == id) return block.copy()
if (block.children.isNotEmpty()) {
val result = block.children.search(id)
if (result != null) return result.copy()
}
}
return null
}
/**
* @param targetId id of the block to update.
* @param targetType new content type for the block that is being updated
*/
@Throws(IllegalStateException::class)
fun Document.changeContentType(targetId : String, targetType: ContentType) {
search(targetId)?.let { target ->
if (target.contentType == targetType) return
if (target.parentId.isNotEmpty()) {
// Changing content type at children level
search(target.parentId)?.let { parent ->
val index = parent.children.indexOf(target)
parent.children[index] = target.copy(contentType = targetType)
// treat toggle block children move.
if (target.contentType == ContentType.Toggle) {
// move toggle block children to an upper level
if (target.children.isNotEmpty()) {
val children = target.children.map { it.copy(parentId = parent.id) }
// remove children from toggle block
target.children.clear()
// add children to their new parent (i.e. parent of the target block)
if (index < parent.children.size - 1) {
val slice = slice(index + 1 until parent.children.size)
parent.children.removeAll { child -> slice.contains(child) }
parent.children.addAll(children + slice)
} else {
parent.children.addAll(children)
}
}
}
} ?: throw IllegalStateException("Could not found parent by id : ${target.parentId}")
} else {
// Changing content type at root level
val index = indexOf(target)
set(index, target.copy(contentType = targetType))
// treat toggle block children move.
if (target.contentType == ContentType.Toggle) {
// move toggle block children to root level
if (target.children.isNotEmpty()) {
val children = target.children.map { it.copy(parentId = "") }
target.children.clear()
// add children to root revel
if (index < size - 1) {
val slice = slice(index + 1 until size)
removeAll { slice.contains(it) }
addAll(children + slice)
} else {
addAll(children)
}
}
}
}
} ?: throw IllegalStateException("Could not find target by id: $targetId")
}
/**
* Deletes block by id.
* @param targetId id of the block to delete
*/
fun Document.delete(targetId : String) {
val block = search(targetId)
check(block != null) { "Could not found block with id : $targetId" }
if (block.parentId.isNotEmpty()) {
val parent = search(block.parentId)
check(parent != null) { "Could not found parent by id: ${block.parentId}" }
parent.children.removeIf { it.id == block.id }
} else {
removeIf { it.id == block.id }
}
}
/**
* @param targetId id of the block to update.
* @param targetContentUpdate new content for the block that is being updated.
*/
fun Document.updateContent(targetId : String, targetContentUpdate : Content) {
val current = search(targetId)
if (current != null) {
if (current.parentId.isNotEmpty()) {
val parent = search(current.parentId)
if (parent != null) {
val index = parent.children.indexOf(current)
parent.children[index] = current.copy(content = targetContentUpdate)
}
} else {
val index = indexOfFirst { block -> block.id == targetId }
set(index, current.copy(content = targetContentUpdate))
}
}
}
/**
* Fixes number order inside document.
*/
fun Document.fixNumberOrder() {
var number = 0
forEach { block ->
if (block.isNumberedList()) {
number++
block.setNumber(number)
} else {
number = 0
}
if (block.children.isNotEmpty()) {
block.children.fixNumberOrder()
}
}
}
/**
* @param indent current indent (starting with 0)
* @return a document representation adapted for rendering.
*
*/
fun Document.toView(indent : Int = 0) : List<BlockView> {
val mapper = BlockViewMapper()
val result = mutableListOf<BlockView>()
forEach { block ->
result.add(mapper.mapToView(model = block, indent = indent))
if (block.isToggle()) {
if (block.children.isNotEmpty() && block.state.expanded) {
result.addAll(block.children.toView(indent.inc()))
}
} else if (block.isList()) {
if (block.children.isNotEmpty()) {
result.addAll(block.children.toView(indent.inc()))
}
} else {
if (block.children.isNotEmpty()) {
result.addAll(block.children.toView(indent))
}
}
}
return result
}
@Throws(IllegalStateException::class)
fun Document.consume(consumerId : String, consumableId : String) {
search(consumerId)?.let { consumer ->
if (consumer.isConsumer())
search(consumableId)?.let { consumable ->
val parent = search(consumable.parentId)
if (parent != null) {
val index = parent.children.indexOf(consumable)
parent.children.removeAt(index)
consumer.children.add(consumable.copy(parentId = consumer.id))
} else {
val index = indexOf(consumable)
removeAt(index)
consumer.children.add(consumable.copy(parentId = consumer.id))
}
} ?: throw IllegalStateException("Could not find consumable with id: $consumableId")
} ?: throw IllegalStateException("Could not find consumer with id: $consumerId")
}
fun Document.moveAfter(previousId : String, targetId : String) {
search(previousId)?.let { previous ->
search(targetId)?.let { target ->
if (previous.hasParent()) {
search(previous.parentId)?.let { parent ->
if (target.hasParent() && target.parentId != parent.id) {
search(target.parentId)?.let { targetParent ->
targetParent.children.removeIf { child -> child.id == targetId }
} ?: throw IllegalStateException("Could not found parent for target id: $targetId")
} else {
removeIf { block -> block.id == target.id }
}
val result = mutableListOf<Block>()
parent.children.forEach { child ->
if (child.id != targetId)
result.add(child)
if (child.id == previousId)
result.add(target.copy(parentId = parent.id))
}
parent.children.apply {
clear()
addAll(result)
}
} ?: throw IllegalStateException("Could not find parent for previous block by parent id: ${previous.parentId}")
} else {
if (target.hasParent()) {
if (target.parentId != previous.parentId) {
search(target.parentId)?.let { targetParent ->
targetParent.children.removeIf { child -> child.id == targetId }
} ?: throw IllegalStateException("Could not found parent for target id: $targetId")
}
val result = mutableListOf<Block>()
forEach { child ->
result.add(child)
if (child.id == previousId)
result.add(target.copy(parentId = previous.parentId))
}
clear()
addAll(result)
} else {
val result = mutableListOf<Block>()
forEach { child ->
if (child.id != targetId)
result.add(child)
if (child.id == previousId)
result.add(target.copy(parentId = previous.parentId))
}
clear()
addAll(result)
}
}
} ?: throw IllegalStateException("Could not find target block with id: $targetId")
} ?: throw IllegalStateException("Could not find previous block with id: $previousId")
}
/**
* Inserts a new block after given block according to block hierarchy.
* @param previousBlockId id of the previous block (new block is inserted after this block)
*/
fun Document.insertNewBlockAfter(previousBlockId : String) {
search(previousBlockId)?.let { previous ->
val newContentType = if (previous.isCheckbox() || previous.isList()) previous.contentType else ContentType.P
val newBlock = Block.new(
parentId = previous.parentId,
contentType = newContentType
)
if (previous.parentId.isNotEmpty()) {
search(previous.parentId)?.let { parent ->
val result = mutableListOf<Block>()
parent.children.forEach { block ->
result.add(block)
if (block.id == previousBlockId) result.add(newBlock)
}
parent.children.apply {
clear()
addAll(result)
}
} ?: throw IllegalStateException("Could not found parent for previous item with parent id: ${previous.parentId}")
} else {
val result = mutableListOf<Block>()
forEach { block ->
result.add(block)
if (block.id == previousBlockId) result.add(newBlock)
}
this.clear()
this.addAll(result)
}
} ?: throw IllegalStateException("Could not found previous block with id: $previousBlockId")
}
/**
* Applies given action on every block of this document.
* Only a variable field of a block can be modified by this method.
* @param action action on block instance.
*/
fun Document.applyToAll(action : (Block) -> Unit) {
forEach { block ->
action(block)
block.children.applyToAll(action)
}
}

View file

@ -1,14 +0,0 @@
package com.agileburo.anytype.feature_editor.domain
object BlockTypes {
const val HORIZONTAL_GRID = 1
const val VERTICAL_GRID = 2
const val EDITABLE = 3
const val DIVIDER = 4
const val VIDEO = 5
const val IMAGE = 6
const val PAGE = 7
const val NEW_PAGE = 8
const val BOOKMARK = 9
const val FILE = 10
}

View file

@ -1,87 +0,0 @@
package com.agileburo.anytype.feature_editor.domain
sealed class Content {
data class Text(
val text : String,
val param : ContentParam,
val marks : List<Mark>
) : Content()
data class Page(
val id : String
) : Content()
data class Bookmark(
val type : String,
val url : String,
val title : String,
val description : String,
val site : String,
val icon : String,
val images : List<Image>
) : Content() {
data class Image(val url : String)
}
@Deprecated("Picture will be downloaded from device or in some other way")
data class Picture(
val url : String,
val type : Type
) : Content() {
enum class Type { ORIGINAL, THUMBNAIL }
}
object Empty : Content()
}
data class ContentParam(val map : MutableMap<String, Any?>) {
var number : Int by map
var checked : Boolean by map
companion object {
fun checkbox(checked : Boolean) : ContentParam {
return ContentParam(
mutableMapOf(
"number" to 0,
"checked" to checked
)
)
}
fun empty() : ContentParam {
return ContentParam(
mutableMapOf(
"number" to 0,
"checked" to false
)
)
}
fun numberedList(number : Int = 1): ContentParam {
return ContentParam(
mutableMapOf(
"number" to number,
"checked" to false
)
)
}
}
}
data class Mark(
val start : Int,
val end : Int,
val type : MarkType,
val param: String
) {
enum class MarkType {
BOLD, ITALIC, UNDERLINE, STRIKE_THROUGH, HYPERTEXT, CODE, UNDEFINED
}
}

View file

@ -1,16 +0,0 @@
package com.agileburo.anytype.feature_editor.domain
object ContentTypes {
const val UNDEFINED = 0
const val PARAGRAPH = 1
const val CODE_SNIPPET = 2
const val HEADER_ONE = 3
const val HEADER_TWO = 4
const val HEADER_THREE = 5
const val NUMBERED_LIST = 6
const val BULLET_LIST_ITEM = 7
const val QUOTE = 8
const val TOGGLE = 9
const val CHECKBOX = 10
const val HEADER_FOUR = 11
}

View file

@ -1,24 +0,0 @@
package com.agileburo.anytype.feature_editor.domain
import com.agileburo.anytype.feature_editor.data.EditorRepo
import io.reactivex.Observable
import io.reactivex.Single
import javax.inject.Inject
interface EditorInteractor {
fun getBlocks(): Single<List<Block>>
fun saveState(list: MutableList<Block>)
}
class EditorInteractorImpl @Inject constructor(private val repo: EditorRepo) : EditorInteractor {
override fun getBlocks(): Single<List<Block>> =
repo.getBlocks()
.flattenAsObservable { blocks -> blocks }
.toList()
override fun saveState(list: MutableList<Block>) {
repo.saveState(list)
}
}

View file

@ -1,140 +0,0 @@
package com.agileburo.anytype.feature_editor.presentation.converter
import com.agileburo.anytype.feature_editor.domain.Block
import com.agileburo.anytype.feature_editor.domain.Content
import com.agileburo.anytype.feature_editor.domain.ContentParam
import com.agileburo.anytype.feature_editor.domain.ContentType
interface BlockContentTypeConverter {
/**
* @param block block to convert
* @param type content type for new block
*/
fun convert(block : Block, type : ContentType) : Block
/**
* @param blocks list of blocks
* @param target block that we need to convert
* @param targetType type for a new block
*/
fun convert(blocks : List<Block>, target : Block, targetType : ContentType) : List<Block>
fun normalizeNumbers(blocks : List<Block>) : List<Block>
fun getPermittedTypes(typeInitial: ContentType): Set<ContentType>
fun getForbiddenTypes(typeInitial: ContentType): Set<ContentType>
}
class BlockContentTypeConverterImpl :
BlockContentTypeConverter {
override
fun getPermittedTypes(typeInitial: ContentType): Set<ContentType> =
setOf(
ContentType.P, ContentType.Code, ContentType.H1, ContentType.H2,
ContentType.H3, ContentType.NumberedList, ContentType.UL, ContentType.Quote,
ContentType.Toggle, ContentType.Check, ContentType.H4, ContentType.None
)
//Если вдруг появятся недопустимые варианты для конвертации, добавлять можно здесь
override fun getForbiddenTypes(typeInitial: ContentType): Set<ContentType> =
when (typeInitial) {
//Выключаем H1 при работе с блоком, используем H2, H2, H4
ContentType.P -> setOf(ContentType.H1)
else -> setOf(ContentType.H1)
}
override fun convert(block: Block, type: ContentType): Block {
return when (type) {
ContentType.NumberedList -> {
block.copy(
contentType = type,
content = (block.content as Content.Text).copy(param = ContentParam.numberedList())
)
}
else -> {
block.copy(contentType = type)
}
}
}
override fun convert(blocks: List<Block>, target: Block, targetType: ContentType): List<Block> {
if (target.contentType == targetType)
return blocks
else
when (targetType) {
ContentType.NumberedList -> {
val result = mutableListOf<Block>()
blocks.forEach { block ->
if (block.id == target.id) {
val item = block.copy(
contentType = targetType,
content = (block.content as Content.Text).copy(
param = ContentParam.numberedList()
)
)
result.add(item)
} else {
result.add(block)
}
}
return normalizeNumbers(result)
}
else -> {
val result = blocks.toMutableList().also { result ->
val index = blocks.indexOf(target)
val converted = target.copy(contentType = targetType)
result[index] = converted
}
return normalizeNumbers(result)
}
}
}
override fun normalizeNumbers(blocks: List<Block>): List<Block> {
if (blocks.isEmpty())
return emptyList()
val result = mutableListOf<Block>()
var number = 0
var isPreviousNumbered = false
blocks.forEach { block ->
if (block.contentType == ContentType.NumberedList) {
if (isPreviousNumbered) {
number++
block.setNumber(number)
} else {
number = 1
block.setNumber(number)
}
isPreviousNumbered = true
} else {
block.setNumber(0)
isPreviousNumbered = false
}
result.add(block)
}
return result
}
}

View file

@ -1,184 +0,0 @@
package com.agileburo.anytype.feature_editor.presentation.mapper
import android.graphics.Typeface
import android.text.SpannableString
import android.text.style.StrikethroughSpan
import android.text.style.StyleSpan
import android.text.style.URLSpan
import com.agileburo.anytype.feature_editor.domain.*
import com.agileburo.anytype.feature_editor.presentation.model.BlockView
import com.agileburo.anytype.feature_editor.presentation.model.BlockView.HeaderView.*
/**
* Created by Konstantin Ivanov
* email : ki@agileburo.com
* on 2019-05-15.
*/
interface ModelMapper<in V, out D> {
fun mapToModel(view: V): D
}
class BlockModelMapper : ModelMapper<BlockView, Block> {
override fun mapToModel(view: BlockView): Block {
//todo Разобраться с parentId
return when (view) {
is BlockView.ParagraphView -> {
Block(
id = view.id,
parentId = "test",
contentType = ContentType.P,
content = Content.Text(
text = view.text.toString(),
marks = fromSpannableToMarks(view.text),
param = ContentParam.empty()
),
blockType = BlockType.Editable
)
}
is BlockView.QuoteView -> {
Block(
id = view.id,
parentId = "",
contentType = ContentType.Quote,
content = Content.Text(
text = view.text.toString(),
marks = fromSpannableToMarks(view.text),
param = ContentParam.empty()
),
blockType = BlockType.Editable
)
}
is BlockView.CodeSnippetView -> {
Block(
id = view.id,
parentId = "",
contentType = ContentType.Code,
content = Content.Text(
text = view.text.toString(),
marks = fromSpannableToMarks(view.text),
param = ContentParam.empty()
),
blockType = BlockType.Editable
)
}
is BlockView.CheckboxView -> {
Block(
id = view.id,
parentId = "",
contentType = ContentType.Check,
content = Content.Text(
text = view.text.toString(),
marks = fromSpannableToMarks(view.text),
param = ContentParam.checkbox(view.isChecked)
),
blockType = BlockType.Editable
)
}
is BlockView.HeaderView -> {
val contentType = when(view.type) {
HeaderType.ONE -> ContentType.H1
HeaderType.TWO -> ContentType.H2
HeaderType.THREE -> ContentType.H3
HeaderType.FOUR -> ContentType.H4
}
Block(
id = view.id,
parentId = "",
contentType = contentType,
content = Content.Text(
text = view.text.toString(),
marks = fromSpannableToMarks(view.text),
param = ContentParam.empty()
),
blockType = BlockType.Editable
)
}
is BlockView.BulletView -> {
Block(
id = view.id,
parentId = "",
contentType = ContentType.UL,
content = Content.Text(
text = view.text.toString(),
marks = fromSpannableToMarks(view.text),
param = ContentParam.empty()
),
blockType = BlockType.Editable
)
}
is BlockView.NumberListItemView -> {
Block(
id = view.id,
parentId = "",
contentType = ContentType.NumberedList,
content = Content.Text(
text = view.text.toString(),
marks = fromSpannableToMarks(view.text),
param = ContentParam.numberedList(view.number)
),
blockType = BlockType.Editable
)
}
is BlockView.ToggleView -> {
Block(
id = view.id,
parentId = "lost",
contentType = ContentType.Toggle,
content = Content.Text(
text = view.text.toString(),
marks = fromSpannableToMarks(view.text),
param = ContentParam.empty()
),
blockType = BlockType.Editable
)
}
else -> TODO()
}
}
private fun fromSpannableToMarks(content: CharSequence): List<Mark> {
val text = SpannableString(content)
val marks = ArrayList<Mark>()
text.getSpans(0, text.length, StyleSpan::class.java).forEach {
val start = text.getSpanStart(it)
val end = text.getSpanEnd(it)
if (start <= end) {
marks.add(Mark(start = start, end = end, param = "", type = getStyleMarkType(it.style)))
}
}
text.getSpans(0, text.length, StrikethroughSpan::class.java).forEach {
val start = text.getSpanStart(it)
val end = text.getSpanEnd(it)
if (start <= end) {
marks.add(Mark(start = start, end = end, param = "", type = Mark.MarkType.STRIKE_THROUGH))
}
}
text.getSpans(0, text.length, URLSpan::class.java).forEach {
val start = text.getSpanStart(it)
val end = text.getSpanEnd(it)
if (start <= end) {
marks.add(Mark(start = start, end = end, param = it.url, type = Mark.MarkType.HYPERTEXT))
}
}
return marks
}
private fun getStyleMarkType(style: Int) =
when (style) {
Typeface.BOLD -> Mark.MarkType.BOLD
Typeface.ITALIC -> Mark.MarkType.ITALIC
else -> Mark.MarkType.UNDEFINED
}
}

View file

@ -1,256 +0,0 @@
package com.agileburo.anytype.feature_editor.presentation.mapper
import android.graphics.Typeface
import android.text.Spannable
import android.text.SpannableString
import android.text.style.ClickableSpan
import android.text.style.StrikethroughSpan
import android.text.style.StyleSpan
import android.text.style.URLSpan
import android.view.View
import com.agileburo.anytype.feature_editor.domain.*
import com.agileburo.anytype.feature_editor.presentation.model.BlockView
import com.agileburo.anytype.feature_editor.ui.CodeBlockSpan
interface ViewMapper<in D, out V> {
fun mapToView(model: D, indent : Int = 0): V
}
class BlockViewMapper : ViewMapper<Block, BlockView> {
override fun mapToView(model: Block, indent : Int): BlockView {
return when (model.blockType) {
is BlockType.Editable -> {
when (model.contentType) {
is ContentType.P -> {
BlockView.ParagraphView(
indent = indent,
id = model.id,
text = fromMarksToSpannable(
marks = (model.content as Content.Text).marks,
text = model.content.text
),
focused = model.state.focused
)
}
is ContentType.H1 -> {
BlockView.HeaderView(
id = model.id,
text = fromMarksToSpannable(
marks = (model.content as Content.Text).marks,
text = model.content.text
),
type = BlockView.HeaderView.HeaderType.ONE,
indent = indent,
focused = model.state.focused
)
}
is ContentType.H2 -> {
BlockView.HeaderView(
id = model.id,
text = fromMarksToSpannable(
marks = (model.content as Content.Text).marks,
text = model.content.text
),
type = BlockView.HeaderView.HeaderType.TWO,
indent = indent,
focused = model.state.focused
)
}
is ContentType.H3 -> {
BlockView.HeaderView(
id = model.id,
text = fromMarksToSpannable(
marks = (model.content as Content.Text).marks,
text = model.content.text
),
type = BlockView.HeaderView.HeaderType.THREE,
indent = indent,
focused = model.state.focused
)
}
is ContentType.H4 -> {
BlockView.HeaderView(
id = model.id,
text = fromMarksToSpannable(
marks = (model.content as Content.Text).marks,
text = model.content.text
),
type = BlockView.HeaderView.HeaderType.FOUR,
indent = indent,
focused = model.state.focused
)
}
is ContentType.Quote -> {
BlockView.QuoteView(
id = model.id,
text = fromMarksToSpannable(
marks = (model.content as Content.Text).marks,
text = model.content.text
),
indent = indent,
focused = model.state.focused
)
}
is ContentType.Code -> {
BlockView.CodeSnippetView(
id = model.id,
text = fromMarksToSpannable(
marks = (model.content as Content.Text).marks,
text = model.content.text
),
indent = indent,
focused = model.state.focused
)
}
is ContentType.Check -> {
BlockView.CheckboxView(
id = model.id,
text = fromMarksToSpannable(
marks = (model.content as Content.Text).marks,
text = model.content.text
),
isChecked = model.content.param.checked,
indent = indent,
focused = model.state.focused
)
}
is ContentType.NumberedList -> {
BlockView.NumberListItemView(
id = model.id,
text = fromMarksToSpannable(
marks = (model.content as Content.Text).marks,
text = model.content.text
),
number = model.content.param.number,
indent = indent,
focused = model.state.focused
)
}
is ContentType.UL -> {
BlockView.BulletView(
id = model.id,
text = fromMarksToSpannable(
marks = (model.content as Content.Text).marks,
text = model.content.text
),
indent = indent,
focused = model.state.focused
)
}
is ContentType.Toggle -> {
BlockView.ToggleView(
id = model.id,
text = fromMarksToSpannable(
marks = (model.content as Content.Text).marks,
text = model.content.text
),
indent = indent,
expanded = model.state.expanded,
focused = model.state.focused
)
}
else -> {
throw NotImplementedError("${model.contentType} is not supported")
}
}
}
is BlockType.Page -> {
BlockView.LinkToPageView(
id = model.id,
title = (model.content as Content.Page).id
)
}
is BlockType.BookMark -> {
BlockView.BookmarkView(
id = model.id,
title = (model.content as Content.Bookmark).title,
description = model.content.description,
url = model.content.url,
image = model.content.images.first().url,
indent = indent
)
}
is BlockType.Divider -> {
BlockView.DividerView(
id = model.id,
indent = indent
)
}
is BlockType.Image -> {
BlockView.PictureView(
id = model.id,
url = (model.content as Content.Picture).url,
indent = indent
)
}
else -> TODO()
}
}
private fun fromMarksToSpannable(text: String, marks: List<Mark>) =
SpannableString(text).apply {
marks.forEach {
when (it.type) {
Mark.MarkType.BOLD -> setSpan(
StyleSpan(Typeface.BOLD),
it.start,
it.end,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
Mark.MarkType.ITALIC -> setSpan(
StyleSpan(Typeface.ITALIC),
it.start,
it.end,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
Mark.MarkType.STRIKE_THROUGH -> setSpan(
StrikethroughSpan(),
it.start,
it.end,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
Mark.MarkType.CODE -> setSpan(
CodeBlockSpan(Typeface.DEFAULT),
it.start,
it.end,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
Mark.MarkType.HYPERTEXT -> {
setSpan(URLSpan(it.param), it.start, it.end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
//TODO решить задачу EditText + Clickable UrlSpan
// val clickableSpan = object : ClickableSpan() {
// override fun onClick(widget: View?) {
// //onClickListener.invoke()
// Timber.d("On link clicked !!!")
// }
// }
// setSpan(clickableSpan, it.start, it.end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
// val method = BetterLinkMovementMethod.getInstance()
// textView.movementMethod = method
// textView.setOnTouchListener { _, event ->
// method.onTouchEvent(textView, textView.text as Spannable, event)
// || itemView.onTouchEvent(event)
// }
// withClickableSpan(it.param, it.start.toInt(), it.end.toInt(), click)
}
else -> throw IllegalArgumentException("Not supported type of marks : ${it.type}")
}
}
}
fun SpannableString.withClickableSpan(clickablePart: String, onClickListener: () -> Unit): SpannableString {
val clickableSpan = object : ClickableSpan() {
override fun onClick(widget: View) = onClickListener.invoke()
}
val clickablePartStart = indexOf(clickablePart)
setSpan(
clickableSpan,
clickablePartStart,
clickablePartStart + clickablePart.length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
return this
}
}

View file

@ -1,111 +0,0 @@
package com.agileburo.anytype.feature_editor.presentation.model
import android.text.SpannableString
sealed class BlockView {
abstract val id : String
var isSelected: Boolean = false
data class ParagraphView(
override val id : String,
override val indent : Int = 0,
override var text : SpannableString,
override val focused: Boolean
) : BlockView(), Editable, Indentable, Focusable
data class HeaderView(
override val id : String,
override var text : SpannableString,
val type : HeaderType,
override val indent: Int = 0,
override val focused: Boolean
) : BlockView(), Editable, Indentable, Focusable {
enum class HeaderType { ONE, TWO, THREE, FOUR }
}
data class QuoteView(
override val id : String,
override var text : SpannableString,
override val indent : Int = 0,
override val focused: Boolean
) : BlockView(), Editable, Indentable, Focusable
data class CheckboxView(
override val id : String,
override var text : SpannableString,
override val indent: Int = 0,
override val focused: Boolean,
val isChecked : Boolean
) : BlockView(), Editable, Indentable, Focusable
data class CodeSnippetView(
override val id : String,
override var text : SpannableString,
override val indent : Int = 0,
override val focused: Boolean
) : BlockView(), Editable, Indentable, Focusable
data class NumberListItemView(
override val id : String,
override var text: SpannableString,
override val indent : Int = 0,
override val focused: Boolean,
val number : Int
) : BlockView(), Editable, Indentable, Focusable
data class BulletView(
override val id : String,
override var text: SpannableString,
override val indent : Int = 0,
override val focused: Boolean
) : BlockView(), Editable, Indentable, Focusable
data class LinkToPageView(
override val id : String,
val title : String,
override val indent : Int = 0
) : BlockView(), Indentable
data class BookmarkView(
override val id : String,
val title : String,
val description : String,
val url : String,
val image : String,
override val indent : Int = 0
) : BlockView(), Indentable
data class DividerView(
override val id : String,
override val indent : Int = 0
) : BlockView(), Indentable
data class PictureView (
override val id : String,
val url : String,
override val indent : Int = 0
) : BlockView(), Indentable
data class ToggleView(
override val id : String,
override val indent : Int = 0,
override var text : SpannableString,
val expanded : Boolean = false,
override val focused: Boolean
) : BlockView(), Editable, Indentable, Focusable
interface Editable {
var text : SpannableString
}
interface Indentable {
val indent : Int
}
interface Focusable {
val focused : Boolean
}
interface Consumer
}

View file

@ -1,172 +0,0 @@
package com.agileburo.anytype.feature_editor.presentation.mvvm
import androidx.lifecycle.ViewModel
import com.agileburo.anytype.core_utils.ext.BaseSchedulerProvider
import com.agileburo.anytype.core_utils.ext.shift
import com.agileburo.anytype.feature_editor.disposedBy
import com.agileburo.anytype.feature_editor.domain.*
import com.agileburo.anytype.feature_editor.presentation.converter.BlockContentTypeConverter
import com.agileburo.anytype.feature_editor.presentation.model.BlockView
import com.agileburo.anytype.feature_editor.presentation.util.DragDropAction
import com.agileburo.anytype.feature_editor.ui.BlockMenuAction
import com.agileburo.anytype.feature_editor.ui.EditorState
import com.jakewharton.rxrelay2.BehaviorRelay
import io.reactivex.disposables.CompositeDisposable
import timber.log.Timber
class EditorViewModel(
private val interactor: EditorInteractor,
private val contentTypeConverter: BlockContentTypeConverter,
private val schedulerProvider: BaseSchedulerProvider
) : ViewModel() {
private val subscriptions by lazy { CompositeDisposable() }
private val document : Document by lazy { mutableListOf<Block>() }
private val progress by lazy { BehaviorRelay.create<EditorState>() }
private var positionInFocus: Int = -1
init {
fetchBlocks()
}
fun observeState() = progress
fun onBlockContentChanged(block: Block) {
document.updateContent(
targetId = block.id,
targetContentUpdate = block.content
)
}
fun onExpandClicked(view : BlockView) {
check(view is BlockView.ToggleView)
document.flatSearch(view.id)?.let { block ->
block.state.expanded = !view.expanded
dispatchBlocksToView()
}
}
fun onBlockMenuAction(action: BlockMenuAction) {
when (action) {
is BlockMenuAction.ContentTypeAction -> {
document.changeContentType(targetId = action.id, targetType = action.newType)
document.fixNumberOrder()
dispatchBlocksToView()
}
is BlockMenuAction.ArchiveAction -> {
removeBlock(action.id)
}
is BlockMenuAction.DuplicateAction -> {
TODO()
}
}
}
fun onBlockDragAndDropAction(action: DragDropAction) = when (action) {
is DragDropAction.Shift -> onShiftAction(from = action.from, to = action.to)
is DragDropAction.Consume -> onConsumeAction(target = action.target, consumer = action.consumer)
}
private fun onShiftAction(from: Int, to: Int) {
val newBlocks = document.shift(from, to)
document.clear()
document.addAll(newBlocks)
dispatchBlocksToView()
normalizeBlocks()
}
//Todo Update with proper consume action
private fun onConsumeAction(target: Int, consumer: Int) {
document.removeAt(target)
progress.accept(EditorState.Remove(target))
normalizeBlocks()
}
fun onConsumeRequested(consumerId : String, consumableId : String) {
document.apply {
consume(consumerId = consumerId, consumableId = consumableId)
fixNumberOrder()
}
dispatchBlocksToView()
}
fun onMoveAfter(previousId : String, targetId : String) {
document.apply {
moveAfter(previousId, targetId)
fixNumberOrder()
}
dispatchBlocksToView()
}
fun onBlockFocus(position: Int) {
if (position == -1) {
positionInFocus = position
clearBlockFocus()
return
}
progress.accept(EditorState.DragDropOff)
positionInFocus = position
}
fun onEnterClicked(id : String) {
Timber.d("On enter clicked with id: $id")
document.apply {
applyToAll { it.state.focused = false }
insertNewBlockAfter(previousBlockId = id)
fixNumberOrder()
}
dispatchBlocksToView()
}
private fun normalizeBlocks() {
val normalized = contentTypeConverter.normalizeNumbers(document)
document.clear()
document.addAll(normalized)
dispatchBlocksToView()
}
private fun clearBlockFocus() =
document.getOrNull(positionInFocus)?.let {
progress.accept(EditorState.DragDropOn)
progress.accept(
EditorState.ClearBlockFocus(positionInFocus, it.contentType)
)
}
private fun fetchBlocks() {
interactor.getBlocks()
.observeOn(schedulerProvider.ui())
.subscribeOn(schedulerProvider.io())
.subscribe(
{ data -> onBlockReceived(data) },
{ error -> Timber.e(error, "Error while fetching document") }
).disposedBy(subscriptions)
}
private fun onBlockReceived(items: List<Block>) {
document.addAll(items)
progress.accept(EditorState.Result(document))
}
private fun removeBlock(id: String) {
document.apply {
delete(id)
fixNumberOrder()
}
dispatchBlocksToView()
}
private fun dispatchBlocksToView() {
progress.accept(EditorState.Updates(document))
}
override fun onCleared() {
subscriptions.clear()
super.onCleared()
}
}

View file

@ -1,23 +0,0 @@
package com.agileburo.anytype.feature_editor.presentation.mvvm
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.agileburo.anytype.core_utils.ext.BaseSchedulerProvider
import com.agileburo.anytype.feature_editor.domain.EditorInteractor
import com.agileburo.anytype.feature_editor.presentation.converter.BlockContentTypeConverter
class EditorViewModelFactory(
private val editorInteractor: EditorInteractor,
private val contentTypeConverter: BlockContentTypeConverter,
private val schedulerProvider: BaseSchedulerProvider
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T =
EditorViewModel(
interactor = editorInteractor,
contentTypeConverter = contentTypeConverter,
schedulerProvider = schedulerProvider
) as T
}

View file

@ -1,21 +0,0 @@
package com.agileburo.anytype.feature_editor.presentation.util
import androidx.recyclerview.widget.DiffUtil
import com.agileburo.anytype.feature_editor.presentation.model.BlockView
class BlockViewDiffUtil(
private val old : List<BlockView>,
private val new : List<BlockView>
) : DiffUtil.Callback() {
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return old[oldItemPosition].id == new[newItemPosition].id
}
override fun getOldListSize(): Int = old.size
override fun getNewListSize(): Int = new.size
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return new[newItemPosition] == old[oldItemPosition]
}
}

View file

@ -1,6 +0,0 @@
package com.agileburo.anytype.feature_editor.presentation.util
sealed class DragDropAction {
data class Consume(val target: Int, val consumer: Int) : DragDropAction()
data class Shift(val from: Int, val to: Int) : DragDropAction()
}

View file

@ -1,6 +0,0 @@
package com.agileburo.anytype.feature_editor.presentation.util
data class SwapRequest(
val from : Int,
val to : Int
)

View file

@ -1,127 +0,0 @@
package com.agileburo.anytype.feature_editor.ui
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.widget.ImageView
import android.widget.PopupWindow
import com.agileburo.anytype.feature_editor.R
import com.agileburo.anytype.feature_editor.domain.ContentType
import com.agileburo.anytype.feature_editor.presentation.model.BlockView
/**
* Created by Konstantin Ivanov
* email : ki@agileburo.com
* on 2019-05-20.
*/
sealed class BlockMenuAction{
data class ContentTypeAction(val id : String, val newType: ContentType): BlockMenuAction()
data class ArchiveAction(val id : String): BlockMenuAction()
data class DuplicateAction(val id: String): BlockMenuAction()
}
const val UNDEFINED_BUTTON = -1
class BlockMenu(
private val context: Context,
private val block: BlockView,
private val menuItemClick: (BlockMenuAction) -> Unit
) : PopupWindow(context) {
init {
setupView()
}
private fun setupView() {
val view = LayoutInflater.from(context).inflate(getLayout(block), null)
view.findViewById<ImageView>(getButton(block))?.isSelected = true
isOutsideTouchable = true
isFocusable = true
contentView = view
setClicks()
}
private fun setClicks() {
contentView.findViewById<View>(R.id.btn_menu_p)?.setOnClickListener {
menuItemClick.invoke(BlockMenuAction.ContentTypeAction(block.id, ContentType.P))
dismiss()
}
contentView.findViewById<View>(R.id.btn_menu_h2)?.setOnClickListener {
menuItemClick.invoke(BlockMenuAction.ContentTypeAction(block.id, ContentType.H2))
dismiss()
}
contentView.findViewById<View>(R.id.btn_menu_h3)?.setOnClickListener {
menuItemClick.invoke(BlockMenuAction.ContentTypeAction(block.id, ContentType.H3))
dismiss()
}
contentView.findViewById<View>(R.id.btn_menu_h4)?.setOnClickListener {
menuItemClick.invoke(BlockMenuAction.ContentTypeAction(block.id, ContentType.H4))
dismiss()
}
contentView.findViewById<View>(R.id.btn_menu_bullet)?.setOnClickListener {
menuItemClick.invoke(BlockMenuAction.ContentTypeAction(block.id, ContentType.UL))
dismiss()
}
contentView.findViewById<View>(R.id.btn_menu_quote)?.setOnClickListener {
menuItemClick.invoke(BlockMenuAction.ContentTypeAction(block.id, ContentType.Quote))
dismiss()
}
contentView.findViewById<View>(R.id.btn_menu_numbered)?.setOnClickListener {
menuItemClick.invoke(BlockMenuAction.ContentTypeAction(block.id, ContentType.NumberedList))
dismiss()
}
contentView.findViewById<View>(R.id.btn_menu_checkbox)?.setOnClickListener {
menuItemClick.invoke(BlockMenuAction.ContentTypeAction(block.id, ContentType.Check))
dismiss()
}
contentView.findViewById<View>(R.id.btn_menu_code)?.setOnClickListener {
menuItemClick.invoke(BlockMenuAction.ContentTypeAction(block.id, ContentType.Code))
dismiss()
}
contentView.findViewById<View>(R.id.btn_menu_archive)?.setOnClickListener {
menuItemClick.invoke(BlockMenuAction.ArchiveAction(block.id))
dismiss()
}
contentView.findViewById<View>(R.id.btn_menu_duplicate)?.setOnClickListener {
menuItemClick.invoke(BlockMenuAction.DuplicateAction(block.id))
dismiss()
}
contentView.findViewById<View>(R.id.btn_menu_toggle)?.setOnClickListener {
menuItemClick.invoke(BlockMenuAction.ContentTypeAction(block.id, ContentType.Toggle))
dismiss()
}
}
private fun getLayout(block: BlockView): Int =
when (block) {
is BlockView.Editable -> R.layout.popup_edit_block
else -> R.layout.popup_non_edit_block
}
private fun getButton(block: BlockView) =
when (block) {
is BlockView.ParagraphView -> R.id.btn_menu_p
is BlockView.HeaderView -> {
when (block.type) {
BlockView.HeaderView.HeaderType.ONE -> UNDEFINED_BUTTON
BlockView.HeaderView.HeaderType.TWO -> R.id.btn_menu_h2
BlockView.HeaderView.HeaderType.THREE -> R.id.btn_menu_h3
BlockView.HeaderView.HeaderType.FOUR -> R.id.btn_menu_h4
}
}
is BlockView.BulletView -> R.id.btn_menu_bullet
is BlockView.QuoteView -> R.id.btn_menu_quote
is BlockView.NumberListItemView -> R.id.btn_menu_numbered
is BlockView.CheckboxView -> R.id.btn_menu_checkbox
is BlockView.CodeSnippetView -> R.id.btn_menu_code
is BlockView.ToggleView -> R.id.btn_menu_toggle
//Todo implement!
is BlockView.LinkToPageView -> throw NotImplementedError()
is BlockView.BookmarkView -> throw NotImplementedError()
is BlockView.DividerView -> throw NotImplementedError()
is BlockView.PictureView -> throw NotImplementedError()
}
}

View file

@ -1,23 +0,0 @@
package com.agileburo.anytype.feature_editor.ui
import android.content.Context
import android.util.AttributeSet
import android.view.KeyEvent
import androidx.appcompat.widget.AppCompatEditText
class ClearFocusEditText : AppCompatEditText {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(
context,
attrs,
defStyle
)
override fun onKeyPreIme(keyCode: Int, event: KeyEvent?): Boolean {
if (keyCode == KeyEvent.KEYCODE_BACK) {
clearFocus()
}
return super.onKeyPreIme(keyCode, event)
}
}

View file

@ -1,20 +0,0 @@
package com.agileburo.anytype.feature_editor.ui
import android.graphics.Color
import android.graphics.Typeface
import androidx.annotation.ColorInt
import android.text.TextPaint
import com.agileburo.anytype.feature_editor.ui.FontSpan
class CodeBlockSpan(
font: Typeface?,
@param:ColorInt private val backgroundColor: Int = Color.LTGRAY
) : FontSpan(font) {
// Since we're only changing the background color, it will not affect the measure state, so
// just override the updateContent draw state.
override fun updateDrawState(textPaint: TextPaint) {
super.updateDrawState(textPaint)
textPaint.bgColor = backgroundColor
}
}

View file

@ -1,349 +0,0 @@
package com.agileburo.anytype.feature_editor.ui
import android.graphics.Canvas
import android.graphics.Color
import android.view.View
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.ItemTouchHelper.*
import androidx.recyclerview.widget.RecyclerView
import com.agileburo.anytype.feature_editor.presentation.model.BlockView
import com.agileburo.anytype.feature_editor.presentation.util.DragDropAction
import com.jakewharton.rxrelay2.BehaviorRelay
import io.reactivex.disposables.CompositeDisposable
import kotlin.math.abs
/**
* Created by Konstantin Ivanov
* email : ki@agileburo.com
* on 2019-08-01.
*/
interface ItemTouchHelperViewHolder {
fun targetView()
fun targetViewBottom()
fun clearTargetView()
}
sealed class DragState {
/** Send Shift action. */
data class Shift(val from: RecyclerView.ViewHolder) : DragState()
/** The opportunity to produce Shift action. Remember shiftView for the future. Render view. */
data class ShiftRequest(val from: RecyclerView.ViewHolder, val to: RecyclerView.ViewHolder) : DragState()
/** Delete shiftView */
object DeleteShiftRequest : DragState()
/** Send Consume action. */
data class Consume(val target: RecyclerView.ViewHolder) : DragState()
/** The opportunity to produce Consume action. Remember consumerView for the future. */
data class ConsumeRequest(val target: Int, val consumerViewHolder: RecyclerView.ViewHolder?) : DragState()
/** The user drag element. */
data class Dragging(val target: Int) : DragState()
/** The element is not controlled by user and simply animating back to its original state. */
data class DraggingNotActive(val view: View, val alpha: Float) : DragState()
/** The user interaction with an element is over and it also completed its animation. */
object Idle : DragState()
}
const val CONSUME_INTERVAL = 7
const val DRAG_STATE_ALPHA = 0.3f
const val CONSUME_STATE_ALPHA = 0.0f
const val IDLE_STATE_ALPHA = 1.0f
const val POSITION_NONE = -1
class DragAndDropBehavior(
private val onDragDropAction: (DragDropAction) -> Unit
) : ItemTouchHelper.Callback() {
private val disposable = CompositeDisposable()
private val subject: BehaviorRelay<DragState> = BehaviorRelay.createDefault(DragState.Idle)
private var shiftView: RecyclerView.ViewHolder? = null
private var consumerPosition: Int = POSITION_NONE
fun init() = disposable.addAll(subject.subscribe { handleState(it) })
fun destroy() = disposable.clear()
override fun onSwiped(p0: RecyclerView.ViewHolder, p1: Int) {}
override fun getMovementFlags(p0: RecyclerView, p1: RecyclerView.ViewHolder): Int =
makeMovementFlags(UP or DOWN, 0)
/**
* The user has removed the finger from the screen.
* Check for null viewHolders for Shift or Consume behavior.
* First check for consume action
*/
override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
super.clearView(recyclerView, viewHolder)
removeDraggableViewProperties(viewHolder.itemView)
if (consumerPosition > POSITION_NONE) {
subject.accept(DragState.Consume(target = viewHolder))
return
}
if (shiftView != null) {
subject.accept(DragState.Shift(from = viewHolder))
return
}
}
/**
* Returns the fraction that the user should move the View to be considered as it is
* dragged. After a view is moved this amount, ItemTouchHelper starts checking for Views
* below it for a possible drop.
*
* @param viewHolder The ViewHolder that is being dragged.
* @return A float value that denotes the fraction of the View size. Dragging value is
* .5f .
*/
override fun getMoveThreshold(viewHolder: RecyclerView.ViewHolder): Float = 0.1f
override fun getAnimationDuration(
recyclerView: RecyclerView,
animationType: Int,
animateDx: Float,
animateDy: Float
): Long = 0
override fun isLongPressDragEnabled(): Boolean = true
override fun isItemViewSwipeEnabled(): Boolean = false
override fun canDropOver(
recyclerView: RecyclerView,
current: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean = target.adapterPosition != 0
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
subject.accept(DragState.ShiftRequest(viewHolder, target))
return false
}
override fun onChildDraw(
c: Canvas,
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
dX: Float,
dY: Float,
actionState: Int,
isCurrentlyActive: Boolean
) {
when (actionState) {
ACTION_STATE_DRAG -> isCurrentlyActive(
recyclerView = recyclerView, dY = dY,
viewHolder = viewHolder, isCurrentlyActive = isCurrentlyActive
)
else -> Unit
}
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)
}
override fun chooseDropTarget(
selected: RecyclerView.ViewHolder,
dropTargets: MutableList<RecyclerView.ViewHolder>,
curX: Int,
curY: Int
): RecyclerView.ViewHolder? {
var winner: RecyclerView.ViewHolder? = null
var winnerScore = Int.MAX_VALUE
val dy = curY - selected.itemView.top
dropTargets.forEach { dropTarget ->
with(dropTarget) {
if (this is BlockView.Consumer) {
val selectedTargetCenter = getSelectedTargetCenter(curY, selected.itemView.height)
when {
dy < 0 -> {
val diff = getConsumeTopBorder(itemView.top, itemView.bottom) - selectedTargetCenter
if (diff > 0 && abs(diff) < winnerScore) {
winnerScore = abs(diff)
winner = this
}
}
dy > 0 -> {
val diff = getConsumeBottomBorder(itemView.top, itemView.bottom) - selectedTargetCenter
if (diff < 0 && abs(diff) < winnerScore) {
winnerScore = abs(diff)
winner = this
}
}
else -> Unit
}
} else {
val diff = getDropTargetCenter(itemView.top, itemView.bottom) -
getSelectedTargetCenter(curY, selected.itemView.height)
when {
dy < 0 ->
if (diff > 0 && abs(diff) < winnerScore) {
winnerScore = abs(diff)
winner = this
} else Unit
dy > 0 ->
if (diff < 0 && abs(diff) < winnerScore) {
winnerScore = abs(diff)
winner = this
} else Unit
else -> Unit
}
}
}
}
if (winner == null) {
subject.accept(DragState.DeleteShiftRequest)
}
return winner
}
private fun setDraggableViewProperties(itemView: View) = with(itemView) {
setBackgroundColor(Color.LTGRAY)
alpha = DRAG_STATE_ALPHA
}
private fun removeDraggableViewProperties(itemView: View) = with(itemView) {
setBackgroundColor(0)
alpha = IDLE_STATE_ALPHA
}
private fun isCurrentlyActive(
isCurrentlyActive: Boolean,
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
dY: Float
) = when (isCurrentlyActive) {
true -> itemDraggedActive(recyclerView, viewHolder, dY)
false -> subject.accept(DragState.DraggingNotActive(viewHolder.itemView, IDLE_STATE_ALPHA))
}
private fun itemDraggedActive(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
dY: Float
) = with(viewHolder.itemView) {
if (alpha != DRAG_STATE_ALPHA) setDraggableViewProperties(this)
for (x in 0 until recyclerView.childCount) {
val child = recyclerView.getChildAt(x)
if (child != this) {
val consumeCenter = (child.top + child.bottom).div(2)
val interval = child.height.div(CONSUME_INTERVAL)
if (isDraggedItemCanBeConsumeByCenterOfDragged(
consumerCenter = consumeCenter,
interval = interval,
baselineY = getDraggedItemBaseline(this, dY).toInt()
)
) {
subject.accept(DragState.DeleteShiftRequest)
val consume = recyclerView.findContainingViewHolder(child)
subject.accept(DragState.ConsumeRequest(viewHolder.adapterPosition, consume))
break
} else {
if (consumerPosition > POSITION_NONE) {
recyclerView.findViewHolderForAdapterPosition(consumerPosition)?.itemView?.setBackgroundColor(0)
}
subject.accept(DragState.Dragging(viewHolder.adapterPosition))
}
}
}
}
private fun handleState(state: DragState) = when (state) {
is DragState.Shift -> {
shiftView?.let {
consumerPosition = POSITION_NONE
(it as? ItemTouchHelperViewHolder)?.clearTargetView()
onDragDropAction.invoke(
DragDropAction.Shift(from = state.from.adapterPosition, to = it.adapterPosition)
)
}
shiftView = null
}
is DragState.ShiftRequest -> {
(shiftView as? ItemTouchHelperViewHolder)?.clearTargetView()
shiftView = null
if (state.from.adapterPosition > state.to.adapterPosition) {
(state.to as? ItemTouchHelperViewHolder)?.targetView()
} else {
(state.to as? ItemTouchHelperViewHolder)?.targetViewBottom()
}
shiftView = state.to
}
is DragState.DeleteShiftRequest -> {
(shiftView as? ItemTouchHelperViewHolder)?.clearTargetView()
shiftView = null
}
is DragState.ConsumeRequest -> {
subject.accept(DragState.DeleteShiftRequest)
state.consumerViewHolder?.let { holder ->
if (holder is BlockView.Consumer) {
holder.itemView.setBackgroundColor(Color.GRAY)
consumerPosition = holder.adapterPosition
}
}
}
is DragState.Consume -> {
shiftView = null
subject.accept(
DragState.DraggingNotActive(view = state.target.itemView, alpha = CONSUME_STATE_ALPHA)
)
onDragDropAction.invoke(
DragDropAction.Consume(target = state.target.adapterPosition, consumer = consumerPosition)
)
}
is DragState.Dragging -> {
/** shiftView не нужно обнулять, так как после DragState.ShiftRequest может
быть DragState.Dragging и тогда в clearView не случится SwapRequest
*/
consumerPosition = POSITION_NONE
}
is DragState.DraggingNotActive -> {
if (state.alpha < IDLE_STATE_ALPHA) {
state.view.alpha = state.alpha
} else Unit
}
is DragState.Idle -> {
subject.accept(DragState.DeleteShiftRequest)
consumerPosition = POSITION_NONE
}
}
private fun getDraggedItemBaseline(draggedView: View, dY: Float): Float =
(draggedView.top + dY + (draggedView.height / 2))
private fun isDraggedItemCanBeConsumeByCenterOfDragged(
consumerCenter: Int,
baselineY: Int,
interval: Int
): Boolean = baselineY in consumerCenter.minus(interval)..consumerCenter.plus(interval)
}
fun getDropTargetCenter(top: Int, bottom: Int) = (top + bottom) / 2
fun getConsumeTopBorder(top: Int, bottom: Int): Int {
val centerY = getDropTargetCenter(top = top, bottom = bottom)
val height = bottom - top
return centerY - height.div(CONSUME_INTERVAL)
}
fun getConsumeBottomBorder(top: Int, bottom: Int): Int {
val centerY = getDropTargetCenter(top = top, bottom = bottom)
val height = bottom - top
return centerY + height.div(CONSUME_INTERVAL)
}
fun getSelectedTargetCenter(curY: Int, height: Int): Int = curY + height.div(2)

View file

@ -1,45 +0,0 @@
package com.agileburo.anytype.feature_editor.ui
import com.agileburo.anytype.feature_editor.domain.Block
import com.agileburo.anytype.feature_editor.domain.ContentType
import com.agileburo.anytype.feature_editor.presentation.util.SwapRequest
/**
* Created by Konstantin Ivanov
* email : ki@agileburo.com
* on 21.03.2019.
*/
sealed class EditorAction {
object PressButton : EditorAction()
data class PressBlock(val id: String) : EditorAction()
data class OnBlockFocus(val position: Int) : EditorAction()
}
sealed class EditBlockAction {
data class TextClick(val block: Block) : EditBlockAction()
data class Header1Click(val block: Block) : EditBlockAction()
data class Header2Click(val block: Block) : EditBlockAction()
data class Header3Click(val block: Block) : EditBlockAction()
data class Header4Click(val block: Block) : EditBlockAction()
data class HighLightClick(val block: Block) : EditBlockAction()
data class BulletClick(val block: Block) : EditBlockAction()
data class NumberedClick(val block: Block) : EditBlockAction()
data class CheckBoxClick(val block: Block) : EditBlockAction()
data class CodeClick(val block: Block) : EditBlockAction()
data class ArchiveBlock(val id: String) : EditBlockAction()
}
sealed class EditorState {
data class ClearBlockFocus(val position: Int, val contentType: ContentType) : EditorState()
object HideKeyboard : EditorState()
data class Result(val blocks: List<Block>) : EditorState()
data class Swap(val request: SwapRequest) : EditorState()
data class Remove(val position: Int) : EditorState()
data class Updates(val blocks: List<Block>) : EditorState()
data class Update(val block: Block) : EditorState()
data class Archive(val id: String) : EditorState()
data class Error(val msg: String) : EditorState()
object DragDropOn : EditorState()
object DragDropOff : EditorState()
object Loading : EditorState()
}

View file

@ -1,181 +0,0 @@
package com.agileburo.anytype.feature_editor.ui
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import com.agileburo.anytype.core_utils.ext.UIExtensions
import com.agileburo.anytype.core_utils.ext.toast
import com.agileburo.anytype.feature_editor.R
import com.agileburo.anytype.feature_editor.disposedBy
import com.agileburo.anytype.feature_editor.domain.Block
import com.agileburo.anytype.feature_editor.domain.ContentType
import com.agileburo.anytype.feature_editor.domain.toView
import com.agileburo.anytype.feature_editor.presentation.mapper.BlockModelMapper
import com.agileburo.anytype.feature_editor.presentation.mapper.BlockViewMapper
import com.agileburo.anytype.feature_editor.presentation.mvvm.EditorViewModel
import com.agileburo.anytype.feature_editor.presentation.mvvm.EditorViewModelFactory
import com.agileburo.anytype.feature_editor.presentation.util.DragDropAction
import com.agileburo.anytype.feature_editor.presentation.util.SwapRequest
import io.reactivex.disposables.CompositeDisposable
import kotlinx.android.synthetic.main.fragment_editor.*
import javax.inject.Inject
abstract class EditorFragment : Fragment() {
private val mapper by lazy { BlockViewMapper() }
private val viewToModelMapper by lazy { BlockModelMapper() }
@Inject
lateinit var factory: EditorViewModelFactory
private val viewModel by lazy {
ViewModelProviders.of(this, factory).get(EditorViewModel::class.java)
}
private val disposable = CompositeDisposable()
private val helper : ItemTouchHelper by lazy {
ItemTouchHelper(dragAndDropBehavior)
}
private val dragAndDropBehavior by lazy {
DragAndDropBehavior(
onDragDropAction = { action ->
if (action is DragDropAction.Consume)
viewModel.onConsumeRequested(
consumerId = blockAdapter.blocks[action.consumer].id,
consumableId = blockAdapter.blocks[action.target].id
)
else if (action is DragDropAction.Shift) {
if (action.from < action.to)
viewModel.onMoveAfter(
previousId = blockAdapter.blocks[action.to].id,
targetId = blockAdapter.blocks[action.from].id
) else
viewModel.onMoveAfter(
previousId = blockAdapter.blocks[action.to - 1].id,
targetId = blockAdapter.blocks[action.from].id
)
}
}
)
}
private val blockAdapter by lazy {
EditorAdapter(
blocks = mutableListOf(),
blockContentListener = { viewModel.onBlockContentChanged(viewToModelMapper.mapToModel(it)) },
menuListener = viewModel::onBlockMenuAction,
focusListener = viewModel::onBlockFocus,
onExpandClick = viewModel::onExpandClicked,
onEnterPressed = viewModel::onEnterClicked
).also {
helper.attachToRecyclerView(blockList)
}
}
abstract fun inject()
override fun onAttach(context: Context) {
inject()
super.onAttach(context)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_editor, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel.observeState()
.subscribe(this::handleState)
.disposedBy(disposable)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initializeView()
dragAndDropBehavior.init()
}
private fun initializeView() = with(blockList) {
layoutManager = LinearLayoutManager(requireContext())
addItemDecoration(SpaceItemDecoration(space = 48, addSpaceBelowLastItem = true))
adapter = blockAdapter
}
override fun onDestroyView() {
dragAndDropBehavior.destroy()
disposable.clear()
super.onDestroyView()
}
private fun handleState(state: EditorState) = when (state) {
is EditorState.Loading -> {
}
is EditorState.Result -> setBlocks(state.blocks)
is EditorState.Update -> updateBlock(state.block)
is EditorState.Updates -> render(state.blocks)
is EditorState.Swap -> swap(state.request)
is EditorState.Remove -> remove(state.position)
is EditorState.Archive -> {
}
is EditorState.Error -> onError(state.msg)
is EditorState.ClearBlockFocus -> clearBlockFocus(state.position, state.contentType)
is EditorState.HideKeyboard -> UIExtensions.hideSoftKeyBoard(requireActivity(), blockList)
is EditorState.DragDropOn -> helper.attachToRecyclerView(blockList)
is EditorState.DragDropOff -> {
//Todo Потенциально опасное место, см. https://issuetracker.google.com/issues/140447176
helper.attachToRecyclerView(null)
}
}
private fun clearBlockFocus(position: Int, contentType: ContentType) {
blockList.layoutManager?.findViewByPosition(position)?.let {
it.findViewById<View>(getEditTextId(contentType))?.clearFocus()
}
}
private fun getEditTextId(contentType: ContentType) =
when (contentType) {
is ContentType.P -> R.id.textEditable
is ContentType.H1 -> R.id.textHeaderOne
is ContentType.H2 -> R.id.textHeaderTwo
is ContentType.H3 -> R.id.textHeaderThree
is ContentType.H4 -> R.id.textHeaderFour
is ContentType.Quote -> R.id.textQuote
is ContentType.Check -> R.id.textCheckBox
is ContentType.Code -> R.id.textCode
is ContentType.UL -> R.id.textBullet
is ContentType.NumberedList -> R.id.contentText
is ContentType.None -> throw IllegalStateException()
is ContentType.Toggle -> throw UnsupportedOperationException("need implement Toggle")
}
private fun setBlocks(blocks: List<Block>) =
blockAdapter.setBlocks(blocks.toMutableList().toView())
private fun updateBlock(block: Block) = blockAdapter.updateBlock(mapper.mapToView(block))
private fun render(blocks: List<Block>) = blockAdapter.update(blocks.toMutableList().toView())
private fun swap(request: SwapRequest) = blockAdapter.swap(request)
private fun remove(position: Int) = blockAdapter.remove(position)
private fun onError(msg: CharSequence) = requireContext().toast(msg)
}

View file

@ -1,23 +0,0 @@
package com.agileburo.anytype.feature_editor.ui
import android.graphics.Typeface
import android.text.TextPaint
import android.text.style.MetricAffectingSpan
open class FontSpan(private val font: Typeface?) : MetricAffectingSpan() {
override fun updateMeasureState(textPaint: TextPaint) = update(textPaint)
override fun updateDrawState(textPaint: TextPaint) = update(textPaint)
private fun update(textPaint: TextPaint) {
textPaint.apply {
val old = typeface
val oldStyle = old?.style ?: 0
// keep the style set before
val font = Typeface.create(font, oldStyle)
typeface = font
}
}
}

View file

@ -1,39 +0,0 @@
package com.agileburo.anytype.feature_editor.ui
import android.content.Context
import android.text.SpannableStringBuilder
import android.text.style.URLSpan
import android.view.LayoutInflater
import android.widget.Button
import android.widget.EditText
import android.widget.PopupWindow
import com.agileburo.anytype.feature_editor.R
class HyperLinkMenu(
private val context: Context,
private val editText: EditText,
private val start: Int, private val end: Int
) : PopupWindow(context) {
init {
setupView()
}
private fun setupView() {
val view = LayoutInflater.from(context).inflate(R.layout.popup_hyperlink, null)
isFocusable = true
isOutsideTouchable = true
contentView = view
setClicks()
}
private fun setClicks() {
val edtLink = contentView.findViewById<EditText>(R.id.edtLink)
contentView.findViewById<Button>(R.id.btnLink).setOnClickListener {
editText.text = SpannableStringBuilder(editText.text).apply {
setSpan(URLSpan(edtLink.text.toString()), start, end, 1)
}
dismiss()
}
}
}

View file

@ -1,27 +0,0 @@
package com.agileburo.anytype.feature_editor.ui
import android.graphics.Rect
import android.view.View
import androidx.recyclerview.widget.RecyclerView
/**
* Created by Konstantin Ivanov
* email : ki@agileburo.com
* on 2019-06-25.
*/
class SpaceItemDecoration(private val space: Int, private val addSpaceBelowLastItem : Boolean) : RecyclerView.ItemDecoration() {
override fun getItemOffsets(
outRect: Rect, view: View,
parent: RecyclerView, state: RecyclerView.State
) {
outRect.bottom = space
if (parent.getChildAdapterPosition(view) == 0) {
outRect.top = space
}
if (addSpaceBelowLastItem && parent.getChildAdapterPosition(view) == parent.adapter?.itemCount?.dec()) {
outRect.bottom = space * 10
}
}
}

View file

@ -1,89 +0,0 @@
package com.agileburo.anytype.feature_editor.ui
import android.annotation.SuppressLint
import android.graphics.Typeface
import android.text.Spannable
import android.text.SpannableString
import android.text.Spanned
import android.text.style.BulletSpan
import android.text.style.ClickableSpan
import android.text.style.StrikethroughSpan
import android.text.style.StyleSpan
import android.view.View
import android.widget.TextView
import com.agileburo.anytype.feature_editor.domain.Mark
import me.saket.bettermovementmethod.BetterLinkMovementMethod
import java.lang.IllegalArgumentException
@SuppressLint("ClickableViewAccessibility")
fun SpannableString.addMarks(
marks: List<Mark>, textView: TextView,
click: (String) -> Unit, itemView: View
) =
apply {
marks.forEach {
when (it.type) {
Mark.MarkType.BOLD -> setSpan(
StyleSpan(Typeface.BOLD),
it.start.toInt(),
it.end.toInt(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
Mark.MarkType.ITALIC -> setSpan(
StyleSpan(Typeface.ITALIC),
it.start.toInt(),
it.end.toInt(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
Mark.MarkType.STRIKE_THROUGH -> setSpan(
StrikethroughSpan(),
it.start.toInt(),
it.end.toInt(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
Mark.MarkType.CODE -> setSpan(
CodeBlockSpan(Typeface.DEFAULT),
it.start.toInt(),
it.end.toInt(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
Mark.MarkType.HYPERTEXT -> {
// val method = BetterLinkMovementMethod.getInstance()
// textView.movementMethod = method
// textView.setOnTouchListener { _, event ->
// method.onTouchEvent(textView, textView.text as Spannable, event)
// || itemView.onTouchEvent(event)
// }
withClickableSpan(it.param, it.start.toInt(), it.end.toInt(), click)
}
else -> throw IllegalArgumentException("Not supported type of marks : ${it.type}")
}
}
}
fun SpannableString.withClickableSpan(
param: String,
start: Int,
end: Int,
onClickListener: (String) -> Unit
): SpannableString {
val clickableSpan = object : ClickableSpan() {
override fun onClick(widget: View) = onClickListener(param)
}
require(start <= end) { "Marks start should be <= end!" }
setSpan(
clickableSpan,
start,
end,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
return this
}
fun SpannableString.withBulletSpan(gapWidth: Int, start: Int): SpannableString {
setSpan(BulletSpan(gapWidth), start, length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
return this
}

View file

@ -1,206 +0,0 @@
package com.agileburo.anytype.feature_editor.ui
import android.graphics.Typeface
import android.text.Editable
import android.text.SpannableStringBuilder
import android.text.style.StrikethroughSpan
import android.text.style.StyleSpan
import android.text.style.URLSpan
import android.view.ActionMode
import android.view.Menu
import android.view.MenuItem
import android.widget.EditText
import com.agileburo.anytype.feature_editor.R
class TextStyleCallback(
private val editText: ClearFocusEditText,
private val linkClick: (EditText, Int, Int) -> Unit
) : ActionMode.Callback {
override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
val start = editText.selectionStart
val end = editText.selectionEnd
val ssb = SpannableStringBuilder(editText.text)
when (item?.itemId) {
R.id.boldInactive -> {
ssb.setSpan(StyleSpan(Typeface.BOLD), start, end, 1)
editText.text = ssb
return true
}
R.id.boldActive -> {
removeStyleSpanFromSubstring(
style = Typeface.BOLD, start = start,
end = end, spannable = ssb
)?.let {
editText.text = it
}
return true
}
R.id.italicInactive -> {
ssb.setSpan(StyleSpan(Typeface.ITALIC), start, end, 1)
editText.text = ssb
return true
}
R.id.italicActive -> {
removeStyleSpanFromSubstring(
style = Typeface.ITALIC, start = start,
end = end, spannable = ssb
)?.let {
editText.text = it
}
return true
}
R.id.strikeInactive -> {
ssb.setSpan(StrikethroughSpan(), start, end, 1)
editText.text = ssb
return true
}
R.id.strikeActive -> {
removeStrikeSpanFromSubstring(
start = start, end = end, spannable = ssb
)?.let {
editText.text = it
}
return true
}
R.id.linkInactive -> {
linkClick.invoke(editText, start, end)
return true
}
R.id.linkActive -> {
removeUrlSpanFromSubstring(
start = start, end = end, spannable = ssb
)?.let {
editText.text = it
}
return true
}
}
return false
}
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
mode?.menuInflater?.let {
it.inflate(R.menu.style_toolbar, menu)
editText.text?.getSpans(
editText.selectionStart,
editText.selectionEnd,
Object::class.java
)?.forEach { span ->
if (span is StyleSpan) {
if (span.style == Typeface.BOLD) {
changeMenuItemsVisibility(
menu = menu,
inactiveId = R.id.boldInactive,
activeId = R.id.boldActive
)
}
if (span.style == Typeface.ITALIC) {
changeMenuItemsVisibility(
menu = menu,
inactiveId = R.id.italicInactive,
activeId = R.id.italicActive
)
}
}
if (span is StrikethroughSpan) {
changeMenuItemsVisibility(
menu = menu,
inactiveId = R.id.strikeInactive,
activeId = R.id.strikeActive
)
}
if (span is URLSpan) {
changeMenuItemsVisibility(
menu = menu,
inactiveId = R.id.linkInactive,
activeId = R.id.linkActive
)
}
}
return true
}
return false
}
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
return false
}
override fun onDestroyActionMode(mode: ActionMode?) {
}
private fun changeMenuItemsVisibility(menu: Menu?, inactiveId: Int, activeId: Int) {
menu?.findItem(inactiveId)?.isVisible = false
menu?.findItem(activeId)?.isVisible = true
}
private fun removeStyleSpanFromSubstring(
style: Int,
start: Int,
end: Int,
spannable: SpannableStringBuilder
): SpannableStringBuilder? {
spannable.getSpans(start, end, StyleSpan::class.java)?.forEach { span ->
if (span.style == style) {
return SpannableStringBuilder().apply {
if (start > 0) append(spannable.subSequence(0, start))
append(
SpannableStringBuilder(spannable, start, end)
.apply { removeSpan(span) }
)
if (end < spannable.length) {
append(spannable.subSequence(end, spannable.length))
}
}
}
}
return null
}
private fun removeStrikeSpanFromSubstring(
start: Int,
end: Int,
spannable: SpannableStringBuilder
): SpannableStringBuilder? {
spannable.getSpans(start, end, StrikethroughSpan::class.java)?.forEach { span ->
return SpannableStringBuilder().apply {
if (start > 0) append(spannable.subSequence(0, start))
append(
SpannableStringBuilder(spannable, start, end)
.apply { removeSpan(span) }
)
if (end < spannable.length) {
append(spannable.subSequence(end, spannable.length))
}
}
}
return null
}
private fun removeUrlSpanFromSubstring(
start: Int,
end: Int, spannable: SpannableStringBuilder
): SpannableStringBuilder? {
spannable.getSpans(start, end, URLSpan::class.java)?.forEach { span ->
return SpannableStringBuilder().apply {
if (start > 0) append(spannable.subSequence(0, start))
append(
SpannableStringBuilder(spannable, start, end)
.apply { removeSpan(span) }
)
if (end < spannable.length) {
append(spannable.subSequence(end, spannable.length))
}
}
}
return null
}
}

View file

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/button_active" android:state_selected="true"/>
<item android:color="@color/button_active" android:state_pressed="true"/>
<item android:color="@color/button_not_active"/>
</selector>

View file

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#FFFFFF" />
<stroke
android:width="1dp"
android:color="@color/design_default_color_primary" />
<padding
android:bottom="1dp"
android:left="1dp"
android:right="1dp"
android:top="1dp" />
</shape>

View file

@ -1,15 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="16"
android:tint="@color/button_tint"
android:viewportHeight="20">
<path
android:fillColor="#ACA996"
android:fillType="nonZero"
android:pathData="M9,1.997v-1L15,7H9z" />
<path
android:fillColor="#ACA996"
android:fillType="nonZero"
android:pathData="M2,2v16h12V6.828L9.172,2H2zM0,0h10l6,6v14H0V0z" />
</vector>

View file

@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="13"
android:viewportHeight="17">
<path
android:fillColor="@color/button_active"
android:fillType="evenOdd"
android:pathData="M0,0.996h6.203c3.466,0 5.232,1.41 5.232,4.162v0.09c0,1.7 -0.795,2.909 -2.56,3.424 2.052,0.425 3.156,1.61 3.156,3.76v0.089c0,2.953 -1.854,4.475 -5.54,4.475H0v-16zM5.938,14.49c1.766,0 2.561,-0.806 2.561,-2.26v-0.09c0,-1.5 -0.795,-2.238 -2.737,-2.238H3.488v4.588h2.45zM5.63,7.575c1.744,0 2.407,-0.626 2.407,-2.059v-0.09c0,-1.342 -0.751,-1.946 -2.429,-1.946H3.488v4.095h2.141z" />
</vector>

View file

@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="13"
android:viewportHeight="17">
<path
android:fillColor="@color/button_not_active"
android:fillType="evenOdd"
android:pathData="M0,0.996h6.203c3.466,0 5.232,1.41 5.232,4.162v0.09c0,1.7 -0.795,2.909 -2.56,3.424 2.052,0.425 3.156,1.61 3.156,3.76v0.089c0,2.953 -1.854,4.475 -5.54,4.475H0v-16zM5.938,14.49c1.766,0 2.561,-0.806 2.561,-2.26v-0.09c0,-1.5 -0.795,-2.238 -2.737,-2.238H3.488v4.588h2.45zM5.63,7.575c1.744,0 2.407,-0.626 2.407,-2.059v-0.09c0,-1.342 -0.751,-1.946 -2.429,-1.946H3.488v4.095h2.141z" />
</vector>

View file

@ -1,23 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="19"
android:tint="@color/button_tint"
android:viewportHeight="16">
<path
android:fillColor="#ACA996"
android:fillType="nonZero"
android:pathData="M6.031,0.996H18v2H6.031zM6.031,6.996h12v2h-12zM6.031,12.996H18v2H6.031z" />
<path
android:fillColor="#ACA996"
android:fillType="nonZero"
android:pathData="M2,1.996m-1.8,0a1.8,1.8 0,1 1,3.6 0a1.8,1.8 0,1 1,-3.6 0" />
<path
android:fillColor="#ACA996"
android:fillType="nonZero"
android:pathData="M2,7.996m-1.8,0a1.8,1.8 0,1 1,3.6 0a1.8,1.8 0,1 1,-3.6 0" />
<path
android:fillColor="#ACA996"
android:fillType="nonZero"
android:pathData="M2,13.996m-1.8,0a1.8,1.8 0,1 1,3.6 0a1.8,1.8 0,1 1,-3.6 0" />
</vector>

View file

@ -1,15 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="@color/button_tint"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:fillColor="#ACA996"
android:fillType="nonZero"
android:pathData="M10.023,18.012a8,8 0,1 0,0 -16,8 8,0 0,0 0,16zM10.023,20.012c-5.522,0 -10,-4.477 -10,-10s4.478,-10 10,-10c5.523,0 10,4.477 10,10s-4.477,10 -10,10z" />
<path
android:fillColor="#ACA996"
android:fillType="nonZero"
android:pathData="M8.509,11.228l5.085,-5.085 1.414,1.414 -6.5,6.5 -3.516,-3.517 1.414,-1.414z" />
</vector>

View file

@ -1,15 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="@color/button_tint"
android:viewportWidth="20"
android:viewportHeight="18">
<path
android:fillColor="#ACA996"
android:fillType="nonZero"
android:pathData="M2,2v14h16L18,2L2,2zM1.111,0L18.89,0C19.503,0 20,0.448 20,1v16c0,0.552 -0.497,1 -1.111,1L1.11,18C0.497,18 0,17.552 0,17L0,1c0,-0.552 0.497,-1 1.111,-1z" />
<path
android:fillColor="#ACA996"
android:fillType="nonZero"
android:pathData="M5.688,9l2.284,2.3V13l-4,-4 4,-4v1.7zM14.284,9L12,11.3V13l4,-4 -4,-4v1.7z" />
</vector>

View file

@ -1,21 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="18"
android:tint="@color/button_tint"
android:viewportHeight="18">
<path
android:fillColor="#00000000"
android:fillType="nonZero"
android:pathData="M1,5h12v12H1z"
android:strokeWidth="2"
android:strokeColor="#ACA996" />
<path
android:fillColor="#ACA996"
android:fillType="nonZero"
android:pathData="M6,0h12v2H6z" />
<path
android:fillColor="#ACA996"
android:fillType="nonZero"
android:pathData="M16,0h2v12h-2z" />
</vector>

View file

@ -1,6 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp"
android:tint="#FFBB2C" android:viewportHeight="24.0"
android:viewportWidth="24.0" android:width="24dp">
<path android:fillColor="#FF000000"
android:pathData="M12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,16c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
</vector>

View file

@ -1,11 +0,0 @@
<!-- drawable/format_header_4.xml -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="@color/button_tint"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#ACA996"
android:pathData="M3,4H5V10H9V4H11V18H9V12H5V18H3V4M18,18V13H13V11L18,4H20V11H21V13H20V18H18M18,11V7.42L15.45,11H18Z" />
</vector>

View file

@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="@color/button_tint"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#ACA996"
android:pathData="M3,4H5V10H9V4H11V18H9V12H5V18H3V4M14,18V16H16V6.31L13.5,7.75V5.44L16,4H18V16H20V18H14Z" />
</vector>

View file

@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="@color/button_tint"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#ACA996"
android:pathData="M3,4H5V10H9V4H11V18H9V12H5V18H3V4M21,18H15A2,2 0 0,1 13,16C13,15.47 13.2,15 13.54,14.64L18.41,9.41C18.78,9.05 19,8.55 19,8A2,2 0 0,0 17,6A2,2 0 0,0 15,8H13A4,4 0 0,1 17,4A4,4 0 0,1 21,8C21,9.1 20.55,10.1 19.83,10.83L15,16H21V18Z" />
</vector>

View file

@ -1,11 +0,0 @@
<!-- drawable/format_header_3.xml -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="@color/button_tint"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#ACA996"
android:pathData="M3,4H5V10H9V4H11V18H9V12H5V18H3V4M15,4H19A2,2 0 0,1 21,6V16A2,2 0 0,1 19,18H15A2,2 0 0,1 13,16V15H15V16H19V12H15V10H19V6H15V7H13V6A2,2 0 0,1 15,4Z" />
</vector>

View file

@ -1,11 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="17"
android:tint="@color/button_tint"
android:viewportHeight="18">
<path
android:fillColor="#ACA996"
android:fillType="nonZero"
android:pathData="M5,2h12v2H5zM5,8h12v2H5zM5,14h12v2H5zM0,0h1v18H0z" />
</vector>

View file

@ -1,14 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="10"
android:viewportHeight="16">
<path
android:fillColor="@color/button_active"
android:fillType="evenOdd"
android:pathData="M5.343,0h1.943L4.657,16H2.714z" />
<path
android:fillColor="@color/button_active"
android:fillType="nonZero"
android:pathData="M2.004,1.508L2.258,0H10l-0.27,1.508zM0,16l0.254,-1.508h7.742L7.726,16z" />
</vector>

View file

@ -1,14 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="10"
android:viewportHeight="16">
<path
android:fillColor="@color/button_not_active"
android:fillType="evenOdd"
android:pathData="M5.343,0h1.943L4.657,16H2.714z" />
<path
android:fillColor="@color/button_not_active"
android:fillType="nonZero"
android:pathData="M2.004,1.508L2.258,0H10l-0.27,1.508zM0,16l0.254,-1.508h7.742L7.726,16z" />
</vector>

View file

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="@color/button_active"
android:pathData="M3.9,12c0,-1.71 1.39,-3.1 3.1,-3.1h4L11,7L7,7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5h4v-1.9L7,15.1c-1.71,0 -3.1,-1.39 -3.1,-3.1zM8,13h8v-2L8,11v2zM17,7h-4v1.9h4c1.71,0 3.1,1.39 3.1,3.1s-1.39,3.1 -3.1,3.1h-4L13,17h4c2.76,0 5,-2.24 5,-5s-2.24,-5 -5,-5z"/>
</vector>

View file

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="@color/button_not_active"
android:pathData="M3.9,12c0,-1.71 1.39,-3.1 3.1,-3.1h4L11,7L7,7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5h4v-1.9L7,15.1c-1.71,0 -3.1,-1.39 -3.1,-3.1zM8,13h8v-2L8,11v2zM17,7h-4v1.9h4c1.71,0 3.1,1.39 3.1,3.1s-1.39,3.1 -3.1,3.1h-4L13,17h4c2.76,0 5,-2.24 5,-5s-2.24,-5 -5,-5z"/>
</vector>

View file

@ -1,15 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="@color/button_tint"
android:viewportWidth="16"
android:viewportHeight="20">
<path
android:fillColor="#ACA996"
android:fillType="nonZero"
android:pathData="M9,1.997v-1L15,7H9z" />
<path
android:fillColor="#ACA996"
android:fillType="nonZero"
android:pathData="M2,2v16h12V6.828L9.172,2H2zM0,0h10l6,6v14H0V0z" />
</vector>

View file

@ -1,15 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="19"
android:tint="@color/button_tint"
android:viewportHeight="19">
<path
android:fillColor="#ACA996"
android:fillType="evenOdd"
android:pathData="M2.038,2.334l-1.22,0.8V1.973L2.206,0.988h1.195v6.016H2.038zM0.19,18.775l2.225,-2.352c0.586,-0.62 0.96,-1.155 0.96,-1.707 0,-0.509 -0.272,-0.823 -0.807,-0.823 -0.543,0 -0.874,0.305 -0.968,1.112H0.352c0.068,-1.418 0.909,-2.165 2.276,-2.165 0.67,0 1.197,0.195 1.562,0.51 0.348,0.33 0.544,0.789 0.544,1.332 0,0.807 -0.467,1.47 -1.172,2.149l-1.147,1.12h2.361v1.045H0.191v-0.22z" />
<path
android:fillColor="#ACA996"
android:fillType="nonZero"
android:pathData="M7.031,2.996H19v2H7.031zM7.031,8.996h9v2h-9zM7.031,14.996H19v2H7.031z" />
</vector>

View file

@ -1,30 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="36dp"
android:height="46dp"
android:viewportWidth="36"
android:viewportHeight="46">
<path
android:pathData="M33.1,45.5C34.5,45.5 35.6,44.4 35.6,43L35.6,13.4C35.6,13 35.4,12.6 35.2,12.3L23.8,0.9C23.7,0.8 23.5,0.7 23.4,0.6C23.4,0.6 23.4,0.6 23.4,0.6C23.2,0.5 23,0.5 22.8,0.5L2.9,0.5C1.5,0.5 0.4,1.6 0.4,3L0.4,43C0.4,44.4 1.5,45.5 2.9,45.5L33.1,45.5ZM34,12.5L25.2,12.5C24.4,12.5 23.7,11.8 23.7,11L23.7,2.2L34,12.5ZM1.4,43L1.4,3C1.4,2.2 2.1,1.5 2.9,1.5L22.7,1.5L22.7,11C22.7,12.4 23.8,13.5 25.2,13.5L34.7,13.5L34.7,43C34.7,43.8 34,44.5 33.2,44.5L2.9,44.5C2,44.5 1.4,43.8 1.4,43Z"
android:strokeWidth="1"
android:fillColor="#000000"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
<path
android:pathData="M7,22.3L17,22.3C17.3,22.3 17.5,22.1 17.5,21.8C17.5,21.5 17.3,21.3 17,21.3L7,21.3C6.7,21.3 6.5,21.5 6.5,21.8C6.5,22.1 6.7,22.3 7,22.3Z"
android:strokeWidth="1"
android:fillColor="#000000"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
<path
android:pathData="M7,28.3L25,28.3C25.3,28.3 25.5,28.1 25.5,27.8C25.5,27.5 25.3,27.3 25,27.3L7,27.3C6.7,27.3 6.5,27.5 6.5,27.8C6.5,28.1 6.7,28.3 7,28.3Z"
android:strokeWidth="1"
android:fillColor="#000000"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
<path
android:pathData="M7,34.3L25,34.3C25.3,34.3 25.5,34.1 25.5,33.8C25.5,33.5 25.3,33.3 25,33.3L7,33.3C6.7,33.3 6.5,33.5 6.5,33.8C6.5,34.1 6.7,34.3 7,34.3Z"
android:strokeWidth="1"
android:fillColor="#000000"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
</vector>

View file

@ -1,14 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="16"
android:viewportHeight="16">
<path
android:fillColor="@color/button_active"
android:fillType="evenOdd"
android:pathData="M7.775,16c-4.04,0 -5.5,-2.292 -5.775,-4.955h1.777c0.233,1.855 0.973,3.427 3.998,3.427 1.946,0 3.448,-1.179 3.448,-2.947s-0.783,-2.532 -3.66,-2.99c-3.173,-0.524 -5.055,-1.506 -5.055,-4.3C2.508,1.855 4.518,0 7.373,0c3.046,0 4.887,1.506 5.204,4.3h-1.65c-0.36,-1.986 -1.439,-2.772 -3.554,-2.772 -2.094,0 -3.152,1.048 -3.152,2.532 0,1.506 0.55,2.336 3.639,2.794 3.363,0.524 5.14,1.615 5.14,4.54C13,14.014 10.779,16 7.775,16z" />
<path
android:fillColor="@color/button_active"
android:fillType="nonZero"
android:pathData="M0,7h16v2H0z" />
</vector>

View file

@ -1,14 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="16"
android:viewportHeight="16">
<path
android:fillColor="@color/button_not_active"
android:fillType="evenOdd"
android:pathData="M7.775,16c-4.04,0 -5.5,-2.292 -5.775,-4.955h1.777c0.233,1.855 0.973,3.427 3.998,3.427 1.946,0 3.448,-1.179 3.448,-2.947s-0.783,-2.532 -3.66,-2.99c-3.173,-0.524 -5.055,-1.506 -5.055,-4.3C2.508,1.855 4.518,0 7.373,0c3.046,0 4.887,1.506 5.204,4.3h-1.65c-0.36,-1.986 -1.439,-2.772 -3.554,-2.772 -2.094,0 -3.152,1.048 -3.152,2.532 0,1.506 0.55,2.336 3.639,2.794 3.363,0.524 5.14,1.615 5.14,4.54C13,14.014 10.779,16 7.775,16z" />
<path
android:fillColor="@color/button_not_active"
android:fillType="nonZero"
android:pathData="M0,7h16v2H0z" />
</vector>

View file

@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="@color/button_tint"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#ACA996"
android:pathData="M18.5,4L19.66,8.35L18.7,8.61C18.25,7.74 17.79,6.87 17.26,6.43C16.73,6 16.11,6 15.5,6H13V16.5C13,17 13,17.5 13.33,17.75C13.67,18 14.33,18 15,18V19H9V18C9.67,18 10.33,18 10.67,17.75C11,17.5 11,17 11,16.5V6H8.5C7.89,6 7.27,6 6.74,6.43C6.21,6.87 5.75,7.74 5.3,8.61L4.34,8.35L5.5,4H18.5Z" />
</vector>

View file

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M5,4v3h5.5v12h3V7H19V4z"/>
</vector>

View file

@ -1,11 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="19"
android:tint="@color/button_tint"
android:viewportHeight="18">
<path
android:fillColor="#ACA996"
android:fillType="nonZero"
android:pathData="M7.031,1.996H19v2H7.031zM7.031,7.996h9v2h-9zM7.031,13.996h12v2h-12zM1.997,2.985L0.001,0.99 0.99,0l2.987,2.983 -2.986,2.99L0,4.986zM1.997,14.985L0.001,12.99 0.99,12l2.987,2.983 -2.986,2.99L0,16.986z" />
</vector>

View file

@ -1,12 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="25dp"
android:height="40dp"
android:viewportWidth="25"
android:viewportHeight="40">
<path
android:pathData="M0.099,39.313l19.853,-19.121l-19.77,-19.504l4.27,-0.001l19.799,19.532l-19.826,19.094z"
android:strokeWidth="1"
android:fillColor="#000000"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
</vector>

View file

@ -1,15 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="16"
android:tint="@color/button_tint"
android:viewportHeight="19">
<path
android:fillColor="#ACA996"
android:fillType="nonZero"
android:pathData="M3,6v11h10V6h2v11a2,2 0,0 1,-2 2H3a2,2 0,0 1,-2 -2V6h2zM0,2h16v1a1,1 0,0 1,-1 1H1a1,1 0,0 1,-1 -1V2z" />
<path
android:fillColor="#ACA996"
android:fillType="nonZero"
android:pathData="M5.992,0h4a1,1 0,0 1,1 1v3h-6V1a1,1 0,0 1,1 -1zM8.992,6h2v9h-2zM4.992,6h2v9h-2z" />
</vector>

View file

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/dark_gray"/>
<corners android:radius="4dp"/>
</shape>

View file

@ -1,29 +0,0 @@
<?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="match_parent"
android:background="@android:color/white">
<ImageView
android:id="@+id/document_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/cont_desc_doc_icon"
android:focusableInTouchMode="true"
android:padding="16dp"
android:src="@drawable/ic_text_block"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/blockList"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/document_icon" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,59 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
android:layout_margin="12dp"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:textStyle="bold"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:id="@+id/title" app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="16dp" android:layout_marginTop="16dp"
app:layout_constraintTop_toTopOf="parent" android:text="@android:string/paste_as_plain_text"
app:layout_constraintEnd_toStartOf="@+id/image" android:layout_marginEnd="16dp"/>
<TextView
android:fontFamily="sans-serif-condensed-light"
android:textSize="14sp"
android:text="TextView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:id="@+id/description" android:layout_marginTop="8dp"
app:layout_constraintTop_toBottomOf="@+id/title" app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="16dp" app:layout_constraintEnd_toStartOf="@+id/image"
android:layout_marginEnd="16dp"/>
<TextView
android:fontFamily="sans-serif-medium"
android:textSize="12sp"
android:text="TextView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:id="@+id/url" android:layout_marginTop="8dp"
app:layout_constraintTop_toBottomOf="@+id/description" app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="16dp" android:layout_marginBottom="16dp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/image"
android:layout_marginEnd="16dp"/>
<ImageView
android:background="@color/dark_gray"
app:layout_constraintWidth_default="percent"
app:layout_constraintWidth_percent=".4"
android:id="@+id/image"
android:layout_width="0dp" android:layout_height="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>

View file

@ -1,50 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:tools="http://schemas.android.com/tools">
<View
android:id="@+id/borderTopBullet"
android:layout_width="match_parent"
android:layout_height="5dp"
android:layout_alignParentTop="true"
android:background="@color/design_default_color_primary"
android:visibility="invisible" />
<ImageView
android:id="@+id/btnBullet"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:contentDescription="@string/editor_toolbar"
android:padding="8dp"
app:srcCompat="@drawable/ic_edit" />
<com.agileburo.anytype.feature_editor.ui.ClearFocusEditText
android:id="@+id/textBullet"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:fontFamily="monospace"
android:layout_toEndOf="@id/btnBullet"
android:background="@android:color/transparent"
android:inputType="textMultiLine|textNoSuggestions"
tools:text="Here are some quick tips for a first-time organization member."
android:paddingTop="16dp"
android:layout_centerVertical="true"
android:paddingBottom="16dp"
android:textColor="@android:color/black"
android:textSize="16sp" />
<View
android:id="@+id/borderBottomBullet"
android:layout_width="match_parent"
android:layout_height="5dp"
android:layout_below="@id/textBullet"
android:background="@color/design_default_color_primary"
android:visibility="invisible" />
</RelativeLayout>

View file

@ -1,58 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:tools="http://schemas.android.com/tools"
android:background="@color/white">
<View
android:id="@+id/borderTopCheckBox"
android:layout_width="match_parent"
android:layout_height="5dp"
android:layout_alignParentTop="true"
android:background="@color/design_default_color_primary"
android:visibility="invisible" />
<ImageView
android:id="@+id/btnCheckboxBlock"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:contentDescription="@string/editor_toolbar"
android:padding="8dp"
app:srcCompat="@drawable/ic_edit" />
<CheckBox
android:id="@+id/checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toEndOf="@id/btnCheckboxBlock"/>
<com.agileburo.anytype.feature_editor.ui.ClearFocusEditText
android:id="@+id/textCheckBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:fontFamily="monospace"
android:layout_toEndOf="@id/checkbox"
android:background="@android:color/transparent"
android:inputType="textMultiLine|textNoSuggestions"
android:layout_centerVertical="true"
tools:text="Here are some quick tips for a first-time organization member."
android:paddingTop="16dp"
android:paddingBottom="16dp"
android:textColor="@android:color/black"
android:textSize="16sp" />
<View
android:id="@+id/borderBottomCheckBox"
android:layout_width="match_parent"
android:layout_height="5dp"
android:layout_below="@id/textCheckBox"
android:background="@color/design_default_color_primary"
android:visibility="invisible" />
</RelativeLayout>

View file

@ -1,46 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white">
<View
android:id="@+id/borderTopCode"
android:layout_width="match_parent"
android:layout_height="5dp"
android:layout_alignParentTop="true"
android:background="@color/design_default_color_primary"
android:visibility="invisible" />
<ImageView
android:id="@+id/btnCode"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:contentDescription="@string/editor_toolbar"
android:padding="8dp"
app:srcCompat="@drawable/ic_edit" />
<com.agileburo.anytype.feature_editor.ui.ClearFocusEditText
android:id="@+id/textCode"
android:layout_marginEnd="8dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toEndOf="@id/btnCode"
android:background="@drawable/rounded_rectangle_gray"
android:inputType="textMultiLine|textNoSuggestions"
android:padding="16dp"
android:textColor="#FFFF"
android:textSize="16sp"
android:typeface="monospace" />
<View
android:id="@+id/borderBottomCode"
android:layout_width="match_parent"
android:layout_height="5dp"
android:layout_below="@id/textCode"
android:background="@color/design_default_color_primary"
android:visibility="invisible" />
</RelativeLayout>

View file

@ -1,12 +0,0 @@
<?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">
<View android:background="@color/gray_background"
android:layout_margin="16dp"
android:alpha="0.5"
android:layout_width="match_parent"
android:layout_height="1dp"/>
</FrameLayout>

View file

@ -1,52 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:id="@+id/container_editable"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<View
android:id="@+id/border_top"
android:layout_width="match_parent"
android:layout_height="5dp"
android:layout_alignParentTop="true"
android:background="@color/design_default_color_primary"
android:visibility="invisible" />
<ImageView
android:id="@+id/btnEditable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:contentDescription="@string/editor_toolbar"
android:padding="8dp"
app:srcCompat="@drawable/ic_edit" />
<com.agileburo.anytype.feature_editor.ui.ClearFocusEditText
android:id="@+id/textEditable"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginStart="16dp"
android:fontFamily="monospace"
android:layout_marginEnd="16dp"
android:layout_toEndOf="@id/btnEditable"
android:background="@android:color/transparent"
android:inputType="textMultiLine|textNoSuggestions"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:textColor="@android:color/black"
android:textSize="16sp"
tools:text="Here are some quick tips for a first-time organization member." />
<View
android:id="@+id/border_bottom"
android:layout_width="match_parent"
android:layout_height="5dp"
android:layout_below="@id/textEditable"
android:background="@color/design_default_color_primary"
android:visibility="invisible" />
</RelativeLayout>

View file

@ -1,53 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:tools="http://schemas.android.com/tools">
<View
android:id="@+id/borderTopHeaderFour"
android:layout_width="match_parent"
android:layout_height="5dp"
android:layout_alignParentTop="true"
android:background="@color/design_default_color_primary"
android:visibility="invisible" />
<ImageView
android:id="@+id/btnHeaderFour"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:contentDescription="@string/editor_toolbar"
android:padding="8dp"
app:srcCompat="@drawable/ic_edit" />
<com.agileburo.anytype.feature_editor.ui.ClearFocusEditText
android:id="@+id/textHeaderFour"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_toEndOf="@id/btnHeaderFour"
android:background="@android:color/transparent"
android:inputType="textMultiLine|textNoSuggestions"
android:paddingTop="16dp"
android:paddingBottom="16dp"
android:fontFamily="monospace"
android:layout_centerVertical="true"
tools:text="Here are some quick tips for a first-time organization member."
android:textColor="@android:color/black"
android:textSize="18sp"
android:textStyle="bold" />
<View
android:id="@+id/borderBottomHeaderFour"
android:layout_width="match_parent"
android:layout_height="5dp"
android:layout_below="@id/textHeaderFour"
android:background="@color/design_default_color_primary"
android:visibility="invisible" />
</RelativeLayout>

View file

@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.agileburo.anytype.feature_editor.ui.ClearFocusEditText
android:fontFamily="monospace"
android:id="@+id/textHeaderOne"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:textSize="32sp"
android:paddingTop="16dp"
android:paddingBottom="16dp"
android:hint="Untitled"
android:textColor="@android:color/black"
android:inputType="textMultiLine|textNoSuggestions"
android:background="@android:color/transparent"
android:textStyle="bold"/>
</RelativeLayout>

View file

@ -1,52 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:tools="http://schemas.android.com/tools">
<View
android:id="@+id/borderTopHeaderThree"
android:layout_width="match_parent"
android:layout_height="5dp"
android:layout_alignParentTop="true"
android:background="@color/design_default_color_primary"
android:visibility="invisible" />
<ImageView
android:id="@+id/btnHeaderThree"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:padding="8dp"
android:contentDescription="@string/editor_toolbar"
app:srcCompat="@drawable/ic_edit"/>
<com.agileburo.anytype.feature_editor.ui.ClearFocusEditText
android:id="@+id/textHeaderThree"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:fontFamily="monospace"
android:layout_toEndOf="@id/btnHeaderThree"
android:inputType="textMultiLine|textNoSuggestions"
android:paddingTop="16dp"
android:paddingBottom="16dp"
tools:text="Here are some quick tips for a first-time organization member."
android:layout_centerVertical="true"
android:textColor="@android:color/black"
android:background="@android:color/transparent"
android:textSize="22sp"
android:textStyle="bold" />
<View
android:id="@+id/borderBottomHeaderThree"
android:layout_width="match_parent"
android:layout_height="5dp"
android:layout_below="@id/textHeaderThree"
android:background="@color/design_default_color_primary"
android:visibility="invisible" />
</RelativeLayout>

View file

@ -1,52 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:tools="http://schemas.android.com/tools">
<View
android:id="@+id/borderTopHeaderTwo"
android:layout_width="match_parent"
android:layout_height="5dp"
android:layout_alignParentTop="true"
android:background="@color/design_default_color_primary"
android:visibility="invisible" />
<ImageView
android:id="@+id/btnHeaderTwo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:padding="8dp"
android:contentDescription="@string/editor_toolbar"
app:srcCompat="@drawable/ic_edit"/>
<com.agileburo.anytype.feature_editor.ui.ClearFocusEditText
android:id="@+id/textHeaderTwo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_toEndOf="@id/btnHeaderTwo"
android:textSize="24sp"
android:paddingTop="16dp"
android:fontFamily="monospace"
android:paddingBottom="16dp"
tools:text="Here are some quick tips for a first-time organization member."
android:layout_centerVertical="true"
android:textColor="@android:color/black"
android:inputType="textMultiLine|textNoSuggestions"
android:background="@android:color/transparent"
android:textStyle="bold" />
<View
android:id="@+id/borderBottomHeaderTwo"
android:layout_width="match_parent"
android:layout_height="5dp"
android:layout_below="@id/textHeaderTwo"
android:background="@color/design_default_color_primary"
android:visibility="invisible" />
</RelativeLayout>

Some files were not shown because too many files have changed in this diff Show more