Add Save as... and improve preview/edit performance

This commit is contained in:
William 'Billy' Brawner 2019-08-18 20:55:25 -07:00
parent 19e678f150
commit fa1dfb9141
6 changed files with 116 additions and 85 deletions

View file

@ -15,11 +15,11 @@ class MarkdownApplication : Application() {
.detectAll() .detectAll()
.penaltyDeath() .penaltyDeath()
.build()) .build())
// StrictMode.setVmPolicy(StrictMode.VmPolicy.Builder() StrictMode.setVmPolicy(StrictMode.VmPolicy.Builder()
// .detectAll() .detectAll()
// .penaltyLog() .penaltyLog()
// .penaltyDeath() .penaltyDeath()
// .build()) .build())
} }
super.onCreate() super.onCreate()
} }

View file

@ -95,7 +95,7 @@ class MainActivity : AppCompatActivity(), ActivityCompat.OnRequestPermissionsRes
} else { } else {
null null
} }
}?: return@withContext } ?: return@withContext
sharedPrefs.edit() sharedPrefs.edit()
.putString(getString(R.string.pref_key_autosave_uri), uri.toString()) .putString(getString(R.string.pref_key_autosave_uri), uri.toString())
.apply() .apply()
@ -131,6 +131,9 @@ class MainActivity : AppCompatActivity(), ActivityCompat.OnRequestPermissionsRes
} }
} }
} }
R.id.action_save_as -> {
requestFileOp(REQUEST_SAVE_FILE)
}
R.id.action_share -> { R.id.action_share -> {
val shareIntent = Intent(Intent.ACTION_SEND) val shareIntent = Intent(Intent.ACTION_SEND)
shareIntent.putExtra(Intent.EXTRA_TEXT, viewModel.markdownUpdates.value) shareIntent.putExtra(Intent.EXTRA_TEXT, viewModel.markdownUpdates.value)

View file

@ -35,6 +35,7 @@ class EditFragment : Fragment(), ViewPagerPage, CoroutineScope {
private var markdownEditorScroller: ScrollView? = null private var markdownEditorScroller: ScrollView? = null
private lateinit var viewModel: MarkdownViewModel private lateinit var viewModel: MarkdownViewModel
override val coroutineContext: CoroutineContext = Dispatchers.Main override val coroutineContext: CoroutineContext = Dispatchers.Main
private var readabilityWatcher: TextWatcher? = null
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
@ -71,56 +72,6 @@ class EditFragment : Fragment(), ViewPagerPage, CoroutineScope {
} }
}) })
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 = ""
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 (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)
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 = searchFor
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 touchDown = 0L
var oldX = 0f var oldX = 0f
@ -157,6 +108,30 @@ class EditFragment : Fragment(), ViewPagerPage, CoroutineScope {
}) })
} }
override fun onStart() {
super.onStart()
launch {
val enableReadability = withContext(Dispatchers.IO) {
context?.let {
PreferenceManager.getDefaultSharedPreferences(it)
.getBoolean(getString(R.string.readability_enabled), false)
}?: false
}
if (enableReadability) {
if (readabilityWatcher == null) {
readabilityWatcher = ReadabilityTextWatcher()
}
markdownEditor?.addTextChangedListener(readabilityWatcher)
} else {
readabilityWatcher?.let {
markdownEditor?.removeTextChangedListener(it)
}
readabilityWatcher = null
}
}
}
override fun onDestroy() { override fun onDestroy() {
coroutineContext[Job]?.let { coroutineContext[Job]?.let {
cancel() cancel()
@ -171,4 +146,45 @@ class EditFragment : Fragment(), ViewPagerPage, CoroutineScope {
override fun onDeselected() { override fun onDeselected() {
markdownEditor?.hideKeyboard() markdownEditor?.hideKeyboard()
} }
inner class ReadabilityTextWatcher : 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 (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)
if (sentence.syllableCount() > 35) color = Color.argb(100, 193, 66, 66)
Log.d("SimpleMarkdown", "Sentence start: ${sentence.start()} end: ${sentence.end()}")
span.setSpan(BackgroundColorSpan(color), sentence.start(), sentence.end(), 0)
}
markdownEditor?.setTextKeepState(span, TextView.BufferType.SPANNABLE)
previousValue = searchFor
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) {
}
}
} }

