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 {
|
plugins {
|
||||||
id("com.android.application")
|
id("com.android.application")
|
||||||
id("kotlin-android-extensions")
|
|
||||||
id("kotlin-android")
|
id("kotlin-android")
|
||||||
id("kotlin-kapt")
|
id("kotlin-kapt")
|
||||||
id("com.osacky.fladle")
|
id("com.osacky.fladle")
|
||||||
|
@ -36,7 +35,7 @@ android {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
compileSdk = 33
|
compileSdk = 34
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
@ -47,7 +46,7 @@ android {
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "com.wbrawner.simplemarkdown"
|
applicationId = "com.wbrawner.simplemarkdown"
|
||||||
minSdk = 23
|
minSdk = 23
|
||||||
targetSdk = 33
|
targetSdk = 34
|
||||||
versionCode = 41
|
versionCode = 41
|
||||||
versionName = "0.8.16"
|
versionName = "0.8.16"
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
@ -89,6 +88,12 @@ android {
|
||||||
execution = "ANDROIDX_TEST_ORCHESTRATOR"
|
execution = "ANDROIDX_TEST_ORCHESTRATOR"
|
||||||
}
|
}
|
||||||
namespace = "com.wbrawner.simplemarkdown"
|
namespace = "com.wbrawner.simplemarkdown"
|
||||||
|
buildFeatures {
|
||||||
|
compose = true
|
||||||
|
}
|
||||||
|
composeOptions {
|
||||||
|
kotlinCompilerExtensionVersion = "1.5.3"
|
||||||
|
}
|
||||||
playConfigs {
|
playConfigs {
|
||||||
register("play") {
|
register("play") {
|
||||||
enabled.set(true)
|
enabled.set(true)
|
||||||
|
@ -105,9 +110,10 @@ play {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
val navigationVersion = "2.5.3"
|
val navigationVersion = "2.7.2"
|
||||||
implementation("androidx.navigation:navigation-fragment-ktx:$navigationVersion")
|
implementation("androidx.navigation:navigation-fragment-ktx:$navigationVersion")
|
||||||
implementation("androidx.navigation:navigation-ui-ktx:$navigationVersion")
|
implementation("androidx.navigation:navigation-ui-ktx:$navigationVersion")
|
||||||
|
implementation("androidx.navigation:navigation-compose:$navigationVersion")
|
||||||
testImplementation("junit:junit:4.13.2")
|
testImplementation("junit:junit:4.13.2")
|
||||||
testImplementation("org.robolectric:robolectric:4.2.1")
|
testImplementation("org.robolectric:robolectric:4.2.1")
|
||||||
val espressoVersion = "3.5.1"
|
val espressoVersion = "3.5.1"
|
||||||
|
@ -119,18 +125,32 @@ dependencies {
|
||||||
androidTestUtil("androidx.test:orchestrator:1.4.2")
|
androidTestUtil("androidx.test:orchestrator:1.4.2")
|
||||||
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
||||||
androidTestImplementation("androidx.test.uiautomator:uiautomator:2.2.0")
|
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("com.wbrawner.plausible:plausible-android:0.1.0-SNAPSHOT")
|
||||||
implementation("androidx.appcompat:appcompat:1.6.0")
|
implementation("androidx.appcompat:appcompat:1.6.1")
|
||||||
implementation("androidx.preference:preference-ktx:1.2.0")
|
implementation("androidx.preference:preference-ktx:1.2.1")
|
||||||
implementation("androidx.fragment:fragment-ktx:1.5.5")
|
implementation("androidx.fragment:fragment-ktx:1.6.1")
|
||||||
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
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("androidx.legacy:legacy-support-v13:1.0.0")
|
||||||
implementation("com.commonsware.cwac:anddown:0.4.0")
|
implementation("com.commonsware.cwac:anddown:0.4.0")
|
||||||
implementation("com.jakewharton.timber:timber:5.0.1")
|
implementation("com.jakewharton.timber:timber:5.0.1")
|
||||||
implementation("androidx.core:core-ktx:1.9.0")
|
implementation("androidx.core:core-ktx:1.12.0")
|
||||||
implementation("androidx.browser:browser:1.4.0")
|
implementation("androidx.browser:browser:1.6.0")
|
||||||
val coroutinesVersion = "1.6.4"
|
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")
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion")
|
||||||
val lifecycleVersion = "2.2.0"
|
val lifecycleVersion = "2.2.0"
|
||||||
implementation("androidx.lifecycle:lifecycle-extensions:$lifecycleVersion")
|
implementation("androidx.lifecycle:lifecycle-extensions:$lifecycleVersion")
|
||||||
|
|
|
@ -12,12 +12,10 @@
|
||||||
android:resizeableActivity="true"
|
android:resizeableActivity="true"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/AppTheme"
|
android:theme="@style/Theme.App.Starting"
|
||||||
tools:ignore="AllowBackup"
|
tools:ignore="AllowBackup"
|
||||||
tools:targetApi="n">
|
tools:targetApi="n">
|
||||||
<activity
|
<activity android:name=".view.activity.MainActivity"
|
||||||
android:name=".view.activity.SplashActivity"
|
|
||||||
android:theme="@style/AppTheme.Splash"
|
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:label="@string/app_name_short">
|
android:label="@string/app_name_short">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
@ -40,7 +38,6 @@
|
||||||
<data android:host="*" />
|
<data android:host="*" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity android:name=".view.activity.MainActivity" android:exported="false" />
|
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="androidx.core.content.FileProvider"
|
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
|
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
|
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
|
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
|
package com.wbrawner.simplemarkdown.view.activity
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.activity.viewModels
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
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.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.navigation.findNavController
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.wbrawner.plausible.android.Plausible
|
import com.wbrawner.plausible.android.Plausible
|
||||||
import com.wbrawner.simplemarkdown.R
|
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 {
|
class MainActivity : AppCompatActivity(), ActivityCompat.OnRequestPermissionsResultCallback {
|
||||||
|
private val viewModel: MarkdownViewModel by viewModels()
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
installSplashScreen()
|
||||||
super.onCreate(savedInstanceState)
|
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 sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
val preferences = mutableMapOf<String, String>()
|
val preferences = mutableMapOf<String, String>()
|
||||||
preferences["Autosave"] = sharedPreferences.getBoolean("autosave", true).toString()
|
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()
|
getBooleanPref(R.string.pref_key_error_reports_enabled, true).toString()
|
||||||
preferences["Readability"] = getBooleanPref(R.string.readability_enabled, false).toString()
|
preferences["Readability"] = getBooleanPref(R.string.readability_enabled, false).toString()
|
||||||
Plausible.event("settings", props = preferences, url = "/")
|
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() {
|
override fun onBackPressed() {
|
||||||
|
@ -34,12 +149,27 @@ class MainActivity : AppCompatActivity(), ActivityCompat.OnRequestPermissionsRes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Context.getBooleanPref(@StringRes key: Int, defaultValue: Boolean) = PreferenceManager.getDefaultSharedPreferences(this).getBoolean(
|
enum class Route(
|
||||||
getString(key),
|
val path: String,
|
||||||
defaultValue
|
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) =
|
||||||
getString(key),
|
PreferenceManager.getDefaultSharedPreferences(this).getBoolean(
|
||||||
defaultValue
|
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
|
//package com.wbrawner.simplemarkdown.view.fragment
|
||||||
|
//
|
||||||
import android.Manifest
|
//import android.app.Activity
|
||||||
import android.app.Activity
|
//import android.content.Context
|
||||||
import android.content.Context
|
//import android.content.Intent
|
||||||
import android.content.Intent
|
//import android.content.pm.PackageManager
|
||||||
import android.content.pm.PackageManager
|
//import android.content.res.Configuration
|
||||||
import android.content.res.Configuration
|
//import android.os.Build
|
||||||
import android.os.Build
|
//import android.os.Bundle
|
||||||
import android.os.Bundle
|
//import android.view.*
|
||||||
import android.view.*
|
//import android.webkit.MimeTypeMap
|
||||||
import android.webkit.MimeTypeMap
|
//import android.widget.Toast
|
||||||
import android.widget.Toast
|
//import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AlertDialog
|
//import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
//import androidx.core.app.ActivityCompat
|
||||||
import androidx.core.app.ActivityCompat
|
//import androidx.fragment.app.Fragment
|
||||||
import androidx.core.content.ContextCompat
|
//import androidx.fragment.app.viewModels
|
||||||
import androidx.fragment.app.Fragment
|
//import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.fragment.app.viewModels
|
//import androidx.navigation.fragment.findNavController
|
||||||
import androidx.lifecycle.lifecycleScope
|
//import androidx.navigation.ui.AppBarConfiguration
|
||||||
import androidx.navigation.fragment.findNavController
|
//import androidx.navigation.ui.setupWithNavController
|
||||||
import androidx.navigation.ui.AppBarConfiguration
|
//import androidx.preference.PreferenceManager
|
||||||
import androidx.navigation.ui.setupWithNavController
|
//import com.wbrawner.plausible.android.Plausible
|
||||||
import androidx.preference.PreferenceManager
|
//import com.wbrawner.simplemarkdown.R
|
||||||
import com.wbrawner.plausible.android.Plausible
|
//import com.wbrawner.simplemarkdown.databinding.FragmentMainBinding
|
||||||
import com.wbrawner.simplemarkdown.R
|
//import com.wbrawner.simplemarkdown.utility.ErrorHandler
|
||||||
import com.wbrawner.simplemarkdown.utility.ErrorHandler
|
//import com.wbrawner.simplemarkdown.utility.errorHandlerImpl
|
||||||
import com.wbrawner.simplemarkdown.utility.errorHandlerImpl
|
//import com.wbrawner.simplemarkdown.view.adapter.EditPagerAdapter
|
||||||
import com.wbrawner.simplemarkdown.view.adapter.EditPagerAdapter
|
//import com.wbrawner.simplemarkdown.viewmodel.MarkdownViewModel
|
||||||
import com.wbrawner.simplemarkdown.viewmodel.MarkdownViewModel
|
//import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.android.synthetic.main.fragment_main.*
|
//import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.Dispatchers
|
//import kotlinx.coroutines.withContext
|
||||||
import kotlinx.coroutines.launch
|
//import timber.log.Timber
|
||||||
import kotlinx.coroutines.withContext
|
//
|
||||||
import timber.log.Timber
|
//class MainFragment : Fragment(), ActivityCompat.OnRequestPermissionsResultCallback {
|
||||||
|
//
|
||||||
class MainFragment : Fragment(), ActivityCompat.OnRequestPermissionsResultCallback {
|
// private val viewModel: MarkdownViewModel by viewModels()
|
||||||
|
// private var appBarConfiguration: AppBarConfiguration? = null
|
||||||
private val viewModel: MarkdownViewModel by viewModels()
|
// private val errorHandler: ErrorHandler by errorHandlerImpl()
|
||||||
private var appBarConfiguration: AppBarConfiguration? = null
|
// private var _binding: FragmentMainBinding? = null
|
||||||
private val errorHandler: ErrorHandler by errorHandlerImpl()
|
// private val binding: FragmentMainBinding
|
||||||
|
// get() = _binding!!
|
||||||
override fun onAttach(context: Context) {
|
//
|
||||||
super.onAttach(context)
|
// override fun onAttach(context: Context) {
|
||||||
if (context !is Activity) return
|
// super.onAttach(context)
|
||||||
lifecycleScope.launch {
|
// if (context !is Activity) return
|
||||||
viewModel.load(context, context.intent?.data)
|
// lifecycleScope.launch {
|
||||||
context.intent?.data = null
|
// viewModel.load(context, context.intent?.data)
|
||||||
}
|
// context.intent?.data = null
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
//
|
||||||
super.onCreate(savedInstanceState)
|
// override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
setHasOptionsMenu(true)
|
// super.onCreate(savedInstanceState)
|
||||||
}
|
// setHasOptionsMenu(true)
|
||||||
|
// }
|
||||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
//
|
||||||
inflater.inflate(R.menu.menu_edit, menu)
|
// override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
// inflater.inflate(R.menu.menu_edit, menu)
|
||||||
menu.findItem(R.id.action_save_as)
|
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
?.setAlphabeticShortcut('S', KeyEvent.META_CTRL_ON or KeyEvent.META_SHIFT_ON)
|
// menu.findItem(R.id.action_save_as)
|
||||||
}
|
// ?.setAlphabeticShortcut('S', KeyEvent.META_CTRL_ON or KeyEvent.META_SHIFT_ON)
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
override fun onCreateView(
|
//
|
||||||
inflater: LayoutInflater,
|
// override fun onCreateView(
|
||||||
container: ViewGroup?,
|
// inflater: LayoutInflater,
|
||||||
savedInstanceState: Bundle?
|
// container: ViewGroup?,
|
||||||
): View? =
|
// savedInstanceState: Bundle?
|
||||||
inflater.inflate(R.layout.fragment_main, container, false)
|
// ): View {
|
||||||
|
// _binding = FragmentMainBinding.inflate(inflater, container, false)
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
// return binding.root
|
||||||
with(findNavController()) {
|
// }
|
||||||
appBarConfiguration = AppBarConfiguration(graph, drawerLayout)
|
//
|
||||||
toolbar.setupWithNavController(this, appBarConfiguration!!)
|
// override fun onDestroyView() {
|
||||||
(activity as? AppCompatActivity)?.setSupportActionBar(toolbar)
|
// super.onDestroyView()
|
||||||
navigationView.setupWithNavController(this)
|
// _binding = null
|
||||||
}
|
// }
|
||||||
val adapter = EditPagerAdapter(childFragmentManager, view.context)
|
//
|
||||||
pager.adapter = adapter
|
// override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
pager.addOnPageChangeListener(adapter)
|
// with(findNavController()) {
|
||||||
pager.pageMargin = 1
|
// appBarConfiguration = AppBarConfiguration(graph, binding.drawerLayout)
|
||||||
pager.setPageMarginDrawable(R.color.colorAccent)
|
// binding.toolbar.setupWithNavController(this, appBarConfiguration!!)
|
||||||
tabLayout.setupWithViewPager(pager)
|
// (activity as? AppCompatActivity)?.setSupportActionBar(binding.toolbar)
|
||||||
if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
// binding.navigationView.setupWithNavController(this)
|
||||||
tabLayout!!.visibility = View.GONE
|
// }
|
||||||
}
|
// val adapter = EditPagerAdapter(childFragmentManager, view.context)
|
||||||
@Suppress("CAST_NEVER_SUCCEEDS")
|
// binding.pager.adapter = adapter
|
||||||
viewModel.fileName.observe(viewLifecycleOwner) {
|
// binding.pager.addOnPageChangeListener(adapter)
|
||||||
toolbar?.title = it
|
// binding.pager.pageMargin = 1
|
||||||
}
|
// binding.pager.setPageMarginDrawable(R.color.colorAccent)
|
||||||
}
|
// binding.tabLayout.setupWithViewPager(binding.pager)
|
||||||
|
// if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
// binding.tabLayout.visibility = View.GONE
|
||||||
return when (item.itemId) {
|
// }
|
||||||
android.R.id.home -> {
|
// @Suppress("CAST_NEVER_SUCCEEDS")
|
||||||
drawerLayout.open()
|
// viewModel.fileName.observe(viewLifecycleOwner) {
|
||||||
true
|
// binding.toolbar.title = it
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
R.id.action_save -> {
|
//
|
||||||
Timber.d("Save clicked")
|
// override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
lifecycleScope.launch {
|
// return when (item.itemId) {
|
||||||
if (!viewModel.save(requireContext())) {
|
// android.R.id.home -> {
|
||||||
requestFileOp(REQUEST_SAVE_FILE)
|
// binding.drawerLayout.open()
|
||||||
} else {
|
// true
|
||||||
Toast.makeText(
|
// }
|
||||||
requireContext(),
|
//
|
||||||
getString(R.string.file_saved, viewModel.fileName.value),
|
// R.id.action_save -> {
|
||||||
Toast.LENGTH_SHORT
|
// Timber.d("Save clicked")
|
||||||
).show()
|
// lifecycleScope.launch {
|
||||||
}
|
// if (!viewModel.save(requireContext())) {
|
||||||
}
|
// requestFileOp(REQUEST_SAVE_FILE)
|
||||||
true
|
// } else {
|
||||||
}
|
// Toast.makeText(
|
||||||
|
// requireContext(),
|
||||||
R.id.action_save_as -> {
|
// getString(R.string.file_saved, viewModel.fileName.value),
|
||||||
Timber.d("Save as clicked")
|
// Toast.LENGTH_SHORT
|
||||||
requestFileOp(REQUEST_SAVE_FILE)
|
// ).show()
|
||||||
true
|
// }
|
||||||
}
|
// }
|
||||||
|
// true
|
||||||
R.id.action_share -> {
|
// }
|
||||||
Timber.d("Share clicked")
|
//
|
||||||
val shareIntent = Intent(Intent.ACTION_SEND)
|
// R.id.action_save_as -> {
|
||||||
shareIntent.putExtra(Intent.EXTRA_TEXT, viewModel.markdownUpdates.value)
|
// Timber.d("Save as clicked")
|
||||||
shareIntent.type = "text/plain"
|
// requestFileOp(REQUEST_SAVE_FILE)
|
||||||
startActivity(
|
// true
|
||||||
Intent.createChooser(
|
// }
|
||||||
shareIntent,
|
//
|
||||||
getString(R.string.share_file)
|
// R.id.action_share -> {
|
||||||
)
|
// Timber.d("Share clicked")
|
||||||
)
|
// val shareIntent = Intent(Intent.ACTION_SEND)
|
||||||
true
|
// shareIntent.putExtra(Intent.EXTRA_TEXT, viewModel.markdownUpdates.value)
|
||||||
}
|
// shareIntent.type = "text/plain"
|
||||||
|
// startActivity(
|
||||||
R.id.action_load -> {
|
// Intent.createChooser(
|
||||||
Timber.d("Load clicked")
|
// shareIntent,
|
||||||
requestFileOp(REQUEST_OPEN_FILE)
|
// getString(R.string.share_file)
|
||||||
true
|
// )
|
||||||
}
|
// )
|
||||||
|
// true
|
||||||
R.id.action_new -> {
|
// }
|
||||||
Timber.d("New clicked")
|
//
|
||||||
promptSaveOrDiscardChanges()
|
// R.id.action_load -> {
|
||||||
true
|
// Timber.d("Load clicked")
|
||||||
}
|
// requestFileOp(REQUEST_OPEN_FILE)
|
||||||
|
// true
|
||||||
R.id.action_lock_swipe -> {
|
// }
|
||||||
Timber.d("Lock swiping clicked")
|
//
|
||||||
item.isChecked = !item.isChecked
|
// R.id.action_new -> {
|
||||||
pager!!.setSwipeLocked(item.isChecked)
|
// Timber.d("New clicked")
|
||||||
true
|
// promptSaveOrDiscardChanges()
|
||||||
}
|
// true
|
||||||
|
// }
|
||||||
else -> super.onOptionsItemSelected(item)
|
//
|
||||||
}
|
// R.id.action_lock_swipe -> {
|
||||||
}
|
// Timber.d("Lock swiping clicked")
|
||||||
|
// item.isChecked = !item.isChecked
|
||||||
override fun onStart() {
|
// binding.pager.setSwipeLocked(item.isChecked)
|
||||||
super.onStart()
|
// true
|
||||||
Plausible.pageView("")
|
// }
|
||||||
lifecycleScope.launch {
|
//
|
||||||
withContext(Dispatchers.IO) {
|
// else -> super.onOptionsItemSelected(item)
|
||||||
val enableErrorReports =
|
// }
|
||||||
PreferenceManager.getDefaultSharedPreferences(requireContext())
|
// }
|
||||||
.getBoolean(getString(R.string.pref_key_error_reports_enabled), true)
|
//
|
||||||
Timber.d("MainFragment started. Error reports enabled? $enableErrorReports")
|
// override fun onStart() {
|
||||||
errorHandler.enable(enableErrorReports)
|
// super.onStart()
|
||||||
}
|
// Plausible.pageView("")
|
||||||
}
|
// lifecycleScope.launch {
|
||||||
}
|
// withContext(Dispatchers.IO) {
|
||||||
|
// val enableErrorReports =
|
||||||
override fun onStop() {
|
// PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
super.onStop()
|
// .getBoolean(getString(R.string.pref_key_error_reports_enabled), true)
|
||||||
val context = context?.applicationContext ?: return
|
// Timber.d("MainFragment started. Error reports enabled? $enableErrorReports")
|
||||||
lifecycleScope.launch {
|
// errorHandler.enable(enableErrorReports)
|
||||||
viewModel.autosave(context, PreferenceManager.getDefaultSharedPreferences(context))
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
// override fun onStop() {
|
||||||
super.onConfigurationChanged(newConfig)
|
// super.onStop()
|
||||||
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
// val context = context?.applicationContext ?: return
|
||||||
Timber.d("Orientation changed to landscape, hiding tabs")
|
// lifecycleScope.launch {
|
||||||
tabLayout?.visibility = View.GONE
|
// viewModel.autosave(context, PreferenceManager.getDefaultSharedPreferences(context))
|
||||||
} else {
|
// }
|
||||||
Timber.d("Orientation changed to portrait, showing tabs")
|
// }
|
||||||
tabLayout?.visibility = View.VISIBLE
|
//
|
||||||
}
|
// override fun onConfigurationChanged(newConfig: Configuration) {
|
||||||
}
|
// super.onConfigurationChanged(newConfig)
|
||||||
|
// if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||||
override fun onRequestPermissionsResult(
|
// Timber.d("Orientation changed to landscape, hiding tabs")
|
||||||
requestCode: Int,
|
// binding.tabLayout.visibility = View.GONE
|
||||||
permissions: Array<String>,
|
// } else {
|
||||||
grantResults: IntArray
|
// Timber.d("Orientation changed to portrait, showing tabs")
|
||||||
) {
|
// binding.tabLayout.visibility = View.VISIBLE
|
||||||
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) {
|
// override fun onRequestPermissionsResult(
|
||||||
// Permission granted, open file save dialog
|
// requestCode: Int,
|
||||||
Timber.d("Storage permissions granted")
|
// permissions: Array<String>,
|
||||||
requestFileOp(requestCode)
|
// grantResults: IntArray
|
||||||
} else {
|
// ) {
|
||||||
// Permission denied, do nothing
|
// when (requestCode) {
|
||||||
Timber.d("Storage permissions denied, unable to save or load files")
|
// REQUEST_SAVE_FILE, REQUEST_OPEN_FILE -> {
|
||||||
context?.let {
|
// // If request is cancelled, the result arrays are empty.
|
||||||
Toast.makeText(it, R.string.no_permissions, Toast.LENGTH_SHORT)
|
// if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
.show()
|
// // 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 {
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
// Toast.makeText(it, R.string.no_permissions, Toast.LENGTH_SHORT)
|
||||||
when (requestCode) {
|
// .show()
|
||||||
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()
|
//
|
||||||
)
|
// override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
return
|
// when (requestCode) {
|
||||||
}
|
// REQUEST_OPEN_FILE -> {
|
||||||
|
// if (resultCode != Activity.RESULT_OK || data?.data == null) {
|
||||||
lifecycleScope.launch {
|
// Timber.w(
|
||||||
context?.let {
|
// "Unable to open file. Result ok? %b Intent uri: %s",
|
||||||
if (!viewModel.load(it, data.data)) {
|
// resultCode == Activity.RESULT_OK,
|
||||||
Toast.makeText(it, R.string.file_load_error, Toast.LENGTH_SHORT).show()
|
// data?.data?.toString()
|
||||||
}
|
// )
|
||||||
}
|
// return
|
||||||
}
|
// }
|
||||||
}
|
//
|
||||||
|
// lifecycleScope.launch {
|
||||||
REQUEST_SAVE_FILE -> {
|
// context?.let {
|
||||||
if (resultCode != Activity.RESULT_OK || data?.data == null) {
|
// if (!viewModel.load(it, data.data)) {
|
||||||
Timber.w(
|
// Toast.makeText(it, R.string.file_load_error, Toast.LENGTH_SHORT).show()
|
||||||
"Unable to save file. Result ok? %b Intent uri: %s",
|
// }
|
||||||
resultCode == Activity.RESULT_OK,
|
// }
|
||||||
data?.data?.toString()
|
// }
|
||||||
)
|
// }
|
||||||
return
|
//
|
||||||
}
|
// REQUEST_SAVE_FILE -> {
|
||||||
|
// if (resultCode != Activity.RESULT_OK || data?.data == null) {
|
||||||
lifecycleScope.launch {
|
// Timber.w(
|
||||||
context?.let {
|
// "Unable to save file. Result ok? %b Intent uri: %s",
|
||||||
viewModel.save(it, data.data)
|
// resultCode == Activity.RESULT_OK,
|
||||||
}
|
// data?.data?.toString()
|
||||||
}
|
// )
|
||||||
}
|
// return
|
||||||
}
|
// }
|
||||||
super.onActivityResult(requestCode, resultCode, data)
|
//
|
||||||
}
|
// lifecycleScope.launch {
|
||||||
|
// context?.let {
|
||||||
private fun promptSaveOrDiscardChanges() {
|
// viewModel.save(it, data.data)
|
||||||
if (!viewModel.shouldPromptSave()) {
|
// }
|
||||||
viewModel.reset(
|
// }
|
||||||
"Untitled.md",
|
// }
|
||||||
PreferenceManager.getDefaultSharedPreferences(requireContext())
|
// }
|
||||||
)
|
// super.onActivityResult(requestCode, resultCode, data)
|
||||||
return
|
// }
|
||||||
}
|
//
|
||||||
val context = context ?: run {
|
// private fun promptSaveOrDiscardChanges() {
|
||||||
Timber.w("Context is null, unable to show prompt for save or discard")
|
// if (!viewModel.shouldPromptSave()) {
|
||||||
return
|
// viewModel.reset(
|
||||||
}
|
// "Untitled.md",
|
||||||
AlertDialog.Builder(context)
|
// PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
.setTitle(R.string.save_changes)
|
// )
|
||||||
.setMessage(R.string.prompt_save_changes)
|
// return
|
||||||
.setNegativeButton(R.string.action_discard) { _, _ ->
|
// }
|
||||||
Timber.d("Discarding changes")
|
// val context = context ?: run {
|
||||||
viewModel.reset(
|
// Timber.w("Context is null, unable to show prompt for save or discard")
|
||||||
"Untitled.md",
|
// return
|
||||||
PreferenceManager.getDefaultSharedPreferences(requireContext())
|
// }
|
||||||
)
|
// AlertDialog.Builder(context)
|
||||||
}
|
// .setTitle(R.string.save_changes)
|
||||||
.setPositiveButton(R.string.action_save) { _, _ ->
|
// .setMessage(R.string.prompt_save_changes)
|
||||||
Timber.d("Saving changes")
|
// .setNegativeButton(R.string.action_discard) { _, _ ->
|
||||||
requestFileOp(REQUEST_SAVE_FILE)
|
// Timber.d("Discarding changes")
|
||||||
}
|
// viewModel.reset(
|
||||||
.create()
|
// "Untitled.md",
|
||||||
.show()
|
// PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
}
|
// )
|
||||||
|
// }
|
||||||
private fun requestFileOp(requestType: Int) {
|
// .setPositiveButton(R.string.action_save) { _, _ ->
|
||||||
val intent = when (requestType) {
|
// Timber.d("Saving changes")
|
||||||
REQUEST_SAVE_FILE -> {
|
// requestFileOp(REQUEST_SAVE_FILE)
|
||||||
Timber.d("Requesting save op")
|
// }
|
||||||
Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
// .create()
|
||||||
type = "text/markdown"
|
// .show()
|
||||||
putExtra(Intent.EXTRA_TITLE, viewModel.fileName.value)
|
// }
|
||||||
}
|
//
|
||||||
}
|
// private fun requestFileOp(requestType: Int) {
|
||||||
|
// val intent = when (requestType) {
|
||||||
REQUEST_OPEN_FILE -> {
|
// REQUEST_SAVE_FILE -> {
|
||||||
Timber.d("Requesting open op")
|
// Timber.d("Requesting save op")
|
||||||
Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
|
// Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
||||||
type = "*/*"
|
// type = "text/markdown"
|
||||||
if (MimeTypeMap.getSingleton().hasMimeType("md")) {
|
// putExtra(Intent.EXTRA_TITLE, viewModel.fileName.value)
|
||||||
// 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"))
|
//
|
||||||
}
|
// REQUEST_OPEN_FILE -> {
|
||||||
}
|
// Timber.d("Requesting open op")
|
||||||
}
|
// Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
|
||||||
|
// type = "*/*"
|
||||||
else -> {
|
// if (MimeTypeMap.getSingleton().hasMimeType("md")) {
|
||||||
Timber.w("Ignoring unknown file op request: $requestType")
|
// // If the device doesn't recognize markdown files then we're not going to be
|
||||||
null
|
// // 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"))
|
||||||
} ?: return
|
// }
|
||||||
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
// }
|
||||||
startActivityForResult(
|
// }
|
||||||
intent,
|
//
|
||||||
requestType
|
// else -> {
|
||||||
)
|
// Timber.w("Ignoring unknown file op request: $requestType")
|
||||||
}
|
// null
|
||||||
|
// }
|
||||||
companion object {
|
// } ?: return
|
||||||
// Request codes
|
// intent.addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
const val REQUEST_OPEN_FILE = 1
|
// startActivityForResult(
|
||||||
const val REQUEST_SAVE_FILE = 2
|
// intent,
|
||||||
const val KEY_AUTOSAVE = "autosave"
|
// 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
|
//package com.wbrawner.simplemarkdown.view.fragment
|
||||||
|
//
|
||||||
import android.content.res.Configuration
|
//import android.content.res.Configuration
|
||||||
import android.os.Bundle
|
//import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
//import android.view.LayoutInflater
|
||||||
import android.view.MenuItem
|
//import android.view.MenuItem
|
||||||
import android.view.View
|
//import android.view.View
|
||||||
import android.view.ViewGroup
|
//import android.view.ViewGroup
|
||||||
import android.widget.Toast
|
//import android.widget.Toast
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
//import androidx.appcompat.app.AppCompatDelegate
|
||||||
import androidx.fragment.app.Fragment
|
//import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.lifecycleScope
|
//import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.navigation.fragment.findNavController
|
//import androidx.navigation.fragment.findNavController
|
||||||
import androidx.navigation.ui.setupWithNavController
|
//import androidx.navigation.ui.setupWithNavController
|
||||||
import com.wbrawner.plausible.android.Plausible
|
//import com.wbrawner.plausible.android.Plausible
|
||||||
import com.wbrawner.simplemarkdown.R
|
//import com.wbrawner.simplemarkdown.R
|
||||||
import com.wbrawner.simplemarkdown.utility.*
|
//import com.wbrawner.simplemarkdown.databinding.FragmentMarkdownInfoBinding
|
||||||
import kotlinx.android.synthetic.main.fragment_markdown_info.*
|
//import com.wbrawner.simplemarkdown.utility.*
|
||||||
import kotlinx.coroutines.launch
|
//import kotlinx.coroutines.launch
|
||||||
|
//
|
||||||
class MarkdownInfoFragment : Fragment() {
|
//class MarkdownInfoFragment : Fragment() {
|
||||||
private val errorHandler: ErrorHandler by errorHandlerImpl()
|
// private val errorHandler: ErrorHandler by errorHandlerImpl()
|
||||||
|
// private var _binding: FragmentMarkdownInfoBinding? = null
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
// private val binding: FragmentMarkdownInfoBinding
|
||||||
super.onCreate(savedInstanceState)
|
// get() = _binding!!
|
||||||
setHasOptionsMenu(true)
|
//
|
||||||
}
|
// override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
// super.onCreate(savedInstanceState)
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
|
// setHasOptionsMenu(true)
|
||||||
inflater.inflate(R.layout.fragment_markdown_info, container, false)
|
// }
|
||||||
|
//
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
// override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||||
val fileName = arguments?.getString(EXTRA_FILE)
|
// _binding = FragmentMarkdownInfoBinding.inflate(inflater, container, false)
|
||||||
if (fileName.isNullOrBlank()) {
|
// return binding.root
|
||||||
findNavController().navigateUp()
|
// }
|
||||||
return
|
//
|
||||||
}
|
// override fun onDestroyView() {
|
||||||
toolbar.setupWithNavController(findNavController())
|
// super.onDestroyView()
|
||||||
|
// _binding = null
|
||||||
val isNightMode = AppCompatDelegate.getDefaultNightMode() ==
|
// }
|
||||||
AppCompatDelegate.MODE_NIGHT_YES
|
//
|
||||||
|| resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
|
// override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
val defaultCssId = if (isNightMode) {
|
// val fileName = arguments?.getString(EXTRA_FILE)
|
||||||
R.string.pref_custom_css_default_dark
|
// if (fileName.isNullOrBlank()) {
|
||||||
} else {
|
// findNavController().navigateUp()
|
||||||
R.string.pref_custom_css_default
|
// return
|
||||||
}
|
// }
|
||||||
val css: String? = getString(defaultCssId)
|
// binding.toolbar.setupWithNavController(findNavController())
|
||||||
lifecycleScope.launch {
|
//
|
||||||
try {
|
// val isNightMode = AppCompatDelegate.getDefaultNightMode() ==
|
||||||
val html = view.context.assets?.readAssetToString(fileName)
|
// AppCompatDelegate.MODE_NIGHT_YES
|
||||||
?.toHtml()
|
// || resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
|
||||||
?: throw RuntimeException("Unable to open stream to $fileName")
|
// val defaultCssId = if (isNightMode) {
|
||||||
infoWebview.loadDataWithBaseURL(null,
|
// R.string.pref_custom_css_default_dark
|
||||||
String.format(FORMAT_CSS, css) + html,
|
// } else {
|
||||||
"text/html",
|
// R.string.pref_custom_css_default
|
||||||
"UTF-8", null
|
// }
|
||||||
)
|
// val css: String? = getString(defaultCssId)
|
||||||
} catch (e: Exception) {
|
// lifecycleScope.launch {
|
||||||
errorHandler.reportException(e)
|
// try {
|
||||||
Toast.makeText(view.context, R.string.file_load_error, Toast.LENGTH_SHORT).show()
|
// val html = view.context.assets?.readAssetToString(fileName)
|
||||||
findNavController().navigateUp()
|
// ?.toHtml()
|
||||||
}
|
// ?: throw RuntimeException("Unable to open stream to $fileName")
|
||||||
}
|
// binding.infoWebview.loadDataWithBaseURL(null,
|
||||||
}
|
// String.format(FORMAT_CSS, css) + html,
|
||||||
|
// "text/html",
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
// "UTF-8", null
|
||||||
if (item.itemId == android.R.id.home) {
|
// )
|
||||||
findNavController().navigateUp()
|
// } catch (e: Exception) {
|
||||||
return true
|
// errorHandler.reportException(e)
|
||||||
}
|
// Toast.makeText(view.context, R.string.file_load_error, Toast.LENGTH_SHORT).show()
|
||||||
return super.onOptionsItemSelected(item)
|
// findNavController().navigateUp()
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
override fun onStart() {
|
// }
|
||||||
super.onStart()
|
//
|
||||||
arguments?.getString(EXTRA_FILE)?.let {
|
// override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
Plausible.pageView(it)
|
// if (item.itemId == android.R.id.home) {
|
||||||
}
|
// findNavController().navigateUp()
|
||||||
}
|
// return true
|
||||||
|
// }
|
||||||
companion object {
|
// return super.onOptionsItemSelected(item)
|
||||||
const val FORMAT_CSS = "<style>" +
|
// }
|
||||||
"%s" +
|
//
|
||||||
"</style>"
|
// override fun onStart() {
|
||||||
const val EXTRA_TITLE = "title"
|
// super.onStart()
|
||||||
const val EXTRA_FILE = "file"
|
// 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) {
|
override fun onAttach(context: Context) {
|
||||||
super.onAttach(context)
|
super.onAttach(context)
|
||||||
updateWebContent(viewModel.markdownUpdates.value ?: "")
|
updateWebContent(viewModel.markdownUpdates.value ?: "")
|
||||||
viewModel.markdownUpdates.observe(this, {
|
// viewModel.markdownUpdates.observe(this, {
|
||||||
updateWebContent(it)
|
// updateWebContent(it)
|
||||||
})
|
// })
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateWebContent(markdown: String) {
|
private fun updateWebContent(markdown: String) {
|
||||||
|
|
|
@ -1,34 +1,45 @@
|
||||||
package com.wbrawner.simplemarkdown.view.fragment
|
//package com.wbrawner.simplemarkdown.view.fragment
|
||||||
|
//
|
||||||
import android.os.Bundle
|
//import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
//import android.view.LayoutInflater
|
||||||
import android.view.MenuItem
|
//import android.view.MenuItem
|
||||||
import android.view.View
|
//import android.view.View
|
||||||
import android.view.ViewGroup
|
//import android.view.ViewGroup
|
||||||
import androidx.fragment.app.Fragment
|
//import androidx.fragment.app.Fragment
|
||||||
import androidx.navigation.fragment.findNavController
|
//import androidx.navigation.fragment.findNavController
|
||||||
import androidx.navigation.ui.setupWithNavController
|
//import androidx.navigation.ui.setupWithNavController
|
||||||
import com.wbrawner.plausible.android.Plausible
|
//import com.wbrawner.plausible.android.Plausible
|
||||||
import com.wbrawner.simplemarkdown.R
|
//import com.wbrawner.simplemarkdown.R
|
||||||
import kotlinx.android.synthetic.main.fragment_settings.*
|
//import com.wbrawner.simplemarkdown.databinding.FragmentSettingsBinding
|
||||||
|
//
|
||||||
class SettingsContainerFragment : Fragment() {
|
//class SettingsContainerFragment : Fragment() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
// private var _binding: FragmentSettingsBinding? = null
|
||||||
super.onCreate(savedInstanceState)
|
// private val binding: FragmentSettingsBinding
|
||||||
setHasOptionsMenu(true)
|
// get() = _binding!!
|
||||||
}
|
//
|
||||||
|
// override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
|
// super.onCreate(savedInstanceState)
|
||||||
inflater.inflate(R.layout.fragment_settings, container, false)
|
// setHasOptionsMenu(true)
|
||||||
|
// }
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
//
|
||||||
toolbar.setupWithNavController(findNavController())
|
// override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||||
}
|
// _binding = FragmentSettingsBinding.inflate(inflater, container, false)
|
||||||
|
// return binding.root
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean = findNavController().navigateUp()
|
// }
|
||||||
|
//
|
||||||
override fun onStart() {
|
// override fun onDestroyView() {
|
||||||
super.onStart()
|
// super.onDestroyView()
|
||||||
Plausible.pageView("Settings")
|
// _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
|
//package com.wbrawner.simplemarkdown.view.fragment
|
||||||
|
//
|
||||||
import android.content.ActivityNotFoundException
|
//import android.content.ActivityNotFoundException
|
||||||
import android.content.Intent
|
//import android.content.Intent
|
||||||
import android.net.Uri
|
//import android.net.Uri
|
||||||
import android.os.Bundle
|
//import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
//import android.view.LayoutInflater
|
||||||
import android.view.View
|
//import android.view.View
|
||||||
import android.view.ViewGroup
|
//import android.view.ViewGroup
|
||||||
import androidx.browser.customtabs.CustomTabsIntent
|
//import androidx.browser.customtabs.CustomTabsIntent
|
||||||
import androidx.fragment.app.Fragment
|
//import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.Observer
|
//import androidx.lifecycle.Observer
|
||||||
import androidx.navigation.fragment.findNavController
|
//import androidx.navigation.fragment.findNavController
|
||||||
import androidx.navigation.ui.setupWithNavController
|
//import androidx.navigation.ui.setupWithNavController
|
||||||
import com.wbrawner.plausible.android.Plausible
|
//import com.wbrawner.plausible.android.Plausible
|
||||||
import com.wbrawner.simplemarkdown.R
|
//import com.wbrawner.simplemarkdown.databinding.FragmentSupportBinding
|
||||||
import com.wbrawner.simplemarkdown.utility.SupportLinkProvider
|
//import com.wbrawner.simplemarkdown.utility.SupportLinkProvider
|
||||||
import kotlinx.android.synthetic.main.fragment_support.*
|
//
|
||||||
|
//class SupportFragment : Fragment() {
|
||||||
class SupportFragment : Fragment() {
|
// private var _binding: FragmentSupportBinding? = null
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
|
// private val binding: FragmentSupportBinding
|
||||||
inflater.inflate(R.layout.fragment_support, container, false)
|
// get() = _binding!!
|
||||||
|
//
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
// override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||||
toolbar.setupWithNavController(findNavController())
|
// _binding = FragmentSupportBinding.inflate(inflater, container, false)
|
||||||
githubButton.setOnClickListener {
|
// return binding.root
|
||||||
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)
|
|
||||||
// }
|
// }
|
||||||
}
|
//
|
||||||
|
// 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.core.content.edit
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.wbrawner.simplemarkdown.utility.getName
|
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.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
@ -22,15 +25,19 @@ import java.util.concurrent.atomic.AtomicBoolean
|
||||||
const val PREF_KEY_AUTOSAVE_URI = "autosave.uri"
|
const val PREF_KEY_AUTOSAVE_URI = "autosave.uri"
|
||||||
|
|
||||||
class MarkdownViewModel(val timber: Timber.Tree = Timber.asTree()) : ViewModel() {
|
class MarkdownViewModel(val timber: Timber.Tree = Timber.asTree()) : ViewModel() {
|
||||||
val fileName = MutableLiveData<String?>("Untitled.md")
|
val fileName = MutableStateFlow("Untitled.md")
|
||||||
val markdownUpdates = MutableLiveData<String>()
|
val markdownUpdates = MutableStateFlow("")
|
||||||
val editorActions = MutableLiveData<EditorAction>()
|
val editorActions = MutableLiveData<EditorAction>()
|
||||||
val uri = MutableLiveData<Uri?>()
|
val uri = MutableLiveData<Uri?>()
|
||||||
private val isDirty = AtomicBoolean(false)
|
private val isDirty = AtomicBoolean(false)
|
||||||
private val saveMutex = Mutex()
|
private val saveMutex = Mutex()
|
||||||
|
|
||||||
fun updateMarkdown(markdown: String?) {
|
init {
|
||||||
this.markdownUpdates.postValue(markdown ?: "")
|
markdownUpdates
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateMarkdown(markdown: String?) = viewModelScope.launch {
|
||||||
|
markdownUpdates.emit(markdown ?: "")
|
||||||
isDirty.set(true)
|
isDirty.set(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,8 +66,8 @@ class MarkdownViewModel(val timber: Timber.Tree = Timber.asTree()) : ViewModel()
|
||||||
return@withContext false
|
return@withContext false
|
||||||
}
|
}
|
||||||
editorActions.postValue(EditorAction.Load(content))
|
editorActions.postValue(EditorAction.Load(content))
|
||||||
markdownUpdates.postValue(content)
|
markdownUpdates.emit(content)
|
||||||
this@MarkdownViewModel.fileName.postValue(fileName)
|
this@MarkdownViewModel.fileName.emit(fileName)
|
||||||
this@MarkdownViewModel.uri.postValue(uri)
|
this@MarkdownViewModel.uri.postValue(uri)
|
||||||
timber.i("Loaded file $fileName from $fileInput")
|
timber.i("Loaded file $fileName from $fileInput")
|
||||||
timber.v("File contents:\n$content")
|
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")
|
timber.w("Open output stream returned null for uri: $uri")
|
||||||
return@withContext false
|
return@withContext false
|
||||||
}
|
}
|
||||||
this@MarkdownViewModel.fileName.postValue(fileName)
|
this@MarkdownViewModel.fileName.emit(fileName)
|
||||||
this@MarkdownViewModel.uri.postValue(uri)
|
this@MarkdownViewModel.uri.postValue(uri)
|
||||||
isDirty.set(false)
|
isDirty.set(false)
|
||||||
timber.i("Saved file $fileName to uri $uri")
|
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")
|
timber.i("Ignoring autosave since manual save is already in progress")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val isAutoSaveEnabled = sharedPrefs.getBoolean(MainFragment.KEY_AUTOSAVE, true)
|
val isAutoSaveEnabled = sharedPrefs.getBoolean(KEY_AUTOSAVE, true)
|
||||||
timber.d("Autosave called. isEnabled? $isAutoSaveEnabled")
|
timber.d("Autosave called. isEnabled? $isAutoSaveEnabled")
|
||||||
if (!isDirty.get() || !isAutoSaveEnabled) {
|
if (!isDirty.get() || !isAutoSaveEnabled) {
|
||||||
timber.i("Ignoring call to autosave. Contents haven't changed or autosave not enabled")
|
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")
|
timber.i("Resetting view model to default state")
|
||||||
fileName.postValue(untitledFileName)
|
fileName.tryEmit(untitledFileName)
|
||||||
uri.postValue(null)
|
uri.postValue(null)
|
||||||
markdownUpdates.postValue("")
|
markdownUpdates.emit("")
|
||||||
editorActions.postValue(EditorAction.Load(""))
|
editorActions.postValue(EditorAction.Load(""))
|
||||||
isDirty.set(false)
|
isDirty.set(false)
|
||||||
timber.i("Removing autosave uri from shared prefs")
|
timber.i("Removing autosave uri from shared prefs")
|
||||||
|
|
|
@ -1,11 +1,16 @@
|
||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
|
<style name="Theme.App" parent="Theme.MaterialComponents.DayNight.NoActionBar">
|
||||||
<!-- Customize your theme here. -->
|
<!-- Customize your theme here. -->
|
||||||
<item name="colorPrimary">@color/colorPrimary</item>
|
<item name="colorPrimary">@color/colorPrimary</item>
|
||||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||||
<item name="colorAccent">@color/colorAccent</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:navigationBarColor">@color/colorBackground</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>
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -42,7 +42,7 @@
|
||||||
<string name="pref_title_readability">Enable readability highlighting (experimental)</string>
|
<string name="pref_title_readability">Enable readability highlighting (experimental)</string>
|
||||||
<string name="pref_readability_off">Readability highlighting is off</string>
|
<string name="pref_readability_off">Readability highlighting is off</string>
|
||||||
<string name="pref_readability_on">Readability highlighting is on</string>
|
<string name="pref_readability_on">Readability highlighting is on</string>
|
||||||
<string name="pref_autosave_on">Files will 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_autosave_off">Files will not be automatically saved</string>
|
||||||
<string name="pref_custom_css">pref.custom_css</string>
|
<string name="pref_custom_css">pref.custom_css</string>
|
||||||
<string name="pref_title_custom_css">Custom CSS</string>
|
<string name="pref_title_custom_css">Custom CSS</string>
|
||||||
|
|
|
@ -1,20 +1,19 @@
|
||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<!-- Base application theme. -->
|
<!-- Base application theme. -->
|
||||||
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
|
<style name="Theme.App" parent="Theme.MaterialComponents.DayNight.NoActionBar">
|
||||||
<!-- Customize your theme here. -->
|
<!-- Customize your theme here. -->
|
||||||
<item name="colorPrimary">@color/colorPrimary</item>
|
<item name="colorPrimary">@color/colorPrimary</item>
|
||||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||||
<item name="colorAccent">@color/colorAccent</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:windowLightStatusBar">true</item>
|
||||||
<item name="android:navigationBarColor">#FF000000</item>
|
<item name="android:navigationBarColor">@android:color/transparent</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="AppTheme.Splash" parent="AppTheme">
|
<style name="Theme.App.Starting" parent="Theme.SplashScreen">
|
||||||
<item name="android:windowLightStatusBar">false</item>
|
<item name="windowSplashScreenBackground">@color/colorPrimary</item>
|
||||||
<item name="android:statusBarColor">@color/colorPrimary</item>
|
<item name="windowSplashScreenAnimatedIcon">@drawable/splash_fg</item>
|
||||||
<item name="android:navigationBarColor">@color/colorPrimary</item>
|
<item name="postSplashScreenTheme">@style/Theme.App</item>
|
||||||
<item name="android:windowBackground">@drawable/splash_bg</item>
|
|
||||||
</style>
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Reference in a new issue