Address lint issues
Some checks failed
Build & Test / Validate (pull_request) Successful in 19s
Build & Test / Run Unit Tests (pull_request) Successful in 10m34s
Build & Test / Run UI Tests (pull_request) Failing after 4m1s

This commit is contained in:
William Brawner 2024-07-31 20:17:14 -06:00
parent 06cbc5ec31
commit 86cd33ff5f
Signed by: wbrawner
GPG key ID: 8FF12381C6C90D35
28 changed files with 157 additions and 134 deletions

View file

@ -97,6 +97,9 @@ android {
commit.set(true)
}
}
lint {
warningsAsErrors = true
}
}
play {

View file

@ -4,6 +4,7 @@ import android.os.Build
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.compose.animation.AnimatedContentTransitionScope
@ -27,6 +28,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.core.app.ActivityCompat
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.WindowCompat
@ -135,21 +137,21 @@ class MainActivity : AppCompatActivity(), ActivityCompat.OnRequestPermissionsRes
}
composable(Route.HELP.path) {
MarkdownInfoScreen(
title = Route.HELP.title,
title = stringResource(Route.HELP.title),
file = "Cheatsheet.md",
navController = navController
)
}
composable(Route.ABOUT.path) {
MarkdownInfoScreen(
title = Route.ABOUT.title,
title = stringResource(Route.ABOUT.title),
file = "Libraries.md",
navController = navController
)
}
composable(Route.PRIVACY.path) {
MarkdownInfoScreen(
title = Route.PRIVACY.title,
title = stringResource(Route.PRIVACY.title),
file = "Privacy Policy.md",
navController = navController
)
@ -162,13 +164,14 @@ class MainActivity : AppCompatActivity(), ActivityCompat.OnRequestPermissionsRes
enum class Route(
val path: String,
val title: String,
@StringRes
val title: Int,
val icon: ImageVector
) {
EDITOR("/", "Editor", Icons.Default.Edit),
SETTINGS("/settings", "Settings", Icons.Default.Settings),
SUPPORT("/support", "Support SimpleMarkdown", Icons.Default.Favorite),
HELP("/help", "Help", Icons.AutoMirrored.Filled.Help),
ABOUT("/about", "About", Icons.Default.Info),
PRIVACY("/privacy", "Privacy", Icons.Default.PrivacyTip),
EDITOR("/", R.string.title_editor, Icons.Default.Edit),
SETTINGS("/settings", R.string.title_settings, Icons.Default.Settings),
SUPPORT("/support", R.string.support_title, Icons.Default.Favorite),
HELP("/help", R.string.title_help, Icons.AutoMirrored.Filled.Help),
ABOUT("/about", R.string.title_about, Icons.Default.Info),
PRIVACY("/privacy", R.string.action_privacy, Icons.Default.PrivacyTip),
}

View file

@ -9,13 +9,16 @@ import com.wbrawner.simplemarkdown.utility.FileHelper
import com.wbrawner.simplemarkdown.utility.PersistentTree
import com.wbrawner.simplemarkdown.utility.PreferenceHelper
import com.wbrawner.simplemarkdown.utility.ReviewHelper
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import timber.log.Timber
import java.io.File
class MarkdownApplication : Application() {
private val coroutineScope = CoroutineScope(Dispatchers.Default)
override fun onCreate() {
if (BuildConfig.DEBUG) {
StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.Builder()
@ -27,9 +30,9 @@ class MarkdownApplication : Application() {
.penaltyLog()
.build())
Timber.plant(Timber.DebugTree())
GlobalScope.launch {
coroutineScope.launch {
try {
Timber.plant(PersistentTree.create(File(getExternalFilesDir(null), "logs")))
Timber.plant(PersistentTree.create(coroutineScope, File(getExternalFilesDir(null), "logs")))
} catch (e: Exception) {
Timber.e(e, "Unable to create PersistentTree")
}

View file

@ -1,5 +1,6 @@
package com.wbrawner.simplemarkdown
import androidx.annotation.StringRes
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
@ -22,7 +23,7 @@ data class EditorState(
val fileName: String = "Untitled.md",
val markdown: String = "",
val path: URI? = null,
val toast: String? = null,
val toast: ParameterizedText? = null,
val alert: AlertDialogModel? = null,
val saveCallback: (() -> Unit)? = null,
/**
@ -95,7 +96,7 @@ class MarkdownViewModel(
markdown = content,
initialMarkdown = content,
reloadToggle = currentState.reloadToggle.inv(),
toast = "Successfully loaded $name"
toast = ParameterizedText(R.string.file_loaded)
)
preferenceHelper[Preference.AUTOSAVE_URI] = actualLoadPath
} ?: throw IllegalStateException("Opened file was null")
@ -103,8 +104,8 @@ class MarkdownViewModel(
Timber.e(e, "Failed to open file at path: $actualLoadPath")
_state.value = _state.value.copy(
alert = AlertDialogModel(
text = "Failed to open file at path: $actualLoadPath",
confirmButton = AlertDialogModel.ButtonModel("OK", onClick = ::dismissAlert)
text = ParameterizedText(R.string.file_load_error),
confirmButton = AlertDialogModel.ButtonModel(ParameterizedText(R.string.ok), onClick = ::dismissAlert)
)
)
}
@ -130,20 +131,19 @@ class MarkdownViewModel(
fileName = name,
path = actualSavePath,
initialMarkdown = currentState.markdown,
toast = if (interactive) "Successfully saved $name" else null
toast = if (interactive) ParameterizedText(R.string.file_saved, arrayOf(name)) else null
)
Timber.i("Saved file $name to uri $actualSavePath")
Timber.i("Persisting autosave uri in shared prefs: $actualSavePath")
preferenceHelper[Preference.AUTOSAVE_URI] = actualSavePath
true
} catch (e: Exception) {
val message = "Failed to save file to $actualSavePath"
Timber.e(e, message)
Timber.e(e, "Failed to save file to $actualSavePath")
_state.value = _state.value.copy(
alert = AlertDialogModel(
text = message,
text = ParameterizedText(R.string.file_save_error),
confirmButton = AlertDialogModel.ButtonModel(
text = "OK",
text = ParameterizedText(R.string.ok),
onClick = ::dismissAlert
)
)
@ -189,9 +189,9 @@ class MarkdownViewModel(
Timber.i("Resetting view model to default state")
if (!force && _state.value.dirty) {
_state.value = _state.value.copy(alert = AlertDialogModel(
text = "Would you like to save your changes?",
text = ParameterizedText(R.string.prompt_save_changes),
confirmButton = AlertDialogModel.ButtonModel(
text = "Yes",
text = ParameterizedText(R.string.yes),
onClick = {
_state.value = _state.value.copy(
saveCallback = {
@ -201,7 +201,7 @@ class MarkdownViewModel(
}
),
dismissButton = AlertDialogModel.ButtonModel(
text = "No",
text = ParameterizedText(R.string.no),
onClick = {
reset(untitledFileName, true)
}
@ -231,9 +231,29 @@ class MarkdownViewModel(
}
data class AlertDialogModel(
val text: String,
val text: ParameterizedText,
val confirmButton: ButtonModel,
val dismissButton: ButtonModel? = null
) {
data class ButtonModel(val text: String, val onClick: () -> Unit)
data class ButtonModel(val text: ParameterizedText, val onClick: () -> Unit)
}
data class ParameterizedText(@StringRes val text: Int, val params: Array<Any> = arrayOf()) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as ParameterizedText
if (text != other.text) return false
if (!params.contentEquals(other.params)) return false
return true
}
override fun hashCode(): Int {
var result = text
result = 31 * result + params.contentHashCode()
return result
}
}

View file

@ -52,15 +52,18 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat.startActivity
import androidx.navigation.NavController
import com.wbrawner.simplemarkdown.AlertDialogModel
import com.wbrawner.simplemarkdown.EditorState
import com.wbrawner.simplemarkdown.MarkdownViewModel
import com.wbrawner.simplemarkdown.ParameterizedText
import com.wbrawner.simplemarkdown.R
import com.wbrawner.simplemarkdown.Route
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
@ -98,7 +101,7 @@ fun MainScreen(
initialMarkdown = initialMarkdown,
markdown = markdown,
setMarkdown = viewModel::updateMarkdown,
message = toast,
message = toast?.stringRes(),
dismissMessage = viewModel::dismissToast,
alert = alert,
dismissAlert = viewModel::dismissAlert,
@ -181,17 +184,17 @@ private fun MainScreen(
onDismissRequest = dismissAlert,
confirmButton = {
TextButton(onClick = it.confirmButton.onClick) {
Text(it.confirmButton.text)
Text(stringResource(it.confirmButton.text.text))
}
},
dismissButton = {
it.dismissButton?.let { dismissButton ->
TextButton(onClick = dismissButton.onClick) {
Text(dismissButton.text)
Text(dismissButton.text.stringRes())
}
}
},
text = { Text(it.text) }
text = { Text(it.text.stringRes()) }
)
}
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
@ -215,28 +218,28 @@ private fun MainScreen(
), null
)
}) {
Icon(imageVector = Icons.Default.Share, contentDescription = "Share")
Icon(imageVector = Icons.Default.Share, contentDescription = stringResource(R.string.action_share))
}
Box {
var menuExpanded by remember { mutableStateOf(false) }
IconButton(onClick = { menuExpanded = true }) {
Icon(imageVector = Icons.Default.MoreVert, "Editor Actions")
Icon(imageVector = Icons.Default.MoreVert, stringResource(R.string.action_editor_actions))
}
DropdownMenu(expanded = menuExpanded,
onDismissRequest = { menuExpanded = false }) {
DropdownMenuItem(text = { Text("New") }, onClick = {
DropdownMenuItem(text = { Text(stringResource(R.string.action_new)) }, onClick = {
menuExpanded = false
reset()
})
DropdownMenuItem(text = { Text("Open") }, onClick = {
DropdownMenuItem(text = { Text(stringResource(R.string.action_open)) }, onClick = {
menuExpanded = false
openFileLauncher.launch(arrayOf("text/*"))
})
DropdownMenuItem(text = { Text("Save") }, onClick = {
DropdownMenuItem(text = { Text(stringResource(R.string.action_save)) }, onClick = {
menuExpanded = false
saveFile(null)
})
DropdownMenuItem(text = { Text("Save as…") },
DropdownMenuItem(text = { Text(stringResource(R.string.action_save_as )) },
onClick = {
menuExpanded = false
saveFileLauncher.launch(fileName)
@ -244,7 +247,7 @@ private fun MainScreen(
if (!enableWideLayout) {
DropdownMenuItem(text = {
Row(verticalAlignment = Alignment.CenterVertically) {
Text("Lock Swiping")
Text(stringResource(R.string.action_lock_swipe))
Checkbox(
checked = lockSwiping,
onCheckedChange = { lockSwiping = !lockSwiping })
@ -332,10 +335,10 @@ private fun TabbedMarkdownEditor(
val coroutineScope = rememberCoroutineScope()
val pagerState = rememberPagerState { 2 }
TabRow(selectedTabIndex = pagerState.currentPage) {
Tab(text = { Text("Edit") },
Tab(text = { Text(stringResource(R.string.action_edit)) },
selected = pagerState.currentPage == 0,
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(0) } })
Tab(text = { Text("Preview") },
Tab(text = { Text(stringResource(R.string.action_preview)) },
selected = pagerState.currentPage == 1,
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(1) } })
}
@ -374,5 +377,7 @@ private fun TabbedMarkdownEditor(
@Composable
fun <P> MarkdownViewModel.collectAsState(prop: KProperty1<EditorState, P>, initial: P): State<P> =
state.map { prop.get(it) }
.collectAsState(initial)
remember(prop) { state.map { prop.get(it) }.distinctUntilChanged() }.collectAsState(initial)
@Composable
fun ParameterizedText.stringRes() = stringResource(text, params)

View file

@ -31,7 +31,7 @@ fun MarkdownInfoScreen(
val context = LocalContext.current
val (markdown, setMarkdown) = remember { mutableStateOf("") }
LaunchedEffect(file) {
setMarkdown(context.assets.readAssetToString(file) ?: "Failed to load $file")
setMarkdown(context.assets.readAssetToString(file))
}
MarkdownText(
modifier = Modifier

View file

@ -17,6 +17,7 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.wbrawner.simplemarkdown.R
import com.wbrawner.simplemarkdown.Route
@ -44,7 +45,7 @@ fun MarkdownNavigationDrawer(
tint = MaterialTheme.colorScheme.onSurface
)
Text(
text = "Simple Markdown",
text = stringResource(R.string.app_name),
style = MaterialTheme.typography.titleLarge
)
}
@ -56,7 +57,7 @@ fun MarkdownNavigationDrawer(
icon = {
Icon(imageVector = route.icon, contentDescription = null)
},
label = { Text(route.title) },
label = { Text(stringResource(route.title)) },
selected = false,
onClick = {
navigate(route)

View file

@ -1,5 +1,6 @@
package com.wbrawner.simplemarkdown.ui
import android.annotation.SuppressLint
import android.view.ViewGroup
import android.webkit.WebView
import androidx.compose.foundation.isSystemInDarkTheme
@ -69,6 +70,7 @@ fun MarkdownText(modifier: Modifier = Modifier, markdown: String) {
}
}
@SuppressLint("SetJavaScriptEnabled")
@OptIn(ExperimentalStdlibApi::class)
@Composable
fun HtmlText(html: String, modifier: Modifier = Modifier) {

View file

@ -24,6 +24,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextRange
@ -33,6 +34,7 @@ import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.dp
import com.wbrawner.simplemarkdown.R
import com.wbrawner.simplemarkdown.model.Readability
@OptIn(ExperimentalMaterial3Api::class)
@ -96,7 +98,7 @@ fun MarkdownTextField(
visualTransformation = VisualTransformation.None,
innerTextField = innerTextField,
placeholder = {
Text("Markdown here…")
Text(stringResource(R.string.markdown_here))
},
singleLine = false,
enabled = true,

View file

@ -11,14 +11,14 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarColors
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarDefaults.topAppBarColors
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.style.TextOverflow
import com.wbrawner.simplemarkdown.R
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class)
@ -32,6 +32,7 @@ fun MarkdownTopAppBar(
scrollBehavior: TopAppBarScrollBehavior? = null,
) {
val coroutineScope = rememberCoroutineScope()
val context = LocalContext.current
TopAppBar(
title = {
Text(text = title, maxLines = 1, overflow = TextOverflow.Ellipsis)
@ -40,10 +41,10 @@ fun MarkdownTopAppBar(
navigationIcon = {
val (icon, contentDescription, onClick) = remember {
if (backAsUp) {
Triple(Icons.AutoMirrored.Filled.ArrowBack, "Go Back", goBack)
Triple(Icons.AutoMirrored.Filled.ArrowBack, context.getString(R.string.action_back), goBack)
} else {
Triple(
Icons.Default.Menu, "Main Menu"
Icons.Default.Menu, context.getString(R.string.action_menu)
) {
coroutineScope.launch {
if (drawerState?.isOpen == true) {

View file

@ -26,10 +26,13 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringArrayResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.wbrawner.simplemarkdown.BuildConfig
import com.wbrawner.simplemarkdown.R
import com.wbrawner.simplemarkdown.ui.theme.SimpleMarkdownTheme
import com.wbrawner.simplemarkdown.utility.Preference
import com.wbrawner.simplemarkdown.utility.PreferenceHelper
@ -47,36 +50,36 @@ fun SettingsScreen(navController: NavController, preferenceHelper: PreferenceHel
.verticalScroll(rememberScrollState())
) {
BooleanPreference(
title = "Autosave",
enabledDescription = "Files will be saved automatically",
disabledDescription = "Files will not be saved automatically",
title = stringResource(R.string.pref_title_autosave),
enabledDescription = stringResource(R.string.pref_autosave_on),
disabledDescription = stringResource(R.string.pref_autosave_off),
preference = Preference.AUTOSAVE_ENABLED,
preferenceHelper = preferenceHelper
)
ListPreference(
title = "Dark mode",
options = listOf("auto", "dark", "light"),
title = stringResource(R.string.title_dark_mode),
options = stringArrayResource(R.array.pref_values_dark_mode),
preference = Preference.DARK_MODE,
preferenceHelper = preferenceHelper
)
BooleanPreference(
title = "Send crash reports",
enabledDescription = "Error reports will be sent",
disabledDescription = "Error reports will not be sent",
title = stringResource(R.string.pref_title_error_reports),
enabledDescription = stringResource(R.string.pref_error_reports_on),
disabledDescription = stringResource(R.string.pref_error_reports_off),
preference = Preference.ERROR_REPORTS_ENABLED,
preferenceHelper = preferenceHelper
)
BooleanPreference(
title = "Send analytics",
enabledDescription = "Analytics events will be sent",
disabledDescription = "Analytics events will not be sent",
title = stringResource(R.string.pref_title_analytics),
enabledDescription = stringResource(R.string.pref_analytics_on),
disabledDescription = stringResource(R.string.pref_analytics_off),
preference = Preference.ANALYTICS_ENABLED,
preferenceHelper = preferenceHelper
)
BooleanPreference(
title = "Readability highlighting",
enabledDescription = "Readability highlighting is on",
disabledDescription = "Readability highlighting is off",
title = stringResource(R.string.pref_title_readability),
enabledDescription = stringResource(R.string.pref_readability_on),
disabledDescription = stringResource(R.string.pref_readability_off),
preference = Preference.READABILITY_ENABLED,
preferenceHelper = preferenceHelper
)
@ -90,9 +93,9 @@ fun SettingsScreen(navController: NavController, preferenceHelper: PreferenceHel
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically) {
Column(verticalArrangement = Arrangement.Center) {
Text(text = "Force a crash", style = MaterialTheme.typography.bodyLarge)
Text(text = stringResource(R.string.action_force_crash), style = MaterialTheme.typography.bodyLarge)
Text(
text = "Purposefully crash the app for testing purposes",
text = stringResource(R.string.description_force_crash),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
@ -155,7 +158,7 @@ fun BooleanPreference(
@Composable
fun ListPreference(
title: String,
options: List<String>,
options: Array<String>,
preference: Preference,
preferenceHelper: PreferenceHelper
) {
@ -171,7 +174,7 @@ fun ListPreference(
@Composable
fun ListPreference(
title: String, options: List<String>, selected: String, setSelected: (String) -> Unit
title: String, options: Array<String>, selected: String, setSelected: (String) -> Unit
) {
var dialogShowing by remember { mutableStateOf(false) }
Column(modifier = Modifier
@ -248,7 +251,7 @@ fun ListPreference_Preview() {
SimpleMarkdownTheme {
Surface {
ListPreference(
"Dark mode", listOf("Light", "Dark", "Auto"), selected, setSelected
"Dark mode", arrayOf("Light", "Dark", "Auto"), selected, setSelected
)
}
}

View file

@ -28,6 +28,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat.startActivity
@ -39,7 +40,7 @@ import com.wbrawner.simplemarkdown.utility.SupportLinks
@Composable
fun SupportScreen(navController: NavController) {
Scaffold(topBar = {
MarkdownTopAppBar(title = "Support SimpleMarkdown", goBack = { navController.popBackStack() })
MarkdownTopAppBar(title = stringResource(R.string.support_title), goBack = { navController.popBackStack() })
}) { paddingValues ->
val context = LocalContext.current
Column(
@ -56,7 +57,7 @@ fun SupportScreen(navController: NavController) {
contentDescription = null,
tint = MaterialTheme.colorScheme.primary
)
Text(context.getString(R.string.support_info), textAlign = TextAlign.Center)
Text(stringResource(R.string.support_info), textAlign = TextAlign.Center)
Spacer(modifier = Modifier.height(8.dp))
Button(
modifier = Modifier.fillMaxWidth(),
@ -71,7 +72,7 @@ fun SupportScreen(navController: NavController) {
contentColor = Color.White
)
) {
Text(context.getString(R.string.action_view_github))
Text(stringResource(R.string.action_view_github))
}
Button(
modifier = Modifier.fillMaxWidth(),
@ -98,7 +99,7 @@ fun SupportScreen(navController: NavController) {
contentColor = Color.White
)
) {
Text(context.getString(R.string.action_rate))
Text(stringResource(R.string.action_rate))
}
SupportLinks()
}

View file

@ -10,17 +10,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.Reader
fun View.showKeyboard() {
(context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager)
.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0)
requestFocus()
}
fun View.hideKeyboard() =
(context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager)
.hideSoftInputFromWindow(windowToken, 0)
suspend fun AssetManager.readAssetToString(asset: String): String? {
suspend fun AssetManager.readAssetToString(asset: String): String {
return withContext(Dispatchers.IO) {
open(asset).reader().use(Reader::readText)
}

View file

@ -2,6 +2,7 @@ package com.wbrawner.simplemarkdown.utility
import android.util.Log
import com.wbrawner.simplemarkdown.utility.PersistentTree.Companion.create
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
@ -18,7 +19,10 @@ import java.util.*
* A [Timber.Tree] implementation that persists all logs to disk for retrieval later. Create
* instances via [create] instead of calling the constructor directly.
*/
class PersistentTree private constructor(private val logFile: File) : Timber.Tree() {
class PersistentTree private constructor(
private val coroutineScope: CoroutineScope,
private val logFile: File
) : Timber.Tree() {
private val dateFormat = object : ThreadLocal<SimpleDateFormat>() {
override fun initialValue(): SimpleDateFormat =
SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US)
@ -26,7 +30,7 @@ class PersistentTree private constructor(private val logFile: File) : Timber.Tre
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
val timestamp = dateFormat.get()!!.format(System.currentTimeMillis())
GlobalScope.launch(Dispatchers.IO) {
coroutineScope.launch(Dispatchers.IO) {
val priorityLetter = when (priority) {
Log.ASSERT -> "A"
Log.DEBUG -> "D"
@ -63,14 +67,14 @@ class PersistentTree private constructor(private val logFile: File) : Timber.Tre
* created/written to
*/
@Throws(IllegalArgumentException::class, IOException::class)
suspend fun create(logDir: File): PersistentTree = withContext(Dispatchers.IO) {
suspend fun create(coroutineScope: CoroutineScope, logDir: File): PersistentTree = withContext(Dispatchers.IO) {
if (!logDir.mkdirs() && !logDir.isDirectory)
throw IllegalArgumentException("Unable to create log directory at ${logDir.absolutePath}")
val timestamp = SimpleDateFormat("yyyyMMddHHmmss", Locale.US).format(Date())
val logFile = File(logDir, "persistent-log-$timestamp.log")
if (!logFile.createNewFile())
throw IOException("Unable to create logFile at ${logFile.absolutePath}")
PersistentTree(logFile)
PersistentTree(coroutineScope, logFile)
}
}
}

View file

@ -23,7 +23,7 @@ interface PreferenceHelper {
fun <T> observe(preference: Preference): StateFlow<T>
}
class AndroidPreferenceHelper(context: Context, val coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.IO)): PreferenceHelper {
class AndroidPreferenceHelper(context: Context, private val coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.IO)): PreferenceHelper {
private val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
private val states = mapOf(
Preference.ANALYTICS_ENABLED to MutableStateFlow(get(Preference.ANALYTICS_ENABLED)),

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

View file

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorBackground">#000000</color>
<color name="colorOnBackground">#FFFFFF</color>
</resources>

View file

@ -4,8 +4,6 @@
<color name="colorPrimaryDark">#b71c1c</color>
<color name="colorAccent">#d32f2f</color>
<color name="colorBackground">#FFFFFF</color>
<color name="colorOnBackground">#000000</color>
<color name="colorBackgroundGitHub">#24292e</color>
<color name="colorWhite">#FFFFFFFF</color>
<color name="colorBackgroundPlayStore">#689f38</color>
</resources>

View file

@ -1,3 +0,0 @@
<resources>
<dimen name="fab_margin">16dp</dimen>
</resources>

View file

@ -2,65 +2,52 @@
<string name="app_name">Simple Markdown</string>
<string name="app_name_short">Markdown</string>
<string name="action_settings">Settings</string>
<string name="action_help">Help</string>
<string name="title_editor">Editor</string>
<string name="title_settings">Settings</string>
<string name="title_help">Help</string>
<string name="title_about">About</string>
<string name="action_edit">Edit</string>
<string name="action_preview">Preview</string>
<string name="ok">OK</string>
<string name="markdown_here">Markdown here…</string>
<string name="action_save">Save</string>
<string name="action_editor_actions">Editor Actions</string>
<string name="action_share">Share</string>
<string name="action_export">Export</string>
<string name="no_shareable_apps">Unable to share file - no capable apps installed</string>
<string name="share_file">Share file to…</string>
<string name="no_permissions">Unable to save file without permissions</string>
<string name="file_saved">Successfully saved %1$s</string>
<string name="action_load">Load</string>
<string name="open_file">Select a file to open</string>
<string name="no_filebrowser">No file browser apps found</string>
<string name="file_save_error">An error occurred while saving the file</string>
<string name="file_loaded">File successfully loaded</string>
<string name="error_write">An error occurred while writing the file</string>
<string name="file_load_error">An error occurred while opening the file</string>
<string name="action_libraries">Libraries</string>
<string name="action_back">Back</string>
<string name="action_menu">Main Menu</string>
<string name="action_new">New</string>
<string name="action_done">Done</string>
<string name="action_open">Open</string>
<string name="yes">Yes</string>
<string name="no">No</string>
<string name="title_activity_settings">Settings</string>
<string name="pref_title_autosave">Enable autosave</string>
<string name="pref_description_autosave">Automatically save files when closing the app</string>
<string name="action_lock_swipe">Lock Swiping</string>
<string name="action_select">Select</string>
<string name="action_privacy">Privacy</string>
<string name="pref_key_error_reports_enabled">crashlytics.enable</string>
<string name="pref_title_error_reports">Enable automated error reports</string>
<string name="pref_error_reports_off">Error reports will not be sent</string>
<string name="pref_error_reports_on">Error reports will be sent</string>
<string name="readability_enabled">readability.enable</string>
<string name="pref_title_analytics">Send analytics</string>
<string name="pref_analytics_off">Analytics events will not be sent</string>
<string name="pref_analytics_on">Analytics events will be sent</string>
<string name="action_force_crash">Force a crash</string>
<string name="description_force_crash">Purposefully crash the app for testing purposes</string>
<string name="pref_title_readability">Enable readability highlighting (experimental)</string>
<string name="pref_readability_off">Readability highlighting is off</string>
<string name="pref_readability_on">Readability highlighting is on</string>
<string name="pref_autosave_on">Files will be automatically saved</string>
<string name="pref_autosave_off">Files will not be automatically saved</string>
<string name="pref_custom_css">pref.custom_css</string>
<string name="pref_title_custom_css">Custom CSS</string>
<string name="pref_description_custom_css">Paste or write your own CSS to be used for the preview pane</string>
<string name="pref_custom_css_default" translatable="false">pre {overflow:scroll; padding:15px; background: #F1F1F1;}</string>
<string name="pref_custom_css_default_dark" translatable="false">body{background:
#000000;color: #F1F1F1;}a{color:#7b91ff;}pre{background:#111111;}</string>
<string name="pref_key_dark_mode">darkMode</string>
<string name="title_dark_mode">Dark Mode</string>
<string name="pref_value_light">Light</string>
<string name="pref_value_dark">Dark</string>
<string name="pref_value_auto">Auto</string>
<!-- <string name="pref_value_auto">Auto</string>-->
<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="save_changes">Save Changes</string>
<string name="prompt_save_changes">Would you like to save your changes?</string>
<string name="action_discard">Discard</string>
<string name="action_save_as">Save as…</string>
<string name="support_info">SimpleMarkdown is a personal project of mine that I develop and
maintain in my free time. I very much appreciate any and all forms of support, whether
@ -73,12 +60,11 @@
<string name="action_view_github">View SimpleMarkdown on GitHub</string>
<string name="support_title">Support SimpleMarkdown</string>
<string name="action_rate">Rate SimpleMarkdown</string>
<string name="description_heart">Heart</string>
<string-array name="pref_entries_dark_mode">
<item>@string/pref_value_light</item>
<item>@string/pref_value_dark</item>
<item>@string/pref_value_auto</item>
</string-array>
<!-- <string-array name="pref_entries_dark_mode">-->
<!-- <item>@string/pref_value_light</item>-->
<!-- <item>@string/pref_value_dark</item>-->
<!-- <item>@string/pref_value_auto</item>-->
<!-- </string-array>-->
<string-array name="pref_values_dark_mode">
<item>@string/pref_key_dark_mode_light</item>
<item>@string/pref_key_dark_mode_dark</item>

View file

@ -47,6 +47,9 @@ android {
kotlinOptions {
jvmTarget = "1.8"
}
lint {
warningsAsErrors = true
}
}
dependencies {

View file

@ -33,6 +33,9 @@ android {
kotlinOptions {
jvmTarget = "1.8"
}
lint {
warningsAsErrors = true
}
}
dependencies {

View file

@ -1,6 +1,6 @@
[versions]
acra = "5.11.3"
activityKtx = "1.9.0"
activityKtx = "1.9.1"
animationCore = "1.6.8"
appcompat = "1.7.0"
billing = "7.0.0"
@ -20,7 +20,7 @@ androidGradlePlugin = "8.5.1"
hamcrestCore = "1.3"
junit = "4.13.2"
kotlin = "2.0.0"
lifecycleViewmodelKtx = "2.8.3"
lifecycleViewmodelKtx = "2.8.4"
material = "1.12.0"
material3WindowSizeClassAndroid = "1.2.1"
materialIconsCore = "1.6.8"

View file

@ -33,6 +33,9 @@ android {
kotlinOptions {
jvmTarget = "1.8"
}
lint {
warningsAsErrors = true
}
}
dependencies {