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

add feature_editor module and move editor logic inside

This commit is contained in:
Ivanov Konstantin 2019-03-18 23:01:48 +03:00
parent 81747aa96e
commit e198e6e656
46 changed files with 1041 additions and 84 deletions

View file

@ -46,11 +46,12 @@ android {
}
ext {
android_compat_version = '28.0.0'
android_compat_version = '1.0.0-beta01'
}
dependencies {
implementation project(':feature_login')
implementation project(':feature_editor')
implementation project(':core_utils')
def applicationDependencies = rootProject.ext.mainApplication
@ -64,28 +65,18 @@ dependencies {
//Application dependencies
implementation applicationDependencies.kotlin
implementation applicationDependencies.navigation
implementation applicationDependencies.navigationUi
implementation "com.android.support:appcompat-v7:$android_compat_version"
implementation "com.android.support:design:$android_compat_version"
implementation "com.android.support:support-v4:$android_compat_version"
implementation "com.android.support:support-annotations:$android_compat_version"
implementation 'com.android.support:recyclerview-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation applicationDependencies.appcompat
implementation applicationDependencies.design
implementation applicationDependencies.recyclerView
implementation applicationDependencies.constraintLayout
implementation applicationDependencies.glide
implementation applicationDependencies.dagger
implementation applicationDependencies.timber
//Redactor
implementation 'com.ebolo:krichtexteditor:0.0.5'
//Unit/Integration tests dependencies
testImplementation unitTestDependencies.junit
//Acceptance tests dependencies
implementation project(':domain')
}

View file

@ -2,7 +2,7 @@ package com.agileburo.anytype
import android.content.Context
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.Navigation
import androidx.navigation.ui.NavigationUI
import com.agileburo.anytype.di.app.MainScreenComponent

View file

@ -2,6 +2,7 @@ package com.agileburo.anytype.di.app
import android.content.Context
import com.agileburo.anytype.AndroidApplication
import com.agileburo.anytype.feature_editor.EditorComponent
import dagger.Component
import javax.inject.Singleton
@ -11,6 +12,8 @@ interface ApplicationComponent {
fun inject(app: AndroidApplication)
fun mainScreenComponent(): MainScreenComponent
fun editorComponent(): EditorComponent
fun context(): Context
}

View file

@ -0,0 +1,25 @@
package com.agileburo.anytype.ui
import android.content.Context
import com.agileburo.anytype.AndroidApplication
import com.agileburo.anytype.feature_editor.ui.EditorFragment
class DocumentFragment : EditorFragment() {
private val appComponent by lazy {
(requireActivity().application as AndroidApplication).applicationComponent
}
private val editorComponent by lazy {
appComponent.editorComponent()
}
override fun onAttach(context: Context?) {
editorComponent.inject(this)
super.onAttach(context)
}
override fun inject() {
editorComponent.inject(this)
}
}

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<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:id="@+id/container"
@ -7,11 +7,10 @@
android:layout_height="match_parent"
tools:context=".MainActivity">
<android.support.design.widget.BottomNavigationView
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottomNavigationView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@color/light_blue_500"
app:itemIconTint="@color/colorAccent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
@ -30,4 +29,4 @@
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/main_navigation" />
</android.support.constraint.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<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">
<com.agileburo.anytype.ui.EditorToolbar
<com.agileburo.anytype.feature_editor.ui.EditorToolbar
android:id="@+id/editorToolbar"
android:layout_width="0dp"
android:layout_height="56dp"
@ -25,9 +25,8 @@
android:imeOptions="actionGo"
android:hint="Введите текст"
android:inputType="textMultiLine"
android:textColorHint="@color/gray_material"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/editorToolbar" />
</android.support.constraint.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<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"
@ -14,4 +14,4 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,12 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
android:background="@color/white"
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/pageRecycler"
android:layout_width="0dp"
android:layout_height="0dp" app:layout_constraintEnd_toEndOf="parent"
@ -15,4 +14,4 @@
app:layout_constraintBottom_toBottomOf="parent" android:layout_marginTop="8dp"
app:layout_constraintTop_toTopOf="parent"/>
</android.support.constraint.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -6,7 +6,6 @@
<View android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="4dp"
android:alpha="0.3"
android:background="@color/blue"/>
android:alpha="0.3" />
</FrameLayout>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<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"
@ -19,7 +19,6 @@
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:text="Bold"
android:textColor="@color/white"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/btnItalic"
@ -39,7 +38,6 @@
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:text="Italic"
android:textColor="@color/white"
android:textStyle="italic"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/btnStrike"
@ -58,10 +56,9 @@
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:text="Strikethrough"
android:textColor="@color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/btnItalic"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -7,7 +7,7 @@
android:title="Docs" />
<item
android:id="@+id/fragmentB"
android:id="@+id/documentFragment"
android:icon="@drawable/ic_profile"
android:title="Profile" />

View file

@ -1,32 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation 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/main_navigation"
app:startDestination="@id/fragmentA">
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main_navigation"
app:startDestination="@id/documentFragment">
<fragment
android:id="@+id/loginFragment2"
android:name="com.agileburo.anytype.feature_login.ui.login.LoginFragment"
android:label="LoginFragment"/>
<fragment
android:id="@+id/fragmentA"
android:name="com.agileburo.anytype.FragmentA"
android:label="FragmentA"
tools:layout="@layout/fragment_a"/>
<fragment
android:id="@+id/fragmentB"
android:name="com.agileburo.anytype.FragmentB"
android:label="FragmentB"
tools:layout="@layout/fragment_b"/>
<fragment
android:id="@+id/fragmentC"
android:name="com.agileburo.anytype.FragmentC"
android:label="FragmentC"
tools:layout="@layout/fragment_c"/>
android:id="@+id/documentFragment"
android:name="com.agileburo.anytype.ui.DocumentFragment"
android:label="DocumentFragment" />
<fragment android:id="@+id/pageFragment"
android:name="com.agileburo.anytype.PageFragment"
android:label="PageFragment"
tools:layout="@layout/fragment_page"/>
</navigation>

View file

@ -0,0 +1,7 @@
package com.agileburo.anytype.core_utils
fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // 'this' corresponds to the list
this[index1] = this[index2]
this[index2] = tmp
}

