From 1a3d67ef581d3938ddede1bbe186d5f29d4c4849 Mon Sep 17 00:00:00 2001 From: Billy Brawner Date: Sat, 17 Aug 2019 13:23:26 -0500 Subject: [PATCH] Clean up much of the Kotlin usage and remove RxJava --- app/build.gradle | 12 +- .../presentation/MarkdownPresenter.kt | 44 ++--- .../presentation/MarkdownPresenterImpl.kt | 185 +++++++----------- .../utility/MarkdownObserver.java | 40 ---- .../utility/ReadabilityObserver.java | 59 ------ .../simplemarkdown/view/MarkdownEditView.kt | 10 - .../view/MarkdownPreviewView.kt | 5 - .../view/activity/MainActivity.kt | 82 ++++---- .../view/activity/SplashActivity.kt | 60 +++--- .../view/fragment/EditFragment.kt | 152 +++++++++----- .../view/fragment/PreviewFragment.kt | 9 +- 11 files changed, 277 insertions(+), 381 deletions(-) delete mode 100644 app/src/main/java/com/wbrawner/simplemarkdown/utility/MarkdownObserver.java delete mode 100644 app/src/main/java/com/wbrawner/simplemarkdown/utility/ReadabilityObserver.java delete mode 100644 app/src/main/java/com/wbrawner/simplemarkdown/view/MarkdownEditView.kt delete mode 100644 app/src/main/java/com/wbrawner/simplemarkdown/view/MarkdownPreviewView.kt diff --git a/app/build.gradle b/app/build.gradle index 3fded25..02aa630 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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' } diff --git a/app/src/main/java/com/wbrawner/simplemarkdown/presentation/MarkdownPresenter.kt b/app/src/main/java/com/wbrawner/simplemarkdown/presentation/MarkdownPresenter.kt index d621ecd..86d606b 100644 --- a/app/src/main/java/com/wbrawner/simplemarkdown/presentation/MarkdownPresenter.kt +++ b/app/src/main/java/com/wbrawner/simplemarkdown/presentation/MarkdownPresenter.kt @@ -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) +} + + diff --git a/app/src/main/java/com/wbrawner/simplemarkdown/presentation/MarkdownPresenterImpl.kt b/app/src/main/java/com/wbrawner/simplemarkdown/presentation/MarkdownPresenterImpl.kt index b8b2823..111b7de 100644 --- a/app/src/main/java/com/wbrawner/simplemarkdown/presentation/MarkdownPresenterImpl.kt +++ b/app/src/main/java/com/wbrawner/simplemarkdown/presentation/MarkdownPresenterImpl.kt @@ -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 } } diff --git a/app/src/main/java/com/wbrawner/simplemarkdown/utility/MarkdownObserver.java b/app/src/main/java/com/wbrawner/simplemarkdown/utility/MarkdownObserver.java deleted file mode 100644 index 235ee98..0000000 --- a/app/src/main/java/com/wbrawner/simplemarkdown/utility/MarkdownObserver.java +++ /dev/null @@ -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 { - private MarkdownPresenter presenter; - private Observable obs; - - public MarkdownObserver(MarkdownPresenter presenter, Observable 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() { - - } -} diff --git a/app/src/main/java/com/wbrawner/simplemarkdown/utility/ReadabilityObserver.java b/app/src/main/java/com/wbrawner/simplemarkdown/utility/ReadabilityObserver.java deleted file mode 100644 index 01629be..0000000 --- a/app/src/main/java/com/wbrawner/simplemarkdown/utility/ReadabilityObserver.java +++ /dev/null @@ -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 { - 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() { - - } -} diff --git a/app/src/main/java/com/wbrawner/simplemarkdown/view/MarkdownEditView.kt b/app/src/main/java/com/wbrawner/simplemarkdown/view/MarkdownEditView.kt deleted file mode 100644 index 70fb0e6..0000000 --- a/app/src/main/java/com/wbrawner/simplemarkdown/view/MarkdownEditView.kt +++ /dev/null @@ -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) -} diff --git a/app/src/main/java/com/wbrawner/simplemarkdown/view/MarkdownPreviewView.kt b/app/src/main/java/com/wbrawner/simplemarkdown/view/MarkdownPreviewView.kt deleted file mode 100644 index 89d006f..0000000 --- a/app/src/main/java/com/wbrawner/simplemarkdown/view/MarkdownPreviewView.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.wbrawner.simplemarkdown.view - -interface MarkdownPreviewView { - fun updatePreview(html: String) -} diff --git a/app/src/main/java/com/wbrawner/simplemarkdown/view/activity/MainActivity.kt b/app/src/main/java/com/wbrawner/simplemarkdown/view/activity/MainActivity.kt index 25c7209..8ccdf00 100644 --- a/app/src/main/java/com/wbrawner/simplemarkdown/view/activity/MainActivity.kt +++ b/app/src/main/java/com/wbrawner/simplemarkdown/view/activity/MainActivity.kt @@ -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() } } } diff --git a/app/src/main/java/com/wbrawner/simplemarkdown/view/activity/SplashActivity.kt b/app/src/main/java/com/wbrawner/simplemarkdown/view/activity/SplashActivity.kt index 042d254..5844389 100644 --- a/app/src/main/java/com/wbrawner/simplemarkdown/view/activity/SplashActivity.kt +++ b/app/src/main/java/com/wbrawner/simplemarkdown/view/activity/SplashActivity.kt @@ -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() + } + } } diff --git a/app/src/main/java/com/wbrawner/simplemarkdown/view/fragment/EditFragment.kt b/app/src/main/java/com/wbrawner/simplemarkdown/view/fragment/EditFragment.kt index 925d423..95b5001 100644 --- a/app/src/main/java/com/wbrawner/simplemarkdown/view/fragment/EditFragment.kt +++ b/app/src/main/java/com/wbrawner/simplemarkdown/view/fragment/EditFragment.kt @@ -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) { diff --git a/app/src/main/java/com/wbrawner/simplemarkdown/view/fragment/PreviewFragment.kt b/app/src/main/java/com/wbrawner/simplemarkdown/view/fragment/PreviewFragment.kt index ed369ac..806236f 100644 --- a/app/src/main/java/com/wbrawner/simplemarkdown/view/fragment/PreviewFragment.kt +++ b/app/src/main/java/com/wbrawner/simplemarkdown/view/fragment/PreviewFragment.kt @@ -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 {