Compare commits
6 commits
06cbc5ec31
...
79a5a3809a
Author | SHA1 | Date | |
---|---|---|---|
79a5a3809a | |||
840ebc4fd1 | |||
b0e8ebbf71 | |||
262b63cfa0 | |||
c6e14d7d0b | |||
86cd33ff5f |
31 changed files with 198 additions and 157 deletions
|
@ -55,6 +55,8 @@ jobs:
|
|||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: '17'
|
||||
- name: Setup Android SDK
|
||||
uses: https://git.wbrawner.com/android-actions/setup-android@v3
|
||||
- name: Build with Gradle
|
||||
uses: https://git.wbrawner.com/gradle/gradle-build-action@v2
|
||||
with:
|
||||
|
|
|
@ -97,6 +97,9 @@ android {
|
|||
commit.set(true)
|
||||
}
|
||||
}
|
||||
lint {
|
||||
warningsAsErrors = true
|
||||
}
|
||||
}
|
||||
|
||||
play {
|
||||
|
|
|
@ -6,13 +6,10 @@ import android.content.Context
|
|||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.webkit.WebView
|
||||
import androidx.compose.ui.input.key.Key
|
||||
import androidx.compose.ui.input.key.KeyEvent
|
||||
import androidx.compose.ui.input.key.NativeKeyEvent
|
||||
import androidx.compose.ui.semantics.SemanticsProperties
|
||||
import androidx.compose.ui.semantics.getOrNull
|
||||
import androidx.compose.ui.test.ExperimentalTestApi
|
||||
import androidx.compose.ui.test.SemanticsMatcher
|
||||
import androidx.compose.ui.test.SemanticsNodeInteraction
|
||||
import androidx.compose.ui.test.assert
|
||||
import androidx.compose.ui.test.assertIsDisplayed
|
||||
import androidx.compose.ui.test.assertIsNotDisplayed
|
||||
|
@ -27,13 +24,8 @@ import androidx.compose.ui.test.junit4.createEmptyComposeRule
|
|||
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.compose.ui.test.performKeyInput
|
||||
import androidx.compose.ui.test.performKeyPress
|
||||
import androidx.compose.ui.test.performTextInput
|
||||
import androidx.compose.ui.test.performTextInputSelection
|
||||
import androidx.compose.ui.test.performTextReplacement
|
||||
import androidx.compose.ui.test.printToLog
|
||||
import androidx.compose.ui.text.TextRange
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.test.core.app.ActivityScenario
|
||||
import androidx.test.core.app.ApplicationProvider.getApplicationContext
|
||||
|
@ -233,7 +225,7 @@ class MarkdownTests {
|
|||
|
||||
private fun ComposeTestRule.checkTitleEquals(title: String) =
|
||||
onNode(hasAnySibling(hasContentDescription("Main Menu")).and(hasText(title)))
|
||||
.assertIsDisplayed()
|
||||
.waitUntilIsDisplayed()
|
||||
|
||||
private fun ComposeTestRule.typeMarkdown(markdown: String) =
|
||||
onNode(hasSetTextAction()).performTextReplacement(markdown)
|
||||
|
@ -243,7 +235,9 @@ class MarkdownTests {
|
|||
val markdownMatcher = SemanticsMatcher("Markdown = [$markdown]") {
|
||||
it.config.getOrNull(SemanticsProperties.EditableText)?.text == markdown
|
||||
}
|
||||
onNode(hasSetTextAction()).assert(markdownMatcher)
|
||||
onNode(hasSetTextAction()).waitUntil {
|
||||
assert(markdownMatcher)
|
||||
}
|
||||
}
|
||||
|
||||
private fun ComposeTestRule.openPreview() = onNodeWithText("Preview").performClick()
|
||||
|
@ -258,12 +252,38 @@ class MarkdownTests {
|
|||
private fun ComposeTestRule.clickSaveMenuItem() = onNodeWithText("Save").performClick()
|
||||
|
||||
private fun ComposeTestRule.verifyDialogIsShown(text: String) =
|
||||
onNode(isDialog().and(hasAnyDescendant(hasText(text)))).assertIsDisplayed()
|
||||
onNode(isDialog().and(hasAnyDescendant(hasText(text)))).waitUntilIsDisplayed()
|
||||
|
||||
private fun ComposeTestRule.verifyDialogIsNotShown() = onNode(isDialog()).assertIsNotDisplayed()
|
||||
private fun ComposeTestRule.verifyDialogIsNotShown() =
|
||||
onNode(isDialog()).waitUntilIsNotDisplayed()
|
||||
|
||||
private fun ComposeTestRule.discardChanges() = onNodeWithText("No").performClick()
|
||||
|
||||
private fun ComposeTestRule.verifyTextIsShown(text: String) =
|
||||
onNodeWithText(text).assertIsDisplayed()
|
||||
onNodeWithText(text).waitUntilIsDisplayed()
|
||||
|
||||
private val ASSERTION_TIMEOUT = 5_000L
|
||||
|
||||
private fun SemanticsNodeInteraction.waitUntil(assertion: SemanticsNodeInteraction.() -> Unit) {
|
||||
val start = System.currentTimeMillis()
|
||||
lateinit var assertionError: AssertionError
|
||||
while (System.currentTimeMillis() - start < ASSERTION_TIMEOUT) {
|
||||
try {
|
||||
assertion()
|
||||
return
|
||||
} catch (e: AssertionError) {
|
||||
assertionError = e
|
||||
Thread.sleep(10)
|
||||
}
|
||||
}
|
||||
throw assertionError
|
||||
}
|
||||
|
||||
private fun SemanticsNodeInteraction.waitUntilIsDisplayed() = waitUntil {
|
||||
assertIsDisplayed()
|
||||
}
|
||||
|
||||
private fun SemanticsNodeInteraction.waitUntilIsNotDisplayed() = waitUntil {
|
||||
assertIsNotDisplayed()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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, arrayOf(name))
|
||||
)
|
||||
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)
|
||||
}
|
||||
|
@ -209,7 +209,8 @@ class MarkdownViewModel(
|
|||
))
|
||||
return
|
||||
}
|
||||
_state.value = EditorState(fileName = untitledFileName)
|
||||
_state.value =
|
||||
EditorState(fileName = untitledFileName, reloadToggle = _state.value.reloadToggle.inv())
|
||||
Timber.i("Removing autosave uri from shared prefs")
|
||||
preferenceHelper[Preference.AUTOSAVE_URI] = null
|
||||
}
|
||||
|
@ -231,9 +232,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
|
||||
}
|
||||
}
|
|
@ -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)
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 |
|
@ -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="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>
|
||||
|
|
|
@ -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_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_loaded">Successfully loaded %1$s</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>
|
||||
|
|
|
@ -5,14 +5,11 @@ import androidx.lifecycle.viewmodel.CreationExtras
|
|||
import com.wbrawner.simplemarkdown.utility.Preference
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||
import kotlinx.coroutines.test.TestResult
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.advanceTimeBy
|
||||
import kotlinx.coroutines.test.advanceUntilIdle
|
||||
import kotlinx.coroutines.test.runCurrent
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlinx.coroutines.test.setMain
|
||||
import org.junit.Assert.assertEquals
|
||||
|
@ -25,8 +22,6 @@ import org.junit.Test
|
|||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.net.URI
|
||||
import java.util.Deque
|
||||
import java.util.concurrent.ConcurrentLinkedDeque
|
||||
|
||||
class MarkdownViewModelTest {
|
||||
private lateinit var fileHelper: FakeFileHelper
|
||||
|
@ -185,7 +180,7 @@ class MarkdownViewModelTest {
|
|||
requireNotNull(onClick)
|
||||
onClick.invoke()
|
||||
}
|
||||
assertEquals(viewModel.state.value, EditorState())
|
||||
assertEquals(viewModel.state.value, EditorState(reloadToggle = 0.inv()))
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -204,7 +199,7 @@ class MarkdownViewModelTest {
|
|||
viewModel.save(uri)
|
||||
assertNotNull(viewModel.state.value.saveCallback)
|
||||
requireNotNull(viewModel.state.value.saveCallback).invoke()
|
||||
assertEquals(viewModel.state.value, EditorState())
|
||||
assertEquals(viewModel.state.value, EditorState(reloadToggle = 0.inv()))
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -47,6 +47,9 @@ android {
|
|||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
lint {
|
||||
warningsAsErrors = true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
|
|
@ -33,6 +33,9 @@ android {
|
|||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
lint {
|
||||
warningsAsErrors = true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -33,6 +33,9 @@ android {
|
|||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
lint {
|
||||
warningsAsErrors = true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
|
Loading…
Reference in a new issue