View file

@ -29,6 +29,8 @@ ext {
retrofit_version = '2.3.0'
okhttp_logging_interceptor_version = '3.8.1'
timber_version = '4.7.1'
rxjava2_version = '2.1.1'
moshi_version = '1.8.0'
//Unit Testing
robolectric_version = '3.8'
@ -46,10 +48,11 @@ ext {
mainApplication = [
kotlin: "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version",
coroutines: "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version",
androidxCore: "androidx.core:core-ktx:$androidx_core_version",
androidxCore: "androidx.core:core-ktx:$androidx_core_version",
navigation: "android.arch.navigation:navigation-fragment-ktx:$navigation_version",
navigationUi: "android.arch.navigation:navigation-ui-ktx:$navigation_version",
viewModel: "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version",
viewModelExtesions: "androidx.lifecycle:lifecycle-extensions:$lifecycle_version",
lifecycleCompiler: "androidx.lifecycle:lifecycle-compiler:$lifecycle_version",
appcompat: "androidx.appcompat:appcompat:$appcompat_version",
constraintLayout: "androidx.constraintlayout:constraintlayout:$constraintLayout_version",
@ -66,7 +69,10 @@ ext {
javaxInject: "javax.inject:javax.inject:$javaxInject_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"
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"
]
unitTesting = [

1
feature_editor/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

View file

@ -0,0 +1,48 @@
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"]
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
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
kapt applicationDependencies.daggerCompiler
compileOnly applicationDependencies.javaxInject
implementation applicationDependencies.kotlin
implementation applicationDependencies.viewModel
implementation applicationDependencies.viewModelExtesions
implementation applicationDependencies.appcompat
implementation applicationDependencies.constraintLayout
implementation applicationDependencies.recyclerView
implementation applicationDependencies.rxjava2
implementation applicationDependencies.rxAndroid
implementation applicationDependencies.dagger
implementation applicationDependencies.timber
testImplementation unitTestDependencies.junit
}

21
feature_editor/proguard-rules.pro vendored Normal file
View file

@ -0,0 +1,21 @@
# 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

@ -0,0 +1,26 @@
package com.agileburo.anytype.feature_editor;
import android.content.Context;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
assertEquals("com.agileburo.anytype.feature_editor.test", appContext.getPackageName());
}
}

View file

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

View file

@ -0,0 +1,77 @@
{
"blocks": [
{
"id": "1cb83d95-7913-5c2e-99e8-1272110ab38f",
"parentId": "",
"type": 3,
"contentType": 3,
"content": "{\"text\":\"Заголовок\",\"marks\":[]}",
"children": []
},
{
"id": "02ca4410-2cff-5978-81e5-09dc76dd003c",
"parentId": "",
"type": 3,
"contentType": 1,
"content": "{\"text\":\"первый элемент\",\"marks\":[]}",
"children": []
},
{
"id": "116271cc-c6e3-5c58-8598-9342567b9a66",
"parentId": "",
"type": 3,
"contentType": 1,
"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": 1,
"content": "{\"text\":\"чайлд третьего\",\"marks\":[]}",
"children": []
},
{
"id": "92152a56-40d3-53ec-a1fa-ec4e4d92b0ec",
"parentId": "93f8810e-590f-5962-a662-2c1fe17e5cbe",
"type": 3,
"contentType": 1,
"content": "{\"text\":\"третий элемент\",\"marks\":[]}",
"children": []
}
]
},
{
"id": "21fe093a-5f74-583b-9c17-ee798e1bfc7e",
"parentId": "",
"type": 3,
"contentType": 1,
"content": "{\"text\":\"четвертый элемент\",\"marks\":[]}",
"children": []
},
{
"id": "f0c40441-2a3f-5150-bad3-e0d43c72a997",
"parentId": "",
"type": 3,
"contentType": 1,
"content": "{\"text\":\"\",\"marks\":[]}",
"children": []
},
{
"id": "01f2a219-94ba-5bd6-a48e-5b9af61e1a10",
"parentId": "",
"type": 3,
"contentType": 1,
"content": "{\"text\":\"\",\"marks\":[]}",
"children": []
}
]
}

