Address lint issues
This commit is contained in:
parent
06cbc5ec31
commit
86cd33ff5f
28 changed files with 157 additions and 134 deletions
|
@ -97,6 +97,9 @@ android {
|
||||||
commit.set(true)
|
commit.set(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
lint {
|
||||||
|
warningsAsErrors = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
play {
|
play {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
|
import androidx.annotation.StringRes
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
import androidx.compose.animation.AnimatedContentTransitionScope
|
import androidx.compose.animation.AnimatedContentTransitionScope
|
||||||
|
@ -27,6 +28,7 @@ import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.core.app.ActivityCompat
|
import androidx.core.app.ActivityCompat
|
||||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
|
@ -135,21 +137,21 @@ class MainActivity : AppCompatActivity(), ActivityCompat.OnRequestPermissionsRes
|
||||||
}
|
}
|
||||||
composable(Route.HELP.path) {
|
composable(Route.HELP.path) {
|
||||||
MarkdownInfoScreen(
|
MarkdownInfoScreen(
|
||||||
title = Route.HELP.title,
|
title = stringResource(Route.HELP.title),
|
||||||
file = "Cheatsheet.md",
|
file = "Cheatsheet.md",
|
||||||
navController = navController
|
navController = navController
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
composable(Route.ABOUT.path) {
|
composable(Route.ABOUT.path) {
|
||||||
MarkdownInfoScreen(
|
MarkdownInfoScreen(
|
||||||
title = Route.ABOUT.title,
|
title = stringResource(Route.ABOUT.title),
|
||||||
file = "Libraries.md",
|
file = "Libraries.md",
|
||||||
navController = navController
|
navController = navController
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
composable(Route.PRIVACY.path) {
|
composable(Route.PRIVACY.path) {
|
||||||
MarkdownInfoScreen(
|
MarkdownInfoScreen(
|
||||||
title = Route.PRIVACY.title,
|
title = stringResource(Route.PRIVACY.title),
|
||||||
file = "Privacy Policy.md",
|
file = "Privacy Policy.md",
|
||||||
navController = navController
|
navController = navController
|
||||||
)
|
)
|
||||||
|
@ -162,13 +164,14 @@ class MainActivity : AppCompatActivity(), ActivityCompat.OnRequestPermissionsRes
|
||||||
|
|
||||||
enum class Route(
|
enum class Route(
|
||||||
val path: String,
|
val path: String,
|
||||||
val title: String,
|
@StringRes
|
||||||
|
val title: Int,
|
||||||
val icon: ImageVector
|
val icon: ImageVector
|
||||||
) {
|
) {
|
||||||
EDITOR("/", "Editor", Icons.Default.Edit),
|
EDITOR("/", R.string.title_editor, Icons.Default.Edit),
|
||||||
SETTINGS("/settings", "Settings", Icons.Default.Settings),
|
SETTINGS("/settings", R.string.title_settings, Icons.Default.Settings),
|
||||||
SUPPORT("/support", "Support SimpleMarkdown", Icons.Default.Favorite),
|
SUPPORT("/support", R.string.support_title, Icons.Default.Favorite),
|
||||||
HELP("/help", "Help", Icons.AutoMirrored.Filled.Help),
|
HELP("/help", R.string.title_help, Icons.AutoMirrored.Filled.Help),
|
||||||
ABOUT("/about", "About", Icons.Default.Info),
|
ABOUT("/about", R.string.title_about, Icons.Default.Info),
|
||||||
PRIVACY("/privacy", "Privacy", Icons.Default.PrivacyTip),
|
PRIVACY("/privacy", R.string.action_privacy, Icons.Default.PrivacyTip),
|
||||||
}
|
}
|
|
@ -9,13 +9,16 @@ import com.wbrawner.simplemarkdown.utility.FileHelper
|
||||||
import com.wbrawner.simplemarkdown.utility.PersistentTree
|
import com.wbrawner.simplemarkdown.utility.PersistentTree
|
||||||
import com.wbrawner.simplemarkdown.utility.PreferenceHelper
|
import com.wbrawner.simplemarkdown.utility.PreferenceHelper
|
||||||
import com.wbrawner.simplemarkdown.utility.ReviewHelper
|
import com.wbrawner.simplemarkdown.utility.ReviewHelper
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
class MarkdownApplication : Application() {
|
class MarkdownApplication : Application() {
|
||||||
|
|
||||||
|
private val coroutineScope = CoroutineScope(Dispatchers.Default)
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
if (BuildConfig.DEBUG) {
|
if (BuildConfig.DEBUG) {
|
||||||
StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.Builder()
|
StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.Builder()
|
||||||
|
@ -27,9 +30,9 @@ class MarkdownApplication : Application() {
|
||||||
.penaltyLog()
|
.penaltyLog()
|
||||||
.build())
|
.build())
|
||||||
Timber.plant(Timber.DebugTree())
|
Timber.plant(Timber.DebugTree())
|
||||||
GlobalScope.launch {
|
coroutineScope.launch {
|
||||||
try {
|
try {
|
||||||
Timber.plant(PersistentTree.create(File(getExternalFilesDir(null), "logs")))
|
Timber.plant(PersistentTree.create(coroutineScope, File(getExternalFilesDir(null), "logs")))
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Timber.e(e, "Unable to create PersistentTree")
|
Timber.e(e, "Unable to create PersistentTree")
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.wbrawner.simplemarkdown
|
package com.wbrawner.simplemarkdown
|
||||||
|
|
||||||
|
import androidx.annotation.StringRes
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
@ -22,7 +23,7 @@ data class EditorState(
|
||||||
val fileName: String = "Untitled.md",
|
val fileName: String = "Untitled.md",
|
||||||
val markdown: String = "",
|
val markdown: String = "",
|
||||||
val path: URI? = null,
|
val path: URI? = null,
|
||||||
val toast: String? = null,
|
val toast: ParameterizedText? = null,
|
||||||
val alert: AlertDialogModel? = null,
|
val alert: AlertDialogModel? = null,
|
||||||
val saveCallback: (() -> Unit)? = null,
|
val saveCallback: (() -> Unit)? = null,
|
||||||
/**
|
/**
|
||||||
|
@ -95,7 +96,7 @@ class MarkdownViewModel(
|
||||||
markdown = content,
|
markdown = content,
|
||||||
initialMarkdown = content,
|
initialMarkdown = content,
|
||||||
reloadToggle = currentState.reloadToggle.inv(),
|
reloadToggle = currentState.reloadToggle.inv(),
|
||||||
toast = "Successfully loaded $name"
|
toast = ParameterizedText(R.string.file_loaded)
|
||||||
)
|
)
|
||||||
preferenceHelper[Preference.AUTOSAVE_URI] = actualLoadPath
|
preferenceHelper[Preference.AUTOSAVE_URI] = actualLoadPath
|
||||||
} ?: throw IllegalStateException("Opened file was null")
|
} ?: throw IllegalStateException("Opened file was null")
|
||||||
|
@ -103,8 +104,8 @@ class MarkdownViewModel(
|
||||||
Timber.e(e, "Failed to open file at path: $actualLoadPath")
|
Timber.e(e, "Failed to open file at path: $actualLoadPath")
|
||||||
_state.value = _state.value.copy(
|
_state.value = _state.value.copy(
|
||||||
alert = AlertDialogModel(
|
alert = AlertDialogModel(
|
||||||
text = "Failed to open file at path: $actualLoadPath",
|
text = ParameterizedText(R.string.file_load_error),
|
||||||
confirmButton = AlertDialogModel.ButtonModel("OK", onClick = ::dismissAlert)
|
confirmButton = AlertDialogModel.ButtonModel(ParameterizedText(R.string.ok), onClick = ::dismissAlert)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -130,20 +131,19 @@ class MarkdownViewModel(
|
||||||
fileName = name,
|
fileName = name,
|
||||||
path = actualSavePath,
|
path = actualSavePath,
|
||||||
initialMarkdown = currentState.markdown,
|
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("Saved file $name to uri $actualSavePath")
|
||||||
Timber.i("Persisting autosave uri in shared prefs: $actualSavePath")
|
Timber.i("Persisting autosave uri in shared prefs: $actualSavePath")
|
||||||
preferenceHelper[Preference.AUTOSAVE_URI] = actualSavePath
|
preferenceHelper[Preference.AUTOSAVE_URI] = actualSavePath
|
||||||
true
|
true
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
val message = "Failed to save file to $actualSavePath"
|
Timber.e(e, "Failed to save file to $actualSavePath")
|
||||||
Timber.e(e, message)
|
|
||||||
_state.value = _state.value.copy(
|
_state.value = _state.value.copy(
|
||||||
alert = AlertDialogModel(
|
alert = AlertDialogModel(
|
||||||
text = message,
|
text = ParameterizedText(R.string.file_save_error),
|
||||||
confirmButton = AlertDialogModel.ButtonModel(
|
confirmButton = AlertDialogModel.ButtonModel(
|
||||||
text = "OK",
|
text = ParameterizedText(R.string.ok),
|
||||||
onClick = ::dismissAlert
|
onClick = ::dismissAlert
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -189,9 +189,9 @@ class MarkdownViewModel(
|
||||||
Timber.i("Resetting view model to default state")
|
Timber.i("Resetting view model to default state")
|
||||||
if (!force && _state.value.dirty) {
|
if (!force && _state.value.dirty) {
|
||||||
_state.value = _state.value.copy(alert = AlertDialogModel(
|
_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(
|
confirmButton = AlertDialogModel.ButtonModel(
|
||||||
text = "Yes",
|
text = ParameterizedText(R.string.yes),
|
||||||
onClick = {
|
onClick = {
|
||||||
_state.value = _state.value.copy(
|
_state.value = _state.value.copy(
|
||||||
saveCallback = {
|
saveCallback = {
|
||||||
|
@ -201,7 +201,7 @@ class MarkdownViewModel(
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
dismissButton = AlertDialogModel.ButtonModel(
|
dismissButton = AlertDialogModel.ButtonModel(
|
||||||
text = "No",
|
text = ParameterizedText(R.string.no),
|
||||||
onClick = {
|
onClick = {
|
||||||
reset(untitledFileName, true)
|
reset(untitledFileName, true)
|
||||||
}
|
}
|
||||||
|
@ -231,9 +231,29 @@ class MarkdownViewModel(
|
||||||
}
|
}
|
||||||
|
|
||||||
data class AlertDialogModel(
|
data class AlertDialogModel(
|
||||||
val text: String,
|
val text: ParameterizedText,
|
||||||
val confirmButton: ButtonModel,
|
val confirmButton: ButtonModel,
|
||||||
val dismissButton: ButtonModel? = null
|
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
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -52,15 +52,18 @@ import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.core.content.ContextCompat.startActivity
|
import androidx.core.content.ContextCompat.startActivity
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import com.wbrawner.simplemarkdown.AlertDialogModel
|
import com.wbrawner.simplemarkdown.AlertDialogModel
|
||||||
import com.wbrawner.simplemarkdown.EditorState
|
import com.wbrawner.simplemarkdown.EditorState
|
||||||
import com.wbrawner.simplemarkdown.MarkdownViewModel
|
import com.wbrawner.simplemarkdown.MarkdownViewModel
|
||||||
|
import com.wbrawner.simplemarkdown.ParameterizedText
|
||||||
import com.wbrawner.simplemarkdown.R
|
import com.wbrawner.simplemarkdown.R
|
||||||
import com.wbrawner.simplemarkdown.Route
|
import com.wbrawner.simplemarkdown.Route
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
@ -98,7 +101,7 @@ fun MainScreen(
|
||||||
initialMarkdown = initialMarkdown,
|
initialMarkdown = initialMarkdown,
|
||||||
markdown = markdown,
|
markdown = markdown,
|
||||||
setMarkdown = viewModel::updateMarkdown,
|
setMarkdown = viewModel::updateMarkdown,
|
||||||
message = toast,
|
message = toast?.stringRes(),
|
||||||
dismissMessage = viewModel::dismissToast,
|
dismissMessage = viewModel::dismissToast,
|
||||||
alert = alert,
|
alert = alert,
|
||||||
dismissAlert = viewModel::dismissAlert,
|
dismissAlert = viewModel::dismissAlert,
|
||||||
|
@ -181,17 +184,17 @@ private fun MainScreen(
|
||||||
onDismissRequest = dismissAlert,
|
onDismissRequest = dismissAlert,
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(onClick = it.confirmButton.onClick) {
|
TextButton(onClick = it.confirmButton.onClick) {
|
||||||
Text(it.confirmButton.text)
|
Text(stringResource(it.confirmButton.text.text))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
it.dismissButton?.let { dismissButton ->
|
it.dismissButton?.let { dismissButton ->
|
||||||
TextButton(onClick = dismissButton.onClick) {
|
TextButton(onClick = dismissButton.onClick) {
|
||||||
Text(dismissButton.text)
|
Text(dismissButton.text.stringRes())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
text = { Text(it.text) }
|
text = { Text(it.text.stringRes()) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
|
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
|
||||||
|
@ -215,28 +218,28 @@ private fun MainScreen(
|
||||||
), null
|
), null
|
||||||
)
|
)
|
||||||
}) {
|
}) {
|
||||||
Icon(imageVector = Icons.Default.Share, contentDescription = "Share")
|
Icon(imageVector = Icons.Default.Share, contentDescription = stringResource(R.string.action_share))
|
||||||
}
|
}
|
||||||
Box {
|
Box {
|
||||||
var menuExpanded by remember { mutableStateOf(false) }
|
var menuExpanded by remember { mutableStateOf(false) }
|
||||||
IconButton(onClick = { menuExpanded = true }) {
|
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,
|
DropdownMenu(expanded = menuExpanded,
|
||||||
onDismissRequest = { menuExpanded = false }) {
|
onDismissRequest = { menuExpanded = false }) {
|
||||||
DropdownMenuItem(text = { Text("New") }, onClick = {
|
DropdownMenuItem(text = { Text(stringResource(R.string.action_new)) }, onClick = {
|
||||||
menuExpanded = false
|
menuExpanded = false
|
||||||
reset()
|
reset()
|
||||||
})
|
})
|
||||||
DropdownMenuItem(text = { Text("Open") }, onClick = {
|
DropdownMenuItem(text = { Text(stringResource(R.string.action_open)) }, onClick = {
|
||||||
menuExpanded = false
|
menuExpanded = false
|
||||||
openFileLauncher.launch(arrayOf("text/*"))
|
openFileLauncher.launch(arrayOf("text/*"))
|
||||||
})
|
})
|
||||||
DropdownMenuItem(text = { Text("Save") }, onClick = {
|
DropdownMenuItem(text = { Text(stringResource(R.string.action_save)) }, onClick = {
|
||||||
menuExpanded = false
|
menuExpanded = false
|
||||||
saveFile(null)
|
saveFile(null)
|
||||||
})
|
})
|
||||||
DropdownMenuItem(text = { Text("Save as…") },
|
DropdownMenuItem(text = { Text(stringResource(R.string.action_save_as )) },
|
||||||
onClick = {
|
onClick = {
|
||||||
menuExpanded = false
|
menuExpanded = false
|
||||||
saveFileLauncher.launch(fileName)
|
saveFileLauncher.launch(fileName)
|
||||||
|
@ -244,7 +247,7 @@ private fun MainScreen(
|
||||||
if (!enableWideLayout) {
|
if (!enableWideLayout) {
|
||||||
DropdownMenuItem(text = {
|
DropdownMenuItem(text = {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
Text("Lock Swiping")
|
Text(stringResource(R.string.action_lock_swipe))
|
||||||
Checkbox(
|
Checkbox(
|
||||||
checked = lockSwiping,
|
checked = lockSwiping,
|
||||||
onCheckedChange = { lockSwiping = !lockSwiping })
|
onCheckedChange = { lockSwiping = !lockSwiping })
|
||||||
|
@ -332,10 +335,10 @@ private fun TabbedMarkdownEditor(
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
val pagerState = rememberPagerState { 2 }
|
val pagerState = rememberPagerState { 2 }
|
||||||
TabRow(selectedTabIndex = pagerState.currentPage) {
|
TabRow(selectedTabIndex = pagerState.currentPage) {
|
||||||
Tab(text = { Text("Edit") },
|
Tab(text = { Text(stringResource(R.string.action_edit)) },
|
||||||
selected = pagerState.currentPage == 0,
|
selected = pagerState.currentPage == 0,
|
||||||
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(0) } })
|
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(0) } })
|
||||||
Tab(text = { Text("Preview") },
|
Tab(text = { Text(stringResource(R.string.action_preview)) },
|
||||||
selected = pagerState.currentPage == 1,
|
selected = pagerState.currentPage == 1,
|
||||||
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(1) } })
|
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(1) } })
|
||||||
}
|
}
|
||||||
|
@ -374,5 +377,7 @@ private fun TabbedMarkdownEditor(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun <P> MarkdownViewModel.collectAsState(prop: KProperty1<EditorState, P>, initial: P): State<P> =
|
fun <P> MarkdownViewModel.collectAsState(prop: KProperty1<EditorState, P>, initial: P): State<P> =
|
||||||
state.map { prop.get(it) }
|
remember(prop) { state.map { prop.get(it) }.distinctUntilChanged() }.collectAsState(initial)
|
||||||
.collectAsState(initial)
|
|
||||||
|
@Composable
|
||||||
|
fun ParameterizedText.stringRes() = stringResource(text, params)
|
|
@ -31,7 +31,7 @@ fun MarkdownInfoScreen(
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val (markdown, setMarkdown) = remember { mutableStateOf("") }
|
val (markdown, setMarkdown) = remember { mutableStateOf("") }
|
||||||
LaunchedEffect(file) {
|
LaunchedEffect(file) {
|
||||||
setMarkdown(context.assets.readAssetToString(file) ?: "Failed to load $file")
|
setMarkdown(context.assets.readAssetToString(file))
|
||||||
}
|
}
|
||||||
MarkdownText(
|
MarkdownText(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
|
@ -17,6 +17,7 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.wbrawner.simplemarkdown.R
|
import com.wbrawner.simplemarkdown.R
|
||||||
import com.wbrawner.simplemarkdown.Route
|
import com.wbrawner.simplemarkdown.Route
|
||||||
|
@ -44,7 +45,7 @@ fun MarkdownNavigationDrawer(
|
||||||
tint = MaterialTheme.colorScheme.onSurface
|
tint = MaterialTheme.colorScheme.onSurface
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = "Simple Markdown",
|
text = stringResource(R.string.app_name),
|
||||||
style = MaterialTheme.typography.titleLarge
|
style = MaterialTheme.typography.titleLarge
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -56,7 +57,7 @@ fun MarkdownNavigationDrawer(
|
||||||
icon = {
|
icon = {
|
||||||
Icon(imageVector = route.icon, contentDescription = null)
|
Icon(imageVector = route.icon, contentDescription = null)
|
||||||
},
|
},
|
||||||
label = { Text(route.title) },
|
label = { Text(stringResource(route.title)) },
|
||||||
selected = false,
|
selected = false,
|
||||||
onClick = {
|
onClick = {
|
||||||
navigate(route)
|
navigate(route)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.wbrawner.simplemarkdown.ui
|
package com.wbrawner.simplemarkdown.ui
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.webkit.WebView
|
import android.webkit.WebView
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
|
@ -69,6 +70,7 @@ fun MarkdownText(modifier: Modifier = Modifier, markdown: String) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("SetJavaScriptEnabled")
|
||||||
@OptIn(ExperimentalStdlibApi::class)
|
@OptIn(ExperimentalStdlibApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun HtmlText(html: String, modifier: Modifier = Modifier) {
|
fun HtmlText(html: String, modifier: Modifier = Modifier) {
|
||||||
|
|
|
@ -24,6 +24,7 @@ import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.SolidColor
|
import androidx.compose.ui.graphics.SolidColor
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
import androidx.compose.ui.text.SpanStyle
|
import androidx.compose.ui.text.SpanStyle
|
||||||
import androidx.compose.ui.text.TextRange
|
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.TextFieldValue
|
||||||
import androidx.compose.ui.text.input.VisualTransformation
|
import androidx.compose.ui.text.input.VisualTransformation
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.wbrawner.simplemarkdown.R
|
||||||
import com.wbrawner.simplemarkdown.model.Readability
|
import com.wbrawner.simplemarkdown.model.Readability
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@ -96,7 +98,7 @@ fun MarkdownTextField(
|
||||||
visualTransformation = VisualTransformation.None,
|
visualTransformation = VisualTransformation.None,
|
||||||
innerTextField = innerTextField,
|
innerTextField = innerTextField,
|
||||||
placeholder = {
|
placeholder = {
|
||||||
Text("Markdown here…")
|
Text(stringResource(R.string.markdown_here))
|
||||||
},
|
},
|
||||||
singleLine = false,
|
singleLine = false,
|
||||||
enabled = true,
|
enabled = true,
|
||||||
|
|
|
@ -11,14 +11,14 @@ import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBar
|
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.TopAppBarDefaults.topAppBarColors
|
||||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import com.wbrawner.simplemarkdown.R
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@ -32,6 +32,7 @@ fun MarkdownTopAppBar(
|
||||||
scrollBehavior: TopAppBarScrollBehavior? = null,
|
scrollBehavior: TopAppBarScrollBehavior? = null,
|
||||||
) {
|
) {
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
val context = LocalContext.current
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = {
|
title = {
|
||||||
Text(text = title, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
Text(text = title, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||||
|
@ -40,10 +41,10 @@ fun MarkdownTopAppBar(
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
val (icon, contentDescription, onClick) = remember {
|
val (icon, contentDescription, onClick) = remember {
|
||||||
if (backAsUp) {
|
if (backAsUp) {
|
||||||
Triple(Icons.AutoMirrored.Filled.ArrowBack, "Go Back", goBack)
|
Triple(Icons.AutoMirrored.Filled.ArrowBack, context.getString(R.string.action_back), goBack)
|
||||||
} else {
|
} else {
|
||||||
Triple(
|
Triple(
|
||||||
Icons.Default.Menu, "Main Menu"
|
Icons.Default.Menu, context.getString(R.string.action_menu)
|
||||||
) {
|
) {
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
if (drawerState?.isOpen == true) {
|
if (drawerState?.isOpen == true) {
|
||||||
|
|
|
@ -26,10 +26,13 @@ import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
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.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import com.wbrawner.simplemarkdown.BuildConfig
|
import com.wbrawner.simplemarkdown.BuildConfig
|
||||||
|
import com.wbrawner.simplemarkdown.R
|
||||||
import com.wbrawner.simplemarkdown.ui.theme.SimpleMarkdownTheme
|
import com.wbrawner.simplemarkdown.ui.theme.SimpleMarkdownTheme
|
||||||
import com.wbrawner.simplemarkdown.utility.Preference
|
import com.wbrawner.simplemarkdown.utility.Preference
|
||||||
import com.wbrawner.simplemarkdown.utility.PreferenceHelper
|
import com.wbrawner.simplemarkdown.utility.PreferenceHelper
|
||||||
|
@ -47,36 +50,36 @@ fun SettingsScreen(navController: NavController, preferenceHelper: PreferenceHel
|
||||||
.verticalScroll(rememberScrollState())
|
.verticalScroll(rememberScrollState())
|
||||||
) {
|
) {
|
||||||
BooleanPreference(
|
BooleanPreference(
|
||||||
title = "Autosave",
|
title = stringResource(R.string.pref_title_autosave),
|
||||||
enabledDescription = "Files will be saved automatically",
|
enabledDescription = stringResource(R.string.pref_autosave_on),
|
||||||
disabledDescription = "Files will not be saved automatically",
|
disabledDescription = stringResource(R.string.pref_autosave_off),
|
||||||
preference = Preference.AUTOSAVE_ENABLED,
|
preference = Preference.AUTOSAVE_ENABLED,
|
||||||
preferenceHelper = preferenceHelper
|
preferenceHelper = preferenceHelper
|
||||||
)
|
)
|
||||||
ListPreference(
|
ListPreference(
|
||||||
title = "Dark mode",
|
title = stringResource(R.string.title_dark_mode),
|
||||||
options = listOf("auto", "dark", "light"),
|
options = stringArrayResource(R.array.pref_values_dark_mode),
|
||||||
preference = Preference.DARK_MODE,
|
preference = Preference.DARK_MODE,
|
||||||
preferenceHelper = preferenceHelper
|
preferenceHelper = preferenceHelper
|
||||||
)
|
)
|
||||||
BooleanPreference(
|
BooleanPreference(
|
||||||
title = "Send crash reports",
|
title = stringResource(R.string.pref_title_error_reports),
|
||||||
enabledDescription = "Error reports will be sent",
|
enabledDescription = stringResource(R.string.pref_error_reports_on),
|
||||||
disabledDescription = "Error reports will not be sent",
|
disabledDescription = stringResource(R.string.pref_error_reports_off),
|
||||||
preference = Preference.ERROR_REPORTS_ENABLED,
|
preference = Preference.ERROR_REPORTS_ENABLED,
|
||||||
preferenceHelper = preferenceHelper
|
preferenceHelper = preferenceHelper
|
||||||
)
|
)
|
||||||
BooleanPreference(
|
BooleanPreference(
|
||||||
title = "Send analytics",
|
title = stringResource(R.string.pref_title_analytics),
|
||||||
enabledDescription = "Analytics events will be sent",
|
enabledDescription = stringResource(R.string.pref_analytics_on),
|
||||||
disabledDescription = "Analytics events will not be sent",
|
disabledDescription = stringResource(R.string.pref_analytics_off),
|
||||||
preference = Preference.ANALYTICS_ENABLED,
|
preference = Preference.ANALYTICS_ENABLED,
|
||||||
preferenceHelper = preferenceHelper
|
preferenceHelper = preferenceHelper
|
||||||
)
|
)
|
||||||
BooleanPreference(
|
BooleanPreference(
|
||||||
title = "Readability highlighting",
|
title = stringResource(R.string.pref_title_readability),
|
||||||
enabledDescription = "Readability highlighting is on",
|
enabledDescription = stringResource(R.string.pref_readability_on),
|
||||||
disabledDescription = "Readability highlighting is off",
|
disabledDescription = stringResource(R.string.pref_readability_off),
|
||||||
preference = Preference.READABILITY_ENABLED,
|
preference = Preference.READABILITY_ENABLED,
|
||||||
preferenceHelper = preferenceHelper
|
preferenceHelper = preferenceHelper
|
||||||
)
|
)
|
||||||
|
@ -90,9 +93,9 @@ fun SettingsScreen(navController: NavController, preferenceHelper: PreferenceHel
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
verticalAlignment = Alignment.CenterVertically) {
|
verticalAlignment = Alignment.CenterVertically) {
|
||||||
Column(verticalArrangement = Arrangement.Center) {
|
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(
|
||||||
text = "Purposefully crash the app for testing purposes",
|
text = stringResource(R.string.description_force_crash),
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
|
@ -155,7 +158,7 @@ fun BooleanPreference(
|
||||||
@Composable
|
@Composable
|
||||||
fun ListPreference(
|
fun ListPreference(
|
||||||
title: String,
|
title: String,
|
||||||
options: List<String>,
|
options: Array<String>,
|
||||||
preference: Preference,
|
preference: Preference,
|
||||||
preferenceHelper: PreferenceHelper
|
preferenceHelper: PreferenceHelper
|
||||||
) {
|
) {
|
||||||
|
@ -171,7 +174,7 @@ fun ListPreference(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ListPreference(
|
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) }
|
var dialogShowing by remember { mutableStateOf(false) }
|
||||||
Column(modifier = Modifier
|
Column(modifier = Modifier
|
||||||
|
@ -248,7 +251,7 @@ fun ListPreference_Preview() {
|
||||||
SimpleMarkdownTheme {
|
SimpleMarkdownTheme {
|
||||||
Surface {
|
Surface {
|
||||||
ListPreference(
|
ListPreference(
|
||||||
"Dark mode", listOf("Light", "Dark", "Auto"), selected, setSelected
|
"Dark mode", arrayOf("Light", "Dark", "Auto"), selected, setSelected
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.core.content.ContextCompat.startActivity
|
import androidx.core.content.ContextCompat.startActivity
|
||||||
|
@ -39,7 +40,7 @@ import com.wbrawner.simplemarkdown.utility.SupportLinks
|
||||||
@Composable
|
@Composable
|
||||||
fun SupportScreen(navController: NavController) {
|
fun SupportScreen(navController: NavController) {
|
||||||
Scaffold(topBar = {
|
Scaffold(topBar = {
|
||||||
MarkdownTopAppBar(title = "Support SimpleMarkdown", goBack = { navController.popBackStack() })
|
MarkdownTopAppBar(title = stringResource(R.string.support_title), goBack = { navController.popBackStack() })
|
||||||
}) { paddingValues ->
|
}) { paddingValues ->
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
Column(
|
Column(
|
||||||
|
@ -56,7 +57,7 @@ fun SupportScreen(navController: NavController) {
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
tint = MaterialTheme.colorScheme.primary
|
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))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
Button(
|
Button(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
@ -71,7 +72,7 @@ fun SupportScreen(navController: NavController) {
|
||||||
contentColor = Color.White
|
contentColor = Color.White
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
Text(context.getString(R.string.action_view_github))
|
Text(stringResource(R.string.action_view_github))
|
||||||
}
|
}
|
||||||
Button(
|
Button(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
@ -98,7 +99,7 @@ fun SupportScreen(navController: NavController) {
|
||||||
contentColor = Color.White
|
contentColor = Color.White
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
Text(context.getString(R.string.action_rate))
|
Text(stringResource(R.string.action_rate))
|
||||||
}
|
}
|
||||||
SupportLinks()
|
SupportLinks()
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,17 +10,7 @@ import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.Reader
|
import java.io.Reader
|
||||||
|
|
||||||
fun View.showKeyboard() {
|
suspend fun AssetManager.readAssetToString(asset: String): String {
|
||||||
(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? {
|
|
||||||
return withContext(Dispatchers.IO) {
|
return withContext(Dispatchers.IO) {
|
||||||
open(asset).reader().use(Reader::readText)
|
open(asset).reader().use(Reader::readText)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package com.wbrawner.simplemarkdown.utility
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.wbrawner.simplemarkdown.utility.PersistentTree.Companion.create
|
import com.wbrawner.simplemarkdown.utility.PersistentTree.Companion.create
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
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
|
* A [Timber.Tree] implementation that persists all logs to disk for retrieval later. Create
|
||||||
* instances via [create] instead of calling the constructor directly.
|
* 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>() {
|
private val dateFormat = object : ThreadLocal<SimpleDateFormat>() {
|
||||||
override fun initialValue(): SimpleDateFormat =
|
override fun initialValue(): SimpleDateFormat =
|
||||||
SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US)
|
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?) {
|
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
|
||||||
val timestamp = dateFormat.get()!!.format(System.currentTimeMillis())
|
val timestamp = dateFormat.get()!!.format(System.currentTimeMillis())
|
||||||
GlobalScope.launch(Dispatchers.IO) {
|
coroutineScope.launch(Dispatchers.IO) {
|
||||||
val priorityLetter = when (priority) {
|
val priorityLetter = when (priority) {
|
||||||
Log.ASSERT -> "A"
|
Log.ASSERT -> "A"
|
||||||
Log.DEBUG -> "D"
|
Log.DEBUG -> "D"
|
||||||
|
@ -63,14 +67,14 @@ class PersistentTree private constructor(private val logFile: File) : Timber.Tre
|
||||||
* created/written to
|
* created/written to
|
||||||
*/
|
*/
|
||||||
@Throws(IllegalArgumentException::class, IOException::class)
|
@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)
|
if (!logDir.mkdirs() && !logDir.isDirectory)
|
||||||
throw IllegalArgumentException("Unable to create log directory at ${logDir.absolutePath}")
|
throw IllegalArgumentException("Unable to create log directory at ${logDir.absolutePath}")
|
||||||
val timestamp = SimpleDateFormat("yyyyMMddHHmmss", Locale.US).format(Date())
|
val timestamp = SimpleDateFormat("yyyyMMddHHmmss", Locale.US).format(Date())
|
||||||
val logFile = File(logDir, "persistent-log-$timestamp.log")
|
val logFile = File(logDir, "persistent-log-$timestamp.log")
|
||||||
if (!logFile.createNewFile())
|
if (!logFile.createNewFile())
|
||||||
throw IOException("Unable to create logFile at ${logFile.absolutePath}")
|
throw IOException("Unable to create logFile at ${logFile.absolutePath}")
|
||||||
PersistentTree(logFile)
|
PersistentTree(coroutineScope, logFile)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -23,7 +23,7 @@ interface PreferenceHelper {
|
||||||
fun <T> observe(preference: Preference): StateFlow<T>
|
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 sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
private val states = mapOf(
|
private val states = mapOf(
|
||||||
Preference.ANALYTICS_ENABLED to MutableStateFlow(get(Preference.ANALYTICS_ENABLED)),
|
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 |
|
@ -1,5 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<color name="colorBackground">#000000</color>
|
|
||||||
<color name="colorOnBackground">#FFFFFF</color>
|
|
||||||
</resources>
|
|
|
@ -4,8 +4,6 @@
|
||||||
<color name="colorPrimaryDark">#b71c1c</color>
|
<color name="colorPrimaryDark">#b71c1c</color>
|
||||||
<color name="colorAccent">#d32f2f</color>
|
<color name="colorAccent">#d32f2f</color>
|
||||||
<color name="colorBackground">#FFFFFF</color>
|
<color name="colorBackground">#FFFFFF</color>
|
||||||
<color name="colorOnBackground">#000000</color>
|
|
||||||
<color name="colorBackgroundGitHub">#24292e</color>
|
<color name="colorBackgroundGitHub">#24292e</color>
|
||||||
<color name="colorWhite">#FFFFFFFF</color>
|
|
||||||
<color name="colorBackgroundPlayStore">#689f38</color>
|
<color name="colorBackgroundPlayStore">#689f38</color>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
<resources>
|
|
||||||
<dimen name="fab_margin">16dp</dimen>
|
|
||||||
</resources>
|
|
|
@ -2,65 +2,52 @@
|
||||||
<string name="app_name">Simple Markdown</string>
|
<string name="app_name">Simple Markdown</string>
|
||||||
<string name="app_name_short">Markdown</string>
|
<string name="app_name_short">Markdown</string>
|
||||||
|
|
||||||
<string name="action_settings">Settings</string>
|
<string name="title_editor">Editor</string>
|
||||||
<string name="action_help">Help</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_edit">Edit</string>
|
||||||
<string name="action_preview">Preview</string>
|
<string name="action_preview">Preview</string>
|
||||||
|
<string name="ok">OK</string>
|
||||||
<string name="markdown_here">Markdown here…</string>
|
<string name="markdown_here">Markdown here…</string>
|
||||||
<string name="action_save">Save</string>
|
<string name="action_save">Save</string>
|
||||||
|
<string name="action_editor_actions">Editor Actions</string>
|
||||||
<string name="action_share">Share</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="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="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_save_error">An error occurred while saving the file</string>
|
||||||
<string name="file_loaded">File successfully loaded</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="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_new">New</string>
|
||||||
<string name="action_done">Done</string>
|
|
||||||
<string name="action_open">Open</string>
|
<string name="action_open">Open</string>
|
||||||
<string name="yes">Yes</string>
|
<string name="yes">Yes</string>
|
||||||
<string name="no">No</string>
|
<string name="no">No</string>
|
||||||
<string name="title_activity_settings">Settings</string>
|
|
||||||
<string name="pref_title_autosave">Enable autosave</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_lock_swipe">Lock Swiping</string>
|
||||||
<string name="action_select">Select</string>
|
|
||||||
<string name="action_privacy">Privacy</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_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_off">Error reports will not be sent</string>
|
||||||
<string name="pref_error_reports_on">Error reports will 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_title_readability">Enable readability highlighting (experimental)</string>
|
||||||
<string name="pref_readability_off">Readability highlighting is off</string>
|
<string name="pref_readability_off">Readability highlighting is off</string>
|
||||||
<string name="pref_readability_on">Readability highlighting is on</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_on">Files will be automatically saved</string>
|
||||||
<string name="pref_autosave_off">Files will not 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="title_dark_mode">Dark Mode</string>
|
||||||
<string name="pref_value_light">Light</string>
|
<string name="pref_value_light">Light</string>
|
||||||
<string name="pref_value_dark">Dark</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_light" translatable="false">light</string>
|
||||||
<string name="pref_key_dark_mode_dark" translatable="false">dark</string>
|
<string name="pref_key_dark_mode_dark" translatable="false">dark</string>
|
||||||
<string name="pref_key_dark_mode_auto" translatable="false">auto</string>
|
<string name="pref_key_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="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="action_save_as">Save as…</string>
|
||||||
<string name="support_info">SimpleMarkdown is a personal project of mine that I develop and
|
<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
|
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="action_view_github">View SimpleMarkdown on GitHub</string>
|
||||||
<string name="support_title">Support SimpleMarkdown</string>
|
<string name="support_title">Support SimpleMarkdown</string>
|
||||||
<string name="action_rate">Rate SimpleMarkdown</string>
|
<string name="action_rate">Rate SimpleMarkdown</string>
|
||||||
<string name="description_heart">Heart</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>
|
<!-- <item>@string/pref_value_auto</item>-->
|
||||||
<item>@string/pref_value_auto</item>
|
<!-- </string-array>-->
|
||||||
</string-array>
|
|
||||||
<string-array name="pref_values_dark_mode">
|
<string-array name="pref_values_dark_mode">
|
||||||
<item>@string/pref_key_dark_mode_light</item>
|
<item>@string/pref_key_dark_mode_light</item>
|
||||||
<item>@string/pref_key_dark_mode_dark</item>
|
<item>@string/pref_key_dark_mode_dark</item>
|
||||||
|
|
|
@ -47,6 +47,9 @@ android {
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = "1.8"
|
jvmTarget = "1.8"
|
||||||
}
|
}
|
||||||
|
lint {
|
||||||
|
warningsAsErrors = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
|
@ -33,6 +33,9 @@ android {
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = "1.8"
|
jvmTarget = "1.8"
|
||||||
}
|
}
|
||||||
|
lint {
|
||||||
|
warningsAsErrors = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[versions]
|
[versions]
|
||||||
acra = "5.11.3"
|
acra = "5.11.3"
|
||||||
activityKtx = "1.9.0"
|
activityKtx = "1.9.1"
|
||||||
animationCore = "1.6.8"
|
animationCore = "1.6.8"
|
||||||
appcompat = "1.7.0"
|
appcompat = "1.7.0"
|
||||||
billing = "7.0.0"
|
billing = "7.0.0"
|
||||||
|
@ -20,7 +20,7 @@ androidGradlePlugin = "8.5.1"
|
||||||
hamcrestCore = "1.3"
|
hamcrestCore = "1.3"
|
||||||
junit = "4.13.2"
|
junit = "4.13.2"
|
||||||
kotlin = "2.0.0"
|
kotlin = "2.0.0"
|
||||||
lifecycleViewmodelKtx = "2.8.3"
|
lifecycleViewmodelKtx = "2.8.4"
|
||||||
material = "1.12.0"
|
material = "1.12.0"
|
||||||
material3WindowSizeClassAndroid = "1.2.1"
|
material3WindowSizeClassAndroid = "1.2.1"
|
||||||
materialIconsCore = "1.6.8"
|
materialIconsCore = "1.6.8"
|
||||||
|
|
|
@ -33,6 +33,9 @@ android {
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = "1.8"
|
jvmTarget = "1.8"
|
||||||
}
|
}
|
||||||
|
lint {
|
||||||
|
warningsAsErrors = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
Loading…
Reference in a new issue