Improve widescreen layout for large devices
This commit is contained in:
parent
6acacf5edb
commit
a48f243e48
3 changed files with 139 additions and 81 deletions
|
@ -110,6 +110,7 @@ play {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation("androidx.compose.material3:material3-window-size-class-android:1.2.0")
|
||||||
val navigationVersion = "2.7.2"
|
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")
|
||||||
|
|
|
@ -20,6 +20,9 @@ import androidx.compose.material.icons.filled.Help
|
||||||
import androidx.compose.material.icons.filled.Info
|
import androidx.compose.material.icons.filled.Info
|
||||||
import androidx.compose.material.icons.filled.PrivacyTip
|
import androidx.compose.material.icons.filled.PrivacyTip
|
||||||
import androidx.compose.material.icons.filled.Settings
|
import androidx.compose.material.icons.filled.Settings
|
||||||
|
import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
|
||||||
|
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
|
||||||
|
import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
@ -48,6 +51,7 @@ class MainActivity : AppCompatActivity(), ActivityCompat.OnRequestPermissionsRes
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
installSplashScreen()
|
installSplashScreen()
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
@ -90,6 +94,7 @@ class MainActivity : AppCompatActivity(), ActivityCompat.OnRequestPermissionsRes
|
||||||
}
|
}
|
||||||
AppCompatDelegate.setDefaultNightMode(darkMode)
|
AppCompatDelegate.setDefaultNightMode(darkMode)
|
||||||
}
|
}
|
||||||
|
val windowSizeClass = calculateWindowSizeClass(this)
|
||||||
SimpleMarkdownTheme {
|
SimpleMarkdownTheme {
|
||||||
val navController = rememberNavController()
|
val navController = rememberNavController()
|
||||||
NavHost(
|
NavHost(
|
||||||
|
@ -121,6 +126,7 @@ class MainActivity : AppCompatActivity(), ActivityCompat.OnRequestPermissionsRes
|
||||||
MainScreen(
|
MainScreen(
|
||||||
navController = navController,
|
navController = navController,
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
|
enableWideLayout = windowSizeClass.widthSizeClass == WindowWidthSizeClass.Expanded,
|
||||||
enableAutosave = autosaveEnabled,
|
enableAutosave = autosaveEnabled,
|
||||||
enableReadability = readabilityEnabled,
|
enableReadability = readabilityEnabled,
|
||||||
darkMode = darkModePreference
|
darkMode = darkModePreference
|
||||||
|
|
|
@ -4,15 +4,19 @@ import android.content.Intent
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.RowScope
|
import androidx.compose.foundation.layout.RowScope
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.imePadding
|
import androidx.compose.foundation.layout.imePadding
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.pager.HorizontalPager
|
import androidx.compose.foundation.pager.HorizontalPager
|
||||||
import androidx.compose.foundation.pager.rememberPagerState
|
import androidx.compose.foundation.pager.rememberPagerState
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
@ -43,6 +47,9 @@ import androidx.compose.material3.TextField
|
||||||
import androidx.compose.material3.TextFieldDefaults
|
import androidx.compose.material3.TextFieldDefaults
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.material3.rememberDrawerState
|
import androidx.compose.material3.rememberDrawerState
|
||||||
|
import androidx.compose.material3.windowsizeclass.WindowSizeClass
|
||||||
|
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
|
||||||
|
import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
|
@ -72,16 +79,17 @@ import com.wbrawner.simplemarkdown.MarkdownViewModel
|
||||||
import com.wbrawner.simplemarkdown.R
|
import com.wbrawner.simplemarkdown.R
|
||||||
import com.wbrawner.simplemarkdown.Route
|
import com.wbrawner.simplemarkdown.Route
|
||||||
import com.wbrawner.simplemarkdown.model.Readability
|
import com.wbrawner.simplemarkdown.model.Readability
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MainScreen(
|
fun MainScreen(
|
||||||
navController: NavController,
|
navController: NavController,
|
||||||
viewModel: MarkdownViewModel,
|
viewModel: MarkdownViewModel,
|
||||||
|
enableWideLayout: Boolean,
|
||||||
enableAutosave: Boolean,
|
enableAutosave: Boolean,
|
||||||
enableReadability: Boolean,
|
enableReadability: Boolean,
|
||||||
darkMode: String,
|
darkMode: String,
|
||||||
|
@ -204,6 +212,7 @@ fun MainScreen(
|
||||||
menuExpanded = false
|
menuExpanded = false
|
||||||
saveFileLauncher.launch(fileName)
|
saveFileLauncher.launch(fileName)
|
||||||
})
|
})
|
||||||
|
if (!enableWideLayout) {
|
||||||
DropdownMenuItem(text = {
|
DropdownMenuItem(text = {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
Text("Lock Swiping")
|
Text("Lock Swiping")
|
||||||
|
@ -217,14 +226,61 @@ fun MainScreen(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}) { paddingValues ->
|
}) { paddingValues ->
|
||||||
val pagerState = rememberPagerState { 2 }
|
val markdown by viewModel.markdown.collectAsState()
|
||||||
|
val (textFieldValue, setTextFieldValue) = remember(clearText) {
|
||||||
|
val annotatedMarkdown = if (enableReadability) {
|
||||||
|
markdown.annotateReadability()
|
||||||
|
} else {
|
||||||
|
AnnotatedString(markdown)
|
||||||
|
}
|
||||||
|
mutableStateOf(TextFieldValue(annotatedMarkdown))
|
||||||
|
}
|
||||||
|
val setTextFieldAndViewModelValues: (TextFieldValue) -> Unit = {
|
||||||
|
setTextFieldValue(it)
|
||||||
|
coroutineScope.launch {
|
||||||
|
viewModel.updateMarkdown(it.text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (enableWideLayout) {
|
||||||
|
Row(modifier = Modifier.fillMaxSize().padding(paddingValues)) {
|
||||||
|
MarkdownTextField(modifier = Modifier.fillMaxHeight().weight(1f), textFieldValue, setTextFieldAndViewModelValues)
|
||||||
|
Spacer(modifier = Modifier.fillMaxHeight().width(1.dp).background(color = MaterialTheme.colorScheme.primary))
|
||||||
|
MarkdownPreview(modifier = Modifier.fillMaxHeight().weight(1f), markdown, darkMode)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(paddingValues)
|
.padding(paddingValues)
|
||||||
) {
|
) {
|
||||||
|
TabbedMarkdownEditor(
|
||||||
|
coroutineScope,
|
||||||
|
lockSwiping,
|
||||||
|
textFieldValue,
|
||||||
|
setTextFieldAndViewModelValues,
|
||||||
|
markdown,
|
||||||
|
darkMode
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
|
private fun TabbedMarkdownEditor(
|
||||||
|
coroutineScope: CoroutineScope,
|
||||||
|
lockSwiping: Boolean,
|
||||||
|
textFieldValue: TextFieldValue,
|
||||||
|
setTextFieldAndViewModelValues: (TextFieldValue) -> Unit,
|
||||||
|
markdown: String,
|
||||||
|
darkMode: String
|
||||||
|
) {
|
||||||
|
val pagerState = rememberPagerState { 2 }
|
||||||
TabRow(selectedTabIndex = pagerState.currentPage) {
|
TabRow(selectedTabIndex = pagerState.currentPage) {
|
||||||
Tab(text = { Text("Edit") },
|
Tab(text = { Text("Edit") },
|
||||||
selected = pagerState.currentPage == 0,
|
selected = pagerState.currentPage == 0,
|
||||||
|
@ -234,10 +290,9 @@ fun MainScreen(
|
||||||
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(1) } })
|
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(1) } })
|
||||||
}
|
}
|
||||||
HorizontalPager(
|
HorizontalPager(
|
||||||
modifier = Modifier.weight(1f), state = pagerState,
|
modifier = Modifier.fillMaxSize(1f), state = pagerState,
|
||||||
userScrollEnabled = !lockSwiping
|
userScrollEnabled = !lockSwiping
|
||||||
) { page ->
|
) { page ->
|
||||||
val markdown by viewModel.markdown.collectAsState()
|
|
||||||
val keyboardController = LocalSoftwareKeyboardController.current
|
val keyboardController = LocalSoftwareKeyboardController.current
|
||||||
LaunchedEffect(page) {
|
LaunchedEffect(page) {
|
||||||
when (page) {
|
when (page) {
|
||||||
|
@ -245,55 +300,13 @@ fun MainScreen(
|
||||||
else -> keyboardController?.hide()
|
else -> keyboardController?.hide()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var textFieldValue by remember(clearText) {
|
|
||||||
val annotatedMarkdown = if (enableReadability) {
|
|
||||||
markdown.annotateReadability()
|
|
||||||
} else {
|
|
||||||
AnnotatedString(markdown)
|
|
||||||
}
|
|
||||||
mutableStateOf(TextFieldValue(annotatedMarkdown))
|
|
||||||
}
|
|
||||||
if (page == 0) {
|
if (page == 0) {
|
||||||
TextField(
|
MarkdownTextField(modifier = Modifier.fillMaxSize(), textFieldValue, setTextFieldAndViewModelValues)
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.imePadding(),
|
|
||||||
colors = TextFieldDefaults.colors(
|
|
||||||
focusedContainerColor = MaterialTheme.colorScheme.surface,
|
|
||||||
unfocusedContainerColor = MaterialTheme.colorScheme.surface,
|
|
||||||
disabledIndicatorColor = Color.Transparent,
|
|
||||||
errorIndicatorColor = Color.Transparent,
|
|
||||||
focusedIndicatorColor = Color.Transparent,
|
|
||||||
unfocusedIndicatorColor = Color.Transparent
|
|
||||||
),
|
|
||||||
value = textFieldValue,
|
|
||||||
onValueChange = {
|
|
||||||
textFieldValue = if (enableReadability) {
|
|
||||||
it.copy(annotatedString = it.text.annotateReadability())
|
|
||||||
} else {
|
|
||||||
it
|
|
||||||
}
|
|
||||||
coroutineScope.launch {
|
|
||||||
viewModel.updateMarkdown(it.text)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
placeholder = {
|
|
||||||
Text("Markdown here…")
|
|
||||||
},
|
|
||||||
textStyle = TextStyle.Default.copy(
|
|
||||||
fontFamily = FontFamily.Monospace,
|
|
||||||
color = MaterialTheme.colorScheme.onSurface
|
|
||||||
),
|
|
||||||
keyboardOptions = KeyboardOptions(capitalization = KeyboardCapitalization.Sentences)
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
MarkdownPreview(modifier = Modifier.fillMaxSize(), markdown, darkMode)
|
MarkdownPreview(modifier = Modifier.fillMaxSize(), markdown, darkMode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun String.annotateReadability(): AnnotatedString {
|
private fun String.annotateReadability(): AnnotatedString {
|
||||||
val readability = Readability(this)
|
val readability = Readability(this)
|
||||||
|
@ -389,3 +402,41 @@ fun MarkdownTopAppBar(
|
||||||
}
|
}
|
||||||
}, actions = actions ?: {})
|
}, actions = actions ?: {})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MarkdownTextField(
|
||||||
|
modifier: Modifier,
|
||||||
|
textFieldValue: TextFieldValue,
|
||||||
|
setTextFieldValue: (TextFieldValue) -> Unit,
|
||||||
|
enableReadability: Boolean = false
|
||||||
|
) {
|
||||||
|
TextField(
|
||||||
|
modifier = modifier.imePadding(),
|
||||||
|
colors = TextFieldDefaults.colors(
|
||||||
|
focusedContainerColor = MaterialTheme.colorScheme.surface,
|
||||||
|
unfocusedContainerColor = MaterialTheme.colorScheme.surface,
|
||||||
|
disabledIndicatorColor = Color.Transparent,
|
||||||
|
errorIndicatorColor = Color.Transparent,
|
||||||
|
focusedIndicatorColor = Color.Transparent,
|
||||||
|
unfocusedIndicatorColor = Color.Transparent
|
||||||
|
),
|
||||||
|
value = textFieldValue,
|
||||||
|
onValueChange = {
|
||||||
|
setTextFieldValue(
|
||||||
|
if (enableReadability) {
|
||||||
|
it.copy(annotatedString = it.text.annotateReadability())
|
||||||
|
} else {
|
||||||
|
it
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
placeholder = {
|
||||||
|
Text("Markdown here…")
|
||||||
|
},
|
||||||
|
textStyle = TextStyle.Default.copy(
|
||||||
|
fontFamily = FontFamily.Monospace,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
|
),
|
||||||
|
keyboardOptions = KeyboardOptions(capitalization = KeyboardCapitalization.Sentences)
|
||||||
|
)
|
||||||
|
}
|
Loading…
Reference in a new issue