View file

@ -0,0 +1,39 @@
package com.agileburo.anytype.feature_editor
import android.content.Context
import com.agileburo.anytype.core_utils.di.PerFeature
import com.agileburo.anytype.feature_editor.data.EditorRepo
import com.agileburo.anytype.feature_editor.data.EditorRepoImpl
import com.agileburo.anytype.feature_editor.domain.EditorInteractor
import com.agileburo.anytype.feature_editor.domain.EditorInteractorImpl
import com.agileburo.anytype.feature_editor.presentation.EditorViewModelFactory
import com.agileburo.anytype.feature_editor.ui.EditorFragment
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 provideRepo(context: Context): EditorRepo = EditorRepoImpl(context = context)
@Provides
@PerFeature
fun provideInteractor(repo: EditorRepo): EditorInteractor = EditorInteractorImpl(repo = repo)
@Provides
@PerFeature
fun provideFactory(interactor: EditorInteractor): EditorViewModelFactory =
EditorViewModelFactory(interactor)
}

View file

@ -0,0 +1,57 @@
package com.agileburo.anytype.feature_editor.data
import android.content.Context
import com.agileburo.anytype.feature_editor.domain.Block
import com.agileburo.anytype.feature_editor.domain.toBlockType
import com.agileburo.anytype.feature_editor.domain.toContentType
import io.reactivex.Single
import io.reactivex.SingleEmitter
import org.json.JSONException
import org.json.JSONObject
import javax.inject.Inject
interface EditorRepo {
fun getBlocks(): Single<List<Block>>
}
class EditorRepoImpl @Inject constructor(
private val context: Context
) : EditorRepo {
override fun getBlocks(): Single<List<Block>> {
return Single.create<List<Block>> { emitter: SingleEmitter<List<Block>> ->
try {
val json = context.assets.open("test.json").bufferedReader().use {
it.readText()
}
val jsonObject = JSONObject(json)
val blocks = jsonObject.getJSONArray("blocks")?.let {
0.until(it.length()).map { i -> it.optJSONObject(i) }
.map {jsonObject -> fromJson(jsonObject) }
}?.toList()
emitter.onSuccess(blocks ?: emptyList())
} catch (e: Exception) {
emitter.onError(e)
}
}
}
}
fun fromJson(jsonObject: JSONObject): Block = with(jsonObject) {
var block: Block? = null
try {
block = Block(
id = getString("id"),
parentId = getString("parentId"),
content = getString("content"),
contentType = getInt("contentType").toContentType(),
type = getInt("type").toBlockType()
)
} catch (e: JSONException) {
e.printStackTrace()
}
return@with block ?: Block()
}

