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 {
|
||||
implementation("androidx.compose.material3:material3-window-size-class-android:1.2.0")
|
||||
val navigationVersion = "2.7.2"
|
||||
implementation("androidx.navigation:navigation-fragment-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.PrivacyTip
|
||||
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.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
|
@ -48,6 +51,7 @@ class MainActivity : AppCompatActivity(), ActivityCompat.OnRequestPermissionsRes
|
|||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
installSplashScreen()
|
||||
super.onCreate(savedInstanceState)
|
||||
|
@ -90,6 +94,7 @@ class MainActivity : AppCompatActivity(), ActivityCompat.OnRequestPermissionsRes
|
|||
}
|
||||
AppCompatDelegate.setDefaultNightMode(darkMode)
|
||||
}
|
||||
val windowSizeClass = calculateWindowSizeClass(this)
|
||||
SimpleMarkdownTheme {
|
||||
val navController = rememberNavController()
|
||||
NavHost(
|
||||
|
@ -121,6 +126,7 @@ class MainActivity : AppCompatActivity(), ActivityCompat.OnRequestPermissionsRes
|
|||
MainScreen(
|
||||
navController = navController,
|
||||
viewModel = viewModel,
|
||||
enableWideLayout = windowSizeClass.widthSizeClass == WindowWidthSizeClass.Expanded,
|
||||
enableAutosave = autosaveEnabled,
|
||||
enableReadability = readabilityEnabled,
|
||||
darkMode = darkModePreference
|
||||
|
|
|
@ -4,15 +4,19 @@ import android.content.Intent
|
|||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
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.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
|
@ -43,6 +47,9 @@ import androidx.compose.material3.TextField
|
|||
import androidx.compose.material3.TextFieldDefaults
|
||||
import androidx.compose.material3.TopAppBar
|
||||
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.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
|
@ -72,16 +79,17 @@ import com.wbrawner.simplemarkdown.MarkdownViewModel
|
|||
import com.wbrawner.simplemarkdown.R
|
||||
import com.wbrawner.simplemarkdown.Route
|
||||
import com.wbrawner.simplemarkdown.model.Readability
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import java.net.URI
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class)
|
||||
@Composable
|
||||
fun MainScreen(
|
||||
navController: NavController,
|
||||
viewModel: MarkdownViewModel,
|
||||
enableWideLayout: Boolean,
|
||||
enableAutosave: Boolean,
|
||||
enableReadability: Boolean,
|
||||
darkMode: String,
|
||||
|
@ -204,93 +212,98 @@ fun MainScreen(
|
|||
menuExpanded = false
|
||||
saveFileLauncher.launch(fileName)
|
||||
})
|
||||
DropdownMenuItem(text = {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text("Lock Swiping")
|
||||
Checkbox(
|
||||
checked = lockSwiping,
|
||||
onCheckedChange = { lockSwiping = !lockSwiping })
|
||||
}
|
||||
}, onClick = {
|
||||
lockSwiping = !lockSwiping
|
||||
menuExpanded = false
|
||||
})
|
||||
if (!enableWideLayout) {
|
||||
DropdownMenuItem(text = {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text("Lock Swiping")
|
||||
Checkbox(
|
||||
checked = lockSwiping,
|
||||
onCheckedChange = { lockSwiping = !lockSwiping })
|
||||
}
|
||||
}, onClick = {
|
||||
lockSwiping = !lockSwiping
|
||||
menuExpanded = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}) { paddingValues ->
|
||||
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) } })
|
||||
val markdown by viewModel.markdown.collectAsState()
|
||||
val (textFieldValue, setTextFieldValue) = remember(clearText) {
|
||||
val annotatedMarkdown = if (enableReadability) {
|
||||
markdown.annotateReadability()
|
||||
} else {
|
||||
AnnotatedString(markdown)
|
||||
}
|
||||
HorizontalPager(
|
||||
modifier = Modifier.weight(1f), state = pagerState,
|
||||
userScrollEnabled = !lockSwiping
|
||||
) { page ->
|
||||
val markdown by viewModel.markdown.collectAsState()
|
||||
val keyboardController = LocalSoftwareKeyboardController.current
|
||||
LaunchedEffect(page) {
|
||||
when (page) {
|
||||
0 -> keyboardController?.show()
|
||||
else -> keyboardController?.hide()
|
||||
}
|
||||
}
|
||||
var textFieldValue by remember(clearText) {
|
||||
val annotatedMarkdown = if (enableReadability) {
|
||||
markdown.annotateReadability()
|
||||
} else {
|
||||
AnnotatedString(markdown)
|
||||
}
|
||||
mutableStateOf(TextFieldValue(annotatedMarkdown))
|
||||
}
|
||||
if (page == 0) {
|
||||
TextField(
|
||||
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 {
|
||||
MarkdownPreview(modifier = Modifier.fillMaxSize(), markdown, darkMode)
|
||||
}
|
||||
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(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.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) {
|
||||
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.fillMaxSize(1f), state = pagerState,
|
||||
userScrollEnabled = !lockSwiping
|
||||
) { page ->
|
||||
val keyboardController = LocalSoftwareKeyboardController.current
|
||||
LaunchedEffect(page) {
|
||||
when (page) {
|
||||
0 -> keyboardController?.show()
|
||||
else -> keyboardController?.hide()
|
||||
}
|
||||
}
|
||||
if (page == 0) {
|
||||
MarkdownTextField(modifier = Modifier.fillMaxSize(), textFieldValue, setTextFieldAndViewModelValues)
|
||||
} else {
|
||||
MarkdownPreview(modifier = Modifier.fillMaxSize(), markdown, darkMode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -389,3 +402,41 @@ fun MarkdownTopAppBar(
|
|||
}
|
||||
}, 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