Clean up much of the Kotlin usage and remove RxJava

This commit is contained in:
Billy Brawner 2019-08-17 13:23:26 -05:00
parent 2899feb7bc
commit 1a3d67ef58
11 changed files with 277 additions and 381 deletions

View file

@ -39,7 +39,6 @@ android {
applicationId "com.wbrawner.simplemarkdown"
minSdkVersion 21
targetSdkVersion 28
multiDexEnabled true
versionCode 20
versionName "0.7.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@ -75,7 +74,6 @@ android {
}
dependencies {
def lifecycle_version = "2.0.0"
testImplementation 'junit:junit:4.12'
testImplementation 'org.robolectric:robolectric:4.2'
implementation fileTree(include: ['*.jar'], dir: 'libs')
@ -95,18 +93,14 @@ dependencies {
annotationProcessor 'com.google.dagger:dagger-compiler:2.22.1'
kapt 'com.google.dagger:dagger-android-processor:2.22.1'
kapt 'com.google.dagger:dagger-compiler:2.22.1'
implementation 'com.jakewharton.rxbinding2:rxbinding:2.0.0'
implementation 'com.jakewharton.rxbinding2:rxbinding-design:2.0.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
implementation 'io.reactivex.rxjava2:rxjava:2.2.7'
implementation 'com.google.firebase:firebase-core:17.0.1'
implementation 'com.android.billingclient:billing:1.2'
implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1'
implementation 'androidx.multidex:multidex:2.0.1'
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
implementation "androidx.core:core-ktx:1.0.2"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
def coroutines_version = "1.3.0-RC2"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
implementation 'eu.crydee:syllable-counter:4.0.2'
}

View file

@ -3,37 +3,37 @@ package com.wbrawner.simplemarkdown.presentation
import android.content.Context
import android.net.Uri
import com.wbrawner.simplemarkdown.view.MarkdownEditView
import com.wbrawner.simplemarkdown.view.MarkdownPreviewView
import java.io.InputStream
import java.io.OutputStream
interface MarkdownPresenter {
var fileName: String
var markdown: String
fun loadFromUri(context: Context, fileUri: Uri)
fun loadMarkdown(
var editView: MarkdownEditView?
var previewView: MarkdownPreviewView?
suspend fun loadFromUri(context: Context, fileUri: Uri): String?
suspend fun loadMarkdown(
fileName: String,
`in`: InputStream,
listener: FileLoadedListener? = null,
replaceCurrentFile: Boolean = true
)
): String?
fun newFile(newName: String)
fun setEditView(editView: MarkdownEditView)
fun setPreviewView(previewView: MarkdownPreviewView)
fun saveMarkdown(listener: MarkdownSavedListener, name: String, outputStream: OutputStream)
suspend fun saveMarkdown(name: String, outputStream: OutputStream): Boolean
fun onMarkdownEdited(markdown: String? = null)
fun generateHTML(markdown: String? = null): String
interface FileLoadedListener {
fun onSuccess(markdown: String)
fun onError()
}
interface MarkdownSavedListener {
fun saveComplete(success: Boolean)
}
fun generateHTML(markdown: String = ""): String
}
interface MarkdownEditView {
var markdown: String
fun setTitle(title: String)
fun onFileSaved(success: Boolean)
fun onFileLoaded(success: Boolean)
}
interface MarkdownPreviewView {
fun updatePreview(html: String)
}

View file

@ -2,163 +2,126 @@ package com.wbrawner.simplemarkdown.presentation
import android.content.Context
import android.net.Uri
import android.os.Handler
import android.provider.OpenableColumns
import com.commonsware.cwac.anddown.AndDown
import com.wbrawner.simplemarkdown.model.MarkdownFile
import com.wbrawner.simplemarkdown.utility.ErrorHandler
import com.wbrawner.simplemarkdown.view.MarkdownEditView
import com.wbrawner.simplemarkdown.view.MarkdownPreviewView
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.InputStream
import java.io.OutputStream
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class MarkdownPresenterImpl @Inject
constructor(private val errorHandler: ErrorHandler) : MarkdownPresenter {
private val fileLock = Any()
private var file: MarkdownFile? = null
class MarkdownPresenterImpl @Inject constructor(private val errorHandler: ErrorHandler) : MarkdownPresenter {
@Volatile
private var editView: MarkdownEditView? = null
private var file: MarkdownFile = MarkdownFile()
@Volatile
private var previewView: MarkdownPreviewView? = null
private val fileHandler = Handler()
override var editView: MarkdownEditView? = null
set(value) {
field = value
onMarkdownEdited(null)
}
@Volatile
override var previewView: MarkdownPreviewView? = null
override var fileName: String
get() = synchronized(fileLock) {
return file!!.name
}
set(name) = synchronized(fileLock) {
file!!.name = name
get() = file.name
set(name) {
file.name = name
}
override var markdown: String
get() = synchronized(fileLock) {
return file!!.content
}
set(markdown) = synchronized(fileLock) {
file!!.content = markdown
get() = file.content
set(markdown) {
file.content = markdown
}
init {
synchronized(fileLock) {
this.file = MarkdownFile()
}
}
override fun loadMarkdown(
override suspend fun loadMarkdown(
fileName: String,
`in`: InputStream,
listener: MarkdownPresenter.FileLoadedListener?,
replaceCurrentFile: Boolean
) {
fileHandler.post {
val tmpFile = MarkdownFile()
if (tmpFile.load(fileName, `in`)) {
if (listener != null) {
val html = generateHTML(tmpFile.content)
listener.onSuccess(html)
}
if (replaceCurrentFile) {
synchronized(fileLock) {
this.file = tmpFile
val currentEditView = editView
if (currentEditView != null) {
currentEditView.onFileLoaded(true)
currentEditView.setTitle(fileName)
currentEditView.markdown = this.file!!.content
onMarkdownEdited(null)
}
}
}
} else {
listener?.onError()
): String? {
val tmpFile = MarkdownFile()
withContext(Dispatchers.IO) {
if (!tmpFile.load(fileName, `in`)) {
throw RuntimeException("Failed to load markdown")
}
}
if (replaceCurrentFile) {
this.file = tmpFile
editView?.let {
it.onFileLoaded(true)
it.setTitle(fileName)
it.markdown = file.content
onMarkdownEdited(file.content)
}
}
return generateHTML(tmpFile.content)
}
override fun newFile(newName: String) {
synchronized(fileLock) {
val currentEditView = editView
if (currentEditView != null) {
file!!.content = currentEditView.markdown
currentEditView.setTitle(newName)
currentEditView.markdown = ""
}
file = MarkdownFile(newName, "")
editView?.let {
file.content = it.markdown
it.setTitle(newName)
it.markdown = ""
}
file = MarkdownFile(newName, "")
}
override fun setEditView(editView: MarkdownEditView) {
this.editView = editView
onMarkdownEdited(null)
}
override fun setPreviewView(previewView: MarkdownPreviewView) {
this.previewView = previewView
}
override fun saveMarkdown(listener: MarkdownPresenter.MarkdownSavedListener, name: String, outputStream: OutputStream) {
val fileSaver = {
val result: Boolean
synchronized(fileLock) {
result = file!!.save(name, outputStream)
}
listener?.saveComplete(result)
val currentEditView = editView
if (currentEditView != null) {
synchronized(fileLock) {
currentEditView.setTitle(file!!.name)
}
currentEditView.onFileSaved(result)
}
override suspend fun saveMarkdown(name: String, outputStream: OutputStream): Boolean {
val result = withContext(Dispatchers.IO) {
file.save(name, outputStream)
}
fileHandler.post(fileSaver)
editView?.let {
it.setTitle(file.name)
it.onFileSaved(result)
}
return result
}
override fun onMarkdownEdited(markdown: String?) {
this.markdown = markdown ?: file?.content ?: ""
fileHandler.post {
val currentPreviewView = previewView
currentPreviewView?.updatePreview(generateHTML(null))
}
this.markdown = markdown ?: file.content
previewView?.updatePreview(generateHTML(this.markdown))
}
override fun generateHTML(markdown: String?): String {
val andDown = AndDown()
val HOEDOWN_FLAGS = AndDown.HOEDOWN_EXT_STRIKETHROUGH or AndDown.HOEDOWN_EXT_TABLES or
AndDown.HOEDOWN_EXT_UNDERLINE or AndDown.HOEDOWN_EXT_SUPERSCRIPT or
AndDown.HOEDOWN_EXT_FENCED_CODE
return andDown.markdownToHtml(markdown, HOEDOWN_FLAGS, 0)
override fun generateHTML(markdown: String): String {
return AndDown().markdownToHtml(markdown, HOEDOWN_FLAGS, 0)
}
override fun loadFromUri(context: Context, fileUri: Uri) {
try {
val `in` = context.contentResolver.openInputStream(fileUri)
override suspend fun loadFromUri(context: Context, fileUri: Uri): String? {
return try {
var fileName: String? = null
if ("content" == fileUri.scheme) {
val retCur = context.contentResolver
.query(fileUri, null, null, null, null)
if (retCur != null) {
val nameIndex = retCur
.getColumnIndex(OpenableColumns.DISPLAY_NAME)
retCur.moveToFirst()
fileName = retCur.getString(nameIndex)
retCur.close()
}
context.contentResolver
.query(
fileUri,
null,
null,
null,
null
)
?.use {
val nameIndex = it.getColumnIndex(OpenableColumns.DISPLAY_NAME)
it.moveToFirst()
fileName = it.getString(nameIndex)
}
} else if ("file" == fileUri.scheme) {
fileName = fileUri.lastPathSegment
}
if (fileName == null) {
fileName = "Untitled.md"
}
loadMarkdown(fileName, `in`!!, null, true)
val inputStream = context.contentResolver.openInputStream(fileUri) ?: return null
loadMarkdown(fileName ?: "Untitled.md", inputStream, true)
} catch (e: Exception) {
errorHandler.reportException(e)
val currentEditView = editView
currentEditView?.onFileLoaded(false)
editView?.onFileLoaded(false)
null
}
}
companion object {
const val HOEDOWN_FLAGS = AndDown.HOEDOWN_EXT_STRIKETHROUGH or AndDown.HOEDOWN_EXT_TABLES or
AndDown.HOEDOWN_EXT_UNDERLINE or AndDown.HOEDOWN_EXT_SUPERSCRIPT or
AndDown.HOEDOWN_EXT_FENCED_CODE
}
}

View file

@ -1,40 +0,0 @@
package com.wbrawner.simplemarkdown.utility;
import com.wbrawner.simplemarkdown.presentation.MarkdownPresenter;
import io.reactivex.Observable;
import io.reactivex.Observer;
import io.reactivex.disposables.Disposable;
public class MarkdownObserver implements Observer<String> {
private MarkdownPresenter presenter;
private Observable<String> obs;
public MarkdownObserver(MarkdownPresenter presenter, Observable<String> obs) {
this.presenter = presenter;
this.obs = obs;
}
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(String markdown) {
presenter.onMarkdownEdited(markdown);
}
@Override
public void onError(Throwable e) {
System.err.println("An error occurred while handling the markdown");
e.printStackTrace();
// TODO: report this?
obs.subscribe(this);
}
@Override
public void onComplete() {
}
}

View file

@ -1,59 +0,0 @@
package com.wbrawner.simplemarkdown.utility;
import android.graphics.Color;
import android.text.SpannableString;
import android.text.style.BackgroundColorSpan;
import android.util.Log;
import android.widget.EditText;
import android.widget.TextView;
import com.wbrawner.simplemarkdown.model.Readability;
import com.wbrawner.simplemarkdown.model.Sentence;
import io.reactivex.Observer;
import io.reactivex.disposables.Disposable;
public class ReadabilityObserver implements Observer<String> {
private EditText text;
private String previousValue = "";
public ReadabilityObserver(EditText text) {
this.text = text;
}
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(String markdown) {
long start = System.currentTimeMillis();
if (markdown.length() < 1) return;
if (previousValue.equals(markdown)) return;
Readability readability = new Readability(markdown);
SpannableString span = new SpannableString(markdown);
for (Sentence sentence : readability.sentences()) {
int color = Color.TRANSPARENT;
if (sentence.syllableCount() > 25) color = Color.argb(100, 229, 232, 42);
if (sentence.syllableCount() > 35) color = Color.argb(100, 193, 66, 66);
span.setSpan(new BackgroundColorSpan(color), sentence.start(), sentence.end(), 0);
}
text.setTextKeepState(span, TextView.BufferType.SPANNABLE);
previousValue = markdown;
long timeTakenMs = System.currentTimeMillis() - start;
Log.d("SimpleMarkdown", "Handled markdown in " + timeTakenMs + "ms");
}
@Override
public void onError(Throwable e) {
System.err.println("An error occurred while handling the markdown");
e.printStackTrace();
// TODO: report this?
}
@Override
public void onComplete() {
}
}

View file

@ -1,10 +0,0 @@
package com.wbrawner.simplemarkdown.view
interface MarkdownEditView {
var markdown: String
fun setTitle(title: String)
fun onFileSaved(success: Boolean)
fun onFileLoaded(success: Boolean)
}

View file

@ -1,5 +0,0 @@
package com.wbrawner.simplemarkdown.view
interface MarkdownPreviewView {
fun updatePreview(html: String)
}

View file

@ -24,19 +24,20 @@ import com.wbrawner.simplemarkdown.utility.ErrorHandler
import com.wbrawner.simplemarkdown.utility.Utils
import com.wbrawner.simplemarkdown.view.adapter.EditPagerAdapter
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.coroutines.*
import java.io.File
import java.io.FileInputStream
import java.io.InputStream
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
class MainActivity : AppCompatActivity(), ActivityCompat.OnRequestPermissionsResultCallback {
class MainActivity : AppCompatActivity(), ActivityCompat.OnRequestPermissionsResultCallback, CoroutineScope {
@Inject
lateinit var presenter: MarkdownPresenter
@Inject
lateinit var errorHandler: ErrorHandler
private var shouldAutoSave = true
private var newFileHandler: NewFileHandler? = null
override val coroutineContext: CoroutineContext = Dispatchers.Main
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -65,7 +66,9 @@ class MainActivity : AppCompatActivity(), ActivityCompat.OnRequestPermissionsRes
super.onUserLeaveHint()
if (shouldAutoSave && presenter.markdown.isNotEmpty() && Utils.isAutosaveEnabled(this)) {
presenter.saveMarkdown(null, "autosave.md", File(filesDir, "autosave.md").outputStream())
launch {
presenter.saveMarkdown("autosave.md", File(filesDir, "autosave.md").outputStream())
}
}
}
@ -130,28 +133,18 @@ class MainActivity : AppCompatActivity(), ActivityCompat.OnRequestPermissionsRes
}
}
infoIntent.putExtra("title", title)
var `in`: InputStream? = null
try {
val assetManager = assets
if (assetManager != null) {
`in` = assetManager.open(fileName)
launch {
try {
val inputStream = assets?.open(fileName)
?: throw RuntimeException("Unable to open stream to $fileName")
val html = presenter.loadMarkdown(fileName, inputStream, false)
infoIntent.putExtra("html", html)
startActivity(infoIntent)
} catch (e: Exception) {
errorHandler.reportException(e)
Toast.makeText(this@MainActivity, R.string.file_load_error, Toast.LENGTH_SHORT).show()
}
presenter.loadMarkdown(fileName, `in`, object : MarkdownPresenter.FileLoadedListener {
override fun onSuccess(html: String) {
infoIntent.putExtra("html", html)
startActivity(infoIntent)
}
override fun onError() {
Toast.makeText(this@MainActivity, R.string.file_load_error, Toast.LENGTH_SHORT)
.show()
}
}, false)
} catch (e: Exception) {
errorHandler.reportException(e)
Toast.makeText(this@MainActivity, R.string.file_load_error, Toast.LENGTH_SHORT).show()
}
}
override fun onRequestPermissionsResult(
@ -197,12 +190,13 @@ class MainActivity : AppCompatActivity(), ActivityCompat.OnRequestPermissionsRes
contentResolver.openFileDescriptor(data.data!!, "r")?.let {
val fileInput = FileInputStream(it.fileDescriptor)
presenter.loadMarkdown(fileName, fileInput)
launch {
presenter.loadMarkdown(fileName, fileInput)
}
}
}
REQUEST_SAVE_FILE -> {
if (resultCode != Activity.RESULT_OK
|| data?.data == null) {
if (resultCode != Activity.RESULT_OK || data?.data == null) {
return
}
@ -213,11 +207,14 @@ class MainActivity : AppCompatActivity(), ActivityCompat.OnRequestPermissionsRes
cursor.getString(nameIndex)
} ?: "Untitled.md"
presenter.saveMarkdown(
newFileHandler,
fileName,
contentResolver.openOutputStream(data.data!!)
)
launch {
val outputStream = contentResolver.openOutputStream(data.data!!)
?: throw RuntimeException("Unable to open output stream to save file")
presenter.saveMarkdown(
fileName,
outputStream
)
}
}
REQUEST_DARK_MODE -> recreate()
}
@ -228,11 +225,10 @@ class MainActivity : AppCompatActivity(), ActivityCompat.OnRequestPermissionsRes
AlertDialog.Builder(this)
.setTitle(R.string.save_changes)
.setMessage(R.string.prompt_save_changes)
.setNegativeButton(R.string.action_discard) { d, _ ->
.setNegativeButton(R.string.action_discard) { _, _ ->
presenter.newFile("Untitled.md")
}
.setPositiveButton(R.string.action_save) { d, _ ->
newFileHandler = NewFileHandler()
.setPositiveButton(R.string.action_save) { _, _ ->
requestFileOp(REQUEST_SAVE_FILE)
}
.create()
@ -283,17 +279,11 @@ class MainActivity : AppCompatActivity(), ActivityCompat.OnRequestPermissionsRes
shouldAutoSave = true
}
private inner class NewFileHandler : MarkdownPresenter.MarkdownSavedListener {
override fun saveComplete(success: Boolean) {
if (success) {
presenter.newFile("Untitled.md")
} else {
Toast.makeText(
this@MainActivity,
R.string.file_save_error,
Toast.LENGTH_SHORT
).show()
}
override fun onDestroy() {
super.onDestroy()
coroutineContext[Job]?.let {
cancel()
}
}
}

View file

@ -10,25 +10,28 @@ import com.wbrawner.simplemarkdown.MarkdownApplication
import com.wbrawner.simplemarkdown.R
import com.wbrawner.simplemarkdown.presentation.MarkdownPresenter
import com.wbrawner.simplemarkdown.utility.ErrorHandler
import kotlinx.coroutines.*
import java.io.File
import java.io.FileInputStream
import java.io.FileNotFoundException
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
class SplashActivity : AppCompatActivity() {
class SplashActivity : AppCompatActivity(), CoroutineScope {
override val coroutineContext: CoroutineContext = Dispatchers.Main
@Inject
internal var presenter: MarkdownPresenter? = null
lateinit var presenter: MarkdownPresenter
@Inject
internal var errorHandler: ErrorHandler? = null
lateinit var errorHandler: ErrorHandler
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
(application as MarkdownApplication).component.inject(this)
if (sharedPreferences.getBoolean(getString(R.string.error_reports_enabled), true)) {
errorHandler!!.init(this)
errorHandler.init(this)
}
val darkModeValue = sharedPreferences.getString(
@ -36,11 +39,10 @@ class SplashActivity : AppCompatActivity() {
getString(R.string.pref_value_auto)
)
var darkMode: Int
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
darkMode = AppCompatDelegate.MODE_NIGHT_AUTO
var darkMode = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
AppCompatDelegate.MODE_NIGHT_AUTO
} else {
darkMode = AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
}
if (darkModeValue != null && !darkModeValue.isEmpty()) {
@ -52,33 +54,26 @@ class SplashActivity : AppCompatActivity() {
}
AppCompatDelegate.setDefaultNightMode(darkMode)
val intent = intent
if (intent != null && intent.data != null) {
presenter!!.loadFromUri(applicationContext, intent.data)
if (intent?.data != null) {
launch {
presenter.loadFromUri(applicationContext, intent.data!!)
}
} else {
presenter!!.fileName = "Untitled.md"
presenter.fileName = "Untitled.md"
val autosave = File(filesDir, "autosave.md")
if (autosave.exists()) {
try {
val fileInputStream = FileInputStream(autosave)
presenter!!.loadMarkdown(
"Untitled.md",
fileInputStream,
object : MarkdownPresenter.FileLoadedListener {
override fun onSuccess(markdown: String) {
autosave.delete()
}
override fun onError() {
autosave.delete()
}
},
true
)
launch {
presenter.loadMarkdown(
"Untitled.md",
autosave.inputStream(),
true
)
autosave.delete()
}
} catch (ignored: FileNotFoundException) {
return
}
}
}
@ -86,4 +81,11 @@ class SplashActivity : AppCompatActivity() {
startActivity(startIntent)
finish()
}
override fun onDestroy() {
super.onDestroy()
coroutineContext[Job]?.let {
cancel()
}
}
}

View file

@ -1,72 +1,126 @@
package com.wbrawner.simplemarkdown.view.fragment
import android.annotation.SuppressLint
import android.graphics.Color
import android.os.Bundle
import android.preference.PreferenceManager
import android.text.Editable
import android.text.SpannableString
import android.text.TextWatcher
import android.text.style.BackgroundColorSpan
import android.util.Log
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.widget.EditText
import android.widget.ScrollView
import android.widget.TextView
import android.widget.Toast
import androidx.fragment.app.Fragment
import com.jakewharton.rxbinding2.widget.RxTextView
import com.wbrawner.simplemarkdown.MarkdownApplication
import com.wbrawner.simplemarkdown.R
import com.wbrawner.simplemarkdown.model.Readability
import com.wbrawner.simplemarkdown.presentation.MarkdownEditView
import com.wbrawner.simplemarkdown.presentation.MarkdownPresenter
import com.wbrawner.simplemarkdown.utility.MarkdownObserver
import com.wbrawner.simplemarkdown.utility.ReadabilityObserver
import com.wbrawner.simplemarkdown.utility.hideKeyboard
import com.wbrawner.simplemarkdown.utility.showKeyboard
import com.wbrawner.simplemarkdown.view.MarkdownEditView
import com.wbrawner.simplemarkdown.view.ViewPagerPage
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import java.util.concurrent.TimeUnit
import kotlinx.coroutines.*
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
import kotlin.math.abs
class EditFragment : Fragment(), MarkdownEditView, ViewPagerPage {
class EditFragment : Fragment(), MarkdownEditView, ViewPagerPage, CoroutineScope {
@Inject
lateinit var presenter: MarkdownPresenter
private var markdownEditor: EditText? = null
private var markdownEditorScroller: ScrollView? = null
override var markdown: String
get() = markdownEditor?.text?.toString() ?: ""
set(value) {
markdownEditor?.setText(value)
}
override val coroutineContext: CoroutineContext = Dispatchers.Main
@SuppressLint("ClickableViewAccessibility")
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
val view = inflater.inflate(R.layout.fragment_edit, container, false)
markdownEditor = view.findViewById(R.id.markdown_edit)
markdownEditorScroller = view.findViewById(R.id.markdown_edit_container)
val activity = activity
if (activity != null) {
(activity.application as MarkdownApplication).component.inject(this)
}
val obs = RxTextView.textChanges(markdownEditor!!)
.debounce(50, TimeUnit.MILLISECONDS)
.map { it.toString() }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
obs.subscribe(MarkdownObserver(presenter, obs))
val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(requireContext())
val enableReadability = sharedPrefs.getBoolean(getString(R.string.readability_enabled), false)
if (enableReadability) {
val readabilityObserver = RxTextView.textChanges(markdownEditor!!)
.debounce(250, TimeUnit.MILLISECONDS)
.map { it.toString() }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
readabilityObserver.subscribe(ReadabilityObserver(markdownEditor))
}
return view
}
savedInstanceState: Bundle?): View? =
inflater.inflate(R.layout.fragment_edit, container, false)
@SuppressLint("ClickableViewAccessibility")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
presenter.setEditView(this@EditFragment)
markdownEditor = view.findViewById(R.id.markdown_edit)
markdownEditorScroller = view.findViewById(R.id.markdown_edit_container)
markdownEditor?.addTextChangedListener(object : TextWatcher {
private var searchFor = ""
override fun afterTextChanged(s: Editable?) {
val searchText = s.toString().trim()
if (searchText == searchFor)
return
searchFor = searchText
launch {
delay(50)
if (searchText != searchFor)
return@launch
presenter.onMarkdownEdited(searchText)
}
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
}
})
val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(requireContext())
val enableReadability = sharedPrefs.getBoolean(getString(R.string.readability_enabled), false)
if (enableReadability) {
markdownEditor?.addTextChangedListener(object : TextWatcher {
private var previousValue = ""
private var searchFor = ""
override fun afterTextChanged(s: Editable?) {
val searchText = s.toString().trim()
if (searchText == searchFor)
return
searchFor = searchText
launch {
delay(250)
if (searchText != searchFor)
return@launch
val start = System.currentTimeMillis()
if (markdown.isEmpty()) return@launch
if (previousValue == markdown) return@launch
val readability = Readability(markdown)
val span = SpannableString(markdown)
for (sentence in readability.sentences()) {
var color = Color.TRANSPARENT
if (sentence.syllableCount() > 25) color = Color.argb(100, 229, 232, 42)
if (sentence.syllableCount() > 35) color = Color.argb(100, 193, 66, 66)
span.setSpan(BackgroundColorSpan(color), sentence.start(), sentence.end(), 0)
}
markdownEditor?.setTextKeepState(span, TextView.BufferType.SPANNABLE)
previousValue = markdown
val timeTakenMs = System.currentTimeMillis() - start
Log.d("SimpleMarkdown", "Handled markdown in " + timeTakenMs + "ms")
}
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
}
})
}
var touchDown = 0L
var oldX = 0f
@ -92,18 +146,32 @@ class EditFragment : Fragment(), MarkdownEditView, ViewPagerPage {
}
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
@Suppress("CAST_NEVER_SUCCEEDS")
(activity?.application as? MarkdownApplication)?.component?.inject(this)
presenter.editView = this@EditFragment
}
override fun onResume() {
super.onResume()
presenter.setEditView(this)
presenter.editView = this
markdown = presenter.markdown
}
override fun onPause() {
super.onPause()
presenter.setEditView(null)
presenter.editView = null
markdownEditor?.hideKeyboard()
}
override fun onDestroy() {
coroutineContext[Job]?.let {
cancel()
}
super.onDestroy()
}
override fun onSelected() {
markdownEditor?.showKeyboard()
}
@ -112,14 +180,6 @@ class EditFragment : Fragment(), MarkdownEditView, ViewPagerPage {
markdownEditor?.hideKeyboard()
}
override fun getMarkdown(): String {
return markdownEditor!!.text.toString()
}
override fun setMarkdown(markdown: String) {
markdownEditor?.setText(markdown)
}
override fun setTitle(title: String) {
val activity = activity
if (activity != null) {

View file

@ -15,7 +15,7 @@ import com.wbrawner.simplemarkdown.BuildConfig
import com.wbrawner.simplemarkdown.MarkdownApplication
import com.wbrawner.simplemarkdown.R
import com.wbrawner.simplemarkdown.presentation.MarkdownPresenter
import com.wbrawner.simplemarkdown.view.MarkdownPreviewView
import com.wbrawner.simplemarkdown.presentation.MarkdownPreviewView
import javax.inject.Inject
@ -50,7 +50,7 @@ class PreviewFragment : Fragment(), MarkdownPreviewView {
}
@Suppress("ConstantConditionIf")
val css: String? = if (!BuildConfig.ENABLE_CUSTOM_CSS) {
getString(defaultCssId)
context?.getString(defaultCssId)
} else {
sharedPreferences!!.getString(
getString(R.string.pref_custom_css),
@ -70,7 +70,7 @@ class PreviewFragment : Fragment(), MarkdownPreviewView {
override fun onResume() {
super.onResume()
presenter.setPreviewView(this)
presenter.previewView = this
presenter.onMarkdownEdited()
}
@ -78,13 +78,14 @@ class PreviewFragment : Fragment(), MarkdownPreviewView {
markdownPreview?.let {
(it.parent as ViewGroup).removeView(it)
it.destroy()
markdownPreview = null
}
super.onDestroyView()
}
override fun onDestroy() {
super.onDestroy()
presenter.setPreviewView(null)
presenter.previewView = null
}
companion object {