Clean up much of the Kotlin usage and remove RxJava
This commit is contained in:
parent
2899feb7bc
commit
1a3d67ef58
11 changed files with 277 additions and 381 deletions
|
@ -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'
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
||||
}
|
||||
}
|
|
@ -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() {
|
||||
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
package com.wbrawner.simplemarkdown.view
|
||||
|
||||
interface MarkdownPreviewView {
|
||||
fun updatePreview(html: String)
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in a new issue