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 com.wbrawner.simplemarkdown.utility.CrashlyticsErrorHandler
import com.wbrawner.simplemarkdown.utility.ErrorHandler
import com.wbrawner.simplemarkdown.viewmodel.MarkdownViewModelFactory
class MarkdownApplication : Application() {
val viewModelFactory: MarkdownViewModelFactory by lazy {
MarkdownViewModelFactory()
}
val errorHandler: ErrorHandler by lazy {
CrashlyticsErrorHandler()
}

View file

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

View file

@ -3,24 +3,32 @@ package com.wbrawner.simplemarkdown.view.activity
import android.content.res.Configuration
import android.os.Bundle
import android.view.MenuItem
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import com.wbrawner.simplemarkdown.MarkdownApplication
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.coroutines.*
import kotlin.coroutines.CoroutineContext
class MarkdownInfoActivity : AppCompatActivity() {
class MarkdownInfoActivity : AppCompatActivity(), CoroutineScope {
override val coroutineContext: CoroutineContext = Dispatchers.Main
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_markdown_info)
setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
val intent = intent
if (intent == null || !intent.hasExtra("title") || !intent.hasExtra("html")) {
val title = intent?.getStringExtra(EXTRA_TITLE)
val fileName = intent?.getStringExtra(EXTRA_FILE)
if (title.isNullOrBlank() || fileName.isNullOrBlank()) {
finish()
return
}
title = intent.getStringExtra("title")
val isNightMode = AppCompatDelegate.getDefaultNightMode() ==
AppCompatDelegate.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
}
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) + intent.getStringExtra("html"),
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()
}
}
}
override fun onDestroy() {
coroutineContext[Job]?.let {
cancel()
}
super.onDestroy()
}
@ -48,8 +73,10 @@ class MarkdownInfoActivity : AppCompatActivity() {
}
companion object {
var FORMAT_CSS = "<style>" +
const val FORMAT_CSS = "<style>" +
"%s" +
"</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.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 androidx.preference.PreferenceManager
import com.wbrawner.simplemarkdown.R
import com.wbrawner.simplemarkdown.viewmodel.MarkdownViewModel
import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext
@ -18,15 +15,8 @@ class SplashActivity : AppCompatActivity(), CoroutineScope {
override val coroutineContext: CoroutineContext = Dispatchers.Main
lateinit var viewModel: MarkdownViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProviders.of(
this,
(application as MarkdownApplication).viewModelFactory
).get(MarkdownViewModel::class.java)
launch {
val darkMode = withContext(Dispatchers.IO) {
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
else -> {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
AppCompatDelegate.MODE_NIGHT_AUTO
AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY
} else {
AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
}
@ -50,10 +40,9 @@ class SplashActivity : AppCompatActivity(), CoroutineScope {
}
AppCompatDelegate.setDefaultNightMode(darkMode)
withContext(Dispatchers.IO) {
var uri = intent?.data
if (uri == null) {
uri = PreferenceManager.getDefaultSharedPreferences(this@SplashActivity)
val uri = withContext(Dispatchers.IO) {
intent?.data
?: PreferenceManager.getDefaultSharedPreferences(this@SplashActivity)
.getString(
getString(R.string.pref_key_autosave_uri),
null
@ -62,9 +51,10 @@ class SplashActivity : AppCompatActivity(), CoroutineScope {
}
}
viewModel.load(this@SplashActivity, uri)
}
val startIntent = Intent(this@SplashActivity, MainActivity::class.java)
.apply {
data = uri
}
startActivity(startIntent)
finish()
}

View file

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

View file

@ -1,31 +1,21 @@
package com.wbrawner.simplemarkdown.view.fragment
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
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.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.SupportActivity
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(
inflater: LayoutInflater,
container: ViewGroup?,
@ -34,21 +24,48 @@ class MainMenuFragment : BottomSheetDialogFragment(), CoroutineScope {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mainMenuNavigationView.setNavigationItemSelectedListener {
when (it.itemId) {
R.id.action_help -> showInfoActivity(context, R.id.action_help)
R.id.action_settings -> {
val settingsIntent = Intent(context, SettingsActivity::class.java)
startActivityForResult(settingsIntent, MainActivity.REQUEST_DARK_MODE)
mainMenuNavigationView.setNavigationItemSelectedListener { menuItem ->
val (intentClass, fileName, title) = when (menuItem.itemId) {
R.id.action_help -> Triple(
MarkdownInfoActivity::class.java,
"Cheatsheet.md",
R.string.action_help
)
R.id.action_settings -> Triple(
SettingsActivity::class.java,
null,
null
)
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")
}
R.id.action_libraries -> showInfoActivity(context, R.id.action_libraries)
R.id.action_privacy -> showInfoActivity(context, R.id.action_privacy)
R.id.action_support -> Intent(context, SupportActivity::class.java)
.apply {
startActivity(this)
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
}
}
@ -61,40 +78,4 @@ class MainMenuFragment : BottomSheetDialogFragment(), CoroutineScope {
}
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_YES
import android.os.Bundle
import android.preference.PreferenceManager
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@ -12,8 +11,8 @@ import androidx.appcompat.app.AppCompatDelegate
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import androidx.preference.PreferenceManager
import com.wbrawner.simplemarkdown.BuildConfig
import com.wbrawner.simplemarkdown.MarkdownApplication
import com.wbrawner.simplemarkdown.R
import com.wbrawner.simplemarkdown.utility.toHtml
import com.wbrawner.simplemarkdown.viewmodel.MarkdownViewModel
@ -35,14 +34,9 @@ class PreviewFragment : Fragment(), CoroutineScope {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
markdownPreview = view.findViewById(R.id.markdown_view)
WebView.setWebContentsDebuggingEnabled(BuildConfig.DEBUG)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProviders.of(
this,
(requireActivity().application as MarkdownApplication).viewModelFactory
).get(MarkdownViewModel::class.java)
activity?.let {
viewModel = ViewModelProviders.of(it).get(MarkdownViewModel::class.java)
} ?: return
launch {
val isNightMode = AppCompatDelegate.getDefaultNightMode() ==
AppCompatDelegate.MODE_NIGHT_YES
@ -53,18 +47,19 @@ class PreviewFragment : Fragment(), CoroutineScope {
R.string.pref_custom_css_default
}
val css = withContext(Dispatchers.IO) {
val context = context ?: return@withContext null
@Suppress("ConstantConditionIf")
if (!BuildConfig.ENABLE_CUSTOM_CSS) {
requireActivity().getString(defaultCssId)
context.getString(defaultCssId)
} else {
PreferenceManager.getDefaultSharedPreferences(requireActivity())
PreferenceManager.getDefaultSharedPreferences(context)
.getString(
getString(R.string.pref_custom_css),
getString(defaultCssId)
) ?: ""
)
}
}
style = String.format(FORMAT_CSS, css)
style = String.format(FORMAT_CSS, css ?: "")
updateWebContent(viewModel.markdownUpdates.value ?: "")
viewModel.markdownUpdates.observe(this@PreviewFragment, Observer<String> {
updateWebContent(it)
@ -85,6 +80,9 @@ class PreviewFragment : Fragment(), CoroutineScope {
}
override fun onDestroyView() {
coroutineContext[Job]?.let {
cancel()
}
markdownPreview?.let {
(it.parent as ViewGroup).removeView(it)
it.destroy()
@ -93,13 +91,6 @@ class PreviewFragment : Fragment(), CoroutineScope {
super.onDestroyView()
}
override fun onDestroy() {
coroutineContext[Job]?.let {
cancel()
}
super.onDestroy()
}
companion object {
var FORMAT_CSS = "<style>" +
"%s" +

View file

@ -3,40 +3,30 @@ 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
import android.preference.PreferenceManager
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
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.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(),
: PreferenceFragmentCompat(),
SharedPreferences.OnSharedPreferenceChangeListener,
CoroutineScope {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.pref_general)
}
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
}
launch(context = Dispatchers.IO) {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity)
sharedPreferences.registerOnSharedPreferenceChangeListener(this@SettingsFragment)
(findPreference(getString(R.string.pref_key_dark_mode)) as? ListPreference)?.let {
@ -48,15 +38,6 @@ class SettingsFragment
}
}
}
}
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) {
if (!isAdded) return
@ -66,7 +47,7 @@ class SettingsFragment
return
}
var darkMode: Int = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
AppCompatDelegate.MODE_NIGHT_AUTO
AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY
} else {
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 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
@ -78,7 +77,7 @@ class MarkdownViewModel : ViewModel() {
return try {
withContext(coroutineContext) {
outputStream.writer().use {
it.write(markdownUpdates.value)
it.write(markdownUpdates.value ?: "")
}
}
true
@ -93,14 +92,3 @@ class MarkdownViewModel : ViewModel() {
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" />
</com.google.android.material.card.MaterialCardView>
</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>