View file

@ -24,6 +24,7 @@ class PreviewFragment : Fragment(), CoroutineScope {
override val coroutineContext: CoroutineContext = Dispatchers.Main override val coroutineContext: CoroutineContext = Dispatchers.Main
lateinit var viewModel: MarkdownViewModel lateinit var viewModel: MarkdownViewModel
private var markdownPreview: WebView? = null private var markdownPreview: WebView? = null
private var style: String = ""
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
@ -42,38 +43,45 @@ class PreviewFragment : Fragment(), CoroutineScope {
this, this,
(requireActivity().application as MarkdownApplication).viewModelFactory (requireActivity().application as MarkdownApplication).viewModelFactory
).get(MarkdownViewModel::class.java) ).get(MarkdownViewModel::class.java)
viewModel.markdownUpdates.observe(this, Observer<String> { launch {
markdownPreview?.post { val isNightMode = AppCompatDelegate.getDefaultNightMode() ==
val isNightMode = AppCompatDelegate.getDefaultNightMode() == AppCompatDelegate.MODE_NIGHT_YES
AppCompatDelegate.MODE_NIGHT_YES || context!!.resources.configuration.uiMode and UI_MODE_NIGHT_MASK == UI_MODE_NIGHT_YES
|| context!!.resources.configuration.uiMode and UI_MODE_NIGHT_MASK == UI_MODE_NIGHT_YES val defaultCssId = if (isNightMode) {
val defaultCssId = if (isNightMode) { R.string.pref_custom_css_default_dark
R.string.pref_custom_css_default_dark } else {
R.string.pref_custom_css_default
}
val css = withContext(Dispatchers.IO) {
@Suppress("ConstantConditionIf")
if (!BuildConfig.ENABLE_CUSTOM_CSS) {
requireActivity().getString(defaultCssId)
} else { } else {
R.string.pref_custom_css_default PreferenceManager.getDefaultSharedPreferences(requireActivity())
} .getString(
launch { getString(R.string.pref_custom_css),
val css = withContext(Dispatchers.IO) { getString(defaultCssId)
@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
)
} }
} }
}) style = String.format(FORMAT_CSS, css)
updateWebContent(viewModel.markdownUpdates.value ?: "")
viewModel.markdownUpdates.observe(this@PreviewFragment, Observer<String> {
updateWebContent(it)
})
}
}
private fun updateWebContent(markdown: String) {
markdownPreview?.post {
launch {
markdownPreview?.loadDataWithBaseURL(null,
style + markdown.toHtml(),
"text/html",
"UTF-8", null
)
}
}
} }
override fun onDestroyView() { override fun onDestroyView() {

View file

@ -17,6 +17,9 @@
<item <item
android:id="@+id/action_save" android:id="@+id/action_save"
android:title="@string/action_save" /> android:title="@string/action_save" />
<item
android:id="@+id/action_save_as"
android:title="@string/action_save_as" />
<item <item
android:id="@+id/action_lock_swipe" android:id="@+id/action_lock_swipe"
android:checkable="true" android:checkable="true"

View file

@ -64,6 +64,7 @@
<string name="save_changes">Save Changes</string> <string name="save_changes">Save Changes</string>
<string name="prompt_save_changes">Would you like to save your changes?</string> <string name="prompt_save_changes">Would you like to save your changes?</string>
<string name="action_discard">Discard</string> <string name="action_discard">Discard</string>
<string name="action_save_as">Save as...</string>
<string-array name="pref_entries_dark_mode"> <string-array name="pref_entries_dark_mode">
<item>@string/pref_value_light</item> <item>@string/pref_value_light</item>
<item>@string/pref_value_dark</item> <item>@string/pref_value_dark</item>