Migrate to Jetpack Compose + Material3
This commit is contained in:
parent
e352add928
commit
643345493e
21 changed files with 1503 additions and 628 deletions
|
@ -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")
|
||||
|
|
|
@ -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">
|
||||
<activity
|
||||
android:name=".view.activity.SplashActivity"
|
||||
android:theme="@style/AppTheme.Splash"
|
||||
<activity android:name=".view.activity.MainActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/app_name_short">
|
||||
<intent-filter>
|
||||
|
@ -40,7 +38,6 @@
|
|||
<data android:host="*" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity android:name=".view.activity.MainActivity" android:exported="false" />
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
## Privacy Policy
|
||||
|
||||
The internet access permission is requested primarily for retrieving images from the internet in
|
||||
case you embed them in your markdown, but it also allows me to send automated error and crash
|
||||
reports to myself whenever the app runs into an issue. These error reports are opt-out, and are
|
||||
|
|
210
app/src/main/java/com/wbrawner/simplemarkdown/ui/MainScreen.kt
Normal file
210
app/src/main/java/com/wbrawner/simplemarkdown/ui/MainScreen.kt
Normal file
|
@ -0,0 +1,210 @@
|
|||
package com.wbrawner.simplemarkdown.ui
|
||||
|
||||
import android.content.Intent
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Menu
|
||||
import androidx.compose.material.icons.filled.MoreVert
|
||||
import androidx.compose.material.icons.filled.Share
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.DismissibleDrawerSheet
|
||||
import androidx.compose.material3.DismissibleNavigationDrawer
|
||||
import androidx.compose.material3.DrawerState
|
||||
import androidx.compose.material3.DrawerValue
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.NavigationDrawerItem
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Tab
|
||||
import androidx.compose.material3.TabRow
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.rememberDrawerState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
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.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.content.ContextCompat.startActivity
|
||||
import androidx.navigation.NavController
|
||||
import com.wbrawner.simplemarkdown.R
|
||||
import com.wbrawner.simplemarkdown.view.activity.Route
|
||||
import com.wbrawner.simplemarkdown.viewmodel.MarkdownViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun MainScreen(navController: NavController, viewModel: MarkdownViewModel) {
|
||||
var lockSwiping by remember { mutableStateOf(false) }
|
||||
MarkdownNavigationDrawer(navigate = { navController.navigate(it.path) }) { drawerState ->
|
||||
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 ?: {})
|
||||
}
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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 = "<style>$css</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)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
|
@ -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<String>,
|
||||
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<String>, 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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
package com.wbrawner.simplemarkdown.ui
|
||||
|
|
@ -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)
|
|
@ -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
|
||||
)
|
||||
}
|
|
@ -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<String, String>()
|
||||
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(
|
||||
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
|
||||
)
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<String>,
|
||||
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<String>,
|
||||
// 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
|
||||
// }
|
||||
//}
|
||||
|
|
|
@ -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 = "<style>" +
|
||||
"%s" +
|
||||
"</style>"
|
||||
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 = "<style>" +
|
||||
// "%s" +
|
||||
// "</style>"
|
||||
// const val EXTRA_TITLE = "title"
|
||||
// const val EXTRA_FILE = "file"
|
||||
// }
|
||||
//}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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")
|
||||
// }
|
||||
//}
|
||||
|
|
|
@ -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
|
||||
//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
|
||||
// }
|
||||
// return super.onOptionsItemSelected(item)
|
||||
//
|
||||
// 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")
|
||||
// }
|
||||
//}
|
|
@ -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<String?>("Untitled.md")
|
||||
val markdownUpdates = MutableLiveData<String>()
|
||||
val fileName = MutableStateFlow("Untitled.md")
|
||||
val markdownUpdates = MutableStateFlow("")
|
||||
val editorActions = MutableLiveData<EditorAction>()
|
||||
val uri = MutableLiveData<Uri?>()
|
||||
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")
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
<resources>
|
||||
|
||||
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
|
||||
<style name="Theme.App" parent="Theme.MaterialComponents.DayNight.NoActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
<item name="android:statusBarColor">@color/colorBackground</item>
|
||||
<item name="android:navigationBarColor">@color/colorBackground</item>
|
||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||
<item name="android:navigationBarColor">@android:color/transparent</item>
|
||||
</style>
|
||||
<style name="Theme.App.Starting" parent="Theme.SplashScreen">
|
||||
<item name="windowSplashScreenBackground">@color/colorBackground</item>
|
||||
<item name="windowSplashScreenAnimatedIcon">@drawable/splash_fg</item>
|
||||
<item name="postSplashScreenTheme">@style/Theme.App</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
<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 automatically save</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>
|
||||
|
|
|
@ -1,20 +1,19 @@
|
|||
<resources>
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
|
||||
<style name="Theme.App" parent="Theme.MaterialComponents.DayNight.NoActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
<item name="android:statusBarColor">@color/colorBackground</item>
|
||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||
<item name="android:windowLightStatusBar">true</item>
|
||||
<item name="android:navigationBarColor">#FF000000</item>
|
||||
<item name="android:navigationBarColor">@android:color/transparent</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.Splash" parent="AppTheme">
|
||||
<item name="android:windowLightStatusBar">false</item>
|
||||
<item name="android:statusBarColor">@color/colorPrimary</item>
|
||||
<item name="android:navigationBarColor">@color/colorPrimary</item>
|
||||
<item name="android:windowBackground">@drawable/splash_bg</item>
|
||||
<style name="Theme.App.Starting" parent="Theme.SplashScreen">
|
||||
<item name="windowSplashScreenBackground">@color/colorPrimary</item>
|
||||
<item name="windowSplashScreenAnimatedIcon">@drawable/splash_fg</item>
|
||||
<item name="postSplashScreenTheme">@style/Theme.App</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
|
Loading…
Reference in a new issue