WIP: Migrate to ViewModel architecture
Signed-off-by: Billy Brawner <billy@wbrawner.com>
This commit is contained in:
parent
f03a91c1d3
commit
04954b96f7
16 changed files with 427 additions and 489 deletions
|
@ -101,7 +101,10 @@ dependencies {
|
|||
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'
|
||||
|
||||
def lifecycle_version = "2.0.0"
|
||||
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
|
||||
kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
|
||||
implementation 'eu.crydee:syllable-counter:4.0.2'
|
||||
}
|
||||
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
package com.wbrawner.simplemarkdown
|
||||
|
||||
import android.content.Context
|
||||
import com.wbrawner.simplemarkdown.view.activity.MainActivity
|
||||
import com.wbrawner.simplemarkdown.view.activity.SplashActivity
|
||||
import com.wbrawner.simplemarkdown.view.fragment.EditFragment
|
||||
import com.wbrawner.simplemarkdown.view.fragment.PreviewFragment
|
||||
import dagger.BindsInstance
|
||||
import dagger.Component
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
@Component(modules = [AppModule::class])
|
||||
interface AppComponent {
|
||||
fun inject(application: MarkdownApplication)
|
||||
fun inject(activity: MainActivity)
|
||||
fun inject(activity: SplashActivity)
|
||||
fun inject(fragment: EditFragment)
|
||||
fun inject(fragment: PreviewFragment)
|
||||
|
||||
@Component.Builder
|
||||
abstract class Builder {
|
||||
@BindsInstance
|
||||
internal abstract fun context(context: Context): Builder
|
||||
|
||||
internal abstract fun build(): AppComponent
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
package com.wbrawner.simplemarkdown
|
||||
|
||||
import com.wbrawner.simplemarkdown.presentation.MarkdownPresenter
|
||||
import com.wbrawner.simplemarkdown.presentation.MarkdownPresenterImpl
|
||||
import com.wbrawner.simplemarkdown.utility.CrashlyticsErrorHandler
|
||||
import com.wbrawner.simplemarkdown.utility.ErrorHandler
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
class AppModule {
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideMarkdownPresenter(errorHandler: ErrorHandler): MarkdownPresenter {
|
||||
return MarkdownPresenterImpl(errorHandler)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
internal fun provideErrorHandler(): ErrorHandler {
|
||||
return CrashlyticsErrorHandler()
|
||||
}
|
||||
}
|
|
@ -1,16 +1,26 @@
|
|||
package com.wbrawner.simplemarkdown
|
||||
|
||||
import android.app.Application
|
||||
import android.os.StrictMode
|
||||
import com.wbrawner.simplemarkdown.viewmodel.MarkdownViewModelFactory
|
||||
|
||||
class MarkdownApplication : Application() {
|
||||
|
||||
lateinit var component: AppComponent
|
||||
private set
|
||||
val viewModelFactory: MarkdownViewModelFactory by lazy {
|
||||
MarkdownViewModelFactory()
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
if (BuildConfig.DEBUG) {
|
||||
StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.Builder()
|
||||
.detectAll()
|
||||
.penaltyDeath()
|
||||
.build())
|
||||
// StrictMode.setVmPolicy(StrictMode.VmPolicy.Builder()
|
||||
// .detectAll()
|
||||
// .penaltyLog()
|
||||
// .penaltyDeath()
|
||||
// .build())
|
||||
}
|
||||
super.onCreate()
|
||||
component = DaggerAppComponent.builder()
|
||||
.context(this)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,25 +10,4 @@ import java.io.Reader
|
|||
*/
|
||||
class MarkdownFile(var name: String = "Untitled.md", var content: String = "") {
|
||||
|
||||
fun load(name: String, inputStream: InputStream): Boolean {
|
||||
this.name = name
|
||||
return try {
|
||||
this.content = inputStream.reader().use(Reader::readText)
|
||||
true
|
||||
} catch (e: Throwable) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fun save(name: String, outputStream: OutputStream): Boolean {
|
||||
this.name = name
|
||||
return try {
|
||||
outputStream.writer().use {
|
||||
it.write(this.content)
|
||||
}
|
||||
true
|
||||
} catch (e: Throwable) {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
package com.wbrawner.simplemarkdown.presentation
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
|
||||
interface MarkdownPresenter {
|
||||
var fileName: String
|
||||
var markdown: String
|
||||
var editView: MarkdownEditView?
|
||||
var previewView: MarkdownPreviewView?
|
||||
suspend fun loadFromUri(context: Context, fileUri: Uri): String?
|
||||
suspend fun loadMarkdown(
|
||||
fileName: String,
|
||||
`in`: InputStream,
|
||||
replaceCurrentFile: Boolean = true
|
||||
): String?
|
||||
fun newFile(newName: String)
|
||||
suspend fun saveMarkdown(name: String, outputStream: OutputStream): Boolean
|
||||
fun onMarkdownEdited(markdown: String? = null)
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
@ -1,127 +0,0 @@
|
|||
package com.wbrawner.simplemarkdown.presentation
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.provider.OpenableColumns
|
||||
import com.commonsware.cwac.anddown.AndDown
|
||||
import com.wbrawner.simplemarkdown.model.MarkdownFile
|
||||
import com.wbrawner.simplemarkdown.utility.ErrorHandler
|
||||
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 {
|
||||
@Volatile
|
||||
private var file: MarkdownFile = MarkdownFile()
|
||||
@Volatile
|
||||
override var editView: MarkdownEditView? = null
|
||||
set(value) {
|
||||
field = value
|
||||
onMarkdownEdited(null)
|
||||
}
|
||||
@Volatile
|
||||
override var previewView: MarkdownPreviewView? = null
|
||||
|
||||
override var fileName: String
|
||||
get() = file.name
|
||||
set(name) {
|
||||
file.name = name
|
||||
}
|
||||
|
||||
override var markdown: String
|
||||
get() = file.content
|
||||
set(markdown) {
|
||||
file.content = markdown
|
||||
}
|
||||
|
||||
override suspend fun loadMarkdown(
|
||||
fileName: String,
|
||||
`in`: InputStream,
|
||||
replaceCurrentFile: Boolean
|
||||
): 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) {
|
||||
editView?.let {
|
||||
file.content = it.markdown
|
||||
it.setTitle(newName)
|
||||
it.markdown = ""
|
||||
}
|
||||
file = MarkdownFile(newName, "")
|
||||
}
|
||||
|
||||
override suspend fun saveMarkdown(name: String, outputStream: OutputStream): Boolean {
|
||||
val result = withContext(Dispatchers.IO) {
|
||||
file.save(name, outputStream)
|
||||
}
|
||||
editView?.let {
|
||||
it.setTitle(file.name)
|
||||
it.onFileSaved(result)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
override fun onMarkdownEdited(markdown: String?) {
|
||||
this.markdown = markdown ?: file.content
|
||||
previewView?.updatePreview(generateHTML(this.markdown))
|
||||
}
|
||||
|
||||
override fun generateHTML(markdown: String): String {
|
||||
return AndDown().markdownToHtml(markdown, HOEDOWN_FLAGS, 0)
|
||||
}
|
||||
|
||||
override suspend fun loadFromUri(context: Context, fileUri: Uri): String? {
|
||||
return try {
|
||||
var fileName: String? = null
|
||||
if ("content" == fileUri.scheme) {
|
||||
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
|
||||
}
|
||||
val inputStream = context.contentResolver.openInputStream(fileUri) ?: return null
|
||||
loadMarkdown(fileName ?: "Untitled.md", inputStream, true)
|
||||
} catch (e: Exception) {
|
||||
errorHandler.reportException(e)
|
||||
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,8 +1,15 @@
|
|||
package com.wbrawner.simplemarkdown.utility
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.AssetManager
|
||||
import android.net.Uri
|
||||
import android.provider.OpenableColumns
|
||||
import android.view.View
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import com.commonsware.cwac.anddown.AndDown
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.Reader
|
||||
|
||||
fun View.showKeyboard() =
|
||||
(context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager)
|
||||
|
@ -10,4 +17,46 @@ fun View.showKeyboard() =
|
|||
|
||||
fun View.hideKeyboard() =
|
||||
(context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager)
|
||||
.hideSoftInputFromWindow(windowToken, 0)
|
||||
.hideSoftInputFromWindow(windowToken, 0)
|
||||
|
||||
suspend fun AssetManager.readAssetToString(asset: String): String? {
|
||||
return withContext(Dispatchers.IO) {
|
||||
open(asset).reader().use(Reader::readText)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
suspend fun String.toHtml(): String {
|
||||
return withContext(Dispatchers.IO) {
|
||||
AndDown().markdownToHtml(this@toHtml, HOEDOWN_FLAGS, 0)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun Uri.getName(context: Context): String {
|
||||
var fileName: String? = null
|
||||
try {
|
||||
if ("content" == scheme) {
|
||||
withContext(Dispatchers.IO) {
|
||||
context.contentResolver.query(
|
||||
this@getName,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)?.use {
|
||||
val nameIndex = it.getColumnIndex(OpenableColumns.DISPLAY_NAME)
|
||||
it.moveToFirst()
|
||||
fileName = it.getString(nameIndex)
|
||||
}
|
||||
}
|
||||
} else if ("file" == scheme) {
|
||||
fileName = lastPathSegment
|
||||
}
|
||||
} catch (ignored: Exception) {
|
||||
ignored.printStackTrace()
|
||||
}
|
||||
return fileName ?: "Untitled.md"
|
||||
}
|
||||
|
|
|
@ -5,10 +5,10 @@ import android.app.Activity
|
|||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.Configuration
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.preference.PreferenceManager
|
||||
import android.provider.OpenableColumns
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
|
@ -18,11 +18,16 @@ import androidx.appcompat.app.AlertDialog
|
|||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import com.wbrawner.simplemarkdown.MarkdownApplication
|
||||
import com.wbrawner.simplemarkdown.R
|
||||
import com.wbrawner.simplemarkdown.presentation.MarkdownPresenter
|
||||
import com.wbrawner.simplemarkdown.utility.ErrorHandler
|
||||
import com.wbrawner.simplemarkdown.utility.getName
|
||||
import com.wbrawner.simplemarkdown.utility.readAssetToString
|
||||
import com.wbrawner.simplemarkdown.utility.toHtml
|
||||
import com.wbrawner.simplemarkdown.view.adapter.EditPagerAdapter
|
||||
import com.wbrawner.simplemarkdown.viewmodel.MarkdownViewModel
|
||||
import kotlinx.android.synthetic.main.activity_main.*
|
||||
import kotlinx.coroutines.*
|
||||
import java.io.File
|
||||
|
@ -32,12 +37,11 @@ import kotlin.coroutines.CoroutineContext
|
|||
|
||||
class MainActivity : AppCompatActivity(), ActivityCompat.OnRequestPermissionsResultCallback, CoroutineScope {
|
||||
|
||||
@Inject
|
||||
lateinit var presenter: MarkdownPresenter
|
||||
@Inject
|
||||
lateinit var errorHandler: ErrorHandler
|
||||
private var shouldAutoSave = true
|
||||
override val coroutineContext: CoroutineContext = Dispatchers.Main
|
||||
private lateinit var viewModel: MarkdownViewModel
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
@ -50,7 +54,10 @@ class MainActivity : AppCompatActivity(), ActivityCompat.OnRequestPermissionsRes
|
|||
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
)
|
||||
}
|
||||
(application as MarkdownApplication).component.inject(this)
|
||||
viewModel = ViewModelProviders.of(
|
||||
this,
|
||||
(application as MarkdownApplication).viewModelFactory
|
||||
).get(MarkdownViewModel::class.java)
|
||||
val adapter = EditPagerAdapter(supportFragmentManager, this@MainActivity)
|
||||
pager.adapter = adapter
|
||||
pager.addOnPageChangeListener(adapter)
|
||||
|
@ -60,16 +67,38 @@ class MainActivity : AppCompatActivity(), ActivityCompat.OnRequestPermissionsRes
|
|||
if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||
tabLayout!!.visibility = View.GONE
|
||||
}
|
||||
@Suppress("CAST_NEVER_SUCCEEDS")
|
||||
viewModel.fileName.observe(this, Observer<String> {
|
||||
title = it
|
||||
})
|
||||
}
|
||||
|
||||
override fun onUserLeaveHint() {
|
||||
super.onUserLeaveHint()
|
||||
val isAutoSaveEnabled = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
.getBoolean(KEY_AUTOSAVE, true)
|
||||
if (shouldAutoSave && presenter.markdown.isNotEmpty() && isAutoSaveEnabled) {
|
||||
launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this@MainActivity)
|
||||
val isAutoSaveEnabled = sharedPrefs.getBoolean(KEY_AUTOSAVE, true)
|
||||
if (!shouldAutoSave || !isAutoSaveEnabled) {
|
||||
return@withContext
|
||||
}
|
||||
|
||||
launch {
|
||||
presenter.saveMarkdown("autosave.md", File(filesDir, "autosave.md").outputStream())
|
||||
val uri = if (viewModel.save(this@MainActivity)) {
|
||||
viewModel.uri.value
|
||||
} else {
|
||||
// The user has left the app, with autosave enabled, and we don't already have a
|
||||
// Uri for them or for some reason we were unable to save to the original Uri. In
|
||||
// this case, we need to just save to internal file storage so that we can recover
|
||||
val fileUri = Uri.fromFile(File(filesDir, viewModel.fileName.value))
|
||||
if (viewModel.save(this@MainActivity, fileUri)) {
|
||||
fileUri
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}?: return@withContext
|
||||
sharedPrefs.edit()
|
||||
.putString(getString(R.string.pref_key_autosave_uri), uri.toString())
|
||||
.apply()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -89,10 +118,22 @@ class MainActivity : AppCompatActivity(), ActivityCompat.OnRequestPermissionsRes
|
|||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.action_save -> requestFileOp(REQUEST_SAVE_FILE)
|
||||
R.id.action_save -> {
|
||||
launch {
|
||||
if (!viewModel.save(this@MainActivity)) {
|
||||
requestFileOp(REQUEST_SAVE_FILE)
|
||||
} else {
|
||||
Toast.makeText(
|
||||
this@MainActivity,
|
||||
getString(R.string.file_saved, viewModel.fileName.value),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
R.id.action_share -> {
|
||||
val shareIntent = Intent(Intent.ACTION_SEND)
|
||||
shareIntent.putExtra(Intent.EXTRA_TEXT, presenter.markdown)
|
||||
shareIntent.putExtra(Intent.EXTRA_TEXT, viewModel.markdownUpdates.value)
|
||||
shareIntent.type = "text/plain"
|
||||
startActivity(Intent.createChooser(
|
||||
shareIntent,
|
||||
|
@ -137,9 +178,9 @@ class MainActivity : AppCompatActivity(), ActivityCompat.OnRequestPermissionsRes
|
|||
infoIntent.putExtra("title", title)
|
||||
launch {
|
||||
try {
|
||||
val inputStream = assets?.open(fileName)
|
||||
val html = assets?.readAssetToString(fileName)
|
||||
?.toHtml()
|
||||
?: throw RuntimeException("Unable to open stream to $fileName")
|
||||
val html = presenter.loadMarkdown(fileName, inputStream, false)
|
||||
infoIntent.putExtra("html", html)
|
||||
startActivity(infoIntent)
|
||||
} catch (e: Exception) {
|
||||
|
@ -183,17 +224,11 @@ class MainActivity : AppCompatActivity(), ActivityCompat.OnRequestPermissionsRes
|
|||
return
|
||||
}
|
||||
|
||||
val fileName = contentResolver.query(data.data!!, null, null, null, null)
|
||||
?.use { cursor ->
|
||||
val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
|
||||
cursor.moveToFirst()
|
||||
cursor.getString(nameIndex)
|
||||
} ?: "Untitled.md"
|
||||
|
||||
contentResolver.openFileDescriptor(data.data!!, "r")?.let {
|
||||
val fileInput = FileInputStream(it.fileDescriptor)
|
||||
launch {
|
||||
presenter.loadMarkdown(fileName, fileInput)
|
||||
launch {
|
||||
val fileLoaded = viewModel.load(this@MainActivity, data.data)
|
||||
if (!fileLoaded) {
|
||||
Toast.makeText(this@MainActivity, R.string.file_load_error, Toast.LENGTH_SHORT)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -202,20 +237,8 @@ class MainActivity : AppCompatActivity(), ActivityCompat.OnRequestPermissionsRes
|
|||
return
|
||||
}
|
||||
|
||||
val fileName = contentResolver.query(data.data!!, null, null, null, null)
|
||||
?.use { cursor ->
|
||||
val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
|
||||
cursor.moveToFirst()
|
||||
cursor.getString(nameIndex)
|
||||
} ?: "Untitled.md"
|
||||
|
||||
launch {
|
||||
val outputStream = contentResolver.openOutputStream(data.data!!)
|
||||
?: throw RuntimeException("Unable to open output stream to save file")
|
||||
presenter.saveMarkdown(
|
||||
fileName,
|
||||
outputStream
|
||||
)
|
||||
viewModel.save(this@MainActivity, data.data)
|
||||
}
|
||||
}
|
||||
REQUEST_DARK_MODE -> recreate()
|
||||
|
@ -224,11 +247,15 @@ class MainActivity : AppCompatActivity(), ActivityCompat.OnRequestPermissionsRes
|
|||
}
|
||||
|
||||
private fun promptSaveOrDiscardChanges() {
|
||||
if (viewModel.originalMarkdown.value == viewModel.markdownUpdates.value) {
|
||||
viewModel.reset("Untitled.md")
|
||||
return
|
||||
}
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle(R.string.save_changes)
|
||||
.setMessage(R.string.prompt_save_changes)
|
||||
.setNegativeButton(R.string.action_discard) { _, _ ->
|
||||
presenter.newFile("Untitled.md")
|
||||
viewModel.reset("Untitled.md")
|
||||
}
|
||||
.setPositiveButton(R.string.action_save) { _, _ ->
|
||||
requestFileOp(REQUEST_SAVE_FILE)
|
||||
|
@ -252,7 +279,7 @@ class MainActivity : AppCompatActivity(), ActivityCompat.OnRequestPermissionsRes
|
|||
REQUEST_SAVE_FILE -> {
|
||||
Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
||||
type = "text/markdown"
|
||||
putExtra(Intent.EXTRA_TITLE, presenter.fileName)
|
||||
putExtra(Intent.EXTRA_TITLE, viewModel.fileName.value)
|
||||
}
|
||||
}
|
||||
REQUEST_OPEN_FILE -> {
|
||||
|
@ -274,14 +301,11 @@ class MainActivity : AppCompatActivity(), ActivityCompat.OnRequestPermissionsRes
|
|||
)
|
||||
}
|
||||
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
title = presenter.fileName
|
||||
shouldAutoSave = true
|
||||
}
|
||||
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
coroutineContext[Job]?.let {
|
||||
|
|
|
@ -1,85 +1,73 @@
|
|||
package com.wbrawner.simplemarkdown.view.activity
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.preference.PreferenceManager
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import com.wbrawner.simplemarkdown.MarkdownApplication
|
||||
import com.wbrawner.simplemarkdown.R
|
||||
import com.wbrawner.simplemarkdown.presentation.MarkdownPresenter
|
||||
import com.wbrawner.simplemarkdown.utility.ErrorHandler
|
||||
import com.wbrawner.simplemarkdown.viewmodel.MarkdownViewModel
|
||||
import kotlinx.coroutines.*
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
import javax.inject.Inject
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
class SplashActivity : AppCompatActivity(), CoroutineScope {
|
||||
|
||||
override val coroutineContext: CoroutineContext = Dispatchers.Main
|
||||
|
||||
@Inject
|
||||
lateinit var presenter: MarkdownPresenter
|
||||
|
||||
@Inject
|
||||
lateinit var errorHandler: ErrorHandler
|
||||
lateinit var viewModel: MarkdownViewModel
|
||||
|
||||
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)
|
||||
}
|
||||
viewModel = ViewModelProviders.of(
|
||||
this,
|
||||
(application as MarkdownApplication).viewModelFactory
|
||||
).get(MarkdownViewModel::class.java)
|
||||
|
||||
val darkModeValue = sharedPreferences.getString(
|
||||
getString(R.string.pref_key_dark_mode),
|
||||
getString(R.string.pref_value_auto)
|
||||
)
|
||||
|
||||
var darkMode = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
|
||||
AppCompatDelegate.MODE_NIGHT_AUTO
|
||||
} else {
|
||||
AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
|
||||
}
|
||||
|
||||
if (darkModeValue != null && !darkModeValue.isEmpty()) {
|
||||
if (darkModeValue.equals(getString(R.string.pref_value_light), ignoreCase = true)) {
|
||||
darkMode = AppCompatDelegate.MODE_NIGHT_NO
|
||||
} else if (darkModeValue.equals(getString(R.string.pref_value_dark), ignoreCase = true)) {
|
||||
darkMode = AppCompatDelegate.MODE_NIGHT_YES
|
||||
}
|
||||
}
|
||||
AppCompatDelegate.setDefaultNightMode(darkMode)
|
||||
|
||||
if (intent?.data != null) {
|
||||
launch {
|
||||
presenter.loadFromUri(applicationContext, intent.data!!)
|
||||
}
|
||||
} else {
|
||||
presenter.fileName = "Untitled.md"
|
||||
val autosave = File(filesDir, "autosave.md")
|
||||
if (autosave.exists()) {
|
||||
try {
|
||||
launch {
|
||||
presenter.loadMarkdown(
|
||||
"Untitled.md",
|
||||
autosave.inputStream(),
|
||||
true
|
||||
launch {
|
||||
val darkMode = withContext(Dispatchers.IO) {
|
||||
val darkModeValue = PreferenceManager.getDefaultSharedPreferences(this@SplashActivity)
|
||||
.getString(
|
||||
getString(R.string.pref_key_dark_mode),
|
||||
getString(R.string.pref_value_auto)
|
||||
)
|
||||
autosave.delete()
|
||||
}
|
||||
} catch (ignored: FileNotFoundException) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val startIntent = Intent(this, MainActivity::class.java)
|
||||
startActivity(startIntent)
|
||||
finish()
|
||||
return@withContext when {
|
||||
darkModeValue.equals(getString(R.string.pref_value_light), ignoreCase = true) -> AppCompatDelegate.MODE_NIGHT_NO
|
||||
darkModeValue.equals(getString(R.string.pref_value_dark), ignoreCase = true) -> AppCompatDelegate.MODE_NIGHT_YES
|
||||
else -> {
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
|
||||
AppCompatDelegate.MODE_NIGHT_AUTO
|
||||
} else {
|
||||
AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
AppCompatDelegate.setDefaultNightMode(darkMode)
|
||||
withContext(Dispatchers.IO) {
|
||||
var uri = intent?.data
|
||||
if (uri == null) {
|
||||
uri = PreferenceManager.getDefaultSharedPreferences(this@SplashActivity)
|
||||
.getString(
|
||||
getString(R.string.pref_key_autosave_uri),
|
||||
null
|
||||
)?.let {
|
||||
Uri.parse(it)
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.load(this@SplashActivity, uri)
|
||||
}
|
||||
val startIntent = Intent(this@SplashActivity, MainActivity::class.java)
|
||||
startActivity(startIntent)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
|
|
@ -16,31 +16,24 @@ 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 androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
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.hideKeyboard
|
||||
import com.wbrawner.simplemarkdown.utility.showKeyboard
|
||||
import com.wbrawner.simplemarkdown.view.ViewPagerPage
|
||||
import com.wbrawner.simplemarkdown.viewmodel.MarkdownViewModel
|
||||
import kotlinx.coroutines.*
|
||||
import javax.inject.Inject
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.math.abs
|
||||
|
||||
class EditFragment : Fragment(), MarkdownEditView, ViewPagerPage, CoroutineScope {
|
||||
@Inject
|
||||
lateinit var presenter: MarkdownPresenter
|
||||
class EditFragment : Fragment(), ViewPagerPage, CoroutineScope {
|
||||
private var markdownEditor: EditText? = null
|
||||
private var markdownEditorScroller: ScrollView? = null
|
||||
override var markdown: String
|
||||
get() = markdownEditor?.text?.toString() ?: ""
|
||||
set(value) {
|
||||
markdownEditor?.setText(value)
|
||||
}
|
||||
private lateinit var viewModel: MarkdownViewModel
|
||||
override val coroutineContext: CoroutineContext = Dispatchers.Main
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
|
@ -67,7 +60,7 @@ class EditFragment : Fragment(), MarkdownEditView, ViewPagerPage, CoroutineScope
|
|||
delay(50)
|
||||
if (searchText != searchFor)
|
||||
return@launch
|
||||
presenter.onMarkdownEdited(searchText)
|
||||
viewModel.updateMarkdown(searchText)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -78,8 +71,15 @@ class EditFragment : Fragment(), MarkdownEditView, ViewPagerPage, CoroutineScope
|
|||
}
|
||||
|
||||
})
|
||||
val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
val enableReadability = sharedPrefs.getBoolean(getString(R.string.readability_enabled), false)
|
||||
var enableReadability = false
|
||||
launch {
|
||||
enableReadability = withContext(Dispatchers.IO) {
|
||||
context?.let {
|
||||
PreferenceManager.getDefaultSharedPreferences(it)
|
||||
.getBoolean(getString(R.string.readability_enabled), false)
|
||||
}?: false
|
||||
}
|
||||
}
|
||||
if (enableReadability) {
|
||||
markdownEditor?.addTextChangedListener(object : TextWatcher {
|
||||
private var previousValue = ""
|
||||
|
@ -97,10 +97,10 @@ class EditFragment : Fragment(), MarkdownEditView, ViewPagerPage, CoroutineScope
|
|||
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)
|
||||
if (searchFor.isEmpty()) return@launch
|
||||
if (previousValue == searchFor) return@launch
|
||||
val readability = Readability(searchFor)
|
||||
val span = SpannableString(searchFor)
|
||||
for (sentence in readability.sentences()) {
|
||||
var color = Color.TRANSPARENT
|
||||
if (sentence.syllableCount() > 25) color = Color.argb(100, 229, 232, 42)
|
||||
|
@ -108,7 +108,7 @@ class EditFragment : Fragment(), MarkdownEditView, ViewPagerPage, CoroutineScope
|
|||
span.setSpan(BackgroundColorSpan(color), sentence.start(), sentence.end(), 0)
|
||||
}
|
||||
markdownEditor?.setTextKeepState(span, TextView.BufferType.SPANNABLE)
|
||||
previousValue = markdown
|
||||
previousValue = searchFor
|
||||
val timeTakenMs = System.currentTimeMillis() - start
|
||||
Log.d("SimpleMarkdown", "Handled markdown in " + timeTakenMs + "ms")
|
||||
}
|
||||
|
@ -148,21 +148,13 @@ class EditFragment : Fragment(), MarkdownEditView, ViewPagerPage, CoroutineScope
|
|||
|
||||
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.editView = this
|
||||
markdown = presenter.markdown
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
presenter.editView = null
|
||||
markdownEditor?.hideKeyboard()
|
||||
viewModel = ViewModelProviders.of(
|
||||
this,
|
||||
(requireActivity().application as MarkdownApplication).viewModelFactory
|
||||
).get(MarkdownViewModel::class.java)
|
||||
viewModel.originalMarkdown.observe(this, Observer<String> {
|
||||
markdownEditor?.setText(it)
|
||||
})
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
@ -179,26 +171,4 @@ class EditFragment : Fragment(), MarkdownEditView, ViewPagerPage, CoroutineScope
|
|||
override fun onDeselected() {
|
||||
markdownEditor?.hideKeyboard()
|
||||
}
|
||||
|
||||
override fun setTitle(title: String) {
|
||||
val activity = activity
|
||||
if (activity != null) {
|
||||
activity.title = title
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFileSaved(success: Boolean) {
|
||||
val message: String = if (success) {
|
||||
getString(R.string.file_saved, presenter.fileName)
|
||||
} else {
|
||||
getString(R.string.file_save_error)
|
||||
}
|
||||
Toast.makeText(activity, message, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onFileLoaded(success: Boolean) {
|
||||
// TODO: Investigate why this fires off so often
|
||||
// int message = success ? R.string.file_loaded : R.string.file_load_error;
|
||||
// Toast.makeText(getActivity(), message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}// Required empty public constructor
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package com.wbrawner.simplemarkdown.view.fragment
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_MASK
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||
import android.os.Bundle
|
||||
|
@ -11,19 +10,20 @@ import android.view.ViewGroup
|
|||
import android.webkit.WebView
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
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.presentation.MarkdownPreviewView
|
||||
import javax.inject.Inject
|
||||
import com.wbrawner.simplemarkdown.utility.toHtml
|
||||
import com.wbrawner.simplemarkdown.viewmodel.MarkdownViewModel
|
||||
import kotlinx.coroutines.*
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
|
||||
class PreviewFragment : Fragment(), MarkdownPreviewView {
|
||||
@Inject
|
||||
lateinit var presenter: MarkdownPresenter
|
||||
class PreviewFragment : Fragment(), CoroutineScope {
|
||||
override val coroutineContext: CoroutineContext = Dispatchers.Main
|
||||
lateinit var viewModel: MarkdownViewModel
|
||||
private var markdownPreview: WebView? = null
|
||||
private var sharedPreferences: SharedPreferences? = null
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
|
@ -32,46 +32,48 @@ class PreviewFragment : Fragment(), MarkdownPreviewView {
|
|||
): View? = inflater.inflate(R.layout.fragment_preview, container, false)
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(view.context)
|
||||
markdownPreview = view.findViewById(R.id.markdown_view)
|
||||
(activity?.application as? MarkdownApplication)?.component?.inject(this)
|
||||
WebView.setWebContentsDebuggingEnabled(BuildConfig.DEBUG)
|
||||
}
|
||||
|
||||
override fun updatePreview(html: String) {
|
||||
markdownPreview?.post {
|
||||
val isNightMode = AppCompatDelegate.getDefaultNightMode() ==
|
||||
AppCompatDelegate.MODE_NIGHT_YES
|
||||
|| context!!.resources.configuration.uiMode and UI_MODE_NIGHT_MASK == UI_MODE_NIGHT_YES
|
||||
val defaultCssId = if (isNightMode) {
|
||||
R.string.pref_custom_css_default_dark
|
||||
} else {
|
||||
R.string.pref_custom_css_default
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
viewModel = ViewModelProviders.of(
|
||||
this,
|
||||
(requireActivity().application as MarkdownApplication).viewModelFactory
|
||||
).get(MarkdownViewModel::class.java)
|
||||
viewModel.markdownUpdates.observe(this, Observer<String> {
|
||||
markdownPreview?.post {
|
||||
val isNightMode = AppCompatDelegate.getDefaultNightMode() ==
|
||||
AppCompatDelegate.MODE_NIGHT_YES
|
||||
|| context!!.resources.configuration.uiMode and UI_MODE_NIGHT_MASK == UI_MODE_NIGHT_YES
|
||||
val defaultCssId = if (isNightMode) {
|
||||
R.string.pref_custom_css_default_dark
|
||||
} else {
|
||||
R.string.pref_custom_css_default
|
||||
}
|
||||
launch {
|
||||
val css = withContext(Dispatchers.IO) {
|
||||
@Suppress("ConstantConditionIf")
|
||||
if (!BuildConfig.ENABLE_CUSTOM_CSS) {
|
||||
requireActivity().getString(defaultCssId)
|
||||
} else {
|
||||
PreferenceManager.getDefaultSharedPreferences(requireActivity())
|
||||
.getString(
|
||||
getString(R.string.pref_custom_css),
|
||||
getString(defaultCssId)
|
||||
)?: ""
|
||||
}
|
||||
}
|
||||
val style = String.format(FORMAT_CSS, css)
|
||||
markdownPreview?.loadDataWithBaseURL(null,
|
||||
style + it.toHtml(),
|
||||
"text/html",
|
||||
"UTF-8", null
|
||||
)
|
||||
}
|
||||
}
|
||||
@Suppress("ConstantConditionIf")
|
||||
val css: String? = if (!BuildConfig.ENABLE_CUSTOM_CSS) {
|
||||
context?.getString(defaultCssId)
|
||||
} else {
|
||||
sharedPreferences!!.getString(
|
||||
getString(R.string.pref_custom_css),
|
||||
getString(defaultCssId)
|
||||
)
|
||||
}
|
||||
|
||||
val style = String.format(FORMAT_CSS, css)
|
||||
|
||||
markdownPreview?.loadDataWithBaseURL(null,
|
||||
style + html,
|
||||
"text/html",
|
||||
"UTF-8", null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
presenter.previewView = this
|
||||
presenter.onMarkdownEdited()
|
||||
})
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
|
@ -84,8 +86,10 @@ class PreviewFragment : Fragment(), MarkdownPreviewView {
|
|||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
coroutineContext[Job]?.let {
|
||||
cancel()
|
||||
}
|
||||
super.onDestroy()
|
||||
presenter.previewView = null
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -3,6 +3,7 @@ package com.wbrawner.simplemarkdown.view.fragment
|
|||
import android.content.SharedPreferences
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.StrictMode
|
||||
import android.preference.ListPreference
|
||||
import android.preference.Preference
|
||||
import android.preference.PreferenceFragment
|
||||
|
@ -13,19 +14,39 @@ import android.view.ViewGroup
|
|||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import com.wbrawner.simplemarkdown.BuildConfig
|
||||
import com.wbrawner.simplemarkdown.R
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.lang.Exception
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
class SettingsFragment : PreferenceFragment(), SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
addPreferencesFromResource(R.xml.pref_general)
|
||||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity)
|
||||
sharedPreferences.registerOnSharedPreferenceChangeListener(this)
|
||||
setListPreferenceSummary(
|
||||
sharedPreferences,
|
||||
findPreference(getString(R.string.pref_key_dark_mode))
|
||||
)
|
||||
if (!BuildConfig.ENABLE_CUSTOM_CSS) {
|
||||
preferenceScreen.removePreference(findPreference(getString(R.string.pref_custom_css)))
|
||||
class SettingsFragment
|
||||
: PreferenceFragment(),
|
||||
SharedPreferences.OnSharedPreferenceChangeListener,
|
||||
CoroutineScope {
|
||||
override val coroutineContext: CoroutineContext = Dispatchers.Main
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
try {
|
||||
// This can be thrown when recreating the activity for theme changes
|
||||
addPreferencesFromResource(R.xml.pref_general)
|
||||
} catch (ignored: Exception) {
|
||||
return@withContext
|
||||
}
|
||||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity)
|
||||
sharedPreferences.registerOnSharedPreferenceChangeListener(this@SettingsFragment)
|
||||
(findPreference(getString(R.string.pref_key_dark_mode)) as? ListPreference)?.let {
|
||||
setListPreferenceSummary(sharedPreferences, it)
|
||||
}
|
||||
@Suppress("ConstantConditionIf")
|
||||
if (!BuildConfig.ENABLE_CUSTOM_CSS) {
|
||||
preferenceScreen.removePreference(findPreference(getString(R.string.pref_custom_css)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -39,34 +60,35 @@ class SettingsFragment : PreferenceFragment(), SharedPreferences.OnSharedPrefere
|
|||
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
|
||||
if (!isAdded) return
|
||||
val preference = findPreference(key)
|
||||
if (preference is ListPreference) {
|
||||
setListPreferenceSummary(sharedPreferences, preference)
|
||||
val preference = findPreference(key) as? ListPreference ?: return
|
||||
setListPreferenceSummary(sharedPreferences, preference)
|
||||
if (preference.key != getString(R.string.pref_key_dark_mode)) {
|
||||
return
|
||||
}
|
||||
if (preference.key == getString(R.string.pref_key_dark_mode)) {
|
||||
var darkMode: Int = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
|
||||
AppCompatDelegate.MODE_NIGHT_AUTO
|
||||
} else {
|
||||
AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
|
||||
}
|
||||
val darkModeValue = sharedPreferences.getString(preference.key, null)
|
||||
if (darkModeValue != null && !darkModeValue.isEmpty()) {
|
||||
if (darkModeValue.equals(getString(R.string.pref_value_light), ignoreCase = true)) {
|
||||
darkMode = AppCompatDelegate.MODE_NIGHT_NO
|
||||
} else if (darkModeValue.equals(getString(R.string.pref_value_dark), ignoreCase = true)) {
|
||||
darkMode = AppCompatDelegate.MODE_NIGHT_YES
|
||||
}
|
||||
}
|
||||
AppCompatDelegate.setDefaultNightMode(darkMode)
|
||||
activity?.recreate()
|
||||
var darkMode: Int = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
|
||||
AppCompatDelegate.MODE_NIGHT_AUTO
|
||||
} else {
|
||||
AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
|
||||
}
|
||||
val darkModeValue = sharedPreferences.getString(preference.key, null)
|
||||
if (darkModeValue != null && darkModeValue.isNotEmpty()) {
|
||||
if (darkModeValue.equals(getString(R.string.pref_value_light), ignoreCase = true)) {
|
||||
darkMode = AppCompatDelegate.MODE_NIGHT_NO
|
||||
} else if (darkModeValue.equals(getString(R.string.pref_value_dark), ignoreCase = true)) {
|
||||
darkMode = AppCompatDelegate.MODE_NIGHT_YES
|
||||
}
|
||||
}
|
||||
AppCompatDelegate.setDefaultNightMode(darkMode)
|
||||
activity?.recreate()
|
||||
}
|
||||
|
||||
private fun setListPreferenceSummary(sharedPreferences: SharedPreferences, preference: Preference) {
|
||||
val listPreference = preference as ListPreference
|
||||
val storedValue = sharedPreferences.getString(preference.getKey(), null) ?: return
|
||||
val index = listPreference.findIndexOfValue(storedValue)
|
||||
private fun setListPreferenceSummary(sharedPreferences: SharedPreferences, preference: ListPreference) {
|
||||
val storedValue = sharedPreferences.getString(
|
||||
preference.key,
|
||||
null
|
||||
) ?: return
|
||||
val index = preference.findIndexOfValue(storedValue)
|
||||
if (index < 0) return
|
||||
preference.setSummary(listPreference.entries[index].toString())
|
||||
preference.summary = preference.entries[index].toString()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
package com.wbrawner.simplemarkdown.viewmodel
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.wbrawner.simplemarkdown.utility.getName
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.FileInputStream
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.io.Reader
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
class MarkdownViewModel : ViewModel() {
|
||||
private val coroutineContext: CoroutineContext = Dispatchers.IO
|
||||
val fileName = MutableLiveData<String>().apply {
|
||||
postValue("Untitled.md")
|
||||
}
|
||||
val markdownUpdates = MutableLiveData<String>()
|
||||
val originalMarkdown = MutableLiveData<String>()
|
||||
val uri = MutableLiveData<Uri>()
|
||||
|
||||
fun updateMarkdown(markdown: String?) {
|
||||
this.markdownUpdates.postValue(markdown ?: "")
|
||||
}
|
||||
|
||||
suspend fun load(context: Context, uri: Uri?): Boolean {
|
||||
if (uri == null) return false
|
||||
return withContext(Dispatchers.IO) {
|
||||
context.contentResolver.openFileDescriptor(uri, "r")?.use {
|
||||
val fileInput = FileInputStream(it.fileDescriptor)
|
||||
val fileName = uri.getName(context)
|
||||
return@withContext if (load(fileInput)) {
|
||||
this@MarkdownViewModel.fileName.postValue(fileName)
|
||||
this@MarkdownViewModel.uri.postValue(uri)
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} ?: false
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun load(inputStream: InputStream): Boolean {
|
||||
return try {
|
||||
withContext(coroutineContext) {
|
||||
val content = inputStream.reader().use(Reader::readText)
|
||||
originalMarkdown.postValue(content)
|
||||
markdownUpdates.postValue(content)
|
||||
}
|
||||
true
|
||||
} catch (e: Throwable) {
|
||||
e.printStackTrace()
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun save(context: Context, givenUri: Uri? = this.uri.value): Boolean {
|
||||
val uri = givenUri ?: this.uri.value ?: return false
|
||||
return withContext(Dispatchers.IO) {
|
||||
val fileName = uri.getName(context)
|
||||
val outputStream = context.contentResolver.openOutputStream(uri)
|
||||
?: return@withContext false
|
||||
if (save(outputStream)) {
|
||||
this@MarkdownViewModel.fileName.postValue(fileName)
|
||||
this@MarkdownViewModel.uri.postValue(uri)
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun save(outputStream: OutputStream): Boolean {
|
||||
return try {
|
||||
withContext(coroutineContext) {
|
||||
outputStream.writer().use {
|
||||
it.write(markdownUpdates.value)
|
||||
}
|
||||
}
|
||||
true
|
||||
} catch (e: Throwable) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fun reset(untitledFileName: String) {
|
||||
fileName.postValue(untitledFileName)
|
||||
originalMarkdown.postValue("")
|
||||
markdownUpdates.postValue("")
|
||||
}
|
||||
}
|
||||
|
||||
class MarkdownViewModelFactory : ViewModelProvider.Factory {
|
||||
private val markdownViewModel: MarkdownViewModel by lazy {
|
||||
MarkdownViewModel()
|
||||
}
|
||||
|
||||
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return markdownViewModel as T
|
||||
}
|
||||
}
|
|
@ -60,6 +60,7 @@
|
|||
<string name="pref_key_dark_mode_light" translatable="false">light</string>
|
||||
<string name="pref_key_dark_mode_dark" translatable="false">dark</string>
|
||||
<string name="pref_key_dark_mode_auto" translatable="false">auto</string>
|
||||
<string name="pref_key_autosave_uri" translatable="false">autosave.uri</string>
|
||||
<string name="save_changes">Save Changes</string>
|
||||
<string name="prompt_save_changes">Would you like to save your changes?</string>
|
||||
<string name="action_discard">Discard</string>
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<ListPreference
|
||||
android:entries="@array/pref_entries_dark_mode"
|
||||
android:entryValues="@array/pref_values_dark_mode"
|
||||
android:defaultValue="@string/pref_value_auto"
|
||||
android:defaultValue="@string/pref_key_dark_mode_auto"
|
||||
android:key="@string/pref_key_dark_mode"
|
||||
android:title="@string/title_dark_mode" />
|
||||
<SwitchPreference
|
||||
|
|
Loading…
Reference in a new issue