diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 60f0d2d..12aefef 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -16,8 +16,8 @@ android { applicationId = "com.yangdai.opennote" minSdk = 29 targetSdk = 34 - versionCode = 123 - versionName = "1.2.3" + versionCode = 124 + versionName = "1.2.4" resourceConfigurations += listOf("en", "zh") testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" diff --git a/app/release/app-release.apk b/app/release/app-release.apk index 4c9f270..9bf779a 100644 Binary files a/app/release/app-release.apk and b/app/release/app-release.apk differ diff --git a/app/src/main/java/com/yangdai/opennote/presentation/component/DrawerContent.kt b/app/src/main/java/com/yangdai/opennote/presentation/component/DrawerContent.kt index 4f66edf..c8f59a9 100644 --- a/app/src/main/java/com/yangdai/opennote/presentation/component/DrawerContent.kt +++ b/app/src/main/java/com/yangdai/opennote/presentation/component/DrawerContent.kt @@ -34,14 +34,15 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.yangdai.opennote.R import com.yangdai.opennote.data.local.entity.FolderEntity -import com.yangdai.opennote.presentation.navigation.Route +import com.yangdai.opennote.presentation.navigation.Folders +import com.yangdai.opennote.presentation.navigation.Settings import kotlinx.collections.immutable.ImmutableList @Composable fun DrawerContent( folderList: ImmutableList, selectedDrawerIndex: Int, - navigateTo: (String) -> Unit, + navigateTo: (Any) -> Unit, onClick: (Int, FolderEntity) -> Unit ) { @@ -60,7 +61,7 @@ fun DrawerContent( ) { IconButton( modifier = Modifier.padding(12.dp), - onClick = { navigateTo(Route.SETTINGS) } + onClick = { navigateTo(Settings) } ) { Icon( imageVector = Icons.Outlined.Settings, @@ -116,7 +117,7 @@ fun DrawerContent( modifier = Modifier .fillMaxWidth() .padding(NavigationDrawerItemDefaults.ItemPadding), - onClick = { navigateTo(Route.FOLDERS) }) { + onClick = { navigateTo(Folders) }) { Text(text = stringResource(R.string.manage_folders), textAlign = TextAlign.Center) } } diff --git a/app/src/main/java/com/yangdai/opennote/presentation/component/LinkDialog.kt b/app/src/main/java/com/yangdai/opennote/presentation/component/LinkDialog.kt index e3fb1dd..0625f65 100644 --- a/app/src/main/java/com/yangdai/opennote/presentation/component/LinkDialog.kt +++ b/app/src/main/java/com/yangdai/opennote/presentation/component/LinkDialog.kt @@ -17,13 +17,12 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.yangdai.opennote.R -import com.yangdai.opennote.presentation.state.LinkState @Composable fun LinkDialog( onDismissRequest: () -> Unit, - onConfirm: (LinkState) -> Unit + onConfirm: (name: String, uri: String) -> Unit ) { var name by remember { mutableStateOf("") } @@ -82,7 +81,7 @@ fun LinkDialog( ) { uri = "https://$uri" } - onConfirm(LinkState(name, uri)) + onConfirm(name, uri) onDismissRequest() } } @@ -101,5 +100,6 @@ fun LinkDialog( @Composable @Preview fun LinkDialogPreview() { - LinkDialog(onDismissRequest = {}, onConfirm = {}) + LinkDialog(onDismissRequest = {}) { _, _ -> + } } diff --git a/app/src/main/java/com/yangdai/opennote/presentation/component/MainContent.kt b/app/src/main/java/com/yangdai/opennote/presentation/component/MainContent.kt index f42eb0a..2d76064 100644 --- a/app/src/main/java/com/yangdai/opennote/presentation/component/MainContent.kt +++ b/app/src/main/java/com/yangdai/opennote/presentation/component/MainContent.kt @@ -64,7 +64,7 @@ import com.yangdai.opennote.R import com.yangdai.opennote.data.local.entity.FolderEntity import com.yangdai.opennote.data.local.entity.NoteEntity import com.yangdai.opennote.presentation.event.ListEvent -import com.yangdai.opennote.presentation.navigation.Route +import com.yangdai.opennote.presentation.navigation.Note import com.yangdai.opennote.presentation.state.DataActionState import com.yangdai.opennote.presentation.state.DataState import kotlinx.collections.immutable.ImmutableList @@ -84,7 +84,7 @@ fun MainContent( isLargeScreen: Boolean, dataState: DataState, folderList: ImmutableList, - navigateTo: (String) -> Unit, + navigateTo: (Any) -> Unit, initializeNoteSelection: () -> Unit, onSearchBarActivationChange: (Boolean) -> Unit, onAllNotesSelectionChange: (Boolean) -> Unit, @@ -319,7 +319,7 @@ fun MainContent( FloatingActionButton( onClick = { onListEvent(ListEvent.AddNote) - navigateTo(Route.NOTE) + navigateTo(Note) } ) { Icon(imageVector = Icons.Outlined.Add, contentDescription = "Add") diff --git a/app/src/main/java/com/yangdai/opennote/presentation/component/MarkdownText.kt b/app/src/main/java/com/yangdai/opennote/presentation/component/MarkdownText.kt index 616dd57..1a10375 100644 --- a/app/src/main/java/com/yangdai/opennote/presentation/component/MarkdownText.kt +++ b/app/src/main/java/com/yangdai/opennote/presentation/component/MarkdownText.kt @@ -25,16 +25,18 @@ fun MarkdownText(html: String) { val textColor = MaterialTheme.colorScheme.onSurface.toArgb() val codeBackgroundColor = MaterialTheme.colorScheme.surfaceVariant.toArgb() - val preCodeBackgroundColor = MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp).toArgb() - val quoteBackgroundColor = MaterialTheme.colorScheme.surfaceDim.toArgb() + val preBackgroundColor = MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp).toArgb() + val quoteBackgroundColor = MaterialTheme.colorScheme.secondaryContainer.toArgb() + val borderColor = MaterialTheme.colorScheme.outline.toArgb() + val hexTextColor = remember { String.format("#%06X", 0xFFFFFF and textColor) } val hexCodeBackgroundColor = remember { String.format("#%06X", 0xFFFFFF and codeBackgroundColor) } - val hexPreCodeBackgroundColor = remember { - String.format("#%06X", 0xFFFFFF and preCodeBackgroundColor) + val hexPreBackgroundColor = remember { + String.format("#%06X", 0xFFFFFF and preBackgroundColor) } val hexQuoteBackgroundColor = remember { String.format("#%06X", 0xFFFFFF and quoteBackgroundColor) @@ -42,6 +44,9 @@ fun MarkdownText(html: String) { val hexLinkColor = remember { String.format("#%06X", 0xFFFFFF and linkColor.toArgb()) } + val hexBorderColor = remember { + String.format("#%06X", 0xFFFFFF and borderColor) + } val customTabsIntent = remember { CustomTabsIntent.Builder() @@ -103,13 +108,17 @@ fun MarkdownText(html: String) { mermaid.initialize({ startOnLoad: true }); diff --git a/app/src/main/java/com/yangdai/opennote/presentation/component/ModalNavigationScreen.kt b/app/src/main/java/com/yangdai/opennote/presentation/component/ModalNavigationScreen.kt index 8e9578f..86b6686 100644 --- a/app/src/main/java/com/yangdai/opennote/presentation/component/ModalNavigationScreen.kt +++ b/app/src/main/java/com/yangdai/opennote/presentation/component/ModalNavigationScreen.kt @@ -16,7 +16,7 @@ fun ModalNavigationScreen( folderList: ImmutableList, selectedDrawerIndex: Int, content: @Composable () -> Unit, - navigateTo: (String) -> Unit, + navigateTo: (Any) -> Unit, selectDrawer: (Int, FolderEntity)-> Unit ) = ModalNavigationDrawer( modifier = Modifier.fillMaxSize(), diff --git a/app/src/main/java/com/yangdai/opennote/presentation/component/NoteEditTextField.kt b/app/src/main/java/com/yangdai/opennote/presentation/component/NoteEditTextField.kt index 3433972..ba88936 100644 --- a/app/src/main/java/com/yangdai/opennote/presentation/component/NoteEditTextField.kt +++ b/app/src/main/java/com/yangdai/opennote/presentation/component/NoteEditTextField.kt @@ -24,7 +24,7 @@ import androidx.compose.ui.input.key.onPreviewKeyEvent import androidx.compose.ui.input.key.type import androidx.compose.ui.res.stringResource import com.yangdai.opennote.R -import com.yangdai.opennote.presentation.util.add +import com.yangdai.opennote.presentation.util.addInNewLine import com.yangdai.opennote.presentation.util.bold import com.yangdai.opennote.presentation.util.inlineCode import com.yangdai.opennote.presentation.util.inlineFunction @@ -49,7 +49,7 @@ fun NoteEditTextField( transferableContent.consume { item: ClipData.Item -> val hasText = item.text.isNotEmpty() if (hasText) { - state.edit { add(item.text.toString()) } + state.edit { addInNewLine(item.text.toString()) } } hasText } diff --git a/app/src/main/java/com/yangdai/opennote/presentation/component/NoteEditorRow.kt b/app/src/main/java/com/yangdai/opennote/presentation/component/NoteEditorRow.kt index e060b62..69b6088 100644 --- a/app/src/main/java/com/yangdai/opennote/presentation/component/NoteEditorRow.kt +++ b/app/src/main/java/com/yangdai/opennote/presentation/component/NoteEditorRow.kt @@ -1,8 +1,11 @@ package com.yangdai.opennote.presentation.component +import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBarsPadding @@ -13,6 +16,8 @@ import androidx.compose.material.icons.automirrored.outlined.Undo import androidx.compose.material.icons.outlined.AddChart import androidx.compose.material.icons.outlined.CheckBox import androidx.compose.material.icons.outlined.Code +import androidx.compose.material.icons.outlined.DataArray +import androidx.compose.material.icons.outlined.DataObject import androidx.compose.material.icons.outlined.DocumentScanner import androidx.compose.material.icons.outlined.FormatBold import androidx.compose.material.icons.outlined.FormatItalic @@ -20,13 +25,19 @@ import androidx.compose.material.icons.outlined.FormatPaint import androidx.compose.material.icons.outlined.FormatQuote import androidx.compose.material.icons.outlined.FormatStrikethrough import androidx.compose.material.icons.outlined.FormatUnderlined +import androidx.compose.material.icons.outlined.HorizontalRule import androidx.compose.material.icons.outlined.Link +import androidx.compose.material.icons.outlined.TableChart import androidx.compose.material.icons.outlined.Title import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource @@ -40,11 +51,14 @@ fun NoteEditorRow( canUndo: Boolean, canRedo: Boolean, onEdit: (String) -> Unit, + onTableButtonClick: () -> Unit, onScanButtonClick: () -> Unit, onTaskButtonClick: () -> Unit, onLinkButtonClick: () -> Unit ) { + var isExpanded by rememberSaveable { mutableStateOf(false) } + Column( modifier = Modifier .fillMaxWidth() @@ -86,13 +100,58 @@ fun NoteEditorRow( if (isMarkdown) { - IconButton(onClick = { onEdit(Constants.Editor.TITLE) }) { + IconButton(onClick = { isExpanded = !isExpanded }) { Icon( imageVector = Icons.Outlined.Title, - contentDescription = "Title" + contentDescription = "Heading Level" ) } + AnimatedVisibility(visible = isExpanded) { + Row( + modifier = Modifier.fillMaxHeight(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Start + ) { + IconButton(onClick = { onEdit(Constants.Editor.H1) }) { + Icon( + painter = painterResource(id = R.drawable.format_h1), + contentDescription = "H1" + ) + } + IconButton(onClick = { onEdit(Constants.Editor.H2) }) { + Icon( + painter = painterResource(id = R.drawable.format_h2), + contentDescription = "H2" + ) + } + IconButton(onClick = { onEdit(Constants.Editor.H3) }) { + Icon( + painter = painterResource(id = R.drawable.format_h3), + contentDescription = "H3" + ) + } + IconButton(onClick = { onEdit(Constants.Editor.H4) }) { + Icon( + painter = painterResource(id = R.drawable.format_h4), + contentDescription = "H4" + ) + } + IconButton(onClick = { onEdit(Constants.Editor.H5) }) { + Icon( + painter = painterResource(id = R.drawable.format_h5), + contentDescription = "H5" + ) + } + IconButton(onClick = { onEdit(Constants.Editor.H6) }) { + Icon( + painter = painterResource(id = R.drawable.format_h6), + contentDescription = "H6" + ) + } + } + } + IconButton(onClick = { onEdit(Constants.Editor.BOLD) }) { Icon( imageVector = Icons.Outlined.FormatBold, @@ -135,6 +194,20 @@ fun NoteEditorRow( ) } + IconButton(onClick = { onEdit(Constants.Editor.INLINE_BRACKETS) }) { + Icon( + imageVector = Icons.Outlined.DataArray, + contentDescription = "Brackets" + ) + } + + IconButton(onClick = { onEdit(Constants.Editor.INLINE_BRACES) }) { + Icon( + imageVector = Icons.Outlined.DataObject, + contentDescription = "Braces" + ) + } + IconButton(onClick = { onEdit(Constants.Editor.INLINE_FUNC) }) { Icon( painter = painterResource(id = R.drawable.function), @@ -149,6 +222,20 @@ fun NoteEditorRow( ) } + IconButton(onClick = { onEdit(Constants.Editor.RULE) }) { + Icon( + imageVector = Icons.Outlined.HorizontalRule, + contentDescription = "Horizontal Rule" + ) + } + + IconButton(onClick = onTableButtonClick) { + Icon( + imageVector = Icons.Outlined.TableChart, + contentDescription = "Table" + ) + } + IconButton(onClick = { onEdit(Constants.Editor.DIAGRAM) }) { Icon( imageVector = Icons.Outlined.AddChart, diff --git a/app/src/main/java/com/yangdai/opennote/presentation/component/PermanentNavigationScreen.kt b/app/src/main/java/com/yangdai/opennote/presentation/component/PermanentNavigationScreen.kt index 9f408af..b051c92 100644 --- a/app/src/main/java/com/yangdai/opennote/presentation/component/PermanentNavigationScreen.kt +++ b/app/src/main/java/com/yangdai/opennote/presentation/component/PermanentNavigationScreen.kt @@ -13,7 +13,7 @@ fun PermanentNavigationScreen( folderList: ImmutableList, selectedDrawerIndex: Int, content: @Composable () -> Unit, - navigateTo: (String) -> Unit, + navigateTo: (Any) -> Unit, selectDrawer: (Int, FolderEntity) -> Unit ) = PermanentNavigationDrawer( modifier = Modifier.fillMaxSize(), diff --git a/app/src/main/java/com/yangdai/opennote/presentation/component/TableDialog.kt b/app/src/main/java/com/yangdai/opennote/presentation/component/TableDialog.kt new file mode 100644 index 0000000..1dae8a3 --- /dev/null +++ b/app/src/main/java/com/yangdai/opennote/presentation/component/TableDialog.kt @@ -0,0 +1,110 @@ +package com.yangdai.opennote.presentation.component + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.OutlinedTextField +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.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.yangdai.opennote.R + +@Composable +fun TableDialog( + onDismissRequest: () -> Unit, + onConfirm: (row: Int, column: Int) -> Unit +) { + + var row by remember { mutableStateOf("") } + var column by remember { mutableStateOf("") } + + var rowError by remember { mutableStateOf(false) } + var columnError by remember { mutableStateOf(false) } + + AlertDialog( + title = { + Text(text = stringResource(R.string.table)) + }, + text = { + Column { + OutlinedTextField( + value = row, + onValueChange = { + row = if (it.length > 3) it.substring(0, 3) else it + rowError = !row.all { char -> char.isDigit() } + }, + isError = rowError, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.NumberPassword, + imeAction = ImeAction.Next + ), + singleLine = true, + placeholder = { Text(text = stringResource(R.string.row)) }) + + Spacer(modifier = Modifier.height(8.dp)) + + OutlinedTextField( + value = column, + onValueChange = { + column = if (it.length > 3) it.substring(0, 3) else it + columnError = !column.all { char -> char.isDigit() } + }, + isError = columnError, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.NumberPassword, + imeAction = ImeAction.Done + ), + singleLine = true, + placeholder = { Text(text = stringResource(R.string.column)) } + ) + } + }, + onDismissRequest = { + onDismissRequest() + }, + confirmButton = { + TextButton( + onClick = { + if (row.isBlank()) { + rowError = true + } + if (column.isBlank()) { + columnError = true + } + if (!rowError && !columnError) { + row = row.trim() + column = column.trim() + onConfirm(row.toInt(), column.toInt()) + onDismissRequest() + } + } + ) { + Text(stringResource(id = android.R.string.ok)) + } + }, + dismissButton = { + TextButton(onClick = { onDismissRequest() }) { + Text(text = stringResource(id = android.R.string.cancel)) + } + } + ) +} + +@Preview +@Composable +fun TableDialogPreview() { + TableDialog({}) { _, _ -> + } +} diff --git a/app/src/main/java/com/yangdai/opennote/presentation/component/TaskDialog.kt b/app/src/main/java/com/yangdai/opennote/presentation/component/TaskDialog.kt index 7e87dfb..9e8a02a 100644 --- a/app/src/main/java/com/yangdai/opennote/presentation/component/TaskDialog.kt +++ b/app/src/main/java/com/yangdai/opennote/presentation/component/TaskDialog.kt @@ -1,64 +1,105 @@ package com.yangdai.opennote.presentation.component +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.height +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.material.icons.Icons +import androidx.compose.material.icons.outlined.Add +import androidx.compose.material.icons.outlined.RemoveCircleOutline import androidx.compose.material3.AlertDialog import androidx.compose.material3.Checkbox +import androidx.compose.material3.FilledTonalIconButton +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.OutlinedTextField 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.mutableStateListOf import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.yangdai.opennote.R -import com.yangdai.opennote.presentation.state.TaskState + +data class TaskItem( + var task: String = "", + var checked: Boolean = false +) @Composable fun TaskDialog( onDismissRequest: () -> Unit, - onConfirm: (TaskState) -> Unit + onConfirm: (taskList: List) -> Unit ) { - var task by remember { - mutableStateOf("") - } - - var checked by remember { - mutableStateOf(false) + val taskList = remember { + mutableStateListOf(TaskItem("", false)) } - var taskError by remember { mutableStateOf(false) } - AlertDialog( title = { - Text(text = stringResource(R.string.task)) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text(text = stringResource(R.string.task)) + + FilledTonalIconButton(onClick = { + taskList.add(TaskItem("", false)) + }) { + Icon( + imageVector = Icons.Outlined.Add, + contentDescription = "Add Task" + ) + } + } }, text = { - Column { + Column( + modifier = Modifier + .fillMaxWidth() + .verticalScroll(rememberScrollState()), + horizontalAlignment = Alignment.CenterHorizontally + ) { - OutlinedTextField( - value = task, - onValueChange = { - task = it - taskError = it.isBlank() - }, - singleLine = true, - isError = taskError - ) + taskList.forEachIndexed { index, taskState -> + Row( + modifier = Modifier + .fillMaxWidth() + .padding(top = 8.dp), + verticalAlignment = Alignment.CenterVertically + ) { - Spacer(modifier = Modifier.height(8.dp)) + Checkbox( + checked = taskState.checked, + onCheckedChange = { + taskList[index] = taskList[index].copy(checked = it) + } + ) - Row(verticalAlignment = Alignment.CenterVertically) { - Checkbox(checked = checked, onCheckedChange = { checked = it }) - Text(text = stringResource(R.string.completed)) + OutlinedTextField( + modifier = Modifier.weight(1f), + value = taskState.task, + onValueChange = { + taskList[index] = taskList[index].copy(task = it) + }, + singleLine = true + ) + + IconButton(onClick = { taskList.removeAt(index) }) { + Icon( + Icons.Outlined.RemoveCircleOutline, + contentDescription = "Remove Task" + ) + } + } } } }, @@ -68,15 +109,8 @@ fun TaskDialog( confirmButton = { TextButton( onClick = { - if (task.isBlank()) { - taskError = true - } - - if (!taskError) { - task = task.trim() - onConfirm(TaskState(task, checked)) - onDismissRequest() - } + onConfirm(taskList) + onDismissRequest() } ) { Text(stringResource(id = android.R.string.ok)) diff --git a/app/src/main/java/com/yangdai/opennote/presentation/navigation/AnimatedNavHost.kt b/app/src/main/java/com/yangdai/opennote/presentation/navigation/AnimatedNavHost.kt index 713e20d..613b01d 100644 --- a/app/src/main/java/com/yangdai/opennote/presentation/navigation/AnimatedNavHost.kt +++ b/app/src/main/java/com/yangdai/opennote/presentation/navigation/AnimatedNavHost.kt @@ -76,7 +76,7 @@ fun AnimatedNavHost( ) = NavHost( modifier = modifier, navController = navController, - startDestination = Route.MAIN, + startDestination = Home, enterTransition = { sharedAxisXIn(initialOffsetX = { (it * INITIAL_OFFSET_FACTOR).toInt() }) }, @@ -91,14 +91,13 @@ fun AnimatedNavHost( }, ) { - composable(route = Route.MAIN) { + composable { MainScreen(isLargeScreen = isLargeScreen) { route -> navController.navigate(route) } } - composable( - route = Route.NOTE, + composable( deepLinks = listOf( navDeepLink { action = Intent.ACTION_SEND @@ -116,23 +115,23 @@ fun AnimatedNavHost( sharedText = sharedText, scannedText = scannedText, navigateUp = { navController.popBackStack() } - ) { navController.navigate(Route.CAMERAX) } + ) { navController.navigate(CameraX) } } - composable(route = Route.FOLDERS) { + composable { FolderScreen { navController.popBackStack() } } - composable(route = Route.CAMERAX) { + composable { CameraXScreen(onCloseClick = { navController.popBackStack() }) { navController.previousBackStackEntry?.savedStateHandle?.set("scannedText", it) navController.popBackStack() } } - composable(route = Route.SETTINGS) { + composable { SettingsScreen { navController.popBackStack() } diff --git a/app/src/main/java/com/yangdai/opennote/presentation/navigation/Route.kt b/app/src/main/java/com/yangdai/opennote/presentation/navigation/Route.kt index 9e0ddf9..350419e 100644 --- a/app/src/main/java/com/yangdai/opennote/presentation/navigation/Route.kt +++ b/app/src/main/java/com/yangdai/opennote/presentation/navigation/Route.kt @@ -1,9 +1,18 @@ package com.yangdai.opennote.presentation.navigation -object Route { - const val MAIN = "main" - const val NOTE = "note" - const val SETTINGS = "settings" - const val FOLDERS = "folders" - const val CAMERAX = "camerax" -} \ No newline at end of file +import kotlinx.serialization.Serializable + +@Serializable +object Home + +@Serializable +object Note + +@Serializable +object Settings + +@Serializable +object Folders + +@Serializable +object CameraX \ No newline at end of file diff --git a/app/src/main/java/com/yangdai/opennote/presentation/screen/MainScreen.kt b/app/src/main/java/com/yangdai/opennote/presentation/screen/MainScreen.kt index b058b60..043a8b4 100644 --- a/app/src/main/java/com/yangdai/opennote/presentation/screen/MainScreen.kt +++ b/app/src/main/java/com/yangdai/opennote/presentation/screen/MainScreen.kt @@ -20,7 +20,6 @@ import androidx.compose.ui.platform.LocalContext import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.yangdai.opennote.MainActivity -import com.yangdai.opennote.presentation.navigation.Route import com.yangdai.opennote.data.local.entity.FolderEntity import com.yangdai.opennote.data.local.entity.NoteEntity import com.yangdai.opennote.presentation.event.ListEvent @@ -28,6 +27,7 @@ import com.yangdai.opennote.presentation.component.MainContent import com.yangdai.opennote.presentation.component.ModalNavigationScreen import com.yangdai.opennote.presentation.component.PermanentNavigationScreen import com.yangdai.opennote.presentation.event.DatabaseEvent +import com.yangdai.opennote.presentation.navigation.Note import com.yangdai.opennote.presentation.viewmodel.SharedViewModel import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.launch @@ -36,7 +36,7 @@ import kotlinx.coroutines.launch fun MainScreen( sharedViewModel: SharedViewModel = hiltViewModel(LocalContext.current as MainActivity), isLargeScreen: Boolean, - navigateTo: (String) -> Unit + navigateTo: (Any) -> Unit ) { val context = LocalContext.current val coroutineScope = rememberCoroutineScope() @@ -157,7 +157,7 @@ fun MainScreen( } else { if (selectedDrawerIndex != 1) { sharedViewModel.onListEvent(ListEvent.OpenNote(it)) - navigateTo(Route.NOTE) + navigateTo(Note) } else { Unit } diff --git a/app/src/main/java/com/yangdai/opennote/presentation/screen/NoteScreen.kt b/app/src/main/java/com/yangdai/opennote/presentation/screen/NoteScreen.kt index 3300953..871a103 100644 --- a/app/src/main/java/com/yangdai/opennote/presentation/screen/NoteScreen.kt +++ b/app/src/main/java/com/yangdai/opennote/presentation/screen/NoteScreen.kt @@ -79,6 +79,7 @@ import com.yangdai.opennote.presentation.component.LinkDialog import com.yangdai.opennote.presentation.component.NoteEditTextField import com.yangdai.opennote.presentation.component.NoteEditorRow import com.yangdai.opennote.presentation.component.ProgressDialog +import com.yangdai.opennote.presentation.component.TableDialog import com.yangdai.opennote.presentation.component.TaskDialog import com.yangdai.opennote.presentation.event.DatabaseEvent import com.yangdai.opennote.presentation.event.NoteEvent @@ -121,25 +122,20 @@ fun NoteScreen( mutableStateOf(false) } + // Whether to show the table dialog + var showTableDialog by rememberSaveable { mutableStateOf(false) } + // Whether to show the link dialog - var showLinkDialog by rememberSaveable { - mutableStateOf(false) - } + var showLinkDialog by rememberSaveable { mutableStateOf(false) } // Whether to show the task dialog - var showTaskDialog by rememberSaveable { - mutableStateOf(false) - } + var showTaskDialog by rememberSaveable { mutableStateOf(false) } // Whether to show the export dialog - var showExportDialog by rememberSaveable { - mutableStateOf(false) - } + var showExportDialog by rememberSaveable { mutableStateOf(false) } // Whether to show the overflow menu - var showMenu by rememberSaveable { - mutableStateOf(false) - } + var showMenu by rememberSaveable { mutableStateOf(false) } // Folder name, default to "All Notes", or the name of the current folder the note is in var folderName by rememberSaveable { mutableStateOf("") } @@ -356,6 +352,7 @@ fun NoteScreen( canRedo = sharedViewModel.canRedo(), canUndo = sharedViewModel.canUndo(), onEdit = { sharedViewModel.onNoteEvent(NoteEvent.Edit(it)) }, + onTableButtonClick = { showTableDialog = true }, onScanButtonClick = onScanTextClick, onTaskButtonClick = { showTaskDialog = true }, onLinkButtonClick = { showLinkDialog = true }) @@ -504,15 +501,21 @@ fun NoteScreen( ) } + if (showTableDialog) { + TableDialog(onDismissRequest = { showTableDialog = false }) { row, column -> + sharedViewModel.addTable(row, column) + } + } + if (showTaskDialog) { TaskDialog(onDismissRequest = { showTaskDialog = false }) { - sharedViewModel.addTask(it.task, it.checked) + sharedViewModel.addTasks(it) } } if (showLinkDialog) { - LinkDialog(onDismissRequest = { showLinkDialog = false }) { - val insertText = "[${it.title}](${it.uri})" + LinkDialog(onDismissRequest = { showLinkDialog = false }) { name, uri -> + val insertText = "[${name}](${uri})" sharedViewModel.addLink(insertText) } } diff --git a/app/src/main/java/com/yangdai/opennote/presentation/state/LinkState.kt b/app/src/main/java/com/yangdai/opennote/presentation/state/LinkState.kt deleted file mode 100644 index 0d7ff70..0000000 --- a/app/src/main/java/com/yangdai/opennote/presentation/state/LinkState.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.yangdai.opennote.presentation.state - -data class LinkState( - val title: String = "", - val uri: String = "" -) diff --git a/app/src/main/java/com/yangdai/opennote/presentation/state/TaskState.kt b/app/src/main/java/com/yangdai/opennote/presentation/state/TaskState.kt deleted file mode 100644 index eb9ba8e..0000000 --- a/app/src/main/java/com/yangdai/opennote/presentation/state/TaskState.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.yangdai.opennote.presentation.state - -data class TaskState( - val task: String = "", - val checked: Boolean = false -) diff --git a/app/src/main/java/com/yangdai/opennote/presentation/util/Constants.kt b/app/src/main/java/com/yangdai/opennote/presentation/util/Constants.kt index 92f198e..e959fda 100644 --- a/app/src/main/java/com/yangdai/opennote/presentation/util/Constants.kt +++ b/app/src/main/java/com/yangdai/opennote/presentation/util/Constants.kt @@ -26,15 +26,27 @@ object Constants { object Editor { const val UNDO = "undo" const val REDO = "redo" - const val TITLE = "title" + + const val H1 = "h1" + const val H2 = "h2" + const val H3 = "h3" + const val H4 = "h4" + const val H5 = "h5" + const val H6 = "h6" + const val BOLD = "bold" const val ITALIC = "italic" const val UNDERLINE = "underline" const val STRIKETHROUGH = "strikethrough" const val MARK = "mark" + const val INLINE_CODE = "inlineCode" + const val INLINE_BRACKETS = "inlineBrackets" + const val INLINE_BRACES = "inlineBraces" const val INLINE_FUNC = "inlineFunction" + const val QUOTE = "quote" + const val RULE = "rule" const val DIAGRAM = "diagram" } diff --git a/app/src/main/java/com/yangdai/opennote/presentation/util/TextFieldMutators.kt b/app/src/main/java/com/yangdai/opennote/presentation/util/TextFieldMutators.kt index f3bcf8c..368a544 100644 --- a/app/src/main/java/com/yangdai/opennote/presentation/util/TextFieldMutators.kt +++ b/app/src/main/java/com/yangdai/opennote/presentation/util/TextFieldMutators.kt @@ -3,7 +3,7 @@ package com.yangdai.opennote.presentation.util import androidx.compose.foundation.text.input.TextFieldBuffer import androidx.compose.ui.text.TextRange -fun TextFieldBuffer.inlineWrap( +private fun TextFieldBuffer.inlineWrap( startWrappedString: String, endWrappedString: String = startWrappedString ) { @@ -30,7 +30,9 @@ fun TextFieldBuffer.strikeThrough() = inlineWrap("~~") fun TextFieldBuffer.mark() = inlineWrap("", "") -fun TextFieldBuffer.diagram() = inlineWrap("
", "\n
") +fun TextFieldBuffer.inlineBrackets() = inlineWrap("[", "]") + +fun TextFieldBuffer.inlineBraces() = inlineWrap("{", "}") fun TextFieldBuffer.inlineCode() = inlineWrap("`") @@ -53,17 +55,54 @@ fun TextFieldBuffer.quote() { ) } -fun TextFieldBuffer.add(str: String) { +private fun TextFieldBuffer.add(str: String) { val initialSelection = selection replace(initialSelection.min, initialSelection.max, str) } fun TextFieldBuffer.addLink(link: String) = add(link) +fun TextFieldBuffer.addInNewLine(str: String) { + val text = toString() + if (selection.min != 0 && text[selection.min - 1] != '\n') { + // 如果不是换行符,那么就先添加一个换行符 + add("\n") + } + add(str) +} + +fun TextFieldBuffer.addMermaid() { + addInNewLine("
\n
") +} + +fun TextFieldBuffer.addHeader(level: Int) { + addInNewLine("#".repeat(level) + " ") +} + +fun TextFieldBuffer.addRule() { + addInNewLine("\n") + add("------") + add("\n") + add("\n") +} + fun TextFieldBuffer.addTask(task: String, checked: Boolean) { if (checked) { - add("- [x] $task") + addInNewLine("- [x] $task") } else { - add("- [ ] $task") + addInNewLine("- [ ] $task") } } + +fun TextFieldBuffer.addTable(row: Int, column: Int) = addInNewLine( + buildString { + append("|") + repeat(column) { append(" HEADER |") } + append("\n|") + repeat(column) { append(" :-----------: |") } + repeat(row) { + append("\n|") + repeat(column) { append(" Element |") } + } + } +) \ No newline at end of file diff --git a/app/src/main/java/com/yangdai/opennote/presentation/viewmodel/SharedViewModel.kt b/app/src/main/java/com/yangdai/opennote/presentation/viewmodel/SharedViewModel.kt index 2636ed7..7923a5c 100644 --- a/app/src/main/java/com/yangdai/opennote/presentation/viewmodel/SharedViewModel.kt +++ b/app/src/main/java/com/yangdai/opennote/presentation/viewmodel/SharedViewModel.kt @@ -25,6 +25,7 @@ import com.yangdai.opennote.domain.repository.DataStoreRepository import com.yangdai.opennote.domain.usecase.NoteOrder import com.yangdai.opennote.domain.usecase.Operations import com.yangdai.opennote.domain.usecase.OrderType +import com.yangdai.opennote.presentation.component.TaskItem import com.yangdai.opennote.presentation.event.DatabaseEvent import com.yangdai.opennote.presentation.event.FolderEvent import com.yangdai.opennote.presentation.event.ListEvent @@ -34,11 +35,16 @@ import com.yangdai.opennote.presentation.state.DataActionState import com.yangdai.opennote.presentation.state.DataState import com.yangdai.opennote.presentation.state.NoteState import com.yangdai.opennote.presentation.util.Constants -import com.yangdai.opennote.presentation.util.add +import com.yangdai.opennote.presentation.util.addHeader +import com.yangdai.opennote.presentation.util.addInNewLine import com.yangdai.opennote.presentation.util.addLink +import com.yangdai.opennote.presentation.util.addRule +import com.yangdai.opennote.presentation.util.addTable import com.yangdai.opennote.presentation.util.addTask import com.yangdai.opennote.presentation.util.bold -import com.yangdai.opennote.presentation.util.diagram +import com.yangdai.opennote.presentation.util.addMermaid +import com.yangdai.opennote.presentation.util.inlineBraces +import com.yangdai.opennote.presentation.util.inlineBrackets import com.yangdai.opennote.presentation.util.inlineCode import com.yangdai.opennote.presentation.util.inlineFunction import com.yangdai.opennote.presentation.util.italic @@ -125,9 +131,7 @@ class SharedViewModel @Inject constructor( @OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class) val html = snapshotFlow { contentState.text } .debounce(100) - .mapLatest { - renderer.render(parser.parse(it.toString())) ?: "" - } + .mapLatest { renderer.render(parser.parse(it.toString())) } .flowOn(Dispatchers.IO) .stateIn( scope = viewModelScope, @@ -394,9 +398,14 @@ class SharedViewModel @Inject constructor( .launchIn(viewModelScope) } + fun addTable(row: Int, column: Int) { + contentState.edit { addTable(row, column) } + } - fun addTask(task: String, checked: Boolean) { - contentState.edit { addTask(task, checked) } + fun addTasks(taskList: List) { + taskList.forEach { + contentState.edit { addTask(it.task, it.checked) } + } } fun addLink(link: String) { @@ -404,7 +413,7 @@ class SharedViewModel @Inject constructor( } fun addText(text: String) { - contentState.edit { add(text) } + contentState.edit { addInNewLine(text) } } fun canUndo() = contentState.undoState.canUndo @@ -476,16 +485,24 @@ class SharedViewModel @Inject constructor( when (event.value) { Constants.Editor.UNDO -> contentState.undoState.undo() Constants.Editor.REDO -> contentState.undoState.redo() - Constants.Editor.TITLE -> contentState.edit { add("#") } + Constants.Editor.H1 -> contentState.edit { addHeader(1) } + Constants.Editor.H2 -> contentState.edit { addHeader(2) } + Constants.Editor.H3 -> contentState.edit { addHeader(3) } + Constants.Editor.H4 -> contentState.edit { addHeader(4) } + Constants.Editor.H5 -> contentState.edit { addHeader(5) } + Constants.Editor.H6 -> contentState.edit { addHeader(6) } Constants.Editor.BOLD -> contentState.edit { bold() } Constants.Editor.ITALIC -> contentState.edit { italic() } Constants.Editor.UNDERLINE -> contentState.edit { underline() } Constants.Editor.STRIKETHROUGH -> contentState.edit { strikeThrough() } Constants.Editor.MARK -> contentState.edit { mark() } Constants.Editor.INLINE_CODE -> contentState.edit { inlineCode() } + Constants.Editor.INLINE_BRACKETS -> contentState.edit { inlineBrackets() } + Constants.Editor.INLINE_BRACES -> contentState.edit { inlineBraces() } Constants.Editor.INLINE_FUNC -> contentState.edit { inlineFunction() } Constants.Editor.QUOTE -> contentState.edit { quote() } - Constants.Editor.DIAGRAM -> contentState.edit { diagram() } + Constants.Editor.RULE -> contentState.edit { addRule() } + Constants.Editor.DIAGRAM -> contentState.edit { addMermaid() } } } } @@ -585,7 +602,9 @@ class SharedViewModel @Inject constructor( } val fileName = noteEntity.title - val content = noteEntity.content + val content = + if (".html" != extension) noteEntity.content + else renderer.render(parser.parse(noteEntity.content)) val values = ContentValues().apply { put(MediaStore.Downloads.DISPLAY_NAME, "$fileName$extension") diff --git a/app/src/main/res/drawable/format_h1.xml b/app/src/main/res/drawable/format_h1.xml new file mode 100644 index 0000000..9ff69a2 --- /dev/null +++ b/app/src/main/res/drawable/format_h1.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/format_h2.xml b/app/src/main/res/drawable/format_h2.xml new file mode 100644 index 0000000..a37e9b4 --- /dev/null +++ b/app/src/main/res/drawable/format_h2.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/format_h3.xml b/app/src/main/res/drawable/format_h3.xml new file mode 100644 index 0000000..be5f7d2 --- /dev/null +++ b/app/src/main/res/drawable/format_h3.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/format_h4.xml b/app/src/main/res/drawable/format_h4.xml new file mode 100644 index 0000000..b51dc9c --- /dev/null +++ b/app/src/main/res/drawable/format_h4.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/format_h5.xml b/app/src/main/res/drawable/format_h5.xml new file mode 100644 index 0000000..9c0a107 --- /dev/null +++ b/app/src/main/res/drawable/format_h5.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/format_h6.xml b/app/src/main/res/drawable/format_h6.xml new file mode 100644 index 0000000..b026261 --- /dev/null +++ b/app/src/main/res/drawable/format_h6.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/function.xml b/app/src/main/res/drawable/function.xml index 31de86f..095c2e1 100644 --- a/app/src/main/res/drawable/function.xml +++ b/app/src/main/res/drawable/function.xml @@ -2,11 +2,11 @@ xmlns:tools="http://schemas.android.com/tools" android:width="24dp" android:height="24dp" + android:tint="?attr/colorControlNormal" android:viewportWidth="960" - android:viewportHeight="960" - android:tint="?attr/colorControlNormal"> - + android:viewportHeight="960"> + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml index 8d06990..20df5a0 100644 --- a/app/src/main/res/drawable/ic_launcher_foreground.xml +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -5,14 +5,15 @@ android:viewportWidth="1280" android:viewportHeight="720" tools:ignore="VectorRaster"> - - - - + + + + diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index 363f710..0188948 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -45,7 +45,6 @@ 重置 重置数据库会导致所有笔记和文件夹被彻底删除且不可恢复,请决定是否继续操作? 任务 - 已完成 富文本 用户指南 没有找到文字 @@ -69,4 +68,7 @@ 升序 降序 排序 + 表格 + 行数 + 列数 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d0501d5..0d229b4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -46,7 +46,6 @@ Reset Resetting the database will cause all notes and folders to be completely deleted and irrecoverable. Please decide whether to continue? Task - Completed RTF User guide No text found @@ -70,4 +69,7 @@ Ascending Descending Sort by + Table + Rows + Columns \ No newline at end of file