Fix various concurrency issues and simplify some logic throughout the app

This commit is contained in:
Billy Brawner 2019-11-08 09:30:55 -06:00 committed by William Brawner
parent 810a334bb0
commit d841fd0225
13 changed files with 143 additions and 255 deletions

View file

@ -4,12 +4,8 @@ import android.app.Application
import android.os.StrictMode import android.os.StrictMode
import com.wbrawner.simplemarkdown.utility.CrashlyticsErrorHandler import com.wbrawner.simplemarkdown.utility.CrashlyticsErrorHandler
import com.wbrawner.simplemarkdown.utility.ErrorHandler import com.wbrawner.simplemarkdown.utility.ErrorHandler
import com.wbrawner.simplemarkdown.viewmodel.MarkdownViewModelFactory
class MarkdownApplication : Application() { class MarkdownApplication : Application() {
val viewModelFactory: MarkdownViewModelFactory by lazy {
MarkdownViewModelFactory()
}
val errorHandler: ErrorHandler by lazy { val errorHandler: ErrorHandler by lazy {
CrashlyticsErrorHandler() CrashlyticsErrorHandler()
} }

View file

@ -20,8 +20,8 @@ import androidx.core.content.ContextCompat
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders import androidx.lifecycle.ViewModelProviders
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.wbrawner.simplemarkdown.MarkdownApplication
import com.wbrawner.simplemarkdown.R import com.wbrawner.simplemarkdown.R
import com.wbrawner.simplemarkdown.utility.hideKeyboard
import com.wbrawner.simplemarkdown.view.adapter.EditPagerAdapter import com.wbrawner.simplemarkdown.view.adapter.EditPagerAdapter
import com.wbrawner.simplemarkdown.view.fragment.MainMenuFragment import com.wbrawner.simplemarkdown.view.fragment.MainMenuFragment
import com.wbrawner.simplemarkdown.viewmodel.MarkdownViewModel import com.wbrawner.simplemarkdown.viewmodel.MarkdownViewModel
@ -49,10 +49,7 @@ class MainActivity : AppCompatActivity(), ActivityCompat.OnRequestPermissionsRes
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
) )
} }
viewModel = ViewModelProviders.of( viewModel = ViewModelProviders.of(this).get(MarkdownViewModel::class.java)
this,
(application as MarkdownApplication).viewModelFactory
).get(MarkdownViewModel::class.java)
val adapter = EditPagerAdapter(supportFragmentManager, this@MainActivity) val adapter = EditPagerAdapter(supportFragmentManager, this@MainActivity)
pager.adapter = adapter pager.adapter = adapter
pager.addOnPageChangeListener(adapter) pager.addOnPageChangeListener(adapter)
@ -66,6 +63,11 @@ class MainActivity : AppCompatActivity(), ActivityCompat.OnRequestPermissionsRes
viewModel.fileName.observe(this, Observer<String> { viewModel.fileName.observe(this, Observer<String> {
title = it title = it
}) })
intent?.data?.let {
launch {
viewModel.load(this@MainActivity, it)
}
}
} }
override fun onUserLeaveHint() { override fun onUserLeaveHint() {
@ -114,11 +116,8 @@ class MainActivity : AppCompatActivity(), ActivityCompat.OnRequestPermissionsRes
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
android.R.id.home -> { android.R.id.home -> {
MainMenuFragment() MainMenuFragment().show(supportFragmentManager, null)
.apply { window.decorView.hideKeyboard()
errorHandler = (application as MarkdownApplication).errorHandler
}
.show(supportFragmentManager, null)
} }
R.id.action_save -> { R.id.action_save -> {
launch { launch {

View file

@ -3,24 +3,32 @@ package com.wbrawner.simplemarkdown.view.activity
import android.content.res.Configuration import android.content.res.Configuration
import android.os.Bundle import android.os.Bundle
import android.view.MenuItem import android.view.MenuItem
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import com.wbrawner.simplemarkdown.MarkdownApplication
import com.wbrawner.simplemarkdown.R import com.wbrawner.simplemarkdown.R
import com.wbrawner.simplemarkdown.utility.readAssetToString
import com.wbrawner.simplemarkdown.utility.toHtml
import kotlinx.android.synthetic.main.activity_markdown_info.* import kotlinx.android.synthetic.main.activity_markdown_info.*
import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext
class MarkdownInfoActivity : AppCompatActivity() { class MarkdownInfoActivity : AppCompatActivity(), CoroutineScope {
override val coroutineContext: CoroutineContext = Dispatchers.Main
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_markdown_info) setContentView(R.layout.activity_markdown_info)
setSupportActionBar(toolbar) setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
val intent = intent val title = intent?.getStringExtra(EXTRA_TITLE)
if (intent == null || !intent.hasExtra("title") || !intent.hasExtra("html")) { val fileName = intent?.getStringExtra(EXTRA_FILE)
if (title.isNullOrBlank() || fileName.isNullOrBlank()) {
finish() finish()
return return
} }
title = intent.getStringExtra("title")
val isNightMode = AppCompatDelegate.getDefaultNightMode() == val isNightMode = AppCompatDelegate.getDefaultNightMode() ==
AppCompatDelegate.MODE_NIGHT_YES AppCompatDelegate.MODE_NIGHT_YES
|| resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES || resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
@ -30,12 +38,29 @@ class MarkdownInfoActivity : AppCompatActivity() {
R.string.pref_custom_css_default R.string.pref_custom_css_default
} }
val css: String? = getString(defaultCssId) val css: String? = getString(defaultCssId)
launch {
try {
val html = assets?.readAssetToString(fileName)
?.toHtml()
?: throw RuntimeException("Unable to open stream to $fileName")
infoWebview.loadDataWithBaseURL(null,
String.format(FORMAT_CSS, css) + html,
"text/html",
"UTF-8", null
)
} catch (e: Exception) {
(application as MarkdownApplication).errorHandler.reportException(e)
Toast.makeText(this@MarkdownInfoActivity, R.string.file_load_error, Toast.LENGTH_SHORT).show()
finish()
}
}
}
infoWebview.loadDataWithBaseURL(null, override fun onDestroy() {
String.format(FORMAT_CSS, css) + intent.getStringExtra("html"), coroutineContext[Job]?.let {
"text/html", cancel()
"UTF-8", null }
) super.onDestroy()
} }
@ -48,8 +73,10 @@ class MarkdownInfoActivity : AppCompatActivity() {
} }
companion object { companion object {
var FORMAT_CSS = "<style>" + const val FORMAT_CSS = "<style>" +
"%s" + "%s" +
"</style>" "</style>"
const val EXTRA_TITLE = "title"
const val EXTRA_FILE = "file"
} }
} }

View file

@ -4,13 +4,10 @@ import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.preference.PreferenceManager
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.lifecycle.ViewModelProviders import androidx.preference.PreferenceManager
import com.wbrawner.simplemarkdown.MarkdownApplication
import com.wbrawner.simplemarkdown.R import com.wbrawner.simplemarkdown.R
import com.wbrawner.simplemarkdown.viewmodel.MarkdownViewModel
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
@ -18,15 +15,8 @@ class SplashActivity : AppCompatActivity(), CoroutineScope {
override val coroutineContext: CoroutineContext = Dispatchers.Main override val coroutineContext: CoroutineContext = Dispatchers.Main
lateinit var viewModel: MarkdownViewModel
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
viewModel = ViewModelProviders.of(
this,
(application as MarkdownApplication).viewModelFactory
).get(MarkdownViewModel::class.java)
launch { launch {
val darkMode = withContext(Dispatchers.IO) { val darkMode = withContext(Dispatchers.IO) {
val darkModeValue = PreferenceManager.getDefaultSharedPreferences(this@SplashActivity) val darkModeValue = PreferenceManager.getDefaultSharedPreferences(this@SplashActivity)
@ -40,7 +30,7 @@ class SplashActivity : AppCompatActivity(), CoroutineScope {
darkModeValue.equals(getString(R.string.pref_value_dark), ignoreCase = true) -> AppCompatDelegate.MODE_NIGHT_YES darkModeValue.equals(getString(R.string.pref_value_dark), ignoreCase = true) -> AppCompatDelegate.MODE_NIGHT_YES
else -> { else -> {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) { if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
AppCompatDelegate.MODE_NIGHT_AUTO AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY
} else { } else {
AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
} }
@ -50,21 +40,21 @@ class SplashActivity : AppCompatActivity(), CoroutineScope {
} }
AppCompatDelegate.setDefaultNightMode(darkMode) AppCompatDelegate.setDefaultNightMode(darkMode)
withContext(Dispatchers.IO) { val uri = withContext(Dispatchers.IO) {
var uri = intent?.data intent?.data
if (uri == null) { ?: PreferenceManager.getDefaultSharedPreferences(this@SplashActivity)
uri = PreferenceManager.getDefaultSharedPreferences(this@SplashActivity) .getString(
.getString( getString(R.string.pref_key_autosave_uri),
getString(R.string.pref_key_autosave_uri), null
null )?.let {
)?.let { Uri.parse(it)
Uri.parse(it) }
}
}
viewModel.load(this@SplashActivity, uri)
} }
val startIntent = Intent(this@SplashActivity, MainActivity::class.java) val startIntent = Intent(this@SplashActivity, MainActivity::class.java)
.apply {
data = uri
}
startActivity(startIntent) startActivity(startIntent)
finish() finish()
} }

View file

@ -3,7 +3,6 @@ package com.wbrawner.simplemarkdown.view.fragment
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.graphics.Color import android.graphics.Color
import android.os.Bundle import android.os.Bundle
import android.preference.PreferenceManager
import android.text.Editable import android.text.Editable
import android.text.SpannableString import android.text.SpannableString
import android.text.TextWatcher import android.text.TextWatcher
@ -19,7 +18,7 @@ import android.widget.TextView
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders import androidx.lifecycle.ViewModelProviders
import com.wbrawner.simplemarkdown.MarkdownApplication import androidx.preference.PreferenceManager
import com.wbrawner.simplemarkdown.R import com.wbrawner.simplemarkdown.R
import com.wbrawner.simplemarkdown.model.Readability import com.wbrawner.simplemarkdown.model.Readability
import com.wbrawner.simplemarkdown.utility.hideKeyboard import com.wbrawner.simplemarkdown.utility.hideKeyboard
@ -33,7 +32,7 @@ import kotlin.math.abs
class EditFragment : Fragment(), ViewPagerPage, CoroutineScope { class EditFragment : Fragment(), ViewPagerPage, CoroutineScope {
private var markdownEditor: EditText? = null private var markdownEditor: EditText? = null
private var markdownEditorScroller: ScrollView? = null private var markdownEditorScroller: ScrollView? = null
private lateinit var viewModel: MarkdownViewModel lateinit var viewModel: MarkdownViewModel
override val coroutineContext: CoroutineContext = Dispatchers.Main override val coroutineContext: CoroutineContext = Dispatchers.Main
private var readabilityWatcher: TextWatcher? = null private var readabilityWatcher: TextWatcher? = null
@ -45,6 +44,9 @@ class EditFragment : Fragment(), ViewPagerPage, CoroutineScope {
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
activity?.let {
viewModel = ViewModelProviders.of(it).get(MarkdownViewModel::class.java)
} ?: return
markdownEditor = view.findViewById(R.id.markdown_edit) markdownEditor = view.findViewById(R.id.markdown_edit)
markdownEditorScroller = view.findViewById(R.id.markdown_edit_container) markdownEditorScroller = view.findViewById(R.id.markdown_edit_container)
markdownEditor?.addTextChangedListener(object : TextWatcher { markdownEditor?.addTextChangedListener(object : TextWatcher {
@ -95,21 +97,9 @@ class EditFragment : Fragment(), ViewPagerPage, CoroutineScope {
} }
false false
} }
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProviders.of(
this,
(requireActivity().application as MarkdownApplication).viewModelFactory
).get(MarkdownViewModel::class.java)
viewModel.originalMarkdown.observe(this, Observer<String> { viewModel.originalMarkdown.observe(this, Observer<String> {
markdownEditor?.setText(it) markdownEditor?.setText(it)
}) })
}
override fun onStart() {
super.onStart()
launch { launch {
val enableReadability = withContext(Dispatchers.IO) { val enableReadability = withContext(Dispatchers.IO) {
context?.let { context?.let {
@ -132,11 +122,11 @@ class EditFragment : Fragment(), ViewPagerPage, CoroutineScope {
} }
} }
override fun onDestroy() { override fun onDestroyView() {
coroutineContext[Job]?.let { coroutineContext[Job]?.let {
cancel() cancel()
} }
super.onDestroy() super.onDestroyView()
} }
override fun onSelected() { override fun onSelected() {

View file

@ -1,31 +1,21 @@
package com.wbrawner.simplemarkdown.view.fragment package com.wbrawner.simplemarkdown.view.fragment
import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast
import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.wbrawner.simplemarkdown.R import com.wbrawner.simplemarkdown.R
import com.wbrawner.simplemarkdown.utility.ErrorHandler
import com.wbrawner.simplemarkdown.utility.readAssetToString
import com.wbrawner.simplemarkdown.utility.toHtml
import com.wbrawner.simplemarkdown.view.activity.MainActivity import com.wbrawner.simplemarkdown.view.activity.MainActivity
import com.wbrawner.simplemarkdown.view.activity.MarkdownInfoActivity import com.wbrawner.simplemarkdown.view.activity.MarkdownInfoActivity
import com.wbrawner.simplemarkdown.view.activity.MarkdownInfoActivity.Companion.EXTRA_FILE
import com.wbrawner.simplemarkdown.view.activity.MarkdownInfoActivity.Companion.EXTRA_TITLE
import com.wbrawner.simplemarkdown.view.activity.SettingsActivity import com.wbrawner.simplemarkdown.view.activity.SettingsActivity
import com.wbrawner.simplemarkdown.view.activity.SupportActivity import com.wbrawner.simplemarkdown.view.activity.SupportActivity
import kotlinx.android.synthetic.main.fragment_menu_main.* import kotlinx.android.synthetic.main.fragment_menu_main.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlin.coroutines.CoroutineContext
class MainMenuFragment : BottomSheetDialogFragment(), CoroutineScope {
override val coroutineContext: CoroutineContext = Dispatchers.Main
lateinit var errorHandler: ErrorHandler
class MainMenuFragment : BottomSheetDialogFragment() {
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
@ -34,20 +24,47 @@ class MainMenuFragment : BottomSheetDialogFragment(), CoroutineScope {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
mainMenuNavigationView.setNavigationItemSelectedListener { mainMenuNavigationView.setNavigationItemSelectedListener { menuItem ->
when (it.itemId) { val (intentClass, fileName, title) = when (menuItem.itemId) {
R.id.action_help -> showInfoActivity(context, R.id.action_help) R.id.action_help -> Triple(
R.id.action_settings -> { MarkdownInfoActivity::class.java,
val settingsIntent = Intent(context, SettingsActivity::class.java) "Cheatsheet.md",
startActivityForResult(settingsIntent, MainActivity.REQUEST_DARK_MODE) R.string.action_help
} )
R.id.action_libraries -> showInfoActivity(context, R.id.action_libraries) R.id.action_settings -> Triple(
R.id.action_privacy -> showInfoActivity(context, R.id.action_privacy) SettingsActivity::class.java,
R.id.action_support -> Intent(context, SupportActivity::class.java) null,
.apply { null
startActivity(this) )
dialog?.dismiss() R.id.action_libraries -> Triple(
} MarkdownInfoActivity::class.java,
"Libraries.md",
R.id.action_libraries
)
R.id.action_privacy -> Triple(
MarkdownInfoActivity::class.java,
"Privacy Policy.md",
R.id.action_libraries
)
R.id.action_support -> Triple(
SupportActivity::class.java,
null,
null
)
else -> throw IllegalStateException("This shouldn't happen")
}
val intent = Intent(context, intentClass)
fileName?.let {
intent.putExtra(EXTRA_FILE, it)
}
title?.let {
intent.putExtra(EXTRA_TITLE, getString(it))
}
if (intentClass == SettingsActivity::class.java) {
startActivityForResult(intent, MainActivity.REQUEST_DARK_MODE)
} else {
startActivity(intent)
dialog?.dismiss()
} }
true true
} }
@ -61,40 +78,4 @@ class MainMenuFragment : BottomSheetDialogFragment(), CoroutineScope {
} }
super.onActivityResult(requestCode, resultCode, data) super.onActivityResult(requestCode, resultCode, data)
} }
private fun showInfoActivity(context: Context?, action: Int) {
val infoIntent = Intent(context, MarkdownInfoActivity::class.java)
var fileName = ""
var title = ""
when (action) {
R.id.action_help -> {
fileName = "Cheatsheet.md"
title = getString(R.string.action_help)
}
R.id.action_libraries -> {
fileName = "Libraries.md"
title = getString(R.string.action_libraries)
}
R.id.action_privacy -> {
fileName = "Privacy Policy.md"
title = getString(R.string.action_privacy)
}
}
infoIntent.putExtra("title", title)
launch {
// TODO: Refactor this to have the info activity load the markdown instead of doing
// it here
try {
val html = context?.assets?.readAssetToString(fileName)
?.toHtml()
?: throw RuntimeException("Unable to open stream to $fileName")
infoIntent.putExtra("html", html)
startActivity(infoIntent, null)
} catch (e: Exception) {
errorHandler.reportException(e)
Toast.makeText(context, R.string.file_load_error, Toast.LENGTH_SHORT).show()
}
dialog?.dismiss()
}
}
} }

View file

@ -3,7 +3,6 @@ package com.wbrawner.simplemarkdown.view.fragment
import android.content.res.Configuration.UI_MODE_NIGHT_MASK import android.content.res.Configuration.UI_MODE_NIGHT_MASK
import android.content.res.Configuration.UI_MODE_NIGHT_YES import android.content.res.Configuration.UI_MODE_NIGHT_YES
import android.os.Bundle import android.os.Bundle
import android.preference.PreferenceManager
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -12,8 +11,8 @@ import androidx.appcompat.app.AppCompatDelegate
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders import androidx.lifecycle.ViewModelProviders
import androidx.preference.PreferenceManager
import com.wbrawner.simplemarkdown.BuildConfig import com.wbrawner.simplemarkdown.BuildConfig
import com.wbrawner.simplemarkdown.MarkdownApplication
import com.wbrawner.simplemarkdown.R import com.wbrawner.simplemarkdown.R
import com.wbrawner.simplemarkdown.utility.toHtml import com.wbrawner.simplemarkdown.utility.toHtml
import com.wbrawner.simplemarkdown.viewmodel.MarkdownViewModel import com.wbrawner.simplemarkdown.viewmodel.MarkdownViewModel
@ -35,14 +34,9 @@ class PreviewFragment : Fragment(), CoroutineScope {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
markdownPreview = view.findViewById(R.id.markdown_view) markdownPreview = view.findViewById(R.id.markdown_view)
WebView.setWebContentsDebuggingEnabled(BuildConfig.DEBUG) WebView.setWebContentsDebuggingEnabled(BuildConfig.DEBUG)
} activity?.let {
viewModel = ViewModelProviders.of(it).get(MarkdownViewModel::class.java)
override fun onActivityCreated(savedInstanceState: Bundle?) { } ?: return
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProviders.of(
this,
(requireActivity().application as MarkdownApplication).viewModelFactory
).get(MarkdownViewModel::class.java)
launch { launch {
val isNightMode = AppCompatDelegate.getDefaultNightMode() == val isNightMode = AppCompatDelegate.getDefaultNightMode() ==
AppCompatDelegate.MODE_NIGHT_YES AppCompatDelegate.MODE_NIGHT_YES
@ -53,18 +47,19 @@ class PreviewFragment : Fragment(), CoroutineScope {
R.string.pref_custom_css_default R.string.pref_custom_css_default
} }
val css = withContext(Dispatchers.IO) { val css = withContext(Dispatchers.IO) {
val context = context ?: return@withContext null
@Suppress("ConstantConditionIf") @Suppress("ConstantConditionIf")
if (!BuildConfig.ENABLE_CUSTOM_CSS) { if (!BuildConfig.ENABLE_CUSTOM_CSS) {
requireActivity().getString(defaultCssId) context.getString(defaultCssId)
} else { } else {
PreferenceManager.getDefaultSharedPreferences(requireActivity()) PreferenceManager.getDefaultSharedPreferences(context)
.getString( .getString(
getString(R.string.pref_custom_css), getString(R.string.pref_custom_css),
getString(defaultCssId) getString(defaultCssId)
) ?: "" )
} }
} }
style = String.format(FORMAT_CSS, css) style = String.format(FORMAT_CSS, css ?: "")
updateWebContent(viewModel.markdownUpdates.value ?: "") updateWebContent(viewModel.markdownUpdates.value ?: "")
viewModel.markdownUpdates.observe(this@PreviewFragment, Observer<String> { viewModel.markdownUpdates.observe(this@PreviewFragment, Observer<String> {
updateWebContent(it) updateWebContent(it)
@ -85,6 +80,9 @@ class PreviewFragment : Fragment(), CoroutineScope {
} }
override fun onDestroyView() { override fun onDestroyView() {
coroutineContext[Job]?.let {
cancel()
}
markdownPreview?.let { markdownPreview?.let {
(it.parent as ViewGroup).removeView(it) (it.parent as ViewGroup).removeView(it)
it.destroy() it.destroy()
@ -93,13 +91,6 @@ class PreviewFragment : Fragment(), CoroutineScope {
super.onDestroyView() super.onDestroyView()
} }
override fun onDestroy() {
coroutineContext[Job]?.let {
cancel()
}
super.onDestroy()
}
companion object { companion object {
var FORMAT_CSS = "<style>" + var FORMAT_CSS = "<style>" +
"%s" + "%s" +

View file

@ -3,61 +3,42 @@ package com.wbrawner.simplemarkdown.view.fragment
import android.content.SharedPreferences import android.content.SharedPreferences
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.StrictMode
import android.preference.ListPreference
import android.preference.Preference
import android.preference.PreferenceFragment
import android.preference.PreferenceManager
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.preference.ListPreference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceManager
import com.wbrawner.simplemarkdown.BuildConfig import com.wbrawner.simplemarkdown.BuildConfig
import com.wbrawner.simplemarkdown.R import com.wbrawner.simplemarkdown.R
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.lang.Exception
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
class SettingsFragment class SettingsFragment
: PreferenceFragment(), : PreferenceFragmentCompat(),
SharedPreferences.OnSharedPreferenceChangeListener, SharedPreferences.OnSharedPreferenceChangeListener,
CoroutineScope { CoroutineScope {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.pref_general)
}
override val coroutineContext: CoroutineContext = Dispatchers.Main override val coroutineContext: CoroutineContext = Dispatchers.Main
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)
launch { launch(context = Dispatchers.IO) {
withContext(Dispatchers.IO) { val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity)
try { sharedPreferences.registerOnSharedPreferenceChangeListener(this@SettingsFragment)
// This can be thrown when recreating the activity for theme changes (findPreference(getString(R.string.pref_key_dark_mode)) as? ListPreference)?.let {
addPreferencesFromResource(R.xml.pref_general) setListPreferenceSummary(sharedPreferences, it)
} catch (ignored: Exception) { }
return@withContext @Suppress("ConstantConditionIf")
} if (!BuildConfig.ENABLE_CUSTOM_CSS) {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity) preferenceScreen.removePreference(findPreference(getString(R.string.pref_custom_css)))
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)))
}
} }
} }
} }
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.preference_list_fragment_safe, container, false)
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) { override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
if (!isAdded) return if (!isAdded) return
val preference = findPreference(key) as? ListPreference ?: return val preference = findPreference(key) as? ListPreference ?: return
@ -66,7 +47,7 @@ class SettingsFragment
return return
} }
var darkMode: Int = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) { var darkMode: Int = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
AppCompatDelegate.MODE_NIGHT_AUTO AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY
} else { } else {
AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
} }

View file

@ -1,20 +0,0 @@
package com.wbrawner.simplemarkdown.view.overrides
import android.content.Context
import android.util.AttributeSet
import android.widget.ListView
class SafeListView : ListView {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
override fun onDetachedFromWindow() {
try {
super.onDetachedFromWindow()
} catch (ignored: Exception) {
}
}
}

View file

@ -4,7 +4,6 @@ import android.content.Context
import android.net.Uri import android.net.Uri
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.wbrawner.simplemarkdown.utility.getName import com.wbrawner.simplemarkdown.utility.getName
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -78,7 +77,7 @@ class MarkdownViewModel : ViewModel() {
return try { return try {
withContext(coroutineContext) { withContext(coroutineContext) {
outputStream.writer().use { outputStream.writer().use {
it.write(markdownUpdates.value) it.write(markdownUpdates.value ?: "")
} }
} }
true true
@ -93,14 +92,3 @@ class MarkdownViewModel : ViewModel() {
markdownUpdates.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
}
}

View file

@ -38,5 +38,4 @@
android:background="@color/colorBackground" /> android:background="@color/colorBackground" />
</com.google.android.material.card.MaterialCardView> </com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="com.wbrawner.simplemarkdown.view.activity.ExplorerActivity"
tools:showIn="@layout/activity_explorer">
<com.wbrawner.simplemarkdown.view.overrides.SafeListView
android:id="@+id/file_list"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/transparent"
android:orientation="vertical">
<com.wbrawner.simplemarkdown.view.overrides.SafeListView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>