View file

@ -0,0 +1,92 @@
package com.agileburo.anytype.feature_editor.domain
import org.json.JSONException
import org.json.JSONObject
/**
* 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 Div : BlockType()
object YouTube : BlockType()
object Image : BlockType()
object Page : BlockType()
object NewPage : BlockType()
object BookMark : BlockType()
object File : BlockType()
}
sealed class ContentType {
object P : ContentType()
object Code : ContentType()
object H1 : ContentType()
object H2 : ContentType()
object H3 : ContentType()
object OL : ContentType()
object UL : ContentType()
object HL : ContentType()
object Toggle : ContentType()
object Check : ContentType()
object H4 : ContentType()
}
data class Block(
val id: String = "",
val parentId: String = "",
val type: BlockType = BlockType.Editable,
val contentType: ContentType = ContentType.H1,
val content: String = "",
val children: List<Block> = emptyList()
)
fun Block.fromJson(jsonObject: JSONObject): Block? = with(jsonObject) {
var block: Block? = null
try {
block = Block(
id = getString("id"),
parentId = getString("parentId"),
content = getString("content"),
contentType = getInt("contentType").toContentType(),
type = getInt("type").toBlockType()
)
} catch (e: JSONException) {
e.printStackTrace()
}
return@with block
}
fun Int.toContentType(): ContentType =
when (this) {
1 -> ContentType.P
2 -> ContentType.Code
3 -> ContentType.H1
4 -> ContentType.H2
5 -> ContentType.H3
6 -> ContentType.OL
7 -> ContentType.UL
8 -> ContentType.HL
9 -> ContentType.Toggle
10 -> ContentType.Check
11 -> ContentType.H4
else -> ContentType.H1
}
fun Int.toBlockType(): BlockType =
when (this) {
1 -> BlockType.HrGrid
2 -> BlockType.VrGrid
3 -> BlockType.Editable
4 -> BlockType.Div
5 -> BlockType.YouTube
6 -> BlockType.Image
7 -> BlockType.Page
8 -> BlockType.NewPage
9 -> BlockType.BookMark
10 -> BlockType.File
else -> BlockType.Editable
}

View file

@ -0,0 +1,19 @@
package com.agileburo.anytype.feature_editor.domain
sealed class Content {
data class Text(
val text : CharSequence,
val marks : List<Mark>
) : Content()
}
data class Mark(
val start : Int,
val end : Int,
val type : MarkType,
val param : Any
) {
enum class MarkType {
BOLD, ITALIC, UNDERLINE, STRIKE_THROUGH
}
}

View file

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

View file

@ -0,0 +1,33 @@
package com.agileburo.anytype.feature_editor.presentation
import androidx.lifecycle.ViewModel
import com.agileburo.anytype.feature_editor.domain.Block
import com.agileburo.anytype.feature_editor.domain.EditorInteractor
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers
import timber.log.Timber
class EditorViewModel(private val interactor: EditorInteractor) : ViewModel() {
val disposable = CompositeDisposable()
fun getBlocks() {
disposable.addAll(
interactor.getBlocks()
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe({t: List<Block>? ->
Timber.d("Get blocks success : $t")
},
{t: Throwable? ->
Timber.d("Get blocks error : $t")
})
)
}
override fun onCleared() {
disposable.clear()
super.onCleared()
}
}

View file

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

View file

@ -0,0 +1,20 @@
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 update draw state.
override fun updateDrawState(textPaint: TextPaint) {
super.updateDrawState(textPaint)
textPaint.bgColor = backgroundColor
}
}

View file

@ -0,0 +1,31 @@
package com.agileburo.anytype.feature_editor.ui
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.ItemTouchHelper.DOWN
import androidx.recyclerview.widget.ItemTouchHelper.UP
import androidx.recyclerview.widget.RecyclerView
class DragAndDropBehavior(private val adapter: EditorAdapter) : ItemTouchHelper.Callback() {
override fun getMovementFlags(p0: RecyclerView, p1: RecyclerView.ViewHolder): Int {
return makeMovementFlags(UP or DOWN, 0)
}
override fun onMove(
recyclerView: androidx.recyclerview.widget.RecyclerView,
viewHolder: androidx.recyclerview.widget.RecyclerView.ViewHolder,
target: androidx.recyclerview.widget.RecyclerView.ViewHolder
): Boolean {
return adapter.onItemMoved(viewHolder.adapterPosition, target.adapterPosition)
}
override fun isLongPressDragEnabled(): Boolean {
return true
}
override fun isItemViewSwipeEnabled(): Boolean {
return false
}
override fun onSwiped(p0: androidx.recyclerview.widget.RecyclerView.ViewHolder, p1: Int) {}
}

View file

@ -0,0 +1,36 @@
package com.agileburo.anytype.feature_editor.ui
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.agileburo.anytype.core_utils.swap
import com.agileburo.anytype.feature_editor.domain.Block
class EditorAdapter(private val blocks: MutableList<Block>): RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun getItemCount() = blocks.size
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
fun onItemMoved(fromPosition: Int, toPosition: Int): Boolean {
swapPosition(fromPosition, toPosition)
notifyItemMoved(fromPosition, toPosition)
return true
}
private fun swapPosition(fromPosition: Int, toPosition: Int) {
blocks.swap(fromPosition, toPosition)
}
sealed class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
class TextHolder(itemView: View) : ViewHolder(itemView)
}
}

View file

@ -0,0 +1,36 @@
package com.agileburo.anytype.feature_editor.ui
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 com.agileburo.anytype.feature_editor.R
import com.agileburo.anytype.feature_editor.presentation.EditorViewModel
import com.agileburo.anytype.feature_editor.presentation.EditorViewModelFactory
import javax.inject.Inject
abstract class EditorFragment: Fragment(){
@Inject
lateinit var factory: EditorViewModelFactory
private val viewModel by lazy {
ViewModelProviders.of(this, factory).get(EditorViewModel::class.java)
}
abstract fun inject()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_editor, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.getBlocks()
}
}

View file

@ -0,0 +1,93 @@
package com.agileburo.anytype.feature_editor.ui
import android.graphics.Typeface
import android.text.*
import android.text.style.CharacterStyle
import android.text.style.StrikethroughSpan
import android.text.style.StyleSpan
import android.text.style.UnderlineSpan
class EditorTextWatcher(
private val codeBlockTypeface : Typeface
) : TextWatcher {
private var spannableText: SpannableStringBuilder? = null
private var spanBold: StyleSpan? = null
private var spanItalic: StyleSpan? = null
private var spanStrike: StrikethroughSpan? = null
private var spanUnderline : UnderlineSpan? = null
private var spanCodeBlock : CodeBlockSpan? = null
var isBoldActive = false
var isItalicActive = false
var isStrokeThroughActive = false
var isUnderlineActive = false
var isCodeBlockActive = false
override fun afterTextChanged(s: Editable?) {
spannableText?.let { spannable ->
spanBold?.let { span ->
s?.setSpanWithCheck(spannable.getSpanStart(span), spannable.getSpanEnd(span), span)
}
spanItalic?.let { span ->
s?.setSpanWithCheck(spannable.getSpanStart(span), spannable.getSpanEnd(span), span)
}
spanStrike?.let { span ->
s?.setSpanWithCheck(spannable.getSpanStart(span), spannable.getSpanEnd(span), span)
}
spanUnderline?.let { span ->
s?.setSpanWithCheck(spannable.getSpanStart(span), spannable.getSpanEnd(span), span)
}
spanCodeBlock?.let { span ->
s?.setSpanWithCheck(spannable.getSpanStart(span), spannable.getSpanEnd(span), span)
}
}
spannableText = null
clearSpans()
}
private fun clearSpans() {
spanBold = null
spanItalic = null
spanStrike = null
spanUnderline = null
spanCodeBlock = null
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
spannableText = SpannableStringBuilder(s).apply {
if (isBoldActive) {
spanBold = StyleSpan(Typeface.BOLD)
setSpan(spanBold, start, start + count, Spanned.SPAN_COMPOSING)
}
if (isItalicActive) {
spanItalic = StyleSpan(Typeface.ITALIC)
setSpan(spanItalic, start, start + count, Spanned.SPAN_COMPOSING)
}
if (isStrokeThroughActive) {
spanStrike = StrikethroughSpan()
setSpan(spanStrike, start, start + count, Spanned.SPAN_COMPOSING)
}
if (isUnderlineActive) {
spanUnderline = UnderlineSpan()
setSpan(spanUnderline, start, start + count, Spanned.SPAN_COMPOSING)
}
if (isCodeBlockActive) {
spanCodeBlock = CodeBlockSpan(codeBlockTypeface)
setSpan(spanCodeBlock, start, start + count, Spanned.SPAN_COMPOSING)
}
}
}
}
fun Editable.setSpanWithCheck(start: Int, end: Int, span: CharacterStyle) {
if (start > -1 && end > -1) {
this.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
}
}

View file

@ -0,0 +1,69 @@
package com.agileburo.anytype.feature_editor.ui
import android.content.Context
import androidx.constraintlayout.widget.ConstraintLayout
import android.util.AttributeSet
import android.view.View
import android.widget.ImageView
import com.agileburo.anytype.feature_editor.R
import kotlinx.android.synthetic.main.view_anytype_editor_toolbar.view.*
class EditorToolbar : ConstraintLayout {
private lateinit var btnBold: ImageView
private lateinit var btnItalic: ImageView
private lateinit var btnStrokeThrough: ImageView
constructor(context: Context) : super(context) {
initialize(context, null)
}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
initialize(context, attrs)
}
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) :
super(context, attrs, defStyleAttr) {
initialize(context, attrs)
}
private fun initialize(context: Context, attrs: AttributeSet?) {
View.inflate(context, R.layout.view_anytype_editor_toolbar, this)
btnBold = findViewById(R.id.btnBold)
btnItalic = findViewById(R.id.btnItalic)
btnStrokeThrough = findViewById(R.id.btnStroke)
}
fun setMainActions(
boldClick: (Boolean) -> Unit,
italicClick: (Boolean) -> Unit,
strokeClick: (Boolean) -> Unit,
underlineClick : (Boolean) -> Unit,
codeBlockClick : (Boolean) -> Unit
) {
btnBold.setOnClickListener {
it.isSelected = !it.isSelected
boldClick(it.isSelected)
}
btnItalic.setOnClickListener {
it.isSelected = !it.isSelected
italicClick(it.isSelected)
}
btnStrokeThrough.setOnClickListener {
it.isSelected = !it.isSelected
strokeClick(it.isSelected)
}
underline.setOnClickListener { button ->
button.isSelected = !button.isSelected
underlineClick(button.isSelected)
}
codeBlock.setOnClickListener { button ->
button.isSelected = !button.isSelected
codeBlockClick(button.isSelected)
}
}
}

View file

@ -0,0 +1,23 @@
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

@ -0,0 +1,24 @@
<?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/holo_blue_dark">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:text="ДОКУМЕНТ"
android:textColor="@android:color/background_light"
android:textSize="30sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,79 @@
<?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="56dp"
android:background="#393834"
android:elevation="4dp">
<ImageView
android:id="@+id/btnBold"
android:layout_width="32dp"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:contentDescription="@string/editor_toolbar"
android:scaleType="center"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_editor_toolbar_bold" />
<ImageView
android:id="@+id/btnItalic"
android:layout_width="32dp"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:contentDescription="@string/editor_toolbar"
android:scaleType="center"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/btnBold"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_editor_toolbar_italic" />
<ImageView
android:id="@+id/btnStroke"
android:layout_width="32dp"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:contentDescription="@string/editor_toolbar"
android:scaleType="center"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/btnItalic"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_editor_toolbar_strike_through" />
<TextView
android:gravity="center"
android:textColor="@color/button_tint"
android:text="U"
android:textSize="28dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/underline" android:layout_marginTop="8dp"
app:layout_constraintTop_toTopOf="parent" android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toEndOf="@+id/btnStroke"
android:layout_marginStart="32dp"/>
<TextView
android:gravity="center"
android:textColor="@color/button_tint"
android:text="Code"
android:textSize="24dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/codeBlock"
android:layout_marginTop="8dp"
app:layout_constraintTop_toTopOf="parent" android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toEndOf="@+id/underline"
android:layout_marginStart="32dp"/>
<View
android:id="@+id/view"
android:layout_width="1dp"
android:layout_height="0dp"
android:background="#6a6962"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="0.0"
app:layout_constraintStart_toEndOf="@+id/codeBlock" android:layout_marginStart="32dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,77 @@
{
"blocks": [
{
"id": "1cb83d95-7913-5c2e-99e8-1272110ab38f",
"parentId": "",
"type": 3,
"contentType": 3,
"content": "{\"text\":\"Заголовок\",\"marks\":[]}",
"children": []
},
{
"id": "02ca4410-2cff-5978-81e5-09dc76dd003c",
"parentId": "",
"type": 3,
"contentType": 1,
"content": "{\"text\":\"первый элемент\",\"marks\":[]}",
"children": []
},
{
"id": "116271cc-c6e3-5c58-8598-9342567b9a66",
"parentId": "",
"type": 3,
"contentType": 1,
"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": 1,
"content": "{\"text\":\"чайлд третьего\",\"marks\":[]}",
"children": []
},
{
"id": "92152a56-40d3-53ec-a1fa-ec4e4d92b0ec",
"parentId": "93f8810e-590f-5962-a662-2c1fe17e5cbe",
"type": 3,
"contentType": 1,
"content": "{\"text\":\"третий элемент\",\"marks\":[]}",
"children": []
}
]
},
{
"id": "21fe093a-5f74-583b-9c17-ee798e1bfc7e",
"parentId": "",
"type": 3,
"contentType": 1,
"content": "{\"text\":\"четвертый элемент\",\"marks\":[]}",
"children": []
},
{
"id": "f0c40441-2a3f-5150-bad3-e0d43c72a997",
"parentId": "",
"type": 3,
"contentType": 1,
"content": "{\"text\":\"\",\"marks\":[]}",
"children": []
},
{
"id": "01f2a219-94ba-5bd6-a48e-5b9af61e1a10",
"parentId": "",
"type": 3,
"contentType": 1,
"content": "{\"text\":\"\",\"marks\":[]}",
"children": []
}
]
}

View file

@ -0,0 +1,3 @@
<resources>
<string name="app_name">Editor</string>
</resources>

View file

@ -0,0 +1,17 @@
package com.agileburo.anytype.feature_editor;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
}

View file

@ -13,10 +13,10 @@ android {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation 'android.arch.lifecycle:extensions:1.1.1'
implementation 'androidx.appcompat:appcompat:1.0.0-beta01'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0-beta01'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
androidTestImplementation 'androidx.test:runner:1.1.0-alpha4'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0-alpha4'
}

View file

@ -1,7 +1,7 @@
package com.agileburo.anytype.feature_login
import android.support.test.InstrumentationRegistry
import android.support.test.runner.AndroidJUnit4
import androidx.test.InstrumentationRegistry
import androidx.test.runner.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith

View file

@ -1,14 +1,14 @@
package com.agileburo.anytype.feature_login.ui.login
import android.arch.lifecycle.ViewModelProviders
import androidx.lifecycle.ViewModelProviders
import android.os.Bundle
import android.support.v4.app.Fragment
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.agileburo.anytype.feature_login.R
class LoginFragment : Fragment() {
class LoginFragment : androidx.fragment.app.Fragment() {
companion object {
fun newInstance() = LoginFragment()

View file

@ -1,6 +1,6 @@
package com.agileburo.anytype.feature_login.ui.login
import android.arch.lifecycle.ViewModel
import androidx.lifecycle.ViewModel
class LoginViewModel : ViewModel() {
// TODO: Implement the ViewModel

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<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:id="@+id/login"
@ -17,4 +17,4 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -14,8 +14,8 @@ org.gradle.jvmargs=-Xmx1536m
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=false
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=false
android.enableJetifier=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official

View file

@ -1,4 +1,3 @@
include ':app',
include ':app', ':feature_editor',
':feature_login',
':core_utils',
':domain'
':core_utils'