diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 6723798..48611fa 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -4,7 +4,6 @@ import java.util.*
plugins {
id("com.android.application")
- id("kotlin-android-extensions")
id("kotlin-android")
id("kotlin-kapt")
id("com.osacky.fladle")
@@ -36,7 +35,7 @@ android {
)
}
}
- compileSdk = 33
+ compileSdk = 34
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
@@ -47,7 +46,7 @@ android {
defaultConfig {
applicationId = "com.wbrawner.simplemarkdown"
minSdk = 23
- targetSdk = 33
+ targetSdk = 34
versionCode = 41
versionName = "0.8.16"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
@@ -89,6 +88,12 @@ android {
execution = "ANDROIDX_TEST_ORCHESTRATOR"
}
namespace = "com.wbrawner.simplemarkdown"
+ buildFeatures {
+ compose = true
+ }
+ composeOptions {
+ kotlinCompilerExtensionVersion = "1.5.3"
+ }
playConfigs {
register("play") {
enabled.set(true)
@@ -105,9 +110,10 @@ play {
}
dependencies {
- val navigationVersion = "2.5.3"
+ val navigationVersion = "2.7.2"
implementation("androidx.navigation:navigation-fragment-ktx:$navigationVersion")
implementation("androidx.navigation:navigation-ui-ktx:$navigationVersion")
+ implementation("androidx.navigation:navigation-compose:$navigationVersion")
testImplementation("junit:junit:4.13.2")
testImplementation("org.robolectric:robolectric:4.2.1")
val espressoVersion = "3.5.1"
@@ -119,18 +125,32 @@ dependencies {
androidTestUtil("androidx.test:orchestrator:1.4.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.uiautomator:uiautomator:2.2.0")
+ implementation("androidx.core:core-splashscreen:1.0.1")
implementation("com.wbrawner.plausible:plausible-android:0.1.0-SNAPSHOT")
- implementation("androidx.appcompat:appcompat:1.6.0")
- implementation("androidx.preference:preference-ktx:1.2.0")
- implementation("androidx.fragment:fragment-ktx:1.5.5")
+ implementation("androidx.appcompat:appcompat:1.6.1")
+ implementation("androidx.preference:preference-ktx:1.2.1")
+ implementation("androidx.fragment:fragment-ktx:1.6.1")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
- implementation("com.google.android.material:material:1.8.0")
+ implementation("com.google.android.material:material:1.9.0")
implementation("androidx.legacy:legacy-support-v13:1.0.0")
implementation("com.commonsware.cwac:anddown:0.4.0")
implementation("com.jakewharton.timber:timber:5.0.1")
- implementation("androidx.core:core-ktx:1.9.0")
- implementation("androidx.browser:browser:1.4.0")
- val coroutinesVersion = "1.6.4"
+ implementation("androidx.core:core-ktx:1.12.0")
+ implementation("androidx.browser:browser:1.6.0")
+ val composeBom = platform("androidx.compose:compose-bom:2023.08.00")
+ implementation(composeBom)
+ androidTestImplementation(composeBom)
+ implementation("androidx.compose.runtime:runtime")
+ implementation("androidx.compose.ui:ui")
+ implementation("androidx.activity:activity-compose")
+ implementation("androidx.compose.foundation:foundation")
+ implementation("androidx.compose.foundation:foundation-layout")
+ implementation("androidx.compose.material:material")
+ implementation("androidx.compose.runtime:runtime-livedata")
+ implementation("androidx.compose.ui:ui-tooling")
+ implementation("androidx.compose.material3:material3")
+ implementation("androidx.compose.material:material-icons-extended")
+ val coroutinesVersion = "1.7.1"
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion")
val lifecycleVersion = "2.2.0"
implementation("androidx.lifecycle:lifecycle-extensions:$lifecycleVersion")
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 4f36fe5..9245ef4 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -12,12 +12,10 @@
android:resizeableActivity="true"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
- android:theme="@style/AppTheme"
+ android:theme="@style/Theme.App.Starting"
tools:ignore="AllowBackup"
tools:targetApi="n">
-
@@ -40,7 +38,6 @@
-
+ Scaffold(topBar = {
+ val fileName by viewModel.fileName.collectAsState()
+ val context = LocalContext.current
+ MarkdownTopAppBar(title = fileName,
+ backAsUp = false,
+ navController = navController,
+ drawerState = drawerState,
+ actions = {
+ IconButton(onClick = {
+ val shareIntent = Intent(Intent.ACTION_SEND)
+ shareIntent.putExtra(Intent.EXTRA_TEXT, viewModel.markdownUpdates.value)
+ shareIntent.type = "text/plain"
+ startActivity(
+ context, Intent.createChooser(
+ shareIntent, context.getString(R.string.share_file)
+ ), null
+ )
+ }) {
+ Icon(imageVector = Icons.Default.Share, contentDescription = "Share")
+ }
+ Box {
+ var menuExpanded by remember { mutableStateOf(false) }
+ IconButton(onClick = { menuExpanded = true }) {
+ Icon(imageVector = Icons.Default.MoreVert, "Editor Actions")
+ }
+ DropdownMenu(expanded = menuExpanded,
+ onDismissRequest = { menuExpanded = false }) {
+ DropdownMenuItem(text = { Text("New") }, onClick = {
+ menuExpanded = false
+ })
+ DropdownMenuItem(text = { Text("Open") }, onClick = {
+ menuExpanded = false
+ })
+ DropdownMenuItem(text = { Text("Save") }, onClick = {
+ menuExpanded = false
+ })
+ DropdownMenuItem(text = { Text("Save as…") },
+ onClick = {
+ menuExpanded = false
+ })
+ DropdownMenuItem(text = {
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ Text("Lock Swiping")
+ Checkbox(checked = lockSwiping, onCheckedChange = { lockSwiping = !lockSwiping })
+ }
+ }, onClick = {
+ lockSwiping = !lockSwiping
+ menuExpanded = false
+ })
+ }
+ }
+ })
+ }) { paddingValues ->
+ val coroutineScope = rememberCoroutineScope()
+ val pagerState = rememberPagerState { 2 }
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(paddingValues)
+ ) {
+ TabRow(selectedTabIndex = pagerState.currentPage) {
+ Tab(text = { Text("Edit") },
+ selected = pagerState.currentPage == 0,
+ onClick = { coroutineScope.launch { pagerState.animateScrollToPage(0) } })
+ Tab(text = { Text("Preview") },
+ selected = pagerState.currentPage == 1,
+ onClick = { coroutineScope.launch { pagerState.animateScrollToPage(1) } })
+ }
+ HorizontalPager(
+ modifier = Modifier.weight(1f), state = pagerState,
+ userScrollEnabled = !lockSwiping
+ ) { page ->
+ val markdown by viewModel.markdownUpdates.collectAsState()
+ if (page == 0) {
+ BasicTextField(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(8.dp),
+ value = markdown,
+ onValueChange = { viewModel.updateMarkdown(it) },
+ textStyle = TextStyle.Default.copy(fontFamily = FontFamily.Monospace)
+ )
+ } else {
+ MarkdownPreview(modifier = Modifier.fillMaxSize(), markdown)
+ }
+ }
+ }
+ }
+ }
+}
+
+@Composable
+fun MarkdownNavigationDrawer(
+ navigate: (Route) -> Unit, content: @Composable (drawerState: DrawerState) -> Unit
+) {
+ val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
+ DismissibleNavigationDrawer(drawerState = drawerState, drawerContent = {
+ DismissibleDrawerSheet {
+ Route.entries.forEach { route ->
+ if (route == Route.EDITOR) {
+ return@forEach
+ }
+ NavigationDrawerItem(icon = {
+ Icon(imageVector = route.icon, contentDescription = null)
+ },
+ label = { Text(route.title) },
+ selected = false,
+ onClick = { navigate(route) })
+ }
+ }
+ }) {
+ content(drawerState)
+ }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun MarkdownTopAppBar(
+ title: String,
+ navController: NavController,
+ backAsUp: Boolean = true,
+ drawerState: DrawerState? = null,
+ actions: (@Composable RowScope.() -> Unit)? = null
+) {
+ val coroutineScope = rememberCoroutineScope()
+ TopAppBar(title = {
+ Text(title)
+ }, navigationIcon = {
+ val (icon, contentDescription, onClick) = remember {
+ if (backAsUp) {
+ Triple(Icons.Default.ArrowBack, "Go Back") { navController.popBackStack() }
+ } else {
+ Triple(
+ Icons.Default.Menu, "Main Menu"
+ ) {
+ coroutineScope.launch {
+ if (drawerState?.isOpen == true) {
+ drawerState.close()
+ } else {
+ drawerState?.open()
+ }
+ }
+ }
+ }
+ }
+ IconButton(onClick = { onClick() }) {
+ Icon(imageVector = icon, contentDescription = contentDescription)
+ }
+ }, actions = actions ?: {})
+}
diff --git a/app/src/main/java/com/wbrawner/simplemarkdown/ui/MarkdownInfoScreen.kt b/app/src/main/java/com/wbrawner/simplemarkdown/ui/MarkdownInfoScreen.kt
new file mode 100644
index 0000000..bf63a78
--- /dev/null
+++ b/app/src/main/java/com/wbrawner/simplemarkdown/ui/MarkdownInfoScreen.kt
@@ -0,0 +1,45 @@
+package com.wbrawner.simplemarkdown.ui
+
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Scaffold
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.navigation.NavController
+import com.wbrawner.simplemarkdown.utility.readAssetToString
+import com.wbrawner.simplemarkdown.utility.toHtml
+import com.wbrawner.simplemarkdown.viewmodel.MarkdownViewModel
+
+@Composable
+fun MarkdownInfoScreen(
+ title: String,
+ file: String,
+ navController: NavController,
+) {
+ Scaffold(
+ topBar = {
+ MarkdownTopAppBar(
+ title = title,
+ navController = navController,
+ )
+ }
+ ) { paddingValues ->
+ val context = LocalContext.current
+ var markdown by remember { mutableStateOf("") }
+ LaunchedEffect(file) {
+ markdown = context.assets.readAssetToString(file)?.toHtml()?: "Failed to load $file"
+ }
+ MarkdownPreview(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(paddingValues),
+ markdown = markdown
+ )
+ }
+}
diff --git a/app/src/main/java/com/wbrawner/simplemarkdown/ui/MarkdownPreview.kt b/app/src/main/java/com/wbrawner/simplemarkdown/ui/MarkdownPreview.kt
new file mode 100644
index 0000000..23aa4ba
--- /dev/null
+++ b/app/src/main/java/com/wbrawner/simplemarkdown/ui/MarkdownPreview.kt
@@ -0,0 +1,69 @@
+package com.wbrawner.simplemarkdown.ui
+
+import android.content.res.Configuration
+import android.webkit.WebView
+import androidx.appcompat.app.AppCompatDelegate
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.preference.PreferenceManager
+import com.wbrawner.simplemarkdown.BuildConfig
+import com.wbrawner.simplemarkdown.R
+import com.wbrawner.simplemarkdown.utility.toHtml
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+@Composable
+fun MarkdownPreview(modifier: Modifier = Modifier, markdown: String) {
+ val coroutineScope = rememberCoroutineScope()
+ val context = LocalContext.current
+ var style by remember { mutableStateOf("") }
+ LaunchedEffect(context) {
+ val isNightMode = AppCompatDelegate.getDefaultNightMode() ==
+ AppCompatDelegate.MODE_NIGHT_YES
+ || context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
+ val defaultCssId = if (isNightMode) {
+ R.string.pref_custom_css_default_dark
+ } else {
+ R.string.pref_custom_css_default
+ }
+ val css = withContext(Dispatchers.IO) {
+ @Suppress("ConstantConditionIf")
+ if (!BuildConfig.ENABLE_CUSTOM_CSS) {
+ context.getString(defaultCssId)
+ } else {
+ PreferenceManager.getDefaultSharedPreferences(context)
+ .getString(
+ context.getString(R.string.pref_custom_css),
+ context.getString(defaultCssId)
+ )
+ }
+ }
+ style = ""
+ }
+ AndroidView(
+ modifier = modifier,
+ factory = { context ->
+ WebView(context)
+ },
+ update = { preview ->
+ coroutineScope.launch {
+ preview.loadDataWithBaseURL(null,
+ style + markdown.toHtml(),
+ "text/html",
+ "UTF-8", null
+ )
+ preview.setBackgroundColor(0x01000000)
+ }
+ }
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/wbrawner/simplemarkdown/ui/SettingsScreen.kt b/app/src/main/java/com/wbrawner/simplemarkdown/ui/SettingsScreen.kt
new file mode 100644
index 0000000..3aef1ed
--- /dev/null
+++ b/app/src/main/java/com/wbrawner/simplemarkdown/ui/SettingsScreen.kt
@@ -0,0 +1,263 @@
+package com.wbrawner.simplemarkdown.ui
+
+import android.content.SharedPreferences
+import android.content.res.Configuration.UI_MODE_NIGHT_YES
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.AlertDialog
+import androidx.compose.material3.AlertDialogDefaults
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.RadioButton
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Switch
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.core.content.edit
+import androidx.navigation.NavController
+import androidx.preference.PreferenceManager
+import com.wbrawner.simplemarkdown.ui.theme.SimpleMarkdownTheme
+
+const val PREF_KEY_AUTOSAVE = "autosave"
+const val PREF_KEY_DARK_MODE = "darkMode"
+const val PREF_KEY_ERROR_REPORTS = "crashlytics.enable"
+const val PREF_KEY_ANALYTICS = "analytics.enable"
+const val PREF_KEY_READABILITY = "readability.enable"
+
+@Composable
+fun SettingsScreen(navController: NavController) {
+ Scaffold(topBar = {
+ MarkdownTopAppBar(title = "Settings", navController = navController)
+ }) { paddingValues ->
+ val context = LocalContext.current
+ val sharedPreferences = remember { PreferenceManager.getDefaultSharedPreferences(context) }
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(paddingValues)
+ .verticalScroll(rememberScrollState())
+ ) {
+ BooleanPreference(
+ title = "Autosave",
+ enabledDescription = "Files will be saved automatically",
+ disabledDescription = "Files will not be saved automatically",
+ preferenceKey = PREF_KEY_AUTOSAVE,
+ sharedPreferences = sharedPreferences
+ )
+ ListPreference(
+ title = "Dark mode",
+ options = listOf("Auto", "Dark", "Light"),
+ defaultValue = "Auto",
+ preferenceKey = PREF_KEY_DARK_MODE,
+ sharedPreferences = sharedPreferences
+ )
+ BooleanPreference(
+ title = "Send crash reports",
+ enabledDescription = "Error reports will be sent",
+ disabledDescription = "Error reports will not be sent",
+ preferenceKey = PREF_KEY_ERROR_REPORTS,
+ sharedPreferences = sharedPreferences
+ )
+ BooleanPreference(
+ title = "Send analytics",
+ enabledDescription = "Analytics events will be sent",
+ disabledDescription = "Analytics events will not be sent",
+ preferenceKey = PREF_KEY_ANALYTICS,
+ sharedPreferences = sharedPreferences
+ )
+ BooleanPreference(
+ title = "Readability highlighting",
+ enabledDescription = "Readability highlighting is on",
+ disabledDescription = "Readability highlighting is off",
+ preferenceKey = PREF_KEY_READABILITY,
+ sharedPreferences = sharedPreferences,
+ defaultValue = false
+ )
+ }
+ }
+}
+
+@Composable
+fun BooleanPreference(
+ title: String,
+ enabledDescription: String,
+ disabledDescription: String,
+ preferenceKey: String,
+ sharedPreferences: SharedPreferences,
+ defaultValue: Boolean = true
+) {
+ var enabled by remember {
+ mutableStateOf(
+ sharedPreferences.getBoolean(
+ preferenceKey, defaultValue
+ )
+ )
+ }
+ BooleanPreference(title = title,
+ enabledDescription = enabledDescription,
+ disabledDescription = disabledDescription,
+ enabled = enabled,
+ setEnabled = {
+ enabled = it
+ sharedPreferences.edit {
+ putBoolean(preferenceKey, it)
+ }
+ })
+}
+
+@Composable
+fun BooleanPreference(
+ title: String,
+ enabledDescription: String,
+ disabledDescription: String,
+ enabled: Boolean,
+ setEnabled: (Boolean) -> Unit
+) {
+ Row(modifier = Modifier
+ .fillMaxWidth()
+ .clickable {
+ setEnabled(!enabled)
+ }
+ .padding(16.dp),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically) {
+ Column(verticalArrangement = Arrangement.Center) {
+ Text(text = title, style = MaterialTheme.typography.bodyLarge)
+ Text(
+ text = if (enabled) enabledDescription else disabledDescription,
+ style = MaterialTheme.typography.bodyMedium,
+ color = MaterialTheme.colorScheme.onSurfaceVariant
+ )
+ }
+ Switch(checked = enabled, onCheckedChange = setEnabled)
+ }
+}
+
+@Composable
+fun ListPreference(
+ title: String,
+ options: List,
+ defaultValue: String,
+ preferenceKey: String,
+ sharedPreferences: SharedPreferences
+) {
+ var selected by remember {
+ mutableStateOf(
+ sharedPreferences.getString(
+ preferenceKey, defaultValue
+ ) ?: defaultValue
+ )
+ }
+
+ ListPreference(title = title, options = options, selected = selected, setSelected = {
+ selected = it
+ sharedPreferences.edit {
+ putString(preferenceKey, it)
+ }
+ })
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun ListPreference(
+ title: String, options: List, selected: String, setSelected: (String) -> Unit
+) {
+ var dialogShowing by remember { mutableStateOf(false) }
+ Column(modifier = Modifier
+ .fillMaxWidth()
+ .clickable {
+ dialogShowing = true
+ }
+ .padding(16.dp), verticalArrangement = Arrangement.Center) {
+ Text(text = title, style = MaterialTheme.typography.bodyLarge)
+ Text(
+ text = selected,
+ style = MaterialTheme.typography.bodyMedium,
+ color = MaterialTheme.colorScheme.onSurfaceVariant
+ )
+ }
+ if (dialogShowing) {
+ AlertDialog(
+ title = {
+ Text(title)
+ },
+ onDismissRequest = { dialogShowing = false },
+ confirmButton = {
+ TextButton(onClick = { dialogShowing = false }) {
+ Text("Cancel")
+ }
+ },
+ text = {
+ Column {
+ options.forEach { option ->
+ val onClick = {
+ setSelected(option)
+ dialogShowing = false
+ }
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .clickable(onClick = onClick),
+ horizontalArrangement = Arrangement.Start,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ RadioButton(selected = option == selected, onClick = onClick)
+ Text(option)
+ }
+ }
+ }
+ }
+ )
+ }
+}
+
+@Preview
+@Preview(uiMode = UI_MODE_NIGHT_YES)
+@Composable
+fun BooleanPreference_Preview() {
+ val (enabled, setEnabled) = remember { mutableStateOf(true) }
+ SimpleMarkdownTheme {
+ Surface {
+ BooleanPreference(
+ "Autosave",
+ "Files will be saved automatically",
+ "Files will not be saved automatically",
+ enabled,
+ setEnabled
+ )
+ }
+ }
+}
+
+@Preview
+@Preview(uiMode = UI_MODE_NIGHT_YES)
+@Composable
+fun ListPreference_Preview() {
+ val (selected, setSelected) = remember { mutableStateOf("Auto") }
+ SimpleMarkdownTheme {
+ Surface {
+ ListPreference(
+ "Dark mode", listOf("Light", "Dark", "Auto"), selected, setSelected
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/wbrawner/simplemarkdown/ui/SupportScreen.kt b/app/src/main/java/com/wbrawner/simplemarkdown/ui/SupportScreen.kt
new file mode 100644
index 0000000..c6a85c4
--- /dev/null
+++ b/app/src/main/java/com/wbrawner/simplemarkdown/ui/SupportScreen.kt
@@ -0,0 +1,2 @@
+package com.wbrawner.simplemarkdown.ui
+
diff --git a/app/src/main/java/com/wbrawner/simplemarkdown/ui/theme/Color.kt b/app/src/main/java/com/wbrawner/simplemarkdown/ui/theme/Color.kt
new file mode 100644
index 0000000..75217c2
--- /dev/null
+++ b/app/src/main/java/com/wbrawner/simplemarkdown/ui/theme/Color.kt
@@ -0,0 +1,67 @@
+package com.wbrawner.simplemarkdown.ui.theme
+import androidx.compose.ui.graphics.Color
+
+val md_theme_light_primary = Color(0xFFBA1A20)
+val md_theme_light_onPrimary = Color(0xFFFFFFFF)
+val md_theme_light_primaryContainer = Color(0xFFFFDAD6)
+val md_theme_light_onPrimaryContainer = Color(0xFF410003)
+val md_theme_light_secondary = Color(0xFF775653)
+val md_theme_light_onSecondary = Color(0xFFFFFFFF)
+val md_theme_light_secondaryContainer = Color(0xFFFFDAD6)
+val md_theme_light_onSecondaryContainer = Color(0xFF2C1513)
+val md_theme_light_tertiary = Color(0xFF725B2E)
+val md_theme_light_onTertiary = Color(0xFFFFFFFF)
+val md_theme_light_tertiaryContainer = Color(0xFFFEDEA6)
+val md_theme_light_onTertiaryContainer = Color(0xFF261900)
+val md_theme_light_error = Color(0xFFBA1A1A)
+val md_theme_light_errorContainer = Color(0xFFFFDAD6)
+val md_theme_light_onError = Color(0xFFFFFFFF)
+val md_theme_light_onErrorContainer = Color(0xFF410002)
+val md_theme_light_background = Color(0xFFFFFBFF)
+val md_theme_light_onBackground = Color(0xFF201A19)
+val md_theme_light_surface = Color(0xFFFFFBFF)
+val md_theme_light_onSurface = Color(0xFF201A19)
+val md_theme_light_surfaceVariant = Color(0xFFF5DDDB)
+val md_theme_light_onSurfaceVariant = Color(0xFF534342)
+val md_theme_light_outline = Color(0xFF857371)
+val md_theme_light_inverseOnSurface = Color(0xFFFBEEEC)
+val md_theme_light_inverseSurface = Color(0xFF362F2E)
+val md_theme_light_inversePrimary = Color(0xFFFFB3AC)
+val md_theme_light_shadow = Color(0xFF000000)
+val md_theme_light_surfaceTint = Color(0xFFBA1A20)
+val md_theme_light_outlineVariant = Color(0xFFD8C2BF)
+val md_theme_light_scrim = Color(0xFF000000)
+
+val md_theme_dark_primary = Color(0xFFFFB3AC)
+val md_theme_dark_onPrimary = Color(0xFF680008)
+val md_theme_dark_primaryContainer = Color(0xFF930010)
+val md_theme_dark_onPrimaryContainer = Color(0xFFFFDAD6)
+val md_theme_dark_secondary = Color(0xFFE7BDB8)
+val md_theme_dark_onSecondary = Color(0xFF442927)
+val md_theme_dark_secondaryContainer = Color(0xFF5D3F3C)
+val md_theme_dark_onSecondaryContainer = Color(0xFFFFDAD6)
+val md_theme_dark_tertiary = Color(0xFFE1C38C)
+val md_theme_dark_onTertiary = Color(0xFF3F2D04)
+val md_theme_dark_tertiaryContainer = Color(0xFF584419)
+val md_theme_dark_onTertiaryContainer = Color(0xFFFEDEA6)
+val md_theme_dark_error = Color(0xFFFFB4AB)
+val md_theme_dark_errorContainer = Color(0xFF93000A)
+val md_theme_dark_onError = Color(0xFF690005)
+val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)
+val md_theme_dark_background = Color(0xFF201A19)
+val md_theme_dark_onBackground = Color(0xFFEDE0DE)
+val md_theme_dark_surface = Color(0xFF201A19)
+val md_theme_dark_onSurface = Color(0xFFEDE0DE)
+val md_theme_dark_surfaceVariant = Color(0xFF534342)
+val md_theme_dark_onSurfaceVariant = Color(0xFFD8C2BF)
+val md_theme_dark_outline = Color(0xFFA08C8A)
+val md_theme_dark_inverseOnSurface = Color(0xFF201A19)
+val md_theme_dark_inverseSurface = Color(0xFFEDE0DE)
+val md_theme_dark_inversePrimary = Color(0xFFBA1A20)
+val md_theme_dark_shadow = Color(0xFF000000)
+val md_theme_dark_surfaceTint = Color(0xFFFFB3AC)
+val md_theme_dark_outlineVariant = Color(0xFF534342)
+val md_theme_dark_scrim = Color(0xFF000000)
+
+
+val seed = Color(0xFFD32F2F)
diff --git a/app/src/main/java/com/wbrawner/simplemarkdown/ui/theme/Theme.kt b/app/src/main/java/com/wbrawner/simplemarkdown/ui/theme/Theme.kt
new file mode 100644
index 0000000..36cd939
--- /dev/null
+++ b/app/src/main/java/com/wbrawner/simplemarkdown/ui/theme/Theme.kt
@@ -0,0 +1,90 @@
+package com.wbrawner.simplemarkdown.ui.theme
+
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.lightColorScheme
+import androidx.compose.material3.darkColorScheme
+import androidx.compose.runtime.Composable
+
+
+private val LightColors = lightColorScheme(
+ primary = md_theme_light_primary,
+ onPrimary = md_theme_light_onPrimary,
+ primaryContainer = md_theme_light_primaryContainer,
+ onPrimaryContainer = md_theme_light_onPrimaryContainer,
+ secondary = md_theme_light_secondary,
+ onSecondary = md_theme_light_onSecondary,
+ secondaryContainer = md_theme_light_secondaryContainer,
+ onSecondaryContainer = md_theme_light_onSecondaryContainer,
+ tertiary = md_theme_light_tertiary,
+ onTertiary = md_theme_light_onTertiary,
+ tertiaryContainer = md_theme_light_tertiaryContainer,
+ onTertiaryContainer = md_theme_light_onTertiaryContainer,
+ error = md_theme_light_error,
+ errorContainer = md_theme_light_errorContainer,
+ onError = md_theme_light_onError,
+ onErrorContainer = md_theme_light_onErrorContainer,
+ background = md_theme_light_background,
+ onBackground = md_theme_light_onBackground,
+ surface = md_theme_light_surface,
+ onSurface = md_theme_light_onSurface,
+ surfaceVariant = md_theme_light_surfaceVariant,
+ onSurfaceVariant = md_theme_light_onSurfaceVariant,
+ outline = md_theme_light_outline,
+ inverseOnSurface = md_theme_light_inverseOnSurface,
+ inverseSurface = md_theme_light_inverseSurface,
+ inversePrimary = md_theme_light_inversePrimary,
+ surfaceTint = md_theme_light_surfaceTint,
+ outlineVariant = md_theme_light_outlineVariant,
+ scrim = md_theme_light_scrim,
+)
+
+
+private val DarkColors = darkColorScheme(
+ primary = md_theme_dark_primary,
+ onPrimary = md_theme_dark_onPrimary,
+ primaryContainer = md_theme_dark_primaryContainer,
+ onPrimaryContainer = md_theme_dark_onPrimaryContainer,
+ secondary = md_theme_dark_secondary,
+ onSecondary = md_theme_dark_onSecondary,
+ secondaryContainer = md_theme_dark_secondaryContainer,
+ onSecondaryContainer = md_theme_dark_onSecondaryContainer,
+ tertiary = md_theme_dark_tertiary,
+ onTertiary = md_theme_dark_onTertiary,
+ tertiaryContainer = md_theme_dark_tertiaryContainer,
+ onTertiaryContainer = md_theme_dark_onTertiaryContainer,
+ error = md_theme_dark_error,
+ errorContainer = md_theme_dark_errorContainer,
+ onError = md_theme_dark_onError,
+ onErrorContainer = md_theme_dark_onErrorContainer,
+ background = md_theme_dark_background,
+ onBackground = md_theme_dark_onBackground,
+ surface = md_theme_dark_surface,
+ onSurface = md_theme_dark_onSurface,
+ surfaceVariant = md_theme_dark_surfaceVariant,
+ onSurfaceVariant = md_theme_dark_onSurfaceVariant,
+ outline = md_theme_dark_outline,
+ inverseOnSurface = md_theme_dark_inverseOnSurface,
+ inverseSurface = md_theme_dark_inverseSurface,
+ inversePrimary = md_theme_dark_inversePrimary,
+ surfaceTint = md_theme_dark_surfaceTint,
+ outlineVariant = md_theme_dark_outlineVariant,
+ scrim = md_theme_dark_scrim,
+)
+
+@Composable
+fun SimpleMarkdownTheme(
+ useDarkTheme: Boolean = isSystemInDarkTheme(),
+ content: @Composable () -> Unit
+) {
+ val colors = if (!useDarkTheme) {
+ LightColors
+ } else {
+ DarkColors
+ }
+
+ MaterialTheme(
+ colorScheme = colors,
+ content = content
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/wbrawner/simplemarkdown/view/activity/MainActivity.kt b/app/src/main/java/com/wbrawner/simplemarkdown/view/activity/MainActivity.kt
index 44f5cfd..6c1bf17 100644
--- a/app/src/main/java/com/wbrawner/simplemarkdown/view/activity/MainActivity.kt
+++ b/app/src/main/java/com/wbrawner/simplemarkdown/view/activity/MainActivity.kt
@@ -1,19 +1,87 @@
package com.wbrawner.simplemarkdown.view.activity
import android.content.Context
+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
+import androidx.compose.animation.EnterTransition
+import androidx.compose.animation.core.EaseIn
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Edit
+import androidx.compose.material.icons.filled.Favorite
+import androidx.compose.material.icons.filled.Help
+import androidx.compose.material.icons.filled.Info
+import androidx.compose.material.icons.filled.PrivacyTip
+import androidx.compose.material.icons.filled.Settings
+import androidx.compose.material3.Text
+import androidx.compose.ui.graphics.vector.ImageVector
import androidx.core.app.ActivityCompat
+import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
+import androidx.core.view.WindowCompat
+import androidx.lifecycle.lifecycleScope
+import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.composable
+import androidx.navigation.compose.rememberNavController
import androidx.navigation.findNavController
import androidx.preference.PreferenceManager
import com.wbrawner.plausible.android.Plausible
import com.wbrawner.simplemarkdown.R
+import com.wbrawner.simplemarkdown.ui.MainScreen
+import com.wbrawner.simplemarkdown.ui.MarkdownInfoScreen
+import com.wbrawner.simplemarkdown.ui.SettingsScreen
+import com.wbrawner.simplemarkdown.ui.theme.SimpleMarkdownTheme
+import com.wbrawner.simplemarkdown.viewmodel.MarkdownViewModel
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+const val KEY_AUTOSAVE = "autosave"
class MainActivity : AppCompatActivity(), ActivityCompat.OnRequestPermissionsResultCallback {
+ private val viewModel: MarkdownViewModel by viewModels()
+
override fun onCreate(savedInstanceState: Bundle?) {
+ installSplashScreen()
super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_main)
+ lifecycleScope.launch {
+ val darkMode = withContext(Dispatchers.IO) {
+ val darkModeValue = getStringPref(
+ R.string.pref_key_dark_mode,
+ getString(R.string.pref_value_auto)
+ )
+
+ return@withContext when {
+ darkModeValue.equals(
+ getString(R.string.pref_value_light),
+ ignoreCase = true
+ ) -> AppCompatDelegate.MODE_NIGHT_NO
+
+ darkModeValue.equals(
+ getString(R.string.pref_value_dark),
+ ignoreCase = true
+ ) -> AppCompatDelegate.MODE_NIGHT_YES
+
+ else -> {
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
+ AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY
+ } else {
+ AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
+ }
+ }
+ }
+ }
+ AppCompatDelegate.setDefaultNightMode(darkMode)
+ }
+ WindowCompat.setDecorFitsSystemWindows(window, false)
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
val preferences = mutableMapOf()
preferences["Autosave"] = sharedPreferences.getBoolean("autosave", true).toString()
@@ -25,6 +93,53 @@ class MainActivity : AppCompatActivity(), ActivityCompat.OnRequestPermissionsRes
getBooleanPref(R.string.pref_key_error_reports_enabled, true).toString()
preferences["Readability"] = getBooleanPref(R.string.readability_enabled, false).toString()
Plausible.event("settings", props = preferences, url = "/")
+ setContent {
+ SimpleMarkdownTheme {
+ val navController = rememberNavController()
+ NavHost(
+ navController = navController,
+ startDestination = Route.EDITOR.path,
+ enterTransition = { fadeIn(
+ animationSpec = tween(
+ 300, easing = LinearEasing
+ )
+ ) + slideIntoContainer(
+ animationSpec = tween(300, easing = EaseIn),
+ towards = AnimatedContentTransitionScope.SlideDirection.Start
+ ) },
+ popEnterTransition = { EnterTransition.None },
+ popExitTransition = {
+ fadeOut(
+ animationSpec = tween(
+ 300, easing = LinearEasing
+ )
+ ) + slideOutOfContainer(
+ animationSpec = tween(300, easing = EaseIn),
+ towards = AnimatedContentTransitionScope.SlideDirection.End
+ )
+ }
+ ) {
+ composable(Route.EDITOR.path) {
+ MainScreen(navController = navController, viewModel = viewModel)
+ }
+ composable(Route.SETTINGS.path) {
+ SettingsScreen(navController = navController)
+ }
+ composable(Route.SUPPORT.path) {
+ Text("To do")
+ }
+ composable(Route.HELP.path) {
+ MarkdownInfoScreen(title = Route.HELP.title, file = "Cheatsheet.md", navController = navController)
+ }
+ composable(Route.ABOUT.path) {
+ MarkdownInfoScreen(title = Route.ABOUT.title, file = "Libraries.md", navController = navController)
+ }
+ composable(Route.PRIVACY.path) {
+ MarkdownInfoScreen(title = Route.PRIVACY.title, file = "Privacy Policy.md", navController = navController)
+ }
+ }
+ }
+ }
}
override fun onBackPressed() {
@@ -34,12 +149,27 @@ class MainActivity : AppCompatActivity(), ActivityCompat.OnRequestPermissionsRes
}
}
-fun Context.getBooleanPref(@StringRes key: Int, defaultValue: Boolean) = PreferenceManager.getDefaultSharedPreferences(this).getBoolean(
- getString(key),
- defaultValue
-)
+enum class Route(
+ val path: String,
+ val title: String,
+ 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.Default.Help),
+ ABOUT("/about", "About", Icons.Default.Info),
+ PRIVACY("/privacy", "Privacy", Icons.Default.PrivacyTip),
+}
-fun Context.getStringPref(@StringRes key: Int, defaultValue: String?) = PreferenceManager.getDefaultSharedPreferences(this).getString(
- getString(key),
- defaultValue
-)
\ No newline at end of file
+fun Context.getBooleanPref(@StringRes key: Int, defaultValue: Boolean) =
+ PreferenceManager.getDefaultSharedPreferences(this).getBoolean(
+ getString(key),
+ defaultValue
+ )
+
+fun Context.getStringPref(@StringRes key: Int, defaultValue: String?) =
+ PreferenceManager.getDefaultSharedPreferences(this).getString(
+ getString(key),
+ defaultValue
+ )
\ No newline at end of file
diff --git a/app/src/main/java/com/wbrawner/simplemarkdown/view/activity/SplashActivity.kt b/app/src/main/java/com/wbrawner/simplemarkdown/view/activity/SplashActivity.kt
deleted file mode 100644
index 833b864..0000000
--- a/app/src/main/java/com/wbrawner/simplemarkdown/view/activity/SplashActivity.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-package com.wbrawner.simplemarkdown.view.activity
-
-import android.content.Intent
-import android.os.Build
-import android.os.Bundle
-import androidx.appcompat.app.AppCompatActivity
-import androidx.appcompat.app.AppCompatDelegate
-import androidx.lifecycle.lifecycleScope
-import androidx.preference.PreferenceManager
-import com.wbrawner.simplemarkdown.R
-import kotlinx.coroutines.*
-import timber.log.Timber
-
-class SplashActivity : AppCompatActivity() {
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- lifecycleScope.launch {
- val darkMode = withContext(Dispatchers.IO) {
- val darkModeValue = PreferenceManager.getDefaultSharedPreferences(this@SplashActivity)
- .getString(
- getString(R.string.pref_key_dark_mode),
- getString(R.string.pref_value_auto)
- )
-
- return@withContext when {
- darkModeValue.equals(getString(R.string.pref_value_light), ignoreCase = true) -> AppCompatDelegate.MODE_NIGHT_NO
- darkModeValue.equals(getString(R.string.pref_value_dark), ignoreCase = true) -> AppCompatDelegate.MODE_NIGHT_YES
- else -> {
- if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
- AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY
- } else {
- AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
- }
- }
- }
-
- }
-
- AppCompatDelegate.setDefaultNightMode(darkMode)
- val uri = intent?.data?.let {
- Timber.d("Using uri from intent: $it")
- it
- } ?: run {
- Timber.d("No intent provided to load data from")
- null
- }
-
- val startIntent = Intent(this@SplashActivity, MainActivity::class.java)
- .apply {
- data = uri
- }
- startActivity(startIntent)
- finish()
- }
- }
-}
diff --git a/app/src/main/java/com/wbrawner/simplemarkdown/view/fragment/MainFragment.kt b/app/src/main/java/com/wbrawner/simplemarkdown/view/fragment/MainFragment.kt
index f72ac82..b44b6d9 100644
--- a/app/src/main/java/com/wbrawner/simplemarkdown/view/fragment/MainFragment.kt
+++ b/app/src/main/java/com/wbrawner/simplemarkdown/view/fragment/MainFragment.kt
@@ -1,328 +1,335 @@
-package com.wbrawner.simplemarkdown.view.fragment
-
-import android.Manifest
-import android.app.Activity
-import android.content.Context
-import android.content.Intent
-import android.content.pm.PackageManager
-import android.content.res.Configuration
-import android.os.Build
-import android.os.Bundle
-import android.view.*
-import android.webkit.MimeTypeMap
-import android.widget.Toast
-import androidx.appcompat.app.AlertDialog
-import androidx.appcompat.app.AppCompatActivity
-import androidx.core.app.ActivityCompat
-import androidx.core.content.ContextCompat
-import androidx.fragment.app.Fragment
-import androidx.fragment.app.viewModels
-import androidx.lifecycle.lifecycleScope
-import androidx.navigation.fragment.findNavController
-import androidx.navigation.ui.AppBarConfiguration
-import androidx.navigation.ui.setupWithNavController
-import androidx.preference.PreferenceManager
-import com.wbrawner.plausible.android.Plausible
-import com.wbrawner.simplemarkdown.R
-import com.wbrawner.simplemarkdown.utility.ErrorHandler
-import com.wbrawner.simplemarkdown.utility.errorHandlerImpl
-import com.wbrawner.simplemarkdown.view.adapter.EditPagerAdapter
-import com.wbrawner.simplemarkdown.viewmodel.MarkdownViewModel
-import kotlinx.android.synthetic.main.fragment_main.*
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-import timber.log.Timber
-
-class MainFragment : Fragment(), ActivityCompat.OnRequestPermissionsResultCallback {
-
- private val viewModel: MarkdownViewModel by viewModels()
- private var appBarConfiguration: AppBarConfiguration? = null
- private val errorHandler: ErrorHandler by errorHandlerImpl()
-
- override fun onAttach(context: Context) {
- super.onAttach(context)
- if (context !is Activity) return
- lifecycleScope.launch {
- viewModel.load(context, context.intent?.data)
- context.intent?.data = null
- }
- }
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setHasOptionsMenu(true)
- }
-
- override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
- inflater.inflate(R.menu.menu_edit, menu)
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- menu.findItem(R.id.action_save_as)
- ?.setAlphabeticShortcut('S', KeyEvent.META_CTRL_ON or KeyEvent.META_SHIFT_ON)
- }
- }
-
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View? =
- inflater.inflate(R.layout.fragment_main, container, false)
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- with(findNavController()) {
- appBarConfiguration = AppBarConfiguration(graph, drawerLayout)
- toolbar.setupWithNavController(this, appBarConfiguration!!)
- (activity as? AppCompatActivity)?.setSupportActionBar(toolbar)
- navigationView.setupWithNavController(this)
- }
- val adapter = EditPagerAdapter(childFragmentManager, view.context)
- pager.adapter = adapter
- pager.addOnPageChangeListener(adapter)
- pager.pageMargin = 1
- pager.setPageMarginDrawable(R.color.colorAccent)
- tabLayout.setupWithViewPager(pager)
- if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
- tabLayout!!.visibility = View.GONE
- }
- @Suppress("CAST_NEVER_SUCCEEDS")
- viewModel.fileName.observe(viewLifecycleOwner) {
- toolbar?.title = it
- }
- }
-
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- return when (item.itemId) {
- android.R.id.home -> {
- drawerLayout.open()
- true
- }
-
- R.id.action_save -> {
- Timber.d("Save clicked")
- lifecycleScope.launch {
- if (!viewModel.save(requireContext())) {
- requestFileOp(REQUEST_SAVE_FILE)
- } else {
- Toast.makeText(
- requireContext(),
- getString(R.string.file_saved, viewModel.fileName.value),
- Toast.LENGTH_SHORT
- ).show()
- }
- }
- true
- }
-
- R.id.action_save_as -> {
- Timber.d("Save as clicked")
- requestFileOp(REQUEST_SAVE_FILE)
- true
- }
-
- R.id.action_share -> {
- Timber.d("Share clicked")
- val shareIntent = Intent(Intent.ACTION_SEND)
- shareIntent.putExtra(Intent.EXTRA_TEXT, viewModel.markdownUpdates.value)
- shareIntent.type = "text/plain"
- startActivity(
- Intent.createChooser(
- shareIntent,
- getString(R.string.share_file)
- )
- )
- true
- }
-
- R.id.action_load -> {
- Timber.d("Load clicked")
- requestFileOp(REQUEST_OPEN_FILE)
- true
- }
-
- R.id.action_new -> {
- Timber.d("New clicked")
- promptSaveOrDiscardChanges()
- true
- }
-
- R.id.action_lock_swipe -> {
- Timber.d("Lock swiping clicked")
- item.isChecked = !item.isChecked
- pager!!.setSwipeLocked(item.isChecked)
- true
- }
-
- else -> super.onOptionsItemSelected(item)
- }
- }
-
- override fun onStart() {
- super.onStart()
- Plausible.pageView("")
- lifecycleScope.launch {
- withContext(Dispatchers.IO) {
- val enableErrorReports =
- PreferenceManager.getDefaultSharedPreferences(requireContext())
- .getBoolean(getString(R.string.pref_key_error_reports_enabled), true)
- Timber.d("MainFragment started. Error reports enabled? $enableErrorReports")
- errorHandler.enable(enableErrorReports)
- }
- }
- }
-
- override fun onStop() {
- super.onStop()
- val context = context?.applicationContext ?: return
- lifecycleScope.launch {
- viewModel.autosave(context, PreferenceManager.getDefaultSharedPreferences(context))
- }
- }
-
- override fun onConfigurationChanged(newConfig: Configuration) {
- super.onConfigurationChanged(newConfig)
- if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
- Timber.d("Orientation changed to landscape, hiding tabs")
- tabLayout?.visibility = View.GONE
- } else {
- Timber.d("Orientation changed to portrait, showing tabs")
- tabLayout?.visibility = View.VISIBLE
- }
- }
-
- override fun onRequestPermissionsResult(
- requestCode: Int,
- permissions: Array,
- grantResults: IntArray
- ) {
- when (requestCode) {
- REQUEST_SAVE_FILE, REQUEST_OPEN_FILE -> {
- // If request is cancelled, the result arrays are empty.
- if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
- // Permission granted, open file save dialog
- Timber.d("Storage permissions granted")
- requestFileOp(requestCode)
- } else {
- // Permission denied, do nothing
- Timber.d("Storage permissions denied, unable to save or load files")
- context?.let {
- Toast.makeText(it, R.string.no_permissions, Toast.LENGTH_SHORT)
- .show()
- }
- }
- }
- }
- }
-
- override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- when (requestCode) {
- REQUEST_OPEN_FILE -> {
- if (resultCode != Activity.RESULT_OK || data?.data == null) {
- Timber.w(
- "Unable to open file. Result ok? %b Intent uri: %s",
- resultCode == Activity.RESULT_OK,
- data?.data?.toString()
- )
- return
- }
-
- lifecycleScope.launch {
- context?.let {
- if (!viewModel.load(it, data.data)) {
- Toast.makeText(it, R.string.file_load_error, Toast.LENGTH_SHORT).show()
- }
- }
- }
- }
-
- REQUEST_SAVE_FILE -> {
- if (resultCode != Activity.RESULT_OK || data?.data == null) {
- Timber.w(
- "Unable to save file. Result ok? %b Intent uri: %s",
- resultCode == Activity.RESULT_OK,
- data?.data?.toString()
- )
- return
- }
-
- lifecycleScope.launch {
- context?.let {
- viewModel.save(it, data.data)
- }
- }
- }
- }
- super.onActivityResult(requestCode, resultCode, data)
- }
-
- private fun promptSaveOrDiscardChanges() {
- if (!viewModel.shouldPromptSave()) {
- viewModel.reset(
- "Untitled.md",
- PreferenceManager.getDefaultSharedPreferences(requireContext())
- )
- return
- }
- val context = context ?: run {
- Timber.w("Context is null, unable to show prompt for save or discard")
- return
- }
- AlertDialog.Builder(context)
- .setTitle(R.string.save_changes)
- .setMessage(R.string.prompt_save_changes)
- .setNegativeButton(R.string.action_discard) { _, _ ->
- Timber.d("Discarding changes")
- viewModel.reset(
- "Untitled.md",
- PreferenceManager.getDefaultSharedPreferences(requireContext())
- )
- }
- .setPositiveButton(R.string.action_save) { _, _ ->
- Timber.d("Saving changes")
- requestFileOp(REQUEST_SAVE_FILE)
- }
- .create()
- .show()
- }
-
- private fun requestFileOp(requestType: Int) {
- val intent = when (requestType) {
- REQUEST_SAVE_FILE -> {
- Timber.d("Requesting save op")
- Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
- type = "text/markdown"
- putExtra(Intent.EXTRA_TITLE, viewModel.fileName.value)
- }
- }
-
- REQUEST_OPEN_FILE -> {
- Timber.d("Requesting open op")
- Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
- type = "*/*"
- if (MimeTypeMap.getSingleton().hasMimeType("md")) {
- // If the device doesn't recognize markdown files then we're not going to be
- // able to open them at all, so there's no sense in filtering them out.
- putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("text/plain", "text/markdown"))
- }
- }
- }
-
- else -> {
- Timber.w("Ignoring unknown file op request: $requestType")
- null
- }
- } ?: return
- intent.addCategory(Intent.CATEGORY_OPENABLE)
- startActivityForResult(
- intent,
- requestType
- )
- }
-
- companion object {
- // Request codes
- const val REQUEST_OPEN_FILE = 1
- const val REQUEST_SAVE_FILE = 2
- const val KEY_AUTOSAVE = "autosave"
- }
-}
+//package com.wbrawner.simplemarkdown.view.fragment
+//
+//import android.app.Activity
+//import android.content.Context
+//import android.content.Intent
+//import android.content.pm.PackageManager
+//import android.content.res.Configuration
+//import android.os.Build
+//import android.os.Bundle
+//import android.view.*
+//import android.webkit.MimeTypeMap
+//import android.widget.Toast
+//import androidx.appcompat.app.AlertDialog
+//import androidx.appcompat.app.AppCompatActivity
+//import androidx.core.app.ActivityCompat
+//import androidx.fragment.app.Fragment
+//import androidx.fragment.app.viewModels
+//import androidx.lifecycle.lifecycleScope
+//import androidx.navigation.fragment.findNavController
+//import androidx.navigation.ui.AppBarConfiguration
+//import androidx.navigation.ui.setupWithNavController
+//import androidx.preference.PreferenceManager
+//import com.wbrawner.plausible.android.Plausible
+//import com.wbrawner.simplemarkdown.R
+//import com.wbrawner.simplemarkdown.databinding.FragmentMainBinding
+//import com.wbrawner.simplemarkdown.utility.ErrorHandler
+//import com.wbrawner.simplemarkdown.utility.errorHandlerImpl
+//import com.wbrawner.simplemarkdown.view.adapter.EditPagerAdapter
+//import com.wbrawner.simplemarkdown.viewmodel.MarkdownViewModel
+//import kotlinx.coroutines.Dispatchers
+//import kotlinx.coroutines.launch
+//import kotlinx.coroutines.withContext
+//import timber.log.Timber
+//
+//class MainFragment : Fragment(), ActivityCompat.OnRequestPermissionsResultCallback {
+//
+// private val viewModel: MarkdownViewModel by viewModels()
+// private var appBarConfiguration: AppBarConfiguration? = null
+// private val errorHandler: ErrorHandler by errorHandlerImpl()
+// private var _binding: FragmentMainBinding? = null
+// private val binding: FragmentMainBinding
+// get() = _binding!!
+//
+// override fun onAttach(context: Context) {
+// super.onAttach(context)
+// if (context !is Activity) return
+// lifecycleScope.launch {
+// viewModel.load(context, context.intent?.data)
+// context.intent?.data = null
+// }
+// }
+//
+// override fun onCreate(savedInstanceState: Bundle?) {
+// super.onCreate(savedInstanceState)
+// setHasOptionsMenu(true)
+// }
+//
+// override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
+// inflater.inflate(R.menu.menu_edit, menu)
+// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+// menu.findItem(R.id.action_save_as)
+// ?.setAlphabeticShortcut('S', KeyEvent.META_CTRL_ON or KeyEvent.META_SHIFT_ON)
+// }
+// }
+//
+// override fun onCreateView(
+// inflater: LayoutInflater,
+// container: ViewGroup?,
+// savedInstanceState: Bundle?
+// ): View {
+// _binding = FragmentMainBinding.inflate(inflater, container, false)
+// return binding.root
+// }
+//
+// override fun onDestroyView() {
+// super.onDestroyView()
+// _binding = null
+// }
+//
+// override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+// with(findNavController()) {
+// appBarConfiguration = AppBarConfiguration(graph, binding.drawerLayout)
+// binding.toolbar.setupWithNavController(this, appBarConfiguration!!)
+// (activity as? AppCompatActivity)?.setSupportActionBar(binding.toolbar)
+// binding.navigationView.setupWithNavController(this)
+// }
+// val adapter = EditPagerAdapter(childFragmentManager, view.context)
+// binding.pager.adapter = adapter
+// binding.pager.addOnPageChangeListener(adapter)
+// binding.pager.pageMargin = 1
+// binding.pager.setPageMarginDrawable(R.color.colorAccent)
+// binding.tabLayout.setupWithViewPager(binding.pager)
+// if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
+// binding.tabLayout.visibility = View.GONE
+// }
+// @Suppress("CAST_NEVER_SUCCEEDS")
+// viewModel.fileName.observe(viewLifecycleOwner) {
+// binding.toolbar.title = it
+// }
+// }
+//
+// override fun onOptionsItemSelected(item: MenuItem): Boolean {
+// return when (item.itemId) {
+// android.R.id.home -> {
+// binding.drawerLayout.open()
+// true
+// }
+//
+// R.id.action_save -> {
+// Timber.d("Save clicked")
+// lifecycleScope.launch {
+// if (!viewModel.save(requireContext())) {
+// requestFileOp(REQUEST_SAVE_FILE)
+// } else {
+// Toast.makeText(
+// requireContext(),
+// getString(R.string.file_saved, viewModel.fileName.value),
+// Toast.LENGTH_SHORT
+// ).show()
+// }
+// }
+// true
+// }
+//
+// R.id.action_save_as -> {
+// Timber.d("Save as clicked")
+// requestFileOp(REQUEST_SAVE_FILE)
+// true
+// }
+//
+// R.id.action_share -> {
+// Timber.d("Share clicked")
+// val shareIntent = Intent(Intent.ACTION_SEND)
+// shareIntent.putExtra(Intent.EXTRA_TEXT, viewModel.markdownUpdates.value)
+// shareIntent.type = "text/plain"
+// startActivity(
+// Intent.createChooser(
+// shareIntent,
+// getString(R.string.share_file)
+// )
+// )
+// true
+// }
+//
+// R.id.action_load -> {
+// Timber.d("Load clicked")
+// requestFileOp(REQUEST_OPEN_FILE)
+// true
+// }
+//
+// R.id.action_new -> {
+// Timber.d("New clicked")
+// promptSaveOrDiscardChanges()
+// true
+// }
+//
+// R.id.action_lock_swipe -> {
+// Timber.d("Lock swiping clicked")
+// item.isChecked = !item.isChecked
+// binding.pager.setSwipeLocked(item.isChecked)
+// true
+// }
+//
+// else -> super.onOptionsItemSelected(item)
+// }
+// }
+//
+// override fun onStart() {
+// super.onStart()
+// Plausible.pageView("")
+// lifecycleScope.launch {
+// withContext(Dispatchers.IO) {
+// val enableErrorReports =
+// PreferenceManager.getDefaultSharedPreferences(requireContext())
+// .getBoolean(getString(R.string.pref_key_error_reports_enabled), true)
+// Timber.d("MainFragment started. Error reports enabled? $enableErrorReports")
+// errorHandler.enable(enableErrorReports)
+// }
+// }
+// }
+//
+// override fun onStop() {
+// super.onStop()
+// val context = context?.applicationContext ?: return
+// lifecycleScope.launch {
+// viewModel.autosave(context, PreferenceManager.getDefaultSharedPreferences(context))
+// }
+// }
+//
+// override fun onConfigurationChanged(newConfig: Configuration) {
+// super.onConfigurationChanged(newConfig)
+// if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
+// Timber.d("Orientation changed to landscape, hiding tabs")
+// binding.tabLayout.visibility = View.GONE
+// } else {
+// Timber.d("Orientation changed to portrait, showing tabs")
+// binding.tabLayout.visibility = View.VISIBLE
+// }
+// }
+//
+// override fun onRequestPermissionsResult(
+// requestCode: Int,
+// permissions: Array,
+// grantResults: IntArray
+// ) {
+// when (requestCode) {
+// REQUEST_SAVE_FILE, REQUEST_OPEN_FILE -> {
+// // If request is cancelled, the result arrays are empty.
+// if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+// // Permission granted, open file save dialog
+// Timber.d("Storage permissions granted")
+// requestFileOp(requestCode)
+// } else {
+// // Permission denied, do nothing
+// Timber.d("Storage permissions denied, unable to save or load files")
+// context?.let {
+// Toast.makeText(it, R.string.no_permissions, Toast.LENGTH_SHORT)
+// .show()
+// }
+// }
+// }
+// }
+// }
+//
+// override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+// when (requestCode) {
+// REQUEST_OPEN_FILE -> {
+// if (resultCode != Activity.RESULT_OK || data?.data == null) {
+// Timber.w(
+// "Unable to open file. Result ok? %b Intent uri: %s",
+// resultCode == Activity.RESULT_OK,
+// data?.data?.toString()
+// )
+// return
+// }
+//
+// lifecycleScope.launch {
+// context?.let {
+// if (!viewModel.load(it, data.data)) {
+// Toast.makeText(it, R.string.file_load_error, Toast.LENGTH_SHORT).show()
+// }
+// }
+// }
+// }
+//
+// REQUEST_SAVE_FILE -> {
+// if (resultCode != Activity.RESULT_OK || data?.data == null) {
+// Timber.w(
+// "Unable to save file. Result ok? %b Intent uri: %s",
+// resultCode == Activity.RESULT_OK,
+// data?.data?.toString()
+// )
+// return
+// }
+//
+// lifecycleScope.launch {
+// context?.let {
+// viewModel.save(it, data.data)
+// }
+// }
+// }
+// }
+// super.onActivityResult(requestCode, resultCode, data)
+// }
+//
+// private fun promptSaveOrDiscardChanges() {
+// if (!viewModel.shouldPromptSave()) {
+// viewModel.reset(
+// "Untitled.md",
+// PreferenceManager.getDefaultSharedPreferences(requireContext())
+// )
+// return
+// }
+// val context = context ?: run {
+// Timber.w("Context is null, unable to show prompt for save or discard")
+// return
+// }
+// AlertDialog.Builder(context)
+// .setTitle(R.string.save_changes)
+// .setMessage(R.string.prompt_save_changes)
+// .setNegativeButton(R.string.action_discard) { _, _ ->
+// Timber.d("Discarding changes")
+// viewModel.reset(
+// "Untitled.md",
+// PreferenceManager.getDefaultSharedPreferences(requireContext())
+// )
+// }
+// .setPositiveButton(R.string.action_save) { _, _ ->
+// Timber.d("Saving changes")
+// requestFileOp(REQUEST_SAVE_FILE)
+// }
+// .create()
+// .show()
+// }
+//
+// private fun requestFileOp(requestType: Int) {
+// val intent = when (requestType) {
+// REQUEST_SAVE_FILE -> {
+// Timber.d("Requesting save op")
+// Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
+// type = "text/markdown"
+// putExtra(Intent.EXTRA_TITLE, viewModel.fileName.value)
+// }
+// }
+//
+// REQUEST_OPEN_FILE -> {
+// Timber.d("Requesting open op")
+// Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
+// type = "*/*"
+// if (MimeTypeMap.getSingleton().hasMimeType("md")) {
+// // If the device doesn't recognize markdown files then we're not going to be
+// // able to open them at all, so there's no sense in filtering them out.
+// putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("text/plain", "text/markdown"))
+// }
+// }
+// }
+//
+// else -> {
+// Timber.w("Ignoring unknown file op request: $requestType")
+// null
+// }
+// } ?: return
+// intent.addCategory(Intent.CATEGORY_OPENABLE)
+// startActivityForResult(
+// intent,
+// requestType
+// )
+// }
+//
+// companion object {
+// // Request codes
+// const val REQUEST_OPEN_FILE = 1
+// const val REQUEST_SAVE_FILE = 2
+// }
+//}
diff --git a/app/src/main/java/com/wbrawner/simplemarkdown/view/fragment/MarkdownInfoFragment.kt b/app/src/main/java/com/wbrawner/simplemarkdown/view/fragment/MarkdownInfoFragment.kt
index 52a8041..29a82ae 100644
--- a/app/src/main/java/com/wbrawner/simplemarkdown/view/fragment/MarkdownInfoFragment.kt
+++ b/app/src/main/java/com/wbrawner/simplemarkdown/view/fragment/MarkdownInfoFragment.kt
@@ -1,89 +1,99 @@
-package com.wbrawner.simplemarkdown.view.fragment
-
-import android.content.res.Configuration
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.MenuItem
-import android.view.View
-import android.view.ViewGroup
-import android.widget.Toast
-import androidx.appcompat.app.AppCompatDelegate
-import androidx.fragment.app.Fragment
-import androidx.lifecycle.lifecycleScope
-import androidx.navigation.fragment.findNavController
-import androidx.navigation.ui.setupWithNavController
-import com.wbrawner.plausible.android.Plausible
-import com.wbrawner.simplemarkdown.R
-import com.wbrawner.simplemarkdown.utility.*
-import kotlinx.android.synthetic.main.fragment_markdown_info.*
-import kotlinx.coroutines.launch
-
-class MarkdownInfoFragment : Fragment() {
- private val errorHandler: ErrorHandler by errorHandlerImpl()
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setHasOptionsMenu(true)
- }
-
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
- inflater.inflate(R.layout.fragment_markdown_info, container, false)
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- val fileName = arguments?.getString(EXTRA_FILE)
- if (fileName.isNullOrBlank()) {
- findNavController().navigateUp()
- return
- }
- toolbar.setupWithNavController(findNavController())
-
- val isNightMode = AppCompatDelegate.getDefaultNightMode() ==
- AppCompatDelegate.MODE_NIGHT_YES
- || resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
- val defaultCssId = if (isNightMode) {
- R.string.pref_custom_css_default_dark
- } else {
- R.string.pref_custom_css_default
- }
- val css: String? = getString(defaultCssId)
- lifecycleScope.launch {
- try {
- val html = view.context.assets?.readAssetToString(fileName)
- ?.toHtml()
- ?: throw RuntimeException("Unable to open stream to $fileName")
- infoWebview.loadDataWithBaseURL(null,
- String.format(FORMAT_CSS, css) + html,
- "text/html",
- "UTF-8", null
- )
- } catch (e: Exception) {
- errorHandler.reportException(e)
- Toast.makeText(view.context, R.string.file_load_error, Toast.LENGTH_SHORT).show()
- findNavController().navigateUp()
- }
- }
- }
-
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- if (item.itemId == android.R.id.home) {
- findNavController().navigateUp()
- return true
- }
- return super.onOptionsItemSelected(item)
- }
-
- override fun onStart() {
- super.onStart()
- arguments?.getString(EXTRA_FILE)?.let {
- Plausible.pageView(it)
- }
- }
-
- companion object {
- const val FORMAT_CSS = ""
- const val EXTRA_TITLE = "title"
- const val EXTRA_FILE = "file"
- }
-}
+//package com.wbrawner.simplemarkdown.view.fragment
+//
+//import android.content.res.Configuration
+//import android.os.Bundle
+//import android.view.LayoutInflater
+//import android.view.MenuItem
+//import android.view.View
+//import android.view.ViewGroup
+//import android.widget.Toast
+//import androidx.appcompat.app.AppCompatDelegate
+//import androidx.fragment.app.Fragment
+//import androidx.lifecycle.lifecycleScope
+//import androidx.navigation.fragment.findNavController
+//import androidx.navigation.ui.setupWithNavController
+//import com.wbrawner.plausible.android.Plausible
+//import com.wbrawner.simplemarkdown.R
+//import com.wbrawner.simplemarkdown.databinding.FragmentMarkdownInfoBinding
+//import com.wbrawner.simplemarkdown.utility.*
+//import kotlinx.coroutines.launch
+//
+//class MarkdownInfoFragment : Fragment() {
+// private val errorHandler: ErrorHandler by errorHandlerImpl()
+// private var _binding: FragmentMarkdownInfoBinding? = null
+// private val binding: FragmentMarkdownInfoBinding
+// get() = _binding!!
+//
+// override fun onCreate(savedInstanceState: Bundle?) {
+// super.onCreate(savedInstanceState)
+// setHasOptionsMenu(true)
+// }
+//
+// override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
+// _binding = FragmentMarkdownInfoBinding.inflate(inflater, container, false)
+// return binding.root
+// }
+//
+// override fun onDestroyView() {
+// super.onDestroyView()
+// _binding = null
+// }
+//
+// override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+// val fileName = arguments?.getString(EXTRA_FILE)
+// if (fileName.isNullOrBlank()) {
+// findNavController().navigateUp()
+// return
+// }
+// binding.toolbar.setupWithNavController(findNavController())
+//
+// val isNightMode = AppCompatDelegate.getDefaultNightMode() ==
+// AppCompatDelegate.MODE_NIGHT_YES
+// || resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
+// val defaultCssId = if (isNightMode) {
+// R.string.pref_custom_css_default_dark
+// } else {
+// R.string.pref_custom_css_default
+// }
+// val css: String? = getString(defaultCssId)
+// lifecycleScope.launch {
+// try {
+// val html = view.context.assets?.readAssetToString(fileName)
+// ?.toHtml()
+// ?: throw RuntimeException("Unable to open stream to $fileName")
+// binding.infoWebview.loadDataWithBaseURL(null,
+// String.format(FORMAT_CSS, css) + html,
+// "text/html",
+// "UTF-8", null
+// )
+// } catch (e: Exception) {
+// errorHandler.reportException(e)
+// Toast.makeText(view.context, R.string.file_load_error, Toast.LENGTH_SHORT).show()
+// findNavController().navigateUp()
+// }
+// }
+// }
+//
+// override fun onOptionsItemSelected(item: MenuItem): Boolean {
+// if (item.itemId == android.R.id.home) {
+// findNavController().navigateUp()
+// return true
+// }
+// return super.onOptionsItemSelected(item)
+// }
+//
+// override fun onStart() {
+// super.onStart()
+// arguments?.getString(EXTRA_FILE)?.let {
+// Plausible.pageView(it)
+// }
+// }
+//
+// companion object {
+// const val FORMAT_CSS = ""
+// const val EXTRA_TITLE = "title"
+// const val EXTRA_FILE = "file"
+// }
+//}
diff --git a/app/src/main/java/com/wbrawner/simplemarkdown/view/fragment/PreviewFragment.kt b/app/src/main/java/com/wbrawner/simplemarkdown/view/fragment/PreviewFragment.kt
index a031deb..804c8ee 100644
--- a/app/src/main/java/com/wbrawner/simplemarkdown/view/fragment/PreviewFragment.kt
+++ b/app/src/main/java/com/wbrawner/simplemarkdown/view/fragment/PreviewFragment.kt
@@ -62,9 +62,9 @@ class PreviewFragment : Fragment() {
override fun onAttach(context: Context) {
super.onAttach(context)
updateWebContent(viewModel.markdownUpdates.value ?: "")
- viewModel.markdownUpdates.observe(this, {
- updateWebContent(it)
- })
+// viewModel.markdownUpdates.observe(this, {
+// updateWebContent(it)
+// })
}
private fun updateWebContent(markdown: String) {
diff --git a/app/src/main/java/com/wbrawner/simplemarkdown/view/fragment/SettingsContainerFragment.kt b/app/src/main/java/com/wbrawner/simplemarkdown/view/fragment/SettingsContainerFragment.kt
index d9188d7..e61c6b7 100644
--- a/app/src/main/java/com/wbrawner/simplemarkdown/view/fragment/SettingsContainerFragment.kt
+++ b/app/src/main/java/com/wbrawner/simplemarkdown/view/fragment/SettingsContainerFragment.kt
@@ -1,34 +1,45 @@
-package com.wbrawner.simplemarkdown.view.fragment
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.MenuItem
-import android.view.View
-import android.view.ViewGroup
-import androidx.fragment.app.Fragment
-import androidx.navigation.fragment.findNavController
-import androidx.navigation.ui.setupWithNavController
-import com.wbrawner.plausible.android.Plausible
-import com.wbrawner.simplemarkdown.R
-import kotlinx.android.synthetic.main.fragment_settings.*
-
-class SettingsContainerFragment : Fragment() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setHasOptionsMenu(true)
- }
-
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
- inflater.inflate(R.layout.fragment_settings, container, false)
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- toolbar.setupWithNavController(findNavController())
- }
-
- override fun onOptionsItemSelected(item: MenuItem): Boolean = findNavController().navigateUp()
-
- override fun onStart() {
- super.onStart()
- Plausible.pageView("Settings")
- }
-}
+//package com.wbrawner.simplemarkdown.view.fragment
+//
+//import android.os.Bundle
+//import android.view.LayoutInflater
+//import android.view.MenuItem
+//import android.view.View
+//import android.view.ViewGroup
+//import androidx.fragment.app.Fragment
+//import androidx.navigation.fragment.findNavController
+//import androidx.navigation.ui.setupWithNavController
+//import com.wbrawner.plausible.android.Plausible
+//import com.wbrawner.simplemarkdown.R
+//import com.wbrawner.simplemarkdown.databinding.FragmentSettingsBinding
+//
+//class SettingsContainerFragment : Fragment() {
+// private var _binding: FragmentSettingsBinding? = null
+// private val binding: FragmentSettingsBinding
+// get() = _binding!!
+//
+// override fun onCreate(savedInstanceState: Bundle?) {
+// super.onCreate(savedInstanceState)
+// setHasOptionsMenu(true)
+// }
+//
+// override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
+// _binding = FragmentSettingsBinding.inflate(inflater, container, false)
+// return binding.root
+// }
+//
+// override fun onDestroyView() {
+// super.onDestroyView()
+// _binding = null
+// }
+//
+// override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+// binding.toolbar.setupWithNavController(findNavController())
+// }
+//
+// override fun onOptionsItemSelected(item: MenuItem): Boolean = findNavController().navigateUp()
+//
+// override fun onStart() {
+// super.onStart()
+// Plausible.pageView("Settings")
+// }
+//}
diff --git a/app/src/main/java/com/wbrawner/simplemarkdown/view/fragment/SupportFragment.kt b/app/src/main/java/com/wbrawner/simplemarkdown/view/fragment/SupportFragment.kt
index 1bef766..6910da7 100644
--- a/app/src/main/java/com/wbrawner/simplemarkdown/view/fragment/SupportFragment.kt
+++ b/app/src/main/java/com/wbrawner/simplemarkdown/view/fragment/SupportFragment.kt
@@ -1,67 +1,69 @@
-package com.wbrawner.simplemarkdown.view.fragment
-
-import android.content.ActivityNotFoundException
-import android.content.Intent
-import android.net.Uri
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.browser.customtabs.CustomTabsIntent
-import androidx.fragment.app.Fragment
-import androidx.lifecycle.Observer
-import androidx.navigation.fragment.findNavController
-import androidx.navigation.ui.setupWithNavController
-import com.wbrawner.plausible.android.Plausible
-import com.wbrawner.simplemarkdown.R
-import com.wbrawner.simplemarkdown.utility.SupportLinkProvider
-import kotlinx.android.synthetic.main.fragment_support.*
-
-class SupportFragment : Fragment() {
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
- inflater.inflate(R.layout.fragment_support, container, false)
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- toolbar.setupWithNavController(findNavController())
- githubButton.setOnClickListener {
- CustomTabsIntent.Builder()
- .addDefaultShareMenuItem()
- .build()
- .launchUrl(view.context, Uri.parse("https://github" +
- ".com/wbrawner/SimpleMarkdown"))
- }
- rateButton.setOnClickListener {
- val playStoreIntent = Intent(Intent.ACTION_VIEW)
- .apply {
- data = Uri.parse("market://details?id=${view.context.packageName}")
- addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY or
- Intent.FLAG_ACTIVITY_NEW_DOCUMENT or
- Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
- }
- try {
- startActivity(playStoreIntent)
- } catch (ignored: ActivityNotFoundException) {
- playStoreIntent.data = Uri.parse("https://play.google.com/store/apps/details?id=${view.context.packageName}")
- startActivity(playStoreIntent)
- }
- }
- SupportLinkProvider(requireActivity()).supportLinks.observe(viewLifecycleOwner, Observer { links ->
- links.forEach {
- supportButtons.addView(it)
- }
- })
- }
-
- override fun onStart() {
- super.onStart()
- Plausible.pageView("Support")
- }
-
- // override fun onOptionsItemSelected(item: MenuItem): Boolean {
-// if (item.itemId == android.R.id.home) {
-// findNavController().navigateUp()
-// return true
-// }
-// return super.onOptionsItemSelected(item)
+//package com.wbrawner.simplemarkdown.view.fragment
+//
+//import android.content.ActivityNotFoundException
+//import android.content.Intent
+//import android.net.Uri
+//import android.os.Bundle
+//import android.view.LayoutInflater
+//import android.view.View
+//import android.view.ViewGroup
+//import androidx.browser.customtabs.CustomTabsIntent
+//import androidx.fragment.app.Fragment
+//import androidx.lifecycle.Observer
+//import androidx.navigation.fragment.findNavController
+//import androidx.navigation.ui.setupWithNavController
+//import com.wbrawner.plausible.android.Plausible
+//import com.wbrawner.simplemarkdown.databinding.FragmentSupportBinding
+//import com.wbrawner.simplemarkdown.utility.SupportLinkProvider
+//
+//class SupportFragment : Fragment() {
+// private var _binding: FragmentSupportBinding? = null
+// private val binding: FragmentSupportBinding
+// get() = _binding!!
+//
+// override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
+// _binding = FragmentSupportBinding.inflate(inflater, container, false)
+// return binding.root
// }
-}
\ No newline at end of file
+//
+// override fun onDestroyView() {
+// super.onDestroyView()
+// _binding = null
+// }
+//
+// override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+// binding.toolbar.setupWithNavController(findNavController())
+// binding.githubButton.setOnClickListener {
+// CustomTabsIntent.Builder()
+// .addDefaultShareMenuItem()
+// .build()
+// .launchUrl(view.context, Uri.parse("https://github" +
+// ".com/wbrawner/SimpleMarkdown"))
+// }
+// binding.rateButton.setOnClickListener {
+// val playStoreIntent = Intent(Intent.ACTION_VIEW)
+// .apply {
+// data = Uri.parse("market://details?id=${view.context.packageName}")
+// addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY or
+// Intent.FLAG_ACTIVITY_NEW_DOCUMENT or
+// Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
+// }
+// try {
+// startActivity(playStoreIntent)
+// } catch (ignored: ActivityNotFoundException) {
+// playStoreIntent.data = Uri.parse("https://play.google.com/store/apps/details?id=${view.context.packageName}")
+// startActivity(playStoreIntent)
+// }
+// }
+// SupportLinkProvider(requireActivity()).supportLinks.observe(viewLifecycleOwner, Observer { links ->
+// links.forEach {
+// binding.supportButtons.addView(it)
+// }
+// })
+// }
+//
+// override fun onStart() {
+// super.onStart()
+// Plausible.pageView("Support")
+// }
+//}
\ No newline at end of file
diff --git a/app/src/main/java/com/wbrawner/simplemarkdown/viewmodel/MarkdownViewModel.kt b/app/src/main/java/com/wbrawner/simplemarkdown/viewmodel/MarkdownViewModel.kt
index cd1aeb2..d582ac3 100644
--- a/app/src/main/java/com/wbrawner/simplemarkdown/viewmodel/MarkdownViewModel.kt
+++ b/app/src/main/java/com/wbrawner/simplemarkdown/viewmodel/MarkdownViewModel.kt
@@ -6,10 +6,13 @@ import android.net.Uri
import androidx.core.content.edit
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
import androidx.preference.PreferenceManager
import com.wbrawner.simplemarkdown.utility.getName
-import com.wbrawner.simplemarkdown.view.fragment.MainFragment
+import com.wbrawner.simplemarkdown.view.activity.KEY_AUTOSAVE
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
@@ -22,15 +25,19 @@ import java.util.concurrent.atomic.AtomicBoolean
const val PREF_KEY_AUTOSAVE_URI = "autosave.uri"
class MarkdownViewModel(val timber: Timber.Tree = Timber.asTree()) : ViewModel() {
- val fileName = MutableLiveData("Untitled.md")
- val markdownUpdates = MutableLiveData()
+ val fileName = MutableStateFlow("Untitled.md")
+ val markdownUpdates = MutableStateFlow("")
val editorActions = MutableLiveData()
val uri = MutableLiveData()
private val isDirty = AtomicBoolean(false)
private val saveMutex = Mutex()
- fun updateMarkdown(markdown: String?) {
- this.markdownUpdates.postValue(markdown ?: "")
+ init {
+ markdownUpdates
+ }
+
+ fun updateMarkdown(markdown: String?) = viewModelScope.launch {
+ markdownUpdates.emit(markdown ?: "")
isDirty.set(true)
}
@@ -59,8 +66,8 @@ class MarkdownViewModel(val timber: Timber.Tree = Timber.asTree()) : ViewModel()
return@withContext false
}
editorActions.postValue(EditorAction.Load(content))
- markdownUpdates.postValue(content)
- this@MarkdownViewModel.fileName.postValue(fileName)
+ markdownUpdates.emit(content)
+ this@MarkdownViewModel.fileName.emit(fileName)
this@MarkdownViewModel.uri.postValue(uri)
timber.i("Loaded file $fileName from $fileInput")
timber.v("File contents:\n$content")
@@ -108,7 +115,7 @@ class MarkdownViewModel(val timber: Timber.Tree = Timber.asTree()) : ViewModel()
timber.w("Open output stream returned null for uri: $uri")
return@withContext false
}
- this@MarkdownViewModel.fileName.postValue(fileName)
+ this@MarkdownViewModel.fileName.emit(fileName)
this@MarkdownViewModel.uri.postValue(uri)
isDirty.set(false)
timber.i("Saved file $fileName to uri $uri")
@@ -129,7 +136,7 @@ class MarkdownViewModel(val timber: Timber.Tree = Timber.asTree()) : ViewModel()
timber.i("Ignoring autosave since manual save is already in progress")
return
}
- val isAutoSaveEnabled = sharedPrefs.getBoolean(MainFragment.KEY_AUTOSAVE, true)
+ val isAutoSaveEnabled = sharedPrefs.getBoolean(KEY_AUTOSAVE, true)
timber.d("Autosave called. isEnabled? $isAutoSaveEnabled")
if (!isDirty.get() || !isAutoSaveEnabled) {
timber.i("Ignoring call to autosave. Contents haven't changed or autosave not enabled")
@@ -148,11 +155,11 @@ class MarkdownViewModel(val timber: Timber.Tree = Timber.asTree()) : ViewModel()
}
}
- fun reset(untitledFileName: String, sharedPrefs: SharedPreferences) {
+ fun reset(untitledFileName: String, sharedPrefs: SharedPreferences) = viewModelScope.launch{
timber.i("Resetting view model to default state")
- fileName.postValue(untitledFileName)
+ fileName.tryEmit(untitledFileName)
uri.postValue(null)
- markdownUpdates.postValue("")
+ markdownUpdates.emit("")
editorActions.postValue(EditorAction.Load(""))
isDirty.set(false)
timber.i("Removing autosave uri from shared prefs")
diff --git a/app/src/main/res/values-night/styles.xml b/app/src/main/res/values-night/styles.xml
index 2d93108..0987e0f 100644
--- a/app/src/main/res/values-night/styles.xml
+++ b/app/src/main/res/values-night/styles.xml
@@ -1,11 +1,16 @@
-
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index a9f3dcf..af86184 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -42,7 +42,7 @@
Enable readability highlighting (experimental)
Readability highlighting is off
Readability highlighting is on
- Files will automatically save
+ Files will be automatically saved
Files will not be automatically saved
pref.custom_css
Custom CSS
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 1f0dad3..a73e4e8 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -1,20 +1,19 @@
-
-